Compare commits

..

224 Commits

Author SHA1 Message Date
Dave Hrycyszyn 44673892a1 Starting work on claim delegator rewards button 2022-05-31 17:13:04 +01:00
Jędrzej Stuczyński 9f51c60bac Wallet simulate bandaid (#1296)
* Removed Add/Sub that somehow got brought back in a merge

* Created 'get_old_and_incorrect_hardcoded_fee' to make wallet as it did before

* Brought back all Operation variants just in case
2022-05-31 15:52:26 +01:00
Jędrzej Stuczyński 58b5389ed9 Consolidate validator-client coin (#1295)
* Created nymd internal coin

* Additional From implementations plus a constructor

* try_add

* Changed client API to use the new coin type

* CoinConverter trait

* Made wallet compilable with the recent changes

* Simplified the API by removing the generics in favour of explicit Coin type

* Fixed validator api

* Fixed up tests and clippy

* Refactored  missed coin-generic API methods

* changelog
2022-05-31 14:10:19 +01:00
Drazen Urch 2f4be6dedc Add claim reward to vesting and mixnet contract (#1292)
* Add claim reward to vesting contract

* Add compound and claim methods to nymd client

* Add TrackReward message

* CHANGELOG
2022-05-31 11:06:10 +02:00
Drazen Urch 189b83e769 Fix uptime integer division, change lambda and sigma from ticked to regular (#1284)
* Add more data to reward-estimate response

* Fix uptime integer division error

* typo

* Reify tuple response

* Fix uptime calculation

* Use lambda and sigma instead of ticked versions for delegator and operator rewards calculation

* Changelog
2022-05-31 11:03:28 +02:00
Jon Häggblad 83f80f094c wallet: tweak log text to avoid confusion 2022-05-31 09:06:57 +02:00
Jędrzej Stuczyński 2dfbd2f714 Removed rustfmt.toml file and adjusted wallet formatting (#1293) 2022-05-30 10:57:49 +01:00
Mark Sinclair 41d5c05a76 Merge pull request #1290 from nymtech/fix/build-dev-wallet-windows
add run-all package to build dev wallet on windows
2022-05-26 09:57:09 +01:00
Bogdan-Ștefan Neacşu a9422a32ed Make stats in network requester opt-in (#1288) 2022-05-26 11:33:25 +03:00
Jędrzej Stuczyński 177f1deefe Feature/gas simulation improvements (#1291)
* Updated cosmrs

* Simpler fmt::Display for Operation

* Adjusted GasPrice parsing test due to changes in Denom FromStr impl

* Removed direct dependency on cosmrs in the wallet

* Removed TryFrom<GasPrice> for Coin as it didn't make much sense

* disgusting workaround for providing serde for fee

* NymdError improvements

* Ability to simulate "send"

* Removed needless conversions

* Changedi nterface to "normal" send to account for new fee

* Removed outdated imports in tests

* Removal of 'Operation' enum

* Implemented simulate endpoints for all other txs
2022-05-26 09:26:49 +01:00
Tommy Verrall a1f633d225 add run-all package to build dev wallet on windows 2022-05-25 17:13:01 +01:00
Jon Häggblad 60526fdb90 wallet: check against importing same mnemonic twice (#1283) 2022-05-24 15:13:45 +02:00
Jędrzej Stuczyński 7d82fe0c0d Feature/various improvements (#1282)
* Added abci::Data field to ExecuteResult

* optional serde support for ed25519 keys

* optional serde support for x25519 keys

* actually calling dotenv at validator API startup

* Added STATE_DENOM network specific constant

* unit test fixes
2022-05-24 09:52:00 +01:00
Bogdan-Ștefan Neacşu d7920a4f50 Use remote source for stats provider address (#1281) 2022-05-23 16:33:47 +03:00
durch 71cfdb4d07 Add vesting glossary 2022-05-23 13:25:20 +02:00
Raphaël Walther db743578a9 Add an extra clean step in nightly build 2022-05-23 11:13:07 +02:00
Bogdan-Ștefan Neacșu ba7f535cb7 Update stats provider mainnet client address 2022-05-20 13:23:58 +03:00
Bogdan-Ștefan Neacşu fd4f5b319c Add serviced client data to stats (#1278)
* Add serviced client data to stats

* Put stats service address in network defaults

* Update CHANGELOG.md
2022-05-19 15:54:51 +03:00
Mark Sinclair 6045f57612 Merge pull request #1279 from nymtech/change-text
change text
2022-05-19 12:50:20 +01:00
Mark Sinclair 878cb3f0e5 Merge pull request #1277 from nymtech/av-uptime-nodes-list
adding percentage symbol and make column wider
2022-05-19 12:50:06 +01:00
gala1234 686c0de5eb change text 2022-05-19 13:42:51 +02:00
Drazen Urch 5ce2e0c8bb Optimize fetch and loop (#1271)
* Optimize fetch and loop

* Better operator fetch

* cargo fmt
2022-05-19 11:33:11 +02:00
Drazen Urch 68e120dbf4 Replace checked with saturating sub (#1275)
* Replace checked with saturating sub

* CHANGELOG
2022-05-18 13:33:19 +02:00
gala1234 1362fcdbfa adding percentage symbol and make column wider 2022-05-18 09:41:56 +02:00
Jon Häggblad 3281f10443 wallet: multi account support in wallet-recovery-cli (#1276)
* wallet-recovery-cli: support multi accounts

* wallet-recovery-cli: simplify tests
2022-05-17 22:48:13 +02:00
Jon Häggblad 2409f85e31 Merge pull request #1273 from nymtech/fix/wallet-store-ids-in-state
wallet: store ids in state and require password to switch account
2022-05-17 22:21:01 +02:00
Jon Häggblad 37c06f338f wallet: just print account id in log statement 2022-05-17 21:56:19 +02:00
fmtabbara 8a37df64b5 update methods 2022-05-17 15:43:37 +01:00
fmtabbara 20828d0d28 rework account selection functions 2022-05-17 15:43:22 +01:00
fmtabbara 1e3c8ed3e0 confirm password modal 2022-05-17 15:41:25 +01:00
fmtabbara 830dbcecfc extend component props 2022-05-17 15:41:06 +01:00
fmtabbara a0d3144837 extend component props 2022-05-17 15:40:18 +01:00
fmtabbara a4988e3547 tidy up 2022-05-17 15:39:56 +01:00
fmtabbara 0b585a15b7 use confirm password modal on account selection 2022-05-17 15:39:45 +01:00
fmtabbara 86a93a71e9 relocate modals to their own directory 2022-05-17 15:39:17 +01:00
fmtabbara 967692bf88 create confirm password modal 2022-05-17 15:38:06 +01:00
Jędrzej Stuczyński 662b4d2fff Modified docker setup to start 2 validators in local consensus (#1270)
it was mostly for my own needs, but figured I might create separate PR for that
2022-05-17 09:52:32 +01:00
Jon Häggblad add4747e99 wallet: more test files and regression tests (#1274)
* wallet: add test file for 1.0.4

* wallet: add test file for 1.0.5

* wallet: add test for multi account wallet file

* rustfmt
2022-05-17 10:20:39 +02:00
Bogdan-Ștefan Neacșu 242733f144 Add release configuration for network requester rocket 2022-05-17 11:19:47 +03:00
Jon Häggblad 0cc1926636 Merge branch 'develop' into fix/wallet-store-ids-in-state 2022-05-17 07:22:30 +02:00
Jon Häggblad 0f54c073a7 wallet: simple cli tool for decrypting the wallet (#1272)
* wallet: simple cli tool for decrypting the wallet

* changelog: add note about wallet file recovery tool
2022-05-17 07:18:50 +02:00
Jon Häggblad 3bfce128a6 wallet: store id instead of mnemonic in state 2022-05-17 00:33:23 +02:00
Raphaël Walther 1b3e79e84d Add a way to manually trigger build 2022-05-16 16:43:42 +02:00
Gala e94f99211f Merge pull request #1269 from nymtech/av-uptime-nodes-list
adding average uptime on mix nodes table
2022-05-16 16:04:10 +02:00
gala1234 003ea095cc remove not needed variable, PR request 2022-05-16 14:12:31 +02:00
gala1234 74d93df74b adding av uptime on mix nodes table 2022-05-16 13:41:10 +02:00
Bogdan-Ștefan Neacşu 0c66cc7393 Feature/service stats (#1267)
* Send message from service provider to stats service

* Put some actual data in stats

* Put stats sender on its own thread and send response data too

* Use SQLite for storing stats

* Add the data interval and timestamp

* Fix clippy

* Set description at boot

* Guard stats service functionality under a feature for now

* Make stats service address data into consts

* Add README to network requester

* Retrieve sql data in interval

* Expose sql data via rocket rest api

* Add entry to changelog
2022-05-16 12:06:15 +03:00
Fouad 82abfa5c5c Merge pull request #1265 from nymtech/sprint/wallet-multiple-accounts
Support multiple accounts in the wallet
2022-05-13 12:46:01 +01:00
fmtabbara f7486f0490 add field validation for mnemonic and account name 2022-05-13 12:22:19 +01:00
Jon Häggblad ee5f0f5808 cargo: update lock files 2022-05-13 13:16:03 +02:00
Jon Häggblad 6cb25cf1fb wallet: dont use default wallet when removing 2022-05-13 11:53:27 +02:00
Jon Häggblad db01c245d9 wallet: update wallet error enum cases 2022-05-13 11:53:22 +02:00
Jon Häggblad 53347bec67 changelog: added note about multiple accounts 2022-05-13 11:23:50 +02:00
Jon Häggblad 252385688d wallet: remove unnecessary blank lines 2022-05-13 09:35:27 +02:00
Jon Häggblad cbcef9fbcd wallet: switch from PathBuf to Path 2022-05-13 09:18:21 +02:00
Jon Häggblad e27fc82524 wallet: use login_id and account_id instead of id and inner_id 2022-05-13 09:00:09 +02:00
Jon Häggblad f661bf0446 wallet: write_to_file function 2022-05-12 22:15:38 +02:00
Jon Häggblad 1d22f35c82 wallet: pass by ref 2022-05-12 22:05:40 +02:00
Jon Häggblad fa41fe62c4 wallet: simplify naming 2022-05-12 21:48:23 +02:00
fmtabbara 94a006a725 Merge branch 'develop' into sprint/wallet-multiple-accounts 2022-05-12 15:13:12 +01:00
Jon Häggblad 98cbd2509c wallet: more moving things around 2022-05-12 15:24:38 +02:00
Jon Häggblad d2d99ca5c9 wallet: additional tidy 2022-05-12 14:43:22 +02:00
Jon Häggblad 9e4904ff37 wallet: tweak names of wallet_storage functions 2022-05-12 14:43:22 +02:00
Jon Häggblad bf9db4128d wallet: qualify with wallet_storage 2022-05-12 14:43:22 +02:00
Jon Häggblad bce86235c7 wallet: create LoginId type to distinguish 2022-05-12 11:56:00 +02:00
Tommy Verrall 423f6bfb55 Merge pull request #1266 from nymtech/feature/update-qa
Update QA network defaults
2022-05-12 10:22:12 +01:00
tommy 38fcbb7f2e fmt 2022-05-12 10:21:37 +01:00
tommy 1785b10d91 clean up 2022-05-12 10:20:52 +01:00
Jon Häggblad 7771c5d3d9 wallet: create password with multi-account by default 2022-05-12 10:28:45 +02:00
Jon Häggblad d6206a04bd wallet: restore test in account 2022-05-12 08:37:54 +02:00
Jon Häggblad 83a7f6577b wallet: logout before re-connecting 2022-05-11 22:26:54 +02:00
Jon Häggblad 97c6567139 wallet: restore unit tests 2022-05-11 22:26:48 +02:00
tommy a1534d23af add correct bech32 address as the network-explorer-api was complaining 2022-05-11 18:17:49 +01:00
tommy bcbda85477 re-map coin type for qa 2022-05-11 16:01:04 +01:00
Jon Häggblad 54e95d795e wallet: revert back to old default account name for the time being 2022-05-11 16:48:10 +02:00
tommy 11c0e79725 fmt 2022-05-11 15:03:06 +01:00
tommy 4525be9871 Update QA vars 2022-05-11 14:56:07 +01:00
fmtabbara b83d4ca1a3 reset to initial step after add account 2022-05-11 13:47:41 +01:00
fmtabbara bfb868bfc7 move textfield components to global components 2022-05-11 13:39:01 +01:00
fmtabbara 7b00282b27 reset add account step on completion 2022-05-11 13:21:22 +01:00
Jon Häggblad 54194c03e1 wallet: fix default inner account name 2022-05-11 12:17:08 +02:00
fmtabbara 87c2a317d5 merge develop 2022-05-11 10:54:25 +01:00
Jon Häggblad be92171fec common/config: fix clippy (#1264)
* common/config: fix clippy

* common/config: use cfg_if
2022-05-10 13:32:18 +02:00
Jon Häggblad 04eef83c15 wallet: add backend support for validator name (#1262)
* wallet: add support for validator nymd name

* changelog: add entry for wallt validator name

* rustfmt

* wallet: keep nymd_name entirely on wallet side

* wallet: lint fixes
2022-05-10 11:26:49 +02:00
fmtabbara 25cc7dbebf content update 2022-05-10 10:23:08 +01:00
Jon Häggblad beeb67e9c2 wallet: change default name of first account 2022-05-10 10:25:40 +02:00
Fouad ec19de6fa3 Merge pull request #1227 from nymtech/feature/multi-accounts-integrate-rust
Multiple accounts
2022-05-09 14:40:59 +01:00
fmtabbara 575845af38 fix lint errors 2022-05-09 13:38:38 +01:00
fmtabbara b6b757436e remove edit button until feature delivered 2022-05-09 13:36:38 +01:00
fmtabbara eda69447de add how-to modal for creating multi accounts 2022-05-09 13:31:01 +01:00
Jędrzej Stuczyński 0f6f47c5ac Fixed overflow in determining reconnection backoff (#1260)
* Fixed overflow in determining connection backoff

* Updated changelog
2022-05-09 13:23:14 +01:00
fmtabbara b57c17e5af fix storybook 2022-05-09 10:34:14 +01:00
Drazen Urch c237b37ea6 Use saturating_sub for extra safety (#1258)
* Use saturating_sub for extra safety

* More saturating_sub

* Save some lines

* Add change log
2022-05-09 09:47:03 +02:00
fmtabbara f2fa221489 big tidy and some refactoring of high level component hierachy 2022-05-07 17:53:46 +01:00
Gala d7e82b075f Network Explorer: add economic dynamics (#1223)
* adding delegators number info on mixnode details

* add PM, Delegators and Avg. Uptime fields to the node list hardcoded

* make delegations number dynamic

* fixing bg color bug

* wip node info statistics

* adding basic tooltip new section and some ui

* tooltip customisation

* progress bar styles

* remove not used import

* fix info icons color

* remove discord icon

* Economic dynamics stats endpoint on the explorer API with dummy fixture data

* fetching economic-dynamics-stats

* Populating the endpoint with real data aggregated from validator api

* Introduced new cache functionalities

* using explorer-api data

* adding marging profit

* adding average update

* Update network-explorer.yml

* adding more info on mix nodes page

* display only part of wallet and node id

* typo

* remove log

* adding new values on node response and fix a typo

* remove delegators number column

* Endpoints for average mixnode uptime

* remove TODO

* Clippy

* some ui fixes for percentage linear progress

* GitHub Actions: build storybook for the Network Explorer and add to notification

* Fix file extension to `.ts`

* Fix up formatting and types

* Add storybook

* Add story for mix node details economics

* Fix unused warning

* adding percentage symbol on uptime in mix nodes

* Change eslint config

* some refactor

* progress bar story

* wip refactoring

* more refactor

* adding empty state to the story

* change default values for empty state

* refactor naming and progress bar contrast

* adding hardcoded selection chance and update the storybook

* adding selection chance stories

* adding the progress bar back

* tooltip button padding fix

* Endpoints for average mixnode uptime

* Fix unused warning

* Rustfmt

* moking selection chance response and new colors

* remove log

* fix camelCases issue

* remove hardcoded code

* remove avg_uptime at mixnodes table

* Add jsonchema to uptimeresponse struct

- add the route for avg_uptimes

* adding space between words

* update selection chance colours

* adding the 2 missing tooltips

* fix up uptimeresponse

* fix duplicate entry

* fmt

* validator-client: use statement

* explorer: PR requests

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
Co-authored-by: Fouad <fmtabbara@hotmail.co.uk>
Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
Co-authored-by: Mark Sinclair <mmsinclair@gmail.com>
Co-authored-by: tommy <tommyvez@protonmail.com>
2022-05-06 20:50:26 +01:00
fmtabbara 0e4787f078 minor refactors 2022-05-06 15:54:38 +01:00
Jędrzej Stuczyński f684664472 Removed an 'expect' in mixnet contract query (#1257)
* Removed an 'expect' in mixnet contract query

* Updated changelog
2022-05-06 14:28:57 +01:00
Jędrzej Stuczyński cd5fff92ad Added network information to --version (or --help) command (#1256)
* Added network information to --version (or --help) command

* Updated changelog
2022-05-06 10:46:17 +01:00
fmtabbara 9b0b961d43 content updates 2022-05-06 09:56:13 +01:00
Raphaël Walther 4b95e71adb Merge pull request #1254 from nymtech/cargo_manifest_path
Fixed nightly build mainifest path
2022-05-06 10:39:30 +02:00
Raphaël Walther cde5b66306 Fixed nightly build mainifest path 2022-05-06 09:44:55 +02:00
Jon Häggblad ac72e20447 wallet: additional test for decrypting stored wallet file 2022-05-06 09:41:36 +02:00
Jon Häggblad 571fd5cb93 wallet: removing an account in a login twice should fail 2022-05-06 09:01:48 +02:00
Jon Häggblad 7f3166d230 changelog: fix link 2022-05-05 17:06:27 +02:00
Jon Häggblad 68050d77df changelog: add note about swagger and validator-api 2022-05-05 16:44:38 +02:00
Jędrzej Stuczyński 943337db0d Running tests with the same opt-level in CI, but preserving debug assertions (#1253) 2022-05-05 15:13:04 +01:00
dependabot[bot] 8fbf84174d Bump ejs from 3.1.6 to 3.1.7 in /nym-wallet/webdriver (#1245)
Bumps [ejs](https://github.com/mde/ejs) from 3.1.6 to 3.1.7.
- [Release notes](https://github.com/mde/ejs/releases)
- [Changelog](https://github.com/mde/ejs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mde/ejs/compare/v3.1.6...v3.1.7)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-05 13:47:24 +01:00
dependabot[bot] d42a175289 Bump async from 2.6.3 to 2.6.4 (#1233)
Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-05 13:47:18 +01:00
dependabot[bot] ac01df0817 Bump async in /clients/native/examples/js-examples/websocket (#1232)
Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-05 13:47:12 +01:00
dependabot[bot] 7b27065608 Bump async from 2.6.3 to 2.6.4 in /clients/webassembly/js-example (#1231)
Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-05 13:47:07 +01:00
Jon Häggblad 0523ccdce8 workflow: add wallet rust ci job (#1251)
* workflow: add wallet rust ci job

* workflow: remove not used matrix file

* workflow: add wallet rust part to nightly build

* workflow: add nym-wallet to step names

* workflow: tweak wallet names

* workflow: run on ubuntu-latest

* workflow: skip sccache

* workflow: tweak name

* workflow: switch back to self-hosted tag
2022-05-05 13:58:07 +02:00
Jędrzej Stuczyński df8ae52d8c Added pull request template with changelog checklist 2022-05-05 11:21:25 +01:00
fmtabbara 7cfaf6fa1e Merge branch 'sprint/wallet-multiple-accounts' into feature/multi-accounts-integrate-rust 2022-05-05 11:16:30 +01:00
fmtabbara 6e9eab4edb allow access to prev steps when creating account 2022-05-05 11:15:56 +01:00
Jon Häggblad 0812378fdd wallet: reset account state when adding and removing accounts 2022-05-05 12:13:31 +02:00
Jon Häggblad 188a7ec91d validator-api: reenable swagger openapi (#1249)
* validator-api: reenable swagger openapi

* rustfmt

* validator-api: rename openapi tag to status
2022-05-05 11:35:00 +02:00
Jon Häggblad c91bf3f8d1 wallet: fix rust unit tests (#1252) 2022-05-05 10:31:00 +02:00
fmtabbara ecb27e2cc2 import accounts wip 2022-05-04 22:36:09 +01:00
fmtabbara a1961dbc2f add accounts wip 2022-05-04 21:34:42 +01:00
fmtabbara ef4af0a1db display account errors 2022-05-04 20:49:19 +01:00
fmtabbara 5930ec1f18 Merge branch 'sprint/wallet-multiple-accounts' into feature/multi-accounts-integrate-rust 2022-05-04 20:46:09 +01:00
fmtabbara 72c7049fca set up loading page for account switch 2022-05-04 20:45:33 +01:00
Jon Häggblad 92cbe651de wallet: add account returns wallet entry 2022-05-04 20:14:01 +02:00
Jędrzej Stuczyński 777fcf8cb3 Release/nym wallet v1.0.4 (#1248)
* Bumped up version numbers to 1.0.1

* Updated changelog attempting to use new format

* Bumped up wallet version number

* Updated changelog attempting to use new format

* Updated tauri version
2022-05-04 17:03:08 +01:00
Jędrzej Stuczyński 945dda0c24 Release/1.0.1 (#1247)
* Bumped up version numbers to 1.0.1

* Updated changelog attempting to use new format
2022-05-04 16:57:36 +01:00
fmtabbara b44b074af7 Merge branch 'sprint/wallet-multiple-accounts' into feature/multi-accounts-integrate-rust 2022-05-04 16:22:31 +01:00
fmtabbara 7a0dff5f00 update main state to respond to account changes 2022-05-04 16:22:00 +01:00
fmtabbara 6685b129bb refactor to use accounts context 2022-05-04 16:21:29 +01:00
fmtabbara e0a80c777e stories broken until mock context added 2022-05-04 16:19:52 +01:00
Jędrzej Stuczyński 479d410d20 Feature/broadcast sync with polling (#1246)
* Broadcast tx in a sync mode and poll for its inclusion

* Adjusted internal type used in TauriTxResult

* Re-exported MsgSend

* Increased polling rate + removed print
2022-05-04 16:19:34 +01:00
fmtabbara ab019266cc derive color from name now address 2022-05-04 16:19:25 +01:00
fmtabbara 92fcae9a37 use context for accounts state 2022-05-04 16:18:56 +01:00
Jon Häggblad 413e2662ff wallet: rustfmt 2022-05-04 16:50:51 +02:00
Jon Häggblad e026a532dd wallet: store unlocked accounts in Vec to preserve order 2022-05-04 16:50:01 +02:00
Mark Sinclair a1ca330ce9 GitHub Actions: upload network requester to releases 2022-05-04 14:31:21 +01:00
Jon Häggblad 139e89643c Endpoints for average mixnode uptime (#1238) 2022-05-04 11:28:41 +02:00
fmtabbara a1e0087760 merge sprint branch updates£ 2022-05-04 10:01:39 +01:00
fmtabbara b15cc094ea small refactors 2022-05-04 10:00:17 +01:00
Jędrzej Stuczyński 8eb3f6f862 Changed opt-level for test code to speed up especially dkg tests 2022-05-04 09:54:18 +01:00
Jędrzej Stuczyński 9032d81d52 Fixed dkg benchmarking code to take into account resharing attributes 2022-05-04 09:44:10 +01:00
Jon Häggblad 7adee63ebe wallet: add show_mnemonic_for_account_in_password 2022-05-04 10:01:34 +02:00
Jon Häggblad 1a5580229b wallet: more unit tests 2022-05-04 01:35:57 +02:00
Jon Häggblad df4c6493d4 wallet: split and add more wallet_store tests 2022-05-03 23:20:16 +02:00
Jon Häggblad 30a41261ea wallet: add more log statements 2022-05-03 20:03:46 +02:00
Fouad f2e95d2fd5 Merge pull request #1244 from nymtech/feature/inclusion-probability-ui-update
update inclusion prob UI update
2022-05-03 16:57:22 +01:00
fmtabbara 5730c914e8 use coming soon chip 2022-05-03 16:38:15 +01:00
fmtabbara 7845e32742 update inclusion prob UI 2022-05-03 15:14:16 +01:00
Jon Häggblad 4cce235e13 validator-api: disable swagger temporarily 2022-05-03 15:49:44 +02:00
Jędrzej Stuczyński 521eb98f25 Generated changelog for 1.0.0 2022-05-03 14:27:54 +01:00
Jon Häggblad c77ccddcb3 Add swagger for validator-api (#1239)
* validator-api: add swagger openapi

* Lock file
2022-05-03 14:40:11 +02:00
Drazen Urch b8ce97e005 Mitigate PM change attack (#1236)
* Mitigate PM change attack

* More paranoid test
2022-05-03 14:36:29 +02:00
Bogdan-Ștefan Neacşu 49e29af5f4 Fix gateway test wallet address (#1237)
* Fix gateway test wallet address

Since we're switching to mainnet as default, use the correct wallet
address format.

* Fix other test with prefix problems
2022-05-03 14:36:29 +02:00
durch 41319fe7ad Fix unbonding for new nodes 2022-05-03 14:36:29 +02:00
durch 4fcf0da5c0 Move amount to liquidate to const 2022-05-03 14:36:29 +02:00
durch 0a6b2a8aaf Fix vesting accounting 2022-05-03 14:36:29 +02:00
Drazen Urch b09db50bba Fix checkpointing (#1230)
* Fix checkpoint strategy and order, test

* Debug pending delegation events

* Debug print

* Switch print to log

* Add debug print

* More printing

* Remove problematic checkpoint

* checkpoint mixnodes in separate block

* more debugs

* Removing old migration just in case

* Printing all checkpoint heights at migration

* Purging old checkpoint from storage

* Attempting to load raw storage value

* More printing...

* Removed expect

* deserialization changes

* error handling

* cleanup

* clippy

* dead code

* Reduce minimum age for rewarding to 1 epoch

* Add checkpoint query

* Get checkpoint at height

* Fix delegation compounding, fix undelegate accumulate_rewards

* Fix potential overflow, add debug logging

* Moar logging

* Fix total_delegations + rewards

* Better error for horrible overflow

* fmt

* Fixed unit test assertions in bech32 validation

Co-authored-by: Jędrzej Stuczyński <jedrzej.stuczynski@gmail.com>
2022-05-03 14:36:29 +02:00
Jędrzej Stuczyński 82e6d7335b Bandwidth credentials disabled by default 2022-05-03 14:36:29 +02:00
Jędrzej Stuczyński 21d19d2447 Renamed testnet mode to disabled credentials mode 2022-05-03 14:36:29 +02:00
Jędrzej Stuczyński 5f116f8104 Set mainnet as the default network build target 2022-05-03 14:36:29 +02:00
Jędrzej Stuczyński 04f633446e Updated binaries versions to 1.0.0 2022-05-03 14:36:29 +02:00
durch be5421719c Fix undelegations 2022-05-03 14:36:29 +02:00
Jon Häggblad 6d874cc34a wallet: remove list_accounts_for_password 2022-04-29 16:54:25 +02:00
Jon Häggblad 7e356ea3b3 wallet: simplify account handling 2022-04-29 16:49:57 +02:00
Jon Häggblad f7be9e7e6f wallet: remove DecryptedAccount and make Accounts clone 2022-04-29 16:27:24 +02:00
Jon Häggblad 05374393ef wallet: contructors for DecryptedAccount 2022-04-29 15:36:06 +02:00
fmtabbara 580656c002 refactor + wip 2022-04-29 14:12:47 +01:00
Jon Häggblad a7caf97b73 wallet: derive_address 2022-04-29 15:12:07 +02:00
Fouad 96f2718b94 Feature/show pending delegations (#1229)
* remove unused import

* use correct types

* set up pending delegations

* remove unused import
2022-04-29 12:53:52 +01:00
fmtabbara 091507b6d8 Merge branch 'sprint/wallet-multiple-accounts' into feature/multi-accounts-integrate-rust 2022-04-29 11:10:21 +01:00
Jon Häggblad 71b5bc9e71 wallet: backend account switching 2022-04-29 11:48:07 +02:00
fmtabbara 8bbffb6a88 prevent bubbling 2022-04-29 10:45:43 +01:00
fmtabbara 31c4fc6807 update display mnemonic component 2022-04-29 10:45:23 +01:00
fmtabbara 66b9b13edc refactor and add more mocks 2022-04-29 10:05:18 +01:00
fmtabbara 1b945ae918 use account switch function 2022-04-27 13:43:39 +01:00
fmtabbara be9b83a87d set up account switching mechanism 2022-04-27 11:19:19 +01:00
Fouad ba1fb17908 Merge pull request #1209 from nymtech/feature/multi-address-wallet
Feature/multi address wallet
2022-04-26 20:51:50 +01:00
Drazen Urch 632612eca0 Bucket inclusion probabilities (#1224)
* Bucket inclusion probabilities

* Make display equal variant name for TS
2022-04-26 16:23:32 +02:00
fmtabbara 5446874ebe align top bar with nav 2022-04-26 14:29:17 +01:00
Drazen Urch c250492f50 Create a new bundled delegation when compounding rewards (#1221)
* Create a new bundled delegation when compounding rewards

* Cleanup migration code

* Revert "Cleanup migration code"

This reverts commit 21d8ad6388.
2022-04-25 20:34:43 +02:00
Mark Sinclair 52fdc3e0cf Bump nym-wallet version 2022-04-25 14:34:23 +01:00
Mark Sinclair 5a91240992 Use new tauri operation to get the runtime env vars 2022-04-25 14:34:23 +01:00
Mark Sinclair c9638097a0 Add tauri operation to get runtime environment vars 2022-04-25 14:34:23 +01:00
Mark Sinclair 383b197e5b Use new query to get pending vesting delegations 2022-04-25 14:34:23 +01:00
Mark Sinclair 787a55a4ba Fix typo 2022-04-25 14:34:23 +01:00
Mark Sinclair bbdd53d2aa Add terminal in dev mode 2022-04-25 14:34:23 +01:00
Mark Sinclair 67f6394a26 Bug fix undelegating locked tokens to use the correct contract methods 2022-04-25 14:34:23 +01:00
Mark Sinclair dbfeaff661 Explorer API - reduce cache time to 60 seconds 2022-04-25 14:19:55 +01:00
Jędrzej Stuczyński 8318002b0a Bugfix/delegation reconcile (#1219)
* Passing proxy value when attempting to compound delegator reward

* Do not attempt to delegate reward to mixnode if its zero

* Additional guards against sending 0 tokens

* Removed sign of sloppiness

* Fixes to rewards and delegation events storage

* Remove block count check, epoch cannot be advanced while in progress

* Add reward compound ops to vesting contract

* Migration to remove 0 value delegationns

Co-authored-by: durch <durch@users.noreply.github.com>
2022-04-25 12:01:45 +01:00
fmtabbara afac630a77 add list accounts function 2022-04-25 11:27:07 +01:00
Tommy Verrall 9fb980dc5e Merge pull request #1212 from pwnfoo/patch-1
chore: fix dark mode rendering
2022-04-22 17:39:50 +01:00
Jędrzej Stuczyński ce77b17534 Bugfix/query proxied pending delegations (#1218)
* Removed 'expect' from one of the queries

* Query for pending vesting delegation events
2022-04-22 14:51:53 +01:00
Jędrzej Stuczyński e6372d3b02 Using custom gas multiplier in the wallet (#1217) 2022-04-21 16:52:17 +01:00
Jędrzej Stuczyński 836ef9d4c8 Feature/vesting accounts support (#1216)
* Utility for parsing Vec<ProtoCoin>

* Support for different types of vesting accounts

* Derived Debug for Account

* Exposed method for querying for account details

* Renamed ibid. to a more appropriate name
2022-04-21 13:18:05 +01:00
Jędrzej Stuczyński dd66697884 Release/1.0.0 rc.2 (#1214)
* Updated binaries versions to 1.0.0-rc.2

* Update GitHub Action to build nym binaries

* Fix contract bootstraping

* Relax the active_set check

* Cleanup migration

* Fix validator-api mainnet config

* Removed reward estimation ts-files

* Removed old migration tests

Co-authored-by: Mark Sinclair <mmsinclair@gmail.com>
Co-authored-by: durch <durch@users.noreply.github.com>
2022-04-21 10:36:48 +01:00
Sachin S. Kamath 7c4bb68399 chore: fix dark mode rendering 2022-04-15 16:43:07 +05:30
Dave Hrycyszyn 58f3a96cde Adding security disclosure instructions 2022-04-14 20:44:01 +01:00
fmtabbara 3250d6982e refactor to separate logic from view 2022-04-14 14:31:55 +01:00
Bogdan-Ștefan Neacşu 76a61cb3ae Feature/spend coconut (#1210)
* Import cw3-flex-multisig and cw4-group contracts

* Add release_funds to coconut-bandwidth-contract

* Create contract.rs file

* Add cw multi test and a test that uses it

* Use mnemonic for coconut mode too

* Stricter access to config file, which contains mnemonic

* Update tests

* Remove signed deposits dir after merging that into sql db

* Clippy nits

* More clippy

* Remove backtraces features to pass clippy tests

* Merge the same mnemonic for rewarding and coconut

* Simplify things, letting network monitor use testnet-mode with gateways

* Unify the nymd clients

* Sqlx common storage for buying/consuming credentials

* Link credential storage to credential client

* Trigger rewarded_set update on bootstrap error

* Fix bug on message signing

* Simplify coconut feature in code and set it in validator-api

* Update some local consts

* Link clients to credential storage

* Simplify sql query and change socks5 too

* Update attr handling such that public ones are usable

* Normalize test addresses

* Fix clippy

* Merge storages for (non)coconut creds

* Fmt miss

* Disable wasm client support for now

Co-authored-by: durch <durch@users.noreply.github.com>
2022-04-14 14:25:21 +02:00
fmtabbara ecdbe1a6fb refactor 2022-04-13 18:08:57 +01:00
fmtabbara 6ba79ee924 use avatar component 2022-04-13 18:07:54 +01:00
fmtabbara 2bebb4b0c2 remove unused component 2022-04-13 18:07:31 +01:00
fmtabbara 81b7d49624 create avatar component 2022-04-13 18:07:06 +01:00
fmtabbara f118a0c854 move story files 2022-04-13 18:06:53 +01:00
Mark Sinclair 94be4c71a4 Update GitHub Action to build nym binaries 2022-04-13 16:22:35 +01:00
fmtabbara db6ecaaecb Merge branch 'sprint/wallet-multiple-accounts' into feature/multi-address-wallet 2022-04-13 14:41:12 +01:00
fmtabbara bd13aa6f35 add new modals 2022-04-13 11:53:13 +01:00
Jędrzej Stuczyński 046dd4cbba Bugfix/unique sphinx key (#1207)
* Cosmwasm beta6 => beta8 upgrade

* Introducing additional unique index constraint on sphinx key

* Unit test for checking for duplicate sphinx key

* Fixed other unit tests due to changed constraint
2022-04-13 10:56:39 +01:00
Drazen Urch 485257d29b Add read and write timeouts (#1206) 2022-04-13 11:39:52 +02:00
fmtabbara 0cad7f635d new stories 2022-04-12 12:25:15 +01:00
Jędrzej Stuczyński 37de4bf2f7 Crypto part of the Groth's NIDKG (#1182)
* Work in progress NIDKG

* Encryption of multiple shares

* Extracted baby-step giant-step lookup table as a separate entity

* Proof of discrete log

* Adjusted discrete log domainn

* Producing proof of log during keygen

* Zeroize for epoch

* Proof of secret sharing

* empty main for compiler appeasement

* Construction of proof of chunking

* Initial untested verification of proof of chunking

* Converted chunk responses from Scalar to u64

* Additional tests for proof of chunking

* Minor cleanup and reorganisation

* Fixed enc/dec to use f0

* Deriving node coverage of required tree nodes

* Finally seemingnly working encryption under nonzero epoch

* Branch park

* Decryption key updates to specified epochs

* Ciphertext integrity checks

* Progress in integration tests

* Fixed ciphertext combining and integration test

* Dealing type and simplification of the integration test

* Benchmark for creation of baby-step-giant-step lookup table

* Initial import cleanup + broken 2nd integration test

* Using correct assertions in the integration test (and correctly combining shares)

* Removed unused modules

* Changed proof of sharing to allow for node indices being different from [1,2,...n]

* Reorganised bte module

* Benchmark for g2 precomputation

* Created more strongly typed Epoch type

which is essentially a Tau such that it is a leaf node

* Extending tau with a temporary oracle output

* Using random oracle for tau extension

* More benchmarks!

* encryption-related benchmarks

* Serialization of PublicKeyWithProof

* Typos

* Removed any changes made in validator-api or smart contracts

* Made the integration test slightly more concise

* Further purge of unused modules

* Fixed combining share to use lagrangian interpolation

* Recovery of verification keys from the dealings

* Verification key verification + extended integration tests

* Fixed Tau not being included in digest for producing Tau_h

* Tau serialization

* Serialization of a BTE Node

* Serialization of DecryptionKey

* Serialization of PublicCoefficients

* Utility method for setting constant coefficient of a polynomial

* Serialization of Ciphertexts

* Serialization of Proof of Secret Sharing

* Serialization of Proof of Chunking

* Serialization of Dealing

* Adjusted capacity of responses_r in proof of chunking

* Made notation more consistent with the paper equivalents

* Optional arguments for creating/verifying resharing dealings
2022-04-12 11:59:26 +01:00
Drazen Urch d3372bfc85 Additional, more informative routes (#1204)
* Have reward set updater run its own timer (#1200)

* Have reward set updater run its own timer

* Filter rocket log spam

* Take last day of uptime for rewarding (#1202)

* Take last day of uptime for rewarding

* Rejigger calculations

* Blacklist based on last 24 hr

* Cleanup

* Clippy

* Additional, more informative routes

* Improve blacklist updates

* Fix rewards estimation
2022-04-12 11:55:32 +02:00
Jon Häggblad a5e6032393 wallet: support multiple accounts per encrypted login (#1205)
* wallet: support multiple accounts per encrypted login

Rework wallet storage to allow grouping accounts under a single
encrypted entry, in a way that is backwards compatible.

* wallet: remove commented out lines
2022-04-12 11:53:38 +02:00
fmtabbara 4edc0700a1 Merge branch 'sprint/wallet-multiple-accounts' into feature/multi-address-wallet 2022-04-12 10:33:43 +01:00
fmtabbara 019a04c0fc code tidy and restructure 2022-04-12 10:33:16 +01:00
Raphaël Walther 25e1bfa345 Moved clean task 2022-04-12 09:26:31 +02:00
dependabot[bot] dc0b9c271c Bump ansi-regex in /docker/typescript_client/upload_contract (#1171)
Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/chalk/ansi-regex/releases)
- [Commits](https://github.com/chalk/ansi-regex/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: ansi-regex
  dependency-type: indirect
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-11 14:07:59 +01:00
Fouad 2e7b8e911f Merge pull request #1201 from nymtech/feature/multi-address-wallet
Feature/multi address wallet
2022-04-08 13:13:09 +01:00
fmtabbara 50f4699c95 change button size 2022-04-07 22:04:19 +01:00
fmtabbara 3265df019a more multi account ui 2022-04-07 22:00:30 +01:00
fmtabbara 902721bda3 yarn lock updated 2022-04-07 16:28:57 +01:00
fmtabbara 585724fc79 more storybook work for multi accounts 2022-04-07 16:27:30 +01:00
fmtabbara c6578384a8 storybook ui for multi addresses 2022-04-07 14:01:58 +01:00
fmtabbara 3b245e16db more multi account UI work 2022-04-07 11:34:48 +01:00
fmtabbara 6d0e2cf491 start component work for multi accounts 2022-04-06 11:25:08 +01:00
442 changed files with 25312 additions and 6144 deletions
+9
View File
@@ -0,0 +1,9 @@
# Description
Closes: #XXXX
<!-- If appropriate, insert relevant description here -->
# Checklist:
- [ ] added a changelog entry to `CHANGELOG.md`
+72
View File
@@ -0,0 +1,72 @@
name: Continuous integration on dispatch
on: workflow_dispatch
jobs:
build:
runs-on: [ self-hosted, custom-linux-exoscale ]
# Enable sccache via environment variable
env:
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
- name: Check out repository code
uses: actions/checkout@v2
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --all-features
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- uses: actions-rs/clippy-check@v1
name: Clippy checks
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
- name: Run clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --workspace -- -D warnings
- name: Build all binaries with coconut enabled
uses: actions-rs/cargo@v1
with:
command: build
args: --workspace --features=coconut
- name: Run all tests with coconut enabled
uses: actions-rs/cargo@v1
with:
command: test
args: --workspace --features=coconut
- name: Run clippy with coconut enabled
uses: actions-rs/cargo@v1
with:
command: clippy
args: --features=coconut -- -D warnings
+14
View File
@@ -31,6 +31,8 @@ jobs:
# continue-on-error: true
- run: yarn && yarn build
continue-on-error: true
- run: yarn storybook:build
name: Build storybook
- name: Deploy branch to CI www
continue-on-error: true
uses: easingthemes/ssh-deploy@main
@@ -42,6 +44,17 @@ jobs:
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/network-explorer-${{ env.GITHUB_REF_SLUG }}
EXCLUDE: "/dist/, /node_modules/"
- name: Deploy storybook to CI www
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "explorer/storybook-static/"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/ne-sb-${{ env.GITHUB_REF_SLUG }}
EXCLUDE: "/dist/, /node_modules/"
- name: Keybase - Node Install
run: npm install
working-directory: .github/workflows/support-files
@@ -51,6 +64,7 @@ jobs:
NYM_PROJECT_NAME: "Network Explorer"
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
NYM_CI_WWW_LOCATION: "network-explorer-${{ env.GITHUB_REF_SLUG }}"
NYM_CI_WWW_LOCATION_STORYBOOK: "ne-sb-${{ env.GITHUB_REF_SLUG }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
+33
View File
@@ -94,12 +94,45 @@ jobs:
command: test
args: --workspace --features=coconut
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
- name: Run clippy with coconut enabled
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --workspace --all-targets --features=coconut -- -D warnings
# nym-wallet (the rust part)
- name: Build nym-wallet rust code
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Run nym-wallet tests
uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Check nym-wallet formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path nym-wallet/Cargo.toml --all -- --check
- name: Run clippy for nym-wallet
uses: actions-rs/cargo@v1
if: ${{ matrix.rust != 'nightly' }}
with:
command: clippy
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-targets -- -D warnings
notification:
needs: build
runs-on: ubuntu-latest
@@ -2,6 +2,9 @@ name: Publish Nym binaries
on:
release:
types: [created]
env:
NETWORK: mainnet
jobs:
publish-nym:
@@ -41,3 +44,4 @@ jobs:
target/release/nym-mixnode
target/release/nym-socks5-client
target/release/nym-validator-api
target/release/nym-network-requester
@@ -1,5 +1,6 @@
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
> :rocket: {{ env.NYM_PROJECT_NAME }} ➡️➡️➡️➡️➡️ **View output:** https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}/
> `storybook`: https://{{ env.NYM_CI_WWW_LOCATION_STORYBOOK }}.{{ env.NYM_CI_WWW_BASE }}
> ✅ **SUCCESS**
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
+57
View File
@@ -0,0 +1,57 @@
name: Nym Wallet (rust)
on:
push:
paths-ignore:
- 'explorer/**'
pull_request:
paths-ignore:
- 'explorer/**'
jobs:
build:
runs-on: [ self-hosted, custom-linux-exoscale ]
steps:
- name: Install Dependencies (Linux)
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
- name: Check out repository code
uses: actions/checkout@v2
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
override: true
components: rustfmt, clippy
- name: Build all binaries
uses: actions-rs/cargo@v1
with:
command: build
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Run all tests
uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path nym-wallet/Cargo.toml --workspace
- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --manifest-path nym-wallet/Cargo.toml --all -- --check
- uses: actions-rs/clippy-check@v1
name: Clippy checks
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-features
- name: Run clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-features -- -D warnings
+347
View File
@@ -1,5 +1,351 @@
# Changelog
## [Unreleased]
### Added
- wallet: require password to switch accounts
- wallet: add simple CLI tool for decrypting and recovering the wallet file.
- wallet: added support for multiple accounts ([#1265])
- wallet: the wallet backend learned how to keep track of validator name, either hardcoded or by querying the status endpoint.
- mixnet-contract: Replace all naked `-` with `saturating_sub`.
- mixnet-contract: Added ClaimOperatorReward and ClaimDelegatorReward messages ([#1292])
- vesting-contract: Added ClaimOperatorReward and ClaimDelegatorReward messages ([#1292])
- validator-api: add Swagger to document the REST API ([#1249]).
- validator-api: add `estimated_node_profit` and `estimated_operator_cost` to `reward-estimate` endpoint ([#1284])
- all: added network compilation target to `--help` (or `--version`) commands ([#1256]).
- network-requester: send traffic statistics from all network requesters and receive it in a special network-requester that aggregates the data and exposes it via a rest API ([#1267], [#1278]).
### Fixed
- mixnet-contract: replaced integer division with fixed for performance calculations ([#1284])
- mixnet-contract: delegator and operator rewards use lambda and sigma instead of lambda_ticked and sigma_ticked ([#1284])
- mixnet-contract: `estimated_delegator_reward` calculation ([#1284])
- vesting-contract: replaced `checked_sub` with `saturating_sub` to fix the underflow in `get_vesting_tokens` ([#1275])
- mixnet-contract: removed `expect` in `query_delegator_reward` and queries containing invalid proxy address should now return a more human-readable error ([#1257])
- mixnet-contract: Under certain circumstances nodes could not be unbonded ([#1255](https://github.com/nymtech/nym/issues/1255)) ([#1258])
- mixnode, gateway: attempting to determine reconnection backoff to persistently failing mixnode could result in a crash ([#1260])
### Changed
- validator-client: created internal `Coin` type that replaces coins from `cosmrs` and `cosmwasm` for API entrypoints [[#1295]]
[#1258]: https://github.com/nymtech/nym/pull/1258
[#1249]: https://github.com/nymtech/nym/pull/1249
[#1256]: https://github.com/nymtech/nym/pull/1256
[#1257]: https://github.com/nymtech/nym/pull/1257
[#1260]: https://github.com/nymtech/nym/pull/1260
[#1265]: https://github.com/nymtech/nym/pull/1265
[#1267]: https://github.com/nymtech/nym/pull/1267
[#1275]: https://github.com/nymtech/nym/pull/1275
[#1278]: https://github.com/nymtech/nym/pull/1278
[#1284]: https://github.com/nymtech/nym/pull/1284
[#1292]: https://github.com/nymtech/nym/pull/1292
[#1295]: https://github.com/nymtech/nym/pull/1295
## [nym-wallet-v1.0.4](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.4) (2022-05-04)
### Changed
- all: the default behaviour of validator client is changed to use `broadcast_sync` and poll for transaction inclusion instead of using `broadcast_commit` to deal with timeouts ([#1246])
## [v1.0.1](https://github.com/nymtech/nym/tree/v1.0.1) (2022-05-04)
### Added
- validator-api: introduced endpoint for getting average mixnode uptime ([#1238])
### Changed
- all: the default behaviour of validator client is changed to use `broadcast_sync` and poll for transaction inclusion instead of using `broadcast_commit` to deal with timeouts ([#1246])
### Fixed
- nym-network-requester: is included in the Github Actions for building release binaries
[#1238]: https://github.com/nymtech/nym/pull/1238
[#1246]: https://github.com/nymtech/nym/pull/1246
## [v1.0.0](https://github.com/nymtech/nym/tree/v1.0.0) (2022-05-03)
[Full Changelog](https://github.com/nymtech/nym/compare/v0.12.1...v1.0.0)
**Merged pull requests:**
- Feature/show pending delegations [\#1229](https://github.com/nymtech/nym/pull/1229) ([fmtabbara](https://github.com/fmtabbara))
- Bucket inclusion probabilities [\#1224](https://github.com/nymtech/nym/pull/1224) ([durch](https://github.com/durch))
- Create a new bundled delegation when compounding rewards [\#1221](https://github.com/nymtech/nym/pull/1221) ([durch](https://github.com/durch))
## [nym-binaries-1.0.0](https://github.com/nymtech/nym/tree/nym-binaries-1.0.0) (2022-04-27)
[Full Changelog](https://github.com/nymtech/nym/compare/nym-wallet-v1.0.3...nym-binaries-1.0.0)
## [nym-wallet-v1.0.3](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.3) (2022-04-25)
[Full Changelog](https://github.com/nymtech/nym/compare/nym-binaries-1.0.0-rc.2...nym-wallet-v1.0.3)
**Fixed bugs:**
- \[Issue\] Wallet 1.0.2 cannot send NYM tokens from a DelayedVestingAccount [\#1215](https://github.com/nymtech/nym/issues/1215)
- Main README not showing properly with GitHub dark mode [\#1211](https://github.com/nymtech/nym/issues/1211)
**Merged pull requests:**
- Bugfix - wallet undelegation for vesting accounts [\#1220](https://github.com/nymtech/nym/pull/1220) ([mmsinclair](https://github.com/mmsinclair))
- Bugfix/delegation reconcile [\#1219](https://github.com/nymtech/nym/pull/1219) ([jstuczyn](https://github.com/jstuczyn))
- Bugfix/query proxied pending delegations [\#1218](https://github.com/nymtech/nym/pull/1218) ([jstuczyn](https://github.com/jstuczyn))
- Using custom gas multiplier in the wallet [\#1217](https://github.com/nymtech/nym/pull/1217) ([jstuczyn](https://github.com/jstuczyn))
- Feature/vesting accounts support [\#1216](https://github.com/nymtech/nym/pull/1216) ([jstuczyn](https://github.com/jstuczyn))
- Release/1.0.0 rc.2 [\#1214](https://github.com/nymtech/nym/pull/1214) ([jstuczyn](https://github.com/jstuczyn))
- chore: fix dark mode rendering [\#1212](https://github.com/nymtech/nym/pull/1212) ([pwnfoo](https://github.com/pwnfoo))
- Feature/spend coconut [\#1210](https://github.com/nymtech/nym/pull/1210) ([neacsu](https://github.com/neacsu))
- Bugfix/unique sphinx key [\#1207](https://github.com/nymtech/nym/pull/1207) ([jstuczyn](https://github.com/jstuczyn))
- Add cache read and write timeouts [\#1206](https://github.com/nymtech/nym/pull/1206) ([durch](https://github.com/durch))
- Additional, more informative routes [\#1204](https://github.com/nymtech/nym/pull/1204) ([durch](https://github.com/durch))
- Feature/aggregated econ dynamics explorer endpoint [\#1203](https://github.com/nymtech/nym/pull/1203) ([jstuczyn](https://github.com/jstuczyn))
- Debugging validator [\#1198](https://github.com/nymtech/nym/pull/1198) ([durch](https://github.com/durch))
- wallet: expose additional validator configuration functionality to the frontend [\#1195](https://github.com/nymtech/nym/pull/1195) ([octol](https://github.com/octol))
- Update rewarding validator address [\#1193](https://github.com/nymtech/nym/pull/1193) ([durch](https://github.com/durch))
- Crypto part of the Groth's NIDKG [\#1182](https://github.com/nymtech/nym/pull/1182) ([jstuczyn](https://github.com/jstuczyn))
- fix unbond page [\#1180](https://github.com/nymtech/nym/pull/1180) ([tommyv1987](https://github.com/tommyv1987))
- Type safe bounds [\#1179](https://github.com/nymtech/nym/pull/1179) ([durch](https://github.com/durch))
- Fix delegation paging [\#1174](https://github.com/nymtech/nym/pull/1174) ([durch](https://github.com/durch))
- Update binaries to rc version [\#1172](https://github.com/nymtech/nym/pull/1172) ([tommyv1987](https://github.com/tommyv1987))
- Bump ansi-regex from 4.1.0 to 4.1.1 in /docker/typescript\_client/upload\_contract [\#1171](https://github.com/nymtech/nym/pull/1171) ([dependabot[bot]](https://github.com/apps/dependabot))
## [nym-binaries-1.0.0-rc.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.0-rc.2) (2022-04-15)
[Full Changelog](https://github.com/nymtech/nym/compare/nym-wallet-v1.0.2...nym-binaries-1.0.0-rc.2)
## [nym-wallet-v1.0.2](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.2) (2022-04-05)
[Full Changelog](https://github.com/nymtech/nym/compare/nym-wallet-v1.0.1...nym-wallet-v1.0.2)
**Merged pull requests:**
- Wallet 1.0.2 visual tweaks [\#1197](https://github.com/nymtech/nym/pull/1197) ([mmsinclair](https://github.com/mmsinclair))
- Password for wallet with routes [\#1196](https://github.com/nymtech/nym/pull/1196) ([fmtabbara](https://github.com/fmtabbara))
- Add auto-updater to Nym Wallet [\#1194](https://github.com/nymtech/nym/pull/1194) ([mmsinclair](https://github.com/mmsinclair))
- Fix clippy warnings for beta toolchain [\#1191](https://github.com/nymtech/nym/pull/1191) ([octol](https://github.com/octol))
- wallet: expose validator urls to the frontend [\#1190](https://github.com/nymtech/nym/pull/1190) ([octol](https://github.com/octol))
- wallet: add test for decrypting stored wallet file [\#1189](https://github.com/nymtech/nym/pull/1189) ([octol](https://github.com/octol))
- Fix clippy warnings [\#1188](https://github.com/nymtech/nym/pull/1188) ([octol](https://github.com/octol))
- Password for wallet with routes [\#1187](https://github.com/nymtech/nym/pull/1187) ([mmsinclair](https://github.com/mmsinclair))
- wallet: add validate\_mnemonic [\#1186](https://github.com/nymtech/nym/pull/1186) ([octol](https://github.com/octol))
- wallet: support removing accounts from the wallet file [\#1185](https://github.com/nymtech/nym/pull/1185) ([octol](https://github.com/octol))
- Feature/adding discord [\#1184](https://github.com/nymtech/nym/pull/1184) ([gala1234](https://github.com/gala1234))
- wallet: config backend for validator selection [\#1183](https://github.com/nymtech/nym/pull/1183) ([octol](https://github.com/octol))
- Add storybook to wallet [\#1178](https://github.com/nymtech/nym/pull/1178) ([mmsinclair](https://github.com/mmsinclair))
- wallet: connection test nymd and api urls independently [\#1170](https://github.com/nymtech/nym/pull/1170) ([octol](https://github.com/octol))
- wallet: wire up account storage [\#1153](https://github.com/nymtech/nym/pull/1153) ([octol](https://github.com/octol))
- Feature/signature on deposit [\#1151](https://github.com/nymtech/nym/pull/1151) ([neacsu](https://github.com/neacsu))
## [nym-wallet-v1.0.1](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.1) (2022-04-05)
[Full Changelog](https://github.com/nymtech/nym/compare/nym-binaries-1.0.0-rc.1...nym-wallet-v1.0.1)
**Closed issues:**
- Check enabling bbbc simultaneously with open access. Estimate what it would take to make this the default compilation target. [\#1175](https://github.com/nymtech/nym/issues/1175)
- Get coconut credential for deposited tokens [\#1138](https://github.com/nymtech/nym/issues/1138)
- Make payments lazy [\#1135](https://github.com/nymtech/nym/issues/1135)
- Uptime on node selection for sets [\#1049](https://github.com/nymtech/nym/issues/1049)
## [nym-binaries-1.0.0-rc.1](https://github.com/nymtech/nym/tree/nym-binaries-1.0.0-rc.1) (2022-03-28)
[Full Changelog](https://github.com/nymtech/nym/compare/nym-wallet-v1.0.0...nym-binaries-1.0.0-rc.1)
**Fixed bugs:**
- \[Issue\]cargo build --release issue [\#1101](https://github.com/nymtech/nym/issues/1101)
- appimage fail to load in Fedora [\#1098](https://github.com/nymtech/nym/issues/1098)
- \[Issue\] React Example project does not compile when using @nymproject/nym-client-wasm v0.9.0-1 [\#878](https://github.com/nymtech/nym/issues/878)
**Closed issues:**
- Make mainnet coin transfers work [\#1096](https://github.com/nymtech/nym/issues/1096)
- Make Nym wallet validators configurable at runtime [\#1026](https://github.com/nymtech/nym/issues/1026)
- Project Platypus e2e / integration testing [\#942](https://github.com/nymtech/nym/issues/942)
- \[Coconut\]: Replace ElGamal with Pedersen commitments [\#901](https://github.com/nymtech/nym/issues/901)
**Merged pull requests:**
- Different values for mixes and gateways [\#1169](https://github.com/nymtech/nym/pull/1169) ([durch](https://github.com/durch))
- Add global blacklist to validator-cache [\#1168](https://github.com/nymtech/nym/pull/1168) ([durch](https://github.com/durch))
- Feature/upgrade rewarding sandbox [\#1167](https://github.com/nymtech/nym/pull/1167) ([durch](https://github.com/durch))
- Bump node-forge from 1.2.1 to 1.3.0 [\#1165](https://github.com/nymtech/nym/pull/1165) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump minimist from 1.2.5 to 1.2.6 in /nym-wallet/webdriver [\#1164](https://github.com/nymtech/nym/pull/1164) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump minimist from 1.2.5 to 1.2.6 in /clients/tauri-client [\#1163](https://github.com/nymtech/nym/pull/1163) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump minimist from 1.2.5 to 1.2.6 in /clients/webassembly/js-example [\#1162](https://github.com/nymtech/nym/pull/1162) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump minimist from 1.2.5 to 1.2.6 in /clients/native/examples/js-examples/websocket [\#1160](https://github.com/nymtech/nym/pull/1160) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump minimist from 1.2.5 to 1.2.6 in /docker/typescript\_client/upload\_contract [\#1159](https://github.com/nymtech/nym/pull/1159) ([dependabot[bot]](https://github.com/apps/dependabot))
- Feature/vesting full [\#1158](https://github.com/nymtech/nym/pull/1158) ([fmtabbara](https://github.com/fmtabbara))
- get\_current\_epoch tauri [\#1156](https://github.com/nymtech/nym/pull/1156) ([durch](https://github.com/durch))
- Cleanup [\#1155](https://github.com/nymtech/nym/pull/1155) ([durch](https://github.com/durch))
- Feature flag reward payments [\#1154](https://github.com/nymtech/nym/pull/1154) ([durch](https://github.com/durch))
- Add Query endpoints for calculating rewards [\#1152](https://github.com/nymtech/nym/pull/1152) ([durch](https://github.com/durch))
- Pending endpoints [\#1150](https://github.com/nymtech/nym/pull/1150) ([durch](https://github.com/durch))
- wallet: add logging [\#1149](https://github.com/nymtech/nym/pull/1149) ([octol](https://github.com/octol))
- wallet: use Urls rather than Strings for validator urls [\#1148](https://github.com/nymtech/nym/pull/1148) ([octol](https://github.com/octol))
- Change accumulated reward to Option, migrate delegations [\#1147](https://github.com/nymtech/nym/pull/1147) ([durch](https://github.com/durch))
- wallet: fetch validators url remotely if available [\#1146](https://github.com/nymtech/nym/pull/1146) ([octol](https://github.com/octol))
- Fix delegated\_free calculation [\#1145](https://github.com/nymtech/nym/pull/1145) ([durch](https://github.com/durch))
- Update Nym wallet dependencies to use `ts-packages` [\#1144](https://github.com/nymtech/nym/pull/1144) ([mmsinclair](https://github.com/mmsinclair))
- wallet: try validators one by one if available [\#1143](https://github.com/nymtech/nym/pull/1143) ([octol](https://github.com/octol))
- Update Network Explorer Packages and add mix node identity key copy [\#1142](https://github.com/nymtech/nym/pull/1142) ([mmsinclair](https://github.com/mmsinclair))
- Feature/vesting token pool selector [\#1140](https://github.com/nymtech/nym/pull/1140) ([fmtabbara](https://github.com/fmtabbara))
- Add `ts-packages` for shared Typescript packages [\#1139](https://github.com/nymtech/nym/pull/1139) ([mmsinclair](https://github.com/mmsinclair))
- allow main-net prefix and denom to work [\#1137](https://github.com/nymtech/nym/pull/1137) ([tommyv1987](https://github.com/tommyv1987))
- Upgrade blake3 to v1.3.1 and tauri to 1.0.0-rc.3 [\#1136](https://github.com/nymtech/nym/pull/1136) ([mmsinclair](https://github.com/mmsinclair))
- Bump url-parse from 1.5.7 to 1.5.10 in /clients/native/examples/js-examples/websocket [\#1134](https://github.com/nymtech/nym/pull/1134) ([dependabot[bot]](https://github.com/apps/dependabot))
- Use network explorer map data with disputed areas [\#1133](https://github.com/nymtech/nym/pull/1133) ([Baro1905](https://github.com/Baro1905))
- Feature/vesting UI [\#1132](https://github.com/nymtech/nym/pull/1132) ([fmtabbara](https://github.com/fmtabbara))
- Refactor to a lazy rewarding system [\#1127](https://github.com/nymtech/nym/pull/1127) ([durch](https://github.com/durch))
- Bump ws from 6.2.1 to 6.2.2 in /clients/webassembly/js-example [\#1126](https://github.com/nymtech/nym/pull/1126) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump url-parse from 1.4.7 to 1.5.7 in /clients/webassembly/react-example [\#1125](https://github.com/nymtech/nym/pull/1125) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump url-parse from 1.5.4 to 1.5.7 in /clients/native/examples/js-examples/websocket [\#1124](https://github.com/nymtech/nym/pull/1124) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump url-parse from 1.5.1 to 1.5.7 in /clients/webassembly/js-example [\#1122](https://github.com/nymtech/nym/pull/1122) ([dependabot[bot]](https://github.com/apps/dependabot))
- update contract address [\#1121](https://github.com/nymtech/nym/pull/1121) ([tommyv1987](https://github.com/tommyv1987))
- Refactor GitHub Actions notifications [\#1119](https://github.com/nymtech/nym/pull/1119) ([mmsinclair](https://github.com/mmsinclair))
- Change `pledge` to `bond` in gateway list [\#1118](https://github.com/nymtech/nym/pull/1118) ([mmsinclair](https://github.com/mmsinclair))
- Bump follow-redirects from 1.14.7 to 1.14.8 in /contracts/basic-bandwidth-generation [\#1117](https://github.com/nymtech/nym/pull/1117) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump follow-redirects from 1.14.3 to 1.14.8 in /explorer [\#1116](https://github.com/nymtech/nym/pull/1116) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump follow-redirects from 1.14.5 to 1.14.8 in /nym-wallet [\#1115](https://github.com/nymtech/nym/pull/1115) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump follow-redirects from 1.14.7 to 1.14.8 in /clients/native/examples/js-examples/websocket [\#1114](https://github.com/nymtech/nym/pull/1114) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump follow-redirects from 1.14.7 to 1.14.8 in /testnet-faucet [\#1113](https://github.com/nymtech/nym/pull/1113) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump follow-redirects from 1.14.1 to 1.14.8 in /clients/webassembly/js-example [\#1112](https://github.com/nymtech/nym/pull/1112) ([dependabot[bot]](https://github.com/apps/dependabot))
- Feature/vesting get current period [\#1111](https://github.com/nymtech/nym/pull/1111) ([durch](https://github.com/durch))
- Bump simple-get from 2.8.1 to 2.8.2 in /contracts/basic-bandwidth-generation [\#1110](https://github.com/nymtech/nym/pull/1110) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump simple-get from 3.1.0 to 3.1.1 in /explorer [\#1109](https://github.com/nymtech/nym/pull/1109) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump simple-get from 3.1.0 to 3.1.1 in /clients/tauri-client [\#1108](https://github.com/nymtech/nym/pull/1108) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump simple-get from 3.1.0 to 3.1.1 in /nym-wallet [\#1107](https://github.com/nymtech/nym/pull/1107) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump node-sass from 4.14.1 to 7.0.0 in /clients/webassembly/react-example [\#1105](https://github.com/nymtech/nym/pull/1105) ([dependabot[bot]](https://github.com/apps/dependabot))
- Fix hardcoded period logic [\#1104](https://github.com/nymtech/nym/pull/1104) ([durch](https://github.com/durch))
- Fixed underflow in rewarding all delegators [\#1099](https://github.com/nymtech/nym/pull/1099) ([jstuczyn](https://github.com/jstuczyn))
- Emit original bond as part of rewarding event [\#1094](https://github.com/nymtech/nym/pull/1094) ([jstuczyn](https://github.com/jstuczyn))
- Add UpdateMixnodeConfigOnBehalf to vestng contract [\#1091](https://github.com/nymtech/nym/pull/1091) ([durch](https://github.com/durch))
- Fixes infinite loops in requests involving pagination [\#1085](https://github.com/nymtech/nym/pull/1085) ([jstuczyn](https://github.com/jstuczyn))
- Removes migration code [\#1071](https://github.com/nymtech/nym/pull/1071) ([jstuczyn](https://github.com/jstuczyn))
- feature/pedersen-commitments [\#1048](https://github.com/nymtech/nym/pull/1048) ([danielementary](https://github.com/danielementary))
- Feature/reuse init owner [\#970](https://github.com/nymtech/nym/pull/970) ([neacsu](https://github.com/neacsu))
## [nym-wallet-v1.0.0](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.0) (2022-02-03)
[Full Changelog](https://github.com/nymtech/nym/compare/v0.12.1...nym-wallet-v1.0.0)
**Implemented enhancements:**
- \[Feature Request\] Please enable registration without need for Telegram account [\#1016](https://github.com/nymtech/nym/issues/1016)
- Fast mixnode launch with a pre-built ISO + VM software [\#1001](https://github.com/nymtech/nym/issues/1001)
**Fixed bugs:**
- \[Issue\] [\#1000](https://github.com/nymtech/nym/issues/1000)
- \[Issue\] `nym-client` requires multiple attempts to run a server [\#869](https://github.com/nymtech/nym/issues/869)
- De-'float'-ing `Interval` \(`Display` impl + `serde`\) [\#1065](https://github.com/nymtech/nym/pull/1065) ([jstuczyn](https://github.com/jstuczyn))
- display client address on wallet creation [\#1058](https://github.com/nymtech/nym/pull/1058) ([fmtabbara](https://github.com/fmtabbara))
**Closed issues:**
- Rewarded set inclusion probability API endpoint [\#1037](https://github.com/nymtech/nym/issues/1037)
- Update cw-storage-plus to 0.11 [\#1032](https://github.com/nymtech/nym/issues/1032)
- Change `u128` fields in `RewardEstimationResponse` to `u64` [\#1029](https://github.com/nymtech/nym/issues/1029)
- Test out the mainnet Gravity Bridge [\#1006](https://github.com/nymtech/nym/issues/1006)
- Add vesting contract interface to nym-wallet [\#959](https://github.com/nymtech/nym/issues/959)
- Mixnode crash [\#486](https://github.com/nymtech/nym/issues/486)
**Merged pull requests:**
- create custom urls for mainnet [\#1095](https://github.com/nymtech/nym/pull/1095) ([fmtabbara](https://github.com/fmtabbara))
- Wallet signing on MacOS [\#1093](https://github.com/nymtech/nym/pull/1093) ([mmsinclair](https://github.com/mmsinclair))
- Fix rust 2018 idioms warnings [\#1092](https://github.com/nymtech/nym/pull/1092) ([octol](https://github.com/octol))
- Prevent contract overwriting [\#1090](https://github.com/nymtech/nym/pull/1090) ([durch](https://github.com/durch))
- Logout operation [\#1087](https://github.com/nymtech/nym/pull/1087) ([jstuczyn](https://github.com/jstuczyn))
- Update to rust edition 2021 everywhere [\#1086](https://github.com/nymtech/nym/pull/1086) ([octol](https://github.com/octol))
- Tag contract errors, and print out lines for easier QA [\#1084](https://github.com/nymtech/nym/pull/1084) ([durch](https://github.com/durch))
- Feature/flexible vesting + utility queries [\#1083](https://github.com/nymtech/nym/pull/1083) ([durch](https://github.com/durch))
- Bump @openzeppelin/contracts from 4.3.1 to 4.4.2 in /contracts/basic-bandwidth-generation [\#1082](https://github.com/nymtech/nym/pull/1082) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump nth-check from 2.0.0 to 2.0.1 in /clients/native/examples/js-examples/websocket [\#1081](https://github.com/nymtech/nym/pull/1081) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump url-parse from 1.5.1 to 1.5.4 in /clients/native/examples/js-examples/websocket [\#1080](https://github.com/nymtech/nym/pull/1080) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump follow-redirects from 1.14.1 to 1.14.7 in /clients/native/examples/js-examples/websocket [\#1079](https://github.com/nymtech/nym/pull/1079) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump nanoid from 3.1.23 to 3.2.0 in /clients/native/examples/js-examples/websocket [\#1078](https://github.com/nymtech/nym/pull/1078) ([dependabot[bot]](https://github.com/apps/dependabot))
- Setup basic test for mixnode stats reporting [\#1077](https://github.com/nymtech/nym/pull/1077) ([octol](https://github.com/octol))
- Make wallet\_address mandatory for mixnode init [\#1076](https://github.com/nymtech/nym/pull/1076) ([octol](https://github.com/octol))
- Tidy nym-mixnode module visibility [\#1075](https://github.com/nymtech/nym/pull/1075) ([octol](https://github.com/octol))
- Feature/wallet login with password [\#1074](https://github.com/nymtech/nym/pull/1074) ([fmtabbara](https://github.com/fmtabbara))
- Add trait to mock client dependency in DelayForwarder [\#1073](https://github.com/nymtech/nym/pull/1073) ([octol](https://github.com/octol))
- Bump rust-version to latest stable for nym-mixnode [\#1072](https://github.com/nymtech/nym/pull/1072) ([octol](https://github.com/octol))
- Fixes CI for our wasm build [\#1069](https://github.com/nymtech/nym/pull/1069) ([jstuczyn](https://github.com/jstuczyn))
- Add @octol as codeowner [\#1068](https://github.com/nymtech/nym/pull/1068) ([octol](https://github.com/octol))
- set-up inclusion probability [\#1067](https://github.com/nymtech/nym/pull/1067) ([fmtabbara](https://github.com/fmtabbara))
- Feature/wasm client [\#1066](https://github.com/nymtech/nym/pull/1066) ([neacsu](https://github.com/neacsu))
- Changed bech32\_prefix from punk to nymt [\#1064](https://github.com/nymtech/nym/pull/1064) ([jstuczyn](https://github.com/jstuczyn))
- Bump nanoid from 3.1.30 to 3.2.0 in /testnet-faucet [\#1063](https://github.com/nymtech/nym/pull/1063) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump nanoid from 3.1.30 to 3.2.0 in /nym-wallet [\#1062](https://github.com/nymtech/nym/pull/1062) ([dependabot[bot]](https://github.com/apps/dependabot))
- Rework vesting contract storage [\#1061](https://github.com/nymtech/nym/pull/1061) ([durch](https://github.com/durch))
- Mixnet Contract constants extraction [\#1060](https://github.com/nymtech/nym/pull/1060) ([jstuczyn](https://github.com/jstuczyn))
- fix: make explorer footer year dynamic [\#1059](https://github.com/nymtech/nym/pull/1059) ([martinyung](https://github.com/martinyung))
- Add mnemonic just on creation, to display it [\#1057](https://github.com/nymtech/nym/pull/1057) ([neacsu](https://github.com/neacsu))
- Network Explorer: updates to API and UI to show the active set [\#1056](https://github.com/nymtech/nym/pull/1056) ([mmsinclair](https://github.com/mmsinclair))
- Made contract addresses for query NymdClient construction optional [\#1055](https://github.com/nymtech/nym/pull/1055) ([jstuczyn](https://github.com/jstuczyn))
- Introduced RPC query for total token supply [\#1053](https://github.com/nymtech/nym/pull/1053) ([jstuczyn](https://github.com/jstuczyn))
- Feature/tokio console [\#1052](https://github.com/nymtech/nym/pull/1052) ([durch](https://github.com/durch))
- Implemented beta clippy lint recommendations [\#1051](https://github.com/nymtech/nym/pull/1051) ([jstuczyn](https://github.com/jstuczyn))
- add new function to update profit percentage [\#1050](https://github.com/nymtech/nym/pull/1050) ([fmtabbara](https://github.com/fmtabbara))
- Upgrade Clap and use declarative argument parsing for nym-mixnode [\#1047](https://github.com/nymtech/nym/pull/1047) ([octol](https://github.com/octol))
- Feature/additional bond validation [\#1046](https://github.com/nymtech/nym/pull/1046) ([fmtabbara](https://github.com/fmtabbara))
- Fix clippy on relevant lints [\#1044](https://github.com/nymtech/nym/pull/1044) ([neacsu](https://github.com/neacsu))
- Bump shelljs from 0.8.4 to 0.8.5 in /contracts/basic-bandwidth-generation [\#1043](https://github.com/nymtech/nym/pull/1043) ([dependabot[bot]](https://github.com/apps/dependabot))
- Endpoint for rewarded set inclusion probabilities [\#1042](https://github.com/nymtech/nym/pull/1042) ([durch](https://github.com/durch))
- Bump follow-redirects from 1.14.4 to 1.14.7 in /contracts/basic-bandwidth-generation [\#1041](https://github.com/nymtech/nym/pull/1041) ([dependabot[bot]](https://github.com/apps/dependabot))
- Bump follow-redirects from 1.14.5 to 1.14.7 in /testnet-faucet [\#1040](https://github.com/nymtech/nym/pull/1040) ([dependabot[bot]](https://github.com/apps/dependabot))
- Feature/node settings update [\#1036](https://github.com/nymtech/nym/pull/1036) ([fmtabbara](https://github.com/fmtabbara))
- Migrate to cw-storage-plus 0.11.1 [\#1035](https://github.com/nymtech/nym/pull/1035) ([durch](https://github.com/durch))
- Bump @openzeppelin/contracts from 4.4.1 to 4.4.2 in /contracts/basic-bandwidth-generation [\#1034](https://github.com/nymtech/nym/pull/1034) ([dependabot[bot]](https://github.com/apps/dependabot))
- Feature/configurable wallet [\#1033](https://github.com/nymtech/nym/pull/1033) ([neacsu](https://github.com/neacsu))
- Feature/downcast reward estimation [\#1031](https://github.com/nymtech/nym/pull/1031) ([durch](https://github.com/durch))
- Wallet UI updates [\#1028](https://github.com/nymtech/nym/pull/1028) ([fmtabbara](https://github.com/fmtabbara))
- Remove migration code [\#1027](https://github.com/nymtech/nym/pull/1027) ([neacsu](https://github.com/neacsu))
- Chore/stricter dependency requirements [\#1025](https://github.com/nymtech/nym/pull/1025) ([jstuczyn](https://github.com/jstuczyn))
- Feature/validator api client endpoints [\#1024](https://github.com/nymtech/nym/pull/1024) ([jstuczyn](https://github.com/jstuczyn))
- Updated cosmrs to 0.4.1 [\#1023](https://github.com/nymtech/nym/pull/1023) ([jstuczyn](https://github.com/jstuczyn))
- Feature/testnet deploy scripts [\#1022](https://github.com/nymtech/nym/pull/1022) ([mfahampshire](https://github.com/mfahampshire))
- Changed wallet's client to a full validator client [\#1021](https://github.com/nymtech/nym/pull/1021) ([jstuczyn](https://github.com/jstuczyn))
- Fix 404 link [\#1020](https://github.com/nymtech/nym/pull/1020) ([RiccardoMasutti](https://github.com/RiccardoMasutti))
- Feature/additional mixnode endpoints [\#1019](https://github.com/nymtech/nym/pull/1019) ([jstuczyn](https://github.com/jstuczyn))
- Introduced denom check when trying to withdraw vested coins [\#1018](https://github.com/nymtech/nym/pull/1018) ([jstuczyn](https://github.com/jstuczyn))
- Add network defaults for qa [\#1017](https://github.com/nymtech/nym/pull/1017) ([neacsu](https://github.com/neacsu))
- Feature/expanded events [\#1015](https://github.com/nymtech/nym/pull/1015) ([jstuczyn](https://github.com/jstuczyn))
- update frontend to use new profit update api [\#1014](https://github.com/nymtech/nym/pull/1014) ([fmtabbara](https://github.com/fmtabbara))
- Feature/node state endpoint [\#1013](https://github.com/nymtech/nym/pull/1013) ([jstuczyn](https://github.com/jstuczyn))
- Feature/hourly set updates [\#1012](https://github.com/nymtech/nym/pull/1012) ([durch](https://github.com/durch))
- Feature/remove unused profit margin [\#1011](https://github.com/nymtech/nym/pull/1011) ([neacsu](https://github.com/neacsu))
- Feature/explorer node status [\#1010](https://github.com/nymtech/nym/pull/1010) ([jstuczyn](https://github.com/jstuczyn))
- Use serial integer instead of random [\#1009](https://github.com/nymtech/nym/pull/1009) ([durch](https://github.com/durch))
- Feature/configure profit [\#1008](https://github.com/nymtech/nym/pull/1008) ([neacsu](https://github.com/neacsu))
- Feature/fix gateway sign [\#1004](https://github.com/nymtech/nym/pull/1004) ([neacsu](https://github.com/neacsu))
- Fix clippy [\#1003](https://github.com/nymtech/nym/pull/1003) ([neacsu](https://github.com/neacsu))
- Update wallet version [\#998](https://github.com/nymtech/nym/pull/998) ([tommyv1987](https://github.com/tommyv1987))
- Fix wallet build instructions [\#997](https://github.com/nymtech/nym/pull/997) ([tommyv1987](https://github.com/tommyv1987))
- Make the separation between testnet-mode and erc20 bandwidth mode clearer [\#994](https://github.com/nymtech/nym/pull/994) ([neacsu](https://github.com/neacsu))
- Bump @openzeppelin/contracts from 3.4.0 to 4.4.1 in /contracts/basic-bandwidth-generation [\#983](https://github.com/nymtech/nym/pull/983) ([dependabot[bot]](https://github.com/apps/dependabot))
- Feature/implicit runtime [\#973](https://github.com/nymtech/nym/pull/973) ([jstuczyn](https://github.com/jstuczyn))
- Differentiate staking and ownership [\#961](https://github.com/nymtech/nym/pull/961) ([durch](https://github.com/durch))
## [v0.12.1](https://github.com/nymtech/nym/tree/v0.12.1) (2021-12-23)
[Full Changelog](https://github.com/nymtech/nym/compare/v0.12.0...v0.12.1)
**Implemented enhancements:**
- Add version check to binaries [\#967](https://github.com/nymtech/nym/issues/967)
**Fixed bugs:**
- \[Issue\] NYM wallet doesn't work after login [\#995](https://github.com/nymtech/nym/issues/995)
- \[Issue\] [\#993](https://github.com/nymtech/nym/issues/993)
- NYM wallet setup trouble\[Issue\] [\#958](https://github.com/nymtech/nym/issues/958)
## [v0.12.0](https://github.com/nymtech/nym/tree/v0.12.0) (2021-12-21)
[Full Changelog](https://github.com/nymtech/nym/compare/v0.11.0...v0.12.0)
@@ -58,6 +404,7 @@
**Merged pull requests:**
- Update wallet to align with versioning on nodes and gateways [\#991](https://github.com/nymtech/nym/pull/991) ([tommyv1987](https://github.com/tommyv1987))
- Fix success view messages. [\#990](https://github.com/nymtech/nym/pull/990) ([tommyv1987](https://github.com/tommyv1987))
- Feature/enable signature check [\#989](https://github.com/nymtech/nym/pull/989) ([neacsu](https://github.com/neacsu))
- Update mixnet contract address [\#988](https://github.com/nymtech/nym/pull/988) ([neacsu](https://github.com/neacsu))
Generated
+343 -222
View File
File diff suppressed because it is too large Load Diff
+6 -1
View File
@@ -9,6 +9,10 @@ overflow-checks = true
[profile.dev]
panic = "abort"
[profile.test]
# equivalent of running in `--release` (but since we're in test profile we're keeping overflow checks and all of those by default)
opt-level = 3
[workspace]
resolver = "2"
@@ -18,14 +22,15 @@ members = [
"clients/native",
"clients/native/websocket-requests",
"clients/socks5",
# "clients/tauri-client/src-tauri",
"common/client-libs/gateway-client",
"common/client-libs/mixnet-client",
"common/client-libs/validator-client",
"common/credential-storage",
"common/coconut-interface",
"common/config",
"common/credentials",
"common/crypto",
"common/crypto/dkg",
"common/bandwidth-claim-contract",
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
"common/cosmwasm-smart-contracts/contracts-common",
+20 -16
View File
@@ -40,36 +40,40 @@ Node, node operator and delegator rewards are determined according to the princi
|Symbol|Definition|
|---|---|
|<img src="https://render.githubusercontent.com/render/math?math=R">|global share of rewards available, starts at 2% of the reward pool.
|<img src="https://render.githubusercontent.com/render/math?math=R_{i}">|node reward for mixnode `i`.
|<img src="https://render.githubusercontent.com/render/math?math=\sigma_{i}">|ratio of total node stake (node bond + all delegations) to the token circulating supply.
|<img src="https://render.githubusercontent.com/render/math?math=\lambda_{i}">|ratio of stake operator has 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.
|<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 NYMT.
|<img src="https://render.githubusercontent.com/render/math?math=R#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}R#gh-dark-mode-only">|global share of rewards available, starts at 2% of the reward pool.
|<img src="https://render.githubusercontent.com/render/math?math=R_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}R_{i}#gh-dark-mode-only">|node reward for mixnode `i`.
|<img src="https://render.githubusercontent.com/render/math?math=\sigma_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\sigma_{i}#gh-dark-mode-only">|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}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\lambda_{i}#gh-dark-mode-only">|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}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\omega_{i}#gh-dark-mode-only">|fraction of total effort undertaken by node `i`, set to `1/k`.
|<img src="https://render.githubusercontent.com/render/math?math=k#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}k#gh-dark-mode-only">|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#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}\alpha#gh-dark-mode-only">|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}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PM_{i}#gh-dark-mode-only">|declared profit margin of operator `i`, defaults to 10% in.
|<img src="https://render.githubusercontent.com/render/math?math=PF_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PF_{i}#gh-dark-mode-only">|uptime of node `i`, scaled to 0 - 1, for the rewarding epoch
|<img src="https://render.githubusercontent.com/render/math?math=PP_{i}#gh-light-mode-only"><img src="https://render.githubusercontent.com/render/math?math=\color{white}PP_{i}#gh-dark-mode-only">|cost of operating node `i` for the duration of the rewarding epoch, set to 40 NYMT.
Node reward for node `i` is determined as:
<img src="https://render.githubusercontent.com/render/math?math=R_{i}=PF_{i} \cdot R \cdot (\sigma^'_{i} \cdot \omega_{i} \cdot k %2b \alpha \cdot \lambda^'_{i} \cdot \sigma^'_{i} \cdot k)/(1 %2b \alpha)">
<img src="https://render.githubusercontent.com/render/math?math=R_{i}=PF_{i} \cdot R \cdot (\sigma^'_{i} \cdot \omega_{i} \cdot k %2b \alpha \cdot \lambda^'_{i} \cdot \sigma^'_{i} \cdot k)/(1 %2b \alpha)#gh-light-mode-only">
<img src="https://render.githubusercontent.com/render/math?math=\color{white}R_{i}=PF_{i} \cdot R \cdot (\sigma^'_{i} \cdot \omega_{i} \cdot k %2b \alpha \cdot \lambda^'_{i} \cdot \sigma^'_{i} \cdot k)/(1 %2b \alpha)#gh-dark-mode-only">
where:
<img src="https://render.githubusercontent.com/render/math?math=\sigma^'_{i} = min\{\sigma_{i}, 1/k\}">
<img src="https://render.githubusercontent.com/render/math?math=\sigma^'_{i} = min\{\sigma_{i}, 1/k\}#gh-light-mode-only">
<img src="https://render.githubusercontent.com/render/math?math=\color{white}\sigma^'_{i} = min\{\sigma_{i}, 1/k\}#gh-dark-mode-only">
and
<img src="https://render.githubusercontent.com/render/math?math=\lambda^'_{i} = min\{\lambda_{i}, 1/k\}">
<img src="https://render.githubusercontent.com/render/math?math=\lambda^'_{i} = min\{\lambda_{i}, 1/k\}#gh-light-mode-only">
<img src="https://render.githubusercontent.com/render/math?math=\color{white}\lambda^'_{i} = min\{\lambda_{i}, 1/k\}#gh-dark-mode-only">
Operator of node `i` is credited with the following amount:
<img src="https://render.githubusercontent.com/render/math?math=min\{PP_{i},R_{i})\} %2b max\{0, (PM_{i} %2b (1 - PM_{i}) \cdot \lambda_{i}/\delta_{i}) \cdot (R_{i} - PP_{i})\}">
<img src="https://render.githubusercontent.com/render/math?math=min\{PP_{i},R_{i})\} %2b max\{0, (PM_{i} %2b (1 - PM_{i}) \cdot \lambda_{i}/\delta_{i}) \cdot (R_{i} - PP_{i})\}#gh-light-mode-only">
<img src="https://render.githubusercontent.com/render/math?math=\color{white}min\{PP_{i},R_{i})\} %2b max\{0, (PM_{i} %2b (1 - PM_{i}) \cdot \lambda_{i}/\delta_{i}) \cdot (R_{i} - PP_{i})\}#gh-dark-mode-only">
Delegate with stake `s` recieves:
<img src="https://render.githubusercontent.com/render/math?math=max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}">
<img src="https://render.githubusercontent.com/render/math?math=max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}#gh-light-mode-only">
<img src="https://render.githubusercontent.com/render/math?math=\color{white}max\{0, (1-PM_{i}) \cdot (s^'/\sigma_{i}) \cdot (R_{i} - PP_{i})\}#gh-dark-mode-only">
where `s'` is stake `s` scaled over total token circulating supply.
+10
View File
@@ -0,0 +1,10 @@
Critical bug or security issue 💥
If you're here because you're trying to figure out how to notify us of a security issue, go to Discord, and alert the core engineers:
Dave Hrycyszyn futurechimp#5430
Drazen Urch drazen#4873
Jedrzej Stuczynski "Jedrzej | Nym#5666"
Please avoid opening public issues on GitHub that contain information about a potential security vulnerability as this makes it difficult to reduce the impact and harm of valid security issues.
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "client-core"
version = "1.0.0-rc.1"
version = "1.0.1"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021"
+16 -30
View File
@@ -103,22 +103,15 @@ impl<T: NymConfig> Config<T> {
self::Client::<T>::default_reply_encryption_key_store_path(&id);
}
#[cfg(not(feature = "coconut"))]
if self
.client
.backup_bandwidth_token_keys_dir
.as_os_str()
.is_empty()
{
self.client.backup_bandwidth_token_keys_dir =
self::Client::<T>::default_backup_bandwidth_token_keys_dir(&id);
if self.client.database_path.as_os_str().is_empty() {
self.client.database_path = self::Client::<T>::default_database_path(&id);
}
self.client.id = id;
}
pub fn with_testnet_mode(&mut self, testnet_mode: bool) {
self.client.testnet_mode = testnet_mode;
pub fn with_disabled_credentials(&mut self, disabled_credentials_mode: bool) {
self.client.disabled_credentials_mode = disabled_credentials_mode;
}
pub fn with_gateway_endpoint<S: Into<String>>(&mut self, id: S, owner: S, listener: S) {
@@ -161,8 +154,8 @@ impl<T: NymConfig> Config<T> {
self.client.id.clone()
}
pub fn get_testnet_mode(&self) -> bool {
self.client.testnet_mode
pub fn get_disabled_credentials_mode(&self) -> bool {
self.client.disabled_credentials_mode
}
pub fn get_nym_root_directory(&self) -> PathBuf {
@@ -213,9 +206,8 @@ impl<T: NymConfig> Config<T> {
self.client.gateway_endpoint.gateway_listener.clone()
}
#[cfg(not(feature = "coconut"))]
pub fn get_backup_bandwidth_token_keys_dir(&self) -> PathBuf {
self.client.backup_bandwidth_token_keys_dir.clone()
pub fn get_database_path(&self) -> PathBuf {
self.client.database_path.clone()
}
#[cfg(not(feature = "coconut"))]
@@ -302,10 +294,10 @@ 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
/// Indicates whether this client is running in a disabled credentials mode, thus attempting
/// to claim bandwidth without presenting bandwidth credentials.
#[serde(default)]
testnet_mode: bool,
disabled_credentials_mode: bool,
/// Addresses to APIs running on validator from which the client gets the view of the network.
validator_api_urls: Vec<Url>,
@@ -337,11 +329,8 @@ pub struct Client<T> {
/// Information regarding how the client should send data to gateway.
gateway_endpoint: GatewayEndpoint,
/// Path to directory containing public/private keys used for bandwidth token purchase.
/// Those are saved in case of emergency, to be able to reclaim bandwidth tokens.
/// The public key is the name of the file, while the private key is the content.
#[cfg(not(feature = "coconut"))]
backup_bandwidth_token_keys_dir: PathBuf,
/// Path to the database containing bandwidth credentials of this client.
database_path: PathBuf,
/// Ethereum private key.
#[cfg(not(feature = "coconut"))]
@@ -365,7 +354,7 @@ impl<T: NymConfig> Default for Client<T> {
Client {
version: env!("CARGO_PKG_VERSION").to_string(),
id: "".to_string(),
testnet_mode: false,
disabled_credentials_mode: true,
validator_api_urls: default_api_endpoints(),
private_identity_key_file: Default::default(),
public_identity_key_file: Default::default(),
@@ -375,8 +364,7 @@ impl<T: NymConfig> Default for Client<T> {
ack_key_file: Default::default(),
reply_encryption_key_store_path: Default::default(),
gateway_endpoint: Default::default(),
#[cfg(not(feature = "coconut"))]
backup_bandwidth_token_keys_dir: Default::default(),
database_path: Default::default(),
#[cfg(not(feature = "coconut"))]
eth_private_key: "".to_string(),
#[cfg(not(feature = "coconut"))]
@@ -415,10 +403,8 @@ impl<T: NymConfig> Client<T> {
fn default_reply_encryption_key_store_path(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("reply_key_store")
}
#[cfg(not(feature = "coconut"))]
fn default_backup_bandwidth_token_keys_dir(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("backup_bandwidth_token_keys")
fn default_database_path(id: &str) -> PathBuf {
T::default_data_directory(Some(id)).join("db.sqlite")
}
}
+1
View File
@@ -20,6 +20,7 @@ tokio = { version = "1.4", features = ["rt-multi-thread", "net", "signal", "macr
coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
coconut-interface = { path = "../../common/coconut-interface" }
credentials = { path = "../../common/credentials" }
credential-storage = { path = "../../common/credential-storage" }
crypto = { path = "../../common/crypto", features = ["rand", "asymmetric", "symmetric", "aes", "hashing"] }
network-defaults = { path = "../../common/network-defaults" }
pemstore = { path = "../../common/pemstore" }
File diff suppressed because one or more lines are too long
+6 -13
View File
@@ -1,19 +1,15 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bip39::Mnemonic;
use coconut_bandwidth_contract_common::deposit::DepositData;
use std::str::FromStr;
use url::Url;
use crate::error::Result;
use crate::{CONTRACT_ADDRESS, MNEMONIC, NYMD_URL};
use bip39::Mnemonic;
use coconut_bandwidth_contract_common::deposit::DepositData;
use coconut_bandwidth_contract_common::msg::ExecuteMsg;
use network_defaults::DEFAULT_NETWORK;
use validator_client::nymd::{
AccountId, CosmosCoin, Decimal, Denom, NymdClient, SigningNymdClient,
};
use std::str::FromStr;
use url::Url;
use validator_client::nymd::{AccountId, Coin, Denom, NymdClient, SigningNymdClient};
pub(crate) struct Client {
nymd_client: NymdClient<SigningNymdClient>,
@@ -55,10 +51,7 @@ impl Client {
let req = ExecuteMsg::DepositFunds {
data: DepositData::new(info.to_string(), verification_key, encryption_key),
};
let funds = vec![CosmosCoin {
denom: self.denom.clone(),
amount: Decimal::from(amount),
}];
let funds = vec![Coin::new(amount as u128, self.denom.to_string())];
Ok(self
.nymd_client
.execute(&self.contract_address, &req, Default::default(), "", funds)
+15 -18
View File
@@ -9,6 +9,8 @@ use std::str::FromStr;
use url::Url;
use coconut_interface::{Attribute, Base58, BlindSignRequest, Bytable, Parameters};
use credential_storage::storage::Storage;
use credential_storage::PersistentStorage;
use credentials::coconut::bandwidth::{BandwidthVoucher, TOTAL_ATTRIBUTES};
use credentials::coconut::utils::obtain_aggregate_signature;
use crypto::asymmetric::{encryption, identity};
@@ -32,7 +34,7 @@ pub(crate) enum Commands {
#[async_trait]
pub(crate) trait Execute {
async fn execute(&self, db: &mut PickleDb) -> Result<()>;
async fn execute(&self, db: &mut PickleDb, shared_storage: PersistentStorage) -> Result<()>;
}
#[derive(Args, Clone)]
@@ -44,7 +46,7 @@ pub(crate) struct Deposit {
#[async_trait]
impl Execute for Deposit {
async fn execute(&self, db: &mut PickleDb) -> Result<()> {
async fn execute(&self, db: &mut PickleDb, _shared_storage: PersistentStorage) -> Result<()> {
let mut rng = OsRng;
let signing_keypair = KeyPair::from(identity::KeyPair::new(&mut rng));
let encryption_keypair = KeyPair::from(encryption::KeyPair::new(&mut rng));
@@ -80,7 +82,7 @@ pub(crate) struct ListDeposits {}
#[async_trait]
impl Execute for ListDeposits {
async fn execute(&self, db: &mut PickleDb) -> Result<()> {
async fn execute(&self, db: &mut PickleDb, _shared_storage: PersistentStorage) -> Result<()> {
for kv in db.iter() {
println!("{:?}", kv.get_value::<State>());
}
@@ -102,7 +104,7 @@ pub(crate) struct GetCredential {
#[async_trait]
impl Execute for GetCredential {
async fn execute(&self, db: &mut PickleDb) -> Result<()> {
async fn execute(&self, db: &mut PickleDb, shared_storage: PersistentStorage) -> Result<()> {
let mut state = db
.get::<State>(&self.tx_hash)
.ok_or(CredentialClientError::NoDeposit)?;
@@ -162,6 +164,15 @@ impl Execute for GetCredential {
let signature =
obtain_aggregate_signature(&params, &bandwidth_credential_attributes, &urls).await?;
shared_storage
.insert_coconut_credential(
state.amount.to_string(),
VOUCHER_INFO.to_string(),
bandwidth_credential_attributes.get_private_attributes()[0].to_bs58(),
bandwidth_credential_attributes.get_private_attributes()[1].to_bs58(),
signature.to_bs58(),
)
.await?;
state.signature = Some(signature.to_bs58());
db.set(&self.tx_hash, &state).unwrap();
@@ -170,17 +181,3 @@ impl Execute for GetCredential {
Ok(())
}
}
#[derive(Args, Clone)]
pub(crate) struct SpendCredential {
/// Spend one of the acquired credentials
#[clap(long)]
id: usize,
}
#[async_trait]
impl Execute for SpendCredential {
async fn execute(&self, _db: &mut PickleDb) -> Result<()> {
Ok(())
}
}
+4
View File
@@ -3,6 +3,7 @@
use thiserror::Error;
use credential_storage::error::StorageError;
use credentials::error::Error as CredentialError;
use crypto::asymmetric::encryption::KeyRecoveryError;
use crypto::asymmetric::identity::Ed25519RecoveryError;
@@ -38,4 +39,7 @@ pub enum CredentialClientError {
#[error("Could not parse X25519 data")]
X25519ParseError(#[from] KeyRecoveryError),
#[error("Could not use shared storage")]
SharedStorageError(#[from] StorageError),
}
+7 -5
View File
@@ -15,9 +15,9 @@ cfg_if::cfg_if! {
use clap::Parser;
use pickledb::{PickleDb, PickleDbDumpPolicy, SerializationMethod};
pub const MNEMONIC: &str = "sun surge soon stomach flavor country gorilla dress oblige stamp attract hip soldier agree steel prize nuclear know enjoy arm bargain always theme matter";
pub const MNEMONIC: &str = "jazz fatigue diagram account outer wrist slide cherry mother grid network pause wolf pig round answer mail junior better hair dismiss toward access end";
pub const NYMD_URL: &str = "http://127.0.0.1:26657";
pub const CONTRACT_ADDRESS: &str = "nymt1vhjnzk9ly03dugffvzfcwgry4dgc8x0sscmfl2";
pub const CONTRACT_ADDRESS: &str = "nymt1nc5tatafv6eyq7llkr2gv50ff9e22mnfp9pc5s";
pub const SIGNER_AUTHORITIES: [&str; 1] = [
"http://127.0.0.1:8080",
];
@@ -32,6 +32,8 @@ cfg_if::cfg_if! {
#[tokio::main]
async fn main() -> Result<()> {
let args = Cli::parse();
let shared_storage = credential_storage::initialise_storage(std::path::PathBuf::from("/tmp/credential.db")).await;
let mut db = match PickleDb::load(
"credential.db",
PickleDbDumpPolicy::AutoDump,
@@ -46,9 +48,9 @@ cfg_if::cfg_if! {
};
match &args.command {
Commands::Deposit(m) => m.execute(&mut db).await?,
Commands::ListDeposits(m) => m.execute(&mut db).await?,
Commands::GetCredential(m) => m.execute(&mut db).await?,
Commands::Deposit(m) => m.execute(&mut db, shared_storage).await?,
Commands::ListDeposits(m) => m.execute(&mut db, shared_storage).await?,
Commands::GetCredential(m) => m.execute(&mut db, shared_storage).await?,
}
Ok(())
+4 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.0.0-rc.1"
version = "1.0.1"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2021"
rust-version = "1.56"
@@ -34,6 +34,7 @@ tokio-tungstenite = "0.14" # websocket
client-core = { path = "../client-core" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
credentials = { path = "../../common/credentials", optional = true }
credential-storage = { path = "../../common/credential-storage" }
config = { path = "../../common/config" }
crypto = { path = "../../common/crypto" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
@@ -42,12 +43,12 @@ nymsphinx = { path = "../../common/nymsphinx" }
pemstore = { path = "../../common/pemstore" }
topology = { path = "../../common/topology" }
websocket-requests = { path = "websocket-requests" }
validator-client = { path = "../../common/client-libs/validator-client" }
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
version-checker = { path = "../../common/version-checker" }
network-defaults = { path = "../../common/network-defaults" }
[features]
coconut = ["coconut-interface", "credentials", "credentials/coconut", "gateway-requests/coconut", "gateway-client/coconut"]
coconut = ["coconut-interface", "credentials", "credentials/coconut", "gateway-requests/coconut", "gateway-client/coconut", "client-core/coconut"]
eth = []
[dev-dependencies]
@@ -592,9 +592,9 @@
}
},
"node_modules/async": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"dev": true,
"dependencies": {
"lodash": "^4.17.14"
@@ -4825,9 +4825,9 @@
"dev": true
},
"async": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"dev": true,
"requires": {
"lodash": "^4.17.14"
+4 -6
View File
@@ -19,9 +19,9 @@ 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
# Indicates whether this client is running in a disabled credentials mode, thus attempting
# to claim bandwidth without presenting bandwidth credentials.
testnet_mode = {{ client.testnet_mode }}
disabled_credentials_mode = {{ client.disabled_credentials_mode }}
# Addresses to APIs running on validator from which the client gets the view of the network.
validator_api_urls = [
@@ -46,10 +46,8 @@ public_encryption_key_file = '{{ client.public_encryption_key_file }}'
# sent but not received back.
reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
# Path to directory containing public/private keys used for bandwidth token purchase.
# Those are saved in case of emergency, to be able to reclaim bandwidth tokens.
# The public key is the name of the file, while the private key is the content.
backup_bandwidth_token_keys_dir = '{{ client.backup_bandwidth_token_keys_dir }}'
# Path to the database containing bandwidth credentials
database_path = '{{ client.database_path }}'
# Ethereum private key.
eth_private_key = '{{ client.eth_private_key }}'
+6 -4
View File
@@ -174,14 +174,16 @@ impl NymClient {
#[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path())
.await,
self.config.get_base().get_validator_api_endpoints(),
*self.key_manager.identity_keypair().public_key(),
);
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path())
.await,
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");
@@ -197,8 +199,8 @@ impl NymClient {
Some(bandwidth_controller),
);
if self.config.get_base().get_testnet_mode() {
gateway_client.set_testnet_mode(true)
if self.config.get_base().get_disabled_credentials_mode() {
gateway_client.set_disabled_credentials_mode(true)
}
gateway_client
.authenticate_and_start()
+7 -60
View File
@@ -4,21 +4,10 @@
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::{Credential, Parameters};
use config::NymConfig;
#[cfg(feature = "coconut")]
use credentials::coconut::{
bandwidth::prepare_for_spending, bandwidth::BandwidthVoucher, bandwidth::TOTAL_ATTRIBUTES,
utils::obtain_aggregate_signature,
};
#[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, VOUCHER_INFO};
use nymsphinx::addressing::clients::Recipient;
use nymsphinx::addressing::nodes::NodeIdentity;
use rand::rngs::OsRng;
@@ -29,16 +18,14 @@ use std::sync::Arc;
use std::time::Duration;
use topology::{filter::VersionFilterable, gateway};
use url::Url;
#[cfg(feature = "coconut")]
use validator_client::nymd::tx::Hash;
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,
DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY, ENABLED_CREDENTIALS_MODE_ARG_NAME,
ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
};
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
@@ -79,68 +66,28 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
#[cfg(not(feature = "coconut"))]
let app = app
.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.")
Arg::with_name(ENABLED_CREDENTIALS_MODE_ARG_NAME)
.long(ENABLED_CREDENTIALS_MODE_ARG_NAME)
.help("Set this client to work in a disabled credentials 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)
.default_value_if(ENABLED_CREDENTIALS_MODE_ARG_NAME, None, DEFAULT_ETH_ENDPOINT)
.required(true))
.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)
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_PRIVATE_KEY)
.default_value_if(ENABLED_CREDENTIALS_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 mut rng = OsRng;
let bandwidth_credential_attributes = BandwidthVoucher::new(
&params,
BANDWIDTH_VALUE.to_string(),
VOUCHER_INFO.to_string(),
Hash::new([0; 32]),
// workaround for putting a valid value here, without deriving clone for the private
// key, until we have actual useful values
identity::PrivateKey::from_base58_string(
identity::KeyPair::new(&mut rng)
.private_key()
.to_base58_string(),
)
.unwrap(),
encryption::KeyPair::new(&mut rng).private_key().clone(),
);
let bandwidth_credential =
obtain_aggregate_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>,
+3 -3
View File
@@ -5,7 +5,7 @@ use crate::client::config::{Config, SocketType};
use clap::ArgMatches;
use url::Url;
pub(crate) const TESTNET_MODE_ARG_NAME: &str = "testnet-mode";
pub(crate) const ENABLED_CREDENTIALS_MODE_ARG_NAME: &str = "enabled-credentials-mode";
#[cfg(not(feature = "coconut"))]
pub(crate) const ETH_ENDPOINT_ARG_NAME: &str = "eth_endpoint";
#[cfg(not(feature = "coconut"))]
@@ -72,8 +72,8 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> C
.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)
if matches.is_present(ENABLED_CREDENTIALS_MODE_ARG_NAME) {
config.get_base_mut().with_disabled_credentials(false)
}
config
+6 -4
View File
@@ -6,7 +6,9 @@ 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 crate::commands::{
ENABLED_CREDENTIALS_MODE_ARG_NAME, ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
};
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
use log::*;
@@ -46,9 +48,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
#[cfg(not(feature = "coconut"))]
let app = app
.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.")
Arg::with_name(ENABLED_CREDENTIALS_MODE_ARG_NAME)
.long(ENABLED_CREDENTIALS_MODE_ARG_NAME)
.help("Set this client to work in a enabled credentials mode that would attempt to use gateway with 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)
+4
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, App, ArgMatches};
use network_defaults::DEFAULT_NETWORK;
pub mod client;
pub mod commands;
@@ -67,6 +68,7 @@ fn long_version() -> String {
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
"#,
"Build Timestamp:",
env!("VERGEN_BUILD_TIMESTAMP"),
@@ -84,6 +86,8 @@ fn long_version() -> String {
env!("VERGEN_RUSTC_CHANNEL"),
"cargo Profile:",
env!("VERGEN_CARGO_PROFILE"),
"Network:",
DEFAULT_NETWORK
)
}
+4 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.0.0-rc.1"
version = "1.0.1"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021"
rust-version = "1.56"
@@ -27,6 +27,7 @@ url = "2.2"
client-core = { path = "../client-core" }
coconut-interface = { path = "../../common/coconut-interface", optional = true }
credentials = { path = "../../common/credentials", optional = true }
credential-storage = { path = "../../common/credential-storage" }
config = { path = "../../common/config" }
crypto = { path = "../../common/crypto" }
gateway-client = { path = "../../common/client-libs/gateway-client" }
@@ -37,12 +38,12 @@ 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" }
validator-client = { path = "../../common/client-libs/validator-client", features = ["nymd-client"] }
version-checker = { path = "../../common/version-checker" }
network-defaults = { path = "../../common/network-defaults" }
[features]
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut", "credentials/coconut"]
coconut = ["coconut-interface", "credentials", "gateway-requests/coconut", "gateway-client/coconut", "credentials/coconut", "client-core/coconut"]
eth = []
[build-dependencies]
+4 -6
View File
@@ -19,9 +19,9 @@ 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
# Indicates whether this client is running in a disabled credentials mode, thus attempting
# to claim bandwidth without presenting bandwidth credentials.
testnet_mode = {{ client.testnet_mode }}
disabled_credentials_mode = {{ client.disabled_credentials_mode }}
# Addresses to APIs running on validator from which the client gets the view of the network.
validator_api_urls = [
@@ -46,10 +46,8 @@ public_encryption_key_file = '{{ client.public_encryption_key_file }}'
# sent but not received back.
reply_encryption_key_store_path = '{{ client.reply_encryption_key_store_path }}'
# Path to directory containing public/private keys used for bandwidth token purchase.
# Those are saved in case of emergency, to be able to reclaim bandwidth tokens.
# The public key is the name of the file, while the private key is the content.
backup_bandwidth_token_keys_dir = '{{ client.backup_bandwidth_token_keys_dir }}'
# Path to the database containing bandwidth credentials
database_path = '{{ client.database_path }}'
# Ethereum private key.
eth_private_key = '{{ client.eth_private_key }}'
+6 -4
View File
@@ -162,14 +162,16 @@ impl NymClient {
#[cfg(feature = "coconut")]
let bandwidth_controller = BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path())
.await,
self.config.get_base().get_validator_api_endpoints(),
*self.key_manager.identity_keypair().public_key(),
);
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = BandwidthController::new(
credential_storage::initialise_storage(self.config.get_base().get_database_path())
.await,
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");
@@ -185,8 +187,8 @@ impl NymClient {
Some(bandwidth_controller),
);
if self.config.get_base().get_testnet_mode() {
gateway_client.set_testnet_mode(true)
if self.config.get_base().get_disabled_credentials_mode() {
gateway_client.set_disabled_credentials_mode(true)
}
gateway_client
.authenticate_and_start()
+7 -60
View File
@@ -4,21 +4,10 @@
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::{Credential, Parameters};
use config::NymConfig;
#[cfg(feature = "coconut")]
use credentials::coconut::{
bandwidth::prepare_for_spending, bandwidth::BandwidthVoucher, bandwidth::TOTAL_ATTRIBUTES,
utils::obtain_aggregate_signature,
};
#[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};
@@ -27,16 +16,14 @@ use std::sync::Arc;
use std::time::Duration;
use topology::{filter::VersionFilterable, gateway};
use url::Url;
#[cfg(feature = "coconut")]
use validator_client::nymd::tx::Hash;
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,
DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY, ENABLED_CREDENTIALS_MODE_ARG_NAME,
ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
};
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
@@ -79,68 +66,28 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
#[cfg(not(feature = "coconut"))]
let app = app
.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.")
Arg::with_name(ENABLED_CREDENTIALS_MODE_ARG_NAME)
.long(ENABLED_CREDENTIALS_MODE_ARG_NAME)
.help("Set this client to work in a enabled credentials mode that would attempt to use gateway with 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)
.default_value_if(ENABLED_CREDENTIALS_MODE_ARG_NAME, None, DEFAULT_ETH_ENDPOINT)
.required(true))
.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)
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_PRIVATE_KEY)
.default_value_if(ENABLED_CREDENTIALS_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 mut rng = OsRng;
let bandwidth_credential_attributes = BandwidthVoucher::new(
&params,
BANDWIDTH_VALUE.to_string(),
network_defaults::VOUCHER_INFO.to_string(),
Hash::new([0; 32]),
// workaround for putting a valid value here, without deriving clone for the private
// key, until we have actual useful values
identity::PrivateKey::from_base58_string(
identity::KeyPair::new(&mut rng)
.private_key()
.to_base58_string(),
)
.unwrap(),
encryption::KeyPair::new(&mut rng).private_key().clone(),
);
let bandwidth_credential =
obtain_aggregate_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>,
+3 -3
View File
@@ -9,7 +9,7 @@ pub(crate) mod init;
pub(crate) mod run;
pub(crate) mod upgrade;
pub(crate) const TESTNET_MODE_ARG_NAME: &str = "testnet-mode";
pub(crate) const ENABLED_CREDENTIALS_MODE_ARG_NAME: &str = "enabled-credentials-mode";
#[cfg(not(feature = "coconut"))]
pub(crate) const ETH_ENDPOINT_ARG_NAME: &str = "eth_endpoint";
#[cfg(not(feature = "coconut"))]
@@ -68,8 +68,8 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> C
.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)
if matches.is_present(ENABLED_CREDENTIALS_MODE_ARG_NAME) {
config.get_base_mut().with_disabled_credentials(false)
}
config
+6 -4
View File
@@ -6,7 +6,9 @@ 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 crate::commands::{
ENABLED_CREDENTIALS_MODE_ARG_NAME, ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
};
use clap::{App, Arg, ArgMatches};
use config::NymConfig;
use log::*;
@@ -52,9 +54,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
#[cfg(not(feature = "coconut"))]
let app = app
.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.")
Arg::with_name(ENABLED_CREDENTIALS_MODE_ARG_NAME)
.long(ENABLED_CREDENTIALS_MODE_ARG_NAME)
.help("Set this client to work in a disabled credentials 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)
+4
View File
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use clap::{crate_version, App, ArgMatches};
use network_defaults::DEFAULT_NETWORK;
pub mod client;
mod commands;
@@ -67,6 +68,7 @@ fn long_version() -> String {
{:<20}{}
{:<20}{}
{:<20}{}
{:<20}{}
"#,
"Build Timestamp:",
env!("VERGEN_BUILD_TIMESTAMP"),
@@ -84,6 +86,8 @@ fn long_version() -> String {
env!("VERGEN_RUSTC_CHANNEL"),
"cargo Profile:",
env!("VERGEN_CARGO_PROFILE"),
"Network:",
DEFAULT_NETWORK
)
}
+5 -3
View File
@@ -16,7 +16,7 @@ use proxy_helpers::connection_controller::{
};
use proxy_helpers::proxy_runner::ProxyRunner;
use rand::RngCore;
use socks5_requests::{ConnectionId, RemoteAddress, Request};
use socks5_requests::{ConnectionId, Message, RemoteAddress, Request};
use std::io;
use std::net::SocketAddr;
use std::pin::Pin;
@@ -224,8 +224,9 @@ impl SocksClient {
async fn send_connect_to_mixnet(&mut self, remote_address: RemoteAddress) {
let req = Request::new_connect(self.connection_id, remote_address, self.self_address);
let msg = Message::Request(req);
let input_message = InputMessage::new_fresh(self.service_provider, req.into_bytes(), false);
let input_message = InputMessage::new_fresh(self.service_provider, msg.into_bytes(), false);
self.input_sender.unbounded_send(input_message).unwrap();
}
@@ -252,7 +253,8 @@ impl SocksClient {
)
.run(move |conn_id, read_data, socket_closed| {
let provider_request = Request::new_send(conn_id, read_data, socket_closed);
InputMessage::new_fresh(recipient, provider_request.into_bytes(), false)
let provider_message = Message::Request(provider_request);
InputMessage::new_fresh(recipient, provider_message.into_bytes(), false)
})
.await
.into_inner();
+1 -1
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 = "1.0.0-rc.1"
version = "1.0.1"
edition = "2021"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0"
+7 -8
View File
@@ -24,8 +24,7 @@
},
"../pkg": {
"name": "@nymproject/nym-client-wasm",
"version": "0.12.0",
"license": "Apache-2.0"
"version": "0.0.1"
},
"node_modules/@discoveryjs/json-ext": {
"version": "0.5.7",
@@ -586,9 +585,9 @@
}
},
"node_modules/async": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"dev": true,
"dependencies": {
"lodash": "^4.17.14"
@@ -4305,9 +4304,9 @@
"dev": true
},
"async": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"dev": true,
"requires": {
"lodash": "^4.17.14"
+11 -14
View File
@@ -26,7 +26,7 @@ const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500);
#[wasm_bindgen]
pub struct NymClient {
validator_server: Url,
testnet_mode: bool,
disabled_credentials_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
@@ -72,7 +72,7 @@ impl NymClient {
on_message: None,
on_gateway_connect: None,
testnet_mode: true,
disabled_credentials_mode: true,
}
}
@@ -85,9 +85,12 @@ 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;
pub fn set_disabled_credentials_mode(&mut self, disabled_credentials_mode: bool) {
console_log!(
"Setting disabled credentials mode to {}",
disabled_credentials_mode
);
self.disabled_credentials_mode = disabled_credentials_mode;
}
fn self_recipient(&self) -> Recipient {
@@ -107,14 +110,8 @@ 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;
let disabled_credentials_mode = self.disabled_credentials_mode;
#[cfg(feature = "coconut")]
let bandwidth_controller = Some(gateway_client::bandwidth::BandwidthController::new(
vec![self.validator_server.clone()],
*self.identity.public_key(),
));
#[cfg(not(feature = "coconut"))]
let bandwidth_controller = None;
let mut client = self.get_and_update_topology().await;
@@ -135,8 +132,8 @@ impl NymClient {
bandwidth_controller,
);
if testnet_mode {
gateway_client.set_testnet_mode(true)
if disabled_credentials_mode {
gateway_client.set_disabled_credentials_mode(true)
}
gateway_client
+5 -2
View File
@@ -17,9 +17,9 @@ url = "2.2"
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
secp256k1 = "0.20.3"
web3 = { version = "0.17.0", default-features = false }
async-trait = { version = "0.1.51" }
# internal
cosmrs = { version = "0.4.1", optional = true }
credentials = { path = "../../credentials" }
crypto = { path = "../../crypto" }
gateway-requests = { path = "../../../gateway/gateway-requests" }
@@ -41,6 +41,9 @@ 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.credential-storage]
path = "../../credential-storage"
# wasm-only dependencies
[target."cfg(target_arch = \"wasm32\")".dependencies.wasm-bindgen]
version = "0.2"
@@ -69,6 +72,6 @@ features = ["js"]
#url = "2.1"
[features]
coconut = ["gateway-requests/coconut", "coconut-interface", "validator-client", "credentials/coconut", "cosmrs"]
coconut = ["gateway-requests/coconut", "coconut-interface", "validator-client", "credentials/coconut"]
wasm = ["web3/wasm", "web3/http", "web3/signing"]
default = ["web3/default"]
@@ -1,33 +1,35 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(target_arch = "wasm32")]
use crate::wasm_storage::{Storage, StorageError};
#[cfg(feature = "coconut")]
use cosmrs::tx::Hash;
use coconut_interface::Base58;
#[cfg(feature = "coconut")]
#[cfg(not(target_arch = "wasm32"))]
use credential_storage::error::StorageError;
#[cfg(not(target_arch = "wasm32"))]
use credential_storage::storage::Storage;
#[cfg(feature = "coconut")]
use credentials::coconut::{
bandwidth::{prepare_for_spending, BandwidthVoucher, TOTAL_ATTRIBUTES},
utils::{obtain_aggregate_signature, obtain_aggregate_verification_key},
bandwidth::prepare_for_spending, utils::obtain_aggregate_verification_key,
};
#[cfg(not(feature = "coconut"))]
use credentials::token::bandwidth::TokenCredential;
#[cfg(feature = "coconut")]
use crypto::asymmetric::encryption;
#[cfg(not(feature = "coconut"))]
use crypto::asymmetric::identity;
use network_defaults::BANDWIDTH_VALUE;
#[cfg(not(feature = "coconut"))]
use network_defaults::{
eth_contract::ETH_ERC20_JSON_ABI, eth_contract::ETH_JSON_ABI, ETH_BURN_FUNCTION_NAME,
ETH_CONTRACT_ADDRESS, ETH_ERC20_APPROVE_FUNCTION_NAME, ETH_ERC20_CONTRACT_ADDRESS,
ETH_MIN_BLOCK_DEPTH, TOKENS_TO_BURN, UTOKENS_TO_BURN,
eth_contract::ETH_ERC20_JSON_ABI, eth_contract::ETH_JSON_ABI, BANDWIDTH_VALUE,
ETH_BURN_FUNCTION_NAME, ETH_CONTRACT_ADDRESS, ETH_ERC20_APPROVE_FUNCTION_NAME,
ETH_ERC20_CONTRACT_ADDRESS, ETH_MIN_BLOCK_DEPTH, TOKENS_TO_BURN, UTOKENS_TO_BURN,
};
#[cfg(not(feature = "coconut"))]
use pemstore::traits::PemStorableKeyPair;
#[cfg(not(feature = "coconut"))]
use rand::rngs::OsRng;
#[cfg(not(feature = "coconut"))]
use secp256k1::SecretKey;
#[cfg(not(feature = "coconut"))]
use std::io::{Read, Write};
#[cfg(not(feature = "coconut"))]
use std::str::FromStr;
#[cfg(not(feature = "coconut"))]
use web3::{
@@ -68,35 +70,35 @@ pub fn eth_erc20_contract(web3: Web3<Http>) -> Contract<Http> {
}
#[derive(Clone)]
pub struct BandwidthController {
pub struct BandwidthController<St: Storage> {
storage: St,
#[cfg(feature = "coconut")]
validator_endpoints: Vec<url::Url>,
#[cfg(feature = "coconut")]
identity: identity::PublicKey,
#[cfg(not(feature = "coconut"))]
contract: Contract<Http>,
#[cfg(not(feature = "coconut"))]
erc20_contract: Contract<Http>,
#[cfg(not(feature = "coconut"))]
eth_private_key: SecretKey,
#[cfg(not(feature = "coconut"))]
backup_bandwidth_token_keys_dir: std::path::PathBuf,
}
impl BandwidthController {
impl<St> BandwidthController<St>
where
St: Storage + Clone + 'static,
{
#[cfg(feature = "coconut")]
pub fn new(validator_endpoints: Vec<url::Url>, identity: identity::PublicKey) -> Self {
pub fn new(storage: St, validator_endpoints: Vec<url::Url>) -> Self {
BandwidthController {
storage,
validator_endpoints,
identity,
}
}
#[cfg(not(feature = "coconut"))]
pub fn new(
storage: St,
eth_endpoint: String,
eth_private_key: String,
backup_bandwidth_token_keys_dir: std::path::PathBuf,
) -> Result<Self, GatewayClientError> {
// Fail early, on invalid url
let transport =
@@ -109,60 +111,42 @@ impl BandwidthController {
.map_err(|_| GatewayClientError::InvalidEthereumPrivateKey)?;
Ok(BandwidthController {
storage,
contract,
erc20_contract,
eth_private_key,
backup_bandwidth_token_keys_dir,
})
}
#[cfg(not(feature = "coconut"))]
fn backup_keypair(&self, keypair: &identity::KeyPair) -> Result<(), GatewayClientError> {
std::fs::create_dir_all(&self.backup_bandwidth_token_keys_dir)?;
let file_path = self
.backup_bandwidth_token_keys_dir
.join(keypair.public_key().to_base58_string());
let mut file = std::fs::File::create(file_path)?;
file.write_all(&keypair.private_key().to_bytes())?;
async fn backup_keypair(&self, keypair: &identity::KeyPair) -> Result<(), GatewayClientError> {
self.storage
.insert_erc20_credential(
keypair.public_key().to_base58_string(),
keypair.private_key().to_base58_string(),
)
.await?;
Ok(())
}
#[cfg(not(feature = "coconut"))]
fn restore_keypair(&self) -> Result<identity::KeyPair, GatewayClientError> {
std::fs::create_dir_all(&self.backup_bandwidth_token_keys_dir)?;
let file = std::fs::read_dir(&self.backup_bandwidth_token_keys_dir)?
.find(|entry| {
entry
.as_ref()
.map(|entry| entry.path().is_file())
.unwrap_or(false)
})
.unwrap_or_else(|| Err(std::io::Error::from(std::io::ErrorKind::NotFound)))?;
let file_path = file.path();
let pub_key = file_path
.file_name()
.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::NotFound))?
.to_str()
.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::NotFound))?;
let mut priv_key = vec![];
std::fs::File::open(file_path.clone())?.read_to_end(&mut priv_key)?;
Ok(identity::KeyPair::from_keys(
identity::PrivateKey::from_bytes(&priv_key).unwrap(),
identity::PublicKey::from_base58_string(pub_key).unwrap(),
))
async fn restore_keypair(&self) -> Result<identity::KeyPair, GatewayClientError> {
let data = self.storage.get_next_erc20_credential().await?;
let public_key = identity::PublicKey::from_base58_string(data.public_key).unwrap();
let private_key = identity::PrivateKey::from_base58_string(data.private_key).unwrap();
Ok(identity::KeyPair::from_keys(private_key, public_key))
}
#[cfg(not(feature = "coconut"))]
fn mark_keypair_as_spent(&self, keypair: &identity::KeyPair) -> Result<(), GatewayClientError> {
let mut spent_dir = self.backup_bandwidth_token_keys_dir.clone();
spent_dir.push("spent");
std::fs::create_dir_all(&spent_dir)?;
let file_path_old = self
.backup_bandwidth_token_keys_dir
.join(keypair.public_key().to_base58_string());
let file_path_new = spent_dir.join(keypair.public_key().to_base58_string());
std::fs::rename(file_path_old, file_path_new)?;
async fn mark_keypair_as_spent(
&self,
keypair: &identity::KeyPair,
) -> Result<(), GatewayClientError> {
self.storage
.consume_erc20_credential(keypair.public_key().to_base58_string())
.await?;
Ok(())
}
@@ -172,39 +156,24 @@ 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 mut rng = OsRng;
// TODO: Decide what is the value and additional info associated with the bandwidth voucher
let bandwidth_credential_attributes = BandwidthVoucher::new(
&params,
BANDWIDTH_VALUE.to_string(),
network_defaults::VOUCHER_INFO.to_string(),
Hash::new([0; 32]),
// workaround for putting a valid value here, without deriving clone for the private
// key, until we have actual useful values
identity::PrivateKey::from_base58_string(
identity::KeyPair::new(&mut rng)
.private_key()
.to_base58_string(),
)
.unwrap(),
encryption::KeyPair::new(&mut rng).private_key().clone(),
);
let bandwidth_credential = obtain_aggregate_signature(
&params,
&bandwidth_credential_attributes,
&self.validator_endpoints,
)
.await?;
// the above would presumably be loaded from a file
let bandwidth_credential = self.storage.get_next_coconut_credential().await?;
let voucher_value = u64::from_str(&bandwidth_credential.voucher_value)
.map_err(|_| StorageError::InconsistentData)?;
let voucher_info = bandwidth_credential.voucher_info.clone();
let serial_number =
coconut_interface::Attribute::try_from_bs58(bandwidth_credential.serial_number)?;
let binding_number =
coconut_interface::Attribute::try_from_bs58(bandwidth_credential.binding_number)?;
let signature =
coconut_interface::Signature::try_from_bs58(bandwidth_credential.signature)?;
// 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,
voucher_value,
voucher_info,
serial_number,
binding_number,
&signature,
&verification_key,
)?)
}
@@ -215,12 +184,12 @@ impl BandwidthController {
gateway_identity: identity::PublicKey,
gateway_owner: String,
) -> Result<TokenCredential, GatewayClientError> {
let kp = match self.restore_keypair() {
let kp = match self.restore_keypair().await {
Ok(kp) => kp,
Err(_) => {
let mut rng = OsRng;
let kp = identity::KeyPair::new(&mut rng);
self.backup_keypair(&kp)?;
self.backup_keypair(&kp).await?;
kp
}
};
@@ -230,7 +199,7 @@ impl BandwidthController {
self.buy_token_credential(verification_key, signed_verification_key, gateway_owner)
.await?;
self.mark_keypair_as_spent(&kp)?;
self.mark_keypair_as_spent(&kp).await?;
let message: Vec<u8> = verification_key
.to_bytes()
+14 -10
View File
@@ -9,8 +9,12 @@ pub use crate::packet_router::{
AcknowledgementReceiver, AcknowledgementSender, MixnetMessageReceiver, MixnetMessageSender,
};
use crate::socket_state::{PartiallyDelegated, SocketState};
#[cfg(target_arch = "wasm32")]
use crate::wasm_storage::PersistentStorage;
#[cfg(feature = "coconut")]
use coconut_interface::Credential;
#[cfg(not(target_arch = "wasm32"))]
use credential_storage::PersistentStorage;
#[cfg(not(feature = "coconut"))]
use credentials::token::bandwidth::TokenCredential;
use crypto::asymmetric::identity;
@@ -41,7 +45,7 @@ const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
pub struct GatewayClient {
authenticated: bool,
testnet_mode: bool,
disabled_credentials_mode: bool,
bandwidth_remaining: i64,
gateway_address: String,
gateway_identity: identity::PublicKey,
@@ -51,7 +55,7 @@ pub struct GatewayClient {
connection: SocketState,
packet_router: PacketRouter,
response_timeout_duration: Duration,
bandwidth_controller: Option<BandwidthController>,
bandwidth_controller: Option<BandwidthController<PersistentStorage>>,
// reconnection related variables
/// Specifies whether client should try to reconnect to gateway on connection failure.
@@ -75,11 +79,11 @@ impl GatewayClient {
mixnet_message_sender: MixnetMessageSender,
ack_sender: AcknowledgementSender,
response_timeout_duration: Duration,
bandwidth_controller: Option<BandwidthController>,
bandwidth_controller: Option<BandwidthController<PersistentStorage>>,
) -> Self {
GatewayClient {
authenticated: false,
testnet_mode: false,
disabled_credentials_mode: true,
bandwidth_remaining: 0,
gateway_address,
gateway_identity,
@@ -96,8 +100,8 @@ impl GatewayClient {
}
}
pub fn set_testnet_mode(&mut self, testnet_mode: bool) {
self.testnet_mode = testnet_mode
pub fn set_disabled_credentials_mode(&mut self, disabled_credentials_mode: bool) {
self.disabled_credentials_mode = disabled_credentials_mode
}
// TODO: later convert into proper builder methods
@@ -130,7 +134,7 @@ impl GatewayClient {
GatewayClient {
authenticated: false,
testnet_mode: false,
disabled_credentials_mode: true,
bandwidth_remaining: 0,
gateway_address,
gateway_identity,
@@ -544,13 +548,13 @@ impl GatewayClient {
if self.shared_key.is_none() {
return Err(GatewayClientError::NoSharedKeyAvailable);
}
if self.bandwidth_controller.is_none() && !self.testnet_mode {
if self.bandwidth_controller.is_none() && !self.disabled_credentials_mode {
return Err(GatewayClientError::NoBandwidthControllerAvailable);
}
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");
if self.disabled_credentials_mode {
info!("The client is running in disabled credentials mode - attempting to claim bandwidth without a credential");
return self.try_claim_testnet_bandwidth().await;
}
+14 -4
View File
@@ -1,6 +1,10 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[cfg(target_arch = "wasm32")]
use crate::wasm_storage::StorageError;
#[cfg(not(target_arch = "wasm32"))]
use credential_storage::error::StorageError;
use gateway_requests::registration::handshake::error::HandshakeError;
use std::io;
use thiserror::Error;
@@ -21,15 +25,18 @@ pub enum GatewayClientError {
#[error("There was a network error - {0}")]
NetworkError(#[from] WsError),
#[error("There was a credential storage error - {0}")]
CredentialStorageError(#[from] StorageError),
#[cfg(feature = "coconut")]
#[error("Coconut error - {0}")]
CoconutError(#[from] coconut_interface::CoconutError),
// TODO: see if `JsValue` is a reasonable type for this
#[cfg(target_arch = "wasm32")]
#[error("There was a network error")]
NetworkErrorWasm(JsValue),
#[cfg(not(feature = "coconut"))]
#[error("Keypair IO error - {0}")]
IOError(#[from] std::io::Error),
#[cfg(not(feature = "coconut"))]
#[error("Could not burn ERC20 token in Ethereum smart contract - {0}")]
BurnTokenError(#[from] Web3Error),
@@ -69,6 +76,9 @@ pub enum GatewayClientError {
#[error("Client does not have enough bandwidth: estimated {0}, remaining: {1}")]
NotEnoughBandwidth(i64, i64),
#[error("There are no more bandwidth credentials acquired. Please buy some more if you want to use the mixnet")]
NoMoreBandwidthCredentials,
#[error("Received an unexpected response")]
UnexpectedResponse,
@@ -13,6 +13,8 @@ pub mod client;
pub mod error;
pub mod packet_router;
pub mod socket_state;
#[cfg(feature = "wasm")]
mod wasm_storage;
/// Helper method for reading from websocket stream. Helps to flatten the structure.
pub(crate) fn cleanup_socket_message(
@@ -0,0 +1,98 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum StorageError {
#[error("Wasm client is not yet supported")]
WasmNotSupported,
#[allow(dead_code)]
#[error("Code shouldn't reach this point")]
InconsistentData,
}
#[derive(Clone)]
pub struct PersistentStorage {}
pub struct CoconutCredential {
pub id: i64,
pub voucher_value: String,
pub voucher_info: String,
pub serial_number: String,
pub binding_number: String,
pub signature: String,
}
pub struct ERC20Credential {
pub id: i64,
pub public_key: String,
pub private_key: String,
pub consumed: bool,
}
#[async_trait]
pub trait Storage: Send + Sync {
async fn insert_coconut_credential(
&self,
voucher_value: String,
voucher_info: String,
serial_number: String,
binding_number: String,
signature: String,
) -> Result<(), StorageError>;
async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, StorageError>;
async fn remove_coconut_credential(&self, id: i64) -> Result<(), StorageError>;
async fn insert_erc20_credential(
&self,
public_key: String,
private_key: String,
) -> Result<(), StorageError>;
async fn get_next_erc20_credential(&self) -> Result<ERC20Credential, StorageError>;
async fn consume_erc20_credential(&self, public_key: String) -> Result<(), StorageError>;
}
#[async_trait]
impl Storage for PersistentStorage {
async fn insert_coconut_credential(
&self,
_voucher_value: String,
_voucher_info: String,
_serial_number: String,
_binding_number: String,
_signature: String,
) -> Result<(), StorageError> {
Err(StorageError::WasmNotSupported)
}
async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, StorageError> {
Err(StorageError::WasmNotSupported)
}
async fn remove_coconut_credential(&self, _id: i64) -> Result<(), StorageError> {
Err(StorageError::WasmNotSupported)
}
async fn insert_erc20_credential(
&self,
_public_key: String,
_private_key: String,
) -> Result<(), StorageError> {
Err(StorageError::WasmNotSupported)
}
async fn get_next_erc20_credential(&self) -> Result<ERC20Credential, StorageError> {
Err(StorageError::WasmNotSupported)
}
async fn consume_erc20_credential(&self, _public_key: String) -> Result<(), StorageError> {
Err(StorageError::WasmNotSupported)
}
}
+48 -12
View File
@@ -133,20 +133,14 @@ impl Client {
if current_attempt == 0 {
None
} else {
// according to https://github.com/tokio-rs/tokio/issues/1953 there's an undocumented
// limit of tokio delay of about 2 years.
// let's ensure our delay is always on a sane side of being maximum 1 hour.
let maximum_sane_delay = Duration::from_secs(60 * 60);
let exp = 2_u32.checked_pow(current_attempt);
let backoff = exp
.and_then(|exp| self.config.initial_reconnection_backoff.checked_mul(exp))
.unwrap_or(self.config.maximum_reconnection_backoff);
Some(std::cmp::min(
maximum_sane_delay,
std::cmp::min(
self.config
.initial_reconnection_backoff
.checked_mul(2_u32.pow(current_attempt))
.unwrap_or(self.config.maximum_reconnection_backoff),
self.config.maximum_reconnection_backoff,
),
backoff,
self.config.maximum_reconnection_backoff,
))
}
}
@@ -254,3 +248,45 @@ impl SendWithoutResponse for Client {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn dummy_client() -> Client {
Client::new(Config {
initial_reconnection_backoff: Duration::from_millis(10_000),
maximum_reconnection_backoff: Duration::from_millis(300_000),
initial_connection_timeout: Duration::from_millis(1_500),
maximum_connection_buffer_size: 128,
})
}
#[test]
fn determining_backoff_works_regardless_of_attempt() {
let client = dummy_client();
assert!(client.determine_backoff(0).is_none());
assert!(client.determine_backoff(1).is_some());
assert!(client.determine_backoff(2).is_some());
assert_eq!(
client.determine_backoff(16).unwrap(),
client.config.maximum_reconnection_backoff
);
assert_eq!(
client.determine_backoff(32).unwrap(),
client.config.maximum_reconnection_backoff
);
assert_eq!(
client.determine_backoff(1024).unwrap(),
client.config.maximum_reconnection_backoff
);
assert_eq!(
client.determine_backoff(65536).unwrap(),
client.config.maximum_reconnection_backoff
);
assert_eq!(
client.determine_backoff(u32::MAX).unwrap(),
client.config.maximum_reconnection_backoff
);
}
}
@@ -32,12 +32,12 @@ validator-api-requests = { path = "../../../validator-api/validator-api-requests
async-trait = { version = "0.1.51", optional = true }
bip39 = { version = "1", features = ["rand"], optional = true }
config = { path = "../../config", optional = true }
cosmrs = { git = "https://github.com/nymtech/cosmos-rust", branch = "bugfix/account-id-length-validation", features = ["rpc", "bip32", "cosmwasm"], optional = true}
prost = { version = "0.9", default-features = false, optional = true }
cosmrs = { version = "0.7.0", features = ["rpc", "bip32", "cosmwasm"], optional = true}
prost = { version = "0.10", default-features = false, optional = true }
flate2 = { version = "1.0.20", optional = true }
sha2 = { version = "0.9.5", optional = true }
itertools = { version = "0.10", optional = true }
cosmwasm-std = { version = "1.0.0-beta6", optional = true }
cosmwasm-std = { version = "1.0.0-beta8", optional = true }
[dev-dependencies]
ts-rs = "6.1.2"
@@ -5,6 +5,9 @@ use crate::{validator_api, ValidatorClientError};
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
use url::Url;
#[cfg(feature = "nymd-client")]
use validator_api_requests::models::UptimeResponse;
use validator_api_requests::models::{
CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
StakeSaturationResponse,
@@ -153,6 +156,10 @@ impl Client<SigningNymdClient> {
)?;
Ok(())
}
pub fn set_nymd_simulated_gas_multiplier(&mut self, multiplier: f32) {
self.nymd.set_simulated_gas_multiplier(multiplier)
}
}
#[cfg(feature = "nymd-client")]
@@ -260,13 +267,14 @@ impl<C> Client<C> {
&self,
address: String,
mix_identity: IdentityKey,
proxy: Option<String>,
) -> Result<u128, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_delegator_rewards(address, mix_identity)
.get_delegator_rewards(address, mix_identity, proxy)
.await?
.u128())
}
@@ -274,13 +282,14 @@ impl<C> Client<C> {
pub async fn get_pending_delegation_events(
&self,
owner_address: String,
proxy_address: Option<String>,
) -> Result<Vec<DelegationEvent>, ValidatorClientError>
where
C: CosmWasmClient + Sync,
{
Ok(self
.nymd
.get_pending_delegation_events(owner_address)
.get_pending_delegation_events(owner_address, proxy_address)
.await?)
}
@@ -576,6 +585,12 @@ impl<C> Client<C> {
Ok(delegations)
}
pub async fn get_mixnode_avg_uptimes(
&self,
) -> Result<Vec<UptimeResponse>, ValidatorClientError> {
Ok(self.validator_api.get_mixnode_avg_uptimes().await?)
}
pub async fn blind_sign(
&self,
request_body: &BlindSignRequestBody,
@@ -0,0 +1,130 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use std::fmt;
pub use cosmrs::Coin as CosmosCoin;
pub use cosmwasm_std::Coin as CosmWasmCoin;
#[derive(Serialize, Deserialize, Clone, Copy, Default, Debug, PartialEq)]
pub struct MismatchedDenoms;
// the reason the coin is created here as opposed to different place in the codebase is that
// eventually we want to either publish the cosmwasm client separately or commit it to
// some other project, like cosmrs. Either way, in that case we can't really have
// a dependency on an internal type
#[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq)]
pub struct Coin {
pub amount: u128,
pub denom: String,
}
impl Coin {
pub fn new<S: Into<String>>(amount: u128, denom: S) -> Self {
Coin {
amount,
denom: denom.into(),
}
}
pub fn try_add(&self, other: &Self) -> Result<Self, MismatchedDenoms> {
if self.denom != other.denom {
Err(MismatchedDenoms)
} else {
Ok(Coin {
amount: self.amount + other.amount,
denom: self.denom.clone(),
})
}
}
}
impl fmt::Display for Coin {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}{}", self.amount, self.denom)
}
}
impl From<Coin> for CosmosCoin {
fn from(coin: Coin) -> Self {
assert!(
coin.amount <= u64::MAX as u128,
"the coin amount is higher than the maximum supported by cosmrs"
);
CosmosCoin {
denom: coin
.denom
.parse()
.expect("the coin should have had a valid denom!"),
amount: (coin.amount as u64).into(),
}
}
}
impl From<CosmosCoin> for Coin {
fn from(coin: CosmosCoin) -> Self {
Coin {
amount: coin
.amount
.to_string()
.parse()
.expect("somehow failed to parse string representation of u64"),
denom: coin.denom.to_string(),
}
}
}
impl From<Coin> for CosmWasmCoin {
fn from(coin: Coin) -> Self {
CosmWasmCoin::new(coin.amount, coin.denom)
}
}
impl From<CosmWasmCoin> for Coin {
fn from(coin: CosmWasmCoin) -> Self {
Coin {
amount: coin.amount.u128(),
denom: coin.denom,
}
}
}
pub trait CoinConverter {
type Target;
fn convert_coin(&self) -> Self::Target;
}
impl CoinConverter for CosmosCoin {
type Target = CosmWasmCoin;
fn convert_coin(&self) -> Self::Target {
CosmWasmCoin::new(
self.amount
.to_string()
.parse()
.expect("cosmos coin had an invalid amount assigned"),
self.denom.to_string(),
)
}
}
impl CoinConverter for CosmWasmCoin {
type Target = CosmosCoin;
fn convert_coin(&self) -> Self::Target {
assert!(
self.amount.u128() <= u64::MAX as u128,
"the coin amount is higher than the maximum supported by cosmrs"
);
CosmosCoin {
denom: self
.denom
.parse()
.expect("cosmwasm coin had an invalid amount assigned"),
amount: (self.amount.u128() as u64).into(),
}
}
}
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::coin::Coin;
use crate::nymd::cosmwasm_client::helpers::{create_pagination, next_page_key};
use crate::nymd::cosmwasm_client::types::{
Account, Code, CodeDetails, Contract, ContractCodeHistoryEntry, ContractCodeId,
@@ -8,9 +9,7 @@ use crate::nymd::cosmwasm_client::types::{
};
use crate::nymd::error::NymdError;
use async_trait::async_trait;
use cosmrs::proto::cosmos::auth::v1beta1::{
BaseAccount, QueryAccountRequest, QueryAccountResponse,
};
use cosmrs::proto::cosmos::auth::v1beta1::{QueryAccountRequest, QueryAccountResponse};
use cosmrs::proto::cosmos::bank::v1beta1::{
QueryAllBalancesRequest, QueryAllBalancesResponse, QueryBalanceRequest, QueryBalanceResponse,
QueryTotalSupplyRequest, QueryTotalSupplyResponse,
@@ -27,17 +26,30 @@ 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, Tx};
use cosmwasm_std::Coin as CosmWasmCoin;
use cosmrs::{tx, AccountId, Coin as CosmosCoin, Denom, Tx};
use prost::Message;
use serde::{Deserialize, Serialize};
use std::convert::{TryFrom, TryInto};
use std::time::Duration;
#[async_trait]
impl CosmWasmClient for HttpClient {}
impl CosmWasmClient for HttpClient {
fn broadcast_polling_rate(&self) -> Duration {
Duration::from_secs(4)
}
fn broadcast_timeout(&self) -> Duration {
Duration::from_secs(60)
}
}
#[async_trait]
pub trait CosmWasmClient: rpc::Client {
// this should probably get redesigned, but I'm leaving those like that temporarily to fix
// the underlying issue more quickly
fn broadcast_polling_rate(&self) -> Duration;
fn broadcast_timeout(&self) -> Duration;
// helper method to remove duplicate code involved in making abci requests with protobuf messages
// TODO: perhaps it should have an additional argument to determine whether the response should
// require proof?
@@ -83,21 +95,16 @@ pub trait CosmWasmClient: rpc::Client {
.make_abci_query::<_, QueryAccountResponse>(path, req)
.await?;
let base_account = res
.account
.map(|account| BaseAccount::decode(account.value.as_ref()))
.transpose()?;
base_account
.map(|base_account| base_account.try_into())
.transpose()
res.account.map(TryFrom::try_from).transpose()
}
async fn get_sequence(&self, address: &AccountId) -> Result<SequenceResponse, NymdError> {
let base_account = self
let account = self
.get_account(address)
.await?
.ok_or_else(|| NymdError::NonExistentAccountError(address.clone()))?;
let base_account = account.try_get_base_account()?;
Ok(SequenceResponse {
account_number: base_account.account_number,
sequence: base_account.sequence,
@@ -128,7 +135,7 @@ pub trait CosmWasmClient: rpc::Client {
.await?;
res.balance
.map(TryFrom::try_from)
.map(|proto| CosmosCoin::try_from(proto).map(Into::into))
.transpose()
.map_err(|_| NymdError::SerializationError("Coin".to_owned()))
}
@@ -159,16 +166,12 @@ pub trait CosmWasmClient: rpc::Client {
raw_balances
.into_iter()
.map(TryFrom::try_from)
.map(|proto| CosmosCoin::try_from(proto).map(Into::into))
.collect::<Result<_, _>>()
.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> {
async fn get_total_supply(&self) -> Result<Vec<Coin>, NymdError> {
let path = Some("/cosmos.bank.v1beta1.Query/TotalSupply".parse().unwrap());
let mut supply = Vec::new();
@@ -191,12 +194,7 @@ pub trait CosmWasmClient: rpc::Client {
supply
.into_iter()
.map(|coin| {
coin.amount.parse().map(|amount| CosmWasmCoin {
denom: coin.denom,
amount,
})
})
.map(|proto| CosmosCoin::try_from(proto).map(Into::into))
.collect::<Result<_, _>>()
.map_err(|_| NymdError::SerializationError("Coins".to_owned()))
}
@@ -260,6 +258,42 @@ pub trait CosmWasmClient: rpc::Client {
Ok(rpc::Client::broadcast_tx_commit(self, tx).await?)
}
async fn broadcast_tx(&self, tx: Transaction) -> Result<TxResponse, NymdError> {
let broadcasted = CosmWasmClient::broadcast_tx_sync(self, tx).await?;
if broadcasted.code.is_err() {
let code_val = broadcasted.code.value();
return Err(NymdError::BroadcastTxErrorDeliverTx {
hash: broadcasted.hash,
height: None,
code: code_val,
raw_log: broadcasted.log.to_string(),
});
}
let tx_hash = broadcasted.hash;
let start = tokio::time::Instant::now();
loop {
log::debug!(
"Polling for result of including {} in a block...",
broadcasted.hash
);
if tokio::time::Instant::now().duration_since(start) >= self.broadcast_timeout() {
return Err(NymdError::BroadcastTimeout {
hash: tx_hash,
timeout: self.broadcast_timeout(),
});
}
if let Ok(poll_res) = self.get_tx(tx_hash).await {
return Ok(poll_res);
}
tokio::time::sleep(self.broadcast_polling_rate()).await;
}
}
async fn get_codes(&self) -> Result<Vec<Code>, NymdError> {
let path = Some("/cosmwasm.wasm.v1.Query/Codes".parse().unwrap());
@@ -440,6 +474,9 @@ pub trait CosmWasmClient: rpc::Client {
// 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
// TODO: we should really stop using the `tx` argument here and use `tx_bytes` exlusively,
// however, at the time of writing this update, while our QA and mainnet networks do support it,
// sandbox is still running old version of wasmd that lacks support for `tx_bytes`
#[allow(deprecated)]
async fn query_simulate(
&self,
@@ -3,7 +3,9 @@
use crate::nymd::error::NymdError;
use cosmrs::proto::cosmos::base::query::v1beta1::{PageRequest, PageResponse};
use cosmrs::proto::cosmos::base::v1beta1::Coin as ProtoCoin;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::Coin;
use flate2::write::GzEncoder;
use flate2::Compression;
use std::io::Write;
@@ -17,7 +19,7 @@ impl CheckResponse for broadcast::tx_commit::Response {
if self.check_tx.code.is_err() {
return Err(NymdError::BroadcastTxErrorCheckTx {
hash: self.hash,
height: self.height,
height: Some(self.height),
code: self.check_tx.code.value(),
raw_log: self.check_tx.log.value().to_owned(),
});
@@ -26,7 +28,7 @@ impl CheckResponse for broadcast::tx_commit::Response {
if self.deliver_tx.code.is_err() {
return Err(NymdError::BroadcastTxErrorDeliverTx {
hash: self.hash,
height: self.height,
height: Some(self.height),
code: self.deliver_tx.code.value(),
raw_log: self.deliver_tx.log.value().to_owned(),
});
@@ -36,6 +38,21 @@ impl CheckResponse for broadcast::tx_commit::Response {
}
}
impl CheckResponse for crate::nymd::TxResponse {
fn check_response(self) -> Result<Self, NymdError> {
if self.tx_result.code.is_err() {
return Err(NymdError::BroadcastTxErrorDeliverTx {
hash: self.hash,
height: Some(self.height),
code: self.tx_result.code.value(),
raw_log: self.tx_result.log.value().to_owned(),
});
}
Ok(self)
}
}
pub(crate) fn compress_wasm_code(code: &[u8]) -> Result<Vec<u8>, NymdError> {
// using compression level 9, same as cosmjs, that optimises for size
let mut encoder = GzEncoder::new(Vec::new(), Compression::best());
@@ -51,6 +68,7 @@ pub(crate) fn create_pagination(key: Vec<u8>) -> PageRequest {
offset: 0,
limit: 0,
count_total: false,
reverse: false,
}
}
@@ -65,3 +83,14 @@ pub(crate) fn next_page_key(pagination_info: Option<PageResponse>) -> Option<Vec
None
}
pub(crate) fn parse_proto_coin_vec(value: Vec<ProtoCoin>) -> Result<Vec<Coin>, NymdError> {
value
.into_iter()
.map(|proto_coin| {
Coin::try_from(&proto_coin).map_err(|_| NymdError::MalformedCoin {
coin_representation: format!("{:?}", proto_coin),
})
})
.collect()
}
@@ -1,22 +1,6 @@
// 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};
@@ -24,16 +8,48 @@ 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 crate::nymd::{CosmosCoin, GasPrice};
use crate::nymd::{Coin, GasPrice, TxResponse};
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, Tx};
use log::debug;
use serde::Serialize;
use sha2::Digest;
use sha2::Sha256;
use std::convert::TryInto;
use std::time::Duration;
// 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,
];
const DEFAULT_BROADCAST_POLLING_RATE: Duration = Duration::from_secs(4);
const DEFAULT_BROADCAST_TIMEOUT: Duration = Duration::from_secs(60);
fn empty_fee() -> tx::Fee {
tx::Fee {
amount: vec![],
gas_limit: Default::default(),
payer: None,
granter: None,
}
}
fn single_unspecified_signer_auth(
public_key: Option<tx::SignerPublicKey>,
sequence_number: tx::SequenceNumber,
) -> tx::AuthInfo {
tx::SignerInfo {
public_key,
mode_info: tx::ModeInfo::Single(tx::mode_info::Single {
mode: SignMode::Unspecified,
}),
sequence: sequence_number,
}
.auth_info(empty_fee())
}
#[async_trait]
pub trait SigningCosmWasmClient: CosmWasmClient {
@@ -60,33 +76,21 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
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()],
body: tx::Body::new(messages, memo, 0u32),
auth_info: single_unspecified_signer_auth(public_key, sequence_response.sequence),
signatures: vec![Vec::new()],
};
self.query_simulate(Some(partial_tx), Vec::new()).await
// for completion sake, once we're able to transition into using `tx_bytes`,
// we might want to use something like this instead:
// let tx_raw: tx::Raw = cosmrs::proto::cosmos::tx::v1beta1::TxRaw {
// body_bytes: partial_tx.body.into_bytes().unwrap(),
// auth_info_bytes: partial_tx.auth_info.into_bytes().unwrap(),
// signatures: partial_tx.signatures,
// }
// .into();
// self.query_simulate(None, tx_raw.to_bytes().unwrap()).await
}
async fn upload(
@@ -111,12 +115,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgStoreCode".to_owned()))?;
let tx_res = self
.sign_and_broadcast_commit(sender_address, vec![upload_msg], fee, memo)
.sign_and_broadcast(sender_address, vec![upload_msg], fee, memo)
.await?
.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);
let logs = parse_raw_logs(tx_res.tx_result.log)?;
let gas_info = GasInfo::new(tx_res.tx_result.gas_wanted, tx_res.tx_result.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
@@ -172,12 +176,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgInstantiateContract".to_owned()))?;
let tx_res = self
.sign_and_broadcast_commit(sender_address, vec![init_msg], fee, memo)
.sign_and_broadcast(sender_address, vec![init_msg], fee, memo)
.await?
.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);
let logs = parse_raw_logs(tx_res.tx_result.log)?;
let gas_info = GasInfo::new(tx_res.tx_result.gas_wanted, tx_res.tx_result.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
@@ -214,14 +218,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgUpdateAdmin".to_owned()))?;
let tx_res = self
.sign_and_broadcast_commit(sender_address, vec![change_admin_msg], fee, memo)
.sign_and_broadcast(sender_address, vec![change_admin_msg], fee, memo)
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
let gas_info = GasInfo::new(tx_res.tx_result.gas_wanted, tx_res.tx_result.gas_used);
Ok(ChangeAdminResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
logs: parse_raw_logs(tx_res.tx_result.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
@@ -242,14 +246,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgClearAdmin".to_owned()))?;
let tx_res = self
.sign_and_broadcast_commit(sender_address, vec![change_admin_msg], fee, memo)
.sign_and_broadcast(sender_address, vec![change_admin_msg], fee, memo)
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
let gas_info = GasInfo::new(tx_res.tx_result.gas_wanted, tx_res.tx_result.gas_used);
Ok(ChangeAdminResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
logs: parse_raw_logs(tx_res.tx_result.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
@@ -277,14 +281,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.map_err(|_| NymdError::SerializationError("MsgMigrateContract".to_owned()))?;
let tx_res = self
.sign_and_broadcast_commit(sender_address, vec![migrate_msg], fee, memo)
.sign_and_broadcast(sender_address, vec![migrate_msg], fee, memo)
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
let gas_info = GasInfo::new(tx_res.tx_result.gas_wanted, tx_res.tx_result.gas_used);
Ok(MigrateResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
logs: parse_raw_logs(tx_res.tx_result.log)?,
transaction_hash: tx_res.hash,
gas_info,
})
@@ -306,20 +310,21 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
sender: sender_address.clone(),
contract: contract_address.clone(),
msg: serde_json::to_vec(msg)?,
funds,
funds: funds.into_iter().map(Into::into).collect(),
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))?;
let tx_res = self
.sign_and_broadcast_commit(sender_address, vec![execute_msg], fee, memo)
.sign_and_broadcast(sender_address, vec![execute_msg], fee, memo)
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
let gas_info = GasInfo::new(tx_res.tx_result.gas_wanted, tx_res.tx_result.gas_used);
Ok(ExecuteResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
logs: parse_raw_logs(tx_res.tx_result.log)?,
data: tx_res.tx_result.data,
transaction_hash: tx_res.hash,
gas_info,
})
@@ -344,7 +349,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
sender: sender_address.clone(),
contract: contract_address.clone(),
msg: serde_json::to_vec(&msg)?,
funds,
funds: funds.into_iter().map(Into::into).collect(),
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))
@@ -352,14 +357,15 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.collect::<Result<_, _>>()?;
let tx_res = self
.sign_and_broadcast_commit(sender_address, messages, fee, memo)
.sign_and_broadcast(sender_address, messages, fee, memo)
.await?
.check_response()?;
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
let gas_info = GasInfo::new(tx_res.tx_result.gas_wanted, tx_res.tx_result.gas_used);
Ok(ExecuteResult {
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
logs: parse_raw_logs(tx_res.tx_result.log)?,
data: tx_res.tx_result.data,
transaction_hash: tx_res.hash,
gas_info,
})
@@ -372,16 +378,16 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
amount: Vec<Coin>,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_commit::Response, NymdError> {
) -> Result<TxResponse, NymdError> {
let send_msg = MsgSend {
from_address: sender_address.clone(),
to_address: recipient_address.clone(),
amount,
amount: amount.into_iter().map(Into::into).collect(),
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgSend".to_owned()))?;
self.sign_and_broadcast_commit(sender_address, vec![send_msg], fee, memo)
self.sign_and_broadcast(sender_address, vec![send_msg], fee, memo)
.await?
.check_response()
}
@@ -392,7 +398,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
msgs: I,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_commit::Response, NymdError>
) -> Result<TxResponse, NymdError>
where
I: IntoIterator<Item = (AccountId, Vec<Coin>)> + Send,
{
@@ -402,14 +408,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
MsgSend {
from_address: sender_address.clone(),
to_address,
amount,
amount: amount.into_iter().map(Into::into).collect(),
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))
})
.collect::<Result<_, _>>()?;
self.sign_and_broadcast_commit(sender_address, messages, fee, memo)
self.sign_and_broadcast(sender_address, messages, fee, memo)
.await?
.check_response()
}
@@ -421,16 +427,16 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
amount: Coin,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_commit::Response, NymdError> {
) -> Result<TxResponse, NymdError> {
let delegate_msg = MsgDelegate {
delegator_address: delegator_address.to_owned(),
validator_address: validator_address.to_owned(),
amount,
amount: amount.into(),
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgDelegate".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![delegate_msg], fee, memo)
self.sign_and_broadcast(delegator_address, vec![delegate_msg], fee, memo)
.await?
.check_response()
}
@@ -442,16 +448,16 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
amount: Coin,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_commit::Response, NymdError> {
) -> Result<TxResponse, NymdError> {
let undelegate_msg = MsgUndelegate {
delegator_address: delegator_address.to_owned(),
validator_address: validator_address.to_owned(),
amount: Some(amount),
amount: amount.into(),
}
.to_any()
.map_err(|_| NymdError::SerializationError("MsgUndelegate".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![undelegate_msg], fee, memo)
self.sign_and_broadcast(delegator_address, vec![undelegate_msg], fee, memo)
.await?
.check_response()
}
@@ -462,7 +468,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
validator_address: &AccountId,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_commit::Response, NymdError> {
) -> Result<TxResponse, NymdError> {
let withdraw_msg = MsgWithdrawDelegatorReward {
delegator_address: delegator_address.to_owned(),
validator_address: validator_address.to_owned(),
@@ -470,7 +476,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
.to_any()
.map_err(|_| NymdError::SerializationError("MsgWithdrawDelegatorReward".to_owned()))?;
self.sign_and_broadcast_commit(delegator_address, vec![withdraw_msg], fee, memo)
self.sign_and_broadcast(delegator_address, vec![withdraw_msg], fee, memo)
.await?
.check_response()
}
@@ -573,6 +579,27 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
CosmWasmClient::broadcast_tx_commit(self, tx_bytes.into()).await
}
/// Broadcast a transaction to the network and monitors its inclusion in a block.
async fn sign_and_broadcast(
&self,
signer_address: &AccountId,
messages: Vec<Any>,
fee: Fee,
memo: impl Into<String> + Send + 'static,
) -> Result<TxResponse, 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()
.map_err(|_| NymdError::SerializationError("Tx".to_owned()))?;
self.broadcast_tx(tx_bytes.into()).await
}
fn sign_direct(
&self,
signer_address: &AccountId,
@@ -638,6 +665,9 @@ pub struct Client {
rpc_client: HttpClient,
signer: DirectSecp256k1HdWallet,
gas_price: GasPrice,
broadcast_polling_rate: Duration,
broadcast_timeout: Duration,
}
impl Client {
@@ -654,8 +684,18 @@ impl Client {
rpc_client,
signer,
gas_price,
broadcast_polling_rate: DEFAULT_BROADCAST_POLLING_RATE,
broadcast_timeout: DEFAULT_BROADCAST_TIMEOUT,
})
}
pub fn set_broadcast_polling_rate(&mut self, broadcast_polling_rate: Duration) {
self.broadcast_polling_rate = broadcast_polling_rate
}
pub fn set_broadcast_timeout(&mut self, broadcast_timeout: Duration) {
self.broadcast_timeout = broadcast_timeout
}
}
#[async_trait]
@@ -669,7 +709,15 @@ impl rpc::Client for Client {
}
#[async_trait]
impl CosmWasmClient for Client {}
impl CosmWasmClient for Client {
fn broadcast_polling_rate(&self) -> Duration {
self.broadcast_polling_rate
}
fn broadcast_timeout(&self) -> Duration {
self.broadcast_timeout
}
}
#[async_trait]
impl SigningCosmWasmClient for Client {
@@ -1,23 +1,35 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// TODO: There's a significant argument to pull those out of the package and make a PR on https://github.com/cosmos/cosmos-rust/
use crate::nymd::cosmwasm_client::helpers::parse_proto_coin_vec;
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::cosmos::auth::v1beta1::{
BaseAccount as ProtoBaseAccount, ModuleAccount as ProtoModuleAccount,
};
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::cosmos::vesting::v1beta1::{
BaseVestingAccount as ProtoBaseVestingAccount,
ContinuousVestingAccount as ProtoContinuousVestingAccount,
DelayedVestingAccount as ProtoDelayedVestingAccount, Period as ProtoPeriod,
PeriodicVestingAccount as ProtoPeriodicVestingAccount,
PermanentLockedAccount as ProtoPermanentLockedAccount,
};
use cosmrs::proto::cosmwasm::wasm::v1::{
CodeInfoResponse, ContractCodeHistoryEntry as ProtoContractCodeHistoryEntry,
ContractCodeHistoryOperationType, ContractInfo as ProtoContractInfo,
};
use cosmrs::tendermint::abci::Data;
use cosmrs::tendermint::{abci, chain};
use cosmrs::tx::{AccountNumber, Gas, SequenceNumber};
use cosmrs::{tx, AccountId, Coin};
use cosmrs::{tx, AccountId, Any, Coin as CosmosCoin};
use prost::Message;
use serde::Serialize;
use std::convert::{TryFrom, TryInto};
@@ -32,8 +44,11 @@ pub struct SequenceResponse {
pub sequence: SequenceNumber,
}
/// BaseAccount defines a base account type. It contains all the necessary fields
/// for basic account functionality. Any custom account type should extend this
/// type for additional functionality (e.g. vesting).
#[derive(Debug)]
pub struct Account {
pub struct BaseAccount {
/// Bech32 account address
pub address: AccountId,
pub pubkey: Option<PublicKey>,
@@ -41,10 +56,10 @@ pub struct Account {
pub sequence: SequenceNumber,
}
impl TryFrom<BaseAccount> for Account {
impl TryFrom<ProtoBaseAccount> for BaseAccount {
type Error = NymdError;
fn try_from(value: BaseAccount) -> Result<Self, Self::Error> {
fn try_from(value: ProtoBaseAccount) -> Result<Self, Self::Error> {
let address: AccountId = value
.address
.parse()
@@ -56,7 +71,7 @@ impl TryFrom<BaseAccount> for Account {
.transpose()
.map_err(|_| NymdError::InvalidPublicKey(address.clone()))?;
Ok(Account {
Ok(BaseAccount {
address,
pubkey,
account_number: value.account_number,
@@ -65,6 +80,261 @@ impl TryFrom<BaseAccount> for Account {
}
}
/// ModuleAccount defines an account for modules that holds coins on a pool.
#[derive(Debug)]
pub struct ModuleAccount {
pub base_account: Option<BaseAccount>,
pub name: String,
pub permissions: Vec<String>,
}
impl TryFrom<ProtoModuleAccount> for ModuleAccount {
type Error = NymdError;
fn try_from(value: ProtoModuleAccount) -> Result<Self, Self::Error> {
let base_account = value.base_account.map(TryFrom::try_from).transpose()?;
Ok(ModuleAccount {
base_account,
name: value.name,
permissions: value.permissions,
})
}
}
/// BaseVestingAccount implements the VestingAccount interface. It contains all
/// the necessary fields needed for any vesting account implementation.
#[derive(Debug)]
pub struct BaseVestingAccount {
pub base_account: Option<BaseAccount>,
pub original_vesting: Vec<CosmosCoin>,
pub delegated_free: Vec<CosmosCoin>,
pub delegated_vesting: Vec<CosmosCoin>,
pub end_time: i64,
}
impl TryFrom<ProtoBaseVestingAccount> for BaseVestingAccount {
type Error = NymdError;
fn try_from(value: ProtoBaseVestingAccount) -> Result<Self, Self::Error> {
let base_account = value.base_account.map(TryFrom::try_from).transpose()?;
let original_vesting = parse_proto_coin_vec(value.original_vesting)?;
let delegated_free = parse_proto_coin_vec(value.delegated_free)?;
let delegated_vesting = parse_proto_coin_vec(value.delegated_vesting)?;
Ok(BaseVestingAccount {
base_account,
original_vesting,
delegated_free,
delegated_vesting,
end_time: value.end_time,
})
}
}
/// ContinuousVestingAccount implements the VestingAccount interface. It
/// continuously vests by unlocking coins linearly with respect to time.
#[derive(Debug)]
pub struct ContinuousVestingAccount {
pub base_vesting_account: Option<BaseVestingAccount>,
pub start_time: i64,
}
impl TryFrom<ProtoContinuousVestingAccount> for ContinuousVestingAccount {
type Error = NymdError;
fn try_from(value: ProtoContinuousVestingAccount) -> Result<Self, Self::Error> {
let base_vesting_account = value
.base_vesting_account
.map(TryFrom::try_from)
.transpose()?;
Ok(ContinuousVestingAccount {
base_vesting_account,
start_time: value.start_time,
})
}
}
/// DelayedVestingAccount implements the VestingAccount interface. It vests all
/// coins after a specific time, but non prior. In other words, it keeps them
/// locked until a specified time.
#[derive(Debug)]
pub struct DelayedVestingAccount {
pub base_vesting_account: Option<BaseVestingAccount>,
}
impl TryFrom<ProtoDelayedVestingAccount> for DelayedVestingAccount {
type Error = NymdError;
fn try_from(value: ProtoDelayedVestingAccount) -> Result<Self, Self::Error> {
let base_vesting_account = value
.base_vesting_account
.map(TryFrom::try_from)
.transpose()?;
Ok(DelayedVestingAccount {
base_vesting_account,
})
}
}
/// Period defines a length of time and amount of coins that will vest.
#[derive(Debug)]
pub struct Period {
pub length: i64,
pub amount: Vec<CosmosCoin>,
}
impl TryFrom<ProtoPeriod> for Period {
type Error = NymdError;
fn try_from(value: ProtoPeriod) -> Result<Self, Self::Error> {
Ok(Period {
length: value.length,
amount: parse_proto_coin_vec(value.amount)?,
})
}
}
/// PeriodicVestingAccount implements the VestingAccount interface. It
/// periodically vests by unlocking coins during each specified period.
#[derive(Debug)]
pub struct PeriodicVestingAccount {
pub base_vesting_account: Option<BaseVestingAccount>,
pub start_time: i64,
pub vesting_periods: Vec<Period>,
}
impl TryFrom<ProtoPeriodicVestingAccount> for PeriodicVestingAccount {
type Error = NymdError;
fn try_from(value: ProtoPeriodicVestingAccount) -> Result<Self, Self::Error> {
let base_vesting_account = value
.base_vesting_account
.map(TryFrom::try_from)
.transpose()?;
let vesting_periods = value
.vesting_periods
.into_iter()
.map(TryFrom::try_from)
.collect::<Result<_, _>>()?;
Ok(PeriodicVestingAccount {
base_vesting_account,
start_time: value.start_time,
vesting_periods,
})
}
}
/// PermanentLockedAccount implements the VestingAccount interface. It does
/// not ever release coins, locking them indefinitely. Coins in this account can
/// still be used for delegating and for governance votes even while locked.
#[derive(Debug)]
pub struct PermanentLockedAccount {
pub base_vesting_account: Option<BaseVestingAccount>,
}
impl TryFrom<ProtoPermanentLockedAccount> for PermanentLockedAccount {
type Error = NymdError;
fn try_from(value: ProtoPermanentLockedAccount) -> Result<Self, Self::Error> {
let base_vesting_account = value
.base_vesting_account
.map(TryFrom::try_from)
.transpose()?;
Ok(PermanentLockedAccount {
base_vesting_account,
})
}
}
#[derive(Debug)]
pub enum Account {
Base(BaseAccount),
Module(ModuleAccount),
BaseVesting(BaseVestingAccount),
ContinuousVesting(ContinuousVestingAccount),
DelayedVesting(DelayedVestingAccount),
PeriodicVesting(PeriodicVestingAccount),
PermanentLockedVesting(PermanentLockedAccount),
}
impl Account {
pub fn try_get_base_account(&self) -> Result<&BaseAccount, NymdError> {
match self {
Account::Base(acc) => Ok(acc),
Account::Module(acc) => acc
.base_account
.as_ref()
.ok_or(NymdError::NoBaseAccountInformationAvailable),
Account::BaseVesting(acc) => acc
.base_account
.as_ref()
.ok_or(NymdError::NoBaseAccountInformationAvailable),
Account::ContinuousVesting(acc) => acc
.base_vesting_account
.as_ref()
.and_then(|vesting_acc| vesting_acc.base_account.as_ref())
.ok_or(NymdError::NoBaseAccountInformationAvailable),
Account::DelayedVesting(acc) => acc
.base_vesting_account
.as_ref()
.and_then(|vesting_acc| vesting_acc.base_account.as_ref())
.ok_or(NymdError::NoBaseAccountInformationAvailable),
Account::PeriodicVesting(acc) => acc
.base_vesting_account
.as_ref()
.and_then(|vesting_acc| vesting_acc.base_account.as_ref())
.ok_or(NymdError::NoBaseAccountInformationAvailable),
Account::PermanentLockedVesting(acc) => acc
.base_vesting_account
.as_ref()
.and_then(|vesting_acc| vesting_acc.base_account.as_ref())
.ok_or(NymdError::NoBaseAccountInformationAvailable),
}
}
}
impl TryFrom<Any> for Account {
type Error = NymdError;
fn try_from(raw_account: Any) -> Result<Self, Self::Error> {
match raw_account.type_url.as_ref() {
"/cosmos.auth.v1beta1.BaseAccount" => Ok(Account::Base(
ProtoBaseAccount::decode(raw_account.value.as_ref())?.try_into()?,
)),
"/cosmos.auth.v1beta1.ModuleAccount" => Ok(Account::Module(
ProtoModuleAccount::decode(raw_account.value.as_ref())?.try_into()?,
)),
"/cosmos.vesting.v1beta1.BaseVestingAccount" => Ok(Account::BaseVesting(
ProtoBaseVestingAccount::decode(raw_account.value.as_ref())?.try_into()?,
)),
"/cosmos.vesting.v1beta1.ContinuousVestingAccount" => Ok(Account::ContinuousVesting(
ProtoContinuousVestingAccount::decode(raw_account.value.as_ref())?.try_into()?,
)),
"/cosmos.vesting.v1beta1.DelayedVestingAccount" => Ok(Account::DelayedVesting(
ProtoDelayedVestingAccount::decode(raw_account.value.as_ref())?.try_into()?,
)),
"/cosmos.vesting.v1beta1.PeriodicVestingAccount" => Ok(Account::PeriodicVesting(
ProtoPeriodicVestingAccount::decode(raw_account.value.as_ref())?.try_into()?,
)),
"/cosmos.vesting.v1beta1.PermanentLockedAccount" => {
Ok(Account::PermanentLockedVesting(
ProtoPermanentLockedAccount::decode(raw_account.value.as_ref())?.try_into()?,
))
}
_ => Err(NymdError::UnsupportedAccountType {
type_url: raw_account.type_url,
}),
}
}
}
#[derive(Debug)]
pub struct Code {
pub code_id: ContractCodeId,
@@ -219,7 +489,7 @@ impl TryFrom<ProtoContractCodeHistoryEntry> for ContractCodeHistoryEntry {
}
}
#[derive(Debug)]
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
pub struct GasInfo {
/// GasWanted is the maximum units of work we allow this tx to perform.
pub gas_wanted: Gas,
@@ -358,7 +628,7 @@ pub struct InstantiateOptions {
/// created and before the instantiation message is executed by the contract.
///
/// Only native tokens are supported.
pub funds: Vec<Coin>,
pub funds: Vec<CosmosCoin>,
/// A bech32 encoded address of an admin account.
/// Caution: an admin has the privilege to upgrade a contract.
@@ -366,6 +636,15 @@ pub struct InstantiateOptions {
pub admin: Option<AccountId>,
}
impl InstantiateOptions {
pub fn new<T: Into<CosmosCoin>>(funds: Vec<T>, admin: Option<AccountId>) -> Self {
InstantiateOptions {
funds: funds.into_iter().map(Into::into).collect(),
admin,
}
}
}
#[derive(Debug)]
pub struct InstantiateResult {
/// The address of the newly instantiated contract
@@ -403,6 +682,8 @@ pub struct MigrateResult {
pub struct ExecuteResult {
pub logs: Vec<Log>,
pub data: Data,
/// Transaction hash (might be used as transaction ID)
pub transaction_hash: tx::Hash,
@@ -5,6 +5,7 @@ use crate::nymd::cosmwasm_client::types::ContractCodeId;
use cosmrs::tendermint::{abci, block};
use cosmrs::{bip32, tx, AccountId};
use std::io;
use std::time::Duration;
use thiserror::Error;
pub use cosmrs::rpc::error::{
@@ -20,9 +21,12 @@ pub enum NymdError {
#[error("There was an issue with bip32 - {0}")]
Bip32Error(#[from] bip32::Error),
#[error("There was an issue with bip32 - {0}")]
#[error("There was an issue with bip39 - {0}")]
Bip39Error(#[from] bip39::Error),
#[error("There was an issue on the cosmrs side - {0}")]
CosmrsError(#[from] cosmrs::Error),
#[error("Failed to derive account address")]
AccountDerivationError,
@@ -38,10 +42,10 @@ pub enum NymdError {
#[error("There was an issue with a tendermint RPC request - {0}")]
TendermintError(#[from] TendermintRpcError),
#[error("There was an issue when attempting to serialize data")]
#[error("There was an issue when attempting to serialize data ({0})")]
SerializationError(String),
#[error("There was an issue when attempting to deserialize data")]
#[error("There was an issue when attempting to deserialize data ({0})")]
DeserializationError(String),
#[error("There was an issue when attempting to encode our protobuf data - {0}")]
@@ -81,21 +85,21 @@ pub enum NymdError {
MalformedLogString,
#[error(
"Error when broadcasting tx {hash} at height {height}. Error occurred during CheckTx phase. Code: {code}; Raw log: {raw_log}"
"Error when broadcasting tx {hash} at height {height:?}. Error occurred during CheckTx phase. Code: {code}; Raw log: {raw_log}"
)]
BroadcastTxErrorCheckTx {
hash: tx::Hash,
height: block::Height,
height: Option<block::Height>,
code: u32,
raw_log: String,
},
#[error(
"Error when broadcasting tx {hash} at height {height}. Error occurred during DeliverTx phase. Code: {code}; Raw log: {raw_log}"
"Error when broadcasting tx {hash} at height {height:?}. Error occurred during DeliverTx phase. Code: {code}; Raw log: {raw_log}"
)]
BroadcastTxErrorDeliverTx {
hash: tx::Hash,
height: block::Height,
height: Option<block::Height>,
code: u32,
raw_log: String,
},
@@ -108,6 +112,18 @@ pub enum NymdError {
#[error("Abci query failed with code {0} - {1}")]
AbciError(u32, abci::Log),
#[error("Unsupported account type: {type_url}")]
UnsupportedAccountType { type_url: String },
#[error("{coin_representation} is not a valid Cosmos Coin")]
MalformedCoin { coin_representation: String },
#[error("This account does not have BaseAccount information available to it")]
NoBaseAccountInformationAvailable,
#[error("Transaction with ID {hash} has been submitted but not yet found on the chain. You might want to check for it later. There was a total wait of {} seconds", .timeout.as_secs())]
BroadcastTimeout { hash: tx::Hash, timeout: Duration },
}
impl NymdError {
@@ -4,7 +4,7 @@
use crate::nymd::error::NymdError;
use config::defaults;
use cosmrs::tx::Gas;
use cosmrs::{Coin, Denom};
use cosmrs::Coin;
use cosmwasm_std::{Decimal, Fraction, Uint128};
use std::ops::Mul;
use std::str::FromStr;
@@ -13,11 +13,12 @@ use std::str::FromStr;
/// the smallest fee token unit, such as 0.012utoken.
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct GasPrice {
// I really hate the combination of cosmwasm Decimal with cosmos-sdk Denom,
// but cosmos-sdk's Decimal is too basic for our needs
// I really dislike the usage of cosmwasm Decimal, but I didn't feel like implementing
// our own maths subcrate just for the purposes of calculating gas requirements
// this should definitely be rectified later on
pub amount: Decimal,
pub denom: Denom,
pub denom: String,
}
impl<'a> Mul<Gas> for &'a GasPrice {
@@ -44,7 +45,10 @@ impl<'a> Mul<Gas> for &'a GasPrice {
assert!(amount.u128() <= u64::MAX as u128);
Coin {
denom: self.denom.clone(),
denom: self
.denom
.parse()
.expect("the gas price has been created with invalid denom"),
amount: (amount.u128() as u64).into(),
}
}
@@ -63,9 +67,7 @@ impl FromStr for GasPrice {
.parse()
.map_err(|_| NymdError::MalformedGasPrice)?;
let possible_denom = s.chars().skip(amount_len).collect::<String>();
let denom = possible_denom
.parse()
.map_err(|_| NymdError::MalformedGasPrice)?;
let denom = possible_denom.trim().to_string();
Ok(GasPrice { amount, denom })
}
@@ -106,8 +108,14 @@ mod tests {
);
assert!(".25upunk".parse::<GasPrice>().is_err());
assert!("0.025 upunk".parse::<GasPrice>().is_err());
assert!("0.025UPUNK".parse::<GasPrice>().is_err());
assert_eq!(
"0.025upunk".parse::<GasPrice>().unwrap(),
"0.025 upunk".parse().unwrap()
);
let gas: GasPrice = "0.025 upunk ".parse().unwrap();
assert_eq!("upunk", gas.denom);
}
#[test]
@@ -1,195 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::GasPrice;
use cosmrs::tx::{Fee, Gas};
use cosmrs::Coin;
use serde::{Deserialize, Serialize};
use std::fmt;
#[cfg_attr(test, derive(ts_rs::TS))]
#[cfg_attr(
test,
ts(export, export_to = "../../../nym-wallet/src/types/rust/operation.ts")
)]
#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum Operation {
Upload,
Init,
Migrate,
ChangeAdmin,
Send,
BondMixnode,
BondMixnodeOnBehalf,
UnbondMixnode,
UnbondMixnodeOnBehalf,
UpdateMixnodeConfig,
DelegateToMixnode,
DelegateToMixnodeOnBehalf,
UndelegateFromMixnode,
UndelegateFromMixnodeOnBehalf,
BondGateway,
BondGatewayOnBehalf,
UnbondGateway,
UnbondGatewayOnBehalf,
UpdateContractSettings,
BeginMixnodeRewarding,
FinishMixnodeRewarding,
TrackUnbondGateway,
TrackUnbondMixnode,
WithdrawVestedCoins,
TrackUndelegation,
CreatePeriodicVestingAccount,
AdvanceCurrentInterval,
AdvanceCurrentEpoch,
WriteRewardedSet,
ClearRewardedSet,
UpdateMixnetAddress,
CheckpointMixnodes,
ReconcileDelegations,
}
pub(crate) fn calculate_fee(gas_price: &GasPrice, gas_limit: Gas) -> Coin {
gas_price * gas_limit
}
impl fmt::Display for Operation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Operation::Upload => f.write_str("Upload"),
Operation::Init => f.write_str("Init"),
Operation::Migrate => f.write_str("Migrate"),
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::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"),
Operation::UpdateMixnetAddress => f.write_str("UpdateMixnetAddress"),
Operation::CheckpointMixnodes => f.write_str("CheckpointMixnodes"),
Operation::ReconcileDelegations => f.write_str("ReconcileDelegations"),
Operation::AdvanceCurrentEpoch => f.write_str("AdvanceCurrentEpoch"),
}
}
}
impl Operation {
// TODO: some value tweaking
pub fn default_gas_limit(&self) -> Gas {
match self {
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::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(),
Operation::UpdateMixnetAddress => 80_000u64.into(),
Operation::CheckpointMixnodes => 175_000u64.into(),
Operation::ReconcileDelegations => 500_000u64.into(),
Operation::AdvanceCurrentEpoch => 175_000u64.into(),
}
}
pub(crate) fn determine_custom_fee(gas_price: &GasPrice, gas_limit: Gas) -> Fee {
// we need to know 2 of the following 3 parameters (the third one is being implicit) in order to construct Fee:
// (source: https://docs.cosmos.network/v0.42/basics/gas-fees.html)
// - gas price
// - gas limit
// - fees
let fee = calculate_fee(gas_price, gas_limit);
Fee::from_amount_and_gas(fee, gas_limit)
}
pub fn default_fee(&self, gas_price: &GasPrice) -> Fee {
Self::determine_custom_fee(gas_price, self.default_gas_limit())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn calculating_fee() {
let expected = Coin {
denom: "upunk".parse().unwrap(),
amount: 1000u64.into(),
};
let gas_price = "1upunk".parse().unwrap();
let gas_limit = 1000u64.into();
assert_eq!(expected, calculate_fee(&gas_price, gas_limit));
let expected = Coin {
denom: "upunk".parse().unwrap(),
amount: 50u64.into(),
};
let gas_price = "0.05upunk".parse().unwrap();
let gas_limit = 1000u64.into();
assert_eq!(expected, calculate_fee(&gas_price, gas_limit));
let expected = Coin {
denom: "upunk".parse().unwrap(),
amount: 100000u64.into(),
};
let gas_price = "100upunk".parse().unwrap();
let gas_limit = 1000u64.into();
assert_eq!(expected, calculate_fee(&gas_price, gas_limit))
}
}
@@ -2,15 +2,15 @@
// SPDX-License-Identifier: Apache-2.0
use cosmrs::tx;
use serde::{Deserialize, Serialize};
pub mod gas_price;
pub mod helpers;
pub const DEFAULT_SIMULATED_GAS_MULTIPLIER: f32 = 1.3;
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Fee {
Manual(tx::Fee),
Manual(#[serde(with = "sealed::TxFee")] tx::Fee),
Auto(Option<f32>),
}
@@ -31,3 +31,91 @@ impl Default for Fee {
Fee::Auto(Some(DEFAULT_SIMULATED_GAS_MULTIPLIER))
}
}
// a workaround to provide serde implementation for tx::Fee. We don't want to ever expose any of those
// types to the public and ideally they will get replaced by proper implementation inside comrs
mod sealed {
use cosmrs::tx::{self, Gas};
use cosmrs::Coin as CosmosCoin;
use cosmrs::{AccountId, Decimal as CosmosDecimal, Denom as CosmosDenom};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
fn cosmos_denom_inner_getter(val: &CosmosDenom) -> String {
val.as_ref().to_string()
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "CosmosDenom")]
struct Denom(#[serde(getter = "cosmos_denom_inner_getter")] String);
impl From<Denom> for CosmosDenom {
fn from(val: Denom) -> Self {
val.0.parse().unwrap()
}
}
fn cosmos_decimal_inner_getter(val: &CosmosDecimal) -> u64 {
// haha, this code is so disgusting. I'll make a PR on cosmrs to slightly alleviate those issues...
// note: unwrap here is fine as the to_string is just returning a stringified u64 which, well, is a valid u64
val.to_string().parse().unwrap()
}
// at the time of writing it the current cosmrs' Decimal is extremely limited...
#[derive(Serialize, Deserialize)]
#[serde(remote = "CosmosDecimal")]
struct Decimal(#[serde(getter = "cosmos_decimal_inner_getter")] u64);
impl From<Decimal> for CosmosDecimal {
fn from(val: Decimal) -> Self {
val.0.into()
}
}
#[derive(Serialize, Deserialize, Clone)]
struct Coin {
#[serde(with = "Denom")]
denom: CosmosDenom,
#[serde(with = "Decimal")]
amount: CosmosDecimal,
}
impl From<Coin> for CosmosCoin {
fn from(val: Coin) -> Self {
CosmosCoin {
denom: val.denom,
amount: val.amount,
}
}
}
impl From<CosmosCoin> for Coin {
fn from(val: CosmosCoin) -> Self {
Coin {
denom: val.denom,
amount: val.amount,
}
}
}
fn coin_vec_ser<S: Serializer>(val: &[CosmosCoin], serializer: S) -> Result<S::Ok, S::Error> {
let vec: Vec<Coin> = val.iter().cloned().map(Into::into).collect();
vec.serialize(serializer)
}
fn coin_vec_deser<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Vec<CosmosCoin>, D::Error> {
let vec: Vec<Coin> = Deserialize::deserialize(deserializer)?;
Ok(vec.iter().cloned().map(Into::into).collect())
}
#[derive(Serialize, Deserialize)]
#[serde(remote = "tx::Fee")]
pub(super) struct TxFee {
#[serde(serialize_with = "coin_vec_ser")]
#[serde(deserialize_with = "coin_vec_deser")]
pub amount: Vec<CosmosCoin>,
pub gas_limit: Gas,
pub payer: Option<AccountId>,
pub granter: Option<AccountId>,
}
}
@@ -3,17 +3,18 @@
use crate::nymd::cosmwasm_client::signing_client;
use crate::nymd::cosmwasm_client::types::{
ChangeAdminResult, ContractCodeId, ExecuteResult, InstantiateOptions, InstantiateResult,
MigrateResult, SequenceResponse, UploadResult,
Account, ChangeAdminResult, ContractCodeId, ExecuteResult, InstantiateOptions,
InstantiateResult, MigrateResult, SequenceResponse, SimulateResponse, UploadResult,
};
use crate::nymd::error::NymdError;
use crate::nymd::fee::DEFAULT_SIMULATED_GAS_MULTIPLIER;
use crate::nymd::wallet::DirectSecp256k1HdWallet;
use cosmrs::rpc::endpoint::broadcast;
use cosmrs::cosmwasm;
use cosmrs::rpc::Error as TendermintRpcError;
use cosmrs::rpc::HttpClientUrl;
use cosmwasm_std::{Coin, Uint128};
use cosmrs::tx::Msg;
use cosmwasm_std::Uint128;
pub use fee::gas_price::GasPrice;
use fee::helpers::Operation;
use mixnet_contract_common::mixnode::DelegationEvent;
use mixnet_contract_common::{
ContractStateParams, Delegation, ExecuteMsg, Gateway, GatewayBond, GatewayOwnershipResponse,
@@ -28,7 +29,8 @@ use std::convert::TryInto;
pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
pub use crate::nymd::fee::Fee;
use crate::nymd::fee::DEFAULT_SIMULATED_GAS_MULTIPLIER;
pub use coin::Coin;
pub use cosmrs::bank::MsgSend;
pub use cosmrs::rpc::endpoint::tx::Response as TxResponse;
pub use cosmrs::rpc::endpoint::validators::Response as ValidatorResponse;
pub use cosmrs::rpc::HttpClient as QueryNymdClient;
@@ -41,11 +43,12 @@ pub use cosmrs::tendermint::validator::Info as TendermintValidatorInfo;
pub use cosmrs::tendermint::Time as TendermintTime;
pub use cosmrs::tx::{self, Gas};
pub use cosmrs::Coin as CosmosCoin;
pub use cosmrs::{AccountId, Decimal, Denom};
pub use cosmrs::{bip32, AccountId, Decimal, Denom};
pub use cosmwasm_std::Coin as CosmWasmCoin;
pub use signing_client::Client as SigningNymdClient;
use std::collections::HashMap;
pub use traits::{VestingQueryClient, VestingSigningClient};
pub mod coin;
pub mod cosmwasm_client;
pub mod error;
pub mod fee;
@@ -59,7 +62,6 @@ pub struct NymdClient<C> {
vesting_contract_address: Option<AccountId>,
erc20_bridge_contract_address: Option<AccountId>,
client_address: Option<Vec<AccountId>>,
custom_gas_limits: HashMap<Operation, Gas>,
simulated_gas_multiplier: f32,
}
@@ -79,7 +81,6 @@ impl NymdClient<QueryNymdClient> {
vesting_contract_address,
erc20_bridge_contract_address,
client_address: None,
custom_gas_limits: HashMap::default(),
simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER,
})
}
@@ -113,7 +114,6 @@ impl NymdClient<SigningNymdClient> {
vesting_contract_address,
erc20_bridge_contract_address,
client_address: Some(client_address),
custom_gas_limits: HashMap::default(),
simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER,
})
}
@@ -146,7 +146,6 @@ impl NymdClient<SigningNymdClient> {
vesting_contract_address,
erc20_bridge_contract_address,
client_address: Some(client_address),
custom_gas_limits: HashMap::default(),
simulated_gas_multiplier: DEFAULT_SIMULATED_GAS_MULTIPLIER,
})
}
@@ -171,6 +170,28 @@ impl<C> NymdClient<C> {
.ok_or(NymdError::NoContractAddressAvailable)
}
pub fn set_simulated_gas_multiplier(&mut self, multiplier: f32) {
self.simulated_gas_multiplier = multiplier;
}
pub fn wrap_contract_execute_message<M>(
&self,
contract_address: &AccountId,
msg: &M,
funds: Vec<Coin>,
) -> Result<cosmwasm::MsgExecuteContract, NymdError>
where
C: SigningCosmWasmClient,
M: ?Sized + Serialize,
{
Ok(cosmwasm::MsgExecuteContract {
sender: self.address().clone(),
contract: contract_address.clone(),
msg: serde_json::to_vec(msg)?,
funds: funds.into_iter().map(Into::into).collect(),
})
}
pub fn address(&self) -> &AccountId
where
C: SigningCosmWasmClient,
@@ -186,39 +207,6 @@ impl<C> NymdClient<C> {
self.client.gas_price()
}
pub fn set_custom_gas_limit(&mut self, operation: Operation, limit: Gas)
where
C: SigningCosmWasmClient + Sync,
{
self.custom_gas_limits.insert(operation, limit);
}
pub fn operation_fee(&self, operation: Operation) -> Fee
where
C: SigningCosmWasmClient + Sync,
{
if let Some(&gas_limit) = self.custom_gas_limits.get(&operation) {
Operation::determine_custom_fee(self.client.gas_price(), gas_limit).into()
} else {
Fee::Auto(Some(self.simulated_gas_multiplier))
}
}
pub fn repeated_operation_fee(&self, operation: Operation, count: u64) -> Fee
where
C: SigningCosmWasmClient + Sync,
{
if let Some(&gas_limit) = self.custom_gas_limits.get(&operation) {
Operation::determine_custom_fee(
self.client.gas_price(),
(gas_limit.value() * count).into(),
)
.into()
} else {
Fee::Auto(Some(self.simulated_gas_multiplier))
}
}
pub async fn account_sequence(&self) -> Result<SequenceResponse, NymdError>
where
C: SigningCosmWasmClient + Sync,
@@ -226,6 +214,16 @@ impl<C> NymdClient<C> {
self.client.get_sequence(self.address()).await
}
pub async fn get_account_details(
&self,
address: &AccountId,
) -> Result<Option<Account>, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
self.client.get_account(address).await
}
pub async fn get_current_block_timestamp(&self) -> Result<TendermintTime, NymdError>
where
C: CosmWasmClient + Sync,
@@ -270,7 +268,7 @@ impl<C> NymdClient<C> {
&self,
address: &AccountId,
denom: Denom,
) -> Result<Option<CosmosCoin>, NymdError>
) -> Result<Option<Coin>, NymdError>
where
C: CosmWasmClient + Sync,
{
@@ -315,6 +313,7 @@ impl<C> NymdClient<C> {
&self,
address: String,
mix_identity: IdentityKey,
proxy: Option<String>,
) -> Result<Uint128, NymdError>
where
C: CosmWasmClient + Sync,
@@ -322,6 +321,7 @@ impl<C> NymdClient<C> {
let request = QueryMsg::QueryDelegatorReward {
address,
mix_identity,
proxy,
};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
@@ -331,11 +331,15 @@ impl<C> NymdClient<C> {
pub async fn get_pending_delegation_events(
&self,
owner_address: String,
proxy_address: Option<String>,
) -> Result<Vec<DelegationEvent>, NymdError>
where
C: CosmWasmClient + Sync,
{
let request = QueryMsg::GetPendingDelegationEvents { owner_address };
let request = QueryMsg::GetPendingDelegationEvents {
owner_address,
proxy_address,
};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
.await
@@ -614,17 +618,39 @@ impl<C> NymdClient<C> {
.await
}
pub async fn simulate<I, M>(&self, messages: I) -> Result<SimulateResponse, NymdError>
where
C: SigningCosmWasmClient + Sync,
I: IntoIterator<Item = M> + Send,
M: Msg,
{
self.client
.simulate(
self.address(),
messages
.into_iter()
.map(|msg| msg.into_any())
.collect::<Result<Vec<_>, _>>()
.map_err(|_| {
NymdError::SerializationError("custom simulate messages".to_owned())
})?,
"simulating execution of transactions",
)
.await
}
/// Send funds from one address to another
pub async fn send(
&self,
recipient: &AccountId,
amount: Vec<CosmosCoin>,
amount: Vec<Coin>,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_commit::Response, NymdError>
fee: Option<Fee>,
) -> Result<TxResponse, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::Send);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
.send_tokens(self.address(), recipient, amount, fee, memo)
.await
@@ -633,13 +659,14 @@ impl<C> NymdClient<C> {
/// Send funds from one address to multiple others
pub async fn send_multiple(
&self,
msgs: Vec<(AccountId, Vec<CosmosCoin>)>,
msgs: Vec<(AccountId, Vec<Coin>)>,
memo: impl Into<String> + Send + 'static,
) -> Result<broadcast::tx_commit::Response, NymdError>
fee: Option<Fee>,
) -> Result<TxResponse, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.repeated_operation_fee(Operation::Send, msgs.len() as u64);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
.send_tokens_multiple(self.address(), msgs, fee, memo)
.await
@@ -651,7 +678,7 @@ impl<C> NymdClient<C> {
msg: &M,
fee: Fee,
memo: impl Into<String> + Send + 'static,
funds: Vec<CosmosCoin>,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
@@ -671,7 +698,7 @@ impl<C> NymdClient<C> {
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
I: IntoIterator<Item = (M, Vec<CosmosCoin>)> + Send,
I: IntoIterator<Item = (M, Vec<Coin>)> + Send,
M: Serialize,
{
self.client
@@ -683,11 +710,12 @@ impl<C> NymdClient<C> {
&self,
wasm_code: Vec<u8>,
memo: impl Into<String> + Send + 'static,
fee: Option<Fee>,
) -> Result<UploadResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::Upload);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
.upload(self.address(), wasm_code, fee, memo)
.await
@@ -700,12 +728,13 @@ impl<C> NymdClient<C> {
label: String,
memo: impl Into<String> + Send + 'static,
options: Option<InstantiateOptions>,
fee: Option<Fee>,
) -> Result<InstantiateResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
M: ?Sized + Serialize + Sync,
{
let fee = self.operation_fee(Operation::Init);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
.instantiate(self.address(), code_id, msg, label, fee, memo, options)
.await
@@ -716,11 +745,12 @@ impl<C> NymdClient<C> {
contract_address: &AccountId,
new_admin: &AccountId,
memo: impl Into<String> + Send + 'static,
fee: Option<Fee>,
) -> Result<ChangeAdminResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::ChangeAdmin);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
.update_admin(self.address(), contract_address, new_admin, fee, memo)
.await
@@ -730,11 +760,12 @@ impl<C> NymdClient<C> {
&self,
contract_address: &AccountId,
memo: impl Into<String> + Send + 'static,
fee: Option<Fee>,
) -> Result<ChangeAdminResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::ChangeAdmin);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
.clear_admin(self.address(), contract_address, fee, memo)
.await
@@ -746,28 +777,113 @@ impl<C> NymdClient<C> {
code_id: ContractCodeId,
msg: &M,
memo: impl Into<String> + Send + 'static,
fee: Option<Fee>,
) -> Result<MigrateResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
M: ?Sized + Serialize + Sync,
{
let fee = self.operation_fee(Operation::Migrate);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
.migrate(self.address(), contract_address, code_id, fee, msg, memo)
.await
}
pub async fn compound_operator_reward(
&self,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::CompoundOperatorReward {};
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
&req,
fee,
"MixnetContract::CompoundOperatorReward",
vec![],
)
.await
}
pub async fn claim_operator_reward(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::ClaimOperatorReward {};
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
&req,
fee,
"MixnetContract::ClaimOperatorReward",
vec![],
)
.await
}
pub async fn compound_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::CompoundDelegatorReward { mix_identity };
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
&req,
fee,
"MixnetContract::CompoundDelegatorReward",
vec![],
)
.await
}
pub async fn claim_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::ClaimDelegatorReward { mix_identity };
self.client
.execute(
self.address(),
self.mixnet_contract_address()?,
&req,
fee,
"MixnetContract::ClaimDelegatorReward",
vec![],
)
.await
}
/// Announce a mixnode, paying a fee.
pub async fn bond_mixnode(
&self,
mixnode: MixNode,
owner_signature: String,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::BondMixnode);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::BondMixnode {
mix_node: mixnode,
@@ -780,7 +896,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Bonding mixnode from rust!",
vec![cosmwasm_coin_to_cosmos_coin(pledge)],
vec![pledge],
)
.await
}
@@ -792,11 +908,12 @@ impl<C> NymdClient<C> {
owner: String,
owner_signature: String,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::BondMixnodeOnBehalf);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::BondMixnodeOnBehalf {
mix_node: mixnode,
@@ -810,7 +927,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Bonding mixnode on behalf from rust!",
vec![cosmwasm_coin_to_cosmos_coin(pledge)],
vec![pledge],
)
.await
}
@@ -819,16 +936,14 @@ impl<C> NymdClient<C> {
pub async fn bond_multiple_mixnodes_on_behalf(
&self,
mixnode_bonds_with_sigs: Vec<(MixNodeBond, String)>,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.repeated_operation_fee(
Operation::BondMixnodeOnBehalf,
mixnode_bonds_with_sigs.len() as u64,
);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let reqs: Vec<(ExecuteMsg, Vec<CosmosCoin>)> = mixnode_bonds_with_sigs
let reqs: Vec<(ExecuteMsg, Vec<Coin>)> = mixnode_bonds_with_sigs
.into_iter()
.map(|(bond, owner_signature)| {
(
@@ -837,7 +952,7 @@ impl<C> NymdClient<C> {
owner: bond.owner.to_string(),
owner_signature,
},
vec![cosmwasm_coin_to_cosmos_coin(bond.pledge_amount)],
vec![bond.pledge_amount.into()],
)
})
.collect();
@@ -854,11 +969,11 @@ impl<C> NymdClient<C> {
}
/// Unbond a mixnode, removing it from the network and reclaiming staked coins
pub async fn unbond_mixnode(&self) -> Result<ExecuteResult, NymdError>
pub async fn unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::UnbondMixnode);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::UnbondMixnode {};
self.client
@@ -868,17 +983,21 @@ impl<C> NymdClient<C> {
&req,
fee,
"Unbonding mixnode from rust!",
Vec::new(),
vec![],
)
.await
}
/// Unbond a mixnode on behalf of the owner, removing it from the network and reclaiming staked coins
pub async fn unbond_mixnode_on_behalf(&self, owner: String) -> Result<ExecuteResult, NymdError>
pub async fn unbond_mixnode_on_behalf(
&self,
owner: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::UnbondMixnodeOnBehalf);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::UnbondMixnodeOnBehalf { owner };
self.client
@@ -888,7 +1007,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Unbonding mixnode on behalf from rust!",
Vec::new(),
vec![],
)
.await
}
@@ -897,11 +1016,12 @@ impl<C> NymdClient<C> {
pub async fn update_mixnode_config(
&self,
profit_margin_percent: u8,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::UpdateMixnodeConfig);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::UpdateMixnodeConfig {
profit_margin_percent,
@@ -913,7 +1033,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Updating mixnode configuration from rust!",
Vec::new(),
vec![],
)
.await
}
@@ -922,12 +1042,13 @@ impl<C> NymdClient<C> {
pub async fn delegate_to_mixnode(
&self,
mix_identity: &str,
amount: &Coin,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::DelegateToMixnode);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::DelegateToMixnode {
mix_identity: mix_identity.to_string(),
@@ -939,7 +1060,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Delegating to mixnode from rust!",
vec![cosmwasm_coin_ptr_to_cosmos_coin(amount)],
vec![amount],
)
.await
}
@@ -950,12 +1071,13 @@ impl<C> NymdClient<C> {
&self,
mix_identity: &str,
delegate: &str,
amount: &Coin,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::DelegateToMixnodeOnBehalf);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::DelegateToMixnodeOnBehalf {
mix_identity: mix_identity.to_string(),
@@ -968,7 +1090,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Delegating to mixnode on behalf from rust!",
vec![cosmwasm_coin_ptr_to_cosmos_coin(amount)],
vec![amount],
)
.await
}
@@ -977,16 +1099,14 @@ impl<C> NymdClient<C> {
pub async fn delegate_to_multiple_mixnodes_on_behalf(
&self,
mixnode_delegations: Vec<Delegation>,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.repeated_operation_fee(
Operation::DelegateToMixnodeOnBehalf,
mixnode_delegations.len() as u64,
);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let reqs: Vec<(ExecuteMsg, Vec<CosmosCoin>)> = mixnode_delegations
let reqs: Vec<(ExecuteMsg, Vec<Coin>)> = mixnode_delegations
.into_iter()
.map(|delegation| {
(
@@ -994,7 +1114,7 @@ impl<C> NymdClient<C> {
mix_identity: delegation.node_identity(),
delegate: delegation.owner().to_string(),
},
vec![cosmwasm_coin_to_cosmos_coin(delegation.amount().clone())],
vec![delegation.amount().clone().into()],
)
})
.collect();
@@ -1014,11 +1134,12 @@ impl<C> NymdClient<C> {
pub async fn remove_mixnode_delegation(
&self,
mix_identity: &str,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::UndelegateFromMixnode);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::UndelegateFromMixnode {
mix_identity: mix_identity.to_string(),
@@ -1030,7 +1151,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Removing mixnode delegation from rust!",
Vec::new(),
vec![],
)
.await
}
@@ -1040,11 +1161,12 @@ impl<C> NymdClient<C> {
&self,
mix_identity: &str,
delegate: &str,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::UndelegateFromMixnodeOnBehalf);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::UndelegateFromMixnodeOnBehalf {
mix_identity: mix_identity.to_string(),
@@ -1057,7 +1179,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Removing mixnode delegation on behalf from rust!",
Vec::new(),
vec![],
)
.await
}
@@ -1068,11 +1190,12 @@ impl<C> NymdClient<C> {
gateway: Gateway,
owner_signature: String,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::BondGateway);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::BondGateway {
gateway,
@@ -1085,7 +1208,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Bonding gateway from rust!",
vec![cosmwasm_coin_to_cosmos_coin(pledge)],
vec![pledge],
)
.await
}
@@ -1097,11 +1220,12 @@ impl<C> NymdClient<C> {
owner: String,
owner_signature: String,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::BondGatewayOnBehalf);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::BondGatewayOnBehalf {
gateway,
@@ -1115,7 +1239,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Bonding gateway on behalf from rust!",
vec![cosmwasm_coin_to_cosmos_coin(pledge)],
vec![pledge],
)
.await
}
@@ -1124,16 +1248,14 @@ impl<C> NymdClient<C> {
pub async fn bond_multiple_gateways_on_behalf(
&self,
gateway_bonds_with_sigs: Vec<(GatewayBond, String)>,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.repeated_operation_fee(
Operation::BondGatewayOnBehalf,
gateway_bonds_with_sigs.len() as u64,
);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let reqs: Vec<(ExecuteMsg, Vec<CosmosCoin>)> = gateway_bonds_with_sigs
let reqs: Vec<(ExecuteMsg, Vec<Coin>)> = gateway_bonds_with_sigs
.into_iter()
.map(|(bond, owner_signature)| {
(
@@ -1142,7 +1264,7 @@ impl<C> NymdClient<C> {
owner: bond.owner.to_string(),
owner_signature,
},
vec![cosmwasm_coin_to_cosmos_coin(bond.pledge_amount)],
vec![bond.pledge_amount.into()],
)
})
.collect();
@@ -1159,11 +1281,11 @@ impl<C> NymdClient<C> {
}
/// Unbond a gateway, removing it from the network and reclaiming staked coins
pub async fn unbond_gateway(&self) -> Result<ExecuteResult, NymdError>
pub async fn unbond_gateway(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::UnbondGateway);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::UnbondGateway {};
self.client
@@ -1173,18 +1295,22 @@ impl<C> NymdClient<C> {
&req,
fee,
"Unbonding gateway from rust!",
Vec::new(),
vec![],
)
.await
}
/// Unbond a gateway on behalf of the owner, removing it from the
/// network and reclaiming staked coins
pub async fn unbond_gateway_on_behalf(&self, owner: String) -> Result<ExecuteResult, NymdError>
pub async fn unbond_gateway_on_behalf(
&self,
owner: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::UnbondGatewayOnBehalf);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::UnbondGatewayOnBehalf { owner };
self.client
@@ -1194,7 +1320,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Unbonding gateway on behalf from rust!",
Vec::new(),
vec![],
)
.await
}
@@ -1202,11 +1328,12 @@ impl<C> NymdClient<C> {
pub async fn update_contract_settings(
&self,
new_params: ContractStateParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::UpdateContractSettings);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::UpdateContractStateParams(new_params);
self.client
@@ -1216,16 +1343,16 @@ impl<C> NymdClient<C> {
&req,
fee,
"Updating contract state from rust!",
Vec::new(),
vec![],
)
.await
}
pub async fn advance_current_epoch(&self) -> Result<ExecuteResult, NymdError>
pub async fn advance_current_epoch(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::AdvanceCurrentEpoch);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::AdvanceCurrentEpoch {};
self.client
@@ -1235,16 +1362,16 @@ impl<C> NymdClient<C> {
&req,
fee,
"Advance current epoch",
Vec::new(),
vec![],
)
.await
}
pub async fn reconcile_delegations(&self) -> Result<ExecuteResult, NymdError>
pub async fn reconcile_delegations(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::ReconcileDelegations);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::ReconcileDelegations {};
self.client
@@ -1254,16 +1381,16 @@ impl<C> NymdClient<C> {
&req,
fee,
"Reconciling delegation events",
Vec::new(),
vec![],
)
.await
}
pub async fn checkpoint_mixnodes(&self) -> Result<ExecuteResult, NymdError>
pub async fn checkpoint_mixnodes(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::CheckpointMixnodes);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::CheckpointMixnodes {};
self.client
@@ -1273,7 +1400,7 @@ impl<C> NymdClient<C> {
&req,
fee,
"Snapshotting mixnodes",
Vec::new(),
vec![],
)
.await
}
@@ -1282,11 +1409,12 @@ impl<C> NymdClient<C> {
&self,
rewarded_set: Vec<IdentityKey>,
expected_active_set_size: u32,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>
where
C: SigningCosmWasmClient + Sync,
{
let fee = self.operation_fee(Operation::WriteRewardedSet);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = ExecuteMsg::WriteRewardedSet {
rewarded_set,
@@ -1299,24 +1427,8 @@ impl<C> NymdClient<C> {
&req,
fee,
"Writing rewarded set",
Vec::new(),
vec![],
)
.await
}
}
fn cosmwasm_coin_to_cosmos_coin(coin: Coin) -> CosmosCoin {
CosmosCoin {
denom: coin.denom.parse().unwrap(),
// this might be a bit iffy, cosmwasm coin stores value as u128, while cosmos does it as u64
amount: (coin.amount.u128() as u64).into(),
}
}
fn cosmwasm_coin_ptr_to_cosmos_coin(coin: &Coin) -> CosmosCoin {
CosmosCoin {
denom: coin.denom.parse().unwrap(),
// this might be a bit iffy, cosmwasm coin stores value as u128, while cosmos does it as u64
amount: (coin.amount.u128() as u64).into(),
}
}
@@ -1,11 +1,12 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nymd::coin::Coin;
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 cosmwasm_std::{Coin as CosmWasmCoin, Timestamp};
use vesting_contract::vesting::Account;
use vesting_contract_common::{
messages::QueryMsg as VestingQueryMsg, OriginalVestingResponse, Period, PledgeData,
@@ -83,8 +84,9 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request)
.await
.map(Into::into)
}
async fn spendable_coins(
@@ -97,8 +99,9 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request)
.await
.map(Into::into)
}
async fn vested_coins(
&self,
@@ -110,8 +113,9 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request)
.await
.map(Into::into)
}
async fn vesting_coins(
&self,
@@ -123,8 +127,9 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request)
.await
.map(Into::into)
}
async fn vesting_start_time(
@@ -173,8 +178,9 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request)
.await
.map(Into::into)
}
async fn delegated_vesting(
@@ -187,8 +193,9 @@ impl<C: CosmWasmClient + Sync + Send> VestingQueryClient for NymdClient<C> {
block_time,
};
self.client
.query_contract_smart(self.vesting_contract_address()?, &request)
.query_contract_smart::<_, CosmWasmCoin>(self.vesting_contract_address()?, &request)
.await
.map(Into::into)
}
async fn get_account(&self, address: &str) -> Result<Account, NymdError> {
@@ -4,35 +4,62 @@
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 crate::nymd::{Coin, Fee, NymdClient};
use async_trait::async_trait;
use cosmwasm_std::Coin;
use mixnet_contract_common::{Gateway, IdentityKey, IdentityKeyRef, MixNode};
use vesting_contract_common::messages::{ExecuteMsg as VestingExecuteMsg, VestingSpecification};
#[async_trait]
pub trait VestingSigningClient {
async fn vesting_claim_operator_reward(
&self,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_claim_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_compound_operator_reward(
&self,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_compound_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_update_mixnode_config(
&self,
profix_margin_percent: u8,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn update_mixnet_address(&self, address: &str) -> Result<ExecuteResult, NymdError>;
async fn update_mixnet_address(
&self,
address: &str,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_bond_gateway(
&self,
gateway: Gateway,
owner_signature: &str,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_unbond_gateway(&self) -> Result<ExecuteResult, NymdError>;
async fn vesting_unbond_gateway(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError>;
async fn vesting_track_unbond_gateway(
&self,
owner: &str,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_bond_mixnode(
@@ -40,33 +67,42 @@ pub trait VestingSigningClient {
mix_node: MixNode,
owner_signature: &str,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_unbond_mixnode(&self) -> Result<ExecuteResult, NymdError>;
async fn vesting_unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError>;
async fn vesting_track_unbond_mixnode(
&self,
owner: &str,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn withdraw_vested_coins(&self, amount: Coin) -> Result<ExecuteResult, NymdError>;
async fn withdraw_vested_coins(
&self,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_track_undelegation(
&self,
address: &str,
mix_identity: IdentityKey,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_delegate_to_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
amount: &Coin,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn vesting_undelegate_from_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
async fn create_periodic_vesting_account(
@@ -75,22 +111,66 @@ pub trait VestingSigningClient {
staking_address: Option<String>,
vesting_spec: Option<VestingSpecification>,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError>;
}
#[async_trait]
impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient<C> {
async fn vesting_update_mixnode_config(
&self,
profit_margin_percent: u8,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::UpdateMixnodeConfig {
profit_margin_percent,
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::UpdateMixnetConfig",
vec![],
)
.await
}
async fn update_mixnet_address(
&self,
address: &str,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::UpdateMixnetAddress {
address: address.to_string(),
};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::UpdateMixnetAddress",
vec![],
)
.await
}
async fn vesting_bond_gateway(
&self,
gateway: Gateway,
owner_signature: &str,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::BondGateway);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::BondGateway {
gateway,
owner_signature: owner_signature.to_string(),
amount: pledge,
amount: pledge.into(),
};
self.client
.execute(
@@ -104,8 +184,8 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
.await
}
async fn vesting_unbond_gateway(&self) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::UnbondGateway);
async fn vesting_unbond_gateway(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::UnbondGateway {};
self.client
.execute(
@@ -123,11 +203,12 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
&self,
owner: &str,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::TrackUnbondGateway);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::TrackUnbondGateway {
owner: owner.to_string(),
amount,
amount: amount.into(),
};
self.client
.execute(
@@ -146,12 +227,13 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
mix_node: MixNode,
owner_signature: &str,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::BondMixnode);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::BondMixnode {
mix_node,
owner_signature: owner_signature.to_string(),
amount: pledge,
amount: pledge.into(),
};
self.client
.execute(
@@ -165,8 +247,8 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
.await
}
async fn vesting_unbond_mixnode(&self) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::UnbondMixnode);
async fn vesting_unbond_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::UnbondMixnode {};
self.client
.execute(
@@ -184,11 +266,12 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
&self,
owner: &str,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::TrackUnbondMixnode);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::TrackUnbondMixnode {
owner: owner.to_string(),
amount,
amount: amount.into(),
};
self.client
.execute(
@@ -201,10 +284,15 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
)
.await
}
async fn withdraw_vested_coins(&self, amount: Coin) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::WithdrawVestedCoins);
let req = VestingExecuteMsg::WithdrawVestedCoins { amount };
async fn withdraw_vested_coins(
&self,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::WithdrawVestedCoins {
amount: amount.into(),
};
self.client
.execute(
self.address(),
@@ -216,18 +304,18 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
)
.await
}
async fn vesting_track_undelegation(
&self,
address: &str,
mix_identity: IdentityKey,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::TrackUndelegation);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::TrackUndelegation {
owner: address.to_string(),
mix_identity,
amount,
amount: amount.into(),
};
self.client
.execute(
@@ -243,12 +331,13 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
async fn vesting_delegate_to_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
amount: &Coin,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::DelegateToMixnode);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::DelegateToMixnode {
mix_identity: mix_identity.into(),
amount: amount.clone(),
amount: amount.into(),
};
self.client
.execute(
@@ -256,16 +345,18 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::DeledateToMixnode",
"VestingContract::DelegateToMixnode",
vec![],
)
.await
}
async fn vesting_undelegate_from_mixnode<'a>(
&self,
mix_identity: IdentityKeyRef<'a>,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::UndelegateFromMixnode);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::UndelegateFromMixnode {
mix_identity: mix_identity.into(),
};
@@ -280,14 +371,16 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
)
.await
}
async fn create_periodic_vesting_account(
&self,
owner_address: &str,
staking_address: Option<String>,
vesting_spec: Option<VestingSpecification>,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::CreatePeriodicVestingAccount);
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::CreateAccount {
owner_address: owner_address.to_string(),
staking_address,
@@ -300,43 +393,80 @@ impl<C: SigningCosmWasmClient + Sync + Send> VestingSigningClient for NymdClient
&req,
fee,
"VestingContract::CreatePeriodicVestingAccount",
vec![cosmwasm_coin_to_cosmos_coin(amount)],
vec![amount],
)
.await
}
async fn update_mixnet_address(&self, address: &str) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::UpdateMixnetAddress);
let req = VestingExecuteMsg::UpdateMixnetAddress {
address: address.to_string(),
};
async fn vesting_claim_operator_reward(
&self,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::ClaimOperatorReward {};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::UpdateMixnetAddress",
"VestingContract::ClaimOperatorReward",
vec![],
)
.await
}
async fn vesting_update_mixnode_config(
async fn vesting_compound_operator_reward(
&self,
profit_margin_percent: u8,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = self.operation_fee(Operation::UpdateMixnodeConfig);
let req = VestingExecuteMsg::UpdateMixnodeConfig {
profit_margin_percent,
};
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::CompoundOperatorReward {};
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::UpdateMixnetConfig",
"VestingContract::CompoundOperatorReward",
vec![],
)
.await
}
async fn vesting_claim_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::ClaimDelegatorReward { mix_identity };
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::ClaimDelegatorReward",
vec![],
)
.await
}
async fn vesting_compound_delegator_reward(
&self,
mix_identity: IdentityKey,
fee: Option<Fee>,
) -> Result<ExecuteResult, NymdError> {
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
let req = VestingExecuteMsg::CompoundDelegatorReward { mix_identity };
self.client
.execute(
self.address(),
self.vesting_contract_address()?,
&req,
fee,
"VestingContract::CompoundDelegatorReward",
vec![],
)
.await
@@ -197,38 +197,45 @@ impl DirectSecp256k1HdWalletBuilder {
#[cfg(test)]
mod tests {
use super::*;
use network_defaults::DEFAULT_NETWORK;
use network_defaults::all::Network::*;
#[test]
fn generating_account_addresses() {
let (addr1, addr2, addr3) = match DEFAULT_NETWORK.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", 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)
let mnemonics = 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",
"acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel",
"step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball"
];
let prefixes = vec![
MAINNET.bech32_prefix(),
SANDBOX.bech32_prefix(),
QA.bech32_prefix(),
];
for (mnemonic, address) in mnemonic_address.into_iter() {
let prefix = DEFAULT_NETWORK.bech32_prefix();
let wallet =
DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic.parse().unwrap()).unwrap();
assert_eq!(
wallet.try_derive_accounts().unwrap()[0].address,
address.parse().unwrap()
)
for prefix in prefixes {
let addrs = match prefix {
"nymt" => vec![
"nymt1jw6mp7d5xqc7w6xm79lha27glmd0vdt339me94",
"nymt1h5hgn94nsq4kh99rjj794hr5h5q6yfm23rjshv",
"nymt17n9flp6jflljg6fp05dsy07wcprf2uuufgn4d4",
],
"n" => vec![
"n1jw6mp7d5xqc7w6xm79lha27glmd0vdt3l9artf",
"n1h5hgn94nsq4kh99rjj794hr5h5q6yfm2lr52es",
"n17n9flp6jflljg6fp05dsy07wcprf2uuu8g40rf",
],
_ => panic!("Test needs to be updated with new bech32 prefix"),
};
for (idx, mnemonic) in mnemonics.iter().enumerate() {
let wallet =
DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic.parse().unwrap())
.unwrap();
assert_eq!(
wallet.try_derive_accounts().unwrap()[0].address,
addrs[idx].parse().unwrap()
)
}
}
}
}
@@ -10,7 +10,7 @@ use std::collections::HashMap;
use url::Url;
use validator_api_requests::models::{
CoreNodeStatusResponse, InclusionProbabilityResponse, MixnodeStatusResponse,
RewardEstimationResponse, StakeSaturationResponse,
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
};
pub mod error;
@@ -253,6 +253,36 @@ impl Client {
.await
}
pub async fn get_mixnode_avg_uptime(
&self,
identity: IdentityKeyRef<'_>,
) -> Result<UptimeResponse, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODE,
identity,
routes::AVG_UPTIME,
],
NO_PARAMS,
)
.await
}
pub async fn get_mixnode_avg_uptimes(&self) -> Result<Vec<UptimeResponse>, ValidatorAPIError> {
self.query_validator_api(
&[
routes::API_VERSION,
routes::STATUS_ROUTES,
routes::MIXNODES,
routes::AVG_UPTIME,
],
NO_PARAMS,
)
.await
}
pub async fn blind_sign(
&self,
request_body: &BlindSignRequestBody,
@@ -26,5 +26,6 @@ pub const SINCE_ARG: &str = "since";
pub const STATUS: &str = "status";
pub const REWARD_ESTIMATION: &str = "reward-estimation";
pub const AVG_UPTIME: &str = "avg_uptime";
pub const STAKE_SATURATION: &str = "stake-saturation";
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
+6
View File
@@ -10,4 +10,10 @@ pub enum CoconutInterfaceError {
#[error("Could not decode base 58 string - {0}")]
MalformedString(#[from] bs58::decode::Error),
#[error("Not enough public attributes were specified")]
NotEnoughPublicAttributes,
#[error("Could not recover bandwidth value")]
InvalidBandwidth,
}
+16 -3
View File
@@ -5,6 +5,7 @@ pub mod error;
use getset::{CopyGetters, Getters};
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use error::CoconutInterfaceError;
@@ -24,9 +25,11 @@ impl Credential {
pub fn new(
n_params: u32,
theta: Theta,
public_attributes: Vec<Vec<u8>>,
voucher_value: String,
voucher_info: String,
signature: &Signature,
) -> Credential {
let public_attributes = vec![voucher_value.into_bytes(), voucher_info.into_bytes()];
Credential {
n_params,
theta,
@@ -35,8 +38,18 @@ impl Credential {
}
}
pub fn public_attributes(&self) -> Vec<Vec<u8>> {
self.public_attributes.clone()
pub fn voucher_value(&self) -> Result<u64, CoconutInterfaceError> {
let bandwidth_vec = self
.public_attributes
.get(0)
.ok_or(CoconutInterfaceError::NotEnoughPublicAttributes)?
.to_owned();
let bandwidth_str = String::from_utf8(bandwidth_vec)
.map_err(|_| CoconutInterfaceError::InvalidBandwidth)?;
let value =
u64::from_str(&bandwidth_str).map_err(|_| CoconutInterfaceError::InvalidBandwidth)?;
Ok(value)
}
pub fn verify(&self, verification_key: &VerificationKey) -> bool {
+1
View File
@@ -7,6 +7,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cfg-if = "1.0.0"
handlebars = "3.0.1"
humantime-serde = "1.0"
log = "0.4"
+17 -5
View File
@@ -4,6 +4,8 @@
use handlebars::Handlebars;
use serde::de::DeserializeOwned;
use serde::Serialize;
#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::{fs, io};
@@ -64,11 +66,21 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
None => fs::create_dir_all(self.config_directory()),
}?;
fs::write(
custom_location
.unwrap_or_else(|| self.config_directory().join(Self::config_file_name())),
templated_config,
)
let location = custom_location
.unwrap_or_else(|| self.config_directory().join(Self::config_file_name()));
cfg_if::cfg_if! {
if #[cfg(unix)] {
fs::write(location.clone(), templated_config)?;
let mut perms = fs::metadata(location.clone())?.permissions();
perms.set_mode(0o600);
fs::set_permissions(location, perms)?;
} else {
fs::write(location, templated_config)?;
}
}
Ok(())
}
fn load_from_file(id: Option<&str>) -> io::Result<Self> {
@@ -6,5 +6,6 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cosmwasm-std = "1.0.0-beta6"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
@@ -1,18 +1,23 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Coin;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::deposit::DepositData;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {}
pub struct InstantiateMsg {
pub multisig_addr: String,
pub pool_addr: String,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
DepositFunds { data: DepositData },
ReleaseFunds { funds: Coin },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@@ -7,4 +7,4 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cosmwasm-std = "1.0.0-beta6"
cosmwasm-std = "1.0.0-beta8"
@@ -7,7 +7,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cosmwasm-std = "1.0.0-beta6"
cosmwasm-std = "1.0.0-beta8"
serde = { version = "1.0", features = ["derive"] }
serde_repr = "0.1"
@@ -13,4 +13,11 @@ pub enum MixnetContractError {
},
#[error("Error casting from U128")]
CastError,
#[error("{source}")]
StdErr {
#[from]
source: cosmwasm_std::StdError,
},
#[error("Division by zero at {}", line!())]
DivisionByZero,
}
@@ -23,7 +23,9 @@ pub const CHANGE_REWARDED_SET_EVENT_TYPE: &str = "change_rewarded_set";
pub const ADVANCE_INTERVAL_EVENT_TYPE: &str = "advance_interval";
pub const ADVANCE_EPOCH_EVENT_TYPE: &str = "advance_epoch";
pub const COMPOUND_DELEGATOR_REWARD_EVENT_TYPE: &str = "compound_delegator_reward";
pub const CLAIM_DELEGATOR_REWARD_EVENT_TYPE: &str = "claim_delegator_reward";
pub const COMPOUND_OPERATOR_REWARD_EVENT_TYPE: &str = "compound_operator_reward";
pub const CLAIM_OPERATOR_REWARD_EVENT_TYPE: &str = "claim_operator_reward";
pub const SNAPSHOT_MIXNODES_EVENT: &str = "snapshot_mixnodes";
// attributes that are used in multiple places
@@ -151,6 +153,11 @@ pub fn new_compound_operator_reward_event(owner: &Addr, amount: Uint128) -> Even
event.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_claim_operator_reward_event(owner: &Addr, amount: Uint128) -> Event {
let event = Event::new(CLAIM_OPERATOR_REWARD_EVENT_TYPE).add_attribute(OWNER_KEY, owner);
event.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_compound_delegator_reward_event(
delegator: &Addr,
proxy: &Option<Addr>,
@@ -171,6 +178,26 @@ pub fn new_compound_delegator_reward_event(
.add_attribute(DELEGATOR_KEY, delegator)
}
pub fn new_claim_delegator_reward_event(
delegator: &Addr,
proxy: &Option<Addr>,
amount: Uint128,
mix_identity: IdentityKeyRef<'_>,
) -> Event {
let mut event =
Event::new(CLAIM_DELEGATOR_REWARD_EVENT_TYPE).add_attribute(DELEGATOR_KEY, delegator);
if let Some(proxy) = proxy {
event = event.add_attribute(PROXY_KEY, proxy)
}
// coin implements Display trait and we use that implementation here
event
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_identity)
.add_attribute(DELEGATOR_KEY, delegator)
}
pub fn new_undelegation_event(
delegator: &Addr,
proxy: &Option<Addr>,
@@ -2,6 +2,9 @@
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::Env;
use schemars::gen::SchemaGenerator;
use schemars::schema::{InstanceType, Schema, SchemaObject};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::fmt::{Display, Formatter};
@@ -64,6 +67,39 @@ pub struct Interval {
length: Duration,
}
impl JsonSchema for Interval {
fn schema_name() -> String {
"Interval".to_owned()
}
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
let mut schema_object = SchemaObject {
instance_type: Some(InstanceType::Object.into()),
..SchemaObject::default()
};
let object_validation = schema_object.object();
object_validation
.properties
.insert("id".to_owned(), gen.subschema_for::<u32>());
object_validation.required.insert("id".to_owned());
// PrimitiveDateTime does not implement JsonSchema. However it has a custom
// serialization to string, so we just specify the schema to be String.
object_validation
.properties
.insert("start".to_owned(), gen.subschema_for::<String>());
object_validation.required.insert("start".to_owned());
object_validation
.properties
.insert("length".to_owned(), gen.subschema_for::<Duration>());
object_validation.required.insert("length".to_owned());
Schema::Object(schema_object)
}
}
impl Interval {
/// Initialize epoch in the contract with default values.
pub fn init_epoch(env: Env) -> Self {
@@ -222,13 +222,19 @@ impl DelegatorRewardParams {
}
pub fn determine_delegation_reward(&self, delegation_amount: Uint128) -> u128 {
if self.sigma == 0 {
return 0;
}
// change all values into their fixed representations
let delegation_amount = U128::from_num(delegation_amount.u128());
let circulating_supply = U128::from_num(self.reward_params.circulating_supply());
let scaled_delegation_amount = delegation_amount / circulating_supply;
// Div by zero checked above
let delegator_reward =
(ONE - self.profit_margin) * scaled_delegation_amount / self.sigma * self.node_profit;
(ONE - self.profit_margin) * (scaled_delegation_amount / self.sigma) * self.node_profit;
let reward = delegator_reward.max(U128::ZERO);
if let Some(int_reward) = reward.checked_cast() {
@@ -250,8 +256,14 @@ impl DelegatorRewardParams {
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
pub struct StoredNodeRewardResult {
reward: Uint128,
lambda: Uint128,
sigma: Uint128,
#[schemars(with = "String")]
#[serde(with = "fixed_U128_as_string")]
lambda: U128,
#[schemars(with = "String")]
#[serde(with = "fixed_U128_as_string")]
sigma: U128,
}
impl StoredNodeRewardResult {
@@ -259,11 +271,11 @@ impl StoredNodeRewardResult {
self.reward
}
pub fn lambda(&self) -> Uint128 {
pub fn lambda(&self) -> U128 {
self.lambda
}
pub fn sigma(&self) -> Uint128 {
pub fn sigma(&self) -> U128 {
self.sigma
}
}
@@ -279,18 +291,8 @@ impl TryFrom<NodeRewardResult> for StoredNodeRewardResult {
.checked_cast()
.ok_or(MixnetContractError::CastError)?,
),
lambda: Uint128::new(
node_reward_result
.lambda()
.checked_cast()
.ok_or(MixnetContractError::CastError)?,
),
sigma: Uint128::new(
node_reward_result
.sigma()
.checked_cast()
.ok_or(MixnetContractError::CastError)?,
),
lambda: node_reward_result.lambda(),
sigma: node_reward_result.sigma(),
})
}
}
@@ -316,6 +318,14 @@ impl NodeRewardResult {
}
}
pub struct RewardEstimate {
pub total_node_reward: u64,
pub operator_reward: u64,
pub delegators_reward: u64,
pub node_profit: u64,
pub operator_cost: u64,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct MixNodeBond {
pub pledge_amount: Coin,
@@ -408,73 +418,92 @@ impl MixNodeBond {
/ U128::from_num(circulating_supply)
}
pub fn lambda_ticked(&self, params: &RewardParams) -> U128 {
// Ratio of a bond to the token circulating supply
self.lambda(params).min(params.one_over_k())
}
pub fn lambda(&self, params: &RewardParams) -> U128 {
// Ratio of a bond to the token circulating supply
let pledge_to_circulating_supply_ratio =
self.pledge_to_circulating_supply(params.circulating_supply());
pledge_to_circulating_supply_ratio.min(params.one_over_k())
self.pledge_to_circulating_supply(params.circulating_supply())
}
pub fn sigma_ticked(&self, params: &RewardParams) -> U128 {
// Ratio of a delegation to the the token circulating supply
self.sigma(params).min(params.one_over_k())
}
pub fn sigma(&self, params: &RewardParams) -> U128 {
// Ratio of a delegation to the the token circulating supply
let total_bond_to_circulating_supply_ratio =
self.total_bond_to_circulating_supply(params.circulating_supply());
total_bond_to_circulating_supply_ratio.min(params.one_over_k())
self.total_bond_to_circulating_supply(params.circulating_supply())
}
pub fn estimate_reward(
&self,
params: &RewardParams,
) -> Result<(u64, u64, u64), MixnetContractError> {
let total_node_reward = self.reward(params);
) -> Result<RewardEstimate, MixnetContractError> {
let total_node_reward = self
.reward(params)
.reward()
.checked_to_num::<u128>()
.unwrap_or_default();
let node_profit = self
.node_profit(params)
.checked_to_num::<u128>()
.unwrap_or_default();
let operator_cost = params
.node
.operator_cost()
.checked_to_num::<u128>()
.unwrap_or_default();
let operator_reward = self.operator_reward(params);
// TODO: This overestimates the reward by a lot, it should take a Uint128 and return estiamte for that
let delegators_reward = self.reward_delegation(self.total_delegation().amount, params);
// Total reward has to be the sum of operator and delegator rewards
let delegators_reward = node_profit - operator_reward;
Ok((
total_node_reward
.reward()
.checked_to_num::<u128>()
.unwrap_or_default()
.try_into()?,
operator_reward.try_into()?,
delegators_reward.try_into()?,
))
Ok(RewardEstimate {
total_node_reward: total_node_reward.try_into()?,
operator_reward: operator_reward.try_into()?,
delegators_reward: delegators_reward.try_into()?,
node_profit: node_profit.try_into()?,
operator_cost: operator_cost.try_into()?,
})
}
// keybase://chat/nymtech#dev-core/14473
pub fn reward(&self, params: &RewardParams) -> NodeRewardResult {
let lambda = self.lambda(params);
let sigma = self.sigma(params);
let lambda_ticked = self.lambda_ticked(params);
let sigma_ticked = self.sigma_ticked(params);
let reward = params.performance()
* params.epoch_reward_pool()
* (sigma * params.omega()
+ params.alpha() * lambda * sigma * params.rewarded_set_size())
* (sigma_ticked * params.omega()
+ params.alpha() * lambda_ticked * sigma_ticked * params.rewarded_set_size())
/ (ONE + params.alpha());
// we only need regular lambda and sigma to calculate operator and delegator rewards
NodeRewardResult {
reward,
lambda,
sigma,
lambda: self.lambda(params),
sigma: self.sigma(params),
}
}
pub fn node_profit(&self, params: &RewardParams) -> U128 {
if self.reward(params).reward() < params.node.operator_cost() {
U128::from_num(0u128)
} else {
self.reward(params).reward() - params.node.operator_cost()
}
self.reward(params)
.reward()
.saturating_sub(params.node.operator_cost())
}
pub fn operator_reward(&self, params: &RewardParams) -> u128 {
let reward = self.reward(params);
let profit = if reward.reward < params.node.operator_cost() {
U128::from_num(0u128)
} else {
reward.reward - params.node.operator_cost()
};
if reward.sigma == 0 {
return 0;
}
let profit = reward.reward.saturating_sub(params.node.operator_cost());
let operator_base_reward = reward.reward.min(params.node.operator_cost());
// Div by zero checked above
let operator_reward = (self.profit_margin()
+ (ONE - self.profit_margin()) * reward.lambda / reward.sigma)
* profit;
@@ -99,6 +99,17 @@ pub enum ExecuteMsg {
},
// AdvanceCurrentInterval {},
AdvanceCurrentEpoch {},
ClaimOperatorReward {},
ClaimOperatorRewardOnBehalf {
owner: String,
},
ClaimDelegatorReward {
mix_identity: IdentityKey,
},
ClaimDelegatorRewardOnBehalf {
mix_identity: IdentityKey,
owner: String,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
@@ -171,9 +182,18 @@ pub enum QueryMsg {
QueryDelegatorReward {
address: String,
mix_identity: IdentityKey,
proxy: Option<String>,
},
GetPendingDelegationEvents {
owner_address: String,
proxy_address: Option<String>,
},
GetCheckpointsForMixnode {
mix_identity: IdentityKey,
},
GetMixnodeAtHeight {
mix_identity: IdentityKey,
height: u64,
},
}
@@ -25,11 +25,11 @@ impl NodeEpochRewards {
self.epoch_id
}
pub fn sigma(&self) -> Uint128 {
pub fn sigma(&self) -> U128 {
self.result.sigma()
}
pub fn lambda(&self) -> Uint128 {
pub fn lambda(&self) -> U128 {
self.result.lambda()
}
@@ -42,25 +42,24 @@ impl NodeEpochRewards {
}
pub fn operator_cost(&self) -> U128 {
U128::from_num(self.params.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128)
self.params.operator_cost()
}
pub fn node_profit(&self) -> U128 {
let reward = U128::from_num(self.reward().u128());
if reward < self.operator_cost() {
U128::from_num(0u128)
} else {
reward - self.operator_cost()
}
// if operating cost is higher then the reward node profit is 0
reward.saturating_sub(self.operator_cost())
}
pub fn operator_reward(&self, profit_margin: U128) -> Result<Uint128, MixnetContractError> {
let reward = self.node_profit();
let operator_base_reward = reward.min(self.operator_cost());
let operator_reward = (profit_margin
+ (ONE - profit_margin) * U128::from_num(self.lambda().u128())
/ U128::from_num(self.sigma().u128()))
* reward;
let div_by_zero_check = if let Some(value) = self.lambda().checked_div(self.sigma()) {
value
} else {
return Err(MixnetContractError::DivisionByZero);
};
let operator_reward = (profit_margin + (ONE - profit_margin) * div_by_zero_check) * reward;
let reward = (operator_reward + operator_base_reward).max(U128::from_num(0u128));
@@ -82,9 +81,15 @@ impl NodeEpochRewards {
let circulating_supply = U128::from_num(epoch_reward_params.circulating_supply());
let scaled_delegation_amount = delegation_amount / circulating_supply;
let delegator_reward = (ONE - profit_margin) * scaled_delegation_amount
/ U128::from_num(self.sigma().u128())
* self.node_profit();
let check_div_by_zero =
if let Some(value) = scaled_delegation_amount.checked_div(self.sigma()) {
value
} else {
return Err(MixnetContractError::DivisionByZero);
};
let delegator_reward = (ONE - profit_margin) * check_div_by_zero * self.node_profit();
let reward = delegator_reward.max(U128::ZERO);
if let Some(int_reward) = reward.checked_cast() {
@@ -173,11 +178,15 @@ impl NodeRewardParams {
}
pub fn operator_cost(&self) -> U128 {
U128::from_num(self.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128)
self.performance() * U128::from_num(DEFAULT_OPERATOR_INTERVAL_COST)
}
pub fn uptime(&self) -> u128 {
self.uptime.u128()
pub fn uptime(&self) -> Uint128 {
self.uptime
}
pub fn performance(&self) -> U128 {
U128::from_num(self.uptime.u128()) / U128::from_num(100)
}
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
@@ -201,6 +210,11 @@ impl RewardParams {
let denom = self.active_set_work_factor() * U128::from_num(self.rewarded_set_size())
- (self.active_set_work_factor() - ONE) * U128::from_num(self.idle_nodes().u128());
if denom == 0 {
return U128::ZERO;
}
// Div by zero checked above
if self.in_active_set() {
// work_active = factor / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
self.active_set_work_factor() / denom * self.rewarded_set_size()
@@ -223,7 +237,7 @@ impl RewardParams {
}
pub fn performance(&self) -> U128 {
U128::from_num(self.node.uptime.u128()) / U128::from_num(100)
self.node.performance()
}
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
@@ -246,8 +260,8 @@ impl RewardParams {
self.node.reward_blockstamp
}
pub fn uptime(&self) -> u128 {
self.node.uptime.u128()
pub fn uptime(&self) -> Uint128 {
self.node.uptime()
}
pub fn one_over_k(&self) -> U128 {
@@ -6,11 +6,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cosmwasm-std = "1.0.0-beta6"
cosmwasm-std = "1.0.0-beta8"
mixnet-contract-common = { path = "../mixnet-contract" }
serde = { version = "1.0", features = ["derive"] }
schemars = "0.8"
cw-storage-plus = "0.13.1"
cw-storage-plus = "0.13.2"
config = { path = "../../config" }
[dev-dependencies]
@@ -20,6 +20,7 @@ pub const VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE: &str = "vesting_update_mixno
pub const TRACK_MIXNODE_UNBOND_EVENT_TYPE: &str = "track_mixnode_unbond";
pub const TRACK_GATEWAY_UNBOND_EVENT_TYPE: &str = "track_gateway_unbond";
pub const TRACK_UNDELEGATION_EVENT_TYPE: &str = "track_undelegation";
pub const TRACK_REWARD_EVENT_TYPE: &str = "track_reaward";
// attributes that are used in multiple places
pub const OWNER_KEY: &str = "owner";
@@ -136,3 +137,7 @@ pub fn new_track_gateway_unbond_event() -> Event {
pub fn new_track_undelegation_event() -> Event {
Event::new(TRACK_UNDELEGATION_EVENT_TYPE)
}
pub fn new_track_reward_event() -> Event {
Event::new(TRACK_REWARD_EVENT_TYPE)
}
@@ -5,6 +5,8 @@ use cosmwasm_std::{Coin, Timestamp};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
pub use messages::{ExecuteMsg, InitMsg, MigrateMsg, QueryMsg};
pub mod events;
pub mod messages;
@@ -49,6 +49,18 @@ impl VestingSpecification {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
TrackReward {
amount: Coin,
address: String,
},
ClaimOperatorReward {},
ClaimDelegatorReward {
mix_identity: String,
},
CompoundDelegatorReward {
mix_identity: String,
},
CompoundOperatorReward {},
UpdateMixnodeConfig {
profit_margin_percent: u8,
},
+20
View File
@@ -0,0 +1,20 @@
[package]
name = "credential-storage"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-trait = { version = "0.1.51" }
nymcoconut = { path = "../nymcoconut" }
log = "0.4"
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"]}
thiserror = "1.0"
tokio = { version = "1.4", features = [ "rt-multi-thread", "net", "signal", "fs" ] }
[build-dependencies]
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] }
tokio = { version = "1.4", features = ["rt-multi-thread", "macros"] }
+30
View File
@@ -0,0 +1,30 @@
/*
* Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
use sqlx::{Connection, SqliteConnection};
use std::env;
#[tokio::main]
async fn main() {
let out_dir = env::var("OUT_DIR").unwrap();
let database_path = format!("{}/coconut-credential-example.sqlite", out_dir);
let mut conn = SqliteConnection::connect(&*format!("sqlite://{}?mode=rwc", database_path))
.await
.expect("Failed to create SQLx database connection");
sqlx::migrate!("./migrations")
.run(&mut conn)
.await
.expect("Failed to perform SQLx migrations");
#[cfg(target_family = "unix")]
println!("cargo:rustc-env=DATABASE_URL=sqlite://{}", &database_path);
#[cfg(target_family = "windows")]
// for some strange reason we need to add a leading `/` to the windows path even though it's
// not a valid windows path... but hey, it works...
println!("cargo:rustc-env=DATABASE_URL=sqlite:///{}", &database_path);
}
@@ -0,0 +1,22 @@
/*
* Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
CREATE TABLE coconut_credentials
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
voucher_value TEXT NOT NULL,
voucher_info TEXT NOT NULL,
serial_number TEXT NOT NULL,
binding_number TEXT NOT NULL,
signature TEXT NOT NULL UNIQUE
);
CREATE TABLE erc20_credentials
(
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
public_key TEXT NOT NULL,
private_key TEXT NOT NULL,
consumed BOOLEAN NOT NULL
);
+67
View File
@@ -0,0 +1,67 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::models::CoconutCredential;
#[derive(Clone)]
pub(crate) struct CoconutCredentialManager {
connection_pool: sqlx::SqlitePool,
}
impl CoconutCredentialManager {
/// Creates new instance of the `CoconutCredentialManager` with the provided sqlite connection pool.
///
/// # Arguments
///
/// * `connection_pool`: database connection pool to use.
pub(crate) fn new(connection_pool: sqlx::SqlitePool) -> Self {
CoconutCredentialManager { connection_pool }
}
/// Inserts provided signature into the database.
///
/// # Arguments
///
/// * `voucher_value`: Plaintext bandwidth value of the credential.
/// * `voucher_info`: Plaintext information of the credential.
/// * `serial_number`: Base58 representation of the serial number attribute.
/// * `binding_number`: Base58 representation of the binding number attribute.
/// * `signature`: Coconut credential in the form of a signature.
pub(crate) async fn insert_coconut_credential(
&self,
voucher_value: String,
voucher_info: String,
serial_number: String,
binding_number: String,
signature: String,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"INSERT INTO coconut_credentials(voucher_value, voucher_info, serial_number, binding_number, signature) VALUES (?, ?, ?, ?, ?)",
voucher_value, voucher_info, serial_number, binding_number, signature
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
/// Tries to retrieve one of the stored, unused credentials.
pub(crate) async fn get_next_coconut_credential(
&self,
) -> Result<CoconutCredential, sqlx::Error> {
sqlx::query_as!(CoconutCredential, "SELECT * FROM coconut_credentials")
.fetch_one(&self.connection_pool)
.await
}
/// Removes from the database the specified credential.
///
/// # Arguments
///
/// * `id`: Database id.
pub(crate) async fn remove_coconut_credential(&self, id: i64) -> Result<(), sqlx::Error> {
sqlx::query!("DELETE FROM coconut_credentials WHERE id = ?", id)
.execute(&self.connection_pool)
.await?;
Ok(())
}
}
+71
View File
@@ -0,0 +1,71 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::models::ERC20Credential;
#[derive(Clone)]
pub(crate) struct ERC20CredentialManager {
connection_pool: sqlx::SqlitePool,
}
impl ERC20CredentialManager {
/// Creates new instance of the `ERC20CredentialManager` with the provided sqlite connection pool.
///
/// # Arguments
///
/// * `connection_pool`: database connection pool to use.
pub(crate) fn new(connection_pool: sqlx::SqlitePool) -> Self {
ERC20CredentialManager { connection_pool }
}
/// Inserts provided signature into the database.
///
/// # Arguments
///
/// * `public_key`: Base58 representation of a public key.
/// * `private_key`: Base58 representation of a private key.
pub(crate) async fn insert_erc20_credential(
&self,
public_key: String,
private_key: String,
) -> Result<(), sqlx::Error> {
sqlx::query!(
"INSERT INTO erc20_credentials(public_key, private_key, consumed) VALUES (?, ?, ?)",
public_key,
private_key,
false,
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
/// Tries to retrieve one of the stored, unused credentials.
pub(crate) async fn get_next_erc20_credential(&self) -> Result<ERC20Credential, sqlx::Error> {
sqlx::query_as!(
ERC20Credential,
"SELECT * FROM erc20_credentials WHERE consumed = false"
)
.fetch_one(&self.connection_pool)
.await
}
/// Mark a credential as being consumed.
pub(crate) async fn consume_erc20_credential(
&self,
public_key: String,
) -> Result<(), sqlx::Error> {
sqlx::query!(
r#"
UPDATE erc20_credentials
SET consumed = true
WHERE public_key = ?
"#,
public_key
)
.execute(&self.connection_pool)
.await?;
Ok(())
}
}
+16
View File
@@ -0,0 +1,16 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use thiserror::Error;
#[derive(Error, Debug)]
pub enum StorageError {
#[error("Database experienced an internal error - {0}")]
InternalDatabaseError(#[from] sqlx::Error),
#[error("Failed to perform database migration - {0}")]
MigrationError(#[from] sqlx::migrate::MigrateError),
#[error("Inconsistent data in database")]
InconsistentData,
}
+144
View File
@@ -0,0 +1,144 @@
/*
* Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/
use crate::coconut::CoconutCredentialManager;
use crate::erc20::ERC20CredentialManager;
use crate::error::StorageError;
use crate::storage::Storage;
use crate::models::{CoconutCredential, ERC20Credential};
use async_trait::async_trait;
use log::{debug, error};
use sqlx::ConnectOptions;
use std::path::{Path, PathBuf};
mod coconut;
mod erc20;
pub mod error;
mod models;
pub mod storage;
// note that clone here is fine as upon cloning the same underlying pool will be used
#[derive(Clone)]
pub struct PersistentStorage {
coconut_credential_manager: CoconutCredentialManager,
erc20_credential_manager: ERC20CredentialManager,
}
impl PersistentStorage {
/// Initialises `PersistentStorage` using the provided path.
///
/// # Arguments
///
/// * `database_path`: path to the database.
pub async fn init<P: AsRef<Path> + Send>(database_path: P) -> Result<Self, StorageError> {
debug!(
"Attempting to connect to database {:?}",
database_path.as_ref().as_os_str()
);
let mut opts = sqlx::sqlite::SqliteConnectOptions::new()
.filename(database_path)
.create_if_missing(true);
opts.disable_statement_logging();
let connection_pool = match sqlx::SqlitePool::connect_with(opts).await {
Ok(db) => db,
Err(err) => {
error!("Failed to connect to SQLx database: {}", err);
return Err(err.into());
}
};
if let Err(err) = sqlx::migrate!("./migrations").run(&connection_pool).await {
error!("Failed to perform migration on the SQLx database: {}", err);
return Err(err.into());
}
Ok(PersistentStorage {
coconut_credential_manager: CoconutCredentialManager::new(connection_pool.clone()),
erc20_credential_manager: ERC20CredentialManager::new(connection_pool),
})
}
}
#[async_trait]
impl Storage for PersistentStorage {
async fn insert_coconut_credential(
&self,
voucher_value: String,
voucher_info: String,
serial_number: String,
binding_number: String,
signature: String,
) -> Result<(), StorageError> {
self.coconut_credential_manager
.insert_coconut_credential(
voucher_value,
voucher_info,
serial_number,
binding_number,
signature,
)
.await?;
Ok(())
}
async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, StorageError> {
let credential = self
.coconut_credential_manager
.get_next_coconut_credential()
.await?;
Ok(credential)
}
async fn remove_coconut_credential(&self, id: i64) -> Result<(), StorageError> {
self.coconut_credential_manager
.remove_coconut_credential(id)
.await?;
Ok(())
}
async fn insert_erc20_credential(
&self,
public_key: String,
private_key: String,
) -> Result<(), StorageError> {
self.erc20_credential_manager
.insert_erc20_credential(public_key, private_key)
.await?;
Ok(())
}
async fn get_next_erc20_credential(&self) -> Result<ERC20Credential, StorageError> {
let credential = self
.erc20_credential_manager
.get_next_erc20_credential()
.await?;
Ok(credential)
}
async fn consume_erc20_credential(&self, public_key: String) -> Result<(), StorageError> {
let credential = self
.erc20_credential_manager
.consume_erc20_credential(public_key)
.await?;
Ok(credential)
}
}
pub async fn initialise_storage(path: PathBuf) -> PersistentStorage {
match PersistentStorage::init(path).await {
Err(err) => panic!("failed to initialise credential storage - {}", err),
Ok(storage) => storage,
}
}
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub struct CoconutCredential {
#[allow(dead_code)]
pub id: i64,
pub voucher_value: String,
pub voucher_info: String,
pub serial_number: String,
pub binding_number: String,
pub signature: String,
}
pub struct ERC20Credential {
#[allow(dead_code)]
pub id: i64,
pub public_key: String,
pub private_key: String,
pub consumed: bool,
}
+52
View File
@@ -0,0 +1,52 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use async_trait::async_trait;
use crate::models::{CoconutCredential, ERC20Credential};
use crate::StorageError;
#[async_trait]
pub trait Storage: Send + Sync {
/// Inserts provided signature into the database.
///
/// # Arguments
///
/// * `signature`: Coconut credential in the form of a signature.
async fn insert_coconut_credential(
&self,
voucher_value: String,
voucher_info: String,
serial_number: String,
binding_number: String,
signature: String,
) -> Result<(), StorageError>;
/// Tries to retrieve one of the stored, unused credentials.
async fn get_next_coconut_credential(&self) -> Result<CoconutCredential, StorageError>;
/// Removes from the database the specified credential.
///
/// # Arguments
///
/// * `signature`: Coconut credential in the form of a signature.
async fn remove_coconut_credential(&self, id: i64) -> Result<(), StorageError>;
/// Inserts provided signature into the database.
///
/// # Arguments
///
/// * `public_key`: Base58 representation of a public key.
/// * `private_key`: Base58 representation of a private key.
async fn insert_erc20_credential(
&self,
public_key: String,
private_key: String,
) -> Result<(), StorageError>;
/// Tries to retrieve one of the stored, unused credential data.
async fn get_next_erc20_credential(&self) -> Result<ERC20Credential, StorageError>;
/// Mark a credential as being consumed.
async fn consume_erc20_credential(&self, public_key: String) -> Result<(), StorageError>;
}
+1 -1
View File
@@ -7,7 +7,7 @@ edition = "2021"
[dependencies]
bls12_381 = { version = "0.5", default-features = false, features = ["pairings", "alloc", "experimental"] }
cosmrs = { version = "0.4.1", optional = true }
cosmrs = { version = "0.7.0", optional = true }
thiserror = "1.0"
url = "2.2"
+13 -13
View File
@@ -11,7 +11,6 @@ use coconut_interface::{
PrivateAttribute, PublicAttribute, Signature, VerificationKey,
};
use crypto::asymmetric::{encryption, identity};
use network_defaults::BANDWIDTH_VALUE;
use cosmrs::tx::Hash;
@@ -159,29 +158,27 @@ impl BandwidthVoucher {
pub fn sign(&self, request: &BlindSignRequest) -> identity::Signature {
let mut message = request.to_bytes();
message.extend_from_slice(self.tx_hash.as_bytes());
message.extend_from_slice(self.tx_hash.to_string().as_bytes());
self.signing_key.sign(&message)
}
}
pub fn prepare_for_spending(
raw_identity: &[u8],
voucher_value: u64,
voucher_info: String,
serial_number: PrivateAttribute,
binding_number: PrivateAttribute,
signature: &Signature,
attributes: &BandwidthVoucher,
verification_key: &VerificationKey,
) -> Result<Credential, Error> {
let public_attributes = vec![
raw_identity.to_vec(),
BANDWIDTH_VALUE.to_be_bytes().to_vec(),
];
let params = Parameters::new(TOTAL_ATTRIBUTES)?;
prepare_credential_for_spending(
&params,
public_attributes,
attributes.serial_number,
attributes.binding_number,
voucher_value,
voucher_info,
serial_number,
binding_number,
signature,
verification_key,
)
@@ -207,7 +204,10 @@ mod test {
.to_base58_string(),
)
.unwrap(),
encryption::KeyPair::new(&mut rng).private_key().clone(),
encryption::PrivateKey::from_bytes(
&encryption::KeyPair::new(&mut rng).private_key().to_bytes(),
)
.unwrap(),
);
assert!(!BandwidthVoucher::verify_against_plain(
&[],
+6 -4
View File
@@ -11,7 +11,7 @@ use crypto::shared_key::recompute_shared_key;
use crypto::symmetric::stream_cipher;
use url::Url;
use crate::coconut::bandwidth::{BandwidthVoucher, PRIVATE_ATTRIBUTES};
use crate::coconut::bandwidth::{BandwidthVoucher, PRIVATE_ATTRIBUTES, PUBLIC_ATTRIBUTES};
use crate::coconut::params::{
ValidatorApiCredentialEncryptionAlgorithm, ValidatorApiCredentialHkdfAlgorithm,
};
@@ -175,7 +175,8 @@ pub async fn obtain_aggregate_signature(
// TODO: better type flow
pub fn prepare_credential_for_spending(
params: &Parameters,
public_attributes: Vec<Vec<u8>>,
voucher_value: u64,
voucher_info: String,
serial_number: Attribute,
binding_number: Attribute,
signature: &Signature,
@@ -190,9 +191,10 @@ pub fn prepare_credential_for_spending(
)?;
Ok(Credential::new(
(public_attributes.len() + PRIVATE_ATTRIBUTES as usize) as u32,
PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES,
theta,
public_attributes,
voucher_value.to_string(),
voucher_info,
signature,
))
}
+7 -9
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
#[cfg(feature = "coconut")]
use coconut_interface::CoconutError;
use coconut_interface::{error::CoconutInterfaceError, CoconutError};
use crypto::asymmetric::encryption::KeyRecoveryError;
use validator_client::ValidatorClientError;
@@ -17,18 +17,16 @@ pub enum Error {
NoValidatorsAvailable,
#[cfg(feature = "coconut")]
#[error("Run into a coconut error - {0}")]
#[error("Ran into a coconut error - {0}")]
CoconutError(#[from] CoconutError),
#[error("Run into a validato client error - {0}")]
#[cfg(feature = "coconut")]
#[error("Ran into a coconut interface error - {0}")]
CoconutInterfaceError(#[from] CoconutInterfaceError),
#[error("Ran into a validator client error - {0}")]
ValidatorClientError(#[from] ValidatorClientError),
#[error("Not enough public attributes were specified")]
NotEnoughPublicAttributes,
#[error("Bandwidth is expected to be represented on 8 bytes")]
InvalidBandwidthSize,
#[error("Bandwidth operation overflowed. {0}")]
BandwidthOverflow(String),
+3
View File
@@ -19,6 +19,8 @@ cipher = { version = "0.4.3", optional = true }
x25519-dalek = { version = "1.1", optional = true }
ed25519-dalek = { version = "1.0", optional = true }
rand = { version = "0.7.3", features = ["wasm-bindgen"], optional = true }
serde_bytes = { version = "0.11.6", optional = true }
serde_crate = { version = "1.0", optional = true, default_features = false, package = "serde" }
subtle-encoding = { version = "0.5", features = ["bech32-preview"]}
# internal
@@ -30,6 +32,7 @@ config = { path="../../common/config" }
rand_chacha = "0.2"
[features]
serde = ["serde_crate", "serde_bytes", "ed25519-dalek/serde", "x25519-dalek/serde"]
asymmetric = ["x25519-dalek", "ed25519-dalek"]
hashing = ["blake3", "digest", "hkdf", "hmac", "generic-array"]
symmetric = ["aes", "ctr", "cipher", "generic-array"]
+41
View File
@@ -0,0 +1,41 @@
[package]
name = "dkg"
version = "0.1.0"
edition = "2021"
resolver = "2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bitvec = "1.0.0"
# unfortunately until https://github.com/zkcrypto/bls12_381/issues/10 is resolved, we have to rely on the fork
# as we need to be able to serialize Gt so that we could create the lookup table for baby-step-giant-step algorithm
bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", branch ="gt-serialisation", default-features = false, features = ["alloc", "pairings", "experimental", "zeroize"] }
bs58 = "0.4"
lazy_static = "1.4.0"
rand = { version = "0.8.5", default-features = false}
rand_chacha = "0.3"
rand_core = "0.6.3"
sha2 = "0.9"
serde = "1.0"
serde_derive = "1.0"
thiserror = "1.0"
zeroize = { version = "1.4", features = ["zeroize_derive"] }
[dependencies.group]
version = "0.11"
default-features = false
[dependencies.ff]
version = "0.11"
default-features = false
[dev-dependencies]
criterion = "0.3"
[[bench]]
name = "benchmarks"
harness = false
+547
View File
@@ -0,0 +1,547 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use bls12_381::{G1Projective, G2Affine, G2Prepared, Scalar};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use dkg::bte::encryption::BabyStepGiantStepLookup;
use dkg::bte::proof_chunking::ProofOfChunking;
use dkg::bte::proof_discrete_log::ProofOfDiscreteLog;
use dkg::bte::proof_sharing::ProofOfSecretSharing;
use dkg::bte::{
decrypt_share, encrypt_shares, keygen, proof_chunking, proof_sharing, setup, DecryptionKey,
Epoch, PublicKey,
};
use dkg::interpolation::polynomial::Polynomial;
use dkg::{Dealing, NodeIndex, Share};
use ff::Field;
use rand_core::{RngCore, SeedableRng};
use std::collections::BTreeMap;
pub fn precompute_default_bsgs_table(c: &mut Criterion) {
c.bench_function("bsgs default table", |b| {
b.iter(|| black_box(BabyStepGiantStepLookup::default()))
});
}
pub fn precomputing_g2_generator_for_miller_loop(c: &mut Criterion) {
let g2 = G2Affine::generator();
c.bench_function("precomputing G2Prepared", |b| {
b.iter(|| black_box(G2Prepared::from(g2)))
});
}
fn prepare_keys(
mut rng: impl RngCore,
nodes: usize,
) -> (BTreeMap<NodeIndex, PublicKey>, Vec<DecryptionKey>) {
let params = setup();
let mut node_indices = (0..nodes).map(|_| rng.next_u64()).collect::<Vec<_>>();
node_indices.sort_unstable();
let mut receivers = BTreeMap::new();
let mut dks = Vec::new();
for index in &node_indices {
let (dk, pk) = keygen(&params, &mut rng);
receivers.insert(*index, *pk.public_key());
dks.push(dk)
}
(receivers, dks)
}
pub fn creating_dealing_for_3_parties(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let threshold = 2;
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 3);
c.bench_function("creating single dealing for 3 parties (threshold 2)", |b| {
b.iter(|| {
black_box({
Dealing::create(
&mut rng,
&params,
receivers.keys().next().copied().unwrap(),
threshold,
epoch,
&receivers,
None,
)
})
})
});
}
pub fn verifying_dealing_made_for_3_parties_and_recovering_share(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let threshold = 2;
let epoch = Epoch::new(2);
let (receivers, mut dks) = prepare_keys(&mut rng, 3);
let (dealing, _) = Dealing::create(
&mut rng,
&params,
receivers.keys().next().copied().unwrap(),
threshold,
epoch,
&receivers,
None,
);
let first_key = dks.get_mut(0).unwrap();
first_key.try_update_to(epoch, &params, &mut rng).unwrap();
c.bench_function(
"verifying single dealing made for 3 parties (threshold 2) and recovering share",
|b| {
b.iter(|| {
assert!(dealing
.verify(&params, epoch, threshold, &receivers, None)
.is_ok());
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, epoch, None).unwrap());
})
},
);
}
pub fn creating_dealing_for_20_parties(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let threshold = 14;
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 20);
c.bench_function(
"creating single dealing for 20 parties (threshold 14)",
|b| {
b.iter(|| {
black_box({
Dealing::create(
&mut rng,
&params,
receivers.keys().next().copied().unwrap(),
threshold,
epoch,
&receivers,
None,
)
})
})
},
);
}
pub fn verifying_dealing_made_for_20_parties_and_recovering_share(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let threshold = 14;
let epoch = Epoch::new(2);
let (receivers, mut dks) = prepare_keys(&mut rng, 20);
let (dealing, _) = Dealing::create(
&mut rng,
&params,
receivers.keys().next().copied().unwrap(),
threshold,
epoch,
&receivers,
None,
);
let first_key = dks.get_mut(0).unwrap();
first_key.try_update_to(epoch, &params, &mut rng).unwrap();
c.bench_function(
"verifying single dealing made for 20 parties (threshold 14) and recovering share",
|b| {
b.iter(|| {
assert!(dealing
.verify(&params, epoch, threshold, &receivers, None)
.is_ok());
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, epoch, None).unwrap());
})
},
);
}
pub fn creating_dealing_for_100_parties(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let threshold = 67;
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 100);
c.bench_function(
"creating single dealing for 100 parties (threshold 67)",
|b| {
b.iter(|| {
black_box({
Dealing::create(
&mut rng,
&params,
receivers.keys().next().copied().unwrap(),
threshold,
epoch,
&receivers,
None,
)
})
})
},
);
}
pub fn verifying_dealing_made_for_100_parties_and_recovering_share(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let threshold = 67;
let epoch = Epoch::new(2);
let (receivers, mut dks) = prepare_keys(&mut rng, 100);
let (dealing, _) = Dealing::create(
&mut rng,
&params,
receivers.keys().next().copied().unwrap(),
threshold,
epoch,
&receivers,
None,
);
let first_key = dks.get_mut(0).unwrap();
first_key.try_update_to(epoch, &params, &mut rng).unwrap();
c.bench_function(
"verifying single dealing made for 100 parties (threshold 67) and recovering share",
|b| {
b.iter(|| {
assert!(dealing
.verify(&params, epoch, threshold, &receivers, None)
.is_ok());
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, epoch, None).unwrap());
})
},
);
}
pub fn creating_proof_of_key_possession(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let g1 = G1Projective::generator();
let x = Scalar::random(&mut rng);
let y = g1 * x;
c.bench_function("creating proof of key possession", |b| {
b.iter(|| black_box(ProofOfDiscreteLog::construct(&mut rng, &y, &x)))
});
}
pub fn verifying_proof_of_key_possession(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let g1 = G1Projective::generator();
let x = Scalar::random(&mut rng);
let y = g1 * x;
let zk_proof = ProofOfDiscreteLog::construct(&mut rng, &y, &x);
c.bench_function("verifying proof of key possession", |b| {
b.iter(|| black_box(zk_proof.verify(&y)))
});
}
pub fn creating_proof_of_chunking_for_100_parties(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 100);
let polynomial = Polynomial::new_random(&mut rng, 67);
let shares = receivers
.keys()
.map(|&node_index| polynomial.evaluate_at(&Scalar::from(node_index)).into())
.collect::<Vec<_>>();
let remote_share_key_pairs = shares
.iter()
.zip(receivers.values())
.map(|(share, key)| (share, key))
.collect::<Vec<_>>();
let ordered_public_keys = receivers.values().copied().collect::<Vec<_>>();
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, epoch, &params, &mut rng);
c.bench_function("creating proof of chunking for 100 parties", |b| {
b.iter(|| {
let chunking_instance =
proof_chunking::Instance::new(&ordered_public_keys, &ciphertexts);
black_box(
ProofOfChunking::construct(&mut rng, chunking_instance, hazmat.r(), &shares)
.expect("failed to construct proof of chunking"),
)
})
});
}
pub fn verifying_proof_of_chunking_for_100_parties(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 100);
let polynomial = Polynomial::new_random(&mut rng, 67);
let shares = receivers
.keys()
.map(|&node_index| polynomial.evaluate_at(&Scalar::from(node_index)).into())
.collect::<Vec<_>>();
let remote_share_key_pairs = shares
.iter()
.zip(receivers.values())
.map(|(share, key)| (share, key))
.collect::<Vec<_>>();
let ordered_public_keys = receivers.values().copied().collect::<Vec<_>>();
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, epoch, &params, &mut rng);
let chunking_instance = proof_chunking::Instance::new(&ordered_public_keys, &ciphertexts);
let proof_of_chunking =
ProofOfChunking::construct(&mut rng, chunking_instance, hazmat.r(), &shares)
.expect("failed to construct proof of chunking");
c.bench_function("verifying proof of chunking for 100 parties", |b| {
b.iter(|| {
let chunking_instance =
proof_chunking::Instance::new(&ordered_public_keys, &ciphertexts);
black_box(proof_of_chunking.verify(chunking_instance))
})
});
}
pub fn creating_proof_of_secret_sharing_for_100_parties(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 100);
let polynomial = Polynomial::new_random(&mut rng, 67);
let shares = receivers
.keys()
.map(|&node_index| polynomial.evaluate_at(&Scalar::from(node_index)).into())
.collect::<Vec<_>>();
let remote_share_key_pairs = shares
.iter()
.zip(receivers.values())
.map(|(share, key)| (share, key))
.collect::<Vec<_>>();
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, epoch, &params, &mut rng);
let combined_ciphertexts = ciphertexts.combine_ciphertexts();
let combined_r = hazmat.combine_rs();
let combined_rr = ciphertexts.combine_rs();
let public_coefficients = polynomial.public_coefficients();
c.bench_function("creating proof of secret sharing for 100 parties", |b| {
b.iter(|| {
let sharing_instance = proof_sharing::Instance::new(
&receivers,
&public_coefficients,
&combined_rr,
&combined_ciphertexts,
);
black_box(
ProofOfSecretSharing::construct(&mut rng, sharing_instance, &combined_r, &shares)
.expect("failed to construct proof of secret sharing"),
)
})
});
}
pub fn verifying_proof_of_secret_sharing_for_100_parties(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 100);
let polynomial = Polynomial::new_random(&mut rng, 67);
let shares = receivers
.keys()
.map(|&node_index| polynomial.evaluate_at(&Scalar::from(node_index)).into())
.collect::<Vec<_>>();
let remote_share_key_pairs = shares
.iter()
.zip(receivers.values())
.map(|(share, key)| (share, key))
.collect::<Vec<_>>();
let (ciphertexts, hazmat) = encrypt_shares(&remote_share_key_pairs, epoch, &params, &mut rng);
let combined_ciphertexts = ciphertexts.combine_ciphertexts();
let combined_r = hazmat.combine_rs();
let combined_rr = ciphertexts.combine_rs();
let public_coefficients = polynomial.public_coefficients();
let sharing_instance = proof_sharing::Instance::new(
&receivers,
&public_coefficients,
&combined_rr,
&combined_ciphertexts,
);
let proof_of_secret_sharing =
ProofOfSecretSharing::construct(&mut rng, sharing_instance, &combined_r, &shares)
.expect("failed to construct proof of secret sharing");
c.bench_function("verifying proof of secret sharing for 100 parties", |b| {
b.iter(|| {
let sharing_instance = proof_sharing::Instance::new(
&receivers,
&public_coefficients,
&combined_rr,
&combined_ciphertexts,
);
black_box(proof_of_secret_sharing.verify(sharing_instance))
})
});
}
pub fn single_share_encryption(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let epoch = Epoch::new(2);
let (_, pk) = keygen(&params, &mut rng);
let polynomial = Polynomial::new_random(&mut rng, 3);
let share: Share = polynomial.evaluate_at(&Scalar::from(42)).into();
c.bench_function("single share encryption", |b| {
b.iter(|| {
black_box(encrypt_shares(
&[(&share, pk.public_key())],
epoch,
&params,
&mut rng,
))
})
});
}
pub fn share_encryption_100(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let epoch = Epoch::new(2);
let (receivers, _) = prepare_keys(&mut rng, 100);
let polynomial = Polynomial::new_random(&mut rng, 3);
let shares = receivers
.keys()
.map(|&node_index| polynomial.evaluate_at(&Scalar::from(node_index)).into())
.collect::<Vec<_>>();
let remote_share_key_pairs = shares
.iter()
.zip(receivers.values())
.map(|(share, key)| (share, key))
.collect::<Vec<_>>();
c.bench_function("100 shares encryption", |b| {
b.iter(|| {
black_box(encrypt_shares(
&remote_share_key_pairs,
epoch,
&params,
&mut rng,
))
})
});
}
pub fn share_decryption(c: &mut Criterion) {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let epoch = Epoch::new(2);
let (mut dk, pk) = keygen(&params, &mut rng);
let polynomial = Polynomial::new_random(&mut rng, 3);
let share: Share = polynomial.evaluate_at(&Scalar::from(42)).into();
let (ciphertexts, _) = encrypt_shares(&[(&share, pk.public_key())], epoch, &params, &mut rng);
dk.try_update_to(epoch, &params, &mut rng).unwrap();
c.bench_function("single share decryption", |b| {
b.iter(|| black_box(decrypt_share(&dk, 0, &ciphertexts, epoch, None)))
});
}
criterion_group!(
utils,
precompute_default_bsgs_table,
precomputing_g2_generator_for_miller_loop,
);
criterion_group!(
dealings_creation,
creating_dealing_for_3_parties,
creating_dealing_for_20_parties,
creating_dealing_for_100_parties,
);
// note: in our setting each party will have to create at least 4 dealings (one per attribute in credential)
// and verify 99 * 4 of them (4 from each other dealer)
criterion_group!(
dealings_verification,
verifying_dealing_made_for_3_parties_and_recovering_share,
verifying_dealing_made_for_20_parties_and_recovering_share,
verifying_dealing_made_for_100_parties_and_recovering_share,
);
criterion_group!(
proofs_of_knowledge,
creating_proof_of_key_possession,
verifying_proof_of_key_possession,
creating_proof_of_chunking_for_100_parties,
verifying_proof_of_chunking_for_100_parties,
creating_proof_of_secret_sharing_for_100_parties,
verifying_proof_of_secret_sharing_for_100_parties
);
criterion_group!(
encryption,
single_share_encryption,
share_encryption_100,
share_decryption,
);
criterion_main!(
utils,
dealings_creation,
dealings_verification,
proofs_of_knowledge,
encryption
);
// TODO: benchmark using affine vs projective representation throughout the crate
// (when conversion / serialization / computation is involved)
+772
View File
@@ -0,0 +1,772 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::bte::keys::{DecryptionKey, PublicKey};
use crate::bte::{Epoch, Params, CHUNK_SIZE, G2_GENERATOR_PREPARED, NUM_CHUNKS, PAIRING_BASE};
use crate::error::DkgError;
use crate::utils::{combine_g1_chunks, combine_scalar_chunks, deserialize_g1, deserialize_g2};
use crate::{Chunk, ChunkedShare, Share};
use bls12_381::{G1Affine, G1Projective, G2Prepared, G2Projective, Gt, Scalar};
use ff::Field;
use group::{Curve, Group, GroupEncoding};
use rand_core::RngCore;
use std::collections::HashMap;
use std::ops::Neg;
use zeroize::Zeroize;
#[derive(Debug)]
#[cfg_attr(test, derive(Clone, PartialEq))]
pub struct Ciphertexts {
pub rr: [G1Projective; NUM_CHUNKS],
pub ss: [G1Projective; NUM_CHUNKS],
pub zz: [G2Projective; NUM_CHUNKS],
pub ciphertext_chunks: Vec<[G1Projective; NUM_CHUNKS]>,
}
impl Ciphertexts {
pub fn verify_integrity(&self, params: &Params, epoch: Epoch) -> bool {
// if this checks fails it means the ciphertext is undefined as values
// in `r`, `s` and `z` are meaningless since technically this ciphertext
// has been created for 0 parties
if self.ciphertext_chunks.is_empty() {
return false;
}
let g1_neg = G1Affine::generator().neg();
let f = epoch
.as_extended_tau(&self.rr, &self.ss, &self.ciphertext_chunks)
.evaluate_f(params);
// we have to use `f` in up to `NUM_CHUNKS` pairings (if everything is valid),
// so perform some precomputation on it
let f_prepared = G2Prepared::from(f.to_affine());
// for each triple (R_i, S_i, Z_i) check whether e(g1, Z_i) == e(R_j, f) • e(S_i, h),
// which is equivalent to checking whether e(R_j, f) • e(S_i, h) • e(g1, Z_i)^-1 == id
// and due to bilinear property whether e(R_j, f) • e(S_i, h) • e(g1^-1, Z_i) == id
for i in 0..self.rr.len() {
let miller = bls12_381::multi_miller_loop(&[
(&self.rr[i].to_affine(), &f_prepared),
(&self.ss[i].to_affine(), &params._h_prepared),
(&g1_neg, &G2Prepared::from(self.zz[i].to_affine())),
]);
let res = miller.final_exponentiation();
if !bool::from(res.is_identity()) {
return false;
}
}
true
}
pub fn combine_rs(&self) -> G1Projective {
combine_g1_chunks(&self.rr)
}
// required for the purposes of the proof of secret sharing
pub fn combine_ciphertexts(&self) -> Vec<G1Projective> {
self.ciphertext_chunks
.iter()
.map(|share_ciphertext| combine_g1_chunks(share_ciphertext))
.collect()
}
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let num_receivers = self.ciphertext_chunks.len();
let mut bytes = Vec::with_capacity(NUM_CHUNKS * ((num_receivers + 2) * 48 + 96) + 4);
for r_i in &self.rr {
bytes.extend_from_slice(r_i.to_bytes().as_ref())
}
for s_i in &self.ss {
bytes.extend_from_slice(s_i.to_bytes().as_ref())
}
for z_i in &self.zz {
bytes.extend_from_slice(z_i.to_bytes().as_ref())
}
bytes.extend_from_slice(&(num_receivers as u32).to_be_bytes());
for c_i in &self.ciphertext_chunks {
for c_ij in c_i {
bytes.extend_from_slice(c_ij.to_bytes().as_ref())
}
}
bytes
}
pub(crate) fn try_from_bytes(bytes: &[u8]) -> Result<Self, DkgError> {
// at the very minimum we must have enough bytes for a single receiver
if bytes.len() < NUM_CHUNKS * (3 * 48 + 96) + 4 {
return Err(DkgError::new_deserialization_failure(
"Ciphertexts",
"insufficient number of bytes provided",
));
}
let mut rr = Vec::with_capacity(NUM_CHUNKS);
let mut ss = Vec::with_capacity(NUM_CHUNKS);
let mut zz = Vec::with_capacity(NUM_CHUNKS);
let mut i = 0;
for _ in 0..NUM_CHUNKS {
rr.push(deserialize_g1(&bytes[i..i + 48]).ok_or_else(|| {
DkgError::new_deserialization_failure("Ciphertexts.r", "invalid curve point")
})?);
i += 48;
}
for _ in 0..NUM_CHUNKS {
ss.push(deserialize_g1(&bytes[i..i + 48]).ok_or_else(|| {
DkgError::new_deserialization_failure("Ciphertexts.s", "invalid curve point")
})?);
i += 48;
}
for _ in 0..NUM_CHUNKS {
zz.push(deserialize_g2(&bytes[i..i + 96]).ok_or_else(|| {
DkgError::new_deserialization_failure("Ciphertexts.z", "invalid curve point")
})?);
i += 96;
}
let num_receivers = u32::from_be_bytes(bytes[i..i + 4].try_into().unwrap()) as usize;
i += 4;
if bytes[i..].len() != num_receivers * NUM_CHUNKS * 48 {
return Err(DkgError::new_deserialization_failure(
"Ciphertexts",
"invalid number of bytes provided",
));
}
let mut ciphertext_chunks = Vec::with_capacity(num_receivers);
for _ in 0..num_receivers {
let mut ci = Vec::with_capacity(NUM_CHUNKS);
for _ in 0..NUM_CHUNKS {
ci.push(deserialize_g1(&bytes[i..i + 48]).ok_or_else(|| {
DkgError::new_deserialization_failure(
"Ciphertexts.ciphertext_chunks",
"invalid curve point",
)
})?);
i += 48;
}
// this unwrap is fine as we have exactly NUM_CHUNKS elements in each vector
ciphertext_chunks.push(ci.try_into().unwrap())
}
// and the same is true here, the unwraps are fine as we have exactly NUM_CHUNKS elements in each as required
Ok(Ciphertexts {
rr: rr.try_into().unwrap(),
ss: ss.try_into().unwrap(),
zz: zz.try_into().unwrap(),
ciphertext_chunks,
})
}
}
#[derive(Zeroize)]
#[zeroize(drop)]
/// Randomness generated during ciphertext generation that is required for proofs of knowledge.
/// It must be handled with extreme care as its misuse might help malicious parties to recover
/// the underlying plaintext.
pub struct HazmatRandomness {
r: [Scalar; NUM_CHUNKS],
s: [Scalar; NUM_CHUNKS],
}
impl HazmatRandomness {
pub fn r(&self) -> &[Scalar; NUM_CHUNKS] {
&self.r
}
pub fn s(&self) -> &[Scalar; NUM_CHUNKS] {
&self.s
}
pub fn combine_rs(&self) -> Scalar {
combine_scalar_chunks(&self.r)
}
}
pub fn encrypt_shares(
shares: &[(&Share, &PublicKey)],
epoch: Epoch,
params: &Params,
mut rng: impl RngCore,
) -> (Ciphertexts, HazmatRandomness) {
let g1 = G1Projective::generator();
let mut rand_rs = Vec::with_capacity(NUM_CHUNKS);
let mut rand_ss = Vec::with_capacity(NUM_CHUNKS);
let mut rr = Vec::with_capacity(NUM_CHUNKS);
let mut ss = Vec::with_capacity(NUM_CHUNKS);
// generate relevant re-usable pseudorandom data
for _ in 0..NUM_CHUNKS {
let rand_r = Scalar::random(&mut rng);
let rand_s = Scalar::random(&mut rng);
// g1^r
let rr_i = g1 * rand_r;
// g1^s
let ss_i = g1 * rand_s;
rand_rs.push(rand_r);
rand_ss.push(rand_s);
rr.push(rr_i);
ss.push(ss_i);
}
// produce per-chunk ciphertexts
let mut cc = Vec::with_capacity(shares.len());
for (share, pk) in shares {
let m = share.to_chunks();
let mut ci = Vec::with_capacity(NUM_CHUNKS);
for (j, chunk) in m.chunks.iter().enumerate() {
// can't really have a more efficient implementation until https://github.com/zkcrypto/bls12_381/pull/70 is merged...
let c = pk.0 * rand_rs[j] + g1 * Scalar::from(*chunk as u64);
ci.push(c)
}
// the conversion must succeed since we must have EXACTLY `NUM_CHUNKS` elements
cc.push(ci.try_into().unwrap())
}
// convert into arrays, note that the unwraps are fine as we have exactly `NUM_CHUNKS` elements in each vector
let rr = rr.try_into().unwrap();
let ss = ss.try_into().unwrap();
let f = epoch.as_extended_tau(&rr, &ss, &cc).evaluate_f(params);
let mut zz = Vec::with_capacity(NUM_CHUNKS);
for i in 0..NUM_CHUNKS {
zz.push(f * rand_rs[i] + params.h * rand_ss[i]);
}
// the conversions here must also succeed since the other vecs also have `NUM_CHUNKS` elements
(
Ciphertexts {
rr,
ss,
zz: zz.try_into().unwrap(),
ciphertext_chunks: cc,
},
HazmatRandomness {
r: rand_rs.try_into().unwrap(),
s: rand_ss.try_into().unwrap(),
},
)
}
pub fn decrypt_share(
dk: &DecryptionKey,
// in the case of multiple receivers, specifies which index of ciphertext chunks should be used
i: usize,
ciphertext: &Ciphertexts,
epoch: Epoch,
lookup_table: Option<&BabyStepGiantStepLookup>,
) -> Result<Share, DkgError> {
let mut plaintext = ChunkedShare::default();
let decryption_node = dk.try_get_compatible_node(epoch)?;
let extended_tau = epoch.as_extended_tau(
&ciphertext.rr,
&ciphertext.ss,
&ciphertext.ciphertext_chunks,
);
if i >= ciphertext.ciphertext_chunks.len() {
return Err(DkgError::UnavailableCiphertext(i));
}
let height = decryption_node.tau.height();
let b_neg = decryption_node
.ds
.iter()
.chain(decryption_node.dh.iter())
.zip(extended_tau.0.iter().by_vals().skip(height))
.filter(|(_, i)| *i)
.map(|(d_i, _)| d_i)
.fold(decryption_node.b, |acc, d_i| acc + d_i)
.neg()
.to_affine();
let e_neg = decryption_node.e.neg().to_affine();
for j in 0..NUM_CHUNKS {
let rr_j = &ciphertext.rr[j];
let ss_j = &ciphertext.ss[j];
let zz_j = ciphertext.zz[j].to_affine();
let cc_ij = &ciphertext.ciphertext_chunks[i][j];
let miller = bls12_381::multi_miller_loop(&[
(&cc_ij.to_affine(), &G2_GENERATOR_PREPARED),
(&rr_j.to_affine(), &G2Prepared::from(b_neg)),
(&decryption_node.a.to_affine(), &G2Prepared::from(zz_j)),
(&ss_j.to_affine(), &G2Prepared::from(e_neg)),
]);
let m = miller.final_exponentiation();
plaintext.chunks[j] = baby_step_giant_step(&m, &PAIRING_BASE, lookup_table)?;
}
plaintext.try_into()
}
pub struct BabyStepGiantStepLookup {
base: Gt,
m: Chunk,
lookup: HashMap<[u8; 576], Chunk>,
}
impl BabyStepGiantStepLookup {
pub fn precompute(base: &Gt) -> Self {
let mut lookup = HashMap::new();
let mut g = Gt::identity();
// 1. m ← Ceiling(√n)
let m = (CHUNK_SIZE as f32).sqrt().ceil() as Chunk;
// 2. For all j where 0 ≤ j < m:
for j in 0..m {
// Compute α^j and store the pair (j, α^j) in a table.
lookup.insert(g.to_uncompressed(), j);
g += base;
}
BabyStepGiantStepLookup {
base: *base,
m,
lookup,
}
}
pub fn try_solve(&self, target: &Gt) -> Result<Chunk, DkgError> {
// 3. Compute α^{m}
let m_neg = Scalar::from(self.m as u64).neg();
let alpha_m = self.base * m_neg;
// 4. γ ← β. (set γ = β)
let mut gamma = *target;
// 5. For all i where 0 ≤ i < m:
for i in 0..self.m {
// 1. Check to see if γ is the second component (αj) of any pair in the table.
if let Some(j) = self.lookup.get(&gamma.to_uncompressed()) {
// 2. If so, return im + j.
return Ok(i * self.m + j);
} else {
// 3. If not, γγα^{m}.
gamma += alpha_m;
}
}
Err(DkgError::UnsolvableDiscreteLog)
}
}
impl Default for BabyStepGiantStepLookup {
fn default() -> Self {
BabyStepGiantStepLookup::precompute(&PAIRING_BASE)
}
}
/// Attempts to solve the discrete log problem g^m, where g is in the Gt group and
/// m should be within the [0, CHUNK_MAX] range.
///
/// The implementation follows the following algorithm: https://en.wikipedia.org/wiki/Baby-step_giant-step#The_algorithm
///
/// # Arguments
///
/// * `target`: the result of the exponentiation, M in M = g^m,
/// * `base`: the base used for exponentiation, g in M = g^m
/// * `lookup_table`: precomputed table containing (j, α^j) pairs
pub fn baby_step_giant_step(
target: &Gt,
base: &Gt,
lookup_table: Option<&BabyStepGiantStepLookup>,
) -> Result<Chunk, DkgError> {
if let Some(lookup_table) = lookup_table {
// compute expected m to make sure the provided lookup is valid
let m = (CHUNK_SIZE as f32).sqrt().ceil() as Chunk;
if &lookup_table.base != base || lookup_table.lookup.len() != m as usize {
return Err(DkgError::MismatchedLookupTable);
}
lookup_table.try_solve(target)
} else {
BabyStepGiantStepLookup::precompute(base).try_solve(target)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bte::{keygen, setup, DEFAULT_BSGS_TABLE};
use rand_core::SeedableRng;
fn verify_hazmat_rand(ciphertext: &Ciphertexts, randomness: &HazmatRandomness) {
let g1 = G1Projective::generator();
for i in 0..ciphertext.rr.len() {
assert_eq!(ciphertext.rr[i], g1 * randomness.r[i]);
assert_eq!(ciphertext.ss[i], g1 * randomness.s[i]);
}
}
#[test]
fn baby_giant_100_without_table() {
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
for i in 0u64..100 {
let base = Gt::random(&mut rng);
let x = (rng.next_u64() + i) % CHUNK_SIZE as u64;
let target = base * Scalar::from(x);
assert_eq!(
baby_step_giant_step(&target, &base, None).unwrap(),
x as Chunk
);
}
}
#[test]
fn baby_giant_100_with_table() {
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let base = Gt::random(&mut rng);
let lookup_table = BabyStepGiantStepLookup::precompute(&base);
let table = Some(&lookup_table);
for i in 0u64..100 {
let x = (rng.next_u64() + i) % CHUNK_SIZE as u64;
let target = base * Scalar::from(x);
assert_eq!(
baby_step_giant_step(&target, &base, table).unwrap(),
x as Chunk
);
}
}
#[test]
fn share_decryption_20() {
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let (decryption_key1, public_key1) = keygen(&params, &mut rng);
let (decryption_key2, public_key2) = keygen(&params, &mut rng);
let epoch = Epoch::new(0);
let lookup_table = &DEFAULT_BSGS_TABLE;
for _ in 0..10 {
let m1 = Share::random(&mut rng);
let m2 = Share::random(&mut rng);
let shares = &[(&m1, &public_key1.key), (&m2, &public_key2.key)];
let (ciphertext, hazmat) = encrypt_shares(shares, epoch, &params, &mut rng);
verify_hazmat_rand(&ciphertext, &hazmat);
let recovered1 =
decrypt_share(&decryption_key1, 0, &ciphertext, epoch, Some(lookup_table)).unwrap();
let recovered2 =
decrypt_share(&decryption_key2, 1, &ciphertext, epoch, Some(lookup_table)).unwrap();
assert_eq!(m1, recovered1);
assert_eq!(m2, recovered2);
}
}
#[test]
fn share_encryption_under_nonzero_epoch() {
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let (mut decryption_key1, public_key1) = keygen(&params, &mut rng);
let (mut decryption_key2, public_key2) = keygen(&params, &mut rng);
let epoch = Epoch::new(12345);
decryption_key1
.try_update_to(epoch, &params, &mut rng)
.unwrap();
decryption_key2
.try_update_to(epoch, &params, &mut rng)
.unwrap();
let lookup_table = &DEFAULT_BSGS_TABLE;
for _ in 0..10 {
let m1 = Share::random(&mut rng);
let m2 = Share::random(&mut rng);
let shares = &[(&m1, &public_key1.key), (&m2, &public_key2.key)];
let (ciphertext, hazmat) = encrypt_shares(shares, epoch, &params, &mut rng);
verify_hazmat_rand(&ciphertext, &hazmat);
let recovered1 =
decrypt_share(&decryption_key1, 0, &ciphertext, epoch, Some(lookup_table)).unwrap();
let recovered2 =
decrypt_share(&decryption_key2, 1, &ciphertext, epoch, Some(lookup_table)).unwrap();
assert_eq!(m1, recovered1);
assert_eq!(m2, recovered2);
}
}
#[test]
fn decryption_with_root_key() {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let (root_key, public_key) = keygen(&params, &mut rng);
let share = Share::random(&mut rng);
let epoch0 = Epoch::new(0);
let epoch42 = Epoch::new(42);
let epoch_big = Epoch::new(3292547435);
let (ciphertext1, hazmat1) =
encrypt_shares(&[(&share, &public_key.key)], epoch0, &params, &mut rng);
verify_hazmat_rand(&ciphertext1, &hazmat1);
let (ciphertext2, hazmat2) =
encrypt_shares(&[(&share, &public_key.key)], epoch42, &params, &mut rng);
verify_hazmat_rand(&ciphertext2, &hazmat2);
let (ciphertext3, hazmat3) =
encrypt_shares(&[(&share, &public_key.key)], epoch_big, &params, &mut rng);
verify_hazmat_rand(&ciphertext3, &hazmat3);
let recovered1 = decrypt_share(&root_key, 0, &ciphertext1, epoch0, None).unwrap();
let recovered2 = decrypt_share(&root_key, 0, &ciphertext2, epoch42, None).unwrap();
let recovered3 = decrypt_share(&root_key, 0, &ciphertext3, epoch_big, None).unwrap();
assert_eq!(share, recovered1);
assert_eq!(share, recovered2);
assert_eq!(share, recovered3);
}
#[test]
fn update_and_decrypt_10() {
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let (mut decryption_key, public_key) = keygen(&params, &mut rng);
for epoch_value in 0..10 {
let epoch = Epoch::new(epoch_value);
let share = Share::random(&mut rng);
decryption_key
.try_update_to(epoch, &params, &mut rng)
.unwrap();
let (ciphertext, hazmat) =
encrypt_shares(&[(&share, &public_key.key)], epoch, &params, &mut rng);
verify_hazmat_rand(&ciphertext, &hazmat);
let recovered = decrypt_share(&decryption_key, 0, &ciphertext, epoch, None).unwrap();
assert_eq!(share, recovered);
}
}
#[test]
fn reblinding_node_doesnt_affect_decryption() {
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let params = setup();
let (mut decryption_key, public_key) = keygen(&params, &mut rng);
let epoch = Epoch::new(12345);
decryption_key
.try_update_to(epoch, &params, &mut rng)
.unwrap();
for node in decryption_key.nodes.iter_mut() {
node.reblind(&params, &mut rng);
}
let share = Share::random(&mut rng);
let (ciphertext, hazmat) =
encrypt_shares(&[(&share, &public_key.key)], epoch, &params, &mut rng);
verify_hazmat_rand(&ciphertext, &hazmat);
let recovered = decrypt_share(&decryption_key, 0, &ciphertext, epoch, None).unwrap();
assert_eq!(share, recovered);
// attempt to update the key again so we have to derive fresh nodes using previous reblinded results
let epoch2 = Epoch::new(67890);
decryption_key
.try_update_to(epoch2, &params, &mut rng)
.unwrap();
for node in decryption_key.nodes.iter_mut() {
node.reblind(&params, &mut rng);
}
let share2 = Share::random(&mut rng);
let (ciphertext, hazmat) =
encrypt_shares(&[(&share2, &public_key.key)], epoch2, &params, &mut rng);
verify_hazmat_rand(&ciphertext, &hazmat);
let recovered = decrypt_share(&decryption_key, 0, &ciphertext, epoch2, None).unwrap();
assert_eq!(share2, recovered);
}
#[test]
fn ciphertext_integrity_check_passes_for_valid_data() {
let params = setup();
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (mut dk, public_key) = keygen(&params, &mut rng);
let epoch = Epoch::new(1);
dk.try_update_to(epoch, &params, &mut rng).unwrap();
let share = Share::random(&mut rng);
let (ciphertext, _) =
encrypt_shares(&[(&share, &public_key.key)], epoch, &params, &mut rng);
assert!(ciphertext.verify_integrity(&params, epoch))
}
#[test]
fn ciphertext_integrity_check_passes_fails_for_malformed_data() {
let params = setup();
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (mut dk, public_key) = keygen(&params, &mut rng);
let epoch = Epoch::new(1);
dk.try_update_to(epoch, &params, &mut rng).unwrap();
let share = Share::random(&mut rng);
let (ciphertext, _) =
encrypt_shares(&[(&share, &public_key.key)], epoch, &params, &mut rng);
let mut bad_cipher1 = ciphertext.clone();
bad_cipher1.rr[4] = G1Projective::generator();
assert!(!bad_cipher1.verify_integrity(&params, epoch));
let mut bad_cipher2 = ciphertext.clone();
bad_cipher2.ss[4] = G1Projective::generator();
assert!(!bad_cipher2.verify_integrity(&params, epoch));
let mut bad_cipher3 = ciphertext;
bad_cipher3.zz[4] = G2Projective::generator();
assert!(!bad_cipher3.verify_integrity(&params, epoch));
}
#[test]
fn ciphertext_integrity_check_passes_fails_for_wrong_epoch() {
let params = setup();
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (mut dk, public_key) = keygen(&params, &mut rng);
let epoch = Epoch::new(1);
dk.try_update_to(epoch, &params, &mut rng).unwrap();
let share = Share::random(&mut rng);
let (ciphertext, _) =
encrypt_shares(&[(&share, &public_key.key)], epoch, &params, &mut rng);
let another_epoch = Epoch::new(2);
assert!(!ciphertext.verify_integrity(&params, another_epoch))
}
#[test]
fn ciphertext_combining() {
let params = setup();
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let nodes = 3;
let mut shares = Vec::new();
let mut public_keys = Vec::new();
for _ in 0..nodes {
shares.push(Share::random(&mut rng));
let (_, pk) = keygen(&params, &mut rng);
public_keys.push(*pk.public_key());
}
let refs = shares.iter().zip(public_keys.iter()).collect::<Vec<_>>();
let (ciphertext, hazmat) = encrypt_shares(&refs, Epoch::new(42), &params, &mut rng);
let combined_r = combine_scalar_chunks(hazmat.r());
let combined_rr = ciphertext.combine_rs();
let combined_ciphertexts = ciphertext.combine_ciphertexts();
let g1 = G1Projective::generator();
for i in 0..nodes {
let expected = public_keys[i].0 * combined_r + g1 * shares[i].0;
assert_eq!(expected, combined_ciphertexts[i]);
assert_eq!(combined_rr, g1 * combined_r);
}
}
#[test]
fn ciphertexts_roundtrip() {
fn random_ciphertexts(mut rng: impl RngCore, num_receivers: usize) -> Ciphertexts {
Ciphertexts {
rr: (0..NUM_CHUNKS)
.map(|_| G1Projective::random(&mut rng))
.collect::<Vec<_>>()
.try_into()
.unwrap(),
ss: (0..NUM_CHUNKS)
.map(|_| G1Projective::random(&mut rng))
.collect::<Vec<_>>()
.try_into()
.unwrap(),
zz: (0..NUM_CHUNKS)
.map(|_| G2Projective::random(&mut rng))
.collect::<Vec<_>>()
.try_into()
.unwrap(),
ciphertext_chunks: (0..num_receivers)
.map(|_| {
(0..NUM_CHUNKS)
.map(|_| G1Projective::random(&mut rng))
.collect::<Vec<_>>()
.try_into()
.unwrap()
})
.collect(),
}
}
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let good_ciphertexts = vec![
random_ciphertexts(&mut rng, 1),
random_ciphertexts(&mut rng, 2),
random_ciphertexts(&mut rng, 10),
];
for ciphertexts in &good_ciphertexts {
let bytes = ciphertexts.to_bytes();
let recovered = Ciphertexts::try_from_bytes(&bytes).unwrap();
assert_eq!(ciphertexts, &recovered);
}
// ciphertext for 0 receivers is invalid by default
let ciphertexts = random_ciphertexts(&mut rng, 0);
let bytes = ciphertexts.to_bytes();
assert!(Ciphertexts::try_from_bytes(&bytes).is_err());
}
}
+875
View File
@@ -0,0 +1,875 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::bte::proof_discrete_log::ProofOfDiscreteLog;
use crate::bte::{Epoch, Params, Tau};
use crate::error::DkgError;
use crate::utils::{deserialize_g1, deserialize_g2, deserialize_scalar};
use bls12_381::{G1Projective, G2Projective, Scalar};
use ff::Field;
use group::GroupEncoding;
use rand_core::RngCore;
use zeroize::Zeroize;
#[derive(Debug, Zeroize)]
#[zeroize(drop)]
#[cfg_attr(test, derive(Clone, PartialEq))]
pub(crate) struct Node {
pub(crate) tau: Tau,
// g1^rho
pub(crate) a: G1Projective,
// g2^x
pub(crate) b: G2Projective,
// f_i^rho, up to lambda_t elements
pub(crate) ds: Vec<G2Projective>,
// fh_i^rho, always lambda_h elements
pub(crate) dh: Vec<G2Projective>,
// h^rho
pub(crate) e: G2Projective,
}
impl Node {
fn new_root(
a: G1Projective,
b: G2Projective,
ds: Vec<G2Projective>,
dh: Vec<G2Projective>,
e: G2Projective,
) -> Self {
Node {
tau: Tau::new_root(),
a,
b,
ds,
dh,
e,
}
}
fn is_root(&self) -> bool {
self.tau.0.is_empty()
}
pub(crate) fn reblind(&mut self, params: &Params, mut rng: impl RngCore) {
let delta = Scalar::random(&mut rng);
self.a += G1Projective::generator() * delta;
// TODO: or do we have to do full tau evaluation here?
self.b += self.tau.evaluate_partial_f(params) * delta;
self.ds
.iter_mut()
.zip(params.fs.iter().skip(self.tau.height()))
.for_each(|(d_i, f_i)| *d_i += f_i * delta);
self.dh
.iter_mut()
.zip(params.fh.iter())
.for_each(|(d_i, f_i)| *d_i += f_i * delta);
self.e += params.h * delta;
}
// note: it's unsafe to use this method outside `try_update_to` as
// we have guaranteed there that `self` is parent of the target
// and that `self.tau != target_tau`
/// Given `self` with `Tau1` and `target_tau` with `Tau2`, such that `Tau1` prefixes `Tau2`,
/// i.e. `Tau2 == Tau1 || SUFFIX`, and `Tau2` is a leaf node, derive all required crypto material
/// for its construction.
fn derive_target_child_with_partials(
&self,
params: &Params,
target_tau: Tau,
partial_b: &G2Projective,
partial_f: &G2Projective,
mut rng: impl RngCore,
) -> Self {
debug_assert!(self.tau.is_parent_of(&target_tau));
debug_assert_ne!(self.tau, target_tau);
let delta = Scalar::random(&mut rng);
let a = self.a + G1Projective::generator() * delta;
let b = partial_b + partial_f * delta;
let ds = self
.ds
.iter()
.zip(params.fs.iter())
.skip(target_tau.height())
.map(|(d_i, f_i)| d_i + f_i * delta)
.collect();
let dh = self
.dh
.iter()
.zip(params.fh.iter())
.map(|(dh_i, fh_i)| dh_i + fh_i * delta)
.collect();
let e = self.e + params.h * delta;
Node {
tau: target_tau,
a,
b,
ds,
dh,
e,
}
}
// note: it's unsafe to use this method outside `try_update_to` as
// we have guaranteed there that `self` is parent of the target
// and that `self.tau != target_tau`
/// Given `self` with `Tau1` and `most_direct_parent` with `Tau2`, such that `Tau1` prefixes `Tau2`,
/// i.e. `Tau2 == Tau1 || SUFFIX`, derive node with `Tau3 = Tau2 || 1`
fn derive_right_nonfinal_child_of_with_partials(
&self,
params: &Params,
most_direct_parent: Tau,
partial_b: &G2Projective,
partial_f: &G2Projective,
mut rng: impl RngCore,
) -> Self {
let right_branch = most_direct_parent.right_child();
debug_assert!(self.tau.is_parent_of(&most_direct_parent));
debug_assert!(self.tau.is_parent_of(&right_branch));
debug_assert_ne!(self.tau, right_branch);
// n is height difference between self and the child
let n = right_branch.height() - self.tau.height();
// i is the index of the last bit we just added
let i = right_branch.height() - 1;
let delta = Scalar::random(&mut rng);
let a = self.a + G1Projective::generator() * delta;
let d0 = self.ds[n - 1];
let b = partial_b + d0 + (partial_f + params.fs[i]) * delta;
let ds = self
.ds
.iter()
.skip(n)
.zip(params.fs.iter().skip(right_branch.height()))
.map(|(d_i, f_i)| d_i + f_i * delta)
.collect();
let dh = self
.dh
.iter()
.zip(params.fh.iter())
.map(|(dh_i, fh_i)| dh_i + fh_i * delta)
.collect();
let e = self.e + params.h * delta;
Node {
tau: right_branch,
a,
b,
ds,
dh,
e,
}
}
// tau_bytes_len || tau || a || b || len_ds || ds || len_dh || dh || e
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let g1_elements = 1;
let g2_elements = self.ds.len() + self.dh.len() + 2;
let tau_bytes = self.tau.to_bytes();
// the extra 12 comes from the triple u32 we use for encoding lengths of tau, ds and dh
let mut bytes =
Vec::with_capacity(tau_bytes.len() + g1_elements * 48 + g2_elements * 96 + 12);
bytes.extend_from_slice(&((tau_bytes.len() as u32).to_be_bytes()));
bytes.extend_from_slice(&tau_bytes);
bytes.extend_from_slice(self.a.to_bytes().as_ref());
bytes.extend_from_slice(self.b.to_bytes().as_ref());
bytes.extend_from_slice(&((self.ds.len() as u32).to_be_bytes()));
for d_i in &self.ds {
bytes.extend_from_slice(d_i.to_bytes().as_ref());
}
bytes.extend_from_slice(&((self.dh.len() as u32).to_be_bytes()));
for dh_i in &self.dh {
bytes.extend_from_slice(dh_i.to_bytes().as_ref());
}
bytes.extend_from_slice(self.e.to_bytes().as_ref());
bytes
}
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, DkgError> {
// at the very least we require bytes for:
// - tau_len ( 4 )
// - tau ( could be 0 for root node )
// - a ( 48 )
// - b ( 96 )
// - length indication of ds ( 4 )
// - length indication of dh ( 4 )
// - e ( 96 )
if bytes.len() < 4 + 48 + 96 + 4 + 4 + 96 {
return Err(DkgError::new_deserialization_failure(
"Node",
"insufficient number of bytes provided",
));
}
let tau_len = u32::from_be_bytes((&bytes[..4]).try_into().unwrap()) as usize;
let mut i = 4;
let tau = Tau::try_from_bytes(&bytes[i..i + tau_len])?;
i += tau_len;
// perform another length check to account for bytes consumed by tau
if bytes[i..].len() < 48 + 96 + 4 + 4 + 96 {
return Err(DkgError::new_deserialization_failure(
"Node",
"insufficient number of bytes provided",
));
}
let a = deserialize_g1(&bytes[i..i + 48]).ok_or_else(|| {
DkgError::new_deserialization_failure("Node.a", "invalid curve point")
})?;
i += 48;
let b = deserialize_g2(&bytes[i..i + 96]).ok_or_else(|| {
DkgError::new_deserialization_failure("Node.b", "invalid curve point")
})?;
i += 96;
let ds_len = u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap()) as usize;
i += 4;
if bytes[i..].len() < ds_len * 96 + 4 {
return Err(DkgError::new_deserialization_failure(
"Node",
"insufficient number of bytes provided (ds)",
));
}
let mut ds = Vec::with_capacity(ds_len);
for j in 0..ds_len {
let d_i = deserialize_g2(&bytes[i..i + 96]).ok_or_else(|| {
DkgError::new_deserialization_failure(
format!("Node.ds_{}", j),
"invalid curve point",
)
})?;
ds.push(d_i);
i += 96;
}
let dh_len = u32::from_be_bytes((&bytes[i..i + 4]).try_into().unwrap()) as usize;
i += 4;
if bytes[i..].len() != (dh_len + 1) * 96 {
return Err(DkgError::new_deserialization_failure(
"Node",
"insufficient number of bytes provided (dh)",
));
}
let mut dh = Vec::with_capacity(dh_len);
for j in 0..dh_len {
let dh_i = deserialize_g2(&bytes[i..i + 96]).ok_or_else(|| {
DkgError::new_deserialization_failure(
format!("Node.dh_{}", j),
"invalid curve point",
)
})?;
dh.push(dh_i);
i += 96;
}
let e = deserialize_g2(&bytes[i..]).ok_or_else(|| {
DkgError::new_deserialization_failure("Node.h", "invalid curve point")
})?;
Ok(Node {
tau,
a,
b,
ds,
dh,
e,
})
}
}
// produces public key and a decryption key for the root of the tree
pub fn keygen(params: &Params, mut rng: impl RngCore) -> (DecryptionKey, PublicKeyWithProof) {
let g1 = G1Projective::generator();
let g2 = G2Projective::generator();
let mut x = Scalar::random(&mut rng);
let y = g1 * x;
let proof = ProofOfDiscreteLog::construct(&mut rng, &y, &x);
let mut rho = Scalar::random(&mut rng);
let a = g1 * rho;
let b = g2 * x + params.f0 * rho;
let ds = params.fs.iter().map(|f_i| f_i * rho).collect();
let dh = params.fh.iter().map(|fh_i| fh_i * rho).collect();
let e = params.h * rho;
let dk = DecryptionKey::new_root(Node::new_root(a, b, ds, dh, e));
let public_key = PublicKey(y);
let key_with_proof = PublicKeyWithProof {
key: public_key,
proof,
};
x.zeroize();
rho.zeroize();
(dk, key_with_proof)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PublicKey(pub(crate) G1Projective);
impl PublicKey {
pub fn verify(&self, proof: &ProofOfDiscreteLog) -> bool {
proof.verify(&self.0)
}
}
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct PublicKeyWithProof {
pub(crate) key: PublicKey,
pub(crate) proof: ProofOfDiscreteLog,
}
impl PublicKeyWithProof {
pub fn verify(&self) -> bool {
self.key.verify(&self.proof)
}
pub fn public_key(&self) -> &PublicKey {
&self.key
}
pub fn to_bytes(&self) -> Vec<u8> {
// we have 2 G1 elements and 1 Scalar
let mut bytes = Vec::with_capacity(2 * 48 + 32);
bytes.extend_from_slice(self.key.0.to_bytes().as_ref());
bytes.extend_from_slice(self.proof.rand_commitment.to_bytes().as_ref());
bytes.extend_from_slice(self.proof.response.to_bytes().as_ref());
bytes
}
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, DkgError> {
if bytes.len() != 2 * 48 + 32 {
return Err(DkgError::new_deserialization_failure(
"PublicKeyWithProof",
"provided bytes had invalid length",
));
}
let y_bytes = &bytes[..48];
let commitment_bytes = &bytes[48..96];
let response_bytes = &bytes[96..];
let y = deserialize_g1(y_bytes).ok_or_else(|| {
DkgError::new_deserialization_failure("PublicKeyWithProof.key.0", "invalid curve point")
})?;
let rand_commitment = deserialize_g1(commitment_bytes).ok_or_else(|| {
DkgError::new_deserialization_failure(
"PublicKeyWithProof.proof.rand_commitment",
"invalid curve point",
)
})?;
let response = deserialize_scalar(response_bytes).ok_or_else(|| {
DkgError::new_deserialization_failure(
"PublicKeyWithProof.proof.response",
"invalid scalar",
)
})?;
Ok(PublicKeyWithProof {
key: PublicKey(y),
proof: ProofOfDiscreteLog {
rand_commitment,
response,
},
})
}
}
#[derive(Debug, Zeroize)]
#[zeroize(drop)]
#[cfg_attr(test, derive(PartialEq))]
pub struct DecryptionKey {
// note that the nodes are ordered from "right" to "left"
pub(crate) nodes: Vec<Node>,
}
impl DecryptionKey {
fn new_root(root_node: Node) -> Self {
DecryptionKey {
nodes: vec![root_node],
}
}
fn current(&self) -> Result<&Node, DkgError> {
// we must have at least a single node, otherwise we have a malformed key
self.nodes.last().ok_or(DkgError::MalformedDecryptionKey)
}
pub fn current_epoch(&self, params: &Params) -> Result<Option<Epoch>, DkgError> {
let current_node = self.current()?;
if current_node.is_root() {
Ok(None)
} else {
Epoch::try_from_tau(&current_node.tau, params).map(Option::Some)
}
}
pub(crate) fn try_get_compatible_node(&self, epoch: Epoch) -> Result<&Node, DkgError> {
let tau = epoch.as_tau();
self.nodes
.iter()
.rev()
.find(|node| node.tau.is_parent_of(&tau))
.ok_or(DkgError::ExpiredKey)
}
pub fn try_update_to_next_epoch(
&mut self,
params: &Params,
mut rng: impl RngCore,
) -> Result<(), DkgError> {
if self.nodes.is_empty() {
return Err(DkgError::MalformedDecryptionKey);
}
let mut target_epoch = Epoch::new(0);
if self.nodes.len() == 1 && self.nodes[0].is_root() {
return self.try_update_to(target_epoch, params, &mut rng);
}
// unwrap is fine as we have asserted self.nodes is not empty
self.nodes.pop().unwrap();
if let Some(tail) = self.nodes.last() {
target_epoch = tail.tau.lowest_valid_epoch_child(params)?;
} else {
// essentially our key consisted of only a single node and it wasn't a root,
// so either it was malformed or we somehow reached the final epoch and wanted to update
// beyond that. Either way, update to l + 1 is impossible
return Err(DkgError::MalformedDecryptionKey);
}
self.try_update_to(target_epoch, params, &mut rng)
}
/// Attempts to update `self` to the provided `epoch`. If the update is not possible,
/// because the target was in the past or the key is malformed, an error is returned.
///
/// Note that this method mutates the key in place and if the original key was malformed,
/// there are no guarantees about its internal state post-call.
pub fn try_update_to(
&mut self,
target_epoch: Epoch,
params: &Params,
mut rng: impl RngCore,
) -> Result<(), DkgError> {
if self.nodes.is_empty() {
// somehow we have an empty decryption key
return Err(DkgError::MalformedDecryptionKey);
}
// makes it easier to work with since we will be generating non-leaf nodes
let target_tau = target_epoch.as_tau();
let current_tau = &self.current()?.tau;
if current_tau == &target_tau {
// our key is already updated to the target
return Ok(());
}
if current_tau > &target_tau {
// we cannot derive keys for past epochs
return Err(DkgError::TargetEpochUpdateInThePast);
}
// drop the nodes that are no longer required and get the most direct parent for the target epoch available
let mut parent = loop {
// if pop() fails the key is malformed since we checked that the target_epoch > current_epoch,
// hence the update should have been possible
let tail = self.nodes.pop().ok_or(DkgError::MalformedDecryptionKey)?;
if tail.tau.is_parent_of(&target_tau) {
break tail;
}
};
// essentially the case of updating epoch n to n + 1, where n is even;
// in that case the last two nodes are [..., epoch_{n+1}, epoch_n]
// so we just have to reblind the n+1 node and we're done
if parent.tau == target_tau {
parent.reblind(params, &mut rng);
self.nodes.push(parent);
return Ok(());
}
// accumulators, note that the previous elements have already been included by the parent,
// i.e. for example for parent at height l <= n, b = g2^x * f0^rho * d1^{tau_1} * ... * dl^{tau_l}
// new_b_accumulator = b * d1^{tau_1} * d2^{tau_2} * ... * dn^{tau_n}
// new_f_accumulator = f0 * f1^{tau_1} * f2^{tau_2} * ... * fn^{tau_n} (up to lambda_t)
let mut new_b_accumulator = parent.b;
let mut new_f_accumulator = parent.tau.evaluate_partial_f(params);
let parent_height = parent.tau.height();
// path from the parent to the child
for (n, bit) in target_tau
.0
.iter()
.by_vals()
.skip(parent.tau.height())
.enumerate()
{
// ith bit of the [child] epoch
// note that n represents height difference between parent and the current bit
let i = n + parent_height;
// if the bit is NOT set, push the right '1' subtree (for future keys)
// so for example if given parent with some `PREFIX` tau and target_epoch being `PREFIX || 010`,
// in the first loop iteration we're going to look at bit `0` and
// derive child node `PREFIX || 1` so that in the future we could derive keys for all other epochs starting with `PREFIX || 1`
// in the next loop iteration we're going to look at bit `1` and simply update the accumulators,
// as we don't need to generate any "left" nodes as all of them would have constructed epochs that are already in the past
// finally, in the last iteration, we look at the bit `0` and derive node `PREFIX || 011`,
// i.e. the one that FOLLOWS the target node.
if !bit {
let direct_parent = target_tau.try_get_parent_at_height(i)?;
self.nodes
.push(parent.derive_right_nonfinal_child_of_with_partials(
params,
direct_parent,
&new_b_accumulator,
&new_f_accumulator,
&mut rng,
));
} else {
// only update the accumulators when the bit is set, as d^0 == identity, so there's
// no point in doing anything else;
// note that we don't have to generate any new nodes when going into the right branch
// of the tree as everything on the left would have been in the past, so we don't care about them
new_b_accumulator += parent.ds[n]; // add d0
new_f_accumulator += params.fs[i]; // f_i
}
}
self.nodes.push(parent.derive_target_child_with_partials(
params,
target_epoch.as_tau(),
&new_b_accumulator,
&new_f_accumulator,
&mut rng,
));
Ok(())
}
pub fn to_bytes(&self) -> Vec<u8> {
let num_nodes = self.nodes.len() as u32;
// unfortunately we're not going to know the expected capacity
let mut bytes = Vec::new();
bytes.extend_from_slice(&num_nodes.to_be_bytes());
for node in &self.nodes {
let mut node_bytes = node.to_bytes();
bytes.extend_from_slice(&((node_bytes.len() as u32).to_be_bytes()));
bytes.append(&mut node_bytes)
}
bytes
}
pub fn try_from_bytes(b: &[u8]) -> Result<Self, DkgError> {
// we have to be able to read the length of nodes
if b.len() < 4 {
return Err(DkgError::new_deserialization_failure(
"DecryptionKey",
"insufficient number of bytes provided",
));
}
let nodes_len = u32::from_be_bytes([b[0], b[1], b[2], b[3]]) as usize;
let mut nodes = Vec::with_capacity(nodes_len);
let mut i = 4;
for _ in 0..nodes_len {
// check if we can actually read the length...
if b[i..].len() < 4 {
return Err(DkgError::new_deserialization_failure(
"DecryptionKey.Node",
"insufficient number of bytes provided for BTE Node recovery",
));
}
let node_bytes = u32::from_be_bytes([b[i], b[i + 1], b[i + 2], b[i + 3]]) as usize;
if b[i + 4..].len() < node_bytes {
return Err(DkgError::new_deserialization_failure(
"DecryptionKey.Node",
"insufficient number of bytes provided for BTE Node recovery",
));
}
i += 4;
let node = Node::try_from_bytes(&b[i..i + node_bytes])?;
nodes.push(node);
i += node_bytes;
}
Ok(DecryptionKey { nodes })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::bte::setup;
use bitvec::bitvec;
use bitvec::order::Msb0;
use rand_core::SeedableRng;
#[test]
fn basic_coverage_nodes() {
// it's some basic test I've been performing when writing the update function, but figured
// might as well put it into a unit test. note that it doesn't check the entire structure,
// but just the few last nodes of low height
let params = setup();
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (mut dk, _) = keygen(&params, &mut rng);
let root_node_copy = dk.nodes.clone();
// this is a root node
assert_eq!(dk.nodes.len(), 1);
assert!(dk.nodes[0].is_root());
// we have to have a node for right branch on each height (1, 01, 001, ... etc)
// plus an additional one for the two left-most leaves (epochs "0" and "1")
dk.try_update_to(Epoch::new(0), &params, &mut rng).unwrap();
assert_eq!(dk.nodes.len(), 33);
let expected_last = Tau::new(0);
// (and yes, I had to look up those names in a thesaurus)
let expected_penultimate = Tau::new(1);
// note that this value is 31bit long
let expected_antepenultimate = Tau(bitvec![u32, Msb0;
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 1
]);
let mut nodes_iter = dk.nodes.iter().rev();
assert_eq!(expected_last, nodes_iter.next().unwrap().tau);
assert_eq!(expected_penultimate, nodes_iter.next().unwrap().tau);
assert_eq!(expected_antepenultimate, nodes_iter.next().unwrap().tau);
let mut epoch_zero_nodes = dk.nodes.clone();
// nodes for epoch1 should be identical for those for epoch0 minus the 00..00 leaf
dk.try_update_to(Epoch::new(1), &params, &mut rng).unwrap();
assert_eq!(dk.nodes.len(), 32);
epoch_zero_nodes.pop().unwrap();
assert_eq!(
epoch_zero_nodes
.iter()
.map(|node| node.tau.clone())
.collect::<Vec<_>>(),
dk.nodes
.iter()
.map(|node| node.tau.clone())
.collect::<Vec<_>>()
);
dk.try_update_to(Epoch::new(2), &params, &mut rng).unwrap();
dk.try_update_to(Epoch::new(3), &params, &mut rng).unwrap();
dk.try_update_to(Epoch::new(4), &params, &mut rng).unwrap();
let expected_last = Tau::new(4);
let expected_penultimate = Tau::new(5);
let expected_antepenultimate = Tau(bitvec![u32, Msb0;
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1
]);
let expected_preantepenultimate = Tau(bitvec![u32, Msb0;
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
]);
assert_eq!(dk.nodes.len(), 32);
let mut nodes_iter = dk.nodes.iter().rev();
assert_eq!(expected_last, nodes_iter.next().unwrap().tau);
assert_eq!(expected_penultimate, nodes_iter.next().unwrap().tau);
assert_eq!(expected_antepenultimate, nodes_iter.next().unwrap().tau);
assert_eq!(expected_preantepenultimate, nodes_iter.next().unwrap().tau);
// the result should be the same of regardless if we update incrementally or go to the target immediately
let mut new_root = DecryptionKey {
nodes: root_node_copy,
};
new_root
.try_update_to(Epoch::new(4), &params, &mut rng)
.unwrap();
assert_eq!(
dk.nodes
.iter()
.map(|node| node.tau.clone())
.collect::<Vec<_>>(),
new_root
.nodes
.iter()
.map(|node| node.tau.clone())
.collect::<Vec<_>>()
);
// getting expected nodes for those epochs is non-trivial for test purposes, but the last node
// should ALWAYS be equal to the target epoch
dk.try_update_to(Epoch::new(42), &params, &mut rng).unwrap();
assert_eq!(dk.nodes.last().unwrap().tau, Tau::new(42));
dk.try_update_to(Epoch::new(123456), &params, &mut rng)
.unwrap();
assert_eq!(dk.nodes.last().unwrap().tau, Tau::new(123456));
dk.try_update_to(Epoch::new(3292547435), &params, &mut rng)
.unwrap();
assert_eq!(dk.nodes.last().unwrap().tau, Tau::new(3292547435));
// trying to go to past epochs fails
assert!(dk
.try_update_to(Epoch::new(531), &params, &mut rng)
.is_err())
}
#[test]
fn updating_to_next_epoch() {
let params = setup();
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (mut dk, _) = keygen(&params, &mut rng);
// for root node current epoch is `None`
assert_eq!(None, dk.current_epoch(&params).unwrap());
// for root node it should result in epoch 0
dk.try_update_to_next_epoch(&params, &mut rng).unwrap();
assert_eq!(Some(Epoch::new(0)), dk.current_epoch(&params).unwrap());
dk.try_update_to_next_epoch(&params, &mut rng).unwrap();
assert_eq!(Some(Epoch::new(1)), dk.current_epoch(&params).unwrap());
dk.try_update_to_next_epoch(&params, &mut rng).unwrap();
assert_eq!(Some(Epoch::new(2)), dk.current_epoch(&params).unwrap());
// if we start from some non-root epoch, it should result in l + 1
dk.try_update_to(Epoch::new(42), &params, &mut rng).unwrap();
dk.try_update_to_next_epoch(&params, &mut rng).unwrap();
assert_eq!(Some(Epoch::new(43)), dk.current_epoch(&params).unwrap());
dk.try_update_to(Epoch::new(12345), &params, &mut rng)
.unwrap();
dk.try_update_to_next_epoch(&params, &mut rng).unwrap();
assert_eq!(Some(Epoch::new(12346)), dk.current_epoch(&params).unwrap());
dk.try_update_to(Epoch::new(3292547435), &params, &mut rng)
.unwrap();
dk.try_update_to_next_epoch(&params, &mut rng).unwrap();
assert_eq!(
Some(Epoch::new(3292547436)),
dk.current_epoch(&params).unwrap()
);
}
#[test]
fn public_key_with_proof_roundtrip() {
let params = setup();
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (_, pk) = keygen(&params, &mut rng);
let bytes = pk.to_bytes();
let recovered = PublicKeyWithProof::try_from_bytes(&bytes).unwrap();
assert_eq!(pk, recovered)
}
#[test]
fn bte_node_roundtrip() {
let params = setup();
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (mut dk, _) = keygen(&params, &mut rng);
let root_node = dk.nodes[0].clone();
let bytes = root_node.to_bytes();
let recovered = Node::try_from_bytes(&bytes).unwrap();
assert_eq!(root_node, recovered);
dk.try_update_to(Epoch::new(3292547435), &params, &mut rng)
.unwrap();
for node in &dk.nodes {
let bytes = node.to_bytes();
let recovered = Node::try_from_bytes(&bytes).unwrap();
assert_eq!(node, &recovered);
}
}
#[test]
fn decryption_key_node_roundtrip() {
let params = setup();
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let (mut dk, _) = keygen(&params, &mut rng);
let bytes = dk.to_bytes();
let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap();
assert_eq!(dk, recovered);
dk.try_update_to(Epoch::new(0), &params, &mut rng).unwrap();
let bytes = dk.to_bytes();
let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap();
assert_eq!(dk, recovered);
dk.try_update_to(Epoch::new(1), &params, &mut rng).unwrap();
let bytes = dk.to_bytes();
let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap();
assert_eq!(dk, recovered);
dk.try_update_to(Epoch::new(42), &params, &mut rng).unwrap();
let bytes = dk.to_bytes();
let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap();
assert_eq!(dk, recovered);
dk.try_update_to(Epoch::new(3292547435), &params, &mut rng)
.unwrap();
let bytes = dk.to_bytes();
let recovered = DecryptionKey::try_from_bytes(&bytes).unwrap();
assert_eq!(dk, recovered);
}
}
+463
View File
@@ -0,0 +1,463 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::DkgError;
use crate::utils::{hash_g2, RandomOracleBuilder};
use crate::{Chunk, Share};
use bitvec::field::BitField;
use bitvec::order::Msb0;
use bitvec::vec::BitVec;
use bitvec::view::BitView;
use bls12_381::{G1Affine, G1Projective, G2Affine, G2Prepared, G2Projective, Gt};
use group::Curve;
use lazy_static::lazy_static;
use zeroize::Zeroize;
pub mod encryption;
pub mod keys;
pub mod proof_chunking;
pub mod proof_discrete_log;
pub mod proof_sharing;
pub use encryption::{decrypt_share, encrypt_shares, Ciphertexts};
pub use keys::{keygen, DecryptionKey, PublicKey, PublicKeyWithProof};
lazy_static! {
pub(crate) static ref PAIRING_BASE: Gt =
bls12_381::pairing(&G1Affine::generator(), &G2Affine::generator());
pub(crate) static ref G2_GENERATOR_PREPARED: G2Prepared =
G2Prepared::from(G2Affine::generator());
pub(crate) static ref DEFAULT_BSGS_TABLE: encryption::BabyStepGiantStepLookup =
encryption::BabyStepGiantStepLookup::default();
}
// Domain tries to follow guidelines specified by:
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1
const SETUP_DOMAIN: &[u8] = b"NYM_COCONUT_NIDKG_V01_CS01_WITH_BLS12381G2_XMD:SHA-256_SSWU_RO_SETUP";
// this particular domain is not for curve hashing, but might as well also follow the same naming pattern
const TREE_TAU_EXTENSION_DOMAIN: &[u8] = b"NYM_COCONUT_NIDKG_V01_CS01_SHA-256_TREE_EXTENSION";
const MAX_EPOCHS_EXP: usize = 32;
const HASH_SECURITY_PARAM: usize = 256;
// note: CHUNK_BYTES * NUM_CHUNKS must equal to SCALAR_SIZE
pub const CHUNK_BYTES: usize = 2;
pub const NUM_CHUNKS: usize = 16;
pub const SCALAR_SIZE: usize = 32;
/// In paper B; number of distinct chunks
pub const CHUNK_SIZE: usize = 1 << (CHUNK_BYTES << 3);
pub(crate) type EpochStore = u32;
#[derive(Clone, Debug, PartialEq, PartialOrd)]
// None empty bitvec implies this is a root node
pub(crate) struct Tau(BitVec<EpochStore, Msb0>);
impl Tau {
pub fn new_root() -> Self {
Tau(BitVec::new())
}
// TODO: perhaps this should be explicitly moved to some test module
#[cfg(test)]
pub(crate) fn new(epoch: EpochStore) -> Self {
Tau(epoch.view_bits().to_bitvec())
}
#[allow(unused)]
pub fn left_child(&self) -> Self {
let mut child = self.0.clone();
child.push(false);
Tau(child)
}
pub fn right_child(&self) -> Self {
let mut child = self.0.clone();
child.push(true);
Tau(child)
}
pub fn is_leaf(&self, params: &Params) -> bool {
self.height() == params.lambda_t
}
pub fn try_get_parent_at_height(&self, height: usize) -> Result<Self, DkgError> {
if height > self.0.len() {
return Err(DkgError::NotAValidParent);
}
Ok(Tau(self.0[..height].to_bitvec()))
}
// essentially is this tau prefixing the other
pub fn is_parent_of(&self, other: &Tau) -> bool {
if self.0.len() > other.0.len() {
return false;
}
for (i, b) in self.0.iter().enumerate() {
if b != other.0[i] {
return false;
}
}
true
}
pub fn lowest_valid_epoch_child(&self, params: &Params) -> Result<Epoch, DkgError> {
if self.0.len() > params.lambda_t {
// this node is already BELOW a valid leaf-epoch node. it can only happen
// if either some invariant was broken or additional data was pushed to `tau`
// in order compute some intermediate results, but in that case this method should have
// never been called anyway. tl;dr: if this is called, the underlying key is malformed
return Err(DkgError::NotAValidParent);
}
let mut child = self.0.clone();
for _ in 0..(params.lambda_t - self.0.len()) {
child.push(false)
}
// the unwrap here is fine as we ensure we have exactly `params.tree_height` bits here
// (we could just propagate the error instead of unwraping and putting it behind an `Ok` anyway
// but I'd prefer to just blow up since this would be a serious error
Ok(Epoch::try_from_tau(&Tau(child), params).unwrap())
}
pub fn height(&self) -> usize {
self.0.len()
}
fn extend(
&self,
rr: &[G1Projective; NUM_CHUNKS],
ss: &[G1Projective; NUM_CHUNKS],
cc: &[[G1Projective; NUM_CHUNKS]],
) -> Self {
let mut random_oracle_builder = RandomOracleBuilder::new(TREE_TAU_EXTENSION_DOMAIN);
random_oracle_builder.update_with_g1_elements(rr.iter());
random_oracle_builder.update_with_g1_elements(ss.iter());
for ciphertext_chunks in cc {
random_oracle_builder.update_with_g1_elements(ciphertext_chunks.iter());
}
let tau_mem = self.0.as_raw_slice();
assert_eq!(tau_mem.len(), 1, "tau length invariant was broken");
random_oracle_builder.update(&tau_mem[0].to_be_bytes());
let oracle_output = random_oracle_builder.finalize();
debug_assert_eq!(oracle_output.len() * 8, HASH_SECURITY_PARAM);
let mut extended_tau = self.clone();
for byte in oracle_output {
extended_tau
.0
.extend_from_bitslice(byte.view_bits::<Msb0>())
}
extended_tau
}
// considers all lambda_t + lambda_h bits
fn evaluate_f(&self, params: &Params) -> G2Projective {
self.0
.iter()
.by_vals()
.zip(params.fs.iter().chain(params.fh.iter()))
.filter(|(i, _)| *i)
.map(|(_, f_i)| f_i)
.fold(params.f0, |acc, f_i| acc + f_i)
}
// only considers up to lambda_t bits
fn evaluate_partial_f(&self, params: &Params) -> G2Projective {
self.0
.iter()
.by_vals()
.zip(params.fs.iter())
.filter(|(i, _)| *i)
.map(|(_, f_i)| f_i)
.fold(params.f0, |acc, f_i| acc + f_i)
}
pub(crate) fn to_bytes(&self) -> Vec<u8> {
let len_bytes = (self.0.len() as u32).to_be_bytes();
len_bytes
.into_iter()
.chain(self.0.chunks(8).map(BitField::load_be))
.collect()
}
pub(crate) fn try_from_bytes(b: &[u8]) -> Result<Self, DkgError> {
if b.len() < 4 {
return Err(DkgError::new_deserialization_failure(
"Tau",
"insufficient number of bytes provided",
));
}
let tau_len = u32::from_be_bytes([b[0], b[1], b[2], b[3]]) as usize;
// maximum theoretical length
if tau_len > MAX_EPOCHS_EXP + HASH_SECURITY_PARAM {
return Err(DkgError::new_deserialization_failure(
"Tau",
format!(
"malformed length {} is greater than maximum {}",
tau_len,
MAX_EPOCHS_EXP + HASH_SECURITY_PARAM
),
));
}
if tau_len == 0 {
if b.len() != 4 {
Err(DkgError::new_deserialization_failure(
"Tau",
"malformed bytes",
))
} else {
Ok(Tau::new_root())
}
} else if b.len() == 4 {
Err(DkgError::new_deserialization_failure(
"Tau",
"insufficient number of bytes provided",
))
} else {
let mut inner = BitVec::repeat(false, tau_len);
for (slot, &byte) in inner.chunks_mut(8).zip(b[4..].iter()) {
slot.store_be(byte);
}
Ok(Tau(inner))
}
}
}
impl Zeroize for Tau {
fn zeroize(&mut self) {
for v in self.0.as_raw_mut_slice() {
v.zeroize()
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub struct Epoch(EpochStore);
impl Epoch {
pub fn new(value: EpochStore) -> Self {
Epoch(value)
}
pub(crate) fn as_tau(&self) -> Tau {
(*self).into()
}
pub(crate) fn as_extended_tau(
&self,
rr: &[G1Projective; NUM_CHUNKS],
ss: &[G1Projective; NUM_CHUNKS],
cc: &[[G1Projective; NUM_CHUNKS]],
) -> Tau {
self.as_tau().extend(rr, ss, cc)
}
pub(crate) fn try_from_tau(tau: &Tau, params: &Params) -> Result<Self, DkgError> {
if !tau.is_leaf(params) {
Err(DkgError::MalformedEpoch)
} else {
Ok(Epoch(tau.0.load_be()))
}
}
}
impl From<Epoch> for Tau {
fn from(epoch: Epoch) -> Self {
Tau(epoch.0.view_bits().to_bitvec())
}
}
impl From<EpochStore> for Epoch {
fn from(epoch: EpochStore) -> Self {
Epoch(epoch)
}
}
pub struct Params {
/// Maximum size of an epoch, in bits.
pub lambda_t: usize,
/// Security parameter of our $H_{\Lamda_H}$ hash function
pub lambda_h: usize,
// keeping f0 separate from the rest of the curve points makes it easier to work with tau
f0: G2Projective,
fs: Vec<G2Projective>, // f_1, f_2, .... f_{lambda_t} in the paper
fh: Vec<G2Projective>, // f_{lambda_t+1}, f_{lambda_t+1}, .... f_{lambda_t+lambda_h} in the paper
h: G2Projective,
/// Precomputed `h` used for the miller loop
_h_prepared: G2Prepared,
}
pub fn setup() -> Params {
let f0 = hash_g2(b"f0", SETUP_DOMAIN);
let fs = (1..=MAX_EPOCHS_EXP)
.map(|i| hash_g2(format!("f{}", i), SETUP_DOMAIN))
.collect();
let fh = (0..HASH_SECURITY_PARAM)
.map(|i| hash_g2(format!("fh{}", i), SETUP_DOMAIN))
.collect();
let h = hash_g2(b"h", SETUP_DOMAIN);
Params {
lambda_t: MAX_EPOCHS_EXP,
lambda_h: HASH_SECURITY_PARAM,
f0,
fs,
fh,
h,
_h_prepared: G2Prepared::from(h.to_affine()),
}
}
#[cfg(test)]
mod tests {
use super::*;
use bitvec::bitvec;
use bitvec::order::Msb0;
#[test]
fn creating_tau_from_epoch() {
assert!(Tau::new_root().0.is_empty());
let zero = Tau::new(0);
assert!(zero.0.iter().by_vals().all(|b| !b));
let one = Tau::new(1);
let mut iter = one.0.iter().by_vals();
// first 31 bits are 0, the last one is 1
for _ in 0..31 {
assert!(!iter.next().unwrap())
}
assert!(iter.next().unwrap());
// 101010 in binary
let forty_two = Tau::new(42);
// first 26 bits are not set
let mut iter = forty_two.0.iter().by_vals();
for _ in 0..26 {
assert!(!iter.next().unwrap())
}
assert!(iter.next().unwrap());
assert!(!iter.next().unwrap());
assert!(iter.next().unwrap());
assert!(!iter.next().unwrap());
assert!(iter.next().unwrap());
assert!(!iter.next().unwrap());
// value that requires an actual u32 (i.e. takes 4 bytes to represent)
// 11000100_01000000_01001001_01101011 in binary
let big_val = Tau::new(3292547435);
let expected = bitvec![u32, Msb0;
1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1,
0, 1, 1
];
assert_eq!(expected, big_val.0)
}
#[test]
fn getting_parent_at_height() {
let tau = Tau(bitvec![u32, Msb0; 1,0,1,1,0,0,1]);
let expected_0 = Tau(BitVec::new());
let expected_1 = Tau(bitvec![u32, Msb0; 1]);
let expected_5 = Tau(bitvec![u32, Msb0; 1,0,1,1,0]);
assert_eq!(expected_0, tau.try_get_parent_at_height(0).unwrap());
assert_eq!(expected_1, tau.try_get_parent_at_height(1).unwrap());
assert_eq!(expected_5, tau.try_get_parent_at_height(5).unwrap());
assert_eq!(tau, tau.try_get_parent_at_height(7).unwrap());
assert!(tau.try_get_parent_at_height(8).is_err())
}
#[test]
fn converting_tau_to_epoch() {
let params = setup();
let tau0: Tau = Epoch::new(0).into();
let tau1: Tau = Epoch::new(1).into();
let tau42: Tau = Epoch::new(42).into();
let tau_big: Tau = Epoch::new(3292547435).into();
assert_eq!(Epoch::new(0), Epoch::try_from_tau(&tau0, &params).unwrap());
assert_eq!(Epoch::new(1), Epoch::try_from_tau(&tau1, &params).unwrap());
assert_eq!(
Epoch::new(42),
Epoch::try_from_tau(&tau42, &params).unwrap()
);
assert_eq!(
Epoch::new(3292547435),
Epoch::try_from_tau(&tau_big, &params).unwrap()
);
assert!(Epoch::try_from_tau(&Tau(BitVec::new()), &params).is_err());
assert!(Epoch::try_from_tau(&Tau(bitvec![u32, Msb0; 1,0,1,1,0]), &params).is_err());
let _31bit_tau = Tau(bitvec![u32, Msb0;
1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1,
0, 1
]);
assert!(Epoch::try_from_tau(&_31bit_tau, &params).is_err());
let _33bit_tau = Tau(bitvec![u32, Msb0;
1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1,
0, 1, 1, 0
]);
assert!(Epoch::try_from_tau(&_33bit_tau, &params).is_err());
}
#[test]
fn tau_roundtrip() {
let good_taus = vec![
Tau::new_root(),
Tau::new(0),
Tau::new(1),
Tau::new(2),
Tau::new(42),
Tau::new(123456),
Tau::new(3292547435),
Tau::new(u32::MAX),
];
for tau in good_taus {
let bytes = tau.to_bytes();
let recovered = Tau::try_from_bytes(&bytes).unwrap();
assert_eq!(tau, recovered);
}
// more valid variants
let mut another_tau = Tau::new(u32::MAX);
another_tau.0.push(true);
another_tau.0.push(false);
another_tau.0.push(true);
let bytes = another_tau.to_bytes();
let recovered = Tau::try_from_bytes(&bytes).unwrap();
assert_eq!(another_tau, recovered);
// ensure there are no panics
let big_length_bytes = [255, 255, 255, 255, 42];
assert!(Tau::try_from_bytes(&big_length_bytes).is_err());
assert!(Tau::try_from_bytes(&[]).is_err());
assert!(Tau::try_from_bytes(&[1, 1, 1, 1]).is_err());
assert!(Tau::try_from_bytes(&[0, 0, 0, 1]).is_err());
assert!(Tau::try_from_bytes(&[1, 0, 0, 0]).is_err());
assert!(Tau::try_from_bytes(&[1, 0, 0]).is_err());
}
}

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