Compare commits
73 Commits
ls
...
simon/measurements
| Author | SHA1 | Date | |
|---|---|---|---|
| e0b8638163 | |||
| fdb5727e5a | |||
| 3da131de15 | |||
| ba3a6eaeb5 | |||
| 9d0bc8ab9e | |||
| 21611a9434 | |||
| 78d9030567 | |||
| f3c16476f9 | |||
| 4678059eaf | |||
| a69d4bb457 | |||
| 134ab2f0d6 | |||
| 1a4e7433b2 | |||
| 9d91a018b2 | |||
| ae9c1b4070 | |||
| adcd8703d3 | |||
| aee4c2d80d | |||
| 5e04f48500 | |||
| a7610a7a88 | |||
| 28e6e9140c | |||
| 271a126556 | |||
| 7da444c7a4 | |||
| 2a7fd71416 | |||
| 6ffd211e51 | |||
| f61ed48240 | |||
| 8d3dc405a5 | |||
| 21bf0fb27b | |||
| 7313f56c01 | |||
| cfef9e96e6 | |||
| 8b9a5cf500 | |||
| ce94ce058f | |||
| 018e4d6079 | |||
| ebadd9799f | |||
| 268fa02b83 | |||
| cb525477e5 | |||
| 4d6d2f0d33 | |||
| c7f8b05604 | |||
| 2878e9be9d | |||
| 7b419c2b12 | |||
| 0049126a91 | |||
| 80c5194d8b | |||
| 27a6b99453 | |||
| 61982de511 | |||
| efd9883197 | |||
| ce4ae8d90c | |||
| 1d2722f994 | |||
| 7e109e7f2d | |||
| f7a0b305df | |||
| 746ec71a0d | |||
| 41a63a0985 | |||
| 5a149c5492 | |||
| cdfa5ee540 | |||
| 71853f69f3 | |||
| bedff1f258 | |||
| 71a10a9a8b | |||
| 605aed6f20 | |||
| 5aee4b1660 | |||
| 68a7bb67de | |||
| 31e93428cf | |||
| 5f56b3eeea | |||
| e69b05693a | |||
| 8ae2451340 | |||
| b3b3279345 | |||
| 94a451c79b | |||
| ec7b959028 | |||
| 7061beea6e | |||
| e408162e26 | |||
| 25ae3895cb | |||
| 60296f2a41 | |||
| 3b97844310 | |||
| 8e6d3c34e2 | |||
| b1fb8bb18c | |||
| 4f59678ded | |||
| 8a1d2af3cf |
@@ -80,7 +80,7 @@ jobs:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Install wasm-opt
|
||||
run: cargo install wasm-opt
|
||||
run: cargo install --version 0.112.0 wasm-opt
|
||||
|
||||
- name: Build release contracts
|
||||
run: make wasm
|
||||
@@ -99,9 +99,14 @@ jobs:
|
||||
cp target/release/nym-network-statistics $OUTPUT_DIR
|
||||
cp target/release/nym-cli $OUTPUT_DIR
|
||||
cp target/release/credential $OUTPUT_DIR
|
||||
cp target/release/explorer-api $OUTPUT_DIR
|
||||
|
||||
cp contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/nym_coconut_bandwidth.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/nym_coconut_dkg.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/cw3_flex_multisig.wasm $OUTPUT_DIR
|
||||
cp contracts/target/wasm32-unknown-unknown/release/cw4_group.wasm $OUTPUT_DIR
|
||||
|
||||
- name: Deploy branch to CI www
|
||||
continue-on-error: true
|
||||
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Install wasm-opt
|
||||
run: cargo install wasm-opt
|
||||
run: cargo install --version 0.112.0 wasm-opt
|
||||
|
||||
- name: Build release contracts
|
||||
run: make wasm
|
||||
|
||||
@@ -39,7 +39,5 @@ validator-api-config.toml
|
||||
dist
|
||||
storybook-static
|
||||
envs/qwerty.env
|
||||
Cargo.lock
|
||||
nym-connect/Cargo.lock
|
||||
.parcel-cache
|
||||
**/.DS_Store
|
||||
|
||||
@@ -4,6 +4,38 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v1.1.14] (2023-04-04)
|
||||
|
||||
- Investigate cause of qwerty validator being in invalid rewarding state ([#3224])
|
||||
- Fix NR config due to changes in #3199 ([#3223])
|
||||
- [Issue] Mixnodes and gateway do not close connections properly ([#3187])
|
||||
- disable sign-ext when using wasm-opt + update wasm-opt ([#3203])
|
||||
- chore: tidy up client `Debug` config section ([#3199])
|
||||
|
||||
[#3224]: https://github.com/nymtech/nym/issues/3224
|
||||
[#3223]: https://github.com/nymtech/nym/issues/3223
|
||||
[#3187]: https://github.com/nymtech/nym/issues/3187
|
||||
[#3203]: https://github.com/nymtech/nym/pull/3203
|
||||
[#3199]: https://github.com/nymtech/nym/pull/3199
|
||||
|
||||
## [v1.1.13] (2023-03-15)
|
||||
|
||||
- NE - instead of throwing a "Mixnode/Gateway not found" error for blacklisted nodes due to bad performance, show their history but tag them as "Having poor performance" ([#2979])
|
||||
- NE - Upgrade Sandbox and make below changes: ([#2332])
|
||||
- Explorer - Updates ([#3168])
|
||||
- Website v2 - deploy infrastructure for strapi and CI ([#2213])
|
||||
- add blockstream green to sp list ([#3180])
|
||||
- mock-nym-api: fix .storybook lint error ([#3178])
|
||||
- Validating new interval config parameters to prevent division by zero ([#3153])
|
||||
|
||||
[#2979]: https://github.com/nymtech/nym/issues/2979
|
||||
[#2332]: https://github.com/nymtech/nym/issues/2332
|
||||
[#3168]: https://github.com/nymtech/nym/issues/3168
|
||||
[#2213]: https://github.com/nymtech/nym/issues/2213
|
||||
[#3180]: https://github.com/nymtech/nym/pull/3180
|
||||
[#3178]: https://github.com/nymtech/nym/pull/3178
|
||||
[#3153]: https://github.com/nymtech/nym/pull/3153
|
||||
|
||||
## [v1.1.12] (2023-03-07)
|
||||
|
||||
- Fix generated docs for mixnet and vesting contract on docs.rs ([#3093])
|
||||
|
||||
Generated
+399
-442
File diff suppressed because it is too large
Load Diff
+10
@@ -107,6 +107,16 @@ license = "Apache-2.0"
|
||||
async-trait = "0.1.64"
|
||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||
cfg-if = "1.0.0"
|
||||
cosmwasm-derive = "=1.0.0"
|
||||
cosmwasm-schema = "=1.0.0"
|
||||
cosmwasm-std = "=1.0.0"
|
||||
cosmwasm-storage = "=1.0.0"
|
||||
cw-utils = "=0.13.4"
|
||||
cw-storage-plus = "=0.13.4"
|
||||
cw2 = { version = "=0.13.4" }
|
||||
cw3 = { version = "=0.13.4" }
|
||||
cw3-fixed-multisig = { version = "=0.13.4" }
|
||||
cw4 = { version = "=0.13.4" }
|
||||
dotenvy = "0.15.6"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4"
|
||||
|
||||
@@ -1,143 +1,90 @@
|
||||
# Default target
|
||||
all: test
|
||||
|
||||
test: clippy-all cargo-test wasm fmt
|
||||
test-no-mobile: clippy-all-no-mobile cargo-test-no-mobile wasm fmt-no-mobile
|
||||
|
||||
test-all: test cargo-test-expensive
|
||||
test-all-no-mobile: test-no-mobile cargo-test-expensive
|
||||
|
||||
no-clippy: build cargo-test wasm fmt
|
||||
no-clippy-no-mobile: build-no-mobile cargo-test-no-mobile wasm fmt-no-mobile
|
||||
|
||||
happy: fmt clippy-happy test
|
||||
happy-no-mobile: fmt-no-mobile clippy-happy-no-mobile test-no-mobile
|
||||
clippy-all: clippy-all-no-mobile clippy-all-connect-mobile
|
||||
clippy-all-no-mobile: clippy-main clippy-main-examples clippy-all-contracts clippy-all-wallet clippy-all-connect clippy-all-wasm-client
|
||||
clippy-happy: clippy-happy-no-mobile clippy-happy-connect-mobile
|
||||
clippy-happy-no-mobile: clippy-happy-main clippy-happy-contracts clippy-happy-wallet clippy-happy-connect
|
||||
cargo-test: cargo-test-no-mobile test-connect-mobile
|
||||
cargo-test-no-mobile: test-main test-contracts test-wallet test-connect
|
||||
cargo-test-expensive: test-main-expensive test-contracts-expensive test-wallet-expensive test-connect-expensive
|
||||
build: build-no-mobile build-connect-mobile
|
||||
build-no-mobile: build-contracts build-wallet build-main build-main-examples build-connect build-wasm-client
|
||||
fmt: fmt-no-mobile fmt-connect-mobile
|
||||
fmt-no-mobile: fmt-main fmt-contracts fmt-wallet fmt-connect fmt-wasm-client
|
||||
|
||||
clippy-happy-main:
|
||||
cargo clippy
|
||||
# -----------------------------------------------------------------------------
|
||||
# Define targets for a given workspace
|
||||
# $(1): name
|
||||
# $(2): path to workspace
|
||||
# $(3): extra arguments to cargo
|
||||
# -----------------------------------------------------------------------------
|
||||
define add_cargo_workspace
|
||||
|
||||
clippy-happy-contracts:
|
||||
cargo clippy --manifest-path contracts/Cargo.toml --target wasm32-unknown-unknown
|
||||
clippy-happy-$(1):
|
||||
cargo clippy --manifest-path $(2)/Cargo.toml $(3)
|
||||
|
||||
clippy-happy-wallet:
|
||||
cargo clippy --manifest-path nym-wallet/Cargo.toml
|
||||
clippy-$(1):
|
||||
cargo clippy --manifest-path $(2)/Cargo.toml --workspace $(3) -- -D warnings
|
||||
|
||||
clippy-happy-connect:
|
||||
cargo clippy --manifest-path nym-connect/desktop/Cargo.toml
|
||||
clippy-$(1)-examples:
|
||||
cargo clippy --manifest-path $(2)/Cargo.toml --workspace --examples -- -D warnings
|
||||
|
||||
clippy-happy-connect-mobile:
|
||||
cargo clippy --manifest-path nym-connect/mobile/src-tauri/Cargo.toml
|
||||
test-$(1):
|
||||
cargo test --manifest-path $(2)/Cargo.toml --workspace
|
||||
|
||||
clippy-main:
|
||||
cargo clippy --workspace -- -D warnings
|
||||
test-$(1)-expensive:
|
||||
cargo test --manifest-path $(2)/Cargo.toml --workspace -- --ignored
|
||||
|
||||
clippy-main-examples:
|
||||
cargo clippy --workspace --examples -- -D warnings
|
||||
build-$(1):
|
||||
cargo build --manifest-path $(2)/Cargo.toml --workspace $(3)
|
||||
|
||||
clippy-wasm:
|
||||
cargo clippy --manifest-path clients/webassembly/Cargo.toml --target wasm32-unknown-unknown --workspace -- -D warnings
|
||||
build-$(1)-examples:
|
||||
cargo build --manifest-path $(2)/Cargo.toml --workspace --examples
|
||||
|
||||
fmt-$(1):
|
||||
cargo fmt --manifest-path $(2)/Cargo.toml --all
|
||||
|
||||
clippy-all-contracts:
|
||||
cargo clippy --workspace --manifest-path contracts/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
|
||||
clippy-happy: clippy-happy-$(1)
|
||||
clippy-all: clippy-$(1) clippy-$(1)-examples
|
||||
cargo-test: test-$(1)
|
||||
cargo-test-expensive: test-$(1)-expensive
|
||||
build: build-$(1) build-$(1)-examples
|
||||
fmt: fmt-$(1)
|
||||
|
||||
clippy-all-wallet:
|
||||
cargo clippy --workspace --manifest-path nym-wallet/Cargo.toml --all-features -- -D warnings
|
||||
endef
|
||||
|
||||
clippy-all-connect:
|
||||
cargo clippy --workspace --manifest-path nym-connect/desktop/Cargo.toml --all-features -- -D warnings
|
||||
# -----------------------------------------------------------------------------
|
||||
# Rust workspaces
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
clippy-all-connect-mobile:
|
||||
cargo clippy --workspace --manifest-path nym-connect/mobile/src-tauri/Cargo.toml --all-features -- -D warnings
|
||||
# Generate targets for the various cargo workspaces
|
||||
|
||||
clippy-all-wasm-client:
|
||||
cargo clippy --workspace --manifest-path clients/webassembly/Cargo.toml --all-features --target wasm32-unknown-unknown -- -D warnings
|
||||
$(eval $(call add_cargo_workspace,main,.))
|
||||
$(eval $(call add_cargo_workspace,contracts,contracts,--target wasm32-unknown-unknown))
|
||||
$(eval $(call add_cargo_workspace,wasm-client,clients/webassembly,--target wasm32-unknown-unknown))
|
||||
$(eval $(call add_cargo_workspace,wallet,nym-wallet,))
|
||||
$(eval $(call add_cargo_workspace,connect,nym-connect/desktop))
|
||||
ifndef NYM_NO_MOBILE
|
||||
$(eval $(call add_cargo_workspace,connect-mobile,nym-connect/mobile/src-tauri))
|
||||
endif
|
||||
|
||||
test-main:
|
||||
cargo test --workspace
|
||||
|
||||
test-main-expensive:
|
||||
cargo test --workspace -- --ignored
|
||||
|
||||
test-contracts:
|
||||
cargo test --manifest-path contracts/Cargo.toml --all-features
|
||||
|
||||
test-contracts-expensive:
|
||||
cargo test --manifest-path contracts/Cargo.toml --all-features -- --ignored
|
||||
|
||||
test-wallet:
|
||||
cargo test --manifest-path nym-wallet/Cargo.toml --all-features
|
||||
|
||||
test-wallet-expensive:
|
||||
cargo test --manifest-path nym-wallet/Cargo.toml --all-features -- --ignored
|
||||
|
||||
test-connect:
|
||||
cargo test --manifest-path nym-connect/desktop/Cargo.toml --all-features
|
||||
|
||||
test-connect-expensive:
|
||||
cargo test --manifest-path nym-connect/desktop/Cargo.toml --all-features -- --ignored
|
||||
|
||||
test-connect-mobile:
|
||||
cargo test --manifest-path nym-connect/mobile/src-tauri/Cargo.toml --all-features
|
||||
|
||||
test-connect-mobile-expensive:
|
||||
cargo test --manifest-path nym-connect/mobile/src-tauri/Cargo.toml --all-features -- --ignored
|
||||
|
||||
build-main:
|
||||
cargo build --workspace
|
||||
|
||||
build-main-examples:
|
||||
cargo build --workspace --examples
|
||||
|
||||
build-contracts:
|
||||
cargo build --manifest-path contracts/Cargo.toml --workspace
|
||||
|
||||
build-wallet:
|
||||
cargo build --manifest-path nym-wallet/Cargo.toml --workspace
|
||||
|
||||
build-connect:
|
||||
cargo build --manifest-path nym-connect/desktop/Cargo.toml --workspace
|
||||
|
||||
build-connect-mobile:
|
||||
cargo build --manifest-path nym-connect/mobile/src-tauri/Cargo.toml --workspace
|
||||
# -----------------------------------------------------------------------------
|
||||
# Convenience targets for crates that are already part of the main workspace
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
build-explorer-api:
|
||||
cargo build --manifest-path explorer-api/Cargo.toml --workspace
|
||||
|
||||
build-wasm-client:
|
||||
cargo build --manifest-path clients/webassembly/Cargo.toml --workspace --target wasm32-unknown-unknown
|
||||
cargo build -p explorer-api
|
||||
|
||||
build-nym-cli:
|
||||
cargo build --release --manifest-path tools/nym-cli/Cargo.toml
|
||||
cargo build -p nym-cli --release
|
||||
|
||||
fmt-main:
|
||||
cargo fmt --all
|
||||
|
||||
fmt-contracts:
|
||||
cargo fmt --manifest-path contracts/Cargo.toml --all
|
||||
|
||||
fmt-wallet:
|
||||
cargo fmt --manifest-path nym-wallet/Cargo.toml --all
|
||||
|
||||
fmt-connect:
|
||||
cargo fmt --manifest-path nym-connect/desktop/Cargo.toml --all
|
||||
|
||||
fmt-connect-mobile:
|
||||
cargo fmt --manifest-path nym-connect/mobile/src-tauri/Cargo.toml --all
|
||||
|
||||
fmt-wasm-client:
|
||||
cargo fmt --manifest-path clients/webassembly/Cargo.toml --all
|
||||
# -----------------------------------------------------------------------------
|
||||
# Misc
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
wasm:
|
||||
RUSTFLAGS='-C link-arg=-s' cargo build --manifest-path contracts/Cargo.toml --release --target wasm32-unknown-unknown
|
||||
wasm-opt -Os contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm -o contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm
|
||||
wasm-opt -Os contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm -o contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm
|
||||
wasm-opt --disable-sign-ext -Os contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm -o contracts/target/wasm32-unknown-unknown/release/vesting_contract.wasm
|
||||
wasm-opt --disable-sign-ext -Os contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm -o contracts/target/wasm32-unknown-unknown/release/mixnet_contract.wasm
|
||||
|
||||
# NOTE: this seems deprecated an not needed anymore?
|
||||
mixnet-opt: wasm
|
||||
cd contracts/mixnet && make opt
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "client-core"
|
||||
version = "1.1.12"
|
||||
version = "1.1.14"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.66"
|
||||
|
||||
@@ -5,7 +5,11 @@ use crate::{client::replies::reply_storage, config::DebugConfig};
|
||||
|
||||
pub fn setup_empty_reply_surb_backend(debug_config: &DebugConfig) -> reply_storage::Empty {
|
||||
reply_storage::Empty {
|
||||
min_surb_threshold: debug_config.minimum_reply_surb_storage_threshold,
|
||||
max_surb_threshold: debug_config.maximum_reply_surb_storage_threshold,
|
||||
min_surb_threshold: debug_config
|
||||
.reply_surbs
|
||||
.minimum_reply_surb_storage_threshold,
|
||||
max_surb_threshold: debug_config
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_storage_threshold,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::addressing::nodes::NodeIdentity;
|
||||
use nym_sphinx::receiver::ReconstructedMessage;
|
||||
use nym_sphinx::receiver::{ReconstructedMessage, SphinxMessageReceiver};
|
||||
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
|
||||
use nym_task::{TaskClient, TaskManager};
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
@@ -236,15 +236,15 @@ where
|
||||
|
||||
let mut stream = LoopCoverTrafficStream::new(
|
||||
ack_key,
|
||||
debug_config.average_ack_delay,
|
||||
debug_config.average_packet_delay,
|
||||
debug_config.loop_cover_traffic_average_delay,
|
||||
debug_config.acknowledgements.average_ack_delay,
|
||||
debug_config.traffic.average_packet_delay,
|
||||
debug_config.cover_traffic.loop_cover_traffic_average_delay,
|
||||
mix_tx,
|
||||
self_address,
|
||||
topology_accessor,
|
||||
);
|
||||
|
||||
if let Some(size) = debug_config.use_extended_packet_size {
|
||||
if let Some(size) = debug_config.traffic.use_extended_packet_size {
|
||||
log::debug!("Setting extended packet size: {:?}", size);
|
||||
stream.set_custom_packet_size(size.into());
|
||||
}
|
||||
@@ -294,14 +294,15 @@ where
|
||||
shutdown: TaskClient,
|
||||
) {
|
||||
info!("Starting received messages buffer controller...");
|
||||
ReceivedMessagesBufferController::new(
|
||||
local_encryption_keypair,
|
||||
query_receiver,
|
||||
mixnet_receiver,
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
)
|
||||
.start_with_shutdown(shutdown)
|
||||
let controller: ReceivedMessagesBufferController<SphinxMessageReceiver> =
|
||||
ReceivedMessagesBufferController::new(
|
||||
local_encryption_keypair,
|
||||
query_receiver,
|
||||
mixnet_receiver,
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
);
|
||||
controller.start_with_shutdown(shutdown)
|
||||
}
|
||||
|
||||
async fn start_gateway_client(
|
||||
@@ -337,7 +338,9 @@ where
|
||||
shared_key,
|
||||
mixnet_message_sender,
|
||||
ack_sender,
|
||||
self.debug_config.gateway_response_timeout,
|
||||
self.debug_config
|
||||
.gateway_connection
|
||||
.gateway_response_timeout,
|
||||
self.bandwidth_controller.take(),
|
||||
shutdown,
|
||||
);
|
||||
@@ -499,7 +502,7 @@ where
|
||||
);
|
||||
Self::start_topology_refresher(
|
||||
topology_provider,
|
||||
self.debug_config.topology_refresh_rate,
|
||||
self.debug_config.topology.topology_refresh_rate,
|
||||
shared_topology_accessor.clone(),
|
||||
task_manager.subscribe(),
|
||||
)
|
||||
@@ -535,7 +538,7 @@ where
|
||||
self_address,
|
||||
);
|
||||
|
||||
if let Some(size) = self.debug_config.use_extended_packet_size {
|
||||
if let Some(size) = self.debug_config.traffic.use_extended_packet_size {
|
||||
log::debug!("Setting extended packet size: {:?}", size);
|
||||
controller_config.set_custom_packet_size(size.into());
|
||||
}
|
||||
@@ -554,7 +557,11 @@ where
|
||||
task_manager.subscribe(),
|
||||
);
|
||||
|
||||
if !self.debug_config.disable_loop_cover_traffic_stream {
|
||||
if !self
|
||||
.debug_config
|
||||
.cover_traffic
|
||||
.disable_loop_cover_traffic_stream
|
||||
{
|
||||
Self::start_cover_traffic_stream(
|
||||
self.debug_config,
|
||||
self.key_manager.ack_key(),
|
||||
|
||||
@@ -30,8 +30,12 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||
// it will only be happening on the very first run and in practice won't incur huge
|
||||
// costs since the storage is going to be empty
|
||||
let mem_store = CombinedReplyStorage::new(
|
||||
debug_config.minimum_reply_surb_storage_threshold,
|
||||
debug_config.maximum_reply_surb_storage_threshold,
|
||||
debug_config
|
||||
.reply_surbs
|
||||
.minimum_reply_surb_storage_threshold,
|
||||
debug_config
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_storage_threshold,
|
||||
);
|
||||
storage_backend
|
||||
.init_fresh(&mem_store)
|
||||
@@ -46,8 +50,12 @@ async fn setup_fresh_backend<P: AsRef<Path>>(
|
||||
fn setup_inactive_backend(debug_config: &DebugConfig) -> fs_backend::Backend {
|
||||
info!("creating inactive surb database");
|
||||
fs_backend::Backend::new_inactive(
|
||||
debug_config.minimum_reply_surb_storage_threshold,
|
||||
debug_config.maximum_reply_surb_storage_threshold,
|
||||
debug_config
|
||||
.reply_surbs
|
||||
.minimum_reply_surb_storage_threshold,
|
||||
debug_config
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_storage_threshold,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+11
@@ -10,6 +10,7 @@ use nym_sphinx::{
|
||||
chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
/// Module responsible for listening for any data resembling acknowledgements from the network
|
||||
/// and firing actions to remove them from the 'Pending' state.
|
||||
@@ -48,10 +49,20 @@ impl AcknowledgementListener {
|
||||
// because nothing was inserted in the first place
|
||||
if frag_id == COVER_FRAG_ID {
|
||||
trace!("Received an ack for a cover message - no need to do anything");
|
||||
let time = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
println!("cover ack : _{}", time);
|
||||
return;
|
||||
}
|
||||
|
||||
trace!("Received {} from the mix network", frag_id);
|
||||
let time = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
println!("real ack : /{:?}/{}", frag_id, time);
|
||||
|
||||
self.action_sender
|
||||
.unbounded_send(Action::new_remove(frag_id))
|
||||
|
||||
+16
@@ -9,6 +9,7 @@ use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
/// Module responsible for dealing with the received messages: splitting them, creating acknowledgements,
|
||||
/// putting everything into sphinx packets, etc.
|
||||
@@ -48,6 +49,11 @@ where
|
||||
lane: TransmissionLane,
|
||||
) {
|
||||
// offload reply handling to the dedicated task
|
||||
let time = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
println!("sending reply: _{}_{}", time, data.len());
|
||||
self.reply_controller_sender
|
||||
.send_reply(recipient_tag, data, lane)
|
||||
}
|
||||
@@ -58,6 +64,11 @@ where
|
||||
content: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
) {
|
||||
let time = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
println!("sending plain: _{}_{}", time, content.len());
|
||||
if let Err(err) = self
|
||||
.message_handler
|
||||
.try_send_plain_message(recipient, content, lane)
|
||||
@@ -74,6 +85,11 @@ where
|
||||
reply_surbs: u32,
|
||||
lane: TransmissionLane,
|
||||
) {
|
||||
let time = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
println!("sending anonymous: _{}_{}", time, content.len());
|
||||
if let Err(err) = self
|
||||
.message_handler
|
||||
.try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane)
|
||||
|
||||
@@ -153,25 +153,35 @@ impl Config {
|
||||
ack_key,
|
||||
self_recipient,
|
||||
packet_size: Default::default(),
|
||||
ack_wait_addition: base_client_debug_config.ack_wait_addition,
|
||||
ack_wait_multiplier: base_client_debug_config.ack_wait_multiplier,
|
||||
average_message_sending_delay: base_client_debug_config.message_sending_average_delay,
|
||||
average_packet_delay_duration: base_client_debug_config.average_packet_delay,
|
||||
average_ack_delay_duration: base_client_debug_config.average_ack_delay,
|
||||
ack_wait_addition: base_client_debug_config.acknowledgements.ack_wait_addition,
|
||||
ack_wait_multiplier: base_client_debug_config
|
||||
.acknowledgements
|
||||
.ack_wait_multiplier,
|
||||
average_message_sending_delay: base_client_debug_config
|
||||
.traffic
|
||||
.message_sending_average_delay,
|
||||
average_packet_delay_duration: base_client_debug_config.traffic.average_packet_delay,
|
||||
average_ack_delay_duration: base_client_debug_config.acknowledgements.average_ack_delay,
|
||||
disable_main_poisson_packet_distribution: base_client_debug_config
|
||||
.traffic
|
||||
.disable_main_poisson_packet_distribution,
|
||||
minimum_reply_surb_request_size: base_client_debug_config
|
||||
.reply_surbs
|
||||
.minimum_reply_surb_request_size,
|
||||
maximum_reply_surb_request_size: base_client_debug_config
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_request_size,
|
||||
maximum_allowed_reply_surb_request_size: base_client_debug_config
|
||||
.reply_surbs
|
||||
.maximum_allowed_reply_surb_request_size,
|
||||
maximum_reply_surb_rerequest_waiting_period: base_client_debug_config
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_rerequest_waiting_period,
|
||||
maximum_reply_surb_drop_waiting_period: base_client_debug_config
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_drop_waiting_period,
|
||||
maximum_reply_surb_age: base_client_debug_config.maximum_reply_surb_age,
|
||||
maximum_reply_key_age: base_client_debug_config.maximum_reply_key_age,
|
||||
maximum_reply_surb_age: base_client_debug_config.reply_surbs.maximum_reply_surb_age,
|
||||
maximum_reply_key_age: base_client_debug_config.reply_surbs.maximum_reply_key_age,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ use rand::{CryptoRng, Rng};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time;
|
||||
@@ -232,7 +233,11 @@ where
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let time = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
println!("cover sent_{}_{:?}",time, self.config.cover_packet_size.size());
|
||||
(
|
||||
generate_loop_cover_packet(
|
||||
&mut self.rng,
|
||||
@@ -250,6 +255,11 @@ where
|
||||
)
|
||||
}
|
||||
StreamMessage::Real(real_message) => {
|
||||
let time = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
println!("real sent: /{:?}/{}/{}", real_message.fragment_id, time, real_message.mix_packet.sphinx_packet().payload.len());
|
||||
(real_message.mix_packet, Some(real_message.fragment_id))
|
||||
}
|
||||
};
|
||||
@@ -297,22 +307,29 @@ where
|
||||
self.sending_delay_controller.current_multiplier()
|
||||
);
|
||||
|
||||
// Even just a single used slot is enough to signal backpressure
|
||||
if used_slots > 0 {
|
||||
if self
|
||||
.sending_delay_controller
|
||||
.is_backpressure_currently_detected(used_slots)
|
||||
{
|
||||
log::trace!("Backpressure detected");
|
||||
self.sending_delay_controller.record_backpressure_detected();
|
||||
}
|
||||
|
||||
// If the buffer is running out, slow down the sending rate
|
||||
// If the buffer is running out, slow down the sending rate by increasing the delay
|
||||
// multiplier.
|
||||
if self.mix_tx.capacity() == 0
|
||||
&& self.sending_delay_controller.not_increased_delay_recently()
|
||||
{
|
||||
self.sending_delay_controller.increase_delay_multiplier();
|
||||
}
|
||||
|
||||
// Very carefully step up the sending rate in case it seems like we can solidly handle the
|
||||
// current rate.
|
||||
if self.sending_delay_controller.is_sending_reliable() {
|
||||
// If it looks like we are sending reliably, increase the sending rate by decreasing the
|
||||
// sending delay multiplier.
|
||||
if !self
|
||||
.sending_delay_controller
|
||||
.was_backpressure_detected_recently()
|
||||
&& self.sending_delay_controller.not_decreased_delay_recently()
|
||||
{
|
||||
self.sending_delay_controller.decrease_delay_multiplier();
|
||||
}
|
||||
}
|
||||
|
||||
+23
-15
@@ -11,11 +11,14 @@ const INCREASE_DELAY_MIN_CHANGE_INTERVAL_SECS: u64 = 1;
|
||||
// The minimum time between decreasing the average delay between packets. We don't want to change
|
||||
// to quickly to keep things somewhat stable. Also there are buffers downstreams meaning we need to
|
||||
// wait a little to see the effect before we decrease further.
|
||||
const DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS: u64 = 30;
|
||||
const DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS: u64 = 3;
|
||||
// The queue length that is required for us to register that backpressure occured. If there are
|
||||
// more than this many packets waiting to be sent, we consider the channel to be under
|
||||
// backpressure.
|
||||
const BACKPRESSURE_THRESHOLD: usize = 10;
|
||||
// If we enough time passes without any sign of backpressure in the channel, we can consider
|
||||
// lowering the average delay. The goal is to keep somewhat stable, rather than maxing out
|
||||
// bandwidth at all times.
|
||||
const ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS: u64 = 30;
|
||||
// lowering the average delay.
|
||||
const ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS: u64 = 1;
|
||||
// The maximum multiplier we apply to the base average Poisson delay.
|
||||
const MAX_DELAY_MULTIPLIER: u32 = 6;
|
||||
// The minium multiplier we apply to the base average Poisson delay.
|
||||
@@ -100,22 +103,27 @@ impl SendingDelayController {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn record_backpressure_detected(&mut self) {
|
||||
self.time_when_backpressure_detected = get_time_now();
|
||||
}
|
||||
|
||||
pub(crate) fn not_increased_delay_recently(&self) -> bool {
|
||||
get_time_now()
|
||||
> self.time_when_changed + Duration::from_secs(INCREASE_DELAY_MIN_CHANGE_INTERVAL_SECS)
|
||||
}
|
||||
|
||||
pub(crate) fn is_sending_reliable(&self) -> bool {
|
||||
let now = get_time_now();
|
||||
let delay_change_interval = Duration::from_secs(DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS);
|
||||
let acceptable_time_without_backpressure =
|
||||
Duration::from_secs(ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS);
|
||||
pub(crate) fn not_decreased_delay_recently(&self) -> bool {
|
||||
get_time_now()
|
||||
> self.time_when_changed + Duration::from_secs(DECREASE_DELAY_MIN_CHANGE_INTERVAL_SECS)
|
||||
}
|
||||
|
||||
now > self.time_when_backpressure_detected + acceptable_time_without_backpressure
|
||||
&& now > self.time_when_changed + delay_change_interval
|
||||
pub(crate) fn is_backpressure_currently_detected(&self, queue_length: usize) -> bool {
|
||||
queue_length > BACKPRESSURE_THRESHOLD
|
||||
}
|
||||
|
||||
pub(crate) fn record_backpressure_detected(&mut self) {
|
||||
self.time_when_backpressure_detected = get_time_now();
|
||||
}
|
||||
|
||||
pub(crate) fn was_backpressure_detected_recently(&self) -> bool {
|
||||
get_time_now()
|
||||
< self.time_when_backpressure_detected
|
||||
+ Duration::from_secs(ACCEPTABLE_TIME_WITHOUT_BACKPRESSURE_SECS)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ use nym_sphinx::params::ReplySurbKeyDigestAlgorithm;
|
||||
use nym_sphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
// Buffer Requests to say "hey, send any reconstructed messages to this channel"
|
||||
// or to say "hey, I'm going offline, don't send anything more to me. Just buffer them instead"
|
||||
@@ -30,13 +31,13 @@ pub type ReceivedBufferRequestReceiver = mpsc::UnboundedReceiver<ReceivedBufferM
|
||||
pub type ReconstructedMessagesSender = mpsc::UnboundedSender<Vec<ReconstructedMessage>>;
|
||||
pub type ReconstructedMessagesReceiver = mpsc::UnboundedReceiver<Vec<ReconstructedMessage>>;
|
||||
|
||||
struct ReceivedMessagesBufferInner {
|
||||
struct ReceivedMessagesBufferInner<R: MessageReceiver> {
|
||||
messages: Vec<ReconstructedMessage>,
|
||||
local_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
|
||||
// TODO: looking how it 'looks' here, perhaps `MessageReceiver` should be renamed to something
|
||||
// else instead.
|
||||
message_receiver: MessageReceiver,
|
||||
message_receiver: R,
|
||||
message_sender: Option<ReconstructedMessagesSender>,
|
||||
|
||||
// TODO: this will get cleared upon re-running the client
|
||||
@@ -45,10 +46,16 @@ struct ReceivedMessagesBufferInner {
|
||||
recently_reconstructed: HashSet<i32>,
|
||||
}
|
||||
|
||||
impl ReceivedMessagesBufferInner {
|
||||
impl<R: MessageReceiver> ReceivedMessagesBufferInner<R> {
|
||||
fn recover_from_fragment(&mut self, fragment_data: &[u8]) -> Option<NymMessage> {
|
||||
let fragment_len = fragment_data.len();
|
||||
if nym_sphinx::cover::is_cover(fragment_data) {
|
||||
trace!("The message was a loop cover message! Skipping it");
|
||||
let time = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
println!("Cover received_{}_{}",time, fragment_len);
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -60,6 +67,12 @@ impl ReceivedMessagesBufferInner {
|
||||
Ok(frag) => frag,
|
||||
};
|
||||
|
||||
let time = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
println!("real received : _{:?}_{}_{}_{}", &fragment.id(),&fragment.current_fragment(),time, fragment_len);
|
||||
|
||||
if self.recently_reconstructed.contains(&fragment.id()) {
|
||||
debug!("Received a chunk of already re-assembled message ({:?})! It probably got here because the ack got lost", fragment.id());
|
||||
return None;
|
||||
@@ -102,13 +115,13 @@ impl ReceivedMessagesBufferInner {
|
||||
&mut self,
|
||||
reply_ciphertext: &mut [u8],
|
||||
reply_key: SurbEncryptionKey,
|
||||
) -> Option<NymMessage> {
|
||||
) -> Result<Option<NymMessage>, MessageRecoveryError> {
|
||||
// note: this performs decryption IN PLACE without extra allocation
|
||||
self.message_receiver
|
||||
.recover_plaintext_from_reply(reply_ciphertext, reply_key);
|
||||
.recover_plaintext_from_reply(reply_ciphertext, reply_key)?;
|
||||
let fragment_data = reply_ciphertext;
|
||||
|
||||
self.recover_from_fragment(fragment_data)
|
||||
Ok(self.recover_from_fragment(fragment_data))
|
||||
}
|
||||
|
||||
fn process_received_regular_packet(&mut self, mut raw_fragment: Vec<u8>) -> Option<NymMessage> {
|
||||
@@ -130,13 +143,13 @@ impl ReceivedMessagesBufferInner {
|
||||
#[derive(Debug, Clone)]
|
||||
// Note: you should NEVER create more than a single instance of this using 'new()'.
|
||||
// You should always use .clone() to create additional instances
|
||||
struct ReceivedMessagesBuffer {
|
||||
inner: Arc<Mutex<ReceivedMessagesBufferInner>>,
|
||||
struct ReceivedMessagesBuffer<R: MessageReceiver> {
|
||||
inner: Arc<Mutex<ReceivedMessagesBufferInner<R>>>,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
}
|
||||
|
||||
impl ReceivedMessagesBuffer {
|
||||
impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
fn new(
|
||||
local_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
@@ -146,7 +159,7 @@ impl ReceivedMessagesBuffer {
|
||||
inner: Arc::new(Mutex::new(ReceivedMessagesBufferInner {
|
||||
messages: Vec::new(),
|
||||
local_encryption_keypair,
|
||||
message_receiver: MessageReceiver::new(),
|
||||
message_receiver: R::new(),
|
||||
message_sender: None,
|
||||
recently_reconstructed: HashSet::new(),
|
||||
})),
|
||||
@@ -328,7 +341,10 @@ impl ReceivedMessagesBuffer {
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_new_received(&mut self, msgs: Vec<Vec<u8>>) {
|
||||
async fn handle_new_received(
|
||||
&mut self,
|
||||
msgs: Vec<Vec<u8>>,
|
||||
) -> Result<(), MessageRecoveryError> {
|
||||
trace!(
|
||||
"Processing {:?} new message that might get added to the buffer!",
|
||||
msgs.len()
|
||||
@@ -344,7 +360,7 @@ impl ReceivedMessagesBuffer {
|
||||
// if yes - this is a reply message
|
||||
let completed_message =
|
||||
if let Some((reply_key, reply_message)) = self.get_reply_key(&mut msg) {
|
||||
inner_guard.process_received_reply(reply_message, reply_key)
|
||||
inner_guard.process_received_reply(reply_message, reply_key)?
|
||||
} else {
|
||||
inner_guard.process_received_regular_packet(msg)
|
||||
};
|
||||
@@ -360,6 +376,7 @@ impl ReceivedMessagesBuffer {
|
||||
if !completed_messages.is_empty() {
|
||||
self.handle_reconstructed_messages(completed_messages).await
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,14 +389,14 @@ pub enum ReceivedBufferMessage {
|
||||
ReceiverDisconnect,
|
||||
}
|
||||
|
||||
struct RequestReceiver {
|
||||
received_buffer: ReceivedMessagesBuffer,
|
||||
struct RequestReceiver<R: MessageReceiver> {
|
||||
received_buffer: ReceivedMessagesBuffer<R>,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
}
|
||||
|
||||
impl RequestReceiver {
|
||||
impl<R: MessageReceiver> RequestReceiver<R> {
|
||||
fn new(
|
||||
received_buffer: ReceivedMessagesBuffer,
|
||||
received_buffer: ReceivedMessagesBuffer<R>,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
) -> Self {
|
||||
RequestReceiver {
|
||||
@@ -422,14 +439,14 @@ impl RequestReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
struct FragmentedMessageReceiver {
|
||||
received_buffer: ReceivedMessagesBuffer,
|
||||
struct FragmentedMessageReceiver<R: MessageReceiver> {
|
||||
received_buffer: ReceivedMessagesBuffer<R>,
|
||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||
}
|
||||
|
||||
impl FragmentedMessageReceiver {
|
||||
impl<R: MessageReceiver> FragmentedMessageReceiver<R> {
|
||||
fn new(
|
||||
received_buffer: ReceivedMessagesBuffer,
|
||||
received_buffer: ReceivedMessagesBuffer<R>,
|
||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||
) -> Self {
|
||||
FragmentedMessageReceiver {
|
||||
@@ -438,13 +455,16 @@ impl FragmentedMessageReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_with_shutdown(&mut self, mut shutdown: nym_task::TaskClient) {
|
||||
async fn run_with_shutdown(
|
||||
&mut self,
|
||||
mut shutdown: nym_task::TaskClient,
|
||||
) -> Result<(), MessageRecoveryError> {
|
||||
debug!("Started FragmentedMessageReceiver with graceful shutdown support");
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
new_messages = self.mixnet_packet_receiver.next() => {
|
||||
if let Some(new_messages) = new_messages {
|
||||
self.received_buffer.handle_new_received(new_messages).await;
|
||||
self.received_buffer.handle_new_received(new_messages).await?;
|
||||
} else {
|
||||
log::trace!("FragmentedMessageReceiver: Stopping since channel closed");
|
||||
break;
|
||||
@@ -457,15 +477,16 @@ impl FragmentedMessageReceiver {
|
||||
}
|
||||
shutdown.recv_timeout().await;
|
||||
log::debug!("FragmentedMessageReceiver: Exiting");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ReceivedMessagesBufferController {
|
||||
fragmented_message_receiver: FragmentedMessageReceiver,
|
||||
request_receiver: RequestReceiver,
|
||||
pub(crate) struct ReceivedMessagesBufferController<R: MessageReceiver> {
|
||||
fragmented_message_receiver: FragmentedMessageReceiver<R>,
|
||||
request_receiver: RequestReceiver<R>,
|
||||
}
|
||||
|
||||
impl ReceivedMessagesBufferController {
|
||||
impl<R: MessageReceiver + Clone + Send + 'static> ReceivedMessagesBufferController<R> {
|
||||
pub(crate) fn new(
|
||||
local_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
@@ -494,9 +515,13 @@ impl ReceivedMessagesBufferController {
|
||||
|
||||
let shutdown_handle = shutdown.clone();
|
||||
spawn_future(async move {
|
||||
fragmented_message_receiver
|
||||
match fragmented_message_receiver
|
||||
.run_with_shutdown(shutdown_handle)
|
||||
.await;
|
||||
.await
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(e) => error!("{e}"),
|
||||
}
|
||||
});
|
||||
spawn_future(async move {
|
||||
request_receiver.run_with_shutdown(shutdown).await;
|
||||
|
||||
@@ -35,8 +35,12 @@ impl ReplyStorageBackend for Backend {
|
||||
) -> Result<Self, Self::StorageError> {
|
||||
Ok(Backend {
|
||||
empty: Empty {
|
||||
min_surb_threshold: debug_config.minimum_reply_surb_storage_threshold,
|
||||
max_surb_threshold: debug_config.maximum_reply_surb_storage_threshold,
|
||||
min_surb_threshold: debug_config
|
||||
.reply_surbs
|
||||
.minimum_reply_surb_storage_threshold,
|
||||
max_surb_threshold: debug_config
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_storage_threshold,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -35,8 +35,12 @@ impl ReplyStorageBackend for Empty {
|
||||
_db_path: Option<PathBuf>,
|
||||
) -> Result<Self, Self::StorageError> {
|
||||
Ok(Self {
|
||||
min_surb_threshold: debug_config.minimum_reply_surb_storage_threshold,
|
||||
max_surb_threshold: debug_config.maximum_reply_surb_storage_threshold,
|
||||
min_surb_threshold: debug_config
|
||||
.reply_surbs
|
||||
.minimum_reply_surb_storage_threshold,
|
||||
max_surb_threshold: debug_config
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_storage_threshold,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ use url::Url;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
pub mod old_config_v1_1_13;
|
||||
pub mod persistence;
|
||||
|
||||
pub const MISSING_VALUE: &str = "MISSING VALUE";
|
||||
@@ -210,11 +211,12 @@ impl<T> Config<T> {
|
||||
}
|
||||
|
||||
pub fn set_high_default_traffic_volume(&mut self) {
|
||||
self.debug.average_packet_delay = Duration::from_millis(10);
|
||||
self.debug.traffic.average_packet_delay = Duration::from_millis(10);
|
||||
// basically don't really send cover messages
|
||||
self.debug.loop_cover_traffic_average_delay = Duration::from_millis(2_000_000);
|
||||
self.debug.cover_traffic.loop_cover_traffic_average_delay =
|
||||
Duration::from_millis(2_000_000);
|
||||
// 250 "real" messages / s
|
||||
self.debug.message_sending_average_delay = Duration::from_millis(4);
|
||||
self.debug.traffic.message_sending_average_delay = Duration::from_millis(4);
|
||||
}
|
||||
|
||||
pub fn with_disabled_cover_traffic(mut self, disabled: bool) -> Self {
|
||||
@@ -225,8 +227,8 @@ impl<T> Config<T> {
|
||||
}
|
||||
|
||||
pub fn set_no_cover_traffic(&mut self) {
|
||||
self.debug.disable_loop_cover_traffic_stream = true;
|
||||
self.debug.disable_main_poisson_packet_distribution = true;
|
||||
self.debug.cover_traffic.disable_loop_cover_traffic_stream = true;
|
||||
self.debug.traffic.disable_main_poisson_packet_distribution = true;
|
||||
}
|
||||
|
||||
pub fn set_custom_version(&mut self, version: &str) {
|
||||
@@ -311,87 +313,93 @@ impl<T> Config<T> {
|
||||
}
|
||||
|
||||
pub fn get_average_packet_delay(&self) -> Duration {
|
||||
self.debug.average_packet_delay
|
||||
self.debug.traffic.average_packet_delay
|
||||
}
|
||||
|
||||
pub fn get_average_ack_delay(&self) -> Duration {
|
||||
self.debug.average_ack_delay
|
||||
self.debug.acknowledgements.average_ack_delay
|
||||
}
|
||||
|
||||
pub fn get_ack_wait_multiplier(&self) -> f64 {
|
||||
self.debug.ack_wait_multiplier
|
||||
self.debug.acknowledgements.ack_wait_multiplier
|
||||
}
|
||||
|
||||
pub fn get_ack_wait_addition(&self) -> Duration {
|
||||
self.debug.ack_wait_addition
|
||||
self.debug.acknowledgements.ack_wait_addition
|
||||
}
|
||||
|
||||
pub fn get_loop_cover_traffic_average_delay(&self) -> Duration {
|
||||
self.debug.loop_cover_traffic_average_delay
|
||||
self.debug.cover_traffic.loop_cover_traffic_average_delay
|
||||
}
|
||||
|
||||
pub fn get_message_sending_average_delay(&self) -> Duration {
|
||||
self.debug.message_sending_average_delay
|
||||
self.debug.traffic.message_sending_average_delay
|
||||
}
|
||||
|
||||
pub fn get_gateway_response_timeout(&self) -> Duration {
|
||||
self.debug.gateway_response_timeout
|
||||
self.debug.gateway_connection.gateway_response_timeout
|
||||
}
|
||||
|
||||
pub fn get_topology_refresh_rate(&self) -> Duration {
|
||||
self.debug.topology_refresh_rate
|
||||
self.debug.topology.topology_refresh_rate
|
||||
}
|
||||
|
||||
pub fn get_topology_resolution_timeout(&self) -> Duration {
|
||||
self.debug.topology_resolution_timeout
|
||||
self.debug.topology.topology_resolution_timeout
|
||||
}
|
||||
|
||||
pub fn get_disabled_loop_cover_traffic_stream(&self) -> bool {
|
||||
self.debug.disable_loop_cover_traffic_stream
|
||||
self.debug.cover_traffic.disable_loop_cover_traffic_stream
|
||||
}
|
||||
|
||||
pub fn get_disabled_main_poisson_packet_distribution(&self) -> bool {
|
||||
self.debug.disable_main_poisson_packet_distribution
|
||||
self.debug.traffic.disable_main_poisson_packet_distribution
|
||||
}
|
||||
|
||||
pub fn get_use_extended_packet_size(&self) -> Option<ExtendedPacketSize> {
|
||||
self.debug.use_extended_packet_size
|
||||
self.debug.traffic.use_extended_packet_size
|
||||
}
|
||||
|
||||
pub fn get_minimum_reply_surb_storage_threshold(&self) -> usize {
|
||||
self.debug.minimum_reply_surb_storage_threshold
|
||||
self.debug.reply_surbs.minimum_reply_surb_storage_threshold
|
||||
}
|
||||
|
||||
pub fn get_maximum_reply_surb_storage_threshold(&self) -> usize {
|
||||
self.debug.maximum_reply_surb_storage_threshold
|
||||
self.debug.reply_surbs.maximum_reply_surb_storage_threshold
|
||||
}
|
||||
|
||||
pub fn get_minimum_reply_surb_request_size(&self) -> u32 {
|
||||
self.debug.minimum_reply_surb_request_size
|
||||
self.debug.reply_surbs.minimum_reply_surb_request_size
|
||||
}
|
||||
|
||||
pub fn get_maximum_reply_surb_request_size(&self) -> u32 {
|
||||
self.debug.maximum_reply_surb_request_size
|
||||
self.debug.reply_surbs.maximum_reply_surb_request_size
|
||||
}
|
||||
|
||||
pub fn get_maximum_allowed_reply_surb_request_size(&self) -> u32 {
|
||||
self.debug.maximum_allowed_reply_surb_request_size
|
||||
self.debug
|
||||
.reply_surbs
|
||||
.maximum_allowed_reply_surb_request_size
|
||||
}
|
||||
|
||||
pub fn get_maximum_reply_surb_rerequest_waiting_period(&self) -> Duration {
|
||||
self.debug.maximum_reply_surb_rerequest_waiting_period
|
||||
self.debug
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_rerequest_waiting_period
|
||||
}
|
||||
|
||||
pub fn get_maximum_reply_surb_drop_waiting_period(&self) -> Duration {
|
||||
self.debug.maximum_reply_surb_drop_waiting_period
|
||||
self.debug
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_drop_waiting_period
|
||||
}
|
||||
|
||||
pub fn get_maximum_reply_surb_age(&self) -> Duration {
|
||||
self.debug.maximum_reply_surb_age
|
||||
self.debug.reply_surbs.maximum_reply_surb_age
|
||||
}
|
||||
|
||||
pub fn get_maximum_reply_key_age(&self) -> Duration {
|
||||
self.debug.maximum_reply_key_age
|
||||
self.debug.reply_surbs.maximum_reply_key_age
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,63 +458,63 @@ impl From<nym_topology::gateway::Node> for GatewayEndpointConfig {
|
||||
pub struct Client<T> {
|
||||
/// Version of the client for which this configuration was created.
|
||||
#[serde(default = "missing_string_value")]
|
||||
version: String,
|
||||
pub version: String,
|
||||
|
||||
/// ID specifies the human readable ID of this particular client.
|
||||
id: String,
|
||||
pub id: String,
|
||||
|
||||
/// Indicates whether this client is running in a disabled credentials mode, thus attempting
|
||||
/// to claim bandwidth without presenting bandwidth credentials.
|
||||
#[serde(default)]
|
||||
disabled_credentials_mode: bool,
|
||||
pub disabled_credentials_mode: bool,
|
||||
|
||||
/// Addresses to nyxd validators via which the client can communicate with the chain.
|
||||
#[serde(alias = "validator_urls")]
|
||||
nyxd_urls: Vec<Url>,
|
||||
pub nyxd_urls: Vec<Url>,
|
||||
|
||||
/// Addresses to APIs running on validator from which the client gets the view of the network.
|
||||
#[serde(alias = "validator_api_urls")]
|
||||
nym_api_urls: Vec<Url>,
|
||||
pub nym_api_urls: Vec<Url>,
|
||||
|
||||
/// Path to file containing private identity key.
|
||||
private_identity_key_file: PathBuf,
|
||||
pub private_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing public identity key.
|
||||
public_identity_key_file: PathBuf,
|
||||
pub public_identity_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing private encryption key.
|
||||
private_encryption_key_file: PathBuf,
|
||||
pub private_encryption_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing public encryption key.
|
||||
public_encryption_key_file: PathBuf,
|
||||
pub public_encryption_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing shared key derived with the specified gateway that is used
|
||||
/// for all communication with it.
|
||||
gateway_shared_key_file: PathBuf,
|
||||
pub gateway_shared_key_file: PathBuf,
|
||||
|
||||
/// Path to file containing key used for encrypting and decrypting the content of an
|
||||
/// acknowledgement so that nobody besides the client knows which packet it refers to.
|
||||
ack_key_file: PathBuf,
|
||||
pub ack_key_file: PathBuf,
|
||||
|
||||
/// Information regarding how the client should send data to gateway.
|
||||
gateway_endpoint: GatewayEndpointConfig,
|
||||
pub gateway_endpoint: GatewayEndpointConfig,
|
||||
|
||||
/// Path to the database containing bandwidth credentials of this client.
|
||||
database_path: PathBuf,
|
||||
pub database_path: PathBuf,
|
||||
|
||||
/// Path to the persistent store for received reply surbs, unused encryption keys and used sender tags.
|
||||
// this was set to use #[serde(default)] for the purposes of compatibility for multi-surbs introduced in 1.1.4.
|
||||
// if you're reading this message and we have already introduced some breaking changes, feel free
|
||||
// to remove that attribute since at this point the client configs should have gotten regenerated
|
||||
#[serde(default)]
|
||||
reply_surb_database_path: PathBuf,
|
||||
pub reply_surb_database_path: PathBuf,
|
||||
|
||||
/// nym_home_directory specifies absolute path to the home nym Clients directory.
|
||||
/// It is expected to use default value and hence .toml file should not redefine this field.
|
||||
nym_root_directory: PathBuf,
|
||||
pub nym_root_directory: PathBuf,
|
||||
|
||||
#[serde(skip)]
|
||||
super_struct: PhantomData<T>,
|
||||
pub super_struct: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: NymConfig> Default for Client<T> {
|
||||
@@ -587,9 +595,9 @@ impl<T: NymConfig> Client<T> {
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Logging {}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct DebugConfig {
|
||||
pub struct Traffic {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent packet is going to be delayed at any given mix node.
|
||||
/// So for a packet going through three mix nodes, on average, it will take three times this value
|
||||
@@ -597,6 +605,74 @@ pub struct DebugConfig {
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub average_packet_delay: Duration,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take another 'real traffic stream' message to be sent.
|
||||
/// If no real packets are available and cover traffic is enabled,
|
||||
/// a loop cover message is sent instead in order to preserve the rate.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub message_sending_average_delay: Duration,
|
||||
|
||||
/// Controls whether the main packet stream constantly produces packets according to the predefined
|
||||
/// poisson distribution.
|
||||
pub disable_main_poisson_packet_distribution: bool,
|
||||
|
||||
/// Controls whether the sent sphinx packet use a NON-DEFAULT bigger size.
|
||||
pub use_extended_packet_size: Option<ExtendedPacketSize>,
|
||||
}
|
||||
|
||||
impl Default for Traffic {
|
||||
fn default() -> Self {
|
||||
Traffic {
|
||||
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
|
||||
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
|
||||
disable_main_poisson_packet_distribution: false,
|
||||
use_extended_packet_size: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct CoverTraffic {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take for another loop cover traffic message to be sent.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub loop_cover_traffic_average_delay: Duration,
|
||||
|
||||
/// Controls whether the dedicated loop cover traffic stream should be enabled.
|
||||
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
|
||||
pub disable_loop_cover_traffic_stream: bool,
|
||||
}
|
||||
|
||||
impl Default for CoverTraffic {
|
||||
fn default() -> Self {
|
||||
CoverTraffic {
|
||||
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
|
||||
disable_loop_cover_traffic_stream: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct GatewayConnection {
|
||||
/// How long we're willing to wait for a response to a message sent to the gateway,
|
||||
/// before giving up on it.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub gateway_response_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Default for GatewayConnection {
|
||||
fn default() -> Self {
|
||||
GatewayConnection {
|
||||
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct Acknowledgements {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent acknowledgement is going to be delayed at any given mix node.
|
||||
/// So for an ack going through three mix nodes, on average, it will take three times this value
|
||||
@@ -614,24 +690,21 @@ pub struct DebugConfig {
|
||||
/// In an ideal network with 0 latency, this value would have been 0.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub ack_wait_addition: Duration,
|
||||
}
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take for another loop cover traffic message to be sent.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub loop_cover_traffic_average_delay: Duration,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take another 'real traffic stream' message to be sent.
|
||||
/// If no real packets are available and cover traffic is enabled,
|
||||
/// a loop cover message is sent instead in order to preserve the rate.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub message_sending_average_delay: Duration,
|
||||
|
||||
/// How long we're willing to wait for a response to a message sent to the gateway,
|
||||
/// before giving up on it.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub gateway_response_timeout: Duration,
|
||||
impl Default for Acknowledgements {
|
||||
fn default() -> Self {
|
||||
Acknowledgements {
|
||||
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
|
||||
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
|
||||
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct Topology {
|
||||
/// The uniform delay every which clients are querying the directory server
|
||||
/// to try to obtain a compatible network topology to send sphinx packets through.
|
||||
#[serde(with = "humantime_serde")]
|
||||
@@ -642,18 +715,20 @@ pub struct DebugConfig {
|
||||
/// did not reach its destination.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub topology_resolution_timeout: Duration,
|
||||
}
|
||||
|
||||
/// Controls whether the dedicated loop cover traffic stream should be enabled.
|
||||
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
|
||||
pub disable_loop_cover_traffic_stream: bool,
|
||||
|
||||
/// Controls whether the main packet stream constantly produces packets according to the predefined
|
||||
/// poisson distribution.
|
||||
pub disable_main_poisson_packet_distribution: bool,
|
||||
|
||||
/// Controls whether the sent sphinx packet use a NON-DEFAULT bigger size.
|
||||
pub use_extended_packet_size: Option<ExtendedPacketSize>,
|
||||
impl Default for Topology {
|
||||
fn default() -> Self {
|
||||
Topology {
|
||||
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
|
||||
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct ReplySurbs {
|
||||
/// Defines the minimum number of reply surbs the client wants to keep in its storage at all times.
|
||||
/// It can only allow to go below that value if its to request additional reply surbs.
|
||||
pub minimum_reply_surb_storage_threshold: usize,
|
||||
@@ -691,29 +766,9 @@ pub struct DebugConfig {
|
||||
pub maximum_reply_key_age: Duration,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ExtendedPacketSize {
|
||||
Extended8,
|
||||
Extended16,
|
||||
Extended32,
|
||||
}
|
||||
|
||||
impl Default for DebugConfig {
|
||||
impl Default for ReplySurbs {
|
||||
fn default() -> Self {
|
||||
DebugConfig {
|
||||
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
|
||||
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
|
||||
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
|
||||
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
|
||||
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
|
||||
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
|
||||
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
|
||||
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
|
||||
disable_loop_cover_traffic_stream: false,
|
||||
disable_main_poisson_packet_distribution: false,
|
||||
use_extended_packet_size: None,
|
||||
ReplySurbs {
|
||||
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
|
||||
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
|
||||
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
|
||||
@@ -728,6 +783,52 @@ impl Default for DebugConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct DebugConfig {
|
||||
/// Defines all configuration options related to traffic streams.
|
||||
pub traffic: Traffic,
|
||||
|
||||
/// Defines all configuration options related to cover traffic stream(s).
|
||||
pub cover_traffic: CoverTraffic,
|
||||
|
||||
/// Defines all configuration options related to the gateway connection.
|
||||
pub gateway_connection: GatewayConnection,
|
||||
|
||||
/// Defines all configuration options related to acknowledgements, such as delays or wait timeouts.
|
||||
pub acknowledgements: Acknowledgements,
|
||||
|
||||
/// Defines all configuration options related topology, such as refresh rates or timeouts.
|
||||
pub topology: Topology,
|
||||
|
||||
/// Defines all configuration options related to reply SURBs.
|
||||
pub reply_surbs: ReplySurbs,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ExtendedPacketSize {
|
||||
Extended8,
|
||||
Extended16,
|
||||
Extended32,
|
||||
}
|
||||
|
||||
// it could be derived, sure, but I'd rather have an explicit implementation in case we had to change
|
||||
// something manually at some point
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl Default for DebugConfig {
|
||||
fn default() -> Self {
|
||||
DebugConfig {
|
||||
traffic: Default::default(),
|
||||
cover_traffic: Default::default(),
|
||||
gateway_connection: Default::default(),
|
||||
acknowledgements: Default::default(),
|
||||
topology: Default::default(),
|
||||
reply_surbs: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ExtendedPacketSize> for PacketSize {
|
||||
fn from(size: ExtendedPacketSize) -> PacketSize {
|
||||
match size {
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::config::{
|
||||
Acknowledgements, Client, Config, CoverTraffic, DebugConfig, ExtendedPacketSize,
|
||||
GatewayConnection, Logging, ReplySurbs, Topology, Traffic, DEFAULT_ACK_WAIT_ADDITION,
|
||||
DEFAULT_ACK_WAIT_MULTIPLIER, DEFAULT_AVERAGE_PACKET_DELAY, DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||
DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY, DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
|
||||
DEFAULT_MAXIMUM_REPLY_KEY_AGE, DEFAULT_MAXIMUM_REPLY_SURB_AGE,
|
||||
DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD, DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
|
||||
DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD,
|
||||
DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD, DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
|
||||
DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE, DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
|
||||
DEFAULT_TOPOLOGY_REFRESH_RATE, DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
|
||||
};
|
||||
use nym_config::NymConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::marker::PhantomData;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct OldConfigV1_1_13<T> {
|
||||
pub client: Client<T>,
|
||||
|
||||
#[serde(default)]
|
||||
logging: Logging,
|
||||
#[serde(default)]
|
||||
debug: OldDebugConfigV1_1_13,
|
||||
}
|
||||
|
||||
impl<T: NymConfig> Default for OldConfigV1_1_13<T> {
|
||||
fn default() -> Self {
|
||||
OldConfigV1_1_13 {
|
||||
client: Client::<T>::default(),
|
||||
logging: Default::default(),
|
||||
debug: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(default, deny_unknown_fields)]
|
||||
pub struct OldDebugConfigV1_1_13 {
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub average_packet_delay: Duration,
|
||||
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub average_ack_delay: Duration,
|
||||
|
||||
pub ack_wait_multiplier: f64,
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub ack_wait_addition: Duration,
|
||||
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub loop_cover_traffic_average_delay: Duration,
|
||||
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub message_sending_average_delay: Duration,
|
||||
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub gateway_response_timeout: Duration,
|
||||
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub topology_refresh_rate: Duration,
|
||||
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub topology_resolution_timeout: Duration,
|
||||
|
||||
pub disable_loop_cover_traffic_stream: bool,
|
||||
|
||||
pub disable_main_poisson_packet_distribution: bool,
|
||||
|
||||
pub use_extended_packet_size: Option<ExtendedPacketSize>,
|
||||
|
||||
pub minimum_reply_surb_storage_threshold: usize,
|
||||
|
||||
pub maximum_reply_surb_storage_threshold: usize,
|
||||
|
||||
pub minimum_reply_surb_request_size: u32,
|
||||
|
||||
pub maximum_reply_surb_request_size: u32,
|
||||
|
||||
pub maximum_allowed_reply_surb_request_size: u32,
|
||||
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub maximum_reply_surb_rerequest_waiting_period: Duration,
|
||||
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub maximum_reply_surb_drop_waiting_period: Duration,
|
||||
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub maximum_reply_surb_age: Duration,
|
||||
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub maximum_reply_key_age: Duration,
|
||||
}
|
||||
|
||||
impl From<OldDebugConfigV1_1_13> for DebugConfig {
|
||||
fn from(value: OldDebugConfigV1_1_13) -> Self {
|
||||
DebugConfig {
|
||||
traffic: Traffic {
|
||||
average_packet_delay: value.average_packet_delay,
|
||||
message_sending_average_delay: value.message_sending_average_delay,
|
||||
disable_main_poisson_packet_distribution: value
|
||||
.disable_main_poisson_packet_distribution,
|
||||
use_extended_packet_size: value.use_extended_packet_size,
|
||||
},
|
||||
cover_traffic: CoverTraffic {
|
||||
loop_cover_traffic_average_delay: value.loop_cover_traffic_average_delay,
|
||||
disable_loop_cover_traffic_stream: value.disable_loop_cover_traffic_stream,
|
||||
},
|
||||
gateway_connection: GatewayConnection {
|
||||
gateway_response_timeout: value.gateway_response_timeout,
|
||||
},
|
||||
acknowledgements: Acknowledgements {
|
||||
average_ack_delay: value.average_ack_delay,
|
||||
ack_wait_multiplier: value.ack_wait_multiplier,
|
||||
ack_wait_addition: value.ack_wait_addition,
|
||||
},
|
||||
topology: Topology {
|
||||
topology_refresh_rate: value.topology_refresh_rate,
|
||||
topology_resolution_timeout: value.topology_resolution_timeout,
|
||||
},
|
||||
reply_surbs: ReplySurbs {
|
||||
minimum_reply_surb_storage_threshold: value.minimum_reply_surb_storage_threshold,
|
||||
maximum_reply_surb_storage_threshold: value.maximum_reply_surb_storage_threshold,
|
||||
minimum_reply_surb_request_size: value.minimum_reply_surb_request_size,
|
||||
maximum_reply_surb_request_size: value.maximum_reply_surb_request_size,
|
||||
maximum_allowed_reply_surb_request_size: value
|
||||
.maximum_allowed_reply_surb_request_size,
|
||||
maximum_reply_surb_rerequest_waiting_period: value
|
||||
.maximum_reply_surb_rerequest_waiting_period,
|
||||
maximum_reply_surb_drop_waiting_period: value
|
||||
.maximum_reply_surb_drop_waiting_period,
|
||||
maximum_reply_surb_age: value.maximum_reply_surb_age,
|
||||
maximum_reply_key_age: value.maximum_reply_key_age,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for OldDebugConfigV1_1_13 {
|
||||
fn default() -> Self {
|
||||
OldDebugConfigV1_1_13 {
|
||||
average_packet_delay: DEFAULT_AVERAGE_PACKET_DELAY,
|
||||
average_ack_delay: DEFAULT_AVERAGE_PACKET_DELAY,
|
||||
ack_wait_multiplier: DEFAULT_ACK_WAIT_MULTIPLIER,
|
||||
ack_wait_addition: DEFAULT_ACK_WAIT_ADDITION,
|
||||
loop_cover_traffic_average_delay: DEFAULT_LOOP_COVER_STREAM_AVERAGE_DELAY,
|
||||
message_sending_average_delay: DEFAULT_MESSAGE_STREAM_AVERAGE_DELAY,
|
||||
gateway_response_timeout: DEFAULT_GATEWAY_RESPONSE_TIMEOUT,
|
||||
topology_refresh_rate: DEFAULT_TOPOLOGY_REFRESH_RATE,
|
||||
topology_resolution_timeout: DEFAULT_TOPOLOGY_RESOLUTION_TIMEOUT,
|
||||
disable_loop_cover_traffic_stream: false,
|
||||
disable_main_poisson_packet_distribution: false,
|
||||
use_extended_packet_size: None,
|
||||
minimum_reply_surb_storage_threshold: DEFAULT_MINIMUM_REPLY_SURB_STORAGE_THRESHOLD,
|
||||
maximum_reply_surb_storage_threshold: DEFAULT_MAXIMUM_REPLY_SURB_STORAGE_THRESHOLD,
|
||||
minimum_reply_surb_request_size: DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE,
|
||||
maximum_reply_surb_request_size: DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE,
|
||||
maximum_allowed_reply_surb_request_size: DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE,
|
||||
maximum_reply_surb_rerequest_waiting_period:
|
||||
DEFAULT_MAXIMUM_REPLY_SURB_REREQUEST_WAITING_PERIOD,
|
||||
maximum_reply_surb_drop_waiting_period: DEFAULT_MAXIMUM_REPLY_SURB_DROP_WAITING_PERIOD,
|
||||
maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE,
|
||||
maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> From<OldConfigV1_1_13<T>> for Config<U> {
|
||||
fn from(value: OldConfigV1_1_13<T>) -> Self {
|
||||
Config {
|
||||
client: Client {
|
||||
version: value.client.version,
|
||||
id: value.client.id,
|
||||
disabled_credentials_mode: value.client.disabled_credentials_mode,
|
||||
nyxd_urls: value.client.nyxd_urls,
|
||||
nym_api_urls: value.client.nym_api_urls,
|
||||
private_identity_key_file: value.client.private_identity_key_file,
|
||||
public_identity_key_file: value.client.public_identity_key_file,
|
||||
private_encryption_key_file: value.client.private_encryption_key_file,
|
||||
public_encryption_key_file: value.client.public_encryption_key_file,
|
||||
gateway_shared_key_file: value.client.gateway_shared_key_file,
|
||||
ack_key_file: value.client.ack_key_file,
|
||||
gateway_endpoint: value.client.gateway_endpoint,
|
||||
database_path: value.client.database_path,
|
||||
reply_surb_database_path: value.client.reply_surb_database_path,
|
||||
nym_root_directory: value.client.nym_root_directory,
|
||||
|
||||
super_struct: PhantomData,
|
||||
},
|
||||
logging: value.logging,
|
||||
debug: value.debug.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,14 +112,14 @@ where
|
||||
{
|
||||
let id = config.get_id();
|
||||
|
||||
// If we are not going to register gateway, and an explicitly chosed gateway is not passed in,
|
||||
// If we are not going to register gateway, and an explicitly chosen gateway is not passed in,
|
||||
// load the existing configuration file
|
||||
if !register_gateway && user_chosen_gateway_id.is_none() {
|
||||
println!("Not registering gateway, will reuse existing config and keys");
|
||||
return load_existing_gateway_config::<C>(&id);
|
||||
}
|
||||
|
||||
// Else, we preceed by querying the nym-api
|
||||
// Else, we proceed by querying the nym-api
|
||||
let gateway = helpers::query_gateway_details(
|
||||
config.get_nym_api_endpoints(),
|
||||
user_chosen_gateway_id,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.12"
|
||||
version = "1.1.14"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -15,6 +15,7 @@ pub use client_core::config::Config as BaseConfig;
|
||||
pub use client_core::config::MISSING_VALUE;
|
||||
pub use client_core::config::{DebugConfig, GatewayEndpointConfig};
|
||||
|
||||
pub mod old_config_v1_1_13;
|
||||
mod template;
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq, Serialize, Clone, Copy)]
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::{Config, Socket};
|
||||
use client_core::config::old_config_v1_1_13::OldConfigV1_1_13 as OldBaseConfigV1_1_13;
|
||||
use nym_config::NymConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct OldConfigV1_1_13 {
|
||||
#[serde(flatten)]
|
||||
base: OldBaseConfigV1_1_13<OldConfigV1_1_13>,
|
||||
|
||||
socket: Socket,
|
||||
}
|
||||
|
||||
impl NymConfig for OldConfigV1_1_13 {
|
||||
fn template() -> &'static str {
|
||||
// not intended to be used
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn default_root_directory() -> PathBuf {
|
||||
dirs::home_dir()
|
||||
.expect("Failed to evaluate $HOME value")
|
||||
.join(".nym")
|
||||
.join("clients")
|
||||
}
|
||||
|
||||
fn try_default_root_directory() -> Option<PathBuf> {
|
||||
dirs::home_dir().map(|path| path.join(".nym").join("clients"))
|
||||
}
|
||||
|
||||
fn root_directory(&self) -> PathBuf {
|
||||
self.base.client.nym_root_directory.clone()
|
||||
}
|
||||
|
||||
fn config_directory(&self) -> PathBuf {
|
||||
self.root_directory()
|
||||
.join(&self.base.client.id)
|
||||
.join("config")
|
||||
}
|
||||
|
||||
fn data_directory(&self) -> PathBuf {
|
||||
self.root_directory()
|
||||
.join(&self.base.client.id)
|
||||
.join("data")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OldConfigV1_1_13> for Config {
|
||||
fn from(value: OldConfigV1_1_13) -> Self {
|
||||
Config {
|
||||
base: value.base.into(),
|
||||
socket: value.socket,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -110,10 +110,15 @@ host = '{{ socket.host }}'
|
||||
|
||||
[debug]
|
||||
|
||||
average_packet_delay = '{{ debug.average_packet_delay }}'
|
||||
average_ack_delay = '{{ debug.average_ack_delay }}'
|
||||
loop_cover_traffic_average_delay = '{{ debug.loop_cover_traffic_average_delay }}'
|
||||
message_sending_average_delay = '{{ debug.message_sending_average_delay }}'
|
||||
[debug.traffic]
|
||||
average_packet_delay = '{{ debug.traffic.average_packet_delay }}'
|
||||
message_sending_average_delay = '{{ debug.traffic.message_sending_average_delay }}'
|
||||
|
||||
[debug.acknowledgements]
|
||||
average_ack_delay = '{{ debug.acknowledgements.average_ack_delay }}'
|
||||
|
||||
[debug.cover_traffic]
|
||||
loop_cover_traffic_average_delay = '{{ debug.cover_traffic.loop_cover_traffic_average_delay }}'
|
||||
|
||||
"#
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::try_upgrade_v1_1_13_config;
|
||||
use crate::{
|
||||
client::config::Config,
|
||||
commands::{override_config, OverrideConfig},
|
||||
@@ -121,6 +122,9 @@ pub(crate) async fn execute(args: &Init) -> Result<(), ClientError> {
|
||||
|
||||
let already_init = Config::default_config_file_path(id).exists();
|
||||
if already_init {
|
||||
// in case we're using old config, try to upgrade it
|
||||
// (if we're using the current version, it's a no-op)
|
||||
try_upgrade_v1_1_13_config(id)?;
|
||||
println!("Client \"{id}\" was already initialised before");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::old_config_v1_1_13::OldConfigV1_1_13;
|
||||
use crate::client::config::{BaseConfig, Config};
|
||||
use clap::CommandFactory;
|
||||
use clap::{Parser, Subcommand};
|
||||
use lazy_static::lazy_static;
|
||||
use log::info;
|
||||
use nym_bin_common::build_information::BinaryBuildInformation;
|
||||
use nym_bin_common::completions::{fig_generate, ArgShell};
|
||||
use nym_config::OptionalSet;
|
||||
use nym_config::{NymConfig, OptionalSet};
|
||||
use std::error::Error;
|
||||
use std::net::IpAddr;
|
||||
|
||||
@@ -102,6 +104,20 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
|
||||
)
|
||||
}
|
||||
|
||||
fn try_upgrade_v1_1_13_config(id: &str) -> std::io::Result<()> {
|
||||
// explicitly load it as v1.1.13 (which is incompatible with the current, i.e. 1.1.14+)
|
||||
let Ok(old_config) = OldConfigV1_1_13::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(());
|
||||
};
|
||||
info!("It seems the client is using <= v1.1.13 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated: Config = old_config.into();
|
||||
updated.save_to_file(None)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
use std::error::Error;
|
||||
use std::net::IpAddr;
|
||||
|
||||
use crate::commands::try_upgrade_v1_1_13_config;
|
||||
use crate::{
|
||||
client::{config::Config, SocketClient},
|
||||
commands::{override_config, OverrideConfig},
|
||||
error::ClientError,
|
||||
};
|
||||
|
||||
use clap::Args;
|
||||
use log::*;
|
||||
use nym_bin_common::version_checker::is_minor_version_compatible;
|
||||
@@ -100,6 +100,10 @@ fn version_check(cfg: &Config) -> bool {
|
||||
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let id = &args.id;
|
||||
|
||||
// in case we're using old config, try to upgrade it
|
||||
// (if we're using the current version, it's a no-op)
|
||||
try_upgrade_v1_1_13_config(id)?;
|
||||
|
||||
let mut config = match Config::load_from_file(id) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.12"
|
||||
version = "1.1.14"
|
||||
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"
|
||||
|
||||
@@ -15,6 +15,7 @@ use std::fmt::Debug;
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub mod old_config_v1_1_13;
|
||||
mod template;
|
||||
|
||||
const DEFAULT_CONNECTION_START_SURBS: u32 = 20;
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::{Config, Socks5, Socks5Debug};
|
||||
use client_core::config::old_config_v1_1_13::OldConfigV1_1_13 as OldBaseConfigV1_1_13;
|
||||
use nym_config::NymConfig;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct OldConfigV1_1_13 {
|
||||
#[serde(flatten)]
|
||||
base: OldBaseConfigV1_1_13<OldConfigV1_1_13>,
|
||||
|
||||
socks5: Socks5,
|
||||
|
||||
#[serde(default)]
|
||||
socks5_debug: Socks5Debug,
|
||||
}
|
||||
|
||||
impl NymConfig for OldConfigV1_1_13 {
|
||||
fn template() -> &'static str {
|
||||
// not intended to be used
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn default_root_directory() -> PathBuf {
|
||||
#[cfg(not(feature = "mobile"))]
|
||||
let base_dir = dirs::home_dir().expect("Failed to evaluate $HOME value");
|
||||
#[cfg(feature = "mobile")]
|
||||
let base_dir = PathBuf::from("/tmp");
|
||||
|
||||
base_dir.join(".nym").join("socks5-clients")
|
||||
}
|
||||
|
||||
fn try_default_root_directory() -> Option<PathBuf> {
|
||||
dirs::home_dir().map(|path| path.join(".nym").join("socks5-clients"))
|
||||
}
|
||||
|
||||
fn root_directory(&self) -> PathBuf {
|
||||
self.base.client.nym_root_directory.clone()
|
||||
}
|
||||
|
||||
fn config_directory(&self) -> PathBuf {
|
||||
self.root_directory()
|
||||
.join(&self.base.client.id)
|
||||
.join("config")
|
||||
}
|
||||
|
||||
fn data_directory(&self) -> PathBuf {
|
||||
self.root_directory()
|
||||
.join(&self.base.client.id)
|
||||
.join("data")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OldConfigV1_1_13> for Config {
|
||||
fn from(value: OldConfigV1_1_13) -> Self {
|
||||
Config {
|
||||
base: value.base.into(),
|
||||
socks5: value.socks5,
|
||||
socks5_debug: value.socks5_debug,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,10 +114,15 @@ send_anonymously = {{ socks5.send_anonymously }}
|
||||
|
||||
[debug]
|
||||
|
||||
average_packet_delay = '{{ debug.average_packet_delay }}'
|
||||
average_ack_delay = '{{ debug.average_ack_delay }}'
|
||||
loop_cover_traffic_average_delay = '{{ debug.loop_cover_traffic_average_delay }}'
|
||||
message_sending_average_delay = '{{ debug.message_sending_average_delay }}'
|
||||
[debug.traffic]
|
||||
average_packet_delay = '{{ debug.traffic.average_packet_delay }}'
|
||||
message_sending_average_delay = '{{ debug.traffic.message_sending_average_delay }}'
|
||||
|
||||
[debug.acknowledgements]
|
||||
average_ack_delay = '{{ debug.acknowledgements.average_ack_delay }}'
|
||||
|
||||
[debug.cover_traffic]
|
||||
loop_cover_traffic_average_delay = '{{ debug.cover_traffic.loop_cover_traffic_average_delay }}'
|
||||
|
||||
"#
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::try_upgrade_v1_1_13_config;
|
||||
use crate::{
|
||||
client::config::Config,
|
||||
commands::{override_config, OverrideConfig},
|
||||
@@ -124,6 +125,9 @@ pub(crate) async fn execute(args: &Init) -> Result<(), Socks5ClientError> {
|
||||
|
||||
let already_init = Config::default_config_file_path(id).exists();
|
||||
if already_init {
|
||||
// in case we're using old config, try to upgrade it
|
||||
// (if we're using the current version, it's a no-op)
|
||||
try_upgrade_v1_1_13_config(id)?;
|
||||
println!("SOCKS5 client \"{id}\" was already initialised before");
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::config::old_config_v1_1_13::OldConfigV1_1_13;
|
||||
use crate::client::config::{BaseConfig, Config};
|
||||
use clap::CommandFactory;
|
||||
use clap::{Parser, Subcommand};
|
||||
use lazy_static::lazy_static;
|
||||
use log::info;
|
||||
use nym_bin_common::build_information::BinaryBuildInformation;
|
||||
use nym_bin_common::completions::{fig_generate, ArgShell};
|
||||
use nym_config::OptionalSet;
|
||||
use nym_config::{NymConfig, OptionalSet};
|
||||
use std::error::Error;
|
||||
|
||||
pub mod init;
|
||||
@@ -101,6 +103,20 @@ pub(crate) fn override_config(config: Config, args: OverrideConfig) -> Config {
|
||||
)
|
||||
}
|
||||
|
||||
fn try_upgrade_v1_1_13_config(id: &str) -> std::io::Result<()> {
|
||||
// explicitly load it as v1.1.13 (which is incompatible with the current, i.e. 1.1.14+)
|
||||
let Ok(old_config) = OldConfigV1_1_13::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(());
|
||||
};
|
||||
info!("It seems the client is using <= v1.1.13 config template.");
|
||||
info!("It is going to get updated to the current specification.");
|
||||
|
||||
let updated: Config = old_config.into();
|
||||
updated.save_to_file(None)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::commands::try_upgrade_v1_1_13_config;
|
||||
use crate::{
|
||||
client::{config::Config, NymClient},
|
||||
commands::{override_config, OverrideConfig},
|
||||
error::Socks5ClientError,
|
||||
};
|
||||
|
||||
use clap::Args;
|
||||
use log::*;
|
||||
use nym_bin_common::version_checker::is_minor_version_compatible;
|
||||
@@ -108,6 +108,10 @@ fn version_check(cfg: &Config) -> bool {
|
||||
pub(crate) async fn execute(args: &Run) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let id = &args.id;
|
||||
|
||||
// in case we're using old config, try to upgrade it
|
||||
// (if we're using the current version, it's a no-op)
|
||||
try_upgrade_v1_1_13_config(id)?;
|
||||
|
||||
let mut config = match Config::load_from_file(id) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
|
||||
@@ -3,8 +3,15 @@
|
||||
|
||||
// due to expansion of #[wasm_bindgen] macro on `Debug` Config struct
|
||||
#![allow(clippy::drop_non_drop)]
|
||||
// another issue due to #[wasm_bindgen] and `Copy` trait
|
||||
#![allow(clippy::drop_copy)]
|
||||
|
||||
use client_core::config::{DebugConfig as ConfigDebug, ExtendedPacketSize, GatewayEndpointConfig};
|
||||
use client_core::config::{
|
||||
Acknowledgements as ConfigAcknowledgements, CoverTraffic as ConfigCoverTraffic,
|
||||
DebugConfig as ConfigDebug, ExtendedPacketSize, GatewayConnection as ConfigGatewayConnection,
|
||||
GatewayEndpointConfig, ReplySurbs as ConfigReplySurbs, Topology as ConfigTopology,
|
||||
Traffic as ConfigTraffic,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use url::Url;
|
||||
@@ -48,15 +55,124 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
// just a helper structure to more easily pass through the JS boundary
|
||||
#[wasm_bindgen]
|
||||
pub struct Debug {
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Traffic {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent packet is going to be delayed at any given mix node.
|
||||
/// So for a packet going through three mix nodes, on average, it will take three times this value
|
||||
/// until the packet reaches its destination.
|
||||
pub average_packet_delay_ms: u64,
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take another 'real traffic stream' message to be sent.
|
||||
/// If no real packets are available and cover traffic is enabled,
|
||||
/// a loop cover message is sent instead in order to preserve the rate.
|
||||
pub message_sending_average_delay_ms: u64,
|
||||
|
||||
/// Controls whether the main packet stream constantly produces packets according to the predefined
|
||||
/// poisson distribution.
|
||||
pub disable_main_poisson_packet_distribution: bool,
|
||||
|
||||
/// Controls whether the sent sphinx packet use the NON-DEFAULT bigger size.
|
||||
pub use_extended_packet_size: bool,
|
||||
}
|
||||
|
||||
impl From<Traffic> for ConfigTraffic {
|
||||
fn from(traffic: Traffic) -> Self {
|
||||
let use_extended_packet_size = traffic
|
||||
.use_extended_packet_size
|
||||
.then(|| ExtendedPacketSize::Extended32);
|
||||
|
||||
ConfigTraffic {
|
||||
average_packet_delay: Duration::from_millis(traffic.average_packet_delay_ms),
|
||||
message_sending_average_delay: Duration::from_millis(
|
||||
traffic.message_sending_average_delay_ms,
|
||||
),
|
||||
disable_main_poisson_packet_distribution: traffic
|
||||
.disable_main_poisson_packet_distribution,
|
||||
use_extended_packet_size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigTraffic> for Traffic {
|
||||
fn from(traffic: ConfigTraffic) -> Self {
|
||||
Traffic {
|
||||
average_packet_delay_ms: traffic.average_packet_delay.as_millis() as u64,
|
||||
message_sending_average_delay_ms: traffic.message_sending_average_delay.as_millis()
|
||||
as u64,
|
||||
disable_main_poisson_packet_distribution: traffic
|
||||
.disable_main_poisson_packet_distribution,
|
||||
use_extended_packet_size: traffic.use_extended_packet_size.is_some(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct CoverTraffic {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take for another loop cover traffic message to be sent.
|
||||
pub loop_cover_traffic_average_delay_ms: u64,
|
||||
|
||||
/// Controls whether the dedicated loop cover traffic stream should be enabled.
|
||||
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay])
|
||||
pub disable_loop_cover_traffic_stream: bool,
|
||||
}
|
||||
|
||||
impl From<CoverTraffic> for ConfigCoverTraffic {
|
||||
fn from(cover_traffic: CoverTraffic) -> Self {
|
||||
ConfigCoverTraffic {
|
||||
loop_cover_traffic_average_delay: Duration::from_millis(
|
||||
cover_traffic.loop_cover_traffic_average_delay_ms,
|
||||
),
|
||||
disable_loop_cover_traffic_stream: cover_traffic.disable_loop_cover_traffic_stream,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigCoverTraffic> for CoverTraffic {
|
||||
fn from(cover_traffic: ConfigCoverTraffic) -> Self {
|
||||
CoverTraffic {
|
||||
loop_cover_traffic_average_delay_ms: cover_traffic
|
||||
.loop_cover_traffic_average_delay
|
||||
.as_millis() as u64,
|
||||
disable_loop_cover_traffic_stream: cover_traffic.disable_loop_cover_traffic_stream,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct GatewayConnection {
|
||||
/// How long we're willing to wait for a response to a message sent to the gateway,
|
||||
/// before giving up on it.
|
||||
pub gateway_response_timeout_ms: u64,
|
||||
}
|
||||
|
||||
impl From<GatewayConnection> for ConfigGatewayConnection {
|
||||
fn from(gateway_connection: GatewayConnection) -> Self {
|
||||
ConfigGatewayConnection {
|
||||
gateway_response_timeout: Duration::from_millis(
|
||||
gateway_connection.gateway_response_timeout_ms,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigGatewayConnection> for GatewayConnection {
|
||||
fn from(gateway_connection: ConfigGatewayConnection) -> Self {
|
||||
GatewayConnection {
|
||||
gateway_response_timeout_ms: gateway_connection.gateway_response_timeout.as_millis()
|
||||
as u64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Acknowledgements {
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// sent acknowledgement is going to be delayed at any given mix node.
|
||||
/// So for an ack going through three mix nodes, on average, it will take three times this value
|
||||
@@ -72,21 +188,31 @@ pub struct Debug {
|
||||
/// it is assumed it was lost and retransmission of the data packet happens.
|
||||
/// In an ideal network with 0 latency, this value would have been 0.
|
||||
pub ack_wait_addition_ms: u64,
|
||||
}
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take for another loop cover traffic message to be sent.
|
||||
pub loop_cover_traffic_average_delay_ms: u64,
|
||||
impl From<Acknowledgements> for ConfigAcknowledgements {
|
||||
fn from(acknowledgements: Acknowledgements) -> Self {
|
||||
ConfigAcknowledgements {
|
||||
average_ack_delay: Duration::from_millis(acknowledgements.average_ack_delay_ms),
|
||||
ack_wait_multiplier: acknowledgements.ack_wait_multiplier,
|
||||
ack_wait_addition: Duration::from_millis(acknowledgements.ack_wait_addition_ms),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The parameter of Poisson distribution determining how long, on average,
|
||||
/// it is going to take another 'real traffic stream' message to be sent.
|
||||
/// If no real packets are available and cover traffic is enabled,
|
||||
/// a loop cover message is sent instead in order to preserve the rate.
|
||||
pub message_sending_average_delay_ms: u64,
|
||||
|
||||
/// How long we're willing to wait for a response to a message sent to the gateway,
|
||||
/// before giving up on it.
|
||||
pub gateway_response_timeout_ms: u64,
|
||||
impl From<ConfigAcknowledgements> for Acknowledgements {
|
||||
fn from(acknowledgements: ConfigAcknowledgements) -> Self {
|
||||
Acknowledgements {
|
||||
average_ack_delay_ms: acknowledgements.average_ack_delay.as_millis() as u64,
|
||||
ack_wait_multiplier: acknowledgements.ack_wait_multiplier,
|
||||
ack_wait_addition_ms: acknowledgements.ack_wait_addition.as_millis() as u64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Topology {
|
||||
/// The uniform delay every which clients are querying the directory server
|
||||
/// to try to obtain a compatible network topology to send sphinx packets through.
|
||||
pub topology_refresh_rate_ms: u64,
|
||||
@@ -95,18 +221,31 @@ pub struct Debug {
|
||||
/// path. This timeout determines waiting period until it is decided that the packet
|
||||
/// did not reach its destination.
|
||||
pub topology_resolution_timeout_ms: u64,
|
||||
}
|
||||
|
||||
/// Controls whether the dedicated loop cover traffic stream should be enabled.
|
||||
/// (and sending packets, on average, every [Self::loop_cover_traffic_average_delay_ms])
|
||||
pub disable_loop_cover_traffic_stream: bool,
|
||||
impl From<Topology> for ConfigTopology {
|
||||
fn from(topology: Topology) -> Self {
|
||||
ConfigTopology {
|
||||
topology_refresh_rate: Duration::from_millis(topology.topology_refresh_rate_ms),
|
||||
topology_resolution_timeout: Duration::from_millis(
|
||||
topology.topology_resolution_timeout_ms,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Controls whether the main packet stream constantly produces packets according to the predefined
|
||||
/// poisson distribution.
|
||||
pub disable_main_poisson_packet_distribution: bool,
|
||||
|
||||
/// Controls whether the sent sphinx packet use the NON-DEFAULT bigger size.
|
||||
pub use_extended_packet_size: bool,
|
||||
impl From<ConfigTopology> for Topology {
|
||||
fn from(topology: ConfigTopology) -> Self {
|
||||
Topology {
|
||||
topology_refresh_rate_ms: topology.topology_refresh_rate.as_millis() as u64,
|
||||
topology_resolution_timeout_ms: topology.topology_resolution_timeout.as_millis() as u64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ReplySurbs {
|
||||
/// Defines the minimum number of reply surbs the client wants to keep in its storage at all times.
|
||||
/// It can only allow to go below that value if its to request additional reply surbs.
|
||||
pub minimum_reply_surb_storage_threshold: usize,
|
||||
@@ -140,46 +279,80 @@ pub struct Debug {
|
||||
pub maximum_reply_key_age_ms: u64,
|
||||
}
|
||||
|
||||
impl From<Debug> for ConfigDebug {
|
||||
fn from(debug: Debug) -> Self {
|
||||
// For now we just always use the (older) 32kb extended size
|
||||
let use_extended_packet_size = debug
|
||||
.use_extended_packet_size
|
||||
.then(|| ExtendedPacketSize::Extended32);
|
||||
|
||||
ConfigDebug {
|
||||
average_packet_delay: Duration::from_millis(debug.average_packet_delay_ms),
|
||||
average_ack_delay: Duration::from_millis(debug.average_ack_delay_ms),
|
||||
ack_wait_multiplier: debug.ack_wait_multiplier,
|
||||
ack_wait_addition: Duration::from_millis(debug.ack_wait_addition_ms),
|
||||
loop_cover_traffic_average_delay: Duration::from_millis(
|
||||
debug.loop_cover_traffic_average_delay_ms,
|
||||
),
|
||||
message_sending_average_delay: Duration::from_millis(
|
||||
debug.message_sending_average_delay_ms,
|
||||
),
|
||||
gateway_response_timeout: Duration::from_millis(debug.gateway_response_timeout_ms),
|
||||
topology_refresh_rate: Duration::from_millis(debug.topology_refresh_rate_ms),
|
||||
topology_resolution_timeout: Duration::from_millis(
|
||||
debug.topology_resolution_timeout_ms,
|
||||
),
|
||||
disable_loop_cover_traffic_stream: debug.disable_loop_cover_traffic_stream,
|
||||
disable_main_poisson_packet_distribution: debug
|
||||
.disable_main_poisson_packet_distribution,
|
||||
use_extended_packet_size,
|
||||
minimum_reply_surb_storage_threshold: debug.minimum_reply_surb_storage_threshold,
|
||||
maximum_reply_surb_storage_threshold: debug.maximum_reply_surb_storage_threshold,
|
||||
minimum_reply_surb_request_size: debug.minimum_reply_surb_request_size,
|
||||
maximum_reply_surb_request_size: debug.maximum_reply_surb_request_size,
|
||||
maximum_allowed_reply_surb_request_size: debug.maximum_allowed_reply_surb_request_size,
|
||||
impl From<ReplySurbs> for ConfigReplySurbs {
|
||||
fn from(reply_surbs: ReplySurbs) -> Self {
|
||||
ConfigReplySurbs {
|
||||
minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold,
|
||||
maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold,
|
||||
minimum_reply_surb_request_size: reply_surbs.minimum_reply_surb_request_size,
|
||||
maximum_reply_surb_request_size: reply_surbs.maximum_reply_surb_request_size,
|
||||
maximum_allowed_reply_surb_request_size: reply_surbs
|
||||
.maximum_allowed_reply_surb_request_size,
|
||||
maximum_reply_surb_rerequest_waiting_period: Duration::from_millis(
|
||||
debug.maximum_reply_surb_rerequest_waiting_period_ms,
|
||||
reply_surbs.maximum_reply_surb_rerequest_waiting_period_ms,
|
||||
),
|
||||
maximum_reply_surb_drop_waiting_period: Duration::from_millis(
|
||||
debug.maximum_reply_surb_drop_waiting_period_ms,
|
||||
reply_surbs.maximum_reply_surb_drop_waiting_period_ms,
|
||||
),
|
||||
maximum_reply_surb_age: Duration::from_millis(debug.maximum_reply_surb_age_ms),
|
||||
maximum_reply_key_age: Duration::from_millis(debug.maximum_reply_key_age_ms),
|
||||
maximum_reply_surb_age: Duration::from_millis(reply_surbs.maximum_reply_surb_age_ms),
|
||||
maximum_reply_key_age: Duration::from_millis(reply_surbs.maximum_reply_key_age_ms),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConfigReplySurbs> for ReplySurbs {
|
||||
fn from(reply_surbs: ConfigReplySurbs) -> Self {
|
||||
ReplySurbs {
|
||||
minimum_reply_surb_storage_threshold: reply_surbs.minimum_reply_surb_storage_threshold,
|
||||
maximum_reply_surb_storage_threshold: reply_surbs.maximum_reply_surb_storage_threshold,
|
||||
minimum_reply_surb_request_size: reply_surbs.minimum_reply_surb_request_size,
|
||||
maximum_reply_surb_request_size: reply_surbs.maximum_reply_surb_request_size,
|
||||
maximum_allowed_reply_surb_request_size: reply_surbs
|
||||
.maximum_allowed_reply_surb_request_size,
|
||||
maximum_reply_surb_rerequest_waiting_period_ms: reply_surbs
|
||||
.maximum_reply_surb_rerequest_waiting_period
|
||||
.as_millis() as u64,
|
||||
maximum_reply_surb_drop_waiting_period_ms: reply_surbs
|
||||
.maximum_reply_surb_drop_waiting_period
|
||||
.as_millis() as u64,
|
||||
maximum_reply_surb_age_ms: reply_surbs.maximum_reply_surb_age.as_millis() as u64,
|
||||
maximum_reply_key_age_ms: reply_surbs.maximum_reply_key_age.as_millis() as u64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// just a helper structure to more easily pass through the JS boundary
|
||||
#[wasm_bindgen]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Debug {
|
||||
/// Defines all configuration options related to traffic streams.
|
||||
pub traffic: Traffic,
|
||||
|
||||
/// Defines all configuration options related to cover traffic stream(s).
|
||||
pub cover_traffic: CoverTraffic,
|
||||
|
||||
/// Defines all configuration options related to the gateway connection.
|
||||
pub gateway_connection: GatewayConnection,
|
||||
|
||||
/// Defines all configuration options related to acknowledgements, such as delays or wait timeouts.
|
||||
pub acknowledgements: Acknowledgements,
|
||||
|
||||
/// Defines all configuration options related topology, such as refresh rates or timeouts.
|
||||
pub topology: Topology,
|
||||
|
||||
/// Defines all configuration options related to reply SURBs.
|
||||
pub reply_surbs: ReplySurbs,
|
||||
}
|
||||
|
||||
impl From<Debug> for ConfigDebug {
|
||||
fn from(debug: Debug) -> Self {
|
||||
ConfigDebug {
|
||||
traffic: debug.traffic.into(),
|
||||
cover_traffic: debug.cover_traffic.into(),
|
||||
gateway_connection: debug.gateway_connection.into(),
|
||||
acknowledgements: debug.acknowledgements.into(),
|
||||
topology: debug.topology.into(),
|
||||
reply_surbs: debug.reply_surbs.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,34 +360,12 @@ impl From<Debug> for ConfigDebug {
|
||||
impl From<ConfigDebug> for Debug {
|
||||
fn from(debug: ConfigDebug) -> Self {
|
||||
Debug {
|
||||
average_packet_delay_ms: debug.average_packet_delay.as_millis() as u64,
|
||||
average_ack_delay_ms: debug.average_ack_delay.as_millis() as u64,
|
||||
ack_wait_multiplier: debug.ack_wait_multiplier,
|
||||
ack_wait_addition_ms: debug.ack_wait_addition.as_millis() as u64,
|
||||
loop_cover_traffic_average_delay_ms: debug.loop_cover_traffic_average_delay.as_millis()
|
||||
as u64,
|
||||
message_sending_average_delay_ms: debug.message_sending_average_delay.as_millis()
|
||||
as u64,
|
||||
gateway_response_timeout_ms: debug.gateway_response_timeout.as_millis() as u64,
|
||||
topology_refresh_rate_ms: debug.topology_refresh_rate.as_millis() as u64,
|
||||
topology_resolution_timeout_ms: debug.topology_resolution_timeout.as_millis() as u64,
|
||||
disable_loop_cover_traffic_stream: debug.disable_loop_cover_traffic_stream,
|
||||
disable_main_poisson_packet_distribution: debug
|
||||
.disable_main_poisson_packet_distribution,
|
||||
use_extended_packet_size: debug.use_extended_packet_size.is_some(),
|
||||
minimum_reply_surb_storage_threshold: debug.minimum_reply_surb_storage_threshold,
|
||||
maximum_reply_surb_storage_threshold: debug.maximum_reply_surb_storage_threshold,
|
||||
minimum_reply_surb_request_size: debug.minimum_reply_surb_request_size,
|
||||
maximum_reply_surb_request_size: debug.maximum_reply_surb_request_size,
|
||||
maximum_allowed_reply_surb_request_size: debug.maximum_allowed_reply_surb_request_size,
|
||||
maximum_reply_surb_rerequest_waiting_period_ms: debug
|
||||
.maximum_reply_surb_rerequest_waiting_period
|
||||
.as_millis() as u64,
|
||||
maximum_reply_surb_drop_waiting_period_ms: debug
|
||||
.maximum_reply_surb_drop_waiting_period
|
||||
.as_millis() as u64,
|
||||
maximum_reply_surb_age_ms: debug.maximum_reply_surb_age.as_millis() as u64,
|
||||
maximum_reply_key_age_ms: debug.maximum_reply_key_age.as_millis() as u64,
|
||||
traffic: debug.traffic.into(),
|
||||
cover_traffic: debug.cover_traffic.into(),
|
||||
gateway_connection: debug.gateway_connection.into(),
|
||||
acknowledgements: debug.acknowledgements.into(),
|
||||
topology: debug.topology.into(),
|
||||
reply_surbs: debug.reply_surbs.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,8 +82,14 @@ impl NymClientBuilder {
|
||||
// with no persistence
|
||||
fn setup_reply_surb_storage_backend(config: &Config) -> browser_backend::Backend {
|
||||
browser_backend::Backend::new(
|
||||
config.debug.minimum_reply_surb_storage_threshold,
|
||||
config.debug.maximum_reply_surb_storage_threshold,
|
||||
config
|
||||
.debug
|
||||
.reply_surbs
|
||||
.minimum_reply_surb_storage_threshold,
|
||||
config
|
||||
.debug
|
||||
.reply_surbs
|
||||
.maximum_reply_surb_storage_threshold,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-bin-common"
|
||||
version = "0.2.0"
|
||||
version = "0.4.0"
|
||||
description = "Common code for nym binaries"
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
|
||||
@@ -40,14 +40,14 @@ async-trait = { workspace = true, optional = true }
|
||||
bip39 = { workspace = true, features = ["rand"], optional = true }
|
||||
nym-config = { path = "../../config", optional = true }
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support", features = ["rpc", "bip32", "cosmwasm"], optional = true}
|
||||
cw3 = { version = "0.13.4", optional = true }
|
||||
cw4 = { version = "0.13.4", optional = true }
|
||||
cw3 = { workspace = true, optional = true }
|
||||
cw4 = { workspace = true, optional = true }
|
||||
prost = { version = "0.10", default-features = false, optional = true }
|
||||
flate2 = { version = "1.0.20", optional = true }
|
||||
sha2 = { version = "0.9.5", optional = true }
|
||||
itertools = { version = "0.10", optional = true }
|
||||
cosmwasm-std = { version = "1.0.0", optional = true }
|
||||
zeroize = { version = "1.5.7", optional = true, features = ["zeroize_derive"] }
|
||||
cosmwasm-std = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
@@ -749,6 +749,12 @@ where
|
||||
Ok(self.nym_api.get_mixnodes_detailed().await?)
|
||||
}
|
||||
|
||||
pub async fn get_cached_mixnodes_detailed_unfiltered(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_mixnodes_detailed_unfiltered().await?)
|
||||
}
|
||||
|
||||
pub async fn get_cached_rewarded_mixnodes(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
|
||||
@@ -144,6 +144,21 @@ impl Client {
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_mixnodes_detailed_unfiltered(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.query_nym_api(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::MIXNODES,
|
||||
routes::DETAILED_UNFILTERED,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_gateways(&self) -> Result<Vec<GatewayBond>, NymAPIError> {
|
||||
self.query_nym_api(&[routes::API_VERSION, routes::GATEWAYS], NO_PARAMS)
|
||||
.await
|
||||
|
||||
@@ -8,6 +8,7 @@ pub const MIXNODES: &str = "mixnodes";
|
||||
pub const GATEWAYS: &str = "gateways";
|
||||
|
||||
pub const DETAILED: &str = "detailed";
|
||||
pub const DETAILED_UNFILTERED: &str = "detailed-unfiltered";
|
||||
pub const ACTIVE: &str = "active";
|
||||
pub const REWARDED: &str = "rewarded";
|
||||
pub const COCONUT_ROUTES: &str = "coconut";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::nyxd::coin::Coin;
|
||||
@@ -9,6 +9,7 @@ use crate::nyxd::{Fee, NyxdClient, SigningCosmWasmClient};
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::AccountId;
|
||||
use nym_contracts_common::signing::MessageSignature;
|
||||
use nym_mixnet_contract_common::families::FamilyHead;
|
||||
use nym_mixnet_contract_common::gateway::GatewayConfigUpdate;
|
||||
use nym_mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
|
||||
use nym_mixnet_contract_common::reward_params::{IntervalRewardingParamsUpdate, Performance};
|
||||
@@ -146,25 +147,16 @@ pub trait MixnetSigningClient {
|
||||
// family related
|
||||
async fn create_family(
|
||||
&self,
|
||||
owner_signature: String,
|
||||
label: String,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.execute_mixnet_contract(
|
||||
fee,
|
||||
MixnetExecuteMsg::CreateFamily {
|
||||
owner_signature,
|
||||
label,
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
self.execute_mixnet_contract(fee, MixnetExecuteMsg::CreateFamily { label }, vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn create_family_on_behalf(
|
||||
&self,
|
||||
owner_address: String,
|
||||
owner_signature: String,
|
||||
label: String,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
@@ -172,7 +164,6 @@ pub trait MixnetSigningClient {
|
||||
fee,
|
||||
MixnetExecuteMsg::CreateFamilyOnBehalf {
|
||||
owner_address,
|
||||
owner_signature,
|
||||
label,
|
||||
},
|
||||
vec![],
|
||||
@@ -182,14 +173,14 @@ pub trait MixnetSigningClient {
|
||||
|
||||
async fn join_family(
|
||||
&self,
|
||||
signature: String,
|
||||
family_head: String,
|
||||
join_permit: MessageSignature,
|
||||
family_head: FamilyHead,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.execute_mixnet_contract(
|
||||
fee,
|
||||
MixnetExecuteMsg::JoinFamily {
|
||||
signature,
|
||||
join_permit,
|
||||
family_head,
|
||||
},
|
||||
vec![],
|
||||
@@ -200,17 +191,15 @@ pub trait MixnetSigningClient {
|
||||
async fn join_family_on_behalf(
|
||||
&self,
|
||||
member_address: String,
|
||||
node_identity_signature: String,
|
||||
family_signature: String,
|
||||
family_head: String,
|
||||
join_permit: MessageSignature,
|
||||
family_head: FamilyHead,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.execute_mixnet_contract(
|
||||
fee,
|
||||
MixnetExecuteMsg::JoinFamilyOnBehalf {
|
||||
member_address,
|
||||
node_identity_signature,
|
||||
family_signature,
|
||||
join_permit,
|
||||
family_head,
|
||||
},
|
||||
vec![],
|
||||
@@ -220,33 +209,23 @@ pub trait MixnetSigningClient {
|
||||
|
||||
async fn leave_family(
|
||||
&self,
|
||||
signature: String,
|
||||
family_head: String,
|
||||
family_head: FamilyHead,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.execute_mixnet_contract(
|
||||
fee,
|
||||
MixnetExecuteMsg::LeaveFamily {
|
||||
signature,
|
||||
family_head,
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
self.execute_mixnet_contract(fee, MixnetExecuteMsg::LeaveFamily { family_head }, vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn leave_family_on_behalf(
|
||||
&self,
|
||||
member_address: String,
|
||||
node_identity_signature: String,
|
||||
family_head: String,
|
||||
family_head: FamilyHead,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.execute_mixnet_contract(
|
||||
fee,
|
||||
MixnetExecuteMsg::LeaveFamilyOnBehalf {
|
||||
member_address,
|
||||
node_identity_signature,
|
||||
family_head,
|
||||
},
|
||||
vec![],
|
||||
@@ -256,22 +235,16 @@ pub trait MixnetSigningClient {
|
||||
|
||||
async fn kick_family_member(
|
||||
&self,
|
||||
signature: String,
|
||||
member: String,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.execute_mixnet_contract(
|
||||
fee,
|
||||
MixnetExecuteMsg::KickFamilyMember { signature, member },
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
self.execute_mixnet_contract(fee, MixnetExecuteMsg::KickFamilyMember { member }, vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn kick_family_member_on_behalf(
|
||||
&self,
|
||||
head_address: String,
|
||||
signature: String,
|
||||
member: String,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
@@ -279,7 +252,6 @@ pub trait MixnetSigningClient {
|
||||
fee,
|
||||
MixnetExecuteMsg::KickFamilyMemberOnBehalf {
|
||||
head_address,
|
||||
signature,
|
||||
member,
|
||||
},
|
||||
vec![],
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::{Coin, Fee, NyxdClient};
|
||||
use async_trait::async_trait;
|
||||
use nym_contracts_common::signing::MessageSignature;
|
||||
use nym_mixnet_contract_common::families::FamilyHead;
|
||||
use nym_mixnet_contract_common::gateway::GatewayConfigUpdate;
|
||||
use nym_mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
|
||||
use nym_mixnet_contract_common::{Gateway, MixId, MixNode};
|
||||
@@ -136,6 +137,50 @@ pub trait VestingSigningClient {
|
||||
cap: Option<PledgeCap>,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError>;
|
||||
|
||||
async fn vesting_create_family(
|
||||
&self,
|
||||
label: String,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.execute_vesting_contract(fee, VestingExecuteMsg::CreateFamily { label }, vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn vesting_join_family(
|
||||
&self,
|
||||
join_permit: MessageSignature,
|
||||
family_head: FamilyHead,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.execute_vesting_contract(
|
||||
fee,
|
||||
VestingExecuteMsg::JoinFamily {
|
||||
join_permit,
|
||||
family_head,
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn vesting_leave_family(
|
||||
&self,
|
||||
family_head: FamilyHead,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.execute_vesting_contract(fee, VestingExecuteMsg::LeaveFamily { family_head }, vec![])
|
||||
.await
|
||||
}
|
||||
|
||||
async fn vesting_kick_family_member(
|
||||
&self,
|
||||
member: String,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.execute_vesting_contract(fee, VestingExecuteMsg::KickFamilyMember { member }, vec![])
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -11,7 +11,7 @@ bs58 = "0.4"
|
||||
comfy-table = "6.0.0"
|
||||
cfg-if = "1.0.0"
|
||||
clap = { version = "4.0", features = ["derive"] }
|
||||
cw-utils = { version = "0.13.4" }
|
||||
cw-utils = { workspace = true }
|
||||
handlebars = "3.0.1"
|
||||
humantime-serde = "1.0"
|
||||
k256 = { version = "0.10", features = ["ecdsa", "sha256"] }
|
||||
@@ -26,9 +26,10 @@ url = "2.2"
|
||||
tap = "1"
|
||||
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
cosmwasm-std = { version = "1.0.0" }
|
||||
cosmwasm-std = { workspace = true }
|
||||
|
||||
validator-client = { path = "../client-libs/validator-client", features = ["nyxd-client"] }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common" }
|
||||
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use validator_client::nyxd::traits::MixnetSigningClient;
|
||||
use validator_client::nyxd::VestingSigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
/// Label that is going to be used for creating the family
|
||||
#[arg(long)]
|
||||
pub family_label: String,
|
||||
|
||||
/// Indicates whether the family is going to get created via a vesting account
|
||||
#[arg(long)]
|
||||
pub with_vesting_account: bool,
|
||||
}
|
||||
|
||||
pub async fn create_family(args: Args, client: SigningClient) {
|
||||
info!("Create family");
|
||||
|
||||
let res = if args.with_vesting_account {
|
||||
client
|
||||
.vesting_create_family(args.family_label, None)
|
||||
.await
|
||||
.expect("failed to create family with vesting account")
|
||||
} else {
|
||||
client
|
||||
.create_family(args.family_label, None)
|
||||
.await
|
||||
.expect("failed to create family")
|
||||
};
|
||||
|
||||
info!("Family creation result: {:?}", res);
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::QueryClient;
|
||||
use crate::utils::account_id_to_cw_addr;
|
||||
use clap::Parser;
|
||||
use cosmrs::AccountId;
|
||||
use log::info;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_mixnet_contract_common::construct_family_join_permit;
|
||||
use nym_mixnet_contract_common::families::FamilyHead;
|
||||
use validator_client::nyxd::traits::MixnetQueryClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
/// Account address (i.e. owner of the family head) which will be used for issuing the permit
|
||||
#[arg(long)]
|
||||
pub address: AccountId,
|
||||
|
||||
/// Indicates whether the member joining the family is going to use the vesting account for joining.
|
||||
#[arg(long)]
|
||||
pub with_vesting_account: bool,
|
||||
|
||||
// might as well validate the value when parsing the arguments
|
||||
/// Identity of the member for whom we're issuing the permit
|
||||
#[arg(long)]
|
||||
pub member: identity::PublicKey,
|
||||
}
|
||||
|
||||
pub async fn create_family_join_permit_sign_payload(args: Args, client: QueryClient) {
|
||||
info!("Create family join permit sign payload");
|
||||
|
||||
// get the address of our mixnode to recover the family head information
|
||||
let Some(mixnode) = client.get_owned_mixnode(&args.address).await.unwrap().mixnode_details else {
|
||||
eprintln!("{} does not seem to even own a mixnode!", args.address);
|
||||
return
|
||||
};
|
||||
|
||||
// make sure this mixnode is actually a family head
|
||||
if client
|
||||
.get_node_family_by_head(mixnode.bond_information.identity())
|
||||
.await
|
||||
.unwrap()
|
||||
.is_none()
|
||||
{
|
||||
eprintln!("{} does not even seem to own a family!", args.address);
|
||||
return;
|
||||
}
|
||||
|
||||
let nonce = match client.get_signing_nonce(&args.address).await {
|
||||
Ok(nonce) => nonce,
|
||||
Err(err) => {
|
||||
eprint!(
|
||||
"failed to query for the signing nonce of {}: {err}",
|
||||
args.address
|
||||
);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// let address = account_id_to_cw_addr(&args.address);
|
||||
let proxy = if args.with_vesting_account {
|
||||
Some(account_id_to_cw_addr(client.vesting_contract_address()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let head = FamilyHead::new(mixnode.bond_information.identity());
|
||||
|
||||
let payload = construct_family_join_permit(nonce, head, proxy, args.member.to_base58_string());
|
||||
println!("{}", payload.to_base58_string().unwrap())
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use nym_contracts_common::signing::MessageSignature;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_mixnet_contract_common::families::FamilyHead;
|
||||
use validator_client::nyxd::traits::MixnetSigningClient;
|
||||
use validator_client::nyxd::VestingSigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
/// The head of the family that we intend to join
|
||||
#[arg(long)]
|
||||
pub family_head: identity::PublicKey,
|
||||
|
||||
/// Indicates whether the member joining the family is going to do so via the vesting contract
|
||||
#[arg(long)]
|
||||
pub with_vesting_account: bool,
|
||||
|
||||
/// Permission, as provided by the family head, for joining the family
|
||||
#[arg(long)]
|
||||
pub join_permit: MessageSignature,
|
||||
}
|
||||
|
||||
pub async fn join_family(args: Args, client: SigningClient) {
|
||||
info!("Join family");
|
||||
|
||||
let family_head = FamilyHead::new(&args.family_head.to_base58_string());
|
||||
|
||||
let res = if args.with_vesting_account {
|
||||
client
|
||||
.vesting_join_family(args.join_permit, family_head, None)
|
||||
.await
|
||||
.expect("failed to join family with vesting account")
|
||||
} else {
|
||||
client
|
||||
.join_family(args.join_permit, family_head, None)
|
||||
.await
|
||||
.expect("failed to join family")
|
||||
};
|
||||
|
||||
info!("Family join result: {:?}", res);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use validator_client::nyxd::traits::MixnetSigningClient;
|
||||
use validator_client::nyxd::VestingSigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
/// The member of the family that we intend to kick
|
||||
#[arg(long)]
|
||||
pub member: identity::PublicKey,
|
||||
|
||||
/// Indicates whether the family was created (and managed) via the vesting contract
|
||||
#[arg(long)]
|
||||
pub with_vesting_account: bool,
|
||||
}
|
||||
|
||||
pub async fn kick_family_member(args: Args, client: SigningClient) {
|
||||
info!("Leave family");
|
||||
|
||||
let member = args.member.to_base58_string();
|
||||
|
||||
let res = if args.with_vesting_account {
|
||||
client
|
||||
.vesting_kick_family_member(member, None)
|
||||
.await
|
||||
.expect("failed to kick family member with vesting account")
|
||||
} else {
|
||||
client
|
||||
.kick_family_member(member, None)
|
||||
.await
|
||||
.expect("failed to kick family member")
|
||||
};
|
||||
|
||||
info!("Family leave result: {:?}", res);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_mixnet_contract_common::families::FamilyHead;
|
||||
use validator_client::nyxd::traits::MixnetSigningClient;
|
||||
use validator_client::nyxd::VestingSigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
/// The head of the family that we intend to leave
|
||||
#[arg(long)]
|
||||
pub family_head: identity::PublicKey,
|
||||
|
||||
/// Indicates whether we joined the family via the vesting contract
|
||||
#[arg(long)]
|
||||
pub with_vesting_account: bool,
|
||||
}
|
||||
|
||||
pub async fn leave_family(args: Args, client: SigningClient) {
|
||||
info!("Leave family");
|
||||
|
||||
let family_head = FamilyHead::new(&args.family_head.to_base58_string());
|
||||
|
||||
let res = if args.with_vesting_account {
|
||||
client
|
||||
.vesting_leave_family(family_head, None)
|
||||
.await
|
||||
.expect("failed to leave family with vesting account")
|
||||
} else {
|
||||
client
|
||||
.leave_family(family_head, None)
|
||||
.await
|
||||
.expect("failed to leave family")
|
||||
};
|
||||
|
||||
info!("Family leave result: {:?}", res);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod create_family;
|
||||
pub mod create_family_join_permit_sign_payload;
|
||||
pub mod join_family;
|
||||
pub mod kick_family_member;
|
||||
pub mod leave_family;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct MixnetOperatorsMixnodeFamilies {
|
||||
#[clap(subcommand)]
|
||||
pub command: MixnetOperatorsMixnodeFamiliesCommands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum MixnetOperatorsMixnodeFamiliesCommands {
|
||||
/// Create family
|
||||
CreateFamily(create_family::Args),
|
||||
|
||||
/// Join family
|
||||
JoinFamily(join_family::Args),
|
||||
|
||||
/// Leave family,
|
||||
LeaveFamily(leave_family::Args),
|
||||
|
||||
/// Kick family member
|
||||
KickFamilyMember(kick_family_member::Args),
|
||||
|
||||
/// Create a message payload that is required to get signed in order to obtain a permit for joining family
|
||||
CreateFamilyJoinPermitSignPayload(create_family_join_permit_sign_payload::Args),
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod bond_mixnode;
|
||||
pub mod families;
|
||||
pub mod keys;
|
||||
pub mod mixnode_bonding_sign_payload;
|
||||
pub mod rewards;
|
||||
@@ -27,6 +28,8 @@ pub enum MixnetOperatorsMixnodeCommands {
|
||||
Rewards(rewards::MixnetOperatorsMixnodeRewards),
|
||||
/// Manage your mixnode settings stored in the directory
|
||||
Settings(settings::MixnetOperatorsMixnodeSettings),
|
||||
/// Operations for mixnode families
|
||||
Families(families::MixnetOperatorsMixnodeFamilies),
|
||||
/// Bond to a mixnode
|
||||
Bond(bond_mixnode::Args),
|
||||
/// Unbond from a mixnode
|
||||
|
||||
@@ -6,7 +6,7 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmwasm-std = { workspace = true }
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
nym-multisig-contract-common = { path = "../multisig-contract" }
|
||||
|
||||
@@ -6,8 +6,8 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cosmwasm-std = "1.0.0"
|
||||
cw-utils = "0.13.4"
|
||||
cosmwasm-std = { workspace = true }
|
||||
cw-utils = { workspace = true }
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-contracts-common"
|
||||
version = "0.2.0"
|
||||
version = "0.4.0"
|
||||
description = "Common library for Nym cosmwasm contracts"
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
@@ -9,7 +9,7 @@ repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
bs58 = "0.4.0"
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmwasm-std = { workspace = true }
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
thiserror = "1"
|
||||
|
||||
@@ -6,6 +6,6 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cw4 = { version = "0.13.4" }
|
||||
cw4 = { workspace = true }
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-mixnet-contract-common"
|
||||
version = "0.2.0"
|
||||
version = "0.4.0"
|
||||
description = "Common library for the Nym mixnet contract"
|
||||
rust-version = "1.62"
|
||||
edition = { workspace = true }
|
||||
@@ -10,12 +10,12 @@ repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
bs58 = "0.4.0"
|
||||
cosmwasm-std = "=1.0.0"
|
||||
cosmwasm-std = { workspace = true }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_repr = "0.1"
|
||||
schemars = "0.8"
|
||||
thiserror = "1.0"
|
||||
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.2.0" }
|
||||
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.4.0" }
|
||||
# use 0.4.1 as that's the version used by cosmwasm-std 1.0.0
|
||||
# (and ideally we don't want to pull the same dependency twice)
|
||||
serde-json-wasm = "=0.4.1"
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use crate::{IdentityKey, IdentityKeyRef};
|
||||
use cosmwasm_std::Addr;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[cfg_attr(feature = "generate-ts", derive(ts_rs::TS))]
|
||||
#[cfg_attr(
|
||||
@@ -20,9 +22,45 @@ pub struct Family {
|
||||
feature = "generate-ts",
|
||||
ts(export_to = "ts-packages/types/src/types/rust/NodeFamilyHead.ts")
|
||||
)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, JsonSchema)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, JsonSchema)]
|
||||
pub struct FamilyHead(IdentityKey);
|
||||
|
||||
impl Serialize for FamilyHead {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
self.0.serialize(serializer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for FamilyHead {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let inner = IdentityKey::deserialize(deserializer)?;
|
||||
Ok(FamilyHead(inner))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for FamilyHead {
|
||||
type Err = <IdentityKey as FromStr>::Err;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
// theoretically we should be verifying whether it's a valid base58 value
|
||||
// (or even better, whether it's a valid ed25519 public key), but definition of
|
||||
// `FamilyHead` might change later
|
||||
Ok(FamilyHead(IdentityKey::from_str(s)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for FamilyHead {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl FamilyHead {
|
||||
pub fn new(identity: IdentityKeyRef<'_>) -> Self {
|
||||
FamilyHead(identity.to_string())
|
||||
@@ -34,11 +72,11 @@ impl FamilyHead {
|
||||
}
|
||||
|
||||
impl Family {
|
||||
pub fn new(head: FamilyHead, proxy: Option<Addr>, label: &str) -> Self {
|
||||
pub fn new(head: FamilyHead, proxy: Option<Addr>, label: String) -> Self {
|
||||
Family {
|
||||
head,
|
||||
proxy: proxy.map(|p| p.to_string()),
|
||||
label: label.to_string(),
|
||||
label,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,3 +98,21 @@ impl Family {
|
||||
&self.label
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn family_head_serde() {
|
||||
let dummy = FamilyHead::new("foomp");
|
||||
|
||||
let ser_str = serde_json_wasm::to_string(&dummy).unwrap();
|
||||
let de_str: FamilyHead = serde_json_wasm::from_str(&ser_str).unwrap();
|
||||
assert_eq!(dummy, de_str);
|
||||
|
||||
let ser_bytes = serde_json_wasm::to_vec(&dummy).unwrap();
|
||||
let de_bytes: FamilyHead = serde_json_wasm::from_slice(&ser_bytes).unwrap();
|
||||
assert_eq!(dummy, de_bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::delegation::OwnerProxySubKey;
|
||||
use crate::error::MixnetContractError;
|
||||
use crate::families::FamilyHead;
|
||||
use crate::gateway::GatewayConfigUpdate;
|
||||
use crate::helpers::IntoBaseDecimal;
|
||||
use crate::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
|
||||
@@ -82,42 +83,35 @@ pub enum ExecuteMsg {
|
||||
// Families
|
||||
/// Only owner of the node can crate the family with node as head
|
||||
CreateFamily {
|
||||
owner_signature: String,
|
||||
label: String,
|
||||
},
|
||||
/// Family head needs to sign the joining node IdentityKey
|
||||
JoinFamily {
|
||||
signature: String,
|
||||
family_head: IdentityKey,
|
||||
join_permit: MessageSignature,
|
||||
family_head: FamilyHead,
|
||||
},
|
||||
LeaveFamily {
|
||||
signature: String,
|
||||
family_head: IdentityKey,
|
||||
family_head: FamilyHead,
|
||||
},
|
||||
KickFamilyMember {
|
||||
signature: String,
|
||||
member: IdentityKey,
|
||||
},
|
||||
CreateFamilyOnBehalf {
|
||||
owner_address: String,
|
||||
owner_signature: String,
|
||||
label: String,
|
||||
},
|
||||
/// Family head needs to sign the joining node IdentityKey, MixNode needs to provide its signature proving that it wants to join the family
|
||||
JoinFamilyOnBehalf {
|
||||
member_address: String,
|
||||
node_identity_signature: String,
|
||||
family_signature: String,
|
||||
family_head: IdentityKey,
|
||||
join_permit: MessageSignature,
|
||||
family_head: FamilyHead,
|
||||
},
|
||||
LeaveFamilyOnBehalf {
|
||||
member_address: String,
|
||||
node_identity_signature: String,
|
||||
family_head: IdentityKey,
|
||||
family_head: FamilyHead,
|
||||
},
|
||||
KickFamilyMemberOnBehalf {
|
||||
head_address: String,
|
||||
signature: String,
|
||||
member: IdentityKey,
|
||||
},
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{Gateway, MixNode, MixNodeCostParams};
|
||||
use crate::families::FamilyHead;
|
||||
use crate::{Gateway, IdentityKey, MixNode, MixNodeCostParams};
|
||||
use contracts_common::signing::{
|
||||
ContractMessageContent, MessageType, Nonce, SignableMessage, SigningPurpose,
|
||||
};
|
||||
@@ -10,6 +11,7 @@ use serde::Serialize;
|
||||
|
||||
pub type SignableMixNodeBondingMsg = SignableMessage<ContractMessageContent<MixnodeBondingPayload>>;
|
||||
pub type SignableGatewayBondingMsg = SignableMessage<ContractMessageContent<GatewayBondingPayload>>;
|
||||
pub type SignableFamilyJoinPermitMsg = SignableMessage<FamilyJoinPermit>;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct MixnodeBondingPayload {
|
||||
@@ -77,75 +79,43 @@ pub fn construct_gateway_bonding_sign_payload(
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct FamilyCreationSignature {
|
||||
label: String,
|
||||
// TODO: add any extra fields?
|
||||
pub struct FamilyJoinPermit {
|
||||
// the granter of this permit
|
||||
family_head: FamilyHead,
|
||||
// whether the **member** will want to join via the proxy (i.e. vesting contract)
|
||||
proxy: Option<Addr>,
|
||||
// the actual member we want to permit to join
|
||||
member_node: IdentityKey,
|
||||
}
|
||||
|
||||
impl FamilyCreationSignature {
|
||||
pub fn new(label: String) -> Self {
|
||||
Self { label }
|
||||
impl FamilyJoinPermit {
|
||||
pub fn new(family_head: FamilyHead, proxy: Option<Addr>, member_node: IdentityKey) -> Self {
|
||||
Self {
|
||||
family_head,
|
||||
proxy,
|
||||
member_node,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SigningPurpose for FamilyCreationSignature {
|
||||
impl SigningPurpose for FamilyJoinPermit {
|
||||
fn message_type() -> MessageType {
|
||||
MessageType::new("family-creation")
|
||||
MessageType::new("family-join-permit")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct FamilyJoinSignature {
|
||||
family_head: String,
|
||||
// TODO: add any extra fields?
|
||||
}
|
||||
pub fn construct_family_join_permit(
|
||||
nonce: Nonce,
|
||||
family_head: FamilyHead,
|
||||
proxy: Option<Addr>,
|
||||
member_node: IdentityKey,
|
||||
) -> SignableFamilyJoinPermitMsg {
|
||||
let payload = FamilyJoinPermit::new(family_head, proxy, member_node);
|
||||
|
||||
impl FamilyJoinSignature {
|
||||
pub fn new(family_head: String) -> Self {
|
||||
Self { family_head }
|
||||
}
|
||||
}
|
||||
|
||||
impl SigningPurpose for FamilyJoinSignature {
|
||||
fn message_type() -> MessageType {
|
||||
MessageType::new("family-join")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct FamilyLeaveSignature {
|
||||
family_head: String,
|
||||
// TODO: add any extra fields?
|
||||
}
|
||||
|
||||
impl FamilyLeaveSignature {
|
||||
pub fn new(family_head: String) -> Self {
|
||||
Self { family_head }
|
||||
}
|
||||
}
|
||||
|
||||
impl SigningPurpose for FamilyLeaveSignature {
|
||||
fn message_type() -> MessageType {
|
||||
MessageType::new("family-leave")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct FamilyKickSignature {
|
||||
member: String,
|
||||
// TODO: add any extra fields?
|
||||
}
|
||||
|
||||
impl FamilyKickSignature {
|
||||
pub fn new(member: String) -> Self {
|
||||
Self { member }
|
||||
}
|
||||
}
|
||||
|
||||
impl SigningPurpose for FamilyKickSignature {
|
||||
fn message_type() -> MessageType {
|
||||
MessageType::new("family-member-removal")
|
||||
}
|
||||
// note: we're NOT wrapping it in `ContractMessageContent` because the family head is not going to be the one
|
||||
// sending the message to the contract
|
||||
SignableMessage::new(nonce, payload)
|
||||
}
|
||||
|
||||
// TODO: depending on our threat model, we should perhaps extend it to include all _on_behalf methods
|
||||
// (update: but we trust our vesting contract since its compromise would be even more devastating so there's no need)
|
||||
|
||||
@@ -6,11 +6,11 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
cw-utils = { version = "0.13.4" }
|
||||
cw3 = { version = "0.13.4" }
|
||||
cw3-fixed-multisig = { version = "0.13.4", features = ["library"] }
|
||||
cw4 = { version = "0.13.4" }
|
||||
cosmwasm-std = "1.0.0"
|
||||
cw-utils = { workspace = true }
|
||||
cw3 = { workspace = true }
|
||||
cw3-fixed-multisig = { workspace = true, features = ["library"] }
|
||||
cw4 = { workspace= true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = { version = "1.0.23" }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-vesting-contract-common"
|
||||
version = "0.2.0"
|
||||
version = "0.4.0"
|
||||
description = "Common library for the Nym vesting contract"
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
@@ -8,9 +8,9 @@ license = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
cosmwasm-std = "1.0.0"
|
||||
mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.2.0" }
|
||||
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.2.0" }
|
||||
cosmwasm-std = { workspace = true }
|
||||
mixnet-contract-common = { path = "../mixnet-contract", package = "nym-mixnet-contract-common", version = "0.4.0" }
|
||||
contracts-common = { path = "../contracts-common", package = "nym-contracts-common", version = "0.4.0" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
schemars = "0.8"
|
||||
ts-rs = {version = "6.1.2", optional = true}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use contracts_common::signing::MessageSignature;
|
||||
use cosmwasm_std::{Coin, Timestamp};
|
||||
use mixnet_contract_common::families::FamilyHead;
|
||||
use mixnet_contract_common::{
|
||||
gateway::GatewayConfigUpdate,
|
||||
mixnode::{MixNodeConfigUpdate, MixNodeCostParams},
|
||||
@@ -60,21 +61,17 @@ pub enum ExecuteMsg {
|
||||
// Families
|
||||
/// Only owner of the node can crate the family with node as head
|
||||
CreateFamily {
|
||||
owner_signature: String,
|
||||
label: String,
|
||||
},
|
||||
/// Family head needs to sign the joining node IdentityKey, the Node provides its signature signaling consent to join the family
|
||||
JoinFamily {
|
||||
node_identity_signature: String,
|
||||
family_signature: String,
|
||||
family_head: IdentityKey,
|
||||
join_permit: MessageSignature,
|
||||
family_head: FamilyHead,
|
||||
},
|
||||
LeaveFamily {
|
||||
node_identity_signature: String,
|
||||
family_head: IdentityKey,
|
||||
family_head: FamilyHead,
|
||||
},
|
||||
KickFamilyMember {
|
||||
signature: String,
|
||||
member: IdentityKey,
|
||||
},
|
||||
TrackReward {
|
||||
|
||||
@@ -99,17 +99,24 @@ impl ConnectionHandler {
|
||||
let mut framed_conn = Framed::new(conn, EchoPacketCodec);
|
||||
while !shutdown_listener.is_shutdown() {
|
||||
tokio::select! {
|
||||
Some(echo_packet) = framed_conn.next() => {
|
||||
biased;
|
||||
_ = shutdown_listener.recv() => {
|
||||
trace!("ConnectionHandler: Shutdown received");
|
||||
}
|
||||
maybe_echo_packet = framed_conn.next() => {
|
||||
// handle echo packet
|
||||
let reply_packet = match echo_packet {
|
||||
Ok(echo_packet) => self.handle_echo_packet(echo_packet),
|
||||
Err(err) => {
|
||||
error!(
|
||||
"The socket connection got corrupted with error: {}. Closing the socket",
|
||||
err
|
||||
let reply_packet = match maybe_echo_packet {
|
||||
Some(Ok(echo_packet)) => self.handle_echo_packet(echo_packet),
|
||||
Some(Err(err)) => {
|
||||
error!(
|
||||
"The socket connection got corrupted with error: {err}. Closing the socket",
|
||||
);
|
||||
return;
|
||||
}
|
||||
None => {
|
||||
error!("The socket connection got terminated by the remote!");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// write back the reply (note the lack of framing)
|
||||
@@ -125,9 +132,6 @@ impl ConnectionHandler {
|
||||
return;
|
||||
}
|
||||
},
|
||||
_ = shutdown_listener.recv() => {
|
||||
trace!("ConnectionHandler: Shutdown received");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ nym-sphinx-types = { path = "types" }
|
||||
nym-crypto = { path = "../crypto", version = "0.2.0" }
|
||||
nym-topology = { path = "../topology" }
|
||||
|
||||
# outfox
|
||||
nym-outfox = { path = "../../nym-outfox" }
|
||||
|
||||
[dev-dependencies]
|
||||
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ use nym_crypto::asymmetric::encryption;
|
||||
use nym_crypto::shared_key::recompute_shared_key;
|
||||
use nym_crypto::symmetric::stream_cipher;
|
||||
use nym_crypto::symmetric::stream_cipher::CipherKey;
|
||||
use nym_outfox::error::OutfoxError;
|
||||
use nym_outfox::lion::lion_transform_decrypt;
|
||||
use nym_sphinx_anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx_anonymous_replies::SurbEncryptionKey;
|
||||
use nym_sphinx_chunking::fragment::Fragment;
|
||||
@@ -74,54 +76,76 @@ pub enum MessageRecoveryError {
|
||||
|
||||
#[error("Failed to recover message fragment - {0}")]
|
||||
FragmentRecoveryError(#[from] ChunkingError),
|
||||
|
||||
#[error("Outfox: {source}")]
|
||||
OutfoxRecoveryError {
|
||||
#[from]
|
||||
source: OutfoxError,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct MessageReceiver {
|
||||
/// High level public structure used to buffer all received data [`Fragment`]s and eventually
|
||||
/// returning original messages that they encapsulate.
|
||||
#[derive(Default)]
|
||||
pub struct OutfoxMessageReceiver {
|
||||
reconstructor: MessageReconstructor,
|
||||
|
||||
/// Number of mix hops each packet ('real' message, ack, reply) is expected to take.
|
||||
/// Note that it does not include gateway hops.
|
||||
num_mix_hops: u8,
|
||||
}
|
||||
|
||||
impl MessageReceiver {
|
||||
impl OutfoxMessageReceiver {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows setting non-default number of expected mix hops in the network.
|
||||
#[must_use]
|
||||
pub fn with_mix_hops(mut self, hops: u8) -> Self {
|
||||
self.num_mix_hops = hops;
|
||||
self
|
||||
impl MessageReceiver for OutfoxMessageReceiver {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn decrypt_raw_message<C>(&self, message: &mut [u8], key: &CipherKey<C>)
|
||||
fn reconstructor(&mut self) -> &mut MessageReconstructor {
|
||||
&mut self.reconstructor
|
||||
}
|
||||
|
||||
fn num_mix_hops(&self) -> u8 {
|
||||
DEFAULT_NUM_MIX_HOPS
|
||||
}
|
||||
|
||||
fn decrypt_raw_message<C>(
|
||||
&self,
|
||||
message: &mut [u8],
|
||||
key: &CipherKey<C>,
|
||||
) -> Result<(), MessageRecoveryError>
|
||||
where
|
||||
C: StreamCipher + KeyIvInit,
|
||||
{
|
||||
let zero_iv = stream_cipher::zero_iv::<C>();
|
||||
stream_cipher::decrypt_in_place::<C>(key, &zero_iv, message)
|
||||
lion_transform_decrypt(message, key)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Given raw fragment data, **WITH KEY DIGEST PREFIX ALREADY REMOVED!!**, uses looked up
|
||||
/// key to decrypt fragment data
|
||||
pub fn recover_plaintext_from_reply(
|
||||
pub trait MessageReceiver {
|
||||
fn new() -> Self;
|
||||
fn reconstructor(&mut self) -> &mut MessageReconstructor;
|
||||
fn num_mix_hops(&self) -> u8;
|
||||
|
||||
fn decrypt_raw_message<C>(
|
||||
&self,
|
||||
message: &mut [u8],
|
||||
key: &CipherKey<C>,
|
||||
) -> Result<(), MessageRecoveryError>
|
||||
where
|
||||
C: StreamCipher + KeyIvInit;
|
||||
|
||||
fn recover_plaintext_from_reply(
|
||||
&self,
|
||||
reply_ciphertext: &mut [u8],
|
||||
reply_key: SurbEncryptionKey,
|
||||
) {
|
||||
) -> Result<(), MessageRecoveryError> {
|
||||
self.decrypt_raw_message::<ReplySurbEncryptionAlgorithm>(
|
||||
reply_ciphertext,
|
||||
reply_key.inner(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Given raw fragment data, recovers the remote ephemeral key, recomputes shared secret,
|
||||
/// uses it to decrypt fragment data
|
||||
pub fn recover_plaintext_from_regular_packet<'a>(
|
||||
fn recover_plaintext_from_regular_packet<'a>(
|
||||
&self,
|
||||
local_key: &encryption::PrivateKey,
|
||||
raw_enc_frag: &'a mut [u8],
|
||||
@@ -146,30 +170,25 @@ impl MessageReceiver {
|
||||
// 3. decrypt fragment data
|
||||
let fragment_ciphertext = &mut raw_enc_frag[encryption::PUBLIC_KEY_SIZE..];
|
||||
|
||||
self.decrypt_raw_message::<PacketEncryptionAlgorithm>(fragment_ciphertext, &encryption_key);
|
||||
self.decrypt_raw_message::<PacketEncryptionAlgorithm>(
|
||||
fragment_ciphertext,
|
||||
&encryption_key,
|
||||
)?;
|
||||
let fragment_data = fragment_ciphertext;
|
||||
|
||||
Ok(fragment_data)
|
||||
}
|
||||
|
||||
/// Given fragment data recovers [`Fragment`] itself.
|
||||
pub fn recover_fragment(&self, frag_data: &[u8]) -> Result<Fragment, MessageRecoveryError> {
|
||||
fn recover_fragment(&self, frag_data: &[u8]) -> Result<Fragment, MessageRecoveryError> {
|
||||
Ok(Fragment::try_from_bytes(frag_data)?)
|
||||
}
|
||||
|
||||
/// Inserts given [`Fragment`] into the reconstructor.
|
||||
/// If it was last remaining [`Fragment`] for the original message, the message is reconstructed
|
||||
/// and returned alongside all (if applicable) set ids used in the message.
|
||||
///
|
||||
/// # Returns:
|
||||
/// - The reconstructed message alongside optional reply SURB,
|
||||
/// - List of ids of all the [`Set`]s used during reconstruction to detect stale retransmissions.
|
||||
pub fn insert_new_fragment(
|
||||
fn insert_new_fragment(
|
||||
&mut self,
|
||||
fragment: Fragment,
|
||||
) -> Result<Option<(NymMessage, Vec<i32>)>, MessageRecoveryError> {
|
||||
if let Some((message, used_sets)) = self.reconstructor.insert_new_fragment(fragment) {
|
||||
match PaddedMessage::new_reconstructed(message).remove_padding(self.num_mix_hops) {
|
||||
if let Some((message, used_sets)) = self.reconstructor().insert_new_fragment(fragment) {
|
||||
match PaddedMessage::new_reconstructed(message).remove_padding(self.num_mix_hops()) {
|
||||
Ok(message) => Ok(Some((message, used_sets))),
|
||||
Err(err) => Err(MessageRecoveryError::MalformedReconstructedMessage {
|
||||
source: err,
|
||||
@@ -182,9 +201,56 @@ impl MessageReceiver {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MessageReceiver {
|
||||
#[derive(Clone)]
|
||||
pub struct SphinxMessageReceiver {
|
||||
/// High level public structure used to buffer all received data [`Fragment`]s and eventually
|
||||
/// returning original messages that they encapsulate.
|
||||
reconstructor: MessageReconstructor,
|
||||
|
||||
/// Number of mix hops each packet ('real' message, ack, reply) is expected to take.
|
||||
/// Note that it does not include gateway hops.
|
||||
num_mix_hops: u8,
|
||||
}
|
||||
|
||||
impl SphinxMessageReceiver {
|
||||
/// Allows setting non-default number of expected mix hops in the network.
|
||||
#[must_use]
|
||||
pub fn with_mix_hops(mut self, hops: u8) -> Self {
|
||||
self.num_mix_hops = hops;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageReceiver for SphinxMessageReceiver {
|
||||
fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn decrypt_raw_message<C>(
|
||||
&self,
|
||||
message: &mut [u8],
|
||||
key: &CipherKey<C>,
|
||||
) -> Result<(), MessageRecoveryError>
|
||||
where
|
||||
C: StreamCipher + KeyIvInit,
|
||||
{
|
||||
let zero_iv = stream_cipher::zero_iv::<C>();
|
||||
stream_cipher::decrypt_in_place::<C>(key, &zero_iv, message);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reconstructor(&mut self) -> &mut MessageReconstructor {
|
||||
&mut self.reconstructor
|
||||
}
|
||||
|
||||
fn num_mix_hops(&self) -> u8 {
|
||||
self.num_mix_hops
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SphinxMessageReceiver {
|
||||
fn default() -> Self {
|
||||
MessageReceiver {
|
||||
SphinxMessageReceiver {
|
||||
reconstructor: Default::default(),
|
||||
num_mix_hops: DEFAULT_NUM_MIX_HOPS,
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ thiserror = "1.0"
|
||||
url = "2.2"
|
||||
ts-rs = "6.1.2"
|
||||
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmrs = { git = "https://github.com/neacsu/cosmos-rust", branch = "neacsu/feegrant_support" }
|
||||
|
||||
validator-client = { path = "../../common/client-libs/validator-client", features = [
|
||||
|
||||
@@ -2,6 +2,21 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
## [v1.1.6] (2023-04-04)
|
||||
- change in-contract signatures to include nonces and to sign entire payloads for family-related operations ([#3125])
|
||||
- change in-contract signatures to include nonces and to sign entire payloads for node bonding (will require wallet changes) ([#3067])
|
||||
- removed migration code from mixnet and vesting contracts ([#3207])
|
||||
|
||||
[#3125]: https://github.com/nymtech/nym/issues/3125
|
||||
[#3067]: https://github.com/nymtech/nym/issues/3067
|
||||
[#3207]: https://github.com/nymtech/nym/pull/3207
|
||||
|
||||
## [v1.1.5] (2023-03-21)
|
||||
|
||||
- Fix contracts and nym-api audit findings ([#3026])
|
||||
|
||||
[#3026]: https://github.com/nymtech/nym/issues/3026
|
||||
|
||||
## [v1.1.4] (2023-02-21)
|
||||
|
||||
- Problem 142 (rust-side) ([#3024])
|
||||
|
||||
Generated
+2028
File diff suppressed because it is too large
Load Diff
@@ -27,3 +27,18 @@ codegen-units = 1
|
||||
panic = 'abort'
|
||||
incremental = false
|
||||
overflow-checks = true
|
||||
|
||||
[workspace.dependencies]
|
||||
cosmwasm-crypto = "=1.0.0"
|
||||
cosmwasm-derive = "=1.0.0"
|
||||
cosmwasm-schema = "=1.0.0"
|
||||
cosmwasm-std = "=1.0.0"
|
||||
cosmwasm-storage = "=1.0.0"
|
||||
cw-controllers = "=0.13.4"
|
||||
cw-multi-test = "=0.13.4"
|
||||
cw-storage-plus = "=0.13.4"
|
||||
cw-utils = "=0.13.4"
|
||||
cw2 = "=0.13.4"
|
||||
cw3 = "=0.13.4"
|
||||
cw3-fixed-multisig = "=0.13.4"
|
||||
cw4 = "=0.13.4"
|
||||
|
||||
@@ -12,10 +12,10 @@ crate-type = ["cdylib", "rlib"]
|
||||
nym-coconut-bandwidth-contract-common = { path = "../../common/cosmwasm-smart-contracts/coconut-bandwidth-contract" }
|
||||
nym-multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" }
|
||||
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmwasm-storage = "1.0.0"
|
||||
cw-storage-plus = "0.13.4"
|
||||
cw-controllers = "0.13.4"
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmwasm-storage = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cw-controllers = { workspace = true }
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
|
||||
@@ -11,18 +11,18 @@ crate-type = ["cdylib", "rlib"]
|
||||
[dependencies]
|
||||
nym-coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut-dkg" }
|
||||
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmwasm-storage = "1.0.0"
|
||||
cw-storage-plus = "0.13.4"
|
||||
cw-controllers = "0.13.4"
|
||||
cw4 = { version = "0.13.4" }
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmwasm-storage = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cw-controllers = { workspace = true }
|
||||
cw4 = { workspace = true }
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = "1.0.23"
|
||||
|
||||
[dev-dependencies]
|
||||
cw-multi-test = { version = "0.13.4" }
|
||||
cw-multi-test = { workspace = true }
|
||||
cw4-group = { path = "../multisig/cw4-group" }
|
||||
nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" }
|
||||
lazy_static = "1.4"
|
||||
|
||||
@@ -12,13 +12,13 @@ nym-coconut-dkg-common = { path = "../../common/cosmwasm-smart-contracts/coconut
|
||||
nym-multisig-contract-common = { path = "../../common/cosmwasm-smart-contracts/multisig-contract" }
|
||||
nym-group-contract-common = { path = "../../common/cosmwasm-smart-contracts/group-contract" }
|
||||
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmwasm-storage = "1.0.0"
|
||||
cw3 = "0.13.4"
|
||||
cw4 = "0.13.4"
|
||||
cw-storage-plus = "0.13.4"
|
||||
cw-controllers = "0.13.4"
|
||||
cw-utils = "0.13.4"
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmwasm-storage = { workspace = true }
|
||||
cw3 = { workspace = true }
|
||||
cw4 = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cw-controllers = { workspace = true }
|
||||
cw-utils = { workspace = true }
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
@@ -26,7 +26,7 @@ thiserror = "1.0.23"
|
||||
|
||||
nym-coconut-bandwidth = { path = "../coconut-bandwidth" }
|
||||
nym-coconut-dkg = { path = "../coconut-dkg" }
|
||||
cw-multi-test = { version = "0.13.4" }
|
||||
cw-multi-test = { workspace = true }
|
||||
cw3-flex-multisig = { path = "../multisig/cw3-flex-multisig" }
|
||||
cw4-group = { path = "../multisig/cw4-group" }
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-mixnet-contract"
|
||||
version = "1.2.0-pre.0"
|
||||
version = "1.3.0"
|
||||
description = "Nym mixnet contract"
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
@@ -22,14 +22,15 @@ name = "mixnet_contract"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.2.0" }
|
||||
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.2.0" }
|
||||
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.2.0" }
|
||||
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.4.0" }
|
||||
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.4.0" }
|
||||
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", version = "0.4.0" }
|
||||
|
||||
cosmwasm-std = "1.0.0"
|
||||
cosmwasm-storage = "1.0.0"
|
||||
cw2 = { version = "0.13.4" }
|
||||
cw-storage-plus = "0.13.4"
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmwasm-storage = { workspace = true }
|
||||
cosmwasm-derive = { workspace = true }
|
||||
cw2 = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
|
||||
bs58 = "0.4.0"
|
||||
schemars = "0.8"
|
||||
@@ -39,7 +40,7 @@ time = { version = "0.3", features = ["macros"] }
|
||||
semver = { version = "1.0.16", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
cosmwasm-schema = "1.0.0"
|
||||
cosmwasm-schema = { workspace = true }
|
||||
rand_chacha = "0.2"
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "rand"] }
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
opt: wasm
|
||||
wasm-opt -Os ../target/wasm32-unknown-unknown/release/mixnet_contract.wasm -o ../target/wasm32-unknown-unknown/release/mixnet_contract.wasm
|
||||
wasm-opt --disable-sign-ext -Os ../target/wasm32-unknown-unknown/release/mixnet_contract.wasm -o ../target/wasm32-unknown-unknown/release/mixnet_contract.wasm
|
||||
|
||||
wasm:
|
||||
RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown
|
||||
|
||||
@@ -8,7 +8,6 @@ use crate::mixnodes::storage as mixnode_storage;
|
||||
use crate::rewards::storage as rewards_storage;
|
||||
use cosmwasm_std::{
|
||||
entry_point, to_binary, Addr, Coin, Deps, DepsMut, Env, MessageInfo, QueryResponse, Response,
|
||||
StdError,
|
||||
};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::{
|
||||
@@ -105,68 +104,56 @@ pub fn execute(
|
||||
crate::mixnodes::transactions::assign_mixnode_layer(deps, info, mix_id, layer)
|
||||
}
|
||||
// families
|
||||
ExecuteMsg::CreateFamily {
|
||||
owner_signature,
|
||||
label,
|
||||
} => crate::families::transactions::try_create_family(deps, info, owner_signature, &label),
|
||||
ExecuteMsg::JoinFamily {
|
||||
signature,
|
||||
family_head,
|
||||
} => {
|
||||
crate::families::transactions::try_join_family(deps, info, None, signature, family_head)
|
||||
ExecuteMsg::CreateFamily { label } => {
|
||||
crate::families::transactions::try_create_family(deps, info, label)
|
||||
}
|
||||
ExecuteMsg::LeaveFamily {
|
||||
signature,
|
||||
ExecuteMsg::JoinFamily {
|
||||
join_permit,
|
||||
family_head,
|
||||
} => crate::families::transactions::try_leave_family(deps, info, signature, family_head),
|
||||
ExecuteMsg::KickFamilyMember { signature, member } => {
|
||||
crate::families::transactions::try_head_kick_member(deps, info, signature, &member)
|
||||
} => crate::families::transactions::try_join_family(deps, info, join_permit, family_head),
|
||||
ExecuteMsg::LeaveFamily { family_head } => {
|
||||
crate::families::transactions::try_leave_family(deps, info, family_head)
|
||||
}
|
||||
ExecuteMsg::KickFamilyMember { member } => {
|
||||
crate::families::transactions::try_head_kick_member(deps, info, member)
|
||||
}
|
||||
ExecuteMsg::CreateFamilyOnBehalf {
|
||||
owner_address,
|
||||
owner_signature,
|
||||
label,
|
||||
} => crate::families::transactions::try_create_family_on_behalf(
|
||||
deps,
|
||||
info,
|
||||
owner_address,
|
||||
owner_signature,
|
||||
&label,
|
||||
label,
|
||||
),
|
||||
ExecuteMsg::JoinFamilyOnBehalf {
|
||||
member_address,
|
||||
node_identity_signature,
|
||||
family_signature,
|
||||
join_permit,
|
||||
family_head,
|
||||
} => crate::families::transactions::try_join_family_on_behalf(
|
||||
deps,
|
||||
info,
|
||||
member_address,
|
||||
Some(node_identity_signature),
|
||||
family_signature,
|
||||
join_permit,
|
||||
family_head,
|
||||
),
|
||||
ExecuteMsg::LeaveFamilyOnBehalf {
|
||||
member_address,
|
||||
node_identity_signature,
|
||||
family_head,
|
||||
} => crate::families::transactions::try_leave_family_on_behalf(
|
||||
deps,
|
||||
info,
|
||||
member_address,
|
||||
node_identity_signature,
|
||||
family_head,
|
||||
),
|
||||
ExecuteMsg::KickFamilyMemberOnBehalf {
|
||||
head_address,
|
||||
signature,
|
||||
member,
|
||||
} => crate::families::transactions::try_head_kick_member_on_behalf(
|
||||
deps,
|
||||
info,
|
||||
head_address,
|
||||
signature,
|
||||
&member,
|
||||
member,
|
||||
),
|
||||
// state/sys-params-related
|
||||
ExecuteMsg::UpdateRewardingValidatorAddress { address } => {
|
||||
@@ -393,13 +380,13 @@ pub fn query(
|
||||
&crate::families::queries::get_family_by_head(&head, deps.storage)?,
|
||||
),
|
||||
QueryMsg::GetFamilyByLabel { label } => to_binary(
|
||||
&crate::families::queries::get_family_by_label(&label, deps.storage)?,
|
||||
&crate::families::queries::get_family_by_label(label, deps.storage)?,
|
||||
),
|
||||
QueryMsg::GetFamilyMembersByHead { head } => to_binary(
|
||||
&crate::families::queries::get_family_members_by_head(&head, deps.storage)?,
|
||||
),
|
||||
QueryMsg::GetFamilyMembersByLabel { label } => to_binary(
|
||||
&crate::families::queries::get_family_members_by_label(&label, deps.storage)?,
|
||||
&crate::families::queries::get_family_members_by_label(label, deps.storage)?,
|
||||
),
|
||||
QueryMsg::GetContractVersion {} => {
|
||||
to_binary(&crate::mixnet_contract_settings::queries::query_contract_version())
|
||||
@@ -603,17 +590,6 @@ pub fn migrate(
|
||||
_env: Env,
|
||||
msg: MigrateMsg,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
// this is the first migration that uses cw2 standard and thus the value in the storage doesn't yet exist
|
||||
// set it instead.
|
||||
if matches!(
|
||||
cw2::get_contract_version(deps.storage),
|
||||
Err(StdError::NotFound { .. })
|
||||
) {
|
||||
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
|
||||
crate::queued_migrations::create_epoch_status(deps.storage)?;
|
||||
return Ok(Response::new());
|
||||
}
|
||||
|
||||
// note: don't remove this particular bit of code as we have to ALWAYS check whether we have to update the stored version
|
||||
let version: Version = CONTRACT_VERSION.parse().map_err(|error: semver::Error| {
|
||||
MixnetContractError::SemVerFailure {
|
||||
@@ -635,8 +611,7 @@ pub fn migrate(
|
||||
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
|
||||
|
||||
// If state structure changed in any contract version in the way migration is needed, it
|
||||
// should occur here
|
||||
crate::queued_migrations::create_epoch_status(deps.storage)?;
|
||||
// should occur here, for example anything from `crate::queued_migrations::`
|
||||
}
|
||||
|
||||
// due to circular dependency on contract addresses (i.e. mixnet contract requiring vesting contract address
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod queries;
|
||||
pub mod signature_helpers;
|
||||
pub mod storage;
|
||||
pub mod transactions;
|
||||
|
||||
@@ -1,31 +1,27 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::constants::{FAMILIES_DEFAULT_RETRIEVAL_LIMIT, FAMILIES_MAX_RETRIEVAL_LIMIT};
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::storage::{families, get_family, get_members, MEMBERS};
|
||||
use crate::constants::{FAMILIES_DEFAULT_RETRIEVAL_LIMIT, FAMILIES_MAX_RETRIEVAL_LIMIT};
|
||||
use cosmwasm_std::{Order, Storage};
|
||||
use cw_storage_plus::Bound;
|
||||
use mixnet_contract_common::families::{Family, FamilyHead};
|
||||
use mixnet_contract_common::{error::MixnetContractError, IdentityKeyRef};
|
||||
use mixnet_contract_common::{IdentityKey, PagedFamiliesResponse, PagedMembersResponse};
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub fn get_family_by_label(
|
||||
label: &str,
|
||||
label: String,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Option<Family>, MixnetContractError> {
|
||||
Ok(families()
|
||||
.idx
|
||||
.label
|
||||
.item(storage, label.to_string())?
|
||||
.map(|o| o.1))
|
||||
Ok(families().idx.label.item(storage, label)?.map(|o| o.1))
|
||||
}
|
||||
|
||||
pub fn get_family_by_head(
|
||||
head: IdentityKeyRef<'_>,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Family, MixnetContractError> {
|
||||
let family_head = FamilyHead::new(head);
|
||||
get_family(&family_head, storage)
|
||||
) -> Result<Option<Family>, MixnetContractError> {
|
||||
Ok(families().may_load(storage, head.to_string())?)
|
||||
}
|
||||
|
||||
pub fn get_family_members_by_head(
|
||||
@@ -38,15 +34,10 @@ pub fn get_family_members_by_head(
|
||||
}
|
||||
|
||||
pub fn get_family_members_by_label(
|
||||
label: &str,
|
||||
label: String,
|
||||
storage: &dyn Storage,
|
||||
) -> Result<Option<HashSet<String>>, MixnetContractError> {
|
||||
if let Some(family) = families()
|
||||
.idx
|
||||
.label
|
||||
.item(storage, label.to_string())?
|
||||
.map(|o| o.1)
|
||||
{
|
||||
if let Some(family) = families().idx.label.item(storage, label)?.map(|o| o.1) {
|
||||
Ok(Some(get_members(&family, storage)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use crate::signing::storage as signing_storage;
|
||||
use crate::support::helpers::decode_ed25519_identity_key;
|
||||
use cosmwasm_std::{Addr, Deps};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::families::FamilyHead;
|
||||
use mixnet_contract_common::{construct_family_join_permit, IdentityKeyRef};
|
||||
use nym_contracts_common::signing::{MessageSignature, Verifier};
|
||||
|
||||
pub(crate) fn verify_family_join_permit(
|
||||
deps: Deps<'_>,
|
||||
granter: FamilyHead,
|
||||
proxy: Option<Addr>,
|
||||
member: IdentityKeyRef,
|
||||
signature: MessageSignature,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
// recover the public key
|
||||
let public_key = decode_ed25519_identity_key(granter.identity())?;
|
||||
|
||||
// that's kinda a backwards way of getting the granter's nonce, but it works, so ¯\_(ツ)_/¯
|
||||
let Some(head_mixnode) = mixnodes_storage::mixnode_bonds()
|
||||
.idx
|
||||
.identity_key
|
||||
.item(deps.storage, granter.identity().to_owned())?.map(|record| record.1) else {
|
||||
return Err(MixnetContractError::FamilyDoesNotExist { head: granter.identity().to_string() })
|
||||
};
|
||||
let nonce = signing_storage::get_signing_nonce(deps.storage, head_mixnode.owner)?;
|
||||
let msg = construct_family_join_permit(nonce, granter, proxy, member.to_owned());
|
||||
|
||||
if deps.api.verify_message(msg, signature, &public_key)? {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(MixnetContractError::InvalidEd25519Signature)
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
use std::collections::HashSet;
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use cosmwasm_std::{Order, StdError, Storage};
|
||||
use cosmwasm_std::{Order, Storage};
|
||||
use cw_storage_plus::{Index, IndexList, IndexedMap, Map, UniqueIndex};
|
||||
use mixnet_contract_common::families::{Family, FamilyHead};
|
||||
use mixnet_contract_common::{error::MixnetContractError, IdentityKey, IdentityKeyRef};
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::constants::{FAMILIES_INDEX_NAMESPACE, FAMILIES_MAP_NAMESPACE, MEMBERS_MAP_NAMESPACE};
|
||||
|
||||
@@ -51,22 +53,8 @@ pub fn get_family(head: &FamilyHead, store: &dyn Storage) -> Result<Family, Mixn
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_family(f: &Family, store: &mut dyn Storage) -> Result<(), MixnetContractError> {
|
||||
match families().save(store, f.head_identity().to_string(), f) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => match &e {
|
||||
StdError::GenericErr { msg } => {
|
||||
if msg.starts_with("Violates unique constraint") {
|
||||
Err(MixnetContractError::FamilyWithLabelExists(
|
||||
f.label().to_string(),
|
||||
))
|
||||
} else {
|
||||
Err(e.into())
|
||||
}
|
||||
}
|
||||
_ => Err(e.into()),
|
||||
},
|
||||
}
|
||||
pub fn save_family(f: &Family, store: &mut dyn Storage) -> Result<(), MixnetContractError> {
|
||||
Ok(families().save(store, f.head_identity().to_string(), f)?)
|
||||
}
|
||||
|
||||
pub fn add_family_member(
|
||||
|
||||
@@ -1,136 +1,99 @@
|
||||
use crate::support::helpers::{
|
||||
ensure_bonded, ensure_sent_by_vesting_contract, validate_family_signature,
|
||||
validate_node_identity_signature,
|
||||
};
|
||||
|
||||
use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response};
|
||||
use mixnet_contract_common::families::{Family, FamilyHead};
|
||||
use mixnet_contract_common::{error::MixnetContractError, IdentityKey, IdentityKeyRef};
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use super::storage::{
|
||||
add_family_member, create_family, get_family, is_any_member, is_family_member,
|
||||
remove_family_member,
|
||||
add_family_member, get_family, is_any_member, is_family_member, remove_family_member,
|
||||
save_family,
|
||||
};
|
||||
use crate::families::queries::get_family_by_label;
|
||||
use crate::families::signature_helpers::verify_family_join_permit;
|
||||
use crate::support::helpers::{ensure_bonded, ensure_sent_by_vesting_contract};
|
||||
use cosmwasm_std::{Addr, DepsMut, MessageInfo, Response};
|
||||
use mixnet_contract_common::families::{Family, FamilyHead};
|
||||
use mixnet_contract_common::{error::MixnetContractError, IdentityKey};
|
||||
use nym_contracts_common::signing::MessageSignature;
|
||||
|
||||
/// Creates a new MixNode family with senders node as head
|
||||
pub fn try_create_family(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
owner_signature: String,
|
||||
label: &str,
|
||||
label: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_create_family(deps, &info.sender, owner_signature, label, None)
|
||||
_try_create_family(deps, &info.sender, label, None)
|
||||
}
|
||||
|
||||
pub fn try_create_family_on_behalf(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
owner_address: String,
|
||||
owner_signature: String,
|
||||
label: &str,
|
||||
label: String,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let owner_address = deps.api.addr_validate(&owner_address)?;
|
||||
_try_create_family(
|
||||
deps,
|
||||
&owner_address,
|
||||
owner_signature,
|
||||
label,
|
||||
Some(info.sender),
|
||||
)
|
||||
_try_create_family(deps, &owner_address, label, Some(info.sender))
|
||||
}
|
||||
|
||||
fn _try_create_family(
|
||||
deps: DepsMut,
|
||||
owner: &Addr,
|
||||
owner_signature: String,
|
||||
label: &str,
|
||||
label: String,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let existing_bond = crate::mixnodes::storage::mixnode_bonds()
|
||||
.idx
|
||||
.owner
|
||||
.item(deps.storage, owner.clone())?
|
||||
.ok_or(MixnetContractError::NoAssociatedMixNodeBond {
|
||||
owner: owner.clone(),
|
||||
})?
|
||||
.1;
|
||||
let existing_bond =
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
|
||||
|
||||
ensure_bonded(&existing_bond)?;
|
||||
|
||||
validate_node_identity_signature(
|
||||
deps.as_ref(),
|
||||
owner,
|
||||
&owner_signature,
|
||||
existing_bond.identity(),
|
||||
)?;
|
||||
|
||||
let family_head = FamilyHead::new(existing_bond.identity());
|
||||
|
||||
if let Ok(_family) = get_family(&family_head, deps.storage) {
|
||||
// can't overwrite existing family
|
||||
if get_family(&family_head, deps.storage).is_ok() {
|
||||
return Err(MixnetContractError::FamilyCanHaveOnlyOne);
|
||||
}
|
||||
|
||||
// the label must be unique
|
||||
if get_family_by_label(label.clone(), deps.storage)?.is_some() {
|
||||
return Err(MixnetContractError::FamilyWithLabelExists(label));
|
||||
}
|
||||
|
||||
let family = Family::new(family_head, proxy, label);
|
||||
create_family(&family, deps.storage)?;
|
||||
save_family(&family, deps.storage)?;
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
pub fn try_join_family(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
// Required for proxy joining
|
||||
node_identity_signature: Option<String>,
|
||||
family_signature: String,
|
||||
family_head: IdentityKey,
|
||||
join_permit: MessageSignature,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let family_head = FamilyHead::new(&family_head);
|
||||
_try_join_family(
|
||||
deps,
|
||||
&info.sender,
|
||||
node_identity_signature,
|
||||
family_signature,
|
||||
family_head,
|
||||
)
|
||||
_try_join_family(deps, &info.sender, join_permit, family_head, None)
|
||||
}
|
||||
|
||||
pub fn try_join_family_on_behalf(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
member_address: String,
|
||||
node_identity_signature: Option<String>,
|
||||
family_signature: String,
|
||||
family_head: IdentityKey,
|
||||
join_permit: MessageSignature,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let member_address = deps.api.addr_validate(&member_address)?;
|
||||
let family_head = FamilyHead::new(&family_head);
|
||||
_try_join_family(
|
||||
deps,
|
||||
&member_address,
|
||||
node_identity_signature,
|
||||
family_signature,
|
||||
family_head,
|
||||
)
|
||||
let proxy = Some(info.sender);
|
||||
_try_join_family(deps, &member_address, join_permit, family_head, proxy)
|
||||
}
|
||||
|
||||
fn _try_join_family(
|
||||
deps: DepsMut,
|
||||
owner: &Addr,
|
||||
node_identity_signature: Option<String>,
|
||||
family_signature: String,
|
||||
join_permit: MessageSignature,
|
||||
family_head: FamilyHead,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let existing_bond = crate::mixnodes::storage::mixnode_bonds()
|
||||
.idx
|
||||
.owner
|
||||
.item(deps.storage, owner.clone())?
|
||||
.ok_or(MixnetContractError::NoAssociatedMixNodeBond {
|
||||
owner: owner.clone(),
|
||||
})?
|
||||
.1;
|
||||
let existing_bond =
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
|
||||
|
||||
ensure_bonded(&existing_bond)?;
|
||||
|
||||
@@ -147,20 +110,12 @@ fn _try_join_family(
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(node_identity_signature) = node_identity_signature {
|
||||
validate_node_identity_signature(
|
||||
deps.as_ref(),
|
||||
owner,
|
||||
&node_identity_signature,
|
||||
existing_bond.identity(),
|
||||
)?;
|
||||
}
|
||||
|
||||
validate_family_signature(
|
||||
verify_family_join_permit(
|
||||
deps.as_ref(),
|
||||
family_head.clone(),
|
||||
proxy,
|
||||
existing_bond.identity(),
|
||||
&family_signature,
|
||||
family_head.identity(),
|
||||
join_permit,
|
||||
)?;
|
||||
|
||||
let family = get_family(&family_head, deps.storage)?;
|
||||
@@ -173,41 +128,30 @@ fn _try_join_family(
|
||||
pub fn try_leave_family(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
signature: String,
|
||||
family_head: IdentityKey,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let family_head = FamilyHead::new(&family_head);
|
||||
_try_leave_family(deps, &info.sender, signature, family_head)
|
||||
_try_leave_family(deps, &info.sender, family_head)
|
||||
}
|
||||
|
||||
pub fn try_leave_family_on_behalf(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
member_address: String,
|
||||
node_family_signature: String,
|
||||
family_head: IdentityKey,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let family_head = FamilyHead::new(&family_head);
|
||||
let member_address = deps.api.addr_validate(&member_address)?;
|
||||
_try_leave_family(deps, &member_address, node_family_signature, family_head)
|
||||
_try_leave_family(deps, &member_address, family_head)
|
||||
}
|
||||
|
||||
fn _try_leave_family(
|
||||
deps: DepsMut,
|
||||
owner: &Addr,
|
||||
node_family_signature: String,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let existing_bond = crate::mixnodes::storage::mixnode_bonds()
|
||||
.idx
|
||||
.owner
|
||||
.item(deps.storage, owner.clone())?
|
||||
.ok_or(MixnetContractError::NoAssociatedMixNodeBond {
|
||||
owner: owner.clone(),
|
||||
})?
|
||||
.1;
|
||||
let existing_bond =
|
||||
crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
|
||||
|
||||
ensure_bonded(&existing_bond)?;
|
||||
|
||||
@@ -226,13 +170,6 @@ fn _try_leave_family(
|
||||
});
|
||||
}
|
||||
|
||||
validate_node_identity_signature(
|
||||
deps.as_ref(),
|
||||
owner,
|
||||
&node_family_signature,
|
||||
existing_bond.identity(),
|
||||
)?;
|
||||
|
||||
remove_family_member(deps.storage, existing_bond.identity());
|
||||
|
||||
Ok(Response::default())
|
||||
@@ -241,62 +178,56 @@ fn _try_leave_family(
|
||||
pub fn try_head_kick_member(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
owner_signature: String,
|
||||
member: IdentityKeyRef,
|
||||
member: IdentityKey,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
_try_head_kick_member(deps, &info.sender, owner_signature, member)
|
||||
_try_head_kick_member(deps, &info.sender, member)
|
||||
}
|
||||
|
||||
pub fn try_head_kick_member_on_behalf(
|
||||
deps: DepsMut,
|
||||
info: MessageInfo,
|
||||
head_address: String,
|
||||
owner_signature: String,
|
||||
member: IdentityKeyRef,
|
||||
member: IdentityKey,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_sent_by_vesting_contract(&info, deps.storage)?;
|
||||
|
||||
let head_address = deps.api.addr_validate(&head_address)?;
|
||||
_try_head_kick_member(deps, &head_address, owner_signature, member)
|
||||
_try_head_kick_member(deps, &head_address, member)
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
fn _try_head_kick_member(
|
||||
deps: DepsMut,
|
||||
owner: &Addr,
|
||||
owner_signature: String,
|
||||
member: IdentityKeyRef<'_>,
|
||||
member: IdentityKey,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
Err(MixnetContractError::NotImplemented)
|
||||
// let existing_bond = crate::mixnodes::storage::mixnode_bonds()
|
||||
// .idx
|
||||
// .owner
|
||||
// .item(deps.storage, owner.clone())?
|
||||
// .ok_or(MixnetContractError::NoAssociatedMixNodeBond {
|
||||
// owner: owner.clone(),
|
||||
// })?
|
||||
// .1;
|
||||
let head_bond = crate::mixnodes::helpers::must_get_mixnode_bond_by_owner(deps.storage, owner)?;
|
||||
|
||||
// ensure_bonded(&existing_bond)?;
|
||||
// make sure we're still in the mixnet
|
||||
ensure_bonded(&head_bond)?;
|
||||
|
||||
// validate_node_identity_signature(
|
||||
// deps.as_ref(),
|
||||
// owner,
|
||||
// &owner_signature,
|
||||
// existing_bond.identity(),
|
||||
// )?;
|
||||
// make sure we're not trying to kick ourselves...
|
||||
if member == head_bond.identity() {
|
||||
return Err(MixnetContractError::CantLeaveOwnFamily {
|
||||
head: head_bond.identity().to_string(),
|
||||
member,
|
||||
});
|
||||
}
|
||||
|
||||
// let family_head = FamilyHead::new(existing_bond.identity());
|
||||
// let family = get_family(&family_head, deps.storage)?;
|
||||
// if !is_family_member(deps.storage, &family, member)? {
|
||||
// return Err(MixnetContractError::NotAMember {
|
||||
// head: family_head.identity().to_string(),
|
||||
// member: existing_bond.identity().to_string(),
|
||||
// });
|
||||
// }
|
||||
// get the family details
|
||||
let family_head = FamilyHead::new(head_bond.identity());
|
||||
let family = get_family(&family_head, deps.storage)?;
|
||||
|
||||
// remove_family_member(deps.storage, member);
|
||||
// Ok(Response::default())
|
||||
// make sure the member we're trying to kick is an actual member
|
||||
if !is_family_member(deps.storage, &family, &member)? {
|
||||
return Err(MixnetContractError::NotAMember {
|
||||
head: family_head.identity().to_string(),
|
||||
member,
|
||||
});
|
||||
}
|
||||
|
||||
// finally get rid of the member
|
||||
remove_family_member(deps.storage, &member);
|
||||
Ok(Response::default())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -322,9 +253,9 @@ mod test {
|
||||
let cost_params = fixtures::mix_node_cost_params_fixture();
|
||||
|
||||
let (head_mixnode, head_bond_sig, head_keypair) = test.mixnode_with_signature(head, None);
|
||||
let (malicious_mixnode, malicious_bond_sig, malicious_keypair) =
|
||||
let (malicious_mixnode, malicious_bond_sig, _malicious_keypair) =
|
||||
test.mixnode_with_signature(malicious_head, None);
|
||||
let (member_mixnode, member_bond_sig, member_keypair) =
|
||||
let (member_mixnode, member_bond_sig, _member_keypair) =
|
||||
test.mixnode_with_signature(member, None);
|
||||
|
||||
crate::mixnodes::transactions::try_add_mixnode(
|
||||
@@ -357,26 +288,14 @@ mod test {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let old_style_head_sig = head_keypair.private_key().sign_text(head);
|
||||
let old_style_malicious_head_sig =
|
||||
malicious_keypair.private_key().sign_text(malicious_head);
|
||||
let old_style_member_sig = member_keypair.private_key().sign_text(member);
|
||||
|
||||
try_create_family(
|
||||
test.deps_mut(),
|
||||
mock_info(head, &[]),
|
||||
old_style_head_sig,
|
||||
"test",
|
||||
)
|
||||
.unwrap();
|
||||
try_create_family(test.deps_mut(), mock_info(head, &[]), "test".to_string()).unwrap();
|
||||
let family_head = FamilyHead::new(&head_mixnode.identity_key);
|
||||
assert!(get_family(&family_head, test.deps().storage).is_ok());
|
||||
|
||||
let nope = try_create_family(
|
||||
test.deps_mut(),
|
||||
mock_info(malicious_head, &[]),
|
||||
old_style_malicious_head_sig,
|
||||
"test",
|
||||
"test".to_string(),
|
||||
);
|
||||
|
||||
match nope {
|
||||
@@ -387,24 +306,23 @@ mod test {
|
||||
},
|
||||
}
|
||||
|
||||
let family = get_family_by_label("test", test.deps().storage).unwrap();
|
||||
let family = get_family_by_label("test".to_string(), test.deps().storage).unwrap();
|
||||
assert!(family.is_some());
|
||||
assert_eq!(family.unwrap().head_identity(), family_head.identity());
|
||||
|
||||
let family = get_family_by_head(family_head.identity(), test.deps().storage).unwrap();
|
||||
let family = get_family_by_head(family_head.identity(), test.deps().storage)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(family.head_identity(), family_head.identity());
|
||||
|
||||
let join_signature = head_keypair
|
||||
.private_key()
|
||||
.sign(member_mixnode.identity_key.as_bytes())
|
||||
.to_base58_string();
|
||||
let join_permit =
|
||||
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key, false);
|
||||
|
||||
try_join_family(
|
||||
test.deps_mut(),
|
||||
mock_info(member, &[]),
|
||||
Some(old_style_member_sig.clone()),
|
||||
join_signature.clone(),
|
||||
head_mixnode.identity_key.clone(),
|
||||
join_permit,
|
||||
family_head.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -414,11 +332,34 @@ mod test {
|
||||
is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap()
|
||||
);
|
||||
|
||||
try_leave_family(
|
||||
try_leave_family(test.deps_mut(), mock_info(member, &[]), family_head.clone()).unwrap();
|
||||
|
||||
let family = get_family(&family_head, test.deps().storage).unwrap();
|
||||
assert!(
|
||||
!is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap()
|
||||
);
|
||||
|
||||
let new_join_permit =
|
||||
test.generate_family_join_permit(&head_keypair, &member_mixnode.identity_key, false);
|
||||
|
||||
try_join_family(
|
||||
test.deps_mut(),
|
||||
mock_info(member, &[]),
|
||||
old_style_member_sig.clone(),
|
||||
head_mixnode.identity_key.clone(),
|
||||
new_join_permit,
|
||||
family_head.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let family = get_family(&family_head, test.deps().storage).unwrap();
|
||||
|
||||
assert!(
|
||||
is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap()
|
||||
);
|
||||
|
||||
try_head_kick_member(
|
||||
test.deps_mut(),
|
||||
mock_info(head, &[]),
|
||||
member_mixnode.identity_key.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
@@ -426,32 +367,6 @@ mod test {
|
||||
assert!(
|
||||
!is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap()
|
||||
);
|
||||
|
||||
try_join_family(
|
||||
test.deps_mut(),
|
||||
mock_info(member, &[]),
|
||||
Some(old_style_member_sig),
|
||||
join_signature,
|
||||
head_mixnode.identity_key,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let family = get_family(&family_head, test.deps().storage).unwrap();
|
||||
|
||||
assert!(
|
||||
is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap()
|
||||
);
|
||||
|
||||
// try_head_kick_member(
|
||||
// deps.as_mut(),
|
||||
// mock_info(&head, &[]),
|
||||
// head_sig.clone(),
|
||||
// &member_mixnode.identity_key.clone(),
|
||||
// )
|
||||
// .unwrap();
|
||||
|
||||
// let family = get_family(&family_head, test.deps().storage).unwrap();
|
||||
// assert!(!is_family_member(test.deps().storage, &family, &member_mixnode.identity_key).unwrap());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -468,15 +383,13 @@ mod test {
|
||||
|
||||
let head = "alice";
|
||||
|
||||
let (_, keypair) = test.add_dummy_mixnode_with_proxy_and_keypair(head, None);
|
||||
let sig = keypair.private_key().sign_text(head);
|
||||
test.add_dummy_mixnode(head, None);
|
||||
|
||||
let res = try_create_family_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
head.to_string(),
|
||||
sig,
|
||||
"label",
|
||||
"label".to_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
@@ -507,23 +420,22 @@ mod test {
|
||||
let new_member = "vin-diesel";
|
||||
|
||||
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
|
||||
let (_, member_keys) = test.add_dummy_mixnode_with_proxy_and_keypair(new_member, None);
|
||||
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
|
||||
|
||||
// TODO: those signatures are WRONG and have to be c hanged
|
||||
let join_signature = head_keys
|
||||
.private_key()
|
||||
.sign_text(&member_keys.public_key().to_base58_string());
|
||||
|
||||
let member_sig = member_keys.private_key().sign_text(new_member);
|
||||
let join_permit = test.generate_family_join_permit(
|
||||
&head_keys,
|
||||
&member_keys.public_key().to_base58_string(),
|
||||
false,
|
||||
);
|
||||
|
||||
let head_identity = head_keys.public_key().to_base58_string();
|
||||
let family_head = FamilyHead::new(&head_identity);
|
||||
let res = try_join_family_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
new_member.to_string(),
|
||||
Some(member_sig),
|
||||
join_signature,
|
||||
head_identity,
|
||||
join_permit,
|
||||
family_head,
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
@@ -554,33 +466,30 @@ mod test {
|
||||
let new_member = "vin-diesel";
|
||||
|
||||
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
|
||||
let (_, member_keys) = test.add_dummy_mixnode_with_proxy_and_keypair(new_member, None);
|
||||
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
|
||||
|
||||
// TODO: those signatures are WRONG and have to be changed
|
||||
let join_signature = head_keys
|
||||
.private_key()
|
||||
.sign_text(&member_keys.public_key().to_base58_string());
|
||||
|
||||
let member_sig = member_keys.private_key().sign_text(new_member);
|
||||
let join_permit = test.generate_family_join_permit(
|
||||
&head_keys,
|
||||
&member_keys.public_key().to_base58_string(),
|
||||
true,
|
||||
);
|
||||
|
||||
let head_identity = head_keys.public_key().to_base58_string();
|
||||
let family_head = FamilyHead::new(&head_identity);
|
||||
try_join_family_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(vesting_contract.as_ref(), &[]),
|
||||
new_member.to_string(),
|
||||
Some(member_sig.clone()),
|
||||
join_signature,
|
||||
head_identity,
|
||||
join_permit,
|
||||
family_head.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let leave_signature = member_sig;
|
||||
let res = try_leave_family_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
new_member.to_string(),
|
||||
leave_signature,
|
||||
head_keys.public_key().to_base58_string(),
|
||||
family_head.clone(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
@@ -611,33 +520,31 @@ mod test {
|
||||
let new_member = "vin-diesel";
|
||||
|
||||
let (_, head_keys) = test.create_dummy_mixnode_with_new_family(head, label);
|
||||
let (_, member_keys) = test.add_dummy_mixnode_with_proxy_and_keypair(new_member, None);
|
||||
let (_, member_keys) = test.add_dummy_mixnode_with_keypair(new_member, None);
|
||||
|
||||
// TODO: those signatures are WRONG and have to be c hanged
|
||||
let join_signature = head_keys
|
||||
.private_key()
|
||||
.sign_text(&member_keys.public_key().to_base58_string());
|
||||
|
||||
let member_sig = member_keys.private_key().sign_text(new_member);
|
||||
let join_permit = test.generate_family_join_permit(
|
||||
&head_keys,
|
||||
&member_keys.public_key().to_base58_string(),
|
||||
true,
|
||||
);
|
||||
|
||||
let head_identity = head_keys.public_key().to_base58_string();
|
||||
let family_head = FamilyHead::new(&head_identity);
|
||||
|
||||
try_join_family_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(vesting_contract.as_ref(), &[]),
|
||||
new_member.to_string(),
|
||||
Some(member_sig),
|
||||
join_signature,
|
||||
head_identity,
|
||||
join_permit,
|
||||
family_head,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// TODO: a completely wrong signature is being used
|
||||
let res = try_head_kick_member_on_behalf(
|
||||
test.deps_mut(),
|
||||
mock_info(illegal_proxy.as_ref(), &[]),
|
||||
head.to_string(),
|
||||
head_keys.private_key().sign_text(head),
|
||||
&member_keys.public_key().to_base58_string(),
|
||||
member_keys.public_key().to_base58_string(),
|
||||
)
|
||||
.unwrap_err();
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::signing::storage as signing_storage;
|
||||
use crate::support::helpers::decode_ed25519_identity_key;
|
||||
use cosmwasm_std::{Addr, Coin, Deps};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::{construct_gateway_bonding_sign_payload, Gateway};
|
||||
@@ -17,10 +18,7 @@ pub(crate) fn verify_gateway_bonding_signature(
|
||||
signature: MessageSignature,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
// recover the public key
|
||||
let mut public_key = [0u8; 32];
|
||||
bs58::decode(&gateway.identity_key)
|
||||
.into(&mut public_key)
|
||||
.map_err(|err| MixnetContractError::MalformedEd25519IdentityKey(err.to_string()))?;
|
||||
let public_key = decode_ed25519_identity_key(&gateway.identity_key)?;
|
||||
|
||||
// reconstruct the payload
|
||||
let nonce = signing_storage::get_signing_nonce(deps.storage, sender.clone())?;
|
||||
|
||||
@@ -327,6 +327,14 @@ pub(crate) fn try_update_interval_config(
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
ensure_is_owner(info.sender, deps.storage)?;
|
||||
|
||||
if epochs_in_interval == 0 {
|
||||
return Err(MixnetContractError::EpochsInIntervalZero);
|
||||
}
|
||||
|
||||
if epoch_duration_secs == 0 {
|
||||
return Err(MixnetContractError::EpochDurationZero);
|
||||
}
|
||||
|
||||
let interval = storage::current_interval(deps.storage)?;
|
||||
if force_immediately || interval.is_current_interval_over(&env) {
|
||||
change_interval_config(
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::signing::storage as signing_storage;
|
||||
use crate::support::helpers::decode_ed25519_identity_key;
|
||||
use cosmwasm_std::{Addr, Coin, Deps};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::{construct_mixnode_bonding_sign_payload, MixNode, MixNodeCostParams};
|
||||
@@ -18,10 +19,7 @@ pub(crate) fn verify_mixnode_bonding_signature(
|
||||
signature: MessageSignature,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
// recover the public key
|
||||
let mut public_key = [0u8; 32];
|
||||
bs58::decode(&mixnode.identity_key)
|
||||
.into(&mut public_key)
|
||||
.map_err(|err| MixnetContractError::MalformedEd25519IdentityKey(err.to_string()))?;
|
||||
let public_key = decode_ed25519_identity_key(&mixnode.identity_key)?;
|
||||
|
||||
// reconstruct the payload
|
||||
let nonce = signing_storage::get_signing_nonce(deps.storage, sender.clone())?;
|
||||
|
||||
@@ -245,12 +245,7 @@ pub(crate) fn _try_remove_mixnode(
|
||||
owner: Addr,
|
||||
proxy: Option<Addr>,
|
||||
) -> Result<Response, MixnetContractError> {
|
||||
let existing_bond = storage::mixnode_bonds()
|
||||
.idx
|
||||
.owner
|
||||
.item(deps.storage, owner.clone())?
|
||||
.ok_or(MixnetContractError::NoAssociatedMixNodeBond { owner })?
|
||||
.1;
|
||||
let existing_bond = must_get_mixnode_bond_by_owner(deps.storage, &owner)?;
|
||||
|
||||
// unbonding is only allowed if the epoch is currently not in the process of being advanced
|
||||
ensure_epoch_in_progress_state(deps.storage)?;
|
||||
|
||||
@@ -1,18 +1,2 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::interval::storage as interval_storage;
|
||||
use cosmwasm_std::Storage;
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::EpochStatus;
|
||||
|
||||
pub(crate) fn create_epoch_status(storage: &mut dyn Storage) -> Result<(), MixnetContractError> {
|
||||
let current_rewarding_validator =
|
||||
crate::mixnet_contract_settings::storage::rewarding_validator_address(storage)?;
|
||||
interval_storage::save_current_epoch_status(
|
||||
storage,
|
||||
&EpochStatus::new(current_rewarding_validator),
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
use crate::gateways::storage as gateways_storage;
|
||||
use crate::mixnet_contract_settings::storage as mixnet_params_storage;
|
||||
use crate::mixnodes::storage as mixnodes_storage;
|
||||
use cosmwasm_std::{
|
||||
wasm_execute, Addr, BankMsg, Coin, CosmosMsg, Deps, MessageInfo, Response, Storage,
|
||||
};
|
||||
use cosmwasm_std::{wasm_execute, Addr, BankMsg, Coin, CosmosMsg, MessageInfo, Response, Storage};
|
||||
use mixnet_contract_common::error::MixnetContractError;
|
||||
use mixnet_contract_common::{EpochState, EpochStatus, IdentityKeyRef, MixId, MixNodeBond};
|
||||
use vesting_contract_common::messages::ExecuteMsg as VestingContractExecuteMsg;
|
||||
@@ -371,184 +369,19 @@ pub(crate) fn ensure_no_existing_bond(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_node_identity_signature(
|
||||
deps: Deps<'_>,
|
||||
owner: &Addr,
|
||||
signature: &str,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
validate_ed25519_signature(deps, owner.as_bytes(), signature, identity)
|
||||
}
|
||||
|
||||
pub fn validate_family_signature(
|
||||
deps: Deps<'_>,
|
||||
family_member: IdentityKeyRef<'_>,
|
||||
signature: &str,
|
||||
family_head: IdentityKeyRef<'_>,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
validate_ed25519_signature(deps, family_member.as_bytes(), signature, family_head)
|
||||
}
|
||||
|
||||
pub(crate) fn validate_ed25519_signature(
|
||||
deps: Deps<'_>,
|
||||
signed_bytes: &[u8],
|
||||
signature: &str,
|
||||
identity: IdentityKeyRef<'_>,
|
||||
) -> Result<(), MixnetContractError> {
|
||||
let mut identity_bytes = [0u8; 32];
|
||||
let mut signature_bytes = [0u8; 64];
|
||||
|
||||
let identity_used_bytes = bs58::decode(identity)
|
||||
.into(&mut identity_bytes)
|
||||
pub(crate) fn decode_ed25519_identity_key(
|
||||
encoded: IdentityKeyRef,
|
||||
) -> Result<[u8; 32], MixnetContractError> {
|
||||
let mut public_key = [0u8; 32];
|
||||
let used = bs58::decode(encoded)
|
||||
.into(&mut public_key)
|
||||
.map_err(|err| MixnetContractError::MalformedEd25519IdentityKey(err.to_string()))?;
|
||||
let signature_used_bytes = bs58::decode(signature)
|
||||
.into(&mut signature_bytes)
|
||||
.map_err(|err| MixnetContractError::MalformedEd25519Signature(err.to_string()))?;
|
||||
|
||||
if identity_used_bytes != 32 {
|
||||
if used != 32 {
|
||||
return Err(MixnetContractError::MalformedEd25519IdentityKey(
|
||||
"Too few bytes provided for the public key".into(),
|
||||
));
|
||||
}
|
||||
|
||||
if signature_used_bytes != 64 {
|
||||
return Err(MixnetContractError::MalformedEd25519Signature(
|
||||
"Too few bytes provided for the signature".into(),
|
||||
));
|
||||
}
|
||||
|
||||
let res = deps
|
||||
.api
|
||||
.ed25519_verify(signed_bytes, &signature_bytes, &identity_bytes)
|
||||
.map_err(cosmwasm_std::StdError::verification_err)?;
|
||||
if !res {
|
||||
Err(MixnetContractError::InvalidEd25519Signature)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use cosmwasm_std::testing::mock_dependencies;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use rand_chacha::rand_core::SeedableRng;
|
||||
|
||||
#[test]
|
||||
fn validating_node_signature() {
|
||||
let deps = mock_dependencies();
|
||||
|
||||
// since those tests are NOT compiled to wasm, we can use rng-related dependency
|
||||
let dummy_seed = [1u8; 32];
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(dummy_seed);
|
||||
|
||||
let short_bs58 = "2SfEgZ4aQUr3HSwqE";
|
||||
let long_bs58 = "g34PyULki9fc3FqKobj5wdVNCaWAt1M9oZowyyMFfWSCejxg7wt574piZVjqjFEN2UXsgZ56KTkKf3jnWD4DJ2Gsf7KXQAvptFfcYRrZHTjMVo3NXcBSNm3wDBKZWZURzp4Fixv";
|
||||
|
||||
let address1 = Addr::unchecked("some-dummy-address1");
|
||||
let address2 = Addr::unchecked("some-dummy-address2");
|
||||
|
||||
let keypair1 = identity::KeyPair::new(&mut rng);
|
||||
let keypair2 = identity::KeyPair::new(&mut rng);
|
||||
|
||||
let sig_addr1_key1 = keypair1
|
||||
.private_key()
|
||||
.sign(address1.as_bytes())
|
||||
.to_base58_string();
|
||||
let sig_addr2_key1 = keypair1
|
||||
.private_key()
|
||||
.sign(address2.as_bytes())
|
||||
.to_base58_string();
|
||||
let sig_addr1_key2 = keypair2
|
||||
.private_key()
|
||||
.sign(address1.as_bytes())
|
||||
.to_base58_string();
|
||||
|
||||
assert_eq!(
|
||||
Err(MixnetContractError::MalformedEd25519IdentityKey(
|
||||
"buffer provided to decode base58 encoded string into was too small".into()
|
||||
)),
|
||||
validate_node_identity_signature(deps.as_ref(), &address1, &sig_addr1_key1, long_bs58,)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Err(MixnetContractError::MalformedEd25519Signature(
|
||||
"buffer provided to decode base58 encoded string into was too small".into()
|
||||
)),
|
||||
validate_node_identity_signature(
|
||||
deps.as_ref(),
|
||||
&address1,
|
||||
long_bs58.into(),
|
||||
&keypair1.public_key().to_base58_string(),
|
||||
)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Err(MixnetContractError::MalformedEd25519IdentityKey(
|
||||
"Too few bytes provided for the public key".into()
|
||||
)),
|
||||
validate_node_identity_signature(deps.as_ref(), &address1, &sig_addr1_key1, short_bs58,)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Err(MixnetContractError::MalformedEd25519Signature(
|
||||
"Too few bytes provided for the signature".into()
|
||||
)),
|
||||
validate_node_identity_signature(
|
||||
deps.as_ref(),
|
||||
&address1,
|
||||
short_bs58.into(),
|
||||
&keypair1.public_key().to_base58_string(),
|
||||
)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Err(MixnetContractError::InvalidEd25519Signature),
|
||||
validate_node_identity_signature(
|
||||
deps.as_ref(),
|
||||
&address1,
|
||||
&sig_addr1_key1,
|
||||
&keypair2.public_key().to_base58_string(),
|
||||
)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Err(MixnetContractError::InvalidEd25519Signature),
|
||||
validate_node_identity_signature(
|
||||
deps.as_ref(),
|
||||
&address1,
|
||||
&sig_addr2_key1,
|
||||
&keypair1.public_key().to_base58_string(),
|
||||
)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Err(MixnetContractError::InvalidEd25519Signature),
|
||||
validate_node_identity_signature(
|
||||
deps.as_ref(),
|
||||
&address2,
|
||||
&sig_addr1_key1,
|
||||
&keypair1.public_key().to_base58_string(),
|
||||
)
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Err(MixnetContractError::InvalidEd25519Signature),
|
||||
validate_node_identity_signature(
|
||||
deps.as_ref(),
|
||||
&address1,
|
||||
&sig_addr1_key2,
|
||||
&keypair1.public_key().to_base58_string(),
|
||||
)
|
||||
);
|
||||
|
||||
assert!(validate_node_identity_signature(
|
||||
deps.as_ref(),
|
||||
&address1,
|
||||
&sig_addr1_key1,
|
||||
&keypair1.public_key().to_base58_string(),
|
||||
)
|
||||
.is_ok());
|
||||
}
|
||||
Ok(public_key)
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ pub mod test_helpers {
|
||||
use mixnet_contract_common::events::{
|
||||
may_find_attribute, MixnetEventType, DELEGATES_REWARD_KEY, OPERATOR_REWARD_KEY,
|
||||
};
|
||||
use mixnet_contract_common::families::FamilyHead;
|
||||
use mixnet_contract_common::mixnode::{MixNodeRewarding, UnbondedMixnode};
|
||||
use mixnet_contract_common::pending_events::{PendingEpochEventData, PendingIntervalEventData};
|
||||
use mixnet_contract_common::reward_params::{Performance, RewardingParams};
|
||||
@@ -61,10 +62,10 @@ pub mod test_helpers {
|
||||
use mixnet_contract_common::rewarding::simulator::Simulator;
|
||||
use mixnet_contract_common::rewarding::RewardDistribution;
|
||||
use mixnet_contract_common::{
|
||||
Delegation, EpochState, EpochStatus, Gateway, GatewayBondingPayload, IdentityKey,
|
||||
InitialRewardingParams, InstantiateMsg, Interval, MixId, MixNode, MixNodeBond,
|
||||
MixnodeBondingPayload, Percent, RewardedSetNodeStatus, SignableGatewayBondingMsg,
|
||||
SignableMixNodeBondingMsg,
|
||||
construct_family_join_permit, Delegation, EpochState, EpochStatus, Gateway,
|
||||
GatewayBondingPayload, IdentityKey, IdentityKeyRef, InitialRewardingParams, InstantiateMsg,
|
||||
Interval, MixId, MixNode, MixNodeBond, MixnodeBondingPayload, Percent,
|
||||
RewardedSetNodeStatus, SignableGatewayBondingMsg, SignableMixNodeBondingMsg,
|
||||
};
|
||||
use nym_contracts_common::signing::{
|
||||
ContractMessageContent, MessageSignature, SignableMessage, SigningAlgorithm, SigningPurpose,
|
||||
@@ -169,26 +170,64 @@ pub mod test_helpers {
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
pub fn generate_family_join_permit(
|
||||
&mut self,
|
||||
family_owner_keys: &identity::KeyPair,
|
||||
member_node: IdentityKeyRef,
|
||||
vesting: bool,
|
||||
) -> MessageSignature {
|
||||
let identity = family_owner_keys.public_key().to_base58_string();
|
||||
|
||||
let head_mixnode = mixnodes_storage::mixnode_bonds()
|
||||
.idx
|
||||
.identity_key
|
||||
.item(self.deps().storage, identity.clone())
|
||||
.unwrap()
|
||||
.map(|record| record.1)
|
||||
.unwrap();
|
||||
|
||||
let family_head = FamilyHead::new(&identity);
|
||||
let owner = head_mixnode.owner;
|
||||
|
||||
let nonce =
|
||||
signing_storage::get_signing_nonce(self.deps().storage, owner.clone()).unwrap();
|
||||
|
||||
let proxy = if vesting {
|
||||
Some(self.vesting_contract())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let msg =
|
||||
construct_family_join_permit(nonce, family_head, proxy, member_node.to_owned());
|
||||
|
||||
let sig_bytes = family_owner_keys
|
||||
.private_key()
|
||||
.sign(&msg.to_plaintext().unwrap())
|
||||
.to_bytes();
|
||||
MessageSignature::from(sig_bytes.as_ref())
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn join_family(
|
||||
&mut self,
|
||||
member: &str,
|
||||
member_keys: &identity::KeyPair,
|
||||
head_keys: &identity::KeyPair,
|
||||
vesting: bool,
|
||||
) {
|
||||
let identity_signature = member_keys.private_key().sign_text(member);
|
||||
let join_signature = head_keys
|
||||
.private_key()
|
||||
.sign(&member_keys.public_key().to_bytes())
|
||||
.to_base58_string();
|
||||
let member_identity = member_keys.public_key().to_base58_string();
|
||||
let head_identity = head_keys.public_key().to_base58_string();
|
||||
|
||||
let join_permit =
|
||||
self.generate_family_join_permit(head_keys, &member_identity, vesting);
|
||||
let family_head = FamilyHead::new(&head_identity);
|
||||
|
||||
try_join_family(
|
||||
self.deps_mut(),
|
||||
mock_info(member, &[]),
|
||||
Some(identity_signature),
|
||||
join_signature,
|
||||
head_identity,
|
||||
join_permit,
|
||||
family_head,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -199,9 +238,8 @@ pub mod test_helpers {
|
||||
label: &str,
|
||||
) -> (MixId, identity::KeyPair) {
|
||||
let (mix_id, keys) = self.add_dummy_mixnode_with_proxy_and_keypair(head, None);
|
||||
let sig = keys.private_key().sign_text(head);
|
||||
|
||||
try_create_family(self.deps_mut(), mock_info(head, &[]), sig, label).unwrap();
|
||||
try_create_family(self.deps_mut(), mock_info(head, &[]), label.to_string()).unwrap();
|
||||
(mix_id, keys)
|
||||
}
|
||||
|
||||
@@ -290,6 +328,53 @@ pub mod test_helpers {
|
||||
ed25519_sign_message(msg, key)
|
||||
}
|
||||
|
||||
pub fn add_dummy_mixnode_with_keypair(
|
||||
&mut self,
|
||||
owner: &str,
|
||||
stake: Option<Uint128>,
|
||||
) -> (MixId, identity::KeyPair) {
|
||||
let stake = self.make_mix_pledge(stake);
|
||||
|
||||
let keypair = identity::KeyPair::new(&mut self.rng);
|
||||
let identity_key = keypair.public_key().to_base58_string();
|
||||
let legit_sphinx_keys = nym_crypto::asymmetric::encryption::KeyPair::new(&mut self.rng);
|
||||
|
||||
let mixnode = MixNode {
|
||||
identity_key,
|
||||
sphinx_key: legit_sphinx_keys.public_key().to_base58_string(),
|
||||
..tests::fixtures::mix_node_fixture()
|
||||
};
|
||||
|
||||
let msg = mixnode_bonding_sign_payload(
|
||||
self.deps(),
|
||||
owner,
|
||||
None,
|
||||
mixnode.clone(),
|
||||
stake.clone(),
|
||||
);
|
||||
let owner_signature = ed25519_sign_message(msg, keypair.private_key());
|
||||
|
||||
let info = mock_info(owner, &stake);
|
||||
let current_id_counter = mixnodes_storage::MIXNODE_ID_COUNTER
|
||||
.may_load(self.deps().storage)
|
||||
.unwrap()
|
||||
.unwrap_or_default();
|
||||
|
||||
let env = self.env();
|
||||
try_add_mixnode(
|
||||
self.deps_mut(),
|
||||
env,
|
||||
info,
|
||||
mixnode,
|
||||
tests::fixtures::mix_node_cost_params_fixture(),
|
||||
owner_signature,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// newly added mixnode gets assigned the current counter + 1
|
||||
(current_id_counter + 1, keypair)
|
||||
}
|
||||
|
||||
pub fn add_dummy_mixnode_with_proxy_and_keypair(
|
||||
&mut self,
|
||||
owner: &str,
|
||||
|
||||
@@ -17,13 +17,13 @@ crate-type = ["cdylib", "rlib"]
|
||||
library = []
|
||||
|
||||
[dependencies]
|
||||
cw-utils = { version = "0.13.4" }
|
||||
cw2 = { version = "0.13.4" }
|
||||
cw3 = { version = "0.13.4" }
|
||||
cw3-fixed-multisig = { version = "0.13.4", features = ["library"] }
|
||||
cw4 = { version = "0.13.4" }
|
||||
cw-storage-plus = { version = "0.13.4" }
|
||||
cosmwasm-std = { version = "1.0.0" }
|
||||
cw-utils = { workspace = true }
|
||||
cw2 = { workspace = true }
|
||||
cw3 = { workspace = true }
|
||||
cw3-fixed-multisig = { workspace = true, features = ["library"] }
|
||||
cw4 = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
schemars = "0.8.1"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
|
||||
@@ -33,4 +33,4 @@ nym-multisig-contract-common = { path= "../../../common/cosmwasm-smart-contracts
|
||||
[dev-dependencies]
|
||||
cosmwasm-schema = { version = "1.0.0" }
|
||||
cw4-group = { path = "../cw4-group", version = "0.13.4" }
|
||||
cw-multi-test = { version = "0.13.4" }
|
||||
cw-multi-test = { workspace = true }
|
||||
|
||||
@@ -26,15 +26,15 @@ library = []
|
||||
[dependencies]
|
||||
nym-group-contract-common = { path = "../../../common/cosmwasm-smart-contracts/group-contract" }
|
||||
|
||||
cw-utils = { version = "0.13.4" }
|
||||
cw2 = { version = "0.13.4" }
|
||||
cw4 = { version = "0.13.4" }
|
||||
cw-controllers = { version = "0.13.4" }
|
||||
cw-storage-plus = { version = "0.13.4" }
|
||||
cosmwasm-std = { version = "1.0.0" }
|
||||
cw-utils = { workspace = true }
|
||||
cw2 = { workspace = true }
|
||||
cw4 = { workspace = true }
|
||||
cw-controllers = { workspace = true }
|
||||
cw-storage-plus = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
schemars = "0.8.1"
|
||||
serde = { version = "1.0.103", default-features = false, features = ["derive"] }
|
||||
thiserror = { version = "1.0.23" }
|
||||
|
||||
[dev-dependencies]
|
||||
cosmwasm-schema = { version = "1.0.0" }
|
||||
cosmwasm-schema = { workspace = true }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-vesting-contract"
|
||||
version = "1.2.0-pre.1"
|
||||
version = "1.3.0"
|
||||
description = "Nym vesting contract"
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
@@ -20,13 +20,14 @@ name = "vesting_contract"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.2.0" }
|
||||
contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", package = "nym-contracts-common", version = "0.2.0" }
|
||||
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.2.0" }
|
||||
mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract", package = "nym-mixnet-contract-common", version = "0.4.0" }
|
||||
contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", package = "nym-contracts-common", version = "0.4.0" }
|
||||
vesting-contract-common = { path = "../../common/cosmwasm-smart-contracts/vesting-contract", package = "nym-vesting-contract-common", version = "0.4.0" }
|
||||
|
||||
cosmwasm-std = { version = "1.0.0 "}
|
||||
cw2 = { version = "0.13.4" }
|
||||
cw-storage-plus = { version = "0.13.4", features = ["iterator"] }
|
||||
cosmwasm-std = { workspace = true }
|
||||
cosmwasm-derive = { workspace = true }
|
||||
cw2 = { workspace = true }
|
||||
cw-storage-plus = { workspace = true, features = ["iterator"] }
|
||||
|
||||
schemars = "0.8"
|
||||
serde = { version = "1.0", default-features = false, features = ["derive"] }
|
||||
@@ -38,6 +39,7 @@ rand_chacha = "0.3.1"
|
||||
base64 = "0.21.0"
|
||||
hex = "0.4.3"
|
||||
serde_json = "1.0.66"
|
||||
cosmwasm-crypto = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { version = "=7.4.3", default-features = false, features = ["build", "git", "rustc"] }
|
||||
|
||||
@@ -11,9 +11,10 @@ use contracts_common::signing::MessageSignature;
|
||||
use contracts_common::ContractBuildInformation;
|
||||
use cosmwasm_std::{
|
||||
coin, entry_point, to_binary, Addr, BankMsg, Coin, Deps, DepsMut, Env, MessageInfo, Order,
|
||||
QueryResponse, Response, StdError, StdResult, Timestamp, Uint128,
|
||||
QueryResponse, Response, StdResult, Timestamp, Uint128,
|
||||
};
|
||||
use cw_storage_plus::Bound;
|
||||
use mixnet_contract_common::families::FamilyHead;
|
||||
use mixnet_contract_common::gateway::GatewayConfigUpdate;
|
||||
use mixnet_contract_common::mixnode::{MixNodeConfigUpdate, MixNodeCostParams};
|
||||
use mixnet_contract_common::{Gateway, MixId, MixNode};
|
||||
@@ -75,15 +76,6 @@ pub fn instantiate(
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
// this is the first migration that uses cw2 standard and thus the value in the storage doesn't yet exist
|
||||
// set it instead.
|
||||
if matches!(
|
||||
cw2::get_contract_version(deps.storage),
|
||||
Err(StdError::NotFound { .. })
|
||||
) {
|
||||
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
|
||||
}
|
||||
|
||||
// note: don't remove this particular bit of code as we have to ALWAYS check whether we have to update the stored version
|
||||
let version: Version =
|
||||
CONTRACT_VERSION
|
||||
@@ -106,7 +98,7 @@ pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Respons
|
||||
cw2::set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
|
||||
|
||||
// If state structure changed in any contract version in the way migration is needed, it
|
||||
// should occur here
|
||||
// should occur here, for example anything from `crate::queued_migrations::`
|
||||
}
|
||||
|
||||
Ok(Response::new())
|
||||
@@ -120,28 +112,13 @@ pub fn execute(
|
||||
msg: ExecuteMsg,
|
||||
) -> Result<Response, ContractError> {
|
||||
match msg {
|
||||
ExecuteMsg::CreateFamily {
|
||||
owner_signature,
|
||||
label,
|
||||
} => try_create_family(info, deps, owner_signature, label),
|
||||
ExecuteMsg::CreateFamily { label } => try_create_family(info, deps, label),
|
||||
ExecuteMsg::JoinFamily {
|
||||
node_identity_signature,
|
||||
family_signature,
|
||||
join_permit,
|
||||
family_head,
|
||||
} => try_join_family(
|
||||
info,
|
||||
deps,
|
||||
node_identity_signature,
|
||||
family_signature,
|
||||
family_head,
|
||||
),
|
||||
ExecuteMsg::LeaveFamily {
|
||||
node_identity_signature,
|
||||
family_head,
|
||||
} => try_leave_family(info, deps, node_identity_signature, family_head),
|
||||
ExecuteMsg::KickFamilyMember { signature, member } => {
|
||||
try_kick_family_member(info, deps, signature, member)
|
||||
}
|
||||
} => try_join_family(info, deps, join_permit, family_head),
|
||||
ExecuteMsg::LeaveFamily { family_head } => try_leave_family(info, deps, family_head),
|
||||
ExecuteMsg::KickFamilyMember { member } => try_kick_family_member(info, deps, member),
|
||||
ExecuteMsg::UpdateLockedPledgeCap { address, cap } => {
|
||||
try_update_locked_pledge_cap(address, cap, info, deps)
|
||||
}
|
||||
@@ -235,44 +212,35 @@ pub fn execute(
|
||||
pub fn try_create_family(
|
||||
info: MessageInfo,
|
||||
deps: DepsMut,
|
||||
owner_signature: String,
|
||||
label: String,
|
||||
) -> Result<Response, ContractError> {
|
||||
let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?;
|
||||
account.try_create_family(deps.storage, owner_signature, label)
|
||||
account.try_create_family(deps.storage, label)
|
||||
}
|
||||
pub fn try_join_family(
|
||||
info: MessageInfo,
|
||||
deps: DepsMut,
|
||||
node_identity_signature: String,
|
||||
family_signature: String,
|
||||
family_head: String,
|
||||
join_permit: MessageSignature,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, ContractError> {
|
||||
let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?;
|
||||
account.try_join_family(
|
||||
deps.storage,
|
||||
node_identity_signature,
|
||||
family_signature,
|
||||
&family_head,
|
||||
)
|
||||
account.try_join_family(deps.storage, join_permit, family_head)
|
||||
}
|
||||
pub fn try_leave_family(
|
||||
info: MessageInfo,
|
||||
deps: DepsMut,
|
||||
node_identity_signature: String,
|
||||
family_head: String,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, ContractError> {
|
||||
let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?;
|
||||
account.try_leave_family(deps.storage, node_identity_signature, &family_head)
|
||||
account.try_leave_family(deps.storage, family_head)
|
||||
}
|
||||
pub fn try_kick_family_member(
|
||||
info: MessageInfo,
|
||||
deps: DepsMut,
|
||||
signature: String,
|
||||
member: String,
|
||||
) -> Result<Response, ContractError> {
|
||||
let account = account_from_address(info.sender.as_ref(), deps.storage, deps.api)?;
|
||||
account.try_head_kick_member(deps.storage, signature, &member)
|
||||
account.try_head_kick_member(deps.storage, &member)
|
||||
}
|
||||
|
||||
/// Update locked_pledge_cap, the hard cap for staking/bonding with unvested tokens.
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
@@ -1,34 +1,32 @@
|
||||
use crate::errors::ContractError;
|
||||
use contracts_common::signing::MessageSignature;
|
||||
use cosmwasm_std::{Response, Storage};
|
||||
use mixnet_contract_common::families::FamilyHead;
|
||||
use mixnet_contract_common::IdentityKeyRef;
|
||||
|
||||
pub trait NodeFamilies {
|
||||
fn try_create_family(
|
||||
&self,
|
||||
storage: &dyn Storage,
|
||||
owner_signature: String,
|
||||
label: String,
|
||||
) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_join_family(
|
||||
&self,
|
||||
storage: &dyn Storage,
|
||||
node_identity_signature: String,
|
||||
family_signature: String,
|
||||
family_head: IdentityKeyRef,
|
||||
join_permit: MessageSignature,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_leave_family(
|
||||
&self,
|
||||
storage: &dyn Storage,
|
||||
signature: String,
|
||||
family_head: IdentityKeyRef,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, ContractError>;
|
||||
|
||||
fn try_head_kick_member(
|
||||
&self,
|
||||
storage: &dyn Storage,
|
||||
signature: String,
|
||||
member: IdentityKeyRef<'_>,
|
||||
) -> Result<Response, ContractError>;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
use super::Account;
|
||||
use crate::{errors::ContractError, storage::MIXNET_CONTRACT_ADDRESS, traits::NodeFamilies};
|
||||
use contracts_common::signing::MessageSignature;
|
||||
use cosmwasm_std::{wasm_execute, Response, Storage};
|
||||
use mixnet_contract_common::families::FamilyHead;
|
||||
use mixnet_contract_common::{ExecuteMsg as MixnetExecuteMsg, IdentityKeyRef};
|
||||
|
||||
impl NodeFamilies for Account {
|
||||
fn try_create_family(
|
||||
&self,
|
||||
storage: &dyn Storage,
|
||||
owner_signature: String,
|
||||
label: String,
|
||||
) -> Result<Response, ContractError> {
|
||||
let msg = MixnetExecuteMsg::CreateFamilyOnBehalf {
|
||||
owner_address: self.owner_address().to_string(),
|
||||
owner_signature,
|
||||
owner_address: self.owner_address().into_string(),
|
||||
label,
|
||||
};
|
||||
|
||||
@@ -24,15 +24,13 @@ impl NodeFamilies for Account {
|
||||
fn try_join_family(
|
||||
&self,
|
||||
storage: &dyn Storage,
|
||||
node_identity_signature: String,
|
||||
family_signature: String,
|
||||
family_head: IdentityKeyRef,
|
||||
join_permit: MessageSignature,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, ContractError> {
|
||||
let msg = MixnetExecuteMsg::JoinFamilyOnBehalf {
|
||||
member_address: self.owner_address().to_string(),
|
||||
node_identity_signature,
|
||||
family_signature,
|
||||
family_head: family_head.to_string(),
|
||||
join_permit,
|
||||
family_head,
|
||||
};
|
||||
|
||||
let msg = wasm_execute(MIXNET_CONTRACT_ADDRESS.load(storage)?, &msg, vec![])?;
|
||||
@@ -43,13 +41,11 @@ impl NodeFamilies for Account {
|
||||
fn try_leave_family(
|
||||
&self,
|
||||
storage: &dyn Storage,
|
||||
node_identity_signature: String,
|
||||
family_head: IdentityKeyRef,
|
||||
family_head: FamilyHead,
|
||||
) -> Result<Response, ContractError> {
|
||||
let msg = MixnetExecuteMsg::LeaveFamilyOnBehalf {
|
||||
member_address: self.owner_address().to_string(),
|
||||
node_identity_signature,
|
||||
family_head: family_head.to_string(),
|
||||
family_head,
|
||||
};
|
||||
|
||||
let msg = wasm_execute(MIXNET_CONTRACT_ADDRESS.load(storage)?, &msg, vec![])?;
|
||||
@@ -60,12 +56,10 @@ impl NodeFamilies for Account {
|
||||
fn try_head_kick_member(
|
||||
&self,
|
||||
storage: &dyn Storage,
|
||||
signature: String,
|
||||
member: IdentityKeyRef<'_>,
|
||||
) -> Result<Response, ContractError> {
|
||||
let msg = MixnetExecuteMsg::KickFamilyMemberOnBehalf {
|
||||
head_address: self.owner_address().to_string(),
|
||||
signature,
|
||||
member: member.to_string(),
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "explorer-api"
|
||||
version = "1.1.12"
|
||||
version = "1.1.14"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
@@ -42,6 +42,7 @@ pub(crate) struct PrettyDetailedMixNodeBond {
|
||||
pub operating_cost: Coin,
|
||||
pub profit_margin_percent: Percent,
|
||||
pub family_id: Option<u16>,
|
||||
pub blacklisted: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, JsonSchema)]
|
||||
|
||||
@@ -162,6 +162,7 @@ impl ThreadsafeMixNodesCache {
|
||||
operating_cost: rewarding_info.cost_params.interval_operating_cost.clone(),
|
||||
profit_margin_percent: rewarding_info.cost_params.profit_margin_percent,
|
||||
family_id,
|
||||
blacklisted: node.blacklisted,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ impl ExplorerApiTasks {
|
||||
|
||||
async fn retrieve_all_mixnodes(&self) -> Vec<MixNodeBondAnnotated> {
|
||||
info!("About to retrieve all mixnode bonds...");
|
||||
self.retrieve_mixnodes(validator_client::Client::get_cached_mixnodes_detailed)
|
||||
self.retrieve_mixnodes(validator_client::Client::get_cached_mixnodes_detailed_unfiltered)
|
||||
.await
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user