Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6dbb3fbd3f | |||
| 95b6ac50be | |||
| 5f2247ab83 | |||
| 842bcfa782 | |||
| c2938e0672 | |||
| 278516ad11 | |||
| ce241339bf | |||
| 8d7428923a | |||
| 4a1a4b6415 |
Generated
+24
-44
@@ -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",
|
||||
@@ -3683,15 +3673,6 @@ dependencies = [
|
||||
"uint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "primitives"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmrs",
|
||||
"cosmwasm-std",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "1.1.3"
|
||||
@@ -6008,7 +5989,6 @@ dependencies = [
|
||||
"log",
|
||||
"mixnet-contract-common",
|
||||
"network-defaults",
|
||||
"primitives",
|
||||
"prost",
|
||||
"reqwest",
|
||||
"serde",
|
||||
@@ -6072,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",
|
||||
@@ -6090,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",
|
||||
|
||||
@@ -44,7 +44,6 @@ members = [
|
||||
"common/nymsphinx/params",
|
||||
"common/nymsphinx/types",
|
||||
"common/pemstore",
|
||||
"common/primitives",
|
||||
"common/socks5/proxy-helpers",
|
||||
"common/socks5/requests",
|
||||
"common/topology",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -21,7 +21,6 @@ url = { version = "2.2", features = ["serde"] }
|
||||
|
||||
coconut-interface = { path = "../../coconut-interface" }
|
||||
network-defaults = { path = "../../network-defaults" }
|
||||
primitives = { path = "../../primitives" }
|
||||
validator-api-requests = { path = "../../../validator-api/validator-api-requests" }
|
||||
|
||||
# required for nymd-client
|
||||
@@ -39,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"
|
||||
|
||||
@@ -369,14 +369,14 @@ pub trait SigningCosmWasmClient: CosmWasmClient {
|
||||
&self,
|
||||
sender_address: &AccountId,
|
||||
recipient_address: &AccountId,
|
||||
amount: Vec<primitives::Coin>,
|
||||
amount: Vec<Coin>,
|
||||
fee: Fee,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_commit::Response, NymdError> {
|
||||
let send_msg = MsgSend {
|
||||
from_address: sender_address.clone(),
|
||||
to_address: recipient_address.clone(),
|
||||
amount: primitives::coin::try_into(amount)?,
|
||||
amount,
|
||||
}
|
||||
.to_any()
|
||||
.map_err(|_| NymdError::SerializationError("MsgSend".to_owned()))?;
|
||||
|
||||
@@ -108,9 +108,6 @@ pub enum NymdError {
|
||||
|
||||
#[error("Abci query failed with code {0} - {1}")]
|
||||
AbciError(u32, abci::Log),
|
||||
|
||||
#[error("Failed to handle primitives")]
|
||||
PrimitivesError(#[from] primitives::PrimitivesError)
|
||||
}
|
||||
|
||||
impl NymdError {
|
||||
|
||||
@@ -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)
|
||||
@@ -606,7 +608,7 @@ impl<C> NymdClient<C> {
|
||||
pub async fn send(
|
||||
&self,
|
||||
recipient: &AccountId,
|
||||
amount: Vec<primitives::Coin>,
|
||||
amount: Vec<CosmosCoin>,
|
||||
memo: impl Into<String> + Send + 'static,
|
||||
) -> Result<broadcast::tx_commit::Response, NymdError>
|
||||
where
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
[package]
|
||||
name = "primitives"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
cosmrs = { version = "0.4.1", features = ["rpc", "bip32", "cosmwasm"] }
|
||||
cosmwasm-std = "1.0.0-beta3"
|
||||
thiserror = "1.0"
|
||||
@@ -1,100 +0,0 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::PrimitivesError;
|
||||
use core::fmt;
|
||||
use cosmwasm_std::Uint128;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Common Coin type for the backend.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Coin {
|
||||
pub amount: Uint128,
|
||||
pub denom: String,
|
||||
}
|
||||
|
||||
impl Coin {
|
||||
pub fn new(amount: u128, denom: impl Into<String>) -> Coin {
|
||||
Coin {
|
||||
amount: Uint128::new(amount),
|
||||
denom: denom.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Coin {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}{}", self.amount, self.denom)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<cosmrs::Coin> for Coin {
|
||||
type Error = PrimitivesError;
|
||||
|
||||
fn try_from(cosmos_coin: cosmrs::Coin) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
amount: cosmos_coin.amount.to_string().as_str().try_into()?,
|
||||
denom: cosmos_coin.denom.to_string(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Coin> for cosmrs::Coin {
|
||||
type Error = PrimitivesError;
|
||||
|
||||
fn try_from(coin: Coin) -> Result<Self, Self::Error> {
|
||||
Ok(Self {
|
||||
denom: cosmrs::Denom::from_str(&coin.denom)?,
|
||||
amount: cosmrs::Decimal::from_str(&coin.amount.to_string())?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn try_into(coins: Vec<Coin>) -> Result<Vec<cosmrs::Coin>, PrimitivesError> {
|
||||
coins.into_iter().map(TryInto::try_into).collect::<Result<Vec<_>, _>>()
|
||||
}
|
||||
|
||||
pub fn into(coins: Vec<Coin>) -> Vec<cosmwasm_std::Coin> {
|
||||
coins.into_iter().map(Into::into).collect()
|
||||
}
|
||||
|
||||
impl From<cosmwasm_std::Coin> for Coin {
|
||||
fn from(cosmwasm_coin: cosmwasm_std::Coin) -> Self {
|
||||
Self {
|
||||
amount: cosmwasm_coin.amount,
|
||||
denom: cosmwasm_coin.denom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Coin> for cosmwasm_std::Coin {
|
||||
fn from(coin: Coin) -> Self {
|
||||
Self {
|
||||
amount: coin.amount,
|
||||
denom: coin.denom,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn convert_to_and_from_cosmwasm_coin() {
|
||||
let coin = Coin::new(42, "ucoin");
|
||||
let cosmwasm_coin: cosmwasm_std::Coin = coin.clone().into();
|
||||
assert_eq!(coin, Coin::from(cosmwasm_coin));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_to_and_from_cosmos_coin() {
|
||||
let coin = Coin::new(42, "ucoin");
|
||||
let cosmos_coin: cosmrs::Coin = coin.clone().try_into().unwrap();
|
||||
assert_eq!(coin, Coin::try_from(cosmos_coin).unwrap());
|
||||
}
|
||||
|
||||
// WIP(JON): more tests
|
||||
// Especially converting from cosmrs::Coin
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum PrimitivesError {
|
||||
#[error("{source}")]
|
||||
CosmwasmError {
|
||||
#[from]
|
||||
source: cosmwasm_std::StdError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
CosmrsError {
|
||||
#[from]
|
||||
source: cosmrs::ErrorReport,
|
||||
},
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod coin;
|
||||
mod error;
|
||||
|
||||
pub use error::PrimitivesError;
|
||||
pub use coin::Coin;
|
||||
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",
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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