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:
committed by
GitHub
parent
5e04f48500
commit
aee4c2d80d
Generated
+1
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
+72
@@ -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 {
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user