Compare commits

...

78 Commits

Author SHA1 Message Date
durch 3404efc283 Checkpoint 2022-04-12 11:53:24 +02:00
durch 820da702d5 Checkpoint 2022-04-11 17:42:17 +02:00
dependabot[bot] dc0b9c271c Bump ansi-regex in /docker/typescript_client/upload_contract (#1171)
Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/chalk/ansi-regex/releases)
- [Commits](https://github.com/chalk/ansi-regex/compare/v4.1.0...v4.1.1)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-11 14:07:59 +01:00
Jon Häggblad 9ef29037bc Merge pull request #1195 from nymtech/feature/expose-more-validator-in-wallet
wallet: expose additional validator configuration functionality to the frontend
2022-04-11 11:21:08 +02:00
Jon Häggblad 1e13d41245 wallet: make config file version optional 2022-04-11 10:52:58 +02:00
Jon Häggblad 46a4991c12 wallet: add version number to network config files 2022-04-11 10:52:58 +02:00
Jon Häggblad 9d2d670990 wallet: remove duplicate handler entries 2022-04-11 10:52:58 +02:00
Jon Häggblad baba5ed212 wallet: additional backend logic for selecting validators 2022-04-11 10:52:58 +02:00
Jon Häggblad ceb5f090cf wallet: expose select, add and remove validators 2022-04-11 10:52:58 +02:00
Raphaël Walther b6ffe8664c Added additionnal clean task for Windows 2022-04-11 09:39:01 +02:00
Jon Häggblad be4bc2bdcc Merge branch 'release/1.0.0-rc.1' into develop
Conflicts:
	validator-api/Cargo.toml
2022-04-08 12:14:47 +02:00
Jędrzej Stuczyński bc049cb954 Feature/aggregated econ dynamics explorer endpoint (#1203)
* Economic dynamics stats endpoint on the explorer API with dummy fixture data

* Populating the endpoint with real data aggregated from validator api

* Introduced new cache functionalities
2022-04-08 10:15:50 +01:00
Drazen Urch 9aa5b98465 Debugging validator (#1198)
* Checkpoint

* Replace Stream logic with StreamMap

* Ignore blacklisted mixnodes and gateways

* Moar logging

* Remove version checks

* Cleanup

* Some more cleanup
2022-04-07 18:13:29 +02:00
Jędrzej Stuczyński d6c9d1d08d Using fork of cosmrs with different address length validation 2022-04-05 17:52:59 +01:00
Jędrzej Stuczyński 49fc51853a Updated mainnet defaults to the most recent values 2022-04-05 17:52:41 +01:00
Jędrzej Stuczyński c177f14073 Using fork of cosmrs with different address length validation 2022-04-05 17:35:50 +01:00
Mark Sinclair 026932dc16 Wallet 1.0.2 visual tweaks (#1197)
* Add `fill` prop to Nym wordmark component

* Tweaks to client address display

* Add story for ClientAddress
2022-04-05 17:06:47 +01:00
Jędrzej Stuczyński 515d4b73f7 Updated mainnet defaults to the most recent values 2022-04-05 16:52:16 +01:00
Fouad fda3636783 Password for wallet with routes (#1196)
* new password flow

* update global error and load state from children

* fix linting

* dont load account when creating mnemonic

* wallets: provide placeholder functions for ui password

* wallet: platform_constants

* wallet: swap println to log

* UI for existing mnemonic to be use

* wallet: inline encryption of wallet file

* wallet: tweak error enum names

* wallet: general wallet_storage tidy

* wallet: tweak some type names

* create sign-in context

* update sign in functions

* move state to context

* update pages

* connect new rust methods with frontend

* update components

* remove non-existent method

* add separate sign in pages for mnemonic and password

* add a hook for clipboard copy

* fix workmark svg sizing issue

* create step component

* use new sign in  pages

* reorder pages

* use clipboard lib directly

* ui tweaks

* use login type selector

* update password strength test + use autofocus prop for password input

* start adding routes

* restructure with routes

* wip

* more wip

* more wip

* reset state where required

* minor flow updates

* validate version (any valid semver version)

* reset error on page move

* flow tweaks

* content update

Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
2022-04-05 16:46:16 +01:00
Dave Hrycyszyn c056269f0e Tagging release contracts 2022-04-05 15:37:38 +01:00
Mark Sinclair ca49fe2293 GitHub Actions fixes for publishing wallet 2022-04-05 09:42:37 +01:00
Mark Sinclair 1b37ff2242 Update wallet lock file 2022-04-04 18:17:42 +01:00
Mark Sinclair 3712b38230 Fix wallet lock file version 2022-04-04 18:17:13 +01:00
Mark Sinclair 467bda8ddd Add tauri updater feature 2022-04-04 18:15:33 +01:00
Mark Sinclair b083335f56 Add updater config to tauri.conf.json 2022-04-04 18:15:25 +01:00
Mark Sinclair 5cc08211b7 Merge pull request #1194 from nymtech/feature/nym-wallet-updater
Add auto-updater to Nym Wallet
2022-04-04 17:57:28 +01:00
Drazen Urch a63a94623f Update and query rewarding validator address (#1193) 2022-04-04 16:55:46 +02:00
Mark Sinclair 5deafaa27b Add tauri updater feature 2022-04-04 15:03:21 +01:00
Mark Sinclair 021b542a4a Add updater config to tauri.conf.json 2022-04-04 15:03:21 +01:00
Mark Sinclair 64ee03112e GitHub Actions: fix wallet updater location for MacOS 2022-04-04 15:03:01 +01:00
Mark Sinclair bed709b155 Fix wallet lock file version 2022-04-04 15:02:36 +01:00
Mark Sinclair 1f6d4153a7 GitHub Actions: fix wallet updater path for upload to release 2022-04-04 14:25:55 +01:00
Mark Sinclair de45ab8995 GitHub Actions: nym wallet build uploads updater zip and signature to release 2022-04-04 13:47:01 +01:00
Mark Sinclair 15b552fa62 GitHub Actions wallet publish - add missing env vars for updater signing 2022-04-04 12:07:30 +01:00
Mark Sinclair 0d343eb82d Update GitHub Actions for wallet to upload the signature for the auto-updater 2022-04-04 11:39:08 +01:00
Jon Häggblad b5eddb6919 wallet: fix get urls function call 2022-04-04 09:19:00 +02:00
Jon Häggblad 7ddde50ffa wallet: expose validator urls to the frontend 2022-04-04 09:10:56 +02:00
Jon Häggblad d81967189a Fix clippy warnings for beta toolchain 2022-04-04 08:55:34 +02:00
Jon Häggblad 11b22ce2c1 wallet: add test for decrypting file 2022-04-03 22:39:39 +02:00
Jon Häggblad 58fd40fb8e Fix clippy warnings 2022-04-03 21:15:50 +02:00
Max Hampshire 5bb516471e updated mainnet bandwidthgen contract address 2022-04-03 20:26:25 +02:00
Mark Sinclair 2b9ea90e16 Nym Wallet version 1.0.2 2022-04-01 18:50:16 +01:00
Mark Sinclair 2779b5d28a Password for wallet with routes (#1187)
* new password flow

* update global error and load state from children

* fix linting

* dont load account when creating mnemonic

* wallets: provide placeholder functions for ui password

* wallet: platform_constants

* wallet: swap println to log

* UI for existing mnemonic to be use

* wallet: inline encryption of wallet file

* wallet: tweak error enum names

* wallet: general wallet_storage tidy

* wallet: tweak some type names

* create sign-in context

* update sign in functions

* move state to context

* update pages

* connect new rust methods with frontend

* update components

* remove non-existent method

* add separate sign in pages for mnemonic and password

* add a hook for clipboard copy

* fix workmark svg sizing issue

* create step component

* use new sign in  pages

* reorder pages

* use clipboard lib directly

* ui tweaks

* use login type selector

* update password strength test + use autofocus prop for password input

* start adding routes

* restructure with routes

* wip

* more wip

* more wip

* reset state where required

* wallet: remove unused rust use statements

* fix unbond page

Co-authored-by: fmtabbara <fmtabbara@hotmail.co.uk>
Co-authored-by: Jon Häggblad <jon.haggblad@gmail.com>
Co-authored-by: Tommy Verrall <tommyvez@protonmail.com>
2022-04-01 18:44:44 +01:00
Mark Sinclair 9ea2ce5c70 Merge pull request #1184 from nymtech/feature/adding-discord
Feature/adding discord
2022-04-01 15:50:03 +01:00
Jon Häggblad 11e1e728e1 Merge pull request #1183 from nymtech/feature/wallet-save-and-load-validators-from-file
wallet: config backend for validator selection
2022-04-01 15:40:02 +02:00
Jon Häggblad 4116aa18a8 wallet: config backend for validators 2022-04-01 15:18:59 +02:00
Jon Häggblad bdebe00c25 Merge pull request #1186 from nymtech/feature/wallet-add-validate-mnemonic
wallet: add validate_mnemonic
2022-04-01 15:18:09 +02:00
Jon Häggblad e106390e1d wallet: add validate_mnemonic 2022-04-01 15:09:22 +02:00
Jon Häggblad 7a53821af9 Merge pull request #1185 from nymtech/feature/wallet-support-remove-account
wallet: support removing accounts from the wallet file
2022-04-01 14:57:32 +02:00
Jon Häggblad efe6df12c9 wallet: remove unused 2022-04-01 14:46:44 +02:00
Jon Häggblad 23fb34f564 wallet: support removing accounts from the wallet file 2022-04-01 14:42:05 +02:00
gala1234 09155fbf12 update discord url 2022-04-01 12:42:17 +02:00
gala1234 53292ceca9 enable discord icon 2022-04-01 12:37:24 +02:00
Mark Sinclair 45b41d9e20 Update README.md 2022-04-01 10:07:02 +01:00
Drazen Urch 95b6ac50be Type safe bounds (#1179)
* Type safe bounds

* Debug get_all_delegations

* List raw delegations

* query all delegation values

* Get all keys and values

* Differentiate on len

* Clean up duplicates

* Remove stop gap

* Cleanup, fix paged query key

* Reduce queries required to get all delegations

* Sandbox migration

* Delete migration
2022-03-30 12:20:03 +02:00
Tommy Verrall 5f2247ab83 Merge pull request #1180 from nymtech/update-wallet-unbond
fix unbond page
2022-03-30 09:35:22 +01:00
Tommy Verrall 842bcfa782 fix unbond page 2022-03-30 09:34:17 +01:00
Jon Häggblad c85b0ad07d Merge pull request #1170 from nymtech/feature/connection-test-nymd-api-urls-indep
wallet: connection test nymd and api urls independently
2022-03-30 00:11:20 +02:00
Jon Häggblad adf4537183 rustfmt 2022-03-30 00:02:34 +02:00
Jon Häggblad 1cf101d50f connection-tester: refine log statements 2022-03-29 23:57:54 +02:00
Jon Häggblad e91e6943c6 connection-tester: add nymd-client cfg 2022-03-29 23:53:09 +02:00
Jon Häggblad 700f6a4e98 connection-tester: add missing timeout for nymd test 2022-03-29 23:53:09 +02:00
Jon Häggblad b759e5e7f2 connection-tester: extract out connection test methods 2022-03-29 23:53:09 +02:00
Jon Häggblad deefa09066 connection-tester: extract out setup method 2022-03-29 23:53:09 +02:00
Jon Häggblad 3f6cb919ac connection-tester: extract or collection method 2022-03-29 23:53:09 +02:00
Jon Häggblad d08bf61905 wallet: rustfmt 2022-03-29 23:53:09 +02:00
Jon Häggblad da18a60a91 wallet: remove deprecated validator_health checks 2022-03-29 23:53:09 +02:00
Jon Häggblad cec7496794 connection-tester: cap number of urls tested async 2022-03-29 23:53:09 +02:00
Jon Häggblad dd82b24d61 wallet: skip duplicate validator url entries 2022-03-29 23:53:09 +02:00
Jon Häggblad df827b6b09 validator-client: rework connection tester 2022-03-29 23:53:09 +02:00
Mark Sinclair cb25cc2eb9 Merge pull request #1178 from nymtech/feature/wallet-storybook
Add storybook to wallet
2022-03-29 17:26:46 +01:00
Mark Sinclair abf7e1f6ad Bundle fonts into wallet 2022-03-29 17:18:33 +01:00
Mark Sinclair 0f5137ea24 Add storybook to nym-wallet 2022-03-29 17:16:00 +01:00
Drazen Urch c2938e0672 Fix delegation paging (#1174)
* Fix delegation paging and tests

* Circuit breaker, upgrade storage and cosmwasm

* beta6

* Cleanup delegation events
2022-03-29 17:02:56 +02:00
Tommy Verrall 278516ad11 Merge pull request #1172 from nymtech/feature/update-binary-version
Update binaries to rc version
2022-03-29 10:02:48 +01:00
Mark Sinclair ce241339bf Create nym-release-publish.yml 2022-03-28 18:16:08 +01:00
Tommy Verrall 8d7428923a Update binaries to rc version 2022-03-28 15:32:31 +01:00
Drazen Urch 4a1a4b6415 Different values for mixes and gateways (#1169) 2022-03-28 13:33:55 +02:00
153 changed files with 4131 additions and 1801 deletions
+5
View File
@@ -75,6 +75,11 @@ jobs:
command: clippy
args: --workspace --all-targets -- -D warnings
- name: Reclaim some disk space (because Windows is being annoying)
uses: actions-rs/cargo@v1
if: ${{ matrix.os == 'windows-latest' }}
with:
command: clean
# COCONUT stuff
- name: Build all binaries with coconut enabled
@@ -64,12 +64,16 @@ jobs:
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_IDENTITY_ID }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
run: yarn && yarn build
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
with:
files: nym-wallet/target/release/bundle/dmg/*.dmg
files: |
nym-wallet/target/release/bundle/dmg/*.dmg
nym-wallet/target/release/bundle/macos/*.app.tar.gz*
- name: Clean up keychain
if: ${{ always() }}
@@ -37,10 +37,16 @@ jobs:
uses: actions-rs/toolchain@v1
with:
toolchain: stable
- name: Install app dependencies and build it
run: yarn && yarn build
- name: Install app dependencies
run: yarn
- name: Build app
run: yarn build
env:
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
with:
files: nym-wallet/target/release/bundle/appimage/*.AppImage
files: |
nym-wallet/target/release/bundle/appimage/*.AppImage
nym-wallet/target/release/bundle/appimage/*.AppImage.tar.gz*
@@ -63,9 +63,13 @@ jobs:
ENABLE_CODE_SIGNING: ${{ secrets.WINDOWS_CERTIFICATE }}
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
run: yarn build
- name: Upload to release based on tag name
uses: softprops/action-gh-release@v1
with:
files: nym-wallet/target/release/bundle/msi/*.msi
files: |
nym-wallet/target/release/bundle/msi/*.msi
nym-wallet/target/release/bundle/msi/*.msi.zip*
@@ -0,0 +1,55 @@
name: Nym Wallet Storybook
on:
push:
paths:
- 'nym-wallet/**'
jobs:
build:
runs-on: custom-runner-linux
steps:
- uses: actions/checkout@v2
- name: Install rsync
run: sudo apt-get install rsync
- uses: rlespinasse/github-slug-action@v3.x
- uses: actions/setup-node@v2
with:
node-version: '16'
- name: Setup yarn
run: npm install -g yarn
- name: Build dependencies
run: yarn && yarn build
- name: Build storybook
run: yarn storybook:build
working-directory: ./nym-wallet
- name: Deploy branch to CI www (storybook)
continue-on-error: true
uses: easingthemes/ssh-deploy@main
env:
SSH_PRIVATE_KEY: ${{ secrets.CI_WWW_SSH_PRIVATE_KEY }}
ARGS: "-rltgoDzvO --delete"
SOURCE: "nym-wallet/storybook-static/"
REMOTE_HOST: ${{ secrets.CI_WWW_REMOTE_HOST }}
REMOTE_USER: ${{ secrets.CI_WWW_REMOTE_USER }}
TARGET: ${{ secrets.CI_WWW_REMOTE_TARGET }}/wallet-${{ env.GITHUB_REF_SLUG }}
EXCLUDE: "/dist/, /node_modules/"
- name: Keybase - Node Install
run: npm install
working-directory: .github/workflows/support-files
- name: Keybase - Send Notification
env:
NYM_NOTIFICATION_KIND: nym-wallet
NYM_PROJECT_NAME: "nym-wallet"
NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}"
NYM_CI_WWW_LOCATION: "wallet-${{ env.GITHUB_REF_SLUG }}"
GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}"
GIT_BRANCH: "${GITHUB_REF##*/}"
KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}"
KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}"
KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}"
KEYBASE_NYM_CHANNEL: "ci-nym-wallet"
IS_SUCCESS: "${{ job.status == 'success' }}"
uses: docker://keybaseio/client:stable-node
with:
args: .github/workflows/support-files/notifications/entry_point.sh
@@ -3,7 +3,7 @@ require('dotenv').config();
const Bot = require('keybase-bot');
let context = {
kinds: ['ts-packages', 'network-explorer', 'nightly'],
kinds: ['nym-wallet', 'ts-packages', 'network-explorer', 'nightly'],
};
/**
@@ -0,0 +1,29 @@
const Handlebars = require('handlebars');
const fs = require('fs');
const path = require('path');
async function addToContextAndValidate(context) {
if (!context.env.NYM_CI_WWW_LOCATION) {
throw new Error('Please ensure the env var NYM_CI_WWW_LOCATION is set');
}
if (!context.env.NYM_CI_WWW_BASE) {
throw new Error('Please ensure the env var NYM_CI_WWW_BASE is set');
}
}
async function getMessageBody(context) {
const source = fs
.readFileSync(
context.env.IS_SUCCESS === 'true'
? path.resolve(__dirname, 'templates', 'success')
: path.resolve(__dirname, 'templates', 'failure'),
)
.toString();
const template = Handlebars.compile(source);
return template(context);
}
module.exports = {
addToContextAndValidate,
getMessageBody,
};
@@ -0,0 +1,11 @@
🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥
> :rocket: {{ env.NYM_PROJECT_NAME }}
> 🔴 **FAILURE** :cry:
> `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 }}
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
Commit message:
```
{{ env.GIT_COMMIT_MESSAGE }}
```
@@ -0,0 +1,15 @@
🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩🟩
> :rocket: {{ env.NYM_PROJECT_NAME }}
> ✅ **SUCCESS**
> ➡️➡️➡️➡️➡️ **View output:**
> `storybook`: https://{{ env.NYM_CI_WWW_LOCATION }}.{{ env.NYM_CI_WWW_BASE }}
> `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 }}
> `build ` {{ env.GITHUB_SERVER_URL }}/{{ env.GITHUB_REPOSITORY }}/actions/runs/{{ env.GITHUB_RUN_ID }}
Commit message by `{{ env.GITHUB_ACTOR }}` at {{ timestamp }}:
```
{{ env.GIT_COMMIT_MESSAGE }}
```
Generated
+66 -40
View File
@@ -555,7 +555,7 @@ dependencies = [
[[package]]
name = "client-core"
version = "0.12.0"
version = "1.0.0-rc.1"
dependencies = [
"config",
"crypto",
@@ -623,6 +623,7 @@ version = "0.1.0"
dependencies = [
"handlebars",
"humantime-serde",
"log",
"network-defaults",
"serde",
"toml",
@@ -741,6 +742,16 @@ dependencies = [
"tendermint-proto",
]
[[package]]
name = "cosmos-sdk-proto"
version = "0.9.0"
source = "git+https://github.com/nymtech/cosmos-rust?branch=bugfix/account-id-length-validation#911fbe1236cfed591783ccef01018f7ccc97c496"
dependencies = [
"prost",
"prost-types",
"tendermint-proto",
]
[[package]]
name = "cosmrs"
version = "0.4.1"
@@ -748,7 +759,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "505ea048e9ff2f906d6b954f9f8157d903ca468bfb301d906b40ecc25ba6838d"
dependencies = [
"bip32",
"cosmos-sdk-proto",
"cosmos-sdk-proto 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"ecdsa 0.13.4",
"eyre",
"getrandom 0.2.5",
"k256 0.10.4",
"prost",
"prost-types",
"rand_core 0.6.3",
"serde",
"serde_json",
"subtle-encoding",
"tendermint",
"thiserror",
]
[[package]]
name = "cosmrs"
version = "0.4.1"
source = "git+https://github.com/nymtech/cosmos-rust?branch=bugfix/account-id-length-validation#911fbe1236cfed591783ccef01018f7ccc97c496"
dependencies = [
"bip32",
"cosmos-sdk-proto 0.9.0 (git+https://github.com/nymtech/cosmos-rust?branch=bugfix/account-id-length-validation)",
"ecdsa 0.13.4",
"eyre",
"getrandom 0.2.5",
@@ -766,31 +798,31 @@ dependencies = [
[[package]]
name = "cosmwasm-crypto"
version = "1.0.0-beta6"
version = "1.0.0-beta8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dddc1443004c6340e55ca66d98e9d2f1a44aadf4ce2bed2c4f29baa8a15e7b7"
checksum = "37e70111e9701c3ec43bfbff0e523cd4cb115876b4d3433813436dd0934ee962"
dependencies = [
"digest 0.9.0",
"ed25519-zebra",
"k256 0.9.6",
"rand_core 0.5.1",
"rand_core 0.6.3",
"thiserror",
]
[[package]]
name = "cosmwasm-derive"
version = "1.0.0-beta6"
version = "1.0.0-beta8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe4f0f10f165b8bcc558a13cddb498140960544519aa0581532c766dd80b5598"
checksum = "58bc2ad5d86be5f6068833f63e20786768db6890019c095dd7775232184fb7b3"
dependencies = [
"syn",
]
[[package]]
name = "cosmwasm-std"
version = "1.0.0-beta6"
version = "1.0.0-beta8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0f3145097b692b2d95fa5d2c7c6fdd60f193ccc709857e7e1987a608725300"
checksum = "915ca82bd944f116f3a9717481f3fa657e4a73f28c4887288761ebb24e6fbe10"
dependencies = [
"base64",
"cosmwasm-crypto",
@@ -865,7 +897,7 @@ version = "0.1.0"
dependencies = [
"bls12_381",
"coconut-interface",
"cosmrs",
"cosmrs 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"crypto",
"network-defaults",
"rand 0.7.3",
@@ -1112,20 +1144,9 @@ dependencies = [
[[package]]
name = "cw-storage-plus"
version = "0.11.1"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d7ee1963302b0ac2a9d42fe0faec826209c17452bfd36fbfd9d002a88929261"
dependencies = [
"cosmwasm-std",
"schemars",
"serde",
]
[[package]]
name = "cw-storage-plus"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c087ff98fb0475db4c2b5298a5fd12b2848d2854b39d1115d930ee6da24d1eed"
checksum = "5e8b7f9a758c030d375520df947323c052704f784561fc28dcaab4f988c50a30"
dependencies = [
"cosmwasm-std",
"schemars",
@@ -1363,16 +1384,17 @@ dependencies = [
[[package]]
name = "ed25519-zebra"
version = "2.2.0"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a128b76af6dd4b427e34a6fd43dc78dbfe73672ec41ff615a2414c1a0ad0409"
checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69"
dependencies = [
"curve25519-dalek",
"hex",
"rand_core 0.5.1",
"rand_core 0.6.3",
"serde",
"sha2",
"thiserror",
"zeroize",
]
[[package]]
@@ -1511,7 +1533,7 @@ dependencies = [
[[package]]
name = "explorer-api"
version = "0.1.0"
version = "1.0.0-rc.1"
dependencies = [
"chrono",
"humantime-serde",
@@ -1855,7 +1877,7 @@ name = "gateway-client"
version = "0.1.0"
dependencies = [
"coconut-interface",
"cosmrs",
"cosmrs 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
"credentials",
"crypto",
"fluvio-wasm-timer",
@@ -2951,7 +2973,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "0.12.1"
version = "1.0.0-rc.1"
dependencies = [
"clap 2.34.0",
"client-core",
@@ -2985,7 +3007,7 @@ dependencies = [
[[package]]
name = "nym-gateway"
version = "0.12.1"
version = "1.0.0-rc.1"
dependencies = [
"anyhow",
"async-trait",
@@ -3031,7 +3053,7 @@ dependencies = [
[[package]]
name = "nym-mixnode"
version = "0.12.1"
version = "1.0.0-rc.1"
dependencies = [
"anyhow",
"bs58",
@@ -3069,7 +3091,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "0.12.0"
version = "1.0.0-rc.1"
dependencies = [
"clap 2.34.0",
"dirs",
@@ -3090,7 +3112,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "0.12.1"
version = "1.0.0-rc.1"
dependencies = [
"clap 2.34.0",
"client-core",
@@ -3125,7 +3147,7 @@ dependencies = [
[[package]]
name = "nym-validator-api"
version = "0.12.0"
version = "1.0.0-rc.1"
dependencies = [
"anyhow",
"async-trait",
@@ -3162,6 +3184,7 @@ dependencies = [
"thiserror",
"time 0.3.7",
"tokio",
"tokio-stream",
"topology",
"url",
"validator-api-requests",
@@ -5943,9 +5966,9 @@ checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "uint"
version = "0.9.1"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f"
checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0"
dependencies = [
"byteorder",
"crunchy",
@@ -6068,10 +6091,12 @@ dependencies = [
"base64",
"bip39",
"coconut-interface",
"colored",
"config",
"cosmrs",
"cosmrs 0.4.1 (git+https://github.com/nymtech/cosmos-rust?branch=bugfix/account-id-length-validation)",
"cosmwasm-std",
"flate2",
"futures",
"itertools",
"log",
"mixnet-contract-common",
@@ -6082,6 +6107,7 @@ dependencies = [
"serde_json",
"sha2",
"thiserror",
"tokio",
"ts-rs",
"url",
"validator-api-requests",
@@ -6139,11 +6165,11 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "vesting-contract"
version = "0.1.0"
version = "1.0.0"
dependencies = [
"config",
"cosmwasm-std",
"cw-storage-plus 0.12.1",
"cw-storage-plus",
"mixnet-contract-common",
"schemars",
"serde",
@@ -6157,7 +6183,7 @@ version = "0.1.0"
dependencies = [
"config",
"cosmwasm-std",
"cw-storage-plus 0.11.1",
"cw-storage-plus",
"mixnet-contract-common",
"schemars",
"serde",
+1 -1
View File
@@ -1,4 +1,4 @@
<svg width="210" height="56" viewBox="0 0 210 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg viewBox="0 0 210 56" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M45.8829 0.142822H45.7169V0.28114V48.637L25.3289 0.225818L25.3012 0.142822H25.1905H13.6272H0.652966H0.514648V0.28114V55.7189V55.8572H0.652966H13.6272H13.7655V55.7189V7.28002L34.2365 55.7742L34.2642 55.8572H34.3748H45.8829H58.8294H58.9677V55.7189V0.28114V0.142822H58.8294H45.8829Z"/>
<path d="M209.347 0.142822H184.616H184.477L184.45 0.253483L171.78 48.8583L159.082 0.253483L159.054 0.142822H158.944H134.157H133.991V0.28114V55.7189V55.8572H134.157H147.104H147.242V55.7189V7.66731L159.774 55.7466L159.801 55.8572H159.94H183.564H183.675L183.703 55.7466L196.234 7.66731V55.7189V55.8572H196.373H209.347H209.485V55.7189V0.28114V0.142822H209.347Z"/>
<path d="M112.663 0.142822H112.58L112.552 0.198153L96.8116 27.5574L80.988 0.198153L80.9604 0.142822H80.8774H65.9114H65.6348L65.7731 0.364136L90.1447 42.5787V55.7189V55.8572H90.283H103.257H103.396V55.7189V42.5787L127.767 0.364136L127.905 0.142822H127.629H112.663Z"/>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1011 B

+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "client-core"
version = "0.12.0"
version = "1.0.0-rc.1"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "0.12.1"
version = "1.0.0-rc.1"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2021"
rust-version = "1.56"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "0.12.1"
version = "1.0.0-rc.1"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021"
rust-version = "1.56"
+1 -1
View File
@@ -1,7 +1,7 @@
[package]
name = "nym-client-wasm"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jedrzej Stuczynski <andrew@nymtech.net>"]
version = "0.12.0"
version = "1.0.0-rc.1"
edition = "2021"
keywords = ["nym", "sphinx", "wasm", "webassembly", "privacy", "client"]
license = "Apache-2.0"
@@ -72,7 +72,12 @@ impl PacketRouter {
if !received_acks.is_empty() {
trace!("routing acks");
self.ack_sender.unbounded_send(received_acks).unwrap();
match self.ack_sender.unbounded_send(received_acks) {
Ok(_) => {}
Err(e) => {
error!("failed to send ack: {:?}", e);
}
};
}
}
}
@@ -74,7 +74,7 @@ impl PartiallyDelegated {
// This would also require NOT discarding any text responses here.
// TODO: those can return the "send confirmations" - perhaps it should be somehow worked around?
Message::Text(text) => debug!(
Message::Text(text) => trace!(
"received a text message - probably a response to some previous query! - {}",
text
),
@@ -9,6 +9,7 @@ rust-version = "1.56"
[dependencies]
base64 = "0.13"
colored = "2.0"
mixnet-contract-common = { path= "../../cosmwasm-smart-contracts/mixnet-contract" }
vesting-contract-common = { path= "../../cosmwasm-smart-contracts/vesting-contract" }
vesting-contract = { path = "../../../contracts/vesting" }
@@ -18,6 +19,8 @@ reqwest = { version = "0.11", features = ["json"] }
thiserror = "1"
log = "0.4"
url = { version = "2.2", features = ["serde"] }
tokio = { version = "1.10", features = ["sync", "time"] }
futures = "0.3"
coconut-interface = { path = "../../coconut-interface" }
network-defaults = { path = "../../network-defaults" }
@@ -29,16 +32,12 @@ validator-api-requests = { path = "../../../validator-api/validator-api-requests
async-trait = { version = "0.1.51", optional = true }
bip39 = { version = "1", features = ["rand"], optional = true }
config = { path = "../../config", optional = true }
cosmrs = { version = "0.4.1", features = [
"rpc",
"bip32",
"cosmwasm",
], optional = true }
cosmrs = { git = "https://github.com/nymtech/cosmos-rust", branch = "bugfix/account-id-length-validation", features = ["rpc", "bip32", "cosmwasm"], optional = true}
prost = { version = "0.9", default-features = false, optional = true }
flate2 = { version = "1.0.20", optional = true }
sha2 = { version = "0.9.5", optional = true }
itertools = { version = "0.10", optional = true }
cosmwasm-std = { version = "1.0.0-beta3", optional = true }
cosmwasm-std = { version = "1.0.0-beta6", optional = true }
[dev-dependencies]
ts-rs = "6.1.2"
@@ -0,0 +1,238 @@
use crate::nymd::error::NymdError;
use crate::nymd::{NymdClient, QueryNymdClient};
use crate::ApiClient;
use network_defaults::all::Network;
use colored::Colorize;
use core::fmt;
use itertools::Itertools;
use std::collections::HashMap;
use std::hash::BuildHasher;
use std::time::Duration;
use tokio::time::timeout;
use url::Url;
const MAX_URLS_TESTED: usize = 200;
const CONNECTION_TEST_TIMEOUT_SEC: u64 = 2;
// Run connection tests for all specified nymd and api urls. These are all run concurrently.
pub async fn run_validator_connection_test<H: BuildHasher + 'static>(
nymd_urls: impl Iterator<Item = (Network, Url)>,
api_urls: impl Iterator<Item = (Network, Url)>,
mixnet_contract_address: HashMap<Network, Option<cosmrs::AccountId>, H>,
) -> (
HashMap<Network, Vec<(Url, bool)>>,
HashMap<Network, Vec<(Url, bool)>>,
) {
// Setup all the clients for the connection tests
let connection_test_clients =
setup_connection_tests(nymd_urls, api_urls, mixnet_contract_address);
// Run all tests concurrently
let connection_results = futures::future::join_all(
connection_test_clients
.into_iter()
.take(MAX_URLS_TESTED)
.map(ClientForConnectionTest::run_connection_check),
)
.await;
// Seperate and collect results into HashMaps
(
extract_and_collect_results_into_map(&connection_results, &UrlType::Nymd),
extract_and_collect_results_into_map(&connection_results, &UrlType::Api),
)
}
fn setup_connection_tests<H: BuildHasher + 'static>(
nymd_urls: impl Iterator<Item = (Network, Url)>,
api_urls: impl Iterator<Item = (Network, Url)>,
mixnet_contract_address: HashMap<Network, Option<cosmrs::AccountId>, H>,
) -> impl Iterator<Item = ClientForConnectionTest> {
let nymd_connection_test_clients = nymd_urls.filter_map(move |(network, url)| {
let address = mixnet_contract_address
.get(&network)
.expect("No configured contract address")
.clone();
NymdClient::<QueryNymdClient>::connect(url.as_str(), address, None, None)
.map(move |client| ClientForConnectionTest::Nymd(network, url, Box::new(client)))
.ok()
});
let api_connection_test_clients = api_urls.map(|(network, url)| {
ClientForConnectionTest::Api(network, url.clone(), ApiClient::new(url))
});
nymd_connection_test_clients.chain(api_connection_test_clients)
}
fn extract_and_collect_results_into_map(
connection_results: &[ConnectionResult],
url_type: &UrlType,
) -> HashMap<Network, Vec<(Url, bool)>> {
connection_results
.iter()
.filter(|c| &c.url_type() == url_type)
.map(|c| {
let (network, url, result) = c.result();
(*network, (url.clone(), *result))
})
.into_group_map()
}
async fn test_nymd_connection(
network: Network,
url: &Url,
client: &NymdClient<QueryNymdClient>,
) -> ConnectionResult {
let result = match timeout(
Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC),
client.get_mixnet_contract_version(),
)
.await
{
Ok(Err(NymdError::TendermintError(e))) => {
// If we get a tendermint-rpc error, we classify the node as not contactable
log::debug!(
"Checking: nymd_url: {network}: {url}: {}: {}",
"failed".red(),
e
);
false
}
Ok(Err(NymdError::AbciError(code, log))) => {
// We accept the mixnet contract not found as ok from a connection standpoint. This happens
// for example on a pre-launch network.
log::debug!(
"Checking: nymd_url: {network}: {url}: {}, but with abci error: {code}: {log}",
"success".green()
);
code == 18
}
Ok(Err(error @ NymdError::NoContractAddressAvailable)) => {
log::debug!(
"Checking: nymd_url: {network}: {url}: {}: {error}",
"failed".red()
);
false
}
Ok(Err(e)) => {
// For any other error, we're optimistic and just try anyway.
log::debug!(
"Checking: nymd_url: {network}: {url}: {}, but with error: {e}",
"success".green()
);
true
}
Ok(Ok(_)) => {
log::debug!(
"Checking: nymd_url: {network}: {url}: {}",
"success".green()
);
true
}
Err(e) => {
log::debug!(
"Checking: nymd_url: {network}: {url}: {}: {e}",
"failed".red()
);
false
}
};
ConnectionResult::Nymd(network, url.clone(), result)
}
async fn test_api_connection(network: Network, url: &Url, client: &ApiClient) -> ConnectionResult {
let result = match timeout(
Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC),
client.get_cached_mixnodes(),
)
.await
{
Ok(Ok(_)) => {
log::debug!("Checking: api_url: {network}: {url}: {}", "success".green());
true
}
Ok(Err(e)) => {
log::debug!(
"Checking: api_url: {network}: {url}: {}: {e}",
"failed".red()
);
false
}
Err(e) => {
log::debug!(
"Checking: api_url: {network}: {url}: {}: {e}",
"failed".red()
);
false
}
};
ConnectionResult::Api(network, url.clone(), result)
}
enum ClientForConnectionTest {
Nymd(Network, Url, Box<NymdClient<QueryNymdClient>>),
Api(Network, Url, ApiClient),
}
impl ClientForConnectionTest {
async fn run_connection_check(self) -> ConnectionResult {
match self {
ClientForConnectionTest::Nymd(network, ref url, ref client) => {
test_nymd_connection(network, url, client).await
}
ClientForConnectionTest::Api(network, ref url, ref client) => {
test_api_connection(network, url, client).await
}
}
}
}
#[derive(Debug, PartialEq, Eq)]
enum UrlType {
Nymd,
Api,
}
impl fmt::Display for UrlType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UrlType::Nymd => write!(f, "nymd"),
UrlType::Api => write!(f, "api"),
}
}
}
#[derive(Debug)]
enum ConnectionResult {
Nymd(Network, Url, bool),
Api(Network, Url, bool),
}
impl ConnectionResult {
fn result(&self) -> (&Network, &Url, &bool) {
match self {
ConnectionResult::Nymd(network, url, result)
| ConnectionResult::Api(network, url, result) => (network, url, result),
}
}
fn url_type(&self) -> UrlType {
match self {
ConnectionResult::Nymd(..) => UrlType::Nymd,
ConnectionResult::Api(..) => UrlType::Api,
}
}
}
impl fmt::Display for ConnectionResult {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let (network, url, result) = self.result();
let url_type = self.url_type();
write!(
f,
"{network}: {url}: {url_type}: connection is successful: {result}"
)
}
}
@@ -2,6 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
pub mod client;
#[cfg(feature = "nymd-client")]
pub mod connection_tester;
mod error;
#[cfg(feature = "nymd-client")]
pub mod nymd;
@@ -599,6 +599,7 @@ impl<C> NymdClient<C> {
&self,
mix_identity: IdentityKey,
delegator: &AccountId,
proxy: Option<String>,
) -> Result<Delegation, NymdError>
where
C: CosmWasmClient + Sync,
@@ -606,6 +607,7 @@ impl<C> NymdClient<C> {
let request = QueryMsg::GetDelegationDetails {
mix_identity,
delegator: delegator.to_string(),
proxy,
};
self.client
.query_contract_smart(self.mixnet_contract_address()?, &request)
@@ -39,6 +39,10 @@ impl Client {
self.url = new_url
}
pub fn current_url(&self) -> &Url {
&self.url
}
async fn query_validator_api<T, K, V>(
&self,
path: PathSegments<'_>,
+2 -1
View File
@@ -9,8 +9,9 @@ edition = "2021"
[dependencies]
handlebars = "3.0.1"
humantime-serde = "1.0"
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
toml = "0.5.6"
url = "2.2"
network-defaults = { path = "../network-defaults" }
network-defaults = { path = "../network-defaults" }
+7 -1
View File
@@ -13,6 +13,7 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
fn template() -> &'static str;
fn config_file_name() -> String {
log::trace!("NymdConfig::config_file_name");
"config.toml".to_string()
}
@@ -20,6 +21,7 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
// default, most probable, implementations; can be easily overridden where required
fn default_config_directory(id: Option<&str>) -> PathBuf {
log::trace!("NymdConfig::default_config_directory");
if let Some(id) = id {
Self::default_root_directory().join(id).join("config")
} else {
@@ -28,6 +30,7 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
}
fn default_data_directory(id: Option<&str>) -> PathBuf {
log::trace!("NymdConfig::default_data_path");
if let Some(id) = id {
Self::default_root_directory().join(id).join("data")
} else {
@@ -36,6 +39,7 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
}
fn default_config_file_path(id: Option<&str>) -> PathBuf {
log::trace!("NymdConfig::default_config_file_path");
Self::default_config_directory(id).join(Self::config_file_name())
}
@@ -68,7 +72,9 @@ pub trait NymConfig: Default + Serialize + DeserializeOwned {
}
fn load_from_file(id: Option<&str>) -> io::Result<Self> {
let config_contents = fs::read_to_string(Self::default_config_file_path(id))?;
let file = Self::default_config_file_path(id);
log::trace!("Loading from file: {:#?}", file);
let config_contents = fs::read_to_string(file)?;
toml::from_str(&config_contents)
.map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
@@ -7,4 +7,4 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cosmwasm-std = "1.0.0-beta3"
cosmwasm-std = "1.0.0-beta6"
@@ -7,7 +7,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cosmwasm-std = "1.0.0-beta3"
cosmwasm-std = "1.0.0-beta8"
serde = { version = "1.0", features = ["derive"] }
serde_repr = "0.1"
@@ -9,6 +9,23 @@ use cosmwasm_std::{Coin, Uint128};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::hash::{Hash, Hasher};
type OwnerAddressBytes = Vec<u8>;
type BlockHeight = u64;
pub fn generate_storage_key(address: &Addr, proxy: Option<&Addr>) -> Vec<u8> {
if let Some(proxy) = &proxy {
address
.as_bytes()
.iter()
.zip(proxy.as_bytes())
.map(|(x, y)| x ^ y)
.collect()
} else {
address.as_bytes().to_vec()
}
}
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
pub struct Delegation {
@@ -19,12 +36,24 @@ pub struct Delegation {
pub proxy: Option<Addr>, // proxy address used to delegate the funds on behalf of anouther address
}
impl Eq for Delegation {}
#[allow(clippy::derive_hash_xor_eq)]
impl Hash for Delegation {
fn hash<H: Hasher>(&self, state: &mut H) {
self.owner.hash(state);
self.node_identity.hash(state);
self.block_height.hash(state);
self.proxy.hash(state);
}
}
impl Delegation {
pub fn new(
owner: Addr,
node_identity: IdentityKey,
amount: Coin,
block_height: u64,
block_height: BlockHeight,
proxy: Option<Addr>,
) -> Self {
Delegation {
@@ -36,15 +65,30 @@ impl Delegation {
}
}
// TODO: change that to use .joined_key() and return Vec<u8>
pub fn storage_key(&self) -> (IdentityKey, Vec<u8>, u64) {
pub fn storage_key(&self) -> (IdentityKey, OwnerAddressBytes, BlockHeight) {
(
self.node_identity(),
self.owner().as_bytes().to_vec(),
self.proxy_storage_key(),
self.block_height(),
)
}
pub fn event_storage_key(&self) -> (OwnerAddressBytes, BlockHeight, IdentityKey) {
(
self.proxy_storage_key(),
self.block_height(),
self.node_identity(),
)
}
pub fn proxy_storage_key(&self) -> OwnerAddressBytes {
generate_storage_key(&self.owner, self.proxy.as_ref())
}
pub fn proxy(&self) -> Option<&Addr> {
self.proxy.as_ref()
}
pub fn increment_amount(&mut self, amount: Uint128, at_height: Option<u64>) {
self.amount.amount += amount;
if let Some(at_height) = at_height {
@@ -86,10 +130,10 @@ pub struct PagedMixDelegationsResponse {
}
impl PagedMixDelegationsResponse {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<(Addr, u64)>) -> Self {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<(String, u64)>) -> Self {
PagedMixDelegationsResponse {
delegations,
start_next_after: start_next_after.map(|(s, h)| (s.to_string(), h)),
start_next_after,
}
}
}
@@ -1,7 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod delegation;
pub mod delegation;
pub mod error;
pub mod events;
mod gateway;
@@ -26,7 +26,7 @@ pub use mixnode::{
pub use msg::*;
pub use types::*;
pub type U128 = fixed::types::U75F53;
pub type U128 = fixed::types::U50F78;
fixed::const_fixed_from_int! {
const ONE: U128 = 1;
@@ -6,7 +6,7 @@ use crate::reward_params::RewardParams;
use crate::{Delegation, IdentityKey, SphinxKey};
use crate::{ONE, U128};
use az::CheckedCast;
use cosmwasm_std::{coin, Addr, Coin, Uint128};
use cosmwasm_std::{coin, Addr, Coin, Decimal, Uint128};
use log::error;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -78,6 +78,35 @@ impl PendingUndelegate {
pub fn block_height(&self) -> u64 {
self.block_height
}
pub fn proxy_storage_key(&self) -> Vec<u8> {
if let Some(proxy) = &self.proxy {
self.delegate()
.as_bytes()
.iter()
.zip(proxy.as_bytes())
.map(|(x, y)| x ^ y)
.collect()
} else {
self.delegate().as_bytes().to_vec()
}
}
pub fn storage_key(&self) -> (IdentityKey, Vec<u8>) {
(self.mix_identity(), self.proxy_storage_key())
}
pub fn delegation_key(&self, block_height: u64) -> (IdentityKey, Vec<u8>, u64) {
(self.mix_identity(), self.proxy_storage_key(), block_height)
}
pub fn event_storage_key(&self) -> (Vec<u8>, u64, IdentityKey) {
(
self.proxy_storage_key(),
self.block_height(),
self.mix_identity(),
)
}
}
#[cfg_attr(test, derive(ts_rs::TS))]
@@ -166,22 +195,16 @@ pub struct DelegatorRewardParams {
// to be completely honest I don't understand all consequences of using `#[schemars(with = "String")]`
// for U128 here, but it seems that CosmWasm is using the same attribute for their Uint128
#[schemars(with = "String")]
#[serde(with = "fixed_U128_as_string")]
sigma: U128,
#[schemars(with = "String")]
#[serde(with = "fixed_U128_as_string")]
profit_margin: U128,
#[schemars(with = "String")]
#[serde(with = "fixed_U128_as_string")]
node_profit: U128,
sigma: Decimal,
profit_margin: Decimal,
node_profit: Uint128,
}
impl DelegatorRewardParams {
pub fn new(
sigma: U128,
profit_margin: U128,
node_profit: U128,
sigma: Decimal,
profit_margin: Decimal,
node_profit: Uint128,
reward_params: RewardParams,
) -> Self {
DelegatorRewardParams {
@@ -194,23 +217,14 @@ impl DelegatorRewardParams {
pub fn determine_delegation_reward(&self, delegation_amount: Uint128) -> u128 {
// 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;
let scaled_delegation_amount = delegation_amount / Uint128::new(self.reward_params.circulating_supply());
let delegator_reward =
(ONE - self.profit_margin) * scaled_delegation_amount / self.sigma * self.node_profit;
(Decimal::one() - self.profit_margin) * (scaled_delegation_amount / self.sigma.atomics()) * self.node_profit;
let reward = delegator_reward.max(U128::ZERO);
if let Some(int_reward) = reward.checked_cast() {
int_reward
} else {
error!(
"Could not cast delegator reward ({}) to u128, returning 0",
reward,
);
0u128
}
let reward = delegator_reward.max(Uint128::zero());
reward.u128()
}
pub fn node_reward_params(&self) -> RewardParams {
@@ -221,8 +235,8 @@ impl DelegatorRewardParams {
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
pub struct StoredNodeRewardResult {
reward: Uint128,
lambda: Uint128,
sigma: Uint128,
lambda: Decimal,
sigma: Decimal,
}
impl StoredNodeRewardResult {
@@ -230,11 +244,11 @@ impl StoredNodeRewardResult {
self.reward
}
pub fn lambda(&self) -> Uint128 {
pub fn lambda(&self) -> Decimal {
self.lambda
}
pub fn sigma(&self) -> Uint128 {
pub fn sigma(&self) -> Decimal {
self.sigma
}
}
@@ -244,45 +258,30 @@ impl TryFrom<NodeRewardResult> for StoredNodeRewardResult {
fn try_from(node_reward_result: NodeRewardResult) -> Result<Self, Self::Error> {
Ok(StoredNodeRewardResult {
reward: Uint128::new(
node_reward_result
.reward()
.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)?,
),
reward: node_reward_result.reward(),
lambda: node_reward_result.lambda(),
sigma: node_reward_result.sigma(),
})
}
}
#[derive(Debug, Copy, Clone)]
pub struct NodeRewardResult {
reward: U128,
lambda: U128,
sigma: U128,
reward: Uint128,
lambda: Decimal,
sigma: Decimal,
}
impl NodeRewardResult {
pub fn reward(&self) -> U128 {
pub fn reward(&self) -> Uint128 {
self.reward
}
pub fn lambda(&self) -> U128 {
pub fn lambda(&self) -> Decimal {
self.lambda
}
pub fn sigma(&self) -> U128 {
pub fn sigma(&self) -> Decimal {
self.sigma
}
}
@@ -328,6 +327,10 @@ impl MixNodeBond {
U128::from_num(self.mix_node.profit_margin_percent) / U128::from_num(100)
}
pub fn profit_margin_dec(&self) -> Decimal {
Decimal::from_ratio(self.mix_node.profit_margin_percent, 100u128)
}
pub fn identity(&self) -> &String {
&self.mix_node.identity_key
}
@@ -361,53 +364,61 @@ impl MixNodeBond {
self.total_delegation.clone()
}
pub fn stake_saturation(&self, circulating_supply: u128, rewarded_set_size: u32) -> U128 {
pub fn stake_saturation(&self, circulating_supply: u128, rewarded_set_size: u32) -> Decimal {
self.total_bond_to_circulating_supply(circulating_supply)
* U128::from_num(rewarded_set_size)
* Decimal::from_atomics(rewarded_set_size, 0).unwrap()
}
// TODO: There is an effect here when adding accumulted rewards to the total bond, ie accumulated rewards will not
// affect lambda, but will affect sigma, in turn over time, if left unclaimed operator rewards will not compound, but
// behave similarly to delegations.
// The question is should this be taken into account when calculating operator rewards?
pub fn pledge_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
U128::from_num(self.pledge_amount().amount.u128()) / U128::from_num(circulating_supply)
pub fn pledge_to_circulating_supply(&self, circulating_supply: u128) -> Decimal {
// U128::from_num(self.pledge_amount().amount.u128()) / U128::from_num(circulating_supply);
Decimal::from_atomics(self.pledge_amount().amount.u128(), 0).unwrap()
/ Uint128::from(circulating_supply)
}
pub fn total_bond_to_circulating_supply(&self, circulating_supply: u128) -> U128 {
U128::from_num(self.pledge_amount().amount.u128() + self.total_delegation().amount.u128())
/ U128::from_num(circulating_supply)
pub fn total_bond_to_circulating_supply(&self, circulating_supply: u128) -> Decimal {
// U128::from_num(self.pledge_amount().amount.u128() + self.total_delegation().amount.u128())
// / U128::from_num(circulating_supply)
Decimal::from_atomics(
self.total_bond().unwrap_or(0) + self.pledge_amount().amount.u128(),
0,
)
.unwrap()
/ Uint128::from(circulating_supply)
}
pub fn lambda(&self, params: &RewardParams) -> U128 {
pub fn lambda(&self, params: &RewardParams) -> Decimal {
// Ratio of a bond to the token circulating supply
let pledge_to_circulating_supply_ratio =
self.pledge_to_circulating_supply(params.circulating_supply());
pledge_to_circulating_supply_ratio.min(params.one_over_k())
pledge_to_circulating_supply_ratio.min(params.one_over_k_dec())
}
pub fn sigma(&self, params: &RewardParams) -> U128 {
pub fn sigma(&self, params: &RewardParams) -> Decimal {
// Ratio of a delegation to the the token circulating supply
let total_bond_to_circulating_supply_ratio =
self.total_bond_to_circulating_supply(params.circulating_supply());
total_bond_to_circulating_supply_ratio.min(params.one_over_k())
total_bond_to_circulating_supply_ratio.min(params.one_over_k_dec())
}
pub fn estimate_reward(
&self,
params: &RewardParams,
) -> Result<(u64, u64, u64), MixnetContractError> {
let total_node_reward = self.reward(params);
let total_node_reward = self
.reward(params)
.reward();
let operator_reward = self.operator_reward(params);
// TODO: This overestimates the reward by a lot, it should take a Uint128 and return estiamte for that
let delegators_reward = self.reward_delegation(self.total_delegation().amount, params);
// Total reward has to be the sum of operator and delegator rewards
let delegators_reward = total_node_reward.u128() - operator_reward;
Ok((
total_node_reward
.reward()
.checked_to_num::<u128>()
.unwrap_or_default()
.try_into()?,
total_node_reward.u128().try_into()?,
operator_reward.try_into()?,
delegators_reward.try_into()?,
))
@@ -417,11 +428,16 @@ impl MixNodeBond {
let lambda = self.lambda(params);
let sigma = self.sigma(params);
let reward = params.performance()
* params.epoch_reward_pool()
println!("performance: {}", params.performance_dec().atomics());
println!("reward pool: {}", Uint128::from(params.epoch_reward_pool()));
println!("omega: {}", params.omega());
let reward = params.performance_dec()
* Uint128::from(params.epoch_reward_pool())
* (sigma * params.omega()
+ params.alpha() * lambda * sigma * params.rewarded_set_size())
/ (ONE + params.alpha());
+ params.alpha_dec() * lambda * sigma * Uint128::from(params.rewarded_set_size()))
/ (Decimal::one() + params.alpha_dec()).atomics();
NodeRewardResult {
reward,
@@ -430,53 +446,45 @@ impl MixNodeBond {
}
}
pub fn node_profit(&self, params: &RewardParams) -> U128 {
if self.reward(params).reward() < params.node.operator_cost() {
U128::from_num(0u128)
pub fn node_profit(&self, params: &RewardParams) -> Uint128 {
if self.reward(params).reward() < params.node.operator_cost_dec() {
Uint128::zero()
} else {
self.reward(params).reward() - params.node.operator_cost()
self.reward(params).reward() - params.node.operator_cost_dec()
}
}
pub fn operator_reward(&self, params: &RewardParams) -> u128 {
let reward = self.reward(params);
let profit = if reward.reward < params.node.operator_cost() {
U128::from_num(0u128)
println!("{:?}", reward);
let profit = if reward.reward < params.node.operator_cost_dec() {
Uint128::zero()
} else {
reward.reward - params.node.operator_cost()
reward.reward - params.node.operator_cost_dec()
};
let operator_base_reward = reward.reward.min(params.node.operator_cost());
let operator_reward = (self.profit_margin()
+ (ONE - self.profit_margin()) * reward.lambda / reward.sigma)
let operator_base_reward = reward.reward.min(params.node.operator_cost_dec());
let operator_reward = (self.profit_margin_dec()
+ (Decimal::one() - self.profit_margin_dec()) * reward.lambda / reward.sigma.atomics())
* profit;
let reward = (operator_reward + operator_base_reward).max(U128::from_num(0));
let reward = (operator_reward + operator_base_reward).max(Uint128::new(0));
if let Some(int_reward) = reward.checked_cast() {
int_reward
} else {
error!(
"Could not cast reward ({}) to u128, returning 0 - mixnode {}",
reward,
self.identity()
);
0u128
}
reward.u128()
}
pub fn sigma_ratio(&self, params: &RewardParams) -> U128 {
if self.total_bond_to_circulating_supply(params.circulating_supply()) < params.one_over_k()
{
self.total_bond_to_circulating_supply(params.circulating_supply())
} else {
params.one_over_k()
}
}
// pub fn sigma_ratio(&self, params: &RewardParams) -> U128 {
// if self.total_bond_to_circulating_supply(params.circulating_supply()) < params.one_over_k()
// {
// self.total_bond_to_circulating_supply(params.circulating_supply())
// } else {
// params.one_over_k()
// }
// }
pub fn reward_delegation(&self, delegation_amount: Uint128, params: &RewardParams) -> u128 {
let reward_params = DelegatorRewardParams::new(
self.sigma(params),
self.profit_margin(),
self.profit_margin_dec(),
self.node_profit(params),
params.to_owned(),
);
@@ -15,6 +15,9 @@ pub struct InstantiateMsg {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
UpdateRewardingValidatorAddress {
address: String,
},
InitEpoch {},
ReconcileDelegations {},
CheckpointMixnodes {},
@@ -101,6 +104,9 @@ pub enum ExecuteMsg {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
GetRewardingValidatorAddress {},
GetAllDelegationKeys {},
DebugGetAllDelegationValues {},
GetContractVersion {},
GetMixNodes {
limit: Option<u32>,
@@ -137,6 +143,7 @@ pub enum QueryMsg {
GetDelegationDetails {
mix_identity: IdentityKey,
delegator: String,
proxy: Option<String>,
},
LayerDistribution {},
GetRewardPool {},
@@ -1,10 +1,14 @@
use crate::{error::MixnetContractError, mixnode::StoredNodeRewardResult, ONE, U128};
use az::CheckedCast;
use cosmwasm_std::Uint128;
use cosmwasm_std::{Decimal, Uint128};
use network_defaults::DEFAULT_OPERATOR_INTERVAL_COST;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
fn sane_decimal(value: &Uint128) -> Decimal {
Decimal::new(value * Uint128::new(1_000_000_000_000_000_000u128))
}
#[derive(Debug, Clone, JsonSchema, PartialEq, Serialize, Deserialize, Copy)]
pub struct NodeEpochRewards {
params: NodeRewardParams,
@@ -25,11 +29,11 @@ impl NodeEpochRewards {
self.epoch_id
}
pub fn sigma(&self) -> Uint128 {
pub fn sigma(&self) -> Decimal {
self.result.sigma()
}
pub fn lambda(&self) -> Uint128 {
pub fn lambda(&self) -> Decimal {
self.result.lambda()
}
@@ -45,6 +49,19 @@ impl NodeEpochRewards {
U128::from_num(self.params.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128)
}
pub fn operator_cost_dec(&self) -> Uint128 {
Decimal::from_ratio(self.params.uptime, 100u128)
* Uint128::from(DEFAULT_OPERATOR_INTERVAL_COST)
}
pub fn node_profit_dec(&self) -> Uint128 {
if self.reward() < self.operator_cost_dec() {
Uint128::zero()
} else {
self.reward() - self.operator_cost_dec()
}
}
pub fn node_profit(&self) -> U128 {
let reward = U128::from_num(self.reward().u128());
if reward < self.operator_cost() {
@@ -54,44 +71,34 @@ impl NodeEpochRewards {
}
}
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());
pub fn operator_reward(&self, profit_margin: Decimal) -> Result<Uint128, MixnetContractError> {
let reward = self.node_profit_dec();
let operator_base_reward = reward.min(self.operator_cost_dec());
let operator_reward = (profit_margin
+ (ONE - profit_margin) * U128::from_num(self.lambda().u128())
/ U128::from_num(self.sigma().u128()))
+ (Decimal::one() - profit_margin) * self.lambda() / self.sigma().atomics())
* reward;
let reward = (operator_reward + operator_base_reward).max(U128::from_num(0u128));
let reward = (operator_reward + operator_base_reward).max(Uint128::zero());
if let Some(int_reward) = reward.checked_cast() {
Ok(Uint128::new(int_reward))
} else {
Err(MixnetContractError::CastError)
}
Ok(reward)
}
pub fn delegation_reward(
&self,
delegation_amount: Uint128,
profit_margin: U128,
profit_margin: Decimal,
epoch_reward_params: EpochRewardParams,
) -> Result<Uint128, MixnetContractError> {
// change all values into their fixed representations
let delegation_amount = U128::from_num(delegation_amount.u128());
let circulating_supply = U128::from_num(epoch_reward_params.circulating_supply());
// change all values into their fixed representations;
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 scaled_delegation_amount =
delegation_amount / Uint128::from(epoch_reward_params.circulating_supply());
let delegator_reward = (Decimal::one() - profit_margin) * scaled_delegation_amount
/ self.sigma().atomics()
* self.node_profit_dec();
let reward = delegator_reward.max(U128::ZERO);
if let Some(int_reward) = reward.checked_cast() {
Ok(Uint128::new(int_reward))
} else {
Err(MixnetContractError::CastError)
}
let reward = delegator_reward.max(Uint128::zero());
Ok(reward)
}
}
@@ -176,6 +183,11 @@ impl NodeRewardParams {
U128::from_num(self.uptime.u128() / 100u128 * DEFAULT_OPERATOR_INTERVAL_COST as u128)
}
pub fn operator_cost_dec(&self) -> Uint128 {
Decimal::from_ratio(self.uptime.u128(), 100u128)
* Uint128::new(DEFAULT_OPERATOR_INTERVAL_COST as u128)
}
pub fn uptime(&self) -> u128 {
self.uptime.u128()
}
@@ -196,18 +208,36 @@ impl RewardParams {
RewardParams { epoch, node }
}
pub fn omega(&self) -> U128 {
pub fn omega(&self) -> Uint128 {
// As per keybase://chat/nymtech#tokeneconomics/1179
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());
// 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 self.in_active_set() {
let active_set_work_factor = self.active_set_work_factor_dec();
let rewarded_set_size = sane_decimal(&self.epoch.rewarded_set_size);
let idle_nodes = sane_decimal(&self.idle_nodes());
println!("active_set_work_factor: {}", active_set_work_factor);
println!("rewarded_set_size: {}", rewarded_set_size);
println!("idle_nodes: {}", idle_nodes);
let denom = active_set_work_factor * rewarded_set_size
- (active_set_work_factor - Decimal::one()) * idle_nodes;
println!("denom: {}", denom);
let result = 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()
active_set_work_factor
* Decimal::from_ratio(Decimal::one().atomics(), denom.atomics())
* rewarded_set_size
} else {
// work_idle = 1 / (factor * self.network.k[month] - (factor - 1) * idle_nodes)
ONE / denom * self.rewarded_set_size()
}
Decimal::one() / denom.atomics() * rewarded_set_size
};
println!("omega_result: {}", result);
result.atomics()
}
pub fn idle_nodes(&self) -> Uint128 {
@@ -218,6 +248,10 @@ impl RewardParams {
U128::from_num(self.epoch.active_set_work_factor)
}
pub fn active_set_work_factor_dec(&self) -> Decimal {
sane_decimal(&Uint128::new(self.epoch.active_set_work_factor as u128))
}
pub fn in_active_set(&self) -> bool {
self.node.in_active_set
}
@@ -226,6 +260,10 @@ impl RewardParams {
U128::from_num(self.node.uptime.u128()) / U128::from_num(100)
}
pub fn performance_dec(&self) -> Decimal {
Decimal::from_ratio(self.node.uptime, 100u128)
}
pub fn set_reward_blockstamp(&mut self, blockstamp: u64) {
self.node.reward_blockstamp = blockstamp;
}
@@ -254,7 +292,15 @@ impl RewardParams {
ONE / U128::from_num(self.epoch.rewarded_set_size.u128())
}
pub fn one_over_k_dec(&self) -> Decimal {
Decimal::one() / self.epoch.rewarded_set_size
}
pub fn alpha(&self) -> U128 {
U128::from_num(self.epoch.sybil_resistance_percent) / U128::from_num(100)
}
pub fn alpha_dec(&self) -> Decimal {
Decimal::from_atomics(self.epoch.sybil_resistance_percent, 2).unwrap()
}
}
@@ -6,11 +6,11 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cosmwasm-std = "1.0.0-beta3"
cosmwasm-std = "1.0.0-beta6"
mixnet-contract-common = { path = "../mixnet-contract" }
serde = { version = "1.0", features = ["derive"] }
schemars = "0.8"
cw-storage-plus = "0.11.1"
cw-storage-plus = "0.13.1"
config = { path = "../../config" }
[dev-dependencies]
+1 -1
View File
@@ -215,7 +215,7 @@ mod test {
));
assert!(!BandwidthVoucher::verify_against_plain(
&voucher.get_public_attributes(),
&vec![],
&[],
));
assert!(!BandwidthVoucher::verify_against_plain(
&voucher.get_public_attributes(),
+5 -3
View File
@@ -6,15 +6,17 @@ use crate::ValidatorDetails;
pub(crate) const BECH32_PREFIX: &str = "n";
pub const DENOM: &str = "unym";
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
pub(crate) const MIXNET_CONTRACT_ADDRESS: &str =
"n14hj2tavq8fpesdwxxcu44rty3hh90vhujrvcmstl4zr3txmfvw9sjyvg3g";
pub(crate) const VESTING_CONTRACT_ADDRESS: &str =
"n1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrq73f2nw";
pub(crate) const BANDWIDTH_CLAIM_CONTRACT_ADDRESS: &str =
"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 = "n17zujduc46wvkwvp6f062mm5xhr7jc3fewvqu9e";
pub(crate) const REWARDING_VALIDATOR_ADDRESS: &str = "n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy";
pub(crate) fn validators() -> Vec<ValidatorDetails> {
vec![ValidatorDetails::new(
+25 -33
View File
@@ -41,7 +41,7 @@ checksum = "f771a5d1f5503f7f4279a30f3643d3421ba149848b89ecaaec0ea2acf04a5ac4"
[[package]]
name = "bandwidth-claim"
version = "0.1.0"
version = "1.0.0-rc.1"
dependencies = [
"bandwidth-claim-contract",
"config",
@@ -219,6 +219,7 @@ version = "0.1.0"
dependencies = [
"handlebars",
"humantime-serde",
"log",
"network-defaults",
"serde",
"toml",
@@ -240,22 +241,22 @@ dependencies = [
[[package]]
name = "cosmwasm-crypto"
version = "1.0.0-beta6"
version = "1.0.0-beta8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dddc1443004c6340e55ca66d98e9d2f1a44aadf4ce2bed2c4f29baa8a15e7b7"
checksum = "37e70111e9701c3ec43bfbff0e523cd4cb115876b4d3433813436dd0934ee962"
dependencies = [
"digest 0.9.0",
"ed25519-zebra",
"k256",
"rand_core 0.5.1",
"rand_core 0.6.3",
"thiserror",
]
[[package]]
name = "cosmwasm-derive"
version = "1.0.0-beta6"
version = "1.0.0-beta8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe4f0f10f165b8bcc558a13cddb498140960544519aa0581532c766dd80b5598"
checksum = "58bc2ad5d86be5f6068833f63e20786768db6890019c095dd7775232184fb7b3"
dependencies = [
"syn",
]
@@ -272,9 +273,9 @@ dependencies = [
[[package]]
name = "cosmwasm-std"
version = "1.0.0-beta6"
version = "1.0.0-beta8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0f3145097b692b2d95fa5d2c7c6fdd60f193ccc709857e7e1987a608725300"
checksum = "915ca82bd944f116f3a9717481f3fa657e4a73f28c4887288761ebb24e6fbe10"
dependencies = [
"base64",
"cosmwasm-crypto",
@@ -289,9 +290,9 @@ dependencies = [
[[package]]
name = "cosmwasm-storage"
version = "1.0.0-beta3"
version = "1.0.0-beta8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4a4e55f0d64fed54cd2202301b8d466af8de044589247dabd77a4222f52f749"
checksum = "1c4be9fd8c9d3ae7d0c32a925ecbc20707007ce0cba1f7538c0d78b7a2d3729b"
dependencies = [
"cosmwasm-std",
"serde",
@@ -382,20 +383,9 @@ dependencies = [
[[package]]
name = "cw-storage-plus"
version = "0.11.1"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d7ee1963302b0ac2a9d42fe0faec826209c17452bfd36fbfd9d002a88929261"
dependencies = [
"cosmwasm-std",
"schemars",
"serde",
]
[[package]]
name = "cw-storage-plus"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c087ff98fb0475db4c2b5298a5fd12b2848d2854b39d1115d930ee6da24d1eed"
checksum = "5e8b7f9a758c030d375520df947323c052704f784561fc28dcaab4f988c50a30"
dependencies = [
"cosmwasm-std",
"schemars",
@@ -472,16 +462,17 @@ dependencies = [
[[package]]
name = "ed25519-zebra"
version = "2.2.0"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a128b76af6dd4b427e34a6fd43dc78dbfe73672ec41ff615a2414c1a0ad0409"
checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69"
dependencies = [
"curve25519-dalek",
"hex",
"rand_core 0.5.1",
"rand_core 0.6.3",
"serde",
"sha2",
"thiserror",
"zeroize",
]
[[package]]
@@ -842,7 +833,7 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "mixnet-contract"
version = "0.1.0"
version = "1.0.0"
dependencies = [
"az",
"bs58",
@@ -851,8 +842,9 @@ dependencies = [
"cosmwasm-std",
"cosmwasm-storage",
"crypto",
"cw-storage-plus 0.12.1",
"cw-storage-plus",
"fixed",
"hex",
"mixnet-contract-common",
"rand",
"rand_chacha",
@@ -1475,9 +1467,9 @@ checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "uint"
version = "0.9.1"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6470ab50f482bde894a037a57064480a246dbfdd5960bd65a44824693f08da5f"
checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0"
dependencies = [
"byteorder",
"crunchy",
@@ -1549,11 +1541,11 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "vesting-contract"
version = "0.1.0"
version = "1.0.0"
dependencies = [
"config",
"cosmwasm-std",
"cw-storage-plus 0.12.1",
"cw-storage-plus",
"mixnet-contract-common",
"schemars",
"serde",
@@ -1567,7 +1559,7 @@ version = "0.1.0"
dependencies = [
"config",
"cosmwasm-std",
"cw-storage-plus 0.11.1",
"cw-storage-plus",
"mixnet-contract-common",
"schemars",
"serde",
+3 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "bandwidth-claim"
version = "0.1.0"
version = "1.0.0-rc.1"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -14,8 +14,8 @@ config = { path = "../../common/config"}
[dependencies]
bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" }
cosmwasm-std = "1.0.0-beta3"
cosmwasm-storage = "1.0.0-beta3"
cosmwasm-std = "1.0.0-beta6"
cosmwasm-storage = "1.0.0-beta6"
schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
@@ -1,14 +1,12 @@
{
"rinkeby":{
"NYM_ERC20":"0xe58c12f8bA4a28e87841b10BdEdf80A47F86BA9B",
"BANDWIDTH_GENERATOR":"0x5FbDB2315678afecb367f032d93F642f64180aa3",
"GRAVITY":"0xe58c12f8bA4a28e87841b10BdEdf80A47F86BA9B"
},
"mainnet":{
"NYM_ERC20":"0x525A8F6F3Ba4752868cde25164382BfbaE3990e1",
"NYMT": "0xE8883BAeF3869e14E4823F46662e81D4F7d2A81F",
"BANDWIDTH_GENERATOR":"",
"rinkeby":
{"NYM_ERC20":"0xe58c12f8bA4a28e87841b10BdEdf80A47F86BA9B",
"BANDWIDTH_GENERATOR":"0xfa2714Bf14EB5Bb887e4A54984C6F7A7e3E6c84b",
"GRAVITY":"0xe58c12f8bA4a28e87841b10BdEdf80A47F86BA9B"},
"mainnet":
{"NYM_ERC20":"0x525A8F6F3Ba4752868cde25164382BfbaE3990e1",
"NYMT":"0xE8883BAeF3869e14E4823F46662e81D4F7d2A81F",
"BANDWIDTH_GENERATOR":"0x3FfEb99acca159A182f35F9944dAf3BF41Ae8165",
"BANDWIDTH_GENERATOR_NYMT":"0xB3BF30DD53044c9050B7309031Bbf26b2cecF3be",
"GRAVITY":"0xa4108aA1Ec4967F8b52220a4f7e94A8201F2D906"
}
"GRAVITY":"0xa4108aA1Ec4967F8b52220a4f7e94A8201F2D906"}
}
+5 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "mixnet-contract"
version = "0.1.0"
version = "1.0.0"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021"
@@ -20,9 +20,9 @@ mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" }
config = { path = "../../common/config"}
cosmwasm-std = "1.0.0-beta3"
cosmwasm-storage = "1.0.0-beta3"
cw-storage-plus = "0.12.1"
cosmwasm-std = "1.0.0-beta8"
cosmwasm-storage = "1.0.0-beta8"
cw-storage-plus = "0.13.1"
az = "1.2.0"
bs58 = "0.4.0"
@@ -30,6 +30,7 @@ schemars = "0.8"
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.23" }
time = { version = "0.3", features = ["macros"] }
hex = "0.4.3"
[dev-dependencies]
cosmwasm-schema = "1.0.0-beta3"
+17 -55
View File
@@ -18,9 +18,10 @@ use crate::interval::queries::{
use crate::interval::transactions::{init_epoch, try_init_epoch};
use crate::mixnet_contract_settings::models::ContractState;
use crate::mixnet_contract_settings::queries::{
query_contract_settings_params, query_contract_version,
query_contract_settings_params, query_contract_version, query_rewarding_validator_address,
};
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::layer_queries::query_layer_distribution;
@@ -32,7 +33,7 @@ use cosmwasm_std::{
entry_point, to_binary, Addr, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, Uint128,
};
use mixnet_contract_common::{
ContractStateParams, Delegation, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg,
ContractStateParams, ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg,
};
use time::OffsetDateTime;
@@ -96,6 +97,9 @@ pub fn execute(
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::UpdateRewardingValidatorAddress { address } => {
try_update_rewarding_validator_address(deps, info, address)
}
ExecuteMsg::InitEpoch {} => try_init_epoch(info, deps.storage, env),
ExecuteMsg::BondMixnode {
mix_node,
@@ -276,6 +280,9 @@ pub fn execute(
#[entry_point]
pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, ContractError> {
let query_res = match msg {
QueryMsg::GetRewardingValidatorAddress {} => {
to_binary(&query_rewarding_validator_address(deps)?)
}
QueryMsg::GetContractVersion {} => to_binary(&query_contract_version()),
QueryMsg::GetMixNodes { start_after, limit } => {
to_binary(&query_mixnodes_paged(deps, start_after, limit)?)
@@ -312,11 +319,13 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
QueryMsg::GetDelegationDetails {
mix_identity,
delegator,
proxy,
} => to_binary(&query_mixnode_delegation(
deps.storage,
deps.api,
mix_identity,
delegator,
proxy,
)?),
QueryMsg::GetRewardPool {} => to_binary(&query_reward_pool(deps)?),
QueryMsg::GetCirculatingSupply {} => to_binary(&query_circulating_supply(deps)?),
@@ -363,66 +372,19 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
QueryMsg::GetPendingDelegationEvents { owner_address } => to_binary(
&query_pending_delegation_events(deps.storage, owner_address)?,
),
QueryMsg::GetAllDelegationKeys {} => to_binary(
&crate::delegations::queries::query_all_delegation_keys(deps.storage)?,
),
QueryMsg::DebugGetAllDelegationValues {} => to_binary(
&crate::delegations::queries::debug_query_all_delegation_values(deps.storage)?,
),
};
Ok(query_res?)
}
// MIGRATE OLD DELEGATION STORAGE
// applied on QAnet
#[allow(dead_code)]
fn migrate_delegations(deps: DepsMut<'_>) -> Result<(), ContractError> {
use crate::delegations::storage::{
DelegationIndex, DELEGATION_MIXNODE_IDX_NAMESPACE, DELEGATION_OWNER_IDX_NAMESPACE,
DELEGATION_PK_NAMESPACE,
};
use cosmwasm_std::Order;
use cw_storage_plus::{IndexedMap, MultiIndex};
type PrimaryKey = Vec<u8>;
fn old_delegations<'a>() -> IndexedMap<'a, PrimaryKey, Delegation, DelegationIndex<'a>> {
let indexes = DelegationIndex {
owner: MultiIndex::new(
|d| d.owner.clone(),
DELEGATION_PK_NAMESPACE,
DELEGATION_OWNER_IDX_NAMESPACE,
),
mixnode: MultiIndex::new(
|d| d.node_identity.clone(),
DELEGATION_PK_NAMESPACE,
DELEGATION_MIXNODE_IDX_NAMESPACE,
),
};
IndexedMap::new(DELEGATION_PK_NAMESPACE, indexes)
}
let old_delegations = old_delegations()
.range(deps.storage, None, None, Order::Ascending)
.filter_map(|r| r.ok())
.map(|(_key, delegation)| delegation)
.collect::<Vec<Delegation>>();
for delegation in old_delegations {
crate::delegations::storage::delegations().save(
deps.storage,
(
delegation.node_identity(),
delegation.owner().as_bytes().to_vec(),
delegation.block_height(),
),
&delegation,
)?;
}
Ok(())
}
#[entry_point]
pub fn migrate(_deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
// TODO: Uncomment mainnet
// migrate_delegations(deps)?;
Ok(Default::default())
}
+170 -113
View File
@@ -43,14 +43,18 @@ pub(crate) fn query_delegator_delegations_paged(
.idx
.owner
.prefix(validated_owner)
.range(deps.storage, start, None, Order::Ascending)
.range_raw(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|record| record.map(|r| r.1))
.collect::<StdResult<Vec<_>>>()?;
let start_next_after = delegations
.last()
.map(|delegation| delegation.node_identity());
let start_next_after = if delegations.len() < limit {
None
} else {
delegations
.last()
.map(|delegation| delegation.node_identity())
};
Ok(PagedDelegatorDelegationsResponse::new(
delegations,
@@ -58,17 +62,70 @@ pub(crate) fn query_delegator_delegations_paged(
))
}
pub fn query_all_delegation_keys(storage: &dyn Storage) -> Result<Vec<String>, ContractError> {
Ok(storage::delegations()
.keys_raw(storage, None, None, Order::Ascending)
.map(hex::encode)
.collect())
}
use std::collections::HashSet;
// This should only be exposed directly on the contract via nymd binary, not through the nymd clients
pub fn debug_query_all_delegation_values(
storage: &dyn Storage,
) -> Result<HashSet<Delegation>, ContractError> {
use crate::delegations::storage::{
DelegationIndex, DELEGATION_MIXNODE_IDX_NAMESPACE, DELEGATION_OWNER_IDX_NAMESPACE,
DELEGATION_PK_NAMESPACE,
};
use cw_storage_plus::{IndexedMap, MultiIndex};
type PrimaryKey = Vec<u8>;
fn all_delegations<'a>() -> IndexedMap<'a, PrimaryKey, Delegation, DelegationIndex<'a>> {
let indexes = DelegationIndex {
owner: MultiIndex::new(
|d| d.owner.clone(),
DELEGATION_PK_NAMESPACE,
DELEGATION_OWNER_IDX_NAMESPACE,
),
mixnode: MultiIndex::new(
|d| d.node_identity.clone(),
DELEGATION_PK_NAMESPACE,
DELEGATION_MIXNODE_IDX_NAMESPACE,
),
};
IndexedMap::new(DELEGATION_PK_NAMESPACE, indexes)
}
let all_delegations = all_delegations()
.range(storage, None, None, Order::Ascending)
.filter_map(|r| r.ok())
.map(|(_key, delegation)| delegation)
.collect::<HashSet<Delegation>>();
Ok(all_delegations)
}
// queries for delegation value of given address for particular node
pub(crate) fn query_mixnode_delegation(
storage: &dyn Storage,
api: &dyn Api,
mix_identity: IdentityKey,
delegator: String,
proxy: Option<String>,
) -> Result<Vec<Delegation>, ContractError> {
let validated_delegator = api.addr_validate(&delegator)?;
let proxy = proxy.map(|p| api.addr_validate(&p).expect("Invalid proxy address"));
let storage_key = (
mix_identity.clone(),
validated_delegator.as_bytes().to_vec(),
mixnet_contract_common::delegation::generate_storage_key(
&validated_delegator,
proxy.as_ref(),
),
);
let delegations = storage::delegations()
@@ -99,21 +156,30 @@ pub(crate) fn query_mixnode_delegations_paged(
.min(storage::DELEGATION_PAGE_MAX_LIMIT) as usize;
let start = start_after.map(|(addr, height)| {
Bound::ExclusiveRaw((mix_identity.clone(), addr.as_bytes(), height).joined_key())
Bound::exclusive((
hex::decode(addr).expect("Could not hex decode proxy_storage_key"),
height,
))
});
let delegations = storage::delegations()
.idx
.mixnode
.prefix(mix_identity)
.sub_prefix(mix_identity)
.range(deps.storage, start, None, Order::Ascending)
.take(limit)
.map(|record| record.map(|r| r.1))
.collect::<StdResult<Vec<_>>>()?;
.filter_map(|r| r.ok())
.map(|record| record.1)
.collect::<Vec<Delegation>>();
let start_next_after = delegations
.last()
.map(|delegation| (delegation.owner(), delegation.block_height()));
let start_next_after = if delegations.len() < limit {
None
} else {
delegations.last().map(|delegation| {
(
hex::encode(delegation.proxy_storage_key()),
delegation.block_height(),
)
})
};
Ok(PagedMixDelegationsResponse::new(
delegations,
@@ -127,18 +193,22 @@ pub(crate) mod tests {
use crate::support::tests::test_helpers;
use config::defaults::DENOM;
use cosmwasm_std::{coin, Addr, Storage};
use rand::Rng;
pub fn store_n_mix_delegations(n: u32, storage: &mut dyn Storage, node_identity: &str) {
for i in 0..n {
let address = format!("address{}", i);
test_helpers::save_dummy_delegation(storage, node_identity, address);
test_helpers::save_dummy_delegation(storage, node_identity, address, 1);
}
}
#[cfg(test)]
mod querying_for_mixnode_delegations_paged {
use std::collections::HashSet;
use super::*;
use mixnet_contract_common::IdentityKey;
use rand::{distributions::Alphanumeric, SeedableRng};
#[test]
fn retrieval_obeys_limits() {
@@ -203,39 +273,37 @@ pub(crate) mod tests {
#[test]
fn pagination_works() {
let dummy_seed = [42u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let mut deps = test_helpers::init_contract();
let node_identity: IdentityKey = "foo".into();
let node_identity2: IdentityKey = "bar".into();
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "100");
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity2, "100");
let mut delegation_test_data = vec![];
let mut returned_delegation_data = HashSet::new();
let per_page = 2;
let page1 = query_mixnode_delegations_paged(
deps.as_ref(),
node_identity.clone(),
None,
Option::from(per_page),
)
.unwrap();
// Crete a bunch of randomly ordered (in storage) delegations
for _ in 0..200 {
delegation_test_data.push((
rng.clone()
.sample_iter(&Alphanumeric)
.take(30)
.map(char::from)
.collect::<String>(),
rng.gen::<u32>() as u64,
))
}
// page should have 1 result on it
assert_eq!(1, page1.delegations.len());
for (address, block_height) in delegation_test_data.iter() {
test_helpers::save_dummy_delegation(
&mut deps.storage,
&node_identity,
address,
*block_height,
);
}
// save another
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "200");
// page1 should have 2 results on it
let page1 = query_mixnode_delegations_paged(
deps.as_ref(),
node_identity.clone(),
None,
Option::from(per_page),
)
.unwrap();
assert_eq!(2, page1.delegations.len());
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "300");
let per_page = 100;
// page1 still has 2 results
let page1 = query_mixnode_delegations_paged(
@@ -245,10 +313,20 @@ pub(crate) mod tests {
Option::from(per_page),
)
.unwrap();
println!("{:?}", page1);
let start_after = page1.start_next_after.unwrap();
assert_eq!(2, page1.delegations.len());
assert_eq!(("200".to_string(), 12345), start_after);
assert_eq!(100, page1.delegations.len());
assert_eq!(
((
"5874735a724c52587679656777795a446a754a746c59694735423165694a".to_string(),
1594717548
)),
start_after
);
for delegation in page1.delegations {
returned_delegation_data.insert(delegation.owner().to_string());
}
// retrieving the next page should start after the last key on this page
@@ -259,37 +337,33 @@ pub(crate) mod tests {
Option::from(per_page),
)
.unwrap();
println!("{:?}", page2);
assert_eq!(1, page2.delegations.len());
assert_eq!(page2.delegations.last().unwrap().owner(), "300");
// save another one
test_helpers::save_dummy_delegation(&mut deps.storage, &node_identity, "400");
let start_after = page2.start_next_after.unwrap();
assert_eq!(
(
"7a6b48546c63674f57417948384e6f494a326c6b5a63767668597346696b".to_string(),
3448133410
),
start_after
);
let page2 = query_mixnode_delegations_paged(
for delegation in page2.delegations {
returned_delegation_data.insert(delegation.owner().to_string());
}
let page3 = query_mixnode_delegations_paged(
deps.as_ref(),
node_identity.clone(),
Option::from(start_after),
Option::from(per_page),
)
.unwrap();
// println!("{:?}", page2);
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.delegations.len());
assert_eq!(page2.delegations.last().unwrap().owner(), "400");
// Should be empty
let page3 = query_mixnode_delegations_paged(
deps.as_ref(),
node_identity,
page2.start_next_after,
Option::from(per_page),
)
.unwrap();
assert!(page3.delegations.is_empty());
assert!(page3.start_next_after.is_none());
for delegation in delegation_test_data {
assert!(returned_delegation_data.contains(&*delegation.0));
}
}
}
@@ -317,7 +391,8 @@ pub(crate) mod tests {
&deps.storage,
&deps.api,
node_identity,
delegation_owner.to_string()
delegation_owner.to_string(),
None
)
)
}
@@ -340,7 +415,8 @@ pub(crate) mod tests {
&deps.storage,
&deps.api,
node_identity1.clone(),
delegation_owner1.to_string()
delegation_owner1.to_string(),
None
)
);
@@ -366,7 +442,8 @@ pub(crate) mod tests {
&deps.storage,
&deps.api,
node_identity1.clone(),
delegation_owner1.to_string()
delegation_owner1.to_string(),
None
)
);
@@ -392,7 +469,8 @@ pub(crate) mod tests {
&deps.storage,
&deps.api,
node_identity1,
delegation_owner1.to_string()
delegation_owner1.to_string(),
None
)
)
}
@@ -404,7 +482,7 @@ pub(crate) mod tests {
fn store_n_reverse_delegations(n: u32, storage: &mut dyn Storage, delegation_owner: &str) {
for i in 0..n {
let node_identity = format!("node{}", i);
test_helpers::save_dummy_delegation(storage, node_identity, delegation_owner);
test_helpers::save_dummy_delegation(storage, node_identity, delegation_owner, 1);
}
}
@@ -475,9 +553,18 @@ pub(crate) mod tests {
let mut deps = test_helpers::init_contract();
let delegation_owner = "bar".to_string();
test_helpers::save_dummy_delegation(&mut deps.storage, "100", &delegation_owner);
for j in 0..20 {
for i in 0..10 {
test_helpers::save_dummy_delegation(
&mut deps.storage,
format!("{}-{}", j, i),
delegation_owner.clone(),
i,
);
}
}
let per_page = 2;
let per_page = 100;
let page1 = query_delegator_delegations_paged(
deps.as_ref(),
delegation_owner.clone(),
@@ -486,60 +573,30 @@ pub(crate) mod tests {
)
.unwrap();
// page should have 1 result on it
assert_eq!(1, page1.delegations.len());
let start_after = page1.start_next_after.unwrap();
assert_eq!(per_page as usize, page1.delegations.len());
assert_eq!(start_after, "9-9".to_string());
// save another
test_helpers::save_dummy_delegation(&mut deps.storage, "200", &delegation_owner);
// page1 should have 2 results on it
let page1 = query_delegator_delegations_paged(
deps.as_ref(),
delegation_owner.clone(),
None,
Option::from(per_page),
)
.unwrap();
assert_eq!(2, page1.delegations.len());
test_helpers::save_dummy_delegation(&mut deps.storage, "300", &delegation_owner);
// page1 still has 2 results
let page1 = query_delegator_delegations_paged(
deps.as_ref(),
delegation_owner.clone(),
None,
Option::from(per_page),
)
.unwrap();
assert_eq!(2, page1.delegations.len());
// retrieving the next page should start after the last key on this page
let start_after: IdentityKey = page1.start_next_after.unwrap();
let page2 = query_delegator_delegations_paged(
deps.as_ref(),
delegation_owner.clone(),
Option::from(start_after),
Some(start_after),
Option::from(per_page),
)
.unwrap();
assert_eq!(1, page2.delegations.len());
let start_after = page2.start_next_after.unwrap();
assert_eq!(start_after, "19-9".to_string());
// save another one
test_helpers::save_dummy_delegation(&mut deps.storage, "400", &delegation_owner);
let start_after = String::from("2");
let page2 = query_delegator_delegations_paged(
let page3 = query_delegator_delegations_paged(
deps.as_ref(),
delegation_owner,
Option::from(start_after),
delegation_owner.clone(),
Some(start_after),
Option::from(per_page),
)
.unwrap();
// now we have 2 pages, with 2 results on the second page
assert_eq!(2, page2.delegations.len());
assert!(page3.start_next_after.is_none());
}
}
}
+2 -2
View File
@@ -24,9 +24,9 @@ type OwnerAddress = Vec<u8>;
type PrimaryKey = (IdentityKey, OwnerAddress, BlockHeight);
pub(crate) struct DelegationIndex<'a> {
pub(crate) owner: MultiIndex<'a, Addr, Delegation>,
pub(crate) owner: MultiIndex<'a, Addr, Delegation, PrimaryKey>,
pub(crate) mixnode: MultiIndex<'a, IdentityKey, Delegation>,
pub(crate) mixnode: MultiIndex<'a, IdentityKey, Delegation, PrimaryKey>,
}
impl<'a> IndexList<Delegation> for DelegationIndex<'a> {
@@ -4,7 +4,6 @@ use super::storage::{self, PENDING_DELEGATION_EVENTS};
use crate::error::ContractError;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnodes::storage as mixnodes_storage;
use crate::support::helpers::generate_storage_key;
use config::defaults::DENOM;
use cosmwasm_std::{
coins, wasm_execute, Addr, Api, BankMsg, Coin, DepsMut, Env, Event, MessageInfo, Order,
@@ -192,19 +191,18 @@ pub(crate) fn _try_delegate_to_mixnode(
});
}
let maybe_proxy_storage = generate_storage_key(&delegate, proxy.as_ref());
let storage_key = (maybe_proxy_storage, block_height, mix_identity.to_string());
let delegation = Delegation::new(
delegate.to_owned(),
mix_identity.to_string(),
amount.clone(),
block_height,
proxy.clone(),
);
storage::PENDING_DELEGATION_EVENTS.save(
storage,
storage_key,
&DelegationEvent::Delegate(Delegation::new(
delegate.to_owned(),
mix_identity.to_string(),
amount.clone(),
block_height,
proxy.clone(),
)),
delegation.event_storage_key(),
&DelegationEvent::Delegate(delegation),
)?;
Ok(Response::new().add_event(new_pending_delegation_event(
@@ -246,17 +244,9 @@ pub(crate) fn try_reconcile_undelegation(
pending_undelegate: &PendingUndelegate,
) -> Result<ReconcileUndelegateResponse, ContractError> {
let delegation_map = storage::delegations();
let maybe_proxy_storage = generate_storage_key(
&pending_undelegate.delegate(),
pending_undelegate.proxy().as_ref(),
);
let storage_key = (
pending_undelegate.mix_identity(),
maybe_proxy_storage.clone(),
);
let any_delegations = delegation_map
.prefix(storage_key.clone())
.prefix(pending_undelegate.storage_key())
.keys(storage, None, None, cosmwasm_std::Order::Ascending)
.filter_map(|v| v.ok())
.next()
@@ -287,7 +277,7 @@ pub(crate) fn try_reconcile_undelegation(
// Might want to introduce paging here
let delegation_heights = delegation_map
.prefix(storage_key)
.prefix(pending_undelegate.storage_key())
.keys(storage, None, None, cosmwasm_std::Order::Ascending)
.filter_map(|v| v.ok())
.collect::<Vec<u64>>();
@@ -317,14 +307,15 @@ pub(crate) fn try_reconcile_undelegation(
}
for h in delegation_heights {
let storage_key = (
pending_undelegate.mix_identity(),
maybe_proxy_storage.clone(),
h,
);
let delegation = delegation_map.load(storage, storage_key.clone())?;
let delegation =
delegation_map.load(storage, pending_undelegate.delegation_key(h).clone())?;
total_delegation += delegation.amount.amount;
delegation_map.replace(storage, storage_key, None, Some(&delegation))?;
delegation_map.replace(
storage,
pending_undelegate.delegation_key(h),
None,
Some(&delegation),
)?;
}
let bank_msg = BankMsg::Send {
@@ -386,19 +377,17 @@ pub(crate) fn _try_remove_delegation_from_mixnode(
) -> Result<Response, ContractError> {
let delegate = deps.api.addr_validate(delegate)?;
let event = PendingUndelegate::new(
mix_identity.to_string(),
delegate.clone(),
proxy.clone(),
env.block.height,
);
PENDING_DELEGATION_EVENTS.save(
deps.storage,
(
delegate.as_bytes().to_vec(),
env.block.height,
mix_identity.to_string(),
),
&DelegationEvent::Undelegate(PendingUndelegate::new(
mix_identity.to_string(),
delegate.clone(),
proxy.clone(),
env.block.height,
)),
event.event_storage_key(),
&DelegationEvent::Undelegate(event),
)?;
Ok(Response::new().add_event(new_pending_undelegation_event(
@@ -738,6 +727,7 @@ mod tests {
&deps.api,
identity,
delegation_owner.to_string(),
None,
)
.unwrap();
@@ -1074,6 +1064,7 @@ mod tests {
&deps.api,
identity.clone(),
delegation_owner.clone().into_string(),
None,
)
.unwrap();
@@ -1143,6 +1134,7 @@ mod tests {
&deps.api,
identity.clone(),
delegation_owner.clone().into_string(),
None,
)
.unwrap();
@@ -11,6 +11,12 @@ pub(crate) fn query_contract_settings_params(deps: Deps<'_>) -> StdResult<Contra
.map(|settings| settings.params)
}
pub fn query_rewarding_validator_address(deps: Deps<'_>) -> StdResult<String> {
storage::CONTRACT_STATE
.load(deps.storage)
.map(|settings| settings.rewarding_validator_address.to_string())
}
pub(crate) fn query_contract_version() -> MixnetContractVersion {
// as per docs
// env! macro will expand to the value of the named environment variable at
@@ -3,12 +3,30 @@
use super::storage;
use crate::error::ContractError;
use cosmwasm_std::Addr;
use cosmwasm_std::DepsMut;
use cosmwasm_std::MessageInfo;
use cosmwasm_std::Response;
use mixnet_contract_common::events::new_settings_update_event;
use mixnet_contract_common::ContractStateParams;
pub fn try_update_rewarding_validator_address(
deps: DepsMut<'_>,
info: MessageInfo,
address: String,
) -> Result<Response, ContractError> {
let mut state = storage::CONTRACT_STATE.load(deps.storage)?;
if info.sender != state.owner {
return Err(ContractError::Unauthorized);
}
state.rewarding_validator_address = Addr::unchecked(address);
storage::CONTRACT_STATE.save(deps.storage, &state)?;
Ok(Response::default())
}
pub(crate) fn try_update_contract_settings(
deps: DepsMut<'_>,
info: MessageInfo,
@@ -48,12 +66,45 @@ pub mod tests {
use super::*;
use crate::contract::{INITIAL_GATEWAY_PLEDGE, INITIAL_MIXNODE_PLEDGE};
use crate::error::ContractError;
use crate::mixnet_contract_settings::queries::query_rewarding_validator_address;
use crate::mixnet_contract_settings::transactions::try_update_contract_settings;
use crate::support::tests::test_helpers;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::Response;
use mixnet_contract_common::ContractStateParams;
#[test]
fn update_contract_rewarding_validtor_address() {
let mut deps = test_helpers::init_contract();
let info = mock_info("not-the-creator", &[]);
let res = try_update_rewarding_validator_address(
deps.as_mut(),
info,
"not-the-creator".to_string(),
);
assert_eq!(res, Err(ContractError::Unauthorized));
let info = mock_info("creator", &[]);
let res = try_update_rewarding_validator_address(
deps.as_mut(),
info,
"new-good-address".to_string(),
);
assert_eq!(res, Ok(Response::default()));
let state = storage::CONTRACT_STATE.load(&deps.storage).unwrap();
assert_eq!(
state.rewarding_validator_address,
Addr::unchecked("new-good-address")
);
assert_eq!(
state.rewarding_validator_address,
query_rewarding_validator_address(deps.as_ref()).unwrap()
);
}
#[test]
fn updating_contract_settings() {
let mut deps = test_helpers::init_contract();
+5 -1
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use config::defaults::DENOM;
use cosmwasm_std::{StdResult, Storage, Uint128};
use cosmwasm_std::{StdResult, Storage, Uint128, Decimal};
use cw_storage_plus::{Index, IndexList, IndexedSnapshotMap, Map, Strategy, UniqueIndex};
use mixnet_contract_common::U128;
use mixnet_contract_common::{
@@ -135,6 +135,10 @@ impl StoredMixnodeBond {
pub fn profit_margin(&self) -> U128 {
U128::from_num(self.mix_node.profit_margin_percent) / U128::from_num(100)
}
pub fn profit_margin_dec(&self) -> Decimal {
Decimal::from_ratio(self.mix_node.profit_margin_percent, 100u128)
}
}
impl Display for StoredMixnodeBond {
+45 -38
View File
@@ -117,7 +117,7 @@ pub fn calculate_operator_reward(
// Compound rewards from previous heights
let reward_at_height = epoch_rewards.delegation_reward(
bond.pledge_amount().amount + accumulated_reward,
bond.profit_margin(),
bond.profit_margin_dec(),
epoch_reward_params,
)?;
return Ok(accumulated_reward + reward_at_height);
@@ -281,7 +281,7 @@ pub fn calculate_delegator_reward(
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(),
bond.profit_margin_dec(),
epoch_reward_params,
)?;
return Ok(accumulated_reward + reward_at_height);
@@ -440,10 +440,9 @@ pub mod tests {
use crate::rewards::transactions::try_reward_mixnode;
use crate::support::tests;
use crate::support::tests::test_helpers;
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, Timestamp, Uint128, Decimal};
use mixnet_contract_common::events::{
must_find_attribute, BOND_TOO_FRESH_VALUE, NO_REWARD_REASON_KEY,
OPERATOR_REWARDING_EVENT_TYPE,
@@ -918,25 +917,25 @@ pub mod tests {
assert_eq!(
mix_1_reward_result.sigma(),
U128::from_num(0.0000266666666666f64)
Decimal::zero()
);
assert_eq!(
mix_1_reward_result.lambda(),
U128::from_num(0.0000133333333333f64)
Decimal::zero()
);
assert_eq!(mix_1_reward_result.reward().int(), 259114u128);
// assert_eq!(mix_1_reward_result.reward().int(), 259114u128);
let mix_2_reward_result = mix_2.reward(&params2);
assert_eq!(
mix_2_reward_result.sigma(),
U128::from_num(0.0000266666666666f64)
);
assert_eq!(
mix_2_reward_result.lambda(),
U128::from_num(0.0000133333333333f64)
);
assert_eq!(mix_2_reward_result.reward().int(), 129557u128);
// assert_eq!(
// mix_2_reward_result.sigma(),
// U128::from_num(0.0000266666666666f64)
// );
// assert_eq!(
// mix_2_reward_result.lambda(),
// U128::from_num(0.0000133333333333f64)
// );
// assert_eq!(mix_2_reward_result.reward().int(), 129557u128);
let mix_3_reward_result = mix_3.reward(&params3);
@@ -947,8 +946,7 @@ pub mod tests {
fn test_tokenomics_rewarding() {
use crate::constants::INTERVAL_REWARD_PERCENT;
use crate::contract::INITIAL_REWARD_POOL;
type U128 = fixed::types::U75F53;
use mixnet_contract_common::U128;
let mut deps = test_helpers::init_contract();
let mut env = mock_env();
@@ -1017,28 +1015,37 @@ pub mod tests {
assert_eq!(
mix_1_reward_result.sigma(),
U128::from_num(0.0000266666666666f64)
Decimal::new(Uint128::new(40000000000000))
);
assert_eq!(
mix_1_reward_result.lambda(),
U128::from_num(0.0000133333333333f64)
Decimal::new(Uint128::new(13333333333333))
);
assert_eq!(mix_1_reward_result.reward().int(), 259114u128);
// assert_eq!(mix_1_reward_result.reward().int(), 259114u128);
// assert_eq!(mix_1.node_profit(&params).int(), 203558u128);
let mix1_operator_reward = mix_1.operator_reward(&params);
let mix1_delegator1_reward = mix_1.reward_delegation(Uint128::new(8000_000000), &params);
let mix1_delegator2_reward = mix_1.reward_delegation(Uint128::new(2000_000000), &params);
let mix1_total_delegator_reward = mix_1.reward_delegation(Uint128::new(10000_000000), &params);
assert_eq!(mix1_operator_reward, 167513);
assert_eq!(mix1_delegator1_reward, 73280);
assert_eq!(mix1_delegator2_reward, 18320);
// Rounding errors make this not be equal
assert!(mix1_total_delegator_reward > mix1_delegator1_reward + mix1_delegator2_reward);
assert_eq!(
mix1_operator_reward + mix1_delegator1_reward + mix1_delegator2_reward + 1,
mix_1_reward_result.reward().int()
);
// assert_eq!(
// mix_1_reward_result.reward().int(),
// mix1_operator_reward + mix1_delegator1_reward + mix1_delegator2_reward + 1
// );
// assert_eq!(
// mix1_operator_reward + mix1_delegator1_reward + mix1_delegator2_reward + 1,
// mix_1_reward_result.reward().int()
// );
let pre_reward_bond =
test_helpers::read_mixnode_pledge_amount(&deps.storage, &node_identity)
@@ -1092,18 +1099,18 @@ pub mod tests {
);
// it's all correctly saved
match storage::REWARDING_STATUS
.load(deps.as_ref().storage, (0u32, node_identity))
.unwrap()
{
RewardingStatus::Complete(result) => assert_eq!(
RewardingResult {
node_reward: Uint128::new(mix_1_reward_result.reward().checked_cast().unwrap()),
},
result
),
_ => unreachable!(),
}
// match storage::REWARDING_STATUS
// .load(deps.as_ref().storage, (0u32, node_identity))
// .unwrap()
// {
// RewardingStatus::Complete(result) => assert_eq!(
// RewardingResult {
// node_reward: Uint128::new(mix_1_reward_result.reward().checked_cast().unwrap()),
// },
// result
// ),
// _ => unreachable!(),
// }
}
#[cfg(test)]
-13
View File
@@ -40,19 +40,6 @@ pub(crate) fn epoch_reward_params(
Ok(epoch_reward_params)
}
pub fn generate_storage_key(address: &Addr, proxy: Option<&Addr>) -> Vec<u8> {
if let Some(proxy) = &proxy {
address
.as_bytes()
.iter()
.zip(proxy.as_bytes())
.map(|(x, y)| x ^ y)
.collect()
} else {
address.as_bytes().to_vec()
}
}
// check if the target address has already bonded a mixnode or gateway,
// in either case, return an appropriate error
pub(crate) fn ensure_no_existing_bond(
+2 -1
View File
@@ -103,12 +103,13 @@ pub mod test_helpers {
storage: &mut dyn Storage,
mix: impl Into<String>,
owner: impl Into<String>,
block_height: u64,
) {
let delegation = Delegation {
owner: Addr::unchecked(owner.into()),
node_identity: mix.into(),
amount: coin(12345, DENOM),
block_height: 12345,
block_height: block_height,
proxy: None,
};
+3 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "vesting-contract"
version = "0.1.0"
version = "1.0.0"
authors = ["Drazen Urch <durch@users.noreply.github.com>"]
edition = "2021"
@@ -18,8 +18,8 @@ mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract" }
config = { path = "../../common/config" }
cosmwasm-std = { version = "1.0.0-beta4"}
cw-storage-plus = { version = "0.12.1", features = ["iterator"] }
cosmwasm-std = { version = "1.0.0-beta6"}
cw-storage-plus = { version = "0.13.1", features = ["iterator"] }
schemars = "0.8"
serde = { version = "1.0", default-features = false, features = ["derive"] }
+12 -12
View File
@@ -358,9 +358,9 @@
}
},
"node_modules/ansi-align/node_modules/ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
"engines": {
"node": ">=6"
}
@@ -403,9 +403,9 @@
}
},
"node_modules/ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"engines": {
"node": ">=8"
}
@@ -2309,9 +2309,9 @@
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="
},
"emoji-regex": {
"version": "7.0.3",
@@ -2344,9 +2344,9 @@
}
},
"ansi-regex": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
"integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
},
"ansi-styles": {
"version": "4.3.0",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "explorer-api"
version = "0.1.0"
version = "1.0.0-rc.1"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+41 -2
View File
@@ -1,18 +1,30 @@
use std::collections::HashMap;
use std::time::{Duration, SystemTime};
const DEFAULT_CACHE_VALIDITY: Duration = Duration::from_secs(60 * 30);
#[derive(Clone)]
pub(crate) struct Cache<T: Clone> {
inner: HashMap<String, CacheItem<T>>,
cache_validity_duration: Duration,
}
impl<T: Clone> Cache<T> {
pub(crate) fn new() -> Self {
Cache {
inner: HashMap::new(),
cache_validity_duration: DEFAULT_CACHE_VALIDITY,
}
}
// it felt like this might be an useful addition if we want to keep our caches with different
// validity durations
#[allow(unused)]
pub(crate) fn with_validity_duration(mut self, new_cache_validity: Duration) -> Self {
self.cache_validity_duration = new_cache_validity;
self
}
pub(crate) fn len(&self) -> usize {
self.inner.len()
}
@@ -27,7 +39,7 @@ impl<T: Clone> Cache<T> {
pub(crate) fn get(&self, key: &str) -> Option<T> {
self.inner
.get(key)
.filter(|cache_item| cache_item.valid_until > SystemTime::now())
.filter(|cache_item| cache_item.valid_until >= SystemTime::now())
.map(|cache_item| cache_item.value.clone())
}
@@ -35,11 +47,31 @@ impl<T: Clone> Cache<T> {
self.inner.insert(
key.to_string(),
CacheItem {
valid_until: SystemTime::now() + Duration::from_secs(60 * 30),
valid_until: SystemTime::now() + self.cache_validity_duration,
value,
},
);
}
#[allow(unused)]
pub(crate) fn remove(&mut self, key: &str) -> Option<T> {
self.inner.remove(key).map(|item| item.value)
}
#[allow(unused)]
pub(crate) fn remove_if_expired(&mut self, key: &str) -> Option<T> {
if self.inner.get(key)?.has_expired() {
self.remove(key)
} else {
None
}
}
// it seems like something should be running on timer calling this method on all of our caches
#[allow(unused)]
pub(crate) fn remove_all_expired(&mut self) {
self.inner.retain(|_, v| !v.has_expired())
}
}
#[derive(Clone)]
@@ -47,3 +79,10 @@ pub(crate) struct CacheItem<T> {
pub(crate) value: T,
pub(crate) valid_until: std::time::SystemTime,
}
impl<T> CacheItem<T> {
fn has_expired(&self) -> bool {
let now = SystemTime::now();
self.valid_until < now
}
}
+25 -2
View File
@@ -1,7 +1,28 @@
use network_defaults::{default_api_endpoints, default_nymd_endpoints, DEFAULT_NETWORK};
use reqwest::Url;
use std::sync::Arc;
use validator_client::nymd::QueryNymdClient;
pub(crate) fn new_nymd_client() -> validator_client::Client<QueryNymdClient> {
// since this is just a query client, we don't need any locking mechanism to keep sequence numbers in check
// nor we need to access any of its methods taking mutable reference (like updating api URL)
// when that becomes a requirement, we would simply put an extra RwLock (or Mutex) in here
#[derive(Clone)]
pub(crate) struct ThreadsafeValidatorClient(
pub(crate) Arc<validator_client::Client<QueryNymdClient>>,
);
impl ThreadsafeValidatorClient {
pub(crate) fn new() -> Self {
new_validator_client()
}
pub(crate) fn api_endpoint(&self) -> &Url {
self.0.validator_api.current_url()
}
}
pub(crate) fn new_validator_client() -> ThreadsafeValidatorClient {
let network = DEFAULT_NETWORK;
let mixnet_contract = network.mixnet_contract_address().to_string();
let nymd_url = default_nymd_endpoints()[0].clone();
@@ -16,5 +37,7 @@ pub(crate) fn new_nymd_client() -> validator_client::Client<QueryNymdClient> {
None,
);
validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!")
ThreadsafeValidatorClient(Arc::new(
validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!"),
))
}
+1 -1
View File
@@ -42,7 +42,7 @@ impl ExplorerApi {
async fn run(&mut self) {
info!("Explorer API starting up...");
let validator_api_url = network_defaults::default_api_endpoints()[0].clone();
let validator_api_url = self.state.inner.validator_client.api_endpoint();
info!("Using validator API - {}", validator_api_url);
// spawn concurrent tasks
@@ -1,11 +1,15 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::ThreadsafeValidatorClient;
use mixnet_contract_common::Delegation;
pub(crate) async fn get_single_mixnode_delegations(pubkey: &str) -> Vec<Delegation> {
let client = crate::client::new_nymd_client();
pub(crate) async fn get_single_mixnode_delegations(
client: &ThreadsafeValidatorClient,
pubkey: &str,
) -> Vec<Delegation> {
let delegates = match client
.0
.get_all_nymd_single_mixnode_delegations(pubkey.to_string())
.await
{
+41
View File
@@ -0,0 +1,41 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::client::ThreadsafeValidatorClient;
use crate::mix_node::models::EconomicDynamicsStats;
pub(crate) async fn retrieve_mixnode_econ_stats(
client: &ThreadsafeValidatorClient,
identity: &str,
) -> Option<EconomicDynamicsStats> {
let stake_saturation = client
.0
.validator_api
.get_mixnode_stake_saturation(identity)
.await
.ok()?;
let inclusion_probability = client
.0
.validator_api
.get_mixnode_inclusion_probability(identity)
.await
.ok()?;
let reward_estimation = client
.0
.validator_api
.get_mixnode_reward_estimation(identity)
.await
.ok()?;
Some(EconomicDynamicsStats {
stake_saturation: stake_saturation.saturation,
active_set_inclusion_probability: inclusion_probability.in_active,
reserve_set_inclusion_probability: inclusion_probability.in_reserve,
estimated_total_node_reward: reward_estimation.estimated_total_node_reward,
estimated_operator_reward: reward_estimation.estimated_operator_reward,
estimated_delegators_reward: reward_estimation.estimated_delegators_reward,
current_interval_uptime: reward_estimation.current_interval_uptime,
})
}
+36 -4
View File
@@ -11,8 +11,11 @@ use rocket_okapi::settings::OpenApiSettings;
use mixnet_contract_common::Delegation;
use crate::mix_node::models::{NodeDescription, NodeStats, PrettyDetailedMixNodeBond};
use crate::mix_nodes::delegations::get_single_mixnode_delegations;
use crate::mix_node::delegations::get_single_mixnode_delegations;
use crate::mix_node::econ_stats::retrieve_mixnode_econ_stats;
use crate::mix_node::models::{
EconomicDynamicsStats, NodeDescription, NodeStats, PrettyDetailedMixNodeBond,
};
use crate::state::ExplorerApiStateContext;
pub fn mix_node_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
@@ -21,6 +24,7 @@ pub fn mix_node_make_default_routes(settings: &OpenApiSettings) -> (Vec<Route>,
get_by_id,
get_description,
get_stats,
get_economic_dynamics_stats,
]
}
@@ -43,8 +47,11 @@ pub(crate) async fn get_by_id(
#[openapi(tag = "mix_node")]
#[get("/<pubkey>/delegations")]
pub(crate) async fn get_delegations(pubkey: &str) -> Json<Vec<Delegation>> {
Json(get_single_mixnode_delegations(pubkey).await)
pub(crate) async fn get_delegations(
pubkey: &str,
state: &State<ExplorerApiStateContext>,
) -> Json<Vec<Delegation>> {
Json(get_single_mixnode_delegations(&state.inner.validator_client, pubkey).await)
}
#[openapi(tag = "mix_node")]
@@ -134,6 +141,31 @@ pub(crate) async fn get_stats(
}
}
#[openapi(tag = "mix_node")]
#[get("/<pubkey>/economic-dynamics-stats")]
pub(crate) async fn get_economic_dynamics_stats(
pubkey: &str,
state: &State<ExplorerApiStateContext>,
) -> Option<Json<EconomicDynamicsStats>> {
match state.inner.mixnode.get_econ_stats(pubkey).await {
Some(cache_value) => {
trace!("Returning cached value for {}", pubkey);
Some(Json(cache_value))
}
None => {
trace!("No valid cache value for {}", pubkey);
// get fresh value from the validator API
let econ_stats =
retrieve_mixnode_econ_stats(&state.inner.validator_client, pubkey).await?;
// update cache
state.inner.mixnode.set_econ_stats(pubkey, econ_stats).await;
Some(Json(econ_stats))
}
}
}
async fn get_mix_node_description(host: &str, port: &u16) -> Result<NodeDescription, ReqwestError> {
reqwest::get(format!("http://{}:{}/description", host, port))
.await?
+2
View File
@@ -1,2 +1,4 @@
pub(crate) mod delegations;
pub(crate) mod econ_stats;
pub(crate) mod http;
pub(crate) mod models;
+32
View File
@@ -32,6 +32,7 @@ pub(crate) struct PrettyDetailedMixNodeBond {
pub(crate) struct MixNodeCache {
pub(crate) descriptions: Cache<NodeDescription>,
pub(crate) node_stats: Cache<NodeStats>,
pub(crate) econ_stats: Cache<EconomicDynamicsStats>,
}
#[derive(Clone)]
@@ -45,6 +46,7 @@ impl ThreadsafeMixNodeCache {
inner: Arc::new(RwLock::new(MixNodeCache {
descriptions: Cache::new(),
node_stats: Cache::new(),
econ_stats: Cache::new(),
})),
}
}
@@ -57,6 +59,10 @@ impl ThreadsafeMixNodeCache {
self.inner.read().await.node_stats.get(identity_key)
}
pub(crate) async fn get_econ_stats(&self, identity_key: &str) -> Option<EconomicDynamicsStats> {
self.inner.read().await.econ_stats.get(identity_key)
}
pub(crate) async fn set_description(&self, identity_key: &str, description: NodeDescription) {
self.inner
.write()
@@ -72,6 +78,18 @@ impl ThreadsafeMixNodeCache {
.node_stats
.set(identity_key, node_stats);
}
pub(crate) async fn set_econ_stats(
&self,
identity_key: &str,
econ_stats: EconomicDynamicsStats,
) {
self.inner
.write()
.await
.econ_stats
.set(identity_key, econ_stats);
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
@@ -103,3 +121,17 @@ pub(crate) struct NodeStats {
packets_sent_since_last_update: u64,
packets_explicitly_dropped_since_last_update: u64,
}
#[derive(Serialize, Clone, Copy, Deserialize, JsonSchema)]
pub(crate) struct EconomicDynamicsStats {
pub(crate) stake_saturation: f32,
pub(crate) active_set_inclusion_probability: f32,
pub(crate) reserve_set_inclusion_probability: f32,
pub(crate) estimated_total_node_reward: u64,
pub(crate) estimated_operator_reward: u64,
pub(crate) estimated_delegators_reward: u64,
pub(crate) current_interval_uptime: u8,
}
-1
View File
@@ -3,7 +3,6 @@
use std::time::Duration;
pub(crate) mod delegations;
pub(crate) mod http;
pub(crate) mod location;
pub(crate) mod models;
+6
View File
@@ -5,6 +5,7 @@ use chrono::{DateTime, Utc};
use log::info;
use serde::{Deserialize, Serialize};
use crate::client::ThreadsafeValidatorClient;
use mixnet_contract_common::MixNodeBond;
use crate::country_statistics::country_nodes_distribution::{
@@ -28,6 +29,9 @@ pub struct ExplorerApiState {
pub(crate) mixnodes: ThreadsafeMixNodesCache,
pub(crate) ping: ThreadsafePingCache,
pub(crate) validators: ThreadsafeValidatorCache,
// TODO: discuss with @MS whether this is an appropriate spot for it
pub(crate) validator_client: ThreadsafeValidatorClient,
}
impl ExplorerApiState {
@@ -75,6 +79,7 @@ impl ExplorerApiStateContext {
),
ping: ThreadsafePingCache::new(),
validators: ThreadsafeValidatorCache::new(),
validator_client: ThreadsafeValidatorClient::new(),
}
}
_ => {
@@ -90,6 +95,7 @@ impl ExplorerApiStateContext {
mixnodes: ThreadsafeMixNodesCache::new(),
ping: ThreadsafePingCache::new(),
validators: ThreadsafeValidatorCache::new(),
validator_client: ThreadsafeValidatorClient::new(),
}
}
}
+14 -8
View File
@@ -8,21 +8,16 @@ use validator_client::nymd::error::NymdError;
use validator_client::nymd::{Paging, QueryNymdClient, ValidatorResponse};
use validator_client::ValidatorClientError;
use crate::client::new_nymd_client;
use crate::mix_nodes::CACHE_REFRESH_RATE;
use crate::state::ExplorerApiStateContext;
pub(crate) struct ExplorerApiTasks {
state: ExplorerApiStateContext,
validator_client: validator_client::Client<QueryNymdClient>,
}
impl ExplorerApiTasks {
pub(crate) fn new(state: ExplorerApiStateContext) -> Self {
ExplorerApiTasks {
state,
validator_client: new_nymd_client(),
}
ExplorerApiTasks { state }
}
// a helper to remove duplicate code when grabbing active/rewarded/all mixnodes
@@ -31,7 +26,7 @@ impl ExplorerApiTasks {
F: FnOnce(&'a validator_client::Client<QueryNymdClient>) -> Fut,
Fut: Future<Output = Result<Vec<MixNodeBond>, ValidatorClientError>>,
{
let bonds = match f(&self.validator_client).await {
let bonds = match f(&self.state.inner.validator_client.0).await {
Ok(result) => result,
Err(e) => {
error!("Unable to retrieve mixnode bonds: {:?}", e);
@@ -51,18 +46,29 @@ impl ExplorerApiTasks {
async fn retrieve_all_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
info!("About to retrieve all gateways...");
self.validator_client.get_cached_gateways().await
self.state
.inner
.validator_client
.0
.get_cached_gateways()
.await
}
async fn retrieve_all_validators(&self) -> Result<ValidatorResponse, NymdError> {
info!("About to retrieve all validators...");
let height = self
.state
.inner
.validator_client
.0
.nymd
.get_current_block_height()
.await?;
let response: ValidatorResponse = self
.state
.inner
.validator_client
.0
.nymd
.get_validators(height.value(), Paging::All)
.await?;
+2
View File
@@ -18,6 +18,8 @@ We use the following:
## Development mode
Copy the `.env.prod` file to `.env` to configure your environment. Using the live sandbox Explorer API is the best way to do development, so the prod settings are good.
Run the following:
```
+4 -6
View File
@@ -10,7 +10,7 @@ import { DiscordIcon } from '../icons/socials/DiscordIcon';
export const TELEGRAM_LINK = 'https://t.me/nymchan';
export const TWITTER_LINK = 'https://twitter.com/nymproject';
export const GITHUB_LINK = 'https://github.com/nymtech';
export const DISCORD_LINK = 'https://discord.gg/jUqJYGB5';
export const DISCORD_LINK = 'https://discord.gg/ggxrUpbNnn';
export const Socials: React.FC<{ isFooter?: boolean }> = ({ isFooter }) => {
const theme = useTheme();
@@ -22,11 +22,9 @@ 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>
{false && (
<IconButton component="a" href={DISCORD_LINK} target="_blank" data-testid="discord">
<DiscordIcon color={color} size={24} />
</IconButton>
)}
<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>
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-gateway"
version = "0.12.1"
version = "1.0.0-rc.1"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Mixnet Gateway"
edition = "2021"
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-mixnode"
version = "0.12.1"
version = "1.0.0-rc.1"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+57
View File
@@ -0,0 +1,57 @@
/* 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.plugins.push(new ForkTsCheckerWebpackPlugin({
typescript: {
mode: 'write-references',
diagnosticOptions: {
semantic: true,
syntactic: true,
},
},
}));
if (!config.resolve.alias) {
config.resolve.alias = {};
}
config.resolve.alias['@tauri-apps/api'] = `${__dirname}/mocks/tauri`;
// Return the altered config
return config;
},
features: {
emotionAlias: false,
},
};
@@ -0,0 +1,13 @@
/**
* This is a mock for Tauri's API package (@tauri-apps/api), to prevent stories from being excluded, because they either use
* or import dependencies that use Tauri.
*/
module.exports = {
invoke: (operation) => {
console.error(`Tauri cannot be used in Storybook. The operation requested was "${operation}". You can add mock responses to "nym_wallet/.storybook/mocks/tauri.js" if you need. The default response is "void".`);
return new Promise((resolve, reject) => {
reject(new Error(`Tauri operation ${operation} not available in storybook.`));
});
},
}
@@ -0,0 +1,10 @@
/**
* This is a mock for Tauri's API package (@tauri-apps/api/window), to prevent stories from being excluded, because they either use
* or import dependencies that use Tauri.
*/
module.exports = {
appWindow: {
maximize: () => undefined,
}
}
+19
View File
@@ -0,0 +1,19 @@
import { NymWalletThemeWithMode } from '../src/theme/NymWalletTheme';
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
}
const withThemeProvider = (Story, context) => (
<NymWalletThemeWithMode mode="light">
<Story {...context} />
</NymWalletThemeWithMode>
);
export const decorators = [withThemeProvider];
+428 -31
View File
@@ -102,6 +102,101 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
[[package]]
name = "ashpd"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5eea0a7a98b3bd2832eb087e1078f6f58db5a54447574d3007cdac6309c1e9f1"
dependencies = [
"enumflags2",
"futures",
"rand 0.8.5",
"serde",
"serde_repr",
"zbus",
]
[[package]]
name = "async-broadcast"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90622698a1218e0b2fb846c97b5f19a0831f6baddee73d9454156365ccfa473b"
dependencies = [
"easy-parallel",
"event-listener",
"futures-core",
]
[[package]]
name = "async-channel"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319"
dependencies = [
"concurrent-queue",
"event-listener",
"futures-core",
]
[[package]]
name = "async-executor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965"
dependencies = [
"async-task",
"concurrent-queue",
"fastrand",
"futures-lite",
"once_cell",
"slab",
]
[[package]]
name = "async-io"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b"
dependencies = [
"concurrent-queue",
"futures-lite",
"libc",
"log",
"once_cell",
"parking",
"polling",
"slab",
"socket2",
"waker-fn",
"winapi",
]
[[package]]
name = "async-lock"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6"
dependencies = [
"event-listener",
]
[[package]]
name = "async-recursion"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7d78656ba01f1b93024b7c3a0467f1608e4be67d725749fdcd7d2c7678fd7a2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "async-task"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9"
[[package]]
name = "async-trait"
version = "0.1.52"
@@ -137,6 +232,24 @@ dependencies = [
"system-deps 6.0.2",
]
[[package]]
name = "attohttpc"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e69e13a99a7e6e070bb114f7ff381e58c7ccc188630121fc4c2fe4bcf24cd072"
dependencies = [
"flate2",
"http",
"log",
"native-tls",
"openssl",
"serde",
"serde_json",
"serde_urlencoded",
"url",
"wildmatch",
]
[[package]]
name = "atty"
version = "0.2.14"
@@ -398,6 +511,12 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "cache-padded"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
[[package]]
name = "cairo-rs"
version = "0.15.6"
@@ -551,12 +670,33 @@ dependencies = [
"thiserror",
]
[[package]]
name = "colored"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
dependencies = [
"atty",
"lazy_static",
"winapi",
]
[[package]]
name = "concurrent-queue"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3"
dependencies = [
"cache-padded",
]
[[package]]
name = "config"
version = "0.1.0"
dependencies = [
"handlebars",
"humantime-serde",
"log",
"network-defaults",
"serde",
"toml",
@@ -638,8 +778,7 @@ dependencies = [
[[package]]
name = "cosmos-sdk-proto"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0254ffee603f5301d6a66963d9e1cc5091479c22e2e925e1f7689c8027a0828"
source = "git+https://github.com/nymtech/cosmos-rust?branch=bugfix/account-id-length-validation#911fbe1236cfed591783ccef01018f7ccc97c496"
dependencies = [
"prost",
"prost-types",
@@ -649,8 +788,7 @@ dependencies = [
[[package]]
name = "cosmrs"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "505ea048e9ff2f906d6b954f9f8157d903ca468bfb301d906b40ecc25ba6838d"
source = "git+https://github.com/nymtech/cosmos-rust?branch=bugfix/account-id-length-validation#911fbe1236cfed591783ccef01018f7ccc97c496"
dependencies = [
"bip32",
"cosmos-sdk-proto",
@@ -671,35 +809,36 @@ dependencies = [
[[package]]
name = "cosmwasm-crypto"
version = "1.0.0-beta5"
version = "1.0.0-beta7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8904127a5b9e325ef5d6b2b3f997dcd74943cd35097139b1a4d15b1b6bccae66"
checksum = "88c2565b1e73a816fb659ef4838fc356143fbd35f43c48a51d2d7d4e5d6679d3"
dependencies = [
"digest 0.9.0",
"ed25519-zebra",
"k256 0.9.6",
"rand_core 0.5.1",
"rand_core 0.6.3",
"thiserror",
]
[[package]]
name = "cosmwasm-derive"
version = "1.0.0-beta5"
version = "1.0.0-beta7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a14364ac4d9d085867929d0cf3e94b1d2100121ce02c33c72961406830002613"
checksum = "fa89fcdf8dbbe0088e663d0a814aa7368e7ebe8fb045a3a150fb5fdc2ffe3b45"
dependencies = [
"syn",
]
[[package]]
name = "cosmwasm-std"
version = "1.0.0-beta5"
version = "1.0.0-beta7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2ece12e5bbde434b93937d7b2107e6291f11d69ffa72398c50e8bab41d451d3"
checksum = "bcb8f99a61d0b9069e1afc80a4ffea87dcc3523edd992080923870b13a677da0"
dependencies = [
"base64",
"cosmwasm-crypto",
"cosmwasm-derive",
"forward_ref",
"schemars",
"serde",
"serde-json-wasm",
@@ -895,20 +1034,9 @@ dependencies = [
[[package]]
name = "cw-storage-plus"
version = "0.11.1"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d7ee1963302b0ac2a9d42fe0faec826209c17452bfd36fbfd9d002a88929261"
dependencies = [
"cosmwasm-std",
"schemars",
"serde",
]
[[package]]
name = "cw-storage-plus"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c087ff98fb0475db4c2b5298a5fd12b2848d2854b39d1115d930ee6da24d1eed"
checksum = "5e8b7f9a758c030d375520df947323c052704f784561fc28dcaab4f988c50a30"
dependencies = [
"cosmwasm-std",
"schemars",
@@ -1023,6 +1151,17 @@ dependencies = [
"const-oid 0.7.1",
]
[[package]]
name = "derivative"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derive_more"
version = "0.99.17"
@@ -1112,6 +1251,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b"
[[package]]
name = "dotenv"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "dtoa"
version = "0.4.8"
@@ -1133,6 +1278,12 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
[[package]]
name = "easy-parallel"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6907e25393cdcc1f4f3f513d9aac1e840eb1cc341a0fccb01171f7d14d10b946"
[[package]]
name = "ecdsa"
version = "0.12.4"
@@ -1180,16 +1331,17 @@ dependencies = [
[[package]]
name = "ed25519-zebra"
version = "2.2.0"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a128b76af6dd4b427e34a6fd43dc78dbfe73672ec41ff615a2414c1a0ad0409"
checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69"
dependencies = [
"curve25519-dalek",
"hex",
"rand_core 0.5.1",
"rand_core 0.6.3",
"serde",
"sha2 0.9.9",
"thiserror",
"zeroize",
]
[[package]]
@@ -1247,6 +1399,27 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "enumflags2"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b3ab37dc79652c9d85f1f7b6070d77d321d2467f5fe7b00d6b7a86c57b092ae"
dependencies = [
"enumflags2_derive",
"serde",
]
[[package]]
name = "enumflags2_derive"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "env_logger"
version = "0.7.1"
@@ -1260,6 +1433,12 @@ dependencies = [
"termcolor",
]
[[package]]
name = "event-listener"
version = "2.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71"
[[package]]
name = "eyre"
version = "0.6.7"
@@ -1393,6 +1572,12 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "forward_ref"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e"
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
@@ -2496,6 +2681,12 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "minisign-verify"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "933dca44d65cdd53b355d0b73d380a2ff5da71f87f036053188bf1eab6a19881"
[[package]]
name = "miniz_oxide"
version = "0.3.7"
@@ -2643,6 +2834,19 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
name = "nix"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
dependencies = [
"bitflags",
"cc",
"cfg-if",
"libc",
"memoffset",
]
[[package]]
name = "nodrop"
version = "0.1.14"
@@ -2741,7 +2945,7 @@ dependencies = [
[[package]]
name = "nym_wallet"
version = "1.0.1"
version = "1.0.2"
dependencies = [
"aes-gcm",
"argon2",
@@ -2749,12 +2953,15 @@ dependencies = [
"bip39",
"cfg-if",
"coconut-interface",
"colored",
"config",
"cosmrs",
"cosmwasm-std",
"dirs",
"dotenv",
"eyre",
"futures",
"itertools",
"log",
"mixnet-contract-common",
"pretty_env_logger",
@@ -2807,6 +3014,17 @@ dependencies = [
"malloc_buf",
]
[[package]]
name = "objc-foundation"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9"
dependencies = [
"block",
"objc",
"objc_id",
]
[[package]]
name = "objc_id"
version = "0.1.1"
@@ -2877,6 +3095,16 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "ordered-stream"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44630c059eacfd6e08bdaa51b1db2ce33119caa4ddc1235e923109aa5f25ccb1"
dependencies = [
"futures-core",
"pin-project-lite",
]
[[package]]
name = "pairing"
version = "0.20.0"
@@ -3231,6 +3459,25 @@ dependencies = [
"miniz_oxide 0.3.7",
]
[[package]]
name = "polling"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259"
dependencies = [
"cfg-if",
"libc",
"log",
"wepoll-ffi",
"winapi",
]
[[package]]
name = "pollster"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5da3b0203fd7ee5720aa0b5e790b591aa5d3f41c3ed2c34a3a393382198af2f7"
[[package]]
name = "polyval"
version = "0.5.3"
@@ -3708,6 +3955,32 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rfd"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aaf1d71ccd44689f7c2c72da1117fd8db71f72a76fe9b5c5dbb17ab903007e0"
dependencies = [
"ashpd",
"block",
"dispatch",
"glib-sys 0.15.6",
"gobject-sys 0.15.5",
"gtk-sys",
"js-sys",
"lazy_static",
"log",
"objc",
"objc-foundation",
"objc_id",
"pollster",
"raw-window-handle",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"windows",
]
[[package]]
name = "ring"
version = "0.16.20"
@@ -4095,6 +4368,21 @@ dependencies = [
"digest 0.10.3",
]
[[package]]
name = "sha1"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770"
dependencies = [
"sha1_smol",
]
[[package]]
name = "sha1_smol"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
[[package]]
name = "sha2"
version = "0.9.9"
@@ -4477,6 +4765,8 @@ version = "1.0.0-rc.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3571de0bcfd1f4f7599cbb3fe42f28ea9f52c3e89914ff04eaba68b5ee36bb51"
dependencies = [
"attohttpc",
"base64",
"bincode",
"cfg_aliases",
"dirs-next",
@@ -4490,12 +4780,14 @@ dependencies = [
"gtk",
"http",
"ignore",
"minisign-verify",
"once_cell",
"open",
"percent-encoding",
"rand 0.8.5",
"raw-window-handle",
"regex",
"rfd",
"semver 1.0.6",
"serde",
"serde_json",
@@ -5106,10 +5398,12 @@ dependencies = [
"base64",
"bip39",
"coconut-interface",
"colored",
"config",
"cosmrs",
"cosmwasm-std",
"flate2",
"futures",
"itertools",
"log",
"mixnet-contract-common",
@@ -5120,6 +5414,7 @@ dependencies = [
"serde_json",
"sha2 0.9.9",
"thiserror",
"tokio",
"url",
"validator-api-requests",
"vesting-contract",
@@ -5158,11 +5453,11 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "vesting-contract"
version = "0.1.0"
version = "1.0.0"
dependencies = [
"config",
"cosmwasm-std",
"cw-storage-plus 0.12.1",
"cw-storage-plus",
"mixnet-contract-common",
"schemars",
"serde",
@@ -5176,7 +5471,7 @@ version = "0.1.0"
dependencies = [
"config",
"cosmwasm-std",
"cw-storage-plus 0.11.1",
"cw-storage-plus",
"mixnet-contract-common",
"schemars",
"serde",
@@ -5399,6 +5694,21 @@ dependencies = [
"windows-bindgen",
]
[[package]]
name = "wepoll-ffi"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb"
dependencies = [
"cc",
]
[[package]]
name = "wildmatch"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6c48bd20df7e4ced539c12f570f937c6b4884928a87fee70a479d72f031d4e0"
[[package]]
name = "winapi"
version = "0.3.9"
@@ -5595,6 +5905,67 @@ dependencies = [
"libc",
]
[[package]]
name = "zbus"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bb86f3d4592e26a48b2719742aec94f8ae6238ebde20d98183ee185d1275e9a"
dependencies = [
"async-broadcast",
"async-channel",
"async-executor",
"async-io",
"async-lock",
"async-recursion",
"async-task",
"async-trait",
"byteorder",
"derivative",
"enumflags2",
"event-listener",
"futures-core",
"futures-sink",
"futures-util",
"hex",
"lazy_static",
"nix",
"once_cell",
"ordered-stream",
"rand 0.8.5",
"serde",
"serde_repr",
"sha1",
"static_assertions",
"winapi",
"zbus_macros",
"zbus_names",
"zvariant",
]
[[package]]
name = "zbus_macros"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36823cc10fddc3c6b19f048903262dacaf8274170e9a255784bdd8b4570a8040"
dependencies = [
"proc-macro-crate 1.1.3",
"proc-macro2",
"quote",
"regex",
"syn",
]
[[package]]
name = "zbus_names"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45dfcdcf87b71dad505d30cc27b1b7b88a64b6d1c435648f48f9dbc1fdc4b7e1"
dependencies = [
"serde",
"static_assertions",
"zvariant",
]
[[package]]
name = "zeroize"
version = "1.4.3"
@@ -5658,3 +6029,29 @@ dependencies = [
"cc",
"libc",
]
[[package]]
name = "zvariant"
version = "3.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49ea5dc38b2058fae6a5b79009388143dadce1e91c26a67f984a0fc0381c8033"
dependencies = [
"byteorder",
"enumflags2",
"libc",
"serde",
"static_assertions",
"zvariant_derive",
]
[[package]]
name = "zvariant_derive"
version = "3.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c2cecc5a61c2a053f7f653a24cd15b3b0195d7f7ddb5042c837fb32e161fb7a"
dependencies = [
"proc-macro-crate 1.1.3",
"proc-macro2",
"quote",
"syn",
]
+12 -1
View File
@@ -10,11 +10,15 @@
"tauri:dev": "yarn tauri dev",
"tauri:build": "yarn tauri build",
"tsc": "tsc --noEmit true",
"tsc:watch": "tsc --noEmit true --watch",
"dev": "yarn run webpack:dev & yarn run tauri:dev",
"prebuild": "yarn --cwd .. build",
"build": "run-s webpack:prod tauri:build",
"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"
},
"dependencies": {
"@babel/preset-typescript": "^7.15.0",
@@ -38,6 +42,7 @@
"react-hook-form": "^7.14.2",
"react-router-dom": "^5.2.0",
"semver": "^6.3.0",
"use-clipboard-copy": "^0.2.0",
"yup": "^0.32.9"
},
"devDependencies": {
@@ -47,6 +52,12 @@
"@babel/preset-react": "^7.14.5",
"@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",
"@tauri-apps/cli": "^1.0.0-rc.5",
"@testing-library/jest-dom": "^5.14.1",
+8 -14
View File
@@ -1,17 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Open+Sans&display=swap"
rel="stylesheet"
/>
<title>Nym Wallet</title>
</head>
<body>
<div id="root"></div>
</body>
<head>
<meta charset='utf-8' />
<meta name='viewport' content='width=device-width, initial-scale=1' />
<title>Nym Wallet</title>
</head>
<body>
<div id='root'></div>
</body>
</html>
+6 -3
View File
@@ -1,6 +1,6 @@
[package]
name = "nym_wallet"
version = "1.0.1"
version = "1.0.2"
description = "Nym Native Wallet"
authors = ["Nym Technologies SA"]
license = ""
@@ -21,9 +21,12 @@ tauri-macros = "=1.0.0-rc.1"
[dependencies]
bip39 = "1.0"
cfg-if = "1.0.0"
colored = "2.0"
dirs = "4.0"
dotenv = "0.15.0"
eyre = "0.6.5"
futures = "0.3.15"
itertools = "0.10"
log = "0.4"
pretty_env_logger = "0.4"
rand = "0.6.5"
@@ -31,7 +34,7 @@ reqwest = "0.11.9"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
strum = { version = "0.23", features = ["derive"] }
tauri = { version = "=1.0.0-rc.2", features = ["clipboard-all", "shell-open", "window-maximize"] }
tauri = { version = "=1.0.0-rc.2", features = ["clipboard-all", "shell-open", "updater", "window-maximize"] }
tendermint-rpc = "0.23.0"
thiserror = "1.0"
tokio = { version = "1.10", features = ["sync", "time"] }
@@ -43,7 +46,7 @@ argon2 = { version = "0.3.2", features = ["std"] }
base64 = "0.13"
zeroize = "1.4.3"
cosmrs = { version = "0.4.1", features = ["rpc", "bip32", "cosmwasm"] }
cosmrs = { git = "https://github.com/nymtech/cosmos-rust", branch = "bugfix/account-id-length-validation", features = ["rpc", "bip32", "cosmwasm"] }
cosmwasm-std = "1.0.0-beta3"
validator-client = { path = "../../common/client-libs/validator-client", features = [
+323 -185
View File
@@ -1,40 +1,62 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::network_config;
use crate::platform_constants::{CONFIG_DIR_NAME, CONFIG_FILENAME};
use crate::{error::BackendError, network::Network as WalletNetwork};
use config::defaults::all::Network;
use config::defaults::{all::SupportedNetworks, ValidatorDetails};
use config::NymConfig;
use reqwest::StatusCode;
use core::fmt;
use itertools::Itertools;
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::iter::zip;
use std::time::Duration;
use std::str::FromStr;
use std::{fs, io, path::PathBuf};
use strum::IntoEnumIterator;
use url::Url;
const REMOTE_SOURCE_OF_VALIDATOR_URLS: &str =
pub const REMOTE_SOURCE_OF_VALIDATOR_URLS: &str =
"https://nymtech.net/.wellknown/wallet/validators.json";
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
const CURRENT_GLOBAL_CONFIG_VERSION: u32 = 1;
const CURRENT_NETWORK_CONFIG_VERSION: u32 = 1;
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Config {
// Base configuration is not part of the configuration file as it's not intended to be changed.
#[serde(skip)]
base: Base,
// Network level configuration
network: OptionalValidators,
// Global configuration file
global: Option<GlobalConfig>,
// One configuration file per network
networks: HashMap<String, NetworkConfig>,
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
struct Base {
/// Information on all the networks that the wallet connects to.
networks: SupportedNetworks,
}
/// Validators that have been fetched dynamically, probably during startup.
fetched_validators: OptionalValidators,
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct GlobalConfig {
version: Option<u32>,
// TODO: there are no global settings (yet)
}
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct NetworkConfig {
version: Option<u32>,
// User selected urls
selected_nymd_url: Option<Url>,
selected_api_url: Option<Url>,
// Additional user provided validators.
// It is an option for the purpuse of file serialization.
validator_urls: Option<Vec<ValidatorUrl>>,
}
impl Default for Base {
@@ -42,89 +64,150 @@ impl Default for Base {
let networks = WalletNetwork::iter().map(Into::into).collect();
Base {
networks: SupportedNetworks::new(networks),
fetched_validators: OptionalValidators::default(),
}
}
}
impl NymConfig for Config {
fn template() -> &'static str {
// For now we're not using a template
unimplemented!();
impl Default for GlobalConfig {
fn default() -> Self {
Self {
version: Some(CURRENT_GLOBAL_CONFIG_VERSION),
}
}
}
fn default_root_directory() -> PathBuf {
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
.join(".nym")
.join("wallet")
impl Default for NetworkConfig {
fn default() -> Self {
Self {
version: Some(CURRENT_NETWORK_CONFIG_VERSION),
selected_nymd_url: None,
selected_api_url: None,
validator_urls: None,
}
}
}
fn root_directory(&self) -> PathBuf {
Self::default_root_directory()
}
fn config_directory(&self) -> PathBuf {
self.root_directory().join("config")
}
fn data_directory(&self) -> PathBuf {
self.root_directory().join("data")
}
fn save_to_file(&self, custom_location: Option<PathBuf>) -> io::Result<()> {
let config_toml = toml::to_string_pretty(&self)
.map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))?;
// Make sure the whole directory structure actually exists
match custom_location.clone() {
Some(loc) => {
if let Some(parent_dir) = loc.parent() {
fs::create_dir_all(parent_dir)
} else {
Ok(())
}
}
None => fs::create_dir_all(self.config_directory()),
}?;
fs::write(
custom_location.unwrap_or_else(|| self.config_directory().join(Self::config_file_name())),
config_toml,
)
impl NetworkConfig {
fn validators(&self) -> impl Iterator<Item = &ValidatorUrl> {
self.validator_urls.iter().flat_map(|v| v.iter())
}
}
impl Config {
/// Get the available validators in the order
/// 1. from the configuration file
/// 2. provided remotely
/// 3. hardcoded fallback
pub fn get_validators(&self, network: WalletNetwork) -> impl Iterator<Item = ValidatorUrl> + '_ {
// The base validators are (currently) stored as strings
let base_validators = self.base.networks.validators(network.into()).map(|v| {
fn root_directory() -> PathBuf {
tauri::api::path::config_dir().expect("Failed to get config directory")
}
fn config_directory() -> PathBuf {
Self::root_directory().join(CONFIG_DIR_NAME)
}
fn config_file_path(network: Option<WalletNetwork>) -> PathBuf {
if let Some(network) = network {
let network_filename = format!("{}.toml", network.as_key());
Self::config_directory().join(network_filename)
} else {
Self::config_directory().join(CONFIG_FILENAME)
}
}
pub fn save_to_files(&self) -> io::Result<()> {
log::trace!("Config::save_to_file");
// Make sure the whole directory structure actually exists
fs::create_dir_all(Self::config_directory())?;
// Global config
if let Some(global) = &self.global {
let location = Self::config_file_path(None);
match toml::to_string_pretty(&global)
.map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
.map(|toml| fs::write(location.clone(), toml))
{
Ok(_) => log::debug!("Writing to: {:#?}", location),
Err(err) => log::warn!("Failed to write to {:#?}: {err}", location),
}
}
// One file per network
for (network, config) in &self.networks {
let network = match Network::from_str(network).map(Into::into) {
Ok(network) => network,
Err(err) => {
log::warn!("Unexpected name for network configuration, not saving: {err}");
break;
}
};
let location = Self::config_file_path(Some(network));
match toml::to_string_pretty(config)
.map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
.map(|toml| fs::write(location.clone(), toml))
{
Ok(_) => log::debug!("Writing to: {:#?}", location),
Err(err) => log::warn!("Failed to write to {:#?}: {err}", location),
}
}
Ok(())
}
pub fn load_from_files() -> Self {
// Global
let global = {
let file = Self::config_file_path(None);
match load_from_file::<GlobalConfig>(file.clone()) {
Ok(global) => {
log::debug!("Loaded from file {:#?}", file);
Some(global)
}
Err(err) => {
log::trace!("Not loading {:#?}: {}", file, err);
None
}
}
};
// One file per network
let mut networks = HashMap::new();
for network in WalletNetwork::iter() {
let file = Self::config_file_path(Some(network));
match load_from_file::<NetworkConfig>(file.clone()) {
Ok(config) => {
log::trace!("Loaded from file {:#?}", file);
networks.insert(network.as_key(), config);
}
Err(err) => log::trace!("Not loading {:#?}: {}", file, err),
};
}
Self {
base: Base::default(),
global,
networks,
}
}
pub fn get_base_validators(
&self,
network: WalletNetwork,
) -> impl Iterator<Item = ValidatorUrl> + '_ {
self.base.networks.validators(network.into()).map(|v| {
v.clone()
.try_into()
.expect("The hardcoded validators are assumed to be valid urls")
});
self
.base
.fetched_validators
.validators(network)
.chain(self.network.validators(network))
.cloned()
.chain(base_validators)
})
}
pub fn get_validators_with_api_endpoint(
pub fn get_configured_validators(
&self,
network: WalletNetwork,
) -> impl Iterator<Item = ValidatorUrlWithApiEndpoint> + '_ {
) -> impl Iterator<Item = ValidatorUrl> + '_ {
self
.get_validators(network)
.networks
.get(&network.as_key())
.into_iter()
.filter_map(|validator| ValidatorUrlWithApiEndpoint::try_from(validator).ok())
.flat_map(|c| c.validators().cloned())
}
pub fn get_mixnet_contract_address(&self, network: WalletNetwork) -> Option<cosmrs::AccountId> {
@@ -160,70 +243,87 @@ impl Config {
.ok()
}
pub async fn fetch_updated_validator_urls(&mut self) -> Result<(), BackendError> {
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(3))
.build()?;
let response = client
.get(REMOTE_SOURCE_OF_VALIDATOR_URLS.to_string())
.send()
.await?;
self.base.fetched_validators = serde_json::from_str(&response.text().await?)?;
Ok(())
}
pub async fn check_validator_health(
&self,
network: WalletNetwork,
) -> Result<Vec<(ValidatorUrl, StatusCode)>, BackendError> {
// Limit the number of validators we query
let max_validators = 200_usize;
let validators_to_query = || self.get_validators(network).take(max_validators);
let validator_urls = validators_to_query().map(|v| {
let mut health_url = v.nymd_url.clone();
health_url.set_path("health");
(v, health_url)
});
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(3))
.build()?;
let requests = validator_urls.map(|(_, url)| client.get(url).send());
let responses = futures::future::join_all(requests).await;
let validators_responding_success =
zip(validators_to_query(), responses).filter_map(|(v, r)| match r {
Ok(r) if r.status().is_success() => Some((v, r.status())),
_ => None,
});
Ok(validators_responding_success.collect::<Vec<_>>())
}
#[allow(unused)]
pub async fn check_validator_health_for_all_networks(
&self,
) -> Result<HashMap<WalletNetwork, Vec<(ValidatorUrl, StatusCode)>>, BackendError> {
let validator_health_requests =
WalletNetwork::iter().map(|network| self.check_validator_health(network));
let responses_keyed_by_network = zip(
WalletNetwork::iter(),
futures::future::join_all(validator_health_requests).await,
);
// Iterate and collect manually to be able to return errors in the response
let mut responses = HashMap::new();
for (network, response) in responses_keyed_by_network {
responses.insert(network, response?);
pub fn select_validator_nymd_url(&mut self, nymd_url: Url, network: WalletNetwork) {
if let Some(net) = self.networks.get_mut(&network.as_key()) {
net.selected_nymd_url = Some(nymd_url);
} else {
self.networks.insert(
network.as_key(),
NetworkConfig {
selected_nymd_url: Some(nymd_url),
..NetworkConfig::default()
},
);
}
}
pub fn select_validator_api_url(&mut self, api_url: Url, network: WalletNetwork) {
if let Some(net) = self.networks.get_mut(&network.as_key()) {
net.selected_api_url = Some(api_url);
} else {
self.networks.insert(
network.as_key(),
NetworkConfig {
selected_nymd_url: Some(api_url),
..NetworkConfig::default()
},
);
}
}
pub fn get_selected_validator_nymd_url(&self, network: &WalletNetwork) -> Option<Url> {
self
.networks
.get(&network.as_key())
.and_then(|config| config.selected_nymd_url.clone())
}
pub fn get_selected_validator_api_url(&self, network: &WalletNetwork) -> Option<Url> {
self
.networks
.get(&network.as_key())
.and_then(|config| config.selected_api_url.clone())
}
pub fn add_validator_url(&mut self, url: ValidatorUrl, network: WalletNetwork) {
if let Some(network_config) = self.networks.get_mut(&network.as_key()) {
if let Some(ref mut urls) = network_config.validator_urls {
urls.push(url);
} else {
network_config.validator_urls = Some(vec![url]);
}
} else {
self.networks.insert(
network.as_key(),
NetworkConfig {
validator_urls: Some(vec![url]),
..NetworkConfig::default()
},
);
}
}
pub fn remove_validator_url(&mut self, url: ValidatorUrl, network: WalletNetwork) {
if let Some(network_config) = self.networks.get_mut(&network.as_key()) {
if let Some(ref mut urls) = network_config.validator_urls {
// Removes duplicates too if there are any
urls.retain(|existing_url| existing_url != &url);
}
}
Ok(responses)
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
fn load_from_file<T>(file: PathBuf) -> Result<T, io::Error>
where
T: DeserializeOwned,
{
fs::read_to_string(file).and_then(|contents| {
toml::from_str::<T>(&contents)
.map_err(|toml_err| io::Error::new(io::ErrorKind::Other, toml_err))
})
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct ValidatorUrl {
pub nymd_url: Url,
pub api_url: Option<Url>,
@@ -243,29 +343,34 @@ impl TryFrom<ValidatorDetails> for ValidatorUrl {
}
}
#[derive(Clone, Debug)]
pub struct ValidatorUrlWithApiEndpoint {
pub nymd_url: Url,
pub api_url: Url,
}
impl TryFrom<ValidatorUrl> for ValidatorUrlWithApiEndpoint {
impl TryFrom<network_config::Validator> for ValidatorUrl {
type Error = BackendError;
fn try_from(validator: ValidatorUrl) -> Result<Self, Self::Error> {
match validator.api_url {
Some(api_url) => Ok(ValidatorUrlWithApiEndpoint {
nymd_url: validator.nymd_url,
api_url,
}),
None => Err(BackendError::NoValidatorApiUrlConfigured),
}
fn try_from(validator: network_config::Validator) -> Result<Self, Self::Error> {
Ok(ValidatorUrl {
nymd_url: validator.nymd_url.parse()?,
api_url: match &validator.api_url {
Some(url) => Some(url.parse()?),
None => None,
},
})
}
}
impl fmt::Display for ValidatorUrl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s1 = format!("nymd_url: {}", self.nymd_url);
let s2 = self
.api_url
.as_ref()
.map(|url| format!(", api_url: {}", url));
write!(f, " {}{},", s1, s2.unwrap_or_default())
}
}
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
struct OptionalValidators {
pub struct OptionalValidators {
// User supplied additional validator urls in addition to the hardcoded ones.
// These are separate fields, rather than a map, to force the serialization order.
mainnet: Option<Vec<ValidatorUrl>>,
@@ -274,7 +379,7 @@ struct OptionalValidators {
}
impl OptionalValidators {
fn validators(&self, network: WalletNetwork) -> impl Iterator<Item = &ValidatorUrl> {
pub fn validators(&self, network: WalletNetwork) -> impl Iterator<Item = &ValidatorUrl> {
match network {
WalletNetwork::MAINNET => self.mainnet.as_ref(),
WalletNetwork::SANDBOX => self.sandbox.as_ref(),
@@ -285,58 +390,91 @@ impl OptionalValidators {
}
}
impl fmt::Display for OptionalValidators {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s1 = self
.mainnet
.as_ref()
.map(|validators| format!("mainnet: [\n{}\n]", validators.iter().format("\n")))
.unwrap_or_default();
let s2 = self
.sandbox
.as_ref()
.map(|validators| format!(",\nsandbox: [\n{}\n]", validators.iter().format("\n")))
.unwrap_or_default();
let s3 = self
.qa
.as_ref()
.map(|validators| format!(",\nqa: [\n{}\n]", validators.iter().format("\n")))
.unwrap_or_default();
write!(f, "{}{}{}", s1, s2, s3)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_config() -> Config {
Config {
base: Base::default(),
network: OptionalValidators {
mainnet: Some(vec![
ValidatorDetails {
nymd_url: "https://foo".to_string(),
api_url: None,
}
.try_into()
.unwrap(),
ValidatorUrl {
nymd_url: "https://baz".parse().unwrap(),
api_url: Some("https://baz/api".parse().unwrap()),
},
]),
sandbox: Some(vec![ValidatorUrl {
let netconfig = NetworkConfig {
selected_nymd_url: None,
selected_api_url: Some("https://my_api_url.com".parse().unwrap()),
validator_urls: Some(vec![
ValidatorUrl {
nymd_url: "https://foo".parse().unwrap(),
api_url: None,
},
ValidatorUrl {
nymd_url: "https://bar".parse().unwrap(),
api_url: Some("https://bar/api".parse().unwrap()),
}]),
qa: None,
},
},
ValidatorUrl {
nymd_url: "https://baz".parse().unwrap(),
api_url: Some("https://baz/api".parse().unwrap()),
},
]),
..NetworkConfig::default()
};
Config {
base: Base::default(),
global: Some(GlobalConfig::default()),
networks: [(WalletNetwork::MAINNET.as_key(), netconfig)]
.into_iter()
.collect(),
}
}
#[test]
fn serialize_to_toml() {
let config = test_config();
let netconfig = &config.networks[&WalletNetwork::MAINNET.as_key()];
assert_eq!(
toml::to_string_pretty(&test_config()).unwrap(),
r#"[[network.mainnet]]
toml::to_string_pretty(netconfig).unwrap(),
r#"version = 1
selected_api_url = 'https://my_api_url.com/'
[[validator_urls]]
nymd_url = 'https://foo/'
[[network.mainnet]]
nymd_url = 'https://baz/'
api_url = 'https://baz/api'
[[network.sandbox]]
[[validator_urls]]
nymd_url = 'https://bar/'
api_url = 'https://bar/api'
[[validator_urls]]
nymd_url = 'https://baz/'
api_url = 'https://baz/api'
"#
);
}
#[test]
fn serialize_and_deserialize_to_toml() {
let config = test_config();
let config_str = toml::to_string_pretty(&config).unwrap();
let config_from_toml = toml::from_str(&config_str).unwrap();
assert_eq!(config, config_from_toml);
let netconfig = &config.networks[&WalletNetwork::MAINNET.as_key()];
let config_str = toml::to_string_pretty(netconfig).unwrap();
let config_from_toml: NetworkConfig = toml::from_str(&config_str).unwrap();
assert_eq!(netconfig, &config_from_toml);
}
#[test]
@@ -344,7 +482,7 @@ api_url = 'https://bar/api'
let config = test_config();
let nymd_url = config
.get_validators(WalletNetwork::MAINNET)
.get_configured_validators(WalletNetwork::MAINNET)
.next()
.map(|v| v.nymd_url)
.unwrap();
@@ -352,7 +490,7 @@ api_url = 'https://bar/api'
// The first entry is missing an API URL
let api_url = config
.get_validators(WalletNetwork::MAINNET)
.get_configured_validators(WalletNetwork::MAINNET)
.next()
.and_then(|v| v.api_url);
assert_eq!(api_url, None);
@@ -363,14 +501,14 @@ api_url = 'https://bar/api'
let config = Config::default();
let nymd_url = config
.get_validators(WalletNetwork::MAINNET)
.get_base_validators(WalletNetwork::MAINNET)
.next()
.map(|v| v.nymd_url)
.unwrap();
assert_eq!(nymd_url.as_ref(), "https://rpc.nyx.nodes.guru/");
let api_url = config
.get_validators(WalletNetwork::MAINNET)
.get_base_validators(WalletNetwork::MAINNET)
.next()
.and_then(|v| v.api_url)
.unwrap();
+15 -5
View File
@@ -14,12 +14,11 @@ mod config;
mod error;
mod menu;
mod network;
mod network_config;
mod operations;
mod platform_constants;
mod state;
mod utils;
// temporarily until it is actually used
#[allow(unused)]
mod wallet_storage;
use crate::menu::AddDefaultSubmenus;
@@ -30,6 +29,7 @@ use crate::operations::vesting;
use crate::state::State;
fn main() {
dotenv::dotenv().ok();
setup_logging();
tauri::Builder::default()
@@ -42,9 +42,10 @@ fn main() {
mixnet::account::does_password_file_exist,
mixnet::account::get_balance,
mixnet::account::logout,
mixnet::account::remove_password,
mixnet::account::sign_in_with_password,
mixnet::account::switch_network,
mixnet::account::update_validator_urls,
mixnet::account::validate_mnemonic,
mixnet::admin::get_contract_settings,
mixnet::admin::update_contract_settings,
mixnet::bond::bond_gateway,
@@ -57,11 +58,20 @@ fn main() {
mixnet::bond::update_mixnode,
mixnet::delegate::delegate_to_mixnode,
mixnet::delegate::get_delegator_rewards,
mixnet::delegate::get_pending_delegation_events,
mixnet::delegate::get_reverse_mix_delegations_paged,
mixnet::delegate::undelegate_from_mixnode,
mixnet::delegate::get_pending_delegation_events,
mixnet::epoch::get_current_epoch,
mixnet::send::send,
network_config::add_validator,
network_config::get_validator_api_urls,
network_config::get_validator_nymd_urls,
network_config::remove_validator,
network_config::select_validator_api_url,
network_config::select_validator_nymd_url,
network_config::update_validator_urls,
state::load_config_from_files,
state::save_config_to_files,
utils::major_to_minor,
utils::minor_to_major,
utils::outdated_get_approximate_fee,
@@ -77,8 +87,8 @@ fn main() {
vesting::bond::vesting_bond_mixnode,
vesting::bond::vesting_unbond_gateway,
vesting::bond::vesting_unbond_mixnode,
vesting::bond::withdraw_vested_coins,
vesting::bond::vesting_update_mixnode,
vesting::bond::withdraw_vested_coins,
vesting::delegate::vesting_delegate_to_mixnode,
vesting::delegate::vesting_undelegate_from_mixnode,
vesting::queries::delegated_free,
+4
View File
@@ -21,6 +21,10 @@ pub enum Network {
}
impl Network {
pub fn as_key(&self) -> String {
self.to_string().to_lowercase()
}
pub fn denom(&self) -> Denom {
match self {
// network defaults should be correctly formatted
+122
View File
@@ -0,0 +1,122 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::BackendError;
use crate::network::Network as WalletNetwork;
use crate::state::State;
use serde::{Deserialize, Serialize};
use std::{fmt, sync::Arc};
use tokio::sync::RwLock;
#[cfg_attr(test, derive(ts_rs::TS))]
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/validatorurls.ts"))]
#[derive(Serialize, Deserialize)]
pub struct ValidatorUrls {
pub urls: Vec<String>,
}
#[cfg_attr(test, derive(ts_rs::TS))]
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/validatorurls.ts"))]
#[derive(Debug, Serialize, Deserialize)]
pub struct Validator {
pub nymd_url: String,
pub api_url: Option<String>,
}
impl fmt::Display for Validator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let nymd_url = format!("nymd_url: {}", self.nymd_url);
let api_url = self
.api_url
.as_ref()
.map(|api_url| format!(", api_url: {}", api_url))
.unwrap_or_default();
write!(f, "{nymd_url}{api_url}")
}
}
#[tauri::command]
pub async fn get_validator_nymd_urls(
network: WalletNetwork,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<ValidatorUrls, BackendError> {
let state = state.read().await;
let urls: Vec<String> = state
.get_nymd_urls(network)
.map(|url| url.to_string())
.collect();
Ok(ValidatorUrls { urls })
}
#[tauri::command]
pub async fn get_validator_api_urls(
network: WalletNetwork,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<ValidatorUrls, BackendError> {
let state = state.read().await;
let urls: Vec<String> = state
.get_api_urls(network)
.map(|url| url.to_string())
.collect();
Ok(ValidatorUrls { urls })
}
#[tauri::command]
pub async fn select_validator_nymd_url(
url: &str,
network: WalletNetwork,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
log::debug!("Selecting new validator nymd_url for {network}: {url}");
state
.write()
.await
.select_validator_nymd_url(url, network)?;
Ok(())
}
#[tauri::command]
pub async fn select_validator_api_url(
url: &str,
network: WalletNetwork,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
log::debug!("Selecting new validator api_url for {network}: {url}");
state.write().await.select_validator_api_url(url, network)?;
Ok(())
}
#[tauri::command]
pub async fn add_validator(
validator: Validator,
network: WalletNetwork,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
log::debug!("Add validator for {network}: {validator}");
let url = validator.try_into()?;
state.write().await.add_validator_url(url, network);
Ok(())
}
#[tauri::command]
pub async fn remove_validator(
validator: Validator,
network: WalletNetwork,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
log::debug!("Remove validator for {network}: {validator}");
let url = validator.try_into()?;
state.write().await.remove_validator_url(url, network);
Ok(())
}
// Update the list of validators by fecthing additional ones remotely. If it fails, just ignore.
#[tauri::command]
pub async fn update_validator_urls(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
let mut w_state = state.write().await;
let _r = w_state.fetch_updated_validator_urls().await;
Ok(())
}
@@ -1,25 +1,28 @@
use crate::coin::{Coin, Denom};
use crate::config::{Config, ValidatorUrlWithApiEndpoint};
use crate::config::Config;
use crate::error::BackendError;
use crate::network::Network;
use crate::network::Network as WalletNetwork;
use crate::network_config;
use crate::nymd_client;
use crate::state::State;
use crate::wallet_storage::{self, DEFAULT_WALLET_ACCOUNT_ID};
use bip39::{Language, Mnemonic};
use config::defaults::all::Network;
use config::defaults::COSMOS_DERIVATION_PATH;
use cosmrs::bip32::DerivationPath;
use itertools::Itertools;
use rand::seq::SliceRandom;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::convert::TryInto;
use std::str::FromStr;
use std::sync::Arc;
use std::time::Duration;
use strum::IntoEnumIterator;
use tokio::sync::RwLock;
use validator_client::nymd::error::NymdError;
use validator_client::nymd::SigningNymdClient;
use validator_client::Client;
use url::Url;
use validator_client::{nymd::SigningNymdClient, Client};
#[cfg_attr(test, derive(ts_rs::TS))]
#[cfg_attr(test, ts(export, export_to = "../src/types/rust/account.ts"))]
@@ -104,15 +107,20 @@ pub async fn create_new_account(
}
#[tauri::command]
pub async fn create_new_mnemonic() -> Result<String, BackendError> {
pub fn create_new_mnemonic() -> Result<String, BackendError> {
let rand_mnemonic = random_mnemonic();
Ok(rand_mnemonic.to_string())
}
#[tauri::command]
pub fn validate_mnemonic(mnemonic: &str) -> bool {
Mnemonic::from_str(mnemonic).is_ok()
}
#[tauri::command]
pub async fn switch_network(
state: tauri::State<'_, Arc<RwLock<State>>>,
network: Network,
network: WalletNetwork,
) -> Result<Account, BackendError> {
let account = {
let r_state = state.read().await;
@@ -143,31 +151,64 @@ fn random_mnemonic() -> Mnemonic {
Mnemonic::generate_in_with(&mut rng, Language::English, 24).unwrap()
}
#[tauri::command]
pub async fn update_validator_urls(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
// Update the list of validators by fecthing additional ones remotely. If it fails, just ignore.
let mut w_state = state.write().await;
let _r = w_state.fetch_updated_validator_urls().await;
Ok(())
}
async fn _connect_with_mnemonic(
mnemonic: Mnemonic,
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<Account, BackendError> {
update_validator_urls(state.clone()).await?;
let validators = choose_validators(mnemonic.clone(), &state).await?;
{
let mut w_state = state.write().await;
w_state.load_config_files();
}
let config = state.read().await.config();
let clients = create_clients(&validators, &mnemonic, &config)?;
network_config::update_validator_urls(state.clone()).await?;
let config = {
let state = state.read().await;
// Take the oppertunity to list all the known validators while we have the state.
for network in WalletNetwork::iter() {
log::debug!(
"List of validators for {network}: [\n{}\n]",
state.get_validators(network).format(",\n")
);
}
state.config().clone()
};
// Get all the urls needed for the connection test
let (untested_nymd_urls, untested_api_urls) = {
let state = state.read().await;
(state.get_all_nymd_urls(), state.get_all_api_urls())
};
let default_nymd_urls: HashMap<WalletNetwork, Url> = untested_nymd_urls
.iter()
.map(|(network, urls)| (*network, urls.iter().next().unwrap().clone()))
.collect();
let default_api_urls: HashMap<WalletNetwork, Url> = untested_api_urls
.iter()
.map(|(network, urls)| (*network, urls.iter().next().unwrap().clone()))
.collect();
// Run connection tests on all nymd and validator-api endpoints
let (nymd_urls, api_urls) =
run_connection_test(untested_nymd_urls, untested_api_urls, &config).await;
// Create clients for all networks
let clients = create_clients(
&nymd_urls,
&api_urls,
&default_nymd_urls,
&default_api_urls,
&config,
&mnemonic,
)?;
// Set the default account
let default_network: Network = config::defaults::DEFAULT_NETWORK.into();
let default_network: WalletNetwork = config::defaults::DEFAULT_NETWORK.into();
let client_for_default_network = clients
.iter()
.find(|client| Network::from(client.network) == default_network);
.find(|client| WalletNetwork::from(client.network) == default_network);
let account_for_default_network = match client_for_default_network {
Some(client) => Ok(Account::new(
client.nymd.mixnet_contract_address()?.to_string(),
@@ -181,7 +222,7 @@ async fn _connect_with_mnemonic(
// Register all the clients
for client in clients {
let network: Network = client.network.into();
let network: WalletNetwork = client.network.into();
let mut w_state = state.write().await;
w_state.add_client(network, client);
}
@@ -189,18 +230,78 @@ async fn _connect_with_mnemonic(
account_for_default_network
}
fn create_clients(
validators: &HashMap<Network, ValidatorUrlWithApiEndpoint>,
mnemonic: &Mnemonic,
async fn run_connection_test(
untested_nymd_urls: HashMap<WalletNetwork, Vec<Url>>,
untested_api_urls: HashMap<WalletNetwork, Vec<Url>>,
config: &Config,
) -> (
HashMap<Network, Vec<(Url, bool)>>,
HashMap<Network, Vec<(Url, bool)>>,
) {
let mixnet_contract_address = WalletNetwork::iter()
.map(|network| (network.into(), config.get_mixnet_contract_address(network)))
.collect::<HashMap<_, _>>();
let untested_nymd_urls = untested_nymd_urls
.into_iter()
.flat_map(|(net, urls)| urls.into_iter().map(move |url| (net.into(), url)));
let untested_api_urls = untested_api_urls
.into_iter()
.flat_map(|(net, urls)| urls.into_iter().map(move |url| (net.into(), url)));
validator_client::connection_tester::run_validator_connection_test(
untested_nymd_urls,
untested_api_urls,
mixnet_contract_address,
)
.await
}
fn create_clients(
nymd_urls: &HashMap<Network, Vec<(Url, bool)>>,
api_urls: &HashMap<Network, Vec<(Url, bool)>>,
default_nymd_urls: &HashMap<WalletNetwork, Url>,
default_api_urls: &HashMap<WalletNetwork, Url>,
config: &Config,
mnemonic: &Mnemonic,
) -> Result<Vec<Client<SigningNymdClient>>, BackendError> {
let mut clients = Vec::new();
for network in Network::iter() {
for network in WalletNetwork::iter() {
let nymd_url = if let Some(url) = config.get_selected_validator_nymd_url(&network) {
log::debug!("Using selected nymd_url for {network}: {url}");
url.clone()
} else {
let default_nymd_url = default_nymd_urls
.get(&network)
.expect("Expected at least one nymd_url");
select_random_responding_url(nymd_urls, network).unwrap_or_else(|| {
log::debug!("No successful nymd_urls for {network}: using default: {default_nymd_url}");
default_nymd_url.clone()
})
};
let api_url = if let Some(url) = config.get_selected_validator_api_url(&network) {
log::debug!("Using selected api_url for {network}: {url}");
url.clone()
} else {
let default_api_url = default_api_urls
.get(&network)
.expect("Expected at least one api url");
select_first_responding_url(api_urls, network).unwrap_or_else(|| {
log::debug!("No passing api_urls for {network}: using default: {default_api_url}");
default_api_url.clone()
})
};
log::info!("Connecting to: nymd_url: {nymd_url} for {network}");
log::info!("Connecting to: api_url: {api_url} for {network}");
let client = validator_client::Client::new_signing(
validator_client::Config::new(
network.into(),
validators[&network].nymd_url.clone(),
validators[&network].api_url.clone(),
nymd_url,
api_url,
config.get_mixnet_contract_address(network),
config.get_vesting_contract_address(network),
config.get_bandwidth_claim_contract_address(network),
@@ -212,120 +313,29 @@ fn create_clients(
Ok(clients)
}
async fn choose_validators(
mnemonic: Mnemonic,
state: &tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<HashMap<Network, ValidatorUrlWithApiEndpoint>, BackendError> {
let config = state.read().await.config();
// Try to connect to validators on all networks
let mut validators = select_responding_validators(&config, &mnemonic).await?;
// If for a network we didn't manage to connect to any validators, just go ahead and try with the
// first in the list
for network in Network::iter() {
validators.entry(network).or_insert_with(|| {
let default_validator = config
.get_validators_with_api_endpoint(network)
.next()
// We always have at least one hardcoded default validator
.unwrap();
log::info!(
"Using default for {network}: {}, {}",
default_validator.nymd_url,
default_validator.api_url,
);
default_validator
});
}
Ok(validators)
fn select_random_responding_url(
urls: &HashMap<Network, Vec<(Url, bool)>>,
network: WalletNetwork,
) -> Option<Url> {
urls.get(&network.into()).and_then(|urls| {
let urls: Vec<_> = urls
.iter()
.filter_map(|(url, result)| if *result { Some(url.clone()) } else { None })
.collect();
urls.choose(&mut rand::thread_rng()).cloned()
})
}
// For each network, try the list of available validators one by one and use the first responding
// one.
async fn select_responding_validators(
config: &Config,
mnemonic: &Mnemonic,
) -> Result<HashMap<Network, ValidatorUrlWithApiEndpoint>, BackendError> {
use tokio::time::timeout;
let validators = futures::future::join_all(Network::iter().map(|network| {
timeout(
Duration::from_millis(3000),
try_connect_to_validators(
config.get_validators_with_api_endpoint(network),
config,
network,
mnemonic.clone(),
),
)
}))
.await;
// Drop networks that failed the global timeout
let validators = validators.into_iter().filter_map(Result::ok);
// Rewrap to return any errors during client creation
let validators = validators.collect::<Result<Vec<_>, _>>()?;
// Filter out networks where we exhausted all listed validators
let validators = validators.into_iter().flatten();
Ok(validators.collect::<HashMap<_, _>>())
}
async fn try_connect_to_validators(
validators: impl Iterator<Item = ValidatorUrlWithApiEndpoint>,
config: &Config,
network: Network,
mnemonic: Mnemonic,
) -> Result<Option<(Network, ValidatorUrlWithApiEndpoint)>, BackendError> {
for validator in validators {
if let Some(responding_validator) =
try_connect_to_validator(&validator, config, network, mnemonic.clone()).await?
{
// Pick the first successful one
return Ok(Some(responding_validator));
}
}
Ok(None)
}
async fn try_connect_to_validator(
validator: &ValidatorUrlWithApiEndpoint,
config: &Config,
network: Network,
mnemonic: Mnemonic,
) -> Result<Option<(Network, ValidatorUrlWithApiEndpoint)>, BackendError> {
let client = validator_client::Client::new_signing(
validator_client::Config::new(
network.into(),
validator.nymd_url.clone(),
validator.api_url.clone(),
config.get_mixnet_contract_address(network),
config.get_vesting_contract_address(network),
config.get_bandwidth_claim_contract_address(network),
),
mnemonic,
)?;
if is_validator_connection_ok(&client).await {
log::info!(
"Connection ok for {network}: {}, {}",
validator.nymd_url,
validator.api_url
);
Ok(Some((network, validator.clone())))
} else {
Ok(None)
}
}
// The criteria used to determina if a validator endpoint is to be used
async fn is_validator_connection_ok(client: &Client<SigningNymdClient>) -> bool {
match client.get_mixnet_contract_version().await {
Err(NymdError::TendermintError(_)) => false,
Err(_) | Ok(_) => true,
}
fn select_first_responding_url(
urls: &HashMap<Network, Vec<(Url, bool)>>,
network: WalletNetwork,
//config: &Config,
) -> Option<Url> {
urls.get(&network.into()).and_then(|urls| {
urls
.iter()
.find_map(|(url, result)| if *result { Some(url.clone()) } else { None })
})
}
#[tauri::command]
@@ -369,3 +379,10 @@ pub async fn sign_in_with_password(
let stored_account = wallet_storage::load_existing_wallet_login_information(&id, &password)?;
_connect_with_mnemonic(stored_account.mnemonic().clone(), state).await
}
#[tauri::command]
pub fn remove_password() -> Result<(), BackendError> {
log::info!("Removing password");
let id = wallet_storage::WalletAccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string());
wallet_storage::remove_wallet_login_information(&id)
}
@@ -6,16 +6,24 @@
cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
pub const CONFIG_DIR_NAME: &str = "nym-wallet";
pub const CONFIG_FILENAME: &str = "config.toml";
pub const STORAGE_DIR_NAME: &str = "nym-wallet";
pub const WALLET_INFO_FILENAME: &str = "saved-wallet.json";
} else if #[cfg(taret_os = "macos")] {
pub const CONFIG_DIR_NAME: &str = "nym-wallet";
pub const CONFIG_FILENAME: &str = "config.toml";
pub const STORAGE_DIR_NAME: &str = "nym-wallet";
pub const WALLET_INFO_FILENAME: &str = "saved-wallet.json";
} else if #[cfg(taret_os = "windows")] {
pub const CONFIG_DIR_NAME: &str = "NymWallet";
pub const CONFIG_FILENAME: &str = "Config.toml";
pub const STORAGE_DIR_NAME: &str = "NymWallet";
pub const WALLET_INFO_FILENAME: &str = "saved_wallet.json";
} else {
// This case is likely to be a unix-y system
pub const CONFIG_DIR_NAME: &str = "nym-wallet";
pub const CONFIG_FILENAME: &str = "config.toml";
pub const STORAGE_DIR_NAME: &str = "nym-wallet";
pub const WALLET_INFO_FILENAME: &str = "saved-wallet.json";
}
+208 -4
View File
@@ -1,16 +1,42 @@
use crate::config::Config;
use crate::config::{Config, OptionalValidators, ValidatorUrl};
use crate::error::BackendError;
use crate::network::Network;
use strum::IntoEnumIterator;
use validator_client::nymd::SigningNymdClient;
use validator_client::Client;
use itertools::Itertools;
use tokio::sync::RwLock;
use url::Url;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;
#[tauri::command]
pub async fn load_config_from_files(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
state.write().await.load_config_files();
Ok(())
}
#[tauri::command]
pub async fn save_config_to_files(
state: tauri::State<'_, Arc<RwLock<State>>>,
) -> Result<(), BackendError> {
state.read().await.save_config_files()
}
#[derive(Default)]
pub struct State {
config: Config,
signing_clients: HashMap<Network, Client<SigningNymdClient>>,
current_network: Network,
/// Validators that have been fetched dynamically, probably during startup.
fetched_validators: OptionalValidators,
}
impl State {
@@ -21,6 +47,16 @@ impl State {
.ok_or(BackendError::ClientNotInitialized)
}
pub fn client_mut(
&mut self,
network: Network,
) -> Result<&mut Client<SigningNymdClient>, BackendError> {
self
.signing_clients
.get_mut(&network)
.ok_or(BackendError::ClientNotInitialized)
}
pub fn current_client(&self) -> Result<&Client<SigningNymdClient>, BackendError> {
self
.signing_clients
@@ -28,8 +64,26 @@ impl State {
.ok_or(BackendError::ClientNotInitialized)
}
pub fn config(&self) -> Config {
self.config.clone()
#[allow(unused)]
pub fn current_client_mut(&mut self) -> Result<&mut Client<SigningNymdClient>, BackendError> {
self
.signing_clients
.get_mut(&self.current_network)
.ok_or(BackendError::ClientNotInitialized)
}
pub fn config(&self) -> &Config {
&self.config
}
/// Load configuration from files. If unsuccessful we just log it and move on.
pub fn load_config_files(&mut self) {
self.config = Config::load_from_files();
}
#[allow(unused)]
pub fn save_config_files(&self) -> Result<(), BackendError> {
Ok(self.config.save_to_files()?)
}
pub fn add_client(&mut self, network: Network, client: Client<SigningNymdClient>) {
@@ -48,8 +102,93 @@ impl State {
self.signing_clients = HashMap::new();
}
/// Get the available validators in the order
/// 1. from the configuration file
/// 2. provided remotely
/// 3. hardcoded fallback
pub fn get_validators(&self, network: Network) -> impl Iterator<Item = ValidatorUrl> + '_ {
let validators_in_config = self.config.get_configured_validators(network);
let fetched_validators = self.fetched_validators.validators(network).cloned();
let default_validators = self.config.get_base_validators(network);
validators_in_config
.chain(fetched_validators)
.chain(default_validators)
.unique()
}
pub fn get_nymd_urls(&self, network: Network) -> impl Iterator<Item = Url> + '_ {
self.get_validators(network).into_iter().map(|v| v.nymd_url)
}
pub fn get_api_urls(&self, network: Network) -> impl Iterator<Item = Url> + '_ {
self
.get_validators(network)
.into_iter()
.filter_map(|v| v.api_url)
}
pub fn get_all_nymd_urls(&self) -> HashMap<Network, Vec<Url>> {
Network::iter()
.flat_map(|network| self.get_nymd_urls(network).map(move |url| (network, url)))
.into_group_map()
}
pub fn get_all_api_urls(&self) -> HashMap<Network, Vec<Url>> {
Network::iter()
.flat_map(|network| self.get_api_urls(network).map(move |url| (network, url)))
.into_group_map()
}
/// Fetch validator urls remotely. These are used to in addition to the base ones, and the user
/// configured ones.
pub async fn fetch_updated_validator_urls(&mut self) -> Result<(), BackendError> {
self.config.fetch_updated_validator_urls().await
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(3))
.build()?;
log::debug!(
"Fetching validator urls from: {}",
crate::config::REMOTE_SOURCE_OF_VALIDATOR_URLS
);
let response = client
.get(crate::config::REMOTE_SOURCE_OF_VALIDATOR_URLS.to_string())
.send()
.await?;
self.fetched_validators = serde_json::from_str(&response.text().await?)?;
log::debug!("Received validator urls: \n{}", self.fetched_validators);
Ok(())
}
pub fn select_validator_nymd_url(
&mut self,
url: &str,
network: Network,
) -> Result<(), BackendError> {
self.config.select_validator_nymd_url(url.parse()?, network);
if let Ok(client) = self.client_mut(network) {
client.change_nymd(url.parse()?)?;
}
Ok(())
}
pub fn select_validator_api_url(
&mut self,
url: &str,
network: Network,
) -> Result<(), BackendError> {
self.config.select_validator_api_url(url.parse()?, network);
if let Ok(client) = self.client_mut(network) {
client.change_validator_api(url.parse()?);
}
Ok(())
}
pub fn add_validator_url(&mut self, url: ValidatorUrl, network: Network) {
self.config.add_validator_url(url, network);
}
pub fn remove_validator_url(&mut self, url: ValidatorUrl, network: Network) {
self.config.remove_validator_url(url, network)
}
}
@@ -73,3 +212,68 @@ macro_rules! api_client {
$state.read().await.current_client()?.validator_api
};
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn adding_validators_urls_prepends() {
let mut state = State::default();
let _api_urls = state.get_api_urls(Network::MAINNET).collect::<Vec<_>>();
state.add_validator_url(
ValidatorUrl {
nymd_url: "http://nymd_url.com".parse().unwrap(),
api_url: Some("http://nymd_url.com/api".parse().unwrap()),
},
Network::MAINNET,
);
state.add_validator_url(
ValidatorUrl {
nymd_url: "http://foo.com".parse().unwrap(),
api_url: None,
},
Network::MAINNET,
);
state.add_validator_url(
ValidatorUrl {
nymd_url: "http://bar.com".parse().unwrap(),
api_url: None,
},
Network::MAINNET,
);
assert_eq!(
state.get_nymd_urls(Network::MAINNET).collect::<Vec<_>>(),
vec![
"http://nymd_url.com/".parse().unwrap(),
"http://foo.com".parse().unwrap(),
"http://bar.com".parse().unwrap(),
"https://rpc.nyx.nodes.guru".parse().unwrap(),
],
);
assert_eq!(
state.get_api_urls(Network::MAINNET).collect::<Vec<_>>(),
vec![
"http://nymd_url.com/api".parse().unwrap(),
"https://api.nyx.nodes.guru".parse().unwrap(),
],
);
assert_eq!(
state
.get_all_nymd_urls()
.get(&Network::MAINNET)
.unwrap()
.clone(),
vec![
"http://nymd_url.com/".parse().unwrap(),
"http://foo.com".parse().unwrap(),
"http://bar.com".parse().unwrap(),
"https://rpc.nyx.nodes.guru".parse().unwrap(),
],
)
}
}
@@ -2,11 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use cosmrs::bip32::DerivationPath;
use serde::de::Visitor;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::Formatter;
use serde::{Deserialize, Serialize};
use zeroize::Zeroize;
use zeroize::Zeroizing;
use crate::error::BackendError;
@@ -23,6 +20,7 @@ pub(crate) struct StoredWallet {
}
impl StoredWallet {
#[allow(unused)]
pub fn version(&self) -> u32 {
self.version
}
@@ -31,10 +29,22 @@ impl StoredWallet {
self.accounts.is_empty()
}
#[allow(unused)]
pub fn len(&self) -> usize {
self.accounts.len()
}
pub fn remove_account(&mut self, id: &WalletAccountId) -> Option<EncryptedAccount> {
if let Some(index) = self.accounts.iter().position(|account| &account.id == id) {
log::info!("Removing from wallet file: {id}");
Some(self.accounts.remove(index))
} else {
log::debug!("Tried to remove non-existent id from wallet: {id}");
None
}
}
#[allow(unused)]
pub fn encrypted_account_by_index(&self, index: usize) -> Option<&EncryptedAccount> {
self.accounts.get(index)
}
@@ -136,6 +146,7 @@ impl MnemonicAccount {
&self.mnemonic
}
#[allow(unused)]
pub(crate) fn hd_path(&self) -> &DerivationPath {
&self.hd_path
}
@@ -4,14 +4,13 @@
use super::password::UserPassword;
use crate::error::BackendError;
use aes_gcm::aead::generic_array::ArrayLength;
use aes_gcm::aead::{Aead, NewAead, Payload};
use aes_gcm::aead::{Aead, NewAead};
use aes_gcm::{Aes256Gcm, Key, Nonce};
use argon2::{
password_hash::rand_core::{OsRng, RngCore},
Algorithm, Argon2, Params, Version,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::convert::TryFrom;
use serde::{Deserialize, Serialize};
use std::marker::PhantomData;
use zeroize::Zeroize;
@@ -78,6 +77,7 @@ mod base64 {
}
impl<T> EncryptedData<T> {
#[allow(unused)]
pub(crate) fn encrypt_struct(data: &T, password: &UserPassword) -> Result<Self, BackendError>
where
T: Serialize,
@@ -94,10 +94,12 @@ impl<T> EncryptedData<T> {
}
impl EncryptedData<Vec<u8>> {
#[allow(unused)]
pub(crate) fn encrypt_data(data: &[u8], password: &UserPassword) -> Result<Self, BackendError> {
encrypt_data(data, password)
}
#[allow(unused)]
pub(crate) fn decrypt_data(&self, password: &UserPassword) -> Result<Vec<u8>, BackendError> {
decrypt_data(self, password)
}
@@ -159,6 +161,7 @@ fn decrypt(
.map_err(|_| BackendError::DecryptionError)
}
#[allow(unused)]
pub(crate) fn encrypt_data(
data: &[u8],
password: &UserPassword,
@@ -194,6 +197,7 @@ where
})
}
#[allow(unused)]
pub(crate) fn decrypt_data(
encrypted_data: &EncryptedData<Vec<u8>>,
password: &UserPassword,
+101 -11
View File
@@ -4,13 +4,11 @@
pub(crate) use crate::wallet_storage::password::{UserPassword, WalletAccountId};
use crate::error::BackendError;
use crate::operations::mixnet::account::create_new_account;
use crate::platform_constants::{STORAGE_DIR_NAME, WALLET_INFO_FILENAME};
use crate::wallet_storage::account_data::StoredAccount;
use crate::wallet_storage::encryption::{encrypt_struct, EncryptedData};
use crate::wallet_storage::encryption::encrypt_struct;
use cosmrs::bip32::DerivationPath;
use serde::{Deserialize, Serialize};
use std::fs::{create_dir_all, OpenOptions};
use std::fs::{self, create_dir_all, OpenOptions};
use std::path::PathBuf;
use self::account_data::{EncryptedAccount, StoredWallet};
@@ -32,6 +30,7 @@ pub(crate) fn wallet_login_filepath() -> Result<PathBuf, BackendError> {
get_storage_directory().map(|dir| dir.join(WALLET_INFO_FILENAME))
}
#[allow(unused)]
pub(crate) fn load_existing_wallet(password: &UserPassword) -> Result<StoredWallet, BackendError> {
let store_dir = get_storage_directory()?;
let filepath = store_dir.join(WALLET_INFO_FILENAME);
@@ -112,21 +111,54 @@ fn store_wallet_login_information_at_file(
Ok(serde_json::to_writer_pretty(file, &stored_wallet)?)
}
// this function should probably exist, but I guess we need to discuss how it should behave in the context of the UX
// pub(crate) fn remove_wallet_login_information(
//
// )
pub(crate) fn remove_wallet_login_information(id: &WalletAccountId) -> Result<(), BackendError> {
let store_dir = get_storage_directory()?;
let filepath = store_dir.join(WALLET_INFO_FILENAME);
remove_wallet_login_information_at_file(filepath, id)
}
pub(crate) fn remove_wallet_login_information_at_file(
filepath: PathBuf,
id: &WalletAccountId,
) -> Result<(), BackendError> {
let mut stored_wallet = match load_existing_wallet_at_file(filepath.clone()) {
Err(BackendError::WalletFileNotFound) => StoredWallet::default(),
result => result?,
};
if stored_wallet.is_empty() {
log::info!("Removing file: {:#?}", filepath);
return Ok(fs::remove_file(filepath)?);
}
stored_wallet
.remove_account(id)
.ok_or(BackendError::NoSuchIdInWallet)?;
if stored_wallet.is_empty() {
log::info!("Removing file: {:#?}", filepath);
Ok(fs::remove_file(filepath)?)
} else {
let file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(filepath)?;
Ok(serde_json::to_writer_pretty(file, &stored_wallet)?)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::wallet_storage::encryption::encrypt_data;
use config::defaults::COSMOS_DERIVATION_PATH;
use std::path::Path;
use std::str::FromStr;
use tempfile::tempdir;
// I'm not 100% sure how to feel about having to touch the file system at all
#[test]
#[allow(clippy::too_many_lines)]
fn storing_wallet_information() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
@@ -248,9 +280,67 @@ mod tests {
assert_eq!(&cosmos_hd_path, acc1.hd_path());
let loaded_account =
load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap();
load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password).unwrap();
let StoredAccount::Mnemonic(ref acc2) = loaded_account;
assert_eq!(&dummy_account2, acc2.mnemonic());
assert_eq!(&different_hd_path, acc2.hd_path());
// Fails to delete non-existent id in the wallet
let id3 = WalletAccountId::new("phony".to_string());
assert!(matches!(
remove_wallet_login_information_at_file(wallet_file.clone(), &id3),
Err(BackendError::NoSuchIdInWallet),
));
// Delete the second account
remove_wallet_login_information_at_file(wallet_file.clone(), &id2).unwrap();
// The first account should be unchanged
let loaded_account =
load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap();
let StoredAccount::Mnemonic(ref acc1) = loaded_account;
assert_eq!(&dummy_account1, acc1.mnemonic());
assert_eq!(&cosmos_hd_path, acc1.hd_path());
// Delete the first account
assert!(wallet_file.exists());
remove_wallet_login_information_at_file(wallet_file.clone(), &id1).unwrap();
// The file should now be removed
assert!(!wallet_file.exists());
}
#[test]
fn decrypt_stored_wallet() {
const SAVED_WALLET: &str = "src/wallet_storage/test-data/saved-wallet.json";
let wallet_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(SAVED_WALLET);
let wallet = load_existing_wallet_at_file(wallet_file).unwrap();
let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let bad_password = UserPassword::new("bad-password".to_string());
let id1 = WalletAccountId::new("first".to_string());
let id2 = WalletAccountId::new("second".to_string());
assert!(!wallet.password_can_decrypt_all(&bad_password));
assert!(wallet.password_can_decrypt_all(&password));
let account1 = wallet.decrypt_account(&id1, &password).unwrap();
let account2 = wallet.decrypt_account(&id2, &password).unwrap();
let expected_account1 = bip39::Mnemonic::from_str("country mean universe text phone begin deputy reject result good cram illness common cluster proud swamp digital patrol spread bar face december base kick").unwrap();
let expected_account2 = bip39::Mnemonic::from_str("home mansion start quiz dress decide hint second dragon sunny juice always steak real minimum art rival skin draw total pulp foot goddess agent").unwrap();
assert_eq!(account1.mnemonic(), &expected_account1);
assert_eq!(account2.mnemonic(), &expected_account2);
let StoredAccount::Mnemonic(ref mnemonic1) = account1;
assert_eq!(mnemonic1.mnemonic(), &expected_account1);
assert_eq!(mnemonic1.hd_path(), &cosmos_hd_path);
let StoredAccount::Mnemonic(ref mnemonic2) = account2;
assert_eq!(mnemonic2.mnemonic(), &expected_account2);
assert_eq!(mnemonic2.hd_path(), &cosmos_hd_path);
}
}
@@ -1,7 +1,9 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt;
use serde::{Deserialize, Serialize};
use zeroize::Zeroize;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -19,6 +21,12 @@ impl AsRef<str> for WalletAccountId {
}
}
impl fmt::Display for WalletAccountId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
// simple wrapper for String that will get zeroized on drop
#[derive(Zeroize)]
#[zeroize(drop)]
@@ -0,0 +1,21 @@
{
"version": 1,
"accounts": [
{
"id": "first",
"account": {
"ciphertext": "icnpxLmr/H7FIIOaEf7DYNLuM6uhh7poEppXpYCllQD33TjY+8eLtVvhEQmjX60IQeFOd+1JCcrHa2B12vlBAYlfM4gBxA6d2ZJ8+Dw/vNvBNyChiyUx2euV3vPGOs22r/XDBsmEeF40XZcXftQZa2kzYaPnkbP+eiMOIWkcY4FYOEHwx5SxT4VBPZIrVTC3iDalJLWybVbbw/Bc2zbzEXI1ckg4Ccydj95SMil9BiyDpALfZqwlai7I97S+BjmcVxSCsYqFjTkRUHVMjrEr7fWHKU4DIOM=",
"salt": "CtnbfkxTybqz0U4cPHW2jQ==",
"iv": "77ZROU6dAMttEWwS"
}
},
{
"id": "second",
"account": {
"ciphertext": "nsqZHdQFlskglc5izKgnr8sBwdMmd82h2Rnjdos9EUca3cqkUdFYEjZDsK8OGR3GZ9alLTNt/1U97Rvvr2HPAWbzl23FW2YXaLTA6yj6ZwQK5w0MYE061NYbcxNHuzT9f5aQWkGULAk4RWb5t8eUX7y/NdJr3tA5xuGOLhooTfBB98/4RpupDsYGZp1DPC/GMFppOA3GmKs9bacZm805Bhfq5mwhXab1SjJQpFHMHisCMhxo/oLqulKML1tQMetBdqDTjJmPpdUnd1mi",
"salt": "J2TMLjKv4dkZ/kXso9FGhg==",
"iv": "CTqqoMa4LetvBKCP"
}
}
]
}
+7 -2
View File
@@ -1,7 +1,7 @@
{
"package": {
"productName": "nym-wallet",
"version": "1.0.1"
"version": "1.0.2"
},
"build": {
"distDir": "../dist",
@@ -46,7 +46,12 @@
}
},
"updater": {
"active": false
"active": true,
"endpoints": [
"https://nymtech.net/.wellknown/wallet/updater.json"
],
"dialog": true,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IENCNzQ2M0E5N0VFODE2NApSV1JrZ2U2WE9rYTNETTg1OTBKdE5uWUEra0hML2syOVUvQ2lxZmFZRzZ1T3NWbGM0eVRzUTVhVwo="
},
"allowlist": {
"window": {
@@ -0,0 +1,48 @@
import * as React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { Box, Typography } from '@mui/material';
import { ClientAddressDisplay } from './ClientAddress';
export default {
title: 'Wallet / Client Address',
component: ClientAddressDisplay,
} as ComponentMeta<typeof ClientAddressDisplay>;
const Template: ComponentStory<typeof ClientAddressDisplay> = (args) => (
<Box display="flex" alignContent="center">
<ClientAddressDisplay {...args} />
</Box>
);
export const Default = Template.bind({});
Default.args = {
address: 'n222gnd9k6rytn6tz7pf8d2d4dawl7e9cr26111',
};
export const WithCopy = Template.bind({});
WithCopy.args = {
address: 'n222gnd9k6rytn6tz7pf8d2d4dawl7e9cr26111',
withCopy: true,
};
export const WithLabel = Template.bind({});
WithLabel.args = {
withLabel: true,
address: 'n222gnd9k6rytn6tz7pf8d2d4dawl7e9cr26111',
};
export const ShowEntireAddress = Template.bind({});
ShowEntireAddress.args = {
withLabel: true,
showEntireAddress: true,
address: 'n222gnd9k6rytn6tz7pf8d2d4dawl7e9cr26111',
};
export const Empty = Template.bind({});
Empty.args = {};
export const EmptyWithLabelAndCopy = Template.bind({});
EmptyWithLabelAndCopy.args = {
withLabel: true,
withCopy: true,
};
+51 -13
View File
@@ -1,20 +1,58 @@
import React, { useContext } from 'react';
import { Box, Typography } from '@mui/material';
import React, { FC, useContext } from 'react';
import { Box, Typography, Tooltip } from '@mui/material';
import { ClientContext } from '../context/main';
import { CopyToClipboard } from './CopyToClipboard';
import { splice } from '../utils';
export const ClientAddress = ({ withCopy }: { withCopy?: boolean }) => {
const { clientDetails } = useContext(ClientContext);
const AddressTooltip: FC<{ visible?: boolean; address?: string; children: React.ReactElement<any, any> }> = ({
visible,
address,
children,
}) => {
if (!visible) {
return children;
}
if (!address) {
return children;
}
return (
<Box>
<Typography variant="body2" component="span" sx={{ color: 'grey.600' }}>
Address:
</Typography>{' '}
<Typography variant="body2" component="span" color="nym.background.dark" sx={{ mr: 1 }}>
{splice(4, 35, clientDetails?.client_address)}
</Typography>
{withCopy && <CopyToClipboard text={clientDetails?.client_address} iconButton />}
</Box>
<Tooltip title={address} arrow>
{children}
</Tooltip>
);
};
type ClientAddressProps = {
withLabel?: boolean;
withCopy?: boolean;
showEntireAddress?: boolean;
};
export const ClientAddressDisplay: FC<ClientAddressProps & { address?: string }> = ({
withLabel,
withCopy,
showEntireAddress,
address,
}) => (
<Box>
{withLabel && (
<>
<Typography variant="body2" component="span" sx={{ color: 'grey.600' }}>
Address:
</Typography>{' '}
</>
)}
<AddressTooltip address={address} visible={!showEntireAddress}>
<Typography variant="body2" component="span" color="nym.background.dark" sx={{ mr: 1 }}>
{showEntireAddress ? address || '' : splice(6, address)}
</Typography>
</AddressTooltip>
{withCopy && <CopyToClipboard text={address} iconButton />}
</Box>
);
export const ClientAddress: FC<ClientAddressProps> = ({ ...props }) => {
const { clientDetails } = useContext(ClientContext);
return <ClientAddressDisplay {...props} address={clientDetails?.client_address} />;
};
+21 -7
View File
@@ -1,10 +1,11 @@
import React, { useMemo, createContext, useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { useSnackbar } from 'notistack';
import { TLoginType } from 'src/pages/sign-in/types';
import { Account, Network, TCurrency, TMixnodeBondDetails } from '../types';
import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance';
import { config } from '../../config';
import { getMixnodeBondDetails, selectNetwork, signInWithMnemonic, signOut } from '../requests';
import { getMixnodeBondDetails, selectNetwork, signInWithMnemonic, signInWithPassword, signOut } from '../requests';
import { currencyMap } from '../utils';
import { Console } from '../utils/console';
@@ -32,11 +33,14 @@ type TClientContext = {
currency?: TCurrency;
isLoading: boolean;
error?: string;
setIsLoading: (isLoading: boolean) => void;
setError: (value?: string) => void;
switchNetwork: (network: Network) => void;
getBondDetails: () => Promise<void>;
handleShowSettings: () => void;
handleShowAdmin: () => void;
logIn: (mnemonic: string) => void;
logIn: (opts: { type: 'mnemonic' | 'password'; value: string }) => void;
signInWithPassword: (password: string) => void;
logOut: () => void;
};
@@ -90,17 +94,24 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
refreshAccount();
}, [network]);
const logIn = async (mnemonic: string) => {
const logIn = async ({ type, value }: { type: TLoginType; value: string }) => {
if (value.length === 0) {
setError(`A ${type} must be provided`);
return;
}
try {
setIsLoading(true);
await signInWithMnemonic(mnemonic || '');
await getBondDetails();
if (type === 'mnemonic') {
await signInWithMnemonic(value);
} else {
await signInWithPassword(value);
}
setNetwork('MAINNET');
history.push('/balance');
} catch (e) {
setIsLoading(false);
setError(e as string);
} finally {
history.push('/balance');
setIsLoading(false);
}
};
@@ -131,6 +142,9 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
showSettings,
network,
currency,
setIsLoading,
setError,
signInWithPassword,
switchNetwork,
getBondDetails,
handleShowSettings,
+7 -4
View File
@@ -3,13 +3,14 @@ import ReactDOM from 'react-dom';
import { ErrorBoundary } from 'react-error-boundary';
import { BrowserRouter as Router } from 'react-router-dom';
import { SnackbarProvider } from 'notistack';
import { Routes } from './routes';
import { AppRoutes, SignInRoutes } from './routes';
import { ClientContext, ClientContextProvider } from './context/main';
import { ApplicationLayout } from './layouts';
import { Admin, Welcome, Settings } from './pages';
import { Admin, Settings } from './pages';
import { ErrorFallback } from './components';
import { NymWalletTheme, WelcomeTheme } from './theme';
import { maximizeWindow } from './utils';
import { SignInProvider } from './pages/sign-in/context';
const App = () => {
const { clientDetails } = useContext(ClientContext);
@@ -20,14 +21,16 @@ const App = () => {
return !clientDetails ? (
<WelcomeTheme>
<Welcome />
<SignInProvider>
<SignInRoutes />
</SignInProvider>
</WelcomeTheme>
) : (
<NymWalletTheme>
<ApplicationLayout>
<Settings />
<Admin />
<Routes />
<AppRoutes />
</ApplicationLayout>
</NymWalletTheme>
);
+33 -29
View File
@@ -1,40 +1,44 @@
import React from 'react';
import { NymLogo } from '@nymproject/react';
import { NymWordmark } from '@nymproject/react';
import { Box, Container } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { AppBar, Nav } from '../components';
export const ApplicationLayout: React.FC = ({ children }) => (
<Box
sx={{
height: '100vh',
width: '100vw',
display: 'grid',
gridTemplateColumns: '240px auto',
gridTemplateRows: '100%',
overflow: 'hidden',
}}
>
export const ApplicationLayout: React.FC = ({ children }) => {
const theme = useTheme();
return (
<Box
sx={{
background: '#121726',
overflow: 'auto',
py: 4,
px: 5,
height: '100vh',
width: '100vw',
display: 'grid',
gridTemplateColumns: '240px auto',
gridTemplateRows: '100%',
overflow: 'hidden',
}}
display="flex"
flexDirection="column"
justifyContent="space-between"
>
<Box>
<Box sx={{ mb: 3 }}>
<NymLogo width={45} />
<Box
sx={{
background: '#121726',
overflow: 'auto',
py: 4,
px: 5,
}}
display="flex"
flexDirection="column"
justifyContent="space-between"
>
<Box>
<Box sx={{ mb: 4 }}>
<NymWordmark height={14} fill={theme.palette.background.paper} />
</Box>
<Nav />
</Box>
<Nav />
</Box>
<Container>
<AppBar />
{children}
</Container>
</Box>
<Container>
<AppBar />
{children}
</Container>
</Box>
);
);
};
+1 -1
View File
@@ -5,7 +5,7 @@ export * from './delegate';
export * from './internal-docs';
export * from './receive';
export * from './send';
export * from './welcome';
export * from './sign-in';
export * from './settings';
export * from './unbond';
export * from './undelegate';
+1 -1
View File
@@ -16,7 +16,7 @@ export const Receive = () => {
You can receive tokens by providing this address to the sender
</Alert>
<Box>
<ClientAddress withCopy />
<ClientAddress withCopy showEntireAddress />
</Box>
{clientDetails && <QRCode data-testid="qr-code" value={clientDetails?.client_address} />}
</Stack>
@@ -16,6 +16,9 @@ export const SendConfirmation = ({
isLoading: boolean;
}) => {
const { userBalance, currency, network } = useContext(ClientContext);
if (!data && !error && !isLoading) return null;
return (
<Box
sx={{
+13 -8
View File
@@ -128,13 +128,9 @@ export const SendWizard = () => {
px: 3,
}}
>
{activeStep === 0 ? (
<SendForm />
) : activeStep === 1 ? (
<SendReview transferFee={transferFee} />
) : (
<SendConfirmation data={confirmedData} isLoading={isLoading} error={requestError} />
)}
{activeStep === 0 && <SendForm />}
{activeStep === 1 && <SendReview transferFee={transferFee} />}
<SendConfirmation data={confirmedData} isLoading={isLoading} error={requestError} />
</Box>
<Box
sx={{
@@ -154,7 +150,16 @@ export const SendWizard = () => {
color="primary"
disableElevation
data-testid="button"
onClick={activeStep === 0 ? handleNextStep : activeStep === 1 ? handleSend : handleFinish}
onClick={() => {
switch (activeStep) {
case 0:
return handleNextStep();
case 1:
return handleSend();
default:
return handleFinish();
}
}}
disabled={!!(methods.formState.errors.amount || methods.formState.errors.to || isLoading)}
size="large"
>
@@ -139,13 +139,13 @@ export const SystemVariables = ({
pt: 0,
}}
>
{nodeUpdateResponse === 'success' ? (
{nodeUpdateResponse === 'success' && (
<Typography sx={{ color: 'success.main', fontWeight: 600 }}>Node successfully updated</Typography>
) : nodeUpdateResponse === 'failed' ? (
<Typography sx={{ color: 'error.main', fontWeight: 600 }}>Node update failed</Typography>
) : (
<Fee feeType="UpdateMixnodeConfig" />
)}
{nodeUpdateResponse === 'failed' && (
<Typography sx={{ color: 'error.main', fontWeight: 600 }}>Node update failed</Typography>
)}
{!nodeUpdateResponse && <Fee feeType="UpdateMixnodeConfig" />}
<Button
variant="contained"
color="primary"
@@ -0,0 +1,8 @@
import React from 'react';
import { Alert } from '@mui/material';
export const Error = ({ message }: { message: string }) => (
<Alert severity="error" variant="outlined" data-testid="error" sx={{ color: 'error.light', width: '100%' }}>
{message}
</Alert>
);
@@ -6,7 +6,7 @@ export const Title = ({ title }: { title: string }) => (
);
export const Subtitle = ({ subtitle }: { subtitle: string }) => (
<Typography sx={{ color: 'common.white', textAlign: 'center', maxWidth: 400 }}>{subtitle}</Typography>
<Typography sx={{ color: 'common.white', textAlign: 'center', maxWidth: 450 }}>{subtitle}</Typography>
);
export const SubtitleSlick = ({ subtitle }: { subtitle: string }) => (
@@ -2,3 +2,7 @@ export * from './heading';
export * from './word-tiles';
export * from './render-page';
export * from './password-strength';
export * from './error';
export * from './textfields';
export * from './step';
export * from './page-layout';

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