Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f7bdad59c | |||
| 6209e78c1e | |||
| 8e5062af96 | |||
| 496e642d7f | |||
| 07e18ec198 | |||
| d5953c28c1 | |||
| 3aa4b66588 | |||
| 005f0ce340 | |||
| 8d1d025fa2 | |||
| 1d53a2f954 | |||
| 963d55273f | |||
| 6872d7bf77 | |||
| 6fe93bcda0 | |||
| 78d568e04e | |||
| 796f5a678a | |||
| 0dfd1cca44 | |||
| eacefd3697 | |||
| 424c25768c | |||
| e460c1700e | |||
| 9935c99d41 | |||
| 2c4aee63bf | |||
| 0ebc395df9 | |||
| 1de3317e75 | |||
| 91c20af893 | |||
| 1365e2f246 | |||
| 3f4f76859b | |||
| 221e1870e5 | |||
| 9b36bccf0c | |||
| 80d7285497 | |||
| b94f2a483d | |||
| f64cfb4cd1 | |||
| 30e2f27c64 | |||
| 3113c1e9a7 | |||
| 1d8a931e0c | |||
| 48d0883b31 | |||
| e271370326 |
@@ -4,6 +4,28 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v1.1.17] (2023-05-02)
|
||||
|
||||
- Add service-provider-directory-contract support to nym-cli ([#3334])
|
||||
- Start using the node-testing-utils (implemented in #3270) in nym-api Network monitor to simplify the logic there ([#3312])
|
||||
- Add service-provider-directory support to validator-client ([#3296])
|
||||
- Allow topology injection in our WASM client ('test my node' feature) ([#3270])
|
||||
- Expose service-provider-directory contract data in nym-api endpoints ([#3242])
|
||||
- Cache service provider contract in nym-api ([#3241])
|
||||
- Feature/1 1 17 docs ([#3370])
|
||||
- adding a test for SP endpoint ([#3367])
|
||||
- Feature/store cipher ([#3350])
|
||||
|
||||
[#3334]: https://github.com/nymtech/nym/issues/3334
|
||||
[#3312]: https://github.com/nymtech/nym/issues/3312
|
||||
[#3296]: https://github.com/nymtech/nym/issues/3296
|
||||
[#3270]: https://github.com/nymtech/nym/issues/3270
|
||||
[#3242]: https://github.com/nymtech/nym/issues/3242
|
||||
[#3241]: https://github.com/nymtech/nym/issues/3241
|
||||
[#3370]: https://github.com/nymtech/nym/pull/3370
|
||||
[#3367]: https://github.com/nymtech/nym/pull/3367
|
||||
[#3350]: https://github.com/nymtech/nym/pull/3350
|
||||
|
||||
## [v1.1.16] (2023-04-25)
|
||||
|
||||
- Explorer - Fix sorting function on Stake Saturation. It is currently working per page and not globally ([#3320])
|
||||
|
||||
Generated
+77
-15
@@ -103,6 +103,17 @@ version = "1.0.70"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4"
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"blake2 0.10.6",
|
||||
"password-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.7"
|
||||
@@ -366,6 +377,15 @@ dependencies = [
|
||||
"opaque-debug 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe"
|
||||
dependencies = [
|
||||
"digest 0.10.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake3"
|
||||
version = "1.3.3"
|
||||
@@ -1576,7 +1596,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "explorer-api"
|
||||
version = "1.1.16"
|
||||
version = "1.1.17"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap 4.1.11",
|
||||
@@ -2757,7 +2777,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ae926706ba42c425c9457121178330d75e273df2e82e28b758faf3de3a9acb9"
|
||||
dependencies = [
|
||||
"arrayref",
|
||||
"blake2",
|
||||
"blake2 0.8.1",
|
||||
"chacha",
|
||||
"keystream",
|
||||
]
|
||||
@@ -3011,7 +3031,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.17"
|
||||
version = "1.1.18"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3047,7 +3067,9 @@ dependencies = [
|
||||
"nym-inclusion-probability",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-multisig-contract-common",
|
||||
"nym-node-tester-utils",
|
||||
"nym-pemstore",
|
||||
"nym-service-provider-directory-common",
|
||||
"nym-sphinx",
|
||||
"nym-task",
|
||||
"nym-topology",
|
||||
@@ -3142,7 +3164,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.16"
|
||||
version = "1.1.17"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.13.1",
|
||||
@@ -3189,6 +3211,7 @@ dependencies = [
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-multisig-contract-common",
|
||||
"nym-network-defaults",
|
||||
"nym-service-provider-directory-common",
|
||||
"nym-validator-client",
|
||||
"nym-vesting-contract-common",
|
||||
"rand 0.6.5",
|
||||
@@ -3203,7 +3226,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.16"
|
||||
version = "1.1.17"
|
||||
dependencies = [
|
||||
"clap 4.1.11",
|
||||
"dirs",
|
||||
@@ -3415,7 +3438,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-crypto"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"aes 0.8.2",
|
||||
"blake3",
|
||||
@@ -3436,6 +3459,7 @@ dependencies = [
|
||||
"subtle-encoding",
|
||||
"thiserror",
|
||||
"x25519-dalek",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3471,7 +3495,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-gateway"
|
||||
version = "1.1.16"
|
||||
version = "1.1.17"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -3619,7 +3643,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnode"
|
||||
version = "1.1.17"
|
||||
version = "1.1.18"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bs58",
|
||||
@@ -3721,7 +3745,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.16"
|
||||
version = "1.1.17"
|
||||
dependencies = [
|
||||
"async-file-watcher",
|
||||
"async-trait",
|
||||
@@ -3763,7 +3787,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-statistics"
|
||||
version = "1.1.16"
|
||||
version = "1.1.17"
|
||||
dependencies = [
|
||||
"dirs",
|
||||
"log",
|
||||
@@ -3876,6 +3900,7 @@ name = "nym-service-provider-directory-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@@ -3897,7 +3922,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.16"
|
||||
version = "1.1.17"
|
||||
dependencies = [
|
||||
"clap 4.1.11",
|
||||
"lazy_static",
|
||||
@@ -3993,6 +4018,7 @@ dependencies = [
|
||||
"nym-sphinx-forwarding",
|
||||
"nym-sphinx-framing",
|
||||
"nym-sphinx-params",
|
||||
"nym-sphinx-routing",
|
||||
"nym-sphinx-types",
|
||||
"nym-topology",
|
||||
"rand 0.7.3",
|
||||
@@ -4102,6 +4128,15 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-sphinx-routing"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nym-sphinx-addressing",
|
||||
"nym-sphinx-types",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-sphinx-types"
|
||||
version = "0.2.0"
|
||||
@@ -4123,6 +4158,20 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-store-cipher"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"argon2",
|
||||
"getrandom 0.2.8",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-task"
|
||||
version = "0.1.0"
|
||||
@@ -4146,6 +4195,7 @@ dependencies = [
|
||||
"nym-crypto",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-sphinx-addressing",
|
||||
"nym-sphinx-routing",
|
||||
"nym-sphinx-types",
|
||||
"rand 0.7.3",
|
||||
"thiserror",
|
||||
@@ -4204,6 +4254,7 @@ dependencies = [
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-multisig-contract-common",
|
||||
"nym-network-defaults",
|
||||
"nym-service-provider-directory-common",
|
||||
"nym-vesting-contract",
|
||||
"nym-vesting-contract-common",
|
||||
"prost 0.10.4",
|
||||
@@ -4470,6 +4521,17 @@ dependencies = [
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "password-hash"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"rand_core 0.6.4",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.12"
|
||||
@@ -5891,7 +5953,7 @@ checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4"
|
||||
dependencies = [
|
||||
"aes 0.7.5",
|
||||
"arrayref",
|
||||
"blake2",
|
||||
"blake2 0.8.1",
|
||||
"bs58",
|
||||
"byteorder",
|
||||
"chacha",
|
||||
@@ -5914,7 +5976,7 @@ source = "git+https://github.com/nymtech/sphinx.git#ca107d94360cdf8bbfbdb12fe532
|
||||
dependencies = [
|
||||
"aes 0.7.5",
|
||||
"arrayref",
|
||||
"blake2",
|
||||
"blake2 0.8.1",
|
||||
"bs58",
|
||||
"byteorder",
|
||||
"chacha",
|
||||
@@ -7468,9 +7530,9 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.5.7"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
|
||||
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
|
||||
dependencies = [
|
||||
"zeroize_derive",
|
||||
]
|
||||
|
||||
@@ -60,12 +60,14 @@ members = [
|
||||
"common/nymsphinx/forwarding",
|
||||
"common/nymsphinx/framing",
|
||||
"common/nymsphinx/params",
|
||||
"common/nymsphinx/routing",
|
||||
"common/nymsphinx/types",
|
||||
"common/pemstore",
|
||||
"common/socks5-client-core",
|
||||
"common/socks5/proxy-helpers",
|
||||
"common/socks5/requests",
|
||||
"common/statistics",
|
||||
"common/store-cipher",
|
||||
"common/task",
|
||||
"common/topology",
|
||||
"common/types",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.16"
|
||||
version = "1.1.17"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.16"
|
||||
version = "1.1.17"
|
||||
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"
|
||||
|
||||
@@ -13,7 +13,7 @@ use std::sync::Arc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use wasm_bindgen::JsValue;
|
||||
use wasm_bindgen_futures::future_to_promise;
|
||||
use wasm_utils::{console_log, js_error, simple_js_error};
|
||||
use wasm_utils::{console_log, simple_js_error};
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct NymClientTestRequest {
|
||||
@@ -142,20 +142,9 @@ impl WasmTopologyExt for Arc<ClientState> {
|
||||
return Err(WasmClientError::NonExistentMixnode { mixnode_identity }.into());
|
||||
};
|
||||
|
||||
let mut test_msgs = Vec::with_capacity(num_test_packets as usize);
|
||||
for i in 1..=num_test_packets {
|
||||
let msg = NodeTestMessage::new_mix(
|
||||
mix,
|
||||
i,
|
||||
num_test_packets,
|
||||
WasmTestMessageExt::new(test_id),
|
||||
);
|
||||
let serialized = match msg.as_bytes() {
|
||||
Ok(bytes) => bytes,
|
||||
Err(err) => return Err(js_error!("failed to serialize test message: {err}")),
|
||||
};
|
||||
test_msgs.push(serialized);
|
||||
}
|
||||
let ext = WasmTestMessageExt::new(test_id);
|
||||
let test_msgs = NodeTestMessage::mix_plaintexts(mix, num_test_packets, ext)
|
||||
.map_err(WasmClientError::from)?;
|
||||
|
||||
let mut updated = current_topology.clone();
|
||||
updated.set_mixes_in_layer(mix.layer.into(), vec![mix.to_owned()]);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::tester::helpers::NodeTestResult;
|
||||
use crate::tester::NodeTestMessage;
|
||||
use crate::tester::helpers::{NodeTestResult, WasmTestMessageExt};
|
||||
use futures::StreamExt;
|
||||
use nym_node_tester_utils::receiver::{Received, ReceivedReceiver};
|
||||
use nym_node_tester_utils::processor::Received;
|
||||
use nym_node_tester_utils::receiver::ReceivedReceiver;
|
||||
use nym_sphinx::chunking::fragment::FragmentIdentifier;
|
||||
use std::collections::HashSet;
|
||||
use std::time::Duration;
|
||||
@@ -21,7 +21,7 @@ pub(crate) struct EphemeralTestReceiver<'a> {
|
||||
duplicate_acks: u32,
|
||||
|
||||
timeout_duration: Duration,
|
||||
receiver_permit: AsyncMutexGuard<'a, ReceivedReceiver>,
|
||||
receiver_permit: AsyncMutexGuard<'a, ReceivedReceiver<WasmTestMessageExt>>,
|
||||
}
|
||||
|
||||
impl<'a> EphemeralTestReceiver<'a> {
|
||||
@@ -38,7 +38,7 @@ impl<'a> EphemeralTestReceiver<'a> {
|
||||
pub(crate) fn new(
|
||||
sent_packets: u32,
|
||||
expected_acks: HashSet<FragmentIdentifier>,
|
||||
receiver_permit: AsyncMutexGuard<'a, ReceivedReceiver>,
|
||||
receiver_permit: AsyncMutexGuard<'a, ReceivedReceiver<WasmTestMessageExt>>,
|
||||
timeout: Duration,
|
||||
) -> Self {
|
||||
EphemeralTestReceiver {
|
||||
@@ -53,23 +53,18 @@ impl<'a> EphemeralTestReceiver<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn on_next_received_packet(&mut self, packet: Option<Received>) -> bool {
|
||||
fn on_next_received_packet(&mut self, packet: Option<Received<WasmTestMessageExt>>) -> bool {
|
||||
let Some(received_packet) = packet else {
|
||||
// can't do anything more...
|
||||
console_error!("packet receiver has stopped processing results!");
|
||||
return true
|
||||
};
|
||||
match received_packet {
|
||||
Received::Message(msg) => match NodeTestMessage::try_recover(msg) {
|
||||
Ok(test_msg) => {
|
||||
if !self.received_valid_messages.insert(test_msg.msg_id) {
|
||||
self.duplicate_packets += 1;
|
||||
}
|
||||
Received::Message(msg) => {
|
||||
if !self.received_valid_messages.insert(msg.msg_id) {
|
||||
self.duplicate_packets += 1;
|
||||
}
|
||||
Err(err) => {
|
||||
console_warn!("failed to recover test message from received packet: {err}")
|
||||
}
|
||||
},
|
||||
}
|
||||
Received::Ack(frag_id) => {
|
||||
if self.expected_acks.contains(&frag_id) {
|
||||
if !self.received_valid_acks.insert(frag_id) {
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
// due to expansion of #[wasm_bindgen] macro on NodeTestResult
|
||||
#![allow(clippy::drop_non_drop)]
|
||||
|
||||
use nym_node_tester_utils::receiver::{Received, ReceivedReceiver};
|
||||
use nym_node_tester_utils::processor::Received;
|
||||
use nym_node_tester_utils::receiver::ReceivedReceiver;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
@@ -14,10 +15,10 @@ use wasm_bindgen::prelude::*;
|
||||
use wasm_utils::{console_log, console_warn};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(super) struct ReceivedReceiverWrapper(Arc<AsyncMutex<ReceivedReceiver>>);
|
||||
pub(super) struct ReceivedReceiverWrapper(Arc<AsyncMutex<ReceivedReceiver<WasmTestMessageExt>>>);
|
||||
|
||||
impl ReceivedReceiverWrapper {
|
||||
pub(super) fn new(inner: ReceivedReceiver) -> Self {
|
||||
pub(super) fn new(inner: ReceivedReceiver<WasmTestMessageExt>) -> Self {
|
||||
ReceivedReceiverWrapper(Arc::new(AsyncMutex::new(inner)))
|
||||
}
|
||||
|
||||
@@ -36,7 +37,7 @@ impl ReceivedReceiverWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn lock(&self) -> AsyncMutexGuard<'_, ReceivedReceiver> {
|
||||
pub(super) async fn lock(&self) -> AsyncMutexGuard<'_, ReceivedReceiver<WasmTestMessageExt>> {
|
||||
self.0.lock().await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ impl NymNodeTesterBuilder {
|
||||
let tester = NodeTester::new(
|
||||
rng,
|
||||
self.base_topology,
|
||||
address(&self.key_manager, gateway_identity),
|
||||
Some(address(&self.key_manager, gateway_identity)),
|
||||
PacketSize::default(),
|
||||
Duration::from_millis(5),
|
||||
Duration::from_millis(5),
|
||||
@@ -266,7 +266,12 @@ impl NymNodeTester {
|
||||
let test_ext = WasmTestMessageExt::new(test_nonce);
|
||||
let mut tester_permit = self.tester.lock().expect("mutex got poisoned");
|
||||
tester_permit
|
||||
.existing_identity_mixnode_test_packets(mixnode_identity, test_ext, num_test_packets)
|
||||
.existing_identity_mixnode_test_packets(
|
||||
mixnode_identity,
|
||||
test_ext,
|
||||
num_test_packets,
|
||||
None,
|
||||
)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ nym-vesting-contract-common = { path = "../../cosmwasm-smart-contracts/vesting-c
|
||||
nym-coconut-bandwidth-contract-common = { path = "../../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
|
||||
nym-multisig-contract-common = { path = "../../cosmwasm-smart-contracts/multisig-contract" }
|
||||
nym-group-contract-common = { path = "../../cosmwasm-smart-contracts/group-contract" }
|
||||
nym-service-provider-directory-common = { path = "../../cosmwasm-smart-contracts/service-provider-directory" }
|
||||
nym-vesting-contract = { path = "../../../contracts/vesting" }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
@@ -63,6 +64,10 @@ name = "offline_signing"
|
||||
# (traits would need to be moved around and refactored themselves)
|
||||
required-features = ["nyxd-client"]
|
||||
|
||||
[[example]]
|
||||
name = "query_service_provider_directory"
|
||||
required-features = ["nyxd-client"]
|
||||
|
||||
[features]
|
||||
nyxd-client = [
|
||||
"async-trait",
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use cosmrs::AccountId;
|
||||
use nym_network_defaults::{setup_env, NymNetworkDetails};
|
||||
use nym_service_provider_directory_common::NymAddress;
|
||||
use nym_validator_client::nyxd::traits::SpDirectoryQueryClient;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
setup_env(Some(&"../../../envs/qa-qwerty.env".parse().unwrap()));
|
||||
let network_details = NymNetworkDetails::new_from_env();
|
||||
let config =
|
||||
nym_validator_client::Config::try_from_nym_network_details(&network_details).unwrap();
|
||||
let client = nym_validator_client::Client::new_query(config).unwrap();
|
||||
|
||||
let config = client.nyxd.get_service_config().await.unwrap();
|
||||
println!("config: {config:?}");
|
||||
|
||||
let services_paged = client.nyxd.get_services_paged(None, None).await.unwrap();
|
||||
println!("services (paged): {services_paged:#?}");
|
||||
|
||||
let services = client.nyxd.get_all_services().await.unwrap();
|
||||
println!("services: {services:#?}");
|
||||
|
||||
let announcer = AccountId::from_str("n1hmf957kc7arcd39rl7xq8l0a4zyg7kxnv7su87").unwrap();
|
||||
let services_by_announcer = client
|
||||
.nyxd
|
||||
.get_services_by_announcer(announcer)
|
||||
.await
|
||||
.unwrap();
|
||||
println!("services (by announcer): {services_by_announcer:#?}");
|
||||
|
||||
let nym_address = NymAddress::new("foo.bar@gateway");
|
||||
let services_by_nym_address = client
|
||||
.nyxd
|
||||
.get_services_by_nym_address(nym_address)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(services_by_announcer, services_by_nym_address);
|
||||
|
||||
let service_info = client.nyxd.get_service_info(1).await;
|
||||
println!("service info: {service_info:#?}");
|
||||
}
|
||||
@@ -117,7 +117,7 @@ async fn test_nyxd_connection(
|
||||
);
|
||||
code == 18
|
||||
}
|
||||
Ok(Err(error @ NyxdError::NoContractAddressAvailable)) => {
|
||||
Ok(Err(error @ NyxdError::NoContractAddressAvailable(_))) => {
|
||||
log::debug!("Checking: nyxd url: {url}: {}: {error}", "failed".red());
|
||||
false
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ pub enum NymAPIError {
|
||||
source: reqwest::Error,
|
||||
},
|
||||
|
||||
#[error("Not found")]
|
||||
NotFound,
|
||||
|
||||
#[error("Request failed with error message - {0}")]
|
||||
GenericRequestFailure(String),
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ use nym_api_requests::models::{
|
||||
};
|
||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, MixId};
|
||||
use reqwest::Response;
|
||||
use nym_service_provider_directory_common::ServiceInfo;
|
||||
use reqwest::{Response, StatusCode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
@@ -76,6 +77,8 @@ impl Client {
|
||||
let res = self.send_get_request(path, params).await?;
|
||||
if res.status().is_success() {
|
||||
Ok(res.json().await?)
|
||||
} else if res.status() == StatusCode::NOT_FOUND {
|
||||
Err(NymAPIError::NotFound)
|
||||
} else {
|
||||
Err(NymAPIError::GenericRequestFailure(res.text().await?))
|
||||
}
|
||||
@@ -480,6 +483,11 @@ impl Client {
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_service_providers(&self) -> Result<Vec<ServiceInfo>, NymAPIError> {
|
||||
self.query_nym_api(&[routes::API_VERSION, routes::SERVICE_PROVIDERS], NO_PARAMS)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
// utility function that should solve the double slash problem in validator API forever.
|
||||
|
||||
@@ -32,3 +32,5 @@ pub const COMPUTE_REWARD_ESTIMATION: &str = "compute-reward-estimation";
|
||||
pub const AVG_UPTIME: &str = "avg_uptime";
|
||||
pub const STAKE_SATURATION: &str = "stake-saturation";
|
||||
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
|
||||
|
||||
pub const SERVICE_PROVIDERS: &str = "service-providers";
|
||||
|
||||
@@ -21,8 +21,8 @@ use std::{io, time::Duration};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum NyxdError {
|
||||
#[error("No contract address is available to perform the call")]
|
||||
NoContractAddressAvailable,
|
||||
#[error("No contract address is available to perform the call: {0}")]
|
||||
NoContractAddressAvailable(String),
|
||||
|
||||
#[error(transparent)]
|
||||
WalletError(#[from] DirectSecp256k1HdWalletError),
|
||||
|
||||
@@ -67,6 +67,7 @@ pub struct Config {
|
||||
pub(crate) group_contract_address: Option<AccountId>,
|
||||
pub(crate) multisig_contract_address: Option<AccountId>,
|
||||
pub(crate) coconut_dkg_contract_address: Option<AccountId>,
|
||||
pub(crate) service_provider_contract_address: Option<AccountId>,
|
||||
// TODO: add this in later commits
|
||||
// pub(crate) gas_price: GasPrice,
|
||||
}
|
||||
@@ -131,6 +132,13 @@ impl Config {
|
||||
details.contracts.coconut_dkg_contract_address.as_ref(),
|
||||
prefix,
|
||||
)?,
|
||||
service_provider_contract_address: Self::parse_optional_account(
|
||||
details
|
||||
.contracts
|
||||
.service_provider_directory_contract_address
|
||||
.as_ref(),
|
||||
prefix,
|
||||
)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -246,6 +254,10 @@ impl<C> NyxdClient<C> {
|
||||
self.config.multisig_contract_address = Some(address);
|
||||
}
|
||||
|
||||
pub fn set_service_provider_contract_address(&mut self, address: AccountId) {
|
||||
self.config.service_provider_contract_address = Some(address);
|
||||
}
|
||||
|
||||
// TODO: this should get changed into Result<&AccountId, NyxdError> (or Option<&AccountId> in future commits
|
||||
// note: what unwrap is doing here is just moving a failure that would have normally
|
||||
// occurred in `connect` when attempting to parse an empty address,
|
||||
@@ -304,6 +316,11 @@ impl<C> NyxdClient<C> {
|
||||
self.config.coconut_dkg_contract_address.as_ref().unwrap()
|
||||
}
|
||||
|
||||
// The service provider directory contract is optional, so we return an Option not a Result
|
||||
pub fn service_provider_contract_address(&self) -> Option<&AccountId> {
|
||||
self.config.service_provider_contract_address.as_ref()
|
||||
}
|
||||
|
||||
pub fn set_simulated_gas_multiplier(&mut self, multiplier: f32) {
|
||||
self.simulated_gas_multiplier = multiplier;
|
||||
}
|
||||
|
||||
@@ -16,15 +16,20 @@ mod mixnet_signing_client;
|
||||
mod multisig_signing_client;
|
||||
mod vesting_signing_client;
|
||||
|
||||
mod sp_directory_query_client;
|
||||
mod sp_directory_signing_client;
|
||||
|
||||
pub use coconut_bandwidth_query_client::CoconutBandwidthQueryClient;
|
||||
pub use dkg_query_client::DkgQueryClient;
|
||||
pub use group_query_client::GroupQueryClient;
|
||||
pub use mixnet_query_client::MixnetQueryClient;
|
||||
pub use multisig_query_client::MultisigQueryClient;
|
||||
pub use sp_directory_query_client::SpDirectoryQueryClient;
|
||||
pub use vesting_query_client::VestingQueryClient;
|
||||
|
||||
pub use coconut_bandwidth_signing_client::CoconutBandwidthSigningClient;
|
||||
pub use dkg_signing_client::DkgSigningClient;
|
||||
pub use mixnet_signing_client::MixnetSigningClient;
|
||||
pub use multisig_signing_client::MultisigSigningClient;
|
||||
pub use sp_directory_signing_client::SpDirectorySigningClient;
|
||||
pub use vesting_signing_client::VestingSigningClient;
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
use async_trait::async_trait;
|
||||
use cosmrs::AccountId;
|
||||
use nym_contracts_common::ContractBuildInformation;
|
||||
use nym_service_provider_directory_common::{
|
||||
msg::QueryMsg as SpQueryMsg,
|
||||
response::{
|
||||
ConfigResponse, PagedServicesListResponse, ServiceInfoResponse, ServicesListResponse,
|
||||
},
|
||||
NymAddress, ServiceId, ServiceInfo,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::nyxd::{error::NyxdError, CosmWasmClient, NyxdClient};
|
||||
|
||||
#[async_trait]
|
||||
pub trait SpDirectoryQueryClient {
|
||||
async fn query_service_provider_contract<T>(&self, query: SpQueryMsg) -> Result<T, NyxdError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>;
|
||||
|
||||
async fn get_service_config(&self) -> Result<ConfigResponse, NyxdError> {
|
||||
self.query_service_provider_contract(SpQueryMsg::Config {})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_service_info(
|
||||
&self,
|
||||
service_id: ServiceId,
|
||||
) -> Result<ServiceInfoResponse, NyxdError> {
|
||||
self.query_service_provider_contract(SpQueryMsg::ServiceId { service_id })
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_services_paged(
|
||||
&self,
|
||||
start_after: Option<ServiceId>,
|
||||
limit: Option<u32>,
|
||||
) -> Result<PagedServicesListResponse, NyxdError> {
|
||||
self.query_service_provider_contract(SpQueryMsg::All { limit, start_after })
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_services_by_announcer(
|
||||
&self,
|
||||
announcer: AccountId,
|
||||
) -> Result<ServicesListResponse, NyxdError> {
|
||||
self.query_service_provider_contract(SpQueryMsg::ByAnnouncer {
|
||||
announcer: announcer.to_string(),
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_services_by_nym_address(
|
||||
&self,
|
||||
nym_address: NymAddress,
|
||||
) -> Result<ServicesListResponse, NyxdError> {
|
||||
self.query_service_provider_contract(SpQueryMsg::ByNymAddress { nym_address })
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_sp_contract_version(&self) -> Result<ContractBuildInformation, NyxdError> {
|
||||
self.query_service_provider_contract(SpQueryMsg::GetContractVersion {})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_all_services(&self) -> Result<Vec<ServiceInfo>, NyxdError> {
|
||||
let mut services = Vec::new();
|
||||
let mut start_after = None;
|
||||
|
||||
loop {
|
||||
let mut paged_response = self.get_services_paged(start_after.take(), None).await?;
|
||||
|
||||
let last_id = paged_response.services.last().map(|serv| serv.service_id);
|
||||
services.append(&mut paged_response.services);
|
||||
|
||||
if let Some(start_after_res) = last_id {
|
||||
start_after = Some(start_after_res)
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(services)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C> SpDirectoryQueryClient for NyxdClient<C>
|
||||
where
|
||||
C: CosmWasmClient + Send + Sync,
|
||||
{
|
||||
async fn query_service_provider_contract<T>(&self, query: SpQueryMsg) -> Result<T, NyxdError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
{
|
||||
self.client
|
||||
.query_contract_smart(
|
||||
self.service_provider_contract_address().ok_or(
|
||||
NyxdError::NoContractAddressAvailable(
|
||||
"service provider directory contract".to_string(),
|
||||
),
|
||||
)?,
|
||||
&query,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C> SpDirectoryQueryClient for crate::Client<C>
|
||||
where
|
||||
C: CosmWasmClient + Send + Sync,
|
||||
{
|
||||
async fn query_service_provider_contract<T>(&self, query: SpQueryMsg) -> Result<T, NyxdError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
{
|
||||
self.nyxd.query_service_provider_contract(query).await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use async_trait::async_trait;
|
||||
use nym_service_provider_directory_common::{
|
||||
msg::ExecuteMsg as SpExecuteMsg, NymAddress, ServiceId, ServiceType,
|
||||
};
|
||||
|
||||
use crate::nyxd::{
|
||||
coin::Coin, cosmwasm_client::types::ExecuteResult, error::NyxdError, Fee, NyxdClient,
|
||||
SigningCosmWasmClient,
|
||||
};
|
||||
|
||||
#[async_trait]
|
||||
pub trait SpDirectorySigningClient {
|
||||
async fn exectute_service_provider_directory_contract(
|
||||
&self,
|
||||
fee: Option<Fee>,
|
||||
msg: SpExecuteMsg,
|
||||
funds: Vec<Coin>,
|
||||
) -> Result<ExecuteResult, NyxdError>;
|
||||
|
||||
async fn announce_service_provider(
|
||||
&self,
|
||||
nym_address: NymAddress,
|
||||
service_type: ServiceType,
|
||||
deposit: Coin,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.exectute_service_provider_directory_contract(
|
||||
fee,
|
||||
SpExecuteMsg::Announce {
|
||||
nym_address,
|
||||
service_type,
|
||||
},
|
||||
vec![deposit],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn delete_service_provider_by_id(
|
||||
&self,
|
||||
service_id: ServiceId,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.exectute_service_provider_directory_contract(
|
||||
fee,
|
||||
SpExecuteMsg::DeleteId { service_id },
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn delete_service_provider_by_nym_address(
|
||||
&self,
|
||||
nym_address: NymAddress,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.exectute_service_provider_directory_contract(
|
||||
fee,
|
||||
SpExecuteMsg::DeleteNymAddress { nym_address },
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_deposit_required(
|
||||
&self,
|
||||
deposit_required: Coin,
|
||||
fee: Option<Fee>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
self.exectute_service_provider_directory_contract(
|
||||
fee,
|
||||
SpExecuteMsg::UpdateDepositRequired {
|
||||
deposit_required: deposit_required.into(),
|
||||
},
|
||||
vec![],
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C> SpDirectorySigningClient for NyxdClient<C>
|
||||
where
|
||||
C: SigningCosmWasmClient + Sync + Send,
|
||||
{
|
||||
async fn exectute_service_provider_directory_contract(
|
||||
&self,
|
||||
fee: Option<Fee>,
|
||||
msg: SpExecuteMsg,
|
||||
funds: Vec<Coin>,
|
||||
) -> Result<ExecuteResult, NyxdError> {
|
||||
let fee = fee.unwrap_or(Fee::Auto(Some(self.simulated_gas_multiplier)));
|
||||
let memo = msg.default_memo();
|
||||
self.client
|
||||
.execute(
|
||||
self.address(),
|
||||
self.service_provider_contract_address().ok_or(
|
||||
NyxdError::NoContractAddressAvailable(
|
||||
"service provider directory contract".to_string(),
|
||||
),
|
||||
)?,
|
||||
&msg,
|
||||
fee,
|
||||
memo,
|
||||
funds,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -38,3 +38,4 @@ nym-vesting-contract-common = { path = "../cosmwasm-smart-contracts/vesting-cont
|
||||
nym-coconut-bandwidth-contract-common = { path = "../cosmwasm-smart-contracts/coconut-bandwidth-contract" }
|
||||
nym-coconut-dkg-common = { path = "../cosmwasm-smart-contracts/coconut-dkg" }
|
||||
nym-multisig-contract-common = { path = "../cosmwasm-smart-contracts/multisig-contract" }
|
||||
nym-service-provider-directory-common = { path = "../cosmwasm-smart-contracts/service-provider-directory" }
|
||||
|
||||
@@ -5,6 +5,7 @@ use clap::{Args, Subcommand};
|
||||
|
||||
pub mod gateway;
|
||||
pub mod mixnode;
|
||||
pub mod service;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
@@ -19,4 +20,6 @@ pub enum MixnetOperatorsCommands {
|
||||
Mixnode(mixnode::MixnetOperatorsMixnode),
|
||||
/// Manage your gateway
|
||||
Gateway(gateway::MixnetOperatorsGateway),
|
||||
/// Manage your service
|
||||
ServiceProvider(service::MixnetOperatorsService),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use nym_service_provider_directory_common::{Coin, NymAddress, ServiceType};
|
||||
use nym_validator_client::nyxd::traits::SpDirectorySigningClient;
|
||||
|
||||
use crate::context::SigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub nym_address: String,
|
||||
|
||||
/// Deposit to be made to the service provider directory, in curent DENOMINATION (e.g. 'unym')
|
||||
#[clap(long)]
|
||||
pub deposit: u128,
|
||||
}
|
||||
|
||||
pub async fn announce(args: Args, client: SigningClient) {
|
||||
info!("Annoucing service provider");
|
||||
|
||||
let nym_address = NymAddress::Address(args.nym_address);
|
||||
let service_type = ServiceType::NetworkRequester;
|
||||
|
||||
let denom = client.current_chain_details().mix_denom.base.as_str();
|
||||
let deposit = Coin::new(args.deposit, denom);
|
||||
|
||||
let res = client
|
||||
.announce_service_provider(nym_address, service_type, deposit.into(), None)
|
||||
.await
|
||||
.expect("Failed to announce service provider");
|
||||
|
||||
info!("Announced service provider: {res:?}");
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use nym_service_provider_directory_common::ServiceId;
|
||||
use nym_validator_client::nyxd::traits::SpDirectorySigningClient;
|
||||
|
||||
use crate::context::SigningClient;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub id: ServiceId,
|
||||
}
|
||||
|
||||
pub async fn delete(args: Args, client: SigningClient) {
|
||||
info!("Deleting service provider with id {}", args.id);
|
||||
|
||||
let res = client
|
||||
.delete_service_provider_by_id(args.id, None)
|
||||
.await
|
||||
.expect("Failed to delete service provider");
|
||||
|
||||
info!("Deleted: {res:?}");
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
use clap::{Args, Subcommand};
|
||||
|
||||
pub mod announce;
|
||||
pub mod delete;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
pub struct MixnetOperatorsService {
|
||||
#[clap(subcommand)]
|
||||
pub command: MixnetOperatorsServiceCommands,
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
pub enum MixnetOperatorsServiceCommands {
|
||||
/// Announce service provider to the world
|
||||
Announce(announce::Args),
|
||||
/// Delete entry for service provider from the directory
|
||||
Delete(delete::Args),
|
||||
}
|
||||
@@ -5,6 +5,7 @@ use clap::{Args, Subcommand};
|
||||
|
||||
pub mod query_all_gateways;
|
||||
pub mod query_all_mixnodes;
|
||||
pub mod query_all_service_providers;
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
#[clap(args_conflicts_with_subcommands = true, subcommand_required = true)]
|
||||
@@ -19,4 +20,6 @@ pub enum MixnetQueryCommands {
|
||||
Mixnodes(query_all_mixnodes::Args),
|
||||
/// Query gateways
|
||||
Gateways(query_all_gateways::Args),
|
||||
/// Query announced service-providers
|
||||
ServiceProviders(query_all_service_providers::Args),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use clap::Parser;
|
||||
use comfy_table::Table;
|
||||
use nym_validator_client::nym_api::error::NymAPIError;
|
||||
|
||||
use crate::context::QueryClientWithNyxd;
|
||||
use crate::utils::show_error;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(value_parser)]
|
||||
#[clap(help = "Optionally, the service provider to display")]
|
||||
pub nym_address: Option<String>,
|
||||
}
|
||||
|
||||
pub async fn query(args: Args, client: &QueryClientWithNyxd) {
|
||||
match client.nym_api.get_service_providers().await {
|
||||
Ok(res) => {
|
||||
if let Some(nym_address) = args.nym_address {
|
||||
let service = res.iter().find(|service| {
|
||||
service
|
||||
.service
|
||||
.nym_address
|
||||
.to_string()
|
||||
.eq_ignore_ascii_case(&nym_address)
|
||||
});
|
||||
println!(
|
||||
"{}",
|
||||
::serde_json::to_string_pretty(&service).expect("json formatting error")
|
||||
);
|
||||
} else {
|
||||
let mut table = Table::new();
|
||||
|
||||
table.set_header(vec!["Service Id", "Announcer", "Nym Address"]);
|
||||
for service in res {
|
||||
table.add_row(vec![
|
||||
service.service_id.to_string(),
|
||||
service.service.announcer.to_string(),
|
||||
service.service.service_type.to_string(),
|
||||
service.service.nym_address.to_string(),
|
||||
]);
|
||||
}
|
||||
|
||||
println!("The service providers in the directory are:");
|
||||
println!("{table}");
|
||||
}
|
||||
}
|
||||
Err(NymAPIError::NotFound) => {
|
||||
println!("nym-api reports no service provider endpoint available");
|
||||
}
|
||||
Err(e) => show_error(e),
|
||||
}
|
||||
}
|
||||
@@ -7,4 +7,5 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
cosmwasm-std = { workspace = true }
|
||||
schemars = "0.8"
|
||||
serde = { workspace = true, default-features = false, features = ["derive"] }
|
||||
|
||||
@@ -5,3 +5,5 @@ pub mod types;
|
||||
|
||||
// Re-export all types at the top-level
|
||||
pub use types::*;
|
||||
|
||||
pub use cosmwasm_std::{Addr, Coin, Decimal, Fraction};
|
||||
|
||||
@@ -40,6 +40,24 @@ impl ExecuteMsg {
|
||||
pub fn delete_id(service_id: ServiceId) -> Self {
|
||||
ExecuteMsg::DeleteId { service_id }
|
||||
}
|
||||
|
||||
pub fn default_memo(&self) -> String {
|
||||
match self {
|
||||
ExecuteMsg::Announce {
|
||||
nym_address,
|
||||
service_type,
|
||||
} => format!("announcing {nym_address} as type {service_type}"),
|
||||
ExecuteMsg::DeleteId { service_id } => {
|
||||
format!("deleting service with service id {service_id}")
|
||||
}
|
||||
ExecuteMsg::DeleteNymAddress { nym_address } => {
|
||||
format!("deleting service with nym address {nym_address}")
|
||||
}
|
||||
ExecuteMsg::UpdateDepositRequired { deposit_required } => {
|
||||
format!("updating the deposit required to {deposit_required}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use crate::{msg::ExecuteMsg, Service, ServiceId, ServiceInfo};
|
||||
use cosmwasm_std::Coin;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
@@ -9,7 +10,7 @@ pub struct ServiceInfoResponse {
|
||||
pub service: Option<Service>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct ServicesListResponse {
|
||||
pub services: Vec<ServiceInfo>,
|
||||
@@ -26,6 +27,14 @@ impl ServicesListResponse {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[ServiceInfo]> for ServicesListResponse {
|
||||
fn from(services: &[ServiceInfo]) -> Self {
|
||||
Self {
|
||||
services: services.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct PagedServicesListResponse {
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
use cosmwasm_std::{Addr, Coin};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// The directory of services are indexed by [`ServiceId`].
|
||||
pub type ServiceId = u32;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, JsonSchema)]
|
||||
pub struct Service {
|
||||
/// The address of the service.
|
||||
pub nym_address: NymAddress,
|
||||
@@ -21,7 +22,7 @@ pub struct Service {
|
||||
}
|
||||
|
||||
/// The types of addresses supported.
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)]
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NymAddress {
|
||||
/// String representation of a nym address, which is of the form
|
||||
@@ -51,7 +52,7 @@ impl Display for NymAddress {
|
||||
}
|
||||
|
||||
/// The type of services provider supported
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Debug)]
|
||||
#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Debug, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ServiceType {
|
||||
NetworkRequester,
|
||||
@@ -66,7 +67,7 @@ impl std::fmt::Display for ServiceType {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
|
||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct ServiceInfo {
|
||||
pub service_id: ServiceId,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-crypto"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
description = "Crypto library for the nym mixnet"
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
@@ -24,6 +24,7 @@ serde_bytes = { version = "0.11.6", optional = true }
|
||||
serde_crate = { version = "1.0", optional = true, default_features = false, package = "serde" }
|
||||
subtle-encoding = { version = "0.5", features = ["bech32-preview"]}
|
||||
thiserror = "1.0.37"
|
||||
zeroize = { version = "1.5.7", optional = true, features = ["zeroize_derive"] }
|
||||
|
||||
# internal
|
||||
nym-sphinx-types = { path = "../nymsphinx/types", version = "0.2.0" }
|
||||
|
||||
@@ -3,7 +3,12 @@
|
||||
|
||||
use crate::var_names::{DEPRECATED_API_VALIDATOR, DEPRECATED_NYMD_VALIDATOR, NYM_API, NYXD};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{env::var, ops::Not, path::PathBuf};
|
||||
use std::{
|
||||
env::{var, VarError},
|
||||
ffi::OsStr,
|
||||
ops::Not,
|
||||
path::PathBuf,
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
pub mod mainnet;
|
||||
@@ -28,6 +33,7 @@ pub struct NymContracts {
|
||||
pub group_contract_address: Option<String>,
|
||||
pub multisig_contract_address: Option<String>,
|
||||
pub coconut_dkg_contract_address: Option<String>,
|
||||
pub service_provider_directory_contract_address: Option<String>,
|
||||
}
|
||||
|
||||
// I wanted to use the simpler `NetworkDetails` name, but there's a clash
|
||||
@@ -68,6 +74,14 @@ impl NymNetworkDetails {
|
||||
}
|
||||
|
||||
pub fn new_from_env() -> Self {
|
||||
fn get_optional_env<K: AsRef<OsStr>>(env: K) -> Option<String> {
|
||||
match var(env) {
|
||||
Ok(var) => Some(var),
|
||||
Err(VarError::NotPresent) => None,
|
||||
err => panic!("Unable to set: {:?}", err),
|
||||
}
|
||||
}
|
||||
|
||||
NymNetworkDetails::new_empty()
|
||||
.with_bech32_account_prefix(
|
||||
var(var_names::BECH32_PREFIX).expect("bech32 prefix not set"),
|
||||
@@ -117,6 +131,9 @@ impl NymNetworkDetails {
|
||||
.with_coconut_dkg_contract(Some(
|
||||
var(var_names::COCONUT_DKG_CONTRACT_ADDRESS).expect("coconut dkg contract not set"),
|
||||
))
|
||||
.with_service_provider_directory_contract(get_optional_env(
|
||||
var_names::SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn new_mainnet() -> Self {
|
||||
@@ -146,6 +163,9 @@ impl NymNetworkDetails {
|
||||
coconut_dkg_contract_address: parse_optional_str(
|
||||
mainnet::COCONUT_DKG_CONTRACT_ADDRESS,
|
||||
),
|
||||
service_provider_directory_contract_address: parse_optional_str(
|
||||
mainnet::SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS,
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -227,6 +247,15 @@ impl NymNetworkDetails {
|
||||
self.contracts.coconut_dkg_contract_address = contract.map(Into::into);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_service_provider_directory_contract<S: Into<String>>(
|
||||
mut self,
|
||||
contract: Option<S>,
|
||||
) -> Self {
|
||||
self.contracts.service_provider_directory_contract_address = contract.map(Into::into);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||
|
||||
@@ -20,6 +20,8 @@ pub(crate) const COCONUT_BANDWIDTH_CONTRACT_ADDRESS: &str =
|
||||
pub(crate) const GROUP_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str = "n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub(crate) const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
|
||||
"n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0";
|
||||
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
|
||||
hex_literal::hex!("0000000000000000000000000000000000000000");
|
||||
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
|
||||
|
||||
@@ -19,6 +19,8 @@ pub const MULTISIG_CONTRACT_ADDRESS: &str = "MULTISIG_CONTRACT_ADDRESS";
|
||||
pub const COCONUT_DKG_CONTRACT_ADDRESS: &str = "COCONUT_DKG_CONTRACT_ADDRESS";
|
||||
pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS";
|
||||
pub const STATISTICS_SERVICE_DOMAIN_ADDRESS: &str = "STATISTICS_SERVICE_DOMAIN_ADDRESS";
|
||||
pub const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
|
||||
"SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS";
|
||||
pub const NYXD: &str = "NYXD";
|
||||
pub const NYM_API: &str = "NYM_API";
|
||||
|
||||
|
||||
@@ -46,4 +46,7 @@ pub enum NetworkTestingError {
|
||||
|
||||
#[error("received a packet that could not be reconstructed into a full message with a single fragment")]
|
||||
NonReconstructablePacket,
|
||||
|
||||
#[error("the recipient of the test packet was never specified")]
|
||||
UnknownPacketRecipient,
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
pub mod error;
|
||||
pub mod message;
|
||||
pub mod node;
|
||||
pub mod processor;
|
||||
pub mod receiver;
|
||||
pub mod tester;
|
||||
|
||||
|
||||
@@ -2,27 +2,18 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::NetworkTestingError;
|
||||
use crate::MixId;
|
||||
use crate::node::TestableNode;
|
||||
use nym_sphinx::message::NymMessage;
|
||||
use nym_topology::{gateway, mix};
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
#[derive(Serialize, Deserialize, Hash, Clone, Copy)]
|
||||
pub enum NodeType {
|
||||
Mixnode(MixId),
|
||||
Gateway,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Hash, Clone, Copy)]
|
||||
#[derive(Serialize, Deserialize, Clone, Copy)]
|
||||
pub struct Empty;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct TestMessage<T = Empty> {
|
||||
pub encoded_node_identity: String,
|
||||
pub node_owner: String,
|
||||
pub node_type: NodeType,
|
||||
pub tested_node: TestableNode,
|
||||
|
||||
pub msg_id: u32,
|
||||
pub total_msgs: u32,
|
||||
@@ -34,26 +25,72 @@ pub struct TestMessage<T = Empty> {
|
||||
}
|
||||
|
||||
impl<T> TestMessage<T> {
|
||||
pub fn new_mix(node: &mix::Node, msg_id: u32, total_msgs: u32, ext: T) -> Self {
|
||||
pub fn new<N: Into<TestableNode>>(node: N, msg_id: u32, total_msgs: u32, ext: T) -> Self {
|
||||
TestMessage {
|
||||
encoded_node_identity: node.identity_key.to_base58_string(),
|
||||
node_owner: node.owner.clone(),
|
||||
node_type: NodeType::Mixnode(node.mix_id),
|
||||
tested_node: node.into(),
|
||||
msg_id,
|
||||
total_msgs,
|
||||
ext,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_mix(node: &mix::Node, msg_id: u32, total_msgs: u32, ext: T) -> Self {
|
||||
Self::new(node, msg_id, total_msgs, ext)
|
||||
}
|
||||
|
||||
pub fn new_gateway(node: &gateway::Node, msg_id: u32, total_msgs: u32, ext: T) -> Self {
|
||||
TestMessage {
|
||||
encoded_node_identity: node.identity_key.to_base58_string(),
|
||||
node_owner: node.owner.clone(),
|
||||
node_type: NodeType::Gateway,
|
||||
msg_id,
|
||||
total_msgs,
|
||||
ext,
|
||||
Self::new(node, msg_id, total_msgs, ext)
|
||||
}
|
||||
|
||||
pub fn new_serialized<N>(
|
||||
node: N,
|
||||
msg_id: u32,
|
||||
total_msgs: u32,
|
||||
ext: T,
|
||||
) -> Result<Vec<u8>, NetworkTestingError>
|
||||
where
|
||||
N: Into<TestableNode>,
|
||||
T: Serialize,
|
||||
{
|
||||
Self::new(node, msg_id, total_msgs, ext).as_bytes()
|
||||
}
|
||||
|
||||
pub fn new_plaintexts<N>(
|
||||
node: &N,
|
||||
total_msgs: u32,
|
||||
ext: T,
|
||||
) -> Result<Vec<Vec<u8>>, NetworkTestingError>
|
||||
where
|
||||
for<'a> &'a N: Into<TestableNode>,
|
||||
T: Serialize + Clone,
|
||||
{
|
||||
let mut msgs = Vec::with_capacity(total_msgs as usize);
|
||||
for msg_id in 1..=total_msgs {
|
||||
msgs.push(Self::new(node, msg_id, total_msgs, ext.clone()).as_bytes()?)
|
||||
}
|
||||
Ok(msgs)
|
||||
}
|
||||
|
||||
pub fn mix_plaintexts(
|
||||
node: &mix::Node,
|
||||
total_msgs: u32,
|
||||
ext: T,
|
||||
) -> Result<Vec<Vec<u8>>, NetworkTestingError>
|
||||
where
|
||||
T: Serialize + Clone,
|
||||
{
|
||||
Self::new_plaintexts(node, total_msgs, ext)
|
||||
}
|
||||
|
||||
pub fn gateway_plaintexts(
|
||||
node: &gateway::Node,
|
||||
total_msgs: u32,
|
||||
ext: T,
|
||||
) -> Result<Vec<Vec<u8>>, NetworkTestingError>
|
||||
where
|
||||
T: Serialize + Clone,
|
||||
{
|
||||
Self::new_plaintexts(node, total_msgs, ext)
|
||||
}
|
||||
|
||||
pub fn as_json_string(&self) -> Result<String, NetworkTestingError>
|
||||
@@ -88,12 +125,3 @@ impl<T> TestMessage<T> {
|
||||
.map_err(|source| NetworkTestingError::MalformedTestMessageReceived { source })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hash> Hash for TestMessage<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.encoded_node_identity.hash(state);
|
||||
self.node_owner.hash(state);
|
||||
self.node_type.hash(state);
|
||||
self.ext.hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::MixId;
|
||||
use nym_topology::{gateway, mix};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)]
|
||||
pub struct TestableNode {
|
||||
pub encoded_identity: String,
|
||||
pub owner: String,
|
||||
|
||||
#[serde(rename = "type")]
|
||||
pub typ: NodeType,
|
||||
}
|
||||
|
||||
impl TestableNode {
|
||||
pub fn new(encoded_identity: String, owner: String, typ: NodeType) -> Self {
|
||||
TestableNode {
|
||||
encoded_identity,
|
||||
owner,
|
||||
typ,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_mixnode(encoded_identity: String, owner: String, mix_id: MixId) -> Self {
|
||||
TestableNode::new(encoded_identity, owner, NodeType::Mixnode { mix_id })
|
||||
}
|
||||
|
||||
pub fn new_gateway(encoded_identity: String, owner: String) -> Self {
|
||||
TestableNode::new(encoded_identity, owner, NodeType::Gateway)
|
||||
}
|
||||
|
||||
pub fn is_mixnode(&self) -> bool {
|
||||
self.typ.is_mixnode()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a mix::Node> for TestableNode {
|
||||
fn from(value: &'a mix::Node) -> Self {
|
||||
TestableNode {
|
||||
encoded_identity: value.identity_key.to_base58_string(),
|
||||
owner: value.owner.clone(),
|
||||
typ: NodeType::Mixnode {
|
||||
mix_id: value.mix_id,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a gateway::Node> for TestableNode {
|
||||
fn from(value: &'a gateway::Node) -> Self {
|
||||
TestableNode {
|
||||
encoded_identity: value.identity_key.to_base58_string(),
|
||||
owner: value.owner.clone(),
|
||||
typ: NodeType::Gateway,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TestableNode {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{} {} owned by {}",
|
||||
self.typ, self.encoded_identity, self.owner
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Hash, Clone, Copy, Debug, Eq, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NodeType {
|
||||
Mixnode { mix_id: MixId },
|
||||
Gateway,
|
||||
}
|
||||
|
||||
impl NodeType {
|
||||
pub fn is_mixnode(&self) -> bool {
|
||||
matches!(self, NodeType::Mixnode { .. })
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for NodeType {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
NodeType::Mixnode { mix_id } => write!(f, "mixnode (mix_id {mix_id})"),
|
||||
NodeType::Gateway => write!(f, "gateway"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::NetworkTestingError;
|
||||
use crate::TestMessage;
|
||||
use nym_crypto::asymmetric::encryption;
|
||||
use nym_sphinx::acknowledgements::identifier::recover_identifier;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::chunking::fragment::FragmentIdentifier;
|
||||
use nym_sphinx::receiver::{MessageReceiver, SphinxMessageReceiver};
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
|
||||
// simple enum containing aggregated processed results
|
||||
pub enum Received<T> {
|
||||
Message(TestMessage<T>),
|
||||
Ack(FragmentIdentifier),
|
||||
}
|
||||
|
||||
impl<T> From<TestMessage<T>> for Received<T> {
|
||||
fn from(value: TestMessage<T>) -> Self {
|
||||
Received::Message(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<FragmentIdentifier> for Received<T> {
|
||||
fn from(value: FragmentIdentifier) -> Self {
|
||||
Received::Ack(value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestPacketProcessor<T, R: MessageReceiver = SphinxMessageReceiver> {
|
||||
local_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
ack_key: Arc<AckKey>,
|
||||
|
||||
/// Structure responsible for decrypting and recovering plaintext message from received ciphertexts.
|
||||
message_receiver: R,
|
||||
|
||||
_ext_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> TestPacketProcessor<T, SphinxMessageReceiver> {
|
||||
pub fn new_sphinx_processor(
|
||||
local_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
ack_key: Arc<AckKey>,
|
||||
) -> Self {
|
||||
Self::new(local_encryption_keypair, ack_key)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R> TestPacketProcessor<T, R>
|
||||
where
|
||||
R: MessageReceiver,
|
||||
{
|
||||
pub fn new(local_encryption_keypair: Arc<encryption::KeyPair>, ack_key: Arc<AckKey>) -> Self {
|
||||
TestPacketProcessor {
|
||||
local_encryption_keypair,
|
||||
ack_key,
|
||||
message_receiver: R::new(),
|
||||
_ext_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_mixnet_message(
|
||||
&mut self,
|
||||
mut raw_message: Vec<u8>,
|
||||
) -> Result<TestMessage<T>, NetworkTestingError>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let plaintext = self
|
||||
.message_receiver
|
||||
.recover_plaintext_from_regular_packet(
|
||||
self.local_encryption_keypair.private_key(),
|
||||
&mut raw_message,
|
||||
)?;
|
||||
let fragment = self.message_receiver.recover_fragment(plaintext)?;
|
||||
|
||||
// test messages must consist of a single fragment
|
||||
let (serialized, _) = self
|
||||
.message_receiver
|
||||
.insert_new_fragment(fragment)?
|
||||
.ok_or(NetworkTestingError::NonReconstructablePacket)?;
|
||||
|
||||
TestMessage::try_recover(serialized)
|
||||
}
|
||||
|
||||
pub fn process_ack(
|
||||
&mut self,
|
||||
raw_ack: Vec<u8>,
|
||||
) -> Result<FragmentIdentifier, NetworkTestingError> {
|
||||
let serialized_ack = recover_identifier(&self.ack_key, &raw_ack)
|
||||
.ok_or(NetworkTestingError::UnrecoverableAck)?;
|
||||
|
||||
FragmentIdentifier::try_from_bytes(serialized_ack)
|
||||
.map_err(|source| NetworkTestingError::MalformedAckIdentifier { source })
|
||||
}
|
||||
}
|
||||
@@ -2,114 +2,95 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::error::NetworkTestingError;
|
||||
use crate::processor::{Received, TestPacketProcessor};
|
||||
use crate::{log_err, log_info, log_warn};
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use nym_crypto::asymmetric::encryption;
|
||||
use nym_sphinx::message::NymMessage;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::receiver::{MessageReceiver, SphinxMessageReceiver};
|
||||
use nym_sphinx::{
|
||||
acknowledgements::{identifier::recover_identifier, AckKey},
|
||||
chunking::fragment::FragmentIdentifier,
|
||||
};
|
||||
use nym_task::TaskClient;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub type ReceivedSender = mpsc::UnboundedSender<Received>;
|
||||
pub type ReceivedReceiver = mpsc::UnboundedReceiver<Received>;
|
||||
|
||||
// simple enum containing aggregated processed results
|
||||
pub enum Received {
|
||||
Message(NymMessage),
|
||||
Ack(FragmentIdentifier),
|
||||
}
|
||||
|
||||
impl From<NymMessage> for Received {
|
||||
fn from(value: NymMessage) -> Self {
|
||||
Received::Message(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FragmentIdentifier> for Received {
|
||||
fn from(value: FragmentIdentifier) -> Self {
|
||||
Received::Ack(value)
|
||||
}
|
||||
}
|
||||
pub type ReceivedSender<T> = mpsc::UnboundedSender<Received<T>>;
|
||||
pub type ReceivedReceiver<T> = mpsc::UnboundedReceiver<Received<T>>;
|
||||
|
||||
// the 'Simple' bit comes from the fact that it expects all received messages to consist of a single `Fragment`
|
||||
pub struct SimpleMessageReceiver<R: MessageReceiver = SphinxMessageReceiver> {
|
||||
local_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
|
||||
ack_key: Arc<AckKey>,
|
||||
|
||||
/// Structure responsible for decrypting and recovering plaintext message from received ciphertexts.
|
||||
message_receiver: R,
|
||||
pub struct SimpleMessageReceiver<T, R: MessageReceiver = SphinxMessageReceiver> {
|
||||
message_processor: TestPacketProcessor<T, R>,
|
||||
|
||||
mixnet_message_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
|
||||
acks_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
|
||||
|
||||
received_sender: ReceivedSender,
|
||||
received_sender: ReceivedSender<T>,
|
||||
shutdown: TaskClient,
|
||||
}
|
||||
|
||||
impl SimpleMessageReceiver<SphinxMessageReceiver> {
|
||||
impl<T> SimpleMessageReceiver<T, SphinxMessageReceiver> {
|
||||
pub fn new_sphinx_receiver(
|
||||
local_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
ack_key: Arc<AckKey>,
|
||||
mixnet_message_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
|
||||
acks_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
|
||||
received_sender: ReceivedSender,
|
||||
received_sender: ReceivedSender<T>,
|
||||
shutdown: TaskClient,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
local_encryption_keypair,
|
||||
ack_key,
|
||||
mixnet_message_receiver,
|
||||
acks_receiver,
|
||||
received_sender,
|
||||
shutdown,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R: MessageReceiver> SimpleMessageReceiver<T, R> {
|
||||
pub fn new(
|
||||
local_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
ack_key: Arc<AckKey>,
|
||||
mixnet_message_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
|
||||
acks_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
|
||||
received_sender: ReceivedSender<T>,
|
||||
shutdown: TaskClient,
|
||||
) -> Self {
|
||||
SimpleMessageReceiver {
|
||||
local_encryption_keypair,
|
||||
ack_key,
|
||||
message_receiver: SphinxMessageReceiver::new(),
|
||||
message_processor: TestPacketProcessor::new(local_encryption_keypair, ack_key),
|
||||
mixnet_message_receiver,
|
||||
acks_receiver,
|
||||
received_sender,
|
||||
shutdown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: MessageReceiver> SimpleMessageReceiver<R> {
|
||||
fn forward_received<T: Into<Received>>(&self, received: T) {
|
||||
fn forward_received<U: Into<Received<T>>>(&self, received: U) {
|
||||
// TODO: remove the unwrap once/if we do graceful shutdowns here
|
||||
self.received_sender
|
||||
.unbounded_send(received.into())
|
||||
.expect("ReceivedReceiver has stopped receiving");
|
||||
}
|
||||
|
||||
fn on_mixnet_message(&mut self, mut raw_message: Vec<u8>) -> Result<(), NetworkTestingError> {
|
||||
let plaintext = self
|
||||
.message_receiver
|
||||
.recover_plaintext_from_regular_packet(
|
||||
self.local_encryption_keypair.private_key(),
|
||||
&mut raw_message,
|
||||
)?;
|
||||
let fragment = self.message_receiver.recover_fragment(plaintext)?;
|
||||
let (recovered, _) = self
|
||||
.message_receiver
|
||||
.insert_new_fragment(fragment)?
|
||||
.ok_or(NetworkTestingError::NonReconstructablePacket)?; // by definition of this receiver, the message must consist of a single fragment
|
||||
|
||||
fn on_mixnet_message(&mut self, raw_message: Vec<u8>) -> Result<(), NetworkTestingError>
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let recovered = self.message_processor.process_mixnet_message(raw_message)?;
|
||||
self.forward_received(recovered);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_ack(&mut self, raw_ack: Vec<u8>) -> Result<(), NetworkTestingError> {
|
||||
let serialized_ack = recover_identifier(&self.ack_key, &raw_ack)
|
||||
.ok_or(NetworkTestingError::UnrecoverableAck)?;
|
||||
|
||||
let frag_id = FragmentIdentifier::try_from_bytes(serialized_ack)
|
||||
.map_err(|source| NetworkTestingError::MalformedAckIdentifier { source })?;
|
||||
|
||||
let frag_id = self.message_processor.process_ack(raw_ack)?;
|
||||
self.forward_received(frag_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn run(&mut self) {
|
||||
pub async fn run(&mut self)
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
while !self.shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
|
||||
@@ -21,7 +21,10 @@ pub struct NodeTester<R> {
|
||||
|
||||
base_topology: NymTopology,
|
||||
|
||||
recipient: Recipient,
|
||||
/// Generally test packets are designed to be sent from ourselves to ourselves,
|
||||
/// However, one might want to customise this behaviour.
|
||||
/// In that case an explicit `Recipient` has to be provided when constructing test packets.
|
||||
self_address: Option<Recipient>,
|
||||
|
||||
packet_size: PacketSize,
|
||||
|
||||
@@ -47,7 +50,7 @@ where
|
||||
pub fn new(
|
||||
rng: R,
|
||||
base_topology: NymTopology,
|
||||
recipient: Recipient,
|
||||
self_address: Option<Recipient>,
|
||||
packet_size: PacketSize,
|
||||
average_packet_delay: Duration,
|
||||
average_ack_delay: Duration,
|
||||
@@ -56,7 +59,7 @@ where
|
||||
Self {
|
||||
rng,
|
||||
base_topology,
|
||||
recipient,
|
||||
self_address,
|
||||
packet_size,
|
||||
average_packet_delay,
|
||||
average_ack_delay,
|
||||
@@ -89,7 +92,7 @@ where
|
||||
mix: &mix::Node,
|
||||
test_packets: u32,
|
||||
) -> Result<Vec<PreparedFragment>, NetworkTestingError> {
|
||||
self.mixnode_test_packets(mix, Empty, test_packets)
|
||||
self.mixnode_test_packets(mix, Empty, test_packets, None)
|
||||
}
|
||||
|
||||
pub fn mixnode_test_packets<T>(
|
||||
@@ -97,6 +100,7 @@ where
|
||||
mix: &mix::Node,
|
||||
msg_ext: T,
|
||||
test_packets: u32,
|
||||
custom_recipient: Option<Recipient>,
|
||||
) -> Result<Vec<PreparedFragment>, NetworkTestingError>
|
||||
where
|
||||
T: Serialize + Clone,
|
||||
@@ -104,9 +108,35 @@ where
|
||||
let ephemeral_topology = self.testable_mix_topology(mix);
|
||||
|
||||
let mut packets = Vec::with_capacity(test_packets as usize);
|
||||
for i in 1..=test_packets {
|
||||
let msg = TestMessage::new_mix(mix, i, test_packets, msg_ext.clone());
|
||||
packets.push(self.create_test_packet(&msg, &ephemeral_topology)?);
|
||||
for plaintext in TestMessage::mix_plaintexts(mix, test_packets, msg_ext)? {
|
||||
packets.push(self.wrap_plaintext_data(
|
||||
plaintext,
|
||||
&ephemeral_topology,
|
||||
custom_recipient,
|
||||
)?);
|
||||
}
|
||||
|
||||
Ok(packets)
|
||||
}
|
||||
|
||||
pub fn mixnodes_test_packets<T>(
|
||||
&mut self,
|
||||
nodes: &[mix::Node],
|
||||
msg_ext: T,
|
||||
test_packets: u32,
|
||||
custom_recipient: Option<Recipient>,
|
||||
) -> Result<Vec<PreparedFragment>, NetworkTestingError>
|
||||
where
|
||||
T: Serialize + Clone,
|
||||
{
|
||||
let mut packets = Vec::new();
|
||||
for node in nodes {
|
||||
packets.append(&mut self.mixnode_test_packets(
|
||||
node,
|
||||
msg_ext.clone(),
|
||||
test_packets,
|
||||
custom_recipient,
|
||||
)?)
|
||||
}
|
||||
|
||||
Ok(packets)
|
||||
@@ -117,6 +147,7 @@ where
|
||||
mix_id: MixId,
|
||||
msg_ext: T,
|
||||
test_packets: u32,
|
||||
custom_recipient: Option<Recipient>,
|
||||
) -> Result<Vec<PreparedFragment>, NetworkTestingError>
|
||||
where
|
||||
T: Serialize + Clone,
|
||||
@@ -125,7 +156,7 @@ where
|
||||
return Err(NetworkTestingError::NonExistentMixnode {mix_id})
|
||||
};
|
||||
|
||||
self.mixnode_test_packets(&node.clone(), msg_ext, test_packets)
|
||||
self.mixnode_test_packets(&node.clone(), msg_ext, test_packets, custom_recipient)
|
||||
}
|
||||
|
||||
pub fn existing_identity_mixnode_test_packets<T>(
|
||||
@@ -133,6 +164,7 @@ where
|
||||
encoded_mix_identity: String,
|
||||
msg_ext: T,
|
||||
test_packets: u32,
|
||||
custom_recipient: Option<Recipient>,
|
||||
) -> Result<Vec<PreparedFragment>, NetworkTestingError>
|
||||
where
|
||||
T: Serialize + Clone,
|
||||
@@ -141,19 +173,57 @@ where
|
||||
return Err(NetworkTestingError::NonExistentMixnodeIdentity { mix_identity: encoded_mix_identity })
|
||||
};
|
||||
|
||||
self.mixnode_test_packets(&node.clone(), msg_ext, test_packets)
|
||||
self.mixnode_test_packets(&node.clone(), msg_ext, test_packets, custom_recipient)
|
||||
}
|
||||
|
||||
pub fn create_test_packet<T>(
|
||||
pub fn gateway_test_packets<T>(
|
||||
&mut self,
|
||||
message: &TestMessage<T>,
|
||||
topology: &NymTopology,
|
||||
) -> Result<PreparedFragment, NetworkTestingError>
|
||||
gateway: &gateway::Node,
|
||||
msg_ext: T,
|
||||
test_packets: u32,
|
||||
custom_recipient: Option<Recipient>,
|
||||
) -> Result<Vec<PreparedFragment>, NetworkTestingError>
|
||||
where
|
||||
T: Serialize,
|
||||
T: Serialize + Clone,
|
||||
{
|
||||
let serialized = message.as_bytes()?;
|
||||
let message = NymMessage::new_plain(serialized);
|
||||
let ephemeral_topology = self.testable_gateway_topology(gateway);
|
||||
|
||||
let mut packets = Vec::with_capacity(test_packets as usize);
|
||||
for plaintext in TestMessage::gateway_plaintexts(gateway, test_packets, msg_ext)? {
|
||||
packets.push(self.wrap_plaintext_data(
|
||||
plaintext,
|
||||
&ephemeral_topology,
|
||||
custom_recipient,
|
||||
)?);
|
||||
}
|
||||
|
||||
Ok(packets)
|
||||
}
|
||||
|
||||
pub fn existing_gateway_test_packets<T>(
|
||||
&mut self,
|
||||
encoded_gateway_identity: String,
|
||||
msg_ext: T,
|
||||
test_packets: u32,
|
||||
custom_recipient: Option<Recipient>,
|
||||
) -> Result<Vec<PreparedFragment>, NetworkTestingError>
|
||||
where
|
||||
T: Serialize + Clone,
|
||||
{
|
||||
let Some(node) = self.base_topology.find_gateway(&encoded_gateway_identity) else {
|
||||
return Err(NetworkTestingError::NonExistentGateway { gateway_identity: encoded_gateway_identity })
|
||||
};
|
||||
|
||||
self.gateway_test_packets(&node.clone(), msg_ext, test_packets, custom_recipient)
|
||||
}
|
||||
|
||||
pub fn wrap_plaintext_data(
|
||||
&mut self,
|
||||
plaintext: Vec<u8>,
|
||||
topology: &NymTopology,
|
||||
custom_recipient: Option<Recipient>,
|
||||
) -> Result<PreparedFragment, NetworkTestingError> {
|
||||
let message = NymMessage::new_plain(plaintext);
|
||||
|
||||
let mut fragments = self.pad_and_split_message(message, self.packet_size);
|
||||
|
||||
@@ -165,13 +235,29 @@ where
|
||||
// we would have returned the error when checking for its length
|
||||
let fragment = fragments.pop().unwrap();
|
||||
|
||||
// the packet is designed to be sent from ourselves to ourselves
|
||||
let address = self.recipient;
|
||||
// either `self_address` or `custom_recipient` has to be specified.
|
||||
let address = custom_recipient.unwrap_or(
|
||||
self.self_address
|
||||
.ok_or(NetworkTestingError::UnknownPacketRecipient)?,
|
||||
);
|
||||
|
||||
// TODO: can we avoid this arc clone?
|
||||
let ack_key = Arc::clone(&self.ack_key);
|
||||
Ok(self.prepare_chunk_for_sending(fragment, topology, &ack_key, &address, &address)?)
|
||||
}
|
||||
|
||||
pub fn create_test_packet<T>(
|
||||
&mut self,
|
||||
message: &TestMessage<T>,
|
||||
topology: &NymTopology,
|
||||
custom_recipient: Option<Recipient>,
|
||||
) -> Result<PreparedFragment, NetworkTestingError>
|
||||
where
|
||||
T: Serialize,
|
||||
{
|
||||
let serialized = message.as_bytes()?;
|
||||
self.wrap_plaintext_data(serialized, topology, custom_recipient)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: CryptoRng + Rng> FragmentPreparer for NodeTester<R> {
|
||||
|
||||
@@ -20,11 +20,12 @@ nym-sphinx-chunking = { path = "chunking" }
|
||||
nym-sphinx-cover = { path = "cover" }
|
||||
nym-sphinx-forwarding = { path = "forwarding" }
|
||||
nym-sphinx-params = { path = "params" }
|
||||
nym-sphinx-routing = { path = "routing" }
|
||||
nym-sphinx-types = { path = "types" }
|
||||
|
||||
# those dependencies are due to intriducing preparer and receiver. Perpaphs that indicates they should be moved
|
||||
# to separate crate?
|
||||
nym-crypto = { path = "../crypto", version = "0.2.0" }
|
||||
nym-crypto = { path = "../crypto", version = "0.3.0" }
|
||||
nym-topology = { path = "../topology" }
|
||||
|
||||
# outfox
|
||||
@@ -32,7 +33,7 @@ nym-outfox = { path = "../../nym-outfox" }
|
||||
|
||||
[dev-dependencies]
|
||||
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
|
||||
nym-crypto = { path = "../crypto", version = "0.2.0", features = ["asymmetric"] }
|
||||
nym-crypto = { path = "../crypto", version = "0.3.0", features = ["asymmetric"] }
|
||||
|
||||
# do not include this when compiling into wasm as it somehow when combined together with reqwest, it will require
|
||||
# net2 via tokio-util -> tokio -> mio -> net2
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "nym-sphinx-routing"
|
||||
version = "0.1.0"
|
||||
description = "Sphinx packet routing as Nym mix packets"
|
||||
edition = { workspace = true }
|
||||
authors = { workspace = true }
|
||||
license = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
thiserror = { workspace = true }
|
||||
|
||||
nym-sphinx-addressing = { path = "../addressing" }
|
||||
nym-sphinx-types = { path = "../types" }
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_sphinx_addressing::clients::Recipient;
|
||||
use nym_sphinx_types::Node;
|
||||
use thiserror::Error;
|
||||
|
||||
pub trait SphinxRouteMaker {
|
||||
type Error;
|
||||
|
||||
fn sphinx_route(&mut self, hops: u8, destination: &Recipient)
|
||||
-> Result<Vec<Node>, Self::Error>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Error, Clone, Copy)]
|
||||
#[error("the route vector contains {available} nodes while {requested} hops are required")]
|
||||
pub struct InvalidNumberOfHops {
|
||||
available: usize,
|
||||
requested: u8,
|
||||
}
|
||||
|
||||
// if one wants to provide a hardcoded route, they can
|
||||
impl SphinxRouteMaker for Vec<Node> {
|
||||
type Error = InvalidNumberOfHops;
|
||||
|
||||
fn sphinx_route(
|
||||
&mut self,
|
||||
hops: u8,
|
||||
_destination: &Recipient,
|
||||
) -> Result<Vec<Node>, InvalidNumberOfHops> {
|
||||
// it's the responsibility of the caller to ensure the hardcoded route has correct number of hops
|
||||
// and that it's final hop include the recipient's gateway.
|
||||
|
||||
if self.len() != hops as usize {
|
||||
Err(InvalidNumberOfHops {
|
||||
available: self.len(),
|
||||
requested: hops,
|
||||
})
|
||||
} else {
|
||||
Ok(self.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,12 @@ pub use nym_sphinx_anonymous_replies as anonymous_replies;
|
||||
pub use nym_sphinx_chunking as chunking;
|
||||
pub use nym_sphinx_cover as cover;
|
||||
pub use nym_sphinx_forwarding as forwarding;
|
||||
pub use nym_sphinx_params as params;
|
||||
pub use nym_sphinx_routing as routing;
|
||||
pub use nym_sphinx_types::*;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use nym_sphinx_framing as framing;
|
||||
pub use nym_sphinx_params as params;
|
||||
pub use nym_sphinx_types::*;
|
||||
|
||||
// TEMP UNTIL FURTHER REFACTORING
|
||||
pub use preparer::payload::NymsphinxPayloadBuilder;
|
||||
|
||||
@@ -38,6 +38,12 @@ pub struct PreparedFragment {
|
||||
pub fragment_identifier: FragmentIdentifier,
|
||||
}
|
||||
|
||||
impl From<PreparedFragment> for MixPacket {
|
||||
fn from(value: PreparedFragment) -> Self {
|
||||
value.mix_packet
|
||||
}
|
||||
}
|
||||
|
||||
// this is extracted into a trait with default implementation to remove duplicate code
|
||||
// (which we REALLY want to avoid with crypto)
|
||||
pub trait FragmentPreparer {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
[package]
|
||||
name = "nym-store-cipher"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
aes-gcm = { version = "0.10.1" }
|
||||
argon2 = { version = "0.5.0" }
|
||||
rand = "0.8.5"
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
thiserror = { workspace = true }
|
||||
zeroize = { version = "1.6.0", features = ["zeroize_derive"] }
|
||||
|
||||
[target.'cfg(target_env = "wasm32-unknown-unknown")'.dependencies]
|
||||
getrandom = { version = "0.2", features = ["js"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
json = ["serde_json"]
|
||||
@@ -0,0 +1,279 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use aes_gcm::aead::{Aead, Nonce};
|
||||
use aes_gcm::{AeadCore, AeadInPlace, Key, KeyInit, KeySizeUser};
|
||||
use rand::{thread_rng, CryptoRng, Fill, RngCore};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_helpers::{argon2_algorithm_helper, argon2_params_helper, argon2_version_helper};
|
||||
use thiserror::Error;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
pub use aes_gcm::Aes256Gcm;
|
||||
pub use argon2::{Algorithm, Argon2, Params, Version};
|
||||
|
||||
mod serde_helpers;
|
||||
|
||||
pub const CURRENT_VERSION: u8 = 1;
|
||||
pub const ARGON2_SALT_SIZE: usize = 16;
|
||||
pub const AES256GCM_NONCE_SIZE: usize = 12;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("failed to encrypt/decrypt provided data: {cause}")]
|
||||
AesFailure { cause: aes_gcm::Error },
|
||||
|
||||
#[error("failed to expand the passphrase: {cause}")]
|
||||
Argon2Failure { cause: argon2::Error },
|
||||
|
||||
#[cfg(feature = "json")]
|
||||
#[error("failed to serialize/deserialize JSON: {source}")]
|
||||
SerdeJsonFailure {
|
||||
#[from]
|
||||
source: serde_json::Error,
|
||||
},
|
||||
|
||||
#[error("failed to generate random bytes: {source}")]
|
||||
RandomError {
|
||||
#[from]
|
||||
source: rand::Error,
|
||||
},
|
||||
|
||||
#[error("the received ciphertext was encrypted with different store version ({received}). The current version is {CURRENT_VERSION}")]
|
||||
VersionMismatch { received: u8 },
|
||||
}
|
||||
|
||||
// it's weird that this couldn't be auto-derived with a `#[from]`...
|
||||
impl From<aes_gcm::Error> for Error {
|
||||
fn from(cause: aes_gcm::Error) -> Self {
|
||||
Error::AesFailure { cause }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<argon2::Error> for Error {
|
||||
fn from(cause: argon2::Error) -> Self {
|
||||
Error::Argon2Failure { cause }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum KdfInfo {
|
||||
Argon2 {
|
||||
/// The Argon2 parameters that were used when deriving the store key.
|
||||
#[serde(with = "argon2_params_helper")]
|
||||
params: Params,
|
||||
|
||||
/// The specific Argon2 algorithm variant used when deriving the store key.
|
||||
#[serde(with = "argon2_algorithm_helper")]
|
||||
algorithm: Algorithm,
|
||||
|
||||
/// The specific version of the Argon2 algorithm used when deriving the store key.
|
||||
#[serde(with = "argon2_version_helper")]
|
||||
version: Version,
|
||||
|
||||
/// The salt that was used when the passphrase was expanded into a store key.
|
||||
kdf_salt: [u8; ARGON2_SALT_SIZE],
|
||||
},
|
||||
}
|
||||
|
||||
impl KdfInfo {
|
||||
pub fn expand_key<C>(&self, passphrase: &[u8]) -> Result<Key<C>, Error>
|
||||
where
|
||||
C: KeySizeUser,
|
||||
{
|
||||
match self {
|
||||
KdfInfo::Argon2 {
|
||||
params,
|
||||
algorithm,
|
||||
version,
|
||||
kdf_salt,
|
||||
} => argon2_derive_cipher_key::<C>(
|
||||
passphrase,
|
||||
kdf_salt,
|
||||
&[],
|
||||
params.clone(),
|
||||
*algorithm,
|
||||
*version,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_default_settings() -> Result<Self, Error> {
|
||||
let kdf_salt = Self::random_salt()?;
|
||||
Ok(KdfInfo::Argon2 {
|
||||
params: Default::default(),
|
||||
algorithm: Default::default(),
|
||||
version: Default::default(),
|
||||
kdf_salt,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn random_salt() -> Result<[u8; ARGON2_SALT_SIZE], Error> {
|
||||
let mut rng = thread_rng();
|
||||
Self::random_salt_with_rng(&mut rng)
|
||||
}
|
||||
|
||||
pub fn random_salt_with_rng<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
) -> Result<[u8; ARGON2_SALT_SIZE], Error> {
|
||||
let mut salt = [0u8; ARGON2_SALT_SIZE];
|
||||
salt.try_fill(rng)?;
|
||||
Ok(salt)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||
pub struct StoreCipher<C = Aes256Gcm>
|
||||
where
|
||||
C: KeySizeUser,
|
||||
{
|
||||
key: Key<C>,
|
||||
}
|
||||
|
||||
impl<C: KeySizeUser + KeyInit> StoreCipher<C>
|
||||
where
|
||||
C: KeySizeUser + KeyInit,
|
||||
{
|
||||
pub fn new(passphrase: &[u8], kdf_info: KdfInfo) -> Result<Self, Error> {
|
||||
let key = kdf_info.expand_key::<C>(passphrase)?;
|
||||
Ok(StoreCipher { key })
|
||||
}
|
||||
|
||||
pub fn new_with_default_kdf(passphrase: &[u8]) -> Result<Self, Error> {
|
||||
let kdf_info = KdfInfo::new_with_default_settings()?;
|
||||
Self::new(passphrase, kdf_info)
|
||||
}
|
||||
|
||||
#[cfg(feature = "json")]
|
||||
pub fn encrypt_json_value<T: Serialize>(&self, data: &T) -> Result<EncryptedData, Error>
|
||||
where
|
||||
C: AeadInPlace,
|
||||
{
|
||||
let raw = serde_json::to_vec(data)?;
|
||||
self.encrypt_data(raw)
|
||||
}
|
||||
|
||||
// Unless you know what you're doing, use `Self::encrypt_data` instead.
|
||||
// As the caller of this method needs to make sure to correctly dispose of the original plaintext.
|
||||
pub fn encrypt_data_ref(&self, data: &[u8]) -> Result<EncryptedData, Error>
|
||||
where
|
||||
C: Aead,
|
||||
{
|
||||
let nonce = Self::random_nonce()?;
|
||||
|
||||
let cipher = C::new(&self.key);
|
||||
let ciphertext = cipher.encrypt(&nonce, data)?;
|
||||
|
||||
Ok(EncryptedData {
|
||||
version: CURRENT_VERSION,
|
||||
ciphertext,
|
||||
nonce: nonce.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn encrypt_data(&self, mut data: Vec<u8>) -> Result<EncryptedData, Error>
|
||||
where
|
||||
C: AeadInPlace,
|
||||
{
|
||||
let nonce = Self::random_nonce()?;
|
||||
|
||||
let cipher = C::new(&self.key);
|
||||
cipher.encrypt_in_place(&nonce, &[], &mut data)?;
|
||||
|
||||
Ok(EncryptedData {
|
||||
version: CURRENT_VERSION,
|
||||
ciphertext: data,
|
||||
nonce: nonce.to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "json")]
|
||||
pub fn decrypt_json_value<T: serde::de::DeserializeOwned>(
|
||||
&self,
|
||||
data: EncryptedData,
|
||||
) -> Result<T, Error>
|
||||
where
|
||||
C: AeadInPlace,
|
||||
{
|
||||
let mut plaintext = self.decrypt_data(data)?;
|
||||
|
||||
// DO NOT USE `?` here!!
|
||||
// We need to make sure to zeroize the plaintext even if deserialization fails!!
|
||||
let value = serde_json::from_slice(&plaintext);
|
||||
|
||||
plaintext.zeroize();
|
||||
Ok(value?)
|
||||
}
|
||||
|
||||
pub fn decrypt_data_unchecked(&self, data: EncryptedData) -> Result<Vec<u8>, Error>
|
||||
where
|
||||
C: Aead,
|
||||
{
|
||||
let cipher = C::new(&self.key);
|
||||
let plaintext = cipher.decrypt(
|
||||
Nonce::<C>::from_slice(&data.nonce),
|
||||
data.ciphertext.as_ref(),
|
||||
)?;
|
||||
Ok(plaintext)
|
||||
}
|
||||
|
||||
pub fn decrypt_data(&self, data: EncryptedData) -> Result<Vec<u8>, Error>
|
||||
where
|
||||
C: Aead,
|
||||
{
|
||||
if data.version != CURRENT_VERSION {
|
||||
return Err(Error::VersionMismatch {
|
||||
received: data.version,
|
||||
});
|
||||
}
|
||||
|
||||
self.decrypt_data_unchecked(data)
|
||||
}
|
||||
|
||||
pub fn random_nonce() -> Result<Nonce<C>, Error>
|
||||
where
|
||||
C: AeadCore,
|
||||
{
|
||||
let mut rng = thread_rng();
|
||||
Self::random_nonce_with_rng(&mut rng)
|
||||
}
|
||||
|
||||
pub fn random_nonce_with_rng<R: RngCore + CryptoRng>(rng: &mut R) -> Result<Nonce<C>, Error>
|
||||
where
|
||||
C: AeadCore,
|
||||
{
|
||||
let mut nonce = Nonce::<C>::default();
|
||||
nonce.try_fill(rng)?;
|
||||
Ok(nonce)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
|
||||
pub struct EncryptedData {
|
||||
pub version: u8,
|
||||
pub ciphertext: Vec<u8>,
|
||||
pub nonce: Vec<u8>,
|
||||
}
|
||||
|
||||
pub fn argon2_derive_cipher_key<C>(
|
||||
passphrase: &[u8],
|
||||
salt: &[u8],
|
||||
pepper: &[u8],
|
||||
params: Params,
|
||||
algorithm: Algorithm,
|
||||
version: Version,
|
||||
) -> Result<Key<C>, Error>
|
||||
where
|
||||
C: KeySizeUser,
|
||||
{
|
||||
let argon2 = if pepper.is_empty() {
|
||||
Argon2::new(algorithm, version, params)
|
||||
} else {
|
||||
Argon2::new_with_secret(pepper, algorithm, version, params)?
|
||||
};
|
||||
|
||||
let mut key = Key::<C>::default();
|
||||
argon2.hash_password_into(passphrase, salt, &mut key)?;
|
||||
|
||||
Ok(key)
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) mod argon2_params_helper {
|
||||
use argon2::Params;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
/// Refer to [argon2::Params] for details.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct Argon2Params {
|
||||
m_cost: u32,
|
||||
t_cost: u32,
|
||||
p_cost: u32,
|
||||
output_len: Option<usize>,
|
||||
// Note: `keyid` and `data` are not longer part of the argon2 standard
|
||||
// (see: <https://github.com/P-H-C/phc-winner-argon2/pull/173>), and should
|
||||
// not be used for any non-legacy work.
|
||||
// So we're explicitly skipping them for serialization
|
||||
}
|
||||
|
||||
impl From<&Params> for Argon2Params {
|
||||
fn from(value: &Params) -> Self {
|
||||
Argon2Params {
|
||||
m_cost: value.m_cost(),
|
||||
t_cost: value.t_cost(),
|
||||
p_cost: value.p_cost(),
|
||||
output_len: value.output_len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Argon2Params> for Params {
|
||||
type Error = argon2::Error;
|
||||
|
||||
fn try_from(value: Argon2Params) -> Result<Self, Self::Error> {
|
||||
Params::new(value.m_cost, value.t_cost, value.p_cost, value.output_len)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize<S: Serializer>(params: &Params, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
Argon2Params::from(params).serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Params, D::Error> {
|
||||
<Argon2Params>::deserialize(deserializer)?
|
||||
.try_into()
|
||||
.map_err(serde::de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod argon2_algorithm_helper {
|
||||
use argon2::Algorithm;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
/// Refer to [argon2::Algorithm] for details.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
enum Argon2Algorithm {
|
||||
Argon2d = 0,
|
||||
Argon2i = 1,
|
||||
Argon2id = 2,
|
||||
}
|
||||
|
||||
impl From<Algorithm> for Argon2Algorithm {
|
||||
fn from(value: Algorithm) -> Self {
|
||||
match value {
|
||||
Algorithm::Argon2d => Argon2Algorithm::Argon2d,
|
||||
Algorithm::Argon2i => Argon2Algorithm::Argon2i,
|
||||
Algorithm::Argon2id => Argon2Algorithm::Argon2id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Argon2Algorithm> for Algorithm {
|
||||
fn from(value: Argon2Algorithm) -> Self {
|
||||
match value {
|
||||
Argon2Algorithm::Argon2d => Algorithm::Argon2d,
|
||||
Argon2Algorithm::Argon2i => Algorithm::Argon2i,
|
||||
Argon2Algorithm::Argon2id => Algorithm::Argon2id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize<S: Serializer>(
|
||||
algorithm: &Algorithm,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
Argon2Algorithm::from(*algorithm).serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Algorithm, D::Error> {
|
||||
<Argon2Algorithm>::deserialize(deserializer).map(From::from)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) mod argon2_version_helper {
|
||||
use argon2::Version;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
/// Refer to [argon2::Version] for details.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[repr(u32)]
|
||||
enum Argon2Version {
|
||||
V0x10 = 0x10,
|
||||
V0x13 = 0x13,
|
||||
}
|
||||
|
||||
impl From<Version> for Argon2Version {
|
||||
fn from(value: Version) -> Self {
|
||||
match value {
|
||||
Version::V0x10 => Argon2Version::V0x10,
|
||||
Version::V0x13 => Argon2Version::V0x13,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Argon2Version> for Version {
|
||||
fn from(value: Argon2Version) -> Self {
|
||||
match value {
|
||||
Argon2Version::V0x10 => Version::V0x10,
|
||||
Argon2Version::V0x13 => Version::V0x13,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize<S: Serializer>(algorithm: &Version, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
Argon2Version::from(*algorithm).serialize(serializer)
|
||||
}
|
||||
|
||||
pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<Version, D::Error> {
|
||||
<Argon2Version>::deserialize(deserializer).map(From::from)
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ nym-crypto = { path = "../crypto" }
|
||||
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
|
||||
nym-sphinx-addressing = { path = "../nymsphinx/addressing" }
|
||||
nym-sphinx-types = { path = "../nymsphinx/types" }
|
||||
nym-sphinx-routing = { path = "../nymsphinx/routing" }
|
||||
nym-bin-common = { path = "../bin-common" }
|
||||
|
||||
[features]
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::MixLayer;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum NymTopologyError {
|
||||
#[error("The provided network topology is empty - there are no mixnodes and no gateways on it - the network request(s) probably failed")]
|
||||
EmptyNetworkTopology,
|
||||
|
||||
#[error("The provided network topology has no gateways available")]
|
||||
NoGatewaysAvailable,
|
||||
|
||||
#[error("The provided network topology has no mixnodes available")]
|
||||
NoMixnodesAvailable,
|
||||
|
||||
#[error("Gateway with identity key {identity_key} doesn't exist")]
|
||||
NonExistentGatewayError { identity_key: String },
|
||||
|
||||
#[error("Wanted to create a mix route with {requested} hops, while only {available} layers are available")]
|
||||
InvalidNumberOfHopsError { available: usize, requested: usize },
|
||||
|
||||
#[error("No mixnodes available on layer {layer}")]
|
||||
EmptyMixLayer { layer: MixLayer },
|
||||
|
||||
#[error("Uneven layer distribution. Layer {layer} has {nodes} on it, while we expected a value between {lower_bound} and {upper_bound} as we have {total_nodes} nodes in total. Full breakdown: {layer_distribution:?}")]
|
||||
UnevenLayerDistribution {
|
||||
layer: MixLayer,
|
||||
nodes: usize,
|
||||
lower_bound: usize,
|
||||
upper_bound: usize,
|
||||
total_nodes: usize,
|
||||
layer_distribution: Vec<(MixLayer, usize)>,
|
||||
},
|
||||
}
|
||||
+10
-33
@@ -14,45 +14,17 @@ use std::fmt::{self, Display, Formatter};
|
||||
use std::io;
|
||||
use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
|
||||
use std::str::FromStr;
|
||||
use thiserror::Error;
|
||||
|
||||
pub mod error;
|
||||
pub mod filter;
|
||||
pub mod gateway;
|
||||
pub mod mix;
|
||||
pub mod random_route_provider;
|
||||
|
||||
#[cfg(feature = "provider-trait")]
|
||||
pub mod provider_trait;
|
||||
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum NymTopologyError {
|
||||
#[error("The provided network topology is empty - there are no mixnodes and no gateways on it - the network request(s) probably failed")]
|
||||
EmptyNetworkTopology,
|
||||
|
||||
#[error("The provided network topology has no gateways available")]
|
||||
NoGatewaysAvailable,
|
||||
|
||||
#[error("The provided network topology has no mixnodes available")]
|
||||
NoMixnodesAvailable,
|
||||
|
||||
#[error("Gateway with identity key {identity_key} doesn't exist")]
|
||||
NonExistentGatewayError { identity_key: String },
|
||||
|
||||
#[error("Wanted to create a mix route with {requested} hops, while only {available} layers are available")]
|
||||
InvalidNumberOfHopsError { available: usize, requested: usize },
|
||||
|
||||
#[error("No mixnodes available on layer {layer}")]
|
||||
EmptyMixLayer { layer: MixLayer },
|
||||
|
||||
#[error("Uneven layer distribution. Layer {layer} has {nodes} on it, while we expected a value between {lower_bound} and {upper_bound} as we have {total_nodes} nodes in total. Full breakdown: {layer_distribution:?}")]
|
||||
UnevenLayerDistribution {
|
||||
layer: MixLayer,
|
||||
nodes: usize,
|
||||
lower_bound: usize,
|
||||
upper_bound: usize,
|
||||
total_nodes: usize,
|
||||
layer_distribution: Vec<(MixLayer, usize)>,
|
||||
},
|
||||
}
|
||||
pub use error::NymTopologyError;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum NetworkAddress {
|
||||
@@ -134,6 +106,12 @@ impl NymTopology {
|
||||
None
|
||||
}
|
||||
|
||||
pub fn find_gateway(&self, gateway_identity: IdentityKeyRef) -> Option<&gateway::Node> {
|
||||
self.gateways
|
||||
.iter()
|
||||
.find(|&gateway| gateway.identity_key.to_base58_string() == gateway_identity)
|
||||
}
|
||||
|
||||
pub fn mixes(&self) -> &BTreeMap<MixLayer, Vec<mix::Node>> {
|
||||
&self.mixes
|
||||
}
|
||||
@@ -182,8 +160,7 @@ impl NymTopology {
|
||||
num_mix_hops: u8,
|
||||
) -> Result<Vec<SphinxNode>, NymTopologyError>
|
||||
where
|
||||
// I don't think there's a need for this RNG to be crypto-secure
|
||||
R: Rng + ?Sized,
|
||||
R: Rng + CryptoRng + ?Sized,
|
||||
{
|
||||
use rand::seq::SliceRandom;
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{NymTopology, NymTopologyError};
|
||||
use nym_sphinx_addressing::clients::Recipient;
|
||||
use nym_sphinx_routing::SphinxRouteMaker;
|
||||
use nym_sphinx_types::Node;
|
||||
use rand::{CryptoRng, Rng};
|
||||
|
||||
pub struct NymTopologyRouteProvider<R> {
|
||||
rng: R,
|
||||
inner: NymTopology,
|
||||
}
|
||||
|
||||
impl<R> SphinxRouteMaker for NymTopologyRouteProvider<R>
|
||||
where
|
||||
R: Rng + CryptoRng,
|
||||
{
|
||||
type Error = NymTopologyError;
|
||||
|
||||
fn sphinx_route(
|
||||
&mut self,
|
||||
hops: u8,
|
||||
destination: &Recipient,
|
||||
) -> Result<Vec<Node>, NymTopologyError> {
|
||||
self.inner
|
||||
.random_route_to_gateway(&mut self.rng, hops, destination.gateway())
|
||||
}
|
||||
}
|
||||
Generated
+2
-1
@@ -1045,7 +1045,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-crypto"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"ed25519-dalek",
|
||||
@@ -1152,6 +1152,7 @@ name = "nym-service-provider-directory-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
* `docs` contains technical documentation hosted at [https://nymtech.net/docs](https://nymtech.net/docs)
|
||||
* `dev-portal` contains developer documentation hosted at [https://nymtech.net/developers](https://nymtech.net/developers)
|
||||
|
||||
Each is built with [`mdbook`](https://rust-lang.github.io/mdBook/index.html) - use `mdbook serve` to build and serve them (defaults to `localhost:3000`).
|
||||
Each directory contains a readme with more information about running and contributing to the projects. Each is built with [`mdbook`](https://rust-lang.github.io/mdBook/index.html) - use `mdbook serve` to build and serve them (defaults to `localhost:3000`).
|
||||
|
||||
@@ -49,9 +49,9 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
|
||||
[preprocessor.variables.variables]
|
||||
# code prerequisites versions
|
||||
minimum_rust_version = "1.66"
|
||||
platform_release_version = "v1.1.16"
|
||||
upcoming_platform_release_version = "v1.1.17" # to use when adding 'edit on github' plugin
|
||||
mix_node_release_version = "v1.1.17"
|
||||
platform_release_version = "v1.1.17"
|
||||
upcoming_platform_release_version = "v1.1.18" # to use when adding 'edit on github' plugin
|
||||
mix_node_release_version = "v1.1.18"
|
||||
#
|
||||
[preprocessor.last-changed]
|
||||
command = "mdbook-last-changed"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[book]
|
||||
title = "Nym Docs v1.1.16"
|
||||
title = "Nym Docs v1.1.17"
|
||||
authors = ["Max Hampshire"]
|
||||
description = "Nym technical documentation"
|
||||
language = "en"
|
||||
@@ -48,11 +48,9 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
|
||||
# https://gitlab.com/tglman/mdbook-variables/
|
||||
[preprocessor.variables.variables]
|
||||
minimum_rust_version = "1.66"
|
||||
platform_release_version = "v1.1.16"
|
||||
upcoming_platform_release_version = "v1.1.17" # to use in 'edit page on github' plugin (coming soon)
|
||||
mix_node_release_version = "v1.1.17"
|
||||
# used by the cmdrun preprocessor - relative path from inside src/<dir>
|
||||
# binaries_path = '../../../../target/release/'
|
||||
platform_release_version = "v1.1.17"
|
||||
upcoming_platform_release_version = "v1.1.18" # to use in 'edit page on github' plugin (coming soon)
|
||||
mix_node_release_version = "v1.1.18"
|
||||
|
||||
[preprocessor.last-changed]
|
||||
command = "mdbook-last-changed"
|
||||
|
||||
@@ -14,7 +14,7 @@ Before you initialise and run your gateway, head to our [website](https://nymtec
|
||||
|
||||
If you don't already have one, please create a Nym address using the wallet, and fund it with tokens. The minimum amount required to bond a gateway is 100 `NYM`, but make sure you have a bit more to account for gas costs.
|
||||
|
||||
`NYM` can be purchased via Bity from the wallet itself, and is currently present on several exchanges.
|
||||
`NYM` can be purchased via Bity from the wallet itself with BTC or fiat, and is currently present on several exchanges.
|
||||
|
||||
> Remember that you can **only** use native Cosmos `NYM` tokens to bond your gateway. You **cannot** use ERC20 representations of `NYM` to run a node.
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ Before you initialise and run your mixnode, head to our [website](https://nymtec
|
||||
|
||||
If you don't already have one, please create a Nym address using the wallet, and fund it with tokens. The minimum amount required to bond a mixnode is 100 `NYM`, but make sure you have a bit more to account for gas costs.
|
||||
|
||||
`NYM` can be purchased via Bity from the wallet itself, and is currently present on several exchanges.
|
||||
`NYM` can be purchased via Bity from the wallet itself with BTC or fiat, and is currently present on several exchanges.
|
||||
|
||||
> Remember that you can **only** use Cosmos `NYM` tokens to bond your mixnode. You **cannot** use ERC20 representations of `NYM` to run a node.
|
||||
|
||||
@@ -122,7 +122,7 @@ You can bond your mix node via the Desktop wallet.
|
||||
|_| |_|\__, |_| |_| |_|
|
||||
|___/
|
||||
|
||||
(nym-mixnode - version 1.1.14)
|
||||
(nym-mixnode - version {{mix_node_release_version}})
|
||||
|
||||
|
||||
>>> attempting to sign 5XrvVEMzRJk2AcT2h1o6ErZNb8z1ZzD3h7teipBW3NUtrtYq7vu4DRMgzZRTPVPnyr2YWCxpmKCMFaEXvksnJ4jt7np3NMLxsLMrFjEBhh67Crtjy4868vCzAivUqzdc365RiqxQQKtv4r9eTk9mTbE9JY8U3TxzKJCSGcBqbrb9JX3HrZVWm6tqbUYbsnku9pqnfeyeUiaYKY44Lm72TYrkZfRrMAZLMATiXT1ntmiKqT37HzRxNZjiH8qHeQEoRHkgDsmXDXRbfppGTpPrN7R4sjynJzehzUBZ8Ug7ovT9FoAHb8kuVQhUiMs1js6tdwtthzQMbPi9vwxUtVvjYknN2fnJgMnckEhzJJpJDCNdH7YhpPaWQnGVVS334mskiuqkbRVrFPJN2nnwArHr3L2cLxSMk9toKfw7ViKJ2p5E5JxiSmKY1cFGZ7uRLsuQ833PJN9JE8crPtkBNefqkbFNz68S5jPmzUShSvAc4TqXKeovDASFmmhKaPqLUrfsSWm7nzuKnzJSMADF6xSuwr9cknMoirqkRkLe7ybJ2ERwSdf5cUxMjF7yjS8tW9hZudnTUb1uPNDuSmPPVrCR12XZyFzBvVgxH51ZNJTym46nqnfA881LQcmFMnCwJf39rVJ4ASLnzEzmuwXj75QoB9ce9kiLmoBNLYe4QKSB6gDd858VnBtBNQELVuCCZbrTYuSCeNdUFhvMwD4kryc1pBYUa8Ro81F3QVfiKN
|
||||
|
||||
@@ -27,7 +27,7 @@ The `mixnet` component currently exposes the logic of two clients: the websocket
|
||||
The `coconut` component is currently being worked on. Right now it exposes logic allowing for the creation of coconut credentials on the Sandbox testnet.
|
||||
|
||||
## Websocket client examples
|
||||
> All the codeblocks below can be found in the `nym-sdk` [examples directory](https://github.com/nymtech/nym/tree/release/{{platform_release_version}}/sdk/rust/nym-sdk/examples) in the monorepo.
|
||||
> All the codeblocks below can be found in the `nym-sdk` [examples directory](https://github.com/nymtech/nym/tree/release/{{platform_release_version}}/sdk/rust/nym-sdk/examples) in the monorepo. Just navigate to `nym/sdk/rust/nym-sdk/examples/` and run the files from there. If you wish to run these outside of the workspace - such as if you want to use one as the basis for your own project - then make sure to import the `sdk`, `tokio`, and `nym_bin_common` crates.
|
||||
|
||||
Lets look at a very simple example of how you can import and use the websocket client in a piece of Rust code (`examples/simple.rs`):
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ GROUP_CONTRACT_ADDRESS=n1fqquzw4mk0pkamgr2ywt2v7h2j9nuyjjn4gvpy8zlpp6xn0uyuzqfm2
|
||||
MULTISIG_CONTRACT_ADDRESS=n1gaq3666chd5348apj8cka8t2mckv7azp9espyr7wgpxyuzur5d0sazpysy
|
||||
COCONUT_DKG_CONTRACT_ADDRESS=n18yadscxw8v35dds7ksv3j0svmjh3h6e7tmxpadk96mvgz27zygkshuf4vs
|
||||
REWARDING_VALIDATOR_ADDRESS=n10yyd98e2tuwu0f7ypz9dy3hhjw7v772q6287gy
|
||||
SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1ryt076cufyddallg5x0gz3qjz0pd3wg0m4cwkg9njhmlnp6u88qq6nczgj
|
||||
STATISTICS_SERVICE_DOMAIN_ADDRESS="https://mainnet-stats.nymte.ch:8090"
|
||||
NYXD="https://qwerty-validator.qa.nymte.ch/"
|
||||
NYM_API="https://qwerty-validator-api.qa.nymte.ch/api"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "explorer-api"
|
||||
version = "1.1.16"
|
||||
version = "1.1.17"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-gateway"
|
||||
version = "1.1.16"
|
||||
version = "1.1.17"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-mixnode"
|
||||
version = "1.1.17"
|
||||
version = "1.1.18"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
+3
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-api"
|
||||
version = "1.1.17"
|
||||
version = "1.1.18"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
@@ -82,6 +82,7 @@ nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet
|
||||
nym-vesting-contract-common = { path = "../common/cosmwasm-smart-contracts/vesting-contract" }
|
||||
nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" }
|
||||
nym-multisig-contract-common = { path = "../common/cosmwasm-smart-contracts/multisig-contract" }
|
||||
nym-service-provider-directory-common = { path = "../common/cosmwasm-smart-contracts/service-provider-directory" }
|
||||
nym-coconut = { path = "../common/nymcoconut" }
|
||||
nym-sphinx = { path = "../common/nymsphinx" }
|
||||
nym-pemstore = { path = "../common/pemstore" }
|
||||
@@ -92,6 +93,7 @@ nym-validator-client = { path = "../common/client-libs/validator-client", featur
|
||||
"nyxd-client",
|
||||
] }
|
||||
nym-bin-common = { path = "../common/bin-common" }
|
||||
nym-node-tester-utils = { path = "../common/node-tester-utils" }
|
||||
|
||||
[features]
|
||||
no-reward = []
|
||||
|
||||
@@ -58,8 +58,6 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
async fn start_nym_api_tasks(
|
||||
config: Config,
|
||||
) -> Result<ShutdownHandles, Box<dyn Error + Send + Sync>> {
|
||||
let system_version = clap::crate_version!();
|
||||
|
||||
let nyxd_client = nyxd::Client::new(&config);
|
||||
let mix_denom = nyxd_client.chain_details().await.mix_denom.base;
|
||||
|
||||
@@ -133,7 +131,6 @@ async fn start_nym_api_tasks(
|
||||
nym_contract_cache_state,
|
||||
storage,
|
||||
nyxd_client.clone(),
|
||||
system_version,
|
||||
&shutdown,
|
||||
)
|
||||
.await;
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_sphinx::message::NymMessage;
|
||||
use nym_sphinx::params::PacketSize;
|
||||
use nym_sphinx::{
|
||||
acknowledgements::AckKey, addressing::clients::Recipient, preparer::MessagePreparer,
|
||||
};
|
||||
use nym_topology::NymTopology;
|
||||
use rand_07::rngs::OsRng;
|
||||
use std::time::Duration;
|
||||
|
||||
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200);
|
||||
const DEFAULT_AVERAGE_ACK_DELAY: Duration = Duration::from_millis(200);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Chunker {
|
||||
rng: OsRng,
|
||||
packet_size: PacketSize,
|
||||
message_preparer: MessagePreparer<OsRng>,
|
||||
}
|
||||
|
||||
impl Chunker {
|
||||
pub(crate) fn new(tested_mix_me: Recipient) -> Self {
|
||||
Chunker {
|
||||
rng: OsRng,
|
||||
// no point in using anything else for monitoring
|
||||
// unless we should make it variable so mixnodes wouldn't know if
|
||||
// non-default packet is for measurement or not
|
||||
packet_size: PacketSize::RegularPacket,
|
||||
message_preparer: MessagePreparer::new(
|
||||
OsRng,
|
||||
tested_mix_me,
|
||||
DEFAULT_AVERAGE_PACKET_DELAY,
|
||||
DEFAULT_AVERAGE_ACK_DELAY,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn prepare_packets_from(
|
||||
&mut self,
|
||||
message: Vec<u8>,
|
||||
topology: &NymTopology,
|
||||
packet_sender: Recipient,
|
||||
) -> Vec<MixPacket> {
|
||||
// I really dislike how we have to overwrite the parameter of the `MessagePreparer` on each run
|
||||
// but without some significant API changes in the `MessagePreparer` this was the easiest
|
||||
// way to being able to have variable sender address.
|
||||
self.message_preparer.set_sender_address(packet_sender);
|
||||
self.prepare_packets(message, topology, packet_sender)
|
||||
}
|
||||
|
||||
fn prepare_packets(
|
||||
&mut self,
|
||||
message: Vec<u8>,
|
||||
topology: &NymTopology,
|
||||
packet_sender: Recipient,
|
||||
) -> Vec<MixPacket> {
|
||||
let ack_key: AckKey = AckKey::new(&mut self.rng);
|
||||
|
||||
let split_message = self
|
||||
.message_preparer
|
||||
.pad_and_split_message(NymMessage::new_plain(message), self.packet_size);
|
||||
|
||||
let mut mix_packets = Vec::with_capacity(split_message.len());
|
||||
for message_chunk in split_message {
|
||||
// don't bother with acks etc. for time being
|
||||
let prepared_fragment = self
|
||||
.message_preparer
|
||||
.prepare_chunk_for_sending(message_chunk, topology, &ack_key, &packet_sender)
|
||||
.unwrap();
|
||||
|
||||
mix_packets.push(prepared_fragment.mix_packet);
|
||||
}
|
||||
mix_packets
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,22 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use futures::Stream;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_client::{AcknowledgementReceiver, MixnetMessageReceiver};
|
||||
use nym_mixnet_contract_common::IdentityKey;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio_stream::StreamMap;
|
||||
|
||||
pub(crate) type GatewayMessages = Vec<Vec<u8>>;
|
||||
pub(crate) enum GatewayMessages {
|
||||
Data(Vec<Vec<u8>>),
|
||||
Acks(Vec<Vec<u8>>),
|
||||
}
|
||||
|
||||
pub(crate) struct GatewaysReader {
|
||||
ack_map: StreamMap<String, AcknowledgementReceiver>,
|
||||
stream_map: StreamMap<String, MixnetMessageReceiver>,
|
||||
ack_map: StreamMap<IdentityKey, AcknowledgementReceiver>,
|
||||
stream_map: StreamMap<IdentityKey, MixnetMessageReceiver>,
|
||||
}
|
||||
|
||||
impl GatewaysReader {
|
||||
@@ -41,20 +45,26 @@ impl GatewaysReader {
|
||||
}
|
||||
|
||||
impl Stream for GatewaysReader {
|
||||
// just return whatever is returned by our main `stream_map`
|
||||
type Item = <StreamMap<String, MixnetMessageReceiver> as Stream>::Item;
|
||||
type Item = (IdentityKey, GatewayMessages);
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
// exhaust the ack map if possible
|
||||
match Pin::new(&mut self.ack_map).poll_next(cx) {
|
||||
Poll::Ready(None) => {
|
||||
// this should have never happened!
|
||||
return Poll::Ready(None);
|
||||
}
|
||||
Poll::Ready(Some(_item)) => (),
|
||||
Poll::Ready(Some(ack_item)) => {
|
||||
// wake immediately in case there's an associated data message
|
||||
cx.waker().wake_by_ref();
|
||||
return Poll::Ready(Some((ack_item.0, GatewayMessages::Acks(ack_item.1))));
|
||||
}
|
||||
Poll::Pending => (),
|
||||
}
|
||||
|
||||
Pin::new(&mut self.stream_map).poll_next(cx)
|
||||
Pin::new(&mut self.stream_map)
|
||||
.poll_next(cx)
|
||||
.map(|maybe_data_item| {
|
||||
maybe_data_item.map(|data_item| (data_item.0, GatewayMessages::Data(data_item.1)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,11 @@ use futures::channel::mpsc;
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_credential_storage::persistent_storage::PersistentStorage;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::receiver::MessageReceiver;
|
||||
use nym_task::TaskManager;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub(crate) mod chunker;
|
||||
pub(crate) mod gateways_reader;
|
||||
pub(crate) mod monitor;
|
||||
pub(crate) mod test_packet;
|
||||
@@ -37,12 +37,10 @@ pub(crate) fn setup<'a>(
|
||||
nym_contract_cache_state: &NymContractCache,
|
||||
storage: &NymApiStorage,
|
||||
nyxd_client: nyxd::Client,
|
||||
system_version: &str,
|
||||
) -> NetworkMonitorBuilder<'a> {
|
||||
NetworkMonitorBuilder::new(
|
||||
config,
|
||||
nyxd_client,
|
||||
system_version,
|
||||
storage.to_owned(),
|
||||
nym_contract_cache_state.to_owned(),
|
||||
)
|
||||
@@ -51,7 +49,6 @@ pub(crate) fn setup<'a>(
|
||||
pub(crate) struct NetworkMonitorBuilder<'a> {
|
||||
config: &'a Config,
|
||||
nyxd_client: nyxd::Client,
|
||||
system_version: String,
|
||||
node_status_storage: NymApiStorage,
|
||||
validator_cache: NymContractCache,
|
||||
}
|
||||
@@ -60,14 +57,12 @@ impl<'a> NetworkMonitorBuilder<'a> {
|
||||
pub(crate) fn new(
|
||||
config: &'a Config,
|
||||
nyxd_client: nyxd::Client,
|
||||
system_version: &str,
|
||||
node_status_storage: NymApiStorage,
|
||||
validator_cache: NymContractCache,
|
||||
) -> Self {
|
||||
NetworkMonitorBuilder {
|
||||
config,
|
||||
nyxd_client,
|
||||
system_version: system_version.to_string(),
|
||||
node_status_storage,
|
||||
validator_cache,
|
||||
}
|
||||
@@ -83,15 +78,16 @@ impl<'a> NetworkMonitorBuilder<'a> {
|
||||
|
||||
let identity_keypair = Arc::new(identity::KeyPair::new(&mut rng));
|
||||
let encryption_keypair = Arc::new(encryption::KeyPair::new(&mut rng));
|
||||
let ack_key = Arc::new(AckKey::new(&mut rng));
|
||||
|
||||
let (gateway_status_update_sender, gateway_status_update_receiver) = mpsc::unbounded();
|
||||
let (received_processor_sender_channel, received_processor_receiver_channel) =
|
||||
mpsc::unbounded();
|
||||
|
||||
let packet_preparer = new_packet_preparer(
|
||||
&self.system_version,
|
||||
self.validator_cache,
|
||||
self.config.get_per_node_test_packets(),
|
||||
Arc::clone(&ack_key),
|
||||
*identity_keypair.public_key(),
|
||||
*encryption_keypair.public_key(),
|
||||
);
|
||||
@@ -118,6 +114,7 @@ impl<'a> NetworkMonitorBuilder<'a> {
|
||||
let received_processor = new_received_processor(
|
||||
received_processor_receiver_channel,
|
||||
Arc::clone(&encryption_keypair),
|
||||
ack_key,
|
||||
);
|
||||
let summary_producer = new_summary_producer(self.config.get_per_node_test_packets());
|
||||
let packet_receiver = new_packet_receiver(
|
||||
@@ -161,16 +158,16 @@ impl<R: MessageReceiver + Send + 'static> NetworkMonitorRunnables<R> {
|
||||
}
|
||||
|
||||
fn new_packet_preparer(
|
||||
system_version: &str,
|
||||
validator_cache: NymContractCache,
|
||||
per_node_test_packets: usize,
|
||||
ack_key: Arc<AckKey>,
|
||||
self_public_identity: identity::PublicKey,
|
||||
self_public_encryption: encryption::PublicKey,
|
||||
) -> PacketPreparer {
|
||||
PacketPreparer::new(
|
||||
system_version,
|
||||
validator_cache,
|
||||
per_node_test_packets,
|
||||
ack_key,
|
||||
self_public_identity,
|
||||
self_public_encryption,
|
||||
)
|
||||
@@ -199,8 +196,9 @@ fn new_packet_sender(
|
||||
fn new_received_processor<R: MessageReceiver + Send + 'static>(
|
||||
packets_receiver: ReceivedProcessorReceiver,
|
||||
client_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
ack_key: Arc<AckKey>,
|
||||
) -> ReceivedProcessor<R> {
|
||||
ReceivedProcessor::new(packets_receiver, client_encryption_keypair)
|
||||
ReceivedProcessor::new(packets_receiver, client_encryption_keypair, ack_key)
|
||||
}
|
||||
|
||||
fn new_summary_producer(per_node_test_packets: usize) -> SummaryProducer {
|
||||
@@ -223,16 +221,10 @@ pub(crate) async fn start<R: MessageReceiver + Send + 'static>(
|
||||
nym_contract_cache_state: &NymContractCache,
|
||||
storage: &NymApiStorage,
|
||||
nyxd_client: nyxd::Client,
|
||||
system_version: &str,
|
||||
shutdown: &TaskManager,
|
||||
) {
|
||||
let monitor_builder = network_monitor::setup(
|
||||
config,
|
||||
nym_contract_cache_state,
|
||||
storage,
|
||||
nyxd_client,
|
||||
system_version,
|
||||
);
|
||||
let monitor_builder =
|
||||
network_monitor::setup(config, nym_contract_cache_state, storage, nyxd_client);
|
||||
info!("Starting network monitor...");
|
||||
let runnables: NetworkMonitorRunnables<R> = monitor_builder.build().await;
|
||||
runnables.spawn_tasks(shutdown);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::network_monitor::monitor::preparer::PacketPreparer;
|
||||
use crate::network_monitor::monitor::processor::ReceivedProcessor;
|
||||
use crate::network_monitor::monitor::sender::PacketSender;
|
||||
use crate::network_monitor::monitor::summary_producer::{SummaryProducer, TestSummary};
|
||||
use crate::network_monitor::test_packet::TestPacket;
|
||||
use crate::network_monitor::test_packet::NodeTestMessage;
|
||||
use crate::network_monitor::test_route::TestRoute;
|
||||
use crate::storage::NymApiStorage;
|
||||
use crate::support::config::Config;
|
||||
@@ -96,10 +96,13 @@ impl<R: MessageReceiver + Send> Monitor<R> {
|
||||
}
|
||||
}
|
||||
|
||||
fn analyse_received_test_route_packets(&self, packets: &[TestPacket]) -> HashMap<u64, usize> {
|
||||
fn analyse_received_test_route_packets(
|
||||
&self,
|
||||
packets: &[NodeTestMessage],
|
||||
) -> HashMap<u64, usize> {
|
||||
let mut received = HashMap::new();
|
||||
for packet in packets {
|
||||
*received.entry(packet.route_id).or_insert(0usize) += 1usize
|
||||
*received.entry(packet.ext.route_id).or_insert(0usize) += 1usize
|
||||
}
|
||||
|
||||
received
|
||||
@@ -258,7 +261,7 @@ impl<R: MessageReceiver + Send> Monitor<R> {
|
||||
|
||||
let received = self.received_processor.return_received().await;
|
||||
let total_received = received.len();
|
||||
info!("Test routes: {:?}", routes);
|
||||
info!("Test routes: {:#?}", routes);
|
||||
info!("Received {}/{} packets", total_received, total_sent);
|
||||
|
||||
let summary = self.summary_producer.produce_summary(
|
||||
@@ -271,7 +274,7 @@ impl<R: MessageReceiver + Send> Monitor<R> {
|
||||
);
|
||||
|
||||
let report = summary.create_report(total_sent, total_received);
|
||||
info!("{}", report);
|
||||
info!("{report}");
|
||||
|
||||
self.submit_new_node_statuses(summary).await;
|
||||
}
|
||||
|
||||
@@ -1,113 +1,50 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::network_monitor::chunker::Chunker;
|
||||
use crate::network_monitor::monitor::sender::GatewayPackets;
|
||||
use crate::network_monitor::test_packet::{NodeType, TestPacket};
|
||||
use crate::network_monitor::test_route::TestRoute;
|
||||
use crate::nym_contract_cache::cache::NymContractCache;
|
||||
use log::info;
|
||||
use nym_crypto::asymmetric::{encryption, identity};
|
||||
use nym_mixnet_contract_common::{Addr, GatewayBond, Layer, MixId, MixNodeBond};
|
||||
use nym_mixnet_contract_common::{GatewayBond, Layer, MixNodeBond};
|
||||
use nym_node_tester_utils::node::TestableNode;
|
||||
use nym_node_tester_utils::NodeTester;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_topology::{gateway, mix, NymTopology};
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::{thread_rng, Rng};
|
||||
use nym_sphinx::params::PacketSize;
|
||||
use nym_topology::{gateway, mix};
|
||||
use rand_07::{rngs::ThreadRng, seq::SliceRandom, thread_rng, Rng};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::convert::TryInto;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
// declared type aliases for easier code reasoning
|
||||
type Version = String;
|
||||
type Id = String;
|
||||
type Owner = Addr;
|
||||
const DEFAULT_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(200);
|
||||
const DEFAULT_AVERAGE_ACK_DELAY: Duration = Duration::from_millis(200);
|
||||
|
||||
#[derive(Clone)]
|
||||
#[allow(dead_code)]
|
||||
pub(crate) enum InvalidNode {
|
||||
Outdated(Id, Owner, NodeType, Version),
|
||||
Malformed(Id, Owner, NodeType),
|
||||
Malformed { node: TestableNode },
|
||||
}
|
||||
|
||||
impl Display for InvalidNode {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
InvalidNode::Outdated(id, owner, _, version) => {
|
||||
write!(
|
||||
f,
|
||||
"Node {} (v{}) owned by {} is outdated",
|
||||
id, version, owner
|
||||
)
|
||||
}
|
||||
InvalidNode::Malformed(id, owner, _) => {
|
||||
write!(f, "Node {} owned by {} is malformed", id, owner)
|
||||
InvalidNode::Malformed { node } => {
|
||||
write!(f, "{node} is malformed")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InvalidNode {
|
||||
pub(crate) fn mix_id(&self) -> Option<MixId> {
|
||||
match self {
|
||||
InvalidNode::Outdated(_, _, node_type, _) => node_type.mix_id(),
|
||||
InvalidNode::Malformed(_, _, node_type) => node_type.mix_id(),
|
||||
impl From<InvalidNode> for TestableNode {
|
||||
fn from(value: InvalidNode) -> Self {
|
||||
match value {
|
||||
InvalidNode::Malformed { node } => node,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn identity(&self) -> String {
|
||||
match self {
|
||||
InvalidNode::Outdated(id, _, _, _) => id.clone(),
|
||||
InvalidNode::Malformed(id, _, _) => id.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn owner(&self) -> String {
|
||||
match self {
|
||||
InvalidNode::Outdated(_, owner, _, _) => owner.into(),
|
||||
InvalidNode::Malformed(_, owner, _) => owner.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone)]
|
||||
pub(crate) struct TestedNode {
|
||||
pub(crate) identity: String,
|
||||
pub(crate) owner: String,
|
||||
pub(crate) node_type: NodeType,
|
||||
}
|
||||
|
||||
impl TestedNode {
|
||||
pub(crate) fn mix_id(&self) -> Option<MixId> {
|
||||
self.node_type.mix_id()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a mix::Node> for TestedNode {
|
||||
fn from(node: &'a mix::Node) -> Self {
|
||||
TestedNode {
|
||||
identity: node.identity_key.to_base58_string(),
|
||||
owner: node.owner.clone(),
|
||||
node_type: NodeType::Mixnode(node.mix_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a gateway::Node> for TestedNode {
|
||||
fn from(node: &'a gateway::Node) -> Self {
|
||||
TestedNode {
|
||||
identity: node.identity_key.to_base58_string(),
|
||||
owner: node.owner.clone(),
|
||||
node_type: NodeType::Gateway,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TestedNode {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} (owned by {})", self.identity, self.owner)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PreparedPackets {
|
||||
@@ -116,10 +53,10 @@ pub(crate) struct PreparedPackets {
|
||||
pub(super) packets: Vec<GatewayPackets>,
|
||||
|
||||
/// Vector containing list of public keys and owners of all nodes mixnodes being tested.
|
||||
pub(super) tested_mixnodes: Vec<TestedNode>,
|
||||
pub(super) tested_mixnodes: Vec<TestableNode>,
|
||||
|
||||
/// Vector containing list of public keys and owners of all gateways being tested.
|
||||
pub(super) tested_gateways: Vec<TestedNode>,
|
||||
pub(super) tested_gateways: Vec<TestableNode>,
|
||||
|
||||
/// All mixnodes that failed to get parsed correctly or were not version compatible.
|
||||
/// They will be marked to the validator as being down for the test.
|
||||
@@ -132,13 +69,12 @@ pub(crate) struct PreparedPackets {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct PacketPreparer {
|
||||
system_version: String,
|
||||
chunker: Option<Chunker>,
|
||||
validator_cache: NymContractCache,
|
||||
|
||||
/// Number of test packets sent to each node
|
||||
per_node_test_packets: usize,
|
||||
|
||||
ack_key: Arc<AckKey>,
|
||||
// TODO: security:
|
||||
// in the future we should really create unique set of keys every time otherwise
|
||||
// gateways might recognise our "test" keys and take special care to always forward those packets
|
||||
@@ -149,45 +85,56 @@ pub(crate) struct PacketPreparer {
|
||||
|
||||
impl PacketPreparer {
|
||||
pub(crate) fn new(
|
||||
system_version: &str,
|
||||
validator_cache: NymContractCache,
|
||||
per_node_test_packets: usize,
|
||||
ack_key: Arc<AckKey>,
|
||||
self_public_identity: identity::PublicKey,
|
||||
self_public_encryption: encryption::PublicKey,
|
||||
) -> Self {
|
||||
PacketPreparer {
|
||||
system_version: system_version.to_owned(),
|
||||
chunker: None,
|
||||
validator_cache,
|
||||
per_node_test_packets,
|
||||
ack_key,
|
||||
self_public_identity,
|
||||
self_public_encryption,
|
||||
}
|
||||
}
|
||||
|
||||
fn wrap_test_packet(
|
||||
&mut self,
|
||||
packet: &TestPacket,
|
||||
topology: &NymTopology,
|
||||
packet_recipient: Recipient,
|
||||
) -> MixPacket {
|
||||
// this should be done only once. We can't really do it at construction time
|
||||
// as there's no sane Default for Recipient
|
||||
if self.chunker.is_none() {
|
||||
self.chunker = Some(Chunker::new(packet_recipient));
|
||||
}
|
||||
let mut mix_packets = self.chunker.as_mut().unwrap().prepare_packets_from(
|
||||
packet.to_bytes(),
|
||||
topology,
|
||||
packet_recipient,
|
||||
);
|
||||
assert_eq!(
|
||||
mix_packets.len(),
|
||||
1,
|
||||
"Our test packets data is longer than a single sphinx packet!"
|
||||
);
|
||||
fn ephemeral_tester(
|
||||
&self,
|
||||
test_route: &TestRoute,
|
||||
self_address: Option<Recipient>,
|
||||
) -> NodeTester<ThreadRng> {
|
||||
let rng = thread_rng();
|
||||
NodeTester::new(
|
||||
rng,
|
||||
// the topology here contains 3 mixnodes and 1 gateway so its cheap to clone it
|
||||
test_route.topology().clone(),
|
||||
self_address,
|
||||
PacketSize::RegularPacket,
|
||||
DEFAULT_AVERAGE_PACKET_DELAY,
|
||||
DEFAULT_AVERAGE_ACK_DELAY,
|
||||
self.ack_key.clone(),
|
||||
)
|
||||
}
|
||||
|
||||
mix_packets.pop().unwrap()
|
||||
// when we're testing mixnodes, the recipient is going to stay constant, so we can specify it ahead of time
|
||||
fn ephemeral_mix_tester(&self, test_route: &TestRoute) -> NodeTester<ThreadRng> {
|
||||
let self_address = self.create_packet_sender(test_route.gateway());
|
||||
self.ephemeral_tester(test_route, Some(self_address))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn ephemeral_gateway_tester(&self, test_route: &TestRoute) -> NodeTester<ThreadRng> {
|
||||
self.ephemeral_tester(test_route, None)
|
||||
}
|
||||
|
||||
async fn topology_wait_backoff(&self, initialisation_backoff: Duration) {
|
||||
info!(
|
||||
"Minimal topology is still not online. Going to check again in {:?}",
|
||||
initialisation_backoff
|
||||
);
|
||||
tokio::time::sleep(initialisation_backoff).await;
|
||||
}
|
||||
|
||||
pub(crate) async fn wait_for_validator_cache_initial_values(&self, minimum_full_routes: usize) {
|
||||
@@ -202,39 +149,30 @@ impl PacketPreparer {
|
||||
let mixnodes = self.validator_cache.mixnodes_basic().await;
|
||||
|
||||
if gateways.len() < minimum_full_routes {
|
||||
info!(
|
||||
"Minimal topology is still not online. Going to check again in {:?}",
|
||||
initialisation_backoff
|
||||
);
|
||||
tokio::time::sleep(initialisation_backoff).await;
|
||||
self.topology_wait_backoff(initialisation_backoff).await;
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut layered_mixes = HashMap::new();
|
||||
let mut layer1_count = 0;
|
||||
let mut layer2_count = 0;
|
||||
let mut layer3_count = 0;
|
||||
|
||||
for mix in mixnodes {
|
||||
let layer = mix.layer;
|
||||
let mixes = layered_mixes.entry(layer).or_insert_with(Vec::new);
|
||||
mixes.push(mix)
|
||||
match mix.layer {
|
||||
Layer::One => layer1_count += 1,
|
||||
Layer::Two => layer2_count += 1,
|
||||
Layer::Three => layer3_count += 1,
|
||||
}
|
||||
}
|
||||
|
||||
// we remove the entries as this gives us the ownership and thus we can unwrap to default value
|
||||
// which makes the code slightly nicer without having to deal with options
|
||||
let layer1 = layered_mixes.remove(&Layer::One).unwrap_or_default();
|
||||
let layer2 = layered_mixes.remove(&Layer::Two).unwrap_or_default();
|
||||
let layer3 = layered_mixes.remove(&Layer::Three).unwrap_or_default();
|
||||
|
||||
if layer1.len() >= minimum_full_routes
|
||||
&& layer2.len() >= minimum_full_routes
|
||||
&& layer3.len() >= minimum_full_routes
|
||||
if layer1_count >= minimum_full_routes
|
||||
&& layer2_count >= minimum_full_routes
|
||||
&& layer3_count >= minimum_full_routes
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
info!(
|
||||
"Minimal topology is still not online. Going to check again in {:?}",
|
||||
initialisation_backoff
|
||||
);
|
||||
tokio::time::sleep(initialisation_backoff).await;
|
||||
self.topology_wait_backoff(initialisation_backoff).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -305,55 +243,39 @@ impl PacketPreparer {
|
||||
|
||||
if most_available == 0 {
|
||||
error!("Cannot construct test routes. No nodes or gateways available");
|
||||
None
|
||||
} else {
|
||||
trace!("Generating test routes...");
|
||||
let mut routes = Vec::new();
|
||||
for i in 0..most_available {
|
||||
let node_1 = match self.try_parse_mix_bond(rand_l1[i]) {
|
||||
Ok(node) => node,
|
||||
Err(id) => {
|
||||
blacklist.insert(id);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let node_2 = match self.try_parse_mix_bond(rand_l2[i]) {
|
||||
Ok(node) => node,
|
||||
Err(id) => {
|
||||
blacklist.insert(id);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let node_3 = match self.try_parse_mix_bond(rand_l3[i]) {
|
||||
Ok(node) => node,
|
||||
Err(id) => {
|
||||
blacklist.insert(id);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let gateway = match self.try_parse_gateway_bond(rand_gateways[i]) {
|
||||
Ok(node) => node,
|
||||
Err(id) => {
|
||||
blacklist.insert(id);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
routes.push(TestRoute::new(
|
||||
rng.gen(),
|
||||
&self.system_version,
|
||||
node_1,
|
||||
node_2,
|
||||
node_3,
|
||||
gateway,
|
||||
))
|
||||
}
|
||||
info!("{:?}", routes);
|
||||
Some(routes)
|
||||
return None;
|
||||
}
|
||||
|
||||
trace!("Generating test routes...");
|
||||
let mut routes = Vec::new();
|
||||
for i in 0..most_available {
|
||||
let Ok(node_1) = self.try_parse_mix_bond(rand_l1[i]) else {
|
||||
blacklist.insert(rand_l1[i].identity().to_owned());
|
||||
continue
|
||||
};
|
||||
|
||||
let Ok(node_2) = self.try_parse_mix_bond(rand_l2[i]) else {
|
||||
blacklist.insert(rand_l2[i].identity().to_owned());
|
||||
continue
|
||||
};
|
||||
|
||||
let Ok(node_3) = self.try_parse_mix_bond(rand_l3[i]) else {
|
||||
blacklist.insert(rand_l3[i].identity().to_owned());
|
||||
continue
|
||||
};
|
||||
|
||||
let Ok(gateway) = self.try_parse_gateway_bond(rand_gateways[i]) else {
|
||||
blacklist.insert(rand_gateways[i].identity().to_owned());
|
||||
continue
|
||||
};
|
||||
|
||||
routes.push(TestRoute::new(rng.gen(), node_1, node_2, node_3, gateway))
|
||||
}
|
||||
info!(
|
||||
"The following routes will be used for testing: {:#?}",
|
||||
routes
|
||||
);
|
||||
Some(routes)
|
||||
}
|
||||
|
||||
fn create_packet_sender(&self, gateway: &gateway::Node) -> Recipient {
|
||||
@@ -369,13 +291,19 @@ impl PacketPreparer {
|
||||
route: &TestRoute,
|
||||
num: usize,
|
||||
) -> GatewayPackets {
|
||||
let mut mix_packets = Vec::with_capacity(num);
|
||||
let test_packet = route.self_test_packet();
|
||||
let recipient = self.create_packet_sender(route.gateway());
|
||||
for _ in 0..num {
|
||||
let mix_packet = self.wrap_test_packet(&test_packet, route.topology(), recipient);
|
||||
mix_packets.push(mix_packet)
|
||||
}
|
||||
let mut tester = self.ephemeral_mix_tester(route);
|
||||
let topology = route.topology();
|
||||
let plaintexts = route.self_test_messages(num);
|
||||
|
||||
// the unwrap here is fine as:
|
||||
// 1. the topology is definitely valid (otherwise we wouldn't be here)
|
||||
// 2. the recipient is specified (by calling **mix**_tester)
|
||||
// 3. the test message is not too long, i.e. when serialized it will fit in a single sphinx packet
|
||||
let mix_packets = plaintexts
|
||||
.into_iter()
|
||||
.map(|p| tester.wrap_plaintext_data(p, topology, None).unwrap())
|
||||
.map(MixPacket::from)
|
||||
.collect();
|
||||
|
||||
GatewayPackets::new(
|
||||
route.gateway_clients_address(),
|
||||
@@ -394,11 +322,13 @@ impl PacketPreparer {
|
||||
if let Ok(parsed_node) = (&mixnode).try_into() {
|
||||
parsed_nodes.push(parsed_node)
|
||||
} else {
|
||||
invalid_nodes.push(InvalidNode::Malformed(
|
||||
mixnode.mix_node.identity_key,
|
||||
mixnode.owner,
|
||||
NodeType::Mixnode(mixnode.mix_id),
|
||||
));
|
||||
invalid_nodes.push(InvalidNode::Malformed {
|
||||
node: TestableNode::new_mixnode(
|
||||
mixnode.identity().to_owned(),
|
||||
mixnode.owner.clone().into_string(),
|
||||
mixnode.mix_id,
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
(parsed_nodes, invalid_nodes)
|
||||
@@ -414,11 +344,12 @@ impl PacketPreparer {
|
||||
if let Ok(parsed_node) = (&gateway).try_into() {
|
||||
parsed_nodes.push(parsed_node)
|
||||
} else {
|
||||
invalid_nodes.push(InvalidNode::Malformed(
|
||||
gateway.gateway.identity_key,
|
||||
gateway.owner,
|
||||
NodeType::Gateway,
|
||||
));
|
||||
invalid_nodes.push(InvalidNode::Malformed {
|
||||
node: TestableNode::new_gateway(
|
||||
gateway.identity().to_owned(),
|
||||
gateway.owner.clone().into_string(),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
(parsed_nodes, invalid_nodes)
|
||||
@@ -449,42 +380,53 @@ impl PacketPreparer {
|
||||
|
||||
// for each test route...
|
||||
for test_route in test_routes {
|
||||
let recipient = self.create_packet_sender(test_route.gateway());
|
||||
let gateway_identity = test_route.gateway_identity();
|
||||
let route_ext = test_route.test_message_ext(test_nonce);
|
||||
let gateway_address = test_route.gateway_clients_address();
|
||||
let gateway_identity = test_route.gateway_identity();
|
||||
|
||||
// it's actually going to be a tiny bit more due to gateway testing, but it's a good enough approximation
|
||||
let mut mix_packets = Vec::with_capacity(mixnodes.len() * self.per_node_test_packets);
|
||||
let mut mix_tester = self.ephemeral_mix_tester(test_route);
|
||||
|
||||
// and for each mixnode...
|
||||
for mixnode in &mixnodes {
|
||||
let test_packet = TestPacket::from_mixnode(mixnode, test_route.id(), test_nonce);
|
||||
let topology = test_route.substitute_mix(mixnode);
|
||||
// produce n mix packets
|
||||
for _ in 0..self.per_node_test_packets {
|
||||
let mix_packet = self.wrap_test_packet(&test_packet, &topology, recipient);
|
||||
mix_packets.push(mix_packet);
|
||||
}
|
||||
}
|
||||
// generate test packets for mixnodes
|
||||
//
|
||||
// the unwrap here is fine as:
|
||||
// 1. the topology is definitely valid (otherwise we wouldn't be here)
|
||||
// 2. the recipient is specified (by calling **mix**_tester)
|
||||
// 3. the test message is not too long, i.e. when serialized it will fit in a single sphinx packet
|
||||
let mixnode_test_packets = mix_tester
|
||||
.mixnodes_test_packets(
|
||||
&mixnodes,
|
||||
route_ext,
|
||||
self.per_node_test_packets as u32,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
let mix_packets = mixnode_test_packets.into_iter().map(Into::into).collect();
|
||||
|
||||
let gateway_packets = all_gateway_packets
|
||||
.entry(gateway_identity.to_bytes())
|
||||
.or_insert_with(|| GatewayPackets::empty(gateway_address, gateway_identity));
|
||||
gateway_packets.push_packets(mix_packets);
|
||||
|
||||
// and for each gateway...
|
||||
// and generate test packets for gateways (note the variable recipient)
|
||||
for gateway in &gateways {
|
||||
let mut gateway_mix_packets = Vec::new();
|
||||
let test_packet = TestPacket::from_gateway(gateway, test_route.id(), test_nonce);
|
||||
let recipient = self.create_packet_sender(gateway);
|
||||
let gateway_identity = gateway.identity_key;
|
||||
let gateway_address = gateway.clients_address();
|
||||
let recipient = self.create_packet_sender(gateway);
|
||||
let topology = test_route.substitute_gateway(gateway);
|
||||
// produce n mix packets
|
||||
for _ in 0..self.per_node_test_packets {
|
||||
let mix_packet = self.wrap_test_packet(&test_packet, &topology, recipient);
|
||||
gateway_mix_packets.push(mix_packet);
|
||||
}
|
||||
|
||||
// the unwrap here is fine as:
|
||||
// 1. the topology is definitely valid (otherwise we wouldn't be here)
|
||||
// 2. the recipient is specified
|
||||
// 3. the test message is not too long, i.e. when serialized it will fit in a single sphinx packet
|
||||
let gateway_test_packets = mix_tester
|
||||
.gateway_test_packets(
|
||||
gateway,
|
||||
route_ext,
|
||||
self.per_node_test_packets as u32,
|
||||
Some(recipient),
|
||||
)
|
||||
.unwrap();
|
||||
let gateway_mix_packets =
|
||||
gateway_test_packets.into_iter().map(Into::into).collect();
|
||||
|
||||
// and push it into existing struct (if it's a "core" gateway being tested against another route)
|
||||
// or create a new one
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
// Copyright 2021-2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::network_monitor::gateways_reader::GatewayMessages;
|
||||
use crate::network_monitor::test_packet::{TestPacket, TestPacketError};
|
||||
use crate::network_monitor::test_packet::{NodeTestMessage, NymApiTestMessageExt};
|
||||
use crate::network_monitor::ROUTE_TESTING_TEST_NONCE;
|
||||
use futures::channel::mpsc;
|
||||
use futures::lock::{Mutex, MutexGuard};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use log::warn;
|
||||
use nym_crypto::asymmetric::encryption;
|
||||
use nym_node_tester_utils::error::NetworkTestingError;
|
||||
use nym_node_tester_utils::processor::TestPacketProcessor;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::receiver::{MessageReceiver, MessageRecoveryError};
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
@@ -20,15 +23,12 @@ pub(crate) type ReceivedProcessorReceiver = mpsc::UnboundedReceiver<GatewayMessa
|
||||
#[derive(Error, Debug)]
|
||||
enum ProcessingError {
|
||||
#[error(
|
||||
"could not recover underlying data from the received packet since it was malformed - {0}"
|
||||
"could not recover underlying data from the received packet since it was malformed: {0}"
|
||||
)]
|
||||
MalformedPacketReceived(#[from] MessageRecoveryError),
|
||||
|
||||
#[error("received a mix packet that was NOT a proper network monitor test packet")]
|
||||
NonTestPacketReceived,
|
||||
|
||||
#[error("the received test packet was malformed - {0}")]
|
||||
MalformedTestPacket(#[from] TestPacketError),
|
||||
#[error("the received test packet was malformed: {0}")]
|
||||
MalformedTestPacket(#[from] NetworkTestingError),
|
||||
|
||||
#[error("received packet with an unexpected nonce. Got: {received}, expected: {expected}")]
|
||||
NonMatchingNonce { received: u64, expected: u64 },
|
||||
@@ -50,52 +50,68 @@ struct ReceivedProcessorInner<R: MessageReceiver> {
|
||||
/// Channel for receiving packets/messages from the gateway clients
|
||||
packets_receiver: ReceivedProcessorReceiver,
|
||||
|
||||
// TODO: right now it's identical for each gateway we send through, but should it?
|
||||
/// Encryption key of the clients sending through the gateways.
|
||||
client_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
|
||||
/// Structure responsible for decrypting and recovering plaintext message from received ciphertexts.
|
||||
message_receiver: R,
|
||||
test_processor: TestPacketProcessor<NymApiTestMessageExt, R>,
|
||||
|
||||
/// Vector containing all received (and decrypted) packets in the current test run.
|
||||
received_packets: Vec<TestPacket>,
|
||||
// TODO: perhaps a different structure would be better here
|
||||
received_packets: Vec<NodeTestMessage>,
|
||||
}
|
||||
|
||||
impl<R: MessageReceiver> ReceivedProcessorInner<R> {
|
||||
fn on_message(&mut self, mut message: Vec<u8>) -> Result<(), ProcessingError> {
|
||||
fn on_received_data(&mut self, raw_message: Vec<u8>) -> Result<(), ProcessingError> {
|
||||
// if the nonce is none it means the packet was received during the 'waiting' for the
|
||||
// next test run
|
||||
if self.test_nonce.is_none() {
|
||||
return Err(ProcessingError::ReceivedOutsideTestRun);
|
||||
}
|
||||
|
||||
let plaintext = self
|
||||
.message_receiver
|
||||
.recover_plaintext_from_regular_packet(
|
||||
self.client_encryption_keypair.private_key(),
|
||||
&mut message,
|
||||
)?;
|
||||
let fragment = self.message_receiver.recover_fragment(plaintext)?;
|
||||
let (recovered, _) = self
|
||||
.message_receiver
|
||||
.insert_new_fragment(fragment)?
|
||||
.ok_or(ProcessingError::NonTestPacketReceived)?; // if it's a test packet it MUST BE reconstructed with single fragment
|
||||
let test_packet = TestPacket::try_from_bytes(&recovered.into_inner_data())?;
|
||||
let test_msg = self.test_processor.process_mixnet_message(raw_message)?;
|
||||
|
||||
// we know nonce is NOT none
|
||||
if test_packet.test_nonce() != self.test_nonce.unwrap() {
|
||||
if test_msg.ext.test_nonce != self.test_nonce.unwrap() {
|
||||
return Err(ProcessingError::NonMatchingNonce {
|
||||
received: test_packet.test_nonce(),
|
||||
received: test_msg.ext.test_nonce,
|
||||
expected: self.test_nonce.unwrap(),
|
||||
});
|
||||
}
|
||||
|
||||
self.received_packets.push(test_packet);
|
||||
self.received_packets.push(test_msg);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn on_received_ack(&mut self, raw_ack: Vec<u8>) -> Result<(), ProcessingError> {
|
||||
// if the nonce is none it means the packet was received during the 'waiting' for the
|
||||
// next test run
|
||||
if self.test_nonce.is_none() {
|
||||
return Err(ProcessingError::ReceivedOutsideTestRun);
|
||||
}
|
||||
|
||||
let frag_id = self.test_processor.process_ack(raw_ack)?;
|
||||
// TODO: hook it up at some point
|
||||
trace!("received a test ack with id {frag_id}. However, we're not going to do anything about it (just yet)");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn finish_run(&mut self) -> Vec<TestPacket> {
|
||||
fn on_received(&mut self, messages: GatewayMessages) {
|
||||
match messages {
|
||||
GatewayMessages::Data(data_msgs) => {
|
||||
for raw in data_msgs {
|
||||
if let Err(err) = self.on_received_data(raw) {
|
||||
warn!(target: "Monitor", "failed to process received gateway message: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
GatewayMessages::Acks(acks) => {
|
||||
for raw in acks {
|
||||
if let Err(err) = self.on_received_ack(raw) {
|
||||
warn!(target: "Monitor", "failed to process received gateway ack: {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_run(&mut self) -> Vec<NodeTestMessage> {
|
||||
self.test_nonce = None;
|
||||
mem::take(&mut self.received_packets)
|
||||
}
|
||||
@@ -110,13 +126,13 @@ impl<R: MessageReceiver + Send + 'static> ReceivedProcessor<R> {
|
||||
pub(crate) fn new(
|
||||
packets_receiver: ReceivedProcessorReceiver,
|
||||
client_encryption_keypair: Arc<encryption::KeyPair>,
|
||||
ack_key: Arc<AckKey>,
|
||||
) -> Self {
|
||||
let inner: Arc<Mutex<ReceivedProcessorInner<R>>> =
|
||||
Arc::new(Mutex::new(ReceivedProcessorInner {
|
||||
test_nonce: None,
|
||||
packets_receiver,
|
||||
client_encryption_keypair,
|
||||
message_receiver: R::new(),
|
||||
test_processor: TestPacketProcessor::new(client_encryption_keypair, ack_key),
|
||||
received_packets: Vec::new(),
|
||||
}));
|
||||
|
||||
@@ -150,13 +166,7 @@ impl<R: MessageReceiver + Send + 'static> ReceivedProcessor<R> {
|
||||
None => return,
|
||||
},
|
||||
messages = inner.packets_receiver.next() => match messages {
|
||||
Some(messages) => {
|
||||
for message in messages {
|
||||
if let Err(err) = inner.on_message(message) {
|
||||
warn!(target: "Monitor", "failed to process received gateway message - {err}")
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(messages) => inner.on_received(messages),
|
||||
None => return,
|
||||
},
|
||||
}
|
||||
@@ -210,7 +220,7 @@ impl<R: MessageReceiver + Send + 'static> ReceivedProcessor<R> {
|
||||
.expect("processing task has died!");
|
||||
}
|
||||
|
||||
pub(super) async fn return_received(&mut self) -> Vec<TestPacket> {
|
||||
pub(super) async fn return_received(&mut self) -> Vec<NodeTestMessage> {
|
||||
// ask for the lock back
|
||||
self.permit_changer
|
||||
.as_mut()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::network_monitor::gateways_reader::{GatewayMessages, GatewaysReader};
|
||||
@@ -66,12 +66,8 @@ impl PacketReceiver {
|
||||
// unwrap here is fine as it can only return a `None` if the PacketSender has died
|
||||
// and if that was the case, then the entire monitor is already in an undefined state
|
||||
update = self.clients_updater.next() => self.process_gateway_update(update.unwrap()),
|
||||
gateway_message = self.gateways_reader.next() => {
|
||||
let Some((_gateway_id, message)) = gateway_message else {
|
||||
log::error!("the gateways reader stream has terminated!");
|
||||
continue
|
||||
};
|
||||
self.process_gateway_messages(message)
|
||||
Some((_gateway_id, messages)) = self.gateways_reader.next() => {
|
||||
self.process_gateway_messages(messages)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::network_monitor::monitor::preparer::{InvalidNode, TestedNode};
|
||||
use crate::network_monitor::test_packet::{NodeType, TestPacket};
|
||||
use crate::network_monitor::monitor::preparer::InvalidNode;
|
||||
use crate::network_monitor::test_packet::NodeTestMessage;
|
||||
use crate::network_monitor::test_route::TestRoute;
|
||||
use nym_mixnet_contract_common::MixId;
|
||||
use nym_node_tester_utils::node::{NodeType, TestableNode};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
const INVALID_MIX_ID: u32 = u32::MAX;
|
||||
|
||||
// just some approximate measures to print to stdout (well, technically stderr since it's being printed via log)
|
||||
const EXCEPTIONAL_THRESHOLD: u8 = 95; // 95 - 100
|
||||
const FINE_THRESHOLD: u8 = 80; // 80 - 95
|
||||
@@ -60,12 +59,12 @@ impl GatewayResult {
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct RouteResult {
|
||||
pub(crate) route: TestRoute,
|
||||
reliability: u8,
|
||||
performance: f32,
|
||||
}
|
||||
|
||||
impl RouteResult {
|
||||
pub(crate) fn new(route: TestRoute, reliability: u8) -> Self {
|
||||
RouteResult { route, reliability }
|
||||
pub(crate) fn new(route: TestRoute, performance: f32) -> Self {
|
||||
RouteResult { route, performance }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,63 +178,63 @@ impl Display for TestReport {
|
||||
writeln!(
|
||||
f,
|
||||
"{:?}, reliability: {:.2}",
|
||||
route_result.route, route_result.reliability
|
||||
route_result.route, route_result.performance
|
||||
)?;
|
||||
}
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"Exceptional mixnodes (reliability >= {}): {}",
|
||||
EXCEPTIONAL_THRESHOLD, self.exceptional_mixnodes
|
||||
"Exceptional mixnodes (reliability >= {EXCEPTIONAL_THRESHOLD}): {}",
|
||||
self.exceptional_mixnodes
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Exceptional gateways (reliability >= {}): {}",
|
||||
EXCEPTIONAL_THRESHOLD, self.exceptional_gateways
|
||||
"Exceptional gateways (reliability >= {EXCEPTIONAL_THRESHOLD}): {}",
|
||||
self.exceptional_gateways
|
||||
)?;
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"Fine mixnodes (reliability {} - {}): {}",
|
||||
FINE_THRESHOLD, EXCEPTIONAL_THRESHOLD, self.fine_mixnodes
|
||||
"Fine mixnodes (reliability {FINE_THRESHOLD} - {EXCEPTIONAL_THRESHOLD}): {}",
|
||||
self.fine_mixnodes
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Fine gateways (reliability {} - {}): {}",
|
||||
FINE_THRESHOLD, EXCEPTIONAL_THRESHOLD, self.fine_gateways
|
||||
"Fine gateways (reliability {FINE_THRESHOLD} - {EXCEPTIONAL_THRESHOLD}): {}",
|
||||
self.fine_gateways
|
||||
)?;
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"Poor mixnodes (reliability {} - {}): {}",
|
||||
POOR_THRESHOLD, FINE_THRESHOLD, self.poor_mixnodes
|
||||
"Poor mixnodes (reliability {POOR_THRESHOLD} - {FINE_THRESHOLD}): {}",
|
||||
self.poor_mixnodes
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Poor gateways (reliability {} - {}): {}",
|
||||
POOR_THRESHOLD, FINE_THRESHOLD, self.poor_gateways
|
||||
"Poor gateways (reliability {POOR_THRESHOLD} - {FINE_THRESHOLD}): {}",
|
||||
self.poor_gateways
|
||||
)?;
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"Unreliable mixnodes (reliability {} - {}): {}",
|
||||
UNRELIABLE_THRESHOLD, POOR_THRESHOLD, self.unreliable_mixnodes
|
||||
"Unreliable mixnodes (reliability {UNRELIABLE_THRESHOLD} - {POOR_THRESHOLD}): {}",
|
||||
self.unreliable_mixnodes
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Unreliable gateways (reliability {} - {}): {}",
|
||||
UNRELIABLE_THRESHOLD, POOR_THRESHOLD, self.unreliable_gateways
|
||||
"Unreliable gateways (reliability {UNRELIABLE_THRESHOLD} - {POOR_THRESHOLD}): {}",
|
||||
self.unreliable_gateways
|
||||
)?;
|
||||
|
||||
writeln!(
|
||||
f,
|
||||
"Unroutable mixnodes (reliability < {}): {}",
|
||||
UNRELIABLE_THRESHOLD, self.unroutable_mixnodes
|
||||
"Unroutable mixnodes (reliability < {UNRELIABLE_THRESHOLD}): {}",
|
||||
self.unroutable_mixnodes
|
||||
)?;
|
||||
writeln!(
|
||||
f,
|
||||
"Unroutable gateways (reliability < {}): {}",
|
||||
UNRELIABLE_THRESHOLD, self.unroutable_gateways
|
||||
"Unroutable gateways (reliability < {UNRELIABLE_THRESHOLD}): {}",
|
||||
self.unroutable_gateways
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
@@ -281,108 +280,77 @@ impl SummaryProducer {
|
||||
|
||||
pub(super) fn produce_summary(
|
||||
&self,
|
||||
tested_mixnodes: Vec<TestedNode>,
|
||||
tested_gateways: Vec<TestedNode>,
|
||||
received_packets: Vec<TestPacket>,
|
||||
tested_mixnodes: Vec<TestableNode>,
|
||||
tested_gateways: Vec<TestableNode>,
|
||||
received_packets: Vec<NodeTestMessage>,
|
||||
invalid_mixnodes: Vec<InvalidNode>,
|
||||
invalid_gateways: Vec<InvalidNode>,
|
||||
test_routes: &[TestRoute],
|
||||
) -> TestSummary {
|
||||
let mut raw_mixnode_results = HashMap::new();
|
||||
let mut raw_gateway_results = HashMap::new();
|
||||
|
||||
let mut raw_route_results = HashMap::new();
|
||||
|
||||
// we expect each route to receive this many packets in the ideal world
|
||||
let per_route_expected =
|
||||
(tested_mixnodes.len() + tested_gateways.len()) * self.per_node_test_packets;
|
||||
let per_node_expected = test_routes.len() * self.per_node_test_packets;
|
||||
|
||||
// TODO: whenever somebody feels like it, this should really get refactored.
|
||||
// probably some wrapper struct would be appropriate here.
|
||||
for tested_mixnode in tested_mixnodes {
|
||||
raw_mixnode_results.insert(
|
||||
(
|
||||
tested_mixnode.mix_id().unwrap_or(INVALID_MIX_ID),
|
||||
tested_mixnode.identity,
|
||||
tested_mixnode.owner,
|
||||
),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
for tested_gateway in tested_gateways {
|
||||
raw_gateway_results.insert((tested_gateway.identity, tested_gateway.owner), 0);
|
||||
}
|
||||
|
||||
for invalid_mixnode in invalid_mixnodes {
|
||||
raw_mixnode_results.insert(
|
||||
(
|
||||
invalid_mixnode.mix_id().unwrap_or(INVALID_MIX_ID),
|
||||
invalid_mixnode.identity(),
|
||||
invalid_mixnode.owner(),
|
||||
),
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
for invalid_gateway in invalid_gateways {
|
||||
raw_gateway_results.insert((invalid_gateway.identity(), invalid_gateway.owner()), 0);
|
||||
}
|
||||
|
||||
let mut raw_route_results = HashMap::new();
|
||||
for test_route in test_routes {
|
||||
raw_route_results.insert(test_route.id(), 0);
|
||||
}
|
||||
|
||||
for received in received_packets {
|
||||
let pub_key = received.pub_key.to_base58_string();
|
||||
let mut raw_results = HashMap::new();
|
||||
|
||||
match received.node_type {
|
||||
NodeType::Mixnode(mix_id) => {
|
||||
*raw_mixnode_results
|
||||
.entry((mix_id, pub_key, received.owner))
|
||||
.or_default() += 1usize;
|
||||
}
|
||||
NodeType::Gateway => {
|
||||
*raw_gateway_results
|
||||
.entry((pub_key, received.owner))
|
||||
.or_default() += 1usize;
|
||||
}
|
||||
}
|
||||
|
||||
*raw_route_results.entry(received.route_id).or_default() += 1usize;
|
||||
for tested_mixnode in tested_mixnodes {
|
||||
raw_results.insert(tested_mixnode, 0);
|
||||
}
|
||||
|
||||
let mixnode_results = raw_mixnode_results
|
||||
.into_iter()
|
||||
.map(|((mix_id, identity_key, owner), received)| {
|
||||
let reliability =
|
||||
(received as f32 / per_node_expected as f32 * 100.0).round() as u8;
|
||||
MixnodeResult::new(mix_id, identity_key, owner, reliability)
|
||||
})
|
||||
.collect();
|
||||
for tested_gateway in tested_gateways {
|
||||
raw_results.insert(tested_gateway, 0);
|
||||
}
|
||||
|
||||
let gateway_results = raw_gateway_results
|
||||
.into_iter()
|
||||
.map(|((identity_key, owner), received)| {
|
||||
let reliability =
|
||||
(received as f32 / per_node_expected as f32 * 100.0).round() as u8;
|
||||
GatewayResult::new(identity_key, owner, reliability)
|
||||
})
|
||||
.collect();
|
||||
for invalid_mixnode in invalid_mixnodes {
|
||||
raw_results.insert(invalid_mixnode.into(), 0);
|
||||
}
|
||||
|
||||
for invalid_gateway in invalid_gateways {
|
||||
raw_results.insert(invalid_gateway.into(), 0);
|
||||
}
|
||||
|
||||
for received in received_packets {
|
||||
*raw_results.entry(received.tested_node).or_default() += 1usize;
|
||||
*raw_route_results.entry(received.ext.route_id).or_default() += 1usize;
|
||||
}
|
||||
|
||||
let mut mixnode_results = Vec::new();
|
||||
let mut gateway_results = Vec::new();
|
||||
|
||||
for (node, received) in raw_results {
|
||||
let performance = received as f32 / per_node_expected as f32 * 100.0;
|
||||
let reliability = performance.round() as u8;
|
||||
|
||||
match node.typ {
|
||||
NodeType::Mixnode { mix_id } => {
|
||||
let res =
|
||||
MixnodeResult::new(mix_id, node.encoded_identity, node.owner, reliability);
|
||||
mixnode_results.push(res)
|
||||
}
|
||||
NodeType::Gateway => {
|
||||
let res = GatewayResult::new(node.encoded_identity, node.owner, reliability);
|
||||
gateway_results.push(res)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let route_results = raw_route_results
|
||||
.into_iter()
|
||||
.filter_map(|(id, received)| {
|
||||
let reliability =
|
||||
(received as f32 / per_route_expected as f32 * 100.0).round() as u8;
|
||||
let performance = received as f32 / per_route_expected as f32 * 100.0;
|
||||
|
||||
// this might be suboptimal as we're going through the entire slice every time
|
||||
// but realistically this slice will never have more than ~ 10 elements AT MOST
|
||||
test_routes
|
||||
.iter()
|
||||
.find(|route| route.id() == id)
|
||||
.map(|route| RouteResult::new(route.clone(), reliability))
|
||||
.map(|route| RouteResult::new(route.clone(), performance))
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
||||
@@ -1,260 +1,32 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::network_monitor::monitor::preparer::TestedNode;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
|
||||
use nym_mixnet_contract_common::MixId;
|
||||
use nym_topology::{gateway, mix};
|
||||
use std::convert::TryInto;
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::mem;
|
||||
use std::str::Utf8Error;
|
||||
use thiserror::Error;
|
||||
use nym_node_tester_utils::error::NetworkTestingError;
|
||||
use nym_node_tester_utils::TestMessage;
|
||||
use nym_topology::mix;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
const MIXNODE_TYPE: u8 = 0;
|
||||
const GATEWAY_TYPE: u8 = 1;
|
||||
pub(crate) type NodeTestMessage = TestMessage<NymApiTestMessageExt>;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Eq, PartialEq, Debug, Hash, Clone, Copy)]
|
||||
pub(crate) enum NodeType {
|
||||
Mixnode(MixId),
|
||||
Gateway,
|
||||
}
|
||||
|
||||
impl NodeType {
|
||||
fn size(&self) -> usize {
|
||||
match self {
|
||||
NodeType::Mixnode(_) => 1 + mem::size_of::<MixId>(),
|
||||
NodeType::Gateway => 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn mix_id(&self) -> Option<MixId> {
|
||||
match self {
|
||||
NodeType::Mixnode(mix_id) => Some(*mix_id),
|
||||
NodeType::Gateway => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn into_bytes(self) -> Vec<u8> {
|
||||
match self {
|
||||
NodeType::Mixnode(mix_id) => {
|
||||
let mut bytes = Vec::with_capacity(5);
|
||||
bytes.push(MIXNODE_TYPE);
|
||||
bytes.extend_from_slice(&mix_id.to_be_bytes());
|
||||
bytes
|
||||
}
|
||||
NodeType::Gateway => vec![GATEWAY_TYPE],
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_from_bytes(b: &[u8]) -> Result<Self, TestPacketError> {
|
||||
if b.is_empty() {
|
||||
return Err(TestPacketError::InvalidNodeType);
|
||||
}
|
||||
match b[0] {
|
||||
t if t == MIXNODE_TYPE => {
|
||||
if b.len() < (1 + mem::size_of::<MixId>()) {
|
||||
return Err(TestPacketError::InvalidNodeType);
|
||||
}
|
||||
Ok(NodeType::Mixnode(MixId::from_be_bytes(
|
||||
b[1..1 + mem::size_of::<MixId>()].try_into().unwrap(),
|
||||
)))
|
||||
}
|
||||
t if t == GATEWAY_TYPE => Ok(NodeType::Gateway),
|
||||
_ => Err(TestPacketError::InvalidNodeType),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(crate) enum TestPacketError {
|
||||
#[error(
|
||||
"the received packet was incomplete. Got {received} bytes but expected at least {min_expected}"
|
||||
)]
|
||||
IncompletePacket {
|
||||
received: usize,
|
||||
min_expected: usize,
|
||||
},
|
||||
|
||||
// TODO: ideally this should contain more information but that'd require more refactoring than I'm willing to commit to now
|
||||
#[error("the received packet did not contain a valid node type")]
|
||||
InvalidNodeType,
|
||||
|
||||
#[error("the received node identity key was malformed - {0}")]
|
||||
InvalidNodeKey(#[from] Ed25519RecoveryError),
|
||||
|
||||
#[error("the received packet contained malformed owner data - {0}")]
|
||||
InvalidOwner(#[from] Utf8Error),
|
||||
}
|
||||
|
||||
#[derive(Eq, Clone, Debug)]
|
||||
pub(crate) struct TestPacket {
|
||||
#[derive(Serialize, Deserialize, Clone, Copy, Hash)]
|
||||
pub(crate) struct NymApiTestMessageExt {
|
||||
pub(crate) route_id: u64,
|
||||
pub(crate) test_nonce: u64,
|
||||
pub(crate) pub_key: identity::PublicKey,
|
||||
pub(crate) owner: String,
|
||||
pub(crate) node_type: NodeType,
|
||||
}
|
||||
|
||||
impl Display for TestPacket {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"TestPacket {{ pub_key: {}, owner: {}, route: {} test nonce: {} }}",
|
||||
self.pub_key.to_base58_string(),
|
||||
self.owner,
|
||||
self.route_id,
|
||||
self.test_nonce
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for TestPacket {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.route_id.hash(state);
|
||||
self.test_nonce.hash(state);
|
||||
self.pub_key.to_bytes().hash(state);
|
||||
self.owner.hash(state);
|
||||
self.node_type.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for TestPacket {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.route_id == other.route_id
|
||||
&& self.test_nonce == other.test_nonce
|
||||
&& self.pub_key.to_bytes() == other.pub_key.to_bytes()
|
||||
&& self.owner == other.owner
|
||||
&& self.node_type == other.node_type
|
||||
}
|
||||
}
|
||||
|
||||
impl TestPacket {
|
||||
pub(crate) fn from_mixnode(mix: &mix::Node, route_id: u64, test_nonce: u64) -> Self {
|
||||
TestPacket {
|
||||
pub_key: mix.identity_key,
|
||||
owner: mix.owner.clone(),
|
||||
impl NymApiTestMessageExt {
|
||||
pub fn new(route_id: u64, test_nonce: u64) -> Self {
|
||||
NymApiTestMessageExt {
|
||||
route_id,
|
||||
test_nonce,
|
||||
node_type: NodeType::Mixnode(mix.mix_id),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_gateway(gateway: &gateway::Node, route_id: u64, test_nonce: u64) -> Self {
|
||||
TestPacket {
|
||||
pub_key: gateway.identity_key,
|
||||
owner: gateway.owner.clone(),
|
||||
route_id,
|
||||
test_nonce,
|
||||
node_type: NodeType::Gateway,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
pub_key: identity::PublicKey,
|
||||
owner: String,
|
||||
route_id: u64,
|
||||
test_nonce: u64,
|
||||
node_type: NodeType,
|
||||
) -> Self {
|
||||
TestPacket {
|
||||
route_id,
|
||||
test_nonce,
|
||||
pub_key,
|
||||
owner,
|
||||
node_type,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn test_nonce(&self) -> u64 {
|
||||
self.test_nonce
|
||||
}
|
||||
|
||||
pub(crate) fn to_bytes(&self) -> Vec<u8> {
|
||||
IntoIterator::into_iter(self.route_id.to_be_bytes())
|
||||
.chain(IntoIterator::into_iter(self.test_nonce.to_be_bytes()))
|
||||
.chain(self.node_type.into_bytes().iter().cloned())
|
||||
.chain(self.pub_key.to_bytes().iter().cloned())
|
||||
.chain(self.owner.as_bytes().iter().cloned())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn try_from_bytes(b: &[u8]) -> Result<Self, TestPacketError> {
|
||||
// route id + test nonce size
|
||||
let n = mem::size_of::<u64>();
|
||||
|
||||
if b.len() < 2 * n + 1 + identity::PUBLIC_KEY_LENGTH {
|
||||
return Err(TestPacketError::IncompletePacket {
|
||||
received: b.len(),
|
||||
min_expected: 2 * n + 1 + identity::PUBLIC_KEY_LENGTH,
|
||||
});
|
||||
}
|
||||
|
||||
// those unwraps can't fail as we've already checked for the size
|
||||
let route_id = u64::from_be_bytes(b[0..n].try_into().unwrap());
|
||||
let test_nonce = u64::from_be_bytes(b[n..2 * n].try_into().unwrap());
|
||||
let node_type = NodeType::try_from_bytes(&b[2 * n..])?;
|
||||
let type_size = node_type.size();
|
||||
|
||||
let pub_key = identity::PublicKey::from_bytes(
|
||||
&b[2 * n + type_size..2 * n + type_size + identity::PUBLIC_KEY_LENGTH],
|
||||
)?;
|
||||
let owner = std::str::from_utf8(&b[2 * n + type_size + identity::PUBLIC_KEY_LENGTH..])?;
|
||||
|
||||
Ok(TestPacket {
|
||||
route_id,
|
||||
node_type,
|
||||
test_nonce,
|
||||
pub_key,
|
||||
owner: owner.to_owned(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TestPacket> for TestedNode {
|
||||
fn from(packet: TestPacket) -> Self {
|
||||
TestedNode {
|
||||
identity: packet.pub_key.to_base58_string(),
|
||||
owner: packet.owner,
|
||||
node_type: packet.node_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_packet_roundtrip() {
|
||||
let mut rng = rand_07::thread_rng();
|
||||
let dummy_keypair = identity::KeyPair::new(&mut rng);
|
||||
let owner = "some owner".to_string();
|
||||
let mix_packet = TestPacket::new(
|
||||
*dummy_keypair.public_key(),
|
||||
owner.clone(),
|
||||
42,
|
||||
123,
|
||||
NodeType::Mixnode(1234),
|
||||
);
|
||||
|
||||
let bytes = mix_packet.to_bytes();
|
||||
let recovered = TestPacket::try_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(mix_packet, recovered);
|
||||
|
||||
let gateway_packet = TestPacket::new(
|
||||
*dummy_keypair.public_key(),
|
||||
owner,
|
||||
42,
|
||||
123,
|
||||
NodeType::Gateway,
|
||||
);
|
||||
|
||||
let bytes = gateway_packet.to_bytes();
|
||||
let recovered = TestPacket::try_from_bytes(&bytes).unwrap();
|
||||
assert_eq!(gateway_packet, recovered);
|
||||
pub fn mix_plaintexts(
|
||||
&self,
|
||||
node: &mix::Node,
|
||||
test_packets: u32,
|
||||
) -> Result<Vec<Vec<u8>>, NetworkTestingError> {
|
||||
NodeTestMessage::mix_plaintexts(node, test_packets, *self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::network_monitor::test_packet::{NodeType, TestPacket};
|
||||
use crate::network_monitor::test_packet::NymApiTestMessageExt;
|
||||
use crate::network_monitor::ROUTE_TESTING_TEST_NONCE;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_topology::{gateway, mix, NymTopology};
|
||||
@@ -10,14 +10,12 @@ use std::fmt::{Debug, Formatter};
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct TestRoute {
|
||||
id: u64,
|
||||
system_version: String,
|
||||
nodes: NymTopology,
|
||||
}
|
||||
|
||||
impl TestRoute {
|
||||
pub(crate) fn new(
|
||||
id: u64,
|
||||
system_version: &str,
|
||||
l1_mix: mix::Node,
|
||||
l2_mix: mix::Node,
|
||||
l3_mix: mix::Node,
|
||||
@@ -33,7 +31,6 @@ impl TestRoute {
|
||||
|
||||
TestRoute {
|
||||
id,
|
||||
system_version: system_version.to_string(),
|
||||
nodes: NymTopology::new(layered_mixes, vec![gateway]),
|
||||
}
|
||||
}
|
||||
@@ -70,30 +67,21 @@ impl TestRoute {
|
||||
&self.nodes
|
||||
}
|
||||
|
||||
pub(crate) fn self_test_packet(&self) -> TestPacket {
|
||||
pub(crate) fn test_message_ext(&self, test_nonce: u64) -> NymApiTestMessageExt {
|
||||
NymApiTestMessageExt::new(self.id, test_nonce)
|
||||
}
|
||||
|
||||
pub(crate) fn self_test_messages(&self, count: usize) -> Vec<Vec<u8>> {
|
||||
// it doesn't really matter which node is "chosen" as the packet has to always
|
||||
// go through the same sequence of hops.
|
||||
// let's just use layer 1 mixnode for this (this choice is completely arbitrary)
|
||||
let mix = &self.nodes.mixes()[&1][0];
|
||||
TestPacket::new(
|
||||
mix.identity_key,
|
||||
mix.owner.clone(),
|
||||
self.id,
|
||||
ROUTE_TESTING_TEST_NONCE,
|
||||
NodeType::Mixnode(mix.mix_id),
|
||||
)
|
||||
}
|
||||
let mix = self.layer_one_mix();
|
||||
|
||||
pub(crate) fn substitute_mix(&self, node: &mix::Node) -> NymTopology {
|
||||
let mut topology = self.nodes.clone();
|
||||
topology.set_mixes_in_layer(node.layer as u8, vec![node.clone()]);
|
||||
topology
|
||||
}
|
||||
|
||||
pub(crate) fn substitute_gateway(&self, gateway: &gateway::Node) -> NymTopology {
|
||||
let mut topology = self.nodes.clone();
|
||||
topology.set_gateways(vec![gateway.clone()]);
|
||||
topology
|
||||
// the unwrap here is fine as the failure can only occur due to serialization and we're not
|
||||
// using any custom implementations
|
||||
NymApiTestMessageExt::new(self.id, ROUTE_TESTING_TEST_NONCE)
|
||||
.mix_plaintexts(mix, count as u32)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,19 +89,12 @@ impl Debug for TestRoute {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"[v{}] Route {}: [G] {} => [M1] {} => [M2] {} => [M3] {}",
|
||||
self.system_version,
|
||||
"Route {}: [G] {} => [M1] {} => [M2] {} => [M3] {}",
|
||||
self.id,
|
||||
self.nodes.gateways()[0].identity().to_base58_string(),
|
||||
self.nodes.mixes_in_layer(1)[0]
|
||||
.identity_key
|
||||
.to_base58_string(),
|
||||
self.nodes.mixes_in_layer(2)[0]
|
||||
.identity_key
|
||||
.to_base58_string(),
|
||||
self.nodes.mixes_in_layer(3)[0]
|
||||
.identity_key
|
||||
.to_base58_string()
|
||||
self.gateway().identity().to_base58_string(),
|
||||
self.layer_one_mix().identity_key.to_base58_string(),
|
||||
self.layer_two_mix().identity_key.to_base58_string(),
|
||||
self.layer_three_mix().identity_key.to_base58_string()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use nym_mixnet_contract_common::{
|
||||
families::FamilyHead, GatewayBond, IdentityKey, Interval, MixId, MixNodeDetails,
|
||||
RewardingParams,
|
||||
};
|
||||
use nym_service_provider_directory_common::ServiceInfo;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub(crate) struct ValidatorCacheData {
|
||||
@@ -22,6 +23,8 @@ pub(crate) struct ValidatorCacheData {
|
||||
pub(crate) current_interval: Cache<Option<Interval>>,
|
||||
|
||||
pub(crate) mix_to_family: Cache<Vec<(IdentityKey, FamilyHead)>>,
|
||||
|
||||
pub(crate) service_providers: Cache<Vec<ServiceInfo>>,
|
||||
}
|
||||
|
||||
impl ValidatorCacheData {
|
||||
@@ -36,6 +39,7 @@ impl ValidatorCacheData {
|
||||
current_interval: Cache::default(),
|
||||
current_reward_params: Cache::default(),
|
||||
mix_to_family: Cache::default(),
|
||||
service_providers: Cache::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+14
-1
@@ -5,6 +5,7 @@ use nym_mixnet_contract_common::{
|
||||
families::FamilyHead, GatewayBond, IdentityKey, Interval, MixId, MixNodeBond, MixNodeDetails,
|
||||
RewardingParams,
|
||||
};
|
||||
use nym_service_provider_directory_common::ServiceInfo;
|
||||
use rocket::fairing::AdHoc;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
@@ -50,6 +51,7 @@ impl NymContractCache {
|
||||
rewarding_params: RewardingParams,
|
||||
current_interval: Interval,
|
||||
mix_to_family: Vec<(IdentityKey, FamilyHead)>,
|
||||
services: Option<Vec<ServiceInfo>>,
|
||||
) {
|
||||
match time::timeout(Duration::from_millis(100), self.inner.write()).await {
|
||||
Ok(mut cache) => {
|
||||
@@ -59,7 +61,8 @@ impl NymContractCache {
|
||||
cache.active_set.update(active_set);
|
||||
cache.current_reward_params.update(Some(rewarding_params));
|
||||
cache.current_interval.update(Some(current_interval));
|
||||
cache.mix_to_family.update(mix_to_family)
|
||||
cache.mix_to_family.update(mix_to_family);
|
||||
cache.service_providers.update(services.unwrap_or_default());
|
||||
}
|
||||
Err(err) => {
|
||||
error!("{err}");
|
||||
@@ -286,6 +289,16 @@ impl NymContractCache {
|
||||
self.mixnode_details(mix_id).await.1
|
||||
}
|
||||
|
||||
pub(crate) async fn services(&self) -> Cache<Vec<ServiceInfo>> {
|
||||
match time::timeout(Duration::from_millis(100), self.inner.read()).await {
|
||||
Ok(cache) => cache.service_providers.clone(),
|
||||
Err(err) => {
|
||||
error!("{err}");
|
||||
Cache::new(Vec::new())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn initialised(&self) -> bool {
|
||||
self.initialised.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::support::caching::CacheNotification;
|
||||
use anyhow::Result;
|
||||
use nym_mixnet_contract_common::{MixId, MixNodeDetails, RewardedSetNodeStatus};
|
||||
use nym_task::TaskClient;
|
||||
use nym_validator_client::nyxd::traits::SpDirectoryQueryClient;
|
||||
use std::{collections::HashMap, sync::atomic::Ordering, time::Duration};
|
||||
use tokio::sync::watch;
|
||||
use tokio::time;
|
||||
@@ -50,6 +51,9 @@ impl NymContractCacheRefresher {
|
||||
let (rewarded_set, active_set) =
|
||||
Self::collect_rewarded_and_active_set_details(&mixnodes, &rewarded_set_map);
|
||||
|
||||
// The service providers are optional
|
||||
let services = self.nyxd_client.get_all_services().await.ok();
|
||||
|
||||
info!(
|
||||
"Updating validator cache. There are {} mixnodes and {} gateways",
|
||||
mixnodes.len(),
|
||||
@@ -65,6 +69,7 @@ impl NymContractCacheRefresher {
|
||||
rewarding_params,
|
||||
current_interval,
|
||||
mix_to_family,
|
||||
services,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -27,7 +27,8 @@ pub(crate) fn nym_contract_cache_routes(settings: &OpenApiSettings) -> (Vec<Rout
|
||||
routes::get_blacklisted_mixnodes,
|
||||
routes::get_blacklisted_gateways,
|
||||
routes::get_interval_reward_params,
|
||||
routes::get_current_epoch
|
||||
routes::get_current_epoch,
|
||||
routes::get_services
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ use nym_mixnet_contract_common::{
|
||||
mixnode::MixNodeDetails, reward_params::RewardingParams, GatewayBond, Interval, MixId,
|
||||
};
|
||||
|
||||
use nym_service_provider_directory_common::response::ServicesListResponse;
|
||||
use rocket::{serde::json::Json, State};
|
||||
use rocket_okapi::openapi;
|
||||
use std::collections::HashSet;
|
||||
@@ -125,3 +126,10 @@ pub async fn get_interval_reward_params(
|
||||
pub async fn get_current_epoch(cache: &State<NymContractCache>) -> Json<Option<Interval>> {
|
||||
Json(cache.current_interval().await.value)
|
||||
}
|
||||
|
||||
#[openapi(tag = "contract-cache")]
|
||||
#[get("/services")]
|
||||
pub async fn get_services(cache: &State<NymContractCache>) -> Json<ServicesListResponse> {
|
||||
let services = cache.services().await.value;
|
||||
Json(services.as_slice().into())
|
||||
}
|
||||
|
||||
@@ -25,8 +25,11 @@ use nym_mixnet_contract_common::{
|
||||
CurrentIntervalResponse, EpochStatus, ExecuteMsg, GatewayBond, IdentityKey, LayerAssignment,
|
||||
MixId, RewardedSetNodeStatus,
|
||||
};
|
||||
use nym_service_provider_directory_common::msg::QueryMsg as SpQueryMsg;
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
use nym_validator_client::nyxd::traits::{MixnetQueryClient, MixnetSigningClient};
|
||||
use nym_validator_client::nyxd::traits::{
|
||||
MixnetQueryClient, MixnetSigningClient, SpDirectoryQueryClient,
|
||||
};
|
||||
use nym_validator_client::nyxd::{
|
||||
cosmwasm_client::types::ExecuteResult,
|
||||
traits::{
|
||||
@@ -497,3 +500,21 @@ impl DkgQueryClient for Client {
|
||||
self.0.read().await.nyxd.query_dkg_contract(query).await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SpDirectoryQueryClient for Client {
|
||||
async fn query_service_provider_contract<T>(
|
||||
&self,
|
||||
query: SpQueryMsg,
|
||||
) -> std::result::Result<T, NyxdError>
|
||||
where
|
||||
for<'a> T: Deserialize<'a>,
|
||||
{
|
||||
self.0
|
||||
.read()
|
||||
.await
|
||||
.nyxd
|
||||
.query_service_provider_contract(query)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import ContractCache from "../../src/endpoints/ContractCache";
|
||||
import ConfigHandler from "../../src/config/configHandler";
|
||||
|
||||
let contract: ContractCache;
|
||||
let config: ConfigHandler;
|
||||
|
||||
describe("Get service provider info", (): void => {
|
||||
beforeAll(async (): Promise<void> => {
|
||||
contract = new ContractCache();
|
||||
config = ConfigHandler.getInstance();
|
||||
});
|
||||
|
||||
it("Get service providers", async (): Promise<void> => {
|
||||
const response = await contract.getServiceProviders();
|
||||
if ("[service_id]" in response) {
|
||||
response.services.forEach((x) => {
|
||||
expect(typeof x.service.nym_address.address).toBe("string");
|
||||
expect(typeof x.service.service_type).toBe("string");
|
||||
expect(typeof x.service.block_height).toBe("number");
|
||||
expect(typeof x.service.announcer).toBe("string");
|
||||
expect(typeof x.service.deposit.amount).toBe("string");
|
||||
expect(typeof x.service.deposit.denom).toBe("string");
|
||||
});
|
||||
} else if ("[ ]" in response) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
AllMixnodes,
|
||||
EpochRewardParams,
|
||||
CurrentEpoch,
|
||||
ServiceProviders,
|
||||
} from "../types/ContractCacheTypes";
|
||||
import { APIClient } from "./abstracts/APIClient";
|
||||
|
||||
@@ -89,4 +90,11 @@ export default class ContractCache extends APIClient {
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getServiceProviders(): Promise<ServiceProviders> {
|
||||
const response = await this.restClient.sendGet({
|
||||
route: `services`,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,3 +123,26 @@ export interface EpochLength {
|
||||
secs: number;
|
||||
nanos: number;
|
||||
}
|
||||
|
||||
export interface ServiceProviders {
|
||||
services: (Services)[];
|
||||
}
|
||||
export interface Services {
|
||||
service_id: number;
|
||||
service: Service;
|
||||
}
|
||||
export interface Service {
|
||||
nym_address: NymAddress;
|
||||
service_type: string;
|
||||
announcer: string;
|
||||
block_height: number;
|
||||
deposit: Deposit;
|
||||
}
|
||||
export interface NymAddress {
|
||||
address: string;
|
||||
}
|
||||
export interface Deposit {
|
||||
denom: string;
|
||||
amount: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -2457,10 +2457,10 @@ prettier-linter-helpers@^1.0.0:
|
||||
dependencies:
|
||||
fast-diff "^1.1.2"
|
||||
|
||||
prettier@2.7.1:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz"
|
||||
integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==
|
||||
prettier@^2.8.7:
|
||||
version "2.8.8"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
|
||||
integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
|
||||
|
||||
pretty-format@^28.0.0, pretty-format@^28.1.3:
|
||||
version "28.1.3"
|
||||
|
||||
Generated
+22
-1
@@ -3392,7 +3392,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-crypto"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"aes 0.8.2",
|
||||
"blake3",
|
||||
@@ -3578,6 +3578,15 @@ dependencies = [
|
||||
"pem",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-service-provider-directory-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-service-providers-common"
|
||||
version = "0.1.0"
|
||||
@@ -3657,6 +3666,7 @@ dependencies = [
|
||||
"nym-sphinx-forwarding",
|
||||
"nym-sphinx-framing",
|
||||
"nym-sphinx-params",
|
||||
"nym-sphinx-routing",
|
||||
"nym-sphinx-types",
|
||||
"nym-topology",
|
||||
"rand 0.7.3",
|
||||
@@ -3764,6 +3774,15 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-sphinx-routing"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"nym-sphinx-addressing",
|
||||
"nym-sphinx-types",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-sphinx-types"
|
||||
version = "0.2.0"
|
||||
@@ -3794,6 +3813,7 @@ dependencies = [
|
||||
"nym-crypto",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-sphinx-addressing",
|
||||
"nym-sphinx-routing",
|
||||
"nym-sphinx-types",
|
||||
"rand 0.7.3",
|
||||
"thiserror",
|
||||
@@ -3826,6 +3846,7 @@ dependencies = [
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-multisig-contract-common",
|
||||
"nym-network-defaults",
|
||||
"nym-service-provider-directory-common",
|
||||
"nym-vesting-contract",
|
||||
"nym-vesting-contract-common",
|
||||
"prost",
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v1.2.2] (2023-05-02)
|
||||
|
||||
- Wallet - change "Bond more" action to "Change bond amount" to allow for increasing and decreasing the amount in the same modal ([#2134])
|
||||
- Split wallet sign in and main into two entry points ([#3188])
|
||||
|
||||
[#2134]: https://github.com/nymtech/nym/issues/2134
|
||||
[#3188]: https://github.com/nymtech/nym/pull/3188
|
||||
|
||||
## [v1.2.1] (2023-04-18)
|
||||
|
||||
- [bug] bonding signature with vesting account ([#3315])
|
||||
|
||||
Generated
+95
-54
@@ -16,10 +16,11 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aead"
|
||||
version = "0.4.3"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877"
|
||||
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"generic-array 0.14.6",
|
||||
]
|
||||
|
||||
@@ -30,22 +31,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher",
|
||||
"cipher 0.3.0",
|
||||
"cpufeatures",
|
||||
"ctr",
|
||||
"ctr 0.8.0",
|
||||
"opaque-debug 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes-gcm"
|
||||
version = "0.9.4"
|
||||
name = "aes"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df5f85a83a7d8b0442b6aa7b504b8212c1733da07b98aae43d4bc21b2cb3cdf6"
|
||||
checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cipher 0.4.4",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aes-gcm"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c"
|
||||
dependencies = [
|
||||
"aead",
|
||||
"aes",
|
||||
"cipher",
|
||||
"ctr",
|
||||
"aes 0.8.2",
|
||||
"cipher 0.4.4",
|
||||
"ctr 0.9.2",
|
||||
"ghash",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
@@ -93,24 +105,13 @@ checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
version = "0.3.4"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25df3c03f1040d0069fcd3907e24e36d59f9b6fa07ba49be0eb25a794f036ba7"
|
||||
checksum = "95c2fcf79ad1932ac6269a738109997a83c227c09b75842ae564dc8ede6a861c"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"blake2 0.10.6",
|
||||
"password-hash 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "argon2"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a27e27b63e4a34caee411ade944981136fdfa535522dc9944d6700196cbd899f"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"blake2 0.10.6",
|
||||
"password-hash 0.4.2",
|
||||
"password-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -505,6 +506,16 @@ dependencies = [
|
||||
"generic-array 0.14.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
|
||||
dependencies = [
|
||||
"crypto-common",
|
||||
"inout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.1.4"
|
||||
@@ -816,6 +827,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array 0.14.6",
|
||||
"rand_core 0.6.4",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
@@ -891,7 +903,16 @@ version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea"
|
||||
dependencies = [
|
||||
"cipher",
|
||||
"cipher 0.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctr"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835"
|
||||
dependencies = [
|
||||
"cipher 0.4.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1672,9 +1693,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ghash"
|
||||
version = "0.4.4"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99"
|
||||
checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40"
|
||||
dependencies = [
|
||||
"opaque-debug 0.3.0",
|
||||
"polyval",
|
||||
@@ -2239,6 +2260,15 @@ dependencies = [
|
||||
"cfb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inout"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
|
||||
dependencies = [
|
||||
"generic-array 0.14.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
@@ -2872,7 +2902,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-crypto"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"ed25519-dalek",
|
||||
@@ -2966,6 +2996,15 @@ dependencies = [
|
||||
"pem",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-service-provider-directory-common"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cosmwasm-std",
|
||||
"schemars",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-sphinx-types"
|
||||
version = "0.2.0"
|
||||
@@ -2973,6 +3012,20 @@ dependencies = [
|
||||
"sphinx-packet",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-store-cipher"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"argon2",
|
||||
"getrandom 0.2.8",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nym-types"
|
||||
version = "1.0.0"
|
||||
@@ -3025,6 +3078,7 @@ dependencies = [
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-multisig-contract-common",
|
||||
"nym-network-defaults",
|
||||
"nym-service-provider-directory-common",
|
||||
"nym-vesting-contract",
|
||||
"nym-vesting-contract-common",
|
||||
"prost",
|
||||
@@ -3071,14 +3125,13 @@ dependencies = [
|
||||
name = "nym-wallet-recovery-cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"anyhow",
|
||||
"argon2 0.4.0",
|
||||
"base64 0.13.1",
|
||||
"bip39",
|
||||
"clap",
|
||||
"log",
|
||||
"nym-bin-common",
|
||||
"nym-store-cipher",
|
||||
"pretty_env_logger",
|
||||
"serde_json",
|
||||
]
|
||||
@@ -3105,10 +3158,8 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym_wallet"
|
||||
version = "1.2.1"
|
||||
version = "1.2.2"
|
||||
dependencies = [
|
||||
"aes-gcm",
|
||||
"argon2 0.3.4",
|
||||
"async-trait",
|
||||
"base64 0.13.1",
|
||||
"bip39",
|
||||
@@ -3129,6 +3180,7 @@ dependencies = [
|
||||
"nym-contracts-common",
|
||||
"nym-crypto",
|
||||
"nym-mixnet-contract-common",
|
||||
"nym-store-cipher",
|
||||
"nym-types",
|
||||
"nym-validator-client",
|
||||
"nym-vesting-contract",
|
||||
@@ -3340,20 +3392,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "password-hash"
|
||||
version = "0.3.2"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d791538a6dcc1e7cb7fe6f6b58aca40e7f79403c45b2bc274008b5e647af1d8"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"rand_core 0.6.4",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "password-hash"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
|
||||
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"rand_core 0.6.4",
|
||||
@@ -3644,9 +3685,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "polyval"
|
||||
version = "0.5.3"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1"
|
||||
checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
@@ -4547,7 +4588,7 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc43eda802856ee82a7555c7b75ceb9e07451741c7a2f5f23d036020e01189d4"
|
||||
dependencies = [
|
||||
"aes",
|
||||
"aes 0.7.5",
|
||||
"arrayref",
|
||||
"blake2 0.8.1",
|
||||
"bs58",
|
||||
@@ -5433,11 +5474,11 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "universal-hash"
|
||||
version = "0.4.1"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05"
|
||||
checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5"
|
||||
dependencies = [
|
||||
"generic-array 0.14.6",
|
||||
"crypto-common",
|
||||
"subtle 2.4.1",
|
||||
]
|
||||
|
||||
@@ -6079,9 +6120,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.5.7"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
|
||||
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"zeroize_derive",
|
||||
|
||||
@@ -6,9 +6,7 @@ edition = "2021"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
aes-gcm = "0.9"
|
||||
anyhow = "1.0"
|
||||
argon2 = "0.4"
|
||||
base64 = "0.13"
|
||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||
clap = { version = "4.0", features = ["derive"] }
|
||||
@@ -17,3 +15,4 @@ pretty_env_logger = "0.4"
|
||||
serde_json = "1.0.0"
|
||||
|
||||
nym-bin-common = { path = "../../common/bin-common" }
|
||||
nym-store-cipher = { path = "../../common/store-cipher" }
|
||||
@@ -6,14 +6,15 @@
|
||||
// code. Furthermore we don't to strongly typed json parsing, all with the intention of being a
|
||||
// little more flexible when interpreting the results.
|
||||
|
||||
use std::fs::File;
|
||||
|
||||
use aes_gcm::{aead::Aead, Aes256Gcm, Key, NewAead, Nonce};
|
||||
use anyhow::{anyhow, Result};
|
||||
use argon2::{Algorithm, Argon2, Params, Version};
|
||||
use clap::Parser;
|
||||
use nym_bin_common::logging::setup_logging;
|
||||
use nym_store_cipher::{
|
||||
Aes256Gcm, Algorithm, EncryptedData, KdfInfo, Params, StoreCipher, Version, ARGON2_SALT_SIZE,
|
||||
CURRENT_VERSION,
|
||||
};
|
||||
use serde_json::Value;
|
||||
use std::fs::File;
|
||||
|
||||
// Mostly defaults
|
||||
const MEMORY_COST: u32 = 16 * 1024; // 4096 is default
|
||||
@@ -179,23 +180,28 @@ fn decrypt_password(
|
||||
salt: &[u8],
|
||||
parse: &ParseMode,
|
||||
) -> Result<DecryptedData> {
|
||||
let params = Params::new(MEMORY_COST, ITERATIONS, PARALLELISM, Some(OUTPUT_LENGTH)).unwrap();
|
||||
let mut kdf_salt: [u8; ARGON2_SALT_SIZE] = Default::default();
|
||||
kdf_salt.copy_from_slice(salt);
|
||||
|
||||
// Argon2id is default, V0x13 is default
|
||||
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
|
||||
let mut key = Key::default();
|
||||
argon2
|
||||
.hash_password_into(password.as_bytes(), salt, &mut key)
|
||||
.map_err(|err| anyhow!("Unable to hash password: {err}"))?;
|
||||
let kdf_info = KdfInfo::Argon2 {
|
||||
params: Params::new(MEMORY_COST, ITERATIONS, PARALLELISM, Some(OUTPUT_LENGTH)).unwrap(),
|
||||
algorithm: Algorithm::Argon2id,
|
||||
version: Version::V0x13,
|
||||
kdf_salt,
|
||||
};
|
||||
|
||||
// Create the Cipher
|
||||
let cipher = Aes256Gcm::new(&key);
|
||||
let cipher = StoreCipher::<Aes256Gcm>::new(password.as_bytes(), kdf_info)?;
|
||||
|
||||
// Decrypt using the nonce, which we get from the IV
|
||||
let nonce = Nonce::from_slice(iv);
|
||||
let data = EncryptedData {
|
||||
version: CURRENT_VERSION,
|
||||
ciphertext: ciphertext.to_vec(),
|
||||
nonce: iv.to_vec(),
|
||||
};
|
||||
|
||||
let plaintext = cipher
|
||||
.decrypt(nonce, ciphertext.as_ref())
|
||||
.decrypt_data_unchecked(data)
|
||||
.map_err(|_| anyhow!("Unable to decrypt"))?;
|
||||
let plaintext = String::from_utf8(plaintext)?;
|
||||
|
||||
|
||||
@@ -114,6 +114,8 @@ mod sandbox {
|
||||
"nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg";
|
||||
pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str =
|
||||
"nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg";
|
||||
pub(crate) const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
|
||||
"n1ryt076cufyddallg5x0gz3qjz0pd3wg0m4cwkg9njhmlnp6u88qq6nczgj";
|
||||
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
|
||||
hex_literal::hex!("8e0DcFF7F3085235C32E845f3667aEB3f1e83133");
|
||||
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
|
||||
@@ -149,6 +151,9 @@ mod sandbox {
|
||||
group_contract_address: parse_optional_str(GROUP_CONTRACT_ADDRESS),
|
||||
multisig_contract_address: parse_optional_str(MULTISIG_CONTRACT_ADDRESS),
|
||||
coconut_dkg_contract_address: parse_optional_str(COCONUT_DKG_CONTRACT_ADDRESS),
|
||||
service_provider_directory_contract_address: parse_optional_str(
|
||||
SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS,
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -176,6 +181,8 @@ mod qa {
|
||||
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs";
|
||||
pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str =
|
||||
"n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs";
|
||||
pub(crate) const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
|
||||
"n1ryt076cufyddallg5x0gz3qjz0pd3wg0m4cwkg9njhmlnp6u88qq6nczgj";
|
||||
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
|
||||
hex_literal::hex!("0000000000000000000000000000000000000000");
|
||||
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
|
||||
@@ -210,6 +217,9 @@ mod qa {
|
||||
group_contract_address: parse_optional_str(GROUP_CONTRACT_ADDRESS),
|
||||
multisig_contract_address: parse_optional_str(MULTISIG_CONTRACT_ADDRESS),
|
||||
coconut_dkg_contract_address: parse_optional_str(COCONUT_DKG_CONTRACT_ADDRESS),
|
||||
service_provider_directory_contract_address: parse_optional_str(
|
||||
SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS,
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nymproject/nym-wallet-app",
|
||||
"version": "1.2.1",
|
||||
"version": "1.2.2",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym_wallet"
|
||||
version = "1.2.1"
|
||||
version = "1.2.2"
|
||||
description = "Nym Native Wallet"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
@@ -45,9 +45,6 @@ tokio = { version = "1.10", features = ["full"] }
|
||||
toml = "0.5.8"
|
||||
url = "2.2"
|
||||
k256 = { version = "0.10", features = ["ecdsa", "sha256"] }
|
||||
|
||||
aes-gcm = "0.9.4"
|
||||
argon2 = { version = "0.3.2", features = ["std"] }
|
||||
base64 = "0.13"
|
||||
zeroize = { version = "1.5", features = ["zeroize_derive", "serde"] }
|
||||
|
||||
@@ -67,6 +64,7 @@ nym-coconut-interface = { path = "../../common/coconut-interface" }
|
||||
nym-vesting-contract = { path = "../../contracts/vesting" }
|
||||
nym-types = { path = "../../common/types" }
|
||||
nym-wallet-types = { path = "../nym-wallet-types" }
|
||||
nym-store-cipher = { path = "../../common/store-cipher", features = ["json"]}
|
||||
|
||||
[dev-dependencies]
|
||||
nym-crypto = { path = "../../common/crypto", features = ["rand"] }
|
||||
|
||||
@@ -49,11 +49,6 @@ pub enum BackendError {
|
||||
source: NymAPIError,
|
||||
},
|
||||
#[error("{source}")]
|
||||
KeyDerivationError {
|
||||
#[from]
|
||||
source: argon2::Error,
|
||||
},
|
||||
#[error("{source}")]
|
||||
IOError {
|
||||
#[from]
|
||||
source: io::Error,
|
||||
@@ -78,10 +73,13 @@ pub enum BackendError {
|
||||
#[from]
|
||||
source: k256::ecdsa::Error,
|
||||
},
|
||||
#[error("failed to encrypt the given data with the provided password")]
|
||||
EncryptionError,
|
||||
#[error("failed to decrypt the given data with the provided password")]
|
||||
DecryptionError,
|
||||
|
||||
#[error(transparent)]
|
||||
StoreCipherError {
|
||||
#[from]
|
||||
source: nym_store_cipher::Error,
|
||||
},
|
||||
|
||||
#[error("Client has not been initialized yet, connect with mnemonic to initialize")]
|
||||
ClientNotInitialized,
|
||||
#[error("No balance available for address {0}")]
|
||||
@@ -94,6 +92,12 @@ pub enum BackendError {
|
||||
WalletFileAlreadyExists,
|
||||
#[error("The wallet file is not found")]
|
||||
WalletFileNotFound,
|
||||
#[error("Invalid update pledge request, the new bond amount is the same as the current one")]
|
||||
WalletPledgeUpdateNoOp,
|
||||
#[error(
|
||||
"Invalid update pledge request, the new bond is a different currency from the current one"
|
||||
)]
|
||||
WalletPledgeUpdateInvalidCurrency,
|
||||
#[error("The wallet file has a malformed name")]
|
||||
WalletFileMalformedFilename,
|
||||
#[error("Unable to archive wallet file")]
|
||||
|
||||
@@ -58,6 +58,7 @@ fn main() {
|
||||
mixnet::admin::update_contract_settings,
|
||||
mixnet::bond::bond_gateway,
|
||||
mixnet::bond::bond_mixnode,
|
||||
mixnet::bond::update_pledge,
|
||||
mixnet::bond::pledge_more,
|
||||
mixnet::bond::decrease_pledge,
|
||||
mixnet::bond::gateway_bond_details,
|
||||
@@ -114,6 +115,7 @@ fn main() {
|
||||
vesting::rewards::vesting_claim_operator_reward,
|
||||
vesting::bond::vesting_bond_gateway,
|
||||
vesting::bond::vesting_bond_mixnode,
|
||||
vesting::bond::vesting_update_pledge,
|
||||
vesting::bond::vesting_pledge_more,
|
||||
vesting::bond::vesting_decrease_pledge,
|
||||
vesting::bond::vesting_unbond_gateway,
|
||||
@@ -152,6 +154,7 @@ fn main() {
|
||||
simulate::mixnet::simulate_bond_gateway,
|
||||
simulate::mixnet::simulate_unbond_gateway,
|
||||
simulate::mixnet::simulate_bond_mixnode,
|
||||
simulate::mixnet::simulate_update_pledge,
|
||||
simulate::mixnet::simulate_pledge_more,
|
||||
simulate::mixnet::simulate_unbond_mixnode,
|
||||
simulate::mixnet::simulate_update_mixnode_config,
|
||||
@@ -164,6 +167,7 @@ fn main() {
|
||||
simulate::vesting::simulate_vesting_bond_gateway,
|
||||
simulate::vesting::simulate_vesting_unbond_gateway,
|
||||
simulate::vesting::simulate_vesting_bond_mixnode,
|
||||
simulate::vesting::simulate_vesting_update_pledge,
|
||||
simulate::vesting::simulate_vesting_pledge_more,
|
||||
simulate::vesting::simulate_vesting_unbond_mixnode,
|
||||
simulate::vesting::simulate_vesting_update_mixnode_config,
|
||||
@@ -181,6 +185,10 @@ fn main() {
|
||||
signatures::ed25519_signing_payload::generate_gateway_bonding_msg_payload,
|
||||
signatures::ed25519_signing_payload::vesting_generate_gateway_bonding_msg_payload,
|
||||
help::log::help_log_toggle_window,
|
||||
app::window::create_main_window,
|
||||
app::window::create_auth_window,
|
||||
app::react::set_react_state,
|
||||
app::react::get_react_state,
|
||||
])
|
||||
.menu(Menu::os_default(&context.package_info().name).add_default_app_submenus())
|
||||
.on_menu_event(|event| {
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
pub mod react;
|
||||
pub mod version;
|
||||
pub mod window;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
use crate::error::BackendError;
|
||||
use crate::state::WalletState;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_react_state(
|
||||
state: tauri::State<'_, WalletState>,
|
||||
) -> Result<Option<String>, BackendError> {
|
||||
let r_state = state.read().await;
|
||||
r_state.get_react_state()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_react_state(
|
||||
state: tauri::State<'_, WalletState>,
|
||||
new_state: Option<String>,
|
||||
) -> Result<(), BackendError> {
|
||||
let mut w_state = state.write().await;
|
||||
w_state.set_react_state(new_state)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user