Compare commits

..

26 Commits

Author SHA1 Message Date
benedetta davico 9eeb61ea0a Merge branch 'develop' into release/2024.8-wispa 2024-07-24 10:56:03 +02:00
Stefano Piermatteo 36c74f30e5 [DOCs/operators]: Syntax fix in setup.md (#4682) 2024-07-24 08:37:33 +00:00
Bogdan-Ștefan Neacşu fd1d437211 Add 1GB/day/user bandwidth cap (#4717)
* Add check for 1GB/day/user and remove stale check

* Use saturated_sub

* Remove from wg peers

* Use 10 seconds instead of 1

* Query bandwidth message

* Ad client query message too

* Keep stale check

* Make bandwidth cap value public

* Fix consumed vs available bug

* Don't overwrite existing registrations

* Use self pub key instead of peer's
2024-07-23 20:49:49 +02:00
Tommy Verrall 4956d13bdc fix conflicts 2024-07-23 17:32:49 +02:00
Jędrzej Stuczyński 6478736654 Merge pull request #4706 from nymtech/chore/remove-old-migration-code
removed mixnode/gateway config migration code and disabled cli without explicit flag
2024-07-23 15:11:36 +01:00
benedettadavico d9f6c0723e updating versions 2024-07-23 15:37:04 +02:00
Jon Häggblad f86050d916 Default construct NodeRole for backwards compatibility (#4722) 2024-07-22 16:04:29 +02:00
Tommy Verrall 52f5656190 Merge pull request #4721 from nymtech/jon/node-role-default
Default construct NodeRole
2024-07-22 15:09:27 +02:00
Jon Häggblad 21cd90f238 Default construct NodeRole for backwards compatibility 2024-07-22 14:59:18 +02:00
import this 4e51188d35 [DOCs/operators]: Guide to back up and restore nym-node (#4720)
* add node backup & node restore guides

* finished: ready to review

* finished: ready to review
2024-07-22 11:17:55 +00:00
John Smith 22eb199936 Update isp-sheet.csv (#4718)
* Update isp-sheet.csv

Added a few known VPS providers which (a) support crypto payment (b) allow TOR in some shape or form (c) more or less know for their stability. Will add more eventually.

* Update isp-sheet.csv

added a few more providers
2024-07-22 10:57:57 +00:00
Tommy Verrall a2fc1bbc96 Merge pull request #4719 from nymtech/serinko/bug-fix/wss-guide
[DOCs/operators]: BugFix - add ssl cert to WSS server block
2024-07-19 12:48:36 +02:00
import this 621599692f add ssl cert to WSS server block 2024-07-19 10:43:02 +00:00
import this 3ad3837c87 done: fix wrong URL and picture formatting (#4714) 2024-07-17 10:47:41 +00:00
import this 4d745e3b7e [DOCs/operators]: Correct ports for bonding (#4707)
* fix port issue for nym-node mixnode

* fix port issue for nym-node mixnode

* simplify language

* clarify moving node info

* syntax fix
2024-07-16 14:00:38 +00:00
Sachin Kamath 3a053b8dd6 fix links (#4712) 2024-07-15 18:06:56 +02:00
Bogdan-Ștefan Neacşu 1f144690da Add upgrades to nym-node for authenticator changes (#4703) (#4710)
* Add iterative upgrades to nym-node

* Authenticator correct configuration

* Add info log

* Enable auth opts on entry gw

* Move ephemeral config from exit_gateway

* Fix fmt

* Fix clippy

* Pass custom transceiver for authenticator

* Fix non-linux build

* Feature gate wg_api

* Change naming from semver to simple incremental

* Move opts unwrap inside the mutable function

* Remove unneeded authenticator_description
2024-07-12 14:45:59 +02:00
Tommy Verrall eec1895acc Merge pull request #4709 from nymtech/dependabot/npm_and_yarn/nym-wallet/webdriver/braces-3.0.3
Bump braces from 3.0.2 to 3.0.3 in /nym-wallet/webdriver
2024-07-12 12:14:04 +02:00
Bogdan-Ștefan Neacşu 72e243042e Add upgrades to nym-node for authenticator changes (#4703)
* Add iterative upgrades to nym-node

* Authenticator correct configuration

* Add info log

* Enable auth opts on entry gw

* Move ephemeral config from exit_gateway

* Fix fmt

* Fix clippy

* Pass custom transceiver for authenticator

* Fix non-linux build

* Feature gate wg_api

* Change naming from semver to simple incremental

* Move opts unwrap inside the mutable function

* Remove unneeded authenticator_description
2024-07-12 12:02:22 +02:00
dependabot[bot] 99864cb7a9 Bump braces from 3.0.2 to 3.0.3 in /nym-wallet/webdriver
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-12 09:09:14 +00:00
Fouad 3155728119 fix explorer mui deps (#4708) 2024-07-12 10:08:29 +01:00
Jędrzej Stuczyński c253b22f69 fixed the positional argument 2024-07-11 16:20:20 +01:00
Jędrzej Stuczyński 66f3a3e9a8 removed mixnode/gateway config migration code and disabled commands without explicit flag 2024-07-11 15:53:10 +01:00
benedettadavico c2ad4e5bb4 Update changelog and bump versions 2024-07-10 11:01:20 +02:00
Tommy Verrall 5f7f5ef92d Merge pull request #4699 from nymtech/release/2024.7-doubledecker
Release/2024.7 doubledecker
2024-07-10 10:46:03 +02:00
benedetta davico 008afe7a85 Merge pull request #4676 from nymtech/release/2024.6-chomp
Release/2024.6-chomp to master
2024-06-27 11:47:55 +02:00
147 changed files with 5139 additions and 4682 deletions
+24 -1
View File
@@ -4,6 +4,30 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [2024.8-wispa] (2024-07-10)
- add event parsing to support cosmos_sdk > 0.50 ([#4697])
- Fix NR config compatibility ([#4690])
- Remove UserAgent constructor since it's weakly typed ([#4689])
- [bugfix]: Node_api_check CLI looked over roles on blacklisted nodes ([#4687])
- Add mixnodes to self describing api cache ([#4684])
- Move and whole bump of crates to workspace and upgrade some ([#4680])
- Remove code that refers to removed nym-network-statistics ([#4679])
- Remove nym-network-statistics ([#4678])
- Create UserAgent that can be passed from the binary to the nym api client ([#4677])
- Add authenticator ([#4667])
[#4697]: https://github.com/nymtech/nym/pull/4697
[#4690]: https://github.com/nymtech/nym/pull/4690
[#4689]: https://github.com/nymtech/nym/pull/4689
[#4687]: https://github.com/nymtech/nym/pull/4687
[#4684]: https://github.com/nymtech/nym/pull/4684
[#4680]: https://github.com/nymtech/nym/pull/4680
[#4679]: https://github.com/nymtech/nym/pull/4679
[#4678]: https://github.com/nymtech/nym/pull/4678
[#4677]: https://github.com/nymtech/nym/pull/4677
[#4667]: https://github.com/nymtech/nym/pull/4667
## [2024.7-doubledecker] (2024-07-04)
- Add an early return in `parse_raw_str_logs` for empty raw log strings. ([#4686])
@@ -513,7 +537,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
[#3187]: https://github.com/nymtech/nym/issues/3187
[#3203]: https://github.com/nymtech/nym/pull/3203
[#3199]: https://github.com/nymtech/nym/pull/3199
>>>>>>> master
## [v1.1.13] (2023-03-15)
Generated
+10 -8
View File
@@ -2093,7 +2093,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "explorer-api"
version = "1.1.36"
version = "1.1.37"
dependencies = [
"chrono",
"clap 4.5.4",
@@ -3849,7 +3849,7 @@ dependencies = [
[[package]]
name = "nym-api"
version = "1.1.40"
version = "1.1.41"
dependencies = [
"anyhow",
"async-trait",
@@ -4057,7 +4057,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.38"
version = "1.1.39"
dependencies = [
"anyhow",
"base64 0.13.1",
@@ -4136,7 +4136,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.37"
version = "1.1.38"
dependencies = [
"bs58 0.5.1",
"clap 4.5.4",
@@ -4950,7 +4950,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.38"
version = "1.1.39"
dependencies = [
"addr",
"anyhow",
@@ -5001,7 +5001,7 @@ dependencies = [
[[package]]
name = "nym-node"
version = "1.1.4"
version = "1.1.5"
dependencies = [
"anyhow",
"bip39",
@@ -5264,7 +5264,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.37"
version = "1.1.38"
dependencies = [
"bs58 0.5.1",
"clap 4.5.4",
@@ -5742,6 +5742,7 @@ name = "nym-wireguard"
version = "0.1.0"
dependencies = [
"base64 0.21.7",
"chrono",
"dashmap",
"defguard_wireguard_rs",
"ip_network",
@@ -5750,6 +5751,7 @@ dependencies = [
"nym-network-defaults",
"nym-task",
"nym-wireguard-types",
"thiserror",
"tokio",
"tokio-stream",
"x25519-dalek",
@@ -5777,7 +5779,7 @@ dependencies = [
[[package]]
name = "nymvisor"
version = "0.1.3"
version = "0.1.4"
dependencies = [
"anyhow",
"bytes",
+1 -1
View File
@@ -52,7 +52,7 @@ References for developers:
You can chat to us in two places:
* The #dev channel on [Matrix](https://matrix.to/#/#dev:nymtech.chat)
* The various developer channels on [Discord](https://discord.gg/nymproject)
* The various developer channels on [Discord](https://nymtech.net/go/discord)
### Tokenomics & Rewards
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.37"
version = "1.1.38"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.37"
version = "1.1.38"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use nym_sphinx::addressing::Recipient;
use nym_wireguard_types::{GatewayClient, InitMessage};
use nym_wireguard_types::{GatewayClient, InitMessage, PeerPublicKey};
use serde::{Deserialize, Serialize};
use crate::make_bincode_serializer;
@@ -57,6 +57,19 @@ impl AuthenticatorRequest {
)
}
pub fn new_query_request(peer_public_key: PeerPublicKey, reply_to: Recipient) -> (Self, u64) {
let request_id = generate_random();
(
Self {
version: VERSION,
data: AuthenticatorRequestData::QueryBandwidth(peer_public_key),
reply_to,
request_id,
},
request_id,
)
}
pub fn to_bytes(&self) -> Result<Vec<u8>, bincode::Error> {
use bincode::Options;
make_bincode_serializer().serialize(self)
@@ -67,4 +80,5 @@ impl AuthenticatorRequest {
pub enum AuthenticatorRequestData {
Initial(InitMessage),
Final(GatewayClient),
QueryBandwidth(PeerPublicKey),
}
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use nym_sphinx::addressing::Recipient;
use nym_wireguard_types::registration::RegistrationData;
use nym_wireguard_types::registration::{RegistrationData, RegistredData, RemainingBandwidthData};
use serde::{Deserialize, Serialize};
use crate::make_bincode_serializer;
@@ -33,10 +33,31 @@ impl AuthenticatorResponse {
}
}
pub fn new_registered(reply_to: Recipient, request_id: u64) -> Self {
pub fn new_registered(
registred_data: RegistredData,
reply_to: Recipient,
request_id: u64,
) -> Self {
Self {
version: VERSION,
data: AuthenticatorResponseData::Registered(RegisteredResponse {
reply: registred_data,
reply_to,
request_id,
}),
reply_to,
}
}
pub fn new_remaining_bandwidth(
remaining_bandwidth_data: Option<RemainingBandwidthData>,
reply_to: Recipient,
request_id: u64,
) -> Self {
Self {
version: VERSION,
data: AuthenticatorResponseData::RemainingBandwidth(RemainingBandwidthResponse {
reply: remaining_bandwidth_data,
reply_to,
request_id,
}),
@@ -64,6 +85,7 @@ impl AuthenticatorResponse {
match &self.data {
AuthenticatorResponseData::PendingRegistration(response) => Some(response.request_id),
AuthenticatorResponseData::Registered(response) => Some(response.request_id),
AuthenticatorResponseData::RemainingBandwidth(response) => Some(response.request_id),
}
}
}
@@ -72,6 +94,7 @@ impl AuthenticatorResponse {
pub enum AuthenticatorResponseData {
PendingRegistration(PendingRegistrationResponse),
Registered(RegisteredResponse),
RemainingBandwidth(RemainingBandwidthResponse),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@@ -85,4 +108,12 @@ pub struct PendingRegistrationResponse {
pub struct RegisteredResponse {
pub request_id: u64,
pub reply_to: Recipient,
pub reply: RegistredData,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RemainingBandwidthResponse {
pub request_id: u64,
pub reply_to: Recipient,
pub reply: Option<RemainingBandwidthData>,
}
@@ -683,24 +683,6 @@ pub trait MixnetSigningClient {
.await
}
async fn migrate_vested_mixnode(&self, fee: Option<Fee>) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(fee, MixnetExecuteMsg::MigrateVestedMixNode {}, vec![])
.await
}
async fn migrate_vested_delegation(
&self,
mix_id: MixId,
fee: Option<Fee>,
) -> Result<ExecuteResult, NyxdError> {
self.execute_mixnet_contract(
fee,
MixnetExecuteMsg::MigrateVestedDelegation { mix_id },
vec![],
)
.await
}
#[cfg(feature = "contract-testing")]
async fn testing_resolve_all_pending_events(
&self,
@@ -946,12 +928,6 @@ mod tests {
MixnetExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, owner } => client
.withdraw_delegator_reward_on_behalf(owner.parse().unwrap(), mix_id, None)
.ignore(),
MixnetExecuteMsg::MigrateVestedMixNode { .. } => {
client.migrate_vested_mixnode(None).ignore()
}
MixnetExecuteMsg::MigrateVestedDelegation { mix_id } => {
client.migrate_vested_delegation(mix_id, None).ignore()
}
#[cfg(feature = "contract-testing")]
MixnetExecuteMsg::TestingResolveAllPendingEvents { .. } => {
@@ -437,7 +437,6 @@ where
mod tests {
use super::*;
use crate::nyxd::contract_traits::tests::{mock_coin, IgnoreValue};
use nym_vesting_contract_common::ExecuteMsg;
// it's enough that this compiles and clippy is happy about it
#[allow(dead_code)]
@@ -561,9 +560,6 @@ mod tests {
VestingExecuteMsg::UpdateLockedPledgeCap { address, cap } => client
.update_locked_pledge_cap(address.parse().unwrap(), cap, None)
.ignore(),
// those will never be manually called by clients
ExecuteMsg::TrackMigratedMixnode { .. } => "explicitly_ignored".ignore(),
ExecuteMsg::TrackMigratedDelegation { .. } => "explicitly_ignored".ignore(),
};
}
}
@@ -1,42 +0,0 @@
// 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::MixId;
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, MixnetSigningClient};
#[derive(Debug, Parser)]
pub struct Args {
#[clap(long)]
pub mix_id: Option<MixId>,
#[clap(long)]
pub identity_key: Option<String>,
}
pub async fn migrate_vested_delegation(args: Args, client: SigningClient) {
let mix_id = match args.mix_id {
Some(mix_id) => mix_id,
None => {
let identity_key = args
.identity_key
.expect("either mix_id or mix_identity has to be specified");
let node_details = client
.get_mixnode_details_by_identity(identity_key)
.await
.expect("contract query failed")
.mixnode_details
.expect("mixnode with the specified identity doesnt exist");
node_details.mix_id()
}
};
let res = client
.migrate_vested_delegation(mix_id, None)
.await
.expect("failed to migrate delegation!");
info!("migration result: {:?}", res)
}
@@ -7,7 +7,6 @@ pub mod rewards;
pub mod delegate_to_mixnode;
pub mod delegate_to_multiple_mixnodes;
pub mod migrate_vested_delegation;
pub mod query_for_delegations;
pub mod undelegate_from_mixnode;
pub mod vesting_delegate_to_mixnode;
@@ -36,6 +35,4 @@ pub enum MixnetDelegatorsCommands {
DelegateVesting(vesting_delegate_to_mixnode::Args),
/// Undelegate from a mixnode (when originally using locked tokens)
UndelegateVesting(vesting_undelegate_from_mixnode::Args),
/// Migrate the delegation to use liquid tokens
MigrateVestedDelegation(migrate_vested_delegation::Args),
}
@@ -96,7 +96,6 @@ async fn print_delegation_events(events: Vec<PendingEpochEvent>, client: &Signin
mix_id,
amount,
proxy,
..
} => {
if owner.as_str() == client.nyxd.address().as_ref() {
table.add_row(vec![
@@ -112,7 +111,6 @@ async fn print_delegation_events(events: Vec<PendingEpochEvent>, client: &Signin
owner,
mix_id,
proxy,
..
} => {
if owner.as_str() == client.nyxd.address().as_ref() {
table.add_row(vec![
@@ -8,7 +8,7 @@ use cosmwasm_std::Coin;
use nym_bin_common::output_format::OutputFormat;
use nym_mixnet_contract_common::construct_gateway_bonding_sign_payload;
use nym_network_defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
use nym_validator_client::nyxd::contract_traits::MixnetQueryClient;
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, NymContractsProvider};
#[derive(Debug, Parser)]
pub struct Args {
@@ -39,6 +39,10 @@ pub struct Args {
)]
pub amount: u128,
/// Indicates whether the gateway is going to get bonded via a vesting account
#[arg(long)]
pub with_vesting_account: bool,
#[clap(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
@@ -70,8 +74,15 @@ pub async fn create_payload(args: Args, client: SigningClient) {
};
let address = account_id_to_cw_addr(&client.address());
let proxy = if args.with_vesting_account {
Some(account_id_to_cw_addr(
client.vesting_contract_address().unwrap(),
))
} else {
None
};
let payload = construct_gateway_bonding_sign_payload(nonce, address, coin, gateway);
let payload = construct_gateway_bonding_sign_payload(nonce, address, proxy, coin, gateway);
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
println!("{}", args.output.format(&wrapper))
}
@@ -5,21 +5,33 @@ use crate::context::SigningClient;
use clap::Parser;
use log::info;
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
use nym_validator_client::nyxd::contract_traits::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
/// Label that is going to be used for creating the family
#[arg(long)]
pub family_label: String,
/// Indicates whether the family is going to get created via a vesting account
#[arg(long)]
pub with_vesting_account: bool,
}
pub async fn create_family(args: Args, client: SigningClient) {
info!("Create family");
let res = client
.create_family(args.family_label, None)
.await
.expect("failed to create family");
let res = if args.with_vesting_account {
client
.vesting_create_family(args.family_label, None)
.await
.expect("failed to create family with vesting account")
} else {
client
.create_family(args.family_label, None)
.await
.expect("failed to create family")
};
info!("Family creation result: {:?}", res);
}
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::context::QueryClient;
use crate::utils::DataWrapper;
use crate::utils::{account_id_to_cw_addr, DataWrapper};
use clap::Parser;
use cosmrs::AccountId;
use log::info;
@@ -10,7 +10,7 @@ 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;
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, NymContractsProvider};
#[derive(Debug, Parser)]
pub struct Args {
@@ -18,6 +18,10 @@ pub struct Args {
#[arg(long)]
pub address: AccountId,
/// Indicates whether the member joining the family is going to use the vesting account for joining.
#[arg(long)]
pub with_vesting_account: bool,
// might as well validate the value when parsing the arguments
/// Identity of the member for whom we're issuing the permit
#[arg(long)]
@@ -64,9 +68,18 @@ pub async fn create_family_join_permit_sign_payload(args: Args, client: QueryCli
}
};
// let address = account_id_to_cw_addr(&args.address);
let proxy = if args.with_vesting_account {
Some(account_id_to_cw_addr(
client.vesting_contract_address().unwrap(),
))
} else {
None
};
let head = FamilyHead::new(mixnode.bond_information.identity());
let payload = construct_family_join_permit(nonce, head, args.member.to_base58_string());
let payload = construct_family_join_permit(nonce, head, proxy, args.member.to_base58_string());
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
println!("{}", args.output.format(&wrapper))
}
@@ -8,6 +8,7 @@ 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;
use nym_validator_client::nyxd::contract_traits::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
@@ -15,6 +16,10 @@ pub struct Args {
#[arg(long)]
pub family_head: identity::PublicKey,
/// Indicates whether the member joining the family is going to do so via the vesting contract
#[arg(long)]
pub with_vesting_account: bool,
/// Permission, as provided by the family head, for joining the family
#[arg(long)]
pub join_permit: MessageSignature,
@@ -25,10 +30,17 @@ pub async fn join_family(args: Args, client: SigningClient) {
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");
let res = if args.with_vesting_account {
client
.vesting_join_family(args.join_permit, family_head, None)
.await
.expect("failed to join family with vesting account")
} else {
client
.join_family(args.join_permit, family_head, None)
.await
.expect("failed to join family")
};
info!("Family join result: {:?}", res);
}
@@ -7,12 +7,17 @@ use log::info;
use nym_crypto::asymmetric::identity;
use nym_mixnet_contract_common::families::FamilyHead;
use nym_validator_client::nyxd::contract_traits::MixnetSigningClient;
use nym_validator_client::nyxd::contract_traits::VestingSigningClient;
#[derive(Debug, Parser)]
pub struct Args {
/// The head of the family that we intend to leave
#[arg(long)]
pub family_head: identity::PublicKey,
/// Indicates whether we joined the family via the vesting contract
#[arg(long)]
pub with_vesting_account: bool,
}
pub async fn leave_family(args: Args, client: SigningClient) {
@@ -20,10 +25,17 @@ pub async fn leave_family(args: Args, client: SigningClient) {
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");
let res = if args.with_vesting_account {
client
.vesting_leave_family(family_head, None)
.await
.expect("failed to leave family with vesting account")
} else {
client
.leave_family(family_head, None)
.await
.expect("failed to leave family")
};
info!("Family leave result: {:?}", res);
}
@@ -1,19 +0,0 @@
// 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_vested_mixnode(_args: Args, client: SigningClient) {
let res = client
.migrate_vested_mixnode(None)
.await
.expect("failed to migrate mixnode!");
info!("migration result: {:?}", res)
}
@@ -11,7 +11,7 @@ use nym_mixnet_contract_common::{construct_mixnode_bonding_sign_payload, MixNode
use nym_network_defaults::{
DEFAULT_HTTP_API_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT,
};
use nym_validator_client::nyxd::contract_traits::MixnetQueryClient;
use nym_validator_client::nyxd::contract_traits::{MixnetQueryClient, NymContractsProvider};
use nym_validator_client::nyxd::CosmWasmCoin;
#[derive(Debug, Parser)]
@@ -52,6 +52,10 @@ pub struct Args {
)]
pub amount: u128,
/// Indicates whether the mixnode is going to get bonded via a vesting account
#[arg(long)]
pub with_vesting_account: bool,
#[clap(short, long, default_value_t = OutputFormat::default())]
output: OutputFormat,
}
@@ -96,9 +100,16 @@ pub async fn create_payload(args: Args, client: SigningClient) {
};
let address = account_id_to_cw_addr(&client.address());
let proxy = if args.with_vesting_account {
Some(account_id_to_cw_addr(
client.vesting_contract_address().unwrap(),
))
} else {
None
};
let payload =
construct_mixnode_bonding_sign_payload(nonce, address, coin, mixnode, cost_params);
construct_mixnode_bonding_sign_payload(nonce, address, proxy, coin, mixnode, cost_params);
let wrapper = DataWrapper::new(payload.to_base58_string().unwrap());
println!("{}", args.output.format(&wrapper))
}
@@ -7,7 +7,6 @@ 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 pledge_more;
pub mod rewards;
@@ -53,6 +52,4 @@ pub enum MixnetOperatorsMixnodeCommands {
DecreasePledge(decrease_pledge::Args),
/// Decrease pledge with locked tokens
DecreasePledgeVesting(vesting_decrease_pledge::Args),
/// Migrate the mixnode to use liquid tokens
MigrateVestedNode(migrate_vested_mixnode::Args),
}
@@ -218,6 +218,7 @@ where
#[derive(Serialize)]
pub struct ContractMessageContent<T> {
pub sender: Addr,
pub proxy: Option<Addr>,
pub funds: Vec<Coin>,
pub data: T,
}
@@ -232,17 +233,25 @@ where
}
impl<T> ContractMessageContent<T> {
pub fn new(sender: Addr, funds: Vec<Coin>, data: T) -> Self {
pub fn new(sender: Addr, proxy: Option<Addr>, funds: Vec<Coin>, data: T) -> Self {
ContractMessageContent {
sender,
proxy,
funds,
data,
}
}
pub fn new_with_info(info: MessageInfo, signer: Addr, data: T) -> Self {
let proxy = if info.sender == signer {
None
} else {
Some(info.sender)
};
ContractMessageContent {
sender: signer,
proxy,
funds: info.funds,
data,
}
@@ -65,6 +65,7 @@ impl Delegation {
cumulative_reward_ratio: Decimal,
amount: Coin,
height: u64,
proxy: Option<Addr>,
) -> Self {
assert!(
amount.amount <= TOKEN_SUPPLY,
@@ -77,7 +78,7 @@ impl Delegation {
cumulative_reward_ratio,
amount,
height,
proxy: None,
proxy,
}
}
@@ -76,11 +76,21 @@ pub enum MixnetContractError {
#[error("Received multiple coin types during staking")]
MultipleDenoms,
#[error("Proxy address mismatch, expected {existing}, got {incoming}")]
ProxyMismatch { existing: String, incoming: String },
#[error("Proxy address ({received}) is not set to the vesting contract ({vesting_contract})")]
ProxyIsNotVestingContract {
received: Addr,
vesting_contract: Addr,
},
#[error(
"Sender of this message ({received}) is not the vesting contract ({vesting_contract})"
)]
SenderIsNotVestingContract {
received: Addr,
vesting_contract: Addr,
},
#[error("Failed to recover ed25519 public key from its base58 representation - {0}")]
MalformedEd25519IdentityKey(String),
@@ -229,17 +239,6 @@ pub enum MixnetContractError {
#[from]
source: ApiVerifierError,
},
#[error("this operation is no longer allowed to be performed with vesting tokens. please move them to your liquid balance and try again")]
DisabledVestingOperation,
#[error(
"this mixnode has not been bonded with the vesting tokens or has already been migrated"
)]
NotAVestingMixnode,
#[error("this delegation has not been performed with the vesting tokens or has already been migrated")]
NotAVestingDelegation,
}
impl MixnetContractError {
@@ -103,6 +103,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 PROXY_KEY: &str = "proxy";
// event-specific attributes
@@ -162,6 +163,7 @@ pub const NEW_EPOCHS_IN_INTERVAL: &str = "new_epochs_in_interval";
pub fn new_delegation_event(
created_at: BlockHeight,
delegator: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
mix_id: MixId,
unit_reward: Decimal,
@@ -169,34 +171,58 @@ pub fn new_delegation_event(
Event::new(MixnetEventType::Delegation)
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
.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,
proxy: &Option<Addr>,
mix_id: MixId,
) -> Event {
Event::new(MixnetEventType::Delegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.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,
proxy: &Option<Addr>,
amount: &Coin,
mix_id: MixId,
) -> Event {
Event::new(MixnetEventType::PendingDelegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.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,
proxy: &Option<Addr>,
amount: Coin,
mix_id: MixId,
) -> Event {
Event::new(MixnetEventType::WithdrawOperatorReward)
.add_attribute(OWNER_KEY, owner.as_str())
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.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,
proxy: &Option<Addr>,
amount: Coin,
mix_id: MixId,
) -> Event {
Event::new(MixnetEventType::WithdrawDelegatorReward)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
.add_attribute(DELEGATION_TARGET_KEY, mix_id.to_string())
}
@@ -252,43 +278,59 @@ 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,
proxy: &Option<Addr>,
mix_id: MixId,
) -> Event {
Event::new(MixnetEventType::Undelegation)
.add_attribute(EVENT_CREATION_HEIGHT_KEY, created_at.to_string())
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.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,
proxy: &Option<Addr>,
mix_id: MixId,
) -> Event {
Event::new(MixnetEventType::PendingUndelegation)
.add_attribute(DELEGATOR_KEY, delegator)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(MIX_ID_KEY, mix_id.to_string())
}
pub fn new_gateway_bonding_event(
owner: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(MixnetEventType::GatewayBonding)
.add_attribute(OWNER_KEY, owner)
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_gateway_unbonding_event(
owner: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
identity: IdentityKeyRef<'_>,
) -> Event {
Event::new(MixnetEventType::GatewayUnbonding)
.add_attribute(OWNER_KEY, owner)
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(AMOUNT_KEY, amount.to_string())
}
pub fn new_mixnode_bonding_event(
owner: &Addr,
proxy: &Option<Addr>,
amount: &Coin,
identity: IdentityKeyRef<'_>,
mix_id: MixId,
@@ -299,6 +341,7 @@ pub fn new_mixnode_bonding_event(
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(ASSIGNED_LAYER_KEY, assigned_layer)
.add_attribute(AMOUNT_KEY, amount.to_string())
}
@@ -337,6 +380,7 @@ pub fn new_mixnode_unbonding_event(created_at: BlockHeight, mix_id: MixId) -> Ev
pub fn new_pending_mixnode_unbonding_event(
owner: &Addr,
proxy: &Option<Addr>,
identity: IdentityKeyRef<'_>,
mix_id: MixId,
) -> Event {
@@ -344,33 +388,43 @@ pub fn new_pending_mixnode_unbonding_event(
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(NODE_IDENTITY_KEY, identity)
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
}
pub fn new_mixnode_config_update_event(
mix_id: MixId,
owner: &Addr,
proxy: &Option<Addr>,
update: &MixNodeConfigUpdate,
) -> Event {
Event::new(MixnetEventType::MixnodeConfigUpdate)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(UPDATED_MIXNODE_CONFIG_KEY, update.to_inline_json())
}
pub fn new_gateway_config_update_event(owner: &Addr, update: &GatewayConfigUpdate) -> Event {
pub fn new_gateway_config_update_event(
owner: &Addr,
proxy: &Option<Addr>,
update: &GatewayConfigUpdate,
) -> Event {
Event::new(MixnetEventType::GatewayConfigUpdate)
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(UPDATED_GATEWAY_CONFIG_KEY, update.to_inline_json())
}
pub fn new_mixnode_pending_cost_params_update_event(
mix_id: MixId,
owner: &Addr,
proxy: &Option<Addr>,
new_costs: &MixNodeCostParams,
) -> Event {
Event::new(MixnetEventType::PendingMixnodeCostParamsUpdate)
.add_attribute(MIX_ID_KEY, mix_id.to_string())
.add_attribute(OWNER_KEY, owner)
.add_optional_attribute(PROXY_KEY, proxy.as_ref())
.add_attribute(UPDATED_MIXNODE_COST_PARAMS_KEY, new_costs.to_inline_json())
}
@@ -3,6 +3,7 @@
use crate::{IdentityKey, IdentityKeyRef};
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Addr;
use schemars::JsonSchema;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::{Display, Formatter};
@@ -83,10 +84,10 @@ impl FamilyHead {
}
impl Family {
pub fn new(head: FamilyHead, label: String) -> Self {
pub fn new(head: FamilyHead, proxy: Option<Addr>, label: String) -> Self {
Family {
head,
proxy: None,
proxy: proxy.map(|p| p.to_string()),
label,
}
}
@@ -55,13 +55,19 @@ pub struct GatewayBond {
}
impl GatewayBond {
pub fn new(pledge_amount: Coin, owner: Addr, block_height: u64, gateway: Gateway) -> Self {
pub fn new(
pledge_amount: Coin,
owner: Addr,
block_height: u64,
gateway: Gateway,
proxy: Option<Addr>,
) -> Self {
GatewayBond {
pledge_amount,
owner,
block_height,
gateway,
proxy: None,
proxy,
}
}
@@ -518,6 +518,7 @@ impl MixNodeBond {
original_pledge: Coin,
layer: Layer,
mix_node: MixNode,
proxy: Option<Addr>,
bonding_height: u64,
) -> Self {
MixNodeBond {
@@ -526,7 +527,7 @@ impl MixNodeBond {
original_pledge,
layer,
mix_node,
proxy: None,
proxy,
bonding_height,
is_unbonding: false,
}
@@ -269,12 +269,6 @@ pub enum ExecuteMsg {
owner: String,
},
// vesting migration:
MigrateVestedMixNode {},
MigrateVestedDelegation {
mix_id: MixId,
},
// testing-only
#[cfg(feature = "contract-testing")]
TestingResolveAllPendingEvents {
@@ -387,9 +381,6 @@ impl ExecuteMsg {
ExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, .. } => {
format!("withdrawing delegator reward from mixnode {mix_id} on behalf")
}
ExecuteMsg::MigrateVestedMixNode { .. } => "migrate vested mixnode".into(),
ExecuteMsg::MigrateVestedDelegation { .. } => "migrate vested delegation".to_string(),
#[cfg(feature = "contract-testing")]
ExecuteMsg::TestingResolveAllPendingEvents { .. } => {
"resolving all pending events".into()
@@ -38,7 +38,6 @@ pub enum PendingEpochEventKind {
/// Request to create a delegation towards particular mixnode.
/// Note that if such delegation already exists, it will get updated with the provided token amount.
#[serde(alias = "Delegate")]
#[non_exhaustive]
Delegate {
/// The address of the owner of the delegation.
owner: Addr,
@@ -56,7 +55,6 @@ pub enum PendingEpochEventKind {
/// Request to remove delegation from particular mixnode.
#[serde(alias = "Undelegate")]
#[non_exhaustive]
Undelegate {
/// The address of the owner of the delegation.
owner: Addr,
@@ -111,23 +109,6 @@ impl PendingEpochEventKind {
kind: self,
}
}
pub fn new_delegate(owner: Addr, mix_id: MixId, amount: Coin) -> Self {
PendingEpochEventKind::Delegate {
owner,
mix_id,
amount,
proxy: None,
}
}
pub fn new_undelegate(owner: Addr, mix_id: MixId) -> Self {
PendingEpochEventKind::Undelegate {
owner,
mix_id,
proxy: None,
}
}
}
impl From<(EpochEventId, PendingEpochEventData)> for PendingEpochEvent {
@@ -47,6 +47,7 @@ impl SimulatedNode {
self.rewarding_details.total_unit_reward,
delegation,
42,
None,
);
self.delegations.insert(delegator, delegation);
@@ -37,12 +37,13 @@ impl SigningPurpose for MixnodeBondingPayload {
pub fn construct_mixnode_bonding_sign_payload(
nonce: Nonce,
sender: Addr,
proxy: Option<Addr>,
pledge: Coin,
mix_node: MixNode,
cost_params: MixNodeCostParams,
) -> SignableMixNodeBondingMsg {
let payload = MixnodeBondingPayload::new(mix_node, cost_params);
let content = ContractMessageContent::new(sender, vec![pledge], payload);
let content = ContractMessageContent::new(sender, proxy, vec![pledge], payload);
SignableMessage::new(nonce, content)
}
@@ -67,11 +68,12 @@ impl SigningPurpose for GatewayBondingPayload {
pub fn construct_gateway_bonding_sign_payload(
nonce: Nonce,
sender: Addr,
proxy: Option<Addr>,
pledge: Coin,
gateway: Gateway,
) -> SignableGatewayBondingMsg {
let payload = GatewayBondingPayload::new(gateway);
let content = ContractMessageContent::new(sender, vec![pledge], payload);
let content = ContractMessageContent::new(sender, proxy, vec![pledge], payload);
SignableMessage::new(nonce, content)
}
@@ -80,14 +82,17 @@ pub fn construct_gateway_bonding_sign_payload(
pub struct FamilyJoinPermit {
// the granter of this permit
family_head: FamilyHead,
// whether the **member** will want to join via the proxy (i.e. vesting contract)
proxy: Option<Addr>,
// the actual member we want to permit to join
member_node: IdentityKey,
}
impl FamilyJoinPermit {
pub fn new(family_head: FamilyHead, member_node: IdentityKey) -> Self {
pub fn new(family_head: FamilyHead, proxy: Option<Addr>, member_node: IdentityKey) -> Self {
Self {
family_head,
proxy,
member_node,
}
}
@@ -102,9 +107,10 @@ impl SigningPurpose for FamilyJoinPermit {
pub fn construct_family_join_permit(
nonce: Nonce,
family_head: FamilyHead,
proxy: Option<Addr>,
member_node: IdentityKey,
) -> SignableFamilyJoinPermitMsg {
let payload = FamilyJoinPermit::new(family_head, member_node);
let payload = FamilyJoinPermit::new(family_head, proxy, member_node);
// 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
@@ -167,11 +167,3 @@ pub fn new_track_undelegation_event() -> Event {
pub fn new_track_reward_event() -> Event {
Event::new(TRACK_REWARD_EVENT_TYPE)
}
pub fn new_track_migrate_mixnode_event() -> Event {
Event::new("track_migrate_vesting_mixnode")
}
pub fn new_track_migrate_delegation_event() -> Event {
Event::new("track_migrate_vesting_delegation")
}
@@ -136,14 +136,6 @@ pub enum ExecuteMsg {
address: String,
cap: PledgeCap,
},
TrackMigratedMixnode {
owner: String,
},
// no need to track migrated gateways as there are no vesting gateways on mainnet
TrackMigratedDelegation {
owner: String,
mix_id: MixId,
},
}
impl ExecuteMsg {
@@ -179,10 +171,6 @@ impl ExecuteMsg {
ExecuteMsg::TransferOwnership { .. } => "VestingExecuteMsg::TransferOwnership",
ExecuteMsg::UpdateStakingAddress { .. } => "VestingExecuteMsg::UpdateStakingAddress",
ExecuteMsg::UpdateLockedPledgeCap { .. } => "VestingExecuteMsg::UpdateLockedPledgeCap",
ExecuteMsg::TrackMigratedMixnode { .. } => "VestingExecuteMsg::TrackMigratedMixnode",
ExecuteMsg::TrackMigratedDelegation { .. } => {
"VestingExecuteMsg::TrackMigratedDelegation"
}
}
}
}
-2
View File
@@ -84,7 +84,6 @@ impl PendingEpochEventData {
mix_id,
amount,
proxy,
..
} => Ok(PendingEpochEventData::Delegate {
owner: owner.into_string(),
mix_id,
@@ -95,7 +94,6 @@ impl PendingEpochEventData {
owner,
mix_id,
proxy,
..
} => Ok(PendingEpochEventData::Undelegate {
owner: owner.into_string(),
mix_id,
@@ -27,12 +27,15 @@ pub type HmacSha256 = Hmac<Sha256>;
pub type Nonce = u64;
pub type Taken = Option<SystemTime>;
pub const BANDWIDTH_CAP_PER_DAY: u64 = 1024 * 1024 * 1024; // 1 GB
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type", rename_all = "camelCase")]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub enum ClientMessage {
Initial(InitMessage),
Final(GatewayClient),
Query(PeerPublicKey),
}
#[derive(Serialize, Deserialize, Debug, Clone)]
@@ -60,6 +63,19 @@ pub struct RegistrationData {
pub wg_port: u16,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RegistredData {
pub pub_key: PeerPublicKey,
pub private_ip: IpAddr,
pub wg_port: u16,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct RemainingBandwidthData {
pub available_bandwidth: u64,
pub suspended: bool,
}
/// Client that wants to register sends its PublicKey bytes mac digest encrypted with a DH shared secret.
/// Gateway/Nym node can then verify pub_key payload using the same process
#[derive(Serialize, Deserialize, Debug, Clone)]
+2
View File
@@ -12,6 +12,7 @@ license.workspace = true
[dependencies]
base64 = { workspace = true }
chrono = { workspace = true }
dashmap = { workspace = true }
defguard_wireguard_rs = { workspace = true }
# The latest version on crates.io at the time of writing this (6.0.0) has a
@@ -24,5 +25,6 @@ nym-crypto = { path = "../crypto", features = ["asymmetric"] }
nym-network-defaults = { path = "../network-defaults" }
nym-task = { path = "../task" }
nym-wireguard-types = { path = "../wireguard-types" }
thiserror = { workspace = true }
tokio = { workspace = true, features = ["rt-multi-thread", "net", "io-util"] }
tokio-stream = { workspace = true }
+11
View File
@@ -0,0 +1,11 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("peers in wireguard don't match with in-memory ")]
PeerMismatch,
#[error("{0}")]
Defguard(#[from] defguard_wireguard_rs::error::WireguardInterfaceError),
}
+25 -14
View File
@@ -1,3 +1,6 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#![cfg_attr(not(target_os = "linux"), allow(dead_code))]
// #![warn(clippy::pedantic)]
// #![warn(clippy::expect_used)]
@@ -6,13 +9,14 @@
use dashmap::DashMap;
use defguard_wireguard_rs::{host::Peer, key::Key, net::IpAddrMask, WGApi};
use nym_crypto::asymmetric::encryption::KeyPair;
use nym_wireguard_types::{Config, Error, GatewayClient, GatewayClientRegistry};
use peer_controller::PeerControlMessage;
use nym_wireguard_types::{Config, Error, GatewayClient, GatewayClientRegistry, PeerPublicKey};
use peer_controller::PeerControlRequest;
use std::sync::Arc;
use tokio::sync::mpsc::{self, UnboundedReceiver};
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
const WG_TUN_NAME: &str = "nymwg";
pub(crate) mod error;
pub mod peer_controller;
pub struct WgApiWrapper {
@@ -39,14 +43,14 @@ pub struct WireguardGatewayData {
config: Config,
keypair: Arc<KeyPair>,
client_registry: Arc<GatewayClientRegistry>,
peer_tx: mpsc::UnboundedSender<PeerControlMessage>,
peer_tx: UnboundedSender<PeerControlRequest>,
}
impl WireguardGatewayData {
pub fn new(
config: Config,
keypair: Arc<KeyPair>,
) -> (Self, mpsc::UnboundedReceiver<PeerControlMessage>) {
) -> (Self, UnboundedReceiver<PeerControlRequest>) {
let (peer_tx, peer_rx) = mpsc::unbounded_channel();
(
WireguardGatewayData {
@@ -75,20 +79,26 @@ impl WireguardGatewayData {
let mut peer = Peer::new(Key::new(client.pub_key.to_bytes()));
peer.allowed_ips
.push(IpAddrMask::new(client.private_ip, 32));
let msg = PeerControlMessage::AddPeer(peer);
let msg = PeerControlRequest::AddPeer(peer);
self.peer_tx.send(msg).map_err(|_| Error::PeerModifyStopped)
}
pub fn remove_peer(&self, client: &GatewayClient) -> Result<(), Error> {
let key = Key::new(client.pub_key().to_bytes());
let msg = PeerControlMessage::RemovePeer(key);
let msg = PeerControlRequest::RemovePeer(key);
self.peer_tx.send(msg).map_err(|_| Error::PeerModifyStopped)
}
pub fn query_bandwidth(&self, peer_public_key: PeerPublicKey) -> Result<(), Error> {
let key = Key::new(peer_public_key.to_bytes());
let msg = PeerControlRequest::QueryBandwidth(key);
self.peer_tx.send(msg).map_err(|_| Error::PeerModifyStopped)
}
}
pub struct WireguardData {
pub inner: WireguardGatewayData,
pub peer_rx: UnboundedReceiver<PeerControlMessage>,
pub peer_rx: UnboundedReceiver<PeerControlRequest>,
}
/// Start wireguard device
@@ -96,6 +106,7 @@ pub struct WireguardData {
pub async fn start_wireguard(
task_client: nym_task::TaskClient,
wireguard_data: WireguardData,
control_tx: UnboundedSender<peer_controller::PeerControlResponse>,
) -> Result<std::sync::Arc<WgApiWrapper>, Box<dyn std::error::Error + Send + Sync + 'static>> {
use base64::{prelude::BASE64_STANDARD, Engine};
use defguard_wireguard_rs::{InterfaceConfiguration, WireguardInterfaceApi};
@@ -135,13 +146,13 @@ pub async fn start_wireguard(
wg_api.configure_peer_routing(&[catch_all_peer])?;
let wg_api = std::sync::Arc::new(WgApiWrapper::new(wg_api));
let mut controller = PeerController::new(wg_api.clone(), wireguard_data.peer_rx);
let mut controller = PeerController::new(
wg_api.clone(),
interface_config.peers,
wireguard_data.peer_rx,
control_tx,
);
tokio::spawn(async move { controller.run(task_client).await });
Ok(wg_api)
}
#[cfg(not(target_os = "linux"))]
pub async fn start_wireguard() {
todo!("WireGuard is currently only supported on Linux");
}
+128 -37
View File
@@ -1,91 +1,182 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use std::{
sync::Arc,
time::{Duration, SystemTime},
};
use defguard_wireguard_rs::{
host::{Host, Peer},
key::Key,
WGApi, WireguardInterfaceApi,
};
use chrono::{Timelike, Utc};
use defguard_wireguard_rs::{host::Peer, key::Key, WireguardInterfaceApi};
use nym_wireguard_types::registration::{RemainingBandwidthData, BANDWIDTH_CAP_PER_DAY};
use std::time::SystemTime;
use std::{collections::HashMap, sync::Arc, time::Duration};
use tokio::sync::mpsc;
use tokio_stream::{wrappers::IntervalStream, StreamExt};
use crate::error::Error;
use crate::WgApiWrapper;
const DEFAULT_PEER_TIMEOUT: Duration = Duration::from_secs(60 * 60); // 1 hour
// To avoid any problems, keep this stale check time bigger (>2x) then the bandwidth cap
// reset time (currently that one is 24h, at UTC midnight)
const DEFAULT_PEER_TIMEOUT: Duration = Duration::from_secs(60 * 60 * 24 * 3); // 3 days
const DEFAULT_PEER_TIMEOUT_CHECK: Duration = Duration::from_secs(60); // 1 minute
pub enum PeerControlMessage {
pub enum PeerControlRequest {
AddPeer(Peer),
RemovePeer(Key),
QueryBandwidth(Key),
}
pub enum PeerControlResponse {
AddPeer {
success: bool,
},
RemovePeer {
success: bool,
},
QueryBandwidth {
bandwidth_data: Option<RemainingBandwidthData>,
},
}
pub struct PeerController {
peer_rx: mpsc::UnboundedReceiver<PeerControlMessage>,
request_rx: mpsc::UnboundedReceiver<PeerControlRequest>,
response_tx: mpsc::UnboundedSender<PeerControlResponse>,
wg_api: Arc<WgApiWrapper>,
timeout_check_interval: IntervalStream,
active_peers: HashMap<Key, Peer>,
suspended_peers: HashMap<Key, Peer>,
last_seen_bandwidth: HashMap<Key, u64>,
}
impl PeerController {
pub fn new(
wg_api: Arc<WgApiWrapper>,
peer_rx: mpsc::UnboundedReceiver<PeerControlMessage>,
peers: Vec<Peer>,
request_rx: mpsc::UnboundedReceiver<PeerControlRequest>,
response_tx: mpsc::UnboundedSender<PeerControlResponse>,
) -> Self {
let timeout_check_interval = tokio_stream::wrappers::IntervalStream::new(
tokio::time::interval(DEFAULT_PEER_TIMEOUT_CHECK),
);
let active_peers = peers
.into_iter()
.map(|peer| (peer.public_key.clone(), peer))
.collect();
PeerController {
wg_api,
peer_rx,
request_rx,
response_tx,
timeout_check_interval,
active_peers,
suspended_peers: HashMap::new(),
last_seen_bandwidth: HashMap::new(),
}
}
fn remove_stale_peers(wg_api: &WGApi, host: Host) {
let current_timestamp = SystemTime::now();
for (key, peer) in host.peers.iter() {
if let Some(timestamp) = peer.last_handshake {
if let Ok(duration_since_handshake) = current_timestamp.duration_since(timestamp) {
if duration_since_handshake > DEFAULT_PEER_TIMEOUT {
if let Err(e) = wg_api.remove_peer(key) {
log::error!("Could not remove stale peer: {:?}", e);
} else {
log::debug!("Removed stale peer {:?}", key);
}
}
fn check_stale_peer(&self, peer: &Peer, current_timestamp: SystemTime) -> Result<bool, Error> {
if let Some(timestamp) = peer.last_handshake {
if let Ok(duration_since_handshake) = current_timestamp.duration_since(timestamp) {
if duration_since_handshake > DEFAULT_PEER_TIMEOUT {
self.wg_api.inner.remove_peer(&peer.public_key)?;
return Ok(true);
}
}
}
Ok(false)
}
fn check_suspend_peer(&mut self, peer: &Peer) -> Result<(), Error> {
let prev_peer = self
.active_peers
.get(&peer.public_key)
.ok_or(Error::PeerMismatch)?;
let data_usage =
(peer.rx_bytes + peer.tx_bytes).saturating_sub(prev_peer.rx_bytes + prev_peer.tx_bytes);
if data_usage > BANDWIDTH_CAP_PER_DAY {
self.wg_api.inner.remove_peer(&peer.public_key)?;
let (moved_key, moved_peer) = self
.active_peers
.remove_entry(&peer.public_key)
.ok_or(Error::PeerMismatch)?;
self.suspended_peers.insert(moved_key, moved_peer);
}
Ok(())
}
fn check_peers(&mut self) -> Result<(), Error> {
// Add 10 seconds to cover edge cases. At worst, we give ten free seconds worth of bandwidth
// by resetting the bandwidth twice
let reset = Utc::now().num_seconds_from_midnight() as u64
<= DEFAULT_PEER_TIMEOUT_CHECK.as_secs() + 10;
if reset {
for (_, peer) in self.suspended_peers.drain() {
self.wg_api.inner.configure_peer(&peer)?;
}
}
let host = self.wg_api.inner.read_interface_data()?;
self.last_seen_bandwidth = host
.peers
.iter()
.map(|(key, peer)| (key.clone(), peer.rx_bytes + peer.tx_bytes))
.collect();
if reset {
self.active_peers = host.peers;
} else {
let current_timestamp = SystemTime::now();
for peer in host.peers.values() {
if !self.check_stale_peer(peer, current_timestamp)? {
self.check_suspend_peer(peer)?;
}
}
}
Ok(())
}
pub async fn run(&mut self, mut task_client: nym_task::TaskClient) {
loop {
tokio::select! {
_ = self.timeout_check_interval.next() => {
match self.wg_api.inner.read_interface_data() {
Ok(host) => Self::remove_stale_peers(&self.wg_api.inner, host),
Err(e) => { log::error!("Could not read peer data: {:?}", e); },
if let Err(e) = self.check_peers() {
log::error!("Error while periodically checking peers: {:?}", e);
}
}
_ = task_client.recv() => {
log::trace!("PeerController handler: Received shutdown");
break;
}
msg = self.peer_rx.recv() => {
msg = self.request_rx.recv() => {
match msg {
Some(PeerControlMessage::AddPeer(peer)) => {
if let Err(e) = self.wg_api.inner.configure_peer(&peer) {
Some(PeerControlRequest::AddPeer(peer)) => {
let success = if let Err(e) = self.wg_api.inner.configure_peer(&peer) {
log::error!("Could not configure peer: {:?}", e);
}
false
} else {
self.active_peers.insert(peer.public_key.clone(), peer);
true
};
self.response_tx.send(PeerControlResponse::AddPeer { success }).ok();
}
Some(PeerControlMessage::RemovePeer(peer_pubkey)) => {
if let Err(e) = self.wg_api.inner.remove_peer(&peer_pubkey) {
Some(PeerControlRequest::RemovePeer(peer_pubkey)) => {
let success = if let Err(e) = self.wg_api.inner.remove_peer(&peer_pubkey) {
log::error!("Could not remove peer: {:?}", e);
}
false
} else {
self.active_peers.remove(&peer_pubkey);
self.suspended_peers.remove(&peer_pubkey);
true
};
self.response_tx.send(PeerControlResponse::RemovePeer { success }).ok();
}
Some(PeerControlRequest::QueryBandwidth(peer_pubkey)) => {
let msg = if self.suspended_peers.contains_key(&peer_pubkey) {
PeerControlResponse::QueryBandwidth { bandwidth_data: Some(RemainingBandwidthData{ available_bandwidth: 0, suspended: true }) }
} else if let Some(&consumed_bandwidth) = self.last_seen_bandwidth.get(&peer_pubkey) {
PeerControlResponse::QueryBandwidth { bandwidth_data: Some(RemainingBandwidthData{ available_bandwidth: BANDWIDTH_CAP_PER_DAY - consumed_bandwidth, suspended: false })}
} else {
PeerControlResponse::QueryBandwidth { bandwidth_data: None }
};
self.response_tx.send(msg).ok();
}
None => {
log::trace!("PeerController [main loop]: stopping since channel closed");
@@ -0,0 +1,239 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::support::helpers::{mix_coin, mix_coins, vesting_owner};
use crate::support::setup::{TestSetup, MIX_DENOM};
use cosmwasm_std::Addr;
use cw_multi_test::Executor;
use nym_contracts_common::Percent;
use nym_mixnet_contract_common::error::MixnetContractError;
use nym_mixnet_contract_common::{ContractStateParams, MixNodeCostParams};
use nym_mixnet_contract_common::{MixOwnershipResponse, QueryMsg as MixnetQueryMsg};
use nym_vesting_contract_common::{ExecuteMsg as VestingExecuteMsg, VestingContractError};
#[test]
fn decrease_mixnode_pledge_from_vesting_account_with_minimum_pledge() {
let mut test = TestSetup::new_simple();
let vesting_account = "vesting-account";
// 0. get the minimum pledge amount
let state_params: ContractStateParams = test
.app
.wrap()
.query_wasm_smart(test.mixnet_contract(), &MixnetQueryMsg::GetStateParams {})
.unwrap();
let minimum_pledge = state_params.minimum_mixnode_pledge;
// 1. create vesting account
let create_msg = VestingExecuteMsg::CreateAccount {
owner_address: vesting_account.to_string(),
staking_address: None,
vesting_spec: None,
cap: None,
};
test.app
.execute_contract(
vesting_owner(),
test.vesting_contract(),
&create_msg,
&mix_coins(1_000_000_000),
)
.unwrap();
// 2. bond mixnode with the vesting account
let pledge = minimum_pledge.clone();
let cost_params = MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: mix_coin(40_000_000),
};
let (mix_node, owner_signature) = test.valid_mixnode_with_sig(
vesting_account,
Some(test.vesting_contract()),
cost_params.clone(),
pledge.clone(),
);
let bond_msg = VestingExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature,
amount: pledge.clone(),
};
test.app
.execute_contract(
Addr::unchecked(vesting_account),
test.vesting_contract(),
&bond_msg,
&[],
)
.unwrap();
// 3. try to decrease the pledge
// trying to decrease by a zero amount - not valid
let decrease_pledge_msg = VestingExecuteMsg::DecreasePledge {
amount: mix_coin(0),
};
let res_zero = test
.app
.execute_contract(
Addr::unchecked(vesting_account),
test.vesting_contract(),
&decrease_pledge_msg,
&[],
)
.unwrap_err();
assert_eq!(
VestingContractError::EmptyFunds,
res_zero.downcast().unwrap()
);
// trying to go below the cap - also not valid
let amount = mix_coin(50_000);
let decrease_pledge_msg = VestingExecuteMsg::DecreasePledge {
amount: amount.clone(),
};
let res_below = test
.app
.execute_contract(
Addr::unchecked(vesting_account),
test.vesting_contract(),
&decrease_pledge_msg,
&[],
)
.unwrap_err();
assert_eq!(
MixnetContractError::InvalidPledgeReduction {
current: pledge.amount,
decrease_by: amount.amount,
minimum: minimum_pledge.amount,
denom: minimum_pledge.denom
},
res_below.downcast().unwrap()
)
}
#[test]
fn decrease_mixnode_pledge_from_vesting_account_with_sufficient_pledge() {
let mut test = TestSetup::new_simple();
let vesting_account = "vesting-account";
// 1. create vesting account
let create_msg = VestingExecuteMsg::CreateAccount {
owner_address: vesting_account.to_string(),
staking_address: None,
vesting_spec: None,
cap: None,
};
test.app
.execute_contract(
vesting_owner(),
test.vesting_contract(),
&create_msg,
&mix_coins(10_000_000_000),
)
.unwrap();
// 2. bond mixnode with the vesting account
let pledge = mix_coin(150_000_000);
let cost_params = MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(10).unwrap(),
interval_operating_cost: mix_coin(40_000_000),
};
let (mix_node, owner_signature) = test.valid_mixnode_with_sig(
vesting_account,
Some(test.vesting_contract()),
cost_params.clone(),
pledge.clone(),
);
let bond_msg = VestingExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature,
amount: pledge,
};
test.app
.execute_contract(
Addr::unchecked(vesting_account),
test.vesting_contract(),
&bond_msg,
&[],
)
.unwrap();
// 3. try to decrease the pledge
let before: MixOwnershipResponse = test
.app
.wrap()
.query_wasm_smart(
test.mixnet_contract(),
&MixnetQueryMsg::GetOwnedMixnode {
address: vesting_account.to_string(),
},
)
.unwrap();
let balance_before = test
.app
.wrap()
.query_balance(test.vesting_contract(), MIX_DENOM)
.unwrap();
assert_eq!(balance_before.amount.u128(), 9_850_000_000);
let decrease_pledge_msg = VestingExecuteMsg::DecreasePledge {
amount: mix_coin(50_000_000),
};
test.app
.execute_contract(
Addr::unchecked(vesting_account),
test.vesting_contract(),
&decrease_pledge_msg,
&[],
)
.unwrap();
let after_decrease: MixOwnershipResponse = test
.app
.wrap()
.query_wasm_smart(
test.mixnet_contract(),
&MixnetQueryMsg::GetOwnedMixnode {
address: vesting_account.to_string(),
},
)
.unwrap();
// note: nothing has changed with the pledge because the event hasn't been resolved yet!
assert_eq!(before.address, after_decrease.address);
let before_details = before.mixnode_details.unwrap();
let after_details = after_decrease.mixnode_details.unwrap();
assert_eq!(
before_details.rewarding_details,
after_details.rewarding_details
);
assert_eq!(
before_details.bond_information,
after_details.bond_information
);
// but we have the pending change saved now!
assert!(before_details.pending_changes.pledge_change.is_none());
assert_eq!(Some(1), after_details.pending_changes.pledge_change);
// 4. resolve events
test.advance_mixnet_epoch();
let balance_after = test
.app
.wrap()
.query_balance(test.vesting_contract(), MIX_DENOM)
.unwrap();
assert_eq!(balance_after.amount.u128(), 9_900_000_000);
}
@@ -12,33 +12,27 @@ pub fn mixnet_owner() -> Addr {
Addr::unchecked(MIXNET_OWNER)
}
#[allow(unused)]
pub fn vesting_owner() -> Addr {
Addr::unchecked(VESTING_OWNER)
}
#[allow(unused)]
pub fn rewarding_validator() -> Addr {
Addr::unchecked(REWARDING_VALIDATOR)
}
#[allow(unused)]
pub fn mix_coins(amount: u128) -> Vec<Coin> {
coins(amount, MIX_DENOM)
}
#[allow(unused)]
pub fn mix_coin(amount: u128) -> Coin {
coin(amount, MIX_DENOM)
}
#[allow(unused)]
pub fn test_rng() -> ChaCha20Rng {
let dummy_seed = [42u8; 32];
ChaCha20Rng::from_seed(dummy_seed)
}
#[allow(unused)]
pub fn mixnet_contract_wrapper() -> Box<dyn Contract<Empty>> {
Box::new(
ContractWrapper::new(
@@ -26,7 +26,6 @@ pub const VESTING_OWNER: &str = "vesting-owner";
pub const REWARDING_VALIDATOR: &str = "rewarding-validator";
pub const MIX_DENOM: &str = "unym";
#[allow(unused)]
pub struct ContractInstantiationResult {
mixnet_contract_address: Addr,
vesting_contract_address: Addr,
@@ -70,15 +69,14 @@ impl TestSetupBuilder {
}
}
#[allow(unused)]
pub struct TestSetup {
pub app: App,
pub rng: ChaCha20Rng,
pub mixnet_contract: Addr,
pub vesting_contract: Addr,
}
#[allow(unused)]
impl TestSetup {
pub fn new_simple() -> Self {
TestSetup::new(Default::default(), fixtures::default_mixnet_init_msg())
@@ -93,6 +91,7 @@ impl TestSetup {
app,
rng: test_rng(),
mixnet_contract: contracts.mixnet_contract_address,
vesting_contract: contracts.vesting_contract_address,
}
}
@@ -100,6 +99,10 @@ impl TestSetup {
self.mixnet_contract.clone()
}
pub fn vesting_contract(&self) -> Addr {
self.vesting_contract.clone()
}
pub fn skip_to_current_epoch_end(&mut self) {
let current_interval: CurrentIntervalResponse = self
.app
@@ -206,6 +209,7 @@ impl TestSetup {
pub fn valid_mixnode_with_sig(
&mut self,
owner: &str,
proxy: Option<Addr>,
cost_params: MixNodeCostParams,
stake: Coin,
) -> (MixNode, MessageSignature) {
@@ -235,7 +239,8 @@ impl TestSetup {
};
let payload = MixnodeBondingPayload::new(mixnode.clone(), cost_params);
let content = ContractMessageContent::new(Addr::unchecked(owner), vec![stake], payload);
let content =
ContractMessageContent::new(Addr::unchecked(owner), proxy, vec![stake], payload);
let sign_payload = SignableMixNodeBondingMsg::new(signing_nonce, content);
let plaintext = sign_payload.to_plaintext().unwrap();
let signature = keypair.private_key().sign(plaintext);
@@ -1,4 +1,5 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
mod decrease_mixnode_pledge;
mod support;
@@ -1146,42 +1146,6 @@
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"migrate_vested_mix_node"
],
"properties": {
"migrate_vested_mix_node": {
"type": "object",
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"migrate_vested_delegation"
],
"properties": {
"migrate_vested_delegation": {
"type": "object",
"required": [
"mix_id"
],
"properties": {
"mix_id": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
],
"definitions": {
-36
View File
@@ -1029,42 +1029,6 @@
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"migrate_vested_mix_node"
],
"properties": {
"migrate_vested_mix_node": {
"type": "object",
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"migrate_vested_delegation"
],
"properties": {
"migrate_vested_delegation": {
"type": "object",
"required": [
"mix_id"
],
"properties": {
"mix_id": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
],
"definitions": {
+115 -31
View File
@@ -118,6 +118,44 @@ pub fn execute(
ExecuteMsg::KickFamilyMember { member } => {
crate::families::transactions::try_head_kick_member(deps, info, member)
}
ExecuteMsg::CreateFamilyOnBehalf {
owner_address,
label,
} => crate::families::transactions::try_create_family_on_behalf(
deps,
info,
owner_address,
label,
),
ExecuteMsg::JoinFamilyOnBehalf {
member_address,
join_permit,
family_head,
} => crate::families::transactions::try_join_family_on_behalf(
deps,
info,
member_address,
join_permit,
family_head,
),
ExecuteMsg::LeaveFamilyOnBehalf {
member_address,
family_head,
} => crate::families::transactions::try_leave_family_on_behalf(
deps,
info,
member_address,
family_head,
),
ExecuteMsg::KickFamilyMemberOnBehalf {
head_address,
member,
} => crate::families::transactions::try_head_kick_member_on_behalf(
deps,
info,
head_address,
member,
),
// state/sys-params-related
ExecuteMsg::UpdateRewardingValidatorAddress { address } => {
crate::mixnet_contract_settings::transactions::try_update_rewarding_validator_address(
@@ -194,23 +232,62 @@ pub fn execute(
cost_params,
owner_signature,
),
ExecuteMsg::BondMixnodeOnBehalf {
mix_node,
cost_params,
owner,
owner_signature,
} => crate::mixnodes::transactions::try_add_mixnode_on_behalf(
deps,
env,
info,
mix_node,
cost_params,
owner,
owner_signature,
),
ExecuteMsg::PledgeMore {} => {
crate::mixnodes::transactions::try_increase_pledge(deps, env, info)
}
ExecuteMsg::PledgeMoreOnBehalf { owner } => {
crate::mixnodes::transactions::try_increase_pledge_on_behalf(deps, env, info, owner)
}
ExecuteMsg::DecreasePledge { decrease_by } => {
crate::mixnodes::transactions::try_decrease_pledge(deps, env, info, decrease_by)
}
ExecuteMsg::DecreasePledgeOnBehalf { owner, decrease_by } => {
crate::mixnodes::transactions::try_decrease_pledge_on_behalf(
deps,
env,
info,
decrease_by,
owner,
)
}
ExecuteMsg::UnbondMixnode {} => {
crate::mixnodes::transactions::try_remove_mixnode(deps, env, info)
}
ExecuteMsg::UnbondMixnodeOnBehalf { owner } => {
crate::mixnodes::transactions::try_remove_mixnode_on_behalf(deps, env, info, owner)
}
ExecuteMsg::UpdateMixnodeCostParams { new_costs } => {
crate::mixnodes::transactions::try_update_mixnode_cost_params(
deps, env, info, new_costs,
)
}
ExecuteMsg::UpdateMixnodeCostParamsOnBehalf { new_costs, owner } => {
crate::mixnodes::transactions::try_update_mixnode_cost_params_on_behalf(
deps, env, info, new_costs, owner,
)
}
ExecuteMsg::UpdateMixnodeConfig { new_config } => {
crate::mixnodes::transactions::try_update_mixnode_config(deps, info, new_config)
}
ExecuteMsg::UpdateMixnodeConfigOnBehalf { new_config, owner } => {
crate::mixnodes::transactions::try_update_mixnode_config_on_behalf(
deps, info, new_config, owner,
)
}
// gateway-related:
ExecuteMsg::BondGateway {
@@ -223,22 +300,52 @@ pub fn execute(
gateway,
owner_signature,
),
ExecuteMsg::BondGatewayOnBehalf {
gateway,
owner,
owner_signature,
} => crate::gateways::transactions::try_add_gateway_on_behalf(
deps,
env,
info,
gateway,
owner,
owner_signature,
),
ExecuteMsg::UnbondGateway {} => {
crate::gateways::transactions::try_remove_gateway(deps, info)
}
ExecuteMsg::UnbondGatewayOnBehalf { owner } => {
crate::gateways::transactions::try_remove_gateway_on_behalf(deps, info, owner)
}
ExecuteMsg::UpdateGatewayConfig { new_config } => {
crate::gateways::transactions::try_update_gateway_config(deps, info, new_config)
}
ExecuteMsg::UpdateGatewayConfigOnBehalf { new_config, owner } => {
crate::gateways::transactions::try_update_gateway_config_on_behalf(
deps, info, new_config, owner,
)
}
// delegation-related:
ExecuteMsg::DelegateToMixnode { mix_id } => {
crate::delegations::transactions::try_delegate_to_mixnode(deps, env, info, mix_id)
}
ExecuteMsg::DelegateToMixnodeOnBehalf { mix_id, delegate } => {
crate::delegations::transactions::try_delegate_to_mixnode_on_behalf(
deps, env, info, mix_id, delegate,
)
}
ExecuteMsg::UndelegateFromMixnode { mix_id } => {
crate::delegations::transactions::try_remove_delegation_from_mixnode(
deps, env, info, mix_id,
)
}
ExecuteMsg::UndelegateFromMixnodeOnBehalf { mix_id, delegate } => {
crate::delegations::transactions::try_remove_delegation_from_mixnode_on_behalf(
deps, env, info, mix_id, delegate,
)
}
// reward-related
ExecuteMsg::RewardMixnode {
@@ -249,37 +356,16 @@ pub fn execute(
ExecuteMsg::WithdrawOperatorReward {} => {
crate::rewards::transactions::try_withdraw_operator_reward(deps, info)
}
ExecuteMsg::WithdrawOperatorRewardOnBehalf { owner } => {
crate::rewards::transactions::try_withdraw_operator_reward_on_behalf(deps, info, owner)
}
ExecuteMsg::WithdrawDelegatorReward { mix_id } => {
crate::rewards::transactions::try_withdraw_delegator_reward(deps, info, mix_id)
}
// vesting migration:
ExecuteMsg::MigrateVestedMixNode { .. } => {
crate::vesting_migration::try_migrate_vested_mixnode(deps, info)
}
ExecuteMsg::MigrateVestedDelegation { mix_id } => {
crate::vesting_migration::try_migrate_vested_delegation(deps, info, mix_id)
}
// legacy vesting
ExecuteMsg::CreateFamilyOnBehalf { .. }
| ExecuteMsg::JoinFamilyOnBehalf { .. }
| ExecuteMsg::LeaveFamilyOnBehalf { .. }
| ExecuteMsg::KickFamilyMemberOnBehalf { .. }
| ExecuteMsg::BondMixnodeOnBehalf { .. }
| ExecuteMsg::PledgeMoreOnBehalf { .. }
| ExecuteMsg::DecreasePledgeOnBehalf { .. }
| ExecuteMsg::UnbondMixnodeOnBehalf { .. }
| ExecuteMsg::UpdateMixnodeCostParamsOnBehalf { .. }
| ExecuteMsg::UpdateMixnodeConfigOnBehalf { .. }
| ExecuteMsg::BondGatewayOnBehalf { .. }
| ExecuteMsg::UnbondGatewayOnBehalf { .. }
| ExecuteMsg::UpdateGatewayConfigOnBehalf { .. }
| ExecuteMsg::DelegateToMixnodeOnBehalf { .. }
| ExecuteMsg::UndelegateFromMixnodeOnBehalf { .. }
| ExecuteMsg::WithdrawOperatorRewardOnBehalf { .. }
| ExecuteMsg::WithdrawDelegatorRewardOnBehalf { .. } => {
Err(MixnetContractError::DisabledVestingOperation)
ExecuteMsg::WithdrawDelegatorRewardOnBehalf { mix_id, owner } => {
crate::rewards::transactions::try_withdraw_delegator_reward_on_behalf(
deps, info, mix_id, owner,
)
}
// testing-only
@@ -521,15 +607,13 @@ pub fn query(
#[entry_point]
pub fn migrate(
mut deps: DepsMut<'_>,
deps: DepsMut<'_>,
_env: Env,
msg: MigrateMsg,
) -> Result<Response, MixnetContractError> {
set_build_information!(deps.storage)?;
cw2::ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
crate::queued_migrations::vesting_purge(deps.branch())?;
// due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address
// and vesting contract requiring the mixnet contract address), if we ever want to deploy any new fresh
// environment, one of the contracts will HAVE TO go through a migration
+73 -2
View File
@@ -302,7 +302,10 @@ mod tests {
mod delegator_delegations {
use super::*;
use cosmwasm_std::Addr;
use crate::delegations::transactions::try_delegate_to_mixnode_on_behalf;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{coin, Addr};
#[test]
fn obeys_limits() {
@@ -450,10 +453,25 @@ mod tests {
#[test]
fn all_retrieved_delegations_are_from_the_specified_delegator() {
let mut test = TestSetup::new();
let env = test.env();
// it means we have, for example, delegation from "delegator1" towards mix1, mix2, ...., from "delegator2" towards mix1, mix2, ...., etc
add_dummy_mixes_with_delegations(&mut test, 50, 100);
// add some proxies while we're at it to make sure they're queried for separately
let with_proxy = "delegator42";
let vesting_contract = test.vesting_contract();
for mix_id in 1..=25 {
try_delegate_to_mixnode_on_behalf(
test.deps_mut(),
env.clone(),
mock_info(vesting_contract.as_ref(), &[coin(100_000, TEST_COIN_DENOM)]),
mix_id,
with_proxy.into(),
)
.unwrap();
}
test.execute_all_pending_events();
// make few queries
let res1 =
query_delegator_delegations_paged(test.deps(), "delegator2".into(), None, None)
@@ -472,6 +490,59 @@ mod tests {
.delegations
.into_iter()
.all(|d| d.owner == Addr::unchecked("delegator35")));
let with_proxy_full =
query_delegator_delegations_paged(test.deps(), with_proxy.into(), None, None)
.unwrap();
assert_eq!(with_proxy_full.delegations.len(), 125);
// all delegations have correct owner
assert!(with_proxy_full
.delegations
.iter()
.all(|d| d.owner == Addr::unchecked(with_proxy)));
// and we have 100 delegations without proxy and 25 with
let no_proxy = with_proxy_full
.delegations
.iter()
.filter(|d| d.proxy.is_none())
.count();
assert_eq!(no_proxy, 100);
let proxy = with_proxy_full
.delegations
.iter()
.filter(|d| d.proxy.is_some())
.count();
assert_eq!(proxy, 25);
assert!(with_proxy_full
.delegations
.iter()
.filter(|d| d.proxy.is_some())
.all(|d| d.proxy.as_ref().unwrap() == vesting_contract));
// now make sure that if we do it in paged manner, we'll get exactly the same result
let per_page = Some(15);
let mut delegations = Vec::new();
let mut start_after = None;
loop {
let mut paged_response = query_delegator_delegations_paged(
test.deps(),
with_proxy.into(),
start_after,
per_page,
)
.unwrap();
delegations.append(&mut paged_response.delegations);
if let Some(start_after_res) = paged_response.start_next_after {
start_after = Some(start_after_res)
} else {
break;
}
}
assert_eq!(with_proxy_full.delegations, delegations)
}
}
+154 -13
View File
@@ -1,12 +1,14 @@
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use crate::interval::storage as interval_storage;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnodes::storage as mixnodes_storage;
use crate::support::helpers::{ensure_epoch_in_progress_state, validate_delegation_stake};
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response};
use crate::support::helpers::{
ensure_epoch_in_progress_state, ensure_sent_by_vesting_contract, validate_delegation_stake,
};
use cosmwasm_std::{Addr, Coin, DepsMut, Env, MessageInfo, Response};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::{
new_pending_delegation_event, new_pending_undelegation_event,
@@ -19,6 +21,30 @@ pub(crate) fn try_delegate_to_mixnode(
env: Env,
info: MessageInfo,
mix_id: MixId,
) -> Result<Response, MixnetContractError> {
_try_delegate_to_mixnode(deps, env, mix_id, info.sender, info.funds, None)
}
pub(crate) fn try_delegate_to_mixnode_on_behalf(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
mix_id: MixId,
delegate: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let delegate = deps.api.addr_validate(&delegate)?;
_try_delegate_to_mixnode(deps, env, mix_id, delegate, info.funds, Some(info.sender))
}
pub(crate) fn _try_delegate_to_mixnode(
deps: DepsMut<'_>,
env: Env,
mix_id: MixId,
delegate: Addr,
amount: Vec<Coin>,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// delegation is only allowed if the epoch is currently not in the process of being advanced
ensure_epoch_in_progress_state(deps.storage)?;
@@ -26,7 +52,7 @@ pub(crate) fn try_delegate_to_mixnode(
// check if the delegation contains any funds of the appropriate denomination
let contract_state = mixnet_params_storage::CONTRACT_STATE.load(deps.storage)?;
let delegation = validate_delegation_stake(
info.funds,
amount,
contract_state.params.minimum_mixnode_delegation,
contract_state.rewarding_denom,
)?;
@@ -41,9 +67,14 @@ pub(crate) fn try_delegate_to_mixnode(
}
// push the event onto the queue and wait for it to be picked up at the end of the epoch
let cosmos_event = new_pending_delegation_event(&info.sender, &delegation, mix_id);
let cosmos_event = new_pending_delegation_event(&delegate, &proxy, &delegation, mix_id);
let epoch_event = PendingEpochEventKind::new_delegate(info.sender, mix_id, delegation);
let epoch_event = PendingEpochEventKind::Delegate {
owner: delegate,
mix_id,
amount: delegation,
proxy,
};
interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?;
Ok(Response::new().add_event(cosmos_event))
@@ -54,12 +85,35 @@ pub(crate) fn try_remove_delegation_from_mixnode(
env: Env,
info: MessageInfo,
mix_id: MixId,
) -> Result<Response, MixnetContractError> {
_try_remove_delegation_from_mixnode(deps, env, mix_id, info.sender, None)
}
pub(crate) fn try_remove_delegation_from_mixnode_on_behalf(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
mix_id: MixId,
delegate: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let delegate = deps.api.addr_validate(&delegate)?;
_try_remove_delegation_from_mixnode(deps, env, mix_id, delegate, Some(info.sender))
}
pub(crate) fn _try_remove_delegation_from_mixnode(
deps: DepsMut<'_>,
env: Env,
mix_id: MixId,
delegate: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// undelegation is only allowed if the epoch is currently not in the process of being advanced
ensure_epoch_in_progress_state(deps.storage)?;
// see if the delegation even exists
let storage_key = Delegation::generate_storage_key(mix_id, &info.sender, None);
let storage_key = Delegation::generate_storage_key(mix_id, &delegate, proxy.as_ref());
if storage::delegations()
.may_load(deps.storage, storage_key)?
@@ -67,15 +121,19 @@ pub(crate) fn try_remove_delegation_from_mixnode(
{
return Err(MixnetContractError::NoMixnodeDelegationFound {
mix_id,
address: info.sender.into_string(),
proxy: None,
address: delegate.into_string(),
proxy: proxy.map(Addr::into_string),
});
}
// push the event onto the queue and wait for it to be picked up at the end of the epoch
let cosmos_event = new_pending_undelegation_event(&info.sender, mix_id);
let cosmos_event = new_pending_undelegation_event(&delegate, &proxy, mix_id);
let epoch_event = PendingEpochEventKind::new_undelegate(info.sender, mix_id);
let epoch_event = PendingEpochEventKind::Undelegate {
owner: delegate,
mix_id,
proxy,
};
interval_storage::push_new_epoch_event(deps.storage, &env, epoch_event)?;
Ok(Response::new().add_event(cosmos_event))
@@ -93,7 +151,7 @@ mod tests {
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::TestSetup;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{coin, Addr, Decimal};
use cosmwasm_std::{coin, Decimal};
use mixnet_contract_common::{EpochState, EpochStatus};
#[test]
@@ -310,17 +368,65 @@ mod tests {
let mix_id = test.add_dummy_mixnode("mix-owner", None);
let amount1 = coin(100_000_000, TEST_COIN_DENOM);
let amount2 = coin(50_000_000, TEST_COIN_DENOM);
let sender1 = mock_info(owner, &[amount1.clone()]);
let sender2 = mock_info(test.vesting_contract().as_str(), &[amount2.clone()]);
try_delegate_to_mixnode(test.deps_mut(), env.clone(), sender1, mix_id).unwrap();
try_delegate_to_mixnode_on_behalf(test.deps_mut(), env, sender2, mix_id, owner.into())
.unwrap();
let events = test.pending_epoch_events();
assert_eq!(
events[0].kind,
PendingEpochEventKind::new_delegate(Addr::unchecked(owner), mix_id, amount1,)
PendingEpochEventKind::Delegate {
owner: Addr::unchecked(owner),
mix_id,
amount: amount1,
proxy: None
}
);
assert_eq!(
events[1].kind,
PendingEpochEventKind::Delegate {
owner: Addr::unchecked(owner),
mix_id,
amount: amount2,
proxy: Some(test.vesting_contract())
}
);
}
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "delegator";
let mix_id = test.add_dummy_mixnode("mix-owner", None);
let res = try_delegate_to_mixnode_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
mix_id,
owner.into(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
)
}
}
@@ -467,5 +573,40 @@ mod tests {
);
assert!(res.is_ok());
}
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "delegator";
let mix_id = test.add_dummy_mixnode("mix-owner", None);
test.add_immediate_delegation_with_illegal_proxy(
owner,
10000u32,
mix_id,
illegal_proxy.clone(),
);
let res = try_remove_delegation_from_mixnode_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
mix_id,
owner.into(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
)
}
}
}
@@ -4,7 +4,7 @@
use crate::mixnodes::storage as mixnodes_storage;
use crate::signing::storage as signing_storage;
use crate::support::helpers::decode_ed25519_identity_key;
use cosmwasm_std::Deps;
use cosmwasm_std::{Addr, Deps};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::families::FamilyHead;
use mixnet_contract_common::{construct_family_join_permit, IdentityKeyRef};
@@ -13,6 +13,7 @@ use nym_contracts_common::signing::{MessageSignature, Verifier};
pub(crate) fn verify_family_join_permit(
deps: Deps<'_>,
granter: FamilyHead,
proxy: Option<Addr>,
member: IdentityKeyRef,
signature: MessageSignature,
) -> Result<(), MixnetContractError> {
@@ -31,7 +32,7 @@ pub(crate) fn verify_family_join_permit(
});
};
let nonce = signing_storage::get_signing_nonce(deps.storage, head_mixnode.owner)?;
let msg = construct_family_join_permit(nonce, granter, member.to_owned());
let msg = construct_family_join_permit(nonce, granter, proxy, member.to_owned());
if deps.api.verify_message(msg, signature, &public_key)? {
Ok(())
+285 -15
View File
@@ -1,4 +1,4 @@
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage::{
@@ -7,20 +7,41 @@ use super::storage::{
};
use crate::families::queries::get_family_by_label;
use crate::families::signature_helpers::verify_family_join_permit;
use crate::support::helpers::ensure_bonded;
use cosmwasm_std::{DepsMut, MessageInfo, Response};
use crate::support::helpers::{ensure_bonded, ensure_sent_by_vesting_contract};
use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response};
use mixnet_contract_common::families::{Family, FamilyHead};
use mixnet_contract_common::{error::MixnetContractError, IdentityKey};
use nym_contracts_common::signing::MessageSignature;
/// Creates a new MixNode family with senders node as head
pub(crate) fn try_create_family(
pub fn try_create_family(
deps: DepsMut,
info: MessageInfo,
label: String,
) -> Result<Response, MixnetContractError> {
_try_create_family(deps, &info.sender, label, None)
}
pub fn try_create_family_on_behalf(
deps: DepsMut,
info: MessageInfo,
owner_address: String,
label: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let owner_address = deps.api.addr_validate(&owner_address)?;
_try_create_family(deps, &owner_address, label, Some(info.sender))
}
fn _try_create_family(
deps: DepsMut,
owner: &Addr,
label: String,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let existing_bond =
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
ensure_bonded(&existing_bond)?;
@@ -39,19 +60,43 @@ pub(crate) fn try_create_family(
return Err(MixnetContractError::FamilyWithLabelExists(label));
}
let family = Family::new(family_head, label);
let family = Family::new(family_head, proxy, label);
save_family(&family, deps.storage)?;
Ok(Response::default())
}
pub(crate) fn try_join_family(
pub fn try_join_family(
deps: DepsMut,
info: MessageInfo,
join_permit: MessageSignature,
family_head: FamilyHead,
) -> Result<Response, MixnetContractError> {
_try_join_family(deps, &info.sender, join_permit, family_head, None)
}
pub fn try_join_family_on_behalf(
deps: DepsMut,
info: MessageInfo,
member_address: String,
join_permit: MessageSignature,
family_head: FamilyHead,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let member_address = deps.api.addr_validate(&member_address)?;
let proxy = Some(info.sender);
_try_join_family(deps, &member_address, join_permit, family_head, proxy)
}
fn _try_join_family(
deps: DepsMut,
owner: &Addr,
join_permit: MessageSignature,
family_head: FamilyHead,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let existing_bond =
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
ensure_bonded(&existing_bond)?;
@@ -71,6 +116,7 @@ pub(crate) fn try_join_family(
verify_family_join_permit(
deps.as_ref(),
family_head.clone(),
proxy,
existing_bond.identity(),
join_permit,
)?;
@@ -82,13 +128,33 @@ pub(crate) fn try_join_family(
Ok(Response::default())
}
pub(crate) fn try_leave_family(
pub fn try_leave_family(
deps: DepsMut,
info: MessageInfo,
family_head: FamilyHead,
) -> Result<Response, MixnetContractError> {
_try_leave_family(deps, &info.sender, family_head)
}
pub fn try_leave_family_on_behalf(
deps: DepsMut,
info: MessageInfo,
member_address: String,
family_head: FamilyHead,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let member_address = deps.api.addr_validate(&member_address)?;
_try_leave_family(deps, &member_address, family_head)
}
fn _try_leave_family(
deps: DepsMut,
owner: &Addr,
family_head: FamilyHead,
) -> Result<Response, MixnetContractError> {
let existing_bond =
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
ensure_bonded(&existing_bond)?;
@@ -112,13 +178,32 @@ pub(crate) fn try_leave_family(
Ok(Response::default())
}
pub(crate) fn try_head_kick_member(
pub fn try_head_kick_member(
deps: DepsMut,
info: MessageInfo,
member: IdentityKey,
) -> Result<Response, MixnetContractError> {
let head_bond =
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
_try_head_kick_member(deps, &info.sender, member)
}
pub fn try_head_kick_member_on_behalf(
deps: DepsMut,
info: MessageInfo,
head_address: String,
member: IdentityKey,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let head_address = deps.api.addr_validate(&head_address)?;
_try_head_kick_member(deps, &head_address, member)
}
fn _try_head_kick_member(
deps: DepsMut,
owner: &Addr,
member: IdentityKey,
) -> Result<Response, MixnetContractError> {
let head_bond = crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
// make sure we're still in the mixnet
ensure_bonded(&head_bond)?;
@@ -236,7 +321,7 @@ mod test {
assert_eq!(family.head_identity(), family_head.identity());
let join_permit =
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key);
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key, false);
try_join_family(
test.deps_mut(),
@@ -260,7 +345,7 @@ mod test {
);
let new_join_permit =
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key);
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key, false);
try_join_family(
test.deps_mut(),
@@ -288,4 +373,189 @@ mod test {
!is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap()
);
}
#[cfg(test)]
mod creating_family {
use super::*;
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let head = "alice";
test.add_dummy_mixnode(head, None);
let res = try_create_family_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[]),
head.to_string(),
"label".to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
)
}
}
#[cfg(test)]
mod joining_family {
use super::*;
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let head = "alice";
let label = "family";
let new_member = "vin-diesel";
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
let join_permit = test.generate_family_join_permit(
&head_keys,
&member_keys.public_key().to_base58_string(),
false,
);
let head_identity = head_keys.public_key().to_base58_string();
let family_head = FamilyHead::new(head_identity);
let res = try_join_family_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[]),
new_member.to_string(),
join_permit,
family_head,
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
)
}
}
#[cfg(test)]
mod leaving_family {
use super::*;
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let head = "alice";
let label = "family";
let new_member = "vin-diesel";
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
let join_permit = test.generate_family_join_permit(
&head_keys,
&member_keys.public_key().to_base58_string(),
true,
);
let head_identity = head_keys.public_key().to_base58_string();
let family_head = FamilyHead::new(head_identity);
try_join_family_on_behalf(
test.deps_mut(),
mock_info(vesting_contract.as_ref(), &[]),
new_member.to_string(),
join_permit,
family_head.clone(),
)
.unwrap();
let res = try_leave_family_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[]),
new_member.to_string(),
family_head,
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
)
}
}
#[cfg(test)]
mod kicking_family_member {
use super::*;
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let head = "alice";
let label = "family";
let new_member = "vin-diesel";
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
let join_permit = test.generate_family_join_permit(
&head_keys,
&member_keys.public_key().to_base58_string(),
true,
);
let head_identity = head_keys.public_key().to_base58_string();
let family_head = FamilyHead::new(head_identity);
try_join_family_on_behalf(
test.deps_mut(),
mock_info(vesting_contract.as_ref(), &[]),
new_member.to_string(),
join_permit,
family_head,
)
.unwrap();
let res = try_head_kick_member_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[]),
head.to_string(),
member_keys.public_key().to_base58_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
)
}
}
}
@@ -12,6 +12,7 @@ use nym_contracts_common::signing::Verifier;
pub(crate) fn verify_gateway_bonding_signature(
deps: Deps<'_>,
sender: Addr,
proxy: Option<Addr>,
pledge: Coin,
gateway: Gateway,
signature: MessageSignature,
@@ -21,7 +22,7 @@ pub(crate) fn verify_gateway_bonding_signature(
// reconstruct the payload
let nonce = signing_storage::get_signing_nonce(deps.storage, sender.clone())?;
let msg = construct_gateway_bonding_sign_payload(nonce, sender, pledge, gateway);
let msg = construct_gateway_bonding_sign_payload(nonce, sender, proxy, pledge, gateway);
if deps.api.verify_message(msg, signature, &public_key)? {
Ok(())
+254 -29
View File
@@ -1,4 +1,4 @@
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::helpers::must_get_gateway_bond_by_owner;
@@ -6,8 +6,10 @@ use super::storage;
use crate::gateways::signature_helpers::verify_gateway_bonding_signature;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::signing::storage as signing_storage;
use crate::support::helpers::{ensure_no_existing_bond, validate_pledge};
use cosmwasm_std::{BankMsg, DepsMut, Env, MessageInfo, Response};
use crate::support::helpers::{
ensure_no_existing_bond, ensure_proxy_match, ensure_sent_by_vesting_contract, validate_pledge,
};
use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, DepsMut, Env, MessageInfo, Response};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::{
new_gateway_bonding_event, new_gateway_config_update_event, new_gateway_unbonding_event,
@@ -15,28 +17,72 @@ use mixnet_contract_common::events::{
use mixnet_contract_common::gateway::GatewayConfigUpdate;
use mixnet_contract_common::{Gateway, GatewayBond};
use nym_contracts_common::signing::MessageSignature;
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
// TODO: perhaps also require the user to explicitly provide what it thinks is the current nonce
// so that we could return a better error message if it doesn't match?
pub(crate) fn try_add_gateway(
pub fn try_add_gateway(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
gateway: Gateway,
owner_signature: MessageSignature,
) -> Result<Response, MixnetContractError> {
_try_add_gateway(
deps,
env,
gateway,
info.funds,
info.sender,
owner_signature,
None,
)
}
pub fn try_add_gateway_on_behalf(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
gateway: Gateway,
owner: String,
owner_signature: MessageSignature,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let proxy = info.sender;
let owner = deps.api.addr_validate(&owner)?;
_try_add_gateway(
deps,
env,
gateway,
info.funds,
owner,
owner_signature,
Some(proxy),
)
}
// TODO: perhaps also require the user to explicitly provide what it thinks is the current nonce
// so that we could return a better error message if it doesn't match?
pub(crate) fn _try_add_gateway(
deps: DepsMut<'_>,
env: Env,
gateway: Gateway,
pledge: Vec<Coin>,
owner: Addr,
owner_signature: MessageSignature,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// check if the pledge contains any funds of the appropriate denomination
let minimum_pledge = mixnet_params_storage::minimum_gateway_pledge(deps.storage)?;
let pledge = validate_pledge(info.funds, minimum_pledge)?;
let pledge = validate_pledge(pledge, minimum_pledge)?;
// if the client has an active bonded mixnode or gateway, don't allow bonding
ensure_no_existing_bond(&info.sender, deps.storage)?;
ensure_no_existing_bond(&owner, deps.storage)?;
// check if somebody else has already bonded a gateway with this identity
if let Some(existing_bond) =
storage::gateways().may_load(deps.storage, &gateway.identity_key)?
{
if existing_bond.owner != info.sender {
if existing_bond.owner != owner {
return Err(MixnetContractError::DuplicateGateway {
owner: existing_bond.owner,
});
@@ -46,62 +92,105 @@ pub(crate) fn try_add_gateway(
// check if this sender actually owns the gateway by checking the signature
verify_gateway_bonding_signature(
deps.as_ref(),
info.sender.clone(),
owner.clone(),
proxy.clone(),
pledge.clone(),
gateway.clone(),
owner_signature,
)?;
// update the signing nonce associated with this sender so that the future signature would be made on the new value
signing_storage::increment_signing_nonce(deps.storage, info.sender.clone())?;
signing_storage::increment_signing_nonce(deps.storage, owner.clone())?;
let gateway_identity = gateway.identity_key.clone();
let bond = GatewayBond::new(
pledge.clone(),
info.sender.clone(),
owner.clone(),
env.block.height,
gateway,
proxy.clone(),
);
storage::gateways().save(deps.storage, bond.identity(), &bond)?;
Ok(Response::new().add_event(new_gateway_bonding_event(
&info.sender,
&owner,
&proxy,
&pledge,
&gateway_identity,
)))
}
pub(crate) fn try_remove_gateway(
pub fn try_remove_gateway_on_behalf(
deps: DepsMut<'_>,
info: MessageInfo,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let proxy = info.sender;
let owner = deps.api.addr_validate(&owner)?;
_try_remove_gateway(deps, owner, Some(proxy))
}
pub fn try_remove_gateway(
deps: DepsMut<'_>,
info: MessageInfo,
) -> Result<Response, MixnetContractError> {
_try_remove_gateway(deps, info.sender, None)
}
pub(crate) fn _try_remove_gateway(
deps: DepsMut<'_>,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// try to find the node of the sender
let gateway_bond = match storage::gateways()
.idx
.owner
.item(deps.storage, info.sender.clone())?
.item(deps.storage, owner.clone())?
{
Some(record) => record.1,
None => return Err(MixnetContractError::NoAssociatedGatewayBond { owner: info.sender }),
None => return Err(MixnetContractError::NoAssociatedGatewayBond { owner }),
};
if proxy != gateway_bond.proxy {
return Err(MixnetContractError::ProxyMismatch {
existing: gateway_bond
.proxy
.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
incoming: proxy.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
});
}
// send bonded funds back to the bond owner
let return_tokens = BankMsg::Send {
to_address: info.sender.to_string(),
to_address: proxy.as_ref().unwrap_or(&owner).to_string(),
amount: vec![gateway_bond.pledge_amount()],
};
// remove the bond
storage::gateways().remove(deps.storage, gateway_bond.identity())?;
Ok(Response::new()
.add_message(return_tokens)
.add_event(new_gateway_unbonding_event(
&info.sender,
&gateway_bond.pledge_amount,
gateway_bond.identity(),
)))
let mut response = Response::new().add_message(return_tokens);
if let Some(proxy) = &proxy {
let msg = VestingContractExecuteMsg::TrackUnbondGateway {
owner: owner.as_str().to_string(),
amount: gateway_bond.pledge_amount(),
};
let track_unbond_message = wasm_execute(proxy, &msg, vec![])?;
response = response.add_message(track_unbond_message);
}
Ok(response.add_event(new_gateway_unbonding_event(
&owner,
&proxy,
&gateway_bond.pledge_amount,
gateway_bond.identity(),
)))
}
pub(crate) fn try_update_gateway_config(
@@ -109,9 +198,36 @@ pub(crate) fn try_update_gateway_config(
info: MessageInfo,
new_config: GatewayConfigUpdate,
) -> Result<Response, MixnetContractError> {
let existing_bond = must_get_gateway_bond_by_owner(deps.storage, &info.sender)?;
let cfg_update_event = new_gateway_config_update_event(&info.sender, &new_config);
let owner = info.sender;
_try_update_gateway_config(deps, new_config, owner, None)
}
pub(crate) fn try_update_gateway_config_on_behalf(
deps: DepsMut,
info: MessageInfo,
new_config: GatewayConfigUpdate,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let owner = deps.api.addr_validate(&owner)?;
let proxy = info.sender;
_try_update_gateway_config(deps, new_config, owner, Some(proxy))
}
pub(crate) fn _try_update_gateway_config(
deps: DepsMut,
new_config: GatewayConfigUpdate,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let existing_bond = must_get_gateway_bond_by_owner(deps.storage, &owner)?;
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
let cfg_update_event = new_gateway_config_update_event(&owner, &proxy, &new_config);
// clippy beta 1.70.0-beta.1 false positive
#[allow(clippy::redundant_clone)]
let mut updated_bond = existing_bond.clone();
updated_bond.gateway.host = new_config.host;
updated_bond.gateway.mix_port = new_config.mix_port;
@@ -138,10 +254,10 @@ pub mod tests {
use crate::mixnet_contract_settings::storage::minimum_gateway_pledge;
use crate::support::tests;
use crate::support::tests::fixtures;
use crate::support::tests::fixtures::good_mixnode_pledge;
use crate::support::tests::fixtures::{good_gateway_pledge, good_mixnode_pledge};
use crate::support::tests::test_helpers::TestSetup;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{Addr, Uint128};
use cosmwasm_std::Uint128;
use mixnet_contract_common::ExecuteMsg;
#[test]
@@ -276,12 +392,42 @@ pub mod tests {
.unwrap();
assert_eq!(1, updated_nonce);
try_remove_gateway(test.deps_mut(), info.clone()).unwrap();
_try_remove_gateway(test.deps_mut(), Addr::unchecked(sender), None).unwrap();
let res = try_add_gateway(test.deps_mut(), env, info, gateway, signature);
assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature));
}
#[test]
fn gateway_add_with_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
let (gateway, sig) = test.gateway_with_signature(owner, None);
let res = try_add_gateway_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &good_gateway_pledge()),
gateway,
owner.to_string(),
sig,
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
)
}
#[test]
fn gateway_remove() {
let mut test = TestSetup::new();
@@ -349,6 +495,7 @@ pub mod tests {
.add_message(expected_message)
.add_event(new_gateway_unbonding_event(
&Addr::unchecked("fred"),
&None,
&tests::fixtures::good_gateway_pledge()[0],
&fred_identity,
));
@@ -363,6 +510,33 @@ pub mod tests {
assert_eq!(&Addr::unchecked("bob"), nodes[0].owner());
}
#[test]
fn gateway_remove_with_illegal_proxy() {
let mut test = TestSetup::new();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
test.add_dummy_gateway_with_illegal_proxy(owner, None, illegal_proxy.clone());
let res = try_remove_gateway_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &good_gateway_pledge()),
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
)
}
#[test]
fn update_gateway_config() {
let mut test = TestSetup::new();
@@ -387,6 +561,22 @@ pub mod tests {
);
test.add_dummy_gateway(owner, None);
let vesting_contract = test.vesting_contract();
// attempted to remove on behalf with invalid proxy (current is `None`)
let res = try_update_gateway_config_on_behalf(
test.deps_mut(),
mock_info(vesting_contract.as_ref(), &[]),
update.clone(),
owner.to_string(),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(),
incoming: vesting_contract.into_string()
})
);
// "normal" update succeeds
let res = try_update_gateway_config(test.deps_mut(), info, update.clone());
@@ -401,4 +591,39 @@ pub mod tests {
assert_eq!(bond.gateway.location, update.location);
assert_eq!(bond.gateway.version, update.version);
}
#[test]
fn updating_gateway_config_with_illegal_proxy() {
let mut test = TestSetup::new();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
test.add_dummy_gateway_with_illegal_proxy(owner, None, illegal_proxy.clone());
let update = GatewayConfigUpdate {
host: "1.1.1.1:1234".to_string(),
mix_port: 1234,
clients_port: 1235,
location: "at home".to_string(),
version: "v1.2.3".to_string(),
};
let res = try_update_gateway_config_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[]),
update,
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract
}
)
}
}
+441 -41
View File
@@ -24,7 +24,7 @@ use crate::interval::storage;
use crate::mixnodes::helpers::{cleanup_post_unbond_mixnode_storage, get_mixnode_details_by_id};
use crate::mixnodes::storage as mixnodes_storage;
use crate::rewards::storage as rewards_storage;
use crate::support::helpers::AttachSendTokens;
use crate::support::helpers::{send_to_proxy_or_owner, VestingTracking};
pub(crate) trait ContractExecutableEvent {
// note: the error only means a HARD error like we failed to read from storage.
@@ -40,6 +40,7 @@ pub(crate) fn delegate(
owner: Addr,
mix_id: MixId,
amount: Coin,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// check if the target node still exists (it might have unbonded between this event getting created
// and being executed). Do note that it's absolutely possible for a mixnode to get immediately
@@ -55,9 +56,20 @@ pub(crate) fn delegate(
_ => {
// if mixnode is no longer bonded or in the process of unbonding, return the tokens back to the
// delegator;
// (read the notes regarding possible epoch progressiong halting behaviour in `maybe_add_track_undelegation_message`)
let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![amount.clone()]);
let response = Response::new()
.send_tokens(&owner, amount.clone())
.add_event(new_delegation_on_unbonded_node_event(&owner, mix_id));
.add_message(return_tokens)
.add_event(new_delegation_on_unbonded_node_event(
&owner, &proxy, mix_id,
))
.maybe_add_track_vesting_undelegation_message(
deps.storage,
proxy,
owner.to_string(),
mix_id,
amount,
)?;
return Ok(response);
}
@@ -72,7 +84,7 @@ pub(crate) fn delegate(
// if there's an existing delegation, then withdraw the full reward and create a new delegation
// with the sum of both
let storage_key = Delegation::generate_storage_key(mix_id, &owner, None);
let storage_key = Delegation::generate_storage_key(mix_id, &owner, proxy.as_ref());
let old_delegation = if let Some(existing_delegation) =
delegations_storage::delegations().may_load(deps.storage, storage_key.clone())?
{
@@ -94,6 +106,7 @@ pub(crate) fn delegate(
let cosmos_event = new_delegation_event(
created_at,
&owner,
&proxy,
&new_delegation_amount,
mix_id,
mix_rewarding.total_unit_reward,
@@ -105,6 +118,7 @@ pub(crate) fn delegate(
mix_rewarding.total_unit_reward,
stored_delegation_amount,
env.block.height,
proxy,
);
// save on reading since `.save()` would have attempted to read old data that we already have on hand
@@ -124,10 +138,11 @@ pub(crate) fn undelegate(
created_at: BlockHeight,
owner: Addr,
mix_id: MixId,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// see if the delegation still exists (in case of impatient user who decided to send multiple
// undelegation requests in an epoch)
let storage_key = Delegation::generate_storage_key(mix_id, &owner, None);
let storage_key = Delegation::generate_storage_key(mix_id, &owner, proxy.as_ref());
let delegation = match delegations_storage::delegations().may_load(deps.storage, storage_key)? {
None => return Ok(Response::default()),
Some(delegation) => delegation,
@@ -140,9 +155,18 @@ pub(crate) fn undelegate(
let tokens_to_return =
delegations::helpers::undelegate(deps.storage, delegation, mix_rewarding)?;
// (read the notes regarding possible epoch progressiong halting behaviour in `maybe_add_track_undelegation_message`)
let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![tokens_to_return.clone()]);
let response = Response::new()
.send_tokens(&owner, tokens_to_return.clone())
.add_event(new_undelegation_event(created_at, &owner, mix_id));
.add_message(return_tokens)
.add_event(new_undelegation_event(created_at, &owner, &proxy, mix_id))
.maybe_add_track_vesting_undelegation_message(
deps.storage,
proxy,
owner.to_string(),
mix_id,
tokens_to_return,
)?;
Ok(response)
}
@@ -173,15 +197,25 @@ pub(crate) fn unbond_mixnode(
.rewarding_details
.operator_pledge_with_reward(rewarding_denom);
let proxy = &node_details.bond_information.proxy;
let owner = &node_details.bond_information.owner;
// send bonded funds (alongside all earned rewards) to the bond owner
let return_tokens = send_to_proxy_or_owner(proxy, owner, vec![tokens.clone()]);
// remove the bond and if there are no delegations left, also the rewarding information
// decrement the associated layer count
cleanup_post_unbond_mixnode_storage(deps.storage, env, &node_details)?;
let response = Response::new()
.send_tokens(owner, tokens.clone())
.add_event(new_mixnode_unbonding_event(created_at, mix_id));
.add_message(return_tokens)
.add_event(new_mixnode_unbonding_event(created_at, mix_id))
.maybe_add_track_vesting_unbond_mixnode_message(
deps.storage,
proxy.clone(),
owner.clone().into_string(),
tokens,
)?;
Ok(response)
}
@@ -277,8 +311,12 @@ pub(crate) fn decrease_pledge(
updated_bond.original_pledge.amount -= decrease_by.amount;
updated_rewarding.decrease_operator_uint128(decrease_by.amount)?;
let proxy = &mix_details.bond_information.proxy;
let owner = &mix_details.bond_information.owner;
// send the removed tokens back to the operator
let return_tokens = send_to_proxy_or_owner(proxy, owner, vec![decrease_by.clone()]);
// update all: bond information, rewarding details and pending pledge changes
mixnodes_storage::mixnode_bonds().replace(
deps.storage,
@@ -290,8 +328,14 @@ pub(crate) fn decrease_pledge(
mixnodes_storage::PENDING_MIXNODE_CHANGES.save(deps.storage, mix_id, &pending_changes)?;
let response = Response::new()
.send_tokens(owner, decrease_by.clone())
.add_event(new_pledge_decrease_event(created_at, mix_id, &decrease_by));
.add_message(return_tokens)
.add_event(new_pledge_decrease_event(created_at, mix_id, &decrease_by))
.maybe_add_track_vesting_decrease_mixnode_pledge(
deps.storage,
proxy.clone(),
owner.clone().to_string(),
decrease_by,
)?;
Ok(response)
}
@@ -305,11 +349,13 @@ impl ContractExecutableEvent for PendingEpochEventData {
owner,
mix_id,
amount,
..
} => delegate(deps, env, self.created_at, owner, mix_id, amount),
PendingEpochEventKind::Undelegate { owner, mix_id, .. } => {
undelegate(deps, self.created_at, owner, mix_id)
}
proxy,
} => delegate(deps, env, self.created_at, owner, mix_id, amount, proxy),
PendingEpochEventKind::Undelegate {
owner,
mix_id,
proxy,
} => undelegate(deps, self.created_at, owner, mix_id, proxy),
PendingEpochEventKind::PledgeMore { mix_id, amount } => {
increase_pledge(deps, self.created_at, mix_id, amount)
}
@@ -426,25 +472,33 @@ impl ContractExecutableEvent for PendingIntervalEventData {
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
use cosmwasm_std::Decimal;
use mixnet_contract_common::Percent;
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
use crate::support::tests::test_helpers;
use crate::support::tests::test_helpers::{assert_decimals, TestSetup};
use cosmwasm_std::Decimal;
use mixnet_contract_common::Percent;
use std::time::Duration;
use super::*;
// note that authorization and basic validation has already been performed for all of those
// before being pushed onto the event queues
#[cfg(test)]
mod delegating {
use super::*;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{coin, to_binary, CosmosMsg, WasmMsg};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use crate::mixnodes::transactions::try_remove_mixnode;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::get_bank_send_msg;
use cosmwasm_std::coin;
use cosmwasm_std::testing::mock_info;
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use super::*;
#[test]
fn returns_the_tokens_if_mixnode_has_unbonded() {
@@ -469,6 +523,7 @@ mod tests {
Addr::unchecked(owner1),
mix_id,
delegation_coin.clone(),
None,
)
.unwrap();
@@ -494,6 +549,7 @@ mod tests {
Addr::unchecked(owner2),
mix_id,
delegation_coin.clone(),
None,
)
.unwrap();
let storage_key =
@@ -532,6 +588,7 @@ mod tests {
Addr::unchecked(owner1),
mix_id,
delegation_coin.clone(),
None,
)
.unwrap();
@@ -557,6 +614,7 @@ mod tests {
Addr::unchecked(owner2),
mix_id,
delegation_coin.clone(),
None,
)
.unwrap();
let storage_key =
@@ -592,6 +650,7 @@ mod tests {
Addr::unchecked(owner),
mix_id,
delegation_coin_new,
None,
)
.unwrap();
@@ -666,6 +725,7 @@ mod tests {
Addr::unchecked(owner),
mix_id,
delegation_coin_new,
None,
)
.unwrap();
@@ -737,6 +797,7 @@ mod tests {
Addr::unchecked(owner),
mix_id,
delegation_coin.clone(),
None,
)
.unwrap();
assert!(get_bank_send_msg(&res).is_none());
@@ -755,14 +816,118 @@ mod tests {
Decimal::from_atomics(delegation, 0).unwrap()
)
}
#[test]
fn attaches_vesting_contract_track_message_if_tokens_are_returned() {
let mut test = TestSetup::new();
let mix_id = test.add_dummy_mixnode("mix-owner", None);
let delegation = 120_000_000u128;
let delegation_coin = coin(delegation, TEST_COIN_DENOM);
let owner = "delegator";
let env = test.env();
unbond_mixnode(test.deps_mut(), &env, 123, mix_id).unwrap();
let vesting_contract = test.vesting_contract();
// for a fresh delegation, nothing was added to the storage either
let res_vesting = delegate(
test.deps_mut(),
&env,
123,
Addr::unchecked(owner),
mix_id,
delegation_coin.clone(),
Some(vesting_contract.clone()),
)
.unwrap();
let storage_key = Delegation::generate_storage_key(
mix_id,
&Addr::unchecked(owner),
Some(vesting_contract.clone()).as_ref(),
);
assert!(delegations_storage::delegations()
.may_load(test.deps().storage, storage_key)
.unwrap()
.is_none());
// and all tokens are returned back to the proxy
let (receiver, sent_amount) = get_bank_send_msg(&res_vesting).unwrap();
assert_eq!(receiver, vesting_contract.as_str());
assert_eq!(sent_amount[0], delegation_coin);
// and we get appropriate track message
let mut found_track = true;
for msg in &res_vesting.messages {
if let CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr,
msg,
funds,
}) = &msg.msg
{
found_track = true;
assert_eq!(contract_addr, vesting_contract.as_str());
let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUndelegation {
owner: owner.to_string(),
mix_id,
amount: delegation_coin.clone(),
})
.unwrap();
assert_eq!(&expected_msg, msg);
assert!(funds.is_empty())
}
}
assert!(found_track);
}
#[test]
fn returns_error_for_illegal_proxy() {
let mut test = TestSetup::new();
let mix_id = test.add_dummy_mixnode("mix-owner", None);
let delegation = 120_000_000u128;
let delegation_coin = coin(delegation, TEST_COIN_DENOM);
let owner = "delegator";
let dummy_proxy = Addr::unchecked("not-vesting-contract");
let env = test.env();
unbond_mixnode(test.deps_mut(), &env, 123, mix_id).unwrap();
let vesting_contract = test.vesting_contract();
// try to add illegal delegation (with invalid proxy)
let res_other_proxy = delegate(
test.deps_mut(),
&env,
123,
Addr::unchecked(owner),
mix_id,
delegation_coin,
Some(dummy_proxy.clone()),
)
.unwrap_err();
assert_eq!(
res_other_proxy,
MixnetContractError::ProxyIsNotVestingContract {
received: dummy_proxy,
vesting_contract,
}
);
}
}
#[cfg(test)]
mod undelegating {
use super::*;
use crate::support::tests::test_helpers::get_bank_send_msg;
use cosmwasm_std::{coin, to_binary, CosmosMsg, WasmMsg};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::get_bank_send_msg;
use super::*;
#[test]
fn doesnt_return_any_tokens_if_it_doesnt_exist() {
let mut test = TestSetup::new();
@@ -770,7 +935,7 @@ mod tests {
let owner = Addr::unchecked("delegator");
let res = undelegate(test.deps_mut(), 123, owner, mix_id).unwrap();
let res = undelegate(test.deps_mut(), 123, owner, mix_id, None).unwrap();
assert!(get_bank_send_msg(&res).is_none());
}
@@ -785,7 +950,7 @@ mod tests {
// this should never happen in actual code, but if we manually messed something up,
// lets make sure this throws an error
rewards_storage::MIXNODE_REWARDING.remove(test.deps_mut().storage, mix_id);
let res = undelegate(test.deps_mut(), 123, owner, mix_id);
let res = undelegate(test.deps_mut(), 123, owner, mix_id, None);
assert!(matches!(
res,
Err(MixnetContractError::InconsistentState { .. })
@@ -831,7 +996,8 @@ mod tests {
let expected_return = delegation + truncated_reward.u128();
let res = undelegate(test.deps_mut(), 123, Addr::unchecked(owner), mix_id).unwrap();
let res =
undelegate(test.deps_mut(), 123, Addr::unchecked(owner), mix_id, None).unwrap();
let (receiver, sent_amount) = get_bank_send_msg(&res).unwrap();
assert_eq!(receiver, owner);
assert_eq!(sent_amount[0].amount.u128(), expected_return);
@@ -849,19 +1015,117 @@ mod tests {
assert!(rewarding.delegates.is_zero());
assert_eq!(rewarding.unique_delegations, 0);
}
#[test]
fn attaches_vesting_contract_track_message_if_tokens_are_returned() {
let mut test = TestSetup::new();
let mix_id = test.add_dummy_mixnode("mix-owner", None);
let delegation = 120_000_000u128;
let delegation_coin = coin(delegation, TEST_COIN_DENOM);
let owner = "delegator";
let vesting_contract = test.vesting_contract();
test.add_immediate_delegation_with_legal_proxy(owner, delegation, mix_id);
let res_vesting = undelegate(
test.deps_mut(),
123,
Addr::unchecked(owner),
mix_id,
Some(vesting_contract.clone()),
)
.unwrap();
let storage_key = Delegation::generate_storage_key(
mix_id,
&Addr::unchecked(owner),
Some(vesting_contract.clone()).as_ref(),
);
assert!(delegations_storage::delegations()
.may_load(test.deps().storage, storage_key)
.unwrap()
.is_none());
// and all tokens are returned back to the proxy
let (receiver, sent_amount) = get_bank_send_msg(&res_vesting).unwrap();
assert_eq!(receiver, vesting_contract.as_str());
assert_eq!(sent_amount[0], delegation_coin);
// and we get appropriate track message
let mut found_track = true;
for msg in &res_vesting.messages {
if let CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr,
msg,
funds,
}) = &msg.msg
{
found_track = true;
assert_eq!(contract_addr, vesting_contract.as_str());
let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUndelegation {
owner: owner.to_string(),
mix_id,
amount: delegation_coin.clone(),
})
.unwrap();
assert_eq!(&expected_msg, msg);
assert!(funds.is_empty())
}
}
assert!(found_track);
}
#[test]
fn returns_error_for_illegal_proxy() {
let mut test = TestSetup::new();
let mix_id = test.add_dummy_mixnode("mix-owner", None);
let delegation = 120_000_000u128;
let owner = "delegator1";
let vesting_contract = test.vesting_contract();
let dummy_proxy = Addr::unchecked("not-vesting-contract");
test.add_immediate_delegation_with_illegal_proxy(
owner,
delegation,
mix_id,
dummy_proxy.clone(),
);
let res_other_proxy = undelegate(
test.deps_mut(),
123,
Addr::unchecked(owner),
mix_id,
Some(dummy_proxy.clone()),
)
.unwrap_err();
assert_eq!(
res_other_proxy,
MixnetContractError::ProxyIsNotVestingContract {
received: dummy_proxy,
vesting_contract,
}
);
}
}
#[cfg(test)]
mod mixnode_unbonding {
use super::*;
use crate::mixnodes::storage as mixnodes_storage;
use crate::mixnodes::transactions::{try_decrease_pledge, try_increase_pledge};
use crate::support::tests::test_helpers::get_bank_send_msg;
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::Uint128;
use cosmwasm_std::{coin, to_binary, CosmosMsg, Uint128, WasmMsg};
use mixnet_contract_common::mixnode::{PendingMixNodeChanges, UnbondedMixnode};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use crate::mixnodes::storage as mixnodes_storage;
use crate::mixnodes::transactions::{_try_decrease_pledge, _try_increase_pledge};
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::get_bank_send_msg;
use super::*;
#[test]
fn returns_hard_error_if_mixnode_doesnt_exist() {
// this should have never happened so hard error MUST be thrown here
@@ -886,10 +1150,12 @@ mod tests {
let pledge = Uint128::new(250_000_000);
let mix_id = test.add_dummy_mixnode(owner, Some(pledge));
try_increase_pledge(
_try_increase_pledge(
test.deps_mut(),
env.clone(),
mock_info(owner, &change.clone()),
change.clone(),
Addr::unchecked(owner),
None,
)
.unwrap();
@@ -904,11 +1170,12 @@ mod tests {
let pledge = Uint128::new(250_000_000);
let mix_id = test.add_dummy_mixnode(owner, Some(pledge));
try_decrease_pledge(
_try_decrease_pledge(
test.deps_mut(),
env.clone(),
mock_info(owner, &[]),
change[0].clone(),
Addr::unchecked(owner),
None,
)
.unwrap();
@@ -996,6 +1263,79 @@ mod tests {
0
)
}
#[test]
fn attaches_vesting_contract_track_message_if_tokens_are_returned() {
let mut test = TestSetup::new();
let vesting_contract = test.vesting_contract();
let pledge = Uint128::new(250_000_000);
let pledge_coin = coin(250_000_000, TEST_COIN_DENOM);
let owner = "mix-owner1";
let mix_id_vesting = test.add_dummy_mixnode_with_legal_proxy(owner, Some(pledge));
let env = test.env();
let res = unbond_mixnode(test.deps_mut(), &env, 123, mix_id_vesting).unwrap();
assert!(mixnodes_storage::mixnode_bonds()
.may_load(test.deps().storage, mix_id_vesting)
.unwrap()
.is_none());
// and all tokens are returned back to the proxy
let (receiver, sent_amount) = get_bank_send_msg(&res).unwrap();
assert_eq!(receiver, vesting_contract.as_str());
assert_eq!(sent_amount[0], pledge_coin);
// and we get appropriate track message
let mut found_track = true;
for msg in &res.messages {
if let CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr,
msg,
funds,
}) = &msg.msg
{
found_track = true;
assert_eq!(contract_addr, vesting_contract.as_str());
let expected_msg = to_binary(&VestingContractExecuteMsg::TrackUnbondMixnode {
owner: owner.to_string(),
amount: pledge_coin.clone(),
})
.unwrap();
assert_eq!(&expected_msg, msg);
assert!(funds.is_empty())
}
}
assert!(found_track);
}
#[test]
fn returns_error_for_illegal_proxy() {
let mut test = TestSetup::new();
let dummy_proxy = Addr::unchecked("not-vesting-contract");
let env = test.env();
let vesting_contract = test.vesting_contract();
let owner = "mix-owner";
let pledge = Uint128::new(250_000_000);
let mix_id_illegal_proxy =
test.add_dummy_mixnode_with_illegal_proxy(owner, Some(pledge), dummy_proxy.clone());
// this is the halting issue that should have never occurred
let res_other_proxy =
unbond_mixnode(test.deps_mut(), &env, 123, mix_id_illegal_proxy).unwrap_err();
assert_eq!(
res_other_proxy,
MixnetContractError::ProxyIsNotVestingContract {
received: dummy_proxy,
vesting_contract,
}
);
}
}
#[cfg(test)]
@@ -1275,10 +1615,12 @@ mod tests {
#[cfg(test)]
mod decreasing_pledge {
use super::*;
use cosmwasm_std::{BankMsg, CosmosMsg, Uint128};
use cosmwasm_std::{to_binary, BankMsg, CosmosMsg, Uint128, WasmMsg};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use super::*;
#[test]
fn returns_hard_error_if_mixnode_doesnt_exist() {
// this should have never happened so hard error MUST be thrown here
@@ -1357,6 +1699,64 @@ mod tests {
)
}
#[test]
fn returns_tokens_back_to_the_proxy_if_bonded_with_vesting() {
let mut test = TestSetup::new();
let owner = "mix-owner";
let mix_id = test.add_dummy_mixnode_with_legal_proxy(owner, None);
test.set_pending_pledge_change(mix_id, None);
let vesting_contract = test.vesting_contract();
let amount = test.coin(12345);
let res = decrease_pledge(test.deps_mut(), 123, mix_id, amount.clone()).unwrap();
assert_eq!(res.messages.len(), 2);
assert_eq!(
res.messages[0].msg,
CosmosMsg::Bank(BankMsg::Send {
to_address: vesting_contract.to_string(),
amount: vec![amount],
})
)
}
#[test]
fn attaches_vesting_track_message() {
let mut test = TestSetup::new();
let mix_id_no_proxy = test.add_dummy_mixnode("mix-owner1", None);
test.set_pending_pledge_change(mix_id_no_proxy, None);
let mix_id_proxy = test.add_dummy_mixnode_with_legal_proxy("mix-owner2", None);
test.set_pending_pledge_change(mix_id_proxy, None);
let vesting_contract = test.vesting_contract();
let amount = test.coin(12345);
let res_no_proxy =
decrease_pledge(test.deps_mut(), 123, mix_id_no_proxy, amount.clone()).unwrap();
// nothing was attached (apart from bank message tested in `returns_tokens_back_to_the_owner`)
// because it wasn't done with proxy!
assert_eq!(res_no_proxy.messages.len(), 1);
let res_proxy =
decrease_pledge(test.deps_mut(), 123, mix_id_proxy, amount.clone()).unwrap();
assert_eq!(res_proxy.messages.len(), 2);
assert_eq!(
res_proxy.messages[1].msg,
CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: vesting_contract.to_string(),
msg: to_binary(&VestingContractExecuteMsg::TrackDecreasePledge {
owner: "mix-owner2".to_string(),
amount,
})
.unwrap(),
funds: vec![],
})
);
}
#[test]
fn without_any_events_in_between_is_equivalent_to_pledging_the_same_amount_immediately() {
let mut test = TestSetup::new();
+10 -4
View File
@@ -159,8 +159,11 @@ mod tests {
}
fn push_dummy_epoch_action(test: &mut TestSetup) {
let dummy_action =
PendingEpochEventKind::new_undelegate(Addr::unchecked("foomp"), test.rng.next_u32());
let dummy_action = PendingEpochEventKind::Undelegate {
owner: Addr::unchecked("foomp"),
mix_id: test.rng.next_u32(),
proxy: None,
};
let env = test.env();
storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
}
@@ -568,8 +571,11 @@ mod tests {
);
// it exists
let dummy_action =
PendingEpochEventKind::new_undelegate(Addr::unchecked("foomp"), test.rng.next_u32());
let dummy_action = PendingEpochEventKind::Undelegate {
owner: Addr::unchecked("foomp"),
mix_id: test.rng.next_u32(),
proxy: None,
};
let env = test.env();
storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action.clone()).unwrap();
let expected = PendingEpochEventResponse {
+10 -8
View File
@@ -222,10 +222,11 @@ mod tests {
let env = test.env();
for _ in 0..500 {
let dummy_action = PendingEpochEventKind::new_undelegate(
Addr::unchecked("foomp"),
test.rng.next_u32(),
);
let dummy_action = PendingEpochEventKind::Undelegate {
owner: Addr::unchecked("foomp"),
mix_id: test.rng.next_u32(),
proxy: None,
};
let id = push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
let expected = EPOCH_EVENT_ID_COUNTER.load(test.deps().storage).unwrap();
assert_eq!(expected, id);
@@ -234,10 +235,11 @@ mod tests {
test.execute_all_pending_events();
for _ in 0..10 {
let dummy_action = PendingEpochEventKind::new_undelegate(
Addr::unchecked("foomp"),
test.rng.next_u32(),
);
let dummy_action = PendingEpochEventKind::Undelegate {
owner: Addr::unchecked("foomp"),
mix_id: test.rng.next_u32(),
proxy: None,
};
let id = push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
let expected = EPOCH_EVENT_ID_COUNTER.load(test.deps().storage).unwrap();
assert_eq!(expected, id);
+57 -17
View File
@@ -373,14 +373,18 @@ mod tests {
use crate::support::tests::test_helpers::TestSetup;
use cosmwasm_std::Addr;
use mixnet_contract_common::pending_events::PendingEpochEventKind;
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
fn push_n_dummy_epoch_actions(test: &mut TestSetup, n: usize) {
// if you attempt to undelegate non-existent delegation,
// it will return an empty response, but will not fail
let env = test.env();
for i in 0..n {
let dummy_action =
PendingEpochEventKind::new_undelegate(Addr::unchecked("foomp"), i as MixId);
let dummy_action = PendingEpochEventKind::Undelegate {
owner: Addr::unchecked("foomp"),
mix_id: i as MixId,
proxy: None,
};
storage::push_new_epoch_event(test.deps_mut().storage, &env, dummy_action).unwrap();
}
}
@@ -402,7 +406,7 @@ mod tests {
mod performing_pending_epoch_actions {
use super::*;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use cosmwasm_std::{coin, coins, BankMsg, Empty, SubMsg};
use cosmwasm_std::{coin, coins, wasm_execute, BankMsg, Empty, SubMsg};
use mixnet_contract_common::events::{
new_active_set_update_event, new_delegation_on_unbonded_node_event,
new_undelegation_event,
@@ -491,6 +495,7 @@ mod tests {
#[test]
fn catches_all_events_and_messages_from_executed_actions() {
let mut test = TestSetup::new();
let vesting_contract = test.vesting_contract();
let env = test.env();
let legit_mix = test.add_dummy_mixnode("mix-owner", None);
@@ -504,15 +509,17 @@ mod tests {
// delegate to node that doesn't exist,
// we expect to receive BankMsg with tokens being returned,
// and event regarding delegation
let non_existent_delegation = PendingEpochEventKind::new_delegate(
Addr::unchecked("foomp"),
123,
coin(123, TEST_COIN_DENOM),
);
let non_existent_delegation = PendingEpochEventKind::Delegate {
owner: Addr::unchecked("foomp"),
mix_id: 123,
amount: coin(123, TEST_COIN_DENOM),
proxy: None,
};
storage::push_new_epoch_event(test.deps_mut().storage, &env, non_existent_delegation)
.unwrap();
expected_events.push(new_delegation_on_unbonded_node_event(
&Addr::unchecked("foomp"),
&None,
123,
));
expected_messages.push(SubMsg::new(BankMsg::Send {
@@ -520,6 +527,33 @@ mod tests {
amount: coins(123, TEST_COIN_DENOM),
}));
// delegation to node that doesn't exist with vesting contract
// we expect the same as above PLUS TrackUndelegation message
let non_existent_delegation = PendingEpochEventKind::Delegate {
owner: Addr::unchecked("foomp2"),
mix_id: 123,
amount: coin(123, TEST_COIN_DENOM),
proxy: Some(vesting_contract.clone()),
};
storage::push_new_epoch_event(test.deps_mut().storage, &env, non_existent_delegation)
.unwrap();
expected_events.push(new_delegation_on_unbonded_node_event(
&Addr::unchecked("foomp2"),
&Some(vesting_contract.clone()),
123,
));
expected_messages.push(SubMsg::new(BankMsg::Send {
to_address: vesting_contract.clone().into_string(),
amount: coins(123, TEST_COIN_DENOM),
}));
let msg = VestingContractExecuteMsg::TrackUndelegation {
owner: "foomp2".to_string(),
mix_id: 123,
amount: coin(123, TEST_COIN_DENOM),
};
let track_undelegate_message = wasm_execute(vesting_contract, &msg, vec![]).unwrap();
expected_messages.push(SubMsg::new(track_undelegate_message));
// updating active set should only emit events and no cosmos messages
let action_with_event = PendingEpochEventKind::UpdateActiveSetSize { new_size: 50 };
storage::push_new_epoch_event(test.deps_mut().storage, &env, action_with_event)
@@ -527,12 +561,16 @@ mod tests {
expected_events.push(new_active_set_update_event(env.block.height, 50));
// undelegation just returns tokens and emits event
let legit_undelegate =
PendingEpochEventKind::new_undelegate(delegator.clone(), legit_mix);
let legit_undelegate = PendingEpochEventKind::Undelegate {
owner: delegator.clone(),
mix_id: legit_mix,
proxy: None,
};
storage::push_new_epoch_event(test.deps_mut().storage, &env, legit_undelegate).unwrap();
expected_events.push(new_undelegation_event(
env.block.height,
&delegator,
&None,
legit_mix,
));
expected_messages.push(SubMsg::new(BankMsg::Send {
@@ -545,9 +583,9 @@ mod tests {
let mut expected = Response::new().add_events(expected_events);
expected.messages = expected_messages;
assert_eq!(res, expected);
assert_eq!(executed, 3);
assert_eq!(executed, 4);
assert_eq!(
3,
4,
storage::LAST_PROCESSED_EPOCH_EVENT
.load(test.deps().storage)
.unwrap()
@@ -1292,15 +1330,17 @@ mod tests {
let mut expected_messages: Vec<SubMsg<Empty>> = Vec::new();
// epoch event
let non_existent_delegation = PendingEpochEventKind::new_delegate(
Addr::unchecked("foomp"),
123,
coin(123, TEST_COIN_DENOM),
);
let non_existent_delegation = PendingEpochEventKind::Delegate {
owner: Addr::unchecked("foomp"),
mix_id: 123,
amount: coin(123, TEST_COIN_DENOM),
proxy: None,
};
storage::push_new_epoch_event(test.deps_mut().storage, &env, non_existent_delegation)
.unwrap();
expected_events.push(new_delegation_on_unbonded_node_event(
&Addr::unchecked("foomp"),
&None,
123,
));
expected_messages.push(SubMsg::new(BankMsg::Send {
-1
View File
@@ -19,4 +19,3 @@ mod support;
#[cfg(feature = "contract-testing")]
mod testing;
mod vesting_migration;
+14 -1
View File
@@ -97,6 +97,7 @@ pub(crate) fn save_new_mixnode(
mixnode: MixNode,
cost_params: MixNodeCostParams,
owner: Addr,
proxy: Option<Addr>,
pledge: Coin,
) -> Result<(MixId, Layer), MixnetContractError> {
let layer = assign_layer(storage)?;
@@ -104,7 +105,15 @@ pub(crate) fn save_new_mixnode(
let current_epoch = interval_storage::current_interval(storage)?.current_epoch_absolute_id();
let mixnode_rewarding = MixNodeRewarding::initialise_new(cost_params, &pledge, current_epoch)?;
let mixnode_bond = MixNodeBond::new(mix_id, owner, pledge, layer, mixnode, env.block.height);
let mixnode_bond = MixNodeBond::new(
mix_id,
owner,
pledge,
layer,
mixnode,
proxy,
env.block.height,
);
// save mixnode bond data
// note that this implicitly checks for uniqueness on identity key, sphinx key and owner
@@ -402,6 +411,7 @@ pub(crate) mod tests {
mixnode,
cost_params.clone(),
owner.clone(),
None,
pledge.clone(),
)
.unwrap();
@@ -434,6 +444,7 @@ pub(crate) mod tests {
mixnode,
cost_params.clone(),
Addr::unchecked("different-owner"),
None,
pledge.clone(),
);
assert!(res.is_err());
@@ -446,6 +457,7 @@ pub(crate) mod tests {
mixnode,
cost_params.clone(),
owner,
None,
pledge.clone(),
);
assert!(res.is_err());
@@ -459,6 +471,7 @@ pub(crate) mod tests {
mixnode,
cost_params,
Addr::unchecked("different-owner"),
None,
pledge,
);
assert!(res.is_err());
@@ -12,6 +12,7 @@ use nym_contracts_common::signing::Verifier;
pub(crate) fn verify_mixnode_bonding_signature(
deps: Deps<'_>,
sender: Addr,
proxy: Option<Addr>,
pledge: Coin,
mixnode: MixNode,
cost_params: MixNodeCostParams,
@@ -22,7 +23,8 @@ pub(crate) fn verify_mixnode_bonding_signature(
// reconstruct the payload
let nonce = signing_storage::get_signing_nonce(deps.storage, sender.clone())?;
let msg = construct_mixnode_bonding_sign_payload(nonce, sender, pledge, mixnode, cost_params);
let msg =
construct_mixnode_bonding_sign_payload(nonce, sender, proxy, pledge, mixnode, cost_params);
if deps.api.verify_message(msg, signature, &public_key)? {
Ok(())
+578 -25
View File
@@ -1,7 +1,8 @@
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use cosmwasm_std::{coin, Coin, DepsMut, Env, MessageInfo, Response, Storage};
use cosmwasm_std::{coin, Addr, Coin, DepsMut, Env, MessageInfo, Response, Storage};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::{
new_mixnode_bonding_event, new_mixnode_config_update_event,
@@ -24,7 +25,8 @@ use crate::mixnodes::signature_helpers::verify_mixnode_bonding_signature;
use crate::signing::storage as signing_storage;
use crate::support::helpers::{
ensure_bonded, ensure_epoch_in_progress_state, ensure_is_authorized, ensure_no_existing_bond,
ensure_no_pending_pledge_changes, validate_pledge,
ensure_no_pending_pledge_changes, ensure_proxy_match, ensure_sent_by_vesting_contract,
validate_pledge,
};
use super::storage;
@@ -59,24 +61,74 @@ pub fn assign_mixnode_layer(
Ok(Response::default())
}
// TODO: perhaps also require the user to explicitly provide what it thinks is the current nonce
// so that we could return a better error message if it doesn't match?
pub(crate) fn try_add_mixnode(
pub fn try_add_mixnode(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
mixnode: MixNode,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner_signature: MessageSignature,
) -> Result<Response, MixnetContractError> {
_try_add_mixnode(
deps,
env,
mix_node,
cost_params,
info.funds,
info.sender,
owner_signature,
None,
)
}
pub fn try_add_mixnode_on_behalf(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
mix_node: MixNode,
cost_params: MixNodeCostParams,
owner: String,
owner_signature: MessageSignature,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let proxy = info.sender;
let owner = deps.api.addr_validate(&owner)?;
_try_add_mixnode(
deps,
env,
mix_node,
cost_params,
info.funds,
owner,
owner_signature,
Some(proxy),
)
}
// I'm not entirely sure how to deal with this warning at the current moment
//
// TODO: perhaps also require the user to explicitly provide what it thinks is the current nonce
// so that we could return a better error message if it doesn't match?
#[allow(clippy::too_many_arguments)]
fn _try_add_mixnode(
deps: DepsMut<'_>,
env: Env,
mixnode: MixNode,
cost_params: MixNodeCostParams,
pledge: Vec<Coin>,
owner: Addr,
owner_signature: MessageSignature,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// check if the pledge contains any funds of the appropriate denomination
let minimum_pledge = mixnet_params_storage::minimum_mixnode_pledge(deps.storage)?;
let pledge = validate_pledge(info.funds, minimum_pledge)?;
let pledge = validate_pledge(pledge, minimum_pledge)?;
// if the client has an active bonded mixnode or gateway, don't allow bonding
// note that this has to be done explicitly as `UniqueIndex` constraint would not protect us
// against attempting to use different node types (i.e. gateways and mixnodes)
ensure_no_existing_bond(&info.sender, deps.storage)?;
ensure_no_existing_bond(&owner, deps.storage)?;
// there's no need to explicitly check whether there already exists mixnode with the same
// identity or sphinx keys as this is going to be done implicitly when attempting to save
@@ -85,7 +137,8 @@ pub(crate) fn try_add_mixnode(
// check if this sender actually owns the mixnode by checking the signature
verify_mixnode_bonding_signature(
deps.as_ref(),
info.sender.clone(),
owner.clone(),
proxy.clone(),
pledge.clone(),
mixnode.clone(),
cost_params.clone(),
@@ -93,7 +146,7 @@ pub(crate) fn try_add_mixnode(
)?;
// update the signing nonce associated with this sender so that the future signature would be made on the new value
signing_storage::increment_signing_nonce(deps.storage, info.sender.clone())?;
signing_storage::increment_signing_nonce(deps.storage, owner.clone())?;
let node_identity = mixnode.identity_key.clone();
let (node_id, layer) = save_new_mixnode(
@@ -101,12 +154,14 @@ pub(crate) fn try_add_mixnode(
env,
mixnode,
cost_params,
info.sender.clone(),
owner.clone(),
proxy.clone(),
pledge.clone(),
)?;
Ok(Response::new().add_event(new_mixnode_bonding_event(
&info.sender,
&owner,
&proxy,
&pledge,
&node_identity,
node_id,
@@ -119,19 +174,43 @@ pub fn try_increase_pledge(
env: Env,
info: MessageInfo,
) -> Result<Response, MixnetContractError> {
let mix_details = get_mixnode_details_by_owner(deps.storage, info.sender.clone())?
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner: info.sender })?;
_try_increase_pledge(deps, env, info.funds, info.sender, None)
}
pub fn try_increase_pledge_on_behalf(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let proxy = info.sender;
let owner = deps.api.addr_validate(&owner)?;
_try_increase_pledge(deps, env, info.funds, owner, Some(proxy))
}
pub fn _try_increase_pledge(
deps: DepsMut<'_>,
env: Env,
increase: Vec<Coin>,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?;
let mut pending_changes = mix_details.pending_changes;
let mix_id = mix_details.mix_id();
// increasing pledge is only allowed if the epoch is currently not in the process of being advanced
ensure_epoch_in_progress_state(deps.storage)?;
ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?;
ensure_bonded(&mix_details.bond_information)?;
ensure_no_pending_pledge_changes(&pending_changes)?;
let rewarding_denom = rewarding_denom(deps.storage)?;
let pledge_increase = validate_pledge(info.funds, coin(1, rewarding_denom))?;
let pledge_increase = validate_pledge(increase, coin(1, rewarding_denom))?;
let cosmos_event = new_pending_pledge_increase_event(mix_id, &pledge_increase);
@@ -153,14 +232,39 @@ pub fn try_decrease_pledge(
info: MessageInfo,
decrease_by: Coin,
) -> Result<Response, MixnetContractError> {
let mix_details = get_mixnode_details_by_owner(deps.storage, info.sender.clone())?
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner: info.sender })?;
_try_decrease_pledge(deps, env, decrease_by, info.sender, None)
}
pub fn try_decrease_pledge_on_behalf(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
decrease_by: Coin,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let proxy = info.sender;
let owner = deps.api.addr_validate(&owner)?;
_try_decrease_pledge(deps, env, decrease_by, owner, Some(proxy))
}
pub fn _try_decrease_pledge(
deps: DepsMut<'_>,
env: Env,
decrease_by: Coin,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?;
let mut pending_changes = mix_details.pending_changes;
let mix_id = mix_details.mix_id();
// decreasing pledge is only allowed if the epoch is currently not in the process of being advanced
ensure_epoch_in_progress_state(deps.storage)?;
ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?;
ensure_bonded(&mix_details.bond_information)?;
ensure_no_pending_pledge_changes(&pending_changes)?;
@@ -208,12 +312,34 @@ pub fn try_decrease_pledge(
Ok(Response::new().add_event(cosmos_event))
}
pub(crate) fn try_remove_mixnode(
pub fn try_remove_mixnode_on_behalf(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let proxy = info.sender;
let owner = deps.api.addr_validate(&owner)?;
_try_remove_mixnode(deps, env, owner, Some(proxy))
}
pub fn try_remove_mixnode(
deps: DepsMut<'_>,
env: Env,
info: MessageInfo,
) -> Result<Response, MixnetContractError> {
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
_try_remove_mixnode(deps, env, info.sender, None)
}
pub(crate) fn _try_remove_mixnode(
deps: DepsMut<'_>,
env: Env,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?;
let pending_changes = storage::PENDING_MIXNODE_CHANGES
.may_load(deps.storage, existing_bond.mix_id)?
.unwrap_or_default();
@@ -222,12 +348,15 @@ pub(crate) fn try_remove_mixnode(
ensure_epoch_in_progress_state(deps.storage)?;
// see if the proxy matches
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
ensure_bonded(&existing_bond)?;
// if there are any pending requests to change the pledge, wait for them to resolve before allowing the unbonding
ensure_no_pending_pledge_changes(&pending_changes)?;
// set `is_unbonding` field
// clippy beta 1.70.0-beta.1 false positive
#[allow(clippy::redundant_clone)]
let mut updated_bond = existing_bond.clone();
updated_bond.is_unbonding = true;
storage::mixnode_bonds().replace(
@@ -246,6 +375,7 @@ pub(crate) fn try_remove_mixnode(
Ok(
Response::new().add_event(new_pending_mixnode_unbonding_event(
&existing_bond.owner,
&existing_bond.proxy,
existing_bond.identity(),
existing_bond.mix_id,
)),
@@ -257,13 +387,39 @@ pub(crate) fn try_update_mixnode_config(
info: MessageInfo,
new_config: MixNodeConfigUpdate,
) -> Result<Response, MixnetContractError> {
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
let owner = info.sender;
_try_update_mixnode_config(deps, new_config, owner, None)
}
pub(crate) fn try_update_mixnode_config_on_behalf(
deps: DepsMut<'_>,
info: MessageInfo,
new_config: MixNodeConfigUpdate,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let owner = deps.api.addr_validate(&owner)?;
let proxy = info.sender;
_try_update_mixnode_config(deps, new_config, owner, Some(proxy))
}
pub(crate) fn _try_update_mixnode_config(
deps: DepsMut<'_>,
new_config: MixNodeConfigUpdate,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?;
ensure_bonded(&existing_bond)?;
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
let cfg_update_event =
new_mixnode_config_update_event(existing_bond.mix_id, &info.sender, &new_config);
new_mixnode_config_update_event(existing_bond.mix_id, &owner, &proxy, &new_config);
// clippy beta 1.70.0-beta.1 false positive
#[allow(clippy::redundant_clone)]
let mut updated_bond = existing_bond.clone();
updated_bond.mix_node.host = new_config.host;
updated_bond.mix_node.mix_port = new_config.mix_port;
@@ -286,18 +442,45 @@ pub(crate) fn try_update_mixnode_cost_params(
env: Env,
info: MessageInfo,
new_costs: MixNodeCostParams,
) -> Result<Response, MixnetContractError> {
let owner = info.sender;
_try_update_mixnode_cost_params(deps, env, new_costs, owner, None)
}
pub(crate) fn try_update_mixnode_cost_params_on_behalf(
deps: DepsMut,
env: Env,
info: MessageInfo,
new_costs: MixNodeCostParams,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let owner = deps.api.addr_validate(&owner)?;
let proxy = info.sender;
_try_update_mixnode_cost_params(deps, env, new_costs, owner, Some(proxy))
}
pub(crate) fn _try_update_mixnode_cost_params(
deps: DepsMut,
env: Env,
new_costs: MixNodeCostParams,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// see if the node still exists
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &info.sender)?;
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?;
// changing cost params is only allowed if the epoch is currently not in the process of being advanced
ensure_epoch_in_progress_state(deps.storage)?;
ensure_proxy_match(&proxy, &existing_bond.proxy)?;
ensure_bonded(&existing_bond)?;
let cosmos_event = new_mixnode_pending_cost_params_update_event(
existing_bond.mix_id,
&info.sender,
&owner,
&proxy,
&new_costs,
);
@@ -314,7 +497,7 @@ pub(crate) fn try_update_mixnode_cost_params(
#[cfg(test)]
pub mod tests {
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::{Addr, Order, StdResult, Uint128};
use cosmwasm_std::{Order, StdResult, Uint128};
use mixnet_contract_common::mixnode::PendingMixNodeChanges;
use mixnet_contract_common::{EpochState, EpochStatus, ExecuteMsg, LayerDistribution, Percent};
@@ -497,6 +680,38 @@ pub mod tests {
assert_eq!(res, Err(MixnetContractError::InvalidEd25519Signature));
}
#[test]
fn mixnode_add_with_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
let (mixnode, sig, _) = test.mixnode_with_signature(owner, None);
let cost_params = fixtures::mix_node_cost_params_fixture();
let res = try_add_mixnode_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &good_mixnode_pledge()),
mixnode,
cost_params,
owner.to_string(),
sig,
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
}
)
}
#[test]
fn removing_mixnode_cant_be_performed_if_epoch_transition_is_in_progress() {
let bad_states = vec![
@@ -546,6 +761,23 @@ pub mod tests {
);
let mix_id = test.add_dummy_mixnode(owner, None);
let vesting_contract = test.vesting_contract();
// attempted to remove on behalf with invalid proxy (current is `None`)
let res = try_remove_mixnode_on_behalf(
test.deps_mut(),
env.clone(),
mock_info(vesting_contract.as_ref(), &[]),
owner.to_string(),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(),
incoming: vesting_contract.into_string(),
})
);
// "normal" unbonding succeeds and unbonding event is pushed to the pending epoch events
let res = try_remove_mixnode(test.deps_mut(), env.clone(), info.clone());
@@ -567,6 +799,35 @@ pub mod tests {
assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
}
#[test]
fn mixnode_remove_with_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
let res = try_remove_mixnode_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &[]),
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
}
)
}
#[test]
fn mixnode_remove_is_not_allowed_if_there_are_pending_pledge_changes() {
let mut test = TestSetup::new();
@@ -647,7 +908,22 @@ pub mod tests {
);
let mix_id = test.add_dummy_mixnode(owner, None);
let vesting_contract = test.vesting_contract();
// attempted to remove on behalf with invalid proxy (current is `None`)
let res = try_update_mixnode_config_on_behalf(
test.deps_mut(),
mock_info(vesting_contract.as_ref(), &[]),
update.clone(),
owner.to_string(),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(),
incoming: vesting_contract.into_string(),
})
);
// "normal" update succeeds
let res = try_update_mixnode_config(test.deps_mut(), info.clone(), update.clone());
assert!(res.is_ok());
@@ -667,6 +943,41 @@ pub mod tests {
assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
}
#[test]
fn updating_mixnode_config_with_illegal_proxy() {
let mut test = TestSetup::new();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
let update = MixNodeConfigUpdate {
host: "1.1.1.1:1234".to_string(),
mix_port: 1234,
verloc_port: 1235,
http_api_port: 1236,
version: "v1.2.3".to_string(),
};
let res = try_update_mixnode_config_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[]),
update,
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
}
)
}
#[test]
fn mixnode_cost_params_cant_be_updated_when_epoch_transition_is_in_progress() {
let bad_states = vec![
@@ -731,7 +1042,23 @@ pub mod tests {
);
let mix_id = test.add_dummy_mixnode(owner, None);
let vesting_contract = test.vesting_contract();
// attempted to remove on behalf with invalid proxy (current is `None`)
let res = try_update_mixnode_cost_params_on_behalf(
test.deps_mut(),
env.clone(),
mock_info(vesting_contract.as_ref(), &[]),
update.clone(),
owner.to_string(),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(),
incoming: vesting_contract.into_string(),
})
);
// "normal" update succeeds
let res = try_update_mixnode_cost_params(
test.deps_mut(),
@@ -772,6 +1099,40 @@ pub mod tests {
assert_eq!(res, Err(MixnetContractError::MixnodeIsUnbonding { mix_id }))
}
#[test]
fn updating_mixnode_cost_params_with_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
let update = MixNodeCostParams {
profit_margin_percent: Percent::from_percentage_value(42).unwrap(),
interval_operating_cost: Coin::new(12345678, TEST_COIN_DENOM),
};
let res = try_update_mixnode_cost_params_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &[]),
update,
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
}
)
}
#[test]
fn adding_mixnode_with_duplicate_sphinx_key_errors_out() {
let mut test = TestSetup::new();
@@ -879,6 +1240,69 @@ pub mod tests {
)
}
#[test]
fn is_not_allowed_if_theres_proxy_mismatch() {
let mut test = TestSetup::new();
let env = test.env();
let owner_without_proxy = Addr::unchecked("no-proxy");
let owner_with_proxy = Addr::unchecked("with-proxy");
let proxy = Addr::unchecked("proxy");
let wrong_proxy = Addr::unchecked("unrelated-proxy");
test.add_dummy_mixnode(owner_without_proxy.as_str(), None);
test.add_dummy_mixnode_with_illegal_proxy(
owner_with_proxy.as_str(),
None,
proxy.clone(),
);
let res = _try_increase_pledge(
test.deps_mut(),
env.clone(),
Vec::new(),
owner_without_proxy.clone(),
Some(proxy),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(),
incoming: "proxy".to_string(),
})
);
let res = _try_increase_pledge(
test.deps_mut(),
env.clone(),
Vec::new(),
owner_with_proxy.clone(),
None,
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "proxy".to_string(),
incoming: "None".to_string(),
})
);
let res = _try_increase_pledge(
test.deps_mut(),
env,
Vec::new(),
owner_with_proxy.clone(),
Some(wrong_proxy),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "proxy".to_string(),
incoming: "unrelated-proxy".to_string(),
})
)
}
#[test]
fn is_not_allowed_if_mixnode_has_unbonded_or_is_unbonding() {
let mut test = TestSetup::new();
@@ -1033,6 +1457,35 @@ pub mod tests {
}
);
}
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
test.add_dummy_mixnode_with_illegal_proxy(owner, None, illegal_proxy.clone());
let res = try_increase_pledge_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
}
)
}
}
#[cfg(test)]
@@ -1093,6 +1546,73 @@ pub mod tests {
)
}
#[test]
fn is_not_allowed_if_theres_proxy_mismatch() {
let mut test = TestSetup::new();
let env = test.env();
let owner_without_proxy = Addr::unchecked("no-proxy");
let owner_with_proxy = Addr::unchecked("with-proxy");
let proxy = Addr::unchecked("proxy");
let wrong_proxy = Addr::unchecked("unrelated-proxy");
// just to make sure that after decrease the value would still be above the minimum
let stake = Uint128::new(100_000_000_000);
let decrease = test.coin(1000);
test.add_dummy_mixnode(owner_without_proxy.as_str(), Some(stake));
test.add_dummy_mixnode_with_illegal_proxy(
owner_with_proxy.as_str(),
Some(stake),
proxy.clone(),
);
let res = _try_decrease_pledge(
test.deps_mut(),
env.clone(),
decrease.clone(),
owner_without_proxy.clone(),
Some(proxy),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "None".to_string(),
incoming: "proxy".to_string(),
})
);
let res = _try_decrease_pledge(
test.deps_mut(),
env.clone(),
decrease.clone(),
owner_with_proxy.clone(),
None,
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "proxy".to_string(),
incoming: "None".to_string(),
})
);
let res = _try_decrease_pledge(
test.deps_mut(),
env,
decrease,
owner_with_proxy.clone(),
Some(wrong_proxy),
);
assert_eq!(
res,
Err(MixnetContractError::ProxyMismatch {
existing: "proxy".to_string(),
incoming: "unrelated-proxy".to_string(),
})
)
}
#[test]
fn is_not_allowed_if_mixnode_has_unbonded_or_is_unbonding() {
let mut test = TestSetup::new();
@@ -1289,5 +1809,38 @@ pub mod tests {
}
);
}
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let env = test.env();
let stake = Uint128::new(100_000_000_000);
let decrease = test.coin(1000);
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "alice";
test.add_dummy_mixnode_with_illegal_proxy(owner, Some(stake), illegal_proxy.clone());
let res = try_decrease_pledge_on_behalf(
test.deps_mut(),
env,
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
decrease,
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
}
)
}
}
}
-49
View File
@@ -1,51 +1,2 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::interval::storage as interval_storage;
use cosmwasm_std::{DepsMut, Order, Storage};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::PendingEpochEventKind;
fn ensure_no_pending_proxy_events(storage: &dyn Storage) -> Result<(), MixnetContractError> {
let last_executed = interval_storage::LAST_PROCESSED_EPOCH_EVENT.load(storage)?;
let last_inserted = interval_storage::EPOCH_EVENT_ID_COUNTER.load(storage)?;
// no pending events
if last_executed == last_inserted {
return Ok(());
}
for maybe_event in
interval_storage::PENDING_EPOCH_EVENTS.range(storage, None, None, Order::Ascending)
{
let (id, event_data) = maybe_event?;
match event_data.kind {
PendingEpochEventKind::Delegate { proxy, .. } => {
if proxy.is_some() {
return Err(MixnetContractError::FailedMigration {
comment: format!(
"there is a pending vesting contract delegation with id {id}"
),
});
}
}
PendingEpochEventKind::Undelegate { proxy, .. } => {
if proxy.is_some() {
return Err(MixnetContractError::FailedMigration {
comment: format!(
"there is a pending vesting contract undelegation with id {id}"
),
});
}
}
_ => continue,
}
}
Ok(())
}
pub(crate) fn vesting_purge(deps: DepsMut) -> Result<(), MixnetContractError> {
ensure_no_pending_proxy_events(deps.storage)?;
Ok(())
}
+194 -33
View File
@@ -1,19 +1,8 @@
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::storage;
use crate::delegations::storage as delegations_storage;
use crate::interval::storage as interval_storage;
use crate::interval::storage::{push_new_epoch_event, push_new_interval_event};
use crate::mixnodes::helpers::get_mixnode_details_by_owner;
use crate::mixnodes::storage as mixnodes_storage;
use crate::rewards::helpers;
use crate::rewards::helpers::update_and_save_last_rewarded;
use crate::support::helpers::{
ensure_bonded, ensure_can_advance_epoch, ensure_epoch_in_progress_state, ensure_is_owner,
AttachSendTokens,
};
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response};
use cosmwasm_std::{wasm_execute, Addr, DepsMut, Env, MessageInfo, Response};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::events::{
new_active_set_update_event, new_mix_rewarding_event,
@@ -27,6 +16,22 @@ use mixnet_contract_common::reward_params::{
IntervalRewardingParamsUpdate, NodeRewardParams, Performance,
};
use mixnet_contract_common::{Delegation, EpochState, MixId};
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
use crate::delegations::storage as delegations_storage;
use crate::interval::storage as interval_storage;
use crate::interval::storage::{push_new_epoch_event, push_new_interval_event};
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnodes::helpers::get_mixnode_details_by_owner;
use crate::mixnodes::storage as mixnodes_storage;
use crate::rewards::helpers;
use crate::rewards::helpers::update_and_save_last_rewarded;
use crate::support::helpers::{
ensure_bonded, ensure_can_advance_epoch, ensure_epoch_in_progress_state, ensure_is_owner,
ensure_proxy_match, ensure_sent_by_vesting_contract, send_to_proxy_or_owner,
};
use super::storage;
pub(crate) fn try_reward_mixnode(
deps: DepsMut<'_>,
@@ -135,16 +140,37 @@ pub(crate) fn try_withdraw_operator_reward(
deps: DepsMut<'_>,
info: MessageInfo,
) -> Result<Response, MixnetContractError> {
// we need to grab all of the node's details, so we'd known original pledge alongside
_try_withdraw_operator_reward(deps, info.sender, None)
}
pub(crate) fn try_withdraw_operator_reward_on_behalf(
deps: DepsMut<'_>,
info: MessageInfo,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let proxy = info.sender;
let owner = deps.api.addr_validate(&owner)?;
_try_withdraw_operator_reward(deps, owner, Some(proxy))
}
pub(crate) fn _try_withdraw_operator_reward(
deps: DepsMut<'_>,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// we need to grab all of the node's details so we'd known original pledge alongside
// all the earned rewards (and obviously to know if this node even exists and is still
// in the bonded state)
let mix_details = get_mixnode_details_by_owner(deps.storage, info.sender.clone())?.ok_or(
let mix_details = get_mixnode_details_by_owner(deps.storage, owner.clone())?.ok_or(
MixnetContractError::NoAssociatedMixNodeBond {
owner: info.sender.clone(),
owner: owner.clone(),
},
)?;
let mix_id = mix_details.mix_id();
ensure_proxy_match(&proxy, &mix_details.bond_information.proxy)?;
ensure_bonded(&mix_details.bond_information)?;
let reward = helpers::withdraw_operator_reward(deps.storage, mix_details)?;
@@ -152,13 +178,26 @@ pub(crate) fn try_withdraw_operator_reward(
// if the reward is zero, don't track or send anything - there's no point
if !reward.amount.is_zero() {
response = response.send_tokens(&info.sender, reward.clone())
let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![reward.clone()]);
response = response.add_message(return_tokens);
if let Some(proxy) = &proxy {
// we can only attempt to send the message to the vesting contract if the proxy IS the vesting contract
// otherwise, we don't care
let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?;
if proxy == vesting_contract {
let msg = VestingContractExecuteMsg::TrackReward {
amount: reward.clone(),
address: owner.clone().into_string(),
};
let track_reward_message = wasm_execute(proxy, &msg, vec![])?;
response = response.add_message(track_reward_message);
}
}
}
Ok(response.add_event(new_withdraw_operator_reward_event(
&info.sender,
reward,
mix_id,
&owner, &proxy, reward, mix_id,
)))
}
@@ -166,15 +205,37 @@ pub(crate) fn try_withdraw_delegator_reward(
deps: DepsMut<'_>,
info: MessageInfo,
mix_id: MixId,
) -> Result<Response, MixnetContractError> {
_try_withdraw_delegator_reward(deps, mix_id, info.sender, None)
}
pub(crate) fn try_withdraw_delegator_reward_on_behalf(
deps: DepsMut<'_>,
info: MessageInfo,
mix_id: MixId,
owner: String,
) -> Result<Response, MixnetContractError> {
ensure_sent_by_vesting_contract(&info, deps.storage)?;
let proxy = info.sender;
let owner = deps.api.addr_validate(&owner)?;
_try_withdraw_delegator_reward(deps, mix_id, owner, Some(proxy))
}
pub(crate) fn _try_withdraw_delegator_reward(
deps: DepsMut<'_>,
mix_id: MixId,
owner: Addr,
proxy: Option<Addr>,
) -> Result<Response, MixnetContractError> {
// see if the delegation even exists
let storage_key = Delegation::generate_storage_key(mix_id, &info.sender, None);
let storage_key = Delegation::generate_storage_key(mix_id, &owner, proxy.as_ref());
let delegation = match delegations_storage::delegations().may_load(deps.storage, storage_key)? {
None => {
return Err(MixnetContractError::NoMixnodeDelegationFound {
mix_id,
address: info.sender.into_string(),
proxy: None,
address: owner.into_string(),
proxy: proxy.map(Addr::into_string),
});
}
Some(delegation) => delegation,
@@ -196,18 +257,33 @@ pub(crate) fn try_withdraw_delegator_reward(
_ => (),
};
ensure_proxy_match(&proxy, &delegation.proxy)?;
let reward = helpers::withdraw_delegator_reward(deps.storage, delegation, mix_rewarding)?;
let mut response = Response::new();
// if the reward is zero, don't track or send anything - there's no point
if !reward.amount.is_zero() {
response = response.send_tokens(&info.sender, reward.clone())
let return_tokens = send_to_proxy_or_owner(&proxy, &owner, vec![reward.clone()]);
response = response.add_message(return_tokens);
if let Some(proxy) = &proxy {
// we can only attempt to send the message to the vesting contract if the proxy IS the vesting contract
// otherwise, we don't care
let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?;
if proxy == vesting_contract {
let msg = VestingContractExecuteMsg::TrackReward {
amount: reward.clone(),
address: owner.clone().into_string(),
};
let track_reward_message = wasm_execute(proxy, &msg, vec![])?;
response = response.add_message(track_reward_message);
}
}
}
Ok(response.add_event(new_withdraw_delegator_reward_event(
&info.sender,
reward,
mix_id,
&owner, &proxy, reward, mix_id,
)))
}
@@ -1329,11 +1405,14 @@ pub mod tests {
#[cfg(test)]
mod withdrawing_delegator_reward {
use crate::interval::pending_events;
use crate::support::tests::test_helpers::{assert_eq_with_leeway, TestSetup};
use cosmwasm_std::{BankMsg, CosmosMsg, Decimal, Uint128};
use cosmwasm_std::{coin, BankMsg, CosmosMsg, Decimal, Uint128};
use mixnet_contract_common::rewarding::helpers::truncate_reward_amount;
use crate::interval::pending_events;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::{assert_eq_with_leeway, TestSetup};
use super::*;
#[test]
@@ -1663,14 +1742,60 @@ pub mod tests {
let accumulated_actual = truncate_reward_amount(accumulated_quad);
assert_eq_with_leeway(total_claimed, accumulated_actual, Uint128::new(6));
}
#[test]
fn fails_for_illegal_proxy() {
let test = TestSetup::new();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let mut test = TestSetup::new();
let mix_id =
test.add_dummy_mixnode("mix-owner1", Some(Uint128::new(1_000_000_000_000)));
let delegator = "delegator";
test.add_immediate_delegation_with_illegal_proxy(
delegator,
100_000_000u128,
mix_id,
illegal_proxy.clone(),
);
// reward the node
test.skip_to_next_epoch_end();
test.force_change_rewarded_set(vec![mix_id]);
test.start_epoch_transition();
test.reward_with_distribution(mix_id, test_helpers::performance(100.0));
let res = try_withdraw_delegator_reward_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
mix_id,
delegator.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
}
)
}
}
#[cfg(test)]
mod withdrawing_operator_reward {
use super::*;
use cosmwasm_std::{coin, BankMsg, CosmosMsg, Uint128};
use crate::interval::pending_events;
use crate::support::tests::fixtures::TEST_COIN_DENOM;
use crate::support::tests::test_helpers::TestSetup;
use cosmwasm_std::{Addr, BankMsg, CosmosMsg, Uint128};
use super::*;
#[test]
fn can_only_be_done_if_bond_exists() {
@@ -1783,6 +1908,42 @@ pub mod tests {
})
);
}
#[test]
fn fails_for_illegal_proxy() {
let mut test = TestSetup::new();
let illegal_proxy = Addr::unchecked("not-vesting-contract");
let vesting_contract = test.vesting_contract();
let owner = "mix-owner1";
let mix_id = test.add_dummy_mixnode_with_illegal_proxy(
owner,
Some(Uint128::new(1_000_000_000_000)),
illegal_proxy.clone(),
);
// reward the node
test.skip_to_next_epoch_end();
test.force_change_rewarded_set(vec![mix_id]);
test.start_epoch_transition();
test.reward_with_distribution(mix_id, test_helpers::performance(100.0));
let res = try_withdraw_operator_reward_on_behalf(
test.deps_mut(),
mock_info(illegal_proxy.as_ref(), &[coin(123, TEST_COIN_DENOM)]),
owner.to_string(),
)
.unwrap_err();
assert_eq!(
res,
MixnetContractError::SenderIsNotVestingContract {
received: illegal_proxy,
vesting_contract,
}
)
}
}
#[cfg(test)]
+174 -10
View File
@@ -2,11 +2,13 @@
// SPDX-License-Identifier: Apache-2.0
use crate::gateways::storage as gateways_storage;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnodes::storage as mixnodes_storage;
use cosmwasm_std::{Addr, BankMsg, Coin, CosmosMsg, Response, Storage};
use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, CosmosMsg, MessageInfo, Response, Storage};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::mixnode::PendingMixNodeChanges;
use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixNodeBond};
use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixId, MixNodeBond};
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
// helper trait to attach `Msg` to a response if it's provided
#[allow(dead_code)]
@@ -24,16 +26,131 @@ impl<T> AttachOptionalMessage<T> for Response<T> {
}
}
pub(crate) trait AttachSendTokens {
fn send_tokens(self, to: impl AsRef<str>, amount: Coin) -> Self;
// another helper trait to remove some duplicate code and consolidate comments regarding
// possible epoch progression halting behaviour
pub(crate) trait VestingTracking
where
Self: Sized,
{
fn maybe_add_track_vesting_undelegation_message(
self,
storage: &dyn Storage,
proxy: Option<Addr>,
owner: String,
mix_id: MixId,
amount: Coin,
) -> Result<Self, MixnetContractError>;
fn maybe_add_track_vesting_unbond_mixnode_message(
self,
storage: &dyn Storage,
proxy: Option<Addr>,
owner: String,
amount: Coin,
) -> Result<Self, MixnetContractError>;
fn maybe_add_track_vesting_decrease_mixnode_pledge(
self,
storage: &dyn Storage,
proxy: Option<Addr>,
owner: String,
amount: Coin,
) -> Result<Self, MixnetContractError>;
}
impl<T> AttachSendTokens for Response<T> {
fn send_tokens(self, to: impl AsRef<str>, amount: Coin) -> Self {
self.add_message(BankMsg::Send {
to_address: to.as_ref().to_string(),
amount: vec![amount],
})
impl VestingTracking for Response {
fn maybe_add_track_vesting_undelegation_message(
self,
storage: &dyn Storage,
proxy: Option<Addr>,
owner: String,
mix_id: MixId,
amount: Coin,
) -> Result<Self, MixnetContractError> {
// if there's a proxy set (i.e. the vesting contract), send the track message
if let Some(proxy) = proxy {
let vesting_contract = mixnet_params_storage::vesting_contract_address(storage)?;
// Note: this can INTENTIONALLY cause epoch progression halt if the proxy is not the vesting contract
// But this is fine, since this situation should have NEVER occurred in the first place
// (as all 'on_behalf' methods, including 'DelegateToMixnodeOnBehalf' that got us here,
// explicitly require the proxy to be the vesting contract)
// 'fixing' it would require manually inspecting the problematic event, investigating
// it's cause and manually (presumably via migration) clearing it.
if proxy != vesting_contract {
return Err(MixnetContractError::ProxyIsNotVestingContract {
received: proxy,
vesting_contract,
});
}
let msg = VestingContractExecuteMsg::TrackUndelegation {
owner,
mix_id,
amount,
};
let track_undelegate_message = wasm_execute(proxy, &msg, vec![])?;
Ok(self.add_message(track_undelegate_message))
} else {
// there's no proxy so nothing to do
Ok(self)
}
}
fn maybe_add_track_vesting_unbond_mixnode_message(
self,
storage: &dyn Storage,
proxy: Option<Addr>,
owner: String,
amount: Coin,
) -> Result<Self, MixnetContractError> {
// if there's a proxy set (i.e. the vesting contract), send the track message
if let Some(proxy) = proxy {
let vesting_contract = mixnet_params_storage::vesting_contract_address(storage)?;
// exactly the same possible halting behaviour as in `maybe_add_track_vesting_undelegation_message`.
if proxy != vesting_contract {
return Err(MixnetContractError::ProxyIsNotVestingContract {
received: proxy,
vesting_contract,
});
}
let msg = VestingContractExecuteMsg::TrackUnbondMixnode { owner, amount };
let track_unbond_message = wasm_execute(proxy, &msg, vec![])?;
Ok(self.add_message(track_unbond_message))
} else {
// there's no proxy so nothing to do
Ok(self)
}
}
fn maybe_add_track_vesting_decrease_mixnode_pledge(
self,
storage: &dyn Storage,
proxy: Option<Addr>,
owner: String,
amount: Coin,
) -> Result<Self, MixnetContractError> {
if let Some(proxy) = proxy {
let vesting_contract = mixnet_params_storage::vesting_contract_address(storage)?;
// exactly the same possible halting behaviour as in `maybe_add_track_vesting_undelegation_message`.
if proxy != vesting_contract {
return Err(MixnetContractError::ProxyIsNotVestingContract {
received: proxy,
vesting_contract,
});
}
let msg = VestingContractExecuteMsg::TrackDecreasePledge { owner, amount };
let track_decrease_pledge_message = wasm_execute(proxy, &msg, vec![])?;
Ok(self.add_message(track_decrease_pledge_message))
} else {
// there's no proxy so nothing to do
Ok(self)
}
}
}
@@ -41,6 +158,20 @@ impl<T> AttachSendTokens for Response<T> {
// api.debug(&*format!("\n\n\n=========================================\n{}\n=========================================\n\n\n", msg.into()));
// }
/// Attempts to construct a `BankMsg` to send specified tokens to the provided
/// proxy address. If that's unavailable, the `BankMsg` will use the "owner" as the
/// "to_address".
pub(crate) fn send_to_proxy_or_owner(
proxy: &Option<Addr>,
owner: &Addr,
amount: Vec<Coin>,
) -> BankMsg {
BankMsg::Send {
to_address: proxy.as_ref().unwrap_or(owner).to_string(),
amount,
}
}
pub(crate) fn validate_pledge(
mut pledge: Vec<Coin>,
minimum_pledge: Coin,
@@ -206,6 +337,39 @@ pub(crate) fn ensure_is_owner(
Ok(())
}
pub(crate) fn ensure_proxy_match(
actual: &Option<Addr>,
expected: &Option<Addr>,
) -> Result<(), MixnetContractError> {
if actual != expected {
return Err(MixnetContractError::ProxyMismatch {
existing: expected
.as_ref()
.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
incoming: actual
.as_ref()
.map_or_else(|| "None".to_string(), |a| a.as_str().to_string()),
});
}
Ok(())
}
pub(crate) fn ensure_sent_by_vesting_contract(
info: &MessageInfo,
storage: &dyn Storage,
) -> Result<(), MixnetContractError> {
let vesting_contract_address =
crate::mixnet_contract_settings::storage::vesting_contract_address(storage)?;
if info.sender != vesting_contract_address {
Err(MixnetContractError::SenderIsNotVestingContract {
received: info.sender.clone(),
vesting_contract: vesting_contract_address,
})
} else {
Ok(())
}
}
pub(crate) fn ensure_bonded(bond: &MixNodeBond) -> Result<(), MixnetContractError> {
if bond.is_unbonding {
return Err(MixnetContractError::MixnodeIsUnbonding {
@@ -22,7 +22,7 @@ pub(crate) fn valid_bond_gateway_msg(
..tests::fixtures::gateway_fixture()
};
let msg = gateway_bonding_sign_payload(deps, sender, gateway.clone(), stake);
let msg = gateway_bonding_sign_payload(deps, sender, None, gateway.clone(), stake);
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
let identity_key = keypair.public_key().to_base58_string();
+278 -16
View File
@@ -14,7 +14,8 @@ pub mod test_helpers {
use crate::delegations::storage as delegations_storage;
use crate::delegations::transactions::try_delegate_to_mixnode;
use crate::families::transactions::{try_create_family, try_join_family};
use crate::gateways::transactions::try_add_gateway;
use crate::gateways::storage as gateways_storage;
use crate::gateways::transactions::{try_add_gateway, try_add_gateway_on_behalf};
use crate::interval::transactions::{
perform_pending_epoch_actions, perform_pending_interval_actions, try_begin_epoch_transition,
};
@@ -26,7 +27,9 @@ pub mod test_helpers {
};
use crate::mixnodes::storage as mixnodes_storage;
use crate::mixnodes::storage::mixnode_bonds;
use crate::mixnodes::transactions::{try_add_mixnode, try_remove_mixnode};
use crate::mixnodes::transactions::{
try_add_mixnode, try_add_mixnode_on_behalf, try_remove_mixnode,
};
use crate::rewards::queries::{
query_pending_delegator_reward, query_pending_mixnode_operator_reward,
};
@@ -42,7 +45,7 @@ pub mod test_helpers {
use cosmwasm_std::testing::mock_info;
use cosmwasm_std::testing::MockApi;
use cosmwasm_std::testing::MockQuerier;
use cosmwasm_std::{coin, coins, Addr, BankMsg, CosmosMsg, Storage};
use cosmwasm_std::{coin, coins, Addr, Api, BankMsg, CosmosMsg, Storage};
use cosmwasm_std::{Coin, Order};
use cosmwasm_std::{Decimal, Empty, MemoryStorage};
use cosmwasm_std::{Deps, OwnedDeps};
@@ -145,6 +148,13 @@ pub mod test_helpers {
self.owner.clone()
}
pub fn vesting_contract(&self) -> Addr {
mixnet_params_storage::CONTRACT_STATE
.load(self.deps().storage)
.unwrap()
.vesting_contract_address
}
pub fn coin(&self, amount: u128) -> Coin {
coin(amount, rewarding_denom(self.deps().storage).unwrap())
}
@@ -168,6 +178,7 @@ pub mod test_helpers {
&mut self,
family_owner_keys: &identity::KeyPair,
member_node: IdentityKeyRef,
vesting: bool,
) -> MessageSignature {
let identity = family_owner_keys.public_key().to_base58_string();
@@ -184,7 +195,14 @@ pub mod test_helpers {
let nonce = signing_storage::get_signing_nonce(self.deps().storage, owner).unwrap();
let msg = construct_family_join_permit(nonce, family_head, member_node.to_owned());
let proxy = if vesting {
Some(self.vesting_contract())
} else {
None
};
let msg =
construct_family_join_permit(nonce, family_head, proxy, member_node.to_owned());
let sig_bytes = family_owner_keys
.private_key()
@@ -199,11 +217,13 @@ pub mod test_helpers {
member: &str,
member_keys: &identity::KeyPair,
head_keys: &identity::KeyPair,
vesting: bool,
) {
let member_identity = member_keys.public_key().to_base58_string();
let head_identity = head_keys.public_key().to_base58_string();
let join_permit = self.generate_family_join_permit(head_keys, &member_identity);
let join_permit =
self.generate_family_join_permit(head_keys, &member_identity, vesting);
let family_head = FamilyHead::new(head_identity);
try_join_family(
@@ -215,13 +235,12 @@ pub mod test_helpers {
.unwrap();
}
#[allow(dead_code)]
pub fn create_dummy_mixnode_with_new_family(
&mut self,
head: &str,
label: &str,
) -> (MixId, identity::KeyPair) {
let (mix_id, keys) = self.add_dummy_mixnode_with_keypair(head, None);
let (mix_id, keys) = self.add_dummy_mixnode_with_proxy_and_keypair(head, None);
try_create_family(self.deps_mut(), mock_info(head, &[]), label.to_string()).unwrap();
(mix_id, keys)
@@ -319,7 +338,7 @@ pub mod test_helpers {
stake: Option<Uint128>,
) -> MessageSignature {
let stake = self.make_mix_pledge(stake);
let msg = mixnode_bonding_sign_payload(self.deps(), owner, mixnode, stake);
let msg = mixnode_bonding_sign_payload(self.deps(), owner, None, mixnode, stake);
ed25519_sign_message(msg, key)
}
@@ -340,8 +359,13 @@ pub mod test_helpers {
..tests::fixtures::mix_node_fixture()
};
let msg =
mixnode_bonding_sign_payload(self.deps(), owner, mixnode.clone(), stake.clone());
let msg = mixnode_bonding_sign_payload(
self.deps(),
owner,
None,
mixnode.clone(),
stake.clone(),
);
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
let info = mock_info(owner, &stake);
@@ -365,6 +389,156 @@ pub mod test_helpers {
(current_id_counter + 1, keypair)
}
pub fn add_dummy_mixnode_with_proxy_and_keypair(
&mut self,
owner: &str,
stake: Option<Uint128>,
) -> (MixId, identity::KeyPair) {
let stake = self.make_mix_pledge(stake);
let proxy = self.vesting_contract();
let keypair = identity::KeyPair::new(&mut self.rng);
let identity_key = keypair.public_key().to_base58_string();
let legit_sphinx_keys = nym_crypto::asymmetric::encryption::KeyPair::new(&mut self.rng);
let mixnode = MixNode {
identity_key,
sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
..tests::fixtures::mix_node_fixture()
};
let msg = mixnode_bonding_sign_payload(
self.deps(),
owner,
Some(proxy.clone()),
mixnode.clone(),
stake.clone(),
);
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
let info = mock_info(proxy.as_str(), &stake);
let current_id_counter = mixnodes_storage::MIXNODE_ID_COUNTER
.may_load(self.deps().storage)
.unwrap()
.unwrap_or_default();
let env = self.env();
try_add_mixnode_on_behalf(
self.deps_mut(),
env,
info,
mixnode,
tests::fixtures::mix_node_cost_params_fixture(),
owner.to_string(),
owner_signature,
)
.unwrap();
// newly added mixnode gets assigned the current counter + 1
(current_id_counter + 1, keypair)
}
pub fn add_dummy_mixnode_with_legal_proxy(
&mut self,
owner: &str,
stake: Option<Uint128>,
) -> MixId {
self.add_dummy_mixnode_with_proxy_and_keypair(owner, stake)
.0
}
pub fn set_illegal_mixnode_proxy(&mut self, mix_id: MixId, proxy: Addr) {
let mut bond_details = mixnodes_storage::mixnode_bonds()
.load(self.deps().storage, mix_id)
.unwrap();
bond_details.proxy = Some(proxy);
mixnodes_storage::mixnode_bonds()
.save(self.deps_mut().storage, mix_id, &bond_details)
.unwrap();
}
pub fn add_dummy_gateway_with_illegal_proxy(
&mut self,
owner: &str,
stake: Option<Uint128>,
proxy: Addr,
) -> IdentityKey {
let gateway_identity = self.add_dummy_gateway_with_legal_proxy(owner, stake);
self.set_illegal_gateway_proxy(&gateway_identity, proxy);
gateway_identity
}
pub fn set_illegal_gateway_proxy(&mut self, gateway_id: &str, proxy: Addr) {
let mut gateway = gateways_storage::gateways()
.load(self.deps().storage, gateway_id)
.unwrap();
gateway.proxy = Some(proxy);
gateways_storage::gateways()
.save(self.deps_mut().storage, gateway_id, &gateway)
.unwrap();
}
pub fn add_dummy_gateway_with_legal_proxy(
&mut self,
owner: &str,
stake: Option<Uint128>,
) -> IdentityKey {
let stake = match stake {
Some(amount) => {
let denom = rewarding_denom(self.deps().storage).unwrap();
Coin { denom, amount }
}
None => minimum_mixnode_pledge(self.deps.as_ref().storage).unwrap(),
};
let keypair = identity::KeyPair::new(&mut self.rng);
let identity_key = keypair.public_key().to_base58_string();
let legit_sphinx_keys = nym_crypto::asymmetric::encryption::KeyPair::new(&mut self.rng);
let proxy = self.vesting_contract();
let gateway = Gateway {
identity_key,
sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
..tests::fixtures::gateway_fixture()
};
let msg = gateway_bonding_sign_payload(
self.deps(),
owner,
Some(proxy.clone()),
gateway.clone(),
vec![stake.clone()],
);
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
let env = self.env();
let info = mock_info(proxy.as_ref(), &[stake]);
try_add_gateway_on_behalf(
self.deps_mut(),
env,
info,
gateway,
owner.to_string(),
owner_signature,
)
.unwrap();
keypair.public_key().to_base58_string()
}
pub fn add_dummy_mixnode_with_illegal_proxy(
&mut self,
owner: &str,
stake: Option<Uint128>,
proxy: Addr,
) -> MixId {
let mix_id = self.add_dummy_mixnode_with_legal_proxy(owner, stake);
self.set_illegal_mixnode_proxy(mix_id, proxy);
mix_id
}
pub fn mixnode_with_signature(
&mut self,
sender: &str,
@@ -381,7 +555,8 @@ pub mod test_helpers {
sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
..tests::fixtures::mix_node_fixture()
};
let msg = mixnode_bonding_sign_payload(self.deps(), sender, mixnode.clone(), stake);
let msg =
mixnode_bonding_sign_payload(self.deps(), sender, None, mixnode.clone(), stake);
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
(mixnode, owner_signature, keypair)
@@ -404,7 +579,8 @@ pub mod test_helpers {
..tests::fixtures::gateway_fixture()
};
let msg = gateway_bonding_sign_payload(self.deps(), sender, gateway.clone(), stake);
let msg =
gateway_bonding_sign_payload(self.deps(), sender, None, gateway.clone(), stake);
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
(gateway, owner_signature)
@@ -449,10 +625,87 @@ pub mod test_helpers {
Addr::unchecked(delegator),
target,
amount,
None,
)
.unwrap();
}
pub fn add_immediate_delegation_with_legal_proxy(
&mut self,
delegator: &str,
amount: impl Into<Uint128>,
target: MixId,
) {
let denom = rewarding_denom(self.deps().storage).unwrap();
let amount = Coin {
denom,
amount: amount.into(),
};
let env = self.env();
let proxy = self.vesting_contract();
pending_events::delegate(
self.deps_mut(),
&env,
env.block.height,
Addr::unchecked(delegator),
target,
amount,
Some(proxy),
)
.unwrap();
}
// to set illegal proxy we have to bypass "normal" flow and put the value
// directly into the storage
pub fn add_immediate_delegation_with_illegal_proxy(
&mut self,
delegator: &str,
amount: impl Into<Uint128>,
target: MixId,
proxy: Addr,
) {
let denom = rewarding_denom(self.deps().storage).unwrap();
let amount = Coin {
denom,
amount: amount.into(),
};
let owner = self.deps.api.addr_validate(delegator).unwrap();
let storage_key = Delegation::generate_storage_key(target, &owner, Some(&proxy));
let mut mix_rewarding = self.mix_rewarding(target);
let mut stored_delegation_amount = amount;
if let Some(existing_delegation) = delegations_storage::delegations()
.may_load(&self.deps.storage, storage_key.clone())
.unwrap()
{
let og_with_reward = mix_rewarding.undelegate(&existing_delegation).unwrap();
stored_delegation_amount.amount += og_with_reward.amount;
}
mix_rewarding
.add_base_delegation(stored_delegation_amount.amount)
.unwrap();
let delegation = Delegation::new(
owner,
target,
mix_rewarding.total_unit_reward,
stored_delegation_amount,
self.env.block.height,
Some(proxy),
);
delegations_storage::delegations()
.save(&mut self.deps.storage, storage_key, &delegation)
.unwrap();
rewards_storage::MIXNODE_REWARDING
.save(&mut self.deps.storage, target, &mix_rewarding)
.unwrap();
}
#[allow(unused)]
pub fn add_delegation(
&mut self,
@@ -471,8 +724,14 @@ pub mod test_helpers {
pub fn remove_immediate_delegation(&mut self, delegator: &str, target: MixId) {
let height = self.env.block.height;
pending_events::undelegate(self.deps_mut(), height, Addr::unchecked(delegator), target)
.unwrap();
pending_events::undelegate(
self.deps_mut(),
height,
Addr::unchecked(delegator),
target,
None,
)
.unwrap();
}
pub fn start_epoch_transition(&mut self) {
@@ -850,6 +1109,7 @@ pub mod test_helpers {
Addr::unchecked(format!("owner{}", i)),
mix_id,
tests::fixtures::good_mixnode_pledge().pop().unwrap(),
None,
)
.unwrap();
}
@@ -945,6 +1205,7 @@ pub mod test_helpers {
pub fn mixnode_bonding_sign_payload(
deps: Deps<'_>,
owner: &str,
proxy: Option<Addr>,
mixnode: MixNode,
stake: Vec<Coin>,
) -> SignableMixNodeBondingMsg {
@@ -953,13 +1214,14 @@ pub mod test_helpers {
signing_storage::get_signing_nonce(deps.storage, Addr::unchecked(owner)).unwrap();
let payload = MixnodeBondingPayload::new(mixnode, cost_params);
let content = ContractMessageContent::new(Addr::unchecked(owner), stake, payload);
let content = ContractMessageContent::new(Addr::unchecked(owner), proxy, stake, payload);
SignableMixNodeBondingMsg::new(nonce, content)
}
pub fn gateway_bonding_sign_payload(
deps: Deps<'_>,
owner: &str,
proxy: Option<Addr>,
gateway: Gateway,
stake: Vec<Coin>,
) -> SignableGatewayBondingMsg {
@@ -967,7 +1229,7 @@ pub mod test_helpers {
signing_storage::get_signing_nonce(deps.storage, Addr::unchecked(owner)).unwrap();
let payload = GatewayBondingPayload::new(gateway);
let content = ContractMessageContent::new(Addr::unchecked(owner), stake, payload);
let content = ContractMessageContent::new(Addr::unchecked(owner), proxy, stake, payload);
SignableGatewayBondingMsg::new(nonce, content)
}
-95
View File
@@ -1,95 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::delegations::storage as delegations_storage;
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
use crate::mixnodes::helpers::get_mixnode_details_by_owner;
use crate::mixnodes::storage as mixnodes_storage;
use crate::support::helpers::{
ensure_bonded, ensure_epoch_in_progress_state, ensure_no_pending_pledge_changes,
};
use cosmwasm_std::{wasm_execute, DepsMut, MessageInfo, Response};
use mixnet_contract_common::error::MixnetContractError;
use mixnet_contract_common::{Delegation, MixId};
use vesting_contract_common::messages::ExecuteMsg as VestingExecuteMsg;
pub(crate) fn try_migrate_vested_mixnode(
deps: DepsMut<'_>,
info: MessageInfo,
) -> Result<Response, MixnetContractError> {
let mix_details = get_mixnode_details_by_owner(deps.storage, info.sender.clone())?.ok_or(
MixnetContractError::NoAssociatedMixNodeBond {
owner: info.sender.clone(),
},
)?;
let mix_id = mix_details.mix_id();
ensure_epoch_in_progress_state(deps.storage)?;
ensure_no_pending_pledge_changes(&mix_details.pending_changes)?;
ensure_bonded(&mix_details.bond_information)?;
let Some(proxy) = &mix_details.bond_information.proxy else {
return Err(MixnetContractError::NotAVestingMixnode);
};
let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?;
if proxy != vesting_contract {
return Err(MixnetContractError::ProxyIsNotVestingContract {
received: proxy.clone(),
vesting_contract,
});
}
let mut updated_bond = mix_details.bond_information.clone();
updated_bond.proxy = None;
mixnodes_storage::mixnode_bonds().replace(
deps.storage,
mix_id,
Some(&updated_bond),
Some(&mix_details.bond_information),
)?;
Ok(Response::new().add_message(wasm_execute(
vesting_contract,
&VestingExecuteMsg::TrackMigratedMixnode {
owner: info.sender.into_string(),
},
vec![],
)?))
}
pub(crate) fn try_migrate_vested_delegation(
deps: DepsMut<'_>,
info: MessageInfo,
mix_id: MixId,
) -> Result<Response, MixnetContractError> {
ensure_epoch_in_progress_state(deps.storage)?;
let vesting_contract = mixnet_params_storage::vesting_contract_address(deps.storage)?;
let storage_key =
Delegation::generate_storage_key(mix_id, &info.sender, Some(&vesting_contract));
let Some(mut delegation) =
delegations_storage::delegations().may_load(deps.storage, storage_key.clone())?
else {
return Err(MixnetContractError::NotAVestingDelegation);
};
// sanity check that's meant to blow up the contract
assert_eq!(delegation.proxy, Some(vesting_contract.clone()));
// update the delegation and save it under the correct storage key
delegation.proxy = None;
let updated_storage_key = Delegation::generate_storage_key(mix_id, &info.sender, None);
delegations_storage::delegations().remove(deps.storage, storage_key)?;
delegations_storage::delegations().save(deps.storage, updated_storage_key, &delegation)?;
Ok(Response::new().add_message(wasm_execute(
vesting_contract,
&VestingExecuteMsg::TrackMigratedDelegation {
owner: info.sender.into_string(),
mix_id,
},
vec![],
)?))
}
@@ -691,54 +691,6 @@
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"track_migrated_mixnode"
],
"properties": {
"track_migrated_mixnode": {
"type": "object",
"required": [
"owner"
],
"properties": {
"owner": {
"type": "string"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"track_migrated_delegation"
],
"properties": {
"track_migrated_delegation": {
"type": "object",
"required": [
"mix_id",
"owner"
],
"properties": {
"mix_id": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"owner": {
"type": "string"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
],
"definitions": {
-48
View File
@@ -669,54 +669,6 @@
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"track_migrated_mixnode"
],
"properties": {
"track_migrated_mixnode": {
"type": "object",
"required": [
"owner"
],
"properties": {
"owner": {
"type": "string"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
{
"type": "object",
"required": [
"track_migrated_delegation"
],
"properties": {
"track_migrated_delegation": {
"type": "object",
"required": [
"mix_id",
"owner"
],
"properties": {
"mix_id": {
"type": "integer",
"format": "uint32",
"minimum": 0.0
},
"owner": {
"type": "string"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
],
"definitions": {
+74 -6
View File
@@ -70,12 +70,55 @@ pub fn execute(
msg: ExecuteMsg,
) -> Result<Response, VestingContractError> {
match msg {
ExecuteMsg::CreateFamily { label } => try_create_family(info, deps, label),
ExecuteMsg::JoinFamily {
join_permit,
family_head,
} => try_join_family(info, deps, join_permit, family_head),
ExecuteMsg::LeaveFamily { family_head } => try_leave_family(info, deps, family_head),
ExecuteMsg::KickFamilyMember { member } => try_kick_family_member(info, deps, member),
ExecuteMsg::UpdateLockedPledgeCap { address, cap } => {
try_update_locked_pledge_cap(address, cap, info, deps)
}
ExecuteMsg::TrackReward { amount, address } => {
try_track_reward(deps, info, amount, &address)
}
ExecuteMsg::ClaimOperatorReward {} => try_claim_operator_reward(deps, info),
ExecuteMsg::ClaimDelegatorReward { mix_id } => {
try_claim_delegator_reward(deps, info, mix_id)
}
ExecuteMsg::UpdateMixnodeConfig { new_config } => {
try_update_mixnode_config(new_config, info, deps)
}
ExecuteMsg::UpdateMixnodeCostParams { new_costs } => {
try_update_mixnode_cost_params(new_costs, info, deps)
}
ExecuteMsg::UpdateMixnetAddress { address } => {
try_update_mixnet_address(address, info, deps)
}
ExecuteMsg::DelegateToMixnode {
mix_id,
amount,
on_behalf_of,
} => try_delegate_to_mixnode(mix_id, amount, on_behalf_of, info, env, deps),
ExecuteMsg::UndelegateFromMixnode {
mix_id,
on_behalf_of,
} => try_undelegate_from_mixnode(mix_id, on_behalf_of, info, deps),
ExecuteMsg::CreateAccount {
owner_address,
staking_address,
vesting_spec,
cap,
} => try_create_periodic_vesting_account(
&owner_address,
staking_address,
vesting_spec,
cap,
info,
env,
deps,
),
ExecuteMsg::WithdrawVestedCoins { amount } => {
try_withdraw_vested_coins(amount, env, info, deps)
}
@@ -84,22 +127,47 @@ pub fn execute(
mix_id,
amount,
} => try_track_undelegation(&owner, mix_id, amount, info, deps),
ExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature,
amount,
} => try_bond_mixnode(
mix_node,
cost_params,
owner_signature,
amount,
info,
env,
deps,
),
ExecuteMsg::PledgeMore { amount } => try_pledge_more(deps, env, info, amount),
ExecuteMsg::DecreasePledge { amount } => try_decrease_pledge(deps, info, amount),
ExecuteMsg::UnbondMixnode {} => try_unbond_mixnode(info, deps),
ExecuteMsg::TrackUnbondMixnode { owner, amount } => {
try_track_unbond_mixnode(&owner, amount, info, deps)
}
ExecuteMsg::TrackDecreasePledge { owner, amount } => {
try_track_decrease_mixnode_pledge(&owner, amount, info, deps)
}
ExecuteMsg::BondGateway {
gateway,
owner_signature,
amount,
} => try_bond_gateway(gateway, owner_signature, amount, info, env, deps),
ExecuteMsg::UnbondGateway {} => try_unbond_gateway(info, deps),
ExecuteMsg::TrackUnbondGateway { owner, amount } => {
try_track_unbond_gateway(&owner, amount, info, deps)
}
ExecuteMsg::TrackMigratedMixnode { owner } => try_track_migrate_mixnode(&owner, info, deps),
ExecuteMsg::TrackMigratedDelegation { owner, mix_id } => {
try_track_migrate_delegation(&owner, mix_id, info, deps)
ExecuteMsg::UpdateGatewayConfig { new_config } => {
try_update_gateway_config(new_config, info, deps)
}
ExecuteMsg::TransferOwnership { to_address } => {
try_transfer_ownership(to_address, info, deps)
}
ExecuteMsg::UpdateStakingAddress { to_address } => {
try_update_staking_address(to_address, info, deps)
}
_ => Err(VestingContractError::Other {
message: "the contract has been disabled".to_string(),
}),
}
}
@@ -61,10 +61,6 @@ pub trait MixnodeBondingAccount {
new_costs: MixNodeCostParams,
storage: &mut dyn Storage,
) -> Result<Response, VestingContractError>;
fn try_track_migrated_mixnode(
&self,
storage: &mut dyn Storage,
) -> Result<(), VestingContractError>;
}
pub trait GatewayBondingAccount {
@@ -44,9 +44,4 @@ pub trait DelegatingAccount {
amount: Coin,
storage: &mut dyn Storage,
) -> Result<(), VestingContractError>;
fn track_migrated_delegation(
&self,
mix_id: MixId,
storage: &mut dyn Storage,
) -> Result<(), VestingContractError>;
}
+2 -32
View File
@@ -18,9 +18,8 @@ use mixnet_contract_common::{
use vesting_contract_common::events::{
new_ownership_transfer_event, new_periodic_vesting_account_event,
new_staking_address_update_event, new_track_gateway_unbond_event,
new_track_migrate_mixnode_event, new_track_mixnode_pledge_decrease_event,
new_track_mixnode_unbond_event, new_track_reward_event, new_track_undelegation_event,
new_vested_coins_withdraw_event,
new_track_mixnode_pledge_decrease_event, new_track_mixnode_unbond_event,
new_track_reward_event, new_track_undelegation_event, new_vested_coins_withdraw_event,
};
use vesting_contract_common::{Account, PledgeCap, VestingContractError, VestingSpecification};
@@ -256,35 +255,6 @@ pub fn try_track_unbond_gateway(
Ok(Response::new().add_event(new_track_gateway_unbond_event()))
}
/// Track vesting mixnode being converted into the usage of liquid tokens. invoked by the mixnet contract after successful migration.
pub fn try_track_migrate_mixnode(
owner: &str,
info: MessageInfo,
deps: DepsMut<'_>,
) -> Result<Response, VestingContractError> {
if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
return Err(VestingContractError::NotMixnetContract(info.sender));
}
let account = account_from_address(owner, deps.storage, deps.api)?;
account.try_track_migrated_mixnode(deps.storage)?;
Ok(Response::new().add_event(new_track_migrate_mixnode_event()))
}
/// Track vesting delegation being converted into the usage of liquid tokens. invoked by the mixnet contract after successful migration.
pub fn try_track_migrate_delegation(
owner: &str,
mix_id: MixId,
info: MessageInfo,
deps: DepsMut<'_>,
) -> Result<Response, VestingContractError> {
if info.sender != MIXNET_CONTRACT_ADDRESS.load(deps.storage)? {
return Err(VestingContractError::NotMixnetContract(info.sender));
}
let account = account_from_address(owner, deps.storage, deps.api)?;
account.track_migrated_delegation(mix_id, deps.storage)?;
Ok(Response::new().add_event(new_track_migrate_mixnode_event()))
}
/// Bond a mixnode, sends [mixnet_contract_common::ExecuteMsg::BondMixnodeOnBehalf] to [crate::storage::MIXNET_CONTRACT_ADDRESS].
pub fn try_bond_mixnode(
mix_node: MixNode,
@@ -125,25 +125,4 @@ impl DelegatingAccount for Account {
self.save_balance(new_balance, storage)?;
Ok(())
}
fn track_migrated_delegation(
&self,
mix_id: MixId,
storage: &mut dyn Storage,
) -> Result<(), VestingContractError> {
let delegation = self.total_delegations_for_mix(mix_id, storage)?;
if delegation.is_zero() {
return Err(VestingContractError::NoSuchDelegation(
self.owner_address.clone(),
mix_id,
));
}
// treat the tokens that were used for delegation as 'withdrawn'
let current_withdrawn = self.load_withdrawn(storage)?;
self.save_withdrawn(current_withdrawn + delegation, storage)?;
// remove the delegation data since it no longer belongs to the vesting contract
self.remove_delegations_for_mix(mix_id, storage)
}
}
@@ -221,24 +221,4 @@ impl MixnodeBondingAccount for Account {
.add_message(update_mixnode_costs_msg)
.add_event(new_vesting_update_mixnode_cost_params_event()))
}
fn try_track_migrated_mixnode(
&self,
storage: &mut dyn Storage,
) -> Result<(), VestingContractError> {
let Some(pledge) = self.load_mixnode_pledge(storage)? else {
return Err(VestingContractError::NoBondFound(
self.owner_address().as_str().to_string(),
));
};
// treat the tokens that were used for bonding as 'withdrawn'
let current_withdrawn = self.load_withdrawn(storage)?;
self.save_withdrawn(current_withdrawn + pledge.amount.amount, storage)?;
// don't change the balance as the tokens are left in the mixnet contract
// remove the pledge data since it no longer belongs to the vesting account
self.remove_mixnode_pledge(storage)
}
}
+296 -54
View File
@@ -13,14 +13,14 @@ pub fn populate_vesting_periods(
#[cfg(test)]
mod tests {
use crate::contract::*;
use crate::storage::*;
use crate::support::tests::helpers::vesting_account_percent_fixture;
use crate::support::tests::helpers::{
init_contract, vesting_account_mid_fixture, vesting_account_new_fixture, TEST_COIN_DENOM,
};
use crate::traits::DelegatingAccount;
use crate::traits::GatewayBondingAccount;
use crate::traits::VestingAccount;
use crate::traits::{GatewayBondingAccount, MixnodeBondingAccount};
use crate::vesting::account::StorableVestingAccountExt;
use crate::vesting::populate_vesting_periods;
use contracts_common::signing::MessageSignature;
@@ -36,56 +36,162 @@ mod tests {
fn test_account_creation() {
let mut deps = init_contract();
let env = mock_env();
let info = mock_info("not_admin", &coins(1_000_000_000_000, TEST_COIN_DENOM));
let msg = ExecuteMsg::CreateAccount {
owner_address: "owner".to_string(),
staking_address: Some("staking".to_string()),
vesting_spec: None,
cap: Some(PledgeCap::Absolute(Uint128::from(100_000_000_000u128))),
};
// Try creating an account when not admin
let response = execute(deps.as_mut(), env.clone(), info, msg.clone());
assert!(response.is_err());
let info = mock_info("admin", &coins(1_000_000_000_000, TEST_COIN_DENOM));
let response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone());
let _response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone());
let created_account = load_account(Addr::unchecked("owner"), &deps.storage)
.unwrap()
.unwrap();
assert_eq!(
response,
Err(VestingContractError::Other {
message: "the contract has been disabled".to_string()
})
created_account.load_balance(&deps.storage).unwrap(),
// One was liquidated
Uint128::new(1_000_000_000_000)
);
// nothing is saved for "staking" account!
let created_account_test_by_staking =
load_account(Addr::unchecked("staking"), &deps.storage).unwrap();
assert!(created_account_test_by_staking.is_none());
// but we can stake on its behalf!
let stake_msg = ExecuteMsg::DelegateToMixnode {
on_behalf_of: Some("owner".to_string()),
mix_id: 42,
amount: coin(500, TEST_COIN_DENOM),
};
let response = execute(
deps.as_mut(),
env.clone(),
mock_info("staking", &[]),
stake_msg,
);
assert!(response.is_ok());
assert_eq!(
created_account.load_balance(&deps.storage).unwrap(),
// One was liquidated
Uint128::new(999_999_999_500)
);
// Try create the same account again
let response = execute(deps.as_mut(), env.clone(), info, msg);
assert!(response.is_err());
let account_again = vesting_account_new_fixture(&mut deps.storage, &env);
assert_eq!(created_account.storage_key(), 1);
assert_ne!(created_account.storage_key(), account_again.storage_key());
}
#[test]
fn test_ownership_transfer() {
let mut deps = init_contract();
let env = mock_env();
let mut env = mock_env();
let info = mock_info("owner", &[]);
let account = vesting_account_new_fixture(&mut deps.storage, &env);
let staker = account.staking_address().unwrap();
let msg = ExecuteMsg::TransferOwnership {
to_address: "new_owner".to_string(),
};
let response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone());
let _response = execute(deps.as_mut(), env.clone(), info.clone(), msg.clone()).unwrap();
let new_owner_account = load_account(Addr::unchecked("new_owner"), &deps.storage)
.unwrap()
.unwrap();
assert_eq!(
response,
Err(VestingContractError::Other {
message: "the contract has been disabled".to_string()
})
new_owner_account.load_balance(&deps.storage),
account.load_balance(&deps.storage)
);
// Check old account is gone
let old_owner_account = load_account(Addr::unchecked("owner"), &deps.storage).unwrap();
assert!(old_owner_account.is_none());
// Not the owner
let response = execute(deps.as_mut(), env.clone(), info, msg);
assert!(response.is_err());
// can't stake on behalf of the original owner anymore, but we can do it for the new one!
let stake_msg = ExecuteMsg::DelegateToMixnode {
on_behalf_of: Some("owner".to_string()),
mix_id: 42,
amount: coin(500, TEST_COIN_DENOM),
};
let response = execute(
deps.as_mut(),
env.clone(),
mock_info(staker.as_ref(), &[]),
stake_msg,
);
assert!(response.is_err());
let new_stake_msg = ExecuteMsg::DelegateToMixnode {
on_behalf_of: Some("new_owner".to_string()),
mix_id: 42,
amount: coin(500, TEST_COIN_DENOM),
};
let response = execute(
deps.as_mut(),
env.clone(),
mock_info(staker.as_ref(), &[]),
new_stake_msg,
);
assert!(response.is_ok());
let info = mock_info("new_owner", &[]);
let msg = ExecuteMsg::UpdateStakingAddress {
to_address: Some("new_staking".to_string()),
};
let _response = execute(deps.as_mut(), env.clone(), info, msg).unwrap();
let msg = ExecuteMsg::WithdrawVestedCoins {
amount: Coin {
amount: Uint128::new(1),
denom: TEST_COIN_DENOM.to_string(),
},
};
let info = mock_info("new_owner", &[]);
env.block.time = Timestamp::from_nanos(env.block.time.nanos() + 100_000_000_000_000_000);
let response = execute(deps.as_mut(), env.clone(), info, msg.clone());
assert!(response.is_ok());
let info = mock_info("owner", &[]);
let response = execute(deps.as_mut(), env.clone(), info, msg);
assert!(response.is_err());
}
#[test]
fn test_staking_account() {
let mut deps = init_contract();
let env = mock_env();
let mut env = mock_env();
let info = mock_info("staking", &[]);
let msg = ExecuteMsg::TransferOwnership {
to_address: "new_owner".to_string(),
};
let response = execute(deps.as_mut(), env.clone(), info.clone(), msg);
assert_eq!(
response,
Err(VestingContractError::Other {
message: "the contract has been disabled".to_string()
})
);
// Only owner can transfer
assert!(response.is_err());
let msg = ExecuteMsg::WithdrawVestedCoins {
amount: Coin {
amount: Uint128::new(1),
denom: "nym".to_string(),
},
};
env.block.time = Timestamp::from_nanos(env.block.time.nanos() + 100_000_000_000_000_000);
let response = execute(deps.as_mut(), env, info, msg);
// Only owner can withdraw
assert!(response.is_err());
}
#[test]
@@ -107,12 +213,31 @@ mod tests {
mock_info(original_staker.as_ref(), &[]),
stake_msg.clone(),
);
assert_eq!(
response,
Err(VestingContractError::Other {
message: "the contract has been disabled".to_string()
})
assert!(response.is_ok());
let info = mock_info("owner", &[]);
let msg = ExecuteMsg::UpdateStakingAddress {
to_address: Some("new_staking".to_string()),
};
let _response = execute(deps.as_mut(), env.clone(), info, msg).unwrap();
// the old staking account can't do any staking anymore!
let response = execute(
deps.as_mut(),
env.clone(),
mock_info(original_staker.as_ref(), &[]),
stake_msg.clone(),
);
assert!(response.is_err());
// but the new one can
let response = execute(
deps.as_mut(),
env.clone(),
mock_info("new_staking", &[]),
stake_msg,
);
assert!(response.is_ok());
}
#[test]
@@ -120,27 +245,66 @@ mod tests {
let mut deps = init_contract();
let env = mock_env();
let amount = coin(1000000000, "unym");
let amount1 = coin(1000000000, "unym");
let amount2 = coin(100, "unym");
// create the accounts
let msg = ExecuteMsg::CreateAccount {
let msg1 = ExecuteMsg::CreateAccount {
owner_address: "vesting1".to_string(),
staking_address: None,
vesting_spec: None,
cap: None,
};
let response = execute(
let res1 = execute(
deps.as_mut(),
env.clone(),
mock_info("admin", &[amount.clone()]),
msg,
mock_info("admin", &[amount1.clone()]),
msg1,
);
assert!(res1.is_ok());
let msg2 = ExecuteMsg::CreateAccount {
owner_address: "vesting2".to_string(),
staking_address: None,
vesting_spec: None,
cap: None,
};
let res2 = execute(
deps.as_mut(),
env.clone(),
mock_info("admin", &[amount2.clone()]),
msg2,
);
assert!(res2.is_ok());
let vesting1 = try_get_vesting_coins("vesting1", None, env.clone(), deps.as_ref()).unwrap();
assert_eq!(vesting1, amount1);
let vesting2 = try_get_vesting_coins("vesting2", None, env.clone(), deps.as_ref()).unwrap();
assert_eq!(vesting2, amount2);
let staking_address_change = ExecuteMsg::UpdateStakingAddress {
to_address: Some("vesting1".to_string()),
};
let res = execute(
deps.as_mut(),
env.clone(),
mock_info("vesting2", &[]),
staking_address_change,
);
assert_eq!(
response,
Err(VestingContractError::Other {
message: "the contract has been disabled".to_string()
})
Err(VestingContractError::StakingAccountExists(
"vesting1".to_string()
)),
res
);
// ensure nothing has changed!
let vesting1 = try_get_vesting_coins("vesting1", None, env.clone(), deps.as_ref()).unwrap();
assert_eq!(vesting1, amount1);
let vesting2 = try_get_vesting_coins("vesting2", None, env, deps.as_ref()).unwrap();
assert_eq!(vesting2, amount2);
}
#[test]
@@ -402,13 +566,63 @@ mod tests {
};
let info = mock_info("admin", &coins(1_000_000_000_000, TEST_COIN_DENOM));
let response = execute(deps.as_mut(), env.clone(), info, msg);
assert_eq!(
response,
Err(VestingContractError::Other {
message: "the contract has been disabled".to_string()
})
let _response = execute(deps.as_mut(), env.clone(), info, msg);
let account = load_account(Addr::unchecked("owner"), &deps.storage)
.unwrap()
.unwrap();
// Try delegating too much
let err = account.try_delegate_to_mixnode(
1,
Coin {
amount: Uint128::new(1_000_000_000_001),
denom: TEST_COIN_DENOM.to_string(),
},
&env,
&mut deps.storage,
);
assert!(err.is_err());
let ok = account.try_delegate_to_mixnode(
1,
Coin {
amount: Uint128::new(90_000_000_000),
denom: TEST_COIN_DENOM.to_string(),
},
&env,
&mut deps.storage,
);
assert!(ok.is_ok());
// Fails due to delegation locked delegation cap
let ok = account.try_delegate_to_mixnode(
1,
Coin {
amount: Uint128::new(20_000_000_000),
denom: TEST_COIN_DENOM.to_string(),
},
&env,
&mut deps.storage,
);
assert!(ok.is_err());
let balance = account.load_balance(&deps.storage).unwrap();
assert_eq!(balance, Uint128::new(910000000000));
// Try delegating too much againcalca
let err = account.try_delegate_to_mixnode(
1,
Coin {
amount: Uint128::new(500_000_000_001),
denom: TEST_COIN_DENOM.to_string(),
},
&env,
&mut deps.storage,
);
assert!(err.is_err());
let total_delegations = account.total_delegations_for_mix(1, &deps.storage).unwrap();
assert_eq!(Uint128::new(90_000_000_000), total_delegations);
}
#[test]
@@ -435,24 +649,52 @@ mod tests {
amount: Uint128::new(40),
},
};
// Try delegating too much
let err = account.try_bond_mixnode(
mix_node.clone(),
cost_params.clone(),
MessageSignature::from(vec![1, 2, 3]),
Coin {
amount: Uint128::new(1_000_000_000_001),
denom: TEST_COIN_DENOM.to_string(),
},
&env,
&mut deps.storage,
);
assert!(err.is_err());
let msg = ExecuteMsg::BondMixnode {
mix_node,
cost_params,
owner_signature: vec![1, 2, 3, 4].into(),
amount: Coin {
let ok = account.try_bond_mixnode(
mix_node.clone(),
cost_params.clone(),
MessageSignature::from(vec![1, 2, 3]),
Coin {
amount: Uint128::new(90_000_000_000),
denom: TEST_COIN_DENOM.to_string(),
},
};
let info = mock_info(account.owner_address.as_str(), &[]);
let response = execute(deps.as_mut(), env.clone(), info, msg);
assert_eq!(
response,
Err(VestingContractError::Other {
message: "the contract has been disabled".to_string()
})
&env,
&mut deps.storage,
);
assert!(ok.is_ok());
let balance = account.load_balance(&deps.storage).unwrap();
assert_eq!(balance, Uint128::new(910_000_000_000));
// Try delegating too much again
let err = account.try_bond_mixnode(
mix_node,
cost_params,
MessageSignature::from(vec![1, 2, 3]),
Coin {
amount: Uint128::new(10_000_000_001),
denom: TEST_COIN_DENOM.to_string(),
},
&env,
&mut deps.storage,
);
assert!(err.is_err());
let pledge = account.load_mixnode_pledge(&deps.storage).unwrap().unwrap();
assert_eq!(Uint128::new(90_000_000_000), pledge.amount().amount);
}
#[test]
@@ -46,4 +46,4 @@ If you have successfully completed KYC on Synaps earlier and provided your Nym w
Using the Nym network is free for now, but user fees will be introduced in the future and those will be paid in NYM tokens, so set them aside! And until then, you can contribute to running our decentralized infrastructure and earn mix mining rewards by setting up Nym mix nodes and bonding your tokens to them. You don't need to run nodes to contribute to the security and performance of the Nym network though. You can pledge your trust in someone else's node by delegating your NYM tokens to it, and receive a share of their mix mining rewards.
You can find out more about how staking works in [this](https://www.youtube.com/watch?v=PcNGcTwlm0I) video from our Chief Scientist Claudia Diaz and if you need help setting up your mix node, choosing one to delegate your tokens to, or anything else, our community is there to help on [Discord](https://discord.gg/nymproject) and [Telegram](https://t.me/nymchan).
You can find out more about how staking works in [this](https://www.youtube.com/watch?v=PcNGcTwlm0I) video from our Chief Scientist Claudia Diaz and if you need help setting up your mix node, choosing one to delegate your tokens to, or anything else, our community is there to help on [Discord](https://nymtech.net/go/discord) and [Telegram](https://t.me/nymchan).
@@ -39,5 +39,5 @@ Follow this [video](https://youtu.be/quj8H2qeOwY?t=97) to see the steps on Teleg
**Now your Telegram runs over NymConnect.**
NymConnect is currently available for several applications and service providers. Support for more apps is on the way. For any bug reports or feedback please reach out to us on Telegram or our [Discord](http://discord.gg/FaTJb8q8) server.
NymConnect is currently available for several applications and service providers. Support for more apps is on the way. For any bug reports or feedback please reach out to us on Telegram or our [Discord](https://nymtech.net/go/discord) server.
+1 -1
View File
@@ -11,7 +11,7 @@ Since these docs autogenerate command output and import docs from binaries in `t
Changes merged to `master` will be autodeployed to the production site.
### Contributing a new translation
To contribute tranlsations in a new language, please get in touch via [Matrix](https://matrix.to/#/#general:nymtech.chat) or [Discord](https://discord.gg/nymproject).
To contribute tranlsations in a new language, please get in touch via [Matrix](https://matrix.to/#/#general:nymtech.chat) or [Discord](https://nymtech.net/go/discord).
### Variables
There are some variables that are shared across the entire docs site, such as the current latest software version.
@@ -36,7 +36,7 @@ Create an account on Sandbox using the nym-cli:
```./nym-cli --config-env-file <path-to>sandbox.env account create```
You will need `nymt` funds sent to this account. Get in touch via Nym [Telegram](https://t.me/nymchan) or [Discord](https://discord.gg/nymproject) and we can send them to you.
You will need `nymt` funds sent to this account. Get in touch via Nym [Telegram](https://t.me/nymchan) or [Discord](https://nymtech.net/go/discord) and we can send them to you.
Next, you init the nym-client with the enabled credentials mode set to true:
+5 -5
View File
@@ -42,17 +42,17 @@ This page displays a full list of all the changes during our release cycle from
### Features
- [Remove the `nym-mixnode` and `nym-gateway` binaries from the CI upload builds action](https://github.com/nymtech/nym/pull/4693):
- [Remove the `nym-mixnode` and `nym-gateway` binaries from the CI upload builds action](https://github.com/nymtech/nym/pull/4693):
- [Add an early return in `parse_raw_str_logs` for empty raw log strings.](https://github.com/nymtech/nym/pull/4686): This accommodates for the v50 + chain upgrade.
- [Bump braces from `3.0.2` to `3.0.3` in `/wasm/mix-fetch/internal-dev`](https://github.com/nymtech/nym/pull/4672): Version update of [braces](https://github.com/micromatch/braces)
- [Bump braces from `3.0.2` to `3.0.3` in `/clients/native/examples/js-examples/websocket`](https://github.com/nymtech/nym/pull/4663): Version update of [braces](https://github.com/micromatch/braces).
- [Bump braces from `3.0.2` to `3.0.3` in `/nym-api/tests`](https://github.com/nymtech/nym/pull/4659): Version update of [braces](https://github.com/micromatch/braces).
- [Bump braces from `3.0.2` to `3.0.3` in `/docker/typescript_client/upload_contract`](https://github.com/nymtech/nym/pull/4658): Version update of [braces](https://github.com/micromatch/braces).
- [Bump `ws` from `8.13.0` to `8.17.1` in `/wasm/client/internal-dev-node`](https://github.com/nymtech/nym/pull/4665): Version update of [`ws`](https://github.com/websockets/ws).
- [Bump `ws` from `8.14.2` to `8.17.1` in `/sdk/typescript/packages/nodejs-client`](https://github.com/nymtech/nym/pull/4662): Version update of [`ws`](https://github.com/websockets/ws).
- [Bump `ws` from `8.13.0` to `8.17.1` in `/wasm/client/internal-dev-node`](https://github.com/nymtech/nym/pull/4665): Version update of [`ws`](https://github.com/websockets/ws).
- [Bump `ws` from `8.14.2` to `8.17.1` in `/sdk/typescript/packages/nodejs-client`](https://github.com/nymtech/nym/pull/4662): Version update of [`ws`](https://github.com/websockets/ws).
- [Add expiry returned on import](https://github.com/nymtech/nym/pull/4670): We need to return the expiry on import for desktop daemon `nym-vpnd`.
- [New clippy lints](https://github.com/nymtech/nym/pull/4660)
- [Remove `nym-connect` directory](https://github.com/nymtech/nym/pull/4643): Since the `nym-vpn` has superseded `nym-connect`, remove `nym-connect` from the repo.
- [Remove `nym-connect` directory](https://github.com/nymtech/nym/pull/4643): Since the `nym-vpn` has superseded `nym-connect`, remove `nym-connect` from the repo.
- [Remove old PR template](https://github.com/nymtech/nym/pull/4639)
### Bugfix
@@ -62,7 +62,7 @@ This page displays a full list of all the changes during our release cycle from
### Operators Guide updates
- [Node description guide](nodes/configuration.md#node-description): Steps to add self-description to `nym-node` and query this information from any node.
- [Web Secure Socket (WSS) guide and reverse proxy update](/nodes/proxy-configuration.md), PR [here](https://github.com/nymtech/nym/pull/4694): A guide to setup `nym-node` in a secure fashion, using WSS via Nginx and Certbot. Landing page (reversed proxy) is updated and simplified.
- [Web Secure Socket (WSS) guide and reverse proxy update](nodes/proxy-configuration.md), PR [here](https://github.com/nymtech/nym/pull/4694): A guide to setup `nym-node` in a secure fashion, using WSS via Nginx and Certbot. Landing page (reversed proxy) is updated and simplified.
---
@@ -8,3 +8,19 @@
[HostSailor](https://hostsailor.com),"USA","Yes, based on ticket","Yes","The IPv6 setup needs custom research and is not documented","05/2024"
[Misaka](https://www.misaka.io/),"South Africa","Yes, native support","No","Very Expensive","05/2024"
[IsHosting](https://ishosting.com/en),"Brazil, Netherlands","Yes, based on ticket","Yes","Expensive","05/2024"
[AlexHost](https://alexhost.com),"Moldova, Bulgaria, Sweden, Netherlands","Yes, on by default","Yes","They allow TOR Bridges, Relays. Exit nodes are only allowed on dedicated servers (prices start from 26 EUR)","07/2024"
[iHostArt](https://ihostart.com),"Romania","Yes, on by default","Yes","Super permissive provider. They do allow Tor Exit/Relay/Bridge. Pro-free speech etc. Recently, IPv6 geolocation was set to North Korea, so be aware.","07/2024"
[Incognet](https://incognet.io),"Netherlands and USA","Yes, on by default","Yes","They allow Tor exit nodes but you must adhere to their rules https://incognet.io/tor-exits","07/2024"
[vSys Host](https://vsys.host),"Ukraine, Netherlands, USA","Yes, on by default","Yes","Pretty permissive provider registered in Ukraine. Should allow Relay/Exit nodes but nothing in T&C, so better double check.","07/2024"
[LiteServer](https://liteserver.nl),"Netherlands","Yes, on by default","Yes","Very reliable Dutch provider. They do allow Relay nodes but for Exit nodes you need to contact them. Always check T&C https://liteserver.nl/legal","07/2024"
[TerraHost](https://terrahost.no),"Norway","Yes, on by default","Yes","Very reliable Norwegian provider. Only allow exit nodes on Dedicated servers subject to certain caveats (you must open a ticket). Always check T&C https://terrahost.no/avtalebetingelser","07/2024"
[Mevspace](https://mevspace.com),"Poland","Yes, on by default","Yes","Flexible Polish providers with 3 DCs in Poland. They do allow Tor Exit nodes but you may need a dedicated server for this. Make sure you open a ticket to check. As of today's date, they have 48h for 1 EUR tariff","07/2024"
[Hostiko](https://hostiko.com.ua),"Ukraine, Germany","Yes, on by default","Yes","Ukrainian provider. They allow Exit nodes on Germany boxes but limit the bandwidth, you also have to restrict certain ports like 25 and 587. Make sure you open a ticket.","07/2024"
[Hostslick](https://hostslick.com),"Netherlands, Germany","Yes, on by default","Yes","Good amount of bandwidth for the price. Make sure you open the ticket if you want to run Exit node","07/2024"
[RDP](https://rdp.sh),"Netherlands, USA, Poland","Yes, on by default","Yes","German provider. Exit nodes are allowed, policy is here https://rdp.sh/docs/faq/tor ports 25,465,587 must be closed. Make sure you open a ticket before running an exit node.","07/2024"
1 **ISP** **Locations** **Public IPv6** **Crypto Payments** **Comments** **Last Updated**
8 [HostSailor](https://hostsailor.com) USA Yes, based on ticket Yes The IPv6 setup needs custom research and is not documented 05/2024
9 [Misaka](https://www.misaka.io/) South Africa Yes, native support No Very Expensive 05/2024
10 [IsHosting](https://ishosting.com/en) Brazil, Netherlands Yes, based on ticket Yes Expensive 05/2024
11 [AlexHost](https://alexhost.com) Moldova, Bulgaria, Sweden, Netherlands Yes, on by default Yes They allow TOR Bridges, Relays. Exit nodes are only allowed on dedicated servers (prices start from 26 EUR) 07/2024
12 [iHostArt](https://ihostart.com) Romania Yes, on by default Yes Super permissive provider. They do allow Tor Exit/Relay/Bridge. Pro-free speech etc. Recently, IPv6 geolocation was set to North Korea, so be aware. 07/2024
13 [Incognet](https://incognet.io) Netherlands and USA Yes, on by default Yes They allow Tor exit nodes but you must adhere to their rules https://incognet.io/tor-exits 07/2024
14 [vSys Host](https://vsys.host) Ukraine, Netherlands, USA Yes, on by default Yes Pretty permissive provider registered in Ukraine. Should allow Relay/Exit nodes but nothing in T&C, so better double check. 07/2024
15 [LiteServer](https://liteserver.nl) Netherlands Yes, on by default Yes Very reliable Dutch provider. They do allow Relay nodes but for Exit nodes you need to contact them. Always check T&C https://liteserver.nl/legal 07/2024
16 [TerraHost](https://terrahost.no) Norway Yes, on by default Yes Very reliable Norwegian provider. Only allow exit nodes on Dedicated servers subject to certain caveats (you must open a ticket). Always check T&C https://terrahost.no/avtalebetingelser 07/2024
17 [Mevspace](https://mevspace.com) Poland Yes, on by default Yes Flexible Polish providers with 3 DCs in Poland. They do allow Tor Exit nodes but you may need a dedicated server for this. Make sure you open a ticket to check. As of today's date, they have 48h for 1 EUR tariff 07/2024
18 [Hostiko](https://hostiko.com.ua) Ukraine, Germany Yes, on by default Yes Ukrainian provider. They allow Exit nodes on Germany boxes but limit the bandwidth, you also have to restrict certain ports like 25 and 587. Make sure you open a ticket. 07/2024
19 [Hostslick](https://hostslick.com) Netherlands, Germany Yes, on by default Yes Good amount of bandwidth for the price. Make sure you open the ticket if you want to run Exit node 07/2024
20 [RDP](https://rdp.sh) Netherlands, USA, Poland Yes, on by default Yes German provider. Exit nodes are allowed, policy is here https://rdp.sh/docs/faq/tor ports 25,465,587 must be closed. Make sure you open a ticket before running an exit node. 07/2024
21
22
23
24
25
26
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

@@ -11,7 +11,7 @@ This document presents an initiative to further support Nyms mission of allow
All the technical changes on the side of Nym nodes - ***Project Smoosh*** - are described in the [FAQ section](../archive/faq/smoosh-faq.md).
```admonish warning
Nym core team cannot provide comprehensive legal advice across all jurisdictions. Knowledge and experience with the legalities are being built up with the help of our counsel and with you, the community of Nym node operators. We encourage Nym node operators to join the operator channels ([Element](https://matrix.to/#/#operators:nymtech.chat), [Discord](https://discord.gg/nymproject), [Telegram](https://t.me/nymchan_help_chat)) to share best practices and experiences.
Nym core team cannot provide comprehensive legal advice across all jurisdictions. Knowledge and experience with the legalities are being built up with the help of our counsel and with you, the community of Nym node operators. We encourage Nym node operators to join the operator channels ([Element](https://matrix.to/#/#operators:nymtech.chat), [Discord](https://nymtech.net/go/discord), [Telegram](https://t.me/nymchan_help_chat)) to share best practices and experiences.
```
+18 -4
View File
@@ -28,16 +28,29 @@ You can bond your `nym-node` via the Desktop wallet.
- To find out your `nym-node` details, run `./nym-node bonding-information --id <ID>`
- To get a correct host address, run `echo "$(curl -4 https://ifconfig.me)"`
2. Open the box called `Show advanced options` and make sure all your ports are set correctly, like the values in this table:
2. Enter the `Amount`, `Operating cost` and `Profit margin` and press `Next`.
| Node type | Port name | Correct port value |
| :-- | :-- | :-- |
| Mixnode | Mix port | `1789` |
| Mixnode | Verloc port | `1790` |
| Mixnode | HTTP api port (picture below) | `8080` |
| Gateway (entry & exit) | Mix port | `1789` |
| Gateway (entry & exit) | Client WS API port | `9000` |
3. You will be asked to run a `sign` command with your `nym-node` - copy and paste the long signature as the value of `--contract-msg` and run it.
- If you bonding `nym-node --mode mixnode` through *Bond mixnode* desktop wallet menu, change manually *HTTP api port* value from deprecated `8000` to `8080` - a generic `nym-node` HTTP port (for all modes).
![](../images/wallet-screenshots/new_http_port.png)
3. Enter the `Amount`, `Operating cost` and `Profit margin` and press `Next`.
4. You will be asked to run a `sign` command with your `nym-node` - copy and paste the long signature as the value of `--contract-msg` and run it.
```
./nym-node sign --contract-msg <PAYLOAD_GENERATED_BY_THE_WALLET>
```
4. Copy the resulting signature string and paste it into the wallet nodal, press `Next` and confirm the transaction:
5. Copy the resulting signature string and paste it into the wallet nodal, press `Next` and confirm the transaction:
```sh
# This is just an example, copy the one from your process
@@ -46,9 +59,10 @@ You can bond your `nym-node` via the Desktop wallet.
```
![Paste Signature](../images/wallet-screenshots/wallet-sign.png)
*This image is just an example, copy-paste your own base58-encoded signature*
5. Your node will now be bonded and ready to recieve traffic, latest at the beginning of the next epoch (at most 1 hour)
6. Your node will now be bonded and ready to recieve traffic, latest at the beginning of the next epoch (at most 1 hour)
If everything worked, you'll see your node running on the either the [Sandbox testnet network explorer](https://sandbox-explorer.nymtech.net) or the [mainnet network explorer](https://explorer.nymtech.net), depending on which network you're running.
@@ -200,9 +200,58 @@ systemctl status <NODE>.service
systemctl daemon-reload
```
This lets your operating system know it's ok to reload the service configuration. Then restart your `<NODE>`.
This lets your operating system know it's OK to reload the service configuration. Then restart your `<NODE>`.
## Backup a node
Anything can happen to the server on which your node is running. To back up your `nym-node` keys and configuration protects the operators against the negative impact of unexpected events. To restart your node on another server, two essential pieces are needed:
1. Node keys to initialise the same node on a new VPS
2. Access to the bonding Nym account (wallet seeds) to edit the IP on smart contract
Assuming that everyone access their wallets from local machine and does *not* store their seeds on VPS, point \#2 should be a given. To backup your `nym-node` keys and configuration in the easiest way possible, copy the entire config directory `.nym` from your VPS to your local desktop, using a special copy command `scp`:
1. Create a directory where you want to store your backup
```sh
mkdir -pv <PATH_TO_TARGET_DIRECTORY>
```
2. Copy configuration folder `.nym` from your VPS to your newly created backup directory:
```sh
scp -r <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:~/.nym/nym-nodes/<YOUR_NODE_ID> <PATH_TO_TARGET_DIRECTORY>
```
3. The `scp` command should print logs, an operator can see directly whether it was successful or if it encountered any error. However, double check that all your needed configuration is in the backup directory.
## Restoring a node
In case your VPS shut down and you have a [backup](#backup-a-node) of your node keys and access to your bonding wallet, you can easily restore your node on another server without losing your delegation.
1. On VPS: Do all [preliminary steps](preliminary-steps.md) needed to run a `nym-node`.
2. On VPS: Create a `.nym/nym-nodes` configuration folder:
```sh
mkdir -pv ~/.nym/nym-nodes
```
3. From machine where your node is backed up (usually local desktop): Copy the folder with your node keys and configuration to the newly created folder on your VPS using `scp` command. Make sure to grab the entire `nym-node` configuration folder, which is called after your local `nym-node` ID, the `-r` flag will take care of all sub-directories and their content:
```sh
scp -r <LOCAL_NODE_CONFIGURATION_FOLDER> <VPS_USER_NAME>@<VPS_HOST_ADDRESS>:~/.nym/nym-nodes/
```
4. The `scp` command should print logs, an operator can see directly whether it was successful or if it encountered any error. However, double check that all your needed configuration is in the target directory `.nym/nym-nodes` on your VPS.
5. Configure your node on the new VPS:
* Edit `~/.nym/nym-nodes/<ID>/config/config.toml` config with the new listening address IP - it's the one under the header `[host]`, called `public_ips = ['<YOUR_PUBLIC_IP>',]` and add your new location (field `location = <LOCATION>`, formats like: 'Jamaica', or two-letter alpha2 (e.g. 'JM'), three-letter alpha3 (e.g. 'JAM') or three-digit numeric-3 (e.g. '388') can be provided). You can see your IP by running a command `echo "$(curl -4 https://ifconfig.me)"`.
* Try to run the node and see if everything works.
* Setup the [systemd](#systemd) automation (don't forget to add the [terms and conditions flag](setup.md#terms--conditions)) to `ExecStart` command, reload the daemon and run the service.
6. And finally change the node smart contract info via the wallet interface. Open Nym Wallet, go to *Bonding*, open *Gateway Settings* or *Mixnode Settings* and change *Host* value to the new `nym-node` IP address. Otherwise the keys will point to the old IP address in the smart contract, and the node will not be able to be connected, and it will fail up-time checks, returning zero performance.
## Moving a node
In case of a need to move a Nym Node from one machine to another and avoiding to lose the delegation, here are few steps how to do it.
@@ -224,11 +273,20 @@ mkdir ~/.nym/nym-nodes
scp -r -3 <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:~/.nym/nym-nodes <TARGET_USER_NAME>@<TARGET_HOST_ADDRESS>:~/.nym/nym-nodes/
```
**On new/target machine**
**On new/target VPS**
* Edit `~/.nym/nym-nodes/<ID>/config/config.toml` config with the new listening address IP.
* Setup the [systemd](#systemd) automation, reload the daemon and run the service, or just simply run the node if you don't use automation
* Change the node smart contract info via the wallet interface. Otherwise the keys will point to the old IP address in the smart contract, and the node will not be able to be connected, and it will fail up-time checks.
* Edit `~/.nym/nym-nodes/<ID>/config/config.toml` config with the new listening address IP - it's the one under the header `[host]`, called `public_ips = ['<YOUR_PUBLIC_IP>',]` and add your new location (field `location = <LOCATION>`, formats like: 'Jamaica', or two-letter alpha2 (e.g. 'JM'), three-letter alpha3 (e.g. 'JAM') or three-digit numeric-3 (e.g. '388') can be provided). You can see your IP by running a command `echo "$(curl -4 https://ifconfig.me)"`.
* Try to run the node and see if everything works.
* Setup the [systemd](#systemd) automation (don't forget to add the [terms and conditions flag](setup.md#terms--conditions)) to `ExecStart` command, reload the daemon and run the service. If you want to use the exact same service config file, you can also copy it from one VPS to another following the same logic:
```sh
scp -r -3 <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:/etc/systemd/system/nym-node.service <TARGET_USER_NAME>@<TARGET_HOST_ADDRESS>:/etc/systemd/system/nym-node.service
```
**In your desktop wallet**
* Change the node smart contract info via the wallet interface. Open Nym Wallet, go to *Bonding*, open *Gateway Settings* or *Mixnode Settings* and change *Host* value to the new `nym-node` IP address. Otherwise the keys will point to the old IP address in the smart contract, and the node will not be able to be connected, and it will fail up-time checks, returning zero performance.
Make sure to stop the old node.
## Rename node local ID
@@ -282,12 +340,12 @@ less ~/.nym/nym-nodes/default-nym-node/config/config.toml
## Ports
All `<NODE>`-specific port configuration can be found in `$HOME/.nym/<NODE>/<YOUR_ID>/config/config.toml`. If you do edit any port configs, remember to restart your client and node processes.
### Nym Node: Minode mode port reference
### Nym Node: Mixnode mode port reference
| Default port | Use |
| ------------ | ------------------------- |
| `1789` | Listen for Mixnet traffic |
| `1790` | Listen for VerLoc traffic |
| `8000` | Metrics http API endpoint |
| `8080` | Metrics http API endpoint |
### Nym Node: Gateway modes port reference
@@ -592,15 +592,28 @@ server {
# WSS configuration
server {
listen ${port_value};
listen ${port_value} ssl http2;
listen [::]:${port_value} ssl http2;
server_name ${host_name};
ssl_certificate /etc/letsencrypt/live/${host_name}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/${host_name}/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
location / {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, HEAD';
add_header 'Access-Control-Allow-Headers' '*';
add_header 'Access-Control-Allow-Headers' '*';
proxy_http_version 1.1;
proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header X-Forwarded-For \$remote_addr;
@@ -699,9 +712,9 @@ We made the landing page customization directory in [*Preliminary steps*](#preli
3. Configure your site to work with `nginx`. Open a new text file `/etc/nginx/sites-available/<HOSTNAME>` and paste the block below. Don't forget to insert your correct values.
~~~admonish example collapsible=true title="Site configuration `/etc/nginx/sites-available/<HOSTNAME>`"
```bash
#################################################################
# EXCHANGE ALL <HOSTNAME>, <WSS_PORT> AND <PATH_TO> VARIABLES ! #
#################################################################
#####################################################
# EXCHANGE ALL <HOSTNAME> & <WSS_PORT> VARIABLES ! #
####################################################
# Reversed proxy configuration for landing page
server {
@@ -744,15 +757,27 @@ server {
# WSS configuration
server {
listen <WSS_PORT>;
listen <WSS_PORT> ssl http2;
listen [::]:<WSS_PORT> ssl http2;
server_name <HOSTNAME>;
ssl_certificate /etc/letsencrypt/live/<HOSTNAME>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<HOSTNAME>/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
location / {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, HEAD';
add_header 'Access-Control-Allow-Headers' '*';
add_header 'Access-Control-Allow-Headers' '*';
proxy_http_version 1.1;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
proxy_set_header X-Forwarded-For $remote_addr;
@@ -230,11 +230,6 @@ Run the node with custom `--id` without initialising:
./nym-node run --id <ID> --deny-init --mode mixnode --accept-operator-terms-and-conditions
```
Run the node with custom `--id` without initialising:
```sh
./nym-node run --id <ID> --deny-init --mode entry-gateway --accept-operator-terms-and-conditions
```
### Migrate
```admonish caution
@@ -11,6 +11,6 @@ If the `/dev/sda` partition is almost full, try pruning some of the `.gz` syslog
## Where can I get more help?
The fastest way to reach one of us or get a help from the community, visit our [Telegram Node Setup Help Chat](https://t.me/nymchan_help_chat) or head to our [Discord](https://discord.gg/nymproject).
The fastest way to reach one of us or get a help from the community, visit our [Telegram Node Setup Help Chat](https://t.me/nymchan_help_chat) or head to our [Discord](https://nymtech.net/go/discord).
For more tech heavy question join our [Matrix core community channel](https://matrix.to/#/#general:nymtech.chat), where you can meet other builders and Nym core team members.
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "explorer-api"
version = "1.1.36"
version = "1.1.37"
edition = "2021"
license.workspace = true
+4 -4
View File
@@ -7,10 +7,10 @@ import { TwitterIcon } from '../icons/socials/TwitterIcon'
import { DiscordIcon } from '../icons/socials/DiscordIcon'
// socials
export const TELEGRAM_LINK = 'https://t.me/nymchan'
export const TWITTER_LINK = 'https://twitter.com/nymproject'
export const GITHUB_LINK = 'https://github.com/nymtech'
export const DISCORD_LINK = 'https://discord.gg/nymproject'
export const TELEGRAM_LINK = 'https://nymtech.net/go/telegram';
export const TWITTER_LINK = 'https://nymtech.net/go/x';
export const GITHUB_LINK = 'https://nymtech.net/go/github';
export const DISCORD_LINK = 'https://nymtech.net/go/discord';
export const Socials: FCWithChildren<{ isFooter?: boolean }> = ({
isFooter = false,
+4 -4
View File
@@ -7,10 +7,10 @@ import { TwitterIcon } from '../icons/socials/TwitterIcon';
import { DiscordIcon } from '../icons/socials/DiscordIcon';
// socials
export const TELEGRAM_LINK = 'https://t.me/nymchan';
export const TWITTER_LINK = 'https://twitter.com/nymproject';
export const GITHUB_LINK = 'https://github.com/nymtech';
export const DISCORD_LINK = 'https://discord.gg/nymproject';
export const TELEGRAM_LINK = 'https://nymtech.net/go/telegram';
export const TWITTER_LINK = 'https://nymtech.net/go/x';
export const GITHUB_LINK = 'https://nymtech.net/go/github';
export const DISCORD_LINK = 'https://nymtech.net/go/discord';
export const Socials: FCWithChildren<{ isFooter?: boolean }> = ({ isFooter }) => {
const theme = useTheme();
-3
View File
@@ -1,7 +1,6 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::commands::upgrade_helpers;
use log::{error, info};
use nym_config::{save_formatted_config_to_file, OptionalSet};
use nym_crypto::asymmetric::identity;
@@ -122,8 +121,6 @@ pub(crate) fn ensure_correct_bech32_prefix(address: &AccountId) -> Result<(), Ga
}
pub(crate) fn try_load_current_config(id: &str) -> Result<Config, GatewayError> {
upgrade_helpers::try_upgrade_config(id)?;
Config::read_from_default_path(id).map_err(|err| {
error!(
"Failed to load config for {id}. Are you sure you have run `init` before? (Error was: {err})",
+8 -2
View File
@@ -2,9 +2,10 @@
// SPDX-License-Identifier: GPL-3.0-only
use crate::Cli;
use anyhow::bail;
use clap::CommandFactory;
use clap::Subcommand;
use log::warn;
use log::{error, warn};
use nym_bin_common::completions::{fig_generate, ArgShell};
use std::io::IsTerminal;
use std::time::Duration;
@@ -17,7 +18,6 @@ pub(crate) mod run;
pub(crate) mod setup_ip_packet_router;
pub(crate) mod setup_network_requester;
pub(crate) mod sign;
mod upgrade_helpers;
#[derive(Subcommand)]
pub(crate) enum Commands {
@@ -55,6 +55,12 @@ pub(crate) enum Commands {
pub(crate) async fn execute(args: Cli) -> anyhow::Result<()> {
let bin_name = "nym-gateway";
if !args.force_run {
let msg = "standalone gateways have been deprecated - please migrate to a `nym-node` via `nym-node migrate gateways` command";
error!("{msg}");
bail!("{msg}")
}
warn!("standalone gateways have been deprecated - please consider migrating it to a `nym-node` via `nym-node migrate gateway` command");
if std::io::stdout().is_terminal() {
// if user is running it in terminal session,
-153
View File
@@ -1,153 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use log::info;
use nym_gateway::config::old_config_v1_1_20::ConfigV1_1_20;
use nym_gateway::config::old_config_v1_1_28::ConfigV1_1_28;
use nym_gateway::config::old_config_v1_1_29::ConfigV1_1_29;
use nym_gateway::config::old_config_v1_1_31::ConfigV1_1_31;
use nym_gateway::config::old_config_v1_1_36::ConfigV1_1_36;
use nym_gateway::config::{default_config_filepath, Config};
use nym_gateway::error::GatewayError;
fn try_upgrade_v1_1_20_config(id: &str) -> Result<bool, GatewayError> {
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
// explicitly load it as v1.1.20 (which is incompatible with the current, i.e. 1.1.21+)
let Ok(old_config) = ConfigV1_1_20::load_from_file(id) else {
// if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(false);
};
info!("It seems the gateway is using <= v1.1.20 config template.");
info!("It is going to get updated to the current specification.");
let updated_step1: ConfigV1_1_28 = old_config.into();
let updated_step2: ConfigV1_1_29 = updated_step1.into();
let updated_step3: ConfigV1_1_31 = updated_step2.into();
let updated_step4: ConfigV1_1_36 = updated_step3.into();
let updated: Config = updated_step4.into();
updated
.save_to_default_location()
.map_err(|err| GatewayError::ConfigSaveFailure {
path: default_config_filepath(id),
id: id.to_string(),
source: err,
})?;
Ok(true)
}
fn try_upgrade_v1_1_28_config(id: &str) -> Result<bool, GatewayError> {
// explicitly load it as v1.1.28 (which is incompatible with the current, i.e. 1.1.29+)
let Ok(old_config) = ConfigV1_1_28::read_from_default_path(id) else {
// if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(false);
};
info!("It seems the gateway is using <= v1.1.28 config template.");
info!("It is going to get updated to the current specification.");
let updated_step1: ConfigV1_1_29 = old_config.into();
let updated_step2: ConfigV1_1_31 = updated_step1.into();
let updated_step3: ConfigV1_1_36 = updated_step2.into();
let updated: Config = updated_step3.into();
updated
.save_to_default_location()
.map_err(|err| GatewayError::ConfigSaveFailure {
path: default_config_filepath(id),
id: id.to_string(),
source: err,
})?;
Ok(true)
}
fn try_upgrade_v1_1_29_config(id: &str) -> Result<bool, GatewayError> {
// explicitly load it as v1.1.29 (which is incompatible with the current, i.e. 1.1.30+)
let Ok(old_config) = ConfigV1_1_29::read_from_default_path(id) else {
// if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(false);
};
info!("It seems the gateway is using <= v1.1.29 config template.");
info!("It is going to get updated to the current specification.");
let updated_step1: ConfigV1_1_31 = old_config.into();
let updated_step2: ConfigV1_1_36 = updated_step1.into();
let updated: Config = updated_step2.into();
updated
.save_to_default_location()
.map_err(|err| GatewayError::ConfigSaveFailure {
path: default_config_filepath(id),
id: id.to_string(),
source: err,
})?;
Ok(true)
}
fn try_upgrade_v1_1_31_config(id: &str) -> Result<bool, GatewayError> {
// explicitly load it as v1.1.31 (which is incompatible with the current, i.e. 1.1.32+)
let Ok(old_config) = ConfigV1_1_31::read_from_default_path(id) else {
// if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(false);
};
info!("It seems the gateway is using <= v1.1.31 config template.");
info!("It is going to get updated to the current specification.");
let updated_step1: ConfigV1_1_36 = old_config.into();
let updated: Config = updated_step1.into();
updated
.save_to_default_location()
.map_err(|err| GatewayError::ConfigSaveFailure {
path: default_config_filepath(id),
id: id.to_string(),
source: err,
})?;
Ok(true)
}
fn try_upgrade_v1_1_36_config(id: &str) -> Result<bool, GatewayError> {
// explicitly load it as v1.1.36 (which is incompatible with the current, i.e. 1.1.37+)
let Ok(old_config) = ConfigV1_1_36::read_from_default_path(id) else {
// if we failed to load it, there might have been nothing to upgrade
// or maybe it was an even older file. in either way. just ignore it and carry on with our day
return Ok(false);
};
info!("It seems the gateway is using <= v1.1.36 config template.");
info!("It is going to get updated to the current specification.");
let updated: Config = old_config.into();
updated
.save_to_default_location()
.map_err(|err| GatewayError::ConfigSaveFailure {
path: default_config_filepath(id),
id: id.to_string(),
source: err,
})?;
Ok(true)
}
pub(crate) fn try_upgrade_config(id: &str) -> Result<(), GatewayError> {
if try_upgrade_v1_1_20_config(id)? {
return Ok(());
}
if try_upgrade_v1_1_28_config(id)? {
return Ok(());
}
if try_upgrade_v1_1_29_config(id)? {
return Ok(());
}
if try_upgrade_v1_1_31_config(id)? {
return Ok(());
}
if try_upgrade_v1_1_36_config(id)? {
return Ok(());
}
Ok(())
}
-5
View File
@@ -22,11 +22,6 @@ use zeroize::{Zeroize, ZeroizeOnDrop};
pub use crate::config::persistence::paths::GatewayPaths;
pub mod old_config_v1_1_20;
pub mod old_config_v1_1_28;
pub mod old_config_v1_1_29;
pub mod old_config_v1_1_31;
pub mod old_config_v1_1_36;
pub mod persistence;
mod template;
-203
View File
@@ -1,203 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config::old_config_v1_1_28::{
ConfigV1_1_28, DebugV1_1_28, GatewayPathsV1_1_28, GatewayV1_1_28, KeysPathsV1_1_28,
LoggingSettingsV1_1_28,
};
use nym_config::legacy_helpers::nym_config::MigrationNymConfig;
use nym_validator_client::nyxd;
use serde::{Deserialize, Serialize};
use std::net::{IpAddr, Ipv4Addr};
use std::path::PathBuf;
use std::str::FromStr;
use std::time::Duration;
use url::Url;
const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "https://mainnet-stats.nymte.ch:8090/";
const NYXD_URL: &str = "https://rpc.nymtech.net";
const NYM_API: &str = "https://validator.nymtech.net/api/";
const DEFAULT_MIX_LISTENING_PORT: u16 = 1789;
const DEFAULT_CLIENT_LISTENING_PORT: u16 = 9000;
const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000);
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
/// returns a `0.0.0.0` / INADDR_ANY
fn bind_all_address() -> IpAddr {
IpAddr::V4(Ipv4Addr::UNSPECIFIED)
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
pub struct ConfigV1_1_20 {
gateway: GatewayV1_1_20,
#[serde(default)]
logging: LoggingV1_1_20,
#[serde(default)]
debug: DebugV1_1_20,
}
impl From<ConfigV1_1_20> for ConfigV1_1_28 {
fn from(value: ConfigV1_1_20) -> Self {
ConfigV1_1_28 {
gateway: GatewayV1_1_28 {
version: value.gateway.version,
id: value.gateway.id,
only_coconut_credentials: value.gateway.only_coconut_credentials,
listening_address: value.gateway.listening_address,
mix_port: value.gateway.mix_port,
clients_port: value.gateway.clients_port,
enabled_statistics: value.gateway.enabled_statistics,
nym_api_urls: value.gateway.nym_api_urls,
nyxd_urls: value.gateway.nyxd_urls,
statistics_service_url: value.gateway.statistics_service_url,
cosmos_mnemonic: value.gateway.cosmos_mnemonic,
},
storage_paths: GatewayPathsV1_1_28 {
keys: KeysPathsV1_1_28 {
private_identity_key_file: value.gateway.private_identity_key_file,
public_identity_key_file: value.gateway.public_identity_key_file,
private_sphinx_key_file: value.gateway.private_sphinx_key_file,
public_sphinx_key_file: value.gateway.public_sphinx_key_file,
},
clients_storage: value.gateway.persistent_storage,
},
logging: value.logging.into(),
debug: value.debug.into(),
}
}
}
impl MigrationNymConfig for ConfigV1_1_20 {
fn default_root_directory() -> PathBuf {
// unless this is run on some esoteric system, it should not fail thus the expect is fine
#[allow(clippy::expect_used)]
dirs::home_dir()
.expect("Failed to evaluate $HOME value")
.join(".nym")
.join("gateways")
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
pub struct GatewayV1_1_20 {
version: String,
id: String,
#[serde(default)]
only_coconut_credentials: bool,
#[serde(default = "bind_all_address")]
listening_address: IpAddr,
announce_address: String,
mix_port: u16,
clients_port: u16,
private_identity_key_file: PathBuf,
public_identity_key_file: PathBuf,
private_sphinx_key_file: PathBuf,
public_sphinx_key_file: PathBuf,
enabled_statistics: bool,
statistics_service_url: Url,
#[serde(alias = "validator_api_urls")]
nym_api_urls: Vec<Url>,
#[serde(alias = "validator_nymd_urls")]
nyxd_urls: Vec<Url>,
cosmos_mnemonic: bip39::Mnemonic,
nym_root_directory: PathBuf,
persistent_storage: PathBuf,
wallet_address: Option<nyxd::AccountId>,
}
impl Default for GatewayV1_1_20 {
fn default() -> Self {
// allow usage of `expect` here as our default mainnet values should have been well-formed.
#[allow(clippy::expect_used)]
GatewayV1_1_20 {
version: env!("CARGO_PKG_VERSION").to_string(),
id: "".to_string(),
only_coconut_credentials: false,
listening_address: bind_all_address(),
announce_address: "127.0.0.1".to_string(),
mix_port: DEFAULT_MIX_LISTENING_PORT,
clients_port: DEFAULT_CLIENT_LISTENING_PORT,
private_identity_key_file: Default::default(),
public_identity_key_file: Default::default(),
private_sphinx_key_file: Default::default(),
public_sphinx_key_file: Default::default(),
enabled_statistics: false,
statistics_service_url: Url::from_str(STATISTICS_SERVICE_DOMAIN_ADDRESS)
.expect("Invalid default statistics service URL"),
nym_api_urls: vec![Url::from_str(NYM_API).expect("Invalid default API URL")],
nyxd_urls: vec![Url::from_str(NYXD_URL).expect("Invalid default nyxd URL")],
cosmos_mnemonic: bip39::Mnemonic::generate(24)
.expect("failed to generate fresh mnemonic"),
nym_root_directory: ConfigV1_1_20::default_root_directory(),
persistent_storage: Default::default(),
wallet_address: None,
}
}
}
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
struct LoggingV1_1_20 {}
impl From<LoggingV1_1_20> for LoggingSettingsV1_1_28 {
fn from(_value: LoggingV1_1_20) -> Self {
LoggingSettingsV1_1_28 {}
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(default)]
struct DebugV1_1_20 {
#[serde(with = "humantime_serde")]
packet_forwarding_initial_backoff: Duration,
#[serde(with = "humantime_serde")]
packet_forwarding_maximum_backoff: Duration,
#[serde(with = "humantime_serde")]
initial_connection_timeout: Duration,
maximum_connection_buffer_size: usize,
#[serde(with = "humantime_serde")]
presence_sending_delay: Duration,
stored_messages_filename_length: u16,
message_retrieval_limit: i64,
use_legacy_framed_packet_version: bool,
}
impl From<DebugV1_1_20> for DebugV1_1_28 {
fn from(value: DebugV1_1_20) -> Self {
DebugV1_1_28 {
packet_forwarding_initial_backoff: value.packet_forwarding_initial_backoff,
packet_forwarding_maximum_backoff: value.packet_forwarding_maximum_backoff,
initial_connection_timeout: value.initial_connection_timeout,
maximum_connection_buffer_size: value.maximum_connection_buffer_size,
presence_sending_delay: value.presence_sending_delay,
stored_messages_filename_length: value.stored_messages_filename_length,
message_retrieval_limit: value.message_retrieval_limit,
use_legacy_framed_packet_version: value.use_legacy_framed_packet_version,
}
}
}
impl Default for DebugV1_1_20 {
fn default() -> Self {
DebugV1_1_20 {
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY,
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH,
message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
// TODO: remember to change it in one of future releases!!
use_legacy_framed_packet_version: true,
}
}
}
-233
View File
@@ -1,233 +0,0 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config::old_config_v1_1_29::{
ConfigV1_1_29, DebugV1_1_29, GatewayPathsV1_1_29, GatewayV1_1_29, KeysPathsV1_1_29,
LoggingSettingsV1_1_29,
};
use nym_config::{
must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR,
};
use serde::{Deserialize, Serialize};
use std::io;
use std::net::IpAddr;
use std::path::{Path, PathBuf};
use std::time::Duration;
use url::Url;
const DEFAULT_GATEWAYS_DIR: &str = "gateways";
// 'DEBUG'
// where applicable, the below are defined in milliseconds
const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000);
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
/// Derive default path to gateway's config directory.
/// It should get resolved to `$HOME/.nym/gateways/<id>/config`
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_GATEWAYS_DIR)
.join(id)
.join(DEFAULT_CONFIG_DIR)
}
/// Derive default path to gateways's config file.
/// It should get resolved to `$HOME/.nym/gateways/<id>/config/config.toml`
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct KeysPathsV1_1_28 {
pub private_identity_key_file: PathBuf,
pub public_identity_key_file: PathBuf,
pub private_sphinx_key_file: PathBuf,
pub public_sphinx_key_file: PathBuf,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct GatewayPathsV1_1_28 {
pub keys: KeysPathsV1_1_28,
#[serde(alias = "persistent_storage")]
pub clients_storage: PathBuf,
}
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct LoggingSettingsV1_1_28 {}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_28 {
pub gateway: GatewayV1_1_28,
pub storage_paths: GatewayPathsV1_1_28,
#[serde(default)]
pub logging: LoggingSettingsV1_1_28,
#[serde(default)]
pub debug: DebugV1_1_28,
}
impl ConfigV1_1_28 {
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
read_config_from_toml_file(default_config_filepath(id))
}
}
impl From<ConfigV1_1_28> for ConfigV1_1_29 {
fn from(value: ConfigV1_1_28) -> Self {
ConfigV1_1_29 {
// \/ ADDED
save_path: None,
// /\ ADDED
gateway: GatewayV1_1_29 {
version: value.gateway.version,
id: value.gateway.id,
only_coconut_credentials: value.gateway.only_coconut_credentials,
listening_address: value.gateway.listening_address,
mix_port: value.gateway.mix_port,
clients_port: value.gateway.clients_port,
enabled_statistics: value.gateway.enabled_statistics,
nym_api_urls: value.gateway.nym_api_urls,
nyxd_urls: value.gateway.nyxd_urls,
statistics_service_url: value.gateway.statistics_service_url,
cosmos_mnemonic: value.gateway.cosmos_mnemonic,
},
storage_paths: GatewayPathsV1_1_29 {
keys: KeysPathsV1_1_29 {
private_identity_key_file: value.storage_paths.keys.private_identity_key_file,
public_identity_key_file: value.storage_paths.keys.public_identity_key_file,
private_sphinx_key_file: value.storage_paths.keys.private_sphinx_key_file,
public_sphinx_key_file: value.storage_paths.keys.public_sphinx_key_file,
},
clients_storage: value.storage_paths.clients_storage,
// \/ ADDED
network_requester_config: None,
// /\ ADDED
},
// \/ ADDED
network_requester: Default::default(),
// /\ ADDED
logging: LoggingSettingsV1_1_29 {},
debug: DebugV1_1_29 {
packet_forwarding_initial_backoff: value.debug.packet_forwarding_initial_backoff,
packet_forwarding_maximum_backoff: value.debug.packet_forwarding_maximum_backoff,
initial_connection_timeout: value.debug.initial_connection_timeout,
maximum_connection_buffer_size: value.debug.maximum_connection_buffer_size,
presence_sending_delay: value.debug.presence_sending_delay,
stored_messages_filename_length: value.debug.stored_messages_filename_length,
message_retrieval_limit: value.debug.message_retrieval_limit,
use_legacy_framed_packet_version: value.debug.use_legacy_framed_packet_version,
},
}
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
pub struct GatewayV1_1_28 {
/// Version of the gateway for which this configuration was created.
pub version: String,
/// ID specifies the human readable ID of this particular gateway.
pub id: String,
/// Indicates whether this gateway is accepting only coconut credentials for accessing the
/// the mixnet, or if it also accepts non-paying clients
#[serde(default)]
pub only_coconut_credentials: bool,
/// Address to which this mixnode will bind to and will be listening for packets.
pub listening_address: IpAddr,
/// Port used for listening for all mixnet traffic.
/// (default: 1789)
pub mix_port: u16,
/// Port used for listening for all client-related traffic.
/// (default: 9000)
pub clients_port: u16,
/// Whether gateway collects and sends anonymized statistics
pub enabled_statistics: bool,
/// Domain address of the statistics service
pub statistics_service_url: Url,
/// Addresses to APIs from which the node gets the view of the network.
#[serde(alias = "validator_api_urls")]
pub nym_api_urls: Vec<Url>,
/// Addresses to validators which the node uses to check for double spending of ERC20 tokens.
#[serde(alias = "validator_nymd_urls")]
pub nyxd_urls: Vec<Url>,
/// Mnemonic of a cosmos wallet used in checking for double spending.
// #[deprecated(note = "move to storage")]
// TODO: I don't think this should be stored directly in the config...
pub cosmos_mnemonic: bip39::Mnemonic,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct DebugV1_1_28 {
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
/// forwarding sphinx packets.
#[serde(with = "humantime_serde")]
pub packet_forwarding_initial_backoff: Duration,
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
/// forwarding sphinx packets.
#[serde(with = "humantime_serde")]
pub packet_forwarding_maximum_backoff: Duration,
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
#[serde(with = "humantime_serde")]
pub initial_connection_timeout: Duration,
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
pub maximum_connection_buffer_size: usize,
/// Delay between each subsequent presence data being sent.
#[serde(with = "humantime_serde")]
pub presence_sending_delay: Duration,
/// Length of filenames for new client messages.
pub stored_messages_filename_length: u16,
/// Number of messages from offline client that can be pulled at once from the storage.
pub message_retrieval_limit: i64,
/// Specifies whether the mixnode should be using the legacy framing for the sphinx packets.
// it's set to true by default. The reason for that decision is to preserve compatibility with the
// existing nodes whilst everyone else is upgrading and getting the code for handling the new field.
// It shall be disabled in the subsequent releases.
pub use_legacy_framed_packet_version: bool,
}
impl Default for DebugV1_1_28 {
fn default() -> Self {
DebugV1_1_28 {
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY,
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH,
message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
use_legacy_framed_packet_version: false,
}
}
}
-282
View File
@@ -1,282 +0,0 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use crate::config::Host;
use nym_config::{
must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR,
};
use serde::{Deserialize, Deserializer, Serialize};
use std::io;
use std::net::IpAddr;
use std::path::{Path, PathBuf};
use std::time::Duration;
use url::Url;
use super::old_config_v1_1_31::{
ConfigV1_1_31, DebugV1_1_31, GatewayPathsV1_1_31, GatewayV1_1_31, KeysPathsV1_1_31,
LoggingSettingsV1_1_31, NetworkRequesterV1_1_31,
};
const DEFAULT_GATEWAYS_DIR: &str = "gateways";
// 'DEBUG'
// where applicable, the below are defined in milliseconds
const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000);
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
/// Derive default path to gateway's config directory.
/// It should get resolved to `$HOME/.nym/gateways/<id>/config`
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_GATEWAYS_DIR)
.join(id)
.join(DEFAULT_CONFIG_DIR)
}
/// Derive default path to gateways's config file.
/// It should get resolved to `$HOME/.nym/gateways/<id>/config/config.toml`
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct KeysPathsV1_1_29 {
pub private_identity_key_file: PathBuf,
pub public_identity_key_file: PathBuf,
pub private_sphinx_key_file: PathBuf,
pub public_sphinx_key_file: PathBuf,
}
fn de_maybe_path<'de, D>(deserializer: D) -> Result<Option<PathBuf>, D::Error>
where
D: Deserializer<'de>,
{
let path = PathBuf::deserialize(deserializer)?;
if path.as_os_str().is_empty() {
Ok(None)
} else {
Ok(Some(path))
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct GatewayPathsV1_1_29 {
pub keys: KeysPathsV1_1_29,
#[serde(alias = "persistent_storage")]
pub clients_storage: PathBuf,
#[serde(deserialize_with = "de_maybe_path")]
pub network_requester_config: Option<PathBuf>,
}
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct LoggingSettingsV1_1_29 {}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_29 {
#[serde(skip)]
pub save_path: Option<PathBuf>,
pub gateway: GatewayV1_1_29,
pub storage_paths: GatewayPathsV1_1_29,
pub network_requester: NetworkRequesterV1_1_29,
#[serde(default)]
pub logging: LoggingSettingsV1_1_29,
#[serde(default)]
pub debug: DebugV1_1_29,
}
impl ConfigV1_1_29 {
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
read_config_from_toml_file(default_config_filepath(id))
}
}
impl From<ConfigV1_1_29> for ConfigV1_1_31 {
fn from(value: ConfigV1_1_29) -> Self {
ConfigV1_1_31 {
save_path: value.save_path,
// \/ ADDED
host: Host {
// this is a very bad default!
public_ips: vec![value.gateway.listening_address],
hostname: None,
},
// /\ ADDED
// \/ ADDED
http: Default::default(),
// /\ ADDED
gateway: GatewayV1_1_31 {
version: value.gateway.version,
id: value.gateway.id,
only_coconut_credentials: value.gateway.only_coconut_credentials,
listening_address: value.gateway.listening_address,
mix_port: value.gateway.mix_port,
clients_port: value.gateway.clients_port,
// \/ ADDED
clients_wss_port: None,
// /\ ADDED
enabled_statistics: value.gateway.enabled_statistics,
nym_api_urls: value.gateway.nym_api_urls,
nyxd_urls: value.gateway.nyxd_urls,
statistics_service_url: value.gateway.statistics_service_url,
cosmos_mnemonic: value.gateway.cosmos_mnemonic,
},
// \/ ADDED
wireguard: Default::default(),
// /\ ADDED
storage_paths: GatewayPathsV1_1_31 {
keys: KeysPathsV1_1_31 {
private_identity_key_file: value.storage_paths.keys.private_identity_key_file,
public_identity_key_file: value.storage_paths.keys.public_identity_key_file,
private_sphinx_key_file: value.storage_paths.keys.private_sphinx_key_file,
public_sphinx_key_file: value.storage_paths.keys.public_sphinx_key_file,
},
clients_storage: value.storage_paths.clients_storage,
network_requester_config: value.storage_paths.network_requester_config,
},
network_requester: NetworkRequesterV1_1_31 {
enabled: value.network_requester.enabled,
},
logging: LoggingSettingsV1_1_31 {},
debug: DebugV1_1_31 {
packet_forwarding_initial_backoff: value.debug.packet_forwarding_initial_backoff,
packet_forwarding_maximum_backoff: value.debug.packet_forwarding_maximum_backoff,
initial_connection_timeout: value.debug.initial_connection_timeout,
maximum_connection_buffer_size: value.debug.maximum_connection_buffer_size,
presence_sending_delay: value.debug.presence_sending_delay,
stored_messages_filename_length: value.debug.stored_messages_filename_length,
message_retrieval_limit: value.debug.message_retrieval_limit,
use_legacy_framed_packet_version: value.debug.use_legacy_framed_packet_version,
},
}
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
pub struct GatewayV1_1_29 {
/// Version of the gateway for which this configuration was created.
pub version: String,
/// ID specifies the human readable ID of this particular gateway.
pub id: String,
/// Indicates whether this gateway is accepting only coconut credentials for accessing the
/// the mixnet, or if it also accepts non-paying clients
#[serde(default)]
pub only_coconut_credentials: bool,
/// Address to which this mixnode will bind to and will be listening for packets.
pub listening_address: IpAddr,
/// Port used for listening for all mixnet traffic.
/// (default: 1789)
pub mix_port: u16,
/// Port used for listening for all client-related traffic.
/// (default: 9000)
pub clients_port: u16,
/// Whether gateway collects and sends anonymized statistics
pub enabled_statistics: bool,
/// Domain address of the statistics service
pub statistics_service_url: Url,
/// Addresses to APIs from which the node gets the view of the network.
#[serde(alias = "validator_api_urls")]
pub nym_api_urls: Vec<Url>,
/// Addresses to validators which the node uses to check for double spending of ERC20 tokens.
#[serde(alias = "validator_nymd_urls")]
pub nyxd_urls: Vec<Url>,
/// Mnemonic of a cosmos wallet used in checking for double spending.
// #[deprecated(note = "move to storage")]
// TODO: I don't think this should be stored directly in the config...
pub cosmos_mnemonic: bip39::Mnemonic,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct NetworkRequesterV1_1_29 {
pub enabled: bool,
}
#[allow(clippy::derivable_impls)]
impl Default for NetworkRequesterV1_1_29 {
fn default() -> Self {
NetworkRequesterV1_1_29 { enabled: false }
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct DebugV1_1_29 {
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
/// forwarding sphinx packets.
#[serde(with = "humantime_serde")]
pub packet_forwarding_initial_backoff: Duration,
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
/// forwarding sphinx packets.
#[serde(with = "humantime_serde")]
pub packet_forwarding_maximum_backoff: Duration,
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
#[serde(with = "humantime_serde")]
pub initial_connection_timeout: Duration,
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
pub maximum_connection_buffer_size: usize,
/// Delay between each subsequent presence data being sent.
#[serde(with = "humantime_serde")]
pub presence_sending_delay: Duration,
/// Length of filenames for new client messages.
pub stored_messages_filename_length: u16,
/// Number of messages from offline client that can be pulled at once from the storage.
pub message_retrieval_limit: i64,
/// Specifies whether the mixnode should be using the legacy framing for the sphinx packets.
// it's set to true by default. The reason for that decision is to preserve compatibility with the
// existing nodes whilst everyone else is upgrading and getting the code for handling the new field.
// It shall be disabled in the subsequent releases.
pub use_legacy_framed_packet_version: bool,
}
impl Default for DebugV1_1_29 {
fn default() -> Self {
DebugV1_1_29 {
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY,
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH,
message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
use_legacy_framed_packet_version: false,
}
}
}
-362
View File
@@ -1,362 +0,0 @@
// Copyright 2020-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use nym_config::{
must_get_home, read_config_from_toml_file, DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILENAME, NYM_DIR,
};
use nym_network_defaults::WG_PORT;
use serde::{Deserialize, Deserializer, Serialize};
use std::io;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::path::{Path, PathBuf};
use std::time::Duration;
use url::Url;
use super::old_config_v1_1_36::{
ConfigV1_1_36, DebugV1_1_36, GatewayPathsV1_1_36, GatewayV1_1_36, KeysPathsV1_1_36,
LoggingSettingsV1_1_36, NetworkRequesterV1_1_36, WireguardPathsV1_1_36, WireguardV1_1_36,
};
use super::{Host, Http};
const DEFAULT_GATEWAYS_DIR: &str = "gateways";
// 'DEBUG'
// where applicable, the below are defined in milliseconds
const DEFAULT_PRESENCE_SENDING_DELAY: Duration = Duration::from_millis(10_000);
const DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF: Duration = Duration::from_millis(10_000);
const DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF: Duration = Duration::from_millis(300_000);
const DEFAULT_INITIAL_CONNECTION_TIMEOUT: Duration = Duration::from_millis(1_500);
const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
fn de_maybe_port<'de, D>(deserializer: D) -> Result<Option<u16>, D::Error>
where
D: Deserializer<'de>,
{
let port = u16::deserialize(deserializer)?;
if port == 0 {
Ok(None)
} else {
Ok(Some(port))
}
}
fn de_maybe_path<'de, D>(deserializer: D) -> Result<Option<PathBuf>, D::Error>
where
D: Deserializer<'de>,
{
let path = PathBuf::deserialize(deserializer)?;
if path.as_os_str().is_empty() {
Ok(None)
} else {
Ok(Some(path))
}
}
/// Derive default path to gateway's config directory.
/// It should get resolved to `$HOME/.nym/gateways/<id>/config`
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
must_get_home()
.join(NYM_DIR)
.join(DEFAULT_GATEWAYS_DIR)
.join(id)
.join(DEFAULT_CONFIG_DIR)
}
/// Derive default path to gateways's config file.
/// It should get resolved to `$HOME/.nym/gateways/<id>/config/config.toml`
pub fn default_config_filepath<P: AsRef<Path>>(id: P) -> PathBuf {
default_config_directory(id).join(DEFAULT_CONFIG_FILENAME)
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct ConfigV1_1_31 {
// additional metadata holding on-disk location of this config file
#[serde(skip)]
pub(crate) save_path: Option<PathBuf>,
pub host: Host,
#[serde(default)]
pub http: Http,
pub gateway: GatewayV1_1_31,
#[serde(default)]
// currently not really used for anything useful
pub wireguard: WireguardV1_1_31,
pub storage_paths: GatewayPathsV1_1_31,
pub network_requester: NetworkRequesterV1_1_31,
#[serde(default)]
pub logging: LoggingSettingsV1_1_31,
#[serde(default)]
pub debug: DebugV1_1_31,
}
impl ConfigV1_1_31 {
pub fn read_from_default_path<P: AsRef<Path>>(id: P) -> io::Result<Self> {
read_config_from_toml_file(default_config_filepath(id))
}
}
impl From<ConfigV1_1_31> for ConfigV1_1_36 {
fn from(value: ConfigV1_1_31) -> Self {
Self {
save_path: value.save_path,
host: value.host,
http: value.http,
gateway: GatewayV1_1_36 {
version: value.gateway.version,
id: value.gateway.id,
only_coconut_credentials: value.gateway.only_coconut_credentials,
listening_address: value.gateway.listening_address,
mix_port: value.gateway.mix_port,
clients_port: value.gateway.clients_port,
clients_wss_port: value.gateway.clients_wss_port,
enabled_statistics: value.gateway.enabled_statistics,
statistics_service_url: value.gateway.statistics_service_url,
nym_api_urls: value.gateway.nym_api_urls,
nyxd_urls: value.gateway.nyxd_urls,
cosmos_mnemonic: value.gateway.cosmos_mnemonic,
},
wireguard: WireguardV1_1_36 {
enabled: value.wireguard.enabled,
bind_address: value.wireguard.bind_address,
announced_port: value.wireguard.announced_port,
private_network_prefix: Default::default(),
storage_paths: WireguardPathsV1_1_36 {
// no fields (yet)
},
},
storage_paths: GatewayPathsV1_1_36 {
keys: KeysPathsV1_1_36 {
private_identity_key_file: value.storage_paths.keys.private_identity_key_file,
public_identity_key_file: value.storage_paths.keys.public_identity_key_file,
private_sphinx_key_file: value.storage_paths.keys.private_sphinx_key_file,
public_sphinx_key_file: value.storage_paths.keys.public_sphinx_key_file,
},
clients_storage: value.storage_paths.clients_storage,
network_requester_config: value.storage_paths.network_requester_config,
// \/ ADDED
ip_packet_router_config: Default::default(),
// /\ ADDED
},
network_requester: NetworkRequesterV1_1_36 {
enabled: value.network_requester.enabled,
},
// \/ ADDED
ip_packet_router: Default::default(),
// /\ ADDED
logging: LoggingSettingsV1_1_36 {
// no fields (yet)
},
debug: DebugV1_1_36 {
packet_forwarding_initial_backoff: value.debug.packet_forwarding_initial_backoff,
packet_forwarding_maximum_backoff: value.debug.packet_forwarding_maximum_backoff,
initial_connection_timeout: value.debug.initial_connection_timeout,
maximum_connection_buffer_size: value.debug.maximum_connection_buffer_size,
presence_sending_delay: value.debug.presence_sending_delay,
stored_messages_filename_length: value.debug.stored_messages_filename_length,
message_retrieval_limit: value.debug.message_retrieval_limit,
use_legacy_framed_packet_version: value.debug.use_legacy_framed_packet_version,
},
}
}
}
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
pub struct GatewayV1_1_31 {
/// Version of the gateway for which this configuration was created.
pub version: String,
/// ID specifies the human readable ID of this particular gateway.
pub id: String,
/// Indicates whether this gateway is accepting only coconut credentials for accessing the
/// the mixnet, or if it also accepts non-paying clients
#[serde(default)]
pub only_coconut_credentials: bool,
/// Address to which this mixnode will bind to and will be listening for packets.
pub listening_address: IpAddr,
/// Port used for listening for all mixnet traffic.
/// (default: 1789)
pub mix_port: u16,
/// Port used for listening for all client-related traffic.
/// (default: 9000)
pub clients_port: u16,
/// If applicable, announced port for listening for secure websocket client traffic.
/// (default: None)
#[serde(deserialize_with = "de_maybe_port")]
pub clients_wss_port: Option<u16>,
/// Whether gateway collects and sends anonymized statistics
pub enabled_statistics: bool,
/// Domain address of the statistics service
pub statistics_service_url: Url,
/// Addresses to APIs from which the node gets the view of the network.
#[serde(alias = "validator_api_urls")]
pub nym_api_urls: Vec<Url>,
/// Addresses to validators which the node uses to check for double spending of ERC20 tokens.
#[serde(alias = "validator_nymd_urls")]
pub nyxd_urls: Vec<Url>,
/// Mnemonic of a cosmos wallet used in checking for double spending.
// #[deprecated(note = "move to storage")]
// TODO: I don't think this should be stored directly in the config...
pub cosmos_mnemonic: bip39::Mnemonic,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(default)]
#[serde(deny_unknown_fields)]
pub struct WireguardV1_1_31 {
/// Specifies whether the wireguard service is enabled on this node.
pub enabled: bool,
/// Socket address this node will use for binding its wireguard interface.
/// default: `0.0.0.0:51820`
pub bind_address: SocketAddr,
/// Port announced to external clients wishing to connect to the wireguard interface.
/// Useful in the instances where the node is behind a proxy.
pub announced_port: u16,
/// Paths for wireguard keys, client registries, etc.
pub storage_paths: WireguardPathsV1_1_31,
}
impl Default for WireguardV1_1_31 {
fn default() -> Self {
Self {
enabled: false,
bind_address: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), WG_PORT),
announced_port: WG_PORT,
storage_paths: WireguardPathsV1_1_31 {},
}
}
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct WireguardPathsV1_1_31 {
// pub keys:
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct GatewayPathsV1_1_31 {
pub keys: KeysPathsV1_1_31,
/// Path to sqlite database containing all persistent data: messages for offline clients,
/// derived shared keys and available client bandwidths.
#[serde(alias = "persistent_storage")]
pub clients_storage: PathBuf,
/// Path to the configuration of the embedded network requester.
#[serde(deserialize_with = "de_maybe_path")]
pub network_requester_config: Option<PathBuf>,
// pub node_description: PathBuf,
// pub cosmos_bip39_mnemonic: PathBuf,
}
#[derive(Debug, Clone, Deserialize, PartialEq, Eq, Serialize)]
pub struct KeysPathsV1_1_31 {
/// Path to file containing private identity key.
pub private_identity_key_file: PathBuf,
/// Path to file containing public identity key.
pub public_identity_key_file: PathBuf,
/// Path to file containing private sphinx key.
pub private_sphinx_key_file: PathBuf,
/// Path to file containing public sphinx key.
pub public_sphinx_key_file: PathBuf,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct NetworkRequesterV1_1_31 {
/// Specifies whether network requester service is enabled in this process.
pub enabled: bool,
}
#[allow(clippy::derivable_impls)]
impl Default for NetworkRequesterV1_1_31 {
fn default() -> Self {
Self { enabled: false }
}
}
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq, Serialize)]
#[serde(deny_unknown_fields)]
pub struct LoggingSettingsV1_1_31 {
// well, we need to implement something here at some point...
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(default)]
pub struct DebugV1_1_31 {
/// Initial value of an exponential backoff to reconnect to dropped TCP connection when
/// forwarding sphinx packets.
#[serde(with = "humantime_serde")]
pub packet_forwarding_initial_backoff: Duration,
/// Maximum value of an exponential backoff to reconnect to dropped TCP connection when
/// forwarding sphinx packets.
#[serde(with = "humantime_serde")]
pub packet_forwarding_maximum_backoff: Duration,
/// Timeout for establishing initial connection when trying to forward a sphinx packet.
#[serde(with = "humantime_serde")]
pub initial_connection_timeout: Duration,
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
pub maximum_connection_buffer_size: usize,
/// Delay between each subsequent presence data being sent.
#[serde(with = "humantime_serde")]
pub presence_sending_delay: Duration,
/// Length of filenames for new client messages.
pub stored_messages_filename_length: u16,
/// Number of messages from offline client that can be pulled at once from the storage.
pub message_retrieval_limit: i64,
/// Specifies whether the mixnode should be using the legacy framing for the sphinx packets.
// it's set to true by default. The reason for that decision is to preserve compatibility with the
// existing nodes whilst everyone else is upgrading and getting the code for handling the new field.
// It shall be disabled in the subsequent releases.
pub use_legacy_framed_packet_version: bool,
}
impl Default for DebugV1_1_31 {
fn default() -> Self {
Self {
packet_forwarding_initial_backoff: DEFAULT_PACKET_FORWARDING_INITIAL_BACKOFF,
packet_forwarding_maximum_backoff: DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
initial_connection_timeout: DEFAULT_INITIAL_CONNECTION_TIMEOUT,
presence_sending_delay: DEFAULT_PRESENCE_SENDING_DELAY,
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH,
message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
use_legacy_framed_packet_version: false,
}
}
}

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