Compare commits

...

43 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
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
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
85 changed files with 1950 additions and 1011 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*
Generated
+62 -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",
@@ -742,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"
@@ -749,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",
@@ -767,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",
@@ -866,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",
@@ -1113,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",
@@ -1364,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]]
@@ -1512,7 +1533,7 @@ dependencies = [
[[package]]
name = "explorer-api"
version = "0.1.0"
version = "1.0.0-rc.1"
dependencies = [
"chrono",
"humantime-serde",
@@ -1856,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",
@@ -2952,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",
@@ -2986,7 +3007,7 @@ dependencies = [
[[package]]
name = "nym-gateway"
version = "0.12.1"
version = "1.0.0-rc.1"
dependencies = [
"anyhow",
"async-trait",
@@ -3032,7 +3053,7 @@ dependencies = [
[[package]]
name = "nym-mixnode"
version = "0.12.1"
version = "1.0.0-rc.1"
dependencies = [
"anyhow",
"bs58",
@@ -3070,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",
@@ -3091,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",
@@ -3126,7 +3147,7 @@ dependencies = [
[[package]]
name = "nym-validator-api"
version = "0.12.0"
version = "1.0.0-rc.1"
dependencies = [
"anyhow",
"async-trait",
@@ -3163,6 +3184,7 @@ dependencies = [
"thiserror",
"time 0.3.7",
"tokio",
"tokio-stream",
"topology",
"url",
"validator-api-requests",
@@ -5944,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",
@@ -6071,7 +6093,7 @@ dependencies = [
"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",
@@ -6143,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",
@@ -6161,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,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
),
@@ -32,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"
@@ -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<'_>,
@@ -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]
+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(
+24 -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",
@@ -241,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",
]
@@ -273,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",
@@ -290,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",
@@ -383,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",
@@ -473,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]]
@@ -843,7 +833,7 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]]
name = "mixnet-contract"
version = "0.1.0"
version = "1.0.0"
dependencies = [
"az",
"bs58",
@@ -852,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",
@@ -1476,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",
@@ -1550,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",
@@ -1568,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"] }
+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?;
+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>",
+404 -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"
@@ -562,6 +681,15 @@ dependencies = [
"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"
@@ -650,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",
@@ -661,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",
@@ -683,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",
@@ -907,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",
@@ -1035,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"
@@ -1151,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"
@@ -1198,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]]
@@ -1265,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"
@@ -1278,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"
@@ -1411,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"
@@ -2514,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"
@@ -2661,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"
@@ -2759,7 +2945,7 @@ dependencies = [
[[package]]
name = "nym_wallet"
version = "1.0.1"
version = "1.0.2"
dependencies = [
"aes-gcm",
"argon2",
@@ -2828,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"
@@ -2898,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"
@@ -3252,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"
@@ -3729,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"
@@ -4116,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"
@@ -4498,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",
@@ -4511,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",
@@ -5182,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",
@@ -5200,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",
@@ -5423,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"
@@ -5619,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"
@@ -5682,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",
]
+2 -2
View File
@@ -34,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"] }
@@ -46,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 = [
+63 -16
View File
@@ -1,6 +1,7 @@
// 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;
@@ -18,6 +19,9 @@ use url::Url;
pub const REMOTE_SOURCE_OF_VALIDATOR_URLS: &str =
"https://nymtech.net/.wellknown/wallet/validators.json";
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.
@@ -36,27 +40,25 @@ struct Base {
networks: SupportedNetworks,
}
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
pub struct GlobalConfig {
version: Option<u32>,
// TODO: there are no global settings (yet)
}
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq, Eq)]
#[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
// Additional user provided validators.
// It is an option for the purpuse of file serialization.
validator_urls: Option<Vec<ValidatorUrl>>,
}
impl NetworkConfig {
fn validators(&self) -> impl Iterator<Item = &ValidatorUrl> {
self.validator_urls.iter().flat_map(|v| v.iter())
}
}
impl Default for Base {
fn default() -> Self {
let networks = WalletNetwork::iter().map(Into::into).collect();
@@ -66,6 +68,31 @@ impl Default for Base {
}
}
impl Default for GlobalConfig {
fn default() -> Self {
Self {
version: Some(CURRENT_GLOBAL_CONFIG_VERSION),
}
}
}
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,
}
}
}
impl NetworkConfig {
fn validators(&self) -> impl Iterator<Item = &ValidatorUrl> {
self.validator_urls.iter().flat_map(|v| v.iter())
}
}
impl Config {
fn root_directory() -> PathBuf {
tauri::api::path::config_dir().expect("Failed to get config directory")
@@ -259,11 +286,11 @@ impl Config {
}
pub fn add_validator_url(&mut self, url: ValidatorUrl, network: WalletNetwork) {
if let Some(net) = self.networks.get_mut(&network.as_key()) {
if let Some(ref mut urls) = net.validator_urls {
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 {
net.validator_urls = Some(vec![url]);
network_config.validator_urls = Some(vec![url]);
}
} else {
self.networks.insert(
@@ -276,9 +303,13 @@ impl Config {
}
}
#[allow(unused)]
pub fn remove_validator_url(&mut self, _url: ValidatorUrl, _network: WalletNetwork) {
todo!();
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);
}
}
}
}
@@ -312,6 +343,20 @@ impl TryFrom<ValidatorDetails> for ValidatorUrl {
}
}
impl TryFrom<network_config::Validator> for ValidatorUrl {
type Error = BackendError;
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);
@@ -389,6 +434,7 @@ mod tests {
api_url: Some("https://baz/api".parse().unwrap()),
},
]),
..NetworkConfig::default()
};
Config {
@@ -406,7 +452,8 @@ mod tests {
let netconfig = &config.networks[&WalletNetwork::MAINNET.as_key()];
assert_eq!(
toml::to_string_pretty(netconfig).unwrap(),
r#"selected_api_url = 'https://my_api_url.com/'
r#"version = 1
selected_api_url = 'https://my_api_url.com/'
[[validator_urls]]
nymd_url = 'https://foo/'
+12 -6
View File
@@ -14,6 +14,7 @@ mod config;
mod error;
mod menu;
mod network;
mod network_config;
mod operations;
mod platform_constants;
mod state;
@@ -35,19 +36,15 @@ fn main() {
.manage(Arc::new(RwLock::new(State::default())))
.invoke_handler(tauri::generate_handler![
mixnet::account::connect_with_mnemonic,
mixnet::account::validate_mnemonic,
mixnet::account::create_new_account,
mixnet::account::create_new_mnemonic,
mixnet::account::create_password,
mixnet::account::does_password_file_exist,
mixnet::account::get_balance,
mixnet::account::get_validator_nymd_urls,
mixnet::account::get_validator_api_urls,
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,
@@ -61,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,
@@ -81,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,
+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(())
}
@@ -2,6 +2,7 @@ use crate::coin::{Coin, Denom};
use crate::config::Config;
use crate::error::BackendError;
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};
@@ -58,13 +59,6 @@ pub struct Balance {
printable_balance: String,
}
#[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 {
urls: Vec<String>,
}
#[tauri::command]
pub async fn connect_with_mnemonic(
mnemonic: String,
@@ -113,7 +107,7 @@ 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())
}
@@ -157,42 +151,6 @@ 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(())
}
#[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 })
}
async fn _connect_with_mnemonic(
mnemonic: Mnemonic,
state: tauri::State<'_, Arc<RwLock<State>>>,
@@ -202,7 +160,7 @@ async fn _connect_with_mnemonic(
w_state.load_config_files();
}
update_validator_urls(state.clone()).await?;
network_config::update_validator_urls(state.clone()).await?;
let config = {
let state = state.read().await;
+41 -4
View File
@@ -7,11 +7,28 @@ 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,
@@ -30,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
@@ -37,6 +64,14 @@ impl State {
.ok_or(BackendError::ClientNotInitialized)
}
#[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
}
@@ -124,32 +159,34 @@ impl State {
Ok(())
}
#[allow(unused)]
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(())
}
#[allow(unused)]
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(())
}
#[allow(unused)]
pub fn add_validator_url(&mut self, url: ValidatorUrl, network: Network) {
self.config.add_validator_url(url, network);
}
#[allow(unused)]
pub fn remove_validator_url(&mut self, url: ValidatorUrl, network: Network) {
self.config.remove_validator_url(url, network)
}
+6 -1
View File
@@ -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} />;
};
+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
@@ -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>
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Box, IconButton, Link, Stack, TextField } from '@mui/material';
import { Box, IconButton, Stack, TextField } from '@mui/material';
import { Visibility, VisibilityOff } from '@mui/icons-material';
import { Error } from './error';
@@ -37,10 +37,9 @@ export const PasswordInput: React.FC<{
password: string;
error?: string;
label: string;
showForgottenPassword?: boolean;
autoFocus?: boolean;
onUpdatePassword: (password: string) => void;
}> = ({ password, label, error, showForgottenPassword, autoFocus, onUpdatePassword }) => {
}> = ({ password, label, error, autoFocus, onUpdatePassword }) => {
const [showPassword, setShowPassword] = useState(false);
return (
@@ -61,18 +60,6 @@ export const PasswordInput: React.FC<{
),
}}
/>
{/* currently unused */}
{showForgottenPassword && (
<Link
underline="none"
variant="body2"
component="div"
sx={{ mt: 1, textAlign: 'right', color: 'info.main', cursor: 'pointer' }}
href="/forgotten-password"
>
Forgotten password?
</Link>
)}
</Box>
{error && <Error message={error} />}
</Stack>
@@ -11,7 +11,9 @@ export const CreateMnemonic = () => {
const history = useHistory();
useEffect(() => {
generateMnemonic();
if (mnemonicWords.length === 0) {
generateMnemonic();
}
}, []);
const { copy, copied } = useClipboard({ copiedTimeout: 5000 });
@@ -23,8 +25,9 @@ export const CreateMnemonic = () => {
</Typography>
<Alert variant="outlined" severity="warning" sx={{ textAlign: 'center' }}>
<Typography>Below is your 24 word mnemonic, please store the mnemonic in a safe place.</Typography>
<Typography>This is the only way to access your wallet!</Typography>
<Typography>
Below is your 24 word mnemonic, make sure to store it in a safe place for accessing your wallet in the future.
</Typography>
</Alert>
<WordTiles mnemonicWords={mnemonicWords} showIndex />
@@ -19,11 +19,11 @@ export const ExistingAccount = () => {
Sign in with password
</Button>
<Box display="flex" justifyContent="space-between">
<Button color="inherit" onClick={() => history.goBack()}>
<Button color="inherit" onClick={() => history.push('/welcome')}>
Back
</Button>
<Button color="info" onClick={() => history.push('/confirm-mnemonic')}>
Create a password
<Button color="info" onClick={() => history.push('/sign-in-mnemonic')}>
Forgot password?
</Button>
</Box>
</Stack>
@@ -1,14 +1,30 @@
import React, { useContext, useState } from 'react';
import React, { useContext, useState, useEffect } from 'react';
import { Box, Button, FormControl, LinearProgress, Stack } from '@mui/material';
import { useHistory } from 'react-router-dom';
import { isPasswordCreated } from 'src/requests';
import { MnemonicInput, Subtitle } from '../components';
import { ClientContext } from '../../../context/main';
export const SignInMnemonic = () => {
const [mnemonic, setMnemonic] = useState('');
const { setError, logIn, error, isLoading } = useContext(ClientContext);
const [passwordExists, setPasswordExists] = useState(true);
const history = useHistory();
const checkForPassword = async () => {
const hasPassword = await isPasswordCreated();
setPasswordExists(hasPassword);
};
const handlePageChange = (page: string) => {
setError(undefined);
history.push(page);
};
useEffect(() => {
checkForPassword();
}, []);
if (isLoading)
return (
<Box width="25%">
@@ -30,19 +46,16 @@ export const SignInMnemonic = () => {
>
Sign in with mnemonic
</Button>
<Button
variant="outlined"
disableElevation
size="large"
onClick={() => {
setError(undefined);
history.push('/existing-account');
}}
fullWidth
sx={{ color: 'common.white', border: '1px solid white', '&:hover': { border: '1px solid white' } }}
>
Back
</Button>
<Box display="flex" justifyContent={passwordExists ? 'center' : 'space-between'}>
<Button color="inherit" onClick={() => handlePageChange('/existing-account')}>
Back
</Button>
{!passwordExists && (
<Button color="info" onClick={() => handlePageChange('/confirm-mnemonic')}>
Create a password
</Button>
)}
</Box>
</Stack>
</FormControl>
</Stack>
@@ -1,6 +1,6 @@
import React, { useContext, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { Button, LinearProgress, FormControl, Stack, Box } from '@mui/material';
import { Box, Button, LinearProgress, FormControl, Stack } from '@mui/material';
import { PasswordInput, Subtitle } from '../components';
import { ClientContext } from '../../../context/main';
@@ -36,19 +36,29 @@ export const SignInPassword = () => {
>
Sign in with password
</Button>
<Button
variant="outlined"
disableElevation
size="large"
onClick={() => {
setError(undefined);
history.push('/existing-account');
}}
fullWidth
sx={{ color: 'common.white', border: '1px solid white', '&:hover': { border: '1px solid white' } }}
>
Back
</Button>
<Box display="flex" justifyContent="space-between">
<Button
color="inherit"
disableElevation
onClick={() => {
setError(undefined);
history.push('/existing-account');
}}
>
Back
</Button>
<Button
color="info"
onClick={() => {
setError(undefined);
history.push('/sign-in-mnemonic');
}}
size="small"
>
Forgotten password?
</Button>
</Box>
</Stack>
</FormControl>
</Stack>
@@ -59,6 +59,9 @@ export const VerifyMnemonic = () => {
>
Next
</Button>
<Button color="inherit" fullWidth size="large" onClick={() => history.goBack()}>
Back
</Button>
</Stack>
</>
);
+5
View File
@@ -25,3 +25,8 @@ export const signInWithPassword = async (password: string): Promise<Account> =>
export const signOut = async (): Promise<void> => {
await invoke('logout');
};
export const isPasswordCreated = async (): Promise<boolean> => {
const res: boolean = await invoke('does_password_file_exist');
return res;
};
@@ -1,3 +1,8 @@
export interface ValidatorUrls {
urls: Array<string>;
}
export interface Validator {
nymd_url: string;
api_url: string | null;
}
+4 -8
View File
@@ -1,7 +1,7 @@
import { invoke } from '@tauri-apps/api';
import { appWindow } from '@tauri-apps/api/window';
import bs58 from 'bs58';
import { minor, valid } from 'semver';
import { valid } from 'semver';
import { userBalance, majorToMinor, getLockedCoins, getSpendableCoins } from '../requests';
import { Coin, Network, TCurrency } from '../types';
import { Console } from './console';
@@ -71,9 +71,7 @@ export const isValidHostname = (value: string) => {
export const validateVersion = (version: string): boolean => {
try {
const minorVersion = minor(version);
const validVersion = valid(version);
return validVersion !== null && minorVersion >= 11;
return valid(version) !== null;
} catch (e) {
return false;
}
@@ -134,11 +132,9 @@ export const currencyMap = (network?: Network): TCurrency => {
}
};
export const splice = (start: number, deleteCount: number, address?: string): string => {
export const splice = (size: number, address?: string): string => {
if (address) {
const array = address.split('');
array.splice(start, deleteCount, '...');
return array.join('');
return `${address.slice(0, size)}...${address.slice(-size)}`;
}
return '';
};
@@ -3,7 +3,7 @@
[package]
name = "nym-network-requester"
version = "0.12.0"
version = "1.0.0-rc.1"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
edition = "2021"
@@ -3,7 +3,7 @@ import Wordmark from '@assets/logo/logo-wordmark.svg';
import { useTheme } from '@mui/material';
import { LogoProps } from './LogoProps';
export const NymWordmark: React.FC<LogoProps> = ({ height, width }) => {
export const NymWordmark: React.FC<LogoProps & { fill?: string }> = ({ height, width, fill }) => {
const theme = useTheme();
return <Wordmark height={height} width={width} fill={theme.palette.text.primary} />;
return <Wordmark height={height} width={width} fill={fill || theme.palette.text.primary} />;
};
+2 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-validator-api"
version = "0.12.0"
version = "1.0.0-rc.1"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
@@ -31,6 +31,7 @@ rocket = { version = "0.5.0-rc.1", features = ["json"] }
serde = "1.0"
serde_json = "1.0"
tokio = { version = "1.4", features = ["rt-multi-thread", "macros", "signal", "time"] }
tokio-stream = "0.1.8"
rocket_cors = { git="https://github.com/lawliet89/rocket_cors", rev="dfd3662c49e2f6fc37df35091cb94d82f7fb5915" }
url = "2.2"
thiserror = "1"
+22 -6
View File
@@ -34,7 +34,8 @@ const DEFAULT_PER_NODE_TEST_PACKETS: usize = 3;
const DEFAULT_CACHE_INTERVAL: Duration = Duration::from_secs(30);
const DEFAULT_MONITOR_THRESHOLD: u8 = 60;
const DEFAULT_MIN_RELIABILITY: u8 = 50;
const DEFAULT_MIN_MIXNODE_RELIABILITY: u8 = 50;
const DEFAULT_MIN_GATEWAY_RELIABILITY: u8 = 20;
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct Config {
@@ -109,8 +110,8 @@ impl Default for Base {
#[serde(default)]
pub struct NetworkMonitor {
// Mixnodes and gateways with relialability lower the this get blacklisted by network monitor, get no traffic and cannot be selected into a rewarded set.
min_reliability: u8,
min_mixnode_reliability: u8, // defaults to 50
min_gateway_reliability: u8, // defaults to 20
/// Specifies whether network monitoring service is enabled in this process.
enabled: bool,
@@ -192,7 +193,8 @@ impl NetworkMonitor {
impl Default for NetworkMonitor {
fn default() -> Self {
NetworkMonitor {
min_reliability: DEFAULT_MIN_RELIABILITY,
min_mixnode_reliability: DEFAULT_MIN_MIXNODE_RELIABILITY,
min_gateway_reliability: DEFAULT_MIN_GATEWAY_RELIABILITY,
enabled: false,
testnet_mode: false,
all_validator_apis: default_api_endpoints(),
@@ -334,6 +336,16 @@ impl Config {
self
}
pub fn with_min_mixnode_reliability(mut self, min_mixnode_reliability: u8) -> Self {
self.network_monitor.min_mixnode_reliability = min_mixnode_reliability;
self
}
pub fn with_min_gateway_reliability(mut self, min_gateway_reliability: u8) -> Self {
self.network_monitor.min_gateway_reliability = min_gateway_reliability;
self
}
#[cfg(not(feature = "coconut"))]
pub fn with_eth_private_key(mut self, eth_private_key: String) -> Self {
self.network_monitor.eth_private_key = eth_private_key;
@@ -423,8 +435,12 @@ impl Config {
self.network_monitor.minimum_test_routes
}
pub fn get_min_reliability(&self) -> u8 {
self.network_monitor.min_reliability
pub fn get_min_mixnode_reliability(&self) -> u8 {
self.network_monitor.min_mixnode_reliability
}
pub fn get_min_gateway_reliability(&self) -> u8 {
self.network_monitor.min_gateway_reliability
}
pub fn get_route_test_packets(&self) -> usize {
+2 -1
View File
@@ -21,7 +21,8 @@ mixnet_contract_address = '{{ base.mixnet_contract_address }}'
[network_monitor]
# Mixnodes and gateways with relialability lower the this get blacklisted by network monitor, get no traffic and cannot be selected into a rewarded set.
min_reliability = {{ network_monitor.min_reliability }}
min_mixnode_reliability = {{ network_monitor.min_mixnode_reliability }} # deafults to 50
min_gateway_reliability = {{ network_monitor.min_gateway_reliability }} # defaults to 20
# Specifies whether network monitoring service is enabled in this process.
enabled = {{ network_monitor.enabled }}
+21
View File
@@ -67,6 +67,9 @@ const ETH_PRIVATE_KEY: &str = "eth_private_key";
const REWARDING_MONITOR_THRESHOLD_ARG: &str = "monitor-threshold";
const MIN_MIXNODE_RELIABILITY_ARG: &str = "min_mixnode_reliability";
const MIN_GATEWAY_RELIABILITY_ARG: &str = "min_gateway_reliability";
fn parse_validators(raw: &str) -> Vec<Url> {
raw.split(',')
.map(|raw_validator| {
@@ -284,6 +287,24 @@ fn override_config(mut config: Config, matches: &ArgMatches<'_>) -> Config {
config = config.with_minimum_interval_monitor_threshold(monitor_threshold)
}
if let Some(reliability) = matches
.value_of(MIN_MIXNODE_RELIABILITY_ARG)
.map(|t| t.parse::<u8>())
{
config = config.with_min_mixnode_reliability(
reliability.expect("Provided reliability is not a u8 number!"),
)
}
if let Some(reliability) = matches
.value_of(MIN_GATEWAY_RELIABILITY_ARG)
.map(|t| t.parse::<u8>())
{
config = config.with_min_gateway_reliability(
reliability.expect("Provided reliability is not a u8 number!"),
)
}
#[cfg(feature = "coconut")]
if let Some(keypair_path) = matches.value_of(KEYPAIR_ARG) {
let keypair_bs58 = std::fs::read_to_string(keypair_path)
@@ -2,181 +2,41 @@
// SPDX-License-Identifier: Apache-2.0
use crypto::asymmetric::identity;
use futures::stream::Stream;
use futures::task::Context;
use gateway_client::{AcknowledgementReceiver, MixnetMessageReceiver};
use std::pin::Pin;
use std::task::{Poll, Waker};
use tokio_stream::StreamMap;
/// Constant used to determine maximum number of times the GatewayReader can poll. It basically
/// tries to solve the same problem that `FuturesUnordered` has: https://github.com/rust-lang/futures-rs/issues/2047
const YIELD_EVERY: usize = 32;
// TODO: Originally I set it to (identity::PublicKey, Vec<Vec<u8>>) and I definitely
// had a reason for doing so, but right now I can't remember what that was...
pub(crate) type GatewayMessages = Vec<Vec<u8>>;
pub(crate) struct GatewayChannel {
id: identity::PublicKey,
message_receiver: MixnetMessageReceiver,
ack_receiver: AcknowledgementReceiver,
is_closed: bool,
}
impl GatewayChannel {
pub(crate) fn new(
id: identity::PublicKey,
message_receiver: MixnetMessageReceiver,
ack_receiver: AcknowledgementReceiver,
) -> Self {
GatewayChannel {
id,
message_receiver,
ack_receiver,
is_closed: false,
}
}
}
impl Stream for GatewayChannel {
type Item = Vec<Vec<u8>>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
if self.is_closed {
return Poll::Ready(None);
}
let mut polled = 0;
// empty the ack channel if anything is on it (we don't care about the content at all at the
// moment)
while let Poll::Ready(_ack) = Pin::new(&mut self.ack_receiver).poll_next(cx) {
polled += 1;
}
let item = futures::ready!(Pin::new(&mut self.message_receiver).poll_next(cx));
match item {
None => Poll::Ready(None),
// if we managed to get an item, try to also read additional ones
Some(mut messages) => {
while let Poll::Ready(new_item) = Pin::new(&mut self.message_receiver).poll_next(cx)
{
polled += 1;
match new_item {
None => {
self.is_closed = true;
cx.waker().wake_by_ref();
return Poll::Ready(Some(messages));
}
Some(mut additional_messages) => {
messages.append(&mut additional_messages);
// it is fine enough to use the same constant here as in the main GatewayReader
// as only a single channel, i.e. the main gateway will be capable
// of returning more than 2 values
if polled >= YIELD_EVERY {
cx.waker().wake_by_ref();
break;
}
}
}
}
Poll::Ready(Some(messages))
}
}
}
}
pub(crate) struct GatewaysReader {
latest_read: usize,
// channels: FuturesUnordered<GatewayChannel>,
channels: Vec<GatewayChannel>,
waker: Option<Waker>,
ack_map: StreamMap<String, AcknowledgementReceiver>,
stream_map: StreamMap<String, MixnetMessageReceiver>,
}
impl GatewaysReader {
pub(crate) fn new() -> Self {
GatewaysReader {
latest_read: 0,
channels: Vec::new(),
waker: None,
ack_map: StreamMap::new(),
stream_map: StreamMap::new(),
}
}
fn remove_nth(&mut self, i: usize) {
self.channels.remove(i);
pub fn stream_map(&mut self) -> &mut StreamMap<String, MixnetMessageReceiver> {
&mut self.stream_map
}
// todo: if we find that this method is called frequently, perhaps the vector should get
// replaced with different data structure
pub(crate) fn remove_by_key(&mut self, key: identity::PublicKey) {
match self.channels.iter().position(|item| item.id == key) {
Some(i) => {
self.channels.remove(i);
}
// this shouldn't ever get thrown, so perhaps a panic would be more in order?
None => error!(
"tried to remove gateway reader {} but it doesn't exist!",
key.to_base58_string()
),
}
}
fn poll_nth(
pub fn add_recievers(
&mut self,
cx: &mut Context<'_>,
i: usize,
) -> Option<Poll<Option<GatewayMessages>>> {
if let Poll::Ready(item) = Pin::new(&mut self.channels[i]).poll_next(cx) {
self.latest_read = i;
match item {
// Some(messages) => return Some(Poll::Ready(Some((self.channels[i].id, messages)))),
Some(messages) => return Some(Poll::Ready(Some(messages))),
// remove dead channel
None => self.remove_nth(i),
}
}
None
id: identity::PublicKey,
message_receiver: MixnetMessageReceiver,
ack_receiver: AcknowledgementReceiver,
) {
let channel_id = id.to_string();
self.stream_map.insert(channel_id.clone(), message_receiver);
self.ack_map.insert(channel_id, ack_receiver);
}
pub(crate) fn insert_channel(&mut self, channel: GatewayChannel) {
self.channels.push(channel);
if let Some(waker) = self.waker.take() {
waker.wake()
}
}
}
// TODO: not sure if this will scale well, but I don't know what would be a good alternative,
// perhaps try to somehow incorporate FuturesUnordered?
// also, perhaps reading should be done in parallel?
impl Stream for GatewaysReader {
// item represents gateway that returned messages alongside the actual messages
type Item = GatewayMessages;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
if self.latest_read >= self.channels.len() {
self.latest_read = 0;
}
// don't start reading from beginning each time to at least slightly help with the bias
for i in self.latest_read..self.channels.len() {
if let Some(item) = self.poll_nth(cx, i) {
return item;
}
}
for i in 0..self.latest_read {
if let Some(item) = self.poll_nth(cx, i) {
return item;
}
}
// if we have no channels available, store the waker to be woken when a new one is pushed
if self.channels.is_empty() {
self.waker = Some(cx.waker().clone())
}
Poll::Pending
pub fn remove_recievers(&mut self, id: &str) {
self.stream_map.remove(id);
self.ack_map.remove(id);
}
}
@@ -44,14 +44,17 @@ impl GatewayPinger {
}
async fn ping_and_cleanup_all_gateways(&self) {
info!(target: "GatewayPinger", "Pinging all active gateways");
info!("Pinging all active gateways");
let lock_acquire_start = Instant::now();
let active_gateway_clients_guard = self.gateway_clients.lock().await;
trace!(target: "GatewayPinger", "Acquiring lock took {:?}", Instant::now().duration_since(lock_acquire_start));
trace!(
"Acquiring lock took {:?}",
Instant::now().duration_since(lock_acquire_start)
);
if active_gateway_clients_guard.is_empty() {
debug!(target: "GatewayPinger", "no gateways to ping");
debug!("no gateways to ping");
return;
}
@@ -89,7 +92,6 @@ impl GatewayPinger {
{
Err(_timeout) => {
warn!(
target: "GatewayPinger",
"we timed out trying to ping {} - assuming the connection is dead.",
active_client.gateway_identity().to_base58_string(),
);
@@ -97,7 +99,6 @@ impl GatewayPinger {
}
Ok(Err(err)) => {
warn!(
target: "GatewayPinger",
"failed to send ping message to gateway {} - {} - assuming the connection is dead.",
active_client.gateway_identity().to_base58_string(),
err,
@@ -112,25 +113,34 @@ impl GatewayPinger {
}
}
info!(
"Purging {} gateways, acquiring lock",
clients_to_purge.len()
);
// purge all dead connections
// reacquire the guard
let lock_acquire_start = Instant::now();
let mut active_gateway_clients_guard = self.gateway_clients.lock().await;
trace!(target: "GatewayPinger", "Acquiring lock took {:?}", Instant::now().duration_since(lock_acquire_start));
info!(
"Acquiring lock took {:?}",
Instant::now().duration_since(lock_acquire_start)
);
for gateway_id in clients_to_purge.into_iter() {
if let Some(removed_handle) = active_gateway_clients_guard.remove(&gateway_id) {
if !removed_handle.is_invalid().await {
info!("Handle is invalid, purging");
// it was not invalidated by the packet sender meaning it probably was some unbonded node
// that was never cleared
self.notify_connection_failure(gateway_id);
}
info!("Handle is not invalid, not purged")
}
}
let ping_end = Instant::now();
let time_taken = ping_end.duration_since(ping_start);
debug!(target: "GatewayPinger", "Pinging all active gateways took {:?}", time_taken);
info!("Pinging all active gateways took {:?}", time_taken);
}
pub(crate) async fn run(&self) {
@@ -42,7 +42,8 @@ pub(super) struct Monitor {
/// The minimum number of test routes that need to be constructed (and working) in order for
/// a monitor test run to be valid.
minimum_test_routes: usize,
min_reliability: u8,
min_mixnode_reliability: u8,
min_gateway_reliability: u8,
}
impl Monitor {
@@ -67,7 +68,8 @@ impl Monitor {
route_test_packets: config.get_route_test_packets(),
test_routes: config.get_test_routes(),
minimum_test_routes: config.get_minimum_test_routes(),
min_reliability: config.get_min_reliability(),
min_mixnode_reliability: config.get_min_mixnode_reliability(),
min_gateway_reliability: config.get_min_gateway_reliability(),
}
}
@@ -78,7 +80,7 @@ impl Monitor {
// uptime calculations
for result in test_summary.mixnode_results.iter() {
if result.reliability < self.min_reliability {
if result.reliability < self.min_mixnode_reliability {
self.packet_preparer
.validator_cache()
.insert_mixnodes_blacklist(result.identity.clone())
@@ -92,7 +94,7 @@ impl Monitor {
}
for result in test_summary.gateway_results.iter() {
if result.reliability < self.min_reliability {
if result.reliability < self.min_gateway_reliability {
self.packet_preparer
.validator_cache()
.insert_gateways_blacklist(result.identity.clone())
@@ -175,9 +177,9 @@ impl Monitor {
for entry in results.iter() {
if *entry.1 == self.route_test_packets {
debug!("✔️ {} succeeded", entry.0)
info!("✔️ {} succeeded", entry.0)
} else {
debug!(
info!(
"❌️ {} failed ({}/{} received)",
entry.0, entry.1, self.route_test_packets
)
@@ -198,7 +200,7 @@ impl Monitor {
}
async fn prepare_test_routes(&mut self) -> Option<Vec<TestRoute>> {
info!(target: "Monitor", "Generating test routes...");
info!("Generating test routes...");
// keep track of nodes that should not be used for route construction
let mut blacklist = HashSet::new();
@@ -207,7 +209,7 @@ impl Monitor {
let mut current_attempt = 0;
// todo: tweak this to something more appropriate
let max_attempts = self.test_routes * 2;
let max_attempts = self.test_routes * 10;
'outer: loop {
if current_attempt >= max_attempts {
@@ -275,13 +277,12 @@ impl Monitor {
.set_new_test_nonce(self.test_nonce)
.await;
debug!("Sending packets to all gateways...");
info!("Sending packets to all gateways...");
self.packet_sender
.send_packets(prepared_packets.packets)
.await;
debug!(
target: "Monitor",
info!(
"Sending is over, waiting for {:?} before checking what we received",
self.packet_delivery_timeout
);
@@ -291,6 +292,8 @@ impl Monitor {
let received = self.received_processor.return_received().await;
let total_received = received.len();
info!("Test routes: {:?}", routes);
info!("Received {}/{} packets", total_received, total_sent);
let summary = self.summary_producer.produce_summary(
prepared_packets.tested_mixnodes,
@@ -308,11 +311,14 @@ impl Monitor {
}
async fn test_run(&mut self) {
info!(target: "Monitor", "Starting test run no. {}", self.test_nonce);
info!("Starting test run no. {}", self.test_nonce);
let start = Instant::now();
if let Some(test_routes) = self.prepare_test_routes().await {
debug!(target: "Monitor", "Determined reliable routes to test all other nodes against. : {:?}", test_routes);
info!(
"Determined reliable routes to test all other nodes against. : {:?}",
test_routes
);
self.test_network_against(&test_routes).await;
} else {
error!("We failed to construct sufficient number of test routes to test the network against")
@@ -25,6 +25,7 @@ type Id = String;
type Owner = Addr;
#[derive(Clone)]
#[allow(dead_code)]
pub(crate) enum InvalidNode {
Outdated(Id, Owner, Version),
Malformed(Id, Owner),
@@ -188,8 +189,8 @@ impl PacketPreparer {
info!("Waiting for minimal topology to be online");
let initialisation_backoff = Duration::from_secs(30);
loop {
let gateways = self.validator_cache.gateways().await;
let mixnodes = self.validator_cache.rewarded_set().await;
let gateways = self.validator_cache.gateways_all().await;
let mixnodes = self.validator_cache.mixnodes_all().await;
if gateways.len() < minimum_full_routes {
info!(
@@ -201,7 +202,7 @@ impl PacketPreparer {
}
let mut layered_mixes = HashMap::new();
for mix in mixnodes.into_inner() {
for mix in mixnodes {
let layer = mix.layer;
let mixes = layered_mixes.entry(layer).or_insert_with(Vec::new);
mixes.push(mix)
@@ -229,7 +230,7 @@ impl PacketPreparer {
}
async fn all_mixnodes_and_gateways(&self) -> (Vec<MixNodeBond>, Vec<GatewayBond>) {
info!(target: "Monitor", "Obtaining network topology...");
info!("Obtaining network topology...");
let mixnodes = self.validator_cache.mixnodes_all().await;
let gateways = self.validator_cache.gateways_all().await;
@@ -239,9 +240,6 @@ impl PacketPreparer {
pub(crate) fn try_parse_mix_bond(&self, mix: &MixNodeBond) -> Result<mix::Node, String> {
let identity = mix.mix_node.identity_key.clone();
if !self.check_version_compatibility(&mix.mix_node.version) {
return Err(identity);
}
mix.try_into().map_err(|_| identity)
}
@@ -250,9 +248,6 @@ impl PacketPreparer {
gateway: &GatewayBond,
) -> Result<gateway::Node, String> {
let identity = gateway.gateway.identity_key.clone();
if !self.check_version_compatibility(&gateway.gateway.version) {
return Err(identity);
}
gateway.try_into().map_err(|_| identity)
}
@@ -270,19 +265,10 @@ impl PacketPreparer {
// separate mixes into layers for easier selection
let mut layered_mixes = HashMap::new();
for mix in mixnodes {
// filter out mixes on the blacklist
if blacklist.contains(&mix.mix_node.identity_key) {
continue;
}
let layer = mix.layer;
let mixes = layered_mixes.entry(layer).or_insert_with(Vec::new);
mixes.push(mix)
}
// filter out gateways on the blacklist
let gateways = gateways
.into_iter()
.filter(|gateway| !blacklist.contains(&gateway.gateway.identity_key))
.collect::<Vec<_>>();
// get all nodes from each layer...
let l1 = layered_mixes.get(&Layer::One)?;
@@ -297,9 +283,6 @@ impl PacketPreparer {
let rand_l3 = l3.choose_multiple(&mut rng, n).collect::<Vec<_>>();
let rand_gateways = gateways.choose_multiple(&mut rng, n).collect::<Vec<_>>();
// let rand_gateways = gateways.iter().filter(|g| g.gateway.host == "109.74.196.254".to_string()).collect::<Vec<_>>();
// let rand_gateways = gateways.iter().filter(|g| g.gateway.host == "185.3.94.33".to_string()).collect::<Vec<_>>();
// the unwrap on `min()` is fine as we know the iterator is not empty
let most_available = *[
rand_l1.len(),
@@ -312,9 +295,10 @@ impl PacketPreparer {
.unwrap();
if most_available == 0 {
// it's impossible to generate a single route
error!("Cannot construct test routes. No nodes or gateways available");
None
} else {
trace!("Generating test routes...");
let mut routes = Vec::new();
for i in 0..most_available {
let node_1 = match self.try_parse_mix_bond(rand_l1[i]) {
@@ -358,14 +342,11 @@ impl PacketPreparer {
gateway,
))
}
info!("{:?}", routes);
Some(routes)
}
}
fn check_version_compatibility(&self, node_version: &str) -> bool {
version_checker::is_minor_version_compatible(node_version, &self.system_version)
}
fn create_packet_sender(&self, gateway: &gateway::Node) -> Recipient {
Recipient::new(
self.self_public_identity,
@@ -404,14 +385,6 @@ impl PacketPreparer {
let mut parsed_nodes = Vec::new();
let mut invalid_nodes = Vec::new();
for mixnode in nodes {
if !self.check_version_compatibility(&mixnode.mix_node.version) {
invalid_nodes.push(InvalidNode::Outdated(
mixnode.mix_node.identity_key,
mixnode.owner,
mixnode.mix_node.version,
));
continue;
}
if let Ok(parsed_node) = (&mixnode).try_into() {
parsed_nodes.push(parsed_node)
} else {
@@ -431,14 +404,6 @@ impl PacketPreparer {
let mut parsed_nodes = Vec::new();
let mut invalid_nodes = Vec::new();
for gateway in nodes {
if !self.check_version_compatibility(&gateway.gateway.version) {
invalid_nodes.push(InvalidNode::Outdated(
gateway.gateway.identity_key,
gateway.owner,
gateway.gateway.version,
));
continue;
}
if let Ok(parsed_node) = (&gateway).try_into() {
parsed_nodes.push(parsed_node)
} else {
@@ -460,18 +425,17 @@ impl PacketPreparer {
// (remember that "idle" nodes are still part of that set)
// we don't care about other nodes, i.e. nodes that are bonded but will not get
// any reward during the current rewarding interval
let (rewarded_set, all_gateways) = self.all_mixnodes_and_gateways().await;
let (mixnodes, gateways) = self.all_mixnodes_and_gateways().await;
let (mixes, invalid_mixnodes) = self.filter_outdated_and_malformed_mixnodes(rewarded_set);
let (gateways, invalid_gateways) =
self.filter_outdated_and_malformed_gateways(all_gateways);
let (mixnodes, invalid_mixnodes) = self.filter_outdated_and_malformed_mixnodes(mixnodes);
let (gateways, invalid_gateways) = self.filter_outdated_and_malformed_gateways(gateways);
let tested_mixnodes = mixes.iter().map(|node| node.into()).collect::<Vec<_>>();
let tested_mixnodes = mixnodes.iter().map(|node| node.into()).collect::<Vec<_>>();
let tested_gateways = gateways.iter().map(|node| node.into()).collect::<Vec<_>>();
let packets_to_create = (test_routes.len() * self.per_node_test_packets)
* (tested_mixnodes.len() + tested_gateways.len());
info!(target: "TestPreparer", "Need to create {} mix packets", packets_to_create);
info!("Need to create {} mix packets", packets_to_create);
let mut all_gateway_packets = HashMap::new();
@@ -483,10 +447,10 @@ impl PacketPreparer {
let gateway_owner = test_route.gateway_owner();
// it's actually going to be a tiny bit more due to gateway testing, but it's a good enough approximation
let mut mix_packets = Vec::with_capacity(mixes.len() * self.per_node_test_packets);
let mut mix_packets = Vec::with_capacity(mixnodes.len() * self.per_node_test_packets);
// and for each mixnode...
for mixnode in &mixes {
for mixnode in &mixnodes {
let test_packet = TestPacket::from_mixnode(mixnode, test_route.id(), test_nonce);
let topology = test_route.substitute_mix(mixnode);
// produce n mix packets
@@ -1,7 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::network_monitor::gateways_reader::{GatewayChannel, GatewayMessages, GatewaysReader};
use crate::network_monitor::gateways_reader::{GatewayMessages, GatewaysReader};
use crate::network_monitor::monitor::processor::ReceivedProcessorSender;
use crypto::asymmetric::identity;
use futures::channel::mpsc;
@@ -40,10 +40,12 @@ impl PacketReceiver {
fn process_gateway_update(&mut self, update: GatewayClientUpdate) {
match update {
GatewayClientUpdate::New(id, (message_receiver, ack_receiver)) => {
let channel = GatewayChannel::new(id, message_receiver, ack_receiver);
self.gateways_reader.insert_channel(channel);
self.gateways_reader
.add_recievers(id, message_receiver, ack_receiver);
}
GatewayClientUpdate::Failure(id) => {
self.gateways_reader.remove_recievers(&id.to_string());
}
GatewayClientUpdate::Failure(id) => self.gateways_reader.remove_by_key(id),
}
}
@@ -62,7 +64,9 @@ impl PacketReceiver {
// similarly gateway reader will never return a `None` as it's implemented
// as an infinite stream that returns Poll::Pending if it doesn't have anything
// to return
messages = self.gateways_reader.next() => self.process_gateway_messages(messages.unwrap()),
Some((_gateway_id, message)) = self.gateways_reader.stream_map().next() => {
self.process_gateway_messages(message)
}
}
}
}
@@ -232,14 +232,13 @@ impl PacketSender {
) -> Result<(), GatewayClientError> {
let gateway_id = client.gateway_identity().to_base58_string();
info!(
target: "MessageSender",
"Got {} packets to send to gateway {}",
mix_packets.len(),
gateway_id
);
if mix_packets.len() <= max_sending_rate {
debug!(target: "MessageSender","Everything is going to get sent as one.");
debug!("Everything is going to get sent as one.");
client.batch_send_mix_packets(mix_packets).await?;
} else {
let packets_per_time_chunk =
@@ -248,7 +247,6 @@ impl PacketSender {
let total_expected_time =
Duration::from_secs_f64(mix_packets.len() as f64 / max_sending_rate as f64);
info!(
target: "MessageSender",
"With our rate of {} packets/s it should take around {:?} to send it all to {} ...",
max_sending_rate, total_expected_time, gateway_id
);
@@ -268,7 +266,7 @@ impl PacketSender {
// this way we won't have to do reallocations in here as they're unavoidable when
// splitting a vector into multiple vectors
while let Some(retained) = split_off_vec(&mut mix_packets, packets_per_time_chunk) {
trace!(target: "MessageSender","Sending {} packets...", mix_packets.len());
trace!("Sending {} packets...", mix_packets.len());
if mix_packets.len() == 1 {
client.send_mix_packet(mix_packets.pop().unwrap()).await?;
@@ -280,7 +278,7 @@ impl PacketSender {
mix_packets = retained;
}
debug!(target: "MessageSender", "Done sending");
debug!("Done sending");
}
Ok(())