Merge remote-tracking branch 'origin/develop' into release/2025.2-hu

This commit is contained in:
Bogdan-Ștefan Neacşu
2025-02-06 13:16:51 +02:00
98 changed files with 2210 additions and 618 deletions
+4 -3
View File
@@ -8,17 +8,18 @@ on:
- 'explorer-api/**'
- 'gateway/**'
- 'integrations/**'
- 'mixnode/**'
- 'nym-api/**'
- 'nym-data-observatory/**'
- 'nym-credential-proxy/**'
- 'nym-network-monitor/**'
- 'nym-node/**'
- 'nym-node-status-api/**'
- 'nym-outfox/**'
- 'nym-validator-rewarder/**'
- 'sdk/lib/**'
- 'nyx-chain-watcher/**'
- 'sdk/ffi/**'
- 'sdk/rust/**'
- 'service-providers/**'
- 'nym-browser-extension/storage/**'
- 'tools/**'
- 'wasm/**'
- 'Cargo.toml'
+1
View File
@@ -1,6 +1,7 @@
name: ci-sdk-wasm
on:
workflow_dispatch:
pull_request:
paths:
- 'wasm/**'
Generated
+279 -127
View File
File diff suppressed because it is too large Load Diff
+34 -34
View File
@@ -52,8 +52,8 @@ members = [
"common/execute",
"common/exit-policy",
"common/gateway-requests",
"common/gateway-storage",
"common/gateway-stats-storage",
"common/gateway-storage",
"common/http-api-client",
"common/http-api-common",
"common/inclusion-probability",
@@ -92,6 +92,7 @@ members = [
"common/topology",
"common/tun",
"common/types",
"common/verloc",
"common/wasm/client-core",
"common/wasm/storage",
"common/wasm/utils",
@@ -103,6 +104,22 @@ members = [
"explorer-api/explorer-client",
"gateway",
"integrations/bity",
"nym-api",
"nym-api/nym-api-requests",
"nym-browser-extension/storage",
"nym-credential-proxy/nym-credential-proxy",
"nym-credential-proxy/nym-credential-proxy-requests",
"nym-credential-proxy/vpn-api-lib-wasm",
"nym-network-monitor",
"nym-node",
"nym-node-status-api/nym-node-status-agent",
"nym-node-status-api/nym-node-status-api",
"nym-node-status-api/nym-node-status-client",
"nym-node/nym-node-metrics",
"nym-node/nym-node-requests",
"nym-outfox",
"nym-validator-rewarder",
"nyx-chain-watcher",
"sdk/ffi/cpp",
"sdk/ffi/go",
"sdk/ffi/shared",
@@ -111,26 +128,16 @@ members = [
"service-providers/common",
"service-providers/ip-packet-router",
"service-providers/network-requester",
"nym-api",
"nym-api/nym-api-requests",
"nym-browser-extension/storage",
"nym-credential-proxy/nym-credential-proxy",
"nym-credential-proxy/nym-credential-proxy-requests",
"nym-credential-proxy/vpn-api-lib-wasm",
"nym-network-monitor",
"nyx-chain-watcher",
"nym-node",
"nym-node/nym-node-requests",
"nym-node/nym-node-metrics",
"nym-node-status-api/nym-node-status-agent",
"nym-node-status-api/nym-node-status-api",
"nym-node-status-api/nym-node-status-client",
"nym-outfox",
"nym-validator-rewarder",
"tools/echo-server",
"tools/internal/ssl-inject",
"tools/echo-server",
"tools/internal/contract-state-importer/importer-cli",
"tools/internal/contract-state-importer/importer-contract",
"tools/internal/mixnet-connectivity-check",
# "tools/internal/sdk-version-bump",
"tools/internal/ssl-inject",
"tools/internal/testnet-manager",
"tools/internal/testnet-manager",
"tools/internal/testnet-manager/dkg-bypass-contract",
"tools/internal/testnet-manager/dkg-bypass-contract",
"tools/nym-cli",
"tools/nym-id-cli",
@@ -142,13 +149,6 @@ members = [
"wasm/mix-fetch",
"wasm/node-tester",
"wasm/zknym-lib",
"tools/echo-server",
"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",
]
default-members = [
@@ -172,7 +172,6 @@ exclude = [
"explorer",
"contracts",
"nym-wallet",
"nym-vpn/ui/src-tauri",
"cpu-cycles",
]
@@ -216,7 +215,7 @@ chacha20 = "0.9.0"
chacha20poly1305 = "0.10.1"
chrono = "0.4.39"
cipher = "0.4.3"
clap = "4.5.26"
clap = "4.5.27"
clap_complete = "4.5"
clap_complete_fig = "4.5"
colored = "2.0"
@@ -249,11 +248,12 @@ futures = "0.3.31"
futures-util = "0.3"
generic-array = "0.14.7"
getrandom = "0.2.10"
getset = "0.1.3"
getset = "0.1.4"
handlebars = "3.5.5"
headers = "0.4.0"
hex = "0.4.3"
hex-literal = "0.3.3"
hickory-resolver = "0.24.2"
hkdf = "0.12.3"
hmac = "0.12.1"
http = "1"
@@ -264,7 +264,7 @@ humantime-serde = "1.1.1"
human-repr = "1.1.0"
hyper = "1.4.1"
hyper-util = "0.1"
indicatif = "0.17.9"
indicatif = "0.17.11"
inquire = "0.6.2"
ip_network = "0.4.1"
ipnetwork = "0.20"
@@ -307,11 +307,11 @@ rocket_cors = "0.6.0"
rocket_okapi = "0.8.0"
safer-ffi = "0.1.13"
schemars = "0.8.21"
semver = "1.0.24"
semver = "1.0.25"
serde = "1.0.217"
serde_bytes = "0.11.15"
serde_derive = "1.0"
serde_json = "1.0.135"
serde_json = "1.0.138"
serde_json_path = "0.7.1"
serde_repr = "0.1"
serde_with = "3.9.0"
@@ -328,9 +328,9 @@ sysinfo = "0.33.0"
tap = "1.0.1"
tar = "0.4.43"
tempfile = "3.15"
thiserror = "1.0.64"
thiserror = "2.0"
time = "0.3.37"
tokio = "1.39"
tokio = "1.43"
tokio-stream = "0.1.17"
tokio-test = "0.4.4"
tokio-tun = "0.11.5"
@@ -384,7 +384,7 @@ cw4 = { version = "=1.1.2" }
cw-controllers = { version = "=1.1.0" }
# cosmrs-related
bip32 = { version = "0.5.2", default-features = false }
bip32 = { version = "0.5.3", default-features = false }
cosmrs = { version = "0.21.0" }
+2 -2
View File
@@ -13,7 +13,7 @@ The platform is composed of multiple Rust crates. Top-level executable binary cr
* `nym-client` - an executable which you can build into your own applications. Use it for interacting with Nym nodes.
* `nym-socks5-client` - a Socks5 proxy you can run on your machine and use with existing applications.
* `nym-explorer` - a (projected) block explorer and (existing) mixnet viewer.
* `nym-wallet` - a desktop wallet implemented using the [Tauri](https://tauri.studio/en/docs/about/intro) framework.
* `nym-wallet` - a desktop wallet implemented using the [Tauri](https://tauri.app)) framework.
* `nym-cli` - a tool for interacting with the network from the CLI.
<!-- coming soon
* `nym-network-monitor` - sends packets through the full system to check that they are working as expected, and stores node uptime histories as the basis of a rewards system ("mixmining" or "proof-of-mixing").
@@ -66,4 +66,4 @@ As a general approach, licensing is as follows this pattern:
- libraries and components are Apache 2.0 or MIT
- documentation is Apache 2.0 or CC0-1.0
Nym Node Operators and Validators Temrs and Conditions can be found [here](https://nym.com/terms-and-conditions/operators/v1.0.0).
Nym Node Operators and Validators Terms and Conditions can be found [here](https://nym.com/operators-validators-terms).
+1
View File
@@ -40,6 +40,7 @@ nym-crypto = { path = "../crypto" }
nym-explorer-client = { path = "../../explorer-api/explorer-client" }
nym-gateway-client = { path = "../client-libs/gateway-client" }
nym-gateway-requests = { path = "../gateway-requests" }
nym-http-api-client = { path = "../http-api-client" }
nym-metrics = { path = "../nym-metrics" }
nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" }
nym-sphinx = { path = "../nymsphinx" }
@@ -15,6 +15,7 @@ pub mod error;
mod manager;
mod models;
#[derive(Clone)]
pub struct OnDiskGatewaysDetails {
manager: StorageManager,
}
@@ -20,12 +20,12 @@ pub enum InMemStorageError {
MalformedGateway(#[from] BadGateway),
}
#[derive(Debug, Default)]
#[derive(Clone, Debug, Default)]
pub struct InMemGatewaysDetails {
inner: Arc<RwLock<InMemStorageInner>>,
}
#[derive(Debug, Default)]
#[derive(Clone, Debug, Default)]
struct InMemStorageInner {
active_gateway: Option<String>,
gateways: HashMap<String, GatewayRegistration>,
@@ -1,6 +1,7 @@
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: Apache-2.0
use super::mix_traffic::ClientRequestSender;
use super::received_buffer::ReceivedBufferMessage;
use super::statistics_control::StatisticsControl;
use crate::client::base_client::storage::helpers::store_client_keys;
@@ -645,13 +646,12 @@ where
fn start_mix_traffic_controller(
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
shutdown: TaskClient,
forget_me: ForgetMe,
) -> BatchMixMessageSender {
) -> (BatchMixMessageSender, ClientRequestSender) {
info!("Starting mix traffic controller...");
let (mix_traffic_controller, mix_tx) =
MixTrafficController::new(gateway_transceiver, forget_me);
let (mix_traffic_controller, mix_tx, client_tx) =
MixTrafficController::new(gateway_transceiver);
mix_traffic_controller.start_with_shutdown(shutdown);
mix_tx
(mix_tx, client_tx)
}
// TODO: rename it as it implies the data is persistent whilst one can use InMemBackend
@@ -833,10 +833,9 @@ where
// traffic stream.
// The MixTrafficController then sends the actual traffic
let message_sender = Self::start_mix_traffic_controller(
let (message_sender, client_request_sender) = Self::start_mix_traffic_controller(
gateway_transceiver,
shutdown.fork("mix_traffic_controller"),
self.forget_me,
);
// Channels that the websocket listener can use to signal downstream to the real traffic
@@ -911,6 +910,8 @@ where
},
stats_reporter,
task_handle: shutdown,
client_request_sender,
forget_me: self.forget_me,
})
}
}
@@ -922,6 +923,7 @@ pub struct BaseClient {
pub client_output: ClientOutputStatus,
pub client_state: ClientState,
pub stats_reporter: ClientStatsSender,
pub client_request_sender: ClientRequestSender,
pub task_handle: TaskHandle,
pub forget_me: ForgetMe,
}
@@ -65,6 +65,7 @@ pub trait MixnetClientStorage {
fn gateway_details_store(&self) -> &Self::GatewaysDetailsStore;
}
#[derive(Clone)]
pub struct Ephemeral {
key_store: InMemEphemeralKeys,
reply_store: reply_storage::Empty,
@@ -120,6 +121,7 @@ impl MixnetClientStorage for Ephemeral {
}
}
#[derive(Clone)]
#[cfg(all(
not(target_arch = "wasm32"),
feature = "fs-surb-storage",
@@ -5,6 +5,7 @@ use crate::client::key_manager::ClientKeys;
use async_trait::async_trait;
use rand::{CryptoRng, RngCore};
use std::error::Error;
use std::sync::Arc;
use tokio::sync::Mutex;
#[cfg(not(target_arch = "wasm32"))]
@@ -65,6 +66,7 @@ pub enum OnDiskKeysError {
},
}
#[derive(Clone)]
#[cfg(not(target_arch = "wasm32"))]
pub struct OnDiskKeys {
paths: ClientKeysPaths,
@@ -194,8 +196,9 @@ impl KeyStore for OnDiskKeys {
}
}
#[derive(Clone)]
pub struct InMemEphemeralKeys {
keys: Mutex<ClientKeys>,
keys: Arc<Mutex<ClientKeys>>,
}
impl InMemEphemeralKeys {
@@ -204,7 +207,7 @@ impl InMemEphemeralKeys {
R: RngCore + CryptoRng,
{
InMemEphemeralKeys {
keys: Mutex::new(ClientKeys::generate_new(rng)),
keys: Arc::new(Mutex::new(ClientKeys::generate_new(rng))),
}
}
}
@@ -2,13 +2,17 @@
// SPDX-License-Identifier: Apache-2.0
use crate::client::mix_traffic::transceiver::GatewayTransceiver;
use crate::{spawn_future, ForgetMe};
use crate::error::ClientCoreError;
use crate::spawn_future;
use log::*;
use nym_gateway_requests::ClientRequest;
use nym_sphinx::forwarding::packet::MixPacket;
use transceiver::ErasedGatewayError;
pub type BatchMixMessageSender = tokio::sync::mpsc::Sender<Vec<MixPacket>>;
pub type BatchMixMessageReceiver = tokio::sync::mpsc::Receiver<Vec<MixPacket>>;
pub type ClientRequestReceiver = tokio::sync::mpsc::Receiver<ClientRequest>;
pub type ClientRequestSender = tokio::sync::mpsc::Sender<ClientRequest>;
pub mod transceiver;
@@ -23,52 +27,67 @@ pub struct MixTrafficController {
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
mix_rx: BatchMixMessageReceiver,
client_rx: ClientRequestReceiver,
// TODO: this is temporary work-around.
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
consecutive_gateway_failure_count: usize,
forget_me: ForgetMe,
}
impl MixTrafficController {
pub fn new<T>(
gateway_transceiver: T,
forget_me: ForgetMe,
) -> (MixTrafficController, BatchMixMessageSender)
) -> (
MixTrafficController,
BatchMixMessageSender,
ClientRequestSender,
)
where
T: GatewayTransceiver + Send + 'static,
{
let (message_sender, message_receiver) =
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
let (client_sender, client_receiver) = tokio::sync::mpsc::channel(1);
(
MixTrafficController {
gateway_transceiver: Box::new(gateway_transceiver),
mix_rx: message_receiver,
client_rx: client_receiver,
consecutive_gateway_failure_count: 0,
forget_me,
},
message_sender,
client_sender,
)
}
pub fn new_dynamic(
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
forget_me: ForgetMe,
) -> (MixTrafficController, BatchMixMessageSender) {
) -> (
MixTrafficController,
BatchMixMessageSender,
ClientRequestSender,
) {
let (message_sender, message_receiver) =
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
let (client_sender, client_receiver) = tokio::sync::mpsc::channel(1);
(
MixTrafficController {
gateway_transceiver,
mix_rx: message_receiver,
client_rx: client_receiver,
consecutive_gateway_failure_count: 0,
forget_me,
},
message_sender,
client_sender,
)
}
async fn on_messages(&mut self, mut mix_packets: Vec<MixPacket>) {
async fn on_messages(
&mut self,
mut mix_packets: Vec<MixPacket>,
) -> Result<(), ErasedGatewayError> {
debug_assert!(!mix_packets.is_empty());
let result = if mix_packets.len() == 1 {
@@ -80,21 +99,14 @@ impl MixTrafficController {
.await
};
match result {
Err(err) => {
error!("Failed to send sphinx packet(s) to the gateway: {err}");
self.consecutive_gateway_failure_count += 1;
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
// todo: in the future this should initiate a 'graceful' shutdown or try
// to reconnect?
panic!("failed to send sphinx packet to the gateway {MAX_FAILURE_COUNT} times in a row - assuming the gateway is dead. Can't do anything about it yet :(")
}
}
Ok(_) => {
trace!("We *might* have managed to forward sphinx packet(s) to the gateway!");
self.consecutive_gateway_failure_count = 0;
}
if result.is_err() {
self.consecutive_gateway_failure_count += 1;
} else {
trace!("We *might* have managed to forward sphinx packet(s) to the gateway!");
self.consecutive_gateway_failure_count = 0;
}
result
}
pub fn start_with_shutdown(mut self, mut shutdown: nym_task::TaskClient) {
@@ -105,13 +117,35 @@ impl MixTrafficController {
tokio::select! {
mix_packets = self.mix_rx.recv() => match mix_packets {
Some(mix_packets) => {
self.on_messages(mix_packets).await;
if let Err(err) = self.on_messages(mix_packets).await {
error!("Failed to send sphinx packet(s) to the gateway: {err}");
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
// Disconnect from the gateway. If we should try to re-connect
// is handled at a higher layer.
error!("Failed to send sphinx packet to the gateway {MAX_FAILURE_COUNT} times in a row - assuming the gateway is dead");
// Do we need to handle the embedded mixnet client case
// separately?
shutdown.send_we_stopped(Box::new(ClientCoreError::GatewayFailedToForwardMessages));
break;
}
}
},
None => {
log::trace!("MixTrafficController: Stopping since channel closed");
break;
}
},
client_request = self.client_rx.recv() => match client_request {
Some(client_request) => {
match self.gateway_transceiver.send_client_request(client_request).await {
Ok(_) => (),
Err(e) => error!("Failed to send client request: {}", e),
};
},
None => {
log::trace!("MixTrafficController, client request channel closed");
}
},
_ = shutdown.recv_with_delay() => {
log::trace!("MixTrafficController: Received shutdown");
break;
@@ -120,25 +154,6 @@ impl MixTrafficController {
}
shutdown.recv_timeout().await;
if self.forget_me.any() {
log::info!("Sending forget me request to the gateway");
match self
.gateway_transceiver
.send_client_request(ClientRequest::ForgetMe {
client: self.forget_me.client(),
stats: self.forget_me.stats(),
})
.await
{
Ok(_) => {
log::info!("Successfully sent forget me request to the gateway");
}
Err(err) => {
log::error!("Failed to send forget me request to the gateway: {err}");
}
}
}
log::debug!("MixTrafficController: Exiting");
});
}
@@ -86,7 +86,9 @@ impl<G: GatewayTransceiver + ?Sized + Send> GatewayTransceiver for Box<G> {
&mut self,
message: ClientRequest,
) -> Result<(), GatewayClientError> {
(**self).send_client_request(message).await
let _ = (**self).send_client_request(message.clone()).await?;
log::debug!("Sent client request: {:?}", message);
Ok(())
}
}
@@ -143,14 +145,7 @@ where
&mut self,
message: ClientRequest,
) -> Result<(), GatewayClientError> {
if let Some(shared_key) = self.gateway_client.shared_key() {
self.gateway_client
.send_websocket_message(message.encrypt(&*shared_key)?)
.await?;
Ok(())
} else {
Err(GatewayClientError::ConnectionInInvalidState)
}
self.gateway_client.send_client_request(message).await
}
}
@@ -626,9 +626,14 @@ where
messages: Vec<RealMessage>,
transmission_lane: TransmissionLane,
) {
self.real_message_sender
if let Err(err) = self
.real_message_sender
.send((messages, transmission_lane))
.await
.expect("real message receiver task (OutQueueControl) has died");
{
error!(
"Failed to forward messages to the real message sender (OutQueueControl): {err}"
);
}
}
}
@@ -545,7 +545,7 @@ where
loop {
tokio::select! {
biased;
_ = shutdown.recv_with_delay() => {
_ = shutdown.recv() => {
log::trace!("OutQueueControl: Received shutdown");
break;
}
@@ -123,6 +123,11 @@ impl StatisticsControl {
loop {
tokio::select! {
biased;
_ = task_client.recv() => {
log::trace!("StatisticsControl: Received shutdown");
break;
},
stats_event = self.stats_rx.recv() => match stats_event {
Some(stats_event) => self.stats.handle_event(stats_event),
None => {
@@ -146,13 +151,8 @@ impl StatisticsControl {
_ = local_report_interval.next() => {
self.stats.local_report(&mut task_client);
}
_ = task_client.recv_with_delay() => {
log::trace!("StatisticsControl: Received shutdown");
break;
},
}
}
task_client.recv_timeout().await;
log::debug!("StatisticsControl: Exiting");
}
+10
View File
@@ -36,6 +36,13 @@ pub enum ClientCoreError {
#[error("no gateway with id: {0}")]
NoGatewayWithId(String),
#[error("Invalid URL: {0}")]
InvalidUrl(String),
#[cfg(not(target_arch = "wasm32"))]
#[error("resolution failed: {0}")]
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
#[error("no gateways on network")]
NoGatewaysOnNetwork,
@@ -96,6 +103,9 @@ pub enum ClientCoreError {
#[error("timed out while trying to establish gateway connection")]
GatewayConnectionTimeout,
#[error("failed to forward mix messages to gateway")]
GatewayFailedToForwardMessages,
#[error("no ping measurements for the gateway ({identity}) performed")]
NoGatewayMeasurements { identity: String },
+4 -3
View File
@@ -15,6 +15,9 @@ use std::{sync::Arc, time::Duration};
use tungstenite::Message;
use url::Url;
#[cfg(not(target_arch = "wasm32"))]
use crate::init::websockets::connect_async;
use nym_topology::NodeId;
#[cfg(not(target_arch = "wasm32"))]
use tokio::net::TcpStream;
@@ -23,8 +26,6 @@ use tokio::time::sleep;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::Instant;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::connect_async;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
#[cfg(target_arch = "wasm32")]
use wasm_utils::websocket::JSWebsocket;
@@ -132,7 +133,7 @@ pub async fn gateways_for_init<R: Rng>(
async fn connect(endpoint: &str) -> Result<WsConn, ClientCoreError> {
match tokio::time::timeout(CONN_TIMEOUT, connect_async(endpoint)).await {
Err(_elapsed) => Err(ClientCoreError::GatewayConnectionTimeout),
Ok(Err(conn_failure)) => Err(conn_failure.into()),
Ok(Err(conn_failure)) => Err(conn_failure),
Ok(Ok((stream, _))) => Ok(stream),
}
}
+2
View File
@@ -26,6 +26,8 @@ use serde::Serialize;
pub mod helpers;
pub mod types;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) mod websockets;
// helpers for error wrapping
+44
View File
@@ -0,0 +1,44 @@
use crate::error::ClientCoreError;
use nym_http_api_client::HickoryDnsResolver;
use tokio::net::TcpStream;
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
use tungstenite::handshake::client::Response;
use url::{Host, Url};
use std::net::SocketAddr;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) async fn connect_async(
endpoint: &str,
) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), ClientCoreError> {
let resolver = HickoryDnsResolver::default();
let uri = Url::parse(endpoint).map_err(|_| ClientCoreError::InvalidUrl(endpoint.to_owned()))?;
let port: u16 = uri.port_or_known_default().unwrap_or(443);
let host = uri
.host()
.ok_or(ClientCoreError::InvalidUrl(endpoint.to_owned()))?;
// Get address for tcp connection, if a domain is provided use our preferred resolver rather than
// the default std resolve
let sock_addrs: Vec<SocketAddr> = match host {
Host::Ipv4(addr) => vec![SocketAddr::new(addr.into(), port)],
Host::Ipv6(addr) => vec![SocketAddr::new(addr.into(), port)],
Host::Domain(domain) => {
// Do a DNS lookup for the domain using our custom DNS resolver
resolver
.resolve_str(domain)
.await?
.into_iter()
.map(|a| SocketAddr::new(a, port))
.collect()
}
};
let stream = TcpStream::connect(&sock_addrs[..]).await?;
tokio_tungstenite::client_async_tls(endpoint, stream)
.await
.map_err(Into::into)
}
@@ -22,7 +22,7 @@ mod error;
mod manager;
mod models;
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct Backend {
temporary_old_path: Option<PathBuf>,
database_path: PathBuf,
@@ -19,7 +19,7 @@ pub mod fs_backend;
#[error("no information provided")]
pub struct UndefinedError;
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct Empty {
// we need to keep 'basic' metadata here to "load" the CombinedReplyStorage
pub min_surb_threshold: usize,
@@ -27,6 +27,7 @@ nym-credential-storage = { path = "../../credential-storage" }
nym-credentials-interface = { path = "../../credentials-interface" }
nym-crypto = { path = "../../crypto" }
nym-gateway-requests = { path = "../../gateway-requests" }
nym-http-api-client = { path = "../../http-api-client" }
nym-network-defaults = { path = "../../network-defaults" }
nym-sphinx = { path = "../../nymsphinx" }
nym-statistics-common = { path = "../../statistics" }
@@ -40,8 +40,6 @@ use url::Url;
use std::os::fd::RawFd;
#[cfg(not(target_arch = "wasm32"))]
use tokio::time::sleep;
#[cfg(not(target_arch = "wasm32"))]
use tokio_tungstenite::connect_async;
#[cfg(not(unix))]
use std::os::raw::c_int as RawFd;
@@ -53,6 +51,11 @@ use zeroize::Zeroizing;
pub mod config;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) mod websockets;
#[cfg(not(target_arch = "wasm32"))]
use websockets::connect_async;
pub struct GatewayConfig {
pub gateway_identity: identity::PublicKey,
@@ -201,15 +204,7 @@ impl<C, St> GatewayClient<C, St> {
"Attemting to establish connection to gateway at: {}",
self.gateway_address
);
let ws_stream = match connect_async(&self.gateway_address).await {
Ok((ws_stream, _)) => ws_stream,
Err(error) => {
return Err(GatewayClientError::NetworkConnectionFailed {
address: self.gateway_address.clone(),
source: error,
})
}
};
let (ws_stream, _) = connect_async(&self.gateway_address).await?;
self.connection = SocketState::Available(Box::new(ws_stream));
@@ -271,6 +266,19 @@ impl<C, St> GatewayClient<C, St> {
}
}
pub async fn send_client_request(
&mut self,
message: ClientRequest,
) -> Result<(), GatewayClientError> {
if let Some(shared_key) = self.shared_key() {
let encrypted = message.encrypt(&*shared_key)?;
Box::pin(self.send_websocket_message(encrypted)).await?;
Ok(())
} else {
Err(GatewayClientError::ConnectionInInvalidState)
}
}
async fn read_control_response(&mut self) -> Result<ServerResponse, GatewayClientError> {
// we use the fact that all request responses are Message::Text and only pushed
// sphinx packets are Message::Binary
@@ -0,0 +1,53 @@
use crate::error::GatewayClientError;
use nym_http_api_client::HickoryDnsResolver;
use tokio::net::TcpStream;
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
use tungstenite::handshake::client::Response;
use url::{Host, Url};
use std::net::SocketAddr;
#[cfg(not(target_arch = "wasm32"))]
pub(crate) async fn connect_async(
endpoint: &str,
) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), GatewayClientError> {
let resolver = HickoryDnsResolver::default();
let uri =
Url::parse(endpoint).map_err(|_| GatewayClientError::InvalidUrl(endpoint.to_owned()))?;
let port: u16 = uri.port_or_known_default().unwrap_or(443);
let host = uri
.host()
.ok_or(GatewayClientError::InvalidUrl(endpoint.to_owned()))?;
// Get address for tcp connection, if a domain is provided use our preferred resolver rather than
// the default std resolve
let sock_addrs: Vec<SocketAddr> = match host {
Host::Ipv4(addr) => vec![SocketAddr::new(addr.into(), port)],
Host::Ipv6(addr) => vec![SocketAddr::new(addr.into(), port)],
Host::Domain(domain) => {
// Do a DNS lookup for the domain using our custom DNS resolver
resolver
.resolve_str(domain)
.await?
.into_iter()
.map(|a| SocketAddr::new(a, port))
.collect()
}
};
let stream = TcpStream::connect(&sock_addrs[..]).await.map_err(|error| {
GatewayClientError::NetworkConnectionFailed {
address: endpoint.to_owned(),
source: error.into(),
}
})?;
tokio_tungstenite::client_async_tls(endpoint, stream)
.await
.map_err(|error| GatewayClientError::NetworkConnectionFailed {
address: endpoint.to_owned(),
source: error,
})
}
@@ -44,7 +44,11 @@ pub enum GatewayClientError {
NetworkConnectionFailed { address: String, source: WsError },
#[error("Invalid URL: {0}")]
InvalidURL(String),
InvalidUrl(String),
#[cfg(not(target_arch = "wasm32"))]
#[error("resolution failed: {0}")]
ResolutionFailed(#[from] nym_http_api_client::HickoryDnsError),
#[error("No shared key was provided or obtained")]
NoSharedKeyAvailable,
@@ -31,6 +31,7 @@ pub use nym_api_requests::{
StakeSaturationResponse, UptimeResponse,
},
nym_nodes::{CachedNodesResponse, SkimmedNode},
NymNetworkDetailsResponse,
};
pub use nym_coconut_dkg_common::types::EpochId;
use nym_contracts_common::IdentityKey;
@@ -1013,6 +1014,15 @@ pub trait NymApiClientExt: ApiClient {
)
.await
}
#[instrument(level = "debug", skip(self))]
async fn get_network_details(&self) -> Result<NymNetworkDetailsResponse, NymAPIError> {
self.get_json(
&[routes::API_VERSION, routes::NETWORK, routes::DETAILS],
NO_PARAMS,
)
.await
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -65,6 +65,8 @@ pub const STAKE_SATURATION: &str = "stake-saturation";
pub const INCLUSION_CHANCE: &str = "inclusion-probability";
pub const SUBMIT_GATEWAY: &str = "submit-gateway-monitoring-results";
pub const SUBMIT_NODE: &str = "submit-node-monitoring-results";
pub const PERFORMANCE: &str = "performance";
pub const SERVICE_PROVIDERS: &str = "services";
pub const DETAILS: &str = "details";
pub const NETWORK: &str = "network";
@@ -8,81 +8,81 @@ use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum VestingContractError {
#[error("VESTING ({}): {0}", line!())]
#[error("VESTING ({l}): {0}", l = line!())]
Std(#[from] StdError),
#[error("VESTING: {0}")]
OverflowError(#[from] OverflowError),
#[error("VESTING ({}): Account does not exist - {0}", line!())]
#[error("VESTING ({l}): Account does not exist - {0}", l = line!())]
NoAccountForAddress(String),
#[error("VESTING ({}): Only admin can perform this action, {0} is not admin", line!())]
#[error("VESTING ({l}): Only admin can perform this action, {0} is not admin", l = line!())]
NotAdmin(String),
#[error("VESTING ({}): Balance not found for existing account ({0}), this is a bug", line!())]
#[error("VESTING ({l}): Balance not found for existing account ({0}), this is a bug", l = line!())]
NoBalanceForAddress(String),
#[error("VESTING ({}): Insufficient balance for address {0} -> {1}", line!())]
#[error("VESTING ({l}): Insufficient balance for address {0} -> {1}", l = line!())]
InsufficientBalance(String, u128),
#[error("VESTING ({}): Insufficient spendable balance for address {0} -> {1}", line!())]
#[error("VESTING ({l}): Insufficient spendable balance for address {0} -> {1}", l = line!())]
InsufficientSpendable(String, u128),
#[error(
"VESTING ({}):Only delegation owner can perform delegation actions, {0} is not the delegation owner"
, line!())]
"VESTING ({l}):Only delegation owner can perform delegation actions, {0} is not the delegation owner"
, l = line!())]
NotDelegate(String),
#[error("VESTING ({}): Total vesting amount is inprobably low -> {0}, this is likely an error", line!())]
#[error("VESTING ({l}): Total vesting amount is inprobably low -> {0}, this is likely an error", l = line!())]
ImprobableVestingAmount(u128),
#[error("VESTING ({}): Address {0} has already bonded a node", line!())]
#[error("VESTING ({l}): Address {0} has already bonded a node", l = line!())]
AlreadyBonded(String),
#[error("VESTING ({}): Received empty funds vector", line!())]
#[error("VESTING ({l}): Received empty funds vector", l = line!())]
EmptyFunds,
#[error("VESTING ({}): Received wrong denom: {0}, expected {1}", line!())]
#[error("VESTING ({l}): Received wrong denom: {0}, expected {1}", l = line!())]
WrongDenom(String, String),
#[error("VESTING ({}): Received multiple denoms, expected 1", line!())]
#[error("VESTING ({l}): Received multiple denoms, expected 1", l = line!())]
MultipleDenoms,
#[error("VESTING ({}): No delegations found for account {0}, mix_identity {1}", line!())]
#[error("VESTING ({l}): No delegations found for account {0}, mix_identity {1}", l = line!())]
NoSuchDelegation(Addr, NodeId),
#[error("VESTING ({}): Only mixnet contract can perform this operation, got {0}", line!())]
#[error("VESTING ({l}): Only mixnet contract can perform this operation, got {0}", l = line!())]
NotMixnetContract(Addr),
#[error("VESTING ({}): Calculation underflowed", line!())]
#[error("VESTING ({l}): Calculation underflowed", l = line!())]
Underflow,
#[error("VESTING ({}): No bond found for account {0}", line!())]
#[error("VESTING ({l}): No bond found for account {0}", l = line!())]
NoBondFound(String),
#[error("VESTING: Attempted to reduce mixnode bond pledge below zero! The current pledge is {current} and we attempted to reduce it by {decrease_by}.")]
InvalidBondPledgeReduction { current: Coin, decrease_by: Coin },
#[error("VESTING ({}): Action can only be executed by account owner -> {0}", line!())]
#[error("VESTING ({l}): Action can only be executed by account owner -> {0}", l = line!())]
NotOwner(String),
#[error("VESTING ({}): Invalid address: {0}", line!())]
#[error("VESTING ({l}): Invalid address: {0}", l = line!())]
InvalidAddress(String),
#[error("VESTING ({}): Account already exists: {0}", line!())]
#[error("VESTING ({l}): Account already exists: {0}", l = line!())]
AccountAlreadyExists(String),
#[error("VESTING ({}): Staking account already exists: {0}", line!())]
#[error("VESTING ({l}): Staking account already exists: {0}", l = line!())]
StakingAccountAlreadyExists(String),
#[error("VESTING ({}): Too few coins sent for vesting account creation, sent {sent}, need at least {need}", line!())]
#[error("VESTING ({l}): Too few coins sent for vesting account creation, sent {sent}, need at least {need}", l = line!())]
MinVestingFunds { sent: u128, need: u128 },
#[error("VESTING ({}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", line!())]
#[error("VESTING ({l}): Maximum amount of locked coins has already been pledged: {current}, cap is {cap}", l = line!())]
LockedPledgeCapReached { current: Uint128, cap: Uint128 },
#[error("VESTING: ({}: Account owned by {owner} has unpopulated vesting periods!", line!())]
#[error("VESTING: ({l}: Account owned by {owner} has unpopulated vesting periods!", l = line!())]
UnpopulatedVestingPeriods { owner: Addr },
#[error("VESTING: Vesting account associated with {0} already exists, only addresses with not existing vesting accounts can be added as staking addresses")]
+1 -1
View File
@@ -19,7 +19,7 @@ use std::error::Error;
// `SELECT total_tickets, used_tickets FROM ecash_ticketbook WHERE expiration_date >= ?`, today_date
// then for each calculate the diff total_tickets - used_tickets and multiply the result by the size of the ticket
#[async_trait]
pub trait Storage: Send + Sync {
pub trait Storage: Clone + Send + Sync {
type StorageError: Error;
async fn close(&self);
+6 -6
View File
@@ -59,12 +59,12 @@ pub enum GatewayRequestsError {
source: NymNodeRoutingAddressError,
},
#[error("received request had invalid size. (actual: {0}, but expected one of: {} (ACK), {} (REGULAR), {}, {}, {} (EXTENDED))",
PacketSize::AckPacket.size(),
PacketSize::RegularPacket.size(),
PacketSize::ExtendedPacket8.size(),
PacketSize::ExtendedPacket16.size(),
PacketSize::ExtendedPacket32.size())
#[error("received request had invalid size. (actual: {0}, but expected one of: {a} (ACK), {r} (REGULAR), {e8}, {e16}, {e32} (EXTENDED))",
a = PacketSize::AckPacket.size(),
r = PacketSize::RegularPacket.size(),
e8 = PacketSize::ExtendedPacket8.size(),
e16 = PacketSize::ExtendedPacket16.size(),
e32 = PacketSize::ExtendedPacket32.size())
]
RequestOfInvalidSize(usize),
@@ -13,7 +13,7 @@ use std::str::FromStr;
use tungstenite::Message;
// wrapper for all encrypted requests for ease of use
#[derive(Serialize, Deserialize, Debug)]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[non_exhaustive]
pub enum ClientRequest {
UpgradeKey {
+7
View File
@@ -15,6 +15,7 @@ async-trait = { workspace = true }
reqwest = { workspace = true, features = ["json"] }
http.workspace = true
url = { workspace = true }
once_cell = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
@@ -22,7 +23,13 @@ tracing = { workspace = true }
nym-bin-common = { path = "../bin-common" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
hickory-resolver = { workspace = true, features = ["dns-over-https-rustls", "webpki-roots"] }
# for request timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed
[target."cfg(target_arch = \"wasm32\")".dependencies.wasmtimer]
workspace = true
features = ["tokio"]
[dev-dependencies]
tokio = { workspace = true, features=["rt", "macros"] }
+178
View File
@@ -0,0 +1,178 @@
//! DNS resolver configuration for internal lookups.
//!
//! The resolver itself is the set combination of the google, cloudflare, and quad9 endpoints
//! supporting DoH and DoT.
//!
//! This resolver implements a fallback mechanism where, should the DNS-over-TLS resolution fail, a
//! followup resolution will be done using the hosts configured default (e.g. `/etc/resolve.conf` on
//! linux).
//!
//! Requires the `dns-over-https-rustls`, `webpki-roots` feature for the
//! `hickory-resolver` crate
#![deny(missing_docs)]
use crate::ClientBuilder;
use std::{net::SocketAddr, sync::Arc};
use hickory_resolver::lookup_ip::LookupIp;
use hickory_resolver::{
config::{LookupIpStrategy, NameServerConfigGroup, ResolverConfig, ResolverOpts},
error::ResolveError,
lookup_ip::LookupIpIntoIter,
TokioAsyncResolver,
};
use once_cell::sync::OnceCell;
use reqwest::dns::{Addrs, Name, Resolve, Resolving};
use tracing::warn;
impl ClientBuilder {
/// Override the DNS resolver implementation used by the underlying http client.
pub fn dns_resolver<R: Resolve + 'static>(mut self, resolver: Arc<R>) -> Self {
self.reqwest_client_builder = self.reqwest_client_builder.dns_resolver(resolver);
self
}
}
struct SocketAddrs {
iter: LookupIpIntoIter,
}
#[derive(Debug, thiserror::Error)]
#[error("hickory-dns resolver error: {hickory_error}")]
/// Error occurring while resolving a hostname into an IP address.
pub struct HickoryDnsError {
#[from]
hickory_error: ResolveError,
}
/// Wrapper around an `AsyncResolver`, which implements the `Resolve` trait.
#[derive(Debug, Default, Clone)]
pub struct HickoryDnsResolver {
/// Since we might not have been called in the context of a
/// Tokio Runtime in initialization, so we must delay the actual
/// construction of the resolver.
state: Arc<OnceCell<TokioAsyncResolver>>,
fallback: Arc<OnceCell<TokioAsyncResolver>>,
}
impl Resolve for HickoryDnsResolver {
fn resolve(&self, name: Name) -> Resolving {
let resolver = self.state.clone();
let fallback = self.fallback.clone();
Box::pin(async move {
let resolver = resolver.get_or_try_init(new_resolver)?;
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
let lookup = match resolver.lookup_ip(name.as_str()).await {
Ok(res) => res,
Err(e) => {
// on failure use the fall back system configured DNS resolver
warn!("primary DNS failed w/ error {e}: using system fallback");
let resolver = fallback.get_or_try_init(new_resolver_system)?;
resolver.lookup_ip(name.as_str()).await?
}
};
let addrs: Addrs = Box::new(SocketAddrs {
iter: lookup.into_iter(),
});
Ok(addrs)
})
}
}
impl Iterator for SocketAddrs {
type Item = SocketAddr;
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(|ip_addr| SocketAddr::new(ip_addr, 0))
}
}
impl HickoryDnsResolver {
/// Attempt to resolve a domain name to a set of ['IpAddr']s
pub async fn resolve_str(&self, name: &str) -> Result<LookupIp, HickoryDnsError> {
let resolver = self.state.get_or_try_init(new_resolver)?;
// try the primary DNS resolver that we set up (DoH or DoT or whatever)
let lookup = match resolver.lookup_ip(name).await {
Ok(res) => res,
Err(e) => {
// on failure use the fall back system configured DNS resolver
warn!("primary DNS failed w/ error {e}: using system fallback");
let resolver = self.fallback.get_or_try_init(new_resolver_system)?;
resolver.lookup_ip(name).await?
}
};
Ok(lookup)
}
}
/// Create a new resolver with a custom DoT based configuration. The options are overridden to look
/// up for both IPv4 and IPv6 addresses to work with "happy eyeballs" algorithm.
fn new_resolver() -> Result<TokioAsyncResolver, HickoryDnsError> {
let mut name_servers = NameServerConfigGroup::google_tls();
name_servers.merge(NameServerConfigGroup::google_https());
// name_servers.merge(NameServerConfigGroup::google_h3());
name_servers.merge(NameServerConfigGroup::quad9_tls());
name_servers.merge(NameServerConfigGroup::quad9_https());
name_servers.merge(NameServerConfigGroup::cloudflare_tls());
name_servers.merge(NameServerConfigGroup::cloudflare_https());
let config = ResolverConfig::from_parts(None, Vec::new(), name_servers);
let mut opts = ResolverOpts::default();
opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
// Would like to enable this when 0.25 stabilizes
// opts.server_ordering_strategy = ServerOrderingStrategy::RoundRobin;
Ok(TokioAsyncResolver::tokio(config, opts))
}
/// Create a new resolver with the default configuration, which reads from the system DNS config
/// (i.e. `/etc/resolve.conf` in unix). The options are overridden to look up for both IPv4 and IPv6
/// addresses to work with "happy eyeballs" algorithm.
fn new_resolver_system() -> Result<TokioAsyncResolver, HickoryDnsError> {
let (config, mut opts) = hickory_resolver::system_conf::read_system_conf()?;
opts.ip_strategy = LookupIpStrategy::Ipv4AndIpv6;
Ok(TokioAsyncResolver::tokio(config, opts))
}
#[cfg(test)]
mod test {
use super::*;
#[tokio::test]
async fn reqwest_hickory_doh() {
let resolver = HickoryDnsResolver::default();
let client = reqwest::ClientBuilder::new()
.dns_resolver(resolver.into())
.build()
.unwrap();
let resp = client
.get("http://ifconfig.me:80")
.send()
.await
.unwrap()
.bytes()
.await
.unwrap();
assert!(!resp.is_empty());
}
#[tokio::test]
async fn dns_lookup() -> Result<(), HickoryDnsError> {
let resolver = HickoryDnsResolver::default();
let domain = "ifconfig.me";
let addrs = resolver.resolve_str(domain).await?;
assert!(addrs.into_iter().next().is_some());
Ok(())
}
}
+17 -4
View File
@@ -6,17 +6,23 @@ use reqwest::header::HeaderValue;
use reqwest::{RequestBuilder, Response, StatusCode};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
use std::time::Duration;
use thiserror::Error;
use tracing::{instrument, warn};
use url::Url;
#[cfg(not(target_arch = "wasm32"))]
use std::sync::Arc;
use std::{fmt::Display, time::Duration};
pub use reqwest::IntoUrl;
mod user_agent;
pub use user_agent::UserAgent;
mod user_agent;
#[cfg(not(target_arch = "wasm32"))]
mod dns;
#[cfg(not(target_arch = "wasm32"))]
pub use dns::{HickoryDnsError, HickoryDnsResolver};
// The timeout is relatively high as we are often making requests over the mixnet, where latency is
// high and chatty protocols take a while to complete.
@@ -86,11 +92,18 @@ impl ClientBuilder {
// TODO: or should we maybe default to https?
Self::new(alt)
} else {
#[cfg(target_arch = "wasm32")]
let reqwest_client_builder = reqwest::ClientBuilder::new();
#[cfg(not(target_arch = "wasm32"))]
let reqwest_client_builder =
reqwest::ClientBuilder::new().dns_resolver(Arc::new(HickoryDnsResolver::default()));
Ok(ClientBuilder {
url: url.into_url()?,
timeout: None,
custom_user_agent: false,
reqwest_client_builder: reqwest::ClientBuilder::new(),
reqwest_client_builder,
})
}
}
+2 -2
View File
@@ -221,7 +221,7 @@ impl TaskManager {
}
}
pub(crate) async fn wait_for_graceful_shutdown(&mut self) {
pub async fn wait_for_graceful_shutdown(&mut self) {
if let Some(notify_rx) = self.notify_rx.take() {
drop(notify_rx);
}
@@ -315,7 +315,7 @@ impl TaskClient {
const MAX_NAME_LENGTH: usize = 128;
const OVERFLOW_NAME: &'static str = "reached maximum TaskClient children name depth";
const SHUTDOWN_TIMEOUT_WAITING_FOR_SIGNAL_ON_EXIT: Duration = Duration::from_secs(5);
const SHUTDOWN_TIMEOUT_WAITING_FOR_SIGNAL_ON_EXIT: Duration = Duration::from_secs(10);
fn new(
notify: watch::Receiver<()>,
+2 -2
View File
@@ -11,7 +11,7 @@ use std::borrow::Borrow;
use std::collections::{HashMap, HashSet};
use std::fmt::Display;
use std::net::IpAddr;
use tracing::{debug, warn};
use tracing::{debug, trace, warn};
pub use crate::node::{EntryDetails, RoutingNode, SupportedRoles};
pub use error::NymTopologyError;
@@ -293,7 +293,7 @@ impl NymTopology {
let has_exit_gateways = !self.rewarded_set.exit_gateways.is_empty();
let has_entry_gateways = !self.rewarded_set.entry_gateways.is_empty();
debug!(
trace!(
has_layer1 = %has_layer1,
has_layer2 = %has_layer2,
has_layer3 = %has_layer3,
@@ -163,3 +163,4 @@ Another command is `version_count` where at least one `nym-node` version is requ
```sh
./node_api_check version_count 1.1.0 1.1.1 1.1.2 1.1.3 --markdown
```
@@ -11,22 +11,38 @@ Tor community created a very helpful table called [*Good Bad ISPs*](https://comm
<div>
<Tabs items={[
<code>nym-node</code>,
<><code>nym-node</code> - dedicated server</>,
<><code>nym-node</code> - VPS</>,
<code>validator</code>,
]} defaultIndex="0">
<MyTab>
#### `nym-node`
Before we conclude the testing with exact results, these are the rough specs:
#### `nym-node` - dedicated server
If you are about to start a new Nym Node, look for a server with this specification:
| **Hardware** | **Minimum Specification** |
| :--- | ---: |
| CPU Cores | 4 |
| Memory | 8 GB RAM |
| Storage | 80 GB |
| Connectivity | IPv4, IPv6, TCP/IP, UDP |
| Bandwidth | > 1Tb |
| Port speed | 1Gbps |
| **Hardware** | **Minimum Specification** |
| :--- | ---: |
| Type of server | Dedicated or bare metal |
| CPU Cores | 4 |
| Memory | 8-16 GB RAM |
| Storage | 50 GB (preferably 80 GB) |
| Connectivity | IPv4, IPv6, TCP/IP, UDP |
| Bandwidth | Unmetered if possible, or \> 20Tb |
| Port speed | 1Gbps |
</MyTab>
<MyTab>
#### `nym-node` - VPS
If you are about to start a new Nym Node, look for a server with this specification:
| **Hardware** | **Minimum Specification** |
| :--- | ---: |
| Type of server | VPS |
| CPU Cores | 8 |
| Memory | 16-32 GB RAM |
| Storage | 50 GB (preferably 80 GB) |
| Connectivity | IPv4, IPv6, TCP/IP, UDP |
| Bandwidth | Unmetered if possible, or \> 40Tb |
| Port speed | 10Gbps |
</MyTab>
<MyTab>
#### Nyx validator
@@ -0,0 +1,37 @@
```
Thank you for forwarding me the notice you received from <COPYRIGHT_CLAIMANT> regarding <CONTENT>. I would like to assure you that I am not hosting the claimed infringing materials, and furthermore, the Digital Millennium Copyright Act's ("DMCA") safe harbors likely protect you from liability arising from this complaint. The notice is likely based upon misunderstandings about the law and about some of the software I run.
As you know, the DMCA creates four "safe harbors" for service providers to protect them from copyright liability for the acts of their users, when the ISPs fulfill certain requirements. (17 U.S.C. 512)
The DMCA's requirements vary depending on the ISP's role. You may be familiar with the "notice and takedown" provisions of section 512(c) of the DMCA; however, those do not apply when an ISP merely acts as a conduit.
Instead, the "conduit" safe harbor of section 512(a) of the DMCA has different and less burdensome eligibility requirements, as the D.C. Circuit Court of Appeals held in RIAA v. Verizon (see https://scholar.google.com/scholar_case?case=15815830240179540527) and the Eighth Circuit Court of Appeals confirmed in RIAA v. Charter (see https://scholar.google.com/scholar_case?case=11547531128234336420).
Under DMCA 512(a), service providers like you are typically protected from damages for copyright infringement claims if you also maintain "a policy that provides for termination in appropriate circumstances of subscribers and account holders of the service provider's system or network who are repeat infringers."
If you have and implement such a policy, and you otherwise qualify for the safe harbor, you should be free from fear of copyright damages.
The copyright notice you received was likely triggered by a program I run called Nym. Nym is a network software that helps users to enhance their privacy, security, and safety online. It does not host any content. Rather, it is part of a network of nodes on the Internet that simply pass packets among themselves before sending them to their destinations, just as any Internet intermediary does.
The difference is that Nym tunnels the connections such that no hop can learn both the source and destination of the packets, giving users protection from nefarious snooping on network traffic. The result is that, unlike most other Internet traffic, the final IP address that the recipient receives is not the IP address of the sender. Nym protects users against hazards such as harassment, spam, and identity theft.
Nym was conceived during the EU-funded project 'Panoramix' R&D project (Horizon 2020 programme) with the goal of protecting communication privacy by building a comprehensive mixnet infrastructure. This project has received funding from the European Unions Horizon 2020 research and innovation programme under the Grant Agreement No 653497, "Privacy and Accountability in Networks via Optimized Randomized Mix-nets (Panoramix)”. (For more on Nym, see https://www.nym.com/, For more on Panoramix, see https://panoramix.me/ ). Nym has continued this work by building a decentralized routing, authentication and payment protocol. Nym will enable developers to build their own sustainable privacy-enhanced services on top of its platform.
I hope, as an organization committed to protecting the privacy of its customers, you'll agree that this is a valuable technology.
While the Nym node that I run may appear to be the source of material that is alleged to be copyright-infringing, I do not host that material.
I do not select the material transmitted through the Nym node that I run, and I have no practical means of either identifying the source of such material or preventing its transmission. I do nothing to encourage or promote the use of the Nym network for copyright infringement.
For these reasons, I am not an infringer of copyright in any materials that are transmitted through the Nym node that I run, either directly or under a theory of contributory or vicarious liability.
Therefore, you should continue to be protected under the DMCA 512(a) safe harbor without taking any further action.
Thank you for working with me on this matter. As a loyal subscriber, I appreciate your notifying me of this issue and hope that the protections of DMCA 512 put any concerns you may have to rest.
If not, please contact me with any further questions.
Very truly yours,
Your customer, <YOUR_NAME/PSEUDONYM>
```
@@ -0,0 +1,15 @@
```
I am reaching out to introduce myself. I am about to spin up a machine with you to run what is called a “nym node” - think of it as somewhat similar to a Tor exit node. You can always recognize a nym node by our domain names: nym-exit
Nym node runners are a decentralized community all over the world. We provide secure internet traffic routing services and privacy protection to ordinary people and businesses via the Nym platform and NymVPN app.
The Nym traffic pattern is somewhat unique, as we route traffic using the “sphinx” packet format (similar to Tor 's onion encryption), which makes all traffic look uniform. I wanted to introduce myself and the machines I am running just so that this unique traffic pattern doesnt raise any flags or issues with you!
We have the highest security standards, which also means I simply relay traffic and do not know the end-destination nor its content. I can not be selective about the material transmitted through the Nym node that I run, and I have no practical means of either identifying the source of such material or preventing its transmission. More information can be found at https://nym.com.
Feel free to ask any questions.
Many thanks,
<YOUR_NAME/PSEUDONYM>
```
@@ -1 +1 @@
805_903_308
807_251_217
@@ -1 +1 @@
1_022_821
1_025_628
@@ -1 +1 @@
402_951_654
403_625_608
@@ -1,7 +1,7 @@
| **Item** | **Description** | **Amount in NYM** |
|:-------------------|:------------------------------------------------------|--------------------:|
| Total Supply | Maximum amount of NYM token in existence | 1_000_000_000 |
| Mixmining Reserve | Tokens releasing for operators rewards | 194_096_191 |
| Vesting Tokens | Tokens locked outside of cicrulation for future claim | 500 |
| Circulating Supply | Amount of unlocked tokens | 805_903_308 |
| Stake Saturation | Optimal size of node self-bond + delegation | 1_022_821 |
| Mixmining Reserve | Tokens releasing for operators rewards | 192_748_782 |
| Vesting Tokens | Tokens locked outside of cicrulation for future claim | 0 |
| Circulating Supply | Amount of unlocked tokens | 807_251_217 |
| Stake Saturation | Optimal size of node self-bond + delegation | 1_025_628 |
@@ -1 +1 @@
Thursday, January 16th 2025, 09:57:52 UTC
Monday, February 3rd 2025, 13:47:19 UTC
-14
View File
@@ -497,13 +497,6 @@ const config = {
permanent: true,
basePath: false,
},
{
source: "/operators/testing/node-api-check.html",
destination:
"/docs/operators/nodes/performance-and-testing/node-api-check",
permanent: true,
basePath: false,
},
{
source: "/operators/testing/prometheus-grafana.html",
destination:
@@ -663,13 +656,6 @@ const config = {
permanent: true,
basePath: false,
},
{
source: "/docs/operators/testing/node-api-check",
destination:
"/docs/operators/nodes/performance-and-testing/node-api-check",
permanent: true,
basePath: false,
},
{
source: "/docs/operators/nodes/proxy-configuration",
destination:
@@ -4,6 +4,8 @@ The information below is generated with [Redoc](https://redocly.com/docs/redoc)
There is also an overview of other Validator endpoints at [https://cosmos.directory/nyx](https://cosmos.directory/nyx).
<br /><br />
<RedocStandalone
specUrl="https://api.nymtech.net/swagger/swagger.yaml"
options={{
@@ -1,9 +1,11 @@
import { RedocStandalone } from 'redoc';
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at []() which is also used to generate the Swagger docs deployed at []().
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://api.sandbox.nymtech.net/swagger/swagger.yaml](https://api.sandbox.nymtech.net/swagger/swagger.yaml).
<br /><br />
<RedocStandalone
specUrl=""
specUrl="https://api.sandbox.nymtech.net/swagger/swagger.yaml"
options={{
nativeScrollbars: true,
theme: {
@@ -3,6 +3,6 @@
The Explorer API is the backend for the [Mixnet Explorer](https://explorer.nymtech.net/).
**This will soon be deprecated in favour of the [Node Status API](../ns-api).**
**This will soon be deprecated in favour of the [Node Status API](ns-api.mdx).**
The code for this service can be found [in our monorepo](https://github.com/nymtech/nym/tree/develop/explorer).
@@ -3,6 +3,8 @@ import { RedocStandalone } from 'redoc';
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://explorer.nymtech.net/api/v1/openapi.json](https://explorer.nymtech.net/api/v1/openapi.json) which is also used to generate the Swagger docs deployed at [https://explorer.nymtech.net/api/swagger/index.html](https://explorer.nymtech.net/api/swagger/index.html).
<br /><br />
<RedocStandalone
specUrl="https://sandbox-explorer.nymtech.net/api/v1/openapi.json"
options={{
@@ -2,6 +2,8 @@ import { RedocStandalone } from 'redoc';
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://sandbox-explorer.nymtech.net/api/v1/openapi.json](https://sandbox-explorer.nymtech.net/api/v1/openapi.json) which is also used to generate the Swagger docs deployed at [https://sandbox-explorer.nymtech.net/api/swagger/index.html](https://sandbox-explorer.nymtech.net/api/swagger/index.html).
<br /><br />
<RedocStandalone
specUrl="https://sandbox-explorer.nymtech.net/api/v1/openapi.json"
options={{
@@ -2,6 +2,8 @@ import { RedocStandalone } from 'redoc';
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json](https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json) which is also used to generate the Swagger docs deployed at [https://mainnet-node-status-api.nymtech.cc/swagger/](https://mainnet-node-status-api.nymtech.cc/swagger/).
<br /><br />
<RedocStandalone
specUrl="https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json"
options={{
@@ -2,6 +2,8 @@ import { RedocStandalone } from 'redoc';
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://sandbox-node-status-api.nymte.ch/api-docs/openapi.json](https://sandbox-node-status-api.nymte.ch/api-docs/openapi.json) which is also used to generate the Swagger docs deployed at [https://sandbox-node-status-api.nymte.ch/swagger/](https://sandbox-node-status-api.nymte.ch/swagger/).
<br /><br />
<RedocStandalone
specUrl="https://sandbox-node-status-api.nymte.ch/api-docs/openapi.json"
options={{
@@ -1,10 +1,27 @@
import APITable from 'components/api-table.tsx';
import { RedocStandalone } from 'redoc';
import { Callout } from 'nextra/components'
You can find the OpenAPI spec found at [https://validator.nymtech.net/api-docs/openapi.json](https://validator.nymtech.net/api-docs/openapi.json) which is also used to generate the Swagger docs deployed at [https://validator.nymtech.net/api/swagger/index.html](https://validator.nymtech.net/api/swagger/index.html).
<Callout type="info" emoji="️">
You can find the Swagger endpoints of other NymAPI instances at the following endpoints:
<APITable endpoint="https://api.nymtech.net/cosmwasm/wasm/v1/contract/n19604yflqggs9mk2z26mqygq43q2kr3n932egxx630svywd5mpxjsztfpvx/smart/eyJnZXRfY3VycmVudF9kZWFsZXJzIjogeyJsaW1pdCI6IDMwfX0=" />
There is also an overview of endpoints at [https://cosmos.directory/nyx](https://cosmos.directory/nyx).
</Callout>
> Interactive Redoc component coming soon
<br /><br />
<RedocStandalone
specUrl="https://validator.nymtech.net/api-docs/openapi.json"
options={{
nativeScrollbars: true,
theme: {
sidebar: {
backgroundColor: '#273239',
textColor: '#FCFDFE'
}
}
}}
/>
@@ -16,7 +16,7 @@ You can check that your binaries are properly compiled with:
You can check the necessary parameters for the available commands by running:
```
./nym-client <COMMAND> --help
./nym-socks5-client <COMMAND> --help
```
## Initialising a new client instance
@@ -2,6 +2,3 @@
The Rust SDK allows developers building applications in Rust to import and interact with Nym clients as they would any other dependency, instead of running the client as a separate process on their machine.
Check the [development status](./rust/development-status) page to see the various modules that make up the SDK, and the [FFI](./rust/ffi) page for Go/C++ developers.
### Generate Crate Docs
In order to generate the crate docs run `cargo doc --open` from `nym/sdk/rust/nym-sdk/`
@@ -4,7 +4,7 @@
The `nym-cli` binary can be built by running `cargo build --release` in the `nym/tools/nym-cli` directory.
## Usage
See the [commands](./nym-cli/commands.mdx) page for an overview of all command options.
See the [commands](commands.mdx) page for an overview of all command options.
## Staking on someone's behalf (for custodians)
@@ -1,5 +1,11 @@
import { Callout } from 'nextra/components'
# Cosmos Kit
<Callout type="warning">
The Typescript SDK is currently undergoing maintenance: a network upgrade elsewhere has temporarily caused a problem. These docs are likely to change after we upgrade the SDK.
</Callout>
The wonderful people of Cosmology have made some [fantastic components](https://cosmoskit.com/) that can be used with
Nym. These include:
@@ -17,7 +23,7 @@ npm create vite@latest
During the environment setup, choose React and subsequently opt for Typescript if you want your application to function smoothly following this tutorial. Next, navigate to your application directory and run the following commands:
```bash
cd < YOUR_APP >
npm i
npm i
npm run dev
```
@@ -57,7 +63,7 @@ export default defineConfig({
Your components have to be wrapped into a [ChainProvider](https://docs.cosmoskit.com/chain-provider),
in order to use the `useChain('nyx')` hook. The nyx chain is provided in the 'chain-registry' NPM package by default.
Now, go to the `src` folder and open your `App.tsx` file to replace all the code with the following, which will allow you to connect and disconnect a Ledger or Keplr wallet to Nyx:
Now, go to the `src` folder and open your `App.tsx` file to replace all the code with the following, which will allow you to connect and disconnect a Ledger or Keplr wallet to Nyx:
```ts
import "./App.css";
@@ -68,7 +74,7 @@ import { wallets as ledger } from '@cosmos-kit/ledger';
import { wallets as keplr } from '@cosmos-kit/keplr';
import { AminoMsg, makeSignDoc } from '@cosmjs/amino';
import { MsgSend } from 'cosmjs-types/cosmos/bank/v1beta1/tx';
export const getDoc = (address: string) => {
const chainId = 'nyx';
const msg: AminoMsg = {
@@ -89,28 +95,28 @@ export const getDoc = (address: string) => {
const doc = makeSignDoc([msg], fee, chainId, memo, accountNumber, sequence);
return doc
};
function MyComponent() {
const {wallet, address, connect, disconnect, getOfflineSignerAmino } =
useChain('nyx');
React.useEffect(() => {
connect();
disconnect();
}, []);
const sign = async () => {
if (!address) return
const doc = getDoc(address);
return getOfflineSignerAmino().signAmino(address, doc);
};
return (
<div>
<div>
<div>
{wallet &&
<div>
<div>Connected to {wallet?.prettyName} </div>
{wallet &&
<div>
<div>Connected to {wallet?.prettyName} </div>
<div>Address: <code>{address}</code></div>
</div>}
</div>
@@ -126,7 +132,7 @@ function MyComponent() {
</div>
);
}
export default function App() {
const assetsFixedUp = React.useMemo(() => {
const nyx = assets.find((a) => a.chain_name === 'nyx');
@@ -139,7 +145,7 @@ export default function App() {
}
return assets;
}, [assets]);
return (
<ChainProvider
chains={[chains.find((c) => c.chain_id === 'nyx')!]}
@@ -149,10 +155,10 @@ export default function App() {
preferredSignType: () => 'amino',
}}
>
<MyComponent/>
</ChainProvider>
)
}
```
@@ -1,7 +1,9 @@
import { Callout } from 'nextra/components';
# `mixFetch`
<Callout type="warning">
The Typescript SDK is currently undergoing maintenance: a network upgrade elsewhere has temporarily caused a problem. These docs are likely to change after we upgrade the SDK.
</Callout>
An easy way to secure parts or all of your web app is to replace calls to [`fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) with `mixFetch`:
MixFetch works the same as vanilla `fetch` as it's a proxied wrapper around the original function.
@@ -15,8 +17,6 @@ Sounds great, are there any catches? Well, there are a few (for now):
- For now, `mixFetch` cannot deal with concurrent requests with the same base URL.
Read [this article](https://blog.nym.com/mixfetch-like-the-fetch-api-but-via-the-mixnet-82acfd435c62) to learn more.
<Callout type="info" emoji="️">
Right now Gateways are not required to run a Secure Websocket (WSS) listener, so only a subset of nodes running in Gateway mode have configured their nodes to do so.
@@ -35,9 +35,9 @@ curl -X 'GET' \
import type { SetupMixFetchOps } from '@nymproject/mix-fetch';
const mixFetchOptions: SetupMixFetchOps = {
preferredGateway: '6Gb7ftQdKveMjPyrxDXeAtfYAX7Zg5mVZHtnRC5MmZ1B', // with WSS
preferredGateway: '23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb', // with WSS
preferredNetworkRequester:
'8rRGWy54oC8drFL9DepMegBt2DLrsqQwCoHMXt9nsnTo.2XjCPVbb4FpQ9hNRcXwb9mTzEAVVk1zf1tcch3wdtNEA@6Gb7ftQdKveMjPyrxDXeAtfYAX7Zg5mVZHtnRC5MmZ1B',
'HuNL1pFprNSKW6jdqppibXP5KNKCNJxDh7ivpYcoULN9.C62NahRTUf6kqpNtDVHXoVriQr6yyaU5LtxdgpbsGrtA@23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb',
mixFetchOverride: {
requestTimeoutMs: 60_000,
},
@@ -90,9 +90,9 @@ import { mixFetch, SetupMixFetchOps } from '@nymproject/mix-fetch-full-fat';
import React from 'react';
const mixFetchOptions: SetupMixFetchOps = {
preferredGateway: '6Gb7ftQdKveMjPyrxDXeAtfYAX7Zg5mVZHtnRC5MmZ1B', // with WSS
preferredGateway: '23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb', // with WSS
preferredNetworkRequester:
'8rRGWy54oC8drFL9DepMegBt2DLrsqQwCoHMXt9nsnTo.2XjCPVbb4FpQ9hNRcXwb9mTzEAVVk1zf1tcch3wdtNEA@6Gb7ftQdKveMjPyrxDXeAtfYAX7Zg5mVZHtnRC5MmZ1B',
'HuNL1pFprNSKW6jdqppibXP5KNKCNJxDh7ivpYcoULN9.C62NahRTUf6kqpNtDVHXoVriQr6yyaU5LtxdgpbsGrtA@23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb',
mixFetchOverride: {
requestTimeoutMs: 60_000,
},
@@ -1,7 +1,9 @@
import { Callout } from 'nextra/components'
# Mixnet Client
<Callout type="warning">
The Typescript SDK is currently undergoing maintenance: a network upgrade elsewhere has temporarily caused a problem. These docs are likely to change after we upgrade the SDK.
</Callout>
As you know by now, in order to send or receive messages over the mixnet, you'll need to use the [`SDK Client`](https://www.npmjs.com/package/@nymproject/sdk), which will allow you to create apps that can use the Nym mixnet and Coconut credentials.
This client is message based - it can only send a one-way message to another client's address.
@@ -1,7 +1,9 @@
import { Callout } from 'nextra/components'
# Nym Smart Contract Clients
<Callout type="warning">
The Typescript SDK is currently undergoing maintenance: a network upgrade elsewhere has temporarily caused a problem. These docs are likely to change after we upgrade the SDK.
</Callout>
As previously mentioned, to query or execute on any of the Nym contracts, you'll need to use one of the [`Contract Clients`](https://www.npmjs.com/package/@nymproject/contract-clients), which contains read-only query and signing clients for all of Nym's smart contracts.
##### Contract Clients list
@@ -29,14 +31,14 @@ npm create vite@latest
During the environment setup, choose React and subsequently opt for Typescript if you want your application to function smoothly following this tutorial. Next, navigate to your application directory and run the following commands:
```bash
cd < YOUR_APP >
npm i
npm i
npm run dev
```
##### Installation
Install the packages and their dependencies if you don't already have them:
```bash
npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate
npm install @nymproject/contract-clients @cosmjs/cosmwasm-stargate
```
## Query clients
@@ -143,7 +145,7 @@ import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";
import { GasPrice } from "@cosmjs/stargate";
import { settings } from "./settings";
export default function Exec() {
let signer: DirectSecp256k1HdWallet;
let signerMixnetClient: any;
@@ -153,7 +155,7 @@ export default function Exec() {
let nodeAddress: string;
let amountToSend: string;
let delegations: any;
async function ExecuteOnNyx() {
// Cosmos client
signer = await DirectSecp256k1HdWallet.fromMnemonic(settings.mnemonic, {
@@ -168,7 +170,7 @@ export default function Exec() {
);
// Save globally
cosmWasmSigningClient = cosmWasmClient;
// Nym client
const mixnetClient = new contracts.Mixnet.MixnetClient(
cosmWasmSigningClient,
@@ -177,9 +179,9 @@ export default function Exec() {
);
// Save globally
signerMixnetClient = mixnetClient;
}
// Get delegations
const getDelegations = async () => {
if (!signerMixnetClient) {
@@ -190,7 +192,7 @@ export default function Exec() {
});
delegations = delegationsObject;
};
// Make delegation
const doDelegation = async () => {
if (!signerMixnetClient) {
@@ -204,7 +206,7 @@ export default function Exec() {
);
console.log(res);
};
// Undelegate all
const doUndelegateAll = async () => {
for (const delegation of delegations.delegations) {
@@ -214,7 +216,7 @@ export default function Exec() {
);
}
};
// Sending tokens
const doSendTokens = async () => {
const memo = "test sending tokens";
@@ -227,10 +229,10 @@ export default function Exec() {
);
console.log(res);
};
ExecuteOnNyx();
setTimeout(() => getDelegations(), 1000);
return (
<div>
<p>Exec</p>
@@ -1,10 +1,15 @@
import "@interchain-ui/react/styles"
import { CosmosKit } from "../../../../components/cosmos-kit";
import { Callout } from 'nextra/components'
import FormattedCosmoskitExampleCode from '../../../../code-examples/sdk/typescript/cosmoskit-example-code.mdx';
# Cosmos Kit
import { Callout } from 'nextra/components'
<Callout type="warning">
The Typescript SDK is currently undergoing maintenance: a network upgrade elsewhere has temporarily caused a problem. These docs are likely to change after we upgrade the SDK.
</Callout>
Below is an example that uses [CosmosKit](https://cosmoskit.com/) to connect and sign a fake transaction with your [Keplr wallet](https://www.keplr.app/) or
[Ledger hardware wallet](https://www.ledger.com/) to this page:
@@ -5,6 +5,10 @@ import Box from '@mui/material/Box';
import FormattedMixFetchExampleCode from '../../../../code-examples/sdk/typescript/mixfetch-example-code.mdx';
import { Callout } from 'nextra/components'
<Callout type="warning">
The Typescript SDK is currently undergoing maintenance: a network upgrade elsewhere has temporarily caused a problem. These docs are likely to change after we upgrade the SDK.
</Callout>
<Callout type="info" emoji="️">
Right now Gateways are not required to run a Secure Websocket (WSS) listener, so only a subset of nodes running in Gateway mode have configured their nodes to do so.
@@ -3,6 +3,11 @@
import { Mixnodes } from '../../../../components/mixnodes';
import Box from '@mui/material/Box';
import FormattedExampleCode from '../../../../code-examples/sdk/typescript/mixnodes-example-code.mdx';
import { Callout } from 'nextra/components'
<Callout type="warning">
The Typescript SDK is currently undergoing maintenance: a network upgrade elsewhere has temporarily caused a problem. These docs are likely to change after we upgrade the SDK.
</Callout>
The Nym Mixnet contract keeps a directory of all mixnodes that can be used to mix traffic.
@@ -4,6 +4,9 @@ import { Traffic } from '../../../../components/traffic';
import Box from '@mui/material/Box';
import FormattedTrafficExampleCode from '../../../../code-examples/sdk/typescript/traffic-example-code.mdx';
<Callout type="warning">
The Typescript SDK is currently undergoing maintenance: a network upgrade elsewhere has temporarily caused a problem. These docs are likely to change after we upgrade the SDK.
</Callout>
Use this tool to experiment with the mixnet: send and receive messages!
@@ -10,6 +10,13 @@ import FormattedWalletConnectCode from '../../../../code-examples/sdk/typescript
import FormattedWalletSendTokensCode from '../../../../code-examples/sdk/typescript/wallet-sendTokens-code.mdx';
import FormattedWalletDelegationsCode from '../../../../code-examples/sdk/typescript/wallet-delegations-code.mdx';
import { Callout } from 'nextra/components'
<Callout type="warning">
The Typescript SDK is currently undergoing maintenance: a network upgrade elsewhere has temporarily caused a problem. These docs are likely to change after we upgrade the SDK.
</Callout>
Here's a small wallet example using testnet for you to test out!
<WalletContextProvider>
@@ -1,8 +1,8 @@
# Network Components
Core components:
* A **Mixnet**, which mixes Sphinx packet traffic so that it cannot be determined who is communicating with whom. Our mixnet is based on a modified version of the [**Loopix** design](concepts/loopix). This is made up of [Nym nodes](architecture/mixnet/nodes) runnning on servers around the world maintained by a decentralised group of Operators.
* Various [**Nym clients**](architecture/mixnet/clients) which manage sending and receiving Sphinx packets, encrypting/decrypting traffic, and providing [cover traffic](./concepts/cover-traffic) to hide 'real' traffic timing.
* A **Mixnet**, which mixes Sphinx packet traffic so that it cannot be determined who is communicating with whom. Our mixnet is based on a modified version of the [**Loopix** design](concepts/loopix). This is made up of [Nym nodes](architecture/mixnet#nodes) runnning on servers around the world maintained by a decentralised group of Operators.
* Various [**Nym clients**](architecture/mixnet#nym-clients) which manage sending and receiving Sphinx packets, encrypting/decrypting traffic, and providing [cover traffic](./concepts/cover-traffic) to hide 'real' traffic timing.
* A CosmWasm-enabled blockchain called [**Nyx**](architecture/nyx), the home of the various smart contracts used by the mixnet. A subset of Nyx Validators run [NymAPI](./architecture/nyx#nymapi) instances, taking part in also producing and verifying [zk-nym credentials](cryptography/zk-nym).
![arch_overview](/images/network/arch/overall-arch.png)
@@ -66,7 +66,7 @@ The Vesting contract has multiple functions:
* Interacting with the Mixnet using vesting (i.e. non-transferable) tokens, allowing users to delegate their unvested tokens.
### Multisig Contract
The multisig contract used by the [NymAPI Quroum](../cryptography/zk-nym/zk-nym-overview) - a subset of the Nyx Validator set taking on the additional work of generating and validating zk-nyms - to execute certain actions in the [zk-nym](./ecash.md) contract.
The multisig contract used by the [NymAPI Quroum](../cryptography/zk-nym/zk-nym-overview) - a subset of the Nyx Validator set taking on the additional work of generating and validating zk-nyms - to execute certain actions in the [zk-nym](../cryptography/zk-nym) contract.
It is essentially an instance of the [canonical](https://github.com/CosmWasm/cw-plus/tree/main/contracts) `cw3-flex-multisig` using the `cw4-group` contract, with one minor change to restrict the addresses allowed to submit proposals.
@@ -1,5 +1,7 @@
import { Steps } from 'nextra/components'
import { Steps } from 'nextra/components';
import { VarInfo } from 'components/variable-info.tsx';
import { Tabs } from 'nextra/components';
import { MyTab } from 'components/generic-tabs.tsx';
# Pre-built Binaries
@@ -18,20 +20,62 @@ If the pre-built binaries don't work or are unavailable for your system, you wil
- Download from your terminal using `curl` or `wget` tool:
```sh
# using curl
curl -L <LINK> -o <PATH>/nym-node
curl -L <LINK> -o <PATH>
# using wget
wget <LINK>
```
In case you want to download binary to your current working directory, drop `<PATH>` from the command
In case you want to download binary to your current working directory, drop `<PATH>` from the command
###### 2. Verify the binary `sha256sum` hash
###### 2. Make the binary executable
<div>
<Tabs items={[
<strong>Manual check</strong>,
<strong>One-liner</strong>,
]} defaultIndex="0">
<MyTab>
To see your binary `sha256sum` hash, run:
```sh
sha256sum <BINARY>
```
```sh
# for example
# sha256sum ./nym-wallet_1.2.15_amd64.AppImage
# or
# sha256sum ./nym-node
```
- Download [`hashes.json`](https://github.com/nymtech/nym/releases) file from the same same *Assets* drop down like your binary
- Open it with your text editor or print its content with `cat hashes.json`
- Check it if your binary `sha256sum` output is in `hashes.json` by using the `sha256sum` and searching for it or using `grep` command:
```
grep -i <SHA_HASH>
```
</MyTab>
<MyTab>
- Download [`hashes.json`](https://github.com/nymtech/nym/releases) file from the same same *Assets* drop down like your binary
- Run this command, substituting `<BINARY>` with your the one you want to check:
```sh
sha256sum <BINARY> | awk '{print $1}' | grep -qF "$(jq -r '.assets | to_entries | .[].value.sha256' hashes.json)" && echo "Hash matches an asset in the JSON file." || echo "Hash does not match any asset in the JSON file."
```
```sh
# for example
# sha256sum ./nym-node | awk '{print $1}' | grep -qF "$(jq -r '.assets | to_entries | .[].value.sha256' hashes.json)" && echo "Hash matches an asset in the JSON file." || echo "Hash does not match any asset in the JSON file."
```
</MyTab>
</Tabs>
</div>
- If your have to extract the binary (it would look like like `<BINARY>.tar.gz`) do it:
```sh
tar -xvzf <BINARY>.tar.gz
```
###### 3. Make the binary executable
- Open terminal in the same directory and run:
```sh
chmod +x <BINARY>
# for example: chmod +x nym-mixnode
chmod u+x <BINARY>
# for example: chmod u+x nym-node
```
</Steps>
+422 -13
View File
@@ -1,9 +1,13 @@
import { Callout } from 'nextra/components';
import { Tabs } from 'nextra/components';
import { MyTab } from 'components/generic-tabs.tsx';
import { RunTabs } from 'components/operators/nodes/node-run-command-tabs';
import { VarInfo } from 'components/variable-info.tsx';
import { AccordionTemplate } from 'components/accordion-template.tsx';
import { Steps } from 'nextra/components';
import AbuseResponse from 'components/operators/templates/dmca_response.md';
import OperatorIntroduction from 'components/operators/templates/provider_introduction.md';
export const TestingSteps = () => (
<div>
@@ -29,6 +33,11 @@ export const TunnelManagerCommands = () => (
</div>
);
export const LoadEndpointInfo = () => (
<div>
Developer notes behind <code>/load</code> endpoint
</div>
);
# Changelog
@@ -38,18 +47,418 @@ This page displays a full list of all the changes during our release cycle from
<VarInfo />
## `v2025.1-reeses`
## `v2025.2-hu`
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.1-reeses)
- [`nym-node`](nodes/nym-node.mdx) version `1.3.0`
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.2-hu)
- [`nym-node`](nodes/nym-node.mdx) version `1.4.0`
```
nym-node
Binary Name: nym-node
Build Timestamp: 2025-01-15T09:50:54.404428047Z
Build Version: 1.3.0
Commit SHA: c202e2d598fabda5fc16a6c1e110255857a2b1ca
Commit Date: 2025-01-15T10:27:39.000000000+01:00
Build Timestamp: 2025-02-04T09:35:42.399220545Z
Build Version: 1.4.0
Commit SHA: 4c2bf3642e8eec0d55c7753e14429d73ac2d0e99
Commit Date: 2025-02-04T10:29:48.000000000+01:00
Commit Branch: HEAD
rustc Version: 1.84.1
rustc Channel: stable
cargo Profile: release
```
### Operators Updates & Tools
<Callout type="info">
From `nym-node v1.3.0` operators can technically choose multiple functionalities for their `nym-node` binary (flagged as `--mode`).
**However, the clients are yet to be developed to be able to make a proper selection for multi-mode nodes!**
**WE ASK OPERATORS TO ASSIGN ONLY ONE FUNCTIONALITY TO `--mode` OPTION PER NODE. PLEASE SELECT ONE MODE ARGUMENT OUT OF: `mixnode` or `entry-gateway` or `exit-gateway`!**
</Callout>
- Updated maintenance guides to [backup](nodes/maintenance#backup-a-node), [restore](nodes/maintenance#restoring-a-node) and [move](nodes/maintenance#moving-a-node) a node, containing a new and important commands to backup and restore `clients.sqlite` database.
- [New explanation of `nym-node` functionalities](nodes/nym-node/setup#functionality-mode) describing how Gateways get selected in Mixnet mode and Wireguard mode.
<Callout type="warning">
Wireguard nodes route data directly to the open internet. Therefore it exposes IP of operators server (VPS) to abuse complains. Before you decide to run a node with active wireguard routing, please read our [Community Counsel pages](community-counsel/exit-gateway) containing more information and some legal content.
**Wireguard mode has no exit policy right now - we are working on the implementation.**
</Callout>
#### Legal support
We have been notified that a handful of nodes have been taken down by abuse reports. We created new pages with [legal suggestions](community-counsel/legal) and email [templates](community-counsel/templates). Here are some useful points on legal support:
- Here is the first version of a response template tailored for Nym operators.
<AccordionTemplate name="Email template: responding to DMCA take-down notices">
<AbuseResponse />
</AccordionTemplate>
- We are starting Operators Legal Clinic with Alexis Roussel, every Wednesday, 14:30 UTC (60 min) in our [Operator Legal Forum channel on Matrix](https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org&via=matrix.su4ka.icu). Come and share your findigs and questions with the rest of the operators.
- Join [Operator Legal Forum channel](https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org&via=matrix.su4ka.icu) and share as much as possible (like screen prints, provider, location etc).
- Join [Community legal counsel](https://nym.com/docs/operators/community-counsel) - our collective knowledge hub. Add your findings by opening a [Pull Request](https://nym.com/docs/operators/add-content)
- While we are working on a new list of more friendly providers, consider to move away from these provides as soon as possible:
- Servinga / VPS2day (AS39378)
- Frantech / Ponynet / BuyVM (AS53667)
- OVH SAS / OVHcloud (AS16276)
- Online S.A.S. / Scaleway (AS12876)
- Hetzner Online GmbH (AS24940)
- IONOS SE (AS8560)
- Psychz Networks (AS40676)
- 1337 Services GmbH / RDP.sh (AS210558)
- Backup your nodes to have access to `.nym` directory locally. Follow [node](nodes/maintenance#backup-a-node) and [proxy configuration](nodes/maintenance#backup-proxy-configuration) backup guides to be able to [restore your node](nodes/maintenance#restoring-a-node) later on on another machine, without losing your delegation.
- We would like to ask operators who use reverse proxy and a domain (required for Gateways) to start using a common convention starting with `nym-exit` for their nodes URL. The entire address should have this new format:
```
nym-exit.<CUSTOM>.<DOMAIN>.<TLD>
```
For example:
```
# for squads running multiple nodes a format can look like this:
nym-exit.ch-node1.mysquad.org
# or like this
nym-exit.3-jamaica.mysquad.org
# for operators having one node per location, the format can look like this:
nym-exit.brazil.mysquad.org
# or if operators decide to not have any custom, they can simply have this format:
nym-exit.mysquad.org
```
**The `NYM-EXIT` part in the beginning is what's important.**
- When registering a domain, check [Top Level Domain (TLD)](https://www.techopedia.com/definition/1348/top-level-domain-tld) terms and conditions. For example `.icu` is a no go. Having a wrong TLD may lead to your domain being taken away from you when facing a DMCA report.
- Write a message to your provider and introduce your intention to run a Nym Node on their service
<AccordionTemplate name="Email template: Introduce yourself to your VPS provider">
<OperatorIntroduction />
</AccordionTemplate>
- New page with [templates for VPS provider](community-counsel/templates)
#### Delegation Program
**If you are interested to sign up to delegation program que, message Merve on Element for the time being as we are working on CRM upgrade. We review nodes dynamically and delegate to new ones once a month.** New operators interested to join DP must follow the rules specified [here](https://delegations.explorenym.net/), run one of two latest binary version and have [Terms & Conditions](nodes/nym-node/setup#terms--conditions) accepted.
Note: Due to the token price we allow operators to have Operators cost 1000 NYM, the profit margin maximum remains 20%.
**Delegation and undelegation in January**
Delegated to all these nodes:
<AccordionTemplate name="New delegated nodes">
```
AMnDNd1Xgw7Em9R5vehP7r12ZNWUZ3jmitDHya6gpvGR
DLkKyYcA5feq43rqZdx6nJBNZvQsdX7kb2f1f6ED4cs2
26ZmTxTVBKHZg8MTKwypHkXZVJhDC7QHuv3BdsyRyTuk
5ZWdDN9pQ18vYkYYs5ZERh4P4JLtMiijscZ6FvwSfVxR
3uBgUJR393acoCRysu6SiLsximiwAMM85QFxq9WD8puC
5rXcNe2a44vXisK3uqLHCzpzvEwcnsijDMU7hg4fcYk8
nuMerN7ahqsptK8zDUZhnxMyDqePza8vWDf8k171EEs
86cNnnRxNpGdEtSX9UP1GpT6xrNvNuxFdHNkZcK7pAJg
9ngFADaYpT54F9i8iYFZLYR7SB7hrubsPArwpqe1RXQb
FQBbq1crAkCrjVBnEN85VqgZgGRMLJV65NJk8bPADdw
FUH3E3dghXjC9hdt3jeAhSsxwozsw1yzBXkoaFqkL4ci
5wF5wN7T2UuSsiUcQyL77NwdnXwAXxFttpZ1dR1nu4kR
4pVq4QSCcq4zNCnBNCfyugPjVhVyGDCBDSW5rghg8oZS
8XW1WWN1PuuAYz2TXQ9iWRekMFEJXjdEq4asMyE2qCCN
CxgjcTTAnRJzDxMbqtX73vEbWwLQmVoW8zS8RZEicgfT
7fwyXd4Cmh92d4piEPHmdFm1Wz1QCFQDf6bnDGNT3M6P
```
</AccordionTemplate>
Undelegated from 8 nodes for not meeting the rules (being offline):
<AccordionTemplate name="Undelegated nodes">
```
# Gateways
DLxLKsd3LTnfudSSmHanPaZACsh1x4MGEzfJS4jQibir
DiciBkjHovXzTDE2EFJKPNj3TGw2oQjr7HPSas2YQPiQ
ADjpymCgjFsE5m7YvnZFxLMscg85dCUUesR5g8yN3Mzz
C9dEABjtFRMD1x4ZnbqnsELGJgutT73bPCfYzqBe7sHB
# Mixnodes
2FGgY5zWq6JP1BvpnLPbedWRYYAZELFCT9rMybNhnxpo
4nKkwPSbkbtkw3yrRKQVAVdviNgFgcNskgcSGdT2Sucy
DjY3T7n6VoHGcETMnmtZKmUU7NZP6AhCs8CoRsSSbViC
FAKhiQ8nW5sAWAxks1WB8u1MAWsapToCSE3KmF9LuGRQ
```
</AccordionTemplate>
Undelegated due to high saturation:
```
5omopSZy59UyNPpx9P97vjBnN6PwPw9x5MVUx5kuNaXt
```
#### Service Grant Program v2
Aside from delegating on top of nodes, Nym runs a Service Grant Program (SGP) to support Exit Gateway operators before they will be rewarded by collecting [zk-nym tickets](../network/cryptography/zk-nym) from users subscription. Operators included in SGP are long term active community members with the highest requirements on the technical setup and upgrading pace. We are about to start a second iteration of SGP very soon (SGPv2). The final slots and locations are yet to be concluded. Priority to participate in SGPv2 will be given to the current operators in SGP. Based on the number of slots, we will then determine how many more operators can sign up.
##### Rules of SGPv2
<Callout type="info">
**We will share more info soon in the channels. The rules are not set in stone and could potentially be altered or updated in the future! Do *not* purchase new servers neither migrate your nodes just yet.**
</Callout>
As we finalising last details of *"Project Smoosh"*, where one binary - `nym-node` - can run as an `entry-gateway`, `mixnode` or `exit-gateway` in Mixnet mode as well as `entry-gateway` or `exit-gateway` in Wireguard mode, we plan to step up the game. SGPv2 grants will be higher if operators can meet new requirements.
**Minimum Specs & Requirements**
These are minimum requirements to become a part of SGPv2. We aim to have nodes on dedicated servers, with exceptions for much stronger VPS to meet the needed criteria while sharing server with other users.
<div>
<Tabs items={[
<><code>nym-node</code> - dedicated server</>,
<><code>nym-node</code> - VPS</>,
]} defaultIndex="0">
<MyTab>
#### `nym-node` - dedicated server
| **Hardware** | **Minimum Specification** |
| :--- | ---: |
| Type of server | Dedicated or bare metal |
| CPU Cores | 4 |
| Memory | 8-16 GB RAM |
| Storage | 50 GB (preferably 80 GB) |
| Connectivity | IPv4, IPv6, TCP/IP, UDP |
| Bandwidth | Unmetered if possible, or {" >"} 20Tb |
| Port speed | 1Gbps |
- Exit Gateways only
- [Terms & Conditions](nodes/nym-node/setup#terms--conditions) accepted
- Nodes are [bonded](nodes/nym-node/bonding#migrate-to-nym-node-in-mixnet-smart-contract) as `nym-node` not as a legacy node
- Timely upgrades without direct message notifications: Only Latest version, and the two preceding are accepted
- Dedicated (or bare metal) machine is to ensure that the numbers above are *dedicated* for `nym-node` operation
- Unmetered bandwidth is to ensure smooth user experience without data allowance limitation. 20Tb is a minimum for locations where unlimited bandwidth is not an option.
- 1Gbps is an expected speed of Nym network in order to meet expectations of NymVPN users
- Squads operating more than 2 nodes are expected to run larger servers and divide them for multiple nodes. We will share a how-to guide soon.
- Operators run [reverse proxy](nodes/nym-node/configuration) with landing page URL starting with `nym-exit` and [Web Secure Socket](nodes/nym-node/configuration)
- Operators must write their providers upfront that they will run a Nym Exit Gateway on their servers, using [this template](community-counsel/templates#introduction-to-server-provider)
- If a node remains offline for more than 5 days for any reason, including abuse reports, and the operator doesn't resolve it neither doesn't communicate the blockers, they will be removed from the program.
</MyTab>
<MyTab>
#### `nym-node` - VPS
| **Hardware** | **Minimum Specification** |
| :--- | ---: |
| Type of server | VPS |
| CPU Cores | 8 |
| Memory | 16-32 GB RAM |
| Storage | 50 GB (preferably 80 GB) |
| Connectivity | IPv4, IPv6, TCP/IP, UDP |
| Bandwidth | Unmetered if possible, or {" >"} 40Tb |
| Port speed | 10Gbps |
- Exit Gateways only
- [Terms & Conditions](nodes/nym-node/setup#terms--conditions) accepted
- Nodes are [bonded](nodes/nym-node/bonding#migrate-to-nym-node-in-mixnet-smart-contract) as `nym-node` not as a legacy node
- Timely upgrades without direct message notifications: Only Latest version, and the two preceding are accepted
- Stronger specs for VPS are to ensure that the `nym-node` operation will remain solid even when sharing a server with other users
- Unmetered bandwidth is to ensure smooth user experience without data allowance limitation. 40Tb is a minimum for locations where unlimited bandwidth is not an option.
- 10Gbps is to ensure that the 1Gbps expected speed of Nym network is met even when sharing a server with other users
- Operators run [reverse proxy](nodes/nym-node/configuration) with landing page URL starting with `nym-exit` and [Web Secure Socket](nodes/nym-node/configuration)
- Operators must write their providers upfront that they will run a Nym Exit Gateway on their servers, using [this template](community-counsel/templates#introduction-to-server-provider)
- If a node remains offline for more than 5 days for any reason, including abuse reports, and the operator doesn't resolve it neither doesn't communicate the blockers, they will be removed from the program.
</MyTab>
</Tabs>
</div>
**Operators interested in joining SGPv2 can start by searching for servers that meet the above criteria, where they may eventually migrate their nodes, and then share their findings by submitting a form which will be shared shortly. We will be first reaching to the current participants of Service Grant Program. Everything will be announced, sending DMs to devrel will not speed up the process!**
### Features
- [build(deps): bump criterion from `0.4.0` to `0.5.1`](https://github.com/nymtech/nym/pull/4911): Bumps [criterion](https://github.com/bheisler/criterion.rs) from `0.4.0` to `0.5.1`.
- [NS API: add mixnet scraper](https://github.com/nymtech/nym/pull/5200)
- [build(deps): bump http from `1.1.0` to `1.2.0`](https://github.com/nymtech/nym/pull/5228): Bumps [http](https://github.com/hyperium/http) from `1.1.0` to `1.2.0`.
- [`http-api-client`: deduplicate code](https://github.com/nymtech/nym/pull/5267): After adding PATCH support in [\#5260](https://github.com/nymtech/nym/pull/5260), it's now time to de-duplicate some of the code
- [Add windows to CI builds](https://github.com/nymtech/nym/pull/5269)
- [nym topology revamp](https://github.com/nymtech/nym/pull/5271): This PR changes the internals of the `NymTopology` to blur the lines between explicit mixnodes and gateways so that what used to be considered a "mixnode" could be a valid egress point of the network. `NymTopology` is no longer divided into `BTreeMap<MixLayer, Vec<mix::Node>>` and `Vec<gateway::Node>`. instead there's information about the current rewarded set (to support future VRF work) and a simple map of `HashMap<NodeId, RoutingNode>`. The new features are mostly controlled via 2 new flags/config values:
- `use_extended_topology` that tells the client to retrieve **all** network nodes rather than the ones that got assigned "active" mixnode role (or support being a gateway)
- `ignore_egress_epoch_role` that tells the client it's fine to construct egress packets to nodes that are **not** assigned entry or exit gateway role
- [Nyx Chain Watcher](https://github.com/nymtech/nym/pull/5274)
- [Include `IPINFO_API_TOKEN` in nightly CI](https://github.com/nymtech/nym/pull/5285)
- [Move tun constants to network defaults](https://github.com/nymtech/nym/pull/5286):
<AccordionTemplate name={<TestingSteps/>}>
1. **Regression Testing**:
- Verified no issues arose when running tests for the affected files.
- Tested TUN behaviour with new nym-nodes in the hu branch.
**Results**:
- **No bugs detected**.
- Tunnels are functioning as expected, with traffic routing and IP generation working seamlessly.
</AccordionTemplate>
- [Add dependabot assignes for the root cargo ecosystem](https://github.com/nymtech/nym/pull/5297)
- [build(deps): bump the patch-updates group across 1 directory with 35 updates](https://github.com/nymtech/nym/pull/5310): Bumps the `patch-updates` group with 33 updates
- [Periodically remove stale gateway messages](https://github.com/nymtech/nym/pull/5312): This PR introduces a simple task that removes gateway messages that haven't been retrieved in (by default) 24h.
<AccordionTemplate name={<TestingSteps/>}>
**Automation Script for Data Cleanup Validation**
Test Objective: Validate that the stale message cleanup mechanism in the database correctly removes records older than the configured threshold (24 hours).
Test Setup:
1. Environment:
* SQLite database
* Bash script: used to insert data.
Steps Performed:
1. Ran the insert_data.sh script to populate the database with test data:
* Recent records inserted successfully.
2. Verified the database content post-insertion: sqlite3 clients.sqlite "SELECT * FROM message_store;"
3. Confirmed that all 20 records (10 recent + 10 stale) were present.
4. Allowed the system to run for 24 hours to trigger the cleanup mechanism.
5. Queried the database again after 24 hours: sqlite3 gateway_storage.db "SELECT * FROM message_store;"
6.
Expected Result:
* All stale records (older than 24 hours) should be removed.
* Recent records should remain in the database.
Actual Result:
* Query after 24 hours showed only the 10 recent records.
* All 10 stale records were successfully removed.
</AccordionTemplate>
- [Use expect in geodata test to give error message on failure](https://github.com/nymtech/nym/pull/5314): Keep hitting this error on CI, from what I think is network hickup. But it's hard to tell form the log since the error is swallowed. Explicitly unwrap the result so we get a more detailed error output.
<AccordionTemplate name={<TestingSteps/>}>
**Quick Code Review**
**Summary**
1. **CI Workflow**: Adjusted paths to optimise build triggers, avoiding unnecessary CI runs while ensuring coverage for key directories
2. **Geolocation Test**: Improved error handling by replacing assertions with `.expect` for clearer debugging in API regression tests
**Conclusion**
Regression testing confirms everything works as intended. **Approved**.
</AccordionTemplate>
- [`CancellationToken`-based shutdowns](https://github.com/nymtech/nym/pull/5325): This PR introduces scaffolding for using `CancellationToken` and `TaskTracker` for our graceful shutdowns rather than the existing `TaskClient` and `TaskManager`.
- [Introduce `/load` endpoint for self-reported quantised Nym Node load](https://github.com/nymtech/nym/pull/5326): This PR introduces a new `/load` endpoint on a `NymNode` to return its current load. It returns the following data:
```rust
pub struct NodeLoad {
pub total: Load,
pub machine: Load,
pub network: Load,
}
```
Where `Load` is quantised into the following buckets / tiers:
```rust
pub enum Load {
Negligible, // 0 - 0.1
Low, // 0.1 - 0.3
Medium, // 0.3 - 0.6
High, // 0.6 - 0.8
VeryHigh, // 0.8 - 0.95
AtCapacity, // >= 0.95
}
```
<AccordionTemplate name={<LoadEndpointInfo/>}>
The actual values for`NodeLoad` are determined as follows:
- For network we approximate current rx/tx rates on all `eth` interfaces and scale them to the range of 0-1Gbps (for this initial iteration we assume the maximum network speed is 1Gbps which would be treated as fully saturated). So for example, if the node is sending at 0.5Gbps, it would get a `Load` of 0.5 and thus value of `Load::Medium`, 0.9Gbps would get `Load` of 0.9 and value of `Load::VeryHigh`, etc. we take the bigger value between rx and tx
- For machine load there's a bit more logic in there:
- Firstly we determine what I call a "base load" of the machine. we do this by taking the average load from the last 5min (via `getloadavg`) and dividing it by the number of CPUs in the machine. For example if the average load of the machine in the last 5min was `1.23` and it has 2 CPUs, then it's `Load` would be `Load::High` (`1.23 / 2 = 0.615`)
- However, whilst CPU utilisation is one of the most important factors, it does not tell the whole story. I tried to also take memory/swap utilisation into consideration (whilst not making it the main factor)
- Thus we calculate two additional auxiliary `Load` values, for memory usage and swap usage, i.e.: `used_memory / total_memory` and `used_swap / total_swap` respectively.
- Then we check whether either of the `MemoryLoad` or `SwapLoad` is bigger than the current base `Load` of the machine we have determined, if so, it's increased by one tier / bucket. For example, say the current machine load is `Load::Low`, but the memory usage is at 90% (`Load::VeryHigh`). that would result in the reported `Load` being bumped up to `Load::Medium` instead. The same logic applies with swap load, **however, only if the total swap > 1GB**. this is to prevent weird edge cases where the machine has hardly any swap.
- Finally, the `.total` `Load` uses the same "tier bumping" behaviour using the `.total` and `.network` loads, i.e. `if network > machine`, then `total = machine + 1`. for example if `machine` `Load` is `Load::Low`, but `network` `Load` is `Load::Medium`, then the `total` `Load` is set to `Load::Medium` instead.
</AccordionTemplate>
- [Bump the `patch-updates` group with 8 updates](https://github.com/nymtech/nym/pull/5336)
- [Bump `tempfile` from `3.14.0` to `3.15.0`](https://github.com/nymtech/nym/pull/5337)
- [Bump `ts-rs` from `10.0.0` to `10.1.0`](https://github.com/nymtech/nym/pull/5338)
- [Updated `cosmrs` and `tendermint-rpc` to their most recent versions](https://github.com/nymtech/nym/pull/5339)
- [Bump mikefarah/yq from `4.44.6` to `4.45.1`](https://github.com/nymtech/nym/pull/5342)
- [Update `indexed_db_futures`](https://github.com/nymtech/nym/pull/5347): Updates the `indexed_db_futures` dependency to stop relying on the fork.
- [Refresh wasm sdk](https://github.com/nymtech/nym/pull/5353): This PR refreshes the wasm clients to make them usable in the current network
- [Client gateway selection](https://github.com/nymtech/nym/pull/5358): Changed how gateway is selected.
- For init the target gateway can't support mixing
- For egress, unless `ignore_epoch_roles` is specified, the gateway can't be currently assigned to a mixing layer. But it can be standby or inactive
- [Exposed `NymApiClient` method for obtaining node performance history](https://github.com/nymtech/nym/pull/5360)
- [Bind to `[::]` on `nym-node` for both IP versions](https://github.com/nymtech/nym/pull/5361)
<AccordionTemplate name={<TestingSteps/>}>
**IPv4 Configuration and Migration Testing**
Testing Steps:
- Initiated and ran a nym-node with version 1.3.1 on an IPv4-only machine, then updated it to the new 'hu', 1.4.0 version.
- Initiated and ran a new nym-node with 'hu', 1.4.0 always on a machine with only IPv4.
- Initiated and ran a new nym-node with 'hu', 1.4.0 on a machine with both IPv4 and IPv6 for regression testing.
Results:
- No functional issues during version updates.
- Logged message on all versions: "no registered client for destination: ff02::2"
Status: Pass
</ AccordionTemplate>
- [Remove empty ephemeral keys](https://github.com/nymtech/nym/pull/5376)
- [Handle ecash network errors differently](https://github.com/nymtech/nym/pull/5378): For errors that could be caused by network problems, we should mark the ticket as still pending, not as bad. This won't fix the underlying problem, but should not assume anymore that the client is the culprit and penalise its bandwidth quota.
- [Make client ignore dual mode nodes by default](https://github.com/nymtech/nym/pull/5388)
<AccordionTemplate name={<TestingSteps/>}>
**Testing Steps:**
- Ensured a client is able to select a node which aside from a gateway, can also act as a mixnode
- Verified 'ignore_epoch_roles' is the default mode
- _note; this is most likely not going to be a permanent solution_
**Results:**
- Clients and topology are behaving correctly
Status: Pass
</ AccordionTemplate>
- [Update version of chain watcher and validator rewarder](https://github.com/nymtech/nym/pull/5394)
### Bugfix
- [Remove unnecessary arguments for nym-api swagger endpoints](https://github.com/nymtech/nym/pull/5272)
- [Fixed sql migration for adding default message timestamp](https://github.com/nymtech/nym/pull/5374)
- [Terminate mixnet socket listener on shutdown](https://github.com/nymtech/nym/pull/5389)
- [Correctly handle ingore epoch roles flag](https://github.com/nymtech/nym/pull/5390)
## `v2025.1-reeses`
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.1-reeses)
- [`nym-node`](nodes/nym-node.mdx) version `1.3.1`
```
nym-node
Binary Name: nym-node
Build Timestamp: 2025-01-16T11:54:17.079662337Z
Build Version: 1.3.1
Commit SHA: 5ab164d229f85bd2dd27ec6e38292c281df2f678
Commit Date: 2025-01-16T12:51:53.000000000+01:00
Commit Branch: HEAD
rustc Version: 1.84.0
rustc Channel: stable
@@ -525,7 +934,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](https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/network_tunnel_manager.sh): run the commands below to make sure
<br />
@@ -659,7 +1068,7 @@ systemctl daemon-reload && service nym-node restart
- Since `v2024.13-magura`, operators do not update their node version in the wallet. [Manual upgrading steps](nodes/maintenance/manual-upgrade.mdx) has been updated accordingly.
- CLI tool [`node_api_check.py`](nodes/performance-and-testing/node-api-check.mdx), helping operators to collect all API values about their nodes locally, is not up to date with the API changes introduced with `v2024.13-magura` release version. Please treat it as unstable before we fix it.
- CLI tool `node_api_check.py`, helping operators to collect all API values about their nodes locally, is not up to date with the API changes introduced with `v2024.13-magura` release version. Please treat it as unstable before we fix it.
#### Error Log
@@ -967,7 +1376,7 @@ Confirm that the deployment workflows work through manual testing
### Operators Updates & Tooling
<Callout type="warning" emoji="⚠️">
**Every operator has to make sure that their nodes [self-described endpoint works](nodes/performance-and-testing/node-api-check#basic-api-usage), otherwise the node will be un-routable and thus won't get any rewards!**
**Every operator has to make sure that their nodes self-described endpoint works, otherwise the node will be un-routable and thus won't get any rewards!**
</Callout>
- **New technical documentation:** All Nym documentation starts from a new entry page [nymtech.net/docs](https://nymtech.net/docs/operators/introduction). To run locally or propose collaboration, start in our [repository](https://github.com/nymtech/nym/tree/develop/documentation)
@@ -1165,7 +1574,7 @@ Some endpoints got purposely deprecated without any equivalent reimplemented sin
##### Mixnet Contract
<Callout type="warning" emoji="">
**Every operator has to make sure that their nodes [self-described endpoint works](nodes/performance-and-testing/node-api-check#basic-api-usage), otherwise the node will be un-routable and thus won't get any rewards!**
**Every operator has to make sure that their nodes self-described endpoint works, otherwise the node will be un-routable and thus won't get any rewards!**
</Callout>
###### High Level Changes
@@ -1979,7 +2388,7 @@ done
- Vesting contract functionalities have been purged, users can only remove tokens from vesting
- Migrating from `mixnode` or `gateway` smart contracts to a new unifying `nym-node` smart contract will be available soon using Nym desktop wallet, just like you are used to for bonding and node settings. After this migration all `nym-nodes` will be able to receive delegation and rewards. We will share a step by step guide once this migration will be deployed. No action needed now.
- [Nym API Check CLI](testing/node-api-check.md) is upgraded according to the latest API endpoints, output is cleaner and more concise.
- Nym API Check CLI is upgraded according to the latest API endpoints, output is cleaner and more concise.
#### Operators Tasks
@@ -2868,7 +3277,7 @@ warning: /home/alice/src/nym/nym/common/dkg/Cargo.toml: `default-features` is ig
- Flow [chart](release-cycle.md#release-flow)
- [Sandbox testnet](sandbox.md) guide: teaching Nym node operators how to run their nodes in Nym Sandbox testnet environment.
- [Terms & Conditions flag](nodes/setup.md#terms--conditions)
- [Node API Check CLI](testing/node-api-check.md)
- Node API Check CLI
- [Pruning VPS `syslog` scripts](troubleshooting/vps-isp.md#pruning-logs)
- [Black-xit: Exiting the blacklist](troubleshooting/nodes.md#my-gateway-is-blacklisted)
@@ -1,5 +1,7 @@
{
"exit-gateway": "Exit Gateway",
"legal": "Legal Counsel",
"templates": "Templates",
"jurisdictions": "Jurisdictions",
"isp-list": "ISP List",
"landing-pages": "Landing Pages",
@@ -0,0 +1,84 @@
import { Callout } from 'nextra/components';
import { Tabs } from 'nextra/components';
import { RunTabs } from 'components/operators/nodes/node-run-command-tabs';
import { VarInfo } from 'components/variable-info.tsx';
import { AccordionTemplate } from 'components/accordion-template.tsx';
import { Steps } from 'nextra/components';
import AbuseResponse from 'components/operators/templates/dmca_response.md';
import OperatorIntroduction from 'components/operators/templates/provider_introduction.md';
# Community Counsel: General Legal Considerations
**This page contains some tips & tricks to support operators running Exit Gateway (`nym-node` in mode `exit-gateway`) gathered by the operators community. Have a look on the points below to strengthen your legal protection.**
<VarInfo />
## Useful Suggestions
#### Introduce Nym Node to your provider
Write a message to your provider telling them about your intention to run a `nym-node` on their infrastructure. You can use this template for it.
<br/>
<AccordionTemplate name="Email template: Introduce yourself to your VPS provider">
<OperatorIntroduction />
</AccordionTemplate>
#### Join Operators Legal Forum
This [Matrix channel]((https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org&via=matrix.su4ka.icu)) is the best place to ask questions and share your experience with others. You can share screen prints of abuse reports and ask for support.
#### Join Operators Legal Clinic
Do you have any questions directed for lawyers? Come and chat with Nym COO Alexis Roussel, every Wednesday 14:30 UTC for 60min in our [Operator Legal Forum channel on Matrix](https://matrix.to/#/!YfoUFsJjsXbWmijbPG:nymtech.chat?via=nymtech.chat&via=matrix.org&via=matrix.su4ka.icu).
#### Use a friendly provider
Nym operators community shares their experience with different ISPs on [this page](isp-list). At the same time, consider to move away from these provides:
- Servinga / VPS2day (AS39378)
- Frantech / Ponynet / BuyVM (AS53667)
- OVH SAS / OVHcloud (AS16276)
- Online S.A.S. / Scaleway (AS12876)
- Hetzner Online GmbH (AS24940)
- IONOS SE (AS8560)
- Psychz Networks (AS40676)
- 1337 Services GmbH / RDP.sh (AS210558)
#### Backup your nodes
Your only way to restore your node is when you have an access to `.nym` directory locally. Follow [node](../nodes/maintenance#backup-a-node) and [proxy configuration](../nodes/maintenance#backup-proxy-configuration) backup guides to be able to [restore your node](../nodes/maintenance#restoring-a-node) on another machine later on, without losing your delegation.
#### Use `nym-exit` prefix on your landing page URL
We would like to ask operators to use [reverse proxy](../nodes/nym-node/configuration/proxy-configuration) with a [landing page](landing-pages). When assigning a domain please use a common convention with `nym-exit` in the beginning of the the page URL as this will create a reputation and reference. The entire address should have this new format:
```
nym-exit.<CUSTOM>.<DOMAIN>.<TLD>
```
For example:
```
# for squads running multiple nodes a format can look like this:
nym-exit.ch-node1.mysquad.org
# or like this
nym-exit.3-jamaica.mysquad.org
# for operators having one node per location, the format can look like this:
nym-exit.brazil.mysquad.org
# or if operators decide to not have any custom, they can simply have this format:
nym-exit.mysquad.org
```
**The `NYM-EXIT` part in the beginning is what's important.**
#### Chose the right TLD
When registering a domain, check [Top Level Domain (TLD)](https://www.techopedia.com/definition/1348/top-level-domain-tld) terms and conditions. For example `.icu` is a no go. Having a wrong TLD may lead to your domain being taken away from you when facing a DMCA report.
#### Respond to abuse reports
Make sure to read notifications from your account provider and if you receive an abuse report, respond to it in time. Here is a template explaining the essential legal background of Nym Node Exit Gateway. Don't forget to adjust the variables.
<br/>
<AccordionTemplate name="Email template: responding to DMCA take-down notices">
<AbuseResponse />
</AccordionTemplate>
#### Help us to improve these pages
Add your findings by opening a [Pull Request](add-content).
@@ -0,0 +1,23 @@
import { Callout } from 'nextra/components';
import { Tabs } from 'nextra/components';
import { RunTabs } from 'components/operators/nodes/node-run-command-tabs';
import { VarInfo } from 'components/variable-info.tsx';
import { AccordionTemplate } from 'components/accordion-template.tsx';
import { Steps } from 'nextra/components';
import AbuseResponse from 'components/operators/templates/dmca_response.md';
import OperatorIntroduction from 'components/operators/templates/provider_introduction.md';
# Templates for Providers
Communication with your provider is an essential part of legal defense, as more often than not, terminating a server is based on the lack of knowledge about Nym Node and it's legal bases. Use the following templates to introduce your intention to run a Nym Node or as a response to abuse report.
<VarInfo/ >
## Email Templates
### Introduction to server provider
<OperatorIntroduction />
### DMCA abuse report response
<AbuseResponse />
+1 -3
View File
@@ -35,9 +35,7 @@ Accepting T&Cs is done via a flag `--accept-operator-terms-and-conditions` added
This is a summary of all needed steps for node operators to setup and configure a `nym-node` and register it to Nym Network:
1. **Start with [Preliminary Steps](nodes/preliminary-steps.mdx), preparing:**
- [VPS](nodes/preliminary-steps/vps-setup.mdx)
- [Nym wallet](nodes/preliminary-steps/wallet-preparation)
1. **Start with [Preliminary Steps](nodes/preliminary-steps.mdx), preparing [VPS](nodes/preliminary-steps/vps-setup.mdx) and [Nym wallet](nodes/preliminary-steps/wallet-preparation)**
2. **[Setup](nodes/nym-node/setup.mdx) the node**
@@ -101,66 +101,178 @@ For more information about your node's port configuration, check the [port refer
Anything can happen to the server on which your node is running. To back up your `nym-node` keys and configuration protects the operators against the negative impact of unexpected events. To restart your node on another server, two essential pieces are needed:
1. Node keys to initialise the same node on a new VPS
2. Access to the bonding Nym account (wallet seeds) to edit the IP on smart contract
1. **Node keys to initialise the same node on a new VPS**
2. **Access to the bonding Nyx account (wallet seeds) to update host on the Mixnet smart contract**
Assuming that everyone access their wallets from local machine and does *not* store their seeds on VPS, point 2. should be a given.
To backup your `nym-node` keys and configuration in the easiest way possible, copy the entire config directory `.nym` from your VPS to your local desktop, using a special copy command `scp`:
Optionally, it can be useful to have a backup your configuration directories to setup [reverse proxy and WSS](nym-node/configuration/proxy-configuration.mdx) quickly as well.
Assuming that everyone access their wallets from local machine and does *not* store their seeds on VPS, point \#2 should be a given.
<Callout type="warning" emoji="⚠️">
Never store your mnemonic seed anywhere online nor do *not* share it with anyone!
</Callout>
To backup your `nym-node` keys and configuration in the easiest way possible, copy the entire config directory `.nym` from your VPS to your local machine, using a special copy command `scp`:
<Steps>
###### 1. Create a directory where you want to store your backup
###### 1. Create a local directory where you want to store your backup
```sh
mkdir -pv <PATH_TO_TARGET_DIRECTORY>
```
###### 2. Copy configuration folder `.nym` from your VPS to your newly created backup directory
```sh
# for example
# mkdir -pv $HOME/backup/my_nym_node/.nym
```
###### 2. Backup `clients.sqlite` database
- Install `sqlite3`
```sh
apt install sqlite3
```
- Open sqlite CLI shell inside `clients.sqlite` database
```sh
sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite
```
- Create backup called `clients_backup.sqlite`
```sh
.backup .nym/nym-nodes/default-nym-node/data/clients_backup.sqlite
```
- Exit sqlite CLI shell
```sh
.exit
```
###### 3. Copy configuration folder `.nym` from your VPS to your newly created backup directory
```sh
scp -r <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:~/.nym/nym-nodes/<ID> <PATH_TO_TARGET_DIRECTORY>
```
###### 4. Verify the success of the backup
The `scp` command should print logs, an operator can see directly whether it was successful or if it encountered any error. However, double check that all your needed configuration is in the backup target directory.
</Steps>
Now you have everything needed to restore your `nym-node` on another server. If you are in a need of doing so, follow the steps in [*Restoring a node*](#restoring-a-node) chapter below.
### Backup proxy configuration
If you run your node behind a [reverse proxy and WSS](nym-node/configuration/proxy-configuration.mdx) (suggested for all Exit Gateways), you may find it useful to backup configuration directories for that as well. It's done by following the same logic, using `scp` command for remote copy:
<Callout>
Given that servers hosting a `nym-node` are expected to be a single purpose machines, we assume in this guide, that there are no other `<HOSTNAME>` sub-directories in `/var/www` and `/etc/nginx/sites-available` then the ones relevant to `nym-node`. In case you prefer to only backup configuration for a particular hostname, just append your `<HOSTNAME>` to the path in the commands below.
</ Callout>
<Steps>
###### 1. Create local sub-directories where you want to store your backup
```sh
mkdir -pv <PATH_TO_TARGET_DIRECTORY>
mkdir -pv <PATH_TO_TARGET_DIRECTORY>
```
```sh
# for example
# mkdir -pv $HOME/backup/my_nym_node/var/www
# mkdir -pv $HOME/backup/my_nym_node/etc/nginx/sites-available
```
###### 1. Backup `/var/www` directory
```sh
scp -r <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:/var/www <PATH_TO_TARGET_DIRECTORY>
```
###### 2. Backup `/etc/nginx/sites-available` directory
```sh
scp -r <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:/etc/nginx/sites-available <PATH_TO_TARGET_DIRECTORY>
```
###### 3. Verify the success of the backup
The `scp` command should print logs, an operator can see directly whether it was successful or if it encountered any error. However, double check that all your needed configuration is in the backup target directory.
</Steps>
<Callout type="info">
To copy files from the root directory (paths starting with `/`), you need root permissions on the remote machine. If your server is not running as the root user, the `scp` command won't work directly, as using the `sudo` prefix would apply to your local machine instead of the remote server.
To resolve this, you need to SSH into the remote server first, and there use the `sudo cp -r <SOURCE_PATH> <TARGET_PATH>` command to copy the necessary directories to a location outside the root directory. After that, you can run `scp` with the correct source path to transfer the files.
</Callout>
## Restoring a node
In case your VPS shut down and you have a [backup](#backup-a-node) of your node keys and access to your bonding wallet, you can easily restore your node on another server without losing your delegation.
In case your VPS was terminated and you have a [backup](#backup-a-node) of your node keys and access to your [bonding](nym-node/bonding) wallet, you can easily restore your node on another server without losing your delegation.
<Steps>
###### 1. Prepare new VPS
- On VPS: Do all [preliminary steps](preliminary-steps.mdx) needed to run a `nym-node`.
SSH into your new VPS and start with:
- On VPS: Create a `.nym/nym-nodes` configuration folder:
- Do all [preliminary steps](preliminary-steps.mdx) needed to run a `nym-node`.
- Create a `.nym/nym-nodes` configuration folder:
```sh
mkdir -pv ~/.nym/nym-nodes
```
###### 2. Restore your node configuration
From machine where your node is backed up (usually local desktop): Copy the folder with your node keys and configuration to the newly created folder on your VPS using `scp` command. Make sure to grab the entire `nym-node` configuration folder, which is called after your local `nym-node` identifier (`<ID>`), the `-r` (recursive) flag will take care of all sub-directories and their content:
Copy the folder with your node configuration and keys from your local machine to the newly created folder on your VPS using `scp` command. Make sure to grab the entire `nym-node` configuration folder, which is called after your local `nym-node` identifier (`<ID>`), the `-r` (recursive) flag will take care of all sub-directories and their content:
```sh
scp -r <PATH_TO_LOCAL_NODE_CONFIGURATION_FOLDER> <VPS_USER_NAME>@<VPS_HOST_ADDRESS>:~/.nym/nym-nodes/
```
```sh
# for example:
scp -r $HOME/backup/my_nym_node/.nym/nym-nodes/default-nym-node root@my-nym-node:~/.nym/nym-nodes/
```
###### 3. Verify the success of the backup
The `scp` command should print logs, an operator can see directly whether it was successful or if it encountered any error. However, double check that all your needed configuration is in the target directory `.nym/nym-nodes` on your VPS.
###### 3. Configure your node on the new VPS
###### 4. Configure your node on the new VPS
* Edit `~/.nym/nym-nodes/<ID>/config/config.toml` config with the new listening address IP - it's the one under the header `[host]`, called `public_ips = [<PUBLIC_IPS>,]` and add your new location (field `location = <LOCATION>`, formats like: 'Jamaica', or two-letter alpha2 (e.g. 'JM'), three-letter alpha3 (e.g. 'JAM') or three-digit numeric-3 (e.g. '388') can be provided). You can see your IP by running a command `echo "$(curl -4 https://ifconfig.me)"`.
* SSH to your VPS
* Run a command `echo "$(curl -4 https://ifconfig.me)"` to see your public IPv4
* Edit config file located at `~/.nym/nym-nodes/<ID>/config/config.toml` correct IP - it's the field under the header `[host]`, called `public_ips = [<PUBLIC_IPS>,]`
* Add your new location field `location = <LOCATION>`, (formats like: 'Jamaica', or two-letter alpha2 (e.g. 'JM'), three-letter alpha3 (e.g. 'JAM') or three-digit numeric-3 (e.g. '388') can be provided).
* Try to run the node and see if everything works.
###### 5. Restore `clients.sqlite` database
- Install `sqlite3`
```sh
apt install sqlite3
```
- Open sqlite CLI shell inside `clients.sqlite` database
```sh
sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite
```
- Restore your backup called `clients_backup.sqlite`
```sh
.restore .nym/nym-nodes/default-nym-node/data/clients_backup.sqlite
```
- Exit sqlite CLI shell
```sh
.exit
```
- Check integrity
```sh
sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite "PRAGMA integrity_check;"
```
The result should return `ok` if the database is intact.
###### 6. Test & validate your setup
* Dry run the node and see if everything works.
* Setup the [systemd](nym-node/configuration.mdx#systemd) automation (don't forget to add the [terms and conditions flag](nym-node/setup.mdx#terms--conditions)) to `ExecStart` command, reload the daemon and run the service.
###### 4. Change the node smart contract info via the wallet interface
@@ -169,6 +281,63 @@ Open Nym Wallet, go to *Bonding*, open *Settings* and change *Host* value to the
</Steps>
Everything should work now. If not, have a look through the `config.toml` file and ensure that there are no details from the past VPS, things like WSS port, landing page asset etc. If you had a configuration with reverse proxy and WSS, continue to set that up as well. If you had it backed up, you can follow the steps to [restore proxy configuration](#restore-proxy-configuration) below, or you can start to [configure proxy from scratch](nym-node/configuration/proxy-configuration.mdx).
### Restoring proxy configuration
If operators moving to a new server had their [proxy configuration backed up](#backup-proxy-configuration), it's possible to simply restore it, following these steps:
<Steps>
###### 1. Copy your backed up directory `/var/www` for landing page to the server
- Start setting up reverse proxy and WSS following [this guide](nym-node/configuration/proxy-configuration.mdx) but instead of creating a new `/var/www/<HOSTNAME>` directory, simply copy there the one from the old server, using `scp` command
```sh
scp -r <PATH_TO_LOCAL_WWW_CONFIGURATION_BACKUP> <VPS_USER_NAME>@<VPS_HOST_ADDRESS>:/var/www/
```
```sh
# for example:
scp -r $HOME/backup/my_nym_node/var/www root@my-nym-node:/var/www
```
###### 2. Make sure that domain is correctly setup
As you see in the guide you use a domain (same like your `<HOSTNAME>`), make sure you have it registered as and redirected to the new IP by logging into your DNS provider and editing the dashboard of the domain.
###### 3. Proceed with all needed `node` configuration
- In the guide follow the steps to [configure your node reverse proxy](nym-node/configuration/proxy-configuration#reverse-proxy-configuration), like installing Nginx, opening the needed ports etc
###### 4. Restore Nginx config
- When you arrive to the point to configure your [`/etc/nginx/sites-available/<HOSTNAME>`](nym-node/configuration/proxy-configuration#2-add-your-endpoint-configuration-to-nginx-by-creating-a-config-file), use again `scp` instead of creating a new one
```sh
scp -r <PATH_TO_LOCAL_NGINX_CONFIGURATION_BACKUP> <VPS_USER_NAME>@<VPS_HOST_ADDRESS>:/etc/nginx/sites-available
```
```sh
# for example:
scp -r $HOME/backup/my_nym_node/etc/nginx/sites-available root@my-nym-node:/etc/nginx/sites-available
```
###### 5. Edit Nginx configuration file with correct hostname
- Open the Nginx config file on your server and see if a domain name needs to be changed to new one. To open the file, use for example nano text editor:
```sh
nano /etc/nginx/sites-available/<HOSTNAME>
```
###### 6. Continue with the Nginx setup
- Go back to the [proxy setup guide](nym-node/configuration/proxy-configuration#3-activate-and-test-nginx-configuration) and continue from the point [\#3](nym-node/configuration/proxy-configuration#3-activate-and-test-nginx-configuration)
###### 7. Edit WSS configuration with the correct hostname
- Open the WSS config file on your server and see if a domain name needs to be changed to new one. To open the file, use for example nano text editor:
```sh
nano /etc/nginx/sites-available/wss-config-nym
```
###### 8. Finish and test the setup
Continue with the [activating and testing](nym-node/configuration/proxy-configuration#2-activate-and-test-nginx-wss-configuration) your reverse proxy and WSS until the end of the guide.
</ Steps>
<Callout type="info">
To copy files to the root directory (paths starting with `/`), you need root permissions on the remote machine. If your server is not running as the root user, the `scp` command won't work directly, as using the `sudo` prefix would apply to your local machine instead of the remote server.
To resolve this, you can SSH into the remote server and create the configuration files manually using the prefix `sudo` and then copy their content from your local backup.
</Callout>
## Moving a node
In case of a need to move a Nym Node from one machine to another and avoiding to lose the delegation, here are few steps how to do it.
@@ -189,21 +358,71 @@ mkdir ~/.nym
#in case no `nym-node` was initialized previously
mkdir ~/.nym/nym-nodes
```
###### 2. Backup `clients.sqlite` database
###### 2. Move the node data and keys to the new machine
- Stop your node
```sh
service nym-node stop
```
- Install `sqlite3`
```sh
apt install sqlite3
```
- Open sqlite CLI shell inside `clients.sqlite` database
```sh
sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite
```
- Create backup called `clients_backup.sqlite`
```sh
.backup .nym/nym-nodes/default-nym-node/data/clients_backup.sqlite
```
- Exit sqlite CLI shell
```sh
.exit
```
###### 3. Move the node data and keys to the new machine
* Open your **local terminal** (as that one's ssh key is authorized in both of the VPS) and run:
```sh
scp -r -3 <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:~/.nym/nym-nodes <TARGET_USER_NAME>@<TARGET_HOST_ADDRESS>:~/.nym/nym-nodes/
```
###### 3. Open new/target VPS terminal and configure the node
###### 4. Open new/target VPS terminal and configure the node
* Edit `~/.nym/nym-nodes/<ID>/config/config.toml` config with the new listening address IP - it's the one under the header `[host]`, called `public_ips = [<PUBLIC_IPS>,]` and add your new location (field `location = <LOCATION>`, formats like: 'Jamaica', or two-letter alpha2 (e.g. 'JM'), three-letter alpha3 (e.g. 'JAM') or three-digit numeric-3 (e.g. '388') can be provided). You can see your IP by running a command `echo "$(curl -4 https://ifconfig.me)"`.
* Try to run the node and see if everything works.
* Setup the [systemd](nym-node/configuration.mdx#systemd) automation (don't forget to add the [terms and conditions flag](nym-node/setup.mdx#terms--conditions)) to `ExecStart` command, reload the daemon and run the service. If you want to use the exact same service config file, you can also copy it from one VPS to another following the same logic by opening your **local terminal** (as that one's ssh key is authorized in both of the VPS) and running:
###### 5. Restore `clients.sqlite` database
- Install `sqlite3`
```sh
apt install sqlite3
```
- Open sqlite CLI shell inside `clients.sqlite` database
```sh
sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite
```
- Restore your backup called `clients_backup.sqlite`
```sh
.restore .nym/nym-nodes/default-nym-node/data/clients_backup.sqlite
```
- Exit sqlite CLI shell
```sh
.exit
```
- Check integrity
```sh
sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite "PRAGMA integrity_check;"
```
The result should return `ok` if the database is intact.
###### 6. Test & validate your setup
* Dry run the node and see if everything works.
* Setup the [systemd](nym-node/configuration.mdx#systemd) automation (don't forget to add the [terms and conditions flag](nym-node/setup.mdx#terms--conditions)) to `ExecStart` command, reload the daemon and run the service.
* If you want to use the exact same service config file, you can also copy it from one VPS to another following the same logic by opening your **local terminal** (as that one's ssh key is authorized in both of the VPS) and running:
```sh
scp -r -3 <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:/etc/systemd/system/nym-node.service <TARGET_USER_NAME>@<TARGET_HOST_ADDRESS>:/etc/systemd/system/nym-node.service
```
@@ -28,7 +28,7 @@ Do not bond your node to the API if the previous steps weren't finished. Bad con
Any new bonded node will provide only the bare minimum information: host, identity key and optionally custom port of its HTTP API - we highly recommend to set that one up to `8080`. Everything else will be discovered via the self-described API for maximum flexibility. This also includes the sphinx key, meaning if the API is not exposed, the node will be unable to route any traffic.
**Every operator has to make sure that their nodes [self-described endpoint works](nodes/performance-and-testing/node-api-check#basic-api-usage), otherwise the node will be un-routable and thus won't get any rewards!**
**Every operator has to make sure that their nodes [self-described endpoint works](../performance-and-testing#functionality--performance-check), otherwise the node will be un-routable and thus won't get any rewards!**
<Callout type="warning" emoji="⚠️">
**Reveal your menominc phrase only in areas out of surveillance of other people and never share it with others. Nym team will never ask you for your mnemonic phrase - in case you were asked by someone it's a scam, do *not* reply to it!**
@@ -36,7 +36,7 @@ Any new bonded node will provide only the bare minimum information: host, identi
## Bond via the Desktop wallet (recommended)
You can bond your `nym-node` via the Desktop wallet.
You can bond your `nym-node` via the Desktop wallet. Nym wallet, just like any Nym binary can be downloaded, verified and made executable following [these simple steps](../../binaries/pre-built-binaries#setup-binaries).
<Steps>
@@ -120,18 +120,13 @@ From `nym-wallet` version `1.2.15` onward the application allows and prompts ope
<Steps>
###### 1. Download the latest wallet from [the release page](https://github.com/nymtech/nym/releases)
###### 1. Download the latest wallet from [the release page](https://github.com/nymtech/nym/releases), verify and make executable
###### 2. Verify the binary and extract it if needed
Nym wallet, just like any Nym binary can be downloaded, verified and made executable following [these simple steps](../../binaries/pre-built-binaries#setup-binaries).
- Download [`hashes.json`](https://github.com/nymtech/nym/releases/download/nym-wallet-v1.2.15/hashes.json)
- Open it with your text editor or print it's content with `cat hashes.json`
- Run `sha256sum <WALLET_BINARY>` for example `sha256sum ./nym-wallet_1.2.15_amd64.AppImage`
- If your have to extract it (like `.tar.gz`) do it
###### 2. Open the wallet and sign in
###### 3. Open the wallet and sign in
###### 4. Migrate!
###### 3. Migrate!
- Go to Bonding and you will be prompted with such message:
@@ -161,9 +156,9 @@ If you want to bond your Mix Node via the CLI, then check out the [relevant sect
This is not relevant for operators running exclusively `mixnode` functionality. For any type of gateway functionality this is a preparation requirement for the upcoming [ticket rewarding](../../tokenomics/mixnet-rewards#roadmap).
</ Callout>
Every `nym-node` client contains a mnemonic of a Nyx account, generated with node initialisation (first `run` command creating all configuration and data files). This mnemonic is located in `$HOME/.nym/nym-nodes/<ID>/data/cosmos_mnemonic`. **This is *not* the same account as the one used for bondng!**
Every `nym-node` client contains a mnemonic of a Nyx account, generated with node initialisation (first `run` command creating all configuration and data files). This mnemonic is located in `$HOME/.nym/nym-nodes/<ID>/data/cosmos_mnemonic`. **This is *not* the same account as the one used for bonding!**
This client account will be used for the process of redemption of tickets collected by nodes running as `entry-gateway` and `exit-gateway` as the redemption requires gateway to create a multisig proposal on the chain for which the client (node) needs to pay transaction fee.
This client account will be used for the process of redemption of [zk-nym tickets](../../../network/cryptography/zk-nym) collected by nodes running as `entry-gateway` and `exit-gateway` as the redemption requires gateway to create a multisig proposal on the chain for which the client (node) needs to pay transaction fee.
Giving the low transaction cost on Cosmos, funding your client Nyx account with 25 NYM tokens should be more than enough. To do so, follow these steps:
@@ -20,12 +20,12 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](.
```sh
nym-node
Binary Name: nym-node
Build Timestamp: 2025-01-15T09:50:54.404428047Z
Build Version: 1.3.0
Commit SHA: c202e2d598fabda5fc16a6c1e110255857a2b1ca
Commit Date: 2025-01-15T10:27:39.000000000+01:00
Build Timestamp: 2025-02-04T09:35:42.399220545Z
Build Version: 1.4.0
Commit SHA: 4c2bf3642e8eec0d55c7753e14429d73ac2d0e99
Commit Date: 2025-02-04T10:29:48.000000000+01:00
Commit Branch: HEAD
rustc Version: 1.84.0
rustc Version: 1.84.1
rustc Channel: stable
cargo Profile: release
```
@@ -39,21 +39,35 @@ cargo Profile: release
<Callout type="info">
From `nym-node v1.3.0` operators can choose multiple functionalities for their `nym-node` binary (flagged as `--mode`).
**However, the clients are yet to be developed to be able to make a proper selection for multi-mode nodes and therefore we ask operators to sign only one functionality to `--mode` option at a time. Please chose out of: `mixnode` or `entry-gateway` or `exit-gateway`.**
We are developing a design where operators can enable multiple modes, and let the Nym API to position the node according the network's needs in the beginning of each epoch.
**However, the clients are yet to be developed to be able to make a proper selection for multi-mode nodes and therefore we ask operators to assign only one functionality to `--mode` option at a time. Please chose out of: `mixnode` or `entry-gateway` or `exit-gateway`. Chosing multiple at once will make your node non-routable!**
</Callout>
Nym Node has three functionalities in the network: `entry-gateway`, `mixnode` and `exit-gateway`.
### Mixnet Routing
- **Entry Gateway (`--mode entry-gateway`)**: A the node to which local client connects. It checks the bandwidth allowance, using [zk-nyms](../../../network/cryptography/zk-nym) and either sends [Sphinx packets](../../../network/cryptography/sphinx) through the mixnet or directly to Exit Gateway in case of dVPN (2-hop) routing. This node also recieves replies and sends them back to users local client.
***Mixnet mode (5-hop) is the full anonymising option of NymVPN. Read more about the Mixnet architecture [here](http://localhost:3000/docs/network/architecture)***
- **Mixnode (`--mode mixnode`)**: Nodes organized in three layers, randmoly selected every epoch (60 minutes), mixing Sphinx packets, adding a slight latency to defend users agains time correlation attacks and sending them further to the next layer or to the Exit Gateway
Nym Node has three functionalities in the Mixnet: `entry-gateway`, `mixnode` and `exit-gateway`. These are selected with a flag `--mode <MODE>` alongside `nym-node` command `run` .
- **Entry Gateway (`--mode entry-gateway`)**: A node to which clients connect. It checks the bandwidth allowance, using [zk-nyms](../../../network/cryptography/zk-nym) and either sends [Sphinx packets](../../../network/cryptography/sphinx) through the mixnet or directly to Exit Gateway in case of dVPN (2-hop) routing. This node also receives replies and sends them back to users local client.
- **Mixnode (`--mode mixnode`)**: Nodes organized in three layers, randomly selected every epoch (60 minutes), mixing Sphinx packets, adding a slight latency to defend users agains time correlation attacks and sending them further to the next layer or to the Exit Gateway
- **Exit Gateway (`--mode exit-gateway`)**: The final node in the mixnet. It puts all packets together and using inbuilt Network requester and IP packet router, it sends traffic to the open internet. This node also recieves replies and sends them back to the user client.
<Callout type="warning">
Exit Gateway is the only node routing data directly to the open internet. Therefore it exposes IP of operators server (VPS) to abuse complains. Before you decide to run an Exit Gateway, please read our [Community Counsel pages](../../community-counsel/exit-gateway) containing more information and some legal content.
Exit Gateway is the only mode routing data directly to the open internet. Therefore it exposes IP of operators server (VPS) to abuse complains. Before you decide to run an Exit Gateway, please read our [Community Counsel pages](../../community-counsel/exit-gateway) containing more information and some legal content.
</Callout>
### dVPN Routing
***dVPN routing (2-hop) is the Fast option of NymVPN. It runs a wireguard tunnel inside another wireguard tunnel. dVPN uses 2 Gateway layers (entry and exit) and no Mixnode layers. If an operator announces and [correctly configure](configuration#connectivity-test-and-configuration) their node to route wireguard, the node can be chosen as entry or exit by any client at any time.***
To activate wireguard routing, operators need to add `--wireguard-enabled true` alongside `nym-node` command `run`.
<Callout type="warning">
Wireguard nodes route data directly to the open internet. Therefore it exposes IP of operators server (VPS) to abuse complains. Before you decide to run a node with active wireguard routing, please read our [Community Counsel pages](../../community-counsel/exit-gateway) containing more information and some legal content.
**Wireguard mode has no exit policy right now - we are working on the implementation.**
</Callout>
Everything essential for each mode exists on `nym-node` by default. For instance, if you run a Mixnode, you'll find that a NR (Network Requester) and IPR (IP Packet Router) addresses exist, but they will be ignored in `mixnode` mode.
@@ -178,7 +192,7 @@ http://<IPv4_ADDRESS>/api/v1/swagger/#/
https://<HOSTNAME>/api/v1/swagger/#/
```
For example to determine which mode your node is running, you can check the `:8080/api/v1/roles` endpoint. For example:
For example to determine which mode your node is running, you can check the `:8080/api/v1/roles` endpoint:
```sh
# sustitude <IPv4_ADDRESS> or <HOSTNAME> with the one corresponding to your node
# for http
@@ -8,6 +8,34 @@ As developers we need to be constantly improving the software. Operators have as
Therefore [monitoring](#monitoring) and [testing](#testing) are essential pieces of our common work. We call out all Nym operators to join the efforts!
## Functionality & Performance Check
If you want to just quickly check your nodes performance, connectivity and much more, visit [harbourmaster.nymtech.net](https://harbourmaster.nymtech.net/).
For more information about available endpoints and their status, you can refer to:
```sh
# sustitude <IPv4_ADDRESS> or <HOSTNAME> with the one corresponding to your node
# for http
http://<IPv4_ADDRESS>:8080/api/v1/swagger/#/
# or
http://<IPv4_ADDRESS>/api/v1/swagger/#/
# for reversed proxy/WSS
https://<HOSTNAME>/api/v1/swagger/#/
```
For example to determine which mode your node is running, you can check the `:8080/api/v1/roles` endpoint:
```sh
# sustitude <IPv4_ADDRESS> or <HOSTNAME> with the one corresponding to your node
# for http
http://<IPv4_ADDRESS>:8080/api/v1/roles
# or
http://<IPv4_ADDRESS>/api/v1/roles
# for reversed proxy/WSS
https://<HOSTNAME>/api/v1/roles
```
## Monitoring
There are multiple ways to monitor performance of nodes and the machines on which they run. For the purpose of maximal privacy and decentralisation of the data - preventing Nym Mixnet from any global adversary takeover - we created these pages as a source of mutual empowerment, a place where operators can share and learn new skills to **setup metrics monitors on their own infrastructure**.
@@ -1,5 +1,4 @@
{
"gateway-probe": "Gateway Probe",
"node-api-check": "Node API Check",
"prometheus-grafana": "Prometheus & Grafana"
}
@@ -146,7 +146,7 @@ Besides these values, the API is also looking whether the node is bonded in Mixn
**The node score is calculated with this formula:**
<Callout type="info" emoji="📌">
> **active_set_selection_probability = total_stake \* ( config_score \* node_performance ) ^ 20 )**
> **active_set_selection_probability = total_stake \* (( config_score \* node_performance ) ^ 20 )**
</Callout>
Note that the score helps prioritize some nodes over others. If all available nodes have the same score, then the selection is done uniformly at random. By raising the config and performance components to 20, values of these parameters that are below one incur a heavy penalization for the nodes selection chances.
@@ -113,8 +113,6 @@ There are a few community explorers as well.
Enter your **identity key** to find your node. Check the contents of the `Mixnode stats` and `Routing score` sections.
You can run [Node API Check CLI](../testing/node-api-check.mdx) to query all API endpoints of your node at once.
[Here](https://github.com/cosmos/chain-registry/blob/master/nyx/chain.json#L158-L187) is a dictionary with Nyx chain registry entry regarding all explorers.
If you want more information, or if your node isn't showing up on the explorer of your choice and you want to double-check, here are some examples on how to check if the node is configured properly.
@@ -345,7 +343,7 @@ In case your Gateway appeared on the [blacklist](https://validator.nymtech.net/a
**What to do**
Begin with a sanity check by opening [harbourmaster.nymtech.net](https://harbourmaster.nymtech.net) and check your node there. To query all API endpoints of your node at once, you can run [Node API Check CLI](../testing/node-api-check.mdx). To see IPv4 and IPv6 routing in real time (harbourmaster can have a cache up to 90 min), run [Gateway Probe CLI](../testing/gateway-probe.mdx).
Begin with a sanity check by opening [harbourmaster.nymtech.net](https://harbourmaster.nymtech.net) and check your node there. To see IPv4 and IPv6 routing in real time (harbourmaster can have a cache up to 90 min), run [Gateway Probe CLI](../testing/gateway-probe.mdx).
Then follow these steps:
@@ -1,62 +1,62 @@
'use client'
"use client";
import * as React from 'react'
import { Alert, AlertTitle, Box, CircularProgress, Grid } from '@mui/material'
import { useParams } from 'next/navigation'
import { ColumnsType, DetailTable } from '@/app/components/DetailTable'
import { ComponentError } from '@/app/components/ComponentError'
import { ContentCard } from '@/app/components/ContentCard'
import { UptimeChart } from '@/app/components/UptimeChart'
import {
NymNodeContextProvider,
useNymNodeContext,
} from '@/app/context/node'
import { useMainContext } from '@/app/context/main'
import { Title } from '@/app/components/Title'
import * as React from "react";
import { Alert, AlertTitle, Box, CircularProgress, Grid } from "@mui/material";
import { useParams } from "next/navigation";
import { ColumnsType, DetailTable } from "@/app/components/DetailTable";
import { ComponentError } from "@/app/components/ComponentError";
import { ContentCard } from "@/app/components/ContentCard";
import { UptimeChart } from "@/app/components/UptimeChart";
import { NymNodeContextProvider, useNymNodeContext } from "@/app/context/node";
import { useMainContext } from "@/app/context/main";
import { Title } from "@/app/components/Title";
import Paper from "@mui/material/Paper";
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableRow from '@mui/material/TableRow';
import {humanReadableCurrencyToString} from "@/app/utils/currency";
import {DeclaredRole} from "@/app/network-components/nodes/DeclaredRole";
import {NodeDelegationsTable, VestingDelegationWarning} from "@/app/network-components/nodes/[id]/NodeDelegationsTable";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableContainer from "@mui/material/TableContainer";
import TableRow from "@mui/material/TableRow";
import { humanReadableCurrencyToString } from "@/app/utils/currency";
import { DeclaredRole } from "@/app/network-components/nodes/DeclaredRole";
import {
NodeDelegationsTable,
VestingDelegationWarning,
} from "@/app/network-components/nodes/[id]/NodeDelegationsTable";
const columns: ColumnsType[] = [
{
field: 'identity_key',
title: 'Identity Key',
headerAlign: 'left',
field: "identity_key",
title: "Identity Key",
headerAlign: "left",
width: 230,
},
{
field: 'bond',
title: 'Bond',
headerAlign: 'left',
field: "bond",
title: "Bond",
headerAlign: "left",
},
{
field: 'host',
title: 'IP',
headerAlign: 'left',
field: "host",
title: "IP",
headerAlign: "left",
width: 99,
},
{
field: 'location',
title: 'Location',
headerAlign: 'left',
field: "location",
title: "Location",
headerAlign: "left",
},
{
field: 'owner',
title: 'Owner',
headerAlign: 'left',
field: "owner",
title: "Owner",
headerAlign: "left",
},
{
field: 'version',
title: 'Version',
headerAlign: 'left',
field: "version",
title: "Version",
headerAlign: "left",
},
]
];
interface NodeEnrichedRowType {
node_id: number;
@@ -71,28 +71,33 @@ interface NodeEnrichedRowType {
function nodeEnrichedToGridRow(node: any): NodeEnrichedRowType {
return {
node_id: node.node_id,
owner: node.bond_information?.owner || '',
identity_key: node.bond_information?.node?.identity_key || '',
location: node.location?.country_name || '',
owner: node.bond_information?.owner || "",
identity_key: node.bond_information?.node?.identity_key || "",
location: node.location?.country_name || "",
bond: node.bond_information?.original_pledge.amount || 0, // TODO: format
host: node.bond_information?.node?.host || '',
version: node.description?.build_information?.build_version || '',
host: node.bond_information?.node?.host || "",
version: node.description?.build_information?.build_version || "",
};
}
/**
* Shows nym node details
*/
const PageNymNodeDetailsWithState = ({
selectedNymNode,
}: {
selectedNymNode?: any
selectedNymNode?: any;
}) => {
const { uptimeHistory } = useNymNodeContext()
const enrichedData = React.useMemo(() => selectedNymNode ? [nodeEnrichedToGridRow(selectedNymNode)] : [], []);
const { uptimeHistory } = useNymNodeContext();
const enrichedData = React.useMemo(
() => (selectedNymNode ? [nodeEnrichedToGridRow(selectedNymNode)] : []),
[]
);
const hasVestingContractDelegations = React.useMemo(() => selectedNymNode?.delegations?.filter((d: any) => d.proxy)?.length, [selectedNymNode]);
const hasVestingContractDelegations = React.useMemo(
() => selectedNymNode?.delegations?.filter((d: any) => d.proxy)?.length,
[selectedNymNode]
);
return (
<Box component="main">
@@ -109,81 +114,128 @@ const PageNymNodeDetailsWithState = ({
</Grid>
<Grid container mt={2} spacing={2}>
{selectedNymNode.rewarding_details &&
<Grid item xs={12} md={4}>
<TableContainer component={Paper}>
<Table>
<TableBody>
<TableRow>
<TableCell colSpan={2}>
Delegations and Rewards
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
<strong>Operator</strong>
</TableCell>
<TableCell align="right">
{humanReadableCurrencyToString({ amount : selectedNymNode.rewarding_details.operator.split('.')[0], denom: "unym" })}
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
<strong>
{hasVestingContractDelegations ?
<VestingDelegationWarning plural={true}>
Delegates&nbsp;({selectedNymNode.rewarding_details.unique_delegations} delegates)
</VestingDelegationWarning> :
<>Delegates&nbsp;({selectedNymNode.rewarding_details.unique_delegations} delegates)</>
}
</strong>
</TableCell>
<TableCell align="right">
{humanReadableCurrencyToString({ amount : selectedNymNode.rewarding_details.delegates.split('.')[0], denom: "unym" })}
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
<strong>Profit margin</strong>
</TableCell>
<TableCell align="right">
{selectedNymNode.rewarding_details.cost_params.profit_margin_percent * 100}%
</TableCell>
</TableRow>
<TableRow>
<TableCell component="th" scope="row" sx={{ color: "inherit" }}>
<strong>Operator costs</strong>
</TableCell>
<TableCell align="right">
{humanReadableCurrencyToString(selectedNymNode.rewarding_details.cost_params.interval_operating_cost)}
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Grid>}
{selectedNymNode.rewarding_details && (
<Grid item xs={12} md={4}>
<TableContainer component={Paper}>
<Table>
<TableBody>
<TableRow>
<TableCell colSpan={2}>Delegations and Rewards</TableCell>
</TableRow>
<TableRow>
<TableCell
component="th"
scope="row"
sx={{ color: "inherit" }}
>
<strong>Operator</strong>
</TableCell>
<TableCell align="right">
{humanReadableCurrencyToString({
amount:
selectedNymNode.rewarding_details.operator.split(
"."
)[0],
denom: "unym",
})}
</TableCell>
</TableRow>
<TableRow>
<TableCell
component="th"
scope="row"
sx={{ color: "inherit" }}
>
<strong>
{hasVestingContractDelegations ? (
<VestingDelegationWarning plural={true}>
Delegates&nbsp;(
{
selectedNymNode.rewarding_details
.unique_delegations
}{" "}
delegates)
</VestingDelegationWarning>
) : (
<>
Delegates&nbsp;(
{
selectedNymNode.rewarding_details
.unique_delegations
}{" "}
delegates)
</>
)}
</strong>
</TableCell>
<TableCell align="right">
{humanReadableCurrencyToString({
amount:
selectedNymNode.rewarding_details.delegates.split(
"."
)[0],
denom: "unym",
})}
</TableCell>
</TableRow>
<TableRow>
<TableCell
component="th"
scope="row"
sx={{ color: "inherit" }}
>
<strong>Profit margin</strong>
</TableCell>
<TableCell align="right">
{selectedNymNode.rewarding_details.cost_params
.profit_margin_percent * 100}
%
</TableCell>
</TableRow>
<TableRow>
<TableCell
component="th"
scope="row"
sx={{ color: "inherit" }}
>
<strong>Operator costs</strong>
</TableCell>
<TableCell align="right">
{humanReadableCurrencyToString(
selectedNymNode.rewarding_details.cost_params
.interval_operating_cost
)}
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Grid>
)}
{selectedNymNode.description?.declared_role && <Grid item xs={12} md={4}>
<TableContainer component={Paper}>
<Table>
<TableBody>
<TableRow>
<TableCell colSpan={2}>
Node roles
</TableCell>
</TableRow>
<TableRow>
<TableCell>
Self declared roles
</TableCell>
<TableCell>
<DeclaredRole declared_role={selectedNymNode.description?.declared_role}/>
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Grid>}
{selectedNymNode.description?.declared_role && (
<Grid item xs={12} md={4}>
<TableContainer component={Paper}>
<Table>
<TableBody>
<TableRow>
<TableCell colSpan={2}>Node roles</TableCell>
</TableRow>
<TableRow>
<TableCell>Self declared roles</TableCell>
<TableCell>
<DeclaredRole
declared_role={
selectedNymNode.description?.declared_role
}
/>
</TableCell>
</TableRow>
</TableBody>
</Table>
</TableContainer>
</Grid>
)}
</Grid>
<Grid container spacing={2} mt={2}>
@@ -205,42 +257,41 @@ const PageNymNodeDetailsWithState = ({
</Grid>
<Box mt={2}>
<NodeDelegationsTable node={selectedNymNode}/>
<NodeDelegationsTable node={selectedNymNode} />
</Box>
</Box>
)
}
);
};
/**
* Guard component to handle loading and not found states
*/
const PageNymNodeDetailGuard = () => {
const [selectedNode, setSelectedNode] = React.useState<any>()
const [selectedNode, setSelectedNode] = React.useState<any>();
const [isLoading, setLoading] = React.useState<boolean>(true);
const [error, setError] = React.useState<string>();
const { fetchNodeById } = useMainContext()
const { id } = useParams()
const { fetchNodeById } = useMainContext();
const { id } = useParams();
React.useEffect(() => {
setSelectedNode(undefined);
setLoading(true);
(async () => {
if(typeof(id) === "string") {
if (typeof id === "string") {
try {
const res = await fetchNodeById(Number.parseInt(id));
setSelectedNode(res);
} catch(e: any) {
} catch (e: any) {
setError(e.message);
}
finally {
} finally {
setLoading(false);
}
}
})();
}, [id])
}, [id, fetchNodeById]);
if (isLoading) {
return <CircularProgress />
return <CircularProgress />;
}
// loaded, but not found
@@ -248,31 +299,38 @@ const PageNymNodeDetailGuard = () => {
return (
<Alert severity="warning">
<AlertTitle>Nym node not found</AlertTitle>
Sorry, we could not find a node with id <code>{id || ''}</code>
Sorry, we could not find a node with id <code>{id || ""}</code>
</Alert>
)
);
}
return <PageNymNodeDetailsWithState selectedNymNode={selectedNode} />
}
if (!selectedNode) {
return (
<Alert severity="warning">
<AlertTitle>Legacy Node</AlertTitle>
Unable to load details for the selected Nym node.
</Alert>
);
}
return <PageNymNodeDetailsWithState selectedNymNode={selectedNode} />;
};
/**
* Wrapper component that adds the node content based on the `id` in the address URL
*/
const PageNymNodeDetail = () => {
const { id } = useParams()
const { id } = useParams();
if (!id || typeof id !== 'string') {
return (
<Alert severity="error">Oh no! Could not find that node</Alert>
)
if (!id || typeof id !== "string") {
return <Alert severity="error">Oh no! Could not find that node</Alert>;
}
return (
<NymNodeContextProvider nymNodeId={id}>
<PageNymNodeDetailGuard />
</NymNodeContextProvider>
)
}
);
};
export default PageNymNodeDetail
export default PageNymNodeDetail;
+11 -1
View File
@@ -1,4 +1,14 @@
/** @type {import('next').NextConfig} */
const nextConfig = {};
const nextConfig = {
async redirects() {
return [
{
source: '/network-components/mixnode/:id', // Match the old URL
destination: '/network-components/nodes/:id', // Redirect to the new URL
permanent: true,
},
];
},
};
export default nextConfig;
+2 -1
View File
@@ -28,7 +28,8 @@
"../assets/*"
]
},
"moduleResolution": "node"
"moduleResolution": "node",
"target": "ES2017"
},
"include": [
"next-env.d.ts",
@@ -7,7 +7,7 @@ use tracing::error;
#[derive(Debug, Error)]
pub enum BandwidthError {
#[error("Provided bandwidth credential asks for more bandwidth than it is supported to add at once (credential value: {0}, supported: {}). Try to split it before attempting again", i64::MAX)]
#[error("Provided bandwidth credential asks for more bandwidth than it is supported to add at once (credential value: {0}, supported: {s}). Try to split it before attempting again", s = i64::MAX)]
UnsupportedBandwidthValue(u64),
#[error("failed to parse the bandwidth voucher value: {source}")]
+1
View File
@@ -28,6 +28,7 @@ nym-serde-helpers = { path = "../../common/serde-helpers", features = ["bs58", "
nym-credentials-interface = { path = "../../common/credentials-interface" }
nym-crypto = { path = "../../common/crypto", features = ["serde", "asymmetric"] }
nym-config = { path = "../../common/config" }
nym-ecash-time = { path = "../../common/ecash-time" }
nym-compact-ecash = { path = "../../common/nym_offline_compact_ecash" }
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common", features = ["naive_float"] }
+6
View File
@@ -12,6 +12,12 @@ pub mod models;
pub mod nym_nodes;
pub mod pagination;
// The response type we fetch from the network details endpoint.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct NymNetworkDetailsResponse {
pub network: nym_config::defaults::NymNetworkDetails,
}
pub trait Deprecatable {
fn deprecate(self) -> Deprecated<Self>
where
+12 -4
View File
@@ -59,9 +59,6 @@ async fn make_clients(
loop {
if Arc::strong_count(&dropped_client) == 1 {
if let Some(client) = Arc::into_inner(dropped_client) {
// let forget_me = ClientRequest::ForgetMe {
// also_from_stats: true,
// };
let client_handle = client.into_inner();
client_handle.disconnect().await;
} else {
@@ -222,10 +219,12 @@ async fn main() -> Result<()> {
TOPOLOGY.get().expect("Topology not set yet!").clone(),
));
let clients_server = clients.clone();
let server_handle = tokio::spawn(async move {
let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::from_str(&args.host)?), args.port);
let server = HttpServer::new(socket, server_cancel_token);
server.run(clients).await
server.run(clients_server).await
});
info!("Waiting for message (ctrl-c to exit)");
@@ -259,6 +258,15 @@ async fn main() -> Result<()> {
};
}
info!("Disconnecting all clients");
let mut clients_guard = clients.write().await;
while let Some(client) = clients_guard.pop_front() {
if let Some(client) = Arc::into_inner(client) {
let client_handle = client.into_inner();
client_handle.disconnect().await;
}
}
cancel_token.cancel();
server_handle.await??;
@@ -151,8 +151,10 @@ pub(crate) mod ipinfo {
#[cfg(test)]
mod api_regression {
use tokio::time::sleep;
use super::*;
use std::{env::var, sync::LazyLock};
use std::{env::var, sync::LazyLock, time::Duration};
static IPINFO_TOKEN: LazyLock<Option<String>> = LazyLock::new(|| var("IPINFO_API_TOKEN").ok());
static CI: LazyLock<Option<String>> = LazyLock::new(|| var("CI").ok());
@@ -174,6 +176,9 @@ mod api_regression {
let location_result = client.locate_ip(my_ip).await;
assert!(location_result.is_ok(), "Did ipinfo response change?");
// Artifical sleep to avoid rate limit
sleep(Duration::from_secs(2)).await;
client
.check_remaining_bandwidth()
.await
+7 -1
View File
@@ -69,5 +69,11 @@ FROM ubuntu:24.04
WORKDIR /nym
RUN apt update && apt install -y wget
COPY --from=builder /usr/src/nym/target/release/nym-node ./
ENTRYPOINT [ "/nym/nym-node" ]
COPY ./nym-node/entrypoint.sh ./
RUN wget https://builds.ci.nymte.ch/develop/nym-cli
RUN chmod +x nym-cli
ENTRYPOINT [ "./entrypoint.sh" ]
+17
View File
@@ -0,0 +1,17 @@
#!/bin/bash
./nym-node run --init-only
BOND_INFO=$(./nym-node bonding-information)
IDENTITY_KEY=$(grep -oP '(?<=Identity Key: ).*' <<<"$BOND_INFO")
SPHINX_KEY=$(grep -oP '(?<=Sphinx Key: ).*' <<<"$BOND_INFO")
VERSION=$(grep -oP '(?<=Version: ).*' <<<"$BOND_INFO" | sed 's/+.*//')
echo "Entering into signature signing..."
CONTRACT_MSG=$(./nym-cli --mnemonic "$NYMNODE_MNEMONIC" mixnet operators nymnode create-node-bonding-sign-payload --host "$NYMNODE_PUBLIC_IPS" --identity-key "$IDENTITY_KEY" --amount 100000000)
SIGNATURE=$(./nym-node sign --contract-msg "$CONTRACT_MSG" | grep -A1 'is:' | tail -n1 | sed 's/^\s*//')
echo "Starting the bond node flow..."
./nym-cli --mnemonic "$NYMNODE_MNEMONIC" mixnet operators nymnode bond --host "$NYMNODE_PUBLIC_IPS" --identity-key "$IDENTITY_KEY" --amount 100000000 --signature "$SIGNATURE"
./nym-node run --deny-init
+1 -1
View File
@@ -21,7 +21,7 @@ tokio = { version = "1", features = ["full"] }
lazy_static = "1.5.0"
# error handling
anyhow = "1.0.95"
thiserror = "1.0.64"
thiserror.workspace = true
[build-dependencies]
uniffi = { version = "0.25.2", features = ["build"] }
@@ -40,6 +40,7 @@ async fn main() {
client.disconnect().await;
}
#[derive(Clone)]
#[allow(unused)]
struct MockClientStorage {
pub key_store: MockKeyStore,
@@ -96,6 +97,7 @@ impl MixnetClientStorage for MockClientStorage {
}
}
#[derive(Clone)]
struct MockKeyStore;
#[async_trait]
@@ -115,6 +117,7 @@ impl KeyStore for MockKeyStore {
}
}
#[derive(Clone)]
struct MockGatewayDetailsStore;
#[async_trait]
+6 -6
View File
@@ -17,22 +17,22 @@ use zeroize::Zeroizing;
/// The way to create this client is by calling
/// [`crate::mixnet::DisconnectedMixnetClient::create_bandwidth_client`] on the associated mixnet
/// client.
pub struct BandwidthAcquireClient<'a, St: Storage> {
pub struct BandwidthAcquireClient<St: Storage + Clone> {
client: DirectSigningHttpRpcNyxdClient,
storage: &'a St,
storage: St,
client_id: Zeroizing<Vec<u8>>,
ticketbook_type: TicketType,
}
impl<'a, St> BandwidthAcquireClient<'a, St>
impl<St> BandwidthAcquireClient<St>
where
St: Storage,
St: Storage + Clone,
<St as Storage>::StorageError: Send + Sync + 'static,
{
pub(crate) fn new(
network_details: NymNetworkDetails,
mnemonic: String,
storage: &'a St,
storage: St,
client_id: Vec<u8>,
ticketbook_type: TicketType,
) -> Result<Self> {
@@ -55,7 +55,7 @@ where
pub async fn acquire(&self) -> Result<()> {
issue_credential(
&self.client,
self.storage,
&self.storage,
self.client_id.deref(),
self.ticketbook_type,
)
+6 -4
View File
@@ -108,7 +108,7 @@ impl MixnetClientBuilder<OnDiskPersistent> {
impl<S> MixnetClientBuilder<S>
where
S: MixnetClientStorage + 'static,
S: MixnetClientStorage + Clone + 'static,
S::ReplyStore: Send + Sync,
S::GatewaysDetailsStore: Sync,
<S::ReplyStore as ReplyStorageBackend>::StorageError: Sync + Send,
@@ -326,7 +326,7 @@ where
/// client.
pub struct DisconnectedMixnetClient<S>
where
S: MixnetClientStorage,
S: MixnetClientStorage + Clone,
{
/// Client configuration
config: Config,
@@ -371,7 +371,7 @@ where
impl<S> DisconnectedMixnetClient<S>
where
S: MixnetClientStorage + 'static,
S: MixnetClientStorage + Clone + 'static,
S::ReplyStore: Send + Sync,
S::GatewaysDetailsStore: Sync,
<S::ReplyStore as ReplyStorageBackend>::StorageError: Sync + Send,
@@ -621,7 +621,7 @@ where
BandwidthAcquireClient::new(
self.config.network_details.clone(),
mnemonic,
self.storage.credential_store(),
self.storage.credential_store().clone(),
client_id,
ticketbook_type,
)
@@ -799,6 +799,8 @@ where
stats_events_reporter,
started_client.task_handle,
None,
started_client.client_request_sender,
started_client.forget_me,
))
}
}
@@ -5,12 +5,15 @@ use async_trait::async_trait;
use futures::{ready, Stream, StreamExt};
use log::error;
use nym_client_core::client::base_client::GatewayConnection;
use nym_client_core::client::mix_traffic::ClientRequestSender;
use nym_client_core::client::{
base_client::{ClientInput, ClientOutput, ClientState},
inbound_messages::InputMessage,
received_buffer::ReconstructedMessagesReceiver,
};
use nym_client_core::ForgetMe;
use nym_crypto::asymmetric::identity;
use nym_gateway_requests::ClientRequest;
use nym_sphinx::addressing::clients::Recipient;
use nym_sphinx::{params::PacketType, receiver::ReconstructedMessage};
use nym_statistics_common::clients::{ClientStatsEvents, ClientStatsSender};
@@ -56,6 +59,8 @@ pub struct MixnetClient {
// internal state used for the `Stream` implementation
_buffered: Vec<ReconstructedMessage>,
pub(crate) client_request_sender: ClientRequestSender,
pub(crate) forget_me: ForgetMe,
}
impl MixnetClient {
@@ -70,6 +75,8 @@ impl MixnetClient {
stats_events_reporter: ClientStatsSender,
task_handle: TaskHandle,
packet_type: Option<PacketType>,
client_request_sender: ClientRequestSender,
forget_me: ForgetMe,
) -> Self {
Self {
nym_address,
@@ -82,6 +89,8 @@ impl MixnetClient {
task_handle,
packet_type,
_buffered: Vec::new(),
client_request_sender,
forget_me,
}
}
@@ -112,6 +121,10 @@ impl MixnetClient {
&self.nym_address
}
pub fn client_request_sender(&self) -> ClientRequestSender {
self.client_request_sender.clone()
}
/// Sign a message with the client's private identity key.
pub fn sign(&self, data: &[u8]) -> identity::Signature {
self.identity_keys.private_key().sign(data)
@@ -201,6 +214,15 @@ impl MixnetClient {
/// Disconnect from the mixnet. Currently it is not supported to reconnect a disconnected
/// client.
pub async fn disconnect(mut self) {
if self.forget_me.any() {
log::debug!("Sending forget me request: {:?}", self.forget_me);
match self.send_forget_me().await {
Ok(_) => (),
Err(e) => error!("Failed to send forget me request: {}", e),
};
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
}
if let TaskHandle::Internal(task_manager) = &mut self.task_handle {
task_manager.signal_shutdown().ok();
task_manager.wait_for_shutdown().await;
@@ -209,6 +231,20 @@ impl MixnetClient {
// note: it's important to take ownership of the struct as if the shutdown is `TaskHandle::External`,
// it must be dropped to finalize the shutdown
}
pub async fn send_forget_me(&self) -> Result<()> {
let client_request = ClientRequest::ForgetMe {
client: self.forget_me.client(),
stats: self.forget_me.stats(),
};
match self.client_request_sender.send(client_request).await {
Ok(_) => Ok(()),
Err(e) => {
error!("Failed to send forget me request: {}", e);
Err(Error::MessageSendingFailure)
}
}
}
}
#[derive(Clone)]