Compare commits

...

36 Commits

Author SHA1 Message Date
farbanas 3f7bdad59c update lock files 2023-05-02 10:54:54 +02:00
farbanas 6209e78c1e bump crates 2023-05-02 10:53:42 +02:00
farbanas 8e5062af96 bump versions and update changelogs for release v1.1.17 2023-05-02 10:39:24 +02:00
mx 496e642d7f Merge pull request #3370 from nymtech/feature/1-1-17-docs
Feature/1 1 17 docs
2023-05-02 08:10:32 +00:00
mx 07e18ec198 added tokio dependency note 2023-04-28 15:51:20 +02:00
mx d5953c28c1 added note on running example code 2023-04-28 15:45:38 +02:00
mx 3aa4b66588 added info re buying NYM from wallet with BTC 2023-04-28 15:43:57 +02:00
mx 005f0ce340 * added correct version variable to sign command output
* added info that you can buy NYM from wallet with BTC
2023-04-28 15:41:55 +02:00
mx 8d1d025fa2 bumped point version 2023-04-28 15:41:44 +02:00
mx 1d53a2f954 updated readme with more details re: each directory having a readme and running them 2023-04-28 15:41:18 +02:00
Tommy Verrall 963d55273f Merge pull request #3367 from nymtech/feature/adding-sp-api-tests
adding a test for SP endpoint
2023-04-27 14:47:26 +01:00
benedettadavico 6872d7bf77 adding a test for SP endpoint 2023-04-27 15:37:59 +02:00
Jon Häggblad 6fe93bcda0 Merge pull request #3332 from nymtech/jon/feat/sp-integrations
Service provider directory support in nym-api, nym-cli, validator-client
2023-04-27 11:51:00 +02:00
Jędrzej Stuczyński 78d568e04e Feature/store cipher (#3350)
* initial nym-store-cipher

* cleanup
2023-04-27 10:24:36 +01:00
Pierre Dommerc 796f5a678a feat(wallet): update bond amount (#3338) 2023-04-26 15:35:47 +02:00
Jon Häggblad 0dfd1cca44 Review comments 2023-04-26 10:58:12 +02:00
Jon Häggblad eacefd3697 nym-cli: add announce and delete service provider 2023-04-26 10:58:12 +02:00
Jon Häggblad 424c25768c Add schemars to lock file 2023-04-26 10:58:12 +02:00
Jon Häggblad e460c1700e Add support for listing services in nym-cli 2023-04-26 10:58:12 +02:00
Jon Häggblad 9935c99d41 nym-api: add service provider endpoint and caching 2023-04-26 10:58:12 +02:00
Jon Häggblad 2c4aee63bf Make the contract optional 2023-04-26 10:58:12 +02:00
Jon Häggblad 0ebc395df9 rustfmt 2023-04-26 10:58:12 +02:00
Jon Häggblad 1de3317e75 Add sp directory contract traits and methods to nyxd client 2023-04-26 10:58:12 +02:00
Tommy Verrall 91c20af893 Merge pull request #3328 from nymtech/feature/shared-network-monitor
Feature/shared network monitor
2023-04-26 09:49:54 +01:00
pierre 1365e2f246 chore(wallet): add v prefix in wallet version 2023-04-26 10:04:58 +02:00
Mark Sinclair 3f4f76859b Merge pull request #3188 from nymtech/feature/wallet-login
Split wallet sign in and main into two entry points
2023-04-25 11:07:00 +01:00
Jędrzej Stuczyński 221e1870e5 removed redundant trait 2023-04-25 09:53:32 +01:00
Jędrzej Stuczyński 9b36bccf0c wasm tester fixes 2023-04-25 09:53:32 +01:00
Jędrzej Stuczyński 80d7285497 further improvements + reduced log noise 2023-04-25 09:53:32 +01:00
Jędrzej Stuczyński b94f2a483d nym-api compiling again 2023-04-25 09:53:32 +01:00
Jędrzej Stuczyński f64cfb4cd1 wip 2023-04-25 09:53:32 +01:00
Mark Sinclair 30e2f27c64 Fix linting error 2023-04-24 15:51:50 +01:00
Mark Sinclair 3113c1e9a7 Fix build issues 2023-04-24 15:41:13 +01:00
Mark Sinclair 1d8a931e0c Do not keep mnemonic or password (and variations) in logs 2023-04-24 12:24:19 +01:00
Mark Sinclair 48d0883b31 Clear stashed state completely on logout 2023-04-24 12:24:19 +01:00
Mark Sinclair e271370326 Split wallet sign in and main into two entry points
Stash some of the state in the Rust process and load it when the React app mounts
Fix connect_with_mnemonic logging
2023-04-24 12:24:19 +01:00
139 changed files with 3169 additions and 1518 deletions
+22
View File
@@ -4,6 +4,28 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
## [Unreleased]
## [v1.1.17] (2023-05-02)
- Add service-provider-directory-contract support to nym-cli ([#3334])
- Start using the node-testing-utils (implemented in #3270) in nym-api Network monitor to simplify the logic there ([#3312])
- Add service-provider-directory support to validator-client ([#3296])
- Allow topology injection in our WASM client ('test my node' feature) ([#3270])
- Expose service-provider-directory contract data in nym-api endpoints ([#3242])
- Cache service provider contract in nym-api ([#3241])
- Feature/1 1 17 docs ([#3370])
- adding a test for SP endpoint ([#3367])
- Feature/store cipher ([#3350])
[#3334]: https://github.com/nymtech/nym/issues/3334
[#3312]: https://github.com/nymtech/nym/issues/3312
[#3296]: https://github.com/nymtech/nym/issues/3296
[#3270]: https://github.com/nymtech/nym/issues/3270
[#3242]: https://github.com/nymtech/nym/issues/3242
[#3241]: https://github.com/nymtech/nym/issues/3241
[#3370]: https://github.com/nymtech/nym/pull/3370
[#3367]: https://github.com/nymtech/nym/pull/3367
[#3350]: https://github.com/nymtech/nym/pull/3350
## [v1.1.16] (2023-04-25)
- Explorer - Fix sorting function on Stake Saturation. It is currently working per page and not globally ([#3320])
Generated
+77 -15
View File
@@ -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",
]
+2
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.16"
version = "1.1.17"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.16"
version = "1.1.17"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
edition = "2021"
+4 -15
View File
@@ -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) {
+5 -4
View File
@@ -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
}
}
+7 -2
View File
@@ -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
}
}
+1
View File
@@ -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,
+2 -1
View File
@@ -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" }
+30 -1
View File
@@ -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)]
+2
View File
@@ -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] =
+2
View File
@@ -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";
+3
View File
@@ -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,
}
+2
View File
@@ -3,6 +3,8 @@
pub mod error;
pub mod message;
pub mod node;
pub mod processor;
pub mod receiver;
pub mod tester;
+60 -32
View File
@@ -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)
}
}
+92
View File
@@ -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"),
}
}
}
+99
View File
@@ -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 })
}
}
+42 -61
View File
@@ -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;
+104 -18
View File
@@ -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> {
+3 -2
View File
@@ -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
+14
View File
@@ -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" }
+43
View File
@@ -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())
}
}
}
+4 -2
View File
@@ -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;
+6
View File
@@ -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 {
+22
View File
@@ -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"]
+279
View File
@@ -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)
}
+132
View File
@@ -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)
}
}
+1
View File
@@ -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]
+36
View File
@@ -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
View File
@@ -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())
}
}
+2 -1
View File
@@ -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",
]
+1 -1
View File
@@ -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`).
+3 -3
View File
@@ -49,9 +49,9 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
[preprocessor.variables.variables]
# code prerequisites versions
minimum_rust_version = "1.66"
platform_release_version = "v1.1.16"
upcoming_platform_release_version = "v1.1.17" # to use when adding 'edit on github' plugin
mix_node_release_version = "v1.1.17"
platform_release_version = "v1.1.17"
upcoming_platform_release_version = "v1.1.18" # to use when adding 'edit on github' plugin
mix_node_release_version = "v1.1.18"
#
[preprocessor.last-changed]
command = "mdbook-last-changed"
+4 -6
View File
@@ -1,5 +1,5 @@
[book]
title = "Nym Docs v1.1.16"
title = "Nym Docs v1.1.17"
authors = ["Max Hampshire"]
description = "Nym technical documentation"
language = "en"
@@ -48,11 +48,9 @@ assets_version = "2.0.0" # do not edit: managed by `mdbook-admonish install`
# https://gitlab.com/tglman/mdbook-variables/
[preprocessor.variables.variables]
minimum_rust_version = "1.66"
platform_release_version = "v1.1.16"
upcoming_platform_release_version = "v1.1.17" # to use in 'edit page on github' plugin (coming soon)
mix_node_release_version = "v1.1.17"
# used by the cmdrun preprocessor - relative path from inside src/<dir>
# binaries_path = '../../../../target/release/'
platform_release_version = "v1.1.17"
upcoming_platform_release_version = "v1.1.18" # to use in 'edit page on github' plugin (coming soon)
mix_node_release_version = "v1.1.18"
[preprocessor.last-changed]
command = "mdbook-last-changed"
@@ -14,7 +14,7 @@ Before you initialise and run your gateway, head to our [website](https://nymtec
If you don't already have one, please create a Nym address using the wallet, and fund it with tokens. The minimum amount required to bond a gateway is 100 `NYM`, but make sure you have a bit more to account for gas costs.
`NYM` can be purchased via Bity from the wallet itself, and is currently present on several exchanges.
`NYM` can be purchased via Bity from the wallet itself with BTC or fiat, and is currently present on several exchanges.
> Remember that you can **only** use native Cosmos `NYM` tokens to bond your gateway. You **cannot** use ERC20 representations of `NYM` to run a node.
@@ -21,7 +21,7 @@ Before you initialise and run your mixnode, head to our [website](https://nymtec
If you don't already have one, please create a Nym address using the wallet, and fund it with tokens. The minimum amount required to bond a mixnode is 100 `NYM`, but make sure you have a bit more to account for gas costs.
`NYM` can be purchased via Bity from the wallet itself, and is currently present on several exchanges.
`NYM` can be purchased via Bity from the wallet itself with BTC or fiat, and is currently present on several exchanges.
> Remember that you can **only** use Cosmos `NYM` tokens to bond your mixnode. You **cannot** use ERC20 representations of `NYM` to run a node.
@@ -122,7 +122,7 @@ You can bond your mix node via the Desktop wallet.
|_| |_|\__, |_| |_| |_|
|___/
(nym-mixnode - version 1.1.14)
(nym-mixnode - version {{mix_node_release_version}})
>>> attempting to sign 5XrvVEMzRJk2AcT2h1o6ErZNb8z1ZzD3h7teipBW3NUtrtYq7vu4DRMgzZRTPVPnyr2YWCxpmKCMFaEXvksnJ4jt7np3NMLxsLMrFjEBhh67Crtjy4868vCzAivUqzdc365RiqxQQKtv4r9eTk9mTbE9JY8U3TxzKJCSGcBqbrb9JX3HrZVWm6tqbUYbsnku9pqnfeyeUiaYKY44Lm72TYrkZfRrMAZLMATiXT1ntmiKqT37HzRxNZjiH8qHeQEoRHkgDsmXDXRbfppGTpPrN7R4sjynJzehzUBZ8Ug7ovT9FoAHb8kuVQhUiMs1js6tdwtthzQMbPi9vwxUtVvjYknN2fnJgMnckEhzJJpJDCNdH7YhpPaWQnGVVS334mskiuqkbRVrFPJN2nnwArHr3L2cLxSMk9toKfw7ViKJ2p5E5JxiSmKY1cFGZ7uRLsuQ833PJN9JE8crPtkBNefqkbFNz68S5jPmzUShSvAc4TqXKeovDASFmmhKaPqLUrfsSWm7nzuKnzJSMADF6xSuwr9cknMoirqkRkLe7ybJ2ERwSdf5cUxMjF7yjS8tW9hZudnTUb1uPNDuSmPPVrCR12XZyFzBvVgxH51ZNJTym46nqnfA881LQcmFMnCwJf39rVJ4ASLnzEzmuwXj75QoB9ce9kiLmoBNLYe4QKSB6gDd858VnBtBNQELVuCCZbrTYuSCeNdUFhvMwD4kryc1pBYUa8Ro81F3QVfiKN
+1 -1
View File
@@ -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`):
+1
View File
@@ -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 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "explorer-api"
version = "1.1.16"
version = "1.1.17"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-gateway"
version = "1.1.16"
version = "1.1.17"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-mixnode"
version = "1.1.17"
version = "1.1.18"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
+3 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-api"
version = "1.1.17"
version = "1.1.18"
authors = [
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
"Jędrzej Stuczyński <andrew@nymtech.net>",
@@ -82,6 +82,7 @@ nym-mixnet-contract-common = { path = "../common/cosmwasm-smart-contracts/mixnet
nym-vesting-contract-common = { path = "../common/cosmwasm-smart-contracts/vesting-contract" }
nym-contracts-common = { path = "../common/cosmwasm-smart-contracts/contracts-common" }
nym-multisig-contract-common = { path = "../common/cosmwasm-smart-contracts/multisig-contract" }
nym-service-provider-directory-common = { path = "../common/cosmwasm-smart-contracts/service-provider-directory" }
nym-coconut = { path = "../common/nymcoconut" }
nym-sphinx = { path = "../common/nymsphinx" }
nym-pemstore = { path = "../common/pemstore" }
@@ -92,6 +93,7 @@ nym-validator-client = { path = "../common/client-libs/validator-client", featur
"nyxd-client",
] }
nym-bin-common = { path = "../common/bin-common" }
nym-node-tester-utils = { path = "../common/node-tester-utils" }
[features]
no-reward = []
-3
View File
@@ -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;
-78
View File
@@ -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
}
}
+19 -9
View File
@@ -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)))
})
}
}
+10 -18
View File
@@ -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);
+9 -6
View File
@@ -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;
}
+164 -222
View File
@@ -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();
+17 -245
View File
@@ -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)
}
}
+17 -36
View File
@@ -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()
)
}
}
+4
View File
@@ -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
View File
@@ -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)
}
+5
View File
@@ -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;
+2 -1
View File
@@ -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
]
}
+8
View File
@@ -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())
}
+22 -1
View File
@@ -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;
}
+4 -4
View File
@@ -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"
+22 -1
View File
@@ -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",
+8
View File
@@ -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])
+95 -54
View File
@@ -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" }
+20 -14
View File
@@ -6,14 +6,15 @@
// code. Furthermore we don't to strongly typed json parsing, all with the intention of being a
// little more flexible when interpreting the results.
use std::fs::File;
use aes_gcm::{aead::Aead, Aes256Gcm, Key, NewAead, Nonce};
use anyhow::{anyhow, Result};
use argon2::{Algorithm, Argon2, Params, Version};
use clap::Parser;
use nym_bin_common::logging::setup_logging;
use nym_store_cipher::{
Aes256Gcm, Algorithm, EncryptedData, KdfInfo, Params, StoreCipher, Version, ARGON2_SALT_SIZE,
CURRENT_VERSION,
};
use serde_json::Value;
use std::fs::File;
// Mostly defaults
const MEMORY_COST: u32 = 16 * 1024; // 4096 is default
@@ -179,23 +180,28 @@ fn decrypt_password(
salt: &[u8],
parse: &ParseMode,
) -> Result<DecryptedData> {
let params = Params::new(MEMORY_COST, ITERATIONS, PARALLELISM, Some(OUTPUT_LENGTH)).unwrap();
let mut kdf_salt: [u8; ARGON2_SALT_SIZE] = Default::default();
kdf_salt.copy_from_slice(salt);
// Argon2id is default, V0x13 is default
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
let mut key = Key::default();
argon2
.hash_password_into(password.as_bytes(), salt, &mut key)
.map_err(|err| anyhow!("Unable to hash password: {err}"))?;
let kdf_info = KdfInfo::Argon2 {
params: Params::new(MEMORY_COST, ITERATIONS, PARALLELISM, Some(OUTPUT_LENGTH)).unwrap(),
algorithm: Algorithm::Argon2id,
version: Version::V0x13,
kdf_salt,
};
// Create the Cipher
let cipher = Aes256Gcm::new(&key);
let cipher = StoreCipher::<Aes256Gcm>::new(password.as_bytes(), kdf_info)?;
// Decrypt using the nonce, which we get from the IV
let nonce = Nonce::from_slice(iv);
let data = EncryptedData {
version: CURRENT_VERSION,
ciphertext: ciphertext.to_vec(),
nonce: iv.to_vec(),
};
let plaintext = cipher
.decrypt(nonce, ciphertext.as_ref())
.decrypt_data_unchecked(data)
.map_err(|_| anyhow!("Unable to decrypt"))?;
let plaintext = String::from_utf8(plaintext)?;
@@ -114,6 +114,8 @@ mod sandbox {
"nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg";
pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str =
"nymt1k8re7jwz6rnnwrktnejdwkwnncte7ek7kk6fvg";
pub(crate) const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
"n1ryt076cufyddallg5x0gz3qjz0pd3wg0m4cwkg9njhmlnp6u88qq6nczgj";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("8e0DcFF7F3085235C32E845f3667aEB3f1e83133");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
@@ -149,6 +151,9 @@ mod sandbox {
group_contract_address: parse_optional_str(GROUP_CONTRACT_ADDRESS),
multisig_contract_address: parse_optional_str(MULTISIG_CONTRACT_ADDRESS),
coconut_dkg_contract_address: parse_optional_str(COCONUT_DKG_CONTRACT_ADDRESS),
service_provider_directory_contract_address: parse_optional_str(
SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS,
),
},
}
}
@@ -176,6 +181,8 @@ mod qa {
pub(crate) const MULTISIG_CONTRACT_ADDRESS: &str = "n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs";
pub(crate) const COCONUT_DKG_CONTRACT_ADDRESS: &str =
"n17p9rzwnnfxcjp32un9ug7yhhzgtkhvl988qccs";
pub(crate) const SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS: &str =
"n1ryt076cufyddallg5x0gz3qjz0pd3wg0m4cwkg9njhmlnp6u88qq6nczgj";
pub(crate) const _ETH_CONTRACT_ADDRESS: [u8; 20] =
hex_literal::hex!("0000000000000000000000000000000000000000");
pub(crate) const _ETH_ERC20_CONTRACT_ADDRESS: [u8; 20] =
@@ -210,6 +217,9 @@ mod qa {
group_contract_address: parse_optional_str(GROUP_CONTRACT_ADDRESS),
multisig_contract_address: parse_optional_str(MULTISIG_CONTRACT_ADDRESS),
coconut_dkg_contract_address: parse_optional_str(COCONUT_DKG_CONTRACT_ADDRESS),
service_provider_directory_contract_address: parse_optional_str(
SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS,
),
},
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@nymproject/nym-wallet-app",
"version": "1.2.1",
"version": "1.2.2",
"main": "index.js",
"license": "MIT",
"scripts": {
+2 -4
View File
@@ -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"] }
+13 -9
View File
@@ -49,11 +49,6 @@ pub enum BackendError {
source: NymAPIError,
},
#[error("{source}")]
KeyDerivationError {
#[from]
source: argon2::Error,
},
#[error("{source}")]
IOError {
#[from]
source: io::Error,
@@ -78,10 +73,13 @@ pub enum BackendError {
#[from]
source: k256::ecdsa::Error,
},
#[error("failed to encrypt the given data with the provided password")]
EncryptionError,
#[error("failed to decrypt the given data with the provided password")]
DecryptionError,
#[error(transparent)]
StoreCipherError {
#[from]
source: nym_store_cipher::Error,
},
#[error("Client has not been initialized yet, connect with mnemonic to initialize")]
ClientNotInitialized,
#[error("No balance available for address {0}")]
@@ -94,6 +92,12 @@ pub enum BackendError {
WalletFileAlreadyExists,
#[error("The wallet file is not found")]
WalletFileNotFound,
#[error("Invalid update pledge request, the new bond amount is the same as the current one")]
WalletPledgeUpdateNoOp,
#[error(
"Invalid update pledge request, the new bond is a different currency from the current one"
)]
WalletPledgeUpdateInvalidCurrency,
#[error("The wallet file has a malformed name")]
WalletFileMalformedFilename,
#[error("Unable to archive wallet file")]
+8
View File
@@ -58,6 +58,7 @@ fn main() {
mixnet::admin::update_contract_settings,
mixnet::bond::bond_gateway,
mixnet::bond::bond_mixnode,
mixnet::bond::update_pledge,
mixnet::bond::pledge_more,
mixnet::bond::decrease_pledge,
mixnet::bond::gateway_bond_details,
@@ -114,6 +115,7 @@ fn main() {
vesting::rewards::vesting_claim_operator_reward,
vesting::bond::vesting_bond_gateway,
vesting::bond::vesting_bond_mixnode,
vesting::bond::vesting_update_pledge,
vesting::bond::vesting_pledge_more,
vesting::bond::vesting_decrease_pledge,
vesting::bond::vesting_unbond_gateway,
@@ -152,6 +154,7 @@ fn main() {
simulate::mixnet::simulate_bond_gateway,
simulate::mixnet::simulate_unbond_gateway,
simulate::mixnet::simulate_bond_mixnode,
simulate::mixnet::simulate_update_pledge,
simulate::mixnet::simulate_pledge_more,
simulate::mixnet::simulate_unbond_mixnode,
simulate::mixnet::simulate_update_mixnode_config,
@@ -164,6 +167,7 @@ fn main() {
simulate::vesting::simulate_vesting_bond_gateway,
simulate::vesting::simulate_vesting_unbond_gateway,
simulate::vesting::simulate_vesting_bond_mixnode,
simulate::vesting::simulate_vesting_update_pledge,
simulate::vesting::simulate_vesting_pledge_more,
simulate::vesting::simulate_vesting_unbond_mixnode,
simulate::vesting::simulate_vesting_update_mixnode_config,
@@ -181,6 +185,10 @@ fn main() {
signatures::ed25519_signing_payload::generate_gateway_bonding_msg_payload,
signatures::ed25519_signing_payload::vesting_generate_gateway_bonding_msg_payload,
help::log::help_log_toggle_window,
app::window::create_main_window,
app::window::create_auth_window,
app::react::set_react_state,
app::react::get_react_state,
])
.menu(Menu::os_default(&context.package_info().name).add_default_app_submenus())
.on_menu_event(|event| {
@@ -1 +1,3 @@
pub mod react;
pub mod version;
pub mod window;
@@ -0,0 +1,19 @@
use crate::error::BackendError;
use crate::state::WalletState;
#[tauri::command]
pub async fn get_react_state(
state: tauri::State<'_, WalletState>,
) -> Result<Option<String>, BackendError> {
let r_state = state.read().await;
r_state.get_react_state()
}
#[tauri::command]
pub async fn set_react_state(
state: tauri::State<'_, WalletState>,
new_state: Option<String>,
) -> Result<(), BackendError> {
let mut w_state = state.write().await;
w_state.set_react_state(new_state)
}

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