Rebased the branch one more time

WIP; rebasing

Another branch squash

Squashing the v3 branch

changing min pledge amounts

logic for adding new nymnode into the contract

converting mixnode/gateway bonding into nym-node bonding

logic for migrating gateways into nymnodes

ibid for mixnodes

further nym-node work + fixed most existing unit tests

forbid nymnode migration with pending cost params changes

preassign nodeid for gateways

changing role assignment and epoch progression

changing role assignment and epoch progression

optional custom http port

logic for unbonding a nym-node

updating Delegation struct

logic for increasing pledge of either mixnode or nymnode

logic for decreasing pledge of either mixnode or a nym node

logic for changing cost params of either mixnode or a nym node

wip

initialise nymnodes storage

fixing transaction tests

fixed naive family tests

reward-compatibility related works

resolving delegation events

introduced rewarded set metadata

another iteration of restoring old tests

updated rewarding part of nym-api

parking the branch

unparking the branch

wip

purged families

added 'ExitGateway' role

passing explicit work factor for rewarding function

remove legacy layers storage

wip: node description queries

added announced ports to self-described api

step1 in gruelling journey of adding node_id to gateways

ensure epoch work never goes above 1.0

changed active set to contain role distribution

[theoretically] sending rewarding messages for the new rewarded set

[theoretically] assigning new rewarded set

reimplementing more nym-api features

remove legacy types

re-implement legacy network monitor

restoring further routes + minor refactor of NodeStatusCache

skimmed routes now return legacy nodes alongside nym-nodes

seemingly restored all functionalities in nym-api

removing more legacy things from the contract

initial contract cleanup

added nym-api endpoints to return generic annotations regardless of type

updated simulator to use new rewarding parameters

more contract cleanup

made existing mixnet contract tests compile

extra validation of nym-node bonding parameters

fixed additional compilation issues

fixed nym-api v3 database migration failure

added additional nym-node contract queries

updated the schema

made additional delegation/rewards queries compatible with both legacy mixnodes and nym-nodes

fixing existing unit tests in mixnet contract

wip

resolved first batch of 500 compiler errors

re-deprecating routes

making wallet's rust backend compile

fixed non-determinism in contract + nym-api build

fixes to the build

populating cotracts-cache with nym-nodes data

more missing nymnodes queries

temp mixnet contract methods + restored result submission in nym-api

allow deprecated routes

submitting correct results for mixnode results

removed deprecated re-export of AxumAppState and removed smurf naming

moved axum modules into support::http

cleaning up nym-api warnings

determine entry gateways before exits

exposed transaction to update nym-node config

missing memo for updating node config

 new routes

added routes to swagger and fixed relative paths

fixed some macro derivations

added nym-node commands to nym-cli
This commit is contained in:
Jędrzej Stuczyński
2024-03-28 11:36:36 +00:00
parent 27ac34522c
commit 51b511b27e
405 changed files with 23134 additions and 13138 deletions
Generated
+3 -8
View File
@@ -4280,7 +4280,6 @@ dependencies = [
"humantime-serde",
"itertools 0.13.0",
"k256",
"log",
"nym-api-requests",
"nym-bandwidth-controller",
"nym-bin-common",
@@ -4305,26 +4304,22 @@ dependencies = [
"nym-node-requests",
"nym-node-tester-utils",
"nym-pemstore",
"nym-serde-helpers",
"nym-sphinx",
"nym-task",
"nym-topology",
"nym-types",
"nym-validator-client",
"nym-vesting-contract-common",
"okapi",
"pin-project",
"rand",
"rand_chacha",
"reqwest 0.12.4",
"rocket",
"rocket_cors",
"rocket_okapi",
"schemars",
"serde",
"serde_json",
"sha2 0.9.9",
"sqlx",
"tap",
"tempfile",
"thiserror",
"time",
@@ -4333,7 +4328,6 @@ dependencies = [
"tokio-util",
"tower-http",
"tracing",
"tracing-subscriber",
"ts-rs",
"url",
"utoipa",
@@ -4356,9 +4350,9 @@ dependencies = [
"nym-crypto",
"nym-ecash-time",
"nym-mixnet-contract-common",
"nym-network-defaults",
"nym-node-requests",
"nym-serde-helpers",
"rocket",
"schemars",
"serde",
"serde_json",
@@ -5473,6 +5467,7 @@ dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-controllers",
"cw-storage-plus",
"cw2",
"humantime-serde",
"log",
+1
View File
@@ -275,6 +275,7 @@ parking_lot = "0.12.3"
pem = "0.8"
petgraph = "0.6.5"
pin-project = "1.0"
pin-project-lite = "0.2.14"
pretty_env_logger = "0.4.0"
publicsuffix = "2.2.3"
quote = "1"
@@ -3,11 +3,11 @@ use log::{debug, error};
use nym_explorer_client::{ExplorerClient, PrettyDetailedMixNodeBond};
use nym_network_defaults::var_names::EXPLORER_API;
use nym_topology::{
nym_topology_from_detailed,
nym_topology_from_basic_info,
provider_trait::{async_trait, TopologyProvider},
NymTopology,
};
use nym_validator_client::client::MixId;
use nym_validator_client::client::NodeId;
use rand::{prelude::SliceRandom, thread_rng};
use std::collections::HashMap;
use tap::TapOptional;
@@ -39,10 +39,10 @@ fn create_explorer_client() -> Option<ExplorerClient> {
fn group_mixnodes_by_country_code(
mixnodes: Vec<PrettyDetailedMixNodeBond>,
) -> HashMap<CountryGroup, Vec<MixId>> {
) -> HashMap<CountryGroup, Vec<NodeId>> {
mixnodes
.into_iter()
.fold(HashMap::<CountryGroup, Vec<MixId>>::new(), |mut acc, m| {
.fold(HashMap::<CountryGroup, Vec<NodeId>>::new(), |mut acc, m| {
if let Some(ref location) = m.location {
let country_code = location.two_letter_iso_country_code.clone();
let group_code = CountryGroup::new(country_code.as_str());
@@ -53,7 +53,7 @@ fn group_mixnodes_by_country_code(
})
}
fn log_mixnode_distribution(mixnodes: &HashMap<CountryGroup, Vec<MixId>>) {
fn log_mixnode_distribution(mixnodes: &HashMap<CountryGroup, Vec<NodeId>>) {
let mixnode_distribution = mixnodes
.iter()
.map(|(k, v)| format!("{}: {}", k, v.len()))
@@ -110,7 +110,7 @@ impl GeoAwareTopologyProvider {
}
async fn get_topology(&self) -> Option<NymTopology> {
let mixnodes = match self.validator_client.get_cached_active_mixnodes().await {
let mixnodes = match self.validator_client.get_basic_mixnodes(None).await {
Err(err) => {
error!("failed to get network mixnodes - {err}");
return None;
@@ -118,7 +118,7 @@ impl GeoAwareTopologyProvider {
Ok(mixes) => mixes,
};
let gateways = match self.validator_client.get_cached_gateways().await {
let gateways = match self.validator_client.get_basic_gateways(None).await {
Err(err) => {
error!("failed to get network gateways - {err}");
return None;
@@ -182,10 +182,10 @@ impl GeoAwareTopologyProvider {
let mixnodes = mixnodes
.into_iter()
.filter(|m| filtered_mixnode_ids.contains(&m.mix_id()))
.filter(|m| filtered_mixnode_ids.contains(&m.node_id))
.collect::<Vec<_>>();
let topology = nym_topology_from_detailed(mixnodes, gateways)
let topology = nym_topology_from_basic_info(&mixnodes, &gateways)
.filter_system_version(&self.client_version);
// TODO: return real error type
-10
View File
@@ -187,16 +187,6 @@ pub enum ClientCoreError {
source: Ed25519RecoveryError,
},
#[error("the account owner of gateway {gateway_id} ({raw_owner}) is malformed: {err}")]
MalformedGatewayOwnerAccountAddress {
gateway_id: String,
raw_owner: String,
// just use the string formatting as opposed to underlying type to avoid having to import cosmrs
err: String,
},
#[error(
"the listening address of gateway {gateway_id} ({raw_listener}) is malformed: {source}"
)]
+14 -14
View File
@@ -53,7 +53,7 @@ pub trait ConnectableGateway {
fn is_wss(&self) -> bool;
}
impl ConnectableGateway for gateway::Node {
impl ConnectableGateway for gateway::LegacyNode {
fn identity(&self) -> &identity::PublicKey {
self.identity()
}
@@ -82,7 +82,7 @@ pub async fn current_gateways<R: Rng>(
rng: &mut R,
nym_apis: &[Url],
user_agent: Option<UserAgent>,
) -> Result<Vec<gateway::Node>, ClientCoreError> {
) -> Result<Vec<gateway::LegacyNode>, ClientCoreError> {
let nym_api = nym_apis
.choose(rng)
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
@@ -94,14 +94,14 @@ pub async fn current_gateways<R: Rng>(
log::debug!("Fetching list of gateways from: {nym_api}");
let gateways = client.get_cached_described_gateways().await?;
let gateways = client.get_basic_gateways(None).await?;
log::debug!("Found {} gateways", gateways.len());
log::trace!("Gateways: {:#?}", gateways);
let valid_gateways = gateways
.into_iter()
.iter()
.filter_map(|gateway| gateway.try_into().ok())
.collect::<Vec<gateway::Node>>();
.collect::<Vec<gateway::LegacyNode>>();
log::debug!("Ater checking validity: {}", valid_gateways.len());
log::trace!("Valid gateways: {:#?}", valid_gateways);
@@ -118,7 +118,7 @@ pub async fn current_gateways<R: Rng>(
pub async fn current_mixnodes<R: Rng>(
rng: &mut R,
nym_apis: &[Url],
) -> Result<Vec<mix::Node>, ClientCoreError> {
) -> Result<Vec<mix::LegacyNode>, ClientCoreError> {
let nym_api = nym_apis
.choose(rng)
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
@@ -126,11 +126,11 @@ pub async fn current_mixnodes<R: Rng>(
log::trace!("Fetching list of mixnodes from: {nym_api}");
let mixnodes = client.get_cached_mixnodes().await?;
let mixnodes = client.get_basic_mixnodes(None).await?;
let valid_mixnodes = mixnodes
.into_iter()
.filter_map(|mixnode| (&mixnode.bond_information).try_into().ok())
.collect::<Vec<mix::Node>>();
.iter()
.filter_map(|mixnode| mixnode.try_into().ok())
.collect::<Vec<mix::LegacyNode>>();
// we were always filtering by version so I'm not removing that 'feature'
let filtered_mixnodes = valid_mixnodes.filter_by_version(env!("CARGO_PKG_VERSION"));
@@ -273,9 +273,9 @@ fn filter_by_tls<G: ConnectableGateway>(
pub(super) fn uniformly_random_gateway<R: Rng>(
rng: &mut R,
gateways: &[gateway::Node],
gateways: &[gateway::LegacyNode],
must_use_tls: bool,
) -> Result<gateway::Node, ClientCoreError> {
) -> Result<gateway::LegacyNode, ClientCoreError> {
filter_by_tls(gateways, must_use_tls)?
.choose(rng)
.ok_or(ClientCoreError::NoGatewaysOnNetwork)
@@ -284,9 +284,9 @@ pub(super) fn uniformly_random_gateway<R: Rng>(
pub(super) fn get_specified_gateway(
gateway_identity: IdentityKeyRef,
gateways: &[gateway::Node],
gateways: &[gateway::LegacyNode],
must_use_tls: bool,
) -> Result<gateway::Node, ClientCoreError> {
) -> Result<gateway::LegacyNode, ClientCoreError> {
log::debug!("Requesting specified gateway: {}", gateway_identity);
let user_gateway = identity::PublicKey::from_base58_string(gateway_identity)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
+1 -1
View File
@@ -50,7 +50,7 @@ async fn setup_new_gateway<K, D>(
key_store: &K,
details_store: &D,
selection_specification: GatewaySelectionSpecification,
available_gateways: Vec<gateway::Node>,
available_gateways: Vec<gateway::LegacyNode>,
) -> Result<InitialisationResult, ClientCoreError>
where
K: KeyStore,
+3 -18
View File
@@ -18,7 +18,6 @@ use nym_validator_client::client::IdentityKey;
use nym_validator_client::nyxd::AccountId;
use serde::Serialize;
use std::fmt::Display;
use std::str::FromStr;
use std::sync::Arc;
use time::OffsetDateTime;
use url::Url;
@@ -39,7 +38,7 @@ pub enum SelectedGateway {
impl SelectedGateway {
pub fn from_topology_node(
node: gateway::Node,
node: gateway::LegacyNode,
must_use_tls: bool,
) -> Result<Self, ClientCoreError> {
let gateway_listener = if must_use_tls {
@@ -51,20 +50,6 @@ impl SelectedGateway {
node.clients_address()
};
let gateway_owner_address = node
.owner
.as_ref()
.map(|raw_owner| {
AccountId::from_str(raw_owner).map_err(|source| {
ClientCoreError::MalformedGatewayOwnerAccountAddress {
gateway_id: node.identity_key.to_base58_string(),
raw_owner: raw_owner.clone(),
err: source.to_string(),
}
})
})
.transpose()?;
let gateway_listener =
Url::parse(&gateway_listener).map_err(|source| ClientCoreError::MalformedListener {
gateway_id: node.identity_key.to_base58_string(),
@@ -74,7 +59,7 @@ impl SelectedGateway {
Ok(SelectedGateway::Remote {
gateway_id: node.identity_key,
gateway_owner_address,
gateway_owner_address: None,
gateway_listener,
})
}
@@ -215,7 +200,7 @@ pub enum GatewaySetup {
specification: GatewaySelectionSpecification,
// TODO: seems to be a bit inefficient to pass them by value
available_gateways: Vec<gateway::Node>,
available_gateways: Vec<gateway::LegacyNode>,
},
ReuseConnection {
@@ -17,11 +17,12 @@ use nym_api_requests::ecash::{
BlindSignRequestBody, BlindedSignatureResponse, PartialCoinIndicesSignatureResponse,
PartialExpirationDateSignatureResponse, VerificationKeyResponse,
};
use nym_api_requests::models::{DescribedGateway, MixNodeBondAnnotated};
use nym_api_requests::legacy::LegacyGatewayBondWithId;
use nym_api_requests::models::{
GatewayCoreStatusResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
RewardEstimationResponse, StakeSaturationResponse,
};
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
use nym_api_requests::nym_nodes::SkimmedNode;
use nym_coconut_dkg_common::types::EpochId;
use nym_http_api_client::UserAgent;
@@ -31,7 +32,7 @@ use url::Url;
pub use crate::nym_api::NymApiClientExt;
pub use nym_mixnet_contract_common::{
mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, MixId,
mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, NodeId,
};
// re-export the type to not break existing imports
@@ -239,7 +240,9 @@ impl<C, S> Client<C, S> {
Ok(self.nym_api.get_active_mixnodes_detailed().await?)
}
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
pub async fn get_cached_gateways(
&self,
) -> Result<Vec<LegacyGatewayBondWithId>, ValidatorClientError> {
Ok(self.nym_api.get_gateways().await?)
}
@@ -321,13 +324,15 @@ impl NymApiClient {
Ok(self.nym_api.get_mixnodes().await?)
}
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
pub async fn get_cached_gateways(
&self,
) -> Result<Vec<LegacyGatewayBondWithId>, ValidatorClientError> {
Ok(self.nym_api.get_gateways().await?)
}
pub async fn get_cached_described_gateways(
&self,
) -> Result<Vec<DescribedGateway>, ValidatorClientError> {
) -> Result<Vec<LegacyDescribedGateway>, ValidatorClientError> {
Ok(self.nym_api.get_gateways_described().await?)
}
@@ -344,7 +349,7 @@ impl NymApiClient {
pub async fn get_mixnode_core_status_count(
&self,
mix_id: MixId,
mix_id: NodeId,
since: Option<i64>,
) -> Result<MixnodeCoreStatusResponse, ValidatorClientError> {
Ok(self
@@ -355,21 +360,21 @@ impl NymApiClient {
pub async fn get_mixnode_status(
&self,
mix_id: MixId,
mix_id: NodeId,
) -> Result<MixnodeStatusResponse, ValidatorClientError> {
Ok(self.nym_api.get_mixnode_status(mix_id).await?)
}
pub async fn get_mixnode_reward_estimation(
&self,
mix_id: MixId,
mix_id: NodeId,
) -> Result<RewardEstimationResponse, ValidatorClientError> {
Ok(self.nym_api.get_mixnode_reward_estimation(mix_id).await?)
}
pub async fn get_mixnode_stake_saturation(
&self,
mix_id: MixId,
mix_id: NodeId,
) -> Result<StakeSaturationResponse, ValidatorClientError> {
Ok(self.nym_api.get_mixnode_stake_saturation(mix_id).await?)
}
@@ -10,8 +10,8 @@ use nym_api_requests::ecash::models::{
VerifyEcashTicketBody,
};
use nym_api_requests::ecash::VerificationKeyResponse;
use nym_api_requests::models::DescribedMixNode;
use nym_api_requests::nym_nodes::{CachedNodesResponse, SkimmedNode};
use nym_api_requests::legacy::LegacyGatewayBondWithId;
use nym_api_requests::models::LegacyDescribedMixNode;
pub use nym_api_requests::{
ecash::{
models::{
@@ -23,19 +23,20 @@ pub use nym_api_requests::{
VerifyEcashCredentialBody,
},
models::{
ComputeRewardEstParam, DescribedGateway, GatewayBondAnnotated, GatewayCoreStatusResponse,
ComputeRewardEstParam, GatewayBondAnnotated, GatewayCoreStatusResponse,
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, InclusionProbabilityResponse,
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
StakeSaturationResponse, UptimeResponse,
LegacyDescribedGateway, MixNodeBondAnnotated, MixnodeCoreStatusResponse,
MixnodeStatusReportResponse, MixnodeStatusResponse, MixnodeUptimeHistoryResponse,
RewardEstimationResponse, StakeSaturationResponse, UptimeResponse,
},
nym_nodes::{CachedNodesResponse, SkimmedNode},
};
pub use nym_coconut_dkg_common::types::EpochId;
use nym_contracts_common::IdentityKey;
pub use nym_http_api_client::Client;
use nym_http_api_client::{ApiClient, NO_PARAMS};
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
use nym_mixnet_contract_common::{IdentityKeyRef, NodeId};
use time::format_description::BorrowedFormatItem;
use time::Date;
@@ -95,12 +96,12 @@ pub trait NymApiClientExt: ApiClient {
.await
}
async fn get_gateways(&self) -> Result<Vec<GatewayBond>, NymAPIError> {
async fn get_gateways(&self) -> Result<Vec<LegacyGatewayBondWithId>, NymAPIError> {
self.get_json(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS)
.await
}
async fn get_gateways_described(&self) -> Result<Vec<DescribedGateway>, NymAPIError> {
async fn get_gateways_described(&self) -> Result<Vec<LegacyDescribedGateway>, NymAPIError> {
self.get_json(
&[routes::API_VERSION, routes::GATEWAYS, routes::DESCRIBED],
NO_PARAMS,
@@ -108,7 +109,7 @@ pub trait NymApiClientExt: ApiClient {
.await
}
async fn get_mixnodes_described(&self) -> Result<Vec<DescribedMixNode>, NymAPIError> {
async fn get_mixnodes_described(&self) -> Result<Vec<LegacyDescribedMixNode>, NymAPIError> {
self.get_json(
&[routes::API_VERSION, routes::MIXNODES, routes::DESCRIBED],
NO_PARAMS,
@@ -194,7 +195,7 @@ pub trait NymApiClientExt: ApiClient {
async fn get_mixnode_report(
&self,
mix_id: MixId,
mix_id: NodeId,
) -> Result<MixnodeStatusReportResponse, NymAPIError> {
self.get_json(
&[
@@ -228,7 +229,7 @@ pub trait NymApiClientExt: ApiClient {
async fn get_mixnode_history(
&self,
mix_id: MixId,
mix_id: NodeId,
) -> Result<MixnodeUptimeHistoryResponse, NymAPIError> {
self.get_json(
&[
@@ -309,7 +310,7 @@ pub trait NymApiClientExt: ApiClient {
async fn get_mixnode_core_status_count(
&self,
mix_id: MixId,
mix_id: NodeId,
since: Option<i64>,
) -> Result<MixnodeCoreStatusResponse, NymAPIError> {
if let Some(since) = since {
@@ -341,7 +342,7 @@ pub trait NymApiClientExt: ApiClient {
async fn get_mixnode_status(
&self,
mix_id: MixId,
mix_id: NodeId,
) -> Result<MixnodeStatusResponse, NymAPIError> {
self.get_json(
&[
@@ -358,7 +359,7 @@ pub trait NymApiClientExt: ApiClient {
async fn get_mixnode_reward_estimation(
&self,
mix_id: MixId,
mix_id: NodeId,
) -> Result<RewardEstimationResponse, NymAPIError> {
self.get_json(
&[
@@ -375,7 +376,7 @@ pub trait NymApiClientExt: ApiClient {
async fn compute_mixnode_reward_estimation(
&self,
mix_id: MixId,
mix_id: NodeId,
request_body: &ComputeRewardEstParam,
) -> Result<RewardEstimationResponse, NymAPIError> {
self.post_json(
@@ -394,7 +395,7 @@ pub trait NymApiClientExt: ApiClient {
async fn get_mixnode_stake_saturation(
&self,
mix_id: MixId,
mix_id: NodeId,
) -> Result<StakeSaturationResponse, NymAPIError> {
self.get_json(
&[
@@ -411,7 +412,7 @@ pub trait NymApiClientExt: ApiClient {
async fn get_mixnode_inclusion_probability(
&self,
mix_id: MixId,
mix_id: NodeId,
) -> Result<InclusionProbabilityResponse, NymAPIError> {
self.get_json(
&[
@@ -426,7 +427,7 @@ pub trait NymApiClientExt: ApiClient {
.await
}
async fn get_mixnode_avg_uptime(&self, mix_id: MixId) -> Result<UptimeResponse, NymAPIError> {
async fn get_mixnode_avg_uptime(&self, mix_id: NodeId) -> Result<UptimeResponse, NymAPIError> {
self.get_json(
&[
routes::API_VERSION,
@@ -440,7 +441,7 @@ pub trait NymApiClientExt: ApiClient {
.await
}
async fn get_mixnodes_blacklisted(&self) -> Result<Vec<MixId>, NymAPIError> {
async fn get_mixnodes_blacklisted(&self) -> Result<Vec<NodeId>, NymAPIError> {
self.get_json(
&[routes::API_VERSION, routes::MIXNODES, routes::BLACKLISTED],
NO_PARAMS,
@@ -7,29 +7,35 @@ use crate::nyxd::error::NyxdError;
use crate::nyxd::CosmWasmClient;
use async_trait::async_trait;
use cosmrs::AccountId;
use nym_api_requests::models::StakeSaturationResponse;
use nym_contracts_common::signing::Nonce;
use nym_mixnet_contract_common::gateway::{PreassignedGatewayIdsResponse, PreassignedId};
use nym_mixnet_contract_common::nym_node::{
EpochAssignmentResponse, NodeDetailsByIdentityResponse, NodeOwnershipResponse,
NodeRewardingDetailsResponse, PagedNymNodeBondsResponse, PagedNymNodeDetailsResponse,
PagedUnbondedNymNodesResponse, Role, RolesMetadataResponse, UnbondedNodeResponse,
UnbondedNymNode,
};
use nym_mixnet_contract_common::reward_params::WorkFactor;
use nym_mixnet_contract_common::{
delegation,
delegation::{MixNodeDelegationResponse, OwnerProxySubKey},
families::{Family, FamilyHead},
delegation::{NodeDelegationResponse, OwnerProxySubKey},
mixnode::{
MixnodeRewardingDetailsResponse, PagedMixnodesDetailsResponse,
PagedUnbondedMixnodesResponse, StakeSaturationResponse, UnbondedMixnodeResponse,
MixStakeSaturationResponse, MixnodeRewardingDetailsResponse, PagedMixnodesDetailsResponse,
PagedUnbondedMixnodesResponse, UnbondedMixnodeResponse,
},
reward_params::{Performance, RewardingParams},
rewarding::{EstimatedCurrentEpochRewardResponse, PendingRewardResponse},
ContractBuildInformation, ContractState, ContractStateParams, CurrentIntervalResponse,
Delegation, EpochEventId, EpochStatus, FamilyByHeadResponse, FamilyByLabelResponse,
FamilyMembersByHeadResponse, FamilyMembersByLabelResponse, GatewayBond, GatewayBondResponse,
GatewayOwnershipResponse, IdentityKey, IdentityKeyRef, IntervalEventId, LayerDistribution,
MixId, MixNodeBond, MixNodeDetails, MixOwnershipResponse, MixnodeDetailsByIdentityResponse,
MixnodeDetailsResponse, NumberOfPendingEventsResponse, PagedAllDelegationsResponse,
PagedDelegatorDelegationsResponse, PagedFamiliesResponse, PagedGatewayResponse,
PagedMembersResponse, PagedMixNodeDelegationsResponse, PagedMixnodeBondsResponse,
PagedRewardedSetResponse, PendingEpochEvent, PendingEpochEventResponse,
PendingEpochEventsResponse, PendingIntervalEvent, PendingIntervalEventResponse,
PendingIntervalEventsResponse, QueryMsg as MixnetQueryMsg, RewardedSetNodeStatus,
UnbondedMixnode,
Delegation, EpochEventId, EpochStatus, GatewayBond, GatewayBondResponse,
GatewayOwnershipResponse, IdentityKey, IdentityKeyRef, IntervalEventId, MixNodeBond,
MixNodeDetails, MixOwnershipResponse, MixnodeDetailsByIdentityResponse, MixnodeDetailsResponse,
NodeId, NumberOfPendingEventsResponse, NymNodeBond, NymNodeDetails,
PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedGatewayResponse,
PagedMixnodeBondsResponse, PagedNodeDelegationsResponse, PendingEpochEvent,
PendingEpochEventResponse, PendingEpochEventsResponse, PendingIntervalEvent,
PendingIntervalEventResponse, PendingIntervalEventsResponse, QueryMsg as MixnetQueryMsg,
RewardedSet, UnbondedMixnode,
};
use serde::Deserialize;
@@ -91,56 +97,11 @@ pub trait MixnetQueryClient {
.await
}
async fn get_rewarded_set_paged(
&self,
start_after: Option<MixId>,
limit: Option<u32>,
) -> Result<PagedRewardedSetResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetRewardedSet { limit, start_after })
.await
}
async fn get_all_node_families_paged(
&self,
start_after: Option<String>,
limit: Option<u32>,
) -> Result<PagedFamiliesResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetAllFamiliesPaged { limit, start_after })
.await
}
async fn get_all_family_members_paged(
&self,
start_after: Option<String>,
limit: Option<u32>,
) -> Result<PagedMembersResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetAllMembersPaged { limit, start_after })
.await
}
async fn get_family_members_by_head<S: Into<String> + Send>(
&self,
head: S,
) -> Result<FamilyMembersByHeadResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetFamilyMembersByHead { head: head.into() })
.await
}
async fn get_family_members_by_label<S: Into<String> + Send>(
&self,
label: S,
) -> Result<FamilyMembersByLabelResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetFamilyMembersByLabel {
label: label.into(),
})
.await
}
// mixnode-related:
async fn get_mixnode_bonds_paged(
&self,
start_after: Option<MixId>,
start_after: Option<NodeId>,
limit: Option<u32>,
) -> Result<PagedMixnodeBondsResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixNodeBonds { limit, start_after })
@@ -149,26 +110,26 @@ pub trait MixnetQueryClient {
async fn get_mixnodes_detailed_paged(
&self,
start_after: Option<MixId>,
start_after: Option<NodeId>,
limit: Option<u32>,
) -> Result<PagedMixnodesDetailsResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixNodesDetailed { limit, start_after })
.await
}
async fn get_unbonded_paged(
async fn get_unbonded_mixnodes_paged(
&self,
start_after: Option<MixId>,
start_after: Option<NodeId>,
limit: Option<u32>,
) -> Result<PagedUnbondedMixnodesResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodes { limit, start_after })
.await
}
async fn get_unbonded_by_owner_paged(
async fn get_unbonded_mixnodes_by_owner_paged(
&self,
owner: &AccountId,
start_after: Option<MixId>,
start_after: Option<NodeId>,
limit: Option<u32>,
) -> Result<PagedUnbondedMixnodesResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodesByOwner {
@@ -179,10 +140,10 @@ pub trait MixnetQueryClient {
.await
}
async fn get_unbonded_by_identity_paged(
async fn get_unbonded_mixnodes_by_identity_paged(
&self,
identity_key: IdentityKeyRef<'_>,
start_after: Option<MixId>,
start_after: Option<NodeId>,
limit: Option<u32>,
) -> Result<PagedUnbondedMixnodesResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodesByIdentityKey {
@@ -205,7 +166,7 @@ pub trait MixnetQueryClient {
async fn get_mixnode_details(
&self,
mix_id: MixId,
mix_id: NodeId,
) -> Result<MixnodeDetailsResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixnodeDetails { mix_id })
.await
@@ -223,7 +184,7 @@ pub trait MixnetQueryClient {
async fn get_mixnode_rewarding_details(
&self,
mix_id: MixId,
mix_id: NodeId,
) -> Result<MixnodeRewardingDetailsResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixnodeRewardingDetails { mix_id })
.await
@@ -231,24 +192,24 @@ pub trait MixnetQueryClient {
async fn get_mixnode_stake_saturation(
&self,
mix_id: MixId,
) -> Result<StakeSaturationResponse, NyxdError> {
mix_id: NodeId,
) -> Result<MixStakeSaturationResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetStakeSaturation { mix_id })
.await
}
async fn get_unbonded_mixnode_information(
&self,
mix_id: MixId,
mix_id: NodeId,
) -> Result<UnbondedMixnodeResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedMixNodeInformation { mix_id })
.await
}
async fn get_layer_distribution(&self) -> Result<LayerDistribution, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetLayerDistribution {})
.await
}
// async fn get_layer_distribution(&self) -> Result<LayerDistribution, NyxdError> {
// self.query_mixnet_contract(MixnetQueryMsg::GetRoleDistribution {})
// .await
// }
// gateway-related:
@@ -281,17 +242,142 @@ pub trait MixnetQueryClient {
.await
}
async fn get_preassigned_gateway_ids_paged(
&self,
start_after: Option<IdentityKey>,
limit: Option<u32>,
) -> Result<PreassignedGatewayIdsResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPreassignedGatewayIds { start_after, limit })
.await
}
// nym-nodes related:
async fn get_nymnode_bonds_paged(
&self,
start_after: Option<NodeId>,
limit: Option<u32>,
) -> Result<PagedNymNodeBondsResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetNymNodeBondsPaged { limit, start_after })
.await
}
async fn get_nymnodes_detailed_paged(
&self,
start_after: Option<NodeId>,
limit: Option<u32>,
) -> Result<PagedNymNodeDetailsResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetNymNodesDetailedPaged { limit, start_after })
.await
}
async fn get_unbonded_nymnodes_paged(
&self,
start_after: Option<NodeId>,
limit: Option<u32>,
) -> Result<PagedUnbondedNymNodesResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedNymNodesPaged { limit, start_after })
.await
}
async fn get_unbonded_nymnodes_by_owner_paged(
&self,
owner: &AccountId,
start_after: Option<NodeId>,
limit: Option<u32>,
) -> Result<PagedUnbondedNymNodesResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedNymNodesByOwnerPaged {
owner: owner.to_string(),
limit,
start_after,
})
.await
}
async fn get_unbonded_nymnodes_by_identity_paged(
&self,
identity_key: IdentityKeyRef<'_>,
start_after: Option<NodeId>,
limit: Option<u32>,
) -> Result<PagedUnbondedNymNodesResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedNymNodesByIdentityKeyPaged {
identity_key: identity_key.to_string(),
limit,
start_after,
})
.await
}
async fn get_owned_nymnode(
&self,
address: &AccountId,
) -> Result<NodeOwnershipResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetOwnedNymNode {
address: address.to_string(),
})
.await
}
async fn get_nymnode_details(
&self,
node_id: NodeId,
) -> Result<NodeOwnershipResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetNymNodeDetails { node_id })
.await
}
async fn get_nymnode_details_by_identity(
&self,
node_identity: IdentityKey,
) -> Result<NodeDetailsByIdentityResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetNymNodeDetailsByIdentityKey { node_identity })
.await
}
async fn get_nymnode_rewarding_details(
&self,
node_id: NodeId,
) -> Result<NodeRewardingDetailsResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetNodeRewardingDetails { node_id })
.await
}
async fn get_node_stake_saturation(
&self,
node_id: NodeId,
) -> Result<StakeSaturationResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetNodeStakeSaturation { node_id })
.await
}
async fn get_unbonded_nymnode_information(
&self,
node_id: NodeId,
) -> Result<UnbondedNodeResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetUnbondedNymNode { node_id })
.await
}
async fn get_role_assignment(&self, role: Role) -> Result<EpochAssignmentResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetRoleAssignment { role })
.await
}
async fn get_rewarded_set_metadata(&self) -> Result<RolesMetadataResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetRewardedSetMetadata {})
.await
}
// delegation-related:
/// Gets list of all delegations towards particular mixnode on particular page.
async fn get_mixnode_delegations_paged(
&self,
mix_id: MixId,
node_id: NodeId,
start_after: Option<String>,
limit: Option<u32>,
) -> Result<PagedMixNodeDelegationsResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetMixnodeDelegations {
mix_id,
) -> Result<PagedNodeDelegationsResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetNodeDelegations {
node_id,
start_after,
limit,
})
@@ -302,7 +388,7 @@ pub trait MixnetQueryClient {
async fn get_delegator_delegations_paged(
&self,
delegator: &AccountId,
start_after: Option<(MixId, OwnerProxySubKey)>,
start_after: Option<(NodeId, OwnerProxySubKey)>,
limit: Option<u32>,
) -> Result<PagedDelegatorDelegationsResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetDelegatorDelegations {
@@ -316,12 +402,12 @@ pub trait MixnetQueryClient {
/// Checks value of delegation of given client towards particular mixnode.
async fn get_delegation_details(
&self,
mix_id: MixId,
node_id: NodeId,
delegator: &AccountId,
proxy: Option<String>,
) -> Result<MixNodeDelegationResponse, NyxdError> {
) -> Result<NodeDelegationResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetDelegationDetails {
mix_id,
node_id,
delegator: delegator.to_string(),
proxy,
})
@@ -351,21 +437,21 @@ pub trait MixnetQueryClient {
async fn get_pending_mixnode_operator_reward(
&self,
mix_id: MixId,
node_id: NodeId,
) -> Result<PendingRewardResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingMixNodeOperatorReward { mix_id })
self.query_mixnet_contract(MixnetQueryMsg::GetPendingNodeOperatorReward { node_id })
.await
}
async fn get_pending_delegator_reward(
&self,
delegator: &AccountId,
mix_id: MixId,
node_id: NodeId,
proxy: Option<String>,
) -> Result<PendingRewardResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetPendingDelegatorReward {
address: delegator.to_string(),
mix_id,
node_id,
proxy,
})
.await
@@ -374,12 +460,14 @@ pub trait MixnetQueryClient {
// given the provided performance, estimate the reward at the end of the current epoch
async fn get_estimated_current_epoch_operator_reward(
&self,
mix_id: MixId,
node_id: NodeId,
estimated_performance: Performance,
estimated_work: Option<WorkFactor>,
) -> Result<EstimatedCurrentEpochRewardResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetEstimatedCurrentEpochOperatorReward {
mix_id,
node_id,
estimated_performance,
estimated_work,
})
.await
}
@@ -388,15 +476,15 @@ pub trait MixnetQueryClient {
async fn get_estimated_current_epoch_delegator_reward(
&self,
delegator: &AccountId,
mix_id: MixId,
proxy: Option<String>,
node_id: NodeId,
estimated_performance: Performance,
estimated_work: Option<WorkFactor>,
) -> Result<EstimatedCurrentEpochRewardResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetEstimatedCurrentEpochDelegatorReward {
address: delegator.to_string(),
mix_id,
proxy,
node_id,
estimated_performance,
estimated_work,
})
.await
}
@@ -450,22 +538,6 @@ pub trait MixnetQueryClient {
})
.await
}
async fn get_node_family_by_label(
&self,
label: String,
) -> Result<FamilyByLabelResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetFamilyByLabel { label })
.await
}
async fn get_node_family_by_head(
&self,
head: String,
) -> Result<FamilyByHeadResponse, NyxdError> {
self.query_mixnet_contract(MixnetQueryMsg::GetFamilyByHead { head })
.await
}
}
// extension trait to the query client to deal with the paged queries
@@ -473,18 +545,35 @@ pub trait MixnetQueryClient {
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait PagedMixnetQueryClient: MixnetQueryClient {
async fn get_all_node_families(&self) -> Result<Vec<Family>, NyxdError> {
collect_paged!(self, get_all_node_families_paged, families)
async fn get_all_nymnode_bonds(&self) -> Result<Vec<NymNodeBond>, NyxdError> {
collect_paged!(self, get_nymnode_bonds_paged, nodes)
}
async fn get_all_family_members(&self) -> Result<Vec<(IdentityKey, FamilyHead)>, NyxdError> {
collect_paged!(self, get_all_family_members_paged, members)
async fn get_all_nymnodes_detailed(&self) -> Result<Vec<NymNodeDetails>, NyxdError> {
collect_paged!(self, get_nymnodes_detailed_paged, nodes)
}
async fn get_all_rewarded_set_mixnodes(
async fn get_all_unbonded_nymnodes(&self) -> Result<Vec<UnbondedNymNode>, NyxdError> {
collect_paged!(self, get_unbonded_nymnodes_paged, nodes)
}
async fn get_all_unbonded_nymnodes_by_owner(
&self,
) -> Result<Vec<(MixId, RewardedSetNodeStatus)>, NyxdError> {
collect_paged!(self, get_rewarded_set_paged, nodes)
owner: &AccountId,
) -> Result<Vec<UnbondedNymNode>, NyxdError> {
collect_paged!(self, get_unbonded_nymnodes_by_owner_paged, nodes, owner)
}
async fn get_all_unbonded_nymnodes_by_identity(
&self,
identity_key: IdentityKeyRef<'_>,
) -> Result<Vec<UnbondedNymNode>, NyxdError> {
collect_paged!(
self,
get_unbonded_nymnodes_by_identity_paged,
nodes,
identity_key
)
}
async fn get_all_mixnode_bonds(&self) -> Result<Vec<MixNodeBond>, NyxdError> {
@@ -495,31 +584,40 @@ pub trait PagedMixnetQueryClient: MixnetQueryClient {
collect_paged!(self, get_mixnodes_detailed_paged, nodes)
}
async fn get_all_unbonded_mixnodes(&self) -> Result<Vec<(MixId, UnbondedMixnode)>, NyxdError> {
collect_paged!(self, get_unbonded_paged, nodes)
async fn get_all_unbonded_mixnodes(&self) -> Result<Vec<(NodeId, UnbondedMixnode)>, NyxdError> {
collect_paged!(self, get_unbonded_mixnodes_paged, nodes)
}
async fn get_all_unbonded_mixnodes_by_owner(
&self,
owner: &AccountId,
) -> Result<Vec<(MixId, UnbondedMixnode)>, NyxdError> {
collect_paged!(self, get_unbonded_by_owner_paged, nodes, owner)
) -> Result<Vec<(NodeId, UnbondedMixnode)>, NyxdError> {
collect_paged!(self, get_unbonded_mixnodes_by_owner_paged, nodes, owner)
}
async fn get_all_unbonded_mixnodes_by_identity(
&self,
identity_key: IdentityKeyRef<'_>,
) -> Result<Vec<(MixId, UnbondedMixnode)>, NyxdError> {
collect_paged!(self, get_unbonded_by_identity_paged, nodes, identity_key)
) -> Result<Vec<(NodeId, UnbondedMixnode)>, NyxdError> {
collect_paged!(
self,
get_unbonded_mixnodes_by_identity_paged,
nodes,
identity_key
)
}
async fn get_all_gateways(&self) -> Result<Vec<GatewayBond>, NyxdError> {
collect_paged!(self, get_gateways_paged, nodes)
}
async fn get_all_preassigned_gateway_ids(&self) -> Result<Vec<PreassignedId>, NyxdError> {
collect_paged!(self, get_preassigned_gateway_ids_paged, ids)
}
async fn get_all_single_mixnode_delegations(
&self,
mix_id: MixId,
mix_id: NodeId,
) -> Result<Vec<Delegation>, NyxdError> {
collect_paged!(self, get_mixnode_delegations_paged, delegations, mix_id)
}
@@ -554,6 +652,65 @@ pub trait PagedMixnetQueryClient: MixnetQueryClient {
#[async_trait]
impl<T> PagedMixnetQueryClient for T where T: MixnetQueryClient {}
// extension help to provide extra functionalities based on existing queries:
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait MixnetQueryClientExt: MixnetQueryClient {
async fn get_rewarded_set(&self) -> Result<RewardedSet, NyxdError> {
let error_response = |message| Err(NyxdError::extension_query_failure("mixnet", message));
let metadata = self.get_rewarded_set_metadata().await?;
if !metadata.metadata.fully_assigned {
return error_response("the rewarded set hasn't been fully assigned for this epoch");
}
let expected_epoch_id = metadata.metadata.epoch_id;
// if we have to query those things more frequently, we could do it concurrently,
// but as it stands now, it happens so infrequently it might as well be sequential
let entry = self.get_role_assignment(Role::EntryGateway).await?;
if entry.epoch_id != expected_epoch_id {
return error_response("the nodes assigned for 'entry' returned unexpected epoch_id");
}
let exit = self.get_role_assignment(Role::ExitGateway).await?;
if exit.epoch_id != expected_epoch_id {
return error_response("the nodes assigned for 'exit' returned unexpected epoch_id");
}
let layer1 = self.get_role_assignment(Role::Layer1).await?;
if layer1.epoch_id != expected_epoch_id {
return error_response("the nodes assigned for 'layer1' returned unexpected epoch_id");
}
let layer2 = self.get_role_assignment(Role::Layer2).await?;
if layer2.epoch_id != expected_epoch_id {
return error_response("the nodes assigned for 'layer2' returned unexpected epoch_id");
}
let layer3 = self.get_role_assignment(Role::Layer3).await?;
if layer3.epoch_id != expected_epoch_id {
return error_response("the nodes assigned for 'layer3' returned unexpected epoch_id");
}
let standby = self.get_role_assignment(Role::Standby).await?;
if standby.epoch_id != expected_epoch_id {
return error_response("the nodes assigned for 'standby' returned unexpected epoch_id");
}
Ok(RewardedSet {
entry_gateways: entry.nodes,
exit_gateways: exit.nodes,
layer1: layer1.nodes,
layer2: layer2.nodes,
layer3: layer3.nodes,
standby: standby.nodes,
})
}
}
#[async_trait]
impl<T> MixnetQueryClientExt for T where T: MixnetQueryClient {}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<C> MixnetQueryClient for C
@@ -576,6 +733,7 @@ where
mod tests {
use super::*;
use crate::nyxd::contract_traits::tests::IgnoreValue;
use nym_mixnet_contract_common::QueryMsg;
// it's enough that this compiles and clippy is happy about it
#[allow(dead_code)]
@@ -585,24 +743,6 @@ mod tests {
) -> u32 {
match msg {
MixnetQueryMsg::Admin {} => client.admin().ignore(),
MixnetQueryMsg::GetAllFamiliesPaged { limit, start_after } => client
.get_all_family_members_paged(start_after, limit)
.ignore(),
MixnetQueryMsg::GetAllMembersPaged { limit, start_after } => client
.get_all_family_members_paged(start_after, limit)
.ignore(),
MixnetQueryMsg::GetFamilyByHead { head } => {
client.get_node_family_by_head(head).ignore()
}
MixnetQueryMsg::GetFamilyByLabel { label } => {
client.get_node_family_by_label(label).ignore()
}
MixnetQueryMsg::GetFamilyMembersByHead { head } => {
client.get_family_members_by_head(head).ignore()
}
MixnetQueryMsg::GetFamilyMembersByLabel { label } => {
client.get_family_members_by_label(label).ignore()
}
MixnetQueryMsg::GetContractVersion {} => client.get_mixnet_contract_version().ignore(),
MixnetQueryMsg::GetCW2ContractVersion {} => {
client.get_mixnet_contract_cw2_version().ignore()
@@ -617,31 +757,28 @@ mod tests {
MixnetQueryMsg::GetCurrentIntervalDetails {} => {
client.get_current_interval_details().ignore()
}
MixnetQueryMsg::GetRewardedSet { limit, start_after } => {
client.get_rewarded_set_paged(start_after, limit).ignore()
}
MixnetQueryMsg::GetMixNodeBonds { limit, start_after } => {
client.get_mixnode_bonds_paged(start_after, limit).ignore()
}
MixnetQueryMsg::GetMixNodesDetailed { limit, start_after } => client
.get_mixnodes_detailed_paged(start_after, limit)
.ignore(),
MixnetQueryMsg::GetUnbondedMixNodes { limit, start_after } => {
client.get_unbonded_paged(start_after, limit).ignore()
}
MixnetQueryMsg::GetUnbondedMixNodes { limit, start_after } => client
.get_unbonded_mixnodes_paged(start_after, limit)
.ignore(),
MixnetQueryMsg::GetUnbondedMixNodesByOwner {
owner,
limit,
start_after,
} => client
.get_unbonded_by_owner_paged(&owner.parse().unwrap(), start_after, limit)
.get_unbonded_mixnodes_by_owner_paged(&owner.parse().unwrap(), start_after, limit)
.ignore(),
MixnetQueryMsg::GetUnbondedMixNodesByIdentityKey {
identity_key,
limit,
start_after,
} => client
.get_unbonded_by_identity_paged(&identity_key, start_after, limit)
.get_unbonded_mixnodes_by_identity_paged(&identity_key, start_after, limit)
.ignore(),
MixnetQueryMsg::GetOwnedMixnode { address } => {
client.get_owned_mixnode(&address.parse().unwrap()).ignore()
@@ -661,7 +798,6 @@ mod tests {
MixnetQueryMsg::GetBondedMixnodeDetailsByIdentity { mix_identity } => client
.get_mixnode_details_by_identity(mix_identity)
.ignore(),
MixnetQueryMsg::GetLayerDistribution {} => client.get_layer_distribution().ignore(),
MixnetQueryMsg::GetGateways { start_after, limit } => {
client.get_gateways_paged(start_after, limit).ignore()
}
@@ -671,8 +807,8 @@ mod tests {
MixnetQueryMsg::GetOwnedGateway { address } => {
client.get_owned_gateway(&address.parse().unwrap()).ignore()
}
MixnetQueryMsg::GetMixnodeDelegations {
mix_id,
MixnetQueryMsg::GetNodeDelegations {
node_id: mix_id,
start_after,
limit,
} => client
@@ -686,7 +822,7 @@ mod tests {
.get_delegator_delegations_paged(&delegator.parse().unwrap(), start_after, limit)
.ignore(),
MixnetQueryMsg::GetDelegationDetails {
mix_id,
node_id: mix_id,
delegator,
proxy,
} => client
@@ -698,33 +834,38 @@ mod tests {
MixnetQueryMsg::GetPendingOperatorReward { address } => client
.get_pending_operator_reward(&address.parse().unwrap())
.ignore(),
MixnetQueryMsg::GetPendingMixNodeOperatorReward { mix_id } => {
MixnetQueryMsg::GetPendingNodeOperatorReward { node_id: mix_id } => {
client.get_pending_mixnode_operator_reward(mix_id).ignore()
}
MixnetQueryMsg::GetPendingDelegatorReward {
address,
mix_id,
node_id: mix_id,
proxy,
} => client
.get_pending_delegator_reward(&address.parse().unwrap(), mix_id, proxy)
.ignore(),
MixnetQueryMsg::GetEstimatedCurrentEpochOperatorReward {
mix_id,
node_id,
estimated_performance,
estimated_work,
} => client
.get_estimated_current_epoch_operator_reward(mix_id, estimated_performance)
.get_estimated_current_epoch_operator_reward(
node_id,
estimated_performance,
estimated_work,
)
.ignore(),
MixnetQueryMsg::GetEstimatedCurrentEpochDelegatorReward {
address,
mix_id,
proxy,
node_id,
estimated_performance,
estimated_work,
} => client
.get_estimated_current_epoch_delegator_reward(
&address.parse().unwrap(),
mix_id,
proxy,
node_id,
estimated_performance,
estimated_work,
)
.ignore(),
MixnetQueryMsg::GetPendingEpochEvents { limit, start_after } => client
@@ -745,6 +886,50 @@ mod tests {
MixnetQueryMsg::GetSigningNonce { address } => {
client.get_signing_nonce(&address.parse().unwrap()).ignore()
}
QueryMsg::GetPreassignedGatewayIds { start_after, limit } => client
.get_preassigned_gateway_ids_paged(start_after, limit)
.ignore(),
QueryMsg::GetNymNodeBondsPaged { limit, start_after } => {
client.get_nymnode_bonds_paged(limit, start_after).ignore()
}
QueryMsg::GetNymNodesDetailedPaged { limit, start_after } => client
.get_nymnodes_detailed_paged(limit, start_after)
.ignore(),
QueryMsg::GetUnbondedNymNode { node_id } => {
client.get_unbonded_nymnode_information(node_id).ignore()
}
QueryMsg::GetUnbondedNymNodesPaged { limit, start_after } => client
.get_unbonded_nymnodes_paged(limit, start_after)
.ignore(),
QueryMsg::GetUnbondedNymNodesByOwnerPaged {
owner,
limit,
start_after,
} => client
.get_unbonded_nymnodes_by_owner_paged(&owner.parse().unwrap(), limit, start_after)
.ignore(),
QueryMsg::GetUnbondedNymNodesByIdentityKeyPaged {
identity_key,
limit,
start_after,
} => client
.get_unbonded_nymnodes_by_identity_paged(&identity_key, limit, start_after)
.ignore(),
QueryMsg::GetOwnedNymNode { address } => {
client.get_owned_nymnode(&address.parse().unwrap()).ignore()
}
QueryMsg::GetNymNodeDetails { node_id } => client.get_nymnode_details(node_id).ignore(),
QueryMsg::GetNymNodeDetailsByIdentityKey { node_identity } => client
.get_nymnode_details_by_identity(node_identity)
.ignore(),
QueryMsg::GetNodeRewardingDetails { node_id } => {
client.get_nymnode_rewarding_details(node_id).ignore()
}
QueryMsg::GetNodeStakeSaturation { node_id } => {
client.get_node_stake_saturation(node_id).ignore()
}
QueryMsg::GetRoleAssignment { role } => client.get_role_assignment(role).ignore(),
QueryMsg::GetRewardedSetMetadata {} => client.get_rewarded_set_metadata().ignore(),
}
}
}
@@ -10,13 +10,15 @@ use crate::signing::signer::OfflineSigner;
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};
use nym_mixnet_contract_common::mixnode::{MixNodeConfigUpdate, NodeCostParams};
use nym_mixnet_contract_common::nym_node::NodeConfigUpdate;
use nym_mixnet_contract_common::reward_params::{
ActiveSetUpdate, IntervalRewardingParamsUpdate, NodeRewardingParameters,
};
use nym_mixnet_contract_common::{
ContractStateParams, ExecuteMsg as MixnetExecuteMsg, Gateway, Layer, LayerAssignment, MixId,
MixNode,
ContractStateParams, ExecuteMsg as MixnetExecuteMsg, Gateway, MixNode, NodeId, NymNode,
RoleAssignment,
};
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -70,14 +72,14 @@ pub trait MixnetSigningClient {
async fn update_active_set_size(
&self,
active_set_size: u32,
update: ActiveSetUpdate,
force_immediately: bool,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateActiveSetSize {
active_set_size,
MixnetExecuteMsg::UpdateActiveSetDistribution {
update,
force_immediately,
},
vec![],
@@ -126,37 +128,6 @@ pub trait MixnetSigningClient {
.await
}
async fn advance_current_epoch(
&self,
new_rewarded_set: Vec<LayerAssignment>,
expected_active_set_size: u32,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::AdvanceCurrentEpoch {
new_rewarded_set,
expected_active_set_size,
},
vec![],
)
.await
}
async fn assign_node_layer(
&self,
mix_id: MixId,
layer: Layer,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::AssignNodeLayer { mix_id, layer },
vec![],
)
.await
}
async fn reconcile_epoch_events(
&self,
limit: Option<u32>,
@@ -170,126 +141,21 @@ pub trait MixnetSigningClient {
.await
}
// family related
async fn create_family(
async fn assign_roles(
&self,
label: String,
assignment: RoleAssignment,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::CreateFamily { label }, vec![])
self.execute_mixnet_contract(fee, MixnetExecuteMsg::AssignRoles { assignment }, vec![])
.await
}
async fn create_family_on_behalf(
&self,
owner_address: String,
label: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::CreateFamilyOnBehalf {
owner_address,
label,
},
vec![],
)
.await
}
async fn join_family(
&self,
join_permit: MessageSignature,
family_head: FamilyHead,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::JoinFamily {
join_permit,
family_head,
},
vec![],
)
.await
}
async fn join_family_on_behalf(
&self,
member_address: String,
join_permit: MessageSignature,
family_head: FamilyHead,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::JoinFamilyOnBehalf {
member_address,
join_permit,
family_head,
},
vec![],
)
.await
}
async fn leave_family(
&self,
family_head: FamilyHead,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::LeaveFamily { family_head }, vec![])
.await
}
async fn leave_family_on_behalf(
&self,
member_address: String,
family_head: FamilyHead,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::LeaveFamilyOnBehalf {
member_address,
family_head,
},
vec![],
)
.await
}
async fn kick_family_member(
&self,
member: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::KickFamilyMember { member }, vec![])
.await
}
async fn kick_family_member_on_behalf(
&self,
head_address: String,
member: String,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::KickFamilyMemberOnBehalf {
head_address,
member,
},
vec![],
)
.await
}
// mixnode-related:
async fn bond_mixnode(
&self,
mix_node: MixNode,
cost_params: MixNodeCostParams,
cost_params: NodeCostParams,
owner_signature: MessageSignature,
pledge: Coin,
fee: Option<Fee>,
@@ -310,7 +176,7 @@ pub trait MixnetSigningClient {
&self,
owner: AccountId,
mix_node: MixNode,
cost_params: MixNodeCostParams,
cost_params: NodeCostParams,
owner_signature: MessageSignature,
pledge: Coin,
fee: Option<Fee>,
@@ -409,14 +275,14 @@ pub trait MixnetSigningClient {
.await
}
async fn update_mixnode_cost_params(
async fn update_cost_params(
&self,
new_costs: MixNodeCostParams,
new_costs: NodeCostParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UpdateMixnodeCostParams { new_costs },
MixnetExecuteMsg::UpdateCostParams { new_costs },
vec![],
)
.await
@@ -425,7 +291,7 @@ pub trait MixnetSigningClient {
async fn update_mixnode_cost_params_on_behalf(
&self,
owner: AccountId,
new_costs: MixNodeCostParams,
new_costs: NodeCostParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
@@ -559,26 +425,75 @@ pub trait MixnetSigningClient {
.await
}
// delegation-related:
// nym-node related:
async fn migrate_legacy_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::MigrateMixnode {}, vec![])
.await
}
async fn delegate_to_mixnode(
async fn migrate_legacy_gateway(
&self,
mix_id: MixId,
amount: Coin,
cost_params: Option<NodeCostParams>,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::DelegateToMixnode { mix_id },
vec![amount],
MixnetExecuteMsg::MigrateGateway { cost_params },
vec![],
)
.await
}
async fn bond_nymnode(
&self,
node: NymNode,
cost_params: NodeCostParams,
owner_signature: MessageSignature,
pledge: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::BondNymNode {
node,
cost_params,
owner_signature,
},
vec![pledge],
)
.await
}
async fn unbond_nymnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::UnbondNymNode {}, vec![])
.await
}
async fn update_nymnode_config(
&self,
update: NodeConfigUpdate,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::UpdateNodeConfig { update }, vec![])
.await
}
// delegation-related:
async fn delegate(
&self,
node_id: NodeId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::Delegate { node_id }, vec![amount])
.await
}
async fn delegate_to_mixnode_on_behalf(
&self,
delegate: AccountId,
mix_id: MixId,
mix_id: NodeId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
@@ -593,23 +508,19 @@ pub trait MixnetSigningClient {
.await
}
async fn undelegate_from_mixnode(
async fn undelegate(
&self,
mix_id: MixId,
node_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::UndelegateFromMixnode { mix_id },
vec![],
)
.await
self.execute_mixnet_contract(fee, MixnetExecuteMsg::Undelegate { node_id }, vec![])
.await
}
async fn undelegate_to_mixnode_on_behalf(
&self,
delegate: AccountId,
mix_id: MixId,
mix_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
@@ -625,18 +536,15 @@ pub trait MixnetSigningClient {
// reward-related
async fn reward_mixnode(
async fn reward_node(
&self,
mix_id: MixId,
performance: Performance,
node_id: NodeId,
params: NodeRewardingParameters,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::RewardMixnode {
mix_id,
performance,
},
MixnetExecuteMsg::RewardNode { node_id, params },
vec![],
)
.await
@@ -664,12 +572,12 @@ pub trait MixnetSigningClient {
async fn withdraw_delegator_reward(
&self,
mix_id: MixId,
node_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::WithdrawDelegatorReward { mix_id },
MixnetExecuteMsg::WithdrawDelegatorReward { node_id },
vec![],
)
.await
@@ -678,7 +586,7 @@ pub trait MixnetSigningClient {
async fn withdraw_delegator_reward_on_behalf(
&self,
owner: AccountId,
mix_id: MixId,
mix_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
@@ -699,7 +607,7 @@ pub trait MixnetSigningClient {
async fn migrate_vested_delegation(
&self,
mix_id: MixId,
mix_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
@@ -761,6 +669,7 @@ where
mod tests {
use super::*;
use crate::nyxd::contract_traits::tests::{mock_coin, IgnoreValue};
use nym_mixnet_contract_common::ExecuteMsg;
// it's enough that this compiles and clippy is happy about it
#[allow(dead_code)]
@@ -770,56 +679,17 @@ mod tests {
) {
match msg {
MixnetExecuteMsg::UpdateAdmin { admin } => client.update_admin(admin, None).ignore(),
MixnetExecuteMsg::AssignNodeLayer { mix_id, layer } => {
client.assign_node_layer(mix_id, layer, None).ignore()
}
MixnetExecuteMsg::CreateFamily { label } => client.create_family(label, None).ignore(),
MixnetExecuteMsg::JoinFamily {
join_permit,
family_head,
} => client.join_family(join_permit, family_head, None).ignore(),
MixnetExecuteMsg::LeaveFamily { family_head } => {
client.leave_family(family_head, None).ignore()
}
MixnetExecuteMsg::KickFamilyMember { member } => {
client.kick_family_member(member, None).ignore()
}
MixnetExecuteMsg::CreateFamilyOnBehalf {
owner_address,
label,
} => client
.create_family_on_behalf(owner_address, label, None)
.ignore(),
MixnetExecuteMsg::JoinFamilyOnBehalf {
member_address,
join_permit,
family_head,
} => client
.join_family_on_behalf(member_address, join_permit, family_head, None)
.ignore(),
MixnetExecuteMsg::LeaveFamilyOnBehalf {
member_address,
family_head,
} => client
.leave_family_on_behalf(member_address, family_head, None)
.ignore(),
MixnetExecuteMsg::KickFamilyMemberOnBehalf {
head_address,
member,
} => client
.kick_family_member_on_behalf(head_address, member, None)
.ignore(),
MixnetExecuteMsg::UpdateRewardingValidatorAddress { address } => client
.update_rewarding_validator_address(address.parse().unwrap(), None)
.ignore(),
MixnetExecuteMsg::UpdateContractStateParams { updated_parameters } => client
.update_contract_state_params(updated_parameters, None)
.ignore(),
MixnetExecuteMsg::UpdateActiveSetSize {
active_set_size,
MixnetExecuteMsg::UpdateActiveSetDistribution {
update,
force_immediately,
} => client
.update_active_set_size(active_set_size, force_immediately, None)
.update_active_set_size(update, force_immediately, None)
.ignore(),
MixnetExecuteMsg::UpdateRewardingParams {
updated_params,
@@ -842,12 +712,6 @@ mod tests {
MixnetExecuteMsg::BeginEpochTransition {} => {
client.begin_epoch_transition(None).ignore()
}
MixnetExecuteMsg::AdvanceCurrentEpoch {
new_rewarded_set,
expected_active_set_size,
} => client
.advance_current_epoch(new_rewarded_set, expected_active_set_size, None)
.ignore(),
MixnetExecuteMsg::ReconcileEpochEvents { limit } => {
client.reconcile_epoch_events(limit, None).ignore()
}
@@ -887,8 +751,8 @@ mod tests {
MixnetExecuteMsg::UnbondMixnodeOnBehalf { owner } => client
.unbond_mixnode_on_behalf(owner.parse().unwrap(), None)
.ignore(),
MixnetExecuteMsg::UpdateMixnodeCostParams { new_costs } => {
client.update_mixnode_cost_params(new_costs, None).ignore()
MixnetExecuteMsg::UpdateCostParams { new_costs } => {
client.update_cost_params(new_costs, None).ignore()
}
MixnetExecuteMsg::UpdateMixnodeCostParamsOnBehalf { new_costs, owner } => client
.update_mixnode_cost_params_on_behalf(owner.parse().unwrap(), new_costs, None)
@@ -928,29 +792,28 @@ mod tests {
MixnetExecuteMsg::UpdateGatewayConfigOnBehalf { new_config, owner } => client
.update_gateway_config_on_behalf(owner.parse().unwrap(), new_config, None)
.ignore(),
MixnetExecuteMsg::DelegateToMixnode { mix_id } => client
.delegate_to_mixnode(mix_id, mock_coin(), None)
.ignore(),
MixnetExecuteMsg::Delegate { node_id: mix_id } => {
client.delegate(mix_id, mock_coin(), None).ignore()
}
MixnetExecuteMsg::DelegateToMixnodeOnBehalf { mix_id, delegate } => client
.delegate_to_mixnode_on_behalf(delegate.parse().unwrap(), mix_id, mock_coin(), None)
.ignore(),
MixnetExecuteMsg::UndelegateFromMixnode { mix_id } => {
client.undelegate_from_mixnode(mix_id, None).ignore()
MixnetExecuteMsg::Undelegate { node_id: mix_id } => {
client.undelegate(mix_id, None).ignore()
}
MixnetExecuteMsg::UndelegateFromMixnodeOnBehalf { mix_id, delegate } => client
.undelegate_to_mixnode_on_behalf(delegate.parse().unwrap(), mix_id, None)
.ignore(),
MixnetExecuteMsg::RewardMixnode {
mix_id,
performance,
} => client.reward_mixnode(mix_id, performance, None).ignore(),
MixnetExecuteMsg::RewardNode { node_id, params } => {
client.reward_node(node_id, params, None).ignore()
}
MixnetExecuteMsg::WithdrawOperatorReward {} => {
client.withdraw_operator_reward(None).ignore()
}
MixnetExecuteMsg::WithdrawOperatorRewardOnBehalf { owner } => client
.withdraw_operator_reward_on_behalf(owner.parse().unwrap(), None)
.ignore(),
MixnetExecuteMsg::WithdrawDelegatorReward { mix_id } => {
MixnetExecuteMsg::WithdrawDelegatorReward { node_id: mix_id } => {
client.withdraw_delegator_reward(mix_id, None).ignore()
}
MixnetExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, owner } => client
@@ -963,6 +826,32 @@ mod tests {
client.migrate_vested_delegation(mix_id, None).ignore()
}
ExecuteMsg::AssignRoles { assignment } => {
client.assign_roles(assignment, None).ignore()
}
ExecuteMsg::MigrateMixnode {} => client.migrate_legacy_mixnode(None).ignore(),
ExecuteMsg::MigrateGateway { cost_params } => {
client.migrate_legacy_gateway(cost_params, None).ignore()
}
ExecuteMsg::BondNymNode {
node,
cost_params,
owner_signature,
} => client
.bond_nymnode(node, cost_params, owner_signature, mock_coin(), None)
.ignore(),
ExecuteMsg::UnbondNymNode {} => client.unbond_nymnode(None).ignore(),
ExecuteMsg::UpdateNodeConfig { update } => {
client.update_nymnode_config(update, None).ignore()
}
ExecuteMsg::TestingUncheckedBondLegacyMixnode { .. } => {
todo!("purposely not implemented")
}
ExecuteMsg::TestingUncheckedBondLegacyGateway { .. } => {
todo!("purposely not implemented")
}
#[cfg(feature = "contract-testing")]
MixnetExecuteMsg::TestingResolveAllPendingEvents { .. } => {
client.testing_resolve_all_pending_events(None).ignore()
@@ -9,7 +9,7 @@ use crate::nyxd::CosmWasmClient;
use async_trait::async_trait;
use cosmwasm_std::{Coin as CosmWasmCoin, Timestamp};
use nym_contracts_common::ContractBuildInformation;
use nym_mixnet_contract_common::MixId;
use nym_mixnet_contract_common::NodeId;
use nym_vesting_contract_common::{
messages::QueryMsg as VestingQueryMsg, Account, AccountVestingCoins, AccountsResponse,
AllDelegationsResponse, BaseVestingAccountInfo, DelegationTimesResponse,
@@ -238,7 +238,7 @@ pub trait VestingQueryClient {
async fn get_vesting_delegation(
&self,
address: &str,
mix_id: MixId,
mix_id: NodeId,
block_timestamp_secs: u64,
) -> Result<VestingDelegation, NyxdError> {
self.query_vesting_contract(VestingQueryMsg::GetDelegation {
@@ -252,7 +252,7 @@ pub trait VestingQueryClient {
async fn get_total_delegation_amount(
&self,
address: &str,
mix_id: MixId,
mix_id: NodeId,
) -> Result<Coin, NyxdError> {
self.query_vesting_contract(VestingQueryMsg::GetTotalDelegationAmount {
address: address.to_string(),
@@ -264,7 +264,7 @@ pub trait VestingQueryClient {
async fn get_delegation_timestamps(
&self,
address: &str,
mix_id: MixId,
mix_id: NodeId,
) -> Result<DelegationTimesResponse, NyxdError> {
self.query_vesting_contract(VestingQueryMsg::GetDelegationTimes {
address: address.to_string(),
@@ -275,7 +275,7 @@ pub trait VestingQueryClient {
async fn get_all_vesting_delegations_paged(
&self,
start_after: Option<(u32, MixId, u64)>,
start_after: Option<(u32, NodeId, u64)>,
limit: Option<u32>,
) -> Result<AllDelegationsResponse, NyxdError> {
self.query_vesting_contract(VestingQueryMsg::GetAllDelegations { start_after, limit })
@@ -9,10 +9,9 @@ use crate::signing::signer::OfflineSigner;
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::{Gateway, MixId, MixNode};
use nym_mixnet_contract_common::mixnode::{MixNodeConfigUpdate, NodeCostParams};
use nym_mixnet_contract_common::{Gateway, MixNode, NodeId};
use nym_vesting_contract_common::messages::ExecuteMsg as VestingExecuteMsg;
use nym_vesting_contract_common::{PledgeCap, VestingSpecification};
@@ -28,7 +27,7 @@ pub trait VestingSigningClient {
async fn vesting_update_mixnode_cost_params(
&self,
new_costs: MixNodeCostParams,
new_costs: NodeCostParams,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_vesting_contract(
@@ -124,7 +123,7 @@ pub trait VestingSigningClient {
async fn vesting_bond_mixnode(
&self,
mix_node: MixNode,
cost_params: MixNodeCostParams,
cost_params: NodeCostParams,
owner_signature: MessageSignature,
pledge: Coin,
fee: Option<Fee>,
@@ -204,7 +203,7 @@ pub trait VestingSigningClient {
async fn vesting_track_undelegation(
&self,
address: &str,
mix_id: MixId,
mix_id: NodeId,
amount: Coin,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
@@ -222,7 +221,7 @@ pub trait VestingSigningClient {
async fn vesting_delegate_to_mixnode(
&self,
mix_id: MixId,
mix_id: NodeId,
amount: Coin,
on_behalf_of: Option<String>,
fee: Option<Fee>,
@@ -241,7 +240,7 @@ pub trait VestingSigningClient {
async fn vesting_undelegate_from_mixnode(
&self,
mix_id: MixId,
mix_id: NodeId,
on_behalf_of: Option<String>,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
@@ -301,7 +300,7 @@ pub trait VestingSigningClient {
async fn vesting_withdraw_delegator_reward(
&self,
mix_id: MixId,
mix_id: NodeId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_vesting_contract(
@@ -354,50 +353,6 @@ pub trait VestingSigningClient {
)
.await
}
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
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -446,21 +401,6 @@ mod tests {
msg: VestingExecuteMsg,
) {
match msg {
VestingExecuteMsg::CreateFamily { label } => {
client.vesting_create_family(label, None).ignore()
}
VestingExecuteMsg::JoinFamily {
join_permit,
family_head,
} => client
.vesting_join_family(join_permit, family_head, None)
.ignore(),
VestingExecuteMsg::LeaveFamily { family_head } => {
client.vesting_leave_family(family_head, None).ignore()
}
VestingExecuteMsg::KickFamilyMember { member } => {
client.vesting_kick_family_member(member, None).ignore()
}
VestingExecuteMsg::TrackReward { amount, address } => client
.vesting_track_reward(amount.into(), address, None)
.ignore(),
@@ -154,6 +154,23 @@ pub enum NyxdError {
#[error("the response data has invalid size. got {got} bytes, but expected {expected} bytes instead")]
MalformedResponseData { got: usize, expected: usize },
#[error(
"one of the extension query for {contract} failed with the following message: {message}"
)]
ExtensionQueryFailure { contract: String, message: String },
}
impl NyxdError {
pub fn extension_query_failure(
contract: impl Into<String>,
message: impl Into<String>,
) -> Self {
NyxdError::ExtensionQueryFailure {
contract: contract.into(),
message: message.into(),
}
}
}
// The purpose of parsing the abci query result is that we want to generate the `pretty_log` if
@@ -4,6 +4,7 @@
use clap::Parser;
use cosmwasm_std::Decimal;
use log::{debug, info};
use nym_mixnet_contract_common::reward_params::RewardedSetParams;
use nym_mixnet_contract_common::{
InitialRewardingParams, InstantiateMsg, OperatingCostRange, Percent, ProfitMarginRange,
};
@@ -56,11 +57,17 @@ pub struct Args {
#[clap(long, default_value_t = 2)]
pub interval_pool_emission: u64,
#[clap(long, default_value_t = 240)]
pub rewarded_set_size: u32,
#[clap(long, default_value_t = 50)]
pub(crate) entry_gateways: u32,
#[clap(long, default_value_t = 240)]
pub active_set_size: u32,
#[clap(long, default_value_t = 70)]
pub(crate) exit_gateways: u32,
#[clap(long, default_value_t = 120)]
pub(crate) mixnodes: u32,
#[clap(long, default_value_t = 0)]
pub(crate) standby: u32,
#[clap(long, default_value_t = Percent::zero())]
pub minimum_profit_margin_percent: Percent,
@@ -95,8 +102,13 @@ pub async fn generate(args: Args) {
.expect("active_set_work_factor can't be converted to Decimal"),
interval_pool_emission: Percent::from_percentage_value(args.interval_pool_emission)
.expect("interval_pool_emission can't be converted to Percent"),
rewarded_set_size: args.rewarded_set_size,
active_set_size: args.active_set_size,
rewarded_set_params: RewardedSetParams {
entry_gateways: args.entry_gateways,
exit_gateways: args.exit_gateways,
mixnodes: args.mixnodes,
standby: args.standby,
},
};
debug!("initial_rewarding_params: {:?}", initial_rewarding_params);
@@ -4,13 +4,13 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::{Coin, MixId};
use nym_mixnet_contract_common::{Coin, NodeId};
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub mix_id: Option<MixId>,
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
@@ -43,7 +43,7 @@ pub async fn delegate_to_mixnode(args: Args, client: SigningClient) {
let coin = Coin::new(args.amount, denom);
let res = client
.delegate_to_mixnode(mix_id, coin.into(), None)
.delegate(mix_id, coin.into(), None)
.await
.expect("failed to delegate to mixnode!");
@@ -1,21 +1,18 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
use std::fs;
use std::fs::OpenOptions;
use clap::Parser;
use comfy_table::Table;
use csv::WriterBuilder;
use log::info;
use nym_mixnet_contract_common::ExecuteMsg;
use nym_mixnet_contract_common::ExecuteMsg::{DelegateToMixnode, UndelegateFromMixnode};
use nym_mixnet_contract_common::PendingEpochEventKind::{Delegate, Undelegate};
use nym_mixnet_contract_common::PendingEpochEventKind;
use nym_validator_client::nyxd::contract_traits::{NymContractsProvider, PagedMixnetQueryClient};
use nym_validator_client::nyxd::Coin;
use std::cmp::Ordering;
use std::collections::{HashMap, HashSet};
use std::fs;
use std::fs::OpenOptions;
use crate::context::SigningClient;
use crate::utils::pretty_coin;
@@ -40,7 +37,7 @@ pub struct Args {
#[derive(Debug)]
pub struct InputFileRow {
pub mix_id: String,
pub node_id: String,
pub amount: Coin,
}
#[derive(Debug)]
@@ -76,7 +73,7 @@ impl InputFileReader {
}
rows.push(InputFileRow {
mix_id,
node_id: mix_id,
amount: Coin {
amount: micro_nym_amount,
denom: "unym".to_string(),
@@ -140,8 +137,10 @@ async fn fetch_delegation_data(
let mut pending_delegation_map: HashMap<String, Coin> = HashMap::new();
for delegation in delegations {
existing_delegation_map
.insert(delegation.mix_id.to_string(), Coin::from(delegation.amount));
existing_delegation_map.insert(
delegation.node_id.to_string(),
Coin::from(delegation.amount),
);
}
// Look for pending delegate / undelegate events which might be of interest to us
@@ -155,27 +154,27 @@ async fn fetch_delegation_data(
for event in pending_events {
match event.event.kind {
// If a pending undelegate tx is found, remove it from delegation map
Undelegate { owner, mix_id, .. } => {
PendingEpochEventKind::Undelegate { owner, node_id, .. } => {
if owner == address.as_ref()
&& existing_delegation_map.contains_key(&mix_id.to_string())
&& existing_delegation_map.contains_key(&node_id.to_string())
{
existing_delegation_map.remove(&mix_id.to_string());
existing_delegation_map.remove(&node_id.to_string());
}
}
// If a pending delegation event is found, gather them to consolidate later
Delegate {
PendingEpochEventKind::Delegate {
owner,
mix_id,
node_id,
amount,
..
} => {
if owner == address.as_ref() {
let mut amount = Coin::from(amount);
if let Some(pending_record) = pending_delegation_map.get(&mix_id.to_string()) {
if let Some(pending_record) = pending_delegation_map.get(&node_id.to_string()) {
amount.amount += pending_record.amount;
}
pending_delegation_map.insert(mix_id.to_string(), amount);
pending_delegation_map.insert(node_id.to_string(), amount);
}
}
_ => {}
@@ -217,7 +216,7 @@ pub async fn delegate_to_multiple_mixnodes(args: Args, client: SigningClient) {
for row in &records.rows {
let input_amount = row.amount.amount;
let existing_delegation_amount = existing_delegation_map
.get(&row.mix_id)
.get(&row.node_id)
.map_or(0, |coin| coin.amount);
match existing_delegation_amount.cmp(&input_amount) {
@@ -229,25 +228,26 @@ pub async fn delegate_to_multiple_mixnodes(args: Args, client: SigningClient) {
amount: input_amount - existing_delegation_amount,
denom: row.amount.denom.clone(),
};
let mix_id = row.mix_id.clone().parse::<u32>().unwrap();
delegation_msgs.push((DelegateToMixnode { mix_id }, vec![difference.clone()]));
let node_id = row.node_id.clone().parse::<u32>().unwrap();
delegation_msgs.push((ExecuteMsg::Delegate { node_id }, vec![difference.clone()]));
delegation_table.add_row(&[
row.mix_id.clone(),
row.node_id.clone(),
pretty_coin(&row.amount),
pretty_coin(&difference),
]);
}
Ordering::Greater => {
let mix_id = row.mix_id.clone().parse::<u32>().unwrap();
let node_id = row.node_id.clone().parse::<u32>().unwrap();
let coins: Vec<Coin> = vec![];
undelegation_msgs.push((UndelegateFromMixnode { mix_id }, coins));
undelegation_table.add_row(&[row.mix_id.clone()]);
undelegation_msgs.push((ExecuteMsg::Undelegate { node_id }, coins));
undelegation_table.add_row(&[row.node_id.clone()]);
if row.amount.amount > 0 {
delegation_msgs.push((DelegateToMixnode { mix_id }, vec![row.amount.clone()]));
delegation_msgs
.push((ExecuteMsg::Delegate { node_id }, vec![row.amount.clone()]));
delegation_table.add_row(&[
row.mix_id.clone(),
row.node_id.clone(),
pretty_coin(&row.amount),
pretty_coin(&row.amount),
]);
@@ -4,13 +4,13 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::MixId;
use nym_mixnet_contract_common::NodeId;
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub mix_id: Option<MixId>,
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
@@ -66,7 +66,7 @@ async fn print_delegations(delegations: Vec<Delegation>, client: &SigningClientW
for delegation in delegations {
table.add_row(vec![
to_iso_timestamp(delegation.height as u32, client).await,
delegation.mix_id.to_string(),
delegation.node_id.to_string(),
pretty_cosmwasm_coin(&delegation.amount),
delegation
.proxy
@@ -93,7 +93,7 @@ async fn print_delegation_events(events: Vec<PendingEpochEvent>, client: &Signin
match event.event.kind {
PendingEpochEventKind::Delegate {
owner,
mix_id,
node_id: mix_id,
amount,
proxy,
..
@@ -110,7 +110,7 @@ async fn print_delegation_events(events: Vec<PendingEpochEvent>, client: &Signin
}
PendingEpochEventKind::Undelegate {
owner,
mix_id,
node_id: mix_id,
proxy,
..
} => {
@@ -4,13 +4,13 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::MixId;
use nym_mixnet_contract_common::NodeId;
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub mix_id: Option<MixId>,
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
@@ -4,13 +4,13 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::MixId;
use nym_mixnet_contract_common::NodeId;
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub mix_id: Option<MixId>,
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
@@ -4,13 +4,13 @@
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::MixId;
use nym_mixnet_contract_common::NodeId;
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub mix_id: Option<MixId>,
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
@@ -36,7 +36,7 @@ pub async fn undelegate_from_mixnode(args: Args, client: SigningClient) {
};
let res = client
.undelegate_from_mixnode(mix_id, None)
.undelegate(mix_id, None)
.await
.expect("failed to remove stake from mixnode!");
@@ -4,7 +4,7 @@
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::{Coin, MixId};
use nym_mixnet_contract_common::{Coin, NodeId};
use nym_validator_client::nyxd::contract_traits::MixnetQueryClient;
use nym_validator_client::nyxd::contract_traits::VestingSigningClient;
@@ -13,7 +13,7 @@ use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub mix_id: Option<MixId>,
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
@@ -3,7 +3,7 @@
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::MixId;
use nym_mixnet_contract_common::NodeId;
use nym_validator_client::nyxd::contract_traits::MixnetQueryClient;
use nym_validator_client::nyxd::contract_traits::VestingSigningClient;
@@ -12,7 +12,7 @@ use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub mix_id: Option<MixId>,
pub mix_id: Option<NodeId>,
#[clap(long)]
pub identity_key: Option<String>,
@@ -5,6 +5,7 @@ use clap::{Args, Subcommand};
pub mod bond_gateway;
pub mod gateway_bonding_sign_payload;
pub mod nymnode_migration;
pub mod settings;
pub mod unbond_gateway;
pub mod vesting_bond_gateway;
@@ -31,4 +32,6 @@ pub enum MixnetOperatorsGatewayCommands {
VestingUnbond(vesting_unbond_gateway::Args),
/// Create base58-encoded payload required for producing valid bonding signature.
CreateGatewayBondingSignPayload(gateway_bonding_sign_payload::Args),
/// Migrate the gateway into a Nym Node
MigrateToNymnode(nymnode_migration::Args),
}
@@ -0,0 +1,56 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use cosmwasm_std::Uint128;
use log::info;
use nym_contracts_common::Percent;
use nym_mixnet_contract_common::{
NodeCostParams, DEFAULT_INTERVAL_OPERATING_COST_AMOUNT, DEFAULT_PROFIT_MARGIN_PERCENT,
};
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
use nym_validator_client::nyxd::CosmWasmCoin;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub profit_margin_percent: Option<u64>,
#[clap(
long,
help = "operating cost in current DENOMINATION (so it would be 'unym', rather than 'nym')"
)]
pub interval_operating_cost: Option<u128>,
}
pub async fn migrate_to_nymnode(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
let cost_params =
if args.profit_margin_percent.is_some() || args.interval_operating_cost.is_some() {
Some(NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(
args.profit_margin_percent
.unwrap_or(DEFAULT_PROFIT_MARGIN_PERCENT),
)
.unwrap(),
interval_operating_cost: CosmWasmCoin {
denom: denom.into(),
amount: Uint128::new(
args.interval_operating_cost
.unwrap_or(DEFAULT_INTERVAL_OPERATING_COST_AMOUNT),
),
},
})
} else {
None
};
let res = client
.migrate_legacy_gateway(cost_params, None)
.await
.expect("failed to migrate gateway!");
info!("migration result: {:?}", res)
}
@@ -1,20 +1,21 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use cosmwasm_std::Uint128;
use log::{info, warn};
use nym_contracts_common::signing::MessageSignature;
use nym_mixnet_contract_common::{Coin, MixNodeCostParams, Percent};
use nym_mixnet_contract_common::{
Coin, NodeCostParams, Percent, DEFAULT_INTERVAL_OPERATING_COST_AMOUNT,
DEFAULT_PROFIT_MARGIN_PERCENT,
};
use nym_network_defaults::{
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
};
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
use nym_validator_client::nyxd::CosmWasmCoin;
use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
@@ -42,7 +43,7 @@ pub struct Args {
pub version: String,
#[clap(long)]
pub profit_margin_percent: Option<u8>,
pub profit_margin_percent: Option<u64>,
#[clap(
long,
@@ -85,14 +86,18 @@ pub async fn bond_mixnode(args: Args, client: SigningClient) {
let coin = Coin::new(args.amount, denom);
let cost_params = MixNodeCostParams {
let cost_params = NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(
args.profit_margin_percent.unwrap_or(10) as u64,
args.profit_margin_percent
.unwrap_or(DEFAULT_PROFIT_MARGIN_PERCENT),
)
.unwrap(),
interval_operating_cost: CosmWasmCoin {
denom: denom.into(),
amount: Uint128::new(args.interval_operating_cost.unwrap_or(40_000_000)),
amount: Uint128::new(
args.interval_operating_cost
.unwrap_or(DEFAULT_INTERVAL_OPERATING_COST_AMOUNT),
),
},
};
@@ -1,25 +0,0 @@
// 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_validator_client::nyxd::contract_traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
/// Label that is going to be used for creating the family
#[arg(long)]
pub family_label: String,
}
pub async fn create_family(args: Args, client: SigningClient) {
info!("Create family");
let res = client
.create_family(args.family_label, None)
.await
.expect("failed to create family");
info!("Family creation result: {:?}", res);
}
@@ -1,72 +0,0 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::QueryClient;
use crate::utils::DataWrapper;
use clap::Parser;
use cosmrs::AccountId;
use log::info;
use nym_bin_common::output_format::OutputFormat;
use nym_crypto::asymmetric::identity;
use nym_mixnet_contract_common::construct_family_join_permit;
use nym_mixnet_contract_common::families::FamilyHead;
use nym_validator_client::nyxd::contract_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,
// 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,
#[clap(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
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().to_string())
.await
.unwrap()
.family
.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 head = FamilyHead::new(mixnode.bond_information.identity());
let payload = construct_family_join_permit(nonce, head, args.member.to_base58_string());
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
println!("{}", args.output.format(&wrapper))
}
@@ -1,34 +0,0 @@
// 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 nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
/// The head of the family that we intend to join
#[arg(long)]
pub family_head: identity::PublicKey,
/// 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 = client
.join_family(args.join_permit, family_head, None)
.await
.expect("failed to join family");
info!("Family join result: {:?}", res);
}
@@ -1,40 +0,0 @@
// 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_validator_client::nyxd::contract_traits::MixnetSigningClient;
use nym_validator_client::nyxd::contract_traits::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);
}
@@ -1,29 +0,0 @@
// 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 nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
/// The head of the family that we intend to leave
#[arg(long)]
pub family_head: identity::PublicKey,
}
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 = client
.leave_family(family_head, None)
.await
.expect("failed to leave family");
info!("Family leave result: {:?}", res);
}
@@ -1,35 +0,0 @@
// 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),
}
@@ -8,7 +8,8 @@ use cosmwasm_std::{Coin, Uint128};
use nym_bin_common::output_format::OutputFormat;
use nym_contracts_common::Percent;
use nym_mixnet_contract_common::{
construct_legacy_mixnode_bonding_sign_payload, MixNodeCostParams,
construct_legacy_mixnode_bonding_sign_payload, NodeCostParams,
DEFAULT_INTERVAL_OPERATING_COST_AMOUNT, DEFAULT_PROFIT_MARGIN_PERCENT,
};
use nym_network_defaults::{
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
@@ -40,7 +41,7 @@ pub struct Args {
pub version: String,
#[clap(long)]
pub profit_margin_percent: Option<u8>,
pub profit_margin_percent: Option<u64>,
#[clap(
long,
@@ -75,14 +76,18 @@ pub async fn create_payload(args: Args, client: SigningClient) {
let coin = Coin::new(args.amount, denom);
let cost_params = MixNodeCostParams {
let cost_params = NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(
args.profit_margin_percent.unwrap_or(10) as u64,
args.profit_margin_percent
.unwrap_or(DEFAULT_PROFIT_MARGIN_PERCENT),
)
.unwrap(),
interval_operating_cost: CosmWasmCoin {
denom: denom.into(),
amount: Uint128::new(args.interval_operating_cost.unwrap_or(40_000_000)),
amount: Uint128::new(
args.interval_operating_cost
.unwrap_or(DEFAULT_INTERVAL_OPERATING_COST_AMOUNT),
),
},
};
@@ -5,10 +5,10 @@ use clap::{Args, Subcommand};
pub mod bond_mixnode;
pub mod decrease_pledge;
pub mod families;
pub mod keys;
pub mod migrate_vested_mixnode;
pub mod mixnode_bonding_sign_payload;
pub mod nymnode_migration;
pub mod pledge_more;
pub mod rewards;
pub mod settings;
@@ -33,8 +33,6 @@ 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
@@ -55,4 +53,6 @@ pub enum MixnetOperatorsMixnodeCommands {
DecreasePledgeVesting(vesting_decrease_pledge::Args),
/// Migrate the mixnode to use liquid tokens
MigrateVestedNode(migrate_vested_mixnode::Args),
/// Migrate the mixnode into a Nym Node
MigrateToNymnode(nymnode_migration::Args),
}
@@ -0,0 +1,19 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {}
pub async fn migrate_to_nymnode(_args: Args, client: SigningClient) {
let res = client
.migrate_legacy_mixnode(None)
.await
.expect("failed to migrate mixnode!");
info!("migration result: {:?}", res)
}
@@ -2,10 +2,13 @@
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use anyhow::{anyhow, bail};
use clap::Parser;
use cosmwasm_std::Uint128;
use log::info;
use nym_mixnet_contract_common::{MixNodeCostParams, Percent};
use nym_mixnet_contract_common::{
NodeCostParams, Percent, DEFAULT_INTERVAL_OPERATING_COST_AMOUNT, DEFAULT_PROFIT_MARGIN_PERCENT,
};
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, MixnetSigningClient};
use nym_validator_client::nyxd::CosmWasmCoin;
@@ -24,62 +27,45 @@ pub struct Args {
pub interval_operating_cost: Option<u128>,
}
pub async fn update_cost_params(args: Args, client: SigningClient) {
pub async fn update_cost_params(args: Args, client: SigningClient) -> anyhow::Result<()> {
let denom = client.current_chain_details().mix_denom.base.as_str();
fn convert_to_percent(value: u64) -> Percent {
Percent::from_percentage_value(value).expect("Invalid value")
}
let default_profit_margin =
Percent::from_percentage_value(DEFAULT_PROFIT_MARGIN_PERCENT).unwrap();
let default_profit_margin: Percent = convert_to_percent(20);
let mix_details = client
.get_owned_mixnode(&client.address())
.await?
.mixnode_details
.ok_or_else(|| anyhow!("the client does not own any mixnodes"))?;
let current_parameters = mix_details.rewarding_details.cost_params;
let mixownership_response = match client.get_owned_mixnode(&client.address()).await {
Ok(response) => response,
Err(_) => {
eprintln!("Failed to obtain owned mixnode");
return;
}
};
let mix_id = match mixownership_response.mixnode_details {
Some(details) => details.bond_information.mix_id,
None => {
eprintln!("Failed to obtain mixnode details");
return;
}
};
let rewarding_response = match client.get_mixnode_rewarding_details(mix_id).await {
Ok(details) => details,
Err(_) => {
eprintln!("Failed to obtain rewarding details");
return;
}
};
let profit_margin_percent = rewarding_response
.rewarding_details
let profit_margin_percent = current_parameters
.map(|rd| rd.cost_params.profit_margin_percent)
.unwrap_or(default_profit_margin);
let profit_margin_value = args
.profit_margin_percent
.map(|pm| convert_to_percent(pm as u64))
.unwrap_or(profit_margin_percent);
.map(|pm| Percent::from_percentage_value(pm as u64))
.unwrap_or(profit_margin_percent)?;
let cost_params = MixNodeCostParams {
let cost_params = NodeCostParams {
profit_margin_percent: profit_margin_value,
interval_operating_cost: CosmWasmCoin {
denom: denom.into(),
amount: Uint128::new(args.interval_operating_cost.unwrap_or(40_000_000)),
amount: Uint128::new(
args.interval_operating_cost
.unwrap_or(DEFAULT_INTERVAL_OPERATING_COST_AMOUNT),
),
},
};
info!("Starting mixnode params updating!");
let res = client
.update_mixnode_cost_params(cost_params, None)
.update_cost_params(cost_params, None)
.await
.expect("failed to update cost params");
info!("Cost params result: {:?}", res)
info!("Cost params result: {:?}", res);
Ok(())
}
@@ -6,7 +6,9 @@ use clap::Parser;
use cosmwasm_std::Uint128;
use log::{info, warn};
use nym_contracts_common::signing::MessageSignature;
use nym_mixnet_contract_common::{Coin, MixNodeCostParams};
use nym_mixnet_contract_common::{
Coin, NodeCostParams, DEFAULT_INTERVAL_OPERATING_COST_AMOUNT, DEFAULT_PROFIT_MARGIN_PERCENT,
};
use nym_mixnet_contract_common::{MixNode, Percent};
use nym_network_defaults::{
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
@@ -40,7 +42,7 @@ pub struct Args {
pub version: String,
#[clap(long)]
pub profit_margin_percent: Option<u8>,
pub profit_margin_percent: Option<u64>,
#[clap(
long,
@@ -84,14 +86,18 @@ pub async fn vesting_bond_mixnode(client: SigningClient, args: Args, denom: &str
let coin = Coin::new(args.amount, denom);
let cost_params = MixNodeCostParams {
let cost_params = NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(
args.profit_margin_percent.unwrap_or(10) as u64,
args.profit_margin_percent
.unwrap_or(DEFAULT_PROFIT_MARGIN_PERCENT),
)
.unwrap(),
interval_operating_cost: CosmWasmCoin {
denom: denom.into(),
amount: Uint128::new(args.interval_operating_cost.unwrap_or(40_000_000)),
amount: Uint128::new(
args.interval_operating_cost
.unwrap_or(DEFAULT_INTERVAL_OPERATING_COST_AMOUNT),
),
},
};
@@ -6,6 +6,7 @@ use clap::{Args, Subcommand};
pub mod gateway;
pub mod identity_key;
pub mod mixnode;
pub mod nymnode;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
@@ -17,9 +18,11 @@ pub struct MixnetOperators {
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsCommands {
/// Manage your mixnode
/// Manage your Nym Node
Nymnode(nymnode::MixnetOperatorsNymNode),
/// Manage your legacy mixnode
Mixnode(mixnode::MixnetOperatorsMixnode),
/// Manage your gateway
/// Manage your legacy gateway
Gateway(gateway::MixnetOperatorsGateway),
/// Sign messages using your private identity key
IdentityKey(identity_key::MixnetOperatorsIdentityKey),
@@ -0,0 +1,89 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use cosmwasm_std::Uint128;
use log::{info, warn};
use nym_contracts_common::signing::MessageSignature;
use nym_mixnet_contract_common::{
Coin, NodeCostParams, Percent, DEFAULT_INTERVAL_OPERATING_COST_AMOUNT,
DEFAULT_PROFIT_MARGIN_PERCENT,
};
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
use nym_validator_client::nyxd::CosmWasmCoin;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub host: String,
#[clap(long)]
pub signature: MessageSignature,
#[clap(long)]
pub http_api_port: Option<u16>,
#[clap(long)]
pub identity_key: String,
#[clap(long)]
pub profit_margin_percent: Option<u64>,
#[clap(
long,
help = "operating cost in current DENOMINATION (so it would be 'unym', rather than 'nym')"
)]
pub interval_operating_cost: Option<u128>,
#[clap(
long,
help = "bonding amount in current DENOMINATION (so it would be 'unym', rather than 'nym')"
)]
pub amount: u128,
#[clap(short, long)]
pub force: bool,
}
pub async fn bond_nymnode(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
info!("Starting nym node bonding!");
// if we're trying to bond less than 1 token
if args.amount < 1_000_000 && !args.force {
warn!("You're trying to bond only {}{} which is less than 1 full token. Are you sure that's what you want? If so, run with `--force` or `-f` flag", args.amount, denom);
return;
}
let nymnode = nym_mixnet_contract_common::NymNode {
host: args.host,
custom_http_port: args.http_api_port,
identity_key: args.identity_key,
};
let coin = Coin::new(args.amount, denom);
let cost_params = NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(
args.profit_margin_percent
.unwrap_or(DEFAULT_PROFIT_MARGIN_PERCENT),
)
.unwrap(),
interval_operating_cost: CosmWasmCoin {
denom: denom.into(),
amount: Uint128::new(
args.interval_operating_cost
.unwrap_or(DEFAULT_INTERVAL_OPERATING_COST_AMOUNT),
),
},
};
let res = client
.bond_nymnode(nymnode, cost_params, args.signature, coin.into(), None)
.await
.expect("failed to bond nymnode!");
info!("Bonding result: {:?}", res)
}
@@ -0,0 +1,20 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use base64::Engine;
use clap::Parser;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(short, long)]
pub key: String,
}
pub fn decode_node_key(args: Args) {
let b64_decoded = base64::prelude::BASE64_STANDARD
.decode(args.key)
.expect("failed to decode base64 string");
let b58_encoded = bs58::encode(&b64_decoded).into_string();
println!("{b58_encoded}")
}
@@ -0,0 +1,19 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::{Args, Subcommand};
pub mod decode_node_key;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
pub struct MixnetOperatorsNymNodeKeys {
#[clap(subcommand)]
pub command: MixnetOperatorsNymNodeKeysCommands,
}
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsNymNodeKeysCommands {
/// Decode a Nym Node key
DecodeNodeKey(decode_node_key::Args),
}
@@ -0,0 +1,43 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::{Args, Subcommand};
pub mod bond_nymnode;
pub mod keys;
pub mod nymnode_bonding_sign_payload;
pub mod pledge;
pub mod rewards;
pub mod settings;
pub mod unbond_nymnode;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
pub struct MixnetOperatorsNymNode {
#[clap(subcommand)]
pub command: MixnetOperatorsNymNodeCommands,
}
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsNymNodeCommands {
/// Operations for Nym Node keys
Keys(keys::MixnetOperatorsNymNodeKeys),
/// Manage your Nym Node operator rewards
Rewards(rewards::MixnetOperatorsNymNodeRewards),
/// Manage your Nym Node settings stored in the directory
Settings(settings::MixnetOperatorsNymNodeSettings),
/// Manage your Nym Node pledge
Pledge(pledge::MixnetOperatorsNymNodePledge),
/// Bond to a Nym Node
Bond(bond_nymnode::Args),
/// Unbond from a Nym Node
Unbond(unbond_nymnode::Args),
/// Create base58-encoded payload required for producing valid bonding signature.
CreateNodeBondingSignPayload(nymnode_bonding_sign_payload::Args),
}
@@ -0,0 +1,90 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use crate::utils::{account_id_to_cw_addr, DataWrapper};
use clap::Parser;
use cosmwasm_std::{Coin, Uint128};
use nym_bin_common::output_format::OutputFormat;
use nym_contracts_common::Percent;
use nym_mixnet_contract_common::{
construct_nym_node_bonding_sign_payload, NodeCostParams,
DEFAULT_INTERVAL_OPERATING_COST_AMOUNT, DEFAULT_PROFIT_MARGIN_PERCENT,
};
use nym_validator_client::nyxd::contract_traits::MixnetQueryClient;
use nym_validator_client::nyxd::CosmWasmCoin;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub host: String,
#[clap(long)]
pub identity_key: String,
#[clap(long)]
pub custom_http_api_port: Option<u16>,
#[clap(long)]
pub profit_margin_percent: Option<u64>,
#[clap(
long,
help = "operating cost in current DENOMINATION (so it would be 'unym', rather than 'nym')"
)]
pub interval_operating_cost: Option<u128>,
#[clap(
long,
help = "bonding amount in current DENOMINATION (so it would be 'unym', rather than 'nym')"
)]
pub amount: u128,
#[clap(short, long, default_value_t = OutputFormat::default())]
pub output: OutputFormat,
}
pub async fn create_payload(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
let mixnode = nym_mixnet_contract_common::NymNode {
host: args.host,
custom_http_port: args.custom_http_api_port,
identity_key: args.identity_key,
};
let coin = Coin::new(args.amount, denom);
let cost_params = NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(
args.profit_margin_percent
.unwrap_or(DEFAULT_PROFIT_MARGIN_PERCENT),
)
.unwrap(),
interval_operating_cost: CosmWasmCoin {
denom: denom.into(),
amount: Uint128::new(
args.interval_operating_cost
.unwrap_or(DEFAULT_INTERVAL_OPERATING_COST_AMOUNT),
),
},
};
let nonce = match client.get_signing_nonce(&client.address()).await {
Ok(nonce) => nonce,
Err(err) => {
eprint!(
"failed to query for the signing nonce of {}: {err}",
client.address()
);
return;
}
};
let address = account_id_to_cw_addr(&client.address());
let payload =
construct_nym_node_bonding_sign_payload(nonce, address, coin, mixnode, cost_params);
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
println!("{}", args.output.format(&wrapper))
}
@@ -0,0 +1,29 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::Coin;
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub decrease_by: u128,
}
pub async fn decrease_pledge(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
info!("Starting to decrease pledge");
let coin = Coin::new(args.decrease_by, denom);
let res = client
.pledge_more(coin.into(), None)
.await
.expect("failed to decrease pledge!");
info!("decreasing pledge: {:?}", res);
}
@@ -0,0 +1,29 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::Coin;
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub amount: u128,
}
pub async fn increase_pledge(args: Args, client: SigningClient) {
let denom = client.current_chain_details().mix_denom.base.as_str();
info!("Starting to pledge more");
let coin = Coin::new(args.amount, denom);
let res = client
.pledge_more(coin.into(), None)
.await
.expect("failed to pledge more!");
info!("pledging more: {:?}", res);
}
@@ -0,0 +1,22 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::{Args, Subcommand};
pub mod decrease_pledge;
pub mod increase_pledge;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
pub struct MixnetOperatorsNymNodePledge {
#[clap(subcommand)]
pub command: MixnetOperatorsNymNodePledgeCommands,
}
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsNymNodePledgeCommands {
/// Increase current pledge
Increase(increase_pledge::Args),
/// decrease current pledge
Decrease(decrease_pledge::Args),
}
@@ -0,0 +1,21 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
#[derive(Debug, Parser)]
pub struct Args {}
pub async fn claim_operator_reward(_args: Args, client: SigningClient) {
info!("Claim operator reward");
let res = client
.withdraw_operator_reward(None)
.await
.expect("failed to claim operator reward");
info!("Claiming operator reward: {:?}", res)
}
@@ -0,0 +1,19 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::{Args, Subcommand};
pub mod claim_operator_reward;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
pub struct MixnetOperatorsNymNodeRewards {
#[clap(subcommand)]
pub command: MixnetOperatorsNymNodeRewardsCommands,
}
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsNymNodeRewardsCommands {
/// Claim rewards
Claim(claim_operator_reward::Args),
}
@@ -0,0 +1,22 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::{Args, Subcommand};
pub mod update_config;
pub mod update_cost_params;
#[derive(Debug, Args)]
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
pub struct MixnetOperatorsNymNodeSettings {
#[clap(subcommand)]
pub command: MixnetOperatorsNymNodeSettingsCommands,
}
#[derive(Debug, Subcommand)]
pub enum MixnetOperatorsNymNodeSettingsCommands {
/// Update Nym Node configuration
UpdateConfig(update_config::Args),
/// Update Nym Node cost parameters
UpdateCostParameters(update_cost_params::Args),
}
@@ -0,0 +1,50 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_mixnet_contract_common::nym_node::NodeConfigUpdate;
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub host: Option<String>,
// ideally this would have been `Option<Option<u16>>`, but not sure if clap would have recognised it
#[clap(long)]
pub custom_http_port: Option<u16>,
// equivalent to setting `custom_http_port` to `None`
#[clap(long)]
pub restore_default_http_port: bool,
}
pub async fn update_config(args: Args, client: SigningClient) {
info!("Update nym node config!");
if client
.get_owned_nymnode(&client.address())
.await
.expect("failed to query the chain for nym node details")
.details
.is_none()
{
log::warn!("this operator does not own a nym node to update");
return;
}
let update = NodeConfigUpdate {
host: args.host,
custom_http_port: args.custom_http_port,
restore_default_http_port: args.restore_default_http_port,
};
let res = client
.update_nymnode_config(update, None)
.await
.expect("updating nym node config");
info!("nym node config updated: {:?}", res)
}
@@ -0,0 +1,71 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::context::SigningClient;
use anyhow::anyhow;
use clap::Parser;
use cosmwasm_std::Uint128;
use log::info;
use nym_mixnet_contract_common::{
NodeCostParams, Percent, DEFAULT_INTERVAL_OPERATING_COST_AMOUNT, DEFAULT_PROFIT_MARGIN_PERCENT,
};
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, MixnetSigningClient};
use nym_validator_client::nyxd::CosmWasmCoin;
#[derive(Debug, Parser)]
pub struct Args {
#[clap(
long,
help = "input your profit margin as follows; (so it would be 20, rather than 0.2)"
)]
pub profit_margin_percent: Option<u64>,
#[clap(
long,
help = "operating cost in current DENOMINATION (so it would be 'unym', rather than 'nym')"
)]
pub interval_operating_cost: Option<u128>,
}
pub async fn update_cost_params(args: Args, client: SigningClient) -> anyhow::Result<()> {
let denom = client.current_chain_details().mix_denom.base.as_str();
let default_profit_margin =
Percent::from_percentage_value(DEFAULT_PROFIT_MARGIN_PERCENT).unwrap();
let node_details = client
.get_owned_nymnode(&client.address())
.await?
.details
.ok_or_else(|| anyhow!("the client does not own any nodes"))?;
let current_parameters = node_details.rewarding_details.cost_params;
let profit_margin_percent = current_parameters
.map(|rd| rd.cost_params.profit_margin_percent)
.unwrap_or(default_profit_margin);
let profit_margin_value = args
.profit_margin_percent
.map(|pm| Percent::from_percentage_value(pm as u64))
.unwrap_or(profit_margin_percent)?;
let cost_params = NodeCostParams {
profit_margin_percent: profit_margin_value,
interval_operating_cost: CosmWasmCoin {
denom: denom.into(),
amount: Uint128::new(
args.interval_operating_cost
.unwrap_or(DEFAULT_INTERVAL_OPERATING_COST_AMOUNT),
),
},
};
info!("Starting nym node params updating!");
let res = client
.update_cost_params(cost_params, None)
.await
.expect("failed to update cost params");
info!("Cost params result: {:?}", res);
Ok(())
}
@@ -0,0 +1,22 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use clap::Parser;
use log::info;
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
use crate::context::SigningClient;
#[derive(Debug, Parser)]
pub struct Args {}
pub async fn unbond_nymnode(_args: Args, client: SigningClient) {
info!("Starting Nym Node unbonding!");
let res = client
.unbond_nymnode(None)
.await
.expect("failed to unbond Nym Node!");
info!("Unbonding result: {:?}", res)
}
@@ -39,7 +39,7 @@ pub async fn query(args: Args, client: &QueryClientWithNyxd) {
node.owner.to_string(),
node.gateway.host.to_string(),
pretty_cosmwasm_coin(&node.pledge_amount),
node.gateway.version,
node.gateway.version.clone(),
]);
}
@@ -8,7 +8,7 @@ use cosmwasm_std::Uint128;
use serde::de::Error;
use serde::{Deserialize, Deserializer};
use std::fmt::{self, Display, Formatter};
use std::ops::Mul;
use std::ops::{Deref, Mul};
use std::str::FromStr;
use thiserror::Error;
@@ -23,7 +23,7 @@ pub fn truncate_decimal(amount: Decimal) -> Uint128 {
#[derive(Error, Debug)]
pub enum ContractsCommonError {
#[error("Provided percent value ({0}) is greater than 100%")]
InvalidPercent(Decimal),
InvalidPercent(String),
#[error("{source}")]
StdErr {
@@ -41,7 +41,7 @@ pub struct Percent(#[serde(deserialize_with = "de_decimal_percent")] Decimal);
impl Percent {
pub fn new(value: Decimal) -> Result<Self, ContractsCommonError> {
if value > Decimal::one() {
Err(ContractsCommonError::InvalidPercent(value))
Err(ContractsCommonError::InvalidPercent(value.to_string()))
} else {
Ok(Percent(value))
}
@@ -51,11 +51,15 @@ impl Percent {
self.0 == Decimal::zero()
}
pub fn zero() -> Self {
pub fn is_hundred(&self) -> bool {
self == &Self::hundred()
}
pub const fn zero() -> Self {
Self(Decimal::zero())
}
pub fn hundred() -> Self {
pub const fn hundred() -> Self {
Self(Decimal::one())
}
@@ -117,6 +121,70 @@ impl Mul<Uint128> for Percent {
}
}
impl Deref for Percent {
type Target = Decimal;
fn deref(&self) -> &Self::Target {
&self.0
}
}
// this is not implemented via From traits due to its naive nature and loss of precision
#[cfg(not(target_arch = "wasm32"))]
pub trait NaiveFloat {
fn naive_to_f64(&self) -> f64;
fn naive_try_from_f64(val: f64) -> Result<Self, ContractsCommonError>
where
Self: Sized;
}
#[cfg(not(target_arch = "wasm32"))]
impl NaiveFloat for Percent {
fn naive_to_f64(&self) -> f64 {
use cosmwasm_std::Fraction;
// note: this conversion loses precision with too many decimal places,
// but for the purposes of displaying basic performance, that's not an issue
self.numerator().u128() as f64 / self.denominator().u128() as f64
}
fn naive_try_from_f64(val: f64) -> Result<Self, ContractsCommonError>
where
Self: Sized,
{
// we are only interested in positive values between 0 and 1
if !(0. ..=1.).contains(&val) {
return Err(ContractsCommonError::InvalidPercent(val.to_string()));
}
fn gcd(mut x: u64, mut y: u64) -> u64 {
while y > 0 {
let rem = x % y;
x = y;
y = rem;
}
x
}
fn to_rational(x: f64) -> (u64, u64) {
let log = x.log2().floor();
if log >= 0.0 {
(x as u64, 1)
} else {
let num: u64 = (x / f64::EPSILON) as _;
let den: u64 = (1.0 / f64::EPSILON) as _;
let gcd = gcd(num, den);
(num / gcd, den / gcd)
}
}
let (n, d) = to_rational(val);
Percent::new(Decimal::from_ratio(n, d))
}
}
// implement custom Deserialize because we want to validate Percent has the correct range
fn de_decimal_percent<'de, D>(deserializer: D) -> Result<Decimal, D::Error>
where
@@ -204,6 +272,7 @@ impl ContractBuildInformation {
#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::Fraction;
#[test]
fn percent_serde() {
@@ -243,4 +312,19 @@ mod tests {
let p = serde_json::from_str::<'_, Percent>("\"1.00\"").unwrap();
assert_eq!(p.round_to_integer(), 100);
}
#[test]
fn naive_float_conversion() {
// around 15 decimal places is the maximum precision we can handle
// which is still way more than enough for what we use it for
let float: f64 = "0.546295475423853".parse().unwrap();
let percent: Percent = "0.546295475423853".parse().unwrap();
assert_eq!(float, percent.naive_to_f64());
let epsilon = Decimal::from_ratio(1u64, 1000000000000000u64);
let converted = Percent::naive_try_from_f64(float).unwrap();
assert!(converted.0 - converted.0 < epsilon);
}
}
@@ -12,6 +12,7 @@ repository = { workspace = true }
bs58 = { workspace = true }
cosmwasm-std = { workspace = true }
cosmwasm-schema = { workspace = true }
cw-storage-plus.workspace = true
cw-controllers = { workspace = true }
cw2 = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"] }
@@ -5,6 +5,9 @@ use cosmwasm_std::{Decimal, Uint128};
pub const TOKEN_SUPPLY: Uint128 = Uint128::new(1_000_000_000_000_000);
pub const DEFAULT_INTERVAL_OPERATING_COST_AMOUNT: u128 = 40_000_000;
pub const DEFAULT_PROFIT_MARGIN_PERCENT: u64 = 20;
// I'm still not 100% sure how to feel about existence of this file
// This is equivalent of representing our display coin with 6 decimal places.
// I'm using this one as opposed to "Decimal::one()", as this provides us with higher accuracy
@@ -3,14 +3,14 @@
use crate::constants::TOKEN_SUPPLY;
use crate::helpers::IntoBaseDecimal;
use crate::{Addr, MixId};
use crate::{Addr, NodeId};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Coin, Decimal, StdResult};
// just use a string representation of those so that we wouldn't need to bother with decoding bytes
// and trying to figure out whether they're valid, etc
pub type OwnerProxySubKey = String;
pub type StorageKey = (MixId, OwnerProxySubKey);
pub type StorageKey = (NodeId, OwnerProxySubKey);
// throughout the contract we ensure that our proxy can ONLY ever be the vesting contract
// thus this method is equivalent to either using the existing address (for when there's no proxy)
@@ -40,8 +40,9 @@ pub struct Delegation {
/// Address of the owner of this delegation.
pub owner: Addr,
/// Id of the MixNode that this delegation was performed against.
pub mix_id: MixId,
/// Id of the Node that this delegation was performed against.
#[serde(alias = "mix_id")]
pub node_id: NodeId,
// Note to UI/UX devs: there's absolutely no point in displaying this value to the users,
// it would serve them no purpose. It's only used for calculating rewards
@@ -56,12 +57,13 @@ pub struct Delegation {
/// Proxy address used to delegate the funds on behalf of another address
pub proxy: Option<Addr>,
// TODO: perhaps add a field to indicate if it was made against old mixnode with #[serde(default)]?
}
impl Delegation {
pub fn new(
owner: Addr,
mix_id: MixId,
node_id: NodeId,
cumulative_reward_ratio: Decimal,
amount: Coin,
height: u64,
@@ -73,7 +75,7 @@ impl Delegation {
Delegation {
owner,
mix_id,
node_id,
cumulative_reward_ratio,
amount,
height,
@@ -82,7 +84,7 @@ impl Delegation {
}
pub fn generate_storage_key(
mix_id: MixId,
mix_id: NodeId,
owner_address: &Addr,
proxy: Option<&Addr>,
) -> StorageKey {
@@ -92,7 +94,7 @@ impl Delegation {
// this function might seem a bit redundant, but I'd rather explicitly keep it around in case
// some types change in the future
pub fn generate_storage_key_with_subkey(
mix_id: MixId,
mix_id: NodeId,
owner_proxy_subkey: OwnerProxySubKey,
) -> StorageKey {
(mix_id, owner_proxy_subkey)
@@ -107,13 +109,13 @@ impl Delegation {
}
pub fn storage_key(&self) -> StorageKey {
Self::generate_storage_key(self.mix_id, &self.owner, self.proxy.as_ref())
Self::generate_storage_key(self.node_id, &self.owner, self.proxy.as_ref())
}
}
/// Response containing paged list of all delegations made towards particular mixnode.
/// Response containing paged list of all delegations made towards particular node.
#[cw_serde]
pub struct PagedMixNodeDelegationsResponse {
pub struct PagedNodeDelegationsResponse {
/// Each individual delegation made.
pub delegations: Vec<Delegation>,
@@ -121,9 +123,9 @@ pub struct PagedMixNodeDelegationsResponse {
pub start_next_after: Option<OwnerProxySubKey>,
}
impl PagedMixNodeDelegationsResponse {
impl PagedNodeDelegationsResponse {
pub fn new(delegations: Vec<Delegation>, start_next_after: Option<OwnerProxySubKey>) -> Self {
PagedMixNodeDelegationsResponse {
PagedNodeDelegationsResponse {
delegations,
start_next_after,
}
@@ -137,13 +139,13 @@ pub struct PagedDelegatorDelegationsResponse {
pub delegations: Vec<Delegation>,
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
pub start_next_after: Option<(MixId, OwnerProxySubKey)>,
pub start_next_after: Option<(NodeId, OwnerProxySubKey)>,
}
impl PagedDelegatorDelegationsResponse {
pub fn new(
delegations: Vec<Delegation>,
start_next_after: Option<(MixId, OwnerProxySubKey)>,
start_next_after: Option<(NodeId, OwnerProxySubKey)>,
) -> Self {
PagedDelegatorDelegationsResponse {
delegations,
@@ -154,19 +156,24 @@ impl PagedDelegatorDelegationsResponse {
/// Response containing delegation details.
#[cw_serde]
pub struct MixNodeDelegationResponse {
pub struct NodeDelegationResponse {
/// If the delegation exists, this field contains its detailed information.
pub delegation: Option<Delegation>,
/// Flag indicating whether the node towards which the delegation was made is still bonded in the network.
#[deprecated(note = "this field will be removed. use .node_still_bonded instead")]
pub mixnode_still_bonded: bool,
pub node_still_bonded: bool,
}
impl MixNodeDelegationResponse {
pub fn new(delegation: Option<Delegation>, mixnode_still_bonded: bool) -> Self {
MixNodeDelegationResponse {
impl NodeDelegationResponse {
pub fn new(delegation: Option<Delegation>, node_still_bonded: bool) -> Self {
#[allow(deprecated)]
NodeDelegationResponse {
delegation,
mixnode_still_bonded,
mixnode_still_bonded: node_still_bonded,
node_still_bonded,
}
}
}
@@ -1,7 +1,10 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{EpochEventId, EpochState, IdentityKey, MixId, OperatingCostRange, ProfitMarginRange};
use crate::nym_node::Role;
use crate::{
EpochEventId, EpochState, IntervalEventId, NodeId, OperatingCostRange, ProfitMarginRange,
};
use contracts_common::signing::verifier::ApiVerifierError;
use contracts_common::Percent;
use cosmwasm_std::{Addr, Coin, Decimal, Uint128};
@@ -34,6 +37,16 @@ pub enum MixnetContractError {
#[error("Not enough funds sent for node pledge. (received {received}, minimum {minimum})")]
InsufficientPledge { received: Coin, minimum: Coin },
#[error(
"the provided value for node host is too long. it must not be longer than 255 characters"
)]
HostTooLong,
#[error(
"the provided node identity public key is not a correctly encoded base58 slice of 32 bytes"
)]
InvalidPubKey,
#[error("Attempted to reduce node pledge ({current}{denom} - {decrease_by}{denom}) below the minimum amount: {minimum}{denom}")]
InvalidPledgeReduction {
current: Uint128,
@@ -45,11 +58,19 @@ pub enum MixnetContractError {
#[error("A pledge change is already pending in this epoch. The event id: {pending_event_id}")]
PendingPledgeChange { pending_event_id: EpochEventId },
#[error(
"A cost params change is already pending in this epoch. The event id: {pending_event_id}"
)]
PendingParamsChange { pending_event_id: IntervalEventId },
#[error("Not enough funds sent for node delegation. (received {received}, minimum {minimum})")]
InsufficientDelegation { received: Coin, minimum: Coin },
#[error("Node ({node_id}) does not exist")]
NymNodeBondNotFound { node_id: NodeId },
#[error("Mixnode ({mix_id}) does not exist")]
MixNodeBondNotFound { mix_id: MixId },
MixNodeBondNotFound { mix_id: NodeId },
#[error("{owner} does not seem to own any mixnodes")]
NoAssociatedMixNodeBond { owner: Addr },
@@ -57,12 +78,18 @@ pub enum MixnetContractError {
#[error("{owner} does not seem to own any gateways")]
NoAssociatedGatewayBond { owner: Addr },
#[error("{owner} does not seem to own any nodes")]
NoAssociatedNodeBond { owner: Addr },
#[error("This address has already bonded a mixnode")]
AlreadyOwnsMixnode,
#[error("This address has already bonded a gateway")]
AlreadyOwnsGateway,
#[error("This address has already bonded a nym-node")]
AlreadyOwnsNymNode,
#[error("Gateway with this identity already exists. Its owner is {owner}")]
DuplicateGateway { owner: Addr },
@@ -103,32 +130,41 @@ pub enum MixnetContractError {
epoch_end: i64,
},
#[error("Mixnode {mix_id} has already been rewarded during the current rewarding epoch ({absolute_epoch_id})")]
MixnodeAlreadyRewarded {
mix_id: MixId,
#[error("attempted to reward a gateway node - this has not been fully integrated yet")]
GatewayRewarding,
#[error("node {node_id} has already been rewarded during the current rewarding epoch ({absolute_epoch_id})")]
NodeAlreadyRewarded {
node_id: NodeId,
absolute_epoch_id: u32,
},
#[error("node {node_id} hasn't been assigned the role of {role} for this epoch")]
IncorrectEpochRole { node_id: NodeId, role: Role },
#[error("Mixnode {mix_id} hasn't been selected to the rewarding set in this epoch ({absolute_epoch_id})")]
MixnodeNotInRewardedSet {
mix_id: MixId,
mix_id: NodeId,
absolute_epoch_id: u32,
},
#[error("Mixnode {mix_id} is currently in the process of unbonding")]
MixnodeIsUnbonding { mix_id: MixId },
MixnodeIsUnbonding { mix_id: NodeId },
#[error("Node {node_id} is currently in the process of unbonding")]
NodeIsUnbonding { node_id: NodeId },
#[error("Mixnode {mix_id} has already unbonded")]
MixnodeHasUnbonded { mix_id: MixId },
MixnodeHasUnbonded { mix_id: NodeId },
#[error("The contract has ended up in a state that was deemed impossible: {comment}")]
InconsistentState { comment: String },
#[error(
"Could not find any delegation information associated with mixnode {mix_id} for {address} (proxy: {proxy:?})"
"Could not find any delegation information associated with node {node_id} for {address} (proxy: {proxy:?})"
)]
NoMixnodeDelegationFound {
mix_id: MixId,
NodeDelegationNotFound {
node_id: NodeId,
address: String,
proxy: Option<String>,
},
@@ -136,63 +172,18 @@ pub enum MixnetContractError {
#[error("Provided message to update rewarding params did not contain any updates")]
EmptyParamsChangeMsg,
#[error("Provided active set size is bigger than the rewarded set")]
#[error("one of the roles in the new active set is empty")]
EmptyRoleAssignment,
#[error("the number of mixnodes in the rewarded set is not divisible by the number of mix-layers (3)")]
UnevenLayerAssignment,
#[error("provided active set is bigger than the rewarded set")]
InvalidActiveSetSize,
#[error("Provided rewarded set size is smaller than the active set")]
InvalidRewardedSetSize,
#[error("Provided active set size is zero")]
ZeroActiveSet,
#[error("Provided rewarded set size is zero")]
ZeroRewardedSet,
#[error("Received unexpected value for the active set. Got: {received}, expected: {expected}")]
UnexpectedActiveSetSize { received: u32, expected: u32 },
#[error("Received unexpected value for the rewarded set. Got: {received}, expected at most: {expected}")]
UnexpectedRewardedSetSize { received: u32, expected: u32 },
#[error("Mixnode {mix_id} appears multiple times in the provided rewarded set update!")]
DuplicateRewardedSetNode { mix_id: MixId },
#[error("Family with head {head} does not exist!")]
FamilyDoesNotExist { head: String },
#[error("Family with label {label} does not exist!")]
FamilyLabelDoesNotExist { label: String },
#[error("Family with label '{0}' already exists")]
FamilyWithLabelExists(String),
#[error("Invalid layer expected 1, 2 or 3, got {0}")]
InvalidLayer(u8),
#[error("Head already has a family")]
FamilyCanHaveOnlyOne,
#[error("Already member of family {0}")]
AlreadyMemberOfFamily(String),
#[error("Can't join own family, family head {head}, member {member}")]
CantJoinOwnFamily {
head: IdentityKey,
member: IdentityKey,
},
#[error("Can't leave own family, family head {head}, member {member}")]
CantLeaveOwnFamily {
head: IdentityKey,
member: IdentityKey,
},
#[error("{member} is not a member of family {head}")]
NotAMember {
head: IdentityKey,
member: IdentityKey,
},
#[error("Feature is not yet implemented")]
NotImplemented,
@@ -219,13 +210,28 @@ pub enum MixnetContractError {
#[error("attempted to reward mixnode out of order. Attempted to reward {attempted_to_reward} while last rewarded was {last_rewarded}.")]
RewardingOutOfOrder {
last_rewarded: MixId,
attempted_to_reward: MixId,
last_rewarded: NodeId,
attempted_to_reward: NodeId,
},
#[error("the epoch is currently not in the 'event reconciliation' state. (the state is {current_state})")]
EpochNotInEventReconciliationState { current_state: EpochState },
#[error(
"the epoch is currently not in the 'role assignment' state. (the state is {current_state})"
)]
EpochNotInRoleAssignmentState { current_state: EpochState },
#[error("unexpected role assignment. got: {got} while expected: {expected}")]
UnexpectedRoleAssignment { expected: Role, got: Role },
#[error("attempted to assign an invalid number of nodes for a role of {role}. got {assigned}, but the maximum allowed is {allowed}")]
IllegalRoleCount {
role: Role,
assigned: u32,
allowed: u32,
},
#[error("the epoch is currently not in the 'epoch advancement' state. (the state is {current_state})")]
EpochNotInAdvancementState { current_state: EpochState },
@@ -258,6 +264,17 @@ pub enum MixnetContractError {
provided: Uint128,
range: OperatingCostRange,
},
#[error(
"currently it's not possible to migrate nodes bonded with vesting tokens into a nym-node. please perform vesting->liquid migration first."
)]
VestingNodeMigration,
#[error("value {got} does not correspond to any known node role")]
UnknownRoleRepresentation { got: u8 },
#[error("the total work for this epoch seems to be bigger than 1.0!")]
TotalWorkAboveOne,
}
impl MixnetContractError {
@@ -1,11 +1,13 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::MixnetContractError;
use crate::gateway::GatewayConfigUpdate;
use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
use crate::reward_params::{IntervalRewardParams, IntervalRewardingParamsUpdate};
use crate::mixnode::{MixNodeConfigUpdate, NodeCostParams};
use crate::nym_node::Role;
use crate::reward_params::{ActiveSetUpdate, IntervalRewardParams, IntervalRewardingParamsUpdate};
use crate::rewarding::RewardDistribution;
use crate::{BlockHeight, ContractStateParams, IdentityKeyRef, Interval, Layer, MixId};
use crate::{BlockHeight, ContractStateParams, EpochId, IdentityKeyRef, Interval, NodeId};
pub use contracts_common::events::*;
use cosmwasm_std::{Addr, Coin, Decimal, Event};
use std::fmt::Display;
@@ -14,6 +16,11 @@ pub const EVENT_VERSION_PREFIX: &str = "v2_";
pub enum MixnetEventType {
MixnodeBonding,
NymNodeBonding,
NymNodeUnbonding,
PendingNymNodeUnbonding,
GatewayMigration,
MixnodeMigration,
PendingPledgeIncrease,
PledgeIncrease,
PendingPledgeDecrease,
@@ -23,9 +30,9 @@ pub enum MixnetEventType {
PendingMixnodeUnbonding,
MixnodeUnbonding,
MixnodeConfigUpdate,
PendingMixnodeCostParamsUpdate,
MixnodeCostParamsUpdate,
MixnodeRewarding,
PendingCostParamsUpdate,
CostParamsUpdate,
NodeRewarding,
WithdrawDelegatorReward,
WithdrawOperatorReward,
PendingActiveSetUpdate,
@@ -41,6 +48,7 @@ pub enum MixnetEventType {
RewardingValidatorUpdate,
BeginEpochTransition,
AdvanceEpoch,
RoleAssignment,
ExecutePendingEpochEvents,
ExecutePendingIntervalEvents,
ReconcilePendingEvents,
@@ -59,6 +67,11 @@ impl Display for MixnetEventType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let event_name = match self {
MixnetEventType::MixnodeBonding => "mixnode_bonding",
MixnetEventType::NymNodeBonding => "nymnode_bonding",
MixnetEventType::NymNodeUnbonding => "nymnode_unbonding",
MixnetEventType::PendingNymNodeUnbonding => "pending_nymnode_unbonding",
MixnetEventType::GatewayMigration => "gateway_migration",
MixnetEventType::MixnodeMigration => "mixnode_migration",
MixnetEventType::PendingPledgeIncrease => "pending_pledge_increase",
MixnetEventType::PledgeIncrease => "pledge_increase",
MixnetEventType::PendingPledgeDecrease => "pending_pledge_decrease",
@@ -68,9 +81,9 @@ impl Display for MixnetEventType {
MixnetEventType::PendingMixnodeUnbonding => "pending_mixnode_unbonding",
MixnetEventType::MixnodeConfigUpdate => "mixnode_config_update",
MixnetEventType::MixnodeUnbonding => "mixnode_unbonding",
MixnetEventType::PendingMixnodeCostParamsUpdate => "pending_mixnode_cost_params_update",
MixnetEventType::MixnodeCostParamsUpdate => "mixnode_cost_params_update",
MixnetEventType::MixnodeRewarding => "mix_rewarding",
MixnetEventType::PendingCostParamsUpdate => "pending_cost_params_update",
MixnetEventType::CostParamsUpdate => "cost_params_update",
MixnetEventType::NodeRewarding => "node_rewarding",
MixnetEventType::WithdrawDelegatorReward => "withdraw_delegator_reward",
MixnetEventType::WithdrawOperatorReward => "withdraw_operator_reward",
MixnetEventType::PendingActiveSetUpdate => "pending_active_set_update",
@@ -87,6 +100,7 @@ impl Display for MixnetEventType {
MixnetEventType::RewardingValidatorUpdate => "rewarding_validator_address_update",
MixnetEventType::BeginEpochTransition => "beginning_epoch_transition",
MixnetEventType::AdvanceEpoch => "advance_epoch",
MixnetEventType::RoleAssignment => "role_assignment",
MixnetEventType::ExecutePendingEpochEvents => "execute_pending_epoch_events",
MixnetEventType::ExecutePendingIntervalEvents => "execute_pending_interval_events",
MixnetEventType::ReconcilePendingEvents => "reconcile_pending_events",
@@ -103,6 +117,7 @@ impl Display for MixnetEventType {
// attributes that are used in multiple places
pub const OWNER_KEY: &str = "owner";
pub const AMOUNT_KEY: &str = "amount";
pub const ERROR_MESSAGE_KEY: &str = "error_message";
// event-specific attributes
@@ -113,16 +128,14 @@ pub const UNIT_REWARD_KEY: &str = "unit_reward";
// bonding/unbonding
pub const MIX_ID_KEY: &str = "mix_id";
pub const NODE_ID_KEY: &str = "node_id";
pub const NODE_IDENTITY_KEY: &str = "identity";
pub const ASSIGNED_LAYER_KEY: &str = "assigned_layer";
// settings change
pub const OLD_MINIMUM_MIXNODE_PLEDGE_KEY: &str = "old_minimum_mixnode_pledge";
pub const OLD_MINIMUM_GATEWAY_PLEDGE_KEY: &str = "old_minimum_gateway_pledge";
pub const OLD_MINIMUM_PLEDGE_KEY: &str = "old_minimum_pledge";
pub const OLD_MINIMUM_DELEGATION_KEY: &str = "old_minimum_delegation";
pub const NEW_MINIMUM_MIXNODE_PLEDGE_KEY: &str = "new_minimum_mixnode_pledge";
pub const NEW_MINIMUM_GATEWAY_PLEDGE_KEY: &str = "new_minimum_gateway_pledge";
pub const NEW_MINIMUM_PLEDGE_KEY: &str = "new_minimum_pledge";
pub const NEW_MINIMUM_DELEGATION_KEY: &str = "new_minimum_delegation";
pub const OLD_REWARDING_VALIDATOR_ADDRESS_KEY: &str = "old_rewarding_validator_address";
@@ -147,11 +160,16 @@ pub const BOND_NOT_FOUND_VALUE: &str = "bond_not_found";
pub const ZERO_PERFORMANCE_VALUE: &str = "zero_performance";
// rewarded set update
pub const ACTIVE_SET_SIZE_KEY: &str = "active_set_size";
pub const NUM_MIXNODES_KEY: &str = "num_mixnodes";
pub const NUM_ENTRIES_KEY: &str = "num_entry_gateways";
pub const NUM_EXITS_KEY: &str = "num_exit_gateways";
pub const CURRENT_EPOCH_KEY: &str = "current_epoch";
pub const NEW_CURRENT_EPOCH_KEY: &str = "new_current_epoch";
pub const ROLE_KEY: &str = "role";
pub const NODE_COUNT_KEY: &str = "node_count";
// interval
pub const EVENTS_EXECUTED_KEY: &str = "number_of_events_executed";
pub const EVENT_CREATION_HEIGHT_KEY: &str = "created_at";
@@ -163,7 +181,7 @@ pub fn new_delegation_event(
created_at: BlockHeight,
delegator: &Addr,
amount: &Coin,
mix_id: MixId,
mix_id: NodeId,
unit_reward: Decimal,
) -> Event {
Event::new(MixnetEventType::Delegation)
@@ -174,45 +192,57 @@ pub fn new_delegation_event(
.add_attribute(UNIT_REWARD_KEY, unit_reward.to_string())
}
pub fn new_delegation_on_unbonded_node_event(delegator: &Addr, mix_id: MixId) -> Event {
pub fn new_delegation_on_unbonded_node_event(delegator: &Addr, mix_id: NodeId) -> Event {
Event::new(MixnetEventType::Delegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
}
pub fn new_pending_delegation_event(delegator: &Addr, amount: &Coin, mix_id: MixId) -> Event {
pub fn new_pending_delegation_event(delegator: &Addr, amount: &Coin, mix_id: NodeId) -> Event {
Event::new(MixnetEventType::PendingDelegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
}
pub fn new_withdraw_operator_reward_event(owner: &Addr, amount: Coin, mix_id: MixId) -> Event {
pub fn new_withdraw_operator_reward_event(owner: &Addr, amount: Coin, mix_id: NodeId) -> Event {
Event::new(MixnetEventType::WithdrawOperatorReward)
.add_attribute(OWNER_KEY, owner.as_str())
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
}
pub fn new_withdraw_delegator_reward_event(delegator: &Addr, amount: Coin, mix_id: MixId) -> Event {
pub fn new_withdraw_delegator_reward_event(
delegator: &Addr,
amount: Coin,
mix_id: NodeId,
) -> Event {
Event::new(MixnetEventType::WithdrawDelegatorReward)
.add_attribute(DELEGATOR_KEY, delegator)
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
}
pub fn new_active_set_update_event(created_at: BlockHeight, new_size: u32) -> Event {
pub fn new_active_set_update_failure(err: MixnetContractError) -> Event {
Event::new(MixnetEventType::ActiveSetUpdate).add_attribute(ERROR_MESSAGE_KEY, err.to_string())
}
pub fn new_active_set_update_event(created_at: BlockHeight, update: ActiveSetUpdate) -> Event {
Event::new(MixnetEventType::ActiveSetUpdate)
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
.add_attribute(ACTIVE_SET_SIZE_KEY, new_size.to_string())
.add_attribute(NUM_MIXNODES_KEY, update.mixnodes.to_string())
.add_attribute(NUM_ENTRIES_KEY, update.entry_gateways.to_string())
.add_attribute(NUM_EXITS_KEY, update.exit_gateways.to_string())
}
pub fn new_pending_active_set_update_event(
new_size: u32,
update: ActiveSetUpdate,
approximate_time_remaining_secs: i64,
) -> Event {
Event::new(MixnetEventType::PendingActiveSetUpdate)
.add_attribute(ACTIVE_SET_SIZE_KEY, new_size.to_string())
.add_attribute(NUM_MIXNODES_KEY, update.mixnodes.to_string())
.add_attribute(NUM_ENTRIES_KEY, update.entry_gateways.to_string())
.add_attribute(NUM_EXITS_KEY, update.exit_gateways.to_string())
.add_attribute(
APPROXIMATE_TIME_LEFT_SECS_KEY,
approximate_time_remaining_secs.to_string(),
@@ -221,7 +251,6 @@ pub fn new_pending_active_set_update_event(
pub fn new_rewarding_params_update_event(
created_at: BlockHeight,
update: IntervalRewardingParamsUpdate,
updated: IntervalRewardParams,
) -> Event {
@@ -252,30 +281,19 @@ pub fn new_pending_rewarding_params_update_event(
)
}
pub fn new_undelegation_event(created_at: BlockHeight, delegator: &Addr, mix_id: MixId) -> Event {
pub fn new_undelegation_event(created_at: BlockHeight, delegator: &Addr, mix_id: NodeId) -> Event {
Event::new(MixnetEventType::Undelegation)
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
.add_attribute(DELEGATOR_KEY, delegator)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
}
pub fn new_pending_undelegation_event(delegator: &Addr, mix_id: MixId) -> Event {
pub fn new_pending_undelegation_event(delegator: &Addr, mix_id: NodeId) -> Event {
Event::new(MixnetEventType::PendingUndelegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
}
pub fn new_gateway_bonding_event(
owner: &Addr,
amount: &Coin,
identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(MixnetEventType::GatewayBonding)
.add_attribute(OWNER_KEY, owner)
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_gateway_unbonding_event(
owner: &Addr,
amount: &Coin,
@@ -287,49 +305,85 @@ pub fn new_gateway_unbonding_event(
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_mixnode_bonding_event(
pub fn new_nym_node_bonding_event(
owner: &Addr,
amount: &Coin,
identity: IdentityKeyRef<'_>,
mix_id: MixId,
assigned_layer: Layer,
node_id: NodeId,
) -> Event {
// coin implements Display trait and we use that implementation here
Event::new(MixnetEventType::MixnodeBonding)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
Event::new(MixnetEventType::NymNodeBonding)
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(OWNER_KEY, owner)
.add_attribute(ASSIGNED_LAYER_KEY, assigned_layer)
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_pending_pledge_increase_event(mix_id: MixId, amount: &Coin) -> Event {
pub fn new_nym_node_unbonding_event(created_at: BlockHeight, node_id: NodeId) -> Event {
Event::new(MixnetEventType::NymNodeUnbonding)
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
.add_attribute(NODE_ID_KEY, node_id.to_string())
}
pub fn new_pending_nym_node_unbonding_event(
owner: &Addr,
identity: IdentityKeyRef<'_>,
node_id: NodeId,
) -> Event {
Event::new(MixnetEventType::PendingNymNodeUnbonding)
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(OWNER_KEY, owner)
}
pub fn new_migrated_gateway_event(
owner: &Addr,
identity: IdentityKeyRef<'_>,
node_id: NodeId,
) -> Event {
Event::new(MixnetEventType::GatewayMigration)
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(OWNER_KEY, owner)
}
pub fn new_migrated_mixnode_event(
owner: &Addr,
identity: IdentityKeyRef<'_>,
node_id: NodeId,
) -> Event {
Event::new(MixnetEventType::MixnodeMigration)
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(OWNER_KEY, owner)
}
pub fn new_pending_pledge_increase_event(node_id: NodeId, amount: &Coin) -> Event {
Event::new(MixnetEventType::PendingPledgeIncrease)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_pledge_increase_event(created_at: BlockHeight, mix_id: MixId, amount: &Coin) -> Event {
pub fn new_pledge_increase_event(created_at: BlockHeight, node_id: NodeId, amount: &Coin) -> Event {
Event::new(MixnetEventType::PledgeIncrease)
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_pending_pledge_decrease_event(mix_id: MixId, amount: &Coin) -> Event {
pub fn new_pending_pledge_decrease_event(node_id: NodeId, amount: &Coin) -> Event {
Event::new(MixnetEventType::PendingPledgeDecrease)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_pledge_decrease_event(created_at: BlockHeight, mix_id: MixId, amount: &Coin) -> Event {
pub fn new_pledge_decrease_event(created_at: BlockHeight, node_id: NodeId, amount: &Coin) -> Event {
Event::new(MixnetEventType::PledgeDecrease)
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_mixnode_unbonding_event(created_at: BlockHeight, mix_id: MixId) -> Event {
pub fn new_mixnode_unbonding_event(created_at: BlockHeight, mix_id: NodeId) -> Event {
Event::new(MixnetEventType::MixnodeUnbonding)
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
@@ -338,7 +392,7 @@ pub fn new_mixnode_unbonding_event(created_at: BlockHeight, mix_id: MixId) -> Ev
pub fn new_pending_mixnode_unbonding_event(
owner: &Addr,
identity: IdentityKeyRef<'_>,
mix_id: MixId,
mix_id: NodeId,
) -> Event {
Event::new(MixnetEventType::PendingMixnodeUnbonding)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
@@ -347,7 +401,7 @@ pub fn new_pending_mixnode_unbonding_event(
}
pub fn new_mixnode_config_update_event(
mix_id: MixId,
mix_id: NodeId,
owner: &Addr,
update: &MixNodeConfigUpdate,
) -> Event {
@@ -363,23 +417,18 @@ pub fn new_gateway_config_update_event(owner: &Addr, update: &GatewayConfigUpdat
.add_attribute(UPDATED_GATEWAY_CONFIG_KEY, update.to_inline_json())
}
pub fn new_mixnode_pending_cost_params_update_event(
mix_id: MixId,
owner: &Addr,
new_costs: &MixNodeCostParams,
) -> Event {
Event::new(MixnetEventType::PendingMixnodeCostParamsUpdate)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(OWNER_KEY, owner)
pub fn new_pending_cost_params_update_event(mix_id: NodeId, new_costs: &NodeCostParams) -> Event {
Event::new(MixnetEventType::PendingCostParamsUpdate)
.add_attribute(NODE_ID_KEY, mix_id.to_string())
.add_attribute(UPDATED_MIXNODE_COST_PARAMS_KEY, new_costs.to_inline_json())
}
pub fn new_mixnode_cost_params_update_event(
pub fn new_cost_params_update_event(
created_at: BlockHeight,
mix_id: MixId,
new_costs: &MixNodeCostParams,
mix_id: NodeId,
new_costs: &NodeCostParams,
) -> Event {
Event::new(MixnetEventType::MixnodeCostParamsUpdate)
Event::new(MixnetEventType::CostParamsUpdate)
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(UPDATED_MIXNODE_COST_PARAMS_KEY, new_costs.to_inline_json())
@@ -397,37 +446,25 @@ pub fn new_settings_update_event(
) -> Event {
let mut event = Event::new(MixnetEventType::ContractSettingsUpdate);
if old_params.minimum_mixnode_pledge != new_params.minimum_mixnode_pledge {
if old_params.minimum_pledge != new_params.minimum_pledge {
event = event
.add_attribute(
OLD_MINIMUM_MIXNODE_PLEDGE_KEY,
old_params.minimum_mixnode_pledge.to_string(),
OLD_MINIMUM_PLEDGE_KEY,
old_params.minimum_pledge.to_string(),
)
.add_attribute(
NEW_MINIMUM_MIXNODE_PLEDGE_KEY,
new_params.minimum_mixnode_pledge.to_string(),
NEW_MINIMUM_PLEDGE_KEY,
new_params.minimum_pledge.to_string(),
)
}
if old_params.minimum_gateway_pledge != new_params.minimum_gateway_pledge {
event = event
.add_attribute(
OLD_MINIMUM_GATEWAY_PLEDGE_KEY,
old_params.minimum_gateway_pledge.to_string(),
)
.add_attribute(
NEW_MINIMUM_GATEWAY_PLEDGE_KEY,
new_params.minimum_gateway_pledge.to_string(),
)
}
if old_params.minimum_mixnode_delegation != new_params.minimum_mixnode_delegation {
if let Some(ref old) = old_params.minimum_mixnode_delegation {
if old_params.minimum_delegation != new_params.minimum_delegation {
if let Some(ref old) = old_params.minimum_delegation {
event = event.add_attribute(OLD_MINIMUM_DELEGATION_KEY, old.to_string())
} else {
event = event.add_attribute(OLD_MINIMUM_DELEGATION_KEY, "None")
}
if let Some(ref new) = new_params.minimum_mixnode_delegation {
if let Some(ref new) = new_params.minimum_delegation {
event = event.add_attribute(NEW_MINIMUM_DELEGATION_KEY, new.to_string())
} else {
event = event.add_attribute(NEW_MINIMUM_DELEGATION_KEY, "None")
@@ -437,41 +474,41 @@ pub fn new_settings_update_event(
event
}
pub fn new_not_found_mix_operator_rewarding_event(interval: Interval, mix_id: MixId) -> Event {
Event::new(MixnetEventType::MixnodeRewarding)
pub fn new_not_found_node_operator_rewarding_event(interval: Interval, node_id: NodeId) -> Event {
Event::new(MixnetEventType::NodeRewarding)
.add_attribute(
INTERVAL_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(NO_REWARD_REASON_KEY, BOND_NOT_FOUND_VALUE)
}
pub fn new_zero_uptime_mix_operator_rewarding_event(interval: Interval, mix_id: MixId) -> Event {
Event::new(MixnetEventType::MixnodeRewarding)
pub fn new_zero_uptime_mix_operator_rewarding_event(interval: Interval, node_id: NodeId) -> Event {
Event::new(MixnetEventType::NodeRewarding)
.add_attribute(
INTERVAL_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(NO_REWARD_REASON_KEY, ZERO_PERFORMANCE_VALUE)
}
pub fn new_mix_rewarding_event(
interval: Interval,
mix_id: MixId,
node_id: NodeId,
reward_distribution: RewardDistribution,
prior_delegates: Decimal,
prior_unit_reward: Decimal,
) -> Event {
Event::new(MixnetEventType::MixnodeRewarding)
Event::new(MixnetEventType::NodeRewarding)
.add_attribute(
INTERVAL_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(PRIOR_DELEGATES_KEY, prior_delegates.to_string())
.add_attribute(PRIOR_UNIT_REWARD_KEY, prior_unit_reward.to_string())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_ID_KEY, node_id.to_string())
.add_attribute(
OPERATOR_REWARD_KEY,
reward_distribution.operator.to_string(),
@@ -489,13 +526,15 @@ pub fn new_epoch_transition_start_event(current_interval: Interval) -> Event {
)
}
pub fn new_advance_epoch_event(interval: Interval, rewarded_nodes: u32) -> Event {
pub fn new_assigned_role_event(role: Role, nodes: u32) -> Event {
Event::new(MixnetEventType::RoleAssignment)
.add_attribute(ROLE_KEY, role.to_string())
.add_attribute(NODE_COUNT_KEY, nodes.to_string())
}
pub fn new_advance_epoch_event(epoch_id: EpochId) -> Event {
Event::new(MixnetEventType::AdvanceEpoch)
.add_attribute(
NEW_CURRENT_EPOCH_KEY,
interval.current_epoch_absolute_id().to_string(),
)
.add_attribute(REWARDED_SET_NODES_KEY, rewarded_nodes.to_string())
.add_attribute(NEW_CURRENT_EPOCH_KEY, epoch_id.to_string())
}
pub fn new_pending_epoch_events_execution_event(executed: u32) -> Event {
@@ -1,189 +0,0 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{IdentityKey, IdentityKeyRef};
use cosmwasm_schema::cw_serde;
use schemars::JsonSchema;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{Display, Formatter};
use std::str::FromStr;
/// A group of mixnodes associated with particular staking entity.
/// When defined all nodes belonging to the same family will be prioritised to be put onto the same layer.
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/NodeFamily.ts")
)]
#[cw_serde]
pub struct Family {
/// Owner of this family.
head: FamilyHead,
/// Optional proxy (i.e. vesting contract address) used when creating the family.
proxy: Option<String>,
/// Human readable label for this family.
label: String,
}
/// Head of particular family as identified by its identity key (i.e. public component of its ed25519 keypair stringified into base58).
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/NodeFamilyHead.ts")
)]
#[derive(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<S: Into<String>>(identity: S) -> Self {
FamilyHead(identity.into())
}
pub fn identity(&self) -> IdentityKeyRef<'_> {
&self.0
}
}
impl Family {
pub fn new(head: FamilyHead, label: String) -> Self {
Family {
head,
proxy: None,
label,
}
}
#[allow(dead_code)]
pub fn head(&self) -> &FamilyHead {
&self.head
}
pub fn head_identity(&self) -> IdentityKeyRef<'_> {
self.head.identity()
}
#[allow(dead_code)]
pub fn proxy(&self) -> Option<&String> {
self.proxy.as_ref()
}
pub fn label(&self) -> &str {
&self.label
}
}
/// Response containing paged list of all families registered in the contract.
#[cw_serde]
pub struct PagedFamiliesResponse {
/// The families registered in the contract.
pub families: Vec<Family>,
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
pub start_next_after: Option<String>,
}
/// Response containing paged list of all family members (of ALL families) registered in the contract.
#[cw_serde]
pub struct PagedMembersResponse {
/// The members alongside their family heads.
pub members: Vec<(IdentityKey, FamilyHead)>,
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
pub start_next_after: Option<String>,
}
/// Response containing family information.
#[cw_serde]
pub struct FamilyByHeadResponse {
/// The family head used for the query.
pub head: FamilyHead,
/// If applicable, the family associated with the provided head.
pub family: Option<Family>,
}
/// Response containing family information.
#[cw_serde]
pub struct FamilyByLabelResponse {
/// The family label used for the query.
pub label: String,
/// If applicable, the family associated with the provided label.
pub family: Option<Family>,
}
/// Response containing family members information.
#[cw_serde]
pub struct FamilyMembersByHeadResponse {
/// The family head used for the query.
pub head: FamilyHead,
/// All members belonging to the specified family.
pub members: Vec<IdentityKey>,
}
/// Response containing family members information.
#[cw_serde]
pub struct FamilyMembersByLabelResponse {
/// The family label used for the query.
pub label: String,
/// All members belonging to the specified family.
pub members: Vec<IdentityKey>,
}
#[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);
}
}
@@ -1,7 +1,7 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{IdentityKey, SphinxKey};
use crate::{IdentityKey, NodeId, SphinxKey};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, Coin};
use std::cmp::Ordering;
@@ -200,6 +200,23 @@ pub struct GatewayBondResponse {
pub gateway: Option<GatewayBond>,
}
#[cw_serde]
pub struct PreassignedId {
/// The identity key (base58-encoded ed25519 public key) of the gateway.
pub identity: IdentityKey,
/// The id pre-assigned to this gateway
pub node_id: NodeId,
}
#[cw_serde]
pub struct PreassignedGatewayIdsResponse {
pub ids: Vec<PreassignedId>,
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
pub start_next_after: Option<IdentityKey>,
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1,7 +1,14 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{Decimal, StdError, StdResult, Uint128};
use crate::error::MixnetContractError;
use crate::mixnode::PendingMixNodeChanges;
use crate::{
EpochEventId, IntervalEventId, MixNodeBond, MixNodeDetails, NodeId, NodeRewarding, NymNodeBond,
NymNodeDetails, PendingNodeChanges,
};
use contracts_common::IdentityKeyRef;
use cosmwasm_std::{Coin, Decimal, StdError, StdResult, Uint128};
pub fn compare_decimals(a: Decimal, b: Decimal, epsilon: Option<Decimal>) {
let epsilon = epsilon.unwrap_or_else(|| Decimal::from_ratio(1u128, 100_000_000u128));
@@ -31,3 +38,158 @@ where
})
}
}
pub trait NodeDetails {
type Bond: NodeBond;
type PendingChanges: PendingChanges;
fn split(self) -> (Self::Bond, NodeRewarding, Self::PendingChanges);
fn rewarding_info(&self) -> &NodeRewarding;
fn bond_info(&self) -> &Self::Bond;
fn pending_changes(&self) -> &Self::PendingChanges;
}
pub trait NodeBond {
fn node_id(&self) -> NodeId;
fn is_unbonding(&self) -> bool;
fn identity(&self) -> IdentityKeyRef;
fn original_pledge(&self) -> &Coin;
fn ensure_bonded(&self) -> Result<(), MixnetContractError> {
if self.is_unbonding() {
return Err(MixnetContractError::NodeIsUnbonding {
node_id: self.node_id(),
});
}
Ok(())
}
}
pub trait PendingChanges {
fn pending_pledge_changes(&self) -> Option<EpochEventId>;
fn pending_cost_params_changes(&self) -> Option<IntervalEventId>;
fn ensure_no_pending_pledge_changes(&self) -> Result<(), MixnetContractError> {
if let Some(pending_event_id) = self.pending_pledge_changes() {
return Err(MixnetContractError::PendingPledgeChange { pending_event_id });
}
Ok(())
}
fn ensure_no_pending_params_changes(&self) -> Result<(), MixnetContractError> {
if let Some(pending_event_id) = self.pending_cost_params_changes() {
return Err(MixnetContractError::PendingParamsChange { pending_event_id });
}
Ok(())
}
}
impl NodeDetails for MixNodeDetails {
type Bond = MixNodeBond;
type PendingChanges = PendingMixNodeChanges;
fn split(self) -> (Self::Bond, NodeRewarding, Self::PendingChanges) {
(
self.bond_information,
self.rewarding_details,
self.pending_changes,
)
}
fn rewarding_info(&self) -> &NodeRewarding {
&self.rewarding_details
}
fn bond_info(&self) -> &Self::Bond {
&self.bond_information
}
fn pending_changes(&self) -> &Self::PendingChanges {
&self.pending_changes
}
}
impl NodeBond for MixNodeBond {
fn node_id(&self) -> NodeId {
self.mix_id
}
fn is_unbonding(&self) -> bool {
self.is_unbonding
}
fn identity(&self) -> IdentityKeyRef {
self.identity()
}
fn original_pledge(&self) -> &Coin {
self.original_pledge()
}
}
impl PendingChanges for PendingMixNodeChanges {
fn pending_pledge_changes(&self) -> Option<EpochEventId> {
self.pledge_change
}
fn pending_cost_params_changes(&self) -> Option<IntervalEventId> {
self.cost_params_change
}
}
impl NodeDetails for NymNodeDetails {
type Bond = NymNodeBond;
type PendingChanges = PendingNodeChanges;
fn split(self) -> (Self::Bond, NodeRewarding, Self::PendingChanges) {
(
self.bond_information,
self.rewarding_details,
self.pending_changes,
)
}
fn rewarding_info(&self) -> &NodeRewarding {
&self.rewarding_details
}
fn bond_info(&self) -> &Self::Bond {
&self.bond_information
}
fn pending_changes(&self) -> &Self::PendingChanges {
&self.pending_changes
}
}
impl NodeBond for NymNodeBond {
fn node_id(&self) -> NodeId {
self.node_id
}
fn is_unbonding(&self) -> bool {
self.is_unbonding
}
fn identity(&self) -> IdentityKeyRef {
self.identity()
}
fn original_pledge(&self) -> &Coin {
&self.original_pledge
}
}
impl PendingChanges for PendingNodeChanges {
fn pending_pledge_changes(&self) -> Option<EpochEventId> {
self.pledge_change
}
fn pending_cost_params_changes(&self) -> Option<IntervalEventId> {
self.cost_params_change
}
}
@@ -2,7 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use crate::error::MixnetContractError;
use crate::MixId;
use crate::nym_node::Role;
use crate::NodeId;
use cosmwasm_schema::cw_serde;
use cosmwasm_schema::schemars::gen::SchemaGenerator;
use cosmwasm_schema::schemars::schema::{InstanceType, Schema, SchemaObject};
@@ -86,7 +87,7 @@ impl EpochStatus {
pub fn update_last_rewarded(
&mut self,
new_last_rewarded: MixId,
new_last_rewarded: NodeId,
) -> Result<bool, MixnetContractError> {
match &mut self.state {
EpochState::Rewarding {
@@ -109,7 +110,7 @@ impl EpochStatus {
}
}
pub fn last_rewarded(&self) -> Result<MixId, MixnetContractError> {
pub fn last_rewarded(&self) -> Result<NodeId, MixnetContractError> {
match self.state {
EpochState::Rewarding { last_rewarded, .. } => Ok(last_rewarded),
state => Err(MixnetContractError::UnexpectedNonRewardingEpochState {
@@ -127,12 +128,23 @@ impl EpochStatus {
Ok(())
}
pub fn ensure_is_in_advancement_state(&self) -> Result<(), MixnetContractError> {
if !matches!(self.state, EpochState::AdvancingEpoch) {
return Err(MixnetContractError::EpochNotInAdvancementState {
pub fn ensure_is_in_expected_role_assignment_state(
&self,
caller: Role,
) -> Result<(), MixnetContractError> {
let EpochState::RoleAssignment { next } = self.state else {
return Err(MixnetContractError::EpochNotInRoleAssignmentState {
current_state: self.state,
});
};
if caller != next {
return Err(MixnetContractError::UnexpectedRoleAssignment {
expected: next,
got: caller,
});
}
Ok(())
}
@@ -147,10 +159,6 @@ impl EpochStatus {
pub fn is_reconciling(&self) -> bool {
matches!(self.state, EpochState::ReconcilingEvents)
}
pub fn is_advancing(&self) -> bool {
matches!(self.state, EpochState::AdvancingEpoch)
}
}
/// The state of the current rewarding epoch.
@@ -167,10 +175,10 @@ pub enum EpochState {
#[serde(alias = "Rewarding")]
Rewarding {
/// The id of the last node that has already received its rewards.
last_rewarded: MixId,
last_rewarded: NodeId,
/// The id of the last node that's going to be rewarded before progressing into the next state.
final_node_id: MixId,
final_node_id: NodeId,
// total_rewarded: u32,
},
@@ -179,10 +187,9 @@ pub enum EpochState {
#[serde(alias = "ReconcilingEvents")]
ReconcilingEvents,
/// Represents the state of an epoch when all mixnodes have already been rewarded for their work in this epoch,
/// all issued actions got resolved and the epoch should now be advanced whilst assigning new rewarded set.
#[serde(alias = "AdvancingEpoch")]
AdvancingEpoch,
/// Represents the state of an epoch when all nodes have already been rewarded for their work in this epoch,
/// all issued actions got resolved and node roles should now be assigned before advancing into the next epoch.
RoleAssignment { next: Role },
}
impl Display for EpochState {
@@ -197,7 +204,9 @@ impl Display for EpochState {
"mix rewarding (last rewarded: {last_rewarded}, final node: {final_node_id})"
),
EpochState::ReconcilingEvents => write!(f, "event reconciliation"),
EpochState::AdvancingEpoch => write!(f, "advancing epoch"),
EpochState::RoleAssignment { next } => {
write!(f, "role assignment with next assignment for: {next}")
}
}
}
}
@@ -359,6 +368,17 @@ impl Interval {
self.current_epoch_end_unix_timestamp() <= env.block.time.seconds() as i64
}
pub fn ensure_current_epoch_is_over(&self, env: &Env) -> Result<(), MixnetContractError> {
if !self.is_current_epoch_over(&env) {
return Err(MixnetContractError::EpochInProgress {
current_block_time: env.block.time.seconds(),
epoch_start: self.current_epoch_start_unix_timestamp(),
epoch_end: self.current_epoch_end_unix_timestamp(),
});
}
Ok(())
}
pub fn secs_until_current_epoch_end(&self, env: &Env) -> i64 {
if self.is_current_epoch_over(env) {
0
@@ -3,32 +3,30 @@
#![warn(clippy::expect_used)]
#![warn(clippy::unwrap_used)]
#![warn(clippy::todo)]
mod constants;
pub mod constants;
pub mod delegation;
pub mod error;
pub mod events;
pub mod families;
pub mod gateway;
pub mod helpers;
pub mod interval;
pub mod mixnode;
pub mod msg;
pub mod nym_node;
pub mod pending_events;
pub mod reward_params;
pub mod rewarding;
pub mod signing_types;
pub mod types;
pub use constants::*;
pub use contracts_common::types::*;
pub use cosmwasm_std::{Addr, Coin, Decimal, Fraction};
pub use delegation::{
Delegation, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse,
PagedMixNodeDelegationsResponse,
};
pub use families::{
Family, FamilyByHeadResponse, FamilyByLabelResponse, FamilyHead, FamilyMembersByHeadResponse,
FamilyMembersByLabelResponse, PagedFamiliesResponse, PagedMembersResponse,
PagedNodeDelegationsResponse,
};
pub use gateway::{
Gateway, GatewayBond, GatewayBondResponse, GatewayConfigUpdate, GatewayOwnershipResponse,
@@ -38,11 +36,12 @@ pub use interval::{
CurrentIntervalResponse, EpochId, EpochState, EpochStatus, Interval, IntervalId,
};
pub use mixnode::{
Layer, MixNode, MixNodeBond, MixNodeConfigUpdate, MixNodeCostParams, MixNodeDetails,
MixNodeRewarding, MixOwnershipResponse, MixnodeDetailsByIdentityResponse,
MixnodeDetailsResponse, PagedMixnodeBondsResponse, RewardedSetNodeStatus, UnbondedMixnode,
LegacyMixLayer, MixNode, MixNodeBond, MixNodeConfigUpdate, MixNodeDetails,
MixOwnershipResponse, MixnodeDetailsByIdentityResponse, MixnodeDetailsResponse, NodeCostParams,
NodeRewarding, PagedMixnodeBondsResponse, UnbondedMixnode,
};
pub use msg::*;
pub use nym_node::{NymNode, NymNodeBond, NymNodeDetails, PendingNodeChanges};
pub use pending_events::{
EpochEventId, IntervalEventId, NumberOfPendingEventsResponse, PendingEpochEvent,
PendingEpochEventData, PendingEpochEventKind, PendingEpochEventResponse,
@@ -50,8 +49,6 @@ pub use pending_events::{
PendingIntervalEventKind, PendingIntervalEventResponse, PendingIntervalEventsResponse,
};
pub use reward_params::{IntervalRewardParams, IntervalRewardingParamsUpdate, RewardingParams};
pub use rewarding::{
EstimatedCurrentEpochRewardResponse, PagedRewardedSetResponse, PendingRewardResponse,
};
pub use rewarding::{EstimatedCurrentEpochRewardResponse, PendingRewardResponse};
pub use signing_types::*;
pub use types::*;
@@ -7,42 +7,18 @@
use crate::constants::{TOKEN_SUPPLY, UNIT_DELEGATION_BASE};
use crate::error::MixnetContractError;
use crate::helpers::IntoBaseDecimal;
use crate::reward_params::{NodeRewardParams, RewardingParams};
use crate::reward_params::{NodeRewardingParameters, RewardingParams};
use crate::rewarding::helpers::truncate_reward;
use crate::rewarding::RewardDistribution;
use crate::{
Delegation, EpochEventId, EpochId, IdentityKey, MixId, OperatingCostRange, Percent,
ProfitMarginRange, SphinxKey,
Delegation, EpochEventId, EpochId, IdentityKey, IntervalEventId, NodeId, OperatingCostRange,
Percent, ProfitMarginRange, SphinxKey,
};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, Coin, Decimal, StdResult, Uint128};
use schemars::JsonSchema;
use serde_repr::{Deserialize_repr, Serialize_repr};
/// Current state of given node in the rewarded set.
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/RewardedSetNodeStatus.ts")
)]
#[cw_serde]
#[derive(Copy)]
pub enum RewardedSetNodeStatus {
/// Node that is currently active, i.e. is expected to be used by clients for mixing packets.
#[serde(alias = "Active")]
Active,
/// Node that is currently in standby, i.e. it's present in the rewarded set but is not active.
#[serde(alias = "Standby")]
Standby,
}
impl RewardedSetNodeStatus {
pub fn is_active(&self) -> bool {
matches!(self, RewardedSetNodeStatus::Active)
}
}
/// Full details associated with given mixnode.
#[cw_serde]
pub struct MixNodeDetails {
@@ -50,7 +26,7 @@ pub struct MixNodeDetails {
pub bond_information: MixNodeBond,
/// Details used for computation of rewarding related data.
pub rewarding_details: MixNodeRewarding,
pub rewarding_details: NodeRewarding,
/// Adjustments to the mixnode that are ought to happen during future epoch transitions.
#[serde(default)]
@@ -60,7 +36,7 @@ pub struct MixNodeDetails {
impl MixNodeDetails {
pub fn new(
bond_information: MixNodeBond,
rewarding_details: MixNodeRewarding,
rewarding_details: NodeRewarding,
pending_changes: PendingMixNodeChanges,
) -> Self {
MixNodeDetails {
@@ -70,14 +46,10 @@ impl MixNodeDetails {
}
}
pub fn mix_id(&self) -> MixId {
pub fn mix_id(&self) -> NodeId {
self.bond_information.mix_id
}
pub fn layer(&self) -> Layer {
self.bond_information.layer
}
pub fn is_unbonding(&self) -> bool {
self.bond_information.is_unbonding
}
@@ -106,10 +78,11 @@ impl MixNodeDetails {
}
}
// currently this struct is shared between mixnodes and nymnodes
#[cw_serde]
pub struct MixNodeRewarding {
pub struct NodeRewarding {
/// Information provided by the operator that influence the cost function.
pub cost_params: MixNodeCostParams,
pub cost_params: NodeCostParams,
/// Total pledge and compounded reward earned by the node operator.
pub operator: Decimal,
@@ -120,7 +93,7 @@ pub struct MixNodeRewarding {
/// Cumulative reward earned by the "unit delegation" since the block 0.
pub total_unit_reward: Decimal,
/// Value of the theoretical "unit delegation" that has delegated to this mixnode at block 0.
/// Value of the theoretical "unit delegation" that has delegated to this node at block 0.
pub unit_delegation: Decimal,
/// Marks the epoch when this node was last rewarded so that we wouldn't accidentally attempt
@@ -133,9 +106,9 @@ pub struct MixNodeRewarding {
pub unique_delegations: u32,
}
impl MixNodeRewarding {
impl NodeRewarding {
pub fn initialise_new(
cost_params: MixNodeCostParams,
cost_params: NodeCostParams,
initial_pledge: &Coin,
current_epoch: EpochId,
) -> Result<Self, MixnetContractError> {
@@ -144,7 +117,7 @@ impl MixNodeRewarding {
"pledge cannot be larger than the token supply"
);
Ok(MixNodeRewarding {
Ok(NodeRewarding {
cost_params,
operator: initial_pledge.amount.into_base_decimal()?,
delegates: Decimal::zero(),
@@ -155,6 +128,15 @@ impl MixNodeRewarding {
})
}
pub fn normalise_cost_function(
&mut self,
allowed_profit_margin: ProfitMarginRange,
allowed_operating_cost: OperatingCostRange,
) {
self.normalise_profit_margin(allowed_profit_margin);
self.normalise_operating_cost(allowed_operating_cost)
}
pub fn normalise_profit_margin(&mut self, allowed_range: ProfitMarginRange) {
self.cost_params.profit_margin_percent =
allowed_range.normalise(self.cost_params.profit_margin_percent)
@@ -257,23 +239,18 @@ impl MixNodeRewarding {
pub fn node_reward(
&self,
reward_params: &RewardingParams,
node_params: NodeRewardParams,
global_params: &RewardingParams,
node_params: NodeRewardingParameters,
) -> Decimal {
let work = if node_params.in_active_set {
reward_params.active_node_work()
} else {
reward_params.standby_node_work()
};
let work = node_params.work_factor;
let alpha = global_params.interval.sybil_resistance;
let alpha = reward_params.interval.sybil_resistance;
reward_params.interval.epoch_reward_budget
* node_params.performance.value()
* self.bond_saturation(reward_params)
global_params.interval.epoch_reward_budget
* node_params.performance
* self.bond_saturation(global_params)
* (work
+ alpha.value() * self.pledge_saturation(reward_params)
/ reward_params.dec_rewarded_set_size())
+ alpha.value() * self.pledge_saturation(global_params)
/ global_params.dec_rewarded_set_size())
/ (Decimal::one() + alpha.value())
}
@@ -285,7 +262,7 @@ impl MixNodeRewarding {
epochs_in_interval: u32,
) -> RewardDistribution {
let node_cost =
self.cost_params.epoch_operating_cost(epochs_in_interval) * node_performance.value();
self.cost_params.epoch_operating_cost(epochs_in_interval) * node_performance;
// check if profit is positive
if node_reward > node_cost {
@@ -315,7 +292,7 @@ impl MixNodeRewarding {
pub fn calculate_epoch_reward(
&self,
reward_params: &RewardingParams,
node_params: NodeRewardParams,
node_params: NodeRewardingParameters,
epochs_in_interval: u32,
) -> RewardDistribution {
let node_reward = self.node_reward(reward_params, node_params);
@@ -341,7 +318,7 @@ impl MixNodeRewarding {
pub fn epoch_rewarding(
&mut self,
reward_params: &RewardingParams,
node_params: NodeRewardParams,
node_params: NodeRewardingParameters,
epochs_in_interval: u32,
absolute_epoch_id: EpochId,
) {
@@ -492,13 +469,30 @@ impl MixNodeRewarding {
amount / self.delegates
}
}
/// Returns a copy of `Self` with zeroed operator value
pub fn clear_operator(&self) -> NodeRewarding {
let mut zeroed = self.clone();
zeroed.operator = Decimal::zero();
zeroed
}
}
/// Basic mixnode information provided by the node operator.
#[cw_serde]
// note: we had to remove `#[cw_serde]` as it enforces `#[serde(deny_unknown_fields)]` which we do not want
// with the removal of explicit .layer field
#[derive(
::cosmwasm_schema::serde::Serialize,
::cosmwasm_schema::serde::Deserialize,
::std::clone::Clone,
::std::fmt::Debug,
::std::cmp::PartialEq,
::cosmwasm_schema::schemars::JsonSchema,
)]
#[schemars(crate = "::cosmwasm_schema::schemars")]
pub struct MixNodeBond {
/// Unique id assigned to the bonded mixnode.
pub mix_id: MixId,
pub mix_id: NodeId,
/// Address of the owner of this mixnode.
pub owner: Addr,
@@ -506,9 +500,9 @@ pub struct MixNodeBond {
/// Original amount pledged by the operator of this node.
pub original_pledge: Coin,
/// Layer assigned to this mixnode.
pub layer: Layer,
// REMOVED (but might be needed due to legacy things, idk yet)
// /// Layer assigned to this mixnode.
// pub layer: Layer,
/// Information provided by the operator for the purposes of bonding.
pub mix_node: MixNode,
@@ -525,26 +519,6 @@ pub struct MixNodeBond {
}
impl MixNodeBond {
pub fn new(
mix_id: MixId,
owner: Addr,
original_pledge: Coin,
layer: Layer,
mix_node: MixNode,
bonding_height: u64,
) -> Self {
MixNodeBond {
mix_id,
owner,
original_pledge,
layer,
mix_node,
proxy: None,
bonding_height,
is_unbonding: false,
}
}
pub fn identity(&self) -> &str {
&self.mix_node.identity_key
}
@@ -595,21 +569,21 @@ pub struct MixNode {
/// The cost parameters, or the cost function, defined for the particular mixnode that influences
/// how the rewards should be split between the node operator and its delegators.
#[cw_serde]
pub struct MixNodeCostParams {
/// The profit margin of the associated mixnode, i.e. the desired percent of the reward to be distributed to the operator.
pub struct NodeCostParams {
/// The profit margin of the associated node, i.e. the desired percent of the reward to be distributed to the operator.
pub profit_margin_percent: Percent,
/// Operating cost of the associated mixnode per the entire interval.
/// Operating cost of the associated node per the entire interval.
pub interval_operating_cost: Coin,
}
impl MixNodeCostParams {
impl NodeCostParams {
pub fn to_inline_json(&self) -> String {
serde_json_wasm::to_string(self).unwrap_or_else(|_| "serialisation failure".into())
}
}
impl MixNodeCostParams {
impl NodeCostParams {
pub fn epoch_operating_cost(&self, epochs_in_interval: u32) -> Decimal {
Decimal::from_ratio(self.interval_operating_cost.amount, epochs_in_interval)
}
@@ -628,38 +602,39 @@ impl MixNodeCostParams {
Deserialize_repr,
JsonSchema,
)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[repr(u8)]
pub enum Layer {
pub enum LegacyMixLayer {
One = 1,
Two = 2,
Three = 3,
}
impl From<Layer> for String {
fn from(layer: Layer) -> Self {
impl From<LegacyMixLayer> for String {
fn from(layer: LegacyMixLayer) -> Self {
(layer as u8).to_string()
}
}
impl TryFrom<u8> for Layer {
impl TryFrom<u8> for LegacyMixLayer {
type Error = MixnetContractError;
fn try_from(i: u8) -> Result<Layer, MixnetContractError> {
fn try_from(i: u8) -> Result<LegacyMixLayer, MixnetContractError> {
match i {
1 => Ok(Layer::One),
2 => Ok(Layer::Two),
3 => Ok(Layer::Three),
1 => Ok(LegacyMixLayer::One),
2 => Ok(LegacyMixLayer::Two),
3 => Ok(LegacyMixLayer::Three),
_ => Err(MixnetContractError::InvalidLayer(i)),
}
}
}
impl From<Layer> for u8 {
fn from(layer: Layer) -> u8 {
impl From<LegacyMixLayer> for u8 {
fn from(layer: LegacyMixLayer) -> u8 {
match layer {
Layer::One => 1,
Layer::Two => 2,
Layer::Three => 3,
LegacyMixLayer::One => 1,
LegacyMixLayer::Two => 2,
LegacyMixLayer::Three => 3,
}
}
}
@@ -673,13 +648,15 @@ impl From<Layer> for u8 {
#[derive(Default, Copy)]
pub struct PendingMixNodeChanges {
pub pledge_change: Option<EpochEventId>,
// pub cost_params_change: Option<IntervalEventId>,
#[serde(default)]
pub cost_params_change: Option<IntervalEventId>,
}
impl PendingMixNodeChanges {
pub fn new_empty() -> PendingMixNodeChanges {
PendingMixNodeChanges {
pledge_change: None,
cost_params_change: None,
}
}
}
@@ -740,11 +717,11 @@ pub struct PagedMixnodeBondsResponse {
pub per_page: usize,
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
pub start_next_after: Option<MixId>,
pub start_next_after: Option<NodeId>,
}
impl PagedMixnodeBondsResponse {
pub fn new(nodes: Vec<MixNodeBond>, per_page: usize, start_next_after: Option<MixId>) -> Self {
pub fn new(nodes: Vec<MixNodeBond>, per_page: usize, start_next_after: Option<NodeId>) -> Self {
PagedMixnodeBondsResponse {
nodes,
per_page,
@@ -766,14 +743,14 @@ pub struct PagedMixnodesDetailsResponse {
pub per_page: usize,
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
pub start_next_after: Option<MixId>,
pub start_next_after: Option<NodeId>,
}
impl PagedMixnodesDetailsResponse {
pub fn new(
nodes: Vec<MixNodeDetails>,
per_page: usize,
start_next_after: Option<MixId>,
start_next_after: Option<NodeId>,
) -> Self {
PagedMixnodesDetailsResponse {
nodes,
@@ -787,21 +764,21 @@ impl PagedMixnodesDetailsResponse {
#[cw_serde]
pub struct PagedUnbondedMixnodesResponse {
/// The past ids of unbonded mixnodes alongside their basic information such as the owner or the identity key.
pub nodes: Vec<(MixId, UnbondedMixnode)>,
pub nodes: Vec<(NodeId, UnbondedMixnode)>,
/// Maximum number of entries that could be included in a response. `per_page <= nodes.len()`
// this field is rather redundant and should be deprecated.
pub per_page: usize,
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
pub start_next_after: Option<MixId>,
pub start_next_after: Option<NodeId>,
}
impl PagedUnbondedMixnodesResponse {
pub fn new(
nodes: Vec<(MixId, UnbondedMixnode)>,
nodes: Vec<(NodeId, UnbondedMixnode)>,
per_page: usize,
start_next_after: Option<MixId>,
start_next_after: Option<NodeId>,
) -> Self {
PagedUnbondedMixnodesResponse {
nodes,
@@ -825,7 +802,7 @@ pub struct MixOwnershipResponse {
#[cw_serde]
pub struct MixnodeDetailsResponse {
/// Id of the requested mixnode.
pub mix_id: MixId,
pub mix_id: NodeId,
/// If there exists a mixnode with the provided id, this field contains its detailed information.
pub mixnode_details: Option<MixNodeDetails>,
@@ -845,17 +822,17 @@ pub struct MixnodeDetailsByIdentityResponse {
#[cw_serde]
pub struct MixnodeRewardingDetailsResponse {
/// Id of the requested mixnode.
pub mix_id: MixId,
pub mix_id: NodeId,
/// If there exists a mixnode with the provided id, this field contains its rewarding information.
pub rewarding_details: Option<MixNodeRewarding>,
pub rewarding_details: Option<NodeRewarding>,
}
/// Response containing basic information of an unbonded mixnode with the provided id.
#[cw_serde]
pub struct UnbondedMixnodeResponse {
/// Id of the requested mixnode.
pub mix_id: MixId,
pub mix_id: NodeId,
/// If there existed a mixnode with the provided id, this field contains its basic information.
pub unbonded_info: Option<UnbondedMixnode>,
@@ -863,9 +840,9 @@ pub struct UnbondedMixnodeResponse {
/// Response containing the current state of the stake saturation of a mixnode with the provided id.
#[cw_serde]
pub struct StakeSaturationResponse {
pub struct MixStakeSaturationResponse {
/// Id of the requested mixnode.
pub mix_id: MixId,
pub mix_id: NodeId,
/// The current stake saturation of this node that is indirectly used in reward calculation formulas.
/// Note that it can't be larger than 1.
@@ -3,15 +3,17 @@
use crate::delegation::{self, OwnerProxySubKey};
use crate::error::MixnetContractError;
use crate::families::FamilyHead;
use crate::gateway::{Gateway, GatewayConfigUpdate};
use crate::helpers::IntoBaseDecimal;
use crate::mixnode::{Layer, MixNode, MixNodeConfigUpdate, MixNodeCostParams};
use crate::mixnode::{MixNode, MixNodeConfigUpdate, NodeCostParams};
use crate::nym_node::{NodeConfigUpdate, Role};
use crate::pending_events::{EpochEventId, IntervalEventId};
use crate::reward_params::{
IntervalRewardParams, IntervalRewardingParamsUpdate, Performance, RewardingParams,
ActiveSetUpdate, IntervalRewardParams, IntervalRewardingParamsUpdate, NodeRewardingParameters,
Performance, RewardedSetParams, RewardingParams, WorkFactor,
};
use crate::types::{ContractStateParams, LayerAssignment, MixId};
use crate::types::{ContractStateParams, NodeId};
use crate::{NymNode, RoleAssignment};
use crate::{OperatingCostRange, ProfitMarginRange};
use contracts_common::{signing::MessageSignature, IdentityKey, Percent};
use cosmwasm_schema::cw_serde;
@@ -21,28 +23,31 @@ use std::time::Duration;
#[cfg(feature = "schema")]
use crate::{
delegation::{
MixNodeDelegationResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse,
PagedMixNodeDelegationsResponse,
NodeDelegationResponse, PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse,
PagedNodeDelegationsResponse,
},
families::{
FamilyByHeadResponse, FamilyByLabelResponse, FamilyMembersByHeadResponse,
FamilyMembersByLabelResponse, PagedFamiliesResponse, PagedMembersResponse,
gateway::{
GatewayBondResponse, GatewayOwnershipResponse, PagedGatewayResponse,
PreassignedGatewayIdsResponse,
},
gateway::{GatewayBondResponse, GatewayOwnershipResponse, PagedGatewayResponse},
interval::{CurrentIntervalResponse, EpochStatus},
mixnode::{
MixOwnershipResponse, MixnodeDetailsByIdentityResponse, MixnodeDetailsResponse,
MixnodeRewardingDetailsResponse, PagedMixnodeBondsResponse, PagedMixnodesDetailsResponse,
PagedUnbondedMixnodesResponse, StakeSaturationResponse, UnbondedMixnodeResponse,
MixOwnershipResponse, MixStakeSaturationResponse, MixnodeDetailsByIdentityResponse,
MixnodeDetailsResponse, MixnodeRewardingDetailsResponse, PagedMixnodeBondsResponse,
PagedMixnodesDetailsResponse, PagedUnbondedMixnodesResponse, UnbondedMixnodeResponse,
},
nym_node::{
EpochAssignmentResponse, NodeDetailsByIdentityResponse, NodeDetailsResponse,
NodeOwnershipResponse, NodeRewardingDetailsResponse, PagedNymNodeBondsResponse,
PagedNymNodeDetailsResponse, PagedUnbondedNymNodesResponse, RolesMetadataResponse,
StakeSaturationResponse, UnbondedNodeResponse,
},
pending_events::{
NumberOfPendingEventsResponse, PendingEpochEventResponse, PendingEpochEventsResponse,
PendingIntervalEventResponse, PendingIntervalEventsResponse,
},
rewarding::{
EstimatedCurrentEpochRewardResponse, PagedRewardedSetResponse, PendingRewardResponse,
},
types::{ContractState, LayerDistribution},
rewarding::{EstimatedCurrentEpochRewardResponse, PendingRewardResponse},
types::ContractState,
};
#[cfg(feature = "schema")]
use contracts_common::{signing::Nonce, ContractBuildInformation};
@@ -76,8 +81,7 @@ pub struct InitialRewardingParams {
pub active_set_work_factor: Decimal,
pub interval_pool_emission: Percent,
pub rewarded_set_size: u32,
pub active_set_size: u32,
pub rewarded_set_params: RewardedSetParams,
}
impl InitialRewardingParams {
@@ -88,8 +92,11 @@ impl InitialRewardingParams {
let epoch_reward_budget = self.initial_reward_pool
/ epochs_in_interval.into_base_decimal()?
* self.interval_pool_emission;
let stake_saturation_point =
self.initial_staking_supply / self.rewarded_set_size.into_base_decimal()?;
let stake_saturation_point = self.initial_staking_supply
/ self
.rewarded_set_params
.rewarded_set_size()
.into_base_decimal()?;
Ok(RewardingParams {
interval: IntervalRewardParams {
@@ -102,8 +109,7 @@ impl InitialRewardingParams {
active_set_work_factor: self.active_set_work_factor,
interval_pool_emission: self.interval_pool_emission,
},
rewarded_set_size: self.rewarded_set_size,
active_set_size: self.active_set_size,
rewarded_set: self.rewarded_set_params,
})
}
}
@@ -115,45 +121,6 @@ pub enum ExecuteMsg {
admin: String,
},
AssignNodeLayer {
mix_id: MixId,
layer: Layer,
},
// Families
/// Only owner of the node can crate the family with node as head
CreateFamily {
label: String,
},
/// Family head needs to sign the joining node IdentityKey
JoinFamily {
join_permit: MessageSignature,
family_head: FamilyHead,
},
LeaveFamily {
family_head: FamilyHead,
},
KickFamilyMember {
member: IdentityKey,
},
CreateFamilyOnBehalf {
owner_address: 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,
join_permit: MessageSignature,
family_head: FamilyHead,
},
LeaveFamilyOnBehalf {
member_address: String,
family_head: FamilyHead,
},
KickFamilyMemberOnBehalf {
head_address: String,
member: IdentityKey,
},
// state/sys-params-related
UpdateRewardingValidatorAddress {
address: String,
@@ -161,8 +128,8 @@ pub enum ExecuteMsg {
UpdateContractStateParams {
updated_parameters: ContractStateParams,
},
UpdateActiveSetSize {
active_set_size: u32,
UpdateActiveSetDistribution {
update: ActiveSetUpdate,
force_immediately: bool,
},
UpdateRewardingParams {
@@ -174,25 +141,24 @@ pub enum ExecuteMsg {
epoch_duration_secs: u64,
force_immediately: bool,
},
BeginEpochTransition {},
AdvanceCurrentEpoch {
new_rewarded_set: Vec<LayerAssignment>,
// families_in_layer: HashMap<String, Layer>,
expected_active_set_size: u32,
},
ReconcileEpochEvents {
limit: Option<u32>,
},
AssignRoles {
assignment: RoleAssignment,
},
// mixnode-related:
BondMixnode {
mix_node: MixNode,
cost_params: MixNodeCostParams,
cost_params: NodeCostParams,
owner_signature: MessageSignature,
},
BondMixnodeOnBehalf {
mix_node: MixNode,
cost_params: MixNodeCostParams,
cost_params: NodeCostParams,
owner_signature: MessageSignature,
owner: String,
},
@@ -211,11 +177,15 @@ pub enum ExecuteMsg {
UnbondMixnodeOnBehalf {
owner: String,
},
UpdateMixnodeCostParams {
new_costs: MixNodeCostParams,
#[serde(
alias = "UpdateMixnodeCostParams",
alias = "update_mixnode_cost_params"
)]
UpdateCostParams {
new_costs: NodeCostParams,
},
UpdateMixnodeCostParamsOnBehalf {
new_costs: MixNodeCostParams,
new_costs: NodeCostParams,
owner: String,
},
UpdateMixnodeConfig {
@@ -225,6 +195,7 @@ pub enum ExecuteMsg {
new_config: MixNodeConfigUpdate,
owner: String,
},
MigrateMixnode {},
// gateway-related:
BondGateway {
@@ -247,44 +218,64 @@ pub enum ExecuteMsg {
new_config: GatewayConfigUpdate,
owner: String,
},
MigrateGateway {
cost_params: Option<NodeCostParams>,
},
// nym-node related:
BondNymNode {
node: NymNode,
cost_params: NodeCostParams,
owner_signature: MessageSignature,
},
UnbondNymNode {},
UpdateNodeConfig {
update: NodeConfigUpdate,
},
// delegation-related:
DelegateToMixnode {
mix_id: MixId,
#[serde(alias = "DelegateToMixnode", alias = "delegate_to_mixnode")]
Delegate {
#[serde(alias = "mix_id")]
node_id: NodeId,
},
DelegateToMixnodeOnBehalf {
mix_id: MixId,
mix_id: NodeId,
delegate: String,
},
UndelegateFromMixnode {
mix_id: MixId,
#[serde(alias = "UndelegateFromMixnode", alias = "undelegate_from_mixnode")]
Undelegate {
#[serde(alias = "mix_id")]
node_id: NodeId,
},
UndelegateFromMixnodeOnBehalf {
mix_id: MixId,
mix_id: NodeId,
delegate: String,
},
// reward-related
RewardMixnode {
mix_id: MixId,
performance: Performance,
RewardNode {
#[serde(alias = "mix_id")]
node_id: NodeId,
params: NodeRewardingParameters,
},
WithdrawOperatorReward {},
WithdrawOperatorRewardOnBehalf {
owner: String,
},
WithdrawDelegatorReward {
mix_id: MixId,
#[serde(alias = "mix_id")]
node_id: NodeId,
},
WithdrawDelegatorRewardOnBehalf {
mix_id: MixId,
mix_id: NodeId,
owner: String,
},
// vesting migration:
MigrateVestedMixNode {},
MigrateVestedDelegation {
mix_id: MixId,
mix_id: NodeId,
},
// testing-only
@@ -292,47 +283,31 @@ pub enum ExecuteMsg {
TestingResolveAllPendingEvents {
limit: Option<u32>,
},
// TO BE REMOVED BEFORE MERGING
TestingUncheckedBondLegacyMixnode {
node: MixNode,
},
TestingUncheckedBondLegacyGateway {
node: Gateway,
},
}
const bad_const: &str = "clippy will yell so that i'd remember to remove those legacy bonding ops";
impl ExecuteMsg {
pub fn default_memo(&self) -> String {
match self {
ExecuteMsg::UpdateAdmin { admin } => format!("updating contract admin to {admin}"),
ExecuteMsg::AssignNodeLayer { mix_id, layer } => {
format!("assigning mix {mix_id} for layer {layer:?}")
}
ExecuteMsg::CreateFamily { .. } => "crating node family with".to_string(),
ExecuteMsg::JoinFamily { family_head, .. } => {
format!("joining family {family_head}")
}
ExecuteMsg::LeaveFamily { family_head, .. } => {
format!("leaving family {family_head}")
}
ExecuteMsg::KickFamilyMember { member, .. } => {
format!("kicking {member} from family")
}
ExecuteMsg::CreateFamilyOnBehalf { .. } => "crating node family with".to_string(),
ExecuteMsg::JoinFamilyOnBehalf { family_head, .. } => {
format!("joining family {family_head}")
}
ExecuteMsg::LeaveFamilyOnBehalf { family_head, .. } => {
format!("leaving family {family_head}")
}
ExecuteMsg::KickFamilyMemberOnBehalf { member, .. } => {
format!("kicking {member} from family")
}
ExecuteMsg::UpdateRewardingValidatorAddress { address } => {
format!("updating rewarding validator to {address}")
}
ExecuteMsg::UpdateContractStateParams { .. } => {
"updating mixnet state parameters".into()
}
ExecuteMsg::UpdateActiveSetSize {
active_set_size,
force_immediately,
} => format!(
"updating active set size to {active_set_size}. forced: {force_immediately}"
),
ExecuteMsg::UpdateActiveSetDistribution {
force_immediately, ..
} => format!("updating active set distribution. forced: {force_immediately}"),
ExecuteMsg::UpdateRewardingParams {
force_immediately, ..
} => format!("updating mixnet rewarding parameters. forced: {force_immediately}"),
@@ -340,7 +315,6 @@ impl ExecuteMsg {
force_immediately, ..
} => format!("updating mixnet interval configuration. forced: {force_immediately}"),
ExecuteMsg::BeginEpochTransition {} => "beginning epoch transition".into(),
ExecuteMsg::AdvanceCurrentEpoch { .. } => "advancing current epoch".into(),
ExecuteMsg::ReconcileEpochEvents { .. } => "reconciling epoch events".into(),
ExecuteMsg::BondMixnode { mix_node, .. } => {
format!("bonding mixnode {}", mix_node.identity_key)
@@ -356,7 +330,7 @@ impl ExecuteMsg {
}
ExecuteMsg::UnbondMixnode { .. } => "unbonding mixnode".into(),
ExecuteMsg::UnbondMixnodeOnBehalf { .. } => "unbonding mixnode on behalf".into(),
ExecuteMsg::UpdateMixnodeCostParams { .. } => "updating mixnode cost parameters".into(),
ExecuteMsg::UpdateCostParams { .. } => "updating mixnode cost parameters".into(),
ExecuteMsg::UpdateMixnodeCostParamsOnBehalf { .. } => {
"updating mixnode cost parameters on behalf".into()
}
@@ -376,25 +350,22 @@ impl ExecuteMsg {
ExecuteMsg::UpdateGatewayConfigOnBehalf { .. } => {
"updating gateway configuration on behalf".into()
}
ExecuteMsg::DelegateToMixnode { mix_id } => format!("delegating to mixnode {mix_id}"),
ExecuteMsg::Delegate { node_id: mix_id } => format!("delegating to mixnode {mix_id}"),
ExecuteMsg::DelegateToMixnodeOnBehalf { mix_id, .. } => {
format!("delegating to mixnode {mix_id} on behalf")
}
ExecuteMsg::UndelegateFromMixnode { mix_id } => {
ExecuteMsg::Undelegate { node_id: mix_id } => {
format!("removing delegation from mixnode {mix_id}")
}
ExecuteMsg::UndelegateFromMixnodeOnBehalf { mix_id, .. } => {
format!("removing delegation from mixnode {mix_id} on behalf")
}
ExecuteMsg::RewardMixnode {
mix_id,
performance,
} => format!("rewarding mixnode {mix_id} for performance {performance}"),
ExecuteMsg::RewardNode { node_id, .. } => format!("rewarding node {node_id}"),
ExecuteMsg::WithdrawOperatorReward { .. } => "withdrawing operator reward".into(),
ExecuteMsg::WithdrawOperatorRewardOnBehalf { .. } => {
"withdrawing operator reward on behalf".into()
}
ExecuteMsg::WithdrawDelegatorReward { mix_id } => {
ExecuteMsg::WithdrawDelegatorReward { node_id: mix_id } => {
format!("withdrawing delegator reward from mixnode {mix_id}")
}
ExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, .. } => {
@@ -402,11 +373,19 @@ impl ExecuteMsg {
}
ExecuteMsg::MigrateVestedMixNode { .. } => "migrate vested mixnode".into(),
ExecuteMsg::MigrateVestedDelegation { .. } => "migrate vested delegation".to_string(),
ExecuteMsg::AssignRoles { .. } => "assigning epoch roles".into(),
ExecuteMsg::MigrateMixnode { .. } => "migrating legacy mixnode".into(),
ExecuteMsg::MigrateGateway { .. } => "migrating legacy gateway".into(),
ExecuteMsg::BondNymNode { .. } => "bonding nym-node".into(),
ExecuteMsg::UnbondNymNode { .. } => "unbonding nym-node".into(),
ExecuteMsg::UpdateNodeConfig { .. } => "updating node config".into(),
#[cfg(feature = "contract-testing")]
ExecuteMsg::TestingResolveAllPendingEvents { .. } => {
"resolving all pending events".into()
}
ExecuteMsg::TestingUncheckedBondLegacyMixnode { .. } => "todo!".into(),
ExecuteMsg::TestingUncheckedBondLegacyGateway { .. } => "todo!".into(),
}
}
}
@@ -417,43 +396,6 @@ pub enum QueryMsg {
#[cfg_attr(feature = "schema", returns(cw_controllers::AdminResponse))]
Admin {},
// families
/// Gets the list of families registered in this contract.
#[cfg_attr(feature = "schema", returns(PagedFamiliesResponse))]
GetAllFamiliesPaged {
/// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.
limit: Option<u32>,
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
start_after: Option<String>,
},
/// Gets the list of all family members registered in this contract.
#[cfg_attr(feature = "schema", returns(PagedMembersResponse))]
GetAllMembersPaged {
/// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.
limit: Option<u32>,
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
start_after: Option<String>,
},
/// Attempts to lookup family information given the family head.
#[cfg_attr(feature = "schema", returns(FamilyByHeadResponse))]
GetFamilyByHead { head: String },
/// Attempts to lookup family information given the family label.
#[cfg_attr(feature = "schema", returns(FamilyByLabelResponse))]
GetFamilyByLabel { label: String },
/// Attempts to retrieve family members given the family head.
#[cfg_attr(feature = "schema", returns(FamilyMembersByHeadResponse))]
GetFamilyMembersByHead { head: String },
/// Attempts to retrieve family members given the family label.
#[cfg_attr(feature = "schema", returns(FamilyMembersByLabelResponse))]
GetFamilyMembersByLabel { label: String },
// state/sys-params-related
/// Gets build information of this contract, such as the commit hash used for the build or rustc version.
#[cfg_attr(feature = "schema", returns(ContractBuildInformation))]
@@ -488,16 +430,6 @@ pub enum QueryMsg {
#[cfg_attr(feature = "schema", returns(CurrentIntervalResponse))]
GetCurrentIntervalDetails {},
/// Gets the current list of mixnodes in the rewarded set.
#[cfg_attr(feature = "schema", returns(PagedRewardedSetResponse))]
GetRewardedSet {
/// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.
limit: Option<u32>,
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
start_after: Option<MixId>,
},
// mixnode-related:
/// Gets the basic list of all currently bonded mixnodes.
#[cfg_attr(feature = "schema", returns(PagedMixnodeBondsResponse))]
@@ -506,7 +438,7 @@ pub enum QueryMsg {
limit: Option<u32>,
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
start_after: Option<MixId>,
start_after: Option<NodeId>,
},
/// Gets the detailed list of all currently bonded mixnodes.
@@ -516,7 +448,7 @@ pub enum QueryMsg {
limit: Option<u32>,
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
start_after: Option<MixId>,
start_after: Option<NodeId>,
},
/// Gets the basic list of all unbonded mixnodes.
@@ -526,20 +458,20 @@ pub enum QueryMsg {
limit: Option<u32>,
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
start_after: Option<MixId>,
start_after: Option<NodeId>,
},
/// Gets the basic list of all unbonded mixnodes that belonged to a particular owner.
#[cfg_attr(feature = "schema", returns(PagedUnbondedMixnodesResponse))]
GetUnbondedMixNodesByOwner {
/// The address of the owner of the the mixnodes used for the query.
/// The address of the owner of the mixnodes used for the query.
owner: String,
/// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.
limit: Option<u32>,
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
start_after: Option<MixId>,
start_after: Option<NodeId>,
},
/// Gets the basic list of all unbonded mixnodes that used the particular identity key.
@@ -552,7 +484,7 @@ pub enum QueryMsg {
limit: Option<u32>,
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
start_after: Option<MixId>,
start_after: Option<NodeId>,
},
/// Gets the detailed mixnode information belonging to the particular owner.
@@ -566,28 +498,28 @@ pub enum QueryMsg {
#[cfg_attr(feature = "schema", returns(MixnodeDetailsResponse))]
GetMixnodeDetails {
/// Id of the node to query.
mix_id: MixId,
mix_id: NodeId,
},
/// Gets the rewarding information of a mixnode with the provided id.
#[cfg_attr(feature = "schema", returns(MixnodeRewardingDetailsResponse))]
GetMixnodeRewardingDetails {
/// Id of the node to query.
mix_id: MixId,
mix_id: NodeId,
},
/// Gets the stake saturation of a mixnode with the provided id.
#[cfg_attr(feature = "schema", returns(StakeSaturationResponse))]
#[cfg_attr(feature = "schema", returns(MixStakeSaturationResponse))]
GetStakeSaturation {
/// Id of the node to query.
mix_id: MixId,
mix_id: NodeId,
},
/// Gets the basic information of an unbonded mixnode with the provided id.
#[cfg_attr(feature = "schema", returns(UnbondedMixnodeResponse))]
GetUnbondedMixNodeInformation {
/// Id of the node to query.
mix_id: MixId,
mix_id: NodeId,
},
/// Gets the detailed mixnode information of a node given its current identity key.
@@ -597,10 +529,6 @@ pub enum QueryMsg {
mix_identity: IdentityKey,
},
/// Gets the current layer configuration of the mix network.
#[cfg_attr(feature = "schema", returns(LayerDistribution))]
GetLayerDistribution {},
// gateway-related:
/// Gets the basic list of all currently bonded gateways.
#[cfg_attr(feature = "schema", returns(PagedGatewayResponse))]
@@ -626,12 +554,128 @@ pub enum QueryMsg {
address: String,
},
// delegation-related:
/// Gets all delegations associated with particular mixnode
#[cfg_attr(feature = "schema", returns(PagedMixNodeDelegationsResponse))]
GetMixnodeDelegations {
/// Get the `NodeId`s of all the legacy gateways that they will get assigned once migrated into NymNodes
#[cfg_attr(feature = "schema", returns(PreassignedGatewayIdsResponse))]
GetPreassignedGatewayIds {
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
start_after: Option<IdentityKey>,
/// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.
limit: Option<u32>,
},
// nym-node-related:
/// Gets the basic list of all currently bonded nymnodes.
#[cfg_attr(feature = "schema", returns(PagedNymNodeBondsResponse))]
GetNymNodeBondsPaged {
/// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.
limit: Option<u32>,
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
start_after: Option<NodeId>,
},
/// Gets the detailed list of all currently bonded nymnodes.
#[cfg_attr(feature = "schema", returns(PagedNymNodeDetailsResponse))]
GetNymNodesDetailedPaged {
/// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.
limit: Option<u32>,
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
start_after: Option<NodeId>,
},
/// Gets the basic information of an unbonded nym-node with the provided id.
#[cfg_attr(feature = "schema", returns(UnbondedNodeResponse))]
GetUnbondedNymNode {
/// Id of the node to query.
mix_id: MixId,
node_id: NodeId,
},
/// Gets the basic list of all unbonded nymnodes.
#[cfg_attr(feature = "schema", returns(PagedUnbondedNymNodesResponse))]
GetUnbondedNymNodesPaged {
/// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.
limit: Option<u32>,
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
start_after: Option<NodeId>,
},
/// Gets the basic list of all unbonded nymnodes that belonged to a particular owner.
#[cfg_attr(feature = "schema", returns(PagedUnbondedNymNodesResponse))]
GetUnbondedNymNodesByOwnerPaged {
/// The address of the owner of the nym-node used for the query
owner: String,
/// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.
limit: Option<u32>,
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
start_after: Option<NodeId>,
},
/// Gets the basic list of all unbonded nymnodes that used the particular identity key.
#[cfg_attr(feature = "schema", returns(PagedUnbondedNymNodesResponse))]
GetUnbondedNymNodesByIdentityKeyPaged {
/// The identity key (base58-encoded ed25519 public key) of the node used for the query.
identity_key: IdentityKey,
/// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.
limit: Option<u32>,
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
start_after: Option<NodeId>,
},
/// Gets the detailed nymnode information belonging to the particular owner.
#[cfg_attr(feature = "schema", returns(NodeOwnershipResponse))]
GetOwnedNymNode {
/// Address of the node owner to use for the query.
address: String,
},
/// Gets the detailed nymnode information of a node with the provided id.
#[cfg_attr(feature = "schema", returns(NodeDetailsResponse))]
GetNymNodeDetails {
/// Id of the node to query.
node_id: NodeId,
},
/// Gets the detailed nym-node information given its current identity key.
#[cfg_attr(feature = "schema", returns(NodeDetailsByIdentityResponse))]
GetNymNodeDetailsByIdentityKey {
/// The identity key (base58-encoded ed25519 public key) of the nym-node used for the query.
node_identity: IdentityKey,
},
/// Gets the rewarding information of a nym-node with the provided id.
#[cfg_attr(feature = "schema", returns(NodeRewardingDetailsResponse))]
GetNodeRewardingDetails {
/// Id of the node to query.
node_id: NodeId,
},
/// Gets the stake saturation of a nym-node with the provided id.
#[cfg_attr(feature = "schema", returns(StakeSaturationResponse))]
GetNodeStakeSaturation {
/// Id of the node to query.
node_id: NodeId,
},
#[cfg_attr(feature = "schema", returns(EpochAssignmentResponse))]
GetRoleAssignment { role: Role },
#[cfg_attr(feature = "schema", returns(RolesMetadataResponse))]
GetRewardedSetMetadata {},
// delegation-related:
/// Gets all delegations associated with particular node
#[cfg_attr(feature = "schema", returns(PagedNodeDelegationsResponse))]
GetNodeDelegations {
/// Id of the node to query.
#[serde(alias = "mix_id")]
node_id: NodeId,
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
start_after: Option<OwnerProxySubKey>,
@@ -649,17 +693,18 @@ pub enum QueryMsg {
delegator: String,
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
start_after: Option<(MixId, OwnerProxySubKey)>,
start_after: Option<(NodeId, OwnerProxySubKey)>,
/// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.
limit: Option<u32>,
},
/// Gets delegation information associated with particular mixnode - delegator pair
#[cfg_attr(feature = "schema", returns(MixNodeDelegationResponse))]
#[cfg_attr(feature = "schema", returns(NodeDelegationResponse))]
GetDelegationDetails {
/// Id of the node to query.
mix_id: MixId,
#[serde(alias = "mix_id")]
node_id: NodeId,
/// The address of the owner of the delegation.
delegator: String,
@@ -689,9 +734,14 @@ pub enum QueryMsg {
/// Gets the reward amount accrued by the particular mixnode that has not yet been claimed.
#[cfg_attr(feature = "schema", returns(PendingRewardResponse))]
GetPendingMixNodeOperatorReward {
#[serde(
alias = "GetPendingMixNodeOperatorReward",
alias = "get_pending_mix_node_operator_reward"
)]
GetPendingNodeOperatorReward {
/// Id of the node to query.
mix_id: MixId,
#[serde(alias = "mix_id")]
node_id: NodeId,
},
/// Gets the reward amount accrued by the particular delegator that has not yet been claimed.
@@ -701,7 +751,8 @@ pub enum QueryMsg {
address: String,
/// Id of the node to query.
mix_id: MixId,
#[serde(alias = "mix_id")]
node_id: NodeId,
/// Entity who made the delegation on behalf of the owner.
/// If present, it's most likely the address of the vesting contract.
@@ -712,10 +763,14 @@ pub enum QueryMsg {
#[cfg_attr(feature = "schema", returns(EstimatedCurrentEpochRewardResponse))]
GetEstimatedCurrentEpochOperatorReward {
/// Id of the node to query.
mix_id: MixId,
#[serde(alias = "mix_id")]
node_id: NodeId,
/// The estimated performance for the current epoch of the given node.
estimated_performance: Performance,
/// The estimated work for the current epoch of the given node.
estimated_work: Option<WorkFactor>,
},
/// Given the provided node performance, attempt to estimate the delegator reward for the current epoch.
@@ -725,14 +780,14 @@ pub enum QueryMsg {
address: String,
/// Id of the node to query.
mix_id: MixId,
/// Entity who made the delegation on behalf of the owner.
/// If present, it's most likely the address of the vesting contract.
proxy: Option<String>,
#[serde(alias = "mix_id")]
node_id: NodeId,
/// The estimated performance for the current epoch of the given node.
estimated_performance: Performance,
/// The estimated work for the current epoch of the given node.
estimated_work: Option<WorkFactor>,
},
// interval-related
@@ -0,0 +1,575 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::MixnetContractError;
use crate::{EpochEventId, EpochId, Gateway, IntervalEventId, MixNode, NodeId, NodeRewarding};
use contracts_common::IdentityKey;
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, Coin, Decimal, StdError, StdResult};
use cw_storage_plus::{IntKey, Key, KeyDeserialize, PrimaryKey};
use std::fmt::{Display, Formatter};
#[cw_serde]
#[derive(PartialOrd, Copy, Hash, Eq)]
#[repr(u8)]
pub enum Role {
#[serde(rename = "eg", alias = "entry", alias = "entry_gateway")]
EntryGateway = 0,
#[serde(rename = "l1", alias = "layer1")]
Layer1 = 1,
#[serde(rename = "l2", alias = "layer2")]
Layer2 = 2,
#[serde(rename = "l3", alias = "layer3")]
Layer3 = 3,
#[serde(rename = "xg", alias = "exit", alias = "exit_gateway")]
ExitGateway = 4,
#[serde(rename = "stb", alias = "standby")]
Standby = 128,
}
impl TryFrom<u8> for Role {
type Error = MixnetContractError;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
n if n == Role::EntryGateway as u8 => Ok(Role::EntryGateway),
n if n == Role::Layer1 as u8 => Ok(Role::Layer1),
n if n == Role::Layer2 as u8 => Ok(Role::Layer2),
n if n == Role::Layer3 as u8 => Ok(Role::Layer3),
n if n == Role::ExitGateway as u8 => Ok(Role::ExitGateway),
n if n == Role::Standby as u8 => Ok(Role::Standby),
n => Err(MixnetContractError::UnknownRoleRepresentation { got: n }),
}
}
}
impl<'a> PrimaryKey<'a> for Role {
type Prefix = <u8 as PrimaryKey<'a>>::Prefix;
type SubPrefix = <u8 as PrimaryKey<'a>>::SubPrefix;
type Suffix = <u8 as PrimaryKey<'a>>::Suffix;
type SuperSuffix = <u8 as PrimaryKey<'a>>::SuperSuffix;
fn key(&self) -> Vec<Key> {
// I'm not sure why it wasn't possible to delegate the call to
// `(*self as u8).key()` directly...
// I guess because of the `Key::Ref(&'a [u8])` variant?
vec![Key::Val8((*self as u8).to_cw_bytes())]
}
fn joined_key(&self) -> Vec<u8> {
(*self as u8).joined_key()
}
fn joined_extra_key(&self, key: &[u8]) -> Vec<u8> {
(*self as u8).joined_extra_key(key)
}
}
impl KeyDeserialize for Role {
type Output = Role;
fn from_vec(value: Vec<u8>) -> StdResult<Self::Output> {
let u8_key: <u8 as KeyDeserialize>::Output = <u8 as KeyDeserialize>::from_vec(value)?;
Role::try_from(u8_key).map_err(|err| StdError::generic_err(err.to_string()))
}
fn from_slice(value: &[u8]) -> StdResult<Self::Output> {
let u8_key: <u8 as KeyDeserialize>::Output = <u8 as KeyDeserialize>::from_slice(value)?;
Role::try_from(u8_key).map_err(|err| StdError::generic_err(err.to_string()))
}
}
impl Role {
pub fn first() -> Role {
Role::ExitGateway
}
pub fn next(&self) -> Option<Self> {
// roles have to be assigned in the following order:
// exit -> entry -> l1 -> l2 -> l3 -> standby
match self {
Role::ExitGateway => Some(Role::EntryGateway),
Role::EntryGateway => Some(Role::Layer1),
Role::Layer1 => Some(Role::Layer2),
Role::Layer2 => Some(Role::Layer3),
Role::Layer3 => Some(Role::Standby),
Role::Standby => None,
}
}
pub fn is_first(&self) -> bool {
self == &Role::first()
}
pub fn is_standby(&self) -> bool {
matches!(self, Role::Standby)
}
}
impl Display for Role {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Role::Layer1 => write!(f, "mix layer 1"),
Role::Layer2 => write!(f, "mix layer 2"),
Role::Layer3 => write!(f, "mix layer 3"),
Role::EntryGateway => write!(f, "entry gateway"),
Role::ExitGateway => write!(f, "exit gateway"),
Role::Standby => write!(f, "standby"),
}
}
}
/// Metadata associated with the rewarded set.
#[cw_serde]
#[derive(Default, Copy)]
pub struct RewardedSetMetadata {
/// Epoch that this data corresponds to.
pub epoch_id: EpochId,
/// Indicates whether all roles got assigned to the set for this epoch.
pub fully_assigned: bool,
/// Metadata for the 'EntryGateway' role
pub entry_gateway_metadata: RoleMetadata,
/// Metadata for the 'ExitGateway' role
pub exit_gateway_metadata: RoleMetadata,
/// Metadata for the 'Layer1' role
pub layer1_metadata: RoleMetadata,
/// Metadata for the 'Layer2' role
pub layer2_metadata: RoleMetadata,
/// Metadata for the 'Layer3' role
pub layer3_metadata: RoleMetadata,
/// Metadata for the 'Standby' role
pub standby_metadata: RoleMetadata,
}
impl RewardedSetMetadata {
pub fn new(epoch_id: EpochId) -> Self {
RewardedSetMetadata {
epoch_id,
fully_assigned: false,
entry_gateway_metadata: Default::default(),
exit_gateway_metadata: Default::default(),
layer1_metadata: Default::default(),
layer2_metadata: Default::default(),
layer3_metadata: Default::default(),
standby_metadata: Default::default(),
}
}
pub fn set_role_count(&mut self, role: Role, num_nodes: u32) {
match role {
Role::EntryGateway => self.entry_gateway_metadata.num_nodes = num_nodes,
Role::Layer1 => self.layer1_metadata.num_nodes = num_nodes,
Role::Layer2 => self.layer2_metadata.num_nodes = num_nodes,
Role::Layer3 => self.layer3_metadata.num_nodes = num_nodes,
Role::ExitGateway => self.exit_gateway_metadata.num_nodes = num_nodes,
Role::Standby => self.standby_metadata.num_nodes = num_nodes,
}
}
pub fn set_highest_id(&mut self, highest_id: NodeId, role: Role) {
match role {
Role::EntryGateway => self.entry_gateway_metadata.highest_id = highest_id,
Role::Layer1 => self.layer1_metadata.highest_id = highest_id,
Role::Layer2 => self.layer2_metadata.highest_id = highest_id,
Role::Layer3 => self.layer3_metadata.highest_id = highest_id,
Role::ExitGateway => self.exit_gateway_metadata.highest_id = highest_id,
Role::Standby => self.standby_metadata.highest_id = highest_id,
}
}
// important note: this currently does **NOT** include gateway role as they're not being rewarded
// and the metadata is primarily used for data lookup during epoch transition
pub fn highest_rewarded_id(&self) -> NodeId {
let mut highest = 0;
if self.layer1_metadata.highest_id > highest {
highest = self.layer1_metadata.highest_id;
}
if self.layer2_metadata.highest_id > highest {
highest = self.layer2_metadata.highest_id;
}
if self.layer3_metadata.highest_id > highest {
highest = self.layer3_metadata.highest_id;
}
if self.standby_metadata.highest_id > highest {
highest = self.standby_metadata.highest_id;
}
highest
}
}
/// Metadata associated with particular node role.
#[cw_serde]
#[derive(Default, Copy)]
pub struct RoleMetadata {
/// Highest, also latest, node-id of a node assigned this role.
pub highest_id: NodeId,
/// Number of nodes assigned this particular role.
pub num_nodes: u32,
}
/// Full details associated with given node.
#[cw_serde]
pub struct NymNodeDetails {
/// Basic bond information of this node, such as owner address, original pledge, etc.
pub bond_information: NymNodeBond,
/// Details used for computation of rewarding related data.
pub rewarding_details: NodeRewarding,
/// Adjustments to the node that are scheduled to happen during future epoch/interval transitions.
pub pending_changes: PendingNodeChanges,
}
impl NymNodeDetails {
pub fn new(
bond_information: NymNodeBond,
rewarding_details: NodeRewarding,
pending_changes: PendingNodeChanges,
) -> Self {
NymNodeDetails {
bond_information,
rewarding_details,
pending_changes,
}
}
pub fn node_id(&self) -> NodeId {
self.bond_information.node_id
}
pub fn is_unbonding(&self) -> bool {
self.bond_information.is_unbonding
}
pub fn original_pledge(&self) -> &Coin {
&self.bond_information.original_pledge
}
pub fn pending_operator_reward(&self) -> Coin {
let pledge = self.original_pledge();
self.rewarding_details.pending_operator_reward(pledge)
}
pub fn pending_detailed_operator_reward(&self) -> StdResult<Decimal> {
let pledge = self.original_pledge();
self.rewarding_details
.pending_detailed_operator_reward(pledge)
}
pub fn total_stake(&self) -> Decimal {
self.rewarding_details.node_bond()
}
pub fn pending_pledge_change(&self) -> Option<EpochEventId> {
self.pending_changes.pledge_change
}
}
///
#[cw_serde]
pub struct NymNodeBond {
/// Unique id assigned to the bonded node.
pub node_id: NodeId,
/// Address of the owner of this nym-node.
pub owner: Addr,
/// Original amount pledged by the operator of this node.
pub original_pledge: Coin,
/// Block height at which this nym-node has been bonded.
pub bonding_height: u64,
/// Flag to indicate whether this node is in the process of unbonding,
/// that will conclude upon the epoch finishing.
pub is_unbonding: bool,
/// Information provided by the operator for the purposes of bonding.
pub node: NymNode,
}
impl NymNodeBond {
pub fn new(
node_id: NodeId,
owner: Addr,
original_pledge: Coin,
node: impl Into<NymNode>,
bonding_height: u64,
) -> NymNodeBond {
Self {
node_id,
owner,
original_pledge,
bonding_height,
is_unbonding: false,
node: node.into(),
}
}
pub fn identity(&self) -> &str {
&self.node.identity_key
}
pub fn ensure_bonded(&self) -> Result<(), MixnetContractError> {
if self.is_unbonding {
return Err(MixnetContractError::NodeIsUnbonding {
node_id: self.node_id,
});
}
Ok(())
}
}
/// Information provided by the node operator during bonding that are used to allow other entities to use the services of this node.
#[cw_serde]
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/NymNode.ts")
)]
pub struct NymNode {
/// Network address of this nym-node, for example 1.1.1.1 or foo.mixnode.com
/// that is used to discover other capabilities of this node.
pub host: String,
/// Allow specifying custom port for accessing the http, and thus self-described, api
/// of this node for the capabilities discovery.
pub custom_http_port: Option<u16>,
/// Base58-encoded ed25519 EdDSA public key.
pub identity_key: IdentityKey,
// TODO: I don't think we want to include sphinx keys here,
// given we want to rotate them and keeping that in sync with contract will be a PITA
}
impl NymNode {
/// Perform naive validation of the attached identity key - makes sure it's correctly encoded
/// and has 32 bytes (as expected from ed25519). we're not, however, checking if it's a valid curve point
pub fn naive_ensure_valid_pubkey(&self) -> Result<(), MixnetContractError> {
let decoded = bs58::decode(&self.identity_key)
.into_vec()
.map_err(|_| MixnetContractError::InvalidPubKey)?;
if decoded.len() != 32 {
return Err(MixnetContractError::InvalidPubKey);
}
Ok(())
}
/// Makes sure the provided host's length is at most 255 characters to prevent abuse.
pub fn ensure_host_in_range(&self) -> Result<(), MixnetContractError> {
if self.host.len() > 255 {
return Err(MixnetContractError::HostTooLong);
}
Ok(())
}
}
impl From<MixNode> for NymNode {
fn from(value: MixNode) -> Self {
NymNode {
host: value.host,
custom_http_port: Some(value.http_api_port),
identity_key: value.identity_key,
}
}
}
impl From<Gateway> for NymNode {
fn from(value: Gateway) -> Self {
NymNode {
host: value.host,
custom_http_port: None,
identity_key: value.identity_key,
}
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/NodeConfigUpdate.ts")
)]
#[cw_serde]
#[derive(Default)]
pub struct NodeConfigUpdate {
pub host: Option<String>,
// ideally this would have been `Option<Option<u16>>`, but not sure if json would have recognised it
pub custom_http_port: Option<u16>,
// equivalent to setting `custom_http_port` to `None`
#[serde(default)]
pub restore_default_http_port: bool,
}
#[cw_serde]
#[derive(Default, Copy)]
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/PendingNodeChanges.ts")
)]
pub struct PendingNodeChanges {
pub pledge_change: Option<EpochEventId>,
pub cost_params_change: Option<IntervalEventId>,
}
impl PendingNodeChanges {
pub fn new_empty() -> PendingNodeChanges {
PendingNodeChanges {
pledge_change: None,
cost_params_change: None,
}
}
pub fn ensure_no_pending_pledge_changes(&self) -> Result<(), MixnetContractError> {
if let Some(pending_event_id) = self.pledge_change {
return Err(MixnetContractError::PendingPledgeChange { pending_event_id });
}
Ok(())
}
pub fn ensure_no_pending_params_changes(&self) -> Result<(), MixnetContractError> {
if let Some(pending_event_id) = self.cost_params_change {
return Err(MixnetContractError::PendingParamsChange { pending_event_id });
}
Ok(())
}
}
/// Basic information of a node that used to be part of the nym network but has already unbonded.
#[cw_serde]
pub struct UnbondedNymNode {
/// Base58-encoded ed25519 EdDSA public key.
pub identity_key: IdentityKey,
/// NodeId assigned to this node.
pub node_id: NodeId,
/// Address of the owner of this nym node.
pub owner: Addr,
/// Block height at which this nym node has unbonded.
pub unbonding_height: u64,
}
/// Response containing rewarding information of a node with the provided id.
#[cw_serde]
pub struct NodeRewardingDetailsResponse {
/// Id of the requested node.
pub node_id: NodeId,
/// If there exists a node with the provided id, this field contains its rewarding information.
pub rewarding_details: Option<NodeRewarding>,
}
/// Response containing details of a node belonging to the particular owner.
#[cw_serde]
pub struct NodeOwnershipResponse {
/// Validated address of the node owner.
pub address: Addr,
/// If the provided address owns a nym-node, this field contains its detailed information.
pub details: Option<NymNodeDetails>,
}
/// Response containing details of a node with the provided id.
#[cw_serde]
pub struct NodeDetailsResponse {
/// Id of the requested node.
pub node_id: NodeId,
/// If there exists a node with the provided id, this field contains its detailed information.
pub details: Option<NymNodeDetails>,
}
/// Response containing details of a bonded node with the provided identity key.
#[cw_serde]
pub struct NodeDetailsByIdentityResponse {
/// The identity key (base58-encoded ed25519 public key) of the node.
pub identity_key: IdentityKey,
/// If there exists a bonded node with the provided identity key, this field contains its detailed information.
pub details: Option<NymNodeDetails>,
}
/// Response containing the current state of the stake saturation of a node with the provided id.
#[cw_serde]
pub struct StakeSaturationResponse {
/// Id of the requested node.
pub node_id: NodeId,
/// The current stake saturation of this node that is indirectly used in reward calculation formulas.
/// Note that it can't be larger than 1.
pub current_saturation: Option<Decimal>,
/// The current, absolute, stake saturation of this node.
/// Note that as the name suggests it can be larger than 1.
/// However, anything beyond that value has no effect on the total node reward.
pub uncapped_saturation: Option<Decimal>,
}
/// Response containing paged list of all nym-nodes that have ever unbonded.
#[cw_serde]
pub struct PagedUnbondedNymNodesResponse {
/// Basic information of the node such as the owner or the identity key.
pub nodes: Vec<UnbondedNymNode>,
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
pub start_next_after: Option<NodeId>,
}
/// Response containing basic information of an unbonded nym-node with the provided id.
#[cw_serde]
pub struct UnbondedNodeResponse {
/// Id of the requested nym-node.
pub node_id: NodeId,
/// If there existed a nym-node with the provided id, this field contains its basic information.
pub details: Option<UnbondedNymNode>,
}
#[cw_serde]
pub struct PagedNymNodeBondsResponse {
/// The nym node bond information present in the contract.
pub nodes: Vec<NymNodeBond>,
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
pub start_next_after: Option<NodeId>,
}
#[cw_serde]
pub struct PagedNymNodeDetailsResponse {
/// All nym-node details stored in the contract.
/// Apart from the basic bond information it also contains details required for all future reward calculation
/// as well as any pending changes requested by the operator.
pub nodes: Vec<NymNodeDetails>,
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
pub start_next_after: Option<NodeId>,
}
#[cw_serde]
pub struct EpochAssignmentResponse {
/// Epoch that this data corresponds to.
pub epoch_id: EpochId,
pub nodes: Vec<NodeId>,
}
#[cw_serde]
pub struct RolesMetadataResponse {
pub metadata: RewardedSetMetadata,
}
@@ -1,9 +1,9 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::mixnode::MixNodeCostParams;
use crate::reward_params::IntervalRewardingParamsUpdate;
use crate::{BlockHeight, MixId};
use crate::mixnode::NodeCostParams;
use crate::reward_params::{ActiveSetUpdate, IntervalRewardingParamsUpdate};
use crate::{BlockHeight, NodeId};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, Coin};
@@ -35,7 +35,7 @@ pub struct PendingEpochEventData {
pub enum PendingEpochEventKind {
// can't just pass the `Delegation` struct here as it's impossible to determine
// `cumulative_reward_ratio` ahead of time
/// Request to create a delegation towards particular mixnode.
/// Request to create a delegation towards particular node.
/// Note that if such delegation already exists, it will get updated with the provided token amount.
#[serde(alias = "Delegate")]
#[non_exhaustive]
@@ -43,8 +43,9 @@ pub enum PendingEpochEventKind {
/// The address of the owner of the delegation.
owner: Addr,
/// The id of the mixnode used for the delegation.
mix_id: MixId,
/// The id of the node used for the delegation.
#[serde(alias = "mix_id")]
node_id: NodeId,
/// The amount of tokens to use for the delegation.
amount: Coin,
@@ -54,15 +55,16 @@ pub enum PendingEpochEventKind {
proxy: Option<Addr>,
},
/// Request to remove delegation from particular mixnode.
/// Request to remove delegation from particular node.
#[serde(alias = "Undelegate")]
#[non_exhaustive]
Undelegate {
/// The address of the owner of the delegation.
owner: Addr,
/// The id of the mixnode used for the delegation.
mix_id: MixId,
/// The id of the node used for the delegation.
#[serde(alias = "mix_id")]
node_id: NodeId,
/// Entity who made the delegation on behalf of the owner.
/// If present, it's most likely the address of the vesting contract.
@@ -70,20 +72,38 @@ pub enum PendingEpochEventKind {
},
/// Request to pledge more tokens (by the node operator) towards its node.
#[serde(alias = "PledgeMore")]
PledgeMore {
/// The id of the mixnode that will have its pledge updated.
mix_id: MixId,
NymNodePledgeMore {
/// The id of the nym node that will have its pledge updated.
node_id: NodeId,
/// The amount of additional tokens to use by the pledge.
/// The amount of additional tokens to use in the pledge.
amount: Coin,
},
/// Request to pledge more tokens (by the node operator) towards its node.
#[serde(alias = "PledgeMore")]
MixnodePledgeMore {
/// The id of the mixnode that will have its pledge updated.
mix_id: NodeId,
/// The amount of additional tokens to use in the pledge.
amount: Coin,
},
/// Request to decrease amount of pledged tokens (by the node operator) from its node.
NymNodeDecreasePledge {
/// The id of the nym node that will have its pledge updated.
node_id: NodeId,
/// The amount of tokens that should be removed from the pledge.
decrease_by: Coin,
},
/// Request to decrease amount of pledged tokens (by the node operator) from its node.
#[serde(alias = "DecreasePledge")]
DecreasePledge {
MixnodeDecreasePledge {
/// The id of the mixnode that will have its pledge updated.
mix_id: MixId,
mix_id: NodeId,
/// The amount of tokens that should be removed from the pledge.
decrease_by: Coin,
@@ -93,15 +113,17 @@ pub enum PendingEpochEventKind {
#[serde(alias = "UnbondMixnode")]
UnbondMixnode {
/// The id of the mixnode that will get unbonded.
mix_id: MixId,
mix_id: NodeId,
},
/// Request to update the current size of the active set.
#[serde(alias = "UpdateActiveSetSize")]
UpdateActiveSetSize {
/// The new desired size of the active set.
new_size: u32,
/// Request to unbond a nym node and completely remove it from the network.
UnbondNymNode {
/// The id of the node that will get unbonded.
node_id: NodeId,
},
/// Request to update the current active set.
UpdateActiveSet { update: ActiveSetUpdate },
}
impl PendingEpochEventKind {
@@ -112,19 +134,19 @@ impl PendingEpochEventKind {
}
}
pub fn new_delegate(owner: Addr, mix_id: MixId, amount: Coin) -> Self {
pub fn new_delegate(owner: Addr, node_id: NodeId, amount: Coin) -> Self {
PendingEpochEventKind::Delegate {
owner,
mix_id,
node_id,
amount,
proxy: None,
}
}
pub fn new_undelegate(owner: Addr, mix_id: MixId) -> Self {
pub fn new_undelegate(owner: Addr, node_id: NodeId) -> Self {
PendingEpochEventKind::Undelegate {
owner,
mix_id,
node_id,
proxy: None,
}
}
@@ -166,10 +188,19 @@ pub enum PendingIntervalEventKind {
#[serde(alias = "ChangeMixCostParams")]
ChangeMixCostParams {
/// The id of the mixnode that will have its cost parameters updated.
mix_id: MixId,
mix_id: NodeId,
/// The new updated cost function of this mixnode.
new_costs: MixNodeCostParams,
new_costs: NodeCostParams,
},
/// Request to update cost parameters of given nym node.
ChangeNymNodeCostParams {
/// The id of the nym node that will have its cost parameters updated.
node_id: NodeId,
/// The new updated cost function of this nym node.
new_costs: NodeCostParams,
},
/// Request to update the underlying rewarding parameters used by the system
@@ -2,11 +2,13 @@
// SPDX-License-Identifier: Apache-2.0
use crate::helpers::IntoBaseDecimal;
use crate::nym_node::Role;
use crate::{error::MixnetContractError, Percent};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Decimal;
pub type Performance = Percent;
pub type WorkFactor = Decimal;
/// Parameters required by the mix-mining reward distribution that do not change during an interval.
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
@@ -86,23 +88,15 @@ pub struct RewardingParams {
/// Parameters that should remain unchanged throughout an interval.
pub interval: IntervalRewardParams,
// while the rewarded set size can change between epochs to accommodate for bandwidth demands,
// the active set size should be unchanged between epochs and should only be adjusted between
// intervals. However, it makes more sense to keep both of those values together as they're
// very strongly related to each other.
/// The expected number of mixnodes in the rewarded set (i.e. active + standby).
pub rewarded_set_size: u32,
/// The expected number of mixnodes in the active set.
pub active_set_size: u32,
pub rewarded_set: RewardedSetParams,
}
impl RewardingParams {
pub fn active_node_work(&self) -> Decimal {
pub fn active_node_work(&self) -> WorkFactor {
self.interval.active_set_work_factor * self.standby_node_work()
}
pub fn standby_node_work(&self) -> Decimal {
pub fn standby_node_work(&self) -> WorkFactor {
let f = self.interval.active_set_work_factor;
let k = self.dec_rewarded_set_size();
let one = Decimal::one();
@@ -113,27 +107,33 @@ impl RewardingParams {
one / (f * k - (f - one) * k_r)
}
pub fn rewarded_set_size(&self) -> u32 {
self.rewarded_set.rewarded_set_size()
}
pub fn active_set_size(&self) -> u32 {
self.rewarded_set.active_set_size()
}
pub fn dec_rewarded_set_size(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places
#[allow(clippy::unwrap_used)]
self.rewarded_set_size.into_base_decimal().unwrap()
self.rewarded_set_size().into_base_decimal().unwrap()
}
pub fn dec_active_set_size(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places
#[allow(clippy::unwrap_used)]
self.active_set_size.into_base_decimal().unwrap()
self.active_set_size().into_base_decimal().unwrap()
}
fn dec_standby_set_size(&self) -> Decimal {
// the unwrap here is fine as we're guaranteed an `u32` is going to fit in a Decimal
// with 0 decimal places
#[allow(clippy::unwrap_used)]
(self.rewarded_set_size - self.active_set_size)
.into_base_decimal()
.unwrap()
self.rewarded_set.standby.into_base_decimal().unwrap()
}
pub fn apply_epochs_in_interval_change(&mut self, new_epochs_in_interval: u32) {
@@ -146,19 +146,35 @@ impl RewardingParams {
* self.interval.interval_pool_emission;
}
pub fn try_change_active_set_size(
&mut self,
new_active_set_size: u32,
pub fn validate_active_set_update(
&self,
update: ActiveSetUpdate,
) -> Result<(), MixnetContractError> {
if new_active_set_size == 0 {
return Err(MixnetContractError::ZeroActiveSet);
}
update.ensure_non_empty()?;
let active_set_size = update.active_set_size();
if new_active_set_size > self.rewarded_set_size {
if active_set_size > self.rewarded_set_size() {
return Err(MixnetContractError::InvalidActiveSetSize);
}
self.active_set_size = new_active_set_size;
Ok(())
}
pub fn try_change_active_set(
&mut self,
update: ActiveSetUpdate,
) -> Result<(), MixnetContractError> {
self.validate_active_set_update(update)?;
let active_set_size = update.active_set_size();
let rewarded_set_size = self.rewarded_set_size();
// safety: due to validation we know that the active_set_size <= rewarded_set_size
let new_standby = rewarded_set_size - active_set_size;
self.rewarded_set.exit_gateways = update.exit_gateways;
self.rewarded_set.entry_gateways = update.entry_gateways;
self.rewarded_set.mixnodes = update.mixnodes;
self.rewarded_set.standby = new_standby;
Ok(())
}
@@ -201,16 +217,10 @@ impl RewardingParams {
self.interval.interval_pool_emission = interval_pool_emission;
}
if let Some(rewarded_set_size) = updates.rewarded_set_size {
if rewarded_set_size == 0 {
return Err(MixnetContractError::ZeroRewardedSet);
}
if rewarded_set_size < self.active_set_size {
return Err(MixnetContractError::InvalidRewardedSetSize);
}
if let Some(rewarded_set_update) = updates.rewarded_set_params {
rewarded_set_update.ensure_valid()?;
recompute_saturation_point = true;
self.rewarded_set_size = rewarded_set_size;
self.rewarded_set = rewarded_set_update;
}
if recompute_epoch_budget {
@@ -221,31 +231,99 @@ impl RewardingParams {
if recompute_saturation_point {
self.interval.stake_saturation_point =
self.interval.staking_supply / self.rewarded_set_size.into_base_decimal()?
self.interval.staking_supply / self.rewarded_set_size().into_base_decimal()?
}
Ok(())
}
}
// TODO: possibly refactor this
/// Parameters used for rewarding particular mixnode.
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/RewardedSetParams.ts")
)]
#[cw_serde]
#[derive(Copy)]
pub struct NodeRewardParams {
/// Performance of the particular node in the current epoch.
pub performance: Percent,
pub struct RewardedSetParams {
/// The expected number of nodes assigned entry gateway role (i.e. [`Role::EntryGateway`])
pub entry_gateways: u32,
/// Flag indicating whether the node has been in the active set during the epoch.
pub in_active_set: bool,
/// The expected number of nodes assigned exit gateway role (i.e. [`Role::ExitGateway`])
pub exit_gateways: u32,
/// The expected number of nodes assigned the 'mixnode' role, i.e. total of [`Role::Layer1`], [`Role::Layer2`] and [`Role::Layer3`].
pub mixnodes: u32,
/// Number of nodes in the 'standby' set. (i.e. [`Role::Standby`])
pub standby: u32,
}
impl NodeRewardParams {
pub fn new(performance: Percent, in_active_set: bool) -> Self {
NodeRewardParams {
performance,
in_active_set,
impl RewardedSetParams {
pub fn active_set_size(&self) -> u32 {
self.entry_gateways + self.exit_gateways + self.mixnodes
}
pub fn rewarded_set_size(&self) -> u32 {
self.active_set_size() + self.standby
}
pub fn ensure_valid(&self) -> Result<(), MixnetContractError> {
if self.entry_gateways == 0 || self.exit_gateways == 0 || self.mixnodes == 0 {
return Err(MixnetContractError::EmptyRoleAssignment);
}
if self.mixnodes % 3 != 0 {
return Err(MixnetContractError::UnevenLayerAssignment);
}
Ok(())
}
pub fn maximum_role_count(&self, role: Role) -> u32 {
match role {
Role::EntryGateway => self.entry_gateways,
Role::Layer1 | Role::Layer2 | Role::Layer3 => self.mixnodes / 3,
Role::ExitGateway => self.exit_gateways,
Role::Standby => self.standby,
}
}
pub fn ensure_role_count(&self, role: Role, assigned: u32) -> Result<(), MixnetContractError> {
let allowed = self.maximum_role_count(role);
if assigned > allowed {
return Err(MixnetContractError::IllegalRoleCount {
role,
assigned,
allowed,
});
}
Ok(())
}
}
/// Parameters used for rewarding particular node.
#[cw_serde]
#[derive(Copy)]
pub struct NodeRewardingParameters {
/// Performance of the particular node in the current epoch.
pub performance: Performance,
/// Amount of work performed by this node in the current epoch
/// also known as 'omega' in the paper
pub work_factor: WorkFactor,
}
impl NodeRewardingParameters {
pub fn new(performance: Performance, work_factor: WorkFactor) -> Self {
NodeRewardingParameters {
performance,
work_factor,
}
}
pub fn is_zero(&self) -> bool {
self.performance.is_zero() || self.work_factor.is_zero()
}
}
@@ -282,8 +360,8 @@ pub struct IntervalRewardingParamsUpdate {
/// Defines the new value of the interval pool emission rate.
pub interval_pool_emission: Option<Percent>,
/// Defines the new size of the rewarded set.
pub rewarded_set_size: Option<u32>,
/// Defines the parameters of the rewarded set.
pub rewarded_set_params: Option<RewardedSetParams>,
}
impl IntervalRewardingParamsUpdate {
@@ -295,10 +373,42 @@ impl IntervalRewardingParamsUpdate {
|| self.sybil_resistance_percent.is_some()
|| self.active_set_work_factor.is_some()
|| self.interval_pool_emission.is_some()
|| self.rewarded_set_size.is_some()
|| self.rewarded_set_params.is_some()
}
pub fn to_inline_json(&self) -> String {
serde_json_wasm::to_string(self).unwrap_or_else(|_| "serialisation failure".into())
}
}
/// Specification on how the active set should be updated.
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/ActiveSetUpdate.ts")
)]
#[cw_serde]
#[derive(Copy, Default)]
pub struct ActiveSetUpdate {
/// The expected number of nodes assigned entry gateway role (i.e. [`Role::EntryGateway`])
pub entry_gateways: u32,
/// The expected number of nodes assigned exit gateway role (i.e. [`Role::ExitGateway`])
pub exit_gateways: u32,
/// The expected number of nodes assigned the 'mixnode' role, i.e. total of [`Role::Layer1`], [`Role::Layer2`] and [`Role::Layer3`].
pub mixnodes: u32,
}
impl ActiveSetUpdate {
pub fn active_set_size(&self) -> u32 {
self.entry_gateways + self.exit_gateways + self.mixnodes
}
pub fn ensure_non_empty(&self) -> Result<(), MixnetContractError> {
if self.entry_gateways == 0 || self.exit_gateways == 0 || self.mixnodes == 0 {
return Err(MixnetContractError::EmptyRoleAssignment);
}
Ok(())
}
}
@@ -18,3 +18,11 @@ pub fn truncate_reward(reward: Decimal, denom: impl Into<String>) -> Coin {
pub fn truncate_reward_amount(reward: Decimal) -> Uint128 {
truncate_decimal(reward)
}
pub fn legacy_standby_work_factor() -> Decimal {
todo!()
}
pub fn legacy_active_work_factor() -> Decimal {
todo!()
}
@@ -1,7 +1,6 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{MixId, RewardedSetNodeStatus};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Coin, Decimal};
@@ -63,7 +62,10 @@ pub struct PendingRewardResponse {
/// The associated mixnode is still fully bonded, meaning it is neither unbonded
/// nor in the process of unbonding that would have finished at the epoch transition.
#[deprecated(note = "this field will be removed. use .node_still_fully_bonded instead")]
pub mixnode_still_fully_bonded: bool,
pub node_still_fully_bonded: bool,
}
/// Response containing estimation of node rewards for the current epoch.
@@ -99,13 +101,3 @@ impl EstimatedCurrentEpochRewardResponse {
}
}
}
/// Response containing paged list of all mixnodes in the rewarded set.
#[cw_serde]
pub struct PagedRewardedSetResponse {
/// Nodes in the current rewarded set.
pub nodes: Vec<(MixId, RewardedSetNodeStatus)>,
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
pub start_next_after: Option<MixId>,
}
@@ -3,23 +3,21 @@
use crate::error::MixnetContractError;
use crate::helpers::IntoBaseDecimal;
use crate::reward_params::NodeRewardParams;
use crate::reward_params::{NodeRewardingParameters, WorkFactor};
use crate::rewarding::simulator::simulated_node::SimulatedNode;
use crate::rewarding::RewardDistribution;
use crate::{
Delegation, Interval, IntervalRewardParams, MixId, MixNodeCostParams, RewardingParams,
};
use crate::{Delegation, Interval, IntervalRewardParams, NodeCostParams, NodeId, RewardingParams};
use cosmwasm_std::{Coin, Decimal};
use std::collections::BTreeMap;
pub mod simulated_node;
pub struct Simulator {
pub nodes: BTreeMap<MixId, SimulatedNode>,
pub nodes: BTreeMap<NodeId, SimulatedNode>,
pub system_rewarding_params: RewardingParams,
pub interval: Interval,
next_mix_id: MixId,
next_mix_id: NodeId,
pending_reward_pool_emission: Decimal,
}
@@ -34,6 +32,14 @@ impl Simulator {
}
}
pub fn legacy_standby_work_factor(&self) -> WorkFactor {
self.system_rewarding_params.standby_node_work()
}
pub fn legacy_active_work_factor(&self) -> WorkFactor {
self.system_rewarding_params.active_node_work()
}
fn advance_epoch(&mut self) -> Result<(), MixnetContractError> {
let updated = self.interval.advance_epoch();
@@ -53,7 +59,7 @@ impl Simulator {
let stake_saturation_point = staking_supply
/ self
.system_rewarding_params
.rewarded_set_size
.rewarded_set_size()
.into_base_decimal()?;
let updated_params = RewardingParams {
@@ -67,8 +73,7 @@ impl Simulator {
active_set_work_factor: old.active_set_work_factor,
interval_pool_emission: old.interval_pool_emission,
},
rewarded_set_size: self.system_rewarding_params.rewarded_set_size,
active_set_size: self.system_rewarding_params.active_set_size,
rewarded_set: self.system_rewarding_params.rewarded_set,
};
self.system_rewarding_params = updated_params;
@@ -82,8 +87,8 @@ impl Simulator {
pub fn bond(
&mut self,
pledge: Coin,
cost_params: MixNodeCostParams,
) -> Result<MixId, MixnetContractError> {
cost_params: NodeCostParams,
) -> Result<NodeId, MixnetContractError> {
let mix_id = self.next_mix_id;
self.nodes.insert(
@@ -105,7 +110,7 @@ impl Simulator {
&mut self,
delegator: S,
delegation: Coin,
mix_id: MixId,
mix_id: NodeId,
) -> Result<(), MixnetContractError> {
let node = self
.nodes
@@ -119,7 +124,7 @@ impl Simulator {
pub fn undelegate<S: Into<String>>(
&mut self,
delegator: S,
mix_id: MixId,
mix_id: NodeId,
) -> Result<(Coin, Coin), MixnetContractError> {
let node = self
.nodes
@@ -130,7 +135,7 @@ impl Simulator {
pub fn simulate_epoch_single_node(
&mut self,
params: NodeRewardParams,
params: NodeRewardingParameters,
) -> Result<RewardDistribution, MixnetContractError> {
assert_eq!(self.nodes.len(), 1);
@@ -148,8 +153,8 @@ impl Simulator {
pub fn simulate_epoch(
&mut self,
node_params: &BTreeMap<MixId, NodeRewardParams>,
) -> Result<BTreeMap<MixId, RewardDistribution>, MixnetContractError> {
node_params: &BTreeMap<NodeId, NodeRewardingParameters>,
) -> Result<BTreeMap<NodeId, RewardDistribution>, MixnetContractError> {
let mut params_keys = node_params.keys().copied().collect::<Vec<_>>();
params_keys.sort_unstable();
let mut node_keys = self.nodes.keys().copied().collect::<Vec<_>>();
@@ -185,7 +190,7 @@ impl Simulator {
&self,
delegation: &Delegation,
) -> Result<Decimal, MixnetContractError> {
Ok(self.nodes[&delegation.mix_id]
Ok(self.nodes[&delegation.node_id]
.rewarding_details
.determine_delegation_reward(delegation)?)
}
@@ -206,7 +211,7 @@ impl Simulator {
// assume node state doesn't change in the interval (kinda unrealistic)
pub fn simulate_full_interval(
&mut self,
node_params: &BTreeMap<MixId, NodeRewardParams>,
node_params: &BTreeMap<NodeId, NodeRewardingParameters>,
) -> Result<(), MixnetContractError> {
for _ in 0..self.interval.epochs_in_interval() {
self.simulate_epoch(node_params)?;
@@ -219,6 +224,7 @@ impl Simulator {
mod tests {
use super::*;
use crate::helpers::compare_decimals;
use crate::reward_params::RewardedSetParams;
use crate::Percent;
use cosmwasm_std::testing::mock_env;
use std::time::Duration;
@@ -226,6 +232,7 @@ mod tests {
#[cfg(test)]
mod single_node_case {
use super::*;
use crate::reward_params::RewardedSetParams;
use crate::rewarding::helpers::truncate_reward_amount;
use cosmwasm_std::coin;
@@ -237,15 +244,23 @@ mod tests {
let profit_margin = Percent::from_percentage_value(10).unwrap();
let interval_operating_cost = Coin::new(40_000_000, "unym");
let epochs_in_interval = 720u32;
let rewarded_set_size = 240;
let active_set_size = 100;
let interval_pool_emission = Percent::from_percentage_value(2).unwrap();
// the import values here are active set being 100 and rewarded set being 240
// since those are the values we were using in the past
let rewarded_set = RewardedSetParams {
entry_gateways: 20,
exit_gateways: 50,
mixnodes: 30,
standby: 140,
};
let reward_pool = 250_000_000_000_000u128;
let staking_supply = 100_000_000_000_000u128;
let epoch_reward_budget =
interval_pool_emission * Decimal::from_ratio(reward_pool, epochs_in_interval);
let stake_saturation_point = Decimal::from_ratio(staking_supply, rewarded_set_size);
let stake_saturation_point =
Decimal::from_ratio(staking_supply, rewarded_set.rewarded_set_size());
let rewarding_params = RewardingParams {
interval: IntervalRewardParams {
@@ -258,8 +273,7 @@ mod tests {
active_set_work_factor: Decimal::percent(1000), // value '10'
interval_pool_emission,
},
rewarded_set_size,
active_set_size,
rewarded_set,
};
let interval = Interval::init_interval(
@@ -270,7 +284,7 @@ mod tests {
let initial_pledge = Coin::new(initial_pledge, "unym");
let mut simulator = Simulator::new(rewarding_params, interval);
let cost_params = MixNodeCostParams {
let cost_params = NodeCostParams {
profit_margin_percent: profit_margin,
interval_operating_cost,
};
@@ -315,8 +329,10 @@ mod tests {
fn simulator_returns_expected_values_for_base_case() {
let mut simulator = base_simulator(10000_000000);
let epoch_params =
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
let epoch_params = NodeRewardingParameters::new(
Percent::from_percentage_value(100).unwrap(),
simulator.legacy_active_work_factor(),
);
let rewards = simulator.simulate_epoch_single_node(epoch_params).unwrap();
assert_eq!(rewards.delegates, Decimal::zero());
@@ -334,8 +350,10 @@ mod tests {
.delegate("alice", Coin::new(18000_000000, "unym"), 0)
.unwrap();
let node_params =
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
let node_params = NodeRewardingParameters::new(
Percent::from_percentage_value(100).unwrap(),
simulator.legacy_active_work_factor(),
);
let rewards = simulator.simulate_epoch_single_node(node_params).unwrap();
compare_decimals(
@@ -364,8 +382,10 @@ mod tests {
#[test]
fn delegation_and_undelegation() {
let mut simulator = base_simulator(10000_000000);
let node_params =
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
let node_params = NodeRewardingParameters::new(
Percent::from_percentage_value(100).unwrap(),
simulator.legacy_active_work_factor(),
);
let rewards1 = simulator.simulate_epoch_single_node(node_params).unwrap();
let expected_operator1 = "1128452.5416104363".parse().unwrap();
@@ -411,8 +431,10 @@ mod tests {
// essentially all delegators' rewards (and the operator itself) are still correctly computed
let original_pledge = coin(10000_000000, "unym");
let mut simulator = base_simulator(original_pledge.amount.u128());
let node_params =
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
let node_params = NodeRewardingParameters::new(
Percent::from_percentage_value(100).unwrap(),
simulator.legacy_active_work_factor(),
);
// add 2 delegations at genesis (because it makes things easier and as shown with previous tests
// delegating at different times still work)
@@ -454,8 +476,10 @@ mod tests {
fn withdrawing_delegator_reward() {
// essentially all delegators' rewards (and the operator itself) are still correctly computed
let mut simulator = base_simulator(10000_000000);
let node_params =
NodeRewardParams::new(Percent::from_percentage_value(100).unwrap(), true);
let node_params = NodeRewardingParameters::new(
Percent::from_percentage_value(100).unwrap(),
simulator.legacy_active_work_factor(),
);
// add 2 delegations at genesis (because it makes things easier and as shown with previous tests
// delegating at different times still work)
@@ -524,7 +548,7 @@ mod tests {
fn simulating_multiple_epochs() {
let mut simulator = base_simulator(10000_000000);
let mut is_active = true;
let mut work_factor = simulator.legacy_active_work_factor();
let mut performance = Percent::from_percentage_value(100).unwrap();
for epoch in 0..720 {
if epoch == 0 {
@@ -538,7 +562,7 @@ mod tests {
.unwrap()
}
if epoch == 89 {
is_active = false;
work_factor = simulator.legacy_standby_work_factor();
}
if epoch == 123 {
simulator
@@ -560,7 +584,7 @@ mod tests {
// TODO: figure out if there's a good way to verify whether `reward` is what we expect it to be
}
if epoch == 345 {
is_active = true;
work_factor = simulator.legacy_active_work_factor();
}
if epoch == 358 {
performance = Percent::from_percentage_value(100).unwrap();
@@ -579,7 +603,7 @@ mod tests {
// this has to always hold
check_rewarding_invariant(&simulator);
let node_params = NodeRewardParams::new(performance, is_active);
let node_params = NodeRewardingParameters::new(performance, work_factor);
simulator.simulate_epoch_single_node(node_params).unwrap();
}
@@ -600,15 +624,23 @@ mod tests {
// rather than just checking the final results
let epochs_in_interval = 1u32;
let rewarded_set_size = 10;
let active_set_size = 6;
let interval_pool_emission = Percent::from_percentage_value(2).unwrap();
// the import values here are active set being 6 and rewarded set being 10
// since those are the values we were using in the past
let rewarded_set = RewardedSetParams {
entry_gateways: 1,
exit_gateways: 2,
mixnodes: 3,
standby: 4,
};
let reward_pool = 250_000_000_000_000u128;
let staking_supply = 100_000_000_000_000u128;
let epoch_reward_budget =
interval_pool_emission * Decimal::from_ratio(reward_pool, epochs_in_interval);
let stake_saturation_point = Decimal::from_ratio(staking_supply, rewarded_set_size);
let stake_saturation_point =
Decimal::from_ratio(staking_supply, rewarded_set.rewarded_set_size());
let rewarding_params = RewardingParams {
interval: IntervalRewardParams {
@@ -621,8 +653,7 @@ mod tests {
active_set_work_factor: Decimal::percent(1000), // value '10'
interval_pool_emission,
},
rewarded_set_size,
active_set_size,
rewarded_set,
};
let interval = Interval::init_interval(
@@ -636,7 +667,7 @@ mod tests {
let n0 = simulator
.bond(
Coin::new(11_000_000_000000, "unym"),
MixNodeCostParams {
NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
@@ -649,7 +680,7 @@ mod tests {
let n1 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
@@ -662,7 +693,7 @@ mod tests {
let n2 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
@@ -675,7 +706,7 @@ mod tests {
let n3 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(0).unwrap(),
interval_operating_cost: Coin::new(500_000_000, "unym"),
},
@@ -688,7 +719,7 @@ mod tests {
let n4 = simulator
.bond(
Coin::new(1000_000000, "unym"),
MixNodeCostParams {
NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
@@ -701,7 +732,7 @@ mod tests {
let n5 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
@@ -714,7 +745,7 @@ mod tests {
let n6 = simulator
.bond(
Coin::new(11_000_000_000000, "unym"),
MixNodeCostParams {
NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
@@ -727,7 +758,7 @@ mod tests {
let n7 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
@@ -740,7 +771,7 @@ mod tests {
let n8 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(0).unwrap(),
interval_operating_cost: Coin::new(500_000_000, "unym"),
},
@@ -753,7 +784,7 @@ mod tests {
let n9 = simulator
.bond(
Coin::new(1_000_000_000000, "unym"),
MixNodeCostParams {
NodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: Coin::new(40_000_000, "unym"),
},
@@ -767,17 +798,20 @@ mod tests {
let uptime_09 = Percent::from_percentage_value(90).unwrap();
let uptime_0 = Percent::from_percentage_value(0).unwrap();
let active_work = simulator.legacy_active_work_factor();
let standby_work = simulator.legacy_standby_work_factor();
let node_params = [
(n0, NodeRewardParams::new(uptime_1, true)),
(n1, NodeRewardParams::new(uptime_1, true)),
(n2, NodeRewardParams::new(uptime_1, true)),
(n3, NodeRewardParams::new(uptime_09, true)),
(n4, NodeRewardParams::new(uptime_09, true)),
(n5, NodeRewardParams::new(uptime_0, true)),
(n6, NodeRewardParams::new(uptime_1, false)),
(n7, NodeRewardParams::new(uptime_1, false)),
(n8, NodeRewardParams::new(uptime_09, false)),
(n9, NodeRewardParams::new(uptime_0, false)),
(n0, NodeRewardingParameters::new(uptime_1, active_work)),
(n1, NodeRewardingParameters::new(uptime_1, active_work)),
(n2, NodeRewardingParameters::new(uptime_1, active_work)),
(n3, NodeRewardingParameters::new(uptime_09, active_work)),
(n4, NodeRewardingParameters::new(uptime_09, active_work)),
(n5, NodeRewardingParameters::new(uptime_0, active_work)),
(n6, NodeRewardingParameters::new(uptime_1, standby_work)),
(n7, NodeRewardingParameters::new(uptime_1, standby_work)),
(n8, NodeRewardingParameters::new(uptime_09, standby_work)),
(n9, NodeRewardingParameters::new(uptime_0, standby_work)),
]
.into_iter()
.collect::<BTreeMap<_, _>>();
@@ -1,7 +1,7 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{Delegation, EpochId, MixId, MixNodeCostParams, MixNodeRewarding};
use crate::{Delegation, EpochId, NodeCostParams, NodeId, NodeRewarding};
use cosmwasm_std::{Addr, Coin};
use std::collections::HashMap;
@@ -9,21 +9,21 @@ use crate::error::MixnetContractError;
use crate::rewarding::helpers::truncate_reward;
pub struct SimulatedNode {
pub mix_id: MixId,
pub rewarding_details: MixNodeRewarding,
pub mix_id: NodeId,
pub rewarding_details: NodeRewarding,
pub delegations: HashMap<String, Delegation>,
}
impl SimulatedNode {
pub fn new(
mix_id: MixId,
cost_params: MixNodeCostParams,
mix_id: NodeId,
cost_params: NodeCostParams,
initial_pledge: &Coin,
current_epoch: EpochId,
) -> Result<Self, MixnetContractError> {
Ok(SimulatedNode {
mix_id,
rewarding_details: MixNodeRewarding::initialise_new(
rewarding_details: NodeRewarding::initialise_new(
cost_params,
initial_pledge,
current_epoch,
@@ -59,8 +59,8 @@ impl SimulatedNode {
) -> Result<(Coin, Coin), MixnetContractError> {
let delegator = delegator.into();
let delegation = self.delegations.remove(&delegator).ok_or(
MixnetContractError::NoMixnodeDelegationFound {
mix_id: MixId::MAX,
MixnetContractError::NodeDelegationNotFound {
node_id: NodeId::MAX,
address: delegator,
proxy: None,
},
@@ -1,8 +1,8 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::families::FamilyHead;
use crate::{Gateway, IdentityKey, MixNode, MixNodeCostParams};
use crate::nym_node::NymNode;
use crate::{Gateway, MixNode, NodeCostParams};
use contracts_common::signing::{
ContractMessageContent, LegacyContractMessageContent, MessageType, Nonce, SignableMessage,
SigningPurpose,
@@ -12,20 +12,20 @@ use serde::Serialize;
pub type SignableMixNodeBondingMsg = SignableMessage<ContractMessageContent<MixnodeBondingPayload>>;
pub type SignableGatewayBondingMsg = SignableMessage<ContractMessageContent<GatewayBondingPayload>>;
pub type SignableNymNodeBondingMsg = SignableMessage<ContractMessageContent<NymNodeBondingPayload>>;
pub type SignableLegacyMixNodeBondingMsg =
SignableMessage<LegacyContractMessageContent<MixnodeBondingPayload>>;
pub type SignableLegacyGatewayBondingMsg =
SignableMessage<LegacyContractMessageContent<GatewayBondingPayload>>;
pub type SignableFamilyJoinPermitMsg = SignableMessage<FamilyJoinPermit>;
#[derive(Serialize)]
pub struct MixnodeBondingPayload {
mix_node: MixNode,
cost_params: MixNodeCostParams,
cost_params: NodeCostParams,
}
impl MixnodeBondingPayload {
pub fn new(mix_node: MixNode, cost_params: MixNodeCostParams) -> Self {
pub fn new(mix_node: MixNode, cost_params: NodeCostParams) -> Self {
Self {
mix_node,
cost_params,
@@ -44,7 +44,7 @@ pub fn construct_mixnode_bonding_sign_payload(
sender: Addr,
pledge: Coin,
mix_node: MixNode,
cost_params: MixNodeCostParams,
cost_params: NodeCostParams,
) -> SignableMixNodeBondingMsg {
let payload = MixnodeBondingPayload::new(mix_node, cost_params);
let content = ContractMessageContent::new(sender, vec![pledge], payload);
@@ -57,7 +57,7 @@ pub fn construct_legacy_mixnode_bonding_sign_payload(
sender: Addr,
pledge: Coin,
mix_node: MixNode,
cost_params: MixNodeCostParams,
cost_params: NodeCostParams,
) -> SignableLegacyMixNodeBondingMsg {
let payload = MixnodeBondingPayload::new(mix_node, cost_params);
let content: LegacyContractMessageContent<_> =
@@ -109,39 +109,48 @@ pub fn construct_legacy_gateway_bonding_sign_payload(
}
#[derive(Serialize)]
pub struct FamilyJoinPermit {
// the granter of this permit
family_head: FamilyHead,
// the actual member we want to permit to join
member_node: IdentityKey,
pub struct NymNodeBondingPayload {
nym_node: NymNode,
cost_params: NodeCostParams,
}
impl FamilyJoinPermit {
pub fn new(family_head: FamilyHead, member_node: IdentityKey) -> Self {
Self {
family_head,
member_node,
impl NymNodeBondingPayload {
pub fn new(nym_node: NymNode, cost_params: NodeCostParams) -> Self {
NymNodeBondingPayload {
nym_node,
cost_params,
}
}
}
impl SigningPurpose for FamilyJoinPermit {
impl SigningPurpose for NymNodeBondingPayload {
fn message_type() -> MessageType {
MessageType::new("family-join-permit")
MessageType::new("nym-node-bonding")
}
}
pub fn construct_family_join_permit(
pub fn construct_nym_node_bonding_sign_payload(
nonce: Nonce,
family_head: FamilyHead,
member_node: IdentityKey,
) -> SignableFamilyJoinPermitMsg {
let payload = FamilyJoinPermit::new(family_head, member_node);
sender: Addr,
pledge: Coin,
nym_node: NymNode,
cost_params: NodeCostParams,
) -> SignableNymNodeBondingMsg {
let payload = NymNodeBondingPayload::new(nym_node, cost_params);
let content = ContractMessageContent::new(sender, vec![pledge], payload);
// 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)
SignableMessage::new(nonce, content)
}
// 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)
pub fn construct_generic_node_bonding_payload<T>(
nonce: Nonce,
sender: Addr,
pledge: Coin,
payload: T,
) -> SignableMessage<ContractMessageContent<T>>
where
T: SigningPurpose,
{
let content = ContractMessageContent::new(sender, vec![pledge], payload);
SignableMessage::new(nonce, content)
}
@@ -1,22 +1,127 @@
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::error::MixnetContractError;
use crate::Layer;
use crate::nym_node::Role;
use contracts_common::Percent;
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Coin;
use cosmwasm_std::{Addr, Uint128};
use std::fmt::{Display, Formatter};
use std::ops::Index;
// type aliases for better reasoning about available data
pub type SphinxKey = String;
pub type SphinxKeyRef<'a> = &'a str;
pub type MixId = u32;
pub type NodeId = u32;
pub type BlockHeight = u64;
#[cw_serde]
pub struct RoleAssignment {
pub role: Role,
pub nodes: Vec<NodeId>,
}
impl RoleAssignment {
pub fn is_final_assignment(&self) -> bool {
self.role.is_standby()
}
}
#[cw_serde]
#[derive(Default)]
pub struct RewardedSet {
pub entry_gateways: Vec<NodeId>,
pub exit_gateways: Vec<NodeId>,
pub layer1: Vec<NodeId>,
pub layer2: Vec<NodeId>,
pub layer3: Vec<NodeId>,
pub standby: Vec<NodeId>,
}
impl RewardedSet {
pub fn is_empty(&self) -> bool {
self.entry_gateways.is_empty()
&& self.exit_gateways.is_empty()
&& self.layer1.is_empty()
&& self.layer2.is_empty()
&& self.layer3.is_empty()
&& self.standby.is_empty()
}
pub fn active_set_size(&self) -> usize {
self.entry_gateways.len()
+ self.exit_gateways.len()
+ self.layer1.len()
+ self.layer2.len()
+ self.layer3.len()
}
pub fn rewarded_set_size(&self) -> usize {
self.active_set_size() + self.standby.len()
}
pub fn try_get_mix_layer(&self, node_id: &NodeId) -> Option<u8> {
if self.layer1.contains(node_id) {
Some(1)
} else if self.layer2.contains(node_id) {
Some(2)
} else if self.layer3.contains(node_id) {
Some(3)
} else {
None
}
}
pub fn is_entry(&self, node_id: &NodeId) -> bool {
self.entry_gateways.contains(node_id)
}
pub fn is_exit(&self, node_id: &NodeId) -> bool {
self.exit_gateways.contains(node_id)
}
pub fn is_active_mixnode(&self, node_id: &NodeId) -> bool {
self.layer1.contains(node_id)
|| self.layer2.contains(node_id)
|| self.layer3.contains(node_id)
}
pub fn gateways(&self) -> Vec<NodeId> {
let mut gateways = Vec::with_capacity(self.entry_gateways.len() + self.exit_gateways.len());
for entry in &self.entry_gateways {
gateways.push(*entry)
}
for exit in &self.exit_gateways {
gateways.push(*exit)
}
gateways
}
pub fn active_mixnodes(&self) -> Vec<NodeId> {
let mut mixnodes =
Vec::with_capacity(self.layer1.len() + self.layer2.len() + self.layer3.len());
for mix in &self.layer1 {
mixnodes.push(*mix)
}
for mix in &self.layer2 {
mixnodes.push(*mix)
}
for mix in &self.layer3 {
mixnodes.push(*mix)
}
mixnodes
}
pub fn is_standby(&self, node_id: &NodeId) -> bool {
self.standby.contains(node_id)
}
}
#[cw_serde]
pub struct RangedValue<T> {
pub minimum: T,
@@ -76,113 +181,6 @@ where
}
}
/// Specifies layer assignment for the given mixnode.
#[cw_serde]
pub struct LayerAssignment {
/// The id of the mixnode.
mix_id: MixId,
/// The layer to which it's going to be assigned
layer: Layer,
}
impl LayerAssignment {
pub fn new(mix_id: MixId, layer: Layer) -> Self {
LayerAssignment { mix_id, layer }
}
pub fn mix_id(&self) -> MixId {
self.mix_id
}
pub fn layer(&self) -> Layer {
self.layer
}
}
/// The current layer distribution of the mix network.
#[cw_serde]
#[derive(Copy, Default)]
pub struct LayerDistribution {
/// Number of nodes on the first layer.
pub layer1: u64,
/// Number of nodes on the second layer.
pub layer2: u64,
/// Number of nodes on the third layer.
pub layer3: u64,
}
impl LayerDistribution {
pub fn choose_with_fewest(&self) -> Layer {
let layers = [
(Layer::One, self.layer1),
(Layer::Two, self.layer2),
(Layer::Three, self.layer3),
];
// we explicitly put 3 elements into the iterator, so the iterator is DEFINITELY
// not empty and thus the unwrap cannot fail
#[allow(clippy::unwrap_used)]
layers.iter().min_by_key(|x| x.1).unwrap().0
}
pub fn increment_layer_count(&mut self, layer: Layer) {
match layer {
Layer::One => self.layer1 += 1,
Layer::Two => self.layer2 += 1,
Layer::Three => self.layer3 += 1,
}
}
pub fn decrement_layer_count(&mut self, layer: Layer) -> Result<(), MixnetContractError> {
match layer {
Layer::One => {
self.layer1 =
self.layer1
.checked_sub(1)
.ok_or(MixnetContractError::OverflowSubtraction {
minuend: self.layer1,
subtrahend: 1,
})?
}
Layer::Two => {
self.layer2 =
self.layer2
.checked_sub(1)
.ok_or(MixnetContractError::OverflowSubtraction {
minuend: self.layer2,
subtrahend: 1,
})?
}
Layer::Three => {
self.layer3 =
self.layer3
.checked_sub(1)
.ok_or(MixnetContractError::OverflowSubtraction {
minuend: self.layer3,
subtrahend: 1,
})?
}
}
Ok(())
}
}
impl Index<Layer> for LayerDistribution {
type Output = u64;
fn index(&self, index: Layer) -> &Self::Output {
match index {
Layer::One => &self.layer1,
Layer::Two => &self.layer2,
Layer::Three => &self.layer3,
}
}
}
/// The current state of the mixnet contract.
#[cw_serde]
pub struct ContractState {
@@ -212,13 +210,10 @@ pub struct ContractState {
#[cw_serde]
pub struct ContractStateParams {
/// Minimum amount a delegator must stake in orders for his delegation to get accepted.
pub minimum_mixnode_delegation: Option<Coin>,
pub minimum_delegation: Option<Coin>,
/// Minimum amount a mixnode must pledge to get into the system.
pub minimum_mixnode_pledge: Coin,
/// Minimum amount a gateway must pledge to get into the system.
pub minimum_gateway_pledge: Coin,
/// Minimum amount a node must pledge to get into the system.
pub minimum_pledge: Coin,
/// Defines the allowed profit margin range of operators.
/// default: 0% - 100%
@@ -3,7 +3,7 @@
use crate::account::VestingAccountStorageKey;
use cosmwasm_std::{Addr, Coin, OverflowError, StdError, Uint128};
use mixnet_contract_common::MixId;
use mixnet_contract_common::NodeId;
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
@@ -50,7 +50,7 @@ pub enum VestingContractError {
MultipleDenoms,
#[error("VESTING ({}): No delegations found for account {0}, mix_identity {1}", line!())]
NoSuchDelegation(Addr, MixId),
NoSuchDelegation(Addr, NodeId),
#[error("VESTING ({}): Only mixnet contract can perform this operation, got {0}", line!())]
NotMixnetContract(Addr),
@@ -95,7 +95,7 @@ pub enum VestingContractError {
TooManyDelegations {
address: Addr,
acc_id: VestingAccountStorageKey,
mix_id: MixId,
mix_id: NodeId,
num: u32,
cap: u32,
},
@@ -6,7 +6,7 @@
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Addr, Coin};
use mixnet_contract_common::MixId;
use mixnet_contract_common::NodeId;
pub mod account;
pub mod error;
@@ -64,7 +64,7 @@ pub struct DelegationTimesResponse {
pub account_id: u32,
/// Id of the mixnode towards which the delegation was made
pub mix_id: MixId,
pub mix_id: NodeId,
/// All timestamps where a delegation was made
pub delegation_timestamps: Vec<u64>,
@@ -77,7 +77,7 @@ pub struct AllDelegationsResponse {
pub delegations: Vec<VestingDelegation>,
/// Field indicating paging information for the following queries if the caller wishes to get further entries.
pub start_next_after: Option<(u32, MixId, u64)>,
pub start_next_after: Option<(u32, NodeId, u64)>,
}
/// Basic information regarding particular vesting account alongside the amount of vesting coins.
@@ -5,11 +5,10 @@ use crate::{PledgeCap, VestingSpecification};
use contracts_common::signing::MessageSignature;
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Coin, Timestamp};
use mixnet_contract_common::families::FamilyHead;
use mixnet_contract_common::{
gateway::GatewayConfigUpdate,
mixnode::{MixNodeConfigUpdate, MixNodeCostParams},
Gateway, IdentityKey, MixId, MixNode,
mixnode::{MixNodeConfigUpdate, NodeCostParams},
Gateway, MixNode, NodeId,
};
#[cfg(feature = "schema")]
@@ -36,32 +35,16 @@ pub struct MigrateMsg {}
#[cw_serde]
pub enum ExecuteMsg {
// Families
/// Only owner of the node can crate the family with node as head
CreateFamily {
label: String,
},
/// Family head needs to sign the joining node IdentityKey, the Node provides its signature signaling consent to join the family
JoinFamily {
join_permit: MessageSignature,
family_head: FamilyHead,
},
LeaveFamily {
family_head: FamilyHead,
},
KickFamilyMember {
member: IdentityKey,
},
TrackReward {
amount: Coin,
address: String,
},
ClaimOperatorReward {},
ClaimDelegatorReward {
mix_id: MixId,
mix_id: NodeId,
},
UpdateMixnodeCostParams {
new_costs: MixNodeCostParams,
new_costs: NodeCostParams,
},
UpdateMixnodeConfig {
new_config: MixNodeConfigUpdate,
@@ -70,12 +53,12 @@ pub enum ExecuteMsg {
address: String,
},
DelegateToMixnode {
mix_id: MixId,
mix_id: NodeId,
amount: Coin,
on_behalf_of: Option<String>,
},
UndelegateFromMixnode {
mix_id: MixId,
mix_id: NodeId,
on_behalf_of: Option<String>,
},
CreateAccount {
@@ -89,12 +72,12 @@ pub enum ExecuteMsg {
},
TrackUndelegation {
owner: String,
mix_id: MixId,
mix_id: NodeId,
amount: Coin,
},
BondMixnode {
mix_node: MixNode,
cost_params: MixNodeCostParams,
cost_params: NodeCostParams,
owner_signature: MessageSignature,
amount: Coin,
},
@@ -142,17 +125,13 @@ pub enum ExecuteMsg {
// no need to track migrated gateways as there are no vesting gateways on mainnet
TrackMigratedDelegation {
owner: String,
mix_id: MixId,
mix_id: NodeId,
},
}
impl ExecuteMsg {
pub fn name(&self) -> &str {
match self {
ExecuteMsg::CreateFamily { .. } => "VestingExecuteMsg::CreateFamily",
ExecuteMsg::JoinFamily { .. } => "VestingExecuteMsg::JoinFamily",
ExecuteMsg::LeaveFamily { .. } => "VestingExecuteMsg::LeaveFamily",
ExecuteMsg::KickFamilyMember { .. } => "VestingExecuteMsg::KickFamilyMember",
ExecuteMsg::TrackReward { .. } => "VestingExecuteMsg::TrackReward",
ExecuteMsg::ClaimOperatorReward { .. } => "VestingExecuteMsg::ClaimOperatorReward",
ExecuteMsg::ClaimDelegatorReward { .. } => "VestingExecuteMsg::ClaimDelegatorReward",
@@ -374,7 +353,7 @@ pub enum QueryMsg {
address: String,
/// Id of the mixnode towards which the delegation has been made.
mix_id: MixId,
mix_id: NodeId,
/// Block timestamp of the delegation.
block_timestamp_secs: u64,
@@ -387,7 +366,7 @@ pub enum QueryMsg {
address: String,
/// Id of the mixnode towards which the delegations have been made.
mix_id: MixId,
mix_id: NodeId,
},
/// Returns timestamps of delegations made towards particular mixnode by the provided vesting account address.
@@ -397,14 +376,14 @@ pub enum QueryMsg {
address: String,
/// Id of the mixnode towards which the delegations have been made.
mix_id: MixId,
mix_id: NodeId,
},
/// Returns all active delegations made with vesting tokens stored in this contract.
#[cfg_attr(feature = "schema", returns(AllDelegationsResponse))]
GetAllDelegations {
/// Pagination control for the values returned by the query. Note that the provided value itself will **not** be used for the response.
start_after: Option<(u32, MixId, u64)>,
start_after: Option<(u32, NodeId, u64)>,
/// Controls the maximum number of entries returned by the query. Note that too large values will be overwritten by a saner default.
limit: Option<u32>,
@@ -4,7 +4,7 @@
use contracts_common::Percent;
use cosmwasm_schema::cw_serde;
use cosmwasm_std::{Coin, Timestamp, Uint128};
use mixnet_contract_common::MixId;
use mixnet_contract_common::NodeId;
use std::str::FromStr;
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
@@ -155,7 +155,7 @@ pub struct VestingDelegation {
pub account_id: u32,
/// The id of the mixnode towards which the delegation has been made.
pub mix_id: MixId,
pub mix_id: NodeId,
/// The block timestamp when the delegation has been made.
pub block_timestamp: u64,
@@ -165,7 +165,7 @@ pub struct VestingDelegation {
}
impl VestingDelegation {
pub fn storage_key(&self) -> (u32, MixId, u64) {
pub fn storage_key(&self) -> (u32, NodeId, u64) {
(self.account_id, self.mix_id, self.block_timestamp)
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::MixId;
use crate::NodeId;
use nym_sphinx::chunking::ChunkingError;
use nym_sphinx::receiver::MessageRecoveryError;
use nym_topology::NymTopologyError;
@@ -19,7 +19,7 @@ pub enum NetworkTestingError {
InvalidTopology(#[from] NymTopologyError),
#[error("The specified mixnode (id: {mix_id}) doesn't exist")]
NonExistentMixnode { mix_id: MixId },
NonExistentMixnode { mix_id: NodeId },
#[error("The specified mixnode (identity: {mix_identity}) doesn't exist")]
NonExistentMixnodeIdentity { mix_identity: String },
+1 -1
View File
@@ -15,7 +15,7 @@ pub use nym_sphinx::{
pub use tester::NodeTester;
// it feels wrong to redefine it, but I don't want to import the whole of contract commons just for this one type
pub(crate) type MixId = u32;
pub(crate) type NodeId = u32;
#[macro_export]
macro_rules! log_err {
+10 -8
View File
@@ -3,6 +3,7 @@
use crate::error::NetworkTestingError;
use crate::node::TestableNode;
use crate::NodeId;
use nym_sphinx::message::NymMessage;
use nym_topology::{gateway, mix};
use serde::de::DeserializeOwned;
@@ -34,13 +35,13 @@ impl<T> TestMessage<T> {
}
}
pub fn new_mix(node: &mix::Node, msg_id: u32, total_msgs: u32, ext: T) -> Self {
pub fn new_mix(node: &mix::LegacyNode, msg_id: u32, total_msgs: u32, ext: T) -> Self {
Self::new(node, msg_id, total_msgs, ext)
}
pub fn new_gateway(node: &gateway::Node, msg_id: u32, total_msgs: u32, ext: T) -> Self {
Self::new(node, msg_id, total_msgs, ext)
}
// pub fn new_gateway(node: &gateway::Node, msg_id: u32, total_msgs: u32, ext: T) -> Self {
// Self::new(node, msg_id, total_msgs, ext)
// }
pub fn new_serialized<N>(
node: N,
@@ -72,7 +73,7 @@ impl<T> TestMessage<T> {
}
pub fn mix_plaintexts(
node: &mix::Node,
node: &mix::LegacyNode,
total_msgs: u32,
ext: T,
) -> Result<Vec<Vec<u8>>, NetworkTestingError>
@@ -82,15 +83,16 @@ impl<T> TestMessage<T> {
Self::new_plaintexts(node, total_msgs, ext)
}
pub fn gateway_plaintexts(
node: &gateway::Node,
pub fn legacy_gateway_plaintexts(
node: &gateway::LegacyNode,
node_id: NodeId,
total_msgs: u32,
ext: T,
) -> Result<Vec<Vec<u8>>, NetworkTestingError>
where
T: Serialize + Clone,
{
Self::new_plaintexts(node, total_msgs, ext)
Self::new_plaintexts(&(node, node_id), total_msgs, ext)
}
pub fn as_json_string(&self) -> Result<String, NetworkTestingError>
+32 -22
View File
@@ -1,7 +1,7 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::MixId;
use crate::NodeId;
use nym_topology::{gateway, mix};
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
@@ -9,27 +9,27 @@ use std::fmt::{Display, Formatter};
#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)]
pub struct TestableNode {
pub encoded_identity: String,
pub owner: String,
pub node_id: NodeId,
#[serde(rename = "type")]
pub typ: NodeType,
}
impl TestableNode {
pub fn new(encoded_identity: String, owner: String, typ: NodeType) -> Self {
pub fn new(encoded_identity: String, typ: NodeType, node_id: NodeId) -> Self {
TestableNode {
encoded_identity,
owner,
node_id,
typ,
}
}
pub fn new_mixnode(encoded_identity: String, owner: String, mix_id: MixId) -> Self {
TestableNode::new(encoded_identity, owner, NodeType::Mixnode { mix_id })
pub fn new_mixnode(encoded_identity: String, node_id: NodeId) -> Self {
TestableNode::new(encoded_identity, NodeType::Mixnode, node_id)
}
pub fn new_gateway(encoded_identity: String, owner: String) -> Self {
TestableNode::new(encoded_identity, owner, NodeType::Gateway)
pub fn new_gateway(encoded_identity: String, node_id: NodeId) -> Self {
TestableNode::new(encoded_identity, NodeType::Gateway, node_id)
}
pub fn is_mixnode(&self) -> bool {
@@ -37,24 +37,34 @@ impl TestableNode {
}
}
impl<'a> From<&'a mix::Node> for TestableNode {
fn from(value: &'a mix::Node) -> Self {
impl<'a> From<&'a mix::LegacyNode> for TestableNode {
fn from(value: &'a mix::LegacyNode) -> Self {
TestableNode {
encoded_identity: value.identity_key.to_base58_string(),
owner: value.owner.as_ref().cloned().unwrap_or_default(),
typ: NodeType::Mixnode {
mix_id: value.mix_id,
},
typ: NodeType::Mixnode,
node_id: value.mix_id,
}
}
}
impl<'a> From<&'a gateway::Node> for TestableNode {
fn from(value: &'a gateway::Node) -> Self {
impl<'a> From<(&'a gateway::LegacyNode, NodeId)> for TestableNode {
fn from((gateway, node_id): (&'a gateway::LegacyNode, NodeId)) -> Self {
(&(gateway, node_id)).into()
}
}
impl<'a> From<&'a (gateway::LegacyNode, NodeId)> for TestableNode {
fn from((gateway, node_id): &'a (gateway::LegacyNode, NodeId)) -> Self {
(gateway, *node_id).into()
}
}
impl<'a, 'b> From<&'a (&'b gateway::LegacyNode, NodeId)> for TestableNode {
fn from((gateway, node_id): &'a (&'b gateway::LegacyNode, NodeId)) -> Self {
TestableNode {
encoded_identity: value.identity_key.to_base58_string(),
owner: value.owner.as_ref().cloned().unwrap_or_default(),
encoded_identity: gateway.identity_key.to_base58_string(),
typ: NodeType::Gateway,
node_id: *node_id,
}
}
}
@@ -63,8 +73,8 @@ impl Display for TestableNode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{} {} owned by {}",
self.typ, self.encoded_identity, self.owner
"{}-{}: {}",
self.typ, self.node_id, self.encoded_identity
)
}
}
@@ -72,7 +82,7 @@ impl Display for TestableNode {
#[derive(Serialize, Deserialize, Hash, Clone, Copy, Debug, Eq, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum NodeType {
Mixnode { mix_id: MixId },
Mixnode,
Gateway,
}
@@ -85,7 +95,7 @@ impl NodeType {
impl Display for NodeType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
NodeType::Mixnode { mix_id } => write!(f, "mixnode (mix_id {mix_id})"),
NodeType::Mixnode => write!(f, "mixnode"),
NodeType::Gateway => write!(f, "gateway"),
}
}
+21 -11
View File
@@ -3,7 +3,7 @@
use crate::error::NetworkTestingError;
use crate::Empty;
use crate::MixId;
use crate::NodeId;
use crate::TestMessage;
use nym_sphinx::acknowledgements::AckKey;
use nym_sphinx::addressing::clients::Recipient;
@@ -76,13 +76,13 @@ where
self
}
pub fn testable_mix_topology(&self, node: &mix::Node) -> NymTopology {
pub fn testable_mix_topology(&self, node: &mix::LegacyNode) -> NymTopology {
let mut topology = self.base_topology.clone();
topology.set_mixes_in_layer(node.layer as u8, vec![node.clone()]);
topology
}
pub fn testable_gateway_topology(&self, gateway: &gateway::Node) -> NymTopology {
pub fn testable_gateway_topology(&self, gateway: &gateway::LegacyNode) -> NymTopology {
let mut topology = self.base_topology.clone();
topology.set_gateways(vec![gateway.clone()]);
topology
@@ -90,7 +90,7 @@ where
pub fn simple_mixnode_test_packets(
&mut self,
mix: &mix::Node,
mix: &mix::LegacyNode,
test_packets: u32,
) -> Result<Vec<PreparedFragment>, NetworkTestingError> {
self.mixnode_test_packets(mix, Empty, test_packets, None)
@@ -98,7 +98,7 @@ where
pub fn mixnode_test_packets<T>(
&mut self,
mix: &mix::Node,
mix: &mix::LegacyNode,
msg_ext: T,
test_packets: u32,
custom_recipient: Option<Recipient>,
@@ -122,7 +122,7 @@ where
pub fn mixnodes_test_packets<T>(
&mut self,
nodes: &[mix::Node],
nodes: &[mix::LegacyNode],
msg_ext: T,
test_packets: u32,
custom_recipient: Option<Recipient>,
@@ -145,7 +145,7 @@ where
pub fn existing_mixnode_test_packets<T>(
&mut self,
mix_id: MixId,
mix_id: NodeId,
msg_ext: T,
test_packets: u32,
custom_recipient: Option<Recipient>,
@@ -182,9 +182,10 @@ where
self.mixnode_test_packets(&node.clone(), msg_ext, test_packets, custom_recipient)
}
pub fn gateway_test_packets<T>(
pub fn legacy_gateway_test_packets<T>(
&mut self,
gateway: &gateway::Node,
gateway: &gateway::LegacyNode,
node_id: NodeId,
msg_ext: T,
test_packets: u32,
custom_recipient: Option<Recipient>,
@@ -195,7 +196,9 @@ where
let ephemeral_topology = self.testable_gateway_topology(gateway);
let mut packets = Vec::with_capacity(test_packets as usize);
for plaintext in TestMessage::gateway_plaintexts(gateway, test_packets, msg_ext)? {
for plaintext in
TestMessage::legacy_gateway_plaintexts(gateway, node_id, test_packets, msg_ext)?
{
packets.push(self.wrap_plaintext_data(
plaintext,
&ephemeral_topology,
@@ -208,6 +211,7 @@ where
pub fn existing_gateway_test_packets<T>(
&mut self,
node_id: NodeId,
encoded_gateway_identity: String,
msg_ext: T,
test_packets: u32,
@@ -222,7 +226,13 @@ where
});
};
self.gateway_test_packets(&node.clone(), msg_ext, test_packets, custom_recipient)
self.legacy_gateway_test_packets(
&node.clone(),
node_id,
msg_ext,
test_packets,
custom_recipient,
)
}
pub fn wrap_plaintext_data(
+8 -8
View File
@@ -220,7 +220,7 @@ impl Default for SphinxMessageReceiver {
mod message_receiver {
use super::*;
use nym_crypto::asymmetric::identity;
use nym_mixnet_contract_common::Layer;
use nym_mixnet_contract_common::LegacyMixLayer;
use nym_topology::{gateway, mix, NymTopology};
use std::collections::BTreeMap;
@@ -233,7 +233,7 @@ mod message_receiver {
let mut mixes = BTreeMap::new();
mixes.insert(
1,
vec![mix::Node {
vec![mix::LegacyNode {
mix_id: 123,
owner: None,
host: "10.20.30.40".parse().unwrap(),
@@ -246,14 +246,14 @@ mod message_receiver {
"B3GzG62aXAZNg14RoMCp3BhELNBrySLr2JqrwyfYFzRc",
)
.unwrap(),
layer: Layer::One,
layer: LegacyMixLayer::One,
version: "0.8.0-dev".into(),
}],
);
mixes.insert(
2,
vec![mix::Node {
vec![mix::LegacyNode {
mix_id: 234,
owner: None,
host: "11.21.31.41".parse().unwrap(),
@@ -266,14 +266,14 @@ mod message_receiver {
"5Z1VqYwM2xeKxd8H7fJpGWasNiDFijYBAee7MErkZ5QT",
)
.unwrap(),
layer: Layer::Two,
layer: LegacyMixLayer::Two,
version: "0.8.0-dev".into(),
}],
);
mixes.insert(
3,
vec![mix::Node {
vec![mix::LegacyNode {
mix_id: 456,
owner: None,
host: "12.22.32.42".parse().unwrap(),
@@ -286,7 +286,7 @@ mod message_receiver {
"9EyjhCggr2QEA2nakR88YHmXgpy92DWxoe2draDRkYof",
)
.unwrap(),
layer: Layer::Three,
layer: LegacyMixLayer::Three,
version: "0.8.0-dev".into(),
}],
);
@@ -294,7 +294,7 @@ mod message_receiver {
NymTopology::new(
// currently coco_nodes don't really exist so this is still to be determined
mixes,
vec![gateway::Node {
vec![gateway::LegacyNode {
owner: None,
host: "1.2.3.4".parse().unwrap(),
mix_host: "1.2.3.4:1789".parse().unwrap(),
+15 -105
View File
@@ -2,13 +2,11 @@
// SPDX-License-Identifier: Apache-2.0
use crate::{filter, NetworkAddress, NodeVersion};
use nym_api_requests::models::DescribedGateway;
use nym_api_requests::nym_nodes::SkimmedNode;
use nym_crypto::asymmetric::{encryption, identity};
use nym_mixnet_contract_common::GatewayBond;
use nym_mixnet_contract_common::NodeId;
use nym_sphinx_addressing::nodes::{NodeIdentity, NymNodeRoutingAddress};
use nym_sphinx_types::Node as SphinxNode;
use nym_api_requests::nym_nodes::SkimmedNode;
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::fmt;
@@ -49,7 +47,9 @@ pub enum GatewayConversionError {
}
#[derive(Clone)]
pub struct Node {
pub struct LegacyNode {
pub node_id: NodeId,
pub host: NetworkAddress,
// we're keeping this as separate resolved field since we do not want to be resolving the potential
// hostname every time we want to construct a path via this node
@@ -65,15 +65,13 @@ pub struct Node {
pub sphinx_key: encryption::PublicKey, // TODO: or nymsphinx::PublicKey? both are x25519
// to be removed:
pub owner: Option<String>,
pub version: NodeVersion,
}
impl std::fmt::Debug for Node {
impl std::fmt::Debug for LegacyNode {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("gateway::Node")
.field("host", &self.host)
.field("owner", &self.owner)
.field("mix_host", &self.mix_host)
.field("clients_ws_port", &self.clients_ws_port)
.field("clients_wss_port", &self.clients_wss_port)
@@ -84,7 +82,7 @@ impl std::fmt::Debug for Node {
}
}
impl Node {
impl LegacyNode {
pub fn parse_host(raw: &str) -> Result<NetworkAddress, GatewayConversionError> {
// safety: this conversion is infallible
// (but we retain result return type for legacy reasons)
@@ -122,25 +120,21 @@ impl Node {
}
}
impl fmt::Display for Node {
impl fmt::Display for LegacyNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Node(id: {}, owner: {:?}, host: {})",
self.identity_key, self.owner, self.host,
)
write!(f, "legacy gateway {} @ {}", self.node_id, self.host)
}
}
impl filter::Versioned for Node {
impl filter::Versioned for LegacyNode {
fn version(&self) -> String {
// TODO: return semver instead
self.version.to_string()
}
}
impl<'a> From<&'a Node> for SphinxNode {
fn from(node: &'a Node) -> Self {
impl<'a> From<&'a LegacyNode> for SphinxNode {
fn from(node: &'a LegacyNode) -> Self {
let node_address_bytes = NymNodeRoutingAddress::from(node.mix_host)
.try_into()
.unwrap();
@@ -149,83 +143,7 @@ impl<'a> From<&'a Node> for SphinxNode {
}
}
impl<'a> TryFrom<&'a GatewayBond> for Node {
type Error = GatewayConversionError;
fn try_from(bond: &'a GatewayBond) -> Result<Self, Self::Error> {
let host = Self::parse_host(&bond.gateway.host)?;
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = Self::extract_mix_host(&host, bond.gateway.mix_port)?;
Ok(Node {
owner: Some(bond.owner.as_str().to_owned()),
host,
mix_host,
clients_ws_port: bond.gateway.clients_port,
clients_wss_port: None,
identity_key: identity::PublicKey::from_base58_string(&bond.gateway.identity_key)?,
sphinx_key: encryption::PublicKey::from_base58_string(&bond.gateway.sphinx_key)?,
version: bond.gateway.version.as_str().into(),
})
}
}
impl TryFrom<GatewayBond> for Node {
type Error = GatewayConversionError;
fn try_from(bond: GatewayBond) -> Result<Self, Self::Error> {
Node::try_from(&bond)
}
}
impl<'a> TryFrom<&'a DescribedGateway> for Node {
type Error = GatewayConversionError;
fn try_from(value: &'a DescribedGateway) -> Result<Self, Self::Error> {
let Some(ref self_described) = value.self_described else {
return (&value.bond).try_into();
};
let ips = &self_described.host_information.ip_address;
if ips.is_empty() {
return Err(GatewayConversionError::NoIpAddressesProvided {
gateway: value.bond.gateway.identity_key.clone(),
});
}
let host = match &self_described.host_information.hostname {
None => NetworkAddress::IpAddr(ips[0]),
Some(hostname) => NetworkAddress::Hostname(hostname.clone()),
};
// get ip from the self-reported values so we wouldn't need to do any hostname resolution
// (which doesn't really work in wasm)
let mix_host = SocketAddr::new(ips[0], value.bond.gateway.mix_port);
Ok(Node {
owner: Some(value.bond.owner.as_str().to_owned()),
host,
mix_host,
clients_ws_port: self_described.mixnet_websockets.ws_port,
clients_wss_port: self_described.mixnet_websockets.wss_port,
identity_key: identity::PublicKey::from_base58_string(
&self_described.host_information.keys.ed25519,
)?,
sphinx_key: encryption::PublicKey::from_base58_string(
&self_described.host_information.keys.x25519,
)?,
version: self_described
.build_information
.build_version
.as_str()
.into(),
})
}
}
impl<'a> TryFrom<&'a SkimmedNode> for Node {
impl<'a> TryFrom<&'a SkimmedNode> for LegacyNode {
type Error = GatewayConversionError;
fn try_from(value: &'a SkimmedNode) -> Result<Self, Self::Error> {
@@ -249,23 +167,15 @@ impl<'a> TryFrom<&'a SkimmedNode> for Node {
NetworkAddress::IpAddr(*ip)
};
Ok(Node {
Ok(LegacyNode {
node_id: value.node_id,
host,
mix_host: SocketAddr::new(*ip, value.mix_port),
clients_ws_port: entry_details.ws_port,
clients_wss_port: entry_details.wss_port,
identity_key: value.ed25519_identity_pubkey.parse()?,
sphinx_key: value.x25519_sphinx_pubkey.parse()?,
owner: None,
version: NodeVersion::Unknown,
})
}
}
impl TryFrom<DescribedGateway> for Node {
type Error = GatewayConversionError;
fn try_from(value: DescribedGateway) -> Result<Self, Self::Error> {
Node::try_from(&value)
}
}
+71 -104
View File
@@ -7,17 +7,15 @@
use crate::filter::VersionFilterable;
pub use error::NymTopologyError;
use log::{debug, info, warn};
use mix::Node;
use nym_api_requests::nym_nodes::{CachedNodesResponse, SkimmedNode};
use nym_config::defaults::var_names::NYM_API;
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
use nym_mixnet_contract_common::{IdentityKeyRef, NodeId};
use nym_sphinx_addressing::nodes::NodeIdentity;
use nym_sphinx_types::Node as SphinxNode;
use rand::prelude::SliceRandom;
use rand::{CryptoRng, Rng};
use std::collections::BTreeMap;
use std::convert::Infallible;
use std::fmt::{self, Display, Formatter};
use std::io;
use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
@@ -25,7 +23,6 @@ use std::str::FromStr;
#[cfg(feature = "serializable")]
use ::serde::{Deserialize, Deserializer, Serialize, Serializer};
use nym_api_requests::models::DescribedGateway;
pub mod error;
pub mod filter;
@@ -118,45 +115,54 @@ impl Display for NetworkAddress {
pub type MixLayer = u8;
// the reason for those having `Legacy` prefix is that eventually they should be using
// exactly the same types
#[derive(Debug, Clone, Default)]
pub struct NymTopology {
mixes: BTreeMap<MixLayer, Vec<mix::Node>>,
gateways: Vec<gateway::Node>,
mixes: BTreeMap<MixLayer, Vec<mix::LegacyNode>>,
gateways: Vec<gateway::LegacyNode>,
}
impl NymTopology {
pub async fn new_from_env() -> Result<Self, NymTopologyError> {
let api_url = std::env::var(NYM_API)?;
info!("Generating topology from {}", api_url);
info!("Generating topology from {api_url}");
let mixnodes = reqwest::get(&format!("{}/v1/mixnodes", api_url))
let mixnodes = reqwest::get(&format!("{api_url}/v1/unstable/nym-nodes/mixnodes/skimmed",))
.await?
.json::<Vec<MixNodeDetails>>()
.json::<CachedNodesResponse<SkimmedNode>>()
.await?
.into_iter()
.map(|details| details.bond_information)
.map(mix::Node::try_from)
.nodes
.iter()
.map(mix::LegacyNode::try_from)
.filter(Result::is_ok)
.collect::<Result<Vec<_>, _>>()?;
let gateways = reqwest::get(&format!("{}/v1/gateways", api_url))
let gateways = reqwest::get(&format!("{api_url}/v1/unstable/nym-nodes/gateways/skimmed",))
.await?
.json::<Vec<GatewayBond>>()
.json::<CachedNodesResponse<SkimmedNode>>()
.await?
.into_iter()
.map(gateway::Node::try_from)
.nodes
.iter()
.map(gateway::LegacyNode::try_from)
.filter(Result::is_ok)
.collect::<Result<Vec<_>, _>>()?;
let topology = NymTopology::new_unordered(mixnodes, gateways);
Ok(topology)
}
pub fn new(mixes: BTreeMap<MixLayer, Vec<mix::Node>>, gateways: Vec<gateway::Node>) -> Self {
pub fn new(
mixes: BTreeMap<MixLayer, Vec<mix::LegacyNode>>,
gateways: Vec<gateway::LegacyNode>,
) -> Self {
NymTopology { mixes, gateways }
}
pub fn new_unordered(unordered_mixes: Vec<mix::Node>, gateways: Vec<gateway::Node>) -> Self {
pub fn new_unordered(
unordered_mixes: Vec<mix::LegacyNode>,
gateways: Vec<gateway::LegacyNode>,
) -> Self {
let mut mixes = BTreeMap::new();
for node in unordered_mixes.into_iter() {
let layer = node.layer as MixLayer;
@@ -171,10 +177,10 @@ impl NymTopology {
where
MI: Iterator<Item = M>,
GI: Iterator<Item = G>,
G: TryInto<gateway::Node>,
M: TryInto<mix::Node>,
<G as TryInto<gateway::Node>>::Error: Display,
<M as TryInto<mix::Node>>::Error: Display,
G: TryInto<gateway::LegacyNode>,
M: TryInto<mix::LegacyNode>,
<G as TryInto<gateway::LegacyNode>>::Error: Display,
<M as TryInto<mix::LegacyNode>>::Error: Display,
{
let mut mixes = BTreeMap::new();
let mut gateways = Vec::new();
@@ -205,14 +211,11 @@ impl NymTopology {
serde_json::from_reader(file).map_err(Into::into)
}
pub fn from_detailed(
mix_details: Vec<MixNodeDetails>,
gateway_bonds: Vec<GatewayBond>,
) -> Self {
nym_topology_from_detailed(mix_details, gateway_bonds)
pub fn from_basic(basic_mixes: &[SkimmedNode], basic_gateways: &[SkimmedNode]) -> Self {
nym_topology_from_basic_info(basic_mixes, basic_gateways)
}
pub fn find_mix(&self, mix_id: MixId) -> Option<&mix::Node> {
pub fn find_mix(&self, mix_id: NodeId) -> Option<&mix::LegacyNode> {
for nodes in self.mixes.values() {
for node in nodes {
if node.mix_id == mix_id {
@@ -223,7 +226,10 @@ impl NymTopology {
None
}
pub fn find_mix_by_identity(&self, mixnode_identity: IdentityKeyRef) -> Option<&mix::Node> {
pub fn find_mix_by_identity(
&self,
mixnode_identity: IdentityKeyRef,
) -> Option<&mix::LegacyNode> {
for nodes in self.mixes.values() {
for node in nodes {
if node.identity_key.to_base58_string() == mixnode_identity {
@@ -234,13 +240,13 @@ impl NymTopology {
None
}
pub fn find_gateway(&self, gateway_identity: IdentityKeyRef) -> Option<&gateway::Node> {
pub fn find_gateway(&self, gateway_identity: IdentityKeyRef) -> Option<&gateway::LegacyNode> {
self.gateways
.iter()
.find(|&gateway| gateway.identity_key.to_base58_string() == gateway_identity)
}
pub fn mixes(&self) -> &BTreeMap<MixLayer, Vec<mix::Node>> {
pub fn mixes(&self) -> &BTreeMap<MixLayer, Vec<mix::LegacyNode>> {
&self.mixes
}
@@ -248,8 +254,8 @@ impl NymTopology {
self.mixes.values().map(|m| m.len()).sum()
}
pub fn mixes_as_vec(&self) -> Vec<mix::Node> {
let mut mixes: Vec<mix::Node> = vec![];
pub fn mixes_as_vec(&self) -> Vec<mix::LegacyNode> {
let mut mixes: Vec<mix::LegacyNode> = vec![];
for layer in self.mixes().values() {
mixes.extend(layer.to_owned())
@@ -257,20 +263,20 @@ impl NymTopology {
mixes
}
pub fn mixes_in_layer(&self, layer: MixLayer) -> Vec<mix::Node> {
pub fn mixes_in_layer(&self, layer: MixLayer) -> Vec<mix::LegacyNode> {
assert!([1, 2, 3].contains(&layer));
self.mixes.get(&layer).unwrap().to_owned()
}
pub fn gateways(&self) -> &[gateway::Node] {
pub fn gateways(&self) -> &[gateway::LegacyNode] {
&self.gateways
}
pub fn get_gateways(&self) -> Vec<gateway::Node> {
pub fn get_gateways(&self) -> Vec<gateway::LegacyNode> {
self.gateways.clone()
}
pub fn get_gateway(&self, gateway_identity: &NodeIdentity) -> Option<&gateway::Node> {
pub fn get_gateway(&self, gateway_identity: &NodeIdentity) -> Option<&gateway::LegacyNode> {
self.gateways
.iter()
.find(|gateway| gateway.identity() == gateway_identity)
@@ -280,11 +286,11 @@ impl NymTopology {
self.get_gateway(gateway_identity).is_some()
}
pub fn set_gateways(&mut self, gateways: Vec<gateway::Node>) {
pub fn set_gateways(&mut self, gateways: Vec<gateway::LegacyNode>) {
self.gateways = gateways
}
pub fn random_gateway<R>(&self, rng: &mut R) -> Result<&gateway::Node, NymTopologyError>
pub fn random_gateway<R>(&self, rng: &mut R) -> Result<&gateway::LegacyNode, NymTopologyError>
where
R: Rng + CryptoRng,
{
@@ -299,7 +305,7 @@ impl NymTopology {
&self,
rng: &mut R,
num_mix_hops: u8,
) -> Result<Vec<Node>, NymTopologyError>
) -> Result<Vec<mix::LegacyNode>, NymTopologyError>
where
R: Rng + CryptoRng + ?Sized,
{
@@ -335,7 +341,7 @@ impl NymTopology {
rng: &mut R,
num_mix_hops: u8,
gateway_identity: &NodeIdentity,
) -> Result<(Vec<mix::Node>, gateway::Node), NymTopologyError>
) -> Result<(Vec<mix::LegacyNode>, gateway::LegacyNode), NymTopologyError>
where
R: Rng + CryptoRng + ?Sized,
{
@@ -376,7 +382,7 @@ impl NymTopology {
}
/// Overwrites the existing nodes in the specified layer
pub fn set_mixes_in_layer(&mut self, layer: u8, mixes: Vec<mix::Node>) {
pub fn set_mixes_in_layer(&mut self, layer: u8, mixes: Vec<mix::LegacyNode>) {
self.mixes.insert(layer, mixes);
}
@@ -491,66 +497,33 @@ impl<'de> Deserialize<'de> for NymTopology {
}
}
pub trait IntoGatewayNode: TryInto<gateway::Node>
where
<Self as TryInto<gateway::Node>>::Error: Display,
{
fn identity(&self) -> IdentityKeyRef;
}
impl IntoGatewayNode for GatewayBond {
fn identity(&self) -> IdentityKeyRef {
&self.gateway.identity_key
}
}
impl IntoGatewayNode for DescribedGateway {
fn identity(&self) -> IdentityKeyRef {
&self.bond.gateway.identity_key
}
}
pub fn nym_topology_from_detailed<G>(
mix_details: Vec<MixNodeDetails>,
gateway_bonds: Vec<G>,
) -> NymTopology
where
G: IntoGatewayNode,
<G as TryInto<gateway::Node>>::Error: Display,
{
pub fn nym_topology_from_basic_info(
basic_mixes: &[SkimmedNode],
basic_gateways: &[SkimmedNode],
) -> NymTopology {
let mut mixes = BTreeMap::new();
for bond in mix_details
.into_iter()
.map(|details| details.bond_information)
{
let layer = bond.layer as MixLayer;
if layer == 0 || layer > 3 {
warn!(
"{} says it's on invalid layer {layer}!",
bond.mix_node.identity_key
);
for mix in basic_mixes {
let Some(layer) = mix.get_mix_layer() else {
warn!("node {} doesn't have any assigned mix layer!", mix.node_id);
continue;
}
let mix_id = bond.mix_id;
let mix_identity = bond.mix_node.identity_key.clone();
};
let layer_entry = mixes.entry(layer).or_insert_with(Vec::new);
match bond.try_into() {
match mix.try_into() {
Ok(mix) => layer_entry.push(mix),
Err(err) => {
warn!("Mix {mix_id} / {mix_identity} is malformed: {err}");
warn!("node (mixnode) {} is malformed: {err}", mix.node_id);
continue;
}
}
}
let mut gateways = Vec::with_capacity(gateway_bonds.len());
for bond in gateway_bonds.into_iter() {
let gate_id = bond.identity().to_owned();
match bond.try_into() {
let mut gateways = Vec::with_capacity(basic_gateways.len());
for gateway in basic_gateways {
match gateway.try_into() {
Ok(gate) => gateways.push(gate),
Err(err) => {
warn!("Gateway {gate_id} is malformed: {err}");
warn!("node (gateway) {} is malformed: {err}", gateway.node_id);
continue;
}
}
@@ -568,13 +541,12 @@ mod converting_mixes_to_vec {
use nym_crypto::asymmetric::{encryption, identity};
use super::*;
use nym_mixnet_contract_common::Layer;
use nym_mixnet_contract_common::LegacyMixLayer;
#[test]
fn returns_a_vec_with_hashmap_values() {
let node1 = mix::Node {
let node1 = mix::LegacyNode {
mix_id: 42,
owner: Some("N/A".to_string()),
host: "3.3.3.3".parse().unwrap(),
mix_host: "3.3.3.3:1789".parse().unwrap(),
identity_key: identity::PublicKey::from_base58_string(
@@ -585,21 +557,15 @@ mod converting_mixes_to_vec {
"C7cown6dYCLZpLiMFC1PaBmhvLvmJmLDJGeRTbPD45bX",
)
.unwrap(),
layer: Layer::One,
layer: LegacyMixLayer::One,
version: "0.2.0".into(),
};
let node2 = mix::Node {
owner: Some("Alice".to_string()),
..node1.clone()
};
let node2 = mix::LegacyNode { ..node1.clone() };
let node3 = mix::Node {
owner: Some("Bob".to_string()),
..node1.clone()
};
let node3 = mix::LegacyNode { ..node1.clone() };
let mut mixes: BTreeMap<MixLayer, Vec<mix::Node>> = BTreeMap::new();
let mut mixes = BTreeMap::new();
mixes.insert(1, vec![node1, node2]);
mixes.insert(2, vec![node3]);
@@ -607,7 +573,8 @@ mod converting_mixes_to_vec {
let mixvec = topology.mixes_as_vec();
assert!(mixvec
.iter()
.any(|node| node.owner.as_ref() == Some(&"N/A".to_string())));
.any(|node| &node.identity_key.to_base58_string()
== "3ebjp1Fb9hdcS1AR6AZihgeJiMHkB5jjJUsvqNnfQwU7"));
}
}
+13 -48
View File
@@ -2,13 +2,12 @@
// SPDX-License-Identifier: Apache-2.0
use crate::{filter, NetworkAddress, NodeVersion};
use nym_api_requests::nym_nodes::{NodeRole, SkimmedNode};
use nym_crypto::asymmetric::{encryption, identity};
pub use nym_mixnet_contract_common::Layer;
use nym_mixnet_contract_common::{MixId, MixNodeBond};
pub use nym_mixnet_contract_common::LegacyMixLayer;
use nym_mixnet_contract_common::NodeId;
use nym_sphinx_addressing::nodes::NymNodeRoutingAddress;
use nym_sphinx_types::Node as SphinxNode;
use nym_api_requests::nym_nodes::{NodeRole, SkimmedNode};
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::fmt::Formatter;
@@ -42,26 +41,24 @@ pub enum MixnodeConversionError {
}
#[derive(Clone)]
pub struct Node {
pub mix_id: MixId,
pub struct LegacyNode {
pub mix_id: NodeId,
pub host: NetworkAddress,
// we're keeping this as separate resolved field since we do not want to be resolving the potential
// hostname every time we want to construct a path via this node
pub mix_host: SocketAddr,
pub identity_key: identity::PublicKey,
pub sphinx_key: encryption::PublicKey, // TODO: or nymsphinx::PublicKey? both are x25519
pub layer: Layer,
pub layer: LegacyMixLayer,
// to be removed:
pub version: NodeVersion,
pub owner: Option<String>,
}
impl std::fmt::Debug for Node {
impl std::fmt::Debug for LegacyNode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("mix::Node")
.field("mix_id", &self.mix_id)
.field("owner", &self.owner)
.field("host", &self.host)
.field("mix_host", &self.mix_host)
.field("identity_key", &self.identity_key.to_base58_string())
@@ -72,7 +69,7 @@ impl std::fmt::Debug for Node {
}
}
impl Node {
impl LegacyNode {
pub fn parse_host(raw: &str) -> Result<NetworkAddress, MixnodeConversionError> {
// safety: this conversion is infallible
// (but we retain result return type for legacy reasons)
@@ -92,15 +89,15 @@ impl Node {
}
}
impl filter::Versioned for Node {
impl filter::Versioned for LegacyNode {
fn version(&self) -> String {
// TODO: return semver instead
self.version.to_string()
}
}
impl<'a> From<&'a Node> for SphinxNode {
fn from(node: &'a Node) -> Self {
impl<'a> From<&'a LegacyNode> for SphinxNode {
fn from(node: &'a LegacyNode) -> Self {
let node_address_bytes = NymNodeRoutingAddress::from(node.mix_host)
.try_into()
.unwrap();
@@ -109,30 +106,7 @@ impl<'a> From<&'a Node> for SphinxNode {
}
}
impl<'a> TryFrom<&'a MixNodeBond> for Node {
type Error = MixnodeConversionError;
fn try_from(bond: &'a MixNodeBond) -> Result<Self, Self::Error> {
let host = Self::parse_host(&bond.mix_node.host)?;
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = Self::extract_mix_host(&host, bond.mix_node.mix_port)?;
Ok(Node {
mix_id: bond.mix_id,
owner: Some(bond.owner.as_str().to_owned()),
host,
mix_host,
identity_key: identity::PublicKey::from_base58_string(&bond.mix_node.identity_key)?,
sphinx_key: encryption::PublicKey::from_base58_string(&bond.mix_node.sphinx_key)?,
layer: bond.layer,
version: bond.mix_node.version.as_str().into(),
})
}
}
impl<'a> TryFrom<&'a SkimmedNode> for Node {
impl<'a> TryFrom<&'a SkimmedNode> for LegacyNode {
type Error = MixnodeConversionError;
fn try_from(value: &'a SkimmedNode) -> Result<Self, Self::Error> {
@@ -155,23 +129,14 @@ impl<'a> TryFrom<&'a SkimmedNode> for Node {
let host = NetworkAddress::IpAddr(*ip);
Ok(Node {
Ok(LegacyNode {
mix_id: value.node_id,
host,
mix_host: SocketAddr::new(*ip, value.mix_port),
identity_key: value.ed25519_identity_pubkey.parse()?,
sphinx_key: value.x25519_sphinx_pubkey.parse()?,
layer,
owner: None,
version: NodeVersion::Unknown,
})
}
}
impl TryFrom<MixNodeBond> for Node {
type Error = MixnodeConversionError;
fn try_from(bond: MixNodeBond) -> Result<Self, Self::Error> {
Node::try_from(&bond)
}
}
+18 -23
View File
@@ -20,6 +20,7 @@ use thiserror::Error;
#[cfg(feature = "wasm-serde-types")]
use tsify::Tsify;
use nym_mixnet_contract_common::NodeId;
#[cfg(feature = "wasm-serde-types")]
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
@@ -109,9 +110,6 @@ pub struct SerializableMixNode {
#[serde(alias = "mix_id")]
pub mix_id: u32,
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
pub owner: Option<String>,
pub host: String,
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
@@ -131,40 +129,38 @@ pub struct SerializableMixNode {
pub version: Option<String>,
}
impl TryFrom<SerializableMixNode> for mix::Node {
impl TryFrom<SerializableMixNode> for mix::LegacyNode {
type Error = SerializableTopologyError;
fn try_from(value: SerializableMixNode) -> Result<Self, Self::Error> {
let host = mix::Node::parse_host(&value.host)?;
let host = mix::LegacyNode::parse_host(&value.host)?;
let mix_port = value.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT);
let version = value.version.map(|v| v.as_str().into()).unwrap_or_default();
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = mix::Node::extract_mix_host(&host, mix_port)?;
let mix_host = mix::LegacyNode::extract_mix_host(&host, mix_port)?;
Ok(mix::Node {
Ok(mix::LegacyNode {
mix_id: value.mix_id,
owner: value.owner,
host,
mix_host,
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
.map_err(MixnodeConversionError::from)?,
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
.map_err(MixnodeConversionError::from)?,
layer: mix::Layer::try_from(value.layer)
layer: mix::LegacyMixLayer::try_from(value.layer)
.map_err(|_| SerializableTopologyError::InvalidMixLayer { value: value.layer })?,
version,
})
}
}
impl<'a> From<&'a mix::Node> for SerializableMixNode {
fn from(value: &'a mix::Node) -> Self {
impl<'a> From<&'a mix::LegacyNode> for SerializableMixNode {
fn from(value: &'a mix::LegacyNode) -> Self {
SerializableMixNode {
mix_id: value.mix_id,
owner: value.owner.clone(),
host: value.host.to_string(),
mix_port: Some(value.mix_host.port()),
identity_key: value.identity_key.to_base58_string(),
@@ -181,11 +177,10 @@ impl<'a> From<&'a mix::Node> for SerializableMixNode {
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct SerializableGateway {
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
pub owner: Option<String>,
pub host: String,
pub node_id: NodeId,
// optional ip address in the case of host being a hostname that can't be resolved
// (thank you wasm)
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
@@ -215,11 +210,11 @@ pub struct SerializableGateway {
pub version: Option<String>,
}
impl TryFrom<SerializableGateway> for gateway::Node {
impl TryFrom<SerializableGateway> for gateway::LegacyNode {
type Error = SerializableTopologyError;
fn try_from(value: SerializableGateway) -> Result<Self, Self::Error> {
let host = gateway::Node::parse_host(&value.host)?;
let host = gateway::LegacyNode::parse_host(&value.host)?;
let mix_port = value.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT);
let clients_ws_port = value
@@ -232,11 +227,11 @@ impl TryFrom<SerializableGateway> for gateway::Node {
let mix_host = if let Some(explicit_ip) = value.explicit_ip {
SocketAddr::new(explicit_ip, mix_port)
} else {
gateway::Node::extract_mix_host(&host, mix_port)?
gateway::LegacyNode::extract_mix_host(&host, mix_port)?
};
Ok(gateway::Node {
owner: value.owner,
Ok(gateway::LegacyNode {
node_id: value.node_id,
host,
mix_host,
clients_ws_port,
@@ -250,11 +245,11 @@ impl TryFrom<SerializableGateway> for gateway::Node {
}
}
impl<'a> From<&'a gateway::Node> for SerializableGateway {
fn from(value: &'a gateway::Node) -> Self {
impl<'a> From<&'a gateway::LegacyNode> for SerializableGateway {
fn from(value: &'a gateway::LegacyNode) -> Self {
SerializableGateway {
owner: value.owner.clone(),
host: value.host.to_string(),
node_id: value.node_id,
explicit_ip: Some(value.mix_host.ip()),
mix_port: Some(value.mix_host.port()),
clients_ws_port: Some(value.clients_ws_port),
+6 -6
View File
@@ -1,9 +1,9 @@
use crate::currency::{DecCoin, RegisteredCoins};
use crate::deprecated::DelegationEvent;
use crate::error::TypesError;
use crate::mixnode::MixNodeCostParams;
use crate::mixnode::NodeCostParams;
use cosmwasm_std::Decimal;
use nym_mixnet_contract_common::{Delegation as MixnetContractDelegation, MixId};
use nym_mixnet_contract_common::{Delegation as MixnetContractDelegation, NodeId};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq, JsonSchema)]
pub struct Delegation {
pub owner: String,
pub mix_id: MixId,
pub mix_id: NodeId,
pub amount: DecCoin,
pub height: u64,
pub proxy: Option<String>, // proxy address used to delegate the funds on behalf of another address
@@ -28,7 +28,7 @@ impl Delegation {
) -> Result<Self, TypesError> {
Ok(Delegation {
owner: delegation.owner.to_string(),
mix_id: delegation.mix_id,
mix_id: delegation.node_id,
amount: reg.attempt_convert_to_display_dec_coin(delegation.amount.into())?,
height: delegation.height,
proxy: delegation.proxy.map(|d| d.to_string()),
@@ -44,14 +44,14 @@ impl Delegation {
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
pub struct DelegationWithEverything {
pub owner: String,
pub mix_id: MixId,
pub mix_id: NodeId,
pub node_identity: String,
pub amount: DecCoin,
pub accumulated_by_delegates: Option<DecCoin>,
pub accumulated_by_operator: Option<DecCoin>,
pub block_height: u64,
pub delegated_on_iso_datetime: Option<String>,
pub cost_params: Option<MixNodeCostParams>,
pub cost_params: Option<NodeCostParams>,
pub avg_uptime_percent: Option<u8>,
#[cfg_attr(feature = "generate-ts", ts(type = "string | null"))]
+2 -2
View File
@@ -4,7 +4,7 @@
use crate::currency::DecCoin;
use crate::error::TypesError;
use crate::pending_events::{PendingEpochEvent, PendingEpochEventData};
use nym_mixnet_contract_common::{IdentityKey, MixId};
use nym_mixnet_contract_common::{IdentityKey, NodeId};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -27,7 +27,7 @@ pub enum DelegationEventKind {
#[derive(Clone, Deserialize, Serialize, PartialEq, Eq, JsonSchema, Debug)]
pub struct DelegationEvent {
pub kind: DelegationEventKind,
pub mix_id: MixId,
pub mix_id: NodeId,
pub address: String,
pub amount: Option<DecCoin>,
pub proxy: Option<String>,
+3
View File
@@ -1,6 +1,8 @@
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#![warn(clippy::todo)]
pub mod account;
pub mod currency;
pub mod delegation;
@@ -12,6 +14,7 @@ pub mod gateway;
pub mod helpers;
pub mod mixnode;
pub mod monitoring;
pub mod nym_node;
pub mod pending_events;
pub mod transaction;
pub mod vesting;
+22 -25
View File
@@ -5,10 +5,9 @@ use crate::currency::{DecCoin, RegisteredCoins};
use crate::error::TypesError;
use cosmwasm_std::Decimal;
use nym_mixnet_contract_common::{
EpochId, MixId, MixNode, MixNodeBond as MixnetContractMixNodeBond,
MixNodeCostParams as MixnetContractMixNodeCostParams,
MixNodeDetails as MixnetContractMixNodeDetails,
MixNodeRewarding as MixnetContractMixNodeRewarding, Percent,
EpochId, MixNode, MixNodeBond as MixnetContractMixNodeBond,
MixNodeDetails as MixnetContractMixNodeDetails, NodeCostParams as MixnetContractNodeCostParams,
NodeId, NodeRewarding as MixnetContractNodeRewarding, Percent,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -23,7 +22,7 @@ use std::net::IpAddr;
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct MixNodeDetails {
pub bond_information: MixNodeBond,
pub rewarding_details: MixNodeRewarding,
pub rewarding_details: NodeRewarding,
}
impl MixNodeDetails {
@@ -36,7 +35,7 @@ impl MixNodeDetails {
details.bond_information,
reg,
)?,
rewarding_details: MixNodeRewarding::from_mixnet_contract_mixnode_rewarding(
rewarding_details: NodeRewarding::from_mixnet_contract_node_rewarding(
details.rewarding_details,
reg,
)?,
@@ -51,10 +50,9 @@ impl MixNodeDetails {
)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct MixNodeBond {
pub mix_id: MixId,
pub mix_id: NodeId,
pub owner: String,
pub original_pledge: DecCoin,
pub layer: String,
pub mix_node: MixNode,
pub proxy: Option<String>,
pub bonding_height: u64,
@@ -71,7 +69,6 @@ impl MixNodeBond {
owner: bond.owner.into_string(),
original_pledge: reg
.attempt_convert_to_display_dec_coin(bond.original_pledge.into())?,
layer: bond.layer.into(),
mix_node: bond.mix_node,
proxy: bond.proxy.map(|p| p.into_string()),
bonding_height: bond.bonding_height,
@@ -83,11 +80,11 @@ impl MixNodeBond {
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/MixNodeRewarding.ts")
ts(export_to = "ts-packages/types/src/types/rust/NodeRewarding.ts")
)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct MixNodeRewarding {
pub cost_params: MixNodeCostParams,
pub struct NodeRewarding {
pub cost_params: NodeCostParams,
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub operator: Decimal,
@@ -106,13 +103,13 @@ pub struct MixNodeRewarding {
pub unique_delegations: u32,
}
impl MixNodeRewarding {
pub fn from_mixnet_contract_mixnode_rewarding(
mix_rewarding: MixnetContractMixNodeRewarding,
impl NodeRewarding {
pub fn from_mixnet_contract_node_rewarding(
mix_rewarding: MixnetContractNodeRewarding,
reg: &RegisteredCoins,
) -> Result<MixNodeRewarding, TypesError> {
Ok(MixNodeRewarding {
cost_params: MixNodeCostParams::from_mixnet_contract_mixnode_cost_params(
) -> Result<NodeRewarding, TypesError> {
Ok(NodeRewarding {
cost_params: NodeCostParams::from_mixnet_contract_mixnode_cost_params(
mix_rewarding.cost_params,
reg,
)?,
@@ -132,19 +129,19 @@ impl MixNodeRewarding {
ts(export_to = "ts-packages/types/src/types/rust/MixNodeCostParams.ts")
)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct MixNodeCostParams {
pub struct NodeCostParams {
#[cfg_attr(feature = "generate-ts", ts(type = "string"))]
pub profit_margin_percent: Percent,
pub interval_operating_cost: DecCoin,
}
impl MixNodeCostParams {
impl NodeCostParams {
pub fn from_mixnet_contract_mixnode_cost_params(
cost_params: MixnetContractMixNodeCostParams,
cost_params: MixnetContractNodeCostParams,
reg: &RegisteredCoins,
) -> Result<MixNodeCostParams, TypesError> {
Ok(MixNodeCostParams {
) -> Result<NodeCostParams, TypesError> {
Ok(NodeCostParams {
profit_margin_percent: cost_params.profit_margin_percent,
interval_operating_cost: reg
.attempt_convert_to_display_dec_coin(cost_params.interval_operating_cost.into())?,
@@ -154,8 +151,8 @@ impl MixNodeCostParams {
pub fn try_convert_to_mixnet_contract_cost_params(
self,
reg: &RegisteredCoins,
) -> Result<MixnetContractMixNodeCostParams, TypesError> {
Ok(MixnetContractMixNodeCostParams {
) -> Result<MixnetContractNodeCostParams, TypesError> {
Ok(MixnetContractNodeCostParams {
profit_margin_percent: self.profit_margin_percent,
interval_operating_cost: reg
.attempt_convert_to_base_coin(self.interval_operating_cost)?
+10 -39
View File
@@ -1,9 +1,8 @@
use std::{collections::HashSet, sync::LazyLock, time::SystemTime};
use nym_crypto::asymmetric::identity::{PrivateKey, PublicKey, Signature};
use nym_mixnet_contract_common::MixId;
use nym_mixnet_contract_common::NodeId;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{collections::HashSet, sync::LazyLock, time::SystemTime};
static NETWORK_MONITORS: LazyLock<HashSet<String>> = LazyLock::new(|| {
let mut nm = HashSet::new();
@@ -13,54 +12,26 @@ static NETWORK_MONITORS: LazyLock<HashSet<String>> = LazyLock::new(|| {
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
pub struct NodeResult {
pub node_id: MixId,
pub node_id: NodeId,
pub identity: String,
pub reliability: u8,
}
#[derive(Debug, Serialize, Deserialize, JsonSchema, Clone)]
pub struct MixnodeResult {
pub mix_id: MixId,
pub identity: String,
pub owner: String,
pub reliability: u8,
}
impl MixnodeResult {
pub fn new(mix_id: MixId, identity: String, owner: String, reliability: u8) -> Self {
MixnodeResult {
mix_id,
impl NodeResult {
pub fn new(node_id: NodeId, identity: String, reliability: u8) -> Self {
NodeResult {
node_id,
identity,
owner,
reliability,
}
}
}
#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone)]
pub struct GatewayResult {
pub identity: String,
pub owner: String,
pub reliability: u8,
pub mix_id: MixId,
}
impl GatewayResult {
pub fn new(identity: String, owner: String, reliability: u8) -> Self {
GatewayResult {
identity,
owner,
reliability,
mix_id: 0,
}
}
}
#[derive(Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum MonitorResults {
Mixnode(Vec<MixnodeResult>),
Gateway(Vec<GatewayResult>),
Mixnode(Vec<NodeResult>),
Gateway(Vec<NodeResult>),
}
#[derive(Serialize, Deserialize, JsonSchema)]
@@ -105,7 +76,7 @@ impl MonitorMessage {
}
}
pub fn from_allowed(&self) -> bool {
pub fn is_in_allowed(&self) -> bool {
NETWORK_MONITORS.contains(&self.signer)
}
+94
View File
@@ -0,0 +1,94 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::currency::{DecCoin, RegisteredCoins};
use crate::error::TypesError;
use crate::mixnode::NodeRewarding;
use nym_mixnet_contract_common::{NodeId, NymNode, PendingNodeChanges};
use nym_mixnet_contract_common::{
NymNodeBond as MixnetContractNymNodeBond, NymNodeDetails as MixnetContractNymNodeDetails,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
/// Full details associated with given node.
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/NymNodeDetails.ts")
)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct NymNodeDetails {
/// Basic bond information of this node, such as owner address, original pledge, etc.
pub bond_information: NymNodeBond,
/// Details used for computation of rewarding related data.
pub rewarding_details: NodeRewarding,
/// Adjustments to the node that are scheduled to happen during future epoch/interval transitions.
pub pending_changes: PendingNodeChanges,
}
impl NymNodeDetails {
pub fn from_mixnet_contract_nym_node_details(
details: MixnetContractNymNodeDetails,
reg: &RegisteredCoins,
) -> Result<NymNodeDetails, TypesError> {
Ok(NymNodeDetails {
bond_information: NymNodeBond::from_mixnet_contract_mixnode_bond(
details.bond_information,
reg,
)?,
rewarding_details: NodeRewarding::from_mixnet_contract_node_rewarding(
details.rewarding_details,
reg,
)?,
pending_changes: details.pending_changes,
})
}
}
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
#[cfg_attr(
feature = "generate-ts",
ts(export_to = "ts-packages/types/src/types/rust/NymNodeBond.ts")
)]
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, JsonSchema)]
pub struct NymNodeBond {
/// Unique id assigned to the bonded node.
pub node_id: NodeId,
/// Address of the owner of this nym-node.
pub owner: String,
/// Original amount pledged by the operator of this node.
pub original_pledge: DecCoin,
/// Block height at which this nym-node has been bonded.
pub bonding_height: u64,
/// Flag to indicate whether this node is in the process of unbonding,
/// that will conclude upon the epoch finishing.
pub is_unbonding: bool,
#[serde(flatten)]
/// Information provided by the operator for the purposes of bonding.
pub node: NymNode,
}
impl NymNodeBond {
pub fn from_mixnet_contract_mixnode_bond(
bond: MixnetContractNymNodeBond,
reg: &RegisteredCoins,
) -> Result<NymNodeBond, TypesError> {
Ok(NymNodeBond {
node_id: bond.node_id,
owner: bond.owner.into_string(),
original_pledge: reg
.attempt_convert_to_display_dec_coin(bond.original_pledge.into())?,
node: bond.node,
bonding_height: bond.bonding_height,
is_unbonding: bond.is_unbonding,
})
}
}
+16 -17
View File
@@ -3,9 +3,9 @@
use crate::currency::{DecCoin, RegisteredCoins};
use crate::error::TypesError;
use crate::mixnode::MixNodeCostParams;
use crate::mixnode::NodeCostParams;
use nym_mixnet_contract_common::{
BlockHeight, EpochEventId, IntervalEventId, IntervalRewardingParamsUpdate, MixId,
BlockHeight, EpochEventId, IntervalEventId, IntervalRewardingParamsUpdate, NodeId,
PendingEpochEvent as MixnetContractPendingEpochEvent,
PendingEpochEventKind as MixnetContractPendingEpochEventKind,
PendingIntervalEvent as MixnetContractPendingIntervalEvent,
@@ -48,25 +48,25 @@ impl PendingEpochEvent {
pub enum PendingEpochEventData {
Delegate {
owner: String,
mix_id: MixId,
mix_id: NodeId,
amount: DecCoin,
proxy: Option<String>,
},
Undelegate {
owner: String,
mix_id: MixId,
mix_id: NodeId,
proxy: Option<String>,
},
PledgeMore {
mix_id: MixId,
mix_id: NodeId,
amount: DecCoin,
},
DecreasePledge {
mix_id: MixId,
mix_id: NodeId,
decrease_by: DecCoin,
},
UnbondMixnode {
mix_id: MixId,
mix_id: NodeId,
},
UpdateActiveSetSize {
new_size: u32,
@@ -81,7 +81,7 @@ impl PendingEpochEventData {
match pending_event {
MixnetContractPendingEpochEventKind::Delegate {
owner,
mix_id,
node_id: mix_id,
amount,
proxy,
..
@@ -93,7 +93,7 @@ impl PendingEpochEventData {
}),
MixnetContractPendingEpochEventKind::Undelegate {
owner,
mix_id,
node_id: mix_id,
proxy,
..
} => Ok(PendingEpochEventData::Undelegate {
@@ -101,13 +101,13 @@ impl PendingEpochEventData {
mix_id,
proxy: proxy.map(|p| p.into_string()),
}),
MixnetContractPendingEpochEventKind::PledgeMore { mix_id, amount } => {
MixnetContractPendingEpochEventKind::MixnodePledgeMore { mix_id, amount } => {
Ok(PendingEpochEventData::PledgeMore {
mix_id,
amount: reg.attempt_convert_to_display_dec_coin(amount.into())?,
})
}
MixnetContractPendingEpochEventKind::DecreasePledge {
MixnetContractPendingEpochEventKind::MixnodeDecreasePledge {
mix_id,
decrease_by,
} => Ok(PendingEpochEventData::DecreasePledge {
@@ -117,9 +117,7 @@ impl PendingEpochEventData {
MixnetContractPendingEpochEventKind::UnbondMixnode { mix_id } => {
Ok(PendingEpochEventData::UnbondMixnode { mix_id })
}
MixnetContractPendingEpochEventKind::UpdateActiveSetSize { new_size } => {
Ok(PendingEpochEventData::UpdateActiveSetSize { new_size })
}
_ => todo!(),
}
}
}
@@ -160,8 +158,8 @@ impl PendingIntervalEvent {
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
pub enum PendingIntervalEventData {
ChangeMixCostParams {
mix_id: MixId,
new_costs: MixNodeCostParams,
mix_id: NodeId,
new_costs: NodeCostParams,
},
UpdateRewardingParams {
@@ -182,7 +180,7 @@ impl PendingIntervalEventData {
MixnetContractPendingIntervalEventKind::ChangeMixCostParams { mix_id, new_costs } => {
Ok(PendingIntervalEventData::ChangeMixCostParams {
mix_id,
new_costs: MixNodeCostParams::from_mixnet_contract_mixnode_cost_params(
new_costs: NodeCostParams::from_mixnet_contract_mixnode_cost_params(
new_costs, reg,
)?,
})
@@ -197,6 +195,7 @@ impl PendingIntervalEventData {
epochs_in_interval,
epoch_duration_secs,
}),
_ => todo!(),
}
}
}
+4 -4
View File
@@ -67,10 +67,10 @@ pub async fn current_network_topology_async(
};
let api_client = NymApiClient::new(url);
let mixnodes = api_client.get_cached_active_mixnodes().await?;
let gateways = api_client.get_cached_gateways().await?;
let mixnodes = api_client.get_basic_mixnodes().await?;
let gateways = api_client.get_basic_gateways().await?;
Ok(NymTopology::from_detailed(mixnodes, gateways).into())
Ok(NymTopology::from_basic(&mixnodes, g & ateways).into())
}
#[wasm_bindgen(js_name = "currentNetworkTopology")]
@@ -88,7 +88,7 @@ pub async fn setup_gateway_wasm(
client_store: &ClientStorage,
force_tls: bool,
chosen_gateway: Option<IdentityKey>,
gateways: &[gateway::Node],
gateways: &[gateway::LegacyNode],
) -> Result<InitialisationResult, WasmCoreError> {
// TODO: so much optimization and extra features could be added here, but that's for the future
+4 -2
View File
@@ -35,9 +35,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.82"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519"
checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356"
[[package]]
name = "arrayref"
@@ -1271,6 +1271,7 @@ dependencies = [
name = "nym-mixnet-contract"
version = "1.5.1"
dependencies = [
"anyhow",
"bs58 0.4.0",
"cosmwasm-derive",
"cosmwasm-schema",
@@ -1297,6 +1298,7 @@ dependencies = [
"cosmwasm-schema",
"cosmwasm-std",
"cw-controllers",
"cw-storage-plus",
"cw2",
"humantime-serde",
"log",
+2 -1
View File
@@ -3,7 +3,7 @@ resolver = "2"
members = [
"coconut-bandwidth",
"coconut-dkg",
"coconut-test",
"coconut-test",
"ecash",
"mixnet",
"mixnet-vesting-integration-tests",
@@ -32,6 +32,7 @@ incremental = false
overflow-checks = true
[workspace.dependencies]
anyhow = "1.0.86"
bs58 = "0.4.0"
cosmwasm-crypto = "=1.4.3"
cosmwasm-derive = "=1.4.3"

Some files were not shown because too many files have changed in this diff Show More