Compare commits

..

32 Commits

Author SHA1 Message Date
Mark Sinclair 1abb5defcc Bump version 2024-12-17 15:03:49 +00:00
Sachin Kamath 3c97e7a1e1 api: fetch addresses from config. 2024-12-17 15:02:30 +00:00
Mark Sinclair 7c2b2623e2 Bump version 2024-12-17 15:02:30 +00:00
Sachin Kamath ac25bf9b7e bugfix: dont manually set last_processed_height for pruning=nothing strat. 2024-12-17 15:02:30 +00:00
Sachin Kamath 91f0321479 chain-scraper : use tx module for parsing transactions 2024-12-17 15:02:28 +00:00
Mark Sinclair a029be3456 nyx-chain-watcher: return average price over 24 hours 2024-12-17 15:01:31 +00:00
Mark Sinclair 3200076289 formatting 2024-12-17 15:01:31 +00:00
Mark Sinclair cf0d75917a change webhook payload to have a structured coin for funds 2024-12-17 15:01:31 +00:00
Mark Sinclair f6dcbc236a bump version 2024-12-17 15:01:31 +00:00
Mark Sinclair d9ede97073 add websocket rpcs to env files 2024-12-17 15:01:28 +00:00
Jędrzej Stuczyński f69420293c added env variable to nuke the db 2024-12-17 15:00:03 +00:00
Jędrzej Stuczyński f8b2770585 even more logs 2024-12-17 15:00:03 +00:00
Jędrzej Stuczyński f1b4ebdbae extra logs 2024-12-17 15:00:03 +00:00
Jędrzej Stuczyński 34436c48b2 explicitly build websocket client in 0.37 compat mode 2024-12-17 15:00:03 +00:00
Jędrzej Stuczyński e4a893e68c allow conversion from CometBFT block subscription 2024-12-17 15:00:03 +00:00
Mark Sinclair 5c1b8fb1fe Bump version 2024-12-17 15:00:03 +00:00
Mark Sinclair a448170dd5 Bump version 2024-12-17 15:00:03 +00:00
Jędrzej Stuczyński 8f697390b9 implemented starting block logic inside the chain scraper itself 2024-12-17 15:00:03 +00:00
Mark Sinclair a1a1336b38 Fix docker entry point and bump version 2024-12-17 15:00:03 +00:00
Mark Sinclair 9dcad2fefc Add Dockerfile and workflow to build 2024-12-17 15:00:02 +00:00
Mark Sinclair 38921eb1ed parse message index and process all log entries 2024-12-17 14:59:17 +00:00
Mark Sinclair 62571f92ea init saves example config 2024-12-17 14:59:17 +00:00
Mark Sinclair b5958c4ddb Add example to README 2024-12-17 14:59:17 +00:00
Mark Sinclair 16cb60e64e Remove migration from seed app 2024-12-17 14:59:17 +00:00
Mark Sinclair 13acddf050 Add config, overrides and CLI 2024-12-17 14:59:17 +00:00
Mark Sinclair dbe8e96cfe Move nym-data-observatory (v0) to nyx-chain-watcher 2024-12-17 14:59:15 +00:00
Mark Sinclair 2dee5fe23c data-observatory: renamed transactions to payments because there is already transaction in the base scraper schema 2024-12-17 14:58:42 +00:00
Mark Sinclair e4c9da6e77 nyxd-scraper: add optional starting height parameter to scrape before listening for new blocks 2024-12-17 14:58:42 +00:00
Sachin Kamath 632b210eee observatory 0.1 2024-12-17 14:58:40 +00:00
Sachin Kamath c32cfebbb1 fix review comments 2024-12-17 14:55:42 +00:00
Sachin Kamath 02d46158db clippy 2024-12-17 14:55:42 +00:00
Sachin Kamath c44913eaf5 nyxd-scraper: add config to make pre-commit storage optional 2024-12-17 14:55:42 +00:00
165 changed files with 3003 additions and 2839 deletions
-1
View File
@@ -15,7 +15,6 @@ jobs:
runs-on: ${{ matrix.os }}
env:
CARGO_TERM_COLOR: always
IPINFO_API_TOKEN: ${{ secrets.IPINFO_API_TOKEN }}
continue-on-error: true
steps:
- name: Check out repository code
Generated
+17 -14
View File
@@ -5028,7 +5028,7 @@ dependencies = [
[[package]]
name = "nym-credential-proxy"
version = "0.1.7"
version = "0.1.6"
dependencies = [
"anyhow",
"async-trait",
@@ -6535,7 +6535,10 @@ name = "nym-topology"
version = "0.1.0"
dependencies = [
"async-trait",
"bs58",
"log",
"nym-api-requests",
"nym-bin-common",
"nym-config",
"nym-crypto",
"nym-mixnet-contract-common",
@@ -6544,10 +6547,10 @@ dependencies = [
"nym-sphinx-types",
"rand",
"reqwest 0.12.4",
"semver 1.0.23",
"serde",
"serde_json",
"thiserror",
"tracing",
"tsify",
"wasm-bindgen",
"wasm-utils",
@@ -6780,7 +6783,6 @@ dependencies = [
"nym-crypto",
"nym-gateway-storage",
"nym-network-defaults",
"nym-node-metrics",
"nym-task",
"nym-wireguard-types",
"thiserror",
@@ -6838,7 +6840,7 @@ dependencies = [
[[package]]
name = "nyx-chain-watcher"
version = "0.1.8"
version = "0.1.7"
dependencies = [
"anyhow",
"async-trait",
@@ -10767,9 +10769,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
[[package]]
name = "wasm-bindgen"
version = "0.2.99"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
dependencies = [
"cfg-if",
"once_cell",
@@ -10778,12 +10780,13 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.99"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.90",
@@ -10804,9 +10807,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.99"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -10814,9 +10817,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.99"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
dependencies = [
"proc-macro2",
"quote",
@@ -10827,9 +10830,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.99"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
[[package]]
name = "wasm-bindgen-test"
+2 -4
View File
@@ -147,9 +147,7 @@ members = [
"tools/internal/contract-state-importer/importer-cli",
"tools/internal/contract-state-importer/importer-contract",
"tools/internal/testnet-manager",
"tools/internal/testnet-manager/dkg-bypass-contract",
"common/verloc",
"tools/internal/mixnet-connectivity-check",
"tools/internal/testnet-manager/dkg-bypass-contract", "common/verloc", "tools/internal/mixnet-connectivity-check",
]
default-members = [
@@ -405,7 +403,7 @@ indexed_db_futures = { git = "https://github.com/TiemenSch/rust-indexed-db", bra
js-sys = "0.3.70"
serde-wasm-bindgen = "0.6.5"
tsify = "0.4.5"
wasm-bindgen = "0.2.99"
wasm-bindgen = "0.2.95"
wasm-bindgen-futures = "0.4.45"
wasmtimer = "0.2.0"
web-sys = "0.3.72"
+2 -2
View File
@@ -3,7 +3,7 @@ name = "nym-client-core"
version = "1.1.15"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
edition = "2021"
rust-version = "1.76"
rust-version = "1.70"
license.workspace = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -45,7 +45,7 @@ nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" }
nym-sphinx = { path = "../nymsphinx" }
nym-statistics-common = { path = "../statistics" }
nym-pemstore = { path = "../pemstore" }
nym-topology = { path = "../topology", features = ["persistence"] }
nym-topology = { path = "../topology", features = ["serializable"] }
nym-mixnet-client = { path = "../client-libs/mixnet-client", default-features = false }
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
nym-task = { path = "../task" }
@@ -550,15 +550,6 @@ pub struct Topology {
/// Specifies a minimum performance of a gateway that is used on route construction.
/// This setting is only applicable when `NymApi` topology is used.
pub minimum_gateway_performance: u8,
/// Specifies whether this client should attempt to retrieve all available network nodes
/// as opposed to just active mixnodes/gateways.
/// Useless without `ignore_epoch_roles = true`
pub use_extended_topology: bool,
/// Specifies whether this client should ignore the current epoch role of the target egress node
/// when constructing the final hop packets.
pub ignore_egress_epoch_role: bool,
}
#[allow(clippy::large_enum_variant)]
@@ -595,8 +586,6 @@ impl Default for Topology {
topology_structure: TopologyStructure::default(),
minimum_mixnode_performance: DEFAULT_MIN_MIXNODE_PERFORMANCE,
minimum_gateway_performance: DEFAULT_MIN_GATEWAY_PERFORMANCE,
use_extended_topology: false,
ignore_egress_epoch_role: false,
}
}
}
@@ -112,7 +112,7 @@ where
source,
}
})?;
hardcoded_topology.entry_capable_nodes().cloned().collect()
hardcoded_topology.get_gateways()
} else {
let mut rng = rand::thread_rng();
crate::init::helpers::current_gateways(
@@ -128,7 +128,7 @@ where
// make sure the list of available gateways doesn't overlap the list of known gateways
let available_gateways = available_gateways
.into_iter()
.filter(|g| !registered_gateways.contains(&g.identity()))
.filter(|g| !registered_gateways.contains(g.identity()))
.collect::<Vec<_>>();
if available_gateways.is_empty() {
@@ -167,7 +167,7 @@ where
source,
}
})?;
hardcoded_topology.entry_capable_nodes().cloned().collect()
hardcoded_topology.get_gateways()
} else {
let mut rng = rand::thread_rng();
crate::init::helpers::current_gateways(
@@ -3,6 +3,7 @@
use super::received_buffer::ReceivedBufferMessage;
use super::statistics_control::StatisticsControl;
use super::topology_control::geo_aware_provider::GeoAwareTopologyProvider;
use crate::client::base_client::storage::helpers::store_client_keys;
use crate::client::base_client::storage::MixnetClientStorage;
use crate::client::cover_traffic_stream::LoopCoverTrafficStream;
@@ -23,7 +24,7 @@ use crate::client::replies::reply_storage::{
};
use crate::client::topology_control::nym_api_provider::NymApiTopologyProvider;
use crate::client::topology_control::{
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
nym_api_provider, TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
};
use crate::config::{Config, DebugConfig};
use crate::error::ClientCoreError;
@@ -463,8 +464,8 @@ where
details_store
.upgrade_stored_remote_gateway_key(gateway_client.gateway_identity(), &updated_key)
.await.map_err(|err| {
error!("failed to store upgraded gateway key! this connection might be forever broken now: {err}");
ClientCoreError::GatewaysDetailsStoreError { source: Box::new(err) }
error!("failed to store upgraded gateway key! this connection might be forever broken now: {err}");
ClientCoreError::GatewaysDetailsStoreError { source: Box::new(err) }
})?
}
@@ -538,15 +539,15 @@ where
// if no custom provider was ... provided ..., create one using nym-api
custom_provider.unwrap_or_else(|| match config_topology.topology_structure {
config::TopologyStructure::NymApi => Box::new(NymApiTopologyProvider::new(
config_topology,
nym_api_provider::Config {
min_mixnode_performance: config_topology.minimum_mixnode_performance,
min_gateway_performance: config_topology.minimum_gateway_performance,
},
nym_api_urls,
user_agent,
)),
config::TopologyStructure::GeoAware(group_by) => {
warn!("using deprecated 'GeoAware' topology provider - this option will be removed very soon");
#[allow(deprecated)]
Box::new(crate::client::topology_control::GeoAwareTopologyProvider::new(nym_api_urls, group_by))
Box::new(GeoAwareTopologyProvider::new(nym_api_urls, group_by))
}
})
}
@@ -557,7 +558,7 @@ where
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
topology_config: config::Topology,
topology_accessor: TopologyAccessor,
local_gateway: NodeIdentity,
local_gateway: &NodeIdentity,
wait_for_gateway: bool,
mut shutdown: TaskClient,
) -> Result<(), ClientCoreError> {
@@ -589,7 +590,7 @@ where
};
if let Err(err) = topology_refresher
.ensure_contains_routable_egress(local_gateway)
.ensure_contains_gateway(local_gateway)
.await
{
if let Some(waiting_timeout) = gateway_wait_timeout {
@@ -739,8 +740,7 @@ where
// channels responsible for controlling ack messages
let (ack_sender, ack_receiver) = mpsc::unbounded();
let shared_topology_accessor =
TopologyAccessor::new(self.config.debug.topology.ignore_egress_epoch_role);
let shared_topology_accessor = TopologyAccessor::new();
// Shutdown notifier for signalling tasks to stop
let shutdown = self
@@ -163,7 +163,6 @@ impl LoopCoverTrafficStream<OsRng> {
// poisson delay, but is it really a problem?
let topology_permit = self.topology_access.get_read_permit().await;
// the ack is sent back to ourselves (and then ignored)
let topology_ref = match topology_permit.try_get_valid_topology_ref(
&self.our_full_destination,
Some(&self.our_full_destination),
@@ -28,6 +28,7 @@ pub enum InputMessage {
recipient: Recipient,
data: Vec<u8>,
lane: TransmissionLane,
mix_hops: Option<u8>,
},
/// Creates a message used for a duplex anonymous communication where the recipient
@@ -43,6 +44,7 @@ pub enum InputMessage {
data: Vec<u8>,
reply_surbs: u32,
lane: TransmissionLane,
mix_hops: Option<u8>,
},
/// Attempt to use our internally received and stored `ReplySurb` to send the message back
@@ -92,6 +94,29 @@ impl InputMessage {
recipient,
data,
lane,
mix_hops: None,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
} else {
message
}
}
// IMHO `new_regular` should take `mix_hops: Option<u8>` as an argument instead of creating
// this function, but that would potentially break backwards compatibility with the current API
pub fn new_regular_with_custom_hops(
recipient: Recipient,
data: Vec<u8>,
lane: TransmissionLane,
packet_type: Option<PacketType>,
mix_hops: Option<u8>,
) -> Self {
let message = InputMessage::Regular {
recipient,
data,
lane,
mix_hops,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
@@ -112,6 +137,7 @@ impl InputMessage {
data,
reply_surbs,
lane,
mix_hops: None,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
@@ -128,12 +154,14 @@ impl InputMessage {
reply_surbs: u32,
lane: TransmissionLane,
packet_type: Option<PacketType>,
mix_hops: Option<u8>,
) -> Self {
let message = InputMessage::Anonymous {
recipient,
data,
reply_surbs,
lane,
mix_hops,
};
if let Some(packet_type) = packet_type {
InputMessage::new_wrapper(message, packet_type)
@@ -73,10 +73,11 @@ where
content: Vec<u8>,
lane: TransmissionLane,
packet_type: PacketType,
mix_hops: Option<u8>,
) {
if let Err(err) = self
.message_handler
.try_send_plain_message(recipient, content, lane, packet_type)
.try_send_plain_message(recipient, content, lane, packet_type, mix_hops)
.await
{
warn!("failed to send a plain message - {err}")
@@ -90,10 +91,18 @@ where
reply_surbs: u32,
lane: TransmissionLane,
packet_type: PacketType,
mix_hops: Option<u8>,
) {
if let Err(err) = self
.message_handler
.try_send_message_with_reply_surbs(recipient, content, reply_surbs, lane, packet_type)
.try_send_message_with_reply_surbs(
recipient,
content,
reply_surbs,
lane,
packet_type,
mix_hops,
)
.await
{
warn!("failed to send a repliable message - {err}")
@@ -106,8 +115,9 @@ where
recipient,
data,
lane,
mix_hops,
} => {
self.handle_plain_message(recipient, data, lane, PacketType::Mix)
self.handle_plain_message(recipient, data, lane, PacketType::Mix, mix_hops)
.await
}
InputMessage::Anonymous {
@@ -115,9 +125,17 @@ where
data,
reply_surbs,
lane,
mix_hops,
} => {
self.handle_repliable_message(recipient, data, reply_surbs, lane, PacketType::Mix)
.await
self.handle_repliable_message(
recipient,
data,
reply_surbs,
lane,
PacketType::Mix,
mix_hops,
)
.await
}
InputMessage::Reply {
recipient_tag,
@@ -135,8 +153,9 @@ where
recipient,
data,
lane,
mix_hops,
} => {
self.handle_plain_message(recipient, data, lane, packet_type)
self.handle_plain_message(recipient, data, lane, packet_type, mix_hops)
.await
}
InputMessage::Anonymous {
@@ -144,9 +163,17 @@ where
data,
reply_surbs,
lane,
mix_hops,
} => {
self.handle_repliable_message(recipient, data, reply_surbs, lane, packet_type)
.await
self.handle_repliable_message(
recipient,
data,
reply_surbs,
lane,
packet_type,
mix_hops,
)
.await
}
InputMessage::Reply {
recipient_tag,
@@ -70,6 +70,7 @@ pub(crate) struct PendingAcknowledgement {
message_chunk: Fragment,
delay: SphinxDelay,
destination: PacketDestination,
mix_hops: Option<u8>,
retransmissions: u32,
}
@@ -79,11 +80,13 @@ impl PendingAcknowledgement {
message_chunk: Fragment,
delay: SphinxDelay,
recipient: Recipient,
mix_hops: Option<u8>,
) -> Self {
PendingAcknowledgement {
message_chunk,
delay,
destination: PacketDestination::KnownRecipient(recipient.into()),
mix_hops,
retransmissions: 0,
}
}
@@ -101,6 +104,9 @@ impl PendingAcknowledgement {
recipient_tag,
extra_surb_request,
},
// Messages sent using SURBs are using the number of mix hops set by the recipient when
// they provided the SURBs, so it doesn't make sense to include it here.
mix_hops: None,
retransmissions: 0,
}
}
@@ -52,12 +52,18 @@ where
packet_recipient: Recipient,
chunk_data: Fragment,
packet_type: PacketType,
mix_hops: Option<u8>,
) -> Result<PreparedFragment, PreparationError> {
debug!("retransmitting normal packet...");
// TODO: Figure out retransmission packet type signaling
self.message_handler
.try_prepare_single_chunk_for_sending(packet_recipient, chunk_data, packet_type)
.try_prepare_single_chunk_for_sending(
packet_recipient,
chunk_data,
packet_type,
mix_hops,
)
.await
}
@@ -104,6 +110,7 @@ where
**recipient,
timed_out_ack.message_chunk.clone(),
packet_type,
timed_out_ack.mix_hops,
)
.await
}
@@ -15,11 +15,11 @@ use nym_sphinx::anonymous_replies::requests::{AnonymousSenderTag, RepliableMessa
use nym_sphinx::anonymous_replies::{ReplySurb, SurbEncryptionKey};
use nym_sphinx::chunking::fragment::{Fragment, FragmentIdentifier};
use nym_sphinx::message::NymMessage;
use nym_sphinx::params::{PacketSize, PacketType};
use nym_sphinx::params::{PacketSize, PacketType, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx::preparer::{MessagePreparer, PreparedFragment};
use nym_sphinx::Delay;
use nym_task::connections::TransmissionLane;
use nym_topology::{NymRouteProvider, NymTopologyError};
use nym_topology::{NymTopology, NymTopologyError};
use rand::{CryptoRng, Rng};
use std::collections::HashMap;
use std::sync::Arc;
@@ -100,6 +100,10 @@ pub(crate) struct Config {
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
average_ack_delay: Duration,
/// Number of mix hops each packet ('real' message, ack, reply) is expected to take.
/// Note that it does not include gateway hops.
num_mix_hops: u8,
/// Primary predefined packet size used for the encapsulated messages.
primary_packet_size: PacketSize,
@@ -121,11 +125,19 @@ impl Config {
deterministic_route_selection,
average_packet_delay,
average_ack_delay,
num_mix_hops: DEFAULT_NUM_MIX_HOPS,
primary_packet_size: PacketSize::default(),
secondary_packet_size: None,
}
}
/// Allows setting non-default number of expected mix hops in the network.
#[allow(dead_code)]
pub fn with_mix_hops(mut self, hops: u8) -> Self {
self.num_mix_hops = hops;
self
}
/// Allows setting non-default size of the sphinx packets sent out.
pub fn with_custom_primary_packet_size(mut self, packet_size: PacketSize) -> Self {
self.primary_packet_size = packet_size;
@@ -173,7 +185,9 @@ where
config.sender_address,
config.average_packet_delay,
config.average_ack_delay,
);
)
.with_mix_hops(config.num_mix_hops);
MessageHandler {
config,
rng,
@@ -202,7 +216,7 @@ where
fn get_topology<'a>(
&self,
permit: &'a TopologyReadPermit<'a>,
) -> Result<&'a NymRouteProvider, PreparationError> {
) -> Result<&'a NymTopology, PreparationError> {
match permit.try_get_valid_topology_ref(&self.config.sender_address, None) {
Ok(topology_ref) => Ok(topology_ref),
Err(err) => {
@@ -219,8 +233,9 @@ where
return self.config.primary_packet_size;
};
let primary_count = msg.required_packets(self.config.primary_packet_size);
let secondary_count = msg.required_packets(secondary_packet);
let primary_count =
msg.required_packets(self.config.primary_packet_size, self.config.num_mix_hops);
let secondary_count = msg.required_packets(secondary_packet, self.config.num_mix_hops);
trace!("This message would require: {primary_count} primary packets or {secondary_count} secondary packets...");
// if there would be no benefit in using the secondary packet - use the primary (duh)
@@ -409,9 +424,10 @@ where
message: Vec<u8>,
lane: TransmissionLane,
packet_type: PacketType,
mix_hops: Option<u8>,
) -> Result<(), PreparationError> {
let message = NymMessage::new_plain(message);
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type)
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type, mix_hops)
.await
}
@@ -421,6 +437,7 @@ where
recipient: Recipient,
lane: TransmissionLane,
packet_type: PacketType,
mix_hops: Option<u8>,
) -> Result<(), PreparationError> {
debug!("Sending non-reply message with packet type {packet_type}");
// TODO: I really dislike existence of this assertion, it implies code has to be re-organised
@@ -453,6 +470,7 @@ where
&self.config.ack_key,
&recipient,
packet_type,
mix_hops,
)?;
let real_message = RealMessage::new(
@@ -460,7 +478,8 @@ where
Some(fragment.fragment_identifier()),
);
let delay = prepared_fragment.total_delay;
let pending_ack = PendingAcknowledgement::new_known(fragment, delay, recipient);
let pending_ack =
PendingAcknowledgement::new_known(fragment, delay, recipient, mix_hops);
real_messages.push(real_message);
pending_acks.push(pending_ack);
@@ -477,6 +496,7 @@ where
recipient: Recipient,
amount: u32,
packet_type: PacketType,
mix_hops: Option<u8>,
) -> Result<(), PreparationError> {
debug!("Sending additional reply SURBs with packet type {packet_type}");
let sender_tag = self.get_or_create_sender_tag(&recipient);
@@ -493,6 +513,7 @@ where
recipient,
TransmissionLane::AdditionalReplySurbs,
packet_type,
mix_hops,
)
.await?;
@@ -509,6 +530,7 @@ where
num_reply_surbs: u32,
lane: TransmissionLane,
packet_type: PacketType,
mix_hops: Option<u8>,
) -> Result<(), SurbWrappedPreparationError> {
debug!("Sending message with reply SURBs with packet type {packet_type}");
let sender_tag = self.get_or_create_sender_tag(&recipient);
@@ -519,7 +541,7 @@ where
let message =
NymMessage::new_repliable(RepliableMessage::new_data(message, sender_tag, reply_surbs));
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type)
self.try_split_and_send_non_reply_message(message, recipient, lane, packet_type, mix_hops)
.await?;
log::trace!("storing {} reply keys", reply_keys.len());
@@ -533,18 +555,23 @@ where
recipient: Recipient,
chunk: Fragment,
packet_type: PacketType,
mix_hops: Option<u8>,
) -> Result<PreparedFragment, PreparationError> {
debug!("Sending single chunk with packet type {packet_type}");
let topology_permit = self.topology_access.get_read_permit().await;
let topology = self.get_topology(&topology_permit)?;
let prepared_fragment = self.message_preparer.prepare_chunk_for_sending(
chunk,
topology,
&self.config.ack_key,
&recipient,
packet_type,
)?;
let prepared_fragment = self
.message_preparer
.prepare_chunk_for_sending(
chunk,
topology,
&self.config.ack_key,
&recipient,
packet_type,
mix_hops,
)
.unwrap();
Ok(prepared_fragment)
}
@@ -597,13 +624,16 @@ where
Err(err) => return Err(err.return_surbs(vec![reply_surb])),
};
let prepared_fragment = self.message_preparer.prepare_reply_chunk_for_sending(
chunk,
topology,
&self.config.ack_key,
reply_surb,
PacketType::Mix,
)?;
let prepared_fragment = self
.message_preparer
.prepare_reply_chunk_for_sending(
chunk,
topology,
&self.config.ack_key,
reply_surb,
PacketType::Mix,
)
.unwrap();
Ok(prepared_fragment)
}
@@ -230,7 +230,6 @@ where
// poisson delay, but is it really a problem?
let topology_permit = self.topology_access.get_read_permit().await;
// the ack is sent back to ourselves (and then ignored)
let topology_ref = match topology_permit.try_get_valid_topology_ref(
&self.config.our_full_destination,
Some(&self.config.our_full_destination),
@@ -516,6 +516,7 @@ where
recipient,
to_send,
nym_sphinx::params::PacketType::Mix,
self.config.reply_surbs.surb_mix_hops,
)
.await
{
@@ -2,7 +2,8 @@
// SPDX-License-Identifier: Apache-2.0
use nym_sphinx::addressing::clients::Recipient;
use nym_topology::{NymRouteProvider, NymTopology, NymTopologyError};
use nym_sphinx::params::DEFAULT_NUM_MIX_HOPS;
use nym_topology::{NymTopology, NymTopologyError};
use std::ops::Deref;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
@@ -16,36 +17,29 @@ pub struct TopologyAccessorInner {
// few seconds, while reads are needed every single packet generated.
// However, proper benchmarks will be needed to determine if `RwLock` is indeed a better
// approach than a `Mutex`
topology: RwLock<NymRouteProvider>,
topology: RwLock<Option<NymTopology>>,
}
impl TopologyAccessorInner {
fn new(initial: NymRouteProvider) -> Self {
fn new() -> Self {
TopologyAccessorInner {
controlled_manually: AtomicBool::new(false),
released_manual_control: Notify::new(),
topology: RwLock::new(initial),
topology: RwLock::new(None),
}
}
async fn update(&self, new: Option<NymTopology>) {
let mut guard = self.topology.write().await;
match new {
Some(updated) => {
guard.update(updated);
}
None => guard.clear_topology(),
}
*self.topology.write().await = new;
}
}
pub struct TopologyReadPermit<'a> {
permit: RwLockReadGuard<'a, NymRouteProvider>,
permit: RwLockReadGuard<'a, Option<NymTopology>>,
}
impl Deref for TopologyReadPermit<'_> {
type Target = NymRouteProvider;
type Target = Option<NymTopology>;
fn deref(&self) -> &Self::Target {
&self.permit
@@ -59,31 +53,43 @@ impl<'a> TopologyReadPermit<'a> {
&'a self,
ack_recipient: &Recipient,
packet_recipient: Option<&Recipient>,
) -> Result<&'a NymRouteProvider, NymTopologyError> {
let route_provider = self.permit.deref();
let topology = &route_provider.topology;
) -> Result<&'a NymTopology, NymTopologyError> {
// 1. Have we managed to get anything from the refresher, i.e. have the nym-api queries gone through?
topology.ensure_not_empty()?;
let topology = self
.permit
.as_ref()
.ok_or(NymTopologyError::EmptyNetworkTopology)?;
// 2. does the topology have a node on each mixing layer?
topology.ensure_minimally_routable()?;
// 2. does it have any mixnode at all?
// 3. does it have any gateways at all?
// 4. does it have a mixnode on each layer?
topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS)?;
// 3. does it contain OUR gateway (so that we could create an ack packet)?
let _ = route_provider.egress_by_identity(ack_recipient.gateway())?;
// 4. for our target recipient, does it contain THEIR gateway (so that we send anything over?)
if let Some(recipient) = packet_recipient {
let _ = route_provider.egress_by_identity(recipient.gateway())?;
// 5. does it contain OUR gateway (so that we could create an ack packet)?
if !topology.gateway_exists(ack_recipient.gateway()) {
return Err(NymTopologyError::NonExistentGatewayError {
identity_key: ack_recipient.gateway().to_base58_string(),
});
}
Ok(route_provider)
// 6. for our target recipient, does it contain THEIR gateway (so that we could create
if let Some(recipient) = packet_recipient {
if !topology.gateway_exists(recipient.gateway()) {
return Err(NymTopologyError::NonExistentGatewayError {
identity_key: recipient.gateway().to_base58_string(),
});
}
}
Ok(topology)
}
}
impl<'a> From<RwLockReadGuard<'a, NymRouteProvider>> for TopologyReadPermit<'a> {
fn from(permit: RwLockReadGuard<'a, NymRouteProvider>) -> Self {
TopologyReadPermit { permit }
impl<'a> From<RwLockReadGuard<'a, Option<NymTopology>>> for TopologyReadPermit<'a> {
fn from(read_permit: RwLockReadGuard<'a, Option<NymTopology>>) -> Self {
TopologyReadPermit {
permit: read_permit,
}
}
}
@@ -93,11 +99,9 @@ pub struct TopologyAccessor {
}
impl TopologyAccessor {
pub fn new(ignore_egress_epoch_roles: bool) -> Self {
pub fn new() -> Self {
TopologyAccessor {
inner: Arc::new(TopologyAccessorInner::new(NymRouteProvider::new_empty(
ignore_egress_epoch_roles,
))),
inner: Arc::new(TopologyAccessorInner::new()),
}
}
@@ -117,21 +121,8 @@ impl TopologyAccessor {
self.inner.released_manual_control.notified().await
}
#[deprecated(note = "use .current_route_provider instead")]
pub async fn current_topology(&self) -> Option<NymTopology> {
self.current_route_provider()
.await
.as_ref()
.map(|p| p.topology.clone())
}
pub async fn current_route_provider(&self) -> Option<RwLockReadGuard<NymRouteProvider>> {
let provider = self.inner.topology.read().await;
if provider.topology.is_empty() {
None
} else {
Some(provider)
}
self.inner.topology.read().await.clone()
}
pub async fn manually_change_topology(&self, new_topology: NymTopology) {
@@ -149,11 +140,15 @@ impl TopologyAccessor {
// only used by the client at startup to get a slightly more reasonable error message
// (currently displays as unused because health checker is disabled due to required changes)
pub async fn ensure_is_routable(&self) -> Result<(), NymTopologyError> {
self.inner
.topology
.read()
.await
.topology
.ensure_minimally_routable()
match self.inner.topology.read().await.deref() {
None => Err(NymTopologyError::EmptyNetworkTopology),
Some(ref topology) => topology.ensure_can_construct_path_through(DEFAULT_NUM_MIX_HOPS),
}
}
}
impl Default for TopologyAccessor {
fn default() -> Self {
TopologyAccessor::new()
}
}
@@ -3,6 +3,7 @@ use log::{debug, error};
use nym_explorer_client::{ExplorerClient, PrettyDetailedMixNodeBond};
use nym_network_defaults::var_names::EXPLORER_API;
use nym_topology::{
nym_topology_from_basic_info,
provider_trait::{async_trait, TopologyProvider},
NymTopology,
};
@@ -14,6 +15,8 @@ use url::Url;
pub use nym_country_group::CountryGroup;
const MIN_NODES_PER_LAYER: usize = 1;
fn create_explorer_client() -> Option<ExplorerClient> {
let Ok(explorer_api_url) = std::env::var(EXPLORER_API) else {
error!("Missing EXPLORER_API");
@@ -60,20 +63,30 @@ fn log_mixnode_distribution(mixnodes: &HashMap<CountryGroup, Vec<NodeId>>) {
}
fn check_layer_integrity(topology: NymTopology) -> Result<(), ()> {
if topology.ensure_minimally_routable().is_err() {
let mixes = topology.mixes();
if mixes.keys().len() < 3 {
error!("Layer is missing in topology!");
return Err(());
}
for (layer, mixnodes) in mixes {
debug!("Layer {:?} has {} mixnodes", layer, mixnodes.len());
if mixnodes.len() < MIN_NODES_PER_LAYER {
error!(
"There are only {} mixnodes in layer {:?}",
mixnodes.len(),
layer
);
return Err(());
}
}
Ok(())
}
#[deprecated(note = "use NymApiTopologyProvider instead as explorer API will soon be removed")]
pub struct GeoAwareTopologyProvider {
validator_client: nym_validator_client::client::NymApiClient,
filter_on: GroupBy,
}
#[allow(deprecated)]
impl GeoAwareTopologyProvider {
pub fn new(mut nym_api_urls: Vec<Url>, filter_on: GroupBy) -> GeoAwareTopologyProvider {
log::info!(
@@ -91,15 +104,6 @@ impl GeoAwareTopologyProvider {
}
async fn get_topology(&self) -> Option<NymTopology> {
let rewarded_set = self
.validator_client
.get_current_rewarded_set()
.await
.inspect_err(|err| error!("failed to get current rewarded set: {err}"))
.ok()?;
let mut topology = NymTopology::new_empty(rewarded_set);
let mixnodes = match self
.validator_client
.get_all_basic_active_mixing_assigned_nodes()
@@ -183,8 +187,7 @@ impl GeoAwareTopologyProvider {
.filter(|m| filtered_mixnode_ids.contains(&m.node_id))
.collect::<Vec<_>>();
topology.add_skimmed_nodes(&mixnodes);
topology.add_skimmed_nodes(&gateways);
let topology = nym_topology_from_basic_info(&mixnodes, &gateways);
// TODO: return real error type
check_layer_integrity(topology.clone()).ok()?;
@@ -193,7 +196,6 @@ impl GeoAwareTopologyProvider {
}
}
#[allow(deprecated)]
#[cfg(not(target_arch = "wasm32"))]
#[async_trait]
impl TopologyProvider for GeoAwareTopologyProvider {
@@ -203,7 +205,6 @@ impl TopologyProvider for GeoAwareTopologyProvider {
}
}
#[allow(deprecated)]
#[cfg(target_arch = "wasm32")]
#[async_trait(?Send)]
impl TopologyProvider for GeoAwareTopologyProvider {
@@ -19,7 +19,6 @@ mod accessor;
pub mod geo_aware_provider;
pub mod nym_api_provider;
#[allow(deprecated)]
pub use geo_aware_provider::GeoAwareTopologyProvider;
pub use nym_api_provider::{Config as NymApiTopologyProviderConfig, NymApiTopologyProvider};
pub use nym_topology::provider_trait::TopologyProvider;
@@ -28,7 +27,7 @@ pub use nym_topology::provider_trait::TopologyProvider;
const MAX_FAILURE_COUNT: usize = 10;
pub struct TopologyRefresherConfig {
pub refresh_rate: Duration,
refresh_rate: Duration,
}
impl TopologyRefresherConfig {
@@ -97,24 +96,28 @@ impl TopologyRefresher {
self.topology_accessor.ensure_is_routable().await
}
pub async fn ensure_contains_routable_egress(
pub async fn ensure_contains_gateway(
&self,
egress: NodeIdentity,
gateway: &NodeIdentity,
) -> Result<(), NymTopologyError> {
let topology = self
.topology_accessor
.current_route_provider()
.current_topology()
.await
.ok_or(NymTopologyError::EmptyNetworkTopology)?;
let _ = topology.egress_by_identity(egress)?;
if !topology.gateway_exists(gateway) {
return Err(NymTopologyError::NonExistentGatewayError {
identity_key: gateway.to_base58_string(),
});
}
Ok(())
}
pub async fn wait_for_gateway(
&mut self,
gateway: NodeIdentity,
gateway: &NodeIdentity,
timeout_duration: Duration,
) -> Result<(), NymTopologyError> {
info!(
@@ -132,7 +135,7 @@ impl TopologyRefresher {
})
}
_ = self.try_refresh() => {
if self.ensure_contains_routable_egress(gateway).await.is_ok() {
if self.ensure_contains_gateway(gateway).await.is_ok() {
return Ok(())
}
info!("gateway '{gateway}' is still not online...");
@@ -4,39 +4,32 @@
use async_trait::async_trait;
use log::{debug, error, warn};
use nym_topology::provider_trait::TopologyProvider;
use nym_topology::NymTopology;
use nym_topology::{NymTopology, NymTopologyError};
use nym_validator_client::UserAgent;
use rand::prelude::SliceRandom;
use rand::thread_rng;
use std::cmp::min;
use url::Url;
// the same values as our current (10.06.24) blacklist
pub const DEFAULT_MIN_MIXNODE_PERFORMANCE: u8 = 50;
pub const DEFAULT_MIN_GATEWAY_PERFORMANCE: u8 = 50;
#[derive(Debug)]
pub struct Config {
pub min_mixnode_performance: u8,
pub min_gateway_performance: u8,
pub use_extended_topology: bool,
pub ignore_egress_epoch_role: bool,
}
impl From<nym_client_core_config_types::Topology> for Config {
fn from(value: nym_client_core_config_types::Topology) -> Self {
impl Default for Config {
fn default() -> Self {
// old values that decided on blacklist membership
Config {
min_mixnode_performance: value.minimum_mixnode_performance,
min_gateway_performance: value.minimum_gateway_performance,
use_extended_topology: value.use_extended_topology,
ignore_egress_epoch_role: value.ignore_egress_epoch_role,
min_mixnode_performance: DEFAULT_MIN_MIXNODE_PERFORMANCE,
min_gateway_performance: DEFAULT_MIN_GATEWAY_PERFORMANCE,
}
}
}
impl Config {
// if we're using 'extended' topology, filter the nodes based on the lowest set performance
fn min_node_performance(&self) -> u8 {
min(self.min_mixnode_performance, self.min_gateway_performance)
}
}
pub struct NymApiTopologyProvider {
config: Config,
@@ -46,11 +39,7 @@ pub struct NymApiTopologyProvider {
}
impl NymApiTopologyProvider {
pub fn new(
config: impl Into<Config>,
mut nym_api_urls: Vec<Url>,
user_agent: Option<UserAgent>,
) -> Self {
pub fn new(config: Config, mut nym_api_urls: Vec<Url>, user_agent: Option<UserAgent>) -> Self {
nym_api_urls.shuffle(&mut thread_rng());
let validator_client = if let Some(user_agent) = user_agent {
@@ -63,7 +52,7 @@ impl NymApiTopologyProvider {
};
NymApiTopologyProvider {
config: config.into(),
config,
validator_client,
nym_api_urls,
currently_used_api: 0,
@@ -81,69 +70,70 @@ impl NymApiTopologyProvider {
.change_nym_api(self.nym_api_urls[self.currently_used_api].clone())
}
/// Verifies whether nodes a reasonably distributed among all mix layers.
///
/// In ideal world we would have 33% nodes on layer 1, 33% on layer 2 and 33% on layer 3.
/// However, this is a rather unrealistic expectation, instead we check whether there exists
/// a layer with more than 66% of nodes or with fewer than 15% and if so, we trigger a failure.
///
/// # Arguments
///
/// * `topology`: active topology constructed from validator api data
fn check_layer_distribution(
&self,
active_topology: &NymTopology,
) -> Result<(), NymTopologyError> {
let lower_threshold = 0.15;
let upper_threshold = 0.66;
active_topology.ensure_even_layer_distribution(lower_threshold, upper_threshold)
}
async fn get_current_compatible_topology(&mut self) -> Option<NymTopology> {
let rewarded_set = self
let mixnodes = match self
.validator_client
.get_current_rewarded_set()
.get_all_basic_active_mixing_assigned_nodes()
.await
.inspect_err(|err| error!("failed to get current rewarded set: {err}"))
.ok()?;
let mut topology = NymTopology::new_empty(rewarded_set);
if self.config.use_extended_topology {
let all_nodes = self
.validator_client
.get_all_basic_nodes()
.await
.inspect_err(|err| error!("failed to get network nodes: {err}"))
.ok()?;
debug!(
"there are {} nodes on the network (before filtering)",
all_nodes.len()
);
topology.add_additional_nodes(all_nodes.iter().filter(|n| {
n.performance.round_to_integer() >= self.config.min_node_performance()
}));
} else {
// if we're not using extended topology, we're only getting active set mixnodes and gateways
let mixnodes = self
.validator_client
.get_all_basic_active_mixing_assigned_nodes()
.await
.inspect_err(|err| error!("failed to get network mixnodes: {err}"))
.ok()?;
// TODO: we really should be getting ACTIVE gateways only
let gateways = self
.validator_client
.get_all_basic_entry_assigned_nodes()
.await
.inspect_err(|err| error!("failed to get network gateways: {err}"))
.ok()?;
debug!(
"there are {} mixnodes and {} gateways in total (before performance filtering)",
mixnodes.len(),
gateways.len()
);
topology.add_additional_nodes(mixnodes.iter().filter(|m| {
m.performance.round_to_integer() >= self.config.min_mixnode_performance
}));
topology.add_additional_nodes(gateways.iter().filter(|m| {
m.performance.round_to_integer() >= self.config.min_gateway_performance
}));
{
Err(err) => {
error!("failed to get network mixnodes - {err}");
return None;
}
Ok(mixes) => mixes,
};
if !topology.is_minimally_routable() {
error!("the current filtered active topology can't be used to construct any packets");
return None;
}
let gateways = match self
.validator_client
.get_all_basic_entry_assigned_nodes()
.await
{
Err(err) => {
error!("failed to get network gateways - {err}");
return None;
}
Ok(gateways) => gateways,
};
Some(topology)
debug!(
"there are {} mixnodes and {} gateways in total (before performance filtering)",
mixnodes.len(),
gateways.len()
);
let topology = NymTopology::from_unordered(
mixnodes.iter().filter(|m| {
m.performance.round_to_integer() >= self.config.min_mixnode_performance
}),
gateways.iter().filter(|g| {
g.performance.round_to_integer() >= self.config.min_gateway_performance
}),
);
if let Err(err) = self.check_layer_distribution(&topology) {
warn!("The current filtered active topology has extremely skewed layer distribution. It cannot be used: {err}");
self.use_next_nym_api();
None
} else {
Some(topology)
}
}
}
@@ -152,11 +142,7 @@ impl NymApiTopologyProvider {
#[async_trait]
impl TopologyProvider for NymApiTopologyProvider {
async fn get_new_topology(&mut self) -> Option<NymTopology> {
let Some(topology) = self.get_current_compatible_topology().await else {
self.use_next_nym_api();
return None;
};
Some(topology)
self.get_current_compatible_topology().await
}
}
@@ -164,10 +150,6 @@ impl TopologyProvider for NymApiTopologyProvider {
#[async_trait(?Send)]
impl TopologyProvider for NymApiTopologyProvider {
async fn get_new_topology(&mut self) -> Option<NymTopology> {
let Some(topology) = self.get_current_compatible_topology().await else {
self.use_next_nym_api();
return None;
};
Some(topology)
self.get_current_compatible_topology().await
}
}
+4 -7
View File
@@ -4,8 +4,8 @@
use crate::client::mix_traffic::transceiver::ErasedGatewayError;
use nym_crypto::asymmetric::identity::Ed25519RecoveryError;
use nym_gateway_client::error::GatewayClientError;
use nym_topology::node::RoutingNodeError;
use nym_topology::{NodeId, NymTopologyError};
use nym_topology::gateway::GatewayConversionError;
use nym_topology::NymTopologyError;
use nym_validator_client::ValidatorClientError;
use std::error::Error;
use std::path::PathBuf;
@@ -74,10 +74,10 @@ pub enum ClientCoreError {
#[error("the gateway id is invalid - {0}")]
UnableToCreatePublicKeyFromGatewayId(Ed25519RecoveryError),
#[error("the node is malformed: {source}")]
#[error("The gateway is malformed: {source}")]
MalformedGateway {
#[from]
source: Box<RoutingNodeError>,
source: GatewayConversionError,
},
#[error("failed to establish connection to gateway: {source}")]
@@ -159,9 +159,6 @@ pub enum ClientCoreError {
#[error("the specified gateway '{gateway}' does not support the wss protocol")]
UnsupportedWssProtocol { gateway: String },
#[error("node {id} ({identity}) does not support mixnet entry mode")]
UnsupportedEntry { id: NodeId, identity: String },
#[error(
"failed to load custom topology using path '{}'. detailed message: {source}", file_path.display()
)]
+20 -40
View File
@@ -7,7 +7,7 @@ use futures::{SinkExt, StreamExt};
use log::{debug, info, trace, warn};
use nym_crypto::asymmetric::identity;
use nym_gateway_client::GatewayClient;
use nym_topology::node::RoutingNode;
use nym_topology::gateway;
use nym_validator_client::client::IdentityKeyRef;
use nym_validator_client::UserAgent;
use rand::{seq::SliceRandom, Rng};
@@ -15,7 +15,6 @@ use std::{sync::Arc, time::Duration};
use tungstenite::Message;
use url::Url;
use nym_topology::NodeId;
#[cfg(not(target_arch = "wasm32"))]
use tokio::net::TcpStream;
#[cfg(not(target_arch = "wasm32"))]
@@ -26,6 +25,7 @@ use tokio::time::Instant;
use tokio_tungstenite::connect_async;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
#[cfg(target_arch = "wasm32")]
use wasm_utils::websocket::JSWebsocket;
#[cfg(target_arch = "wasm32")]
@@ -48,30 +48,22 @@ const PING_TIMEOUT: Duration = Duration::from_millis(1000);
// The abstraction that some of these helpers use
pub trait ConnectableGateway {
fn node_id(&self) -> NodeId;
fn identity(&self) -> identity::PublicKey;
fn clients_address(&self, prefer_ipv6: bool) -> Option<String>;
fn identity(&self) -> &identity::PublicKey;
fn clients_address(&self) -> String;
fn is_wss(&self) -> bool;
}
impl ConnectableGateway for RoutingNode {
fn node_id(&self) -> NodeId {
self.node_id
impl ConnectableGateway for gateway::LegacyNode {
fn identity(&self) -> &identity::PublicKey {
self.identity()
}
fn identity(&self) -> identity::PublicKey {
self.identity_key
}
fn clients_address(&self, prefer_ipv6: bool) -> Option<String> {
self.ws_entry_address(prefer_ipv6)
fn clients_address(&self) -> String {
self.clients_address()
}
fn is_wss(&self) -> bool {
self.entry
.as_ref()
.map(|e| e.clients_wss_port.is_some())
.unwrap_or_default()
self.clients_wss_port.is_some()
}
}
@@ -91,7 +83,7 @@ pub async fn current_gateways<R: Rng>(
nym_apis: &[Url],
user_agent: Option<UserAgent>,
minimum_performance: u8,
) -> Result<Vec<RoutingNode>, ClientCoreError> {
) -> Result<Vec<gateway::LegacyNode>, ClientCoreError> {
let nym_api = nym_apis
.choose(rng)
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
@@ -112,7 +104,7 @@ pub async fn current_gateways<R: Rng>(
.iter()
.filter(|g| g.performance.round_to_integer() >= minimum_performance)
.filter_map(|gateway| gateway.try_into().ok())
.collect::<Vec<_>>();
.collect::<Vec<gateway::LegacyNode>>();
log::debug!("After checking validity: {}", valid_gateways.len());
log::trace!("Valid gateways: {:#?}", valid_gateways);
@@ -142,12 +134,7 @@ async fn measure_latency<G>(gateway: &G) -> Result<GatewayWithLatency<G>, Client
where
G: ConnectableGateway,
{
let Some(addr) = gateway.clients_address(false) else {
return Err(ClientCoreError::UnsupportedEntry {
id: gateway.node_id(),
identity: gateway.identity().to_string(),
});
};
let addr = gateway.clients_address();
trace!(
"establishing connection to {} ({addr})...",
gateway.identity(),
@@ -203,7 +190,7 @@ where
Ok(GatewayWithLatency::new(gateway, avg))
}
pub async fn choose_gateway_by_latency<R: Rng, G: ConnectableGateway + Clone>(
pub async fn choose_gateway_by_latency<'a, R: Rng, G: ConnectableGateway + Clone>(
rng: &mut R,
gateways: &[G],
must_use_tls: bool,
@@ -218,7 +205,7 @@ pub async fn choose_gateway_by_latency<R: Rng, G: ConnectableGateway + Clone>(
let gateways_with_latency = Arc::new(tokio::sync::Mutex::new(Vec::new()));
futures::stream::iter(gateways)
.for_each_concurrent(CONCURRENT_GATEWAYS_MEASURED, |gateway| async {
let id = gateway.identity();
let id = *gateway.identity();
trace!("measuring latency to {id}...");
match measure_latency(gateway).await {
Ok(with_latency) => {
@@ -265,9 +252,9 @@ fn filter_by_tls<G: ConnectableGateway>(
pub(super) fn uniformly_random_gateway<R: Rng>(
rng: &mut R,
gateways: &[RoutingNode],
gateways: &[gateway::LegacyNode],
must_use_tls: bool,
) -> Result<RoutingNode, ClientCoreError> {
) -> Result<gateway::LegacyNode, ClientCoreError> {
filter_by_tls(gateways, must_use_tls)?
.choose(rng)
.ok_or(ClientCoreError::NoGatewaysOnNetwork)
@@ -276,9 +263,9 @@ pub(super) fn uniformly_random_gateway<R: Rng>(
pub(super) fn get_specified_gateway(
gateway_identity: IdentityKeyRef,
gateways: &[RoutingNode],
gateways: &[gateway::LegacyNode],
must_use_tls: bool,
) -> Result<RoutingNode, ClientCoreError> {
) -> Result<gateway::LegacyNode, ClientCoreError> {
log::debug!("Requesting specified gateway: {}", gateway_identity);
let user_gateway = identity::PublicKey::from_base58_string(gateway_identity)
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
@@ -288,14 +275,7 @@ pub(super) fn get_specified_gateway(
.find(|gateway| gateway.identity_key == user_gateway)
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_identity.to_string()))?;
let Some(entry_details) = gateway.entry.as_ref() else {
return Err(ClientCoreError::UnsupportedEntry {
id: gateway.node_id,
identity: gateway.identity().to_string(),
});
};
if must_use_tls && entry_details.clients_wss_port.is_none() {
if must_use_tls && gateway.clients_wss_port.is_none() {
return Err(ClientCoreError::UnsupportedWssProtocol {
gateway: gateway_identity.to_string(),
});
+2 -2
View File
@@ -19,7 +19,7 @@ use crate::init::types::{
use nym_client_core_gateways_storage::GatewaysDetailsStore;
use nym_client_core_gateways_storage::{GatewayDetails, GatewayRegistration};
use nym_gateway_client::client::InitGatewayClient;
use nym_topology::node::RoutingNode;
use nym_topology::gateway;
use rand::rngs::OsRng;
use rand::{CryptoRng, RngCore};
use serde::Serialize;
@@ -50,7 +50,7 @@ async fn setup_new_gateway<K, D>(
key_store: &K,
details_store: &D,
selection_specification: GatewaySelectionSpecification,
available_gateways: Vec<RoutingNode>,
available_gateways: Vec<gateway::LegacyNode>,
) -> Result<InitialisationResult, ClientCoreError>
where
K: KeyStore,
+5 -12
View File
@@ -13,7 +13,7 @@ use nym_crypto::asymmetric::identity;
use nym_gateway_client::client::InitGatewayClient;
use nym_gateway_requests::shared_key::SharedGatewayKey;
use nym_sphinx::addressing::clients::Recipient;
use nym_topology::node::RoutingNode;
use nym_topology::gateway;
use nym_validator_client::client::IdentityKey;
use nym_validator_client::nyxd::AccountId;
use serde::Serialize;
@@ -38,23 +38,16 @@ pub enum SelectedGateway {
impl SelectedGateway {
pub fn from_topology_node(
node: RoutingNode,
node: gateway::LegacyNode,
must_use_tls: bool,
) -> Result<Self, ClientCoreError> {
// for now, let's use 'old' behaviour, if you want to change it, you can pass it up the enum stack yourself : )
let prefer_ipv6 = false;
let gateway_listener = if must_use_tls {
node.ws_entry_address_tls()
node.clients_address_tls()
.ok_or(ClientCoreError::UnsupportedWssProtocol {
gateway: node.identity_key.to_base58_string(),
})?
} else {
node.ws_entry_address(prefer_ipv6)
.ok_or(ClientCoreError::UnsupportedEntry {
id: node.node_id,
identity: node.identity_key.to_base58_string(),
})?
node.clients_address()
};
let gateway_listener =
@@ -207,7 +200,7 @@ pub enum GatewaySetup {
specification: GatewaySelectionSpecification,
// TODO: seems to be a bit inefficient to pass them by value
available_gateways: Vec<RoutingNode>,
available_gateways: Vec<gateway::LegacyNode>,
},
ReuseConnection {
+2 -1
View File
@@ -14,7 +14,8 @@ pub mod error;
pub mod init;
pub use nym_topology::{
HardcodedTopologyProvider, NymRouteProvider, NymTopology, NymTopologyError, TopologyProvider,
HardcodedTopologyProvider, NymTopology, NymTopologyError, SerializableNymTopology,
SerializableTopologyError, TopologyProvider,
};
#[cfg(target_arch = "wasm32")]
@@ -32,10 +32,10 @@ use time::Date;
use url::Url;
pub use crate::nym_api::NymApiClientExt;
use nym_mixnet_contract_common::EpochRewardedSet;
pub use nym_mixnet_contract_common::{
mixnode::MixNodeDetails, GatewayBond, IdentityKey, IdentityKeyRef, NodeId, NymNodeDetails,
};
// re-export the type to not break existing imports
pub use crate::coconut::EcashApiClient;
@@ -367,10 +367,6 @@ impl NymApiClient {
Ok(self.nym_api.get_basic_gateways().await?.nodes)
}
pub async fn get_current_rewarded_set(&self) -> Result<EpochRewardedSet, ValidatorClientError> {
Ok(self.nym_api.get_rewarded_set().await?.into())
}
/// retrieve basic information for nodes are capable of operating as an entry gateway
/// this includes legacy gateways and nym-nodes
pub async fn get_all_basic_entry_assigned_nodes(
@@ -13,7 +13,7 @@ use nym_api_requests::ecash::models::{
use nym_api_requests::ecash::VerificationKeyResponse;
use nym_api_requests::models::{
AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse,
NodeRefreshBody, NymNodeDescription, RewardedSetResponse,
NodeRefreshBody, NymNodeDescription,
};
use nym_api_requests::nym_nodes::PaginatedCachedNodesResponse;
use nym_api_requests::pagination::PaginatedResponse;
@@ -235,15 +235,6 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[instrument(level = "debug", skip(self))]
async fn get_rewarded_set(&self) -> Result<RewardedSetResponse, NymAPIError> {
self.get_json(
&[routes::API_VERSION, "nym-nodes", "rewarded-set"],
NO_PARAMS,
)
.await
}
/// retrieve basic information for nodes are capable of operating as an entry gateway
/// this includes legacy gateways and nym-nodes
#[instrument(level = "debug", skip(self))]
@@ -921,7 +912,6 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[instrument(level = "debug", skip(self))]
async fn force_refresh_describe_cache(
&self,
request: &NodeRefreshBody,
@@ -934,7 +924,6 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[instrument(level = "debug", skip(self))]
async fn issued_ticketbooks_for(
&self,
expiration_date: Date,
@@ -951,7 +940,6 @@ pub trait NymApiClientExt: ApiClient {
.await
}
#[instrument(level = "debug", skip(self))]
async fn issued_ticketbooks_challenge(
&self,
expiration_date: Date,
@@ -26,10 +26,10 @@ use nym_mixnet_contract_common::{
reward_params::{Performance, RewardingParams},
rewarding::{EstimatedCurrentEpochRewardResponse, PendingRewardResponse},
ContractBuildInformation, ContractState, ContractStateParams, CurrentIntervalResponse,
CurrentNymNodeVersionResponse, Delegation, EpochEventId, EpochRewardedSet, EpochStatus,
GatewayBond, GatewayBondResponse, GatewayOwnershipResponse, HistoricalNymNodeVersionEntry,
IdentityKey, IdentityKeyRef, IntervalEventId, MixNodeBond, MixNodeDetails,
MixOwnershipResponse, MixnodeDetailsByIdentityResponse, MixnodeDetailsResponse, NodeId,
CurrentNymNodeVersionResponse, Delegation, EpochEventId, EpochStatus, GatewayBond,
GatewayBondResponse, GatewayOwnershipResponse, HistoricalNymNodeVersionEntry, IdentityKey,
IdentityKeyRef, IntervalEventId, MixNodeBond, MixNodeDetails, MixOwnershipResponse,
MixnodeDetailsByIdentityResponse, MixnodeDetailsResponse, NodeId,
NumberOfPendingEventsResponse, NymNodeBond, NymNodeDetails, NymNodeVersionHistoryResponse,
PagedAllDelegationsResponse, PagedDelegatorDelegationsResponse, PagedGatewayResponse,
PagedMixnodeBondsResponse, PagedNodeDelegationsResponse, PendingEpochEvent,
@@ -670,7 +670,7 @@ impl<T> PagedMixnetQueryClient for T where T: MixnetQueryClient {}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait MixnetQueryClientExt: MixnetQueryClient {
async fn get_rewarded_set(&self) -> Result<EpochRewardedSet, NyxdError> {
async fn get_rewarded_set(&self) -> Result<RewardedSet, NyxdError> {
let error_response = |message| Err(NyxdError::extension_query_failure("mixnet", message));
let metadata = self.get_rewarded_set_metadata().await?;
@@ -711,16 +711,13 @@ pub trait MixnetQueryClientExt: MixnetQueryClient {
return error_response("the nodes assigned for 'standby' returned unexpected epoch_id");
}
Ok(EpochRewardedSet {
epoch_id: expected_epoch_id,
assignment: RewardedSet {
entry_gateways: entry.nodes,
exit_gateways: exit.nodes,
layer1: layer1.nodes,
layer2: layer2.nodes,
layer3: layer3.nodes,
standby: standby.nodes,
},
Ok(RewardedSet {
entry_gateways: entry.nodes,
exit_gateways: exit.nodes,
layer1: layer1.nodes,
layer2: layer2.nodes,
layer3: layer3.nodes,
standby: standby.nodes,
})
}
}
@@ -7,7 +7,6 @@
use crate::constants::{TOKEN_SUPPLY, UNIT_DELEGATION_BASE};
use crate::error::MixnetContractError;
use crate::helpers::IntoBaseDecimal;
use crate::nym_node::Role;
use crate::reward_params::{NodeRewardingParameters, RewardingParams};
use crate::rewarding::helpers::truncate_reward;
use crate::rewarding::RewardDistribution;
@@ -612,16 +611,6 @@ pub enum LegacyMixLayer {
Three = 3,
}
impl From<LegacyMixLayer> for Role {
fn from(layer: LegacyMixLayer) -> Self {
match layer {
LegacyMixLayer::One => Role::Layer1,
LegacyMixLayer::Two => Role::Layer2,
LegacyMixLayer::Three => Role::Layer3,
}
}
}
impl From<LegacyMixLayer> for String {
fn from(layer: LegacyMixLayer) -> Self {
(layer as u8).to_string()
@@ -3,7 +3,6 @@
use crate::config_score::{ConfigScoreParams, OutdatedVersionWeights, VersionScoreFormulaParams};
use crate::nym_node::Role;
use crate::EpochId;
use contracts_common::Percent;
use cosmwasm_schema::cw_serde;
use cosmwasm_std::Coin;
@@ -33,23 +32,6 @@ impl RoleAssignment {
}
}
#[cw_serde]
#[derive(Default)]
pub struct EpochRewardedSet {
pub epoch_id: EpochId,
pub assignment: RewardedSet,
}
impl From<(EpochId, RewardedSet)> for EpochRewardedSet {
fn from((epoch_id, assignment): (EpochId, RewardedSet)) -> Self {
EpochRewardedSet {
epoch_id,
assignment,
}
}
}
#[cw_serde]
#[derive(Default)]
pub struct RewardedSet {
@@ -87,29 +69,6 @@ impl RewardedSet {
pub fn rewarded_set_size(&self) -> usize {
self.active_set_size() + self.standby.len()
}
pub fn get_role(&self, node_id: NodeId) -> Option<Role> {
// given each role has ~100 entries in them, doing linear lookup with vec should be fine
if self.entry_gateways.contains(&node_id) {
return Some(Role::EntryGateway);
}
if self.exit_gateways.contains(&node_id) {
return Some(Role::ExitGateway);
}
if self.layer1.contains(&node_id) {
return Some(Role::Layer1);
}
if self.layer2.contains(&node_id) {
return Some(Role::Layer2);
}
if self.layer3.contains(&node_id) {
return Some(Role::Layer3);
}
if self.standby.contains(&node_id) {
return Some(Role::Standby);
}
None
}
}
#[cw_serde]
@@ -23,11 +23,6 @@ impl SqliteEcashTicketbookManager {
SqliteEcashTicketbookManager { connection_pool }
}
/// Closes the connection pool.
pub async fn close(&self) {
self.connection_pool.close().await
}
pub(crate) async fn cleanup_expired(&self, deadline: Date) -> Result<(), sqlx::Error> {
sqlx::query!(
"DELETE FROM ecash_ticketbook WHERE expiration_date <= ?",
@@ -43,10 +43,6 @@ impl Debug for EphemeralStorage {
impl Storage for EphemeralStorage {
type StorageError = StorageError;
async fn close(&self) {
// nothing to do here
}
async fn cleanup_expired(&self) -> Result<(), Self::StorageError> {
self.storage_manager.cleanup_expired().await;
Ok(())
@@ -89,10 +89,6 @@ impl PersistentStorage {
impl Storage for PersistentStorage {
type StorageError = StorageError;
async fn close(&self) {
self.storage_manager.close().await
}
/// remove all expired ticketbooks and expiration date signatures
async fn cleanup_expired(&self) -> Result<(), Self::StorageError> {
let ecash_yesterday = ecash_today().date().previous_day().unwrap();
-2
View File
@@ -22,8 +22,6 @@ use std::error::Error;
pub trait Storage: Send + Sync {
type StorageError: Error;
async fn close(&self);
/// remove all expired ticketbooks and expiration date signatures
async fn cleanup_expired(&self) -> Result<(), Self::StorageError>;
@@ -9,7 +9,7 @@ use futures::{Sink, Stream};
use rand::{CryptoRng, RngCore};
use tungstenite::Message as WsMessage;
impl<S, R> State<'_, S, R> {
impl<'a, S, R> State<'a, S, R> {
async fn client_handshake_inner(&mut self) -> Result<(), HandshakeError>
where
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin,
@@ -10,7 +10,7 @@ use crate::registration::handshake::{error::HandshakeError, WsItem};
use futures::{Sink, Stream};
use tungstenite::Message as WsMessage;
impl<S, R> State<'_, S, R> {
impl<'a, S, R> State<'a, S, R> {
async fn gateway_handshake_inner(
&mut self,
raw_init_message: Vec<u8>,
+195 -195
View File
@@ -192,28 +192,6 @@ impl Client {
&self.base_url
}
pub fn create_request<B, K, V>(
&self,
method: reqwest::Method,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: Option<&B>,
) -> RequestBuilder
where
B: Serialize + ?Sized,
K: AsRef<str>,
V: AsRef<str>,
{
let url = sanitize_url(&self.base_url, path, params);
let mut request = self.reqwest_client.request(method.clone(), url);
if let Some(body) = json_body {
request = request.json(body);
}
request
}
pub fn create_get_request<K, V>(
&self,
path: PathSegments<'_>,
@@ -227,6 +205,38 @@ impl Client {
self.reqwest_client.get(url)
}
#[instrument(level = "debug", skip_all, fields(path=?path))]
async fn send_get_request<K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<Response, HttpClientError<E>>
where
K: AsRef<str>,
V: AsRef<str>,
E: Display,
{
tracing::trace!("Sending GET request");
let url = sanitize_url(&self.base_url, path, params);
#[cfg(target_arch = "wasm32")]
{
Ok(
wasmtimer::tokio::timeout(
self.request_timeout,
self.reqwest_client.get(url).send(),
)
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??,
)
}
#[cfg(not(target_arch = "wasm32"))]
{
Ok(self.reqwest_client.get(url).send().await?)
}
}
pub fn create_post_request<B, K, V>(
&self,
path: PathSegments<'_>,
@@ -242,6 +252,36 @@ impl Client {
self.reqwest_client.post(url).json(json_body)
}
async fn send_post_request<B, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<Response, HttpClientError<E>>
where
B: Serialize + ?Sized,
K: AsRef<str>,
V: AsRef<str>,
E: Display,
{
let url = sanitize_url(&self.base_url, path, params);
#[cfg(target_arch = "wasm32")]
{
Ok(wasmtimer::tokio::timeout(
self.request_timeout,
self.reqwest_client.post(url).json(json_body).send(),
)
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??)
}
#[cfg(not(target_arch = "wasm32"))]
{
Ok(self.reqwest_client.post(url).json(json_body).send().await?)
}
}
pub fn create_delete_request<K, V>(
&self,
path: PathSegments<'_>,
@@ -255,6 +295,35 @@ impl Client {
self.reqwest_client.delete(url)
}
pub async fn send_delete_request<K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<Response, HttpClientError<E>>
where
K: AsRef<str>,
V: AsRef<str>,
E: Display,
{
tracing::trace!("Sending DELETE request");
let url = sanitize_url(&self.base_url, path, params);
#[cfg(target_arch = "wasm32")]
{
Ok(wasmtimer::tokio::timeout(
self.request_timeout,
self.reqwest_client.delete(url).send(),
)
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??)
}
#[cfg(not(target_arch = "wasm32"))]
{
Ok(self.reqwest_client.delete(url).send().await?)
}
}
pub fn create_patch_request<B, K, V>(
&self,
path: PathSegments<'_>,
@@ -270,87 +339,6 @@ impl Client {
self.reqwest_client.patch(url).json(json_body)
}
async fn send_request<B, K, V, E>(
&self,
method: reqwest::Method,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: Option<&B>,
) -> Result<Response, HttpClientError<E>>
where
B: Serialize + ?Sized,
K: AsRef<str>,
V: AsRef<str>,
E: Display,
{
let url = sanitize_url(&self.base_url, path, params);
let mut request = self.reqwest_client.request(method.clone(), url);
if let Some(body) = json_body {
request = request.json(body);
}
#[cfg(target_arch = "wasm32")]
{
Ok(
wasmtimer::tokio::timeout(self.request_timeout, request.send())
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??,
)
}
#[cfg(not(target_arch = "wasm32"))]
{
Ok(request.send().await?)
}
}
#[instrument(level = "debug", skip_all, fields(path=?path))]
async fn send_get_request<K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<Response, HttpClientError<E>>
where
K: AsRef<str>,
V: AsRef<str>,
E: Display,
{
self.send_request(reqwest::Method::GET, path, params, None::<&()>)
.await
}
async fn send_post_request<B, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<Response, HttpClientError<E>>
where
B: Serialize + ?Sized,
K: AsRef<str>,
V: AsRef<str>,
E: Display,
{
self.send_request(reqwest::Method::POST, path, params, Some(json_body))
.await
}
pub async fn send_delete_request<K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
) -> Result<Response, HttpClientError<E>>
where
K: AsRef<str>,
V: AsRef<str>,
E: Display,
{
self.send_request(reqwest::Method::DELETE, path, params, None::<&()>)
.await
}
pub async fn send_patch_request<B, K, V, E>(
&self,
path: PathSegments<'_>,
@@ -363,8 +351,27 @@ impl Client {
V: AsRef<str>,
E: Display,
{
self.send_request(reqwest::Method::PATCH, path, params, Some(json_body))
let url = sanitize_url(&self.base_url, path, params);
#[cfg(target_arch = "wasm32")]
{
Ok(wasmtimer::tokio::timeout(
self.request_timeout,
self.reqwest_client.patch(url).json(json_body).send(),
)
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??)
}
#[cfg(not(target_arch = "wasm32"))]
{
Ok(self
.reqwest_client
.patch(url)
.json(json_body)
.send()
.await?)
}
}
#[instrument(level = "debug", skip_all)]
@@ -432,39 +439,6 @@ impl Client {
parse_response(res, true).await
}
async fn call_json_endpoint<B, T, S, E>(
&self,
method: reqwest::Method,
endpoint: S,
json_body: Option<&B>,
) -> Result<T, HttpClientError<E>>
where
B: Serialize + ?Sized,
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str>,
{
let mut request = self
.reqwest_client
.request(method.clone(), self.base_url.join(endpoint.as_ref())?);
if let Some(body) = json_body {
request = request.json(body);
}
#[cfg(target_arch = "wasm32")]
let res = {
wasmtimer::tokio::timeout(self.request_timeout, request.send())
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??
};
#[cfg(not(target_arch = "wasm32"))]
let res = { request.send().await? };
parse_response(res, false).await
}
#[instrument(level = "debug", skip_all)]
pub async fn get_json_endpoint<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
where
@@ -472,8 +446,27 @@ impl Client {
E: Display + DeserializeOwned,
S: AsRef<str>,
{
self.call_json_endpoint(reqwest::Method::GET, endpoint, None::<&()>)
#[cfg(target_arch = "wasm32")]
let res = {
wasmtimer::tokio::timeout(
self.request_timeout,
self.reqwest_client
.get(self.base_url.join(endpoint.as_ref())?)
.send(),
)
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??
};
#[cfg(not(target_arch = "wasm32"))]
let res = {
self.reqwest_client
.get(self.base_url.join(endpoint.as_ref())?)
.send()
.await?
};
parse_response(res, false).await
}
pub async fn post_json_endpoint<B, T, S, E>(
@@ -487,8 +480,29 @@ impl Client {
E: Display + DeserializeOwned,
S: AsRef<str>,
{
self.call_json_endpoint(reqwest::Method::POST, endpoint, Some(json_body))
#[cfg(target_arch = "wasm32")]
let res = {
wasmtimer::tokio::timeout(
self.request_timeout,
self.reqwest_client
.post(self.base_url.join(endpoint.as_ref())?)
.json(json_body)
.send(),
)
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??
};
#[cfg(not(target_arch = "wasm32"))]
let res = {
self.reqwest_client
.post(self.base_url.join(endpoint.as_ref())?)
.json(json_body)
.send()
.await?
};
parse_response(res, true).await
}
pub async fn delete_json_endpoint<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
@@ -497,8 +511,27 @@ impl Client {
E: Display + DeserializeOwned,
S: AsRef<str>,
{
self.call_json_endpoint(reqwest::Method::DELETE, endpoint, None::<&()>)
#[cfg(target_arch = "wasm32")]
let res = {
wasmtimer::tokio::timeout(
self.request_timeout,
self.reqwest_client
.delete(self.base_url.join(endpoint.as_ref())?)
.send(),
)
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??
};
#[cfg(not(target_arch = "wasm32"))]
let res = {
self.reqwest_client
.delete(self.base_url.join(endpoint.as_ref())?)
.send()
.await?
};
parse_response(res, false).await
}
pub async fn patch_json_endpoint<B, T, S, E>(
@@ -512,8 +545,29 @@ impl Client {
E: Display + DeserializeOwned,
S: AsRef<str>,
{
self.call_json_endpoint(reqwest::Method::PATCH, endpoint, Some(json_body))
#[cfg(target_arch = "wasm32")]
let res = {
wasmtimer::tokio::timeout(
self.request_timeout,
self.reqwest_client
.patch(self.base_url.join(endpoint.as_ref())?)
.json(json_body)
.send(),
)
.await
.map_err(|_timeout| HttpClientError::RequestTimeout)??
};
#[cfg(not(target_arch = "wasm32"))]
let res = {
self.reqwest_client
.patch(self.base_url.join(endpoint.as_ref())?)
.json(json_body)
.send()
.await?
};
parse_response(res, true).await
}
}
@@ -558,19 +612,6 @@ pub trait ApiClient {
V: AsRef<str> + Sync,
E: Display + DeserializeOwned;
async fn patch_json<B, T, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<T, HttpClientError<E>>
where
B: Serialize + ?Sized + Sync,
for<'a> T: Deserialize<'a>,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display + DeserializeOwned;
/// `get` json data from the provided absolute endpoint, i.e. for example `"/api/v1/mixnodes?since=12345"`
async fn get_json_from<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
where
@@ -594,17 +635,6 @@ pub trait ApiClient {
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str> + Sync + Send;
async fn patch_json_data_at<B, T, S, E>(
&self,
endpoint: S,
json_body: &B,
) -> Result<T, HttpClientError<E>>
where
B: Serialize + ?Sized + Sync,
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str> + Sync + Send;
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -654,22 +684,6 @@ impl ApiClient for Client {
self.delete_json(path, params).await
}
async fn patch_json<B, T, K, V, E>(
&self,
path: PathSegments<'_>,
params: Params<'_, K, V>,
json_body: &B,
) -> Result<T, HttpClientError<E>>
where
B: Serialize + ?Sized + Sync,
for<'a> T: Deserialize<'a>,
K: AsRef<str> + Sync,
V: AsRef<str> + Sync,
E: Display + DeserializeOwned,
{
self.patch_json(path, params, json_body).await
}
async fn get_json_from<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
where
for<'a> T: Deserialize<'a>,
@@ -701,20 +715,6 @@ impl ApiClient for Client {
{
self.delete_json_endpoint(endpoint).await
}
async fn patch_json_data_at<B, T, S, E>(
&self,
endpoint: S,
json_body: &B,
) -> Result<T, HttpClientError<E>>
where
B: Serialize + ?Sized + Sync,
for<'a> T: Deserialize<'a>,
E: Display + DeserializeOwned,
S: AsRef<str> + Sync + Send,
{
self.patch_json_endpoint(endpoint, json_body).await
}
}
// utility function that should solve the double slash problem in API urls forever.
@@ -63,7 +63,6 @@ impl From<v6::request::StaticConnectRequest> for v7::request::StaticConnectReque
}
}
#[allow(deprecated)]
impl From<v6::request::DynamicConnectRequest> for v7::request::DynamicConnectRequest {
fn from(dynamic_connect_request: v6::request::DynamicConnectRequest) -> Self {
Self {
@@ -51,7 +51,6 @@ impl IpPacketRequest {
)
}
#[allow(deprecated)]
pub fn new_dynamic_connect_request(
reply_to: Recipient,
reply_to_hops: Option<u8>,
@@ -286,9 +285,6 @@ pub struct DynamicConnectRequest {
// The number of mix node hops that responses should take, in addition to the entry and exit
// node. Zero means only client -> entry -> exit -> client.
#[deprecated(
note = "clients can no longer control number of hops to use. this field is scheduled for removal in V8"
)]
pub reply_to_hops: Option<u8>,
// The average delay at each mix node, in milliseconds. Currently this is not supported by the
-11
View File
@@ -57,14 +57,3 @@ pub mod wireguard {
pub const WG_TUN_DEVICE_IP_ADDRESS_V6: Ipv6Addr = Ipv6Addr::new(0xfc01, 0, 0, 0, 0, 0, 0, 0x1); // fc01::1
pub const WG_TUN_DEVICE_NETMASK_V6: u8 = 112;
}
pub mod mixnet_vpn {
use std::net::{Ipv4Addr, Ipv6Addr};
// The interface used to route traffic
pub const NYM_TUN_BASE_NAME: &str = "nymtun";
pub const NYM_TUN_DEVICE_ADDRESS_V4: Ipv4Addr = Ipv4Addr::new(10, 0, 0, 1);
pub const NYM_TUN_DEVICE_NETMASK_V4: Ipv4Addr = Ipv4Addr::new(255, 255, 0, 0);
pub const NYM_TUN_DEVICE_ADDRESS_V6: Ipv6Addr = Ipv6Addr::new(0xfc00, 0, 0, 0, 0, 0, 0, 0x1); // fc00::1
pub const NYM_TUN_DEVICE_NETMASK_V6: &str = "112";
}
+31 -33
View File
@@ -2,9 +2,10 @@
// SPDX-License-Identifier: Apache-2.0
use crate::error::NetworkTestingError;
use crate::node::{NodeType, TestableNode};
use crate::node::TestableNode;
use crate::NodeId;
use nym_sphinx::message::NymMessage;
use nym_topology::node::RoutingNode;
use nym_topology::{gateway, mix};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
@@ -25,76 +26,73 @@ pub struct TestMessage<T = Empty> {
}
impl<T> TestMessage<T> {
pub fn new(tested_node: TestableNode, 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 {
tested_node,
tested_node: node.into(),
msg_id,
total_msgs,
ext,
}
}
pub fn new_mix(node: &RoutingNode, msg_id: u32, total_msgs: u32, ext: T) -> Self {
Self::new(
TestableNode::new_routing(node, NodeType::Mixnode),
msg_id,
total_msgs,
ext,
)
pub fn new_mix(node: &mix::LegacyNode, msg_id: u32, total_msgs: u32, ext: T) -> Self {
Self::new(node, msg_id, total_msgs, ext)
}
pub fn new_gateway(node: &RoutingNode, msg_id: u32, total_msgs: u32, ext: T) -> Self {
Self::new(
TestableNode::new_routing(node, NodeType::Gateway),
msg_id,
total_msgs,
ext,
)
// pub fn new_gateway(node: &gateway::Node, msg_id: u32, total_msgs: u32, ext: T) -> Self {
// 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(
node: TestableNode,
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.clone(), msg_id, total_msgs, ext.clone()).as_bytes()?)
msgs.push(Self::new(node, msg_id, total_msgs, ext.clone()).as_bytes()?)
}
Ok(msgs)
}
pub fn mix_plaintexts(
node: &RoutingNode,
node: &mix::LegacyNode,
total_msgs: u32,
ext: T,
) -> Result<Vec<Vec<u8>>, NetworkTestingError>
where
T: Serialize + Clone,
{
Self::new_plaintexts(
TestableNode::new_routing(node, NodeType::Mixnode),
total_msgs,
ext,
)
Self::new_plaintexts(node, total_msgs, ext)
}
pub fn legacy_gateway_plaintexts(
node: &RoutingNode,
node: &gateway::LegacyNode,
node_id: NodeId,
total_msgs: u32,
ext: T,
) -> Result<Vec<Vec<u8>>, NetworkTestingError>
where
T: Serialize + Clone,
{
Self::new_plaintexts(
TestableNode::new_routing(node, NodeType::Gateway),
total_msgs,
ext,
)
Self::new_plaintexts(&(node, node_id), total_msgs, ext)
}
pub fn as_json_string(&self) -> Result<String, NetworkTestingError>
+33 -9
View File
@@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
use crate::NodeId;
use nym_topology::node::RoutingNode;
use nym_topology::{gateway, mix};
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
@@ -24,14 +24,6 @@ impl TestableNode {
}
}
pub fn new_routing(routing_node: &RoutingNode, typ: NodeType) -> Self {
TestableNode::new(
routing_node.identity_key.to_base58_string(),
typ,
routing_node.node_id,
)
}
pub fn new_mixnode(encoded_identity: String, node_id: NodeId) -> Self {
TestableNode::new(encoded_identity, NodeType::Mixnode, node_id)
}
@@ -45,6 +37,38 @@ impl TestableNode {
}
}
impl<'a> From<&'a mix::LegacyNode> for TestableNode {
fn from(value: &'a mix::LegacyNode) -> Self {
TestableNode {
encoded_identity: value.identity_key.to_base58_string(),
typ: NodeType::Mixnode,
node_id: value.mix_id,
}
}
}
impl<'a> From<(&'a gateway::LegacyNode, NodeId)> for TestableNode {
fn from((gateway, node_id): (&'a gateway::LegacyNode, NodeId)) -> Self {
(&(gateway, node_id)).into()
}
}
impl<'a> From<&'a (gateway::LegacyNode, NodeId)> for TestableNode {
fn from((gateway, node_id): &'a (gateway::LegacyNode, NodeId)) -> Self {
(gateway, *node_id).into()
}
}
impl<'a, 'b> From<&'a (&'b gateway::LegacyNode, NodeId)> for TestableNode {
fn from((gateway, node_id): &'a (&'b gateway::LegacyNode, NodeId)) -> Self {
TestableNode {
encoded_identity: gateway.identity_key.to_base58_string(),
typ: NodeType::Gateway,
node_id: *node_id,
}
}
}
impl Display for TestableNode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
+95 -41
View File
@@ -2,22 +2,21 @@
// SPDX-License-Identifier: Apache-2.0
use crate::error::NetworkTestingError;
use crate::Empty;
use crate::NodeId;
use crate::TestMessage;
use nym_sphinx::acknowledgements::AckKey;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::message::NymMessage;
use nym_sphinx::params::PacketSize;
use nym_sphinx::params::{PacketSize, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx::preparer::{FragmentPreparer, PreparedFragment};
use nym_sphinx_params::PacketType;
use nym_topology::node::RoutingNode;
use nym_topology::{NymRouteProvider, NymTopology, Role};
use nym_topology::{gateway, mix, NymTopology};
use rand::{CryptoRng, Rng};
use serde::Serialize;
use std::sync::Arc;
use std::time::Duration;
pub use nym_topology::node::LegacyMixLayer;
pub struct NodeTester<R> {
rng: R,
@@ -39,6 +38,10 @@ pub struct NodeTester<R> {
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
average_ack_delay: Duration,
/// Number of mix hops each packet ('real' message, ack, reply) is expected to take.
/// Note that it does not include gateway hops.
num_mix_hops: u8,
// while acks are going to be ignored they still need to be constructed
// so that the gateway would be able to correctly process and forward the message
ack_key: Arc<AckKey>,
@@ -67,27 +70,41 @@ where
deterministic_route_selection,
average_packet_delay,
average_ack_delay,
num_mix_hops: DEFAULT_NUM_MIX_HOPS,
ack_key,
}
}
pub fn testable_mix_topology(&self, layer: LegacyMixLayer, node: &RoutingNode) -> NymTopology {
/// Allows setting non-default number of expected mix hops in the network.
#[allow(dead_code)]
pub fn with_mix_hops(mut self, hops: u8) -> Self {
self.num_mix_hops = hops;
self
}
pub fn testable_mix_topology(&self, node: &mix::LegacyNode) -> NymTopology {
let mut topology = self.base_topology.clone();
topology.set_testable_node(layer.into(), node.clone());
topology.set_mixes_in_layer(node.layer as u8, vec![node.clone()]);
topology
}
pub fn testable_gateway_topology(&self, node: &RoutingNode) -> NymTopology {
pub fn testable_gateway_topology(&self, gateway: &gateway::LegacyNode) -> NymTopology {
let mut topology = self.base_topology.clone();
topology.set_testable_node(Role::EntryGateway, node.clone());
topology.set_testable_node(Role::ExitGateway, node.clone());
topology.set_gateways(vec![gateway.clone()]);
topology
}
pub fn simple_mixnode_test_packets(
&mut self,
mix: &mix::LegacyNode,
test_packets: u32,
) -> Result<Vec<PreparedFragment>, NetworkTestingError> {
self.mixnode_test_packets(mix, Empty, test_packets, None)
}
pub fn mixnode_test_packets<T>(
&mut self,
mix: &RoutingNode,
legacy_mix_layer: LegacyMixLayer,
mix: &mix::LegacyNode,
msg_ext: T,
test_packets: u32,
custom_recipient: Option<Recipient>,
@@ -95,9 +112,7 @@ where
where
T: Serialize + Clone,
{
let ephemeral_topology =
NymRouteProvider::from(self.testable_mix_topology(legacy_mix_layer, mix))
.with_ignore_egress_epoch_roles(true);
let ephemeral_topology = self.testable_mix_topology(mix);
let mut packets = Vec::with_capacity(test_packets as usize);
for plaintext in TestMessage::mix_plaintexts(mix, test_packets, msg_ext)? {
@@ -113,7 +128,7 @@ where
pub fn mixnodes_test_packets<T>(
&mut self,
nodes: &[(LegacyMixLayer, RoutingNode)],
nodes: &[mix::LegacyNode],
msg_ext: T,
test_packets: u32,
custom_recipient: Option<Recipient>,
@@ -122,10 +137,9 @@ where
T: Serialize + Clone,
{
let mut packets = Vec::new();
for (layer, node) in nodes {
for node in nodes {
packets.append(&mut self.mixnode_test_packets(
node,
*layer,
msg_ext.clone(),
test_packets,
custom_recipient,
@@ -135,10 +149,9 @@ where
Ok(packets)
}
pub fn existing_identity_mixnode_test_packets<T>(
pub fn existing_mixnode_test_packets<T>(
&mut self,
encoded_mix_identity: String,
layer: LegacyMixLayer,
mix_id: NodeId,
msg_ext: T,
test_packets: u32,
custom_recipient: Option<Recipient>,
@@ -146,30 +159,39 @@ where
where
T: Serialize + Clone,
{
let Ok(identity) = encoded_mix_identity.parse() else {
let Some(node) = self.base_topology.find_mix(mix_id) else {
return Err(NetworkTestingError::NonExistentMixnode { mix_id });
};
self.mixnode_test_packets(&node.clone(), msg_ext, test_packets, custom_recipient)
}
pub fn existing_identity_mixnode_test_packets<T>(
&mut self,
encoded_mix_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_mix_by_identity(&encoded_mix_identity)
else {
return Err(NetworkTestingError::NonExistentMixnodeIdentity {
mix_identity: encoded_mix_identity,
});
};
let Some(node) = self.base_topology.find_node_by_identity(identity) else {
return Err(NetworkTestingError::NonExistentMixnodeIdentity {
mix_identity: encoded_mix_identity,
});
};
self.mixnode_test_packets(
&node.clone(),
layer,
msg_ext,
test_packets,
custom_recipient,
)
self.mixnode_test_packets(&node.clone(), msg_ext, test_packets, custom_recipient)
}
pub fn legacy_gateway_test_packets<T>(
&mut self,
gateway: &RoutingNode,
gateway: &gateway::LegacyNode,
node_id: NodeId,
msg_ext: T,
test_packets: u32,
custom_recipient: Option<Recipient>,
@@ -177,11 +199,12 @@ where
where
T: Serialize + Clone,
{
let ephemeral_topology = NymRouteProvider::from(self.testable_gateway_topology(gateway))
.with_ignore_egress_epoch_roles(true);
let ephemeral_topology = self.testable_gateway_topology(gateway);
let mut packets = Vec::with_capacity(test_packets as usize);
for plaintext in TestMessage::legacy_gateway_plaintexts(gateway, test_packets, msg_ext)? {
for plaintext in
TestMessage::legacy_gateway_plaintexts(gateway, node_id, test_packets, msg_ext)?
{
packets.push(self.wrap_plaintext_data(
plaintext,
&ephemeral_topology,
@@ -192,10 +215,36 @@ where
Ok(packets)
}
pub fn existing_gateway_test_packets<T>(
&mut self,
node_id: NodeId,
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.legacy_gateway_test_packets(
&node.clone(),
node_id,
msg_ext,
test_packets,
custom_recipient,
)
}
pub fn wrap_plaintext_data(
&mut self,
plaintext: Vec<u8>,
topology: &NymRouteProvider,
topology: &NymTopology,
custom_recipient: Option<Recipient>,
) -> Result<PreparedFragment, NetworkTestingError> {
let message = NymMessage::new_plain(plaintext);
@@ -225,13 +274,14 @@ where
&address,
&address,
PacketType::Mix,
None,
)?)
}
pub fn create_test_packet<T>(
&mut self,
message: &TestMessage<T>,
topology: &NymRouteProvider,
topology: &NymTopology,
custom_recipient: Option<Recipient>,
) -> Result<PreparedFragment, NetworkTestingError>
where
@@ -257,6 +307,10 @@ impl<R: CryptoRng + Rng> FragmentPreparer for NodeTester<R> {
1
}
fn num_mix_hops(&self) -> u8 {
self.num_mix_hops
}
fn average_packet_delay(&self) -> Duration {
self.average_packet_delay
}
@@ -115,7 +115,10 @@ pub fn aggregate_signatures(
let params = ecash_group_parameters();
// aggregate the signature
let signature = Aggregatable::aggregate(signatures, indices)?;
let signature = match Aggregatable::aggregate(signatures, indices) {
Ok(res) => res,
Err(err) => return Err(err),
};
// Ensure the aggregated signature is not an infinity point
if bool::from(signature.is_at_infinity()) {
@@ -8,10 +8,10 @@ use nym_sphinx_addressing::nodes::{
NymNodeRoutingAddress, NymNodeRoutingAddressError, MAX_NODE_ADDRESS_UNPADDED_LEN,
};
use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::PacketType;
use nym_sphinx_params::{PacketType, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx_types::delays::Delay;
use nym_sphinx_types::{NymPacket, NymPacketError, MIN_PACKET_SIZE};
use nym_topology::{NymRouteProvider, NymTopologyError};
use nym_topology::{NymTopology, NymTopologyError};
use rand::{CryptoRng, RngCore};
use std::time;
@@ -43,13 +43,14 @@ impl SurbAck {
ack_key: &AckKey,
marshaled_fragment_id: [u8; 5],
average_delay: time::Duration,
topology: &NymRouteProvider,
topology: &NymTopology,
packet_type: PacketType,
) -> Result<Self, NymTopologyError>
where
R: RngCore + CryptoRng,
{
let route = topology.random_route_to_egress(rng, recipient.gateway())?;
let route =
topology.random_route_to_gateway(rng, DEFAULT_NUM_MIX_HOPS, recipient.gateway())?;
let delays = nym_sphinx_routing::generate_hop_delays(average_delay, route.len());
let destination = recipient.as_sphinx_destination();
+2 -2
View File
@@ -131,8 +131,8 @@ impl Recipient {
&self.client_encryption_key
}
pub fn gateway(&self) -> NodeIdentity {
self.gateway
pub fn gateway(&self) -> &NodeIdentity {
&self.gateway
}
pub fn to_bytes(self) -> RecipientBytes {
@@ -6,9 +6,9 @@ use nym_crypto::{generic_array::typenum::Unsigned, Digest};
use nym_sphinx_addressing::clients::Recipient;
use nym_sphinx_addressing::nodes::{NymNodeRoutingAddress, MAX_NODE_ADDRESS_UNPADDED_LEN};
use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm};
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx_types::{NymPacket, SURBMaterial, SphinxError, SURB};
use nym_topology::{NymRouteProvider, NymTopologyError};
use nym_topology::{NymTopology, NymTopologyError};
use rand::{CryptoRng, RngCore};
use serde::de::{Error as SerdeError, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
@@ -89,12 +89,13 @@ impl ReplySurb {
rng: &mut R,
recipient: &Recipient,
average_delay: time::Duration,
topology: &NymRouteProvider,
topology: &NymTopology,
) -> Result<Self, NymTopologyError>
where
R: RngCore + CryptoRng,
{
let route = topology.random_route_to_egress(rng, recipient.gateway())?;
let route =
topology.random_route_to_gateway(rng, DEFAULT_NUM_MIX_HOPS, recipient.gateway())?;
let delays = nym_sphinx_routing::generate_hop_delays(average_delay, route.len());
let destination = recipient.as_sphinx_destination();
@@ -109,12 +110,15 @@ impl ReplySurb {
/// Returns the expected number of bytes the [`ReplySURB`] will take after serialization.
/// Useful for deserialization from a bytes stream.
pub fn serialized_len() -> usize {
pub fn serialized_len(mix_hops: u8) -> usize {
use nym_sphinx_types::{HEADER_SIZE, NODE_ADDRESS_LENGTH, PAYLOAD_KEY_SIZE};
// the SURB itself consists of SURB_header, first hop address and set of payload keys
// for each hop (3x mix + egress)
SurbEncryptionKeySize::USIZE + HEADER_SIZE + NODE_ADDRESS_LENGTH + 4 * PAYLOAD_KEY_SIZE
// (note extra 1 for the gateway)
SurbEncryptionKeySize::USIZE
+ HEADER_SIZE
+ NODE_ADDRESS_LENGTH
+ (1 + mix_hops as usize) * PAYLOAD_KEY_SIZE
}
pub fn encryption_key(&self) -> &SurbEncryptionKey {
@@ -169,7 +169,10 @@ impl RepliableMessage {
.collect()
}
pub fn try_from_bytes(bytes: &[u8]) -> Result<Self, InvalidReplyRequestError> {
pub fn try_from_bytes(
bytes: &[u8],
num_mix_hops: u8,
) -> Result<Self, InvalidReplyRequestError> {
if bytes.len() < SENDER_TAG_SIZE + 1 {
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
}
@@ -177,8 +180,11 @@ impl RepliableMessage {
AnonymousSenderTag::from_bytes(bytes[..SENDER_TAG_SIZE].try_into().unwrap());
let content_tag = RepliableMessageContentTag::try_from(bytes[SENDER_TAG_SIZE])?;
let content =
RepliableMessageContent::try_from_bytes(&bytes[SENDER_TAG_SIZE + 1..], content_tag)?;
let content = RepliableMessageContent::try_from_bytes(
&bytes[SENDER_TAG_SIZE + 1..],
num_mix_hops,
content_tag,
)?;
Ok(RepliableMessage {
sender_tag,
@@ -186,20 +192,23 @@ impl RepliableMessage {
})
}
pub fn serialized_size(&self) -> usize {
pub fn serialized_size(&self, num_mix_hops: u8) -> usize {
let content_type_size = 1;
SENDER_TAG_SIZE + content_type_size + self.content.serialized_size()
SENDER_TAG_SIZE + content_type_size + self.content.serialized_size(num_mix_hops)
}
}
// this recovery code is shared between all variants containing reply surbs
fn recover_reply_surbs(bytes: &[u8]) -> Result<(Vec<ReplySurb>, usize), InvalidReplyRequestError> {
fn recover_reply_surbs(
bytes: &[u8],
num_mix_hops: u8,
) -> Result<(Vec<ReplySurb>, usize), InvalidReplyRequestError> {
let mut consumed = mem::size_of::<u32>();
if bytes.len() < consumed {
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
}
let num_surbs = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
let surb_size = ReplySurb::serialized_len();
let surb_size = ReplySurb::serialized_len(num_mix_hops);
if bytes[consumed..].len() < num_surbs as usize * surb_size {
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
}
@@ -298,13 +307,14 @@ impl RepliableMessageContent {
fn try_from_bytes(
bytes: &[u8],
num_mix_hops: u8,
tag: RepliableMessageContentTag,
) -> Result<Self, InvalidReplyRequestError> {
if bytes.is_empty() {
return Err(InvalidReplyRequestError::RequestTooShortToDeserialize);
}
let (reply_surbs, n) = recover_reply_surbs(bytes)?;
let (reply_surbs, n) = recover_reply_surbs(bytes, num_mix_hops)?;
match tag {
RepliableMessageContentTag::Data => Ok(RepliableMessageContent::Data {
@@ -330,7 +340,7 @@ impl RepliableMessageContent {
}
}
fn serialized_size(&self) -> usize {
fn serialized_size(&self, num_mix_hops: u8) -> usize {
match self {
RepliableMessageContent::Data {
message,
@@ -338,18 +348,19 @@ impl RepliableMessageContent {
} => {
let num_reply_surbs_tag = mem::size_of::<u32>();
num_reply_surbs_tag
+ reply_surbs.len() * ReplySurb::serialized_len()
+ reply_surbs.len() * ReplySurb::serialized_len(num_mix_hops)
+ message.len()
}
RepliableMessageContent::AdditionalSurbs { reply_surbs } => {
let num_reply_surbs_tag = mem::size_of::<u32>();
num_reply_surbs_tag + reply_surbs.len() * ReplySurb::serialized_len()
num_reply_surbs_tag + reply_surbs.len() * ReplySurb::serialized_len(num_mix_hops)
}
RepliableMessageContent::Heartbeat {
additional_reply_surbs,
} => {
let num_reply_surbs_tag = mem::size_of::<u32>();
num_reply_surbs_tag + additional_reply_surbs.len() * ReplySurb::serialized_len()
num_reply_surbs_tag
+ additional_reply_surbs.len() * ReplySurb::serialized_len(num_mix_hops)
}
}
}
@@ -567,11 +578,11 @@ mod tests {
}
}
pub(super) fn reply_surb(rng: &mut ChaCha20Rng) -> ReplySurb {
pub(super) fn reply_surb(rng: &mut ChaCha20Rng, num_mix_hops: u8) -> ReplySurb {
// due to gateway
const HOPS: u8 = 4;
let route = (0..HOPS).map(|_| node(rng)).collect();
let delays = (0..HOPS)
let num_hops = num_mix_hops + 1;
let route = (0..num_hops).map(|_| node(rng)).collect();
let delays = (0..num_hops)
.map(|_| Delay::new_from_nanos(rng.next_u64()))
.collect();
let mut destination_bytes = [0u8; 32];
@@ -594,40 +605,47 @@ mod tests {
}
}
pub(super) fn reply_surbs(rng: &mut ChaCha20Rng, n: usize) -> Vec<ReplySurb> {
pub(super) fn reply_surbs(
rng: &mut ChaCha20Rng,
num_mix_hops: u8,
n: usize,
) -> Vec<ReplySurb> {
let mut surbs = Vec::with_capacity(n);
for _ in 0..n {
surbs.push(reply_surb(rng))
surbs.push(reply_surb(rng, num_mix_hops))
}
surbs
}
pub(super) fn repliable_content_data(
rng: &mut ChaCha20Rng,
num_mix_hops: u8,
msg_len: usize,
surbs: usize,
) -> RepliableMessageContent {
RepliableMessageContent::Data {
message: random_vec_u8(rng, msg_len),
reply_surbs: reply_surbs(rng, surbs),
reply_surbs: reply_surbs(rng, num_mix_hops, surbs),
}
}
pub(super) fn repliable_content_surbs(
rng: &mut ChaCha20Rng,
num_mix_hops: u8,
surbs: usize,
) -> RepliableMessageContent {
RepliableMessageContent::AdditionalSurbs {
reply_surbs: reply_surbs(rng, surbs),
reply_surbs: reply_surbs(rng, num_mix_hops, surbs),
}
}
pub(super) fn repliable_content_heartbeat(
rng: &mut ChaCha20Rng,
num_mix_hops: u8,
surbs: usize,
) -> RepliableMessageContent {
RepliableMessageContent::Heartbeat {
additional_reply_surbs: reply_surbs(rng, surbs),
additional_reply_surbs: reply_surbs(rng, num_mix_hops, surbs),
}
}
@@ -658,54 +676,70 @@ mod tests {
#[test]
fn serialized_size_matches_actual_serialization() {
let mut rng = fixtures::test_rng();
let num_mix_hops = 3;
let data1 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_data(&mut rng, 10000, 0),
content: fixtures::repliable_content_data(&mut rng, num_mix_hops, 10000, 0),
};
assert_eq!(data1.serialized_size(), data1.into_bytes().len());
assert_eq!(
data1.serialized_size(num_mix_hops),
data1.into_bytes().len()
);
let data2 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_data(&mut rng, 10, 100),
content: fixtures::repliable_content_data(&mut rng, num_mix_hops, 10, 100),
};
assert_eq!(data2.serialized_size(), data2.into_bytes().len());
assert_eq!(
data2.serialized_size(num_mix_hops),
data2.into_bytes().len()
);
let data3 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_data(&mut rng, 100000, 1000),
content: fixtures::repliable_content_data(&mut rng, num_mix_hops, 100000, 1000),
};
assert_eq!(data3.serialized_size(), data3.into_bytes().len());
assert_eq!(
data3.serialized_size(num_mix_hops),
data3.into_bytes().len()
);
let additional_surbs1 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_surbs(&mut rng, 1),
content: fixtures::repliable_content_surbs(&mut rng, num_mix_hops, 1),
};
assert_eq!(
additional_surbs1.serialized_size(),
additional_surbs1.serialized_size(num_mix_hops),
additional_surbs1.into_bytes().len()
);
let additional_surbs2 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_surbs(&mut rng, 1000),
content: fixtures::repliable_content_surbs(&mut rng, num_mix_hops, 1000),
};
assert_eq!(
additional_surbs2.serialized_size(),
additional_surbs2.serialized_size(num_mix_hops),
additional_surbs2.into_bytes().len()
);
let heartbeat1 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_heartbeat(&mut rng, 1),
content: fixtures::repliable_content_heartbeat(&mut rng, num_mix_hops, 1),
};
assert_eq!(heartbeat1.serialized_size(), heartbeat1.into_bytes().len());
assert_eq!(
heartbeat1.serialized_size(num_mix_hops),
heartbeat1.into_bytes().len()
);
let heartbeat2 = RepliableMessage {
sender_tag: fixtures::sender_tag(&mut rng),
content: fixtures::repliable_content_heartbeat(&mut rng, 1000),
content: fixtures::repliable_content_heartbeat(&mut rng, num_mix_hops, 1000),
};
assert_eq!(heartbeat2.serialized_size(), heartbeat2.into_bytes().len());
assert_eq!(
heartbeat2.serialized_size(num_mix_hops),
heartbeat2.into_bytes().len()
);
}
}
@@ -716,33 +750,49 @@ mod tests {
#[test]
fn serialized_size_matches_actual_serialization() {
let mut rng = fixtures::test_rng();
let num_mix_hops = 3;
let data1 = fixtures::repliable_content_data(&mut rng, 10000, 0);
assert_eq!(data1.serialized_size(), data1.into_bytes().len());
let data2 = fixtures::repliable_content_data(&mut rng, 10, 100);
assert_eq!(data2.serialized_size(), data2.into_bytes().len());
let data3 = fixtures::repliable_content_data(&mut rng, 100000, 1000);
assert_eq!(data3.serialized_size(), data3.into_bytes().len());
let additional_surbs1 = fixtures::repliable_content_surbs(&mut rng, 1);
let data1 = fixtures::repliable_content_data(&mut rng, num_mix_hops, 10000, 0);
assert_eq!(
additional_surbs1.serialized_size(),
data1.serialized_size(num_mix_hops),
data1.into_bytes().len()
);
let data2 = fixtures::repliable_content_data(&mut rng, num_mix_hops, 10, 100);
assert_eq!(
data2.serialized_size(num_mix_hops),
data2.into_bytes().len()
);
let data3 = fixtures::repliable_content_data(&mut rng, num_mix_hops, 100000, 1000);
assert_eq!(
data3.serialized_size(num_mix_hops),
data3.into_bytes().len()
);
let additional_surbs1 = fixtures::repliable_content_surbs(&mut rng, num_mix_hops, 1);
assert_eq!(
additional_surbs1.serialized_size(num_mix_hops),
additional_surbs1.into_bytes().len()
);
let additional_surbs2 = fixtures::repliable_content_surbs(&mut rng, 1000);
let additional_surbs2 = fixtures::repliable_content_surbs(&mut rng, num_mix_hops, 1000);
assert_eq!(
additional_surbs2.serialized_size(),
additional_surbs2.serialized_size(num_mix_hops),
additional_surbs2.into_bytes().len()
);
let heartbeat1 = fixtures::repliable_content_heartbeat(&mut rng, 1);
assert_eq!(heartbeat1.serialized_size(), heartbeat1.into_bytes().len());
let heartbeat1 = fixtures::repliable_content_heartbeat(&mut rng, num_mix_hops, 1);
assert_eq!(
heartbeat1.serialized_size(num_mix_hops),
heartbeat1.into_bytes().len()
);
let heartbeat2 = fixtures::repliable_content_heartbeat(&mut rng, 1000);
assert_eq!(heartbeat2.serialized_size(), heartbeat2.into_bytes().len());
let heartbeat2 = fixtures::repliable_content_heartbeat(&mut rng, num_mix_hops, 1000);
assert_eq!(
heartbeat2.serialized_size(num_mix_hops),
heartbeat2.into_bytes().len()
);
}
}
+17 -6
View File
@@ -69,11 +69,11 @@ pub mod monitoring {
}
}
pub fn fragment_sent(fragment: &Fragment, client_nonce: i32, destination: PublicKey) {
pub fn fragment_sent(fragment: &Fragment, client_nonce: i32, destination: PublicKey, hops: u8) {
if enabled() {
let id = fragment.fragment_identifier().set_id();
let mut entry = FRAGMENTS_SENT.entry(id).or_default();
let s = SentFragment::new(fragment.header(), now!(), client_nonce, destination);
let s = SentFragment::new(fragment.header(), now!(), client_nonce, destination, hops);
entry.push(s);
}
}
@@ -82,11 +82,16 @@ pub mod monitoring {
#[derive(Debug, Clone)]
pub struct FragmentMixParams {
destination: PublicKey,
hops: u8,
}
impl FragmentMixParams {
pub fn destination(&self) -> PublicKey {
self.destination
pub fn destination(&self) -> &PublicKey {
&self.destination
}
pub fn hops(&self) -> u8 {
self.hops
}
}
@@ -100,8 +105,14 @@ pub struct SentFragment {
}
impl SentFragment {
fn new(header: FragmentHeader, at: u64, client_nonce: i32, destination: PublicKey) -> Self {
let mixnet_params = FragmentMixParams { destination };
fn new(
header: FragmentHeader,
at: u64,
client_nonce: i32,
destination: PublicKey,
hops: u8,
) -> Self {
let mixnet_params = FragmentMixParams { destination, hops };
SentFragment {
header,
at,
+8 -5
View File
@@ -10,9 +10,11 @@ use nym_sphinx_addressing::nodes::NymNodeRoutingAddress;
use nym_sphinx_chunking::fragment::COVER_FRAG_ID;
use nym_sphinx_forwarding::packet::MixPacket;
use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::{PacketEncryptionAlgorithm, PacketHkdfAlgorithm, PacketType};
use nym_sphinx_params::{
PacketEncryptionAlgorithm, PacketHkdfAlgorithm, PacketType, DEFAULT_NUM_MIX_HOPS,
};
use nym_sphinx_types::NymPacket;
use nym_topology::{NymRouteProvider, NymTopologyError};
use nym_topology::{NymTopology, NymTopologyError};
use rand::{CryptoRng, RngCore};
use std::time;
@@ -34,7 +36,7 @@ pub enum CoverMessageError {
pub fn generate_loop_cover_surb_ack<R>(
rng: &mut R,
topology: &NymRouteProvider,
topology: &NymTopology,
ack_key: &AckKey,
full_address: &Recipient,
average_ack_delay: time::Duration,
@@ -57,7 +59,7 @@ where
#[allow(clippy::too_many_arguments)]
pub fn generate_loop_cover_packet<R>(
rng: &mut R,
topology: &NymRouteProvider,
topology: &NymTopology,
ack_key: &AckKey,
full_address: &Recipient,
average_ack_delay: time::Duration,
@@ -116,7 +118,8 @@ where
.chain(cover_content)
.collect();
let route = topology.random_route_to_egress(rng, full_address.gateway())?;
let route =
topology.random_route_to_gateway(rng, DEFAULT_NUM_MIX_HOPS, full_address.gateway())?;
let delays = nym_sphinx_routing::generate_hop_delays(average_packet_delay, route.len());
let destination = full_address.as_sphinx_destination();
+4
View File
@@ -16,6 +16,10 @@ pub mod packet_sizes;
pub mod packet_types;
pub mod packet_version;
// If somebody can provide an argument why it might be reasonable to have more than 255 mix hops,
// I will change this to [`usize`]
pub const DEFAULT_NUM_MIX_HOPS: u8 = 3;
// TODO: not entirely sure how to feel about those being defined here, ideally it'd be where [`Fragment`]
// is defined, but that'd introduce circular dependencies as the acknowledgements crate also needs
// access to that
+33 -1
View File
@@ -1,10 +1,19 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_sphinx_types::{delays, Delay};
use std::time::Duration;
use nym_sphinx_addressing::clients::Recipient;
use nym_sphinx_types::{delays, Delay, 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 {
@@ -12,6 +21,29 @@ pub struct InvalidNumberOfHops {
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())
}
}
}
pub fn generate_hop_delays(average_packet_delay: Duration, num_hops: usize) -> Vec<Delay> {
if average_packet_delay.is_zero() {
vec![nym_sphinx_types::Delay::new_from_millis(0); num_hops]
+11 -11
View File
@@ -149,7 +149,7 @@ impl NymMessage {
.collect()
}
fn try_from_bytes(bytes: &[u8]) -> Result<Self, NymMessageError> {
fn try_from_bytes(bytes: &[u8], num_mix_hops: u8) -> Result<Self, NymMessageError> {
if bytes.is_empty() {
return Err(NymMessageError::EmptyMessage);
}
@@ -158,7 +158,7 @@ impl NymMessage {
match typ_tag {
NymMessageType::Plain => Ok(NymMessage::Plain(bytes[1..].to_vec())),
NymMessageType::Repliable => Ok(NymMessage::Repliable(
RepliableMessage::try_from_bytes(&bytes[1..])?,
RepliableMessage::try_from_bytes(&bytes[1..], num_mix_hops)?,
)),
NymMessageType::Reply => Ok(NymMessage::Reply(ReplyMessage::try_from_bytes(
&bytes[1..],
@@ -166,10 +166,10 @@ impl NymMessage {
}
}
fn serialized_size(&self) -> usize {
fn serialized_size(&self, num_mix_hops: u8) -> usize {
let inner_size = match self {
NymMessage::Plain(msg) => msg.len(),
NymMessage::Repliable(msg) => msg.serialized_size(),
NymMessage::Repliable(msg) => msg.serialized_size(num_mix_hops),
NymMessage::Reply(msg) => msg.serialized_size(),
};
let message_type_size = 1;
@@ -207,9 +207,9 @@ impl NymMessage {
}
/// Determines the number of required packets of the provided size for the split message.
pub fn required_packets(&self, packet_size: PacketSize) -> usize {
pub fn required_packets(&self, packet_size: PacketSize, num_mix_hops: u8) -> usize {
let plaintext_per_packet = self.true_available_plaintext_per_packet(packet_size);
let serialized_len = self.serialized_size();
let serialized_len = self.serialized_size(num_mix_hops);
let (num_fragments, _) =
chunking::number_of_required_fragments(serialized_len, plaintext_per_packet);
@@ -279,11 +279,11 @@ impl PaddedMessage {
}
// reverse of NymMessage::pad_to_full_packet_lengths
pub fn remove_padding(self) -> Result<NymMessage, NymMessageError> {
pub fn remove_padding(self, num_mix_hops: u8) -> Result<NymMessage, NymMessageError> {
// we are looking for first occurrence of 1 in the tail and we get its index
if let Some(padding_end) = self.0.iter().rposition(|b| *b == 1) {
// and now we only take bytes until that point (but not including it)
NymMessage::try_from_bytes(&self.0[..padding_end])
NymMessage::try_from_bytes(&self.0[..padding_end], num_mix_hops)
} else {
Err(NymMessageError::InvalidMessagePadding)
}
@@ -304,7 +304,7 @@ mod tests {
fn serialized_size_matches_actual_serialization() {
// plain
let plain = NymMessage::new_plain(vec![1, 2, 3, 4, 5]);
assert_eq!(plain.serialized_size(), plain.into_bytes().len());
assert_eq!(plain.serialized_size(3), plain.into_bytes().len());
// a single variant for each repliable and reply is enough as they are more thoroughly tested
// internally
@@ -313,9 +313,9 @@ mod tests {
[42u8; 16].into(),
vec![],
));
assert_eq!(repliable.serialized_size(), repliable.into_bytes().len());
assert_eq!(repliable.serialized_size(3), repliable.into_bytes().len());
let reply = NymMessage::new_reply(ReplyMessage::new_data_message(vec![1, 2, 3, 4, 5]));
assert_eq!(reply.serialized_size(), reply.into_bytes().len());
assert_eq!(reply.serialized_size(3), reply.into_bytes().len());
}
}
+37 -16
View File
@@ -14,9 +14,9 @@ use nym_sphinx_anonymous_replies::reply_surb::ReplySurb;
use nym_sphinx_chunking::fragment::{Fragment, FragmentIdentifier};
use nym_sphinx_forwarding::packet::MixPacket;
use nym_sphinx_params::packet_sizes::PacketSize;
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm};
use nym_sphinx_params::{PacketType, ReplySurbKeyDigestAlgorithm, DEFAULT_NUM_MIX_HOPS};
use nym_sphinx_types::{Delay, NymPacket};
use nym_topology::{NymRouteProvider, NymTopologyError};
use nym_topology::{NymTopology, NymTopologyError};
use rand::{CryptoRng, Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
@@ -54,13 +54,14 @@ pub trait FragmentPreparer {
fn deterministic_route_selection(&self) -> bool;
fn rng(&mut self) -> &mut Self::Rng;
fn nonce(&self) -> i32;
fn num_mix_hops(&self) -> u8;
fn average_packet_delay(&self) -> Duration;
fn average_ack_delay(&self) -> Duration;
fn generate_reply_surbs(
&mut self,
amount: usize,
topology: &NymRouteProvider,
topology: &NymTopology,
reply_recipient: &Recipient,
) -> Result<Vec<ReplySurb>, NymTopologyError> {
let mut reply_surbs = Vec::with_capacity(amount);
@@ -78,7 +79,7 @@ pub trait FragmentPreparer {
&mut self,
recipient: &Recipient,
fragment_id: FragmentIdentifier,
topology: &NymRouteProvider,
topology: &NymTopology,
ack_key: &AckKey,
packet_type: PacketType,
) -> Result<SurbAck, NymTopologyError> {
@@ -108,7 +109,7 @@ pub trait FragmentPreparer {
fn prepare_reply_chunk_for_sending(
&mut self,
fragment: Fragment,
topology: &NymRouteProvider,
topology: &NymTopology,
ack_key: &AckKey,
reply_surb: ReplySurb,
packet_sender: &Recipient,
@@ -129,8 +130,9 @@ pub trait FragmentPreparer {
.expect("the message has been incorrectly fragmented");
// this is not going to be accurate by any means. but that's the best estimation we can do
let expected_forward_delay =
Delay::new_from_millis((self.average_packet_delay().as_millis() * 3) as u64);
let expected_forward_delay = Delay::new_from_millis(
(self.average_packet_delay().as_millis() * self.num_mix_hops() as u128) as u64,
);
let fragment_identifier = fragment.fragment_identifier();
@@ -188,11 +190,12 @@ pub trait FragmentPreparer {
fn prepare_chunk_for_sending(
&mut self,
fragment: Fragment,
topology: &NymRouteProvider,
topology: &NymTopology,
ack_key: &AckKey,
packet_sender: &Recipient,
packet_recipient: &Recipient,
packet_type: PacketType,
mix_hops: Option<u8>,
) -> Result<PreparedFragment, NymTopologyError> {
debug!("Preparing chunk for sending");
// each plain or repliable packet (i.e. not a reply) attaches an ephemeral public key so that the recipient
@@ -201,7 +204,8 @@ pub trait FragmentPreparer {
let fragment_header = fragment.header();
let destination = packet_recipient.gateway();
monitoring::fragment_sent(&fragment, self.nonce(), destination);
let hops = mix_hops.unwrap_or(self.num_mix_hops());
monitoring::fragment_sent(&fragment, self.nonce(), *destination, hops);
let non_reply_overhead = encryption::PUBLIC_KEY_SIZE;
let expected_plaintext = match packet_type {
@@ -236,16 +240,16 @@ pub trait FragmentPreparer {
};
// generate pseudorandom route for the packet
log::trace!("Preparing chunk for sending");
log::trace!("Preparing chunk for sending with {hops} mix hops");
let route = if self.deterministic_route_selection() {
log::trace!("using deterministic route selection");
let seed = fragment_header.seed().wrapping_mul(self.nonce());
let mut rng = ChaCha8Rng::seed_from_u64(seed as u64);
topology.random_route_to_egress(&mut rng, destination)?
topology.random_route_to_gateway(&mut rng, hops, destination)?
} else {
log::trace!("using pseudorandom route selection");
let mut rng = self.rng();
topology.random_route_to_egress(&mut rng, destination)?
topology.random_route_to_gateway(&mut rng, hops, destination)?
};
let destination = packet_recipient.as_sphinx_destination();
@@ -331,6 +335,10 @@ pub struct MessagePreparer<R> {
/// Average delay an acknowledgement packet is going to get delay at a single mixnode.
average_ack_delay: Duration,
/// Number of mix hops each packet ('real' message, ack, reply) is expected to take.
/// Note that it does not include gateway hops.
num_mix_hops: u8,
nonce: i32,
}
@@ -353,10 +361,17 @@ where
sender_address,
average_packet_delay,
average_ack_delay,
num_mix_hops: DEFAULT_NUM_MIX_HOPS,
nonce,
}
}
/// Allows setting non-default number of expected mix hops in the network.
pub fn with_mix_hops(mut self, hops: u8) -> Self {
self.num_mix_hops = hops;
self
}
/// Overwrites existing sender address with the provided value.
pub fn set_sender_address(&mut self, sender_address: Recipient) {
self.sender_address = sender_address;
@@ -365,7 +380,7 @@ where
pub fn generate_reply_surbs(
&mut self,
amount: usize,
topology: &NymRouteProvider,
topology: &NymTopology,
) -> Result<Vec<ReplySurb>, NymTopologyError> {
let mut reply_surbs = Vec::with_capacity(amount);
for _ in 0..amount {
@@ -384,7 +399,7 @@ where
pub fn prepare_reply_chunk_for_sending(
&mut self,
fragment: Fragment,
topology: &NymRouteProvider,
topology: &NymTopology,
ack_key: &AckKey,
reply_surb: ReplySurb,
packet_type: PacketType,
@@ -405,10 +420,11 @@ where
pub fn prepare_chunk_for_sending(
&mut self,
fragment: Fragment,
topology: &NymRouteProvider,
topology: &NymTopology,
ack_key: &AckKey,
packet_recipient: &Recipient,
packet_type: PacketType,
mix_hops: Option<u8>,
) -> Result<PreparedFragment, NymTopologyError> {
let sender = self.sender_address;
@@ -420,6 +436,7 @@ where
&sender,
packet_recipient,
packet_type,
mix_hops,
)
}
@@ -427,7 +444,7 @@ where
pub fn generate_surb_ack(
&mut self,
fragment_id: FragmentIdentifier,
topology: &NymRouteProvider,
topology: &NymTopology,
ack_key: &AckKey,
packet_type: PacketType,
) -> Result<SurbAck, NymTopologyError> {
@@ -466,6 +483,10 @@ impl<R: CryptoRng + Rng> FragmentPreparer for MessagePreparer<R> {
self.nonce
}
fn num_mix_hops(&self) -> u8 {
self.num_mix_hops
}
fn average_packet_delay(&self) -> Duration {
self.average_packet_delay
}
+129 -2
View File
@@ -14,6 +14,7 @@ use nym_sphinx_chunking::reconstruction::MessageReconstructor;
use nym_sphinx_chunking::ChunkingError;
use nym_sphinx_params::{
PacketEncryptionAlgorithm, PacketHkdfAlgorithm, ReplySurbEncryptionAlgorithm,
DEFAULT_NUM_MIX_HOPS,
};
use thiserror::Error;
@@ -78,6 +79,7 @@ pub enum MessageRecoveryError {
pub trait MessageReceiver {
fn new() -> Self;
fn reconstructor(&mut self) -> &mut MessageReconstructor;
fn num_mix_hops(&self) -> u8;
fn decrypt_raw_message<C>(
&self,
@@ -141,7 +143,7 @@ pub trait MessageReceiver {
fragment: Fragment,
) -> Result<Option<(NymMessage, Vec<i32>)>, MessageRecoveryError> {
if let Some((message, used_sets)) = self.reconstructor().insert_new_fragment(fragment) {
match PaddedMessage::new_reconstructed(message).remove_padding() {
match PaddedMessage::new_reconstructed(message).remove_padding(self.num_mix_hops()) {
Ok(message) => Ok(Some((message, used_sets))),
Err(err) => Err(MessageRecoveryError::MalformedReconstructedMessage {
source: err,
@@ -154,11 +156,28 @@ pub trait MessageReceiver {
}
}
#[derive(Clone, Default)]
#[derive(Clone)]
pub struct SphinxMessageReceiver {
/// High level public structure used to buffer all received data [`Fragment`]s and eventually
/// returning original messages that they encapsulate.
reconstructor: MessageReconstructor,
/// Number of mix hops each packet ('real' message, ack, reply) is expected to take.
/// Note that it does not include gateway hops.
num_mix_hops: u8,
}
impl SphinxMessageReceiver {
/// Allows setting non-default number of expected mix hops in the network.
// IMPORTANT NOTE: this is among others used to deserialize SURBs. Meaning that this is a
// global setting and currently always set to the default value. The implication is that it is
// not currently possible to have different number of hops for different SURB messages. So,
// don't try to use <3 mix hops for SURBs until this is refactored.
#[must_use]
pub fn with_mix_hops(mut self, hops: u8) -> Self {
self.num_mix_hops = hops;
self
}
}
impl MessageReceiver for SphinxMessageReceiver {
@@ -182,4 +201,112 @@ impl MessageReceiver for SphinxMessageReceiver {
fn reconstructor(&mut self) -> &mut MessageReconstructor {
&mut self.reconstructor
}
fn num_mix_hops(&self) -> u8 {
self.num_mix_hops
}
}
impl Default for SphinxMessageReceiver {
fn default() -> Self {
SphinxMessageReceiver {
reconstructor: Default::default(),
num_mix_hops: DEFAULT_NUM_MIX_HOPS,
}
}
}
#[cfg(test)]
mod message_receiver {
use super::*;
use nym_crypto::asymmetric::identity;
use nym_mixnet_contract_common::LegacyMixLayer;
use nym_topology::{gateway, mix, NymTopology};
use std::collections::BTreeMap;
// TODO: is it somehow maybe possible to move it to `topology` and have if conditionally
// available to other modules?
/// Returns a hardcoded, valid instance of [`NymTopology`] that is to be used in
/// tests requiring instance of topology.
#[allow(dead_code)]
fn topology_fixture() -> NymTopology {
let mut mixes = BTreeMap::new();
mixes.insert(
1,
vec![mix::LegacyNode {
mix_id: 123,
host: "10.20.30.40".parse().unwrap(),
mix_host: "10.20.30.40:1789".parse().unwrap(),
identity_key: identity::PublicKey::from_base58_string(
"3ebjp1Fb9hdcS1AR6AZihgeJiMHkB5jjJUsvqNnfQwU7",
)
.unwrap(),
sphinx_key: encryption::PublicKey::from_base58_string(
"B3GzG62aXAZNg14RoMCp3BhELNBrySLr2JqrwyfYFzRc",
)
.unwrap(),
layer: LegacyMixLayer::One,
version: "0.8.0-dev".into(),
}],
);
mixes.insert(
2,
vec![mix::LegacyNode {
mix_id: 234,
host: "11.21.31.41".parse().unwrap(),
mix_host: "11.21.31.41:1789".parse().unwrap(),
identity_key: identity::PublicKey::from_base58_string(
"D6YaMzLSY7mANtSQRKXsmMZpqgqiVkeiagKM4V4oFPFr",
)
.unwrap(),
sphinx_key: encryption::PublicKey::from_base58_string(
"5Z1VqYwM2xeKxd8H7fJpGWasNiDFijYBAee7MErkZ5QT",
)
.unwrap(),
layer: LegacyMixLayer::Two,
version: "0.8.0-dev".into(),
}],
);
mixes.insert(
3,
vec![mix::LegacyNode {
mix_id: 456,
host: "12.22.32.42".parse().unwrap(),
mix_host: "12.22.32.42:1789".parse().unwrap(),
identity_key: identity::PublicKey::from_base58_string(
"GkWDysw4AjESv1KiAiVn7JzzCMJeksxNSXVfr1PpX8wD",
)
.unwrap(),
sphinx_key: encryption::PublicKey::from_base58_string(
"9EyjhCggr2QEA2nakR88YHmXgpy92DWxoe2draDRkYof",
)
.unwrap(),
layer: LegacyMixLayer::Three,
version: "0.8.0-dev".into(),
}],
);
NymTopology::new(
// currently coco_nodes don't really exist so this is still to be determined
mixes,
vec![gateway::LegacyNode {
node_id: 789,
host: "1.2.3.4".parse().unwrap(),
mix_host: "1.2.3.4:1789".parse().unwrap(),
clients_ws_port: 9000,
clients_wss_port: None,
identity_key: identity::PublicKey::from_base58_string(
"FioFa8nMmPpQnYi7JyojoTuwGLeyNS8BF4ChPr29zUML",
)
.unwrap(),
sphinx_key: encryption::PublicKey::from_base58_string(
"EB42xvMFMD5rUCstE2CDazgQQJ22zLv8SPm1Luxni44c",
)
.unwrap(),
version: "0.8.0-dev".into(),
}],
)
}
}
@@ -428,7 +428,7 @@ impl PacketStatisticsControl {
while self
.history
.front()
.is_some_and(|&(t, _)| t < recording_window)
.map_or(false, |&(t, _)| t < recording_window)
{
self.history.pop_front();
}
@@ -462,7 +462,7 @@ impl PacketStatisticsControl {
while self
.rates
.front()
.is_some_and(|&(t, _)| t < recording_window)
.map_or(false, |&(t, _)| t < recording_window)
{
self.rates.pop_front();
}
+7 -4
View File
@@ -12,13 +12,15 @@ documentation = { workspace = true }
[dependencies]
async-trait = { workspace = true, optional = true }
tracing = { workspace = true }
bs58 = { workspace = true }
log = { workspace = true }
rand = { workspace = true }
reqwest = { workspace = true, features = ["json"] }
serde = { workspace = true, features = ["derive"] }
semver = { workspace = true }
thiserror = { workspace = true }
# 'serde' feature
# 'serializable' feature
serde = { workspace = true, features = ["derive"], optional = true }
serde_json = { workspace = true, optional = true }
# 'wasm-serde-types' feature
@@ -26,6 +28,7 @@ tsify = { workspace = true, features = ["js"], optional = true }
wasm-bindgen = { workspace = true, optional = true }
## internal
nym-bin-common = { path = "../bin-common" }
nym-config = { path = "../config" }
nym-crypto = { path = "../crypto", features = ["sphinx", "outfox"] }
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
@@ -48,5 +51,5 @@ wasm-utils = { path = "../wasm/utils", default-features = false, optional = true
default = ["provider-trait"]
provider-trait = ["async-trait"]
wasm-serde-types = ["tsify", "wasm-bindgen", "wasm-utils"]
persistence = ["serde_json"]
serializable = ["serde", "serde_json"]
outfox = []
+8 -12
View File
@@ -4,28 +4,18 @@
use std::array::TryFromSliceError;
use crate::MixLayer;
use nym_sphinx_addressing::NodeIdentity;
use nym_sphinx_types::NymPacketError;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum NymTopologyError {
#[error("the provided network topology is empty - there are no valid nodes on it - the network request(s) probably failed")]
#[error("The provided network topology is empty - there are no mixnodes and no gateways on it - the network request(s) probably failed")]
EmptyNetworkTopology,
#[error("no node with identity {node_identity} is known")]
NonExistentNode { node_identity: Box<NodeIdentity> },
#[error("could not use node with identity {node_identity} as egress since it didn't get assigned valid role in the current epoch")]
InvalidEgressRole { node_identity: Box<NodeIdentity> },
#[error("one (or more) of mixing layers does not have any valid nodes available")]
InsufficientMixingNodes,
#[error("The provided network topology has no gateways available")]
NoGatewaysAvailable,
#[error("The provided network topology has no valid mixnodes available")]
#[error("The provided network topology has no mixnodes available")]
NoMixnodesAvailable,
#[error("Gateway with identity key {identity_key} doesn't exist")]
@@ -65,6 +55,12 @@ pub enum NymTopologyError {
#[error("{0}")]
ReqwestError(#[from] reqwest::Error),
#[error("{0}")]
MixnodeConversionError(#[from] crate::mix::MixnodeConversionError),
#[error("{0}")]
GatewayConversionError(#[from] crate::gateway::GatewayConversionError),
#[error("{0}")]
VarError(#[from] std::env::VarError),
}
+174
View File
@@ -0,0 +1,174 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{NetworkAddress, NodeVersion};
use nym_api_requests::nym_nodes::SkimmedNode;
use nym_crypto::asymmetric::{encryption, identity};
use nym_mixnet_contract_common::NodeId;
use nym_sphinx_addressing::nodes::{NodeIdentity, NymNodeRoutingAddress};
use nym_sphinx_types::Node as SphinxNode;
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::fmt;
use std::fmt::Formatter;
use std::io;
use std::net::AddrParseError;
use std::net::SocketAddr;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum GatewayConversionError {
#[error("gateway identity key was malformed - {0}")]
InvalidIdentityKey(#[from] identity::Ed25519RecoveryError),
#[error("gateway sphinx key was malformed - {0}")]
InvalidSphinxKey(#[from] encryption::KeyRecoveryError),
#[error("'{value}' is not a valid gateway address - {source}")]
InvalidAddress {
value: String,
#[source]
source: io::Error,
},
#[error("'{gateway}' has not provided any valid ip addresses")]
NoIpAddressesProvided { gateway: String },
#[error("'{gateway}' has provided a malformed ip address: {err}")]
MalformedIpAddress {
gateway: String,
#[source]
err: AddrParseError,
},
#[error("provided node is not an entry gateway in this epoch!")]
NotGateway,
}
#[derive(Clone)]
pub struct LegacyNode {
pub node_id: NodeId,
pub host: NetworkAddress,
// we're keeping this as separate resolved field since we do not want to be resolving the potential
// hostname every time we want to construct a path via this node
pub mix_host: SocketAddr,
// #[serde(alias = "clients_port")]
pub clients_ws_port: u16,
// #[serde(default)]
pub clients_wss_port: Option<u16>,
pub identity_key: identity::PublicKey,
pub sphinx_key: encryption::PublicKey, // TODO: or nymsphinx::PublicKey? both are x25519
// to be removed:
pub version: NodeVersion,
}
impl std::fmt::Debug for LegacyNode {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("gateway::Node")
.field("host", &self.host)
.field("mix_host", &self.mix_host)
.field("clients_ws_port", &self.clients_ws_port)
.field("clients_wss_port", &self.clients_wss_port)
.field("identity_key", &self.identity_key.to_base58_string())
.field("sphinx_key", &self.sphinx_key.to_base58_string())
.field("version", &self.version)
.finish()
}
}
impl LegacyNode {
pub fn parse_host(raw: &str) -> Result<NetworkAddress, GatewayConversionError> {
// safety: this conversion is infallible
// (but we retain result return type for legacy reasons)
Ok(raw.parse().unwrap())
}
pub fn extract_mix_host(
host: &NetworkAddress,
mix_port: u16,
) -> Result<SocketAddr, GatewayConversionError> {
Ok(host.to_socket_addrs(mix_port).map_err(|err| {
GatewayConversionError::InvalidAddress {
value: host.to_string(),
source: err,
}
})?[0])
}
pub fn identity(&self) -> &NodeIdentity {
&self.identity_key
}
pub fn clients_address(&self) -> String {
self.clients_address_tls()
.unwrap_or_else(|| self.clients_address_no_tls())
}
pub fn clients_address_no_tls(&self) -> String {
format!("ws://{}:{}", self.host, self.clients_ws_port)
}
pub fn clients_address_tls(&self) -> Option<String> {
self.clients_wss_port
.map(|p| format!("wss://{}:{p}", self.host))
}
}
impl fmt::Display for LegacyNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "legacy gateway {} @ {}", self.node_id, self.host)
}
}
impl<'a> From<&'a LegacyNode> for SphinxNode {
fn from(node: &'a LegacyNode) -> Self {
let node_address_bytes = NymNodeRoutingAddress::from(node.mix_host)
.try_into()
.unwrap();
SphinxNode::new(node_address_bytes, (&node.sphinx_key).into())
}
}
impl<'a> TryFrom<&'a SkimmedNode> for LegacyNode {
type Error = GatewayConversionError;
fn try_from(value: &'a SkimmedNode) -> Result<Self, Self::Error> {
let Some(entry_details) = &value.entry else {
return Err(GatewayConversionError::NotGateway);
};
if value.ip_addresses.is_empty() {
return Err(GatewayConversionError::NoIpAddressesProvided {
gateway: value.ed25519_identity_pubkey.to_base58_string(),
});
}
// safety: we just checked the slice is not empty
#[allow(clippy::unwrap_used)]
let ip = value.ip_addresses.choose(&mut thread_rng()).unwrap();
let host = if let Some(hostname) = &entry_details.hostname {
NetworkAddress::Hostname(hostname.to_string())
} else {
NetworkAddress::IpAddr(*ip)
};
Ok(LegacyNode {
node_id: value.node_id,
host,
mix_host: SocketAddr::new(*ip, value.mix_port),
clients_ws_port: entry_details.ws_port,
clients_wss_port: entry_details.wss_port,
identity_key: value.ed25519_identity_pubkey,
sphinx_key: value.x25519_sphinx_pubkey,
version: NodeVersion::Unknown,
})
}
}
+486 -448
View File
File diff suppressed because it is too large Load Diff
+135
View File
@@ -0,0 +1,135 @@
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use crate::{NetworkAddress, NodeVersion};
use nym_api_requests::nym_nodes::{NodeRole, SkimmedNode};
use nym_crypto::asymmetric::{encryption, identity};
pub use nym_mixnet_contract_common::LegacyMixLayer;
use nym_mixnet_contract_common::NodeId;
use nym_sphinx_addressing::nodes::NymNodeRoutingAddress;
use nym_sphinx_types::Node as SphinxNode;
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::fmt::Formatter;
use std::io;
use std::net::SocketAddr;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MixnodeConversionError {
#[error("mixnode identity key was malformed - {0}")]
InvalidIdentityKey(#[from] identity::Ed25519RecoveryError),
#[error("mixnode sphinx key was malformed - {0}")]
InvalidSphinxKey(#[from] encryption::KeyRecoveryError),
#[error("'{value}' is not a valid mixnode address - {source}")]
InvalidAddress {
value: String,
#[source]
source: io::Error,
},
#[error("invalid mix layer")]
InvalidLayer,
#[error("'{mixnode}' has not provided any valid ip addresses")]
NoIpAddressesProvided { mixnode: String },
#[error("provided node is not a mixnode in this epoch!")]
NotMixnode,
}
#[derive(Clone)]
pub struct LegacyNode {
pub mix_id: NodeId,
pub host: NetworkAddress,
// we're keeping this as separate resolved field since we do not want to be resolving the potential
// hostname every time we want to construct a path via this node
pub mix_host: SocketAddr,
pub identity_key: identity::PublicKey,
pub sphinx_key: encryption::PublicKey, // TODO: or nymsphinx::PublicKey? both are x25519
pub layer: LegacyMixLayer,
// to be removed:
pub version: NodeVersion,
}
impl std::fmt::Debug for LegacyNode {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("mix::Node")
.field("mix_id", &self.mix_id)
.field("host", &self.host)
.field("mix_host", &self.mix_host)
.field("identity_key", &self.identity_key.to_base58_string())
.field("sphinx_key", &self.sphinx_key.to_base58_string())
.field("layer", &self.layer)
.field("version", &self.version)
.finish()
}
}
impl LegacyNode {
pub fn parse_host(raw: &str) -> Result<NetworkAddress, MixnodeConversionError> {
// safety: this conversion is infallible
// (but we retain result return type for legacy reasons)
Ok(raw.parse().unwrap())
}
pub fn extract_mix_host(
host: &NetworkAddress,
mix_port: u16,
) -> Result<SocketAddr, MixnodeConversionError> {
Ok(host.to_socket_addrs(mix_port).map_err(|err| {
MixnodeConversionError::InvalidAddress {
value: host.to_string(),
source: err,
}
})?[0])
}
}
impl<'a> From<&'a LegacyNode> for SphinxNode {
fn from(node: &'a LegacyNode) -> Self {
let node_address_bytes = NymNodeRoutingAddress::from(node.mix_host)
.try_into()
.unwrap();
SphinxNode::new(node_address_bytes, (&node.sphinx_key).into())
}
}
impl<'a> TryFrom<&'a SkimmedNode> for LegacyNode {
type Error = MixnodeConversionError;
fn try_from(value: &'a SkimmedNode) -> Result<Self, Self::Error> {
if value.ip_addresses.is_empty() {
return Err(MixnodeConversionError::NoIpAddressesProvided {
mixnode: value.ed25519_identity_pubkey.to_base58_string(),
});
}
let layer = match value.role {
NodeRole::Mixnode { layer } => layer
.try_into()
.map_err(|_| MixnodeConversionError::InvalidLayer)?,
_ => return Err(MixnodeConversionError::NotMixnode),
};
// safety: we just checked the slice is not empty
#[allow(clippy::unwrap_used)]
let ip = value.ip_addresses.choose(&mut thread_rng()).unwrap();
let host = NetworkAddress::IpAddr(*ip);
Ok(LegacyNode {
mix_id: value.node_id,
host,
mix_host: SocketAddr::new(*ip, value.mix_port),
identity_key: value.ed25519_identity_pubkey,
sphinx_key: value.x25519_sphinx_pubkey,
layer,
version: NodeVersion::Unknown,
})
}
}
-143
View File
@@ -1,143 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_api_requests::models::DeclaredRoles;
use nym_api_requests::nym_nodes::SkimmedNode;
use nym_crypto::asymmetric::{ed25519, x25519};
use nym_mixnet_contract_common::NodeId;
use nym_sphinx_addressing::nodes::NymNodeRoutingAddress;
use nym_sphinx_types::Node as SphinxNode;
use serde::{Deserialize, Serialize};
use std::net::{IpAddr, SocketAddr};
use thiserror::Error;
pub use nym_mixnet_contract_common::LegacyMixLayer;
#[derive(Error, Debug)]
pub enum RoutingNodeError {
#[error("node {node_id} ('{identity}') has not provided any valid ip addresses")]
NoIpAddressesProvided { node_id: NodeId, identity: String },
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct EntryDetails {
// to allow client to choose ipv6 preference, if available
pub ip_addresses: Vec<IpAddr>,
pub clients_ws_port: u16,
pub hostname: Option<String>,
pub clients_wss_port: Option<u16>,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct SupportedRoles {
pub mixnode: bool,
pub mixnet_entry: bool,
pub mixnet_exit: bool,
}
impl From<DeclaredRoles> for SupportedRoles {
fn from(value: DeclaredRoles) -> Self {
SupportedRoles {
mixnode: value.mixnode,
mixnet_entry: value.entry,
mixnet_exit: value.exit_nr && value.exit_ipr,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RoutingNode {
pub node_id: NodeId,
pub mix_host: SocketAddr,
pub entry: Option<EntryDetails>,
pub identity_key: ed25519::PublicKey,
pub sphinx_key: x25519::PublicKey,
pub supported_roles: SupportedRoles,
}
impl RoutingNode {
pub fn ws_entry_address_tls(&self) -> Option<String> {
let entry = self.entry.as_ref()?;
let hostname = entry.hostname.as_ref()?;
let wss_port = entry.clients_wss_port?;
Some(format!("wss://{hostname}:{wss_port}"))
}
pub fn ws_entry_address_no_tls(&self, prefer_ipv6: bool) -> Option<String> {
let entry = self.entry.as_ref()?;
if let Some(hostname) = entry.hostname.as_ref() {
return Some(format!("ws://{hostname}:{}", entry.clients_ws_port));
}
if prefer_ipv6 {
if let Some(ipv6) = entry.ip_addresses.iter().find(|ip| ip.is_ipv6()) {
return Some(format!("ws://{ipv6}:{}", entry.clients_ws_port));
}
}
let any_ip = entry.ip_addresses.first()?;
Some(format!("ws://{any_ip}:{}", entry.clients_ws_port))
}
pub fn ws_entry_address(&self, prefer_ipv6: bool) -> Option<String> {
if let Some(tls) = self.ws_entry_address_tls() {
return Some(tls);
}
self.ws_entry_address_no_tls(prefer_ipv6)
}
pub fn identity(&self) -> ed25519::PublicKey {
self.identity_key
}
}
impl<'a> From<&'a RoutingNode> for SphinxNode {
fn from(node: &'a RoutingNode) -> Self {
// SAFETY: this conversion is infallible as all versions of socket addresses have
// sufficiently small bytes representation to fit inside `NodeAddressBytes`
#[allow(clippy::unwrap_used)]
let node_address_bytes = NymNodeRoutingAddress::from(node.mix_host)
.try_into()
.unwrap();
SphinxNode::new(node_address_bytes, (&node.sphinx_key).into())
}
}
impl<'a> TryFrom<&'a SkimmedNode> for RoutingNode {
type Error = RoutingNodeError;
fn try_from(value: &'a SkimmedNode) -> Result<Self, Self::Error> {
// IF YOU EVER ADD "performance" TO RoutingNode,
// MAKE SURE TO UPDATE THE LAZY IMPLEMENTATION OF
// `impl NodeDescriptionTopologyExt for NymNodeDescription`!!!
let Some(first_ip) = value.ip_addresses.first() else {
return Err(RoutingNodeError::NoIpAddressesProvided {
node_id: value.node_id,
identity: value.ed25519_identity_pubkey.to_string(),
});
};
let entry = value.entry.as_ref().map(|entry| EntryDetails {
ip_addresses: value.ip_addresses.clone(),
clients_ws_port: entry.ws_port,
hostname: entry.hostname.clone(),
clients_wss_port: entry.wss_port,
});
Ok(RoutingNode {
node_id: value.node_id,
mix_host: SocketAddr::new(*first_ip, value.mix_port),
entry,
identity_key: value.ed25519_identity_pubkey,
sphinx_key: value.x25519_sphinx_pubkey,
supported_roles: value.supported_roles.into(),
})
}
}
+1 -1
View File
@@ -22,7 +22,7 @@ pub struct HardcodedTopologyProvider {
}
impl HardcodedTopologyProvider {
#[cfg(feature = "persistence")]
#[cfg(feature = "serializable")]
pub fn new_from_file<P: AsRef<std::path::Path>>(path: P) -> std::io::Result<Self> {
NymTopology::new_from_file(path).map(Self::new)
}
@@ -0,0 +1,30 @@
// 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};
#[allow(dead_code)]
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())
}
}
-122
View File
@@ -1,122 +0,0 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_mixnet_contract_common::nym_node::Role;
use nym_mixnet_contract_common::{EpochId, EpochRewardedSet, NodeId, RewardedSet};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct CachedEpochRewardedSet {
pub epoch_id: EpochId,
pub entry_gateways: HashSet<NodeId>,
pub exit_gateways: HashSet<NodeId>,
pub layer1: HashSet<NodeId>,
pub layer2: HashSet<NodeId>,
pub layer3: HashSet<NodeId>,
pub standby: HashSet<NodeId>,
}
impl From<EpochRewardedSet> for CachedEpochRewardedSet {
fn from(value: EpochRewardedSet) -> Self {
CachedEpochRewardedSet {
epoch_id: value.epoch_id,
entry_gateways: value.assignment.entry_gateways.into_iter().collect(),
exit_gateways: value.assignment.exit_gateways.into_iter().collect(),
layer1: value.assignment.layer1.into_iter().collect(),
layer2: value.assignment.layer2.into_iter().collect(),
layer3: value.assignment.layer3.into_iter().collect(),
standby: value.assignment.standby.into_iter().collect(),
}
}
}
impl From<CachedEpochRewardedSet> for EpochRewardedSet {
fn from(value: CachedEpochRewardedSet) -> Self {
EpochRewardedSet {
epoch_id: value.epoch_id,
assignment: RewardedSet {
entry_gateways: value.entry_gateways.into_iter().collect(),
exit_gateways: value.exit_gateways.into_iter().collect(),
layer1: value.layer1.into_iter().collect(),
layer2: value.layer2.into_iter().collect(),
layer3: value.layer3.into_iter().collect(),
standby: value.standby.into_iter().collect(),
},
}
}
}
impl CachedEpochRewardedSet {
pub fn is_empty(&self) -> bool {
self.entry_gateways.is_empty()
&& self.exit_gateways.is_empty()
&& self.layer1.is_empty()
&& self.layer2.is_empty()
&& self.layer3.is_empty()
&& self.standby.is_empty()
}
pub fn role(&self, node_id: NodeId) -> Option<Role> {
if self.entry_gateways.contains(&node_id) {
Some(Role::EntryGateway)
} else if self.exit_gateways.contains(&node_id) {
Some(Role::ExitGateway)
} else if self.layer1.contains(&node_id) {
Some(Role::Layer1)
} else if self.layer2.contains(&node_id) {
Some(Role::Layer2)
} else if self.layer3.contains(&node_id) {
Some(Role::Layer3)
} else if self.standby.contains(&node_id) {
Some(Role::Standby)
} else {
None
}
}
pub fn legacy_mix_layer(&self, node_id: &NodeId) -> Option<u8> {
if self.layer1.contains(node_id) {
Some(1)
} else if self.layer2.contains(node_id) {
Some(2)
} else if self.layer3.contains(node_id) {
Some(3)
} else {
None
}
}
pub fn is_standby(&self, node_id: &NodeId) -> bool {
self.standby.contains(node_id)
}
pub fn is_active_mixnode(&self, node_id: &NodeId) -> bool {
self.layer1.contains(node_id)
|| self.layer2.contains(node_id)
|| self.layer3.contains(node_id)
}
pub fn gateways(&self) -> HashSet<NodeId> {
let mut gateways =
HashSet::with_capacity(self.entry_gateways.len() + self.exit_gateways.len());
gateways.extend(&self.entry_gateways);
gateways.extend(&self.exit_gateways);
gateways
}
pub fn active_mixnodes(&self) -> HashSet<NodeId> {
let mut mixnodes =
HashSet::with_capacity(self.layer1.len() + self.layer2.len() + self.layer3.len());
mixnodes.extend(&self.layer1);
mixnodes.extend(&self.layer2);
mixnodes.extend(&self.layer3);
mixnodes
}
}
+262
View File
@@ -0,0 +1,262 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
#![allow(unknown_lints)]
// clippy::empty_docs is not on stable as of 1.77
// due to the code generated by Tsify
#![allow(clippy::empty_docs)]
use crate::gateway::GatewayConversionError;
use crate::mix::MixnodeConversionError;
use crate::{gateway, mix, MixLayer, NymTopology};
use nym_config::defaults::{DEFAULT_CLIENT_LISTENING_PORT, DEFAULT_MIX_LISTENING_PORT};
use nym_crypto::asymmetric::{encryption, identity};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use std::net::{IpAddr, SocketAddr};
use thiserror::Error;
#[cfg(feature = "wasm-serde-types")]
use tsify::Tsify;
use nym_mixnet_contract_common::NodeId;
#[cfg(feature = "wasm-serde-types")]
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
#[cfg(feature = "wasm-serde-types")]
use wasm_utils::error::simple_js_error;
#[derive(Debug, Error)]
pub enum SerializableTopologyError {
#[error("got invalid mix layer {value}. Expected 1, 2 or 3.")]
InvalidMixLayer { value: u8 },
#[error(transparent)]
GatewayConversion(#[from] GatewayConversionError),
#[error(transparent)]
MixnodeConversion(#[from] MixnodeConversionError),
#[error("The provided mixnode map was malformed: {msg}")]
MalformedMixnodeMap { msg: String },
#[error("The provided gateway list was malformed: {msg}")]
MalformedGatewayList { msg: String },
}
#[cfg(feature = "wasm-serde-types")]
impl From<SerializableTopologyError> for JsValue {
fn from(value: SerializableTopologyError) -> Self {
simple_js_error(value.to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "wasm-serde-types", derive(Tsify))]
#[cfg_attr(feature = "wasm-serde-types", tsify(into_wasm_abi, from_wasm_abi))]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct SerializableNymTopology {
pub mixnodes: BTreeMap<MixLayer, Vec<SerializableMixNode>>,
pub gateways: Vec<SerializableGateway>,
}
impl TryFrom<SerializableNymTopology> for NymTopology {
type Error = SerializableTopologyError;
fn try_from(value: SerializableNymTopology) -> Result<Self, Self::Error> {
let mut converted_mixes = BTreeMap::new();
for (layer, nodes) in value.mixnodes {
let layer_nodes = nodes
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?;
converted_mixes.insert(layer, layer_nodes);
}
let gateways = value
.gateways
.into_iter()
.map(TryInto::try_into)
.collect::<Result<_, _>>()?;
Ok(NymTopology::new(converted_mixes, gateways))
}
}
impl From<NymTopology> for SerializableNymTopology {
fn from(value: NymTopology) -> Self {
SerializableNymTopology {
mixnodes: value
.mixes()
.iter()
.map(|(&l, nodes)| (l, nodes.iter().map(Into::into).collect()))
.collect(),
gateways: value.gateways().iter().map(Into::into).collect(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "wasm-serde-types", derive(Tsify))]
#[cfg_attr(feature = "wasm-serde-types", tsify(into_wasm_abi, from_wasm_abi))]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct SerializableMixNode {
// this is a `MixId` but due to typescript issue, we're using u32 directly.
#[serde(alias = "mix_id")]
pub mix_id: u32,
pub host: String,
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
#[serde(alias = "mix_port")]
pub mix_port: Option<u16>,
#[serde(alias = "identity_key")]
pub identity_key: String,
#[serde(alias = "sphinx_key")]
pub sphinx_key: String,
// this is a `MixLayer` but due to typescript issue, we're using u8 directly.
pub layer: u8,
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
pub version: Option<String>,
}
impl TryFrom<SerializableMixNode> for mix::LegacyNode {
type Error = SerializableTopologyError;
fn try_from(value: SerializableMixNode) -> Result<Self, Self::Error> {
let host = mix::LegacyNode::parse_host(&value.host)?;
let mix_port = value.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT);
let version = value.version.map(|v| v.as_str().into()).unwrap_or_default();
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = mix::LegacyNode::extract_mix_host(&host, mix_port)?;
Ok(mix::LegacyNode {
mix_id: value.mix_id,
host,
mix_host,
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
.map_err(MixnodeConversionError::from)?,
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
.map_err(MixnodeConversionError::from)?,
layer: mix::LegacyMixLayer::try_from(value.layer)
.map_err(|_| SerializableTopologyError::InvalidMixLayer { value: value.layer })?,
version,
})
}
}
impl<'a> From<&'a mix::LegacyNode> for SerializableMixNode {
fn from(value: &'a mix::LegacyNode) -> Self {
SerializableMixNode {
mix_id: value.mix_id,
host: value.host.to_string(),
mix_port: Some(value.mix_host.port()),
identity_key: value.identity_key.to_base58_string(),
sphinx_key: value.sphinx_key.to_base58_string(),
layer: value.layer.into(),
version: Some(value.version.to_string()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "wasm-serde-types", derive(Tsify))]
#[cfg_attr(feature = "wasm-serde-types", tsify(into_wasm_abi, from_wasm_abi))]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct SerializableGateway {
pub host: String,
pub node_id: NodeId,
// optional ip address in the case of host being a hostname that can't be resolved
// (thank you wasm)
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
#[serde(alias = "explicit_ip")]
pub explicit_ip: Option<IpAddr>,
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
#[serde(alias = "mix_port")]
pub mix_port: Option<u16>,
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
#[serde(alias = "clients_port")]
#[serde(alias = "clients_ws_port")]
pub clients_ws_port: Option<u16>,
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
#[serde(alias = "clients_wss_port")]
pub clients_wss_port: Option<u16>,
#[serde(alias = "identity_key")]
pub identity_key: String,
#[serde(alias = "sphinx_key")]
pub sphinx_key: String,
#[cfg_attr(feature = "wasm-serde-types", tsify(optional))]
pub version: Option<String>,
}
impl TryFrom<SerializableGateway> for gateway::LegacyNode {
type Error = SerializableTopologyError;
fn try_from(value: SerializableGateway) -> Result<Self, Self::Error> {
let host = gateway::LegacyNode::parse_host(&value.host)?;
let mix_port = value.mix_port.unwrap_or(DEFAULT_MIX_LISTENING_PORT);
let clients_ws_port = value
.clients_ws_port
.unwrap_or(DEFAULT_CLIENT_LISTENING_PORT);
let version = value.version.map(|v| v.as_str().into()).unwrap_or_default();
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = if let Some(explicit_ip) = value.explicit_ip {
SocketAddr::new(explicit_ip, mix_port)
} else {
gateway::LegacyNode::extract_mix_host(&host, mix_port)?
};
Ok(gateway::LegacyNode {
node_id: value.node_id,
host,
mix_host,
clients_ws_port,
clients_wss_port: value.clients_wss_port,
identity_key: identity::PublicKey::from_base58_string(&value.identity_key)
.map_err(GatewayConversionError::from)?,
sphinx_key: encryption::PublicKey::from_base58_string(&value.sphinx_key)
.map_err(GatewayConversionError::from)?,
version,
})
}
}
impl<'a> From<&'a gateway::LegacyNode> for SerializableGateway {
fn from(value: &'a gateway::LegacyNode) -> Self {
SerializableGateway {
host: value.host.to_string(),
node_id: value.node_id,
explicit_ip: Some(value.mix_host.ip()),
mix_port: Some(value.mix_host.port()),
clients_ws_port: Some(value.clients_ws_port),
clients_wss_port: value.clients_wss_port,
identity_key: value.identity_key.to_base58_string(),
sphinx_key: value.sphinx_key.to_base58_string(),
version: Some(value.version.to_string()),
}
}
}
-123
View File
@@ -1,123 +0,0 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
// due to the code generated by Tsify
#![allow(clippy::empty_docs)]
use crate::node::{EntryDetails, RoutingNode, RoutingNodeError, SupportedRoles};
use crate::{CachedEpochRewardedSet, NymTopology};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::net::SocketAddr;
use thiserror::Error;
use tsify::Tsify;
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
use wasm_utils::error::simple_js_error;
#[derive(Debug, Error)]
pub enum SerializableTopologyError {
#[error(transparent)]
NodeConversion(#[from] RoutingNodeError),
#[error("{provided} is not a valid ed25519 public key")]
MalformedIdentity { provided: String },
#[error("{provided} is not a valid x25519 public key")]
MalformedSphinxKey { provided: String },
}
#[cfg(feature = "wasm-serde-types")]
impl From<SerializableTopologyError> for JsValue {
fn from(value: SerializableTopologyError) -> Self {
simple_js_error(value.to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Tsify)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct WasmFriendlyNymTopology {
pub rewarded_set: CachedEpochRewardedSet,
pub node_details: HashMap<u32, WasmFriendlyRoutingNode>,
}
impl TryFrom<WasmFriendlyNymTopology> for NymTopology {
type Error = SerializableTopologyError;
fn try_from(value: WasmFriendlyNymTopology) -> Result<Self, Self::Error> {
let node_details = value
.node_details
.into_values()
.map(|details| details.try_into())
.collect::<Result<_, _>>()?;
Ok(NymTopology::new(value.rewarded_set, node_details))
}
}
impl From<NymTopology> for WasmFriendlyNymTopology {
fn from(value: NymTopology) -> Self {
WasmFriendlyNymTopology {
rewarded_set: value.rewarded_set,
node_details: value
.node_details
.into_iter()
.map(|(k, v)| (k, v.into()))
.collect(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Tsify)]
#[tsify(into_wasm_abi, from_wasm_abi)]
#[serde(rename_all = "camelCase")]
#[serde(deny_unknown_fields)]
pub struct WasmFriendlyRoutingNode {
pub node_id: u32,
pub mix_host: SocketAddr,
pub entry: Option<EntryDetails>,
pub identity_key: String,
pub sphinx_key: String,
pub supported_roles: SupportedRoles,
}
impl TryFrom<WasmFriendlyRoutingNode> for RoutingNode {
type Error = SerializableTopologyError;
fn try_from(value: WasmFriendlyRoutingNode) -> Result<Self, Self::Error> {
Ok(RoutingNode {
node_id: value.node_id,
mix_host: value.mix_host,
entry: value.entry,
identity_key: value.identity_key.as_str().parse().map_err(|_| {
SerializableTopologyError::MalformedIdentity {
provided: value.identity_key,
}
})?,
sphinx_key: value.sphinx_key.as_str().parse().map_err(|_| {
SerializableTopologyError::MalformedIdentity {
provided: value.sphinx_key,
}
})?,
supported_roles: value.supported_roles,
})
}
}
impl From<RoutingNode> for WasmFriendlyRoutingNode {
fn from(node: RoutingNode) -> Self {
WasmFriendlyRoutingNode {
node_id: node.node_id,
mix_host: node.mix_host,
entry: node.entry,
identity_key: node.identity_key.to_string(),
sphinx_key: node.sphinx_key.to_string(),
supported_roles: node.supported_roles,
}
}
}
+2 -2
View File
@@ -29,10 +29,10 @@ nym-credential-storage = { path = "../../credential-storage" }
nym-crypto = { path = "../../crypto", features = ["asymmetric", "serde"] }
nym-gateway-client = { path = "../../client-libs/gateway-client", default-features = false, features = ["wasm"] }
nym-sphinx = { path = "../../nymsphinx" }
nym-sphinx-acknowledgements = { path = "../../nymsphinx/acknowledgements", features = ["serde"] }
nym-sphinx-acknowledgements = { path = "../../nymsphinx/acknowledgements", features = ["serde"]}
nym-statistics-common = { path = "../../statistics" }
nym-task = { path = "../../task" }
nym-topology = { path = "../../topology", features = ["wasm-serde-types"] }
nym-topology = { path = "../../topology", features = ["serializable", "wasm-serde-types"] }
nym-validator-client = { path = "../../client-libs/validator-client", default-features = false }
wasm-utils = { path = "../utils" }
wasm-storage = { path = "../storage" }
-13
View File
@@ -387,15 +387,6 @@ pub struct TopologyWasm {
/// Specifies a minimum performance of a gateway that is used on route construction.
/// This setting is only applicable when `NymApi` topology is used.
pub minimum_gateway_performance: u8,
/// Specifies whether this client should attempt to retrieve all available network nodes
/// as opposed to just active mixnodes/gateways.
/// Useless without `ignore_epoch_roles = true`
pub use_extended_topology: bool,
/// Specifies whether this client should ignore the current epoch role of the target egress node
/// when constructing the final hop packets.
pub ignore_egress_epoch_role: bool,
}
impl Default for TopologyWasm {
@@ -418,8 +409,6 @@ impl From<TopologyWasm> for ConfigTopology {
topology_structure: Default::default(),
minimum_mixnode_performance: topology.minimum_mixnode_performance,
minimum_gateway_performance: topology.minimum_gateway_performance,
use_extended_topology: topology.use_extended_topology,
ignore_egress_epoch_role: topology.ignore_egress_epoch_role,
}
}
}
@@ -435,8 +424,6 @@ impl From<ConfigTopology> for TopologyWasm {
disable_refreshing: topology.disable_refreshing,
minimum_mixnode_performance: topology.minimum_mixnode_performance,
minimum_gateway_performance: topology.minimum_gateway_performance,
use_extended_topology: topology.use_extended_topology,
ignore_egress_epoch_role: topology.ignore_egress_epoch_role,
}
}
}
@@ -271,17 +271,6 @@ pub struct TopologyWasmOverride {
/// This setting is only applicable when `NymApi` topology is used.
#[tsify(optional)]
pub minimum_gateway_performance: Option<u8>,
/// Specifies whether this client should attempt to retrieve all available network nodes
/// as opposed to just active mixnodes/gateways.
/// Useless without `ignore_epoch_roles = true`
#[tsify(optional)]
pub use_extended_topology: Option<bool>,
/// Specifies whether this client should ignore the current epoch role of the target egress node
/// when constructing the final hop packets.
#[tsify(optional)]
pub ignore_egress_epoch_role: Option<bool>,
}
impl From<TopologyWasmOverride> for TopologyWasm {
@@ -305,12 +294,6 @@ impl From<TopologyWasmOverride> for TopologyWasm {
minimum_gateway_performance: value
.minimum_gateway_performance
.unwrap_or(def.minimum_gateway_performance),
use_extended_topology: value
.use_extended_topology
.unwrap_or(def.use_extended_topology),
ignore_egress_epoch_role: value
.ignore_egress_epoch_role
.unwrap_or(def.ignore_egress_epoch_role),
}
}
}
+7 -13
View File
@@ -15,8 +15,7 @@ use nym_client_core::init::{
};
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
use nym_topology::wasm_helpers::WasmFriendlyNymTopology;
use nym_topology::{NymTopology, RoutingNode};
use nym_topology::{gateway, NymTopology, SerializableNymTopology};
use nym_validator_client::client::IdentityKey;
use nym_validator_client::NymApiClient;
use rand::thread_rng;
@@ -56,7 +55,7 @@ pub fn parse_sender_tag(tag: &str) -> Result<AnonymousSenderTag, WasmCoreError>
pub async fn current_network_topology_async(
nym_api_url: String,
) -> Result<WasmFriendlyNymTopology, WasmCoreError> {
) -> Result<SerializableNymTopology, WasmCoreError> {
let url: Url = match nym_api_url.parse() {
Ok(url) => url,
Err(source) => {
@@ -68,17 +67,12 @@ pub async fn current_network_topology_async(
};
let api_client = NymApiClient::new(url);
let rewarded_set = api_client.get_current_rewarded_set().await?;
let mixnodes = api_client
.get_all_basic_active_mixing_assigned_nodes()
.await?;
let gateways = api_client.get_all_basic_entry_assigned_nodes().await?;
let mut topology = NymTopology::new_empty(rewarded_set);
topology.add_skimmed_nodes(&mixnodes);
topology.add_skimmed_nodes(&gateways);
Ok(topology.into())
Ok(NymTopology::from_basic(&mixnodes, &gateways).into())
}
#[wasm_bindgen(js_name = "currentNetworkTopology")]
@@ -96,7 +90,7 @@ pub async fn setup_gateway_wasm(
client_store: &ClientStorage,
force_tls: bool,
chosen_gateway: Option<IdentityKey>,
gateways: Vec<RoutingNode>,
gateways: &[gateway::LegacyNode],
) -> Result<InitialisationResult, WasmCoreError> {
// TODO: so much optimization and extra features could be added here, but that's for the future
@@ -113,7 +107,7 @@ pub async fn setup_gateway_wasm(
GatewaySetup::New {
specification: selection_spec,
available_gateways: gateways,
available_gateways: gateways.to_vec(),
}
};
@@ -131,7 +125,7 @@ pub async fn setup_gateway_from_api(
) -> Result<InitialisationResult, WasmCoreError> {
let mut rng = thread_rng();
let gateways = current_gateways(&mut rng, nym_apis, None, minimum_performance).await?;
setup_gateway_wasm(client_store, force_tls, chosen_gateway, gateways).await
setup_gateway_wasm(client_store, force_tls, chosen_gateway, &gateways).await
}
pub async fn setup_from_topology(
@@ -140,6 +134,6 @@ pub async fn setup_from_topology(
topology: &NymTopology,
client_store: &ClientStorage,
) -> Result<InitialisationResult, WasmCoreError> {
let gateways = topology.entry_capable_nodes().cloned().collect::<Vec<_>>();
let gateways = topology.gateways();
setup_gateway_wasm(client_store, force_tls, explicit_gateway, gateways).await
}
+33 -8
View File
@@ -1,26 +1,51 @@
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use nym_topology::wasm_helpers::SerializableTopologyError;
use nym_topology::SerializableTopologyError;
use nym_validator_client::client::IdentityKeyRef;
use wasm_utils::console_log;
pub use nym_topology::wasm_helpers::{WasmFriendlyNymTopology, WasmFriendlyRoutingNode};
pub use nym_topology::{Role, RoutingNode};
pub use nym_topology::{
gateway, mix, SerializableGateway, SerializableMixNode, SerializableNymTopology,
};
// redeclare this as a type alias for easy of use
pub type WasmTopologyError = SerializableTopologyError;
// helper trait to define extra functionality on the external type that we used to have here before
pub trait SerializableTopologyExt {
// fn print(&self);
fn print(&self);
fn ensure_contains_gateway_id(&self, gateway_id: IdentityKeyRef) -> bool;
}
impl SerializableTopologyExt for WasmFriendlyNymTopology {
impl SerializableTopologyExt for SerializableNymTopology {
fn print(&self) {
if !self.mixnodes.is_empty() {
console_log!("mixnodes:");
for (layer, nodes) in &self.mixnodes {
console_log!("\tlayer {layer}:");
for node in nodes {
// console_log!("\t\t{} - {}", node.mix_id, node.identity_key)
console_log!("\t\t{} - {:?}", node.mix_id, node)
}
}
} else {
console_log!("NO MIXNODES")
}
if !self.gateways.is_empty() {
console_log!("gateways:");
for gateway in &self.gateways {
// console_log!("\t{}", gateway.identity_key)
console_log!("\t{:?}", gateway)
}
} else {
console_log!("NO GATEWAYS")
}
}
fn ensure_contains_gateway_id(&self, gateway_id: IdentityKeyRef) -> bool {
self.node_details
.values()
.any(|node| node.identity_key == gateway_id)
self.gateways.iter().any(|g| g.identity_key == gateway_id)
}
}
-1
View File
@@ -36,4 +36,3 @@ nym-gateway-storage = { path = "../gateway-storage" }
nym-network-defaults = { path = "../network-defaults" }
nym-task = { path = "../task" }
nym-wireguard-types = { path = "../wireguard-types" }
nym-node-metrics = { path = "../../nym-node/nym-node-metrics" }
-2
View File
@@ -85,7 +85,6 @@ pub struct WireguardData {
#[cfg(target_os = "linux")]
pub async fn start_wireguard(
storage: nym_gateway_storage::GatewayStorage,
metrics: nym_node_metrics::NymNodeMetrics,
all_peers: Vec<nym_gateway_storage::models::WireguardPeer>,
task_client: nym_task::TaskClient,
wireguard_data: WireguardData,
@@ -176,7 +175,6 @@ pub async fn start_wireguard(
let wg_api = std::sync::Arc::new(WgApiWrapper::new(wg_api));
let mut controller = PeerController::new(
storage,
metrics,
wg_api.clone(),
host,
peer_bandwidth_managers,
-52
View File
@@ -16,9 +16,7 @@ use nym_credential_verification::{
ClientBandwidth,
};
use nym_gateway_storage::GatewayStorage;
use nym_node_metrics::NymNodeMetrics;
use nym_wireguard_types::DEFAULT_PEER_TIMEOUT_CHECK;
use std::time::{Duration, SystemTime};
use std::{collections::HashMap, sync::Arc};
use tokio::sync::{mpsc, RwLock};
use tokio_stream::{wrappers::IntervalStream, StreamExt};
@@ -67,11 +65,6 @@ pub struct QueryBandwidthControlResponse {
pub struct PeerController {
storage: GatewayStorage,
// we have "all" metrics of a node, but they're behind a single Arc pointer,
// so the overhead is minimal
metrics: NymNodeMetrics,
// used to receive commands from individual handles too
request_tx: mpsc::Sender<PeerControlRequest>,
request_rx: mpsc::Receiver<PeerControlRequest>,
@@ -83,10 +76,8 @@ pub struct PeerController {
}
impl PeerController {
#[allow(clippy::too_many_arguments)]
pub fn new(
storage: GatewayStorage,
metrics: NymNodeMetrics,
wg_api: Arc<WgApiWrapper>,
initial_host_information: Host,
bw_storage_managers: HashMap<Key, (Option<SharedBandwidthStorageManager>, Peer)>,
@@ -132,7 +123,6 @@ impl PeerController {
request_rx,
timeout_check_interval,
task_client,
metrics,
}
}
@@ -267,46 +257,6 @@ impl PeerController {
}))
}
fn update_metrics(&self, new_host: &Host) {
let now = SystemTime::now();
const ACTIVITY_THRESHOLD: Duration = Duration::from_secs(60);
let total_peers = new_host.peers.len();
let mut active_peers = 0;
let mut total_rx = 0;
let mut total_tx = 0;
for peer in new_host.peers.values() {
total_rx += peer.rx_bytes;
total_tx += peer.tx_bytes;
// if a peer hasn't performed a handshake in last minute,
// I think it's reasonable to assume it's no longer active
let Some(last_handshake) = peer.last_handshake else {
continue;
};
let Ok(elapsed) = now.duration_since(last_handshake) else {
continue;
};
if elapsed < ACTIVITY_THRESHOLD {
active_peers += 1;
}
}
self.metrics.wireguard.update(
// if the conversion fails it means we're running not running on a 64bit system
// and that's a reason enough for this failure.
total_rx.try_into().expect(
"failed to convert bytes from u64 to usize - are you running on non 64bit system?",
),
total_tx.try_into().expect(
"failed to convert bytes from u64 to usize - are you running on non 64bit system?",
),
total_peers,
active_peers,
);
}
pub async fn run(&mut self) {
info!("started wireguard peer controller");
loop {
@@ -316,8 +266,6 @@ impl PeerController {
log::error!("Can't read wireguard kernel data");
continue;
};
self.update_metrics(&host);
*self.host_information.write().await = host;
}
_ = self.task_client.recv() => {
@@ -1,7 +1,7 @@
```sh
Binary Name: nym-api
Build Timestamp: 2024-12-18T10:26:40.905460408Z
Build Timestamp: 2024-12-10T11:37:07.839071360Z
Build Version: 1.1.46
Commit SHA: 62045d76b32265f6a5e6358ab7aebbc827d47dcd
Commit Date: 2024-11-26T11:53:05.000000000+01:00
@@ -1,7 +1,7 @@
```sh
Binary Name: nym-client
Build Timestamp: 2024-12-18T10:26:40.905460408Z
Build Timestamp: 2024-12-10T11:37:07.839071360Z
Build Version: 1.1.44
Commit SHA: 62045d76b32265f6a5e6358ab7aebbc827d47dcd
Commit Date: 2024-11-26T11:53:05.000000000+01:00
@@ -1,7 +1,7 @@
```sh
Binary Name: nym-node
Build Timestamp: 2024-12-18T10:26:40.905460408Z
Build Timestamp: 2024-12-10T11:37:07.839071360Z
Build Version: 1.1.11
Commit SHA: 62045d76b32265f6a5e6358ab7aebbc827d47dcd
Commit Date: 2024-11-26T11:53:05.000000000+01:00
@@ -1,7 +1,7 @@
```sh
Binary Name: nym-socks5-client
Build Timestamp: 2024-12-18T10:26:40.905460408Z
Build Timestamp: 2024-12-10T11:37:07.839071360Z
Build Version: 1.1.44
Commit SHA: 62045d76b32265f6a5e6358ab7aebbc827d47dcd
Commit Date: 2024-11-26T11:53:05.000000000+01:00
@@ -1,7 +1,7 @@
```sh
Binary Name: nymvisor
Build Timestamp: 2024-12-18T10:26:40.905460408Z
Build Timestamp: 2024-12-10T11:37:07.839071360Z
Build Version: 0.1.9
Commit SHA: 62045d76b32265f6a5e6358ab7aebbc827d47dcd
Commit Date: 2024-11-26T11:53:05.000000000+01:00
@@ -152,7 +152,7 @@ Example output:
```sh
Binary Name: nym-socks5-client
Build Timestamp: 2024-12-18T10:26:40.905460408Z
Build Timestamp: 2024-12-10T11:37:07.839071360Z
Build Version: 1.1.44
Commit SHA: 62045d76b32265f6a5e6358ab7aebbc827d47dcd
Commit Date: 2024-11-26T11:53:05.000000000+01:00
@@ -117,7 +117,7 @@ Example output:
```sh
Binary Name: nym-client
Build Timestamp: 2024-12-18T10:26:40.905460408Z
Build Timestamp: 2024-12-10T11:37:07.839071360Z
Build Version: 1.1.44
Commit SHA: 62045d76b32265f6a5e6358ab7aebbc827d47dcd
Commit Date: 2024-11-26T11:53:05.000000000+01:00
@@ -39,25 +39,6 @@ This page displays a full list of all the changes during our release cycle from
<VarInfo />
## `v2024.14-crunch-patched`
Patch for `v2024.14-crunch` release. [Fixes an issue](https://github.com/nymtech/nym/commit/b656003306184061588f25df0b8b4555b41157f4) to allow only one private IP pair & compatibility issues between nym-nodes and older clients.
- [Release binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2024.14-crunch-patched)
- [`nym-node`](nodes/nym-node.mdx) version `1.2.1`
```sh
nym-node
Binary Name: nym-node
Build Timestamp: 2024-12-18T10:18:42.978852430Z
Build Version: 1.2.1
Commit SHA: 8d5a41a790e96ae5e821964865affaa7d3343eab
Commit Date: 2024-12-18T11:07:49.000000000+01:00
Commit Branch: HEAD
rustc Version: 1.83.0
rustc Channel: stable
cargo Profile: release
```
## `v2024.14-crunch`
- [Release binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2024.14-crunch)
@@ -82,7 +63,7 @@ cargo Profile: release
- [Bump elliptic from `6.5.4` to `6.5.7` in /testnet-faucet](https://github.com/nymtech/nym/pull/4768): Bumps [elliptic](https://github.com/indutny/elliptic) from `6.5.4` to `6.5.7`.
- [build(deps): bump micromatch from `4.0.4` to `4.0.8` in /nym-wallet/webdriver](https://github.com/nymtech/nym/pull/4789): Bumps [micromatch](https://github.com/micromatch/micromatch) from `4.0.4` to `4.0.8`.
- [build(deps): bump micromatch from `4.0.4` to `4.0.8` in /nym-wallet/webdriver](https://github.com/nymtech/nym/pull/4789): Bumps [micromatch](https://github.com/micromatch/micromatch) from `4.0.4` to `4.0.8`.
- [build(deps): bump axios from 1.6.0 to 1.7.5 in /nym-api/tests](https://github.com/nymtech/nym/pull/4790) Bumps [axios](https://github.com/axios/axios) from 1.6.0 to 1.7.5.
@@ -99,26 +80,26 @@ cargo Profile: release
| Package | From | To |
| --- | --- | --- |
| [anyhow](https://github.com/dtolnay/anyhow) | `1.0.89` | `1.0.90` |
| [clap](https://github.com/clap-rs/clap) | `4.5.18` | `4.5.20` |
| [clap_complete](https://github.com/clap-rs/clap) | `4.5.29` | `4.5.33` |
| [pin-project](https://github.com/taiki-e/pin-project) | `1.1.5` | `1.1.6` |
| [serde](https://github.com/serde-rs/serde) | `1.0.210` | `1.0.211` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.128` | `1.0.132` |
| [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) | `0.2.93` | `0.2.95` |
| [wasm-bindgen-futures](https://github.com/rustwasm/wasm-bindgen) | `0.4.43` | `0.4.45` |
| [web-sys](https://github.com/rustwasm/wasm-bindgen) | `0.3.70` | `0.3.72` |
| [clap](https://github.com/clap-rs/clap) | `4.5.18` | `4.5.20` |
| [clap_complete](https://github.com/clap-rs/clap) | `4.5.29` | `4.5.33` |
| [pin-project](https://github.com/taiki-e/pin-project) | `1.1.5` | `1.1.6` |
| [serde](https://github.com/serde-rs/serde) | `1.0.210` | `1.0.211` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.128` | `1.0.132` |
| [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen) | `0.2.93` | `0.2.95` |
| [wasm-bindgen-futures](https://github.com/rustwasm/wasm-bindgen) | `0.4.43` | `0.4.45` |
| [web-sys](https://github.com/rustwasm/wasm-bindgen) | `0.3.70` | `0.3.72` |
| Updates `anyhow` | `1.0.89` | `1.0.90` |
- [[Product Data] Introduce data persistence on gateways](https://github.com/nymtech/nym/pull/5022): This PR builds on top of [\#4974](https://github.com/nymtech/nym/pull/4974), not changing the behavior of the data collection, but persisting them in a sqlite database so they can be kept across restarts and crashes. It also leave the door open for other stats module to use that storage if needed. Here are some points of interest:
- New [`gateway_stats_storage`](https://github.com/nymtech/nym/tree/simon/gateway_stats_persistence/common/gateway-stats-storage) crate
- [Config migration](https://github.com/nymtech/nym/blob/simon/gateway_stats_persistence/nym-node/src/config/old_configs/old_config_v4.rs) resulting from the added database.
- Resulting changes in the [`statistics`](https://github.com/nymtech/nym/tree/simon/gateway_stats_persistence/gateway/src/node/statistics) module to account the new storage system
- Resulting changes in the [`statistics`](https://github.com/nymtech/nym/tree/simon/gateway_stats_persistence/gateway/src/node/statistics) module to account the new storage system
- [Integrate nym-credential-proxy into workspace](https://github.com/nymtech/nym/pull/5027): Integrate `nym-credential-proxy` into the main workspace
- [Authenticator CLI client mode](https://github.com/nymtech/nym/pull/5044)
- [Node Status API](https://github.com/nymtech/nym/pull/5050): merging a long-diverged feature branch - all commits here were their own merge requests
- [Node Status API](https://github.com/nymtech/nym/pull/5050): merging a long-diverged feature branch - all commits here were their own merge requests
- [IPv6 support for wireguard](https://github.com/nymtech/nym/pull/5059)
@@ -128,23 +109,23 @@ cargo Profile: release
- [chore: ecash contract migration to remove unused 'redemption_gateway_share'](https://github.com/nymtech/nym/pull/5104)
- [[Product Data] Client-side stats collection ](https://github.com/nymtech/nym/pull/5107): The goal is to anonymously gather stats from nym-clients. These stats will be sent through the mixnet to a Nym run service provider that will gather them. This PR sets the scene to send stats in a mixnet message to an address. The address can be set when the client is created. Current stats include some infos on sent packets along with platform information. If a receiving address is set, the client will send a mixnet packet every 5min to this address. Otherwise, nothing happens and the client runs as usual.
- [[Product Data] Client-side stats collection ](https://github.com/nymtech/nym/pull/5107): The goal is to anonymously gather stats from nym-clients. These stats will be sent through the mixnet to a Nym run service provider that will gather them. This PR sets the scene to send stats in a mixnet message to an address. The address can be set when the client is created. Current stats include some infos on sent packets along with platform information. If a receiving address is set, the client will send a mixnet packet every 5min to this address. Otherwise, nothing happens and the client runs as usual.
- [Send mixnet packet stats using task client](https://github.com/nymtech/nym/pull/5109)
- [Add granular log on nym-node](https://github.com/nymtech/nym/pull/5111) and make use of it for `defguard_wireguard_rs` big info logs
- [Rewarding for ticketbook issuance](https://github.com/nymtech/nym/pull/5112): Revamps the current validator rewarder to allow for rewards for issuing the zk-nym ticketbooks.
- [[Product Data] Add stats reporting configuration in client config ](https://github.com/nymtech/nym/pull/5115): Adds the stats reporting address to client configs. It can be set in the config file, as a CLI argument and as an env var in a `.env` file. As the stats reporting config in now in the `DebugConfig`, the `StatsReportingConfig` is no longer required, making the propagation of these changes more readable
- [Rewarding for ticketbook issuance](https://github.com/nymtech/nym/pull/5112): Revamps the current validator rewarder to allow for rewards for issuing the zk-nym ticketbooks.
- [[Product Data] Add stats reporting configuration in client config ](https://github.com/nymtech/nym/pull/5115): Adds the stats reporting address to client configs. It can be set in the config file, as a CLI argument and as an env var in a `.env` file. As the stats reporting config in now in the `DebugConfig`, the `StatsReportingConfig` is no longer required, making the propagation of these changes more readable
- [config score](https://github.com/nymtech/nym/pull/5117): introduces a concept of a `config_score` to a nym node which influences performance and thus rewarding amounts and chances of being in the rewarded set. Currently it's influenced by the following factors:
- Accepting terms and conditions (not accepted: 0)
- Exposing self-described API (not exposed: 0)
- Running "nym-node" binary (legacy binary: 0)
- Number of versions behind the core (`score = 0.995 ^ (X * versions_behind ^ 1.65)`)
- The old performance is now treated as `routing_score`
- the "new" performance = `routing_score * config_score`
- The old performance is now treated as `routing_score`
- the "new" performance = `routing_score * config_score`
- [Add Dockerfile and add env vars for clap arguments](https://github.com/nymtech/nym/pull/5118)
@@ -158,7 +139,7 @@ cargo Profile: release
- Added flag to Agent to generate keypairs
- Agent requests are signed by agent
- Server-side requests are checked for authentication
- [CI: reduce jobs running on cluster](https://github.com/nymtech/nym/pull/5132)
- [Removed ci-nym-api-tests.yml which was running outdated (and broken) tests](https://github.com/nymtech/nym/pull/5133)
@@ -200,7 +181,7 @@ cargo Profile: release
- Adds new endpoints to get:
- `nym-nodes` (list + by id)
- account balance + delegations + rewarding + vesting
- Explorer UI (NextJS)
- List of nym-nodes
- Remove service providers routes (Harbour Master shows these)
@@ -227,7 +208,7 @@ cargo Profile: release
- [`nym-api` NMv1 adjustments](https://github.com/nymtech/nym/pull/5209)
- [Nmv2 add debug config](https://github.com/nymtech/nym/pull/5212): Adds debug config to disable poisson process, cover traffic and min performance filtering
- [Nmv2 add debug config](https://github.com/nymtech/nym/pull/5212): Adds debug config to disable poisson process, cover traffic and min performance filtering
- [introduce UNSTABLE endpoints for returning network monitor run details](https://github.com/nymtech/nym/pull/5214)
@@ -245,14 +226,14 @@ cargo Profile: release
- [Correct IPv6 address generation](https://github.com/nymtech/nym/pull/5113)
- [bugfix: don't send empty BankMsg in ecash contract](https://github.com/nymtech/nym/pull/5121): If ticketbook prices were to be set so low the resultant redemption would have created `BankMsg` with value of 0, that message is no longer going to be sent
- [bugfix: don't send empty BankMsg in ecash contract](https://github.com/nymtech/nym/pull/5121): If ticketbook prices were to be set so low the resultant redemption would have created `BankMsg` with value of 0, that message is no longer going to be sent
- [fix: validator-rewarder GH job](https://github.com/nymtech/nym/pull/5151)
- [bugfix: correctly expose ecash-related data on nym-api](https://github.com/nymtech/nym/pull/5155): This PR makes fixes to ecash-related endpoints on `nym-api`
- [bugfix: correctly expose ecash-related data on nym-api](https://github.com/nymtech/nym/pull/5155): This PR makes fixes to ecash-related endpoints on `nym-api`
- global data (such as aggregated signatures and keys) are actually always available by all apis
- global data (such as aggregated signatures and keys) are actually always available by all apis
- [bugfix: use default value for verloc config when deserialising missing values](https://github.com/nymtech/nym/pull/5177)
- [bugfix: fixed nym-node config migrations (again)](https://github.com/nymtech/nym/pull/5179)
@@ -282,7 +263,7 @@ cargo Profile: release
- `nym-node` has now implemented [IPv6 support for wireguard](https://github.com/nymtech/nym/pull/5059)
- [`network_tunnel_manager.sh` updated](network): run the commands below to make sure
- [`network_tunnel_manager.sh` updated](network): run the commands below to make sure
<br />
@@ -306,7 +287,7 @@ $HOME/nym-binaries/network_tunnel_manager.sh configure_dns_and_icmp_wg ; \
$HOME/nym-binaries/network_tunnel_manager.sh adjust_ip_forwarding ; \
$HOME/nym-binaries/network_tunnel_manager.sh check_ipv6_ipv4_forwarding; \
systemctl daemon-reload && service nym-node restart && journalctl -u nym-node -f
systemctl daemon-reload && service nym-node restart && journalctl -u nym-node -f
```
Then run the jokes in a new window for control
@@ -326,7 +307,7 @@ $HOME/nym-binaries/network_tunnel_manager.sh joke_through_wg_tunnel
## `magura-drift`
Second patch to `v2024.13-magura` release version.
Second patch to `v2024.13-magura` release version.
- [Release binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2024.13-magura-drift)
- [`nym-node`](nodes/nym-node.mdx) version `1.1.12`
@@ -344,7 +325,7 @@ rustc Channel: stable
cargo Profile: release
```
- This patch adds a peer storage manager to fix issues causing external clients to be blocked, ensuring they can successfully connect to different nodes.
- This patch adds a peer storage manager to fix issues causing external clients to be blocked, ensuring they can successfully connect to different nodes.
## `v2024.13-magura-patched`
@@ -366,7 +347,7 @@ cargo Profile: release
<Callout type="warning" emoji="⚠️">
After changes coming along with `v2024.13-magura` (`nym-node v1.1.10`), Nym Explorer is no longer picking all values correctly. Instead of fixing this outdated explorer, we are working on a new one, coming out soon.
After changes coming along with `v2024.13-magura` (`nym-node v1.1.10`), Nym Explorer is no longer picking all values correctly. Instead of fixing this outdated explorer, we are working on a new one, coming out soon.
[Nym Harbourmaster](https://harbourmaster.nymtech.net) has cache of 90min, expect your values to be updated with delay. We are aware of some issues with Nym Harbourmaster and working hard to resolve them in the upcoming explorer v2. To check your routing values in real time, you can use [`nym-gateway-probe`](nodes/performance-and-testing/gateway-probe).
</Callout>
@@ -447,7 +428,7 @@ Be careful when running commands within sqlite database.
```sh
cd $HOME/.nym/nym-nodes/<ID>/data
```
- Enter the database:
- Enter the database:
```sh
sqlite3 clients.sqlite
```
@@ -467,7 +448,7 @@ select * from wireguard_peer where public_key like "%<WG_PEER_STRING_END>%";
###### 3. Exit and restart the service
Run `.quit` and:
Run `.quit` and:
```sh
systemctl restart nym-node.service
```
+3 -1
View File
@@ -120,7 +120,9 @@ impl ThreadsafeGatewayCache {
.read()
.await
.get(&identity_key)
.is_some_and(|cache_item| cache_item.valid_until > SystemTime::now())
.map_or(false, |cache_item| {
cache_item.valid_until > SystemTime::now()
})
}
pub(crate) async fn get_locations(&self) -> GatewayLocationCache {
+3 -1
View File
@@ -106,7 +106,9 @@ impl ThreadsafeMixNodesCache {
.read()
.await
.get(&mix_id)
.is_some_and(|cache_item| cache_item.valid_until > SystemTime::now())
.map_or(false, |cache_item| {
cache_item.valid_until > SystemTime::now()
})
}
pub(crate) async fn get_locations(&self) -> MixnodeLocationCache {
+3 -1
View File
@@ -60,7 +60,9 @@ impl ThreadSafeNymNodesCache {
.read()
.await
.get(&node_id)
.is_some_and(|cache_item| cache_item.valid_until > SystemTime::now())
.map_or(false, |cache_item| {
cache_item.valid_until > SystemTime::now()
})
}
pub(crate) async fn get_bonded_nymnodes(
-8
View File
@@ -37,7 +37,6 @@ mod internal_service_providers;
pub use client_handling::active_clients::ActiveClientsStore;
pub use nym_gateway_stats_storage::PersistentStatsStorage;
pub use nym_gateway_storage::{error::GatewayStorageError, GatewayStorage};
use nym_node_metrics::NymNodeMetrics;
pub use nym_sdk::{NymApiTopologyProvider, NymApiTopologyProviderConfig, UserAgent};
#[derive(Debug, Clone)]
@@ -82,8 +81,6 @@ pub struct GatewayTasksBuilder {
metrics_sender: MetricEventsSender,
metrics: NymNodeMetrics,
mnemonic: Arc<Zeroizing<bip39::Mnemonic>>,
shutdown: TaskClient,
@@ -105,14 +102,12 @@ impl Drop for GatewayTasksBuilder {
}
impl GatewayTasksBuilder {
#[allow(clippy::too_many_arguments)]
pub fn new(
config: Config,
identity: Arc<ed25519::KeyPair>,
storage: GatewayStorage,
mix_packet_sender: MixForwardingSender,
metrics_sender: MetricEventsSender,
metrics: NymNodeMetrics,
mnemonic: Arc<Zeroizing<bip39::Mnemonic>>,
shutdown: TaskClient,
) -> GatewayTasksBuilder {
@@ -126,7 +121,6 @@ impl GatewayTasksBuilder {
storage,
mix_packet_sender,
metrics_sender,
metrics,
mnemonic,
shutdown,
ecash_manager: None,
@@ -449,7 +443,6 @@ impl GatewayTasksBuilder {
pub async fn try_start_wireguard(
&mut self,
) -> Result<Arc<nym_wireguard::WgApiWrapper>, Box<dyn std::error::Error + Send + Sync>> {
let _ = self.metrics.clone();
unimplemented!("wireguard is not supported on this platform")
}
@@ -467,7 +460,6 @@ impl GatewayTasksBuilder {
let wg_handle = nym_wireguard::start_wireguard(
self.storage.clone(),
self.metrics.clone(),
all_peers,
self.shutdown.fork("wireguard"),
wireguard_data,
+1 -37
View File
@@ -20,9 +20,7 @@ use nym_crypto::asymmetric::x25519::{
use nym_mixnet_contract_common::nym_node::Role;
use nym_mixnet_contract_common::reward_params::{Performance, RewardingParams};
use nym_mixnet_contract_common::rewarding::RewardEstimate;
use nym_mixnet_contract_common::{
EpochId, GatewayBond, IdentityKey, Interval, MixNode, NodeId, Percent,
};
use nym_mixnet_contract_common::{GatewayBond, IdentityKey, Interval, MixNode, NodeId, Percent};
use nym_network_defaults::{DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_PORT};
use nym_node_requests::api::v1::authenticator::models::Authenticator;
use nym_node_requests::api::v1::gateway::models::Wireguard;
@@ -1344,10 +1342,6 @@ impl NodeRefreshBody {
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
pub struct RewardedSetResponse {
#[serde(default)]
#[schema(value_type = u32)]
pub epoch_id: EpochId,
pub entry_gateways: Vec<NodeId>,
pub exit_gateways: Vec<NodeId>,
@@ -1361,36 +1355,6 @@ pub struct RewardedSetResponse {
pub standby: Vec<NodeId>,
}
impl From<RewardedSetResponse> for nym_mixnet_contract_common::EpochRewardedSet {
fn from(res: RewardedSetResponse) -> Self {
nym_mixnet_contract_common::EpochRewardedSet {
epoch_id: res.epoch_id,
assignment: nym_mixnet_contract_common::RewardedSet {
entry_gateways: res.entry_gateways,
exit_gateways: res.exit_gateways,
layer1: res.layer1,
layer2: res.layer2,
layer3: res.layer3,
standby: res.standby,
},
}
}
}
impl From<nym_mixnet_contract_common::EpochRewardedSet> for RewardedSetResponse {
fn from(r: nym_mixnet_contract_common::EpochRewardedSet) -> Self {
RewardedSetResponse {
epoch_id: r.epoch_id,
entry_gateways: r.assignment.entry_gateways,
exit_gateways: r.assignment.exit_gateways,
layer1: r.assignment.layer1,
layer2: r.assignment.layer2,
layer3: r.assignment.layer3,
standby: r.assignment.standby,
}
}
}
pub use config_score::*;
pub mod config_score {
use nym_contracts_common::NaiveFloat;
+2 -2
View File
@@ -72,7 +72,7 @@ pub enum NodeRoleQueryParam {
ExitGateway,
}
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema, Default)]
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
pub enum NodeRole {
// a properly active mixnode
Mixnode {
@@ -88,7 +88,6 @@ pub enum NodeRole {
// equivalent of node that's in rewarded set but not in the inactive set
Standby,
#[default]
Inactive,
}
@@ -135,6 +134,7 @@ pub struct SkimmedNode {
#[schema(value_type = Vec<String>)]
pub ip_addresses: Vec<IpAddr>,
// TODO: to be deprecated in favour of well-known hardcoded port for everyone
pub mix_port: u16,
#[serde(with = "bs58_x25519_pubkey")]
+2 -3
View File
@@ -6,7 +6,7 @@ use crate::support::caching::Cache;
use cosmwasm_std::{Decimal, Fraction};
use nym_api_requests::models::NodeAnnotation;
use nym_mixnet_contract_common::reward_params::{NodeRewardingParameters, Performance, WorkFactor};
use nym_mixnet_contract_common::{EpochRewardedSet, ExecuteMsg, NodeId, RewardingParams};
use nym_mixnet_contract_common::{ExecuteMsg, NodeId, RewardedSet, RewardingParams};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tokio::sync::RwLockReadGuard;
@@ -93,12 +93,11 @@ impl EpochAdvancer {
pub(crate) async fn load_nodes_for_rewarding(
&self,
nodes: &EpochRewardedSet,
nodes: &RewardedSet,
// we only need reward parameters for active set work factor and rewarded/active set sizes;
// we do not need exact values of reward pool, staking supply, etc., so it's fine if it's slightly out of sync
global_rewarding_params: RewardingParams,
) -> Vec<RewardedNodeWithParams> {
let nodes = &nodes.assignment;
// currently we are using constant omega for nodes, but that will change with tickets
// or different reward split between entry, exit, etc. at that point this will have to be calculated elsewhere
let active_node_work_factor = global_rewarding_params.active_node_work();
+2 -2
View File
@@ -181,8 +181,8 @@ impl<R: MessageReceiver + Send + Sync> Monitor<R> {
}
fn blacklist_route_nodes(&self, route: &TestRoute, blacklist: &mut HashSet<NodeId>) {
for mix in route.topology().mixnodes() {
blacklist.insert(mix.node_id);
for mix in route.topology().mixes_as_vec() {
blacklist.insert(mix.mix_id);
}
blacklist.insert(route.gateway().node_id);
}
+114 -93
View File
@@ -5,26 +5,26 @@ use crate::network_monitor::monitor::sender::GatewayPackets;
use crate::network_monitor::test_route::TestRoute;
use crate::node_describe_cache::{DescribedNodes, NodeDescriptionTopologyExt};
use crate::node_status_api::NodeStatusCache;
use crate::nym_contract_cache::cache::NymContractCache;
use crate::nym_contract_cache::cache::{CachedRewardedSet, NymContractCache};
use crate::support::caching::cache::SharedCache;
use crate::support::legacy_helpers::legacy_host_to_ips_and_hostname;
use nym_api_requests::legacy::{LegacyGatewayBondWithId, LegacyMixNodeBondWithLayer};
use nym_api_requests::models::{NodeAnnotation, NymNodeDescription};
use nym_contracts_common::NaiveFloat;
use nym_crypto::asymmetric::{encryption, identity};
use nym_mixnet_contract_common::{LegacyMixLayer, NodeId};
use nym_node_tester_utils::node::{NodeType, TestableNode};
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_sphinx::params::{PacketSize, PacketType};
use nym_topology::node::{EntryDetails, RoutingNode, SupportedRoles};
use nym_topology::gateway::GatewayConversionError;
use nym_topology::mix::MixnodeConversionError;
use nym_topology::{gateway, mix};
use rand::prelude::SliceRandom;
use rand::{rngs::ThreadRng, thread_rng, Rng};
use std::collections::HashMap;
use std::fmt::{self, Display, Formatter};
use std::net::SocketAddr;
use std::sync::Arc;
use std::time::Duration;
use tracing::{debug, error, info, trace};
@@ -135,7 +135,7 @@ impl PacketPreparer {
// 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());
let self_address = self.create_packet_sender(test_route.gateway());
self.ephemeral_tester(test_route, Some(self_address))
}
@@ -210,87 +210,75 @@ impl PacketPreparer {
(mixnodes, gateways)
}
pub(crate) fn try_parse_legacy_mix_bond(
pub(crate) fn try_parse_mix_bond(
&self,
bond: &LegacyMixNodeBondWithLayer,
) -> Result<RoutingNode, String> {
fn parse_bond(bond: &LegacyMixNodeBondWithLayer) -> Option<RoutingNode> {
let (ips, _) = legacy_host_to_ips_and_hostname(&bond.mix_node.host)?;
) -> Result<mix::LegacyNode, String> {
fn parse_bond(
bond: &LegacyMixNodeBondWithLayer,
) -> Result<mix::LegacyNode, MixnodeConversionError> {
let host = mix::LegacyNode::parse_host(&bond.mix_node.host)?;
Some(RoutingNode {
node_id: bond.mix_id,
mix_host: SocketAddr::new(*ips.first()?, bond.mix_node.mix_port),
entry: None,
identity_key: identity::PublicKey::from_base58_string(&bond.mix_node.identity_key)
.ok()?,
sphinx_key: encryption::PublicKey::from_base58_string(&bond.mix_node.sphinx_key)
.ok()?,
supported_roles: SupportedRoles {
mixnode: true,
mixnet_entry: false,
mixnet_exit: false,
},
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = mix::LegacyNode::extract_mix_host(&host, bond.mix_node.mix_port)?;
Ok(mix::LegacyNode {
mix_id: bond.mix_id,
host,
mix_host,
identity_key: identity::PublicKey::from_base58_string(&bond.mix_node.identity_key)?,
sphinx_key: encryption::PublicKey::from_base58_string(&bond.mix_node.sphinx_key)?,
layer: bond.layer,
version: bond.mix_node.version.as_str().into(),
})
}
let identity = bond.mix_node.identity_key.clone();
parse_bond(bond).ok_or(identity)
parse_bond(bond).map_err(|_| identity)
}
pub(crate) fn try_parse_legacy_gateway_bond(
pub(crate) fn try_parse_gateway_bond(
&self,
gateway: &LegacyGatewayBondWithId,
) -> Result<RoutingNode, String> {
fn parse_bond(bond: &LegacyGatewayBondWithId) -> Option<RoutingNode> {
let (ips, hostname) = legacy_host_to_ips_and_hostname(&bond.gateway.host)?;
) -> Result<gateway::LegacyNode, String> {
fn parse_bond(
bond: &LegacyGatewayBondWithId,
) -> Result<gateway::LegacyNode, GatewayConversionError> {
let host = gateway::LegacyNode::parse_host(&bond.gateway.host)?;
Some(RoutingNode {
// try to completely resolve the host in the mix situation to avoid doing it every
// single time we want to construct a path
let mix_host = gateway::LegacyNode::extract_mix_host(&host, bond.gateway.mix_port)?;
Ok(gateway::LegacyNode {
node_id: bond.node_id,
mix_host: SocketAddr::new(*ips.first()?, bond.gateway.mix_port),
entry: Some(EntryDetails {
ip_addresses: ips,
clients_ws_port: bond.gateway.clients_port,
hostname,
clients_wss_port: None,
}),
identity_key: identity::PublicKey::from_base58_string(&bond.gateway.identity_key)
.ok()?,
sphinx_key: encryption::PublicKey::from_base58_string(&bond.gateway.sphinx_key)
.ok()?,
supported_roles: SupportedRoles {
mixnode: false,
mixnet_entry: true,
mixnet_exit: false,
},
host,
mix_host,
clients_ws_port: bond.gateway.clients_port,
clients_wss_port: None,
identity_key: identity::PublicKey::from_base58_string(&bond.gateway.identity_key)?,
sphinx_key: encryption::PublicKey::from_base58_string(&bond.gateway.sphinx_key)?,
version: bond.gateway.version.as_str().into(),
})
}
let identity = gateway.gateway.identity_key.clone();
parse_bond(gateway).ok_or(identity)
}
fn random_legacy_layer<R: Rng>(&self, rng: &mut R) -> LegacyMixLayer {
let layer_choices = [
LegacyMixLayer::One,
LegacyMixLayer::Two,
LegacyMixLayer::Three,
];
// SAFETY: the slice is not empty so the unwrap is fine
#[allow(clippy::unwrap_used)]
layer_choices.choose(rng).copied().unwrap()
parse_bond(gateway).map_err(|_| identity)
}
fn to_legacy_layered_mixes<'a, R: Rng>(
&self,
rng: &mut R,
rewarded_set: &CachedRewardedSet,
node_statuses: &HashMap<NodeId, NodeAnnotation>,
mixing_nym_nodes: impl Iterator<Item = &'a NymNodeDescription> + 'a,
) -> HashMap<LegacyMixLayer, Vec<(RoutingNode, f64)>> {
) -> HashMap<LegacyMixLayer, Vec<(mix::LegacyNode, f64)>> {
let mut layered_mixes = HashMap::new();
for mixing_nym_node in mixing_nym_nodes {
let Some(parsed_node) = self.nym_node_to_routing_node(mixing_nym_node) else {
let Some(parsed_node) = self.nym_node_to_legacy_mix(rng, rewarded_set, mixing_nym_node)
else {
continue;
};
// if the node is not present, default to 0.5
@@ -298,7 +286,7 @@ impl PacketPreparer {
.get(&mixing_nym_node.node_id)
.map(|node| node.last_24h_performance.naive_to_f64())
.unwrap_or(0.5);
let layer = self.random_legacy_layer(rng);
let layer = parsed_node.layer;
let layer_mixes = layered_mixes.entry(layer).or_insert_with(Vec::new);
layer_mixes.push((parsed_node, weight))
}
@@ -310,11 +298,11 @@ impl PacketPreparer {
&self,
node_statuses: &HashMap<NodeId, NodeAnnotation>,
gateway_capable_nym_nodes: impl Iterator<Item = &'a NymNodeDescription> + 'a,
) -> Vec<(RoutingNode, f64)> {
) -> Vec<(gateway::LegacyNode, f64)> {
let mut gateways = Vec::new();
for gateway_capable_node in gateway_capable_nym_nodes {
let Some(parsed_node) = self.nym_node_to_routing_node(gateway_capable_node) else {
let Some(parsed_node) = self.nym_node_to_legacy_gateway(gateway_capable_node) else {
continue;
};
// if the node is not present, default to 0.5
@@ -333,6 +321,8 @@ impl PacketPreparer {
// if generated fewer than n, blacklist will be updated by external function with correctly generated
// routes so that they wouldn't be reused
pub(crate) async fn prepare_test_routes(&self, n: usize) -> Option<Vec<TestRoute>> {
let rewarded_set = self.contract_cache.rewarded_set().await?;
let descriptions = self.described_cache.get().await.ok()?;
let statuses = self.node_status_cache.node_annotations().await?;
@@ -343,7 +333,8 @@ impl PacketPreparer {
let mut rng = thread_rng();
// separate mixes into layers for easier selection alongside the selection weights
let layered_mixes = self.to_legacy_layered_mixes(&mut rng, &statuses, mixing_nym_nodes);
let layered_mixes =
self.to_legacy_layered_mixes(&mut rng, &rewarded_set, &statuses, mixing_nym_nodes);
let gateways = self.to_legacy_gateway_nodes(&statuses, gateway_capable_nym_nodes);
// get all nodes from each layer...
@@ -403,7 +394,7 @@ impl PacketPreparer {
Some(routes)
}
fn create_packet_sender(&self, gateway: &RoutingNode) -> Recipient {
fn create_packet_sender(&self, gateway: &gateway::LegacyNode) -> Recipient {
Recipient::new(
self.self_public_identity,
self.self_public_encryption,
@@ -419,8 +410,7 @@ impl PacketPreparer {
_packet_type: PacketType,
) -> GatewayPackets {
let mut tester = self.ephemeral_mix_tester(route);
let topology = route.testable_route_provider();
let topology = route.topology();
let plaintexts = route.self_test_messages(num);
// the unwrap here is fine as:
@@ -429,7 +419,7 @@ impl PacketPreparer {
// 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(|p| tester.wrap_plaintext_data(p, topology, None).unwrap())
.map(MixPacket::from)
.collect();
@@ -443,11 +433,11 @@ impl PacketPreparer {
fn filter_outdated_and_malformed_mixnodes(
&self,
nodes: Vec<LegacyMixNodeBondWithLayer>,
) -> (Vec<RoutingNode>, Vec<InvalidNode>) {
) -> (Vec<mix::LegacyNode>, Vec<InvalidNode>) {
let mut parsed_nodes = Vec::new();
let mut invalid_nodes = Vec::new();
for mixnode in nodes {
if let Ok(parsed_node) = self.try_parse_legacy_mix_bond(&mixnode) {
if let Ok(parsed_node) = self.try_parse_mix_bond(&mixnode) {
parsed_nodes.push(parsed_node)
} else {
invalid_nodes.push(InvalidNode::Malformed {
@@ -461,12 +451,12 @@ impl PacketPreparer {
fn filter_outdated_and_malformed_gateways(
&self,
nodes: Vec<LegacyGatewayBondWithId>,
) -> (Vec<RoutingNode>, Vec<InvalidNode>) {
) -> (Vec<(gateway::LegacyNode, NodeId)>, Vec<InvalidNode>) {
let mut parsed_nodes = Vec::new();
let mut invalid_nodes = Vec::new();
for gateway in nodes {
if let Ok(parsed_node) = self.try_parse_legacy_gateway_bond(&gateway) {
parsed_nodes.push(parsed_node)
if let Ok(parsed_node) = self.try_parse_gateway_bond(&gateway) {
parsed_nodes.push((parsed_node, gateway.node_id))
} else {
invalid_nodes.push(InvalidNode::Malformed {
node: TestableNode::new_gateway(
@@ -479,8 +469,41 @@ impl PacketPreparer {
(parsed_nodes, invalid_nodes)
}
fn nym_node_to_routing_node(&self, description: &NymNodeDescription) -> Option<RoutingNode> {
description.try_to_topology_node().ok()
fn nym_node_to_legacy_mix<R: Rng>(
&self,
rng: &mut R,
rewarded_set: &CachedRewardedSet,
mixing_nym_node: &NymNodeDescription,
) -> Option<mix::LegacyNode> {
let maybe_explicit_layer = rewarded_set
.try_get_mix_layer(&mixing_nym_node.node_id)
.and_then(|layer| LegacyMixLayer::try_from(layer).ok());
let layer = match maybe_explicit_layer {
Some(layer) => layer,
None => {
let layer_choices = [
LegacyMixLayer::One,
LegacyMixLayer::Two,
LegacyMixLayer::Three,
];
// if nym-node doesn't have a layer assigned, since it's either standby or inactive,
// we have to choose one randomly for the testing purposes
// SAFETY: the slice is not empty so the unwrap is fine
#[allow(clippy::unwrap_used)]
layer_choices.choose(rng).copied().unwrap()
}
};
mixing_nym_node.try_to_topology_mix_node(layer).ok()
}
fn nym_node_to_legacy_gateway(
&self,
gateway_capable_node: &NymNodeDescription,
) -> Option<gateway::LegacyNode> {
gateway_capable_node.try_to_topology_gateway().ok()
}
pub(super) async fn prepare_test_packets(
@@ -491,6 +514,7 @@ impl PacketPreparer {
_packet_type: PacketType,
) -> PreparedPackets {
let (mixnodes, gateways) = self.all_legacy_mixnodes_and_gateways().await;
let rewarded_set = self.contract_cache.rewarded_set().await;
let descriptions = self
.described_cache
@@ -508,32 +532,28 @@ impl PacketPreparer {
// summary of nodes that got tested
let mut mixnodes_under_test = mixnodes_to_test_details
.iter()
.map(|node| TestableNode::new_routing(node, NodeType::Mixnode))
.map(|node| node.into())
.collect::<Vec<_>>();
let mut gateways_under_test = gateways_to_test_details
.iter()
.map(|node| TestableNode::new_routing(node, NodeType::Gateway))
.map(|node| node.into())
.collect::<Vec<_>>();
// try to add nym-nodes into the fold
for mix in mixing_nym_nodes {
if let Some(parsed) = self.nym_node_to_routing_node(mix) {
mixnodes_under_test.push(TestableNode::new_routing(&parsed, NodeType::Mixnode));
mixnodes_to_test_details.push(parsed);
if let Some(rewarded_set) = rewarded_set {
let mut rng = thread_rng();
for mix in mixing_nym_nodes {
if let Some(parsed) = self.nym_node_to_legacy_mix(&mut rng, &rewarded_set, mix) {
mixnodes_under_test.push(TestableNode::from(&parsed));
mixnodes_to_test_details.push(parsed);
}
}
}
// assign random layer to each node
let mut rng = thread_rng();
let mixnodes_to_test_details = mixnodes_to_test_details
.into_iter()
.map(|node| (self.random_legacy_layer(&mut rng), node))
.collect::<Vec<_>>();
for gateway in gateway_capable_nym_nodes {
if let Some(parsed) = self.nym_node_to_routing_node(gateway) {
gateways_under_test.push(TestableNode::new_routing(&parsed, NodeType::Gateway));
gateways_to_test_details.push(parsed);
if let Some(parsed) = self.nym_node_to_legacy_gateway(gateway) {
gateways_under_test.push((&parsed, gateway.node_id).into());
gateways_to_test_details.push((parsed, gateway.node_id));
}
}
@@ -574,10 +594,10 @@ impl PacketPreparer {
gateway_packets.push_packets(mix_packets);
// and generate test packets for gateways (note the variable recipient)
for gateway in &gateways_to_test_details {
for (gateway, node_id) in &gateways_to_test_details {
let recipient = self.create_packet_sender(gateway);
let gateway_identity = gateway.identity_key;
let gateway_address = gateway.ws_entry_address(false);
let gateway_address = gateway.clients_address();
// the unwrap here is fine as:
// 1. the topology is definitely valid (otherwise we wouldn't be here)
@@ -587,6 +607,7 @@ impl PacketPreparer {
let gateway_test_packets = mix_tester
.legacy_gateway_test_packets(
gateway,
*node_id,
route_ext,
self.per_node_test_packets as u32,
Some(recipient),
+12 -19
View File
@@ -35,7 +35,7 @@ const TIME_CHUNK_SIZE: Duration = Duration::from_millis(50);
pub(crate) struct GatewayPackets {
/// Network address of the target gateway if wanted to be accessed by the client.
/// It is a websocket address.
pub(crate) clients_address: Option<String>,
pub(crate) clients_address: String,
/// Public key of the target gateway.
pub(crate) pub_key: ed25519::PublicKey,
@@ -46,7 +46,7 @@ pub(crate) struct GatewayPackets {
impl GatewayPackets {
pub(crate) fn new(
clients_address: Option<String>,
clients_address: String,
pub_key: ed25519::PublicKey,
packets: Vec<MixPacket>,
) -> Self {
@@ -57,17 +57,15 @@ impl GatewayPackets {
}
}
pub(crate) fn gateway_config(&self) -> Option<GatewayConfig> {
self.clients_address
.clone()
.map(|gateway_listener| GatewayConfig {
gateway_identity: self.pub_key,
gateway_owner: None,
gateway_listener,
})
pub(crate) fn gateway_config(&self) -> GatewayConfig {
GatewayConfig {
gateway_identity: self.pub_key,
gateway_owner: None,
gateway_listener: self.clients_address.clone(),
}
}
pub(crate) fn empty(clients_address: Option<String>, pub_key: ed25519::PublicKey) -> Self {
pub(crate) fn empty(clients_address: String, pub_key: ed25519::PublicKey) -> Self {
GatewayPackets {
clients_address,
pub_key,
@@ -358,22 +356,17 @@ impl PacketSender {
fresh_gateway_client_data: Arc<FreshGatewayClientData>,
max_sending_rate: usize,
) -> Option<GatewayClientHandle> {
let identity = packets.pub_key;
let Some(gateway_config) = packets.gateway_config() else {
warn!("gateway {identity} didn't provide valid entry information");
return None;
};
let (mut client, gateway_channels) =
Self::create_new_gateway_client_handle_and_authenticate(
gateway_config,
packets.gateway_config(),
&fresh_gateway_client_data,
gateway_connection_timeout,
gateway_bandwidth_claim_timeout,
)
.await?;
let identity = client.gateway_identity();
let estimated_time =
Duration::from_secs_f64(packets.packets.len() as f64 / max_sending_rate as f64);
// give some leeway
+2 -2
View File
@@ -3,7 +3,7 @@
use nym_node_tester_utils::error::NetworkTestingError;
use nym_node_tester_utils::TestMessage;
use nym_topology::node::RoutingNode;
use nym_topology::mix;
use serde::{Deserialize, Serialize};
pub(crate) type NodeTestMessage = TestMessage<NymApiTestMessageExt>;
@@ -24,7 +24,7 @@ impl NymApiTestMessageExt {
pub fn mix_plaintexts(
&self,
node: &RoutingNode,
node: &mix::LegacyNode,
test_packets: u32,
) -> Result<Vec<Vec<u8>>, NetworkTestingError> {
NodeTestMessage::mix_plaintexts(node, test_packets, *self)
+23 -48
View File
@@ -4,10 +4,7 @@
use crate::network_monitor::test_packet::NymApiTestMessageExt;
use crate::network_monitor::ROUTE_TESTING_TEST_NONCE;
use nym_crypto::asymmetric::identity;
use nym_mixnet_contract_common::nym_node::Role;
use nym_mixnet_contract_common::{EpochId, EpochRewardedSet, RewardedSet};
use nym_topology::node::RoutingNode;
use nym_topology::{NymRouteProvider, NymTopology};
use nym_topology::{gateway, mix, NymTopology};
use std::fmt::{Debug, Formatter};
#[derive(Clone)]
@@ -19,28 +16,22 @@ pub(crate) struct TestRoute {
impl TestRoute {
pub(crate) fn new(
id: u64,
l1_mix: RoutingNode,
l2_mix: RoutingNode,
l3_mix: RoutingNode,
gateway: RoutingNode,
l1_mix: mix::LegacyNode,
l2_mix: mix::LegacyNode,
l3_mix: mix::LegacyNode,
gateway: gateway::LegacyNode,
) -> Self {
let fake_rewarded_set = EpochRewardedSet {
epoch_id: EpochId::MAX,
assignment: RewardedSet {
entry_gateways: vec![gateway.node_id],
exit_gateways: vec![],
layer1: vec![l1_mix.node_id],
layer2: vec![l2_mix.node_id],
layer3: vec![l3_mix.node_id],
standby: vec![],
},
};
let nodes = vec![l1_mix, l2_mix, l3_mix, gateway];
let layered_mixes = [
(1u8, vec![l1_mix]),
(2u8, vec![l2_mix]),
(3u8, vec![l3_mix]),
]
.into_iter()
.collect();
TestRoute {
id,
nodes: NymTopology::new(fake_rewarded_set, nodes),
nodes: NymTopology::new(layered_mixes, vec![gateway]),
}
}
@@ -48,36 +39,24 @@ impl TestRoute {
self.id
}
pub(crate) fn gateway(&self) -> RoutingNode {
// SAFETY: we inserted entry gateway at construction
#[allow(clippy::unwrap_used)]
self.nodes
.nodes_with_role(Role::EntryGateway)
.next()
.unwrap()
.clone()
pub(crate) fn gateway(&self) -> &gateway::LegacyNode {
&self.nodes.gateways()[0]
}
pub(crate) fn layer_one_mix(&self) -> &RoutingNode {
// SAFETY: we inserted layer1 node at construction
#[allow(clippy::unwrap_used)]
self.nodes.nodes_with_role(Role::Layer1).next().unwrap()
pub(crate) fn layer_one_mix(&self) -> &mix::LegacyNode {
&self.nodes.mixes().get(&1).unwrap()[0]
}
pub(crate) fn layer_two_mix(&self) -> &RoutingNode {
// SAFETY: we inserted layer2 node at construction
#[allow(clippy::unwrap_used)]
self.nodes.nodes_with_role(Role::Layer2).next().unwrap()
pub(crate) fn layer_two_mix(&self) -> &mix::LegacyNode {
&self.nodes.mixes().get(&2).unwrap()[0]
}
pub(crate) fn layer_three_mix(&self) -> &RoutingNode {
// SAFETY: we inserted layer3 node at construction
#[allow(clippy::unwrap_used)]
self.nodes.nodes_with_role(Role::Layer3).next().unwrap()
pub(crate) fn layer_three_mix(&self) -> &mix::LegacyNode {
&self.nodes.mixes().get(&3).unwrap()[0]
}
pub(crate) fn gateway_clients_address(&self) -> Option<String> {
self.gateway().ws_entry_address(false)
pub(crate) fn gateway_clients_address(&self) -> String {
self.gateway().clients_address()
}
pub(crate) fn gateway_identity(&self) -> identity::PublicKey {
@@ -88,10 +67,6 @@ impl TestRoute {
&self.nodes
}
pub(crate) fn testable_route_provider(&self) -> NymRouteProvider {
self.nodes.clone().into()
}
pub(crate) fn test_message_ext(&self, test_nonce: u64) -> NymApiTestMessageExt {
NymApiTestMessageExt::new(self.id, test_nonce)
}
+83 -7
View File
@@ -12,10 +12,13 @@ use futures::{stream, StreamExt};
use nym_api_requests::legacy::{LegacyGatewayBondWithId, LegacyMixNodeDetailsWithLayer};
use nym_api_requests::models::{DescribedNodeType, NymNodeData, NymNodeDescription};
use nym_config::defaults::DEFAULT_NYM_NODE_HTTP_PORT;
use nym_mixnet_contract_common::{NodeId, NymNodeDetails};
use nym_mixnet_contract_common::{LegacyMixLayer, NodeId, NymNodeDetails};
use nym_node_requests::api::client::{NymNodeApiClientError, NymNodeApiClientExt};
use nym_topology::node::{RoutingNode, RoutingNodeError};
use nym_topology::gateway::GatewayConversionError;
use nym_topology::mix::MixnodeConversionError;
use nym_topology::{gateway, mix, NetworkAddress};
use std::collections::HashMap;
use std::net::SocketAddr;
use std::time::Duration;
use thiserror::Error;
use tracing::{debug, error, info};
@@ -62,14 +65,87 @@ pub enum NodeDescribeCacheError {
// this exists because I've been moving things around quite a lot and now the place that holds the type
// doesn't have relevant dependencies for proper impl
pub(crate) trait NodeDescriptionTopologyExt {
fn try_to_topology_node(&self) -> Result<RoutingNode, RoutingNodeError>;
fn try_to_topology_mix_node(
&self,
layer: LegacyMixLayer,
) -> Result<mix::LegacyNode, MixnodeConversionError>;
fn try_to_topology_gateway(&self) -> Result<gateway::LegacyNode, GatewayConversionError>;
}
impl NodeDescriptionTopologyExt for NymNodeDescription {
fn try_to_topology_node(&self) -> Result<RoutingNode, RoutingNodeError> {
// for the purposes of routing, performance is completely ignored,
// so add dummy value and piggyback on existing conversion
(&self.to_skimmed_node(Default::default(), Default::default())).try_into()
// TODO: this might have to be moved around
fn try_to_topology_mix_node(
&self,
layer: LegacyMixLayer,
) -> Result<mix::LegacyNode, MixnodeConversionError> {
let keys = &self.description.host_information.keys;
let ips = &self.description.host_information.ip_address;
if ips.is_empty() {
return Err(MixnodeConversionError::NoIpAddressesProvided {
mixnode: keys.ed25519.to_base58_string(),
});
}
let host = match &self.description.host_information.hostname {
None => NetworkAddress::IpAddr(ips[0]),
Some(hostname) => NetworkAddress::Hostname(hostname.clone()),
};
// get ip from the self-reported values so we wouldn't need to do any hostname resolution
// (which doesn't really work in wasm)
let mix_host = SocketAddr::new(ips[0], self.description.mix_port());
Ok(mix::LegacyNode {
mix_id: self.node_id,
host,
mix_host,
identity_key: keys.ed25519,
sphinx_key: keys.x25519,
layer,
version: self
.description
.build_information
.build_version
.as_str()
.into(),
})
}
fn try_to_topology_gateway(&self) -> Result<gateway::LegacyNode, GatewayConversionError> {
let keys = &self.description.host_information.keys;
let ips = &self.description.host_information.ip_address;
if ips.is_empty() {
return Err(GatewayConversionError::NoIpAddressesProvided {
gateway: keys.ed25519.to_base58_string(),
});
}
let host = match &self.description.host_information.hostname {
None => NetworkAddress::IpAddr(ips[0]),
Some(hostname) => NetworkAddress::Hostname(hostname.clone()),
};
// get ip from the self-reported values so we wouldn't need to do any hostname resolution
// (which doesn't really work in wasm)
let mix_host = SocketAddr::new(ips[0], self.description.mix_port());
Ok(gateway::LegacyNode {
node_id: self.node_id,
host,
mix_host,
clients_ws_port: self.description.mixnet_websockets.ws_port,
clients_wss_port: self.description.mixnet_websockets.wss_port,
identity_key: self.description.host_information.keys.ed25519,
sphinx_key: self.description.host_information.keys.x25519,
version: self
.description
.build_information
.build_version
.as_str()
.into(),
})
}
}
+33 -17
View File
@@ -6,7 +6,7 @@ use crate::node_status_api::helpers::RewardedSetStatus;
use crate::node_status_api::models::Uptime;
use crate::node_status_api::reward_estimate::{compute_apy_from_reward, compute_reward_estimate};
use crate::nym_contract_cache::cache::data::ConfigScoreData;
use crate::support::legacy_helpers::legacy_host_to_ips_and_hostname;
use crate::nym_contract_cache::cache::CachedRewardedSet;
use crate::support::storage::NymApiStorage;
use nym_api_requests::legacy::{LegacyGatewayBondWithId, LegacyMixNodeDetailsWithLayer};
use nym_api_requests::models::DescribedNodeType::{LegacyGateway, LegacyMixnode, NymNode};
@@ -17,8 +17,10 @@ use nym_api_requests::models::{
use nym_contracts_common::NaiveFloat;
use nym_mixnet_contract_common::{Interval, NodeId, VersionScoreFormulaParams};
use nym_mixnet_contract_common::{NymNodeDetails, RewardingParams};
use nym_topology::CachedEpochRewardedSet;
use nym_topology::NetworkAddress;
use std::collections::{HashMap, HashSet};
use std::net::ToSocketAddrs;
use std::str::FromStr;
use tracing::trace;
pub(super) async fn get_mixnode_reliability_from_storage(
@@ -146,10 +148,7 @@ fn calculate_config_score(
}
// TODO: this might have to be moved to a different file if other places also rely on this functionality
fn get_rewarded_set_status(
rewarded_set: &CachedEpochRewardedSet,
node_id: NodeId,
) -> RewardedSetStatus {
fn get_rewarded_set_status(rewarded_set: &CachedRewardedSet, node_id: NodeId) -> RewardedSetStatus {
if rewarded_set.is_standby(&node_id) {
RewardedSetStatus::Standby
} else if rewarded_set.is_active_mixnode(&node_id) {
@@ -165,7 +164,7 @@ pub(super) async fn annotate_legacy_mixnodes_nodes_with_details(
mixnodes: Vec<LegacyMixNodeDetailsWithLayer>,
interval_reward_params: RewardingParams,
current_interval: Interval,
rewarded_set: &CachedEpochRewardedSet,
rewarded_set: &CachedRewardedSet,
blacklist: &HashSet<NodeId>,
) -> HashMap<NodeId, MixNodeBondAnnotated> {
let mut annotated = HashMap::new();
@@ -204,11 +203,21 @@ pub(super) async fn annotate_legacy_mixnodes_nodes_with_details(
.ok()
.unwrap_or_default();
let Some((ip_addresses, _)) =
legacy_host_to_ips_and_hostname(&mixnode.bond_information.mix_node.host)
else {
continue;
};
// safety: this conversion is infallible
let ip_addresses =
match NetworkAddress::from_str(&mixnode.bond_information.mix_node.host).unwrap() {
NetworkAddress::IpAddr(ip) => vec![ip],
NetworkAddress::Hostname(hostname) => {
// try to resolve it
(
hostname.as_str(),
mixnode.bond_information.mix_node.mix_port,
)
.to_socket_addrs()
.map(|iter| iter.map(|s| s.ip()).collect::<Vec<_>>())
.unwrap_or_default()
}
};
let (estimated_operator_apy, estimated_delegators_apy) =
compute_apy_from_reward(&mixnode, reward_estimate, current_interval);
@@ -254,10 +263,17 @@ pub(crate) async fn annotate_legacy_gateways_with_details(
.ok()
.unwrap_or_default();
let Some((ip_addresses, _)) =
legacy_host_to_ips_and_hostname(&gateway_bond.bond.gateway.host)
else {
continue;
// safety: this conversion is infallible
let ip_addresses = match NetworkAddress::from_str(&gateway_bond.bond.gateway.host).unwrap()
{
NetworkAddress::IpAddr(ip) => vec![ip],
NetworkAddress::Hostname(hostname) => {
// try to resolve it
(hostname.as_str(), gateway_bond.bond.gateway.mix_port)
.to_socket_addrs()
.map(|iter| iter.map(|s| s.ip()).collect::<Vec<_>>())
.unwrap_or_default()
}
};
annotated.insert(
@@ -282,7 +298,7 @@ pub(crate) async fn produce_node_annotations(
legacy_mixnodes: &[LegacyMixNodeDetailsWithLayer],
legacy_gateways: &[LegacyGatewayBondWithId],
nym_nodes: &[NymNodeDetails],
rewarded_set: &CachedEpochRewardedSet,
rewarded_set: &CachedRewardedSet,
current_interval: Interval,
described_nodes: &DescribedNodes,
) -> HashMap<NodeId, NodeAnnotation> {
@@ -347,6 +347,9 @@ async fn _latest_monitor_run_report(
#[utoipa::path(
tag = "UNSTABLE - DO **NOT** USE",
get,
params(
PaginationRequest
),
path = "/v1/status/network-monitor/unstable/run/{monitor_run_id}/details",
responses(
(status = 200, body = NetworkMonitorRunDetailsResponse)
@@ -367,6 +370,9 @@ pub async fn monitor_run_report(
#[utoipa::path(
tag = "UNSTABLE - DO **NOT** USE",
get,
params(
PaginationRequest
),
path = "/v1/status/network-monitor/unstable/run/latest/details",
responses(
(status = 200, body = NetworkMonitorRunDetailsResponse)
+118 -4
View File
@@ -3,16 +3,130 @@
use crate::support::caching::Cache;
use nym_api_requests::legacy::{LegacyGatewayBondWithId, LegacyMixNodeDetailsWithLayer};
use nym_api_requests::models::ConfigScoreDataResponse;
use nym_api_requests::models::{ConfigScoreDataResponse, RewardedSetResponse};
use nym_contracts_common::ContractBuildInformation;
use nym_mixnet_contract_common::nym_node::Role;
use nym_mixnet_contract_common::{
ConfigScoreParams, HistoricalNymNodeVersionEntry, Interval, NodeId, NymNodeDetails,
RewardingParams,
RewardedSet, RewardingParams,
};
use nym_topology::CachedEpochRewardedSet;
use nym_validator_client::nyxd::AccountId;
use std::collections::{HashMap, HashSet};
#[derive(Default, Clone)]
pub(crate) struct CachedRewardedSet {
pub(crate) entry_gateways: HashSet<NodeId>,
pub(crate) exit_gateways: HashSet<NodeId>,
pub(crate) layer1: HashSet<NodeId>,
pub(crate) layer2: HashSet<NodeId>,
pub(crate) layer3: HashSet<NodeId>,
pub(crate) standby: HashSet<NodeId>,
}
impl From<RewardedSet> for CachedRewardedSet {
fn from(value: RewardedSet) -> Self {
CachedRewardedSet {
entry_gateways: value.entry_gateways.into_iter().collect(),
exit_gateways: value.exit_gateways.into_iter().collect(),
layer1: value.layer1.into_iter().collect(),
layer2: value.layer2.into_iter().collect(),
layer3: value.layer3.into_iter().collect(),
standby: value.standby.into_iter().collect(),
}
}
}
impl From<CachedRewardedSet> for RewardedSet {
fn from(value: CachedRewardedSet) -> Self {
RewardedSet {
entry_gateways: value.entry_gateways.into_iter().collect(),
exit_gateways: value.exit_gateways.into_iter().collect(),
layer1: value.layer1.into_iter().collect(),
layer2: value.layer2.into_iter().collect(),
layer3: value.layer3.into_iter().collect(),
standby: value.standby.into_iter().collect(),
}
}
}
impl From<&CachedRewardedSet> for RewardedSetResponse {
fn from(value: &CachedRewardedSet) -> Self {
RewardedSetResponse {
entry_gateways: value.entry_gateways.iter().copied().collect(),
exit_gateways: value.exit_gateways.iter().copied().collect(),
layer1: value.layer1.iter().copied().collect(),
layer2: value.layer2.iter().copied().collect(),
layer3: value.layer3.iter().copied().collect(),
standby: value.standby.iter().copied().collect(),
}
}
}
impl CachedRewardedSet {
pub(crate) fn role(&self, node_id: NodeId) -> Option<Role> {
if self.entry_gateways.contains(&node_id) {
Some(Role::EntryGateway)
} else if self.exit_gateways.contains(&node_id) {
Some(Role::ExitGateway)
} else if self.layer1.contains(&node_id) {
Some(Role::Layer1)
} else if self.layer2.contains(&node_id) {
Some(Role::Layer2)
} else if self.layer3.contains(&node_id) {
Some(Role::Layer3)
} else if self.standby.contains(&node_id) {
Some(Role::Standby)
} else {
None
}
}
pub fn try_get_mix_layer(&self, node_id: &NodeId) -> Option<u8> {
if self.layer1.contains(node_id) {
Some(1)
} else if self.layer2.contains(node_id) {
Some(2)
} else if self.layer3.contains(node_id) {
Some(3)
} else {
None
}
}
pub fn is_standby(&self, node_id: &NodeId) -> bool {
self.standby.contains(node_id)
}
pub fn is_active_mixnode(&self, node_id: &NodeId) -> bool {
self.layer1.contains(node_id)
|| self.layer2.contains(node_id)
|| self.layer3.contains(node_id)
}
#[allow(dead_code)]
pub(crate) fn gateways(&self) -> HashSet<NodeId> {
let mut gateways =
HashSet::with_capacity(self.entry_gateways.len() + self.exit_gateways.len());
gateways.extend(&self.entry_gateways);
gateways.extend(&self.exit_gateways);
gateways
}
pub(crate) fn active_mixnodes(&self) -> HashSet<NodeId> {
let mut mixnodes =
HashSet::with_capacity(self.layer1.len() + self.layer2.len() + self.layer3.len());
mixnodes.extend(&self.layer1);
mixnodes.extend(&self.layer2);
mixnodes.extend(&self.layer3);
mixnodes
}
}
#[derive(Clone)]
pub(crate) struct ConfigScoreData {
pub(crate) config_score_params: ConfigScoreParams,
@@ -36,7 +150,7 @@ pub(crate) struct ContractCacheData {
pub(crate) legacy_mixnodes: Cache<Vec<LegacyMixNodeDetailsWithLayer>>,
pub(crate) legacy_gateways: Cache<Vec<LegacyGatewayBondWithId>>,
pub(crate) nym_nodes: Cache<Vec<NymNodeDetails>>,
pub(crate) rewarded_set: Cache<CachedEpochRewardedSet>,
pub(crate) rewarded_set: Cache<CachedRewardedSet>,
// this purposely does not deal with nym-nodes as they don't have a concept of a blacklist.
// instead clients are meant to be filtering out them themselves based on the provided scores.
+7 -6
View File
@@ -11,10 +11,9 @@ use nym_api_requests::legacy::{
use nym_api_requests::models::MixnodeStatus;
use nym_crypto::asymmetric::ed25519;
use nym_mixnet_contract_common::{
ConfigScoreParams, EpochRewardedSet, HistoricalNymNodeVersionEntry, Interval, NodeId,
NymNodeDetails, RewardingParams,
ConfigScoreParams, HistoricalNymNodeVersionEntry, Interval, NodeId, NymNodeDetails,
RewardedSet, RewardingParams,
};
use nym_topology::CachedEpochRewardedSet;
use std::{
collections::HashSet,
sync::{
@@ -30,6 +29,8 @@ use tracing::{debug, error};
pub(crate) mod data;
pub(crate) mod refresher;
pub(crate) use self::data::CachedRewardedSet;
const CACHE_TIMEOUT_MS: u64 = 100;
#[derive(Clone)]
@@ -79,7 +80,7 @@ impl NymContractCache {
mixnodes: Vec<LegacyMixNodeDetailsWithLayer>,
gateways: Vec<LegacyGatewayBondWithId>,
nym_nodes: Vec<NymNodeDetails>,
rewarded_set: EpochRewardedSet,
rewarded_set: RewardedSet,
config_score_params: ConfigScoreParams,
nym_node_version_history: Vec<HistoricalNymNodeVersionEntry>,
rewarding_params: RewardingParams,
@@ -263,11 +264,11 @@ impl NymContractCache {
.into_inner()
}
pub async fn rewarded_set(&self) -> Option<RwLockReadGuard<Cache<CachedEpochRewardedSet>>> {
pub async fn rewarded_set(&self) -> Option<RwLockReadGuard<Cache<CachedRewardedSet>>> {
self.get(|cache| &cache.rewarded_set).await
}
pub async fn rewarded_set_owned(&self) -> Cache<CachedEpochRewardedSet> {
pub async fn rewarded_set_owned(&self) -> Cache<CachedRewardedSet> {
self.get_owned(|cache| cache.rewarded_set.clone_cache())
.await
.unwrap_or_default()

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