Feature/families signatures (#3156)

* wip family creation signatures + cli

* nym-cli commands for creating families

* Changed family join signature inside the contract

* Generating family join permit via nym-cli

* ability to join families via nym-cli

* more strongly typed FamilyHead arguments

* initial work on removing redundant family signatures

* removed all redundant signatures from families in the mixnet contract

* moved up the call stack

* nym-cli family operations

* fixed family related unit tests

* family member kick

* removed family operations from the wallet

* clippy
This commit is contained in:
Jędrzej Stuczyński
2023-03-23 15:36:10 +00:00
committed by GitHub
parent 5e04f48500
commit aee4c2d80d
36 changed files with 859 additions and 785 deletions
Generated
+1
View File
@@ -3210,6 +3210,7 @@ dependencies = [
"nym-coconut-bandwidth-contract-common",
"nym-coconut-dkg-common",
"nym-contracts-common",
"nym-crypto",
"nym-mixnet-contract-common",
"nym-multisig-contract-common",
"nym-network-defaults",
@@ -1,4 +1,4 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::nyxd::coin::Coin;
@@ -9,6 +9,7 @@ use crate::nyxd::{Fee, NyxdClient, SigningCosmWasmClient};
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_contracts_common::signing::MessageSignature;
use nym_mixnet_contract_common::families::FamilyHead;
use nym_mixnet_contract_common::gateway::GatewayConfigUpdate;
use nym_mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use nym_mixnet_contract_common::reward_params::{IntervalRewardingParamsUpdate, Performance};
@@ -146,25 +147,16 @@ pub trait MixnetSigningClient {
// family related
async fn create_family(
&self,
owner_signature: String,
label: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::CreateFamily {
owner_signature,
label,
},
vec![],
)
.await
self.execute_mixnet_contract(fee, MixnetExecuteMsg::CreateFamily { label }, vec![])
.await
}
async fn create_family_on_behalf(
&self,
owner_address: String,
owner_signature: String,
label: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
@@ -172,7 +164,6 @@ pub trait MixnetSigningClient {
fee,
MixnetExecuteMsg::CreateFamilyOnBehalf {
owner_address,
owner_signature,
label,
},
vec![],
@@ -182,14 +173,14 @@ pub trait MixnetSigningClient {
async fn join_family(
&self,
signature: String,
family_head: String,
join_permit: MessageSignature,
family_head: FamilyHead,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::JoinFamily {
signature,
join_permit,
family_head,
},
vec![],
@@ -200,17 +191,15 @@ pub trait MixnetSigningClient {
async fn join_family_on_behalf(
&self,
member_address: String,
node_identity_signature: String,
family_signature: String,
family_head: String,
join_permit: MessageSignature,
family_head: FamilyHead,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::JoinFamilyOnBehalf {
member_address,
node_identity_signature,
family_signature,
join_permit,
family_head,
},
vec![],
@@ -220,33 +209,23 @@ pub trait MixnetSigningClient {
async fn leave_family(
&self,
signature: String,
family_head: String,
family_head: FamilyHead,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::LeaveFamily {
signature,
family_head,
},
vec![],
)
.await
self.execute_mixnet_contract(fee, MixnetExecuteMsg::LeaveFamily { family_head }, vec![])
.await
}
async fn leave_family_on_behalf(
&self,
member_address: String,
node_identity_signature: String,
family_head: String,
family_head: FamilyHead,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::LeaveFamilyOnBehalf {
member_address,
node_identity_signature,
family_head,
},
vec![],
@@ -256,22 +235,16 @@ pub trait MixnetSigningClient {
async fn kick_family_member(
&self,
signature: String,
member: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::KickFamilyMember { signature, member },
vec![],
)
.await
self.execute_mixnet_contract(fee, MixnetExecuteMsg::KickFamilyMember { member }, vec![])
.await
}
async fn kick_family_member_on_behalf(
&self,
head_address: String,
signature: String,
member: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
@@ -279,7 +252,6 @@ pub trait MixnetSigningClient {
fee,
MixnetExecuteMsg::KickFamilyMemberOnBehalf {
head_address,
signature,
member,
},
vec![],
@@ -7,6 +7,7 @@ use crate::nyxd::error::NyxdError;
use crate::nyxd::{Coin, Fee, NyxdClient};
use async_trait::async_trait;
use nym_contracts_common::signing::MessageSignature;
use nym_mixnet_contract_common::families::FamilyHead;
use nym_mixnet_contract_common::gateway::GatewayConfigUpdate;
use nym_mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use nym_mixnet_contract_common::{Gateway, MixId, MixNode};
@@ -136,6 +137,50 @@ pub trait VestingSigningClient {
cap: Option<PledgeCap>,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError>;
async fn vesting_create_family(
&self,
label: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_vesting_contract(fee, VestingExecuteMsg::CreateFamily { label }, vec![])
.await
}
async fn vesting_join_family(
&self,
join_permit: MessageSignature,
family_head: FamilyHead,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_vesting_contract(
fee,
VestingExecuteMsg::JoinFamily {
join_permit,
family_head,
},
vec![],
)
.await
}
async fn vesting_leave_family(
&self,
family_head: FamilyHead,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_vesting_contract(fee, VestingExecuteMsg::LeaveFamily { family_head }, vec![])
.await
}
async fn vesting_kick_family_member(
&self,
member: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_vesting_contract(fee, VestingExecuteMsg::KickFamilyMember { member }, vec![])
.await
}
}
#[async_trait]
+1
View File
@@ -29,6 +29,7 @@ cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegr
cosmwasm-std = { workspace = true }
validator-client = { path = "../client-libs/validator-client", features = ["nyxd-client"] }
nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] }
nym-network-defaults = { path = "../network-defaults" }
nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common" }
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
@@ -0,0 +1,37 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use validator_client::nyxd::traits::MixnetSigningClient;
use validator_client::nyxd::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
/// Label that is going to be used for creating the family
#[arg(long)]
pub family_label: String,
/// Indicates whether the family is going to get created via a vesting account
#[arg(long)]
pub with_vesting_account: bool,
}
pub async fn create_family(args: Args, client: SigningClient) {
info!("Create family");
let res = if args.with_vesting_account {
client
.vesting_create_family(args.family_label, None)
.await
.expect("failed to create family with vesting account")
} else {
client
.create_family(args.family_label, None)
.await
.expect("failed to create family")
};
info!("Family creation result: {:?}", res);
}
@@ -0,0 +1,72 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::QueryClient;
use crate::utils::account_id_to_cw_addr;
use clap::Parser;
use cosmrs::AccountId;
use log::info;
use nym_crypto::asymmetric::identity;
use nym_mixnet_contract_common::construct_family_join_permit;
use nym_mixnet_contract_common::families::FamilyHead;
use validator_client::nyxd::traits::MixnetQueryClient;
#[derive(Debug, Parser)]
pub struct Args {
/// Account address (i.e. owner of the family head) which will be used for issuing the permit
#[arg(long)]
pub address: AccountId,
/// Indicates whether the member joining the family is going to use the vesting account for joining.
#[arg(long)]
pub with_vesting_account: bool,
// might as well validate the value when parsing the arguments
/// Identity of the member for whom we're issuing the permit
#[arg(long)]
pub member: identity::PublicKey,
}
pub async fn create_family_join_permit_sign_payload(args: Args, client: QueryClient) {
info!("Create family join permit sign payload");
// get the address of our mixnode to recover the family head information
let Some(mixnode) = client.get_owned_mixnode(&args.address).await.unwrap().mixnode_details else {
eprintln!("{} does not seem to even own a mixnode!", args.address);
return
};
// make sure this mixnode is actually a family head
if client
.get_node_family_by_head(mixnode.bond_information.identity())
.await
.unwrap()
.is_none()
{
eprintln!("{} does not even seem to own a family!", args.address);
return;
}
let nonce = match client.get_signing_nonce(&args.address).await {
Ok(nonce) => nonce,
Err(err) => {
eprint!(
"failed to query for the signing nonce of {}: {err}",
args.address
);
return;
}
};
// let address = account_id_to_cw_addr(&args.address);
let proxy = if args.with_vesting_account {
Some(account_id_to_cw_addr(client.vesting_contract_address()))
} else {
None
};
let head = FamilyHead::new(mixnode.bond_information.identity());
let payload = construct_family_join_permit(nonce, head, proxy, args.member.to_base58_string());
println!("{}", payload.to_base58_string().unwrap())
}
@@ -0,0 +1,46 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_contracts_common::signing::MessageSignature;
use nym_crypto::asymmetric::identity;
use nym_mixnet_contract_common::families::FamilyHead;
use validator_client::nyxd::traits::MixnetSigningClient;
use validator_client::nyxd::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
/// The head of the family that we intend to join
#[arg(long)]
pub family_head: identity::PublicKey,
/// Indicates whether the member joining the family is going to do so via the vesting contract
#[arg(long)]
pub with_vesting_account: bool,
/// Permission, as provided by the family head, for joining the family
#[arg(long)]
pub join_permit: MessageSignature,
}
pub async fn join_family(args: Args, client: SigningClient) {
info!("Join family");
let family_head = FamilyHead::new(&args.family_head.to_base58_string());
let res = if args.with_vesting_account {
client
.vesting_join_family(args.join_permit, family_head, None)
.await
.expect("failed to join family with vesting account")
} else {
client
.join_family(args.join_permit, family_head, None)
.await
.expect("failed to join family")
};
info!("Family join result: {:?}", res);
}
@@ -0,0 +1,40 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_crypto::asymmetric::identity;
use validator_client::nyxd::traits::MixnetSigningClient;
use validator_client::nyxd::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
/// The member of the family that we intend to kick
#[arg(long)]
pub member: identity::PublicKey,
/// Indicates whether the family was created (and managed) via the vesting contract
#[arg(long)]
pub with_vesting_account: bool,
}
pub async fn kick_family_member(args: Args, client: SigningClient) {
info!("Leave family");
let member = args.member.to_base58_string();
let res = if args.with_vesting_account {
client
.vesting_kick_family_member(member, None)
.await
.expect("failed to kick family member with vesting account")
} else {
client
.kick_family_member(member, None)
.await
.expect("failed to kick family member")
};
info!("Family leave result: {:?}", res);
}
@@ -0,0 +1,41 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_crypto::asymmetric::identity;
use nym_mixnet_contract_common::families::FamilyHead;
use validator_client::nyxd::traits::MixnetSigningClient;
use validator_client::nyxd::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
/// The head of the family that we intend to leave
#[arg(long)]
pub family_head: identity::PublicKey,
/// Indicates whether we joined the family via the vesting contract
#[arg(long)]
pub with_vesting_account: bool,
}
pub async fn leave_family(args: Args, client: SigningClient) {
info!("Leave family");
let family_head = FamilyHead::new(&args.family_head.to_base58_string());
let res = if args.with_vesting_account {
client
.vesting_leave_family(family_head, None)
.await
.expect("failed to leave family with vesting account")
} else {
client
.leave_family(family_head, None)
.await
.expect("failed to leave family")
};
info!("Family leave result: {:?}", res);
}
@@ -0,0 +1,35 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::{Args, Subcommand};
pub mod create_family;
pub mod create_family_join_permit_sign_payload;
pub mod join_family;
pub mod kick_family_member;
pub mod leave_family;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
pub struct MixnetOperatorsMixnodeFamilies {
#[clap(subcommand)]
pub command: MixnetOperatorsMixnodeFamiliesCommands,
}
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsMixnodeFamiliesCommands {
/// Create family
CreateFamily(create_family::Args),
/// Join family
JoinFamily(join_family::Args),
/// Leave family,
LeaveFamily(leave_family::Args),
/// Kick family member
KickFamilyMember(kick_family_member::Args),
/// Create a message payload that is required to get signed in order to obtain a permit for joining family
CreateFamilyJoinPermitSignPayload(create_family_join_permit_sign_payload::Args),
}
@@ -4,6 +4,7 @@
use clap::{Args, Subcommand};
pub mod bond_mixnode;
pub mod families;
pub mod keys;
pub mod mixnode_bonding_sign_payload;
pub mod rewards;
@@ -27,6 +28,8 @@ pub enum MixnetOperatorsMixnodeCommands {
Rewards(rewards::MixnetOperatorsMixnodeRewards),
/// Manage your mixnode settings stored in the directory
Settings(settings::MixnetOperatorsMixnodeSettings),
/// Operations for mixnode families
Families(families::MixnetOperatorsMixnodeFamilies),
/// Bond to a mixnode
Bond(bond_mixnode::Args),
/// Unbond from a mixnode
@@ -1,7 +1,9 @@
use crate::{IdentityKey, IdentityKeyRef};
use cosmwasm_std::Addr;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{Display, Formatter};
use std::str::FromStr;
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
@@ -20,9 +22,45 @@ pub struct Family {
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/NodeFamilyHead.ts")
)]
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, JsonSchema)]
#[derive(Debug, Clone, Eq, PartialEq, JsonSchema)]
pub struct FamilyHead(IdentityKey);
impl Serialize for FamilyHead {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.serialize(serializer)
}
}
impl<'de> Deserialize<'de> for FamilyHead {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let inner = IdentityKey::deserialize(deserializer)?;
Ok(FamilyHead(inner))
}
}
impl FromStr for FamilyHead {
type Err = <IdentityKey as FromStr>::Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// theoretically we should be verifying whether it's a valid base58 value
// (or even better, whether it's a valid ed25519 public key), but definition of
// `FamilyHead` might change later
Ok(FamilyHead(IdentityKey::from_str(s)?))
}
}
impl Display for FamilyHead {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl FamilyHead {
pub fn new(identity: IdentityKeyRef<'_>) -> Self {
FamilyHead(identity.to_string())
@@ -34,11 +72,11 @@ impl FamilyHead {
}
impl Family {
pub fn new(head: FamilyHead, proxy: Option<Addr>, label: &str) -> Self {
pub fn new(head: FamilyHead, proxy: Option<Addr>, label: String) -> Self {
Family {
head,
proxy: proxy.map(|p| p.to_string()),
label: label.to_string(),
label,
}
}
@@ -60,3 +98,21 @@ impl Family {
&self.label
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn family_head_serde() {
let dummy = FamilyHead::new("foomp");
let ser_str = serde_json_wasm::to_string(&dummy).unwrap();
let de_str: FamilyHead = serde_json_wasm::from_str(&ser_str).unwrap();
assert_eq!(dummy, de_str);
let ser_bytes = serde_json_wasm::to_vec(&dummy).unwrap();
let de_bytes: FamilyHead = serde_json_wasm::from_slice(&ser_bytes).unwrap();
assert_eq!(dummy, de_bytes);
}
}
@@ -3,6 +3,7 @@
use crate::delegation::OwnerProxySubKey;
use crate::error::MixnetContractError;
use crate::families::FamilyHead;
use crate::gateway::GatewayConfigUpdate;
use crate::helpers::IntoBaseDecimal;
use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
@@ -82,42 +83,35 @@ 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,
join_permit: MessageSignature,
family_head: FamilyHead,
},
LeaveFamily {
signature: String,
family_head: IdentityKey,
family_head: FamilyHead,
},
KickFamilyMember {
signature: String,
member: IdentityKey,
},
CreateFamilyOnBehalf {
owner_address: String,
owner_signature: String,
label: String,
},
/// Family head needs to sign the joining node IdentityKey, MixNode needs to provide its signature proving that it wants to join the family
JoinFamilyOnBehalf {
member_address: String,
node_identity_signature: String,
family_signature: String,
family_head: IdentityKey,
join_permit: MessageSignature,
family_head: FamilyHead,
},
LeaveFamilyOnBehalf {
member_address: String,
node_identity_signature: String,
family_head: IdentityKey,
family_head: FamilyHead,
},
KickFamilyMemberOnBehalf {
head_address: String,
signature: String,
member: IdentityKey,
},
@@ -1,7 +1,8 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{Gateway, MixNode, MixNodeCostParams};
use crate::families::FamilyHead;
use crate::{Gateway, IdentityKey, MixNode, MixNodeCostParams};
use contracts_common::signing::{
ContractMessageContent, MessageType, Nonce, SignableMessage, SigningPurpose,
};
@@ -10,6 +11,7 @@ use serde::Serialize;
pub type SignableMixNodeBondingMsg = SignableMessage<ContractMessageContent<MixnodeBondingPayload>>;
pub type SignableGatewayBondingMsg = SignableMessage<ContractMessageContent<GatewayBondingPayload>>;
pub type SignableFamilyJoinPermitMsg = SignableMessage<FamilyJoinPermit>;
#[derive(Serialize)]
pub struct MixnodeBondingPayload {
@@ -77,75 +79,43 @@ pub fn construct_gateway_bonding_sign_payload(
}
#[derive(Serialize)]
pub struct FamilyCreationSignature {
label: String,
// TODO: add any extra fields?
pub struct FamilyJoinPermit {
// the granter of this permit
family_head: FamilyHead,
// whether the **member** will want to join via the proxy (i.e. vesting contract)
proxy: Option<Addr>,
// the actual member we want to permit to join
member_node: IdentityKey,
}
impl FamilyCreationSignature {
pub fn new(label: String) -> Self {
Self { label }
impl FamilyJoinPermit {
pub fn new(family_head: FamilyHead, proxy: Option<Addr>, member_node: IdentityKey) -> Self {
Self {
family_head,
proxy,
member_node,
}
}
}
impl SigningPurpose for FamilyCreationSignature {
impl SigningPurpose for FamilyJoinPermit {
fn message_type() -> MessageType {
MessageType::new("family-creation")
MessageType::new("family-join-permit")
}
}
#[derive(Serialize)]
pub struct FamilyJoinSignature {
family_head: String,
// TODO: add any extra fields?
}
pub fn construct_family_join_permit(
nonce: Nonce,
family_head: FamilyHead,
proxy: Option<Addr>,
member_node: IdentityKey,
) -> SignableFamilyJoinPermitMsg {
let payload = FamilyJoinPermit::new(family_head, proxy, member_node);
impl FamilyJoinSignature {
pub fn new(family_head: String) -> Self {
Self { family_head }
}
}
impl SigningPurpose for FamilyJoinSignature {
fn message_type() -> MessageType {
MessageType::new("family-join")
}
}
#[derive(Serialize)]
pub struct FamilyLeaveSignature {
family_head: String,
// TODO: add any extra fields?
}
impl FamilyLeaveSignature {
pub fn new(family_head: String) -> Self {
Self { family_head }
}
}
impl SigningPurpose for FamilyLeaveSignature {
fn message_type() -> MessageType {
MessageType::new("family-leave")
}
}
#[derive(Serialize)]
pub struct FamilyKickSignature {
member: String,
// TODO: add any extra fields?
}
impl FamilyKickSignature {
pub fn new(member: String) -> Self {
Self { member }
}
}
impl SigningPurpose for FamilyKickSignature {
fn message_type() -> MessageType {
MessageType::new("family-member-removal")
}
// note: we're NOT wrapping it in `ContractMessageContent` because the family head is not going to be the one
// sending the message to the contract
SignableMessage::new(nonce, payload)
}
// TODO: depending on our threat model, we should perhaps extend it to include all _on_behalf methods
// (update: but we trust our vesting contract since its compromise would be even more devastating so there's no need)
@@ -1,5 +1,6 @@
use contracts_common::signing::MessageSignature;
use cosmwasm_std::{Coin, Timestamp};
use mixnet_contract_common::families::FamilyHead;
use mixnet_contract_common::{
gateway::GatewayConfigUpdate,
mixnode::{MixNodeConfigUpdate, MixNodeCostParams},
@@ -60,21 +61,17 @@ 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, the Node provides its signature signaling consent to join the family
JoinFamily {
node_identity_signature: String,
family_signature: String,
family_head: IdentityKey,
join_permit: MessageSignature,
family_head: FamilyHead,
},
LeaveFamily {
node_identity_signature: String,
family_head: IdentityKey,
family_head: FamilyHead,
},
KickFamilyMember {
signature: String,
member: IdentityKey,
},
TrackReward {
+16 -28
View File
@@ -104,68 +104,56 @@ pub fn execute(
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, None, signature, family_head)
ExecuteMsg::CreateFamily { label } => {
crate::families::transactions::try_create_family(deps, info, label)
}
ExecuteMsg::LeaveFamily {
signature,
ExecuteMsg::JoinFamily {
join_permit,
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)
} => crate::families::transactions::try_join_family(deps, info, join_permit, family_head),
ExecuteMsg::LeaveFamily { family_head } => {
crate::families::transactions::try_leave_family(deps, info, family_head)
}
ExecuteMsg::KickFamilyMember { member } => {
crate::families::transactions::try_head_kick_member(deps, info, member)
}
ExecuteMsg::CreateFamilyOnBehalf {
owner_address,
owner_signature,
label,
} => crate::families::transactions::try_create_family_on_behalf(
deps,
info,
owner_address,
owner_signature,
&label,
label,
),
ExecuteMsg::JoinFamilyOnBehalf {
member_address,
node_identity_signature,
family_signature,
join_permit,
family_head,
} => crate::families::transactions::try_join_family_on_behalf(
deps,
info,
member_address,
Some(node_identity_signature),
family_signature,
join_permit,
family_head,
),
ExecuteMsg::LeaveFamilyOnBehalf {
member_address,
node_identity_signature,
family_head,
} => crate::families::transactions::try_leave_family_on_behalf(
deps,
info,
member_address,
node_identity_signature,
family_head,
),
ExecuteMsg::KickFamilyMemberOnBehalf {
head_address,
signature,
member,
} => crate::families::transactions::try_head_kick_member_on_behalf(
deps,
info,
head_address,
signature,
&member,
member,
),
// state/sys-params-related
ExecuteMsg::UpdateRewardingValidatorAddress { address } => {
@@ -392,13 +380,13 @@ pub fn query(
&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)?,
&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)?,
&crate::families::queries::get_family_members_by_label(label, deps.storage)?,
),
QueryMsg::GetContractVersion {} => {
to_binary(&crate::mixnet_contract_settings::queries::query_contract_version())
+4
View File
@@ -1,3 +1,7 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
pub mod queries;
pub mod signature_helpers;
pub mod storage;
pub mod transactions;
+10 -19
View File
@@ -1,31 +1,27 @@
use std::collections::HashSet;
use crate::constants::{FAMILIES_DEFAULT_RETRIEVAL_LIMIT, FAMILIES_MAX_RETRIEVAL_LIMIT};
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage::{families, get_family, get_members, MEMBERS};
use crate::constants::{FAMILIES_DEFAULT_RETRIEVAL_LIMIT, FAMILIES_MAX_RETRIEVAL_LIMIT};
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};
use std::collections::HashSet;
pub fn get_family_by_label(
label: &str,
label: String,
storage: &dyn Storage,
) -> Result<Option<Family>, MixnetContractError> {
Ok(families()
.idx
.label
.item(storage, label.to_string())?
.map(|o| o.1))
Ok(families().idx.label.item(storage, label)?.map(|o| o.1))
}
pub fn get_family_by_head(
head: IdentityKeyRef<'_>,
storage: &dyn Storage,
) -> Result<Family, MixnetContractError> {
let family_head = FamilyHead::new(head);
get_family(&family_head, storage)
) -> Result<Option<Family>, MixnetContractError> {
Ok(families().may_load(storage, head.to_string())?)
}
pub fn get_family_members_by_head(
@@ -38,15 +34,10 @@ pub fn get_family_members_by_head(
}
pub fn get_family_members_by_label(
label: &str,
label: String,
storage: &dyn Storage,
) -> Result<Option<HashSet<String>>, MixnetContractError> {
if let Some(family) = families()
.idx
.label
.item(storage, label.to_string())?
.map(|o| o.1)
{
if let Some(family) = families().idx.label.item(storage, label)?.map(|o| o.1) {
Ok(Some(get_members(&family, storage)?))
} else {
Ok(None)
@@ -0,0 +1,38 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnodes::storage as mixnodes_storage;
use crate::signing::storage as signing_storage;
use crate::support::helpers::decode_ed25519_identity_key;
use cosmwasm_std::{Addr, Deps};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::families::FamilyHead;
use mixnet_contract_common::{construct_family_join_permit, IdentityKeyRef};
use nym_contracts_common::signing::{MessageSignature, Verifier};
pub(crate) fn verify_family_join_permit(
deps: Deps<'_>,
granter: FamilyHead,
proxy: Option<Addr>,
member: IdentityKeyRef,
signature: MessageSignature,
) -> Result<(), MixnetContractError> {
// recover the public key
let public_key = decode_ed25519_identity_key(granter.identity())?;
// that's kinda a backwards way of getting the granter's nonce, but it works, so ¯\_(ツ)_/¯
let Some(head_mixnode) = mixnodes_storage::mixnode_bonds()
.idx
.identity_key
.item(deps.storage, granter.identity().to_owned())?.map(|record| record.1) else {
return Err(MixnetContractError::FamilyDoesNotExist { head: granter.identity().to_string() })
};
let nonce = signing_storage::get_signing_nonce(deps.storage, head_mixnode.owner)?;
let msg = construct_family_join_permit(nonce, granter, proxy, member.to_owned());
if deps.api.verify_message(msg, signature, &public_key)? {
Ok(())
} else {
Err(MixnetContractError::InvalidEd25519Signature)
}
}
+6 -18
View File
@@ -1,9 +1,11 @@
use std::collections::HashSet;
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Order, StdError, Storage};
use cosmwasm_std::{Order, 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 std::collections::HashSet;
use crate::constants::{FAMILIES_INDEX_NAMESPACE, FAMILIES_MAP_NAMESPACE, MEMBERS_MAP_NAMESPACE};
@@ -51,22 +53,8 @@ pub fn get_family(head: &FamilyHead, store: &dyn Storage) -> Result<Family, Mixn
}
}
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 save_family(f: &Family, store: &mut dyn Storage) -> Result<(), MixnetContractError> {
Ok(families().save(store, f.head_identity().to_string(), f)?)
}
pub fn add_family_member(
+146 -239
View File
@@ -1,136 +1,99 @@
use crate::support::helpers::{
ensure_bonded, ensure_sent_by_vesting_contract, 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};
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage::{
add_family_member, create_family, get_family, is_any_member, is_family_member,
remove_family_member,
add_family_member, get_family, is_any_member, is_family_member, remove_family_member,
save_family,
};
use crate::families::queries::get_family_by_label;
use crate::families::signature_helpers::verify_family_join_permit;
use crate::support::helpers::{ensure_bonded, ensure_sent_by_vesting_contract};
use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response};
use mixnet_contract_common::families::{Family, FamilyHead};
use mixnet_contract_common::{error::MixnetContractError, IdentityKey};
use nym_contracts_common::signing::MessageSignature;
/// Creates a new MixNode family with senders node as head
pub fn try_create_family(
deps: DepsMut,
info: MessageInfo,
owner_signature: String,
label: &str,
label: String,
) -> Result<Response, MixnetContractError> {
_try_create_family(deps, &info.sender, owner_signature, label, None)
_try_create_family(deps, &info.sender, label, None)
}
pub fn try_create_family_on_behalf(
deps: DepsMut,
info: MessageInfo,
owner_address: String,
owner_signature: String,
label: &str,
label: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let owner_address = deps.api.addr_validate(&owner_address)?;
_try_create_family(
deps,
&owner_address,
owner_signature,
label,
Some(info.sender),
)
_try_create_family(deps, &owner_address, label, Some(info.sender))
}
fn _try_create_family(
deps: DepsMut,
owner: &Addr,
owner_signature: String,
label: &str,
label: String,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let existing_bond = crate::mixnodes::storage::mixnode_bonds()
.idx
.owner
.item(deps.storage, owner.clone())?
.ok_or(MixnetContractError::NoAssociatedMixNodeBond {
owner: owner.clone(),
})?
.1;
let existing_bond =
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
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) {
// can't overwrite existing family
if get_family(&family_head, deps.storage).is_ok() {
return Err(MixnetContractError::FamilyCanHaveOnlyOne);
}
// the label must be unique
if get_family_by_label(label.clone(), deps.storage)?.is_some() {
return Err(MixnetContractError::FamilyWithLabelExists(label));
}
let family = Family::new(family_head, proxy, label);
create_family(&family, deps.storage)?;
save_family(&family, deps.storage)?;
Ok(Response::default())
}
pub fn try_join_family(
deps: DepsMut,
info: MessageInfo,
// Required for proxy joining
node_identity_signature: Option<String>,
family_signature: String,
family_head: IdentityKey,
join_permit: MessageSignature,
family_head: FamilyHead,
) -> Result<Response, MixnetContractError> {
let family_head = FamilyHead::new(&family_head);
_try_join_family(
deps,
&info.sender,
node_identity_signature,
family_signature,
family_head,
)
_try_join_family(deps, &info.sender, join_permit, family_head, None)
}
pub fn try_join_family_on_behalf(
deps: DepsMut,
info: MessageInfo,
member_address: String,
node_identity_signature: Option<String>,
family_signature: String,
family_head: IdentityKey,
join_permit: MessageSignature,
family_head: FamilyHead,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let member_address = deps.api.addr_validate(&member_address)?;
let family_head = FamilyHead::new(&family_head);
_try_join_family(
deps,
&member_address,
node_identity_signature,
family_signature,
family_head,
)
let proxy = Some(info.sender);
_try_join_family(deps, &member_address, join_permit, family_head, proxy)
}
fn _try_join_family(
deps: DepsMut,
owner: &Addr,
node_identity_signature: Option<String>,
family_signature: String,
join_permit: MessageSignature,
family_head: FamilyHead,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let existing_bond = crate::mixnodes::storage::mixnode_bonds()
.idx
.owner
.item(deps.storage, owner.clone())?
.ok_or(MixnetContractError::NoAssociatedMixNodeBond {
owner: owner.clone(),
})?
.1;
let existing_bond =
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
ensure_bonded(&existing_bond)?;
@@ -147,20 +110,12 @@ fn _try_join_family(
));
}
if let Some(node_identity_signature) = node_identity_signature {
validate_node_identity_signature(
deps.as_ref(),
owner,
&node_identity_signature,
existing_bond.identity(),
)?;
}
validate_family_signature(
verify_family_join_permit(
deps.as_ref(),
family_head.clone(),
proxy,
existing_bond.identity(),
&family_signature,
family_head.identity(),
join_permit,
)?;
let family = get_family(&family_head, deps.storage)?;
@@ -173,41 +128,30 @@ fn _try_join_family(
pub fn try_leave_family(
deps: DepsMut,
info: MessageInfo,
signature: String,
family_head: IdentityKey,
family_head: FamilyHead,
) -> Result<Response, MixnetContractError> {
let family_head = FamilyHead::new(&family_head);
_try_leave_family(deps, &info.sender, signature, family_head)
_try_leave_family(deps, &info.sender, family_head)
}
pub fn try_leave_family_on_behalf(
deps: DepsMut,
info: MessageInfo,
member_address: String,
node_family_signature: String,
family_head: IdentityKey,
family_head: FamilyHead,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let family_head = FamilyHead::new(&family_head);
let member_address = deps.api.addr_validate(&member_address)?;
_try_leave_family(deps, &member_address, node_family_signature, family_head)
_try_leave_family(deps, &member_address, family_head)
}
fn _try_leave_family(
deps: DepsMut,
owner: &Addr,
node_family_signature: String,
family_head: FamilyHead,
) -> Result<Response, MixnetContractError> {
let existing_bond = crate::mixnodes::storage::mixnode_bonds()
.idx
.owner
.item(deps.storage, owner.clone())?
.ok_or(MixnetContractError::NoAssociatedMixNodeBond {
owner: owner.clone(),
})?
.1;
let existing_bond =
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
ensure_bonded(&existing_bond)?;
@@ -226,13 +170,6 @@ fn _try_leave_family(
});
}
validate_node_identity_signature(
deps.as_ref(),
owner,
&node_family_signature,
existing_bond.identity(),
)?;
remove_family_member(deps.storage, existing_bond.identity());
Ok(Response::default())
@@ -241,62 +178,56 @@ fn _try_leave_family(
pub fn try_head_kick_member(
deps: DepsMut,
info: MessageInfo,
owner_signature: String,
member: IdentityKeyRef,
member: IdentityKey,
) -> Result<Response, MixnetContractError> {
_try_head_kick_member(deps, &info.sender, owner_signature, member)
_try_head_kick_member(deps, &info.sender, member)
}
pub fn try_head_kick_member_on_behalf(
deps: DepsMut,
info: MessageInfo,
head_address: String,
owner_signature: String,
member: IdentityKeyRef,
member: IdentityKey,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let head_address = deps.api.addr_validate(&head_address)?;
_try_head_kick_member(deps, &head_address, owner_signature, member)
_try_head_kick_member(deps, &head_address, member)
}
#[allow(unused_variables)]
fn _try_head_kick_member(
deps: DepsMut,
owner: &Addr,
owner_signature: String,
member: IdentityKeyRef<'_>,
member: IdentityKey,
) -> Result<Response, MixnetContractError> {
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;
let head_bond = crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
// ensure_bonded(&existing_bond)?;
// make sure we're still in the mixnet
ensure_bonded(&head_bond)?;
// validate_node_identity_signature(
// deps.as_ref(),
// owner,
// &owner_signature,
// existing_bond.identity(),
// )?;
// make sure we're not trying to kick ourselves...
if member == head_bond.identity() {
return Err(MixnetContractError::CantLeaveOwnFamily {
head: head_bond.identity().to_string(),
member,
});
}
// 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(),
// });
// }
// get the family details
let family_head = FamilyHead::new(head_bond.identity());
let family = get_family(&family_head, deps.storage)?;
// remove_family_member(deps.storage, member);
// Ok(Response::default())
// make sure the member we're trying to kick is an actual member
if !is_family_member(deps.storage, &family, &member)? {
return Err(MixnetContractError::NotAMember {
head: family_head.identity().to_string(),
member,
});
}
// finally get rid of the member
remove_family_member(deps.storage, &member);
Ok(Response::default())
}
#[cfg(test)]
@@ -322,9 +253,9 @@ mod test {
let cost_params = fixtures::mix_node_cost_params_fixture();
let (head_mixnode, head_bond_sig, head_keypair) = test.mixnode_with_signature(head, None);
let (malicious_mixnode, malicious_bond_sig, malicious_keypair) =
let (malicious_mixnode, malicious_bond_sig, _malicious_keypair) =
test.mixnode_with_signature(malicious_head, None);
let (member_mixnode, member_bond_sig, member_keypair) =
let (member_mixnode, member_bond_sig, _member_keypair) =
test.mixnode_with_signature(member, None);
crate::mixnodes::transactions::try_add_mixnode(
@@ -357,26 +288,14 @@ mod test {
)
.unwrap();
let old_style_head_sig = head_keypair.private_key().sign_text(head);
let old_style_malicious_head_sig =
malicious_keypair.private_key().sign_text(malicious_head);
let old_style_member_sig = member_keypair.private_key().sign_text(member);
try_create_family(
test.deps_mut(),
mock_info(head, &[]),
old_style_head_sig,
"test",
)
.unwrap();
try_create_family(test.deps_mut(), mock_info(head, &[]), "test".to_string()).unwrap();
let family_head = FamilyHead::new(&head_mixnode.identity_key);
assert!(get_family(&family_head, test.deps().storage).is_ok());
let nope = try_create_family(
test.deps_mut(),
mock_info(malicious_head, &[]),
old_style_malicious_head_sig,
"test",
"test".to_string(),
);
match nope {
@@ -387,24 +306,23 @@ mod test {
},
}
let family = get_family_by_label("test", test.deps().storage).unwrap();
let family = get_family_by_label("test".to_string(), 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(), test.deps().storage).unwrap();
let family = get_family_by_head(family_head.identity(), test.deps().storage)
.unwrap()
.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();
let join_permit =
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key, false);
try_join_family(
test.deps_mut(),
mock_info(member, &[]),
Some(old_style_member_sig.clone()),
join_signature.clone(),
head_mixnode.identity_key.clone(),
join_permit,
family_head.clone(),
)
.unwrap();
@@ -414,11 +332,34 @@ mod test {
is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap()
);
try_leave_family(
try_leave_family(test.deps_mut(), mock_info(member, &[]), family_head.clone()).unwrap();
let family = get_family(&family_head, test.deps().storage).unwrap();
assert!(
!is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap()
);
let new_join_permit =
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key, false);
try_join_family(
test.deps_mut(),
mock_info(member, &[]),
old_style_member_sig.clone(),
head_mixnode.identity_key.clone(),
new_join_permit,
family_head.clone(),
)
.unwrap();
let family = get_family(&family_head, test.deps().storage).unwrap();
assert!(
is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap()
);
try_head_kick_member(
test.deps_mut(),
mock_info(head, &[]),
member_mixnode.identity_key.clone(),
)
.unwrap();
@@ -426,32 +367,6 @@ mod test {
assert!(
!is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap()
);
try_join_family(
test.deps_mut(),
mock_info(member, &[]),
Some(old_style_member_sig),
join_signature,
head_mixnode.identity_key,
)
.unwrap();
let family = get_family(&family_head, test.deps().storage).unwrap();
assert!(
is_family_member(test.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, test.deps().storage).unwrap();
// assert!(!is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap());
}
#[cfg(test)]
@@ -468,15 +383,13 @@ mod test {
let head = "alice";
let (_, keypair) = test.add_dummy_mixnode_with_proxy_and_keypair(head, None);
let sig = keypair.private_key().sign_text(head);
test.add_dummy_mixnode(head, None);
let res = try_create_family_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[]),
head.to_string(),
sig,
"label",
"label".to_string(),
)
.unwrap_err();
@@ -507,23 +420,22 @@ mod test {
let new_member = "vin-diesel";
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
let (_, member_keys) = test.add_dummy_mixnode_with_proxy_and_keypair(new_member, None);
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
// TODO: those signatures are WRONG and have to be c hanged
let join_signature = head_keys
.private_key()
.sign_text(&member_keys.public_key().to_base58_string());
let member_sig = member_keys.private_key().sign_text(new_member);
let join_permit = test.generate_family_join_permit(
&head_keys,
&member_keys.public_key().to_base58_string(),
false,
);
let head_identity = head_keys.public_key().to_base58_string();
let family_head = FamilyHead::new(&head_identity);
let res = try_join_family_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[]),
new_member.to_string(),
Some(member_sig),
join_signature,
head_identity,
join_permit,
family_head,
)
.unwrap_err();
@@ -554,33 +466,30 @@ mod test {
let new_member = "vin-diesel";
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
let (_, member_keys) = test.add_dummy_mixnode_with_proxy_and_keypair(new_member, None);
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
// TODO: those signatures are WRONG and have to be changed
let join_signature = head_keys
.private_key()
.sign_text(&member_keys.public_key().to_base58_string());
let member_sig = member_keys.private_key().sign_text(new_member);
let join_permit = test.generate_family_join_permit(
&head_keys,
&member_keys.public_key().to_base58_string(),
true,
);
let head_identity = head_keys.public_key().to_base58_string();
let family_head = FamilyHead::new(&head_identity);
try_join_family_on_behalf(
test.deps_mut(),
mock_info(vesting_contract.as_ref(), &[]),
new_member.to_string(),
Some(member_sig.clone()),
join_signature,
head_identity,
join_permit,
family_head.clone(),
)
.unwrap();
let leave_signature = member_sig;
let res = try_leave_family_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[]),
new_member.to_string(),
leave_signature,
head_keys.public_key().to_base58_string(),
family_head.clone(),
)
.unwrap_err();
@@ -611,33 +520,31 @@ mod test {
let new_member = "vin-diesel";
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
let (_, member_keys) = test.add_dummy_mixnode_with_proxy_and_keypair(new_member, None);
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
// TODO: those signatures are WRONG and have to be c hanged
let join_signature = head_keys
.private_key()
.sign_text(&member_keys.public_key().to_base58_string());
let member_sig = member_keys.private_key().sign_text(new_member);
let join_permit = test.generate_family_join_permit(
&head_keys,
&member_keys.public_key().to_base58_string(),
true,
);
let head_identity = head_keys.public_key().to_base58_string();
let family_head = FamilyHead::new(&head_identity);
try_join_family_on_behalf(
test.deps_mut(),
mock_info(vesting_contract.as_ref(), &[]),
new_member.to_string(),
Some(member_sig),
join_signature,
head_identity,
join_permit,
family_head,
)
.unwrap();
// TODO: a completely wrong signature is being used
let res = try_head_kick_member_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[]),
head.to_string(),
head_keys.private_key().sign_text(head),
&member_keys.public_key().to_base58_string(),
member_keys.public_key().to_base58_string(),
)
.unwrap_err();
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::signing::storage as signing_storage;
use crate::support::helpers::decode_ed25519_identity_key;
use cosmwasm_std::{Addr, Coin, Deps};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::{construct_gateway_bonding_sign_payload, Gateway};
@@ -17,10 +18,7 @@ pub(crate) fn verify_gateway_bonding_signature(
signature: MessageSignature,
) -> Result<(), MixnetContractError> {
// recover the public key
let mut public_key = [0u8; 32];
bs58::decode(&gateway.identity_key)
.into(&mut public_key)
.map_err(|err| MixnetContractError::MalformedEd25519IdentityKey(err.to_string()))?;
let public_key = decode_ed25519_identity_key(&gateway.identity_key)?;
// reconstruct the payload
let nonce = signing_storage::get_signing_nonce(deps.storage, sender.clone())?;
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::signing::storage as signing_storage;
use crate::support::helpers::decode_ed25519_identity_key;
use cosmwasm_std::{Addr, Coin, Deps};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::{construct_mixnode_bonding_sign_payload, MixNode, MixNodeCostParams};
@@ -18,10 +19,7 @@ pub(crate) fn verify_mixnode_bonding_signature(
signature: MessageSignature,
) -> Result<(), MixnetContractError> {
// recover the public key
let mut public_key = [0u8; 32];
bs58::decode(&mixnode.identity_key)
.into(&mut public_key)
.map_err(|err| MixnetContractError::MalformedEd25519IdentityKey(err.to_string()))?;
let public_key = decode_ed25519_identity_key(&mixnode.identity_key)?;
// reconstruct the payload
let nonce = signing_storage::get_signing_nonce(deps.storage, sender.clone())?;
@@ -245,12 +245,7 @@ pub(crate) fn _try_remove_mixnode(
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let existing_bond = storage::mixnode_bonds()
.idx
.owner
.item(deps.storage, owner.clone())?
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?
.1;
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?;
// unbonding is only allowed if the epoch is currently not in the process of being advanced
ensure_epoch_in_progress_state(deps.storage)?;
+9 -176
View File
@@ -4,9 +4,7 @@
use crate::gateways::storage as gateways_storage;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnodes::storage as mixnodes_storage;
use cosmwasm_std::{
wasm_execute, Addr, BankMsg, Coin, CosmosMsg, Deps, MessageInfo, Response, Storage,
};
use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, CosmosMsg, MessageInfo, Response, Storage};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixId, MixNodeBond};
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
@@ -371,184 +369,19 @@ pub(crate) fn ensure_no_existing_bond(
Ok(())
}
pub fn validate_node_identity_signature(
deps: Deps<'_>,
owner: &Addr,
signature: &str,
identity: IdentityKeyRef<'_>,
) -> Result<(), MixnetContractError> {
validate_ed25519_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_ed25519_signature(deps, family_member.as_bytes(), signature, family_head)
}
pub(crate) fn validate_ed25519_signature(
deps: Deps<'_>,
signed_bytes: &[u8],
signature: &str,
identity: IdentityKeyRef<'_>,
) -> Result<(), MixnetContractError> {
let mut identity_bytes = [0u8; 32];
let mut signature_bytes = [0u8; 64];
let identity_used_bytes = bs58::decode(identity)
.into(&mut identity_bytes)
pub(crate) fn decode_ed25519_identity_key(
encoded: IdentityKeyRef,
) -> Result<[u8; 32], MixnetContractError> {
let mut public_key = [0u8; 32];
let used = bs58::decode(encoded)
.into(&mut public_key)
.map_err(|err| MixnetContractError::MalformedEd25519IdentityKey(err.to_string()))?;
let signature_used_bytes = bs58::decode(signature)
.into(&mut signature_bytes)
.map_err(|err| MixnetContractError::MalformedEd25519Signature(err.to_string()))?;
if identity_used_bytes != 32 {
if used != 32 {
return Err(MixnetContractError::MalformedEd25519IdentityKey(
"Too few bytes provided for the public key".into(),
));
}
if signature_used_bytes != 64 {
return Err(MixnetContractError::MalformedEd25519Signature(
"Too few bytes provided for the signature".into(),
));
}
let res = deps
.api
.ed25519_verify(signed_bytes, &signature_bytes, &identity_bytes)
.map_err(cosmwasm_std::StdError::verification_err)?;
if !res {
Err(MixnetContractError::InvalidEd25519Signature)
} else {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::testing::mock_dependencies;
use nym_crypto::asymmetric::identity;
use rand_chacha::rand_core::SeedableRng;
#[test]
fn validating_node_signature() {
let deps = mock_dependencies();
// since those tests are NOT compiled to wasm, we can use rng-related dependency
let dummy_seed = [1u8; 32];
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
let short_bs58 = "2SfEgZ4aQUr3HSwqE";
let long_bs58 = "g34PyULki9fc3FqKobj5wdVNCaWAt1M9oZowyyMFfWSCejxg7wt574piZVjqjFEN2UXsgZ56KTkKf3jnWD4DJ2Gsf7KXQAvptFfcYRrZHTjMVo3NXcBSNm3wDBKZWZURzp4Fixv";
let address1 = Addr::unchecked("some-dummy-address1");
let address2 = Addr::unchecked("some-dummy-address2");
let keypair1 = identity::KeyPair::new(&mut rng);
let keypair2 = identity::KeyPair::new(&mut rng);
let sig_addr1_key1 = keypair1
.private_key()
.sign(address1.as_bytes())
.to_base58_string();
let sig_addr2_key1 = keypair1
.private_key()
.sign(address2.as_bytes())
.to_base58_string();
let sig_addr1_key2 = keypair2
.private_key()
.sign(address1.as_bytes())
.to_base58_string();
assert_eq!(
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, long_bs58,)
);
assert_eq!(
Err(MixnetContractError::MalformedEd25519Signature(
"buffer provided to decode base58 encoded string into was too small".into()
)),
validate_node_identity_signature(
deps.as_ref(),
&address1,
long_bs58.into(),
&keypair1.public_key().to_base58_string(),
)
);
assert_eq!(
Err(MixnetContractError::MalformedEd25519IdentityKey(
"Too few bytes provided for the public key".into()
)),
validate_node_identity_signature(deps.as_ref(), &address1, &sig_addr1_key1, short_bs58,)
);
assert_eq!(
Err(MixnetContractError::MalformedEd25519Signature(
"Too few bytes provided for the signature".into()
)),
validate_node_identity_signature(
deps.as_ref(),
&address1,
short_bs58.into(),
&keypair1.public_key().to_base58_string(),
)
);
assert_eq!(
Err(MixnetContractError::InvalidEd25519Signature),
validate_node_identity_signature(
deps.as_ref(),
&address1,
&sig_addr1_key1,
&keypair2.public_key().to_base58_string(),
)
);
assert_eq!(
Err(MixnetContractError::InvalidEd25519Signature),
validate_node_identity_signature(
deps.as_ref(),
&address1,
&sig_addr2_key1,
&keypair1.public_key().to_base58_string(),
)
);
assert_eq!(
Err(MixnetContractError::InvalidEd25519Signature),
validate_node_identity_signature(
deps.as_ref(),
&address2,
&sig_addr1_key1,
&keypair1.public_key().to_base58_string(),
)
);
assert_eq!(
Err(MixnetContractError::InvalidEd25519Signature),
validate_node_identity_signature(
deps.as_ref(),
&address1,
&sig_addr1_key2,
&keypair1.public_key().to_base58_string(),
)
);
assert!(validate_node_identity_signature(
deps.as_ref(),
&address1,
&sig_addr1_key1,
&keypair1.public_key().to_base58_string(),
)
.is_ok());
}
Ok(public_key)
}
+99 -14
View File
@@ -54,6 +54,7 @@ pub mod test_helpers {
use mixnet_contract_common::events::{
may_find_attribute, MixnetEventType, DELEGATES_REWARD_KEY, OPERATOR_REWARD_KEY,
};
use mixnet_contract_common::families::FamilyHead;
use mixnet_contract_common::mixnode::{MixNodeRewarding, UnbondedMixnode};
use mixnet_contract_common::pending_events::{PendingEpochEventData, PendingIntervalEventData};
use mixnet_contract_common::reward_params::{Performance, RewardingParams};
@@ -61,10 +62,10 @@ pub mod test_helpers {
use mixnet_contract_common::rewarding::simulator::Simulator;
use mixnet_contract_common::rewarding::RewardDistribution;
use mixnet_contract_common::{
Delegation, EpochState, EpochStatus, Gateway, GatewayBondingPayload, IdentityKey,
InitialRewardingParams, InstantiateMsg, Interval, MixId, MixNode, MixNodeBond,
MixnodeBondingPayload, Percent, RewardedSetNodeStatus, SignableGatewayBondingMsg,
SignableMixNodeBondingMsg,
construct_family_join_permit, Delegation, EpochState, EpochStatus, Gateway,
GatewayBondingPayload, IdentityKey, IdentityKeyRef, InitialRewardingParams, InstantiateMsg,
Interval, MixId, MixNode, MixNodeBond, MixnodeBondingPayload, Percent,
RewardedSetNodeStatus, SignableGatewayBondingMsg, SignableMixNodeBondingMsg,
};
use nym_contracts_common::signing::{
ContractMessageContent, MessageSignature, SignableMessage, SigningAlgorithm, SigningPurpose,
@@ -169,26 +170,64 @@ pub mod test_helpers {
.collect::<Vec<_>>()
}
pub fn generate_family_join_permit(
&mut self,
family_owner_keys: &identity::KeyPair,
member_node: IdentityKeyRef,
vesting: bool,
) -> MessageSignature {
let identity = family_owner_keys.public_key().to_base58_string();
let head_mixnode = mixnodes_storage::mixnode_bonds()
.idx
.identity_key
.item(self.deps().storage, identity.clone())
.unwrap()
.map(|record| record.1)
.unwrap();
let family_head = FamilyHead::new(&identity);
let owner = head_mixnode.owner;
let nonce =
signing_storage::get_signing_nonce(self.deps().storage, owner.clone()).unwrap();
let proxy = if vesting {
Some(self.vesting_contract())
} else {
None
};
let msg =
construct_family_join_permit(nonce, family_head, proxy, member_node.to_owned());
let sig_bytes = family_owner_keys
.private_key()
.sign(&msg.to_plaintext().unwrap())
.to_bytes();
MessageSignature::from(sig_bytes.as_ref())
}
#[allow(unused)]
pub fn join_family(
&mut self,
member: &str,
member_keys: &identity::KeyPair,
head_keys: &identity::KeyPair,
vesting: bool,
) {
let identity_signature = member_keys.private_key().sign_text(member);
let join_signature = head_keys
.private_key()
.sign(&member_keys.public_key().to_bytes())
.to_base58_string();
let member_identity = member_keys.public_key().to_base58_string();
let head_identity = head_keys.public_key().to_base58_string();
let join_permit =
self.generate_family_join_permit(head_keys, &member_identity, vesting);
let family_head = FamilyHead::new(&head_identity);
try_join_family(
self.deps_mut(),
mock_info(member, &[]),
Some(identity_signature),
join_signature,
head_identity,
join_permit,
family_head,
)
.unwrap();
}
@@ -199,9 +238,8 @@ pub mod test_helpers {
label: &str,
) -> (MixId, identity::KeyPair) {
let (mix_id, keys) = self.add_dummy_mixnode_with_proxy_and_keypair(head, None);
let sig = keys.private_key().sign_text(head);
try_create_family(self.deps_mut(), mock_info(head, &[]), sig, label).unwrap();
try_create_family(self.deps_mut(), mock_info(head, &[]), label.to_string()).unwrap();
(mix_id, keys)
}
@@ -290,6 +328,53 @@ pub mod test_helpers {
ed25519_sign_message(msg, key)
}
pub fn add_dummy_mixnode_with_keypair(
&mut self,
owner: &str,
stake: Option<Uint128>,
) -> (MixId, identity::KeyPair) {
let stake = self.make_mix_pledge(stake);
let keypair = identity::KeyPair::new(&mut self.rng);
let identity_key = keypair.public_key().to_base58_string();
let legit_sphinx_keys = nym_crypto::asymmetric::encryption::KeyPair::new(&mut self.rng);
let mixnode = MixNode {
identity_key,
sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
..tests::fixtures::mix_node_fixture()
};
let msg = mixnode_bonding_sign_payload(
self.deps(),
owner,
None,
mixnode.clone(),
stake.clone(),
);
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
let info = mock_info(owner, &stake);
let current_id_counter = mixnodes_storage::MIXNODE_ID_COUNTER
.may_load(self.deps().storage)
.unwrap()
.unwrap_or_default();
let env = self.env();
try_add_mixnode(
self.deps_mut(),
env,
info,
mixnode,
tests::fixtures::mix_node_cost_params_fixture(),
owner_signature,
)
.unwrap();
// newly added mixnode gets assigned the current counter + 1
(current_id_counter + 1, keypair)
}
pub fn add_dummy_mixnode_with_proxy_and_keypair(
&mut self,
owner: &str,
+13 -36
View File
@@ -14,6 +14,7 @@ use cosmwasm_std::{
QueryResponse, Response, StdResult, Timestamp, Uint128,
};
use cw_storage_plus::Bound;
use mixnet_contract_common::families::FamilyHead;
use mixnet_contract_common::gateway::GatewayConfigUpdate;
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use mixnet_contract_common::{Gateway, MixId, MixNode};
@@ -111,28 +112,13 @@ pub fn execute(
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::CreateFamily {
owner_signature,
label,
} => try_create_family(info, deps, owner_signature, label),
ExecuteMsg::CreateFamily { label } => try_create_family(info, deps, label),
ExecuteMsg::JoinFamily {
node_identity_signature,
family_signature,
join_permit,
family_head,
} => try_join_family(
info,
deps,
node_identity_signature,
family_signature,
family_head,
),
ExecuteMsg::LeaveFamily {
node_identity_signature,
family_head,
} => try_leave_family(info, deps, node_identity_signature, family_head),
ExecuteMsg::KickFamilyMember { signature, member } => {
try_kick_family_member(info, deps, signature, member)
}
} => try_join_family(info, deps, join_permit, family_head),
ExecuteMsg::LeaveFamily { family_head } => try_leave_family(info, deps, family_head),
ExecuteMsg::KickFamilyMember { member } => try_kick_family_member(info, deps, member),
ExecuteMsg::UpdateLockedPledgeCap { address, cap } => {
try_update_locked_pledge_cap(address, cap, info, deps)
}
@@ -226,44 +212,35 @@ pub fn execute(
pub fn try_create_family(
info: MessageInfo,
deps: DepsMut,
owner_signature: String,
label: String,
) -> Result<Response, ContractError> {
let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?;
account.try_create_family(deps.storage, owner_signature, label)
account.try_create_family(deps.storage, label)
}
pub fn try_join_family(
info: MessageInfo,
deps: DepsMut,
node_identity_signature: String,
family_signature: String,
family_head: String,
join_permit: MessageSignature,
family_head: FamilyHead,
) -> Result<Response, ContractError> {
let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?;
account.try_join_family(
deps.storage,
node_identity_signature,
family_signature,
&family_head,
)
account.try_join_family(deps.storage, join_permit, family_head)
}
pub fn try_leave_family(
info: MessageInfo,
deps: DepsMut,
node_identity_signature: String,
family_head: String,
family_head: FamilyHead,
) -> Result<Response, ContractError> {
let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?;
account.try_leave_family(deps.storage, node_identity_signature, &family_head)
account.try_leave_family(deps.storage, family_head)
}
pub fn try_kick_family_member(
info: MessageInfo,
deps: DepsMut,
signature: String,
member: String,
) -> Result<Response, ContractError> {
let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?;
account.try_head_kick_member(deps.storage, signature, &member)
account.try_head_kick_member(deps.storage, &member)
}
/// Update locked_pledge_cap, the hard cap for staking/bonding with unvested tokens.
@@ -1,34 +1,32 @@
use crate::errors::ContractError;
use contracts_common::signing::MessageSignature;
use cosmwasm_std::{Response, Storage};
use mixnet_contract_common::families::FamilyHead;
use mixnet_contract_common::IdentityKeyRef;
pub trait NodeFamilies {
fn try_create_family(
&self,
storage: &dyn Storage,
owner_signature: String,
label: String,
) -> Result<Response, ContractError>;
fn try_join_family(
&self,
storage: &dyn Storage,
node_identity_signature: String,
family_signature: String,
family_head: IdentityKeyRef,
join_permit: MessageSignature,
family_head: FamilyHead,
) -> Result<Response, ContractError>;
fn try_leave_family(
&self,
storage: &dyn Storage,
signature: String,
family_head: IdentityKeyRef,
family_head: FamilyHead,
) -> Result<Response, ContractError>;
fn try_head_kick_member(
&self,
storage: &dyn Storage,
signature: String,
member: IdentityKeyRef<'_>,
) -> Result<Response, ContractError>;
}
@@ -1,18 +1,18 @@
use super::Account;
use crate::{errors::ContractError, storage::MIXNET_CONTRACT_ADDRESS, traits::NodeFamilies};
use contracts_common::signing::MessageSignature;
use cosmwasm_std::{wasm_execute, Response, Storage};
use mixnet_contract_common::families::FamilyHead;
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<Response, ContractError> {
let msg = MixnetExecuteMsg::CreateFamilyOnBehalf {
owner_address: self.owner_address().to_string(),
owner_signature,
owner_address: self.owner_address().into_string(),
label,
};
@@ -24,15 +24,13 @@ impl NodeFamilies for Account {
fn try_join_family(
&self,
storage: &dyn Storage,
node_identity_signature: String,
family_signature: String,
family_head: IdentityKeyRef,
join_permit: MessageSignature,
family_head: FamilyHead,
) -> Result<Response, ContractError> {
let msg = MixnetExecuteMsg::JoinFamilyOnBehalf {
member_address: self.owner_address().to_string(),
node_identity_signature,
family_signature,
family_head: family_head.to_string(),
join_permit,
family_head,
};
let msg = wasm_execute(MIXNET_CONTRACT_ADDRESS.load(storage)?, &msg, vec![])?;
@@ -43,13 +41,11 @@ impl NodeFamilies for Account {
fn try_leave_family(
&self,
storage: &dyn Storage,
node_identity_signature: String,
family_head: IdentityKeyRef,
family_head: FamilyHead,
) -> Result<Response, ContractError> {
let msg = MixnetExecuteMsg::LeaveFamilyOnBehalf {
member_address: self.owner_address().to_string(),
node_identity_signature,
family_head: family_head.to_string(),
family_head,
};
let msg = wasm_execute(MIXNET_CONTRACT_ADDRESS.load(storage)?, &msg, vec![])?;
@@ -60,12 +56,10 @@ impl NodeFamilies for Account {
fn try_head_kick_member(
&self,
storage: &dyn Storage,
signature: String,
member: IdentityKeyRef<'_>,
) -> Result<Response, ContractError> {
let msg = MixnetExecuteMsg::KickFamilyMemberOnBehalf {
head_address: self.owner_address().to_string(),
signature,
member: member.to_string(),
};
-4
View File
@@ -74,10 +74,6 @@ 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,
@@ -1,81 +0,0 @@
use crate::error::BackendError;
use crate::state::WalletState;
use nym_types::transaction::TransactionExecuteResult;
use validator_client::nyxd::traits::MixnetSigningClient;
use validator_client::nyxd::Fee;
#[tauri::command]
pub async fn create_family(
signature: String,
label: String,
fee: Option<Fee>,
state: tauri::State<'_, WalletState>,
) -> Result<TransactionExecuteResult, BackendError> {
let guard = state.read().await;
let fee_amount = guard.convert_tx_fee(fee.as_ref());
let res = guard
.current_client()?
.nyxd
.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<Fee>,
state: tauri::State<'_, WalletState>,
) -> Result<TransactionExecuteResult, BackendError> {
let guard = state.read().await;
let fee_amount = guard.convert_tx_fee(fee.as_ref());
let res = guard
.current_client()?
.nyxd
.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<Fee>,
state: tauri::State<'_, WalletState>,
) -> Result<TransactionExecuteResult, BackendError> {
let guard = state.read().await;
let fee_amount = guard.convert_tx_fee(fee.as_ref());
let res = guard
.current_client()?
.nyxd
.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<Fee>,
state: tauri::State<'_, WalletState>,
) -> Result<TransactionExecuteResult, BackendError> {
let guard = state.read().await;
let fee_amount = guard.convert_tx_fee(fee.as_ref());
let res = guard
.current_client()?
.nyxd
.kick_family_member(signature, member, fee)
.await?;
Ok(TransactionExecuteResult::from_execute_result(
res, fee_amount,
)?)
}
@@ -2,7 +2,6 @@ pub mod account;
pub mod admin;
pub mod bond;
pub mod delegate;
pub mod families;
pub mod interval;
pub mod rewards;
pub mod send;
+4 -2
View File
@@ -37,7 +37,7 @@ There are two ways to provide this:
### Passing named arguments
You will need to pass the following with every command as an argument:
You will need to pass the following with most commands as arguments:
```
--mnemonic <MNEMONIC>
@@ -70,7 +70,7 @@ nym-cli --help
- query for a block at a height
- query for a block at a timestamp
### 🪐 `cosmwasm`
### 🪐 cosmwasm
- upload a smart contract
- instantiate a smart contract
@@ -90,6 +90,8 @@ nym-cli --help
- query for waiting rewards
- withdraw rewards
- manage mixnode settings
- create payload for family creation signature
- create family
#### 🥩 Delegators
+6 -3
View File
@@ -135,13 +135,16 @@ async fn wait_for_interrupt() {
}
#[tokio::main]
async fn main() {
async fn main() -> anyhow::Result<()> {
setup_logging();
let cli = Cli::parse();
tokio::select! {
_ = wait_for_interrupt() => warn!("Received interrupt - the specified command might have not completed!"),
_ = execute(cli) => (),
_ = wait_for_interrupt() => {
warn!("Received interrupt - the specified command might have not completed!");
Ok(())
},
res = execute(cli) => res
}
}
@@ -0,0 +1,47 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_cli_commands::context::{create_query_client, create_signing_client, ClientArgs};
use nym_cli_commands::validator::mixnet::operators::mixnode::families::MixnetOperatorsMixnodeFamiliesCommands;
use nym_network_defaults::NymNetworkDetails;
pub(crate) async fn execute(
global_args: ClientArgs,
families: nym_cli_commands::validator::mixnet::operators::mixnode::families::MixnetOperatorsMixnodeFamilies,
network_details: &NymNetworkDetails,
) -> anyhow::Result<()> {
match families.command {
MixnetOperatorsMixnodeFamiliesCommands::CreateFamily(args) => {
nym_cli_commands::validator::mixnet::operators::mixnode::families::create_family::create_family(
args,
create_signing_client(global_args, network_details)?,
)
.await
}
MixnetOperatorsMixnodeFamiliesCommands::JoinFamily(args) => {
nym_cli_commands::validator::mixnet::operators::mixnode::families::join_family::join_family(
args,
create_signing_client(global_args, network_details)?,
)
.await
}
MixnetOperatorsMixnodeFamiliesCommands::LeaveFamily(args) => {
nym_cli_commands::validator::mixnet::operators::mixnode::families::leave_family::leave_family(
args,
create_signing_client(global_args, network_details)?,
)
.await
}
MixnetOperatorsMixnodeFamiliesCommands::KickFamilyMember(args) => {
nym_cli_commands::validator::mixnet::operators::mixnode::families::kick_family_member::kick_family_member(
args,
create_signing_client(global_args, network_details)?,
)
.await
}
MixnetOperatorsMixnodeFamiliesCommands::CreateFamilyJoinPermitSignPayload(args) => {
nym_cli_commands::validator::mixnet::operators::mixnode::families::create_family_join_permit_sign_payload::create_family_join_permit_sign_payload(args, create_query_client(network_details)?).await
}
}
Ok(())
}
@@ -4,6 +4,7 @@
use nym_cli_commands::context::{create_signing_client, ClientArgs};
use nym_network_defaults::NymNetworkDetails;
pub(crate) mod families;
pub(crate) mod keys;
pub(crate) mod rewards;
pub(crate) mod settings;
@@ -23,6 +24,9 @@ pub(crate) async fn execute(
nym_cli_commands::validator::mixnet::operators::mixnode::MixnetOperatorsMixnodeCommands::Settings(settings) => {
settings::execute(global_args, settings, network_details).await?
}
nym_cli_commands::validator::mixnet::operators::mixnode::MixnetOperatorsMixnodeCommands::Families(families) => {
families::execute(global_args, families, network_details).await?
}
nym_cli_commands::validator::mixnet::operators::mixnode::MixnetOperatorsMixnodeCommands::Bond(args) => {
nym_cli_commands::validator::mixnet::operators::mixnode::bond_mixnode::bond_mixnode(args, create_signing_client(global_args, network_details)?).await
}