Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6dbb3fbd3f | |||
| 95b6ac50be | |||
| 5f2247ab83 | |||
| 842bcfa782 | |||
| c2938e0672 | |||
| 278516ad11 | |||
| ce241339bf | |||
| 8d7428923a | |||
| 4a1a4b6415 |
@@ -0,0 +1,43 @@
|
||||
name: Publish Nym binaries
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
publish-nym:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ubuntu-latest]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Check the release tag starts with `nym-binaries-`
|
||||
if: startsWith(github.ref, 'refs/tags/nym-binaries-') == false
|
||||
uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
core.setFailed('Release tag did not start with nym-binaries-...')
|
||||
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Build all binaries
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --workspace --release
|
||||
|
||||
- name: Upload to release based on tag name
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
files: |
|
||||
target/release/nym-client
|
||||
target/release/nym-gateway
|
||||
target/release/nym-mixnode
|
||||
target/release/nym-socks5-client
|
||||
target/release/nym-validator-api
|
||||
Generated
+24
-34
@@ -549,7 +549,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "client-core"
|
||||
version = "0.12.0"
|
||||
version = "1.0.0-rc.1"
|
||||
dependencies = [
|
||||
"config",
|
||||
"crypto",
|
||||
@@ -750,31 +750,31 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-crypto"
|
||||
version = "1.0.0-beta6"
|
||||
version = "1.0.0-beta7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dddc1443004c6340e55ca66d98e9d2f1a44aadf4ce2bed2c4f29baa8a15e7b7"
|
||||
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-beta6"
|
||||
version = "1.0.0-beta7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe4f0f10f165b8bcc558a13cddb498140960544519aa0581532c766dd80b5598"
|
||||
checksum = "fa89fcdf8dbbe0088e663d0a814aa7368e7ebe8fb045a3a150fb5fdc2ffe3b45"
|
||||
dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-std"
|
||||
version = "1.0.0-beta6"
|
||||
version = "1.0.0-beta7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0f3145097b692b2d95fa5d2c7c6fdd60f193ccc709857e7e1987a608725300"
|
||||
checksum = "bcb8f99a61d0b9069e1afc80a4ffea87dcc3523edd992080923870b13a677da0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cosmwasm-crypto",
|
||||
@@ -1071,20 +1071,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",
|
||||
@@ -1322,16 +1311,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]]
|
||||
@@ -1470,7 +1460,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "explorer-api"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0-rc.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"humantime-serde",
|
||||
@@ -2902,7 +2892,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "0.12.1"
|
||||
version = "1.0.0-rc.1"
|
||||
dependencies = [
|
||||
"clap 2.34.0",
|
||||
"client-core",
|
||||
@@ -2936,7 +2926,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-gateway"
|
||||
version = "0.12.1"
|
||||
version = "1.0.0-rc.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -2982,7 +2972,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnode"
|
||||
version = "0.12.1"
|
||||
version = "1.0.0-rc.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bs58",
|
||||
@@ -3020,7 +3010,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "0.12.0"
|
||||
version = "1.0.0-rc.1"
|
||||
dependencies = [
|
||||
"clap 2.34.0",
|
||||
"dirs",
|
||||
@@ -3041,7 +3031,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "0.12.1"
|
||||
version = "1.0.0-rc.1"
|
||||
dependencies = [
|
||||
"clap 2.34.0",
|
||||
"client-core",
|
||||
@@ -3076,7 +3066,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-validator-api"
|
||||
version = "0.12.0"
|
||||
version = "1.0.0-rc.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"attohttpc",
|
||||
@@ -6062,11 +6052,11 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "vesting-contract"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0-rc.1"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus 0.12.1",
|
||||
"cw-storage-plus",
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -6080,7 +6070,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus 0.11.1",
|
||||
"cw-storage-plus",
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<svg viewBox="0 0 210 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="210" height="56" viewBox="0 0 210 56" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M45.8829 0.142822H45.7169V0.28114V48.637L25.3289 0.225818L25.3012 0.142822H25.1905H13.6272H0.652966H0.514648V0.28114V55.7189V55.8572H0.652966H13.6272H13.7655V55.7189V7.28002L34.2365 55.7742L34.2642 55.8572H34.3748H45.8829H58.8294H58.9677V55.7189V0.28114V0.142822H58.8294H45.8829Z"/>
|
||||
<path d="M209.347 0.142822H184.616H184.477L184.45 0.253483L171.78 48.8583L159.082 0.253483L159.054 0.142822H158.944H134.157H133.991V0.28114V55.7189V55.8572H134.157H147.104H147.242V55.7189V7.66731L159.774 55.7466L159.801 55.8572H159.94H183.564H183.675L183.703 55.7466L196.234 7.66731V55.7189V55.8572H196.373H209.347H209.485V55.7189V0.28114V0.142822H209.347Z"/>
|
||||
<path d="M112.663 0.142822H112.58L112.552 0.198153L96.8116 27.5574L80.988 0.198153L80.9604 0.142822H80.8774H65.9114H65.6348L65.7731 0.364136L90.1447 42.5787V55.7189V55.8572H90.283H103.257H103.396V55.7189V42.5787L127.767 0.364136L127.905 0.142822H127.629H112.663Z"/>
|
||||
|
||||
|
Before Width: | Height: | Size: 1011 B After Width: | Height: | Size: 1.0 KiB |
@@ -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,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,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,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"
|
||||
|
||||
@@ -38,7 +38,7 @@ 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"
|
||||
|
||||
@@ -589,6 +589,7 @@ impl<C> NymdClient<C> {
|
||||
&self,
|
||||
mix_identity: IdentityKey,
|
||||
delegator: &AccountId,
|
||||
proxy: Option<String>,
|
||||
) -> Result<Delegation, NymdError>
|
||||
where
|
||||
C: CosmWasmClient + Sync,
|
||||
@@ -596,6 +597,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)
|
||||
|
||||
@@ -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-beta6"
|
||||
|
||||
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;
|
||||
|
||||
@@ -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))]
|
||||
|
||||
@@ -101,6 +101,8 @@ pub enum ExecuteMsg {
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum QueryMsg {
|
||||
GetAllDelegationKeys {},
|
||||
DebugGetAllDelegationValues {},
|
||||
GetContractVersion {},
|
||||
GetMixNodes {
|
||||
limit: Option<u32>,
|
||||
@@ -137,6 +139,7 @@ pub enum QueryMsg {
|
||||
GetDelegationDetails {
|
||||
mix_identity: IdentityKey,
|
||||
delegator: String,
|
||||
proxy: Option<String>,
|
||||
},
|
||||
LayerDistribution {},
|
||||
GetRewardPool {},
|
||||
|
||||
@@ -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]
|
||||
|
||||
Generated
+22
-31
@@ -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",
|
||||
@@ -218,22 +218,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-crypto"
|
||||
version = "1.0.0-beta6"
|
||||
version = "1.0.0-beta7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dddc1443004c6340e55ca66d98e9d2f1a44aadf4ce2bed2c4f29baa8a15e7b7"
|
||||
checksum = "88c2565b1e73a816fb659ef4838fc356143fbd35f43c48a51d2d7d4e5d6679d3"
|
||||
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-beta7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe4f0f10f165b8bcc558a13cddb498140960544519aa0581532c766dd80b5598"
|
||||
checksum = "fa89fcdf8dbbe0088e663d0a814aa7368e7ebe8fb045a3a150fb5fdc2ffe3b45"
|
||||
dependencies = [
|
||||
"syn",
|
||||
]
|
||||
@@ -250,9 +250,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-std"
|
||||
version = "1.0.0-beta6"
|
||||
version = "1.0.0-beta7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f0f3145097b692b2d95fa5d2c7c6fdd60f193ccc709857e7e1987a608725300"
|
||||
checksum = "bcb8f99a61d0b9069e1afc80a4ffea87dcc3523edd992080923870b13a677da0"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cosmwasm-crypto",
|
||||
@@ -267,9 +267,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cosmwasm-storage"
|
||||
version = "1.0.0-beta3"
|
||||
version = "1.0.0-beta7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4a4e55f0d64fed54cd2202301b8d466af8de044589247dabd77a4222f52f749"
|
||||
checksum = "07f856099c824aa8f2488e62d1da3fc06383d3fdbc764573595f451be43441a2"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"serde",
|
||||
@@ -360,20 +360,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",
|
||||
@@ -450,16 +439,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]]
|
||||
@@ -820,7 +810,7 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
|
||||
[[package]]
|
||||
name = "mixnet-contract"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0-rc.1"
|
||||
dependencies = [
|
||||
"az",
|
||||
"bs58",
|
||||
@@ -829,8 +819,9 @@ dependencies = [
|
||||
"cosmwasm-std",
|
||||
"cosmwasm-storage",
|
||||
"crypto",
|
||||
"cw-storage-plus 0.12.1",
|
||||
"cw-storage-plus",
|
||||
"fixed",
|
||||
"hex",
|
||||
"mixnet-contract-common",
|
||||
"rand",
|
||||
"rand_chacha",
|
||||
@@ -1527,11 +1518,11 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
|
||||
|
||||
[[package]]
|
||||
name = "vesting-contract"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0-rc.1"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus 0.12.1",
|
||||
"cw-storage-plus",
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
@@ -1545,7 +1536,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"config",
|
||||
"cosmwasm-std",
|
||||
"cw-storage-plus 0.11.1",
|
||||
"cw-storage-plus",
|
||||
"mixnet-contract-common",
|
||||
"schemars",
|
||||
"serde",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bandwidth-claim"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0-rc.1"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
@@ -14,8 +14,8 @@ config = { path = "../../common/config"}
|
||||
[dependencies]
|
||||
bandwidth-claim-contract = { path = "../../common/bandwidth-claim-contract" }
|
||||
|
||||
cosmwasm-std = "1.0.0-beta3"
|
||||
cosmwasm-storage = "1.0.0-beta3"
|
||||
cosmwasm-std = "1.0.0-beta6"
|
||||
cosmwasm-storage = "1.0.0-beta6"
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mixnet-contract"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0-rc.1"
|
||||
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-beta6"
|
||||
cosmwasm-storage = "1.0.0-beta6"
|
||||
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"
|
||||
|
||||
@@ -32,7 +32,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;
|
||||
|
||||
@@ -312,11 +312,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 +365,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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "vesting-contract"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0-rc.1"
|
||||
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"] }
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -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
@@ -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>",
|
||||
|
||||
Generated
-1
@@ -2843,7 +2843,6 @@ dependencies = [
|
||||
"argon2",
|
||||
"base64",
|
||||
"bip39",
|
||||
"cfg-if",
|
||||
"coconut-interface",
|
||||
"config",
|
||||
"cosmrs",
|
||||
|
||||
@@ -38,7 +38,6 @@
|
||||
"react-hook-form": "^7.14.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"semver": "^6.3.0",
|
||||
"use-clipboard-copy": "^0.2.0",
|
||||
"yup": "^0.32.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -20,7 +20,6 @@ tauri-macros = "=1.0.0-rc.1"
|
||||
|
||||
[dependencies]
|
||||
bip39 = "1.0"
|
||||
cfg-if = "1.0.0"
|
||||
dirs = "4.0"
|
||||
eyre = "0.6.5"
|
||||
futures = "0.3.15"
|
||||
|
||||
@@ -77,18 +77,10 @@ pub enum BackendError {
|
||||
NetworkNotSupported(config::defaults::all::Network),
|
||||
#[error("Could not access the local data storage directory")]
|
||||
UnknownStorageDirectory,
|
||||
#[error("No nymd validator configured")]
|
||||
NoNymdValidatorConfigured,
|
||||
#[error("No validator API URL configured")]
|
||||
NoValidatorApiUrlConfigured,
|
||||
#[error("The wallet file already exists")]
|
||||
WalletFileAlreadyExists,
|
||||
#[error("The wallet file is not found")]
|
||||
WalletFileNotFound,
|
||||
#[error("Account ID not found in wallet")]
|
||||
NoSuchIdInWallet,
|
||||
#[error("Account ID already found in wallet")]
|
||||
IdAlreadyExistsInWallet,
|
||||
#[error("Adding a different password to the wallet not currently supported")]
|
||||
WalletDifferentPasswordDetected,
|
||||
}
|
||||
|
||||
impl Serialize for BackendError {
|
||||
|
||||
@@ -15,7 +15,6 @@ mod error;
|
||||
mod menu;
|
||||
mod network;
|
||||
mod operations;
|
||||
mod platform_constants;
|
||||
mod state;
|
||||
mod utils;
|
||||
// temporarily until it is actually used
|
||||
@@ -38,11 +37,8 @@ fn main() {
|
||||
mixnet::account::connect_with_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::logout,
|
||||
mixnet::account::sign_in_with_password,
|
||||
mixnet::account::switch_network,
|
||||
mixnet::account::update_validator_urls,
|
||||
mixnet::admin::get_contract_settings,
|
||||
|
||||
@@ -4,11 +4,8 @@ use crate::error::BackendError;
|
||||
use crate::network::Network;
|
||||
use crate::nymd_client;
|
||||
use crate::state::State;
|
||||
use crate::wallet_storage::{self, DEFAULT_WALLET_ACCOUNT_ID};
|
||||
|
||||
use bip39::{Language, Mnemonic};
|
||||
use config::defaults::COSMOS_DERIVATION_PATH;
|
||||
use cosmrs::bip32::DerivationPath;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::convert::TryInto;
|
||||
@@ -230,10 +227,9 @@ async fn choose_validators(
|
||||
.next()
|
||||
// We always have at least one hardcoded default validator
|
||||
.unwrap();
|
||||
log::info!(
|
||||
println!(
|
||||
"Using default for {network}: {}, {}",
|
||||
default_validator.nymd_url,
|
||||
default_validator.api_url,
|
||||
default_validator.nymd_url, default_validator.api_url,
|
||||
);
|
||||
default_validator
|
||||
});
|
||||
@@ -309,10 +305,9 @@ async fn try_connect_to_validator(
|
||||
)?;
|
||||
|
||||
if is_validator_connection_ok(&client).await {
|
||||
log::info!(
|
||||
println!(
|
||||
"Connection ok for {network}: {}, {}",
|
||||
validator.nymd_url,
|
||||
validator.api_url
|
||||
validator.nymd_url, validator.api_url
|
||||
);
|
||||
Ok(Some((network, validator.clone())))
|
||||
} else {
|
||||
@@ -327,45 +322,3 @@ async fn is_validator_connection_ok(client: &Client<SigningNymdClient>) -> bool
|
||||
Err(_) | Ok(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn does_password_file_exist() -> Result<bool, BackendError> {
|
||||
log::info!("Checking wallet file");
|
||||
let file = wallet_storage::wallet_login_filepath()?;
|
||||
if file.exists() {
|
||||
log::info!("Exists: {}", file.to_string_lossy());
|
||||
Ok(true)
|
||||
} else {
|
||||
log::info!("Does not exist: {}", file.to_string_lossy());
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn create_password(mnemonic: String, password: String) -> Result<(), BackendError> {
|
||||
if does_password_file_exist()? {
|
||||
return Err(BackendError::WalletFileAlreadyExists);
|
||||
}
|
||||
log::info!("Creating password");
|
||||
|
||||
let mnemonic = Mnemonic::from_str(&mnemonic)?;
|
||||
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
|
||||
// Currently we only support a single, default, id in the wallet
|
||||
let id = wallet_storage::WalletAccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string());
|
||||
let password = wallet_storage::UserPassword::new(password);
|
||||
wallet_storage::store_wallet_login_information(mnemonic, hd_path, id, &password)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn sign_in_with_password(
|
||||
password: String,
|
||||
state: tauri::State<'_, Arc<RwLock<State>>>,
|
||||
) -> Result<Account, BackendError> {
|
||||
log::info!("Signing in with password");
|
||||
|
||||
// Currently we only support a single, default, id in the wallet
|
||||
let id = wallet_storage::WalletAccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string());
|
||||
let password = wallet_storage::UserPassword::new(password);
|
||||
let stored_account = wallet_storage::load_existing_wallet_login_information(&id, &password)?;
|
||||
_connect_with_mnemonic(stored_account.mnemonic().clone(), state).await
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Specify filenames and other platform specific constants to respect platform conventions, or at
|
||||
// least, something popular on each respective platform.
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(target_os = "linux")] {
|
||||
pub const STORAGE_DIR_NAME: &str = "nym-wallet";
|
||||
pub const WALLET_INFO_FILENAME: &str = "saved-wallet.json";
|
||||
} else if #[cfg(taret_os = "macos")] {
|
||||
pub const STORAGE_DIR_NAME: &str = "nym-wallet";
|
||||
pub const WALLET_INFO_FILENAME: &str = "saved-wallet.json";
|
||||
} else if #[cfg(taret_os = "windows")] {
|
||||
pub const STORAGE_DIR_NAME: &str = "NymWallet";
|
||||
pub const WALLET_INFO_FILENAME: &str = "saved_wallet.json";
|
||||
} else {
|
||||
// This case is likely to be a unix-y system
|
||||
pub const STORAGE_DIR_NAME: &str = "nym-wallet";
|
||||
pub const WALLET_INFO_FILENAME: &str = "saved-wallet.json";
|
||||
}
|
||||
}
|
||||
@@ -8,96 +8,6 @@ use std::fmt::Formatter;
|
||||
use zeroize::Zeroize;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use crate::error::BackendError;
|
||||
|
||||
use super::encryption::EncryptedData;
|
||||
use super::password::WalletAccountId;
|
||||
use super::UserPassword;
|
||||
|
||||
const CURRENT_WALLET_FILE_VERSION: u32 = 1;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub(crate) struct StoredWallet {
|
||||
version: u32,
|
||||
accounts: Vec<EncryptedAccount>,
|
||||
}
|
||||
|
||||
impl StoredWallet {
|
||||
pub fn version(&self) -> u32 {
|
||||
self.version
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.accounts.is_empty()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.accounts.len()
|
||||
}
|
||||
|
||||
pub fn encrypted_account_by_index(&self, index: usize) -> Option<&EncryptedAccount> {
|
||||
self.accounts.get(index)
|
||||
}
|
||||
|
||||
fn encrypted_account(
|
||||
&self,
|
||||
id: &WalletAccountId,
|
||||
) -> Result<&EncryptedData<StoredAccount>, BackendError> {
|
||||
self
|
||||
.accounts
|
||||
.iter()
|
||||
.find(|account| &account.id == id)
|
||||
.map(|account| &account.account)
|
||||
.ok_or(BackendError::NoSuchIdInWallet)
|
||||
}
|
||||
|
||||
pub fn add_encrypted_account(
|
||||
&mut self,
|
||||
new_account: EncryptedAccount,
|
||||
) -> Result<(), BackendError> {
|
||||
if self.encrypted_account(&new_account.id).is_ok() {
|
||||
return Err(BackendError::IdAlreadyExistsInWallet);
|
||||
}
|
||||
self.accounts.push(new_account);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn decrypt_account(
|
||||
&self,
|
||||
id: &WalletAccountId,
|
||||
password: &UserPassword,
|
||||
) -> Result<StoredAccount, BackendError> {
|
||||
self.encrypted_account(id)?.decrypt_struct(password)
|
||||
}
|
||||
|
||||
pub fn decrypt_all(&self, password: &UserPassword) -> Result<Vec<StoredAccount>, BackendError> {
|
||||
self
|
||||
.accounts
|
||||
.iter()
|
||||
.map(|account| account.account.decrypt_struct(password))
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
}
|
||||
|
||||
pub fn password_can_decrypt_all(&self, password: &UserPassword) -> bool {
|
||||
self.decrypt_all(password).is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for StoredWallet {
|
||||
fn default() -> Self {
|
||||
StoredWallet {
|
||||
version: CURRENT_WALLET_FILE_VERSION,
|
||||
accounts: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub(crate) struct EncryptedAccount {
|
||||
pub id: WalletAccountId,
|
||||
pub account: EncryptedData<StoredAccount>,
|
||||
}
|
||||
|
||||
// future-proofing
|
||||
#[derive(Serialize, Deserialize, Debug, Zeroize)]
|
||||
#[serde(untagged)]
|
||||
@@ -114,14 +24,6 @@ impl StoredAccount {
|
||||
) -> StoredAccount {
|
||||
StoredAccount::Mnemonic(MnemonicAccount { mnemonic, hd_path })
|
||||
}
|
||||
|
||||
// If we add accounts backed by something that is not a mnemonic, this should probably be changed
|
||||
// to return `Option<..>`.
|
||||
pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic {
|
||||
match self {
|
||||
StoredAccount::Mnemonic(account) => account.mnemonic(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
@@ -131,6 +33,8 @@ pub(crate) struct MnemonicAccount {
|
||||
hd_path: DerivationPath,
|
||||
}
|
||||
|
||||
// we only ever want to expose those getters in the test code
|
||||
#[cfg(test)]
|
||||
impl MnemonicAccount {
|
||||
pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic {
|
||||
&self.mnemonic
|
||||
|
||||
@@ -42,7 +42,7 @@ pub(crate) struct EncryptedData<T> {
|
||||
|
||||
impl<T> Drop for EncryptedData<T> {
|
||||
fn drop(&mut self) {
|
||||
self.zeroize();
|
||||
self.zeroize()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) use crate::wallet_storage::password::{UserPassword, WalletAccountId};
|
||||
|
||||
use crate::error::BackendError;
|
||||
use crate::operations::mixnet::account::create_new_account;
|
||||
use crate::platform_constants::{STORAGE_DIR_NAME, WALLET_INFO_FILENAME};
|
||||
use crate::wallet_storage::account_data::StoredAccount;
|
||||
use crate::wallet_storage::encryption::{encrypt_struct, EncryptedData};
|
||||
use crate::wallet_storage::password::UserPassword;
|
||||
use cosmrs::bip32::DerivationPath;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fs::{create_dir_all, OpenOptions};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use self::account_data::{EncryptedAccount, StoredWallet};
|
||||
|
||||
pub(crate) mod account_data;
|
||||
pub(crate) mod encryption;
|
||||
|
||||
mod password;
|
||||
|
||||
pub(crate) const DEFAULT_WALLET_ACCOUNT_ID: &str = "default";
|
||||
const STORAGE_DIR_NAME: &str = "NymWallet";
|
||||
const WALLET_INFO_FILENAME: &str = "saved_wallet.json";
|
||||
|
||||
fn get_storage_directory() -> Result<PathBuf, BackendError> {
|
||||
tauri::api::path::local_data_dir()
|
||||
@@ -32,76 +25,52 @@ pub(crate) fn wallet_login_filepath() -> Result<PathBuf, BackendError> {
|
||||
get_storage_directory().map(|dir| dir.join(WALLET_INFO_FILENAME))
|
||||
}
|
||||
|
||||
pub(crate) fn load_existing_wallet(password: &UserPassword) -> Result<StoredWallet, BackendError> {
|
||||
let store_dir = get_storage_directory()?;
|
||||
let filepath = store_dir.join(WALLET_INFO_FILENAME);
|
||||
load_existing_wallet_at_file(filepath)
|
||||
}
|
||||
|
||||
fn load_existing_wallet_at_file(filepath: PathBuf) -> Result<StoredWallet, BackendError> {
|
||||
if !filepath.exists() {
|
||||
return Err(BackendError::WalletFileNotFound);
|
||||
}
|
||||
let file = OpenOptions::new().read(true).open(filepath)?;
|
||||
let wallet: StoredWallet = serde_json::from_reader(file)?;
|
||||
Ok(wallet)
|
||||
}
|
||||
|
||||
pub(crate) fn load_existing_wallet_login_information(
|
||||
id: &WalletAccountId,
|
||||
password: &UserPassword,
|
||||
) -> Result<StoredAccount, BackendError> {
|
||||
) -> Result<Vec<StoredAccount>, BackendError> {
|
||||
let store_dir = get_storage_directory()?;
|
||||
let filepath = store_dir.join(WALLET_INFO_FILENAME);
|
||||
load_existing_wallet_login_information_at_file(filepath, id, password)
|
||||
|
||||
load_existing_wallet_login_information_at_file(filepath, password)
|
||||
}
|
||||
|
||||
fn load_existing_wallet_login_information_at_file(
|
||||
filepath: PathBuf,
|
||||
id: &WalletAccountId,
|
||||
password: &UserPassword,
|
||||
) -> Result<StoredAccount, BackendError> {
|
||||
load_existing_wallet_at_file(filepath)?.decrypt_account(id, password)
|
||||
) -> Result<Vec<StoredAccount>, BackendError> {
|
||||
if !filepath.exists() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let file = OpenOptions::new().read(true).open(filepath)?;
|
||||
let encrypted_data: EncryptedData<Vec<StoredAccount>> = serde_json::from_reader(file)?;
|
||||
encrypted_data.decrypt_struct(password)
|
||||
}
|
||||
|
||||
pub(crate) fn store_wallet_login_information(
|
||||
mnemonic: bip39::Mnemonic,
|
||||
hd_path: DerivationPath,
|
||||
id: WalletAccountId,
|
||||
password: &UserPassword,
|
||||
password: UserPassword,
|
||||
) -> Result<(), BackendError> {
|
||||
// make sure the entire directory structure exists
|
||||
let store_dir = get_storage_directory()?;
|
||||
create_dir_all(&store_dir)?;
|
||||
let filepath = store_dir.join(WALLET_INFO_FILENAME);
|
||||
|
||||
store_wallet_login_information_at_file(filepath, mnemonic, hd_path, id, password)
|
||||
store_wallet_login_information_at_file(filepath, mnemonic, hd_path, &password)
|
||||
}
|
||||
|
||||
fn store_wallet_login_information_at_file(
|
||||
filepath: PathBuf,
|
||||
mnemonic: bip39::Mnemonic,
|
||||
hd_path: DerivationPath,
|
||||
id: WalletAccountId,
|
||||
password: &UserPassword,
|
||||
) -> Result<(), BackendError> {
|
||||
let mut stored_wallet = match load_existing_wallet_at_file(filepath.clone()) {
|
||||
Err(BackendError::WalletFileNotFound) => StoredWallet::default(),
|
||||
result => result?,
|
||||
};
|
||||
|
||||
// Confirm that the given password also can unlock the other entries
|
||||
if !stored_wallet.password_can_decrypt_all(password) {
|
||||
return Err(BackendError::WalletDifferentPasswordDetected);
|
||||
}
|
||||
|
||||
let mut all_accounts =
|
||||
load_existing_wallet_login_information_at_file(filepath.clone(), password)?;
|
||||
let new_account = StoredAccount::new_mnemonic_backed_account(mnemonic, hd_path);
|
||||
let new_encrypted_account = EncryptedAccount {
|
||||
id,
|
||||
account: encrypt_struct(&new_account, password)?,
|
||||
};
|
||||
all_accounts.push(new_account);
|
||||
|
||||
stored_wallet.add_encrypted_account(new_encrypted_account)?;
|
||||
let encrypted = encrypt_struct(&all_accounts, password)?;
|
||||
|
||||
let file = OpenOptions::new()
|
||||
.create(true)
|
||||
@@ -109,7 +78,9 @@ fn store_wallet_login_information_at_file(
|
||||
.truncate(true)
|
||||
.open(filepath)?;
|
||||
|
||||
Ok(serde_json::to_writer_pretty(file, &stored_wallet)?)
|
||||
serde_json::to_writer_pretty(file, &encrypted)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// this function should probably exist, but I guess we need to discuss how it should behave in the context of the UX
|
||||
@@ -120,11 +91,15 @@ fn store_wallet_login_information_at_file(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::wallet_storage::encryption::encrypt_data;
|
||||
use config::defaults::COSMOS_DERIVATION_PATH;
|
||||
use std::path::Path;
|
||||
use tempfile::tempdir;
|
||||
|
||||
fn read_encrypted_blob(file: PathBuf) -> EncryptedData<Vec<StoredAccount>> {
|
||||
let file = OpenOptions::new().read(true).open(&file).unwrap();
|
||||
serde_json::from_reader(file).unwrap()
|
||||
}
|
||||
|
||||
// I'm not 100% sure how to feel about having to touch the file system at all
|
||||
#[test]
|
||||
fn storing_wallet_information() {
|
||||
@@ -139,36 +114,29 @@ mod tests {
|
||||
let password = UserPassword::new("password".to_string());
|
||||
let bad_password = UserPassword::new("bad-password".to_string());
|
||||
|
||||
let id1 = WalletAccountId::new("first".to_string());
|
||||
let id2 = WalletAccountId::new("second".to_string());
|
||||
// nothing was stored on the disk, so regardless of password used, there will be no error, but
|
||||
// returned list will be empty
|
||||
assert!(
|
||||
load_existing_wallet_login_information_at_file(wallet_file.clone(), &password)
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
assert!(
|
||||
load_existing_wallet_login_information_at_file(wallet_file.clone(), &bad_password)
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
|
||||
// Nothing was stored on the disk
|
||||
assert!(matches!(
|
||||
load_existing_wallet_at_file(wallet_file.clone()),
|
||||
Err(BackendError::WalletFileNotFound),
|
||||
));
|
||||
assert!(matches!(
|
||||
load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password),
|
||||
Err(BackendError::WalletFileNotFound),
|
||||
));
|
||||
|
||||
// Store the first account
|
||||
// store the first account
|
||||
store_wallet_login_information_at_file(
|
||||
wallet_file.clone(),
|
||||
dummy_account1.clone(),
|
||||
cosmos_hd_path.clone(),
|
||||
id1.clone(),
|
||||
&password,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let stored_wallet = load_existing_wallet_at_file(wallet_file.clone()).unwrap();
|
||||
assert_eq!(stored_wallet.len(), 1);
|
||||
assert_eq!(
|
||||
stored_wallet.encrypted_account_by_index(0).unwrap().id,
|
||||
WalletAccountId::new("first".to_string())
|
||||
);
|
||||
let encrypted_blob = &stored_wallet.encrypted_account_by_index(0).unwrap().account;
|
||||
let encrypted_blob = read_encrypted_blob(wallet_file.clone());
|
||||
|
||||
// some actual ciphertext was saved
|
||||
assert!(!encrypted_blob.ciphertext().is_empty());
|
||||
@@ -178,78 +146,53 @@ mod tests {
|
||||
let original_salt = encrypted_blob.salt().to_vec();
|
||||
|
||||
// trying to load it with wrong password now fails
|
||||
assert!(matches!(
|
||||
load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &bad_password),
|
||||
Err(BackendError::DecryptionError),
|
||||
));
|
||||
// and with the wrong id also fails
|
||||
assert!(matches!(
|
||||
load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password),
|
||||
Err(BackendError::NoSuchIdInWallet),
|
||||
));
|
||||
assert!(
|
||||
load_existing_wallet_login_information_at_file(wallet_file.clone(), &bad_password).is_err()
|
||||
);
|
||||
|
||||
// and storing the same id again fails
|
||||
assert!(matches!(
|
||||
store_wallet_login_information_at_file(
|
||||
wallet_file.clone(),
|
||||
dummy_account1.clone(),
|
||||
cosmos_hd_path.clone(),
|
||||
id1.clone(),
|
||||
&password,
|
||||
),
|
||||
Err(BackendError::IdAlreadyExistsInWallet),
|
||||
));
|
||||
let loaded_accounts =
|
||||
load_existing_wallet_login_information_at_file(wallet_file.clone(), &password).unwrap();
|
||||
println!("{:?}", loaded_accounts);
|
||||
assert_eq!(1, loaded_accounts.len());
|
||||
|
||||
let loaded_account =
|
||||
load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap();
|
||||
|
||||
let StoredAccount::Mnemonic(ref acc) = loaded_account;
|
||||
let StoredAccount::Mnemonic(acc) = &loaded_accounts[0];
|
||||
assert_eq!(&dummy_account1, acc.mnemonic());
|
||||
assert_eq!(&cosmos_hd_path, acc.hd_path());
|
||||
|
||||
// Can't store extra account if you use different password
|
||||
assert!(matches!(
|
||||
store_wallet_login_information_at_file(
|
||||
wallet_file.clone(),
|
||||
dummy_account2.clone(),
|
||||
cosmos_hd_path.clone(),
|
||||
id2.clone(),
|
||||
&bad_password
|
||||
),
|
||||
Err(BackendError::WalletDifferentPasswordDetected),
|
||||
));
|
||||
// can't store extra account if you use different password
|
||||
assert!(store_wallet_login_information_at_file(
|
||||
wallet_file.clone(),
|
||||
dummy_account2.clone(),
|
||||
cosmos_hd_path.clone(),
|
||||
&bad_password,
|
||||
)
|
||||
.is_err());
|
||||
|
||||
// add extra account properly now
|
||||
store_wallet_login_information_at_file(
|
||||
wallet_file.clone(),
|
||||
dummy_account2.clone(),
|
||||
different_hd_path.clone(),
|
||||
id2.clone(),
|
||||
&password,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let loaded_accounts = load_existing_wallet_at_file(wallet_file.clone()).unwrap();
|
||||
assert_eq!(2, loaded_accounts.len());
|
||||
let encrypted_blob = &loaded_accounts
|
||||
.encrypted_account_by_index(1)
|
||||
.unwrap()
|
||||
.account;
|
||||
let encrypted_blob = read_encrypted_blob(wallet_file.clone());
|
||||
|
||||
// fresh IV and salt are used
|
||||
assert_ne!(original_iv, encrypted_blob.iv());
|
||||
assert_ne!(original_salt, encrypted_blob.salt());
|
||||
|
||||
let loaded_accounts =
|
||||
load_existing_wallet_login_information_at_file(wallet_file, &password).unwrap();
|
||||
assert_eq!(2, loaded_accounts.len());
|
||||
|
||||
// first account should be unchanged
|
||||
let loaded_account =
|
||||
load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap();
|
||||
let StoredAccount::Mnemonic(ref acc1) = loaded_account;
|
||||
let StoredAccount::Mnemonic(acc1) = &loaded_accounts[0];
|
||||
assert_eq!(&dummy_account1, acc1.mnemonic());
|
||||
assert_eq!(&cosmos_hd_path, acc1.hd_path());
|
||||
|
||||
let loaded_account =
|
||||
load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap();
|
||||
let StoredAccount::Mnemonic(ref acc2) = loaded_account;
|
||||
let StoredAccount::Mnemonic(acc2) = &loaded_accounts[1];
|
||||
assert_eq!(&dummy_account2, acc2.mnemonic());
|
||||
assert_eq!(&different_hd_path, acc2.hd_path());
|
||||
}
|
||||
|
||||
@@ -1,24 +1,8 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub(crate) struct WalletAccountId(String);
|
||||
|
||||
impl WalletAccountId {
|
||||
pub(crate) fn new(id: String) -> WalletAccountId {
|
||||
WalletAccountId(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for WalletAccountId {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
// simple wrapper for String that will get zeroized on drop
|
||||
#[derive(Zeroize)]
|
||||
#[zeroize(drop)]
|
||||
|
||||
@@ -4,10 +4,9 @@ import { useSnackbar } from 'notistack';
|
||||
import { Account, Network, TCurrency, TMixnodeBondDetails } from '../types';
|
||||
import { TUseuserBalance, useGetBalance } from '../hooks/useGetBalance';
|
||||
import { config } from '../../config';
|
||||
import { getMixnodeBondDetails, selectNetwork, signInWithMnemonic, signInWithPassword, signOut } from '../requests';
|
||||
import { getMixnodeBondDetails, selectNetwork, signInWithMnemonic, signOut } from '../requests';
|
||||
import { currencyMap } from '../utils';
|
||||
import { Console } from '../utils/console';
|
||||
import { TLoginType } from 'src/pages/welcome/types';
|
||||
|
||||
export const { ADMIN_ADDRESS, IS_DEV_MODE } = config;
|
||||
|
||||
@@ -33,14 +32,11 @@ type TClientContext = {
|
||||
currency?: TCurrency;
|
||||
isLoading: boolean;
|
||||
error?: string;
|
||||
setIsLoading: (isLoading: boolean) => void;
|
||||
setError: (value?: string) => void;
|
||||
switchNetwork: (network: Network) => void;
|
||||
getBondDetails: () => Promise<void>;
|
||||
handleShowSettings: () => void;
|
||||
handleShowAdmin: () => void;
|
||||
logIn: (opts: { type: 'mnemonic' | 'password'; value: string }) => void;
|
||||
signInWithPassword: (password: string) => void;
|
||||
logIn: (mnemonic: string) => void;
|
||||
logOut: () => void;
|
||||
};
|
||||
|
||||
@@ -94,23 +90,16 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
refreshAccount();
|
||||
}, [network]);
|
||||
|
||||
const logIn = async ({ type, value }: { type: TLoginType; value: string }) => {
|
||||
if (value.length === 0) {
|
||||
setError(`A ${type} must be provided`);
|
||||
return;
|
||||
}
|
||||
const logIn = async (mnemonic: string) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
if (type === 'mnemonic') {
|
||||
await signInWithMnemonic(value);
|
||||
} else {
|
||||
await signInWithPassword(value);
|
||||
}
|
||||
await signInWithMnemonic(mnemonic || '');
|
||||
await getBondDetails();
|
||||
setNetwork('MAINNET');
|
||||
} catch (e) {
|
||||
setIsLoading(false);
|
||||
setError(e as string);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
history.push('/balance');
|
||||
}
|
||||
};
|
||||
@@ -142,9 +131,6 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
showSettings,
|
||||
network,
|
||||
currency,
|
||||
setIsLoading,
|
||||
setError,
|
||||
signInWithPassword,
|
||||
switchNetwork,
|
||||
getBondDetails,
|
||||
handleShowSettings,
|
||||
|
||||
@@ -10,7 +10,6 @@ import { Admin, Welcome, Settings } from './pages';
|
||||
import { ErrorFallback } from './components';
|
||||
import { NymWalletTheme, WelcomeTheme } from './theme';
|
||||
import { maximizeWindow } from './utils';
|
||||
import { SignInProvider } from './pages/welcome/context';
|
||||
|
||||
const App = () => {
|
||||
const { clientDetails } = useContext(ClientContext);
|
||||
@@ -21,9 +20,7 @@ const App = () => {
|
||||
|
||||
return !clientDetails ? (
|
||||
<WelcomeTheme>
|
||||
<SignInProvider>
|
||||
<Welcome />
|
||||
</SignInProvider>
|
||||
<Welcome />
|
||||
</WelcomeTheme>
|
||||
) : (
|
||||
<NymWalletTheme>
|
||||
|
||||
@@ -16,9 +16,6 @@ export const SendConfirmation = ({
|
||||
isLoading: boolean;
|
||||
}) => {
|
||||
const { userBalance, currency, network } = useContext(ClientContext);
|
||||
|
||||
if (!data && !error && !isLoading) return null;
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
|
||||
@@ -128,9 +128,13 @@ export const SendWizard = () => {
|
||||
px: 3,
|
||||
}}
|
||||
>
|
||||
{activeStep === 0 && <SendForm />}
|
||||
{activeStep === 1 && <SendReview transferFee={transferFee} />}
|
||||
<SendConfirmation data={confirmedData} isLoading={isLoading} error={requestError} />
|
||||
{activeStep === 0 ? (
|
||||
<SendForm />
|
||||
) : activeStep === 1 ? (
|
||||
<SendReview transferFee={transferFee} />
|
||||
) : (
|
||||
<SendConfirmation data={confirmedData} isLoading={isLoading} error={requestError} />
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
@@ -150,16 +154,7 @@ export const SendWizard = () => {
|
||||
color="primary"
|
||||
disableElevation
|
||||
data-testid="button"
|
||||
onClick={() => {
|
||||
switch (activeStep) {
|
||||
case 0:
|
||||
return handleNextStep();
|
||||
case 1:
|
||||
return handleSend();
|
||||
default:
|
||||
return handleFinish();
|
||||
}
|
||||
}}
|
||||
onClick={activeStep === 0 ? handleNextStep : activeStep === 1 ? handleSend : handleFinish}
|
||||
disabled={!!(methods.formState.errors.amount || methods.formState.errors.to || isLoading)}
|
||||
size="large"
|
||||
>
|
||||
|
||||
@@ -139,13 +139,13 @@ export const SystemVariables = ({
|
||||
pt: 0,
|
||||
}}
|
||||
>
|
||||
{nodeUpdateResponse === 'success' && (
|
||||
{nodeUpdateResponse === 'success' ? (
|
||||
<Typography sx={{ color: 'success.main', fontWeight: 600 }}>Node successfully updated</Typography>
|
||||
)}
|
||||
{nodeUpdateResponse === 'failed' && (
|
||||
) : nodeUpdateResponse === 'failed' ? (
|
||||
<Typography sx={{ color: 'error.main', fontWeight: 600 }}>Node update failed</Typography>
|
||||
) : (
|
||||
<Fee feeType="UpdateMixnodeConfig" />
|
||||
)}
|
||||
{!nodeUpdateResponse && <Fee feeType="UpdateMixnodeConfig" />}
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Alert, Box, Button, CircularProgress } from '@mui/material';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { Fee, NymCard } from '../../components';
|
||||
import { useCheckOwnership } from '../../hooks/useCheckOwnership';
|
||||
import { ClientContext } from '../../context/main';
|
||||
import { unbond } from '../../requests';
|
||||
import { unbond, vestingUnbond } from '../../requests';
|
||||
import { PageLayout } from '../../layouts';
|
||||
|
||||
export const Unbond = () => {
|
||||
@@ -11,6 +12,8 @@ export const Unbond = () => {
|
||||
const { checkOwnership, ownership } = useCheckOwnership();
|
||||
const { userBalance, getBondDetails } = useContext(ClientContext);
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
useEffect(() => {
|
||||
const initialiseForm = async () => {
|
||||
await checkOwnership();
|
||||
@@ -32,11 +35,20 @@ export const Unbond = () => {
|
||||
disabled={isLoading}
|
||||
onClick={async () => {
|
||||
setIsLoading(true);
|
||||
await unbond(ownership.nodeType!);
|
||||
await userBalance.fetchBalance();
|
||||
await getBondDetails();
|
||||
await checkOwnership();
|
||||
setIsLoading(false);
|
||||
try {
|
||||
if (ownership.vestingPledge) {
|
||||
await vestingUnbond(ownership.nodeType!);
|
||||
} else {
|
||||
await unbond(ownership.nodeType!);
|
||||
}
|
||||
} catch (e) {
|
||||
enqueueSnackbar(`Failed to unbond ${ownership.nodeType}}`, { variant: 'error' });
|
||||
} finally {
|
||||
await getBondDetails();
|
||||
await checkOwnership();
|
||||
await userBalance.fetchBalance();
|
||||
setIsLoading(false);
|
||||
}
|
||||
}}
|
||||
color="inherit"
|
||||
>
|
||||
|
||||
@@ -17,7 +17,7 @@ export const SignInContent: React.FC = () => {
|
||||
setInputError(undefined);
|
||||
|
||||
try {
|
||||
await logIn({ type: 'mnemonic', value: mnemonic });
|
||||
await logIn(mnemonic || '');
|
||||
setIsLoading(false);
|
||||
} catch (error: any) {
|
||||
setIsLoading(false);
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
import React from 'react';
|
||||
import { Alert } from '@mui/material';
|
||||
|
||||
export const Error = ({ message }: { message: string }) => (
|
||||
<Alert severity="error" variant="outlined" data-testid="error" sx={{ color: 'error.light', width: '100%' }}>
|
||||
{message}
|
||||
</Alert>
|
||||
);
|
||||
@@ -2,6 +2,3 @@ export * from './heading';
|
||||
export * from './word-tiles';
|
||||
export * from './render-page';
|
||||
export * from './password-strength';
|
||||
export * from './error';
|
||||
export * from './textfields';
|
||||
export * from './step';
|
||||
|
||||
@@ -5,8 +5,8 @@ import { LinearProgress, Stack, Typography, Box } from '@mui/material';
|
||||
|
||||
type TStrength = 'weak' | 'medium' | 'strong' | 'init';
|
||||
|
||||
const strong = /^(?=.*[a-z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/;
|
||||
const medium = /^(((?=.*[a-z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[0-9])))(?=.{6,})/;
|
||||
const strong = /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})/;
|
||||
const medium = /^(((?=.*[a-z])(?=.*[A-Z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[A-Z])(?=.*[0-9])))(?=.{6,})/;
|
||||
|
||||
const colorMap = {
|
||||
init: 'inherit' as 'inherit',
|
||||
@@ -41,24 +41,7 @@ const getTextColor = (strength: TStrength) => {
|
||||
}
|
||||
};
|
||||
|
||||
const getPasswordStrength = (strength: TStrength) => {
|
||||
switch (strength) {
|
||||
case 'strong':
|
||||
return 100;
|
||||
case 'medium':
|
||||
return 50;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
export const PasswordStrength = ({
|
||||
password,
|
||||
onChange,
|
||||
}: {
|
||||
password: string;
|
||||
onChange: (isStrong: boolean) => void;
|
||||
}) => {
|
||||
export const PasswordStrength = ({ password }: { password: string }) => {
|
||||
const [strength, setStrength] = useState<TStrength>('init');
|
||||
|
||||
useEffect(() => {
|
||||
@@ -79,17 +62,13 @@ export const PasswordStrength = ({
|
||||
setStrength('weak');
|
||||
}, [password]);
|
||||
|
||||
useEffect(() => {
|
||||
if (strength === 'strong') {
|
||||
onChange(true);
|
||||
} else {
|
||||
onChange(false);
|
||||
}
|
||||
}, [strength]);
|
||||
|
||||
return (
|
||||
<Stack spacing={0.5}>
|
||||
<LinearProgress variant="determinate" color={colorMap[strength]} value={getPasswordStrength(strength)} />
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
color={colorMap[strength]}
|
||||
value={strength === 'strong' ? 100 : strength === 'medium' ? 50 : 0}
|
||||
/>
|
||||
<Box display="flex" alignItems="center">
|
||||
<LockOutlined sx={{ fontSize: 15, color: getTextColor(strength) }} />
|
||||
<Typography variant="caption" sx={{ ml: 0.5, color: getTextColor(strength) }}>
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { Typography } from '@mui/material';
|
||||
import { TPages } from '../types';
|
||||
|
||||
export const Step = ({ currentPage, totalSteps }: { currentPage: TPages; totalSteps: number }) => {
|
||||
const mapPage = useCallback(() => {
|
||||
switch (currentPage) {
|
||||
case 'create mnemonic':
|
||||
return 1;
|
||||
case 'verify mnemonic':
|
||||
return 2;
|
||||
case 'create password':
|
||||
return 3;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}, [currentPage]);
|
||||
|
||||
if (mapPage() === 0) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Typography sx={{ color: 'grey.400' }}>
|
||||
Create account. Step {mapPage()}/{totalSteps}
|
||||
</Typography>
|
||||
);
|
||||
};
|
||||
@@ -1,78 +0,0 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Box, IconButton, Link, Stack, TextField, Typography } from '@mui/material';
|
||||
import { Visibility, VisibilityOff } from '@mui/icons-material';
|
||||
import { Error } from './error';
|
||||
|
||||
export const MnemonicInput: React.FC<{
|
||||
mnemonic: string;
|
||||
error?: string;
|
||||
onUpdateMnemonic: (mnemonic: string) => void;
|
||||
}> = ({ mnemonic, error, onUpdateMnemonic }) => {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
return (
|
||||
<Stack spacing={2}>
|
||||
<TextField
|
||||
label="Mnemonic"
|
||||
type={showPassword ? 'input' : 'password'}
|
||||
value={mnemonic}
|
||||
onChange={(e) => onUpdateMnemonic(e.target.value)}
|
||||
multiline={!!showPassword}
|
||||
rows={4}
|
||||
autoFocus
|
||||
fullWidth
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<IconButton onClick={() => setShowPassword((show) => !show)}>
|
||||
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{error && <Error message={error} />}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
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 }) => {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
return (
|
||||
<Stack spacing={2}>
|
||||
<Box>
|
||||
<TextField
|
||||
label={label}
|
||||
fullWidth
|
||||
value={password}
|
||||
onChange={(e) => onUpdatePassword(e.target.value)}
|
||||
type={showPassword ? 'input' : 'password'}
|
||||
autoFocus={autoFocus}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<IconButton onClick={() => setShowPassword((show) => !show)}>
|
||||
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
{showForgottenPassword && (
|
||||
<Link
|
||||
underline="none"
|
||||
variant="body2"
|
||||
component="div"
|
||||
sx={{ mt: 1, textAlign: 'right', color: 'info.main', cursor: 'pointer' }}
|
||||
>
|
||||
Forgotten password?
|
||||
</Link>
|
||||
)}
|
||||
</Box>
|
||||
{error && <Error message={error} />}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -7,19 +7,17 @@ export const WordTile = ({
|
||||
index,
|
||||
disabled,
|
||||
onClick,
|
||||
button,
|
||||
}: {
|
||||
mnemonicWord: string;
|
||||
index?: number;
|
||||
disabled?: boolean;
|
||||
onClick?: boolean;
|
||||
button?: boolean;
|
||||
}) => (
|
||||
<Card
|
||||
variant="outlined"
|
||||
sx={{
|
||||
background: button ? '#151A2C' : 'transparent',
|
||||
border: button ? '1px solid #3A4053' : 'none',
|
||||
background: '#151A2C',
|
||||
border: '1px solid #3A4053',
|
||||
cursor: onClick ? 'pointer' : 'default',
|
||||
opacity: disabled ? 0.2 : 1,
|
||||
}}
|
||||
@@ -42,12 +40,10 @@ export const WordTiles = ({
|
||||
mnemonicWords,
|
||||
showIndex,
|
||||
onClick,
|
||||
buttons,
|
||||
}: {
|
||||
mnemonicWords?: TMnemonicWords;
|
||||
showIndex?: boolean;
|
||||
onClick?: ({ name, index }: { name: string; index: number }) => void;
|
||||
buttons?: boolean;
|
||||
}) => {
|
||||
if (mnemonicWords) {
|
||||
return (
|
||||
@@ -59,7 +55,6 @@ export const WordTiles = ({
|
||||
index={showIndex ? index : undefined}
|
||||
onClick={!!onClick}
|
||||
disabled={disabled}
|
||||
button={buttons}
|
||||
/>
|
||||
</Grid>
|
||||
))}
|
||||
|
||||
@@ -1,72 +0,0 @@
|
||||
import React, { createContext, useEffect, useMemo, useState } from 'react';
|
||||
import { createMnemonic, signInWithMnemonic } from 'src/requests';
|
||||
import { TMnemonicWords } from '../types';
|
||||
|
||||
export const SignInContext = createContext({} as TSignInContent);
|
||||
|
||||
export type TSignInContent = {
|
||||
error?: string;
|
||||
password: string;
|
||||
mnemonic: string;
|
||||
mnemonicWords: TMnemonicWords;
|
||||
setError: (err?: string) => void;
|
||||
setMnemonic: (mnc: string) => void;
|
||||
generateMnemonic: () => Promise<void>;
|
||||
validateMnemonic: () => Promise<void>;
|
||||
setPassword: (paswd: string) => void;
|
||||
};
|
||||
|
||||
const mnemonicToArray = (mnemonic: string): TMnemonicWords =>
|
||||
mnemonic
|
||||
.split(' ')
|
||||
.reduce((a, c: string, index) => [...a, { name: c, index: index + 1, disabled: false }], [] as TMnemonicWords);
|
||||
|
||||
export const SignInProvider: React.FC = ({ children }) => {
|
||||
const [password, setPassword] = useState('');
|
||||
const [mnemonic, setMnemonic] = useState('');
|
||||
const [mnemonicWords, setMnemonicWords] = useState<TMnemonicWords>([]);
|
||||
const [error, setError] = useState<string>();
|
||||
|
||||
const generateMnemonic = async () => {
|
||||
const mnemonicPhrase = await createMnemonic();
|
||||
setMnemonic(mnemonicPhrase);
|
||||
};
|
||||
|
||||
const validateMnemonic = async () => {
|
||||
try {
|
||||
await signInWithMnemonic(mnemonic);
|
||||
} catch (e) {
|
||||
setError(e as string);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (mnemonic.length > 0) {
|
||||
const mnemonicArray = mnemonicToArray(mnemonic);
|
||||
setMnemonicWords(mnemonicArray);
|
||||
} else {
|
||||
setMnemonicWords([]);
|
||||
}
|
||||
}, [mnemonic]);
|
||||
|
||||
return (
|
||||
<SignInContext.Provider
|
||||
value={useMemo(
|
||||
() => ({
|
||||
error,
|
||||
password,
|
||||
mnemonic,
|
||||
mnemonicWords,
|
||||
setError,
|
||||
setMnemonic,
|
||||
generateMnemonic,
|
||||
validateMnemonic,
|
||||
setPassword,
|
||||
}),
|
||||
[error, password, mnemonic, mnemonicWords],
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SignInContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -1,24 +1,31 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { Stack, Box, CircularProgress } from '@mui/material';
|
||||
import { NymWordmark } from '@nymproject/react';
|
||||
import {
|
||||
CreatePassword,
|
||||
ExistingAccount,
|
||||
CreateMnemonic,
|
||||
VerifyMnemonic,
|
||||
WelcomeContent,
|
||||
SignInMnemonic,
|
||||
} from './pages';
|
||||
import { TLoginType, TPages } from './types';
|
||||
import { RenderPage, Step } from './components';
|
||||
import { NymLogo } from '@nymproject/react';
|
||||
import { CircularProgress, Stack, Box } from '@mui/material';
|
||||
import { ExistingAccount, WelcomeContent } from './pages';
|
||||
import { TPages } from './types';
|
||||
import { RenderPage } from './components';
|
||||
import { CreateAccountContent } from './_legacy_create-account';
|
||||
import { ClientContext } from '../../context/main';
|
||||
import { SignInPassword } from './pages/signin-password';
|
||||
|
||||
// const testMnemonic =
|
||||
// 'futuristic big receptive caption saw hug odd spoon internal dime bike rake helpless left distribution gusty eyes beg enormous word influence trashy pets curl';
|
||||
//
|
||||
// const mnemonicToArray = (mnemonic: string): TMnemonicWords =>
|
||||
// mnemonic
|
||||
// .split(' ')
|
||||
// .reduce((a, c: string, index) => [...a, { name: c, index: index + 1, disabled: false }], [] as TMnemonicWords);
|
||||
|
||||
export const Welcome = () => {
|
||||
const [page, setPage] = useState<TPages>('welcome');
|
||||
const [loginType, setLoginType] = useState<TLoginType>('mnemonic');
|
||||
// const [mnemonicWords, setMnemonicWords] = useState<TMnemonicWords>();
|
||||
|
||||
const { isLoading } = useContext(ClientContext);
|
||||
|
||||
// useEffect(() => {
|
||||
// const mnemonicArray = mnemonicToArray(testMnemonic)
|
||||
// setMnemonicWords(mnemonicArray)
|
||||
// }, [])
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@@ -43,39 +50,28 @@ export const Welcome = () => {
|
||||
<CircularProgress size={72} />
|
||||
) : (
|
||||
<Stack spacing={3} alignItems="center" sx={{ width: 1080 }}>
|
||||
<NymWordmark width={75} />
|
||||
<Step currentPage={page} totalSteps={3} />
|
||||
<NymLogo width={75} />
|
||||
<RenderPage page={page}>
|
||||
<WelcomeContent
|
||||
onUseExisting={() => setPage('existing account')}
|
||||
onCreateAccount={() => setPage('create mnemonic')}
|
||||
onCreateAccountComplete={() => setPage('legacy create account')}
|
||||
page="welcome"
|
||||
/>
|
||||
<CreateMnemonic
|
||||
onNext={() => setPage('verify mnemonic')}
|
||||
onPrev={() => setPage('create password')}
|
||||
page="create mnemonic"
|
||||
/>
|
||||
<VerifyMnemonic onNext={() => setPage('create password')} onPrev={() => {}} page="verify mnemonic" />
|
||||
<CreatePassword
|
||||
onSkip={() => {
|
||||
setLoginType('mnemonic');
|
||||
setPage('existing account');
|
||||
}}
|
||||
onNext={() => {
|
||||
setLoginType('password');
|
||||
setPage('existing account');
|
||||
}}
|
||||
page="create password"
|
||||
/>
|
||||
<ExistingAccount
|
||||
onPrev={() => setPage('welcome')}
|
||||
page="existing account"
|
||||
loginType={loginType}
|
||||
setLoginType={(loginType) => setLoginType(loginType)}
|
||||
/>
|
||||
<SignInMnemonic onPrev={() => setPage('welcome')} page="sign in with mnemonic" />
|
||||
<SignInPassword onPrev={() => setPage('welcome')} page="sign in with password" />
|
||||
|
||||
<CreateAccountContent page="legacy create account" showSignIn={() => setPage('existing account')} />
|
||||
{/* <MnemonicWords
|
||||
mnemonicWords={mnemonicWords}
|
||||
onNext={() => setPage('verify mnemonic')}
|
||||
onPrev={() => setPage('welcome')}
|
||||
page="create account"
|
||||
/>
|
||||
<VerifyMnemonic
|
||||
mnemonicWords={mnemonicWords}
|
||||
onComplete={() => setPage('create password')}
|
||||
page="verify mnemonic"
|
||||
/>
|
||||
<CreatePassword page="create password" /> */}
|
||||
<ExistingAccount onPrev={() => setPage('welcome')} page="existing account" />
|
||||
</RenderPage>
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { Alert, Button, Grid, Stack, Typography } from '@mui/material';
|
||||
import { Check, ContentCopySharp } from '@mui/icons-material';
|
||||
import { useClipboard } from 'use-clipboard-copy';
|
||||
import { WordTiles } from '../components';
|
||||
import { TPages } from '../types';
|
||||
import { SignInContext } from '../context';
|
||||
|
||||
export const CreateMnemonic = ({ onNext }: { page: TPages; onNext: () => void; onPrev: () => void }) => {
|
||||
const { mnemonic, mnemonicWords, generateMnemonic, validateMnemonic, setMnemonic, setError } =
|
||||
useContext(SignInContext);
|
||||
|
||||
useEffect(() => {
|
||||
generateMnemonic();
|
||||
}, []);
|
||||
|
||||
const { copy, copied } = useClipboard({ copiedTimeout: 5000 });
|
||||
|
||||
return (
|
||||
<Stack alignItems="center" spacing={3}>
|
||||
<Typography sx={{ color: 'common.white', fontWeight: 600 }} textAlign="center">
|
||||
Write down your mnemonic
|
||||
</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>
|
||||
</Alert>
|
||||
|
||||
<WordTiles mnemonicWords={mnemonicWords} showIndex />
|
||||
|
||||
<Button
|
||||
color="inherit"
|
||||
disableElevation
|
||||
size="large"
|
||||
onClick={() => {
|
||||
copy(mnemonic);
|
||||
}}
|
||||
sx={{
|
||||
width: 250,
|
||||
}}
|
||||
endIcon={!copied ? <ContentCopySharp /> : <Check color="success" />}
|
||||
>
|
||||
Copy mnemonic
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disableElevation
|
||||
size="large"
|
||||
onClick={onNext}
|
||||
sx={{ width: 250 }}
|
||||
disabled={!copied}
|
||||
>
|
||||
I saved my mnemonic
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -1,74 +1,60 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { Alert, Button, FormControl, Stack } from '@mui/material';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { TPages } from '../types';
|
||||
import React, { useState } from 'react';
|
||||
import { Button, FormControl, Grid, IconButton, Stack, TextField } from '@mui/material';
|
||||
import { VisibilityOff, Visibility } from '@mui/icons-material';
|
||||
import { Subtitle, Title, PasswordStrength } from '../components';
|
||||
import { PasswordInput } from '../components/textfields';
|
||||
import { SignInContext } from '../context';
|
||||
import { createPassword } from '../../../requests';
|
||||
|
||||
export const CreatePassword = ({ onSkip, onNext }: { page: TPages; onNext: () => void; onSkip: () => void }) => {
|
||||
const { password, setPassword } = useContext(SignInContext);
|
||||
const [confirmedPassword, setConfirmedPassword] = useState<string>('');
|
||||
const [isStrongPassword, setIsStrongPassword] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const { mnemonic } = useContext(SignInContext);
|
||||
|
||||
const handleSkip = () => {
|
||||
setPassword('');
|
||||
onSkip();
|
||||
};
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const storePassword = async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await createPassword({ mnemonic, password });
|
||||
enqueueSnackbar('Password successfully created', { variant: 'success' });
|
||||
setPassword('');
|
||||
onNext();
|
||||
} catch (e) {
|
||||
enqueueSnackbar(e as string, { variant: 'error' });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
export const CreatePassword = () => {
|
||||
const [password, setPassword] = useState<string>('');
|
||||
const [confirmedPassword, setConfirmedPassword] = useState<string>();
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [showConfirmedPassword, setShowConfirmedPassword] = useState(false);
|
||||
|
||||
return (
|
||||
<Stack spacing={3} alignItems="center" minWidth="50%">
|
||||
<Title title="Create optional password" />
|
||||
<Subtitle subtitle="Password should be min 8 characters, at least one number and one symbol" />
|
||||
<FormControl fullWidth>
|
||||
<Stack spacing={2}>
|
||||
<>
|
||||
<PasswordInput
|
||||
password={password}
|
||||
onUpdatePassword={(pswd) => setPassword(pswd)}
|
||||
label="Password"
|
||||
autoFocus
|
||||
/>
|
||||
<PasswordStrength password={password} onChange={(isStrong) => setIsStrongPassword(isStrong)} />
|
||||
</>
|
||||
<PasswordInput
|
||||
password={confirmedPassword}
|
||||
onUpdatePassword={(pswd) => setConfirmedPassword(pswd)}
|
||||
label="Confirm password"
|
||||
/>
|
||||
<Button
|
||||
size="large"
|
||||
variant="contained"
|
||||
disabled={password !== confirmedPassword || password.length === 0 || !isStrongPassword || isLoading}
|
||||
onClick={storePassword}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
<Button size="large" color="info" onClick={handleSkip}>
|
||||
Skip and sign in with mnemonic
|
||||
</Button>
|
||||
</Stack>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
<>
|
||||
<Title title="Create password" />
|
||||
<Subtitle subtitle="Create a strong password. Min 8 characters, at least one capital letter, number and special symbol" />
|
||||
<Grid container justifyContent="center">
|
||||
<Grid item xs={6}>
|
||||
<FormControl fullWidth>
|
||||
<Stack spacing={2}>
|
||||
<TextField
|
||||
label="Password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
type={showPassword ? 'input' : 'password'}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<IconButton onClick={() => setShowPassword((show) => !show)}>
|
||||
{showPassword ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<PasswordStrength password={password} />
|
||||
<TextField
|
||||
label="Confirm password"
|
||||
value={confirmedPassword}
|
||||
onChange={(e) => setConfirmedPassword(e.target.value)}
|
||||
type={showConfirmedPassword ? 'input' : 'password'}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<IconButton onClick={() => setShowConfirmedPassword((show) => !show)}>
|
||||
{showConfirmedPassword ? <VisibilityOff /> : <Visibility />}
|
||||
</IconButton>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size="large"
|
||||
variant="contained"
|
||||
disabled={password !== confirmedPassword || password.length === 0}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</Stack>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,78 +1,55 @@
|
||||
/* eslint-disable react/no-unused-prop-types */
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { Alert, Button, FormControl, Stack, ToggleButton, ToggleButtonGroup } from '@mui/material';
|
||||
import { ClientContext } from 'src/context/main';
|
||||
import { Subtitle, MnemonicInput, PasswordInput } from '../components';
|
||||
import { TLoginType, TPages } from '../types';
|
||||
import { Alert, Button, Stack, TextField } from '@mui/material';
|
||||
import { Subtitle } from '../components';
|
||||
import { ClientContext } from '../../../context/main';
|
||||
import { TPages } from '../types';
|
||||
|
||||
export const ExistingAccount: React.FC<{
|
||||
page: TPages;
|
||||
loginType: TLoginType;
|
||||
setLoginType: (type: 'mnemonic' | 'password') => void;
|
||||
onPrev: () => void;
|
||||
}> = ({ loginType, setLoginType, onPrev }) => {
|
||||
const [password, setPassword] = useState('');
|
||||
const [mnemonic, setMnemonic] = useState('');
|
||||
const { setError, logIn, error } = useContext(ClientContext);
|
||||
export const ExistingAccount: React.FC<{ page: TPages; onPrev: () => void }> = ({ onPrev }) => {
|
||||
const [mnemonic, setMnemonic] = useState<string>('');
|
||||
|
||||
const { logIn, error } = useContext(ClientContext);
|
||||
|
||||
const handleSignIn = async () => {
|
||||
await logIn(mnemonic);
|
||||
};
|
||||
|
||||
const handleSignInOnEnter = ({ key }: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (key.toLowerCase() === 'enter') {
|
||||
logIn(mnemonic);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Subtitle subtitle={`Enter your ${loginType} for existing wallet`} />
|
||||
<Alert variant="outlined" severity="info">
|
||||
You can use either a mnemonic or a password to access your wallet
|
||||
</Alert>
|
||||
<Stack spacing={2} minWidth="50%">
|
||||
<ToggleButtonGroup
|
||||
fullWidth
|
||||
value={loginType}
|
||||
exclusive
|
||||
onChange={(_: React.MouseEvent<HTMLElement>, value: TLoginType) => {
|
||||
setError(undefined);
|
||||
setLoginType(value);
|
||||
}}
|
||||
>
|
||||
<ToggleButton value="mnemonic">Mnemonic</ToggleButton>
|
||||
<ToggleButton value="password">Password</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
<FormControl fullWidth>
|
||||
<Stack spacing={2}>
|
||||
{loginType === 'mnemonic' && (
|
||||
<MnemonicInput mnemonic={mnemonic} onUpdateMnemonic={(mnc) => setMnemonic(mnc)} error={error} />
|
||||
)}
|
||||
{loginType === 'password' && (
|
||||
<PasswordInput
|
||||
password={password}
|
||||
onUpdatePassword={(pswd) => setPassword(pswd)}
|
||||
label="Password"
|
||||
autoFocus
|
||||
error={error}
|
||||
/>
|
||||
)}
|
||||
<Stack spacing={2} sx={{ width: 400 }} alignItems="center">
|
||||
<Subtitle subtitle="Enter your mnemonic from existing wallet" />
|
||||
<TextField
|
||||
value={mnemonic}
|
||||
onChange={(e) => setMnemonic(e.target.value)}
|
||||
multiline
|
||||
rows={5}
|
||||
fullWidth
|
||||
onKeyDown={handleSignInOnEnter}
|
||||
/>
|
||||
{error && (
|
||||
<Alert severity="error" variant="outlined" data-testid="error" sx={{ color: 'error.light', width: '100%' }}>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
size="large"
|
||||
fullWidth
|
||||
onClick={() => logIn({ type: loginType, value: loginType === 'mnemonic' ? mnemonic : password })}
|
||||
>
|
||||
{`Sign in with ${loginType}`}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
disableElevation
|
||||
size="large"
|
||||
onClick={() => {
|
||||
setError(undefined);
|
||||
onPrev();
|
||||
}}
|
||||
fullWidth
|
||||
sx={{ color: 'common.white', border: '1px solid white', '&:hover': { border: '1px solid white' } }}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Stack>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
</>
|
||||
<Button variant="contained" size="large" fullWidth onClick={handleSignIn}>
|
||||
Sign in
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
disableElevation
|
||||
size="large"
|
||||
onClick={onPrev}
|
||||
fullWidth
|
||||
sx={{ color: 'common.white', border: '1px solid white', '&:hover': { border: '1px solid white' } }}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
export * from './welcome-content';
|
||||
export * from './create-mnemonic';
|
||||
export * from './mnemonic-words';
|
||||
export * from './verify-mnemonic';
|
||||
export * from './create-password';
|
||||
export * from './existing-account';
|
||||
export * from './signin-mnemonic';
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import { Alert, Button, Typography } from '@mui/material';
|
||||
import { WordTiles } from '../components';
|
||||
import { TMnemonicWords } from '../types';
|
||||
|
||||
export const MnemonicWords = ({
|
||||
mnemonicWords,
|
||||
onNext,
|
||||
onPrev,
|
||||
}: {
|
||||
mnemonicWords?: TMnemonicWords;
|
||||
onNext: () => void;
|
||||
onPrev: () => void;
|
||||
}) => (
|
||||
<>
|
||||
<Typography sx={{ color: 'common.white', fontWeight: 600 }}>Write down your mnemonic</Typography>
|
||||
<Alert icon={false} severity="info" sx={{ bgcolor: '#18263B', color: '#50ABFF', width: 625 }}>
|
||||
Please store your mnemonic in a safe place. This is the only way to access your wallet!
|
||||
</Alert>
|
||||
<WordTiles mnemonicWords={mnemonicWords} showIndex />
|
||||
<Button variant="contained" color="primary" disableElevation size="large" onClick={onNext} sx={{ width: 250 }}>
|
||||
Verify mnemonic
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
disableElevation
|
||||
size="large"
|
||||
onClick={onPrev}
|
||||
sx={{ color: 'common.white', border: '1px solid white', '&:hover': { border: '1px solid white' }, width: 250 }}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
@@ -1,43 +0,0 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { Button, FormControl, Stack } from '@mui/material';
|
||||
import { MnemonicInput, Subtitle } from '../components';
|
||||
import { ClientContext } from '../../../context/main';
|
||||
import { TPages } from '../types';
|
||||
|
||||
export const SignInMnemonic = ({ page, onPrev }: { page: TPages; onPrev: () => void }) => {
|
||||
const [mnemonic, setMnemonic] = useState('');
|
||||
const { setError, logIn, error } = useContext(ClientContext);
|
||||
|
||||
return (
|
||||
<Stack spacing={2} alignItems="center" minWidth="50%">
|
||||
<Subtitle subtitle="Enter a mnemonic to sign in" />
|
||||
<FormControl fullWidth>
|
||||
<Stack spacing={2}>
|
||||
<MnemonicInput mnemonic={mnemonic} onUpdateMnemonic={(mnc) => setMnemonic(mnc)} error={error} />
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
size="large"
|
||||
fullWidth
|
||||
onClick={() => logIn({ type: 'mnemonic', value: mnemonic })}
|
||||
>
|
||||
{`Sign in with mnemonic`}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
disableElevation
|
||||
size="large"
|
||||
onClick={() => {
|
||||
setError(undefined);
|
||||
onPrev();
|
||||
}}
|
||||
fullWidth
|
||||
sx={{ color: 'common.white', border: '1px solid white', '&:hover': { border: '1px solid white' } }}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Stack>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { Button, FormControl, Stack } from '@mui/material';
|
||||
import { PasswordInput, Subtitle } from '../components';
|
||||
import { ClientContext } from '../../../context/main';
|
||||
import { TPages } from '../types';
|
||||
|
||||
export const SignInPassword = ({ onPrev }: { page: TPages; onPrev: () => void }) => {
|
||||
const [password, setPassword] = useState('');
|
||||
const { setError, logIn, error } = useContext(ClientContext);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack spacing={2} alignItems="center" minWidth="50%">
|
||||
<Subtitle subtitle="Enter a password to sign in" />
|
||||
<FormControl fullWidth>
|
||||
<Stack spacing={2}>
|
||||
<PasswordInput
|
||||
label="Enter password"
|
||||
password={password}
|
||||
onUpdatePassword={(pswd) => setPassword(pswd)}
|
||||
error={error}
|
||||
autoFocus
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="large"
|
||||
fullWidth
|
||||
onClick={() => logIn({ type: 'password', value: password })}
|
||||
>
|
||||
{`Sign in with password`}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outlined"
|
||||
disableElevation
|
||||
size="large"
|
||||
onClick={() => {
|
||||
setError(undefined);
|
||||
onPrev();
|
||||
}}
|
||||
fullWidth
|
||||
sx={{ color: 'common.white', border: '1px solid white', '&:hover': { border: '1px solid white' } }}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
</Stack>
|
||||
</FormControl>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,19 +1,22 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Button, Stack } from '@mui/material';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button } from '@mui/material';
|
||||
import { HiddenWords, Subtitle, Title, WordTiles } from '../components';
|
||||
import { THiddenMnemonicWord, THiddenMnemonicWords, TMnemonicWord, TMnemonicWords, TPages } from '../types';
|
||||
import { THiddenMnemonicWord, THiddenMnemonicWords, TMnemonicWord, TMnemonicWords } from '../types';
|
||||
import { randomNumberBetween } from '../../../utils';
|
||||
import { SignInContext } from '../context';
|
||||
|
||||
const numberOfRandomWords = 6;
|
||||
const numberOfRandomWords = 4;
|
||||
|
||||
export const VerifyMnemonic = ({ onNext }: { page: TPages; onNext: () => void; onPrev: () => void }) => {
|
||||
export const VerifyMnemonic = ({
|
||||
mnemonicWords,
|
||||
onComplete,
|
||||
}: {
|
||||
mnemonicWords?: TMnemonicWords;
|
||||
onComplete: () => void;
|
||||
}) => {
|
||||
const [randomWords, setRandomWords] = useState<TMnemonicWords>();
|
||||
const [hiddenRandomWords, setHiddenRandomWords] = useState<THiddenMnemonicWords>();
|
||||
const [currentSelection, setCurrentSelection] = useState(0);
|
||||
|
||||
const { mnemonicWords } = useContext(SignInContext);
|
||||
|
||||
useEffect(() => {
|
||||
if (mnemonicWords) {
|
||||
const newRandomWords = getRandomEntriesFromArray<TMnemonicWord>(mnemonicWords, numberOfRandomWords);
|
||||
@@ -45,21 +48,16 @@ export const VerifyMnemonic = ({ onNext }: { page: TPages; onNext: () => void; o
|
||||
<WordTiles
|
||||
mnemonicWords={randomWords}
|
||||
onClick={currentSelection !== numberOfRandomWords ? revealWord : undefined}
|
||||
buttons
|
||||
/>
|
||||
<Stack spacing={3} sx={{ width: 300 }}>
|
||||
<Button
|
||||
variant="contained"
|
||||
fullWidth
|
||||
size="large"
|
||||
disabled={currentSelection !== numberOfRandomWords}
|
||||
onClick={() => {
|
||||
onNext();
|
||||
}}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</Stack>
|
||||
<Button
|
||||
variant="contained"
|
||||
sx={{ width: 300 }}
|
||||
size="large"
|
||||
disabled={currentSelection !== numberOfRandomWords}
|
||||
onClick={onComplete}
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,17 +7,35 @@ import { TPages } from '../types';
|
||||
export const WelcomeContent: React.FC<{
|
||||
page: TPages;
|
||||
onUseExisting: () => void;
|
||||
onCreateAccount: () => void;
|
||||
}> = ({ onUseExisting, onCreateAccount }) => (
|
||||
onCreateAccountComplete: () => void;
|
||||
}> = ({ onUseExisting, onCreateAccountComplete }) => (
|
||||
<>
|
||||
<Title title="Welcome to NYM" />
|
||||
<SubtitleSlick subtitle="Next generation of privacy" />
|
||||
<Stack spacing={3} minWidth={300}>
|
||||
<Button fullWidth color="primary" variant="contained" size="large" onClick={onUseExisting}>
|
||||
Sign in
|
||||
<Stack spacing={3} sx={{ width: 300 }}>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disableElevation
|
||||
size="large"
|
||||
onClick={onCreateAccountComplete}
|
||||
>
|
||||
Create Account
|
||||
</Button>
|
||||
<Button fullWidth color="inherit" disableElevation size="large" onClick={onCreateAccount}>
|
||||
Create account
|
||||
<Button
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
size="large"
|
||||
sx={{
|
||||
color: 'common.white',
|
||||
border: '1px solid white',
|
||||
'&:hover': { border: '1px solid white', '&:hover': { background: 'none' } },
|
||||
}}
|
||||
onClick={onUseExisting}
|
||||
disableRipple
|
||||
>
|
||||
Sign in
|
||||
</Button>
|
||||
</Stack>
|
||||
</>
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
export type TPages =
|
||||
| 'welcome'
|
||||
| 'create mnemonic'
|
||||
| 'create account'
|
||||
| 'verify mnemonic'
|
||||
| 'create password'
|
||||
| 'existing account'
|
||||
| 'select network'
|
||||
| 'legacy create account'
|
||||
| 'sign in with mnemonic'
|
||||
| 'sign in with password';
|
||||
| 'legacy create account';
|
||||
|
||||
export type TMnemonicWord = {
|
||||
name: string;
|
||||
@@ -19,5 +17,3 @@ export type TMnemonicWords = TMnemonicWord[];
|
||||
export type THiddenMnemonicWord = { hidden: boolean } & TMnemonicWord;
|
||||
|
||||
export type THiddenMnemonicWords = THiddenMnemonicWord[];
|
||||
|
||||
export type TLoginType = 'mnemonic' | 'password';
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { Account } from '../types';
|
||||
import { Account, TCreateAccount } from '../types';
|
||||
|
||||
export const createMnemonic = async (): Promise<string> => invoke('create_new_mnemonic');
|
||||
export const createAccount = async (): Promise<TCreateAccount> => {
|
||||
const res: TCreateAccount = await invoke('create_new_account');
|
||||
return res;
|
||||
};
|
||||
|
||||
export const createPassword = async ({ mnemonic, password }: { mnemonic: string; password: string }): Promise<void> => {
|
||||
await invoke('create_password', { mnemonic, password });
|
||||
export const createMnemonic = async (): Promise<string> => {
|
||||
const res: string = await invoke('create_new_mnemonic');
|
||||
return res;
|
||||
};
|
||||
|
||||
export const signInWithMnemonic = async (mnemonic: string): Promise<Account> => {
|
||||
@@ -12,11 +16,6 @@ export const signInWithMnemonic = async (mnemonic: string): Promise<Account> =>
|
||||
return res;
|
||||
};
|
||||
|
||||
export const signInWithPassword = async (password: string): Promise<Account> => {
|
||||
const res: Account = await invoke('sign_in_with_password', { password });
|
||||
return res;
|
||||
};
|
||||
|
||||
export const signOut = async (): Promise<void> => {
|
||||
await invoke('logout');
|
||||
};
|
||||
|
||||
@@ -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 @@
|
||||
|
||||
[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>",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -131,9 +131,13 @@ impl<C> ValidatorCacheRefresher<C> {
|
||||
self.nymd_client.get_gateways(),
|
||||
)?;
|
||||
|
||||
let rewarded_set_identities = self.nymd_client.get_rewarded_set_identities().await?;
|
||||
let (rewarded_set, active_set) =
|
||||
self.collect_rewarded_and_active_set_details(&mixnodes, rewarded_set_identities);
|
||||
let (rewarded_set, active_set) = if let Ok(rewarded_set_identities) =
|
||||
self.nymd_client.get_rewarded_set_identities().await
|
||||
{
|
||||
self.collect_rewarded_and_active_set_details(&mixnodes, rewarded_set_identities)
|
||||
} else {
|
||||
(Vec::new(), Vec::new())
|
||||
};
|
||||
|
||||
let epoch_rewarding_params = self.nymd_client.get_current_epoch_reward_params().await?;
|
||||
|
||||
@@ -154,15 +158,19 @@ impl<C> ValidatorCacheRefresher<C> {
|
||||
.await;
|
||||
|
||||
if let Some(notify) = &self.update_rewarded_set_notify {
|
||||
let update_details = self
|
||||
if let Ok(update_details) = self
|
||||
.nymd_client
|
||||
.get_current_rewarded_set_update_details()
|
||||
.await?;
|
||||
|
||||
if update_details.last_refreshed_block + (update_details.refresh_rate_blocks as u64)
|
||||
< update_details.current_height
|
||||
.await
|
||||
{
|
||||
// there's only ever a single waiter -> the set updater
|
||||
if update_details.last_refreshed_block + (update_details.refresh_rate_blocks as u64)
|
||||
< update_details.current_height
|
||||
{
|
||||
// there's only ever a single waiter -> the set updater
|
||||
notify.notify_one()
|
||||
}
|
||||
} else {
|
||||
// This has the potential to be spammy, we'll find out
|
||||
notify.notify_one()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,6 +64,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| {
|
||||
@@ -276,6 +279,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)
|
||||
|
||||
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user