Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7fc9eca46f | |||
| 4e5c765a0d | |||
| e1abbc0b5b | |||
| 373cc54f3f | |||
| a276608fd0 | |||
| b332a6b556 | |||
| c610389198 | |||
| d283ecae22 | |||
| 2120acfdad | |||
| b613775551 | |||
| 73d4896b0b | |||
| b8b66fa4ad | |||
| ba59022160 | |||
| 8e2c64a867 | |||
| 73ae09cb76 | |||
| fd238b1d1b | |||
| 1f2d888626 | |||
| 9f47b05ed6 | |||
| f559a03732 | |||
| 3e12766afd | |||
| 1c93cc8a68 | |||
| 4865f7f205 | |||
| a785950d76 | |||
| 328d1c25b7 | |||
| 862a248902 | |||
| f826904407 | |||
| 67467ca76d | |||
| a53ae5b6b5 | |||
| bfc495ef29 | |||
| 9d74c22f9b | |||
| f978552a3a | |||
| b2477dd81b | |||
| dcd712430e | |||
| 5dcad5ffd4 | |||
| 210269cb9c | |||
| 8c59106add | |||
| 264771f8cf | |||
| 28d27eb84e | |||
| d57757584c | |||
| 6da00513a7 | |||
| 035faf70e4 | |||
| b238d108e6 | |||
| 5581b15094 | |||
| af19c4ecfd | |||
| 1855423981 | |||
| 229561bab9 | |||
| 7d2044c177 | |||
| 7885d3c986 | |||
| 78fb3c2293 | |||
| 0ed43d2439 | |||
| 69c1a32392 | |||
| bb372fb35c | |||
| 7fcc8cdd19 | |||
| 098bd4eb3c | |||
| 1ebb0c7daa | |||
| 377e06daab | |||
| b54d82a01a | |||
| ad052ef498 | |||
| a3bc4af8fe | |||
| b6d57e2862 |
@@ -30,6 +30,7 @@ jobs:
|
||||
mixnode_hash: ${{ steps.binary-hashes.outputs.mixnode_hash }}
|
||||
gateway_hash: ${{ steps.binary-hashes.outputs.gateway_hash }}
|
||||
nymvisor_hash: ${{ steps.binary-hashes.outputs.nymvisor_hash }}
|
||||
nymnode_hash: ${{ steps.binary-hashes.outputs.nymnode_hash }}
|
||||
socks5_hash: ${{ steps.binary-hashes.outputs.socks5_hash }}
|
||||
netreq_hash: ${{ steps.binary-hashes.outputs.netreq_hash }}
|
||||
cli_hash: ${{ steps.binary-hashes.outputs.cli_hash }}
|
||||
@@ -38,6 +39,7 @@ jobs:
|
||||
mixnode_version: ${{ steps.binary-versions.outputs.mixnode_version }}
|
||||
gateway_version: ${{ steps.binary-versions.outputs.gateway_version }}
|
||||
nymvisor_version: ${{ steps.binary-versions.outputs.nymvisor_version }}
|
||||
nymnode_version: ${{ steps.binary-versions.outputs.nymnode_version }}
|
||||
socks5_version: ${{ steps.binary-versions.outputs.socks5_version }}
|
||||
netreq_version: ${{ steps.binary-versions.outputs.netreq_version }}
|
||||
cli_version: ${{ steps.binary-versions.outputs.cli_version }}
|
||||
@@ -81,6 +83,7 @@ jobs:
|
||||
target/release/nym-network-statistics
|
||||
target/release/nym-cli
|
||||
target/release/nymvisor
|
||||
target/release/nym-node
|
||||
retention-days: 30
|
||||
|
||||
- id: create-release
|
||||
@@ -99,6 +102,7 @@ jobs:
|
||||
target/release/nym-network-statistics
|
||||
target/release/nym-cli
|
||||
target/release/nymvisor
|
||||
target/release/nym-node
|
||||
|
||||
push-release-data-client:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
|
||||
@@ -4,6 +4,20 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2024.3-eclipse] (2024-04-22)
|
||||
|
||||
- Initial release of the first iteration of the Nym Node
|
||||
- Improvements to gateway functionality
|
||||
- IPR development
|
||||
- Removal of allow list in favour of implementing an exit policy
|
||||
- Explorer delegation: enables direct delegation to nodes via the Nym Explorer
|
||||
|
||||
|
||||
## [2024.2-fast-and-furious] (2024-03-25)
|
||||
|
||||
- Internal testing pre-release
|
||||
|
||||
|
||||
## [2024.1-marabou] (2024-02-15)
|
||||
|
||||
**New Features:**
|
||||
|
||||
Generated
+22
-17
@@ -2547,7 +2547,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "explorer-api"
|
||||
version = "1.1.33"
|
||||
version = "1.1.34"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap 4.4.7",
|
||||
@@ -3507,7 +3507,7 @@ dependencies = [
|
||||
"futures-util",
|
||||
"http 0.2.9",
|
||||
"hyper 0.14.27",
|
||||
"rustls 0.21.10",
|
||||
"rustls 0.21.11",
|
||||
"tokio",
|
||||
"tokio-rustls 0.24.1",
|
||||
]
|
||||
@@ -5024,7 +5024,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.35"
|
||||
version = "1.1.37"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -5113,6 +5113,7 @@ dependencies = [
|
||||
"schemars",
|
||||
"serde",
|
||||
"tendermint",
|
||||
"time",
|
||||
"ts-rs",
|
||||
]
|
||||
|
||||
@@ -5185,7 +5186,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.34"
|
||||
version = "1.1.35"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.13.1",
|
||||
@@ -5266,7 +5267,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.33"
|
||||
version = "1.1.34"
|
||||
dependencies = [
|
||||
"bs58 0.5.0",
|
||||
"clap 4.4.7",
|
||||
@@ -5683,7 +5684,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-gateway"
|
||||
version = "1.1.33"
|
||||
version = "1.1.35"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -5756,7 +5757,9 @@ dependencies = [
|
||||
"nym-validator-client",
|
||||
"rand 0.7.3",
|
||||
"serde",
|
||||
"si-scale",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tokio-tungstenite",
|
||||
@@ -5976,7 +5979,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-mixnode"
|
||||
version = "1.1.35"
|
||||
version = "1.1.37"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
@@ -6095,7 +6098,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.33"
|
||||
version = "1.1.34"
|
||||
dependencies = [
|
||||
"addr",
|
||||
"anyhow",
|
||||
@@ -6147,7 +6150,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-statistics"
|
||||
version = "1.1.33"
|
||||
version = "1.1.34"
|
||||
dependencies = [
|
||||
"dirs 4.0.0",
|
||||
"log",
|
||||
@@ -6164,7 +6167,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node"
|
||||
version = "0.1.0"
|
||||
version = "1.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bip39",
|
||||
@@ -6172,6 +6175,7 @@ dependencies = [
|
||||
"cargo_metadata",
|
||||
"celes",
|
||||
"clap 4.4.7",
|
||||
"colored",
|
||||
"cupid",
|
||||
"humantime-serde",
|
||||
"ipnetwork 0.16.0",
|
||||
@@ -6247,6 +6251,7 @@ dependencies = [
|
||||
"nym-exit-policy",
|
||||
"nym-http-api-client",
|
||||
"nym-wireguard-types",
|
||||
"rand_chacha 0.2.2",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -6433,7 +6438,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.33"
|
||||
version = "1.1.34"
|
||||
dependencies = [
|
||||
"bs58 0.5.0",
|
||||
"clap 4.4.7",
|
||||
@@ -8313,7 +8318,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite 0.2.13",
|
||||
"rustls 0.21.10",
|
||||
"rustls 0.21.11",
|
||||
"rustls-native-certs",
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
@@ -8697,9 +8702,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.21.10"
|
||||
version = "0.21.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba"
|
||||
checksum = "7fecbfb7b1444f477b345853b1fce097a2c6fb637b2bfb87e6bc5db0f043fae4"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring 0.17.4",
|
||||
@@ -10096,7 +10101,7 @@ version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
|
||||
dependencies = [
|
||||
"rustls 0.21.10",
|
||||
"rustls 0.21.11",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
@@ -10157,7 +10162,7 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
"rustls 0.21.10",
|
||||
"rustls 0.21.11",
|
||||
"rustls-native-certs",
|
||||
"tokio",
|
||||
"tokio-rustls 0.24.1",
|
||||
@@ -10591,7 +10596,7 @@ dependencies = [
|
||||
"httparse",
|
||||
"log",
|
||||
"rand 0.8.5",
|
||||
"rustls 0.21.10",
|
||||
"rustls 0.21.11",
|
||||
"sha1",
|
||||
"thiserror",
|
||||
"url",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.33"
|
||||
version = "1.1.34"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -8,5 +8,7 @@ use nym_client_core::cli_helpers::client_import_credential::{
|
||||
};
|
||||
|
||||
pub(crate) async fn execute(args: CommonClientImportCredentialArgs) -> Result<(), ClientError> {
|
||||
import_credential::<CliNativeClient, _>(args).await
|
||||
import_credential::<CliNativeClient, _>(args).await?;
|
||||
println!("successfully imported credential!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.33"
|
||||
version = "1.1.34"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>"]
|
||||
description = "A SOCKS5 localhost proxy that converts incoming messages to Sphinx and sends them to a Nym address"
|
||||
edition = "2021"
|
||||
|
||||
@@ -10,5 +10,7 @@ use nym_client_core::cli_helpers::client_import_credential::{
|
||||
pub(crate) async fn execute(
|
||||
args: CommonClientImportCredentialArgs,
|
||||
) -> Result<(), Socks5ClientError> {
|
||||
import_credential::<CliSocks5Client, _>(args).await
|
||||
import_credential::<CliSocks5Client, _>(args).await?;
|
||||
println!("successfully imported credential!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ pub mod acquire;
|
||||
pub mod error;
|
||||
mod utils;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BandwidthController<C, St> {
|
||||
storage: St,
|
||||
client: C,
|
||||
|
||||
@@ -201,7 +201,7 @@ where
|
||||
log::debug!("Setting up gateway");
|
||||
match setup {
|
||||
GatewaySetup::MustLoad { gateway_id } => {
|
||||
log::trace!("GatewaySetup::MustLoad with id: {gateway_id:?}");
|
||||
log::debug!("GatewaySetup::MustLoad with id: {gateway_id:?}");
|
||||
use_loaded_gateway_details(key_store, details_store, gateway_id).await
|
||||
}
|
||||
GatewaySetup::New {
|
||||
@@ -209,7 +209,7 @@ where
|
||||
available_gateways,
|
||||
wg_tun_address,
|
||||
} => {
|
||||
log::trace!("GatewaySetup::New with spec: {specification:?}");
|
||||
log::debug!("GatewaySetup::New with spec: {specification:?}");
|
||||
setup_new_gateway(
|
||||
key_store,
|
||||
details_store,
|
||||
@@ -224,7 +224,7 @@ where
|
||||
gateway_details,
|
||||
client_keys: managed_keys,
|
||||
} => {
|
||||
log::trace!("GatewaySetup::ReuseConnection");
|
||||
log::debug!("GatewaySetup::ReuseConnection");
|
||||
Ok(reuse_gateway_connection(
|
||||
authenticated_ephemeral_client,
|
||||
*gateway_details,
|
||||
|
||||
@@ -16,6 +16,8 @@ thiserror = { workspace = true }
|
||||
url = { workspace = true }
|
||||
rand = { version = "0.7.3", features = ["wasm-bindgen"] }
|
||||
tokio = { version = "1.24.1", features = ["macros"] }
|
||||
si-scale = "0.2.2"
|
||||
time.workspace = true
|
||||
|
||||
# internal
|
||||
nym-bandwidth-controller = { path = "../../bandwidth-controller" }
|
||||
|
||||
@@ -79,6 +79,7 @@ impl GatewayConfig {
|
||||
}
|
||||
|
||||
// TODO: this should be refactored into a state machine that keeps track of its authentication state
|
||||
#[derive(Debug)]
|
||||
pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
|
||||
authenticated: bool,
|
||||
disabled_credentials_mode: bool,
|
||||
@@ -849,6 +850,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
// type alias for an ease of use
|
||||
pub type InitGatewayClient = GatewayClient<InitOnly>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InitOnly;
|
||||
|
||||
impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
|
||||
@@ -10,9 +10,14 @@ use futures::stream::{SplitSink, SplitStream};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use log::*;
|
||||
use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
use nym_gateway_requests::ServerResponse;
|
||||
use nym_task::TaskClient;
|
||||
use si_scale::helpers::bibytes2;
|
||||
use std::os::raw::c_int as RawFd;
|
||||
use std::sync::atomic::{AtomicI64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tungstenite::Message;
|
||||
|
||||
#[cfg(unix)]
|
||||
@@ -48,6 +53,22 @@ pub(crate) fn ws_fd(_conn: &WsConn) -> Option<RawFd> {
|
||||
None
|
||||
}
|
||||
|
||||
// disgusting? absolutely, but does the trick for now
|
||||
static LAST_LOGGED_BANDWIDTH_TS: AtomicI64 = AtomicI64::new(0);
|
||||
|
||||
fn maybe_log_bandwidth(remaining: i64) {
|
||||
// SAFETY: this value is always populated with valid timestamps
|
||||
let last =
|
||||
OffsetDateTime::from_unix_timestamp(LAST_LOGGED_BANDWIDTH_TS.load(Ordering::Relaxed))
|
||||
.unwrap();
|
||||
let now = OffsetDateTime::now_utc();
|
||||
if last + Duration::from_secs(10) < now {
|
||||
log::info!("remaining bandwidth: {}", bibytes2(remaining as f64));
|
||||
LAST_LOGGED_BANDWIDTH_TS.store(now.unix_timestamp(), Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PartiallyDelegated {
|
||||
sink_half: SplitSink<WsConn, Message>,
|
||||
delegated_stream: (SplitStreamReceiver, oneshot::Sender<()>),
|
||||
@@ -55,7 +76,10 @@ pub(crate) struct PartiallyDelegated {
|
||||
}
|
||||
|
||||
impl PartiallyDelegated {
|
||||
fn recover_received_plaintexts(ws_msgs: Vec<Message>, shared_key: &SharedKeys) -> Vec<Vec<u8>> {
|
||||
fn recover_received_plaintexts(
|
||||
ws_msgs: Vec<Message>,
|
||||
shared_key: &SharedKeys,
|
||||
) -> Result<Vec<Vec<u8>>, GatewayClientError> {
|
||||
let mut plaintexts = Vec::with_capacity(ws_msgs.len());
|
||||
for ws_msg in ws_msgs {
|
||||
match ws_msg {
|
||||
@@ -73,15 +97,32 @@ impl PartiallyDelegated {
|
||||
// TODO: those can return the "send confirmations" - perhaps it should be somehow worked around?
|
||||
Message::Text(text) => {
|
||||
trace!(
|
||||
"received a text message - probably a response to some previous query! - {}",
|
||||
text
|
||||
"received a text message - probably a response to some previous query! - {text}",
|
||||
);
|
||||
match ServerResponse::try_from(text)
|
||||
.map_err(|_| GatewayClientError::MalformedResponse)?
|
||||
{
|
||||
ServerResponse::Send {
|
||||
remaining_bandwidth,
|
||||
} => maybe_log_bandwidth(remaining_bandwidth),
|
||||
ServerResponse::Error { message } => {
|
||||
error!("gateway failure: {message}");
|
||||
return Err(GatewayClientError::GatewayError(message));
|
||||
}
|
||||
other => {
|
||||
warn!(
|
||||
"received illegal message of type {} in an authenticated client",
|
||||
other.name()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
plaintexts
|
||||
Ok(plaintexts)
|
||||
}
|
||||
|
||||
fn route_socket_messages(
|
||||
@@ -89,7 +130,7 @@ impl PartiallyDelegated {
|
||||
packet_router: &PacketRouter,
|
||||
shared_key: &SharedKeys,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
let plaintexts = Self::recover_received_plaintexts(ws_msgs, shared_key);
|
||||
let plaintexts = Self::recover_received_plaintexts(ws_msgs, shared_key)?;
|
||||
packet_router.route_received(plaintexts)
|
||||
}
|
||||
|
||||
@@ -129,7 +170,8 @@ impl PartiallyDelegated {
|
||||
};
|
||||
|
||||
if let Err(err) = Self::route_socket_messages(ws_msgs, &packet_router, shared_key.as_ref()) {
|
||||
log::warn!("Route socket messages failed: {err}");
|
||||
log::error!("Route socket messages failed: {err}");
|
||||
break Err(err)
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -221,6 +263,7 @@ impl PartiallyDelegated {
|
||||
// we can either have the stream itself or an option to re-obtain it
|
||||
// by notifying the future owning it to finish the execution and awaiting the result
|
||||
// which should be almost immediate (or an invalid state which should never, ever happen)
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum SocketState {
|
||||
Available(Box<WsConn>),
|
||||
PartiallyDelegated(PartiallyDelegated),
|
||||
|
||||
@@ -148,6 +148,10 @@ pub async fn execute(args: Args, client: SigningClient) -> anyhow::Result<()> {
|
||||
bail!("the provided free pass request has too long expiry (expiry is set to on {expiration_date})")
|
||||
}
|
||||
|
||||
if expiration_date < now {
|
||||
bail!("the provided free pass expiry is set in the past!")
|
||||
}
|
||||
|
||||
// issuance start
|
||||
block_until_coconut_is_available(&client).await?;
|
||||
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs;
|
||||
use std::fs::OpenOptions;
|
||||
|
||||
use clap::Parser;
|
||||
use comfy_table::Table;
|
||||
use csv::WriterBuilder;
|
||||
use log::info;
|
||||
use nym_mixnet_contract_common::ExecuteMsg;
|
||||
use nym_mixnet_contract_common::ExecuteMsg::{DelegateToMixnode, UndelegateFromMixnode};
|
||||
|
||||
use nym_mixnet_contract_common::PendingEpochEventKind::{Delegate, Undelegate};
|
||||
use nym_validator_client::nyxd::contract_traits::{NymContractsProvider, PagedMixnetQueryClient};
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
|
||||
use crate::context::SigningClient;
|
||||
use crate::utils::pretty_coin;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Args {
|
||||
#[clap(long)]
|
||||
pub memo: Option<String>,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
help = "Input csv files with delegation amounts. Format: (mixID, amount(in NYM))"
|
||||
)]
|
||||
pub input: String,
|
||||
|
||||
#[clap(
|
||||
long,
|
||||
help = "An output file path (CSV format) to create or append a log of results to"
|
||||
)]
|
||||
pub output: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InputFileRow {
|
||||
pub mix_id: String,
|
||||
pub amount: Coin,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct InputFileReader {
|
||||
pub rows: Vec<InputFileRow>,
|
||||
}
|
||||
|
||||
impl InputFileReader {
|
||||
pub fn new(path: &str) -> Result<InputFileReader, anyhow::Error> {
|
||||
let file_contents = fs::read_to_string(path)?;
|
||||
let mut rows = Vec::new();
|
||||
let mut mix_id_set = HashSet::new();
|
||||
|
||||
for line in file_contents
|
||||
.lines()
|
||||
.map(str::trim)
|
||||
.filter(|line| !line.is_empty())
|
||||
{
|
||||
let tokens: Vec<_> = line.split(',').collect();
|
||||
if tokens.len() != 2 {
|
||||
anyhow::bail!("Incorrect format: {}", line);
|
||||
}
|
||||
let mix_id = tokens[0].trim().to_string();
|
||||
let input_amount = tokens[1]
|
||||
.trim()
|
||||
.parse::<u128>()
|
||||
.map_err(|_| anyhow::anyhow!("'{}' has an invalid amount", line))?;
|
||||
|
||||
let micro_nym_amount = input_amount * 1_000_000;
|
||||
|
||||
if !mix_id_set.insert(mix_id.clone()) {
|
||||
anyhow::bail!("Duplicate mix_id found: {}", mix_id);
|
||||
}
|
||||
|
||||
rows.push(InputFileRow {
|
||||
mix_id,
|
||||
amount: Coin {
|
||||
amount: micro_nym_amount,
|
||||
denom: "unym".to_string(),
|
||||
},
|
||||
});
|
||||
}
|
||||
Ok(InputFileReader { rows })
|
||||
}
|
||||
}
|
||||
|
||||
fn write_to_csv(
|
||||
output_details: Vec<[String; 3]>,
|
||||
output_file: Option<String>,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
if let Some(file_path) = output_file {
|
||||
// Determine if the file exists and is not empty
|
||||
let file_exists = fs::metadata(&file_path)
|
||||
.map(|metadata| metadata.len() > 0)
|
||||
.unwrap_or(false);
|
||||
|
||||
// Open the file for appending or creation
|
||||
let file = OpenOptions::new()
|
||||
.append(true)
|
||||
.create(true)
|
||||
.open(&file_path)?;
|
||||
|
||||
if !file_exists {
|
||||
let mut wtr = csv::Writer::from_writer(&file);
|
||||
wtr.write_record(["Operation", "Transaction Hash", "Timestamp"])?;
|
||||
wtr.flush()?;
|
||||
}
|
||||
|
||||
let mut wtr = WriterBuilder::new()
|
||||
.has_headers(!file_exists)
|
||||
.from_writer(file);
|
||||
|
||||
// Write the details to the CSV file
|
||||
for detail in output_details {
|
||||
wtr.write_record(&detail)?;
|
||||
}
|
||||
wtr.flush()?;
|
||||
info!("All operations saved to output file");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn fetch_delegation_data(
|
||||
client: &SigningClient,
|
||||
) -> Result<HashMap<String, Coin>, anyhow::Error> {
|
||||
let address = client.address();
|
||||
// Fetch all delegations for the user
|
||||
let delegations = match client.get_all_delegator_delegations(&address).await {
|
||||
Ok(delegations) => delegations,
|
||||
Err(e) => {
|
||||
anyhow::bail!("Error fetching delegations: {}", e)
|
||||
}
|
||||
};
|
||||
|
||||
// Build a map to make it easier to handle delegation data
|
||||
let mut existing_delegation_map: HashMap<String, Coin> = HashMap::new();
|
||||
let mut pending_delegation_map: HashMap<String, Coin> = HashMap::new();
|
||||
|
||||
for delegation in delegations {
|
||||
existing_delegation_map
|
||||
.insert(delegation.mix_id.to_string(), Coin::from(delegation.amount));
|
||||
}
|
||||
|
||||
// Look for pending delegate / undelegate events which might be of interest to us
|
||||
let pending_events = match client.get_all_pending_epoch_events().await {
|
||||
Ok(events) => events,
|
||||
Err(e) => {
|
||||
anyhow::bail!("Error fetching pending epoch events: {}", e);
|
||||
}
|
||||
};
|
||||
|
||||
for event in pending_events {
|
||||
match event.event.kind {
|
||||
// If a pending undelegate tx is found, remove it from delegation map
|
||||
Undelegate { owner, mix_id, .. } => {
|
||||
if owner == address.as_ref()
|
||||
&& existing_delegation_map.get(&mix_id.to_string()).is_some()
|
||||
{
|
||||
existing_delegation_map.remove(&mix_id.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// If a pending delegation event is found, gather them to consolidate later
|
||||
Delegate {
|
||||
owner,
|
||||
mix_id,
|
||||
amount,
|
||||
..
|
||||
} => {
|
||||
if owner == address.as_ref() {
|
||||
let mut amount = Coin::from(amount);
|
||||
if let Some(pending_record) = pending_delegation_map.get(&mix_id.to_string()) {
|
||||
amount.amount += pending_record.amount;
|
||||
}
|
||||
pending_delegation_map.insert(mix_id.to_string(), amount);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
|
||||
// Consolidate pending events into delegation map
|
||||
for (mix_id, amount) in pending_delegation_map {
|
||||
existing_delegation_map
|
||||
.entry(mix_id)
|
||||
.and_modify(|e| e.amount += amount.amount)
|
||||
.or_insert(amount);
|
||||
}
|
||||
|
||||
Ok(existing_delegation_map)
|
||||
}
|
||||
|
||||
pub async fn delegate_to_multiple_mixnodes(args: Args, client: SigningClient) {
|
||||
let records = match InputFileReader::new(&args.input) {
|
||||
Ok(records) => records,
|
||||
Err(e) => {
|
||||
println!("Error reading input file: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let existing_delegation_map = fetch_delegation_data(&client)
|
||||
.await
|
||||
.expect("Could not fetch existing delegations");
|
||||
|
||||
let mut delegation_table = Table::new();
|
||||
let mut undelegation_table = Table::new();
|
||||
let mut delegation_msgs: Vec<(ExecuteMsg, Vec<Coin>)> = Vec::new();
|
||||
let mut undelegation_msgs: Vec<(ExecuteMsg, Vec<Coin>)> = Vec::new();
|
||||
|
||||
delegation_table.set_header(["Mix ID", "Input Amount", "Adjusted Amount"]);
|
||||
undelegation_table.set_header(["Mix ID"]);
|
||||
|
||||
for row in &records.rows {
|
||||
let input_amount = row.amount.amount;
|
||||
let existing_delegation_amount = existing_delegation_map
|
||||
.get(&row.mix_id)
|
||||
.map_or(0, |coin| coin.amount);
|
||||
|
||||
match existing_delegation_amount.cmp(&input_amount) {
|
||||
Ordering::Equal => continue, // No action needed if amounts are equal
|
||||
|
||||
Ordering::Less => {
|
||||
// Delegate the difference if the existing delegation is less
|
||||
let difference = Coin {
|
||||
amount: input_amount - existing_delegation_amount,
|
||||
denom: row.amount.denom.clone(),
|
||||
};
|
||||
let mix_id = row.mix_id.clone().parse::<u32>().unwrap();
|
||||
delegation_msgs.push((DelegateToMixnode { mix_id }, vec![difference.clone()]));
|
||||
delegation_table.add_row(&[
|
||||
row.mix_id.clone(),
|
||||
pretty_coin(&row.amount),
|
||||
pretty_coin(&difference),
|
||||
]);
|
||||
}
|
||||
|
||||
Ordering::Greater => {
|
||||
let mix_id = row.mix_id.clone().parse::<u32>().unwrap();
|
||||
let coins: Vec<Coin> = vec![];
|
||||
undelegation_msgs.push((UndelegateFromMixnode { mix_id }, coins));
|
||||
undelegation_table.add_row(&[row.mix_id.clone()]);
|
||||
|
||||
if row.amount.amount > 0 {
|
||||
delegation_msgs.push((DelegateToMixnode { mix_id }, vec![row.amount.clone()]));
|
||||
delegation_table.add_row(&[
|
||||
row.mix_id.clone(),
|
||||
pretty_coin(&row.amount),
|
||||
pretty_coin(&row.amount),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if delegation_msgs.is_empty() && undelegation_msgs.is_empty() {
|
||||
println!("Nothing to do. Delegations are up-to-date!");
|
||||
return;
|
||||
}
|
||||
|
||||
if !undelegation_msgs.is_empty() {
|
||||
println!("Undelegation records : \n{}\n\n", undelegation_table);
|
||||
}
|
||||
|
||||
if !delegation_msgs.is_empty() {
|
||||
println!("Delegation records : \n{}\n\n", delegation_table);
|
||||
}
|
||||
|
||||
let ans = inquire::Confirm::new("Do you want to continue with the shown operations?")
|
||||
.with_default(false)
|
||||
.with_help_message("You must confirm before the transactions are signed")
|
||||
.prompt();
|
||||
|
||||
if let Err(e) = ans {
|
||||
info!("Aborting, {}...", e);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Ok(false) = ans {
|
||||
info!("Aborting:: User denied proceeding with signing!");
|
||||
return;
|
||||
}
|
||||
|
||||
let mut output_details: Vec<[String; 3]> = Vec::new();
|
||||
let now = time::OffsetDateTime::now_utc();
|
||||
let now = now
|
||||
.format(&time::format_description::well_known::Rfc3339)
|
||||
.unwrap();
|
||||
|
||||
let mixnet_contract = client
|
||||
.mixnet_contract_address()
|
||||
.expect("mixnet contract address is not available");
|
||||
|
||||
// Execute all undelegation transactions
|
||||
if !undelegation_msgs.is_empty() {
|
||||
let res = client
|
||||
.execute_multiple(
|
||||
mixnet_contract,
|
||||
undelegation_msgs.clone(),
|
||||
None,
|
||||
format!(
|
||||
"Undelegate from {} nodes via nym-cli",
|
||||
undelegation_msgs.len()
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("Could not undelegate!");
|
||||
|
||||
println!(
|
||||
"Undelegation transaction successful : {}",
|
||||
res.transaction_hash
|
||||
);
|
||||
output_details.push([
|
||||
"Undelegate".to_string(),
|
||||
res.transaction_hash.to_string(),
|
||||
now.clone(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Execute all delegation delegations
|
||||
if !delegation_msgs.is_empty() {
|
||||
let res = client
|
||||
.execute_multiple(
|
||||
mixnet_contract,
|
||||
delegation_msgs,
|
||||
None,
|
||||
format!(
|
||||
"Delegatation to {} nodes via nym-cli",
|
||||
undelegation_msgs.len()
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("Could not delegate");
|
||||
|
||||
println!(
|
||||
"Delegation transaction successful : {}",
|
||||
res.transaction_hash
|
||||
);
|
||||
output_details.push([
|
||||
"Delegate".to_string(),
|
||||
res.transaction_hash.to_string(),
|
||||
now.clone(),
|
||||
]);
|
||||
}
|
||||
|
||||
if args.output.is_some() {
|
||||
if let Err(e) = write_to_csv(output_details, args.output) {
|
||||
info!("Failed to write to CSV, {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use clap::{Args, Subcommand};
|
||||
pub mod rewards;
|
||||
|
||||
pub mod delegate_to_mixnode;
|
||||
pub mod delegate_to_multiple_mixnodes;
|
||||
pub mod query_for_delegations;
|
||||
pub mod undelegate_from_mixnode;
|
||||
pub mod vesting_delegate_to_mixnode;
|
||||
@@ -26,6 +27,8 @@ pub enum MixnetDelegatorsCommands {
|
||||
Rewards(rewards::MixnetDelegatorsReward),
|
||||
/// Delegate to a mixnode
|
||||
Delegate(delegate_to_mixnode::Args),
|
||||
/// Perform bulk delegations from an input file
|
||||
DelegateMulti(delegate_to_multiple_mixnodes::Args),
|
||||
/// Undelegate from a mixnode
|
||||
Undelegate(undelegate_from_mixnode::Args),
|
||||
/// Delegate to a mixnode with locked tokens
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
ALTER TABLE coconut_credentials
|
||||
RENAME TO old_coconut_credentials;
|
||||
|
||||
CREATE TABLE coconut_credentials
|
||||
(
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
|
||||
-- introduce a way for us to introduce breaking changes in serialization
|
||||
serialization_revision INTEGER NOT NULL,
|
||||
|
||||
-- the best we can do without enums
|
||||
credential_type TEXT CHECK ( credential_type IN ('BandwidthVoucher', 'FreeBandwidthPass') ) NOT NULL,
|
||||
credential_data BLOB NOT NULL UNIQUE,
|
||||
epoch_id INTEGER NOT NULL,
|
||||
|
||||
-- this field is only really applicable to free passes
|
||||
expired BOOLEAN NOT NULL
|
||||
);
|
||||
|
||||
ALTER TABLE credential_usage
|
||||
RENAME TO old_credential_usage;
|
||||
|
||||
-- for bandwidth vouchers there's going to be only a single entry; for freepasses there can be as many as there are gateways
|
||||
CREATE TABLE credential_usage
|
||||
(
|
||||
credential_id INTEGER NOT NULL REFERENCES coconut_credentials (id),
|
||||
gateway_id_bs58 TEXT NOT NULL,
|
||||
|
||||
-- no matter credential type, we can't spend the same credential with the same gateway multiple times
|
||||
UNIQUE (credential_id, gateway_id_bs58)
|
||||
);
|
||||
|
||||
INSERT INTO coconut_credentials
|
||||
SELECT *
|
||||
FROM old_coconut_credentials;
|
||||
|
||||
|
||||
INSERT INTO credential_usage
|
||||
SELECT *
|
||||
FROM old_credential_usage;
|
||||
|
||||
DROP TABLE old_coconut_credentials;
|
||||
DROP TABLE old_credential_usage;
|
||||
@@ -44,7 +44,7 @@ impl CoconutCredentialManager {
|
||||
r#"
|
||||
SELECT *
|
||||
FROM coconut_credentials
|
||||
WHERE coconut_credentials.credential_type == "FreeBandwidthPass"
|
||||
WHERE coconut_credentials.credential_type == "FreeBandwidthPass" AND coconut_credentials.expired = false
|
||||
AND NOT EXISTS (SELECT 1
|
||||
FROM credential_usage
|
||||
WHERE credential_usage.credential_id = coconut_credentials.id
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
use crate::backends::memory::CoconutCredentialManager;
|
||||
use crate::error::StorageError;
|
||||
use crate::models::{StorableIssuedCredential, StoredIssuedCredential};
|
||||
@@ -23,6 +25,12 @@ impl Default for EphemeralStorage {
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for EphemeralStorage {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "EphemeralStorage")
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Storage for EphemeralStorage {
|
||||
type StorageError = StorageError;
|
||||
|
||||
@@ -474,6 +474,10 @@ impl TaskClient {
|
||||
self.mode.set_should_not_signal_on_drop();
|
||||
}
|
||||
|
||||
pub fn disarm(&mut self) {
|
||||
self.mark_as_success();
|
||||
}
|
||||
|
||||
pub fn send_we_stopped(&mut self, err: SentError) {
|
||||
if self.mode.is_dummy() {
|
||||
return;
|
||||
|
||||
@@ -205,10 +205,10 @@ impl<'a> TryFrom<&'a DescribedGateway> for Node {
|
||||
clients_ws_port: self_described.mixnet_websockets.ws_port,
|
||||
clients_wss_port: self_described.mixnet_websockets.wss_port,
|
||||
identity_key: identity::PublicKey::from_base58_string(
|
||||
&self_described.host_information.keys.ed25519_identity,
|
||||
&self_described.host_information.keys.ed25519,
|
||||
)?,
|
||||
sphinx_key: encryption::PublicKey::from_base58_string(
|
||||
&self_described.host_information.keys.x25519_sphinx,
|
||||
&self_described.host_information.keys.x25519,
|
||||
)?,
|
||||
version: self_described
|
||||
.build_information
|
||||
|
||||
@@ -6,6 +6,7 @@ use futures::{Sink, Stream};
|
||||
use gloo_net::websocket::futures::WebSocket;
|
||||
use gloo_net::websocket::{Message, WebSocketError};
|
||||
use gloo_utils::errors::JsError;
|
||||
use std::fmt::{self, Formatter};
|
||||
use std::io;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
@@ -77,6 +78,12 @@ impl Stream for JSWebsocket {
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for JSWebsocket {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "JSWebSocket")
|
||||
}
|
||||
}
|
||||
|
||||
impl Sink<WsMessage> for JSWebsocket {
|
||||
type Error = WsError;
|
||||
|
||||
|
||||
@@ -24,8 +24,6 @@
|
||||
- [Linux](nymvpn/gui-linux.md)
|
||||
- [MacOS](nymvpn/gui-mac.md)
|
||||
- [CLI](nymvpn/cli.md)
|
||||
- [Linux](nymvpn/cli-linux.md)
|
||||
- [MacOS](nymvpn/cli-mac.md)
|
||||
- [Troubleshooting](nymvpn/troubleshooting.md)
|
||||
- [NymVPN FAQ](nymvpn/faq.md)
|
||||
- [NymConnect X Monero](tutorials/monero.md)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# NymVPN alpha CLI: Guide for GNU/Linux
|
||||
# NymVPN alpha CLI Guide
|
||||
|
||||
```admonish info
|
||||
NymVPN is an experimental software and it's for testing purposes only. All users testing the client are expected to sign GDPR Information Sheet and Consent Form (shared at the workshop) so we use their results to improve the client, and submit the form [*NymVPN User research*]({{nym_vpn_form_url}}) with the testing results.
|
||||
@@ -8,7 +8,7 @@ NymVPN is an experimental software and it's for testing purposes only. All users
|
||||
|
||||
> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets.
|
||||
|
||||
1. Open Github [releases page]({{nym_vpn_releases}}) and download the binary for Debian based Linux
|
||||
1. Open Github [releases page]({{nym_vpn_releases}}) and download the CLI latest binary for your system
|
||||
|
||||
2. Verify sha hash of your downloaded binary with the one listed on the [releases page]({{nym_vpn_releases}}). You can use a simple `shasum` command and compare strings (ie with Python) or run in the same directory the following command, exchanging `<SHA_STRING>` with the one of your binary, like in the example:
|
||||
```sh
|
||||
@@ -25,17 +25,12 @@ tar -xvf <BINARY>.tar.gz
|
||||
# tar -xvf nym-vpn-cli_<!-- cmdrun scripts/nym_vpn_cli_version.sh -->_ubuntu-22.04_x86_64.tar.gz
|
||||
```
|
||||
|
||||
4. Make executable by running:
|
||||
4. Make executable:
|
||||
```sh
|
||||
# make sure you are in the right sub-directory
|
||||
chmod u+x ./nym-vpn-cli
|
||||
```
|
||||
|
||||
5. Create Sandbox environment config file by saving [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) as `sandbox.env` in the same directory as your NymVPN binaries by running:
|
||||
```sh
|
||||
curl -o sandbox.env -L https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env
|
||||
```
|
||||
|
||||
## Run NymVPN
|
||||
|
||||
**For NymVPN to work, all other VPNs must be switched off!** At this alpha stage of NymVPN, the network connection (wifi) must be reconnected after or in between the testing rounds.
|
||||
@@ -47,7 +42,7 @@ Make sure your terminal is open in the same directory as your `nym-vpn-cli` bina
|
||||
```sh
|
||||
sudo ./nym-vpn-cli -c ./sandbox.env --entry-gateway-id <ENTRY_GATEWAY_ID> --exit-router-address <EXIT_ROUTER_ADDRESS> --enable-wireguard --private-key <PRIVATE_KEY> --wg-ip <WIREGUARD_IP>
|
||||
```
|
||||
3. To choose different Gateways, visit [nymvpn.com/en/alpha/api/gateways](https://nymvpn.com/en/alpha/api/gateways) and pick one
|
||||
3. To choose different Gateways, visit [explorer.nymtech.net/network-components/gateways](https://explorer.nymtech.net/network-components/gateways) and copy-paste an identity key of your choice
|
||||
4. See all possibilities in [command explanation](#cli-commands-and-options) section below
|
||||
|
||||
In case of errors, see [troubleshooting section](troubleshooting.md).
|
||||
@@ -56,16 +51,16 @@ In case of errors, see [troubleshooting section](troubleshooting.md).
|
||||
|
||||
The basic syntax of `nym-vpn-cli` is:
|
||||
```sh
|
||||
sudo ./nym-vpn-cli -c ./sandbox.env --entry-gateway-id <ENTRY_GATEWAY_ID> --exit-router-address <EXIT_ROUTER_ADDRESS> --enable-wireguard --private-key <PRIVATE_KEY> --wg-ip <WG_IP>
|
||||
sudo ./nym-vpn-cli <--exit-router-address <EXIT_ROUTER_ADDRESS>|--exit-gateway-id <EXIT_GATEWAY_ID>|--exit-gateway-country <EXIT_GATEWAY_COUNTRY>>
|
||||
```
|
||||
* To choose different Gateways, visit [nymvpn.com/en/alpha/api/gateways](https://nymvpn.com/en/alpha/api/gateways)
|
||||
* To choose different Gateways, visit [nymvpn.com/en/alpha/api/gateways](https://explorer.nymtech.net/network-components/gateways)
|
||||
* To see all possibilities run with `--help` flag:
|
||||
```sh
|
||||
./nym-vpn-cli --help
|
||||
```
|
||||
~~~admonish example collapsible=true title="Console output"
|
||||
```sh
|
||||
Usage: nym-vpn-cli [OPTIONS]
|
||||
Usage: nym-vpn-cli [OPTIONS] <--exit-router-address <EXIT_ROUTER_ADDRESS>|--exit-gateway-id <EXIT_GATEWAY_ID>|--exit-gateway-country <EXIT_GATEWAY_COUNTRY>>
|
||||
|
||||
Options:
|
||||
-c, --config-env-file <CONFIG_ENV_FILE>
|
||||
@@ -76,11 +71,13 @@ Options:
|
||||
Mixnet public ID of the entry gateway
|
||||
--entry-gateway-country <ENTRY_GATEWAY_COUNTRY>
|
||||
Auto-select entry gateway by country ISO
|
||||
--entry-gateway-low-latency
|
||||
Auto-select entry gateway by latency
|
||||
--exit-router-address <EXIT_ROUTER_ADDRESS>
|
||||
Mixnet recipient address
|
||||
--exit-gateway-id <EXIT_GATEWAY_ID>
|
||||
|
||||
--exit-router-country <EXIT_ROUTER_COUNTRY>
|
||||
--exit-gateway-country <EXIT_GATEWAY_COUNTRY>
|
||||
Mixnet recipient address
|
||||
--enable-wireguard
|
||||
Enable the wireguard traffic between the client and the entry gateway
|
||||
@@ -88,8 +85,10 @@ Options:
|
||||
Associated private key
|
||||
--wg-ip <WG_IP>
|
||||
The IP address of the wireguard interface used for the first hop to the entry gateway
|
||||
--nym-ip <NYM_IP>
|
||||
The IP address of the nym TUN device that wraps IP packets in sphinx packets
|
||||
--nym-ipv4 <NYM_IPV4>
|
||||
The IPv4 address of the nym TUN device that wraps IP packets in sphinx packets
|
||||
--nym-ipv6 <NYM_IPV6>
|
||||
The IPv6 address of the nym TUN device that wraps IP packets in sphinx packets
|
||||
--nym-mtu <NYM_MTU>
|
||||
The MTU of the nym TUN device that wraps IP packets in sphinx packets
|
||||
--disable-routing
|
||||
@@ -125,3 +124,19 @@ Here is a list of the options and their descriptions. Some are essential, some a
|
||||
- `--ip` is the IP address of the TUN device. That is the IP address of the local private network that is set up between local client and the Exit Gateway.
|
||||
- `--mtu`: The MTU of the TUN device. That is the max IP packet size of the local private network that is set up between local client and the Exit Gateway.
|
||||
- `--disable-routing`: Disable routing all traffic through the VPN TUN device.
|
||||
|
||||
## Testnet environment
|
||||
|
||||
If you want to run NymVPN CLI in Nym Sandbox environment, there are a few adjustments to be done:
|
||||
|
||||
1. Create Sandbox environment config file by saving [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) as `sandbox.env` in the same directory as your NymVPN binaries by running:
|
||||
```sh
|
||||
curl -o sandbox.env -L https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env
|
||||
```
|
||||
|
||||
1. Check available Gateways at [nymvpn.com/en/alpha/api/gateways](https://nymvpn.com/en/alpha/api/gateways)
|
||||
|
||||
2. Run with a flag `-c`
|
||||
```sh
|
||||
sudo ./nym-vpn-cli -c <PATH_TO>/sandbox.env <--exit-router-address <EXIT_ROUTER_ADDRESS>|--exit-gateway-id <EXIT_GATEWAY_ID>|--exit-gateway-country <EXIT_GATEWAY_COUNTRY>>
|
||||
```
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# NymVPN Command Line Interface (CLI)
|
||||
|
||||
```admonish info
|
||||
Our alpha testing round is done with participants at live workshop events. This guide will not work for everyone, as the NymVPN source code is not yet publicly accessible. The alpha testing is done on Nym testnet Sandbox environment, this configuration is limited and will not work in the future.
|
||||
|
||||
**If you commit to test NymVPN alpha, please start with the [user research form]({{nym_vpn_form_url}}) where all the steps will be provided**. If you disagree with any of the conditions listed, please leave this page.
|
||||
```
|
||||
|
||||
Follow the simple [automated script](#automated-script-for-cli-installation) below to install and run NymVPN CLI. If you prefer to do a manual setup follow the steps in the guide for [Linux](cli-linux.md) or [MacOS](cli-mac.md).
|
||||
|
||||
Visit NymVPN alpha latest [release page]({{nym_vpn_releases}}) to check sha sums or download the binaries directly.
|
||||
|
||||
## Automated Script for CLI Installation
|
||||
|
||||
We wrote a [script](https://gist.github.com/serinko/d65450653d6bbafacbcee71c9cb8fb31) which does download of the CLI, sha256 verification, extraction, installation and configuration for Linux and MacOS users automatically following the steps below:
|
||||
|
||||
1. Open a terminal window in a directory where you want the script and NymVPN CLI binary be downloaded and run
|
||||
```sh
|
||||
curl -o execute-nym-vpn-cli-binary.sh -L https://gist.githubusercontent.com/tommyv1987/87267ded27e1eb7651aa9cc745ddf4af/raw/d39f98dbb36ccff761a7e940073388a6fe7b73fe/execute-nym-vpn-cli-binary.sh && chmod u+x execute-nym-vpn-cli-binary.sh && sudo -E ./execute-nym-vpn-cli-binary.sh
|
||||
```
|
||||
|
||||
2. Follow the prompts in the program
|
||||
|
||||
3. The script will automatically start the client. Make sure to **turn off any other VPNs** and follow the prompts:
|
||||
|
||||
* It prints a JSON view of existing Gateways and prompt you to:
|
||||
- *Make sure to use two different Gateways for entry and exit!*
|
||||
- `enter a gateway ID:` paste one of the values labeled with a key `"identityKey"` printed above (without `" "`)
|
||||
- `enter an exit address:` paste one of the values labeled with a key `"address"` printed above (without `" "`)
|
||||
- `do you want five hop or two hop?`: type `five` or `two`
|
||||
- `enable WireGuard? (yes/no):` if you chose yes, find your private key and wireguard IP [here](https://nymvpn.com/en/alpha)
|
||||
|
||||
To run `nym-vpn-cli` again, reconnect your wifi, move to the directory of your CLI binary `cd ~/nym-vpn-cli-dir` and follow the guide for [Linux](cli-linux.md#run-nymvpn) or [MacOS](cli-mac.md#run-nymvpn). If you find it too difficult, just run this script again - like in step \#3 above.
|
||||
|
||||
In case of errors check out the [troubleshooting](troubleshooting.md) section.
|
||||
@@ -1,35 +1,151 @@
|
||||
# NymVPN Command Line Interface (CLI)
|
||||
# NymVPN alpha CLI Guide
|
||||
|
||||
```admonish info
|
||||
Our alpha testing round is done with participants at live workshop events. This guide will not work for everyone, as the NymVPN source code is not yet publicly accessible. The alpha testing is done on Nym testnet Sandbox environment, this configuration is limited and will not work in the future.
|
||||
|
||||
**If you commit to test NymVPN alpha, please start with the [user research form]({{nym_vpn_form_url}}) where all the steps will be provided**. If you disagree with any of the conditions listed, please leave this page.
|
||||
NymVPN is an experimental software and it's for testing purposes only. All users testing the client are expected to sign GDPR Information Sheet and Consent Form (shared at the workshop) so we use their results to improve the client, and submit the form [*NymVPN User research*]({{nym_vpn_form_url}}) with the testing results.
|
||||
```
|
||||
|
||||
Follow the simple [automated script](#automated-script-for-cli-installation) below to install and run NymVPN CLI. If you prefer to do a manual setup follow the steps in the guide for [Linux](cli-linux.md) or [MacOS](cli-mac.md).
|
||||
## Installation
|
||||
|
||||
Visit NymVPN alpha latest [release page]({{nym_vpn_releases}}) to check sha sums or download the binaries directly.
|
||||
> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets.
|
||||
|
||||
## Automated Script for CLI Installation
|
||||
1. Open Github [releases page]({{nym_vpn_releases}}) and download the CLI latest binary for your system
|
||||
|
||||
We wrote a [script](https://gist.github.com/serinko/d65450653d6bbafacbcee71c9cb8fb31) which does download of the CLI, sha256 verification, extraction, installation and configuration for Linux and MacOS users automatically following the steps below:
|
||||
|
||||
1. Open a terminal window in a directory where you want the script and NymVPN CLI binary be downloaded and run
|
||||
2. Verify sha hash of your downloaded binary with the one listed on the [releases page]({{nym_vpn_releases}}). You can use a simple `shasum` command and compare strings (ie with Python) or run in the same directory the following command, exchanging `<SHA_STRING>` with the one of your binary, like in the example:
|
||||
```sh
|
||||
curl -o execute-nym-vpn-cli-binary.sh -L https://gist.githubusercontent.com/tommyv1987/87267ded27e1eb7651aa9cc745ddf4af/raw/d39f98dbb36ccff761a7e940073388a6fe7b73fe/execute-nym-vpn-cli-binary.sh && chmod u+x execute-nym-vpn-cli-binary.sh && sudo -E ./execute-nym-vpn-cli-binary.sh
|
||||
echo "<SHA_STRING>" | shasum -a 256 -c
|
||||
|
||||
# choose a correct one according to your binary, this is just an example
|
||||
# echo "0e4abb461e86b2c168577e0294112a3bacd3a24bf8565b49783bfebd9b530e23 nym-vpn-cli_<!-- cmdrun scripts/nym_vpn_cli_version.sh -->_ubuntu-22.04_amd64.tar.gz" | shasum -a 256 -c
|
||||
```
|
||||
|
||||
2. Follow the prompts in the program
|
||||
3. Extract files:
|
||||
```sh
|
||||
tar -xvf <BINARY>.tar.gz
|
||||
# for example
|
||||
# tar -xvf nym-vpn-cli_<!-- cmdrun scripts/nym_vpn_cli_version.sh -->_ubuntu-22.04_x86_64.tar.gz
|
||||
```
|
||||
|
||||
3. The script will automatically start the client. Make sure to **turn off any other VPNs** and follow the prompts:
|
||||
4. Make executable:
|
||||
```sh
|
||||
# make sure you are in the right sub-directory
|
||||
chmod u+x nym-vpn-cli
|
||||
```
|
||||
|
||||
* It prints a JSON view of existing Gateways and prompt you to:
|
||||
- *Make sure to use two different Gateways for entry and exit!*
|
||||
- `enter a gateway ID:` paste one of the values labeled with a key `"identityKey"` printed above (without `" "`)
|
||||
- `enter an exit address:` paste one of the values labeled with a key `"address"` printed above (without `" "`)
|
||||
- `do you want five hop or two hop?`: type `five` or `two`
|
||||
- `enable WireGuard? (yes/no):` if you chose yes, find your private key and wireguard IP [here](https://nymvpn.com/en/alpha)
|
||||
## Run NymVPN
|
||||
|
||||
To run `nym-vpn-cli` again, reconnect your wifi, move to the directory of your CLI binary `cd ~/nym-vpn-cli-dir` and follow the guide for [Linux](cli-linux.md#run-nymvpn) or [MacOS](cli-mac.md#run-nymvpn). If you find it too difficult, just run this script again - like in step \#3 above.
|
||||
**For NymVPN to work, all other VPNs must be switched off!** At this alpha stage of NymVPN, the network connection (wifi) must be reconnected after or in between the testing rounds.
|
||||
|
||||
In case of errors check out the [troubleshooting](troubleshooting.md) section.
|
||||
Make sure your terminal is open in the same directory as your `nym-vpn-cli` binary.
|
||||
|
||||
1. Run it as root with `sudo` - the command will look like this with specified arguments:
|
||||
```sh
|
||||
# choose only one conditional --argument listed in {brackets}
|
||||
sudo ./nym-vpn-cli { --exit-router-address <EXIT_ROUTER_ADDRESS>|--exit-gateway-id <EXIT_GATEWAY_ID>|--exit-gateway-country <EXIT_GATEWAY_COUNTRY> }
|
||||
```
|
||||
|
||||
2. To choose different Gateways, visit [explorer.nymtech.net/network-components/gateways](https://explorer.nymtech.net/network-components/gateways) and copy-paste an identity key of your choice
|
||||
|
||||
```admonish note
|
||||
Nym Exit Gateway functionality was implemented just recently and not all the Gateways are upgraded and ready to handle the VPN connections. If you want to make sure you are connecting to a Gateway with an embedded Network Requester, IP Packet Router and applied Nym exit policy, visit [this page](https://nymtech.net/events/fast-and-furious), scroll down to the list and search Gateways with all the functionalities enabled.
|
||||
```
|
||||
|
||||
3. See all possibilities in [command explanation](#cli-commands-and-options) section below
|
||||
|
||||
4. In case of errors, see [troubleshooting section](troubleshooting.md)
|
||||
|
||||
|
||||
### CLI Commands and Options
|
||||
|
||||
The basic syntax of `nym-vpn-cli` is:
|
||||
```sh
|
||||
# choose only one conditional --argument listed in {brackets}
|
||||
sudo ./nym-vpn-cli { --exit-router-address <EXIT_ROUTER_ADDRESS>|--exit-gateway-id <EXIT_GATEWAY_ID>|--exit-gateway-country <EXIT_GATEWAY_COUNTRY> }
|
||||
```
|
||||
|
||||
To see all the possibilities run with `--help` flag:
|
||||
```sh
|
||||
./nym-vpn-cli --help
|
||||
```
|
||||
~~~admonish example collapsible=true title="Console output"
|
||||
```sh
|
||||
Usage: nym-vpn-cli [OPTIONS] <--exit-router-address <EXIT_ROUTER_ADDRESS>|--exit-gateway-id <EXIT_GATEWAY_ID>|--exit-gateway-country <EXIT_GATEWAY_COUNTRY>>
|
||||
|
||||
Options:
|
||||
-c, --config-env-file <CONFIG_ENV_FILE>
|
||||
Path pointing to an env file describing the network
|
||||
--mixnet-client-path <MIXNET_CLIENT_PATH>
|
||||
Path to the data directory of a previously initialised mixnet client, where the keys reside
|
||||
--entry-gateway-id <ENTRY_GATEWAY_ID>
|
||||
Mixnet public ID of the entry gateway
|
||||
--entry-gateway-country <ENTRY_GATEWAY_COUNTRY>
|
||||
Auto-select entry gateway by country ISO
|
||||
--entry-gateway-low-latency
|
||||
Auto-select entry gateway by latency
|
||||
--exit-router-address <EXIT_ROUTER_ADDRESS>
|
||||
Mixnet recipient address
|
||||
--exit-gateway-id <EXIT_GATEWAY_ID>
|
||||
|
||||
--exit-gateway-country <EXIT_GATEWAY_COUNTRY>
|
||||
Mixnet recipient address
|
||||
--enable-wireguard
|
||||
Enable the wireguard traffic between the client and the entry gateway
|
||||
--private-key <PRIVATE_KEY>
|
||||
Associated private key
|
||||
--wg-ip <WG_IP>
|
||||
The IP address of the wireguard interface used for the first hop to the entry gateway
|
||||
--nym-ipv4 <NYM_IPV4>
|
||||
The IPv4 address of the nym TUN device that wraps IP packets in sphinx packets
|
||||
--nym-ipv6 <NYM_IPV6>
|
||||
The IPv6 address of the nym TUN device that wraps IP packets in sphinx packets
|
||||
--nym-mtu <NYM_MTU>
|
||||
The MTU of the nym TUN device that wraps IP packets in sphinx packets
|
||||
--disable-routing
|
||||
Disable routing all traffic through the nym TUN device. When the flag is set, the nym TUN device will be created, but to route traffic through it you will need to do it manually, e.g. ping -Itun0
|
||||
--enable-two-hop
|
||||
Enable two-hop mixnet traffic. This means that traffic jumps directly from entry gateway to exit gateway
|
||||
--enable-poisson-rate
|
||||
Enable Poisson process rate limiting of outbound traffic
|
||||
--disable-background-cover-traffic
|
||||
Disable constant rate background loop cover traffic
|
||||
-h, --help
|
||||
Print help
|
||||
-V, --version
|
||||
Print version
|
||||
```
|
||||
~~~
|
||||
|
||||
Here is a list of the options and their descriptions. Some are essential, some are more technical and not needed to be adjusted by users.
|
||||
|
||||
**Fundamental commands and arguments**
|
||||
|
||||
- `--entry-gateway-id`: paste one of the values labeled with a key `"identityKey"` (without `" "`)
|
||||
- `--exit-gateway-id`: paste one of the values labeled with a key `"identityKey"` (without `" "`)
|
||||
- `--exit-router-address`: paste one of the values labeled with a key `"address"` (without `" "`)
|
||||
- `--enable-wireguard`: Enable the wireguard traffic between the client and the entry gateway. NymVPN uses Mullvad libraries for wrapping `wireguard-go` and to setup local routing rules to route all traffic to the TUN virtual network device
|
||||
- `--wg-ip`: The address of the wireguard interface, you can get it [here](https://nymvpn.com/en/alpha)
|
||||
- `--private-key`: get your private key for testing purposes [here](https://nymvpn.com/en/alpha)
|
||||
- `--enable-two-hop` is a faster setup where the traffic is routed from the client to Entry Gateway and directly to Exit Gateway (default is 5-hops)
|
||||
|
||||
**Advanced options**
|
||||
|
||||
- `-c` is a path to an enviroment config, like [`sandbox.env`](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env)
|
||||
- `--enable-poisson`: Enables process rate limiting of outbound traffic (disabled by default). It means that NymVPN client will send packets at a steady stream to the Entry Gateway. By default it's on average one sphinx packet per 20ms, but there is some randomness (poisson distribution). When there are no real data to fill the sphinx packets with, cover packets are generated instead.
|
||||
- `--ip` is the IP address of the TUN device. That is the IP address of the local private network that is set up between local client and the Exit Gateway.
|
||||
- `--mtu`: The MTU of the TUN device. That is the max IP packet size of the local private network that is set up between local client and the Exit Gateway.
|
||||
- `--disable-routing`: Disable routing all traffic through the VPN TUN device.
|
||||
|
||||
## Testnet environment
|
||||
|
||||
If you want to run NymVPN CLI in Nym Sandbox environment, there are a few adjustments to be done:
|
||||
|
||||
1. Create Sandbox environment config file by saving [this](https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env) as `sandbox.env` in the same directory as your NymVPN binaries:
|
||||
```sh
|
||||
curl -o sandbox.env -L https://raw.githubusercontent.com/nymtech/nym/develop/envs/sandbox.env
|
||||
```
|
||||
|
||||
1. Check available Gateways at [nymvpn.com/en/alpha/api/gateways](https://nymvpn.com/en/alpha/api/gateways)
|
||||
|
||||
2. Run with a flag `-c`
|
||||
```sh
|
||||
sudo ./nym-vpn-cli -c <PATH_TO>/sandbox.env <--exit-router-address <EXIT_ROUTER_ADDRESS>|--exit-gateway-id <EXIT_GATEWAY_ID>|--exit-gateway-country <EXIT_GATEWAY_COUNTRY>>
|
||||
```
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# NymVPN - Desktop (GUI)
|
||||
|
||||
```admonish info
|
||||
Our alpha testing round is done with participants at live workshop events. This guide will not work for everyone, as the NymVPN source code is not yet publicly accessible. The alpha testing is done on Nym testnet Sandbox environment, this configuration is limited and will not work in the future.
|
||||
Our alpha testing round is done with participants at live workshop events and the application in this stage may not work for everyone.
|
||||
|
||||
**If you commit to test NymVPN alpha, please start with the [user research form]({{nym_vpn_form_url}}) where all the steps will be provided**. If you disagree with any of the conditions listed, please leave this page.
|
||||
```
|
||||
|
||||
This is the alpha version of NymVPN desktop application (GUI). A demo of how the client will look like for majority of day-to-day users.
|
||||
This is a desktop (GUI) version of NymVPN client. A demo of how the application will look like for majority of day-to-day users.
|
||||
|
||||
Follow the simple [automated script](#automated-script-for-gui-installation) below to install and run NymVPN GUI. If the script didn't work for your distribution or you prefer to do a manual setup follow the steps in the guide for [Linux](gui-linux.md) or [MacOS](gui-mac.md) .
|
||||
|
||||
@@ -14,12 +14,12 @@ Visit NymVPN alpha latest [release page]({{nym_vpn_releases}}) to check sha sums
|
||||
|
||||
## Linux AppImage Automated Installation Method
|
||||
|
||||
The latest releases contain `appimage.sh` script. This method makes the installation simple for Linux users who want to run NymVPN from AppImmage. Executing the command below will download the binary to `~/.local/bin` and verify the checksum.
|
||||
The latest releases contain `appimage.sh` script. This method makes the installation simple for Linux users who want to run NymVPN from AppImmage. Executing the command below will download the binary to `~/.local/bin` and verify the checksum:
|
||||
```sh
|
||||
curl -fsSL https://github.com/nymtech/nym-vpn-client/releases/download/nym-vpn-desktop-v<!-- cmdrun scripts/nym_vpn_desktop_version.sh -->/appimage.sh | bash
|
||||
```
|
||||
|
||||
Run with the command
|
||||
Run with the command:
|
||||
```sh
|
||||
sudo -E ~/.local/bin/nym-vpn.appimage
|
||||
```
|
||||
|
||||
@@ -5,16 +5,15 @@
|
||||
**Nym proudly presents NymVPN alpha** - a client that uses [Nym Mixnet](https://nymtech.net) to anonymise all of a user's internet traffic through either a 5-hop mixnet (for a full network privacy) or the faster 2-hop decentralised VPN (with some extra features).
|
||||
|
||||
|
||||
**You are invited to take part in the alpha testing** of this new application. The following pages provide a how-to guide, explaining steps to install and run NymVPN [CLI](cli.md) and [GUI](gui.md) on the Sandbox testnet environment.
|
||||
**You are invited to take part in the alpha testing** of this new application. The following pages provide a how-to guide, explaining steps to install and run NymVPN [CLI](cli.md) and [GUI](gui.md).
|
||||
|
||||
**Here is how**
|
||||
|
||||
1. Go to the NymVPN [testers form]({{nym_vpn_form_url}})
|
||||
2. Please consent to the GDPR so we can use the results
|
||||
2. Fill and submit the [form!]({{nym_vpn_form_url}})
|
||||
3. To test the GUI, [go here](gui.md)
|
||||
4. To test the CLI, [go here](cli.md)
|
||||
5. Fill and submit the [form!]({{nym_vpn_form_url}})
|
||||
6. Join the [NymVPN matrix channel](https://matrix.to/#/#NymVPN:nymtech.chat) if you have any questions, comments or blockers
|
||||
5. Join the [NymVPN matrix channel](https://matrix.to/#/#NymVPN:nymtech.chat) if you have any questions, comments or blockers
|
||||
|
||||
***NymVPN alpha testing will last from 15th of January - 15th of February.***
|
||||
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
|
||||
Below are listed some points which may need to be addressed when testing NymVPN alpha. If you crashed into any errors which are not listed, please contact us at the testing workshop or in the NymVPN [Matrix channel](https://matrix.to/#/#NymVPN:nymtech.chat).
|
||||
|
||||
#### NymVPN attempts to connect to sandbox testnet
|
||||
|
||||
If you testing the latest versions and you correctly expect the client to run over the mainnet, but it listens to `https://sandbox-nym-api1.nymtech.net`, it's probably because of your previous configuration.
|
||||
|
||||
Check your `config.toml` either in the directory from which you run your client or in `~/.config/nym-vpn/` and remove `sandbox.env` from the config file and folder.
|
||||
|
||||
If the problem persists (probably due to some locally cache) download [`mainnet.env`](https://github.com/nymtech/nym/blob/master/envs/mainnet.env) and save it to the same directory.
|
||||
|
||||
#### Running GUI failed due to `TOML parse error`
|
||||
|
||||
If you see this error when running NymVPN alpha desktop, it's because the older versions needed entry location in `config.toml` configuration file. From `v0.0.3` the entry location is selected directly by the user in the application. This error is due to an old `app-data.toml` config in your computer.
|
||||
|
||||
+4
-4
@@ -13,10 +13,10 @@ DENOMS_EXPONENT=6
|
||||
REWARDING_VALIDATOR_ADDRESS=n1pefc2utwpy5w78p2kqdsfmpjxfwmn9d39k5mqa
|
||||
MIXNET_CONTRACT_ADDRESS=n1xr3rq8yvd7qplsw5yx90ftsr2zdhg4e9z60h5duusgxpv72hud3sjkxkav
|
||||
VESTING_CONTRACT_ADDRESS=n1unyuj8qnmygvzuex3dwmg9yzt9alhvyeat0uu0jedg2wj33efl5qackslz
|
||||
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n13902g92xfefeyzuyed49snlm5fxv5ms6mdq5kvrut27hasdw5a9q9vyw6c
|
||||
GROUP_CONTRACT_ADDRESS=n18nczmqw6adwxg2wnlef3hf0etf8anccafp2pjpul5rrtmv96umyq5mv7t5
|
||||
MULTISIG_CONTRACT_ADDRESS=n1q3zzxl78rlmxv3vn0uf4vkyz285lk8q2xzne299yt9x6mpfgk90qukuzmv
|
||||
COCONUT_DKG_CONTRACT_ADDRESS=n1jsz20ggp5a6v76j060erkzvxmeus8htlpl77yxp878f0gf95cyaq6p2pee
|
||||
COCONUT_BANDWIDTH_CONTRACT_ADDRESS=n1ljlwey4xdj0zs7zueepc48nkr033fca6fjgvurfvttqegm8dvsrswsul70
|
||||
GROUP_CONTRACT_ADDRESS=n10v3rjnq4cjyccfykyams68ztce337gksuu6f0lvtl4meuwvkewaqru4uav
|
||||
MULTISIG_CONTRACT_ADDRESS=n1cemnu8as0ls45v3caunpesl8jlsfw2ff9rlwnltlecp7zrxct4dsqc2y42
|
||||
COCONUT_DKG_CONTRACT_ADDRESS=n1zx96qgd88vqlzcxkpwzks7kqs5ctrx36xtzfc58p7q6c4ng9anlqzc4nh8
|
||||
NAME_SERVICE_CONTRACT_ADDRESS=n12ne7qtmdwd0j03t9t5es8md66wq4e5xg9neladrsag8fx3y89rcs36asfp
|
||||
SERVICE_PROVIDER_DIRECTORY_CONTRACT_ADDRESS=n1ps5yutd7sufwg058qd7ac7ldnlazsvmhzqwucsfxmm445d70u8asqxpur4
|
||||
EPHEMERA_CONTRACT_ADDRESS=n19lc9u84cz0yz3fww5283nucc9yvr8gsjmgeul0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "explorer-api"
|
||||
version = "1.1.33"
|
||||
version = "1.1.34"
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
|
||||
|
||||
+3
-2
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-gateway"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.33"
|
||||
version = "1.1.35"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
@@ -38,6 +38,7 @@ sqlx = { workspace = true, features = [
|
||||
"sqlite",
|
||||
"macros",
|
||||
"migrate",
|
||||
"time"
|
||||
] }
|
||||
subtle-encoding = { version = "0.5", features = ["bech32-preview"] }
|
||||
thiserror = { workspace = true }
|
||||
@@ -49,7 +50,7 @@ tokio = { workspace = true, features = [
|
||||
"time",
|
||||
] }
|
||||
tokio-stream = { version = "0.1.11", features = ["fs"] }
|
||||
tokio-tungstenite = { vworkspace = true }
|
||||
tokio-tungstenite = { workspace = true }
|
||||
tokio-util = { workspace = true, features = ["codec"] }
|
||||
url = { workspace = true, features = ["serde"] }
|
||||
time = { workspace = true }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum HandshakeError {
|
||||
#[error(
|
||||
"received key material of invalid length - {0}. Expected: {}",
|
||||
|
||||
@@ -190,6 +190,22 @@ impl ClientControlRequest {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> String {
|
||||
match self {
|
||||
ClientControlRequest::Authenticate { .. } => "Authenticate".to_string(),
|
||||
ClientControlRequest::RegisterHandshakeInitRequest { .. } => {
|
||||
"RegisterHandshakeInitRequest".to_string()
|
||||
}
|
||||
ClientControlRequest::BandwidthCredential { .. } => "BandwidthCredential".to_string(),
|
||||
ClientControlRequest::BandwidthCredentialV2 { .. } => {
|
||||
"BandwidthCredentialV2".to_string()
|
||||
}
|
||||
ClientControlRequest::ClaimFreeTestnetBandwidth => {
|
||||
"ClaimFreeTestnetBandwidth".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_enc_coconut_bandwidth_credential_v1(
|
||||
credential: &OldV1Credential,
|
||||
shared_key: &SharedKeys,
|
||||
@@ -291,6 +307,15 @@ pub enum ServerResponse {
|
||||
}
|
||||
|
||||
impl ServerResponse {
|
||||
pub fn name(&self) -> String {
|
||||
match self {
|
||||
ServerResponse::Authenticate { .. } => "Authenticate".to_string(),
|
||||
ServerResponse::Register { .. } => "Register".to_string(),
|
||||
ServerResponse::Bandwidth { .. } => "Bandwidth".to_string(),
|
||||
ServerResponse::Send { .. } => "Send".to_string(),
|
||||
ServerResponse::Error { .. } => "Error".to_string(),
|
||||
}
|
||||
}
|
||||
pub fn new_error<S: Into<String>>(msg: S) -> Self {
|
||||
ServerResponse::Error {
|
||||
message: msg.into(),
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
/*
|
||||
* Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
ALTER TABLE available_bandwidth
|
||||
ADD COLUMN freepass_expiration TIMESTAMP WITHOUT TIME ZONE;
|
||||
@@ -42,6 +42,9 @@ const DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE: usize = 2000;
|
||||
const DEFAULT_STORED_MESSAGE_FILENAME_LENGTH: u16 = 16;
|
||||
const DEFAULT_MESSAGE_RETRIEVAL_LIMIT: i64 = 100;
|
||||
|
||||
const DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE: Duration = Duration::from_millis(5);
|
||||
const DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT: i64 = 512 * 1024; // 512kB
|
||||
|
||||
/// Derive default path to gateway's config directory.
|
||||
/// It should get resolved to `$HOME/.nym/gateways/<id>/config`
|
||||
pub fn default_config_directory<P: AsRef<Path>>(id: P) -> PathBuf {
|
||||
@@ -516,6 +519,13 @@ pub struct Debug {
|
||||
/// Number of messages from offline client that can be pulled at once from the storage.
|
||||
pub message_retrieval_limit: i64,
|
||||
|
||||
/// Defines maximum delay between client bandwidth information being flushed to the persistent storage.
|
||||
#[serde(with = "humantime_serde")]
|
||||
pub client_bandwidth_max_flushing_rate: Duration,
|
||||
|
||||
/// Defines a maximum change in client bandwidth before it gets flushed to the persistent storage.
|
||||
pub client_bandwidth_max_delta_flushing_amount: i64,
|
||||
|
||||
/// Specifies whether the mixnode should be using the legacy framing for the sphinx packets.
|
||||
// it's set to true by default. The reason for that decision is to preserve compatibility with the
|
||||
// existing nodes whilst everyone else is upgrading and getting the code for handling the new field.
|
||||
@@ -533,6 +543,9 @@ impl Default for Debug {
|
||||
maximum_connection_buffer_size: DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
|
||||
stored_messages_filename_length: DEFAULT_STORED_MESSAGE_FILENAME_LENGTH,
|
||||
message_retrieval_limit: DEFAULT_MESSAGE_RETRIEVAL_LIMIT,
|
||||
client_bandwidth_max_flushing_rate: DEFAULT_CLIENT_BANDWIDTH_MAX_FLUSHING_RATE,
|
||||
client_bandwidth_max_delta_flushing_amount:
|
||||
DEFAULT_CLIENT_BANDWIDTH_MAX_DELTA_FLUSHING_AMOUNT,
|
||||
use_legacy_framed_packet_version: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,6 +165,7 @@ impl From<ConfigV1_1_31> for Config {
|
||||
stored_messages_filename_length: value.debug.stored_messages_filename_length,
|
||||
message_retrieval_limit: value.debug.message_retrieval_limit,
|
||||
use_legacy_framed_packet_version: value.debug.use_legacy_framed_packet_version,
|
||||
..Default::default()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ fn load_gateway_details(
|
||||
};
|
||||
|
||||
Ok(api_requests::v1::gateway::models::Gateway {
|
||||
enforces_zk_nyms: config.gateway.only_coconut_credentials,
|
||||
client_interfaces: api_requests::v1::gateway::models::ClientInterfaces {
|
||||
wireguard,
|
||||
mixnet_websockets: Some(api_requests::v1::gateway::models::WebSockets {
|
||||
|
||||
@@ -42,18 +42,35 @@ pub struct Bandwidth {
|
||||
}
|
||||
|
||||
impl Bandwidth {
|
||||
pub const fn new(value: u64) -> Bandwidth {
|
||||
pub const fn new_unchecked(value: u64) -> Bandwidth {
|
||||
Bandwidth { value }
|
||||
}
|
||||
|
||||
pub fn try_from_raw_value(value: &str, typ: CredentialType) -> Result<Self, BandwidthError> {
|
||||
let bandwidth_value =
|
||||
pub fn new(bandwidth_value: u64) -> Result<Bandwidth, BandwidthError> {
|
||||
if bandwidth_value > i64::MAX as u64 {
|
||||
// note that this would have represented more than 1 exabyte,
|
||||
// which is like 125,000 worth of hard drives, so I don't think we have
|
||||
// to worry about it for now...
|
||||
warn!("Somehow we received bandwidth value higher than 9223372036854775807. We don't really want to deal with this now");
|
||||
return Err(BandwidthError::UnsupportedBandwidthValue(bandwidth_value));
|
||||
}
|
||||
|
||||
Ok(Bandwidth {
|
||||
value: bandwidth_value,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn parse_raw_bandwidth(
|
||||
value: &str,
|
||||
typ: CredentialType,
|
||||
) -> Result<(u64, Option<OffsetDateTime>), BandwidthError> {
|
||||
let (bandwidth_value, freepass_expiration) =
|
||||
match typ {
|
||||
CredentialType::Voucher => {
|
||||
let token_value: u64 = value
|
||||
.parse()
|
||||
.map_err(|source| BandwidthError::VoucherValueParsingFailure { source })?;
|
||||
token_value * nym_network_defaults::BYTES_PER_UTOKEN
|
||||
(token_value * nym_network_defaults::BYTES_PER_UTOKEN, None)
|
||||
}
|
||||
CredentialType::FreePass => {
|
||||
let expiry_timestamp: i64 = value
|
||||
@@ -70,21 +87,10 @@ impl Bandwidth {
|
||||
if expiry_date < now {
|
||||
return Err(BandwidthError::ExpiredFreePass { expiry_date });
|
||||
}
|
||||
nym_network_defaults::BYTES_PER_FREEPASS
|
||||
(nym_network_defaults::BYTES_PER_FREEPASS, Some(expiry_date))
|
||||
}
|
||||
};
|
||||
|
||||
if bandwidth_value > i64::MAX as u64 {
|
||||
// note that this would have represented more than 1 exabyte,
|
||||
// which is like 125,000 worth of hard drives, so I don't think we have
|
||||
// to worry about it for now...
|
||||
warn!("Somehow we received bandwidth value higher than 9223372036854775807. We don't really want to deal with this now");
|
||||
return Err(BandwidthError::UnsupportedBandwidthValue(bandwidth_value));
|
||||
}
|
||||
|
||||
Ok(Bandwidth {
|
||||
value: bandwidth_value,
|
||||
})
|
||||
Ok((bandwidth_value, freepass_expiration))
|
||||
}
|
||||
|
||||
pub fn value(&self) -> u64 {
|
||||
|
||||
@@ -8,4 +8,5 @@ mod bandwidth;
|
||||
pub(crate) mod embedded_clients;
|
||||
pub(crate) mod websocket;
|
||||
|
||||
pub(crate) const FREE_TESTNET_BANDWIDTH_VALUE: Bandwidth = Bandwidth::new(64 * 1024 * 1024 * 1024); // 64GB
|
||||
pub(crate) const FREE_TESTNET_BANDWIDTH_VALUE: Bandwidth =
|
||||
Bandwidth::new_unchecked(64 * 1024 * 1024 * 1024); // 64GB
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier;
|
||||
use crate::node::client_handling::websocket::connection_handler::BandwidthFlushingBehaviourConfig;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use std::sync::Arc;
|
||||
|
||||
// I can see this being possible expanded with say storage or client store
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct CommonHandlerState {
|
||||
pub(crate) coconut_verifier: Arc<CoconutVerifier>,
|
||||
pub(crate) local_identity: Arc<identity::KeyPair>,
|
||||
pub(crate) only_coconut_credentials: bool,
|
||||
pub(crate) bandwidth_cfg: BandwidthFlushingBehaviourConfig,
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::client_handling::bandwidth::BandwidthError;
|
||||
use crate::node::client_handling::websocket::connection_handler::ClientBandwidth;
|
||||
use crate::node::{
|
||||
client_handling::{
|
||||
bandwidth::Bandwidth,
|
||||
@@ -34,6 +35,7 @@ use nym_validator_client::coconut::CoconutApiError;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::{process, time::Duration};
|
||||
use thiserror::Error;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio_tungstenite::tungstenite::{protocol::Message, Error as WsError};
|
||||
|
||||
@@ -42,6 +44,11 @@ pub enum RequestHandlingError {
|
||||
#[error("Internal gateway storage error")]
|
||||
StorageError(#[from] StorageError),
|
||||
|
||||
#[error(
|
||||
"the database entry for bandwidth of the registered client {client_address} is missing!"
|
||||
)]
|
||||
MissingClientBandwidthEntry { client_address: String },
|
||||
|
||||
#[error("Provided bandwidth IV is malformed - {0}")]
|
||||
MalformedIV(#[from] IVConversionError),
|
||||
|
||||
@@ -51,8 +58,8 @@ pub enum RequestHandlingError {
|
||||
#[error("Provided binary request was malformed - {0}")]
|
||||
InvalidTextRequest(<ClientControlRequest as TryFrom<String>>::Error),
|
||||
|
||||
#[error("The received request is not valid in the current context")]
|
||||
IllegalRequest,
|
||||
#[error("The received request is not valid in the current context: {additional_context}")]
|
||||
IllegalRequest { additional_context: String },
|
||||
|
||||
#[error("Provided bandwidth credential did not verify correctly on {0}")]
|
||||
InvalidBandwidthCredential(String),
|
||||
@@ -90,9 +97,18 @@ pub enum RequestHandlingError {
|
||||
#[error("the provided credential did not contain a valid type attribute")]
|
||||
InvalidTypeAttribute,
|
||||
|
||||
#[error("insufficient bandwidth available to process the request. required: {required}B, available: {available}B")]
|
||||
OutOfBandwidth { required: i64, available: i64 },
|
||||
|
||||
#[error("the provided credential did not have a bandwidth attribute")]
|
||||
MissingBandwidthAttribute,
|
||||
|
||||
#[error("attempted to claim a bandwidth voucher for an account using a free pass (it expires on {expiration})")]
|
||||
BandwidthVoucherForFreePassAccount { expiration: OffsetDateTime },
|
||||
|
||||
#[error("attempted to claim another free pass for the account while another free pass is still active (it expires on {expiration})")]
|
||||
PreexistingFreePass { expiration: OffsetDateTime },
|
||||
|
||||
#[error("the DKG contract is unavailable")]
|
||||
UnavailableDkgContract,
|
||||
}
|
||||
@@ -121,6 +137,7 @@ impl IntoWSMessage for Result<ServerResponse, RequestHandlingError> {
|
||||
pub(crate) struct AuthenticatedHandler<R, S, St> {
|
||||
inner: FreshHandler<R, S, St>,
|
||||
client: ClientDetails,
|
||||
client_bandwidth: ClientBandwidth,
|
||||
mix_receiver: MixMessageReceiver,
|
||||
// Occasionally the handler is requested to ping the connected client for confirm that it's
|
||||
// active, such as when a duplicate connection is detected. This hashmap stores the oneshot
|
||||
@@ -153,19 +170,32 @@ where
|
||||
/// * `fresh`: fresh, unauthenticated, connection handler.
|
||||
/// * `client`: details (i.e. address and shared keys) of the registered client
|
||||
/// * `mix_receiver`: channel used for receiving messages from the mixnet destined for this client.
|
||||
pub(crate) fn upgrade(
|
||||
pub(crate) async fn upgrade(
|
||||
fresh: FreshHandler<R, S, St>,
|
||||
client: ClientDetails,
|
||||
mix_receiver: MixMessageReceiver,
|
||||
is_active_request_receiver: IsActiveRequestReceiver,
|
||||
) -> Self {
|
||||
AuthenticatedHandler {
|
||||
) -> Result<Self, RequestHandlingError> {
|
||||
// note: the `upgrade` function can only be called after registering or authenticating the client,
|
||||
// meaning the appropriate database rows must have been created
|
||||
// so in theory we could just unwrap the value here, but since we're returning a Result anyway,
|
||||
// we might as well return a failure response instead
|
||||
let bandwidth = fresh
|
||||
.storage
|
||||
.get_available_bandwidth(client.address)
|
||||
.await?
|
||||
.ok_or(RequestHandlingError::MissingClientBandwidthEntry {
|
||||
client_address: client.address.as_base58_string(),
|
||||
})?;
|
||||
|
||||
Ok(AuthenticatedHandler {
|
||||
inner: fresh,
|
||||
client,
|
||||
client_bandwidth: ClientBandwidth::new(bandwidth.into()),
|
||||
mix_receiver,
|
||||
is_active_request_receiver,
|
||||
is_active_ping_pending_reply: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Explicitly removes handle from the global store.
|
||||
@@ -175,15 +205,10 @@ where
|
||||
.disconnect(self.client.address)
|
||||
}
|
||||
|
||||
/// Checks the amount of bandwidth available for the connected client.
|
||||
async fn get_available_bandwidth(&self) -> Result<i64, RequestHandlingError> {
|
||||
let bandwidth = self
|
||||
.inner
|
||||
.storage
|
||||
.get_available_bandwidth(self.client.address)
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
Ok(bandwidth)
|
||||
async fn expire_freepass(&mut self) -> Result<(), RequestHandlingError> {
|
||||
self.client_bandwidth.bandwidth = Default::default();
|
||||
self.client_bandwidth.update_flush_data();
|
||||
Ok(self.inner.expire_freepass(self.client.address).await?)
|
||||
}
|
||||
|
||||
/// Increases the amount of available bandwidth of the connected client by the specified value.
|
||||
@@ -191,11 +216,27 @@ where
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `amount`: amount to increase the available bandwidth by.
|
||||
async fn increase_bandwidth(&self, bandwidth: Bandwidth) -> Result<(), RequestHandlingError> {
|
||||
async fn increase_bandwidth(
|
||||
&mut self,
|
||||
bandwidth: Bandwidth,
|
||||
) -> Result<(), RequestHandlingError> {
|
||||
self.client_bandwidth.bandwidth.bytes += bandwidth.value() as i64;
|
||||
|
||||
// any increases to bandwidth should get flushed immediately
|
||||
// (we don't want to accidentally miss somebody claiming a gigabyte voucher)
|
||||
self.flush_bandwidth().await
|
||||
}
|
||||
|
||||
async fn set_freepass_expiration(
|
||||
&mut self,
|
||||
expiration: OffsetDateTime,
|
||||
) -> Result<(), RequestHandlingError> {
|
||||
self.client_bandwidth.bandwidth.freepass_expiration = Some(expiration);
|
||||
self.inner
|
||||
.storage
|
||||
.increase_bandwidth(self.client.address, bandwidth.value() as i64)
|
||||
.set_freepass_expiration(self.client.address, expiration)
|
||||
.await?;
|
||||
self.client_bandwidth.update_flush_data();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -204,11 +245,18 @@ where
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `amount`: amount to decrease the available bandwidth by.
|
||||
async fn consume_bandwidth(&self, amount: i64) -> Result<(), RequestHandlingError> {
|
||||
self.inner
|
||||
.storage
|
||||
.consume_bandwidth(self.client.address, amount)
|
||||
.await?;
|
||||
async fn consume_bandwidth(&mut self, amount: i64) -> Result<(), RequestHandlingError> {
|
||||
self.client_bandwidth.bandwidth.bytes -= amount;
|
||||
|
||||
// since we're going to be operating on a fair use policy anyway, even if we crash and let extra few packets
|
||||
// through, that's completely fine
|
||||
if self
|
||||
.client_bandwidth
|
||||
.should_flush(self.inner.shared_state.bandwidth_cfg)
|
||||
{
|
||||
self.flush_bandwidth().await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -232,6 +280,22 @@ where
|
||||
let serial_number = credential.data.blinded_serial_number();
|
||||
trace!("processing credential {}", serial_number.to_bs58());
|
||||
|
||||
// if we already have had received a free pass (that's not expired, don't accept any additional bandwidth)
|
||||
if self.client_bandwidth.bandwidth.freepass_expired() {
|
||||
// the free pass we used before has expired -> reset our state and handle the request as normal
|
||||
self.expire_freepass().await?;
|
||||
} else if let Some(expiration) = self.client_bandwidth.bandwidth.freepass_expiration {
|
||||
// the free pass is still valid -> return error
|
||||
return match credential.data.typ {
|
||||
CredentialType::Voucher => {
|
||||
Err(RequestHandlingError::BandwidthVoucherForFreePassAccount { expiration })
|
||||
}
|
||||
CredentialType::FreePass => {
|
||||
Err(RequestHandlingError::PreexistingFreePass { expiration })
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let already_spent = self
|
||||
.inner
|
||||
.storage
|
||||
@@ -247,12 +311,6 @@ where
|
||||
credential.data.epoch_id
|
||||
);
|
||||
|
||||
let aggregated_verification_key = self
|
||||
.inner
|
||||
.coconut_verifier
|
||||
.verification_key(credential.data.epoch_id)
|
||||
.await?;
|
||||
|
||||
if !credential.data.validate_type_attribute() {
|
||||
trace!("mismatch in the type attribute");
|
||||
return Err(RequestHandlingError::InvalidTypeAttribute);
|
||||
@@ -264,41 +322,52 @@ where
|
||||
};
|
||||
|
||||
// this will extract token amounts out of bandwidth vouchers and validate expiry of free passes
|
||||
let bandwidth = Bandwidth::try_from_raw_value(bandwidth_attribute, credential.data.typ)?;
|
||||
let (raw_bandwidth, freepass_expiration) =
|
||||
Bandwidth::parse_raw_bandwidth(bandwidth_attribute, credential.data.typ)?;
|
||||
|
||||
let bandwidth = Bandwidth::new(raw_bandwidth)?;
|
||||
|
||||
trace!("embedded bandwidth: {bandwidth:?}");
|
||||
|
||||
// locally verify the credential
|
||||
let params = bandwidth_credential_params();
|
||||
if !credential.data.verify(params, &aggregated_verification_key) {
|
||||
trace!("the credential did not verify correctly");
|
||||
return Err(RequestHandlingError::InvalidBandwidthCredential(
|
||||
String::from("local credential verification has failed"),
|
||||
));
|
||||
{
|
||||
let aggregated_verification_key = self
|
||||
.inner
|
||||
.shared_state
|
||||
.coconut_verifier
|
||||
.verification_key(credential.data.epoch_id)
|
||||
.await?;
|
||||
|
||||
let params = bandwidth_credential_params();
|
||||
if !credential.data.verify(params, &aggregated_verification_key) {
|
||||
trace!("the credential did not verify correctly");
|
||||
return Err(RequestHandlingError::InvalidBandwidthCredential(
|
||||
String::from("local credential verification has failed"),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let was_freepass = match credential.data.typ {
|
||||
match credential.data.typ {
|
||||
CredentialType::Voucher => {
|
||||
trace!("the credential is a bandwidth voucher. attempting to release the funds");
|
||||
let api_clients = self
|
||||
.inner
|
||||
.shared_state
|
||||
.coconut_verifier
|
||||
.api_clients(credential.data.epoch_id)
|
||||
.await?;
|
||||
|
||||
self.inner
|
||||
.shared_state
|
||||
.coconut_verifier
|
||||
.release_bandwidth_voucher_funds(&api_clients, credential)
|
||||
.await?;
|
||||
false
|
||||
}
|
||||
CredentialType::FreePass => {
|
||||
// no need to do anything special here, we already extracted the bandwidth amount and checked expiry
|
||||
info!("received a free pass credential");
|
||||
|
||||
true
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// technically this is not atomic, i.e. checking for the spending and then marking as spent,
|
||||
// but because we have the `UNIQUE` constraint on the database table
|
||||
@@ -311,12 +380,21 @@ where
|
||||
trace!("storing serial number information");
|
||||
self.inner
|
||||
.storage
|
||||
.insert_spent_credential(serial_number, was_freepass, self.client.address)
|
||||
.insert_spent_credential(
|
||||
serial_number,
|
||||
freepass_expiration.is_some(),
|
||||
self.client.address,
|
||||
)
|
||||
.await?;
|
||||
|
||||
trace!("increasing client bandwidth");
|
||||
self.increase_bandwidth(bandwidth).await?;
|
||||
let available_total = self.get_available_bandwidth().await?;
|
||||
// set free pass expiration
|
||||
if let Some(expiration) = freepass_expiration {
|
||||
self.set_freepass_expiration(expiration).await?;
|
||||
}
|
||||
|
||||
let available_total = self.client_bandwidth.bandwidth.bytes;
|
||||
|
||||
Ok(ServerResponse::Bandwidth { available_total })
|
||||
}
|
||||
@@ -367,17 +445,47 @@ where
|
||||
) -> Result<ServerResponse, RequestHandlingError> {
|
||||
debug!("handling testnet bandwidth request");
|
||||
|
||||
if self.inner.only_coconut_credentials {
|
||||
if self.inner.shared_state.only_coconut_credentials {
|
||||
return Err(RequestHandlingError::OnlyCoconutCredentials);
|
||||
}
|
||||
|
||||
self.increase_bandwidth(FREE_TESTNET_BANDWIDTH_VALUE)
|
||||
.await?;
|
||||
let available_total = self.get_available_bandwidth().await?;
|
||||
let available_total = self.client_bandwidth.bandwidth.bytes;
|
||||
|
||||
Ok(ServerResponse::Bandwidth { available_total })
|
||||
}
|
||||
|
||||
async fn flush_bandwidth(&mut self) -> Result<(), RequestHandlingError> {
|
||||
trace!("flushing client bandwidth to the underlying storage");
|
||||
self.inner
|
||||
.storage
|
||||
.set_bandwidth(self.client.address, self.client_bandwidth.bandwidth.bytes)
|
||||
.await?;
|
||||
self.client_bandwidth.update_flush_data();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn try_use_bandwidth(
|
||||
&mut self,
|
||||
required_bandwidth: i64,
|
||||
) -> Result<i64, RequestHandlingError> {
|
||||
if self.client_bandwidth.bandwidth.freepass_expired() {
|
||||
self.expire_freepass().await?;
|
||||
}
|
||||
let available_bandwidth = self.client_bandwidth.bandwidth.bytes;
|
||||
|
||||
if available_bandwidth < required_bandwidth {
|
||||
return Err(RequestHandlingError::OutOfBandwidth {
|
||||
required: required_bandwidth,
|
||||
available: available_bandwidth,
|
||||
});
|
||||
}
|
||||
|
||||
self.consume_bandwidth(required_bandwidth).await?;
|
||||
Ok(self.client_bandwidth.bandwidth.bytes)
|
||||
}
|
||||
|
||||
/// Tries to handle request to forward sphinx packet into the network. The request can only succeed
|
||||
/// if the client has enough available bandwidth.
|
||||
///
|
||||
@@ -387,24 +495,16 @@ where
|
||||
///
|
||||
/// * `mix_packet`: packet received from the client that should get forwarded into the network.
|
||||
async fn handle_forward_sphinx(
|
||||
&self,
|
||||
&mut self,
|
||||
mix_packet: MixPacket,
|
||||
) -> Result<ServerResponse, RequestHandlingError> {
|
||||
let consumed_bandwidth = mix_packet.packet().len() as i64;
|
||||
let required_bandwidth = mix_packet.packet().len() as i64;
|
||||
|
||||
let available_bandwidth = self.get_available_bandwidth().await?;
|
||||
|
||||
if available_bandwidth < consumed_bandwidth {
|
||||
return Ok(ServerResponse::new_error(
|
||||
"Insufficient bandwidth available",
|
||||
));
|
||||
}
|
||||
|
||||
self.consume_bandwidth(consumed_bandwidth).await?;
|
||||
let remaining_bandwidth = self.try_use_bandwidth(required_bandwidth).await?;
|
||||
self.forward_packet(mix_packet);
|
||||
|
||||
Ok(ServerResponse::Send {
|
||||
remaining_bandwidth: available_bandwidth - consumed_bandwidth,
|
||||
remaining_bandwidth,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -413,7 +513,7 @@ where
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `bin_msg`: raw message to handle.
|
||||
async fn handle_binary(&self, bin_msg: Vec<u8>) -> Message {
|
||||
async fn handle_binary(&mut self, bin_msg: Vec<u8>) -> Message {
|
||||
trace!("binary request");
|
||||
// this function decrypts the request and checks the MAC
|
||||
match BinaryRequest::try_from_encrypted_tagged_bytes(bin_msg, &self.client.shared_keys) {
|
||||
@@ -455,7 +555,13 @@ where
|
||||
.handle_claim_testnet_bandwidth()
|
||||
.await
|
||||
.into_ws_message(),
|
||||
_ => RequestHandlingError::IllegalRequest.into_error_message(),
|
||||
other => RequestHandlingError::IllegalRequest {
|
||||
additional_context: format!(
|
||||
"received illegal message of type {} in an authenticated client",
|
||||
other.name()
|
||||
),
|
||||
}
|
||||
.into_error_message(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::error::RequestHandlingError;
|
||||
use futures::{
|
||||
channel::{mpsc, oneshot},
|
||||
SinkExt, StreamExt,
|
||||
@@ -20,18 +21,19 @@ use nym_gateway_requests::{
|
||||
use nym_mixnet_client::forwarder::MixForwardingSender;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio_tungstenite::tungstenite::{protocol::Message, Error as WsError};
|
||||
|
||||
use crate::node::client_handling::websocket::common_state::CommonHandlerState;
|
||||
use crate::node::client_handling::websocket::connection_handler::AvailableBandwidth;
|
||||
use crate::node::{
|
||||
client_handling::{
|
||||
active_clients::ActiveClientsStore,
|
||||
websocket::{
|
||||
connection_handler::{
|
||||
coconut::CoconutVerifier, AuthenticatedHandler, ClientDetails, InitialAuthResult,
|
||||
SocketStream,
|
||||
AuthenticatedHandler, ClientDetails, InitialAuthResult, SocketStream,
|
||||
},
|
||||
message_receiver::{IsActive, IsActiveRequestSender},
|
||||
},
|
||||
@@ -77,24 +79,55 @@ pub(crate) enum InitialAuthenticationError {
|
||||
|
||||
#[error("Attempted to negotiate connection with client using incompatible protocol version. Ours is {current} and the client reports {client:?}")]
|
||||
IncompatibleProtocol { client: Option<u8>, current: u8 },
|
||||
|
||||
#[error("failed to send authentication error response: {source}")]
|
||||
ErrorResponseSendFailure {
|
||||
#[source]
|
||||
source: WsError,
|
||||
},
|
||||
|
||||
#[error("failed to send authentication response: {source}")]
|
||||
ResponseSendFailure {
|
||||
#[source]
|
||||
source: WsError,
|
||||
},
|
||||
|
||||
#[error("possibly received a sphinx packet without prior authentication. Request is going to be ignored")]
|
||||
BinaryRequestWithoutAuthentication,
|
||||
|
||||
#[error("received a connection close message")]
|
||||
CloseMessage,
|
||||
|
||||
#[error("the connection has unexpectedly closed")]
|
||||
ClosedConnection,
|
||||
|
||||
#[error("failed to obtain message from websocket stream: {source}")]
|
||||
FailedToReadMessage {
|
||||
#[source]
|
||||
source: WsError,
|
||||
},
|
||||
|
||||
#[error("could not establish client details")]
|
||||
EmptyClientDetails,
|
||||
|
||||
#[error("failed to upgrade the client handler: {source}")]
|
||||
HandlerUpgradeFailure { source: RequestHandlingError },
|
||||
}
|
||||
|
||||
impl InitialAuthenticationError {
|
||||
/// Converts this Error into an appropriate websocket Message.
|
||||
fn into_error_message(self) -> Message {
|
||||
fn to_error_message(&self) -> Message {
|
||||
ServerResponse::new_error(self.to_string()).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct FreshHandler<R, S, St> {
|
||||
rng: R,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
pub(crate) only_coconut_credentials: bool,
|
||||
pub(crate) shared_state: CommonHandlerState,
|
||||
pub(crate) active_clients_store: ActiveClientsStore,
|
||||
pub(crate) outbound_mix_sender: MixForwardingSender,
|
||||
pub(crate) socket_connection: SocketStream<S>,
|
||||
pub(crate) storage: St,
|
||||
pub(crate) coconut_verifier: Arc<CoconutVerifier>,
|
||||
|
||||
// currently unused (but populated)
|
||||
pub(crate) negotiated_protocol: Option<u8>,
|
||||
@@ -113,23 +146,19 @@ where
|
||||
pub(crate) fn new(
|
||||
rng: R,
|
||||
conn: S,
|
||||
only_coconut_credentials: bool,
|
||||
outbound_mix_sender: MixForwardingSender,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
storage: St,
|
||||
active_clients_store: ActiveClientsStore,
|
||||
coconut_verifier: Arc<CoconutVerifier>,
|
||||
shared_state: CommonHandlerState,
|
||||
) -> Self {
|
||||
FreshHandler {
|
||||
rng,
|
||||
active_clients_store,
|
||||
only_coconut_credentials,
|
||||
outbound_mix_sender,
|
||||
socket_connection: SocketStream::RawTcp(conn),
|
||||
local_identity,
|
||||
storage,
|
||||
coconut_verifier,
|
||||
negotiated_protocol: None,
|
||||
shared_state,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,7 +200,7 @@ where
|
||||
gateway_handshake(
|
||||
&mut self.rng,
|
||||
ws_stream,
|
||||
self.local_identity.as_ref(),
|
||||
self.shared_state.local_identity.as_ref(),
|
||||
init_msg,
|
||||
)
|
||||
.await
|
||||
@@ -436,7 +465,7 @@ where
|
||||
// Ask the other connection to ping if they are still active.
|
||||
// Use a oneshot channel to return the result to us
|
||||
let (ping_result_sender, ping_result_receiver) = oneshot::channel();
|
||||
log::debug!("Asking other connection handler to ping the connected client to see if it is still active");
|
||||
debug!("Asking other connection handler to ping the connected client to see if it is still active");
|
||||
if let Err(err) = is_active_request_tx.send(ping_result_sender).await {
|
||||
warn!("Failed to send ping request to other handler: {err}");
|
||||
}
|
||||
@@ -448,31 +477,31 @@ where
|
||||
IsActive::NotActive => {
|
||||
// The other handler reported that the client is not active, so we can
|
||||
// disconnect the other client and continue with this connection.
|
||||
log::debug!("Other handler reports it is not active");
|
||||
debug!("Other handler reports it is not active");
|
||||
self.active_clients_store.disconnect(address);
|
||||
}
|
||||
IsActive::Active => {
|
||||
// The other handled reported a positive reply, so we have to assume it's
|
||||
// still active and disconnect this connection.
|
||||
log::info!("Other handler reports it is active");
|
||||
info!("Other handler reports it is active");
|
||||
return Err(InitialAuthenticationError::DuplicateConnection);
|
||||
}
|
||||
IsActive::BusyPinging => {
|
||||
// The other handler is already busy pinging the client, so we have to
|
||||
// assume it's still active and disconnect this connection.
|
||||
log::debug!("Other handler reports it is already busy pinging the client");
|
||||
debug!("Other handler reports it is already busy pinging the client");
|
||||
return Err(InitialAuthenticationError::DuplicateConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Err(_)) => {
|
||||
// Other channel failed to reply (the channel sender probably dropped)
|
||||
log::info!("Other connection failed to reply, disconnecting it in favour of this new connection");
|
||||
info!("Other connection failed to reply, disconnecting it in favour of this new connection");
|
||||
self.active_clients_store.disconnect(address);
|
||||
}
|
||||
Err(_) => {
|
||||
// Timeout waiting for reply
|
||||
log::warn!(
|
||||
warn!(
|
||||
"Other connection timed out, disconnecting it in favour of this new connection"
|
||||
);
|
||||
self.active_clients_store.disconnect(address);
|
||||
@@ -509,7 +538,7 @@ where
|
||||
|
||||
// Check for duplicate clients
|
||||
if let Some(client_tx) = self.active_clients_store.get_remote_client(address) {
|
||||
log::warn!("Detected duplicate connection for client: {address}");
|
||||
warn!("Detected duplicate connection for client: {address}");
|
||||
self.handle_duplicate_client(address, client_tx.is_active_request_sender)
|
||||
.await?;
|
||||
}
|
||||
@@ -518,11 +547,17 @@ where
|
||||
.authenticate_client(address, encrypted_address, iv)
|
||||
.await?;
|
||||
let status = shared_keys.is_some();
|
||||
let bandwidth_remaining = self
|
||||
.storage
|
||||
.get_available_bandwidth(address)
|
||||
.await?
|
||||
.unwrap_or(0);
|
||||
|
||||
let available_bandwidth: AvailableBandwidth =
|
||||
self.storage.get_available_bandwidth(address).await?.into();
|
||||
|
||||
let bandwidth_remaining = if available_bandwidth.freepass_expired() {
|
||||
self.expire_freepass(address).await?;
|
||||
0
|
||||
} else {
|
||||
available_bandwidth.bytes
|
||||
};
|
||||
|
||||
let client_details =
|
||||
shared_keys.map(|shared_keys| ClientDetails::new(address, shared_keys));
|
||||
|
||||
@@ -536,6 +571,13 @@ where
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) async fn expire_freepass(
|
||||
&self,
|
||||
client: DestinationAddressBytes,
|
||||
) -> Result<(), StorageError> {
|
||||
self.storage.reset_freepass_bandwidth(client).await
|
||||
}
|
||||
|
||||
/// Attempts to finalize registration of the client by storing the derived shared keys in the
|
||||
/// persistent store as well as creating entry for its bandwidth allocation.
|
||||
///
|
||||
@@ -657,7 +699,7 @@ where
|
||||
// TODO: somehow cleanup this method
|
||||
pub(crate) async fn perform_initial_authentication(
|
||||
mut self,
|
||||
) -> Option<AuthenticatedHandler<R, S, St>>
|
||||
) -> Result<AuthenticatedHandler<R, S, St>, InitialAuthenticationError>
|
||||
where
|
||||
S: AsyncRead + AsyncWrite + Unpin + Send,
|
||||
{
|
||||
@@ -666,40 +708,51 @@ where
|
||||
while let Some(msg) = self.read_websocket_message().await {
|
||||
let msg = match msg {
|
||||
Ok(msg) => msg,
|
||||
Err(err) => {
|
||||
debug!("failed to obtain message from websocket stream! stopping connection handler: {err}");
|
||||
break;
|
||||
Err(source) => {
|
||||
debug!("failed to obtain message from websocket stream! stopping connection handler: {source}");
|
||||
return Err(InitialAuthenticationError::FailedToReadMessage { source });
|
||||
}
|
||||
};
|
||||
|
||||
if msg.is_close() {
|
||||
break;
|
||||
return Err(InitialAuthenticationError::CloseMessage);
|
||||
}
|
||||
|
||||
// ONLY handle 'Authenticate' or 'Register' requests, ignore everything else
|
||||
match msg {
|
||||
Message::Close(_) => break,
|
||||
// we have explicitly checked for close message
|
||||
Message::Close(_) => unreachable!(),
|
||||
Message::Text(text_msg) => {
|
||||
let (mix_sender, mix_receiver) = mpsc::unbounded();
|
||||
match self.handle_initial_authentication_request(text_msg).await {
|
||||
return match self.handle_initial_authentication_request(text_msg).await {
|
||||
Err(err) => {
|
||||
if let Err(err) =
|
||||
self.send_websocket_message(err.into_error_message()).await
|
||||
debug!("authentication failure: {err}");
|
||||
|
||||
// try to send error to the client
|
||||
if let Err(source) =
|
||||
self.send_websocket_message(err.to_error_message()).await
|
||||
{
|
||||
debug!("Failed to send authentication error response - {err}");
|
||||
return None;
|
||||
debug!("Failed to send authentication error response: {source}");
|
||||
return Err(InitialAuthenticationError::ErrorResponseSendFailure {
|
||||
source,
|
||||
});
|
||||
}
|
||||
// return the underlying error
|
||||
Err(err)
|
||||
}
|
||||
Ok(auth_result) => {
|
||||
if let Err(err) = self
|
||||
// try to send auth response back to the client
|
||||
if let Err(source) = self
|
||||
.send_websocket_message(auth_result.server_response.into())
|
||||
.await
|
||||
{
|
||||
debug!("Failed to send authentication response - {err}");
|
||||
return None;
|
||||
debug!("Failed to send authentication response: {source}");
|
||||
return Err(InitialAuthenticationError::ResponseSendFailure {
|
||||
source,
|
||||
});
|
||||
}
|
||||
|
||||
return if let Some(client_details) = auth_result.client_details {
|
||||
if let Some(client_details) = auth_result.client_details {
|
||||
// Channel for handlers to ask other handlers if they are still active.
|
||||
let (is_active_request_sender, is_active_request_receiver) =
|
||||
mpsc::unbounded();
|
||||
@@ -708,23 +761,29 @@ where
|
||||
mix_sender,
|
||||
is_active_request_sender,
|
||||
);
|
||||
Some(AuthenticatedHandler::upgrade(
|
||||
AuthenticatedHandler::upgrade(
|
||||
self,
|
||||
client_details,
|
||||
mix_receiver,
|
||||
is_active_request_receiver,
|
||||
))
|
||||
)
|
||||
.await
|
||||
.map_err(|source| {
|
||||
InitialAuthenticationError::HandlerUpgradeFailure { source }
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// honestly, it's been so long I don't remember under what conditions its possible (if at all)
|
||||
// to have empty client details
|
||||
Err(InitialAuthenticationError::EmptyClientDetails)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Message::Binary(_) => {
|
||||
// perhaps logging level should be reduced here, let's leave it for now and see what happens
|
||||
// if client is working correctly, this should have never happened
|
||||
debug!("possibly received a sphinx packet without prior authentication. Request is going to be ignored");
|
||||
if let Err(err) = self
|
||||
if let Err(source) = self
|
||||
.send_websocket_message(
|
||||
ServerResponse::new_error(
|
||||
"binary request without prior authentication",
|
||||
@@ -733,15 +792,18 @@ where
|
||||
)
|
||||
.await
|
||||
{
|
||||
debug!("Failed to send error response during authentication: {err}",)
|
||||
return Err(InitialAuthenticationError::ErrorResponseSendFailure {
|
||||
source,
|
||||
});
|
||||
}
|
||||
return None;
|
||||
return Err(InitialAuthenticationError::BinaryRequestWithoutAuthentication);
|
||||
}
|
||||
|
||||
_ => continue,
|
||||
};
|
||||
}
|
||||
None
|
||||
|
||||
Err(InitialAuthenticationError::ClosedConnection)
|
||||
}
|
||||
|
||||
pub(crate) async fn start_handling(self, shutdown: nym_task::TaskClient)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::node::storage::Storage;
|
||||
use log::{trace, warn};
|
||||
use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
@@ -8,6 +9,8 @@ use nym_gateway_requests::ServerResponse;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use nym_task::TaskClient;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio_tungstenite::WebSocketStream;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
@@ -103,12 +106,84 @@ pub(crate) async fn handle_connection<R, S, St>(
|
||||
trace!("received shutdown signal while performing initial authentication");
|
||||
return;
|
||||
}
|
||||
Some(None) => {
|
||||
warn!("authentication has failed");
|
||||
Some(Err(err)) => {
|
||||
warn!("authentication has failed: {err}");
|
||||
return;
|
||||
}
|
||||
Some(Some(auth_handle)) => auth_handle.listen_for_requests(shutdown).await,
|
||||
Some(Ok(auth_handle)) => auth_handle.listen_for_requests(shutdown).await,
|
||||
}
|
||||
|
||||
trace!("The handler is done!");
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) struct BandwidthFlushingBehaviourConfig {
|
||||
/// Defines maximum delay between client bandwidth information being flushed to the persistent storage.
|
||||
pub client_bandwidth_max_flushing_rate: Duration,
|
||||
|
||||
/// Defines a maximum change in client bandwidth before it gets flushed to the persistent storage.
|
||||
pub client_bandwidth_max_delta_flushing_amount: i64,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Config> for BandwidthFlushingBehaviourConfig {
|
||||
fn from(value: &'a Config) -> Self {
|
||||
BandwidthFlushingBehaviourConfig {
|
||||
client_bandwidth_max_flushing_rate: value.debug.client_bandwidth_max_flushing_rate,
|
||||
client_bandwidth_max_delta_flushing_amount: value
|
||||
.debug
|
||||
.client_bandwidth_max_delta_flushing_amount,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub(crate) struct AvailableBandwidth {
|
||||
pub(crate) bytes: i64,
|
||||
pub(crate) freepass_expiration: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
impl AvailableBandwidth {
|
||||
pub(crate) fn freepass_expired(&self) -> bool {
|
||||
if let Some(expiration) = self.freepass_expiration {
|
||||
if expiration < OffsetDateTime::now_utc() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ClientBandwidth {
|
||||
pub(crate) bandwidth: AvailableBandwidth,
|
||||
pub(crate) last_flushed: OffsetDateTime,
|
||||
pub(crate) bytes_at_last_flush: i64,
|
||||
}
|
||||
|
||||
impl ClientBandwidth {
|
||||
pub(crate) fn new(bandwidth: AvailableBandwidth) -> ClientBandwidth {
|
||||
ClientBandwidth {
|
||||
bandwidth,
|
||||
last_flushed: OffsetDateTime::now_utc(),
|
||||
bytes_at_last_flush: bandwidth.bytes,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn should_flush(&self, cfg: BandwidthFlushingBehaviourConfig) -> bool {
|
||||
if (self.bytes_at_last_flush - self.bandwidth.bytes).abs()
|
||||
>= cfg.client_bandwidth_max_delta_flushing_amount
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if self.last_flushed + cfg.client_bandwidth_max_flushing_rate < OffsetDateTime::now_utc() {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) fn update_flush_data(&mut self) {
|
||||
self.last_flushed = OffsetDateTime::now_utc();
|
||||
self.bytes_at_last_flush = self.bandwidth.bytes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,37 +2,26 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::client_handling::active_clients::ActiveClientsStore;
|
||||
use crate::node::client_handling::websocket::connection_handler::coconut::CoconutVerifier;
|
||||
use crate::node::client_handling::websocket::common_state::CommonHandlerState;
|
||||
use crate::node::client_handling::websocket::connection_handler::FreshHandler;
|
||||
use crate::node::storage::Storage;
|
||||
use log::*;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_mixnet_client::forwarder::MixForwardingSender;
|
||||
use rand::rngs::OsRng;
|
||||
use std::net::SocketAddr;
|
||||
use std::process;
|
||||
use std::sync::Arc;
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
pub(crate) struct Listener {
|
||||
address: SocketAddr,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
only_coconut_credentials: bool,
|
||||
pub(crate) coconut_verifier: Arc<CoconutVerifier>,
|
||||
shared_state: CommonHandlerState,
|
||||
}
|
||||
|
||||
impl Listener {
|
||||
pub(crate) fn new(
|
||||
address: SocketAddr,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
only_coconut_credentials: bool,
|
||||
coconut_verifier: Arc<CoconutVerifier>,
|
||||
) -> Self {
|
||||
pub(crate) fn new(address: SocketAddr, shared_state: CommonHandlerState) -> Self {
|
||||
Listener {
|
||||
address,
|
||||
local_identity,
|
||||
only_coconut_credentials,
|
||||
coconut_verifier,
|
||||
shared_state,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,12 +60,10 @@ impl Listener {
|
||||
let handle = FreshHandler::new(
|
||||
OsRng,
|
||||
socket,
|
||||
self.only_coconut_credentials,
|
||||
outbound_mix_sender.clone(),
|
||||
Arc::clone(&self.local_identity),
|
||||
storage.clone(),
|
||||
active_clients_store.clone(),
|
||||
Arc::clone(&self.coconut_verifier),
|
||||
self.shared_state.clone(),
|
||||
);
|
||||
let shutdown = shutdown.clone().named(format!("ClientConnectionHandler_{remote_addr}"));
|
||||
tokio::spawn(async move { handle.start_handling(shutdown).await });
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
|
||||
pub(crate) use listener::Listener;
|
||||
|
||||
pub(crate) mod common_state;
|
||||
pub(crate) mod connection_handler;
|
||||
pub(crate) mod listener;
|
||||
pub(crate) mod message_receiver;
|
||||
|
||||
pub(crate) use common_state::CommonHandlerState;
|
||||
|
||||
@@ -150,7 +150,7 @@ impl<St> Gateway<St> {
|
||||
network_requester_opts,
|
||||
ip_packet_router_opts,
|
||||
client_registry: Arc::new(DashMap::new()),
|
||||
run_http_server: false,
|
||||
run_http_server: true,
|
||||
task_client: None,
|
||||
})
|
||||
}
|
||||
@@ -171,7 +171,7 @@ impl<St> Gateway<St> {
|
||||
sphinx_keypair,
|
||||
storage,
|
||||
client_registry: Arc::new(DashMap::new()),
|
||||
run_http_server: false,
|
||||
run_http_server: true,
|
||||
task_client: None,
|
||||
}
|
||||
}
|
||||
@@ -254,13 +254,14 @@ impl<St> Gateway<St> {
|
||||
self.config.gateway.clients_port,
|
||||
);
|
||||
|
||||
websocket::Listener::new(
|
||||
listening_address,
|
||||
Arc::clone(&self.identity_keypair),
|
||||
self.config.gateway.only_coconut_credentials,
|
||||
let shared_state = websocket::CommonHandlerState {
|
||||
coconut_verifier,
|
||||
)
|
||||
.start(
|
||||
local_identity: Arc::clone(&self.identity_keypair),
|
||||
only_coconut_credentials: self.config.gateway.only_coconut_credentials,
|
||||
bandwidth_cfg: (&self.config).into(),
|
||||
};
|
||||
|
||||
websocket::Listener::new(listening_address, shared_state).start(
|
||||
forwarding_channel,
|
||||
self.storage.clone(),
|
||||
active_clients_store,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::storage::models::{PersistedBandwidth, SpentCredential};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct BandwidthManager {
|
||||
@@ -36,6 +37,53 @@ impl BandwidthManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the freepass expiration date of the particular client to the provided date.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_address_bs58`: base58-encoded address of the client.
|
||||
/// * `freepass_expiration`: the expiration date of the associated free pass.
|
||||
pub(crate) async fn set_freepass_expiration(
|
||||
&self,
|
||||
client_address_bs58: &str,
|
||||
freepass_expiration: OffsetDateTime,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE available_bandwidth
|
||||
SET freepass_expiration = ?
|
||||
WHERE client_address_bs58 = ?
|
||||
"#,
|
||||
freepass_expiration,
|
||||
client_address_bs58
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Reset all the bandwidth associated with the freepass and reset its expiration date
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_address_bs58`: base58-encoded address of the client.
|
||||
pub(crate) async fn reset_freepass_bandwidth(
|
||||
&self,
|
||||
client_address_bs58: &str,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE available_bandwidth
|
||||
SET available = 0, freepass_expiration = NULL
|
||||
WHERE client_address_bs58 = ?
|
||||
"#,
|
||||
client_address_bs58
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Tries to retrieve available bandwidth for the particular client.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -45,22 +93,19 @@ impl BandwidthManager {
|
||||
&self,
|
||||
client_address_bs58: &str,
|
||||
) -> Result<Option<PersistedBandwidth>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
PersistedBandwidth,
|
||||
"SELECT * FROM available_bandwidth WHERE client_address_bs58 = ?",
|
||||
client_address_bs58
|
||||
)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
sqlx::query_as("SELECT * FROM available_bandwidth WHERE client_address_bs58 = ?")
|
||||
.bind(client_address_bs58)
|
||||
.fetch_optional(&self.connection_pool)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Increases available bandwidth of the particular client by the specified amount.
|
||||
/// Sets available bandwidth of the particular client to the provided amount;
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_address_bs58`: base58-encoded address of the client.
|
||||
/// * `amount`: amount of available bandwidth to be added to the client.
|
||||
pub(crate) async fn increase_available_bandwidth(
|
||||
/// * `client_address`: address of the client
|
||||
/// * `amount`: the updated client bandwidth amount.
|
||||
pub(crate) async fn set_available_bandwidth(
|
||||
&self,
|
||||
client_address_bs58: &str,
|
||||
amount: i64,
|
||||
@@ -68,32 +113,7 @@ impl BandwidthManager {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE available_bandwidth
|
||||
SET available = available + ?
|
||||
WHERE client_address_bs58 = ?
|
||||
"#,
|
||||
amount,
|
||||
client_address_bs58
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Decreases available bandwidth of the particular client by the specified amount.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_address_bs58`: base58-encoded address of the client.
|
||||
/// * `amount`: amount of available bandwidth to be removed from the client.
|
||||
pub(crate) async fn decrease_available_bandwidth(
|
||||
&self,
|
||||
client_address_bs58: &str,
|
||||
amount: i64,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
UPDATE available_bandwidth
|
||||
SET available = available - ?
|
||||
SET available = ?
|
||||
WHERE client_address_bs58 = ?
|
||||
"#,
|
||||
amount,
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::node::storage::bandwidth::BandwidthManager;
|
||||
use crate::node::storage::error::StorageError;
|
||||
use crate::node::storage::inboxes::InboxManager;
|
||||
use crate::node::storage::models::{PersistedSharedKeys, StoredMessage};
|
||||
use crate::node::storage::models::{PersistedBandwidth, PersistedSharedKeys, StoredMessage};
|
||||
use crate::node::storage::shared_keys::SharedKeysManager;
|
||||
use async_trait::async_trait;
|
||||
use log::{debug, error};
|
||||
@@ -13,6 +13,7 @@ use nym_gateway_requests::registration::handshake::SharedKeys;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use sqlx::ConnectOptions;
|
||||
use std::path::Path;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
mod bandwidth;
|
||||
pub(crate) mod error;
|
||||
@@ -102,6 +103,28 @@ pub trait Storage: Send + Sync {
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<(), StorageError>;
|
||||
|
||||
/// Set the freepass expiration date of the particular client to the provided date.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_address`: address of the client
|
||||
/// * `freepass_expiration`: the expiration date of the associated free pass.
|
||||
async fn set_freepass_expiration(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
freepass_expiration: OffsetDateTime,
|
||||
) -> Result<(), StorageError>;
|
||||
|
||||
/// Reset all the bandwidth associated with the freepass and reset its expiration date
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_address`: address of the client
|
||||
async fn reset_freepass_bandwidth(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<(), StorageError>;
|
||||
|
||||
/// Tries to retrieve available bandwidth for the particular client.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -110,27 +133,15 @@ pub trait Storage: Send + Sync {
|
||||
async fn get_available_bandwidth(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<Option<i64>, StorageError>;
|
||||
) -> Result<Option<PersistedBandwidth>, StorageError>;
|
||||
|
||||
/// Increases available bandwidth of the particular client by the specified amount.
|
||||
/// Sets available bandwidth of the particular client to the provided amount;
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_address`: address of the client
|
||||
/// * `amount`: amount of available bandwidth to be added to the client.
|
||||
async fn increase_bandwidth(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
amount: i64,
|
||||
) -> Result<(), StorageError>;
|
||||
|
||||
/// Decreases available bandwidth of the particular client by the specified amount.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `client_address`: address of the client
|
||||
/// * `amount`: amount of available bandwidth to be removed from the client.
|
||||
async fn consume_bandwidth(
|
||||
/// * `amount`: the updated client bandwidth amount.
|
||||
async fn set_bandwidth(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
amount: i64,
|
||||
@@ -295,36 +306,44 @@ impl Storage for PersistentStorage {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_available_bandwidth(
|
||||
async fn set_freepass_expiration(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<Option<i64>, StorageError> {
|
||||
let res = self
|
||||
.bandwidth_manager
|
||||
.get_available_bandwidth(&client_address.as_base58_string())
|
||||
.await
|
||||
.map(|bandwidth_option| bandwidth_option.map(|bandwidth| bandwidth.available))?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
async fn increase_bandwidth(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
amount: i64,
|
||||
freepass_expiration: OffsetDateTime,
|
||||
) -> Result<(), StorageError> {
|
||||
self.bandwidth_manager
|
||||
.increase_available_bandwidth(&client_address.as_base58_string(), amount)
|
||||
.set_freepass_expiration(&client_address.as_base58_string(), freepass_expiration)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn consume_bandwidth(
|
||||
async fn reset_freepass_bandwidth(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<(), StorageError> {
|
||||
self.bandwidth_manager
|
||||
.reset_freepass_bandwidth(&client_address.as_base58_string())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn get_available_bandwidth(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<Option<PersistedBandwidth>, StorageError> {
|
||||
Ok(self
|
||||
.bandwidth_manager
|
||||
.get_available_bandwidth(&client_address.as_base58_string())
|
||||
.await?)
|
||||
}
|
||||
|
||||
async fn set_bandwidth(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
amount: i64,
|
||||
) -> Result<(), StorageError> {
|
||||
self.bandwidth_manager
|
||||
.decrease_available_bandwidth(&client_address.as_base58_string(), amount)
|
||||
.set_available_bandwidth(&client_address.as_base58_string(), amount)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -422,22 +441,29 @@ impl Storage for InMemStorage {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn get_available_bandwidth(
|
||||
async fn set_freepass_expiration(
|
||||
&self,
|
||||
_client_address: DestinationAddressBytes,
|
||||
) -> Result<Option<i64>, StorageError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn increase_bandwidth(
|
||||
&self,
|
||||
_client_address: DestinationAddressBytes,
|
||||
_amount: i64,
|
||||
_freepass_expiration: OffsetDateTime,
|
||||
) -> Result<(), StorageError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn consume_bandwidth(
|
||||
async fn reset_freepass_bandwidth(
|
||||
&self,
|
||||
_client_address: DestinationAddressBytes,
|
||||
) -> Result<(), StorageError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn get_available_bandwidth(
|
||||
&self,
|
||||
_client_address: DestinationAddressBytes,
|
||||
) -> Result<Option<PersistedBandwidth>, StorageError> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn set_bandwidth(
|
||||
&self,
|
||||
_client_address: DestinationAddressBytes,
|
||||
_amount: i64,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright 2021-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::client_handling::websocket::connection_handler::AvailableBandwidth;
|
||||
use sqlx::FromRow;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
pub struct PersistedSharedKeys {
|
||||
pub(crate) client_address_bs58: String,
|
||||
@@ -15,10 +17,30 @@ pub struct StoredMessage {
|
||||
pub(crate) content: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromRow)]
|
||||
pub struct PersistedBandwidth {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) client_address_bs58: String,
|
||||
pub(crate) available: i64,
|
||||
pub(crate) freepass_expiration: Option<OffsetDateTime>,
|
||||
}
|
||||
|
||||
impl From<PersistedBandwidth> for AvailableBandwidth {
|
||||
fn from(value: PersistedBandwidth) -> Self {
|
||||
AvailableBandwidth {
|
||||
bytes: value.available,
|
||||
freepass_expiration: value.freepass_expiration,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<PersistedBandwidth>> for AvailableBandwidth {
|
||||
fn from(value: Option<PersistedBandwidth>) -> Self {
|
||||
match value {
|
||||
None => AvailableBandwidth::default(),
|
||||
Some(b) => b.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, FromRow)]
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-mixnode"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.35"
|
||||
version = "1.1.37"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#![allow(unused)]
|
||||
|
||||
use crate::node::http::legacy::description::description;
|
||||
use crate::node::http::legacy::hardware::hardware;
|
||||
use crate::node::http::legacy::state::MixnodeAppState;
|
||||
|
||||
@@ -105,7 +105,7 @@ impl<'a> HttpApiBuilder<'a> {
|
||||
|
||||
let router = nym_node_http_api::NymNodeRouter::new(config, None, None);
|
||||
let server = router
|
||||
.with_merged(legacy::routes(self.legacy_mixnode, self.legacy_descriptor))
|
||||
// .with_merged(legacy::routes(self.legacy_mixnode, self.legacy_descriptor))
|
||||
.build_server(&bind_address)?
|
||||
.with_task_client(task_client);
|
||||
tokio::spawn(async move { server.run().await });
|
||||
|
||||
@@ -46,7 +46,7 @@ pub struct MixNode {
|
||||
impl MixNode {
|
||||
pub fn new(config: Config) -> Result<Self, MixnodeError> {
|
||||
Ok(MixNode {
|
||||
run_http_server: false,
|
||||
run_http_server: true,
|
||||
descriptor: Self::load_node_description(&config),
|
||||
identity_keypair: Arc::new(load_identity_keys(&config)?),
|
||||
sphinx_keypair: Arc::new(load_sphinx_keys(&config)?),
|
||||
@@ -64,7 +64,7 @@ impl MixNode {
|
||||
sphinx_keypair: Arc<encryption::KeyPair>,
|
||||
) -> Self {
|
||||
MixNode {
|
||||
run_http_server: false,
|
||||
run_http_server: true,
|
||||
task_client: None,
|
||||
config,
|
||||
descriptor,
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-api"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.35"
|
||||
version = "1.1.37"
|
||||
authors = [
|
||||
"Dave Hrycyszyn <futurechimp@users.noreply.github.com>",
|
||||
"Jędrzej Stuczyński <andrew@nymtech.net>",
|
||||
|
||||
@@ -15,12 +15,14 @@ schemars = { workspace = true, features = ["preserve_order"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
ts-rs = { workspace = true, optional = true }
|
||||
tendermint = { workspace = true }
|
||||
time = { workspace = true, features = ["serde", "parsing", "formatting"] }
|
||||
|
||||
|
||||
# for serde on secp256k1 signatures
|
||||
ecdsa = { version = "0.16", features = ["serde"] }
|
||||
|
||||
nym-credentials-interface = { path = "../../common/credentials-interface" }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["serde", "asymmetric"]}
|
||||
nym-crypto = { path = "../../common/crypto", features = ["serde", "asymmetric"] }
|
||||
|
||||
nym-mixnet-contract-common = { path = "../../common/cosmwasm-smart-contracts/mixnet-contract" }
|
||||
nym-node-requests = { path = "../../nym-node/nym-node-requests", default-features = false }
|
||||
|
||||
@@ -9,12 +9,16 @@ use nym_mixnet_contract_common::rewarding::RewardEstimate;
|
||||
use nym_mixnet_contract_common::{
|
||||
GatewayBond, IdentityKey, Interval, MixId, MixNode, Percent, RewardedSetNodeStatus,
|
||||
};
|
||||
use nym_node_requests::api::v1::gateway::models::WebSockets;
|
||||
use nym_node_requests::api::v1::node::models::{BinaryBuildInformationOwned, HostInformation};
|
||||
use nym_node_requests::api::v1::node::models::BinaryBuildInformationOwned;
|
||||
use schemars::gen::SchemaGenerator;
|
||||
use schemars::schema::{InstanceType, Schema, SchemaObject};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::net::IpAddr;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::{fmt, time::Duration};
|
||||
use time::OffsetDateTime;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
pub struct RequestError {
|
||||
@@ -356,8 +360,119 @@ pub struct CirculatingSupplyResponse {
|
||||
pub circulating_supply: Coin,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema)]
|
||||
pub struct HostInformation {
|
||||
pub ip_address: Vec<IpAddr>,
|
||||
pub hostname: Option<String>,
|
||||
pub keys: HostKeys,
|
||||
}
|
||||
|
||||
impl From<nym_node_requests::api::v1::node::models::HostInformation> for HostInformation {
|
||||
fn from(value: nym_node_requests::api::v1::node::models::HostInformation) -> Self {
|
||||
HostInformation {
|
||||
ip_address: value.ip_address,
|
||||
hostname: value.hostname,
|
||||
keys: value.keys.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema)]
|
||||
pub struct HostKeys {
|
||||
pub ed25519: String,
|
||||
pub x25519: String,
|
||||
}
|
||||
|
||||
impl From<nym_node_requests::api::v1::node::models::HostKeys> for HostKeys {
|
||||
fn from(value: nym_node_requests::api::v1::node::models::HostKeys) -> Self {
|
||||
HostKeys {
|
||||
ed25519: value.ed25519_identity,
|
||||
x25519: value.x25519_sphinx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema)]
|
||||
pub struct WebSockets {
|
||||
pub ws_port: u16,
|
||||
|
||||
pub wss_port: Option<u16>,
|
||||
}
|
||||
|
||||
impl From<nym_node_requests::api::v1::gateway::models::WebSockets> for WebSockets {
|
||||
fn from(value: nym_node_requests::api::v1::gateway::models::WebSockets) -> Self {
|
||||
WebSockets {
|
||||
ws_port: value.ws_port,
|
||||
wss_port: value.wss_port,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const fn unix_epoch() -> OffsetDateTime {
|
||||
OffsetDateTime::UNIX_EPOCH
|
||||
}
|
||||
|
||||
// for all intents and purposes it's just OffsetDateTime, but we need JsonSchema...
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct OffsetDateTimeJsonSchemaWrapper(#[serde(default = "unix_epoch")] pub OffsetDateTime);
|
||||
|
||||
impl Default for OffsetDateTimeJsonSchemaWrapper {
|
||||
fn default() -> Self {
|
||||
OffsetDateTimeJsonSchemaWrapper(unix_epoch())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OffsetDateTimeJsonSchemaWrapper> for OffsetDateTime {
|
||||
fn from(value: OffsetDateTimeJsonSchemaWrapper) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OffsetDateTime> for OffsetDateTimeJsonSchemaWrapper {
|
||||
fn from(value: OffsetDateTime) -> Self {
|
||||
OffsetDateTimeJsonSchemaWrapper(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for OffsetDateTimeJsonSchemaWrapper {
|
||||
type Target = OffsetDateTime;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for OffsetDateTimeJsonSchemaWrapper {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
// implementation taken from: https://github.com/GREsau/schemars/pull/207
|
||||
impl JsonSchema for OffsetDateTimeJsonSchemaWrapper {
|
||||
fn is_referenceable() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn schema_name() -> String {
|
||||
"DateTime".into()
|
||||
}
|
||||
|
||||
fn json_schema(_: &mut SchemaGenerator) -> Schema {
|
||||
SchemaObject {
|
||||
instance_type: Some(InstanceType::String.into()),
|
||||
format: Some("date-time".into()),
|
||||
..Default::default()
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema)]
|
||||
pub struct NymNodeDescription {
|
||||
#[serde(default)]
|
||||
pub last_polled: OffsetDateTimeJsonSchemaWrapper,
|
||||
|
||||
pub host_information: HostInformation,
|
||||
|
||||
// TODO: do we really care about ALL build info or just the version?
|
||||
@@ -402,3 +517,41 @@ pub struct IpPacketRouterDetails {
|
||||
/// address of the embedded ip packet router
|
||||
pub address: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema)]
|
||||
pub struct ApiHealthResponse {
|
||||
pub status: ApiStatus,
|
||||
pub uptime: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ApiStatus {
|
||||
Up,
|
||||
}
|
||||
|
||||
impl ApiHealthResponse {
|
||||
pub fn new_healthy(uptime: Duration) -> Self {
|
||||
ApiHealthResponse {
|
||||
status: ApiStatus::Up,
|
||||
uptime: uptime.as_secs(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ApiStatus {
|
||||
pub fn is_up(&self) -> bool {
|
||||
matches!(self, ApiStatus::Up)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, schemars::JsonSchema)]
|
||||
pub struct SignerInformationResponse {
|
||||
pub cosmos_address: String,
|
||||
|
||||
pub identity: String,
|
||||
|
||||
pub announce_address: String,
|
||||
|
||||
pub verification_key: Option<String>,
|
||||
}
|
||||
|
||||
@@ -67,6 +67,10 @@ fn validate_freepass_public_attributes(res: &FreePassRequest) -> Result<()> {
|
||||
return Err(CoconutError::TooLongFreePass { expiry_date });
|
||||
}
|
||||
|
||||
if expiry_date < now {
|
||||
return Err(CoconutError::FreePassExpiryInThePast { expiry_date });
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,9 @@ pub enum CoconutError {
|
||||
)]
|
||||
TooLongFreePass { expiry_date: OffsetDateTime },
|
||||
|
||||
#[error("the provided free pass expiry is set in the past!")]
|
||||
FreePassExpiryInThePast { expiry_date: OffsetDateTime },
|
||||
|
||||
#[error("the received bandwidth voucher did not contain deposit value")]
|
||||
MissingBandwidthValue,
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2022-2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_coconut::VerificationKey;
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_dkg::Scalar;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
@@ -63,6 +64,13 @@ impl KeyPair {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn verification_key(&self) -> Option<RwLockReadGuard<'_, VerificationKey>> {
|
||||
RwLockReadGuard::try_map(self.get().await?, |maybe_keypair| {
|
||||
maybe_keypair.as_ref().map(|k| k.keys.verification_key())
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub async fn read_keys(&self) -> RwLockReadGuard<'_, Option<KeyPairWithEpoch>> {
|
||||
self.keys.read().await
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ pub(crate) mod node_describe_cache;
|
||||
pub(crate) mod node_status_api;
|
||||
pub(crate) mod nym_contract_cache;
|
||||
pub(crate) mod nym_nodes;
|
||||
mod status;
|
||||
pub(crate) mod support;
|
||||
|
||||
struct ShutdownHandles {
|
||||
|
||||
@@ -16,6 +16,7 @@ use nym_mixnet_contract_common::Gateway;
|
||||
use nym_node_requests::api::client::{NymNodeApiClientError, NymNodeApiClientExt};
|
||||
use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
// type alias for ease of use
|
||||
pub type DescribedNodes = HashMap<IdentityKey, NymNodeDescription>;
|
||||
@@ -181,11 +182,12 @@ async fn get_gateway_description(
|
||||
};
|
||||
|
||||
let description = NymNodeDescription {
|
||||
host_information: host_info.data,
|
||||
host_information: host_info.data.into(),
|
||||
last_polled: OffsetDateTime::now_utc().into(),
|
||||
build_information: build_info,
|
||||
network_requester,
|
||||
ip_packet_router,
|
||||
mixnet_websockets: websockets,
|
||||
mixnet_websockets: websockets.into(),
|
||||
};
|
||||
|
||||
Ok((gateway.identity_key, description))
|
||||
@@ -211,7 +213,7 @@ impl CacheItemProvider for NodeDescriptionProvider {
|
||||
}
|
||||
|
||||
// TODO: somehow bypass the 'higher-ranked lifetime error' and remove that redundant clone
|
||||
let websockets = stream::iter(
|
||||
let node_description = stream::iter(
|
||||
gateways
|
||||
// .deref()
|
||||
// .clone()
|
||||
@@ -224,7 +226,7 @@ impl CacheItemProvider for NodeDescriptionProvider {
|
||||
match res {
|
||||
Ok((identity, description)) => Some((identity, description)),
|
||||
Err(err) => {
|
||||
debug!("{err}");
|
||||
debug!("failed to obtain gateway self-described data: {err}");
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -232,7 +234,7 @@ impl CacheItemProvider for NodeDescriptionProvider {
|
||||
.collect::<HashMap<_, _>>()
|
||||
.await;
|
||||
|
||||
Ok(websockets)
|
||||
Ok(node_description)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::coconut;
|
||||
use nym_bin_common::bin_info;
|
||||
use nym_bin_common::build_information::BinaryBuildInformation;
|
||||
use okapi::openapi3::OpenApi;
|
||||
use rocket::Route;
|
||||
use rocket_okapi::openapi_get_routes_spec;
|
||||
use rocket_okapi::settings::OpenApiSettings;
|
||||
use tokio::time::Instant;
|
||||
|
||||
pub(crate) mod routes;
|
||||
|
||||
pub(crate) struct ApiStatusState {
|
||||
startup_time: Instant,
|
||||
build_information: BinaryBuildInformation,
|
||||
signer_information: Option<SignerState>,
|
||||
}
|
||||
|
||||
pub(crate) struct SignerState {
|
||||
// static information
|
||||
pub cosmos_address: String,
|
||||
|
||||
pub identity: String,
|
||||
|
||||
pub announce_address: String,
|
||||
|
||||
pub(crate) coconut_keypair: coconut::keys::KeyPair,
|
||||
}
|
||||
|
||||
impl ApiStatusState {
|
||||
pub fn new() -> Self {
|
||||
ApiStatusState {
|
||||
startup_time: Instant::now(),
|
||||
build_information: bin_info!(),
|
||||
signer_information: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_zk_nym_signer(&mut self, signer_information: SignerState) {
|
||||
self.signer_information = Some(signer_information)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn api_status_routes(settings: &OpenApiSettings) -> (Vec<Route>, OpenApi) {
|
||||
openapi_get_routes_spec![
|
||||
settings:
|
||||
routes::health,
|
||||
routes::build_information,
|
||||
routes::signer_information
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node_status_api::models::ErrorResponse;
|
||||
use crate::status::ApiStatusState;
|
||||
use nym_api_requests::models::{ApiHealthResponse, SignerInformationResponse};
|
||||
use nym_bin_common::build_information::BinaryBuildInformationOwned;
|
||||
use nym_coconut::Base58;
|
||||
use rocket::http::Status;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
use rocket_okapi::openapi;
|
||||
|
||||
#[openapi(tag = "Api Status")]
|
||||
#[get("/health")]
|
||||
pub(crate) async fn health(state: &State<ApiStatusState>) -> Json<ApiHealthResponse> {
|
||||
let uptime = state.startup_time.elapsed();
|
||||
let health = ApiHealthResponse::new_healthy(uptime);
|
||||
Json(health)
|
||||
}
|
||||
|
||||
#[openapi(tag = "Api Status")]
|
||||
#[get("/build-information")]
|
||||
pub(crate) async fn build_information(
|
||||
state: &State<ApiStatusState>,
|
||||
) -> Json<BinaryBuildInformationOwned> {
|
||||
Json(state.build_information.to_owned())
|
||||
}
|
||||
|
||||
#[openapi(tag = "Api Status")]
|
||||
#[get("/signer-information")]
|
||||
pub(crate) async fn signer_information(
|
||||
state: &State<ApiStatusState>,
|
||||
) -> Result<Json<SignerInformationResponse>, ErrorResponse> {
|
||||
let signer_state = state.signer_information.as_ref().ok_or_else(|| {
|
||||
ErrorResponse::new(
|
||||
"this api does not expose zk-nym signing functionalities",
|
||||
Status::InternalServerError,
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(Json(SignerInformationResponse {
|
||||
cosmos_address: signer_state.cosmos_address.clone(),
|
||||
identity: signer_state.identity.clone(),
|
||||
announce_address: signer_state.announce_address.clone(),
|
||||
verification_key: signer_state
|
||||
.coconut_keypair
|
||||
.verification_key()
|
||||
.await
|
||||
.map(|maybe_vk| maybe_vk.to_bs58()),
|
||||
}))
|
||||
}
|
||||
@@ -9,6 +9,7 @@ use crate::network::network_routes;
|
||||
use crate::node_describe_cache::DescribedNodes;
|
||||
use crate::node_status_api::{self, NodeStatusCache};
|
||||
use crate::nym_contract_cache::cache::NymContractCache;
|
||||
use crate::status::{api_status_routes, ApiStatusState, SignerState};
|
||||
use crate::support::caching::cache::SharedCache;
|
||||
use crate::support::config::Config;
|
||||
use crate::support::{nyxd, storage};
|
||||
@@ -27,7 +28,7 @@ pub(crate) mod openapi;
|
||||
pub(crate) async fn setup_rocket(
|
||||
config: &Config,
|
||||
network_details: NetworkDetails,
|
||||
_nyxd_client: nyxd::Client,
|
||||
nyxd_client: nyxd::Client,
|
||||
identity_keypair: identity::KeyPair,
|
||||
coconut_keypair: coconut::keys::KeyPair,
|
||||
) -> anyhow::Result<Rocket<Ignite>> {
|
||||
@@ -45,6 +46,7 @@ pub(crate) async fn setup_rocket(
|
||||
"" => nym_contract_cache::nym_contract_cache_routes(&openapi_settings),
|
||||
"/status" => node_status_api::node_status_routes(&openapi_settings, config.network_monitor.enabled),
|
||||
"/network" => network_routes(&openapi_settings),
|
||||
"/api-status" => api_status_routes(&openapi_settings),
|
||||
"" => nym_node_routes(&openapi_settings),
|
||||
}
|
||||
|
||||
@@ -68,18 +70,34 @@ pub(crate) async fn setup_rocket(
|
||||
None
|
||||
};
|
||||
|
||||
let mut status_state = ApiStatusState::new();
|
||||
|
||||
let rocket = if config.coconut_signer.enabled {
|
||||
// make sure we have some tokens to cover multisig fees
|
||||
let balance = _nyxd_client.balance(&mix_denom).await?;
|
||||
let balance = nyxd_client.balance(&mix_denom).await?;
|
||||
if balance.amount < coconut::MINIMUM_BALANCE {
|
||||
let address = _nyxd_client.address().await;
|
||||
let address = nyxd_client.address().await;
|
||||
let min = Coin::new(coconut::MINIMUM_BALANCE, mix_denom);
|
||||
bail!("the account ({address}) doesn't have enough funds to cover verification fees. it has {balance} while it needs at least {min}")
|
||||
}
|
||||
|
||||
let comm_channel = QueryCommunicationChannel::new(_nyxd_client.clone());
|
||||
let cosmos_address = nyxd_client.address().await.to_string();
|
||||
let announce_address = config
|
||||
.coconut_signer
|
||||
.announce_address
|
||||
.clone()
|
||||
.map(|u| u.to_string())
|
||||
.unwrap_or_default();
|
||||
status_state.add_zk_nym_signer(SignerState {
|
||||
cosmos_address,
|
||||
identity: identity_keypair.public_key().to_base58_string(),
|
||||
announce_address,
|
||||
coconut_keypair: coconut_keypair.clone(),
|
||||
});
|
||||
|
||||
let comm_channel = QueryCommunicationChannel::new(nyxd_client.clone());
|
||||
rocket.attach(coconut::stage(
|
||||
_nyxd_client.clone(),
|
||||
nyxd_client.clone(),
|
||||
mix_denom,
|
||||
identity_keypair,
|
||||
coconut_keypair,
|
||||
@@ -97,7 +115,7 @@ pub(crate) async fn setup_rocket(
|
||||
rocket
|
||||
};
|
||||
|
||||
Ok(rocket.ignite().await?)
|
||||
Ok(rocket.manage(status_state).ignite().await?)
|
||||
}
|
||||
|
||||
fn setup_cors() -> Result<Cors> {
|
||||
|
||||
+3
-2
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node"
|
||||
version = "0.1.0"
|
||||
version = "1.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
@@ -18,6 +18,7 @@ anyhow.workspace = true
|
||||
bip39 = { workspace = true, features = ["zeroize"] }
|
||||
bs58.workspace = true
|
||||
celes = "2.4.0" # country codes
|
||||
colored = "2"
|
||||
clap = { workspace = true, features = ["cargo", "env"] }
|
||||
humantime-serde = { workspace = true }
|
||||
ipnetwork = "0.16.0"
|
||||
@@ -58,4 +59,4 @@ nym-ip-packet-router = { path = "../service-providers/ip-packet-router" }
|
||||
|
||||
[build-dependencies]
|
||||
# temporary bonding information v1 (to grab and parse nym-mixnode and nym-gateway package versions)
|
||||
cargo_metadata = "0.18.1"
|
||||
cargo_metadata = "0.18.1"
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use cargo_metadata::MetadataCommand;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
// that's disgusting, but it works, so it's good enough for now ¯\_(ツ)_/¯
|
||||
fn main() {
|
||||
let out_dir: PathBuf = std::env::var("OUT_DIR").unwrap().into();
|
||||
|
||||
let path: PathBuf = std::env::var("CARGO_MANIFEST_DIR").unwrap().into();
|
||||
|
||||
let mix_path = path.parent().unwrap().join("mixnode");
|
||||
let gateway_path = path.parent().unwrap().join("gateway");
|
||||
|
||||
let mix_meta = MetadataCommand::new()
|
||||
.manifest_path("./Cargo.toml")
|
||||
.current_dir(&mix_path)
|
||||
.exec()
|
||||
.unwrap();
|
||||
let mix_version = &mix_meta.root_package().unwrap().version;
|
||||
|
||||
let gateway_meta = MetadataCommand::new()
|
||||
.manifest_path("./Cargo.toml")
|
||||
.current_dir(&gateway_path)
|
||||
.exec()
|
||||
.unwrap();
|
||||
|
||||
let gateway_version = &gateway_meta.root_package().unwrap().version;
|
||||
|
||||
fs::write(out_dir.join("mixnode_version"), mix_version.to_string()).unwrap();
|
||||
fs::write(out_dir.join("gateway_version"), gateway_version.to_string()).unwrap();
|
||||
|
||||
println!("cargo::rerun-if-changed=build.rs");
|
||||
}
|
||||
@@ -36,6 +36,9 @@ nym-bin-common = { path = "../../common/bin-common", features = ["bin_info_schem
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
rand_chacha = "0.2"
|
||||
nym-crypto = { path = "../../common/crypto", features = ["rand"] }
|
||||
|
||||
|
||||
[features]
|
||||
default = ["client"]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::api::v1::node::models::HostInformation;
|
||||
use crate::api::v1::node::models::{HostInformation, LegacyHostInformation};
|
||||
use crate::error::Error;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use schemars::JsonSchema;
|
||||
@@ -61,7 +61,16 @@ impl SignedHostInformation {
|
||||
return false;
|
||||
};
|
||||
|
||||
self.verify(&pub_key)
|
||||
if self.verify(&pub_key) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// attempt to verify legacy signature
|
||||
SignedData {
|
||||
data: LegacyHostInformation::from(self.data.clone()),
|
||||
signature: self.signature.clone(),
|
||||
}
|
||||
.verify(&pub_key)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,3 +93,71 @@ impl Display for ErrorResponse {
|
||||
self.message.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nym_crypto::asymmetric::{ed25519, x25519};
|
||||
use rand_chacha::rand_core::SeedableRng;
|
||||
|
||||
#[test]
|
||||
fn dummy_signed_host_verification() {
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]);
|
||||
let ed22519 = ed25519::KeyPair::new(&mut rng);
|
||||
let x25519_sphinx = x25519::KeyPair::new(&mut rng);
|
||||
|
||||
let host_info = crate::api::v1::node::models::HostInformation {
|
||||
ip_address: vec!["1.1.1.1".parse().unwrap()],
|
||||
hostname: Some("foomp.com".to_string()),
|
||||
keys: crate::api::v1::node::models::HostKeys {
|
||||
ed25519_identity: ed22519.public_key().to_base58_string(),
|
||||
x25519_sphinx: x25519_sphinx.public_key().to_base58_string(),
|
||||
x25519_noise: "".to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
let signed_info = SignedHostInformation::new(host_info, ed22519.private_key()).unwrap();
|
||||
assert!(signed_info.verify(ed22519.public_key()));
|
||||
assert!(signed_info.verify_host_information())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dummy_legacy_signed_host_verification() {
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed([0u8; 32]);
|
||||
let ed22519 = ed25519::KeyPair::new(&mut rng);
|
||||
let x25519_sphinx = x25519::KeyPair::new(&mut rng);
|
||||
|
||||
let legacy_info = crate::api::v1::node::models::LegacyHostInformation {
|
||||
ip_address: vec!["1.1.1.1".parse().unwrap()],
|
||||
hostname: Some("foomp.com".to_string()),
|
||||
keys: crate::api::v1::node::models::LegacyHostKeys {
|
||||
ed25519: ed22519.public_key().to_base58_string(),
|
||||
x25519: x25519_sphinx.public_key().to_base58_string(),
|
||||
},
|
||||
};
|
||||
|
||||
let host_info = crate::api::v1::node::models::HostInformation {
|
||||
ip_address: legacy_info.ip_address.clone(),
|
||||
hostname: legacy_info.hostname.clone(),
|
||||
keys: crate::api::v1::node::models::HostKeys {
|
||||
ed25519_identity: legacy_info.keys.ed25519.clone(),
|
||||
x25519_sphinx: legacy_info.keys.x25519.clone(),
|
||||
x25519_noise: "".to_string(),
|
||||
},
|
||||
};
|
||||
|
||||
// signature on legacy data
|
||||
let signature = SignedData::new(legacy_info, ed22519.private_key())
|
||||
.unwrap()
|
||||
.signature;
|
||||
|
||||
// signed blob with the 'current' structure
|
||||
let current_struct = SignedData {
|
||||
data: host_info,
|
||||
signature,
|
||||
};
|
||||
|
||||
assert!(!current_struct.verify(ed22519.public_key()));
|
||||
assert!(current_struct.verify_host_information())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@ use serde::{Deserialize, Serialize};
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub struct Gateway {
|
||||
#[serde(default)]
|
||||
pub enforces_zk_nyms: bool,
|
||||
|
||||
pub client_interfaces: ClientInterfaces,
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,23 @@ pub struct HostInformation {
|
||||
pub keys: HostKeys,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct LegacyHostInformation {
|
||||
pub ip_address: Vec<IpAddr>,
|
||||
pub hostname: Option<String>,
|
||||
pub keys: LegacyHostKeys,
|
||||
}
|
||||
|
||||
impl From<HostInformation> for LegacyHostInformation {
|
||||
fn from(value: HostInformation) -> Self {
|
||||
LegacyHostInformation {
|
||||
ip_address: value.ip_address,
|
||||
hostname: value.hostname,
|
||||
keys: value.keys.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub struct HostKeys {
|
||||
@@ -49,6 +66,21 @@ pub struct HostKeys {
|
||||
pub x25519_noise: String,
|
||||
}
|
||||
|
||||
impl From<HostKeys> for LegacyHostKeys {
|
||||
fn from(value: HostKeys) -> Self {
|
||||
LegacyHostKeys {
|
||||
ed25519: value.ed25519_identity,
|
||||
x25519: value.x25519_sphinx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct LegacyHostKeys {
|
||||
pub ed25519: String,
|
||||
pub x25519: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
|
||||
pub struct HostSystem {
|
||||
|
||||
@@ -5,8 +5,12 @@ use crate::cli::helpers::{
|
||||
EntryGatewayArgs, ExitGatewayArgs, HostArgs, HttpArgs, MixnetArgs, MixnodeArgs, WireguardArgs,
|
||||
};
|
||||
use crate::node::description::save_node_description;
|
||||
use crate::node::helpers::{load_ed25519_identity_public_key, store_x25519_noise_keypair};
|
||||
use crate::node::helpers::{
|
||||
bonding_version, load_ed25519_identity_public_key, store_x25519_noise_keypair,
|
||||
};
|
||||
use clap::ValueEnum;
|
||||
use colored::Color::TrueColor;
|
||||
use colored::Colorize;
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use nym_gateway::helpers::{load_ip_packet_router_config, load_network_requester_config};
|
||||
use nym_gateway::GatewayError;
|
||||
@@ -223,6 +227,7 @@ async fn migrate_mixnode(mut args: Args) -> Result<(), NymNodeError> {
|
||||
packet_forwarding_maximum_backoff: cfg.debug.packet_forwarding_maximum_backoff,
|
||||
initial_connection_timeout: cfg.debug.initial_connection_timeout,
|
||||
maximum_connection_buffer_size: cfg.debug.maximum_connection_buffer_size,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}))
|
||||
@@ -385,6 +390,7 @@ async fn migrate_gateway(mut args: Args) -> Result<(), NymNodeError> {
|
||||
packet_forwarding_maximum_backoff: cfg.debug.packet_forwarding_maximum_backoff,
|
||||
initial_connection_timeout: cfg.debug.initial_connection_timeout,
|
||||
maximum_connection_buffer_size: cfg.debug.maximum_connection_buffer_size,
|
||||
..Default::default()
|
||||
},
|
||||
}))
|
||||
.with_mixnode(args.mixnode.override_config_section(config::MixnodeConfig {
|
||||
@@ -625,7 +631,28 @@ pub(crate) async fn execute(args: Args) -> Result<(), NymNodeError> {
|
||||
trace!("args: {args:#?}");
|
||||
|
||||
match args.node_type {
|
||||
NodeType::Mixnode => migrate_mixnode(args).await,
|
||||
NodeType::Gateway => migrate_gateway(args).await,
|
||||
NodeType::Mixnode => migrate_mixnode(args).await?,
|
||||
NodeType::Gateway => migrate_gateway(args).await?,
|
||||
}
|
||||
|
||||
let orange = TrueColor {
|
||||
r: 251,
|
||||
g: 110,
|
||||
b: 78,
|
||||
};
|
||||
|
||||
println!("{}", "** Attention **".color(orange).bold());
|
||||
print!("Please consider updating the '");
|
||||
print!("{}", "version".color(orange));
|
||||
print!("' field of your ");
|
||||
print!("{}", "existing".bold().underline());
|
||||
println!(" node to:");
|
||||
println!();
|
||||
println!("{}", bonding_version().bold().color(orange));
|
||||
println!();
|
||||
print!("in the settings section of the ");
|
||||
println!("{}", "Nym Wallet".bold().color(orange));
|
||||
println!();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ pub(crate) async fn execute(mut args: Args) -> Result<(), NymNodeError> {
|
||||
let config_path = args.config.config_path();
|
||||
let output = args.output;
|
||||
let bonding_info_path = args.bonding_information_output.clone();
|
||||
let init_only = args.init_only;
|
||||
|
||||
let config = if !config_path.exists() {
|
||||
debug!("no configuration file found at '{}'", config_path.display());
|
||||
@@ -25,16 +26,11 @@ pub(crate) async fn execute(mut args: Args) -> Result<(), NymNodeError> {
|
||||
if args.deny_init {
|
||||
return Err(NymNodeError::ForbiddenInitialisation { config_path });
|
||||
}
|
||||
let init_only = args.init_only;
|
||||
|
||||
let maybe_custom_mnemonic = args.take_mnemonic();
|
||||
|
||||
let config = args.build_config()?;
|
||||
NymNode::initialise(&config, maybe_custom_mnemonic).await?;
|
||||
if init_only {
|
||||
debug!("returning due to the 'init-only' flag");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
config
|
||||
} else {
|
||||
@@ -73,5 +69,10 @@ pub(crate) async fn execute(mut args: Args) -> Result<(), NymNodeError> {
|
||||
})?;
|
||||
}
|
||||
|
||||
if init_only {
|
||||
debug!("returning due to the 'init-only' flag");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
nym_node.run().await
|
||||
}
|
||||
|
||||
@@ -195,6 +195,22 @@ pub(crate) struct MixnetArgs {
|
||||
env = NYMNODE_NYM_APIS_ARG
|
||||
)]
|
||||
pub(crate) nym_api_urls: Option<Vec<Url>>,
|
||||
|
||||
/// Addresses to nyxd chain endpoint which the node will use for chain interactions.
|
||||
#[clap(
|
||||
long,
|
||||
value_delimiter = ',',
|
||||
env = NYMNODE_NYXD_URLS_ARG
|
||||
)]
|
||||
pub(crate) nyxd_urls: Option<Vec<Url>>,
|
||||
|
||||
/// Specifies whether this node should **NOT** use noise protocol in the connections (currently not implemented)
|
||||
#[clap(
|
||||
hide = true,
|
||||
long,
|
||||
env = NYMNODE_UNSAFE_DISABLE_NOISE
|
||||
)]
|
||||
pub(crate) unsafe_disable_noise: bool,
|
||||
}
|
||||
|
||||
impl MixnetArgs {
|
||||
@@ -210,6 +226,12 @@ impl MixnetArgs {
|
||||
if let Some(nym_api_urls) = self.nym_api_urls {
|
||||
section.nym_api_urls = nym_api_urls
|
||||
}
|
||||
if let Some(nyxd_urls) = self.nyxd_urls {
|
||||
section.nyxd_urls = nyxd_urls
|
||||
}
|
||||
if self.unsafe_disable_noise {
|
||||
section.debug.unsafe_disable_noise = true
|
||||
}
|
||||
section
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,6 +437,9 @@ pub struct MixnetDebug {
|
||||
|
||||
/// Maximum number of packets that can be stored waiting to get sent to a particular connection.
|
||||
pub maximum_connection_buffer_size: usize,
|
||||
|
||||
/// Specifies whether this node should **NOT** use noise protocol in the connections (currently not implemented)
|
||||
pub unsafe_disable_noise: bool,
|
||||
}
|
||||
|
||||
impl MixnetDebug {
|
||||
@@ -453,6 +456,8 @@ impl Default for MixnetDebug {
|
||||
packet_forwarding_maximum_backoff: Self::DEFAULT_PACKET_FORWARDING_MAXIMUM_BACKOFF,
|
||||
initial_connection_timeout: Self::DEFAULT_INITIAL_CONNECTION_TIMEOUT,
|
||||
maximum_connection_buffer_size: Self::DEFAULT_MAXIMUM_CONNECTION_BUFFER_SIZE,
|
||||
// to be changed by @SW once the implementation is there
|
||||
unsafe_disable_noise: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,8 @@ pub mod vars {
|
||||
// mixnet:
|
||||
pub const NYMNODE_MIXNET_BIND_ADDRESS_ARG: &str = "NYMNODE_MIXNET_BIND_ADDRESS";
|
||||
pub const NYMNODE_NYM_APIS_ARG: &str = "NYMNODE_NYM_APIS";
|
||||
pub const NYMNODE_NYXD_URLS_ARG: &str = "NYMNODE_NYXD";
|
||||
pub const NYMNODE_UNSAFE_DISABLE_NOISE: &str = "UNSAFE_DISABLE_NOISE";
|
||||
|
||||
// wireguard:
|
||||
pub const NYMNODE_WG_ENABLED_ARG: &str = "NYMNODE_WG_ENABLED";
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::node::helpers::{load_ed25519_identity_public_key, load_x25519_sphinx_public_key};
|
||||
use crate::node::helpers::{
|
||||
bonding_version, load_ed25519_identity_public_key, load_x25519_sphinx_public_key,
|
||||
};
|
||||
use nym_node::config::{Config, NodeMode};
|
||||
use nym_node::error::NymNodeError;
|
||||
use semver::{BuildMetadata, Version};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
@@ -60,26 +61,12 @@ impl MixnodeBondingInformation {
|
||||
x25519_sphinx_key: String,
|
||||
) -> MixnodeBondingInformation {
|
||||
MixnodeBondingInformation {
|
||||
version: Self::get_version(),
|
||||
version: bonding_version(),
|
||||
host: "YOU NEED TO FILL THIS FIELD MANUALLY".to_string(),
|
||||
identity_key: ed25519_identity_key,
|
||||
sphinx_key: x25519_sphinx_key,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
fn get_version() -> String {
|
||||
// SAFETY:
|
||||
// 1. the value has been put into the environment during build.rs, so it must exist,
|
||||
// 2. and the obtained version has already been parsed into semver in build.rs, so it must be a valid semver
|
||||
let raw = include_str!(concat!(env!("OUT_DIR"), "/mixnode_version"));
|
||||
let mut semver: Version = raw.parse().unwrap();
|
||||
|
||||
// if it's not empty, then we messed up our own versioning
|
||||
assert!(semver.build.is_empty());
|
||||
semver.build = BuildMetadata::new("nymnode").unwrap();
|
||||
semver.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for MixnodeBondingInformation {
|
||||
@@ -108,27 +95,13 @@ impl GatewayBondingInformation {
|
||||
x25519_sphinx_key: String,
|
||||
) -> GatewayBondingInformation {
|
||||
GatewayBondingInformation {
|
||||
version: Self::get_version(),
|
||||
version: bonding_version(),
|
||||
host: "YOU NEED TO FILL THIS FIELD MANUALLY".to_string(),
|
||||
location: "YOU NEED TO FILL THIS FIELD MANUALLY".to_string(),
|
||||
identity_key: ed25519_identity_key,
|
||||
sphinx_key: x25519_sphinx_key,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
fn get_version() -> String {
|
||||
// SAFETY:
|
||||
// 1. the value has been put into the file during build.rs, so it must exist,
|
||||
// 2. and the obtained version has already been parsed into semver in build.rs, so it must be a valid semver
|
||||
let raw = include_str!(concat!(env!("OUT_DIR"), "/gateway_version"));
|
||||
let mut semver: Version = raw.parse().unwrap();
|
||||
|
||||
// if it's not empty, then we messed up our own versioning
|
||||
assert!(semver.build.is_empty());
|
||||
semver.build = BuildMetadata::new("nymnode").unwrap();
|
||||
semver.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for GatewayBondingInformation {
|
||||
|
||||
@@ -7,10 +7,24 @@ use nym_node::error::{KeyIOFailure, NymNodeError};
|
||||
use nym_node_http_api::api::api_requests::v1::node::models::NodeDescription;
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
use nym_pemstore::KeyPairPath;
|
||||
use semver::{BuildMetadata, Version};
|
||||
use serde::Serialize;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::path::Path;
|
||||
|
||||
#[allow(clippy::unwrap_used)]
|
||||
pub fn bonding_version() -> String {
|
||||
// SAFETY:
|
||||
// the value has been put there by cargo
|
||||
let raw = env!("CARGO_PKG_VERSION");
|
||||
let mut semver: Version = raw.parse().unwrap();
|
||||
|
||||
// if it's not empty, then we messed up our own versioning
|
||||
assert!(semver.build.is_empty());
|
||||
semver.build = BuildMetadata::new("nymnode").unwrap();
|
||||
semver.to_string()
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(crate) struct DisplayDetails {
|
||||
pub(crate) current_mode: NodeMode,
|
||||
|
||||
@@ -16,13 +16,19 @@ pub(crate) fn sign_host_details(
|
||||
x25519_noise: &x25519::PublicKey,
|
||||
ed22519_identity: &ed25519::KeyPair,
|
||||
) -> Result<api_requests::v1::node::models::SignedHostInformation, NymNodeError> {
|
||||
let x25519_noise = if config.mixnet.debug.unsafe_disable_noise {
|
||||
String::new()
|
||||
} else {
|
||||
x25519_noise.to_base58_string()
|
||||
};
|
||||
|
||||
let host_info = api_requests::v1::node::models::HostInformation {
|
||||
ip_address: config.host.public_ips.clone(),
|
||||
hostname: config.host.hostname.clone(),
|
||||
keys: api_requests::v1::node::models::HostKeys {
|
||||
ed25519_identity: ed22519_identity.public_key().to_base58_string(),
|
||||
x25519_sphinx: x22519_sphinx.to_base58_string(),
|
||||
x25519_noise: x25519_noise.to_base58_string(),
|
||||
x25519_noise,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -484,6 +484,7 @@ impl NymNode {
|
||||
wss_port: self.config.entry_gateway.announce_wss_port,
|
||||
});
|
||||
let gateway_details = api_requests::v1::gateway::models::Gateway {
|
||||
enforces_zk_nyms: self.config.entry_gateway.enforce_zk_nyms,
|
||||
client_interfaces: api_requests::v1::gateway::models::ClientInterfaces {
|
||||
wireguard,
|
||||
mixnet_websockets,
|
||||
|
||||
@@ -130,6 +130,7 @@ impl OutfoxPacket {
|
||||
// We know that we'll always get 4 nodes, so we can unwrap here
|
||||
let processing_node = nodes.last().unwrap();
|
||||
let destination_node = nodes.first().unwrap();
|
||||
OsRng.fill_bytes(&mut secret_key);
|
||||
stage_params.encode_mix_layer(
|
||||
&mut buffer[range],
|
||||
&secret_key,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym_wallet"
|
||||
version = "1.2.12"
|
||||
version = "1.2.13"
|
||||
description = "Nym Native Wallet"
|
||||
authors = ["Nym Technologies SA"]
|
||||
license = ""
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"package": {
|
||||
"productName": "nym-wallet",
|
||||
"version": "1.2.12"
|
||||
"version": "1.2.13"
|
||||
},
|
||||
"build": {
|
||||
"distDir": "../dist",
|
||||
|
||||
+15
-14
@@ -3,30 +3,31 @@ import os.path
|
||||
import sys
|
||||
|
||||
|
||||
def add_mixnode(base_network, base_dir, mix_id):
|
||||
def add_mixnode(base_network, base_dir, mix_id, port_delta):
|
||||
with open(os.path.join(base_dir, "mix" + str(mix_id) + ".json"), "r") as json_blob:
|
||||
mix_data = json.load(json_blob)
|
||||
|
||||
base_network["mixnodes"][str(mix_id)][0]["identity_key"] = mix_data["identity_key"]
|
||||
base_network["mixnodes"][str(mix_id)][0]["sphinx_key"] = mix_data["sphinx_key"]
|
||||
base_network["mixnodes"][str(mix_id)][0]["mix_port"] = mix_data["mix_port"]
|
||||
base_network["mixnodes"][str(mix_id)][0]["mix_port"] = 10000 + port_delta
|
||||
base_network["mixnodes"][str(mix_id)][0]["version"] = mix_data["version"]
|
||||
base_network["mixnodes"][str(mix_id)][0]["host"] = mix_data["bind_address"]
|
||||
base_network["mixnodes"][str(mix_id)][0]["layer"] = mix_id
|
||||
base_network["mixnodes"][str(mix_id)][0]["host"] = "127.0.0.1"
|
||||
base_network["mixnodes"][str(mix_id)][0]["layer"] = mix_id % 3 + 1
|
||||
base_network["mixnodes"][str(mix_id)][0]["mix_id"] = mix_id
|
||||
base_network["mixnodes"][str(mix_id)][0]["owner"] = "whatever"
|
||||
base_network["mixnodes"][str(mix_id)][0]["owner"] = "n1jw6mp7d5xqc7w6xm79lha27glmd0vdt3l9artf"
|
||||
return base_network
|
||||
|
||||
|
||||
def add_gateway(base_network, base_dir):
|
||||
def add_gateway(base_network, base_dir, port_delta):
|
||||
with open(os.path.join(base_dir, "gateway.json"), "r") as json_blob:
|
||||
gateway_data = json.load(json_blob)
|
||||
base_network["gateways"][0]["identity_key"] = gateway_data["identity_key"]
|
||||
base_network["gateways"][0]["sphinx_key"] = gateway_data["sphinx_key"]
|
||||
base_network["gateways"][0]["mix_port"] = gateway_data["mix_port"]
|
||||
base_network["gateways"][0]["clients_port"] = gateway_data["clients_port"]
|
||||
base_network["gateways"][0]["mix_port"] = 10000 + port_delta
|
||||
base_network["gateways"][0]["clients_port"] = 9000
|
||||
# base_network["gateways"][0]["version"] = gateway_data["version"]
|
||||
base_network["gateways"][0]["host"] = gateway_data["bind_address"]
|
||||
base_network["gateways"][0]["owner"] = "whatever"
|
||||
base_network["gateways"][0]["host"] = "127.0.0.1"
|
||||
base_network["gateways"][0]["owner"] = "n1jw6mp7d5xqc7w6xm79lha27glmd0vdt3l9artf"
|
||||
return base_network
|
||||
|
||||
|
||||
@@ -41,10 +42,10 @@ def main(args):
|
||||
}
|
||||
|
||||
base_dir = args[0]
|
||||
base_network = add_mixnode(base_network, base_dir, 1)
|
||||
base_network = add_mixnode(base_network, base_dir, 2)
|
||||
base_network = add_mixnode(base_network, base_dir, 3)
|
||||
base_network = add_gateway(base_network, base_dir)
|
||||
base_network = add_mixnode(base_network, base_dir, 1, 1)
|
||||
base_network = add_mixnode(base_network, base_dir, 2, 2)
|
||||
base_network = add_mixnode(base_network, base_dir, 3, 3)
|
||||
base_network = add_gateway(base_network, base_dir, 4)
|
||||
|
||||
with open(os.path.join(base_dir, "network.json"), "w") as out:
|
||||
json.dump(base_network, out, indent=2)
|
||||
|
||||
@@ -20,16 +20,16 @@ echo "Using $localnetdir for the localnet"
|
||||
|
||||
# initialise mixnet
|
||||
echo "initialising mixnode1..."
|
||||
cargo run --release --bin nym-mixnode -- init --id "mix1-$suffix" --host 127.0.0.1 --mix-port 10001 --verloc-port 20001 --http-api-port 30001 --metrics-key=lala --output=json >>"$localnetdir/mix1.json"
|
||||
cargo run --release --bin nym-node -- run --id "mix1-$suffix" --init-only --mixnet-bind-address=127.0.0.1:10001 --verloc-bind-address 127.0.0.1:20001 --http-bind-address 127.0.0.1:30001 --http-access-token=lala --output=json --bonding-information-output "$localnetdir/mix1.json"
|
||||
|
||||
echo "initialising mixnode2..."
|
||||
cargo run --release --bin nym-mixnode -- init --id "mix2-$suffix" --host 127.0.0.1 --mix-port 10002 --verloc-port 20002 --http-api-port 30002 --metrics-key=lala --output=json >>"$localnetdir/mix2.json"
|
||||
cargo run --release --bin nym-node -- run --id "mix2-$suffix" --init-only --mixnet-bind-address=127.0.0.1:10002 --verloc-bind-address 127.0.0.1:20002 --http-bind-address 127.0.0.1:30002 --http-access-token=lala --output=json --bonding-information-output "$localnetdir/mix2.json"
|
||||
|
||||
echo "initialising mixnode3..."
|
||||
cargo run --release --bin nym-mixnode -- init --id "mix3-$suffix" --host 127.0.0.1 --mix-port 10003 --verloc-port 20003 --http-api-port 30003 --metrics-key=lala --output=json >>"$localnetdir/mix3.json"
|
||||
cargo run --release --bin nym-node -- run --id "mix3-$suffix" --init-only --mixnet-bind-address=127.0.0.1:10003 --verloc-bind-address 127.0.0.1:20003 --http-bind-address 127.0.0.1:30003 --http-access-token=lala --output=json --bonding-information-output "$localnetdir/mix3.json"
|
||||
|
||||
echo "initialising gateway..."
|
||||
cargo run --release --bin nym-gateway -- init --id "gateway-$suffix" --host 127.0.0.1 --mix-port 10004 --clients-port 9000 --output=json >>"$localnetdir/gateway.json"
|
||||
cargo run --release --bin nym-node -- run --id "gateway-$suffix" --init-only --mode entry --mixnet-bind-address=127.0.0.1:10004 --entry-bind-address 127.0.0.1:9000 --verloc-bind-address 127.0.0.1:20004 --http-bind-address 127.0.0.1:30004 --http-access-token=lala --output=json --bonding-information-output "$localnetdir/gateway.json"
|
||||
|
||||
# build the topology
|
||||
echo "combining json files..."
|
||||
@@ -42,10 +42,10 @@ echo "the full network file is located at $networkfile"
|
||||
echo "starting the mixnet..."
|
||||
tmux start-server
|
||||
|
||||
tmux new-session -d -s localnet -n Mixnet -d "/usr/bin/env sh -c \" cargo run --release --bin nym-mixnode -- run --id mix1-$suffix \""
|
||||
tmux split-window -t localnet:0 "/usr/bin/env sh -c \" cargo run --release --bin nym-mixnode -- run --id mix2-$suffix \""
|
||||
tmux split-window -t localnet:0 "/usr/bin/env sh -c \" cargo run --release --bin nym-mixnode -- run --id mix3-$suffix \""
|
||||
tmux split-window -t localnet:0 "/usr/bin/env sh -c \" cargo run --release --bin nym-gateway -- run --id gateway-$suffix --local \""
|
||||
tmux new-session -d -s localnet -n Mixnet -d "/usr/bin/env sh -c \" cargo run --release --bin nym-node -- run --id mix1-$suffix \""
|
||||
tmux split-window -t localnet:0 "/usr/bin/env sh -c \" cargo run --release --bin nym-node -- run --id mix2-$suffix \""
|
||||
tmux split-window -t localnet:0 "/usr/bin/env sh -c \" cargo run --release --bin nym-node -- run --id mix3-$suffix \""
|
||||
tmux split-window -t localnet:0 "/usr/bin/env sh -c \" cargo run --release --bin nym-node -- run --id gateway-$suffix \""
|
||||
|
||||
while ! nc -z localhost 9000; do
|
||||
echo "waiting for nym-gateway to launch on port 9000..."
|
||||
|
||||
Generated
+4
-4
@@ -1424,9 +1424,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "eyre"
|
||||
version = "0.6.11"
|
||||
version = "0.6.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799"
|
||||
checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
|
||||
dependencies = [
|
||||
"indenter",
|
||||
"once_cell",
|
||||
@@ -1841,9 +1841,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.24"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
|
||||
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
|
||||
Generated
+4
-4
@@ -1424,9 +1424,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "eyre"
|
||||
version = "0.6.11"
|
||||
version = "0.6.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799"
|
||||
checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
|
||||
dependencies = [
|
||||
"indenter",
|
||||
"once_cell",
|
||||
@@ -1841,9 +1841,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.24"
|
||||
version = "0.3.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
|
||||
checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
|
||||
Generated
+2
-2
@@ -1424,9 +1424,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
|
||||
|
||||
[[package]]
|
||||
name = "eyre"
|
||||
version = "0.6.11"
|
||||
version = "0.6.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6267a1fa6f59179ea4afc8e50fd8612a3cc60bc858f786ff877a4a8cb042799"
|
||||
checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
|
||||
dependencies = [
|
||||
"indenter",
|
||||
"once_cell",
|
||||
|
||||
@@ -39,7 +39,7 @@ mod socks5_client;
|
||||
mod traits;
|
||||
|
||||
pub use client::{DisconnectedMixnetClient, IncludedSurbs, MixnetClientBuilder};
|
||||
pub use config::{Config, KeyMode};
|
||||
pub use config::Config;
|
||||
pub use native_client::MixnetClient;
|
||||
pub use native_client::MixnetClientSender;
|
||||
pub use nym_client_core::{
|
||||
|
||||
@@ -10,8 +10,11 @@ use crate::NymNetworkDetails;
|
||||
use crate::{Error, Result};
|
||||
use futures::channel::mpsc;
|
||||
use futures::StreamExt;
|
||||
use log::warn;
|
||||
use nym_client_core::client::base_client::storage::helpers::get_all_registered_identities;
|
||||
use log::{debug, warn};
|
||||
use nym_client_core::client::base_client::storage::helpers::{
|
||||
get_active_gateway_identity, get_all_registered_identities, has_gateway_details,
|
||||
set_active_gateway,
|
||||
};
|
||||
use nym_client_core::client::base_client::storage::{
|
||||
Ephemeral, GatewaysDetailsStore, MixnetClientStorage, OnDiskPersistent,
|
||||
};
|
||||
@@ -23,6 +26,7 @@ use nym_client_core::client::{
|
||||
use nym_client_core::config::DebugConfig;
|
||||
use nym_client_core::error::ClientCoreError;
|
||||
use nym_client_core::init::helpers::current_gateways;
|
||||
use nym_client_core::init::setup_gateway;
|
||||
use nym_client_core::init::types::{GatewaySelectionSpecification, GatewaySetup};
|
||||
use nym_network_defaults::WG_TUN_DEVICE_IP_ADDRESS;
|
||||
use nym_socks5_client_core::config::Socks5;
|
||||
@@ -248,13 +252,15 @@ where
|
||||
|
||||
/// Construct a [`DisconnectedMixnetClient`] from the setup specified.
|
||||
pub fn build(self) -> Result<DisconnectedMixnetClient<S>> {
|
||||
let client = DisconnectedMixnetClient::new(self.config, self.socks5_config, self.storage)?
|
||||
.custom_gateway_transceiver(self.custom_gateway_transceiver)
|
||||
.custom_topology_provider(self.custom_topology_provider)
|
||||
.custom_shutdown(self.custom_shutdown)
|
||||
.wireguard_mode(self.wireguard_mode)
|
||||
.wait_for_gateway(self.wait_for_gateway)
|
||||
.force_tls(self.force_tls);
|
||||
let mut client =
|
||||
DisconnectedMixnetClient::new(self.config, self.socks5_config, self.storage)?;
|
||||
|
||||
client.custom_gateway_transceiver = self.custom_gateway_transceiver;
|
||||
client.custom_topology_provider = self.custom_topology_provider;
|
||||
client.custom_shutdown = self.custom_shutdown;
|
||||
client.wireguard_mode = self.wireguard_mode;
|
||||
client.wait_for_gateway = self.wait_for_gateway;
|
||||
client.force_tls = self.force_tls;
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
@@ -355,48 +361,6 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn custom_shutdown(mut self, shutdown: Option<TaskClient>) -> Self {
|
||||
self.custom_shutdown = shutdown;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn custom_topology_provider(
|
||||
mut self,
|
||||
provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
) -> Self {
|
||||
self.custom_topology_provider = provider;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn custom_gateway_transceiver(
|
||||
mut self,
|
||||
gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send + Sync>>,
|
||||
) -> Self {
|
||||
self.custom_gateway_transceiver = gateway_transceiver;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn wireguard_mode(mut self, wireguard_mode: bool) -> Self {
|
||||
self.wireguard_mode = wireguard_mode;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn wait_for_gateway(mut self, wait_for_gateway: bool) -> Self {
|
||||
self.wait_for_gateway = wait_for_gateway;
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn force_tls(mut self, must_use_tls: bool) -> Self {
|
||||
self.force_tls = must_use_tls;
|
||||
self
|
||||
}
|
||||
|
||||
fn get_api_endpoints(&self) -> Vec<Url> {
|
||||
self.config
|
||||
.network_details
|
||||
@@ -426,6 +390,65 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
async fn setup_client_keys(&self) -> Result<()> {
|
||||
let mut rng = OsRng;
|
||||
let key_store = self.storage.key_store();
|
||||
|
||||
if key_store.load_keys().await.is_err() {
|
||||
debug!("Generating new client keys");
|
||||
nym_client_core::init::generate_new_client_keys(&mut rng, key_store).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn print_all_registered_gateway_identities(&self) {
|
||||
match get_all_registered_identities(self.storage.gateway_details_store()).await {
|
||||
Err(err) => {
|
||||
warn!("failed to query for all registered gateways: {err}")
|
||||
}
|
||||
Ok(all_ids) => {
|
||||
if !all_ids.is_empty() {
|
||||
debug!("this client is already registered with the following gateways:");
|
||||
for id in all_ids {
|
||||
debug!("{id}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn print_selected_gateway(&self) {
|
||||
match self.storage.gateway_details_store().active_gateway().await {
|
||||
Err(err) => {
|
||||
warn!("failed to query for the current active gateway: {err}")
|
||||
}
|
||||
Ok(active) => {
|
||||
if let Some(active) = active.registration {
|
||||
let id = active.details.gateway_id();
|
||||
debug!("currently selected gateway: {0}", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn set_active_gateway_if_previously_registered(
|
||||
&self,
|
||||
user_chosen_gateway: &str,
|
||||
) -> Result<bool> {
|
||||
let storage = self.storage.gateway_details_store();
|
||||
// Stricly speaking, `set_active_gateway` does this check internally as well, but since the
|
||||
// error is boxed away and we're using a generic storage, it's not so easy to match on it.
|
||||
// This function is at least less likely to fail on something unrelated to the existence of
|
||||
// the gateway in the set of registered gateways
|
||||
if has_gateway_details(storage, user_chosen_gateway).await? {
|
||||
set_active_gateway(storage, user_chosen_gateway).await?;
|
||||
Ok(true)
|
||||
} else {
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
|
||||
async fn new_gateway_setup(&self) -> Result<GatewaySetup, ClientCoreError> {
|
||||
let nym_api_endpoints = self.get_api_endpoints();
|
||||
|
||||
@@ -445,67 +468,67 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if the client already has an active gateway enabled.
|
||||
async fn has_active_gateway(&self) -> bool {
|
||||
let storage = self.storage.gateway_details_store();
|
||||
|
||||
match storage.active_gateway().await {
|
||||
Err(err) => {
|
||||
warn!("failed to query for the current active gateway: {err}");
|
||||
return false;
|
||||
}
|
||||
Ok(active) => {
|
||||
if active.registration.is_some() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match get_all_registered_identities(storage).await {
|
||||
Err(err) => {
|
||||
warn!("failed to query for all registered gateways: {err}")
|
||||
}
|
||||
Ok(all_ids) => {
|
||||
if !all_ids.is_empty() {
|
||||
warn!("this client doesn't have an active gateway set, however, it's already registered with the following gateways (consider making one of them active):");
|
||||
for id in all_ids {
|
||||
warn!("{id}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// Register with a gateway. If a gateway is provided in the config then that will try to be
|
||||
/// used. If none is specified, a gateway at random will be picked.
|
||||
/// used. If none is specified, a gateway at random will be picked. The used gateway is saved
|
||||
/// as the active gateway.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return an error if you try to re-register when in an already registered
|
||||
/// state.
|
||||
pub async fn register_and_authenticate_gateway(&mut self) -> Result<()> {
|
||||
pub async fn setup_gateway(&mut self) -> Result<()> {
|
||||
if !matches!(self.state, BuilderState::New) {
|
||||
return Err(Error::ReregisteringGatewayNotSupported);
|
||||
}
|
||||
|
||||
log::debug!("Registering with gateway");
|
||||
self.print_all_registered_gateway_identities().await;
|
||||
self.print_selected_gateway().await;
|
||||
|
||||
let gateway_setup = if self.has_active_gateway().await {
|
||||
GatewaySetup::MustLoad { gateway_id: None }
|
||||
} else {
|
||||
self.new_gateway_setup().await?
|
||||
// Try to set active gateway to the same as the user chosen one, if it's in the set of
|
||||
// gateways that is already registered.
|
||||
if let Some(ref user_chosen_gateway) = self.config.user_chosen_gateway {
|
||||
if self
|
||||
.set_active_gateway_if_previously_registered(user_chosen_gateway)
|
||||
.await?
|
||||
{
|
||||
debug!("user chosen gateway is already registered, set as active");
|
||||
}
|
||||
}
|
||||
|
||||
let active_gateway =
|
||||
get_active_gateway_identity(self.storage.gateway_details_store()).await?;
|
||||
|
||||
// Determine the gateway setup based on the currently active gateway and the user-chosen
|
||||
// gateway.
|
||||
let gateway_setup = match (self.config.user_chosen_gateway.as_ref(), active_gateway) {
|
||||
// When a user-chosen gateway exists and matches the active one.
|
||||
(Some(user_chosen_gateway), Some(active_gateway))
|
||||
if &active_gateway.to_base58_string() == user_chosen_gateway =>
|
||||
{
|
||||
GatewaySetup::MustLoad { gateway_id: None }
|
||||
}
|
||||
// When a user-chosen gateway exists but there's no active gateway, or it doesn't match the active one.
|
||||
(Some(_), _) => self.new_gateway_setup().await?,
|
||||
// When no user-chosen gateway exists but there's an active gateway.
|
||||
(None, Some(_)) => GatewaySetup::MustLoad { gateway_id: None },
|
||||
// When there's no user-chosen gateway and no active gateway.
|
||||
(None, None) => self.new_gateway_setup().await?,
|
||||
};
|
||||
|
||||
// this will perform necessary key and details load and optional store
|
||||
let _init_result = nym_client_core::init::setup_gateway(
|
||||
let init_results = setup_gateway(
|
||||
gateway_setup,
|
||||
self.storage.key_store(),
|
||||
self.storage.gateway_details_store(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
set_active_gateway(
|
||||
self.storage.gateway_details_store(),
|
||||
&init_results.gateway_id().to_base58_string(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.state = BuilderState::Registered {};
|
||||
Ok(())
|
||||
}
|
||||
@@ -527,13 +550,8 @@ where
|
||||
}
|
||||
|
||||
async fn connect_to_mixnet_common(mut self) -> Result<(BaseClient, Recipient)> {
|
||||
// if we don't care about our keys, explicitly register
|
||||
if !self.config.key_mode.is_keep() {
|
||||
self.register_and_authenticate_gateway().await?;
|
||||
}
|
||||
|
||||
// otherwise, the whole key setup and gateway selection dance will be done for us
|
||||
// when we start the base client
|
||||
self.setup_client_keys().await?;
|
||||
self.setup_gateway().await?;
|
||||
|
||||
let nyxd_endpoints = self.get_nyxd_endpoints();
|
||||
let nym_api_endpoints = self.get_api_endpoints();
|
||||
@@ -543,25 +561,11 @@ where
|
||||
.config
|
||||
.as_base_client_config(nyxd_endpoints, nym_api_endpoints.clone());
|
||||
|
||||
let known_gateway = self.has_active_gateway().await;
|
||||
|
||||
// if we have a known gateway, don't bother doing all of those queries
|
||||
let gateway_setup = if known_gateway {
|
||||
None
|
||||
} else {
|
||||
Some(self.new_gateway_setup().await?)
|
||||
};
|
||||
|
||||
let mut base_builder: BaseClientBuilder<_, _> =
|
||||
BaseClientBuilder::new(&base_config, self.storage, self.dkg_query_client)
|
||||
.with_wait_for_gateway(self.wait_for_gateway)
|
||||
.with_wireguard_connection(self.wireguard_mode);
|
||||
|
||||
if !known_gateway {
|
||||
// safety: `gateway_setup` is always set whenever `known_gateway` is false
|
||||
base_builder = base_builder.with_gateway_setup(gateway_setup.unwrap());
|
||||
}
|
||||
|
||||
// let mut base_builder: BaseClientBuilder<_, _> = if !known_gateway {
|
||||
// // we need to setup a new gateway
|
||||
// let setup = self.new_gateway_setup().await;
|
||||
|
||||
@@ -5,30 +5,12 @@ use url::Url;
|
||||
|
||||
const DEFAULT_SDK_CLIENT_ID: &str = "_default-nym-sdk-client";
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub enum KeyMode {
|
||||
/// Use existing key files if they exists, otherwise create new ones.
|
||||
#[default]
|
||||
Keep,
|
||||
/// Create new keys, overwriting any potential previously existing keys.
|
||||
Overwrite,
|
||||
}
|
||||
|
||||
impl KeyMode {
|
||||
pub(crate) fn is_keep(&self) -> bool {
|
||||
matches!(self, KeyMode::Keep)
|
||||
}
|
||||
}
|
||||
|
||||
/// Config struct for [`crate::mixnet::MixnetClient`]
|
||||
#[derive(Default)]
|
||||
pub struct Config {
|
||||
/// If the user has explicitly specified a gateway.
|
||||
pub user_chosen_gateway: Option<String>,
|
||||
|
||||
/// Determines how to handle existing key files found.
|
||||
pub key_mode: KeyMode,
|
||||
|
||||
/// The details of the network we're using. It defaults to the mainnet network.
|
||||
pub network_details: NymNetworkDetails,
|
||||
|
||||
|
||||
@@ -10,5 +10,7 @@ use nym_ip_packet_router::error::IpPacketRouterError;
|
||||
pub(crate) async fn execute(
|
||||
args: CommonClientImportCredentialArgs,
|
||||
) -> Result<(), IpPacketRouterError> {
|
||||
import_credential::<CliIpPacketRouterClient, _>(args).await
|
||||
import_credential::<CliIpPacketRouterClient, _>(args).await?;
|
||||
println!("successfully imported credential!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-network-requester"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.33"
|
||||
version = "1.1.34"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version = "1.70"
|
||||
|
||||
@@ -10,5 +10,7 @@ use nym_client_core::cli_helpers::client_import_credential::{
|
||||
pub(crate) async fn execute(
|
||||
args: CommonClientImportCredentialArgs,
|
||||
) -> Result<(), NetworkRequesterError> {
|
||||
import_credential::<CliNetworkRequesterClient, _>(args).await
|
||||
import_credential::<CliNetworkRequesterClient, _>(args).await?;
|
||||
println!("successfully imported credential!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -444,7 +444,7 @@ impl NRServiceProvider {
|
||||
controller_sender: ControllerSender,
|
||||
mix_input_sender: MixProxySender<MixnetMessage>,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
shutdown: TaskClient,
|
||||
mut shutdown: TaskClient,
|
||||
) {
|
||||
let mut conn = match socks5::tcp::Connection::new(
|
||||
connection_id,
|
||||
@@ -456,6 +456,7 @@ impl NRServiceProvider {
|
||||
Ok(conn) => conn,
|
||||
Err(err) => {
|
||||
log::error!("error while connecting to {remote_addr}: {err}",);
|
||||
shutdown.disarm();
|
||||
|
||||
// inform the remote that the connection is closed before it even was established
|
||||
let mixnet_message = MixnetMessage::new_network_data_response(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-network-statistics"
|
||||
version = "1.1.33"
|
||||
version = "1.1.34"
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-cli"
|
||||
version = "1.1.34"
|
||||
version = "1.1.35"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
|
||||
@@ -23,6 +23,9 @@ pub(crate) async fn execute(
|
||||
nym_cli_commands::validator::mixnet::delegators::MixnetDelegatorsCommands::DelegateVesting(args) => {
|
||||
nym_cli_commands::validator::mixnet::delegators::vesting_delegate_to_mixnode::vesting_delegate_to_mixnode(args, create_signing_client(global_args, network_details)?).await
|
||||
}
|
||||
nym_cli_commands::validator::mixnet::delegators::MixnetDelegatorsCommands::DelegateMulti(args) => {
|
||||
nym_cli_commands::validator::mixnet::delegators::delegate_to_multiple_mixnodes::delegate_to_multiple_mixnodes(args, create_signing_client(global_args, network_details)?).await
|
||||
}
|
||||
nym_cli_commands::validator::mixnet::delegators::MixnetDelegatorsCommands::Undelegate(args) => {
|
||||
nym_cli_commands::validator::mixnet::delegators::undelegate_from_mixnode::undelegate_from_mixnode(args, create_signing_client(global_args, network_details)?).await
|
||||
}
|
||||
|
||||
@@ -765,9 +765,9 @@ sync-promise-expanded@^1.0.0:
|
||||
integrity sha512-pdxxEOaeKO6LghTz0Fe7yw82fx95gtS0SxVgRvIwvN4h9qTie8oOF/pWuH8PGp+PVduS84RXXxO/xrW93Nno9w==
|
||||
|
||||
tar@^6.0.2, tar@^6.1.11, tar@^6.1.2:
|
||||
version "6.1.15"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.15.tgz#c9738b0b98845a3b344d334b8fa3041aaba53a69"
|
||||
integrity sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a"
|
||||
integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==
|
||||
dependencies:
|
||||
chownr "^2.0.0"
|
||||
fs-minipass "^2.0.0"
|
||||
|
||||
@@ -2,6 +2,6 @@ module go-mix-conn
|
||||
|
||||
go 1.20
|
||||
|
||||
require golang.org/x/net v0.17.0
|
||||
require golang.org/x/net v0.23.0
|
||||
|
||||
require golang.org/x/text v0.13.0 // indirect
|
||||
require golang.org/x/text v0.14.0 // indirect
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user