Compare commits

...

19 Commits

Author SHA1 Message Date
benedettadavico 59feaf0c07 Merge branch 'develop' into feature/wasm-tests 2023-03-16 17:36:28 +01:00
benedettadavico f20eaa875c sdk wasm tests 2023-03-16 17:35:16 +01:00
Jon Häggblad 4c2967a733 Delete stray .gitignore file 2023-03-16 15:27:45 +00:00
Jon Häggblad 2b80e5d1c9 Remove leftover file from deleted crate 2023-03-16 12:59:46 +00:00
Jon Häggblad 4e7ff53214 Contract and client support for updating gateway config (#3166)
* mixnet-contract: add update gateway config

* mixnet-contract: tests for updating gateway config

* vesting-contract: add update gateway config

* validator-client: add update gateway config

* wallet: add update_gateway_config

* common/commands: add support for setting gateway config

* Remove commented out line

* Review fixes

* Generate ts file for GatewayConfigUpdate type

* Add generated GatewayConfigUpdate.ts file
2023-03-16 13:43:56 +01:00
Jon Häggblad 9ec36e49b7 contracts: remove .gitignore with Cargo.lock in it
While developing the service-provider-directory contract I ran into
issues with the lock file being inconsistent for cosmwasm-std (1.0 vs
1.2) and was hidden due to ignoring the lock file
2023-03-16 12:28:35 +00:00
pierre dd13073037 docs(connect-android): add build note 2023-03-15 12:29:23 +01:00
Pierre Dommerc 1010df1077 refactor(wallet): ui adjustments (#3182) 2023-03-15 12:22:00 +01:00
Bogdan-Ștefan Neacşu 9eaf9cf491 Feature/fix resharing (#3139)
* Compare verified vks against current group instead of initial dealers

* Fix various dkg logs

* API auto-advance epoch even on corrupt states

* Use verified vks as ultimate truth for dealers

* Set initial dealers based of verified vk

* Extend register period even more

* Fix test

* Use shares from current epoch

* Save initial dealers only when triggering resharing

* Fix tests

* Backup the last InProgress state too

* Reset previous signers that are not initial dealers

* Add unit test for bug reproduction

* More verbose debug logging

* Handle edge case for coconut keypair removal

* Update dkg api test

* Remove dealings directly for each key

* Replacement data is saved only on the first reshare start

* More debug logging

* On failed DKG, just reset

* Clippy fix
2023-03-15 11:16:43 +00:00
pierre 8e96318478 build(connect-android): try to fix fdroid build 2023-03-15 10:25:53 +01:00
benedettadavico 6e27497f14 sdk tests 2023-03-14 18:41:08 +01:00
Jędrzej Stuczyński 54287666e8 chore: simplify mnemonic zeroize story (#3165)
* updated bip39 dependency to simplify our zeroize story

* Replaced UserPassword wrapper with Zeroizing type alias

* fixed wallet-types cosmwasm-std dependency version
2023-03-13 11:29:56 +00:00
Tommy Verrall 6de829163d Merge pull request #3173 from nymtech/feature/nym-cli-tweak
Feature/nym cli tweak
2023-03-13 13:24:13 +02:00
benedettadavico adb5ed7c30 typo fix 2023-03-13 12:14:14 +01:00
benedettadavico 2b019e57df merge develop 2023-03-13 12:12:00 +01:00
benedettadavico 30c07712e3 format 2023-03-13 12:03:38 +01:00
benedettadavico 82c92501d9 vesting stuff 2023-03-13 12:01:22 +01:00
benedettadavico c2a871a1a7 typo 2023-03-10 17:10:27 +01:00
benedettadavico dfd7bd5889 adding pledge more 2023-03-10 17:10:01 +01:00
98 changed files with 1854 additions and 1142 deletions
Generated
+31 -30
View File
@@ -302,22 +302,23 @@ dependencies = [
[[package]]
name = "bip39"
version = "1.0.1"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e89470017230c38e52b82b3ee3f530db1856ba1d434e3a67a3456a8a8dec5f"
checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f"
dependencies = [
"bitcoin_hashes",
"rand 0.6.5",
"rand_core 0.4.2",
"rand 0.8.5",
"rand_core 0.6.4",
"serde",
"unicode-normalization",
"zeroize",
]
[[package]]
name = "bitcoin_hashes"
version = "0.9.7"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ce18265ec2324ad075345d5814fbeed4f41f0a660055dc78840b74d19b874b1"
checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4"
[[package]]
name = "bitflags"
@@ -2845,12 +2846,6 @@ dependencies = [
"serde",
]
[[package]]
name = "maybe-uninit"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
name = "memchr"
version = "2.5.0"
@@ -4171,7 +4166,7 @@ dependencies = [
"instant",
"libc",
"redox_syscall",
"smallvec 1.10.0",
"smallvec",
"winapi",
]
@@ -4184,7 +4179,7 @@ dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec 1.10.0",
"smallvec",
"windows-sys 0.45.0",
]
@@ -5057,7 +5052,7 @@ dependencies = [
"pin-project-lite",
"ref-cast",
"serde",
"smallvec 1.10.0",
"smallvec",
"stable-pattern",
"state",
"time 0.3.17",
@@ -5552,15 +5547,6 @@ dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "smallvec"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0"
dependencies = [
"maybe-uninit",
]
[[package]]
name = "smallvec"
version = "1.10.0"
@@ -5725,7 +5711,7 @@ dependencies = [
"percent-encoding",
"rustls 0.19.1",
"sha2 0.10.6",
"smallvec 1.10.0",
"smallvec",
"sqlformat 0.1.8",
"sqlx-rt 0.5.13",
"stringprep",
@@ -5773,7 +5759,7 @@ dependencies = [
"rustls 0.20.8",
"rustls-pemfile",
"sha2 0.10.6",
"smallvec 1.10.0",
"smallvec",
"sqlformat 0.2.1",
"sqlx-rt 0.6.2",
"stringprep",
@@ -6198,6 +6184,21 @@ dependencies = [
"serde_json",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.25.0"
@@ -6495,7 +6496,7 @@ dependencies = [
"once_cell",
"regex",
"sharded-slab",
"smallvec 1.10.0",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
@@ -6661,11 +6662,11 @@ checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "unicode-normalization"
version = "0.1.9"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09c8070a9942f5e7cfccd93f490fdebd230ee3c3c9f107cb25bad5351ef671cf"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"smallvec 0.6.14",
"tinyvec",
]
[[package]]
+1
View File
@@ -105,6 +105,7 @@ license = "Apache-2.0"
[workspace.dependencies]
async-trait = "0.1.64"
bip39 = { version = "2.0.0", features = ["zeroize"] }
cfg-if = "1.0.0"
dotenvy = "0.15.6"
lazy_static = "1.4.0"
+1 -1
View File
@@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
bip39 = "1.0.1"
bip39 = { workspace = true }
clap = { version = "4.0", features = ["cargo", "derive"] }
log = "0.4"
rand = "0.7.3"
-3
View File
@@ -1,3 +0,0 @@
/target
**/*.rs.bk
Cargo.lock
View File
@@ -37,7 +37,7 @@ nym-execute = { path = "../../execute" }
# at some point it might be possible to make it wasm-compatible
# perhaps after https://github.com/cosmos/cosmos-rust/pull/97 is resolved (and tendermint-rs is updated)
async-trait = { workspace = true, optional = true }
bip39 = { version = "1", features = ["rand"], optional = true }
bip39 = { workspace = true, features = ["rand"], optional = true }
nym-config = { path = "../../config", optional = true }
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true}
cw3 = { version = "0.13.4", optional = true }
@@ -47,7 +47,7 @@ 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", optional = true }
zeroize = { version = "1.5.7", optional = true }
zeroize = { version = "1.5.7", optional = true, features = ["zeroize_derive"] }
[dev-dependencies]
ts-rs = "6.1.2"
@@ -9,6 +9,7 @@ use crate::nyxd::{Fee, NyxdClient, SigningCosmWasmClient};
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_contracts_common::signing::MessageSignature;
use nym_mixnet_contract_common::gateway::GatewayConfigUpdate;
use nym_mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use nym_mixnet_contract_common::reward_params::{IntervalRewardingParamsUpdate, Performance};
use nym_mixnet_contract_common::{
@@ -498,6 +499,36 @@ pub trait MixnetSigningClient {
.await
}
async fn update_gateway_config(
&self,
new_config: GatewayConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateGatewayConfig { new_config },
vec![],
)
.await
}
async fn update_gateway_config_on_behalf(
&self,
owner: AccountId,
new_config: GatewayConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateGatewayConfigOnBehalf {
new_config,
owner: owner.to_string(),
},
vec![],
)
.await
}
// delegation-related:
async fn delegate_to_mixnode(
@@ -7,6 +7,7 @@ use crate::nyxd::error::NyxdError;
use crate::nyxd::{Coin, Fee, NyxdClient};
use async_trait::async_trait;
use nym_contracts_common::signing::MessageSignature;
use nym_mixnet_contract_common::gateway::GatewayConfigUpdate;
use nym_mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use nym_mixnet_contract_common::{Gateway, MixId, MixNode};
use nym_vesting_contract_common::messages::{
@@ -35,6 +36,12 @@ pub trait VestingSigningClient {
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError>;
async fn vesting_update_gateway_config(
&self,
new_config: GatewayConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError>;
async fn update_mixnet_address(
&self,
address: &str,
@@ -185,6 +192,19 @@ impl<C: SigningCosmWasmClient + Sync + Send + Clone> VestingSigningClient for Ny
.await
}
async fn vesting_update_gateway_config(
&self,
new_config: GatewayConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_vesting_contract(
fee,
VestingExecuteMsg::UpdateGatewayConfig { new_config },
vec![],
)
.await
}
async fn update_mixnet_address(
&self,
address: &str,
@@ -8,7 +8,7 @@ use cosmrs::crypto::PublicKey;
use cosmrs::tx::SignDoc;
use cosmrs::{tx, AccountId};
use nym_config::defaults;
use zeroize::Zeroize;
use zeroize::{Zeroize, ZeroizeOnDrop};
/// Derivation information required to derive a keypair and an address from a mnemonic.
#[derive(Debug, Clone)]
@@ -41,7 +41,7 @@ impl AccountData {
type Secp256k1Keypair = (SigningKey, PublicKey);
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Zeroize, ZeroizeOnDrop)]
pub struct DirectSecp256k1HdWallet {
/// Base secret
secret: bip39::Mnemonic,
@@ -54,30 +54,10 @@ pub struct DirectSecp256k1HdWallet {
// that would include the secret key which is a dyn EcdsaSigner and hence not Sync making the wallet
// not Sync and if used on the signing client in an async trait, it wouldn't be Send
/// Derivation instructions
#[zeroize(skip)]
accounts: Vec<Secp256k1Derivation>,
}
impl Zeroize for DirectSecp256k1HdWallet {
fn zeroize(&mut self) {
// in ideal world, Mnemonic would have had zeroize defined on it (there's an almost year old PR that introduces it)
// and the memory would have been filled with zeroes.
//
// we really don't want to keep our real mnemonic in memory, so let's do the semi-nasty thing
// of overwriting it with a fresh mnemonic that was never used before
//
// note: this function can only fail on an invalid word count, which clearly is not the case here
self.secret = bip39::Mnemonic::generate(self.secret.word_count()).unwrap();
self.seed.zeroize();
// there's nothing secret about derivation paths
}
}
impl Drop for DirectSecp256k1HdWallet {
fn drop(&mut self) {
self.zeroize()
}
}
impl DirectSecp256k1HdWallet {
pub fn builder(prefix: &str) -> DirectSecp256k1HdWalletBuilder {
DirectSecp256k1HdWalletBuilder::new(prefix)
+1 -1
View File
@@ -6,7 +6,7 @@ edition = "2021"
[dependencies]
base64 = "0.13.0"
bip39 = "1.0.1"
bip39 = { workspace = true }
bs58 = "0.4"
comfy-table = "6.0.0"
cfg-if = "1.0.0"
@@ -6,9 +6,11 @@ use clap::{Args, Subcommand};
pub mod rewards;
pub mod delegate_to_mixnode;
pub mod pledge_more;
pub mod query_for_delegations;
pub mod undelegate_from_mixnode;
pub mod vesting_delegate_to_mixnode;
pub mod vesting_pledge_more;
pub mod vesting_undelegate_from_mixnode;
#[derive(Debug, Args)]
@@ -32,4 +34,8 @@ pub enum MixnetDelegatorsCommands {
DelegateVesting(vesting_delegate_to_mixnode::Args),
/// Undelegate from a mixnode (when originally using locked tokens)
UndelegateVesting(vesting_undelegate_from_mixnode::Args),
/// Pledge more
PledgeMore(pledge_more::Args),
/// Pledge more with locked tokens
PledgeMoreVesting(vesting_pledge_more::Args),
}
@@ -0,0 +1,29 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::Coin;
use validator_client::nyxd::traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub amount: u128,
}
pub async fn pledge_more(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
info!("Starting to pledge more");
let coin = Coin::new(args.amount, denom);
let res = client
.pledge_more(coin.into(), None)
.await
.expect("failed to pledge more!");
info!("pledging more: {:?}", res);
}
@@ -0,0 +1,30 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::Coin;
use validator_client::nyxd::VestingSigningClient;
use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub amount: u128,
}
pub async fn vesting_pledge_more(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
info!("Starting vesting pledge more");
let coin = Coin::new(args.amount, denom);
let res = client
.vesting_pledge_more(coin.into(), None)
.await
.expect("failed to pledge more!");
info!("vesting pledge more: {:?}", res);
}
@@ -5,6 +5,7 @@ use clap::{Args, Subcommand};
pub mod bond_gateway;
pub mod gateway_bonding_sign_payload;
pub mod settings;
pub mod unbond_gateway;
pub mod vesting_bond_gateway;
pub mod vesting_unbond_gateway;
@@ -18,14 +19,16 @@ pub struct MixnetOperatorsGateway {
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsGatewayCommands {
/// Manage your gateway settings stored in the directory
Settings(settings::MixnetOperatorsGatewaySettings),
/// Bond to a gateway
Bond(bond_gateway::Args),
/// Unbound from a gateway
Unbound(unbond_gateway::Args),
/// Unbond from a gateway
Unbond(unbond_gateway::Args),
/// Bond to a gateway with locked tokens
VestingBond(vesting_bond_gateway::Args),
/// Unbound from a gateway (when originally using locked tokens)
VestingUnbound(vesting_unbond_gateway::Args),
/// Unbond from a gateway (when originally using locked tokens)
VestingUnbond(vesting_unbond_gateway::Args),
/// Create base58-encoded payload required for producing valid bonding signature.
CreateGatewayBondingSignPayload(gateway_bonding_sign_payload::Args),
}
@@ -0,0 +1,22 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::{Args, Subcommand};
pub mod update_config;
pub mod vesting_update_config;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
pub struct MixnetOperatorsGatewaySettings {
#[clap(subcommand)]
pub command: MixnetOperatorsGatewaySettingsCommands,
}
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsGatewaySettingsCommands {
/// Update gateway configuration
UpdateConfig(update_config::Args),
/// Update gateway configuration for a gateway bonded with locked tokens
VestingUpdateConfig(vesting_update_config::Args),
}
@@ -0,0 +1,60 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::GatewayConfigUpdate;
use validator_client::nyxd::traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub host: Option<String>,
#[clap(long)]
pub mix_port: Option<u16>,
#[clap(long)]
pub clients_port: Option<u16>,
#[clap(long)]
pub location: Option<String>,
#[clap(long)]
pub version: Option<String>,
}
pub async fn update_config(args: Args, client: SigningClient) {
info!("Update gateway config!");
let current_details = match client
.get_owned_gateway(client.address())
.await
.expect("failed to query the chain for gateway details")
.gateway
{
Some(details) => details,
None => {
log::warn!("this operator does not own a gateway to update");
return;
}
};
let update = GatewayConfigUpdate {
host: args.host.unwrap_or(current_details.gateway.host),
mix_port: args.mix_port.unwrap_or(current_details.gateway.mix_port),
clients_port: args
.clients_port
.unwrap_or(current_details.gateway.clients_port),
location: args.location.unwrap_or(current_details.gateway.location),
version: args.version.unwrap_or(current_details.gateway.version),
};
let res = client
.update_gateway_config(update, None)
.await
.expect("updating gateway config");
info!("gateway config updated: {:?}", res)
}
@@ -0,0 +1,61 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::GatewayConfigUpdate;
use validator_client::nyxd::traits::MixnetQueryClient;
use validator_client::nyxd::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub host: Option<String>,
#[clap(long)]
pub mix_port: Option<u16>,
#[clap(long)]
pub clients_port: Option<u16>,
#[clap(long)]
pub location: Option<String>,
#[clap(long)]
pub version: Option<String>,
}
pub async fn vesting_update_config(client: SigningClient, args: Args) {
info!("Update vesting gateway config!");
let current_details = match client
.get_owned_gateway(client.address())
.await
.expect("failed to query the chain for gateway details")
.gateway
{
Some(details) => details,
None => {
log::warn!("this operator does not own a gateway to update");
return;
}
};
let update = GatewayConfigUpdate {
host: args.host.unwrap_or(current_details.gateway.host),
mix_port: args.mix_port.unwrap_or(current_details.gateway.mix_port),
clients_port: args
.clients_port
.unwrap_or(current_details.gateway.clients_port),
location: args.location.unwrap_or(current_details.gateway.location),
version: args.version.unwrap_or(current_details.gateway.version),
};
let res = client
.vesting_update_gateway_config(update, None)
.await
.expect("updating vesting gateway config");
info!("gateway config updated: {:?}", res)
}
@@ -29,12 +29,12 @@ pub enum MixnetOperatorsMixnodeCommands {
Settings(settings::MixnetOperatorsMixnodeSettings),
/// Bond to a mixnode
Bond(bond_mixnode::Args),
/// Unbound from a mixnode
Unbound(unbond_mixnode::Args),
/// Unbond from a mixnode
Unbond(unbond_mixnode::Args),
/// Bond to a mixnode with locked tokens
BondVesting(vesting_bond_mixnode::Args),
/// Unbound from a mixnode (when originally using locked tokens)
UnboundVesting(vesting_unbond_mixnode::Args),
/// Unbond from a mixnode (when originally using locked tokens)
UnbondVesting(vesting_unbond_mixnode::Args),
/// Create base58-encoded payload required for producing valid bonding signature.
CreateMixnodeBondingSignPayload(mixnode_bonding_sign_payload::Args),
}
@@ -21,7 +21,7 @@ pub const TOTAL_DEALINGS: usize = 2 + 2 + 1;
#[derive(Serialize, Deserialize, Default, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
pub struct InitialReplacementData {
pub initial_dealers: Vec<Addr>,
pub initial_height: Option<u64>,
pub initial_height: u64,
}
#[derive(
@@ -1,6 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::gateway::GatewayConfigUpdate;
use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use crate::reward_params::{IntervalRewardParams, IntervalRewardingParamsUpdate};
use crate::rewarding::RewardDistribution;
@@ -42,6 +43,7 @@ pub enum MixnetEventType {
ReconcilePendingEvents,
PendingIntervalConfigUpdate,
IntervalConfigUpdate,
GatewayConfigUpdate,
}
impl From<MixnetEventType> for String {
@@ -86,6 +88,7 @@ impl ToString for MixnetEventType {
MixnetEventType::PendingIntervalConfigUpdate => "pending_interval_config_update",
MixnetEventType::IntervalConfigUpdate => "interval_config_update",
MixnetEventType::DelegationOnUnbonding => "delegation_on_unbonding_node",
MixnetEventType::GatewayConfigUpdate => "gateway_config_update",
};
format!("{EVENT_VERSION_PREFIX}{event_name}")
@@ -122,6 +125,7 @@ pub const OLD_REWARDING_VALIDATOR_ADDRESS_KEY: &str = "old_rewarding_validator_a
pub const NEW_REWARDING_VALIDATOR_ADDRESS_KEY: &str = "new_rewarding_validator_address";
pub const UPDATED_MIXNODE_CONFIG_KEY: &str = "updated_mixnode_config";
pub const UPDATED_GATEWAY_CONFIG_KEY: &str = "updated_gateway_config";
pub const UPDATED_MIXNODE_COST_PARAMS_KEY: &str = "updated_mixnode_cost_params";
// rewarding
@@ -382,6 +386,17 @@ pub fn new_mixnode_config_update_event(
.add_attribute(UPDATED_MIXNODE_CONFIG_KEY, update.to_inline_json())
}
pub fn new_gateway_config_update_event(
owner: &Addr,
proxy: &Option<Addr>,
update: &GatewayConfigUpdate,
) -> Event {
Event::new(MixnetEventType::GatewayConfigUpdate)
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(UPDATED_GATEWAY_CONFIG_KEY, update.to_inline_json())
}
pub fn new_mixnode_pending_cost_params_update_event(
mix_id: MixId,
owner: &Addr,
@@ -112,6 +112,26 @@ impl Display for GatewayBond {
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/GatewayConfigUpdate.ts")
)]
#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize, JsonSchema)]
pub struct GatewayConfigUpdate {
pub host: String,
pub mix_port: u16,
pub clients_port: u16,
pub location: String,
pub version: String,
}
impl GatewayConfigUpdate {
pub fn to_inline_json(&self) -> String {
serde_json::to_string(self).unwrap_or_else(|_| "serialisation failure".into())
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct PagedGatewayResponse {
pub nodes: Vec<GatewayBond>,
@@ -27,7 +27,8 @@ pub use delegation::{
PagedMixNodeDelegationsResponse,
};
pub use gateway::{
Gateway, GatewayBond, GatewayBondResponse, GatewayOwnershipResponse, PagedGatewayResponse,
Gateway, GatewayBond, GatewayBondResponse, GatewayConfigUpdate, GatewayOwnershipResponse,
PagedGatewayResponse,
};
pub use interval::{
CurrentIntervalResponse, EpochState, EpochStatus, Interval, NumberOfPendingEventsResponse,
@@ -3,6 +3,7 @@
use crate::delegation::OwnerProxySubKey;
use crate::error::MixnetContractError;
use crate::gateway::GatewayConfigUpdate;
use crate::helpers::IntoBaseDecimal;
use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use crate::reward_params::{
@@ -199,6 +200,13 @@ pub enum ExecuteMsg {
UnbondGatewayOnBehalf {
owner: String,
},
UpdateGatewayConfig {
new_config: GatewayConfigUpdate,
},
UpdateGatewayConfigOnBehalf {
new_config: GatewayConfigUpdate,
owner: String,
},
// delegation-related:
DelegateToMixnode {
@@ -313,6 +321,10 @@ impl ExecuteMsg {
}
ExecuteMsg::UnbondGateway { .. } => "unbonding gateway".into(),
ExecuteMsg::UnbondGatewayOnBehalf { .. } => "unbonding gateway on behalf".into(),
ExecuteMsg::UpdateGatewayConfig { .. } => "updating gateway configuration".into(),
ExecuteMsg::UpdateGatewayConfigOnBehalf { .. } => {
"updating gateway configuration on behalf".into()
}
ExecuteMsg::DelegateToMixnode { mix_id } => format!("delegating to mixnode {mix_id}"),
ExecuteMsg::DelegateToMixnodeOnBehalf { mix_id, .. } => {
format!("delegating to mixnode {mix_id} on behalf")
@@ -17,6 +17,7 @@ pub const VESTING_MIXNODE_BONDING_EVENT_TYPE: &str = "vesting_mixnode_bonding";
pub const VESTING_PLEDGE_MORE_EVENT_TYPE: &str = "vesting_pledge_more";
pub const VESTING_MIXNODE_UNBONDING_EVENT_TYPE: &str = "vesting_mixnode_unbonding";
pub const VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE: &str = "vesting_update_mixnode_config";
pub const VESTING_UPDATE_GATEWAY_CONFIG_EVENT_TYPE: &str = "vesting_update_gateway_config";
pub const VESTING_UPDATE_MIXNODE_COST_PARAMS_EVENT_TYPE: &str =
"vesting_update_mixnode_cost_params";
@@ -121,6 +122,10 @@ pub fn new_vesting_update_mixnode_config_event() -> Event {
Event::new(VESTING_UPDATE_MIXNODE_CONFIG_EVENT_TYPE)
}
pub fn new_vesting_update_gateway_config_event() -> Event {
Event::new(VESTING_UPDATE_GATEWAY_CONFIG_EVENT_TYPE)
}
pub fn new_vesting_update_mixnode_cost_params_event() -> Event {
Event::new(VESTING_UPDATE_MIXNODE_COST_PARAMS_EVENT_TYPE)
}
@@ -1,6 +1,7 @@
use contracts_common::signing::MessageSignature;
use cosmwasm_std::{Coin, Timestamp};
use mixnet_contract_common::{
gateway::GatewayConfigUpdate,
mixnode::{MixNodeConfigUpdate, MixNodeCostParams},
Gateway, IdentityKey, MixId, MixNode,
};
@@ -140,6 +141,9 @@ pub enum ExecuteMsg {
owner: String,
amount: Coin,
},
UpdateGatewayConfig {
new_config: GatewayConfigUpdate,
},
TransferOwnership {
to_address: String,
},
@@ -179,6 +183,7 @@ impl ExecuteMsg {
ExecuteMsg::BondGateway { .. } => "VestingExecuteMsg::BondGateway",
ExecuteMsg::UnbondGateway { .. } => "VestingExecuteMsg::UnbondGateway",
ExecuteMsg::TrackUnbondGateway { .. } => "VestingExecuteMsg::TrackUnbondGateway",
ExecuteMsg::UpdateGatewayConfig { .. } => "VestingExecuteMsg::UpdateGatewayConfig",
ExecuteMsg::TransferOwnership { .. } => "VestingExecuteMsg::TransferOwnership",
ExecuteMsg::UpdateStakingAddress { .. } => "VestingExecuteMsg::UpdateStakingAddress",
ExecuteMsg::UpdateLockedPledgeCap { .. } => "VestingExecuteMsg::UpdateLockedPledgeCap",
-1
View File
@@ -1 +0,0 @@
Cargo.lock
@@ -22,7 +22,7 @@ fn verify_dealer(deps: DepsMut<'_>, dealer: &Addr, resharing: bool) -> Result<()
let state = STATE.load(deps.storage)?;
let height = if resharing {
INITIAL_REPLACEMENT_DATA.load(deps.storage)?.initial_height
Some(INITIAL_REPLACEMENT_DATA.load(deps.storage)?.initial_height)
} else {
None
};
@@ -105,7 +105,7 @@ pub(crate) mod tests {
deps.as_mut().storage,
&InitialReplacementData {
initial_dealers: vec![details1.address, details2.address, details3.address],
initial_height: Some(1),
initial_height: 1,
},
)
.unwrap();
@@ -126,7 +126,7 @@ pub(crate) mod tests {
INITIAL_REPLACEMENT_DATA
.update::<_, ContractError>(deps.as_mut().storage, |mut data| {
data.initial_height = Some(2);
data.initial_height = 2;
Ok(data)
})
.unwrap();
@@ -110,7 +110,7 @@ pub(crate) mod tests {
deps.as_mut().storage,
&InitialReplacementData {
initial_dealers: vec![],
initial_height: None,
initial_height: 1,
},
)
.unwrap();
@@ -7,6 +7,7 @@ use crate::epoch_state::storage::{CURRENT_EPOCH, INITIAL_REPLACEMENT_DATA, THRES
use crate::epoch_state::utils::check_epoch_state;
use crate::error::ContractError;
use crate::state::STATE;
use crate::verification_key_shares::storage::verified_dealers;
use cosmwasm_std::{Addr, Deps, DepsMut, Env, Order, Response, Storage};
use nym_coconut_dkg_common::types::{Epoch, EpochState, InitialReplacementData};
@@ -19,7 +20,13 @@ fn reset_epoch_state(storage: &mut dyn Storage) -> Result<(), ContractError> {
for dealer_addr in dealers {
let details = current_dealers().load(storage, &dealer_addr)?;
for dealings in DEALINGS_BYTES {
dealings.remove(storage, &details.address);
let dealing_keys: Vec<_> = dealings
.keys(storage, None, None, Order::Ascending)
.flatten()
.collect();
for key in dealing_keys {
dealings.remove(storage, &key);
}
}
current_dealers().remove(storage, &dealer_addr)?;
past_dealers().save(storage, &dealer_addr, &details)?;
@@ -46,15 +53,9 @@ fn dealers_still_active(
}
fn dealers_eq_members(deps: &DepsMut<'_>) -> Result<bool, ContractError> {
let dealers_still_active = dealers_still_active(
&deps.as_ref(),
current_dealers()
.keys(deps.storage, None, None, Order::Ascending)
.flatten(),
)?;
let all_dealers = current_dealers()
.keys(deps.storage, None, None, Order::Ascending)
.count();
let verified_dealers = verified_dealers(deps.storage)?;
let all_dealers = verified_dealers.len();
let dealers_still_active = dealers_still_active(&deps.as_ref(), verified_dealers.into_iter())?;
let group_members = STATE
.load(deps.storage)?
.group_addr
@@ -66,7 +67,11 @@ fn dealers_eq_members(deps: &DepsMut<'_>) -> Result<bool, ContractError> {
fn replacement_threshold_surpassed(deps: &DepsMut<'_>) -> Result<bool, ContractError> {
let threshold = THRESHOLD.load(deps.storage)? as usize;
let initial_dealers = INITIAL_REPLACEMENT_DATA.load(deps.storage)?.initial_dealers;
let initial_dealers = verified_dealers(deps.storage)?;
if initial_dealers.is_empty() {
// possibly failed DKG, just reset and start again
return Ok(true);
}
let initial_dealer_count = initial_dealers.len();
let replacement_threshold = threshold - (initial_dealers.len() + 2 - 1) / 2 + 1;
let removed_dealer_count =
@@ -90,24 +95,23 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
let next_epoch = if let Some(state) = current_epoch.state.next() {
// We are during DKG process
let mut new_state = state;
if let EpochState::DealingExchange { resharing } = state {
if let EpochState::DealingExchange { .. } = state {
let current_dealers = current_dealers()
.keys(deps.storage, None, None, Order::Ascending)
.collect::<Result<Vec<Addr>, _>>()?;
if current_dealers.is_empty() {
// If no dealer registered yet, we just stay in the same state until there's at least one
let group_members =
STATE
.load(deps.storage)?
.group_addr
.list_members(&deps.querier, None, None)?;
if current_dealers.len() < group_members.len() {
// If not all group members registered yet, we just stay in the same state until
// they either register or they get kicked out of the group
new_state = current_epoch.state;
} else {
// note: ceiling in integer division can be achieved via q = (x + y - 1) / y;
let threshold = (2 * current_dealers.len() as u64 + 3 - 1) / 3;
THRESHOLD.save(deps.storage, &threshold)?;
if !resharing {
let replacement_data = InitialReplacementData {
initial_dealers: current_dealers,
initial_height: None,
};
INITIAL_REPLACEMENT_DATA.save(deps.storage, &replacement_data)?;
}
}
};
Epoch::new(
@@ -129,13 +133,23 @@ pub(crate) fn advance_epoch_state(deps: DepsMut<'_>, env: Env) -> Result<Respons
// Dealer set changed, we need to redo DKG...
let state = if replacement_threshold_surpassed(&deps)? {
// ... in reset mode
INITIAL_REPLACEMENT_DATA.remove(deps.storage);
EpochState::default()
} else {
// ... in reshare mode
INITIAL_REPLACEMENT_DATA.update::<_, ContractError>(deps.storage, |mut data| {
data.initial_height = Some(env.block.height);
Ok(data)
})?;
if INITIAL_REPLACEMENT_DATA.may_load(deps.storage)?.is_some() {
INITIAL_REPLACEMENT_DATA.update::<_, ContractError>(deps.storage, |mut data| {
data.initial_height = env.block.height;
Ok(data)
})?;
} else {
let replacement_data = InitialReplacementData {
initial_dealers: verified_dealers(deps.storage)?,
initial_height: env.block.height,
};
INITIAL_REPLACEMENT_DATA.save(deps.storage, &replacement_data)?;
}
EpochState::PublicKeySubmission { resharing: true }
};
reset_epoch_state(deps.storage)?;
@@ -158,10 +172,8 @@ pub(crate) fn try_surpassed_threshold(
check_epoch_state(deps.storage, EpochState::InProgress)?;
let threshold = THRESHOLD.load(deps.storage)?;
let dealers = current_dealers()
.keys(deps.storage, None, None, Order::Ascending)
.flatten();
if dealers_still_active(&deps.as_ref(), dealers)? < threshold as usize {
let dealers = verified_dealers(deps.storage)?;
if dealers_still_active(&deps.as_ref(), dealers.into_iter())? < threshold as usize {
reset_epoch_state(deps.storage)?;
CURRENT_EPOCH.update::<_, ContractError>(deps.storage, |epoch| {
Ok(Epoch::new(
@@ -180,8 +192,9 @@ pub(crate) fn try_surpassed_threshold(
pub(crate) mod tests {
use super::*;
use crate::error::ContractError::EarlyEpochStateAdvancement;
use crate::support::tests::fixtures::dealer_details_fixture;
use crate::support::tests::fixtures::{dealer_details_fixture, vk_share_fixture};
use crate::support::tests::helpers::{init_contract, GROUP_MEMBERS};
use crate::verification_key_shares::storage::vk_shares;
use cosmwasm_std::testing::mock_env;
use cosmwasm_std::Addr;
use cw4::Member;
@@ -204,11 +217,15 @@ pub(crate) mod tests {
for n in [10, 25, 50, 100] {
let dealers: Vec<_> = (0..n).map(dealer_details_fixture).collect();
let shares: Vec<_> = (0..n).map(|idx| vk_share_fixture(&format!("owner{}", idx), 0)).collect();
let initial_dealers = dealers.iter().map(|d| d.address.clone()).collect();
let data = InitialReplacementData {
initial_dealers,
initial_height: None,
initial_height: 1,
};
for share in shares {
vk_shares().save(deps.as_mut().storage, (&share.owner, 0), &share).unwrap();
}
for f in [two_thirds, three_fourths, ninty_pc] {
let threshold = f(n);
THRESHOLD.save(deps.as_mut().storage, &threshold).unwrap();
@@ -247,39 +264,39 @@ pub(crate) mod tests {
assert!(dealers_eq_members(&deps.as_mut()).unwrap());
let details = dealer_details_fixture(1);
let different_details = dealer_details_fixture(2);
current_dealers()
.save(deps.as_mut().storage, &details.address, &details)
let share = vk_share_fixture("owner2", 0);
let different_share = vk_share_fixture("owner4", 0);
vk_shares()
.save(deps.as_mut().storage, (&share.owner, 0), &share)
.unwrap();
assert!(!dealers_eq_members(&deps.as_mut()).unwrap());
current_dealers()
.remove(deps.as_mut().storage, &details.address)
vk_shares()
.remove(deps.as_mut().storage, (&share.owner, 0))
.unwrap();
GROUP_MEMBERS.lock().unwrap().push((
Member {
addr: "owner1".to_string(),
addr: "owner2".to_string(),
weight: 10,
},
1,
));
assert!(!dealers_eq_members(&deps.as_mut()).unwrap());
current_dealers()
vk_shares()
.save(
deps.as_mut().storage,
&different_details.address,
&different_details,
(&different_share.owner, 0),
&different_share,
)
.unwrap();
assert!(!dealers_eq_members(&deps.as_mut()).unwrap());
current_dealers()
.remove(deps.as_mut().storage, &different_details.address)
vk_shares()
.remove(deps.as_mut().storage, (&different_share.owner, 0))
.unwrap();
current_dealers()
.save(deps.as_mut().storage, &details.address, &details)
vk_shares()
.save(deps.as_mut().storage, (&share.owner, 0), &share)
.unwrap();
assert!(dealers_eq_members(&deps.as_mut()).unwrap());
}
@@ -407,6 +424,12 @@ pub(crate) mod tests {
);
// setup dealer details
let all_shares: [_; 4] = std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0));
for share in all_shares.iter() {
vk_shares()
.save(deps.as_mut().storage, (&share.owner, 0), share)
.unwrap();
}
let all_details: [_; 4] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 1));
for details in all_details.iter() {
current_dealers()
@@ -431,12 +454,6 @@ pub(crate) mod tests {
.time
.plus_seconds(epoch.time_configuration.dealing_exchange_time_secs)
);
let replacement_data = INITIAL_REPLACEMENT_DATA.load(&deps.storage).unwrap();
let expected_replacement_data = InitialReplacementData {
initial_dealers: all_details.iter().map(|d| d.address.clone()).collect(),
initial_height: None,
};
assert_eq!(replacement_data, expected_replacement_data);
env.block.time = env
.block
@@ -588,8 +605,14 @@ pub(crate) mod tests {
);
assert_eq!(curr_epoch, expected_epoch);
assert!(THRESHOLD.may_load(&deps.storage).unwrap().is_none());
let replacement_data = INITIAL_REPLACEMENT_DATA.load(&deps.storage).unwrap();
let expected_replacement_data = InitialReplacementData {
initial_dealers: all_details.iter().map(|d| d.address.clone()).collect(),
initial_height: 12345,
};
assert_eq!(replacement_data, expected_replacement_data);
let all_details: [_; 2] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 2));
let all_details: [_; 4] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 2));
for details in all_details.iter() {
past_dealers().remove(deps.as_mut().storage, &details.address).unwrap();
current_dealers()
@@ -607,6 +630,17 @@ pub(crate) mod tests {
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
}
let all_shares: [_; 4] = std::array::from_fn(|i| {
let mut share = vk_share_fixture(&format!("owner{}", i + 1), 1);
share.verified = i % 2 == 0;
share
});
for share in all_shares.iter() {
vk_shares()
.save(deps.as_mut().storage, (&share.owner, 0), share)
.unwrap();
}
// Group changed even more, surpassing threshold, so re-run dkg in reset mode
*GROUP_MEMBERS.lock().unwrap().last_mut().unwrap() = (
Member {
@@ -623,7 +657,7 @@ pub(crate) mod tests {
advance_epoch_state(deps.as_mut(), env.clone()).unwrap();
let curr_epoch = CURRENT_EPOCH.load(deps.as_mut().storage).unwrap();
let expected_epoch = Epoch::new(
EpochState::PublicKeySubmission { resharing: false },
EpochState::PublicKeySubmission { resharing: true },
prev_epoch.epoch_id + 1,
prev_epoch.time_configuration,
env.block.time,
@@ -672,12 +706,25 @@ pub(crate) mod tests {
}
);
let all_shares: [_; 3] = std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0));
for share in all_shares.iter() {
vk_shares()
.save(deps.as_mut().storage, (&share.owner, 0), share)
.unwrap();
}
let all_details: [_; 3] = std::array::from_fn(|i| dealer_details_fixture(i as u64 + 1));
for details in all_details.iter() {
current_dealers()
.save(deps.as_mut().storage, &details.address, details)
.unwrap();
}
let all_shares: [_; 3] = std::array::from_fn(|i| vk_share_fixture(&format!("owner{}", i + 1), 0));
for share in all_shares.iter() {
vk_shares()
.save(deps.as_mut().storage, (&share.owner, share.epoch_id), share)
.unwrap();
}
for times in [
time_configuration.public_key_submission_time_secs,
@@ -4,7 +4,9 @@
// SPDX-License-Identifier: Apache-2.0
use crate::constants::{VK_SHARES_EPOCH_ID_IDX_NAMESPACE, VK_SHARES_PK_NAMESPACE};
use cosmwasm_std::Addr;
use crate::epoch_state::storage::CURRENT_EPOCH;
use crate::error::ContractError;
use cosmwasm_std::{Addr, Order, Storage};
use cw_storage_plus::{Index, IndexList, IndexedMap, MultiIndex};
use nym_coconut_dkg_common::types::EpochId;
use nym_coconut_dkg_common::verification_key::ContractVKShare;
@@ -35,3 +37,21 @@ pub(crate) fn vk_shares<'a>() -> IndexedMap<'a, VKShareKey<'a>, ContractVKShare,
};
IndexedMap::new(VK_SHARES_PK_NAMESPACE, indexes)
}
pub(crate) fn verified_dealers(storage: &dyn Storage) -> Result<Vec<Addr>, ContractError> {
let epoch_id = CURRENT_EPOCH.load(storage)?.epoch_id;
Ok(vk_shares()
.idx
.epoch_id
.prefix(epoch_id)
.range(storage, None, None, Order::Ascending)
.flatten()
.filter_map(|(_, share)| {
if share.verified {
Some(share.owner)
} else {
None
}
})
.collect())
}
+8
View File
@@ -318,6 +318,14 @@ pub fn execute(
ExecuteMsg::UnbondGatewayOnBehalf { owner } => {
crate::gateways::transactions::try_remove_gateway_on_behalf(deps, info, owner)
}
ExecuteMsg::UpdateGatewayConfig { new_config } => {
crate::gateways::transactions::try_update_gateway_config(deps, info, new_config)
}
ExecuteMsg::UpdateGatewayConfigOnBehalf { new_config, owner } => {
crate::gateways::transactions::try_update_gateway_config_on_behalf(
deps, info, new_config, owner,
)
}
// delegation-related:
ExecuteMsg::DelegateToMixnode { mix_id } => {
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use cosmwasm_std::{Addr, Storage};
use mixnet_contract_common::{error::MixnetContractError, GatewayBond};
pub(crate) fn must_get_gateway_bond_by_owner(
store: &dyn Storage,
owner: &Addr,
) -> Result<GatewayBond, MixnetContractError> {
Ok(storage::gateways()
.idx
.owner
.item(store, owner.clone())?
.ok_or(MixnetContractError::NoAssociatedGatewayBond {
owner: owner.clone(),
})?
.1)
}
+1
View File
@@ -1,6 +1,7 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod helpers;
pub mod queries;
pub mod signature_helpers;
pub mod storage;
+148 -2
View File
@@ -1,16 +1,20 @@
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::helpers::must_get_gateway_bond_by_owner;
use super::storage;
use crate::gateways::signature_helpers::verify_gateway_bonding_signature;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::signing::storage as signing_storage;
use crate::support::helpers::{
ensure_no_existing_bond, ensure_sent_by_vesting_contract, validate_pledge,
ensure_no_existing_bond, ensure_proxy_match, ensure_sent_by_vesting_contract, validate_pledge,
};
use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::{new_gateway_bonding_event, new_gateway_unbonding_event};
use mixnet_contract_common::events::{
new_gateway_bonding_event, new_gateway_config_update_event, new_gateway_unbonding_event,
};
use mixnet_contract_common::gateway::GatewayConfigUpdate;
use mixnet_contract_common::{Gateway, GatewayBond};
use nym_contracts_common::signing::MessageSignature;
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
@@ -189,6 +193,56 @@ pub(crate) fn _try_remove_gateway(
)))
}
pub(crate) fn try_update_gateway_config(
deps: DepsMut<'_>,
info: MessageInfo,
new_config: GatewayConfigUpdate,
) -> Result<Response, MixnetContractError> {
let owner = info.sender;
_try_update_gateway_config(deps, new_config, owner, None)
}
pub(crate) fn try_update_gateway_config_on_behalf(
deps: DepsMut,
info: MessageInfo,
new_config: GatewayConfigUpdate,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let owner = deps.api.addr_validate(&owner)?;
let proxy = info.sender;
_try_update_gateway_config(deps, new_config, owner, Some(proxy))
}
pub(crate) fn _try_update_gateway_config(
deps: DepsMut,
new_config: GatewayConfigUpdate,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let existing_bond = must_get_gateway_bond_by_owner(deps.storage, &owner)?;
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
let cfg_update_event = new_gateway_config_update_event(&owner, &proxy, &new_config);
let mut updated_bond = existing_bond.clone();
updated_bond.gateway.host = new_config.host;
updated_bond.gateway.mix_port = new_config.mix_port;
updated_bond.gateway.clients_port = new_config.clients_port;
updated_bond.gateway.location = new_config.location;
updated_bond.gateway.version = new_config.version;
storage::gateways().replace(
deps.storage,
existing_bond.identity(),
Some(&updated_bond),
Some(&existing_bond),
)?;
Ok(Response::new().add_event(cfg_update_event))
}
#[cfg(test)]
pub mod tests {
use super::*;
@@ -196,6 +250,7 @@ pub mod tests {
use crate::gateways::queries;
use crate::gateways::transactions::{
try_add_gateway, try_add_gateway_on_behalf, try_remove_gateway_on_behalf,
try_update_gateway_config, try_update_gateway_config_on_behalf,
};
use crate::interval::pending_events;
use crate::mixnet_contract_settings::storage::minimum_gateway_pledge;
@@ -207,6 +262,7 @@ pub mod tests {
use cosmwasm_std::{Addr, BankMsg, Response, Uint128};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::new_gateway_unbonding_event;
use mixnet_contract_common::gateway::GatewayConfigUpdate;
use mixnet_contract_common::ExecuteMsg;
#[test]
@@ -485,4 +541,94 @@ pub mod tests {
}
)
}
#[test]
fn update_gateway_config() {
let mut test = TestSetup::new();
let owner = "alice";
let info = mock_info(owner, &[]);
let update = GatewayConfigUpdate {
host: "1.1.1.1:1234".to_string(),
mix_port: 1234,
clients_port: 1235,
location: "home".to_string(),
version: "v1.2.3".to_string(),
};
// try updating a non existing gateway bond
let res = try_update_gateway_config(test.deps_mut(), info.clone(), update.clone());
assert_eq!(
res,
Err(MixnetContractError::NoAssociatedGatewayBond {
owner: Addr::unchecked(owner)
})
);
test.add_dummy_gateway(owner, None);
let vesting_contract = test.vesting_contract();
// attempted to remove on behalf with invalid proxy (current is `None`)
let res = try_update_gateway_config_on_behalf(
test.deps_mut(),
mock_info(vesting_contract.as_ref(), &[]),
update.clone(),
owner.to_string(),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(),
incoming: vesting_contract.into_string()
})
);
// "normal" update succeeds
let res = try_update_gateway_config(test.deps_mut(), info, update.clone());
assert!(res.is_ok());
// and the config has actually been updated
let bond =
must_get_gateway_bond_by_owner(test.deps().storage, &Addr::unchecked(owner)).unwrap();
assert_eq!(bond.gateway.host, update.host);
assert_eq!(bond.gateway.mix_port, update.mix_port);
assert_eq!(bond.gateway.clients_port, update.clients_port);
assert_eq!(bond.gateway.location, update.location);
assert_eq!(bond.gateway.version, update.version);
}
#[test]
fn updating_gateway_config_with_illegal_proxy() {
let mut test = TestSetup::new();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
test.add_dummy_gateway_with_illegal_proxy(owner, None, illegal_proxy.clone());
let update = GatewayConfigUpdate {
host: "1.1.1.1:1234".to_string(),
mix_port: 1234,
clients_port: 1235,
location: "at home".to_string(),
version: "v1.2.3".to_string(),
};
let res = try_update_gateway_config_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[]),
update,
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
)
}
}
@@ -295,7 +295,7 @@ pub(crate) fn try_update_mixnode_config(
}
pub(crate) fn try_update_mixnode_config_on_behalf(
deps: DepsMut,
deps: DepsMut<'_>,
info: MessageInfo,
new_config: MixNodeConfigUpdate,
owner: String,
@@ -308,7 +308,7 @@ pub(crate) fn try_update_mixnode_config_on_behalf(
}
pub(crate) fn _try_update_mixnode_config(
deps: DepsMut,
deps: DepsMut<'_>,
new_config: MixNodeConfigUpdate,
owner: Addr,
proxy: Option<Addr>,
+1 -1
View File
@@ -20,7 +20,7 @@ pub mod test_helpers {
perform_pending_epoch_actions, perform_pending_interval_actions, try_begin_epoch_transition,
};
use crate::interval::{pending_events, storage as interval_storage};
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnet_contract_settings::storage::{self as mixnet_params_storage};
use crate::mixnet_contract_settings::storage::{
minimum_gateway_pledge, minimum_mixnode_pledge, rewarding_denom,
rewarding_validator_address,
+13
View File
@@ -14,6 +14,7 @@ use cosmwasm_std::{
QueryResponse, Response, StdError, StdResult, Timestamp, Uint128,
};
use cw_storage_plus::Bound;
use mixnet_contract_common::gateway::GatewayConfigUpdate;
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use mixnet_contract_common::{Gateway, MixId, MixNode};
use semver::Version;
@@ -219,6 +220,9 @@ pub fn execute(
ExecuteMsg::TrackUnbondGateway { owner, amount } => {
try_track_unbond_gateway(&owner, amount, info, deps)
}
ExecuteMsg::UpdateGatewayConfig { new_config } => {
try_update_gateway_config(new_config, info, deps)
}
ExecuteMsg::TransferOwnership { to_address } => {
try_transfer_ownership(to_address, info, deps)
}
@@ -300,6 +304,15 @@ pub fn try_update_mixnode_config(
account.try_update_mixnode_config(new_config, deps.storage)
}
pub fn try_update_gateway_config(
new_config: GatewayConfigUpdate,
info: MessageInfo,
deps: DepsMut,
) -> Result<Response, ContractError> {
let account = account_from_address(info.sender.as_str(), deps.storage, deps.api)?;
account.try_update_gateway_config(new_config, deps.storage)
}
pub fn try_update_mixnode_cost_params(
new_costs: MixNodeCostParams,
info: MessageInfo,
@@ -2,6 +2,7 @@ use crate::errors::ContractError;
use contracts_common::signing::MessageSignature;
use cosmwasm_std::{Coin, Env, Response, Storage};
use mixnet_contract_common::{
gateway::GatewayConfigUpdate,
mixnode::{MixNodeConfigUpdate, MixNodeCostParams},
Gateway, MixNode,
};
@@ -64,4 +65,10 @@ pub trait GatewayBondingAccount {
amount: Coin,
storage: &mut dyn Storage,
) -> Result<(), ContractError>;
fn try_update_gateway_config(
&self,
new_config: GatewayConfigUpdate,
storage: &mut dyn Storage,
) -> Result<Response, ContractError>;
}
@@ -4,9 +4,12 @@ use crate::storage::MIXNET_CONTRACT_ADDRESS;
use crate::traits::GatewayBondingAccount;
use contracts_common::signing::MessageSignature;
use cosmwasm_std::{wasm_execute, Coin, Env, Response, Storage, Uint128};
use mixnet_contract_common::{ExecuteMsg as MixnetExecuteMsg, Gateway};
use mixnet_contract_common::{
gateway::GatewayConfigUpdate, ExecuteMsg as MixnetExecuteMsg, Gateway,
};
use vesting_contract_common::events::{
new_vesting_gateway_bonding_event, new_vesting_gateway_unbonding_event,
new_vesting_update_gateway_config_event,
};
use super::Account;
@@ -78,4 +81,22 @@ impl GatewayBondingAccount for Account {
self.remove_gateway_pledge(storage)?;
Ok(())
}
fn try_update_gateway_config(
&self,
new_config: GatewayConfigUpdate,
storage: &mut dyn Storage,
) -> Result<Response, ContractError> {
let msg = MixnetExecuteMsg::UpdateGatewayConfigOnBehalf {
new_config,
owner: self.owner_address().into_string(),
};
let update_gateway_config_msg =
wasm_execute(MIXNET_CONTRACT_ADDRESS.load(storage)?, &msg, vec![])?;
Ok(Response::new()
.add_message(update_gateway_config_msg)
.add_event(new_vesting_update_gateway_config_event()))
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ rust-version = "1.56"
[dependencies]
anyhow = "1.0.53"
async-trait = { workspace = true }
bip39 = "1.0.1"
bip39 = { workspace = true }
bs58 = "0.4.0"
clap = { version = "4.0", features = ["cargo", "derive"] }
colored = "2.0"
+1 -1
View File
@@ -17,7 +17,7 @@ rust-version = "1.56"
[dependencies]
async-trait = { workspace = true }
bs58 = {version = "0.4.0" }
bip39 = "1"
bip39 = { workspace = true }
cfg-if = "1.0"
clap = { version = "4.0", features = ["cargo", "derive"] }
console-subscriber = { version = "0.1.1", optional = true } # validator-api needs to be built with RUSTFLAGS="--cfg tokio_unstable"
+68 -49
View File
@@ -84,6 +84,18 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
})
}
async fn dump_persistent_state(&self) {
if !self.state.coconut_keypair_is_some().await {
// Delete the files just in case the process is killed before the new keys are generated
std::fs::remove_file(&self.secret_key_path).ok();
std::fs::remove_file(&self.verification_key_path).ok();
}
let persistent_state = PersistentState::from(&self.state);
if let Err(err) = persistent_state.save_to_file(self.state.persistent_state_path()) {
warn!("Could not backup the state for this iteration: {err}");
}
}
pub(crate) async fn handle_epoch_state(&mut self) {
match self.dkg_client.get_current_epoch().await {
Err(err) => warn!("Could not get current epoch state {err}"),
@@ -99,57 +111,64 @@ impl<R: RngCore + CryptoRng + Clone> DkgController<R> {
return;
}
if let Err(err) = self.state.is_consistent(epoch.state).await {
error!("Epoch state is corrupted - {err}, the process should be terminated");
return;
}
let ret = match epoch.state {
EpochState::PublicKeySubmission { resharing } => {
public_key_submission(&self.dkg_client, &mut self.state, resharing).await
}
EpochState::DealingExchange { resharing } => {
dealing_exchange(
&self.dkg_client,
&mut self.state,
self.rng.clone(),
resharing,
)
.await
}
EpochState::VerificationKeySubmission { resharing } => {
let keypair_path = nym_pemstore::KeyPairPath::new(
self.secret_key_path.clone(),
self.verification_key_path.clone(),
);
verification_key_submission(
&self.dkg_client,
&mut self.state,
&keypair_path,
resharing,
)
.await
}
EpochState::VerificationKeyValidation { resharing } => {
verification_key_validation(&self.dkg_client, &mut self.state, resharing)
debug!("Epoch state is corrupted - {err}. Awaiting for a DKG restart.");
} else {
let ret = match epoch.state {
EpochState::PublicKeySubmission { resharing } => {
public_key_submission(&self.dkg_client, &mut self.state, resharing)
.await
}
EpochState::DealingExchange { resharing } => {
dealing_exchange(
&self.dkg_client,
&mut self.state,
self.rng.clone(),
resharing,
)
.await
}
EpochState::VerificationKeyFinalization { resharing } => {
verification_key_finalization(&self.dkg_client, &mut self.state, resharing)
}
EpochState::VerificationKeySubmission { resharing } => {
let keypair_path = nym_pemstore::KeyPairPath::new(
self.secret_key_path.clone(),
self.verification_key_path.clone(),
);
verification_key_submission(
&self.dkg_client,
&mut self.state,
&keypair_path,
resharing,
)
.await
}
// Just wait, in case we need to redo dkg at some point
EpochState::InProgress => {
self.state.set_was_in_progress();
Ok(())
}
};
if let Err(err) = ret {
warn!("Could not handle this iteration for the epoch state: {err}");
} else if epoch.state != EpochState::InProgress {
let persistent_state = PersistentState::from(&self.state);
if let Err(err) =
persistent_state.save_to_file(self.state.persistent_state_path())
{
warn!("Could not backup the state for this iteration: {err}");
}
EpochState::VerificationKeyValidation { resharing } => {
verification_key_validation(
&self.dkg_client,
&mut self.state,
resharing,
)
.await
}
EpochState::VerificationKeyFinalization { resharing } => {
verification_key_finalization(
&self.dkg_client,
&mut self.state,
resharing,
)
.await
}
// Just wait, in case we need to redo dkg at some point
EpochState::InProgress => {
self.state.set_was_in_progress();
// We're dumping state here so that we don't do it uselessly during the
// long InProgress state
self.dump_persistent_state().await;
Ok(())
}
};
if let Err(err) = ret {
warn!("Could not handle this iteration for the epoch state: {err}");
} else if epoch.state != EpochState::InProgress {
self.dump_persistent_state().await;
}
}
if let Ok(current_timestamp) =
+24 -11
View File
@@ -4,6 +4,7 @@
use crate::coconut::dkg::client::DkgClient;
use crate::coconut::dkg::state::{ConsistentState, State};
use crate::coconut::error::CoconutError;
use log::debug;
use nym_coconut_dkg_common::types::TOTAL_DEALINGS;
use nym_contracts_common::dealings::ContractSafeBytes;
use nym_dkg::bte::setup;
@@ -18,6 +19,7 @@ pub(crate) async fn dealing_exchange(
resharing: bool,
) -> Result<(), CoconutError> {
if state.receiver_index().is_some() {
debug!("Receiver index was set previously, nothing to do");
return Ok(());
}
@@ -45,6 +47,7 @@ pub(crate) async fn dealing_exchange(
return Err(CoconutError::CorruptedCoconutKeyPair);
}
// We can now erase the keypair from memory
debug!("Removing coconut keypair from memory");
state.set_coconut_keypair(None).await;
scalars.push(x);
scalars
@@ -59,6 +62,11 @@ pub(crate) async fn dealing_exchange(
if !resharing || initial_dealers.iter().any(|d| *d == own_address) {
let params = setup();
for _ in 0..TOTAL_DEALINGS {
debug!(
"Submitting dealing for indexes {:?} with resharing: {}",
receivers.keys().collect::<Vec<_>>(),
prior_resharing_secrets.front().is_some()
);
let (dealing, _) = Dealing::create(
rng.clone(),
&params,
@@ -71,9 +79,11 @@ pub(crate) async fn dealing_exchange(
.submit_dealing(ContractSafeBytes::from(&dealing), resharing)
.await?;
}
} else {
debug!("Nothing to do, waiting for initial dealers to submit dealings");
}
info!("DKG: Finished submitting dealing");
info!("DKG: Finished dealing exchange");
state.set_receiver_index(receiver_index);
Ok(())
@@ -109,7 +119,7 @@ pub(crate) mod tests {
fn insert_dealers(
params: &Params,
dealer_details_db: &Arc<RwLock<HashMap<String, DealerDetails>>>,
dealer_details_db: &Arc<RwLock<HashMap<String, (DealerDetails, bool)>>>,
) -> Vec<DkgKeyPair> {
let mut keypairs = vec![];
for (idx, addr) in TEST_VALIDATORS_ADDRESS.iter().enumerate() {
@@ -119,12 +129,15 @@ pub(crate) mod tests {
keypairs.push(keypair);
dealer_details_db.write().unwrap().insert(
addr.to_string(),
DealerDetails {
address: Addr::unchecked(*addr),
bte_public_key_with_proof,
announce_address: format!("localhost:80{}", idx),
assigned_index: (idx + 1) as u64,
},
(
DealerDetails {
address: Addr::unchecked(*addr),
bte_public_key_with_proof,
announce_address: format!("localhost:80{}", idx),
assigned_index: (idx + 1) as u64,
},
true,
),
);
}
keypairs
@@ -216,7 +229,7 @@ pub(crate) mod tests {
.unwrap()
.entry(TEST_VALIDATORS_ADDRESS[1].to_string())
.and_modify(|details| {
let mut bytes = bs58::decode(details.bte_public_key_with_proof.clone())
let mut bytes = bs58::decode(details.0.bte_public_key_with_proof.clone())
.into_vec()
.unwrap();
// Find another value for last byte that still deserializes to a public key with proof
@@ -231,7 +244,7 @@ pub(crate) mod tests {
break;
}
}
details.bte_public_key_with_proof = bs58::encode(&bytes).into_string();
details.0.bte_public_key_with_proof = bs58::encode(&bytes).into_string();
});
dealing_exchange(&dkg_client, &mut state, OsRng, false)
@@ -257,7 +270,7 @@ pub(crate) mod tests {
let threshold_db = Arc::new(RwLock::new(Some(3)));
let initial_dealers_db = Arc::new(RwLock::new(Some(InitialReplacementData {
initial_dealers: vec![Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])],
initial_height: Some(100),
initial_height: 100,
})));
let dkg_client = DkgClient::new(
DummyClient::new(
+16 -1
View File
@@ -4,6 +4,7 @@
use crate::coconut::dkg::client::DkgClient;
use crate::coconut::dkg::state::State;
use crate::coconut::error::CoconutError;
use log::debug;
use nym_coconut_dkg_common::dealer::DealerType;
pub(crate) async fn public_key_submission(
@@ -12,9 +13,21 @@ pub(crate) async fn public_key_submission(
resharing: bool,
) -> Result<(), CoconutError> {
if state.was_in_progress() {
state.reset_persistent(resharing).await;
let own_address = dkg_client.get_address().await.as_ref().to_string();
let is_initial_dealer = dkg_client
.get_initial_dealers()
.await?
.map(|data| data.initial_dealers.iter().any(|d| *d == own_address))
.unwrap_or(false);
let reset_coconut_keypair = !resharing || !is_initial_dealer;
debug!(
"Resetting state, with coconut keypair reset: {}",
reset_coconut_keypair
);
state.reset_persistent(reset_coconut_keypair).await;
}
if state.node_index().is_some() {
debug!("Node index was set previously, nothing to do");
return Ok(());
}
@@ -23,12 +36,14 @@ pub(crate) async fn public_key_submission(
let index = if let Some(details) = dealer_details.details {
if dealer_details.dealer_type == DealerType::Past {
// If it was a dealer in a previous epoch, re-register it for this epoch
debug!("Registering for the current DKG round, with keys from a previous epoch");
dkg_client
.register_dealer(bte_key, state.announce_address().to_string(), resharing)
.await?;
}
details.assigned_index
} else {
debug!("Registering for the first time to be a dealer");
// First time registration
dkg_client
.register_dealer(bte_key, state.announce_address().to_string(), resharing)
+8 -3
View File
@@ -5,6 +5,7 @@ use crate::coconut::dkg::complaints::ComplaintReason;
use crate::coconut::error::CoconutError;
use crate::coconut::keypair::KeyPair as CoconutKeyPair;
use cosmwasm_std::Addr;
use log::debug;
use nym_coconut_dkg_common::dealer::DealerDetails;
use nym_coconut_dkg_common::types::EpochState;
use nym_dkg::bte::{keys::KeyPair as DkgKeyPair, PublicKey, PublicKeyWithProof};
@@ -133,7 +134,7 @@ impl ConsistentState for State {
fn proposal_id_value(&self) -> Result<u64, CoconutError> {
self.proposal_id.ok_or(CoconutError::UnrecoverableState {
reason: String::from("Proposal id should have benn set"),
reason: String::from("Proposal id should have been set"),
})
}
}
@@ -242,8 +243,8 @@ impl State {
}
}
pub async fn reset_persistent(&mut self, resharing: bool) {
if !resharing {
pub async fn reset_persistent(&mut self, reset_coconut_keypair: bool) {
if reset_coconut_keypair {
self.coconut_keypair.set(None).await;
}
self.node_index = Default::default();
@@ -360,6 +361,10 @@ impl State {
.iter_mut()
.find(|(addr, _)| *addr == dealer_addr)
{
debug!(
"Dealer {} misbehaved: {:?}. It will be marked locally as bad dealer and ignored",
dealer_addr, reason
);
*value = Err(reason);
}
}
+258 -18
View File
@@ -8,6 +8,7 @@ use crate::coconut::error::CoconutError;
use crate::coconut::helpers::accepted_vote_err;
use cosmwasm_std::Addr;
use cw3::{ProposalResponse, Status};
use log::debug;
use nym_coconut_dkg_common::event_attributes::DKG_PROPOSAL_ID;
use nym_coconut_dkg_common::types::{NodeIndex, TOTAL_DEALINGS};
use nym_coconut_dkg_common::verification_key::owner_from_cosmos_msgs;
@@ -116,6 +117,11 @@ fn derive_partial_keypair(
}
})
.unzip();
debug!(
"Recovering verification keys from dealings of dealers {:?} with receivers {:?}",
filtered_dealers,
filtered_receivers_by_idx.keys().collect::<Vec<_>>()
);
let recovered = try_recover_verification_keys(
&filtered_dealings,
threshold,
@@ -123,10 +129,12 @@ fn derive_partial_keypair(
)?;
recovered_vks.push(recovered);
debug!("Decrypting shares");
let shares = filtered_dealings
.iter()
.map(|dealing| decrypt_share(dk, node_index_value, &dealing.ciphertexts, None))
.collect::<Result<_, _>>()?;
debug!("Combining shares into one secret");
let scalar = combine_shares(shares, &filtered_dealers)?;
scalars.push(scalar);
}
@@ -152,13 +160,19 @@ pub(crate) async fn verification_key_submission(
resharing: bool,
) -> Result<(), CoconutError> {
if state.coconut_keypair_is_some().await {
debug!("Coconut keypair was set previously, nothing to do");
return Ok(());
}
let threshold = state.threshold()?;
let dealings_maps =
deterministic_filter_dealers(dkg_client, state, threshold, resharing).await?;
debug!(
"Filtered dealers to {:?}",
dealings_maps[0].keys().collect::<Vec<_>>()
);
let coconut_keypair = derive_partial_keypair(state, threshold, dealings_maps)?;
debug!("Derived own coconut keypair");
let vk_share = coconut_keypair.verification_key().to_bs58();
nym_pemstore::store_keypair(&coconut_keypair, keypair_path)?;
let res = dkg_client
@@ -173,6 +187,10 @@ pub(crate) async fn verification_key_submission(
.map_err(|_| CoconutError::ProposalIdError {
reason: String::from("proposal id could not be parsed to u64"),
})?;
debug!(
"Submitted own verification key share, proposal id {} is attached to it",
proposal_id
);
state.set_proposal_id(proposal_id);
state.set_coconut_keypair(Some(coconut_keypair)).await;
info!("DKG: Submitted own verification key");
@@ -195,6 +213,7 @@ pub(crate) async fn verification_key_validation(
_resharing: bool,
) -> Result<(), CoconutError> {
if state.voted_vks() {
debug!("Already voted on the verification keys, nothing to do");
return Ok(());
}
@@ -225,10 +244,15 @@ pub(crate) async fn verification_key_validation(
.position(|node_index| contract_share.node_index == *node_index)
{
let ret = if !check_vk_pairing(&params, &recovered_partials[idx], &vk) {
debug!(
"Voting NO to proposal {} because of failed VK pairing",
proposal_id
);
dkg_client
.vote_verification_key_share(proposal_id, false)
.await
} else {
debug!("Voting YES to proposal {}", proposal_id);
dkg_client
.vote_verification_key_share(proposal_id, true)
.await
@@ -237,6 +261,10 @@ pub(crate) async fn verification_key_validation(
}
}
Err(_) => {
debug!(
"Voting NO to proposal {} because of failed base 58 deserialization",
proposal_id
);
let ret = dkg_client
.vote_verification_key_share(proposal_id, false)
.await;
@@ -256,6 +284,7 @@ pub(crate) async fn verification_key_finalization(
_resharing: bool,
) -> Result<(), CoconutError> {
if state.executed_proposal() {
debug!("Already executed the proposal, nothing to do");
return Ok(());
}
@@ -294,7 +323,7 @@ pub(crate) mod tests {
use validator_client::nyxd::AccountId;
struct MockContractDb {
dealer_details_db: Arc<RwLock<HashMap<String, DealerDetails>>>,
dealer_details_db: Arc<RwLock<HashMap<String, (DealerDetails, bool)>>>,
dealings_db: Arc<RwLock<HashMap<String, Vec<ContractSafeBytes>>>>,
proposal_db: Arc<RwLock<HashMap<u64, ProposalResponse>>>,
verification_share_db: Arc<RwLock<HashMap<String, ContractVKShare>>>,
@@ -315,10 +344,11 @@ pub(crate) mod tests {
}
}
const TEST_VALIDATORS_ADDRESS: [&str; 3] = [
const TEST_VALIDATORS_ADDRESS: [&str; 4] = [
"n1aq9kakfgwqcufr23lsv644apavcntrsqsk4yus",
"n1s9l3xr4g0rglvk4yctktmck3h4eq0gp6z2e20v",
"n19kl4py32vsk297dm93ezem992cdyzdy4zuc2x6",
"n1jfrs6cmw9t7dv0x8cgny6geunzjh56n2s89fkv",
];
async fn prepare_clients_and_states(db: &MockContractDb) -> Vec<(DkgClient, State)> {
@@ -418,7 +448,7 @@ pub(crate) mod tests {
.unwrap();
assert_eq!(filtered.len(), TOTAL_DEALINGS);
for mapping in filtered.iter() {
assert_eq!(mapping.len(), 3);
assert_eq!(mapping.len(), 4);
}
}
}
@@ -471,7 +501,7 @@ pub(crate) mod tests {
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
*db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData {
initial_dealers: vec![Addr::unchecked(TEST_VALIDATORS_ADDRESS[0])],
initial_height: None,
initial_height: 1,
});
let filtered = deterministic_filter_dealers(dkg_client, state, 2, true)
.await
@@ -504,7 +534,7 @@ pub(crate) mod tests {
for (dkg_client, state) in clients_and_states.iter_mut().skip(1) {
*db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData {
initial_dealers: vec![],
initial_height: None,
initial_height: 1,
});
let filtered = deterministic_filter_dealers(dkg_client, state, 2, true)
.await
@@ -542,7 +572,7 @@ pub(crate) mod tests {
.unwrap();
assert_eq!(filtered.len(), TOTAL_DEALINGS);
for mapping in filtered.iter() {
assert_eq!(mapping.len(), 2);
assert_eq!(mapping.len(), 3);
}
let corrupted_status = state
.all_dealers()
@@ -803,6 +833,9 @@ pub(crate) mod tests {
async fn reshare_preserves_keys() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states_with_finalization(&db).await;
for (_, state) in clients_and_states.iter_mut() {
state.set_was_in_progress();
}
let params = Parameters::new(4).unwrap();
let mut vks = vec![];
@@ -839,23 +872,22 @@ pub(crate) mod tests {
KeyPair::new(),
);
let removed_dealer = clients_and_states.first().unwrap().0.get_address().await;
db.dealer_details_db
.write()
.unwrap()
.remove(removed_dealer.as_ref());
for (_, active) in db.dealer_details_db.write().unwrap().values_mut() {
*active = false;
}
*db.dealings_db.write().unwrap() = Default::default();
*db.verification_share_db.write().unwrap() = Default::default();
let mut initial_dealers = vec![];
for (dkg_client, _) in clients_and_states.iter() {
let client_address = Addr::unchecked(dkg_client.get_address().await.as_ref());
initial_dealers.push(client_address);
}
*db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData {
initial_dealers: vec![
Addr::unchecked(clients_and_states[1].0.get_address().await.as_ref()),
Addr::unchecked(clients_and_states[2].0.get_address().await.as_ref()),
],
initial_height: None,
initial_dealers,
initial_height: 1,
});
*clients_and_states.first_mut().unwrap() = (new_dkg_client, state);
clients_and_states[1].1.set_was_in_progress();
clients_and_states[2].1.set_was_in_progress();
for (dkg_client, state) in clients_and_states.iter_mut() {
public_key_submission(dkg_client, state, true)
@@ -880,6 +912,214 @@ pub(crate) mod tests {
std::fs::remove_file(private_key_path).unwrap();
std::fs::remove_file(public_key_path).unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
verification_key_validation(dkg_client, state, true)
.await
.unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
verification_key_finalization(dkg_client, state, true)
.await
.unwrap();
}
assert!(db
.proposal_db
.read()
.unwrap()
.values()
.all(|proposal| { proposal.status == Status::Executed }));
let mut vks = vec![];
let mut indices = vec![];
for (_, state) in clients_and_states.iter() {
let vk = state
.coconut_secret_key()
.await
.unwrap()
.verification_key(&params);
let index = state.node_index().unwrap();
vks.push(vk);
indices.push(index);
}
let reshared_master_vk = aggregate_verification_keys(&vks, Some(&indices)).unwrap();
assert_eq!(initial_master_vk, reshared_master_vk);
}
#[tokio::test]
#[ignore] // expensive test
async fn reshare_after_reset() {
let db = MockContractDb::new();
let mut clients_and_states = prepare_clients_and_states_with_finalization(&db).await;
for (_, state) in clients_and_states.iter_mut() {
state.set_was_in_progress();
}
let new_dkg_client = DkgClient::new(
DummyClient::new(
AccountId::from_str("n1vxkywf9g4cg0k2dehanzwzz64jw782qm0kuynf").unwrap(),
)
.with_dealer_details(&db.dealer_details_db)
.with_dealings(&db.dealings_db)
.with_proposal_db(&db.proposal_db)
.with_verification_share(&db.verification_share_db)
.with_threshold(&db.threshold_db)
.with_initial_dealers_db(&db.initial_dealers_db),
);
let keypair = DkgKeyPair::new(&setup(), OsRng);
let state = State::new(
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
keypair,
KeyPair::new(),
);
let new_dkg_client2 = DkgClient::new(
DummyClient::new(
AccountId::from_str("n1sqkxzh7nl6kgndr4ew9795t2nkwmd8tpql67q7").unwrap(),
)
.with_dealer_details(&db.dealer_details_db)
.with_dealings(&db.dealings_db)
.with_proposal_db(&db.proposal_db)
.with_verification_share(&db.verification_share_db)
.with_threshold(&db.threshold_db)
.with_initial_dealers_db(&db.initial_dealers_db),
);
let keypair = DkgKeyPair::new(&setup(), OsRng);
let state2 = State::new(
PathBuf::default(),
PersistentState::default(),
Url::parse("localhost:8000").unwrap(),
keypair,
KeyPair::new(),
);
for (_, active) in db.dealer_details_db.write().unwrap().values_mut() {
*active = false;
}
*db.dealings_db.write().unwrap() = Default::default();
*db.verification_share_db.write().unwrap() = Default::default();
clients_and_states.pop().unwrap();
let (initial_client2, initial_state2) = clients_and_states.pop().unwrap();
clients_and_states.push((new_dkg_client, state));
clients_and_states.push((new_dkg_client2, state2));
// DKG in reset mode
for (dkg_client, state) in clients_and_states.iter_mut() {
public_key_submission(dkg_client, state, false)
.await
.unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
dealing_exchange(dkg_client, state, OsRng, false)
.await
.unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
let random_file: usize = OsRng.gen();
let private_key_path = temp_dir().join(format!("private{}.pem", random_file));
let public_key_path = temp_dir().join(format!("public{}.pem", random_file));
let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone());
verification_key_submission(dkg_client, state, &keypair_path, false)
.await
.unwrap();
std::fs::remove_file(private_key_path).unwrap();
std::fs::remove_file(public_key_path).unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
verification_key_validation(dkg_client, state, false)
.await
.unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
verification_key_finalization(dkg_client, state, false)
.await
.unwrap();
}
assert!(db
.proposal_db
.read()
.unwrap()
.values()
.all(|proposal| { proposal.status == Status::Executed }));
for (_, state) in clients_and_states.iter_mut() {
state.set_was_in_progress();
}
// DKG in reshare mode
let params = Parameters::new(4).unwrap();
let mut vks = vec![];
let mut indices = vec![];
for (_, state) in clients_and_states.iter() {
let vk = state
.coconut_secret_key()
.await
.unwrap()
.verification_key(&params);
let index = state.node_index().unwrap();
vks.push(vk);
indices.push(index);
}
let initial_master_vk = aggregate_verification_keys(&vks, Some(&indices)).unwrap();
for (_, active) in db.dealer_details_db.write().unwrap().values_mut() {
*active = false;
}
*db.dealings_db.write().unwrap() = Default::default();
*db.verification_share_db.write().unwrap() = Default::default();
let mut initial_dealers = vec![];
for (dkg_client, _) in clients_and_states.iter() {
let client_address = Addr::unchecked(dkg_client.get_address().await.as_ref());
initial_dealers.push(client_address);
}
*db.initial_dealers_db.write().unwrap() = Some(InitialReplacementData {
initial_dealers,
initial_height: 1,
});
*clients_and_states.last_mut().unwrap() = (initial_client2, initial_state2);
for (dkg_client, state) in clients_and_states.iter_mut() {
public_key_submission(dkg_client, state, true)
.await
.unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
dealing_exchange(dkg_client, state, OsRng, true)
.await
.unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
let random_file: usize = OsRng.gen();
let private_key_path = temp_dir().join(format!("private{}.pem", random_file));
let public_key_path = temp_dir().join(format!("public{}.pem", random_file));
let keypair_path = KeyPairPath::new(private_key_path.clone(), public_key_path.clone());
verification_key_submission(dkg_client, state, &keypair_path, true)
.await
.unwrap();
std::fs::remove_file(private_key_path).unwrap();
std::fs::remove_file(public_key_path).unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
verification_key_validation(dkg_client, state, true)
.await
.unwrap();
}
for (dkg_client, state) in clients_and_states.iter_mut() {
verification_key_finalization(dkg_client, state, true)
.await
.unwrap();
}
// assert!(db
// .proposal_db
// .read()
// .unwrap()
// .values()
// .all(|proposal| { proposal.status == Status::Executed }));
let mut vks = vec![];
let mut indices = vec![];
for (_, state) in clients_and_states.iter() {
+1 -1
View File
@@ -91,7 +91,7 @@ pub enum CoconutError {
#[error("Failed to recover assigned node index: {reason}")]
NodeIndexRecoveryError { reason: String },
#[error("Unrecoverable state: {reason}. Process should be restarted")]
#[error("Unrecoverable state: {reason}")]
UnrecoverableState { reason: String },
#[error("DKG has not finished yet in order to derive the coconut key")]
+52 -27
View File
@@ -69,7 +69,7 @@ pub(crate) struct DummyClient {
spent_credential_db: Arc<RwLock<HashMap<String, SpendCredentialResponse>>>,
epoch: Arc<RwLock<Epoch>>,
dealer_details: Arc<RwLock<HashMap<String, DealerDetails>>>,
dealer_details: Arc<RwLock<HashMap<String, (DealerDetails, bool)>>>,
threshold: Arc<RwLock<Option<Threshold>>>,
dealings: Arc<RwLock<HashMap<String, Vec<ContractSafeBytes>>>>,
verification_share: Arc<RwLock<HashMap<String, ContractVKShare>>>,
@@ -122,7 +122,7 @@ impl DummyClient {
pub fn with_dealer_details(
mut self,
dealer_details: &Arc<RwLock<HashMap<String, DealerDetails>>>,
dealer_details: &Arc<RwLock<HashMap<String, (DealerDetails, bool)>>>,
) -> Self {
self.dealer_details = Arc::clone(dealer_details);
self
@@ -233,14 +233,25 @@ impl super::client::Client for DummyClient {
}
async fn get_self_registered_dealer_details(&self) -> Result<DealerDetailsResponse> {
let (details, dealer_type) = if let Some((details, current)) = self
.dealer_details
.read()
.unwrap()
.get(self.validator_address.as_ref())
.cloned()
{
let dealer_type = if current {
DealerType::Current
} else {
DealerType::Past
};
(Some(details), dealer_type)
} else {
(None, DealerType::Unknown)
};
Ok(DealerDetailsResponse {
details: self
.dealer_details
.read()
.unwrap()
.get(self.validator_address.as_ref())
.cloned(),
dealer_type: DealerType::Current,
details,
dealer_type,
})
}
@@ -251,6 +262,7 @@ impl super::client::Client for DummyClient {
.unwrap()
.values()
.cloned()
.filter_map(|(d, current)| if current { Some(d) } else { None })
.collect())
}
@@ -287,13 +299,11 @@ impl super::client::Client for DummyClient {
_fee: Option<Fee>,
) -> Result<()> {
if let Some(proposal) = self.proposal_db.write().unwrap().get_mut(&proposal_id) {
// for now, just suppose that first vote is honest
if proposal.status == cw3::Status::Open {
if vote_yes {
proposal.status = cw3::Status::Passed;
} else {
proposal.status = cw3::Status::Rejected;
}
// for now, just suppose that every vote is honest
if !vote_yes {
proposal.status = cw3::Status::Rejected;
} else if vote_yes && proposal.status == cw3::Status::Open {
proposal.status = cw3::Status::Passed;
}
}
Ok(())
@@ -323,22 +333,33 @@ impl super::client::Client for DummyClient {
_resharing: bool,
) -> Result<ExecuteResult> {
let mut dealer_details = self.dealer_details.write().unwrap();
let assigned_index =
if let Some(details) = dealer_details.get(self.validator_address.as_ref()) {
details.assigned_index
} else {
let assigned_index = OsRng.gen();
dealer_details.insert(
self.validator_address.to_string(),
let assigned_index = if let Some((details, active)) =
dealer_details.get_mut(self.validator_address.as_ref())
{
*active = true;
details.assigned_index
} else {
// let assigned_index = OsRng.gen();
let assigned_index = dealer_details
.values()
.map(|(d, _)| d.assigned_index)
.max()
.unwrap_or(0)
+ 1;
dealer_details.insert(
self.validator_address.to_string(),
(
DealerDetails {
address: Addr::unchecked(self.validator_address.to_string()),
bte_public_key_with_proof,
announce_address,
assigned_index,
},
);
assigned_index
};
true,
),
);
assigned_index
};
Ok(ExecuteResult {
logs: vec![Log {
msg_index: 0,
@@ -380,13 +401,17 @@ impl super::client::Client for DummyClient {
share: VerificationKeyShare,
resharing: bool,
) -> Result<ExecuteResult> {
let dealer_details = self
let (dealer_details, active) = self
.dealer_details
.read()
.unwrap()
.get(self.validator_address.as_ref())
.unwrap()
.clone();
if !active {
// Just throw some error, not really the correct one
return Err(CoconutError::DepositEncrKeyNotFound);
}
self.verification_share.write().unwrap().insert(
self.validator_address.to_string(),
ContractVKShare {
+1 -1
View File
@@ -20,7 +20,7 @@ tauri-macros = "^1.2.1"
[dependencies]
anyhow = "1.0"
bip39 = "1.0"
bip39 = { version = "2.0.0", features = ["zeroize"] }
dirs = "4.0"
eyre = "0.6.5"
fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs", branch = "release"}
+2
View File
@@ -54,6 +54,8 @@ yarn webpack:prod
WRY_ANDROID_PACKAGE=net.nymtech.nym_connect WRY_ANDROID_LIBRARY=nym_connect cargo tauri android build --debug --apk
```
**NOTE**: Production build without the `--debug` flag requires a signed build.
# Storybook
Run storybook with:
+49 -182
View File
@@ -186,15 +186,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "autocfg"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78"
dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@@ -245,15 +236,16 @@ dependencies = [
[[package]]
name = "bip39"
version = "1.1.0"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5b9d9748b5770d1539657653dc5ac3cd9353549e74238dc0d96c22919128b94"
checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f"
dependencies = [
"bitcoin_hashes",
"rand 0.6.5",
"rand_core 0.4.2",
"rand 0.8.5",
"rand_core 0.6.4",
"serde",
"unicode-normalization",
"zeroize",
]
[[package]]
@@ -501,7 +493,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa"
dependencies = [
"smallvec 1.10.0",
"smallvec",
]
[[package]]
@@ -649,15 +641,6 @@ dependencies = [
"wasm-utils",
]
[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
dependencies = [
"bitflags",
]
[[package]]
name = "cocoa"
version = "0.24.1"
@@ -1005,7 +988,7 @@ dependencies = [
"phf 0.8.0",
"proc-macro2",
"quote",
"smallvec 1.10.0",
"smallvec",
"syn",
]
@@ -1687,12 +1670,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e"
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "funty"
version = "2.0.0"
@@ -2028,7 +2005,7 @@ dependencies = [
"libc",
"once_cell",
"pin-project-lite",
"smallvec 1.10.0",
"smallvec",
"thiserror",
]
@@ -2076,7 +2053,7 @@ dependencies = [
"gobject-sys",
"libc",
"once_cell",
"smallvec 1.10.0",
"smallvec",
"thiserror",
]
@@ -2659,7 +2636,7 @@ version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"hashbrown 0.12.3",
"serde",
]
@@ -3009,7 +2986,7 @@ version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"scopeguard",
]
@@ -3095,12 +3072,6 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "maybe-uninit"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
name = "memchr"
version = "2.5.0"
@@ -3113,7 +3084,7 @@ version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg 1.1.0",
"autocfg",
]
[[package]]
@@ -3273,7 +3244,7 @@ version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"num-traits",
]
@@ -3283,7 +3254,7 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"num-integer",
"num-traits",
]
@@ -3294,7 +3265,7 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"libm",
]
@@ -4002,7 +3973,7 @@ version = "0.9.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"cc",
"libc",
"openssl-src",
@@ -4097,7 +4068,7 @@ dependencies = [
"instant",
"libc",
"redox_syscall",
"smallvec 1.10.0",
"smallvec",
"winapi",
]
@@ -4110,7 +4081,7 @@ dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec 1.10.0",
"smallvec",
"windows-sys 0.45.0",
]
@@ -4545,25 +4516,6 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
dependencies = [
"autocfg 0.1.8",
"libc",
"rand_chacha 0.1.1",
"rand_core 0.4.2",
"rand_hc 0.1.0",
"rand_isaac",
"rand_jitter",
"rand_os",
"rand_pcg 0.1.2",
"rand_xorshift",
"winapi",
]
[[package]]
name = "rand"
version = "0.7.3"
@@ -4574,8 +4526,8 @@ dependencies = [
"libc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc 0.2.0",
"rand_pcg 0.2.1",
"rand_hc",
"rand_pcg",
]
[[package]]
@@ -4589,16 +4541,6 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
dependencies = [
"autocfg 0.1.8",
"rand_core 0.3.1",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
@@ -4619,21 +4561,6 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_core"
version = "0.5.1"
@@ -4662,15 +4589,6 @@ dependencies = [
"rand 0.7.3",
]
[[package]]
name = "rand_hc"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
@@ -4680,50 +4598,6 @@ dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_isaac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_jitter"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
dependencies = [
"libc",
"rand_core 0.4.2",
"winapi",
]
[[package]]
name = "rand_os"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
dependencies = [
"cloudabi",
"fuchsia-cprng",
"libc",
"rand_core 0.4.2",
"rdrand",
"winapi",
]
[[package]]
name = "rand_pcg"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
dependencies = [
"autocfg 0.1.8",
"rand_core 0.4.2",
]
[[package]]
name = "rand_pcg"
version = "0.2.1"
@@ -4733,15 +4607,6 @@ dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_xorshift"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "raw-window-handle"
version = "0.5.0"
@@ -4751,15 +4616,6 @@ dependencies = [
"cty",
]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
@@ -5177,7 +5033,7 @@ dependencies = [
"phf_codegen",
"precomputed-hash",
"servo_arc",
"smallvec 1.10.0",
"smallvec",
"thin-slice",
]
@@ -5445,16 +5301,7 @@ version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "smallvec"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0"
dependencies = [
"maybe-uninit",
"autocfg",
]
[[package]]
@@ -5626,7 +5473,7 @@ dependencies = [
"percent-encoding",
"rustls 0.19.1",
"sha2 0.10.6",
"smallvec 1.10.0",
"smallvec",
"sqlformat 0.1.8",
"sqlx-rt 0.5.13",
"stringprep",
@@ -5673,7 +5520,7 @@ dependencies = [
"rustls 0.20.8",
"rustls-pemfile",
"sha2 0.10.6",
"smallvec 1.10.0",
"smallvec",
"sqlformat 0.2.1",
"sqlx-rt 0.6.2",
"stringprep",
@@ -6363,13 +6210,28 @@ dependencies = [
"time-core",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"bytes",
"libc",
"memchr",
@@ -6564,7 +6426,7 @@ dependencies = [
"once_cell",
"regex",
"sharded-slab",
"smallvec 1.10.0",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
@@ -6667,11 +6529,11 @@ checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "unicode-normalization"
version = "0.1.9"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09c8070a9942f5e7cfccd93f490fdebd230ee3c3c9f107cb25bad5351ef671cf"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"smallvec 0.6.14",
"tinyvec",
]
[[package]]
@@ -7438,3 +7300,8 @@ dependencies = [
"syn",
"synstructure",
]
[[patch.unused]]
name = "tauri-mobile"
version = "0.2.4"
source = "git+https://github.com/tauri-apps/tauri-mobile?branch=dev#442f0d2c7328930db61058a55706d22e6a401c16"
+5 -1
View File
@@ -19,9 +19,13 @@ crate-type = ["staticlib", "cdylib", "rlib"]
tauri-build = { version = "2.0.0-alpha.1", features = [] }
# tauri-build = { git = "https://github.com/tauri-apps/tauri", branch = "next", features = [] }
# TODO untill new tauri version includes https://github.com/tauri-apps/tauri-mobile/pull/111
[patch.crates-io]
tauri-mobile = { git = "https://github.com/tauri-apps/tauri-mobile", branch = "dev" }
[dependencies]
anyhow = "1.0"
bip39 = "1.0"
bip39 = { version = "2.0.0", features = ["zeroize"] }
chrono = "0.4"
dirs = "4.0"
eyre = "0.6.5"
+59 -197
View File
@@ -181,15 +181,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "autocfg"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78"
dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@@ -240,22 +231,23 @@ dependencies = [
[[package]]
name = "bip39"
version = "1.0.1"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e89470017230c38e52b82b3ee3f530db1856ba1d434e3a67a3456a8a8dec5f"
checksum = "93f2635620bf0b9d4576eb7bb9a38a55df78bd1205d26fa994b25911a69f212f"
dependencies = [
"bitcoin_hashes",
"rand 0.6.5",
"rand_core 0.4.2",
"rand 0.8.5",
"rand_core 0.6.4",
"serde",
"unicode-normalization",
"zeroize",
]
[[package]]
name = "bitcoin_hashes"
version = "0.9.7"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ce18265ec2324ad075345d5814fbeed4f41f0a660055dc78840b74d19b874b1"
checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4"
[[package]]
name = "bitflags"
@@ -476,7 +468,7 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3431df59f28accaf4cb4eed4a9acc66bea3f3c3753aa6cdc2f024174ef232af7"
dependencies = [
"smallvec 1.10.0",
"smallvec",
]
[[package]]
@@ -485,7 +477,7 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa"
dependencies = [
"smallvec 1.10.0",
"smallvec",
]
[[package]]
@@ -569,15 +561,6 @@ dependencies = [
"os_str_bytes",
]
[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
dependencies = [
"bitflags",
]
[[package]]
name = "cocoa"
version = "0.24.1"
@@ -869,7 +852,7 @@ dependencies = [
"phf 0.8.0",
"proc-macro2",
"quote",
"smallvec 1.10.0",
"smallvec",
"syn",
]
@@ -1431,12 +1414,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e"
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "funty"
version = "2.0.0"
@@ -1762,7 +1739,7 @@ dependencies = [
"gobject-sys",
"libc",
"once_cell",
"smallvec 1.10.0",
"smallvec",
"thiserror",
]
@@ -2248,7 +2225,7 @@ version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"hashbrown",
"serde",
]
@@ -2508,7 +2485,7 @@ version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"scopeguard",
]
@@ -2581,12 +2558,6 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5"
[[package]]
name = "maybe-uninit"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
name = "memchr"
version = "2.5.0"
@@ -2599,7 +2570,7 @@ version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg 1.1.0",
"autocfg",
]
[[package]]
@@ -2729,7 +2700,7 @@ version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"num-traits",
]
@@ -2739,7 +2710,7 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"num-integer",
"num-traits",
]
@@ -2750,7 +2721,7 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"libm",
]
@@ -2878,6 +2849,20 @@ dependencies = [
"thiserror",
]
[[package]]
name = "nym-crypto"
version = "0.2.0"
dependencies = [
"bs58",
"ed25519-dalek",
"nym-pemstore",
"nym-sphinx-types",
"rand 0.7.3",
"subtle-encoding",
"thiserror",
"x25519-dalek",
]
[[package]]
name = "nym-dkg"
version = "0.1.0"
@@ -2899,20 +2884,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "nym-crypto"
version = "0.1.0"
dependencies = [
"bs58",
"ed25519-dalek",
"nym-pemstore",
"nym-sphinx-types",
"rand 0.7.3",
"subtle-encoding",
"thiserror",
"x25519-dalek",
]
[[package]]
name = "nym-execute"
version = "0.1.0"
@@ -2984,7 +2955,7 @@ dependencies = [
[[package]]
name = "nym-sphinx-types"
version = "0.1.0"
version = "0.2.0"
dependencies = [
"sphinx-packet",
]
@@ -3110,7 +3081,6 @@ dependencies = [
"nym-wallet-types",
"once_cell",
"pretty_env_logger",
"rand 0.6.5",
"rand_chacha 0.2.2",
"reqwest",
"serde",
@@ -3258,7 +3228,7 @@ version = "0.9.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"cc",
"libc",
"pkg-config",
@@ -3330,7 +3300,7 @@ dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec 1.10.0",
"smallvec",
"windows-sys 0.45.0",
]
@@ -3790,25 +3760,6 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
dependencies = [
"autocfg 0.1.8",
"libc",
"rand_chacha 0.1.1",
"rand_core 0.4.2",
"rand_hc 0.1.0",
"rand_isaac",
"rand_jitter",
"rand_os",
"rand_pcg 0.1.2",
"rand_xorshift",
"winapi",
]
[[package]]
name = "rand"
version = "0.7.3"
@@ -3819,8 +3770,8 @@ dependencies = [
"libc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc 0.2.0",
"rand_pcg 0.2.1",
"rand_hc",
"rand_pcg",
]
[[package]]
@@ -3834,16 +3785,6 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
dependencies = [
"autocfg 0.1.8",
"rand_core 0.3.1",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
@@ -3864,21 +3805,6 @@ dependencies = [
"rand_core 0.6.4",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_core"
version = "0.5.1"
@@ -3907,15 +3833,6 @@ dependencies = [
"rand 0.7.3",
]
[[package]]
name = "rand_hc"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
@@ -3925,50 +3842,6 @@ dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_isaac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_jitter"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
dependencies = [
"libc",
"rand_core 0.4.2",
"winapi",
]
[[package]]
name = "rand_os"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
dependencies = [
"cloudabi",
"fuchsia-cprng",
"libc",
"rand_core 0.4.2",
"rdrand",
"winapi",
]
[[package]]
name = "rand_pcg"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
dependencies = [
"autocfg 0.1.8",
"rand_core 0.4.2",
]
[[package]]
name = "rand_pcg"
version = "0.2.1"
@@ -3978,15 +3851,6 @@ dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_xorshift"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "raw-window-handle"
version = "0.5.0"
@@ -3996,15 +3860,6 @@ dependencies = [
"cty",
]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
@@ -4350,7 +4205,7 @@ dependencies = [
"phf_codegen",
"precomputed-hash",
"servo_arc",
"smallvec 1.10.0",
"smallvec",
"thin-slice",
]
@@ -4605,16 +4460,7 @@ version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef"
dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "smallvec"
version = "0.6.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0"
dependencies = [
"maybe-uninit",
"autocfg",
]
[[package]]
@@ -5281,13 +5127,28 @@ dependencies = [
"time-core",
]
[[package]]
name = "tinyvec"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
dependencies = [
"autocfg 1.1.0",
"autocfg",
"bytes",
"libc",
"memchr",
@@ -5434,7 +5295,7 @@ dependencies = [
"once_cell",
"regex",
"sharded-slab",
"smallvec 1.10.0",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
@@ -5517,11 +5378,11 @@ checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "unicode-normalization"
version = "0.1.9"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09c8070a9942f5e7cfccd93f490fdebd230ee3c3c9f107cb25bad5351ef671cf"
checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921"
dependencies = [
"smallvec 0.6.14",
"tinyvec",
]
[[package]]
@@ -6228,6 +6089,7 @@ version = "1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
dependencies = [
"serde",
"zeroize_derive",
]
@@ -10,7 +10,7 @@ aes-gcm = "0.9"
anyhow = "1.0"
argon2 = "0.4"
base64 = "0.13"
bip39 = "1.0"
bip39 = { version = "2.0.0", features = ["zeroize"] }
clap = { version = "4.0", features = ["derive"] }
log = "0.4"
pretty_env_logger = "0.4"
+1 -1
View File
@@ -11,7 +11,7 @@ serde_json = "1.0"
strum = { version = "0.23", features = ["derive"] }
ts-rs = "6.1.2"
cosmwasm-std = "1.0.0-beta8"
cosmwasm-std = "1.0.0"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
nym-config = { path = "../../common/config" }
+2 -3
View File
@@ -20,7 +20,7 @@ tauri-macros = "=1.2.1"
[dependencies]
async-trait = "0.1.64"
bip39 = "1.0"
bip39 = { version = "2.0.0", features = ["zeroize", "rand"] }
cfg-if = "1.0.0"
colored = "2.0"
dirs = "4.0"
@@ -32,7 +32,6 @@ itertools = "0.10"
log = { version = "0.4", features = ["serde"] }
once_cell = "1.7.2"
pretty_env_logger = "0.4"
rand = "0.6.5"
reqwest = {version = "0.11.9", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
@@ -50,7 +49,7 @@ k256 = { version = "0.10", features = ["ecdsa", "sha256"] }
aes-gcm = "0.9.4"
argon2 = { version = "0.3.2", features = ["std"] }
base64 = "0.13"
zeroize = "1.4.3"
zeroize = { version = "1.5", features = ["zeroize_derive", "serde"] }
cosmwasm-std = "1.0.0"
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
+1
View File
@@ -63,6 +63,7 @@ fn main() {
mixnet::bond::unbond_mixnode,
mixnet::bond::update_mixnode_cost_params,
mixnet::bond::update_mixnode_config,
mixnet::bond::update_gateway_config,
mixnet::bond::get_number_of_mixnode_delegators,
mixnet::bond::get_mix_node_description,
mixnet::bond::get_mixnode_avg_uptime,
@@ -2,15 +2,14 @@ use crate::config::{Config, CUSTOM_SIMULATED_GAS_MULTIPLIER};
use crate::error::BackendError;
use crate::network_config;
use crate::state::{WalletAccountIds, WalletState};
use crate::utils::{SensitiveStringWrapper, ZeroizeMnemonicWrapper};
use crate::wallet_storage::{self, UserPassword, DEFAULT_LOGIN_ID};
use bip39::rand::{self, seq::SliceRandom};
use bip39::{Language, Mnemonic};
use cosmrs::bip32::DerivationPath;
use itertools::Itertools;
use nym_config::defaults::{NymNetworkDetails, COSMOS_DERIVATION_PATH};
use nym_types::account::{Account, AccountEntry, Balance};
use nym_wallet_types::network::Network as WalletNetwork;
use rand::seq::SliceRandom;
use std::collections::HashMap;
use strum::IntoEnumIterator;
use url::Url;
@@ -19,10 +18,9 @@ use validator_client::{nyxd::SigningNyxdClient, Client};
#[tauri::command]
pub async fn connect_with_mnemonic(
mnemonic: SensitiveStringWrapper,
mnemonic: Mnemonic,
state: tauri::State<'_, WalletState>,
) -> Result<Account, BackendError> {
let mnemonic = ZeroizeMnemonicWrapper::try_from_string(mnemonic)?;
_connect_with_mnemonic(mnemonic, state).await
}
@@ -48,13 +46,13 @@ pub async fn get_balance(state: tauri::State<'_, WalletState>) -> Result<Balance
}
#[tauri::command]
pub fn create_new_mnemonic() -> SensitiveStringWrapper {
random_mnemonic().into_string()
pub fn create_new_mnemonic() -> Mnemonic {
random_mnemonic()
}
#[tauri::command]
pub fn validate_mnemonic(mnemonic: SensitiveStringWrapper) -> bool {
ZeroizeMnemonicWrapper::try_from_string(mnemonic).is_ok()
pub fn validate_mnemonic(_mnemonic: Mnemonic) -> bool {
true
}
#[tauri::command]
@@ -82,15 +80,13 @@ pub async fn logout(state: tauri::State<'_, WalletState>) -> Result<(), BackendE
Ok(())
}
fn random_mnemonic() -> ZeroizeMnemonicWrapper {
fn random_mnemonic() -> Mnemonic {
let mut rng = rand::thread_rng();
Mnemonic::generate_in_with(&mut rng, Language::English, 24)
.unwrap()
.into()
Mnemonic::generate_in_with(&mut rng, Language::English, 24).unwrap()
}
async fn _connect_with_mnemonic(
mnemonic: ZeroizeMnemonicWrapper,
mnemonic: Mnemonic,
state: tauri::State<'_, WalletState>,
) -> Result<Account, BackendError> {
{
@@ -141,7 +137,7 @@ async fn _connect_with_mnemonic(
&default_nyxd_urls,
&default_api_urls,
&config,
mnemonic.as_ref(),
&mnemonic,
)?;
// Set the default account
@@ -298,16 +294,12 @@ pub fn does_password_file_exist() -> Result<bool, BackendError> {
}
#[tauri::command]
pub fn create_password(
mnemonic: SensitiveStringWrapper,
password: UserPassword,
) -> Result<(), BackendError> {
pub fn create_password(mnemonic: Mnemonic, password: UserPassword) -> Result<(), BackendError> {
if does_password_file_exist()? {
return Err(BackendError::WalletFileAlreadyExists);
}
log::info!("Creating password");
let mnemonic = ZeroizeMnemonicWrapper::try_from_string(mnemonic)?;
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
// Currently we only support a single, default, login id in the wallet
let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string());
@@ -330,7 +322,7 @@ pub async fn sign_in_with_password(
set_state_with_all_accounts(stored_login, first_login_id_when_converting, state.clone())
.await?;
_connect_with_mnemonic(mnemonic.into(), state).await
_connect_with_mnemonic(mnemonic, state).await
}
fn extract_first_mnemonic(
@@ -370,7 +362,7 @@ pub async fn sign_in_with_password_and_account_id(
set_state_with_all_accounts(stored_login, first_login_id_when_converting, state.clone())
.await?;
_connect_with_mnemonic(mnemonic.into(), state).await
_connect_with_mnemonic(mnemonic, state).await
}
fn extract_mnemonic(
@@ -404,13 +396,12 @@ pub fn archive_wallet_file() -> Result<(), BackendError> {
#[tauri::command]
pub async fn add_account_for_password(
mnemonic: SensitiveStringWrapper,
mnemonic: Mnemonic,
password: UserPassword,
account_id: &str,
state: tauri::State<'_, WalletState>,
) -> Result<AccountEntry, BackendError> {
log::info!("Adding account for the current password: {account_id}");
let mnemonic = ZeroizeMnemonicWrapper::try_from_string(mnemonic)?;
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
// Currently we only support a single, default, login id in the wallet
let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string());
@@ -427,13 +418,7 @@ pub async fn add_account_for_password(
let address = {
let state = state.read().await;
let network: NymNetworkDetails = state.current_network().into();
// safety: the call to `clone_inner` is fine here as the raw mnemonic will get immediately
// passed to `DirectSecp256k1HdWallet` which will zeroize it on drop
derive_address(
mnemonic.into_cloned_inner(),
&network.chain_details.bech32_account_prefix,
)?
.to_string()
derive_address(mnemonic, &network.chain_details.bech32_account_prefix)?.to_string()
};
// Re-read all the acccounts from the wallet to reset the state, rather than updating it
@@ -553,19 +538,19 @@ pub async fn list_accounts(
pub fn show_mnemonic_for_account_in_password(
account_id: String,
password: UserPassword,
) -> Result<SensitiveStringWrapper, BackendError> {
) -> Result<Mnemonic, BackendError> {
log::info!("Getting mnemonic for: {account_id}");
let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string());
let account_id = wallet_storage::AccountId::new(account_id);
let mnemonic = _show_mnemonic_for_account_in_password(&login_id, &account_id, &password)?;
Ok(mnemonic.into_string())
Ok(mnemonic)
}
fn _show_mnemonic_for_account_in_password(
login_id: &wallet_storage::LoginId,
account_id: &wallet_storage::AccountId,
password: &wallet_storage::UserPassword,
) -> Result<ZeroizeMnemonicWrapper, BackendError> {
) -> Result<Mnemonic, BackendError> {
let stored_account = wallet_storage::load_existing_login(login_id, password)?;
let mnemonic = match stored_account {
wallet_storage::StoredLogin::Mnemonic(ref account) => account.mnemonic().clone(),
@@ -575,7 +560,7 @@ fn _show_mnemonic_for_account_in_password(
.mnemonic()
.clone(),
};
Ok(mnemonic.into())
Ok(mnemonic)
}
#[cfg(test)]
@@ -590,8 +575,8 @@ mod tests {
use super::*;
// This decryptes a stored wallet file using the same procedure as when signing in. Most tests
// related to the encryped wallet storage is in `wallet_storage`.
// This decrypts a stored wallet file using the same procedure as when signing in. Most tests
// related to the encrypted wallet storage is in `wallet_storage`.
#[test]
fn decrypt_stored_wallet_for_sign_in() {
const SAVED_WALLET: &str = "src/wallet_storage/test-data/saved-wallet.json";
@@ -8,6 +8,7 @@ use crate::operations::helpers::{
use crate::state::WalletState;
use crate::{nyxd_client, Gateway, MixNode};
use nym_contracts_common::signing::MessageSignature;
use nym_mixnet_contract_common::gateway::GatewayConfigUpdate;
use nym_mixnet_contract_common::{MixId, MixNodeConfigUpdate};
use nym_types::currency::DecCoin;
use nym_types::gateway::GatewayBond;
@@ -227,6 +228,31 @@ pub async fn update_mixnode_config(
)?)
}
#[tauri::command]
pub async fn update_gateway_config(
update: GatewayConfigUpdate,
fee: Option<Fee>,
state: tauri::State<'_, WalletState>,
) -> Result<TransactionExecuteResult, BackendError> {
let guard = state.read().await;
let fee_amount = guard.convert_tx_fee(fee.as_ref());
log::info!(
">>> Update gateway config: update = {}, fee {:?}",
update.to_inline_json(),
fee,
);
let res = guard
.current_client()?
.nyxd
.update_gateway_config(update, fee)
.await?;
log::info!("<<< tx hash = {}", res.transaction_hash);
log::trace!("<<< {:?}", res);
Ok(TransactionExecuteResult::from_execute_result(
res, fee_amount,
)?)
}
#[tauri::command]
pub async fn get_mixnode_avg_uptime(
state: tauri::State<'_, WalletState>,
+1 -94
View File
@@ -4,16 +4,14 @@
use crate::error::BackendError;
use crate::nyxd_client;
use crate::state::WalletState;
use bip39::Mnemonic;
use cosmwasm_std::Decimal;
use nym_mixnet_contract_common::{IdentityKey, MixId, Percent};
use nym_types::currency::DecCoin;
use nym_types::mixnode::MixNodeCostParams;
use nym_wallet_types::app::AppEnv;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde::{Deserialize, Serialize};
use validator_client::nyxd::traits::MixnetQueryClient;
use validator_client::nyxd::{tx, Coin, CosmosCoin, Gas, GasPrice};
use zeroize::Zeroize;
fn get_env_as_option(key: &str) -> Option<String> {
match ::std::env::var(key) {
@@ -194,94 +192,3 @@ pub async fn get_old_and_incorrect_hardcoded_fee(
log::info!("hardcoded fee for {:?} is {:?}", operation, coin);
guard.attempt_convert_to_display_dec_coin(coin)
}
#[derive(Zeroize)]
#[zeroize(drop)]
pub struct SensitiveStringWrapper(String);
impl<'de> Deserialize<'de> for SensitiveStringWrapper {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(SensitiveStringWrapper(String::deserialize(deserializer)?))
}
}
impl Serialize for SensitiveStringWrapper {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
// unfortunately this serialized value will live on...
self.0.serialize(serializer)
}
}
impl From<String> for SensitiveStringWrapper {
fn from(value: String) -> Self {
SensitiveStringWrapper(value)
}
}
impl AsRef<str> for SensitiveStringWrapper {
fn as_ref(&self) -> &str {
self.0.as_ref()
}
}
#[derive(Clone)]
// can't do it natively until https://github.com/rust-bitcoin/rust-bip39/pull/32 gets merged and released...
pub(crate) struct ZeroizeMnemonicWrapper(bip39::Mnemonic);
impl From<bip39::Mnemonic> for ZeroizeMnemonicWrapper {
fn from(value: Mnemonic) -> Self {
ZeroizeMnemonicWrapper(value)
}
}
impl Zeroize for ZeroizeMnemonicWrapper {
fn zeroize(&mut self) {
// overwrite the mnemonic value with a completely random one
// (a poor man's zeroize until bip39 crate does it properly...)
self.0 = Mnemonic::generate(self.0.word_count()).unwrap();
}
}
impl Drop for ZeroizeMnemonicWrapper {
fn drop(&mut self) {
self.zeroize()
}
}
impl AsRef<bip39::Mnemonic> for ZeroizeMnemonicWrapper {
fn as_ref(&self) -> &Mnemonic {
&self.0
}
}
impl ZeroizeMnemonicWrapper {
pub(crate) fn into_string(self) -> SensitiveStringWrapper {
SensitiveStringWrapper(self.0.to_string())
}
pub(crate) fn try_from_string(string: SensitiveStringWrapper) -> Result<Self, bip39::Error> {
let res = string.as_ref().parse()?;
Ok(ZeroizeMnemonicWrapper(res))
}
// special care must be taken when calling this method as the mnemonic will no longer get zeroized!
pub(crate) fn into_cloned_inner(self) -> bip39::Mnemonic {
self.0.clone()
}
#[cfg(test)]
pub(crate) fn unchecked_clone_inner(&self) -> bip39::Mnemonic {
self.0.clone()
}
#[cfg(test)]
pub(crate) fn generate_random() -> Self {
ZeroizeMnemonicWrapper(Mnemonic::generate(24).unwrap())
}
}
@@ -14,16 +14,14 @@
// In the future we might want to simplify by dropping the support for a single account entry,
// instead treating as muliple accounts with one entry.
use serde::{Deserialize, Serialize};
use validator_client::nyxd::bip32::DerivationPath;
use zeroize::Zeroize;
use crate::error::BackendError;
use crate::utils::ZeroizeMnemonicWrapper;
use super::encryption::EncryptedData;
use super::password::{AccountId, LoginId};
use super::UserPassword;
use crate::error::BackendError;
use bip39::Mnemonic;
use serde::{Deserialize, Serialize};
use validator_client::nyxd::bip32::DerivationPath;
use zeroize::{Zeroize, ZeroizeOnDrop};
const CURRENT_WALLET_FILE_VERSION: u32 = 1;
@@ -153,9 +151,8 @@ impl EncryptedLogin {
/// A stored login is either a account, such as a mnemonic, or a list of multiple accounts where
/// each has an inner id. Future proofed for having private key backed accounts.
#[derive(Serialize, Deserialize, Debug, Zeroize)]
#[derive(Serialize, Deserialize, Debug, Zeroize, ZeroizeOnDrop)]
#[serde(untagged)]
#[zeroize(drop)]
pub(crate) enum StoredLogin {
Mnemonic(MnemonicAccount),
// PrivateKey(PrivateKeyAccount)
@@ -193,8 +190,7 @@ impl StoredLogin {
}
/// Multiple stored accounts, each entry having an id and a data field.
#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)]
#[zeroize(drop)]
#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, ZeroizeOnDrop, PartialEq, Eq)]
pub(crate) struct MultipleAccounts {
accounts: Vec<WalletAccount>,
}
@@ -238,19 +234,17 @@ impl MultipleAccounts {
pub(crate) fn add(
&mut self,
id: AccountId,
mnemonic: ZeroizeMnemonicWrapper,
mnemonic: Mnemonic,
hd_path: DerivationPath,
) -> Result<(), BackendError> {
if self.get_account(&id).is_some() {
Err(BackendError::WalletAccountIdAlreadyExistsInWalletLogin)
} else if self.get_account_with_mnemonic(mnemonic.as_ref()).is_some() {
} else if self.get_account_with_mnemonic(&mnemonic).is_some() {
Err(BackendError::WalletMnemonicAlreadyExistsInWalletLogin)
} else {
self.accounts.push(WalletAccount::new(
id,
// safety: the call to `clone_inner` is fine here as the raw mnemonic will get immediately
// passed to `MnemonicAccount` which will zeroize it on drop
MnemonicAccount::new(mnemonic.into_cloned_inner(), hd_path),
MnemonicAccount::new(mnemonic, hd_path),
));
Ok(())
}
@@ -272,8 +266,7 @@ impl From<Vec<WalletAccount>> for MultipleAccounts {
}
/// An entry in the list of stored accounts
#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)]
#[zeroize(drop)]
#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, ZeroizeOnDrop, PartialEq, Eq)]
pub(crate) struct WalletAccount {
id: AccountId,
account: AccountData,
@@ -307,19 +300,20 @@ impl WalletAccount {
/// An account usually is a mnemonic account, but in the future it might be backed by a private
/// key.
#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)]
#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, ZeroizeOnDrop, PartialEq, Eq)]
#[serde(untagged)]
#[zeroize(drop)]
enum AccountData {
Mnemonic(MnemonicAccount),
// PrivateKey(PrivateKeyAccount)
}
/// An account backed by a unique mnemonic.
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, ZeroizeOnDrop, PartialEq, Eq)]
pub(crate) struct MnemonicAccount {
mnemonic: bip39::Mnemonic,
#[serde(with = "display_hd_path")]
// there's nothing secret about our derivation path
#[zeroize(skip)]
hd_path: DerivationPath,
}
@@ -338,27 +332,6 @@ impl MnemonicAccount {
}
}
impl Zeroize for MnemonicAccount {
fn zeroize(&mut self) {
// in ideal world, Mnemonic would have had zeroize defined on it (there's an almost year old PR that introduces it)
// and the memory would have been filled with zeroes.
//
// we really don't want to keep our real mnemonic in memory, so let's do the semi-nasty thing
// of overwriting it with a fresh mnemonic that was never used before
//
// note: this function can only fail on an invalid word count, which clearly is not the case here
self.mnemonic = bip39::Mnemonic::generate(self.mnemonic.word_count()).unwrap();
// further note: we don't really care about the hd_path, there's nothing secret about it.
}
}
impl Drop for MnemonicAccount {
fn drop(&mut self) {
self.zeroize()
}
}
mod display_hd_path {
use serde::{Deserialize, Deserializer, Serializer};
use validator_client::nyxd::bip32::DerivationPath;
+107 -191
View File
@@ -14,7 +14,7 @@ pub(crate) use crate::wallet_storage::password::{AccountId, LoginId, UserPasswor
use crate::error::BackendError;
use crate::platform_constants::{STORAGE_DIR_NAME, WALLET_INFO_FILENAME};
use crate::utils::ZeroizeMnemonicWrapper;
use bip39::Mnemonic;
use std::ffi::OsString;
use std::fs::{self, create_dir_all, OpenOptions};
use std::path::{Path, PathBuf};
@@ -139,7 +139,7 @@ fn store_login_at_file(
}
pub(crate) fn store_login_with_multiple_accounts(
mnemonic: ZeroizeMnemonicWrapper,
mnemonic: Mnemonic,
hd_path: DerivationPath,
id: LoginId,
password: &UserPassword,
@@ -154,7 +154,7 @@ pub(crate) fn store_login_with_multiple_accounts(
fn store_login_with_multiple_accounts_at_file(
filepath: &Path,
mnemonic: ZeroizeMnemonicWrapper,
mnemonic: Mnemonic,
hd_path: DerivationPath,
id: LoginId,
password: &UserPassword,
@@ -185,7 +185,7 @@ fn store_login_with_multiple_accounts_at_file(
/// account in the list of accounts associated with the encrypted entry. The inner id for this
/// entry will be set to the same as the outer, unencrypted, id.
pub(crate) fn append_account_to_login(
mnemonic: ZeroizeMnemonicWrapper,
mnemonic: Mnemonic,
hd_path: DerivationPath,
id: LoginId,
inner_id: AccountId,
@@ -201,7 +201,7 @@ pub(crate) fn append_account_to_login(
fn append_account_to_login_at_file(
filepath: &Path,
mnemonic: ZeroizeMnemonicWrapper,
mnemonic: Mnemonic,
hd_path: DerivationPath,
id: LoginId,
inner_id: AccountId,
@@ -422,19 +422,12 @@ mod tests {
fn store_single_login() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
store_login_at_file(
&wallet_file,
account1.into_cloned_inner(),
hd_path,
id1.clone(),
&password,
)
.unwrap();
store_login_at_file(&wallet_file, account1, hd_path, id1.clone(), &password).unwrap();
let stored_wallet = load_existing_wallet_at_file(&wallet_file).unwrap();
assert_eq!(stored_wallet.len(), 1);
@@ -450,7 +443,7 @@ mod tests {
fn store_single_login_with_multi() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -478,7 +471,7 @@ mod tests {
fn store_twice_for_the_same_id_fails() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -486,7 +479,7 @@ mod tests {
// Store the first login
store_login_at_file(
&wallet_file,
account1.unchecked_clone_inner(),
account1.clone(),
hd_path.clone(),
id1.clone(),
&password,
@@ -495,13 +488,7 @@ mod tests {
// and storing the same id again fails
assert!(matches!(
store_login_at_file(
&wallet_file,
account1.into_cloned_inner(),
hd_path,
id1,
&password,
),
store_login_at_file(&wallet_file, account1, hd_path, id1, &password,),
Err(BackendError::WalletLoginIdAlreadyExists),
));
}
@@ -510,7 +497,7 @@ mod tests {
fn store_twice_for_the_same_id_fails_with_multiple() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -542,20 +529,13 @@ mod tests {
fn load_with_wrong_password_fails() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let bad_password = UserPassword::new("bad-password".to_string());
let id1 = LoginId::new("first".to_string());
store_login_at_file(
&wallet_file,
account1.into_cloned_inner(),
hd_path,
id1.clone(),
&password,
)
.unwrap();
store_login_at_file(&wallet_file, account1, hd_path, id1.clone(), &password).unwrap();
// Trying to load it with wrong password now fails
assert!(matches!(
@@ -568,7 +548,7 @@ mod tests {
fn load_with_wrong_password_fails_with_multi() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let bad_password = UserPassword::new("bad-password".to_string());
@@ -594,20 +574,13 @@ mod tests {
fn load_with_wrong_id_fails() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
let id2 = LoginId::new("second".to_string());
store_login_at_file(
&wallet_file,
account1.into_cloned_inner(),
hd_path,
id1,
&password,
)
.unwrap();
store_login_at_file(&wallet_file, account1, hd_path, id1, &password).unwrap();
// Trying to load with the wrong id
assert!(matches!(
@@ -620,7 +593,7 @@ mod tests {
fn load_with_wrong_id_fails_with_multi() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -640,14 +613,14 @@ mod tests {
fn store_and_load_a_single_login() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
store_login_at_file(
&wallet_file,
account1.unchecked_clone_inner(),
account1.clone(),
hd_path.clone(),
id1.clone(),
&password,
@@ -656,7 +629,7 @@ mod tests {
let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap();
let acc = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(account1.as_ref(), acc.mnemonic());
assert_eq!(&account1, acc.mnemonic());
assert_eq!(&hd_path, acc.hd_path());
}
@@ -664,7 +637,7 @@ mod tests {
fn store_and_load_a_single_login_with_multi() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let acc1 = ZeroizeMnemonicWrapper::generate_random();
let acc1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -685,7 +658,7 @@ mod tests {
.get_account(&DEFAULT_FIRST_ACCOUNT_NAME.into())
.unwrap();
assert_eq!(account.id().as_ref(), DEFAULT_FIRST_ACCOUNT_NAME);
assert_eq!(account.mnemonic(), acc1.as_ref());
assert_eq!(account.mnemonic(), &acc1);
assert_eq!(account.hd_path(), &hd_path);
}
@@ -693,8 +666,8 @@ mod tests {
fn store_a_second_login_with_a_different_password_fails() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let bad_password = UserPassword::new("bad-password".to_string());
@@ -703,7 +676,7 @@ mod tests {
store_login_at_file(
&wallet_file,
account1.into_cloned_inner(),
account1,
cosmos_hd_path.clone(),
id1,
&password,
@@ -712,13 +685,7 @@ mod tests {
// Can't store a second login if you use different password
assert!(matches!(
store_login_at_file(
&wallet_file,
account2.into_cloned_inner(),
cosmos_hd_path,
id2,
&bad_password
),
store_login_at_file(&wallet_file, account2, cosmos_hd_path, id2, &bad_password),
Err(BackendError::WalletDifferentPasswordDetected),
));
}
@@ -727,8 +694,8 @@ mod tests {
fn store_a_second_login_with_a_different_password_fails_with_multi() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let bad_password = UserPassword::new("bad-password".to_string());
@@ -761,8 +728,8 @@ mod tests {
fn store_two_mnemonic_accounts_gives_different_salts_and_iv() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let different_hd_path: DerivationPath = "m".parse().unwrap();
let password = UserPassword::new("password".to_string());
@@ -770,14 +737,7 @@ mod tests {
let id2 = LoginId::new("second".to_string());
// Store the first account
store_login_at_file(
&wallet_file,
account1.into_cloned_inner(),
hd_path,
id1,
&password,
)
.unwrap();
store_login_at_file(&wallet_file, account1, hd_path, id1, &password).unwrap();
let stored_wallet = load_existing_wallet_at_file(&wallet_file).unwrap();
let encrypted_blob = &stored_wallet
@@ -790,14 +750,7 @@ mod tests {
let original_salt = encrypted_blob.salt().to_vec();
// Add an extra account
store_login_at_file(
&wallet_file,
account2.into_cloned_inner(),
different_hd_path,
id2,
&password,
)
.unwrap();
store_login_at_file(&wallet_file, account2, different_hd_path, id2, &password).unwrap();
let loaded_accounts = load_existing_wallet_at_file(&wallet_file).unwrap();
assert_eq!(loaded_accounts.len(), 2);
@@ -815,8 +768,8 @@ mod tests {
fn store_two_mnemonic_accounts_using_two_logins() {
let store_dir = tempdir().unwrap();
let wallet = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let different_hd_path: DerivationPath = "m".parse().unwrap();
let password = UserPassword::new("password".to_string());
@@ -826,7 +779,7 @@ mod tests {
// Store the first account
store_login_at_file(
&wallet,
account1.unchecked_clone_inner(),
account1.clone(),
cosmos_hd_path.clone(),
id1.clone(),
&password,
@@ -835,13 +788,13 @@ mod tests {
let login = load_existing_login_at_file(&wallet, &id1, &password).unwrap();
let acc = login.as_mnemonic_account().unwrap();
assert_eq!(account1.as_ref(), acc.mnemonic());
assert_eq!(&account1, acc.mnemonic());
assert_eq!(&cosmos_hd_path, acc.hd_path());
// Add an extra account
store_login_at_file(
&wallet,
account2.unchecked_clone_inner(),
account2.clone(),
different_hd_path.clone(),
id2.clone(),
&password,
@@ -851,12 +804,12 @@ mod tests {
// first account should be unchanged
let loaded_login = load_existing_login_at_file(&wallet, &id1, &password).unwrap();
let acc1 = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(account1.as_ref(), acc1.mnemonic());
assert_eq!(&account1, acc1.mnemonic());
assert_eq!(&cosmos_hd_path, acc1.hd_path());
let loaded_login = load_existing_login_at_file(&wallet, &id2, &password).unwrap();
let acc2 = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(account2.as_ref(), acc2.mnemonic());
assert_eq!(&account2, acc2.mnemonic());
assert_eq!(&different_hd_path, acc2.hd_path());
}
@@ -864,8 +817,8 @@ mod tests {
fn store_one_mnemonic_account_and_one_multi_account() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let different_hd_path: DerivationPath = "m".parse().unwrap();
let password = UserPassword::new("password".to_string());
@@ -875,7 +828,7 @@ mod tests {
// Store the first account
store_login_at_file(
&wallet_file,
account1.unchecked_clone_inner(),
account1.clone(),
hd_path.clone(),
id1.clone(),
&password,
@@ -884,7 +837,7 @@ mod tests {
let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap();
let acc = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(account1.as_ref(), acc.mnemonic());
assert_eq!(&account1, acc.mnemonic());
assert_eq!(&hd_path, acc.hd_path());
// Add an extra account
@@ -900,7 +853,7 @@ mod tests {
// first account should be unchanged
let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap();
let acc1 = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(account1.as_ref(), acc1.mnemonic());
assert_eq!(&account1, acc1.mnemonic());
assert_eq!(&hd_path, acc1.hd_path());
let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap();
@@ -910,7 +863,7 @@ mod tests {
.get_account(&DEFAULT_FIRST_ACCOUNT_NAME.into())
.unwrap();
assert_eq!(account.id().as_ref(), DEFAULT_FIRST_ACCOUNT_NAME);
assert_eq!(account.mnemonic(), account2.as_ref());
assert_eq!(account.mnemonic(), &account2);
assert_eq!(account.hd_path(), &different_hd_path);
}
@@ -918,7 +871,7 @@ mod tests {
fn remove_non_existent_id_fails() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -938,8 +891,8 @@ mod tests {
fn store_and_remove_wallet_login_information() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let different_hd_path: DerivationPath = "m".parse().unwrap();
let password = UserPassword::new("password".to_string());
@@ -949,7 +902,7 @@ mod tests {
// Store two accounts with two different passwords
store_login_at_file(
&wallet_file,
account1.unchecked_clone_inner(),
account1.clone(),
cosmos_hd_path.clone(),
id1.clone(),
&password,
@@ -957,7 +910,7 @@ mod tests {
.unwrap();
store_login_at_file(
&wallet_file,
account2.unchecked_clone_inner(),
account2.clone(),
different_hd_path.clone(),
id2.clone(),
&password,
@@ -967,12 +920,12 @@ mod tests {
// Load and compare
let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap();
let acc1 = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(account1.as_ref(), acc1.mnemonic());
assert_eq!(&account1, acc1.mnemonic());
assert_eq!(&cosmos_hd_path, acc1.hd_path());
let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap();
let acc2 = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(account2.as_ref(), acc2.mnemonic());
assert_eq!(&account2, acc2.mnemonic());
assert_eq!(&different_hd_path, acc2.hd_path());
// Delete the second account
@@ -981,7 +934,7 @@ mod tests {
// The first account should be unchanged
let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap();
let acc1 = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(account1.as_ref(), acc1.mnemonic());
assert_eq!(&account1, acc1.mnemonic());
assert_eq!(&cosmos_hd_path, acc1.hd_path());
// And we can't load the second one anymore
@@ -1008,8 +961,8 @@ mod tests {
fn append_account_converts_the_type() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -1017,7 +970,7 @@ mod tests {
store_login_at_file(
&wallet_file,
account1.unchecked_clone_inner(),
account1.clone(),
hd_path.clone(),
id1.clone(),
&password,
@@ -1027,7 +980,7 @@ mod tests {
// Check that it's there as the correct non-multiple type
let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap();
let acc = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(acc.mnemonic(), account1.as_ref());
assert_eq!(acc.mnemonic(), &account1);
assert_eq!(acc.hd_path(), &hd_path);
append_account_to_login_at_file(
@@ -1044,14 +997,8 @@ mod tests {
let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap();
let loaded_accounts = loaded_login.as_multiple_accounts().unwrap();
let expected = vec![
WalletAccount::new(
id1.into(),
MnemonicAccount::new(account1.into_cloned_inner(), hd_path.clone()),
),
WalletAccount::new(
id2,
MnemonicAccount::new(account2.into_cloned_inner(), hd_path),
),
WalletAccount::new(id1.into(), MnemonicAccount::new(account1, hd_path.clone())),
WalletAccount::new(id2, MnemonicAccount::new(account2, hd_path)),
]
.into();
assert_eq!(loaded_accounts, &expected);
@@ -1061,10 +1008,10 @@ mod tests {
fn append_accounts_to_existing_login() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account3 = ZeroizeMnemonicWrapper::generate_random();
let account4 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let account3 = Mnemonic::generate(24).unwrap();
let account4 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -1074,7 +1021,7 @@ mod tests {
store_login_at_file(
&wallet_file,
account1.unchecked_clone_inner(),
account1.clone(),
hd_path.clone(),
id1.clone(),
&password,
@@ -1083,7 +1030,7 @@ mod tests {
store_login_at_file(
&wallet_file,
account2.unchecked_clone_inner(),
account2.clone(),
hd_path.clone(),
id2.clone(),
&password,
@@ -1093,7 +1040,7 @@ mod tests {
// Check that it's there as the correct non-multiple type
let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap();
let acc2 = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(acc2.mnemonic(), account2.as_ref());
assert_eq!(acc2.mnemonic(), &account2);
assert_eq!(acc2.hd_path(), &hd_path);
// Add a third and fourth mnenonic grouped together with the second one
@@ -1119,24 +1066,15 @@ mod tests {
// Check that we can load all four
let loaded_login = load_existing_login_at_file(&wallet_file, &id1, &password).unwrap();
let acc1 = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(acc1.mnemonic(), account1.as_ref());
assert_eq!(acc1.mnemonic(), &account1);
assert_eq!(acc1.hd_path(), &hd_path);
let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap();
let loaded_accounts = loaded_login.as_multiple_accounts().unwrap();
let expected = vec![
WalletAccount::new(
id2.into(),
MnemonicAccount::new(account2.into_cloned_inner(), hd_path.clone()),
),
WalletAccount::new(
id3,
MnemonicAccount::new(account3.into_cloned_inner(), hd_path.clone()),
),
WalletAccount::new(
id4,
MnemonicAccount::new(account4.into_cloned_inner(), hd_path),
),
WalletAccount::new(id2.into(), MnemonicAccount::new(account2, hd_path.clone())),
WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path.clone())),
WalletAccount::new(id4, MnemonicAccount::new(account4, hd_path)),
]
.into();
assert_eq!(loaded_accounts, &expected);
@@ -1146,10 +1084,10 @@ mod tests {
fn append_accounts_to_existing_login_with_multi() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account3 = ZeroizeMnemonicWrapper::generate_random();
let account4 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let account3 = Mnemonic::generate(24).unwrap();
let account4 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -1200,7 +1138,7 @@ mod tests {
let loaded_accounts = loaded_login.as_multiple_accounts().unwrap();
let expected = vec![WalletAccount::new(
DEFAULT_FIRST_ACCOUNT_NAME.into(),
MnemonicAccount::new(account1.into_cloned_inner(), hd_path.clone()),
MnemonicAccount::new(account1, hd_path.clone()),
)]
.into();
assert_eq!(loaded_accounts, &expected);
@@ -1210,16 +1148,10 @@ mod tests {
let expected = vec![
WalletAccount::new(
DEFAULT_FIRST_ACCOUNT_NAME.into(),
MnemonicAccount::new(account2.into_cloned_inner(), hd_path.clone()),
),
WalletAccount::new(
id3,
MnemonicAccount::new(account3.into_cloned_inner(), hd_path.clone()),
),
WalletAccount::new(
id4,
MnemonicAccount::new(account4.into_cloned_inner(), hd_path),
MnemonicAccount::new(account2, hd_path.clone()),
),
WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path.clone())),
WalletAccount::new(id4, MnemonicAccount::new(account4, hd_path)),
]
.into();
assert_eq!(loaded_accounts, &expected);
@@ -1229,7 +1161,7 @@ mod tests {
fn append_the_same_mnemonic_twice_fails() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -1254,21 +1186,14 @@ mod tests {
fn delete_the_same_account_twice_for_a_login_fails() {
let store_dir = tempdir().unwrap();
let wallet = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
let id2 = AccountId::new("second".to_string());
store_login_at_file(
&wallet,
account1.into_cloned_inner(),
hd_path.clone(),
id1.clone(),
&password,
)
.unwrap();
store_login_at_file(&wallet, account1, hd_path.clone(), id1.clone(), &password).unwrap();
append_account_to_login_at_file(
&wallet,
@@ -1292,8 +1217,8 @@ mod tests {
fn delete_the_same_account_twice_for_a_login_fails_with_multi() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -1330,9 +1255,9 @@ mod tests {
fn delete_appended_account_doesnt_affect_others() {
let store_dir = tempdir().unwrap();
let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account3 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let account3 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -1341,7 +1266,7 @@ mod tests {
store_login_at_file(
&wallet_file,
account1.into_cloned_inner(),
account1,
hd_path.clone(),
id1.clone(),
&password,
@@ -1350,7 +1275,7 @@ mod tests {
store_login_at_file(
&wallet_file,
account2.unchecked_clone_inner(),
account2.clone(),
hd_path.clone(),
id2.clone(),
&password,
@@ -1373,14 +1298,8 @@ mod tests {
let loaded_login = load_existing_login_at_file(&wallet_file, &id2, &password).unwrap();
let loaded_accounts = loaded_login.as_multiple_accounts().unwrap();
let expected = vec![
WalletAccount::new(
id2.into(),
MnemonicAccount::new(account2.into_cloned_inner(), hd_path.clone()),
),
WalletAccount::new(
id3,
MnemonicAccount::new(account3.into_cloned_inner(), hd_path),
),
WalletAccount::new(id2.into(), MnemonicAccount::new(account2, hd_path.clone())),
WalletAccount::new(id3, MnemonicAccount::new(account3, hd_path)),
]
.into();
assert_eq!(loaded_accounts, &expected);
@@ -1390,8 +1309,8 @@ mod tests {
fn remove_all_accounts_for_a_login_removes_the_file_when_empty() {
let store_dir = tempdir().unwrap();
let wallet = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -1439,9 +1358,9 @@ mod tests {
fn remove_all_accounts_for_a_login_removes_that_login() {
let store_dir = tempdir().unwrap();
let wallet = store_dir.path().join(WALLET_INFO_FILENAME);
let account1 = ZeroizeMnemonicWrapper::generate_random();
let account2 = ZeroizeMnemonicWrapper::generate_random();
let account3 = ZeroizeMnemonicWrapper::generate_random();
let account1 = Mnemonic::generate(24).unwrap();
let account2 = Mnemonic::generate(24).unwrap();
let account3 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -1496,7 +1415,7 @@ mod tests {
let acc3 = loaded_login.as_multiple_accounts().unwrap();
let expected = vec![WalletAccount::new(
DEFAULT_FIRST_ACCOUNT_NAME.into(),
MnemonicAccount::new(account3.into_cloned_inner(), hd_path),
MnemonicAccount::new(account3, hd_path),
)]
.into();
assert_eq!(acc3, &expected);
@@ -1506,10 +1425,10 @@ mod tests {
fn append_accounts_and_remove_appended_accounts() {
let store_dir = tempdir().unwrap();
let wallet = store_dir.path().join(WALLET_INFO_FILENAME);
let acc1 = ZeroizeMnemonicWrapper::generate_random();
let acc2 = ZeroizeMnemonicWrapper::generate_random();
let acc3 = ZeroizeMnemonicWrapper::generate_random();
let acc4 = ZeroizeMnemonicWrapper::generate_random();
let acc1 = Mnemonic::generate(24).unwrap();
let acc2 = Mnemonic::generate(24).unwrap();
let acc3 = Mnemonic::generate(24).unwrap();
let acc4 = Mnemonic::generate(24).unwrap();
let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap();
let password = UserPassword::new("password".to_string());
let id1 = LoginId::new("first".to_string());
@@ -1519,7 +1438,7 @@ mod tests {
store_login_at_file(
&wallet,
acc1.unchecked_clone_inner(),
acc1.clone(),
hd_path.clone(),
id1.clone(),
&password,
@@ -1528,7 +1447,7 @@ mod tests {
store_login_at_file(
&wallet,
acc2.unchecked_clone_inner(),
acc2.clone(),
hd_path.clone(),
id2.clone(),
&password,
@@ -1564,12 +1483,9 @@ mod tests {
let expected = vec![
WalletAccount::new(
id2.clone().into(),
MnemonicAccount::new(acc2.into_cloned_inner(), hd_path.clone()),
),
WalletAccount::new(
id4.clone(),
MnemonicAccount::new(acc4.into_cloned_inner(), hd_path.clone()),
MnemonicAccount::new(acc2, hd_path.clone()),
),
WalletAccount::new(id4.clone(), MnemonicAccount::new(acc4, hd_path.clone())),
]
.into();
assert_eq!(loaded_accounts, &expected);
@@ -1585,7 +1501,7 @@ mod tests {
// The first login is still available
let loaded_login = load_existing_login_at_file(&wallet, &id1, &password).unwrap();
let account = loaded_login.as_mnemonic_account().unwrap();
assert_eq!(account.mnemonic(), acc1.as_ref());
assert_eq!(account.mnemonic(), &acc1);
assert_eq!(account.hd_path(), &hd_path);
}
@@ -1,13 +1,12 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use serde::{Deserialize, Serialize};
use std::fmt;
use serde::{Deserialize, Deserializer, Serialize};
use zeroize::Zeroize;
use zeroize::{Zeroize, Zeroizing};
// The `LoginId` is the top level id in the wallet file, and is not stored encrypted
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Zeroize)]
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub(crate) struct LoginId(String);
impl LoginId {
@@ -81,26 +80,4 @@ impl fmt::Display for AccountId {
}
// simple wrapper for String that will get zeroized on drop
#[derive(Zeroize)]
#[zeroize(drop)]
pub struct UserPassword(String);
impl UserPassword {
#[cfg(test)]
pub(crate) fn new(inner: String) -> Self {
UserPassword(inner)
}
pub(crate) fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
}
impl<'de> Deserialize<'de> for UserPassword {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
Ok(UserPassword(String::deserialize(deserializer)?))
}
}
pub type UserPassword = Zeroizing<String>;
@@ -22,12 +22,12 @@ export const MultiAccountWithPwdHowTo = ({ show, handleClose }: { show: boolean;
>
<Stack spacing={2}>
<Warning sx={{ textAlign: 'center' }}>
<Typography fontWeight={600} sx={{ mb: 1 }}>
<Typography variant="body2" fontWeight={600} sx={{ mb: 1 }}>
This machine already has a password set on it
</Typography>
<Typography>
<Typography variant="caption">
In order to import or create account(s) you need to log in with your password or create a new one. Creating a
new password will overwrite any old one. Make sure your menonics are all wirtten down before creating a new
new password will overwrite any old one. Make sure your mnemonics are all written down before creating a new
password.
</Typography>
</Warning>
@@ -35,13 +35,14 @@ export const MultiAccountWithPwdHowTo = ({ show, handleClose }: { show: boolean;
{passwordCreationSteps.map((step, index) => (
<Stack key={step} direction="row" spacing={1}>
<Typography fontWeight={600}>{`${index + 1}.`}</Typography>
<Typography>{`${step}`}</Typography>
<Typography variant="body2">{`${step}`}</Typography>
</Stack>
))}
<Link
href="https://nymtech.net/docs/stable/wallet#importing-or-creating-accounts-when-you-have-signed-in-with-mnemonic-but-a-password-already-exists-on-your-machine"
target="_blank"
text="Open Nym docs for this guide in a browser window"
variant="body2"
fontWeight={600}
/>
</Stack>
+14 -3
View File
@@ -1,8 +1,18 @@
import React, { useState } from 'react';
import { Alert as MuiAlert, IconButton } from '@mui/material';
import { Alert as MuiAlert, IconButton, SxProps } from '@mui/material';
import { Close } from '@mui/icons-material';
export const Alert = ({ title, dismissable }: { title: string | React.ReactNode; dismissable?: boolean }) => {
export const Alert = ({
title,
dismissable,
sxAlert,
bgColor,
}: {
title: string | React.ReactNode;
dismissable?: boolean;
sxAlert?: SxProps;
bgColor?: string;
}) => {
const [displayAlert, setDisplayAlert] = useState(true);
const handleDismiss = () => setDisplayAlert(false);
@@ -14,9 +24,10 @@ export const Alert = ({ title, dismissable }: { title: string | React.ReactNode;
sx={{
width: '100%',
borderRadius: 0,
bgcolor: 'background.default',
bgcolor: bgColor || 'background.default',
color: (theme) => theme.palette.nym.nymWallet.text.blue,
'& .MuiAlert-icon': { color: 'nym.nymWallet.text.blue', mr: 1 },
...sxAlert,
}}
action={
dismissable && (
+7 -1
View File
@@ -1,4 +1,5 @@
import React from 'react';
import { Link } from '@nymproject/react/link/Link';
import { Box, Button, Typography } from '@mui/material';
import { NymCard } from '../NymCard';
@@ -18,7 +19,12 @@ export const Bond = ({
justifyContent: 'space-between',
}}
>
<Typography variant="body2">Bond a mixnode or a gateway</Typography>
<Typography variant="body2">
Bond a mix node or a gateway. Learn how to set up and run a node{' '}
<Link href="https://nymtech.net/docs/nodes/setup-guides.html" target="_blank">
here
</Link>
</Typography>
<Box
sx={{
display: 'flex',
@@ -42,6 +42,7 @@ const GatewayInitForm = ({
initialValue={gatewayData?.identityKey}
errorText={errors.identityKey?.message}
onChanged={(value) => setValue('identityKey', value)}
showTickOnValid={false}
/>
<TextField
{...register('sphinxKey')}
@@ -36,6 +36,7 @@ const MixnodeInitForm = ({ mixnodeData, onNext }: { mixnodeData: MixnodeData; on
initialValue={mixnodeData?.identityKey}
errorText={errors.identityKey?.message}
onChanged={(value) => setValue('identityKey', value)}
showTickOnValid={false}
/>
<TextField
{...register('sphinxKey')}
+1 -1
View File
@@ -40,7 +40,7 @@ export const ClientAddressDisplay: FC<ClientAddressProps & { address?: string }>
)}
<AddressTooltip address={address} visible={!showEntireAddress}>
<Typography variant="body2" component="span" sx={{ mr: 1, color: 'text.primary', fontWeight: 400 }}>
<Typography variant="body2" component="span" sx={{ color: 'text.primary', fontWeight: 400 }}>
{showEntireAddress ? address || '' : splice(6, address)}
</Typography>
</AddressTooltip>
@@ -257,6 +257,7 @@ export const DelegateModal: FCWithChildren<{
textFieldProps={{
autoFocus: !initialIdentityKey,
}}
showTickOnValid={false}
/>
</Box>
<Typography
@@ -277,16 +278,9 @@ export const DelegateModal: FCWithChildren<{
autoFocus={Boolean(initialIdentityKey)}
onChanged={handleAmountChanged}
denom={denom}
validationError={errorAmount}
/>
</Box>
<Typography
component="div"
textAlign="left"
variant="caption"
sx={{ color: 'error.main', mx: 2, mt: errorAmount && 1 }}
>
{errorAmount}
</Typography>
<Box sx={{ mt: 3 }}>
<ModalListItem label="Account balance" value={accountBalance?.toUpperCase()} divider fontWeight={600} />
</Box>
@@ -0,0 +1,20 @@
import * as React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { Box } from '@mui/material';
import { MockMainContextProvider } from '../context/mocks/main';
import { NetworkSelector } from './NetworkSelector';
export default {
title: 'Wallet / Network Selector',
component: NetworkSelector,
} as ComponentMeta<typeof NetworkSelector>;
const Template: ComponentStory<typeof NetworkSelector> = () => (
<Box mt={2} height={800}>
<MockMainContextProvider>
<NetworkSelector />
</MockMainContextProvider>
</Box>
);
export const Default = Template.bind({});
+25 -6
View File
@@ -1,6 +1,6 @@
import React, { useState, useContext } from 'react';
import { Button, List, ListItem, ListItemIcon, ListItemText, ListSubheader, Popover } from '@mui/material';
import { ArrowDropDown, CheckSharp } from '@mui/icons-material';
import { Button, List, ListItemButton, ListItemIcon, ListItemText, ListSubheader, Popover, Stack } from '@mui/material';
import { ArrowDropDown, Check } from '@mui/icons-material';
import { Network } from 'src/types';
import { AppContext } from '../context/main';
import { config } from '../config';
@@ -16,10 +16,29 @@ const NetworkItem: FCWithChildren<{ title: string; isSelected: boolean; onSelect
isSelected,
onSelect,
}) => (
<ListItem button onClick={onSelect}>
<ListItemIcon>{isSelected && <CheckSharp color="success" />}</ListItemIcon>
<ListItemText>{title}</ListItemText>
</ListItem>
<ListItemButton
onClick={onSelect}
sx={{
minWidth: '180px',
'&:hover': {
backgroundColor: isSelected ? 'rgba(251, 110, 78, 0.08) !important' : undefined,
},
}}
>
<Stack direction="row" justifyContent="space-between" alignItems="center" gap={2} width="100%">
<ListItemText
primaryTypographyProps={{
color: isSelected ? 'primary' : undefined,
}}
primary={title}
/>
{isSelected && (
<ListItemIcon sx={{ justifyContent: 'flex-end' }}>
<Check color="primary" fontSize="small" />
</ListItemIcon>
)}
</Stack>
</ListItemButton>
);
export const NetworkSelector = () => {
@@ -19,7 +19,6 @@ export const ReceiveModal = ({
return (
<SimpleModal
header="Receive"
subHeader="Provide your address to receive tokens"
open
onClose={onClose}
okLabel=""
@@ -128,7 +128,7 @@ export const SendInputModal = ({
<Stack gap={0.5} sx={{ mt: 1 }}>
<ModalListItem label="Account balance" value={balance?.toUpperCase()} divider fontWeight={600} />
<Typography fontSize="smaller" sx={{ color: 'text.primary' }}>
Est. fee for this transaction will be show on the next page
Est. fee for this transaction will be shown on the next page
</Typography>
</Stack>
<FormControlLabel
@@ -139,7 +139,7 @@ export const SendInputModal = ({
{showMore && (
<Stack direction="column" gap={3} mt={2} mb={3}>
<CurrencyFormField
label="Fees"
label="Fee"
onChanged={(v) => onUserFeesChange(v)}
initialValue={userFees?.amount}
fullWidth
+1 -1
View File
@@ -77,7 +77,7 @@ export const SendModal = ({ onClose, hasStorybookStyles }: { onClose: () => void
} catch (e) {
Console.error(e as string);
if (/Raw log: out of gas/.test(e as string)) {
setGasError('Out of gas, please increase the amount of fees');
setGasError('Specified fee was too small. Please increase the amount and try again');
} else {
setSendError(true);
}
@@ -0,0 +1,24 @@
import * as React from 'react';
import { ComponentMeta, ComponentStory } from '@storybook/react';
import { Box } from '@mui/material';
import { TokenPoolSelector } from './TokenPoolSelector';
import { MockMainContextProvider } from '../context/mocks/main';
export default {
title: 'Wallet / Token pool',
component: TokenPoolSelector,
} as ComponentMeta<typeof TokenPoolSelector>;
const Template: ComponentStory<typeof TokenPoolSelector> = (args) => (
<Box mt={2} height={800}>
<MockMainContextProvider>
<TokenPoolSelector {...args} />
</MockMainContextProvider>
</Box>
);
export const Default = Template.bind({});
Default.args = {
disabled: false,
onSelect: () => {},
};
+31 -12
View File
@@ -1,5 +1,15 @@
import React, { useContext, useEffect, useState } from 'react';
import { FormControl, InputLabel, ListItemText, MenuItem, Select, SelectChangeEvent, Typography } from '@mui/material';
import {
FormControl,
InputLabel,
ListItemText,
MenuItem,
Select,
SelectChangeEvent,
Stack,
Typography,
} from '@mui/material';
import { Check as CheckIcon } from '@mui/icons-material';
import { AppContext } from '../context/main';
export type TPoolOption = 'balance' | 'locked';
@@ -40,20 +50,29 @@ export const TokenPoolSelector: FCWithChildren<{ disabled: boolean; onSelect: (p
renderValue={(val) => <Typography sx={{ textTransform: 'capitalize' }}>{val}</Typography>}
>
<MenuItem value="balance">
<ListItemText
primary="Balance"
secondary={`${balance?.printable_balance}`}
secondaryTypographyProps={{ sx: { textTransform: 'uppercase' } }}
/>
<Stack direction="row" alignItems="center" gap={2} width="100%">
<ListItemText
primary="Balance"
secondary={`${balance?.printable_balance}`}
secondaryTypographyProps={{ sx: { textTransform: 'uppercase', color: 'nym.text.muted' } }}
/>
{value === 'balance' && <CheckIcon fontSize="small" />}
</Stack>
</MenuItem>
<MenuItem value="locked">
{tokenAllocation && (
<ListItemText
primary="Locked"
secondary={`${
+tokenAllocation.locked + +tokenAllocation.spendable
} ${clientDetails?.display_mix_denom.toUpperCase()}`}
/>
<Stack direction="row" alignItems="center" gap={2} width="100%">
<ListItemText
primary="Locked"
secondary={`${
+tokenAllocation.locked + +tokenAllocation.spendable
} ${clientDetails?.display_mix_denom.toUpperCase()}`}
secondaryTypographyProps={{
sx: { textTransform: 'uppercase', color: 'nym.text.muted' },
}}
/>
{value === 'locked' && <CheckIcon fontSize="small" />}
</Stack>
)}
</MenuItem>
</Select>
@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import { Box, Button, Divider, Grid, TextField, Typography } from '@mui/material';
import { Button, Divider, Grid, Stack, TextField, Typography } from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { isMixnode } from 'src/types';
import { simulateUpdateMixnodeConfig, simulateVestingUpdateMixnodeConfig, updateMixnodeConfig } from 'src/requests';
@@ -62,7 +62,7 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
};
return (
<Grid container xs item>
<Grid container xs>
{fee && (
<ConfirmTx
open
@@ -76,14 +76,17 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
{isSubmitting && <LoadingModal />}
<Alert
title={
<Box sx={{ fontWeight: 600 }}>
Changing these values will ONLY change the data about your node on the blockchain. Remember to change your
nodes config file with the same values too
</Box>
<Stack>
<Typography fontWeight={600}>
Changing these values will ONLY change the data about your node on the blockchain.
</Typography>
<Typography>Remember to change your nodes config file with the same values too.</Typography>
</Stack>
}
bgColor={`${theme.palette.nym.nymWallet.text.blue}0D !important`}
dismissable
/>
<Grid container>
<Grid container mt={2}>
<Grid item container direction="row" alignItems="left" justifyContent="space-between" padding={3}>
<Grid item>
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
@@ -126,7 +129,7 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
</Grid>
</Grid>
</Grid>
<Divider flexItem />
<Divider sx={{ width: '100%' }} />
<Grid item container direction="row" alignItems="left" justifyContent="space-between" padding={3}>
<Grid item>
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
@@ -147,7 +150,7 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
</Grid>
</Grid>
</Grid>
<Divider flexItem />
<Divider sx={{ width: '100%' }} />
<Grid item container direction="row" alignItems="left" justifyContent="space-between" padding={3}>
<Grid item>
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
@@ -168,31 +171,35 @@ export const InfoSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBon
</Grid>
</Grid>
</Grid>
<Divider flexItem />
<Grid container justifyContent="end">
<Button
size="large"
variant="contained"
disabled={isSubmitting || !isDirty || !isValid}
onClick={handleSubmit((data) =>
getFee(bondedNode.proxy ? simulateVestingUpdateMixnodeConfig : simulateUpdateMixnodeConfig, {
host: data.host,
mix_port: data.mixPort,
verloc_port: data.verlocPort,
http_api_port: data.httpApiPort,
version: data.version,
}),
)}
sx={{ m: 3 }}
>
Submit changes to the blockchain
</Button>
<Divider sx={{ width: '100%' }} />
<Grid item container direction="row" justifyContent="space-between" padding={3}>
<Grid item />
<Grid spacing={3} item container alignItems="center" xs={12} md={6}>
<Button
size="large"
variant="contained"
disabled={isSubmitting || !isDirty || !isValid}
onClick={handleSubmit((data) =>
getFee(bondedNode.proxy ? simulateVestingUpdateMixnodeConfig : simulateUpdateMixnodeConfig, {
host: data.host,
mix_port: data.mixPort,
verloc_port: data.verlocPort,
http_api_port: data.httpApiPort,
version: data.version,
}),
)}
sx={{ m: 3, mr: 0 }}
fullWidth
>
Submit changes to the blockchain
</Button>
</Grid>
</Grid>
</Grid>
<SimpleModal
open={openConfirmationModal}
header="Your changes are submitted to the blockchain"
subHeader="Remember to change the values
subHeader="Remember to change the values
on your nodes config file too."
okLabel="close"
hideCloseIcon
@@ -1,7 +1,18 @@
import React, { useContext, useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
import { Box, Button, Divider, FormHelperText, Grid, InputAdornment, TextField, Typography } from '@mui/material';
import {
Box,
Button,
Divider,
FormHelperText,
Grid,
InputAdornment,
Stack,
TextField,
Tooltip,
Typography,
} from '@mui/material';
import { useTheme } from '@mui/material/styles';
import { CurrencyDenom, MixNodeCostParams } from '@nymproject/types';
import { CurrencyFormField } from '@nymproject/react/currency/CurrencyFormField';
@@ -24,6 +35,12 @@ import { AppContext } from 'src/context';
import { useGetFee } from 'src/hooks/useGetFee';
import { ConfirmTx } from 'src/components/ConfirmTX';
import { LoadingModal } from 'src/components/Modals/LoadingModal';
import { InfoOutlined } from '@mui/icons-material';
const operatorCostHint = `This is your (operator) rewards including the PM and cost. Rewards are automatically compounded every epoch.You can redeem your rewards at any time.
`;
const profitMarginHint =
'PM is the percentage of the node rewards that you as the node operator take before rewards are distributed to the delegators.';
export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode }): JSX.Element => {
const [openConfirmationModal, setOpenConfirmationModal] = useState<boolean>(false);
@@ -114,10 +131,9 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
<Grid
container
xs
item
sx={{
'& .MuiGrid-item': {
pl: 0,
pl: 3,
},
}}
>
@@ -132,13 +148,29 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
/>
)}
{isSubmitting && <LoadingModal />}
<Alert title={<Box component="span" sx={{ fontWeight: 600 }}>{`Next interval: ${intervalTime}`}</Box>} />
<Grid container direction="column">
<Alert
title={<Typography sx={{ fontWeight: 600 }}>{`Next interval: ${intervalTime}`}</Typography>}
bgColor={`${theme.palette.nym.nymWallet.text.blue}0D !important`}
sxAlert={{
icon: false as unknown as number,
'& .MuiAlert-message': {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
},
}}
/>
<Grid container direction="column" mt={2}>
<Grid item container alignItems="left" justifyContent="space-between" padding={3} spacing={1}>
<Grid item xl={6}>
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
Profit Margin
</Typography>
<Tooltip title={profitMarginHint} placement="top-end">
<Stack flexDirection="row" gap={0.5}>
<InfoOutlined fontSize="inherit" />
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
Profit Margin
</Typography>
</Stack>
</Tooltip>
<Typography
variant="body1"
sx={{
@@ -180,12 +212,17 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
</Grid>
)}
</Grid>
<Divider flexItem sx={{ position: 'relative', left: '-24px', width: 'calc(100% + 24px)' }} />
<Divider sx={{ width: '100%' }} />
<Grid item container direction="row" alignItems="left" justifyContent="space-between" padding={3} spacing={1}>
<Grid item>
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
Operating cost
</Typography>
<Tooltip title={operatorCostHint} placement="top-end">
<Stack flexDirection="row" gap={0.5}>
<InfoOutlined fontSize="inherit" />
<Typography variant="body1" sx={{ fontWeight: 600, mb: 1 }}>
Operator cost
</Typography>
</Stack>
</Tooltip>
<Typography
variant="body1"
sx={{
@@ -201,7 +238,7 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
<CurrencyFormField
required
fullWidth
label="Operating cost"
label="Operator cost"
onChanged={(newValue) => {
setValue('operatorCost', newValue, { shouldValidate: true, shouldDirty: true });
}}
@@ -221,23 +258,27 @@ export const ParametersSettings = ({ bondedNode }: { bondedNode: TBondedMixnode
)}
</Grid>
</Grid>
<Divider flexItem sx={{ position: 'relative', left: '-24px', width: 'calc(100% + 24px)' }} />
<Grid container justifyContent="end">
<Button
size="large"
variant="contained"
disabled={isSubmitting || !isDirty || !isValid}
onClick={handleSubmit((data) => {
getFee(bondedNode.proxy ? simulateVestingUpdateMixnodeCostParams : simulateUpdateMixnodeCostParams, {
profit_margin_percent: (+data.profitMargin / 100).toString(),
interval_operating_cost: data.operatorCost,
});
})}
type="submit"
sx={{ m: 3 }}
>
Submit changes to the blockchain
</Button>
<Divider sx={{ width: '100%' }} />
<Grid item container direction="row" justifyContent="space-between" padding={3}>
<Grid item />
<Grid item xs={12} md={6}>
<Button
size="large"
variant="contained"
disabled={isSubmitting || !isDirty || !isValid}
onClick={handleSubmit((data) => {
getFee(bondedNode.proxy ? simulateVestingUpdateMixnodeCostParams : simulateUpdateMixnodeCostParams, {
profit_margin_percent: (+data.profitMargin / 100).toString(),
interval_operating_cost: data.operatorCost,
});
})}
type="submit"
sx={{ m: 3, mr: 0, ml: 0 }}
fullWidth
>
Save changes
</Button>
</Grid>
</Grid>
</Grid>
<SimpleModal
@@ -5,7 +5,7 @@ import { TBondedMixnode, TBondedGateway } from '../../../../../context/bonding';
import { InfoSettings } from './InfoSettings';
import { ParametersSettings } from './ParametersSettings';
const nodeGeneralNav = ['Info', 'Parameters'];
const nodeGeneralNav = ['Node info', 'Parameters'];
export const NodeGeneralSettings = ({ bondedNode }: { bondedNode: TBondedMixnode | TBondedGateway }) => {
const [settingsCard, setSettingsCard] = useState<string>(nodeGeneralNav[0]);
+18
View File
@@ -303,6 +303,24 @@ export const getDesignTokens = (mode: PaletteMode): ThemeOptions => {
},
},
},
MuiSelect: {
defaultProps: {
MenuProps: {
PaperProps: {
sx: {
'&& .Mui-selected': {
color: nymPalette.highlight,
backgroundColor: (t) =>
t.palette.mode === 'dark' ? `${t.palette.background.default} !important` : '#FFFFFF !important',
},
'&& .Mui-selected:hover': {
backgroundColor: 'rgba(251, 110, 78, 0.08) !important',
},
},
},
},
},
},
MuiMenu: {
styleOverrides: {
list: ({ _, theme }) => ({
@@ -1,5 +0,0 @@
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
@@ -0,0 +1,2 @@
transaction.png
node_modules/
@@ -0,0 +1,27 @@
{
"name": "sdk_wasm_tests",
"version": "1.0.2",
"license": "ISC",
"main": "index.js",
"scripts": {
"test": "mocha --timeout 10000 --require ts-node/register src/tests/*.spec.ts",
"build": "npm run",
"testlocal": "npm run build && npm run test"
},
"dependencies": {
"prettier": "^2.8.4",
"puppeteer": "^19.7.5"
},
"devDependencies": {
"@types/chai": "^4.2.18",
"@types/mocha": "^10.0.1",
"@types/node": "^18.15.3",
"@types/puppeteer": "^7.0.4",
"chai": "^4.3.4",
"mocha": "^10.2.0",
"mochawesome": "^7.1.3",
"puppeteer-debug": "^2.0.0",
"ts-node": "^10.0.0",
"typescript": "^4.3.4"
}
}
@@ -0,0 +1,3 @@
{
"localhost": "http://localhost:3000/"
}
@@ -0,0 +1,7 @@
{
"sender": "#sender",
"recipient": "#recipient",
"id": "#message",
"send_button": "#send-button",
"output": "#output"
}
@@ -0,0 +1,51 @@
import { expect } from 'chai';
const selectors = require('../selectors/attr.json');
import kermit from '../utils/kermit';
const config = require('../config/config.json');
import delay from '../utils/helper';
describe('run base test', async () => {
let browser;
let page;
before(async () => {
browser = await kermit();
page = await browser.newPage();
await page.goto(config.localhost);
await delay(5000);
});
after(async () => {
await page.screenshot({ path: 'transaction.png' });
await browser.close();
});
it('Validate an address can be pasted', async () => {
const senderaddress = await page.evaluate(() => {
return (document.querySelector('#sender') as HTMLInputElement).value;
});
await page.type(selectors.recipient, senderaddress);
});
it('Validate a mesage can be typed', async () => {
await page.click(selectors.id, { clickCount: 3 });
await page.type(selectors.id, "Hi, I'm a test");
});
it('Validate the message can be sent and received', async () => {
await page.click(selectors.send_button);
await delay(1500);
const sentmessage = await page.evaluate(() => {
return (document.querySelector('#output') as Element).firstChild.textContent;
});
const receivedmesage = await page.evaluate(() => {
return (document.querySelector('#output') as Element).lastChild.textContent;
});
console.log(receivedmesage);
console.log(sentmessage);
expect(receivedmesage).contains('received');
expect(sentmessage).contains('sent');
});
});
@@ -0,0 +1,5 @@
export default async function delay(time) {
return new Promise(function (resolve) {
setTimeout(resolve, time);
});
}
@@ -0,0 +1,10 @@
import * as puppeteer from 'puppeteer';
const defaultOptions = {
headless: true,
};
export default async (options = undefined) => {
const puppeterOptions = options === undefined ? defaultOptions : options;
return await puppeteer.launch(puppeterOptions);
};
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": true,
"noImplicitAny": false
},
"include": ["src/tests/*.spec.ts"]
}
+1 -1
View File
@@ -16,7 +16,7 @@ pretty_env_logger = "0.4"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1.11", features = [ "net", "rt-multi-thread", "macros", "signal"] }
bip39 = "1.0.1"
bip39 = { workspace = true }
anyhow = "1"
tap = "1"
@@ -20,13 +20,24 @@ pub(crate) async fn execute(
nym_cli_commands::validator::mixnet::delegators::MixnetDelegatorsCommands::Delegate(args) => {
nym_cli_commands::validator::mixnet::delegators::delegate_to_mixnode::delegate_to_mixnode(args, create_signing_client(global_args, network_details)?).await
}
nym_cli_commands::validator::mixnet::delegators::MixnetDelegatorsCommands::DelegateVesting(args) => {
nym_cli_commands::validator::mixnet::delegators::vesting_delegate_to_mixnode::vesting_delegate_to_mixnode(args, create_signing_client(global_args, network_details)?).await
}
nym_cli_commands::validator::mixnet::delegators::MixnetDelegatorsCommands::PledgeMore(args) => {
nym_cli_commands::validator::mixnet::delegators::pledge_more::pledge_more(args, create_signing_client(global_args, network_details)?).await
}
nym_cli_commands::validator::mixnet::delegators::MixnetDelegatorsCommands::PledgeMoreVesting(args) => {
nym_cli_commands::validator::mixnet::delegators::vesting_pledge_more::vesting_pledge_more(args, create_signing_client(global_args, network_details)?).await
}
nym_cli_commands::validator::mixnet::delegators::MixnetDelegatorsCommands::Undelegate(args) => {
nym_cli_commands::validator::mixnet::delegators::undelegate_from_mixnode::undelegate_from_mixnode(args, create_signing_client(global_args, network_details)?).await
}
nym_cli_commands::validator::mixnet::delegators::MixnetDelegatorsCommands::UndelegateVesting(args) => {
nym_cli_commands::validator::mixnet::delegators::vesting_undelegate_from_mixnode::vesting_undelegate_from_mixnode(args, create_signing_client(global_args, network_details)?).await
}
nym_cli_commands::validator::mixnet::delegators::MixnetDelegatorsCommands::List(args) => {
nym_cli_commands::validator::mixnet::delegators::query_for_delegations::execute(args, create_signing_client_with_nym_api(global_args, network_details)?).await
}
_ => unreachable!(),
}
Ok(())
}
@@ -13,7 +13,7 @@ pub(crate) async fn execute(
nym_cli_commands::validator::mixnet::operators::gateway::MixnetOperatorsGatewayCommands::Bond(args) => {
nym_cli_commands::validator::mixnet::operators::gateway::bond_gateway::bond_gateway(args, create_signing_client(global_args, network_details)?).await
},
nym_cli_commands::validator::mixnet::operators::gateway::MixnetOperatorsGatewayCommands::Unbound(_args) => {
nym_cli_commands::validator::mixnet::operators::gateway::MixnetOperatorsGatewayCommands::Unbond(_args) => {
nym_cli_commands::validator::mixnet::operators::gateway::unbond_gateway::unbond_gateway(create_signing_client(global_args, network_details)?).await
},
nym_cli_commands::validator::mixnet::operators::gateway::MixnetOperatorsGatewayCommands::CreateGatewayBondingSignPayload(args) => {
@@ -26,7 +26,7 @@ pub(crate) async fn execute(
nym_cli_commands::validator::mixnet::operators::mixnode::MixnetOperatorsMixnodeCommands::Bond(args) => {
nym_cli_commands::validator::mixnet::operators::mixnode::bond_mixnode::bond_mixnode(args, create_signing_client(global_args, network_details)?).await
}
nym_cli_commands::validator::mixnet::operators::mixnode::MixnetOperatorsMixnodeCommands::Unbound(args) => {
nym_cli_commands::validator::mixnet::operators::mixnode::MixnetOperatorsMixnodeCommands::Unbond(args) => {
nym_cli_commands::validator::mixnet::operators::mixnode::unbond_mixnode::unbond_mixnode(args, create_signing_client(global_args, network_details)?).await
}
nym_cli_commands::validator::mixnet::operators::mixnode::MixnetOperatorsMixnodeCommands::CreateMixnodeBondingSignPayload(args) => {
+8 -8
View File
@@ -3803,8 +3803,8 @@ const completion: Fig.Spec = {
],
},
{
name: "unbound",
description: "Unbound from a mixnode",
name: "unbond",
description: "Unbond from a mixnode",
options: [
{
name: "--mnemonic",
@@ -3994,8 +3994,8 @@ const completion: Fig.Spec = {
],
},
{
name: "unbound-vesting",
description: "Unbound from a mixnode (when originally using locked tokens)",
name: "unbond-vesting",
description: "Unbond from a mixnode (when originally using locked tokens)",
options: [
{
name: "--gas",
@@ -4296,8 +4296,8 @@ const completion: Fig.Spec = {
],
},
{
name: "unbound",
description: "Unbound from a gateway",
name: "unbond",
description: "Unbond from a gateway",
options: [
{
name: "--mnemonic",
@@ -4473,8 +4473,8 @@ const completion: Fig.Spec = {
],
},
{
name: "vesting-unbound",
description: "Unbound from a gateway (when originally using locked tokens)",
name: "vesting-unbond",
description: "Unbond from a gateway (when originally using locked tokens)",
options: [
{
name: "--mnemonic",
+4 -2
View File
@@ -5,8 +5,9 @@ use nym_api_requests::models::{
};
use nym_mixnet_contract_common::rewarding::RewardEstimate;
use nym_mixnet_contract_common::{
Interval as ContractInterval, IntervalRewardParams, IntervalRewardingParamsUpdate, MixNode,
MixNodeConfigUpdate, RewardedSetNodeStatus, RewardingParams, UnbondedMixnode,
GatewayConfigUpdate, Interval as ContractInterval, IntervalRewardParams,
IntervalRewardingParamsUpdate, MixNode, MixNodeConfigUpdate, RewardedSetNodeStatus,
RewardingParams, UnbondedMixnode,
};
use nym_types::account::{Account, AccountEntry, AccountWithMnemonic, Balance};
use nym_types::currency::{CurrencyDenom, DecCoin};
@@ -90,6 +91,7 @@ fn main() {
do_export!(Gas);
do_export!(GasInfo);
do_export!(Gateway);
do_export!(GatewayConfigUpdate);
do_export!(GatewayBond);
do_export!(CurrencyDenom);
do_export!(DecCoin);
@@ -84,7 +84,7 @@ export const CurrencyFormField: FCWithChildren<{
// it can't be lower than one micro coin
if (newNumber < MIN_VALUE) {
setValidationError('Amount cannot be less than 1 uNYM');
setValidationError('Amount cannot be less than 0.000001');
return fireOnValidate(false);
}
@@ -0,0 +1,9 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export interface GatewayConfigUpdate {
host: string;
mix_port: number;
clients_port: number;
location: string;
version: string;
}