From 2923d4b8725f04a0097461852862cf348b0998da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Thu, 19 Aug 2021 22:03:07 +0300 Subject: [PATCH 01/28] Update template toml key (#746) --- clients/native/src/client/config/template.rs | 2 +- clients/socks5/src/client/config/template.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/native/src/client/config/template.rs b/clients/native/src/client/config/template.rs index b2ca0ec46b..66b33d7e3c 100644 --- a/clients/native/src/client/config/template.rs +++ b/clients/native/src/client/config/template.rs @@ -20,7 +20,7 @@ version = '{{ client.version }}' id = '{{ client.id }}' # Addresses to APIs running on validator from which the client gets the view of the network. -validator_rest_urls = [ +validator_api_urls = [ {{#each client.validator_api_urls }} '{{this}}', {{/each}} diff --git a/clients/socks5/src/client/config/template.rs b/clients/socks5/src/client/config/template.rs index 0bdce7b650..70e196d3c1 100644 --- a/clients/socks5/src/client/config/template.rs +++ b/clients/socks5/src/client/config/template.rs @@ -20,7 +20,7 @@ version = '{{ client.version }}' id = '{{ client.id }}' # Addresses to APIs running on validator from which the client gets the view of the network. -validator_rest_urls = [ +validator_api_urls = [ {{#each client.validator_api_urls }} '{{this}}', {{/each}} From 15ae0f521e46336c0e8518e9b749dde9d702077a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Mon, 23 Aug 2021 10:26:51 +0100 Subject: [PATCH 02/28] Feature/more reliable uptime calculation (#747) * New database table holding monitor run info * SQL interface for new table * Updated uptime calculation to instead rely on number of monitor test runs --- .../20210819120000_monitor_runs.sql | 7 + .../src/network_monitor/monitor/mod.rs | 12 ++ validator-api/src/node_status_api/models.rs | 25 ++- validator-api/src/node_status_api/utils.rs | 69 +++++--- validator-api/src/storage/manager.rs | 59 +++++-- validator-api/src/storage/mod.rs | 152 +++++++++++++++--- 6 files changed, 260 insertions(+), 64 deletions(-) create mode 100644 validator-api/migrations/20210819120000_monitor_runs.sql diff --git a/validator-api/migrations/20210819120000_monitor_runs.sql b/validator-api/migrations/20210819120000_monitor_runs.sql new file mode 100644 index 0000000000..0b6561862a --- /dev/null +++ b/validator-api/migrations/20210819120000_monitor_runs.sql @@ -0,0 +1,7 @@ +-- keeping track of all monitor runs that have happened will help to +-- solve an issue of mixnode being online only for a single check and yet being assigned 100% uptime +CREATE TABLE monitor_run +( + id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + timestamp INTEGER NOT NULL +) \ No newline at end of file diff --git a/validator-api/src/network_monitor/monitor/mod.rs b/validator-api/src/network_monitor/monitor/mod.rs index ccfff233a0..39bbe0dda9 100644 --- a/validator-api/src/network_monitor/monitor/mod.rs +++ b/validator-api/src/network_monitor/monitor/mod.rs @@ -77,6 +77,18 @@ impl Monitor { // TODO: slightly more graceful shutdown here process::exit(1); } + + // indicate our run has completed successfully and should be used in any future + // uptime calculations + if let Err(err) = self.node_status_storage.insert_monitor_run().await { + error!( + "Failed to submit monitor run information to the database - {}", + err + ); + + // TODO: slightly more graceful shutdown here + process::exit(1); + } } // checking it this way with a TestReport is rather suboptimal but given the fact we're only diff --git a/validator-api/src/node_status_api/models.rs b/validator-api/src/node_status_api/models.rs index a2a174bd97..5f57e17b43 100644 --- a/validator-api/src/node_status_api/models.rs +++ b/validator-api/src/node_status_api/models.rs @@ -7,6 +7,7 @@ use rocket::http::{ContentType, Status}; use rocket::response::{self, Responder, Response}; use rocket::Request; use serde::{Deserialize, Serialize}; +use sqlx::types::time::OffsetDateTime; use std::convert::TryFrom; use std::fmt::{self, Display, Formatter}; use std::io::Cursor; @@ -90,13 +91,21 @@ pub struct MixnodeStatusReport { impl MixnodeStatusReport { pub(crate) fn construct_from_last_day_reports( + report_time: OffsetDateTime, identity: String, owner: String, last_day_ipv4: Vec, last_day_ipv6: Vec, + last_hour_test_runs: usize, + last_day_test_runs: usize, ) -> Self { - let node_uptimes = - NodeUptimes::calculate_from_last_day_reports(last_day_ipv4, last_day_ipv6); + let node_uptimes = NodeUptimes::calculate_from_last_day_reports( + report_time, + last_day_ipv4, + last_day_ipv6, + last_hour_test_runs, + last_day_test_runs, + ); MixnodeStatusReport { identity, @@ -128,13 +137,21 @@ pub struct GatewayStatusReport { impl GatewayStatusReport { pub(crate) fn construct_from_last_day_reports( + report_time: OffsetDateTime, identity: String, owner: String, last_day_ipv4: Vec, last_day_ipv6: Vec, + last_hour_test_runs: usize, + last_day_test_runs: usize, ) -> Self { - let node_uptimes = - NodeUptimes::calculate_from_last_day_reports(last_day_ipv4, last_day_ipv6); + let node_uptimes = NodeUptimes::calculate_from_last_day_reports( + report_time, + last_day_ipv4, + last_day_ipv6, + last_hour_test_runs, + last_day_test_runs, + ); GatewayStatusReport { identity, diff --git a/validator-api/src/node_status_api/utils.rs b/validator-api/src/node_status_api/utils.rs index a7d4f2d52d..3d4405bfca 100644 --- a/validator-api/src/node_status_api/utils.rs +++ b/validator-api/src/node_status_api/utils.rs @@ -4,7 +4,9 @@ use crate::node_status_api::models::Uptime; use crate::node_status_api::{FIFTEEN_MINUTES, ONE_HOUR}; use crate::storage::models::NodeStatus; +use log::warn; use sqlx::types::time::OffsetDateTime; +use std::cmp::max; // A temporary helper struct used to produce reports for active nodes. pub(crate) struct ActiveNodeDayStatuses { @@ -30,33 +32,23 @@ pub(crate) struct NodeUptimes { impl NodeUptimes { pub(crate) fn calculate_from_last_day_reports( + report_time: OffsetDateTime, last_day_ipv4: Vec, last_day_ipv6: Vec, + last_hour_test_runs: usize, + last_day_test_runs: usize, ) -> Self { - let now = OffsetDateTime::now_utc(); - let hour_ago = (now - ONE_HOUR).unix_timestamp(); - let fifteen_minutes_ago = (now - FIFTEEN_MINUTES).unix_timestamp(); + let hour_ago = (report_time - ONE_HOUR).unix_timestamp(); + let fifteen_minutes_ago = (report_time - FIFTEEN_MINUTES).unix_timestamp(); - let ipv4_day_total = last_day_ipv4.len(); - let ipv6_day_total = last_day_ipv6.len(); + let mut ipv4_day_up = last_day_ipv4.iter().filter(|report| report.up).count(); + let mut ipv6_day_up = last_day_ipv6.iter().filter(|report| report.up).count(); - let ipv4_day_up = last_day_ipv4.iter().filter(|report| report.up).count(); - let ipv6_day_up = last_day_ipv6.iter().filter(|report| report.up).count(); - - let ipv4_hour_total = last_day_ipv4 - .iter() - .filter(|report| report.timestamp >= hour_ago) - .count(); - let ipv6_hour_total = last_day_ipv6 - .iter() - .filter(|report| report.timestamp >= hour_ago) - .count(); - - let ipv4_hour_up = last_day_ipv4 + let mut ipv4_hour_up = last_day_ipv4 .iter() .filter(|report| report.up && report.timestamp >= hour_ago) .count(); - let ipv6_hour_up = last_day_ipv6 + let mut ipv6_hour_up = last_day_ipv6 .iter() .filter(|report| report.up && report.timestamp >= hour_ago) .count(); @@ -73,15 +65,42 @@ impl NodeUptimes { .map(|status| status.timestamp >= fifteen_minutes_ago && status.up) // make sure its within last 15min .unwrap_or_default(); - // the unwraps in Uptime::from_ratio are fine because it's impossible for us to have more "up" results than all results in total - // because both of those values originate from the same vector + // If somehow we have more "up" reports than the actual test runs it means something weird is going on + // (or we just started running this code on old data, so if it appears for first 24h, it's fine and actually expected + // as we would not have any run information from the past) + // Either way, bound the the number of "up" reports by number of test runs and log warnings + // if that happens + if ipv4_hour_up > last_hour_test_runs || ipv6_hour_up > last_hour_test_runs { + warn!( + "We have more 'up' reports than the actual number of test runs in last hour! ({} ipv4 'ups', {} ipv6 'ups' for {} test runs)", + ipv4_hour_up, + ipv6_hour_up, + last_hour_test_runs, + ); + ipv4_hour_up = max(ipv4_hour_up, last_hour_test_runs); + ipv6_hour_up = max(ipv6_hour_up, last_hour_test_runs); + } + + if ipv4_day_up > last_day_test_runs || ipv6_day_up > last_day_test_runs { + warn!( + "We have more 'up' reports than the actual number of test runs in last day! ({} ipv4 'ups', {} ipv6 'ups' for {} test runs)", + ipv4_day_up, + ipv6_day_up, + last_day_test_runs, + ); + ipv4_day_up = max(ipv4_day_up, last_day_test_runs); + ipv6_day_up = max(ipv6_day_up, last_day_test_runs); + } + + // the unwraps in Uptime::from_ratio are fine because it's impossible for us to have more "up" results + // than total test runs as we just bounded them NodeUptimes { most_recent_ipv4, most_recent_ipv6, - last_hour_ipv4: Uptime::from_ratio(ipv4_hour_up, ipv4_hour_total).unwrap(), - last_hour_ipv6: Uptime::from_ratio(ipv6_hour_up, ipv6_hour_total).unwrap(), - last_day_ipv4: Uptime::from_ratio(ipv4_day_up, ipv4_day_total).unwrap(), - last_day_ipv6: Uptime::from_ratio(ipv6_day_up, ipv6_day_total).unwrap(), + last_hour_ipv4: Uptime::from_ratio(ipv4_hour_up, last_hour_test_runs).unwrap(), + last_hour_ipv6: Uptime::from_ratio(ipv6_hour_up, last_hour_test_runs).unwrap(), + last_day_ipv4: Uptime::from_ratio(ipv4_day_up, last_day_test_runs).unwrap(), + last_day_ipv6: Uptime::from_ratio(ipv6_day_up, last_day_test_runs).unwrap(), } } } diff --git a/validator-api/src/storage/manager.rs b/validator-api/src/storage/manager.rs index c95dbfbb0c..3e18786a2a 100644 --- a/validator-api/src/storage/manager.rs +++ b/validator-api/src/storage/manager.rs @@ -4,10 +4,8 @@ use crate::network_monitor::monitor::summary_producer::NodeResult; use crate::node_status_api::models::{HistoricalUptime, Uptime}; use crate::node_status_api::utils::ActiveNodeDayStatuses; -use crate::node_status_api::ONE_DAY; use crate::storage::models::{ActiveNode, NodeStatus}; use crate::storage::UnixTimestamp; -use sqlx::types::time::OffsetDateTime; use std::convert::TryFrom; #[derive(Clone)] @@ -463,6 +461,43 @@ impl StorageManager { Ok(()) } + /// Creates a database entry for a finished network monitor test run. + /// + /// # Arguments + /// + /// * `timestamp`: unix timestamp at which the monitor test run has occurred + pub(crate) async fn insert_monitor_run( + &self, + timestamp: UnixTimestamp, + ) -> Result<(), sqlx::Error> { + sqlx::query!("INSERT INTO monitor_run(timestamp) VALUES (?)", timestamp) + .execute(&self.connection_pool) + .await?; + Ok(()) + } + + /// Obtains number of network monitor test runs that have occurred within the specified interval. + /// + /// # Arguments + /// + /// * `since`: unix timestamp indicating the lower bound interval of the selection. + /// * `until`: unix timestamp indicating the upper bound interval of the selection. + pub(crate) async fn get_monitor_runs_count( + &self, + since: UnixTimestamp, + until: UnixTimestamp, + ) -> Result { + let count = sqlx::query!( + "SELECT COUNT(*) as count FROM monitor_run WHERE timestamp > ? AND timestamp < ?", + since, + until, + ) + .fetch_one(&self.connection_pool) + .await? + .count; + Ok(count) + } + pub(crate) async fn purge_old_mixnode_ipv4_statuses( &self, timestamp: UnixTimestamp, @@ -579,19 +614,17 @@ impl StorageManager { // since technically it doesn't touch any SQL directly pub(crate) async fn get_all_active_mixnodes_statuses( &self, + since: UnixTimestamp, ) -> Result, sqlx::Error> { - let now = OffsetDateTime::now_utc(); - let day_ago = (now - ONE_DAY).unix_timestamp(); - - let active_nodes = self.get_all_active_mixnodes(day_ago).await?; + let active_nodes = self.get_all_active_mixnodes(since).await?; let mut active_day_statuses = Vec::with_capacity(active_nodes.len()); for active_node in active_nodes.into_iter() { let ipv4_statuses = self - .get_mixnode_ipv4_statuses_since_by_id(active_node.id, day_ago) + .get_mixnode_ipv4_statuses_since_by_id(active_node.id, since) .await?; let ipv6_statuses = self - .get_mixnode_ipv6_statuses_since_by_id(active_node.id, day_ago) + .get_mixnode_ipv6_statuses_since_by_id(active_node.id, since) .await?; let statuses = ActiveNodeDayStatuses { @@ -614,19 +647,17 @@ impl StorageManager { // since technically it doesn't touch any SQL directly pub(crate) async fn get_all_active_gateways_statuses( &self, + since: UnixTimestamp, ) -> Result, sqlx::Error> { - let now = OffsetDateTime::now_utc(); - let day_ago = (now - ONE_DAY).unix_timestamp(); - - let active_nodes = self.get_all_active_gateways(day_ago).await?; + let active_nodes = self.get_all_active_gateways(since).await?; let mut active_day_statuses = Vec::with_capacity(active_nodes.len()); for active_node in active_nodes.into_iter() { let ipv4_statuses = self - .get_gateway_ipv4_statuses_since_by_id(active_node.id, day_ago) + .get_gateway_ipv4_statuses_since_by_id(active_node.id, since) .await?; let ipv6_statuses = self - .get_gateway_ipv6_statuses_since_by_id(active_node.id, day_ago) + .get_gateway_ipv6_statuses_since_by_id(active_node.id, since) .await?; let statuses = ActiveNodeDayStatuses { diff --git a/validator-api/src/storage/mod.rs b/validator-api/src/storage/mod.rs index 253566ac8a..2256e94999 100644 --- a/validator-api/src/storage/mod.rs +++ b/validator-api/src/storage/mod.rs @@ -6,7 +6,7 @@ use crate::node_status_api::models::{ GatewayStatusReport, GatewayUptimeHistory, MixnodeStatusReport, MixnodeUptimeHistory, NodeStatusApiError, Uptime, }; -use crate::node_status_api::ONE_DAY; +use crate::node_status_api::{ONE_DAY, ONE_HOUR}; use crate::storage::manager::StorageManager; use crate::storage::models::NodeStatus; use rocket::fairing::{self, AdHoc}; @@ -67,46 +67,58 @@ impl NodeStatusStorage { }) } - /// Gets all statuses for particular mixnode (ipv4 and ipv6) that were inserted in last 24h. - async fn get_mixnode_daily_statuses( + /// Gets all statuses for particular mixnode (ipv4 and ipv6) that were inserted + /// since the provided timestamp. + /// + /// Returns tuple containing vectors of ipv4 statuses and ipv6 statuses. + /// + /// # Arguments + /// + /// * `identity`: identity key of the mixnode to query. + /// * `since`: unix timestamp indicating the lower bound interval of the selection. + async fn get_mixnode_statuses( &self, identity: &str, + since: UnixTimestamp, ) -> Result<(Vec, Vec), NodeStatusApiError> { - let now = OffsetDateTime::now_utc(); - let day_ago = now - ONE_DAY; - let ipv4_statuses = self .manager - .get_mixnode_ipv4_statuses_since(identity, day_ago.unix_timestamp()) + .get_mixnode_ipv4_statuses_since(identity, since) .await .map_err(|_| NodeStatusApiError::InternalDatabaseError)?; let ipv6_statuses = self .manager - .get_mixnode_ipv6_statuses_since(identity, day_ago.unix_timestamp()) + .get_mixnode_ipv6_statuses_since(identity, since) .await .map_err(|_| NodeStatusApiError::InternalDatabaseError)?; Ok((ipv4_statuses, ipv6_statuses)) } - /// Gets all statuses for particular gateway (ipv4 and ipv6) that were inserted in last 24h. - async fn get_gateway_daily_statuses( + /// Gets all statuses for particular gateway (ipv4 and ipv6) that were inserted + /// since the provided timestamp. + /// + /// Returns tuple containing vectors of ipv4 statuses and ipv6 statuses. + /// + /// # Arguments + /// + /// * `identity`: identity key of the gateway to query. + /// * `since`: unix timestamp indicating the lower bound interval of the selection. + async fn get_gateway_statuses( &self, identity: &str, + since: UnixTimestamp, ) -> Result<(Vec, Vec), NodeStatusApiError> { - let now = OffsetDateTime::now_utc(); - let day_ago = now - ONE_DAY; - let ipv4_statuses = self .manager - .get_gateway_ipv4_statuses_since(identity, day_ago.unix_timestamp()) + .get_gateway_ipv4_statuses_since(identity, since) .await .map_err(|_| NodeStatusApiError::InternalDatabaseError)?; let ipv6_statuses = self .manager - .get_gateway_ipv6_statuses_since(identity, day_ago.unix_timestamp()) + .get_gateway_ipv6_statuses_since(identity, since) .await .map_err(|_| NodeStatusApiError::InternalDatabaseError)?; @@ -118,7 +130,11 @@ impl NodeStatusStorage { &self, identity: &str, ) -> Result { - let (ipv4_statuses, ipv6_statuses) = self.get_mixnode_daily_statuses(identity).await?; + let now = OffsetDateTime::now_utc(); + let day_ago = (now - ONE_DAY).unix_timestamp(); + let hour_ago = (now - ONE_HOUR).unix_timestamp(); + + let (ipv4_statuses, ipv6_statuses) = self.get_mixnode_statuses(identity, day_ago).await?; // if we have no statuses, the node doesn't exist (or monitor is down), but either way, we can't make a report if ipv4_statuses.is_empty() { @@ -127,6 +143,14 @@ impl NodeStatusStorage { )); } + // determine the number of runs the mixnode should have been online for + let last_hour_runs_count = self + .get_monitor_runs_count(hour_ago, now.unix_timestamp()) + .await?; + let last_day_runs_count = self + .get_monitor_runs_count(day_ago, now.unix_timestamp()) + .await?; + // now, technically this is not a critical error, but this should have NEVER happened in the first place // so something super weird is going on if ipv4_statuses.len() != ipv6_statuses.len() { @@ -145,10 +169,13 @@ impl NodeStatusStorage { .expect("The node doesn't have an owner even though we have status information on it!"); Ok(MixnodeStatusReport::construct_from_last_day_reports( + now, identity.to_owned(), mixnode_owner, ipv4_statuses, ipv6_statuses, + last_hour_runs_count, + last_day_runs_count, )) } @@ -156,7 +183,11 @@ impl NodeStatusStorage { &self, identity: &str, ) -> Result { - let (ipv4_statuses, ipv6_statuses) = self.get_gateway_daily_statuses(identity).await?; + let now = OffsetDateTime::now_utc(); + let day_ago = (now - ONE_DAY).unix_timestamp(); + let hour_ago = (now - ONE_HOUR).unix_timestamp(); + + let (ipv4_statuses, ipv6_statuses) = self.get_gateway_statuses(identity, day_ago).await?; // if we have no statuses, the node doesn't exist (or monitor is down), but either way, we can't make a report if ipv4_statuses.is_empty() { @@ -165,6 +196,14 @@ impl NodeStatusStorage { )); } + // determine the number of runs the gateway should have been online for + let last_hour_runs_count = self + .get_monitor_runs_count(hour_ago, now.unix_timestamp()) + .await?; + let last_day_runs_count = self + .get_monitor_runs_count(day_ago, now.unix_timestamp()) + .await?; + // now, technically this is not a critical error, but this should have NEVER happened in the first place // so something super weird is going on if ipv4_statuses.len() != ipv6_statuses.len() { @@ -185,10 +224,13 @@ impl NodeStatusStorage { ); Ok(GatewayStatusReport::construct_from_last_day_reports( + now, identity.to_owned(), gateway_owner, ipv4_statuses, ipv6_statuses, + last_hour_runs_count, + last_day_runs_count, )) } @@ -257,18 +299,33 @@ impl NodeStatusStorage { pub(crate) async fn get_all_mixnode_reports( &self, ) -> Result, NodeStatusApiError> { + let now = OffsetDateTime::now_utc(); + let day_ago = (now - ONE_DAY).unix_timestamp(); + let hour_ago = (now - ONE_HOUR).unix_timestamp(); + + // determine the number of runs the mixnodes should have been online for + let last_hour_runs_count = self + .get_monitor_runs_count(hour_ago, now.unix_timestamp()) + .await?; + let last_day_runs_count = self + .get_monitor_runs_count(day_ago, now.unix_timestamp()) + .await?; + let reports = self .manager - .get_all_active_mixnodes_statuses() + .get_all_active_mixnodes_statuses(day_ago) .await .map_err(|_| NodeStatusApiError::InternalDatabaseError)? .into_iter() .map(|statuses| { MixnodeStatusReport::construct_from_last_day_reports( + now, statuses.identity, statuses.owner, statuses.ipv4_statuses, statuses.ipv6_statuses, + last_hour_runs_count, + last_day_runs_count, ) }) .collect(); @@ -281,18 +338,33 @@ impl NodeStatusStorage { pub(crate) async fn get_all_gateway_reports( &self, ) -> Result, NodeStatusApiError> { + let now = OffsetDateTime::now_utc(); + let day_ago = (now - ONE_DAY).unix_timestamp(); + let hour_ago = (now - ONE_HOUR).unix_timestamp(); + + // determine the number of runs the gateways should have been online for + let last_hour_runs_count = self + .get_monitor_runs_count(hour_ago, now.unix_timestamp()) + .await?; + let last_day_runs_count = self + .get_monitor_runs_count(day_ago, now.unix_timestamp()) + .await?; + let reports = self .manager - .get_all_active_gateways_statuses() + .get_all_active_gateways_statuses(day_ago) .await .map_err(|_| NodeStatusApiError::InternalDatabaseError)? .into_iter() .map(|statuses| { GatewayStatusReport::construct_from_last_day_reports( + now, statuses.identity, statuses.owner, statuses.ipv4_statuses, statuses.ipv6_statuses, + last_hour_runs_count, + last_day_runs_count, ) }) .collect(); @@ -321,15 +393,53 @@ impl NodeStatusStorage { .map_err(|_| NodeStatusApiError::InternalDatabaseError) } + /// Inserts an entry to the database with the network monitor test run information + /// that has occurred at this instant. + pub(crate) async fn insert_monitor_run(&self) -> Result<(), NodeStatusApiError> { + let now = OffsetDateTime::now_utc().unix_timestamp(); + + self.manager + .insert_monitor_run(now) + .await + .map_err(|_| NodeStatusApiError::InternalDatabaseError) + } + + /// Obtains number of network monitor test runs that have occurred within the specified interval. + /// + /// # Arguments + /// + /// * `since`: unix timestamp indicating the lower bound interval of the selection. + /// * `until`: unix timestamp indicating the upper bound interval of the selection. + pub(crate) async fn get_monitor_runs_count( + &self, + since: UnixTimestamp, + until: UnixTimestamp, + ) -> Result { + let run_count = self + .manager + .get_monitor_runs_count(since, until) + .await + .map_err(|_| NodeStatusApiError::InternalDatabaseError)?; + + if run_count < 0 { + // I don't think it's ever possible for SQL to return a negative value from COUNT? + return Err(NodeStatusApiError::InternalDatabaseError); + } + Ok(run_count as usize) + } + // Called on timer/reward script async fn update_historical_uptimes( &self, today_iso_8601: &str, ) -> Result<(), NodeStatusApiError> { + let now = OffsetDateTime::now_utc(); + let day_ago = (now - ONE_DAY).unix_timestamp(); + // get statuses for all active mixnodes... let active_mixnodes_statuses = self .manager - .get_all_active_mixnodes_statuses() + .get_all_active_mixnodes_statuses(day_ago) .await .map_err(|_| NodeStatusApiError::InternalDatabaseError)?; @@ -368,7 +478,7 @@ impl NodeStatusStorage { // get statuses for all active gateways... let active_gateways_statuses = self .manager - .get_all_active_gateways_statuses() + .get_all_active_gateways_statuses(day_ago) .await .map_err(|_| NodeStatusApiError::InternalDatabaseError)?; From b3568a26f57faaffe7b8d20ab90cb4cb456eae5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Tue, 24 Aug 2021 11:25:05 +0300 Subject: [PATCH 03/28] Revert "Migration commit, will be reverted after the testnet contract is updated" (#749) This reverts commit 38d868bcce3738c00d0f60bbc40373efd3cf7c6e. --- common/mixnet-contract/src/lib.rs | 2 -- common/mixnet-contract/src/types.rs | 6 ------ contracts/mixnet/src/contract.rs | 6 ++---- contracts/mixnet/src/state.rs | 4 ---- 4 files changed, 2 insertions(+), 16 deletions(-) diff --git a/common/mixnet-contract/src/lib.rs b/common/mixnet-contract/src/lib.rs index 6779514b29..785497197b 100644 --- a/common/mixnet-contract/src/lib.rs +++ b/common/mixnet-contract/src/lib.rs @@ -13,5 +13,3 @@ pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayRe pub use mixnode::{Layer, MixNode, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse}; pub use msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; pub use types::{IdentityKey, IdentityKeyRef, LayerDistribution, SphinxKey, StateParams}; - -pub use types::default_delegation_reward; diff --git a/common/mixnet-contract/src/types.rs b/common/mixnet-contract/src/types.rs index 8a202fde61..9d0404daf0 100644 --- a/common/mixnet-contract/src/types.rs +++ b/common/mixnet-contract/src/types.rs @@ -26,10 +26,6 @@ impl LayerDistribution { } } -pub fn default_delegation_reward() -> Decimal { - Decimal::percent(110) -} - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct StateParams { pub epoch_length: u32, // length of an epoch, expressed in hours @@ -38,9 +34,7 @@ pub struct StateParams { pub minimum_gateway_bond: Uint128, // minimum amount a gateway must bond to get into the system pub mixnode_bond_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25 pub gateway_bond_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25 - #[serde(default = "default_delegation_reward")] pub mixnode_delegation_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25 - #[serde(default = "default_delegation_reward")] pub gateway_delegation_reward_rate: Decimal, // annual reward rate, expressed as a decimal like 1.25 pub mixnode_active_set_size: u32, } diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 9dd24a7eb6..5487ea451e 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -3,7 +3,7 @@ use crate::helpers::calculate_epoch_reward_rate; use crate::state::State; -use crate::storage::{config, config_read, layer_distribution}; +use crate::storage::{config, layer_distribution}; use crate::{error::ContractError, queries, transactions}; use config::defaults::NETWORK_MONITOR_ADDRESS; use cosmwasm_std::{ @@ -181,9 +181,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result Result { - let state = config_read(deps.storage).load().unwrap(); - config(deps.storage).save(&state)?; +pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { Ok(Default::default()) } diff --git a/contracts/mixnet/src/state.rs b/contracts/mixnet/src/state.rs index d4f0034ff6..c537366f07 100644 --- a/contracts/mixnet/src/state.rs +++ b/contracts/mixnet/src/state.rs @@ -6,8 +6,6 @@ use mixnet_contract::StateParams; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use mixnet_contract::default_delegation_reward; - #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct State { pub owner: Addr, // only the owner account can update state @@ -17,8 +15,6 @@ pub struct State { // helper values to avoid having to recalculate them on every single payment operation pub mixnode_epoch_bond_reward: Decimal, // reward per epoch expressed as a decimal like 0.05 pub gateway_epoch_bond_reward: Decimal, // reward per epoch expressed as a decimal like 0.05 - #[serde(default = "default_delegation_reward")] pub mixnode_epoch_delegation_reward: Decimal, // reward per epoch expressed as a decimal like 0.05 - #[serde(default = "default_delegation_reward")] pub gateway_epoch_delegation_reward: Decimal, // reward per epoch expressed as a decimal like 0.05 } From c6b286a1dba0dd1a1a1df021cc4e8e45873eed15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Wed, 25 Aug 2021 14:50:57 +0100 Subject: [PATCH 04/28] Fixed argument parsing for ipv6 'good' topology (#751) --- validator-api/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/validator-api/src/main.rs b/validator-api/src/main.rs index b3601de13f..4011daf403 100644 --- a/validator-api/src/main.rs +++ b/validator-api/src/main.rs @@ -146,7 +146,7 @@ fn override_config(mut config: Config, matches: &ArgMatches) -> Config { config = config.with_v4_good_topology(v4_topology_path) } - if let Some(v6_topology_path) = matches.value_of(V4_TOPOLOGY_ARG) { + if let Some(v6_topology_path) = matches.value_of(V6_TOPOLOGY_ARG) { config = config.with_v6_good_topology(v6_topology_path) } From 6bdfe7f8954177240894945dee86926eb3df08ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 26 Aug 2021 11:21:01 +0100 Subject: [PATCH 05/28] Correctly bounding nominator of uptime calculation (#752) --- validator-api/src/node_status_api/utils.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/validator-api/src/node_status_api/utils.rs b/validator-api/src/node_status_api/utils.rs index 3d4405bfca..08cff22f95 100644 --- a/validator-api/src/node_status_api/utils.rs +++ b/validator-api/src/node_status_api/utils.rs @@ -6,7 +6,7 @@ use crate::node_status_api::{FIFTEEN_MINUTES, ONE_HOUR}; use crate::storage::models::NodeStatus; use log::warn; use sqlx::types::time::OffsetDateTime; -use std::cmp::max; +use std::cmp::min; // A temporary helper struct used to produce reports for active nodes. pub(crate) struct ActiveNodeDayStatuses { @@ -77,8 +77,8 @@ impl NodeUptimes { ipv6_hour_up, last_hour_test_runs, ); - ipv4_hour_up = max(ipv4_hour_up, last_hour_test_runs); - ipv6_hour_up = max(ipv6_hour_up, last_hour_test_runs); + ipv4_hour_up = min(ipv4_hour_up, last_hour_test_runs); + ipv6_hour_up = min(ipv6_hour_up, last_hour_test_runs); } if ipv4_day_up > last_day_test_runs || ipv6_day_up > last_day_test_runs { @@ -88,8 +88,8 @@ impl NodeUptimes { ipv6_day_up, last_day_test_runs, ); - ipv4_day_up = max(ipv4_day_up, last_day_test_runs); - ipv6_day_up = max(ipv6_day_up, last_day_test_runs); + ipv4_day_up = min(ipv4_day_up, last_day_test_runs); + ipv6_day_up = min(ipv6_day_up, last_day_test_runs); } // the unwraps in Uptime::from_ratio are fine because it's impossible for us to have more "up" results From 1811df9ddb696279e2fc4cb58eeb9df55f2b1843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 27 Aug 2021 13:52:10 +0100 Subject: [PATCH 06/28] Enabled validators api argument (#753) --- validator-api/src/main.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/validator-api/src/main.rs b/validator-api/src/main.rs index 4011daf403..afb8218dda 100644 --- a/validator-api/src/main.rs +++ b/validator-api/src/main.rs @@ -79,6 +79,12 @@ fn parse_args<'a>() -> ArgMatches<'a> { .long(NYMD_VALIDATOR_ARG) .takes_value(true) ) + .arg( + Arg::with_name(API_VALIDATORS_ARG) + .help("specifies list of all validators on the network issuing coconut credentials. Ensure they are properly ordered") + .long(API_VALIDATORS_ARG) + .takes_value(true) + ) .arg(Arg::with_name(MIXNET_CONTRACT_ARG) .long(MIXNET_CONTRACT_ARG) .help("Address of the validator contract managing the network") From 5f42a9bd052aebc496d639be4cb2aeadc4a575cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Fri, 27 Aug 2021 13:52:18 +0100 Subject: [PATCH 07/28] NetworkMonitorBuilder - starting the monitor after rocket has launched (#754) * NetworkMonitorBuilder - starting the monitor after rocket has launched * Removed unused import --- validator-api/src/main.rs | 61 ++++---- validator-api/src/network_monitor/mod.rs | 176 +++++++++++++---------- 2 files changed, 134 insertions(+), 103 deletions(-) diff --git a/validator-api/src/main.rs b/validator-api/src/main.rs index afb8218dda..ef765dc209 100644 --- a/validator-api/src/main.rs +++ b/validator-api/src/main.rs @@ -7,7 +7,7 @@ extern crate rocket; use crate::cache::ValidatorCacheRefresher; use crate::config::Config; use crate::network_monitor::tested_network::good_topology::parse_topology_file; -use crate::network_monitor::{new_monitor_runnables, NetworkMonitorRunnables}; +use crate::network_monitor::NetworkMonitorBuilder; use crate::nymd_client::Client; use crate::storage::NodeStatusStorage; use ::config::{defaults::DEFAULT_VALIDATOR_API_PORT, NymConfig}; @@ -16,10 +16,13 @@ use cache::ValidatorCache; use clap::{App, Arg, ArgMatches}; use coconut::InternalSignRequest; use log::info; +use rocket::fairing::AdHoc; use rocket::http::Method; use rocket::{Ignite, Rocket}; use rocket_cors::{AllowedHeaders, AllowedOrigins, Cors}; use std::process; +use std::sync::Arc; +use tokio::sync::Notify; use url::Url; pub(crate) mod cache; @@ -220,10 +223,16 @@ fn setup_cors() -> Result { Ok(cors) } -async fn setup_network_monitor( - config: &Config, +fn setup_liftoff_notify(notify: Arc) -> AdHoc { + AdHoc::on_liftoff("Liftoff notifier", |_| { + Box::pin(async move { notify.notify_one() }) + }) +} + +fn setup_network_monitor<'a>( + config: &'a Config, rocket: &Rocket, -) -> Option { +) -> Option> { if !config.get_network_monitor_enabled() { return None; } @@ -236,19 +245,16 @@ async fn setup_network_monitor( let v6_topology = parse_topology_file(config.get_v6_good_topology_file()); network_monitor::check_if_up_to_date(&v4_topology, &v6_topology); - Some( - new_monitor_runnables( - config, - v4_topology, - v6_topology, - node_status_storage, - validator_cache, - ) - .await, - ) + Some(NetworkMonitorBuilder::new( + config, + v4_topology, + v6_topology, + node_status_storage, + validator_cache, + )) } -async fn setup_rocket(config: &Config) -> Result> { +async fn setup_rocket(config: &Config, liftoff_notify: Arc) -> Result> { // let's build our rocket! let rocket_config = rocket::config::Config { // TODO: probably the port should be configurable? @@ -257,6 +263,7 @@ async fn setup_rocket(config: &Config) -> Result> { }; let rocket = rocket::custom(rocket_config) .attach(setup_cors()?) + .attach(setup_liftoff_notify(liftoff_notify)) .attach(ValidatorCache::stage()) .attach(InternalSignRequest::stage(config.keypair())); @@ -296,10 +303,11 @@ async fn main() -> Result<()> { let matches = parse_args(); let config = override_config(config, &matches); + let liftoff_notify = Arc::new(Notify::new()); // let's build our rocket! - let rocket = setup_rocket(&config).await?; - let monitor_runnables = setup_network_monitor(&config, &rocket).await; + let rocket = setup_rocket(&config, Arc::clone(&liftoff_notify)).await?; + let monitor_builder = setup_network_monitor(&config, &rocket); let validator_cache = rocket.state::().unwrap().clone(); @@ -327,19 +335,24 @@ async fn main() -> Result<()> { tokio::spawn(async move { validator_cache_refresher.run().await }); } - if let Some(runnables) = monitor_runnables { + // launch the rocket! + let shutdown_handle = rocket.shutdown(); + tokio::spawn(rocket.launch()); + + // to finish building our monitor, we need to have rocket up and running so that we could + // obtain our bandwidth credential + if let Some(monitor_builder) = monitor_builder { info!("Starting network monitor..."); - // spawn network monitor! + // wait for rocket's liftoff stage + liftoff_notify.notified().await; + + // we're ready to go! spawn the network monitor! + let runnables = monitor_builder.build().await; runnables.spawn_tasks(); } else { info!("Network monitoring is disabled."); } - // and launch the rocket - let shutdown_handle = rocket.shutdown(); - - tokio::spawn(rocket.launch()); - wait_for_interrupt().await; shutdown_handle.notify(); diff --git a/validator-api/src/network_monitor/mod.rs b/validator-api/src/network_monitor/mod.rs index f59bb5367c..24715c32dd 100644 --- a/validator-api/src/network_monitor/mod.rs +++ b/validator-api/src/network_monitor/mod.rs @@ -31,6 +31,103 @@ pub(crate) mod monitor; pub(crate) mod test_packet; pub(crate) mod tested_network; +pub(crate) struct NetworkMonitorBuilder<'a> { + config: &'a Config, + tested_network: TestedNetwork, + node_status_storage: NodeStatusStorage, + validator_cache: ValidatorCache, +} + +impl<'a> NetworkMonitorBuilder<'a> { + pub(crate) fn new( + config: &'a Config, + v4_topology: NymTopology, + v6_topology: NymTopology, + node_status_storage: NodeStatusStorage, + validator_cache: ValidatorCache, + ) -> Self { + let tested_network = TestedNetwork::new_good(v4_topology, v6_topology); + + NetworkMonitorBuilder { + config, + tested_network, + node_status_storage, + validator_cache, + } + } + + pub(crate) async fn build(self) -> NetworkMonitorRunnables { + // TODO: in the future I guess this should somehow change to distribute the load + let tested_mix_gateway = self.tested_network.main_v4_gateway().clone(); + info!( + "* gateway for testing mixnodes: {}", + tested_mix_gateway.identity_key.to_base58_string() + ); + + // TODO: those keys change constant throughout the whole execution of the monitor. + // and on top of that, they are used with ALL the gateways -> presumably this should change + // in the future + let mut rng = rand::rngs::OsRng; + + let identity_keypair = Arc::new(identity::KeyPair::new(&mut rng)); + let encryption_keypair = Arc::new(encryption::KeyPair::new(&mut rng)); + + let test_mixnode_sender = Recipient::new( + *identity_keypair.public_key(), + *encryption_keypair.public_key(), + tested_mix_gateway.identity_key, + ); + + let (gateway_status_update_sender, gateway_status_update_receiver) = mpsc::unbounded(); + let (received_processor_sender_channel, received_processor_receiver_channel) = + mpsc::unbounded(); + + let packet_preparer = new_packet_preparer( + self.validator_cache, + self.tested_network.clone(), + test_mixnode_sender, + *identity_keypair.public_key(), + *encryption_keypair.public_key(), + ); + + let bandwidth_credential = + TEMPORARY_obtain_bandwidth_credential(self.config, identity_keypair.public_key()).await; + + let packet_sender = new_packet_sender( + self.config, + gateway_status_update_sender, + Arc::clone(&identity_keypair), + bandwidth_credential, + self.config.get_gateway_sending_rate(), + ); + + let received_processor = new_received_processor( + received_processor_receiver_channel, + Arc::clone(&encryption_keypair), + ); + let summary_producer = new_summary_producer(self.config.get_detailed_report()); + let packet_receiver = new_packet_receiver( + gateway_status_update_receiver, + received_processor_sender_channel, + ); + + let monitor = monitor::Monitor::new( + self.config, + packet_preparer, + packet_sender, + received_processor, + summary_producer, + self.node_status_storage, + self.tested_network, + ); + + NetworkMonitorRunnables { + monitor, + packet_receiver, + } + } +} + pub(crate) struct NetworkMonitorRunnables { monitor: Monitor, packet_receiver: PacketReceiver, @@ -48,85 +145,6 @@ impl NetworkMonitorRunnables { } } -pub(crate) async fn new_monitor_runnables( - config: &Config, - v4_topology: NymTopology, - v6_topology: NymTopology, - node_status_storage: NodeStatusStorage, - validator_cache: ValidatorCache, -) -> NetworkMonitorRunnables { - // TODO: in the future I guess this should somehow change to distribute the load - let tested_mix_gateway = v4_topology.gateways()[0].clone(); - info!( - "* gateway for testing mixnodes: {}", - tested_mix_gateway.identity_key.to_base58_string() - ); - - let tested_network = TestedNetwork::new_good(v4_topology, v6_topology); - - // TODO: those keys change constant throughout the whole execution of the monitor. - // and on top of that, they are used with ALL the gateways -> presumably this should change - // in the future - let mut rng = rand::rngs::OsRng; - - let identity_keypair = Arc::new(identity::KeyPair::new(&mut rng)); - let encryption_keypair = Arc::new(encryption::KeyPair::new(&mut rng)); - - let test_mixnode_sender = Recipient::new( - *identity_keypair.public_key(), - *encryption_keypair.public_key(), - tested_mix_gateway.identity_key, - ); - - let (gateway_status_update_sender, gateway_status_update_receiver) = mpsc::unbounded(); - let (received_processor_sender_channel, received_processor_receiver_channel) = - mpsc::unbounded(); - - let packet_preparer = new_packet_preparer( - validator_cache, - tested_network.clone(), - test_mixnode_sender, - *identity_keypair.public_key(), - *encryption_keypair.public_key(), - ); - - let bandwidth_credential = - TEMPORARY_obtain_bandwidth_credential(config, identity_keypair.public_key()).await; - - let packet_sender = new_packet_sender( - config, - gateway_status_update_sender, - Arc::clone(&identity_keypair), - bandwidth_credential, - config.get_gateway_sending_rate(), - ); - - let received_processor = new_received_processor( - received_processor_receiver_channel, - Arc::clone(&encryption_keypair), - ); - let summary_producer = new_summary_producer(config.get_detailed_report()); - let packet_receiver = new_packet_receiver( - gateway_status_update_receiver, - received_processor_sender_channel, - ); - - let monitor = monitor::Monitor::new( - config, - packet_preparer, - packet_sender, - received_processor, - summary_producer, - node_status_storage, - tested_network, - ); - - NetworkMonitorRunnables { - monitor, - packet_receiver, - } -} - fn new_packet_preparer( validator_cache: ValidatorCache, tested_network: TestedNetwork, From 982ee0266c92628909ea0b5691b2abd31950f084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Fri, 27 Aug 2021 16:02:34 +0300 Subject: [PATCH 08/28] Feature/get own delegations (#748) * Introduce reverse delegation bucket * Add client command * Fix clippy error * Added tests in queries * Add tests in transactions * Migration code. Will be reverted after it's called on testnet * Replace unwrap with expect * Move some test code in the right file... ... to remove unnecessary auxiliary function. * Reduce the scope to migration auxiliary functions * Rename everything from [node]reverse to reverse[node] * Fix fmt --- .../validator-client/src/client.rs | 104 +++++ .../validator-client/src/nymd/mod.rs | 44 +- common/mixnet-contract/src/delegation.rs | 42 ++ common/mixnet-contract/src/lib.rs | 5 +- common/mixnet-contract/src/msg.rs | 10 + contracts/mixnet/src/contract.rs | 143 ++++++- contracts/mixnet/src/queries.rs | 396 +++++++++++++++++- contracts/mixnet/src/storage.rs | 166 +++++++- contracts/mixnet/src/transactions.rs | 238 ++++++++--- 9 files changed, 1079 insertions(+), 69 deletions(-) diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 595b97c185..bb7000cff9 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -243,6 +243,58 @@ impl Client { Ok(delegations) } + pub async fn get_all_nymd_reverse_mixnode_delegations( + &self, + delegation_owner: &cosmos_sdk::AccountId, + ) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync, + { + let mut delegations = Vec::new(); + let mut start_after = None; + loop { + let mut paged_response = self + .nymd + .get_reverse_mix_delegations_paged( + mixnet_contract::Addr::unchecked(delegation_owner.as_ref()), + start_after.take(), + self.mixnode_delegations_page_limit, + ) + .await?; + delegations.append(&mut paged_response.delegated_nodes); + + if let Some(start_after_res) = paged_response.start_next_after { + start_after = Some(start_after_res) + } else { + break; + } + } + + Ok(delegations) + } + + pub async fn get_all_nymd_mixnode_delegations_of_owner( + &self, + delegation_owner: &cosmos_sdk::AccountId, + ) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync, + { + let mut delegations = Vec::new(); + for node_identity in self + .get_all_nymd_reverse_mixnode_delegations(delegation_owner) + .await? + { + let delegation = self + .nymd + .get_mix_delegation(node_identity, delegation_owner) + .await?; + delegations.push(delegation); + } + + Ok(delegations) + } + pub async fn get_all_nymd_gateway_delegations( &self, identity: mixnet_contract::IdentityKey, @@ -273,6 +325,58 @@ impl Client { Ok(delegations) } + pub async fn get_all_nymd_reverse_gateway_delegations( + &self, + delegation_owner: &cosmos_sdk::AccountId, + ) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync, + { + let mut delegations = Vec::new(); + let mut start_after = None; + loop { + let mut paged_response = self + .nymd + .get_reverse_gateway_delegations_paged( + mixnet_contract::Addr::unchecked(delegation_owner.as_ref()), + start_after.take(), + self.mixnode_delegations_page_limit, + ) + .await?; + delegations.append(&mut paged_response.delegated_nodes); + + if let Some(start_after_res) = paged_response.start_next_after { + start_after = Some(start_after_res) + } else { + break; + } + } + + Ok(delegations) + } + + pub async fn get_all_nymd_gateway_delegations_of_owner( + &self, + delegation_owner: &cosmos_sdk::AccountId, + ) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync, + { + let mut delegations = Vec::new(); + for node_identity in self + .get_all_nymd_reverse_gateway_delegations(delegation_owner) + .await? + { + let delegation = self + .nymd + .get_gateway_delegation(node_identity, delegation_owner) + .await?; + delegations.push(delegation); + } + + Ok(delegations) + } + pub async fn blind_sign( &self, request_body: &BlindSignRequestBody, diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index 32bde42a8e..50becfc291 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -18,7 +18,9 @@ use cosmwasm_std::Coin; use mixnet_contract::{ Addr, Delegation, ExecuteMsg, Gateway, GatewayOwnershipResponse, IdentityKey, LayerDistribution, MixNode, MixOwnershipResponse, PagedGatewayDelegationsResponse, - PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse, QueryMsg, StateParams, + PagedGatewayResponse, PagedMixDelegationsResponse, PagedMixnodeResponse, + PagedReverseGatewayDelegationsResponse, PagedReverseMixDelegationsResponse, QueryMsg, + StateParams, }; use serde::Serialize; use std::collections::HashMap; @@ -259,6 +261,26 @@ impl NymdClient { .await } + /// Gets list of all the mixnodes on which a particular address delegated. + pub async fn get_reverse_mix_delegations_paged( + &self, + delegation_owner: Addr, + start_after: Option, + page_limit: Option, + ) -> Result + where + C: CosmWasmClient + Sync, + { + let request = QueryMsg::GetReverseMixDelegations { + delegation_owner, + start_after, + limit: page_limit, + }; + self.client + .query_contract_smart(self.contract_address()?, &request) + .await + } + /// Checks value of delegation of given client towards particular mixnode. pub async fn get_mix_delegation( &self, @@ -297,6 +319,26 @@ impl NymdClient { .await } + /// Gets list of all the gateways on which a particular address delegated. + pub async fn get_reverse_gateway_delegations_paged( + &self, + delegation_owner: Addr, + start_after: Option, + page_limit: Option, + ) -> Result + where + C: CosmWasmClient + Sync, + { + let request = QueryMsg::GetReverseGatewayDelegations { + delegation_owner, + start_after, + limit: page_limit, + }; + self.client + .query_contract_smart(self.contract_address()?, &request) + .await + } + /// Checks value of delegation of given client towards particular gateway. pub async fn get_gateway_delegation( &self, diff --git a/common/mixnet-contract/src/delegation.rs b/common/mixnet-contract/src/delegation.rs index e17f588e46..12b1ac80d4 100644 --- a/common/mixnet-contract/src/delegation.rs +++ b/common/mixnet-contract/src/delegation.rs @@ -58,6 +58,27 @@ impl PagedMixDelegationsResponse { } } +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] +pub struct PagedReverseMixDelegationsResponse { + pub delegation_owner: Addr, + pub delegated_nodes: Vec, + pub start_next_after: Option, +} + +impl PagedReverseMixDelegationsResponse { + pub fn new( + delegation_owner: Addr, + delegated_nodes: Vec, + start_next_after: Option, + ) -> Self { + PagedReverseMixDelegationsResponse { + delegation_owner, + delegated_nodes, + start_next_after, + } + } +} + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] pub struct PagedGatewayDelegationsResponse { pub node_identity: IdentityKey, @@ -78,3 +99,24 @@ impl PagedGatewayDelegationsResponse { } } } + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] +pub struct PagedReverseGatewayDelegationsResponse { + pub delegation_owner: Addr, + pub delegated_nodes: Vec, + pub start_next_after: Option, +} + +impl PagedReverseGatewayDelegationsResponse { + pub fn new( + delegation_owner: Addr, + delegated_nodes: Vec, + start_next_after: Option, + ) -> Self { + PagedReverseGatewayDelegationsResponse { + delegation_owner, + delegated_nodes, + start_next_after, + } + } +} diff --git a/common/mixnet-contract/src/lib.rs b/common/mixnet-contract/src/lib.rs index 785497197b..e259065e31 100644 --- a/common/mixnet-contract/src/lib.rs +++ b/common/mixnet-contract/src/lib.rs @@ -8,7 +8,10 @@ mod msg; mod types; pub use cosmwasm_std::{Addr, Coin}; -pub use delegation::{Delegation, PagedGatewayDelegationsResponse, PagedMixDelegationsResponse}; +pub use delegation::{ + Delegation, PagedGatewayDelegationsResponse, PagedMixDelegationsResponse, + PagedReverseGatewayDelegationsResponse, PagedReverseMixDelegationsResponse, +}; pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayResponse}; pub use mixnode::{Layer, MixNode, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse}; pub use msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; diff --git a/common/mixnet-contract/src/msg.rs b/common/mixnet-contract/src/msg.rs index 43fdcc0d90..32cb96da86 100644 --- a/common/mixnet-contract/src/msg.rs +++ b/common/mixnet-contract/src/msg.rs @@ -75,6 +75,11 @@ pub enum QueryMsg { start_after: Option, limit: Option, }, + GetReverseMixDelegations { + delegation_owner: Addr, + start_after: Option, + limit: Option, + }, GetMixDelegation { mix_identity: IdentityKey, address: Addr, @@ -84,6 +89,11 @@ pub enum QueryMsg { start_after: Option, limit: Option, }, + GetReverseGatewayDelegations { + delegation_owner: Addr, + start_after: Option, + limit: Option, + }, GetGatewayDelegation { gateway_identity: IdentityKey, address: Addr, diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 5487ea451e..32af49cf66 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -3,14 +3,19 @@ use crate::helpers::calculate_epoch_reward_rate; use crate::state::State; -use crate::storage::{config, layer_distribution}; +use crate::storage::{ + config, layer_distribution, reverse_gateway_delegations, reverse_mix_delegations, +}; use crate::{error::ContractError, queries, transactions}; use config::defaults::NETWORK_MONITOR_ADDRESS; use cosmwasm_std::{ entry_point, to_binary, Addr, Decimal, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, Uint128, }; -use mixnet_contract::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StateParams}; +use mixnet_contract::{ + Delegation, ExecuteMsg, IdentityKey, IdentityKeyRef, InstantiateMsg, MigrateMsg, QueryMsg, + StateParams, +}; pub const INITIAL_DEFAULT_EPOCH_LENGTH: u32 = 2; @@ -149,6 +154,16 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result to_binary(&queries::query_reverse_mixnode_delegations_paged( + deps, + delegation_owner, + start_after, + limit, + )?), QueryMsg::GetMixDelegation { mix_identity, address, @@ -167,6 +182,16 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result to_binary(&queries::query_reverse_gateway_delegations_paged( + deps, + delegation_owner, + start_after, + limit, + )?), QueryMsg::GetGatewayDelegation { gateway_identity, address, @@ -181,7 +206,119 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result Result { +pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { + fn get_all_mixnodes_identities(deps: &DepsMut) -> Result, ContractError> { + let mut mixnode_bonds = Vec::new(); + let mut start_after = None; + loop { + let mut paged_response = + queries::query_mixnodes_paged(deps.as_ref(), start_after, None)?; + mixnode_bonds.append(&mut paged_response.nodes); + + if let Some(start_after_res) = paged_response.start_next_after { + start_after = Some(start_after_res) + } else { + break; + } + } + let mixnodes = mixnode_bonds + .into_iter() + .map(|bond| bond.mix_node.identity_key) + .collect(); + + Ok(mixnodes) + } + + fn get_all_gateways_identities(deps: &DepsMut) -> Result, ContractError> { + let mut gateway_bonds = Vec::new(); + let mut start_after = None; + loop { + let mut paged_response = + queries::query_gateways_paged(deps.as_ref(), start_after, None)?; + gateway_bonds.append(&mut paged_response.nodes); + + if let Some(start_after_res) = paged_response.start_next_after { + start_after = Some(start_after_res) + } else { + break; + } + } + let gateways = gateway_bonds + .into_iter() + .map(|bond| bond.gateway.identity_key) + .collect(); + + Ok(gateways) + } + + fn get_all_mixnode_delegations( + deps: &DepsMut, + mix_identity: IdentityKeyRef, + ) -> Result, ContractError> { + let mut delegations = Vec::new(); + let mut start_after = None; + loop { + let mut paged_response = queries::query_mixnode_delegations_paged( + deps.as_ref(), + mix_identity.into(), + start_after, + None, + )?; + delegations.append(&mut paged_response.delegations); + + if let Some(start_after_res) = paged_response.start_next_after { + start_after = Some(start_after_res) + } else { + break; + } + } + + Ok(delegations) + } + + fn get_all_gateway_delegations( + deps: &DepsMut, + gateway_identity: IdentityKeyRef, + ) -> Result, ContractError> { + let mut delegations = Vec::new(); + let mut start_after = None; + loop { + let mut paged_response = queries::query_gateway_delegations_paged( + deps.as_ref(), + gateway_identity.into(), + start_after, + None, + )?; + delegations.append(&mut paged_response.delegations); + + if let Some(start_after_res) = paged_response.start_next_after { + start_after = Some(start_after_res) + } else { + break; + } + } + + Ok(delegations) + } + + let mixnodes_identities = get_all_mixnodes_identities(&deps)?; + for mix_identity in mixnodes_identities { + let delegations = get_all_mixnode_delegations(&deps, &mix_identity)?; + for delegation in delegations { + reverse_mix_delegations(deps.storage, &delegation.owner()) + .save(mix_identity.as_bytes(), &())?; + } + } + + let gateways_identities = get_all_gateways_identities(&deps)?; + for gateway_identity in gateways_identities { + let delegations = get_all_gateway_delegations(&deps, &gateway_identity)?; + for delegation in delegations { + reverse_gateway_delegations(deps.storage, &delegation.owner()) + .save(gateway_identity.as_bytes(), &())?; + } + } + Ok(Default::default()) } diff --git a/contracts/mixnet/src/queries.rs b/contracts/mixnet/src/queries.rs index ed546a72d3..31f3d9c9ae 100644 --- a/contracts/mixnet/src/queries.rs +++ b/contracts/mixnet/src/queries.rs @@ -5,6 +5,7 @@ use crate::error::ContractError; use crate::storage::{ gateway_delegations_read, gateways_owners_read, gateways_read, mix_delegations_read, mixnodes_owners_read, mixnodes_read, read_layer_distribution, read_state_params, + reverse_gateway_delegations_read, reverse_mix_delegations_read, }; use config::defaults::DENOM; use cosmwasm_std::Deps; @@ -14,7 +15,8 @@ use cosmwasm_std::{coin, Addr}; use mixnet_contract::{ Delegation, GatewayBond, GatewayOwnershipResponse, IdentityKey, LayerDistribution, MixNodeBond, MixOwnershipResponse, PagedGatewayDelegationsResponse, PagedGatewayResponse, - PagedMixDelegationsResponse, PagedMixnodeResponse, StateParams, + PagedMixDelegationsResponse, PagedMixnodeResponse, PagedReverseGatewayDelegationsResponse, + PagedReverseMixDelegationsResponse, StateParams, }; const BOND_PAGE_MAX_LIMIT: u32 = 100; @@ -123,7 +125,9 @@ pub(crate) fn query_mixnode_delegations_paged( .map(|res| { res.map(|entry| { Delegation::new( - Addr::unchecked(String::from_utf8(entry.0).unwrap()), + Addr::unchecked(String::from_utf8(entry.0).expect( + "Non-UTF8 address used as key in bucket. The storage is corrupted!", + )), coin(entry.1.u128(), DENOM), ) }) @@ -139,6 +143,37 @@ pub(crate) fn query_mixnode_delegations_paged( )) } +pub(crate) fn query_reverse_mixnode_delegations_paged( + deps: Deps, + delegation_owner: Addr, + start_after: Option, + limit: Option, +) -> StdResult { + let limit = limit + .unwrap_or(DELEGATION_PAGE_DEFAULT_LIMIT) + .min(DELEGATION_PAGE_MAX_LIMIT) as usize; + let start = calculate_start_value(start_after); + + let delegations = reverse_mix_delegations_read(deps.storage, &delegation_owner) + .range(start.as_deref(), None, Order::Ascending) + .take(limit) + .map(|res| { + res.map(|entry| { + String::from_utf8(entry.0) + .expect("Non-UTF8 address used as key in bucket. The storage is corrupted!") + }) + }) + .collect::>>()?; + + let start_next_after = delegations.last().cloned(); + + Ok(PagedReverseMixDelegationsResponse::new( + delegation_owner, + delegations, + start_next_after, + )) +} + // queries for delegation value of given address for particular node pub(crate) fn query_mixnode_delegation( deps: Deps, @@ -174,7 +209,9 @@ pub(crate) fn query_gateway_delegations_paged( .map(|res| { res.map(|entry| { Delegation::new( - Addr::unchecked(String::from_utf8(entry.0).unwrap()), + Addr::unchecked(String::from_utf8(entry.0).expect( + "Non-UTF8 address used as key in bucket. The storage is corrupted!", + )), coin(entry.1.u128(), DENOM), ) }) @@ -190,6 +227,37 @@ pub(crate) fn query_gateway_delegations_paged( )) } +pub(crate) fn query_reverse_gateway_delegations_paged( + deps: Deps, + delegation_owner: Addr, + start_after: Option, + limit: Option, +) -> StdResult { + let limit = limit + .unwrap_or(DELEGATION_PAGE_DEFAULT_LIMIT) + .min(DELEGATION_PAGE_MAX_LIMIT) as usize; + let start = calculate_start_value(start_after); + + let delegations = reverse_gateway_delegations_read(deps.storage, &delegation_owner) + .range(start.as_deref(), None, Order::Ascending) + .take(limit) + .map(|res| { + res.map(|entry| { + String::from_utf8(entry.0) + .expect("Non-UTF8 address used as key in bucket. The storage is corrupted!") + }) + }) + .collect::>>()?; + + let start_next_after = delegations.last().cloned(); + + Ok(PagedReverseGatewayDelegationsResponse::new( + delegation_owner, + delegations, + start_next_after, + )) +} + // queries for delegation value of given address for particular node pub(crate) fn query_gateway_delegation( deps: Deps, @@ -786,6 +854,167 @@ mod tests { ) } + #[cfg(test)] + mod querying_for_reverse_mixnode_delegations_paged { + use super::*; + use crate::storage::reverse_mix_delegations; + + fn store_n_reverse_delegations(n: u32, storage: &mut dyn Storage, delegation_owner: &Addr) { + for i in 0..n { + let node_identity = format!("node{}", i); + reverse_mix_delegations(storage, delegation_owner) + .save(node_identity.as_bytes(), &()) + .unwrap(); + } + } + + #[test] + fn retrieval_obeys_limits() { + let mut deps = helpers::init_contract(); + let limit = 2; + let delegation_owner = Addr::unchecked("foo"); + store_n_reverse_delegations(100, &mut deps.storage, &delegation_owner); + + let page1 = query_reverse_mixnode_delegations_paged( + deps.as_ref(), + delegation_owner, + None, + Option::from(limit), + ) + .unwrap(); + assert_eq!(limit, page1.delegated_nodes.len() as u32); + } + + #[test] + fn retrieval_has_default_limit() { + let mut deps = helpers::init_contract(); + let delegation_owner = Addr::unchecked("foo"); + store_n_reverse_delegations( + DELEGATION_PAGE_DEFAULT_LIMIT * 10, + &mut deps.storage, + &delegation_owner, + ); + + // query without explicitly setting a limit + let page1 = query_reverse_mixnode_delegations_paged( + deps.as_ref(), + delegation_owner, + None, + None, + ) + .unwrap(); + assert_eq!( + DELEGATION_PAGE_DEFAULT_LIMIT, + page1.delegated_nodes.len() as u32 + ); + } + + #[test] + fn retrieval_has_max_limit() { + let mut deps = helpers::init_contract(); + let delegation_owner = Addr::unchecked("foo"); + store_n_reverse_delegations( + DELEGATION_PAGE_DEFAULT_LIMIT * 10, + &mut deps.storage, + &delegation_owner, + ); + + // query with a crazy high limit in an attempt to use too many resources + let crazy_limit = 1000 * DELEGATION_PAGE_DEFAULT_LIMIT; + let page1 = query_reverse_mixnode_delegations_paged( + deps.as_ref(), + delegation_owner, + None, + Option::from(crazy_limit), + ) + .unwrap(); + + // we default to a decent sized upper bound instead + let expected_limit = DELEGATION_PAGE_MAX_LIMIT; + assert_eq!(expected_limit, page1.delegated_nodes.len() as u32); + } + + #[test] + fn pagination_works() { + let mut deps = helpers::init_contract(); + let delegation_owner = Addr::unchecked("bar"); + + reverse_mix_delegations(&mut deps.storage, &delegation_owner) + .save("1".as_bytes(), &()) + .unwrap(); + + let per_page = 2; + let page1 = query_reverse_mixnode_delegations_paged( + deps.as_ref(), + delegation_owner.clone(), + None, + Option::from(per_page), + ) + .unwrap(); + + // page should have 1 result on it + assert_eq!(1, page1.delegated_nodes.len()); + + // save another + reverse_mix_delegations(&mut deps.storage, &delegation_owner) + .save("2".as_bytes(), &()) + .unwrap(); + + // page1 should have 2 results on it + let page1 = query_reverse_mixnode_delegations_paged( + deps.as_ref(), + delegation_owner.clone(), + None, + Option::from(per_page), + ) + .unwrap(); + assert_eq!(2, page1.delegated_nodes.len()); + + reverse_mix_delegations(&mut deps.storage, &delegation_owner) + .save("3".as_bytes(), &()) + .unwrap(); + + // page1 still has 2 results + let page1 = query_reverse_mixnode_delegations_paged( + deps.as_ref(), + delegation_owner.clone(), + None, + Option::from(per_page), + ) + .unwrap(); + assert_eq!(2, page1.delegated_nodes.len()); + + // retrieving the next page should start after the last key on this page + let start_after: IdentityKey = String::from("2"); + let page2 = query_reverse_mixnode_delegations_paged( + deps.as_ref(), + delegation_owner.clone(), + Option::from(start_after), + Option::from(per_page), + ) + .unwrap(); + + assert_eq!(1, page2.delegated_nodes.len()); + + // save another one + reverse_mix_delegations(&mut deps.storage, &delegation_owner) + .save("4".as_bytes(), &()) + .unwrap(); + + let start_after = String::from("2"); + let page2 = query_reverse_mixnode_delegations_paged( + deps.as_ref(), + delegation_owner, + Option::from(start_after), + Option::from(per_page), + ) + .unwrap(); + + // now we have 2 pages, with 2 results on the second page + assert_eq!(2, page2.delegated_nodes.len()); + } + } + #[cfg(test)] mod querying_for_gateway_delegations_paged { use super::*; @@ -1010,4 +1239,165 @@ mod tests { query_gateway_delegation(deps.as_ref(), node_identity1, delegation_owner1) ) } + + #[cfg(test)] + mod querying_for_reverse_gateway_delegations_paged { + use super::*; + use crate::storage::reverse_gateway_delegations; + + fn store_n_reverse_delegations(n: u32, storage: &mut dyn Storage, delegation_owner: &Addr) { + for i in 0..n { + let node_identity = format!("node{}", i); + reverse_gateway_delegations(storage, delegation_owner) + .save(node_identity.as_bytes(), &()) + .unwrap(); + } + } + + #[test] + fn retrieval_obeys_limits() { + let mut deps = helpers::init_contract(); + let limit = 2; + let delegation_owner = Addr::unchecked("foo"); + store_n_reverse_delegations(100, &mut deps.storage, &delegation_owner); + + let page1 = query_reverse_gateway_delegations_paged( + deps.as_ref(), + delegation_owner, + None, + Option::from(limit), + ) + .unwrap(); + assert_eq!(limit, page1.delegated_nodes.len() as u32); + } + + #[test] + fn retrieval_has_default_limit() { + let mut deps = helpers::init_contract(); + let delegation_owner = Addr::unchecked("foo"); + store_n_reverse_delegations( + DELEGATION_PAGE_DEFAULT_LIMIT * 10, + &mut deps.storage, + &delegation_owner, + ); + + // query without explicitly setting a limit + let page1 = query_reverse_gateway_delegations_paged( + deps.as_ref(), + delegation_owner, + None, + None, + ) + .unwrap(); + assert_eq!( + DELEGATION_PAGE_DEFAULT_LIMIT, + page1.delegated_nodes.len() as u32 + ); + } + + #[test] + fn retrieval_has_max_limit() { + let mut deps = helpers::init_contract(); + let delegation_owner = Addr::unchecked("foo"); + store_n_reverse_delegations( + DELEGATION_PAGE_DEFAULT_LIMIT * 10, + &mut deps.storage, + &delegation_owner, + ); + + // query with a crazy high limit in an attempt to use too many resources + let crazy_limit = 1000 * DELEGATION_PAGE_DEFAULT_LIMIT; + let page1 = query_reverse_gateway_delegations_paged( + deps.as_ref(), + delegation_owner, + None, + Option::from(crazy_limit), + ) + .unwrap(); + + // we default to a decent sized upper bound instead + let expected_limit = DELEGATION_PAGE_MAX_LIMIT; + assert_eq!(expected_limit, page1.delegated_nodes.len() as u32); + } + + #[test] + fn pagination_works() { + let mut deps = helpers::init_contract(); + let delegation_owner = Addr::unchecked("bar"); + + reverse_gateway_delegations(&mut deps.storage, &delegation_owner) + .save("1".as_bytes(), &()) + .unwrap(); + + let per_page = 2; + let page1 = query_reverse_gateway_delegations_paged( + deps.as_ref(), + delegation_owner.clone(), + None, + Option::from(per_page), + ) + .unwrap(); + + // page should have 1 result on it + assert_eq!(1, page1.delegated_nodes.len()); + + // save another + reverse_gateway_delegations(&mut deps.storage, &delegation_owner) + .save("2".as_bytes(), &()) + .unwrap(); + + // page1 should have 2 results on it + let page1 = query_reverse_gateway_delegations_paged( + deps.as_ref(), + delegation_owner.clone(), + None, + Option::from(per_page), + ) + .unwrap(); + assert_eq!(2, page1.delegated_nodes.len()); + + reverse_gateway_delegations(&mut deps.storage, &delegation_owner) + .save("3".as_bytes(), &()) + .unwrap(); + + // page1 still has 2 results + let page1 = query_reverse_gateway_delegations_paged( + deps.as_ref(), + delegation_owner.clone(), + None, + Option::from(per_page), + ) + .unwrap(); + assert_eq!(2, page1.delegated_nodes.len()); + + // retrieving the next page should start after the last key on this page + let start_after: IdentityKey = String::from("2"); + let page2 = query_reverse_gateway_delegations_paged( + deps.as_ref(), + delegation_owner.clone(), + Option::from(start_after), + Option::from(per_page), + ) + .unwrap(); + + assert_eq!(1, page2.delegated_nodes.len()); + + // save another one + reverse_gateway_delegations(&mut deps.storage, &delegation_owner) + .save("4".as_bytes(), &()) + .unwrap(); + + let start_after = String::from("2"); + let page2 = query_reverse_gateway_delegations_paged( + deps.as_ref(), + delegation_owner, + Option::from(start_after), + Option::from(per_page), + ) + .unwrap(); + + // now we have 2 pages, with 2 results on the second page + assert_eq!(2, page2.delegated_nodes.len()); + } + } } diff --git a/contracts/mixnet/src/storage.rs b/contracts/mixnet/src/storage.rs index 76eb357f86..e778cfc250 100644 --- a/contracts/mixnet/src/storage.rs +++ b/contracts/mixnet/src/storage.rs @@ -9,7 +9,8 @@ use cosmwasm_storage::{ Singleton, }; use mixnet_contract::{ - GatewayBond, IdentityKey, IdentityKeyRef, Layer, LayerDistribution, MixNodeBond, StateParams, + Addr, GatewayBond, IdentityKey, IdentityKeyRef, Layer, LayerDistribution, MixNodeBond, + StateParams, }; // storage prefixes @@ -29,6 +30,8 @@ const PREFIX_GATEWAYS_OWNERS: &[u8] = b"go"; const PREFIX_MIX_DELEGATION: &[u8] = b"md"; const PREFIX_GATEWAY_DELEGATION: &[u8] = b"gd"; +const PREFIX_REVERSE_MIX_DELEGATION: &[u8] = b"dm"; +const PREFIX_REVERSE_GATEWAY_DELEGATION: &[u8] = b"dg"; // Contract-level stuff @@ -301,6 +304,17 @@ pub fn mix_delegations_read<'a>( ReadonlyBucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()]) } +pub fn reverse_mix_delegations<'a>(storage: &'a mut dyn Storage, owner: &Addr) -> Bucket<'a, ()> { + Bucket::multilevel(storage, &[PREFIX_REVERSE_MIX_DELEGATION, owner.as_bytes()]) +} + +pub fn reverse_mix_delegations_read<'a>( + storage: &'a dyn Storage, + owner: &Addr, +) -> ReadonlyBucket<'a, ()> { + ReadonlyBucket::multilevel(storage, &[PREFIX_REVERSE_MIX_DELEGATION, owner.as_bytes()]) +} + pub fn gateway_delegations<'a>( storage: &'a mut dyn Storage, gateway_identity: IdentityKeyRef, @@ -321,6 +335,26 @@ pub fn gateway_delegations_read<'a>( ) } +pub fn reverse_gateway_delegations<'a>( + storage: &'a mut dyn Storage, + owner: &Addr, +) -> Bucket<'a, ()> { + Bucket::multilevel( + storage, + &[PREFIX_REVERSE_GATEWAY_DELEGATION, owner.as_bytes()], + ) +} + +pub fn reverse_gateway_delegations_read<'a>( + storage: &'a dyn Storage, + owner: &Addr, +) -> ReadonlyBucket<'a, ()> { + ReadonlyBucket::multilevel( + storage, + &[PREFIX_REVERSE_GATEWAY_DELEGATION, owner.as_bytes()], + ) +} + // currently not used outside tests #[cfg(test)] pub(crate) fn read_gateway_bond( @@ -574,6 +608,71 @@ mod tests { } } + #[cfg(test)] + mod reverse_mix_delegations { + use super::*; + use crate::support::tests::helpers; + + #[test] + fn reverse_mix_delegation_exists() { + let mut deps = helpers::init_contract(); + let node_identity: IdentityKey = "foo".into(); + let delegation_owner = Addr::unchecked("bar"); + + reverse_mix_delegations(&mut deps.storage, &delegation_owner) + .save(node_identity.as_bytes(), &()) + .unwrap(); + + assert!( + reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner) + .may_load(node_identity.as_bytes()) + .unwrap() + .is_some(), + ); + } + + #[test] + fn reverse_mix_delegation_returns_none_if_delegation_doesnt_exist() { + let mut deps = helpers::init_contract(); + + let node_identity1: IdentityKey = "foo1".into(); + let node_identity2: IdentityKey = "foo2".into(); + let delegation_owner1 = Addr::unchecked("bar"); + let delegation_owner2 = Addr::unchecked("bar2"); + + assert!( + reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner1) + .may_load(node_identity1.as_bytes()) + .unwrap() + .is_none() + ); + + // add delegation for a different node + reverse_mix_delegations(&mut deps.storage, &delegation_owner1) + .save(node_identity2.as_bytes(), &()) + .unwrap(); + + assert!( + reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner1) + .may_load(node_identity1.as_bytes()) + .unwrap() + .is_none() + ); + + // add delegation from a different owner + reverse_mix_delegations(&mut deps.storage, &delegation_owner2) + .save(node_identity1.as_bytes(), &()) + .unwrap(); + + assert!( + reverse_mix_delegations_read(deps.as_ref().storage, &delegation_owner1) + .may_load(node_identity1.as_bytes()) + .unwrap() + .is_none() + ); + } + } + #[cfg(test)] mod increasing_gateway_delegated_stakes { use super::*; @@ -710,4 +809,69 @@ mod tests { } } } + + #[cfg(test)] + mod reverse_gateway_delegations { + use super::*; + use crate::support::tests::helpers; + + #[test] + fn reverse_gateway_delegation_exists() { + let mut deps = helpers::init_contract(); + let node_identity: IdentityKey = "foo".into(); + let delegation_owner = Addr::unchecked("bar"); + + reverse_gateway_delegations(&mut deps.storage, &delegation_owner) + .save(node_identity.as_bytes(), &()) + .unwrap(); + + assert!( + reverse_gateway_delegations_read(deps.as_ref().storage, &delegation_owner) + .may_load(node_identity.as_bytes()) + .unwrap() + .is_some(), + ); + } + + #[test] + fn reverse_gateway_delegation_returns_none_if_delegation_doesnt_exist() { + let mut deps = helpers::init_contract(); + + let node_identity1: IdentityKey = "foo1".into(); + let node_identity2: IdentityKey = "foo2".into(); + let delegation_owner1 = Addr::unchecked("bar"); + let delegation_owner2 = Addr::unchecked("bar2"); + + assert!( + reverse_gateway_delegations_read(deps.as_ref().storage, &delegation_owner1) + .may_load(node_identity1.as_bytes()) + .unwrap() + .is_none() + ); + + // add delegation for a different node + reverse_gateway_delegations(&mut deps.storage, &delegation_owner1) + .save(node_identity2.as_bytes(), &()) + .unwrap(); + + assert!( + reverse_gateway_delegations_read(deps.as_ref().storage, &delegation_owner1) + .may_load(node_identity1.as_bytes()) + .unwrap() + .is_none() + ); + + // add delegation from a different owner + reverse_gateway_delegations(&mut deps.storage, &delegation_owner2) + .save(node_identity1.as_bytes(), &()) + .unwrap(); + + assert!( + reverse_gateway_delegations_read(deps.as_ref().storage, &delegation_owner1) + .may_load(node_identity1.as_bytes()) + .unwrap() + .is_none() + ); + } + } } diff --git a/contracts/mixnet/src/transactions.rs b/contracts/mixnet/src/transactions.rs index 70c30f014d..5f1f41b3c3 100644 --- a/contracts/mixnet/src/transactions.rs +++ b/contracts/mixnet/src/transactions.rs @@ -572,6 +572,8 @@ pub(crate) fn try_delegate_to_mixnode( None => delegation_bucket.save(sender_bytes, &info.funds[0].amount)?, } + reverse_mix_delegations(deps.storage, &info.sender).save(mix_identity.as_bytes(), &())?; + Ok(Response::default()) } @@ -584,8 +586,9 @@ pub(crate) fn try_remove_delegation_from_mixnode( let sender_bytes = info.sender.as_bytes(); match delegation_bucket.may_load(sender_bytes)? { Some(delegation) => { - // remove delegation from the bucket + // remove delegation from the buckets delegation_bucket.remove(sender_bytes); + reverse_mix_delegations(deps.storage, &info.sender).remove(mix_identity.as_bytes()); // send delegated funds back to the delegation owner let messages = vec![BankMsg::Send { @@ -656,6 +659,9 @@ pub(crate) fn try_delegate_to_gateway( None => delegation_bucket.save(sender_bytes, &info.funds[0].amount)?, } + reverse_gateway_delegations(deps.storage, &info.sender) + .save(gateway_identity.as_bytes(), &())?; + Ok(Response::default()) } @@ -668,8 +674,10 @@ pub(crate) fn try_remove_delegation_from_gateway( let sender_bytes = info.sender.as_bytes(); match delegation_bucket.may_load(sender_bytes)? { Some(delegation) => { - // remove delegation from the bucket + // remove delegation from the buckets delegation_bucket.remove(sender_bytes); + reverse_gateway_delegations(deps.storage, &info.sender) + .remove(gateway_identity.as_bytes()); // send delegated funds back to the delegation owner let messages = vec![BankMsg::Send { @@ -2030,11 +2038,12 @@ pub mod tests { let mut deps = helpers::init_contract(); let mixnode_owner = "bob"; let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); let delegation = coin(123, DENOM); assert!(try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &vec![delegation.clone()]), + mock_info(delegation_owner.as_str(), &vec![delegation.clone()]), identity.clone() ) .is_ok()); @@ -2042,9 +2051,14 @@ pub mod tests { assert_eq!( delegation.amount, mix_delegations_read(&deps.storage, &identity) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() ); + assert!( + reverse_mix_delegations_read(&deps.storage, &delegation_owner) + .load(identity.as_bytes()) + .is_ok() + ); // node's "total_delegation" is increased assert_eq!( @@ -2062,6 +2076,7 @@ pub mod tests { let mixnode_owner = "bob"; let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap(); @@ -2071,7 +2086,7 @@ pub mod tests { }), try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &coins(123, DENOM)), + mock_info(delegation_owner.as_str(), &coins(123, DENOM)), identity ) ); @@ -2086,10 +2101,11 @@ pub mod tests { try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap(); let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); let delegation = coin(123, DENOM); + let delegation_owner = Addr::unchecked("sender"); assert!(try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &vec![delegation.clone()]), + mock_info(delegation_owner.as_str(), &vec![delegation.clone()]), identity.clone() ) .is_ok()); @@ -2097,9 +2113,14 @@ pub mod tests { assert_eq!( delegation.amount, mix_delegations_read(&deps.storage, &identity) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() ); + assert!( + reverse_mix_delegations_read(&deps.storage, &delegation_owner) + .load(identity.as_bytes()) + .is_ok() + ); // node's "total_delegation" is increased assert_eq!( @@ -2116,20 +2137,21 @@ pub mod tests { let mut deps = helpers::init_contract(); let mixnode_owner = "bob"; let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); let delegation1 = coin(100, DENOM); let delegation2 = coin(50, DENOM); try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &vec![delegation1.clone()]), + mock_info(delegation_owner.as_str(), &vec![delegation1.clone()]), identity.clone(), ) .unwrap(); try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &vec![delegation2.clone()]), + mock_info(delegation_owner.as_str(), &vec![delegation2.clone()]), identity.clone(), ) .unwrap(); @@ -2137,9 +2159,14 @@ pub mod tests { assert_eq!( delegation1.amount + delegation2.amount, mix_delegations_read(&deps.storage, &identity) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() ); + assert!( + reverse_mix_delegations_read(&deps.storage, &delegation_owner) + .load(identity.as_bytes()) + .is_ok() + ); // node's "total_delegation" is sum of both assert_eq!( @@ -2158,10 +2185,11 @@ pub mod tests { let mixnode_owner = "bob"; let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &coins(100, DENOM)), + mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) .unwrap(); @@ -2174,7 +2202,7 @@ pub mod tests { }), try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &coins(50, DENOM)), + mock_info(delegation_owner.as_str(), &coins(50, DENOM)), identity ) ); @@ -2187,17 +2215,18 @@ pub mod tests { let mixnode_owner2 = "fred"; let identity1 = add_mixnode(mixnode_owner1, good_mixnode_bond(), &mut deps); let identity2 = add_mixnode(mixnode_owner2, good_mixnode_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); assert!(try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &coins(123, DENOM)), + mock_info(delegation_owner.as_str(), &coins(123, DENOM)), identity1.clone() ) .is_ok()); assert!(try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &coins(42, DENOM)), + mock_info(delegation_owner.as_str(), &coins(42, DENOM)), identity2.clone() ) .is_ok()); @@ -2205,18 +2234,28 @@ pub mod tests { assert_eq!( 123, mix_delegations_read(&deps.storage, &identity1) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() .u128() ); + assert!( + reverse_mix_delegations_read(&deps.storage, &delegation_owner) + .load(identity1.as_bytes()) + .is_ok() + ); assert_eq!( 42, mix_delegations_read(&deps.storage, &identity2) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() .u128() ); + assert!( + reverse_mix_delegations_read(&deps.storage, &delegation_owner) + .load(identity2.as_bytes()) + .is_ok() + ); } #[test] @@ -2259,10 +2298,11 @@ pub mod tests { let mixnode_owner = "bob"; let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &coins(100, DENOM)), + mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) .unwrap(); @@ -2272,10 +2312,15 @@ pub mod tests { assert_eq!( 100, mix_delegations_read(&deps.storage, &identity) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() .u128() ); + assert!( + reverse_mix_delegations_read(&deps.storage, &delegation_owner) + .load(identity.as_bytes()) + .is_ok() + ); } } @@ -2291,15 +2336,16 @@ pub mod tests { let mixnode_owner = "bob"; let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); assert_eq!( Err(ContractError::NoMixnodeDelegationFound { identity: identity.clone(), - address: Addr::unchecked("sender"), + address: delegation_owner.clone(), }), try_remove_delegation_from_mixnode( deps.as_mut(), - mock_info("sender", &[]), + mock_info(delegation_owner.as_str(), &[]), identity, ) ); @@ -2311,10 +2357,11 @@ pub mod tests { let mixnode_owner = "bob"; let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &coins(100, DENOM)), + mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) .unwrap(); @@ -2323,7 +2370,7 @@ pub mod tests { Ok(Response { submessages: vec![], messages: vec![BankMsg::Send { - to_address: "sender".into(), + to_address: delegation_owner.clone().into(), amount: coins(100, DENOM), } .into()], @@ -2332,15 +2379,21 @@ pub mod tests { }), try_remove_delegation_from_mixnode( deps.as_mut(), - mock_info("sender", &[]), + mock_info(delegation_owner.as_str(), &[]), identity.clone(), ) ); assert!(mix_delegations_read(&deps.storage, &identity) - .may_load(b"sender") + .may_load(delegation_owner.as_bytes()) .unwrap() .is_none()); + assert!( + reverse_mix_delegations_read(&deps.storage, &delegation_owner) + .may_load(identity.as_bytes()) + .unwrap() + .is_none() + ); // and total delegation is cleared assert_eq!( @@ -2359,10 +2412,11 @@ pub mod tests { let mixnode_owner = "bob"; let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender", &coins(100, DENOM)), + mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) .unwrap(); @@ -2373,7 +2427,7 @@ pub mod tests { Ok(Response { submessages: vec![], messages: vec![BankMsg::Send { - to_address: "sender".into(), + to_address: delegation_owner.clone().into(), amount: coins(100, DENOM), } .into()], @@ -2382,15 +2436,21 @@ pub mod tests { }), try_remove_delegation_from_mixnode( deps.as_mut(), - mock_info("sender", &[]), + mock_info(delegation_owner.as_str(), &[]), identity.clone(), ) ); assert!(mix_delegations_read(&deps.storage, &identity) - .may_load(b"sender") + .may_load(delegation_owner.as_bytes()) .unwrap() .is_none()); + assert!( + reverse_mix_delegations_read(&deps.storage, &delegation_owner) + .may_load(identity.as_bytes()) + .unwrap() + .is_none() + ); } #[test] @@ -2398,20 +2458,22 @@ pub mod tests { let mut deps = helpers::init_contract(); let mixnode_owner = "bob"; let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner1 = Addr::unchecked("sender1"); + let delegation_owner2 = Addr::unchecked("sender2"); let delegation1 = coin(123, DENOM); let delegation2 = coin(234, DENOM); assert!(try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender1", &vec![delegation1.clone()]), + mock_info(delegation_owner1.as_str(), &vec![delegation1.clone()]), identity.clone() ) .is_ok()); assert!(try_delegate_to_mixnode( deps.as_mut(), - mock_info("sender2", &vec![delegation2.clone()]), + mock_info(delegation_owner2.as_str(), &vec![delegation2.clone()]), identity.clone() ) .is_ok()); @@ -2419,7 +2481,7 @@ pub mod tests { // sender1 undelegates try_remove_delegation_from_mixnode( deps.as_mut(), - mock_info("sender1", &[]), + mock_info(delegation_owner1.as_str(), &[]), identity.clone(), ) .unwrap(); @@ -2638,11 +2700,12 @@ pub mod tests { let mut deps = helpers::init_contract(); let gateway_owner = "bob"; let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); let delegation = coin(123, DENOM); assert!(try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &vec![delegation.clone()]), + mock_info(delegation_owner.as_str(), &vec![delegation.clone()]), identity.clone() ) .is_ok()); @@ -2650,9 +2713,14 @@ pub mod tests { assert_eq!( delegation.amount, gateway_delegations_read(&deps.storage, &identity) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() ); + assert!( + reverse_gateway_delegations_read(&deps.storage, &delegation_owner) + .load(identity.as_bytes()) + .is_ok() + ); // node's "total_delegation" is increased assert_eq!( @@ -2670,6 +2738,8 @@ pub mod tests { let gateway_owner = "bob"; let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); + try_remove_gateway(deps.as_mut(), mock_info(gateway_owner, &[])).unwrap(); assert_eq!( @@ -2678,7 +2748,7 @@ pub mod tests { }), try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &coins(123, DENOM)), + mock_info(delegation_owner.as_str(), &coins(123, DENOM)), identity ) ); @@ -2693,10 +2763,11 @@ pub mod tests { try_remove_gateway(deps.as_mut(), mock_info(gateway_owner, &[])).unwrap(); let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); let delegation = coin(123, DENOM); + let delegation_owner = Addr::unchecked("sender"); assert!(try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &vec![delegation.clone()]), + mock_info(delegation_owner.as_str(), &vec![delegation.clone()]), identity.clone() ) .is_ok()); @@ -2704,9 +2775,14 @@ pub mod tests { assert_eq!( delegation.amount, gateway_delegations_read(&deps.storage, &identity) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() ); + assert!( + reverse_gateway_delegations_read(&deps.storage, &delegation_owner) + .load(identity.as_bytes()) + .is_ok() + ); // node's "total_delegation" is increased assert_eq!( @@ -2723,20 +2799,21 @@ pub mod tests { let mut deps = helpers::init_contract(); let gateway_owner = "bob"; let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); let delegation1 = coin(100, DENOM); let delegation2 = coin(50, DENOM); try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &vec![delegation1.clone()]), + mock_info(delegation_owner.as_str(), &vec![delegation1.clone()]), identity.clone(), ) .unwrap(); try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &vec![delegation2.clone()]), + mock_info(delegation_owner.as_str(), &vec![delegation2.clone()]), identity.clone(), ) .unwrap(); @@ -2744,9 +2821,14 @@ pub mod tests { assert_eq!( delegation1.amount + delegation2.amount, gateway_delegations_read(&deps.storage, &identity) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() ); + assert!( + reverse_gateway_delegations_read(&deps.storage, &delegation_owner) + .load(identity.as_bytes()) + .is_ok() + ); // node's "total_delegation" is sum of both assert_eq!( @@ -2765,10 +2847,11 @@ pub mod tests { let gateway_owner = "bob"; let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &coins(100, DENOM)), + mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) .unwrap(); @@ -2781,7 +2864,7 @@ pub mod tests { }), try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &coins(50, DENOM)), + mock_info(delegation_owner.as_str(), &coins(50, DENOM)), identity.clone() ) ); @@ -2794,17 +2877,18 @@ pub mod tests { let gateway_owner2 = "fred"; let identity1 = add_gateway(gateway_owner1, good_gateway_bond(), &mut deps); let identity2 = add_gateway(gateway_owner2, good_gateway_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); assert!(try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &coins(123, DENOM)), + mock_info(delegation_owner.as_str(), &coins(123, DENOM)), identity1.clone() ) .is_ok()); assert!(try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &coins(42, DENOM)), + mock_info(delegation_owner.as_str(), &coins(42, DENOM)), identity2.clone() ) .is_ok()); @@ -2812,18 +2896,28 @@ pub mod tests { assert_eq!( 123, gateway_delegations_read(&deps.storage, &identity1) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() .u128() ); + assert!( + reverse_gateway_delegations_read(&deps.storage, &delegation_owner) + .load(identity1.as_bytes()) + .is_ok() + ); assert_eq!( 42, gateway_delegations_read(&deps.storage, &identity2) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() .u128() ); + assert!( + reverse_gateway_delegations_read(&deps.storage, &delegation_owner) + .load(identity2.as_bytes()) + .is_ok() + ); } #[test] @@ -2866,10 +2960,11 @@ pub mod tests { let gateway_owner = "bob"; let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &coins(100, DENOM)), + mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) .unwrap(); @@ -2879,10 +2974,15 @@ pub mod tests { assert_eq!( 100, gateway_delegations_read(&deps.storage, &identity) - .load(b"sender") + .load(delegation_owner.as_bytes()) .unwrap() .u128() ); + assert!( + reverse_gateway_delegations_read(&deps.storage, &delegation_owner) + .load(identity.as_bytes()) + .is_ok() + ); } } @@ -2898,14 +2998,16 @@ pub mod tests { let gateway_owner = "bob"; let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); + assert_eq!( Err(ContractError::NoGatewayDelegationFound { identity: identity.clone(), - address: Addr::unchecked("sender"), + address: delegation_owner.clone(), }), try_remove_delegation_from_gateway( deps.as_mut(), - mock_info("sender", &[]), + mock_info(delegation_owner.as_str(), &[]), identity, ) ); @@ -2917,10 +3019,11 @@ pub mod tests { let gateway_owner = "bob"; let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &coins(100, DENOM)), + mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) .unwrap(); @@ -2929,7 +3032,7 @@ pub mod tests { Ok(Response { submessages: vec![], messages: vec![BankMsg::Send { - to_address: "sender".into(), + to_address: delegation_owner.clone().into(), amount: coins(100, DENOM), } .into()], @@ -2938,15 +3041,21 @@ pub mod tests { }), try_remove_delegation_from_gateway( deps.as_mut(), - mock_info("sender", &[]), + mock_info(delegation_owner.as_str(), &[]), identity.clone(), ) ); assert!(gateway_delegations_read(&deps.storage, &identity) - .may_load(b"sender") + .may_load(delegation_owner.as_bytes()) .unwrap() .is_none()); + assert!( + reverse_mix_delegations_read(&deps.storage, &delegation_owner) + .may_load(identity.as_bytes()) + .unwrap() + .is_none() + ); // and total delegation is cleared assert_eq!( @@ -2965,10 +3074,11 @@ pub mod tests { let gateway_owner = "bob"; let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); try_delegate_to_gateway( deps.as_mut(), - mock_info("sender", &coins(100, DENOM)), + mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) .unwrap(); @@ -2979,7 +3089,7 @@ pub mod tests { Ok(Response { submessages: vec![], messages: vec![BankMsg::Send { - to_address: "sender".into(), + to_address: delegation_owner.clone().into(), amount: coins(100, DENOM), } .into()], @@ -2988,15 +3098,21 @@ pub mod tests { }), try_remove_delegation_from_gateway( deps.as_mut(), - mock_info("sender", &[]), + mock_info(delegation_owner.as_str(), &[]), identity.clone(), ) ); assert!(gateway_delegations_read(&deps.storage, &identity) - .may_load(b"sender") + .may_load(delegation_owner.as_bytes()) .unwrap() .is_none()); + assert!( + reverse_gateway_delegations_read(&deps.storage, &delegation_owner) + .may_load(identity.as_bytes()) + .unwrap() + .is_none() + ); } #[test] @@ -3004,20 +3120,22 @@ pub mod tests { let mut deps = helpers::init_contract(); let gateway_owner = "bob"; let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner1 = Addr::unchecked("sender1"); + let delegation_owner2 = Addr::unchecked("sender2"); let delegation1 = coin(123, DENOM); let delegation2 = coin(234, DENOM); assert!(try_delegate_to_gateway( deps.as_mut(), - mock_info("sender1", &vec![delegation1.clone()]), + mock_info(delegation_owner1.as_str(), &vec![delegation1.clone()]), identity.clone() ) .is_ok()); assert!(try_delegate_to_gateway( deps.as_mut(), - mock_info("sender2", &vec![delegation2.clone()]), + mock_info(delegation_owner2.as_str(), &vec![delegation2.clone()]), identity.clone() ) .is_ok()); @@ -3025,7 +3143,7 @@ pub mod tests { // sender1 undelegates try_remove_delegation_from_gateway( deps.as_mut(), - mock_info("sender1", &[]), + mock_info(delegation_owner1.as_str(), &[]), identity.clone(), ) .unwrap(); From 122f5d9f2e5c1ced96e3b9ba0c74ef8b7dbde2c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Mon, 30 Aug 2021 10:27:20 +0300 Subject: [PATCH 09/28] Feature/cred after handshake (#745) * Call perform_initial_authentication instead of register in clients * Refactor the register/authenticate functions a bit * Introduce Bandwidth request type * Add encryption layer to cred * Remove cred pass and check from handshake * Replaced unreachable! with error * Changed decrypt_tagged signature to not take mutable ownership of data * Put handle_bandwidth work inside a function * Add check before unwrap * Remove unnecessary async * Decouple bandwidth credential from authentication * Use new_error for ServerResponse:Error * Send a fresh IV each time the BandwidthCredential request is sent * Remove unwrap of bincode::serialize * Add comment regarding Bandwidth response * Remove _mut from naming * Leave Debug trait alone, as the initial error doesn't reproduce anymore * Pass iv as Vec instead of base58 string * Renamed AuthenticationIV to IV, as it is now used for more the just authentication * Did some IV refactorization --- clients/client-core/src/client/key_manager.rs | 4 +- clients/native/src/commands/init.rs | 4 +- clients/socks5/src/commands/init.rs | 4 +- clients/tauri-client/src-tauri/src/main.rs | 2 +- .../client-libs/gateway-client/src/client.rs | 75 +++++++++++++++---- .../client-libs/gateway-client/src/error.rs | 10 +++ common/coconut-interface/src/lib.rs | 2 +- .../src/authentication/encrypted_address.rs | 11 +-- .../src/authentication/mod.rs | 1 - .../src/{authentication => }/iv.rs | 20 ++--- gateway/gateway-requests/src/lib.rs | 6 +- .../src/registration/handshake/client.rs | 9 +-- .../src/registration/handshake/error.rs | 2 - .../src/registration/handshake/gateway.rs | 20 +---- .../src/registration/handshake/mod.rs | 15 +--- .../src/registration/handshake/shared_key.rs | 38 +++++++++- .../src/registration/handshake/state.rs | 18 +---- gateway/gateway-requests/src/types.rs | 70 +++++++++-------- .../node/client_handling/clients_handler.rs | 6 +- .../websocket/connection_handler.rs | 55 ++++++++++++-- gateway/src/node/storage/ledger.rs | 4 +- 21 files changed, 228 insertions(+), 148 deletions(-) rename gateway/gateway-requests/src/{authentication => }/iv.rs (81%) diff --git a/clients/client-core/src/client/key_manager.rs b/clients/client-core/src/client/key_manager.rs index cad8f726d4..f852af3806 100644 --- a/clients/client-core/src/client/key_manager.rs +++ b/clients/client-core/src/client/key_manager.rs @@ -61,8 +61,8 @@ impl KeyManager { // I have absolutely no idea why the compiler insists it's unused. The call happens during client::init::execute #[allow(dead_code)] /// After shared key with the gateway is derived, puts its ownership to this instance of a [`KeyManager`]. - pub fn insert_gateway_shared_key(&mut self, gateway_shared_key: SharedKeys) { - self.gateway_shared_key = Some(Arc::new(gateway_shared_key)) + pub fn insert_gateway_shared_key(&mut self, gateway_shared_key: Arc) { + self.gateway_shared_key = Some(gateway_shared_key) } /// Loads previously stored keys from the disk. diff --git a/clients/native/src/commands/init.rs b/clients/native/src/commands/init.rs index 2035daf78c..cb37d96b27 100644 --- a/clients/native/src/commands/init.rs +++ b/clients/native/src/commands/init.rs @@ -79,7 +79,7 @@ async fn register_with_gateway( gateway: &gateway::Node, our_identity: Arc, validator_urls: Vec, -) -> SharedKeys { +) -> Arc { let timeout = Duration::from_millis(1500); let coconut_credential = prepare_temporary_credential(&validator_urls, &our_identity.public_key().to_bytes()).await; @@ -95,7 +95,7 @@ async fn register_with_gateway( .await .expect("failed to establish connection with the gateway!"); gateway_client - .register() + .perform_initial_authentication() .await .expect("failed to register with the gateway!") } diff --git a/clients/socks5/src/commands/init.rs b/clients/socks5/src/commands/init.rs index 99e8eff716..58916dd45c 100644 --- a/clients/socks5/src/commands/init.rs +++ b/clients/socks5/src/commands/init.rs @@ -79,7 +79,7 @@ async fn register_with_gateway( gateway: &gateway::Node, our_identity: Arc, validator_urls: Vec, -) -> SharedKeys { +) -> Arc { let timeout = Duration::from_millis(1500); let coconut_credential = prepare_temporary_credential(&validator_urls, &our_identity.public_key().to_bytes()).await; @@ -95,7 +95,7 @@ async fn register_with_gateway( .await .expect("failed to establish connection with the gateway!"); gateway_client - .register() + .perform_initial_authentication() .await .expect("failed to register with the gateway!") } diff --git a/clients/tauri-client/src-tauri/src/main.rs b/clients/tauri-client/src-tauri/src/main.rs index 7c5bb06df7..bef090400f 100644 --- a/clients/tauri-client/src-tauri/src/main.rs +++ b/clients/tauri-client/src-tauri/src/main.rs @@ -144,7 +144,7 @@ async fn verify_credential( .ok_or("Got invalid signature idx")?, ); - Ok(credential.verify(&verification_key).await) + Ok(credential.verify(&verification_key)) } #[tauri::command] diff --git a/common/client-libs/gateway-client/src/client.rs b/common/client-libs/gateway-client/src/client.rs index 52bdc40a35..d250bc720d 100644 --- a/common/client-libs/gateway-client/src/client.rs +++ b/common/client-libs/gateway-client/src/client.rs @@ -12,7 +12,7 @@ use coconut_interface::Credential; use crypto::asymmetric::identity; use futures::{FutureExt, SinkExt, StreamExt}; use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes; -use gateway_requests::authentication::iv::AuthenticationIV; +use gateway_requests::iv::IV; use gateway_requests::registration::handshake::{client_handshake, SharedKeys}; use gateway_requests::{BinaryRequest, ClientControlRequest, ServerResponse}; use log::*; @@ -36,6 +36,8 @@ const DEFAULT_RECONNECTION_BACKOFF: Duration = Duration::from_secs(5); pub struct GatewayClient { authenticated: bool, + // TODO: This should be replaced by an actual bandwidth value, with 0 meaning no bandwidth + has_bandwidth: bool, gateway_address: String, gateway_identity: identity::PublicKey, local_identity: Arc, @@ -70,6 +72,7 @@ impl GatewayClient { ) -> Self { GatewayClient { authenticated: false, + has_bandwidth: false, gateway_address, gateway_identity, local_identity, @@ -114,6 +117,7 @@ impl GatewayClient { GatewayClient { authenticated: false, + has_bandwidth: false, gateway_address, gateway_identity, local_identity, @@ -362,7 +366,7 @@ impl GatewayClient { } } - pub async fn register(&mut self) -> Result { + async fn register(&mut self) -> Result<(), GatewayClientError> { if !self.connection.is_established() { return Err(GatewayClientError::ConnectionNotEstablished); } @@ -379,21 +383,26 @@ impl GatewayClient { ws_stream, self.local_identity.as_ref(), self.gateway_identity, - self.coconut_credential.clone(), ) .await .map_err(GatewayClientError::RegistrationFailure), _ => unreachable!(), }?; - - self.authenticated = true; - Ok(shared_key) + self.authenticated = match self.read_control_response().await? { + ServerResponse::Register { status } => Ok(status), + ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)), + _ => Err(GatewayClientError::UnexpectedResponse), + }?; + if self.authenticated { + self.shared_key = Some(Arc::new(shared_key)); + } + Ok(()) } - pub async fn authenticate( + async fn authenticate( &mut self, shared_key: Option, - ) -> Result { + ) -> Result<(), GatewayClientError> { if shared_key.is_none() && self.shared_key.is_none() { return Err(GatewayClientError::NoSharedKeyAvailable); } @@ -409,7 +418,7 @@ impl GatewayClient { let shared_key = shared_key .as_ref() .unwrap_or_else(|| self.shared_key.as_ref().unwrap()); - let iv = AuthenticationIV::new_random(&mut rng); + let iv = IV::new_random(&mut rng); let self_address = self .local_identity .as_ref() @@ -420,15 +429,14 @@ impl GatewayClient { let msg = ClientControlRequest::new_authenticate(self_address, encrypted_address, iv).into(); - let authenticated = match self.send_websocket_message(msg).await? { + match self.send_websocket_message(msg).await? { ServerResponse::Authenticate { status } => { self.authenticated = status; - Ok(status) + Ok(()) } ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)), - _ => unreachable!(), - }?; - Ok(authenticated) + _ => Err(GatewayClientError::UnexpectedResponse), + } } /// Helper method to either call register or authenticate based on self.shared_key value @@ -438,8 +446,7 @@ impl GatewayClient { if self.shared_key.is_some() { self.authenticate(None).await?; } else { - let shared_key = self.register().await?; - self.shared_key = Some(Arc::new(shared_key)); + self.register().await?; } if self.authenticated { // if we are authenticated it means we MUST have an associated shared_key @@ -449,6 +456,32 @@ impl GatewayClient { } } + pub async fn claim_bandwidth(&mut self) -> Result<(), GatewayClientError> { + if !self.authenticated { + return Err(GatewayClientError::NotAuthenticated); + } + if self.shared_key.is_none() { + return Err(GatewayClientError::NoSharedKeyAvailable); + } + + let mut rng = OsRng; + let iv = IV::new_random(&mut rng); + + let msg = ClientControlRequest::new_enc_bandwidth_credential( + &self.coconut_credential, + self.shared_key.as_ref().unwrap(), + iv, + ) + .ok_or(GatewayClientError::SerializeCredential)? + .into(); + self.has_bandwidth = match self.send_websocket_message(msg).await? { + ServerResponse::Bandwidth { status } => Ok(status), + ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)), + _ => Err(GatewayClientError::UnexpectedResponse), + }?; + Ok(()) + } + pub async fn batch_send_mix_packets( &mut self, packets: Vec, @@ -456,6 +489,9 @@ impl GatewayClient { if !self.authenticated { return Err(GatewayClientError::NotAuthenticated); } + if !self.has_bandwidth { + return Err(GatewayClientError::NotEnoughBandwidth); + } if !self.connection.is_established() { return Err(GatewayClientError::ConnectionNotEstablished); } @@ -514,6 +550,9 @@ impl GatewayClient { if !self.authenticated { return Err(GatewayClientError::NotAuthenticated); } + if !self.has_bandwidth { + return Err(GatewayClientError::NotEnoughBandwidth); + } if !self.connection.is_established() { return Err(GatewayClientError::ConnectionNotEstablished); } @@ -559,6 +598,9 @@ impl GatewayClient { if !self.authenticated { return Err(GatewayClientError::NotAuthenticated); } + if !self.has_bandwidth { + return Err(GatewayClientError::NotEnoughBandwidth); + } if self.connection.is_partially_delegated() { return Ok(()); } @@ -591,6 +633,7 @@ impl GatewayClient { self.establish_connection().await?; } let shared_key = self.perform_initial_authentication().await?; + self.claim_bandwidth().await?; // this call is NON-blocking self.start_listening_for_mixnet_messages()?; diff --git a/common/client-libs/gateway-client/src/error.rs b/common/client-libs/gateway-client/src/error.rs index 2f9938b57a..170314e484 100644 --- a/common/client-libs/gateway-client/src/error.rs +++ b/common/client-libs/gateway-client/src/error.rs @@ -21,7 +21,10 @@ pub enum GatewayClientError { NoSharedKeyAvailable, ConnectionAbruptlyClosed, MalformedResponse, + SerializeCredential, NotAuthenticated, + NotEnoughBandwidth, + UnexpectedResponse, ConnectionInInvalidState, RegistrationFailure(HandshakeError), AuthenticationFailure, @@ -98,6 +101,13 @@ impl fmt::Display for GatewayClientError { GatewayClientError::GatewayError(err) => { write!(f, "gateway returned an error response - {}", err) } + GatewayClientError::UnexpectedResponse => write!(f, "received an unexpected response"), + GatewayClientError::NotEnoughBandwidth => { + write!(f, "client does not have enough bandwidth") + } + GatewayClientError::SerializeCredential => { + write!(f, "credential could not be serialized") + } } } } diff --git a/common/coconut-interface/src/lib.rs b/common/coconut-interface/src/lib.rs index 823dfb562d..b068e7525c 100644 --- a/common/coconut-interface/src/lib.rs +++ b/common/coconut-interface/src/lib.rs @@ -45,7 +45,7 @@ impl Credential { .collect() } - pub async fn verify(&self, verification_key: &VerificationKey) -> bool { + pub fn verify(&self, verification_key: &VerificationKey) -> bool { let params = Parameters::new(self.n_params).unwrap(); coconut_rs::verify_credential( ¶ms, diff --git a/gateway/gateway-requests/src/authentication/encrypted_address.rs b/gateway/gateway-requests/src/authentication/encrypted_address.rs index 73ae885dfd..36c5f969ed 100644 --- a/gateway/gateway-requests/src/authentication/encrypted_address.rs +++ b/gateway/gateway-requests/src/authentication/encrypted_address.rs @@ -1,7 +1,7 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::authentication::iv::AuthenticationIV; +use crate::iv::IV; use crate::registration::handshake::shared_key::SharedKeys; use crypto::symmetric::stream_cipher; use nymsphinx::params::GatewayEncryptionAlgorithm; @@ -23,7 +23,7 @@ pub enum EncryptedAddressConversionError { } impl EncryptedAddressBytes { - pub fn new(address: &DestinationAddressBytes, key: &SharedKeys, iv: &AuthenticationIV) -> Self { + pub fn new(address: &DestinationAddressBytes, key: &SharedKeys, iv: &IV) -> Self { let ciphertext = stream_cipher::encrypt::( key.encryption_key(), iv.inner(), @@ -35,12 +35,7 @@ impl EncryptedAddressBytes { EncryptedAddressBytes(enc_address) } - pub fn verify( - &self, - address: &DestinationAddressBytes, - key: &SharedKeys, - iv: &AuthenticationIV, - ) -> bool { + pub fn verify(&self, address: &DestinationAddressBytes, key: &SharedKeys, iv: &IV) -> bool { self == &Self::new(address, key, iv) } diff --git a/gateway/gateway-requests/src/authentication/mod.rs b/gateway/gateway-requests/src/authentication/mod.rs index cf1ba5aab3..3e2a4c4d5a 100644 --- a/gateway/gateway-requests/src/authentication/mod.rs +++ b/gateway/gateway-requests/src/authentication/mod.rs @@ -2,4 +2,3 @@ // SPDX-License-Identifier: Apache-2.0 pub mod encrypted_address; -pub mod iv; diff --git a/gateway/gateway-requests/src/authentication/iv.rs b/gateway/gateway-requests/src/iv.rs similarity index 81% rename from gateway/gateway-requests/src/authentication/iv.rs rename to gateway/gateway-requests/src/iv.rs index a0964c96bf..95fae40095 100644 --- a/gateway/gateway-requests/src/authentication/iv.rs +++ b/gateway/gateway-requests/src/iv.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crypto::generic_array::{typenum::Unsigned, GenericArray}; -use crypto::symmetric::stream_cipher::{random_iv, NewStreamCipher, IV}; +use crypto::symmetric::stream_cipher::{random_iv, NewStreamCipher, IV as CryptoIV}; use nymsphinx::params::GatewayEncryptionAlgorithm; use rand::{CryptoRng, RngCore}; @@ -10,7 +10,7 @@ type NonceSize = ::NonceSize; // I think 'IV' looks better than 'Iv', feel free to change that. #[allow(clippy::upper_case_acronyms)] -pub struct AuthenticationIV(IV); +pub struct IV(CryptoIV); #[derive(Debug)] // I think 'IV' looks better than 'Iv', feel free to change that. @@ -21,9 +21,9 @@ pub enum IVConversionError { StringOfInvalidLengthError, } -impl AuthenticationIV { +impl IV { pub fn new_random(rng: &mut R) -> Self { - AuthenticationIV(random_iv::(rng)) + IV(random_iv::(rng)) } pub fn try_from_bytes(bytes: &[u8]) -> Result { @@ -31,7 +31,7 @@ impl AuthenticationIV { return Err(IVConversionError::BytesOfInvalidLengthError); } - Ok(AuthenticationIV(GenericArray::clone_from_slice(bytes))) + Ok(IV(GenericArray::clone_from_slice(bytes))) } pub fn to_bytes(&self) -> Vec { @@ -42,7 +42,7 @@ impl AuthenticationIV { self.0.as_ref() } - pub fn inner(&self) -> &IV { + pub fn inner(&self) -> &CryptoIV { &self.0 } @@ -56,8 +56,8 @@ impl AuthenticationIV { return Err(IVConversionError::StringOfInvalidLengthError); } - Ok(AuthenticationIV( - GenericArray::from_exact_iter(decoded).expect("Invalid vector length!"), + Ok(IV( + GenericArray::from_exact_iter(decoded).expect("Invalid vector length!") )) } @@ -66,8 +66,8 @@ impl AuthenticationIV { } } -impl From for String { - fn from(iv: AuthenticationIV) -> Self { +impl From for String { + fn from(iv: IV) -> Self { iv.to_base58_string() } } diff --git a/gateway/gateway-requests/src/lib.rs b/gateway/gateway-requests/src/lib.rs index bbfd8abd5f..24018ba219 100644 --- a/gateway/gateway-requests/src/lib.rs +++ b/gateway/gateway-requests/src/lib.rs @@ -1,19 +1,19 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +pub use crypto::generic_array; use crypto::hmac::{hmac::Mac, HmacOutput}; use nymsphinx::params::GatewayIntegrityHmacAlgorithm; +pub use types::*; pub mod authentication; +pub mod iv; pub mod registration; pub mod types; pub const DUMMY_MESSAGE_CONTENT: &[u8] = b"[DUMMY MESSAGE] Wanting something does not give you the right to have it."; -pub use crypto::generic_array; -pub use types::*; - pub type GatewayMac = HmacOutput; // TODO: could using `Mac` trait here for OutputSize backfire? diff --git a/gateway/gateway-requests/src/registration/handshake/client.rs b/gateway/gateway-requests/src/registration/handshake/client.rs index 9848c621c3..784364ebe2 100644 --- a/gateway/gateway-requests/src/registration/handshake/client.rs +++ b/gateway/gateway-requests/src/registration/handshake/client.rs @@ -24,18 +24,11 @@ impl<'a> ClientHandshake<'a> { ws_stream: &'a mut S, identity: &'a crypto::asymmetric::identity::KeyPair, gateway_pubkey: identity::PublicKey, - coconut_credential: coconut_interface::Credential, ) -> Self where S: Stream + Sink + Unpin + Send + 'a, { - let mut state = State::new( - rng, - ws_stream, - identity, - Some(gateway_pubkey), - Some(coconut_credential), - ); + let mut state = State::new(rng, ws_stream, identity, Some(gateway_pubkey)); ClientHandshake { handshake_future: Box::pin(async move { diff --git a/gateway/gateway-requests/src/registration/handshake/error.rs b/gateway/gateway-requests/src/registration/handshake/error.rs index dbb083d0b9..0bf672d2b5 100644 --- a/gateway/gateway-requests/src/registration/handshake/error.rs +++ b/gateway/gateway-requests/src/registration/handshake/error.rs @@ -25,8 +25,6 @@ pub enum HandshakeError { MalformedRequest, #[error("sent request was malformed")] HandshakeFailure, - #[error("could not verify Coconut Credential")] - InvalidCoconutCredential, #[error("could not deserialize from slice: {source}")] DeserializationError { #[from] diff --git a/gateway/gateway-requests/src/registration/handshake/gateway.rs b/gateway/gateway-requests/src/registration/handshake/gateway.rs index bf786f687e..8663f9af5f 100644 --- a/gateway/gateway-requests/src/registration/handshake/gateway.rs +++ b/gateway/gateway-requests/src/registration/handshake/gateway.rs @@ -4,7 +4,6 @@ use crate::registration::handshake::shared_key::SharedKeys; use crate::registration::handshake::state::State; use crate::registration::handshake::{error::HandshakeError, WsItem}; -use coconut_interface::VerificationKey; use crypto::asymmetric::encryption; use futures::future::BoxFuture; use futures::task::{Context, Poll}; @@ -23,12 +22,11 @@ impl<'a> GatewayHandshake<'a> { ws_stream: &'a mut S, identity: &'a crypto::asymmetric::identity::KeyPair, received_init_payload: Vec, - verification_key: &'a VerificationKey, ) -> Self where S: Stream + Sink + Unpin + Send + 'a, { - let mut state = State::new(rng, ws_stream, identity, None, None); + let mut state = State::new(rng, ws_stream, identity, None); GatewayHandshake { handshake_future: Box::pin(async move { // If any step along the way failed (that are non-network related), @@ -50,27 +48,13 @@ impl<'a> GatewayHandshake<'a> { } } - // init: <- pub_key || g^x || credential + // init: <- pub_key || g^x let init_message = check_processing_error( State::::parse_init_message(received_init_payload), &mut state, ) .await?; - let credential = init_message.credential(); - - check_processing_error( - { - if !credential.verify(verification_key).await { - Err(HandshakeError::InvalidCoconutCredential) - } else { - Ok(()) - } - }, - &mut state, - ) - .await?; - let remote_identity = init_message.local_id_pubkey(); let remote_ephemeral_key = init_message.ephemeral_key(); state.update_remote_identity(remote_identity); diff --git a/gateway/gateway-requests/src/registration/handshake/mod.rs b/gateway/gateway-requests/src/registration/handshake/mod.rs index 715b0dedba..0b3b95bf7f 100644 --- a/gateway/gateway-requests/src/registration/handshake/mod.rs +++ b/gateway/gateway-requests/src/registration/handshake/mod.rs @@ -6,8 +6,6 @@ use self::error::HandshakeError; #[cfg(not(target_arch = "wasm32"))] use self::gateway::GatewayHandshake; pub use self::shared_key::{SharedKeySize, SharedKeys}; -#[cfg(not(target_arch = "wasm32"))] -use coconut_interface::VerificationKey; use crypto::asymmetric::identity; use futures::{Sink, Stream}; use rand::{CryptoRng, RngCore}; @@ -32,12 +30,11 @@ pub async fn client_handshake<'a, S>( ws_stream: &'a mut S, identity: &'a identity::KeyPair, gateway_pubkey: identity::PublicKey, - coconut_credential: coconut_interface::Credential, ) -> Result where S: Stream + Sink + Unpin + Send + 'a, { - ClientHandshake::new(rng, ws_stream, identity, gateway_pubkey, coconut_credential).await + ClientHandshake::new(rng, ws_stream, identity, gateway_pubkey).await } #[cfg(not(target_arch = "wasm32"))] @@ -46,19 +43,11 @@ pub async fn gateway_handshake<'a, S>( ws_stream: &'a mut S, identity: &'a identity::KeyPair, received_init_payload: Vec, - verification_key: &VerificationKey, ) -> Result where S: Stream + Sink + Unpin + Send + 'a, { - GatewayHandshake::new( - rng, - ws_stream, - identity, - received_init_payload, - verification_key, - ) - .await + GatewayHandshake::new(rng, ws_stream, identity, received_init_payload).await } /* diff --git a/gateway/gateway-requests/src/registration/handshake/shared_key.rs b/gateway/gateway-requests/src/registration/handshake/shared_key.rs index 7fc655ee19..b07c6827d7 100644 --- a/gateway/gateway-requests/src/registration/handshake/shared_key.rs +++ b/gateway/gateway-requests/src/registration/handshake/shared_key.rs @@ -1,11 +1,12 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::{GatewayMacSize, GatewayRequestsError}; use crypto::generic_array::{ typenum::{Sum, Unsigned, U16}, GenericArray, }; -use crypto::hmac::compute_keyed_hmac; +use crypto::hmac::{compute_keyed_hmac, recompute_keyed_hmac_and_verify_tag}; use crypto::symmetric::stream_cipher::{self, Key, NewStreamCipher, IV}; use nymsphinx::params::{GatewayEncryptionAlgorithm, GatewayIntegrityHmacAlgorithm}; use pemstore::traits::PemStorableKey; @@ -102,6 +103,41 @@ impl SharedKeys { .collect() } + pub fn decrypt_tagged( + &self, + enc_data: &[u8], + iv: Option<&IV>, + ) -> Result, GatewayRequestsError> { + let mac_size = GatewayMacSize::to_usize(); + if enc_data.len() < mac_size { + return Err(GatewayRequestsError::TooShortRequest); + } + + let mac_tag = &enc_data[..mac_size]; + let message_bytes = &enc_data[mac_size..]; + + if !recompute_keyed_hmac_and_verify_tag::( + self.mac_key(), + message_bytes, + mac_tag, + ) { + return Err(GatewayRequestsError::InvalidMac); + } + + // couldn't have made the first borrow mutable as you can't have an immutable borrow + // together with a mutable one + let mut message_bytes_mut = &mut enc_data.to_vec()[mac_size..]; + + let zero_iv = stream_cipher::zero_iv::(); + let iv = iv.unwrap_or(&zero_iv); + stream_cipher::decrypt_in_place::( + self.encryption_key(), + iv, + &mut message_bytes_mut, + ); + Ok(message_bytes_mut.to_vec()) + } + pub fn encryption_key(&self) -> &Key { &self.encryption_key } diff --git a/gateway/gateway-requests/src/registration/handshake/state.rs b/gateway/gateway-requests/src/registration/handshake/state.rs index 97e2ae56e6..9b45b89292 100644 --- a/gateway/gateway-requests/src/registration/handshake/state.rs +++ b/gateway/gateway-requests/src/registration/handshake/state.rs @@ -5,7 +5,6 @@ use crate::registration::handshake::error::HandshakeError; use crate::registration::handshake::shared_key::{SharedKeySize, SharedKeys}; use crate::registration::handshake::WsItem; use crate::types; -use coconut_interface::Credential; use crypto::{ asymmetric::{encryption, identity}, generic_array::typenum::Unsigned, @@ -24,19 +23,13 @@ use tungstenite::Message as WsMessage; pub struct InitMessage { local_id_pubkey: [u8; identity::PUBLIC_KEY_LENGTH], ephemeral_key: [u8; identity::PUBLIC_KEY_LENGTH], - credential: Option, } impl InitMessage { - fn new( - local_id_pubkey: &identity::PublicKey, - ephemeral_key: &encryption::PublicKey, - credential: Credential, - ) -> Self { + fn new(local_id_pubkey: &identity::PublicKey, ephemeral_key: &encryption::PublicKey) -> Self { InitMessage { local_id_pubkey: local_id_pubkey.to_bytes(), ephemeral_key: ephemeral_key.to_bytes(), - credential: Some(credential), } } @@ -50,11 +43,6 @@ impl InitMessage { encryption::PublicKey::from_bytes(&self.ephemeral_key).unwrap() } - #[cfg(not(target_arch = "wasm32"))] - pub fn credential(&self) -> Credential { - self.credential.clone().unwrap() - } - fn to_bytes(&self) -> Vec { bincode::serialize(self).unwrap() } @@ -85,7 +73,6 @@ pub(crate) struct State<'a, S> { /// The known or received public identity key of the remote. /// Ideally it would always be known before the handshake was initiated. remote_pubkey: Option, - coconut_credential: Option, } impl<'a, S> State<'a, S> { @@ -94,7 +81,6 @@ impl<'a, S> State<'a, S> { ws_stream: &'a mut S, identity: &'a identity::KeyPair, remote_pubkey: Option, - credential: Option, ) -> Self { let ephemeral_keypair = encryption::KeyPair::new(rng); State { @@ -103,7 +89,6 @@ impl<'a, S> State<'a, S> { identity, remote_pubkey, derived_shared_keys: None, - coconut_credential: credential, } } @@ -119,7 +104,6 @@ impl<'a, S> State<'a, S> { InitMessage::new( self.identity.public_key(), self.ephemeral_keypair.public_key(), - self.coconut_credential.clone().unwrap(), ) .to_bytes() } diff --git a/gateway/gateway-requests/src/types.rs b/gateway/gateway-requests/src/types.rs index 1c751783e9..3ada132538 100644 --- a/gateway/gateway-requests/src/types.rs +++ b/gateway/gateway-requests/src/types.rs @@ -2,9 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use crate::authentication::encrypted_address::EncryptedAddressBytes; -use crate::authentication::iv::AuthenticationIV; +use crate::iv::IV; use crate::registration::handshake::SharedKeys; use crate::GatewayMacSize; +use coconut_interface::Credential; use crypto::generic_array::typenum::Unsigned; use crypto::hmac::recompute_keyed_hmac_and_verify_tag; use crypto::symmetric::stream_cipher; @@ -112,13 +113,17 @@ pub enum ClientControlRequest { }, #[serde(alias = "handshakePayload")] RegisterHandshakeInitRequest { data: Vec }, + BandwidthCredential { + enc_credential: Vec, + iv: Vec, + }, } impl ClientControlRequest { pub fn new_authenticate( address: DestinationAddressBytes, enc_address: EncryptedAddressBytes, - iv: AuthenticationIV, + iv: IV, ) -> Self { ClientControlRequest::Authenticate { address: address.as_base58_string(), @@ -126,6 +131,34 @@ impl ClientControlRequest { iv: iv.to_base58_string(), } } + + pub fn new_enc_bandwidth_credential( + credential: &Credential, + shared_key: &SharedKeys, + iv: IV, + ) -> Option { + match bincode::serialize(credential) { + Ok(serialized_credential) => { + let enc_credential = + shared_key.encrypt_and_tag(&serialized_credential, Some(iv.inner())); + + Some(ClientControlRequest::BandwidthCredential { + enc_credential, + iv: iv.to_bytes(), + }) + } + _ => None, + } + } + + pub fn try_from_enc_bandwidth_credential( + enc_credential: Vec, + shared_key: &SharedKeys, + iv: IV, + ) -> Result { + let credential = shared_key.decrypt_tagged(&enc_credential, Some(iv.inner()))?; + bincode::deserialize(&credential).map_err(|_| GatewayRequestsError::MalformedEncryption) + } } impl From for Message { @@ -158,6 +191,8 @@ impl TryInto for ClientControlRequest { pub enum ServerResponse { Authenticate { status: bool }, Register { status: bool }, + // Maybe we could return the remaining bandwidth? + Bandwidth { status: bool }, Send { status: bool }, Error { message: String }, } @@ -210,39 +245,14 @@ pub enum BinaryRequest { // would work there. impl BinaryRequest { pub fn try_from_encrypted_tagged_bytes( - mut raw_req: Vec, + raw_req: Vec, shared_keys: &SharedKeys, ) -> Result { - let mac_size = GatewayMacSize::to_usize(); - if raw_req.len() < mac_size { - return Err(GatewayRequestsError::TooShortRequest); - } - - let mac_tag = &raw_req[..mac_size]; - let message_bytes = &raw_req[mac_size..]; - - if !recompute_keyed_hmac_and_verify_tag::( - shared_keys.mac_key(), - message_bytes, - mac_tag, - ) { - return Err(GatewayRequestsError::InvalidMac); - } - - // couldn't have made the first borrow mutable as you can't have an immutable borrow - // together with a mutable one - let mut message_bytes_mut = &mut raw_req[mac_size..]; - - let zero_iv = stream_cipher::zero_iv::(); - stream_cipher::decrypt_in_place::( - shared_keys.encryption_key(), - &zero_iv, - &mut message_bytes_mut, - ); + let message_bytes = &shared_keys.decrypt_tagged(&raw_req, None)?; // right now there's only a single option possible which significantly simplifies the logic // if we decided to allow for more 'binary' messages, the API wouldn't need to change. - let mix_packet = MixPacket::try_from_bytes(message_bytes_mut)?; + let mix_packet = MixPacket::try_from_bytes(message_bytes)?; Ok(BinaryRequest::ForwardSphinx(mix_packet)) } diff --git a/gateway/src/node/client_handling/clients_handler.rs b/gateway/src/node/client_handling/clients_handler.rs index 110f056f30..2051d9b501 100644 --- a/gateway/src/node/client_handling/clients_handler.rs +++ b/gateway/src/node/client_handling/clients_handler.rs @@ -10,7 +10,7 @@ use futures::{ StreamExt, }; use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes; -use gateway_requests::authentication::iv::AuthenticationIV; +use gateway_requests::iv::IV; use gateway_requests::registration::handshake::SharedKeys; use log::*; use nymsphinx::DestinationAddressBytes; @@ -34,7 +34,7 @@ pub(crate) enum ClientsHandlerRequest { Authenticate( DestinationAddressBytes, EncryptedAddressBytes, - AuthenticationIV, + IV, MixMessageSender, ClientsHandlerResponseSender, ), @@ -193,7 +193,7 @@ impl ClientsHandler { &mut self, address: DestinationAddressBytes, encrypted_address: EncryptedAddressBytes, - iv: AuthenticationIV, + iv: IV, comm_channel: MixMessageSender, res_channel: ClientsHandlerResponseSender, ) { diff --git a/gateway/src/node/client_handling/websocket/connection_handler.rs b/gateway/src/node/client_handling/websocket/connection_handler.rs index 82c459fe4a..9cf55b0e31 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler.rs @@ -14,7 +14,7 @@ use futures::{ SinkExt, StreamExt, }; use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes; -use gateway_requests::authentication::iv::AuthenticationIV; +use gateway_requests::iv::IV; use gateway_requests::registration::handshake::error::HandshakeError; use gateway_requests::registration::handshake::{gateway_handshake, SharedKeys}; use gateway_requests::types::{BinaryRequest, ClientControlRequest, ServerResponse}; @@ -53,6 +53,8 @@ pub(crate) struct Handle { rng: R, remote_address: Option, shared_key: Option, + // TODO: This should be replaced by an actual bandwidth value, with 0 meaning no bandwidth + has_bandwidth: bool, clients_handler_sender: ClientsHandlerRequestSender, outbound_mix_sender: MixForwardingSender, socket_connection: SocketStream, @@ -79,6 +81,7 @@ where rng, remote_address: None, shared_key: None, + has_bandwidth: false, clients_handler_sender, outbound_mix_sender, socket_connection: SocketStream::RawTcp(conn), @@ -119,7 +122,6 @@ where ws_stream, self.local_identity.as_ref(), init_msg, - &self.aggregated_verification_key, ) .await } @@ -192,6 +194,9 @@ where } async fn handle_binary(&self, bin_msg: Vec) -> Message { + if !self.has_bandwidth { + return ServerResponse::new_error("Not enough bandwidth").into(); + } trace!("Handling binary message (presumably sphinx packet)"); // this function decrypts the request and checks the MAC @@ -236,7 +241,7 @@ where } }; - let iv = match AuthenticationIV::try_from_base58_string(iv) { + let iv = match IV::try_from_base58_string(iv) { Ok(iv) => iv, Err(e) => { trace!("failed to parse received IV {:?}", e); @@ -342,12 +347,45 @@ where } } - // currently there are no valid control messages you can send after authentication - async fn handle_text(&mut self, _: String) -> Message { - trace!("Handling text message (presumably control message)"); + async fn handle_bandwidth(&mut self, enc_credential: Vec, iv: Vec) -> ServerResponse { + if self.shared_key.is_none() { + return ServerResponse::new_error("No shared key has been exchanged with the gateway"); + } + let iv = match IV::try_from_bytes(&iv) { + Ok(iv) => iv, + Err(e) => { + trace!("failed to parse received IV {:?}", e); + return ServerResponse::new_error("malformed iv"); + } + }; + let credential = match ClientControlRequest::try_from_enc_bandwidth_credential( + enc_credential, + self.shared_key.as_ref().unwrap(), + iv, + ) { + Ok(c) => c, + Err(e) => { + return ServerResponse::new_error(e.to_string()); + } + }; + let status = credential.verify(&self.aggregated_verification_key); + self.has_bandwidth = status; + ServerResponse::Bandwidth { status } + } - error!("Currently there are no text messages besides 'Authenticate' and 'Register' and they were already dealt with!"); - ServerResponse::new_error("invalid request").into() + // currently the bandwidth credential request is the only one we can receive after + // authentication + async fn handle_text(&mut self, raw_request: String) -> Message { + if let Ok(request) = ClientControlRequest::try_from(raw_request) { + match request { + ClientControlRequest::BandwidthCredential { enc_credential, iv } => { + self.handle_bandwidth(enc_credential, iv).await.into() + } + _ => ServerResponse::new_error("invalid request").into(), + } + } else { + ServerResponse::new_error("malformed request").into() + } } async fn handle_request(&mut self, raw_request: Message) -> Option { @@ -384,6 +422,7 @@ where ClientControlRequest::RegisterHandshakeInitRequest { data } => { self.handle_register(data, mix_sender).await } + _ => ServerResponse::new_error("invalid request"), } } else { // TODO: is this a malformed request or rather a network error and diff --git a/gateway/src/node/storage/ledger.rs b/gateway/src/node/storage/ledger.rs index e5d6c5aec2..ba69835886 100644 --- a/gateway/src/node/storage/ledger.rs +++ b/gateway/src/node/storage/ledger.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use gateway_requests::authentication::encrypted_address::EncryptedAddressBytes; -use gateway_requests::authentication::iv::AuthenticationIV; use gateway_requests::generic_array::typenum::Unsigned; +use gateway_requests::iv::IV; use gateway_requests::registration::handshake::{SharedKeySize, SharedKeys}; use log::*; use nymsphinx::{DestinationAddressBytes, DESTINATION_ADDRESS_LENGTH}; @@ -79,7 +79,7 @@ impl ClientLedger { &self, client_address: &DestinationAddressBytes, encrypted_address: &EncryptedAddressBytes, - iv: &AuthenticationIV, + iv: &IV, ) -> Result { match self.db.get(client_address.as_bytes_ref()) { Err(e) => Err(ClientLedgerError::Read(e)), From 92e13a5d005d3093717c77332cb5837cec4cfd34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Tue, 31 Aug 2021 14:51:15 +0300 Subject: [PATCH 10/28] Feature/add blockstamp (#756) * Add RawDelegationData * Fix current tests for the new stored data * Added migration commit. Will be reverted after doing the migration * New tests for block height * Use current blockstamp instead of 24h old one * Put _alot_ of migration stuff in the migrate function scope --- common/mixnet-contract/src/delegation.rs | 17 +- common/mixnet-contract/src/lib.rs | 2 +- contracts/mixnet/src/contract.rs | 145 ++++++++-- contracts/mixnet/src/queries.rs | 49 ++-- contracts/mixnet/src/storage.rs | 59 ++-- contracts/mixnet/src/support/tests.rs | 8 +- contracts/mixnet/src/transactions.rs | 353 ++++++++++++++++++++--- 7 files changed, 514 insertions(+), 119 deletions(-) diff --git a/common/mixnet-contract/src/delegation.rs b/common/mixnet-contract/src/delegation.rs index 12b1ac80d4..5aab311db5 100644 --- a/common/mixnet-contract/src/delegation.rs +++ b/common/mixnet-contract/src/delegation.rs @@ -2,11 +2,26 @@ #![allow(clippy::field_reassign_with_default)] use crate::{Addr, IdentityKey}; -use cosmwasm_std::Coin; +use cosmwasm_std::{Coin, Uint128}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::fmt::Display; +#[derive(Debug, Deserialize, PartialEq, Serialize)] +pub struct RawDelegationData { + pub amount: Uint128, + pub block_height: u64, +} + +impl RawDelegationData { + pub fn new(amount: Uint128, block_height: u64) -> Self { + RawDelegationData { + amount, + block_height, + } + } +} + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] pub struct Delegation { owner: Addr, diff --git a/common/mixnet-contract/src/lib.rs b/common/mixnet-contract/src/lib.rs index e259065e31..73a3769e01 100644 --- a/common/mixnet-contract/src/lib.rs +++ b/common/mixnet-contract/src/lib.rs @@ -10,7 +10,7 @@ mod types; pub use cosmwasm_std::{Addr, Coin}; pub use delegation::{ Delegation, PagedGatewayDelegationsResponse, PagedMixDelegationsResponse, - PagedReverseGatewayDelegationsResponse, PagedReverseMixDelegationsResponse, + PagedReverseGatewayDelegationsResponse, PagedReverseMixDelegationsResponse, RawDelegationData, }; pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayResponse}; pub use mixnode::{Layer, MixNode, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse}; diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 32af49cf66..62cd403093 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -2,18 +2,19 @@ // SPDX-License-Identifier: Apache-2.0 use crate::helpers::calculate_epoch_reward_rate; +use crate::queries::DELEGATION_PAGE_MAX_LIMIT; use crate::state::State; -use crate::storage::{ - config, layer_distribution, reverse_gateway_delegations, reverse_mix_delegations, -}; +use crate::storage::{config, gateway_delegations, layer_distribution, mix_delegations}; use crate::{error::ContractError, queries, transactions}; -use config::defaults::NETWORK_MONITOR_ADDRESS; +use config::defaults::{DENOM, NETWORK_MONITOR_ADDRESS}; use cosmwasm_std::{ - entry_point, to_binary, Addr, Decimal, Deps, DepsMut, Env, MessageInfo, QueryResponse, - Response, Uint128, + coin, entry_point, to_binary, Addr, Decimal, Deps, DepsMut, Env, MessageInfo, Order, + QueryResponse, Response, StdResult, Storage, Uint128, }; +use cosmwasm_storage::ReadonlyBucket; use mixnet_contract::{ - Delegation, ExecuteMsg, IdentityKey, IdentityKeyRef, InstantiateMsg, MigrateMsg, QueryMsg, + Delegation, ExecuteMsg, IdentityKey, IdentityKeyRef, InstantiateMsg, MigrateMsg, + PagedGatewayDelegationsResponse, PagedMixDelegationsResponse, QueryMsg, RawDelegationData, StateParams, }; @@ -94,7 +95,7 @@ pub fn instantiate( #[entry_point] pub fn execute( deps: DepsMut, - _env: Env, + env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { @@ -113,13 +114,13 @@ pub fn execute( transactions::try_reward_gateway(deps, info, identity, uptime) } ExecuteMsg::DelegateToMixnode { mix_identity } => { - transactions::try_delegate_to_mixnode(deps, info, mix_identity) + transactions::try_delegate_to_mixnode(deps, env, info, mix_identity) } ExecuteMsg::UndelegateFromMixnode { mix_identity } => { transactions::try_remove_delegation_from_mixnode(deps, info, mix_identity) } ExecuteMsg::DelegateToGateway { gateway_identity } => { - transactions::try_delegate_to_gateway(deps, info, gateway_identity) + transactions::try_delegate_to_gateway(deps, env, info, gateway_identity) } ExecuteMsg::UndelegateFromGateway { gateway_identity } => { transactions::try_remove_delegation_from_gateway(deps, info, gateway_identity) @@ -206,7 +207,110 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result Result { +pub fn migrate(deps: DepsMut, env: Env, _msg: MigrateMsg) -> Result { + const PREFIX_MIX_DELEGATION: &[u8] = b"md"; + const PREFIX_GATEWAY_DELEGATION: &[u8] = b"gd"; + const DELEGATION_PAGE_DEFAULT_LIMIT: u32 = 500; + + fn calculate_start_value>(start_after: Option) -> Option> { + start_after.as_ref().map(|identity| { + identity + .as_ref() + .as_bytes() + .iter() + .cloned() + .chain(std::iter::once(0)) + .collect() + }) + } + + fn query_mixnode_old_delegations_paged( + deps: Deps, + mix_identity: IdentityKey, + start_after: Option, + limit: Option, + ) -> StdResult { + let limit = limit + .unwrap_or(DELEGATION_PAGE_DEFAULT_LIMIT) + .min(DELEGATION_PAGE_MAX_LIMIT) as usize; + let start = calculate_start_value(start_after); + + let delegations = mix_old_delegations_read(deps.storage, &mix_identity) + .range(start.as_deref(), None, Order::Ascending) + .take(limit) + .map(|res| { + res.map(|entry| { + Delegation::new( + Addr::unchecked(String::from_utf8(entry.0).expect( + "Non-UTF8 address used as key in bucket. The storage is corrupted!", + )), + coin(entry.1.u128(), DENOM), + ) + }) + }) + .collect::>>()?; + + let start_next_after = delegations.last().map(|delegation| delegation.owner()); + + Ok(PagedMixDelegationsResponse::new( + mix_identity, + delegations, + start_next_after, + )) + } + + fn query_gateway_old_delegations_paged( + deps: Deps, + gateway_identity: IdentityKey, + start_after: Option, + limit: Option, + ) -> StdResult { + let limit = limit + .unwrap_or(DELEGATION_PAGE_DEFAULT_LIMIT) + .min(DELEGATION_PAGE_MAX_LIMIT) as usize; + let start = calculate_start_value(start_after); + + let delegations = gateway_old_delegations_read(deps.storage, &gateway_identity) + .range(start.as_deref(), None, Order::Ascending) + .take(limit) + .map(|res| { + res.map(|entry| { + Delegation::new( + Addr::unchecked(String::from_utf8(entry.0).expect( + "Non-UTF8 address used as key in bucket. The storage is corrupted!", + )), + coin(entry.1.u128(), DENOM), + ) + }) + }) + .collect::>>()?; + + let start_next_after = delegations.last().map(|delegation| delegation.owner()); + + Ok(PagedGatewayDelegationsResponse::new( + gateway_identity, + delegations, + start_next_after, + )) + } + + fn mix_old_delegations_read<'a>( + storage: &'a dyn Storage, + mix_identity: IdentityKeyRef, + ) -> ReadonlyBucket<'a, Uint128> { + ReadonlyBucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()]) + } + + fn gateway_old_delegations_read<'a>( + storage: &'a dyn Storage, + gateway_identity: IdentityKeyRef, + ) -> ReadonlyBucket<'a, Uint128> { + ReadonlyBucket::multilevel( + storage, + &[PREFIX_GATEWAY_DELEGATION, gateway_identity.as_bytes()], + ) + } + fn get_all_mixnodes_identities(deps: &DepsMut) -> Result, ContractError> { let mut mixnode_bonds = Vec::new(); let mut start_after = None; @@ -258,7 +362,7 @@ pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result Result Result Result Ok(Delegation::new( address, - coin(delegation_value.u128(), DENOM), + coin(delegation_value.amount.u128(), DENOM), )), None => Err(ContractError::NoMixnodeDelegationFound { identity: mix_identity, @@ -212,7 +212,7 @@ pub(crate) fn query_gateway_delegations_paged( Addr::unchecked(String::from_utf8(entry.0).expect( "Non-UTF8 address used as key in bucket. The storage is corrupted!", )), - coin(entry.1.u128(), DENOM), + coin(entry.1.amount.u128(), DENOM), ) }) }) @@ -267,7 +267,7 @@ pub(crate) fn query_gateway_delegation( match gateway_delegations_read(deps.storage, &gateway_identity).may_load(address.as_bytes())? { Some(delegation_value) => Ok(Delegation::new( address, - coin(delegation_value.u128(), DENOM), + coin(delegation_value.amount.u128(), DENOM), )), None => Err(ContractError::NoGatewayDelegationFound { identity: gateway_identity, @@ -282,10 +282,12 @@ mod tests { use crate::state::State; use crate::storage::{config, gateway_delegations, gateways, mix_delegations, mixnodes}; use crate::support::tests::helpers; - use crate::support::tests::helpers::{good_gateway_bond, good_mixnode_bond}; + use crate::support::tests::helpers::{ + good_gateway_bond, good_mixnode_bond, raw_delegation_fixture, + }; use crate::transactions; use cosmwasm_std::testing::mock_info; - use cosmwasm_std::{Addr, Storage, Uint128}; + use cosmwasm_std::{Addr, Storage}; use mixnet_contract::{Gateway, MixNode}; #[test] @@ -632,14 +634,12 @@ mod tests { #[cfg(test)] mod querying_for_mixnode_delegations_paged { use super::*; - use crate::storage::mix_delegations; - use cosmwasm_std::Uint128; fn store_n_delegations(n: u32, storage: &mut dyn Storage, node_identity: &IdentityKey) { for i in 0..n { let address = format!("address{}", i); mix_delegations(storage, node_identity) - .save(address.as_bytes(), &Uint128(42)) + .save(address.as_bytes(), &raw_delegation_fixture(42)) .unwrap(); } } @@ -711,7 +711,7 @@ mod tests { let node_identity: IdentityKey = "foo".into(); mix_delegations(&mut deps.storage, &node_identity) - .save("1".as_bytes(), &Uint128(42)) + .save("1".as_bytes(), &raw_delegation_fixture(42)) .unwrap(); let per_page = 2; @@ -728,7 +728,7 @@ mod tests { // save another mix_delegations(&mut deps.storage, &node_identity) - .save("2".as_bytes(), &Uint128(42)) + .save("2".as_bytes(), &raw_delegation_fixture(42)) .unwrap(); // page1 should have 2 results on it @@ -742,7 +742,7 @@ mod tests { assert_eq!(2, page1.delegations.len()); mix_delegations(&mut deps.storage, &node_identity) - .save("3".as_bytes(), &Uint128(42)) + .save("3".as_bytes(), &raw_delegation_fixture(42)) .unwrap(); // page1 still has 2 results @@ -769,7 +769,7 @@ mod tests { // save another one mix_delegations(&mut deps.storage, &node_identity) - .save("4".as_bytes(), &Uint128(42)) + .save("4".as_bytes(), &raw_delegation_fixture(42)) .unwrap(); let start_after = Addr::unchecked("2"); @@ -793,7 +793,7 @@ mod tests { let delegation_owner = Addr::unchecked("bar"); mix_delegations(&mut deps.storage, &node_identity) - .save(delegation_owner.as_bytes(), &Uint128(42)) + .save(delegation_owner.as_bytes(), &raw_delegation_fixture(42)) .unwrap(); assert_eq!( @@ -825,7 +825,7 @@ mod tests { // add delegation from a different address mix_delegations(&mut deps.storage, &node_identity1) - .save(delegation_owner2.as_bytes(), &Uint128(42)) + .save(delegation_owner2.as_bytes(), &raw_delegation_fixture(42)) .unwrap(); assert_eq!( @@ -842,7 +842,7 @@ mod tests { // add delegation for a different node mix_delegations(&mut deps.storage, &node_identity2) - .save(delegation_owner1.as_bytes(), &Uint128(42)) + .save(delegation_owner1.as_bytes(), &raw_delegation_fixture(42)) .unwrap(); assert_eq!( @@ -1019,13 +1019,12 @@ mod tests { mod querying_for_gateway_delegations_paged { use super::*; use crate::storage::gateway_delegations; - use cosmwasm_std::Uint128; fn store_n_delegations(n: u32, storage: &mut dyn Storage, node_identity: &IdentityKey) { for i in 0..n { let address = format!("address{}", i); gateway_delegations(storage, node_identity) - .save(address.as_bytes(), &Uint128(42)) + .save(address.as_bytes(), &raw_delegation_fixture(42)) .unwrap(); } } @@ -1097,7 +1096,7 @@ mod tests { let node_identity: IdentityKey = "foo".into(); gateway_delegations(&mut deps.storage, &node_identity) - .save("1".as_bytes(), &Uint128(42)) + .save("1".as_bytes(), &raw_delegation_fixture(42)) .unwrap(); let per_page = 2; @@ -1114,7 +1113,7 @@ mod tests { // save another gateway_delegations(&mut deps.storage, &node_identity) - .save("2".as_bytes(), &Uint128(42)) + .save("2".as_bytes(), &raw_delegation_fixture(42)) .unwrap(); // page1 should have 2 results on it @@ -1128,7 +1127,7 @@ mod tests { assert_eq!(2, page1.delegations.len()); gateway_delegations(&mut deps.storage, &node_identity) - .save("3".as_bytes(), &Uint128(42)) + .save("3".as_bytes(), &raw_delegation_fixture(42)) .unwrap(); // page1 still has 2 results @@ -1155,7 +1154,7 @@ mod tests { // save another one gateway_delegations(&mut deps.storage, &node_identity) - .save("4".as_bytes(), &Uint128(42)) + .save("4".as_bytes(), &raw_delegation_fixture(42)) .unwrap(); let start_after = Addr::unchecked("2"); @@ -1179,7 +1178,7 @@ mod tests { let delegation_owner = Addr::unchecked("bar"); gateway_delegations(&mut deps.storage, &node_identity) - .save(delegation_owner.as_bytes(), &Uint128(42)) + .save(delegation_owner.as_bytes(), &raw_delegation_fixture(42)) .unwrap(); assert_eq!( @@ -1211,7 +1210,7 @@ mod tests { // add delegation from a different address gateway_delegations(&mut deps.storage, &node_identity1) - .save(delegation_owner2.as_bytes(), &Uint128(42)) + .save(delegation_owner2.as_bytes(), &raw_delegation_fixture(42)) .unwrap(); assert_eq!( @@ -1228,7 +1227,7 @@ mod tests { // add delegation for a different node gateway_delegations(&mut deps.storage, &node_identity2) - .save(delegation_owner1.as_bytes(), &Uint128(42)) + .save(delegation_owner1.as_bytes(), &raw_delegation_fixture(42)) .unwrap(); assert_eq!( diff --git a/contracts/mixnet/src/storage.rs b/contracts/mixnet/src/storage.rs index e778cfc250..3de3b08c9b 100644 --- a/contracts/mixnet/src/storage.rs +++ b/contracts/mixnet/src/storage.rs @@ -10,7 +10,7 @@ use cosmwasm_storage::{ }; use mixnet_contract::{ Addr, GatewayBond, IdentityKey, IdentityKeyRef, Layer, LayerDistribution, MixNodeBond, - StateParams, + RawDelegationData, StateParams, }; // storage prefixes @@ -193,11 +193,11 @@ pub(crate) fn increase_mix_delegated_stakes( ); // and for each of them increase the stake proportionally to the reward - for (delegator_address, amount) in delegations_chunk.into_iter() { - let reward = amount * scaled_reward_rate; - let new_amount = amount + reward; + for (delegator_address, mut delegation) in delegations_chunk.into_iter() { + let reward = delegation.amount * scaled_reward_rate; + delegation.amount += reward; total_rewarded += reward; - mix_delegations(storage, mix_identity).save(&delegator_address, &new_amount)?; + mix_delegations(storage, mix_identity).save(&delegator_address, &delegation)?; } } @@ -237,11 +237,11 @@ pub(crate) fn increase_gateway_delegated_stakes( ); // and for each of them increase the stake proportionally to the reward - for (delegator_address, amount) in delegations_chunk.into_iter() { - let reward = amount * scaled_reward_rate; - let new_amount = amount + reward; + for (delegator_address, mut delegation) in delegations_chunk.into_iter() { + let reward = delegation.amount * scaled_reward_rate; + delegation.amount += reward; total_rewarded += reward; - gateway_delegations(storage, gateway_identity).save(&delegator_address, &new_amount)?; + gateway_delegations(storage, gateway_identity).save(&delegator_address, &delegation)?; } } @@ -293,14 +293,14 @@ pub fn gateways_owners_read(storage: &dyn Storage) -> ReadonlyBucket( storage: &'a mut dyn Storage, mix_identity: IdentityKeyRef, -) -> Bucket<'a, Uint128> { +) -> Bucket<'a, RawDelegationData> { Bucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()]) } pub fn mix_delegations_read<'a>( storage: &'a dyn Storage, mix_identity: IdentityKeyRef, -) -> ReadonlyBucket<'a, Uint128> { +) -> ReadonlyBucket<'a, RawDelegationData> { ReadonlyBucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()]) } @@ -318,7 +318,7 @@ pub fn reverse_mix_delegations_read<'a>( pub fn gateway_delegations<'a>( storage: &'a mut dyn Storage, gateway_identity: IdentityKeyRef, -) -> Bucket<'a, Uint128> { +) -> Bucket<'a, RawDelegationData> { Bucket::multilevel( storage, &[PREFIX_GATEWAY_DELEGATION, gateway_identity.as_bytes()], @@ -328,7 +328,7 @@ pub fn gateway_delegations<'a>( pub fn gateway_delegations_read<'a>( storage: &'a dyn Storage, gateway_identity: IdentityKeyRef, -) -> ReadonlyBucket<'a, Uint128> { +) -> ReadonlyBucket<'a, RawDelegationData> { ReadonlyBucket::multilevel( storage, &[PREFIX_GATEWAY_DELEGATION, gateway_identity.as_bytes()], @@ -382,6 +382,7 @@ mod tests { use super::*; use crate::support::tests::helpers::{ gateway_bond_fixture, gateway_fixture, mix_node_fixture, mixnode_bond_fixture, + raw_delegation_fixture, }; use config::defaults::DENOM; use cosmwasm_std::testing::MockStorage; @@ -523,7 +524,10 @@ mod tests { let delegator_address = Addr::unchecked("bob"); mix_delegations(&mut deps.storage, &node_identity) - .save(delegator_address.as_bytes(), &Uint128(1000)) + .save( + delegator_address.as_bytes(), + &RawDelegationData::new(1000u128.into(), 42), + ) .unwrap(); let total_increase = @@ -532,8 +536,9 @@ mod tests { assert_eq!(Uint128(1), total_increase); + // amount is incremented, block height remains the same assert_eq!( - Uint128(1001), + RawDelegationData::new(1001u128.into(), 42), mix_delegations_read(&mut deps.storage, &node_identity) .load(delegator_address.as_bytes()) .unwrap() @@ -551,7 +556,7 @@ mod tests { for i in 0..100 { let delegator_address = Addr::unchecked(format!("address{}", i)); mix_delegations(&mut deps.storage, &node_identity) - .save(delegator_address.as_bytes(), &Uint128(1000)) + .save(delegator_address.as_bytes(), &raw_delegation_fixture(1000)) .unwrap(); } @@ -564,7 +569,7 @@ mod tests { for i in 0..100 { let delegator_address = Addr::unchecked(format!("address{}", i)); assert_eq!( - Uint128(1001), + raw_delegation_fixture(1001), mix_delegations_read(&mut deps.storage, &node_identity) .load(delegator_address.as_bytes()) .unwrap() @@ -583,7 +588,7 @@ mod tests { for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 { let delegator_address = Addr::unchecked(format!("address{}", i)); mix_delegations(&mut deps.storage, &node_identity) - .save(delegator_address.as_bytes(), &Uint128(1000)) + .save(delegator_address.as_bytes(), &raw_delegation_fixture(1000)) .unwrap(); } @@ -599,7 +604,7 @@ mod tests { for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 { let delegator_address = Addr::unchecked(format!("address{}", i)); assert_eq!( - Uint128(1001), + raw_delegation_fixture(1001), mix_delegations_read(&mut deps.storage, &node_identity) .load(delegator_address.as_bytes()) .unwrap() @@ -716,7 +721,10 @@ mod tests { let delegator_address = Addr::unchecked("bob"); gateway_delegations(&mut deps.storage, &node_identity) - .save(delegator_address.as_bytes(), &Uint128(1000)) + .save( + delegator_address.as_bytes(), + &RawDelegationData::new(1000u128.into(), 42), + ) .unwrap(); let total_increase = increase_gateway_delegated_stakes( @@ -728,8 +736,9 @@ mod tests { assert_eq!(Uint128(1), total_increase); + // amount is incremented, block height remains the same assert_eq!( - Uint128(1001), + RawDelegationData::new(1001u128.into(), 42), gateway_delegations_read(&mut deps.storage, &node_identity) .load(delegator_address.as_bytes()) .unwrap() @@ -747,7 +756,7 @@ mod tests { for i in 0..100 { let delegator_address = Addr::unchecked(format!("address{}", i)); gateway_delegations(&mut deps.storage, &node_identity) - .save(delegator_address.as_bytes(), &Uint128(1000)) + .save(delegator_address.as_bytes(), &raw_delegation_fixture(1000)) .unwrap(); } @@ -763,7 +772,7 @@ mod tests { for i in 0..100 { let delegator_address = Addr::unchecked(format!("address{}", i)); assert_eq!( - Uint128(1001), + raw_delegation_fixture(1001), gateway_delegations_read(&mut deps.storage, &node_identity) .load(delegator_address.as_bytes()) .unwrap() @@ -782,7 +791,7 @@ mod tests { for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 { let delegator_address = Addr::unchecked(format!("address{}", i)); gateway_delegations(&mut deps.storage, &node_identity) - .save(delegator_address.as_bytes(), &Uint128(1000)) + .save(delegator_address.as_bytes(), &raw_delegation_fixture(1000)) .unwrap(); } @@ -801,7 +810,7 @@ mod tests { for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 { let delegator_address = Addr::unchecked(format!("address{}", i)); assert_eq!( - Uint128(1001), + raw_delegation_fixture(1001), gateway_delegations_read(&mut deps.storage, &node_identity) .load(delegator_address.as_bytes()) .unwrap() diff --git a/contracts/mixnet/src/support/tests.rs b/contracts/mixnet/src/support/tests.rs index 72993e6721..fd1ac7fade 100644 --- a/contracts/mixnet/src/support/tests.rs +++ b/contracts/mixnet/src/support/tests.rs @@ -5,7 +5,6 @@ pub mod helpers { use crate::contract::{instantiate, INITIAL_MIXNODE_BOND}; use crate::transactions::{try_add_gateway, try_add_mixnode}; use config::defaults::DENOM; - use cosmwasm_std::coin; use cosmwasm_std::from_binary; use cosmwasm_std::testing::mock_dependencies; use cosmwasm_std::testing::mock_env; @@ -16,10 +15,11 @@ pub mod helpers { use cosmwasm_std::Addr; use cosmwasm_std::Coin; use cosmwasm_std::OwnedDeps; + use cosmwasm_std::{coin, Uint128}; use cosmwasm_std::{Empty, MemoryStorage}; use mixnet_contract::{ Gateway, GatewayBond, InstantiateMsg, Layer, MixNode, MixNodeBond, PagedGatewayResponse, - PagedMixnodeResponse, QueryMsg, + PagedMixnodeResponse, QueryMsg, RawDelegationData, }; pub fn add_mixnode( @@ -159,6 +159,10 @@ pub mod helpers { GatewayBond::new(coin(50, DENOM), Addr::unchecked("foo"), gateway) } + pub fn raw_delegation_fixture(amount: u128) -> RawDelegationData { + RawDelegationData::new(Uint128(amount), 42) + } + pub fn query_contract_balance( address: Addr, deps: OwnedDeps, diff --git a/contracts/mixnet/src/transactions.rs b/contracts/mixnet/src/transactions.rs index 5f1f41b3c3..7260f751aa 100644 --- a/contracts/mixnet/src/transactions.rs +++ b/contracts/mixnet/src/transactions.rs @@ -7,11 +7,12 @@ use crate::queries; use crate::storage::*; use config::defaults::DENOM; use cosmwasm_std::{ - attr, coins, BankMsg, Coin, Decimal, DepsMut, MessageInfo, Order, Response, StdResult, Uint128, + attr, coins, BankMsg, Coin, Decimal, DepsMut, Env, MessageInfo, Order, Response, StdResult, + Uint128, }; use cosmwasm_storage::ReadonlyBucket; use mixnet_contract::{ - Gateway, GatewayBond, IdentityKey, Layer, MixNode, MixNodeBond, StateParams, + Gateway, GatewayBond, IdentityKey, Layer, MixNode, MixNodeBond, RawDelegationData, StateParams, }; const OLD_DELEGATIONS_CHUNK_SIZE: usize = 500; @@ -23,7 +24,7 @@ const OLD_DELEGATIONS_CHUNK_SIZE: usize = 500; // 3. The node unbonds // 4. Some of the addresses that delegated in the past have not removed the delegation yet // 5. The node rebonds with the same identity -fn find_old_delegations(delegations_bucket: ReadonlyBucket) -> StdResult { +fn find_old_delegations(delegations_bucket: ReadonlyBucket) -> StdResult { // I think it's incredibly unlikely to ever read more than that // but in case we do, we should guard ourselves against possible // out of memory errors (wasm contracts can only allocate at most 2MB @@ -45,7 +46,7 @@ fn find_old_delegations(delegations_bucket: ReadonlyBucket) -> StdResul continue; } - let value = delegation?.1; + let value = delegation?.1.amount; total_delegation.amount += value; } @@ -540,6 +541,7 @@ fn validate_delegation_stake(delegation: &[Coin]) -> Result<(), ContractError> { pub(crate) fn try_delegate_to_mixnode( deps: DepsMut, + env: Env, info: MessageInfo, mix_identity: IdentityKey, ) -> Result { @@ -565,12 +567,13 @@ pub(crate) fn try_delegate_to_mixnode( let sender_bytes = info.sender.as_bytes(); // write the delegation - match delegation_bucket.may_load(sender_bytes)? { - Some(existing_delegation) => { - delegation_bucket.save(sender_bytes, &(existing_delegation + info.funds[0].amount))? - } - None => delegation_bucket.save(sender_bytes, &info.funds[0].amount)?, - } + let new_amount = match delegation_bucket.may_load(sender_bytes)? { + Some(existing_delegation) => existing_delegation.amount + info.funds[0].amount, + None => info.funds[0].amount, + }; + // the block height is reset, if it existed + let new_delegation = RawDelegationData::new(new_amount, env.block.height); + delegation_bucket.save(sender_bytes, &new_delegation)?; reverse_mix_delegations(deps.storage, &info.sender).save(mix_identity.as_bytes(), &())?; @@ -593,7 +596,7 @@ pub(crate) fn try_remove_delegation_from_mixnode( // send delegated funds back to the delegation owner let messages = vec![BankMsg::Send { to_address: info.sender.to_string(), - amount: coins(delegation.u128(), DENOM), + amount: coins(delegation.amount.u128(), DENOM), } .into()]; @@ -606,7 +609,7 @@ pub(crate) fn try_remove_delegation_from_mixnode( existing_bond.total_delegation.amount = existing_bond .total_delegation .amount - .checked_sub(delegation) + .checked_sub(delegation.amount) .unwrap(); mixnodes_bucket.save(mix_identity.as_bytes(), &existing_bond)?; } @@ -627,6 +630,7 @@ pub(crate) fn try_remove_delegation_from_mixnode( pub(crate) fn try_delegate_to_gateway( deps: DepsMut, + env: Env, info: MessageInfo, gateway_identity: IdentityKey, ) -> Result { @@ -652,12 +656,13 @@ pub(crate) fn try_delegate_to_gateway( let sender_bytes = info.sender.as_bytes(); // write the delegation - match delegation_bucket.may_load(sender_bytes)? { - Some(existing_delegation) => { - delegation_bucket.save(sender_bytes, &(existing_delegation + info.funds[0].amount))? - } - None => delegation_bucket.save(sender_bytes, &info.funds[0].amount)?, - } + let new_amount = match delegation_bucket.may_load(sender_bytes)? { + Some(existing_delegation) => existing_delegation.amount + info.funds[0].amount, + None => info.funds[0].amount, + }; + // the block height is reset, if it existed + let new_delegation = RawDelegationData::new(new_amount, env.block.height); + delegation_bucket.save(sender_bytes, &new_delegation)?; reverse_gateway_delegations(deps.storage, &info.sender) .save(gateway_identity.as_bytes(), &())?; @@ -682,7 +687,7 @@ pub(crate) fn try_remove_delegation_from_gateway( // send delegated funds back to the delegation owner let messages = vec![BankMsg::Send { to_address: info.sender.to_string(), - amount: coins(delegation.u128(), DENOM), + amount: coins(delegation.amount.u128(), DENOM), } .into()]; @@ -697,7 +702,7 @@ pub(crate) fn try_remove_delegation_from_gateway( existing_bond.total_delegation.amount = existing_bond .total_delegation .amount - .checked_sub(delegation) + .checked_sub(delegation.amount) .unwrap(); gateways_bucket.save(gateway_identity.as_bytes(), &existing_bond)?; } @@ -734,7 +739,7 @@ pub mod tests { use crate::support::tests::helpers; use crate::support::tests::helpers::{ add_gateway, add_mixnode, gateway_fixture, good_gateway_bond, good_mixnode_bond, - mix_node_fixture, + mix_node_fixture, raw_delegation_fixture, }; use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::{coin, coins, from_binary, Addr, Uint128}; @@ -1802,7 +1807,7 @@ pub mod tests { .unwrap(); mix_delegations(&mut deps.storage, &node_identity) - .save(b"delegator", &Uint128(initial_delegation)) + .save(b"delegator", &raw_delegation_fixture(initial_delegation)) .unwrap(); let bond_reward_rate = read_mixnode_epoch_bond_reward_rate(deps.as_ref().storage); @@ -1901,7 +1906,7 @@ pub mod tests { .unwrap(); gateway_delegations(&mut deps.storage, &node_identity) - .save(b"delegator", &Uint128(initial_delegation)) + .save(b"delegator", &raw_delegation_fixture(initial_delegation)) .unwrap(); let bond_reward_rate = read_gateway_epoch_bond_reward_rate(deps.as_ref().storage); @@ -2027,6 +2032,7 @@ pub mod tests { }), try_delegate_to_mixnode( deps.as_mut(), + mock_env(), mock_info("sender", &coins(123, DENOM)), "non-existent-mix-identity".into() ) @@ -2043,13 +2049,14 @@ pub mod tests { let delegation = coin(123, DENOM); assert!(try_delegate_to_mixnode( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &vec![delegation.clone()]), identity.clone() ) .is_ok()); assert_eq!( - delegation.amount, + RawDelegationData::new(delegation.amount, mock_env().block.height), mix_delegations_read(&deps.storage, &identity) .load(delegation_owner.as_bytes()) .unwrap() @@ -2086,6 +2093,7 @@ pub mod tests { }), try_delegate_to_mixnode( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &coins(123, DENOM)), identity ) @@ -2105,13 +2113,14 @@ pub mod tests { assert!(try_delegate_to_mixnode( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &vec![delegation.clone()]), identity.clone() ) .is_ok()); assert_eq!( - delegation.amount, + RawDelegationData::new(delegation.amount, mock_env().block.height), mix_delegations_read(&deps.storage, &identity) .load(delegation_owner.as_bytes()) .unwrap() @@ -2144,6 +2153,7 @@ pub mod tests { try_delegate_to_mixnode( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &vec![delegation1.clone()]), identity.clone(), ) @@ -2151,13 +2161,17 @@ pub mod tests { try_delegate_to_mixnode( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &vec![delegation2.clone()]), identity.clone(), ) .unwrap(); assert_eq!( - delegation1.amount + delegation2.amount, + RawDelegationData::new( + delegation1.amount + delegation2.amount, + mock_env().block.height + ), mix_delegations_read(&deps.storage, &identity) .load(delegation_owner.as_bytes()) .unwrap() @@ -2179,6 +2193,106 @@ pub mod tests { ) } + #[test] + fn block_height_is_updated_on_new_delegation() { + let mut deps = helpers::init_contract(); + let mixnode_owner = "bob"; + let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); + let delegation = coin(100, DENOM); + + let env1 = mock_env(); + let mut env2 = mock_env(); + let initial_height = env1.block.height; + let updated_height = initial_height + 42; + // second env has grown in block height + env2.block.height = updated_height; + + try_delegate_to_mixnode( + deps.as_mut(), + env1, + mock_info(delegation_owner.as_str(), &vec![delegation.clone()]), + identity.clone(), + ) + .unwrap(); + + assert_eq!( + RawDelegationData::new(delegation.amount, initial_height), + mix_delegations_read(&deps.storage, &identity) + .load(delegation_owner.as_bytes()) + .unwrap() + ); + + try_delegate_to_mixnode( + deps.as_mut(), + env2, + mock_info(delegation_owner.as_str(), &vec![delegation.clone()]), + identity.clone(), + ) + .unwrap(); + + assert_eq!( + RawDelegationData::new(delegation.amount + delegation.amount, updated_height), + mix_delegations_read(&deps.storage, &identity) + .load(delegation_owner.as_bytes()) + .unwrap() + ); + } + + #[test] + fn block_height_is_not_updated_on_different_delegator() { + let mut deps = helpers::init_contract(); + let mixnode_owner = "bob"; + let identity = add_mixnode(mixnode_owner, good_mixnode_bond(), &mut deps); + let delegation_owner1 = Addr::unchecked("sender1"); + let delegation_owner2 = Addr::unchecked("sender2"); + let delegation1 = coin(100, DENOM); + let delegation2 = coin(120, DENOM); + + let env1 = mock_env(); + let mut env2 = mock_env(); + let initial_height = env1.block.height; + let second_height = initial_height + 42; + // second env has grown in block height + env2.block.height = second_height; + + try_delegate_to_mixnode( + deps.as_mut(), + env1, + mock_info(delegation_owner1.as_str(), &vec![delegation1.clone()]), + identity.clone(), + ) + .unwrap(); + + assert_eq!( + RawDelegationData::new(delegation1.amount, initial_height), + mix_delegations_read(&deps.storage, &identity) + .load(delegation_owner1.as_bytes()) + .unwrap() + ); + + try_delegate_to_mixnode( + deps.as_mut(), + env2, + mock_info(delegation_owner2.as_str(), &vec![delegation2.clone()]), + identity.clone(), + ) + .unwrap(); + + assert_eq!( + RawDelegationData::new(delegation1.amount, initial_height), + mix_delegations_read(&deps.storage, &identity) + .load(delegation_owner1.as_bytes()) + .unwrap() + ); + assert_eq!( + RawDelegationData::new(delegation2.amount, second_height), + mix_delegations_read(&deps.storage, &identity) + .load(delegation_owner2.as_bytes()) + .unwrap() + ); + } + #[test] fn is_disallowed_for_already_delegated_node_if_it_unbonded() { let mut deps = helpers::init_contract(); @@ -2189,6 +2303,7 @@ pub mod tests { try_delegate_to_mixnode( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) @@ -2202,6 +2317,7 @@ pub mod tests { }), try_delegate_to_mixnode( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &coins(50, DENOM)), identity ) @@ -2219,6 +2335,7 @@ pub mod tests { assert!(try_delegate_to_mixnode( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &coins(123, DENOM)), identity1.clone() ) @@ -2226,17 +2343,17 @@ pub mod tests { assert!(try_delegate_to_mixnode( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &coins(42, DENOM)), identity2.clone() ) .is_ok()); assert_eq!( - 123, + RawDelegationData::new(123u128.into(), mock_env().block.height), mix_delegations_read(&deps.storage, &identity1) .load(delegation_owner.as_bytes()) .unwrap() - .u128() ); assert!( reverse_mix_delegations_read(&deps.storage, &delegation_owner) @@ -2245,11 +2362,10 @@ pub mod tests { ); assert_eq!( - 42, + RawDelegationData::new(42u128.into(), mock_env().block.height), mix_delegations_read(&deps.storage, &identity2) .load(delegation_owner.as_bytes()) .unwrap() - .u128() ); assert!( reverse_mix_delegations_read(&deps.storage, &delegation_owner) @@ -2269,6 +2385,7 @@ pub mod tests { assert!(try_delegate_to_mixnode( deps.as_mut(), + mock_env(), mock_info("sender1", &vec![delegation1.clone()]), identity.clone() ) @@ -2276,6 +2393,7 @@ pub mod tests { assert!(try_delegate_to_mixnode( deps.as_mut(), + mock_env(), mock_info("sender2", &vec![delegation2.clone()]), identity.clone() ) @@ -2302,6 +2420,7 @@ pub mod tests { try_delegate_to_mixnode( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) @@ -2310,11 +2429,10 @@ pub mod tests { try_remove_mixnode(deps.as_mut(), mock_info(mixnode_owner, &[])).unwrap(); assert_eq!( - 100, + RawDelegationData::new(100u128.into(), mock_env().block.height), mix_delegations_read(&deps.storage, &identity) .load(delegation_owner.as_bytes()) .unwrap() - .u128() ); assert!( reverse_mix_delegations_read(&deps.storage, &delegation_owner) @@ -2361,6 +2479,7 @@ pub mod tests { try_delegate_to_mixnode( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) @@ -2416,6 +2535,7 @@ pub mod tests { try_delegate_to_mixnode( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) @@ -2466,6 +2586,7 @@ pub mod tests { assert!(try_delegate_to_mixnode( deps.as_mut(), + mock_env(), mock_info(delegation_owner1.as_str(), &vec![delegation1.clone()]), identity.clone() ) @@ -2473,6 +2594,7 @@ pub mod tests { assert!(try_delegate_to_mixnode( deps.as_mut(), + mock_env(), mock_info(delegation_owner2.as_str(), &vec![delegation2.clone()]), identity.clone() ) @@ -2513,13 +2635,13 @@ pub mod tests { let identity = add_mixnode(node_owner, good_mixnode_bond(), &mut deps); mix_delegations(&mut deps.storage, &identity) - .save(b"delegator1", &Uint128(initial_delegation1)) + .save(b"delegator1", &raw_delegation_fixture(initial_delegation1)) .unwrap(); mix_delegations(&mut deps.storage, &identity) - .save(b"delegator2", &Uint128(initial_delegation2)) + .save(b"delegator2", &raw_delegation_fixture(initial_delegation2)) .unwrap(); mix_delegations(&mut deps.storage, &identity) - .save(b"delegator3", &Uint128(initial_delegation3)) + .save(b"delegator3", &raw_delegation_fixture(initial_delegation3)) .unwrap(); let bond_reward = read_mixnode_epoch_bond_reward_rate(deps.as_ref().storage); @@ -2550,6 +2672,7 @@ pub mod tests { mix_delegations_read(deps.as_ref().storage, &identity) .load("delegator1".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -2557,6 +2680,7 @@ pub mod tests { mix_delegations_read(deps.as_ref().storage, &identity) .load("delegator2".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -2564,6 +2688,7 @@ pub mod tests { mix_delegations_read(deps.as_ref().storage, &identity) .load("delegator3".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -2606,6 +2731,7 @@ pub mod tests { mix_delegations_read(deps.as_ref().storage, &identity) .load("delegator1".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -2613,6 +2739,7 @@ pub mod tests { mix_delegations_read(deps.as_ref().storage, &identity) .load("delegator2".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -2620,6 +2747,7 @@ pub mod tests { mix_delegations_read(deps.as_ref().storage, &identity) .load("delegator3".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -2649,6 +2777,7 @@ pub mod tests { mix_delegations_read(deps.as_ref().storage, &identity) .load("delegator1".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -2656,6 +2785,7 @@ pub mod tests { mix_delegations_read(deps.as_ref().storage, &identity) .load("delegator2".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -2663,6 +2793,7 @@ pub mod tests { mix_delegations_read(deps.as_ref().storage, &identity) .load("delegator3".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -2689,6 +2820,7 @@ pub mod tests { }), try_delegate_to_gateway( deps.as_mut(), + mock_env(), mock_info("sender", &coins(123, DENOM)), "non-existent-gateway-identity".into() ) @@ -2705,13 +2837,14 @@ pub mod tests { let delegation = coin(123, DENOM); assert!(try_delegate_to_gateway( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &vec![delegation.clone()]), identity.clone() ) .is_ok()); assert_eq!( - delegation.amount, + RawDelegationData::new(delegation.amount, mock_env().block.height), gateway_delegations_read(&deps.storage, &identity) .load(delegation_owner.as_bytes()) .unwrap() @@ -2748,6 +2881,7 @@ pub mod tests { }), try_delegate_to_gateway( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &coins(123, DENOM)), identity ) @@ -2767,13 +2901,14 @@ pub mod tests { assert!(try_delegate_to_gateway( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &vec![delegation.clone()]), identity.clone() ) .is_ok()); assert_eq!( - delegation.amount, + RawDelegationData::new(delegation.amount, mock_env().block.height), gateway_delegations_read(&deps.storage, &identity) .load(delegation_owner.as_bytes()) .unwrap() @@ -2806,6 +2941,7 @@ pub mod tests { try_delegate_to_gateway( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &vec![delegation1.clone()]), identity.clone(), ) @@ -2813,13 +2949,17 @@ pub mod tests { try_delegate_to_gateway( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &vec![delegation2.clone()]), identity.clone(), ) .unwrap(); assert_eq!( - delegation1.amount + delegation2.amount, + RawDelegationData::new( + delegation1.amount + delegation2.amount, + mock_env().block.height + ), gateway_delegations_read(&deps.storage, &identity) .load(delegation_owner.as_bytes()) .unwrap() @@ -2841,6 +2981,106 @@ pub mod tests { ) } + #[test] + fn block_height_is_updated_on_new_delegation() { + let mut deps = helpers::init_contract(); + let gateway_owner = "bob"; + let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner = Addr::unchecked("sender"); + let delegation = coin(100, DENOM); + + let env1 = mock_env(); + let mut env2 = mock_env(); + let initial_height = env1.block.height; + let updated_height = initial_height + 42; + // second env has grown in block height + env2.block.height = updated_height; + + try_delegate_to_gateway( + deps.as_mut(), + env1, + mock_info(delegation_owner.as_str(), &vec![delegation.clone()]), + identity.clone(), + ) + .unwrap(); + + assert_eq!( + RawDelegationData::new(delegation.amount, initial_height), + gateway_delegations_read(&deps.storage, &identity) + .load(delegation_owner.as_bytes()) + .unwrap() + ); + + try_delegate_to_gateway( + deps.as_mut(), + env2, + mock_info(delegation_owner.as_str(), &vec![delegation.clone()]), + identity.clone(), + ) + .unwrap(); + + assert_eq!( + RawDelegationData::new(delegation.amount + delegation.amount, updated_height), + gateway_delegations_read(&deps.storage, &identity) + .load(delegation_owner.as_bytes()) + .unwrap() + ); + } + + #[test] + fn block_height_is_not_updated_on_different_delegator() { + let mut deps = helpers::init_contract(); + let gateway_owner = "bob"; + let identity = add_gateway(gateway_owner, good_gateway_bond(), &mut deps); + let delegation_owner1 = Addr::unchecked("sender1"); + let delegation_owner2 = Addr::unchecked("sender2"); + let delegation1 = coin(100, DENOM); + let delegation2 = coin(120, DENOM); + + let env1 = mock_env(); + let mut env2 = mock_env(); + let initial_height = env1.block.height; + let second_height = initial_height + 42; + // second env has grown in block height + env2.block.height = second_height; + + try_delegate_to_gateway( + deps.as_mut(), + env1, + mock_info(delegation_owner1.as_str(), &vec![delegation1.clone()]), + identity.clone(), + ) + .unwrap(); + + assert_eq!( + RawDelegationData::new(delegation1.amount, initial_height), + gateway_delegations_read(&deps.storage, &identity) + .load(delegation_owner1.as_bytes()) + .unwrap() + ); + + try_delegate_to_gateway( + deps.as_mut(), + env2, + mock_info(delegation_owner2.as_str(), &vec![delegation2.clone()]), + identity.clone(), + ) + .unwrap(); + + assert_eq!( + RawDelegationData::new(delegation1.amount, initial_height), + gateway_delegations_read(&deps.storage, &identity) + .load(delegation_owner1.as_bytes()) + .unwrap() + ); + assert_eq!( + RawDelegationData::new(delegation2.amount, second_height), + gateway_delegations_read(&deps.storage, &identity) + .load(delegation_owner2.as_bytes()) + .unwrap() + ); + } + #[test] fn is_disallowed_for_already_delegated_node_if_it_unbonded() { let mut deps = helpers::init_contract(); @@ -2851,6 +3091,7 @@ pub mod tests { try_delegate_to_gateway( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) @@ -2864,6 +3105,7 @@ pub mod tests { }), try_delegate_to_gateway( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &coins(50, DENOM)), identity.clone() ) @@ -2881,6 +3123,7 @@ pub mod tests { assert!(try_delegate_to_gateway( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &coins(123, DENOM)), identity1.clone() ) @@ -2888,17 +3131,17 @@ pub mod tests { assert!(try_delegate_to_gateway( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &coins(42, DENOM)), identity2.clone() ) .is_ok()); assert_eq!( - 123, + RawDelegationData::new(123u128.into(), mock_env().block.height), gateway_delegations_read(&deps.storage, &identity1) .load(delegation_owner.as_bytes()) .unwrap() - .u128() ); assert!( reverse_gateway_delegations_read(&deps.storage, &delegation_owner) @@ -2907,11 +3150,10 @@ pub mod tests { ); assert_eq!( - 42, + RawDelegationData::new(42u128.into(), mock_env().block.height), gateway_delegations_read(&deps.storage, &identity2) .load(delegation_owner.as_bytes()) .unwrap() - .u128() ); assert!( reverse_gateway_delegations_read(&deps.storage, &delegation_owner) @@ -2931,6 +3173,7 @@ pub mod tests { assert!(try_delegate_to_gateway( deps.as_mut(), + mock_env(), mock_info("sender1", &vec![delegation1.clone()]), identity.clone() ) @@ -2938,6 +3181,7 @@ pub mod tests { assert!(try_delegate_to_gateway( deps.as_mut(), + mock_env(), mock_info("sender2", &vec![delegation2.clone()]), identity.clone() ) @@ -2964,6 +3208,7 @@ pub mod tests { try_delegate_to_gateway( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) @@ -2972,11 +3217,10 @@ pub mod tests { try_remove_gateway(deps.as_mut(), mock_info(gateway_owner, &[])).unwrap(); assert_eq!( - 100, + RawDelegationData::new(100u128.into(), mock_env().block.height), gateway_delegations_read(&deps.storage, &identity) .load(delegation_owner.as_bytes()) .unwrap() - .u128() ); assert!( reverse_gateway_delegations_read(&deps.storage, &delegation_owner) @@ -3023,6 +3267,7 @@ pub mod tests { try_delegate_to_gateway( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) @@ -3078,6 +3323,7 @@ pub mod tests { try_delegate_to_gateway( deps.as_mut(), + mock_env(), mock_info(delegation_owner.as_str(), &coins(100, DENOM)), identity.clone(), ) @@ -3128,6 +3374,7 @@ pub mod tests { assert!(try_delegate_to_gateway( deps.as_mut(), + mock_env(), mock_info(delegation_owner1.as_str(), &vec![delegation1.clone()]), identity.clone() ) @@ -3135,6 +3382,7 @@ pub mod tests { assert!(try_delegate_to_gateway( deps.as_mut(), + mock_env(), mock_info(delegation_owner2.as_str(), &vec![delegation2.clone()]), identity.clone() ) @@ -3175,13 +3423,13 @@ pub mod tests { let identity = add_gateway(node_owner, good_gateway_bond(), &mut deps); gateway_delegations(&mut deps.storage, &identity) - .save(b"delegator1", &Uint128(initial_delegation1)) + .save(b"delegator1", &raw_delegation_fixture(initial_delegation1)) .unwrap(); gateway_delegations(&mut deps.storage, &identity) - .save(b"delegator2", &Uint128(initial_delegation2)) + .save(b"delegator2", &raw_delegation_fixture(initial_delegation2)) .unwrap(); gateway_delegations(&mut deps.storage, &identity) - .save(b"delegator3", &Uint128(initial_delegation3)) + .save(b"delegator3", &raw_delegation_fixture(initial_delegation3)) .unwrap(); let bond_reward = read_gateway_epoch_bond_reward_rate(deps.as_ref().storage); @@ -3212,6 +3460,7 @@ pub mod tests { gateway_delegations_read(deps.as_ref().storage, &identity) .load("delegator1".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -3219,6 +3468,7 @@ pub mod tests { gateway_delegations_read(deps.as_ref().storage, &identity) .load("delegator2".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -3226,6 +3476,7 @@ pub mod tests { gateway_delegations_read(deps.as_ref().storage, &identity) .load("delegator3".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -3268,6 +3519,7 @@ pub mod tests { gateway_delegations_read(deps.as_ref().storage, &identity) .load("delegator1".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -3275,6 +3527,7 @@ pub mod tests { gateway_delegations_read(deps.as_ref().storage, &identity) .load("delegator2".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -3282,6 +3535,7 @@ pub mod tests { gateway_delegations_read(deps.as_ref().storage, &identity) .load("delegator3".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -3311,6 +3565,7 @@ pub mod tests { gateway_delegations_read(deps.as_ref().storage, &identity) .load("delegator1".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -3318,6 +3573,7 @@ pub mod tests { gateway_delegations_read(deps.as_ref().storage, &identity) .load("delegator2".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -3325,6 +3581,7 @@ pub mod tests { gateway_delegations_read(deps.as_ref().storage, &identity) .load("delegator3".as_bytes()) .unwrap() + .amount ); assert_eq!( @@ -3373,7 +3630,7 @@ pub mod tests { let mut write_bucket = mix_delegations(&mut deps.storage, &node_identity); for i in 1..=total_delegations { let delegator = Addr::unchecked(format!("delegator{}", i)); - let delegation = Uint128(i as u128); + let delegation = raw_delegation_fixture(i as u128); write_bucket .save(delegator.as_bytes(), &delegation) .unwrap(); From 2bc858cde351cb299c8986fd1045da6d832acbdb Mon Sep 17 00:00:00 2001 From: Mark Sinclair Date: Fri, 27 Aug 2021 09:32:26 +0100 Subject: [PATCH 11/28] explorer-api: port test: split out address resolution and add units tests --- explorer-api/src/ping/http.rs | 103 +++++++++++++++++++++++++--------- 1 file changed, 76 insertions(+), 27 deletions(-) diff --git a/explorer-api/src/ping/http.rs b/explorer-api/src/ping/http.rs index 248db7874d..30cac9702e 100644 --- a/explorer-api/src/ping/http.rs +++ b/explorer-api/src/ping/http.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use std::net::ToSocketAddrs; +use std::net::{SocketAddr, ToSocketAddrs}; use std::time::Duration; use rocket::serde::json::Json; @@ -80,32 +80,81 @@ async fn port_check(bond: &MixNodeBond) -> HashMap { ports } -async fn do_port_check(host: &str, port: u16) -> bool { - let addr = format!("{}:{}", host, port) - .to_socket_addrs() - .unwrap() - .next() - .unwrap(); - match tokio::time::timeout( - CONNECTION_TIMEOUT_SECONDS, - tokio::net::TcpStream::connect(addr), - ) - .await - { - Ok(Ok(_stream)) => { - // didn't timeout and tcp stream is open - trace!("Successfully pinged {}", addr); - true - } - Ok(Err(_stream_err)) => { - error!("{} ping failed {:}", addr, _stream_err); - // didn't timeout but couldn't open tcp stream - false - } - Err(_timeout) => { - // timed out - error!("{} timed out {:}", addr, _timeout); - false +fn resolve_host(host: &str, port: u16) -> Option { + match format!("{}:{}", host, port).to_socket_addrs() { + Ok(mut addrs) => addrs.next(), + Err(e) => { + error!("Failed to resolve {}:{} - {}", host, port, e); + None } } } + +async fn do_port_check(host: &str, port: u16) -> bool { + match resolve_host(host, port) { + Some(addr) => match tokio::time::timeout( + CONNECTION_TIMEOUT_SECONDS, + tokio::net::TcpStream::connect(addr), + ) + .await + { + Ok(Ok(_stream)) => { + // didn't timeout and tcp stream is open + trace!("Successfully pinged {}", addr); + true + } + Ok(Err(_stream_err)) => { + error!("{} ping failed {:}", addr, _stream_err); + // didn't timeout but couldn't open tcp stream + false + } + Err(_timeout) => { + // timed out + error!("{} timed out {:}", addr, _timeout); + false + } + }, + None => false, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn resolve_host_with_valid_ip_address_returns_some() { + assert!(resolve_host("8.8.8.8", 1234).is_some()); + assert!(resolve_host("2001:4860:4860::8888", 1234).is_some()); + } + + #[test] + fn resolve_host_with_valid_hostname_returns_some() { + assert!(resolve_host("nymtech.net", 1234).is_some()); + } + + #[test] + fn resolve_host_with_malformed_ip_address_returns_some() { + // these have missing values filled in + assert!(resolve_host("1.2.3", 1234).is_some()); // 1.2.0.3 + assert!(resolve_host("1.2", 1234).is_some()); // 1.0.0.2 + assert!(resolve_host("1", 1234).is_some()); // 0.0.0.1 + } + + #[test] + fn resolve_host_with_unknown_hostname_returns_none() { + assert!(resolve_host( + "some-unknown-hostname-that-will-never-resolve.nymtech.net", + 1234 + ) + .is_none()); + } + + #[test] + fn resolve_host_with_bad_strings_return_none() { + assert!(resolve_host("", 1234).is_none()); + assert!(resolve_host("🤘", 1234).is_none()); + assert!(resolve_host("@", 1234).is_none()); + assert!(resolve_host("*", 1234).is_none()); + } +} From 92f976a45d2d05c1250d6351392a3acc4eb1c01f Mon Sep 17 00:00:00 2001 From: Mark Sinclair Date: Fri, 27 Aug 2021 11:34:21 +0100 Subject: [PATCH 12/28] explorer-api: sanitize hostname before running checks to avoid leading or trailing spaces that are known to exist in the current test net --- explorer-api/src/ping/http.rs | 51 ++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/explorer-api/src/ping/http.rs b/explorer-api/src/ping/http.rs index 30cac9702e..6728c7ebac 100644 --- a/explorer-api/src/ping/http.rs +++ b/explorer-api/src/ping/http.rs @@ -5,9 +5,10 @@ use std::time::Duration; use rocket::serde::json::Json; use rocket::{Route, State}; +use mixnet_contract::MixNodeBond; + use crate::ping::models::PingResponse; use crate::state::ExplorerApiStateContext; -use mixnet_contract::MixNodeBond; const CONNECTION_TIMEOUT_SECONDS: Duration = Duration::from_secs(10); @@ -80,18 +81,31 @@ async fn port_check(bond: &MixNodeBond) -> HashMap { ports } -fn resolve_host(host: &str, port: u16) -> Option { - match format!("{}:{}", host, port).to_socket_addrs() { +fn sanitize_and_resolve_host(host: &str, port: u16) -> Option { + // trim the host + let trimmed_host = host.trim(); + + // host must be at least one non-whitespace character + if trimmed_host.is_empty() { + return None; + } + + // the host string should hopefully parse and resolve into a valid socket address + let parsed_host = format!("{}:{}", trimmed_host, port); + match parsed_host.to_socket_addrs() { Ok(mut addrs) => addrs.next(), Err(e) => { - error!("Failed to resolve {}:{} - {}", host, port, e); + error!( + "Failed to resolve {}:{} -> {}. Error: {}", + host, port, parsed_host, e + ); None } } } async fn do_port_check(host: &str, port: u16) -> bool { - match resolve_host(host, port) { + match sanitize_and_resolve_host(host, port) { Some(addr) => match tokio::time::timeout( CONNECTION_TIMEOUT_SECONDS, tokio::net::TcpStream::connect(addr), @@ -124,26 +138,25 @@ mod tests { #[test] fn resolve_host_with_valid_ip_address_returns_some() { - assert!(resolve_host("8.8.8.8", 1234).is_some()); - assert!(resolve_host("2001:4860:4860::8888", 1234).is_some()); + assert!(sanitize_and_resolve_host("8.8.8.8", 1234).is_some()); + assert!(sanitize_and_resolve_host("2001:4860:4860::8888", 1234).is_some()); } #[test] fn resolve_host_with_valid_hostname_returns_some() { - assert!(resolve_host("nymtech.net", 1234).is_some()); + assert!(sanitize_and_resolve_host("nymtech.net", 1234).is_some()); } #[test] - fn resolve_host_with_malformed_ip_address_returns_some() { - // these have missing values filled in - assert!(resolve_host("1.2.3", 1234).is_some()); // 1.2.0.3 - assert!(resolve_host("1.2", 1234).is_some()); // 1.0.0.2 - assert!(resolve_host("1", 1234).is_some()); // 0.0.0.1 + fn resolve_host_with_malformed_ip_address_returns_none() { + // these are invalid ip addresses + assert!(sanitize_and_resolve_host("192.168.1.999", 1234).is_none()); + assert!(sanitize_and_resolve_host("10.999.999.999", 1234).is_none()); } #[test] fn resolve_host_with_unknown_hostname_returns_none() { - assert!(resolve_host( + assert!(sanitize_and_resolve_host( "some-unknown-hostname-that-will-never-resolve.nymtech.net", 1234 ) @@ -152,9 +165,11 @@ mod tests { #[test] fn resolve_host_with_bad_strings_return_none() { - assert!(resolve_host("", 1234).is_none()); - assert!(resolve_host("🤘", 1234).is_none()); - assert!(resolve_host("@", 1234).is_none()); - assert!(resolve_host("*", 1234).is_none()); + assert!(sanitize_and_resolve_host("", 1234).is_none()); + assert!(sanitize_and_resolve_host(" ", 1234).is_none()); + assert!(sanitize_and_resolve_host(" 🤘 ", 1234).is_none()); + assert!(sanitize_and_resolve_host("🤘", 1234).is_none()); + assert!(sanitize_and_resolve_host("@", 1234).is_none()); + assert!(sanitize_and_resolve_host("*", 1234).is_none()); } } From cdf0d443411618e0952c35bfb5972fc82023cdfa Mon Sep 17 00:00:00 2001 From: Mark Sinclair Date: Tue, 31 Aug 2021 14:37:28 +0100 Subject: [PATCH 13/28] explorer-api: turned down logging from `error` to `warn` --- explorer-api/src/ping/http.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/explorer-api/src/ping/http.rs b/explorer-api/src/ping/http.rs index 6728c7ebac..8853fbac61 100644 --- a/explorer-api/src/ping/http.rs +++ b/explorer-api/src/ping/http.rs @@ -95,7 +95,7 @@ fn sanitize_and_resolve_host(host: &str, port: u16) -> Option { match parsed_host.to_socket_addrs() { Ok(mut addrs) => addrs.next(), Err(e) => { - error!( + warn!( "Failed to resolve {}:{} -> {}. Error: {}", host, port, parsed_host, e ); @@ -118,13 +118,13 @@ async fn do_port_check(host: &str, port: u16) -> bool { true } Ok(Err(_stream_err)) => { - error!("{} ping failed {:}", addr, _stream_err); + warn!("{} ping failed {:}", addr, _stream_err); // didn't timeout but couldn't open tcp stream false } Err(_timeout) => { // timed out - error!("{} timed out {:}", addr, _timeout); + warn!("{} timed out {:}", addr, _timeout); false } }, From c42f3c684425d0e718ca55adacbe5afa99502603 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Thu, 2 Sep 2021 12:29:47 +0100 Subject: [PATCH 14/28] add app alert banner --- wallet-web/pages/_app.tsx | 103 +++++++++++++++++++++++--------------- 1 file changed, 63 insertions(+), 40 deletions(-) diff --git a/wallet-web/pages/_app.tsx b/wallet-web/pages/_app.tsx index c7064abf06..f4fd63eab4 100644 --- a/wallet-web/pages/_app.tsx +++ b/wallet-web/pages/_app.tsx @@ -1,49 +1,72 @@ -import React, { useState } from 'react'; -import Head from 'next/head'; -import { ThemeProvider } from '@material-ui/core/styles'; -import CssBaseline from '@material-ui/core/CssBaseline'; -import { theme } from '../lib/theme'; -import type { AppProps } from 'next/app'; -import { ValidatorClientContext } from "../contexts/ValidatorClient"; +import React, { useState } from 'react' +import { Alert, AlertTitle } from '@material-ui/lab' +import Head from 'next/head' +import { ThemeProvider } from '@material-ui/core/styles' +import CssBaseline from '@material-ui/core/CssBaseline' +import { theme } from '../lib/theme' +import type { AppProps } from 'next/app' +import { ValidatorClientContext } from '../contexts/ValidatorClient' +import { Close } from '@material-ui/icons' +import { IconButton } from '@material-ui/core' // TODO: should it perhaps be pulled from some config or also user provided? -export const BONDING_CONTRACT_ADDRESS: string = "punk10pyejy66429refv3g35g2t7am0was7yalwrzen"; +export const BONDING_CONTRACT_ADDRESS: string = + 'punk10pyejy66429refv3g35g2t7am0was7yalwrzen' export const VALIDATOR_URLS: string[] = [ - "https://testnet-milhon-validator1.nymtech.net", - "https://testnet-milhon-validator2.nymtech.net", -]; -export const ADDRESS_LENGTH: number = 43; -export const ADMIN_ADDRESS: string = "punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0" -export const DENOM: string = "punk"; // used everywhere else -export const KEY_LENGTH: number = 32; -export const UDENOM: string = "upunk"; // required for client and coin construction - + 'https://testnet-milhon-validator1.nymtech.net', + 'https://testnet-milhon-validator2.nymtech.net', +] +export const ADDRESS_LENGTH: number = 43 +export const ADMIN_ADDRESS: string = + 'punk1h3w4nj7kny5dfyjw2le4vm74z03v9vd4dstpu0' +export const DENOM: string = 'punk' // used everywhere else +export const KEY_LENGTH: number = 32 +export const UDENOM: string = 'upunk' // required for client and coin construction export default function Application(props: AppProps) { - const { Component, pageProps } = props; + const { Component, pageProps } = props - const [client, setClient] = useState(null) + const [client, setClient] = useState(null) + const [showAlert, setShowAlert] = useState(true) - React.useEffect(() => { - const jssStyles = document.querySelector('#jss-server-side'); - if (jssStyles) { - jssStyles.parentElement.removeChild(jssStyles); - } - }, []); + React.useEffect(() => { + const jssStyles = document.querySelector('#jss-server-side') + if (jssStyles) { + jssStyles.parentElement.removeChild(jssStyles) + } + }, []) - return ( - - - - - Nym - - - - - - - - - ); + return ( + + + + + Nym + + + + + {showAlert && ( + setShowAlert(false)}> + + + } + > + Network maintenance + Testnet Milhon is currently down for maintenance. You may find + that certain features in the wallet do not work during this + period. + + )} + + + + + ) } From 1a3b83752e0f5e4eed5515ba0b90f3551c1f0a81 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Sep 2021 15:45:56 +0100 Subject: [PATCH 15/28] Bump next from 11.1.0 to 11.1.1 in /wallet-web (#758) Bumps [next](https://github.com/vercel/next.js) from 11.1.0 to 11.1.1. - [Release notes](https://github.com/vercel/next.js/releases) - [Changelog](https://github.com/vercel/next.js/blob/canary/release.js) - [Commits](https://github.com/vercel/next.js/compare/v11.1.0...v11.1.1) --- updated-dependencies: - dependency-name: next dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- wallet-web/package-lock.json | 395 +++++++++++++++++------------------ wallet-web/package.json | 2 +- wallet-web/yarn.lock | 111 +++++----- 3 files changed, 242 insertions(+), 266 deletions(-) diff --git a/wallet-web/package-lock.json b/wallet-web/package-lock.json index 6b029202cd..a1a9e22e12 100644 --- a/wallet-web/package-lock.json +++ b/wallet-web/package-lock.json @@ -11,10 +11,10 @@ "@material-ui/core": "^4.11.3", "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.57", - "@nymproject/nym-validator-client": "0.16.0", + "@nymproject/nym-validator-client": "0.17.0", "@types/react-dom": "^17.0.3", "bs58": "^4.0.1", - "next": "11.1.0", + "next": "11.1.1", "react": "17.0.2", "react-dom": "17.0.2", "semver": "^7.3.5" @@ -1785,11 +1785,14 @@ } }, "node_modules/@babel/runtime": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", - "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", + "version": "7.15.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.3.tgz", + "integrity": "sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==", "dependencies": { "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" } }, "node_modules/@babel/template": { @@ -1906,14 +1909,6 @@ "@cosmjs/utils": "^0.25.5" } }, - "node_modules/@cosmjs/cosmwasm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@cosmjs/cosmwasm/-/cosmwasm-0.25.5.tgz", - "integrity": "sha512-PsK28ARcow5hEd+hKAdGsjtjz/g5UMtPpSWD7l1WxWhuASLmJVXFebBUPJAu3CDeFG3FrlCRP5J8tLvJOisuJQ==", - "dependencies": { - "@cosmjs/cosmwasm-launchpad": "^0.25.5" - } - }, "node_modules/@cosmjs/cosmwasm-launchpad": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@cosmjs/cosmwasm-launchpad/-/cosmwasm-launchpad-0.25.5.tgz", @@ -1996,33 +1991,6 @@ "fast-deep-equal": "^3.1.3" } }, - "node_modules/@cosmjs/launchpad/node_modules/axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "dependencies": { - "follow-redirects": "^1.10.0" - } - }, - "node_modules/@cosmjs/launchpad/node_modules/follow-redirects": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz", - "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/@cosmjs/math": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@cosmjs/math/-/math-0.25.5.tgz", @@ -2093,33 +2061,6 @@ "xstream": "^11.14.0" } }, - "node_modules/@cosmjs/tendermint-rpc/node_modules/axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "dependencies": { - "follow-redirects": "^1.10.0" - } - }, - "node_modules/@cosmjs/tendermint-rpc/node_modules/follow-redirects": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz", - "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, "node_modules/@cosmjs/utils": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@cosmjs/utils/-/utils-0.25.5.tgz", @@ -3011,19 +2952,19 @@ "integrity": "sha512-jDJTpta+P4p1NZTFVLHJ/TLFVYVcOqv6l8xwOeBKNPMgY/zDYH/YH7SJbvrr/h1RcS9GzbPcLKGzpuK9cV56UA==" }, "node_modules/@next/env": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@next/env/-/env-11.1.0.tgz", - "integrity": "sha512-zPJkMFRenSf7BLlVee8987G0qQXAhxy7k+Lb/5hLAGkPVHAHm+oFFeL+2ipbI2KTEFlazdmGY0M+AlLQn7pWaw==" + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-11.1.1.tgz", + "integrity": "sha512-UEAzlfKofotLmj9LIgNixAfXpRck9rt/1CU9Q4ZtNDueGBJQP3HUzPHlrLChltWY2TA5MOzDQGL82H0a3+i5Ag==" }, "node_modules/@next/polyfill-module": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@next/polyfill-module/-/polyfill-module-11.1.0.tgz", - "integrity": "sha512-64EgW8SzJRQls2yJ5DkuljRxgE24o2kYtX/ghTkPUJYsfidHMWzQGwg26IgRbb/uHqTd1G0W5UkKag+Nt8TWaQ==" + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/polyfill-module/-/polyfill-module-11.1.1.tgz", + "integrity": "sha512-9FyVSnz00WGdlLsgc2w1xL1Lm/Q25y6FYIyA+1WlJvT6LA2lbR78GKiHgedzUvrAatVGAcg/Og+d0d7B4tsJOg==" }, "node_modules/@next/react-dev-overlay": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@next/react-dev-overlay/-/react-dev-overlay-11.1.0.tgz", - "integrity": "sha512-h+ry0sTk1W3mJw+TwEf91aqLbBJ5oqAsxfx+QryqEItNtfW6zLSSjxkyTYTqX8DkgSssQQutQfATkzBVgOR+qQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/react-dev-overlay/-/react-dev-overlay-11.1.1.tgz", + "integrity": "sha512-CXc/A0DbSk5VXYu4+zr0fHm52Zh/LhPlLyVPEctJOZL64ccxkls5xGoXvgolJCku9L0pLjJzvdfAmhNLOp5dyw==", "dependencies": { "@babel/code-frame": "7.12.11", "anser": "1.4.9", @@ -3107,9 +3048,9 @@ } }, "node_modules/@next/react-refresh-utils": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@next/react-refresh-utils/-/react-refresh-utils-11.1.0.tgz", - "integrity": "sha512-g5DtFTpLTGa36iy9DuZawtJeitI11gysFGKPQQqy+mNbSFazguArcJ10gAYFlbqpIi4boUamWNI5mAoSPx3kog==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/react-refresh-utils/-/react-refresh-utils-11.1.1.tgz", + "integrity": "sha512-j186y+lWc8BHAuysAWvlOqO9Bp7E3BLK/d/Ju3W2sP5BCH5ZLyLG/p308zSy/O0MGTag0B038ZA1dCy/msouRQ==", "peerDependencies": { "react-refresh": "0.8.3", "webpack": "^4 || ^5" @@ -3120,6 +3061,66 @@ } } }, + "node_modules/@next/swc-darwin-arm64": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-11.1.1.tgz", + "integrity": "sha512-KyB0aLpfQ+B2dsyGYpkM0ZwK3PV0t4C4b9yjgQc1VoTVnIjzXdDPnNOuVvmD849ZNOHfj3x8e2rlbxkj0lPm3A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-11.1.1.tgz", + "integrity": "sha512-B3ZXgrGx0bQplbrk2oggPjKPPsmyg8Fl0PJLMTVQ+erQ8g1m5QzyS9P6tB3SiIZa180JgENuguTHlVK5qEj4UA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-11.1.1.tgz", + "integrity": "sha512-qvZL7gSKF+E+GZ3L1XiTnE3cOh9rk0wkqimT/q+wwcZA4E720Lu4lrT79I3HPuj6i/JPgGvmNskcnYrDeaoFaw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-11.1.1.tgz", + "integrity": "sha512-jhnCiA1De1L+kA0gmHG1AJijHoxOcrETWziDWy8fcqSrM1NlC4aJ5Mnu6k0QMcM9MnmXTA4TQZOEv3kF7vhJUQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@node-rs/helper": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@node-rs/helper/-/helper-1.2.1.tgz", @@ -3129,17 +3130,15 @@ } }, "node_modules/@nymproject/nym-validator-client": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@nymproject/nym-validator-client/-/nym-validator-client-0.16.0.tgz", - "integrity": "sha512-tczc9qx68D3CJsw+BZ3N6ReCq/smgpgL58iQQ0yo5MD2ZoGIu6J8Zhg2vau/IR9uRyGtg4ipNSe0Wtxi++pnJQ==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nymproject/nym-validator-client/-/nym-validator-client-0.17.0.tgz", + "integrity": "sha512-MtzKECm3ZSV/P8ro6ZbKqHiSqg3IU3WXUpiGdFamfWnvVDa7Q3SSyxc26kr59CH8DnrkMWa2/N96i1dIVj3YYA==", "dependencies": { - "@cosmjs/cosmwasm": "^0.25.5", "@cosmjs/cosmwasm-stargate": "^0.25.5", - "@cosmjs/crypto": "^0.25.5", - "@cosmjs/launchpad": "^0.25.5", "@cosmjs/math": "^0.25.5", "@cosmjs/proto-signing": "^0.25.5", - "axios": "^0.19.2" + "@cosmjs/stargate": "^0.25.5", + "axios": "^0.21.1" } }, "node_modules/@protobufjs/aspromise": { @@ -3746,12 +3745,11 @@ } }, "node_modules/axios": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", - "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", - "deprecated": "Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", "dependencies": { - "follow-redirects": "1.5.10" + "follow-redirects": "^1.10.0" } }, "node_modules/babel-jest": { @@ -4696,14 +4694,6 @@ "node": ">=10" } }, - "node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dependencies": { - "ms": "2.0.0" - } - }, "node_modules/decimal.js": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", @@ -5176,14 +5166,22 @@ } }, "node_modules/follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "dependencies": { - "debug": "=3.1.0" - }, + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.2.tgz", + "integrity": "sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], "engines": { "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, "node_modules/foreach": { @@ -8319,16 +8317,16 @@ "dev": true }, "node_modules/next": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/next/-/next-11.1.0.tgz", - "integrity": "sha512-GHBk/c7Wyr6YbFRFZF37I0X7HKzkHHI8pur/loyXo5AIE8wdkbGPGO0ds3vNAO6f8AxZAKGCRYtAzoGlVLoifA==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/next/-/next-11.1.1.tgz", + "integrity": "sha512-vfLJDkwAHsZUho5R1K4w49nfYhftUMWNmeNSjCtulOvnRBuEFb7ROyRZOQk7f29rMz02eLQrPZ9yiAmPsexL2g==", "dependencies": { - "@babel/runtime": "7.12.5", + "@babel/runtime": "7.15.3", "@hapi/accept": "5.0.2", - "@next/env": "11.1.0", - "@next/polyfill-module": "11.1.0", - "@next/react-dev-overlay": "11.1.0", - "@next/react-refresh-utils": "11.1.0", + "@next/env": "11.1.1", + "@next/polyfill-module": "11.1.1", + "@next/react-dev-overlay": "11.1.1", + "@next/react-refresh-utils": "11.1.1", "@node-rs/helper": "1.2.1", "assert": "2.0.0", "ast-types": "0.13.2", @@ -8370,7 +8368,7 @@ "timers-browserify": "2.0.12", "tty-browserify": "0.0.1", "use-subscription": "1.5.1", - "util": "0.12.3", + "util": "0.12.4", "vm-browserify": "1.1.2", "watchpack": "2.1.1" }, @@ -8380,6 +8378,12 @@ "engines": { "node": ">=12.0.0" }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "11.1.1", + "@next/swc-darwin-x64": "11.1.1", + "@next/swc-linux-x64-gnu": "11.1.1", + "@next/swc-win32-x64-msvc": "11.1.1" + }, "peerDependencies": { "fibers": ">= 3.1.0", "node-sass": "^4.0.0 || ^5.0.0", @@ -10083,9 +10087,9 @@ } }, "node_modules/util": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.3.tgz", - "integrity": "sha512-I8XkoQwE+fPQEhy9v012V+TSdH2kp9ts29i20TaaDUXsg7x/onePbhFJUExBfv/2ay1ZOp/Vsm3nDlmnFGSAog==", + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", + "integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==", "dependencies": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", @@ -11650,9 +11654,9 @@ } }, "@babel/runtime": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", - "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", + "version": "7.15.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.3.tgz", + "integrity": "sha512-OvwMLqNXkCXSz1kSm58sEsNuhqOx/fKpnUnKnFB5v8uDda5bLNEHNgKPvhDN6IU0LDcnHQ90LlJ0Q6jnyBSIBA==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -11752,14 +11756,6 @@ "@cosmjs/utils": "^0.25.5" } }, - "@cosmjs/cosmwasm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@cosmjs/cosmwasm/-/cosmwasm-0.25.5.tgz", - "integrity": "sha512-PsK28ARcow5hEd+hKAdGsjtjz/g5UMtPpSWD7l1WxWhuASLmJVXFebBUPJAu3CDeFG3FrlCRP5J8tLvJOisuJQ==", - "requires": { - "@cosmjs/cosmwasm-launchpad": "^0.25.5" - } - }, "@cosmjs/cosmwasm-launchpad": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@cosmjs/cosmwasm-launchpad/-/cosmwasm-launchpad-0.25.5.tgz", @@ -11840,21 +11836,6 @@ "@cosmjs/utils": "^0.25.5", "axios": "^0.21.1", "fast-deep-equal": "^3.1.3" - }, - "dependencies": { - "axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "requires": { - "follow-redirects": "^1.10.0" - } - }, - "follow-redirects": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz", - "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==" - } } }, "@cosmjs/math": { @@ -11925,21 +11906,6 @@ "axios": "^0.21.1", "readonly-date": "^1.0.0", "xstream": "^11.14.0" - }, - "dependencies": { - "axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "requires": { - "follow-redirects": "^1.10.0" - } - }, - "follow-redirects": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz", - "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==" - } } }, "@cosmjs/utils": { @@ -12576,19 +12542,19 @@ "integrity": "sha512-jDJTpta+P4p1NZTFVLHJ/TLFVYVcOqv6l8xwOeBKNPMgY/zDYH/YH7SJbvrr/h1RcS9GzbPcLKGzpuK9cV56UA==" }, "@next/env": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@next/env/-/env-11.1.0.tgz", - "integrity": "sha512-zPJkMFRenSf7BLlVee8987G0qQXAhxy7k+Lb/5hLAGkPVHAHm+oFFeL+2ipbI2KTEFlazdmGY0M+AlLQn7pWaw==" + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/env/-/env-11.1.1.tgz", + "integrity": "sha512-UEAzlfKofotLmj9LIgNixAfXpRck9rt/1CU9Q4ZtNDueGBJQP3HUzPHlrLChltWY2TA5MOzDQGL82H0a3+i5Ag==" }, "@next/polyfill-module": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@next/polyfill-module/-/polyfill-module-11.1.0.tgz", - "integrity": "sha512-64EgW8SzJRQls2yJ5DkuljRxgE24o2kYtX/ghTkPUJYsfidHMWzQGwg26IgRbb/uHqTd1G0W5UkKag+Nt8TWaQ==" + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/polyfill-module/-/polyfill-module-11.1.1.tgz", + "integrity": "sha512-9FyVSnz00WGdlLsgc2w1xL1Lm/Q25y6FYIyA+1WlJvT6LA2lbR78GKiHgedzUvrAatVGAcg/Og+d0d7B4tsJOg==" }, "@next/react-dev-overlay": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@next/react-dev-overlay/-/react-dev-overlay-11.1.0.tgz", - "integrity": "sha512-h+ry0sTk1W3mJw+TwEf91aqLbBJ5oqAsxfx+QryqEItNtfW6zLSSjxkyTYTqX8DkgSssQQutQfATkzBVgOR+qQ==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/react-dev-overlay/-/react-dev-overlay-11.1.1.tgz", + "integrity": "sha512-CXc/A0DbSk5VXYu4+zr0fHm52Zh/LhPlLyVPEctJOZL64ccxkls5xGoXvgolJCku9L0pLjJzvdfAmhNLOp5dyw==", "requires": { "@babel/code-frame": "7.12.11", "anser": "1.4.9", @@ -12649,11 +12615,35 @@ } }, "@next/react-refresh-utils": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@next/react-refresh-utils/-/react-refresh-utils-11.1.0.tgz", - "integrity": "sha512-g5DtFTpLTGa36iy9DuZawtJeitI11gysFGKPQQqy+mNbSFazguArcJ10gAYFlbqpIi4boUamWNI5mAoSPx3kog==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/react-refresh-utils/-/react-refresh-utils-11.1.1.tgz", + "integrity": "sha512-j186y+lWc8BHAuysAWvlOqO9Bp7E3BLK/d/Ju3W2sP5BCH5ZLyLG/p308zSy/O0MGTag0B038ZA1dCy/msouRQ==", "requires": {} }, + "@next/swc-darwin-arm64": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-11.1.1.tgz", + "integrity": "sha512-KyB0aLpfQ+B2dsyGYpkM0ZwK3PV0t4C4b9yjgQc1VoTVnIjzXdDPnNOuVvmD849ZNOHfj3x8e2rlbxkj0lPm3A==", + "optional": true + }, + "@next/swc-darwin-x64": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-11.1.1.tgz", + "integrity": "sha512-B3ZXgrGx0bQplbrk2oggPjKPPsmyg8Fl0PJLMTVQ+erQ8g1m5QzyS9P6tB3SiIZa180JgENuguTHlVK5qEj4UA==", + "optional": true + }, + "@next/swc-linux-x64-gnu": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-11.1.1.tgz", + "integrity": "sha512-qvZL7gSKF+E+GZ3L1XiTnE3cOh9rk0wkqimT/q+wwcZA4E720Lu4lrT79I3HPuj6i/JPgGvmNskcnYrDeaoFaw==", + "optional": true + }, + "@next/swc-win32-x64-msvc": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-11.1.1.tgz", + "integrity": "sha512-jhnCiA1De1L+kA0gmHG1AJijHoxOcrETWziDWy8fcqSrM1NlC4aJ5Mnu6k0QMcM9MnmXTA4TQZOEv3kF7vhJUQ==", + "optional": true + }, "@node-rs/helper": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@node-rs/helper/-/helper-1.2.1.tgz", @@ -12663,17 +12653,15 @@ } }, "@nymproject/nym-validator-client": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@nymproject/nym-validator-client/-/nym-validator-client-0.16.0.tgz", - "integrity": "sha512-tczc9qx68D3CJsw+BZ3N6ReCq/smgpgL58iQQ0yo5MD2ZoGIu6J8Zhg2vau/IR9uRyGtg4ipNSe0Wtxi++pnJQ==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@nymproject/nym-validator-client/-/nym-validator-client-0.17.0.tgz", + "integrity": "sha512-MtzKECm3ZSV/P8ro6ZbKqHiSqg3IU3WXUpiGdFamfWnvVDa7Q3SSyxc26kr59CH8DnrkMWa2/N96i1dIVj3YYA==", "requires": { - "@cosmjs/cosmwasm": "^0.25.5", "@cosmjs/cosmwasm-stargate": "^0.25.5", - "@cosmjs/crypto": "^0.25.5", - "@cosmjs/launchpad": "^0.25.5", "@cosmjs/math": "^0.25.5", "@cosmjs/proto-signing": "^0.25.5", - "axios": "^0.19.2" + "@cosmjs/stargate": "^0.25.5", + "axios": "^0.21.1" } }, "@protobufjs/aspromise": { @@ -13195,11 +13183,11 @@ } }, "axios": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", - "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", "requires": { - "follow-redirects": "1.5.10" + "follow-redirects": "^1.10.0" } }, "babel-jest": { @@ -13985,14 +13973,6 @@ } } }, - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - }, "decimal.js": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz", @@ -14355,12 +14335,9 @@ } }, "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "requires": { - "debug": "=3.1.0" - } + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.2.tgz", + "integrity": "sha512-yLR6WaE2lbF0x4K2qE2p9PEXKLDjUjnR/xmjS3wHAYxtlsI9MLLBJUZirAHKzUZDGLxje7w/cXR49WOUo4rbsA==" }, "foreach": { "version": "2.0.5", @@ -16702,16 +16679,20 @@ "dev": true }, "next": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/next/-/next-11.1.0.tgz", - "integrity": "sha512-GHBk/c7Wyr6YbFRFZF37I0X7HKzkHHI8pur/loyXo5AIE8wdkbGPGO0ds3vNAO6f8AxZAKGCRYtAzoGlVLoifA==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/next/-/next-11.1.1.tgz", + "integrity": "sha512-vfLJDkwAHsZUho5R1K4w49nfYhftUMWNmeNSjCtulOvnRBuEFb7ROyRZOQk7f29rMz02eLQrPZ9yiAmPsexL2g==", "requires": { - "@babel/runtime": "7.12.5", + "@babel/runtime": "7.15.3", "@hapi/accept": "5.0.2", - "@next/env": "11.1.0", - "@next/polyfill-module": "11.1.0", - "@next/react-dev-overlay": "11.1.0", - "@next/react-refresh-utils": "11.1.0", + "@next/env": "11.1.1", + "@next/polyfill-module": "11.1.1", + "@next/react-dev-overlay": "11.1.1", + "@next/react-refresh-utils": "11.1.1", + "@next/swc-darwin-arm64": "11.1.1", + "@next/swc-darwin-x64": "11.1.1", + "@next/swc-linux-x64-gnu": "11.1.1", + "@next/swc-win32-x64-msvc": "11.1.1", "@node-rs/helper": "1.2.1", "assert": "2.0.0", "ast-types": "0.13.2", @@ -16753,7 +16734,7 @@ "timers-browserify": "2.0.12", "tty-browserify": "0.0.1", "use-subscription": "1.5.1", - "util": "0.12.3", + "util": "0.12.4", "vm-browserify": "1.1.2", "watchpack": "2.1.1" }, @@ -18105,9 +18086,9 @@ } }, "util": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.3.tgz", - "integrity": "sha512-I8XkoQwE+fPQEhy9v012V+TSdH2kp9ts29i20TaaDUXsg7x/onePbhFJUExBfv/2ay1ZOp/Vsm3nDlmnFGSAog==", + "version": "0.12.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz", + "integrity": "sha512-bxZ9qtSlGUWSOy9Qa9Xgk11kSslpuZwaxCg4sNIDj6FLucDab2JxnHwyNTCpHMtK1MjoQiWQ6DiUMZYbSrO+Sw==", "requires": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", diff --git a/wallet-web/package.json b/wallet-web/package.json index 9b845a2893..509ff97076 100644 --- a/wallet-web/package.json +++ b/wallet-web/package.json @@ -18,7 +18,7 @@ "@nymproject/nym-validator-client": "0.17.0", "@types/react-dom": "^17.0.3", "bs58": "^4.0.1", - "next": "11.1.0", + "next": "11.1.1", "react": "17.0.2", "react-dom": "17.0.2", "semver": "^7.3.5" diff --git a/wallet-web/yarn.lock b/wallet-web/yarn.lock index c025b1eef2..46b2251bfa 100644 --- a/wallet-web/yarn.lock +++ b/wallet-web/yarn.lock @@ -821,9 +821,9 @@ "@babel/helper-validator-option" "^7.14.5" "@babel/plugin-transform-typescript" "^7.14.5" -"@babel/runtime@7.12.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": - version "7.12.5" - resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz" +"@babel/runtime@7.15.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": + version "7.15.3" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.3.tgz#2e1c2880ca118e5b2f9988322bd8a7656a32502b" dependencies: regenerator-runtime "^0.13.4" @@ -920,12 +920,6 @@ pako "^2.0.2" protobufjs "~6.10.2" -"@cosmjs/cosmwasm@^0.25.5": - version "0.25.5" - resolved "https://registry.npmjs.org/@cosmjs/cosmwasm/-/cosmwasm-0.25.5.tgz" - dependencies: - "@cosmjs/cosmwasm-launchpad" "^0.25.5" - "@cosmjs/crypto@^0.25.5": version "0.25.5" resolved "https://registry.npmjs.org/@cosmjs/crypto/-/crypto-0.25.5.tgz" @@ -1312,17 +1306,17 @@ version "1.0.3" resolved "https://registry.yarnpkg.com/@napi-rs/triples/-/triples-1.0.3.tgz#76d6d0c3f4d16013c61e45dfca5ff1e6c31ae53c" -"@next/env@11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@next/env/-/env-11.1.0.tgz#cae83d8e0a65aa9f2af3368f8269ffd9d911746a" +"@next/env@11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@next/env/-/env-11.1.1.tgz#d403282accbe8795aa2341f0e02c2e8bfc92bfb0" -"@next/polyfill-module@11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@next/polyfill-module/-/polyfill-module-11.1.0.tgz#ee6b9117a1f9bb137479dfa51d5a9e38e066a62f" +"@next/polyfill-module@11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@next/polyfill-module/-/polyfill-module-11.1.1.tgz#89d5a70685a52a0fad79f05a1f97a6b15cc727aa" -"@next/react-dev-overlay@11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@next/react-dev-overlay/-/react-dev-overlay-11.1.0.tgz#8d4e8020a4cbdacbca431a0bf40c4d28187083af" +"@next/react-dev-overlay@11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@next/react-dev-overlay/-/react-dev-overlay-11.1.1.tgz#3cd99202a85412bada8ba9c8e3f4cf7c19294b24" dependencies: "@babel/code-frame" "7.12.11" anser "1.4.9" @@ -1336,9 +1330,25 @@ stacktrace-parser "0.1.10" strip-ansi "6.0.0" -"@next/react-refresh-utils@11.1.0": - version "11.1.0" - resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-11.1.0.tgz#60c3c7b127a5dab8b0a2889a7dcf8a90d2c4e592" +"@next/react-refresh-utils@11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@next/react-refresh-utils/-/react-refresh-utils-11.1.1.tgz#8d1a5432a53c9f987503d5ab07d3241230afb33f" + +"@next/swc-darwin-arm64@11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-11.1.1.tgz#ea9a76bcff00945df29a81bc43b3b22dd0a6cb53" + +"@next/swc-darwin-x64@11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-11.1.1.tgz#95838e9116897ae734d02fdbbfa601b6f52adaf3" + +"@next/swc-linux-x64-gnu@11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-11.1.1.tgz#42c4973213a880977ebdfad01474217d7d71e8c2" + +"@next/swc-win32-x64-msvc@11.1.1": + version "11.1.1" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-11.1.1.tgz#1ffcbd01a0155fa8558f7aefffea1066e9bebe74" "@node-rs/helper@1.2.1": version "1.2.1" @@ -1346,17 +1356,15 @@ dependencies: "@napi-rs/triples" "^1.0.3" -"@nymproject/nym-validator-client@0.16.0": - version "0.16.0" - resolved "https://registry.npmjs.org/@nymproject/nym-validator-client/-/nym-validator-client-0.16.0.tgz" +"@nymproject/nym-validator-client@0.17.0": + version "0.17.0" + resolved "https://registry.yarnpkg.com/@nymproject/nym-validator-client/-/nym-validator-client-0.17.0.tgz#b03af24295cbd2f1b94ffd0c38c5a493e994d111" dependencies: - "@cosmjs/cosmwasm" "^0.25.5" "@cosmjs/cosmwasm-stargate" "^0.25.5" - "@cosmjs/crypto" "^0.25.5" - "@cosmjs/launchpad" "^0.25.5" "@cosmjs/math" "^0.25.5" "@cosmjs/proto-signing" "^0.25.5" - axios "^0.19.2" + "@cosmjs/stargate" "^0.25.5" + axios "^0.21.1" "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": version "1.1.2" @@ -1658,12 +1666,6 @@ available-typed-arrays@^1.0.2: dependencies: array-filter "^1.0.0" -axios@^0.19.2: - version "0.19.2" - resolved "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz" - dependencies: - follow-redirects "1.5.10" - axios@^0.21.1: version "0.21.1" resolved "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz" @@ -2221,12 +2223,6 @@ debug@4, debug@^4.1.0, debug@^4.1.1: dependencies: ms "2.1.2" -debug@=3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz" - dependencies: - ms "2.0.0" - decimal.js@^10.2.1: version "10.3.1" resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.3.1.tgz" @@ -2494,12 +2490,6 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -follow-redirects@1.5.10: - version "1.5.10" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz" - dependencies: - debug "=3.1.0" - follow-redirects@^1.10.0: version "1.13.3" resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz" @@ -3617,16 +3607,16 @@ natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" -next@11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/next/-/next-11.1.0.tgz#767d4c4fa0b9b0c768cdbd6c9f03dd86b5d701c0" +next@11.1.1: + version "11.1.1" + resolved "https://registry.yarnpkg.com/next/-/next-11.1.1.tgz#ca15c6d6b4b4bf8c3e859f7fc4f9657ce59bcb63" dependencies: - "@babel/runtime" "7.12.5" + "@babel/runtime" "7.15.3" "@hapi/accept" "5.0.2" - "@next/env" "11.1.0" - "@next/polyfill-module" "11.1.0" - "@next/react-dev-overlay" "11.1.0" - "@next/react-refresh-utils" "11.1.0" + "@next/env" "11.1.1" + "@next/polyfill-module" "11.1.1" + "@next/react-dev-overlay" "11.1.1" + "@next/react-refresh-utils" "11.1.1" "@node-rs/helper" "1.2.1" assert "2.0.0" ast-types "0.13.2" @@ -3668,9 +3658,14 @@ next@11.1.0: timers-browserify "2.0.12" tty-browserify "0.0.1" use-subscription "1.5.1" - util "0.12.3" + util "0.12.4" vm-browserify "1.1.2" watchpack "2.1.1" + optionalDependencies: + "@next/swc-darwin-arm64" "11.1.1" + "@next/swc-darwin-x64" "11.1.1" + "@next/swc-linux-x64-gnu" "11.1.1" + "@next/swc-win32-x64-msvc" "11.1.1" node-fetch@2.6.1: version "2.6.1" @@ -4637,9 +4632,9 @@ util@0.10.3: dependencies: inherits "2.0.1" -util@0.12.3, util@^0.12.0: - version "0.12.3" - resolved "https://registry.npmjs.org/util/-/util-0.12.3.tgz" +util@0.12.4, util@^0.12.0: + version "0.12.4" + resolved "https://registry.yarnpkg.com/util/-/util-0.12.4.tgz#66121a31420df8f01ca0c464be15dfa1d1850253" dependencies: inherits "^2.0.3" is-arguments "^1.0.4" From 219c45a352584ab7f9efb957107a671a4e19d13e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C4=99drzej=20Stuczy=C5=84ski?= Date: Thu, 2 Sep 2021 15:48:29 +0100 Subject: [PATCH 16/28] Updated cosmos-sdk (#761) * Updated cosmos-sdk * Re-exposing more things --- Cargo.lock | 14 +++++++----- .../client-libs/validator-client/Cargo.toml | 6 ++--- .../validator-client/src/client.rs | 16 +++++++------- .../src/nymd/cosmwasm_client/client.rs | 22 +++++++++---------- .../src/nymd/cosmwasm_client/helpers.rs | 4 ++-- .../src/nymd/cosmwasm_client/logs.rs | 2 +- .../src/nymd/cosmwasm_client/mod.rs | 2 +- .../nymd/cosmwasm_client/signing_client.rs | 18 +++++++-------- .../src/nymd/cosmwasm_client/types.rs | 12 +++++----- .../validator-client/src/nymd/error.rs | 4 ++-- .../validator-client/src/nymd/fee_helpers.rs | 4 ++-- .../validator-client/src/nymd/gas_price.rs | 2 +- .../validator-client/src/nymd/mod.rs | 13 ++++++----- .../validator-client/src/nymd/wallet/mod.rs | 12 +++++----- 14 files changed, 67 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index daf1ab435d..43f73ac4bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -905,8 +905,9 @@ dependencies = [ [[package]] name = "cosmos-sdk-proto" -version = "0.6.0" -source = "git+https://github.com/cosmos/cosmos-rust/#ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7115eae4b8518967b8e737a3ef76025f2d007de7906d88c18f00b8bbfc70f51e" dependencies = [ "prost", "prost-types", @@ -914,9 +915,10 @@ dependencies = [ ] [[package]] -name = "cosmos_sdk" -version = "0.2.0" -source = "git+https://github.com/cosmos/cosmos-rust/#ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30" +name = "cosmrs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "609eba7eee6d9929927cbdf15f9fe96d250c316c5e1b983c4a47a10f60da5ea7" dependencies = [ "bip32", "cosmos-sdk-proto", @@ -6325,7 +6327,7 @@ dependencies = [ "bip39", "coconut-interface", "config", - "cosmos_sdk", + "cosmrs", "cosmwasm-std", "flate2", "itertools 0.10.1", diff --git a/common/client-libs/validator-client/Cargo.toml b/common/client-libs/validator-client/Cargo.toml index d811bcf5ef..cf2e9ea306 100644 --- a/common/client-libs/validator-client/Cargo.toml +++ b/common/client-libs/validator-client/Cargo.toml @@ -24,8 +24,8 @@ network-defaults = { path = "../../network-defaults" } # perhaps after https://github.com/cosmos/cosmos-rust/pull/97 is resolved (and tendermint-rs is updated) async-trait = { version = "0.1.51", optional = true } bip39 = { version = "1", features = ["rand"], optional = true } -config = { path = "../../config", optional = true} -cosmos_sdk = { git = "https://github.com/cosmos/cosmos-rust/", commit="ba012bd820240d3df2d9a0ab1deabe4ecd9a2f30", features = ["rpc", "bip32", "cosmwasm"], optional = true } +config = { path = "../../config", optional = true } +cosmrs = { version = "0.1", features = ["rpc", "bip32", "cosmwasm"], optional = true } prost = { version = "0.7", default-features = false, optional = true } flate2 = { version = "1.0.20", optional = true } sha2 = { version = "0.9.5", optional = true } @@ -33,4 +33,4 @@ itertools = { version = "0.10", optional = true } cosmwasm-std = { git = "https://github.com/jstuczyn/cosmwasm", branch="0.14.1-updatedk256", optional = true } [features] -nymd-client = ["async-trait", "bip39", "config", "cosmos_sdk", "prost", "flate2", "sha2", "itertools", "cosmwasm-std"] +nymd-client = ["async-trait", "bip39", "config", "cosmrs", "prost", "flate2", "sha2", "itertools", "cosmwasm-std"] diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index bb7000cff9..c093b08d9d 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -14,7 +14,7 @@ use url::Url; pub struct Config { api_url: Url, nymd_url: Url, - mixnet_contract_address: Option, + mixnet_contract_address: Option, mixnode_page_limit: Option, gateway_page_limit: Option, @@ -27,7 +27,7 @@ impl Config { pub fn new( nymd_url: Url, api_url: Url, - mixnet_contract_address: Option, + mixnet_contract_address: Option, ) -> Self { Config { nymd_url, @@ -63,7 +63,7 @@ impl Config { #[cfg(feature = "nymd-client")] pub struct Client { - mixnet_contract_address: Option, + mixnet_contract_address: Option, mnemonic: Option, mixnode_page_limit: Option, @@ -154,7 +154,7 @@ impl Client { // use case: somebody initialised client without a contract in order to upload and initialise one // and now they want to actually use it without making new client - pub fn set_mixnet_contract_address(&mut self, mixnet_contract_address: cosmos_sdk::AccountId) { + pub fn set_mixnet_contract_address(&mut self, mixnet_contract_address: cosmrs::AccountId) { self.mixnet_contract_address = Some(mixnet_contract_address) } @@ -245,7 +245,7 @@ impl Client { pub async fn get_all_nymd_reverse_mixnode_delegations( &self, - delegation_owner: &cosmos_sdk::AccountId, + delegation_owner: &cosmrs::AccountId, ) -> Result, ValidatorClientError> where C: CosmWasmClient + Sync, @@ -275,7 +275,7 @@ impl Client { pub async fn get_all_nymd_mixnode_delegations_of_owner( &self, - delegation_owner: &cosmos_sdk::AccountId, + delegation_owner: &cosmrs::AccountId, ) -> Result, ValidatorClientError> where C: CosmWasmClient + Sync, @@ -327,7 +327,7 @@ impl Client { pub async fn get_all_nymd_reverse_gateway_delegations( &self, - delegation_owner: &cosmos_sdk::AccountId, + delegation_owner: &cosmrs::AccountId, ) -> Result, ValidatorClientError> where C: CosmWasmClient + Sync, @@ -357,7 +357,7 @@ impl Client { pub async fn get_all_nymd_gateway_delegations_of_owner( &self, - delegation_owner: &cosmos_sdk::AccountId, + delegation_owner: &cosmrs::AccountId, ) -> Result, ValidatorClientError> where C: CosmWasmClient + Sync, diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs index 1a78c9c33c..8bd8a0b207 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/client.rs @@ -8,21 +8,21 @@ use crate::nymd::cosmwasm_client::types::{ }; use crate::nymd::error::NymdError; use async_trait::async_trait; -use cosmos_sdk::proto::cosmos::auth::v1beta1::{ +use cosmrs::proto::cosmos::auth::v1beta1::{ BaseAccount, QueryAccountRequest, QueryAccountResponse, }; -use cosmos_sdk::proto::cosmos::bank::v1beta1::{ +use cosmrs::proto::cosmos::bank::v1beta1::{ QueryAllBalancesRequest, QueryAllBalancesResponse, QueryBalanceRequest, QueryBalanceResponse, }; -use cosmos_sdk::proto::cosmwasm::wasm::v1beta1::*; -use cosmos_sdk::rpc::endpoint::block::Response as BlockResponse; -use cosmos_sdk::rpc::endpoint::broadcast; -use cosmos_sdk::rpc::endpoint::tx::Response as TxResponse; -use cosmos_sdk::rpc::query::Query; -use cosmos_sdk::rpc::{self, HttpClient, Order}; -use cosmos_sdk::tendermint::abci::Transaction; -use cosmos_sdk::tendermint::{abci, block, chain}; -use cosmos_sdk::{AccountId, Coin, Denom}; +use cosmrs::proto::cosmwasm::wasm::v1beta1::*; +use cosmrs::rpc::endpoint::block::Response as BlockResponse; +use cosmrs::rpc::endpoint::broadcast; +use cosmrs::rpc::endpoint::tx::Response as TxResponse; +use cosmrs::rpc::query::Query; +use cosmrs::rpc::{self, HttpClient, Order}; +use cosmrs::tendermint::abci::Transaction; +use cosmrs::tendermint::{abci, block, chain}; +use cosmrs::{AccountId, Coin, Denom}; use prost::Message; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/helpers.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/helpers.rs index 66a736aa52..2b4ef52226 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/helpers.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/helpers.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::nymd::error::NymdError; -use cosmos_sdk::proto::cosmos::base::query::v1beta1::PageRequest; -use cosmos_sdk::rpc::endpoint::broadcast; +use cosmrs::proto::cosmos::base::query::v1beta1::PageRequest; +use cosmrs::rpc::endpoint::broadcast; use flate2::write::GzEncoder; use flate2::Compression; use std::io::Write; diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs index 115ffa1abc..462b5bb580 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/logs.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::nymd::error::NymdError; -use cosmos_sdk::tendermint::abci; +use cosmrs::tendermint::abci; use itertools::Itertools; use serde::Deserialize; diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/mod.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/mod.rs index 5b04e0d47d..86a489fca0 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/mod.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/mod.rs @@ -3,7 +3,7 @@ use crate::nymd::error::NymdError; use crate::nymd::wallet::DirectSecp256k1HdWallet; -use cosmos_sdk::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl}; +use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl}; use std::convert::TryInto; pub mod client; 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 bb9a0e588b..e3352bb683 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 @@ -8,13 +8,13 @@ use crate::nymd::cosmwasm_client::types::*; use crate::nymd::error::NymdError; use crate::nymd::wallet::DirectSecp256k1HdWallet; use async_trait::async_trait; -use cosmos_sdk::bank::MsgSend; -use cosmos_sdk::distribution::MsgWithdrawDelegatorReward; -use cosmos_sdk::rpc::endpoint::broadcast; -use cosmos_sdk::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest}; -use cosmos_sdk::staking::{MsgDelegate, MsgUndelegate}; -use cosmos_sdk::tx::{Fee, Msg, MsgType, SignDoc, SignerInfo}; -use cosmos_sdk::{cosmwasm, rpc, tx, AccountId, Coin}; +use cosmrs::bank::MsgSend; +use cosmrs::distribution::MsgWithdrawDelegatorReward; +use cosmrs::rpc::endpoint::broadcast; +use cosmrs::rpc::{Error as TendermintRpcError, HttpClient, HttpClientUrl, SimpleRequest}; +use cosmrs::staking::{MsgDelegate, MsgUndelegate}; +use cosmrs::tx::{Fee, Msg, MsgType, SignDoc, SignerInfo}; +use cosmrs::{cosmwasm, rpc, tx, AccountId, Coin}; use serde::Serialize; use sha2::Digest; use sha2::Sha256; @@ -287,7 +287,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { let delegate_msg = MsgDelegate { delegator_address: delegator_address.to_owned(), validator_address: validator_address.to_owned(), - amount: Some(amount), + amount, } .to_msg() .map_err(|_| NymdError::SerializationError("MsgDelegate".to_owned()))?; @@ -407,7 +407,7 @@ pub trait SigningCosmWasmClient: CosmWasmClient { let auth_info = signer_info.auth_info(fee); // ideally I'd prefer to have the entire error put into the NymdError::SigningFailure - // but I'm super hesitant to trying to downcast the eyre::Report to cosmos_sdk::error::Error + // but I'm super hesitant to trying to downcast the eyre::Report to cosmrs::error::Error let sign_doc = SignDoc::new( &tx_body, &auth_info, diff --git a/common/client-libs/validator-client/src/nymd/cosmwasm_client/types.rs b/common/client-libs/validator-client/src/nymd/cosmwasm_client/types.rs index 179447001f..65767c3ed5 100644 --- a/common/client-libs/validator-client/src/nymd/cosmwasm_client/types.rs +++ b/common/client-libs/validator-client/src/nymd/cosmwasm_client/types.rs @@ -5,15 +5,15 @@ use crate::nymd::cosmwasm_client::logs::Log; use crate::nymd::error::NymdError; -use cosmos_sdk::crypto::PublicKey; -use cosmos_sdk::proto::cosmos::auth::v1beta1::BaseAccount; -use cosmos_sdk::proto::cosmwasm::wasm::v1beta1::{ +use cosmrs::crypto::PublicKey; +use cosmrs::proto::cosmos::auth::v1beta1::BaseAccount; +use cosmrs::proto::cosmwasm::wasm::v1beta1::{ CodeInfoResponse, ContractCodeHistoryEntry as ProtoContractCodeHistoryEntry, ContractCodeHistoryOperationType, ContractInfo as ProtoContractInfo, }; -use cosmos_sdk::tendermint::chain; -use cosmos_sdk::tx::{AccountNumber, SequenceNumber}; -use cosmos_sdk::{tx, AccountId, Coin}; +use cosmrs::tendermint::chain; +use cosmrs::tx::{AccountNumber, SequenceNumber}; +use cosmrs::{tx, AccountId, Coin}; use serde::Serialize; use std::convert::TryFrom; diff --git a/common/client-libs/validator-client/src/nymd/error.rs b/common/client-libs/validator-client/src/nymd/error.rs index d4b0b15f15..01f730f477 100644 --- a/common/client-libs/validator-client/src/nymd/error.rs +++ b/common/client-libs/validator-client/src/nymd/error.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::nymd::cosmwasm_client::types::ContractCodeId; -use cosmos_sdk::tendermint::block; -use cosmos_sdk::{bip32, rpc, tx, AccountId}; +use cosmrs::tendermint::block; +use cosmrs::{bip32, rpc, tx, AccountId}; use std::io; use thiserror::Error; 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 c2d70cc187..b8b8b0669e 100644 --- a/common/client-libs/validator-client/src/nymd/fee_helpers.rs +++ b/common/client-libs/validator-client/src/nymd/fee_helpers.rs @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 use crate::nymd::GasPrice; -use cosmos_sdk::tx::{Fee, Gas}; -use cosmos_sdk::Coin; +use cosmrs::tx::{Fee, Gas}; +use cosmrs::Coin; use cosmwasm_std::Uint128; #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] diff --git a/common/client-libs/validator-client/src/nymd/gas_price.rs b/common/client-libs/validator-client/src/nymd/gas_price.rs index 12278c9c5a..6a76d6b5a5 100644 --- a/common/client-libs/validator-client/src/nymd/gas_price.rs +++ b/common/client-libs/validator-client/src/nymd/gas_price.rs @@ -3,7 +3,7 @@ use crate::nymd::error::NymdError; use config::defaults; -use cosmos_sdk::Denom; +use cosmrs::Denom; use cosmwasm_std::Decimal; use std::str::FromStr; diff --git a/common/client-libs/validator-client/src/nymd/mod.rs b/common/client-libs/validator-client/src/nymd/mod.rs index 50becfc291..a99cfc91eb 100644 --- a/common/client-libs/validator-client/src/nymd/mod.rs +++ b/common/client-libs/validator-client/src/nymd/mod.rs @@ -9,11 +9,10 @@ use crate::nymd::cosmwasm_client::types::{ use crate::nymd::error::NymdError; use crate::nymd::fee_helpers::Operation; use crate::nymd::wallet::DirectSecp256k1HdWallet; -use cosmos_sdk::rpc::endpoint::broadcast; -use cosmos_sdk::rpc::{Error as TendermintRpcError, HttpClientUrl}; -use cosmos_sdk::tx::{Fee, Gas}; -use cosmos_sdk::Coin as CosmosCoin; -use cosmos_sdk::{AccountId, Denom}; +use cosmrs::rpc::endpoint::broadcast; +use cosmrs::rpc::{Error as TendermintRpcError, HttpClientUrl}; +use cosmrs::tx::{Fee, Gas}; + use cosmwasm_std::Coin; use mixnet_contract::{ Addr, Delegation, ExecuteMsg, Gateway, GatewayOwnershipResponse, IdentityKey, @@ -29,7 +28,9 @@ use std::convert::TryInto; pub use crate::nymd::cosmwasm_client::client::CosmWasmClient; pub use crate::nymd::cosmwasm_client::signing_client::SigningCosmWasmClient; pub use crate::nymd::gas_price::GasPrice; -pub use cosmos_sdk::rpc::HttpClient as QueryNymdClient; +pub use cosmrs::rpc::HttpClient as QueryNymdClient; +pub use cosmrs::Coin as CosmosCoin; +pub use cosmrs::{AccountId, Denom}; pub use signing_client::Client as SigningNymdClient; pub mod cosmwasm_client; 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 ef43a65f84..5ead527212 100644 --- a/common/client-libs/validator-client/src/nymd/wallet/mod.rs +++ b/common/client-libs/validator-client/src/nymd/wallet/mod.rs @@ -3,11 +3,11 @@ use crate::nymd::error::NymdError; use config::defaults; -use cosmos_sdk::bip32::{DerivationPath, XPrv}; -use cosmos_sdk::crypto::secp256k1::SigningKey; -use cosmos_sdk::crypto::PublicKey; -use cosmos_sdk::tx::SignDoc; -use cosmos_sdk::{tx, AccountId}; +use cosmrs::bip32::{DerivationPath, XPrv}; +use cosmrs::crypto::secp256k1::SigningKey; +use cosmrs::crypto::PublicKey; +use cosmrs::tx::SignDoc; +use cosmrs::{tx, AccountId}; /// Derivation information required to derive a keypair and an address from a mnemonic. struct Secp256k1Derivation { @@ -95,7 +95,7 @@ impl DirectSecp256k1HdWallet { sign_doc: SignDoc, ) -> Result { // ideally I'd prefer to have the entire error put into the NymdError::SigningFailure - // but I'm super hesitant to trying to downcast the eyre::Report to cosmos_sdk::error::Error + // but I'm super hesitant to trying to downcast the eyre::Report to cosmrs::error::Error sign_doc .sign(&signer.private_key) .map_err(|_| NymdError::SigningFailure) From 28be53eefb9a1a6950b0b84e8117c6829df93c37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Thu, 2 Sep 2021 18:26:40 +0300 Subject: [PATCH 17/28] Add block_height in the Delegation structure as well (#757) --- common/mixnet-contract/src/delegation.rs | 13 +++++++---- contracts/mixnet/src/contract.rs | 4 ++++ contracts/mixnet/src/queries.rs | 28 +++++++++++++++++++----- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/common/mixnet-contract/src/delegation.rs b/common/mixnet-contract/src/delegation.rs index 5aab311db5..023472bb02 100644 --- a/common/mixnet-contract/src/delegation.rs +++ b/common/mixnet-contract/src/delegation.rs @@ -26,11 +26,16 @@ impl RawDelegationData { pub struct Delegation { owner: Addr, amount: Coin, + block_height: u64, } impl Delegation { - pub fn new(owner: Addr, amount: Coin) -> Self { - Delegation { owner, amount } + pub fn new(owner: Addr, amount: Coin, block_height: u64) -> Self { + Delegation { + owner, + amount, + block_height, + } } pub fn amount(&self) -> &Coin { @@ -46,8 +51,8 @@ impl Display for Delegation { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, - "{} {} delegated by {}", - self.amount.amount, self.amount.denom, self.owner + "{} {} delegated by {} at block {}", + self.amount.amount, self.amount.denom, self.owner, self.block_height ) } } diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 62cd403093..1d22196037 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -245,6 +245,8 @@ pub fn migrate(deps: DepsMut, env: Env, _msg: MigrateMsg) -> Result Result Ok(Delegation::new( address, coin(delegation_value.amount.u128(), DENOM), + delegation_value.block_height, )), None => Err(ContractError::NoMixnodeDelegationFound { identity: mix_identity, @@ -213,6 +215,7 @@ pub(crate) fn query_gateway_delegations_paged( "Non-UTF8 address used as key in bucket. The storage is corrupted!", )), coin(entry.1.amount.u128(), DENOM), + entry.1.block_height, ) }) }) @@ -268,6 +271,7 @@ pub(crate) fn query_gateway_delegation( Some(delegation_value) => Ok(Delegation::new( address, coin(delegation_value.amount.u128(), DENOM), + delegation_value.block_height, )), None => Err(ContractError::NoGatewayDelegationFound { identity: gateway_identity, @@ -288,7 +292,7 @@ mod tests { use crate::transactions; use cosmwasm_std::testing::mock_info; use cosmwasm_std::{Addr, Storage}; - use mixnet_contract::{Gateway, MixNode}; + use mixnet_contract::{Gateway, MixNode, RawDelegationData}; #[test] fn mixnodes_empty_on_init() { @@ -793,11 +797,18 @@ mod tests { let delegation_owner = Addr::unchecked("bar"); mix_delegations(&mut deps.storage, &node_identity) - .save(delegation_owner.as_bytes(), &raw_delegation_fixture(42)) + .save( + delegation_owner.as_bytes(), + &RawDelegationData::new(42u128.into(), 12_345), + ) .unwrap(); assert_eq!( - Ok(Delegation::new(delegation_owner.clone(), coin(42, DENOM))), + Ok(Delegation::new( + delegation_owner.clone(), + coin(42, DENOM), + 12_345 + )), query_mixnode_delegation(deps.as_ref(), node_identity, delegation_owner) ) } @@ -1178,11 +1189,18 @@ mod tests { let delegation_owner = Addr::unchecked("bar"); gateway_delegations(&mut deps.storage, &node_identity) - .save(delegation_owner.as_bytes(), &raw_delegation_fixture(42)) + .save( + delegation_owner.as_bytes(), + &&RawDelegationData::new(42u128.into(), 12_345), + ) .unwrap(); assert_eq!( - Ok(Delegation::new(delegation_owner.clone(), coin(42, DENOM))), + Ok(Delegation::new( + delegation_owner.clone(), + coin(42, DENOM), + 12_345 + )), query_gateway_delegation(deps.as_ref(), node_identity, delegation_owner) ) } From 2789ee8f18768bf7fd9b26a63ee928b286996049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Fri, 3 Sep 2021 15:30:45 +0300 Subject: [PATCH 18/28] Update coconut-rs and use hash_to_scalar from there (#765) Failed tests are due to some nightly issue, not related to this PR --- Cargo.lock | 8 +++----- common/coconut-interface/Cargo.toml | 3 +-- common/coconut-interface/src/lib.rs | 22 ---------------------- 3 files changed, 4 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 43f73ac4bd..50d7be17ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -430,6 +430,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54757888b09a69be70b5ec303e382a74227392086ba808cb01eeca29233a2397" dependencies = [ + "digest 0.9.0", "ff", "group", "pairing", @@ -698,13 +699,12 @@ dependencies = [ "coconut-rs", "getset", "serde", - "sha2", ] [[package]] name = "coconut-rs" -version = "0.4.0" -source = "git+https://github.com/nymtech/coconut.git?branch=0.4.0#183cff805aa73a908b681ac3fcdff7d084c4f42b" +version = "0.5.0" +source = "git+https://github.com/nymtech/coconut.git?branch=0.5.0#a1b72d51aa2a67b73b9f58d707030ae6dc70af7f" dependencies = [ "bls12_381", "bs58", @@ -717,8 +717,6 @@ dependencies = [ "serde", "serde_derive", "sha2", - "sha3", - "subtle 2.4.0", "thiserror", ] diff --git a/common/coconut-interface/Cargo.toml b/common/coconut-interface/Cargo.toml index 2a5ed40a53..3303688d9f 100644 --- a/common/coconut-interface/Cargo.toml +++ b/common/coconut-interface/Cargo.toml @@ -6,7 +6,6 @@ description = "Crutch library until there is proper SerDe support for coconut st [dependencies] serde = { version = "1.0", features = ["derive"] } -sha2 = "0.9.3" getset = "0.1.1" -coconut-rs = { git = "https://github.com/nymtech/coconut.git", branch = "0.4.0" } +coconut-rs = { git = "https://github.com/nymtech/coconut.git", branch = "0.5.0" } diff --git a/common/coconut-interface/src/lib.rs b/common/coconut-interface/src/lib.rs index b068e7525c..8e1d4f8969 100644 --- a/common/coconut-interface/src/lib.rs +++ b/common/coconut-interface/src/lib.rs @@ -3,10 +3,6 @@ use getset::{CopyGetters, Getters}; use serde::{Deserialize, Serialize}; -use sha2::{ - digest::{generic_array::typenum::Unsigned, Digest}, - Sha256, -}; pub use coconut_rs::*; @@ -147,21 +143,3 @@ impl VerificationKeyResponse { VerificationKeyResponse { key } } } - -pub fn hash_to_scalar(msg: M) -> Attribute -where - M: AsRef<[u8]>, -{ - let mut h = Sha256::new(); - h.update(msg); - let digest = h.finalize(); - - let mut bytes = [0u8; 64]; - let pad_size = 64usize - .checked_sub(::OutputSize::to_usize()) - .unwrap_or_default(); - - bytes[pad_size..].copy_from_slice(&digest); - - Attribute::from_bytes_wide(&bytes) -} From 9a3d824a4a451d4906219607f4a7bcd27dbbeba4 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Mon, 6 Sep 2021 20:39:24 +0100 Subject: [PATCH 19/28] remove alert --- wallet-web/components/AppAlert.tsx | 30 ++++++++++++++++++++++++++++++ wallet-web/pages/_app.tsx | 20 +------------------- 2 files changed, 31 insertions(+), 19 deletions(-) create mode 100644 wallet-web/components/AppAlert.tsx diff --git a/wallet-web/components/AppAlert.tsx b/wallet-web/components/AppAlert.tsx new file mode 100644 index 0000000000..6e7bff3583 --- /dev/null +++ b/wallet-web/components/AppAlert.tsx @@ -0,0 +1,30 @@ +import { IconButton } from '@material-ui/core' +import { Close } from '@material-ui/icons' +import { Alert, AlertProps, AlertTitle } from '@material-ui/lab' +import React, { useState } from 'react' + +export const AppAlert = ({ + message, + severity = 'info', + title, +}: { + message: string + severity?: AlertProps['severity'] + title?: string +}) => { + const [showAlert, setShowAlert] = useState(true) + + return showAlert ? ( + setShowAlert(false)}> + + + } + > + {title} + {message} + + ) : null +} diff --git a/wallet-web/pages/_app.tsx b/wallet-web/pages/_app.tsx index f4fd63eab4..0cce398e52 100644 --- a/wallet-web/pages/_app.tsx +++ b/wallet-web/pages/_app.tsx @@ -1,14 +1,11 @@ import React, { useState } from 'react' -import { Alert, AlertTitle } from '@material-ui/lab' import Head from 'next/head' import { ThemeProvider } from '@material-ui/core/styles' import CssBaseline from '@material-ui/core/CssBaseline' import { theme } from '../lib/theme' import type { AppProps } from 'next/app' import { ValidatorClientContext } from '../contexts/ValidatorClient' -import { Close } from '@material-ui/icons' -import { IconButton } from '@material-ui/core' - +import { AppAlert } from '../components/AppAlert' // TODO: should it perhaps be pulled from some config or also user provided? export const BONDING_CONTRACT_ADDRESS: string = 'punk10pyejy66429refv3g35g2t7am0was7yalwrzen' @@ -49,21 +46,6 @@ export default function Application(props: AppProps) { - {showAlert && ( - setShowAlert(false)}> - - - } - > - Network maintenance - Testnet Milhon is currently down for maintenance. You may find - that certain features in the wallet do not work during this - period. - - )} From 08276e6e427679f1b437e370a66dd1140631b429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Tue, 7 Sep 2021 16:33:30 +0200 Subject: [PATCH 20/28] Remove migration code (#759) --- contracts/mixnet/src/contract.rs | 244 +------------------------------ 1 file changed, 6 insertions(+), 238 deletions(-) diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 1d22196037..257f14324b 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -2,21 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 use crate::helpers::calculate_epoch_reward_rate; -use crate::queries::DELEGATION_PAGE_MAX_LIMIT; use crate::state::State; -use crate::storage::{config, gateway_delegations, layer_distribution, mix_delegations}; +use crate::storage::{config, layer_distribution}; use crate::{error::ContractError, queries, transactions}; -use config::defaults::{DENOM, NETWORK_MONITOR_ADDRESS}; +use config::defaults::NETWORK_MONITOR_ADDRESS; use cosmwasm_std::{ - coin, entry_point, to_binary, Addr, Decimal, Deps, DepsMut, Env, MessageInfo, Order, - QueryResponse, Response, StdResult, Storage, Uint128, -}; -use cosmwasm_storage::ReadonlyBucket; -use mixnet_contract::{ - Delegation, ExecuteMsg, IdentityKey, IdentityKeyRef, InstantiateMsg, MigrateMsg, - PagedGatewayDelegationsResponse, PagedMixDelegationsResponse, QueryMsg, RawDelegationData, - StateParams, + entry_point, to_binary, Addr, Decimal, Deps, DepsMut, Env, MessageInfo, QueryResponse, + Response, Uint128, }; +use mixnet_contract::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StateParams}; pub const INITIAL_DEFAULT_EPOCH_LENGTH: u32 = 2; @@ -207,233 +201,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result Result { - const PREFIX_MIX_DELEGATION: &[u8] = b"md"; - const PREFIX_GATEWAY_DELEGATION: &[u8] = b"gd"; - const DELEGATION_PAGE_DEFAULT_LIMIT: u32 = 500; - - fn calculate_start_value>(start_after: Option) -> Option> { - start_after.as_ref().map(|identity| { - identity - .as_ref() - .as_bytes() - .iter() - .cloned() - .chain(std::iter::once(0)) - .collect() - }) - } - - fn query_mixnode_old_delegations_paged( - deps: Deps, - mix_identity: IdentityKey, - start_after: Option, - limit: Option, - ) -> StdResult { - let limit = limit - .unwrap_or(DELEGATION_PAGE_DEFAULT_LIMIT) - .min(DELEGATION_PAGE_MAX_LIMIT) as usize; - let start = calculate_start_value(start_after); - - let delegations = mix_old_delegations_read(deps.storage, &mix_identity) - .range(start.as_deref(), None, Order::Ascending) - .take(limit) - .map(|res| { - res.map(|entry| { - Delegation::new( - Addr::unchecked(String::from_utf8(entry.0).expect( - "Non-UTF8 address used as key in bucket. The storage is corrupted!", - )), - coin(entry.1.u128(), DENOM), - // dummy data, used as a placeholder in the context of migration - 42, - ) - }) - }) - .collect::>>()?; - - let start_next_after = delegations.last().map(|delegation| delegation.owner()); - - Ok(PagedMixDelegationsResponse::new( - mix_identity, - delegations, - start_next_after, - )) - } - - fn query_gateway_old_delegations_paged( - deps: Deps, - gateway_identity: IdentityKey, - start_after: Option, - limit: Option, - ) -> StdResult { - let limit = limit - .unwrap_or(DELEGATION_PAGE_DEFAULT_LIMIT) - .min(DELEGATION_PAGE_MAX_LIMIT) as usize; - let start = calculate_start_value(start_after); - - let delegations = gateway_old_delegations_read(deps.storage, &gateway_identity) - .range(start.as_deref(), None, Order::Ascending) - .take(limit) - .map(|res| { - res.map(|entry| { - Delegation::new( - Addr::unchecked(String::from_utf8(entry.0).expect( - "Non-UTF8 address used as key in bucket. The storage is corrupted!", - )), - coin(entry.1.u128(), DENOM), - // dummy data, used as a placeholder in the context of migration - 42, - ) - }) - }) - .collect::>>()?; - - let start_next_after = delegations.last().map(|delegation| delegation.owner()); - - Ok(PagedGatewayDelegationsResponse::new( - gateway_identity, - delegations, - start_next_after, - )) - } - - fn mix_old_delegations_read<'a>( - storage: &'a dyn Storage, - mix_identity: IdentityKeyRef, - ) -> ReadonlyBucket<'a, Uint128> { - ReadonlyBucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()]) - } - - fn gateway_old_delegations_read<'a>( - storage: &'a dyn Storage, - gateway_identity: IdentityKeyRef, - ) -> ReadonlyBucket<'a, Uint128> { - ReadonlyBucket::multilevel( - storage, - &[PREFIX_GATEWAY_DELEGATION, gateway_identity.as_bytes()], - ) - } - - fn get_all_mixnodes_identities(deps: &DepsMut) -> Result, ContractError> { - let mut mixnode_bonds = Vec::new(); - let mut start_after = None; - loop { - let mut paged_response = - queries::query_mixnodes_paged(deps.as_ref(), start_after, None)?; - mixnode_bonds.append(&mut paged_response.nodes); - - if let Some(start_after_res) = paged_response.start_next_after { - start_after = Some(start_after_res) - } else { - break; - } - } - let mixnodes = mixnode_bonds - .into_iter() - .map(|bond| bond.mix_node.identity_key) - .collect(); - - Ok(mixnodes) - } - - fn get_all_gateways_identities(deps: &DepsMut) -> Result, ContractError> { - let mut gateway_bonds = Vec::new(); - let mut start_after = None; - loop { - let mut paged_response = - queries::query_gateways_paged(deps.as_ref(), start_after, None)?; - gateway_bonds.append(&mut paged_response.nodes); - - if let Some(start_after_res) = paged_response.start_next_after { - start_after = Some(start_after_res) - } else { - break; - } - } - let gateways = gateway_bonds - .into_iter() - .map(|bond| bond.gateway.identity_key) - .collect(); - - Ok(gateways) - } - - fn get_all_mixnode_delegations( - deps: &DepsMut, - mix_identity: IdentityKeyRef, - ) -> Result, ContractError> { - let mut delegations = Vec::new(); - let mut start_after = None; - loop { - let mut paged_response = query_mixnode_old_delegations_paged( - deps.as_ref(), - mix_identity.into(), - start_after, - None, - )?; - delegations.append(&mut paged_response.delegations); - - if let Some(start_after_res) = paged_response.start_next_after { - start_after = Some(start_after_res) - } else { - break; - } - } - - Ok(delegations) - } - - fn get_all_gateway_delegations( - deps: &DepsMut, - gateway_identity: IdentityKeyRef, - ) -> Result, ContractError> { - let mut delegations = Vec::new(); - let mut start_after = None; - loop { - let mut paged_response = query_gateway_old_delegations_paged( - deps.as_ref(), - gateway_identity.into(), - start_after, - None, - )?; - delegations.append(&mut paged_response.delegations); - - if let Some(start_after_res) = paged_response.start_next_after { - start_after = Some(start_after_res) - } else { - break; - } - } - - Ok(delegations) - } - - let mixnodes_identities = get_all_mixnodes_identities(&deps)?; - for mix_identity in mixnodes_identities { - let delegations = get_all_mixnode_delegations(&deps, &mix_identity)?; - for delegation in delegations { - let old_delegation_bucket = mix_old_delegations_read(deps.storage, &mix_identity); - let amount = old_delegation_bucket.load(delegation.owner().as_bytes())?; - let new_delegation_data = RawDelegationData::new(amount, env.block.height); - let mut delegation_bucket = mix_delegations(deps.storage, &mix_identity); - delegation_bucket.save(delegation.owner().as_bytes(), &new_delegation_data)?; - } - } - - let gateways_identities = get_all_gateways_identities(&deps)?; - for gateway_identity in gateways_identities { - let delegations = get_all_gateway_delegations(&deps, &gateway_identity)?; - for delegation in delegations { - let old_delegation_bucket = - gateway_old_delegations_read(deps.storage, &gateway_identity); - let amount = old_delegation_bucket.load(delegation.owner().as_bytes())?; - let new_delegation_data = RawDelegationData::new(amount, env.block.height); - let mut delegation_bucket = gateway_delegations(deps.storage, &gateway_identity); - delegation_bucket.save(delegation.owner().as_bytes(), &new_delegation_data)?; - } - } - +pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { Ok(Default::default()) } From e00e77db15c2c18699c58bf771940c16f63a610f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Wed, 8 Sep 2021 15:07:24 +0200 Subject: [PATCH 21/28] Feature/bond blockstamp (#760) * Add block_height to MixNode/GatewayBond * Reward based on blockstamp of bonded node or of delegation * Add specific tests * Add migration code * Apply doc nit --- common/mixnet-contract/src/gateway.rs | 7 +- common/mixnet-contract/src/lib.rs | 7 + common/mixnet-contract/src/mixnode.rs | 13 +- contracts/mixnet/src/contract.rs | 40 ++- contracts/mixnet/src/queries.rs | 38 ++- contracts/mixnet/src/storage.rs | 216 +++++++++++++-- contracts/mixnet/src/support/tests.rs | 5 +- contracts/mixnet/src/transactions.rs | 385 ++++++++++++++++++++++++-- 8 files changed, 638 insertions(+), 73 deletions(-) diff --git a/common/mixnet-contract/src/gateway.rs b/common/mixnet-contract/src/gateway.rs index 7b9c816b4a..a6b6d91e7a 100644 --- a/common/mixnet-contract/src/gateway.rs +++ b/common/mixnet-contract/src/gateway.rs @@ -7,6 +7,8 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::fmt::Display; +use crate::current_block_height; + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] pub struct Gateway { pub host: String, @@ -24,15 +26,18 @@ pub struct GatewayBond { pub bond_amount: Coin, pub total_delegation: Coin, pub owner: Addr, + #[serde(default = "current_block_height")] + pub block_height: u64, pub gateway: Gateway, } impl GatewayBond { - pub fn new(bond_amount: Coin, owner: Addr, gateway: Gateway) -> Self { + pub fn new(bond_amount: Coin, owner: Addr, block_height: u64, gateway: Gateway) -> Self { GatewayBond { total_delegation: coin(0, &bond_amount.denom), bond_amount, owner, + block_height, gateway, } } diff --git a/common/mixnet-contract/src/lib.rs b/common/mixnet-contract/src/lib.rs index 73a3769e01..a3ad1a7fc2 100644 --- a/common/mixnet-contract/src/lib.rs +++ b/common/mixnet-contract/src/lib.rs @@ -16,3 +16,10 @@ pub use gateway::{Gateway, GatewayBond, GatewayOwnershipResponse, PagedGatewayRe pub use mixnode::{Layer, MixNode, MixNodeBond, MixOwnershipResponse, PagedMixnodeResponse}; pub use msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg}; pub use types::{IdentityKey, IdentityKeyRef, LayerDistribution, SphinxKey, StateParams}; + +use std::sync::atomic::{AtomicU64, Ordering}; +pub static CURRENT_BLOCK_HEIGHT: AtomicU64 = AtomicU64::new(0); + +pub fn current_block_height() -> u64 { + CURRENT_BLOCK_HEIGHT.load(Ordering::Relaxed) +} diff --git a/common/mixnet-contract/src/mixnode.rs b/common/mixnet-contract/src/mixnode.rs index 9870516b93..7c20186b67 100644 --- a/common/mixnet-contract/src/mixnode.rs +++ b/common/mixnet-contract/src/mixnode.rs @@ -8,6 +8,8 @@ use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; use std::fmt::Display; +use crate::current_block_height; + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] pub struct MixNode { pub host: String, @@ -35,16 +37,25 @@ pub struct MixNodeBond { pub total_delegation: Coin, pub owner: Addr, pub layer: Layer, + #[serde(default = "current_block_height")] + pub block_height: u64, pub mix_node: MixNode, } impl MixNodeBond { - pub fn new(bond_amount: Coin, owner: Addr, layer: Layer, mix_node: MixNode) -> Self { + pub fn new( + bond_amount: Coin, + owner: Addr, + layer: Layer, + block_height: u64, + mix_node: MixNode, + ) -> Self { MixNodeBond { total_delegation: coin(0, &bond_amount.denom), bond_amount, owner, layer, + block_height, mix_node, } } diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 257f14324b..7dc7843723 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -94,18 +94,22 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::BondMixnode { mix_node } => transactions::try_add_mixnode(deps, info, mix_node), + ExecuteMsg::BondMixnode { mix_node } => { + transactions::try_add_mixnode(deps, env, info, mix_node) + } ExecuteMsg::UnbondMixnode {} => transactions::try_remove_mixnode(deps, info), - ExecuteMsg::BondGateway { gateway } => transactions::try_add_gateway(deps, info, gateway), + ExecuteMsg::BondGateway { gateway } => { + transactions::try_add_gateway(deps, env, info, gateway) + } ExecuteMsg::UnbondGateway {} => transactions::try_remove_gateway(deps, info), ExecuteMsg::UpdateStateParams(params) => { transactions::try_update_state_params(deps, info, params) } ExecuteMsg::RewardMixnode { identity, uptime } => { - transactions::try_reward_mixnode(deps, info, identity, uptime) + transactions::try_reward_mixnode(deps, env, info, identity, uptime) } ExecuteMsg::RewardGateway { identity, uptime } => { - transactions::try_reward_gateway(deps, info, identity, uptime) + transactions::try_reward_gateway(deps, env, info, identity, uptime) } ExecuteMsg::DelegateToMixnode { mix_identity } => { transactions::try_delegate_to_mixnode(deps, env, info, mix_identity) @@ -201,7 +205,33 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result Result { +pub fn migrate(deps: DepsMut, env: Env, _msg: MigrateMsg) -> Result { + use crate::storage::{gateways, gateways_read, mixnodes, mixnodes_read}; + use cosmwasm_std::{Order, StdResult}; + use mixnet_contract::CURRENT_BLOCK_HEIGHT; + use mixnet_contract::{GatewayBond, MixNodeBond}; + use std::sync::atomic::Ordering; + + CURRENT_BLOCK_HEIGHT.store(env.block.height, Ordering::Relaxed); + + let bonds = mixnodes_read(deps.storage) + .range(None, None, Order::Ascending) + .map(|res| res.map(|item| item.1)) + .collect::>>()?; + + for bond in bonds { + mixnodes(deps.storage).save(bond.identity().as_bytes(), &bond)?; + } + + let bonds = gateways_read(deps.storage) + .range(None, None, Order::Ascending) + .map(|res| res.map(|item| item.1)) + .collect::>>()?; + + for bond in bonds { + gateways(deps.storage).save(bond.identity().as_bytes(), &bond)?; + } + Ok(Default::default()) } diff --git a/contracts/mixnet/src/queries.rs b/contracts/mixnet/src/queries.rs index 535b3beb71..a9643a372e 100644 --- a/contracts/mixnet/src/queries.rs +++ b/contracts/mixnet/src/queries.rs @@ -290,7 +290,7 @@ mod tests { good_gateway_bond, good_mixnode_bond, raw_delegation_fixture, }; use crate::transactions; - use cosmwasm_std::testing::mock_info; + use cosmwasm_std::testing::{mock_env, mock_info}; use cosmwasm_std::{Addr, Storage}; use mixnet_contract::{Gateway, MixNode, RawDelegationData}; @@ -546,8 +546,13 @@ mod tests { identity_key: "bobsnode".into(), ..helpers::mix_node_fixture() }; - transactions::try_add_mixnode(deps.as_mut(), mock_info("bob", &good_mixnode_bond()), node) - .unwrap(); + transactions::try_add_mixnode( + deps.as_mut(), + mock_env(), + mock_info("bob", &good_mixnode_bond()), + node, + ) + .unwrap(); let res = query_owns_mixnode(deps.as_ref(), Addr::unchecked("fred")).unwrap(); assert!(!res.has_node); @@ -557,8 +562,13 @@ mod tests { identity_key: "fredsnode".into(), ..helpers::mix_node_fixture() }; - transactions::try_add_mixnode(deps.as_mut(), mock_info("fred", &good_mixnode_bond()), node) - .unwrap(); + transactions::try_add_mixnode( + deps.as_mut(), + mock_env(), + mock_info("fred", &good_mixnode_bond()), + node, + ) + .unwrap(); let res = query_owns_mixnode(deps.as_ref(), Addr::unchecked("fred")).unwrap(); assert!(res.has_node); @@ -583,8 +593,13 @@ mod tests { identity_key: "bobsnode".into(), ..helpers::gateway_fixture() }; - transactions::try_add_gateway(deps.as_mut(), mock_info("bob", &good_gateway_bond()), node) - .unwrap(); + transactions::try_add_gateway( + deps.as_mut(), + mock_env(), + mock_info("bob", &good_gateway_bond()), + node, + ) + .unwrap(); let res = query_owns_gateway(deps.as_ref(), Addr::unchecked("fred")).unwrap(); assert!(!res.has_gateway); @@ -594,8 +609,13 @@ mod tests { identity_key: "fredsnode".into(), ..helpers::gateway_fixture() }; - transactions::try_add_gateway(deps.as_mut(), mock_info("fred", &good_gateway_bond()), node) - .unwrap(); + transactions::try_add_gateway( + deps.as_mut(), + mock_env(), + mock_info("fred", &good_gateway_bond()), + node, + ) + .unwrap(); let res = query_owns_gateway(deps.as_ref(), Addr::unchecked("fred")).unwrap(); assert!(res.has_gateway); diff --git a/contracts/mixnet/src/storage.rs b/contracts/mixnet/src/storage.rs index 3de3b08c9b..c038691389 100644 --- a/contracts/mixnet/src/storage.rs +++ b/contracts/mixnet/src/storage.rs @@ -3,6 +3,7 @@ use crate::queries; use crate::state::State; +use crate::transactions::MINIMUM_BLOCK_AGE_FOR_REWARDING; use cosmwasm_std::{Decimal, Order, StdResult, Storage, Uint128}; use cosmwasm_storage::{ bucket, bucket_read, singleton, singleton_read, Bucket, ReadonlyBucket, ReadonlySingleton, @@ -164,6 +165,7 @@ pub(crate) fn increase_mix_delegated_stakes( storage: &mut dyn Storage, mix_identity: IdentityKeyRef, scaled_reward_rate: Decimal, + reward_blockstamp: u64, ) -> StdResult { let chunk_size = queries::DELEGATION_PAGE_MAX_LIMIT as usize; @@ -193,11 +195,15 @@ pub(crate) fn increase_mix_delegated_stakes( ); // and for each of them increase the stake proportionally to the reward + // if at least `MINIMUM_BLOCK_AGE_FOR_REWARDING` blocks have been created + // since they delegated for (delegator_address, mut delegation) in delegations_chunk.into_iter() { - let reward = delegation.amount * scaled_reward_rate; - delegation.amount += reward; - total_rewarded += reward; - mix_delegations(storage, mix_identity).save(&delegator_address, &delegation)?; + if delegation.block_height + MINIMUM_BLOCK_AGE_FOR_REWARDING <= reward_blockstamp { + let reward = delegation.amount * scaled_reward_rate; + delegation.amount += reward; + total_rewarded += reward; + mix_delegations(storage, mix_identity).save(&delegator_address, &delegation)?; + } } } @@ -208,6 +214,7 @@ pub(crate) fn increase_gateway_delegated_stakes( storage: &mut dyn Storage, gateway_identity: IdentityKeyRef, scaled_reward_rate: Decimal, + reward_blockstamp: u64, ) -> StdResult { let chunk_size = queries::DELEGATION_PAGE_MAX_LIMIT as usize; @@ -237,11 +244,16 @@ pub(crate) fn increase_gateway_delegated_stakes( ); // and for each of them increase the stake proportionally to the reward + // if at least `MINIMUM_BLOCK_AGE_FOR_REWARDING` blocks have been created + // since they delegated for (delegator_address, mut delegation) in delegations_chunk.into_iter() { - let reward = delegation.amount * scaled_reward_rate; - delegation.amount += reward; - total_rewarded += reward; - gateway_delegations(storage, gateway_identity).save(&delegator_address, &delegation)?; + if delegation.block_height + MINIMUM_BLOCK_AGE_FOR_REWARDING <= reward_blockstamp { + let reward = delegation.amount * scaled_reward_rate; + delegation.amount += reward; + total_rewarded += reward; + gateway_delegations(storage, gateway_identity) + .save(&delegator_address, &delegation)?; + } } } @@ -435,6 +447,7 @@ mod tests { total_delegation: coin(0, DENOM), owner: node_owner.clone(), layer: Layer::One, + block_height: 12_345, mix_node: MixNode { identity_key: node_identity.clone(), ..mix_node_fixture() @@ -468,6 +481,7 @@ mod tests { bond_amount: coin(bond_value, DENOM), total_delegation: coin(0, DENOM), owner: node_owner.clone(), + block_height: 12_345, gateway: Gateway { identity_key: node_identity.clone(), ..gateway_fixture() @@ -498,9 +512,13 @@ mod tests { // 0.001 let reward = Decimal::from_ratio(1u128, 1000u128); - let total_increase = - increase_mix_delegated_stakes(&mut deps.storage, node_identity.as_ref(), reward) - .unwrap(); + let total_increase = increase_mix_delegated_stakes( + &mut deps.storage, + node_identity.as_ref(), + reward, + 42, + ) + .unwrap(); // there was no increase assert!(total_increase.is_zero()); @@ -518,6 +536,7 @@ mod tests { fn when_there_is_a_single_delegation() { let mut deps = mock_dependencies(&[]); let node_identity: IdentityKey = "nodeidentity".into(); + let delegation_blockstamp = 42; // 0.001 let reward = Decimal::from_ratio(1u128, 1000u128); @@ -526,13 +545,17 @@ mod tests { mix_delegations(&mut deps.storage, &node_identity) .save( delegator_address.as_bytes(), - &RawDelegationData::new(1000u128.into(), 42), + &RawDelegationData::new(1000u128.into(), delegation_blockstamp), ) .unwrap(); - let total_increase = - increase_mix_delegated_stakes(&mut deps.storage, node_identity.as_ref(), reward) - .unwrap(); + let total_increase = increase_mix_delegated_stakes( + &mut deps.storage, + node_identity.as_ref(), + reward, + delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING, + ) + .unwrap(); assert_eq!(Uint128(1), total_increase); @@ -545,10 +568,67 @@ mod tests { ) } + #[test] + fn when_there_is_a_single_delegation_depending_on_blockstamp() { + let mut deps = mock_dependencies(&[]); + let node_identity: IdentityKey = "nodeidentity".into(); + let delegation_blockstamp = 42; + + // 0.001 + let reward = Decimal::from_ratio(1u128, 1000u128); + + let delegator_address = Addr::unchecked("bob"); + mix_delegations(&mut deps.storage, &node_identity) + .save( + delegator_address.as_bytes(), + &RawDelegationData::new(1000u128.into(), delegation_blockstamp), + ) + .unwrap(); + + let total_increase = increase_mix_delegated_stakes( + &mut deps.storage, + node_identity.as_ref(), + reward, + delegation_blockstamp + MINIMUM_BLOCK_AGE_FOR_REWARDING - 1, + ) + .unwrap(); + + // there was no increase + assert!(total_increase.is_zero()); + + // amount is not incremented + assert_eq!( + RawDelegationData::new(1000u128.into(), delegation_blockstamp), + mix_delegations_read(&mut deps.storage, &node_identity) + .load(delegator_address.as_bytes()) + .unwrap() + ); + + let total_increase = increase_mix_delegated_stakes( + &mut deps.storage, + node_identity.as_ref(), + reward, + delegation_blockstamp + MINIMUM_BLOCK_AGE_FOR_REWARDING, + ) + .unwrap(); + + // there is an increase now, that the lock period has passed + assert_eq!(Uint128(1), total_increase); + + // amount is incremented + assert_eq!( + RawDelegationData::new(1001u128.into(), delegation_blockstamp), + mix_delegations_read(&mut deps.storage, &node_identity) + .load(delegator_address.as_bytes()) + .unwrap() + ) + } + #[test] fn when_there_are_multiple_delegations() { let mut deps = mock_dependencies(&[]); let node_identity: IdentityKey = "nodeidentity".into(); + let delegation_blockstamp = 42; // 0.001 let reward = Decimal::from_ratio(1u128, 1000u128); @@ -556,13 +636,20 @@ mod tests { for i in 0..100 { let delegator_address = Addr::unchecked(format!("address{}", i)); mix_delegations(&mut deps.storage, &node_identity) - .save(delegator_address.as_bytes(), &raw_delegation_fixture(1000)) + .save( + delegator_address.as_bytes(), + &RawDelegationData::new(1000u128.into(), delegation_blockstamp), + ) .unwrap(); } - let total_increase = - increase_mix_delegated_stakes(&mut deps.storage, node_identity.as_ref(), reward) - .unwrap(); + let total_increase = increase_mix_delegated_stakes( + &mut deps.storage, + node_identity.as_ref(), + reward, + delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING, + ) + .unwrap(); assert_eq!(Uint128(100), total_increase); @@ -581,6 +668,7 @@ mod tests { fn when_there_are_more_delegations_than_page_size() { let mut deps = mock_dependencies(&[]); let node_identity: IdentityKey = "nodeidentity".into(); + let delegation_blockstamp = 42; // 0.001 let reward = Decimal::from_ratio(1u128, 1000u128); @@ -588,13 +676,20 @@ mod tests { for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 { let delegator_address = Addr::unchecked(format!("address{}", i)); mix_delegations(&mut deps.storage, &node_identity) - .save(delegator_address.as_bytes(), &raw_delegation_fixture(1000)) + .save( + delegator_address.as_bytes(), + &RawDelegationData::new(1000u128.into(), delegation_blockstamp), + ) .unwrap(); } - let total_increase = - increase_mix_delegated_stakes(&mut deps.storage, node_identity.as_ref(), reward) - .unwrap(); + let total_increase = increase_mix_delegated_stakes( + &mut deps.storage, + node_identity.as_ref(), + reward, + delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING, + ) + .unwrap(); assert_eq!( Uint128(queries::DELEGATION_PAGE_MAX_LIMIT as u128 * 10), @@ -696,6 +791,7 @@ mod tests { &mut deps.storage, node_identity.as_ref(), reward, + 42, ) .unwrap(); @@ -715,6 +811,7 @@ mod tests { fn when_there_is_a_single_delegation() { let mut deps = mock_dependencies(&[]); let node_identity: IdentityKey = "nodeidentity".into(); + let delegation_blockstamp = 42; // 0.001 let reward = Decimal::from_ratio(1u128, 1000u128); @@ -723,7 +820,7 @@ mod tests { gateway_delegations(&mut deps.storage, &node_identity) .save( delegator_address.as_bytes(), - &RawDelegationData::new(1000u128.into(), 42), + &RawDelegationData::new(1000u128.into(), delegation_blockstamp), ) .unwrap(); @@ -731,6 +828,7 @@ mod tests { &mut deps.storage, node_identity.as_ref(), reward, + delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING, ) .unwrap(); @@ -745,10 +843,67 @@ mod tests { ) } + #[test] + fn when_there_is_a_single_delegation_depending_on_blockstamp() { + let mut deps = mock_dependencies(&[]); + let node_identity: IdentityKey = "nodeidentity".into(); + let delegation_blockstamp = 42; + + // 0.001 + let reward = Decimal::from_ratio(1u128, 1000u128); + + let delegator_address = Addr::unchecked("bob"); + gateway_delegations(&mut deps.storage, &node_identity) + .save( + delegator_address.as_bytes(), + &RawDelegationData::new(1000u128.into(), delegation_blockstamp), + ) + .unwrap(); + + let total_increase = increase_gateway_delegated_stakes( + &mut deps.storage, + node_identity.as_ref(), + reward, + delegation_blockstamp + MINIMUM_BLOCK_AGE_FOR_REWARDING - 1, + ) + .unwrap(); + + // there was no increase + assert!(total_increase.is_zero()); + + // amount is not incremented + assert_eq!( + RawDelegationData::new(1000u128.into(), delegation_blockstamp), + gateway_delegations_read(&mut deps.storage, &node_identity) + .load(delegator_address.as_bytes()) + .unwrap() + ); + + let total_increase = increase_gateway_delegated_stakes( + &mut deps.storage, + node_identity.as_ref(), + reward, + delegation_blockstamp + MINIMUM_BLOCK_AGE_FOR_REWARDING, + ) + .unwrap(); + + // there is an increase now, that the lock period has passed + assert_eq!(Uint128(1), total_increase); + + // amount is incremented + assert_eq!( + RawDelegationData::new(1001u128.into(), delegation_blockstamp), + gateway_delegations_read(&mut deps.storage, &node_identity) + .load(delegator_address.as_bytes()) + .unwrap() + ) + } + #[test] fn when_there_are_multiple_delegations() { let mut deps = mock_dependencies(&[]); let node_identity: IdentityKey = "nodeidentity".into(); + let delegation_blockstamp = 42; // 0.001 let reward = Decimal::from_ratio(1u128, 1000u128); @@ -756,7 +911,10 @@ mod tests { for i in 0..100 { let delegator_address = Addr::unchecked(format!("address{}", i)); gateway_delegations(&mut deps.storage, &node_identity) - .save(delegator_address.as_bytes(), &raw_delegation_fixture(1000)) + .save( + delegator_address.as_bytes(), + &RawDelegationData::new(1000u128.into(), delegation_blockstamp), + ) .unwrap(); } @@ -764,6 +922,7 @@ mod tests { &mut deps.storage, node_identity.as_ref(), reward, + delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING, ) .unwrap(); @@ -784,6 +943,7 @@ mod tests { fn when_there_are_more_delegations_than_page_size() { let mut deps = mock_dependencies(&[]); let node_identity: IdentityKey = "nodeidentity".into(); + let delegation_blockstamp = 42; // 0.001 let reward = Decimal::from_ratio(1u128, 1000u128); @@ -791,7 +951,10 @@ mod tests { for i in 0..queries::DELEGATION_PAGE_MAX_LIMIT * 10 { let delegator_address = Addr::unchecked(format!("address{}", i)); gateway_delegations(&mut deps.storage, &node_identity) - .save(delegator_address.as_bytes(), &raw_delegation_fixture(1000)) + .save( + delegator_address.as_bytes(), + &RawDelegationData::new(1000u128.into(), delegation_blockstamp), + ) .unwrap(); } @@ -799,6 +962,7 @@ mod tests { &mut deps.storage, node_identity.as_ref(), reward, + delegation_blockstamp + 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING, ) .unwrap(); diff --git a/contracts/mixnet/src/support/tests.rs b/contracts/mixnet/src/support/tests.rs index fd1ac7fade..70b761e0fd 100644 --- a/contracts/mixnet/src/support/tests.rs +++ b/contracts/mixnet/src/support/tests.rs @@ -31,6 +31,7 @@ pub mod helpers { let key = format!("{}mixnode", sender); try_add_mixnode( deps.as_mut(), + mock_env(), info, MixNode { identity_key: key.clone(), @@ -67,6 +68,7 @@ pub mod helpers { let key = format!("{}gateway", sender); try_add_gateway( deps.as_mut(), + mock_env(), info, Gateway { identity_key: key.clone(), @@ -129,6 +131,7 @@ pub mod helpers { coin(50, DENOM), Addr::unchecked("foo"), Layer::One, + 12_345, mix_node, ) } @@ -156,7 +159,7 @@ pub mod helpers { identity_key: "identity".to_string(), version: "0.10.0".to_string(), }; - GatewayBond::new(coin(50, DENOM), Addr::unchecked("foo"), gateway) + GatewayBond::new(coin(50, DENOM), Addr::unchecked("foo"), 12_345, gateway) } pub fn raw_delegation_fixture(amount: u128) -> RawDelegationData { diff --git a/contracts/mixnet/src/transactions.rs b/contracts/mixnet/src/transactions.rs index 7260f751aa..2e753ebf06 100644 --- a/contracts/mixnet/src/transactions.rs +++ b/contracts/mixnet/src/transactions.rs @@ -16,6 +16,7 @@ use mixnet_contract::{ }; const OLD_DELEGATIONS_CHUNK_SIZE: usize = 500; +pub(crate) const MINIMUM_BLOCK_AGE_FOR_REWARDING: u64 = 17280; // Looks for the total amount of delegations towards a particular node. // This function is used only in very specific circumstances: @@ -87,6 +88,7 @@ fn validate_mixnode_bond(bond: &[Coin], minimum_bond: Uint128) -> Result<(), Con pub(crate) fn try_add_mixnode( deps: DepsMut, + env: Env, info: MessageInfo, mix_node: MixNode, ) -> Result { @@ -126,7 +128,13 @@ pub(crate) fn try_add_mixnode( let layer_distribution = queries::query_layer_distribution(deps.as_ref()); let layer = layer_distribution.choose_with_fewest(); - let mut bond = MixNodeBond::new(info.funds[0].clone(), info.sender.clone(), layer, mix_node); + let mut bond = MixNodeBond::new( + info.funds[0].clone(), + info.sender.clone(), + layer, + env.block.height, + mix_node, + ); // this might potentially require more gas if a significant number of delegations was there let delegations_bucket = mix_delegations_read(deps.storage, &bond.mix_node.identity_key); @@ -216,6 +224,7 @@ fn validate_gateway_bond(bond: &[Coin], minimum_bond: Uint128) -> Result<(), Con pub(crate) fn try_add_gateway( deps: DepsMut, + env: Env, info: MessageInfo, gateway: Gateway, ) -> Result { @@ -252,7 +261,12 @@ pub(crate) fn try_add_gateway( let minimum_bond = read_state_params(deps.storage).minimum_gateway_bond; validate_gateway_bond(&info.funds, minimum_bond)?; - let mut bond = GatewayBond::new(info.funds[0].clone(), info.sender.clone(), gateway); + let mut bond = GatewayBond::new( + info.funds[0].clone(), + info.sender.clone(), + env.block.height, + gateway, + ); // this might potentially require more gas if a significant number of delegations was there let delegations_bucket = gateway_delegations_read(deps.storage, &bond.gateway.identity_key); @@ -391,6 +405,7 @@ pub(crate) fn try_update_state_params( pub(crate) fn try_reward_mixnode( deps: DepsMut, + env: Env, info: MessageInfo, mix_identity: IdentityKey, uptime: u32, @@ -431,14 +446,22 @@ pub(crate) fn try_reward_mixnode( let bond_scaled_reward_rate = scale_reward_by_uptime(bond_reward_rate, uptime)?; let delegation_scaled_reward_rate = scale_reward_by_uptime(delegation_reward_rate, uptime)?; - let node_reward = current_bond.bond_amount.amount * bond_scaled_reward_rate; - let total_delegation_reward = - increase_mix_delegated_stakes(deps.storage, &mix_identity, delegation_scaled_reward_rate)?; + let mut node_reward = Uint128(0); + let total_delegation_reward = increase_mix_delegated_stakes( + deps.storage, + &mix_identity, + delegation_scaled_reward_rate, + env.block.height, + )?; // update current bond with the reward given to the node and the delegators - current_bond.bond_amount.amount += node_reward; - current_bond.total_delegation.amount += total_delegation_reward; - mixnodes(deps.storage).save(mix_identity.as_bytes(), ¤t_bond)?; + // if it has been bonded for long enough + if current_bond.block_height + MINIMUM_BLOCK_AGE_FOR_REWARDING <= env.block.height { + node_reward = current_bond.bond_amount.amount * bond_scaled_reward_rate; + current_bond.bond_amount.amount += node_reward; + current_bond.total_delegation.amount += total_delegation_reward; + mixnodes(deps.storage).save(mix_identity.as_bytes(), ¤t_bond)?; + } Ok(Response { submessages: vec![], @@ -453,6 +476,7 @@ pub(crate) fn try_reward_mixnode( pub(crate) fn try_reward_gateway( deps: DepsMut, + env: Env, info: MessageInfo, gateway_identity: IdentityKey, uptime: u32, @@ -493,17 +517,22 @@ pub(crate) fn try_reward_gateway( let scaled_bond_reward_rate = scale_reward_by_uptime(bond_reward_rate, uptime)?; let scaled_delegation_reward_rate = scale_reward_by_uptime(delegation_reward_rate, uptime)?; - let node_reward = current_bond.bond_amount.amount * scaled_bond_reward_rate; + let mut node_reward = Uint128(0); let total_delegation_reward = increase_gateway_delegated_stakes( deps.storage, &gateway_identity, scaled_delegation_reward_rate, + env.block.height, )?; // update current bond with the reward given to the node and the delegators - current_bond.bond_amount.amount += node_reward; - current_bond.total_delegation.amount += total_delegation_reward; - gateways(deps.storage).save(gateway_identity.as_bytes(), ¤t_bond)?; + // if it has been bonded for long enough + if current_bond.block_height + MINIMUM_BLOCK_AGE_FOR_REWARDING <= env.block.height { + node_reward = current_bond.bond_amount.amount * scaled_bond_reward_rate; + current_bond.bond_amount.amount += node_reward; + current_bond.total_delegation.amount += total_delegation_reward; + gateways(deps.storage).save(gateway_identity.as_bytes(), ¤t_bond)?; + } Ok(Response { submessages: vec![], @@ -1082,6 +1111,7 @@ pub mod tests { let info = mock_info("fred", &good_mixnode_bond()); try_add_mixnode( deps.as_mut(), + mock_env(), info, MixNode { identity_key: "fredsmixnode".to_string(), @@ -1481,6 +1511,7 @@ pub mod tests { let info = mock_info("fred", &good_gateway_bond()); try_add_gateway( deps.as_mut(), + mock_env(), info, Gateway { identity_key: "fredsgateway".into(), @@ -1773,6 +1804,7 @@ pub mod tests { #[test] fn rewarding_mixnode() { let mut deps = helpers::init_contract(); + let mut env = mock_env(); let current_state = config(deps.as_mut().storage).load().unwrap(); let network_monitor_address = current_state.network_monitor_address; @@ -1781,12 +1813,13 @@ pub mod tests { // errors out if executed by somebody else than network monitor let info = mock_info("not-the-monitor", &[]); - let res = try_reward_mixnode(deps.as_mut(), info, node_identity.clone(), 100); + let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 100); assert_eq!(res, Err(ContractError::Unauthorized)); // returns bond not found attribute if the target owner hasn't bonded any mixnodes let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_mixnode(deps.as_mut(), info, node_identity.clone(), 100).unwrap(); + let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 100) + .unwrap(); assert_eq!(vec![attr("result", "bond not found")], res.attributes); let initial_bond = 100_000000; @@ -1796,6 +1829,7 @@ pub mod tests { total_delegation: coin(initial_delegation, DENOM), owner: node_owner.clone(), layer: Layer::One, + block_height: env.block.height, mix_node: MixNode { identity_key: node_identity.clone(), ..mix_node_fixture() @@ -1807,9 +1841,14 @@ pub mod tests { .unwrap(); mix_delegations(&mut deps.storage, &node_identity) - .save(b"delegator", &raw_delegation_fixture(initial_delegation)) + .save( + b"delegator", + &RawDelegationData::new(initial_delegation.into(), env.block.height), + ) .unwrap(); + env.block.height += 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING; + let bond_reward_rate = read_mixnode_epoch_bond_reward_rate(deps.as_ref().storage); let delegation_reward_rate = read_mixnode_epoch_delegation_reward_rate(deps.as_ref().storage); @@ -1822,7 +1861,8 @@ pub mod tests { let expected_delegation = expected_delegation_reward + Uint128(initial_delegation); let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_mixnode(deps.as_mut(), info, node_identity.clone(), 100).unwrap(); + let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 100) + .unwrap(); assert_eq!( expected_bond, @@ -1850,7 +1890,136 @@ pub mod tests { let expected_delegation = expected_delegation_reward + expected_delegation; let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_mixnode(deps.as_mut(), info, node_identity.clone(), 20).unwrap(); + let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 20) + .unwrap(); + + assert_eq!( + expected_bond, + read_mixnode_bond(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + assert_eq!( + expected_delegation, + read_mixnode_delegation(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + + assert_eq!( + vec![ + attr("bond increase", expected_bond_reward), + attr("total delegation increase", expected_delegation_reward), + ], + res.attributes + ); + } + + #[test] + fn rewarding_mixnode_blockstamp_based() { + let mut deps = helpers::init_contract(); + let mut env = mock_env(); + let current_state = config(deps.as_mut().storage).load().unwrap(); + let network_monitor_address = current_state.network_monitor_address; + + let node_owner: Addr = Addr::unchecked("node-owner"); + let node_identity: IdentityKey = "nodeidentity".into(); + + let initial_bond = 100_000000; + let initial_delegation = 200_000000; + let mixnode_bond = MixNodeBond { + bond_amount: coin(initial_bond, DENOM), + total_delegation: coin(initial_delegation, DENOM), + owner: node_owner.clone(), + layer: Layer::One, + block_height: env.block.height, + mix_node: MixNode { + identity_key: node_identity.clone(), + ..mix_node_fixture() + }, + }; + + mixnodes(deps.as_mut().storage) + .save(node_identity.as_bytes(), &mixnode_bond) + .unwrap(); + + // delegation happens later, but not later enough + env.block.height += MINIMUM_BLOCK_AGE_FOR_REWARDING - 1; + + mix_delegations(&mut deps.storage, &node_identity) + .save( + b"delegator", + &RawDelegationData::new(initial_delegation.into(), env.block.height), + ) + .unwrap(); + + let bond_reward_rate = read_mixnode_epoch_bond_reward_rate(deps.as_ref().storage); + let delegation_reward_rate = + read_mixnode_epoch_delegation_reward_rate(deps.as_ref().storage); + let scaled_bond_reward = scale_reward_by_uptime(bond_reward_rate, 100).unwrap(); + let scaled_delegation_reward = scale_reward_by_uptime(delegation_reward_rate, 100).unwrap(); + + // no reward is due + let expected_bond_reward = Uint128(0); + let expected_delegation_reward = Uint128(0); + let expected_bond = expected_bond_reward + Uint128(initial_bond); + let expected_delegation = expected_delegation_reward + Uint128(initial_delegation); + + let info = mock_info(network_monitor_address.as_ref(), &[]); + let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 100) + .unwrap(); + + assert_eq!( + expected_bond, + read_mixnode_bond(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + assert_eq!( + expected_delegation, + read_mixnode_delegation(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + + assert_eq!( + vec![ + attr("bond increase", expected_bond_reward), + attr("total delegation increase", expected_delegation_reward), + ], + res.attributes + ); + + // reward can happen now, but only for bonded node + env.block.height += 1; + let expected_bond_reward = expected_bond * scaled_bond_reward; + let expected_delegation_reward = Uint128(0); + let expected_bond = expected_bond_reward + expected_bond; + let expected_delegation = expected_delegation_reward + expected_delegation; + + let info = mock_info(network_monitor_address.as_ref(), &[]); + let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 100) + .unwrap(); + + assert_eq!( + expected_bond, + read_mixnode_bond(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + assert_eq!( + expected_delegation, + read_mixnode_delegation(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + + assert_eq!( + vec![ + attr("bond increase", expected_bond_reward), + attr("total delegation increase", expected_delegation_reward), + ], + res.attributes + ); + + // reward happens now, both for node owner and delegators + env.block.height += MINIMUM_BLOCK_AGE_FOR_REWARDING - 1; + let expected_bond_reward = expected_bond * scaled_bond_reward; + let expected_delegation_reward = expected_delegation * scaled_delegation_reward; + let expected_bond = expected_bond_reward + expected_bond; + let expected_delegation = expected_delegation_reward + expected_delegation; + + let info = mock_info(network_monitor_address.as_ref(), &[]); + let res = try_reward_mixnode(deps.as_mut(), env.clone(), info, node_identity.clone(), 100) + .unwrap(); assert_eq!( expected_bond, @@ -1873,6 +2042,7 @@ pub mod tests { #[test] fn rewarding_gateway() { let mut deps = helpers::init_contract(); + let mut env = mock_env(); let current_state = config(deps.as_mut().storage).load().unwrap(); let network_monitor_address = current_state.network_monitor_address; @@ -1881,12 +2051,13 @@ pub mod tests { // errors out if executed by somebody else than network monitor let info = mock_info("not-the-monitor", &[]); - let res = try_reward_gateway(deps.as_mut(), info, node_identity.clone(), 100); + let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 100); assert_eq!(res, Err(ContractError::Unauthorized)); // returns bond not found attribute if the target owner hasn't bonded any gateways let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_gateway(deps.as_mut(), info, node_identity.clone(), 100).unwrap(); + let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 100) + .unwrap(); assert_eq!(vec![attr("result", "bond not found")], res.attributes); let initial_bond = 100_000000; @@ -1895,6 +2066,7 @@ pub mod tests { bond_amount: coin(initial_bond, DENOM), total_delegation: coin(initial_delegation, DENOM), owner: node_owner.clone(), + block_height: env.block.height, gateway: Gateway { identity_key: node_identity.clone(), ..gateway_fixture() @@ -1909,6 +2081,8 @@ pub mod tests { .save(b"delegator", &raw_delegation_fixture(initial_delegation)) .unwrap(); + env.block.height += 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING; + let bond_reward_rate = read_gateway_epoch_bond_reward_rate(deps.as_ref().storage); let delegation_reward_rate = read_gateway_epoch_delegation_reward_rate(deps.as_ref().storage); @@ -1921,7 +2095,8 @@ pub mod tests { let expected_delegation = expected_delegation_reward + Uint128(initial_delegation); let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_gateway(deps.as_mut(), info, node_identity.clone(), 100).unwrap(); + let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 100) + .unwrap(); assert_eq!( expected_bond, @@ -1950,7 +2125,8 @@ pub mod tests { let expected_delegation = expected_delegation_reward + expected_delegation; let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_gateway(deps.as_mut(), info, node_identity.clone(), 20).unwrap(); + let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 20) + .unwrap(); assert_eq!( expected_bond, @@ -1971,6 +2147,133 @@ pub mod tests { ); } + #[test] + fn rewarding_gateway_blockstamp_based() { + let mut deps = helpers::init_contract(); + let mut env = mock_env(); + let current_state = config(deps.as_mut().storage).load().unwrap(); + let network_monitor_address = current_state.network_monitor_address; + + let node_owner: Addr = Addr::unchecked("node-owner"); + let node_identity: IdentityKey = "nodeidentity".into(); + + let initial_bond = 100_000000; + let initial_delegation = 200_000000; + let gateway_bond = GatewayBond { + bond_amount: coin(initial_bond, DENOM), + total_delegation: coin(initial_delegation, DENOM), + owner: node_owner.clone(), + block_height: env.block.height, + gateway: Gateway { + identity_key: node_identity.clone(), + ..gateway_fixture() + }, + }; + + gateways(deps.as_mut().storage) + .save(node_identity.as_bytes(), &gateway_bond) + .unwrap(); + + // delegation happens later, but not later enough + env.block.height += MINIMUM_BLOCK_AGE_FOR_REWARDING - 1; + + gateway_delegations(&mut deps.storage, &node_identity) + .save( + b"delegator", + &RawDelegationData::new(initial_delegation.into(), env.block.height), + ) + .unwrap(); + + let bond_reward_rate = read_gateway_epoch_bond_reward_rate(deps.as_ref().storage); + let delegation_reward_rate = + read_gateway_epoch_delegation_reward_rate(deps.as_ref().storage); + let scaled_bond_reward = scale_reward_by_uptime(bond_reward_rate, 100).unwrap(); + let scaled_delegation_reward = scale_reward_by_uptime(delegation_reward_rate, 100).unwrap(); + + // no reward is due + let expected_bond_reward = Uint128(0); + let expected_delegation_reward = Uint128(0); + let expected_bond = expected_bond_reward + Uint128(initial_bond); + let expected_delegation = expected_delegation_reward + Uint128(initial_delegation); + + let info = mock_info(network_monitor_address.as_ref(), &[]); + let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 100) + .unwrap(); + + assert_eq!( + expected_bond, + read_gateway_bond(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + assert_eq!( + expected_delegation, + read_gateway_delegation(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + + assert_eq!( + vec![ + attr("bond increase", expected_bond_reward), + attr("total delegation increase", expected_delegation_reward), + ], + res.attributes + ); + + // reward can happen now, but only for bonded node + env.block.height += 1; + let expected_bond_reward = expected_bond * scaled_bond_reward; + let expected_delegation_reward = Uint128(0); + let expected_bond = expected_bond_reward + expected_bond; + let expected_delegation = expected_delegation_reward + expected_delegation; + + let info = mock_info(network_monitor_address.as_ref(), &[]); + let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 100) + .unwrap(); + + assert_eq!( + expected_bond, + read_gateway_bond(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + assert_eq!( + expected_delegation, + read_gateway_delegation(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + + assert_eq!( + vec![ + attr("bond increase", expected_bond_reward), + attr("total delegation increase", expected_delegation_reward), + ], + res.attributes + ); + + // reward happens now, both for node owner and delegators + env.block.height += MINIMUM_BLOCK_AGE_FOR_REWARDING - 1; + let expected_bond_reward = expected_bond * scaled_bond_reward; + let expected_delegation_reward = expected_delegation * scaled_delegation_reward; + let expected_bond = expected_bond_reward + expected_bond; + let expected_delegation = expected_delegation_reward + expected_delegation; + + let info = mock_info(network_monitor_address.as_ref(), &[]); + let res = try_reward_gateway(deps.as_mut(), env.clone(), info, node_identity.clone(), 100) + .unwrap(); + + assert_eq!( + expected_bond, + read_gateway_bond(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + assert_eq!( + expected_delegation, + read_gateway_delegation(deps.as_ref().storage, node_identity.as_bytes()).unwrap() + ); + + assert_eq!( + vec![ + attr("bond increase", expected_bond_reward), + attr("total delegation increase", expected_delegation_reward), + ], + res.attributes + ); + } + #[cfg(test)] mod delegation_stake_validation { use super::*; @@ -2623,6 +2926,7 @@ pub mod tests { #[test] fn delegators_on_mix_node_reward_rate() { let mut deps = helpers::init_contract(); + let mut env = mock_env(); let current_state = config(deps.as_mut().storage).load().unwrap(); let network_monitor_address = current_state.network_monitor_address; @@ -2635,15 +2939,26 @@ pub mod tests { let identity = add_mixnode(node_owner, good_mixnode_bond(), &mut deps); mix_delegations(&mut deps.storage, &identity) - .save(b"delegator1", &raw_delegation_fixture(initial_delegation1)) + .save( + b"delegator1", + &RawDelegationData::new(initial_delegation1.into(), env.block.height), + ) .unwrap(); mix_delegations(&mut deps.storage, &identity) - .save(b"delegator2", &raw_delegation_fixture(initial_delegation2)) + .save( + b"delegator2", + &RawDelegationData::new(initial_delegation2.into(), env.block.height), + ) .unwrap(); mix_delegations(&mut deps.storage, &identity) - .save(b"delegator3", &raw_delegation_fixture(initial_delegation3)) + .save( + b"delegator3", + &RawDelegationData::new(initial_delegation3.into(), env.block.height), + ) .unwrap(); + env.block.height += 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING; + let bond_reward = read_mixnode_epoch_bond_reward_rate(deps.as_ref().storage); let delegation_reward = read_mixnode_epoch_delegation_reward_rate(deps.as_ref().storage); @@ -2660,7 +2975,8 @@ pub mod tests { let expected_delegation3 = expected_delegation3_reward + Uint128(initial_delegation3); let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_mixnode(deps.as_mut(), info, identity.clone(), 100).unwrap(); + let res = + try_reward_mixnode(deps.as_mut(), env.clone(), info, identity.clone(), 100).unwrap(); assert_eq!( expected_bond, @@ -2719,7 +3035,8 @@ pub mod tests { let expected_delegation3 = expected_delegation3_reward + expected_delegation3; let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_mixnode(deps.as_mut(), info, identity.clone(), 20).unwrap(); + let res = + try_reward_mixnode(deps.as_mut(), env.clone(), info, identity.clone(), 20).unwrap(); assert_eq!( expected_bond, @@ -2765,7 +3082,8 @@ pub mod tests { // if the node was 0% up, nobody will get any rewards let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_mixnode(deps.as_mut(), info, identity.clone(), 0).unwrap(); + let res = + try_reward_mixnode(deps.as_mut(), env.clone(), info, identity.clone(), 0).unwrap(); assert_eq!( expected_bond, @@ -3411,6 +3729,7 @@ pub mod tests { #[test] fn delegators_on_gateway_reward_rate() { let mut deps = helpers::init_contract(); + let mut env = mock_env(); let current_state = config(deps.as_mut().storage).load().unwrap(); let network_monitor_address = current_state.network_monitor_address; @@ -3432,6 +3751,8 @@ pub mod tests { .save(b"delegator3", &raw_delegation_fixture(initial_delegation3)) .unwrap(); + env.block.height += 2 * MINIMUM_BLOCK_AGE_FOR_REWARDING; + let bond_reward = read_gateway_epoch_bond_reward_rate(deps.as_ref().storage); let delegation_reward = read_gateway_epoch_delegation_reward_rate(deps.as_ref().storage); @@ -3448,7 +3769,8 @@ pub mod tests { let expected_delegation3 = expected_delegation3_reward + Uint128(initial_delegation3); let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_gateway(deps.as_mut(), info, identity.clone(), 100).unwrap(); + let res = + try_reward_gateway(deps.as_mut(), env.clone(), info, identity.clone(), 100).unwrap(); assert_eq!( expected_bond, @@ -3507,7 +3829,8 @@ pub mod tests { let expected_delegation3 = expected_delegation3_reward + expected_delegation3; let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_gateway(deps.as_mut(), info, identity.clone(), 20).unwrap(); + let res = + try_reward_gateway(deps.as_mut(), env.clone(), info, identity.clone(), 20).unwrap(); assert_eq!( expected_bond, @@ -3553,7 +3876,8 @@ pub mod tests { // if the node was 0% up, nobody will get any rewards let info = mock_info(network_monitor_address.as_ref(), &[]); - let res = try_reward_gateway(deps.as_mut(), info, identity.clone(), 0).unwrap(); + let res = + try_reward_gateway(deps.as_mut(), env.clone(), info, identity.clone(), 0).unwrap(); assert_eq!( expected_bond, @@ -3651,6 +3975,7 @@ pub mod tests { for owner in ["alice", "bob"] { try_add_mixnode( deps.as_mut(), + mock_env(), mock_info(owner, &good_mixnode_bond()), MixNode { identity_key: owner.to_string(), From c9dce0c1da29a99b1c483009a0658b2236c3a4e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogdan-=C8=98tefan=20Neac=C5=9Fu?= Date: Thu, 9 Sep 2021 11:21:45 +0200 Subject: [PATCH 22/28] Feature/consumable bandwidth (#766) * Set actual value for bandwidth Also put it as a public attribute, such that it can be actively used by the credential consumer * Switch from sending Attribute structs to sending the actual attribute bytes over the wire * Add atomic bandwidth value to gateway * Consume bandwidth based on the mix packet size * Use Bandwidth struct for specific functionality * Move bandwidth code outside the dependency path of wasm client * Use u64 instead of AtomicU64, as the handling is not parallel --- Cargo.lock | 1 + clients/tauri-client/src-tauri/src/main.rs | 22 +++-- common/coconut-interface/src/lib.rs | 28 +++--- common/credentials/src/bandwidth.rs | 17 ++-- common/credentials/src/error.rs | 12 +++ common/credentials/src/utils.rs | 16 ++-- gateway/gateway-requests/Cargo.toml | 1 + gateway/src/node/client_handling/bandwidth.rs | 85 +++++++++++++++++++ gateway/src/node/client_handling/mod.rs | 1 + .../websocket/connection_handler.rs | 55 +++++++++--- .../client_handling/websocket/listener.rs | 4 + .../src/node/client_handling/websocket/mod.rs | 4 +- 12 files changed, 196 insertions(+), 50 deletions(-) create mode 100644 gateway/src/node/client_handling/bandwidth.rs diff --git a/Cargo.lock b/Cargo.lock index 50d7be17ca..e13f354df6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1873,6 +1873,7 @@ dependencies = [ "bincode", "bs58", "coconut-interface", + "credentials", "crypto", "futures", "log", diff --git a/clients/tauri-client/src-tauri/src/main.rs b/clients/tauri-client/src-tauri/src/main.rs index bef090400f..9e065821fa 100644 --- a/clients/tauri-client/src-tauri/src/main.rs +++ b/clients/tauri-client/src-tauri/src/main.rs @@ -4,7 +4,7 @@ )] use coconut_interface::{ - self, Attribute, Credential, Parameters, Signature, Theta, VerificationKey, + self, hash_to_scalar, Attribute, Credential, Parameters, Signature, Theta, VerificationKey, }; use credentials::{obtain_aggregate_signature, obtain_aggregate_verification_key}; use std::sync::Arc; @@ -15,19 +15,29 @@ struct State { signatures: Vec, n_attributes: u32, params: Parameters, + public_attributes_bytes: Vec>, public_attributes: Vec, private_attributes: Vec, aggregated_verification_key: Option, } impl State { - fn init(public_attributes: Vec, private_attributes: Vec) -> State { - let n_attributes = (public_attributes.len() + private_attributes.len()) as u32; + fn init(public_attributes_bytes: Vec>, private_attributes_bytes: Vec>) -> State { + let n_attributes = (public_attributes_bytes.len() + private_attributes_bytes.len()) as u32; let params = Parameters::new(n_attributes).unwrap(); + let public_attributes = public_attributes_bytes + .iter() + .map(hash_to_scalar) + .collect::>(); + let private_attributes = private_attributes_bytes + .iter() + .map(hash_to_scalar) + .collect::>(); State { signatures: Vec::new(), n_attributes, params, + public_attributes_bytes, public_attributes, private_attributes, aggregated_verification_key: None, @@ -137,7 +147,7 @@ async fn verify_credential( let credential = Credential::new( state.n_attributes, theta, - &state.public_attributes, + state.public_attributes_bytes.clone(), state .signatures .get(idx) @@ -170,8 +180,8 @@ async fn get_credential( } fn main() { - let public_attributes = vec![coconut_interface::hash_to_scalar("public_key")]; - let private_attributes = vec![coconut_interface::hash_to_scalar("private_key")]; + let public_attributes = vec![b"public_key".to_vec()]; + let private_attributes = vec![b"private_key".to_vec()]; tauri::Builder::default() .manage(Arc::new(RwLock::new(State::init( public_attributes, diff --git a/common/coconut-interface/src/lib.rs b/common/coconut-interface/src/lib.rs index 8e1d4f8969..3d0a09881d 100644 --- a/common/coconut-interface/src/lib.rs +++ b/common/coconut-interface/src/lib.rs @@ -12,7 +12,7 @@ pub struct Credential { n_params: u32, #[getset(get = "pub")] theta: Theta, - public_attributes: Vec, + public_attributes: Vec>, #[getset(get = "pub")] signature: Signature, } @@ -20,35 +20,29 @@ impl Credential { pub fn new( n_params: u32, theta: Theta, - public_attributes: &[Attribute], + public_attributes: Vec>, signature: &Signature, ) -> Credential { Credential { n_params, theta, - public_attributes: public_attributes - .iter() - .map(|attr| attr.to_bs58()) - .collect(), + public_attributes, signature: *signature, } } - pub fn public_attributes(&self) -> Vec { - self.public_attributes - .iter() - .map(|x| Attribute::try_from_bs58(x).unwrap()) - .collect() + pub fn public_attributes(&self) -> Vec> { + self.public_attributes.clone() } pub fn verify(&self, verification_key: &VerificationKey) -> bool { let params = Parameters::new(self.n_params).unwrap(); - coconut_rs::verify_credential( - ¶ms, - verification_key, - &self.theta, - &self.public_attributes(), - ) + let public_attributes = self + .public_attributes + .iter() + .map(hash_to_scalar) + .collect::>(); + coconut_rs::verify_credential(¶ms, verification_key, &self.theta, &public_attributes) } } diff --git a/common/credentials/src/bandwidth.rs b/common/credentials/src/bandwidth.rs index 66096f732b..0dff055a30 100644 --- a/common/credentials/src/bandwidth.rs +++ b/common/credentials/src/bandwidth.rs @@ -6,12 +6,13 @@ // right now this has no double-spending protection, spender binding, etc // it's the simplest possible case +use url::Url; + use crate::error::Error; use crate::utils::{obtain_aggregate_signature, prepare_credential_for_spending}; use coconut_interface::{hash_to_scalar, Credential, Parameters, Signature, VerificationKey}; -use url::Url; -const BANDWIDTH_VALUE: &str = "Bandwidth: infinite (for now)"; +const BANDWIDTH_VALUE: u64 = 1024 * 1024; // 1 MB pub const PUBLIC_ATTRIBUTES: u32 = 1; pub const PRIVATE_ATTRIBUTES: u32 = 1; @@ -19,8 +20,8 @@ pub const TOTAL_ATTRIBUTES: u32 = PUBLIC_ATTRIBUTES + PRIVATE_ATTRIBUTES; // TODO: this definitely has to be moved somewhere else. It's just a temporary solution pub async fn obtain_signature(raw_identity: &[u8], validators: &[Url]) -> Result { - let public_attributes = vec![hash_to_scalar(raw_identity)]; - let private_attributes = vec![hash_to_scalar(BANDWIDTH_VALUE)]; + let public_attributes = vec![hash_to_scalar(BANDWIDTH_VALUE.to_be_bytes())]; + let private_attributes = vec![hash_to_scalar(raw_identity)]; let params = Parameters::new(TOTAL_ATTRIBUTES)?; @@ -32,15 +33,15 @@ pub fn prepare_for_spending( signature: &Signature, verification_key: &VerificationKey, ) -> Result { - let public_attributes = vec![hash_to_scalar(raw_identity)]; - let private_attributes = vec![hash_to_scalar(BANDWIDTH_VALUE)]; + let public_attributes = vec![BANDWIDTH_VALUE.to_be_bytes().to_vec()]; + let private_attributes = vec![raw_identity.to_vec()]; let params = Parameters::new(TOTAL_ATTRIBUTES)?; prepare_credential_for_spending( ¶ms, - &public_attributes, - &private_attributes, + public_attributes, + private_attributes, signature, verification_key, ) diff --git a/common/credentials/src/error.rs b/common/credentials/src/error.rs index 552d346d6d..f2161ca9ac 100644 --- a/common/credentials/src/error.rs +++ b/common/credentials/src/error.rs @@ -18,4 +18,16 @@ pub enum Error { #[error("Run into a validato client error - {0}")] ValidatorClientError(#[from] ValidatorClientError), + + #[error("Not enough public attributes were specified")] + NotEnoughPublicAttributes, + + #[error("Bandwidth is expected to be represented on 8 bytes")] + InvalidBandwidthSize, + + #[error("Bandwidth operation overflowed. {0}")] + BandwidthOverflow(String), + + #[error("There is not associated bandwidth for the given client")] + MissingBandwidth, } diff --git a/common/credentials/src/utils.rs b/common/credentials/src/utils.rs index d12ab4decc..b4f7438f74 100644 --- a/common/credentials/src/utils.rs +++ b/common/credentials/src/utils.rs @@ -3,9 +3,9 @@ use crate::error::Error; use coconut_interface::{ - aggregate_signature_shares, aggregate_verification_keys, prepare_blind_sign, prove_credential, - Attribute, BlindSignRequestBody, Credential, Parameters, Signature, SignatureShare, - VerificationKey, + aggregate_signature_shares, aggregate_verification_keys, hash_to_scalar, prepare_blind_sign, + prove_credential, Attribute, BlindSignRequestBody, Credential, Parameters, Signature, + SignatureShare, VerificationKey, }; use url::Url; @@ -118,12 +118,16 @@ pub async fn obtain_aggregate_signature( // TODO: better type flow pub fn prepare_credential_for_spending( params: &Parameters, - public_attributes: &[Attribute], - private_attributes: &[Attribute], + public_attributes: Vec>, + private_attributes: Vec>, signature: &Signature, verification_key: &VerificationKey, ) -> Result { - let theta = prove_credential(params, verification_key, signature, private_attributes)?; + let private_attributes = private_attributes + .iter() + .map(hash_to_scalar) + .collect::>(); + let theta = prove_credential(params, verification_key, signature, &private_attributes)?; Ok(Credential::new( (public_attributes.len() + private_attributes.len()) as u32, diff --git a/gateway/gateway-requests/Cargo.toml b/gateway/gateway-requests/Cargo.toml index e9d28094ae..442d9837f0 100644 --- a/gateway/gateway-requests/Cargo.toml +++ b/gateway/gateway-requests/Cargo.toml @@ -24,6 +24,7 @@ crypto = { path = "../../common/crypto" } pemstore = { path = "../../common/pemstore" } coconut-interface = { path = "../../common/coconut-interface" } +credentials = { path = "../../common/credentials"} [dependencies.tungstenite] version = "0.13.0" diff --git a/gateway/src/node/client_handling/bandwidth.rs b/gateway/src/node/client_handling/bandwidth.rs new file mode 100644 index 0000000000..203b75fd0a --- /dev/null +++ b/gateway/src/node/client_handling/bandwidth.rs @@ -0,0 +1,85 @@ +// Copyright 2021 - Nym Technologies SA +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::HashMap; +use std::convert::TryFrom; +use std::sync::Arc; +use tokio::sync::RwLock; + +use coconut_interface::Credential; +use credentials::error::Error; +use nymsphinx::DestinationAddressBytes; + +const BANDWIDTH_INDEX: usize = 0; + +pub type BandwidthDatabase = Arc>>; + +pub fn empty_bandwidth_database() -> BandwidthDatabase { + Arc::new(RwLock::new(HashMap::new())) +} + +pub struct Bandwidth { + value: u64, +} + +impl Bandwidth { + pub fn value(&self) -> u64 { + self.value + } + + pub async fn consume_bandwidth( + bandwidths: &BandwidthDatabase, + remote_address: &DestinationAddressBytes, + consumed: u64, + ) -> Result<(), Error> { + if let Some(bandwidth) = bandwidths.write().await.get_mut(remote_address) { + if let Some(res) = bandwidth.checked_sub(consumed) { + *bandwidth = res; + Ok(()) + } else { + Err(Error::BandwidthOverflow(String::from( + "Allocate more bandwidth for consumption", + ))) + } + } else { + Err(Error::MissingBandwidth) + } + } + + pub async fn increase_bandwidth( + bandwidths: &BandwidthDatabase, + remote_address: &DestinationAddressBytes, + increase: u64, + ) -> Result<(), Error> { + let mut db = bandwidths.write().await; + if let Some(bandwidth) = db.get_mut(remote_address) { + if let Some(new_bandwidth) = bandwidth.checked_add(increase) { + *bandwidth = new_bandwidth; + } else { + return Err(Error::BandwidthOverflow(String::from( + "Use some of the already allocated bandwidth", + ))); + } + } else { + db.insert(*remote_address, increase); + } + Ok(()) + } +} + +impl TryFrom for Bandwidth { + type Error = Error; + + fn try_from(credential: Credential) -> Result { + match credential.public_attributes().get(BANDWIDTH_INDEX) { + None => Err(Error::NotEnoughPublicAttributes), + Some(attr) => match <[u8; 8]>::try_from(attr.as_slice()) { + Ok(bandwidth_bytes) => { + let value = u64::from_be_bytes(bandwidth_bytes); + Ok(Self { value }) + } + Err(_) => Err(Error::InvalidBandwidthSize), + }, + } + } +} diff --git a/gateway/src/node/client_handling/mod.rs b/gateway/src/node/client_handling/mod.rs index 9ad982e49b..8e8013fb93 100644 --- a/gateway/src/node/client_handling/mod.rs +++ b/gateway/src/node/client_handling/mod.rs @@ -1,5 +1,6 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +mod bandwidth; pub(crate) mod clients_handler; pub(crate) mod websocket; diff --git a/gateway/src/node/client_handling/websocket/connection_handler.rs b/gateway/src/node/client_handling/websocket/connection_handler.rs index 9cf55b0e31..dc81c5b1dd 100644 --- a/gateway/src/node/client_handling/websocket/connection_handler.rs +++ b/gateway/src/node/client_handling/websocket/connection_handler.rs @@ -1,6 +1,7 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::node::client_handling::bandwidth::{Bandwidth, BandwidthDatabase}; use crate::node::client_handling::clients_handler::{ ClientsHandlerRequest, ClientsHandlerRequestSender, ClientsHandlerResponse, }; @@ -24,6 +25,7 @@ use mixnet_client::forwarder::MixForwardingSender; use nymsphinx::DestinationAddressBytes; use rand::{CryptoRng, Rng}; use std::convert::TryFrom; +use std::mem; use std::sync::Arc; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_tungstenite::{ @@ -53,14 +55,13 @@ pub(crate) struct Handle { rng: R, remote_address: Option, shared_key: Option, - // TODO: This should be replaced by an actual bandwidth value, with 0 meaning no bandwidth - has_bandwidth: bool, clients_handler_sender: ClientsHandlerRequestSender, outbound_mix_sender: MixForwardingSender, socket_connection: SocketStream, local_identity: Arc, aggregated_verification_key: VerificationKey, + bandwidths: BandwidthDatabase, } impl Handle @@ -76,17 +77,18 @@ where outbound_mix_sender: MixForwardingSender, local_identity: Arc, aggregated_verification_key: VerificationKey, + bandwidths: BandwidthDatabase, ) -> Self { Handle { rng, remote_address: None, shared_key: None, - has_bandwidth: false, clients_handler_sender, outbound_mix_sender, socket_connection: SocketStream::RawTcp(conn), local_identity, aggregated_verification_key, + bandwidths, } } @@ -194,9 +196,6 @@ where } async fn handle_binary(&self, bin_msg: Vec) -> Message { - if !self.has_bandwidth { - return ServerResponse::new_error("Not enough bandwidth").into(); - } trace!("Handling binary message (presumably sphinx packet)"); // this function decrypts the request and checks the MAC @@ -210,8 +209,19 @@ where Ok(request) => match request { // currently only a single type exists BinaryRequest::ForwardSphinx(mix_packet) => { - self.outbound_mix_sender.unbounded_send(mix_packet).unwrap(); - ServerResponse::Send { status: true } + let consumed_bandwidth = mem::size_of_val(&mix_packet) as u64; + if let Err(e) = Bandwidth::consume_bandwidth( + &self.bandwidths, + &self.remote_address.unwrap(), + consumed_bandwidth, + ) + .await + { + ServerResponse::new_error(format!("{:?}", e)) + } else { + self.outbound_mix_sender.unbounded_send(mix_packet).unwrap(); + ServerResponse::Send { status: true } + } } }, } @@ -351,6 +361,9 @@ where if self.shared_key.is_none() { return ServerResponse::new_error("No shared key has been exchanged with the gateway"); } + if self.remote_address.is_none() { + return ServerResponse::new_error("No remote address has been set"); + } let iv = match IV::try_from_bytes(&iv) { Ok(iv) => iv, Err(e) => { @@ -368,9 +381,29 @@ where return ServerResponse::new_error(e.to_string()); } }; - let status = credential.verify(&self.aggregated_verification_key); - self.has_bandwidth = status; - ServerResponse::Bandwidth { status } + if credential.verify(&self.aggregated_verification_key) { + match Bandwidth::try_from(credential) { + Ok(bandwidth) => { + if let Err(e) = Bandwidth::increase_bandwidth( + &self.bandwidths, + &self.remote_address.unwrap(), + bandwidth.value(), + ) + .await + { + return ServerResponse::Error { + message: format!("{:?}", e), + }; + } + ServerResponse::Bandwidth { status: true } + } + Err(e) => ServerResponse::Error { + message: format!("{:?}", e), + }, + } + } else { + ServerResponse::Bandwidth { status: false } + } } // currently the bandwidth credential request is the only one we can receive after diff --git a/gateway/src/node/client_handling/websocket/listener.rs b/gateway/src/node/client_handling/websocket/listener.rs index 7978e77a93..fa1c26aaa6 100644 --- a/gateway/src/node/client_handling/websocket/listener.rs +++ b/gateway/src/node/client_handling/websocket/listener.rs @@ -1,6 +1,7 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +use crate::node::client_handling::bandwidth::empty_bandwidth_database; use crate::node::client_handling::clients_handler::ClientsHandlerRequestSender; use crate::node::client_handling::websocket::connection_handler::Handle; use coconut_interface::VerificationKey; @@ -46,6 +47,8 @@ impl Listener { } }; + let bandwidths = empty_bandwidth_database(); + loop { match tcp_listener.accept().await { Ok((socket, remote_addr)) => { @@ -59,6 +62,7 @@ impl Listener { outbound_mix_sender.clone(), Arc::clone(&self.local_identity), self.aggregated_verification_key.clone(), + Arc::clone(&bandwidths), ); tokio::spawn(async move { handle.start_handling().await }); } diff --git a/gateway/src/node/client_handling/websocket/mod.rs b/gateway/src/node/client_handling/websocket/mod.rs index b9920c505f..df45a32a69 100644 --- a/gateway/src/node/client_handling/websocket/mod.rs +++ b/gateway/src/node/client_handling/websocket/mod.rs @@ -1,8 +1,8 @@ // Copyright 2020 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 +pub(crate) use listener::Listener; + pub(crate) mod connection_handler; pub(crate) mod listener; pub(crate) mod message_receiver; - -pub(crate) use listener::Listener; From 0b9c03ca900a834856efa37706dafac7685b5f79 Mon Sep 17 00:00:00 2001 From: Dave Hrycyszyn Date: Fri, 10 Sep 2021 11:23:21 +0300 Subject: [PATCH 23/28] Adding deps for building the Tauri wallet under Ubuntu (#770) --- clients/tauri-client/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/clients/tauri-client/README.md b/clients/tauri-client/README.md index 865d0c6426..849b049db4 100644 --- a/clients/tauri-client/README.md +++ b/clients/tauri-client/README.md @@ -1,3 +1,19 @@ +## Prerequisites + +On Ubuntu-ish systems (not tested on Debian but may work): + +``` +sudo apt update && sudo apt install libwebkit2gtk-4.0-dev \ + build-essential \ + curl \ + wget \ + libssl-dev \ + libgtk-3-dev \ + libappindicator3-dev \ + patchelf \ + librsvg2-dev``` + + ## Getting started From 66ab5de442de91a50556117b62ef028b6b249f6f Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Fri, 10 Sep 2021 16:16:58 +0100 Subject: [PATCH 24/28] add app alert --- wallet-web/components/bond/BondNodeForm.tsx | 10 ++++++++++ wallet-web/pages/_app.tsx | 6 +++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/wallet-web/components/bond/BondNodeForm.tsx b/wallet-web/components/bond/BondNodeForm.tsx index 18d208abc7..a0d275c119 100644 --- a/wallet-web/components/bond/BondNodeForm.tsx +++ b/wallet-web/components/bond/BondNodeForm.tsx @@ -43,6 +43,7 @@ export const BondNodeForm = ({ {type === NodeType.Gateway && ( ) => manageForm.handlePortChange('mixPort', e.target.value) @@ -199,6 +206,7 @@ export const BondNodeForm = ({ <> ) => manageForm.handlePortChange('verlocPort', e.target.value) @@ -214,6 +222,7 @@ export const BondNodeForm = ({ ) => manageForm.handlePortChange('httpApiPort', e.target.value) @@ -230,6 +239,7 @@ export const BondNodeForm = ({ ) : ( ) => manageForm.handlePortChange('clientsPort', e.target.value) diff --git a/wallet-web/pages/_app.tsx b/wallet-web/pages/_app.tsx index 0cce398e52..07dc9cf534 100644 --- a/wallet-web/pages/_app.tsx +++ b/wallet-web/pages/_app.tsx @@ -24,7 +24,6 @@ export default function Application(props: AppProps) { const { Component, pageProps } = props const [client, setClient] = useState(null) - const [showAlert, setShowAlert] = useState(true) React.useEffect(() => { const jssStyles = document.querySelector('#jss-server-side') @@ -46,6 +45,11 @@ export default function Application(props: AppProps) { + From cba3625394dae3f736dab23018df5fcc3c9a4f5d Mon Sep 17 00:00:00 2001 From: Mark Sinclair Date: Mon, 13 Sep 2021 10:33:41 +0100 Subject: [PATCH 25/28] explorer-api: add API resource to show the delegations for each mix node --- explorer-api/Cargo.toml | 2 +- explorer-api/src/mix_node/http.rs | 10 ++++++++-- explorer-api/src/mix_nodes/mod.rs | 33 +++++++++++++++++++++++++++++-- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/explorer-api/Cargo.toml b/explorer-api/Cargo.toml index c5906c376a..cf97221de4 100644 --- a/explorer-api/Cargo.toml +++ b/explorer-api/Cargo.toml @@ -23,4 +23,4 @@ pretty_env_logger = "0.4.0" mixnet-contract = { path = "../common/mixnet-contract" } network-defaults = { path = "../common/network-defaults" } -validator-client = { path = "../common/client-libs/validator-client" } +validator-client = { path = "../common/client-libs/validator-client", features=["nymd-client"] } diff --git a/explorer-api/src/mix_node/http.rs b/explorer-api/src/mix_node/http.rs index 20b5c2434c..56cb5ba848 100644 --- a/explorer-api/src/mix_node/http.rs +++ b/explorer-api/src/mix_node/http.rs @@ -6,11 +6,11 @@ use serde::Serialize; use mixnet_contract::{Addr, Coin, Layer, MixNode}; use crate::mix_node::models::{NodeDescription, NodeStats}; -use crate::mix_nodes::Location; +use crate::mix_nodes::{get_mixnode_delegations, Location}; use crate::state::ExplorerApiStateContext; pub fn mix_node_make_default_routes() -> Vec { - routes_with_openapi![get_description, get_stats, list] + routes_with_openapi![get_delegations, get_description, get_stats, list] } #[derive(Clone, Debug, Serialize, JsonSchema)] @@ -51,6 +51,12 @@ pub(crate) async fn list( ) } +#[openapi(tag = "mix_node")] +#[get("//delegations")] +pub(crate) async fn get_delegations(pubkey: &str) -> Json> { + Json(get_mixnode_delegations(pubkey).await) +} + #[openapi(tag = "mix_node")] #[get("//description")] pub(crate) async fn get_description( diff --git a/explorer-api/src/mix_nodes/mod.rs b/explorer-api/src/mix_nodes/mod.rs index 9ed89fc4d0..2c82ef22d9 100644 --- a/explorer-api/src/mix_nodes/mod.rs +++ b/explorer-api/src/mix_nodes/mod.rs @@ -8,8 +8,11 @@ use rocket::tokio::sync::RwLock; use serde::{Deserialize, Serialize}; use crate::mix_nodes::utils::map_2_letter_to_3_letter_country_code; -use mixnet_contract::MixNodeBond; -use network_defaults::default_api_endpoints; +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; @@ -163,6 +166,32 @@ pub(crate) async fn retrieve_mixnodes() -> Vec { bonds } +pub(crate) async fn get_mixnode_delegations(pubkey: &str) -> Vec { + let client = new_nymd_client(); + let delegates = match client + .get_all_nymd_mixnode_delegations(pubkey.to_string()) + .await + { + Ok(result) => result, + Err(e) => { + error!("Could not get delegations for mix node {}: {:?}", pubkey, 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())); + + 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()) From e84af4f6018772feb4896b31702f2d33ee4c1fb1 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Tue, 14 Sep 2021 15:15:26 +0200 Subject: [PATCH 26/28] Migrate legacy delegation data (#771) * Skip ReadOnlyBucket deserialization errors * empty migration * clippy * cargo schema * Drop invalid delegation data * Dont drop old data * Add todo * Unify on type param * gateways are different * cargo fmt Co-authored-by: Drazen Urch --- contracts/mixnet/Cargo.lock | 4 +- contracts/mixnet/schema/execute_msg.json | 349 ++++++++++++++++++ contracts/mixnet/schema/handle_msg.json | 134 ------- .../{init_msg.json => instantiate_msg.json} | 2 +- contracts/mixnet/schema/mix_node_bond.json | 64 +++- contracts/mixnet/schema/query_msg.json | 273 +++++++++++++- contracts/mixnet/schema/state.json | 82 +++- contracts/mixnet/src/contract.rs | 63 +++- contracts/mixnet/src/storage.rs | 20 + contracts/mixnet/src/transactions.rs | 58 ++- 10 files changed, 851 insertions(+), 198 deletions(-) create mode 100644 contracts/mixnet/schema/execute_msg.json delete mode 100644 contracts/mixnet/schema/handle_msg.json rename contracts/mixnet/schema/{init_msg.json => instantiate_msg.json} (73%) diff --git a/contracts/mixnet/Cargo.lock b/contracts/mixnet/Cargo.lock index ed361dcf24..b36c6fb5a5 100644 --- a/contracts/mixnet/Cargo.lock +++ b/contracts/mixnet/Cargo.lock @@ -727,9 +727,9 @@ checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" [[package]] name = "syn" -version = "1.0.60" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663" dependencies = [ "proc-macro2", "quote", diff --git a/contracts/mixnet/schema/execute_msg.json b/contracts/mixnet/schema/execute_msg.json new file mode 100644 index 0000000000..b329acb2a8 --- /dev/null +++ b/contracts/mixnet/schema/execute_msg.json @@ -0,0 +1,349 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ExecuteMsg", + "anyOf": [ + { + "type": "object", + "required": [ + "bond_mixnode" + ], + "properties": { + "bond_mixnode": { + "type": "object", + "required": [ + "mix_node" + ], + "properties": { + "mix_node": { + "$ref": "#/definitions/MixNode" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unbond_mixnode" + ], + "properties": { + "unbond_mixnode": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "bond_gateway" + ], + "properties": { + "bond_gateway": { + "type": "object", + "required": [ + "gateway" + ], + "properties": { + "gateway": { + "$ref": "#/definitions/Gateway" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "unbond_gateway" + ], + "properties": { + "unbond_gateway": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "update_state_params" + ], + "properties": { + "update_state_params": { + "$ref": "#/definitions/StateParams" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "delegate_to_mixnode" + ], + "properties": { + "delegate_to_mixnode": { + "type": "object", + "required": [ + "mix_identity" + ], + "properties": { + "mix_identity": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "undelegate_from_mixnode" + ], + "properties": { + "undelegate_from_mixnode": { + "type": "object", + "required": [ + "mix_identity" + ], + "properties": { + "mix_identity": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "delegate_to_gateway" + ], + "properties": { + "delegate_to_gateway": { + "type": "object", + "required": [ + "gateway_identity" + ], + "properties": { + "gateway_identity": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "undelegate_from_gateway" + ], + "properties": { + "undelegate_from_gateway": { + "type": "object", + "required": [ + "gateway_identity" + ], + "properties": { + "gateway_identity": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "reward_mixnode" + ], + "properties": { + "reward_mixnode": { + "type": "object", + "required": [ + "identity", + "uptime" + ], + "properties": { + "identity": { + "type": "string" + }, + "uptime": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "reward_gateway" + ], + "properties": { + "reward_gateway": { + "type": "object", + "required": [ + "identity", + "uptime" + ], + "properties": { + "identity": { + "type": "string" + }, + "uptime": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + } + }, + "additionalProperties": false + } + ], + "definitions": { + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "Gateway": { + "type": "object", + "required": [ + "clients_port", + "host", + "identity_key", + "location", + "mix_port", + "sphinx_key", + "version" + ], + "properties": { + "clients_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "host": { + "type": "string" + }, + "identity_key": { + "description": "Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients", + "type": "string" + }, + "location": { + "type": "string" + }, + "mix_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "MixNode": { + "type": "object", + "required": [ + "host", + "http_api_port", + "identity_key", + "mix_port", + "sphinx_key", + "verloc_port", + "version" + ], + "properties": { + "host": { + "type": "string" + }, + "http_api_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "identity_key": { + "description": "Base58 encoded ed25519 EdDSA public key.", + "type": "string" + }, + "mix_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "sphinx_key": { + "type": "string" + }, + "verloc_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, + "version": { + "type": "string" + } + } + }, + "StateParams": { + "type": "object", + "required": [ + "epoch_length", + "gateway_bond_reward_rate", + "gateway_delegation_reward_rate", + "minimum_gateway_bond", + "minimum_mixnode_bond", + "mixnode_active_set_size", + "mixnode_bond_reward_rate", + "mixnode_delegation_reward_rate" + ], + "properties": { + "epoch_length": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "gateway_bond_reward_rate": { + "$ref": "#/definitions/Decimal" + }, + "gateway_delegation_reward_rate": { + "$ref": "#/definitions/Decimal" + }, + "minimum_gateway_bond": { + "$ref": "#/definitions/Uint128" + }, + "minimum_mixnode_bond": { + "$ref": "#/definitions/Uint128" + }, + "mixnode_active_set_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "mixnode_bond_reward_rate": { + "$ref": "#/definitions/Decimal" + }, + "mixnode_delegation_reward_rate": { + "$ref": "#/definitions/Decimal" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/contracts/mixnet/schema/handle_msg.json b/contracts/mixnet/schema/handle_msg.json deleted file mode 100644 index 8e3729ceb6..0000000000 --- a/contracts/mixnet/schema/handle_msg.json +++ /dev/null @@ -1,134 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "HandleMsg", - "anyOf": [ - { - "type": "object", - "required": [ - "register_mixnode" - ], - "properties": { - "register_mixnode": { - "type": "object", - "required": [ - "mix_node" - ], - "properties": { - "mix_node": { - "$ref": "#/definitions/MixNode" - } - } - } - } - }, - { - "type": "object", - "required": [ - "un_register_mixnode" - ], - "properties": { - "un_register_mixnode": { - "type": "object" - } - } - }, - { - "type": "object", - "required": [ - "bond_gateway" - ], - "properties": { - "bond_gateway": { - "type": "object", - "required": [ - "gateway" - ], - "properties": { - "gateway": { - "$ref": "#/definitions/Gateway" - } - } - } - } - }, - { - "type": "object", - "required": [ - "unbond_gateway" - ], - "properties": { - "unbond_gateway": { - "type": "object" - } - } - } - ], - "definitions": { - "Gateway": { - "type": "object", - "required": [ - "clients_host", - "identity_key", - "location", - "mix_host", - "sphinx_key", - "version" - ], - "properties": { - "clients_host": { - "type": "string" - }, - "identity_key": { - "description": "Base58 encoded ed25519 EdDSA public key of the gateway used to derive shared keys with clients", - "type": "string" - }, - "location": { - "type": "string" - }, - "mix_host": { - "type": "string" - }, - "sphinx_key": { - "type": "string" - }, - "version": { - "type": "string" - } - } - }, - "MixNode": { - "type": "object", - "required": [ - "host", - "identity_key", - "layer", - "location", - "sphinx_key", - "version" - ], - "properties": { - "host": { - "type": "string" - }, - "identity_key": { - "description": "Base58 encoded ed25519 EdDSA public key.", - "type": "string" - }, - "layer": { - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "location": { - "type": "string" - }, - "sphinx_key": { - "type": "string" - }, - "version": { - "type": "string" - } - } - } - } -} diff --git a/contracts/mixnet/schema/init_msg.json b/contracts/mixnet/schema/instantiate_msg.json similarity index 73% rename from contracts/mixnet/schema/init_msg.json rename to contracts/mixnet/schema/instantiate_msg.json index 2b274b4e6d..44588cf226 100644 --- a/contracts/mixnet/schema/init_msg.json +++ b/contracts/mixnet/schema/instantiate_msg.json @@ -1,5 +1,5 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InitMsg", + "title": "InstantiateMsg", "type": "object" } diff --git a/contracts/mixnet/schema/mix_node_bond.json b/contracts/mixnet/schema/mix_node_bond.json index bc6e6c97a2..21c52482ec 100644 --- a/contracts/mixnet/schema/mix_node_bond.json +++ b/contracts/mixnet/schema/mix_node_bond.json @@ -3,25 +3,40 @@ "title": "MixNodeBond", "type": "object", "required": [ - "amount", + "bond_amount", + "layer", "mix_node", - "owner" + "owner", + "total_delegation" ], "properties": { - "amount": { - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } + "block_height": { + "default": 0, + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "bond_amount": { + "$ref": "#/definitions/Coin" + }, + "layer": { + "$ref": "#/definitions/Layer" }, "mix_node": { "$ref": "#/definitions/MixNode" }, "owner": { - "$ref": "#/definitions/HumanAddr" + "$ref": "#/definitions/Addr" + }, + "total_delegation": { + "$ref": "#/definitions/Coin" } }, "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, "Coin": { "type": "object", "required": [ @@ -37,44 +52,59 @@ } } }, - "HumanAddr": { - "type": "string" + "Layer": { + "type": "string", + "enum": [ + "Gateway", + "One", + "Two", + "Three" + ] }, "MixNode": { "type": "object", "required": [ "host", + "http_api_port", "identity_key", - "layer", - "location", + "mix_port", "sphinx_key", + "verloc_port", "version" ], "properties": { "host": { "type": "string" }, + "http_api_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, "identity_key": { "description": "Base58 encoded ed25519 EdDSA public key.", "type": "string" }, - "layer": { + "mix_port": { "type": "integer", - "format": "uint64", + "format": "uint16", "minimum": 0.0 }, - "location": { - "type": "string" - }, "sphinx_key": { "type": "string" }, + "verloc_port": { + "type": "integer", + "format": "uint16", + "minimum": 0.0 + }, "version": { "type": "string" } } }, "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" } } diff --git a/contracts/mixnet/schema/query_msg.json b/contracts/mixnet/schema/query_msg.json index 43c7bff800..5a9c13e1fd 100644 --- a/contracts/mixnet/schema/query_msg.json +++ b/contracts/mixnet/schema/query_msg.json @@ -20,18 +20,15 @@ "minimum": 0.0 }, "start_after": { - "anyOf": [ - { - "$ref": "#/definitions/HumanAddr" - }, - { - "type": "null" - } + "type": [ + "string", + "null" ] } } } - } + }, + "additionalProperties": false }, { "type": "object", @@ -50,10 +47,96 @@ "format": "uint32", "minimum": 0.0 }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "owns_mixnode" + ], + "properties": { + "owns_mixnode": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "owns_gateway" + ], + "properties": { + "owns_gateway": { + "type": "object", + "required": [ + "address" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "state_params" + ], + "properties": { + "state_params": { + "type": "object" + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_mix_delegations" + ], + "properties": { + "get_mix_delegations": { + "type": "object", + "required": [ + "mix_identity" + ], + "properties": { + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "mix_identity": { + "type": "string" + }, "start_after": { "anyOf": [ { - "$ref": "#/definitions/HumanAddr" + "$ref": "#/definitions/Addr" }, { "type": "null" @@ -62,11 +145,179 @@ } } } - } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_reverse_mix_delegations" + ], + "properties": { + "get_reverse_mix_delegations": { + "type": "object", + "required": [ + "delegation_owner" + ], + "properties": { + "delegation_owner": { + "$ref": "#/definitions/Addr" + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_mix_delegation" + ], + "properties": { + "get_mix_delegation": { + "type": "object", + "required": [ + "address", + "mix_identity" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "mix_identity": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_gateway_delegations" + ], + "properties": { + "get_gateway_delegations": { + "type": "object", + "required": [ + "gateway_identity" + ], + "properties": { + "gateway_identity": { + "type": "string" + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_reverse_gateway_delegations" + ], + "properties": { + "get_reverse_gateway_delegations": { + "type": "object", + "required": [ + "delegation_owner" + ], + "properties": { + "delegation_owner": { + "$ref": "#/definitions/Addr" + }, + "limit": { + "type": [ + "integer", + "null" + ], + "format": "uint32", + "minimum": 0.0 + }, + "start_after": { + "type": [ + "string", + "null" + ] + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "get_gateway_delegation" + ], + "properties": { + "get_gateway_delegation": { + "type": "object", + "required": [ + "address", + "gateway_identity" + ], + "properties": { + "address": { + "$ref": "#/definitions/Addr" + }, + "gateway_identity": { + "type": "string" + } + } + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "layer_distribution" + ], + "properties": { + "layer_distribution": { + "type": "object" + } + }, + "additionalProperties": false } ], "definitions": { - "HumanAddr": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", "type": "string" } } diff --git a/contracts/mixnet/schema/state.json b/contracts/mixnet/schema/state.json index d559e403ee..33ea553981 100644 --- a/contracts/mixnet/schema/state.json +++ b/contracts/mixnet/schema/state.json @@ -3,15 +3,91 @@ "title": "State", "type": "object", "required": [ - "owner" + "gateway_epoch_bond_reward", + "gateway_epoch_delegation_reward", + "mixnode_epoch_bond_reward", + "mixnode_epoch_delegation_reward", + "network_monitor_address", + "owner", + "params" ], "properties": { + "gateway_epoch_bond_reward": { + "$ref": "#/definitions/Decimal" + }, + "gateway_epoch_delegation_reward": { + "$ref": "#/definitions/Decimal" + }, + "mixnode_epoch_bond_reward": { + "$ref": "#/definitions/Decimal" + }, + "mixnode_epoch_delegation_reward": { + "$ref": "#/definitions/Decimal" + }, + "network_monitor_address": { + "$ref": "#/definitions/Addr" + }, "owner": { - "$ref": "#/definitions/HumanAddr" + "$ref": "#/definitions/Addr" + }, + "params": { + "$ref": "#/definitions/StateParams" } }, "definitions": { - "HumanAddr": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, + "StateParams": { + "type": "object", + "required": [ + "epoch_length", + "gateway_bond_reward_rate", + "gateway_delegation_reward_rate", + "minimum_gateway_bond", + "minimum_mixnode_bond", + "mixnode_active_set_size", + "mixnode_bond_reward_rate", + "mixnode_delegation_reward_rate" + ], + "properties": { + "epoch_length": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "gateway_bond_reward_rate": { + "$ref": "#/definitions/Decimal" + }, + "gateway_delegation_reward_rate": { + "$ref": "#/definitions/Decimal" + }, + "minimum_gateway_bond": { + "$ref": "#/definitions/Uint128" + }, + "minimum_mixnode_bond": { + "$ref": "#/definitions/Uint128" + }, + "mixnode_active_set_size": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "mixnode_bond_reward_rate": { + "$ref": "#/definitions/Decimal" + }, + "mixnode_delegation_reward_rate": { + "$ref": "#/definitions/Decimal" + } + } + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" } } diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 7dc7843723..88b76a96bf 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -10,7 +10,9 @@ use cosmwasm_std::{ entry_point, to_binary, Addr, Decimal, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response, Uint128, }; -use mixnet_contract::{ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, StateParams}; +use mixnet_contract::{ + ExecuteMsg, InstantiateMsg, MigrateMsg, QueryMsg, RawDelegationData, StateParams, +}; pub const INITIAL_DEFAULT_EPOCH_LENGTH: u32 = 2; @@ -205,31 +207,68 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> Result Result { - use crate::storage::{gateways, gateways_read, mixnodes, mixnodes_read}; +pub fn migrate(mut deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { + use crate::storage::{ + gateway_delegations, gateway_delegations_read, gateway_delegations_read_old, gateways_read, + mix_delegations, mix_delegations_read, mix_delegations_read_old, mixnodes_read, + }; + use crate::transactions::delegations; use cosmwasm_std::{Order, StdResult}; - use mixnet_contract::CURRENT_BLOCK_HEIGHT; use mixnet_contract::{GatewayBond, MixNodeBond}; - use std::sync::atomic::Ordering; - CURRENT_BLOCK_HEIGHT.store(env.block.height, Ordering::Relaxed); + // Read existing delegations data, drop invalid values, and rewrite delegations data with valid data only + fn overwrite_mixnode_delegations_data( + identity: &str, + deps: &mut DepsMut, + ) -> Result<(), ContractError> { + let delegations_bucket = mix_delegations_read(deps.storage, identity); + let old_delegations_bucket = mix_delegations_read_old(deps.storage, identity); + let mut delegations_vec = delegations(delegations_bucket)?; + let old_delegations = delegations::(old_delegations_bucket)?; + for delegation in old_delegations { + delegations_vec.push((delegation.0, RawDelegationData::new(delegation.1, 1))) + } - let bonds = mixnodes_read(deps.storage) + for (key, delegation) in delegations_vec { + mix_delegations(deps.storage, identity).save(&key, &delegation)?; + } + Ok(()) + } + + fn overwrite_gateway_delegations_data( + identity: &str, + deps: &mut DepsMut, + ) -> Result<(), ContractError> { + let delegations_bucket = gateway_delegations_read(deps.storage, identity); + let old_delegations_bucket = gateway_delegations_read_old(deps.storage, identity); + let mut delegations_vec = delegations(delegations_bucket)?; + let old_delegations = delegations::(old_delegations_bucket)?; + for delegation in old_delegations { + delegations_vec.push((delegation.0, RawDelegationData::new(delegation.1, 1))) + } + + for (key, delegation) in delegations_vec { + gateway_delegations(deps.storage, identity).save(&key, &delegation)?; + } + Ok(()) + } + + let mixnet_bonds = mixnodes_read(deps.storage) .range(None, None, Order::Ascending) .map(|res| res.map(|item| item.1)) .collect::>>()?; - for bond in bonds { - mixnodes(deps.storage).save(bond.identity().as_bytes(), &bond)?; + for bond in mixnet_bonds { + overwrite_mixnode_delegations_data(bond.identity(), &mut deps)?; } - let bonds = gateways_read(deps.storage) + let gateway_bonds = gateways_read(deps.storage) .range(None, None, Order::Ascending) .map(|res| res.map(|item| item.1)) .collect::>>()?; - for bond in bonds { - gateways(deps.storage).save(bond.identity().as_bytes(), &bond)?; + for bond in gateway_bonds { + overwrite_gateway_delegations_data(bond.identity(), &mut deps)?; } Ok(Default::default()) diff --git a/contracts/mixnet/src/storage.rs b/contracts/mixnet/src/storage.rs index c038691389..43f1398d89 100644 --- a/contracts/mixnet/src/storage.rs +++ b/contracts/mixnet/src/storage.rs @@ -36,6 +36,8 @@ const PREFIX_REVERSE_GATEWAY_DELEGATION: &[u8] = b"dg"; // Contract-level stuff +// TODO Unify bucket and mixnode storage functions + pub fn config(storage: &mut dyn Storage) -> Singleton { singleton(storage, CONFIG_KEY) } @@ -316,6 +318,14 @@ pub fn mix_delegations_read<'a>( ReadonlyBucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()]) } +// https://github.com/nymtech/nym/blob/122f5d9f2e5c1ced96e3b9ba0c74ef8b7dbde2c7/contracts/mixnet/src/storage.rs +pub fn mix_delegations_read_old<'a>( + storage: &'a dyn Storage, + mix_identity: IdentityKeyRef, +) -> ReadonlyBucket<'a, Uint128> { + ReadonlyBucket::multilevel(storage, &[PREFIX_MIX_DELEGATION, mix_identity.as_bytes()]) +} + pub fn reverse_mix_delegations<'a>(storage: &'a mut dyn Storage, owner: &Addr) -> Bucket<'a, ()> { Bucket::multilevel(storage, &[PREFIX_REVERSE_MIX_DELEGATION, owner.as_bytes()]) } @@ -347,6 +357,16 @@ pub fn gateway_delegations_read<'a>( ) } +pub fn gateway_delegations_read_old<'a>( + storage: &'a dyn Storage, + gateway_identity: IdentityKeyRef, +) -> ReadonlyBucket<'a, Uint128> { + ReadonlyBucket::multilevel( + storage, + &[PREFIX_GATEWAY_DELEGATION, gateway_identity.as_bytes()], + ) +} + pub fn reverse_gateway_delegations<'a>( storage: &'a mut dyn Storage, owner: &Addr, diff --git a/contracts/mixnet/src/transactions.rs b/contracts/mixnet/src/transactions.rs index 2e753ebf06..781269a3f9 100644 --- a/contracts/mixnet/src/transactions.rs +++ b/contracts/mixnet/src/transactions.rs @@ -14,6 +14,8 @@ use cosmwasm_storage::ReadonlyBucket; use mixnet_contract::{ Gateway, GatewayBond, IdentityKey, Layer, MixNode, MixNodeBond, RawDelegationData, StateParams, }; +use serde::de::DeserializeOwned; +use serde::Serialize; const OLD_DELEGATIONS_CHUNK_SIZE: usize = 500; pub(crate) const MINIMUM_BLOCK_AGE_FOR_REWARDING: u64 = 17280; @@ -25,12 +27,15 @@ pub(crate) const MINIMUM_BLOCK_AGE_FOR_REWARDING: u64 = 17280; // 3. The node unbonds // 4. Some of the addresses that delegated in the past have not removed the delegation yet // 5. The node rebonds with the same identity -fn find_old_delegations(delegations_bucket: ReadonlyBucket) -> StdResult { +pub fn delegations( + delegations_bucket: ReadonlyBucket, +) -> StdResult, T)>> { // I think it's incredibly unlikely to ever read more than that // but in case we do, we should guard ourselves against possible // out of memory errors (wasm contracts can only allocate at most 2MB // of RAM, so we don't want to box the entire iterator) - let mut total_delegation = Coin::new(0, DENOM); + let mut delegations = Vec::new(); + // let mut total_delegation = Coin::new(0, DENOM); let mut start = None; loop { let iterator = delegations_bucket @@ -39,16 +44,23 @@ fn find_old_delegations(delegations_bucket: ReadonlyBucket) - let mut iterated = 0; - for delegation in iterator { + for result_tuple in iterator { iterated += 1; - if iterated == OLD_DELEGATIONS_CHUNK_SIZE + 1 { - // we reached start of next chunk, don't process it, mark it for the next iteration of the loop - start = Some(delegation?.0); - continue; + match result_tuple { + Ok((position, delegation)) => { + // We might skip some values due to deserializatio errors + if iterated > OLD_DELEGATIONS_CHUNK_SIZE { + // we reached start of next chunk, don't process it, mark it for the next iteration of the loop + start = Some(position); + continue; + } + delegations.push((position, delegation)) + } + Err(_e) => { + // Skip errors + continue; + } } - - let value = delegation?.1.amount; - total_delegation.amount += value; } if iterated <= OLD_DELEGATIONS_CHUNK_SIZE { @@ -57,7 +69,17 @@ fn find_old_delegations(delegations_bucket: ReadonlyBucket) - } } - Ok(total_delegation) + Ok(delegations) +} + +fn total_delegations(delegations_bucket: ReadonlyBucket) -> StdResult { + match delegations(delegations_bucket) { + Ok(delegations) => Ok(Coin::new( + delegations.iter().fold(0, |acc, x| acc + x.1.amount.u128()), + "upunk", + )), + Err(e) => Err(e), + } } fn validate_mixnode_bond(bond: &[Coin], minimum_bond: Uint128) -> Result<(), ContractError> { @@ -138,7 +160,7 @@ pub(crate) fn try_add_mixnode( // this might potentially require more gas if a significant number of delegations was there let delegations_bucket = mix_delegations_read(deps.storage, &bond.mix_node.identity_key); - let existing_delegation = find_old_delegations(delegations_bucket)?; + let existing_delegation = total_delegations(delegations_bucket)?; bond.total_delegation = existing_delegation; let identity = bond.identity(); @@ -270,7 +292,7 @@ pub(crate) fn try_add_gateway( // this might potentially require more gas if a significant number of delegations was there let delegations_bucket = gateway_delegations_read(deps.storage, &bond.gateway.identity_key); - let existing_delegation = find_old_delegations(delegations_bucket)?; + let existing_delegation = total_delegations(delegations_bucket)?; bond.total_delegation = existing_delegation; let identity = bond.identity(); @@ -3928,7 +3950,7 @@ pub mod tests { let node_identity: IdentityKey = "nodeidentity".into(); let read_bucket = mix_delegations_read(&deps.storage, &node_identity); - let old_delegations = find_old_delegations(read_bucket).unwrap(); + let old_delegations = total_delegations(read_bucket).unwrap(); assert_eq!(Coin::new(0, DENOM), old_delegations); } @@ -3945,14 +3967,14 @@ pub mod tests { OLD_DELEGATIONS_CHUNK_SIZE * 3 + 1, ]; - for total_delegations in num_delegations { + for delegations in num_delegations { let mut deps = helpers::init_contract(); let node_identity: IdentityKey = "nodeidentity".into(); // delegate some stake let mut write_bucket = mix_delegations(&mut deps.storage, &node_identity); - for i in 1..=total_delegations { + for i in 1..=delegations { let delegator = Addr::unchecked(format!("delegator{}", i)); let delegation = raw_delegation_fixture(i as u128); write_bucket @@ -3961,9 +3983,9 @@ pub mod tests { } let read_bucket = mix_delegations_read(&deps.storage, &node_identity); - let old_delegations = find_old_delegations(read_bucket).unwrap(); + let old_delegations = total_delegations(read_bucket).unwrap(); - let total_delegation = (1..=total_delegations as u128).into_iter().sum(); + let total_delegation = (1..=delegations as u128).into_iter().sum(); assert_eq!(Coin::new(total_delegation, DENOM), old_delegations); } } From 1403449ad51ec2cba19997af88e60f1435512171 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Tue, 14 Sep 2021 16:00:21 +0100 Subject: [PATCH 27/28] enable bonding --- wallet-web/components/bond/BondNodeForm.tsx | 10 ---------- wallet-web/pages/_app.tsx | 6 ------ 2 files changed, 16 deletions(-) diff --git a/wallet-web/components/bond/BondNodeForm.tsx b/wallet-web/components/bond/BondNodeForm.tsx index a0d275c119..18d208abc7 100644 --- a/wallet-web/components/bond/BondNodeForm.tsx +++ b/wallet-web/components/bond/BondNodeForm.tsx @@ -43,7 +43,6 @@ export const BondNodeForm = ({ {type === NodeType.Gateway && ( ) => manageForm.handlePortChange('mixPort', e.target.value) @@ -206,7 +199,6 @@ export const BondNodeForm = ({ <> ) => manageForm.handlePortChange('verlocPort', e.target.value) @@ -222,7 +214,6 @@ export const BondNodeForm = ({ ) => manageForm.handlePortChange('httpApiPort', e.target.value) @@ -239,7 +230,6 @@ export const BondNodeForm = ({ ) : ( ) => manageForm.handlePortChange('clientsPort', e.target.value) diff --git a/wallet-web/pages/_app.tsx b/wallet-web/pages/_app.tsx index 07dc9cf534..9d95f386d9 100644 --- a/wallet-web/pages/_app.tsx +++ b/wallet-web/pages/_app.tsx @@ -5,7 +5,6 @@ import CssBaseline from '@material-ui/core/CssBaseline' import { theme } from '../lib/theme' import type { AppProps } from 'next/app' import { ValidatorClientContext } from '../contexts/ValidatorClient' -import { AppAlert } from '../components/AppAlert' // TODO: should it perhaps be pulled from some config or also user provided? export const BONDING_CONTRACT_ADDRESS: string = 'punk10pyejy66429refv3g35g2t7am0was7yalwrzen' @@ -45,11 +44,6 @@ export default function Application(props: AppProps) { - From 976dd7aae2e7ee6c8a0fdbe368f393b1c26ad704 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Wed, 15 Sep 2021 17:28:49 +0200 Subject: [PATCH 28/28] Add block_height method to Delegation (#778) Co-authored-by: Drazen Urch --- common/mixnet-contract/src/delegation.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/common/mixnet-contract/src/delegation.rs b/common/mixnet-contract/src/delegation.rs index 023472bb02..f0efe37297 100644 --- a/common/mixnet-contract/src/delegation.rs +++ b/common/mixnet-contract/src/delegation.rs @@ -45,6 +45,10 @@ impl Delegation { pub fn owner(&self) -> Addr { self.owner.clone() } + + pub fn block_height(&self) -> u64 { + self.block_height + } } impl Display for Delegation {