From 7e3bc2d6bb154451d905c17ba17f350d4df32da7 Mon Sep 17 00:00:00 2001 From: Drazen Urch Date: Mon, 5 Dec 2022 09:36:06 +0100 Subject: [PATCH] Node family management (#1670) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Family management messages * Add family queries * Add queries to client * Layer assignment message * Paged family queries, annotate mixnodes with family * Add layer assignments to epoch operations * Remove family layer peristence * Add NotImplemented error for kick Co-authored-by: Jędrzej Stuczyński --- .../validator-client/src/client.rs | 55 +- .../src/nymd/traits/mixnet_query_client.rs | 39 +- .../src/nymd/traits/mixnet_signing_client.rs | 145 +- .../mixnet-contract/src/error.rs | 38 +- .../mixnet-contract/src/families.rs | 63 + .../mixnet-contract/src/lib.rs | 1 + .../mixnet-contract/src/mixnode.rs | 24 +- .../mixnet-contract/src/msg.rs | 95 +- .../mixnet-contract/src/types.rs | 34 + .../vesting-contract/src/messages.rs | 25 +- contracts/.gitignore | 1 + contracts/Cargo.lock | 1786 ----------------- contracts/mixnet/src/constants.rs | 7 + contracts/mixnet/src/contract.rs | 83 +- contracts/mixnet/src/families/mod.rs | 3 + contracts/mixnet/src/families/queries.rs | 107 + contracts/mixnet/src/families/storage.rs | 99 + contracts/mixnet/src/families/transactions.rs | 414 ++++ contracts/mixnet/src/gateways/transactions.rs | 2 +- contracts/mixnet/src/interval/transactions.rs | 126 +- contracts/mixnet/src/lib.rs | 1 + contracts/mixnet/src/mixnodes/transactions.rs | 44 +- contracts/mixnet/src/support/helpers.rs | 48 +- contracts/mixnet/src/support/tests/mod.rs | 4 +- contracts/vesting/src/contract.rs | 54 +- contracts/vesting/src/traits/mod.rs | 2 + contracts/vesting/src/traits/node_families.rs | 33 + contracts/vesting/src/vesting/account/mod.rs | 1 + .../src/vesting/account/node_families.rs | 74 + nym-wallet/src-tauri/src/main.rs | 4 + .../src/operations/mixnet/families.rs | 81 + .../src-tauri/src/operations/mixnet/mod.rs | 1 + validator-api/src/contract_cache/mod.rs | 21 +- validator-api/src/epoch_operations/error.rs | 5 + validator-api/src/epoch_operations/mod.rs | 94 +- validator-api/src/nymd_client.rs | 23 +- .../validator-api-requests/src/models.rs | 2 + 37 files changed, 1768 insertions(+), 1871 deletions(-) create mode 100644 common/cosmwasm-smart-contracts/mixnet-contract/src/families.rs create mode 100644 contracts/.gitignore delete mode 100644 contracts/Cargo.lock create mode 100644 contracts/mixnet/src/families/mod.rs create mode 100644 contracts/mixnet/src/families/queries.rs create mode 100644 contracts/mixnet/src/families/storage.rs create mode 100644 contracts/mixnet/src/families/transactions.rs create mode 100644 contracts/vesting/src/traits/node_families.rs create mode 100644 contracts/vesting/src/vesting/account/node_families.rs create mode 100644 nym-wallet/src-tauri/src/operations/mixnet/families.rs diff --git a/common/client-libs/validator-client/src/client.rs b/common/client-libs/validator-client/src/client.rs index 8ddce33a4f..6a218f1fa2 100644 --- a/common/client-libs/validator-client/src/client.rs +++ b/common/client-libs/validator-client/src/client.rs @@ -1,6 +1,5 @@ // Copyright 2021 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 - use crate::{validator_api, ValidatorClientError}; use coconut_dkg_common::types::NodeIndex; #[cfg(feature = "nymd-client")] @@ -10,9 +9,10 @@ use coconut_dkg_common::{ #[cfg(feature = "nymd-client")] use coconut_interface::Base58; use coconut_interface::VerificationKey; +use mixnet_contract_common::families::{Family, FamilyHead}; use mixnet_contract_common::mixnode::MixNodeDetails; -use mixnet_contract_common::MixId; use mixnet_contract_common::{GatewayBond, IdentityKeyRef}; +use mixnet_contract_common::{IdentityKey, MixId}; #[cfg(feature = "nymd-client")] use std::str::FromStr; use validator_api_requests::coconut::{ @@ -220,6 +220,7 @@ impl Client { 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: cosmrs::AccountId) { self.nymd .set_mixnet_contract_address(mixnet_contract_address) @@ -229,6 +230,56 @@ impl Client { self.nymd.mixnet_contract_address().clone() } + pub async fn get_all_node_families(&self) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync + Send, + { + let mut families = Vec::new(); + let mut start_after = None; + + loop { + let paged_response = self + .nymd + .get_all_node_families_paged(start_after.take(), None) + .await?; + families.extend(paged_response.families); + + if let Some(start_after_res) = paged_response.start_next_after { + start_after = Some(start_after_res) + } else { + break; + } + } + + Ok(families) + } + + pub async fn get_all_family_members( + &self, + ) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync + Send, + { + let mut members = Vec::new(); + let mut start_after = None; + + loop { + let paged_response = self + .nymd + .get_all_family_members_paged(start_after.take(), None) + .await?; + members.extend(paged_response.members); + + if let Some(start_after_res) = paged_response.start_next_after { + start_after = Some(start_after_res) + } else { + break; + } + } + + Ok(members) + } + // basically handles paging for us pub async fn get_all_nymd_rewarded_set_mixnodes( &self, diff --git a/common/client-libs/validator-client/src/nymd/traits/mixnet_query_client.rs b/common/client-libs/validator-client/src/nymd/traits/mixnet_query_client.rs index 675c15c5d1..8c6c2e8d41 100644 --- a/common/client-libs/validator-client/src/nymd/traits/mixnet_query_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/mixnet_query_client.rs @@ -7,6 +7,7 @@ use crate::nymd::NymdClient; use async_trait::async_trait; use cosmrs::AccountId; use mixnet_contract_common::delegation::{MixNodeDelegationResponse, OwnerProxySubKey}; +use mixnet_contract_common::families::Family; use mixnet_contract_common::mixnode::{ MixNodeDetails, MixnodeRewardingDetailsResponse, PagedMixnodesDetailsResponse, PagedUnbondedMixnodesResponse, StakeSaturationResponse, UnbondedMixnodeResponse, @@ -20,9 +21,9 @@ use mixnet_contract_common::{ CurrentIntervalResponse, EpochEventId, GatewayBondResponse, GatewayOwnershipResponse, IdentityKey, IntervalEventId, LayerDistribution, MixId, MixOwnershipResponse, MixnodeDetailsResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, - PagedGatewayResponse, PagedMixNodeDelegationsResponse, PagedMixnodeBondsResponse, - PagedRewardedSetResponse, PendingEpochEventsResponse, PendingIntervalEventsResponse, - QueryMsg as MixnetQueryMsg, + PagedFamiliesResponse, PagedGatewayResponse, PagedMembersResponse, + PagedMixNodeDelegationsResponse, PagedMixnodeBondsResponse, PagedRewardedSetResponse, + PendingEpochEventsResponse, PendingIntervalEventsResponse, QueryMsg as MixnetQueryMsg, }; use serde::Deserialize; @@ -73,6 +74,24 @@ pub trait MixnetQueryClient { .await } + async fn get_all_node_families_paged( + &self, + start_after: Option, + limit: Option, + ) -> Result { + self.query_mixnet_contract(MixnetQueryMsg::GetAllFamiliesPaged { limit, start_after }) + .await + } + + async fn get_all_family_members_paged( + &self, + start_after: Option, + limit: Option, + ) -> Result { + self.query_mixnet_contract(MixnetQueryMsg::GetAllMembersPaged { limit, start_after }) + .await + } + // mixnode-related: async fn get_mixnode_bonds_paged( @@ -357,6 +376,20 @@ pub trait MixnetQueryClient { }) .await } + + async fn get_node_family_by_label(&self, label: &str) -> Result, NymdError> { + self.query_mixnet_contract(MixnetQueryMsg::GetFamilyByLabel { + label: label.to_string(), + }) + .await + } + + async fn get_node_family_by_head(&self, head: &str) -> Result, NymdError> { + self.query_mixnet_contract(MixnetQueryMsg::GetFamilyByHead { + head: head.to_string(), + }) + .await + } } #[async_trait] diff --git a/common/client-libs/validator-client/src/nymd/traits/mixnet_signing_client.rs b/common/client-libs/validator-client/src/nymd/traits/mixnet_signing_client.rs index ad7773305a..f2a2057081 100644 --- a/common/client-libs/validator-client/src/nymd/traits/mixnet_signing_client.rs +++ b/common/client-libs/validator-client/src/nymd/traits/mixnet_signing_client.rs @@ -11,7 +11,7 @@ use cosmrs::AccountId; use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams}; use mixnet_contract_common::reward_params::{IntervalRewardingParamsUpdate, Performance}; use mixnet_contract_common::{ - ContractStateParams, ExecuteMsg as MixnetExecuteMsg, Gateway, MixId, MixNode, + ContractStateParams, ExecuteMsg as MixnetExecuteMsg, Gateway, LayerAssignment, MixId, MixNode, }; #[async_trait] @@ -108,7 +108,7 @@ pub trait MixnetSigningClient { async fn advance_current_epoch( &self, - new_rewarded_set: Vec, + new_rewarded_set: Vec, expected_active_set_size: u32, fee: Option, ) -> Result { @@ -136,6 +136,147 @@ pub trait MixnetSigningClient { .await } + // family related + async fn create_family( + &self, + owner_signature: String, + label: String, + fee: Option, + ) -> Result { + self.execute_mixnet_contract( + fee, + MixnetExecuteMsg::CreateFamily { + owner_signature, + label, + }, + vec![], + ) + .await + } + + async fn create_family_on_behalf( + &self, + owner_address: String, + owner_signature: String, + label: String, + fee: Option, + ) -> Result { + self.execute_mixnet_contract( + fee, + MixnetExecuteMsg::CreateFamilyOnBehalf { + owner_address, + owner_signature, + label, + }, + vec![], + ) + .await + } + + async fn join_family( + &self, + signature: String, + family_head: String, + fee: Option, + ) -> Result { + self.execute_mixnet_contract( + fee, + MixnetExecuteMsg::JoinFamily { + signature, + family_head, + }, + vec![], + ) + .await + } + + async fn join_family_on_behalf( + &self, + member_address: String, + signature: String, + family_head: String, + fee: Option, + ) -> Result { + self.execute_mixnet_contract( + fee, + MixnetExecuteMsg::JoinFamilyOnBehalf { + member_address, + signature, + family_head, + }, + vec![], + ) + .await + } + + async fn leave_family( + &self, + signature: String, + family_head: String, + fee: Option, + ) -> Result { + self.execute_mixnet_contract( + fee, + MixnetExecuteMsg::LeaveFamily { + signature, + family_head, + }, + vec![], + ) + .await + } + + async fn leave_family_on_behalf( + &self, + member_address: String, + signature: String, + family_head: String, + fee: Option, + ) -> Result { + self.execute_mixnet_contract( + fee, + MixnetExecuteMsg::LeaveFamilyOnBehalf { + member_address, + signature, + family_head, + }, + vec![], + ) + .await + } + + async fn kick_family_member( + &self, + signature: String, + member: String, + fee: Option, + ) -> Result { + self.execute_mixnet_contract( + fee, + MixnetExecuteMsg::KickFamilyMember { signature, member }, + vec![], + ) + .await + } + + async fn kick_family_member_on_behalf( + &self, + head_address: String, + signature: String, + member: String, + fee: Option, + ) -> Result { + self.execute_mixnet_contract( + fee, + MixnetExecuteMsg::KickFamilyMemberOnBehalf { + head_address, + signature, + member, + }, + vec![], + ) + .await + } // mixnode-related: async fn bond_mixnode( diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs index e0d369f933..6eff27c315 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/error.rs @@ -1,7 +1,7 @@ // Copyright 2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 -use crate::MixId; +use crate::{IdentityKey, MixId}; use cosmwasm_std::{Addr, Coin, Decimal}; use thiserror::Error; @@ -133,4 +133,40 @@ pub enum MixnetContractError { #[error("Mixnode {mix_id} appears multiple times in the provided rewarded set update!")] DuplicateRewardedSetNode { mix_id: MixId }, + + #[error("Family with head {head} does not exist!")] + FamilyDoesNotExist { head: String }, + + #[error("Family with label '{0}' already exists")] + FamilyWithLabelExists(String), + + #[error("Invalid layer expected 1, 2 or 3, got {0}")] + InvalidLayer(u8), + + #[error("Head already has a family")] + FamilyCanHaveOnlyOne, + + #[error("Already member of family {0}")] + AlreadyMemberOfFamily(String), + + #[error("Can't join own family, family head {head}, member {member}")] + CantJoinOwnFamily { + head: IdentityKey, + member: IdentityKey, + }, + + #[error("Can't leave own family, family head {head}, member {member}")] + CantLeaveOwnFamily { + head: IdentityKey, + member: IdentityKey, + }, + + #[error("{member} is not a member of family {head}")] + NotAMember { + head: IdentityKey, + member: IdentityKey, + }, + + #[error("Feature is not yet implemented")] + NotImplemented, } diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/families.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/families.rs new file mode 100644 index 0000000000..b552da1931 --- /dev/null +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/families.rs @@ -0,0 +1,63 @@ +use crate::{IdentityKey, IdentityKeyRef}; +use cosmwasm_std::Addr; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts(export_to = "ts-packages/types/src/types/rust/NodeFamily.ts") +)] +#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, JsonSchema)] +pub struct Family { + head: FamilyHead, + proxy: Option, + label: String, +} + +#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] +#[cfg_attr( + feature = "generate-ts", + ts(export_to = "ts-packages/types/src/types/rust/NodeFamilyHead.ts") +)] +#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, JsonSchema)] +pub struct FamilyHead(IdentityKey); + +impl FamilyHead { + pub fn new(identity: IdentityKeyRef<'_>) -> Self { + FamilyHead(identity.to_string()) + } + + pub fn identity(&self) -> IdentityKeyRef<'_> { + &self.0 + } +} + +impl Family { + pub fn new(head: FamilyHead, proxy: Option, label: &str) -> Self { + Family { + head, + proxy: proxy.map(|p| p.to_string()), + label: label.to_string(), + } + } + + #[allow(dead_code)] + pub fn head(&self) -> &FamilyHead { + &self.head + } + + pub fn head_identity(&self) -> IdentityKeyRef<'_> { + self.head.identity() + } + + #[allow(dead_code)] + pub fn proxy(&self) -> Option<&String> { + self.proxy.as_ref() + } + + #[allow(dead_code)] + pub fn label(&self) -> &str { + &self.label + } +} diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs index 351828585a..adabf7de30 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/lib.rs @@ -8,6 +8,7 @@ mod constants; pub mod delegation; pub mod error; pub mod events; +pub mod families; pub mod gateway; pub mod helpers; mod interval; diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs index f8568e2c6e..90dacbea30 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/mixnode.rs @@ -36,7 +36,6 @@ impl RewardedSetNodeStatus { #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)] pub struct MixNodeDetails { pub bond_information: MixNodeBond, - pub rewarding_details: MixNodeRewarding, } @@ -579,6 +578,29 @@ impl From for String { } } +impl TryFrom for Layer { + type Error = MixnetContractError; + + fn try_from(i: u8) -> Result { + match i { + 1 => Ok(Layer::One), + 2 => Ok(Layer::Two), + 3 => Ok(Layer::Three), + _ => Err(MixnetContractError::InvalidLayer(i)), + } + } +} + +impl From for u8 { + fn from(layer: Layer) -> u8 { + match layer { + Layer::One => 1, + Layer::Two => 2, + Layer::Three => 3, + } + } +} + #[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))] #[cfg_attr( feature = "generate-ts", diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs index 7bb3dd766c..cc86757367 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/msg.rs @@ -8,7 +8,7 @@ use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams}; use crate::reward_params::{ IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams, }; -use crate::{delegation, ContractStateParams, MixId, Percent}; +use crate::{delegation, ContractStateParams, Layer, LayerAssignment, MixId, Percent}; use crate::{Gateway, IdentityKey, MixNode}; use cosmwasm_std::Decimal; use schemars::JsonSchema; @@ -73,6 +73,51 @@ impl InitialRewardingParams { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { + AssignNodeLayer { + mix_id: MixId, + layer: Layer, + }, + // Families + /// Only owner of the node can crate the family with node as head + CreateFamily { + owner_signature: String, + label: String, + }, + /// Family head needs to sign the joining node IdentityKey + JoinFamily { + signature: String, + family_head: IdentityKey, + }, + LeaveFamily { + signature: String, + family_head: IdentityKey, + }, + KickFamilyMember { + signature: String, + member: IdentityKey, + }, + CreateFamilyOnBehalf { + owner_address: String, + owner_signature: String, + label: String, + }, + /// Family head needs to sign the joining node IdentityKey + JoinFamilyOnBehalf { + member_address: String, + signature: String, + family_head: IdentityKey, + }, + LeaveFamilyOnBehalf { + member_address: String, + signature: String, + family_head: IdentityKey, + }, + KickFamilyMemberOnBehalf { + head_address: String, + signature: String, + member: IdentityKey, + }, + // state/sys-params-related UpdateRewardingValidatorAddress { address: String, @@ -94,7 +139,8 @@ pub enum ExecuteMsg { force_immediately: bool, }, AdvanceCurrentEpoch { - new_rewarded_set: Vec, + new_rewarded_set: Vec, + // families_in_layer: HashMap, expected_active_set_size: u32, }, ReconcileEpochEvents { @@ -194,6 +240,29 @@ pub enum ExecuteMsg { impl ExecuteMsg { pub fn default_memo(&self) -> String { match self { + ExecuteMsg::AssignNodeLayer { mix_id, layer } => { + format!("assigning mix {} for layer {:?}", mix_id, layer) + } + ExecuteMsg::CreateFamily { .. } => "crating node family with".to_string(), + ExecuteMsg::JoinFamily { family_head, .. } => { + format!("joining family {}", family_head) + } + ExecuteMsg::LeaveFamily { family_head, .. } => { + format!("leaving family {}", family_head) + } + ExecuteMsg::KickFamilyMember { member, .. } => { + format!("kicking {} from family", member) + } + ExecuteMsg::CreateFamilyOnBehalf { .. } => "crating node family with".to_string(), + ExecuteMsg::JoinFamilyOnBehalf { family_head, .. } => { + format!("joining family {}", family_head) + } + ExecuteMsg::LeaveFamilyOnBehalf { family_head, .. } => { + format!("leaving family {}", family_head) + } + ExecuteMsg::KickFamilyMemberOnBehalf { member, .. } => { + format!("kicking {} from family", member) + } ExecuteMsg::UpdateRewardingValidatorAddress { address } => { format!("updating rewarding validator to {}", address) } @@ -286,6 +355,27 @@ impl ExecuteMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { + // families + GetAllFamiliesPaged { + limit: Option, + start_after: Option, + }, + GetAllMembersPaged { + limit: Option, + start_after: Option, + }, + GetFamilyByHead { + head: String, + }, + GetFamilyByLabel { + label: String, + }, + GetFamilyMembersByHead { + head: String, + }, + GetFamilyMembersByLabel { + label: String, + }, // state/sys-params-related GetContractVersion {}, GetRewardingValidatorAddress {}, @@ -340,7 +430,6 @@ pub enum QueryMsg { mix_identity: IdentityKey, }, GetLayerDistribution {}, - // gateway-related: GetGateways { start_after: Option, diff --git a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs index 09223a8e8a..c17520a2b6 100644 --- a/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs +++ b/common/cosmwasm-smart-contracts/mixnet-contract/src/types.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::error::MixnetContractError; +use crate::families::{Family, FamilyHead}; use crate::{Layer, RewardedSetNodeStatus}; use cosmwasm_std::Addr; use cosmwasm_std::Coin; @@ -21,6 +22,27 @@ pub type BlockHeight = u64; pub type EpochEventId = u32; pub type IntervalEventId = u32; +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema, PartialEq)] +pub struct LayerAssignment { + mix_id: MixId, + layer: Layer, +} + +impl LayerAssignment { + pub fn new(mix_id: MixId, layer: Layer) -> Self { + LayerAssignment { mix_id, layer } + } + + pub fn mix_id(&self) -> MixId { + self.mix_id + } + + pub fn layer(&self) -> Layer { + self.layer + } +} + #[derive(Debug, Default, Serialize, Deserialize, Copy, Clone, Eq, PartialEq)] pub struct LayerDistribution { pub layer1: u64, @@ -126,3 +148,15 @@ pub struct PagedRewardedSetResponse { pub nodes: Vec<(MixId, RewardedSetNodeStatus)>, pub start_next_after: Option, } + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)] +pub struct PagedFamiliesResponse { + pub families: Vec, + pub start_next_after: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)] +pub struct PagedMembersResponse { + pub members: Vec<(IdentityKey, FamilyHead)>, + pub start_next_after: Option, +} diff --git a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs index 69eb323156..3edc6c2303 100644 --- a/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs +++ b/common/cosmwasm-smart-contracts/vesting-contract/src/messages.rs @@ -1,7 +1,7 @@ use cosmwasm_std::{Coin, Timestamp}; use mixnet_contract_common::{ mixnode::{MixNodeConfigUpdate, MixNodeCostParams}, - Gateway, MixId, MixNode, + Gateway, IdentityKey, MixId, MixNode, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -57,6 +57,25 @@ impl VestingSpecification { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { + // Families + /// Only owner of the node can crate the family with node as head + CreateFamily { + owner_signature: String, + label: String, + }, + /// Family head needs to sign the joining node IdentityKey + JoinFamily { + signature: String, + family_head: IdentityKey, + }, + LeaveFamily { + signature: String, + family_head: IdentityKey, + }, + KickFamilyMember { + signature: String, + member: IdentityKey, + }, TrackReward { amount: Coin, address: String, @@ -134,6 +153,10 @@ pub enum ExecuteMsg { impl ExecuteMsg { pub fn name(&self) -> &str { match self { + ExecuteMsg::CreateFamily { .. } => "VestingExecuteMsg::CreateFamily", + ExecuteMsg::JoinFamily { .. } => "VestingExecuteMsg::JoinFamily", + ExecuteMsg::LeaveFamily { .. } => "VestingExecuteMsg::LeaveFamily", + ExecuteMsg::KickFamilyMember { .. } => "VestingExecuteMsg::KickFamilyMember", ExecuteMsg::TrackReward { .. } => "VestingExecuteMsg::TrackReward", ExecuteMsg::ClaimOperatorReward { .. } => "VestingExecuteMsg::ClaimOperatorReward", ExecuteMsg::ClaimDelegatorReward { .. } => "VestingExecuteMsg::ClaimDelegatorReward", diff --git a/contracts/.gitignore b/contracts/.gitignore new file mode 100644 index 0000000000..ffa3bbd20e --- /dev/null +++ b/contracts/.gitignore @@ -0,0 +1 @@ +Cargo.lock \ No newline at end of file diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock deleted file mode 100644 index 9da3d72397..0000000000 --- a/contracts/Cargo.lock +++ /dev/null @@ -1,1786 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "aes" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", - "ctr", - "opaque-debug 0.3.0", -] - -[[package]] -name = "anyhow" -version = "1.0.52" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3" - -[[package]] -name = "arrayref" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" - -[[package]] -name = "autocfg" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" - -[[package]] -name = "bandwidth-claim-contract" -version = "0.1.0" -dependencies = [ - "schemars", - "serde", -] - -[[package]] -name = "base16ct" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" - -[[package]] -name = "base64" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" - -[[package]] -name = "base64ct" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea908e7347a8c64e378c17e30ef880ad73e3b4498346b055c2c00ea342f3179" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "blake2" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94cb07b0da6a73955f8fb85d24c466778e70cda767a568229b104f0264089330" -dependencies = [ - "byte-tools", - "crypto-mac 0.7.0", - "digest 0.8.1", - "opaque-debug 0.2.3", -] - -[[package]] -name = "block-buffer" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" -dependencies = [ - "generic-array 0.14.5", -] - -[[package]] -name = "bs58" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" - -[[package]] -name = "bumpalo" -version = "3.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" - -[[package]] -name = "byte-tools" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" - -[[package]] -name = "byteorder" -version = "1.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" - -[[package]] -name = "bytes" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" - -[[package]] -name = "cc" -version = "1.0.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" -dependencies = [ - "jobserver", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chacha" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddf3c081b5fba1e5615640aae998e0fbd10c24cbd897ee39ed754a77601a4862" -dependencies = [ - "byteorder", - "keystream", -] - -[[package]] -name = "chrono" -version = "0.4.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" -dependencies = [ - "libc", - "num-integer", - "num-traits", - "time 0.1.43", - "winapi", -] - -[[package]] -name = "cipher" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" -dependencies = [ - "generic-array 0.14.5", -] - -[[package]] -name = "coconut-bandwidth" -version = "0.1.0" -dependencies = [ - "bandwidth-claim-contract", - "coconut-bandwidth-contract-common", - "cosmwasm-std", - "cosmwasm-storage", - "cw-controllers", - "cw-storage-plus", - "multisig-contract-common", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "coconut-bandwidth-contract-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "multisig-contract-common", - "schemars", - "serde", -] - -[[package]] -name = "coconut-dkg" -version = "0.1.0" -dependencies = [ - "coconut-dkg-common", - "cosmwasm-std", - "cosmwasm-storage", - "cw-controllers", - "cw-multi-test", - "cw-storage-plus", - "cw4", - "cw4-group", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "coconut-dkg-common" -version = "0.1.0" -dependencies = [ - "contracts-common", - "cosmwasm-std", - "cw-utils", - "multisig-contract-common", - "schemars", - "serde", -] - -[[package]] -name = "coconut-test" -version = "0.1.0" -dependencies = [ - "bandwidth-claim-contract", - "coconut-bandwidth", - "coconut-bandwidth-contract-common", - "coconut-dkg", - "coconut-dkg-common", - "cosmwasm-std", - "cosmwasm-storage", - "cw-controllers", - "cw-multi-test", - "cw-storage-plus", - "cw-utils", - "cw3", - "cw3-flex-multisig", - "cw4", - "cw4-group", - "multisig-contract-common", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "const-oid" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" - -[[package]] -name = "contracts-common" -version = "0.1.0" -dependencies = [ - "bs58", - "cosmwasm-std", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "cosmwasm-crypto" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb0afef2325df81aadbf9be1233f522ed8f6e91df870c764bc44cca2b1415bd" -dependencies = [ - "digest 0.9.0", - "ed25519-zebra", - "k256", - "rand_core 0.6.3", - "thiserror", -] - -[[package]] -name = "cosmwasm-derive" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b36e527620a2a3e00e46b6e731ab6c9b68d11069c986f7d7be8eba79ef081a4" -dependencies = [ - "syn", -] - -[[package]] -name = "cosmwasm-schema" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "772e80bbad231a47a2068812b723a1ff81dd4a0d56c9391ac748177bea3a61da" -dependencies = [ - "schemars", - "serde_json", -] - -[[package]] -name = "cosmwasm-std" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875994993c2082a6fcd406937bf0fca21c349e4a624f3810253a14fa83a3a195" -dependencies = [ - "base64", - "cosmwasm-crypto", - "cosmwasm-derive", - "forward_ref", - "schemars", - "serde", - "serde-json-wasm", - "thiserror", - "uint", -] - -[[package]] -name = "cosmwasm-storage" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d18403b07304d15d304dad11040d45bbcaf78d603b4be3fb5e2685c16f9229b5" -dependencies = [ - "cosmwasm-std", - "serde", -] - -[[package]] -name = "cpufeatures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" -dependencies = [ - "libc", -] - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "crypto" -version = "0.1.0" -dependencies = [ - "bs58", - "ed25519-dalek", - "nymsphinx-types", - "pemstore", - "rand", - "subtle-encoding", - "x25519-dalek", -] - -[[package]] -name = "crypto-bigint" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" -dependencies = [ - "generic-array 0.14.5", - "rand_core 0.6.3", - "subtle 2.4.1", - "zeroize", -] - -[[package]] -name = "crypto-mac" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" -dependencies = [ - "generic-array 0.12.4", - "subtle 1.0.0", -] - -[[package]] -name = "crypto-mac" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" -dependencies = [ - "generic-array 0.14.5", - "subtle 2.4.1", -] - -[[package]] -name = "ctr" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" -dependencies = [ - "cipher", -] - -[[package]] -name = "curve25519-dalek" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" -dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.5.1", - "subtle 2.4.1", - "zeroize", -] - -[[package]] -name = "cw-controllers" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f0bc6019b4d3d81e11f5c384bcce7173e2210bd654d75c6c9668e12cca05dfa" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus", - "cw-utils", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "cw-multi-test" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f9a8ab7c3c29ec93cb7a39ce4b14a05e053153b4a17ef7cf2246af1b7c087e" -dependencies = [ - "anyhow", - "cosmwasm-std", - "cosmwasm-storage", - "cw-storage-plus", - "cw-utils", - "derivative", - "itertools", - "prost", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "cw-storage-plus" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "648b1507290bbc03a8d88463d7cd9b04b1fa0155e5eef366c4fa052b9caaac7a" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", -] - -[[package]] -name = "cw-utils" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dbaecb78c8e8abfd6b4258c7f4fbeb5c49a5e45ee4d910d3240ee8e1d714e1b" -dependencies = [ - "cosmwasm-std", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "cw2" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cf4639517490dd36b333bbd6c4fbd92e325fd0acf4683b41753bc5eb63bfc1" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus", - "schemars", - "serde", -] - -[[package]] -name = "cw3" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe19462a7f644ba60c19d3443cb90d00c50d9b6b3b0a3a7fca93df8261af979b" -dependencies = [ - "cosmwasm-std", - "cw-utils", - "schemars", - "serde", -] - -[[package]] -name = "cw3-fixed-multisig" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df54aa54c13f405ec4ab36b6217538bc957d439eee58f89312db05a79caf6706" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus", - "cw-utils", - "cw2", - "cw3", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "cw3-flex-multisig" -version = "0.13.1" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-multi-test", - "cw-storage-plus", - "cw-utils", - "cw2", - "cw3", - "cw3-fixed-multisig", - "cw4", - "cw4-group", - "multisig-contract-common", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "cw4" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0acc3549d5ce11c6901b3a676f2e2628684722197054d97cd0101ea174ed5cbd" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus", - "schemars", - "serde", -] - -[[package]] -name = "cw4-group" -version = "0.13.4" -dependencies = [ - "cosmwasm-schema", - "cosmwasm-std", - "cw-controllers", - "cw-storage-plus", - "cw-utils", - "cw2", - "cw4", - "schemars", - "serde", - "thiserror", -] - -[[package]] -name = "der" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" -dependencies = [ - "const-oid", -] - -[[package]] -name = "derivative" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "digest" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -dependencies = [ - "generic-array 0.12.4", -] - -[[package]] -name = "digest" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" -dependencies = [ - "generic-array 0.14.5", -] - -[[package]] -name = "dyn-clone" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf" - -[[package]] -name = "ecdsa" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0d69ae62e0ce582d56380743515fefaf1a8c70cec685d9677636d7e30ae9dc9" -dependencies = [ - "der", - "elliptic-curve", - "rfc6979", - "signature", -] - -[[package]] -name = "ed25519" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" -dependencies = [ - "signature", -] - -[[package]] -name = "ed25519-dalek" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c762bae6dcaf24c4c84667b8579785430908723d5c889f469d76a41d59cc7a9d" -dependencies = [ - "curve25519-dalek", - "ed25519", - "rand", - "serde", - "sha2", - "zeroize", -] - -[[package]] -name = "ed25519-zebra" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403ef3e961ab98f0ba902771d29f842058578bb1ce7e3c59dad5a6a93e784c69" -dependencies = [ - "curve25519-dalek", - "hex", - "rand_core 0.6.3", - "serde", - "sha2", - "thiserror", - "zeroize", -] - -[[package]] -name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - -[[package]] -name = "elliptic-curve" -version = "0.11.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b477563c2bfed38a3b7a60964c49e058b2510ad3f12ba3483fd8f62c2306d6" -dependencies = [ - "base16ct", - "crypto-bigint", - "der", - "ff", - "generic-array 0.14.5", - "group", - "rand_core 0.6.3", - "sec1", - "subtle 2.4.1", - "zeroize", -] - -[[package]] -name = "enum-iterator" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" -dependencies = [ - "enum-iterator-derive", -] - -[[package]] -name = "enum-iterator-derive" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "ff" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "131655483be284720a17d74ff97592b8e76576dc25563148601df2d7c9080924" -dependencies = [ - "rand_core 0.6.3", - "subtle 2.4.1", -] - -[[package]] -name = "form_urlencoded" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" -dependencies = [ - "matches", - "percent-encoding", -] - -[[package]] -name = "forward_ref" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8cbd1169bd7b4a0a20d92b9af7a7e0422888bd38a6f5ec29c1fd8c1558a272e" - -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - -[[package]] -name = "generic-array" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.10.2+wasi-snapshot-preview1", -] - -[[package]] -name = "getset" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e45727250e75cc04ff2846a66397da8ef2b3db8e40e0cef4df67950a07621eb9" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "git2" -version = "0.13.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29229cc1b24c0e6062f6e742aa3e256492a5323365e5ed3413599f8a5eff7d6" -dependencies = [ - "bitflags", - "libc", - "libgit2-sys", - "log", - "url", -] - -[[package]] -name = "group" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" -dependencies = [ - "ff", - "rand_core 0.6.3", - "subtle 2.4.1", -] - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - -[[package]] -name = "hkdf" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01706d578d5c281058480e673ae4086a9f4710d8df1ad80a5b03e39ece5f886b" -dependencies = [ - "digest 0.9.0", - "hmac", -] - -[[package]] -name = "hmac" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b" -dependencies = [ - "crypto-mac 0.11.1", - "digest 0.9.0", -] - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "humantime-serde" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a3db5ea5923d99402c94e9feb261dc5ee9b4efa158b0315f788cf549cc200c" -dependencies = [ - "humantime", - "serde", -] - -[[package]] -name = "idna" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" -dependencies = [ - "matches", - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "itertools" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" - -[[package]] -name = "jobserver" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" -dependencies = [ - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc9ffccd38c451a86bf13657df244e9c3f37493cce8e5e21e940963777acc84" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "k256" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19c3a5e0a0b8450278feda242592512e09f61c72e018b8cd5c859482802daf2d" -dependencies = [ - "cfg-if", - "ecdsa", - "elliptic-curve", - "sec1", - "sha2", -] - -[[package]] -name = "keystream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33070833c9ee02266356de0c43f723152bd38bd96ddf52c82b3af10c9138b28" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.112" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125" - -[[package]] -name = "libgit2-sys" -version = "0.12.26+1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e1c899248e606fbfe68dcb31d8b0176ebab833b103824af31bddf4b7457494" -dependencies = [ - "cc", - "libc", - "libz-sys", - "pkg-config", -] - -[[package]] -name = "libm" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" - -[[package]] -name = "libz-sys" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "lioness" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9" -dependencies = [ - "arrayref", - "blake2", - "chacha", - "keystream", -] - -[[package]] -name = "log" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "matches" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" - -[[package]] -name = "mixnet-contract" -version = "1.1.0" -dependencies = [ - "bs58", - "cosmwasm-schema", - "cosmwasm-std", - "cosmwasm-storage", - "crypto", - "cw-storage-plus", - "mixnet-contract-common", - "rand_chacha", - "schemars", - "serde", - "thiserror", - "time 0.3.6", - "vergen", - "vesting-contract-common", -] - -[[package]] -name = "mixnet-contract-common" -version = "0.1.0" -dependencies = [ - "bs58", - "contracts-common", - "cosmwasm-std", - "humantime-serde", - "log", - "schemars", - "serde", - "serde_json", - "serde_repr", - "thiserror", - "time 0.3.6", -] - -[[package]] -name = "multisig-contract-common" -version = "0.1.0" -dependencies = [ - "cosmwasm-std", - "cw-utils", - "cw3", - "cw3-fixed-multisig", - "cw4", - "schemars", - "serde", -] - -[[package]] -name = "num-integer" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_threads" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71a1eb3a36534514077c1e079ada2fb170ef30c47d203aa6916138cf882ecd52" -dependencies = [ - "libc", -] - -[[package]] -name = "nymsphinx-types" -version = "0.1.0" -dependencies = [ - "sphinx", -] - -[[package]] -name = "once_cell" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" - -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "pem" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" -dependencies = [ - "base64", - "once_cell", - "regex", -] - -[[package]] -name = "pemstore" -version = "0.1.0" -dependencies = [ - "pem", -] - -[[package]] -name = "percent-encoding" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" - -[[package]] -name = "pkcs8" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" -dependencies = [ - "der", - "spki", - "zeroize", -] - -[[package]] -name = "pkg-config" -version = "0.3.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58893f751c9b0412871a09abd62ecd2a00298c6c83befa223ef98c52aef40cbe" - -[[package]] -name = "ppv-lite86" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro2" -version = "1.0.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "prost" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" -dependencies = [ - "bytes", - "prost-derive", -] - -[[package]] -name = "prost-derive" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" -dependencies = [ - "anyhow", - "itertools", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "quote" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha", - "rand_core 0.5.1", - "rand_hc", -] - -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - -[[package]] -name = "rand_core" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" -dependencies = [ - "getrandom 0.2.3", -] - -[[package]] -name = "rand_distr" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9532ada3929fb8b2e9dbe28d1e06c9b2cc65813f074fcb6bd5fbefeff9d56" -dependencies = [ - "num-traits", - "rand", -] - -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "regex" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" - -[[package]] -name = "rfc6979" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ef608575f6392792f9ecf7890c00086591d29a83910939d430753f7c050525" -dependencies = [ - "crypto-bigint", - "hmac", - "zeroize", -] - -[[package]] -name = "rustc_version" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" -dependencies = [ - "semver", -] - -[[package]] -name = "rustversion" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" - -[[package]] -name = "ryu" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" - -[[package]] -name = "schemars" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6b5a3c80cea1ab61f4260238409510e814e38b4b563c06044edf91e7dc070e3" -dependencies = [ - "dyn-clone", - "schemars_derive", - "serde", - "serde_json", -] - -[[package]] -name = "schemars_derive" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ae4dce13e8614c46ac3c38ef1c0d668b101df6ac39817aebdaa26642ddae9b" -dependencies = [ - "proc-macro2", - "quote", - "serde_derive_internals", - "syn", -] - -[[package]] -name = "sec1" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08da66b8b0965a5555b6bd6639e68ccba85e1e2506f5fbb089e93f8a04e1a2d1" -dependencies = [ - "der", - "generic-array 0.14.5", - "pkcs8", - "subtle 2.4.1", - "zeroize", -] - -[[package]] -name = "semver" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "568a8e6258aa33c13358f81fd834adb854c6f7c9468520910a9b1e8fac068012" - -[[package]] -name = "serde" -version = "1.0.133" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97565067517b60e2d1ea8b268e59ce036de907ac523ad83a0475da04e818989a" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde-json-wasm" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479b4dbc401ca13ee8ce902851b834893251404c4f3c65370a49e047a6be09a5" -dependencies = [ - "serde", -] - -[[package]] -name = "serde_derive" -version = "1.0.133" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_derive_internals" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dbab34ca63057a1f15280bdf3c39f2b1eb1b54c17e98360e511637aef7418c6" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2bb9cd061c5865d345bb02ca49fcef1391741b672b54a0bf7b679badec3142" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_repr" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98d0516900518c29efa217c298fa1f4e6c6ffc85ae29fd7f4ee48f176e1a9ed5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "sha2" -version = "0.9.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" -dependencies = [ - "block-buffer", - "cfg-if", - "cpufeatures", - "digest 0.9.0", - "opaque-debug 0.3.0", -] - -[[package]] -name = "signature" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2807892cfa58e081aa1f1111391c7a0649d4fa127a4ffbe34bcbfb35a1171a4" -dependencies = [ - "digest 0.9.0", - "rand_core 0.6.3", -] - -[[package]] -name = "sphinx" -version = "0.1.0" -source = "git+https://github.com/nymtech/sphinx?rev=c494250f2a78bed33a618d470792418eee932859#c494250f2a78bed33a618d470792418eee932859" -dependencies = [ - "aes", - "arrayref", - "blake2", - "bs58", - "byteorder", - "chacha", - "curve25519-dalek", - "digest 0.9.0", - "hkdf", - "hmac", - "lioness", - "log", - "rand", - "rand_distr", - "sha2", - "subtle 2.4.1", -] - -[[package]] -name = "spki" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "subtle" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" - -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - -[[package]] -name = "subtle-encoding" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" -dependencies = [ - "zeroize", -] - -[[package]] -name = "syn" -version = "1.0.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a684ac3dcd8913827e18cd09a68384ee66c1de24157e3c556c9ab16d85695fb7" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "synstructure" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] - -[[package]] -name = "thiserror" -version = "1.0.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "time" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d54b9298e05179c335de2b9645d061255bcd5155f843b3e328d2cfe0a5b413" -dependencies = [ - "itoa", - "libc", - "num_threads", - "time-macros", -] - -[[package]] -name = "time-macros" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" - -[[package]] -name = "tinyvec" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" - -[[package]] -name = "typenum" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" - -[[package]] -name = "uint" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" - -[[package]] -name = "unicode-normalization" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "url" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" -dependencies = [ - "form_urlencoded", - "idna", - "matches", - "percent-encoding", -] - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "vergen" -version = "5.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cf88d94e969e7956d924ba70741316796177fa0c79a2c9f4ab04998d96e966e" -dependencies = [ - "anyhow", - "cfg-if", - "chrono", - "enum-iterator", - "getset", - "git2", - "rustc_version", - "rustversion", - "thiserror", -] - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "vesting-contract" -version = "1.1.0" -dependencies = [ - "contracts-common", - "cosmwasm-std", - "cw-storage-plus", - "mixnet-contract-common", - "schemars", - "serde", - "thiserror", - "vergen", - "vesting-contract-common", -] - -[[package]] -name = "vesting-contract-common" -version = "0.1.0" -dependencies = [ - "contracts-common", - "cosmwasm-std", - "mixnet-contract-common", - "schemars", - "serde", -] - -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - -[[package]] -name = "wasm-bindgen" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a317bf8f9fba2476b4b2c85ef4c4af8ff39c3c7f0cdfeed4f82c34a880aa837b" -dependencies = [ - "bumpalo", - "lazy_static", - "log", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d56146e7c495528bf6587663bea13a8eb588d39b36b679d83972e1a2dbbdacf9" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7803e0eea25835f8abdc585cd3021b3deb11543c6fe226dcd30b228857c5c5ab" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "x25519-dalek" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a0c105152107e3b96f6a00a65e86ce82d9b125230e1c4302940eca58ff71f4f" -dependencies = [ - "curve25519-dalek", - "rand_core 0.5.1", - "zeroize", -] - -[[package]] -name = "zeroize" -version = "1.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" -dependencies = [ - "zeroize_derive", -] - -[[package]] -name = "zeroize_derive" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] diff --git a/contracts/mixnet/src/constants.rs b/contracts/mixnet/src/constants.rs index 3d3ed82caf..882b89def4 100644 --- a/contracts/mixnet/src/constants.rs +++ b/contracts/mixnet/src/constants.rs @@ -35,6 +35,9 @@ pub const INTERVAL_EVENTS_MAX_RETRIEVAL_LIMIT: u32 = 250; pub const REWARDED_SET_DEFAULT_RETRIEVAL_LIMIT: u32 = 500; pub const REWARDED_SET_MAX_RETRIEVAL_LIMIT: u32 = 1000; +pub const FAMILIES_DEFAULT_RETRIEVAL_LIMIT: u32 = 10; +pub const FAMILIES_MAX_RETRIEVAL_LIMIT: u32 = 20; + // storage keys pub(crate) const DELEGATION_PK_NAMESPACE: &str = "dl"; pub(crate) const DELEGATION_OWNER_IDX_NAMESPACE: &str = "dlo"; @@ -69,3 +72,7 @@ pub(crate) const UNBONDED_MIXNODES_IDENTITY_IDX_NAMESPACE: &str = "umi"; pub(crate) const REWARDING_PARAMS_KEY: &str = "rparams"; pub(crate) const PENDING_REWARD_POOL_KEY: &str = "prp"; pub(crate) const MIXNODES_REWARDING_PK_NAMESPACE: &str = "mnr"; + +pub(crate) const FAMILIES_INDEX_NAMESPACE: &str = "faml2"; +pub(crate) const FAMILIES_MAP_NAMESPACE: &str = "fam2"; +pub(crate) const MEMBERS_MAP_NAMESPACE: &str = "memb2"; diff --git a/contracts/mixnet/src/contract.rs b/contracts/mixnet/src/contract.rs index 63bf3f1a72..ade53d7d89 100644 --- a/contracts/mixnet/src/contract.rs +++ b/contracts/mixnet/src/contract.rs @@ -1,6 +1,5 @@ // Copyright 2021-2022 - Nym Technologies SA // SPDX-License-Identifier: Apache-2.0 - use crate::constants::{INITIAL_GATEWAY_PLEDGE_AMOUNT, INITIAL_MIXNODE_PLEDGE_AMOUNT}; use crate::interval::storage as interval_storage; use crate::mixnet_contract_settings::storage as mixnet_params_storage; @@ -82,6 +81,69 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { + ExecuteMsg::AssignNodeLayer { mix_id, layer } => { + crate::mixnodes::transactions::assign_mixnode_layer(deps, info, mix_id, layer) + } + // families + ExecuteMsg::CreateFamily { + owner_signature, + label, + } => crate::families::transactions::try_create_family(deps, info, owner_signature, &label), + ExecuteMsg::JoinFamily { + signature, + family_head, + } => crate::families::transactions::try_join_family(deps, info, signature, family_head), + ExecuteMsg::LeaveFamily { + signature, + family_head, + } => crate::families::transactions::try_leave_family(deps, info, signature, family_head), + ExecuteMsg::KickFamilyMember { signature, member } => { + crate::families::transactions::try_head_kick_member(deps, info, signature, &member) + } + ExecuteMsg::CreateFamilyOnBehalf { + owner_address, + owner_signature, + label, + } => crate::families::transactions::try_create_family_on_behalf( + deps, + info, + owner_address, + owner_signature, + &label, + ), + ExecuteMsg::JoinFamilyOnBehalf { + member_address, + signature, + family_head, + } => crate::families::transactions::try_join_family_on_behalf( + deps, + info, + member_address, + signature, + family_head, + ), + ExecuteMsg::LeaveFamilyOnBehalf { + member_address, + signature, + family_head, + } => crate::families::transactions::try_leave_family_on_behalf( + deps, + info, + member_address, + signature, + family_head, + ), + ExecuteMsg::KickFamilyMemberOnBehalf { + head_address, + signature, + member, + } => crate::families::transactions::try_head_kick_member_on_behalf( + deps, + info, + head_address, + signature, + &member, + ), // state/sys-params-related ExecuteMsg::UpdateRewardingValidatorAddress { address } => { crate::mixnet_contract_settings::transactions::try_update_rewarding_validator_address( @@ -129,6 +191,7 @@ pub fn execute( ), ExecuteMsg::AdvanceCurrentEpoch { new_rewarded_set, + // families_in_layer, expected_active_set_size, } => crate::interval::transactions::try_advance_epoch( deps, @@ -285,6 +348,24 @@ pub fn query( msg: QueryMsg, ) -> Result { let query_res = match msg { + QueryMsg::GetAllFamiliesPaged { limit, start_after } => to_binary( + &crate::families::queries::get_all_families_paged(deps.storage, start_after, limit)?, + ), + QueryMsg::GetAllMembersPaged { limit, start_after } => to_binary( + &crate::families::queries::get_all_members_paged(deps.storage, start_after, limit)?, + ), + QueryMsg::GetFamilyByHead { head } => to_binary( + &crate::families::queries::get_family_by_head(&head, deps.storage)?, + ), + QueryMsg::GetFamilyByLabel { label } => to_binary( + &crate::families::queries::get_family_by_label(&label, deps.storage)?, + ), + QueryMsg::GetFamilyMembersByHead { head } => to_binary( + &crate::families::queries::get_family_members_by_head(&head, deps.storage)?, + ), + QueryMsg::GetFamilyMembersByLabel { label } => to_binary( + &crate::families::queries::get_family_members_by_label(&label, deps.storage)?, + ), QueryMsg::GetContractVersion {} => { to_binary(&crate::mixnet_contract_settings::queries::query_contract_version()) } diff --git a/contracts/mixnet/src/families/mod.rs b/contracts/mixnet/src/families/mod.rs new file mode 100644 index 0000000000..f355b587a7 --- /dev/null +++ b/contracts/mixnet/src/families/mod.rs @@ -0,0 +1,3 @@ +pub mod queries; +pub mod storage; +pub mod transactions; diff --git a/contracts/mixnet/src/families/queries.rs b/contracts/mixnet/src/families/queries.rs new file mode 100644 index 0000000000..238c38aaa7 --- /dev/null +++ b/contracts/mixnet/src/families/queries.rs @@ -0,0 +1,107 @@ +use std::collections::HashSet; + +use crate::constants::{FAMILIES_DEFAULT_RETRIEVAL_LIMIT, FAMILIES_MAX_RETRIEVAL_LIMIT}; + +use super::storage::{families, get_family, get_members, MEMBERS}; +use cosmwasm_std::{Order, Storage}; +use cw_storage_plus::Bound; +use mixnet_contract_common::families::{Family, FamilyHead}; +use mixnet_contract_common::{error::MixnetContractError, IdentityKeyRef}; +use mixnet_contract_common::{IdentityKey, PagedFamiliesResponse, PagedMembersResponse}; + +pub fn get_family_by_label( + label: &str, + storage: &dyn Storage, +) -> Result, MixnetContractError> { + Ok(families() + .idx + .label + .item(storage, label.to_string())? + .map(|o| o.1)) +} + +pub fn get_family_by_head( + head: IdentityKeyRef<'_>, + storage: &dyn Storage, +) -> Result { + let family_head = FamilyHead::new(head); + get_family(&family_head, storage) +} + +pub fn get_family_members_by_head( + head: IdentityKeyRef<'_>, + storage: &dyn Storage, +) -> Result, MixnetContractError> { + let family_head = FamilyHead::new(head); + let family = get_family(&family_head, storage)?; + get_members(&family, storage) +} + +pub fn get_family_members_by_label( + label: &str, + storage: &dyn Storage, +) -> Result>, MixnetContractError> { + if let Some(family) = families() + .idx + .label + .item(storage, label.to_string())? + .map(|o| o.1) + { + Ok(Some(get_members(&family, storage)?)) + } else { + Ok(None) + } +} + +pub fn get_all_families_paged( + storage: &dyn Storage, + start_after: Option, + limit: Option, +) -> Result { + let limit = limit + .unwrap_or(FAMILIES_DEFAULT_RETRIEVAL_LIMIT) + .min(FAMILIES_MAX_RETRIEVAL_LIMIT) as usize; + + let start = start_after.map(Bound::exclusive); + + let response = families() + .range(storage, start, None, Order::Ascending) + .take(limit) + .filter_map(|r| r.ok()) + .map(|(_key, family)| family) + .collect::>(); + + let start_next_after = response + .last() + .map(|response| response.head_identity().to_string()); + + Ok(PagedFamiliesResponse { + families: response, + start_next_after, + }) +} + +pub fn get_all_members_paged( + storage: &dyn Storage, + start_after: Option, + limit: Option, +) -> Result { + let limit = limit + .unwrap_or(FAMILIES_DEFAULT_RETRIEVAL_LIMIT) + .min(FAMILIES_MAX_RETRIEVAL_LIMIT) as usize; + + let start = start_after.map(Bound::exclusive); + + let response = MEMBERS + .range(storage, start, None, Order::Ascending) + .take(limit) + .filter_map(|r| r.ok()) + .collect::>(); + + let start_next_after = response.last().map(|r| r.0.clone()); + + Ok(PagedMembersResponse { + members: response, + start_next_after, + }) +} diff --git a/contracts/mixnet/src/families/storage.rs b/contracts/mixnet/src/families/storage.rs new file mode 100644 index 0000000000..2a99f9054e --- /dev/null +++ b/contracts/mixnet/src/families/storage.rs @@ -0,0 +1,99 @@ +use std::collections::HashSet; + +use cosmwasm_std::{Order, StdError, Storage}; +use cw_storage_plus::{Index, IndexList, IndexedMap, Map, UniqueIndex}; +use mixnet_contract_common::families::{Family, FamilyHead}; +use mixnet_contract_common::{error::MixnetContractError, IdentityKey, IdentityKeyRef}; + +use crate::constants::{FAMILIES_INDEX_NAMESPACE, FAMILIES_MAP_NAMESPACE, MEMBERS_MAP_NAMESPACE}; + +pub struct FamilyIndex<'a> { + pub label: UniqueIndex<'a, String, Family>, +} + +impl<'a> IndexList for FamilyIndex<'a> { + fn get_indexes(&'_ self) -> Box> + '_> { + let v: Vec<&dyn Index> = vec![&self.label]; + Box::new(v.into_iter()) + } +} + +// storage access function. +pub fn families<'a>() -> IndexedMap<'a, String, Family, FamilyIndex<'a>> { + let indexes = FamilyIndex { + label: UniqueIndex::new(|d| d.label().to_string(), FAMILIES_INDEX_NAMESPACE), + }; + IndexedMap::new(FAMILIES_MAP_NAMESPACE, indexes) +} + +pub const MEMBERS: Map = Map::new(MEMBERS_MAP_NAMESPACE); + +pub fn get_members( + family: &Family, + store: &dyn Storage, +) -> Result, MixnetContractError> { + Ok(MEMBERS + .range(store, None, None, Order::Ascending) + .filter_map(|res| res.ok()) + .filter(|(_member, head)| head == family.head()) + .map(|(member, _storage_key)| member) + .collect()) +} + +pub fn get_family(head: &FamilyHead, store: &dyn Storage) -> Result { + let key = head.identity(); + if let Some(family) = families().may_load(store, key.to_string())? { + Ok(family) + } else { + Err(MixnetContractError::FamilyDoesNotExist { + head: head.identity().to_string(), + }) + } +} + +pub fn create_family(f: &Family, store: &mut dyn Storage) -> Result<(), MixnetContractError> { + match families().save(store, f.head_identity().to_string(), f) { + Ok(()) => Ok(()), + Err(e) => match &e { + StdError::GenericErr { msg } => { + if msg.starts_with("Violates unique constraint") { + Err(MixnetContractError::FamilyWithLabelExists( + f.label().to_string(), + )) + } else { + Err(e.into()) + } + } + _ => Err(e.into()), + }, + } +} + +pub fn add_family_member( + f: &Family, + store: &mut dyn Storage, + member: IdentityKeyRef<'_>, +) -> Result<(), MixnetContractError> { + Ok(MEMBERS.save(store, member.to_string(), f.head())?) +} + +pub fn remove_family_member(store: &mut dyn Storage, member: IdentityKeyRef<'_>) { + MEMBERS.remove(store, member.to_string()) +} + +#[allow(dead_code)] +pub fn is_family_member( + store: &dyn Storage, + f: &Family, + member: IdentityKeyRef<'_>, +) -> Result { + let m = get_members(f, store)?; + Ok(m.contains(member)) +} + +pub fn is_any_member( + store: &dyn Storage, + member: IdentityKeyRef<'_>, +) -> Result, MixnetContractError> { + Ok(MEMBERS.may_load(store, member.to_string())?) +} diff --git a/contracts/mixnet/src/families/transactions.rs b/contracts/mixnet/src/families/transactions.rs new file mode 100644 index 0000000000..7f9412f1f2 --- /dev/null +++ b/contracts/mixnet/src/families/transactions.rs @@ -0,0 +1,414 @@ +use crate::support::helpers::{ + ensure_bonded, validate_family_signature, validate_node_identity_signature, +}; + +use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response}; +use mixnet_contract_common::families::{Family, FamilyHead}; +use mixnet_contract_common::{error::MixnetContractError, IdentityKey, IdentityKeyRef}; + +use super::storage::{ + add_family_member, create_family, get_family, is_any_member, is_family_member, + remove_family_member, +}; + +/// Creates a new MixNode family with senders node as head +pub fn try_create_family( + deps: DepsMut, + info: MessageInfo, + owner_signature: String, + label: &str, +) -> Result { + _try_create_family(deps, &info.sender, owner_signature, label, None) +} + +pub fn try_create_family_on_behalf( + deps: DepsMut, + info: MessageInfo, + owner_address: String, + owner_signature: String, + label: &str, +) -> Result { + let owner_address = deps.api.addr_validate(&owner_address)?; + _try_create_family( + deps, + &owner_address, + owner_signature, + label, + Some(info.sender), + ) +} + +fn _try_create_family( + deps: DepsMut, + owner: &Addr, + owner_signature: String, + label: &str, + proxy: Option, +) -> Result { + let existing_bond = crate::mixnodes::storage::mixnode_bonds() + .idx + .owner + .item(deps.storage, owner.clone())? + .ok_or(MixnetContractError::NoAssociatedMixNodeBond { + owner: owner.clone(), + })? + .1; + + ensure_bonded(&existing_bond)?; + + validate_node_identity_signature( + deps.as_ref(), + owner, + &owner_signature, + existing_bond.identity(), + )?; + + let family_head = FamilyHead::new(existing_bond.identity()); + + if let Ok(_family) = get_family(&family_head, deps.storage) { + return Err(MixnetContractError::FamilyCanHaveOnlyOne); + } + + let family = Family::new(family_head, proxy, label); + create_family(&family, deps.storage)?; + Ok(Response::default()) +} + +pub fn try_join_family( + deps: DepsMut, + info: MessageInfo, + signature: String, + family_head: IdentityKey, +) -> Result { + let family_head = FamilyHead::new(&family_head); + _try_join_family(deps, &info.sender, signature, family_head) +} + +pub fn try_join_family_on_behalf( + deps: DepsMut, + _info: MessageInfo, + member_address: String, + signature: String, + family_head: IdentityKey, +) -> Result { + let member_address = deps.api.addr_validate(&member_address)?; + let family_head = FamilyHead::new(&family_head); + _try_join_family(deps, &member_address, signature, family_head) +} + +fn _try_join_family( + deps: DepsMut, + owner: &Addr, + signature: String, + family_head: FamilyHead, +) -> Result { + let existing_bond = crate::mixnodes::storage::mixnode_bonds() + .idx + .owner + .item(deps.storage, owner.clone())? + .ok_or(MixnetContractError::NoAssociatedMixNodeBond { + owner: owner.clone(), + })? + .1; + + ensure_bonded(&existing_bond)?; + + if family_head.identity() == existing_bond.identity() { + return Err(MixnetContractError::CantJoinOwnFamily { + head: family_head.identity().to_string(), + member: existing_bond.identity().to_string(), + }); + } + + if let Some(family) = is_any_member(deps.storage, existing_bond.identity())? { + return Err(MixnetContractError::AlreadyMemberOfFamily( + family.identity().to_string(), + )); + } + + validate_family_signature( + deps.as_ref(), + existing_bond.identity(), + &signature, + family_head.identity(), + )?; + + let family = get_family(&family_head, deps.storage)?; + + add_family_member(&family, deps.storage, existing_bond.identity())?; + + Ok(Response::default()) +} + +pub fn try_leave_family( + deps: DepsMut, + info: MessageInfo, + signature: String, + family_head: IdentityKey, +) -> Result { + let family_head = FamilyHead::new(&family_head); + _try_leave_family(deps, &info.sender, signature, family_head) +} + +pub fn try_leave_family_on_behalf( + deps: DepsMut, + _info: MessageInfo, + member_address: String, + signature: String, + family_head: IdentityKey, +) -> Result { + let family_head = FamilyHead::new(&family_head); + let member_address = deps.api.addr_validate(&member_address)?; + _try_leave_family(deps, &member_address, signature, family_head) +} + +fn _try_leave_family( + deps: DepsMut, + owner: &Addr, + signature: String, + family_head: FamilyHead, +) -> Result { + let existing_bond = crate::mixnodes::storage::mixnode_bonds() + .idx + .owner + .item(deps.storage, owner.clone())? + .ok_or(MixnetContractError::NoAssociatedMixNodeBond { + owner: owner.clone(), + })? + .1; + + ensure_bonded(&existing_bond)?; + + if family_head.identity() == existing_bond.identity() { + return Err(MixnetContractError::CantLeaveOwnFamily { + head: family_head.identity().to_string(), + member: existing_bond.identity().to_string(), + }); + } + + let family = get_family(&family_head, deps.storage)?; + if !is_family_member(deps.storage, &family, existing_bond.identity())? { + return Err(MixnetContractError::NotAMember { + head: family_head.identity().to_string(), + member: existing_bond.identity().to_string(), + }); + } + + validate_family_signature( + deps.as_ref(), + existing_bond.identity(), + &signature, + family_head.identity(), + )?; + + remove_family_member(deps.storage, existing_bond.identity()); + + Ok(Response::default()) +} + +pub fn try_head_kick_member( + deps: DepsMut, + info: MessageInfo, + owner_signature: String, + member: IdentityKeyRef, +) -> Result { + _try_head_kick_member(deps, &info.sender, owner_signature, member) +} + +pub fn try_head_kick_member_on_behalf( + deps: DepsMut, + _info: MessageInfo, + head_address: String, + owner_signature: String, + member: IdentityKeyRef, +) -> Result { + let head_address = deps.api.addr_validate(&head_address)?; + _try_head_kick_member(deps, &head_address, owner_signature, member) +} + +#[allow(unused_variables)] +fn _try_head_kick_member( + deps: DepsMut, + owner: &Addr, + owner_signature: String, + member: IdentityKeyRef<'_>, +) -> Result { + Err(MixnetContractError::NotImplemented) + // let existing_bond = crate::mixnodes::storage::mixnode_bonds() + // .idx + // .owner + // .item(deps.storage, owner.clone())? + // .ok_or(MixnetContractError::NoAssociatedMixNodeBond { + // owner: owner.clone(), + // })? + // .1; + + // ensure_bonded(&existing_bond)?; + + // validate_node_identity_signature( + // deps.as_ref(), + // owner, + // &owner_signature, + // existing_bond.identity(), + // )?; + + // let family_head = FamilyHead::new(existing_bond.identity()); + // let family = get_family(&family_head, deps.storage)?; + // if !is_family_member(deps.storage, &family, member)? { + // return Err(MixnetContractError::NotAMember { + // head: family_head.identity().to_string(), + // member: existing_bond.identity().to_string(), + // }); + // } + + // remove_family_member(deps.storage, member); + // Ok(Response::default()) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::families::queries::{get_family_by_head, get_family_by_label}; + use crate::families::storage::is_family_member; + use crate::mixnet_contract_settings::storage::minimum_mixnode_pledge; + use crate::support::tests::{fixtures, test_helpers}; + use cosmwasm_std::testing::{mock_env, mock_info}; + + #[test] + fn test_family_crud() { + let mut deps = test_helpers::init_contract(); + let env = mock_env(); + let mut rng = test_helpers::test_rng(); + + let head = "alice"; + let malicious_head = "timmy"; + + let minimum_pledge = minimum_mixnode_pledge(deps.as_ref().storage).unwrap(); + + let (head_mixnode, head_sig, head_keypair) = + test_helpers::mixnode_with_signature(&mut rng, head); + + let (malicious_mixnode, malicious_sig, _malicious_keypair) = + test_helpers::mixnode_with_signature(&mut rng, malicious_head); + + let cost_params = fixtures::mix_node_cost_params_fixture(); + + let member = "bob"; + let (member_mixnode, member_sig, _) = + test_helpers::mixnode_with_signature(&mut rng, member); + + // we are informed that we didn't send enough funds + crate::mixnodes::transactions::try_add_mixnode( + deps.as_mut(), + env.clone(), + mock_info(head, &[minimum_pledge.clone()]), + head_mixnode.clone(), + cost_params.clone(), + head_sig.clone(), + ) + .unwrap(); + + crate::mixnodes::transactions::try_add_mixnode( + deps.as_mut(), + env.clone(), + mock_info(malicious_head, &[minimum_pledge.clone()]), + malicious_mixnode.clone(), + cost_params.clone(), + malicious_sig.clone(), + ) + .unwrap(); + + crate::mixnodes::transactions::try_add_mixnode( + deps.as_mut(), + env.clone(), + mock_info(member, &[minimum_pledge.clone()]), + member_mixnode.clone(), + cost_params.clone(), + member_sig.clone(), + ) + .unwrap(); + + try_create_family( + deps.as_mut(), + mock_info(head.clone(), &[]), + head_sig.clone(), + "test", + ) + .unwrap(); + let family_head = FamilyHead::new(&head_mixnode.identity_key); + assert!(get_family(&family_head, &deps.storage).is_ok()); + + let nope = try_create_family( + deps.as_mut(), + mock_info(malicious_head.clone(), &[]), + malicious_sig.clone(), + "test", + ); + + match nope { + Ok(_) => panic!("This should fail, since family with label already exists"), + Err(e) => match e { + MixnetContractError::FamilyWithLabelExists(label) => assert_eq!(label, "test"), + _ => panic!("This should return FamilyWithLabelExists"), + }, + } + + let family = get_family_by_label("test", &deps.storage).unwrap(); + assert!(family.is_some()); + assert_eq!(family.unwrap().head_identity(), family_head.identity()); + + let family = get_family_by_head(family_head.identity(), &deps.storage).unwrap(); + assert_eq!(family.head_identity(), family_head.identity()); + + let join_signature = head_keypair + .private_key() + .sign(member_mixnode.identity_key.as_bytes()) + .to_base58_string(); + + try_join_family( + deps.as_mut(), + mock_info(member, &[]), + join_signature.clone(), + head_mixnode.identity_key.clone(), + ) + .unwrap(); + + let family = get_family(&family_head, &deps.storage).unwrap(); + + assert!(is_family_member(&deps.storage, &family, &member_mixnode.identity_key).unwrap()); + + try_leave_family( + deps.as_mut(), + mock_info(member, &[]), + join_signature.clone(), + head_mixnode.identity_key.clone(), + ) + .unwrap(); + + let family = get_family(&family_head, &deps.storage).unwrap(); + assert!(!is_family_member(&deps.storage, &family, &member_mixnode.identity_key).unwrap()); + + try_join_family( + deps.as_mut(), + mock_info(member, &[]), + join_signature.clone(), + head_mixnode.identity_key.clone(), + ) + .unwrap(); + + let family = get_family(&family_head, &deps.storage).unwrap(); + + assert!(is_family_member(&deps.storage, &family, &member_mixnode.identity_key).unwrap()); + + // try_head_kick_member( + // deps.as_mut(), + // mock_info(&head, &[]), + // head_sig.clone(), + // &member_mixnode.identity_key.clone(), + // ) + // .unwrap(); + + // let family = get_family(&family_head, &deps.storage).unwrap(); + // assert!(!is_family_member(&deps.storage, &family, &member_mixnode.identity_key).unwrap()); + } +} diff --git a/contracts/mixnet/src/gateways/transactions.rs b/contracts/mixnet/src/gateways/transactions.rs index 55973f7589..dc53817987 100644 --- a/contracts/mixnet/src/gateways/transactions.rs +++ b/contracts/mixnet/src/gateways/transactions.rs @@ -82,7 +82,7 @@ pub(crate) fn _try_add_gateway( validate_node_identity_signature( deps.as_ref(), &owner, - owner_signature, + &owner_signature, &gateway.identity_key, )?; diff --git a/contracts/mixnet/src/interval/transactions.rs b/contracts/mixnet/src/interval/transactions.rs index 705f36804a..a55978e4e4 100644 --- a/contracts/mixnet/src/interval/transactions.rs +++ b/contracts/mixnet/src/interval/transactions.rs @@ -5,6 +5,7 @@ use super::storage; use crate::interval::helpers::change_interval_config; use crate::interval::pending_events::ContractExecutableEvent; use crate::interval::storage::push_new_interval_event; +use crate::mixnodes::transactions::update_mixnode_layer; use crate::rewards; use crate::rewards::storage as rewards_storage; use crate::support::helpers::{ensure_is_authorized, ensure_is_owner}; @@ -16,7 +17,7 @@ use mixnet_contract_common::events::{ new_reconcile_pending_events, }; use mixnet_contract_common::pending_events::PendingIntervalEventKind; -use mixnet_contract_common::MixId; +use mixnet_contract_common::{LayerAssignment, MixId}; use std::collections::BTreeSet; // those two should be called in separate tx (from advancing epoch), @@ -202,7 +203,7 @@ pub fn try_advance_epoch( mut deps: DepsMut<'_>, env: Env, info: MessageInfo, - new_rewarded_set: Vec, + layer_assignments: Vec, expected_active_set_size: u32, ) -> Result { // Only rewarding validator can attempt to advance epoch @@ -247,12 +248,18 @@ pub fn try_advance_epoch( } let updated_interval = current_interval.advance_epoch(); - let num_nodes = new_rewarded_set.len(); + let num_nodes = layer_assignments.len(); + + let new_rewarded_set = layer_assignments.iter().map(|l| l.mix_id()).collect(); // finally save updated interval and the rewarded set storage::save_interval(deps.storage, &updated_interval)?; update_rewarded_set(deps.storage, new_rewarded_set, expected_active_set_size)?; + for a in layer_assignments { + update_mixnode_layer(a.mix_id(), a.layer(), deps.storage)?; + } + Ok(response.add_event(new_advance_epoch_event(updated_interval, num_nodes as u32))) } @@ -1133,30 +1140,40 @@ mod tests { #[cfg(test)] mod advancing_epoch { use super::*; + use crate::mixnodes::queries::query_mixnode_details; use crate::rewards::models::RewardPoolChange; use crate::support::tests::fixtures::TEST_COIN_DENOM; use cosmwasm_std::testing::mock_info; - use cosmwasm_std::{coin, coins, BankMsg, Decimal, Empty, SubMsg}; + use cosmwasm_std::{coin, coins, BankMsg, Decimal, Empty, SubMsg, Uint128}; use mixnet_contract_common::events::{ new_delegation_on_unbonded_node_event, new_rewarding_params_update_event, }; use mixnet_contract_common::reward_params::IntervalRewardingParamsUpdate; - use mixnet_contract_common::RewardedSetNodeStatus; + use mixnet_contract_common::{Layer, RewardedSetNodeStatus}; #[test] fn can_only_be_performed_by_specified_rewarding_validator() { let mut test = TestSetup::new(); + test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("2", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("3", Some(Uint128::new(100000000))); let current_active_set = test.rewarding_params().active_set_size; let some_sender = mock_info("foomper", &[]); test.skip_to_current_epoch_end(); + let layer_assignments = vec![ + LayerAssignment::new(1, Layer::One), + LayerAssignment::new(2, Layer::Two), + LayerAssignment::new(3, Layer::Three), + ]; + let env = test.env(); let res = try_advance_epoch( test.deps_mut(), env, some_sender, - vec![1, 2, 3], + layer_assignments.clone(), current_active_set, ); assert_eq!(res, Err(MixnetContractError::Unauthorized)); @@ -1168,9 +1185,10 @@ mod tests { test.deps_mut(), env, sender, - vec![1, 2, 3], + layer_assignments, current_active_set, ); + println!("{:?}", res); assert!(res.is_ok()) } @@ -1179,13 +1197,23 @@ mod tests { let mut test = TestSetup::new(); let current_active_set = test.rewarding_params().active_set_size; + test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("2", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("3", Some(Uint128::new(100000000))); + + let layer_assignments = vec![ + LayerAssignment::new(1, Layer::One), + LayerAssignment::new(2, Layer::Two), + LayerAssignment::new(3, Layer::Three), + ]; + let env = test.env(); let sender = test.rewarding_validator(); let res = try_advance_epoch( test.deps_mut(), env, sender.clone(), - vec![1, 2, 3], + layer_assignments.clone(), current_active_set, ); assert!(matches!( @@ -1193,6 +1221,24 @@ mod tests { Err(MixnetContractError::EpochInProgress { .. }) )); + let mixnode_1 = query_mixnode_details(test.deps.as_ref(), 1).unwrap(); + assert_eq!( + mixnode_1.mixnode_details.unwrap().bond_information.layer, + Layer::One + ); + + let mixnode_1 = query_mixnode_details(test.deps.as_ref(), 2).unwrap(); + assert_eq!( + mixnode_1.mixnode_details.unwrap().bond_information.layer, + Layer::Two + ); + + let mixnode_1 = query_mixnode_details(test.deps.as_ref(), 3).unwrap(); + assert_eq!( + mixnode_1.mixnode_details.unwrap().bond_information.layer, + Layer::Three + ); + // sanity check test.skip_to_current_epoch_end(); let env = test.env(); @@ -1200,7 +1246,7 @@ mod tests { test.deps_mut(), env, sender, - vec![1, 2, 3], + layer_assignments, current_active_set, ); assert!(res.is_ok()) @@ -1211,17 +1257,27 @@ mod tests { let mut test = TestSetup::new(); let current_active_set = test.rewarding_params().active_set_size; + test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("2", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("3", Some(Uint128::new(100000000))); + push_n_dummy_epoch_actions(&mut test, 10); push_n_dummy_interval_actions(&mut test, 10); test.skip_to_current_epoch_end(); + let layer_assignments = vec![ + LayerAssignment::new(1, Layer::One), + LayerAssignment::new(2, Layer::Two), + LayerAssignment::new(3, Layer::Three), + ]; + let env = test.env(); let sender = test.rewarding_validator(); try_advance_epoch( test.deps_mut(), env, sender, - vec![1, 2, 3], + layer_assignments, current_active_set, ) .unwrap(); @@ -1237,17 +1293,27 @@ mod tests { let mut test = TestSetup::new(); let current_active_set = test.rewarding_params().active_set_size; + test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("2", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("3", Some(Uint128::new(100000000))); + push_n_dummy_epoch_actions(&mut test, 10); push_n_dummy_interval_actions(&mut test, 10); test.skip_to_current_interval_end(); + let layer_assignments = vec![ + LayerAssignment::new(1, Layer::One), + LayerAssignment::new(2, Layer::Two), + LayerAssignment::new(3, Layer::Three), + ]; + let env = test.env(); let sender = test.rewarding_validator(); try_advance_epoch( test.deps_mut(), env, sender, - vec![1, 2, 3], + layer_assignments, current_active_set, ) .unwrap(); @@ -1264,6 +1330,10 @@ mod tests { let env = test.env(); let current_active_set = test.rewarding_params().active_set_size; + test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("2", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("3", Some(Uint128::new(100000000))); + let mut expected_events = Vec::new(); let mut expected_messages: Vec> = Vec::new(); @@ -1310,13 +1380,19 @@ mod tests { test.skip_to_current_interval_end(); + let layer_assignments = vec![ + LayerAssignment::new(1, Layer::One), + LayerAssignment::new(2, Layer::Two), + LayerAssignment::new(3, Layer::Three), + ]; + let env = test.env(); let sender = test.rewarding_validator(); let res = try_advance_epoch( test.deps_mut(), env, sender, - vec![1, 2, 3], + layer_assignments, current_active_set, ) .unwrap(); @@ -1343,6 +1419,10 @@ mod tests { let mut test = TestSetup::new(); let current_active_set = test.rewarding_params().active_set_size; + test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("2", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("3", Some(Uint128::new(100000000))); + let start_params = test.rewarding_params(); let pool_update = Decimal::from_atomics(100_000_000u32, 0).unwrap(); @@ -1357,6 +1437,12 @@ mod tests { ) .unwrap(); + let layer_assignments = vec![ + LayerAssignment::new(1, Layer::One), + LayerAssignment::new(2, Layer::Two), + LayerAssignment::new(3, Layer::Three), + ]; + // end of epoch - nothing has happened let sender = test.rewarding_validator(); test.skip_to_current_epoch_end(); @@ -1365,7 +1451,7 @@ mod tests { test.deps_mut(), env, sender, - vec![1, 2, 3], + layer_assignments.clone(), current_active_set, ) .unwrap(); @@ -1384,7 +1470,7 @@ mod tests { test.deps_mut(), env, sender, - vec![1, 2, 3], + layer_assignments, current_active_set, ) .unwrap(); @@ -1413,10 +1499,20 @@ mod tests { let mut test = TestSetup::new(); let current_active_set = test.rewarding_params().active_set_size; + test.add_dummy_mixnode("1", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("2", Some(Uint128::new(100000000))); + test.add_dummy_mixnode("3", Some(Uint128::new(100000000))); + let interval_pre = test.current_interval(); let rewarded_set_pre = test.rewarded_set(); assert!(rewarded_set_pre.is_empty()); + let layer_assignments = vec![ + LayerAssignment::new(1, Layer::One), + LayerAssignment::new(2, Layer::Two), + LayerAssignment::new(3, Layer::Three), + ]; + let sender = test.rewarding_validator(); test.skip_to_current_interval_end(); let env = test.env(); @@ -1424,7 +1520,7 @@ mod tests { test.deps_mut(), env, sender, - vec![1, 2, 3], + layer_assignments, current_active_set, ) .unwrap(); diff --git a/contracts/mixnet/src/lib.rs b/contracts/mixnet/src/lib.rs index c22e7dc1a1..6f420d3541 100644 --- a/contracts/mixnet/src/lib.rs +++ b/contracts/mixnet/src/lib.rs @@ -7,6 +7,7 @@ mod constants; pub mod contract; mod delegations; +mod families; mod gateways; mod interval; mod mixnet_contract_settings; diff --git a/contracts/mixnet/src/mixnodes/transactions.rs b/contracts/mixnet/src/mixnodes/transactions.rs index 92fa131a9e..1d0da0449f 100644 --- a/contracts/mixnet/src/mixnodes/transactions.rs +++ b/contracts/mixnet/src/mixnodes/transactions.rs @@ -10,10 +10,10 @@ use crate::mixnodes::helpers::{ get_mixnode_details_by_owner, must_get_mixnode_bond_by_owner, save_new_mixnode, }; use crate::support::helpers::{ - ensure_bonded, ensure_no_existing_bond, ensure_proxy_match, validate_node_identity_signature, - validate_pledge, + ensure_bonded, ensure_is_authorized, ensure_no_existing_bond, ensure_proxy_match, + validate_node_identity_signature, validate_pledge, }; -use cosmwasm_std::{coin, Addr, Coin, DepsMut, Env, MessageInfo, Response}; +use cosmwasm_std::{coin, Addr, Coin, DepsMut, Env, MessageInfo, Response, Storage}; use mixnet_contract_common::error::MixnetContractError; use mixnet_contract_common::events::{ new_mixnode_bonding_event, new_mixnode_config_update_event, @@ -22,7 +22,37 @@ use mixnet_contract_common::events::{ }; use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams}; use mixnet_contract_common::pending_events::{PendingEpochEventKind, PendingIntervalEventKind}; -use mixnet_contract_common::MixNode; +use mixnet_contract_common::{Layer, MixId, MixNode}; + +pub(crate) fn update_mixnode_layer( + mix_id: MixId, + layer: Layer, + storage: &mut dyn Storage, +) -> Result<(), MixnetContractError> { + let bond = if let Some(bond_information) = storage::mixnode_bonds().may_load(storage, mix_id)? { + bond_information + } else { + return Err(MixnetContractError::MixNodeBondNotFound { mix_id }); + }; + let mut updated_bond = bond.clone(); + updated_bond.layer = layer; + + storage::mixnode_bonds().replace(storage, bond.mix_id, Some(&updated_bond), Some(&bond))?; + Ok(()) +} + +pub fn assign_mixnode_layer( + deps: DepsMut<'_>, + info: MessageInfo, + mix_id: MixId, + layer: Layer, +) -> Result { + ensure_is_authorized(info.sender, deps.storage)?; + + update_mixnode_layer(mix_id, layer, deps.storage)?; + + Ok(Response::default()) +} pub fn try_add_mixnode( deps: DepsMut<'_>, @@ -96,7 +126,7 @@ fn _try_add_mixnode( validate_node_identity_signature( deps.as_ref(), &owner, - owner_signature, + &owner_signature, &mixnode.identity_key, )?; @@ -359,7 +389,7 @@ pub mod tests { // if we don't send enough funds let info = mock_info(sender, &[insufficient_pledge.clone()]); - let (mixnode, sig) = test_helpers::mixnode_with_signature(&mut rng, sender); + let (mixnode, sig, _) = test_helpers::mixnode_with_signature(&mut rng, sender); let cost_params = fixtures::mix_node_cost_params_fixture(); // we are informed that we didn't send enough funds @@ -427,7 +457,7 @@ pub mod tests { ); let info = mock_info(sender2, &tests::fixtures::good_mixnode_pledge()); - let (mixnode, sig) = test_helpers::mixnode_with_signature(&mut rng, sender2); + let (mixnode, sig, _) = test_helpers::mixnode_with_signature(&mut rng, sender2); let result = try_add_mixnode( deps.as_mut(), diff --git a/contracts/mixnet/src/support/helpers.rs b/contracts/mixnet/src/support/helpers.rs index 9ec82ac2b8..ee47b48f3a 100644 --- a/contracts/mixnet/src/support/helpers.rs +++ b/contracts/mixnet/src/support/helpers.rs @@ -194,14 +194,30 @@ pub(crate) fn ensure_no_existing_bond( Ok(()) } -pub(crate) fn validate_node_identity_signature( +pub fn validate_node_identity_signature( deps: Deps<'_>, owner: &Addr, - signature: String, + signature: &str, identity: IdentityKeyRef<'_>, ) -> Result<(), MixnetContractError> { - let owner_bytes = owner.as_bytes(); + validate_signature(deps, owner.as_bytes(), signature, identity) +} +pub fn validate_family_signature( + deps: Deps<'_>, + family_member: IdentityKeyRef<'_>, + signature: &str, + family_head: IdentityKeyRef<'_>, +) -> Result<(), MixnetContractError> { + validate_signature(deps, family_member.as_bytes(), signature, family_head) +} + +pub(crate) fn validate_signature( + deps: Deps<'_>, + signed_bytes: &[u8], + signature: &str, + identity: IdentityKeyRef<'_>, +) -> Result<(), MixnetContractError> { let mut identity_bytes = [0u8; 32]; let mut signature_bytes = [0u8; 64]; @@ -226,7 +242,7 @@ pub(crate) fn validate_node_identity_signature( let res = deps .api - .ed25519_verify(owner_bytes, &signature_bytes, &identity_bytes) + .ed25519_verify(signed_bytes, &signature_bytes, &identity_bytes) .map_err(cosmwasm_std::StdError::verification_err)?; if !res { Err(MixnetContractError::InvalidEd25519Signature) @@ -276,12 +292,7 @@ mod tests { Err(MixnetContractError::MalformedEd25519IdentityKey( "buffer provided to decode base58 encoded string into was too small".into() )), - validate_node_identity_signature( - deps.as_ref(), - &address1, - sig_addr1_key1.clone(), - long_bs58, - ) + validate_node_identity_signature(deps.as_ref(), &address1, &sig_addr1_key1, long_bs58,) ); assert_eq!( @@ -300,12 +311,7 @@ mod tests { Err(MixnetContractError::MalformedEd25519IdentityKey( "Too few bytes provided for the public key".into() )), - validate_node_identity_signature( - deps.as_ref(), - &address1, - sig_addr1_key1.clone(), - short_bs58, - ) + validate_node_identity_signature(deps.as_ref(), &address1, &sig_addr1_key1, short_bs58,) ); assert_eq!( @@ -325,7 +331,7 @@ mod tests { validate_node_identity_signature( deps.as_ref(), &address1, - sig_addr1_key1.clone(), + &sig_addr1_key1, &keypair2.public_key().to_base58_string(), ) ); @@ -335,7 +341,7 @@ mod tests { validate_node_identity_signature( deps.as_ref(), &address1, - sig_addr2_key1, + &sig_addr2_key1, &keypair1.public_key().to_base58_string(), ) ); @@ -345,7 +351,7 @@ mod tests { validate_node_identity_signature( deps.as_ref(), &address2, - sig_addr1_key1.clone(), + &sig_addr1_key1, &keypair1.public_key().to_base58_string(), ) ); @@ -355,7 +361,7 @@ mod tests { validate_node_identity_signature( deps.as_ref(), &address1, - sig_addr1_key2, + &sig_addr1_key2, &keypair1.public_key().to_base58_string(), ) ); @@ -363,7 +369,7 @@ mod tests { assert!(validate_node_identity_signature( deps.as_ref(), &address1, - sig_addr1_key1, + &sig_addr1_key1, &keypair1.public_key().to_base58_string(), ) .is_ok()); diff --git a/contracts/mixnet/src/support/tests/mod.rs b/contracts/mixnet/src/support/tests/mod.rs index 3d17b324df..9f03f9c93c 100644 --- a/contracts/mixnet/src/support/tests/mod.rs +++ b/contracts/mixnet/src/support/tests/mod.rs @@ -46,6 +46,7 @@ pub mod test_helpers { use cosmwasm_std::{Deps, OwnedDeps}; use cosmwasm_std::{DepsMut, MessageInfo}; use cosmwasm_std::{Env, Response, Timestamp, Uint128}; + use crypto::asymmetric::identity::KeyPair; use mixnet_contract_common::events::{ may_find_attribute, MixnetEventType, DELEGATES_REWARD_KEY, OPERATOR_REWARD_KEY, }; @@ -556,7 +557,7 @@ pub mod test_helpers { pub fn mixnode_with_signature( mut rng: impl RngCore + CryptoRng, sender: &str, - ) -> (MixNode, String) { + ) -> (MixNode, String, KeyPair) { let keypair = crypto::asymmetric::identity::KeyPair::new(&mut rng); let legit_sphinx_key = crypto::asymmetric::encryption::KeyPair::new(&mut rng); let owner_signature = keypair @@ -574,6 +575,7 @@ pub mod test_helpers { ..tests::fixtures::mix_node_fixture() }, owner_signature, + keypair, ) } diff --git a/contracts/vesting/src/contract.rs b/contracts/vesting/src/contract.rs index 90a72d2ae8..4a528b41d9 100644 --- a/contracts/vesting/src/contract.rs +++ b/contracts/vesting/src/contract.rs @@ -5,7 +5,7 @@ use crate::storage::{ MIXNET_CONTRACT_ADDRESS, MIX_DENOM, }; use crate::traits::{ - DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, VestingAccount, + DelegatingAccount, GatewayBondingAccount, MixnodeBondingAccount, NodeFamilies, VestingAccount, }; use crate::vesting::{populate_vesting_periods, Account}; use contracts_common::ContractBuildInformation; @@ -60,6 +60,21 @@ pub fn execute( msg: ExecuteMsg, ) -> Result { match msg { + ExecuteMsg::CreateFamily { + owner_signature, + label, + } => try_create_family(info, deps, owner_signature, label), + ExecuteMsg::JoinFamily { + signature, + family_head, + } => try_join_family(info, deps, signature, family_head), + ExecuteMsg::LeaveFamily { + signature, + family_head, + } => try_leave_family(info, deps, signature, family_head), + ExecuteMsg::KickFamilyMember { signature, member } => { + try_kick_family_member(info, deps, signature, member) + } ExecuteMsg::UpdateLockedPledgeCap { address, cap } => { try_update_locked_pledge_cap(address, cap, info, deps) } @@ -144,6 +159,43 @@ pub fn execute( } } +pub fn try_create_family( + info: MessageInfo, + deps: DepsMut, + owner_signature: String, + label: String, +) -> Result { + let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?; + account.try_create_family(deps.storage, owner_signature, label) +} +pub fn try_join_family( + info: MessageInfo, + deps: DepsMut, + signature: String, + family_head: String, +) -> Result { + let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?; + account.try_join_family(deps.storage, signature, &family_head) +} +pub fn try_leave_family( + info: MessageInfo, + deps: DepsMut, + signature: String, + family_head: String, +) -> Result { + let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?; + account.try_leave_family(deps.storage, signature, &family_head) +} +pub fn try_kick_family_member( + info: MessageInfo, + deps: DepsMut, + signature: String, + member: String, +) -> Result { + let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?; + account.try_head_kick_member(deps.storage, signature, &member) +} + /// Update locked_pledge_cap, the hard cap for staking/bonding with unvested tokens. /// /// Callable by ADMIN only, see [instantiate]. diff --git a/contracts/vesting/src/traits/mod.rs b/contracts/vesting/src/traits/mod.rs index 60cff028d4..d1126bb92c 100644 --- a/contracts/vesting/src/traits/mod.rs +++ b/contracts/vesting/src/traits/mod.rs @@ -1,7 +1,9 @@ pub mod bonding_account; pub mod delegating_account; +pub mod node_families; pub mod vesting_account; pub use self::bonding_account::{GatewayBondingAccount, MixnodeBondingAccount}; pub use self::delegating_account::DelegatingAccount; +pub use self::node_families::NodeFamilies; pub use self::vesting_account::VestingAccount; diff --git a/contracts/vesting/src/traits/node_families.rs b/contracts/vesting/src/traits/node_families.rs new file mode 100644 index 0000000000..274ac41f94 --- /dev/null +++ b/contracts/vesting/src/traits/node_families.rs @@ -0,0 +1,33 @@ +use crate::errors::ContractError; +use cosmwasm_std::{Response, Storage}; +use mixnet_contract_common::IdentityKeyRef; + +pub trait NodeFamilies { + fn try_create_family( + &self, + storage: &dyn Storage, + owner_signature: String, + label: String, + ) -> Result; + + fn try_join_family( + &self, + storage: &dyn Storage, + signature: String, + family_head: IdentityKeyRef, + ) -> Result; + + fn try_leave_family( + &self, + storage: &dyn Storage, + signature: String, + family_head: IdentityKeyRef, + ) -> Result; + + fn try_head_kick_member( + &self, + storage: &dyn Storage, + signature: String, + member: IdentityKeyRef<'_>, + ) -> Result; +} diff --git a/contracts/vesting/src/vesting/account/mod.rs b/contracts/vesting/src/vesting/account/mod.rs index 71663247b7..a368431689 100644 --- a/contracts/vesting/src/vesting/account/mod.rs +++ b/contracts/vesting/src/vesting/account/mod.rs @@ -16,6 +16,7 @@ use vesting_contract_common::{Period, PledgeCap, PledgeData}; mod delegating_account; mod gateway_bonding_account; mod mixnode_bonding_account; +mod node_families; mod vesting_account; fn generate_storage_key(storage: &mut dyn Storage) -> Result { diff --git a/contracts/vesting/src/vesting/account/node_families.rs b/contracts/vesting/src/vesting/account/node_families.rs new file mode 100644 index 0000000000..a83efdabf9 --- /dev/null +++ b/contracts/vesting/src/vesting/account/node_families.rs @@ -0,0 +1,74 @@ +use super::Account; +use crate::{errors::ContractError, storage::MIXNET_CONTRACT_ADDRESS, traits::NodeFamilies}; +use cosmwasm_std::{wasm_execute, Response, Storage}; +use mixnet_contract_common::{ExecuteMsg as MixnetExecuteMsg, IdentityKeyRef}; + +impl NodeFamilies for Account { + fn try_create_family( + &self, + storage: &dyn Storage, + owner_signature: String, + label: String, + ) -> Result { + let msg = MixnetExecuteMsg::CreateFamilyOnBehalf { + owner_address: self.owner_address().to_string(), + owner_signature, + label, + }; + + let msg = wasm_execute(MIXNET_CONTRACT_ADDRESS.load(storage)?, &msg, vec![])?; + + Ok(Response::new().add_message(msg)) + } + + fn try_join_family( + &self, + storage: &dyn Storage, + signature: String, + family_head: IdentityKeyRef, + ) -> Result { + let msg = MixnetExecuteMsg::JoinFamilyOnBehalf { + member_address: self.owner_address().to_string(), + signature, + family_head: family_head.to_string(), + }; + + let msg = wasm_execute(MIXNET_CONTRACT_ADDRESS.load(storage)?, &msg, vec![])?; + + Ok(Response::new().add_message(msg)) + } + + fn try_leave_family( + &self, + storage: &dyn Storage, + signature: String, + family_head: IdentityKeyRef, + ) -> Result { + let msg = MixnetExecuteMsg::LeaveFamilyOnBehalf { + member_address: self.owner_address().to_string(), + signature, + family_head: family_head.to_string(), + }; + + let msg = wasm_execute(MIXNET_CONTRACT_ADDRESS.load(storage)?, &msg, vec![])?; + + Ok(Response::new().add_message(msg)) + } + + fn try_head_kick_member( + &self, + storage: &dyn Storage, + signature: String, + member: IdentityKeyRef<'_>, + ) -> Result { + let msg = MixnetExecuteMsg::KickFamilyMemberOnBehalf { + head_address: self.owner_address().to_string(), + signature, + member: member.to_string(), + }; + + let msg = wasm_execute(MIXNET_CONTRACT_ADDRESS.load(storage)?, &msg, vec![])?; + + Ok(Response::new().add_message(msg)) + } +} diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index 125be1cb8c..d34a04f920 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -73,6 +73,10 @@ fn main() { mixnet::delegate::get_all_mix_delegations, mixnet::delegate::undelegate_from_mixnode, mixnet::delegate::undelegate_all_from_mixnode, + mixnet::families::create_family, + mixnet::families::join_family, + mixnet::families::leave_family, + mixnet::families::kick_family_member, mixnet::interval::get_current_interval, mixnet::interval::get_pending_epoch_events, mixnet::interval::get_pending_interval_events, diff --git a/nym-wallet/src-tauri/src/operations/mixnet/families.rs b/nym-wallet/src-tauri/src/operations/mixnet/families.rs new file mode 100644 index 0000000000..6e391ab722 --- /dev/null +++ b/nym-wallet/src-tauri/src/operations/mixnet/families.rs @@ -0,0 +1,81 @@ +use crate::error::BackendError; +use crate::state::WalletState; +use nym_types::transaction::TransactionExecuteResult; +use validator_client::nymd::traits::MixnetSigningClient; +use validator_client::nymd::Fee; + +#[tauri::command] +pub async fn create_family( + signature: String, + label: String, + fee: Option, + state: tauri::State<'_, WalletState>, +) -> Result { + let guard = state.read().await; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); + let res = guard + .current_client()? + .nymd + .create_family(signature, label, fee) + .await?; + Ok(TransactionExecuteResult::from_execute_result( + res, fee_amount, + )?) +} + +#[tauri::command] +pub async fn join_family( + signature: String, + family_head: String, + fee: Option, + state: tauri::State<'_, WalletState>, +) -> Result { + let guard = state.read().await; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); + let res = guard + .current_client()? + .nymd + .join_family(signature, family_head, fee) + .await?; + Ok(TransactionExecuteResult::from_execute_result( + res, fee_amount, + )?) +} + +#[tauri::command] +pub async fn leave_family( + signature: String, + family_head: String, + fee: Option, + state: tauri::State<'_, WalletState>, +) -> Result { + let guard = state.read().await; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); + let res = guard + .current_client()? + .nymd + .leave_family(signature, family_head, fee) + .await?; + Ok(TransactionExecuteResult::from_execute_result( + res, fee_amount, + )?) +} + +#[tauri::command] +pub async fn kick_family_member( + signature: String, + member: String, + fee: Option, + state: tauri::State<'_, WalletState>, +) -> Result { + let guard = state.read().await; + let fee_amount = guard.convert_tx_fee(fee.as_ref()); + let res = guard + .current_client()? + .nymd + .kick_family_member(signature, member, fee) + .await?; + Ok(TransactionExecuteResult::from_execute_result( + res, fee_amount, + )?) +} diff --git a/nym-wallet/src-tauri/src/operations/mixnet/mod.rs b/nym-wallet/src-tauri/src/operations/mixnet/mod.rs index cb94278ee2..8b19a26dd0 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/mod.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/mod.rs @@ -2,6 +2,7 @@ pub mod account; pub mod admin; pub mod bond; pub mod delegate; +pub mod families; pub mod interval; pub mod rewards; pub mod send; diff --git a/validator-api/src/contract_cache/mod.rs b/validator-api/src/contract_cache/mod.rs index 95d984533c..3aa47c5e8a 100644 --- a/validator-api/src/contract_cache/mod.rs +++ b/validator-api/src/contract_cache/mod.rs @@ -5,6 +5,7 @@ use crate::nymd_client::Client; use crate::storage::ValidatorApiStorage; use ::time::OffsetDateTime; use anyhow::Result; +use mixnet_contract_common::families::FamilyHead; use mixnet_contract_common::mixnode::MixNodeDetails; use mixnet_contract_common::reward_params::{Performance, RewardingParams}; use mixnet_contract_common::{ @@ -141,7 +142,11 @@ impl ValidatorCacheRefresher { interval_reward_params: RewardingParams, current_interval: Interval, rewarded_set: &HashMap, + mix_to_family: Vec<(IdentityKey, FamilyHead)>, ) -> Vec { + let mix_to_family = mix_to_family + .into_iter() + .collect::>(); let mut annotated = Vec::new(); for mixnode in mixnodes { let stake_saturation = mixnode @@ -174,6 +179,10 @@ impl ValidatorCacheRefresher { current_interval, ); + let family = mix_to_family + .get(&mixnode.bond_information.identity().to_string()) + .cloned(); + annotated.push(MixNodeBondAnnotated { mixnode_details: mixnode, stake_saturation, @@ -181,6 +190,7 @@ impl ValidatorCacheRefresher { performance, estimated_operator_apy, estimated_delegators_apy, + family, }); } annotated @@ -226,10 +236,18 @@ impl ValidatorCacheRefresher { let mixnodes = self.nymd_client.get_mixnodes().await?; let gateways = self.nymd_client.get_gateways().await?; + let mix_to_family = self.nymd_client.get_all_family_members().await?; + let rewarded_set = self.get_rewarded_set_map().await; let mixnodes = self - .annotate_node_with_details(mixnodes, rewarding_params, current_interval, &rewarded_set) + .annotate_node_with_details( + mixnodes, + rewarding_params, + current_interval, + &rewarded_set, + mix_to_family, + ) .await; let (rewarded_set, active_set) = @@ -322,6 +340,7 @@ impl ValidatorCache { }) } + #[allow(clippy::too_many_arguments)] async fn update_cache( &self, mixnodes: Vec, diff --git a/validator-api/src/epoch_operations/error.rs b/validator-api/src/epoch_operations/error.rs index 20f0edc277..bb94df5f49 100644 --- a/validator-api/src/epoch_operations/error.rs +++ b/validator-api/src/epoch_operations/error.rs @@ -25,6 +25,11 @@ pub enum RewardingError { #[from] source: std::num::TryFromIntError, }, + #[error("{source}")] + WeightedError { + #[from] + source: rand::distributions::WeightedError, + }, } impl From for RewardingError { diff --git a/validator-api/src/epoch_operations/mod.rs b/validator-api/src/epoch_operations/mod.rs index cb33962890..159cd942fb 100644 --- a/validator-api/src/epoch_operations/mod.rs +++ b/validator-api/src/epoch_operations/mod.rs @@ -19,11 +19,14 @@ use crate::storage::ValidatorApiStorage; use mixnet_contract_common::{ reward_params::Performance, CurrentIntervalResponse, ExecuteMsg, Interval, MixId, }; +use mixnet_contract_common::{Layer, LayerAssignment}; use rand::prelude::SliceRandom; use rand::rngs::OsRng; +use std::collections::HashMap; use std::collections::HashSet; use std::time::Duration; use tokio::time::sleep; +use validator_api_requests::models::MixNodeBondAnnotated; use validator_client::nymd::SigningNymdClient; pub(crate) mod error; @@ -32,7 +35,6 @@ mod helpers; use crate::epoch_operations::helpers::stake_to_f64; use crate::node_status_api::ONE_DAY; use error::RewardingError; -use mixnet_contract_common::mixnode::MixNodeDetails; use task::ShutdownListener; #[derive(Debug, Clone, Copy)] @@ -57,6 +59,16 @@ pub struct RewardedSetUpdater { storage: ValidatorApiStorage, } +// Weight of a layer being chose is reciprocal to current count in layer +fn layer_weight(l: &Layer, layer_assignments: &HashMap) -> f32 { + let total = layer_assignments.values().fold(0., |acc, i| acc + i); + if total == 0. { + 1. + } else { + 1. - (layer_assignments.get(l).unwrap_or(&0.) / total) + } +} + impl RewardedSetUpdater { pub(crate) async fn current_interval_details( &self, @@ -76,23 +88,62 @@ impl RewardedSetUpdater { }) } + async fn determine_layers( + &self, + rewarded_set: &[MixNodeBondAnnotated], + ) -> Result<(Vec, HashMap), RewardingError> { + let mut families_in_layer: HashMap = HashMap::new(); + let mut assignments = vec![]; + let mut layer_assignments: HashMap = HashMap::new(); + let mut rng = OsRng; + let layers = vec![Layer::One, Layer::Two, Layer::Three]; + + for mix in rewarded_set { + // Get layer already assigned to nodes family, if any + let family_layer = mix + .family + .as_ref() + .and_then(|h| families_in_layer.get(h.identity())); + + // Same node families are always assigned to the same layer, otherwise layer selected by a random weighted choice + let layer = if let Some(layer) = family_layer { + layer.to_owned() + } else { + layers + .choose_weighted(&mut rng, |l| layer_weight(l, &layer_assignments))? + .to_owned() + }; + + assignments.push(LayerAssignment::new(mix.mix_id(), layer)); + + // layer accounting + let layer_entry = layer_assignments.entry(layer).or_insert(0.); + *layer_entry += 1.; + if let Some(ref family) = mix.family { + families_in_layer.insert(family.identity().to_string(), layer); + } + } + + Ok((assignments, families_in_layer)) + } + fn determine_rewarded_set( &self, - mixnodes: Vec, + mixnodes: &[MixNodeBondAnnotated], nodes_to_select: u32, - ) -> Vec { + ) -> Result, RewardingError> { if mixnodes.is_empty() { - return Vec::new(); + return Ok(Vec::new()); } let mut rng = OsRng; // generate list of mixnodes and their relatively weight (by total stake) let choices = mixnodes - .into_iter() + .iter() .map(|mix| { - let total_stake = stake_to_f64(mix.total_stake()); - (mix.mix_id(), total_stake) + let total_stake = stake_to_f64(mix.mixnode_details.total_stake()); + (mix.to_owned(), total_stake) }) .collect::>(); @@ -101,11 +152,10 @@ impl RewardedSetUpdater { // - we have invalid weights, i.e. less than zero or NaNs - it shouldn't happen in our case as we safely cast down from u128 // - all weights are zero - it's impossible in our case as the list of nodes is not empty and weight is proportional to stake. You must have non-zero stake in order to bond // - we have more than u32::MAX values (which is incredibly unrealistic to have 4B mixnodes bonded... literally every other person on the planet would need one) - choices - .choose_multiple_weighted(&mut rng, nodes_to_select as usize, |item| item.1) - .unwrap() - .map(|(mix_id, _weight)| *mix_id) - .collect() + Ok(choices + .choose_multiple_weighted(&mut rng, nodes_to_select as usize, |item| item.1)? + .map(|(mix, _weight)| mix.to_owned()) + .collect()) } async fn reward_current_rewarded_set( @@ -186,15 +236,19 @@ impl RewardedSetUpdater { async fn update_rewarded_set_and_advance_epoch( &self, - all_mixnodes: Vec, + all_mixnodes: &[MixNodeBondAnnotated], ) -> Result<(), RewardingError> { // we grab rewarding parameters here as they might have gotten updated when performing epoch actions let rewarding_parameters = self.nymd_client.get_current_rewarding_parameters().await?; let new_rewarded_set = - self.determine_rewarded_set(all_mixnodes, rewarding_parameters.rewarded_set_size); + self.determine_rewarded_set(all_mixnodes, rewarding_parameters.rewarded_set_size)?; + + let (layer_assignments, _families_in_layer) = + self.determine_layers(&new_rewarded_set).await?; + self.nymd_client - .advance_current_epoch(new_rewarded_set, rewarding_parameters.active_set_size) + .advance_current_epoch(layer_assignments, rewarding_parameters.active_set_size) .await?; Ok(()) @@ -219,15 +273,11 @@ impl RewardedSetUpdater { let epoch_end = interval.current_epoch_end(); - let all_nodes = self.validator_cache.mixnodes().await; - if all_nodes.is_empty() { + let all_mixnodes = self.validator_cache.mixnodes_detailed().await; + if all_mixnodes.is_empty() { log::warn!("there don't seem to be any mixnodes on the network!") } - // get list of all mixnodes BEFORE rewarding happens as to now be biased by rewards - // that might be given to them - let all_mixnodes = self.validator_cache.mixnodes().await; - // Reward all the nodes in the still current, soon to be previous rewarded set log::info!("Rewarding the current rewarded set..."); if let Err(err) = self.reward_current_rewarded_set(interval).await { @@ -257,7 +307,7 @@ impl RewardedSetUpdater { log::info!("Advancing epoch and updating the rewarded set..."); if let Err(err) = self - .update_rewarded_set_and_advance_epoch(all_mixnodes) + .update_rewarded_set_and_advance_epoch(&all_mixnodes) .await { log::error!("FAILED to advance the current epoch... - {}", err); diff --git a/validator-api/src/nymd_client.rs b/validator-api/src/nymd_client.rs index 0abefc0875..676a65e8d9 100644 --- a/validator-api/src/nymd_client.rs +++ b/validator-api/src/nymd_client.rs @@ -4,10 +4,12 @@ use crate::config::Config; use crate::epoch_operations::MixnodeToReward; use config::defaults::{NymNetworkDetails, DEFAULT_VALIDATOR_API_PORT}; +use mixnet_contract_common::families::{Family, FamilyHead}; use mixnet_contract_common::mixnode::MixNodeDetails; use mixnet_contract_common::reward_params::RewardingParams; use mixnet_contract_common::{ - CurrentIntervalResponse, ExecuteMsg, GatewayBond, MixId, RewardedSetNodeStatus, + CurrentIntervalResponse, ExecuteMsg, GatewayBond, IdentityKey, LayerAssignment, MixId, + RewardedSetNodeStatus, }; use std::sync::Arc; use tokio::sync::RwLock; @@ -191,6 +193,23 @@ impl Client { .await } + #[allow(dead_code)] + pub(crate) async fn get_all_node_families(&self) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync + Send, + { + self.0.read().await.get_all_node_families().await + } + + pub(crate) async fn get_all_family_members( + &self, + ) -> Result, ValidatorClientError> + where + C: CosmWasmClient + Sync + Send, + { + self.0.read().await.get_all_family_members().await + } + pub(crate) async fn send_rewarding_messages( &self, nodes: &[MixnodeToReward], @@ -237,7 +256,7 @@ impl Client { pub(crate) async fn advance_current_epoch( &self, - new_rewarded_set: Vec, + new_rewarded_set: Vec, expected_active_set_size: u32, ) -> Result<(), ValidatorClientError> where diff --git a/validator-api/validator-api-requests/src/models.rs b/validator-api/validator-api-requests/src/models.rs index 046e4152b3..eee713a701 100644 --- a/validator-api/validator-api-requests/src/models.rs +++ b/validator-api/validator-api-requests/src/models.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use cosmwasm_std::{Coin, Decimal}; +use mixnet_contract_common::families::FamilyHead; use mixnet_contract_common::mixnode::MixNodeDetails; use mixnet_contract_common::reward_params::{Performance, RewardingParams}; use mixnet_contract_common::rewarding::RewardEstimate; @@ -100,6 +101,7 @@ pub struct MixNodeBondAnnotated { pub performance: Performance, pub estimated_operator_apy: Decimal, pub estimated_delegators_apy: Decimal, + pub family: Option, } impl MixNodeBondAnnotated {