From e0135173486d0dd8510b11dc2701a4d12854da29 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Wed, 5 Jan 2022 15:28:17 +0100 Subject: [PATCH 01/67] Use serial integer instead of random (#1009) * Use serial integer instead of random * [ci skip] Generate TS types * cargo fmt * Return u32 --- Cargo.lock | 2 - contracts/Cargo.lock | 51 +++----------------- contracts/vesting/Cargo.toml | 4 +- contracts/vesting/src/storage.rs | 3 +- contracts/vesting/src/vesting/account/mod.rs | 21 +++----- contracts/vesting/src/vesting/mod.rs | 5 +- nym-wallet/Cargo.lock | 2 - 7 files changed, 18 insertions(+), 70 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 99aa0cac83..6824e1e4e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7275,9 +7275,7 @@ dependencies = [ "config", "cosmwasm-std", "cw-storage-plus", - "getrandom 0.2.3", "mixnet-contract", - "rand 0.8.4", "schemars", "serde", "thiserror", diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock index 5874784f8e..d1e0a14153 100644 --- a/contracts/Cargo.lock +++ b/contracts/Cargo.lock @@ -326,7 +326,7 @@ dependencies = [ "log", "nymsphinx-types", "pemstore", - "rand 0.7.3", + "rand", "subtle-encoding", "x25519-dalek", ] @@ -458,7 +458,7 @@ checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" dependencies = [ "curve25519-dalek", "ed25519", - "rand 0.7.3", + "rand", "serde", "sha2", "zeroize", @@ -592,10 +592,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi 0.10.2+wasi-snapshot-preview1", - "wasm-bindgen", ] [[package]] @@ -858,8 +856,8 @@ dependencies = [ "cw-storage-plus", "fixed", "mixnet-contract", - "rand 0.7.3", - "rand_chacha 0.2.2", + "rand", + "rand_chacha", "schemars", "serde", "thiserror", @@ -1067,21 +1065,9 @@ checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom 0.1.16", "libc", - "rand_chacha 0.2.2", + "rand_chacha", "rand_core 0.5.1", - "rand_hc 0.2.0", -] - -[[package]] -name = "rand" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.3", - "rand_hc 0.3.1", + "rand_hc", ] [[package]] @@ -1094,16 +1080,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.3", -] - [[package]] name = "rand_core" version = "0.5.1" @@ -1129,7 +1105,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56" dependencies = [ "num-traits", - "rand 0.7.3", + "rand", ] [[package]] @@ -1141,15 +1117,6 @@ dependencies = [ "rand_core 0.5.1", ] -[[package]] -name = "rand_hc" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" -dependencies = [ - "rand_core 0.6.3", -] - [[package]] name = "regex" version = "1.5.4" @@ -1330,7 +1297,7 @@ dependencies = [ "hmac", "lioness", "log", - "rand 0.7.3", + "rand", "rand_distr", "sha2", "subtle 2.4.1", @@ -1558,9 +1525,7 @@ dependencies = [ "config", "cosmwasm-std", "cw-storage-plus", - "getrandom 0.2.3", "mixnet-contract", - "rand 0.8.4", "schemars", "serde", "thiserror", diff --git a/contracts/vesting/Cargo.toml b/contracts/vesting/Cargo.toml index 931d7c2d75..55e731443c 100644 --- a/contracts/vesting/Cargo.toml +++ b/contracts/vesting/Cargo.toml @@ -22,6 +22,4 @@ cw-storage-plus = { version = "0.10.3", features = ["iterator"] } schemars = "0.8" serde = { version = "1.0.103", default-features = false, features = ["derive"] } -thiserror = { version = "1.0.23" } -rand = {version = "0.8.4", features = ["std_rng"]} -getrandom = { version = "0.2.3", features = ["js"]} \ No newline at end of file +thiserror = { version = "1.0.23" } \ No newline at end of file diff --git a/contracts/vesting/src/storage.rs b/contracts/vesting/src/storage.rs index f8227d8581..eb24ba5409 100644 --- a/contracts/vesting/src/storage.rs +++ b/contracts/vesting/src/storage.rs @@ -1,8 +1,9 @@ use crate::errors::ContractError; use crate::vesting::Account; use cosmwasm_std::{Addr, Api, Storage}; -use cw_storage_plus::Map; +use cw_storage_plus::{Item, Map}; +pub const KEY: Item = Item::new("key"); const ACCOUNTS: Map = Map::new("acc"); pub fn delete_account(address: &Addr, storage: &mut dyn Storage) -> Result<(), ContractError> { diff --git a/contracts/vesting/src/vesting/account/mod.rs b/contracts/vesting/src/vesting/account/mod.rs index 424a6ca27f..4156630147 100644 --- a/contracts/vesting/src/vesting/account/mod.rs +++ b/contracts/vesting/src/vesting/account/mod.rs @@ -1,12 +1,10 @@ use super::{PledgeData, VestingPeriod}; use crate::contract::NUM_VESTING_PERIODS; use crate::errors::ContractError; -use crate::storage::save_account; +use crate::storage::{save_account, KEY}; use cosmwasm_std::{Addr, Coin, Order, Storage, Timestamp, Uint128}; use cw_storage_plus::{Item, Map}; use mixnet_contract::IdentityKey; -use rand::rngs::StdRng; -use rand::{RngCore, SeedableRng}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -20,17 +18,10 @@ const BALANCE_SUFFIX: &str = "ba"; const PLEDGE_SUFFIX: &str = "bo"; const GATEWAY_SUFFIX: &str = "ga"; -fn generate_storage_key(b: &[u8], storage: &dyn Storage) -> Result { - let mut rng = StdRng::seed_from_u64(b.iter().fold(0, |acc, x| acc + *x as u64)); - // Be paranoid and check for collisions - loop { - let key = rng.next_u64().to_string(); - let balance_key = format!("{}{}", key, BALANCE_SUFFIX); - let balance: Item = Item::new(&balance_key); - if balance.may_load(storage)?.is_none() { - return Ok(key); - } - } +fn generate_storage_key(storage: &mut dyn Storage) -> Result { + let key = KEY.may_load(storage)?.unwrap_or(0) + 1; + KEY.save(storage, &key)?; + Ok(key) } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -52,7 +43,7 @@ impl Account { periods: Vec, storage: &mut dyn Storage, ) -> Result { - let storage_key = generate_storage_key(owner_address.as_bytes(), storage)?; + let storage_key = generate_storage_key(storage)?.to_string(); let amount = coin.amount; let account = Account { owner_address, diff --git a/contracts/vesting/src/vesting/mod.rs b/contracts/vesting/src/vesting/mod.rs index 35d6d03d08..ef4551c11f 100644 --- a/contracts/vesting/src/vesting/mod.rs +++ b/contracts/vesting/src/vesting/mod.rs @@ -78,10 +78,7 @@ mod tests { // Test key collision avoidance let account_again = vesting_account_fixture(&mut deps.storage, &env); - assert_eq!( - created_account.balance_key(), - "5032709489228919411ba".to_string() - ); + assert_eq!(created_account.balance_key(), "1ba".to_string()); assert_ne!(created_account.balance_key(), account_again.balance_key()); } diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index 7ab0c36296..1ef0c5f80d 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -5188,9 +5188,7 @@ dependencies = [ "config", "cosmwasm-std", "cw-storage-plus", - "getrandom 0.2.3", "mixnet-contract", - "rand 0.8.4", "schemars", "serde", "thiserror", From ec4955f81444eda658685c0929c05791847b7325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 6 Jan 2022 10:38:14 +0000 Subject: [PATCH 02/67] Feature/explorer node status (#1010) * Restored mixnode refresh rate to a more sane value * Moved PrettyMixNodeBondWithLocation to models.rs * Renaming * Exposed ability to query for rewarded mixnodes in the validator client * Reorganised mix_nodes module * Determining mixnode status (active/standby/inactive) * Moved LocationCache to separate lock * Minor cleanup * Changed serialization case of status enum * Made clippy happier * Slightly better grammar --- .../validator-client/src/client.rs | 6 + .../validator-client/src/validator_api/mod.rs | 5 + .../src/validator_api/routes.rs | 1 + .../src/country_statistics/geolocate.rs | 18 +- explorer-api/src/main.rs | 8 +- explorer-api/src/mix_node/http.rs | 24 +- explorer-api/src/mix_node/models.rs | 34 ++- explorer-api/src/mix_nodes/delegations.rs | 31 +++ explorer-api/src/mix_nodes/http.rs | 9 +- explorer-api/src/mix_nodes/location.rs | 62 +++++ explorer-api/src/mix_nodes/mod.rs | 249 +----------------- explorer-api/src/mix_nodes/models.rs | 150 +++++++++++ explorer-api/src/mix_nodes/tasks.rs | 77 +++++- explorer-api/src/mix_nodes/utils.rs | 24 +- explorer-api/src/state.rs | 5 +- 15 files changed, 421 insertions(+), 282 deletions(-) create mode 100644 explorer-api/src/mix_nodes/delegations.rs create mode 100644 explorer-api/src/mix_nodes/location.rs create mode 100644 explorer-api/src/mix_nodes/models.rs diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 8ad6e843d7..afacd3260c 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -420,6 +420,12 @@ impl ApiClient { Ok(self.validator_api.get_active_mixnodes().await?) } + pub async fn get_cached_rewarded_mixnodes( + &self, + ) -> Result, ValidatorClientError> { + Ok(self.validator_api.get_rewarded_mixnodes().await?) + } + pub async fn get_cached_mixnodes(&self) -> Result, ValidatorClientError> { Ok(self.validator_api.get_mixnodes().await?) } diff --git a/common/client-libs/validator-client/src/validator_api/mod.rs b/common/client-libs/validator-client/src/validator_api/mod.rs index 8b993a5b7e..841e59b69e 100644 --- a/common/client-libs/validator-client/src/validator_api/mod.rs +++ b/common/client-libs/validator-client/src/validator_api/mod.rs @@ -73,6 +73,11 @@ impl Client { .await } + pub async fn get_rewarded_mixnodes(&self) -> Result, ValidatorAPIError> { + self.query_validator_api(&[routes::API_VERSION, routes::MIXNODES, routes::REWARDED]) + .await + } + pub async fn blind_sign( &self, request_body: &BlindSignRequestBody, diff --git a/common/client-libs/validator-client/src/validator_api/routes.rs b/common/client-libs/validator-client/src/validator_api/routes.rs index 4a22b48150..ac171b01e6 100644 --- a/common/client-libs/validator-client/src/validator_api/routes.rs +++ b/common/client-libs/validator-client/src/validator_api/routes.rs @@ -8,6 +8,7 @@ pub const MIXNODES: &str = "mixnodes"; pub const GATEWAYS: &str = "gateways"; pub const ACTIVE: &str = "active"; +pub const REWARDED: &str = "rewarded"; pub const COCONUT_BLIND_SIGN: &str = "blind_sign"; pub const COCONUT_VERIFICATION_KEY: &str = "verification_key"; diff --git a/explorer-api/src/country_statistics/geolocate.rs b/explorer-api/src/country_statistics/geolocate.rs index a09231ceae..55cd428798 100644 --- a/explorer-api/src/country_statistics/geolocate.rs +++ b/explorer-api/src/country_statistics/geolocate.rs @@ -1,10 +1,12 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::mix_nodes::location::{GeoLocation, Location}; +use crate::state::ExplorerApiStateContext; use log::{info, warn}; use reqwest::Error as ReqwestError; use thiserror::Error; -use crate::mix_nodes::{GeoLocation, Location}; -use crate::state::ExplorerApiStateContext; - pub(crate) struct GeoLocateTask { state: ExplorerApiStateContext, } @@ -34,7 +36,15 @@ impl GeoLocateTask { } async fn locate_mix_nodes(&mut self) { - let mixnode_bonds = self.state.inner.mix_nodes.get().await.value; + // I'm unwrapping to the default value to get rid of an extra indentation level from the `if let Some(...) = ...` + // If the value is None, we'll unwrap to an empty hashmap and the `values()` loop won't do any work anyway + let mixnode_bonds = self + .state + .inner + .mix_nodes + .get_mixnodes() + .await + .unwrap_or_default(); for (i, cache_item) in mixnode_bonds.values().enumerate() { if self diff --git a/explorer-api/src/main.rs b/explorer-api/src/main.rs index 7312962d77..999e9f3fc7 100644 --- a/explorer-api/src/main.rs +++ b/explorer-api/src/main.rs @@ -36,13 +36,11 @@ impl ExplorerApi { async fn run(&mut self) { info!("Explorer API starting up..."); - info!( - "Using validator API - {}", - network_defaults::default_api_endpoints()[0].clone() - ); + let validator_api_url = network_defaults::default_api_endpoints()[0].clone(); + info!("Using validator API - {}", validator_api_url); // spawn concurrent tasks - mix_nodes::tasks::MixNodesTasks::new(self.state.clone()).start(); + mix_nodes::tasks::MixNodesTasks::new(self.state.clone(), validator_api_url).start(); country_statistics::distribution::CountryStatisticsDistributionTask::new( self.state.clone(), ) diff --git a/explorer-api/src/mix_node/http.rs b/explorer-api/src/mix_node/http.rs index 79b2b5750e..a663f594c5 100644 --- a/explorer-api/src/mix_node/http.rs +++ b/explorer-api/src/mix_node/http.rs @@ -1,16 +1,16 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::mix_node::models::{NodeDescription, NodeStats}; +use crate::mix_nodes::delegations::{get_mixnode_delegations, get_single_mixnode_delegations}; +use crate::state::ExplorerApiStateContext; +use mixnet_contract::Delegation; use reqwest::Error as ReqwestError; use rocket::serde::json::Json; use rocket::{Route, State}; use rocket_okapi::okapi::openapi3::OpenApi; use rocket_okapi::openapi_get_routes_spec; use rocket_okapi::settings::OpenApiSettings; -use serde::Serialize; - -use mixnet_contract::{Addr, Coin, Delegation, Layer, MixNode}; - -use crate::mix_node::models::{NodeDescription, NodeStats}; -use crate::mix_nodes::{get_mixnode_delegations, get_single_mixnode_delegations, Location}; -use crate::state::ExplorerApiStateContext; pub fn mix_node_make_default_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { openapi_get_routes_spec![ @@ -21,16 +21,6 @@ pub fn mix_node_make_default_routes(settings: &OpenApiSettings) -> (Vec, ] } -#[derive(Clone, Debug, Serialize, JsonSchema)] -pub(crate) struct PrettyMixNodeBondWithLocation { - pub location: Option, - pub pledge_amount: Coin, - pub total_delegation: Coin, - pub owner: Addr, - pub layer: Layer, - pub mix_node: MixNode, -} - #[openapi(tag = "mix_node")] #[get("//delegations")] pub(crate) async fn get_delegations(pubkey: &str) -> Json> { diff --git a/explorer-api/src/mix_node/models.rs b/explorer-api/src/mix_node/models.rs index 8aca0dc33e..323946a47f 100644 --- a/explorer-api/src/mix_node/models.rs +++ b/explorer-api/src/mix_node/models.rs @@ -1,11 +1,33 @@ -use std::sync::Arc; -use std::time::SystemTime; - -use serde::Deserialize; -use serde::Serialize; -use tokio::sync::RwLock; +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 use crate::mix_node::cache::Cache; +use crate::mix_nodes::location::Location; +use mixnet_contract::{Addr, Coin, Layer, MixNode}; +use serde::Deserialize; +use serde::Serialize; +use std::sync::Arc; +use std::time::SystemTime; +use tokio::sync::RwLock; + +#[derive(Clone, Debug, Serialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub(crate) enum MixnodeStatus { + Active, // in both the active set and the rewarded set + Standby, // only in the rewarded set + Inactive, // in neither the rewarded set nor the active set +} + +#[derive(Clone, Debug, Serialize, JsonSchema)] +pub(crate) struct PrettyDetailedMixNodeBond { + pub location: Option, + pub status: MixnodeStatus, + pub pledge_amount: Coin, + pub total_delegation: Coin, + pub owner: Addr, + pub layer: Layer, + pub mix_node: MixNode, +} pub(crate) struct MixNodeCache { pub(crate) descriptions: Cache, diff --git a/explorer-api/src/mix_nodes/delegations.rs b/explorer-api/src/mix_nodes/delegations.rs new file mode 100644 index 0000000000..17f15989a7 --- /dev/null +++ b/explorer-api/src/mix_nodes/delegations.rs @@ -0,0 +1,31 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use mixnet_contract::Delegation; + +pub(crate) async fn get_single_mixnode_delegations(pubkey: &str) -> Vec { + let client = super::utils::new_nymd_client(); + let delegates = match client + .get_all_nymd_single_mixnode_delegations(pubkey.to_string()) + .await + { + Ok(result) => result, + Err(e) => { + error!("Could not get delegations for mix node {}: {:?}", pubkey, e); + vec![] + } + }; + delegates +} + +pub(crate) async fn get_mixnode_delegations() -> Vec { + let client = super::utils::new_nymd_client(); + let delegates = match client.get_all_network_delegations().await { + Ok(result) => result, + Err(e) => { + error!("Could not get all mix delegations: {:?}", e); + vec![] + } + }; + delegates +} diff --git a/explorer-api/src/mix_nodes/http.rs b/explorer-api/src/mix_nodes/http.rs index efe75f6221..a2b8b0eaba 100644 --- a/explorer-api/src/mix_nodes/http.rs +++ b/explorer-api/src/mix_nodes/http.rs @@ -1,12 +1,11 @@ +use crate::mix_node::models::PrettyDetailedMixNodeBond; +use crate::state::ExplorerApiStateContext; use rocket::serde::json::Json; use rocket::{Route, State}; use rocket_okapi::okapi::openapi3::OpenApi; use rocket_okapi::openapi_get_routes_spec; use rocket_okapi::settings::OpenApiSettings; -use crate::mix_node::http::PrettyMixNodeBondWithLocation; -use crate::state::ExplorerApiStateContext; - pub fn mix_nodes_make_default_routes(settings: &OpenApiSettings) -> (Vec, OpenApi) { openapi_get_routes_spec![settings: list] } @@ -15,6 +14,6 @@ pub fn mix_nodes_make_default_routes(settings: &OpenApiSettings) -> (Vec, #[get("/")] pub(crate) async fn list( state: &State, -) -> Json> { - Json(state.inner.mix_nodes.get_mixnodes_with_location().await) +) -> Json> { + Json(state.inner.mix_nodes.get_detailed_mixnodes().await) } diff --git a/explorer-api/src/mix_nodes/location.rs b/explorer-api/src/mix_nodes/location.rs new file mode 100644 index 0000000000..80f24f6635 --- /dev/null +++ b/explorer-api/src/mix_nodes/location.rs @@ -0,0 +1,62 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::mix_nodes::utils::map_2_letter_to_3_letter_country_code; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::time::{Duration, SystemTime}; + +pub(crate) type LocationCache = HashMap; + +#[allow(dead_code)] +#[derive(Debug, Deserialize)] +pub(crate) struct GeoLocation { + pub(crate) ip: String, + pub(crate) country_code: String, + pub(crate) country_name: String, + pub(crate) region_code: String, + pub(crate) region_name: String, + pub(crate) city: String, + pub(crate) zip_code: String, + pub(crate) time_zone: String, + pub(crate) latitude: f32, + pub(crate) longitude: f32, + pub(crate) metro_code: u32, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub(crate) struct LocationCacheItem { + pub(crate) location: Option, + pub(crate) valid_until: SystemTime, +} + +#[derive(Clone, Debug, JsonSchema, Serialize, Deserialize)] +pub(crate) struct Location { + pub(crate) two_letter_iso_country_code: String, + pub(crate) three_letter_iso_country_code: String, + pub(crate) country_name: String, + pub(crate) lat: f32, + pub(crate) lng: f32, +} + +impl Location { + pub(crate) fn new(geo_location: GeoLocation) -> Self { + let three_letter_iso_country_code = map_2_letter_to_3_letter_country_code(&geo_location); + Location { + country_name: geo_location.country_name, + two_letter_iso_country_code: geo_location.country_code, + three_letter_iso_country_code, + lat: geo_location.latitude, + lng: geo_location.longitude, + } + } +} + +impl LocationCacheItem { + pub(crate) fn new_from_location(location: Option) -> Self { + LocationCacheItem { + location, + valid_until: SystemTime::now() + Duration::from_secs(60 * 60 * 24), // valid for 1 day + } + } +} diff --git a/explorer-api/src/mix_nodes/mod.rs b/explorer-api/src/mix_nodes/mod.rs index dcbd5638e2..dacff38e32 100644 --- a/explorer-api/src/mix_nodes/mod.rs +++ b/explorer-api/src/mix_nodes/mod.rs @@ -1,241 +1,14 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::time::Duration; + +pub(crate) mod delegations; pub(crate) mod http; +pub(crate) mod location; +pub(crate) mod models; pub(crate) mod tasks; -mod utils; +pub(crate) mod utils; -use std::collections::HashMap; -use std::sync::Arc; -use std::time::{Duration, SystemTime}; - -use rocket::tokio::sync::RwLock; -use serde::{Deserialize, Serialize}; - -use crate::mix_node::http::PrettyMixNodeBondWithLocation; -use crate::mix_nodes::utils::map_2_letter_to_3_letter_country_code; -use mixnet_contract::{Delegation, MixNodeBond}; -use network_defaults::{ - default_api_endpoints, default_nymd_endpoints, DEFAULT_MIXNET_CONTRACT_ADDRESS, -}; -use validator_client::nymd::QueryNymdClient; - -pub(crate) type LocationCache = HashMap; - -#[allow(dead_code)] -#[derive(Debug, Deserialize)] -pub(crate) struct GeoLocation { - pub(crate) ip: String, - pub(crate) country_code: String, - pub(crate) country_name: String, - pub(crate) region_code: String, - pub(crate) region_name: String, - pub(crate) city: String, - pub(crate) zip_code: String, - pub(crate) time_zone: String, - pub(crate) latitude: f32, - pub(crate) longitude: f32, - pub(crate) metro_code: u32, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub(crate) struct LocationCacheItem { - pub(crate) location: Option, - pub(crate) valid_until: SystemTime, -} - -#[derive(Clone, Debug, JsonSchema, Serialize, Deserialize)] -pub(crate) struct Location { - pub(crate) two_letter_iso_country_code: String, - pub(crate) three_letter_iso_country_code: String, - pub(crate) country_name: String, - pub(crate) lat: f32, - pub(crate) lng: f32, -} - -impl Location { - pub(crate) fn new(geo_location: GeoLocation) -> Self { - let three_letter_iso_country_code = map_2_letter_to_3_letter_country_code(&geo_location); - Location { - country_name: geo_location.country_name, - two_letter_iso_country_code: geo_location.country_code, - three_letter_iso_country_code, - lat: geo_location.latitude, - lng: geo_location.longitude, - } - } -} - -impl LocationCacheItem { - pub(crate) fn new_from_location(location: Option) -> Self { - LocationCacheItem { - location, - valid_until: SystemTime::now() + Duration::from_secs(60 * 60 * 24), // valid for 1 day - } - } -} - -#[derive(Clone, Debug)] -pub(crate) struct MixNodesResult { - pub(crate) valid_until: SystemTime, - pub(crate) value: HashMap, - location_cache: LocationCache, -} - -#[derive(Clone)] -pub(crate) struct ThreadsafeMixNodesResult { - inner: Arc>, -} - -impl ThreadsafeMixNodesResult { - pub(crate) fn new() -> Self { - ThreadsafeMixNodesResult { - inner: Arc::new(RwLock::new(MixNodesResult { - value: HashMap::new(), - valid_until: SystemTime::now() - Duration::from_secs(60), // in the past - location_cache: LocationCache::new(), - })), - } - } - - pub(crate) fn new_with_location_cache(location_cache: LocationCache) -> Self { - ThreadsafeMixNodesResult { - inner: Arc::new(RwLock::new(MixNodesResult { - value: HashMap::new(), - valid_until: SystemTime::now() - Duration::from_secs(60), // in the past - location_cache, - })), - } - } - - pub(crate) async fn is_location_valid(&self, identity_key: &str) -> bool { - self.inner - .read() - .await - .location_cache - .get(identity_key) - .map(|cache_item| cache_item.valid_until > SystemTime::now()) - .unwrap_or(false) - } - - pub(crate) async fn get_location_cache(&self) -> LocationCache { - self.inner.read().await.location_cache.clone() - } - - pub(crate) async fn set_location(&self, identity_key: &str, location: Option) { - let mut guard = self.inner.write().await; - - // cache the location for this mix node so that it can be used when the mix node list is refreshed - guard.location_cache.insert( - identity_key.to_string(), - LocationCacheItem::new_from_location(location), - ); - } - - pub(crate) async fn get(&self) -> MixNodesResult { - // check ttl - let valid_until = self.inner.read().await.valid_until; - - if valid_until < SystemTime::now() { - // force reload - self.refresh().await; - } - - // return in-memory cache - self.inner.read().await.clone() - } - - pub(crate) async fn get_mixnodes_with_location(&self) -> Vec { - let guard = self.inner.read().await; - guard - .value - .values() - .map(|bond| { - let location = guard.location_cache.get(&bond.mix_node.identity_key); - let copy = bond.clone(); - PrettyMixNodeBondWithLocation { - location: location.and_then(|l| l.location.clone()), - pledge_amount: copy.pledge_amount, - total_delegation: copy.total_delegation, - owner: copy.owner, - layer: copy.layer, - mix_node: copy.mix_node, - } - }) - .collect() - } - - pub(crate) async fn refresh(&self) { - // get mixnodes and cache the new value - let value = retrieve_mixnodes().await; - let location_cache = self.inner.read().await.location_cache.clone(); - *self.inner.write().await = MixNodesResult { - value: value - .into_iter() - .map(|bond| (bond.mix_node.identity_key.to_string(), bond)) - .collect(), - valid_until: SystemTime::now() + Duration::from_secs(30), - location_cache, - }; - } -} - -pub(crate) async fn retrieve_mixnodes() -> Vec { - let client = new_validator_client(); - - info!("About to retrieve mixnode bonds..."); - - let bonds: Vec = match client.get_cached_mixnodes().await { - Ok(result) => result, - Err(e) => { - error!("Unable to retrieve mixnode bonds: {:?}", e); - vec![] - } - }; - info!("Fetched {} mixnode bonds", bonds.len()); - bonds -} - -pub(crate) async fn get_single_mixnode_delegations(pubkey: &str) -> Vec { - let client = new_nymd_client(); - let delegates = match client - .get_all_nymd_single_mixnode_delegations(pubkey.to_string()) - .await - { - Ok(result) => result, - Err(e) => { - error!("Could not get delegations for mix node {}: {:?}", pubkey, e); - vec![] - } - }; - delegates -} - -pub(crate) async fn get_mixnode_delegations() -> Vec { - let client = new_nymd_client(); - let delegates = match client.get_all_network_delegations().await { - Ok(result) => result, - Err(e) => { - error!("Could not get all mix delegations: {:?}", e); - vec![] - } - }; - delegates -} - -fn new_nymd_client() -> validator_client::Client { - let mixnet_contract = DEFAULT_MIXNET_CONTRACT_ADDRESS.to_string(); - let nymd_url = default_nymd_endpoints()[0].clone(); - let api_url = default_api_endpoints()[0].clone(); - - let client_config = validator_client::Config::new( - nymd_url, - api_url, - Some(mixnet_contract.parse().unwrap()), - None, - ); - - validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!") -} - -// TODO: inject constants -fn new_validator_client() -> validator_client::ApiClient { - validator_client::ApiClient::new(default_api_endpoints()[0].clone()) -} +pub(crate) const MIXNODES_CACHE_REFRESH_RATE: Duration = Duration::from_secs(30); +pub(crate) const MIXNODES_CACHE_ENTRY_TTL: Duration = Duration::from_secs(60); diff --git a/explorer-api/src/mix_nodes/models.rs b/explorer-api/src/mix_nodes/models.rs new file mode 100644 index 0000000000..7ccd179f86 --- /dev/null +++ b/explorer-api/src/mix_nodes/models.rs @@ -0,0 +1,150 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::mix_node::models::{MixnodeStatus, PrettyDetailedMixNodeBond}; +use crate::mix_nodes::location::{Location, LocationCache, LocationCacheItem}; +use crate::mix_nodes::MIXNODES_CACHE_ENTRY_TTL; +use mixnet_contract::MixNodeBond; +use std::collections::{HashMap, HashSet}; +use std::sync::Arc; +use std::time::{Duration, SystemTime}; +use tokio::sync::RwLock; + +#[derive(Clone, Debug)] +pub(crate) struct MixNodesResult { + pub(crate) valid_until: SystemTime, + pub(crate) all_mixnodes: HashMap, + active_mixnodes: HashSet, + rewarded_mixnodes: HashSet, +} + +impl MixNodesResult { + fn new() -> Self { + MixNodesResult { + valid_until: SystemTime::now() - Duration::from_secs(60), // in the past + all_mixnodes: HashMap::new(), + active_mixnodes: HashSet::new(), + rewarded_mixnodes: HashSet::new(), + } + } + + fn determine_node_status(&self, public_key: &str) -> MixnodeStatus { + if self.active_mixnodes.contains(public_key) { + MixnodeStatus::Active + } else if self.rewarded_mixnodes.contains(public_key) { + MixnodeStatus::Standby + } else { + MixnodeStatus::Inactive + } + } + + fn is_valid(&self) -> bool { + self.valid_until >= SystemTime::now() + } + + fn get_mixnode(&self, pubkey: &str) -> Option { + if self.is_valid() { + self.all_mixnodes.get(pubkey).cloned() + } else { + None + } + } + + fn get_mixnodes(&self) -> Option> { + if self.is_valid() { + Some(self.all_mixnodes.clone()) + } else { + None + } + } +} + +#[derive(Clone)] +pub(crate) struct ThreadsafeMixNodesResult { + mixnode_results: Arc>, + location_cache: Arc>, +} + +impl ThreadsafeMixNodesResult { + pub(crate) fn new() -> Self { + ThreadsafeMixNodesResult { + mixnode_results: Arc::new(RwLock::new(MixNodesResult::new())), + location_cache: Arc::new(RwLock::new(LocationCache::new())), + } + } + + pub(crate) fn new_with_location_cache(location_cache: LocationCache) -> Self { + ThreadsafeMixNodesResult { + mixnode_results: Arc::new(RwLock::new(MixNodesResult::new())), + location_cache: Arc::new(RwLock::new(location_cache)), + } + } + + pub(crate) async fn is_location_valid(&self, identity_key: &str) -> bool { + self.location_cache + .read() + .await + .get(identity_key) + .map(|cache_item| cache_item.valid_until > SystemTime::now()) + .unwrap_or(false) + } + + pub(crate) async fn get_location_cache(&self) -> LocationCache { + self.location_cache.read().await.clone() + } + + pub(crate) async fn set_location(&self, identity_key: &str, location: Option) { + // cache the location for this mix node so that it can be used when the mix node list is refreshed + self.location_cache.write().await.insert( + identity_key.to_string(), + LocationCacheItem::new_from_location(location), + ); + } + + pub(crate) async fn get_mixnode(&self, pubkey: &str) -> Option { + self.mixnode_results.read().await.get_mixnode(pubkey) + } + + pub(crate) async fn get_mixnodes(&self) -> Option> { + self.mixnode_results.read().await.get_mixnodes() + } + + pub(crate) async fn get_detailed_mixnodes(&self) -> Vec { + let mixnodes_guard = self.mixnode_results.read().await; + let location_guard = self.location_cache.read().await; + + mixnodes_guard + .all_mixnodes + .values() + .map(|bond| { + let location = location_guard.get(&bond.mix_node.identity_key); + let copy = bond.clone(); + PrettyDetailedMixNodeBond { + location: location.and_then(|l| l.location.clone()), + status: mixnodes_guard.determine_node_status(&bond.mix_node.identity_key), + pledge_amount: copy.pledge_amount, + total_delegation: copy.total_delegation, + owner: copy.owner, + layer: copy.layer, + mix_node: copy.mix_node, + } + }) + .collect() + } + + pub(crate) async fn update_cache( + &self, + all_bonds: Vec, + rewarded_nodes: HashSet, + active_nodes: HashSet, + ) { + let mut guard = self.mixnode_results.write().await; + guard.all_mixnodes = all_bonds + .into_iter() + .map(|bond| (bond.mix_node.identity_key.to_string(), bond)) + .collect(); + guard.rewarded_mixnodes = rewarded_nodes; + guard.active_mixnodes = active_nodes; + guard.valid_until = SystemTime::now() + MIXNODES_CACHE_ENTRY_TTL; + } +} diff --git a/explorer-api/src/mix_nodes/tasks.rs b/explorer-api/src/mix_nodes/tasks.rs index f89cd61217..d1580a832c 100644 --- a/explorer-api/src/mix_nodes/tasks.rs +++ b/explorer-api/src/mix_nodes/tasks.rs @@ -1,24 +1,93 @@ +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::mix_nodes::MIXNODES_CACHE_REFRESH_RATE; use crate::state::ExplorerApiStateContext; +use mixnet_contract::MixNodeBond; +use reqwest::Url; +use std::future::Future; +use validator_client::ValidatorClientError; pub(crate) struct MixNodesTasks { state: ExplorerApiStateContext, + validator_api_client: validator_client::ApiClient, } impl MixNodesTasks { - pub(crate) fn new(state: ExplorerApiStateContext) -> Self { - MixNodesTasks { state } + pub(crate) fn new(state: ExplorerApiStateContext, validator_api_endpoint: Url) -> Self { + MixNodesTasks { + state, + validator_api_client: validator_client::ApiClient::new(validator_api_endpoint), + } + } + + // a helper to remove duplicate code when grabbing active/rewarded/all mixnodes + async fn retrieve_mixnodes<'a, F, Fut>(&'a self, f: F) -> Vec + where + F: FnOnce(&'a validator_client::ApiClient) -> Fut, + Fut: Future, ValidatorClientError>>, + { + let bonds = match f(&self.validator_api_client).await { + Ok(result) => result, + Err(e) => { + error!("Unable to retrieve mixnode bonds: {:?}", e); + vec![] + } + }; + + info!("Fetched {} mixnode bonds", bonds.len()); + bonds + } + + async fn retrieve_all_mixnodes(&self) -> Vec { + info!("About to retrieve all mixnode bonds..."); + self.retrieve_mixnodes(validator_client::ApiClient::get_cached_mixnodes) + .await + } + + async fn retrieve_rewarded_mixnodes(&self) -> Vec { + info!("About to retrieve rewarded mixnode bonds..."); + self.retrieve_mixnodes(validator_client::ApiClient::get_cached_rewarded_mixnodes) + .await + } + + async fn retrieve_active_mixnodes(&self) -> Vec { + info!("About to retrieve active mixnode bonds..."); + self.retrieve_mixnodes(validator_client::ApiClient::get_cached_active_mixnodes) + .await + } + + async fn update_mixnode_cache(&self) { + let all_bonds = self.retrieve_all_mixnodes().await; + let rewarded_nodes = self + .retrieve_rewarded_mixnodes() + .await + .into_iter() + .map(|bond| bond.mix_node.identity_key) + .collect(); + let active_nodes = self + .retrieve_active_mixnodes() + .await + .into_iter() + .map(|bond| bond.mix_node.identity_key) + .collect(); + self.state + .inner + .mix_nodes + .update_cache(all_bonds, rewarded_nodes, active_nodes) + .await; } pub(crate) fn start(self) { info!("Spawning mix nodes task runner..."); tokio::spawn(async move { - let mut interval_timer = tokio::time::interval(std::time::Duration::from_secs(2)); + let mut interval_timer = tokio::time::interval(MIXNODES_CACHE_REFRESH_RATE); loop { // wait for the next interval tick interval_timer.tick().await; info!("Updating mix node cache..."); - self.state.inner.mix_nodes.refresh().await; + self.update_mixnode_cache().await; info!("Done"); } }); diff --git a/explorer-api/src/mix_nodes/utils.rs b/explorer-api/src/mix_nodes/utils.rs index 7e46ce7afd..542bfe8660 100644 --- a/explorer-api/src/mix_nodes/utils.rs +++ b/explorer-api/src/mix_nodes/utils.rs @@ -1,5 +1,12 @@ -use crate::mix_nodes::GeoLocation; +// Copyright 2022 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use crate::mix_nodes::location::GeoLocation; use isocountry::CountryCode; +use network_defaults::{ + default_api_endpoints, default_nymd_endpoints, DEFAULT_MIXNET_CONTRACT_ADDRESS, +}; +use validator_client::nymd::QueryNymdClient; pub(crate) fn map_2_letter_to_3_letter_country_code(geo: &GeoLocation) -> String { match CountryCode::for_alpha2(&geo.country_code) { @@ -13,3 +20,18 @@ pub(crate) fn map_2_letter_to_3_letter_country_code(geo: &GeoLocation) -> String } } } + +pub(crate) fn new_nymd_client() -> validator_client::Client { + let mixnet_contract = DEFAULT_MIXNET_CONTRACT_ADDRESS.to_string(); + let nymd_url = default_nymd_endpoints()[0].clone(); + let api_url = default_api_endpoints()[0].clone(); + + let client_config = validator_client::Config::new( + nymd_url, + api_url, + Some(mixnet_contract.parse().unwrap()), + None, + ); + + validator_client::Client::new_query(client_config).expect("Failed to connect to nymd!") +} diff --git a/explorer-api/src/state.rs b/explorer-api/src/state.rs index 3202abc9ea..d6be0e3f70 100644 --- a/explorer-api/src/state.rs +++ b/explorer-api/src/state.rs @@ -11,7 +11,8 @@ use crate::country_statistics::country_nodes_distribution::{ ConcurrentCountryNodesDistribution, CountryNodesDistribution, }; use crate::mix_node::models::ThreadsafeMixNodeCache; -use crate::mix_nodes::{LocationCache, ThreadsafeMixNodesResult}; +use crate::mix_nodes::location::LocationCache; +use crate::mix_nodes::models::ThreadsafeMixNodesResult; use crate::ping::models::ThreadsafePingCache; // TODO: change to an environment variable with a default value @@ -27,7 +28,7 @@ pub struct ExplorerApiState { impl ExplorerApiState { pub(crate) async fn get_mix_node(&self, pubkey: &str) -> Option { - self.mix_nodes.get().await.value.get(pubkey).cloned() + self.mix_nodes.get_mixnode(pubkey).await } } From 30e93c33bba54d768d97cf5e3f2e16223f7ebb25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Thu, 6 Jan 2022 13:03:14 +0100 Subject: [PATCH 03/67] Feature/configure profit (#1008) * Introduce a method to update mixnode configuration Right now, only for profit_margin_percent * Check that the new profit margin is valid * Extend a bit the test coverage of mixnode update * Create validator client function * [ci skip] Generate TS types * Update wallet * Update the bond height as well, as if a rebond was made Co-authored-by: neacsu --- .../validator-client/src/nymd/fee/helpers.rs | 3 + .../validator-client/src/nymd/mod.rs | 25 ++++ common/mixnet-contract/src/msg.rs | 3 + contracts/mixnet/src/contract.rs | 8 ++ contracts/mixnet/src/mixnodes/transactions.rs | 107 ++++++++++++++++++ nym-wallet/src-tauri/src/main.rs | 1 + .../src-tauri/src/operations/mixnet/bond.rs | 11 ++ nym-wallet/src/types/rust/operation.ts | 1 + 8 files changed, 159 insertions(+) diff --git a/common/client-libs/validator-client/src/nymd/fee/helpers.rs b/common/client-libs/validator-client/src/nymd/fee/helpers.rs index cc16e21cb5..0ddbf15356 100644 --- a/common/client-libs/validator-client/src/nymd/fee/helpers.rs +++ b/common/client-libs/validator-client/src/nymd/fee/helpers.rs @@ -20,6 +20,7 @@ pub enum Operation { BondMixnodeOnBehalf, UnbondMixnode, UnbondMixnodeOnBehalf, + UpdateMixnodeConfig, DelegateToMixnode, DelegateToMixnodeOnBehalf, UndelegateFromMixnode, @@ -57,6 +58,7 @@ impl fmt::Display for Operation { Operation::BondMixnode => f.write_str("BondMixnode"), Operation::BondMixnodeOnBehalf => f.write_str("BondMixnodeOnBehalf"), Operation::UnbondMixnode => f.write_str("UnbondMixnode"), + Operation::UpdateMixnodeConfig => f.write_str("UpdateMixnodeConfig"), Operation::UnbondMixnodeOnBehalf => f.write_str("UnbondMixnodeOnBehalf"), Operation::BondGateway => f.write_str("BondGateway"), Operation::BondGatewayOnBehalf => f.write_str("BondGatewayOnBehalf"), @@ -94,6 +96,7 @@ impl Operation { Operation::BondMixnodeOnBehalf => 200_000u64.into(), Operation::UnbondMixnode => 175_000u64.into(), Operation::UnbondMixnodeOnBehalf => 175_000u64.into(), + Operation::UpdateMixnodeConfig => 175_000u64.into(), Operation::DelegateToMixnode => 175_000u64.into(), Operation::DelegateToMixnodeOnBehalf => 175_000u64.into(), Operation::UndelegateFromMixnode => 175_000u64.into(), diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index 72f72a220f..71c8766cf7 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -773,6 +773,31 @@ impl NymdClient { .await } + /// Update the configuration of a mixnode. Right now, only possible for profit margin. + pub async fn update_mixnode_config( + &self, + profit_margin_percent: u8, + ) -> Result + where + C: SigningCosmWasmClient + Sync, + { + let fee = self.operation_fee(Operation::UpdateMixnodeConfig); + + let req = ExecuteMsg::UpdateMixnodeConfig { + profit_margin_percent, + }; + self.client + .execute( + self.address(), + self.mixnet_contract_address()?, + &req, + fee, + "Updating mixnode configuration from rust!", + Vec::new(), + ) + .await + } + /// Delegates specified amount of stake to particular mixnode. pub async fn delegate_to_mixnode( &self, diff --git a/common/mixnet-contract/src/msg.rs b/common/mixnet-contract/src/msg.rs index c744a4f56e..8f74824ec0 100644 --- a/common/mixnet-contract/src/msg.rs +++ b/common/mixnet-contract/src/msg.rs @@ -20,6 +20,9 @@ pub enum ExecuteMsg { owner_signature: String, }, UnbondMixnode {}, + UpdateMixnodeConfig { + profit_margin_percent: u8, + }, BondGateway { gateway: Gateway, owner_signature: String, diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index d46c7aa3c7..2cf4d65686 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -104,6 +104,14 @@ pub fn execute( ExecuteMsg::UnbondMixnode {} => { crate::mixnodes::transactions::try_remove_mixnode(deps, info) } + ExecuteMsg::UpdateMixnodeConfig { + profit_margin_percent, + } => crate::mixnodes::transactions::try_update_mixnode_config( + deps, + env, + info, + profit_margin_percent, + ), ExecuteMsg::BondGateway { gateway, owner_signature, diff --git a/contracts/mixnet/src/mixnodes/transactions.rs b/contracts/mixnet/src/mixnodes/transactions.rs index 3f7c3d40e3..5ec7cdc33a 100644 --- a/contracts/mixnet/src/mixnodes/transactions.rs +++ b/contracts/mixnet/src/mixnodes/transactions.rs @@ -204,6 +204,42 @@ pub(crate) fn _try_remove_mixnode( Ok(response) } +pub(crate) fn try_update_mixnode_config( + deps: DepsMut, + env: Env, + info: MessageInfo, + profit_margin_percent: u8, +) -> Result { + let owner = deps.api.addr_validate(info.sender.as_ref())?; + let mix_identity = storage::mixnodes() + .idx + .owner + .item(deps.storage, owner.clone())? + .ok_or(ContractError::NoAssociatedMixNodeBond { owner })? + .1 + .identity() + .clone(); + + // We don't have to check lower bound as its an u8 + if profit_margin_percent > 100 { + return Err(ContractError::InvalidProfitMarginPercent( + profit_margin_percent, + )); + } + + storage::mixnodes().update(deps.storage, &mix_identity, |mixnode_bond_opt| { + mixnode_bond_opt + .map(|mut mixnode_bond| { + mixnode_bond.mix_node.profit_margin_percent = profit_margin_percent; + mixnode_bond.block_height = env.block.height; + mixnode_bond + }) + .ok_or(ContractError::NoBondFound) + })?; + + Ok(Response::new()) +} + fn validate_mixnode_pledge( mut pledge: Vec, minimum_pledge: Uint128, @@ -587,6 +623,77 @@ pub mod tests { ); } + #[test] + fn updating_mixnode_config() { + let sender = "bob"; + let mut deps = test_helpers::init_contract(); + let info = mock_info(sender, &[]); + + // try updating a non existing mixnode bond + let msg = ExecuteMsg::UpdateMixnodeConfig { + profit_margin_percent: 10, + }; + let ret = execute(deps.as_mut(), mock_env(), info.clone(), msg); + assert_eq!( + ret, + Err(ContractError::NoAssociatedMixNodeBond { + owner: Addr::unchecked(sender) + }) + ); + + test_helpers::add_mixnode( + sender, + tests::fixtures::good_mixnode_pledge(), + deps.as_mut(), + ); + + // check the initial profit margin is set to the fixture value + let fixture_profit_margin = tests::fixtures::mix_node_fixture().profit_margin_percent; + assert_eq!( + fixture_profit_margin, + storage::mixnodes() + .idx + .owner + .item(deps.as_ref().storage, Addr::unchecked("bob")) + .unwrap() + .unwrap() + .1 + .mix_node + .profit_margin_percent + ); + + // try updating with an invalid value + let profit_margin_percent = 101; + let msg = ExecuteMsg::UpdateMixnodeConfig { + profit_margin_percent, + }; + let ret = execute(deps.as_mut(), mock_env(), info.clone(), msg); + assert_eq!( + ret, + Err(ContractError::InvalidProfitMarginPercent( + profit_margin_percent + )) + ); + + let profit_margin_percent = fixture_profit_margin + 10; + let msg = ExecuteMsg::UpdateMixnodeConfig { + profit_margin_percent, + }; + execute(deps.as_mut(), mock_env(), info, msg).unwrap(); + assert_eq!( + profit_margin_percent, + storage::mixnodes() + .idx + .owner + .item(deps.as_ref().storage, Addr::unchecked("bob")) + .unwrap() + .unwrap() + .1 + .mix_node + .profit_margin_percent + ); + } + #[test] fn validating_mixnode_bond() { // you must send SOME funds diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index e402137e39..a52b9a4a91 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -36,6 +36,7 @@ fn main() { mixnet::bond::bond_mixnode, mixnet::bond::unbond_gateway, mixnet::bond::unbond_mixnode, + mixnet::bond::update_mixnode, mixnet::bond::mixnode_bond_details, mixnet::bond::gateway_bond_details, mixnet::delegate::delegate_to_mixnode, diff --git a/nym-wallet/src-tauri/src/operations/mixnet/bond.rs b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs index b1801b576f..fce6c4219a 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/bond.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/bond.rs @@ -50,6 +50,17 @@ pub async fn bond_mixnode( Ok(()) } +#[tauri::command] +pub async fn update_mixnode( + profit_margin_percent: u8, + state: tauri::State<'_, Arc>>, +) -> Result<(), BackendError> { + client!(state) + .update_mixnode_config(profit_margin_percent) + .await?; + Ok(()) +} + #[tauri::command] pub async fn mixnode_bond_details( state: tauri::State<'_, Arc>>, diff --git a/nym-wallet/src/types/rust/operation.ts b/nym-wallet/src/types/rust/operation.ts index 545c7bbe81..6c8d38c221 100644 --- a/nym-wallet/src/types/rust/operation.ts +++ b/nym-wallet/src/types/rust/operation.ts @@ -8,6 +8,7 @@ export type Operation = | "BondMixnodeOnBehalf" | "UnbondMixnode" | "UnbondMixnodeOnBehalf" + | "UpdateMixnodeConfig" | "DelegateToMixnode" | "DelegateToMixnodeOnBehalf" | "UndelegateFromMixnode" From 70138ff54a5ad402a0a1758d000985ecad7b52ba Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Thu, 6 Jan 2022 13:00:50 +0000 Subject: [PATCH 04/67] update frontend to use new profit update api --- nym-wallet/src/pages/settings/index.tsx | 6 +-- .../src/pages/settings/system-variables.tsx | 41 +++++-------------- .../src/pages/settings/validationSchema.ts | 1 - nym-wallet/src/requests/index.ts | 3 ++ 4 files changed, 15 insertions(+), 36 deletions(-) diff --git a/nym-wallet/src/pages/settings/index.tsx b/nym-wallet/src/pages/settings/index.tsx index fca3b126dc..b25859d88a 100644 --- a/nym-wallet/src/pages/settings/index.tsx +++ b/nym-wallet/src/pages/settings/index.tsx @@ -24,7 +24,7 @@ export const Settings = () => { setMixnodeDetails(details) } if (showSettings) getBondDetails() - }, [showSettings]) + }, [showSettings, selectedTab]) const handleTabChange = (event: React.SyntheticEvent, newTab: number) => setSelectedTab(newTab) @@ -50,9 +50,7 @@ export const Settings = () => { )} {selectedTab === 0 && mixnodeDetails && } - {selectedTab === 1 && mixnodeDetails && ( - - )} + {selectedTab === 1 && mixnodeDetails && } {selectedTab === 2 && mixnodeDetails && } diff --git a/nym-wallet/src/pages/settings/system-variables.tsx b/nym-wallet/src/pages/settings/system-variables.tsx index befdbd72bc..1f131c957e 100644 --- a/nym-wallet/src/pages/settings/system-variables.tsx +++ b/nym-wallet/src/pages/settings/system-variables.tsx @@ -15,23 +15,16 @@ import { AccessTimeOutlined, PercentOutlined } from '@mui/icons-material' import { yupResolver } from '@hookform/resolvers/yup' import { useForm } from 'react-hook-form' import { InfoTooltip } from '../../components/InfoToolTip' -import { EnumNodeType, TMixnodeBondDetails } from '../../types' +import { TMixnodeBondDetails } from '../../types' import { validationSchema } from './validationSchema' -import { bond, unbond } from '../../requests' +import { updateMixnode } from '../../requests' import { ClientContext } from '../../context/main' type TFormData = { profitMarginPercent: number - signature: string } -export const SystemVariables = ({ - mixnodeDetails, - pledge, -}: { - mixnodeDetails: TMixnodeBondDetails['mix_node'] - pledge: TMixnodeBondDetails['pledge_amount'] -}) => { +export const SystemVariables = ({ mixnodeDetails }: { mixnodeDetails: TMixnodeBondDetails['mix_node'] }) => { const [nodeUpdateResponse, setNodeUpdateResponse] = useState<'success' | 'failed'>() const { @@ -40,28 +33,21 @@ export const SystemVariables = ({ formState: { errors, isSubmitting }, } = useForm({ resolver: yupResolver(validationSchema), - defaultValues: { profitMarginPercent: mixnodeDetails.profit_margin_percent.toString(), signature: '' }, + defaultValues: { profitMarginPercent: mixnodeDetails.profit_margin_percent.toString() }, }) const { userBalance } = useContext(ClientContext) const onSubmit = async (data: TFormData) => { - await unbond(EnumNodeType.mixnode) - await bond({ - type: EnumNodeType.mixnode, - data: { ...mixnodeDetails, profit_margin_percent: data.profitMarginPercent }, - pledge: { denom: 'Minor', amount: pledge.amount }, - //hardcoded for the moment as required in bonding but not necessary - ownerSignature: data.signature, - }) - .then(() => { + try { + await updateMixnode({ profitMarginPercent: data.profitMarginPercent }).then(() => { userBalance.fetchBalance() setNodeUpdateResponse('success') }) - .catch((e) => { - setNodeUpdateResponse('failed') - console.log(e) - }) + } catch (e) { + setNodeUpdateResponse('failed') + console.log(e) + } } return ( @@ -80,13 +66,6 @@ export const SystemVariables = ({ error={!!errors.profitMarginPercent} disabled={isSubmitting} /> - export const getMixnodeBondDetails = async (): Promise => await invoke('mixnode_bond_details') + +export const updateMixnode = async ({ profitMarginPercent }: { profitMarginPercent: number }) => + await invoke('update_mixnode', { profitMarginPercent }) From db2ce8070c9e834238df4363c9a400b86c4aae51 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Thu, 6 Jan 2022 16:16:32 +0000 Subject: [PATCH 05/67] display mixnode update fee --- .../src/pages/settings/system-variables.tsx | 18 ++++++++++++++---- nym-wallet/src/requests/index.ts | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/nym-wallet/src/pages/settings/system-variables.tsx b/nym-wallet/src/pages/settings/system-variables.tsx index 1f131c957e..0ad6b50e25 100644 --- a/nym-wallet/src/pages/settings/system-variables.tsx +++ b/nym-wallet/src/pages/settings/system-variables.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from 'react' +import React, { useContext, useEffect, useState } from 'react' import { Box, Button, @@ -17,8 +17,8 @@ import { useForm } from 'react-hook-form' import { InfoTooltip } from '../../components/InfoToolTip' import { TMixnodeBondDetails } from '../../types' import { validationSchema } from './validationSchema' -import { updateMixnode } from '../../requests' -import { ClientContext } from '../../context/main' +import { getGasFee, updateMixnode } from '../../requests' +import { ClientContext, MAJOR_CURRENCY } from '../../context/main' type TFormData = { profitMarginPercent: number @@ -26,6 +26,7 @@ type TFormData = { export const SystemVariables = ({ mixnodeDetails }: { mixnodeDetails: TMixnodeBondDetails['mix_node'] }) => { const [nodeUpdateResponse, setNodeUpdateResponse] = useState<'success' | 'failed'>() + const [configFee, setConfigFee] = useState() const { register, @@ -36,6 +37,13 @@ export const SystemVariables = ({ mixnodeDetails }: { mixnodeDetails: TMixnodeBo defaultValues: { profitMarginPercent: mixnodeDetails.profit_margin_percent.toString() }, }) + useEffect(() => { + ;(async () => { + const fee = await getGasFee('UpdateMixnodeConfig') + setConfigFee(fee.amount) + })() + }, []) + const { userBalance } = useContext(ClientContext) const onSubmit = async (data: TFormData) => { @@ -107,7 +115,9 @@ export const SystemVariables = ({ mixnodeDetails }: { mixnodeDetails: TMixnodeBo ) : nodeUpdateResponse === 'failed' ? ( Node updated failed ) : ( - + + Fee for this transaction: {`${configFee} ${MAJOR_CURRENCY}`}{' '} + )} diff --git a/nym-wallet/src/pages/delegate/DelegateForm.tsx b/nym-wallet/src/pages/delegate/DelegateForm.tsx index dbcff4c4b0..e8c671daa8 100644 --- a/nym-wallet/src/pages/delegate/DelegateForm.tsx +++ b/nym-wallet/src/pages/delegate/DelegateForm.tsx @@ -115,9 +115,7 @@ export const DelegateForm = ({ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', - borderTop: (theme) => `1px solid ${theme.palette.grey[200]}`, - bgcolor: 'grey.100', - padding: 2, + padding: 3, }} > diff --git a/nym-wallet/src/pages/receive/index.tsx b/nym-wallet/src/pages/receive/index.tsx index 66a41cd792..afab517dc5 100644 --- a/nym-wallet/src/pages/receive/index.tsx +++ b/nym-wallet/src/pages/receive/index.tsx @@ -1,65 +1,37 @@ import React, { useContext } from 'react' import QRCode from 'qrcode.react' -import { Alert, Box, Card, Grid, Typography, useMediaQuery } from '@mui/material' +import { Alert, Box, Stack, Typography } from '@mui/material' import { CopyToClipboard, NymCard } from '../../components' import { Layout } from '../../layouts' import { ClientContext } from '../../context/main' export const Receive = () => { const { clientDetails } = useContext(ClientContext) - const matches = useMediaQuery('(min-width:769px)') return ( - - - - You can receive tokens by providing this address to the sender - - - - + + You can receive tokens by providing this address to the sender + + + - - - - {clientDetails?.client_address} - - - - - - {clientDetails && } - - - - - - + Your address: {clientDetails?.client_address} + + + + + {clientDetails && } + ) diff --git a/nym-wallet/src/pages/send/SendWizard.tsx b/nym-wallet/src/pages/send/SendWizard.tsx index f3965b3d0d..12abd78f24 100644 --- a/nym-wallet/src/pages/send/SendWizard.tsx +++ b/nym-wallet/src/pages/send/SendWizard.tsx @@ -138,9 +138,7 @@ export const SendWizard = () => { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', - borderTop: (theme) => `1px solid ${theme.palette.grey[200]}`, - bgcolor: 'grey.100', - p: 2, + p: 3, }} > {activeStep === 1 && ( @@ -155,6 +153,7 @@ export const SendWizard = () => { data-testid="button" onClick={activeStep === 0 ? handleNextStep : activeStep === 1 ? handleSend : handleFinish} disabled={!!(methods.formState.errors.amount || methods.formState.errors.to || isLoading)} + size="large" > {activeStep === 0 ? 'Next' : activeStep === 1 ? 'Send' : 'Finish'} diff --git a/nym-wallet/src/pages/settings/profile.tsx b/nym-wallet/src/pages/settings/profile.tsx index 228d63e8ad..e9e4c6b0fb 100644 --- a/nym-wallet/src/pages/settings/profile.tsx +++ b/nym-wallet/src/pages/settings/profile.tsx @@ -18,9 +18,7 @@ export const Profile = () => { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', - borderTop: (theme) => `1px solid ${theme.palette.grey[300]}`, - bgcolor: 'grey.200', - padding: 2, + padding: 3, }} > diff --git a/nym-wallet/src/pages/undelegate/UndelegateForm.tsx b/nym-wallet/src/pages/undelegate/UndelegateForm.tsx index aedb9884a9..6f1a7731ab 100644 --- a/nym-wallet/src/pages/undelegate/UndelegateForm.tsx +++ b/nym-wallet/src/pages/undelegate/UndelegateForm.tsx @@ -100,9 +100,7 @@ export const UndelegateForm = ({ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', - borderTop: (theme) => `1px solid ${theme.palette.grey[200]}`, - bgcolor: 'grey.100', - p: 2, + p: 3, }} > diff --git a/nym-wallet/src/pages/undelegate/index.tsx b/nym-wallet/src/pages/undelegate/index.tsx index 4e90d739f9..3e7d57558f 100644 --- a/nym-wallet/src/pages/undelegate/index.tsx +++ b/nym-wallet/src/pages/undelegate/index.tsx @@ -91,9 +91,7 @@ export const Undelegate = () => { display: 'flex', alignItems: 'center', justifyContent: 'flex-end', - borderTop: '1px solid grey[200]', - bgcolor: 'grey.100', - p: 2, + p: 3, }} > diff --git a/nym-wallet/src/theme/theme.tsx b/nym-wallet/src/theme/theme.tsx index 02e4f92b9f..db13d2a16a 100644 --- a/nym-wallet/src/theme/theme.tsx +++ b/nym-wallet/src/theme/theme.tsx @@ -201,6 +201,7 @@ export const getDesignTokens = (mode: PaletteMode): ThemeOptions => { styleOverrides: { sizeLarge: { height: 55, + padding: '13px 24px', }, }, }, From 1cc06ef34921a0c745d2582dbf5ee46ec228f6e4 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Mon, 10 Jan 2022 17:31:43 +0000 Subject: [PATCH 11/67] update get_approx_fee to new function name _outdated_get_approx_fee --- nym-wallet/src/pages/internal-docs/ApiList.tsx | 2 +- nym-wallet/src/requests/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nym-wallet/src/pages/internal-docs/ApiList.tsx b/nym-wallet/src/pages/internal-docs/ApiList.tsx index d55197cff6..997b177a00 100644 --- a/nym-wallet/src/pages/internal-docs/ApiList.tsx +++ b/nym-wallet/src/pages/internal-docs/ApiList.tsx @@ -139,7 +139,7 @@ export const ApiList = () => { diff --git a/nym-wallet/src/requests/index.ts b/nym-wallet/src/requests/index.ts index f07d219540..c1630bc974 100644 --- a/nym-wallet/src/requests/index.ts +++ b/nym-wallet/src/requests/index.ts @@ -28,7 +28,7 @@ export const majorToMinor = async (amount: string): Promise => await invok // NOTE: this uses OUTDATED defaults that might have no resemblance with the reality // as for the actual transaction, the gas cost is being simulated beforehand export const getGasFee = async (operation: Operation): Promise => - await invoke('get_approximate_fee', { operation }) + await invoke('outdated_get_approximate_fee', { operation }) export const delegate = async ({ type, From 5c3c0ac39ed3c22809f5a0bcdb5195f3c273e36a Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Mon, 10 Jan 2022 21:17:47 +0000 Subject: [PATCH 12/67] remove border lines and grey bg for card component --- nym-wallet/src/pages/bond/BondForm.tsx | 1 + nym-wallet/src/pages/bond/index.tsx | 5 ++--- nym-wallet/src/pages/delegate/DelegateForm.tsx | 3 ++- nym-wallet/src/pages/delegate/index.tsx | 3 ++- nym-wallet/src/pages/settings/system-variables.tsx | 3 ++- nym-wallet/src/pages/unbond/index.tsx | 1 + nym-wallet/src/pages/undelegate/index.tsx | 1 + 7 files changed, 11 insertions(+), 6 deletions(-) diff --git a/nym-wallet/src/pages/bond/BondForm.tsx b/nym-wallet/src/pages/bond/BondForm.tsx index 306239eeb8..a17ce3510f 100644 --- a/nym-wallet/src/pages/bond/BondForm.tsx +++ b/nym-wallet/src/pages/bond/BondForm.tsx @@ -376,6 +376,7 @@ export const BondForm = ({ alignItems: 'center', justifyContent: 'flex-end', padding: 3, + pt: 0, }} > )} diff --git a/nym-wallet/src/pages/settings/system-variables.tsx b/nym-wallet/src/pages/settings/system-variables.tsx index 4ffb412e7c..5c2514b870 100644 --- a/nym-wallet/src/pages/settings/system-variables.tsx +++ b/nym-wallet/src/pages/settings/system-variables.tsx @@ -66,8 +66,8 @@ export const SystemVariables = ({ return ( <> - - + + - - + } />} /> - + } />} /> - { const [isLoading, setIsLoading] = useState(false) @@ -20,7 +21,7 @@ export const Unbond = () => { return ( - + {ownership?.hasOwnership && ( - + - - - )} - - - - Checkout the{' '} - - list of mixnodes - {' '} - for uptime and performances to help make delegation decisions - + + + + )} + + + + Checkout the{' '} + + list of mixnodes + {' '} + for uptime and performances to help make delegation decisions + + ) } diff --git a/nym-wallet/src/pages/unbond/index.tsx b/nym-wallet/src/pages/unbond/index.tsx index c66078b196..f1ecadbd3c 100644 --- a/nym-wallet/src/pages/unbond/index.tsx +++ b/nym-wallet/src/pages/unbond/index.tsx @@ -22,36 +22,38 @@ export const Unbond = () => { return ( - {ownership?.hasOwnership && ( - { - setIsLoading(true) - await unbond(ownership.nodeType) - await userBalance.fetchBalance() - await getBondDetails() - await checkOwnership() - setIsLoading(false) - }} - color="inherit" - > - Unbond - - } - sx={{ m: 2 }} - > - {`Looks like you already have a ${ownership.nodeType} bonded.`} - - )} - - - - {!ownership.hasOwnership && ( + {ownership?.hasOwnership ? ( + <> + { + setIsLoading(true) + await unbond(ownership.nodeType) + await userBalance.fetchBalance() + await getBondDetails() + await checkOwnership() + setIsLoading(false) + }} + color="inherit" + > + Unbond + + } + sx={{ m: 2 }} + > + {`Looks like you already have a ${ownership.nodeType} bonded.`} + + + + + + + ) : ( You don't currently have a bonded node From 5190a541a684612628bffe3a349da1f3d1fbfa01 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Tue, 18 Jan 2022 11:30:56 +0000 Subject: [PATCH 57/67] add warning to delegate page --- nym-wallet/src/pages/delegate/index.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nym-wallet/src/pages/delegate/index.tsx b/nym-wallet/src/pages/delegate/index.tsx index 73871c0a92..2895c08285 100644 --- a/nym-wallet/src/pages/delegate/index.tsx +++ b/nym-wallet/src/pages/delegate/index.tsx @@ -24,6 +24,9 @@ export const Delegate = () => { Icon={DelegateIcon} > <> + + Always ensure you leave yourself enough funds to UNDELEGATE + {status === EnumRequestStatus.initial && ( { From aa75e54419a68bf7cf50ad9636c85927b442c4c1 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Tue, 18 Jan 2022 11:45:27 +0000 Subject: [PATCH 58/67] dont display warnings on successful bond or delegate --- nym-wallet/src/pages/bond/index.tsx | 8 +++++--- nym-wallet/src/pages/delegate/index.tsx | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/nym-wallet/src/pages/bond/index.tsx b/nym-wallet/src/pages/bond/index.tsx index 7dfb7b994c..e53b704988 100644 --- a/nym-wallet/src/pages/bond/index.tsx +++ b/nym-wallet/src/pages/bond/index.tsx @@ -31,9 +31,11 @@ export const Bond = () => { return ( - - Always ensure you leave yourself enough funds to UNBOND - + {status === EnumRequestStatus.initial && ( + + Always ensure you leave yourself enough funds to UNBOND + + )} {ownership?.hasOwnership && ( { Icon={DelegateIcon} > <> - - Always ensure you leave yourself enough funds to UNDELEGATE - + {status === EnumRequestStatus.initial && ( + + Always ensure you leave yourself enough funds to UNDELEGATE + + )} {status === EnumRequestStatus.initial && ( { From e3d8b71ea2a71742cf5d692708eb3f45e9aa5f09 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Tue, 18 Jan 2022 21:26:15 +0100 Subject: [PATCH 59/67] Endpoint for rewarded set inclusion probabilities (#1042) * Add validator-api endpoint * Add validator-client method * Make it a bit nicer * Address review comments * Cap probability at 1. --- Cargo.lock | 28 ++- .../validator-client/src/validator_api/mod.rs | 18 ++ .../src/validator_api/routes.rs | 1 + validator-api/Cargo.toml | 5 +- validator-api/src/cache/mod.rs | 212 ++++++++++++++++-- validator-api/src/cache/routes.rs | 13 ++ 6 files changed, 260 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fde6d7bcee..f076131607 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -208,7 +208,24 @@ dependencies = [ "serde_json", "serde_urlencoded 0.6.1", "url", - "wildmatch", + "wildmatch 1.1.0", +] + +[[package]] +name = "attohttpc" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e69e13a99a7e6e070bb114f7ff381e58c7ccc188630121fc4c2fe4bcf24cd072" +dependencies = [ + "flate2", + "http", + "log", + "native-tls", + "openssl", + "serde", + "serde_json", + "url", + "wildmatch 2.1.0", ] [[package]] @@ -3861,6 +3878,7 @@ name = "nym-validator-api" version = "0.12.0" dependencies = [ "anyhow", + "attohttpc 0.18.0", "clap", "coconut-interface", "config", @@ -6580,7 +6598,7 @@ version = "1.0.0-beta.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79a0579dcc6fb883fe90dd3c66d76b8b8f4a1786e1e915e314b2017a500ede09" dependencies = [ - "attohttpc", + "attohttpc 0.17.0", "bincode", "cfg_aliases", "dirs-next", @@ -7768,6 +7786,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f44b95f62d34113cf558c93511ac93027e03e9c29a60dd0fd70e6e025c7270a" +[[package]] +name = "wildmatch" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c48bd20df7e4ced539c12f570f937c6b4884928a87fee70a479d72f031d4e0" + [[package]] name = "winapi" version = "0.3.9" diff --git a/common/client-libs/validator-client/src/validator_api/mod.rs b/common/client-libs/validator-client/src/validator_api/mod.rs index 6511b1157c..528f3b9939 100644 --- a/common/client-libs/validator-client/src/validator_api/mod.rs +++ b/common/client-libs/validator-client/src/validator_api/mod.rs @@ -6,6 +6,7 @@ use crate::validator_api::routes::{CORE_STATUS_COUNT, SINCE_ARG}; use coconut_interface::{BlindSignRequestBody, BlindedSignatureResponse, VerificationKeyResponse}; use mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixNodeBond}; use serde::{Deserialize, Serialize}; +use std::collections::HashMap; use url::Url; use validator_api_requests::models::{ CoreNodeStatusResponse, MixnodeStatusResponse, RewardEstimationResponse, @@ -101,6 +102,23 @@ impl Client { .await } + pub async fn get_probs_mixnode_rewarded( + &self, + mixnode_id: &str, + ) -> Result, ValidatorAPIError> { + self.query_validator_api( + &[ + routes::API_VERSION, + routes::MIXNODES, + routes::REWARDED, + routes::INCLUSION_CHANCE, + &mixnode_id.to_string(), + ], + NO_PARAMS, + ) + .await + } + pub async fn get_gateway_core_status_count( &self, identity: IdentityKeyRef<'_>, diff --git a/common/client-libs/validator-client/src/validator_api/routes.rs b/common/client-libs/validator-client/src/validator_api/routes.rs index 7259ed6610..e7785d4ec5 100644 --- a/common/client-libs/validator-client/src/validator_api/routes.rs +++ b/common/client-libs/validator-client/src/validator_api/routes.rs @@ -23,3 +23,4 @@ pub const SINCE_ARG: &str = "since"; pub const STATUS: &str = "status"; pub const REWARD_ESTIMATION: &str = "reward-estimation"; pub const STAKE_SATURATION: &str = "stake-saturation"; +pub const INCLUSION_CHANCE: &str = "inclusion-probability"; diff --git a/validator-api/Cargo.toml b/validator-api/Cargo.toml index 2e12d321ab..4b0b9bf318 100644 --- a/validator-api/Cargo.toml +++ b/validator-api/Cargo.toml @@ -62,4 +62,7 @@ coconut = ["coconut-interface", "credentials", "gateway-client/coconut"] [build-dependencies] tokio = { version = "1.4", features = ["rt-multi-thread", "macros"] } sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "sqlite", "macros", "migrate"] } -vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] } \ No newline at end of file +vergen = { version = "5", default-features = false, features = ["build", "git", "rustc", "cargo"] } + +[dev-dependencies] +attohttpc = {version = "0.18.0", features = ["json"]} \ No newline at end of file diff --git a/validator-api/src/cache/mod.rs b/validator-api/src/cache/mod.rs index 865af46966..5efc838025 100644 --- a/validator-api/src/cache/mod.rs +++ b/validator-api/src/cache/mod.rs @@ -14,7 +14,9 @@ use rand::prelude::SliceRandom; use rand_chacha::rand_core::SeedableRng; use rand_chacha::ChaCha20Rng; use rocket::fairing::AdHoc; -use serde::Serialize; +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; +use std::fmt; use std::sync::atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}; use std::sync::Arc; use std::time::Duration; @@ -26,6 +28,22 @@ use validator_client::nymd::CosmWasmClient; pub(crate) mod routes; +#[derive(Debug, Serialize, Deserialize)] +pub struct InclusionProbabilityResponse { + in_active: f32, + in_reserve: f32, +} + +impl fmt::Display for InclusionProbabilityResponse { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "in_active: {:.5}, in_reserve: {:.5}", + self.in_active, self.in_reserve + ) + } +} + pub struct ValidatorCacheRefresher { nymd_client: Client, cache: ValidatorCache, @@ -178,6 +196,7 @@ impl ValidatorCache { routes::get_gateways, routes::get_active_mixnodes, routes::get_rewarded_mixnodes, + routes::get_probs_mixnode_rewarded ], ) }) @@ -214,31 +233,152 @@ impl ValidatorCache { // seed our rng with the hash of the block of the most recent rewarding interval let mut rng = ChaCha20Rng::from_seed(block_hash.unwrap()); - // generate list of mixnodes and their relatively weight (by total stake) - let choices = mixnodes - .iter() - .map(|mix| { - // note that the theoretical maximum possible stake is equal to the total - // supply of all tokens, i.e. 1B (which is 1 quadrillion of native tokens, i.e. 10^15 ~ 2^50) - // which is way below maximum value of f64, so the cast is fine - let total_stake = mix.total_stake().unwrap_or_default() as f64; - (mix, total_stake) - }) // if for some reason node is invalid, treat it as 0 stake/weight - .collect::>(); + self.stake_weighted_choice(mixnodes, nodes_to_select as usize, &mut rng) + } + fn stake_weighted_choice( + &self, + mixnodes: &[MixNodeBond], + nodes_to_select: usize, + rng: &mut ChaCha20Rng, + ) -> Vec { + // generate list of mixnodes and their relatively weight (by total stake) + let choices = self.generate_mixnode_stake_tuples(mixnodes); // the unwrap here is fine as an error can only be thrown under one of the following conditions: // - our mixnode list is empty - we have already checked for that // - we have invalid weights, i.e. less than zero or NaNs - it shouldn't happen in our case as we safely cast down from u128 // - all weights are zero - it's impossible in our case as the list of nodes is not empty and weight is proportional to stake. You must have non-zero stake in order to bond // - we have more than u32::MAX values (which is incredibly unrealistic to have 4B mixnodes bonded... literally every other person on the planet would need one) choices - .choose_multiple_weighted(&mut rng, nodes_to_select as usize, |item| item.1) + .choose_multiple_weighted(rng, nodes_to_select, |item| item.1) .unwrap() - .map(|(bond, _weight)| *bond) + .map(|(bond, _weight)| bond) .cloned() .collect() } + fn generate_mixnode_stake_tuples(&self, mixnodes: &[MixNodeBond]) -> Vec<(MixNodeBond, f64)> { + mixnodes + .iter() + .map(|mix| { + // note that the theoretical maximum possible stake is equal to the total + // supply of all tokens, i.e. 1B (which is 1 quadrillion of native tokens, i.e. 10^15 ~ 2^50) + // which is way below maximum value of f64, so the cast is fine + let total_stake = mix.total_stake().unwrap_or_default() as f64; + (mix.clone(), total_stake) + }) // if for some reason node is invalid, treat it as 0 stake/weight + .collect() + } + + // Estimate probability that a node will end up in the rewarded set, by running the selection process 100 times and aggregating the results. + // If a node is in the active set it is not counted as being in the reserve set, the probabilities are exclusive. Cumulative probabilitiy + // can be obtained by summing the two probabilities returned. + async fn probs_mixnode_rewarded_calculate( + &self, + target_mixnode_id: IdentityKey, + mixnodes: Option>, + ) -> Option { + let mixnodes = if let Some(nodes) = mixnodes { + nodes + } else { + self.inner.mixnodes.read().await.value.clone() + }; + let total_bonded_tokens = mixnodes + .iter() + .fold(0u128, |acc, x| acc + x.total_stake().unwrap_or_default()) + as f64; + let target_mixnode = mixnodes + .iter() + .find(|x| x.identity() == &target_mixnode_id)?; + let rewarded_set_size = self + .inner + .current_mixnode_rewarded_set_size + .load(Ordering::SeqCst) as f64; + let active_set_size = self + .inner + .current_mixnode_active_set_size + .load(Ordering::SeqCst) as f64; + + // For running comparison tests below, needs improvement + // let rewarded_set_size = 720.; + // let active_set_size = 300.; + + let prob_one_draw = + target_mixnode.total_stake().unwrap_or_default() as f64 / total_bonded_tokens; + // Chance to be selected in any draw for active set + let prob_active_set = active_set_size * prob_one_draw; + // This is likely slightly too high, as we're not correcting form them not being selected in active, should be chance to be selected, minus the chance for being not selected in reserve + let prob_reserve_set = (rewarded_set_size - active_set_size) * prob_one_draw; + // (rewarded_set_size - active_set_size) * prob_one_draw * (1. - prob_active_set); + + Some(InclusionProbabilityResponse { + in_active: if prob_active_set > 1. { + 1. + } else { + prob_active_set + } as f32, + in_reserve: if prob_reserve_set > 1. { + 1. + } else { + prob_reserve_set + } as f32, + }) + } + + #[allow(dead_code)] + async fn probs_mixnode_rewarded_simulate( + &self, + target_mixnode_id: IdentityKey, + mixnodes: Option>, + ) -> Option { + let mut in_active = 0; + let mut in_reserve = 0; + let mixnodes = if let Some(nodes) = mixnodes { + nodes + } else { + self.inner.mixnodes.read().await.value.clone() + }; + let rewarded_set_size = self + .inner + .current_mixnode_rewarded_set_size + .load(Ordering::SeqCst) as usize; + let active_set_size = self + .inner + .current_mixnode_active_set_size + .load(Ordering::SeqCst) as usize; + let mut rng = ChaCha20Rng::from_entropy(); + + // For running comparison tests below, needs improvement + // let rewarded_set_size = 720.; + // let active_set_size = 300.; + + let it = 100; + + for _ in 0..it { + let mut rewarded_set = + self.stake_weighted_choice(&mixnodes, rewarded_set_size, &mut rng); + let reserve_set = rewarded_set + .split_off(active_set_size) + .iter() + .map(|bond| bond.identity().clone()) + .collect::>(); + let active_set = rewarded_set + .iter() + .map(|bond| bond.identity().clone()) + .collect::>(); + + if active_set.contains(&target_mixnode_id) { + in_active += 1; + } else if reserve_set.contains(&target_mixnode_id) { + in_reserve += 1; + } + } + Some(InclusionProbabilityResponse { + in_active: in_active as f32 / it as f32, + in_reserve: in_reserve as f32 / it as f32, + }) + } + async fn update_cache( &self, mixnodes: Vec, @@ -415,3 +555,47 @@ impl ValidatorCacheInner { } } } + +// #[cfg(test)] +// mod test { +// use crate::cache::InclusionProbabilityResponse; + +// use super::ValidatorCache; +// use mixnet_contract_common::MixNodeBond; + +// #[tokio::test] +// async fn test_inclusion_probabilities() { +// let cache = ValidatorCache::new(); +// let response = attohttpc::get("https://sandbox-validator.nymtech.net/api/v1/mixnodes") +// .send() +// .unwrap(); +// let mixnodes: Vec = response.json().unwrap(); +// let calculated = cache +// .probs_mixnode_rewarded_calculate( +// "ysmgeYJQPBFzB2TgqBjN5BE3Rb79CgFANrnJaYd8woQ".to_string(), +// Some(mixnodes.clone()), +// ) +// .await +// .unwrap(); +// let mut simulated_avg = Vec::new(); +// for _ in 0..1000 { +// let simulated = cache +// .probs_mixnode_rewarded_simulate( +// "ysmgeYJQPBFzB2TgqBjN5BE3Rb79CgFANrnJaYd8woQ".to_string(), +// Some(mixnodes.clone()), +// ) +// .await +// .unwrap(); +// simulated_avg.push(simulated); +// } +// let simulated = simulated_avg.iter().fold((0., 0.), |acc, x| { +// (acc.0 + x.in_active, acc.1 + x.in_reserve) +// }); +// let simulated = InclusionProbabilityResponse { +// in_active: simulated.0 / 100., +// in_reserve: simulated.1 / 100., +// }; +// println!("calculted: {calculated}"); +// println!("simulated: {simulated}"); +// } +// } diff --git a/validator-api/src/cache/routes.rs b/validator-api/src/cache/routes.rs index be902cd311..c08c520b7f 100644 --- a/validator-api/src/cache/routes.rs +++ b/validator-api/src/cache/routes.rs @@ -1,6 +1,7 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::cache::InclusionProbabilityResponse; use crate::cache::ValidatorCache; use mixnet_contract_common::{GatewayBond, MixNodeBond}; use rocket::serde::json::Json; @@ -25,3 +26,15 @@ pub(crate) async fn get_rewarded_mixnodes(cache: &State) -> Json pub(crate) async fn get_active_mixnodes(cache: &State) -> Json> { Json(cache.active_mixnodes().await.value) } + +#[get("/mixnodes/rewarded/inclusion-probability/")] +pub(crate) async fn get_probs_mixnode_rewarded( + cache: &State, + mixnode_id: String, +) -> Json> { + Json( + cache + .probs_mixnode_rewarded_calculate(mixnode_id, None) + .await, + ) +} From 522229459b19e8b13cb12099d89eac5323494662 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Tue, 18 Jan 2022 22:37:51 +0200 Subject: [PATCH 60/67] Fix clippy on relevant lints (#1044) * Fix clippy on relevant lints return_self_not_must_use still produces errors, but that will be auto-fixed once the change to move it to pedantic is released to beta channel * Run fmt --- clients/client-core/src/client/key_manager.rs | 4 ++-- clients/client-core/src/client/reply_key_storage.rs | 4 ++-- clients/native/examples/websocket_binarysend.rs | 4 ++-- clients/native/examples/websocket_textsend.rs | 4 ++-- .../src/nymd/cosmwasm_client/signing_client.rs | 5 +---- common/crypto/src/hkdf.rs | 2 +- common/nymsphinx/src/preparer/mod.rs | 8 +------- .../src/registration/handshake/shared_key.rs | 6 +++--- validator-api/src/main.rs | 2 +- 9 files changed, 15 insertions(+), 24 deletions(-) diff --git a/clients/client-core/src/client/key_manager.rs b/clients/client-core/src/client/key_manager.rs index f852af3806..2e7069a6a8 100644 --- a/clients/client-core/src/client/key_manager.rs +++ b/clients/client-core/src/client/key_manager.rs @@ -79,9 +79,9 @@ impl KeyManager { ))?; let gateway_shared_key: SharedKeys = - pemstore::load_key(&client_pathfinder.gateway_shared_key().to_owned())?; + pemstore::load_key(client_pathfinder.gateway_shared_key())?; - let ack_key: AckKey = pemstore::load_key(&client_pathfinder.ack_key().to_owned())?; + let ack_key: AckKey = pemstore::load_key(client_pathfinder.ack_key())?; // TODO: ack key is never stored so it is generated now. But perhaps it should be stored // after all for consistency sake? diff --git a/clients/client-core/src/client/reply_key_storage.rs b/clients/client-core/src/client/reply_key_storage.rs index e934e4c5b3..e9ed2be1c8 100644 --- a/clients/client-core/src/client/reply_key_storage.rs +++ b/clients/client-core/src/client/reply_key_storage.rs @@ -59,7 +59,7 @@ impl ReplyKeyStorage { ) -> Result<(), ReplyKeyStorageError> { let digest = encryption_key.compute_digest(); - let insertion_result = match self.db.insert(digest.to_vec(), encryption_key.to_bytes()) { + let insertion_result = match self.db.insert(digest, encryption_key.to_bytes()) { Err(e) => Err(ReplyKeyStorageError::DbWriteError(e)), Ok(existing_key) => { if existing_key.is_some() { @@ -79,7 +79,7 @@ impl ReplyKeyStorage { &self, key_digest: EncryptionKeyDigest, ) -> Result, ReplyKeyStorageError> { - let removal_result = match self.db.remove(&key_digest.to_vec()) { + let removal_result = match self.db.remove(key_digest) { Err(e) => Err(ReplyKeyStorageError::DbReadError(e)), Ok(existing_key) => { Ok(existing_key.map(|existing_key| self.read_encryption_key(existing_key))) diff --git a/clients/native/examples/websocket_binarysend.rs b/clients/native/examples/websocket_binarysend.rs index 3e9f563a0d..7ddf53e913 100644 --- a/clients/native/examples/websocket_binarysend.rs +++ b/clients/native/examples/websocket_binarysend.rs @@ -35,7 +35,7 @@ async fn send_file_with_reply() { let (mut ws_stream, _) = connect_async(uri).await.unwrap(); let recipient = get_self_address(&mut ws_stream).await; - println!("our full address is: {}", recipient.to_string()); + println!("our full address is: {}", recipient); let read_data = std::fs::read("examples/dummy_file").unwrap(); @@ -83,7 +83,7 @@ async fn send_file_without_reply() { let (mut ws_stream, _) = connect_async(uri).await.unwrap(); let recipient = get_self_address(&mut ws_stream).await; - println!("our full address is: {}", recipient.to_string()); + println!("our full address is: {}", recipient); let read_data = std::fs::read("examples/dummy_file").unwrap(); diff --git a/clients/native/examples/websocket_textsend.rs b/clients/native/examples/websocket_textsend.rs index 6804ea53b4..a6d00aa55f 100644 --- a/clients/native/examples/websocket_textsend.rs +++ b/clients/native/examples/websocket_textsend.rs @@ -36,7 +36,7 @@ async fn send_text_with_reply() { let (mut ws_stream, _) = connect_async(uri).await.unwrap(); let recipient = get_self_address(&mut ws_stream).await; - println!("our full address is: {}", recipient.to_string()); + println!("our full address is: {}", recipient); let send_request = json!({ "type" : "send", @@ -76,7 +76,7 @@ async fn send_text_without_reply() { let (mut ws_stream, _) = connect_async(uri).await.unwrap(); let recipient = get_self_address(&mut ws_stream).await; - println!("our full address is: {}", recipient.to_string()); + println!("our full address is: {}", recipient); let send_request = json!({ "type" : "send", diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs index 977b5a75f1..53067c6690 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/signing_client.rs @@ -160,10 +160,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { { let init_msg = cosmwasm::MsgInstantiateContract { sender: sender_address.clone(), - admin: options - .as_mut() - .map(|options| options.admin.take()) - .flatten(), + admin: options.as_mut().and_then(|options| options.admin.take()), code_id, // now this is a weird one. the protobuf files say this field is optional, // but if you omit it, the initialisation will fail CheckTx diff --git a/common/crypto/src/hkdf.rs b/common/crypto/src/hkdf.rs index 5300947448..141b5c607c 100644 --- a/common/crypto/src/hkdf.rs +++ b/common/crypto/src/hkdf.rs @@ -22,7 +22,7 @@ where let hkdf = Hkdf::::new(salt, ikm); let mut okm = vec![0u8; okm_length]; - hkdf.expand(info.unwrap_or_else(|| &[]), &mut okm)?; + hkdf.expand(info.unwrap_or(&[]), &mut okm)?; Ok(okm) } diff --git a/common/nymsphinx/src/preparer/mod.rs b/common/nymsphinx/src/preparer/mod.rs index 06ead921fe..63d2f67559 100644 --- a/common/nymsphinx/src/preparer/mod.rs +++ b/common/nymsphinx/src/preparer/mod.rs @@ -386,13 +386,7 @@ where // (note: surb_ack_bytes contains SURB_ACK_FIRST_HOP || SURB_ACK_DATA ) let packet_payload: Vec<_> = surb_ack_bytes .into_iter() - .chain( - reply_surb - .encryption_key() - .compute_digest() - .to_vec() - .into_iter(), - ) + .chain(reply_surb.encryption_key().compute_digest().iter().copied()) .chain(reply_content.into_iter()) .collect(); diff --git a/gateway/gateway-requests/src/registration/handshake/shared_key.rs b/gateway/gateway-requests/src/registration/handshake/shared_key.rs index afa0d2a842..23c69409cc 100644 --- a/gateway/gateway-requests/src/registration/handshake/shared_key.rs +++ b/gateway/gateway-requests/src/registration/handshake/shared_key.rs @@ -148,9 +148,9 @@ impl SharedKeys { pub fn to_bytes(&self) -> Vec { self.encryption_key - .to_vec() - .into_iter() - .chain(self.mac_key.to_vec().into_iter()) + .iter() + .copied() + .chain(self.mac_key.iter().copied()) .collect() } diff --git a/validator-api/src/main.rs b/validator-api/src/main.rs index 589b06d25f..a32995ea2c 100644 --- a/validator-api/src/main.rs +++ b/validator-api/src/main.rs @@ -302,7 +302,7 @@ fn override_config(mut config: Config, matches: &ArgMatches) -> Config { let monitor_threshold = monitor_threshold.expect("Provided monitor threshold is not a number!"); assert!( - !(monitor_threshold > 100), + monitor_threshold <= 100, "Provided monitor threshold is greater than 100!" ); config = config.with_minimum_epoch_monitor_threshold(monitor_threshold) From c61f89144e4742c6156e94093161500155e5b0b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jan 2022 14:00:06 +0000 Subject: [PATCH 61/67] Bump follow-redirects from 1.14.5 to 1.14.7 in /testnet-faucet (#1040) Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.5 to 1.14.7. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.5...v1.14.7) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- testnet-faucet/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testnet-faucet/yarn.lock b/testnet-faucet/yarn.lock index 15a51c7dc6..8e8a21efd3 100644 --- a/testnet-faucet/yarn.lock +++ b/testnet-faucet/yarn.lock @@ -2674,9 +2674,9 @@ find-root@^1.1.0: integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== follow-redirects@^1.0.0, follow-redirects@^1.14.0: - version "1.14.5" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.5.tgz#f09a5848981d3c772b5392309778523f8d85c381" - integrity sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA== + version "1.14.7" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.7.tgz#2004c02eb9436eee9a21446a6477debf17e81685" + integrity sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ== foreach@^2.0.5: version "2.0.5" From 135f1a6e7f32a322177100b1cedc7f1cc38316c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jan 2022 14:00:12 +0000 Subject: [PATCH 62/67] Bump follow-redirects in /contracts/basic-bandwidth-generation (#1041) Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.4 to 1.14.7. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.4...v1.14.7) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- contracts/basic-bandwidth-generation/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/basic-bandwidth-generation/package-lock.json b/contracts/basic-bandwidth-generation/package-lock.json index eba9f64f04..9b62362542 100644 --- a/contracts/basic-bandwidth-generation/package-lock.json +++ b/contracts/basic-bandwidth-generation/package-lock.json @@ -5059,9 +5059,9 @@ } }, "follow-redirects": { - "version": "1.14.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz", - "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==", + "version": "1.14.7", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.7.tgz", + "integrity": "sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==", "dev": true }, "for-each": { From 5b6c1c032ccb11acab66f7eff63ac5e6e6ec7762 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jan 2022 14:00:19 +0000 Subject: [PATCH 63/67] Bump shelljs in /contracts/basic-bandwidth-generation (#1043) Bumps [shelljs](https://github.com/shelljs/shelljs) from 0.8.4 to 0.8.5. - [Release notes](https://github.com/shelljs/shelljs/releases) - [Changelog](https://github.com/shelljs/shelljs/blob/master/CHANGELOG.md) - [Commits](https://github.com/shelljs/shelljs/compare/v0.8.4...v0.8.5) --- updated-dependencies: - dependency-name: shelljs dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- contracts/basic-bandwidth-generation/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/basic-bandwidth-generation/package-lock.json b/contracts/basic-bandwidth-generation/package-lock.json index 9b62362542..4276fd8631 100644 --- a/contracts/basic-bandwidth-generation/package-lock.json +++ b/contracts/basic-bandwidth-generation/package-lock.json @@ -17880,9 +17880,9 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, "shelljs": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", - "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", + "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", "dev": true, "requires": { "glob": "^7.0.0", From 93e962524a4ac09675596b575521091742053ac4 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Wed, 19 Jan 2022 14:05:43 +0000 Subject: [PATCH 64/67] update types --- clients/validator/src/types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/clients/validator/src/types.ts b/clients/validator/src/types.ts index 9b8dc11b2c..786b72ff6c 100644 --- a/clients/validator/src/types.ts +++ b/clients/validator/src/types.ts @@ -135,6 +135,7 @@ export type MixNode = { sphinx_key: string; identity_key: string; version: string; + profit_margin_percent: number; }; export type GatewayBond = { From ecdbe034fa76d8a7fd1a465c86a403f7fab4edb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 19 Jan 2022 19:32:48 +0000 Subject: [PATCH 65/67] Implemented beta clippy lint recommendations (#1051) --- common/client-libs/validator-client/src/client.rs | 1 + common/client-libs/validator-client/src/nymd/wallet/mod.rs | 1 + common/client-libs/validator-client/src/validator_api/mod.rs | 2 +- common/mixnode-common/src/verloc/mod.rs | 1 + common/nymsphinx/src/preparer/mod.rs | 1 + common/nymsphinx/src/receiver.rs | 1 + common/topology/src/filter.rs | 1 + common/topology/src/lib.rs | 2 ++ 8 files changed, 9 insertions(+), 1 deletion(-) diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 4a3653fb21..2caf379fe6 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -25,6 +25,7 @@ use validator_api_requests::models::{ }; #[cfg(feature = "nymd-client")] +#[must_use] pub struct Config { api_url: Url, nymd_url: Url, diff --git a/common/client-libs/validator-client/src/nymd/wallet/mod.rs b/common/client-libs/validator-client/src/nymd/wallet/mod.rs index 195ebc9239..8abbccb7a0 100644 --- a/common/client-libs/validator-client/src/nymd/wallet/mod.rs +++ b/common/client-libs/validator-client/src/nymd/wallet/mod.rs @@ -133,6 +133,7 @@ impl DirectSecp256k1HdWallet { } } +#[must_use] pub struct DirectSecp256k1HdWalletBuilder { /// The password to use when deriving a BIP39 seed from a mnemonic. bip39_password: String, diff --git a/common/client-libs/validator-client/src/validator_api/mod.rs b/common/client-libs/validator-client/src/validator_api/mod.rs index 528f3b9939..c30a76b174 100644 --- a/common/client-libs/validator-client/src/validator_api/mod.rs +++ b/common/client-libs/validator-client/src/validator_api/mod.rs @@ -112,7 +112,7 @@ impl Client { routes::MIXNODES, routes::REWARDED, routes::INCLUSION_CHANCE, - &mixnode_id.to_string(), + mixnode_id, ], NO_PARAMS, ) diff --git a/common/mixnode-common/src/verloc/mod.rs b/common/mixnode-common/src/verloc/mod.rs index 52060cb804..623bdc9e18 100644 --- a/common/mixnode-common/src/verloc/mod.rs +++ b/common/mixnode-common/src/verloc/mod.rs @@ -77,6 +77,7 @@ impl Config { } } +#[must_use] pub struct ConfigBuilder(Config); impl ConfigBuilder { diff --git a/common/nymsphinx/src/preparer/mod.rs b/common/nymsphinx/src/preparer/mod.rs index 63d2f67559..3f56f9bada 100644 --- a/common/nymsphinx/src/preparer/mod.rs +++ b/common/nymsphinx/src/preparer/mod.rs @@ -54,6 +54,7 @@ impl From for PreparationError { /// an optional reply-SURB, padding it to appropriate length, encrypting its content, /// and chunking into appropriate size [`Fragment`]s. #[cfg_attr(not(target_arch = "wasm32"), derive(Clone))] +#[must_use] pub struct MessagePreparer { /// Instance of a cryptographically secure random number generator. rng: R, diff --git a/common/nymsphinx/src/receiver.rs b/common/nymsphinx/src/receiver.rs index d06afa9a91..1707983ca3 100644 --- a/common/nymsphinx/src/receiver.rs +++ b/common/nymsphinx/src/receiver.rs @@ -58,6 +58,7 @@ impl MessageReceiver { } /// Allows setting non-default number of expected mix hops in the network. + #[must_use] pub fn with_mix_hops(mut self, hops: u8) -> Self { self.num_mix_hops = hops; self diff --git a/common/topology/src/filter.rs b/common/topology/src/filter.rs index 652b5f92e9..6c1a3c2fe1 100644 --- a/common/topology/src/filter.rs +++ b/common/topology/src/filter.rs @@ -9,6 +9,7 @@ pub trait Versioned: Clone { } pub trait VersionFilterable { + #[must_use] fn filter_by_version(&self, expected_version: &str) -> Self; } diff --git a/common/topology/src/lib.rs b/common/topology/src/lib.rs index c14256512a..7d5b9843d4 100644 --- a/common/topology/src/lib.rs +++ b/common/topology/src/lib.rs @@ -206,10 +206,12 @@ impl NymTopology { true } + #[must_use] pub fn filter_system_version(&self, expected_version: &str) -> Self { self.filter_node_versions(expected_version, expected_version) } + #[must_use] pub fn filter_node_versions( &self, expected_mix_version: &str, From 8b166f12f8eeb1e2ed1044eda6b0da691b6eb00f Mon Sep 17 00:00:00 2001 From: durch Date: Thu, 20 Jan 2022 11:17:33 +0100 Subject: [PATCH 66/67] Instrument tokio console --- Cargo.lock | 262 ++++++++++++++++++++++++++++++++++++-- validator-api/Cargo.toml | 3 + validator-api/src/main.rs | 5 + 3 files changed, 258 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f8213321ad..c67a8649c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -771,6 +771,41 @@ dependencies = [ "url", ] +[[package]] +name = "console-api" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f67643a7d716307ad10b3e3aef02826382acbe349a3e7605ac57556148bc87" +dependencies = [ + "prost", + "prost-types", + "tonic", + "tonic-build", + "tracing-core", +] + +[[package]] +name = "console-subscriber" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829835c211a0247cd11e65e13cec8696b879374879c35ce162ce8098b23c90d4" +dependencies = [ + "console-api", + "crossbeam-channel", + "futures", + "hdrhistogram", + "humantime 2.1.0", + "serde", + "serde_json", + "thread_local", + "tokio", + "tokio-stream", + "tonic", + "tracing", + "tracing-core", + "tracing-subscriber", +] + [[package]] name = "console_error_panic_hook" version = "0.1.6" @@ -1941,6 +1976,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "fixedbitset" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" + [[package]] name = "flate2" version = "1.0.22" @@ -2596,9 +2637,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.4" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7f3675cfef6a30c8031cf9e6493ebdc3bb3272a3fea3923c4210d1830e6a472" +checksum = "0c9de88456263e249e241fcd211d3954e2c9b0ef7ccfc235a444eb367cae3689" dependencies = [ "bytes", "fnv", @@ -2651,6 +2692,19 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "hdrhistogram" +version = "7.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6490be71f07a5f62b564bc58e36953f675833df11c7e4a0647bee7a07ca1ec5e" +dependencies = [ + "base64", + "byteorder", + "flate2", + "nom", + "num-traits", +] + [[package]] name = "headers" version = "0.3.4" @@ -2753,9 +2807,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "399c583b2979440c60be0821a6199eca73bc3c8dcd9d070d75ac726e2c6186e5" +checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" dependencies = [ "bytes", "http", @@ -2807,9 +2861,9 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.13" +version = "0.14.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15d1cfb9e4f68655fa04c01f59edb405b6074a0f7118ea881e5026e4a1cd8593" +checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55" dependencies = [ "bytes", "futures-channel", @@ -2865,6 +2919,18 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -3481,6 +3547,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + [[package]] name = "native-tls" version = "0.2.8" @@ -3880,9 +3952,11 @@ version = "0.12.0" dependencies = [ "anyhow", "attohttpc 0.18.0", + "cfg-if 1.0.0", "clap", "coconut-interface", "config", + "console-subscriber", "credentials", "crypto", "dirs", @@ -4393,6 +4467,16 @@ dependencies = [ "sha-1 0.8.2", ] +[[package]] +name = "petgraph" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "phf" version = "0.8.0" @@ -4735,6 +4819,26 @@ dependencies = [ "prost-derive", ] +[[package]] +name = "prost-build" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" +dependencies = [ + "bytes", + "heck", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph", + "prost", + "prost-types", + "regex", + "tempfile", + "which", +] + [[package]] name = "prost-derive" version = "0.9.0" @@ -5851,6 +5955,15 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -6995,11 +7108,10 @@ dependencies = [ [[package]] name = "tokio" -version = "1.12.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc" +checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838" dependencies = [ - "autocfg 1.0.1", "bytes", "libc", "memchr", @@ -7010,14 +7122,25 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "tokio-macros", + "tracing", "winapi", ] [[package]] -name = "tokio-macros" -version = "1.3.0" +name = "tokio-io-timeout" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54473be61f4ebe4efd09cec9bd5d16fa51d70ea0192213d754d2d500457db110" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-macros" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ "proc-macro2", "quote", @@ -7107,6 +7230,49 @@ dependencies = [ "serde", ] +[[package]] +name = "tonic" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff08f4649d10a70ffa3522ca559031285d8e421d727ac85c60825761818f5d0a" +dependencies = [ + "async-stream", + "async-trait", + "base64", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "prost-derive", + "tokio", + "tokio-stream", + "tokio-util", + "tower", + "tower-layer", + "tower-service", + "tracing", + "tracing-futures", +] + +[[package]] +name = "tonic-build" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9403f1bafde247186684b230dc6f38b5cd514584e8bec1dd32514be4745fa757" +dependencies = [ + "proc-macro2", + "prost-build", + "quote", + "syn", +] + [[package]] name = "topology" version = "0.1.0" @@ -7121,6 +7287,33 @@ dependencies = [ "version-checker", ] +[[package]] +name = "tower" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5651b5f6860a99bd1adb59dbfe1db8beb433e73709d9032b413a77e2fb7c066a" +dependencies = [ + "futures-core", + "futures-util", + "indexmap", + "pin-project", + "pin-project-lite", + "rand 0.8.4", + "slab", + "tokio", + "tokio-stream", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343bc9466d3fe6b0f960ef45960509f84480bf4fd96f92901afe7ff3df9d3a62" + [[package]] name = "tower-service" version = "0.3.1" @@ -7134,10 +7327,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84f96e095c0c82419687c20ddf5cb3eadb61f4e1405923c9dc8e53a1adacbda8" dependencies = [ "cfg-if 1.0.0", + "log", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" version = "0.1.20" @@ -7147,6 +7353,27 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77be66445c4eeebb934a7340f227bfe7b338173d3f8c00a60a5a58005c9faecf" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + [[package]] name = "try-lock" version = "0.2.3" @@ -7765,6 +7992,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "which" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea187a8ef279bc014ec368c27a920da2024d2a711109bfbe3440585d5cf27ad9" +dependencies = [ + "either", + "lazy_static", + "libc", +] + [[package]] name = "whoami" version = "1.1.4" diff --git a/validator-api/Cargo.toml b/validator-api/Cargo.toml index 4b0b9bf318..77224e1816 100644 --- a/validator-api/Cargo.toml +++ b/validator-api/Cargo.toml @@ -55,6 +55,9 @@ validator-client = { path="../common/client-libs/validator-client", features = [ version-checker = { path="../common/version-checker" } coconut-interface = { path = "../common/coconut-interface", optional = true } credentials = { path = "../common/credentials", optional = true } +# validator-api needs to be built with RUSTFLAGS="--cfg tokio_unstable" +console-subscriber = { version = "0.1.1", optional = true} +cfg-if = "1.0" [features] coconut = ["coconut-interface", "credentials", "gateway-client/coconut"] diff --git a/validator-api/src/main.rs b/validator-api/src/main.rs index a32995ea2c..698cbc970c 100644 --- a/validator-api/src/main.rs +++ b/validator-api/src/main.rs @@ -570,6 +570,11 @@ async fn run_validator_api(matches: ArgMatches<'static>) -> Result<()> { async fn main() -> Result<()> { println!("Starting validator api..."); + cfg_if::cfg_if! {if #[cfg(feature = "console-subscriber")] { + // instriment tokio console subscriber needs RUSTFLAGS="--cfg tokio_unstable" at build time + console_subscriber::init(); + }} + setup_logging(); let args = parse_args(); run_validator_api(args).await From 6ff02bc2a1e581976450bfd09cd4ff48a360ce38 Mon Sep 17 00:00:00 2001 From: durch Date: Thu, 20 Jan 2022 11:31:35 +0100 Subject: [PATCH 67/67] Fix wallet clippy lints --- nym-wallet/src-tauri/src/network.rs | 2 ++ nym-wallet/src-tauri/src/operations/mixnet/account.rs | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/nym-wallet/src-tauri/src/network.rs b/nym-wallet/src-tauri/src/network.rs index ff05ea86e2..3c04f70253 100644 --- a/nym-wallet/src-tauri/src/network.rs +++ b/nym-wallet/src-tauri/src/network.rs @@ -8,6 +8,7 @@ use strum::EnumIter; use crate::error::BackendError; use config::defaults::all::Network as ConfigNetwork; +#[allow(clippy::upper_case_acronyms)] #[cfg_attr(test, derive(ts_rs::TS))] #[derive(Copy, Clone, Debug, Deserialize, EnumIter, Eq, Hash, PartialEq, Serialize)] pub enum Network { @@ -21,6 +22,7 @@ impl Default for Network { } } +#[allow(clippy::from_over_into)] impl Into for Network { fn into(self) -> ConfigNetwork { match self { diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 1fbfa653c6..eac5a21677 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -142,7 +142,6 @@ async fn _connect_with_mnemonic( w_state.add_client(network, client); } - default_account.ok_or(BackendError::NetworkNotSupported( - config::defaults::default_network(), - )) + default_account + .ok_or_else(|| BackendError::NetworkNotSupported(config::defaults::default_network())) }