Compare commits
176 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 686c0de5eb | |||
| 5ce2e0c8bb | |||
| 68e120dbf4 | |||
| 3281f10443 | |||
| 2409f85e31 | |||
| 37c06f338f | |||
| 8a37df64b5 | |||
| 20828d0d28 | |||
| 1e3c8ed3e0 | |||
| 830dbcecfc | |||
| a0d3144837 | |||
| a4988e3547 | |||
| 0b585a15b7 | |||
| 86a93a71e9 | |||
| 967692bf88 | |||
| 662b4d2fff | |||
| add4747e99 | |||
| 242733f144 | |||
| 0cc1926636 | |||
| 0f54c073a7 | |||
| 3bfce128a6 | |||
| 1b3e79e84d | |||
| e94f99211f | |||
| 003ea095cc | |||
| 74d93df74b | |||
| 0c66cc7393 | |||
| 82abfa5c5c | |||
| f7486f0490 | |||
| ee5f0f5808 | |||
| 6cb25cf1fb | |||
| db01c245d9 | |||
| 53347bec67 | |||
| 252385688d | |||
| cbcef9fbcd | |||
| e27fc82524 | |||
| f661bf0446 | |||
| 1d22f35c82 | |||
| fa41fe62c4 | |||
| 94a006a725 | |||
| 98cbd2509c | |||
| d2d99ca5c9 | |||
| 9e4904ff37 | |||
| bf9db4128d | |||
| bce86235c7 | |||
| 423f6bfb55 | |||
| 38fcbb7f2e | |||
| 1785b10d91 | |||
| 7771c5d3d9 | |||
| d6206a04bd | |||
| 83a7f6577b | |||
| 97c6567139 | |||
| a1534d23af | |||
| bcbda85477 | |||
| 54e95d795e | |||
| 11c0e79725 | |||
| 4525be9871 | |||
| b83d4ca1a3 | |||
| bfb868bfc7 | |||
| 7b00282b27 | |||
| 54194c03e1 | |||
| 87c2a317d5 | |||
| be92171fec | |||
| 04eef83c15 | |||
| 25cc7dbebf | |||
| beeb67e9c2 | |||
| ec19de6fa3 | |||
| 575845af38 | |||
| b6b757436e | |||
| eda69447de | |||
| 0f6f47c5ac | |||
| b57c17e5af | |||
| c237b37ea6 | |||
| f2fa221489 | |||
| d7e82b075f | |||
| 0e4787f078 | |||
| f684664472 | |||
| cd5fff92ad | |||
| 9b0b961d43 | |||
| 4b95e71adb | |||
| cde5b66306 | |||
| ac72e20447 | |||
| 571fd5cb93 | |||
| 7f3166d230 | |||
| 68050d77df | |||
| 943337db0d | |||
| 8fbf84174d | |||
| d42a175289 | |||
| ac01df0817 | |||
| 7b27065608 | |||
| 0523ccdce8 | |||
| df8ae52d8c | |||
| 7cfaf6fa1e | |||
| 6e9eab4edb | |||
| 0812378fdd | |||
| 188a7ec91d | |||
| c91bf3f8d1 | |||
| ecb27e2cc2 | |||
| a1961dbc2f | |||
| ef4af0a1db | |||
| 5930ec1f18 | |||
| 72c7049fca | |||
| 92cbe651de | |||
| 777fcf8cb3 | |||
| 945dda0c24 | |||
| b44b074af7 | |||
| 7a0dff5f00 | |||
| 6685b129bb | |||
| e0a80c777e | |||
| 479d410d20 | |||
| ab019266cc | |||
| 92fcae9a37 | |||
| 413e2662ff | |||
| e026a532dd | |||
| a1ca330ce9 | |||
| 139e89643c | |||
| a1e0087760 | |||
| b15cc094ea | |||
| 8eb3f6f862 | |||
| 9032d81d52 | |||
| 7adee63ebe | |||
| 1a5580229b | |||
| df4c6493d4 | |||
| 30a41261ea | |||
| f2e95d2fd5 | |||
| 5730c914e8 | |||
| 7845e32742 | |||
| 4cce235e13 | |||
| 521eb98f25 | |||
| c77ccddcb3 | |||
| b8ce97e005 | |||
| 49e29af5f4 | |||
| 41319fe7ad | |||
| 4fcf0da5c0 | |||
| 0a6b2a8aaf | |||
| b09db50bba | |||
| 82e6d7335b | |||
| 21d19d2447 | |||
| 5f116f8104 | |||
| 04f633446e | |||
| be5421719c | |||
| 6d874cc34a | |||
| 7e356ea3b3 | |||
| f7be9e7e6f | |||
| 05374393ef | |||
| 580656c002 | |||
| a7caf97b73 | |||
| 091507b6d8 | |||
| 71b5bc9e71 | |||
| 8bbffb6a88 | |||
| 31c4fc6807 | |||
| 66b9b13edc | |||
| 1b945ae918 | |||
| be9b83a87d | |||
| ba1fb17908 | |||
| 5446874ebe | |||
| afac630a77 | |||
| 3250d6982e | |||
| ecdbe1a6fb | |||
| 6ba79ee924 | |||
| 2bebb4b0c2 | |||
| 81b7d49624 | |||
| f118a0c854 | |||
| db6ecaaecb | |||
| bd13aa6f35 | |||
| 0cad7f635d | |||
| a5e6032393 | |||
| 4edc0700a1 | |||
| 019a04c0fc | |||
| 2e7b8e911f | |||
| 50f4699c95 | |||
| 3265df019a | |||
| 902721bda3 | |||
| 585724fc79 | |||
| c6578384a8 | |||
| 3b245e16db | |||
| 6d0e2cf491 |
@@ -0,0 +1,9 @@
|
||||
# Description
|
||||
|
||||
Closes: #XXXX
|
||||
|
||||
<!-- If appropriate, insert relevant description here -->
|
||||
|
||||
# Checklist:
|
||||
|
||||
- [ ] added a changelog entry to `CHANGELOG.md`
|
||||
@@ -0,0 +1,72 @@
|
||||
name: Continuous integration on dispatch
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: [ self-hosted, custom-linux-exoscale ]
|
||||
# Enable sccache via environment variable
|
||||
env:
|
||||
RUSTC_WRAPPER: /home/ubuntu/.cargo/bin/sccache
|
||||
steps:
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
|
||||
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace
|
||||
|
||||
- name: Run all tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --all-features
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
name: Clippy checks
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --all-features
|
||||
|
||||
- name: Run clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --workspace -- -D warnings
|
||||
|
||||
- name: Build all binaries with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace --features=coconut
|
||||
|
||||
- name: Run all tests with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --features=coconut
|
||||
|
||||
- name: Run clippy with coconut enabled
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --features=coconut -- -D warnings
|
||||
@@ -31,6 +31,8 @@ jobs:
|
||||
# continue-on-error: true
|
||||
- run: yarn && yarn build
|
||||
continue-on-error: true
|
||||
- run: yarn storybook:build
|
||||
name: Build storybook
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
@@ -42,6 +44,17 @@ jobs:
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/network-explorer-${{ env.GITHUB_REF_SLUG }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Deploy storybook to CI www
|
||||
continue-on-error: true
|
||||
uses: easingthemes/ssh-deploy@main
|
||||
env:
|
||||
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
|
||||
ARGS: "-rltgoDzvO --delete"
|
||||
SOURCE: "explorer/storybook-static/"
|
||||
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
|
||||
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
|
||||
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/ne-sb-${{ env.GITHUB_REF_SLUG }}
|
||||
EXCLUDE: "/dist/, /node_modules/"
|
||||
- name: Keybase - Node Install
|
||||
run: npm install
|
||||
working-directory: .github/workflows/support-files
|
||||
@@ -51,6 +64,7 @@ jobs:
|
||||
NYM_PROJECT_NAME: "Network Explorer"
|
||||
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
|
||||
NYM_CI_WWW_LOCATION: "network-explorer-${{ env.GITHUB_REF_SLUG }}"
|
||||
NYM_CI_WWW_LOCATION_STORYBOOK: "ne-sb-${{ env.GITHUB_REF_SLUG }}"
|
||||
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
|
||||
GIT_BRANCH: "${GITHUB_REF##*/}"
|
||||
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
|
||||
|
||||
@@ -100,6 +100,33 @@ jobs:
|
||||
with:
|
||||
command: clippy
|
||||
args: --workspace --all-targets --features=coconut -- -D warnings
|
||||
|
||||
# nym-wallet (the rust part)
|
||||
- name: Build nym-wallet rust code
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --manifest-path nym-wallet/Cargo.toml --workspace
|
||||
|
||||
- name: Run nym-wallet tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path nym-wallet/Cargo.toml --workspace
|
||||
|
||||
- name: Check nym-wallet formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --manifest-path nym-wallet/Cargo.toml --all -- --check
|
||||
|
||||
- name: Run clippy for nym-wallet
|
||||
uses: actions-rs/cargo@v1
|
||||
if: ${{ matrix.rust != 'nightly' }}
|
||||
with:
|
||||
command: clippy
|
||||
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-targets -- -D warnings
|
||||
|
||||
notification:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -44,3 +44,4 @@ jobs:
|
||||
target/release/nym-mixnode
|
||||
target/release/nym-socks5-client
|
||||
target/release/nym-validator-api
|
||||
target/release/nym-network-requester
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
|
||||
> :rocket: {{ env.NYM_PROJECT_NAME }} ➡️➡️➡️➡️➡️ **View output:** https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}/
|
||||
> `storybook`: https://{{ env.NYM_CI_WWW_LOCATION_STORYBOOK }}.{{ env.NYM_CI_WWW_BASE }}
|
||||
> ✅ **SUCCESS**
|
||||
> `branch` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/tree/{{ env.GIT_BRANCH_NAME }}
|
||||
> `commit` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/commit/{{ env.GITHUB_SHA }}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
name: Nym Wallet (rust)
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'explorer/**'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- 'explorer/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: [ self-hosted, custom-linux-exoscale ]
|
||||
steps:
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev squashfs-tools
|
||||
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --manifest-path nym-wallet/Cargo.toml --workspace
|
||||
|
||||
- name: Run all tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --manifest-path nym-wallet/Cargo.toml --workspace
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --manifest-path nym-wallet/Cargo.toml --all -- --check
|
||||
|
||||
- uses: actions-rs/clippy-check@v1
|
||||
name: Clippy checks
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-features
|
||||
|
||||
- name: Run clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --manifest-path nym-wallet/Cargo.toml --workspace --all-features -- -D warnings
|
||||
+333
@@ -1,5 +1,337 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- wallet: require password to switch accounts
|
||||
- wallet: add simple CLI tool for decrypting and recovering the wallet file.
|
||||
- wallet: added support for multiple accounts ([#1265])
|
||||
- wallet: the wallet backend learned how to keep track of validator name, either hardcoded or by querying the status endpoint.
|
||||
- mixnet-contract: Replace all naked `-` with `saturating_sub`.
|
||||
- validator-api: add Swagger to document the REST API ([#1249]).
|
||||
- all: added network compilation target to `--help` (or `--version`) commands ([#1256]).
|
||||
- network-requester: send traffic statistics from all network requesters and receive it in a special network-requester that aggregates the data and exposes it via a rest API ([#1267]).
|
||||
|
||||
### Fixed
|
||||
|
||||
- vesting-contract: replaced `checked_sub` with `saturating_sub` to fix the underflow in `get_vesting_tokens` ([#1275])
|
||||
- mixnet-contract: removed `expect` in `query_delegator_reward` and queries containing invalid proxy address should now return a more human-readable error ([#1257])
|
||||
- mixnet-contract: Under certain circumstances nodes could not be unbonded ([#1255](https://github.com/nymtech/nym/issues/1255)) ([#1258])
|
||||
- mixnode, gateway: attempting to determine reconnection backoff to persistently failing mixnode could result in a crash ([#1260])
|
||||
|
||||
[#1258]: https://github.com/nymtech/nym/pull/1258
|
||||
[#1249]: https://github.com/nymtech/nym/pull/1249
|
||||
[#1256]: https://github.com/nymtech/nym/pull/1256
|
||||
[#1257]: https://github.com/nymtech/nym/pull/1257
|
||||
[#1260]: https://github.com/nymtech/nym/pull/1260
|
||||
[#1265]: https://github.com/nymtech/nym/pull/1265
|
||||
[#1267]: https://github.com/nymtech/nym/pull/1267
|
||||
[#1275]: https://github.com/nymtech/nym/pull/1275
|
||||
|
||||
## [nym-wallet-v1.0.4](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.4) (2022-05-04)
|
||||
|
||||
### Changed
|
||||
|
||||
- all: the default behaviour of validator client is changed to use `broadcast_sync` and poll for transaction inclusion instead of using `broadcast_commit` to deal with timeouts ([#1246])
|
||||
|
||||
## [v1.0.1](https://github.com/nymtech/nym/tree/v1.0.1) (2022-05-04)
|
||||
|
||||
### Added
|
||||
|
||||
- validator-api: introduced endpoint for getting average mixnode uptime ([#1238])
|
||||
|
||||
### Changed
|
||||
|
||||
- all: the default behaviour of validator client is changed to use `broadcast_sync` and poll for transaction inclusion instead of using `broadcast_commit` to deal with timeouts ([#1246])
|
||||
|
||||
### Fixed
|
||||
|
||||
- nym-network-requester: is included in the Github Actions for building release binaries
|
||||
|
||||
[#1238]: https://github.com/nymtech/nym/pull/1238
|
||||
[#1246]: https://github.com/nymtech/nym/pull/1246
|
||||
|
||||
## [v1.0.0](https://github.com/nymtech/nym/tree/v1.0.0) (2022-05-03)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/v0.12.1...v1.0.0)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Feature/show pending delegations [\#1229](https://github.com/nymtech/nym/pull/1229) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Bucket inclusion probabilities [\#1224](https://github.com/nymtech/nym/pull/1224) ([durch](https://github.com/durch))
|
||||
- Create a new bundled delegation when compounding rewards [\#1221](https://github.com/nymtech/nym/pull/1221) ([durch](https://github.com/durch))
|
||||
|
||||
## [nym-binaries-1.0.0](https://github.com/nymtech/nym/tree/nym-binaries-1.0.0) (2022-04-27)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/nym-wallet-v1.0.3...nym-binaries-1.0.0)
|
||||
|
||||
## [nym-wallet-v1.0.3](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.3) (2022-04-25)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/nym-binaries-1.0.0-rc.2...nym-wallet-v1.0.3)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- \[Issue\] Wallet 1.0.2 cannot send NYM tokens from a DelayedVestingAccount [\#1215](https://github.com/nymtech/nym/issues/1215)
|
||||
- Main README not showing properly with GitHub dark mode [\#1211](https://github.com/nymtech/nym/issues/1211)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Bugfix - wallet undelegation for vesting accounts [\#1220](https://github.com/nymtech/nym/pull/1220) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- Bugfix/delegation reconcile [\#1219](https://github.com/nymtech/nym/pull/1219) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Bugfix/query proxied pending delegations [\#1218](https://github.com/nymtech/nym/pull/1218) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Using custom gas multiplier in the wallet [\#1217](https://github.com/nymtech/nym/pull/1217) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/vesting accounts support [\#1216](https://github.com/nymtech/nym/pull/1216) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Release/1.0.0 rc.2 [\#1214](https://github.com/nymtech/nym/pull/1214) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- chore: fix dark mode rendering [\#1212](https://github.com/nymtech/nym/pull/1212) ([pwnfoo](https://github.com/pwnfoo))
|
||||
- Feature/spend coconut [\#1210](https://github.com/nymtech/nym/pull/1210) ([neacsu](https://github.com/neacsu))
|
||||
- Bugfix/unique sphinx key [\#1207](https://github.com/nymtech/nym/pull/1207) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Add cache read and write timeouts [\#1206](https://github.com/nymtech/nym/pull/1206) ([durch](https://github.com/durch))
|
||||
- Additional, more informative routes [\#1204](https://github.com/nymtech/nym/pull/1204) ([durch](https://github.com/durch))
|
||||
- Feature/aggregated econ dynamics explorer endpoint [\#1203](https://github.com/nymtech/nym/pull/1203) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Debugging validator [\#1198](https://github.com/nymtech/nym/pull/1198) ([durch](https://github.com/durch))
|
||||
- wallet: expose additional validator configuration functionality to the frontend [\#1195](https://github.com/nymtech/nym/pull/1195) ([octol](https://github.com/octol))
|
||||
- Update rewarding validator address [\#1193](https://github.com/nymtech/nym/pull/1193) ([durch](https://github.com/durch))
|
||||
- Crypto part of the Groth's NIDKG [\#1182](https://github.com/nymtech/nym/pull/1182) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- fix unbond page [\#1180](https://github.com/nymtech/nym/pull/1180) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Type safe bounds [\#1179](https://github.com/nymtech/nym/pull/1179) ([durch](https://github.com/durch))
|
||||
- Fix delegation paging [\#1174](https://github.com/nymtech/nym/pull/1174) ([durch](https://github.com/durch))
|
||||
- Update binaries to rc version [\#1172](https://github.com/nymtech/nym/pull/1172) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Bump ansi-regex from 4.1.0 to 4.1.1 in /docker/typescript\_client/upload\_contract [\#1171](https://github.com/nymtech/nym/pull/1171) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
|
||||
## [nym-binaries-1.0.0-rc.2](https://github.com/nymtech/nym/tree/nym-binaries-1.0.0-rc.2) (2022-04-15)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/nym-wallet-v1.0.2...nym-binaries-1.0.0-rc.2)
|
||||
|
||||
## [nym-wallet-v1.0.2](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.2) (2022-04-05)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/nym-wallet-v1.0.1...nym-wallet-v1.0.2)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Wallet 1.0.2 visual tweaks [\#1197](https://github.com/nymtech/nym/pull/1197) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- Password for wallet with routes [\#1196](https://github.com/nymtech/nym/pull/1196) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Add auto-updater to Nym Wallet [\#1194](https://github.com/nymtech/nym/pull/1194) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- Fix clippy warnings for beta toolchain [\#1191](https://github.com/nymtech/nym/pull/1191) ([octol](https://github.com/octol))
|
||||
- wallet: expose validator urls to the frontend [\#1190](https://github.com/nymtech/nym/pull/1190) ([octol](https://github.com/octol))
|
||||
- wallet: add test for decrypting stored wallet file [\#1189](https://github.com/nymtech/nym/pull/1189) ([octol](https://github.com/octol))
|
||||
- Fix clippy warnings [\#1188](https://github.com/nymtech/nym/pull/1188) ([octol](https://github.com/octol))
|
||||
- Password for wallet with routes [\#1187](https://github.com/nymtech/nym/pull/1187) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- wallet: add validate\_mnemonic [\#1186](https://github.com/nymtech/nym/pull/1186) ([octol](https://github.com/octol))
|
||||
- wallet: support removing accounts from the wallet file [\#1185](https://github.com/nymtech/nym/pull/1185) ([octol](https://github.com/octol))
|
||||
- Feature/adding discord [\#1184](https://github.com/nymtech/nym/pull/1184) ([gala1234](https://github.com/gala1234))
|
||||
- wallet: config backend for validator selection [\#1183](https://github.com/nymtech/nym/pull/1183) ([octol](https://github.com/octol))
|
||||
- Add storybook to wallet [\#1178](https://github.com/nymtech/nym/pull/1178) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- wallet: connection test nymd and api urls independently [\#1170](https://github.com/nymtech/nym/pull/1170) ([octol](https://github.com/octol))
|
||||
- wallet: wire up account storage [\#1153](https://github.com/nymtech/nym/pull/1153) ([octol](https://github.com/octol))
|
||||
- Feature/signature on deposit [\#1151](https://github.com/nymtech/nym/pull/1151) ([neacsu](https://github.com/neacsu))
|
||||
|
||||
## [nym-wallet-v1.0.1](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.1) (2022-04-05)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/nym-binaries-1.0.0-rc.1...nym-wallet-v1.0.1)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Check enabling bbbc simultaneously with open access. Estimate what it would take to make this the default compilation target. [\#1175](https://github.com/nymtech/nym/issues/1175)
|
||||
- Get coconut credential for deposited tokens [\#1138](https://github.com/nymtech/nym/issues/1138)
|
||||
- Make payments lazy [\#1135](https://github.com/nymtech/nym/issues/1135)
|
||||
- Uptime on node selection for sets [\#1049](https://github.com/nymtech/nym/issues/1049)
|
||||
|
||||
## [nym-binaries-1.0.0-rc.1](https://github.com/nymtech/nym/tree/nym-binaries-1.0.0-rc.1) (2022-03-28)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/nym-wallet-v1.0.0...nym-binaries-1.0.0-rc.1)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- \[Issue\]cargo build --release issue [\#1101](https://github.com/nymtech/nym/issues/1101)
|
||||
- appimage fail to load in Fedora [\#1098](https://github.com/nymtech/nym/issues/1098)
|
||||
- \[Issue\] React Example project does not compile when using @nymproject/nym-client-wasm v0.9.0-1 [\#878](https://github.com/nymtech/nym/issues/878)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Make mainnet coin transfers work [\#1096](https://github.com/nymtech/nym/issues/1096)
|
||||
- Make Nym wallet validators configurable at runtime [\#1026](https://github.com/nymtech/nym/issues/1026)
|
||||
- Project Platypus e2e / integration testing [\#942](https://github.com/nymtech/nym/issues/942)
|
||||
- \[Coconut\]: Replace ElGamal with Pedersen commitments [\#901](https://github.com/nymtech/nym/issues/901)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Different values for mixes and gateways [\#1169](https://github.com/nymtech/nym/pull/1169) ([durch](https://github.com/durch))
|
||||
- Add global blacklist to validator-cache [\#1168](https://github.com/nymtech/nym/pull/1168) ([durch](https://github.com/durch))
|
||||
- Feature/upgrade rewarding sandbox [\#1167](https://github.com/nymtech/nym/pull/1167) ([durch](https://github.com/durch))
|
||||
- Bump node-forge from 1.2.1 to 1.3.0 [\#1165](https://github.com/nymtech/nym/pull/1165) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump minimist from 1.2.5 to 1.2.6 in /nym-wallet/webdriver [\#1164](https://github.com/nymtech/nym/pull/1164) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump minimist from 1.2.5 to 1.2.6 in /clients/tauri-client [\#1163](https://github.com/nymtech/nym/pull/1163) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump minimist from 1.2.5 to 1.2.6 in /clients/webassembly/js-example [\#1162](https://github.com/nymtech/nym/pull/1162) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump minimist from 1.2.5 to 1.2.6 in /clients/native/examples/js-examples/websocket [\#1160](https://github.com/nymtech/nym/pull/1160) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump minimist from 1.2.5 to 1.2.6 in /docker/typescript\_client/upload\_contract [\#1159](https://github.com/nymtech/nym/pull/1159) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Feature/vesting full [\#1158](https://github.com/nymtech/nym/pull/1158) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- get\_current\_epoch tauri [\#1156](https://github.com/nymtech/nym/pull/1156) ([durch](https://github.com/durch))
|
||||
- Cleanup [\#1155](https://github.com/nymtech/nym/pull/1155) ([durch](https://github.com/durch))
|
||||
- Feature flag reward payments [\#1154](https://github.com/nymtech/nym/pull/1154) ([durch](https://github.com/durch))
|
||||
- Add Query endpoints for calculating rewards [\#1152](https://github.com/nymtech/nym/pull/1152) ([durch](https://github.com/durch))
|
||||
- Pending endpoints [\#1150](https://github.com/nymtech/nym/pull/1150) ([durch](https://github.com/durch))
|
||||
- wallet: add logging [\#1149](https://github.com/nymtech/nym/pull/1149) ([octol](https://github.com/octol))
|
||||
- wallet: use Urls rather than Strings for validator urls [\#1148](https://github.com/nymtech/nym/pull/1148) ([octol](https://github.com/octol))
|
||||
- Change accumulated reward to Option, migrate delegations [\#1147](https://github.com/nymtech/nym/pull/1147) ([durch](https://github.com/durch))
|
||||
- wallet: fetch validators url remotely if available [\#1146](https://github.com/nymtech/nym/pull/1146) ([octol](https://github.com/octol))
|
||||
- Fix delegated\_free calculation [\#1145](https://github.com/nymtech/nym/pull/1145) ([durch](https://github.com/durch))
|
||||
- Update Nym wallet dependencies to use `ts-packages` [\#1144](https://github.com/nymtech/nym/pull/1144) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- wallet: try validators one by one if available [\#1143](https://github.com/nymtech/nym/pull/1143) ([octol](https://github.com/octol))
|
||||
- Update Network Explorer Packages and add mix node identity key copy [\#1142](https://github.com/nymtech/nym/pull/1142) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- Feature/vesting token pool selector [\#1140](https://github.com/nymtech/nym/pull/1140) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Add `ts-packages` for shared Typescript packages [\#1139](https://github.com/nymtech/nym/pull/1139) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- allow main-net prefix and denom to work [\#1137](https://github.com/nymtech/nym/pull/1137) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Upgrade blake3 to v1.3.1 and tauri to 1.0.0-rc.3 [\#1136](https://github.com/nymtech/nym/pull/1136) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- Bump url-parse from 1.5.7 to 1.5.10 in /clients/native/examples/js-examples/websocket [\#1134](https://github.com/nymtech/nym/pull/1134) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Use network explorer map data with disputed areas [\#1133](https://github.com/nymtech/nym/pull/1133) ([Baro1905](https://github.com/Baro1905))
|
||||
- Feature/vesting UI [\#1132](https://github.com/nymtech/nym/pull/1132) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Refactor to a lazy rewarding system [\#1127](https://github.com/nymtech/nym/pull/1127) ([durch](https://github.com/durch))
|
||||
- Bump ws from 6.2.1 to 6.2.2 in /clients/webassembly/js-example [\#1126](https://github.com/nymtech/nym/pull/1126) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump url-parse from 1.4.7 to 1.5.7 in /clients/webassembly/react-example [\#1125](https://github.com/nymtech/nym/pull/1125) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump url-parse from 1.5.4 to 1.5.7 in /clients/native/examples/js-examples/websocket [\#1124](https://github.com/nymtech/nym/pull/1124) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump url-parse from 1.5.1 to 1.5.7 in /clients/webassembly/js-example [\#1122](https://github.com/nymtech/nym/pull/1122) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- update contract address [\#1121](https://github.com/nymtech/nym/pull/1121) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Refactor GitHub Actions notifications [\#1119](https://github.com/nymtech/nym/pull/1119) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- Change `pledge` to `bond` in gateway list [\#1118](https://github.com/nymtech/nym/pull/1118) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- Bump follow-redirects from 1.14.7 to 1.14.8 in /contracts/basic-bandwidth-generation [\#1117](https://github.com/nymtech/nym/pull/1117) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump follow-redirects from 1.14.3 to 1.14.8 in /explorer [\#1116](https://github.com/nymtech/nym/pull/1116) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump follow-redirects from 1.14.5 to 1.14.8 in /nym-wallet [\#1115](https://github.com/nymtech/nym/pull/1115) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump follow-redirects from 1.14.7 to 1.14.8 in /clients/native/examples/js-examples/websocket [\#1114](https://github.com/nymtech/nym/pull/1114) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump follow-redirects from 1.14.7 to 1.14.8 in /testnet-faucet [\#1113](https://github.com/nymtech/nym/pull/1113) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump follow-redirects from 1.14.1 to 1.14.8 in /clients/webassembly/js-example [\#1112](https://github.com/nymtech/nym/pull/1112) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Feature/vesting get current period [\#1111](https://github.com/nymtech/nym/pull/1111) ([durch](https://github.com/durch))
|
||||
- Bump simple-get from 2.8.1 to 2.8.2 in /contracts/basic-bandwidth-generation [\#1110](https://github.com/nymtech/nym/pull/1110) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump simple-get from 3.1.0 to 3.1.1 in /explorer [\#1109](https://github.com/nymtech/nym/pull/1109) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump simple-get from 3.1.0 to 3.1.1 in /clients/tauri-client [\#1108](https://github.com/nymtech/nym/pull/1108) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump simple-get from 3.1.0 to 3.1.1 in /nym-wallet [\#1107](https://github.com/nymtech/nym/pull/1107) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump node-sass from 4.14.1 to 7.0.0 in /clients/webassembly/react-example [\#1105](https://github.com/nymtech/nym/pull/1105) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Fix hardcoded period logic [\#1104](https://github.com/nymtech/nym/pull/1104) ([durch](https://github.com/durch))
|
||||
- Fixed underflow in rewarding all delegators [\#1099](https://github.com/nymtech/nym/pull/1099) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Emit original bond as part of rewarding event [\#1094](https://github.com/nymtech/nym/pull/1094) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Add UpdateMixnodeConfigOnBehalf to vestng contract [\#1091](https://github.com/nymtech/nym/pull/1091) ([durch](https://github.com/durch))
|
||||
- Fixes infinite loops in requests involving pagination [\#1085](https://github.com/nymtech/nym/pull/1085) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Removes migration code [\#1071](https://github.com/nymtech/nym/pull/1071) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- feature/pedersen-commitments [\#1048](https://github.com/nymtech/nym/pull/1048) ([danielementary](https://github.com/danielementary))
|
||||
- Feature/reuse init owner [\#970](https://github.com/nymtech/nym/pull/970) ([neacsu](https://github.com/neacsu))
|
||||
|
||||
## [nym-wallet-v1.0.0](https://github.com/nymtech/nym/tree/nym-wallet-v1.0.0) (2022-02-03)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/v0.12.1...nym-wallet-v1.0.0)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- \[Feature Request\] Please enable registration without need for Telegram account [\#1016](https://github.com/nymtech/nym/issues/1016)
|
||||
- Fast mixnode launch with a pre-built ISO + VM software [\#1001](https://github.com/nymtech/nym/issues/1001)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- \[Issue\] [\#1000](https://github.com/nymtech/nym/issues/1000)
|
||||
- \[Issue\] `nym-client` requires multiple attempts to run a server [\#869](https://github.com/nymtech/nym/issues/869)
|
||||
- De-'float'-ing `Interval` \(`Display` impl + `serde`\) [\#1065](https://github.com/nymtech/nym/pull/1065) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- display client address on wallet creation [\#1058](https://github.com/nymtech/nym/pull/1058) ([fmtabbara](https://github.com/fmtabbara))
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Rewarded set inclusion probability API endpoint [\#1037](https://github.com/nymtech/nym/issues/1037)
|
||||
- Update cw-storage-plus to 0.11 [\#1032](https://github.com/nymtech/nym/issues/1032)
|
||||
- Change `u128` fields in `RewardEstimationResponse` to `u64` [\#1029](https://github.com/nymtech/nym/issues/1029)
|
||||
- Test out the mainnet Gravity Bridge [\#1006](https://github.com/nymtech/nym/issues/1006)
|
||||
- Add vesting contract interface to nym-wallet [\#959](https://github.com/nymtech/nym/issues/959)
|
||||
- Mixnode crash [\#486](https://github.com/nymtech/nym/issues/486)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- create custom urls for mainnet [\#1095](https://github.com/nymtech/nym/pull/1095) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Wallet signing on MacOS [\#1093](https://github.com/nymtech/nym/pull/1093) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- Fix rust 2018 idioms warnings [\#1092](https://github.com/nymtech/nym/pull/1092) ([octol](https://github.com/octol))
|
||||
- Prevent contract overwriting [\#1090](https://github.com/nymtech/nym/pull/1090) ([durch](https://github.com/durch))
|
||||
- Logout operation [\#1087](https://github.com/nymtech/nym/pull/1087) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Update to rust edition 2021 everywhere [\#1086](https://github.com/nymtech/nym/pull/1086) ([octol](https://github.com/octol))
|
||||
- Tag contract errors, and print out lines for easier QA [\#1084](https://github.com/nymtech/nym/pull/1084) ([durch](https://github.com/durch))
|
||||
- Feature/flexible vesting + utility queries [\#1083](https://github.com/nymtech/nym/pull/1083) ([durch](https://github.com/durch))
|
||||
- Bump @openzeppelin/contracts from 4.3.1 to 4.4.2 in /contracts/basic-bandwidth-generation [\#1082](https://github.com/nymtech/nym/pull/1082) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump nth-check from 2.0.0 to 2.0.1 in /clients/native/examples/js-examples/websocket [\#1081](https://github.com/nymtech/nym/pull/1081) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump url-parse from 1.5.1 to 1.5.4 in /clients/native/examples/js-examples/websocket [\#1080](https://github.com/nymtech/nym/pull/1080) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump follow-redirects from 1.14.1 to 1.14.7 in /clients/native/examples/js-examples/websocket [\#1079](https://github.com/nymtech/nym/pull/1079) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump nanoid from 3.1.23 to 3.2.0 in /clients/native/examples/js-examples/websocket [\#1078](https://github.com/nymtech/nym/pull/1078) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Setup basic test for mixnode stats reporting [\#1077](https://github.com/nymtech/nym/pull/1077) ([octol](https://github.com/octol))
|
||||
- Make wallet\_address mandatory for mixnode init [\#1076](https://github.com/nymtech/nym/pull/1076) ([octol](https://github.com/octol))
|
||||
- Tidy nym-mixnode module visibility [\#1075](https://github.com/nymtech/nym/pull/1075) ([octol](https://github.com/octol))
|
||||
- Feature/wallet login with password [\#1074](https://github.com/nymtech/nym/pull/1074) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Add trait to mock client dependency in DelayForwarder [\#1073](https://github.com/nymtech/nym/pull/1073) ([octol](https://github.com/octol))
|
||||
- Bump rust-version to latest stable for nym-mixnode [\#1072](https://github.com/nymtech/nym/pull/1072) ([octol](https://github.com/octol))
|
||||
- Fixes CI for our wasm build [\#1069](https://github.com/nymtech/nym/pull/1069) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Add @octol as codeowner [\#1068](https://github.com/nymtech/nym/pull/1068) ([octol](https://github.com/octol))
|
||||
- set-up inclusion probability [\#1067](https://github.com/nymtech/nym/pull/1067) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Feature/wasm client [\#1066](https://github.com/nymtech/nym/pull/1066) ([neacsu](https://github.com/neacsu))
|
||||
- Changed bech32\_prefix from punk to nymt [\#1064](https://github.com/nymtech/nym/pull/1064) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Bump nanoid from 3.1.30 to 3.2.0 in /testnet-faucet [\#1063](https://github.com/nymtech/nym/pull/1063) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump nanoid from 3.1.30 to 3.2.0 in /nym-wallet [\#1062](https://github.com/nymtech/nym/pull/1062) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Rework vesting contract storage [\#1061](https://github.com/nymtech/nym/pull/1061) ([durch](https://github.com/durch))
|
||||
- Mixnet Contract constants extraction [\#1060](https://github.com/nymtech/nym/pull/1060) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- fix: make explorer footer year dynamic [\#1059](https://github.com/nymtech/nym/pull/1059) ([martinyung](https://github.com/martinyung))
|
||||
- Add mnemonic just on creation, to display it [\#1057](https://github.com/nymtech/nym/pull/1057) ([neacsu](https://github.com/neacsu))
|
||||
- Network Explorer: updates to API and UI to show the active set [\#1056](https://github.com/nymtech/nym/pull/1056) ([mmsinclair](https://github.com/mmsinclair))
|
||||
- Made contract addresses for query NymdClient construction optional [\#1055](https://github.com/nymtech/nym/pull/1055) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Introduced RPC query for total token supply [\#1053](https://github.com/nymtech/nym/pull/1053) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/tokio console [\#1052](https://github.com/nymtech/nym/pull/1052) ([durch](https://github.com/durch))
|
||||
- Implemented beta clippy lint recommendations [\#1051](https://github.com/nymtech/nym/pull/1051) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- add new function to update profit percentage [\#1050](https://github.com/nymtech/nym/pull/1050) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Upgrade Clap and use declarative argument parsing for nym-mixnode [\#1047](https://github.com/nymtech/nym/pull/1047) ([octol](https://github.com/octol))
|
||||
- Feature/additional bond validation [\#1046](https://github.com/nymtech/nym/pull/1046) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Fix clippy on relevant lints [\#1044](https://github.com/nymtech/nym/pull/1044) ([neacsu](https://github.com/neacsu))
|
||||
- Bump shelljs from 0.8.4 to 0.8.5 in /contracts/basic-bandwidth-generation [\#1043](https://github.com/nymtech/nym/pull/1043) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Endpoint for rewarded set inclusion probabilities [\#1042](https://github.com/nymtech/nym/pull/1042) ([durch](https://github.com/durch))
|
||||
- Bump follow-redirects from 1.14.4 to 1.14.7 in /contracts/basic-bandwidth-generation [\#1041](https://github.com/nymtech/nym/pull/1041) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Bump follow-redirects from 1.14.5 to 1.14.7 in /testnet-faucet [\#1040](https://github.com/nymtech/nym/pull/1040) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Feature/node settings update [\#1036](https://github.com/nymtech/nym/pull/1036) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Migrate to cw-storage-plus 0.11.1 [\#1035](https://github.com/nymtech/nym/pull/1035) ([durch](https://github.com/durch))
|
||||
- Bump @openzeppelin/contracts from 4.4.1 to 4.4.2 in /contracts/basic-bandwidth-generation [\#1034](https://github.com/nymtech/nym/pull/1034) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Feature/configurable wallet [\#1033](https://github.com/nymtech/nym/pull/1033) ([neacsu](https://github.com/neacsu))
|
||||
- Feature/downcast reward estimation [\#1031](https://github.com/nymtech/nym/pull/1031) ([durch](https://github.com/durch))
|
||||
- Wallet UI updates [\#1028](https://github.com/nymtech/nym/pull/1028) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Remove migration code [\#1027](https://github.com/nymtech/nym/pull/1027) ([neacsu](https://github.com/neacsu))
|
||||
- Chore/stricter dependency requirements [\#1025](https://github.com/nymtech/nym/pull/1025) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/validator api client endpoints [\#1024](https://github.com/nymtech/nym/pull/1024) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Updated cosmrs to 0.4.1 [\#1023](https://github.com/nymtech/nym/pull/1023) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/testnet deploy scripts [\#1022](https://github.com/nymtech/nym/pull/1022) ([mfahampshire](https://github.com/mfahampshire))
|
||||
- Changed wallet's client to a full validator client [\#1021](https://github.com/nymtech/nym/pull/1021) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Fix 404 link [\#1020](https://github.com/nymtech/nym/pull/1020) ([RiccardoMasutti](https://github.com/RiccardoMasutti))
|
||||
- Feature/additional mixnode endpoints [\#1019](https://github.com/nymtech/nym/pull/1019) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Introduced denom check when trying to withdraw vested coins [\#1018](https://github.com/nymtech/nym/pull/1018) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Add network defaults for qa [\#1017](https://github.com/nymtech/nym/pull/1017) ([neacsu](https://github.com/neacsu))
|
||||
- Feature/expanded events [\#1015](https://github.com/nymtech/nym/pull/1015) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- update frontend to use new profit update api [\#1014](https://github.com/nymtech/nym/pull/1014) ([fmtabbara](https://github.com/fmtabbara))
|
||||
- Feature/node state endpoint [\#1013](https://github.com/nymtech/nym/pull/1013) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Feature/hourly set updates [\#1012](https://github.com/nymtech/nym/pull/1012) ([durch](https://github.com/durch))
|
||||
- Feature/remove unused profit margin [\#1011](https://github.com/nymtech/nym/pull/1011) ([neacsu](https://github.com/neacsu))
|
||||
- Feature/explorer node status [\#1010](https://github.com/nymtech/nym/pull/1010) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Use serial integer instead of random [\#1009](https://github.com/nymtech/nym/pull/1009) ([durch](https://github.com/durch))
|
||||
- Feature/configure profit [\#1008](https://github.com/nymtech/nym/pull/1008) ([neacsu](https://github.com/neacsu))
|
||||
- Feature/fix gateway sign [\#1004](https://github.com/nymtech/nym/pull/1004) ([neacsu](https://github.com/neacsu))
|
||||
- Fix clippy [\#1003](https://github.com/nymtech/nym/pull/1003) ([neacsu](https://github.com/neacsu))
|
||||
- Update wallet version [\#998](https://github.com/nymtech/nym/pull/998) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Fix wallet build instructions [\#997](https://github.com/nymtech/nym/pull/997) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Make the separation between testnet-mode and erc20 bandwidth mode clearer [\#994](https://github.com/nymtech/nym/pull/994) ([neacsu](https://github.com/neacsu))
|
||||
- Bump @openzeppelin/contracts from 3.4.0 to 4.4.1 in /contracts/basic-bandwidth-generation [\#983](https://github.com/nymtech/nym/pull/983) ([dependabot[bot]](https://github.com/apps/dependabot))
|
||||
- Feature/implicit runtime [\#973](https://github.com/nymtech/nym/pull/973) ([jstuczyn](https://github.com/jstuczyn))
|
||||
- Differentiate staking and ownership [\#961](https://github.com/nymtech/nym/pull/961) ([durch](https://github.com/durch))
|
||||
|
||||
## [v0.12.1](https://github.com/nymtech/nym/tree/v0.12.1) (2021-12-23)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/v0.12.0...v0.12.1)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Add version check to binaries [\#967](https://github.com/nymtech/nym/issues/967)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- \[Issue\] NYM wallet doesn't work after login [\#995](https://github.com/nymtech/nym/issues/995)
|
||||
- \[Issue\] [\#993](https://github.com/nymtech/nym/issues/993)
|
||||
- NYM wallet setup trouble\[Issue\] [\#958](https://github.com/nymtech/nym/issues/958)
|
||||
|
||||
## [v0.12.0](https://github.com/nymtech/nym/tree/v0.12.0) (2021-12-21)
|
||||
|
||||
[Full Changelog](https://github.com/nymtech/nym/compare/v0.11.0...v0.12.0)
|
||||
@@ -58,6 +390,7 @@
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Update wallet to align with versioning on nodes and gateways [\#991](https://github.com/nymtech/nym/pull/991) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Fix success view messages. [\#990](https://github.com/nymtech/nym/pull/990) ([tommyv1987](https://github.com/tommyv1987))
|
||||
- Feature/enable signature check [\#989](https://github.com/nymtech/nym/pull/989) ([neacsu](https://github.com/neacsu))
|
||||
- Update mixnet contract address [\#988](https://github.com/nymtech/nym/pull/988) ([neacsu](https://github.com/neacsu))
|
||||
|
||||
Generated
+19
-8
@@ -581,7 +581,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "client-core"
|
||||
version = "1.0.0-rc.2"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"config",
|
||||
"crypto",
|
||||
@@ -648,6 +648,7 @@ dependencies = [
|
||||
name = "config"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"handlebars",
|
||||
"humantime-serde",
|
||||
"log",
|
||||
@@ -1594,7 +1595,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "explorer-api"
|
||||
version = "1.0.0-rc.2"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"humantime-serde",
|
||||
@@ -3043,7 +3044,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.0.0-rc.2"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"clap 2.34.0",
|
||||
"client-core",
|
||||
@@ -3078,7 +3079,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-gateway"
|
||||
version = "1.0.0-rc.2"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3124,7 +3125,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnode"
|
||||
version = "1.0.0-rc.2"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bs58",
|
||||
@@ -3162,20 +3163,26 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.0.0-rc.2"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"clap 2.34.0",
|
||||
"dirs",
|
||||
"futures",
|
||||
"ipnetwork",
|
||||
"log",
|
||||
"network-defaults",
|
||||
"nymsphinx",
|
||||
"ordered-buffer",
|
||||
"pretty_env_logger",
|
||||
"proxy-helpers",
|
||||
"publicsuffix",
|
||||
"rand 0.7.3",
|
||||
"rocket",
|
||||
"serde",
|
||||
"socks5-requests",
|
||||
"sqlx",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"websocket-requests",
|
||||
@@ -3183,7 +3190,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.0.0-rc.2"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"clap 2.34.0",
|
||||
"client-core",
|
||||
@@ -3219,7 +3226,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-validator-api"
|
||||
version = "1.0.0-rc.2"
|
||||
version = "1.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3243,6 +3250,7 @@ dependencies = [
|
||||
"mixnet-contract-common",
|
||||
"nymcoconut",
|
||||
"nymsphinx",
|
||||
"okapi",
|
||||
"pin-project",
|
||||
"pretty_env_logger",
|
||||
"rand 0.7.3",
|
||||
@@ -3250,7 +3258,9 @@ dependencies = [
|
||||
"reqwest",
|
||||
"rocket",
|
||||
"rocket_cors",
|
||||
"rocket_okapi",
|
||||
"rocket_sync_db_pools",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
@@ -5215,6 +5225,7 @@ dependencies = [
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"crc",
|
||||
"crossbeam-queue",
|
||||
"either",
|
||||
|
||||
@@ -9,6 +9,10 @@ overflow-checks = true
|
||||
[profile.dev]
|
||||
panic = "abort"
|
||||
|
||||
[profile.test]
|
||||
# equivalent of running in `--release` (but since we're in test profile we're keeping overflow checks and all of those by default)
|
||||
opt-level = 3
|
||||
|
||||
[workspace]
|
||||
|
||||
resolver = "2"
|
||||
|
||||
@@ -26,7 +26,7 @@ clippy-all-wallet:
|
||||
cargo clippy --workspace --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
|
||||
|
||||
test-main:
|
||||
cargo test --all-features --workspace --release
|
||||
cargo test --all-features --workspace
|
||||
|
||||
test-contracts:
|
||||
cargo test --manifest-path contracts/Cargo.toml --all-features
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "client-core"
|
||||
version = "1.0.0-rc.2"
|
||||
version = "1.0.1"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -110,8 +110,8 @@ impl<T: NymConfig> Config<T> {
|
||||
self.client.id = id;
|
||||
}
|
||||
|
||||
pub fn with_testnet_mode(&mut self, testnet_mode: bool) {
|
||||
self.client.testnet_mode = testnet_mode;
|
||||
pub fn with_disabled_credentials(&mut self, disabled_credentials_mode: bool) {
|
||||
self.client.disabled_credentials_mode = disabled_credentials_mode;
|
||||
}
|
||||
|
||||
pub fn with_gateway_endpoint<S: Into<String>>(&mut self, id: S, owner: S, listener: S) {
|
||||
@@ -154,8 +154,8 @@ impl<T: NymConfig> Config<T> {
|
||||
self.client.id.clone()
|
||||
}
|
||||
|
||||
pub fn get_testnet_mode(&self) -> bool {
|
||||
self.client.testnet_mode
|
||||
pub fn get_disabled_credentials_mode(&self) -> bool {
|
||||
self.client.disabled_credentials_mode
|
||||
}
|
||||
|
||||
pub fn get_nym_root_directory(&self) -> PathBuf {
|
||||
@@ -294,10 +294,10 @@ pub struct Client<T> {
|
||||
/// ID specifies the human readable ID of this particular client.
|
||||
id: String,
|
||||
|
||||
/// Indicates whether this client is running in a testnet mode, thus attempting
|
||||
/// Indicates whether this client is running in a disabled credentials mode, thus attempting
|
||||
/// to claim bandwidth without presenting bandwidth credentials.
|
||||
#[serde(default)]
|
||||
testnet_mode: bool,
|
||||
disabled_credentials_mode: bool,
|
||||
|
||||
/// Addresses to APIs running on validator from which the client gets the view of the network.
|
||||
validator_api_urls: Vec<Url>,
|
||||
@@ -354,7 +354,7 @@ impl<T: NymConfig> Default for Client<T> {
|
||||
Client {
|
||||
version: env!("CARGO_PKG_VERSION").to_string(),
|
||||
id: "".to_string(),
|
||||
testnet_mode: false,
|
||||
disabled_credentials_mode: true,
|
||||
validator_api_urls: default_api_endpoints(),
|
||||
private_identity_key_file: Default::default(),
|
||||
public_identity_key_file: Default::default(),
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.0.0-rc.2"
|
||||
version = "1.0.1"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
|
||||
@@ -592,9 +592,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.14"
|
||||
@@ -4825,9 +4825,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"async": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.17.14"
|
||||
|
||||
@@ -19,9 +19,9 @@ version = '{{ client.version }}'
|
||||
# Human readable ID of this particular client.
|
||||
id = '{{ client.id }}'
|
||||
|
||||
# Indicates whether this client is running in a testnet mode, thus attempting
|
||||
# Indicates whether this client is running in a disabled credentials mode, thus attempting
|
||||
# to claim bandwidth without presenting bandwidth credentials.
|
||||
testnet_mode = {{ client.testnet_mode }}
|
||||
disabled_credentials_mode = {{ client.disabled_credentials_mode }}
|
||||
|
||||
# Addresses to APIs running on validator from which the client gets the view of the network.
|
||||
validator_api_urls = [
|
||||
|
||||
@@ -199,8 +199,8 @@ impl NymClient {
|
||||
Some(bandwidth_controller),
|
||||
);
|
||||
|
||||
if self.config.get_base().get_testnet_mode() {
|
||||
gateway_client.set_testnet_mode(true)
|
||||
if self.config.get_base().get_disabled_credentials_mode() {
|
||||
gateway_client.set_disabled_credentials_mode(true)
|
||||
}
|
||||
gateway_client
|
||||
.authenticate_and_start()
|
||||
|
||||
@@ -24,8 +24,8 @@ use crate::commands::override_config;
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use crate::commands::{
|
||||
DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY, ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
|
||||
TESTNET_MODE_ARG_NAME,
|
||||
DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY, ENABLED_CREDENTIALS_MODE_ARG_NAME,
|
||||
ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
|
||||
};
|
||||
|
||||
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
@@ -66,22 +66,22 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let app = app
|
||||
.arg(
|
||||
Arg::with_name(TESTNET_MODE_ARG_NAME)
|
||||
.long(TESTNET_MODE_ARG_NAME)
|
||||
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
|
||||
Arg::with_name(ENABLED_CREDENTIALS_MODE_ARG_NAME)
|
||||
.long(ENABLED_CREDENTIALS_MODE_ARG_NAME)
|
||||
.help("Set this client to work in a disabled credentials mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
|
||||
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
|
||||
)
|
||||
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
|
||||
.long(ETH_ENDPOINT_ARG_NAME)
|
||||
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
|
||||
.takes_value(true)
|
||||
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_ENDPOINT)
|
||||
.default_value_if(ENABLED_CREDENTIALS_MODE_ARG_NAME, None, DEFAULT_ETH_ENDPOINT)
|
||||
.required(true))
|
||||
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
|
||||
.long(ETH_PRIVATE_KEY_ARG_NAME)
|
||||
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
|
||||
.takes_value(true)
|
||||
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_PRIVATE_KEY)
|
||||
.default_value_if(ENABLED_CREDENTIALS_MODE_ARG_NAME, None, DEFAULT_ETH_PRIVATE_KEY)
|
||||
.required(true)
|
||||
);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ use crate::client::config::{Config, SocketType};
|
||||
use clap::ArgMatches;
|
||||
use url::Url;
|
||||
|
||||
pub(crate) const TESTNET_MODE_ARG_NAME: &str = "testnet-mode";
|
||||
pub(crate) const ENABLED_CREDENTIALS_MODE_ARG_NAME: &str = "enabled-credentials-mode";
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub(crate) const ETH_ENDPOINT_ARG_NAME: &str = "eth_endpoint";
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
@@ -72,8 +72,8 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> C
|
||||
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY);
|
||||
}
|
||||
|
||||
if !cfg!(feature = "eth") || matches.is_present(TESTNET_MODE_ARG_NAME) {
|
||||
config.get_base_mut().with_testnet_mode(true)
|
||||
if matches.is_present(ENABLED_CREDENTIALS_MODE_ARG_NAME) {
|
||||
config.get_base_mut().with_disabled_credentials(false)
|
||||
}
|
||||
|
||||
config
|
||||
|
||||
@@ -6,7 +6,9 @@ use crate::client::NymClient;
|
||||
use crate::commands::override_config;
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use crate::commands::{ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME, TESTNET_MODE_ARG_NAME};
|
||||
use crate::commands::{
|
||||
ENABLED_CREDENTIALS_MODE_ARG_NAME, ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
|
||||
};
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use config::NymConfig;
|
||||
use log::*;
|
||||
@@ -46,9 +48,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let app = app
|
||||
.arg(
|
||||
Arg::with_name(TESTNET_MODE_ARG_NAME)
|
||||
.long(TESTNET_MODE_ARG_NAME)
|
||||
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
|
||||
Arg::with_name(ENABLED_CREDENTIALS_MODE_ARG_NAME)
|
||||
.long(ENABLED_CREDENTIALS_MODE_ARG_NAME)
|
||||
.help("Set this client to work in a enabled credentials mode that would attempt to use gateway with bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
|
||||
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
|
||||
)
|
||||
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{crate_version, App, ArgMatches};
|
||||
use network_defaults::DEFAULT_NETWORK;
|
||||
|
||||
pub mod client;
|
||||
pub mod commands;
|
||||
@@ -67,6 +68,7 @@ fn long_version() -> String {
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
"#,
|
||||
"Build Timestamp:",
|
||||
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||
@@ -84,6 +86,8 @@ fn long_version() -> String {
|
||||
env!("VERGEN_RUSTC_CHANNEL"),
|
||||
"cargo Profile:",
|
||||
env!("VERGEN_CARGO_PROFILE"),
|
||||
"Network:",
|
||||
DEFAULT_NETWORK
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.0.0-rc.2"
|
||||
version = "1.0.1"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.56"
|
||||
|
||||
@@ -19,9 +19,9 @@ version = '{{ client.version }}'
|
||||
# Human readable ID of this particular client.
|
||||
id = '{{ client.id }}'
|
||||
|
||||
# Indicates whether this client is running in a testnet mode, thus attempting
|
||||
# Indicates whether this client is running in a disabled credentials mode, thus attempting
|
||||
# to claim bandwidth without presenting bandwidth credentials.
|
||||
testnet_mode = {{ client.testnet_mode }}
|
||||
disabled_credentials_mode = {{ client.disabled_credentials_mode }}
|
||||
|
||||
# Addresses to APIs running on validator from which the client gets the view of the network.
|
||||
validator_api_urls = [
|
||||
|
||||
@@ -187,8 +187,8 @@ impl NymClient {
|
||||
Some(bandwidth_controller),
|
||||
);
|
||||
|
||||
if self.config.get_base().get_testnet_mode() {
|
||||
gateway_client.set_testnet_mode(true)
|
||||
if self.config.get_base().get_disabled_credentials_mode() {
|
||||
gateway_client.set_disabled_credentials_mode(true)
|
||||
}
|
||||
gateway_client
|
||||
.authenticate_and_start()
|
||||
|
||||
@@ -22,8 +22,8 @@ use crate::commands::override_config;
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use crate::commands::{
|
||||
DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY, ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
|
||||
TESTNET_MODE_ARG_NAME,
|
||||
DEFAULT_ETH_ENDPOINT, DEFAULT_ETH_PRIVATE_KEY, ENABLED_CREDENTIALS_MODE_ARG_NAME,
|
||||
ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
|
||||
};
|
||||
|
||||
pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
@@ -66,22 +66,22 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let app = app
|
||||
.arg(
|
||||
Arg::with_name(TESTNET_MODE_ARG_NAME)
|
||||
.long(TESTNET_MODE_ARG_NAME)
|
||||
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
|
||||
Arg::with_name(ENABLED_CREDENTIALS_MODE_ARG_NAME)
|
||||
.long(ENABLED_CREDENTIALS_MODE_ARG_NAME)
|
||||
.help("Set this client to work in a enabled credentials mode that would attempt to use gateway with bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
|
||||
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
|
||||
)
|
||||
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
|
||||
.long(ETH_ENDPOINT_ARG_NAME)
|
||||
.help("URL of an Ethereum full node that we want to use for getting bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
|
||||
.takes_value(true)
|
||||
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_ENDPOINT)
|
||||
.default_value_if(ENABLED_CREDENTIALS_MODE_ARG_NAME, None, DEFAULT_ETH_ENDPOINT)
|
||||
.required(true))
|
||||
.arg(Arg::with_name(ETH_PRIVATE_KEY_ARG_NAME)
|
||||
.long(ETH_PRIVATE_KEY_ARG_NAME)
|
||||
.help("Ethereum private key used for obtaining bandwidth tokens from ERC20 tokens. If you don't want to set this value, use --testnet-mode instead")
|
||||
.takes_value(true)
|
||||
.default_value_if(TESTNET_MODE_ARG_NAME, None, DEFAULT_ETH_PRIVATE_KEY)
|
||||
.default_value_if(ENABLED_CREDENTIALS_MODE_ARG_NAME, None, DEFAULT_ETH_PRIVATE_KEY)
|
||||
.required(true)
|
||||
);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ pub(crate) mod init;
|
||||
pub(crate) mod run;
|
||||
pub(crate) mod upgrade;
|
||||
|
||||
pub(crate) const TESTNET_MODE_ARG_NAME: &str = "testnet-mode";
|
||||
pub(crate) const ENABLED_CREDENTIALS_MODE_ARG_NAME: &str = "enabled-credentials-mode";
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
pub(crate) const ETH_ENDPOINT_ARG_NAME: &str = "eth_endpoint";
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
@@ -68,8 +68,8 @@ pub(crate) fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> C
|
||||
.with_eth_private_key(DEFAULT_ETH_PRIVATE_KEY);
|
||||
}
|
||||
|
||||
if !cfg!(feature = "eth") || matches.is_present(TESTNET_MODE_ARG_NAME) {
|
||||
config.get_base_mut().with_testnet_mode(true)
|
||||
if matches.is_present(ENABLED_CREDENTIALS_MODE_ARG_NAME) {
|
||||
config.get_base_mut().with_disabled_credentials(false)
|
||||
}
|
||||
|
||||
config
|
||||
|
||||
@@ -6,7 +6,9 @@ use crate::client::NymClient;
|
||||
use crate::commands::override_config;
|
||||
#[cfg(feature = "eth")]
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
use crate::commands::{ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME, TESTNET_MODE_ARG_NAME};
|
||||
use crate::commands::{
|
||||
ENABLED_CREDENTIALS_MODE_ARG_NAME, ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME,
|
||||
};
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use config::NymConfig;
|
||||
use log::*;
|
||||
@@ -52,9 +54,9 @@ pub fn command_args<'a, 'b>() -> clap::App<'a, 'b> {
|
||||
#[cfg(not(feature = "coconut"))]
|
||||
let app = app
|
||||
.arg(
|
||||
Arg::with_name(TESTNET_MODE_ARG_NAME)
|
||||
.long(TESTNET_MODE_ARG_NAME)
|
||||
.help("Set this client to work in a testnet mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
|
||||
Arg::with_name(ENABLED_CREDENTIALS_MODE_ARG_NAME)
|
||||
.long(ENABLED_CREDENTIALS_MODE_ARG_NAME)
|
||||
.help("Set this client to work in a disabled credentials mode that would attempt to use gateway without bandwidth credential requirement. If this value is set, --eth_endpoint and --eth_private_key don't need to be set.")
|
||||
.conflicts_with_all(&[ETH_ENDPOINT_ARG_NAME, ETH_PRIVATE_KEY_ARG_NAME])
|
||||
)
|
||||
.arg(Arg::with_name(ETH_ENDPOINT_ARG_NAME)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{crate_version, App, ArgMatches};
|
||||
use network_defaults::DEFAULT_NETWORK;
|
||||
|
||||
pub mod client;
|
||||
mod commands;
|
||||
@@ -67,6 +68,7 @@ fn long_version() -> String {
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
{:<20}{}
|
||||
"#,
|
||||
"Build Timestamp:",
|
||||
env!("VERGEN_BUILD_TIMESTAMP"),
|
||||
@@ -84,6 +86,8 @@ fn long_version() -> String {
|
||||
env!("VERGEN_RUSTC_CHANNEL"),
|
||||
"cargo Profile:",
|
||||
env!("VERGEN_CARGO_PROFILE"),
|
||||
"Network:",
|
||||
DEFAULT_NETWORK
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ use proxy_helpers::connection_controller::{
|
||||
};
|
||||
use proxy_helpers::proxy_runner::ProxyRunner;
|
||||
use rand::RngCore;
|
||||
use socks5_requests::{ConnectionId, RemoteAddress, Request};
|
||||
use socks5_requests::{ConnectionId, Message, RemoteAddress, Request};
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
use std::pin::Pin;
|
||||
@@ -224,8 +224,9 @@ impl SocksClient {
|
||||
|
||||
async fn send_connect_to_mixnet(&mut self, remote_address: RemoteAddress) {
|
||||
let req = Request::new_connect(self.connection_id, remote_address, self.self_address);
|
||||
let msg = Message::Request(req);
|
||||
|
||||
let input_message = InputMessage::new_fresh(self.service_provider, req.into_bytes(), false);
|
||||
let input_message = InputMessage::new_fresh(self.service_provider, msg.into_bytes(), false);
|
||||
self.input_sender.unbounded_send(input_message).unwrap();
|
||||
}
|
||||
|
||||
@@ -252,7 +253,8 @@ impl SocksClient {
|
||||
)
|
||||
.run(move |conn_id, read_data, socket_closed| {
|
||||
let provider_request = Request::new_send(conn_id, read_data, socket_closed);
|
||||
InputMessage::new_fresh(recipient, provider_request.into_bytes(), false)
|
||||
let provider_message = Message::Request(provider_request);
|
||||
InputMessage::new_fresh(recipient, provider_message.into_bytes(), false)
|
||||
})
|
||||
.await
|
||||
.into_inner();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "nym-client-wasm"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
|
||||
version = "1.0.0-rc.2"
|
||||
version = "1.0.1"
|
||||
edition = "2021"
|
||||
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
|
||||
license = "Apache-2.0"
|
||||
|
||||
+7
-8
@@ -24,8 +24,7 @@
|
||||
},
|
||||
"../pkg": {
|
||||
"name": "@nymproject/nym-client-wasm",
|
||||
"version": "0.12.0",
|
||||
"license": "Apache-2.0"
|
||||
"version": "0.0.1"
|
||||
},
|
||||
"node_modules/@discoveryjs/json-ext": {
|
||||
"version": "0.5.7",
|
||||
@@ -586,9 +585,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.14"
|
||||
@@ -4305,9 +4304,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"async": {
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||
"version": "2.6.4",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
|
||||
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.17.14"
|
||||
|
||||
@@ -26,7 +26,7 @@ const DEFAULT_GATEWAY_RESPONSE_TIMEOUT: Duration = Duration::from_millis(1_500);
|
||||
#[wasm_bindgen]
|
||||
pub struct NymClient {
|
||||
validator_server: Url,
|
||||
testnet_mode: bool,
|
||||
disabled_credentials_mode: bool,
|
||||
|
||||
// TODO: technically this doesn't need to be an Arc since wasm is run on a single thread
|
||||
// however, once we eventually combine this code with the native-client's, it will make things
|
||||
@@ -72,7 +72,7 @@ impl NymClient {
|
||||
|
||||
on_message: None,
|
||||
on_gateway_connect: None,
|
||||
testnet_mode: true,
|
||||
disabled_credentials_mode: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,9 +85,12 @@ impl NymClient {
|
||||
self.on_gateway_connect = Some(on_connect)
|
||||
}
|
||||
|
||||
pub fn set_testnet_mode(&mut self, testnet_mode: bool) {
|
||||
console_log!("Setting testnet mode to {}", testnet_mode);
|
||||
self.testnet_mode = testnet_mode;
|
||||
pub fn set_disabled_credentials_mode(&mut self, disabled_credentials_mode: bool) {
|
||||
console_log!(
|
||||
"Setting disabled credentials mode to {}",
|
||||
disabled_credentials_mode
|
||||
);
|
||||
self.disabled_credentials_mode = disabled_credentials_mode;
|
||||
}
|
||||
|
||||
fn self_recipient(&self) -> Recipient {
|
||||
@@ -107,7 +110,7 @@ impl NymClient {
|
||||
|
||||
// Right now it's impossible to have async exported functions to take `&self` rather than self
|
||||
pub async fn initial_setup(self) -> Self {
|
||||
let testnet_mode = self.testnet_mode;
|
||||
let disabled_credentials_mode = self.disabled_credentials_mode;
|
||||
|
||||
let bandwidth_controller = None;
|
||||
|
||||
@@ -129,8 +132,8 @@ impl NymClient {
|
||||
bandwidth_controller,
|
||||
);
|
||||
|
||||
if testnet_mode {
|
||||
gateway_client.set_testnet_mode(true)
|
||||
if disabled_credentials_mode {
|
||||
gateway_client.set_disabled_credentials_mode(true)
|
||||
}
|
||||
|
||||
gateway_client
|
||||
|
||||
@@ -45,7 +45,7 @@ const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5);
|
||||
|
||||
pub struct GatewayClient {
|
||||
authenticated: bool,
|
||||
testnet_mode: bool,
|
||||
disabled_credentials_mode: bool,
|
||||
bandwidth_remaining: i64,
|
||||
gateway_address: String,
|
||||
gateway_identity: identity::PublicKey,
|
||||
@@ -83,7 +83,7 @@ impl GatewayClient {
|
||||
) -> Self {
|
||||
GatewayClient {
|
||||
authenticated: false,
|
||||
testnet_mode: false,
|
||||
disabled_credentials_mode: true,
|
||||
bandwidth_remaining: 0,
|
||||
gateway_address,
|
||||
gateway_identity,
|
||||
@@ -100,8 +100,8 @@ impl GatewayClient {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_testnet_mode(&mut self, testnet_mode: bool) {
|
||||
self.testnet_mode = testnet_mode
|
||||
pub fn set_disabled_credentials_mode(&mut self, disabled_credentials_mode: bool) {
|
||||
self.disabled_credentials_mode = disabled_credentials_mode
|
||||
}
|
||||
|
||||
// TODO: later convert into proper builder methods
|
||||
@@ -134,7 +134,7 @@ impl GatewayClient {
|
||||
|
||||
GatewayClient {
|
||||
authenticated: false,
|
||||
testnet_mode: false,
|
||||
disabled_credentials_mode: true,
|
||||
bandwidth_remaining: 0,
|
||||
gateway_address,
|
||||
gateway_identity,
|
||||
@@ -548,13 +548,13 @@ impl GatewayClient {
|
||||
if self.shared_key.is_none() {
|
||||
return Err(GatewayClientError::NoSharedKeyAvailable);
|
||||
}
|
||||
if self.bandwidth_controller.is_none() && !self.testnet_mode {
|
||||
if self.bandwidth_controller.is_none() && !self.disabled_credentials_mode {
|
||||
return Err(GatewayClientError::NoBandwidthControllerAvailable);
|
||||
}
|
||||
|
||||
warn!("Not enough bandwidth. Trying to get more bandwidth, this might take a while");
|
||||
if self.testnet_mode {
|
||||
info!("The client is running in testnet mode - attempting to claim bandwidth without a credential");
|
||||
if self.disabled_credentials_mode {
|
||||
info!("The client is running in disabled credentials mode - attempting to claim bandwidth without a credential");
|
||||
return self.try_claim_testnet_bandwidth().await;
|
||||
}
|
||||
|
||||
|
||||
@@ -133,20 +133,14 @@ impl Client {
|
||||
if current_attempt == 0 {
|
||||
None
|
||||
} else {
|
||||
// according to https://github.com/tokio-rs/tokio/issues/1953 there's an undocumented
|
||||
// limit of tokio delay of about 2 years.
|
||||
// let's ensure our delay is always on a sane side of being maximum 1 hour.
|
||||
let maximum_sane_delay = Duration::from_secs(60 * 60);
|
||||
let exp = 2_u32.checked_pow(current_attempt);
|
||||
let backoff = exp
|
||||
.and_then(|exp| self.config.initial_reconnection_backoff.checked_mul(exp))
|
||||
.unwrap_or(self.config.maximum_reconnection_backoff);
|
||||
|
||||
Some(std::cmp::min(
|
||||
maximum_sane_delay,
|
||||
std::cmp::min(
|
||||
self.config
|
||||
.initial_reconnection_backoff
|
||||
.checked_mul(2_u32.pow(current_attempt))
|
||||
.unwrap_or(self.config.maximum_reconnection_backoff),
|
||||
self.config.maximum_reconnection_backoff,
|
||||
),
|
||||
backoff,
|
||||
self.config.maximum_reconnection_backoff,
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -254,3 +248,45 @@ impl SendWithoutResponse for Client {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn dummy_client() -> Client {
|
||||
Client::new(Config {
|
||||
initial_reconnection_backoff: Duration::from_millis(10_000),
|
||||
maximum_reconnection_backoff: Duration::from_millis(300_000),
|
||||
initial_connection_timeout: Duration::from_millis(1_500),
|
||||
maximum_connection_buffer_size: 128,
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn determining_backoff_works_regardless_of_attempt() {
|
||||
let client = dummy_client();
|
||||
assert!(client.determine_backoff(0).is_none());
|
||||
assert!(client.determine_backoff(1).is_some());
|
||||
assert!(client.determine_backoff(2).is_some());
|
||||
assert_eq!(
|
||||
client.determine_backoff(16).unwrap(),
|
||||
client.config.maximum_reconnection_backoff
|
||||
);
|
||||
assert_eq!(
|
||||
client.determine_backoff(32).unwrap(),
|
||||
client.config.maximum_reconnection_backoff
|
||||
);
|
||||
assert_eq!(
|
||||
client.determine_backoff(1024).unwrap(),
|
||||
client.config.maximum_reconnection_backoff
|
||||
);
|
||||
assert_eq!(
|
||||
client.determine_backoff(65536).unwrap(),
|
||||
client.config.maximum_reconnection_backoff
|
||||
);
|
||||
assert_eq!(
|
||||
client.determine_backoff(u32::MAX).unwrap(),
|
||||
client.config.maximum_reconnection_backoff
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@ use crate::{validator_api, ValidatorClientError};
|
||||
use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse};
|
||||
use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond};
|
||||
use url::Url;
|
||||
|
||||
#[cfg(feature = "nymd-client")]
|
||||
use validator_api_requests::models::UptimeResponse;
|
||||
use validator_api_requests::models::{
|
||||
CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse,
|
||||
StakeSaturationResponse,
|
||||
@@ -582,6 +585,12 @@ impl<C> Client<C> {
|
||||
Ok(delegations)
|
||||
}
|
||||
|
||||
pub async fn get_mixnode_avg_uptimes(
|
||||
&self,
|
||||
) -> Result<Vec<UptimeResponse>, ValidatorClientError> {
|
||||
Ok(self.validator_api.get_mixnode_avg_uptimes().await?)
|
||||
}
|
||||
|
||||
pub async fn blind_sign(
|
||||
&self,
|
||||
request_body: &BlindSignRequestBody,
|
||||
|
||||
@@ -30,12 +30,26 @@ use cosmwasm_std::Coin as CosmWasmCoin;
|
||||
use prost::Message;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::time::Duration;
|
||||
|
||||
#[async_trait]
|
||||
impl CosmWasmClient for HttpClient {}
|
||||
impl CosmWasmClient for HttpClient {
|
||||
fn broadcast_polling_rate(&self) -> Duration {
|
||||
Duration::from_secs(4)
|
||||
}
|
||||
|
||||
fn broadcast_timeout(&self) -> Duration {
|
||||
Duration::from_secs(60)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait CosmWasmClient: rpc::Client {
|
||||
// this should probably get redesigned, but I'm leaving those like that temporarily to fix
|
||||
// the underlying issue more quickly
|
||||
fn broadcast_polling_rate(&self) -> Duration;
|
||||
fn broadcast_timeout(&self) -> Duration;
|
||||
|
||||
// helper method to remove duplicate code involved in making abci requests with protobuf messages
|
||||
// TODO: perhaps it should have an additional argument to determine whether the response should
|
||||
// require proof?
|
||||
@@ -253,6 +267,42 @@ pub trait CosmWasmClient: rpc::Client {
|
||||
Ok(rpc::Client::broadcast_tx_commit(self, tx).await?)
|
||||
}
|
||||
|
||||
async fn broadcast_tx(&self, tx: Transaction) -> Result<TxResponse, NymdError> {
|
||||
let broadcasted = CosmWasmClient::broadcast_tx_sync(self, tx).await?;
|
||||
|
||||
if broadcasted.code.is_err() {
|
||||
let code_val = broadcasted.code.value();
|
||||
return Err(NymdError::BroadcastTxErrorDeliverTx {
|
||||
hash: broadcasted.hash,
|
||||
height: None,
|
||||
code: code_val,
|
||||
raw_log: broadcasted.log.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let tx_hash = broadcasted.hash;
|
||||
|
||||
let start = tokio::time::Instant::now();
|
||||
loop {
|
||||
log::debug!(
|
||||
"Polling for result of including {} in a block...",
|
||||
broadcasted.hash
|
||||
);
|
||||
if tokio::time::Instant::now().duration_since(start) >= self.broadcast_timeout() {
|
||||
return Err(NymdError::BroadcastTimeout {
|
||||
hash: tx_hash,
|
||||
timeout: self.broadcast_timeout(),
|
||||
});
|
||||
}
|
||||
|
||||
if let Ok(poll_res) = self.get_tx(tx_hash).await {
|
||||
return Ok(poll_res);
|
||||
}
|
||||
|
||||
tokio::time::sleep(self.broadcast_polling_rate()).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_codes(&self) -> Result<Vec<Code>, NymdError> {
|
||||
let path = Some("/cosmwasm.wasm.v1.Query/Codes".parse().unwrap());
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ impl CheckResponse for broadcast::tx_commit::Response {
|
||||
if self.check_tx.code.is_err() {
|
||||
return Err(NymdError::BroadcastTxErrorCheckTx {
|
||||
hash: self.hash,
|
||||
height: self.height,
|
||||
height: Some(self.height),
|
||||
code: self.check_tx.code.value(),
|
||||
raw_log: self.check_tx.log.value().to_owned(),
|
||||
});
|
||||
@@ -28,7 +28,7 @@ impl CheckResponse for broadcast::tx_commit::Response {
|
||||
if self.deliver_tx.code.is_err() {
|
||||
return Err(NymdError::BroadcastTxErrorDeliverTx {
|
||||
hash: self.hash,
|
||||
height: self.height,
|
||||
height: Some(self.height),
|
||||
code: self.deliver_tx.code.value(),
|
||||
raw_log: self.deliver_tx.log.value().to_owned(),
|
||||
});
|
||||
@@ -38,6 +38,21 @@ impl CheckResponse for broadcast::tx_commit::Response {
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckResponse for crate::nymd::TxResponse {
|
||||
fn check_response(self) -> Result<Self, NymdError> {
|
||||
if self.tx_result.code.is_err() {
|
||||
return Err(NymdError::BroadcastTxErrorDeliverTx {
|
||||
hash: self.hash,
|
||||
height: Some(self.height),
|
||||
code: self.tx_result.code.value(),
|
||||
raw_log: self.tx_result.log.value().to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn compress_wasm_code(code: &[u8]) -> Result<Vec<u8>, NymdError> {
|
||||
// using compression level 9, same as cosmjs, that optimises for size
|
||||
let mut encoder = GzEncoder::new(Vec::new(), Compression::best());
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::bank::MsgSend;
|
||||
@@ -24,7 +25,7 @@ use crate::nymd::cosmwasm_client::types::*;
|
||||
use crate::nymd::error::NymdError;
|
||||
use crate::nymd::fee::{Fee, DEFAULT_SIMULATED_GAS_MULTIPLIER};
|
||||
use crate::nymd::wallet::DirectSecp256k1HdWallet;
|
||||
use crate::nymd::{CosmosCoin, GasPrice};
|
||||
use crate::nymd::{CosmosCoin, GasPrice, TxResponse};
|
||||
|
||||
// we need to have **a** valid secp256k1 signature for simulation purposes.
|
||||
// it doesn't matter what it is as long as it parses correctly
|
||||
@@ -35,6 +36,9 @@ const DUMMY_SECP256K1_SIGNATURE: &[u8] = &[
|
||||
91,
|
||||
];
|
||||
|
||||
const DEFAULT_BROADCAST_POLLING_RATE: Duration = Duration::from_secs(4);
|
||||
const DEFAULT_BROADCAST_TIMEOUT: Duration = Duration::from_secs(60);
|
||||
|
||||
#[async_trait]
|
||||
pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
fn signer(&self) -> &DirectSecp256k1HdWallet;
|
||||
@@ -111,12 +115,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.map_err(|_| NymdError::SerializationError("MsgStoreCode".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
.sign_and_broadcast_commit(sender_address, vec![upload_msg], fee, memo)
|
||||
.sign_and_broadcast(sender_address, vec![upload_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
let logs = parse_raw_logs(tx_res.deliver_tx.log)?;
|
||||
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
|
||||
let logs = parse_raw_logs(tx_res.tx_result.log)?;
|
||||
let gas_info = GasInfo::new(tx_res.tx_result.gas_wanted, tx_res.tx_result.gas_used);
|
||||
|
||||
// TODO: should those strings be extracted into some constants?
|
||||
// the reason I think unwrap here is fine is that if the transaction succeeded and those
|
||||
@@ -172,12 +176,12 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.map_err(|_| NymdError::SerializationError("MsgInstantiateContract".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
.sign_and_broadcast_commit(sender_address, vec![init_msg], fee, memo)
|
||||
.sign_and_broadcast(sender_address, vec![init_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
let logs = parse_raw_logs(tx_res.deliver_tx.log)?;
|
||||
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
|
||||
let logs = parse_raw_logs(tx_res.tx_result.log)?;
|
||||
let gas_info = GasInfo::new(tx_res.tx_result.gas_wanted, tx_res.tx_result.gas_used);
|
||||
|
||||
// TODO: should those strings be extracted into some constants?
|
||||
// the reason I think unwrap here is fine is that if the transaction succeeded and those
|
||||
@@ -214,14 +218,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.map_err(|_| NymdError::SerializationError("MsgUpdateAdmin".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
.sign_and_broadcast_commit(sender_address, vec![change_admin_msg], fee, memo)
|
||||
.sign_and_broadcast(sender_address, vec![change_admin_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
|
||||
let gas_info = GasInfo::new(tx_res.tx_result.gas_wanted, tx_res.tx_result.gas_used);
|
||||
|
||||
Ok(ChangeAdminResult {
|
||||
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
|
||||
logs: parse_raw_logs(tx_res.tx_result.log)?,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
@@ -242,14 +246,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.map_err(|_| NymdError::SerializationError("MsgClearAdmin".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
.sign_and_broadcast_commit(sender_address, vec![change_admin_msg], fee, memo)
|
||||
.sign_and_broadcast(sender_address, vec![change_admin_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
|
||||
let gas_info = GasInfo::new(tx_res.tx_result.gas_wanted, tx_res.tx_result.gas_used);
|
||||
|
||||
Ok(ChangeAdminResult {
|
||||
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
|
||||
logs: parse_raw_logs(tx_res.tx_result.log)?,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
@@ -277,14 +281,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.map_err(|_| NymdError::SerializationError("MsgMigrateContract".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
.sign_and_broadcast_commit(sender_address, vec![migrate_msg], fee, memo)
|
||||
.sign_and_broadcast(sender_address, vec![migrate_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
|
||||
let gas_info = GasInfo::new(tx_res.tx_result.gas_wanted, tx_res.tx_result.gas_used);
|
||||
|
||||
Ok(MigrateResult {
|
||||
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
|
||||
logs: parse_raw_logs(tx_res.tx_result.log)?,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
@@ -312,14 +316,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.map_err(|_| NymdError::SerializationError("MsgExecuteContract".to_owned()))?;
|
||||
|
||||
let tx_res = self
|
||||
.sign_and_broadcast_commit(sender_address, vec![execute_msg], fee, memo)
|
||||
.sign_and_broadcast(sender_address, vec![execute_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
|
||||
let gas_info = GasInfo::new(tx_res.tx_result.gas_wanted, tx_res.tx_result.gas_used);
|
||||
|
||||
Ok(ExecuteResult {
|
||||
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
|
||||
logs: parse_raw_logs(tx_res.tx_result.log)?,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
@@ -352,14 +356,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
let tx_res = self
|
||||
.sign_and_broadcast_commit(sender_address, messages, fee, memo)
|
||||
.sign_and_broadcast(sender_address, messages, fee, memo)
|
||||
.await?
|
||||
.check_response()?;
|
||||
|
||||
let gas_info = GasInfo::new(tx_res.deliver_tx.gas_wanted, tx_res.deliver_tx.gas_used);
|
||||
let gas_info = GasInfo::new(tx_res.tx_result.gas_wanted, tx_res.tx_result.gas_used);
|
||||
|
||||
Ok(ExecuteResult {
|
||||
logs: parse_raw_logs(tx_res.deliver_tx.log)?,
|
||||
logs: parse_raw_logs(tx_res.tx_result.log)?,
|
||||
transaction_hash: tx_res.hash,
|
||||
gas_info,
|
||||
})
|
||||
@@ -372,7 +376,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
amount: Vec<Coin>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_commit::Response, NymdError> {
|
||||
) -> Result<TxResponse, NymdError> {
|
||||
let send_msg = MsgSend {
|
||||
from_address: sender_address.clone(),
|
||||
to_address: recipient_address.clone(),
|
||||
@@ -381,7 +385,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgSend".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(sender_address, vec![send_msg], fee, memo)
|
||||
self.sign_and_broadcast(sender_address, vec![send_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()
|
||||
}
|
||||
@@ -392,7 +396,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
msgs: I,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_commit::Response, NymdError>
|
||||
) -> Result<TxResponse, NymdError>
|
||||
where
|
||||
I: IntoIterator<Item = (AccountId, Vec<Coin>)> + Send,
|
||||
{
|
||||
@@ -409,7 +413,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
})
|
||||
.collect::<Result<_, _>>()?;
|
||||
|
||||
self.sign_and_broadcast_commit(sender_address, messages, fee, memo)
|
||||
self.sign_and_broadcast(sender_address, messages, fee, memo)
|
||||
.await?
|
||||
.check_response()
|
||||
}
|
||||
@@ -421,7 +425,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
amount: Coin,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_commit::Response, NymdError> {
|
||||
) -> Result<TxResponse, NymdError> {
|
||||
let delegate_msg = MsgDelegate {
|
||||
delegator_address: delegator_address.to_owned(),
|
||||
validator_address: validator_address.to_owned(),
|
||||
@@ -430,7 +434,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgDelegate".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(delegator_address, vec![delegate_msg], fee, memo)
|
||||
self.sign_and_broadcast(delegator_address, vec![delegate_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()
|
||||
}
|
||||
@@ -442,7 +446,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
amount: Coin,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_commit::Response, NymdError> {
|
||||
) -> Result<TxResponse, NymdError> {
|
||||
let undelegate_msg = MsgUndelegate {
|
||||
delegator_address: delegator_address.to_owned(),
|
||||
validator_address: validator_address.to_owned(),
|
||||
@@ -451,7 +455,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgUndelegate".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(delegator_address, vec![undelegate_msg], fee, memo)
|
||||
self.sign_and_broadcast(delegator_address, vec![undelegate_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()
|
||||
}
|
||||
@@ -462,7 +466,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
validator_address: &AccountId,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_commit::Response, NymdError> {
|
||||
) -> Result<TxResponse, NymdError> {
|
||||
let withdraw_msg = MsgWithdrawDelegatorReward {
|
||||
delegator_address: delegator_address.to_owned(),
|
||||
validator_address: validator_address.to_owned(),
|
||||
@@ -470,7 +474,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgWithdrawDelegatorReward".to_owned()))?;
|
||||
|
||||
self.sign_and_broadcast_commit(delegator_address, vec![withdraw_msg], fee, memo)
|
||||
self.sign_and_broadcast(delegator_address, vec![withdraw_msg], fee, memo)
|
||||
.await?
|
||||
.check_response()
|
||||
}
|
||||
@@ -573,6 +577,27 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
CosmWasmClient::broadcast_tx_commit(self, tx_bytes.into()).await
|
||||
}
|
||||
|
||||
/// Broadcast a transaction to the network and monitors its inclusion in a block.
|
||||
async fn sign_and_broadcast(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
messages: Vec<Any>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<TxResponse, NymdError> {
|
||||
let memo = memo.into();
|
||||
let fee = self
|
||||
.determine_transaction_fee(signer_address, &messages, fee, &memo)
|
||||
.await?;
|
||||
|
||||
let tx_raw = self.sign(signer_address, messages, fee, memo).await?;
|
||||
let tx_bytes = tx_raw
|
||||
.to_bytes()
|
||||
.map_err(|_| NymdError::SerializationError("Tx".to_owned()))?;
|
||||
|
||||
self.broadcast_tx(tx_bytes.into()).await
|
||||
}
|
||||
|
||||
fn sign_direct(
|
||||
&self,
|
||||
signer_address: &AccountId,
|
||||
@@ -638,6 +663,9 @@ pub struct Client {
|
||||
rpc_client: HttpClient,
|
||||
signer: DirectSecp256k1HdWallet,
|
||||
gas_price: GasPrice,
|
||||
|
||||
broadcast_polling_rate: Duration,
|
||||
broadcast_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
@@ -654,8 +682,18 @@ impl Client {
|
||||
rpc_client,
|
||||
signer,
|
||||
gas_price,
|
||||
broadcast_polling_rate: DEFAULT_BROADCAST_POLLING_RATE,
|
||||
broadcast_timeout: DEFAULT_BROADCAST_TIMEOUT,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_broadcast_polling_rate(&mut self, broadcast_polling_rate: Duration) {
|
||||
self.broadcast_polling_rate = broadcast_polling_rate
|
||||
}
|
||||
|
||||
pub fn set_broadcast_timeout(&mut self, broadcast_timeout: Duration) {
|
||||
self.broadcast_timeout = broadcast_timeout
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -669,7 +707,15 @@ impl rpc::Client for Client {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CosmWasmClient for Client {}
|
||||
impl CosmWasmClient for Client {
|
||||
fn broadcast_polling_rate(&self) -> Duration {
|
||||
self.broadcast_polling_rate
|
||||
}
|
||||
|
||||
fn broadcast_timeout(&self) -> Duration {
|
||||
self.broadcast_timeout
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SigningCosmWasmClient for Client {
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::nymd::cosmwasm_client::types::ContractCodeId;
|
||||
use cosmrs::tendermint::{abci, block};
|
||||
use cosmrs::{bip32, tx, AccountId};
|
||||
use std::io;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
|
||||
pub use cosmrs::rpc::error::{
|
||||
@@ -81,21 +82,21 @@ pub enum NymdError {
|
||||
MalformedLogString,
|
||||
|
||||
#[error(
|
||||
"Error when broadcasting tx {hash} at height {height}. Error occurred during CheckTx phase. Code: {code}; Raw log: {raw_log}"
|
||||
"Error when broadcasting tx {hash} at height {height:?}. Error occurred during CheckTx phase. Code: {code}; Raw log: {raw_log}"
|
||||
)]
|
||||
BroadcastTxErrorCheckTx {
|
||||
hash: tx::Hash,
|
||||
height: block::Height,
|
||||
height: Option<block::Height>,
|
||||
code: u32,
|
||||
raw_log: String,
|
||||
},
|
||||
|
||||
#[error(
|
||||
"Error when broadcasting tx {hash} at height {height}. Error occurred during DeliverTx phase. Code: {code}; Raw log: {raw_log}"
|
||||
"Error when broadcasting tx {hash} at height {height:?}. Error occurred during DeliverTx phase. Code: {code}; Raw log: {raw_log}"
|
||||
)]
|
||||
BroadcastTxErrorDeliverTx {
|
||||
hash: tx::Hash,
|
||||
height: block::Height,
|
||||
height: Option<block::Height>,
|
||||
code: u32,
|
||||
raw_log: String,
|
||||
},
|
||||
@@ -117,6 +118,9 @@ pub enum NymdError {
|
||||
|
||||
#[error("This account does not have BaseAccount information available to it")]
|
||||
NoBaseAccountInformationAvailable,
|
||||
|
||||
#[error("Transaction with ID {hash} has been submitted but not yet found on the chain. You might want to check for it later. There was a total wait of {} seconds", .timeout.as_secs())]
|
||||
BroadcastTimeout { hash: tx::Hash, timeout: Duration },
|
||||
}
|
||||
|
||||
impl NymdError {
|
||||
|
||||
@@ -8,7 +8,6 @@ use crate::nymd::cosmwasm_client::types::{
|
||||
};
|
||||
use crate::nymd::error::NymdError;
|
||||
use crate::nymd::wallet::DirectSecp256k1HdWallet;
|
||||
use cosmrs::rpc::endpoint::broadcast;
|
||||
use cosmrs::rpc::Error as TendermintRpcError;
|
||||
use cosmrs::rpc::HttpClientUrl;
|
||||
use cosmwasm_std::{Coin, Uint128};
|
||||
@@ -23,12 +22,14 @@ use mixnet_contract_common::{
|
||||
PagedRewardedSetResponse, QueryMsg, RewardedSetUpdateDetails,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
|
||||
pub use crate::nymd::cosmwasm_client::client::CosmWasmClient;
|
||||
pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient;
|
||||
pub use crate::nymd::fee::Fee;
|
||||
use crate::nymd::fee::DEFAULT_SIMULATED_GAS_MULTIPLIER;
|
||||
pub use cosmrs::bank::MsgSend;
|
||||
pub use cosmrs::rpc::endpoint::tx::Response as TxResponse;
|
||||
pub use cosmrs::rpc::endpoint::validators::Response as ValidatorResponse;
|
||||
pub use cosmrs::rpc::HttpClient as QueryNymdClient;
|
||||
@@ -43,7 +44,6 @@ pub use cosmrs::tx::{self, Gas};
|
||||
pub use cosmrs::Coin as CosmosCoin;
|
||||
pub use cosmrs::{AccountId, Decimal, Denom};
|
||||
pub use signing_client::Client as SigningNymdClient;
|
||||
use std::collections::HashMap;
|
||||
pub use traits::{VestingQueryClient, VestingSigningClient};
|
||||
|
||||
pub mod cosmwasm_client;
|
||||
@@ -640,7 +640,7 @@ impl<C> NymdClient<C> {
|
||||
recipient: &AccountId,
|
||||
amount: Vec<CosmosCoin>,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_commit::Response, NymdError>
|
||||
) -> Result<TxResponse, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
@@ -655,7 +655,7 @@ impl<C> NymdClient<C> {
|
||||
&self,
|
||||
msgs: Vec<(AccountId, Vec<CosmosCoin>)>,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_commit::Response, NymdError>
|
||||
) -> Result<TxResponse, NymdError>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync,
|
||||
{
|
||||
|
||||
@@ -197,38 +197,45 @@ impl DirectSecp256k1HdWalletBuilder {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use network_defaults::DEFAULT_NETWORK;
|
||||
use network_defaults::all::Network::*;
|
||||
|
||||
#[test]
|
||||
fn generating_account_addresses() {
|
||||
let (addr1, addr2, addr3) = match DEFAULT_NETWORK.bech32_prefix() {
|
||||
"punk" => (
|
||||
"punk1jw6mp7d5xqc7w6xm79lha27glmd0vdt32a3fj2",
|
||||
"punk1h5hgn94nsq4kh99rjj794hr5h5q6yfm22mcqqn",
|
||||
"punk17n9flp6jflljg6fp05dsy07wcprf2uuujse962",
|
||||
),
|
||||
"nymt" => (
|
||||
"nymt1jw6mp7d5xqc7w6xm79lha27glmd0vdt339me94",
|
||||
"nymt1h5hgn94nsq4kh99rjj794hr5h5q6yfm23rjshv",
|
||||
"nymt17n9flp6jflljg6fp05dsy07wcprf2uuufgn4d4",
|
||||
),
|
||||
_ => panic!("Test needs to be updated with new bech32 prefix"),
|
||||
};
|
||||
// test vectors produced from our js wallet
|
||||
let mnemonic_address = vec![
|
||||
("crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove", addr1),
|
||||
("acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel", addr2),
|
||||
("step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball", addr3)
|
||||
let mnemonics = vec![
|
||||
"crush minute paddle tobacco message debate cabin peace bar jacket execute twenty winner view sure mask popular couch penalty fragile demise fresh pizza stove",
|
||||
"acquire rebel spot skin gun such erupt pull swear must define ill chief turtle today flower chunk truth battle claw rigid detail gym feel",
|
||||
"step income throw wheat mobile ship wave drink pool sudden upset jaguar bar globe rifle spice frost bless glimpse size regular carry aspect ball"
|
||||
];
|
||||
let prefixes = vec![
|
||||
MAINNET.bech32_prefix(),
|
||||
SANDBOX.bech32_prefix(),
|
||||
QA.bech32_prefix(),
|
||||
];
|
||||
|
||||
for (mnemonic, address) in mnemonic_address.into_iter() {
|
||||
let prefix = DEFAULT_NETWORK.bech32_prefix();
|
||||
let wallet =
|
||||
DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic.parse().unwrap()).unwrap();
|
||||
assert_eq!(
|
||||
wallet.try_derive_accounts().unwrap()[0].address,
|
||||
address.parse().unwrap()
|
||||
)
|
||||
for prefix in prefixes {
|
||||
let addrs = match prefix {
|
||||
"nymt" => vec![
|
||||
"nymt1jw6mp7d5xqc7w6xm79lha27glmd0vdt339me94",
|
||||
"nymt1h5hgn94nsq4kh99rjj794hr5h5q6yfm23rjshv",
|
||||
"nymt17n9flp6jflljg6fp05dsy07wcprf2uuufgn4d4",
|
||||
],
|
||||
"n" => vec![
|
||||
"n1jw6mp7d5xqc7w6xm79lha27glmd0vdt3l9artf",
|
||||
"n1h5hgn94nsq4kh99rjj794hr5h5q6yfm2lr52es",
|
||||
"n17n9flp6jflljg6fp05dsy07wcprf2uuu8g40rf",
|
||||
],
|
||||
_ => panic!("Test needs to be updated with new bech32 prefix"),
|
||||
};
|
||||
for (idx, mnemonic) in mnemonics.iter().enumerate() {
|
||||
let wallet =
|
||||
DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic.parse().unwrap())
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
wallet.try_derive_accounts().unwrap()[0].address,
|
||||
addrs[idx].parse().unwrap()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use std::collections::HashMap;
|
||||
use url::Url;
|
||||
use validator_api_requests::models::{
|
||||
CoreNodeStatusResponse, InclusionProbabilityResponse, MixnodeStatusResponse,
|
||||
RewardEstimationResponse, StakeSaturationResponse,
|
||||
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
|
||||
};
|
||||
|
||||
pub mod error;
|
||||
@@ -253,6 +253,36 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnode_avg_uptime(
|
||||
&self,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
) -> Result<UptimeResponse, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
routes::MIXNODE,
|
||||
identity,
|
||||
routes::AVG_UPTIME,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnode_avg_uptimes(&self) -> Result<Vec<UptimeResponse>, ValidatorAPIError> {
|
||||
self.query_validator_api(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
routes::MIXNODES,
|
||||
routes::AVG_UPTIME,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn blind_sign(
|
||||
&self,
|
||||
request_body: &BlindSignRequestBody,
|
||||
|
||||
@@ -26,5 +26,6 @@ pub const SINCE_ARG: &str = "since";
|
||||
|
||||
pub const STATUS: &str = "status";
|
||||
pub const REWARD_ESTIMATION: &str = "reward-estimation";
|
||||
pub const AVG_UPTIME: &str = "avg_uptime";
|
||||
pub const STAKE_SATURATION: &str = "stake-saturation";
|
||||
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
|
||||
|
||||
@@ -7,6 +7,7 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "1.0.0"
|
||||
handlebars = "3.0.1"
|
||||
humantime-serde = "1.0"
|
||||
log = "0.4"
|
||||
|
||||
@@ -69,14 +69,16 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
|
||||
let location = custom_location
|
||||
.unwrap_or_else(|| self.config_directory().join(Self::config_file_name()));
|
||||
|
||||
fs::write(location.clone(), templated_config)?;
|
||||
|
||||
#[cfg(unix)]
|
||||
let mut perms = fs::metadata(location.clone())?.permissions();
|
||||
#[cfg(unix)]
|
||||
perms.set_mode(0o600);
|
||||
#[cfg(unix)]
|
||||
fs::set_permissions(location, perms)?;
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(unix)] {
|
||||
fs::write(location.clone(), templated_config)?;
|
||||
let mut perms = fs::metadata(location.clone())?.permissions();
|
||||
perms.set_mode(0o600);
|
||||
fs::set_permissions(location, perms)?;
|
||||
} else {
|
||||
fs::write(location, templated_config)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -13,4 +13,11 @@ pub enum MixnetContractError {
|
||||
},
|
||||
#[error("Error casting from U128")]
|
||||
CastError,
|
||||
#[error("{source}")]
|
||||
StdErr {
|
||||
#[from]
|
||||
source: cosmwasm_std::StdError,
|
||||
},
|
||||
#[error("Division by zero at {}", line!())]
|
||||
DivisionByZero,
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::Env;
|
||||
use schemars::gen::SchemaGenerator;
|
||||
use schemars::schema::{InstanceType, Schema, SchemaObject};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::convert::TryInto;
|
||||
use std::fmt::{Display, Formatter};
|
||||
@@ -64,6 +67,39 @@ pub struct Interval {
|
||||
length: Duration,
|
||||
}
|
||||
|
||||
impl JsonSchema for Interval {
|
||||
fn schema_name() -> String {
|
||||
"Interval".to_owned()
|
||||
}
|
||||
|
||||
fn json_schema(gen: &mut SchemaGenerator) -> Schema {
|
||||
let mut schema_object = SchemaObject {
|
||||
instance_type: Some(InstanceType::Object.into()),
|
||||
..SchemaObject::default()
|
||||
};
|
||||
|
||||
let object_validation = schema_object.object();
|
||||
object_validation
|
||||
.properties
|
||||
.insert("id".to_owned(), gen.subschema_for::<u32>());
|
||||
object_validation.required.insert("id".to_owned());
|
||||
|
||||
// PrimitiveDateTime does not implement JsonSchema. However it has a custom
|
||||
// serialization to string, so we just specify the schema to be String.
|
||||
object_validation
|
||||
.properties
|
||||
.insert("start".to_owned(), gen.subschema_for::<String>());
|
||||
object_validation.required.insert("start".to_owned());
|
||||
|
||||
object_validation
|
||||
.properties
|
||||
.insert("length".to_owned(), gen.subschema_for::<Duration>());
|
||||
object_validation.required.insert("length".to_owned());
|
||||
|
||||
Schema::Object(schema_object)
|
||||
}
|
||||
}
|
||||
|
||||
impl Interval {
|
||||
/// Initialize epoch in the contract with default values.
|
||||
pub fn init_epoch(env: Env) -> Self {
|
||||
|
||||
@@ -222,11 +222,17 @@ impl DelegatorRewardParams {
|
||||
}
|
||||
|
||||
pub fn determine_delegation_reward(&self, delegation_amount: Uint128) -> u128 {
|
||||
if self.sigma == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// change all values into their fixed representations
|
||||
let delegation_amount = U128::from_num(delegation_amount.u128());
|
||||
let circulating_supply = U128::from_num(self.reward_params.circulating_supply());
|
||||
|
||||
let scaled_delegation_amount = delegation_amount / circulating_supply;
|
||||
|
||||
// Div by zero checked above
|
||||
let delegator_reward =
|
||||
(ONE - self.profit_margin) * (scaled_delegation_amount / self.sigma) * self.node_profit;
|
||||
|
||||
@@ -250,8 +256,14 @@ impl DelegatorRewardParams {
|
||||
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
|
||||
pub struct StoredNodeRewardResult {
|
||||
reward: Uint128,
|
||||
lambda: Uint128,
|
||||
sigma: Uint128,
|
||||
|
||||
#[schemars(with = "String")]
|
||||
#[serde(with = "fixed_U128_as_string")]
|
||||
lambda: U128,
|
||||
|
||||
#[schemars(with = "String")]
|
||||
#[serde(with = "fixed_U128_as_string")]
|
||||
sigma: U128,
|
||||
}
|
||||
|
||||
impl StoredNodeRewardResult {
|
||||
@@ -259,11 +271,11 @@ impl StoredNodeRewardResult {
|
||||
self.reward
|
||||
}
|
||||
|
||||
pub fn lambda(&self) -> Uint128 {
|
||||
pub fn lambda(&self) -> U128 {
|
||||
self.lambda
|
||||
}
|
||||
|
||||
pub fn sigma(&self) -> Uint128 {
|
||||
pub fn sigma(&self) -> U128 {
|
||||
self.sigma
|
||||
}
|
||||
}
|
||||
@@ -279,18 +291,8 @@ impl TryFrom<NodeRewardResult> for StoredNodeRewardResult {
|
||||
.checked_cast()
|
||||
.ok_or(MixnetContractError::CastError)?,
|
||||
),
|
||||
lambda: Uint128::new(
|
||||
node_reward_result
|
||||
.lambda()
|
||||
.checked_cast()
|
||||
.ok_or(MixnetContractError::CastError)?,
|
||||
),
|
||||
sigma: Uint128::new(
|
||||
node_reward_result
|
||||
.sigma()
|
||||
.checked_cast()
|
||||
.ok_or(MixnetContractError::CastError)?,
|
||||
),
|
||||
lambda: node_reward_result.lambda(),
|
||||
sigma: node_reward_result.sigma(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -469,12 +471,16 @@ impl MixNodeBond {
|
||||
|
||||
pub fn operator_reward(&self, params: &RewardParams) -> u128 {
|
||||
let reward = self.reward(params);
|
||||
if reward.sigma == 0 {
|
||||
return 0;
|
||||
}
|
||||
let profit = if reward.reward < params.node.operator_cost() {
|
||||
U128::from_num(0u128)
|
||||
} else {
|
||||
reward.reward - params.node.operator_cost()
|
||||
};
|
||||
let operator_base_reward = reward.reward.min(params.node.operator_cost());
|
||||
// Div by zero checked above
|
||||
let operator_reward = (self.profit_margin()
|
||||
+ (ONE - self.profit_margin()) * reward.lambda / reward.sigma)
|
||||
* profit;
|
||||
|
||||
@@ -177,6 +177,13 @@ pub enum QueryMsg {
|
||||
owner_address: String,
|
||||
proxy_address: Option<String>,
|
||||
},
|
||||
GetCheckpointsForMixnode {
|
||||
mix_identity: IdentityKey,
|
||||
},
|
||||
GetMixnodeAtHeight {
|
||||
mix_identity: IdentityKey,
|
||||
height: u64,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
|
||||
@@ -25,11 +25,11 @@ impl NodeEpochRewards {
|
||||
self.epoch_id
|
||||
}
|
||||
|
||||
pub fn sigma(&self) -> Uint128 {
|
||||
pub fn sigma(&self) -> U128 {
|
||||
self.result.sigma()
|
||||
}
|
||||
|
||||
pub fn lambda(&self) -> Uint128 {
|
||||
pub fn lambda(&self) -> U128 {
|
||||
self.result.lambda()
|
||||
}
|
||||
|
||||
@@ -47,20 +47,19 @@ impl NodeEpochRewards {
|
||||
|
||||
pub fn node_profit(&self) -> U128 {
|
||||
let reward = U128::from_num(self.reward().u128());
|
||||
if reward < self.operator_cost() {
|
||||
U128::from_num(0u128)
|
||||
} else {
|
||||
reward - self.operator_cost()
|
||||
}
|
||||
// if operating cost is higher then the reward node profit is 0
|
||||
reward.saturating_sub(self.operator_cost())
|
||||
}
|
||||
|
||||
pub fn operator_reward(&self, profit_margin: U128) -> Result<Uint128, MixnetContractError> {
|
||||
let reward = self.node_profit();
|
||||
let operator_base_reward = reward.min(self.operator_cost());
|
||||
let operator_reward = (profit_margin
|
||||
+ (ONE - profit_margin) * U128::from_num(self.lambda().u128())
|
||||
/ U128::from_num(self.sigma().u128()))
|
||||
* reward;
|
||||
let div_by_zero_check = if let Some(value) = self.lambda().checked_div(self.sigma()) {
|
||||
value
|
||||
} else {
|
||||
return Err(MixnetContractError::DivisionByZero);
|
||||
};
|
||||
let operator_reward = (profit_margin + (ONE - profit_margin) * div_by_zero_check) * reward;
|
||||
|
||||
let reward = (operator_reward + operator_base_reward).max(U128::from_num(0u128));
|
||||
|
||||
@@ -82,9 +81,15 @@ impl NodeEpochRewards {
|
||||
let circulating_supply = U128::from_num(epoch_reward_params.circulating_supply());
|
||||
|
||||
let scaled_delegation_amount = delegation_amount / circulating_supply;
|
||||
let delegator_reward = (ONE - profit_margin) * scaled_delegation_amount
|
||||
/ U128::from_num(self.sigma().u128())
|
||||
* self.node_profit();
|
||||
|
||||
let check_div_by_zero =
|
||||
if let Some(value) = scaled_delegation_amount.checked_div(self.sigma()) {
|
||||
value
|
||||
} else {
|
||||
return Err(MixnetContractError::DivisionByZero);
|
||||
};
|
||||
|
||||
let delegator_reward = (ONE - profit_margin) * check_div_by_zero * self.node_profit();
|
||||
|
||||
let reward = delegator_reward.max(U128::ZERO);
|
||||
if let Some(int_reward) = reward.checked_cast() {
|
||||
@@ -201,6 +206,11 @@ impl RewardParams {
|
||||
let denom = self.active_set_work_factor() * U128::from_num(self.rewarded_set_size())
|
||||
- (self.active_set_work_factor() - ONE) * U128::from_num(self.idle_nodes().u128());
|
||||
|
||||
if denom == 0 {
|
||||
return U128::ZERO;
|
||||
}
|
||||
|
||||
// Div by zero checked above
|
||||
if self.in_active_set() {
|
||||
// work_active = factor / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
|
||||
self.active_set_work_factor() / denom * self.rewarded_set_size()
|
||||
|
||||
@@ -68,6 +68,7 @@ pub fn creating_dealing_for_3_parties(c: &mut Criterion) {
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
None,
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -89,6 +90,7 @@ pub fn verifying_dealing_made_for_3_parties_and_recovering_share(c: &mut Criteri
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
None,
|
||||
);
|
||||
|
||||
let first_key = dks.get_mut(0).unwrap();
|
||||
@@ -99,7 +101,7 @@ pub fn verifying_dealing_made_for_3_parties_and_recovering_share(c: &mut Criteri
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
assert!(dealing
|
||||
.verify(¶ms, epoch, threshold, &receivers)
|
||||
.verify(¶ms, epoch, threshold, &receivers, None)
|
||||
.is_ok());
|
||||
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, epoch, None).unwrap());
|
||||
})
|
||||
@@ -128,6 +130,7 @@ pub fn creating_dealing_for_20_parties(c: &mut Criterion) {
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
None,
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -150,6 +153,7 @@ pub fn verifying_dealing_made_for_20_parties_and_recovering_share(c: &mut Criter
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
None,
|
||||
);
|
||||
|
||||
let first_key = dks.get_mut(0).unwrap();
|
||||
@@ -160,7 +164,7 @@ pub fn verifying_dealing_made_for_20_parties_and_recovering_share(c: &mut Criter
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
assert!(dealing
|
||||
.verify(¶ms, epoch, threshold, &receivers)
|
||||
.verify(¶ms, epoch, threshold, &receivers, None)
|
||||
.is_ok());
|
||||
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, epoch, None).unwrap());
|
||||
})
|
||||
@@ -189,6 +193,7 @@ pub fn creating_dealing_for_100_parties(c: &mut Criterion) {
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
None,
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -211,6 +216,7 @@ pub fn verifying_dealing_made_for_100_parties_and_recovering_share(c: &mut Crite
|
||||
threshold,
|
||||
epoch,
|
||||
&receivers,
|
||||
None,
|
||||
);
|
||||
|
||||
let first_key = dks.get_mut(0).unwrap();
|
||||
@@ -221,7 +227,7 @@ pub fn verifying_dealing_made_for_100_parties_and_recovering_share(c: &mut Crite
|
||||
|b| {
|
||||
b.iter(|| {
|
||||
assert!(dealing
|
||||
.verify(¶ms, epoch, threshold, &receivers)
|
||||
.verify(¶ms, epoch, threshold, &receivers, None)
|
||||
.is_ok());
|
||||
black_box(decrypt_share(first_key, 0, &dealing.ciphertexts, epoch, None).unwrap());
|
||||
})
|
||||
|
||||
@@ -69,9 +69,10 @@ mod tests {
|
||||
#[test]
|
||||
fn wrong_prefix_fails() {
|
||||
assert_eq!(
|
||||
Err(Bech32Error::WrongPrefix(
|
||||
"your bech32 address prefix should be nymt, not punk".to_string()
|
||||
)),
|
||||
Err(Bech32Error::WrongPrefix(format!(
|
||||
"your bech32 address prefix should be {}, not punk",
|
||||
DEFAULT_NETWORK.bech32_prefix()
|
||||
))),
|
||||
validate_bech32_prefix("punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0")
|
||||
)
|
||||
}
|
||||
@@ -80,7 +81,9 @@ mod tests {
|
||||
fn correct_prefix_works() {
|
||||
assert_eq!(
|
||||
Ok(()),
|
||||
validate_bech32_prefix("nymt1z9egw0knv47nmur0p8vk4rcx59h9gg4zuxrrr9")
|
||||
validate_bech32_prefix(
|
||||
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
fn main() {
|
||||
match option_env!("NETWORK") {
|
||||
Some("mainnet") => println!("cargo:rustc-cfg=network=\"mainnet\"",),
|
||||
None | Some("sandbox") => println!("cargo:rustc-cfg=network=\"sandbox\"",),
|
||||
None | Some("mainnet") => println!("cargo:rustc-cfg=network=\"mainnet\"",),
|
||||
Some("sandbox") => println!("cargo:rustc-cfg=network=\"sandbox\"",),
|
||||
Some("qa") => println!("cargo:rustc-cfg=network=\"qa\""),
|
||||
_ => panic!("No such network"),
|
||||
}
|
||||
|
||||
@@ -101,6 +101,13 @@ impl ValidatorDetails {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_name(nymd_url: &str, api_url: Option<&str>) -> Self {
|
||||
ValidatorDetails {
|
||||
nymd_url: nymd_url.to_string(),
|
||||
api_url: api_url.map(ToString::to_string),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nymd_url(&self) -> Url {
|
||||
self.nymd_url
|
||||
.parse()
|
||||
|
||||
@@ -21,6 +21,6 @@ pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hh
|
||||
pub(crate) fn validators() -> Vec<ValidatorDetails> {
|
||||
vec![ValidatorDetails::new(
|
||||
"https://rpc.nyx.nodes.guru/",
|
||||
Some("https://validator.nymtech.net/api"),
|
||||
Some("https://validator.nymtech.net/api/"),
|
||||
)]
|
||||
}
|
||||
|
||||
@@ -3,22 +3,24 @@
|
||||
|
||||
use crate::ValidatorDetails;
|
||||
|
||||
pub(crate) const BECH32_PREFIX: &str = "nymt";
|
||||
pub const DENOM: &str = "unymt";
|
||||
pub(crate) const BECH32_PREFIX: &str = "n";
|
||||
pub const DENOM: &str = "unym";
|
||||
|
||||
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = "nymt17x6pt4msccvawgxjeg5nmnygttu56tftg5l6j3";
|
||||
pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "nymt1t4dmskxea0avvrj8xtmu66hv7dkyg9s8059t3c";
|
||||
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
|
||||
"n1suhgf5svhu4usrurvxzlgn54ksxmn8gljarjtxqnapv8kjnp4nrsd3qaep";
|
||||
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
|
||||
"n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav";
|
||||
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
|
||||
"nymt17p9rzwnnfxcjp32un9ug7yhhzgtkhvl9f8xzkv";
|
||||
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
|
||||
hex_literal::hex!("0000000000000000000000000000000000000000");
|
||||
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
|
||||
hex_literal::hex!("0000000000000000000000000000000000000000");
|
||||
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "nymt1dn52nx8wv9wkqmrvj6tcmdzh4es6jt8tr7f6j9";
|
||||
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n1tfzd4qz3a45u8p4mr5zmzv66457uwjgcl05jdq";
|
||||
|
||||
pub(crate) fn validators() -> Vec<ValidatorDetails> {
|
||||
vec![ValidatorDetails::new(
|
||||
"https://qa-validator.nymtech.net",
|
||||
Some("https://qa-validator.nymtech.net/api"),
|
||||
Some("https://qa-validator-api.nymtech.net/api"),
|
||||
)]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod msg;
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
|
||||
pub use msg::*;
|
||||
pub use request::*;
|
||||
pub use response::*;
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::request::{Request, RequestError};
|
||||
use crate::response::{Response, ResponseError};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MessageError {
|
||||
Request(RequestError),
|
||||
Response(ResponseError),
|
||||
NoData,
|
||||
UnknownMessageType,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for MessageError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
MessageError::Request(r) => write!(f, "{}", r),
|
||||
MessageError::Response(r) => write!(f, "{:?}", r),
|
||||
MessageError::NoData => write!(f, "no data provided"),
|
||||
MessageError::UnknownMessageType => write!(f, "unknown message type received"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Message {
|
||||
Request(Request),
|
||||
Response(Response),
|
||||
}
|
||||
|
||||
impl Message {
|
||||
const REQUEST_FLAG: u8 = 0;
|
||||
const RESPONSE_FLAG: u8 = 1;
|
||||
|
||||
pub fn try_from_bytes(b: &[u8]) -> Result<Message, MessageError> {
|
||||
if b.is_empty() {
|
||||
return Err(MessageError::NoData);
|
||||
}
|
||||
|
||||
if b[0] == Self::REQUEST_FLAG {
|
||||
Request::try_from_bytes(&b[1..])
|
||||
.map(Message::Request)
|
||||
.map_err(MessageError::Request)
|
||||
} else if b[0] == Self::RESPONSE_FLAG {
|
||||
Response::try_from_bytes(&b[1..])
|
||||
.map(Message::Response)
|
||||
.map_err(MessageError::Response)
|
||||
} else {
|
||||
Err(MessageError::UnknownMessageType)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
match self {
|
||||
Self::Request(r) => std::iter::once(Self::REQUEST_FLAG)
|
||||
.chain(r.into_bytes().iter().cloned())
|
||||
.collect(),
|
||||
Self::Response(r) => std::iter::once(Self::RESPONSE_FLAG)
|
||||
.chain(r.into_bytes().iter().cloned())
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+4
-3
@@ -41,7 +41,7 @@ checksum = "f771a5d1f5503f7f4279a30f3643d3421ba149848b89ecaaec0ea2acf04a5ac4"
|
||||
|
||||
[[package]]
|
||||
name = "bandwidth-claim"
|
||||
version = "1.0.0-rc.2"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"bandwidth-claim-contract",
|
||||
"config",
|
||||
@@ -227,6 +227,7 @@ dependencies = [
|
||||
name = "config"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"handlebars",
|
||||
"humantime-serde",
|
||||
"log",
|
||||
@@ -980,9 +981,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
version = "0.4.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bandwidth-claim"
|
||||
version = "1.0.0-rc.2"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// approximately 1 week (assuming 5s per block)
|
||||
// i.e. approximately quarter of the interval (there are 3600 * 60 * 7 = 604800 seconds in a week, i.e. ~604800 / 5 = 120960 blocks)
|
||||
pub const MINIMUM_BLOCK_AGE_FOR_REWARDING: u64 = 120960;
|
||||
// approximately 1 epoch (assuming 5s per block)
|
||||
pub const MINIMUM_BLOCK_AGE_FOR_REWARDING: u64 = 720;
|
||||
|
||||
pub const INTERVAL_REWARD_PERCENT: u8 = 2; // Used to calculate interval reward pool
|
||||
pub const SYBIL_RESISTANCE_PERCENT: u8 = 30;
|
||||
|
||||
@@ -24,14 +24,17 @@ use crate::mixnet_contract_settings::queries::{
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::mixnet_contract_settings::transactions::try_update_rewarding_validator_address;
|
||||
use crate::mixnodes::bonding_queries as mixnode_queries;
|
||||
use crate::mixnodes::bonding_queries::query_mixnodes_paged;
|
||||
use crate::mixnodes::bonding_queries::{
|
||||
query_checkpoints_for_mixnode, query_mixnode_at_height, query_mixnodes_paged,
|
||||
};
|
||||
use crate::mixnodes::layer_queries::query_layer_distribution;
|
||||
use crate::rewards::queries::{
|
||||
query_circulating_supply, query_reward_pool, query_rewarding_status,
|
||||
};
|
||||
use crate::rewards::storage as rewards_storage;
|
||||
use cosmwasm_std::{
|
||||
entry_point, to_binary, Addr, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, Uint128,
|
||||
entry_point, to_binary, Addr, Api, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
|
||||
Uint128,
|
||||
};
|
||||
use mixnet_contract_common::mixnode::DelegationEvent;
|
||||
use mixnet_contract_common::{
|
||||
@@ -54,6 +57,10 @@ pub const INITIAL_ACTIVE_SET_WORK_FACTOR: u8 = 10;
|
||||
pub const DEFAULT_FIRST_INTERVAL_START: OffsetDateTime =
|
||||
time::macros::datetime!(2022-01-01 12:00 UTC);
|
||||
|
||||
pub fn debug_with_visibility<S: Into<String>>(api: &dyn Api, msg: S) {
|
||||
api.debug(&*format!("\n\n\n=========================================\n{}\n=========================================\n\n\n", msg.into()));
|
||||
}
|
||||
|
||||
fn default_initial_state(owner: Addr, rewarding_validator_address: Addr) -> ContractState {
|
||||
ContractState {
|
||||
owner,
|
||||
@@ -387,13 +394,19 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
|
||||
QueryMsg::DebugGetAllDelegationValues {} => to_binary(
|
||||
&crate::delegations::queries::debug_query_all_delegation_values(deps.storage)?,
|
||||
),
|
||||
QueryMsg::GetCheckpointsForMixnode { mix_identity } => {
|
||||
to_binary(&query_checkpoints_for_mixnode(deps, mix_identity)?)
|
||||
}
|
||||
QueryMsg::GetMixnodeAtHeight {
|
||||
mix_identity,
|
||||
height,
|
||||
} => to_binary(&query_mixnode_at_height(deps, mix_identity, height)?),
|
||||
};
|
||||
|
||||
Ok(query_res?)
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
fn deal_with_zero_delegations(deps: DepsMut<'_>) -> Result<(), ContractError> {
|
||||
// if there exists any delegation of 0 value, remove it
|
||||
let zero_delegations = delegations()
|
||||
.range(deps.storage, None, None, cosmwasm_std::Order::Ascending)
|
||||
@@ -422,6 +435,13 @@ pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Respons
|
||||
.remove(deps.storage, delegation_event);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
deal_with_zero_delegations(deps)?;
|
||||
|
||||
Ok(Default::default())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use super::storage::{self, PENDING_DELEGATION_EVENTS};
|
||||
// use crate::contract::debug_with_visibility;
|
||||
// use crate::contract::debug_with_visibility;
|
||||
use crate::error::ContractError;
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
@@ -28,12 +30,13 @@ pub fn try_reconcile_all_delegation_events(
|
||||
return Err(ContractError::Unauthorized);
|
||||
}
|
||||
|
||||
_try_reconcile_all_delegation_events(deps.storage)
|
||||
_try_reconcile_all_delegation_events(deps.storage, deps.api)
|
||||
}
|
||||
|
||||
// TODO: Error handling?
|
||||
pub(crate) fn _try_reconcile_all_delegation_events(
|
||||
storage: &mut dyn Storage,
|
||||
api: &dyn Api,
|
||||
) -> Result<Response, ContractError> {
|
||||
let pending_delegation_events = PENDING_DELEGATION_EVENTS
|
||||
.range(storage, None, None, Order::Ascending)
|
||||
@@ -42,6 +45,8 @@ pub(crate) fn _try_reconcile_all_delegation_events(
|
||||
|
||||
let mut response = Response::new();
|
||||
|
||||
// debug_with_visibility(api, "Reconciling delegation events");
|
||||
|
||||
for (key, delegation_event) in pending_delegation_events {
|
||||
match delegation_event {
|
||||
DelegationEvent::Delegate(delegation) => {
|
||||
@@ -53,7 +58,8 @@ pub(crate) fn _try_reconcile_all_delegation_events(
|
||||
response = response.add_event(event);
|
||||
}
|
||||
DelegationEvent::Undelegate(pending_undelegate) => {
|
||||
let undelegate_response = try_reconcile_undelegation(storage, &pending_undelegate)?;
|
||||
let undelegate_response =
|
||||
try_reconcile_undelegation(storage, api, &pending_undelegate)?;
|
||||
response = response.add_event(undelegate_response.event);
|
||||
if let Some(msg) = undelegate_response.bank_msg {
|
||||
response = response.add_message(msg);
|
||||
@@ -101,8 +107,7 @@ pub(crate) fn try_delegate_to_mixnode(
|
||||
let amount = validate_delegation_stake(info.funds)?;
|
||||
|
||||
_try_delegate_to_mixnode(
|
||||
deps.storage,
|
||||
deps.api,
|
||||
deps,
|
||||
env.block.height,
|
||||
&mix_identity,
|
||||
info.sender.as_str(),
|
||||
@@ -122,8 +127,7 @@ pub(crate) fn try_delegate_to_mixnode_on_behalf(
|
||||
let amount = validate_delegation_stake(info.funds)?;
|
||||
|
||||
_try_delegate_to_mixnode(
|
||||
deps.storage,
|
||||
deps.api,
|
||||
deps,
|
||||
env.block.height,
|
||||
&mix_identity,
|
||||
&delegate,
|
||||
@@ -173,19 +177,18 @@ pub(crate) fn try_reconcile_delegation(
|
||||
}
|
||||
|
||||
pub(crate) fn _try_delegate_to_mixnode(
|
||||
storage: &mut dyn Storage,
|
||||
api: &dyn Api,
|
||||
deps: DepsMut<'_>,
|
||||
block_height: u64,
|
||||
mix_identity: &str,
|
||||
delegate: &str,
|
||||
amount: Coin,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, ContractError> {
|
||||
let delegate = api.addr_validate(delegate)?;
|
||||
let delegate = deps.api.addr_validate(delegate)?;
|
||||
|
||||
// check if the target node actually exists
|
||||
if mixnodes_storage::mixnodes()
|
||||
.may_load(storage, mix_identity)?
|
||||
.may_load(deps.storage, mix_identity)?
|
||||
.is_none()
|
||||
{
|
||||
return Err(ContractError::MixNodeBondNotFound {
|
||||
@@ -202,7 +205,7 @@ pub(crate) fn _try_delegate_to_mixnode(
|
||||
);
|
||||
|
||||
if storage::PENDING_DELEGATION_EVENTS
|
||||
.may_load(storage, delegation.event_storage_key())?
|
||||
.may_load(deps.storage, delegation.event_storage_key())?
|
||||
.is_some()
|
||||
{
|
||||
return Err(ContractError::DelegationEventAlreadyPending {
|
||||
@@ -213,7 +216,7 @@ pub(crate) fn _try_delegate_to_mixnode(
|
||||
}
|
||||
|
||||
storage::PENDING_DELEGATION_EVENTS.save(
|
||||
storage,
|
||||
deps.storage,
|
||||
delegation.event_storage_key(),
|
||||
&DelegationEvent::Delegate(delegation),
|
||||
)?;
|
||||
@@ -253,10 +256,13 @@ pub struct ReconcileUndelegateResponse {
|
||||
|
||||
pub(crate) fn try_reconcile_undelegation(
|
||||
storage: &mut dyn Storage,
|
||||
api: &dyn Api,
|
||||
pending_undelegate: &PendingUndelegate,
|
||||
) -> Result<ReconcileUndelegateResponse, ContractError> {
|
||||
let delegation_map = storage::delegations();
|
||||
|
||||
// debug_with_visibility(api, "Reconciling undelegations");
|
||||
|
||||
let any_delegations = delegation_map
|
||||
.prefix(pending_undelegate.storage_key())
|
||||
.keys(storage, None, None, cosmwasm_std::Order::Ascending)
|
||||
@@ -280,10 +286,13 @@ pub(crate) fn try_reconcile_undelegation(
|
||||
|
||||
let reward = crate::rewards::transactions::calculate_delegator_reward(
|
||||
storage,
|
||||
api,
|
||||
pending_undelegate.proxy_storage_key(),
|
||||
&pending_undelegate.mix_identity(),
|
||||
)?;
|
||||
|
||||
// debug_with_visibility(api, format!("Delegator reward: {}", reward));
|
||||
|
||||
// Might want to introduce paging here
|
||||
let delegation_heights = delegation_map
|
||||
.prefix(pending_undelegate.storage_key())
|
||||
@@ -307,12 +316,23 @@ pub(crate) fn try_reconcile_undelegation(
|
||||
|
||||
let mut total_delegation = Uint128::zero();
|
||||
|
||||
if crate::mixnodes::storage::mixnodes()
|
||||
.may_load(storage, &pending_undelegate.mix_identity())?
|
||||
.is_none()
|
||||
// debug_with_visibility(api, "Reducing accumulated rewards");
|
||||
|
||||
{
|
||||
// Since the mixnode is no longer bonded the reward did not compound and we need to manually add it to the total
|
||||
total_delegation = reward;
|
||||
if let Some(mut bond) = crate::mixnodes::storage::mixnodes()
|
||||
.may_load(storage, &pending_undelegate.mix_identity())?
|
||||
{
|
||||
let remaining = bond.accumulated_rewards().saturating_sub(reward);
|
||||
// debug_with_visibility(api, format!("Remaining accumulated rewards: {}", remaining));
|
||||
bond.accumulated_rewards = Some(remaining);
|
||||
|
||||
crate::mixnodes::storage::mixnodes().save(
|
||||
storage,
|
||||
&pending_undelegate.mix_identity(),
|
||||
&bond,
|
||||
pending_undelegate.block_height(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
for h in delegation_heights {
|
||||
@@ -327,6 +347,40 @@ pub(crate) fn try_reconcile_undelegation(
|
||||
)?;
|
||||
}
|
||||
|
||||
mixnodes_storage::TOTAL_DELEGATION.update::<_, ContractError>(
|
||||
storage,
|
||||
&pending_undelegate.mix_identity(),
|
||||
|total_node_delegation| {
|
||||
// debug_with_visibility(api, "Setting total delegation");
|
||||
let remaining = match total_node_delegation.unwrap().checked_sub(total_delegation) {
|
||||
Ok(remaining) => remaining,
|
||||
Err(_) => {
|
||||
// debug_with_visibility(
|
||||
// api,
|
||||
// format!(
|
||||
// "Overflowed delegation subsctraction, {} - {}",
|
||||
// total_node_delegation.unwrap(),
|
||||
// total_delegation
|
||||
// ),
|
||||
// );
|
||||
return Err(ContractError::TotalDelegationSubOverflow {
|
||||
mix_identity: pending_undelegate.mix_identity(),
|
||||
total_node_delegation: total_node_delegation.unwrap().u128(),
|
||||
to_subtract: total_delegation.u128(),
|
||||
});
|
||||
}
|
||||
};
|
||||
// debug_with_visibility(api, format!("Remaining total delegation: {}", remaining));
|
||||
// the first unwrap is fine because the delegation information MUST exist, otherwise we would
|
||||
// have never gotten here in the first place
|
||||
// the second unwrap is also fine because we should NEVER underflow here,
|
||||
// if we do, it means we have some serious error in our logic
|
||||
Ok(remaining)
|
||||
},
|
||||
)?;
|
||||
|
||||
let total_funds = total_delegation + reward;
|
||||
|
||||
// don't add a bank message if it would have resulted in attempting to send 0 tokens
|
||||
let bank_msg = if total_delegation != Uint128::zero() {
|
||||
Some(BankMsg::Send {
|
||||
@@ -335,34 +389,19 @@ pub(crate) fn try_reconcile_undelegation(
|
||||
.as_ref()
|
||||
.unwrap_or(&pending_undelegate.delegate())
|
||||
.to_string(),
|
||||
amount: coins(total_delegation.u128(), DENOM),
|
||||
amount: coins(total_funds.u128(), DENOM),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
mixnodes_storage::TOTAL_DELEGATION.update::<_, ContractError>(
|
||||
storage,
|
||||
&pending_undelegate.mix_identity(),
|
||||
|total_node_delegation| {
|
||||
// the first unwrap is fine because the delegation information MUST exist, otherwise we would
|
||||
// have never gotten here in the first place
|
||||
// the second unwrap is also fine because we should NEVER underflow here,
|
||||
// if we do, it means we have some serious error in our logic
|
||||
Ok(total_node_delegation
|
||||
.unwrap()
|
||||
.checked_sub(total_delegation)
|
||||
.unwrap())
|
||||
},
|
||||
)?;
|
||||
|
||||
let mut wasm_msg = None;
|
||||
|
||||
if let Some(proxy) = &pending_undelegate.proxy() {
|
||||
let msg = Some(VestingContractExecuteMsg::TrackUndelegation {
|
||||
owner: pending_undelegate.delegate().as_str().to_string(),
|
||||
mix_identity: pending_undelegate.mix_identity(),
|
||||
amount: Coin::new(total_delegation.u128(), DENOM),
|
||||
amount: Coin::new(total_funds.u128(), DENOM),
|
||||
});
|
||||
|
||||
wasm_msg = Some(wasm_execute(proxy, &msg, vec![one_ucoin()])?);
|
||||
@@ -372,9 +411,11 @@ pub(crate) fn try_reconcile_undelegation(
|
||||
&pending_undelegate.delegate(),
|
||||
&pending_undelegate.proxy(),
|
||||
&pending_undelegate.mix_identity(),
|
||||
total_delegation,
|
||||
total_funds,
|
||||
);
|
||||
|
||||
// debug_with_visibility(api, "Done");
|
||||
|
||||
Ok(ReconcileUndelegateResponse {
|
||||
bank_msg,
|
||||
wasm_msg,
|
||||
@@ -527,7 +568,7 @@ mod tests {
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
let expected = Delegation::new(
|
||||
delegation_owner.clone(),
|
||||
@@ -606,7 +647,7 @@ mod tests {
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
let expected = Delegation::new(
|
||||
delegation_owner.clone(),
|
||||
@@ -669,7 +710,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
// let expected = Delegation::new(
|
||||
// delegation_owner.clone(),
|
||||
@@ -724,7 +765,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
initial_height,
|
||||
@@ -745,7 +786,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
let delegations = crate::delegations::queries::query_mixnode_delegation(
|
||||
&deps.storage,
|
||||
@@ -790,7 +831,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
initial_height,
|
||||
@@ -811,7 +852,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
initial_height,
|
||||
@@ -899,7 +940,7 @@ mod tests {
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
let expected1 = Delegation::new(
|
||||
delegation_owner.clone(),
|
||||
@@ -964,7 +1005,7 @@ mod tests {
|
||||
identity.clone(),
|
||||
)
|
||||
.is_ok());
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
// node's "total_delegation" is sum of both
|
||||
assert_eq!(
|
||||
@@ -994,7 +1035,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
try_remove_mixnode(mock_env(), deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap();
|
||||
|
||||
@@ -1082,7 +1123,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
let _delegation = query_mixnode_delegation(
|
||||
&deps.storage,
|
||||
@@ -1152,7 +1193,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
let delegation = query_mixnode_delegation(
|
||||
&deps.storage,
|
||||
@@ -1187,7 +1228,7 @@ mod tests {
|
||||
)
|
||||
);
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
assert!(test_helpers::read_delegation(
|
||||
&deps.storage,
|
||||
@@ -1220,7 +1261,7 @@ mod tests {
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
assert!(try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
@@ -1230,7 +1271,7 @@ mod tests {
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
|
||||
// sender1 undelegates
|
||||
try_remove_delegation_from_mixnode(
|
||||
@@ -1241,7 +1282,7 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage).unwrap();
|
||||
_try_reconcile_all_delegation_events(&mut deps.storage, &deps.api).unwrap();
|
||||
// but total delegation should still equal to what sender2 sent
|
||||
// node's "total_delegation" is sum of both
|
||||
assert_eq!(
|
||||
|
||||
@@ -168,4 +168,15 @@ pub enum ContractError {
|
||||
identity: String,
|
||||
kind: String, // delegation | undelegation
|
||||
},
|
||||
#[error("Attempted to subsctract more then the total delegation, this MUST never happen! mix: {mix_identity}, total_node_delegation {total_node_delegation}, to_subtract {to_subtract}")]
|
||||
TotalDelegationSubOverflow {
|
||||
mix_identity: String,
|
||||
total_node_delegation: u128,
|
||||
to_subtract: u128,
|
||||
},
|
||||
#[error("Profit margin can be updated only once during a rolling 30 day interval, last update was at {last_update_time} and current block time is {current_block_time}")]
|
||||
UpdatePMTooSoon {
|
||||
last_update_time: u64,
|
||||
current_block_time: u64,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,13 +1,33 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::storage;
|
||||
use super::storage::{self, StoredMixnodeBond};
|
||||
use cosmwasm_std::{Deps, Order, StdResult};
|
||||
use cw_storage_plus::Bound;
|
||||
use mixnet_contract_common::{
|
||||
IdentityKey, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse,
|
||||
};
|
||||
|
||||
pub fn query_mixnode_at_height(
|
||||
deps: Deps<'_>,
|
||||
mix_identity: String,
|
||||
height: u64,
|
||||
) -> StdResult<Option<StoredMixnodeBond>> {
|
||||
storage::mixnodes().may_load_at_height(deps.storage, &mix_identity, height)
|
||||
}
|
||||
|
||||
pub fn query_checkpoints_for_mixnode(
|
||||
deps: Deps<'_>,
|
||||
mix_identity: IdentityKey,
|
||||
) -> StdResult<Vec<u64>> {
|
||||
Ok(storage::mixnodes()
|
||||
.changelog()
|
||||
.prefix(&mix_identity)
|
||||
.keys(deps.storage, None, None, Order::Ascending)
|
||||
.filter_map(|x| x.ok())
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn query_mixnodes_paged(
|
||||
deps: Deps<'_>,
|
||||
start_after: Option<IdentityKey>,
|
||||
|
||||
@@ -19,6 +19,8 @@ const MIXNODES_PK_CHANGELOG: &str = "mn__change";
|
||||
const MIXNODES_OWNER_IDX_NAMESPACE: &str = "mno";
|
||||
const MIXNODES_SPHINX_IDX_NAMESPACE: &str = "mns";
|
||||
|
||||
const LAST_PM_UPDATE_NAMESPACE: &str = "lpm";
|
||||
|
||||
// paged retrieval limits for all queries and transactions
|
||||
pub(crate) const BOND_PAGE_MAX_LIMIT: u32 = 75;
|
||||
pub(crate) const BOND_PAGE_DEFAULT_LIMIT: u32 = 50;
|
||||
@@ -26,6 +28,9 @@ pub(crate) const BOND_PAGE_DEFAULT_LIMIT: u32 = 50;
|
||||
pub(crate) const TOTAL_DELEGATION: Map<'_, IdentityKeyRef<'_>, Uint128> =
|
||||
Map::new(TOTAL_DELEGATION_NAMESPACE);
|
||||
|
||||
pub(crate) const LAST_PM_UPDATE_TIME: Map<'_, IdentityKeyRef<'_>, u64> =
|
||||
Map::new(LAST_PM_UPDATE_NAMESPACE);
|
||||
|
||||
pub(crate) struct MixnodeBondIndex<'a> {
|
||||
pub(crate) owner: UniqueIndex<'a, Addr, StoredMixnodeBond>,
|
||||
|
||||
@@ -55,7 +60,7 @@ pub(crate) fn mixnodes<'a>(
|
||||
MIXNODES_PK_NAMESPACE,
|
||||
MIXNODES_PK_CHECKPOINTS,
|
||||
MIXNODES_PK_CHANGELOG,
|
||||
Strategy::Never,
|
||||
Strategy::Selected,
|
||||
indexes,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::storage;
|
||||
use super::storage::{self, LAST_PM_UPDATE_TIME};
|
||||
use crate::error::ContractError;
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::mixnodes::layer_queries::query_layer_distribution;
|
||||
@@ -18,6 +18,8 @@ use mixnet_contract_common::MixNode;
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
use vesting_contract_common::one_ucoin;
|
||||
|
||||
const MIN_PM_UPDATE_INTERVAL: u64 = 60 * 60 * 24 * 30; // one month roughly
|
||||
|
||||
pub fn try_checkpoint_mixnodes(
|
||||
storage: &mut dyn Storage,
|
||||
block_height: u64,
|
||||
@@ -152,6 +154,8 @@ fn _try_add_mixnode(
|
||||
storage::TOTAL_DELEGATION.save(deps.storage, identity, &Uint128::zero())?;
|
||||
}
|
||||
|
||||
storage::LAST_PM_UPDATE_TIME.save(deps.storage, identity, &env.block.time.seconds())?;
|
||||
|
||||
mixnet_params_storage::increment_layer_count(deps.storage, stored_bond.layer)?;
|
||||
|
||||
Ok(Response::new().add_event(new_mixnode_bonding_event(
|
||||
@@ -191,6 +195,7 @@ pub(crate) fn _try_remove_mixnode(
|
||||
|
||||
crate::rewards::transactions::_try_compound_operator_reward(
|
||||
deps.storage,
|
||||
deps.api,
|
||||
env.block.height,
|
||||
&owner,
|
||||
None,
|
||||
@@ -293,6 +298,19 @@ pub(crate) fn _try_update_mixnode_config(
|
||||
});
|
||||
}
|
||||
|
||||
let last_update_time = storage::LAST_PM_UPDATE_TIME
|
||||
.load(deps.storage, mixnode_bond.identity())
|
||||
.unwrap_or(0);
|
||||
|
||||
let current_block_time = env.block.time.seconds();
|
||||
|
||||
if current_block_time - last_update_time < MIN_PM_UPDATE_INTERVAL {
|
||||
return Err(ContractError::UpdatePMTooSoon {
|
||||
last_update_time,
|
||||
current_block_time,
|
||||
});
|
||||
}
|
||||
|
||||
// We don't have to check lower bound as its an u8
|
||||
if profit_margin_percent > 100 {
|
||||
return Err(ContractError::InvalidProfitMarginPercent(
|
||||
@@ -315,6 +333,8 @@ pub(crate) fn _try_update_mixnode_config(
|
||||
},
|
||||
)?;
|
||||
|
||||
LAST_PM_UPDATE_TIME.save(deps.storage, mixnode_bond.identity(), ¤t_block_time)?;
|
||||
|
||||
let mut response = Response::new();
|
||||
|
||||
if let Some(proxy) = proxy {
|
||||
@@ -361,6 +381,8 @@ fn validate_mixnode_pledge(
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use std::f64::MIN;
|
||||
|
||||
use super::*;
|
||||
use crate::contract::{execute, query, INITIAL_MIXNODE_PLEDGE};
|
||||
use crate::error::ContractError;
|
||||
@@ -714,6 +736,7 @@ pub mod tests {
|
||||
#[test]
|
||||
fn updating_mixnode_config() {
|
||||
let sender = "bob";
|
||||
let mut env = mock_env();
|
||||
let mut deps = test_helpers::init_contract();
|
||||
let info = mock_info(sender, &[]);
|
||||
|
||||
@@ -721,7 +744,7 @@ pub mod tests {
|
||||
let msg = ExecuteMsg::UpdateMixnodeConfig {
|
||||
profit_margin_percent: 10,
|
||||
};
|
||||
let ret = execute(deps.as_mut(), mock_env(), info.clone(), msg);
|
||||
let ret = execute(deps.as_mut(), env.clone(), info.clone(), msg);
|
||||
assert_eq!(
|
||||
ret,
|
||||
Err(ContractError::NoAssociatedMixNodeBond {
|
||||
@@ -750,12 +773,14 @@ pub mod tests {
|
||||
.profit_margin_percent
|
||||
);
|
||||
|
||||
env.block.time = env.block.time.plus_seconds(MIN_PM_UPDATE_INTERVAL + 1);
|
||||
|
||||
// try updating with an invalid value
|
||||
let profit_margin_percent = 101;
|
||||
let msg = ExecuteMsg::UpdateMixnodeConfig {
|
||||
profit_margin_percent,
|
||||
};
|
||||
let ret = execute(deps.as_mut(), mock_env(), info.clone(), msg);
|
||||
let ret = execute(deps.as_mut(), env.clone(), info.clone(), msg);
|
||||
assert_eq!(
|
||||
ret,
|
||||
Err(ContractError::InvalidProfitMarginPercent(
|
||||
@@ -767,7 +792,7 @@ pub mod tests {
|
||||
let msg = ExecuteMsg::UpdateMixnodeConfig {
|
||||
profit_margin_percent,
|
||||
};
|
||||
execute(deps.as_mut(), mock_env(), info, msg).unwrap();
|
||||
execute(deps.as_mut(), env, info, msg).unwrap();
|
||||
assert_eq!(
|
||||
profit_margin_percent,
|
||||
storage::mixnodes()
|
||||
@@ -881,4 +906,51 @@ pub mod tests {
|
||||
// change identity but reuse sphinx key
|
||||
assert!(try_add_mixnode(deps.as_mut(), mock_env(), info_bob, mixnode, sig2).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updating_pm_too_often_fails() {
|
||||
use super::MIN_PM_UPDATE_INTERVAL;
|
||||
|
||||
let mut deps = test_helpers::init_contract();
|
||||
let mut env = mock_env();
|
||||
|
||||
let keypair1 = crypto::asymmetric::identity::KeyPair::new(&mut thread_rng());
|
||||
let sig1 = keypair1.private_key().sign_text("alice");
|
||||
|
||||
let info_alice = mock_info("alice", &tests::fixtures::good_mixnode_pledge());
|
||||
|
||||
let mixnode = MixNode {
|
||||
host: "1.2.3.4".to_string(),
|
||||
mix_port: 1234,
|
||||
verloc_port: 1234,
|
||||
http_api_port: 1234,
|
||||
sphinx_key: crypto::asymmetric::encryption::KeyPair::new(&mut thread_rng())
|
||||
.public_key()
|
||||
.to_base58_string(),
|
||||
identity_key: keypair1.public_key().to_base58_string(),
|
||||
version: "v0.1.2.3".to_string(),
|
||||
profit_margin_percent: 10,
|
||||
};
|
||||
|
||||
assert!(try_add_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
info_alice.clone(),
|
||||
mixnode.clone(),
|
||||
sig1
|
||||
)
|
||||
.is_ok());
|
||||
|
||||
env.block.time = env.block.time.plus_seconds(MIN_PM_UPDATE_INTERVAL - 1);
|
||||
|
||||
// fails if too soon after bonding
|
||||
assert!(
|
||||
try_update_mixnode_config(deps.as_mut(), env.clone(), info_alice.clone(), 20).is_err()
|
||||
);
|
||||
|
||||
env.block.time = env.block.time.plus_seconds(2);
|
||||
|
||||
// succeds after some time
|
||||
assert!(try_update_mixnode_config(deps.as_mut(), env, info_alice, 20).is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ pub fn query_operator_reward(deps: Deps, owner: String) -> Result<Uint128, Contr
|
||||
}
|
||||
};
|
||||
|
||||
super::transactions::calculate_operator_reward(deps.storage, &owner_address, &bond)
|
||||
super::transactions::calculate_operator_reward(deps.storage, deps.api, &owner_address, &bond)
|
||||
}
|
||||
|
||||
pub fn query_delegator_reward(
|
||||
@@ -48,17 +48,20 @@ pub fn query_delegator_reward(
|
||||
mix_identity: IdentityKey,
|
||||
proxy: Option<String>,
|
||||
) -> Result<Uint128, ContractError> {
|
||||
let proxy = proxy.map(|p| {
|
||||
deps.api
|
||||
.addr_validate(&p)
|
||||
.map_err(|_| ContractError::InvalidAddress(p))
|
||||
.expect("proxy address is invalid")
|
||||
});
|
||||
let proxy = match proxy {
|
||||
Some(proxy) => Some(
|
||||
deps.api
|
||||
.addr_validate(&proxy)
|
||||
.map_err(|_| ContractError::InvalidAddress(proxy))?,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let key = mixnet_contract_common::delegation::generate_storage_key(
|
||||
&deps.api.addr_validate(&owner)?,
|
||||
proxy.as_ref(),
|
||||
);
|
||||
super::transactions::calculate_delegator_reward(deps.storage, key, &mix_identity)
|
||||
super::transactions::calculate_delegator_reward(deps.storage, deps.api, key, &mix_identity)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -60,5 +60,5 @@ pub fn decr_reward_pool(
|
||||
|
||||
pub fn circulating_supply(storage: &dyn Storage) -> StdResult<Uint128> {
|
||||
let reward_pool = REWARD_POOL.load(storage)?;
|
||||
Ok(Uint128::new(TOTAL_SUPPLY) - reward_pool)
|
||||
Ok(Uint128::new(TOTAL_SUPPLY).saturating_sub(reward_pool))
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use super::storage::{
|
||||
OPERATOR_REWARD_CLAIMED_HEIGHT,
|
||||
};
|
||||
use crate::constants;
|
||||
use crate::contract::debug_with_visibility;
|
||||
use crate::delegations::storage as delegations_storage;
|
||||
use crate::delegations::transactions::_try_delegate_to_mixnode;
|
||||
use crate::error::ContractError;
|
||||
@@ -15,6 +16,7 @@ use crate::rewards::helpers;
|
||||
use crate::support::helpers::is_authorized;
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::{Addr, Api, Coin, DepsMut, Env, MessageInfo, Order, Response, Storage, Uint128};
|
||||
use cw_storage_plus::Bound;
|
||||
use mixnet_contract_common::events::{
|
||||
new_compound_delegator_reward_event, new_compound_operator_reward_event,
|
||||
new_mix_operator_rewarding_event, new_not_found_mix_operator_rewarding_event,
|
||||
@@ -35,8 +37,13 @@ pub fn try_compound_operator_reward_on_behalf(
|
||||
let proxy = deps.api.addr_validate(info.sender.as_str())?;
|
||||
let owner = deps.api.addr_validate(&owner)?;
|
||||
|
||||
let reward =
|
||||
_try_compound_operator_reward(deps.storage, env.block.height, &owner, Some(proxy))?;
|
||||
let reward = _try_compound_operator_reward(
|
||||
deps.storage,
|
||||
deps.api,
|
||||
env.block.height,
|
||||
&owner,
|
||||
Some(proxy),
|
||||
)?;
|
||||
|
||||
Ok(Response::new().add_event(new_compound_operator_reward_event(&owner, reward)))
|
||||
}
|
||||
@@ -47,13 +54,15 @@ pub fn try_compound_operator_reward(
|
||||
info: MessageInfo,
|
||||
) -> Result<Response, ContractError> {
|
||||
let owner = deps.api.addr_validate(info.sender.as_str())?;
|
||||
let reward = _try_compound_operator_reward(deps.storage, env.block.height, &owner, None)?;
|
||||
let reward =
|
||||
_try_compound_operator_reward(deps.storage, deps.api, env.block.height, &owner, None)?;
|
||||
|
||||
Ok(Response::new().add_event(new_compound_operator_reward_event(&owner, reward)))
|
||||
}
|
||||
|
||||
pub fn _try_compound_operator_reward(
|
||||
storage: &mut dyn Storage,
|
||||
api: &dyn Api,
|
||||
block_height: u64,
|
||||
owner: &Addr,
|
||||
proxy: Option<Addr>,
|
||||
@@ -72,8 +81,9 @@ pub fn _try_compound_operator_reward(
|
||||
}
|
||||
|
||||
let mut updated_bond = bond.clone();
|
||||
let reward = calculate_operator_reward(storage, owner, &bond)?;
|
||||
updated_bond.accumulated_rewards = Some(updated_bond.accumulated_rewards() - reward);
|
||||
let reward = calculate_operator_reward(storage, api, owner, &bond)?;
|
||||
updated_bond.accumulated_rewards =
|
||||
Some(updated_bond.accumulated_rewards().saturating_sub(reward));
|
||||
updated_bond.pledge_amount.amount += reward;
|
||||
mixnodes().replace(
|
||||
storage,
|
||||
@@ -94,6 +104,7 @@ pub fn _try_compound_operator_reward(
|
||||
|
||||
pub fn calculate_operator_reward(
|
||||
storage: &dyn Storage,
|
||||
api: &dyn Api,
|
||||
owner: &Addr,
|
||||
bond: &StoredMixnodeBond,
|
||||
) -> Result<Uint128, ContractError> {
|
||||
@@ -104,26 +115,33 @@ pub fn calculate_operator_reward(
|
||||
let accumulated_rewards = mixnodes()
|
||||
.changelog()
|
||||
.prefix(bond.identity())
|
||||
.keys(storage, None, None, Order::Ascending)
|
||||
.keys(
|
||||
storage,
|
||||
Some(Bound::exclusive(last_claimed_height)),
|
||||
None,
|
||||
Order::Ascending,
|
||||
)
|
||||
.filter_map(|height| height.ok())
|
||||
.filter(|height| last_claimed_height <= *height)
|
||||
.fold(
|
||||
Ok(Uint128::zero()),
|
||||
|acc, height| -> Result<Uint128, ContractError> {
|
||||
let accumulated_reward = acc?;
|
||||
if let Some(bond) =
|
||||
mixnodes().may_load_at_height(storage, bond.identity().as_str(), height)?
|
||||
if let Some(bond) = mixnodes()
|
||||
.may_load_at_height(storage, bond.identity().as_str(), height)
|
||||
.ok()
|
||||
.flatten()
|
||||
{
|
||||
if let Some(epoch_rewards) = bond.epoch_rewards {
|
||||
let epoch_reward_params =
|
||||
epoch_reward_params_for_id(storage, epoch_rewards.epoch_id())?;
|
||||
if let Some(ref epoch_rewards) = bond.epoch_rewards {
|
||||
// Compound rewards from previous heights
|
||||
let reward_at_height = epoch_rewards.delegation_reward(
|
||||
bond.pledge_amount().amount + accumulated_reward,
|
||||
bond.profit_margin(),
|
||||
epoch_reward_params,
|
||||
)?;
|
||||
return Ok(accumulated_reward + reward_at_height);
|
||||
match epoch_rewards.operator_reward(bond.profit_margin()) {
|
||||
Ok(reward) => return Ok(accumulated_reward + reward),
|
||||
Err(err) => {
|
||||
debug_with_visibility(
|
||||
api,
|
||||
format!("Failed to calculate operator reward: {:?}", err),
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
Ok(accumulated_reward)
|
||||
@@ -150,8 +168,7 @@ pub fn try_compound_delegator_reward_on_behalf(
|
||||
let owner = deps.api.addr_validate(&owner)?;
|
||||
let reward = _try_compound_delegator_reward(
|
||||
env.block.height,
|
||||
deps.api,
|
||||
deps.storage,
|
||||
deps,
|
||||
owner.as_str(),
|
||||
&mix_identity,
|
||||
Some(proxy.clone()),
|
||||
@@ -176,8 +193,7 @@ pub fn try_compound_delegator_reward(
|
||||
let owner = deps.api.addr_validate(info.sender.as_str())?;
|
||||
let reward = _try_compound_delegator_reward(
|
||||
env.block.height,
|
||||
deps.api,
|
||||
deps.storage,
|
||||
deps,
|
||||
owner.as_str(),
|
||||
&mix_identity,
|
||||
None,
|
||||
@@ -195,8 +211,7 @@ pub fn try_compound_delegator_reward(
|
||||
|
||||
pub fn _try_compound_delegator_reward(
|
||||
block_height: u64,
|
||||
api: &dyn Api,
|
||||
storage: &mut dyn Storage,
|
||||
mut deps: DepsMut<'_>,
|
||||
owner_address: &str,
|
||||
mix_identity: &str,
|
||||
proxy: Option<Addr>,
|
||||
@@ -204,25 +219,25 @@ pub fn _try_compound_delegator_reward(
|
||||
let delegation_map = crate::delegations::storage::delegations();
|
||||
|
||||
let key = mixnet_contract_common::delegation::generate_storage_key(
|
||||
&api.addr_validate(owner_address)?,
|
||||
&deps.api.addr_validate(owner_address)?,
|
||||
proxy.as_ref(),
|
||||
);
|
||||
let reward = calculate_delegator_reward(storage, key.clone(), mix_identity)?;
|
||||
let reward = calculate_delegator_reward(deps.storage, deps.api, key.clone(), mix_identity)?;
|
||||
let mut compounded_delegation = reward;
|
||||
|
||||
// Might want to introduce paging here
|
||||
let delegation_heights = delegation_map
|
||||
.prefix((mix_identity.to_string(), key.clone()))
|
||||
.keys(storage, None, None, cosmwasm_std::Order::Ascending)
|
||||
.keys(deps.storage, None, None, cosmwasm_std::Order::Ascending)
|
||||
.filter_map(|v| v.ok())
|
||||
.collect::<Vec<u64>>();
|
||||
|
||||
for h in delegation_heights {
|
||||
let delegation =
|
||||
delegation_map.load(storage, (mix_identity.to_string(), key.clone(), h))?;
|
||||
delegation_map.load(deps.storage, (mix_identity.to_string(), key.clone(), h))?;
|
||||
compounded_delegation += delegation.amount.amount;
|
||||
delegation_map.replace(
|
||||
storage,
|
||||
deps.storage,
|
||||
(mix_identity.to_string(), key.clone(), h),
|
||||
None,
|
||||
Some(&delegation),
|
||||
@@ -231,13 +246,12 @@ pub fn _try_compound_delegator_reward(
|
||||
|
||||
if compounded_delegation != Uint128::zero() {
|
||||
_try_delegate_to_mixnode(
|
||||
storage,
|
||||
api,
|
||||
deps.branch(),
|
||||
block_height,
|
||||
mix_identity,
|
||||
owner_address,
|
||||
Coin {
|
||||
amount: reward,
|
||||
amount: compounded_delegation,
|
||||
denom: DENOM.to_string(),
|
||||
},
|
||||
proxy,
|
||||
@@ -245,15 +259,14 @@ pub fn _try_compound_delegator_reward(
|
||||
}
|
||||
|
||||
{
|
||||
//TODO: Node exists all is well, life goes on, if it does not exist we'll just return the reward to the caller as there is nothing to do on the bond
|
||||
if let Some(mut bond) = mixnodes().may_load(storage, mix_identity)? {
|
||||
bond.accumulated_rewards = Some(bond.accumulated_rewards() - reward);
|
||||
mixnodes().save(storage, mix_identity, &bond, block_height)?;
|
||||
if let Some(mut bond) = mixnodes().may_load(deps.storage, mix_identity)? {
|
||||
bond.accumulated_rewards = Some(bond.accumulated_rewards().saturating_sub(reward));
|
||||
mixnodes().save(deps.storage, mix_identity, &bond, block_height)?;
|
||||
}
|
||||
}
|
||||
|
||||
DELEGATOR_REWARD_CLAIMED_HEIGHT.save(
|
||||
storage,
|
||||
deps.storage,
|
||||
(key, mix_identity.to_string()),
|
||||
&block_height,
|
||||
)?;
|
||||
@@ -266,6 +279,7 @@ pub fn _try_compound_delegator_reward(
|
||||
// + last_reward_claimed height is correctly used
|
||||
pub fn calculate_delegator_reward(
|
||||
storage: &dyn Storage,
|
||||
api: &dyn Api,
|
||||
key: Vec<u8>,
|
||||
mix_identity: &str,
|
||||
) -> Result<Uint128, ContractError> {
|
||||
@@ -275,45 +289,96 @@ pub fn calculate_delegator_reward(
|
||||
|
||||
// Get delegations newer then last_claimed_height, it would be nice to also fold this into the iteration bellow but it should be ok for now, as
|
||||
// I doubt folks refresh their delegations often
|
||||
let delegations = delegations_storage::delegations()
|
||||
let mut delegations = delegations_storage::delegations()
|
||||
.prefix((mix_identity.to_string(), key))
|
||||
.range(storage, None, None, Order::Descending)
|
||||
.range(
|
||||
storage,
|
||||
Some(Bound::exclusive(last_claimed_height)),
|
||||
None,
|
||||
Order::Descending,
|
||||
)
|
||||
.filter_map(|record| record.ok())
|
||||
.filter(|(height, _)| last_claimed_height <= *height)
|
||||
.map(|(_, delegation)| delegation)
|
||||
.collect::<Vec<Delegation>>();
|
||||
|
||||
// Accumulate outside of the loop to gain some speed, on a log of checkpoints
|
||||
let mut delegation_at_height = Uint128::zero();
|
||||
|
||||
// This is a bit gnarly, but we want to avoid loading all heights, the loading mixnodes, so we're doing it all in the iterator
|
||||
let accumulated_rewards = mixnodes()
|
||||
.changelog()
|
||||
.prefix(mix_identity)
|
||||
.keys(storage, None, None, Order::Ascending)
|
||||
.keys(
|
||||
storage,
|
||||
Some(Bound::exclusive(last_claimed_height)),
|
||||
None,
|
||||
Order::Ascending,
|
||||
)
|
||||
.filter_map(|height| height.ok())
|
||||
.filter(|height| last_claimed_height <= *height)
|
||||
// Get all checkpoints greater then last claimed delegation height
|
||||
.fold(
|
||||
Ok(Uint128::zero()),
|
||||
|acc, height| -> Result<Uint128, ContractError> {
|
||||
let accumulated_reward = acc?;
|
||||
let delegation_at_height = delegations
|
||||
delegation_at_height = delegations
|
||||
.iter()
|
||||
.filter(|d| height <= d.block_height)
|
||||
.fold(Uint128::zero(), |total, delegation| {
|
||||
.filter(|d| d.block_height <= height)
|
||||
.fold(delegation_at_height, |total, delegation| {
|
||||
total + delegation.amount.amount
|
||||
});
|
||||
// Drop what we've processed
|
||||
// This should be replaced with drain_filter once it stabilizes
|
||||
delegations.retain(|d| d.block_height > height);
|
||||
// debug_with_visibility(
|
||||
// api,
|
||||
// format!("delegation at height {} - {}", height, delegation_at_height),
|
||||
// );
|
||||
if delegation_at_height != Uint128::zero() {
|
||||
if let Some(bond) =
|
||||
mixnodes().may_load_at_height(storage, mix_identity, height)?
|
||||
// debug_with_visibility(
|
||||
// api,
|
||||
// format!("Loading bond {} at height {}", mix_identity, height),
|
||||
// );
|
||||
if let Some(bond) = mixnodes()
|
||||
.may_load_at_height(storage, mix_identity, height)
|
||||
.ok()
|
||||
.flatten()
|
||||
{
|
||||
if let Some(epoch_rewards) = bond.epoch_rewards {
|
||||
if let Some(ref epoch_rewards) = bond.epoch_rewards {
|
||||
// Compound rewards from previous heights
|
||||
let epoch_reward_params =
|
||||
epoch_reward_params_for_id(storage, epoch_rewards.epoch_id())?;
|
||||
let reward_at_height = epoch_rewards.delegation_reward(
|
||||
delegation_at_height + accumulated_reward,
|
||||
bond.profit_margin(),
|
||||
epoch_reward_params,
|
||||
)?;
|
||||
return Ok(accumulated_reward + reward_at_height);
|
||||
match epoch_reward_params_for_id(storage, epoch_rewards.epoch_id()) {
|
||||
Ok(params) => {
|
||||
let reward_at_height = match epoch_rewards.delegation_reward(
|
||||
delegation_at_height + accumulated_reward,
|
||||
bond.profit_margin(),
|
||||
params,
|
||||
) {
|
||||
Ok(reward) => {
|
||||
// debug_with_visibility(
|
||||
// api,
|
||||
// format!("Reward at height {} - {}", height, reward),
|
||||
// );
|
||||
reward
|
||||
}
|
||||
Err(err) => {
|
||||
debug_with_visibility(
|
||||
api,
|
||||
format!(
|
||||
"Error calculating reward at {} - {}",
|
||||
height, err
|
||||
),
|
||||
);
|
||||
Uint128::zero()
|
||||
}
|
||||
};
|
||||
return Ok(accumulated_reward + reward_at_height);
|
||||
}
|
||||
Err(_err) => {
|
||||
debug_with_visibility(
|
||||
api,
|
||||
format!("No epoch reward params for epoch {}", height),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -455,7 +520,9 @@ pub(crate) fn try_reward_mixnode(
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::constants::EPOCHS_IN_INTERVAL;
|
||||
use crate::delegations::transactions::try_delegate_to_mixnode;
|
||||
use crate::delegations::transactions::{
|
||||
_try_remove_delegation_from_mixnode, try_delegate_to_mixnode,
|
||||
};
|
||||
use crate::error::ContractError;
|
||||
use crate::interval::storage::{
|
||||
current_epoch_reward_params, save_epoch, save_epoch_reward_params,
|
||||
@@ -472,7 +539,7 @@ pub mod tests {
|
||||
use az::CheckedCast;
|
||||
use config::defaults::DENOM;
|
||||
use cosmwasm_std::testing::{mock_env, mock_info};
|
||||
use cosmwasm_std::{coin, coins, Addr, Timestamp, Uint128};
|
||||
use cosmwasm_std::{coin, coins, Addr, StdError, Timestamp, Uint128};
|
||||
use mixnet_contract_common::events::{
|
||||
must_find_attribute, BOND_TOO_FRESH_VALUE, NO_REWARD_REASON_KEY,
|
||||
OPERATOR_REWARDING_EVENT_TYPE,
|
||||
@@ -808,9 +875,13 @@ pub mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reward_additivity() {
|
||||
fn test_reward_additivity_and_snapshots() {
|
||||
use crate::constants::INTERVAL_REWARD_PERCENT;
|
||||
use crate::contract::INITIAL_REWARD_POOL;
|
||||
use crate::mixnodes::transactions::try_add_mixnode;
|
||||
use rand::thread_rng;
|
||||
|
||||
let mixnodes = crate::mixnodes::storage::mixnodes();
|
||||
|
||||
type U128 = fixed::types::U75F53;
|
||||
|
||||
@@ -820,14 +891,81 @@ pub mod tests {
|
||||
.load(deps.as_ref().storage)
|
||||
.unwrap();
|
||||
let rewarding_validator_address = current_state.rewarding_validator_address;
|
||||
|
||||
let info = mock_info(rewarding_validator_address.as_str(), &[]);
|
||||
|
||||
crate::mixnodes::transactions::try_checkpoint_mixnodes(
|
||||
&mut deps.storage,
|
||||
env.block.height,
|
||||
info.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
let checkpoints = mixnodes
|
||||
.changelog()
|
||||
.keys(&deps.storage, None, None, Order::Ascending)
|
||||
.filter_map(|x| x.ok())
|
||||
.collect::<Vec<(IdentityKey, u64)>>();
|
||||
assert_eq!(0, checkpoints.len());
|
||||
|
||||
let period_reward_pool = (INITIAL_REWARD_POOL / 100 / EPOCHS_IN_INTERVAL as u128)
|
||||
* INTERVAL_REWARD_PERCENT as u128;
|
||||
assert_eq!(period_reward_pool, 6_944_444_444);
|
||||
let circulating_supply = storage::circulating_supply(&deps.storage).unwrap().u128();
|
||||
assert_eq!(circulating_supply, 750_000_000_000_000u128);
|
||||
|
||||
let node_owner: Addr = Addr::unchecked("alice");
|
||||
let node_identity = test_helpers::add_mixnode(
|
||||
let sender = Addr::unchecked("alice");
|
||||
let stake = coins(10_000_000_000, DENOM);
|
||||
|
||||
let keypair = crypto::asymmetric::identity::KeyPair::new(&mut thread_rng());
|
||||
let owner_signature = keypair
|
||||
.private_key()
|
||||
.sign(sender.as_bytes())
|
||||
.to_base58_string();
|
||||
|
||||
let legit_sphinx_key = crypto::asymmetric::encryption::KeyPair::new(&mut thread_rng());
|
||||
|
||||
let info = mock_info(sender.as_str(), &stake);
|
||||
|
||||
let node_identity_1 = keypair.public_key().to_base58_string();
|
||||
|
||||
env.block.height;
|
||||
|
||||
try_add_mixnode(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
info.clone(),
|
||||
MixNode {
|
||||
identity_key: node_identity_1.clone(),
|
||||
sphinx_key: legit_sphinx_key.public_key().to_base58_string(),
|
||||
..tests::fixtures::mix_node_fixture()
|
||||
},
|
||||
owner_signature,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// tick
|
||||
|
||||
env.block.height += 1;
|
||||
|
||||
let info = mock_info(rewarding_validator_address.as_str(), &[]);
|
||||
crate::mixnodes::transactions::try_checkpoint_mixnodes(
|
||||
&mut deps.storage,
|
||||
env.block.height,
|
||||
info.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
mixnodes
|
||||
.assert_checkpointed(&deps.storage, env.block.height)
|
||||
.unwrap();
|
||||
let checkpoints = mixnodes
|
||||
.changelog()
|
||||
.keys(&deps.storage, None, None, Order::Ascending)
|
||||
.filter_map(|x| x.ok())
|
||||
.collect::<Vec<(IdentityKey, u64)>>();
|
||||
assert_eq!(checkpoints.len(), 1);
|
||||
|
||||
let node_owner: Addr = Addr::unchecked("johnny");
|
||||
let node_identity_2 = test_helpers::add_mixnode(
|
||||
node_owner.as_str(),
|
||||
coins(10_000_000_000, DENOM),
|
||||
deps.as_mut(),
|
||||
@@ -837,7 +975,7 @@ pub mod tests {
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("alice_d1", &[coin(8000_000000, DENOM)]),
|
||||
node_identity.clone(),
|
||||
node_identity_1.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -845,17 +983,10 @@ pub mod tests {
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
mock_info("alice_d2", &[coin(2000_000000, DENOM)]),
|
||||
node_identity.clone(),
|
||||
node_identity_1.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let node_owner: Addr = Addr::unchecked("bob");
|
||||
let node_identity_2 = test_helpers::add_mixnode(
|
||||
node_owner.as_str(),
|
||||
coins(10_000_000_000, DENOM),
|
||||
deps.as_mut(),
|
||||
);
|
||||
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
mock_env(),
|
||||
@@ -895,13 +1026,16 @@ pub mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
crate::delegations::transactions::_try_reconcile_all_delegation_events(&mut deps.storage)
|
||||
.unwrap();
|
||||
crate::delegations::transactions::_try_reconcile_all_delegation_events(
|
||||
&mut deps.storage,
|
||||
&deps.api,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
|
||||
env.block.height += 2 * constants::MINIMUM_BLOCK_AGE_FOR_REWARDING;
|
||||
|
||||
let mix_1 = mixnodes_storage::read_full_mixnode_bond(&deps.storage, &node_identity)
|
||||
let mix_1 = mixnodes_storage::read_full_mixnode_bond(&deps.storage, &node_identity_1)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
let mix_1_uptime = 100;
|
||||
@@ -942,6 +1076,230 @@ pub mod tests {
|
||||
|
||||
let mix_1_reward_result = mix_1.reward(¶ms);
|
||||
|
||||
let info = mock_info(rewarding_validator_address.as_str(), &[]);
|
||||
crate::mixnodes::transactions::try_checkpoint_mixnodes(
|
||||
&mut deps.storage,
|
||||
env.block.height,
|
||||
info.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
mixnodes
|
||||
.assert_checkpointed(&deps.storage, env.block.height)
|
||||
.unwrap();
|
||||
|
||||
try_reward_mixnode(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
info.clone(),
|
||||
node_identity_1.clone(),
|
||||
node_reward_params,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mix_after_reward = mixnodes.may_load(&deps.storage, &node_identity_1).unwrap();
|
||||
// println!("{:?}", mix_after_reward);
|
||||
|
||||
let checkpoints = mixnodes
|
||||
.changelog()
|
||||
.prefix(&node_identity_1)
|
||||
.keys(&deps.storage, None, None, Order::Ascending)
|
||||
.filter_map(|x| x.ok())
|
||||
.collect::<Vec<u64>>();
|
||||
assert_eq!(checkpoints.len(), 2);
|
||||
|
||||
env.block.height += 10000;
|
||||
env.block.time = env.block.time.plus_seconds(3601);
|
||||
|
||||
try_advance_epoch(
|
||||
env.clone(),
|
||||
&mut deps.storage,
|
||||
rewarding_validator_address.to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// After two snapshots we should see an increase in delegation
|
||||
try_delegate_to_mixnode(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
mock_info("alice_d1", &[coin(8000_000000, DENOM)]),
|
||||
node_identity_1.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
crate::delegations::transactions::_try_reconcile_all_delegation_events(
|
||||
&mut deps.storage,
|
||||
&deps.api,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let info = mock_info(rewarding_validator_address.as_str(), &[]);
|
||||
crate::mixnodes::transactions::try_checkpoint_mixnodes(
|
||||
&mut deps.storage,
|
||||
env.block.height,
|
||||
info.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
mixnodes
|
||||
.assert_checkpointed(&deps.storage, env.block.height)
|
||||
.unwrap();
|
||||
|
||||
try_reward_mixnode(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
info.clone(),
|
||||
node_identity_1.clone(),
|
||||
node_reward_params,
|
||||
)
|
||||
.unwrap();
|
||||
let mix_after_reward_2 = mixnodes.may_load(&deps.storage, &node_identity_1).unwrap();
|
||||
|
||||
assert_ne!(mix_after_reward, mix_after_reward_2);
|
||||
|
||||
let checkpoints = mixnodes
|
||||
.changelog()
|
||||
.prefix(&node_identity_1)
|
||||
.keys(&deps.storage, None, None, Order::Ascending)
|
||||
.collect::<Vec<Result<u64, StdError>>>();
|
||||
assert_eq!(checkpoints.len(), 3);
|
||||
|
||||
env.block.height += 10000;
|
||||
env.block.time = env.block.time.plus_seconds(3601);
|
||||
|
||||
try_advance_epoch(
|
||||
env.clone(),
|
||||
&mut deps.storage,
|
||||
rewarding_validator_address.to_string(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let info = mock_info(rewarding_validator_address.as_str(), &[]);
|
||||
crate::mixnodes::transactions::try_checkpoint_mixnodes(
|
||||
&mut deps.storage,
|
||||
env.block.height,
|
||||
info.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
mixnodes
|
||||
.assert_checkpointed(&deps.storage, env.block.height)
|
||||
.unwrap();
|
||||
|
||||
try_reward_mixnode(
|
||||
deps.as_mut(),
|
||||
env.clone(),
|
||||
info.clone(),
|
||||
node_identity_1.clone(),
|
||||
node_reward_params,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let checkpoints = mixnodes
|
||||
.changelog()
|
||||
.prefix(&node_identity_1)
|
||||
.keys(&deps.storage, None, None, Order::Ascending)
|
||||
.filter_map(|x| x.ok())
|
||||
.collect::<Vec<u64>>();
|
||||
assert_eq!(checkpoints.len(), 4);
|
||||
|
||||
let delegation_map = crate::delegations::storage::delegations();
|
||||
let key = "alice_d1".as_bytes().to_vec();
|
||||
|
||||
let last_claimed_height = storage::DELEGATOR_REWARD_CLAIMED_HEIGHT
|
||||
.load(&deps.storage, (key.clone(), node_identity_1.to_string()))
|
||||
.unwrap_or(0);
|
||||
|
||||
assert_eq!(last_claimed_height, 0);
|
||||
|
||||
let viable_delegations = delegation_map
|
||||
.prefix((node_identity_1.to_string(), key.clone()))
|
||||
.range(&deps.storage, None, None, Order::Descending)
|
||||
.filter_map(|record| record.ok())
|
||||
.filter(|(height, _)| last_claimed_height <= *height)
|
||||
.map(|(_, delegation)| delegation)
|
||||
.collect::<Vec<Delegation>>();
|
||||
|
||||
assert_eq!(viable_delegations.len(), 2);
|
||||
|
||||
let viable_heights = mixnodes
|
||||
.changelog()
|
||||
.prefix(&node_identity_1)
|
||||
.keys(&deps.storage, None, None, Order::Ascending)
|
||||
.filter_map(|height| height.ok())
|
||||
.filter(|height| last_claimed_height <= *height)
|
||||
.collect::<Vec<u64>>();
|
||||
|
||||
// Should be equal to the number of checkpoints
|
||||
assert_eq!(viable_heights.len(), 4);
|
||||
|
||||
for (i, h) in viable_heights.into_iter().enumerate() {
|
||||
let delegation_at_height = viable_delegations
|
||||
.iter()
|
||||
.filter(|d| d.block_height <= h)
|
||||
.fold(Uint128::zero(), |total, delegation| {
|
||||
total + delegation.amount.amount
|
||||
});
|
||||
if i < 2 {
|
||||
assert_eq!(delegation_at_height, Uint128::new(8000000000));
|
||||
} else {
|
||||
assert_eq!(delegation_at_height, Uint128::new(16000000000));
|
||||
}
|
||||
}
|
||||
|
||||
let alice_reward =
|
||||
calculate_delegator_reward(&deps.storage, &deps.api, key.clone(), &node_identity_1)
|
||||
.unwrap();
|
||||
assert_eq!(alice_reward, Uint128::new(304552));
|
||||
|
||||
let mix_0 = mixnodes.load(&deps.storage, &node_identity_1).unwrap();
|
||||
|
||||
_try_compound_delegator_reward(
|
||||
env.block.height,
|
||||
deps.as_mut(),
|
||||
"alice_d1",
|
||||
&node_identity_1,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
crate::delegations::transactions::_try_reconcile_all_delegation_events(
|
||||
&mut deps.storage,
|
||||
&deps.api,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let delegations = crate::delegations::storage::delegations()
|
||||
.prefix((node_identity_1.to_string(), key.clone()))
|
||||
.range(&deps.storage, None, None, Order::Ascending)
|
||||
.filter_map(|x| x.ok())
|
||||
.map(|(_, delegation)| delegation)
|
||||
.collect::<Vec<Delegation>>();
|
||||
assert_eq!(delegations.len(), 1);
|
||||
|
||||
let delegation = delegations.first().unwrap();
|
||||
assert_eq!(delegation.amount.amount, Uint128::new(16000000000 + 304552));
|
||||
|
||||
let mix_1 = mixnodes
|
||||
.load(&deps.storage, &node_identity_1.clone())
|
||||
.unwrap();
|
||||
|
||||
_try_remove_delegation_from_mixnode(deps.as_mut(), env, node_identity_1, "alice_d1", None)
|
||||
.unwrap();
|
||||
|
||||
crate::delegations::transactions::_try_reconcile_all_delegation_events(
|
||||
&mut deps.storage,
|
||||
&deps.api,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
mix_0.accumulated_rewards(),
|
||||
mix_1.accumulated_rewards() + alice_reward
|
||||
);
|
||||
|
||||
let operator_reward =
|
||||
calculate_operator_reward(&deps.storage, &deps.api, &Addr::unchecked("alice"), &mix_1)
|
||||
.unwrap();
|
||||
assert_eq!(operator_reward, Uint128::new(352532));
|
||||
|
||||
assert_eq!(
|
||||
mix_1_reward_result.sigma(),
|
||||
U128::from_num(0.0000266666666666f64)
|
||||
@@ -1011,8 +1369,11 @@ pub mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
crate::delegations::transactions::_try_reconcile_all_delegation_events(&mut deps.storage)
|
||||
.unwrap();
|
||||
crate::delegations::transactions::_try_reconcile_all_delegation_events(
|
||||
&mut deps.storage,
|
||||
&deps.api,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let info = mock_info(rewarding_validator_address.as_ref(), &[]);
|
||||
env.block.height += 2 * constants::MINIMUM_BLOCK_AGE_FOR_REWARDING;
|
||||
|
||||
@@ -334,6 +334,7 @@ fn try_create_periodic_vesting_account(
|
||||
if info.sender != ADMIN.load(deps.storage)? {
|
||||
return Err(ContractError::NotAdmin(info.sender.as_str().to_string()));
|
||||
}
|
||||
|
||||
let account_exists = account_from_address(owner_address, deps.storage, deps.api).is_ok();
|
||||
if account_exists {
|
||||
return Err(ContractError::AccountAlreadyExists(
|
||||
@@ -344,6 +345,7 @@ fn try_create_periodic_vesting_account(
|
||||
let vesting_spec = vesting_spec.unwrap_or_default();
|
||||
|
||||
let coin = validate_funds(&info.funds)?;
|
||||
|
||||
let owner_address = deps.api.addr_validate(owner_address)?;
|
||||
let staking_address = if let Some(staking_address) = staking_address {
|
||||
Some(deps.api.addr_validate(&staking_address)?)
|
||||
@@ -357,6 +359,9 @@ fn try_create_periodic_vesting_account(
|
||||
let periods = populate_vesting_periods(start_time, vesting_spec);
|
||||
|
||||
let start_time = Timestamp::from_seconds(start_time);
|
||||
|
||||
let response = Response::new();
|
||||
|
||||
Account::new(
|
||||
owner_address.clone(),
|
||||
staking_address.clone(),
|
||||
@@ -366,24 +371,6 @@ fn try_create_periodic_vesting_account(
|
||||
deps.storage,
|
||||
)?;
|
||||
|
||||
let mut response = Response::new();
|
||||
|
||||
let send_tokens_owner = BankMsg::Send {
|
||||
to_address: owner_address.as_str().to_string(),
|
||||
amount: vec![Coin::new(1_000_000, DENOM)],
|
||||
};
|
||||
|
||||
response = response.add_message(send_tokens_owner);
|
||||
|
||||
if let Some(staking_address) = staking_address.as_ref() {
|
||||
let send_tokens_staking = BankMsg::Send {
|
||||
to_address: staking_address.clone().as_str().to_string(),
|
||||
amount: vec![Coin::new(1_000_000, DENOM)],
|
||||
};
|
||||
|
||||
response = response.add_message(send_tokens_staking);
|
||||
}
|
||||
|
||||
Ok(response.add_event(new_periodic_vesting_account_event(
|
||||
&owner_address,
|
||||
&coin,
|
||||
|
||||
@@ -44,4 +44,6 @@ pub enum ContractError {
|
||||
InvalidAddress(String),
|
||||
#[error("VESTING ({}): Account already exists: {0}", line!())]
|
||||
AccountAlreadyExists(String),
|
||||
#[error("VESTING ({}): Too few coins sent for vesting account creation, sent {sent}, need at least {need}", line!())]
|
||||
MinVestingFunds { sent: u128, need: u128 },
|
||||
}
|
||||
|
||||
@@ -14,24 +14,22 @@ impl VestingAccount for Account {
|
||||
env: &Env,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Coin, ContractError> {
|
||||
// Returns 0 in case of underflow.
|
||||
// Returns 0 in case of underflow. Which is fine, as the amount of pledged and delegated tokens can be larger then vesting_coins due to rewards and vesting periods expiring
|
||||
Ok(Coin {
|
||||
amount: Uint128::new(
|
||||
self.get_vesting_coins(block_time, env)?
|
||||
.amount
|
||||
.u128()
|
||||
.checked_sub(
|
||||
.saturating_sub(
|
||||
self.get_delegated_vesting(block_time, env, storage)?
|
||||
.amount
|
||||
.u128(),
|
||||
)
|
||||
.ok_or(ContractError::Underflow)?
|
||||
.checked_sub(
|
||||
.saturating_sub(
|
||||
self.get_pledged_vesting(block_time, env, storage)?
|
||||
.amount
|
||||
.u128(),
|
||||
)
|
||||
.ok_or(ContractError::Underflow)?,
|
||||
),
|
||||
),
|
||||
denom: DENOM.to_string(),
|
||||
})
|
||||
|
||||
@@ -77,6 +77,7 @@ mod tests {
|
||||
assert_eq!(created_account_test_by_staking, created_account);
|
||||
assert_eq!(
|
||||
created_account.load_balance(&deps.storage).unwrap(),
|
||||
// One was liquidated
|
||||
Uint128::new(1_000_000_000_000)
|
||||
);
|
||||
// Try create the same account again
|
||||
|
||||
+116
-100
@@ -1,106 +1,122 @@
|
||||
version: '3.7'
|
||||
x-bech32-prefix: &BECH32_PREFIX
|
||||
nymt
|
||||
x-wasmd-version: &WASMD_VERSION
|
||||
v0.21.0
|
||||
x-wasmd-commit-hash: &WASMD_COMMIT_HASH
|
||||
1d436638af7cacb5aeeb7248b57b085c64f3ae35
|
||||
|
||||
x-network: &NETWORK
|
||||
BECH32_PREFIX: nymt
|
||||
DENOM: nymt
|
||||
STAKE_DENOM: nyxt
|
||||
WASMD_VERSION: v0.26.0
|
||||
WASMD_COMMIT_HASH: dc5ef6fe84f0a5e3b0894692a18cc48fb5b00adf
|
||||
|
||||
services:
|
||||
genesis_validator:
|
||||
build:
|
||||
context: docker/validator
|
||||
args:
|
||||
BECH32_PREFIX: *BECH32_PREFIX
|
||||
WASMD_VERSION: *WASMD_VERSION
|
||||
WASMD_COMMIT_HASH: *WASMD_COMMIT_HASH
|
||||
image: validator:latest
|
||||
ports:
|
||||
- "26657:26657"
|
||||
- "1317:1317"
|
||||
container_name: genesis_validator
|
||||
volumes:
|
||||
- "genesis_volume:/genesis_volume"
|
||||
environment:
|
||||
BECH32_PREFIX: *BECH32_PREFIX
|
||||
WASMD_VERSION: *WASMD_VERSION
|
||||
command: ["genesis"]
|
||||
secondary_validator:
|
||||
build:
|
||||
context: docker/validator
|
||||
args:
|
||||
BECH32_PREFIX: *BECH32_PREFIX
|
||||
WASMD_VERSION: *WASMD_VERSION
|
||||
image: validator:latest
|
||||
volumes:
|
||||
- "genesis_volume:/genesis_volume:ro"
|
||||
environment:
|
||||
BECH32_PREFIX: *BECH32_PREFIX
|
||||
WASMD_VERSION: *WASMD_VERSION
|
||||
depends_on:
|
||||
- "genesis_validator"
|
||||
command: ["secondary"]
|
||||
mixnet_contract:
|
||||
build: docker/mixnet_contract
|
||||
image: contract:latest
|
||||
volumes:
|
||||
- ".:/nym"
|
||||
vesting_contract:
|
||||
build: docker/vesting_contract
|
||||
image: vesting_contract:latest
|
||||
volumes:
|
||||
- ".:/nym"
|
||||
contract_uploader:
|
||||
build: docker/typescript_client
|
||||
image: contract_uploader:typescript
|
||||
volumes:
|
||||
- "genesis_volume:/genesis_volume:ro"
|
||||
- "contract_volume:/contract_volume"
|
||||
- ".:/nym"
|
||||
depends_on:
|
||||
- "genesis_validator"
|
||||
- "secondary_validator"
|
||||
- "mixnet_contract"
|
||||
environment:
|
||||
BECH32_PREFIX: *BECH32_PREFIX
|
||||
mnemonic_echo:
|
||||
build: docker/mnemonic_echo
|
||||
image: mnemonic_echo:latest
|
||||
volumes:
|
||||
- "genesis_volume:/genesis_volume:ro"
|
||||
depends_on:
|
||||
- "genesis_validator"
|
||||
genesis_validator:
|
||||
build:
|
||||
context: docker/validator
|
||||
args: *NETWORK
|
||||
image: validator:latest
|
||||
ports:
|
||||
- "26657:26657"
|
||||
- "1317:1317"
|
||||
container_name: genesis_validator
|
||||
volumes:
|
||||
- "genesis_volume:/genesis_volume"
|
||||
- "genesis_nymd:/root/.nymd"
|
||||
environment: *NETWORK
|
||||
networks:
|
||||
localnet:
|
||||
ipv4_address: 172.168.10.2
|
||||
command: [ "genesis" ]
|
||||
secondary_validator:
|
||||
build:
|
||||
context: docker/validator
|
||||
args: *NETWORK
|
||||
image: validator:latest
|
||||
ports:
|
||||
- "36657:26657"
|
||||
- "2317:1317"
|
||||
volumes:
|
||||
- "genesis_volume:/genesis_volume"
|
||||
- "secondary_nymd:/root/.nymd"
|
||||
environment: *NETWORK
|
||||
networks:
|
||||
localnet:
|
||||
ipv4_address: 172.168.10.3
|
||||
depends_on:
|
||||
- "genesis_validator"
|
||||
command: [ "secondary" ]
|
||||
# mixnet_contract:
|
||||
# build: docker/mixnet_contract
|
||||
# image: contract:latest
|
||||
# volumes:
|
||||
# - ".:/nym"
|
||||
# vesting_contract:
|
||||
# build: docker/vesting_contract
|
||||
# image: vesting_contract:latest
|
||||
# volumes:
|
||||
# - ".:/nym"
|
||||
# contract_uploader:
|
||||
# build: docker/typescript_client
|
||||
# image: contract_uploader:typescript
|
||||
# volumes:
|
||||
# - "genesis_volume:/genesis_volume:ro"
|
||||
# - "contract_volume:/contract_volume"
|
||||
# - ".:/nym"
|
||||
# depends_on:
|
||||
# - "genesis_validator"
|
||||
# - "secondary_validator"
|
||||
# - "mixnet_contract"
|
||||
# environment:
|
||||
# BECH32_PREFIX: *BECH32_PREFIX
|
||||
mnemonic_echo:
|
||||
build: docker/mnemonic_echo
|
||||
image: mnemonic_echo:latest
|
||||
volumes:
|
||||
- "genesis_volume:/genesis_volume:ro"
|
||||
depends_on:
|
||||
- "genesis_validator"
|
||||
- "secondary_validator"
|
||||
|
||||
mongo:
|
||||
image: mongo:latest
|
||||
command:
|
||||
- --storageEngine=wiredTiger
|
||||
volumes:
|
||||
- mongo_data:/data/db
|
||||
block_explorer:
|
||||
build:
|
||||
context: https://github.com/forbole/big-dipper.git#v0.41.x-7
|
||||
image: block_explorer:v0.41.x-7
|
||||
ports:
|
||||
- "3080:3000"
|
||||
depends_on:
|
||||
- "mongo"
|
||||
environment:
|
||||
ROOT_URL: ${APP_ROOT_URL:-http://localhost}
|
||||
MONGO_URL: mongodb://mongo:27017/meteor
|
||||
PORT: 3000
|
||||
METEOR_SETTINGS: ${METEOR_SETTINGS}
|
||||
explorer:
|
||||
build:
|
||||
context: docker/explorer
|
||||
image: explorer:latest
|
||||
ports:
|
||||
- "3040:3000"
|
||||
depends_on:
|
||||
- "genesis_validator"
|
||||
- "block_explorer"
|
||||
# mongo:
|
||||
# image: mongo:latest
|
||||
# command:
|
||||
# - --storageEngine=wiredTiger
|
||||
# volumes:
|
||||
# - mongo_data:/data/db
|
||||
# block_explorer:
|
||||
# build:
|
||||
# context: https://github.com/forbole/big-dipper.git#v0.41.x-7
|
||||
# image: block_explorer:v0.41.x-7
|
||||
# ports:
|
||||
# - "3080:3000"
|
||||
# depends_on:
|
||||
# - "mongo"
|
||||
# environment:
|
||||
# ROOT_URL: ${APP_ROOT_URL:-http://localhost}
|
||||
# MONGO_URL: mongodb://mongo:27017/meteor
|
||||
# PORT: 3000
|
||||
# METEOR_SETTINGS: ${METEOR_SETTINGS}
|
||||
# explorer:
|
||||
# build:
|
||||
# context: docker/explorer
|
||||
# image: explorer:latest
|
||||
# ports:
|
||||
# - "3040:3000"
|
||||
# depends_on:
|
||||
# - "genesis_validator"
|
||||
# - "block_explorer"
|
||||
|
||||
volumes:
|
||||
genesis_volume:
|
||||
contract_volume:
|
||||
mongo_data:
|
||||
genesis_volume:
|
||||
genesis_nymd:
|
||||
secondary_nymd:
|
||||
|
||||
# contract_volume:
|
||||
# mongo_data:
|
||||
|
||||
|
||||
networks:
|
||||
localnet:
|
||||
driver: bridge
|
||||
ipam:
|
||||
driver: default
|
||||
config:
|
||||
- subnet: 172.168.10.0/25
|
||||
@@ -1,9 +1,16 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Wait for the mnemonic to be generated
|
||||
# Wait for the mnemonic(s) to be generated
|
||||
while ! [ -s /genesis_volume/genesis_mnemonic ]; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "This is the current mnemonic:"
|
||||
while ! [ -s /genesis_volume/secondary_mnemonic ]; do
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "This is the current genesis mnemonic:"
|
||||
cat /genesis_volume/genesis_mnemonic
|
||||
|
||||
echo "This is the current secondary mnemonic:"
|
||||
cat /genesis_volume/secondary_mnemonic
|
||||
|
||||
@@ -6,17 +6,29 @@ PASSPHRASE=passphrase
|
||||
cd /root
|
||||
|
||||
if [ "$1" = "genesis" ]; then
|
||||
if [ ! -d "/root/.nymd" ]; then
|
||||
if [ ! -f "/root/.nymd/config/genesis.json" ]; then
|
||||
./nymd init nymnet --chain-id nymnet 2> /dev/null
|
||||
sed -i 's/minimum-gas-prices = ""/minimum-gas-prices = "0.025u'"${BECH32_PREFIX}"'"/' /root/.nymd/config/app.toml
|
||||
# staking/governance token is hardcoded in config, change this
|
||||
sed -i "s/\"stake\"/\"u${STAKE_DENOM}\"/" /root/.nymd/config/genesis.json
|
||||
sed -i 's/minimum-gas-prices = ""/minimum-gas-prices = "0.025u'"${DENOM}"'"/' /root/.nymd/config/app.toml
|
||||
sed -i '0,/enable = false/s//enable = true/g' /root/.nymd/config/app.toml
|
||||
sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = \["*"\]/' /root/.nymd/config/config.toml
|
||||
sed -i 's/create_empty_blocks = true/create_empty_blocks = false/' /root/.nymd/config/config.toml
|
||||
sed -i 's/laddr = "tcp:\/\/127.0.0.1:26657"/laddr = "tcp:\/\/0.0.0.0:26657"/' /root/.nymd/config/config.toml
|
||||
yes "${PASSPHRASE}" | ./nymd keys add node_admin 2>&1 >/dev/null | tail -n 1 > /genesis_volume/genesis_mnemonic
|
||||
ADDRESS=$(yes "${PASSPHRASE}" | ./nymd keys show node_admin -a)
|
||||
yes "${PASSPHRASE}" | ./nymd add-genesis-account "${ADDRESS}" 1000000000000000u${BECH32_PREFIX},1000000000000000stake
|
||||
yes "${PASSPHRASE}" | ./nymd gentx node_admin 1000000000stake --chain-id nymnet 2> /dev/null
|
||||
|
||||
# create accounts
|
||||
yes "${PASSPHRASE}" | ./nymd keys add node_admin 2>&1 >/dev/null | tail -n 1 > /root/.nymd/mnemonic
|
||||
yes "${PASSPHRASE}" | ./nymd keys add secondary 2>&1 >/dev/null | tail -n 1 > /root/.nymd/secondary_mnemonic
|
||||
cp /root/.nymd/mnemonic /genesis_volume/genesis_mnemonic
|
||||
cp /root/.nymd/secondary_mnemonic /genesis_volume/secondary_mnemonic
|
||||
|
||||
# add genesis accounts with some initial tokens
|
||||
GENESIS_ADDRESS=$(yes "${PASSPHRASE}" | ./nymd keys show node_admin -a)
|
||||
SECONDARY_ADDRESS=$(yes "${PASSPHRASE}" | ./nymd keys show secondary -a)
|
||||
yes "${PASSPHRASE}" | ./nymd add-genesis-account "${GENESIS_ADDRESS}" 1000000000000000u"${DENOM}",1000000000000000u"${STAKE_DENOM}"
|
||||
yes "${PASSPHRASE}" | ./nymd add-genesis-account "${SECONDARY_ADDRESS}" 1000000000000000u"${DENOM}",1000000000000000u"${STAKE_DENOM}"
|
||||
|
||||
yes "${PASSPHRASE}" | ./nymd gentx node_admin 1000000000u"${STAKE_DENOM}" --chain-id nymnet 2> /dev/null
|
||||
./nymd collect-gentxs 2> /dev/null
|
||||
./nymd validate-genesis > /dev/null
|
||||
cp /root/.nymd/config/genesis.json /genesis_volume/genesis.json
|
||||
@@ -26,7 +38,7 @@ if [ "$1" = "genesis" ]; then
|
||||
fi
|
||||
./nymd start
|
||||
elif [ "$1" = "secondary" ]; then
|
||||
if [ ! -d "/root/.nymd" ]; then
|
||||
if [ ! -f "/root/.nymd/config/genesis.json" ]; then
|
||||
./nymd init nymnet --chain-id nym-secondary 2> /dev/null
|
||||
|
||||
# Wait until the genesis node writes the genesis.json to the shared volume
|
||||
@@ -34,16 +46,27 @@ elif [ "$1" = "secondary" ]; then
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# wait for the actual validator to start up
|
||||
sleep 5
|
||||
|
||||
cp /genesis_volume/genesis.json /root/.nymd/config/genesis.json
|
||||
GENESIS_PEER=$(cat /root/.nymd/config/genesis.json | grep '"memo"' | cut -d'"' -f 4)
|
||||
GENESIS_IP=$(cat /root/.nymd/config/genesis.json | grep '"memo"' | cut -d'@' -f2 | cut -d: -f1)
|
||||
sed -i 's/persistent_peers = ""/persistent_peers = "'"${GENESIS_PEER}"'"/' /root/.nymd/config/config.toml
|
||||
sed -i 's/minimum-gas-prices = ""/minimum-gas-prices = "0.025u'"${BECH32_PREFIX}"'"/' /root/.nymd/config/app.toml
|
||||
sed -i '0,/enable = false/s//enable = true/g' /root/.nymd/config/app.toml
|
||||
sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = \["*"\]/' /root/.nymd/config/config.toml
|
||||
sed -i 's/create_empty_blocks = true/create_empty_blocks = false/' /root/.nymd/config/config.toml
|
||||
sed -i 's/laddr = "tcp:\/\/127.0.0.1:26657"/laddr = "tcp:\/\/0.0.0.0:26657"/' /root/.nymd/config/config.toml
|
||||
yes "${PASSPHRASE}" | ./nymd keys add node_admin 2> mnemonic > /dev/null
|
||||
|
||||
# import mnemonic generated by the genesis validator (have a local copy for ease of use)
|
||||
cp /genesis_volume/secondary_mnemonic /root/.nymd/mnemonic
|
||||
{ cat /root/.nymd/mnemonic; echo "${PASSPHRASE}"; echo "${PASSPHRASE}"; } | ./nymd keys add node_admin --recover #> /dev/null
|
||||
./nymd validate-genesis > /dev/null
|
||||
|
||||
# create validator
|
||||
# don't even ask about those sleeps...
|
||||
{ echo "${PASSPHRASE}"; sleep 10; yes; sleep 10; } | ./nymd tx staking create-validator --amount=10000000u"${STAKE_DENOM}" --fees 100000u"${DENOM}" --pubkey="$(./nymd tendermint show-validator)" --moniker="secondary" --commission-rate="0.10" --commission-max-rate="0.20" --commission-max-change-rate="0.01" --min-self-delegation="1" --chain-id=nymnet --from=node_admin -b async --node http://"${GENESIS_IP}":26657
|
||||
else
|
||||
echo "Validator already initialized, starting with the existing configuration."
|
||||
echo "If you want to re-init the validator, destroy the existing container"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "explorer-api"
|
||||
version = "1.0.0-rc.2"
|
||||
version = "1.0.1"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -28,6 +28,7 @@ pub(crate) struct PrettyDetailedMixNodeBond {
|
||||
pub owner: Addr,
|
||||
pub layer: Layer,
|
||||
pub mix_node: MixNode,
|
||||
pub avg_uptime: Option<u8>,
|
||||
}
|
||||
|
||||
pub(crate) struct MixNodeCache {
|
||||
|
||||
@@ -9,7 +9,9 @@ use serde::Serialize;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use mixnet_contract_common::MixNodeBond;
|
||||
use validator_client::models::UptimeResponse;
|
||||
|
||||
use crate::cache::Cache;
|
||||
use crate::mix_node::models::{MixnodeStatus, PrettyDetailedMixNodeBond};
|
||||
use crate::mix_nodes::location::{Location, LocationCache, LocationCacheItem};
|
||||
use crate::mix_nodes::CACHE_ENTRY_TTL;
|
||||
@@ -76,10 +78,16 @@ impl MixNodesResult {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct MixNodeHealth {
|
||||
avg_uptime: u8,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ThreadsafeMixNodesCache {
|
||||
mixnodes: Arc<RwLock<MixNodesResult>>,
|
||||
locations: Arc<RwLock<LocationCache>>,
|
||||
mixnode_health: Arc<RwLock<Cache<MixNodeHealth>>>,
|
||||
}
|
||||
|
||||
impl ThreadsafeMixNodesCache {
|
||||
@@ -87,6 +95,7 @@ impl ThreadsafeMixNodesCache {
|
||||
ThreadsafeMixNodesCache {
|
||||
mixnodes: Arc::new(RwLock::new(MixNodesResult::new())),
|
||||
locations: Arc::new(RwLock::new(LocationCache::new())),
|
||||
mixnode_health: Arc::new(RwLock::new(Cache::new())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +103,7 @@ impl ThreadsafeMixNodesCache {
|
||||
ThreadsafeMixNodesCache {
|
||||
mixnodes: Arc::new(RwLock::new(MixNodesResult::new())),
|
||||
locations: Arc::new(RwLock::new(locations)),
|
||||
mixnode_health: Arc::new(RwLock::new(Cache::new())),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,9 +142,11 @@ impl ThreadsafeMixNodesCache {
|
||||
) -> Option<PrettyDetailedMixNodeBond> {
|
||||
let mixnodes_guard = self.mixnodes.read().await;
|
||||
let location_guard = self.locations.read().await;
|
||||
let mixnode_health_guard = self.mixnode_health.read().await;
|
||||
|
||||
let bond = mixnodes_guard.get_mixnode(identity_key);
|
||||
let location = location_guard.get(identity_key);
|
||||
let health = mixnode_health_guard.get(identity_key);
|
||||
|
||||
match bond {
|
||||
Some(bond) => Some(PrettyDetailedMixNodeBond {
|
||||
@@ -145,6 +157,7 @@ impl ThreadsafeMixNodesCache {
|
||||
owner: bond.owner,
|
||||
layer: bond.layer,
|
||||
mix_node: bond.mix_node,
|
||||
avg_uptime: health.map(|m| m.avg_uptime),
|
||||
}),
|
||||
None => None,
|
||||
}
|
||||
@@ -153,6 +166,7 @@ impl ThreadsafeMixNodesCache {
|
||||
pub(crate) async fn get_detailed_mixnodes(&self) -> Vec<PrettyDetailedMixNodeBond> {
|
||||
let mixnodes_guard = self.mixnodes.read().await;
|
||||
let location_guard = self.locations.read().await;
|
||||
let mixnode_health_guard = self.mixnode_health.read().await;
|
||||
|
||||
mixnodes_guard
|
||||
.all_mixnodes
|
||||
@@ -160,6 +174,7 @@ impl ThreadsafeMixNodesCache {
|
||||
.map(|bond| {
|
||||
let location = location_guard.get(&bond.mix_node.identity_key);
|
||||
let copy = bond.clone();
|
||||
let health = mixnode_health_guard.get(&bond.mix_node.identity_key);
|
||||
PrettyDetailedMixNodeBond {
|
||||
location: location.and_then(|l| l.location.clone()),
|
||||
status: mixnodes_guard.determine_node_status(&bond.mix_node.identity_key),
|
||||
@@ -168,6 +183,7 @@ impl ThreadsafeMixNodesCache {
|
||||
owner: copy.owner,
|
||||
layer: copy.layer,
|
||||
mix_node: copy.mix_node,
|
||||
avg_uptime: health.map(|m| m.avg_uptime),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
@@ -188,4 +204,14 @@ impl ThreadsafeMixNodesCache {
|
||||
guard.active_mixnodes = active_nodes;
|
||||
guard.valid_until = SystemTime::now() + CACHE_ENTRY_TTL;
|
||||
}
|
||||
|
||||
pub(crate) async fn update_health_cache(&self, all_uptimes: Vec<UptimeResponse>) {
|
||||
let mut mixnode_health = self.mixnode_health.write().await;
|
||||
for uptime in all_uptimes {
|
||||
let health = MixNodeHealth {
|
||||
avg_uptime: uptime.avg_uptime,
|
||||
};
|
||||
mixnode_health.set(&uptime.identity, health);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use std::future::Future;
|
||||
|
||||
use mixnet_contract_common::{GatewayBond, MixNodeBond};
|
||||
use validator_client::models::UptimeResponse;
|
||||
use validator_client::nymd::error::NymdError;
|
||||
use validator_client::nymd::{Paging, QueryNymdClient, ValidatorResponse};
|
||||
use validator_client::ValidatorClientError;
|
||||
@@ -88,6 +89,17 @@ impl ExplorerApiTasks {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn retrieve_all_mixnode_avg_uptimes(
|
||||
&self,
|
||||
) -> Result<Vec<UptimeResponse>, ValidatorClientError> {
|
||||
self.state
|
||||
.inner
|
||||
.validator_client
|
||||
.0
|
||||
.get_mixnode_avg_uptimes()
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_mixnode_cache(&self) {
|
||||
let all_bonds = self.retrieve_all_mixnodes().await;
|
||||
let rewarded_nodes = self
|
||||
@@ -109,6 +121,21 @@ impl ExplorerApiTasks {
|
||||
.await;
|
||||
}
|
||||
|
||||
async fn update_mixnode_health_cache(&self) {
|
||||
match self.retrieve_all_mixnode_avg_uptimes().await {
|
||||
Ok(response) => {
|
||||
self.state
|
||||
.inner
|
||||
.mixnodes
|
||||
.update_health_cache(response)
|
||||
.await
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to get mixnode avg uptimes: {:?}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn update_validators_cache(&self) {
|
||||
match self.retrieve_all_validators().await {
|
||||
Ok(response) => self.state.inner.validators.update_cache(response).await,
|
||||
@@ -145,6 +172,9 @@ impl ExplorerApiTasks {
|
||||
|
||||
info!("Updating mix node cache...");
|
||||
self.update_mixnode_cache().await;
|
||||
|
||||
info!("Updating mix node health cache...");
|
||||
self.update_mixnode_health_cache().await;
|
||||
info!("Done");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
'@nymproject/eslint-config-react-typescript'
|
||||
],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.ts'],
|
||||
parserOptions: {
|
||||
project: 'tsconfig.json',
|
||||
tsconfigRootDir: __dirname,
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"extends": [
|
||||
"@nymproject/eslint-config-react-typescript"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/* eslint-disable no-param-reassign */
|
||||
const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
|
||||
const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||
addons: ['@storybook/addon-links', '@storybook/addon-essentials', '@storybook/addon-interactions'],
|
||||
framework: '@storybook/react',
|
||||
core: {
|
||||
builder: 'webpack5',
|
||||
},
|
||||
// webpackFinal: async (config, { configType }) => {
|
||||
// // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
|
||||
// // You can change the configuration based on that.
|
||||
// // 'PRODUCTION' is used when building the static version of storybook.
|
||||
webpackFinal: async (config) => {
|
||||
config.module.rules.forEach((rule) => {
|
||||
// look for SVG import rule and replace
|
||||
// NOTE: the rule before modification is /\.(svg|ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/
|
||||
if (rule.test?.toString().includes('svg')) {
|
||||
rule.test = /\.(ico|jpg|jpeg|png|apng|gif|eot|otf|webp|ttf|woff|woff2|cur|ani|pdf)(\?.*)?$/;
|
||||
}
|
||||
});
|
||||
|
||||
// handle asset loading with this
|
||||
config.module.rules.unshift({
|
||||
test: /\.svg(\?.*)?$/i,
|
||||
issuer: /\.[jt]sx?$/,
|
||||
use: ['@svgr/webpack'],
|
||||
});
|
||||
|
||||
config.resolve.extensions = ['.tsx', '.ts', '.js'];
|
||||
config.resolve.plugins = [new TsconfigPathsPlugin()];
|
||||
|
||||
config.resolve.fallback = {
|
||||
fs: false,
|
||||
tls: false,
|
||||
path: false,
|
||||
http: false,
|
||||
https: false,
|
||||
stream: false,
|
||||
crypto: false,
|
||||
net: false,
|
||||
zlib: false,
|
||||
};
|
||||
|
||||
config.plugins.push(new ForkTsCheckerWebpackPlugin({
|
||||
typescript: {
|
||||
mode: 'write-references',
|
||||
diagnosticOptions: {
|
||||
semantic: true,
|
||||
syntactic: true,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
// Return the altered config
|
||||
return config;
|
||||
},
|
||||
features: {
|
||||
emotionAlias: false,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
/* eslint-disable react/react-in-jsx-scope */
|
||||
import { NymNetworkExplorerThemeProvider } from '@nymproject/mui-theme';
|
||||
import { Box } from '@mui/material';
|
||||
|
||||
export const parameters = {
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const withThemeProvider = (Story, context) => (
|
||||
<div style={{ display: 'grid', height: '100%', gridTemplateColumns: '50% 50%' }}>
|
||||
<div>
|
||||
<NymNetworkExplorerThemeProvider mode="light">
|
||||
<Box
|
||||
p={4}
|
||||
sx={{
|
||||
display: 'grid',
|
||||
gridTemplateRows: '80vh 2rem',
|
||||
background: (theme) => theme.palette.background.default,
|
||||
color: (theme) => theme.palette.text.primary,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ overflowY: 'auto' }}>
|
||||
<Story {...context} />
|
||||
</Box>
|
||||
<h4 style={{ textAlign: 'center' }}>Light mode</h4>
|
||||
</Box>
|
||||
</NymNetworkExplorerThemeProvider>
|
||||
</div>
|
||||
<div>
|
||||
<NymNetworkExplorerThemeProvider mode="dark">
|
||||
<Box
|
||||
p={4}
|
||||
sx={{
|
||||
display: 'grid',
|
||||
gridTemplateRows: '80vh 2rem',
|
||||
background: (theme) => theme.palette.background.default,
|
||||
color: (theme) => theme.palette.text.primary,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ overflowY: 'auto' }}>
|
||||
<Story {...context} />
|
||||
</Box>
|
||||
<h4 style={{ textAlign: 'center' }}>Dark mode</h4>
|
||||
</Box>
|
||||
</NymNetworkExplorerThemeProvider>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const decorators = [withThemeProvider];
|
||||
+13
-3
@@ -30,8 +30,15 @@
|
||||
"use-clipboard-copy": "^0.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.15.0",
|
||||
"@nymproject/eslint-config-react-typescript": "^1.0.0",
|
||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.4",
|
||||
"@storybook/addon-actions": "^6.4.19",
|
||||
"@storybook/addon-essentials": "^6.4.19",
|
||||
"@storybook/addon-interactions": "^6.4.19",
|
||||
"@storybook/addon-links": "^6.4.19",
|
||||
"@storybook/react": "^6.4.19",
|
||||
"@storybook/testing-library": "^0.0.9",
|
||||
"@svgr/webpack": "^6.1.1",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^12.0.0",
|
||||
@@ -95,10 +102,13 @@
|
||||
"build:serve": "npx serve dist",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"tsc": "tsc",
|
||||
"tsc:watch": "tsc --watch",
|
||||
"tsc": "tsc --noEmit true",
|
||||
"tsc:watch": "tsc --watch --noEmit true",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint src --fix"
|
||||
"lint:fix": "eslint src --fix",
|
||||
"prestorybook": "yarn --cwd .. build",
|
||||
"storybook": "start-storybook -p 6006",
|
||||
"storybook:build": "build-storybook"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
MixNodeResponse,
|
||||
MixNodeResponseItem,
|
||||
MixnodeStatus,
|
||||
MixNodeEconomicDynamicsStatsResponse,
|
||||
StatsResponse,
|
||||
StatusResponse,
|
||||
SummaryOverviewResponse,
|
||||
@@ -122,6 +123,9 @@ export class Api {
|
||||
static fetchMixnodeDescriptionById = async (id: string): Promise<MixNodeDescriptionResponse> =>
|
||||
(await fetch(`${MIXNODE_API}/${id}/description`)).json();
|
||||
|
||||
static fetchMixnodeEconomicDynamicsStatsById = async (id: string): Promise<MixNodeEconomicDynamicsStatsResponse> =>
|
||||
(await fetch(`${MIXNODE_API}/${id}/economic-dynamics-stats`)).json();
|
||||
|
||||
static fetchStatusById = async (id: string): Promise<StatusResponse> => (await fetch(`${MIXNODE_PING}/${id}`)).json();
|
||||
|
||||
static fetchUptimeStoryById = async (id: string): Promise<UptimeStoryResponse> =>
|
||||
|
||||
@@ -12,12 +12,13 @@ export type ColumnsType = {
|
||||
headerAlign: string;
|
||||
flex?: number;
|
||||
width?: number;
|
||||
tooltipInfo?: string;
|
||||
};
|
||||
|
||||
export interface UniversalTableProps {
|
||||
export interface UniversalTableProps<T = any> {
|
||||
tableName: string;
|
||||
columnsData: ColumnsType[];
|
||||
rows: any[];
|
||||
rows: T[];
|
||||
}
|
||||
|
||||
function formatCellValues(val: string | number, field: string) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { Alert, Box, CircularProgress, useMediaQuery } from '@mui/material';
|
||||
import { Alert, Box, CircularProgress, useMediaQuery, Typography } from '@mui/material';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import Table from '@mui/material/Table';
|
||||
import TableBody from '@mui/material/TableBody';
|
||||
@@ -98,7 +98,7 @@ export const BondBreakdownTable: React.FC = () => {
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
<TableRow>
|
||||
<TableCell align="left">Pledge total</TableCell>
|
||||
<TableCell align="left">Self</TableCell>
|
||||
<TableCell align="left" data-testid="pledge-total-amount">
|
||||
{bonds.pledges}
|
||||
</TableCell>
|
||||
@@ -127,21 +127,62 @@ export const BondBreakdownTable: React.FC = () => {
|
||||
sx={{
|
||||
maxHeight: 400,
|
||||
overflowY: 'scroll',
|
||||
p: 2,
|
||||
background: theme.palette.background.paper,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'baseline',
|
||||
width: '100%',
|
||||
p: 2,
|
||||
borderBottom: `1px solid ${theme.palette.divider}`,
|
||||
}}
|
||||
data-testid="delegations-total-amount"
|
||||
>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: 16,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
Delegations
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
fontSize: 12,
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
{`(${delegations?.data?.length} delegators)`}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Table stickyHeader>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell sx={{ fontWeight: 600, background: '#242C3D' }} align="left">
|
||||
<TableCell
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
background: theme.palette.background.paper,
|
||||
}}
|
||||
align="left"
|
||||
>
|
||||
Delegators
|
||||
</TableCell>
|
||||
<TableCell sx={{ fontWeight: 600, background: '#242C3D' }} align="left">
|
||||
<TableCell
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
background: theme.palette.background.paper,
|
||||
}}
|
||||
align="left"
|
||||
>
|
||||
Stake
|
||||
</TableCell>
|
||||
<TableCell
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
background: '#242C3D',
|
||||
background: theme.palette.background.paper,
|
||||
width: '200px',
|
||||
}}
|
||||
align="left"
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { ColumnsType } from '../../DetailTable';
|
||||
|
||||
export const EconomicsInfoColumns: ColumnsType[] = [
|
||||
{
|
||||
field: 'estimatedTotalReward',
|
||||
title: 'Estimated Total Reward',
|
||||
flex: 1,
|
||||
headerAlign: 'left',
|
||||
tooltipInfo: 'Estimated reward per epoch for this profit margin if your node is selected in the active set.',
|
||||
},
|
||||
{
|
||||
field: 'estimatedOperatorReward',
|
||||
title: 'Estimated Operator Reward',
|
||||
flex: 1,
|
||||
headerAlign: 'left',
|
||||
tooltipInfo: 'Estimated reward per epoch for this profit margin if your node is selected in the active set.',
|
||||
},
|
||||
{
|
||||
field: 'selectionChance',
|
||||
title: 'Active Set Probability',
|
||||
flex: 1,
|
||||
headerAlign: 'left',
|
||||
tooltipInfo:
|
||||
'Probability of getting selected in the reward set (active and standby nodes) in the next epoch. The more your stake, the higher the chances to be selected.',
|
||||
},
|
||||
{
|
||||
field: 'stakeSaturation',
|
||||
title: 'Stake Saturation',
|
||||
flex: 1,
|
||||
headerAlign: 'left',
|
||||
tooltipInfo:
|
||||
'Level of stake saturation for this node. Nodes receive more rewards the higher their saturation level, up to 100%. Beyond 100% no additional rewards are granted. The current stake saturation level is: 1 million NYM, computed as S/K where S is total amount of tokens available to stakeholders and K is the number of nodes in the reward set.',
|
||||
},
|
||||
{
|
||||
field: 'profitMargin',
|
||||
title: 'Profit Margin',
|
||||
flex: 1,
|
||||
headerAlign: 'left',
|
||||
tooltipInfo:
|
||||
'Percentage of the delegates rewards that the operator takes as fee before rewards are distributed to the delegates.',
|
||||
},
|
||||
{
|
||||
field: 'avgUptime',
|
||||
title: 'Avg. Uptime',
|
||||
flex: 1,
|
||||
headerAlign: 'left',
|
||||
tooltipInfo: 'Node’s average uptime in the last 24h.',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,31 @@
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import { EconomicsProgress } from './EconomicsProgress';
|
||||
|
||||
export default {
|
||||
title: 'Mix Node Detail/Economics/ProgressBar',
|
||||
component: EconomicsProgress,
|
||||
} as ComponentMeta<typeof EconomicsProgress>;
|
||||
|
||||
const Template: ComponentStory<typeof EconomicsProgress> = (args) => <EconomicsProgress {...args} />;
|
||||
|
||||
export const Empty = Template.bind({});
|
||||
Empty.args = {};
|
||||
|
||||
export const OverThreshold = Template.bind({});
|
||||
OverThreshold.args = {
|
||||
threshold: 100,
|
||||
value: 120,
|
||||
};
|
||||
|
||||
export const UnderThreshold = Template.bind({});
|
||||
UnderThreshold.args = {
|
||||
threshold: 100,
|
||||
value: 80,
|
||||
};
|
||||
|
||||
export const OnThreshold = Template.bind({});
|
||||
OnThreshold.args = {
|
||||
threshold: 100,
|
||||
value: 100,
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
import * as React from 'react';
|
||||
import LinearProgress, { LinearProgressProps } from '@mui/material/LinearProgress';
|
||||
import { useTheme } from '@mui/material/styles';
|
||||
import { Box } from '@mui/system';
|
||||
|
||||
const parseToNumber = (value: number | undefined | string) =>
|
||||
typeof value === 'string' ? parseInt(value || '', 10) : value || 0;
|
||||
|
||||
export const EconomicsProgress: React.FC<
|
||||
LinearProgressProps & {
|
||||
threshold?: number;
|
||||
}
|
||||
> = ({ threshold, ...props }) => {
|
||||
const theme = useTheme();
|
||||
const { value } = props;
|
||||
|
||||
const valueNumber: number = parseToNumber(value);
|
||||
const thresholdNumber: number = parseToNumber(threshold);
|
||||
const percentageColor = valueNumber > (threshold || 100) ? 'warning' : 'inherit';
|
||||
const percentageToDisplay = Math.min(valueNumber, thresholdNumber);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: 6 / 10,
|
||||
color: valueNumber > (threshold || 100) ? theme.palette.warning.main : theme.palette.nym.wallet.fee,
|
||||
}}
|
||||
>
|
||||
<LinearProgress
|
||||
{...props}
|
||||
variant="determinate"
|
||||
color={percentageColor}
|
||||
value={percentageToDisplay}
|
||||
sx={{ width: '100%', borderRadius: '5px', backgroundColor: theme.palette.common.white }}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,129 @@
|
||||
import * as React from 'react';
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import { DelegatorsInfoTable } from './Table';
|
||||
import { EconomicsInfoColumns } from './Columns';
|
||||
import { EconomicsInfoRowWithIndex } from './types';
|
||||
|
||||
export default {
|
||||
title: 'Mix Node Detail/Economics',
|
||||
component: DelegatorsInfoTable,
|
||||
} as ComponentMeta<typeof DelegatorsInfoTable>;
|
||||
|
||||
const row: EconomicsInfoRowWithIndex = {
|
||||
id: 1,
|
||||
selectionChance: {
|
||||
value: 'High',
|
||||
},
|
||||
avgUptime: {
|
||||
value: '65 %',
|
||||
},
|
||||
estimatedOperatorReward: {
|
||||
value: '80000.123456 NYM',
|
||||
},
|
||||
estimatedTotalReward: {
|
||||
value: '80000.123456 NYM',
|
||||
},
|
||||
profitMargin: {
|
||||
value: '10 %',
|
||||
},
|
||||
stakeSaturation: {
|
||||
value: '80 %',
|
||||
progressBarValue: 80,
|
||||
},
|
||||
};
|
||||
|
||||
const rowVeryHighProbabilitySelection: EconomicsInfoRowWithIndex = {
|
||||
...row,
|
||||
selectionChance: {
|
||||
value: 'Very High',
|
||||
},
|
||||
};
|
||||
|
||||
const rowModerateProbabilitySelection: EconomicsInfoRowWithIndex = {
|
||||
...row,
|
||||
selectionChance: {
|
||||
value: 'Moderate',
|
||||
},
|
||||
};
|
||||
|
||||
const rowLowProbabilitySelection: EconomicsInfoRowWithIndex = {
|
||||
...row,
|
||||
selectionChance: {
|
||||
value: 'Low',
|
||||
},
|
||||
};
|
||||
|
||||
const rowVeryLowProbabilitySelection: EconomicsInfoRowWithIndex = {
|
||||
...row,
|
||||
selectionChance: {
|
||||
value: 'Very Low',
|
||||
},
|
||||
};
|
||||
|
||||
const emptyRow: EconomicsInfoRowWithIndex = {
|
||||
id: 1,
|
||||
selectionChance: {
|
||||
value: '-',
|
||||
progressBarValue: 0,
|
||||
},
|
||||
avgUptime: {
|
||||
value: '-',
|
||||
},
|
||||
estimatedOperatorReward: {
|
||||
value: '-',
|
||||
},
|
||||
estimatedTotalReward: {
|
||||
value: '-',
|
||||
},
|
||||
profitMargin: {
|
||||
value: '-',
|
||||
},
|
||||
stakeSaturation: {
|
||||
value: '-',
|
||||
progressBarValue: 0,
|
||||
},
|
||||
};
|
||||
|
||||
const Template: ComponentStory<typeof DelegatorsInfoTable> = (args) => <DelegatorsInfoTable {...args} />;
|
||||
|
||||
export const Empty = Template.bind({});
|
||||
Empty.args = {
|
||||
rows: [emptyRow],
|
||||
columnsData: EconomicsInfoColumns,
|
||||
tableName: 'storybook',
|
||||
};
|
||||
|
||||
export const selectionChanceVeryHigh = Template.bind({});
|
||||
selectionChanceVeryHigh.args = {
|
||||
rows: [rowVeryHighProbabilitySelection],
|
||||
columnsData: EconomicsInfoColumns,
|
||||
tableName: 'storybook',
|
||||
};
|
||||
|
||||
export const selectionChanceHigh = Template.bind({});
|
||||
selectionChanceHigh.args = {
|
||||
rows: [row],
|
||||
columnsData: EconomicsInfoColumns,
|
||||
tableName: 'storybook',
|
||||
};
|
||||
|
||||
export const selectionChanceModerate = Template.bind({});
|
||||
selectionChanceModerate.args = {
|
||||
rows: [rowModerateProbabilitySelection],
|
||||
columnsData: EconomicsInfoColumns,
|
||||
tableName: 'storybook',
|
||||
};
|
||||
|
||||
export const selectionChanceLow = Template.bind({});
|
||||
selectionChanceLow.args = {
|
||||
rows: [rowLowProbabilitySelection],
|
||||
columnsData: EconomicsInfoColumns,
|
||||
tableName: 'storybook',
|
||||
};
|
||||
|
||||
export const selectionChanceVeryLow = Template.bind({});
|
||||
selectionChanceVeryLow.args = {
|
||||
rows: [rowVeryLowProbabilitySelection],
|
||||
columnsData: EconomicsInfoColumns,
|
||||
tableName: 'storybook',
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
import { currencyToString } from '../../../utils/currency';
|
||||
import { useMixnodeContext } from '../../../context/mixnode';
|
||||
import { ApiState, MixNodeEconomicDynamicsStatsResponse } from '../../../typeDefs/explorer-api';
|
||||
import { EconomicsInfoRowWithIndex } from './types';
|
||||
|
||||
const selectionChance = (economicDynamicsStats: ApiState<MixNodeEconomicDynamicsStatsResponse> | undefined) => {
|
||||
const inclusionProbability = economicDynamicsStats?.data?.active_set_inclusion_probability;
|
||||
switch (inclusionProbability) {
|
||||
case 'High':
|
||||
case 'Moderate':
|
||||
case 'Low':
|
||||
return inclusionProbability;
|
||||
case 'VeryHigh':
|
||||
return 'Very High';
|
||||
case 'VeryLow':
|
||||
return 'Very Low';
|
||||
default:
|
||||
return '-';
|
||||
}
|
||||
};
|
||||
|
||||
export const EconomicsInfoRows = (): EconomicsInfoRowWithIndex => {
|
||||
const { economicDynamicsStats, mixNode } = useMixnodeContext();
|
||||
|
||||
const estimatedNodeRewards =
|
||||
currencyToString((economicDynamicsStats?.data?.estimated_total_node_reward || '').toString()) || '-';
|
||||
const estimatedOperatorRewards =
|
||||
currencyToString((economicDynamicsStats?.data?.estimated_operator_reward || '').toString()) || '-';
|
||||
const stakeSaturation = economicDynamicsStats?.data?.stake_saturation || '-';
|
||||
const profitMargin = mixNode?.data?.mix_node.profit_margin_percent || '-';
|
||||
const avgUptime = economicDynamicsStats?.data?.current_interval_uptime;
|
||||
|
||||
return {
|
||||
id: 1,
|
||||
estimatedTotalReward: {
|
||||
value: estimatedNodeRewards,
|
||||
},
|
||||
estimatedOperatorReward: {
|
||||
value: estimatedOperatorRewards,
|
||||
},
|
||||
selectionChance: {
|
||||
value: selectionChance(economicDynamicsStats),
|
||||
},
|
||||
stakeSaturation: {
|
||||
progressBarValue: typeof stakeSaturation === 'number' ? stakeSaturation * 100 : 0,
|
||||
value: typeof stakeSaturation === 'number' ? `${(stakeSaturation * 100).toFixed(2)} %` : '-',
|
||||
},
|
||||
profitMargin: {
|
||||
value: profitMargin ? `${profitMargin} %` : '-',
|
||||
},
|
||||
avgUptime: {
|
||||
value: avgUptime ? `${avgUptime} %` : '-',
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,175 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
IconButton,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
} from '@mui/material';
|
||||
import { Box } from '@mui/system';
|
||||
import { styled, useTheme, Theme } from '@mui/material/styles';
|
||||
import Tooltip, { tooltipClasses, TooltipProps } from '@mui/material/Tooltip';
|
||||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||
import { EconomicsRowsType, EconomicsInfoRowWithIndex } from './types';
|
||||
import { EconomicsProgress } from './EconomicsProgress';
|
||||
import { cellStyles } from '../../Universal-DataGrid';
|
||||
import { UniversalTableProps } from '../../DetailTable';
|
||||
|
||||
const tooltipBackGroundColor = '#A0AED1';
|
||||
const threshold = 100;
|
||||
|
||||
const textColour = (value: EconomicsRowsType, field: string, theme: Theme) => {
|
||||
const progressBarValue = value?.progressBarValue || 0;
|
||||
const fieldValue = value.value;
|
||||
|
||||
if (progressBarValue > 100) {
|
||||
return theme.palette.warning.main;
|
||||
}
|
||||
if (field === 'selectionChance') {
|
||||
switch (fieldValue) {
|
||||
case 'High':
|
||||
case 'Very High':
|
||||
return theme.palette.nym.networkExplorer.selectionChance.overModerate;
|
||||
case 'Moderate':
|
||||
return theme.palette.nym.networkExplorer.selectionChance.moderate;
|
||||
case 'Low':
|
||||
case 'Very Low':
|
||||
return theme.palette.nym.networkExplorer.selectionChance.underModerate;
|
||||
default:
|
||||
return theme.palette.nym.wallet.fee;
|
||||
}
|
||||
}
|
||||
return theme.palette.nym.wallet.fee;
|
||||
};
|
||||
|
||||
const formatCellValues = (value: EconomicsRowsType, field: string, theme: Theme) => {
|
||||
const isTablet = useMediaQuery(theme.breakpoints.down('lg'));
|
||||
if (value.progressBarValue) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', flexDirection: isTablet ? 'column' : 'row' }} id="field">
|
||||
<Typography
|
||||
sx={{
|
||||
mr: isTablet ? 0 : 1,
|
||||
mb: isTablet ? 1 : 0,
|
||||
fontWeight: '600',
|
||||
fontSize: '12px',
|
||||
}}
|
||||
id={field}
|
||||
>
|
||||
{value.value}
|
||||
</Typography>
|
||||
<EconomicsProgress threshold={threshold} value={value.progressBarValue} />
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }} id="field">
|
||||
<Typography sx={{ mr: 1, fontWeight: '600', fontSize: '12px' }} id={field}>
|
||||
{value.value}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export const DelegatorsInfoTable: React.FC<UniversalTableProps<EconomicsInfoRowWithIndex>> = ({
|
||||
tableName,
|
||||
columnsData,
|
||||
rows,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const CustomTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||
<Tooltip {...props} classes={{ popper: className }} />
|
||||
))({
|
||||
[`& .${tooltipClasses.tooltip}`]: {
|
||||
maxWidth: 230,
|
||||
background: tooltipBackGroundColor,
|
||||
color: theme.palette.nym.networkExplorer.nav.hover,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 650 }} aria-label={tableName}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
{columnsData?.map(({ field, title, flex, tooltipInfo }) => (
|
||||
<TableCell key={field} sx={{ fontSize: 14, fontWeight: 600, flex }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
{tooltipInfo && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||
<CustomTooltip
|
||||
title={tooltipInfo}
|
||||
id={field}
|
||||
placement="top-start"
|
||||
sx={{
|
||||
'& .MuiTooltip-arrow': {
|
||||
color: '#A0AED1',
|
||||
},
|
||||
}}
|
||||
arrow
|
||||
>
|
||||
<IconButton
|
||||
sx={{
|
||||
padding: 0,
|
||||
py: 1,
|
||||
pr: 1,
|
||||
}}
|
||||
disableFocusRipple
|
||||
disableRipple
|
||||
>
|
||||
<InfoOutlinedIcon
|
||||
sx={{
|
||||
height: '18px',
|
||||
width: '18px',
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</CustomTooltip>
|
||||
</Box>
|
||||
)}
|
||||
{title}
|
||||
</Box>
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{rows?.map((eachRow) => (
|
||||
<TableRow key={eachRow.id} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
||||
{columnsData?.map((_, index: number) => {
|
||||
const { field } = columnsData[index];
|
||||
const value: EconomicsRowsType = (eachRow as any)[field];
|
||||
|
||||
return (
|
||||
<TableCell
|
||||
key={_.title}
|
||||
component="th"
|
||||
scope="row"
|
||||
variant="body"
|
||||
sx={{
|
||||
...cellStyles,
|
||||
padding: 2,
|
||||
width: 200,
|
||||
fontSize: 12,
|
||||
fontWeight: 600,
|
||||
color: textColour(value, field, theme),
|
||||
}}
|
||||
data-testid={`${_.title.replace(/ /g, '-')}-value`}
|
||||
>
|
||||
{formatCellValues(value, columnsData[index].field, theme)}
|
||||
</TableCell>
|
||||
);
|
||||
})}
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
export { DelegatorsInfoTable } from './Table';
|
||||
export { EconomicsInfoColumns } from './Columns';
|
||||
export { EconomicsInfoRows } from './Rows';
|
||||
@@ -0,0 +1,15 @@
|
||||
export type EconomicsRowsType = {
|
||||
progressBarValue?: number;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export interface EconomicsInfoRow {
|
||||
estimatedTotalReward: EconomicsRowsType;
|
||||
estimatedOperatorReward: EconomicsRowsType;
|
||||
selectionChance: EconomicsRowsType;
|
||||
stakeSaturation: EconomicsRowsType;
|
||||
profitMargin: EconomicsRowsType;
|
||||
avgUptime: EconomicsRowsType;
|
||||
}
|
||||
|
||||
export type EconomicsInfoRowWithIndex = EconomicsInfoRow & { id: number };
|
||||
@@ -11,6 +11,8 @@ export type MixnodeRowType = {
|
||||
self_percentage: string;
|
||||
host: string;
|
||||
layer: string;
|
||||
profit_percentage: string;
|
||||
avg_uptime: number;
|
||||
};
|
||||
|
||||
export function mixnodeToGridRow(arrayOfMixnodes?: MixNodeResponse): MixnodeRowType[] {
|
||||
@@ -22,6 +24,7 @@ export function mixNodeResponseItemToMixnodeRowType(item: MixNodeResponseItem):
|
||||
const delegations = Number(item.total_delegation.amount) || 0;
|
||||
const totalBond = pledge + delegations;
|
||||
const selfPercentage = ((pledge * 100) / totalBond).toFixed(2);
|
||||
const profitPercentage = item.mix_node.profit_margin_percent || 0;
|
||||
return {
|
||||
id: item.owner,
|
||||
status: item.status,
|
||||
@@ -32,5 +35,7 @@ export function mixNodeResponseItemToMixnodeRowType(item: MixNodeResponseItem):
|
||||
self_percentage: selfPercentage,
|
||||
host: item?.mix_node?.host || '',
|
||||
layer: item?.layer || '',
|
||||
profit_percentage: `${profitPercentage}%`,
|
||||
avg_uptime: item.avg_uptime,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -22,9 +22,12 @@ export const Socials: React.FC<{ isFooter?: boolean }> = ({ isFooter }) => {
|
||||
<IconButton component="a" href={TELEGRAM_LINK} target="_blank" data-testid="telegram">
|
||||
<TelegramIcon color={color} size={24} />
|
||||
</IconButton>
|
||||
<IconButton component="a" href={DISCORD_LINK} target="_blank" data-testid="discord">
|
||||
<DiscordIcon color={color} size={24} />
|
||||
</IconButton>
|
||||
{false && (
|
||||
<IconButton component="a" href={DISCORD_LINK} target="_blank" data-testid="discord">
|
||||
<DiscordIcon color={color} size={24} />
|
||||
</IconButton>
|
||||
)}
|
||||
|
||||
<IconButton component="a" href={TWITTER_LINK} target="_blank" data-testid="twitter">
|
||||
<TwitterIcon color={color} size={24} />
|
||||
</IconButton>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user