Compare commits

..

41 Commits

Author SHA1 Message Date
Mark Sinclair 0c3c13ae88 Change nym-connect window title to NymConnect 2022-08-24 15:48:26 +01:00
Jon Häggblad 8c8b7d71d0 validator-api: create node status cache with selection probabilies (#1547)
* validator-api: create node status cache with selection probabilies

Create a node status cache to complement the contract cache. Initially
we store the simulated active set selection probabilities.

* validator-api: add validator cache watch channel

* changelog: add note

* validator-api: clippy fixes

* validator-api: fix clippy

* validator-api: additional fields to inclusion probabilities response

* selection chance: revert back to 3 buckets

* selection chance: revert buckets again

* rustfmt

* validator-api: remove the old get_mixnode_inclusion_probability

* node-status-cache: return error when refreshing

* inclusion-simulator: cap on wall clock time

* node status cache: tidy
2022-08-24 14:29:21 +02:00
Jon Häggblad 3163c5f054 gateway-client: clippy::question-mark 2022-08-24 09:51:47 +02:00
Jon Häggblad 4a1794b2f1 nymcoconut: fix named-arguments-used-positionally 2022-08-24 09:51:47 +02:00
Jon Häggblad 1898b8ed96 validator-api: add bounds checks in compute_mixnode_reward_estimation (#1559) 2022-08-24 09:30:12 +02:00
Mark Sinclair a23471859d GitHub Actions: fix up artifacts on nym-cli-publish 2022-08-23 20:19:57 +01:00
Mark Sinclair 9d8c9edf22 Add GitHub Action to build nym-cli on all platforms 2022-08-23 19:27:03 +01:00
Pierre Dommerc 5ea7b24efc fix(nym-wallet): bonding, enhance error handling (#1543)
open a dialog for user feedback when error occurs
add some better error logging
2022-08-23 17:53:57 +02:00
Jędrzej Stuczyński a43a24faa8 Exposed direct queries to smart contract on NymdClient (#1558)
* Exposed direct queries to smart contract on NymdClient

* Updated changelog
2022-08-23 15:45:43 +01:00
Jędrzej Stuczyński 39ee215005 Storing delegations using block timestamp as opposed to block height (#1544)
* Storing delegations using block timestamp as opposed to block height

* Updated changelog
2022-08-23 09:42:34 +01:00
Mark Sinclair ef7961f58e Update nym-release-publish.yml 2022-08-19 18:32:28 +01:00
Mark Sinclair e628338b33 GitHub Actions: add manual dispatch and artifact upload 2022-08-19 18:23:16 +01:00
Pierre Dommerc 1bb137f87f Nym-connect - service provider persistant storage (#1540)
* feat(nym-connect): local storage service provider

* feat(nym-connect): local storage service provider

* feat(nym-connect): local storage service provider

* Add some extra height to the window to stop the scrollbar apearing

* Show the service description when selecting a Service Provider

* Bump version

* Update changelog

* fix(nym-connect): hotfix

* fix(nym-connect): hotfix

* fix(nym-connect): wrong disabled state for connection button

Co-authored-by: Mark Sinclair <mmsinclair@gmail.com>
2022-08-18 18:53:03 +02:00
Dave Hrycyszyn 773f9e5ead Moving helpful comment into docs comment 2022-08-18 10:45:42 +01:00
Pierre Dommerc 79f695f138 Wallet: new UI for pending delegation (#1499)
* refactor(wallet-delegation): new delegation pending ui

* refactor(wallet-delegation): remove pending delegation table

* refactor(wallet-delegation): new pending delegation ui

* refactor(wallet-delegation): remove logs

* refactor(wallet-delegation): better typing

* refactor(wallet-balance): feedback review
2022-08-18 11:20:32 +02:00
Pierre Dommerc 3aabbcf876 fix(nym-wallet): make get_operator_rewards request success optional (#1542) 2022-08-17 17:21:36 +02:00
Jędrzej Stuczyński d96b7408db Allowing optional fee for top-level NymdClient (#1541)
* Allowing optional fee for top-level NymdClient

* Updated changelog

* Fixed usages of said methods in validator-api
2022-08-17 13:27:53 +01:00
Jon Häggblad 728b0f4549 inclusion-probability: new crate for simulation active set inclusion (#1534) 2022-08-16 14:19:55 +02:00
tommy 0a37c81709 remove not needed prior state of denom 2022-08-16 12:05:38 +02:00
Jędrzej Stuczyński 957cbb45b0 Chore/linter updates (#1530)
* Removed needless return

* Added Eq derivation where applicable for types with PartialEq
2022-08-12 17:56:49 +01:00
Pierre Dommerc 8ec074cb1f fix(wallet): typo (#1527) 2022-08-12 16:02:27 +02:00
Jess ab5740087f Update CHANGELOG.md 2022-08-12 14:55:39 +01:00
Jess 6af59c303e Update CHANGELOG.md 2022-08-12 14:54:09 +01:00
Jess 27b384e034 Update CHANGELOG.md 2022-08-12 14:53:40 +01:00
Dave Hrycyszyn 7f5ce3ffeb Removing wallet entries from main changelog 2022-08-12 12:58:49 +01:00
Dave Hrycyszyn 89b6667c75 Splitting changelogs into their own files 2022-08-12 12:50:17 +01:00
Tommy Verrall a94a9aeaf5 Update README.md 2022-08-11 16:18:06 +01:00
tommy 6bc8b88a20 WIP - validator-api-tests
- Test base line
- Jest / Typescript / Node
2022-08-11 17:14:04 +02:00
Fouad 21f3991714 update sample env (#1523) 2022-08-11 12:21:19 +01:00
Tommy Verrall cd8eba988a Merge pull request #1522 from nymtech/release/wallet-v1.0.8
Release/wallet v1.0.8
2022-08-11 11:45:53 +01:00
Tommy Verrall d2b3841bbd Update Cargo.toml 2022-08-11 11:45:28 +01:00
Tommy Verrall de877fb337 Merge pull request #1521 from nymtech/release/nym-binaries-1.0.2
Release/nym binaries 1.0.2
2022-08-11 11:42:36 +01:00
Tommy Verrall d4c2b9060f Update changelog for v1.0.2 -reference 2022-08-11 10:02:34 +01:00
Bogdan-Ștefan Neacşu 41ac866729 Feature/validator api panics (#1520)
* Coconut unwraps

* Fail softly on epoch queries

* Update changelog

* Retry a few times before failing as before
2022-08-11 11:58:17 +03:00
Raphaël Walther a7afd2a1c7 Added audit workflow 2022-08-11 10:41:57 +02:00
Bogdan-Ștefan Neacșu c7fdcf0a79 Change for default mainnet and fix typo 2022-08-10 13:40:28 +03:00
tommy 8edc762df9 Update
- update the cargo toml
- amend the work flow file
- and the envs to mainnet (disclaimer add qa after)
2022-08-10 11:10:38 +02:00
tommy 7b15f350cd add service-provider vesioning to match other binaries 2022-08-10 10:14:07 +02:00
tommy 2b4917b8b1 fix messaging on init for nym-client 2022-08-10 10:11:00 +02:00
tommy 921e558660 Update versions on binaries 2022-08-09 11:56:54 +02:00
tommy d62638b8e2 update wallet version 2022-08-08 16:53:00 +02:00
81 changed files with 5673 additions and 785 deletions
@@ -166,8 +166,8 @@ jobs:
GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "ci-nightly"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMTECH_TEAM }}"
KEYBASE_NYM_CHANNEL: "test"
IS_SUCCESS: "${{ env.WORKFLOW_CONCLUSION == 'success' }}"
uses: docker://keybaseio/client:stable-node
with:
+50
View File
@@ -0,0 +1,50 @@
name: Publish Nym CLI binaries
on:
workflow_dispatch:
release:
types: [created]
env:
NETWORK: mainnet
jobs:
publish-nym-cli:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v3
- name: Check the release tag starts with `nym-cli-`
if: startsWith(github.ref, 'refs/tags/nym-cli-') == false && github.event_name != 'workflow_dispatch'
uses: actions/github-script@v3
with:
script: |
core.setFailed('Release tag did not start with nym-cli-...')
- name: Install Rust stable
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Build binary
run: make build-nym-cli
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: nym-cli-${{ matrix.platform }}
path: |
target/release/nym-cli*
retention-days: 30
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
if: github.event_name == 'release'
with:
files: |
target/release/nym-cli
+21 -1
View File
@@ -1,5 +1,7 @@
name: Publish Nym binaries
on:
workflow_dispatch:
release:
types: [created]
@@ -18,7 +20,7 @@ jobs:
- uses: actions/checkout@v3
- name: Check the release tag starts with `nym-binaries-`
if: startsWith(github.ref, 'refs/tags/nym-binaries-') == false
if: startsWith(github.ref, 'refs/tags/nym-binaries-') == false && github.event_name != 'workflow_dispatch'
uses: actions/github-script@v3
with:
script: |
@@ -35,8 +37,24 @@ jobs:
command: build
args: --workspace --release
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: my-artifact
path: |
target/release/nym-client
target/release/nym-gateway
target/release/nym-mixnode
target/release/nym-socks5-client
target/release/nym-validator-api
target/release/nym-network-requester
target/release/nym-network-statistics
target/release/nym-cli
retention-days: 30
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
if: github.event_name == 'release'
with:
files: |
target/release/nym-client
@@ -45,3 +63,5 @@ jobs:
target/release/nym-socks5-client
target/release/nym-validator-api
target/release/nym-network-requester
target/release/nym-network-statistics
target/release/nym-cli
+19 -254
View File
@@ -2,8 +2,22 @@
Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
## [Unreleased]
### Added
- validator-client: added `query_contract_smart` and `query_contract_raw` on `NymdClient` ([#1558])
### Changed
- validator-client: made `fee` argument optional for `execute` and `execute_multiple` ([#1541])
[#1541]: https://github.com/nymtech/nym/pull/1541
[#1558]: https://github.com/nymtech/nym/pull/1558
## [nym-binaries-1.0.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.2)
### Added
@@ -19,12 +33,14 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- validator-api: add Swagger to document the REST API ([#1249]).
- validator-api: Added new endpoints for coconut spending flow and communications with coconut & multisig contracts ([#1261])
- validator-api: add `uptime`, `estimated_operator_apy`, `estimated_delegators_apy` to `/mixnodes/detailed` endpoint ([#1393])
- validator-api: add node info cache storing simulated active set inclusion probabilities
- network-statistics: a new mixnet service that aggregates and exposes anonymized data about mixnet services ([#1328])
- mixnode: Added basic mixnode hardware reporting to the HTTP API ([#1308]).
- validator-api: endpoint, in coconut mode, for returning the validator-api cosmos address ([#1404]).
- validator-client: add `denom` argument and add simple test for querying an account balance
- gateway, validator-api: Checks for coconut credential double spending attempts, taking the coconut bandwidth contract as source of truth ([#1457])
- coconut-bandwidth-contract: Record the state of a coconut credential; create specific proposal for releasing funds ([#1457])
- inclusion-probability: add simulator for active set inclusion probability
### Fixed
@@ -53,6 +69,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- All binaries and cosmwasm blobs are configured at runtime now; binaries are configured using environment variables or .env files and contracts keep the configuration parameters in storage ([#1463])
- gateway, network-statistics: include gateway id in the sent statistical data ([#1478])
- network explorer: tweak how active set probability is shown ([#1503])
- validator-api: rewarder set update fails without panicking on possible nymd queries ([#1520])
[#1249]: https://github.com/nymtech/nym/pull/1249
@@ -81,90 +98,7 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[#1487]: https://github.com/nymtech/nym/pull/1487
[#1496]: https://github.com/nymtech/nym/pull/1496
[#1503]: https://github.com/nymtech/nym/pull/1503
## [nym-connect-v1.0.1](https://github.com/nymtech/nym/tree/nym-connect-v1.0.1) (2022-07-22)
### Added
- nym-connect: initial proof-of-concept of a UI around the socks5 client was added
- nym-connect: add ability to select network requester and gateway ([#1427])
- nym-connect: add ability to export gateway keys as JSON
- nym-connect: add auto updater
### Changed
- nym-connect: reuse config id instead of creating a new id on each connection
[#1427]: https://github.com/nymtech/nym/pull/1427
## [nym-wallet-v1.0.7](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.7) (2022-07-11)
- wallet: dark mode
- wallet: when simulating gas costs, an automatic adjustment is being used ([#1388]).
[#1388]: https://github.com/nymtech/nym/pull/1388
## [nym-contracts-v1.0.1](https://github.com/nymtech/nym/tree/nym-contracts-v1.0.1) (2022-06-22)
### Added
- mixnet-contract: Added ClaimOperatorReward and ClaimDelegatorReward messages ([#1292])
- mixnet-contract: Replace all naked `-` with `saturating_sub`.
- mixnet-contract: Added staking_supply field to ContractStateParams.
- mixnet-contract: Added a query to get MixnodeBond by identity key ([#1369]).
- mixnet-contract: Added a query to get GatewayBond by identity key ([#1369]).
- vesting-contract: Added ClaimOperatorReward and ClaimDelegatorReward messages ([#1292])
- vesting-contract: Added limit to the amount of tokens one can pledge ([#1331])
### Fixed
- mixnet-contract: `estimated_delegator_reward` calculation ([#1284])
- mixnet-contract: delegator and operator rewards use lambda and sigma instead of lambda_ticked and sigma_ticked ([#1284])
- 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: replaced integer division with fixed for performance calculations ([#1284])
- mixnet-contract: Under certain circumstances nodes could not be unbonded ([#1255](https://github.com/nymtech/nym/issues/1255)) ([#1258])
- mixnet-contract: Using correct staking supply when distributing rewards. ([#1373])
- vesting-contract: replaced `checked_sub` with `saturating_sub` to fix the underflow in `get_vesting_tokens` ([#1275])
[#1255]: https://github.com/nymtech/nym/pull/1255
[#1257]: https://github.com/nymtech/nym/pull/1257
[#1258]: https://github.com/nymtech/nym/pull/1258
[#1275]: https://github.com/nymtech/nym/pull/1275
[#1284]: https://github.com/nymtech/nym/pull/1284
[#1292]: https://github.com/nymtech/nym/pull/1292
[#1331]: https://github.com/nymtech/nym/pull/1331
[#1369]: https://github.com/nymtech/nym/pull/1369
[#1373]: https://github.com/nymtech/nym/pull/1373
## [nym-wallet-v1.0.6](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.6) (2022-06-21)
- wallet: undelegating now uses either the mixnet or vesting contract, or both, depending on how delegations were made
- wallet: redeeming and compounding now uses both the mixnet and vesting contract
- wallet: the wallet backend learned how to archive wallet files
- wallet: add ENABLE_QA_MODE environment variable to enable QA mode on built wallet
## [nym-wallet-v1.0.5](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.5) (2022-06-14)
- wallet: add simple CLI tool for decrypting and recovering the wallet file.
- wallet: added support for multiple accounts ([#1265])
- wallet: compound and claim reward endpoints for operators and delegators ([#1302])
- wallet: require password to switch accounts
- wallet: the wallet backend learned how to keep track of validator name, either hardcoded or by querying the status endpoint.
- wallet: new delegation and rewards UI
- wallet: show version in nav bar
- wallet: contract admin route put back
- wallet: staking_supply field to StateParams
- wallet: show transaction hash for redeeming or compounding rewards
[#1265]: https://github.com/nymtech/nym/pull/1265
[#1302]: https://github.com/nymtech/nym/pull/1302
## [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])
[#1520]: https://github.com/nymtech/nym/pull/1520
## [v1.0.1](https://github.com/nymtech/nym/tree/v1.0.1) (2022-05-04)
@@ -197,77 +131,10 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[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)
@@ -346,108 +213,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
- 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)
Generated
+26 -15
View File
@@ -2496,6 +2496,15 @@ dependencies = [
"syn",
]
[[package]]
name = "inclusion-probability"
version = "0.1.0"
dependencies = [
"log",
"rand 0.8.5",
"thiserror",
]
[[package]]
name = "indenter"
version = "0.3.3"
@@ -2743,9 +2752,9 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.16"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if 1.0.0",
]
@@ -3056,7 +3065,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.0.1"
version = "1.0.2"
dependencies = [
"clap 3.2.8",
"client-core",
@@ -3090,7 +3099,7 @@ dependencies = [
[[package]]
name = "nym-gateway"
version = "1.0.1"
version = "1.0.2"
dependencies = [
"anyhow",
"async-trait",
@@ -3138,7 +3147,7 @@ dependencies = [
[[package]]
name = "nym-mixnode"
version = "1.0.1"
version = "1.0.2"
dependencies = [
"anyhow",
"bs58",
@@ -3178,7 +3187,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.0.1"
version = "1.0.2"
dependencies = [
"async-trait",
"clap 2.34.0",
@@ -3206,7 +3215,7 @@ dependencies = [
[[package]]
name = "nym-network-statistics"
version = "0.1.0"
version = "1.0.2"
dependencies = [
"dirs",
"log",
@@ -3222,7 +3231,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.0.1"
version = "1.0.2"
dependencies = [
"clap 3.2.8",
"client-core",
@@ -3283,7 +3292,7 @@ dependencies = [
[[package]]
name = "nym-validator-api"
version = "1.0.1"
version = "1.0.2"
dependencies = [
"anyhow",
"async-trait",
@@ -3306,6 +3315,7 @@ dependencies = [
"gateway-client",
"getset",
"humantime-serde",
"inclusion-probability",
"log",
"mixnet-contract-common",
"multisig-contract-common",
@@ -3325,6 +3335,7 @@ dependencies = [
"serde",
"serde_json",
"sqlx",
"tap",
"task",
"thiserror",
"time 0.3.9",
@@ -4981,9 +4992,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.79"
version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
checksum = "38dd04e3c8279e75b31ef29dbdceebfe5ad89f4d0937213c53f7d49d01b3d5a7"
dependencies = [
"itoa 1.0.1",
"ryu",
@@ -5652,18 +5663,18 @@ checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]]
name = "thiserror"
version = "1.0.30"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.30"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21"
dependencies = [
"proc-macro2",
"quote",
+9 -7
View File
@@ -22,22 +22,23 @@ members = [
"clients/native",
"clients/native/websocket-requests",
"clients/socks5",
"common/bandwidth-claim-contract",
"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/execute",
"common/bandwidth-claim-contract",
"common/cosmwasm-smart-contracts/coconut-bandwidth-contract",
"common/cosmwasm-smart-contracts/contracts-common",
"common/cosmwasm-smart-contracts/mixnet-contract",
"common/cosmwasm-smart-contracts/multisig-contract",
"common/cosmwasm-smart-contracts/vesting-contract",
"common/credential-storage",
"common/credentials",
"common/crypto",
"common/crypto/dkg",
"common/execute",
"common/inclusion-probability",
"common/mixnode-common",
"common/network-defaults",
"common/nonexhaustive-delayqueue",
@@ -53,9 +54,9 @@ members = [
"common/nymsphinx/params",
"common/nymsphinx/types",
"common/pemstore",
"common/statistics",
"common/socks5/proxy-helpers",
"common/socks5/requests",
"common/statistics",
"common/task",
"common/topology",
"common/types",
@@ -76,6 +77,7 @@ default-members = [
"clients/socks5",
"gateway",
"service-providers/network-requester",
"service-providers/network-statistics",
"mixnode",
"validator-api",
"explorer-api",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.0.1"
version = "1.0.2"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -82,7 +82,7 @@ fn version_check(cfg: &Config) -> bool {
if binary_version == config_version {
true
} else {
warn!("The mixnode binary has different version than what is specified in config file! {} and {}", binary_version, config_version);
warn!("The native-client binary has different version than what is specified in config file! {} and {}", binary_version, config_version);
if is_minor_version_compatible(binary_version, config_version) {
info!("but they are still semver compatible. However, consider running the `upgrade` command");
true
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.0.1"
version = "1.0.2"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
@@ -162,12 +162,10 @@ impl PartiallyDelegated {
.expect("stream sender was somehow dropped without sending anything!");
if let Some(res) = receive_res {
if let Err(err) = res {
// the receiver got an error. most likely a network one.
return Err(err);
} else {
panic!("This should have NEVER happened - returned a stream before receiving notification")
}
let _res = res?;
panic!(
"This should have NEVER happened - returned a stream before receiving notification"
)
}
// this call failing is incredibly unlikely, but not impossible.
@@ -24,7 +24,7 @@ use mixnet_contract_common::{
PagedMixDelegationsResponse, PagedMixnodeResponse, PagedRewardedSetResponse, QueryMsg,
RewardedSetUpdateDetails,
};
use serde::Serialize;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::time::SystemTime;
use vesting_contract_common::ExecuteMsg as VestingExecuteMsg;
@@ -282,6 +282,30 @@ impl<C> NymdClient<C> {
self.simulated_gas_multiplier = multiplier;
}
pub async fn query_contract_smart<M, T>(
&self,
contract: &AccountId,
query_msg: &M,
) -> Result<T, NymdError>
where
C: CosmWasmClient + Sync,
M: ?Sized + Serialize + Sync,
for<'a> T: Deserialize<'a>,
{
self.client.query_contract_smart(contract, query_msg).await
}
pub async fn query_contract_raw(
&self,
contract: &AccountId,
query_data: Vec<u8>,
) -> Result<Vec<u8>, NymdError>
where
C: CosmWasmClient + Sync,
{
self.client.query_contract_raw(contract, query_data).await
}
pub fn wrap_contract_execute_message<M>(
&self,
contract_address: &AccountId,
@@ -893,7 +917,7 @@ impl<C> NymdClient<C> {
&self,
contract_address: &AccountId,
msg: &M,
fee: Fee,
fee: Option<Fee>,
memo: impl Into<String> + Send + 'static,
funds: Vec<Coin>,
) -> Result<ExecuteResult, NymdError>
@@ -901,6 +925,7 @@ impl<C> NymdClient<C> {
C: SigningCosmWasmClient + Sync,
M: ?Sized + Serialize + Sync,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
.execute(self.address(), contract_address, msg, fee, memo, funds)
.await
@@ -910,7 +935,7 @@ impl<C> NymdClient<C> {
&self,
contract_address: &AccountId,
msgs: I,
fee: Fee,
fee: Option<Fee>,
memo: impl Into<String> + Send + 'static,
) -> Result<ExecuteResult, NymdError>
where
@@ -918,6 +943,7 @@ impl<C> NymdClient<C> {
I: IntoIterator<Item = (M, Vec<Coin>)> + Send,
M: Serialize,
{
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
self.client
.execute_multiple(self.address(), contract_address, msgs, fee, memo)
.await
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "inclusion-probability"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
log = "0.4.17"
rand = "0.8.5"
thiserror = "1.0.32"
+11
View File
@@ -0,0 +1,11 @@
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("The list of cumulative stake was unexpectedly empty")]
EmptyListCumulStake,
#[error("Sample point was unexpectedly out of bounds")]
SamplePointOutOfBounds,
#[error("Norm computation failed on different size arrays")]
NormDifferenceSizeArrays,
#[error("Computed probabilities are fewer than input number of nodes")]
ResultsShorterThanInput,
}
+403
View File
@@ -0,0 +1,403 @@
//! Active set inclusion probability simulator
use std::time::{Duration, Instant};
use error::Error;
mod error;
const TOLERANCE_L2_NORM: f64 = 1e-4;
const TOLERANCE_MAX_NORM: f64 = 1e-4;
pub struct SelectionProbability {
pub active_set_probability: Vec<f64>,
pub reserve_set_probability: Vec<f64>,
pub samples: u64,
pub time: Duration,
pub delta_l2: f64,
pub delta_max: f64,
}
pub fn simulate_selection_probability_mixnodes(
list_stake_for_mixnodes: &[u128],
active_set_size: usize,
reserve_set_size: usize,
max_samples: u64,
max_time: Duration,
) -> Result<SelectionProbability, Error> {
log::trace!("Simulating mixnode active set selection probability");
// In case the active set size is larger than the number of bonded mixnodes, they all have 100%
// chance we don't have to go through with the simulation
if list_stake_for_mixnodes.len() <= active_set_size {
return Ok(SelectionProbability {
active_set_probability: vec![1.0; list_stake_for_mixnodes.len()],
reserve_set_probability: vec![0.0; list_stake_for_mixnodes.len()],
samples: 0,
time: Duration::ZERO,
delta_l2: 0.0,
delta_max: 0.0,
});
}
// Total number of existing (registered) nodes
let num_mixnodes = list_stake_for_mixnodes.len();
// Cumulative stake ordered by node index
let list_cumul = cumul_sum(list_stake_for_mixnodes);
// The computed probabilities
let mut active_set_probability = vec![0.0; num_mixnodes];
let mut reserve_set_probability = vec![0.0; num_mixnodes];
// Number sufficiently large to have a good approximation of selection probability
let mut samples = 0;
let mut delta_l2;
let mut delta_max;
let mut rng = rand::thread_rng();
// Make sure we bound the time we allow it to run
let start_time = Instant::now();
loop {
samples += 1;
let mut sample_active_mixnodes = Vec::new();
let mut sample_reserve_mixnodes = Vec::new();
let mut list_cumul_temp = list_cumul.clone();
let active_set_probability_previous = active_set_probability.clone();
// Select the active nodes for the epoch (hour)
while sample_active_mixnodes.len() < active_set_size
&& sample_active_mixnodes.len() < list_cumul_temp.len()
{
let candidate = sample_candidate(&list_cumul_temp, &mut rng)?;
if !sample_active_mixnodes.contains(&candidate) {
sample_active_mixnodes.push(candidate);
remove_mixnode_from_cumul_stake(candidate, &mut list_cumul_temp);
}
}
// Select the reserve nodes for the epoch (hour)
while sample_reserve_mixnodes.len() < reserve_set_size
&& sample_reserve_mixnodes.len() + sample_active_mixnodes.len() < list_cumul_temp.len()
{
let candidate = sample_candidate(&list_cumul_temp, &mut rng)?;
if !sample_reserve_mixnodes.contains(&candidate)
&& !sample_active_mixnodes.contains(&candidate)
{
sample_reserve_mixnodes.push(candidate);
remove_mixnode_from_cumul_stake(candidate, &mut list_cumul_temp);
}
}
// Sum up nodes being in active or reserve set
for active_mixnodes in sample_active_mixnodes {
active_set_probability[active_mixnodes] += 1.0;
}
for reserve_mixnodes in sample_reserve_mixnodes {
reserve_set_probability[reserve_mixnodes] += 1.0;
}
// Convergence critera only on active set.
// We devide by samples to get the average, that is not really part of the delta
// computation.
delta_l2 =
l2_diff(&active_set_probability, &active_set_probability_previous)? / (samples as f64);
delta_max =
max_diff(&active_set_probability, &active_set_probability_previous)? / (samples as f64);
if samples > 10 && delta_l2 < TOLERANCE_L2_NORM && delta_max < TOLERANCE_MAX_NORM
|| samples >= max_samples
{
break;
}
// Stop if we run out of time
if start_time.elapsed() > max_time {
log::debug!("Simulation ran out of time, stopping");
break;
}
}
// Divide occurrences with the number of samples once we're done to get the probabilities.
active_set_probability
.iter_mut()
.for_each(|x| *x /= samples as f64);
reserve_set_probability
.iter_mut()
.for_each(|x| *x /= samples as f64);
// Some sanity checks of the output
if active_set_probability.len() != num_mixnodes || reserve_set_probability.len() != num_mixnodes
{
return Err(Error::ResultsShorterThanInput);
}
Ok(SelectionProbability {
active_set_probability,
reserve_set_probability,
samples,
time: start_time.elapsed(),
delta_l2,
delta_max,
})
}
// Compute the cumulative sum
fn cumul_sum<'a>(list: impl IntoIterator<Item = &'a u128>) -> Vec<u128> {
let mut list_cumul = Vec::new();
let mut cumul = 0;
for entry in list {
cumul += entry;
list_cumul.push(cumul);
}
list_cumul
}
fn sample_candidate(list_cumul: &[u128], rng: &mut rand::rngs::ThreadRng) -> Result<usize, Error> {
use rand::distributions::{Distribution, Uniform};
let uniform = Uniform::from(0..*list_cumul.last().ok_or(Error::EmptyListCumulStake)?);
let r = uniform.sample(rng);
let candidate = list_cumul
.iter()
.enumerate()
.find(|(_, x)| *x >= &r)
.ok_or(Error::SamplePointOutOfBounds)?
.0;
Ok(candidate)
}
// Update list of cumulative stake to reflect eliminating the picked node
fn remove_mixnode_from_cumul_stake(candidate: usize, list_cumul_stake: &mut [u128]) {
let prob_candidate = if candidate == 0 {
list_cumul_stake[0]
} else {
list_cumul_stake[candidate] - list_cumul_stake[candidate - 1]
};
for cumul in list_cumul_stake.iter_mut().skip(candidate) {
*cumul -= prob_candidate;
}
}
// Compute the difference in l2-norm
fn l2_diff(v1: &[f64], v2: &[f64]) -> Result<f64, Error> {
if v1.len() != v2.len() {
return Err(Error::NormDifferenceSizeArrays);
}
Ok(v1
.iter()
.zip(v2)
.map(|(&i1, &i2)| (i1 - i2).powi(2))
.sum::<f64>()
.sqrt())
}
// Compute the difference in max-norm
fn max_diff(v1: &[f64], v2: &[f64]) -> Result<f64, Error> {
if v1.len() != v2.len() {
return Err(Error::NormDifferenceSizeArrays);
}
Ok(v1
.iter()
.zip(v2)
.map(|(x, y)| (x - y).abs())
.fold(f64::NEG_INFINITY, f64::max))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn compute_cumul_sum() {
let v = cumul_sum(&vec![1, 2, 3]);
assert_eq!(v, &[1, 3, 6]);
}
#[test]
fn remove_mixnode_from_cumul() {
let mut cumul_stake = vec![1, 2, 3, 4, 5, 6];
remove_mixnode_from_cumul_stake(3, &mut cumul_stake);
assert_eq!(cumul_stake, &[1, 2, 3, 3, 4, 5]);
}
#[test]
fn max_norm() {
let v1 = vec![1.0, 2.0, 3.0];
let v2 = vec![2.0, 4.0, -6.0];
assert!((max_diff(&v1, &v2).unwrap() - 9.0).abs() < f64::EPSILON);
}
#[test]
fn ls_norm() {
let v1 = vec![1.0, 2.0, 3.0];
let v2 = vec![2.0, 3.0, -2.0];
assert!((l2_diff(&v1, &v2).unwrap() - 5.196_152_422_706_632).abs() < 1e2 * f64::EPSILON);
}
// Replicate the results from the Python simulation code in https://github.com/nymtech/team-core/issues/114
#[test]
fn replicate_python_simulation() {
let active_set_size = 4;
let standby_set_size = 1;
// this has to contain the total stake per node
let list_mix = vec![
100, 100, 3000, 500_000, 100, 10, 10, 10, 10, 10, 30000, 500, 200, 52345,
];
let max_samples = 100_000;
let max_time = Duration::from_secs(10);
let SelectionProbability {
active_set_probability,
reserve_set_probability,
samples,
time: _,
delta_l2,
delta_max,
} = simulate_selection_probability_mixnodes(
&list_mix,
active_set_size,
standby_set_size,
max_samples,
max_time,
)
.unwrap();
// These values comes from running the python simulator for a very long time
let expected_active_set_probability = vec![
0.025_070_8,
0.025_073_2,
0.744_117,
0.999_999,
0.025_000_2,
0.002_524_4,
0.002_527_8,
0.002_528_6,
0.002_569_6,
0.002_513_6,
0.994,
0.125_482_8,
0.050_279_8,
0.998_313_2,
];
// The same check is used in the convergence criterion, and hence should be reflected in
// `delta_max` too.
assert!(
max_diff(&active_set_probability, &expected_active_set_probability).unwrap() < 1e-2
);
let expected_reserve_set_probability = vec![
0.076_392_4,
0.076_499,
0.204_893_6,
1e-06,
0.076_278_8,
0.007_720_6,
0.007_673,
0.007_700_2,
0.007_669_4,
0.007_731_2,
0.005_789_4,
0.368_465_6,
0.151_537_2,
0.001_648_6,
];
assert!(
max_diff(&reserve_set_probability, &expected_reserve_set_probability).unwrap() < 1e-2
);
// We converge around 20_000, add another 500 for some slack due to random values
assert!(samples < 20_500);
assert!(delta_l2 < TOLERANCE_L2_NORM);
assert!(delta_max < TOLERANCE_MAX_NORM);
}
#[test]
fn fewer_nodes_than_active_set_size() {
let active_set_size = 10;
let standby_set_size = 3;
let list_mix = vec![100, 100, 3000];
let max_samples = 100_000;
let max_time = Duration::from_secs(10);
let SelectionProbability {
active_set_probability,
reserve_set_probability,
samples,
time: _,
delta_l2,
delta_max,
} = simulate_selection_probability_mixnodes(
&list_mix,
active_set_size,
standby_set_size,
max_samples,
max_time,
)
.unwrap();
// These values comes from running the python simulator for a very long time
let expected_active_set_probability = vec![1.0, 1.0, 1.0];
let expected_reserve_set_probability = vec![0.0, 0.0, 0.0];
assert!(
max_diff(&active_set_probability, &expected_active_set_probability).unwrap()
< 1e1 * f64::EPSILON
);
assert!(
max_diff(&reserve_set_probability, &expected_reserve_set_probability).unwrap()
< 1e1 * f64::EPSILON
);
// We converge around 20_000, add another 500 for some slack due to random values
assert_eq!(samples, 0);
assert!(delta_l2 < f64::EPSILON);
assert!(delta_max < f64::EPSILON);
}
#[test]
fn fewer_nodes_than_reward_set_size() {
let active_set_size = 4;
let standby_set_size = 3;
let list_mix = vec![100, 100, 3000, 342, 3_498_234];
let max_samples = 100_000_000;
let max_time = Duration::from_secs(10);
let SelectionProbability {
active_set_probability,
reserve_set_probability,
samples,
time: _,
delta_l2,
delta_max,
} = simulate_selection_probability_mixnodes(
&list_mix,
active_set_size,
standby_set_size,
max_samples,
max_time,
)
.unwrap();
// These values comes from running the python simulator for a very long time
let expected_active_set_probability = vec![0.546, 0.538, 0.999, 0.915, 1.0];
let expected_reserve_set_probability = vec![0.453, 0.461, 0.0005, 0.084, 0.0];
assert!(
max_diff(&active_set_probability, &expected_active_set_probability).unwrap() < 1e-2,
);
assert!(
max_diff(&reserve_set_probability, &expected_reserve_set_probability).unwrap() < 1e-2,
);
// We converge around 20_000, add another 500 for some slack due to random values
assert!(samples < 20_500);
assert!(delta_l2 < TOLERANCE_L2_NORM);
assert!(delta_max < TOLERANCE_MAX_NORM);
}
}
+1 -1
View File
@@ -24,7 +24,7 @@ pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy";
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "http://127.0.0.1:8090";
pub(crate) const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "https://mainnet-stats.nymte.ch:8090/";
pub const NYMD_VALIDATOR: &str = "https://rpc.nyx.nodes.guru/";
pub const API_VALIDATOR: &str = "https://validator.nymtech.net/api/";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
+1 -1
View File
@@ -42,7 +42,7 @@ pub enum CoconutError {
)]
DeserializationMinLength { min: usize, actual: usize },
#[error("Tried to deserialize {object} with bytes of invalid length. Expected {actual} < {} or {modulus_target} % {modulus} == 0")]
#[error("Tried to deserialize {object} with bytes of invalid length. Expected {actual} < {object} or {modulus_target} % {modulus} == 0")]
DeserializationInvalidLength {
actual: usize,
target: usize,
+40
View File
@@ -0,0 +1,40 @@
## Unreleased
### Fixed
- vesting-contract: the contract now correctly stores delegations with their timestamp as opposed to using block height ([#1544])
[#1544]: https://github.com/nymtech/nym/pull/1544
## [nym-contracts-v1.0.1](https://github.com/nymtech/nym/tree/nym-contracts-v1.0.1) (2022-06-22)
### Added
- mixnet-contract: Added ClaimOperatorReward and ClaimDelegatorReward messages ([#1292])
- mixnet-contract: Replace all naked `-` with `saturating_sub`.
- mixnet-contract: Added staking_supply field to ContractStateParams.
- mixnet-contract: Added a query to get MixnodeBond by identity key ([#1369]).
- mixnet-contract: Added a query to get GatewayBond by identity key ([#1369]).
- vesting-contract: Added ClaimOperatorReward and ClaimDelegatorReward messages ([#1292])
- vesting-contract: Added limit to the amount of tokens one can pledge ([#1331])
### Fixed
- mixnet-contract: `estimated_delegator_reward` calculation ([#1284])
- mixnet-contract: delegator and operator rewards use lambda and sigma instead of lambda_ticked and sigma_ticked ([#1284])
- 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: replaced integer division with fixed for performance calculations ([#1284])
- mixnet-contract: Under certain circumstances nodes could not be unbonded ([#1255](https://github.com/nymtech/nym/issues/1255)) ([#1258])
- mixnet-contract: Using correct staking supply when distributing rewards. ([#1373])
- vesting-contract: replaced `checked_sub` with `saturating_sub` to fix the underflow in `get_vesting_tokens` ([#1275])
[#1255]: https://github.com/nymtech/nym/pull/1255
[#1257]: https://github.com/nymtech/nym/pull/1257
[#1258]: https://github.com/nymtech/nym/pull/1258
[#1275]: https://github.com/nymtech/nym/pull/1275
[#1284]: https://github.com/nymtech/nym/pull/1284
[#1292]: https://github.com/nymtech/nym/pull/1292
[#1331]: https://github.com/nymtech/nym/pull/1331
[#1369]: https://github.com/nymtech/nym/pull/1369
[#1373]: https://github.com/nymtech/nym/pull/1373
@@ -18,7 +18,6 @@ pub fn migrate_config_from_env(
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct OldContractState {
pub owner: Addr,
pub mix_denom: String,
pub rewarding_validator_address: Addr,
pub params: ContractStateParams,
}
+4 -4
View File
@@ -5,7 +5,7 @@ use cw_storage_plus::{Item, Map};
use mixnet_contract_common::IdentityKey;
use vesting_contract_common::PledgeData;
type BlockHeight = u64;
pub(crate) type BlockTimestampSecs = u64;
pub const KEY: Item<'_, u32> = Item::new("key");
const ACCOUNTS: Map<'_, String, Account> = Map::new("acc");
@@ -14,7 +14,7 @@ const BALANCES: Map<'_, u32, Uint128> = Map::new("blc");
const WITHDRAWNS: Map<'_, u32, Uint128> = Map::new("wthd");
const BOND_PLEDGES: Map<'_, u32, PledgeData> = Map::new("bnd");
const GATEWAY_PLEDGES: Map<'_, u32, PledgeData> = Map::new("gtw");
pub const DELEGATIONS: Map<'_, (u32, IdentityKey, BlockHeight), Uint128> = Map::new("dlg");
pub const DELEGATIONS: Map<'_, (u32, IdentityKey, BlockTimestampSecs), Uint128> = Map::new("dlg");
pub const ADMIN: Item<'_, String> = Item::new("adm");
pub const MIXNET_CONTRACT_ADDRESS: Item<'_, String> = Item::new("mix");
pub const MIX_DENOM: Item<'_, String> = Item::new("den");
@@ -35,7 +35,7 @@ pub fn update_locked_pledge_cap(
}
pub fn save_delegation(
key: (u32, IdentityKey, BlockHeight),
key: (u32, IdentityKey, BlockTimestampSecs),
amount: Uint128,
storage: &mut dyn Storage,
) -> Result<(), ContractError> {
@@ -44,7 +44,7 @@ pub fn save_delegation(
}
pub fn remove_delegation(
key: (u32, IdentityKey, BlockHeight),
key: (u32, IdentityKey, BlockTimestampSecs),
storage: &mut dyn Storage,
) -> Result<(), ContractError> {
DELEGATIONS.remove(storage, key);
@@ -88,7 +88,7 @@ impl DelegatingAccount for Account {
vec![coin.clone()],
)?;
self.track_delegation(
env.block.height,
env.block.time.seconds(),
mix_identity,
current_balance,
coin,
@@ -129,14 +129,14 @@ impl DelegatingAccount for Account {
fn track_delegation(
&self,
block_height: u64,
block_timestamp_secs: u64,
mix_identity: IdentityKey,
current_balance: Uint128,
delegation: Coin,
storage: &mut dyn Storage,
) -> Result<(), ContractError> {
save_delegation(
(self.storage_key(), mix_identity, block_height),
(self.storage_key(), mix_identity, block_timestamp_secs),
delegation.amount,
storage,
)?;
+18 -4
View File
@@ -3,7 +3,7 @@ use crate::errors::ContractError;
use crate::storage::{
load_balance, load_bond_pledge, load_gateway_pledge, load_withdrawn, remove_bond_pledge,
remove_delegation, remove_gateway_pledge, save_account, save_balance, save_bond_pledge,
save_gateway_pledge, save_withdrawn, DELEGATIONS, KEY,
save_gateway_pledge, save_withdrawn, BlockTimestampSecs, DELEGATIONS, KEY,
};
use cosmwasm_std::{Addr, Coin, Order, Storage, Timestamp, Uint128};
use cw_storage_plus::Bound;
@@ -101,10 +101,9 @@ impl Account {
}
}
/// Returns the index of the next vesting period. Unless the current time is somehow in the past or vesting has not started yet.
/// In case vesting is over it will always return NUM_VESTING_PERIODS.
pub fn get_current_vesting_period(&self, block_time: Timestamp) -> Period {
// Returns the index of the next vesting period. Unless the current time is somehow in the past or vesting has not started yet.
// In case vesting is over it will always return NUM_VESTING_PERIODS.
if block_time.seconds() < self.periods.first().unwrap().start_time {
Period::Before
} else if self.periods.last().unwrap().end_time() < block_time {
@@ -262,4 +261,19 @@ impl Account {
.filter_map(|x| x.ok())
.fold(Uint128::zero(), |acc, (_key, val)| acc + val))
}
pub fn total_delegations_at_timestamp(
&self,
storage: &dyn Storage,
start_time: BlockTimestampSecs,
) -> Result<Uint128, ContractError> {
Ok(DELEGATIONS
.sub_prefix(self.storage_key())
.range(storage, None, None, Order::Ascending)
.filter_map(|x| x.ok())
.filter(|((_mix, block_time), _amount)| *block_time <= start_time)
.fold(Uint128::zero(), |acc, ((_mix, _block_time), amount)| {
acc + amount
}))
}
}
@@ -1,7 +1,7 @@
use crate::errors::ContractError;
use crate::storage::{delete_account, save_account, DELEGATIONS, MIX_DENOM};
use crate::storage::{delete_account, save_account, MIX_DENOM};
use crate::traits::VestingAccount;
use cosmwasm_std::{Addr, Coin, Env, Order, Storage, Timestamp, Uint128};
use cosmwasm_std::{Addr, Coin, Env, Storage, Timestamp, Uint128};
use vesting_contract_common::{OriginalVestingResponse, Period};
use super::Account;
@@ -16,13 +16,6 @@ impl VestingAccount for Account {
+ self.get_pledged_vesting(None, env, storage)?.amount)
}
fn track_reward(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError> {
let current_balance = self.load_balance(storage)?;
let new_balance = current_balance + amount.amount;
self.save_balance(new_balance, storage)?;
Ok(())
}
fn locked_coins(
&self,
block_time: Option<Timestamp>,
@@ -141,14 +134,7 @@ impl VestingAccount for Account {
Period::In(idx) => self.periods[idx as usize].start_time,
};
let coin = DELEGATIONS
.sub_prefix(self.storage_key())
.range(storage, None, None, Order::Ascending)
.filter_map(|x| x.ok())
.filter(|((_mix, block_time), _amount)| *block_time < start_time)
.fold(Uint128::zero(), |acc, ((_mix, _block_time), amount)| {
acc + amount
});
let coin = self.total_delegations_at_timestamp(storage, start_time)?;
let amount = Uint128::new(coin.u128().min(max_available.u128()));
@@ -158,6 +144,7 @@ impl VestingAccount for Account {
})
}
// TODO: why do we allow querying for block times in the past? - just use env.block.time all the time
fn get_delegated_vesting(
&self,
block_time: Option<Timestamp>,
@@ -166,9 +153,18 @@ impl VestingAccount for Account {
) -> Result<Coin, ContractError> {
let block_time = block_time.unwrap_or(env.block.time);
let delegated_free = self.get_delegated_free(Some(block_time), env, storage)?;
let total_delegations = self.total_delegations(storage)?;
let amount = total_delegations - delegated_free.amount;
let period = self.get_current_vesting_period(block_time);
let start_time = match period {
Period::Before => 0,
Period::After => u64::MAX,
Period::In(idx) => self.periods[idx as usize].start_time,
};
let delegations_before_start_time =
self.total_delegations_at_timestamp(storage, start_time)?;
let amount = delegations_before_start_time - delegated_free.amount;
Ok(Coin {
amount,
@@ -261,4 +257,11 @@ impl VestingAccount for Account {
save_account(self, storage)?;
Ok(())
}
fn track_reward(&self, amount: Coin, storage: &mut dyn Storage) -> Result<(), ContractError> {
let current_balance = self.load_balance(storage)?;
let new_balance = current_balance + amount.amount;
self.save_balance(new_balance, storage)?;
Ok(())
}
}
+169 -1
View File
@@ -44,10 +44,11 @@ mod tests {
use crate::traits::DelegatingAccount;
use crate::traits::VestingAccount;
use crate::traits::{GatewayBondingAccount, MixnodeBondingAccount};
use crate::vesting::{populate_vesting_periods, Account};
use cosmwasm_std::testing::{mock_env, mock_info};
use cosmwasm_std::{coins, Addr, Coin, Timestamp, Uint128};
use mixnet_contract_common::{Gateway, MixNode};
use vesting_contract_common::messages::ExecuteMsg;
use vesting_contract_common::messages::{ExecuteMsg, VestingSpecification};
use vesting_contract_common::Period;
#[test]
@@ -757,4 +758,171 @@ mod tests {
.unwrap();
assert_eq!(Uint128::zero(), bonded_vesting.amount);
}
#[test]
fn delegated_free() {
let mut deps = init_contract();
let mut env = mock_env();
let vesting_period_length_secs = 3600;
let account_creation_timestamp = 1650000000;
let account_creation_blockheight = 12345;
// this value is completely arbitrary, I just wanted to keep consistent
// (and make sure that if block timestamp increases so does the block height)
let blocks_per_period = 100;
env.block.height = account_creation_blockheight;
env.block.time = Timestamp::from_seconds(account_creation_timestamp);
// lets define some helper timestamps
// our account is set to be created after 2 vesting periods already passed
let vesting_start_blockheight = account_creation_blockheight - 2 * blocks_per_period;
let vesting_start_timestamp = account_creation_timestamp - 2 * vesting_period_length_secs;
let vesting_period2_start_blockheight = vesting_start_blockheight + blocks_per_period;
let vesting_period2_start_timestamp = vesting_start_timestamp + vesting_period_length_secs;
// this vesting period is currently in progress!
let vesting_period3_start_blockheight =
vesting_period2_start_blockheight + blocks_per_period;
let vesting_period3_start_timestamp =
vesting_period2_start_timestamp + vesting_period_length_secs;
// and this one is in the future! (in relation to account creation)
let vesting_period4_start_blockheight =
vesting_period3_start_blockheight + blocks_per_period;
let vesting_period4_start_timestamp =
vesting_period3_start_timestamp + vesting_period_length_secs;
// lets create our vesting account
let periods = populate_vesting_periods(
vesting_start_timestamp,
VestingSpecification::new(None, Some(vesting_period_length_secs), None),
);
let vesting_account = Account::new(
Addr::unchecked("owner"),
Some(Addr::unchecked("staking")),
Coin {
amount: Uint128::new(1_000_000_000_000),
denom: TEST_COIN_DENOM.to_string(),
},
Timestamp::from_seconds(account_creation_timestamp),
periods,
deps.as_mut().storage,
)
.unwrap();
// time for some delegations
let mix_identity = "alice".to_string();
let delegation = Coin {
amount: Uint128::new(90_000_000_000),
denom: TEST_COIN_DENOM.to_string(),
};
// delegate explicitly at the time the account was created
// (i.e. after 2 vesting periods already elapsed)
env.block.height = account_creation_blockheight;
env.block.time = Timestamp::from_seconds(account_creation_timestamp);
let ok = vesting_account.try_delegate_to_mixnode(
mix_identity.clone(),
delegation.clone(),
&env,
&mut deps.storage,
);
assert!(ok.is_ok());
let vested_coins = vesting_account
.get_vested_coins(None, &env, &deps.storage)
.unwrap();
let vesting_coins = vesting_account
.get_vesting_coins(None, &env, &deps.storage)
.unwrap();
assert_eq!(vested_coins.amount, Uint128::new(250_000_000_000));
assert_eq!(vesting_coins.amount, Uint128::new(750_000_000_000));
let delegated_free = vesting_account
.get_delegated_free(None, &env, &deps.storage)
.unwrap();
let delegated_vesting = vesting_account
.get_delegated_vesting(None, &env, &deps.storage)
.unwrap();
// all good so far
assert_eq!(delegated_free.amount, Uint128::new(90_000_000_000));
assert_eq!(delegated_vesting.amount, Uint128::zero());
// some time passes, and we're now into the next vesting period, more of our coins got unlocked!
env.block.height = vesting_period4_start_blockheight;
env.block.time = Timestamp::from_seconds(vesting_period4_start_timestamp);
let vested_coins = vesting_account
.get_vested_coins(None, &env, &deps.storage)
.unwrap();
let vesting_coins = vesting_account
.get_vesting_coins(None, &env, &deps.storage)
.unwrap();
assert_eq!(vested_coins.amount, Uint128::new(375_000_000_000));
assert_eq!(vesting_coins.amount, Uint128::new(625_000_000_000));
// and nothing about our existing delegation changed
let delegated_free = vesting_account
.get_delegated_free(None, &env, &deps.storage)
.unwrap();
let delegated_vesting = vesting_account
.get_delegated_vesting(None, &env, &deps.storage)
.unwrap();
assert_eq!(delegated_free.amount, Uint128::new(90_000_000_000));
assert_eq!(delegated_vesting.amount, Uint128::zero());
// however, create a new delegation now in this brand new vesting period
let delegation = Coin {
amount: Uint128::new(50_000_000_000),
denom: TEST_COIN_DENOM.to_string(),
};
let ok = vesting_account.try_delegate_to_mixnode(
mix_identity.clone(),
delegation.clone(),
&env,
&mut deps.storage,
);
assert!(ok.is_ok());
// we're still good here, we have delegated in total 140M from our vested tokens!
let delegated_free = vesting_account
.get_delegated_free(None, &env, &deps.storage)
.unwrap();
let delegated_vesting = vesting_account
.get_delegated_vesting(None, &env, &deps.storage)
.unwrap();
assert_eq!(delegated_free.amount, Uint128::new(140_000_000_000));
assert_eq!(delegated_vesting.amount, Uint128::zero());
// but let's ask now a different question:
// how many vested tokens have I had delegated during vesting period3? (i.e. after account creation)
let delegated_free = vesting_account
.get_delegated_free(
Some(Timestamp::from_seconds(vesting_period3_start_timestamp)),
&env,
&deps.storage,
)
.unwrap();
let delegated_vesting = vesting_account
.get_delegated_vesting(
Some(Timestamp::from_seconds(vesting_period3_start_timestamp)),
&env,
&deps.storage,
)
.unwrap();
// returns 90M as the 50M delegation didn't exist at this point of time
assert_eq!(delegated_free.amount, Uint128::new(90_000_000_000));
// the 50M delegation wasn't a thing here for VESTING tokens either
assert_eq!(delegated_vesting.amount, Uint128::zero());
}
}
+1 -1
View File
@@ -15,6 +15,6 @@ BANDWIDTH_CLAIM_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
MULTISIG_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
STATISTICS_SERVICE_DOMAIN_ADDRESS="http://127.0.0.1:8090"
STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090"
NYMD_VALIDATOR="https://rpc.nyx.nodes.guru/"
API_VALIDATOR="https://validator.nymtech.net/api/"
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-gateway"
version = "1.0.1"
version = "1.0.2"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Mixnet Gateway"
edition = "2021"
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-mixnode"
version = "1.0.1"
version = "1.0.2"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+25
View File
@@ -0,0 +1,25 @@
## [nym-connect-v1.0.2](https://github.com/nymtech/nym/tree/nym-connect-v1.0.2) (2022-08-18)
### Changed
- nym-connect: "load balance" the service providers by picking a random Service Provider for each Service and storing in local storage so it remains sticky for the user ([#1540])
- nym-connect: the ServiceProviderSelector only displays the available Services, and picks a random Service Provider for Services the user has never used before ([#1540])
- nym-connect: add `local-forage` for storing user settings ([#1540])
[#1540]: https://github.com/nymtech/nym/pull/1540
## [nym-connect-v1.0.1](https://github.com/nymtech/nym/tree/nym-connect-v1.0.1) (2022-07-22)
### Added
- nym-connect: initial proof-of-concept of a UI around the socks5 client was added
- nym-connect: add ability to select network requester and gateway ([#1427])
- nym-connect: add ability to export gateway keys as JSON
- nym-connect: add auto updater
### Changed
- nym-connect: reuse config id instead of creating a new id on each connection
[#1427]: https://github.com/nymtech/nym/pull/1427
+2 -2
View File
@@ -3404,7 +3404,7 @@ dependencies = [
[[package]]
name = "nym-connect"
version = "1.0.1"
version = "1.0.2"
dependencies = [
"bip39",
"client-core",
@@ -3436,7 +3436,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.0.1"
version = "1.0.2"
dependencies = [
"clap",
"client-core",
+1
View File
@@ -40,6 +40,7 @@
"react-hook-form": "^7.14.2",
"react-router-dom": "^5.2.0",
"semver": "^6.3.0",
"@tauri-apps/tauri-forage": "^1.0.0-beta.2",
"yup": "^0.32.9"
},
"devDependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-connect"
version = "1.0.1"
version = "1.0.2"
description = "nym-connect"
authors = ["Nym Technologies SA"]
license = ""
+3 -3
View File
@@ -1,7 +1,7 @@
{
"package": {
"productName": "nym-connect",
"version": "1.0.1"
"version": "1.0.2"
},
"build": {
"distDir": "../dist",
@@ -65,9 +65,9 @@
},
"windows": [
{
"title": "Nym Connect",
"title": "NymConnect",
"width": 240,
"height": 480,
"height": 500,
"resizable": false
}
],
+50 -50
View File
@@ -1,6 +1,56 @@
import React from 'react';
import { ConnectionStatusKind } from '../types';
const getBusyFillColor = (color: string): string => {
if (color === '#60D6EF') {
return '#21D072';
}
return '#60D6EF';
};
const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isError: boolean): string => {
if (isError && hover) {
return '#21D072';
}
if (isError) {
return '#40475C';
}
switch (status) {
case ConnectionStatusKind.disconnected:
if (hover) {
return '#21D072';
}
return '#60D6EF';
case ConnectionStatusKind.connecting:
case ConnectionStatusKind.disconnecting:
return '#60D6EF';
default:
// connected
if (hover) {
return '#DA465B';
}
return '#21D072';
}
};
const getStatusText = (status: ConnectionStatusKind, hover: boolean): string => {
switch (status) {
case ConnectionStatusKind.disconnected:
return 'Connect';
case ConnectionStatusKind.connecting:
return 'Connecting';
case ConnectionStatusKind.disconnecting:
return 'Connected';
default:
// connected
if (hover) {
return 'Disconnect';
}
return 'Connected';
}
};
export const ConnectionButton: React.FC<{
status: ConnectionStatusKind;
disabled?: boolean;
@@ -130,53 +180,3 @@ export const ConnectionButton: React.FC<{
</svg>
);
};
const getBusyFillColor = (color: string): string => {
if (color === '#60D6EF') {
return '#21D072';
}
return '#60D6EF';
};
const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isError: boolean): string => {
if (isError && hover) {
return '#21D072';
}
if (isError) {
return '#40475C';
}
switch (status) {
case ConnectionStatusKind.disconnected:
if (hover) {
return '#21D072';
}
return '#60D6EF';
case ConnectionStatusKind.connecting:
case ConnectionStatusKind.disconnecting:
return '#60D6EF';
default:
// connected
if (hover) {
return '#DA465B';
}
return '#21D072';
}
};
const getStatusText = (status: ConnectionStatusKind, hover: boolean): string => {
switch (status) {
case ConnectionStatusKind.disconnected:
return 'Connect';
case ConnectionStatusKind.connecting:
return 'Connecting';
case ConnectionStatusKind.disconnecting:
return 'Connected';
default:
// connected
if (hover) {
return 'Disconnect';
}
return 'Connected';
}
};
@@ -1,24 +1,53 @@
import React from 'react';
import React, { useEffect, useMemo } from 'react';
import IconButton from '@mui/material/IconButton';
import Menu from '@mui/material/Menu';
import MenuItem from '@mui/material/MenuItem';
import ArrowDropDownCircleIcon from '@mui/icons-material/ArrowDropDownCircle';
import { Box, CircularProgress, Stack, Tooltip, Typography } from '@mui/material';
import { ServiceProvider, Services } from '../types/directory';
import { ServiceProvider, Service, Services } from '../types/directory';
type ServiceWithRandomSp = {
id: string;
description: string;
sp: ServiceProvider;
};
export const ServiceProviderSelector: React.FC<{
onChange?: (serviceProvider: ServiceProvider) => void;
services?: Services;
}> = ({ services, onChange }) => {
const [serviceProvider, setServiceProvider] = React.useState<ServiceProvider | undefined>();
currentSp?: ServiceProvider;
}> = ({ services, currentSp, onChange }) => {
const [service, setService] = React.useState<Service>();
const [serviceProvider, setServiceProvider] = React.useState<ServiceProvider | undefined>(currentSp);
const textEl = React.useRef<null | HTMLElement>(null);
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
useEffect(() => {
if (!serviceProvider && currentSp) {
setServiceProvider(currentSp);
}
}, [currentSp]);
useEffect(() => {
if (services && serviceProvider) {
// retrieve the service corresponding to this service provider
setService(
services.find((s) =>
s.items.some(
({ id, address, gateway }) =>
id === serviceProvider.id && address === serviceProvider.address && gateway === serviceProvider.gateway,
),
),
);
}
}, [serviceProvider, services]);
const handleClick = () => {
setAnchorEl(textEl.current);
};
const handleClose = (newServiceProvider?: ServiceProvider) => {
if (newServiceProvider) {
if (newServiceProvider && newServiceProvider !== currentSp) {
setServiceProvider(newServiceProvider);
onChange?.(newServiceProvider);
}
@@ -39,6 +68,16 @@ export const ServiceProviderSelector: React.FC<{
);
}
const servicesWithRandomSp: ServiceWithRandomSp[] = useMemo(
() =>
services.map(({ id, items, description }) => ({
id,
description,
sp: items[Math.floor(Math.random() * items.length)],
})),
[services],
);
return (
<>
<Box display="flex" alignItems="center" justifyContent="space-between" sx={{ mt: 3 }}>
@@ -48,7 +87,7 @@ export const ServiceProviderSelector: React.FC<{
fontWeight={700}
color={(theme) => (serviceProvider ? undefined : theme.palette.primary.main)}
>
{serviceProvider ? serviceProvider.description : 'Select a service'}
{service ? service.description : 'Select a service'}
</Typography>
<IconButton
id="service-provider-button"
@@ -65,44 +104,46 @@ export const ServiceProviderSelector: React.FC<{
anchorEl={anchorEl}
open={open}
onClose={() => handleClose()}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
MenuListProps={{
'aria-labelledby': 'service-provider-button',
sx: {
minWidth: 160,
},
}}
>
{services.map((service) => (
<>
<MenuItem disabled dense sx={{ fontSize: 'small', fontWeight: 'bold', mb: -1 }}>
{service.description}
</MenuItem>
{service.items.map((sp) => (
<MenuItem dense sx={{ fontSize: 'small', ml: 2, height: 'auto' }} onClick={() => handleClose(sp)}>
<Tooltip
title={
<Stack direction="column">
<Typography fontSize="inherit">
<code>{sp.id}</code>
</Typography>
<Typography fontSize="inherit" fontWeight={700}>
{sp.description}
</Typography>
<Typography fontSize="inherit">
Gateway <code>{sp.gateway.slice(0, 10)}...</code>
</Typography>
<Typography fontSize="inherit">
Provider <code>{sp.address.slice(0, 10)}...</code>
</Typography>
</Stack>
}
arrow
placement="top"
>
<Typography fontSize="inherit" noWrap>
{servicesWithRandomSp.map(({ id, description, sp }) => (
<MenuItem dense key={id} sx={{ fontSize: 'small', fontWeight: 'bold' }} onClick={() => handleClose(sp)}>
<Tooltip
title={
<Stack direction="column">
<Typography fontSize="inherit">
<code>{sp.id}</code>
</Typography>
<Typography fontSize="inherit" fontWeight={700}>
{sp.description}
</Typography>
</Tooltip>
</MenuItem>
))}
</>
<Typography fontSize="inherit">
Gateway <code>{sp.gateway.slice(0, 10)}...</code>
</Typography>
<Typography fontSize="inherit">
Provider <code>{sp.address.slice(0, 10)}...</code>
</Typography>
</Stack>
}
arrow
placement="top"
>
<Typography>{description}</Typography>
</Tooltip>
</MenuItem>
))}
</Menu>
</>
+61 -22
View File
@@ -1,8 +1,9 @@
import React, { createContext, useCallback, useContext, useEffect, useState } from 'react';
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { DateTime } from 'luxon';
import { invoke } from '@tauri-apps/api';
import type { UnlistenFn } from '@tauri-apps/api/event';
import { listen } from '@tauri-apps/api/event';
import { forage } from '@tauri-apps/tauri-forage';
import { ConnectionStatusKind } from '../types';
import { ConnectionStatsItem } from '../components/ConnectionStats';
import { ServiceProvider, Services } from '../types/directory';
@@ -36,7 +37,7 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
const [connectionStatus, setConnectionStatus] = useState<ConnectionStatusKind>(ConnectionStatusKind.disconnected);
const [connectionStats, setConnectionStats] = useState<ConnectionStatsItem[]>();
const [connectedSince, setConnectedSince] = useState<DateTime>();
const [services, setServices] = React.useState<Services>();
const [services, setServices] = React.useState<Services>([]);
const [serviceProvider, setRawServiceProvider] = React.useState<ServiceProvider>();
useEffect(() => {
@@ -72,33 +73,71 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
await invoke('start_disconnecting');
}, []);
const setSpInStorage = async (sp: ServiceProvider) => {
await forage.setItem({
key: 'nym-connect-sp',
value: sp,
} as any)();
};
const setServiceProvider = useCallback(async (newServiceProvider: ServiceProvider) => {
await invoke('set_gateway', { gateway: newServiceProvider.gateway });
await invoke('set_service_provider', { serviceProvider: newServiceProvider.address });
await setSpInStorage(newServiceProvider);
setRawServiceProvider(newServiceProvider);
}, []);
return (
<ClientContext.Provider
value={{
mode,
setMode,
connectionStatus,
setConnectionStatus,
connectionStats,
setConnectionStats,
connectedSince,
setConnectedSince,
startConnecting,
startDisconnecting,
services,
serviceProvider,
setServiceProvider,
}}
>
{children}
</ClientContext.Provider>
const getSpFromStorage = async () => {
try {
const spFromStorage = await forage.getItem({ key: 'nym-connect-sp' })();
if (spFromStorage) {
setRawServiceProvider(spFromStorage);
}
} catch (e) {
console.warn(e);
}
};
useEffect(() => {
const validityCheck = async () => {
if (services.length > 0 && serviceProvider) {
const isValid = services.some(({ items }) => items.some(({ id }) => id === serviceProvider.id));
if (!isValid) {
console.warn('invalid SP, cleaning local storage');
await forage.removeItem({
key: 'nym-connect-sp',
})();
setRawServiceProvider(undefined);
}
}
};
validityCheck();
}, [services, serviceProvider]);
useEffect(() => {
getSpFromStorage();
}, []);
const contextValue = useMemo(
() => ({
mode,
setMode,
connectionStatus,
setConnectionStatus,
connectionStats,
setConnectionStats,
connectedSince,
setConnectedSince,
startConnecting,
startDisconnecting,
services,
serviceProvider,
setServiceProvider,
}),
[mode, connectedSince, connectionStatus, connectionStats, connectedSince, services, serviceProvider],
);
return <ClientContext.Provider value={contextValue}>{children}</ClientContext.Provider>;
};
export const useClientContext = () => useContext(ClientContext);
+5 -2
View File
@@ -6,6 +6,7 @@ import { ConnectionStatusKind } from '../types';
import { NeedHelp } from '../components/NeedHelp';
import { ServiceProviderSelector } from '../components/ServiceProviderSelector';
import { ServiceProvider, Services } from '../types/directory';
import { useClientContext } from '../context/main';
export const DefaultLayout: React.FC<{
status: ConnectionStatusKind;
@@ -20,6 +21,8 @@ export const DefaultLayout: React.FC<{
setServiceProvider(newServiceProvider);
onServiceProviderChange?.(newServiceProvider);
};
const { serviceProvider: currentSp } = useClientContext();
return (
<AppWindowFrame>
<Typography fontWeight="400" fontSize="12px" textAlign="center" sx={{ opacity: 0.6 }}>
@@ -31,10 +34,10 @@ export const DefaultLayout: React.FC<{
<br />
Nym mixnet for privacy.
</Typography>
<ServiceProviderSelector services={services} onChange={handleServiceProviderChange} />
<ServiceProviderSelector services={services} onChange={handleServiceProviderChange} currentSp={currentSp} />
<ConnectionButton
status={status}
disabled={serviceProvider === undefined}
disabled={serviceProvider === undefined && currentSp === undefined}
busy={busy}
isError={isError}
onClick={onConnectClick}
+1 -1
View File
@@ -1 +1 @@
ADMIN_ADDRESS={"MAINNET":[],"SANDBOX":[],"QA":["n1c8te4wlc25re97qw5nh8urtpek494zqx30lza2","n177krl498arsfupd2p9097kwfmuf07lkj6crmj9"]}
ADMIN_ADDRESS={\"MAINNET\":[],\"SANDBOX\":[],\"QA\":[\"n1c8te4wlc25re97qw5nh8urtpek494zqx30lza2\",\"n177krl498arsfupd2p9097kwfmuf07lkj6crmj9\"]}
+213
View File
@@ -0,0 +1,213 @@
## [nym-wallet-v1.0.8](https://github.com/nymtech/nym/releases/tag/nym-wallet-v1.0.8) (2022-08-11)
- wallet: new bonding flow and screen for bonded node
- wallet: compound and redeem functionalities for operator rewards
- wallet: a few minor touch ups and bug fixes
## [nym-wallet-v1.0.7](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.7) (2022-07-11)
- wallet: dark mode
- wallet: when simulating gas costs, an automatic adjustment is being used ([#1388]).
[#1388]: https://github.com/nymtech/nym/pull/1388
## [nym-wallet-v1.0.6](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.6) (2022-06-21)
- wallet: undelegating now uses either the mixnet or vesting contract, or both, depending on how delegations were made
- wallet: redeeming and compounding now uses both the mixnet and vesting contract
- wallet: the wallet backend learned how to archive wallet files
- wallet: add ENABLE_QA_MODE environment variable to enable QA mode on built wallet
## [nym-wallet-v1.0.5](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.5) (2022-06-14)
- wallet: add simple CLI tool for decrypting and recovering the wallet file.
- wallet: added support for multiple accounts ([#1265])
- wallet: compound and claim reward endpoints for operators and delegators ([#1302])
- wallet: require password to switch accounts
- wallet: the wallet backend learned how to keep track of validator name, either hardcoded or by querying the status endpoint.
- wallet: new delegation and rewards UI
- wallet: show version in nav bar
- wallet: contract admin route put back
- wallet: staking_supply field to StateParams
- wallet: show transaction hash for redeeming or compounding rewards
[#1265]: https://github.com/nymtech/nym/pull/1265
[#1302]: https://github.com/nymtech/nym/pull/1302
## [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])
## [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-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-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))
+1 -1
View File
@@ -3055,7 +3055,7 @@ dependencies = [
[[package]]
name = "nym_wallet"
version = "1.0.7"
version = "1.0.8"
dependencies = [
"aes-gcm",
"argon2 0.3.4",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym_wallet"
version = "1.0.7"
version = "1.0.8"
description = "Nym Native Wallet"
authors = ["Nym Technologies SA"]
license = ""
@@ -254,7 +254,7 @@ pub async fn get_mix_node_description(
host: &str,
port: u16,
) -> Result<NodeDescription, BackendError> {
return fetch_mix_node_description(host, port)
fetch_mix_node_description(host, port)
.await
.map_err(|e| BackendError::ReqwestError { source: e });
.map_err(|e| BackendError::ReqwestError { source: e })
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"package": {
"productName": "nym-wallet",
"version": "1.0.7"
"version": "1.0.8"
},
"build": {
"distDir": "../dist",
@@ -75,7 +75,7 @@ export const BondedMixnode = ({
id: 'pm-cell',
},
{
cell: `${operatorRewards.amount} ${operatorRewards.denom}`,
cell: operatorRewards ? `${operatorRewards.amount} ${operatorRewards.denom}` : '-',
id: 'operator-rewards-cell',
},
{
@@ -86,7 +86,7 @@ export const BondedMixnode = ({
cell: (
<BondedMixnodeActions
onActionSelect={onActionSelect}
disabledRedeemAndCompound={Number(mixnode.operatorRewards.amount) === 0}
disabledRedeemAndCompound={(operatorRewards && Number(operatorRewards.amount) === 0) || false}
/>
),
id: 'actions-cell',
@@ -43,7 +43,9 @@ export const CompoundRewardsModal = ({
>
<ModalListItem
label="Rewards to redeem"
value={`${node.operatorRewards.amount} ${node.operatorRewards.denom.toUpperCase()}`}
value={
node.operatorRewards ? `${node.operatorRewards.amount} ${node.operatorRewards.denom.toUpperCase()}` : '-'
}
divider
/>
<ModalFee fee={fee} isLoading={isFeeLoading} divider />
@@ -43,7 +43,9 @@ export const RedeemRewardsModal = ({
>
<ModalListItem
label="Rewards to redeem"
value={`${node.operatorRewards.amount} ${node.operatorRewards.denom.toUpperCase()}`}
value={
node.operatorRewards ? `${node.operatorRewards.amount} ${node.operatorRewards.denom.toUpperCase()}` : '-'
}
divider
/>
<ModalFee fee={fee} isLoading={isFeeLoading} divider />
@@ -61,7 +61,9 @@ export const UnbondModal = ({ node, onConfirm, onClose, onError }: Props) => {
{isMixnode(node) && (
<ModalListItem
label="Operator rewards"
value={`${node.operatorRewards.amount} ${node.operatorRewards.denom.toUpperCase()}`}
value={
node.operatorRewards ? `${node.operatorRewards.amount} ${node.operatorRewards.denom.toUpperCase()}` : '-'
}
divider
/>
)}
@@ -98,7 +98,7 @@ export const DelegationsActionsMenu: React.FC<{
<ActionsMenuItem title="Undelegate" Icon={<Undelegate />} onClick={() => handleActionSelect('undelegate')} />
<ActionsMenuItem
title="Redeem"
description="Trasfer your rewards to your balance"
description="Transfer your rewards to your balance"
Icon={<Typography sx={{ pl: 1 }}>R</Typography>}
onClick={() => handleActionSelect('redeem')}
disabled={disableRedeemingRewards}
@@ -16,12 +16,13 @@ import {
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import { visuallyHidden } from '@mui/utils';
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import { DelegationWithEverything } from '@nymproject/types';
import { DelegationEvent, DelegationWithEverything } from '@nymproject/types';
import { Link } from '@nymproject/react/link/Link';
import { format, formatDistanceToNow, parseISO } from 'date-fns';
import { styled } from '@mui/material/styles';
import { tableCellClasses } from '@mui/material/TableCell';
import { DelegationListItemActions, DelegationsActionsMenu } from './DelegationActions';
import { DelegationWithEvent, isPendingDelegation, TDelegations } from '../../context/delegations';
const StyledTooltipTableCell = styled(TableCell)(({ theme }) => ({
[`&.${tableCellClasses.head}`]: {
@@ -71,13 +72,30 @@ function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
return 0;
}
function sortPendingDelegation(a: DelegationWithEvent, b: DelegationWithEvent) {
if (isPendingDelegation(a) && isPendingDelegation(b)) return 0;
if (isPendingDelegation(b)) return -1;
if (isPendingDelegation(a)) return 1;
return 2;
}
function getComparator<Key extends keyof DelegationWithEverything>(
order: Order,
orderBy: Key,
): (a: DelegationWithEverything, b: DelegationWithEverything) => number {
): (a: DelegationWithEvent, b: DelegationWithEvent) => number {
return order === 'desc'
? (a, b) => descendingComparator(a, b, orderBy)
: (a, b) => -descendingComparator(a, b, orderBy);
? (a, b) => {
const pendingSort = sortPendingDelegation(a, b);
if (pendingSort === 2)
return descendingComparator(a as DelegationWithEverything, b as DelegationWithEverything, orderBy);
return pendingSort;
}
: (a, b) => {
const pendingSort = -sortPendingDelegation(a, b);
if (pendingSort === 2)
return -descendingComparator(a as DelegationWithEverything, b as DelegationWithEverything, orderBy);
return pendingSort;
};
}
const EnhancedTableHead: React.FC<EnhancedTableProps> = ({ order, orderBy, onRequestSort }) => {
@@ -119,7 +137,7 @@ const EnhancedTableHead: React.FC<EnhancedTableProps> = ({ order, orderBy, onReq
export const DelegationList: React.FC<{
isLoading?: boolean;
items?: DelegationWithEverything[];
items?: TDelegations;
onItemActionClick?: (item: DelegationWithEverything, action: DelegationListItemActions) => void;
explorerUrl: string;
}> = ({ isLoading, items, onItemActionClick, explorerUrl }) => {
@@ -132,6 +150,15 @@ export const DelegationList: React.FC<{
setOrderBy(property);
};
const getRewardValue = (item: DelegationWithEvent) => {
if (isPendingDelegation(item)) {
return '';
}
// eslint-disable-next-line @typescript-eslint/naming-convention
const { accumulated_rewards } = item;
return !accumulated_rewards ? '-' : `${accumulated_rewards.amount} ${accumulated_rewards.denom}`;
};
return (
<TableContainer>
<Table sx={{ width: '100%' }}>
@@ -149,12 +176,19 @@ export const DelegationList: React.FC<{
noIcon
/>
</TableCell>
<TableCell>{!item.avg_uptime_percent ? '-' : `${item.avg_uptime_percent}%`}</TableCell>
<TableCell>{!item.profit_margin_percent ? '-' : `${item.profit_margin_percent}%`}</TableCell>
<TableCell>
{!item.stake_saturation ? '-' : `${Math.round(item.stake_saturation * 100000) / 1000}%`}
{!isPendingDelegation(item) && (!item.avg_uptime_percent ? '-' : `${item.avg_uptime_percent}%`)}
</TableCell>
<TableCell>
{!isPendingDelegation(item) && (!item.profit_margin_percent ? '-' : `${item.profit_margin_percent}%`)}
</TableCell>
<TableCell>
{!isPendingDelegation(item) &&
(!item.stake_saturation ? '-' : `${Math.round(item.stake_saturation * 100000) / 1000}%`)}
</TableCell>
<TableCell>
{!isPendingDelegation(item) && format(new Date(item.delegated_on_iso_datetime), 'dd/MM/yyyy')}
</TableCell>
<TableCell>{format(new Date(item.delegated_on_iso_datetime), 'dd/MM/yyyy')}</TableCell>
<TableCell>
<Tooltip
placement="right"
@@ -169,55 +203,65 @@ export const DelegationList: React.FC<{
</TableRow>
</TableHead>
<TableBody>
{item.history.map((historyItem) => (
<TableRow key={`${historyItem.block_height}`}>
<StyledTooltipTableCell>
{formatDistanceToNow(parseISO(historyItem.delegated_on_iso_datetime), {
addSuffix: true,
})}
</StyledTooltipTableCell>
<StyledTooltipTableCell>
<Typography fontSize="inherit" noWrap>
{`${historyItem.amount.amount} ${historyItem.amount.denom}`}
{historyItem.uses_vesting_contract_tokens && (
<LockOutlinedIcon fontSize="inherit" sx={{ ml: 0.5 }} />
)}
</Typography>
</StyledTooltipTableCell>
<StyledTooltipTableCell>{historyItem.block_height}</StyledTooltipTableCell>
</TableRow>
))}
{!isPendingDelegation(item) &&
item.history.map((historyItem) => (
<TableRow key={`${historyItem.block_height}`}>
<StyledTooltipTableCell>
{formatDistanceToNow(parseISO(historyItem.delegated_on_iso_datetime), {
addSuffix: true,
})}
</StyledTooltipTableCell>
<StyledTooltipTableCell>
<Typography fontSize="inherit" noWrap>
{`${historyItem.amount.amount} ${historyItem.amount.denom}`}
{historyItem.uses_vesting_contract_tokens && (
<LockOutlinedIcon fontSize="inherit" sx={{ ml: 0.5 }} />
)}
</Typography>
</StyledTooltipTableCell>
<StyledTooltipTableCell>{historyItem.block_height}</StyledTooltipTableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
}
arrow
>
<span
style={{ cursor: 'pointer', textTransform: 'uppercase' }}
>{`${item.amount.amount} ${item.amount.denom}`}</span>
<span style={{ cursor: 'pointer', textTransform: 'uppercase' }}>
{!isPendingDelegation(item) && `${item.amount.amount} ${item.amount.denom}`}
</span>
</Tooltip>
</TableCell>
<TableCell sx={{ textTransform: 'uppercase' }}>
{!item.accumulated_rewards
? '-'
: `${item.accumulated_rewards.amount} ${item.accumulated_rewards.denom}`}
</TableCell>
<TableCell sx={{ textTransform: 'uppercase' }}>{getRewardValue(item)}</TableCell>
<TableCell align="right">
{!item.pending_events.length ? (
{!isPendingDelegation(item) && !item.pending_events.length && (
<DelegationsActionsMenu
isPending={undefined}
onActionClick={(action) => (onItemActionClick ? onItemActionClick(item, action) : undefined)}
disableRedeemingRewards={!item.accumulated_rewards || item.accumulated_rewards.amount === '0'}
disableCompoundRewards={!item.accumulated_rewards || item.accumulated_rewards.amount === '0'}
/>
) : (
)}
{!isPendingDelegation(item) && item.pending_events.length > 0 && (
<Tooltip
title="There will be a new epoch roughly every hour when your changes will take effect"
title="Your changes will take effect when
the new epoch starts. There is a new
epoch every hour."
arrow
>
<Chip label="Pending events" />
<Chip label="Pending Events" />
</Tooltip>
)}
{isPendingDelegation(item) && (
<Tooltip
title={`Your delegation of ${item.amount?.amount} ${item.amount?.denom} will take effect
when the new epoch starts. There is a new
epoch every hour.`}
arrow
>
<Chip label="Pending Events" />
</Tooltip>
)}
</TableCell>
@@ -1,160 +0,0 @@
import React, { FC } from 'react';
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
import {
Box,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TableSortLabel,
Tooltip,
Typography,
} from '@mui/material';
import { CopyToClipboard } from '@nymproject/react/clipboard/CopyToClipboard';
import { DelegationEvent } from '@nymproject/types';
import { ArrowDropDown } from '@mui/icons-material';
import { visuallyHidden } from '@mui/utils';
import { Link } from '@nymproject/react/link/Link';
type Order = 'asc' | 'desc';
interface HeadCell {
id: keyof DelegationEvent;
label: string;
sortable: boolean;
disablePadding?: boolean;
}
interface EnhancedTableProps {
onRequestSort: (event: React.MouseEvent<unknown>, property: keyof DelegationEvent) => void;
order: Order;
orderBy: string;
}
const headCells: HeadCell[] = [
{ id: 'node_identity', label: 'Node ID', sortable: true },
{ id: 'amount', label: 'Delegation', sortable: true },
{ id: 'kind', label: 'Type', sortable: true },
];
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
if (b[orderBy] < a[orderBy]) {
return -1;
}
if (b[orderBy] > a[orderBy]) {
return 1;
}
return 0;
}
function getComparator<Key extends keyof DelegationEvent>(
order: Order,
orderBy: Key,
): (a: DelegationEvent, b: DelegationEvent) => number {
return order === 'desc'
? (a, b) => descendingComparator(a, b, orderBy)
: (a, b) => -descendingComparator(a, b, orderBy);
}
const EnhancedTableHead: React.FC<EnhancedTableProps> = ({ order, orderBy, onRequestSort }) => {
const createSortHandler = (property: keyof DelegationEvent) => (event: React.MouseEvent<unknown>) => {
onRequestSort(event, property);
};
return (
<TableHead>
<TableRow>
{headCells.map((headCell) => (
<TableCell
key={headCell.id}
align="left"
padding={headCell.disablePadding ? 'none' : 'normal'}
sortDirection={orderBy === headCell.id ? order : false}
color="secondary"
>
<TableSortLabel
active={orderBy === headCell.id}
direction={orderBy === headCell.id ? order : 'asc'}
onClick={createSortHandler(headCell.id)}
IconComponent={ArrowDropDown}
>
{headCell.label}
{orderBy === headCell.id ? (
<Box component="span" sx={visuallyHidden}>
{order === 'desc' ? 'sorted descending' : 'sorted ascending'}
</Box>
) : null}
</TableSortLabel>
</TableCell>
))}
</TableRow>
</TableHead>
);
};
export const PendingEvents: FC<{ pendingEvents: DelegationEvent[]; explorerUrl: string }> = ({
pendingEvents,
explorerUrl,
}) => {
const [order, setOrder] = React.useState<Order>('asc');
const [orderBy, setOrderBy] = React.useState<keyof DelegationEvent>('node_identity');
const handleRequestSort = (event: React.MouseEvent<unknown>, property: keyof DelegationEvent) => {
const isAsc = orderBy === property && order === 'asc';
setOrder(isAsc ? 'desc' : 'asc');
setOrderBy(property);
};
if (pendingEvents.length === 0) return <Typography>No pending events</Typography>;
return (
<TableContainer>
<Table sx={{ width: '100%' }}>
<EnhancedTableHead order={order} orderBy={orderBy} onRequestSort={handleRequestSort} />
<TableBody>
{pendingEvents.sort(getComparator(order, orderBy)).map((item) => (
<TableRow key={`${item.node_identity}-${item.block_height}`}>
<TableCell>
<CopyToClipboard
sx={{ fontSize: 16, mr: 1 }}
value={item.node_identity}
tooltip={
<>
Copy identity key <strong>{item.node_identity}</strong> to clipboard
</>
}
/>
<Tooltip
title={
<>
Click to view <strong>{item.node_identity}</strong> in the Network Explorer
</>
}
placement="right"
arrow
>
<Link
target="_blank"
href={`${explorerUrl}/network-components/mixnode/${item.node_identity}`}
text={`${item.node_identity.slice(0, 6)}...${item.node_identity.slice(-6)}`}
/>
</Tooltip>
</TableCell>
<TableCell>{!item.amount ? '-' : `${item.amount?.amount} ${item.amount?.denom.toUpperCase()}`}</TableCell>
<TableCell>
{item.kind === 'Delegate' ? 'Delegation' : 'Undelegation'}
{item.proxy && (
<Tooltip title="Uses tokens for your vesting account" arrow>
<LockOutlinedIcon fontSize="inherit" sx={{ ml: 0.5 }} />
</Tooltip>
)}
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
);
};
@@ -1,9 +1,6 @@
import React from 'react';
import { Box, Stack, Typography, TypographyProps } from '@mui/material';
import { ModalDivider } from './ModalDivider';
import { fontWeight } from '@mui/system';
type TFontWeight = 'strong' | 'light';
export const ModalListItem: React.FC<{
label: string;
+14 -19
View File
@@ -1,6 +1,6 @@
import { FeeDetails, DecCoin, MixnodeStatus, TransactionExecuteResult } from '@nymproject/types';
import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { isGateway, isMixnode, Network, TBondGatewayArgs, TBondMixNodeArgs } from 'src/types';
import { isGateway, isMixnode, TBondGatewayArgs, TBondMixNodeArgs } from 'src/types';
import { Console } from 'src/utils/console';
import {
bondGateway as bondGatewayRequest,
@@ -28,19 +28,6 @@ import {
import { useCheckOwnership } from '../hooks/useCheckOwnership';
import { AppContext } from './main';
const bonded: TBondedMixnode = {
name: 'Monster node',
identityKey: 'B2Xx4haarLWMajX8w259oHjtRZsC7nHwagbWrJNiA3QC',
bond: { denom: 'nym', amount: '1234' },
delegators: 123,
operatorRewards: { denom: 'nym', amount: '12' },
profitMargin: 10,
stake: { denom: 'nym', amount: '99' },
stakeSaturation: 99,
status: 'active',
};
// TODO add relevant data
export type TBondedMixnode = {
name?: string;
identityKey: string;
@@ -48,13 +35,12 @@ export type TBondedMixnode = {
bond: DecCoin;
stakeSaturation: number;
profitMargin: number;
operatorRewards: DecCoin;
operatorRewards?: DecCoin;
delegators: number;
status: MixnodeStatus;
proxy?: string;
};
// TODO add relevant data
export interface TBondedGateway {
name: string;
identityKey: string;
@@ -132,7 +118,6 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
stakeSaturation: 0,
numberOfDelegators: 0,
};
try {
const statusResponse = await getMixnodeStatus(identityKey);
additionalDetails.status = statusResponse.status;
@@ -173,7 +158,12 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
if (ownership.hasOwnership && ownership.nodeType === 'mixnode' && clientDetails) {
try {
const data = await getMixnodeBondDetails();
const operatorRewards = await getOperatorRewards(clientDetails?.client_address);
let operatorRewards;
try {
operatorRewards = await getOperatorRewards(clientDetails?.client_address);
} catch (e) {
Console.warn(`get_operator_rewards request failed: ${e}`);
}
if (data) {
const { status, stakeSaturation, numberOfDelegators } = await getAdditionalMixnodeDetails(
data.mix_node.identity_key,
@@ -195,6 +185,7 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
} as TBondedMixnode);
}
} catch (e: any) {
Console.warn(e);
setError(`While fetching current bond state, an error occurred: ${e}`);
}
}
@@ -211,11 +202,11 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
ip: data.gateway.host,
location: data.gateway.location,
bond: data.pledge_amount,
delegators: bonded.delegators,
proxy: data.proxy,
} as TBondedGateway);
}
} catch (e: any) {
Console.warn(e);
setError(`While fetching current bond state, an error occurred: ${e}`);
}
}
@@ -244,6 +235,7 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
}
return tx;
} catch (e: any) {
Console.warn(e);
setError(`an error occurred: ${e}`);
} finally {
setIsLoading(false);
@@ -265,6 +257,7 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
}
return tx;
} catch (e: any) {
Console.warn(e);
setError(`an error occurred: ${e}`);
} finally {
setIsLoading(false);
@@ -281,6 +274,7 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
if (bondedNode && isGateway(bondedNode) && bondedNode.proxy) tx = await vestingUnbondGateway(fee?.fee);
if (bondedNode && isGateway(bondedNode) && !bondedNode.proxy) tx = await unbondGatewayRequest(fee?.fee);
} catch (e) {
Console.warn(e);
setError(`an error occurred: ${e as string}`);
} finally {
setIsLoading(false);
@@ -295,6 +289,7 @@ export const BondingContextProvider = ({ children }: { children?: React.ReactNod
if (bondedNode?.proxy) tx = await updateMixnodeVestingRequest(pm, fee?.fee);
else tx = await updateMixnodeRequest(pm, fee?.fee);
} catch (e: any) {
Console.warn(e);
setError(`an error occurred: ${e}`);
} finally {
setIsLoading(false);
+14 -3
View File
@@ -14,7 +14,7 @@ import { TPoolOption } from 'src/components';
export type TDelegationContext = {
isLoading: boolean;
error?: string;
delegations?: DelegationWithEverything[];
delegations?: TDelegations;
pendingDelegations?: DelegationEvent[];
totalDelegations?: string;
totalRewards?: string;
@@ -35,6 +35,12 @@ export type TDelegationTransaction = {
transactionUrl: string;
};
export type DelegationWithEvent = DelegationWithEverything | DelegationEvent;
export type TDelegations = DelegationWithEvent[];
export const isPendingDelegation = (delegation: DelegationWithEvent): delegation is DelegationEvent =>
'kind' in delegation;
export const DelegationContext = createContext<TDelegationContext>({
isLoading: true,
refresh: async () => undefined,
@@ -51,7 +57,7 @@ export const DelegationContextProvider: FC<{
}> = ({ network, children }) => {
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string>();
const [delegations, setDelegations] = useState<undefined | DelegationWithEverything[]>();
const [delegations, setDelegations] = useState<undefined | TDelegations>();
const [totalDelegations, setTotalDelegations] = useState<undefined | string>();
const [totalRewards, setTotalRewards] = useState<undefined | string>();
const [pendingDelegations, setPendingDelegations] = useState<DelegationEvent[]>();
@@ -87,8 +93,13 @@ export const DelegationContextProvider: FC<{
const data = await getDelegationSummary();
const pending = await getAllPendingDelegations();
const pendingOnNewNodes = pending.filter((event) => {
const some = data.delegations.some(({ node_identity }) => node_identity === event.node_identity);
return !some;
});
setPendingDelegations(pending);
setDelegations(data.delegations);
setDelegations([...data.delegations, ...pendingOnNewNodes]);
setTotalDelegations(`${data.total_delegations.amount} ${data.total_delegations.denom}`);
setTotalRewards(`${data.total_rewards.amount} ${data.total_rewards.denom}`);
} catch (e) {
+3 -3
View File
@@ -1,5 +1,5 @@
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { TransactionExecuteResult } from '@nymproject/types';
import { DelegationWithEverything, TransactionExecuteResult } from '@nymproject/types';
import { RewardsContext, TRewardsTransaction } from '../rewards';
import { useDelegationContext } from '../delegations';
import { mockSleep } from './utils';
@@ -9,7 +9,7 @@ export const MockRewardsContextProvider: FC = ({ children }) => {
const [error, setError] = useState<string>();
const [totalRewards, setTotalRewards] = useState<undefined | string>();
const { delegations } = useDelegationContext();
const delegationsHash = delegations?.map((d) => d.accumulated_rewards).join(',');
const delegationsHash = delegations?.map((d) => (d as DelegationWithEverything).accumulated_rewards).join(',');
const resetState = () => {
setIsLoading(true);
@@ -19,7 +19,7 @@ export const MockRewardsContextProvider: FC = ({ children }) => {
const recalculate = () => {
const sum: number | undefined = delegations
?.map((d) => (d.accumulated_rewards ? Number(10) : Number(0)))
?.map((d) => ((d as DelegationWithEverything).accumulated_rewards ? Number(10) : Number(0)))
.reduce((acc, cur) => acc + cur, Number(0));
setTotalRewards(sum ? `${sum} NYM` : undefined);
+16 -5
View File
@@ -1,4 +1,4 @@
import React, { useContext, useState } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { FeeDetails } from '@nymproject/types';
import { TPoolOption } from 'src/components';
import { Bond } from 'src/components/Bonding/Bond';
@@ -16,9 +16,8 @@ import { isGateway, isMixnode, TBondGatewayArgs, TBondMixNodeArgs } from 'src/ty
import { BondedGateway } from 'src/components/Bonding/BondedGateway';
import { RedeemRewardsModal } from 'src/components/Bonding/modals/RedeemRewardsModal';
import { CompoundRewardsModal } from 'src/components/Bonding/modals/CompoundRewardsModal';
import { PageLayout } from '../../layouts';
import { BondingContextProvider, useBondingContext } from '../../context';
import { Box } from '@mui/material';
import { BondingContextProvider, useBondingContext } from '../../context';
const Bonding = () => {
const [showModal, setShowModal] = useState<
@@ -42,19 +41,31 @@ const Bonding = () => {
compoundRewards,
isLoading,
checkOwnership,
error,
} = useBondingContext();
useEffect(() => {
if (error) {
setShowModal(undefined);
setConfirmationDetails({
status: 'error',
title: 'An error occurred',
subtitle: error,
});
}
}, [error]);
const handleCloseModal = async () => {
setShowModal(undefined);
await checkOwnership();
};
const handleError = (error: string) => {
const handleError = (e: string) => {
setShowModal(undefined);
setConfirmationDetails({
status: 'error',
title: 'An error occurred',
subtitle: error,
subtitle: e,
});
};
-11
View File
@@ -5,7 +5,6 @@ import { DelegationWithEverything, FeeDetails, DecCoin } from '@nymproject/types
import { Link } from '@nymproject/react/link/Link';
import { AppContext, urls } from 'src/context/main';
import { DelegationList } from 'src/components/Delegation/DelegationList';
import { PendingEvents } from 'src/components/Delegation/PendingEvents';
import { TPoolOption } from 'src/components';
import { Console } from 'src/utils/console';
import { CompoundModal } from 'src/components/Rewards/CompoundModal';
@@ -49,7 +48,6 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
const {
delegations,
pendingDelegations,
totalDelegations,
totalRewards,
isLoading,
@@ -322,15 +320,6 @@ export const Delegation: FC<{ isStorybook?: boolean }> = ({ isStorybook }) => {
</Stack>
</Paper>
{pendingDelegations && (
<Paper elevation={0} sx={{ p: 3, mt: 2 }}>
<Stack spacing={5}>
<Typography variant="h6">Pending Delegation Events</Typography>
<PendingEvents pendingEvents={pendingDelegations} explorerUrl={urls(network).networkExplorer} />
</Stack>
</Paper>
)}
{showNewDelegationModal && (
<DelegateModal
open={showNewDelegationModal}
@@ -30,14 +30,18 @@ const DataField = ({ title, info, Indicator }: { title: string; info: string; In
);
const colorMap: { [key in SelectionChance]: string } = {
VeryLow: 'error.main',
Low: 'error.main',
Moderate: 'warning.main',
High: 'success.main',
VeryHigh: 'success.main',
};
const textMap: { [key in SelectionChance]: string } = {
VeryLow: 'VeryLow',
Low: 'Low',
Moderate: 'Moderate',
High: 'High',
VeryHigh: 'Very high',
};
@@ -3,7 +3,7 @@
[package]
name = "nym-network-requester"
version = "1.0.1"
version = "1.0.2"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2021"
@@ -1,6 +1,6 @@
[package]
name = "nym-network-statistics"
version = "0.1.0"
version = "1.0.2"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+10 -9
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-validator-api"
version = "1.0.1"
version = "1.0.2"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
@@ -16,7 +16,9 @@ rust-version = "1.56"
[dependencies]
async-trait = "0.1.52"
cfg-if = "1.0"
clap = "2.33.0"
console-subscriber = { version = "0.1.1", optional = true} # validator-api needs to be built with RUSTFLAGS="--cfg tokio_unstable"
dirs = "4.0"
dotenv = "0.15.0"
futures = "0.3"
@@ -31,6 +33,7 @@ rocket = { version = "0.5.0-rc.2", features = ["json"] }
rocket_cors = { git="https://github.com/lawliet89/rocket_cors", rev="dfd3662c49e2f6fc37df35091cb94d82f7fb5915" }
serde = "1.0"
serde_json = "1.0"
tap = "1.0.1"
thiserror = "1"
time = { version = "0.3", features = ["serde-human-readable", "parsing"]}
tokio = { version = "1.19.1", features = ["rt-multi-thread", "macros", "signal", "time"] }
@@ -51,27 +54,26 @@ schemars = { version = "0.8", features = ["preserve_order"] }
## internal
coconut-bandwidth-contract-common = { path = "../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
coconut-interface = { path = "../common/coconut-interface", optional = true }
config = { path = "../common/config" }
cosmwasm-std = "1.0.0"
credential-storage = { path = "../common/credential-storage" }
credentials = { path = "../common/credentials", optional = true }
crypto = { path="../common/crypto" }
gateway-client = { path="../common/client-libs/gateway-client" }
inclusion-probability = { path = "../common/inclusion-probability" }
mixnet-contract-common = { path= "../common/cosmwasm-smart-contracts/mixnet-contract" }
multisig-contract-common = { path = "../common/cosmwasm-smart-contracts/multisig-contract" }
nymcoconut = { path = "../common/nymcoconut", optional = true }
nymsphinx = { path="../common/nymsphinx" }
task = { path = "../common/task" }
topology = { path="../common/topology" }
validator-api-requests = { path = "validator-api-requests" }
validator-client = { path="../common/client-libs/validator-client", features = ["nymd-client"] }
version-checker = { path="../common/version-checker" }
coconut-interface = { path = "../common/coconut-interface", optional = true }
credentials = { path = "../common/credentials", optional = true }
credential-storage = { path = "../common/credential-storage" }
# validator-api needs to be built with RUSTFLAGS="--cfg tokio_unstable"
console-subscriber = { version = "0.1.1", optional = true}
cfg-if = "1.0"
[features]
coconut = ["coconut-interface", "credentials", "gateway-client/coconut", "credentials/coconut", "validator-api-requests/coconut"]
coconut = ["coconut-interface", "credentials", "gateway-client/coconut", "credentials/coconut", "validator-api-requests/coconut", "nymcoconut"]
no-reward = []
generate-ts = []
@@ -82,6 +84,5 @@ vergen = { version = "5", default-features = false, features = ["build", "git",
[dev-dependencies]
attohttpc = {version = "0.18.0", features = ["json"]}
nymcoconut = { path = "../common/nymcoconut" }
cw3 = "0.13.2"
cw-utils = "0.13.2"
+3
View File
@@ -31,6 +31,9 @@ pub enum CoconutError {
#[error("Nymd error - {0}")]
NymdError(#[from] NymdError),
#[error("Coconut internal error - {0}")]
CoconutInternalError(#[from] nymcoconut::CoconutError),
#[error("Could not find a deposit event in the transaction provided")]
DepositEventNotFound,
+5 -6
View File
@@ -192,15 +192,14 @@ impl InternalSignRequest {
}
}
fn blind_sign(request: InternalSignRequest, key_pair: &KeyPair) -> BlindedSignature {
let params = Parameters::new(request.total_params()).unwrap();
coconut_interface::blind_sign(
fn blind_sign(request: InternalSignRequest, key_pair: &KeyPair) -> Result<BlindedSignature> {
let params = Parameters::new(request.total_params())?;
Ok(coconut_interface::blind_sign(
&params,
&key_pair.secret_key(),
request.blind_sign_request(),
request.public_attributes(),
)
.unwrap()
)?)
}
#[post("/blind-sign", data = "<blind_sign_request_body>")]
@@ -226,7 +225,7 @@ pub async fn post_blind_sign(
blind_sign_request_body.public_attributes(),
blind_sign_request_body.blind_sign_request().clone(),
);
let blinded_signature = blind_sign(internal_request, &state.key_pair);
let blinded_signature = blind_sign(internal_request, &state.key_pair)?;
let response = state
.encrypt_and_store(
+24 -4
View File
@@ -23,7 +23,7 @@ use std::collections::HashSet;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;
use tokio::sync::{watch, RwLock};
use tokio::time;
use validator_api_requests::models::{MixNodeBondAnnotated, MixnodeStatus};
use validator_client::nymd::CosmWasmClient;
@@ -31,6 +31,13 @@ use validator_client::nymd::CosmWasmClient;
pub(crate) mod reward_estimate;
pub(crate) mod routes;
// The cache can emit notifications to listeners about the current state
#[derive(Debug, PartialEq, Eq)]
pub enum CacheNotification {
Start,
Updated,
}
pub struct ValidatorCacheRefresher<C> {
nymd_client: Client<C>,
cache: ValidatorCache,
@@ -38,6 +45,9 @@ pub struct ValidatorCacheRefresher<C> {
// Readonly: some of the quantities cached depends on values from the storage.
storage: Option<ValidatorApiStorage>,
// Notify listeners that the cache has been updated
update_notifier: watch::Sender<CacheNotification>,
}
#[derive(Clone)]
@@ -73,14 +83,14 @@ pub struct Cache<T> {
}
impl<T: Clone> Cache<T> {
fn new(value: T) -> Self {
pub(super) fn new(value: T) -> Self {
Cache {
value,
as_at: current_unix_timestamp(),
}
}
fn update(&mut self, value: T) {
pub(super) fn update(&mut self, value: T) {
self.value = value;
self.as_at = current_unix_timestamp()
}
@@ -101,11 +111,13 @@ impl<C> ValidatorCacheRefresher<C> {
cache: ValidatorCache,
storage: Option<ValidatorApiStorage>,
) -> Self {
let (tx, _) = watch::channel(CacheNotification::Start);
ValidatorCacheRefresher {
nymd_client,
cache,
caching_interval,
storage,
update_notifier: tx,
}
}
@@ -117,6 +129,10 @@ impl<C> ValidatorCacheRefresher<C> {
.ok()
}
pub fn subscribe(&self) -> watch::Receiver<CacheNotification> {
self.update_notifier.subscribe()
}
async fn annotate_bond_with_details(
&self,
mixnodes: Vec<MixNodeBond>,
@@ -250,6 +266,10 @@ impl<C> ValidatorCacheRefresher<C> {
)
.await;
if let Err(err) = self.update_notifier.send(CacheNotification::Updated) {
warn!("Failed to notify validator cache refresh: {}", err);
}
Ok(())
}
@@ -271,7 +291,7 @@ impl<C> ValidatorCacheRefresher<C> {
}
}
_ = shutdown.recv() => {
trace!("UpdateHandler: Received shutdown");
trace!("ValidatorCacheRefresher: Received shutdown");
}
}
}
+29 -7
View File
@@ -19,6 +19,7 @@ use anyhow::Result;
use clap::{crate_version, App, Arg, ArgMatches};
use contract_cache::ValidatorCache;
use log::{info, warn};
use node_status_api::NodeStatusCache;
use okapi::openapi3::OpenApi;
use rocket::fairing::AdHoc;
use rocket::http::Method;
@@ -470,7 +471,8 @@ async fn setup_rocket(
.mount("/swagger", make_swagger_ui(&swagger::get_docs()))
.attach(setup_cors()?)
.attach(setup_liftoff_notify(liftoff_notify))
.attach(ValidatorCache::stage());
.attach(ValidatorCache::stage())
.attach(NodeStatusCache::stage());
// This is not a very nice approach. A lazy value would be more suitable, but that's still
// a nightly feature: https://github.com/rust-lang/rust/issues/74465
@@ -579,10 +581,11 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> {
let monitor_builder = setup_network_monitor(&config, system_version, &rocket);
let validator_cache = rocket.state::<ValidatorCache>().unwrap().clone();
let node_status_cache = rocket.state::<NodeStatusCache>().unwrap().clone();
// if network monitor is disabled, we're not going to be sending any rewarding hence
// we're not starting signing client
if config.get_network_monitor_enabled() {
let validator_cache_listener = if config.get_network_monitor_enabled() {
// Main storage
let storage = rocket.state::<ValidatorApiStorage>().unwrap().clone();
@@ -592,13 +595,14 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> {
let shutdown_listener = shutdown.subscribe();
tokio::spawn(async move { uptime_updater.run(shutdown_listener).await });
// spawn the cache refresher
// spawn the validator cache refresher
let validator_cache_refresher = ValidatorCacheRefresher::new(
signing_nymd_client.clone(),
config.get_caching_interval(),
validator_cache.clone(),
Some(storage.clone()),
);
let validator_cache_listener = validator_cache_refresher.subscribe();
let shutdown_listener = shutdown.subscribe();
tokio::spawn(async move { validator_cache_refresher.run(shutdown_listener).await });
@@ -606,19 +610,37 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> {
let mut rewarded_set_updater =
RewardedSetUpdater::new(signing_nymd_client, validator_cache.clone(), storage).await?;
tokio::spawn(async move { rewarded_set_updater.run().await.unwrap() });
validator_cache_listener
} else {
// Spawn the validator cache refresher.
// When the network monitor is not enabled, we spawn the validator cache refresher task
// with just a nymd client, in contrast to a signing client.
let nymd_client = Client::new_query(&config);
let validator_cache_refresher = ValidatorCacheRefresher::new(
nymd_client,
config.get_caching_interval(),
validator_cache,
validator_cache.clone(),
None,
);
let validator_cache_listener = validator_cache_refresher.subscribe();
let shutdown_listener = shutdown.subscribe();
// spawn our cacher
tokio::spawn(async move { validator_cache_refresher.run(shutdown_listener).await });
}
validator_cache_listener
};
// Spawn the node status cache refresher.
// It is primarily refreshed in-sync with the validator cache, however provide a fallback
// caching interval that is twice the validator cache
let mut validator_api_cache_refresher = node_status_api::NodeStatusCacheRefresher::new(
node_status_cache,
validator_cache,
validator_cache_listener,
config.get_caching_interval().saturating_mul(2),
);
let shutdown_listener = shutdown.subscribe();
tokio::spawn(async move { validator_api_cache_refresher.run(shutdown_listener).await });
// launch the rocket!
// Rocket handles shutdown on it's own, but its shutdown handling should be incorporated
+244
View File
@@ -0,0 +1,244 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use rocket::fairing::AdHoc;
use serde::Serialize;
use tap::TapFallible;
use tokio::{
sync::{watch, RwLock},
time,
};
use std::{sync::Arc, time::Duration};
use mixnet_contract_common::{reward_params::EpochRewardParams, MixNodeBond};
use task::ShutdownListener;
use validator_api_requests::models::InclusionProbability;
use crate::contract_cache::{Cache, CacheNotification, ValidatorCache};
const CACHE_TIMOUT_MS: u64 = 100;
const MAX_SIMULATION_SAMPLES: u64 = 5000;
const MAX_SIMULATION_TIME_SEC: u64 = 15;
enum NodeStatusCacheError {
SimulationFailed,
}
// A node status cache suitable for caching values computed in one sweep, such as active set
// inclusion probabilities that are computed for all mixnodes at the same time.
//
// The cache can be triggered to update on contract cache changes, and/or periodically on a timer.
#[derive(Clone)]
pub struct NodeStatusCache {
inner: Arc<RwLock<NodeStatusCacheInner>>,
}
struct NodeStatusCacheInner {
inclusion_probabilities: Cache<InclusionProbabilities>,
}
#[derive(Clone, Default, Serialize, schemars::JsonSchema)]
pub(crate) struct InclusionProbabilities {
pub inclusion_probabilities: Vec<InclusionProbability>,
pub samples: u64,
pub elapsed: Duration,
pub delta_max: f64,
pub delta_l2: f64,
}
impl InclusionProbabilities {
pub fn node(&self, id: &str) -> Option<&InclusionProbability> {
self.inclusion_probabilities.iter().find(|x| x.id == id)
}
}
impl NodeStatusCache {
fn new() -> Self {
NodeStatusCache {
inner: Arc::new(RwLock::new(NodeStatusCacheInner::new())),
}
}
pub fn stage() -> AdHoc {
AdHoc::on_ignite("Node Status Cache", |rocket| async {
rocket.manage(Self::new())
})
}
async fn update_cache(&self, inclusion_probabilities: InclusionProbabilities) {
match time::timeout(Duration::from_millis(CACHE_TIMOUT_MS), self.inner.write()).await {
Ok(mut cache) => {
cache
.inclusion_probabilities
.update(inclusion_probabilities);
}
Err(e) => error!("{e}"),
}
}
pub(crate) async fn inclusion_probabilities(&self) -> Option<Cache<InclusionProbabilities>> {
match time::timeout(Duration::from_millis(CACHE_TIMOUT_MS), self.inner.read()).await {
Ok(cache) => Some(cache.inclusion_probabilities.clone()),
Err(e) => {
error!("{e}");
None
}
}
}
}
impl NodeStatusCacheInner {
pub fn new() -> Self {
Self {
inclusion_probabilities: Default::default(),
}
}
}
// Long running task responsible of keeping the cache up-to-date.
pub struct NodeStatusCacheRefresher {
cache: NodeStatusCache,
contract_cache: ValidatorCache,
contract_cache_listener: watch::Receiver<CacheNotification>,
fallback_caching_interval: Duration,
}
impl NodeStatusCacheRefresher {
pub(crate) fn new(
cache: NodeStatusCache,
contract_cache: ValidatorCache,
contract_cache_listener: watch::Receiver<CacheNotification>,
fallback_caching_interval: Duration,
) -> Self {
Self {
cache,
contract_cache,
contract_cache_listener,
fallback_caching_interval,
}
}
pub async fn run(&mut self, mut shutdown: ShutdownListener) {
let mut fallback_interval = time::interval(self.fallback_caching_interval);
while !shutdown.is_shutdown() {
tokio::select! {
// Update node status cache when the contract cache / validator cache is updated
Ok(_) = self.contract_cache_listener.changed() => {
self.update_on_notify(&mut fallback_interval).await;
}
// ... however, if we don't receive any notifications we fall back to periodic
// refreshes
_ = fallback_interval.tick() => {
self.update_on_timer().await;
}
_ = shutdown.recv() => {
log::trace!("NodeStatusCacheRefresher: Received shutdown");
}
}
}
log::info!("NodeStatusCacheRefresher: Exiting");
}
async fn update_on_notify(&self, fallback_interval: &mut time::Interval) {
log::debug!(
"Validator cache event detected: {:?}",
&*self.contract_cache_listener.borrow(),
);
let _ = self.refresh_cache().await;
fallback_interval.reset();
}
async fn update_on_timer(&self) {
log::debug!("Timed trigger for the node status cache");
let have_contract_cache_data =
*self.contract_cache_listener.borrow() != CacheNotification::Start;
if have_contract_cache_data {
let _ = self.refresh_cache().await;
} else {
log::trace!(
"Skipping updating node status cache, is the contract cache not yet available?"
);
}
}
async fn refresh_cache(&self) -> Result<(), NodeStatusCacheError> {
log::info!("Updating node status cache");
let mixnode_bonds = self.contract_cache.mixnodes().await;
let params = self.contract_cache.epoch_reward_params().await.into_inner();
let inclusion_probabilities = compute_inclusion_probabilities(&mixnode_bonds, params)
.ok_or_else(|| {
error!(
"Failed to simulate selection probabilties for mixnodes, not updating cache"
);
NodeStatusCacheError::SimulationFailed
})?;
self.cache.update_cache(inclusion_probabilities).await;
Ok(())
}
}
fn compute_inclusion_probabilities(
mixnode_bonds: &[MixNodeBond],
params: EpochRewardParams,
) -> Option<InclusionProbabilities> {
let active_set_size = params
.active_set_size()
.try_into()
.tap_err(|e| error!("Active set size unexpectantly large: {e}"))
.ok()?;
let standby_set_size = (params.rewarded_set_size() - params.active_set_size())
.try_into()
.tap_err(|e| error!("Active set size larger than rewarded set size, a contradiction: {e}"))
.ok()?;
// Unzip list of total bonds into ids and bonds.
// We need to go through this zip/unzip procedure to make sure we have matching identities
// for the input to the simulator, which assumes the identity is the position in the vec
let (ids, mixnode_total_bonds) = unzip_into_mixnode_ids_and_total_bonds(mixnode_bonds);
// Compute inclusion probabilitites and keep track of how long time it took.
let results = inclusion_probability::simulate_selection_probability_mixnodes(
&mixnode_total_bonds,
active_set_size,
standby_set_size,
MAX_SIMULATION_SAMPLES,
Duration::from_secs(MAX_SIMULATION_TIME_SEC),
)
.tap_err(|err| error!("{err}"))
.ok()?;
Some(InclusionProbabilities {
inclusion_probabilities: zip_ids_together_with_results(&ids, &results),
samples: results.samples,
elapsed: results.time,
delta_max: results.delta_max,
delta_l2: results.delta_l2,
})
}
fn unzip_into_mixnode_ids_and_total_bonds(
mixnode_bonds: &[MixNodeBond],
) -> (Vec<&String>, Vec<u128>) {
mixnode_bonds
.iter()
.filter_map(|m| m.total_bond().map(|b| (m.identity(), b)))
.unzip()
}
fn zip_ids_together_with_results(
ids: &[&String],
results: &inclusion_probability::SelectionProbability,
) -> Vec<InclusionProbability> {
ids.iter()
.zip(results.active_set_probability.iter())
.zip(results.reserve_set_probability.iter())
.map(|((id, a), r)| InclusionProbability {
id: (*id).to_string(),
in_active: *a,
in_reserve: *r,
})
.collect()
}
+5
View File
@@ -1,11 +1,14 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub(crate) use cache::{NodeStatusCache, NodeStatusCacheRefresher};
use okapi::openapi3::OpenApi;
use rocket::Route;
use rocket_okapi::{openapi_get_routes_spec, settings::OpenApiSettings};
use std::time::Duration;
pub(crate) mod cache;
pub(crate) mod local_guard;
pub(crate) mod models;
pub(crate) mod routes;
@@ -35,6 +38,7 @@ pub(crate) fn node_status_routes(
routes::get_mixnode_inclusion_probability,
routes::get_mixnode_avg_uptime,
routes::get_mixnode_avg_uptimes,
routes::get_mixnode_inclusion_probabilities,
]
} else {
// in the minimal variant we would not have access to endpoints relying on existence
@@ -43,6 +47,7 @@ pub(crate) fn node_status_routes(
routes::get_mixnode_status,
routes::get_mixnode_stake_saturation,
routes::get_mixnode_inclusion_probability,
routes::get_mixnode_inclusion_probabilities,
]
}
}
+58 -38
View File
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::contract_cache::Cache;
use crate::node_status_api::models::{
ErrorResponse, GatewayStatusReport, GatewayUptimeHistory, MixnodeStatusReport,
MixnodeUptimeHistory,
@@ -16,11 +17,12 @@ use rocket_okapi::openapi;
use schemars::JsonSchema;
use serde::Deserialize;
use validator_api_requests::models::{
CoreNodeStatusResponse, InclusionProbabilityResponse, MixnodeStatusResponse,
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
AllInclusionProbabilitiesResponse, CoreNodeStatusResponse, InclusionProbabilityResponse,
MixnodeStatusResponse, RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
};
use super::models::Uptime;
use super::NodeStatusCache;
async fn average_mixnode_uptime(
identity: &str,
@@ -227,6 +229,12 @@ pub(crate) async fn compute_mixnode_reward_estimation(
// For these parameters we either use the provided ones, or fall back to the system ones
let uptime = if let Some(uptime) = user_reward_param.uptime {
if uptime > 100 {
return Err(ErrorResponse::new(
"Provided uptime out of bounds",
Status::UnprocessableEntity,
));
}
uptime
} else {
average_mixnode_uptime(&identity, current_epoch, storage)
@@ -245,13 +253,23 @@ pub(crate) async fn compute_mixnode_reward_estimation(
bond.mixnode_bond.total_delegation.amount = total_delegation.into();
}
if bond.mixnode_bond.pledge_amount.amount.u128()
+ bond.mixnode_bond.total_delegation.amount.u128()
> reward_params.staking_supply()
{
return Err(ErrorResponse::new(
"Pledge plus delegation too large",
Status::UnprocessableEntity,
));
}
let node_reward_params = NodeRewardParams::new(0, u128::from(uptime), is_active);
let reward_params = RewardParams::new(reward_params, node_reward_params);
estimate_reward(&bond.mixnode_bond, base_operator_cost, reward_params, as_at)
} else {
Err(ErrorResponse::new(
"mixnode bond not found",
"Mixnode bond not found",
Status::NotFound,
))
}
@@ -291,43 +309,21 @@ pub(crate) async fn get_mixnode_stake_saturation(
#[openapi(tag = "status")]
#[get("/mixnode/<identity>/inclusion-probability")]
pub(crate) async fn get_mixnode_inclusion_probability(
cache: &State<ValidatorCache>,
node_status_cache: &State<NodeStatusCache>,
identity: String,
) -> Json<Option<InclusionProbabilityResponse>> {
let mixnodes = cache.mixnodes().await;
let rewarding_params = cache.epoch_reward_params().await.into_inner();
if let Some(target_mixnode) = mixnodes.iter().find(|x| x.identity() == &identity) {
let total_bonded_tokens = mixnodes
.iter()
.fold(0u128, |acc, x| acc + x.total_bond().unwrap_or_default())
as f64;
let rewarded_set_size = rewarding_params.rewarded_set_size() as f64;
let active_set_size = rewarding_params.active_set_size() as f64;
let prob_one_draw =
target_mixnode.total_bond().unwrap_or_default() as f64 / total_bonded_tokens;
// Chance to be selected in any draw for active set
let prob_active_set = if mixnodes.len() <= active_set_size as usize {
1.0
} else {
active_set_size * prob_one_draw
};
// This is likely slightly too high, as we're not correcting form them not being selected in active, should be chance to be selected, minus the chance for being not selected in reserve
let prob_reserve_set = if mixnodes.len() <= rewarded_set_size as usize {
1.0
} else {
(rewarded_set_size - active_set_size) * prob_one_draw
};
Json(Some(InclusionProbabilityResponse {
in_active: prob_active_set.into(),
in_reserve: prob_reserve_set.into(),
}))
} else {
Json(None)
}
node_status_cache
.inclusion_probabilities()
.await
.map(Cache::into_inner)
.and_then(|p| p.node(&identity).cloned())
.map(|p| {
Json(Some(InclusionProbabilityResponse {
in_active: p.in_active.into(),
in_reserve: p.in_reserve.into(),
}))
})
.unwrap_or(Json(None))
}
#[openapi(tag = "status")]
@@ -368,3 +364,27 @@ pub(crate) async fn get_mixnode_avg_uptimes(
Ok(Json(response))
}
#[openapi(tag = "status")]
#[get("/mixnodes/inclusion_probability")]
pub(crate) async fn get_mixnode_inclusion_probabilities(
cache: &State<NodeStatusCache>,
) -> Result<Json<AllInclusionProbabilitiesResponse>, ErrorResponse> {
if let Some(prob) = cache.inclusion_probabilities().await {
let as_at = prob.timestamp();
let prob = prob.into_inner();
Ok(Json(AllInclusionProbabilitiesResponse {
inclusion_probabilities: prob.inclusion_probabilities,
samples: prob.samples,
elapsed: prob.elapsed,
delta_max: prob.delta_max,
delta_l2: prob.delta_l2,
as_at,
}))
} else {
Err(ErrorResponse::new(
"No data available".to_string(),
Status::ServiceUnavailable,
))
}
}
+2 -3
View File
@@ -364,15 +364,14 @@ impl<C> Client<C> {
let memo = "Performing epoch operations".to_string();
self.execute_multiple_with_retry(msgs, Default::default(), memo)
.await?;
self.execute_multiple_with_retry(msgs, None, memo).await?;
Ok(())
}
async fn execute_multiple_with_retry<M>(
&self,
msgs: Vec<(M, Vec<Coin>)>,
fee: Fee,
fee: Option<Fee>,
memo: String,
) -> Result<(), RewardingError>
where
+54 -11
View File
@@ -22,6 +22,7 @@ use mixnet_contract_common::{IdentityKey, Interval, MixNodeBond};
use rand::prelude::SliceRandom;
use rand::rngs::OsRng;
use std::collections::HashSet;
use std::ops::Deref;
use std::time::Duration;
use time::OffsetDateTime;
use tokio::time::sleep;
@@ -58,7 +59,44 @@ impl MixnodeToReward {
}
// Epoch has all the same semantics as interval, but has a lower set duration
type Epoch = Interval;
pub struct Epoch(Interval);
impl Epoch {
pub(crate) async fn update_to_latest(
&mut self,
rewarded_set_updater: &RewardedSetUpdater,
) -> Result<(), RewardingError> {
let mut wait_time = 2;
let mut ret = Ok(());
for _ in 0..3 {
match rewarded_set_updater.nymd_client.get_current_epoch().await {
Ok(epoch) => {
*self = Epoch(epoch);
return Ok(());
}
Err(e) => {
warn!(
"Could not update epoch: {:?}. Retrying again in {} seconds",
e, wait_time
);
ret = Err(e);
}
};
sleep(Duration::from_secs(wait_time)).await;
wait_time *= wait_time;
}
error!("Failed to update epoch. Exiting now");
Ok(ret?)
}
}
impl Deref for Epoch {
type Target = Interval;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct RewardedSetUpdater {
nymd_client: Client<SigningNymdClient>,
@@ -68,7 +106,7 @@ pub struct RewardedSetUpdater {
impl RewardedSetUpdater {
pub(crate) async fn epoch(&self) -> Result<Epoch, RewardingError> {
Ok(self.nymd_client.get_current_epoch().await?)
Ok(Epoch(self.nymd_client.get_current_epoch().await?))
}
pub(crate) async fn new(
@@ -120,9 +158,10 @@ impl RewardedSetUpdater {
async fn reward_current_rewarded_set(
&self,
epoch: &mut Epoch,
) -> Result<Vec<(ExecuteMsg, Vec<Coin>)>, RewardingError> {
let to_reward = self.nodes_to_reward().await?;
let epoch = self.epoch().await?;
let to_reward = self.nodes_to_reward(epoch).await?;
epoch.update_to_latest(self).await?;
// self.storage.insert_started_epoch_rewarding(epoch).await?;
@@ -157,8 +196,11 @@ impl RewardedSetUpdater {
}
}
async fn nodes_to_reward(&self) -> Result<Vec<MixnodeToReward>, RewardingError> {
let epoch = self.epoch().await?;
async fn nodes_to_reward(
&self,
epoch: &mut Epoch,
) -> Result<Vec<MixnodeToReward>, RewardingError> {
epoch.update_to_latest(self).await?;
let active_set = self
.validator_cache
.active_set_detailed()
@@ -200,8 +242,8 @@ impl RewardedSetUpdater {
}
// This is where the epoch gets advanced, and all epoch related transactions originate
async fn update(&self) -> Result<(), RewardingError> {
let epoch = self.epoch().await?;
async fn update(&self, epoch: &mut Epoch) -> Result<(), RewardingError> {
epoch.update_to_latest(self).await?;
log::info!("Starting rewarded set update");
// we know the entries are not stale, as a matter of fact they were JUST updated, since we got notified
let all_nodes = self.validator_cache.mixnodes().await;
@@ -218,7 +260,7 @@ impl RewardedSetUpdater {
// log::info!("Rewarded current rewarded set... SUCCESS");
// }
let reward_msgs = self.reward_current_rewarded_set().await?;
let reward_msgs = self.reward_current_rewarded_set(epoch).await?;
let rewarded_set_size = epoch_reward_params.rewarded_set_size() as u32;
let active_set_size = epoch_reward_params.active_set_size() as u32;
@@ -288,11 +330,12 @@ impl RewardedSetUpdater {
pub(crate) async fn run(&mut self) -> Result<(), RewardingError> {
self.validator_cache.wait_for_initial_values().await;
let mut epoch = self.epoch().await?;
loop {
// wait until the cache refresher determined its time to update the rewarded/active sets
let time = OffsetDateTime::now_utc().unix_timestamp();
let epoch = self.epoch().await?;
epoch.update_to_latest(self).await?;
let time_to_epoch_change = epoch.end_unix_timestamp() - time;
if time_to_epoch_change <= 0 {
self.update_blacklist(&epoch).await?;
@@ -300,7 +343,7 @@ impl RewardedSetUpdater {
"Time to epoch change is {}, updating rewarded set",
time_to_epoch_change
);
self.update().await?;
self.update(&mut epoch).await?;
} else {
log::info!(
"Waiting for epoch change, time to epoch change is {}",
+30
View File
@@ -0,0 +1,30 @@
<!-- ABOUT THE PROJECT -->
## validator-api-test suite
A Typescript test framework utilising Jest and Node to perform tests against the NYM validator-apis.
<!-- GETTING STARTED -->
## Getting Started
### Installation
1. Having `yarn` install on your machine
1. Install all the packages - `yarn install`
2. Peeping the package.json will show you the commands to run for the tests
<!-- USAGE EXAMPLES -->
## Usage
1. Run the testsuite - currently the configuration hasn't fully been set up to switch envs
```
yarn test
```
## ToDo
1. Finish happy and negative test scenarios
2. Full reporting
3. Fully functioniting env switching
4. Run in CI
5. Docker?
Kudos to jmfiola21@gmail.com for the baseline of this framework
@@ -0,0 +1,123 @@
import Status from "../../../src/endpoints/Status";
import ConfigHandler from "../../../src/config/configHandler";
let status: Status;
let config: ConfigHandler;
describe("Get mixnode data", (): void => {
beforeAll(async (): Promise<void> => {
status = new Status();
config = ConfigHandler.getInstance();
});
it("Get a mixnode stake saturation", async (): Promise<void> => {
const identity_key = config.environmnetConfig.mixnode_identity;
const response = await status.getMixnodeStakeSaturation(identity_key);
console.log(response.as_at);
console.log(response.saturation);
expect(typeof response.as_at).toBe("number");
expect(typeof response.saturation).toBe("number");
});
it("Get a mixnode average uptime", async (): Promise<void> => {
const identity_key = config.environmnetConfig.mixnode_identity;
const response = await status.getMixnodeAverageUptime(identity_key);
console.log(response.avg_uptime);
console.log(response.identity);
expect(identity_key).toStrictEqual(response.identity);
expect(typeof response.avg_uptime).toBe("number");
});
it("Get a mixnode history", async (): Promise<void> => {
const identity_key = config.environmnetConfig.mixnode_identity;
const response = await status.getMixnodeHistory(identity_key);
response.history.forEach((x) => {
console.log(x.date);
console.log(x.uptime);
});
console.log(response.identity);
console.log(response.owner);
expect(identity_key).toStrictEqual(response.identity);
expect(typeof response.owner).toBe("string");
});
it("Get a gateway history", async (): Promise<void> => {
const identity_key = config.environmnetConfig.gateway_identity;
const response = await status.getGatewayHistory(identity_key);
response.history.forEach((x) => {
console.log(x.date);
console.log(x.uptime);
});
console.log(response.identity);
console.log(response.owner);
expect(identity_key).toStrictEqual(response.identity);
expect(typeof response.owner).toBe("string");
});
it("Get a gateway history", async (): Promise<void> => {
const identity_key = config.environmnetConfig.gateway_identity;
const response = await status.getGatewayCoreCount(identity_key);
console.log(response.count);
console.log(response.identity);
expect(identity_key).toStrictEqual(response.identity);
expect(typeof response.count).toBe("number");
});
it("Get a gateway history", async (): Promise<void> => {
const identity_key = config.environmnetConfig.mixnode_identity;
const response = await status.getMixnodeCoreCount(identity_key);
console.log(response.count);
console.log(response.identity);
expect(identity_key).toStrictEqual(response.identity);
expect(typeof response.count).toBe("number");
});
it("Get a mixnode status", async (): Promise<void> => {
const identity_key = config.environmnetConfig.mixnode_identity;
const response = await status.getMixnodeStatus(identity_key);
console.log(response.status);
expect(response.status).toStrictEqual("active");
});
it("Get a mixnode reward estimation", async (): Promise<void> => {
const identity_key = config.environmnetConfig.mixnode_identity;
const response = await status.getMixnodeRewardComputation(identity_key);
console.log(response.estimated_delegators_reward);
console.log(response.estimated_node_profit);
console.log(response.estimated_operator_cost);
console.log(response.estimated_operator_reward);
console.log(response.estimated_total_node_reward);
console.log(response.reward_params);
console.log(response.as_at);
console.log(response);
//assertions to come
//expect(response).toStrictEqual('something');
});
it("Get a mixnode inclusion probability", async (): Promise<void> => {
const identity_key = config.environmnetConfig.mixnode_identity;
const response = await status.getMixnodeInclusionProbability(identity_key);
console.log(response.in_active);
console.log(response.in_reserve);
//assertions to come
//expect(response).toStrictEqual('something');
});
});
+6
View File
@@ -0,0 +1,6 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
reporters: ["default", "jest-junit"],
collectCoverageFrom: ["src/**/*.ts"]
};
+49
View File
@@ -0,0 +1,49 @@
{
"name": "validator-api-test-suite",
"version": "1.0.0",
"description": "a basic validator-api suite to test the validator-api",
"main": "dist/index.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "jest --forceExit --detectOpenHandles --passWithNoTests",
"build": "tsc",
"lint": "eslint --ext .js,.ts,.tsx .",
"lint:fix": "eslint --fix",
"cleanup": "rm -rf node_modules; rm -rf dist; yarn install"
},
"author": "Nymtech",
"license": "MIT",
"files": [
"dist"
],
"engines": {
"node": "18.1.0",
"npm": "8.x"
},
"dependencies": {
"axios": "^0.27.2",
"eslint": "^8.21.0",
"form-data": "4.0.0",
"json-stringify-safe": "5.0.1",
"tslog": "3.3.3",
"uuid": "8.3.2",
"yaml": "^2.1.1"
},
"devDependencies": {
"@types/jest": "28.1.6",
"@typescript-eslint/eslint-plugin": "^5.12.1",
"@typescript-eslint/parser": "^5.33.0",
"axios-mock-adapter": "^1.20.0",
"eslint-config-prettier": "^8.4.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^28.1.3",
"jest-junit": "^14.0.0",
"prettier": "2.7.1",
"process": "0.11.10",
"ts-jest": "28.0.7",
"typescript": "^4.7.4",
"uuidv4": "^6.2.12"
}
}
@@ -0,0 +1,15 @@
common:
request_headers:
Accept: application/json
Content-Type: application/json
qa:
api_base_url: https://qa-validator-api.nymtech.net/api/v1
mixnode_identity: DLdMKLPywEy1vnu3yPrtXvzY7fw1puiiHpA9n9UQatiQ
gateway_identity: CgQrYP8etksSBf4nALNqp93SHPpgFwEUyTsjBNNLj5WM
log_level: debug
prod:
api_base_url: https://qa-validator-api.nymtech.net/api/v1
mixnode_identity: DLdMKLPywEy1vnu3yPrtXvzY7fw1puiiHpA9n9UQatiQ
gateway_identity: CgQrYP8etksSBf4nALNqp93SHPpgFwEUyTsjBNNLj5WM
log_level: debug
time_zone: utc
@@ -0,0 +1,63 @@
import { readFileSync } from "fs";
import { TLogLevelName } from "tslog";
import YAML from "yaml";
class ConfigHandler {
private static instance: ConfigHandler;
private validEnvironments = ["qa", "prod"];
public commonConfig: { request_headers: object };
public environment: string;
public environmnetConfig: {
log_level: TLogLevelName;
time_zone: string;
api_base_url: string;
mixnode_identity: string;
gateway_identity: string;
};
private constructor() {
this.setCommonConfig();
this.setEnvironmentConfig(process.env.TEST_ENV || "prod");
}
public static getInstance(): ConfigHandler {
if (!ConfigHandler.instance) {
ConfigHandler.instance = new ConfigHandler();
}
return ConfigHandler.instance;
}
private setCommonConfig(): void {
try {
this.commonConfig = YAML.parse(
readFileSync("src/config/config.yaml", "utf8")
).common;
} catch (error) {
throw Error(`Error reading common config: (${error})`);
}
}
private setEnvironmentConfig(environment: string): void {
this.ensureEnvironmentIsValid(environment);
try {
this.environmnetConfig = YAML.parse(
readFileSync("src/config/config.yaml", "utf8")
)[environment];
} catch (error) {
throw Error(`Error reading environment config: (${error})`);
}
}
private ensureEnvironmentIsValid(environment: string): void {
if (this.validEnvironments.indexOf(environment) === -1) {
throw Error(`Config environment is not valid: "${environment}"`);
}
}
}
export default ConfigHandler;
+177
View File
@@ -0,0 +1,177 @@
import { AxiosResponse } from "axios";
import {
ActiveStatus,
AvgUptime,
CoreCount,
EstimatedReward,
InclusionProbability,
NodeHistory,
Report,
StakeSaturation,
} from "../../src/interfaces/StatusInterfaces";
import { APIClient } from "./abstracts/APIClient";
export default class Status extends APIClient {
constructor() {
super("/status");
}
public async getMixnodeStatusReport(identity_key: string): Promise<Report> {
const response = await this.restClient.sendGet({
route: `/mixnode/${identity_key}/report`,
});
return <Report>{
identity: response.data.identity,
owner: response.data.owner,
most_recent: response.data.most_recent,
last_hour: response.data.last_hour,
last_day: response.data.last_day,
};
}
public async getGatewayStatusReport(identity_key: string): Promise<Report> {
const response = await this.restClient.sendGet({
route: `/gateway/${identity_key}/report`,
});
return <Report>{
identity: response.data.identity,
owner: response.data.owner,
most_recent: response.data.most_recent,
last_hour: response.data.last_hour,
last_day: response.data.last_day,
};
}
public async getGatewayHistory(identity_key: string): Promise<NodeHistory> {
const response = await this.restClient.sendGet({
route: `/gateway/${identity_key}/history`,
});
return <NodeHistory>{
identity: response.data.identity,
owner: response.data.owner,
history: response.data.history,
};
}
public async getMixnodeStakeSaturation(
identity_key: string
): Promise<StakeSaturation> {
const response = await this.restClient.sendGet({
route: `/mixnode/${identity_key}/stake-saturation`,
});
return <StakeSaturation>{
as_at: response.data.as_at,
saturation: response.data.saturation,
};
}
public async getMixnodeCoreCount(identity_key: string): Promise<CoreCount> {
const response = await this.restClient.sendGet({
route: `/mixnode/${identity_key}/core-status-count`,
});
return <CoreCount>{
identity: response.data.identity,
count: response.data.count,
};
}
public async getGatewayCoreCount(identity_key: string): Promise<CoreCount> {
const response = await this.restClient.sendGet({
route: `/gateway/${identity_key}/core-status-count`,
});
return <CoreCount>{
identity: response.data.identity,
count: response.data.count,
};
}
public async getMixnodeRewardComputation(
identity_key: string
): Promise<EstimatedReward> {
const response = await this.restClient.sendGet({
route: `/mixnode/${identity_key}/reward-estimation`,
});
return <EstimatedReward>{
estimated_total_node_reward: response.data.estimated_total_node_reward,
estimated_operator_reward: response.data.estimated_operator_reward,
estimated_delegators_reward: response.data.estimated_delegators_reward,
estimated_node_profit: response.data.estimated_node_profit,
estimated_operator_cost: response.data.estimated_operator_cost,
reward_params: response.data.reward_params,
as_at: response.data.as_at,
};
}
public async getMixnodeRewardEstimatedComputation(
identity_key: string
): Promise<EstimatedReward> {
const response = await this.restClient.sendPost({
route: `/mixnode/${identity_key}/compute-reward-estimation`,
});
return <EstimatedReward>{
estimated_total_node_reward: response.data.estimated_total_node_reward,
estimated_operator_reward: response.data.estimated_operator_reward,
estimated_delegators_reward: response.data.estimated_delegators_reward,
estimated_node_profit: response.data.estimated_node_profit,
estimated_operator_cost: response.data.estimated_operator_cost,
reward_params: response.data.reward_params,
as_at: response.data.as_at,
};
}
public async getMixnodeHistory(identity_key: string): Promise<NodeHistory> {
const response = await this.restClient.sendGet({
route: `/mixnode/${identity_key}/history`,
});
return <NodeHistory>{
identity: response.data.identity,
owner: response.data.owner,
history: response.data.history,
};
}
public async getMixnodeAverageUptime(
identity_key: string
): Promise<AvgUptime> {
const response = await this.restClient.sendGet({
route: `/mixnode/${identity_key}/avg_uptime`,
});
return <AvgUptime>{
identity: response.data.identity,
avg_uptime: response.data.avg_uptime,
};
}
public async getMixnodeInclusionProbability(
identity_key: string
): Promise<InclusionProbability> {
const response = await this.restClient.sendGet({
route: `/mixnode/${identity_key}/inclusion-probability`,
});
return <InclusionProbability>{
in_active: response.data.in_active,
in_reserve: response.data.in_reserve,
};
}
public async getMixnodeStatus(identity_key: string): Promise<ActiveStatus> {
const response = await this.restClient.sendGet({
route: `/mixnode/${identity_key}/status`,
});
return <ActiveStatus>{
status: response.data.status,
};
}
}
@@ -0,0 +1,30 @@
import { Logger } from "tslog";
import ConfigHandler from "../../config/configHandler";
import { RestClient } from "../../restClient/RestClient";
export abstract class APIClient {
protected constructor(serviceUrl: string) {
const baseUrl: string = this.config.environmnetConfig.api_base_url;
this.url = baseUrl + serviceUrl;
this.restClient = new RestClient(this.url);
this.serviceName = this.constructor.toString().match(/\w+/g)[1];
this.log.info(`The Service URL for ${this.serviceName} is ${this.url}`);
}
public createdItemIds: Set<string> = new Set();
protected config = ConfigHandler.getInstance();
protected log: Logger = new Logger({
minLevel: this.config.environmnetConfig.log_level,
dateTimeTimezone:
this.config.environmnetConfig.time_zone ||
Intl.DateTimeFormat().resolvedOptions().timeZone,
});
protected url: string;
public restClient: RestClient;
protected serviceName: string;
}
@@ -0,0 +1,72 @@
export type Epoch = {
epoch_reward_pool: string;
rewarded_set_size: string;
active_set_size: string;
staking_supply: string;
sybil_resistance_percent: number;
active_set_work_factor: number;
};
export type Node = {
reward_blockstamp: number;
uptime: string;
in_active_set: boolean;
};
export type RewardParams = {
epoch: Epoch;
node: Node;
};
export type EstimatedReward = {
estimated_total_node_reward: number;
estimated_operator_reward: number;
estimated_delegators_reward: number;
estimated_node_profit: number;
estimated_operator_cost: number;
reward_params: RewardParams;
as_at: number;
};
export type StakeSaturation = {
saturation: number;
as_at: number;
};
export type AvgUptime = {
identity: string;
avg_uptime: number;
};
export type InclusionProbability = {
in_active: string;
in_reserve: string;
};
export type Report = {
identity: string;
owner: string;
most_recent: number;
last_hour: number;
last_day: number;
};
export type History = {
date: string;
uptime: number;
};
export type NodeHistory = {
identity: string;
owner: string;
history: History[];
};
export type CoreCount = {
identity: string;
count: number;
};
export type ActiveStatus = {
status: string;
};
@@ -0,0 +1,243 @@
import axios, {
AxiosInstance,
AxiosRequestConfig,
AxiosResponse,
Method,
} from "axios";
import { Logger } from "tslog";
import { stringify } from "yaml";
import https from "https";
import ConfigHandler from "../config/configHandler";
const config = ConfigHandler.getInstance();
const log = new Logger({
minLevel: config.environmnetConfig.log_level,
dateTimeTimezone:
config.environmnetConfig.time_zone ||
Intl.DateTimeFormat().resolvedOptions().timeZone,
});
function isSet(property): boolean {
return property !== undefined && property !== null;
}
export class RestClient {
private static authToken: string;
private axiosInstance: AxiosInstance;
constructor(baseUrl: string) {
this.axiosInstance = axios.create({ baseURL: baseUrl });
}
private httpsAgent = new https.Agent({
rejectUnauthorized: false,
});
// Not returning an actual auth token for this example project.
// Just showing how it can be done!
static async getToken(requestHeaders: object) {
requestHeaders["Authorization"] = `asdf`;
}
public async callEndpoint({
route,
method,
authToken,
headers,
data,
additionalConfigs,
params,
}: IAxiosCallEndpointArgs): Promise<AxiosResponse> {
let response;
let responseLog = "Response: ";
let requestHeaders = headers;
// if headers are not passed in, use the default headers
if (requestHeaders == undefined) {
requestHeaders = { ...config.commonConfig.request_headers };
}
// if authToken is passed in, add it to the request headers
if (authToken !== undefined) {
requestHeaders = {
...requestHeaders,
...{
Authorization: `Bearer ${authToken}`,
},
};
}
// if we have not set the auth headers yet, set them
else if (!requestHeaders.Authorization) {
await RestClient.getToken(requestHeaders);
}
log.debug(
RestClient.prepareLogRecord({
route,
method,
headers: requestHeaders,
data,
additionalConfigs,
params,
})
);
await this.axiosInstance
.request({
url: route,
method,
data,
headers: requestHeaders,
httpsAgent: this.httpsAgent,
params,
...additionalConfigs,
})
.then((res) => {
response = res;
responseLog = `<Success> Status = ${res.status} ${res.statusText}`;
})
.catch((error) => {
response = error.response;
if (response === undefined)
responseLog = `<Error> Something wrong happened, did not get proper error from server! (${error.message})`;
else
responseLog = `<Error> Status = ${response.status} ${response.statusText}, ${error.message}`;
});
log.debug(responseLog);
return response;
}
public async sendPost({
route,
authToken,
data,
params,
headers,
additionalConfigs,
}: IAxiosHttpRequestArgs): Promise<any> {
return this.callEndpoint({
route,
method: "POST",
authToken,
data,
params,
headers,
additionalConfigs,
});
}
public async sendGet({
route,
authToken,
params,
headers,
additionalConfigs,
}: IAxiosHttpRequestArgs): Promise<any> {
return this.callEndpoint({
route,
method: "GET",
authToken,
params,
headers,
additionalConfigs,
});
}
public async sendDelete({
route,
authToken,
params,
headers,
additionalConfigs,
}: IAxiosHttpRequestArgs): Promise<any> {
return this.callEndpoint({
route,
method: "DELETE",
authToken,
params,
headers,
additionalConfigs,
});
}
public async sendPatch({
route,
authToken,
data,
headers,
additionalConfigs,
}: IAxiosHttpRequestArgs): Promise<any> {
return this.callEndpoint({
route,
method: "PATCH",
authToken,
data,
headers,
additionalConfigs,
});
}
public async sendPut({
route,
authToken,
data,
headers,
additionalConfigs,
}: IAxiosHttpRequestArgs): Promise<any> {
return this.callEndpoint({
route,
method: "PUT",
authToken,
data,
headers,
additionalConfigs,
});
}
private static prepareLogRecord({
route,
method,
headers,
data,
additionalConfigs,
params,
}: IAxiosCallEndpointArgs): string {
let logRecord = `Request: ${method} ${route}`;
if (isSet(headers))
logRecord = `${logRecord}\nHeaders: ${stringify(headers)}`;
if (isSet(params)) logRecord = `${logRecord}\nParams: ${stringify(params)}`;
if (isSet(additionalConfigs)) {
logRecord = `${logRecord}\nAdditional Configuration: ${stringify(
additionalConfigs
)}`;
}
if (isSet(data)) {
const jsonData = stringify(data);
// We don't want to log anything that isn't json data
logRecord = `${logRecord}\nData: ${
jsonData === undefined ? "Some data, not JSON!" : jsonData
}`;
}
return logRecord;
}
}
export interface IAxiosHttpRequestArgs {
route: string;
authToken?: string;
data?: object;
params?: object;
headers?: any;
additionalConfigs?: AxiosRequestConfig;
}
export interface IAxiosCallEndpointArgs extends IAxiosHttpRequestArgs {
method: Method;
}
+5
View File
@@ -0,0 +1,5 @@
{
"extends": "./tsconfig.json",
"include": ["src/**/*", "functional_test/**/*", "unit_test/**/*", "*.ts"],
"exclude": ["node_modules", "dist"]
}
+25
View File
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"removeComments": true,
"allowJs": true,
"preserveConstEnums": true,
"module": "commonjs",
"target": "ES2019",
"declaration": true,
"esModuleInterop": true,
"sourceMap": true,
"lib": ["esnext"],
"outDir": "dist",
"resolveJsonModule": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"paths": {
"*": ["types/*"]
},
"baseUrl": "./",
"typeRoots": ["node_modules/@types"],
"alwaysStrict": true
},
"include": ["src/**/*", "functional_test/**/*"],
"exclude": ["unit_test/**/*"]
}
File diff suppressed because it is too large Load Diff
@@ -4,7 +4,7 @@
use mixnet_contract_common::{reward_params::RewardParams, MixNode, MixNodeBond};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::{fmt, time::Duration};
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
@@ -101,16 +101,20 @@ pub type StakeSaturation = f32;
)]
pub enum SelectionChance {
VeryHigh,
High,
Moderate,
Low,
VeryLow,
}
impl From<f64> for SelectionChance {
fn from(p: f64) -> SelectionChance {
match p {
p if p > 0.15 => SelectionChance::VeryHigh,
p if p >= 0.05 => SelectionChance::Moderate,
_ => SelectionChance::Low,
p if p > 0.98 => SelectionChance::VeryHigh,
p if p > 0.9 => SelectionChance::High,
p if p > 0.7 => SelectionChance::Moderate,
p if p > 0.5 => SelectionChance::Low,
_ => SelectionChance::VeryLow,
}
}
}
@@ -119,8 +123,10 @@ impl fmt::Display for SelectionChance {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SelectionChance::VeryHigh => write!(f, "VeryHigh"),
SelectionChance::High => write!(f, "High"),
SelectionChance::Moderate => write!(f, "Moderate"),
SelectionChance::Low => write!(f, "Low"),
SelectionChance::VeryLow => write!(f, "VeryLow"),
}
}
}
@@ -145,3 +151,20 @@ impl fmt::Display for InclusionProbabilityResponse {
)
}
}
#[derive(Clone, Serialize, schemars::JsonSchema)]
pub struct AllInclusionProbabilitiesResponse {
pub inclusion_probabilities: Vec<InclusionProbability>,
pub samples: u64,
pub elapsed: Duration,
pub delta_max: f64,
pub delta_l2: f64,
pub as_at: i64,
}
#[derive(Clone, Serialize, schemars::JsonSchema)]
pub struct InclusionProbability {
pub id: String,
pub in_active: f64,
pub in_reserve: f64,
}