Compare commits
52 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| deae210b82 | |||
| 5b2b45a6eb | |||
| 896a3e1be6 | |||
| 1eaa13155c | |||
| 534187cc8f | |||
| 25b4934f69 | |||
| 5ef7e24893 | |||
| 87ef46bc05 | |||
| 68ca41a6be | |||
| a1a5c7772d | |||
| f8d68d8ef0 | |||
| a9d86508b5 | |||
| bb7fa587de | |||
| 6585732dfc | |||
| 2065e0fc17 | |||
| 3f7bdad59c | |||
| 6209e78c1e | |||
| 8e5062af96 | |||
| 496e642d7f | |||
| 07e18ec198 | |||
| d5953c28c1 | |||
| 3aa4b66588 | |||
| 005f0ce340 | |||
| 8d1d025fa2 | |||
| 1d53a2f954 | |||
| 966d123608 | |||
| 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 |
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: 'Documentation'
|
||||
about: Suggest a fix or enhancement to the documentation or developer portal content
|
||||
title: "[DOCS]"
|
||||
labels: documentation
|
||||
assignees: mfahampshire
|
||||
|
||||
---
|
||||
|
||||
Is your issue either:
|
||||
- [ ] a fix to existing documentation (e.g. fixing a broken link or incorrect command)
|
||||
- [ ] an enhancement (e.g. adding a description for an undocumented feature)
|
||||
|
||||
Please briefly describe your issue:
|
||||
@@ -4,6 +4,34 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [v1.1.18] (2023-05-09)
|
||||
|
||||
- Implement heartbeat messages between socks5 proxy and network requester ([#3215])
|
||||
|
||||
[#3215]: https://github.com/nymtech/nym/issues/3215
|
||||
|
||||
## [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.18"
|
||||
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.18"
|
||||
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.18"
|
||||
upcoming_platform_release_version = "v1.1.19" # to use when adding 'edit on github' plugin
|
||||
mix_node_release_version = "v1.1.19"
|
||||
#
|
||||
[preprocessor.last-changed]
|
||||
command = "mdbook-last-changed"
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
# Community Guides
|
||||
|
||||
We aren't the only ones writing documentation: the Nym developer community is also a great source of guides and resources, some of which we've included here. If you would like to share any resources with the community, please submit a pull request to the `main` branch of the `nymtech/dev-portal` [repository](https://github.com/nymtech/dev-portal).
|
||||
We aren't the only ones writing documentation: the Nym developer community is also a great source of guides and resources, some of which we've included here.
|
||||
|
||||
## <img src='../images/profile_picture/pastenym_ntv_pp.png' style="float: right; width: 75px; height: 75px;">Awesome NYM
|
||||
|
||||
by No Trust Verify
|
||||
## <img src='../images/profile_picture/pastenym_ntv_pp.png' style="float: right; width: 75px; height: 75px;"> No Trust Verify
|
||||
|
||||
>No Trust Verify is a project that aims to build open-source, privacy-enhancing technologies that make it easier to use the Internet securely and anonymously. Their focus is on providing tools and services that make it simple for developers to create decentralized applications (dApps) that respect users' privacy.
|
||||
* [Website](https://notrustverify.github.io/awesome-nym/)
|
||||
* [GitHub](https://github.com/notrustverify/awesome-nym)
|
||||
|
||||
* [Awesome Nym list](https://notrustverify.github.io/awesome-nym/) ([GitHub](https://github.com/notrustverify/awesome-nym))
|
||||
* A lot of guides can be found on the [NTV Blog](https://blog.notrustverify.ch/)
|
||||
|
||||
## <img src='../images/profile_picture/pineappleproxy_pp.png' style="float: right; width: 75px; height: 75px;">The Way of the NYMJA
|
||||
|
||||
by Pineapple Proxy🍍
|
||||
|
||||
>Born out of a study group from Nym's Shipyard Academy, Pineapple Proxy has emerged as a cluster of motivated and skilled individuals who see the new internet taking shape. With vibecare at the heart of their approach, this zesty collective is on a mission to make privacy convenient for everyone via content, new tools, events, and novel experiences. They believe in collective intelligence, empathy, and collaboration as the means by which privacy will become a meaningful reality.
|
||||
* [Website](https://pnproxy.org/welcome.html)
|
||||
* [GitHub](https://github.com/Pineapple-Proxy-DAO/web)
|
||||
|
||||
* [Website](https://pnproxy.org/welcome.html) ([GitHub](https://github.com/Pineapple-Proxy-DAO/web))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[book]
|
||||
title = "Nym Docs v1.1.16"
|
||||
title = "Nym Docs v1.1.18"
|
||||
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.18"
|
||||
upcoming_platform_release_version = "v1.1.19" # to use in 'edit page on github' plugin (coming soon)
|
||||
mix_node_release_version = "v1.1.19"
|
||||
|
||||
[preprocessor.last-changed]
|
||||
command = "mdbook-last-changed"
|
||||
|
||||
@@ -5,15 +5,15 @@
|
||||
- [Network Overview](architecture/network-overview.md)
|
||||
- [Mixnet Traffic Flow](architecture/traffic-flow.md)
|
||||
<!-- todo reintroduce this with themed images -->
|
||||
<!-- - [Network Rewards](architecture/network-rewards.md) -->
|
||||
<!-- - [Network Rewards](architecture/network-rewards.md) -->
|
||||
|
||||
# Binaries
|
||||
- [Pre-built Binaries](binaries/pre-built-binaries.md)
|
||||
- [Building from Source](binaries/building-nym.md)
|
||||
- [Binary Initialisation and Configuration](binaries/init-and-config.md)
|
||||
- [Version Compatibility Table](binaries/version-compatiblity.md)
|
||||
<!-- - [Version Compatibility Table](binaries/version-compatiblity.md) -->
|
||||
|
||||
# Nodes
|
||||
# Nodes
|
||||
- [Node Setup Guides](nodes/setup-guides.md)
|
||||
- [Mix Node](nodes/mix-node-setup.md)
|
||||
- [Gateway](nodes/gateway-setup.md)
|
||||
@@ -21,7 +21,7 @@
|
||||
- [Validator](nodes/validator-setup.md)
|
||||
- [Troubleshooting](nodes/troubleshooting.md)
|
||||
|
||||
# Clients
|
||||
# Clients
|
||||
- [Clients Overview](clients/overview.md)
|
||||
- [Websocket](clients/websocket-client.md)
|
||||
- [Socks5](clients/socks5-client.md)
|
||||
@@ -36,7 +36,7 @@
|
||||
- [Desktop Wallet](wallet/desktop-wallet.md)
|
||||
- [CLI Wallet](wallet/cli-wallet.md)
|
||||
|
||||
# Explorers
|
||||
# Explorers
|
||||
- [Mixnet Explorer](explorers/mixnet-explorer.md)
|
||||
|
||||
# Nyx Blockchain
|
||||
@@ -47,14 +47,14 @@
|
||||
- [RPC Nodes](nyx/rpc-node.md)
|
||||
- [Ledger Live Support](nyx/ledger-live.md)
|
||||
|
||||
# Coconut
|
||||
# Coconut
|
||||
- [Coconut](coconut.md)
|
||||
- [Bandwidth Credentials](bandwidth-credentials.md)
|
||||
|
||||
# Tools
|
||||
# Tools
|
||||
- [NymCLI](tools/nym-cli.md)
|
||||
|
||||
---
|
||||
---
|
||||
# Misc.
|
||||
- [Code of Conduct](coc.md)
|
||||
- [Licensing](licensing.md)
|
||||
|
||||
@@ -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.18"
|
||||
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.18"
|
||||
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.19"
|
||||
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.19"
|
||||
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,7 @@ mod sandbox {
|
||||
"nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg";
|
||||
pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str =
|
||||
"nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg";
|
||||
pub(crate) const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str = "";
|
||||
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
|
||||
hex_literal::hex!("8e0DcFF7F3085235C32E845f3667aEB3f1e83133");
|
||||
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
|
||||
@@ -149,6 +150,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 +180,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 +216,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}")]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user