Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b83fdd34af | |||
| 356cd2eeac | |||
| 0f6ec8610e | |||
| c3b8c4b2f7 | |||
| 271b9e545c | |||
| 9641f01670 | |||
| a7bb3e8d91 | |||
| 79ce611d21 | |||
| 960e817b8f | |||
| 8b03e66ba7 | |||
| 6a35581299 | |||
| ce124a29a7 | |||
| f62d8813e0 | |||
| a9cf016af2 | |||
| a8403b585b | |||
| e9a7b48da0 | |||
| 66792f57ed | |||
| f8d863249e | |||
| 7d59a2477a | |||
| eca88b0fa4 | |||
| b80a4c8614 | |||
| ec5d342e3a | |||
| 6565655861 | |||
| 5aba886f14 | |||
| 3ee73d541e | |||
| 4588a3036e | |||
| 6194ac07b8 | |||
| a7fcfef5a3 | |||
| fa927b82d8 | |||
| f724478763 | |||
| 040f4f2500 | |||
| 63002e784a | |||
| 4a0b683b70 | |||
| 9e84b1f0c1 | |||
| bf031ad6de | |||
| 933769401c | |||
| ddd85704bb | |||
| 17860c809f | |||
| 2d00fcd934 | |||
| c2c3df98cb | |||
| f429092e21 | |||
| d7ef68d8d1 | |||
| 1a334b575d | |||
| 2126736aff | |||
| a69aa23609 | |||
| 8a2d98e3ce | |||
| 9c4243914e | |||
| 143ede268d | |||
| 81bddb5f6d | |||
| 247ebb7c43 | |||
| 01c052e9a4 | |||
| 3880971e57 | |||
| 6bd31b9521 | |||
| 430c33eb04 | |||
| d45d1eb313 | |||
| 3cb3ebd79b | |||
| b42e5b063e | |||
| f6b30d0db6 | |||
| c33e4c0836 | |||
| be92ccf0da | |||
| 35bf49c48c | |||
| 7335a3dad4 | |||
| 09ea406c02 | |||
| 8c6f84b3fe | |||
| 27dc9c8024 | |||
| 6c781a0064 | |||
| 080ec80722 | |||
| 9c17239831 | |||
| f6c19ec02b | |||
| 94ff8a79ee | |||
| 155c4d37ef | |||
| 2a6fe6624d | |||
| f52f07f6ec | |||
| b709d3ba0b | |||
| 40dd7dc95e | |||
| b2f6836756 | |||
| 87e429d78a | |||
| 4178809555 | |||
| 9de5d7213a | |||
| 94eb362a71 | |||
| 0f615f48f2 | |||
| d511611641 | |||
| 17d3ff2d77 | |||
| dd3dcfa7fe | |||
| 86ea2d23cb | |||
| 42a37442e8 | |||
| 6b24f081e1 | |||
| 6e5d0dac1b | |||
| 5f2740bf66 | |||
| ecb15034d3 | |||
| bd49c222a3 | |||
| 0d397ab5cc |
@@ -26,6 +26,7 @@ jobs:
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -99,7 +100,6 @@ jobs:
|
||||
cp target/release/nymvisor $OUTPUT_DIR
|
||||
cp target/release/nym-node $OUTPUT_DIR
|
||||
cp target/release/nym-cli $OUTPUT_DIR
|
||||
cp target/release/explorer-api $OUTPUT_DIR
|
||||
if [ ${{ github.event_name == 'workflow_dispatch' && inputs.enable_deb == true }} = true ]; then
|
||||
cp target/debian/*.deb $OUTPUT_DIR
|
||||
fi
|
||||
|
||||
@@ -12,6 +12,7 @@ jobs:
|
||||
runs-on: arc-ubuntu-22.04
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
@@ -37,6 +37,7 @@ jobs:
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
IPINFO_API_TOKEN: ${{ secrets.IPINFO_API_TOKEN }}
|
||||
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||
steps:
|
||||
- name: Install Dependencies (Linux)
|
||||
run: sudo apt-get update && sudo apt-get -y install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libudev-dev squashfs-tools protobuf-compiler
|
||||
|
||||
@@ -4,6 +4,28 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.4-dorina-patched] (2025-03-06)
|
||||
|
||||
- use legacy crypto for constructing SURB headers ([#5579])
|
||||
- bugfix: make sure to correctly decode response content when putting it into error message ([#5571])
|
||||
- Tweak surb management to be more conservative ([#5570])
|
||||
- Deserialize v5 authenticator requests ([#5568])
|
||||
- chore: additional logs when attempting to load ecash keys ([#5567])
|
||||
- add full response body to error message upon decoding failure ([#5566])
|
||||
- hotfix: ensure we bail on merkle leaves insertion upon missing data ([#5565])
|
||||
- feature: v2 authentication request (#5537) ([#5563])
|
||||
- Create authenticator v5 request/response types ([#5561])
|
||||
|
||||
[#5579]: https://github.com/nymtech/nym/pull/5579
|
||||
[#5571]: https://github.com/nymtech/nym/pull/5571
|
||||
[#5570]: https://github.com/nymtech/nym/pull/5570
|
||||
[#5568]: https://github.com/nymtech/nym/pull/5568
|
||||
[#5567]: https://github.com/nymtech/nym/pull/5567
|
||||
[#5566]: https://github.com/nymtech/nym/pull/5566
|
||||
[#5565]: https://github.com/nymtech/nym/pull/5565
|
||||
[#5563]: https://github.com/nymtech/nym/pull/5563
|
||||
[#5561]: https://github.com/nymtech/nym/pull/5561
|
||||
|
||||
## [2025.4-dorina] (2025-03-04)
|
||||
|
||||
- fixed sphinx version metrics registration ([#5546])
|
||||
|
||||
Generated
+867
-801
File diff suppressed because it is too large
Load Diff
+25
-24
@@ -137,7 +137,7 @@ members = [
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract",
|
||||
"tools/internal/testnet-manager/dkg-bypass-contract", "tools/internal/validator-status-check",
|
||||
"tools/nym-cli",
|
||||
"tools/nym-id-cli",
|
||||
"tools/nym-nr-query",
|
||||
@@ -191,10 +191,10 @@ aes = "0.8.1"
|
||||
aes-gcm = "0.10.1"
|
||||
aes-gcm-siv = "0.11.1"
|
||||
ammonia = "4"
|
||||
anyhow = "1.0.95"
|
||||
anyhow = "1.0.97"
|
||||
arc-swap = "1.7.1"
|
||||
argon2 = "0.5.0"
|
||||
async-trait = "0.1.86"
|
||||
async-trait = "0.1.87"
|
||||
axum = "0.7.5"
|
||||
axum-client-ip = "0.6.1"
|
||||
axum-extra = "0.9.4"
|
||||
@@ -205,24 +205,24 @@ bincode = "1.3.3"
|
||||
bip39 = { version = "2.0.0", features = ["zeroize"] }
|
||||
bit-vec = "0.7.0" # can we unify those?
|
||||
bitvec = "1.0.0"
|
||||
blake3 = "1.5.5"
|
||||
blake3 = "1.6.1"
|
||||
bloomfilter = "1.0.14"
|
||||
bs58 = "0.5.1"
|
||||
bytecodec = "0.4.15"
|
||||
bytes = "1.7.2"
|
||||
bytes = "1.10.1"
|
||||
cargo_metadata = "0.18.1"
|
||||
celes = "2.5.0"
|
||||
cfg-if = "1.0.0"
|
||||
chacha20 = "0.9.0"
|
||||
chacha20poly1305 = "0.10.1"
|
||||
chrono = "0.4.39"
|
||||
chrono = "0.4.40"
|
||||
cipher = "0.4.3"
|
||||
clap = "4.5.30"
|
||||
clap = "4.5.31"
|
||||
clap_complete = "4.5"
|
||||
clap_complete_fig = "4.5"
|
||||
colored = "2.2"
|
||||
comfy-table = "7.1.4"
|
||||
console = "0.15.10"
|
||||
console = "0.15.11"
|
||||
console-subscriber = "0.1.1"
|
||||
console_error_panic_hook = "0.1"
|
||||
const-str = "0.5.6"
|
||||
@@ -241,17 +241,18 @@ doc-comment = "0.3"
|
||||
dotenvy = "0.15.6"
|
||||
ecdsa = "0.16"
|
||||
ed25519-dalek = "2.1"
|
||||
encoding_rs = "0.8.35"
|
||||
env_logger = "0.11.6"
|
||||
envy = "0.4"
|
||||
etherparse = "0.13.0"
|
||||
eyre = "0.6.9"
|
||||
fastrand = "2.1.1"
|
||||
flate2 = "1.0.35"
|
||||
flate2 = "1.1.0"
|
||||
futures = "0.3.31"
|
||||
futures-util = "0.3"
|
||||
generic-array = "0.14.7"
|
||||
getrandom = "0.2.10"
|
||||
getset = "0.1.4"
|
||||
getset = "0.1.5"
|
||||
handlebars = "3.5.5"
|
||||
headers = "0.4.0"
|
||||
hex = "0.4.3"
|
||||
@@ -272,7 +273,7 @@ inquire = "0.6.2"
|
||||
ip_network = "0.4.1"
|
||||
ipnetwork = "0.20"
|
||||
isocountry = "0.3.2"
|
||||
itertools = "0.13.0"
|
||||
itertools = "0.14.0"
|
||||
k256 = "0.13"
|
||||
lazy_static = "1.5.0"
|
||||
ledger-transport = "0.10.0"
|
||||
@@ -307,21 +308,21 @@ reqwest = { version = "0.12.4", default-features = false }
|
||||
rocket = "0.5.0"
|
||||
rocket_cors = "0.6.0"
|
||||
rocket_okapi = "0.8.0"
|
||||
rs_merkle = "1.4.2"
|
||||
rs_merkle = "1.5.0"
|
||||
safer-ffi = "0.1.13"
|
||||
schemars = "0.8.21"
|
||||
semver = "1.0.25"
|
||||
serde = "1.0.217"
|
||||
serde_bytes = "0.11.15"
|
||||
schemars = "0.8.22"
|
||||
semver = "1.0.26"
|
||||
serde = "1.0.219"
|
||||
serde_bytes = "0.11.17"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0.138"
|
||||
serde_json = "1.0.140"
|
||||
serde_json_path = "0.7.2"
|
||||
serde_repr = "0.1"
|
||||
serde_with = "3.9.0"
|
||||
serde_yaml = "0.9.25"
|
||||
sha2 = "0.10.8"
|
||||
si-scale = "0.2.3"
|
||||
sphinx-packet = "0.3.1"
|
||||
sphinx-packet = "=0.3.2"
|
||||
sqlx = "0.7.4"
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
@@ -330,10 +331,10 @@ syn = "1"
|
||||
sysinfo = "0.33.0"
|
||||
tap = "1.0.1"
|
||||
tar = "0.4.44"
|
||||
tempfile = "3.15"
|
||||
tempfile = "3.18"
|
||||
thiserror = "2.0"
|
||||
time = "0.3.37"
|
||||
tokio = "1.43"
|
||||
time = "0.3.39"
|
||||
tokio = "1.44"
|
||||
tokio-postgres = "0.7"
|
||||
tokio-stream = "0.1.17"
|
||||
tokio-test = "0.4.4"
|
||||
@@ -369,9 +370,9 @@ prometheus = { version = "0.13.0" }
|
||||
# unfortunately until https://github.com/zkcrypto/bls12_381/issues/10 is resolved, we have to rely on the fork
|
||||
# as we need to be able to serialize Gt so that we could create the lookup table for baby-step-giant-step algorithm
|
||||
# plus to make our live easier we need serde support from https://github.com/zkcrypto/bls12_381/pull/125
|
||||
bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", default-features = false, branch = "temp/experimental-serdect" }
|
||||
bls12_381 = { git = "https://github.com/jstuczyn/bls12_381", default-features = false, branch = "temp/experimental-serdect-updated" }
|
||||
group = { version = "0.13.0", default-features = false }
|
||||
ff = { version = "0.13.0", default-features = false }
|
||||
ff = { version = "0.13.1", default-features = false }
|
||||
subtle = "2.5.0"
|
||||
|
||||
# cosmwasm-related
|
||||
@@ -446,4 +447,4 @@ dbg_macro = "deny"
|
||||
exit = "deny"
|
||||
panic = "deny"
|
||||
unimplemented = "deny"
|
||||
unreachable = "deny"
|
||||
unreachable = "deny"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.49"
|
||||
version = "1.1.51"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.49"
|
||||
version = "1.1.51"
|
||||
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"
|
||||
|
||||
@@ -50,7 +50,7 @@ const DEFAULT_MINIMUM_REPLY_SURB_THRESHOLD_BUFFER: usize = 0;
|
||||
// define how much to request at once
|
||||
// clients/client-core/src/client/replies/reply_controller.rs
|
||||
const DEFAULT_MINIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 10;
|
||||
const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 30;
|
||||
const DEFAULT_MAXIMUM_REPLY_SURB_REQUEST_SIZE: u32 = 50;
|
||||
|
||||
const DEFAULT_MAXIMUM_ALLOWED_SURB_REQUEST_SIZE: u32 = 500;
|
||||
|
||||
@@ -658,6 +658,9 @@ pub struct ReplySurbs {
|
||||
/// Specifies the number of mixnet hops the packet should go through. If not specified, then
|
||||
/// the default value is used.
|
||||
pub surb_mix_hops: Option<u8>,
|
||||
|
||||
/// Specifies if we should reset all the sender tags on startup
|
||||
pub fresh_sender_tags: bool,
|
||||
}
|
||||
|
||||
impl Default for ReplySurbs {
|
||||
@@ -675,6 +678,7 @@ impl Default for ReplySurbs {
|
||||
maximum_reply_surb_age: DEFAULT_MAXIMUM_REPLY_SURB_AGE,
|
||||
maximum_reply_key_age: DEFAULT_MAXIMUM_REPLY_KEY_AGE,
|
||||
surb_mix_hops: None,
|
||||
fresh_sender_tags: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +139,8 @@ where
|
||||
let gateway_setup = GatewaySetup::New {
|
||||
specification: selection_spec,
|
||||
available_gateways,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
};
|
||||
|
||||
let init_details =
|
||||
|
||||
@@ -187,6 +187,8 @@ where
|
||||
let gateway_setup = GatewaySetup::New {
|
||||
specification: selection_spec,
|
||||
available_gateways,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
};
|
||||
|
||||
let init_details =
|
||||
|
||||
@@ -88,7 +88,7 @@ pub async fn setup_fs_reply_surb_backend<P: AsRef<Path>>(
|
||||
let db_path = db_path.as_ref();
|
||||
if db_path.exists() {
|
||||
info!("loading existing surb database");
|
||||
match fs_backend::Backend::try_load(db_path).await {
|
||||
match fs_backend::Backend::try_load(db_path, surb_config.fresh_sender_tags).await {
|
||||
Ok(backend) => Ok(backend),
|
||||
Err(err) => {
|
||||
error!("failed to setup persistent storage backend for our reply needs: {err}. We're going to create a fresh database instead. This behaviour might change in the future");
|
||||
|
||||
@@ -11,6 +11,8 @@ use nym_topology::node::RoutingNode;
|
||||
use nym_validator_client::client::IdentityKeyRef;
|
||||
use nym_validator_client::UserAgent;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
#[cfg(unix)]
|
||||
use std::os::fd::RawFd;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use tungstenite::Message;
|
||||
use url::Url;
|
||||
@@ -313,9 +315,15 @@ pub(super) async fn register_with_gateway(
|
||||
gateway_id: identity::PublicKey,
|
||||
gateway_listener: Url,
|
||||
our_identity: Arc<identity::KeyPair>,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
) -> Result<RegistrationResult, ClientCoreError> {
|
||||
let mut gateway_client =
|
||||
GatewayClient::new_init(gateway_listener, gateway_id, our_identity.clone());
|
||||
let mut gateway_client = GatewayClient::new_init(
|
||||
gateway_listener,
|
||||
gateway_id,
|
||||
our_identity.clone(),
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
);
|
||||
|
||||
gateway_client.establish_connection().await.map_err(|err| {
|
||||
log::warn!("Failed to establish connection with gateway!");
|
||||
|
||||
@@ -23,6 +23,8 @@ use nym_topology::node::RoutingNode;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use serde::Serialize;
|
||||
#[cfg(unix)]
|
||||
use std::{os::fd::RawFd, sync::Arc};
|
||||
|
||||
pub mod helpers;
|
||||
pub mod types;
|
||||
@@ -53,6 +55,7 @@ async fn setup_new_gateway<K, D>(
|
||||
details_store: &D,
|
||||
selection_specification: GatewaySelectionSpecification,
|
||||
available_gateways: Vec<RoutingNode>,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
) -> Result<InitialisationResult, ClientCoreError>
|
||||
where
|
||||
K: KeyStore,
|
||||
@@ -108,9 +111,14 @@ where
|
||||
// if we're using a 'normal' gateway setup, do register
|
||||
let our_identity = client_keys.identity_keypair();
|
||||
|
||||
let registration =
|
||||
helpers::register_with_gateway(gateway_id, gateway_listener.clone(), our_identity)
|
||||
.await?;
|
||||
let registration = helpers::register_with_gateway(
|
||||
gateway_id,
|
||||
gateway_listener.clone(),
|
||||
our_identity,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
)
|
||||
.await?;
|
||||
(
|
||||
GatewayDetails::new_remote(
|
||||
gateway_id,
|
||||
@@ -203,9 +211,19 @@ where
|
||||
GatewaySetup::New {
|
||||
specification,
|
||||
available_gateways,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
} => {
|
||||
log::debug!("GatewaySetup::New with spec: {specification:?}");
|
||||
setup_new_gateway(key_store, details_store, specification, available_gateways).await
|
||||
setup_new_gateway(
|
||||
key_store,
|
||||
details_store,
|
||||
specification,
|
||||
available_gateways,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
)
|
||||
.await
|
||||
}
|
||||
GatewaySetup::ReuseConnection {
|
||||
authenticated_ephemeral_client,
|
||||
|
||||
@@ -18,6 +18,8 @@ use nym_validator_client::client::IdentityKey;
|
||||
use nym_validator_client::nyxd::AccountId;
|
||||
use serde::Serialize;
|
||||
use std::fmt::{Debug, Display};
|
||||
#[cfg(unix)]
|
||||
use std::os::fd::RawFd;
|
||||
use std::sync::Arc;
|
||||
use time::OffsetDateTime;
|
||||
use url::Url;
|
||||
@@ -208,6 +210,10 @@ pub enum GatewaySetup {
|
||||
|
||||
// TODO: seems to be a bit inefficient to pass them by value
|
||||
available_gateways: Vec<RoutingNode>,
|
||||
|
||||
/// Callback useful for allowing initial connection to gateway
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
},
|
||||
|
||||
ReuseConnection {
|
||||
@@ -231,6 +237,8 @@ impl Debug for GatewaySetup {
|
||||
GatewaySetup::New {
|
||||
specification,
|
||||
available_gateways,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: _,
|
||||
} => f
|
||||
.debug_struct("GatewaySetup::New")
|
||||
.field("specification", specification)
|
||||
@@ -270,6 +278,8 @@ impl GatewaySetup {
|
||||
additional_data: None,
|
||||
},
|
||||
available_gateways: vec![],
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
CombinedReplyStorage, ReceivedReplySurbsMap, ReplyStorageBackend, SentReplyKeys, UsedSenderTags,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use log::{error, info, warn};
|
||||
use log::{debug, error, info, warn};
|
||||
use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -52,7 +52,10 @@ impl Backend {
|
||||
Ok(backend)
|
||||
}
|
||||
|
||||
pub async fn try_load<P: AsRef<Path>>(database_path: P) -> Result<Self, StorageError> {
|
||||
pub async fn try_load<P: AsRef<Path>>(
|
||||
database_path: P,
|
||||
fresh_sender_tags: bool,
|
||||
) -> Result<Self, StorageError> {
|
||||
let owned_path: PathBuf = database_path.as_ref().into();
|
||||
if owned_path.file_name().is_none() {
|
||||
return Err(StorageError::DatabasePathWithoutFilename {
|
||||
@@ -118,6 +121,9 @@ impl Backend {
|
||||
if days > 2 {
|
||||
info!("it's been over {days} days and {hours} hours since we last used our data store. our used sender tags are already outdated - we're going to purge them now.");
|
||||
manager.delete_all_tags().await?;
|
||||
} else if fresh_sender_tags {
|
||||
debug!("starting with fresh sender tags");
|
||||
manager.delete_all_tags().await?;
|
||||
}
|
||||
|
||||
Ok(Backend {
|
||||
|
||||
@@ -204,15 +204,15 @@ impl<C, St> GatewayClient<C, St> {
|
||||
"Attemting to establish connection to gateway at: {}",
|
||||
self.gateway_address
|
||||
);
|
||||
let (ws_stream, _) = connect_async(&self.gateway_address).await?;
|
||||
let (ws_stream, _) = connect_async(
|
||||
&self.gateway_address,
|
||||
#[cfg(unix)]
|
||||
self.connection_fd_callback.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.connection = SocketState::Available(Box::new(ws_stream));
|
||||
|
||||
#[cfg(unix)]
|
||||
if let (Some(callback), Some(fd)) = (self.connection_fd_callback.as_ref(), self.ws_fd()) {
|
||||
callback.as_ref()(fd);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1065,6 +1065,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
gateway_listener: Url,
|
||||
gateway_identity: identity::PublicKey,
|
||||
local_identity: Arc<identity::KeyPair>,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
) -> Self {
|
||||
log::trace!("Initialising gateway client");
|
||||
use futures::channel::mpsc;
|
||||
@@ -1090,7 +1091,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
stats_reporter: ClientStatsSender::new(None, task_client.clone()),
|
||||
negotiated_protocol: None,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
connection_fd_callback,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
use crate::error::GatewayClientError;
|
||||
|
||||
use nym_http_api_client::HickoryDnsResolver;
|
||||
#[cfg(unix)]
|
||||
use std::{
|
||||
os::fd::{AsRawFd, RawFd},
|
||||
sync::Arc,
|
||||
};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream};
|
||||
use tungstenite::handshake::client::Response;
|
||||
@@ -11,7 +16,10 @@ use std::net::SocketAddr;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) async fn connect_async(
|
||||
endpoint: &str,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
) -> Result<(WebSocketStream<MaybeTlsStream<TcpStream>>, Response), GatewayClientError> {
|
||||
use tokio::net::TcpSocket;
|
||||
|
||||
let resolver = HickoryDnsResolver::default();
|
||||
let uri =
|
||||
Url::parse(endpoint).map_err(|_| GatewayClientError::InvalidUrl(endpoint.to_owned()))?;
|
||||
@@ -30,21 +38,56 @@ pub(crate) async fn connect_async(
|
||||
// Do a DNS lookup for the domain using our custom DNS resolver
|
||||
resolver
|
||||
.resolve_str(domain)
|
||||
.await?
|
||||
.await
|
||||
.inspect_err(|err| tracing::error!("Resolve error {err}"))?
|
||||
.into_iter()
|
||||
.map(|a| SocketAddr::new(a, port))
|
||||
.collect()
|
||||
}
|
||||
};
|
||||
|
||||
let stream = TcpStream::connect(&sock_addrs[..]).await.map_err(|error| {
|
||||
GatewayClientError::NetworkConnectionFailed {
|
||||
address: endpoint.to_owned(),
|
||||
source: error.into(),
|
||||
let mut stream = Err(GatewayClientError::NoEndpointForConnection {
|
||||
address: endpoint.to_owned(),
|
||||
});
|
||||
for sock_addr in sock_addrs {
|
||||
tracing::info!("Trying with {sock_addr}");
|
||||
let socket = if sock_addr.is_ipv4() {
|
||||
TcpSocket::new_v4()
|
||||
} else {
|
||||
TcpSocket::new_v6()
|
||||
}
|
||||
})?;
|
||||
.map_err(|err| {
|
||||
tracing::error!("Couldn't create the socket");
|
||||
GatewayClientError::NetworkConnectionFailed {
|
||||
address: endpoint.to_owned(),
|
||||
source: err.into(),
|
||||
}
|
||||
})?;
|
||||
|
||||
tokio_tungstenite::client_async_tls(endpoint, stream)
|
||||
tracing::info!("Preparing to call callback");
|
||||
#[cfg(unix)]
|
||||
if let Some(callback) = connection_fd_callback.as_ref() {
|
||||
tracing::info!("Calling callback");
|
||||
callback.as_ref()(socket.as_raw_fd());
|
||||
}
|
||||
tracing::info!("Preparing to connect");
|
||||
|
||||
match socket.connect(sock_addr).await {
|
||||
Ok(s) => {
|
||||
stream = Ok(s);
|
||||
break;
|
||||
}
|
||||
Err(err) => {
|
||||
stream = Err(GatewayClientError::NetworkConnectionFailed {
|
||||
address: endpoint.to_owned(),
|
||||
source: err.into(),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tokio_tungstenite::client_async_tls(endpoint, stream?)
|
||||
.await
|
||||
.map_err(|error| GatewayClientError::NetworkConnectionFailed {
|
||||
address: endpoint.to_owned(),
|
||||
|
||||
@@ -43,6 +43,9 @@ pub enum GatewayClientError {
|
||||
#[error("connection failed: {address}: {source}")]
|
||||
NetworkConnectionFailed { address: String, source: WsError },
|
||||
|
||||
#[error("no socket address for endpoint: {address}")]
|
||||
NoEndpointForConnection { address: String },
|
||||
|
||||
#[error("Invalid URL: {0}")]
|
||||
InvalidUrl(String),
|
||||
|
||||
|
||||
@@ -23,11 +23,12 @@ use nym_api_requests::models::{
|
||||
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
|
||||
};
|
||||
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
|
||||
use nym_api_requests::nym_nodes::SkimmedNode;
|
||||
use nym_api_requests::nym_nodes::{NodesByAddressesResponse, SkimmedNode};
|
||||
use nym_coconut_dkg_common::types::EpochId;
|
||||
use nym_ecash_contract_common::deposit::DepositId;
|
||||
use nym_http_api_client::UserAgent;
|
||||
use nym_network_defaults::NymNetworkDetails;
|
||||
use std::net::IpAddr;
|
||||
use time::Date;
|
||||
use url::Url;
|
||||
|
||||
@@ -710,4 +711,11 @@ impl NymApiClient {
|
||||
.issued_ticketbooks_challenge(expiration_date, deposits)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn nodes_by_addresses(
|
||||
&self,
|
||||
addresses: Vec<IpAddr>,
|
||||
) -> Result<NodesByAddressesResponse, ValidatorClientError> {
|
||||
Ok(self.nym_api.nodes_by_addresses(addresses).await?)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,12 @@ impl TryFrom<ContractVKShare> for EcashApiClient {
|
||||
|
||||
let url_address = Url::parse(&share.announce_address)?;
|
||||
|
||||
// The NymApiClient constructed here uses the default (hickory DoT/DoH) resolver because
|
||||
// this EcashApiClient is used by both client and non-client applications.
|
||||
//
|
||||
// In non-client applications this resolver can cause warning logs about H2 connection
|
||||
// failure. This indicates that the long lived https connection was closed by the remote
|
||||
// peer and the resolver will have to reconnect. It should not impact actual functionality
|
||||
Ok(EcashApiClient {
|
||||
api_client: NymApiClient::new(url_address),
|
||||
verification_key: VerificationKeyAuth::try_from_bs58(&share.share)?,
|
||||
|
||||
@@ -12,10 +12,13 @@ use nym_api_requests::ecash::models::{
|
||||
};
|
||||
use nym_api_requests::ecash::VerificationKeyResponse;
|
||||
use nym_api_requests::models::{
|
||||
AnnotationResponse, ApiHealthResponse, LegacyDescribedMixNode, NodePerformanceResponse,
|
||||
NodeRefreshBody, NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse,
|
||||
AnnotationResponse, ApiHealthResponse, BinaryBuildInformationOwned, ChainStatusResponse,
|
||||
LegacyDescribedMixNode, NodePerformanceResponse, NodeRefreshBody, NymNodeDescription,
|
||||
PerformanceHistoryResponse, RewardedSetResponse,
|
||||
};
|
||||
use nym_api_requests::nym_nodes::{
|
||||
NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponse,
|
||||
};
|
||||
use nym_api_requests::nym_nodes::PaginatedCachedNodesResponse;
|
||||
use nym_api_requests::pagination::PaginatedResponse;
|
||||
pub use nym_api_requests::{
|
||||
ecash::{
|
||||
@@ -40,6 +43,7 @@ pub use nym_http_api_client::Client;
|
||||
use nym_http_api_client::{ApiClient, NO_PARAMS};
|
||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId, NymNodeDetails};
|
||||
use std::net::IpAddr;
|
||||
use time::format_description::BorrowedFormatItem;
|
||||
use time::Date;
|
||||
use tracing::instrument;
|
||||
@@ -66,6 +70,19 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn build_information(&self) -> Result<BinaryBuildInformationOwned, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
routes::API_STATUS_ROUTES,
|
||||
routes::BUILD_INFORMATION,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
@@ -1015,6 +1032,23 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn nodes_by_addresses(
|
||||
&self,
|
||||
addresses: Vec<IpAddr>,
|
||||
) -> Result<NodesByAddressesResponse, NymAPIError> {
|
||||
self.post_json(
|
||||
&[
|
||||
routes::API_VERSION,
|
||||
"unstable",
|
||||
routes::NYM_NODES_ROUTES,
|
||||
routes::nym_nodes::BY_ADDRESSES,
|
||||
],
|
||||
NO_PARAMS,
|
||||
&NodesByAddressesRequestBody { addresses },
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_network_details(&self) -> Result<NymNetworkDetailsResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
@@ -1023,6 +1057,15 @@ pub trait NymApiClientExt: ApiClient {
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_chain_status(&self) -> Result<ChainStatusResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::API_VERSION, routes::NETWORK, routes::CHAIN_STATUS],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
|
||||
@@ -43,11 +43,14 @@ pub mod nym_nodes {
|
||||
pub const NYM_NODES_BONDED: &str = "bonded";
|
||||
pub const NYM_NODES_REWARDED_SET: &str = "rewarded-set";
|
||||
pub const NYM_NODES_REFRESH_DESCRIBED: &str = "refresh-described";
|
||||
pub const BY_ADDRESSES: &str = "by-addresses";
|
||||
}
|
||||
|
||||
pub const STATUS_ROUTES: &str = "status";
|
||||
pub const API_STATUS_ROUTES: &str = "api-status";
|
||||
pub const HEALTH: &str = "health";
|
||||
pub const BUILD_INFORMATION: &str = "build-information";
|
||||
|
||||
pub const MIXNODE: &str = "mixnode";
|
||||
pub const GATEWAY: &str = "gateway";
|
||||
pub const NYM_NODES: &str = "nym-nodes";
|
||||
@@ -69,4 +72,5 @@ pub const SUBMIT_NODE: &str = "submit-node-monitoring-results";
|
||||
pub const SERVICE_PROVIDERS: &str = "services";
|
||||
|
||||
pub const DETAILS: &str = "details";
|
||||
pub const CHAIN_STATUS: &str = "chain-status";
|
||||
pub const NETWORK: &str = "network";
|
||||
|
||||
@@ -62,6 +62,7 @@ pub use cw3;
|
||||
pub use cw4;
|
||||
pub use cw_controllers;
|
||||
pub use fee::{gas_price::GasPrice, GasAdjustable, GasAdjustment};
|
||||
pub use prost::Name;
|
||||
pub use tendermint_rpc::endpoint::block::Response as BlockResponse;
|
||||
pub use tendermint_rpc::{
|
||||
endpoint::{tx::Response as TxResponse, validators::Response as ValidatorResponse},
|
||||
|
||||
@@ -25,15 +25,6 @@ pub fn in6addr_any_init() -> IpAddr {
|
||||
IpAddr::V6(Ipv6Addr::UNSPECIFIED)
|
||||
}
|
||||
|
||||
/// Helper for providing binding warnings if node tries to bind to any of those
|
||||
pub const SPECIAL_ADDRESSES: &[IpAddr] = &[
|
||||
IpAddr::V4(Ipv4Addr::LOCALHOST),
|
||||
IpAddr::V4(Ipv4Addr::UNSPECIFIED),
|
||||
IpAddr::V4(Ipv4Addr::BROADCAST),
|
||||
IpAddr::V6(Ipv6Addr::LOCALHOST),
|
||||
IpAddr::V6(Ipv6Addr::UNSPECIFIED),
|
||||
];
|
||||
|
||||
// TODO: is it really part of 'Config'?
|
||||
pub trait OptionalSet {
|
||||
/// If the value is available (i.e. `Some`), the provided closure is applied.
|
||||
|
||||
@@ -21,7 +21,7 @@ lazy_static = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
rand_core = { workspace = true }
|
||||
sha2 = "0.9"
|
||||
sha2 = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
+96
-2
@@ -54,12 +54,12 @@ pub(crate) fn hash_to_scalar<M: AsRef<[u8]>>(msg: M, domain: &[u8]) -> Scalar {
|
||||
pub(crate) fn hash_to_scalars<M: AsRef<[u8]>>(msg: M, domain: &[u8], n: usize) -> Vec<Scalar> {
|
||||
let mut output = vec![Scalar::zero(); n];
|
||||
|
||||
Scalar::hash_to_field::<ExpandMsgXmd<Sha256>>(msg.as_ref(), domain, &mut output);
|
||||
Scalar::hash_to_field::<ExpandMsgXmd<Sha256>, _>([msg], domain, &mut output);
|
||||
output
|
||||
}
|
||||
|
||||
pub(crate) fn hash_g2<M: AsRef<[u8]>>(msg: M, domain: &[u8]) -> G2Projective {
|
||||
<G2Projective as HashToCurve<ExpandMsgXmd<Sha256>>>::hash_to_curve(msg, domain)
|
||||
<G2Projective as HashToCurve<ExpandMsgXmd<Sha256>>>::hash_to_curve([msg], domain)
|
||||
}
|
||||
|
||||
pub(crate) fn combine_scalar_chunks(chunks: &[Scalar]) -> Scalar {
|
||||
@@ -112,3 +112,97 @@ pub(crate) fn deserialize_g2(b: &[u8]) -> Option<G2Projective> {
|
||||
G2Projective::from_bytes(&encoding).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use bls12_381::G2Affine;
|
||||
|
||||
#[test]
|
||||
fn test_hash_to_scalar() {
|
||||
let msg1 = "foo";
|
||||
let expected1 = Scalar::from_bytes(&[
|
||||
253, 57, 224, 227, 175, 195, 226, 82, 46, 175, 33, 126, 171, 239, 255, 92, 108, 168, 6,
|
||||
79, 90, 11, 235, 236, 221, 10, 85, 133, 42, 81, 95, 30,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let msg2 = "bar";
|
||||
let expected2 = Scalar::from_bytes(&[
|
||||
48, 83, 69, 52, 42, 18, 135, 244, 211, 190, 160, 196, 118, 154, 24, 126, 0, 125, 72,
|
||||
201, 170, 225, 123, 201, 52, 120, 171, 132, 235, 182, 20, 26,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let msg3 = [
|
||||
33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123,
|
||||
250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27,
|
||||
115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101,
|
||||
60, 42, 92, 128, 131, 161, 43,
|
||||
];
|
||||
let expected3 = Scalar::from_bytes(&[
|
||||
128, 189, 8, 43, 186, 55, 52, 61, 171, 196, 159, 177, 162, 100, 27, 143, 85, 83, 218,
|
||||
171, 91, 220, 155, 25, 7, 38, 2, 36, 4, 93, 136, 4,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
hash_to_scalar(msg1, b"NYMECASH-V01-CS02-with-expander-SHA256"),
|
||||
expected1
|
||||
);
|
||||
assert_eq!(
|
||||
hash_to_scalar(msg2, b"NYMECASH-V01-CS02-with-expander-SHA256"),
|
||||
expected2
|
||||
);
|
||||
assert_eq!(
|
||||
hash_to_scalar(msg3, b"NYMECASH-V01-CS02-with-expander-SHA256"),
|
||||
expected3
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_g2() {
|
||||
let msg1 = "foo";
|
||||
let expected1 = G2Affine::from_compressed(&[
|
||||
175, 187, 62, 7, 29, 17, 42, 93, 28, 93, 234, 253, 101, 166, 158, 187, 153, 82, 93, 18,
|
||||
11, 233, 36, 107, 51, 117, 30, 127, 32, 254, 210, 77, 133, 12, 253, 255, 84, 128, 36,
|
||||
214, 234, 103, 50, 21, 26, 78, 112, 49, 20, 69, 19, 109, 7, 78, 33, 227, 196, 180, 168,
|
||||
219, 73, 251, 192, 221, 41, 138, 160, 131, 191, 186, 156, 117, 179, 179, 191, 235, 171,
|
||||
26, 219, 148, 170, 179, 11, 38, 137, 14, 95, 115, 171, 186, 163, 82, 158, 6, 239, 88,
|
||||
])
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
let msg2 = "bar";
|
||||
let expected2 = G2Affine::from_compressed(&[
|
||||
183, 25, 90, 187, 34, 184, 30, 182, 215, 242, 158, 83, 116, 34, 210, 96, 188, 79, 83,
|
||||
255, 100, 122, 90, 188, 196, 93, 164, 253, 20, 106, 205, 33, 48, 140, 60, 149, 66, 246,
|
||||
121, 244, 146, 66, 170, 60, 113, 95, 102, 237, 25, 231, 8, 42, 121, 124, 180, 140, 34,
|
||||
104, 173, 251, 89, 189, 28, 196, 49, 66, 101, 38, 68, 44, 40, 235, 21, 35, 204, 123,
|
||||
218, 238, 216, 92, 134, 217, 212, 246, 176, 77, 187, 0, 245, 134, 132, 73, 31, 44, 137,
|
||||
197,
|
||||
])
|
||||
.unwrap()
|
||||
.into();
|
||||
let msg3 = [
|
||||
33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123,
|
||||
250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27,
|
||||
115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101,
|
||||
60, 42, 92, 128, 131, 161, 43,
|
||||
];
|
||||
let expected3 = G2Affine::from_compressed(&[
|
||||
151, 185, 8, 123, 223, 150, 192, 192, 115, 10, 3, 129, 49, 179, 31, 108, 0, 17, 46,
|
||||
231, 184, 164, 247, 228, 22, 142, 87, 70, 120, 111, 154, 15, 245, 110, 32, 84, 53, 117,
|
||||
239, 93, 89, 119, 32, 17, 39, 250, 198, 137, 6, 95, 137, 202, 54, 244, 238, 190, 11,
|
||||
217, 237, 95, 72, 59, 140, 56, 3, 42, 61, 195, 192, 101, 46, 204, 207, 75, 70, 176,
|
||||
207, 48, 24, 195, 248, 234, 178, 168, 54, 109, 19, 189, 51, 52, 120, 69, 248, 226, 102,
|
||||
91,
|
||||
])
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
assert_eq!(hash_g2(msg1, b"DUMMY_TEST_DOMAIN"), expected1);
|
||||
assert_eq!(hash_g2(msg2, b"DUMMY_TEST_DOMAIN"), expected2);
|
||||
assert_eq!(hash_g2(msg3, b"DUMMY_TEST_DOMAIN"), expected3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ use nym_sphinx::params::packet_sizes::PacketSize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::string::FromUtf8Error;
|
||||
use thiserror::Error;
|
||||
use time::OffsetDateTime;
|
||||
|
||||
// specific errors (that should not be nested!!) for clients to match on
|
||||
#[derive(Debug, Copy, Clone, Error, Serialize, Deserialize)]
|
||||
@@ -112,15 +113,15 @@ pub enum AuthenticationFailure {
|
||||
#[error("failed to verify request signature")]
|
||||
InvalidSignature(#[from] SignatureError),
|
||||
|
||||
#[error("provided request timestamp is in the future")]
|
||||
RequestTimestampInFuture,
|
||||
|
||||
#[error("the client is not registered")]
|
||||
NotRegistered,
|
||||
|
||||
#[error("the provided request is too stale to process")]
|
||||
StaleRequest,
|
||||
#[error("the provided request timestamp is excessively skewed. got {received} whilst the server time is {server}")]
|
||||
ExcessiveTimestampSkew {
|
||||
received: OffsetDateTime,
|
||||
server: OffsetDateTime,
|
||||
},
|
||||
|
||||
#[error("the provided request timestamp is smaller or equal to a one previously used")]
|
||||
#[error("the provided request timestamp is smaller or equal to one previously used")]
|
||||
RequestReuse,
|
||||
}
|
||||
|
||||
@@ -38,13 +38,22 @@ impl AuthenticateRequest {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn verify_timestamp(&self, max_request_age: Duration) -> Result<(), AuthenticationFailure> {
|
||||
pub fn verify_timestamp(
|
||||
&self,
|
||||
max_request_timestamp_skew: Duration,
|
||||
) -> Result<(), AuthenticationFailure> {
|
||||
let now = OffsetDateTime::now_utc();
|
||||
if self.content.request_timestamp() + max_request_age < now {
|
||||
return Err(AuthenticationFailure::StaleRequest);
|
||||
if self.content.request_timestamp() < now - max_request_timestamp_skew {
|
||||
return Err(AuthenticationFailure::ExcessiveTimestampSkew {
|
||||
received: self.content.request_timestamp(),
|
||||
server: now,
|
||||
});
|
||||
}
|
||||
if self.content.request_timestamp() > now {
|
||||
return Err(AuthenticationFailure::RequestTimestampInFuture);
|
||||
if self.content.request_timestamp() - max_request_timestamp_skew > now {
|
||||
return Err(AuthenticationFailure::ExcessiveTimestampSkew {
|
||||
received: self.content.request_timestamp(),
|
||||
server: now,
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -21,6 +21,12 @@ serde_json = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
|
||||
# used for decoding text responses (they were already implicitly included)
|
||||
bytes = { workspace = true }
|
||||
encoding_rs = { workspace = true }
|
||||
mime = { workspace = true }
|
||||
|
||||
|
||||
nym-bin-common = { path = "../bin-common" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
|
||||
@@ -32,4 +38,4 @@ workspace = true
|
||||
features = ["tokio"]
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features=["rt", "macros"] }
|
||||
tokio = { workspace = true, features = ["rt", "macros"] }
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
//! DNS resolver configuration for internal lookups.
|
||||
//!
|
||||
//! The resolver itself is the set combination of the google, cloudflare, and quad9 endpoints
|
||||
@@ -9,6 +12,19 @@
|
||||
//!
|
||||
//! Requires the `dns-over-https-rustls`, `webpki-roots` feature for the
|
||||
//! `hickory-resolver` crate
|
||||
//!
|
||||
//!
|
||||
//! Note: The hickory DoH resolver can cause warning logs about H2 connection failure. This
|
||||
//! indicates that the long lived https connection was closed by the remote peer and the resolver
|
||||
//! will have to reconnect. It should not impact actual functionality.
|
||||
//!
|
||||
//! code ref: https://github.com/hickory-dns/hickory-dns/blob/06a8b1ce9bd9322d8e6accf857d30257e1274427/crates/proto/src/h2/h2_client_stream.rs#L534
|
||||
//!
|
||||
//! example log:
|
||||
//!
|
||||
//! ```txt
|
||||
//! WARN /home/ubuntu/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/hickory-proto-0.24.3/src/h2/h2_client_stream.rs:493: h2 connection failed: unexpected end of file
|
||||
//! ```
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use crate::ClientBuilder;
|
||||
@@ -33,6 +49,13 @@ impl ClientBuilder {
|
||||
/// Override the DNS resolver implementation used by the underlying http client.
|
||||
pub fn dns_resolver<R: Resolve + 'static>(mut self, resolver: Arc<R>) -> Self {
|
||||
self.reqwest_client_builder = self.reqwest_client_builder.dns_resolver(resolver);
|
||||
self.use_secure_dns = false;
|
||||
self
|
||||
}
|
||||
|
||||
/// Override the DNS resolver implementation used by the underlying http client.
|
||||
pub fn no_hickory_dns(mut self) -> Self {
|
||||
self.use_secure_dns = false;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,13 +147,13 @@ use thiserror::Error;
|
||||
use tracing::{instrument, warn};
|
||||
use url::Url;
|
||||
|
||||
use http::HeaderMap;
|
||||
pub use reqwest::IntoUrl;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::net::SocketAddr;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use reqwest::IntoUrl;
|
||||
|
||||
mod user_agent;
|
||||
pub use user_agent::UserAgent;
|
||||
|
||||
@@ -228,6 +228,8 @@ pub struct ClientBuilder {
|
||||
timeout: Option<Duration>,
|
||||
custom_user_agent: bool,
|
||||
reqwest_client_builder: reqwest::ClientBuilder,
|
||||
#[allow(dead_code)] // not dead code, just unused in wasm
|
||||
use_secure_dns: bool,
|
||||
}
|
||||
|
||||
impl ClientBuilder {
|
||||
@@ -239,37 +241,46 @@ impl ClientBuilder {
|
||||
U: IntoUrl,
|
||||
E: Display,
|
||||
{
|
||||
// a naive check: if the provided URL does not start with http(s), add that scheme
|
||||
let str_url = url.as_str();
|
||||
|
||||
// a naive check: if the provided URL does not start with http(s), add that scheme
|
||||
if !str_url.starts_with("http") {
|
||||
let alt = format!("http://{str_url}");
|
||||
warn!("the provided url ('{str_url}') does not contain scheme information. Changing it to '{alt}' ...");
|
||||
// TODO: or should we maybe default to https?
|
||||
Self::new(alt)
|
||||
} else {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let reqwest_client_builder = reqwest::ClientBuilder::new();
|
||||
Ok(Self::new_with_url(url.into_url()?))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let reqwest_client_builder = {
|
||||
let r = reqwest::ClientBuilder::new()
|
||||
.dns_resolver(Arc::new(HickoryDnsResolver::default()));
|
||||
/// Constructs a new http `ClientBuilder` from a valid url.
|
||||
pub fn new_with_url(url: Url) -> Self {
|
||||
if !url.scheme().starts_with("http") {
|
||||
warn!("the provided url ('{url}') does not use HTTP / HTTPS scheme");
|
||||
}
|
||||
|
||||
// Note this is extra as the `gzip` feature for `reqwest` crate should be enabled which
|
||||
// `"Enable[s] auto gzip decompression by checking the Content-Encoding response header."`
|
||||
//
|
||||
// I am going to leave it here anyways so that gzip decompression is attempted even if
|
||||
// that feature is removed.
|
||||
r.gzip(true)
|
||||
};
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let reqwest_client_builder = reqwest::ClientBuilder::new();
|
||||
|
||||
Ok(ClientBuilder {
|
||||
url: url.into_url()?,
|
||||
timeout: None,
|
||||
custom_user_agent: false,
|
||||
reqwest_client_builder,
|
||||
})
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let reqwest_client_builder = {
|
||||
let r = reqwest::ClientBuilder::new();
|
||||
|
||||
// Note this is extra as the `gzip` feature for `reqwest` crate should be enabled which
|
||||
// `"Enable[s] auto gzip decompression by checking the Content-Encoding response header."`
|
||||
//
|
||||
// I am going to leave it here anyways so that gzip decompression is attempted even if
|
||||
// that feature is removed.
|
||||
r.gzip(true)
|
||||
};
|
||||
|
||||
ClientBuilder {
|
||||
url,
|
||||
timeout: None,
|
||||
custom_user_agent: false,
|
||||
reqwest_client_builder,
|
||||
use_secure_dns: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,10 +336,18 @@ impl ClientBuilder {
|
||||
let mut builder = self
|
||||
.reqwest_client_builder
|
||||
.timeout(self.timeout.unwrap_or(DEFAULT_TIMEOUT));
|
||||
|
||||
// if no custom user agent was set, use a default
|
||||
if !self.custom_user_agent {
|
||||
builder =
|
||||
builder.user_agent(format!("nym-http-api-client/{}", env!("CARGO_PKG_VERSION")))
|
||||
}
|
||||
|
||||
// unless explicitly disabled use the DoT/DoH enabled resolver
|
||||
if self.use_secure_dns {
|
||||
builder = builder.dns_resolver(Arc::new(HickoryDnsResolver::default()));
|
||||
}
|
||||
|
||||
builder.build()?
|
||||
};
|
||||
|
||||
@@ -355,6 +374,9 @@ pub struct Client {
|
||||
impl Client {
|
||||
/// Create a new http `Client`
|
||||
// no timeout until https://github.com/seanmonstar/reqwest/issues/1135 is fixed
|
||||
//
|
||||
// In order to prevent interference in API requests at the DNS phase we default to a resolver
|
||||
// that uses DoT and DoH.
|
||||
pub fn new(base_url: Url, timeout: Option<Duration>) -> Self {
|
||||
Self::new_url::<_, String>(base_url, timeout).expect(
|
||||
"we provided valid url and we were unwrapping previous construction errors anyway",
|
||||
@@ -855,6 +877,26 @@ fn sanitize_url<K: AsRef<str>, V: AsRef<str>>(
|
||||
url
|
||||
}
|
||||
|
||||
fn decode_as_text(bytes: &bytes::Bytes, headers: HeaderMap) -> String {
|
||||
use encoding_rs::{Encoding, UTF_8};
|
||||
use mime::Mime;
|
||||
|
||||
let content_type = headers
|
||||
.get(http::header::CONTENT_TYPE)
|
||||
.and_then(|value| value.to_str().ok())
|
||||
.and_then(|value| value.parse::<Mime>().ok());
|
||||
|
||||
let encoding_name = content_type
|
||||
.as_ref()
|
||||
.and_then(|mime| mime.get_param("charset").map(|charset| charset.as_str()))
|
||||
.unwrap_or("utf-8");
|
||||
|
||||
let encoding = Encoding::for_label(encoding_name.as_bytes()).unwrap_or(UTF_8);
|
||||
|
||||
let (text, _, _) = encoding.decode(bytes);
|
||||
text.into_owned()
|
||||
}
|
||||
|
||||
/// Attempt to parse a json object from an HTTP response
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub async fn parse_response<T, E>(res: Response, allow_empty: bool) -> Result<T, HttpClientError<E>>
|
||||
@@ -870,6 +912,8 @@ where
|
||||
return Err(HttpClientError::EmptyResponse { status });
|
||||
}
|
||||
}
|
||||
let headers = res.headers().clone();
|
||||
tracing::trace!("headers: {:?}", headers);
|
||||
|
||||
if res.status().is_success() {
|
||||
// internally reqwest is first retrieving bytes and then performing parsing via serde_json
|
||||
@@ -878,10 +922,10 @@ where
|
||||
match serde_json::from_slice(&full) {
|
||||
Ok(data) => Ok(data),
|
||||
Err(err) => {
|
||||
let text = String::from_utf8_lossy(&full);
|
||||
let content = decode_as_text(&full, headers);
|
||||
Err(HttpClientError::ResponseDecodeFailure {
|
||||
source: err,
|
||||
content: text.into_owned(),
|
||||
content,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,10 @@ bls12_381 = { workspace = true, features = ["alloc", "pairings", "experimental",
|
||||
bincode.workspace = true
|
||||
cfg-if.workspace = true
|
||||
itertools = { workspace = true }
|
||||
digest = "0.9"
|
||||
digest = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
sha2 = "0.9"
|
||||
sha2 = { workspace = true }
|
||||
bs58 = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
rayon = { workspace = true, optional = true }
|
||||
|
||||
@@ -113,17 +113,13 @@ const G1_HASH_DOMAIN: &[u8] = b"NYMECASH-V01-CS02-with-BLS12381G1_XMD:SHA-256_SS
|
||||
const SCALAR_HASH_DOMAIN: &[u8] = b"NYMECASH-V01-CS02-with-expander-SHA256";
|
||||
|
||||
pub fn hash_g1<M: AsRef<[u8]>>(msg: M) -> G1Projective {
|
||||
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve(msg, G1_HASH_DOMAIN)
|
||||
<G1Projective as HashToCurve<ExpandMsgXmd<sha2::Sha256>>>::hash_to_curve([msg], G1_HASH_DOMAIN)
|
||||
}
|
||||
|
||||
pub fn hash_to_scalar<M: AsRef<[u8]>>(msg: M) -> Scalar {
|
||||
let mut output = vec![Scalar::zero()];
|
||||
|
||||
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>>(
|
||||
msg.as_ref(),
|
||||
SCALAR_HASH_DOMAIN,
|
||||
&mut output,
|
||||
);
|
||||
Scalar::hash_to_field::<ExpandMsgXmd<sha2::Sha256>, _>([msg], SCALAR_HASH_DOMAIN, &mut output);
|
||||
output[0]
|
||||
}
|
||||
|
||||
@@ -401,4 +397,75 @@ mod tests {
|
||||
assert_eq!(hash_to_scalar(msg2), hash_to_scalar(msg2));
|
||||
assert_ne!(hash_to_scalar(msg1), hash_to_scalar(msg2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_to_scalar() {
|
||||
let msg1 = "foo";
|
||||
let expected1 = Scalar::from_bytes(&[
|
||||
253, 57, 224, 227, 175, 195, 226, 82, 46, 175, 33, 126, 171, 239, 255, 92, 108, 168, 6,
|
||||
79, 90, 11, 235, 236, 221, 10, 85, 133, 42, 81, 95, 30,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let msg2 = "bar";
|
||||
let expected2 = Scalar::from_bytes(&[
|
||||
48, 83, 69, 52, 42, 18, 135, 244, 211, 190, 160, 196, 118, 154, 24, 126, 0, 125, 72,
|
||||
201, 170, 225, 123, 201, 52, 120, 171, 132, 235, 182, 20, 26,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
let msg3 = [
|
||||
33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123,
|
||||
250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27,
|
||||
115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101,
|
||||
60, 42, 92, 128, 131, 161, 43,
|
||||
];
|
||||
let expected3 = Scalar::from_bytes(&[
|
||||
128, 189, 8, 43, 186, 55, 52, 61, 171, 196, 159, 177, 162, 100, 27, 143, 85, 83, 218,
|
||||
171, 91, 220, 155, 25, 7, 38, 2, 36, 4, 93, 136, 4,
|
||||
])
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(hash_to_scalar(msg1), expected1);
|
||||
assert_eq!(hash_to_scalar(msg2), expected2);
|
||||
assert_eq!(hash_to_scalar(msg3), expected3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hash_to_g1() {
|
||||
let msg1 = "foo";
|
||||
let expected1 = G1Affine::from_compressed(&[
|
||||
161, 109, 186, 0, 192, 221, 83, 87, 71, 31, 120, 201, 185, 35, 62, 239, 46, 120, 117,
|
||||
150, 191, 227, 128, 161, 78, 201, 207, 167, 86, 181, 229, 115, 2, 6, 178, 16, 251, 118,
|
||||
219, 115, 184, 96, 2, 10, 31, 63, 150, 70,
|
||||
])
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
let msg2 = "bar";
|
||||
let expected2 = G1Affine::from_compressed(&[
|
||||
135, 102, 204, 42, 221, 49, 209, 192, 250, 87, 59, 255, 197, 93, 37, 113, 38, 2, 154,
|
||||
233, 68, 234, 206, 182, 121, 212, 166, 210, 74, 155, 190, 33, 203, 237, 176, 60, 249,
|
||||
241, 53, 170, 18, 168, 49, 35, 1, 151, 205, 174,
|
||||
])
|
||||
.unwrap()
|
||||
.into();
|
||||
let msg3 = [
|
||||
33, 135, 76, 234, 71, 35, 247, 216, 39, 242, 42, 88, 152, 29, 74, 135, 9, 29, 216, 123,
|
||||
250, 87, 108, 29, 245, 126, 109, 102, 84, 71, 158, 224, 145, 243, 49, 121, 244, 27,
|
||||
115, 121, 25, 66, 216, 67, 97, 101, 140, 160, 77, 239, 114, 215, 152, 48, 15, 231, 101,
|
||||
60, 42, 92, 128, 131, 161, 43,
|
||||
];
|
||||
let expected3 = G1Affine::from_compressed(&[
|
||||
184, 200, 211, 115, 47, 45, 39, 185, 105, 9, 222, 247, 132, 241, 121, 130, 238, 224,
|
||||
155, 109, 105, 201, 137, 154, 132, 149, 214, 233, 136, 69, 77, 132, 174, 30, 46, 123,
|
||||
20, 92, 219, 18, 45, 29, 208, 127, 158, 145, 130, 41,
|
||||
])
|
||||
.unwrap()
|
||||
.into();
|
||||
|
||||
assert_eq!(hash_g1(msg1), expected1);
|
||||
assert_eq!(hash_g1(msg2), expected2);
|
||||
assert_eq!(hash_g1(msg3), expected3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,6 +161,12 @@ impl From<NymNodeRoutingAddress> for SocketAddr {
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<SocketAddr> for NymNodeRoutingAddress {
|
||||
fn as_ref(&self) -> &SocketAddr {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<NodeAddressBytes> for NymNodeRoutingAddress {
|
||||
type Error = NymNodeRoutingAddressError;
|
||||
|
||||
|
||||
@@ -30,7 +30,6 @@ pub use sphinx_packet::{
|
||||
route::{Destination, DestinationAddressBytes, Node, NodeAddressBytes, SURBIdentifier},
|
||||
surb::{SURBMaterial, SURB},
|
||||
version::Version,
|
||||
version::UPDATED_LEGACY_VERSION,
|
||||
Error as SphinxError, ProcessedPacket, ProcessedPacketData,
|
||||
};
|
||||
|
||||
@@ -91,12 +90,8 @@ impl NymPacket {
|
||||
destination: &Destination,
|
||||
delays: &[Delay],
|
||||
) -> Result<NymPacket, NymPacketError> {
|
||||
// FIXME:
|
||||
// for now explicitly use the legacy version until sufficient number of nodes
|
||||
// understand both variants
|
||||
Ok(NymPacket::Sphinx(
|
||||
SphinxPacketBuilder::new()
|
||||
.with_version(UPDATED_LEGACY_VERSION)
|
||||
.with_payload_size(size)
|
||||
.build_packet(message, route, destination, delays)?,
|
||||
))
|
||||
|
||||
@@ -182,9 +182,11 @@ impl BlockProcessor {
|
||||
// the ones concerned with individual messages
|
||||
for (index, msg) in block_tx.tx.body.messages.iter().enumerate() {
|
||||
for msg_module in &mut self.msg_modules {
|
||||
msg_module
|
||||
.handle_msg(index, msg, &block_tx, &mut tx)
|
||||
.await?
|
||||
if msg.type_url == msg_module.type_url() {
|
||||
msg_module
|
||||
.handle_msg(index, msg, &block_tx, &mut tx)
|
||||
.await?
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,15 @@ pub enum ScraperError {
|
||||
source: cosmrs::ErrorReport,
|
||||
},
|
||||
|
||||
#[error("could not parse msg in tx {hash} at index {index} into {type_url}: {source}")]
|
||||
MsgParseFailure {
|
||||
hash: Hash,
|
||||
index: usize,
|
||||
type_url: String,
|
||||
#[source]
|
||||
source: cosmrs::ErrorReport,
|
||||
},
|
||||
|
||||
#[error("received an invalid chain subscription event of kind {kind} while we were waiting for new block data (query: '{query}')")]
|
||||
InvalidSubscriptionEvent { query: String, kind: String },
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ use cosmrs::Any;
|
||||
|
||||
#[async_trait]
|
||||
pub trait MsgModule {
|
||||
fn type_url(&self) -> String;
|
||||
|
||||
async fn handle_msg(
|
||||
&mut self,
|
||||
index: usize,
|
||||
|
||||
@@ -9,3 +9,4 @@ repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
pem = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
@@ -6,6 +6,7 @@ use pem::Pem;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use tracing::debug;
|
||||
|
||||
pub mod traits;
|
||||
|
||||
@@ -46,6 +47,10 @@ where
|
||||
T: PemStorableKey,
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
debug!(
|
||||
"attempting to load key with the following pem type: {}",
|
||||
T::pem_type()
|
||||
);
|
||||
let key_pem = read_pem_file(path)?;
|
||||
|
||||
if T::pem_type() != key_pem.tag {
|
||||
|
||||
@@ -103,4 +103,8 @@ impl LaneQueueLengthsInner {
|
||||
{
|
||||
self.map.entry(*lane).and_modify(f);
|
||||
}
|
||||
|
||||
pub fn total(&self) -> usize {
|
||||
self.map.values().sum()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,6 +254,15 @@ impl NymTopology {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_additional_nodes<N>(mut self, nodes: impl Iterator<Item = N>) -> Self
|
||||
where
|
||||
N: TryInto<RoutingNode>,
|
||||
<N as TryInto<RoutingNode>>::Error: Display,
|
||||
{
|
||||
self.add_additional_nodes(nodes);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn has_node_details(&self, node_id: NodeId) -> bool {
|
||||
self.node_details.contains_key(&node_id)
|
||||
}
|
||||
|
||||
@@ -494,6 +494,9 @@ pub struct ReplySurbsWasm {
|
||||
/// Defines how many mix nodes the reply surb should go through.
|
||||
/// If not set, the default value is going to be used.
|
||||
pub surb_mix_hops: Option<u8>,
|
||||
|
||||
/// Specifies if we should reset all the sender tags on startup
|
||||
pub fresh_sender_tags: bool,
|
||||
}
|
||||
|
||||
impl Default for ReplySurbsWasm {
|
||||
@@ -525,6 +528,7 @@ impl From<ReplySurbsWasm> for ConfigReplySurbs {
|
||||
reply_surbs.maximum_reply_key_age_ms as u64,
|
||||
),
|
||||
surb_mix_hops: reply_surbs.surb_mix_hops,
|
||||
fresh_sender_tags: reply_surbs.fresh_sender_tags,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -548,6 +552,7 @@ impl From<ConfigReplySurbs> for ReplySurbsWasm {
|
||||
maximum_reply_surb_age_ms: reply_surbs.maximum_reply_surb_age.as_millis() as u32,
|
||||
maximum_reply_key_age_ms: reply_surbs.maximum_reply_key_age.as_millis() as u32,
|
||||
surb_mix_hops: reply_surbs.surb_mix_hops,
|
||||
fresh_sender_tags: reply_surbs.fresh_sender_tags,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,6 +378,9 @@ pub struct ReplySurbsWasmOverride {
|
||||
|
||||
#[tsify(optional)]
|
||||
pub surb_mix_hops: Option<u8>,
|
||||
|
||||
/// Specifies if we should reset all the sender tags on startup
|
||||
pub fresh_sender_tags: bool,
|
||||
}
|
||||
|
||||
impl From<ReplySurbsWasmOverride> for ReplySurbsWasm {
|
||||
@@ -416,6 +419,7 @@ impl From<ReplySurbsWasmOverride> for ReplySurbsWasm {
|
||||
.maximum_reply_key_age_ms
|
||||
.unwrap_or(def.maximum_reply_key_age_ms),
|
||||
surb_mix_hops: value.surb_mix_hops,
|
||||
fresh_sender_tags: value.fresh_sender_tags,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+55
-17
@@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
@@ -1185,6 +1185,7 @@ name = "nym-pemstore"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"pem",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1251,6 +1252,12 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
|
||||
|
||||
[[package]]
|
||||
name = "pkcs8"
|
||||
version = "0.9.0"
|
||||
@@ -1470,9 +1477,9 @@ checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
|
||||
|
||||
[[package]]
|
||||
name = "schemars"
|
||||
version = "0.8.21"
|
||||
version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92"
|
||||
checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
|
||||
dependencies = [
|
||||
"dyn-clone",
|
||||
"schemars_derive",
|
||||
@@ -1482,9 +1489,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "schemars_derive"
|
||||
version = "0.8.21"
|
||||
version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e"
|
||||
checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1522,18 +1529,18 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.25"
|
||||
version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
|
||||
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.217"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@@ -1558,9 +1565,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.217"
|
||||
version = "1.0.219"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1777,9 +1784,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.37"
|
||||
version = "0.3.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21"
|
||||
checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
@@ -1794,15 +1801,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "time-core"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||
checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.19"
|
||||
version = "0.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de"
|
||||
checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
@@ -1840,6 +1847,37 @@ dependencies = [
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.98",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.18.0"
|
||||
|
||||
@@ -7,4 +7,6 @@ package-lock.json
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
.env
|
||||
.env
|
||||
|
||||
scratch.md
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
- Run synchronization against [pool.ntp.org](https://www.ntppool.org/en/):
|
||||
```bash
|
||||
ntpdate -q pool.ntp.org
|
||||
```
|
||||
- Enable `ntp` service, start and review the status:
|
||||
```bash
|
||||
systemctl enable --now ntp
|
||||
service ntp start
|
||||
service ntp status
|
||||
```
|
||||
+1
-1
@@ -1 +1 @@
|
||||
807_251_217
|
||||
808_623_916
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
0.64%
|
||||
0.68%
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
44.332
|
||||
42.157
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
1_025_628
|
||||
1_028_488
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
403_625_608
|
||||
404_311_958
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
| **Item** | **Description** | **Amount in NYM** |
|
||||
|:-------------------|:------------------------------------------------------|--------------------:|
|
||||
| Total Supply | Maximum amount of NYM token in existence | 1_000_000_000 |
|
||||
| Mixmining Reserve | Tokens releasing for operators rewards | 192_748_782 |
|
||||
| Mixmining Reserve | Tokens releasing for operators rewards | 191_376_083 |
|
||||
| Vesting Tokens | Tokens locked outside of cicrulation for future claim | 0 |
|
||||
| Circulating Supply | Amount of unlocked tokens | 807_251_217 |
|
||||
| Stake Saturation | Optimal size of node self-bond + delegation | 1_025_628 |
|
||||
| Circulating Supply | Amount of unlocked tokens | 808_623_916 |
|
||||
| Stake Saturation | Optimal size of node self-bond + delegation | 1_028_488 |
|
||||
|
||||
@@ -1 +1 @@
|
||||
Monday, February 3rd 2025, 13:47:19 UTC
|
||||
Tuesday, March 11th 2025, 11:04:18 UTC
|
||||
|
||||
@@ -16,8 +16,10 @@ Options:
|
||||
If this is a brand new nym-node, specify whether it should only be initialised without actually running the subprocesses [env: NYMNODE_INIT_ONLY=]
|
||||
--local
|
||||
Flag specifying this node will be running in a local setting [env: NYMNODE_LOCAL=]
|
||||
--mode <MODE>
|
||||
Specifies the current mode of this nym-node [env: NYMNODE_MODE=] [possible values: mixnode, entry-gateway, exit-gateway]
|
||||
--mode [<MODE>...]
|
||||
Specifies the current mode(s) of this nym-node [env: NYMNODE_MODE=] [possible values: mixnode, entry-gateway, exit-gateway, exit-providers-only]
|
||||
--modes <MODES>
|
||||
Specifies the current mode(s) of this nym-node as a single flag [env: NYMNODE_MODES=] [possible values: mixnode, entry-gateway, exit-gateway, exit-providers-only]
|
||||
-w, --write-changes
|
||||
If this node has been initialised before, specify whether to write any new changes to the config file [env: NYMNODE_WRITE_CONFIG_CHANGES=]
|
||||
--bonding-information-output <BONDING_INFORMATION_OUTPUT>
|
||||
@@ -31,7 +33,7 @@ Options:
|
||||
--location <LOCATION>
|
||||
Optional **physical** location of this node's server. Either full country name (e.g. 'Poland'), two-letter alpha2 (e.g. 'PL'), three-letter alpha3 (e.g. 'POL') or three-digit numeric-3 (e.g. '616') can be provided [env: NYMNODE_LOCATION=]
|
||||
--http-bind-address <HTTP_BIND_ADDRESS>
|
||||
Socket address this node will use for binding its http API. default: `0.0.0.0:8080` [env: NYMNODE_HTTP_BIND_ADDRESS=]
|
||||
Socket address this node will use for binding its http API. default: `[::]:8080` [env: NYMNODE_HTTP_BIND_ADDRESS=]
|
||||
--landing-page-assets-path <LANDING_PAGE_ASSETS_PATH>
|
||||
Path to assets directory of custom landing page of this node [env: NYMNODE_HTTP_LANDING_ASSETS=]
|
||||
--http-access-token <HTTP_ACCESS_TOKEN>
|
||||
@@ -43,27 +45,29 @@ Options:
|
||||
--expose-crypto-hardware <EXPOSE_CRYPTO_HARDWARE>
|
||||
Specify whether detailed system crypto hardware information should be exposed. default: true [env: NYMNODE_HTTP_EXPOSE_CRYPTO_HARDWARE=] [possible values: true, false]
|
||||
--mixnet-bind-address <MIXNET_BIND_ADDRESS>
|
||||
Address this node will bind to for listening for mixnet packets default: `0.0.0.0:1789` [env: NYMNODE_MIXNET_BIND_ADDRESS=]
|
||||
Address this node will bind to for listening for mixnet packets default: `[::]:1789` [env: NYMNODE_MIXNET_BIND_ADDRESS=]
|
||||
--mixnet-announce-port <MIXNET_ANNOUNCE_PORT>
|
||||
If applicable, custom port announced in the self-described API that other clients and nodes will use. Useful when the node is behind a proxy [env: NYMNODE_MIXNET_ANNOUNCE_PORT=]
|
||||
--nym-api-urls <NYM_API_URLS>
|
||||
Addresses to nym APIs from which the node gets the view of the network [env: NYMNODE_NYM_APIS=]
|
||||
--nyxd-urls <NYXD_URLS>
|
||||
Addresses to nyxd chain endpoint which the node will use for chain interactions [env: NYMNODE_NYXD=]
|
||||
--enable-console-logging <ENABLE_CONSOLE_LOGGING>
|
||||
Specify whether running statistics of this node should be logged to the console [env: NYMNODE_ENABLE_CONSOLE_LOGGING=] [possible values: true, false]
|
||||
--wireguard-enabled <WIREGUARD_ENABLED>
|
||||
Specifies whether the wireguard service is enabled on this node [env: NYMNODE_WG_ENABLED=] [possible values: true, false]
|
||||
--wireguard-bind-address <WIREGUARD_BIND_ADDRESS>
|
||||
Socket address this node will use for binding its wireguard interface. default: `0.0.0.0:51822` [env: NYMNODE_WG_BIND_ADDRESS=]
|
||||
Socket address this node will use for binding its wireguard interface. default: `[::]:51822` [env: NYMNODE_WG_BIND_ADDRESS=]
|
||||
--wireguard-announced-port <WIREGUARD_ANNOUNCED_PORT>
|
||||
Port announced to external clients wishing to connect to the wireguard interface. Useful in the instances where the node is behind a proxy [env: NYMNODE_WG_ANNOUNCED_PORT=]
|
||||
--wireguard-private-network-prefix <WIREGUARD_PRIVATE_NETWORK_PREFIX>
|
||||
The prefix denoting the maximum number of the clients that can be connected via Wireguard. The maximum value for IPv4 is 32 and for IPv6 is 128 [env: NYMNODE_WG_PRIVATE_NETWORK_PREFIX=]
|
||||
--verloc-bind-address <VERLOC_BIND_ADDRESS>
|
||||
Socket address this node will use for binding its verloc API. default: `0.0.0.0:1790` [env: NYMNODE_VERLOC_BIND_ADDRESS=]
|
||||
Socket address this node will use for binding its verloc API. default: `[::]:1790` [env: NYMNODE_VERLOC_BIND_ADDRESS=]
|
||||
--verloc-announce-port <VERLOC_ANNOUNCE_PORT>
|
||||
If applicable, custom port announced in the self-described API that other clients and nodes will use. Useful when the node is behind a proxy [env: NYMNODE_VERLOC_ANNOUNCE_PORT=]
|
||||
--entry-bind-address <ENTRY_BIND_ADDRESS>
|
||||
Socket address this node will use for binding its client websocket API. default: `0.0.0.0:9000` [env: NYMNODE_ENTRY_BIND_ADDRESS=]
|
||||
Socket address this node will use for binding its client websocket API. default: `[::]:9000` [env: NYMNODE_ENTRY_BIND_ADDRESS=]
|
||||
--announce-ws-port <ANNOUNCE_WS_PORT>
|
||||
Custom announced port for listening for websocket client traffic. If unspecified, the value from the `bind_address` will be used instead [env: NYMNODE_ENTRY_ANNOUNCE_WS_PORT=]
|
||||
--announce-wss-port <ANNOUNCE_WSS_PORT>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
{
|
||||
"mainnet":"Mainnet Endpoints",
|
||||
"sandbox":"Sandbox Endpoints"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
|
||||
{
|
||||
"mainnet":"Mainnet Endpoints",
|
||||
"sandbox":"Sandbox Endpoints"
|
||||
}
|
||||
@@ -1,11 +1,8 @@
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
# Node Status API
|
||||
|
||||
The Node Status API contains information about the network, its topology, and the routing scores of all nodes within it. It offers broadly similar information to the experimental [Habourmaster frontend](https://harbourmaster.nymtech.net/) but is stable where the Harbourmaster is subject to sudden changes as we modify and experiment with how we scrape and present data about the Mixnet infrastructure.
|
||||
The Node Status API serves information about individual `nym-nodes` in the Mixnet, such as which role they are operating in, statistics about them, services such as Network Requesters, as well as summaries of the state of the Mixnet.
|
||||
|
||||
<Callout type="info">
|
||||
People building applications or dashboards which requires information about nodes, their uptime, and their delegations should use this instead of Habourmaster.
|
||||
We recommend that developers building applications such as explorers or analytics interfaces about the Mixnet run their own instance of the API, in order to promote a robust network of downstream services, and spread the load of API calls amongst as many endpoints as possible.
|
||||
</Callout>
|
||||
|
||||
The code for this service can be found [in our monorepo](https://github.com/nymtech/nym/tree/develop/nym-node-status-api). In the future we will encourage developers to run their own instance of this API in order to distribute endpoints and query load.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"ns-api-run-deploy":"Run Instance",
|
||||
"mainnet":"Mainnet Endpoints",
|
||||
"sandbox":"Sandbox Endpoints"
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
import { RedocStandalone } from 'redoc';
|
||||
import { Callout } from 'nextra/components'
|
||||
|
||||
The information below is generated with [Redoc](https://redocly.com/docs/redoc) consuming the OpenAPI spec found at [https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json](https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json) which is also used to generate the Swagger docs deployed at [https://mainnet-node-status-api.nymtech.cc/swagger/](https://mainnet-node-status-api.nymtech.cc/swagger/).
|
||||
|
||||
<br /><br />
|
||||
|
||||
|
||||
<RedocStandalone
|
||||
specUrl="https://mainnet-node-status-api.nymtech.cc/api-docs/openapi.json"
|
||||
options={{
|
||||
|
||||
@@ -0,0 +1,336 @@
|
||||
import { Callout } from 'nextra/components'
|
||||
import { AccordionTemplate } from 'components/accordion-template.tsx'
|
||||
|
||||
# NS API: Deployment Guide
|
||||
|
||||
## Components
|
||||
The Node Status API is made up of 3 components:
|
||||
- `nym-node-status-api`
|
||||
- `nym-node-status-client`
|
||||
- `nym-node-status-agent`
|
||||
|
||||
The API stores its data in a local SQLite database. It periodically gets most of its data from a [NymAPI](./nym-api) instance, and for instances also running with the Gateway Probe, uses the stored topology to conduct performance probe tests.
|
||||
|
||||
## Gateway Probe
|
||||
You can run the `nym-node-status-api` alone, but if you want to run probes for `nym-node`s running as Gateways, you have to also run the `nym-node-status-agent` (which consumes the `nym-node-status-client` lib). The probe will run a set of tests per Gateway node, and return the sort of results seen [here](https://harbourmaster.nymtech.net/gateway/23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb).
|
||||
|
||||
## UI
|
||||
The API exposes a [Swagger](https://swagger.io/docs/) documentation UI by default on port `8000` (see an example [here](https://mainnet-node-status-api.nymtech.cc/swagger/)).
|
||||
|
||||
Currently we are not shipping a custom UI component for the Node Status API. The [Harbourmaster](https://harbourmaster.nymtech.net/) frontend consumes this API, amongst other things, but this is an internal repo we use to experiment with new APIs and data on, so it is not public yet.
|
||||
|
||||
<Callout type="info">
|
||||
We invite developers to roll their own UI for their Node Status API instance.
|
||||
</Callout>
|
||||
|
||||
## Docker Images
|
||||
We will ship Docker images for both the `agent` and `api` in the future. There are Docker images for both in root of each corresponding crate ([`agent`](https://github.com/nymtech/nym/blob/09ea406c02e9a3beebc062f525e4ea1b4222dcbb/nym-node-status-api/nym-node-status-agent/Dockerfile), [`api`](https://github.com/nymtech/nym/blob/develop/nym-node-status-api/nym-node-status-api/Dockerfile)) which are used internally, which could be a starting point for developers to dockerize their instance for the moment.
|
||||
|
||||
## Build
|
||||
### Prerequisites
|
||||
- Rust
|
||||
- SQLite
|
||||
- Get an `ipinfo` key following instructions [here](https://github.com/ipinfo/rust?tab=readme-ov-file#getting-started).
|
||||
|
||||
### Compilation
|
||||
```shell
|
||||
cargo build --release --package nym-node-status-api --package nym-node-status-agent --package nym-node-status-client
|
||||
```
|
||||
|
||||
## Run
|
||||
Since the Node Status API depends on both flags and environmental variables, it might be easier to run the binary via a script like the one below - this this script essentially just `source`-s the defined `.env` file after exporting certain binary-specific variables, and then runs the binary. You can find the `.env` files [here](https://github.com/nymtech/nym/tree/master/envs).
|
||||
|
||||
<Callout type="info">
|
||||
All CLI flags are configurable as environmental variables and vice versa, so take the following scripts / setups as guides that you can change however best suits your setup. You can see all definitions [here](https://github.com/nymtech/nym/blob/develop/nym-node-status-api/nym-node-status-api/src/cli/mod.rs#L14).
|
||||
</Callout>
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
export ENVIRONMENT=${ENVIRONMENT:-"mainnet"} # see nym/envs/ for all possible environments
|
||||
export NYM_API_CLIENT_TIMEOUT=60
|
||||
export NODE_STATUS_API_TESTRUN_REFRESH_INTERVAL=120
|
||||
export IPINFO_API_TOKEN=<YOUR_IPINFO_API_KEY>
|
||||
|
||||
monorepo_root=<PATH/TO/NYM/>
|
||||
set -a
|
||||
source "${monorepo_root}/envs/${ENVIRONMENT}.env"
|
||||
echo ${monorepo_root}/envs/${ENVIRONMENT}.env
|
||||
set +a
|
||||
export RUST_LOG=${RUST_LOG:-debug} # debug is useful to check everything is working initially, but quite verbose
|
||||
|
||||
echo "Verifying environment variables were properly sourced:"
|
||||
echo "RUST_LOG=${RUST_LOG}"
|
||||
echo "BECH32_PREFIX=${BECH32_PREFIX}"
|
||||
echo "NETWORK_NAME=${NETWORK_NAME}"
|
||||
|
||||
<PATH/TO/>nym-node-status-api -- --ipinfo-api-token $IPINFO_API_TOKEN
|
||||
```
|
||||
|
||||
### Functionality Without Gateway Probe
|
||||
Data will be restricted to information that doesn't involve Probe results; `routing` and `config` scores will be `0` and `last_probe` results `null`, as you can see in this snipped output of the `gateways` endpoint:
|
||||
|
||||
<AccordionTemplate name="Output">
|
||||
```shell
|
||||
{
|
||||
"gateway_identity_key": "23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb",
|
||||
"bonded": true,
|
||||
"performance": 99,
|
||||
"self_described": {
|
||||
"authenticator": {
|
||||
"address": "6Gdtw13Fa46AvkqkHELZZCMKWASDodoJeK9APRNpjjdj.7ji8DDkpjA2AdgwK7wbZm8yi4xZGogGJeypBQt4hAw3P@23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb"
|
||||
},
|
||||
"auxiliary_details": {
|
||||
"accepted_operator_terms_and_conditions": true,
|
||||
"announce_ports": {
|
||||
"mix_port": null,
|
||||
"verloc_port": null
|
||||
},
|
||||
"location": null
|
||||
},
|
||||
"build_information": {
|
||||
"binary_name": "nym-node",
|
||||
"build_timestamp": "2025-02-13T11:49:34.670488195Z",
|
||||
"build_version": "1.5.0",
|
||||
"cargo_profile": "release",
|
||||
"cargo_triple": "x86_64-unknown-linux-gnu",
|
||||
"commit_branch": "HEAD",
|
||||
"commit_sha": "a3e19b4563843055b305ea9a397eb1ad84b5c378",
|
||||
"commit_timestamp": "2025-02-10T18:14:47.000000000+01:00",
|
||||
"rustc_channel": "stable",
|
||||
"rustc_version": "1.84.1"
|
||||
},
|
||||
"declared_role": {
|
||||
"entry": true,
|
||||
"exit_ipr": true,
|
||||
"exit_nr": true,
|
||||
"mixnode": false
|
||||
},
|
||||
"host_information": {
|
||||
"hostname": "bwng1.bwnym.xyz",
|
||||
"ip_address": [
|
||||
"95.164.2.86"
|
||||
],
|
||||
"keys": {
|
||||
"ed25519": "23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb",
|
||||
"x25519": "H6pFjqtdSVxkxEQ3wFnuSoobDAUqHx1bYMVJzPZdRByn",
|
||||
"x25519_noise": null
|
||||
}
|
||||
},
|
||||
"ip_packet_router": {
|
||||
"address": "7ms2D2uYiTuhX6MKeVL5rz5usgehEoxAAovwYm9nJyBF.3siMjk3wTU7ykaXLNi9c7LpX8yonYKPCA4BQoMwhsfTV@23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb"
|
||||
},
|
||||
"last_polled": "2025-03-03 09:48:03.635274187 +00:00:00",
|
||||
"mixnet_websockets": {
|
||||
"ws_port": 9000,
|
||||
"wss_port": 9001
|
||||
},
|
||||
"network_requester": {
|
||||
"address": "HuNL1pFprNSKW6jdqppibXP5KNKCNJxDh7ivpYcoULN9.C62NahRTUf6kqpNtDVHXoVriQr6yyaU5LtxdgpbsGrtA@23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb",
|
||||
"uses_exit_policy": true
|
||||
},
|
||||
"wireguard": {
|
||||
"port": 51822,
|
||||
"public_key": "6o8x9GitFjcrkjrJnivWaQCPnxXykQPYLneNr2FEB8Vq"
|
||||
}
|
||||
},
|
||||
"explorer_pretty_bond": {
|
||||
"identity_key": "23A7CSaBSA2L67PWuFTPXUnYrCdyVcB7ATYsjUsfdftb",
|
||||
"location": {
|
||||
"latitude": 52.5083,
|
||||
"longitude": 5.475,
|
||||
"two_letter_iso_country_code": "NL"
|
||||
},
|
||||
"owner": "n1cp5gq0apat6c7qmenqp5zjprn2vwvc7jl29j8r",
|
||||
"pledge_amount": {
|
||||
"amount": "100000000",
|
||||
"denom": "unym"
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"moniker": "bwn_g1",
|
||||
"website": "https://bwnym.xyz",
|
||||
"security_contact": "bwnym@proton.me",
|
||||
"details": "This gateway is part of the NYM project, which is dedicated to create outstanding privacy software that is legally compliant without sacrificing integrity or having any backdoors."
|
||||
},
|
||||
"last_probe_result": null,
|
||||
"last_probe_log": null,
|
||||
"last_testrun_utc": null,
|
||||
"last_updated_utc": "2025-03-03T10:45:48+00:00",
|
||||
"routing_score": 0.0,
|
||||
"config_score": 0
|
||||
},
|
||||
```
|
||||
</AccordionTemplate>
|
||||
|
||||
If you have already run the API before, make sure to add the following to your script `--database-url "sqlite://node-status-api.sqlite?mode=rwc` so it will continue using the same DB.
|
||||
|
||||
### Functionality with Gateway Probe
|
||||
If you want to enable Gateway node probes and have the NS API store that data, you need to periodically run the `nym-node-status-agent`, authenticated with your `nym-node-status-api` instance. Authentication is to make sure that only the `-agent` you are operating (or others you trust) are submitting data to your API instance.
|
||||
|
||||
#### Compile Gateway Probe
|
||||
The `nym-node-status-agent` is a thin wrapper around the Gateway Probe binary which currently is in the NymVPN repo. `git checkout` to the most recent [release](https://github.com/nymtech/nym-vpn-client/releases), and compile the probe by following the [readme instructions](https://github.com/nymtech/nym-vpn-client/tree/develop/nym-vpn-core/crates/nym-gateway-probe). You will point the `-agent` at this binary when doing Probe testruns.
|
||||
|
||||
#### Generate Keypair
|
||||
```shell
|
||||
<PATH/TO/>nym-node-status-agent generate-keypair --path <PATH/TO/KEY/FILE/TO/GENERATE>/<KEY_NAME>
|
||||
# e.g.
|
||||
# nym-node-status-agent generate-keypair --path ~/.ssh/ns-agent-key
|
||||
```
|
||||
|
||||
You will then want to export the generated `public-key` so its accessible to the `nym-node-status-api` however you are setting your environmental variables, as `NODE_STATUS_API_AGENT_KEY_LIST`:
|
||||
|
||||
```bash
|
||||
export NODE_STATUS_API_AGENT_KEY_LIST=<YOUR_KEY> # e.g. "H4z8kx5Kkf5JNQHfxaE1MwRndjDCD1C7HsVhHTFfBZ4J"
|
||||
```
|
||||
|
||||
In this situation, you are probably only using one key. However, it is possible to set multiple keys as a comma seperated list, in case you wish to whitelist multiple `-agent`s to be able to submit to a single `-api` instance.
|
||||
|
||||
#### Run the Node Status Agent
|
||||
```shell
|
||||
<PATH/TO/>nym-node-status-agent run-probe --server-address http://127.0.0.1 --server-port 8000 --ns-api-auth-key "<NS_AGENT_PRIVATE_KEY>" --probe-path <PATH/TO/> nym-gateway-probe
|
||||
```
|
||||
|
||||
You will see a lot of output like so:
|
||||
|
||||
<AccordionTemplate name="Output">
|
||||
```shell
|
||||
listen_port=48586
|
||||
public_key=3f95caf771b8a63b9bdae6c186f7aba2eae93483f1c82c0243b5e00c85b4ec26
|
||||
preshared_key=0000000000000000000000000000000000000000000000000000000000000000
|
||||
protocol_version=1
|
||||
endpoint=185.186.78.251:51822
|
||||
last_handshake_time_sec=0
|
||||
last_handshake_time_nsec=0
|
||||
tx_bytes=0
|
||||
rx_bytes=0
|
||||
persistent_keepalive_interval=0
|
||||
allowed_ip=0.0.0.0/0
|
||||
2025/03/03 18:58:28 Pinging nymtech.net seq=0
|
||||
2025/03/03 18:58:29 Ping latency: 44.503483ms
|
||||
2025/03/03 18:58:29 Pinging nymtech.net seq=1
|
||||
2025/03/03 18:58:29 Ping latency: 42.852414ms
|
||||
2025/03/03 18:58:29 Pinging nymtech.net seq=2
|
||||
2025/03/03 18:58:29 Ping latency: 43.627256ms
|
||||
2025/03/03 18:58:29 Pinging nymtech.net seq=3
|
||||
2025/03/03 18:58:29 Ping latency: 43.638839ms
|
||||
2025/03/03 18:58:29 Pinging nymtech.net seq=4
|
||||
2025/03/03 18:58:29 Ping latency: 43.345357ms
|
||||
2025/03/03 18:58:29 Pinging 1.1.1.1 seq=0
|
||||
2025/03/03 18:58:29 Ping latency: 46.327233ms
|
||||
2025/03/03 18:58:34 Pinging 1.1.1.1 seq=1
|
||||
2025/03/03 18:58:34 Ping latency: 46.273726ms
|
||||
2025/03/03 18:58:39 Pinging 1.1.1.1 seq=2
|
||||
2025/03/03 18:58:39 Ping latency: 46.542774ms
|
||||
2025/03/03 18:58:44 Pinging 1.1.1.1 seq=3
|
||||
2025/03/03 18:58:44 Ping latency: 45.663545ms
|
||||
2025/03/03 18:58:49 Pinging 1.1.1.1 seq=4
|
||||
2025/03/03 18:58:49 Ping latency: 43.803063ms
|
||||
2025/03/03 18:58:56 Downloaded file content length: 1.00 MB
|
||||
2025/03/03 18:58:56 Download duration: 1.308072386s
|
||||
2025/03/03 18:58:56 private_key=1083749e43f4f8fb008f3f7deef9107ef86a68969670ddbb9f07bf85f94fb564
|
||||
listen_port=39129
|
||||
public_key=3f95caf771b8a63b9bdae6c186f7aba2eae93483f1c82c0243b5e00c85b4ec26
|
||||
preshared_key=0000000000000000000000000000000000000000000000000000000000000000
|
||||
protocol_version=1
|
||||
endpoint=185.186.78.251:51822
|
||||
last_handshake_time_sec=0
|
||||
last_handshake_time_nsec=0
|
||||
tx_bytes=0
|
||||
rx_bytes=0
|
||||
persistent_keepalive_interval=0
|
||||
allowed_ip=::/0
|
||||
2025/03/03 18:58:56 Pinging ipv6.google.com seq=0
|
||||
2025/03/03 18:58:56 Ping latency: 42.839528ms
|
||||
2025/03/03 18:58:56 Pinging ipv6.google.com seq=1
|
||||
2025/03/03 18:58:56 Ping latency: 54.844651ms
|
||||
2025/03/03 18:58:56 Pinging ipv6.google.com seq=2
|
||||
2025/03/03 18:58:56 Ping latency: 51.23104ms
|
||||
2025/03/03 18:58:56 Pinging ipv6.google.com seq=3
|
||||
2025/03/03 18:58:56 Ping latency: 43.320409ms
|
||||
2025/03/03 18:58:56 Pinging ipv6.google.com seq=4
|
||||
2025/03/03 18:58:56 Ping latency: 63.517358ms
|
||||
2025/03/03 18:58:56 Pinging 2001:4860:4860::8888 seq=0
|
||||
2025/03/03 18:58:56 Ping latency: 54.682534ms
|
||||
2025/03/03 18:59:01 Pinging 2001:4860:4860::8888 seq=1
|
||||
2025/03/03 18:59:01 Ping latency: 55.56235ms
|
||||
2025/03/03 18:59:06 Pinging 2001:4860:4860::8888 seq=2
|
||||
2025/03/03 18:59:06 Ping latency: 55.970418ms
|
||||
2025/03/03 18:59:11 Pinging 2001:4860:4860::8888 seq=3
|
||||
2025/03/03 18:59:14 Failed to send ping: i/o timeout
|
||||
2025/03/03 18:59:19 Pinging 2001:4860:4860::8888 seq=4
|
||||
2025/03/03 18:59:22 Failed to send ping: i/o timeout
|
||||
2025/03/03 18:59:27 Pinging 2606:4700:4700::1111 seq=0
|
||||
2025/03/03 18:59:27 Ping latency: 45.072616ms
|
||||
2025/03/03 18:59:32 Pinging 2606:4700:4700::1111 seq=1
|
||||
2025/03/03 18:59:32 Ping latency: 44.357306ms
|
||||
2025/03/03 18:59:37 Pinging 2606:4700:4700::1111 seq=2
|
||||
2025/03/03 18:59:37 Ping latency: 44.013562ms
|
||||
2025/03/03 18:59:42 Pinging 2606:4700:4700::1111 seq=3
|
||||
2025/03/03 18:59:42 Ping latency: 46.94342ms
|
||||
2025/03/03 18:59:47 Pinging 2606:4700:4700::1111 seq=4
|
||||
2025/03/03 18:59:48 Ping latency: 43.372288ms
|
||||
2025/03/03 18:59:53 Pinging 2620:fe::fe seq=0
|
||||
2025/03/03 18:59:53 Ping latency: 42.164952ms
|
||||
2025/03/03 18:59:58 Pinging 2620:fe::fe seq=1
|
||||
2025/03/03 18:59:58 Ping latency: 42.295812ms
|
||||
2025/03/03 19:00:03 Pinging 2620:fe::fe seq=2
|
||||
2025/03/03 19:00:03 Ping latency: 43.117534ms
|
||||
2025/03/03 19:00:08 Pinging 2620:fe::fe seq=3
|
||||
2025/03/03 19:00:08 Ping latency: 44.26068ms
|
||||
2025/03/03 19:00:13 Pinging 2620:fe::fe seq=4
|
||||
2025/03/03 19:00:13 Ping latency: 45.29956ms
|
||||
2025/03/03 19:00:21 Downloaded file content length: 10.00 MB
|
||||
2025/03/03 19:00:21 Download duration: 3.39529252s
|
||||
```
|
||||
</AccordionTemplate>
|
||||
|
||||
Whilst you can run the `-agent` directly, it might be easier to run multiple instances in parallel with a script like so:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
export ENVIRONMENT=${ENVIRONMENT:-"mainnet"}
|
||||
|
||||
probe_git_ref="nym-vpn-core-v1.4.0" # check for the most recent release
|
||||
monorepo_root=<PATH/TO/NYM/>
|
||||
|
||||
set -a
|
||||
source "${monorepo_root}/envs/${ENVIRONMENT}.env"
|
||||
set +a
|
||||
|
||||
export RUST_LOG="info"
|
||||
export NODE_STATUS_AGENT_SERVER_ADDRESS="http://127.0.0.1"
|
||||
export NODE_STATUS_AGENT_SERVER_PORT="8000"
|
||||
export NODE_STATUS_AGENT_AUTH_KEY=<NS_AGENT_PRIVATE_KEY>
|
||||
export NODE_STATUS_AGENT_PROBE_EXTRA_ARGS="netstack-download-timeout-sec=30,netstack-num-ping=2,netstack-send-timeout-sec=1,netstack-recv-timeout-sec=1"
|
||||
|
||||
workers=${1:-1}
|
||||
echo "Running $workers workers in parallel"
|
||||
|
||||
function swarm() {
|
||||
local workers=$1
|
||||
|
||||
for ((i = 1; i <= workers; i++)); do
|
||||
${monorepo_root}/target/release/nym-node-status-agent run-probe --probe-path ~/<PATH/TO>/nym-vpn-client/nym-vpn-core/target/debug/nym-gateway-probe &
|
||||
done
|
||||
|
||||
wait
|
||||
|
||||
echo "All agents completed"
|
||||
}
|
||||
|
||||
swarm $workers
|
||||
```
|
||||
|
||||
And run specifying the number of workers with `./<SCRIPT_NAME>.sh <NUMBER_OF_WORKERS>`.
|
||||
|
||||
<Callout type="info">
|
||||
When running the probe, use logging level `RUST_LOG=info`. The Node Status API relies on that granularity for parsing probe results, and the logs grow incessantly if a lower level (e.g. `DEBUG`) is used.
|
||||
</Callout>
|
||||
|
||||
### Ports
|
||||
By default the API listens on `8000`, so you will need to configure this post to be reachable on your remote server. You can modify this with the `--http_port` flag.
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"mainnet":"Mainnet Endpoints"
|
||||
}
|
||||
@@ -47,6 +47,218 @@ This page displays a full list of all the changes during our release cycle from
|
||||
|
||||
<VarInfo />
|
||||
|
||||
## `v2025.4-dorina-patched`
|
||||
|
||||
Patched version of `dorina` with a few fixes and tweaks to the release. We would like to ask `nym-node` operators to upgrade to this version as quickly as possible to implement the fixes across the network and improve general quality before NymVPN launch.
|
||||
|
||||
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.4-dorina-patched)
|
||||
- [`nym-node`](nodes/nym-node.mdx) version `1.6.2`
|
||||
|
||||
```shell
|
||||
nym-node
|
||||
Binary Name: nym-node
|
||||
Build Timestamp: 2025-03-06T20:32:36.922212778Z
|
||||
Build Version: 1.6.2
|
||||
Commit SHA: 247ebb7c4339de0a298a7fcb2574122a8306c3b8
|
||||
Commit Date: 2025-03-06T21:26:16.000000000+01:00
|
||||
Commit Branch: HEAD
|
||||
rustc Version: 1.85.0
|
||||
rustc Channel: stable
|
||||
cargo Profile: release
|
||||
```
|
||||
|
||||
- Use legacy crypto for constructing SURB headers ([#5579])
|
||||
- Bugfix: make sure to correctly decode response content when putting it into error message ([#5571])
|
||||
- Tweak surb management to be more conservative ([#5570])
|
||||
- Deserialize v5 authenticator requests ([#5568])
|
||||
- Chore: additional logs when attempting to load ecash keys ([#5567])
|
||||
- Add full response body to error message upon decoding failure ([#5566])
|
||||
- Hotfix: ensure we bail on merkle leaves insertion upon missing data ([#5565])
|
||||
- Feature: v2 authentication request (#5537) ([#5563])
|
||||
- Create authenticator v5 request/response types ([#5561])
|
||||
|
||||
[#5579]: https://github.com/nymtech/nym/pull/5579
|
||||
[#5571]: https://github.com/nymtech/nym/pull/5571
|
||||
[#5570]: https://github.com/nymtech/nym/pull/5570
|
||||
[#5568]: https://github.com/nymtech/nym/pull/5568
|
||||
[#5567]: https://github.com/nymtech/nym/pull/5567
|
||||
[#5566]: https://github.com/nymtech/nym/pull/5566
|
||||
[#5565]: https://github.com/nymtech/nym/pull/5565
|
||||
[#5563]: https://github.com/nymtech/nym/pull/5563
|
||||
[#5561]: https://github.com/nymtech/nym/pull/5561
|
||||
|
||||
|
||||
## `v2025.4-dorina`
|
||||
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.4-dorina)
|
||||
- [`nym-node`](nodes/nym-node.mdx) version `1.6.0`
|
||||
```shell
|
||||
Binary Name: nym-node
|
||||
Build Timestamp: 2025-03-04T09:03:11.322601809Z
|
||||
Build Version: 1.6.0
|
||||
Commit SHA: 7060fa6dad58f17543f5086c73b1854ad1ceae60
|
||||
Commit Date: 2025-03-03T17:24:10.000000000Z
|
||||
Commit Branch: release/2025.4-dorina
|
||||
rustc Version: 1.86.0-nightly
|
||||
rustc Channel: nightly
|
||||
cargo Profile: release
|
||||
```
|
||||
|
||||
### Operators Updates & Tools
|
||||
|
||||
- New advanced [guide to virtualise a dedicated server](nodes/preliminary-steps/vps-setup/advanced), providing steps for experienced operators and aspiring sys-admins who seek for higher optimisation and better efficiency of their work orchestrating multiple nodes.
|
||||
- New Service Grant program is being implemented by operators setting up new ~150 Exit Gateways following the updated [specs for `nym-node`](nodes#minimum-requirements)
|
||||
|
||||
### Features
|
||||
- [Feature/chain status api](https://github.com/nymtech/nym/pull/5539):
|
||||
this PR introduces `/v1/network/chain-status` endpoint on nym api to give basic information about the current block as seen by this API (with some caching) and updates `/health` endpoint to give stall information.
|
||||
|
||||
- [Add SURBs soft threshold](https://github.com/nymtech/nym/pull/5535): the IPR should try to keep a buffer of available SURBs to reduce latency.
|
||||
|
||||
- [Simplify IPR v8](https://github.com/nymtech/nym/pull/5532): purge stuff from IPR v8 to simplify; use protocol field in v8.
|
||||
|
||||
- [Shared instance for DNS AsyncResolver](https://github.com/nymtech/nym/pull/5523):
|
||||
make the `HickoryDnsResolver` use a shared instance by default to limit fd use. Now the multiple `nym_http_api_client::Client`s that get built should take advantage of a shared connection pool for DNS lookups. If for some reason a client does need an independent AsyncResolver this can still be done with the added `thread_resolver` function.
|
||||
|
||||
- [cherry-pick 17d3ff2d775f61aee381d90a304ed416c08f33fc onto dorina](https://github.com/nymtech/nym/pull/5519)
|
||||
|
||||
- [cherry-pick 6e5d0dac1b75413c5f09122b0d953f8ec6ef48df onto dorina](https://github.com/nymtech/nym/pull/5518)
|
||||
|
||||
- [feat: add config option for maximum number of client connections](https://github.com/nymtech/nym/pull/5513)
|
||||
|
||||
- [IPR request types v8](https://github.com/nymtech/nym/pull/5498): Bump IPR request/response types to v8
|
||||
|
||||
- [Support static routes for HTTP requests](https://github.com/nymtech/nym/pull/5487)
|
||||
|
||||
- [added missing import to doctest](https://github.com/nymtech/nym/pull/5480)
|
||||
|
||||
- [adjusted TestSetup::new_complex to ensure bonded node's existence](https://github.com/nymtech/nym/pull/5478)
|
||||
|
||||
- [Trigger contracts CI on main workspace Cargo changes](https://github.com/nymtech/nym/pull/5477): since the contracts workspace depends on the common code in the main workspace, and since the contracts are critical to not have regressions in, trigger contracts CI on any changes to the workspace Cargo.toml and lock files.
|
||||
|
||||
- [Run cargo autoinherit](https://github.com/nymtech/nym/pull/5460): run `cargo autoinherit` to move a bunch of dependencies to the workspace level. `Cargo.lock` remains untouched.
|
||||
|
||||
- [Disable debug in wasm and wallet workflows too](https://github.com/nymtech/nym/pull/5459)
|
||||
|
||||
- [Fix clippy::precedence](https://github.com/nymtech/nym/pull/5457): fix clippy warnings for the Rust beta toolchain.
|
||||
|
||||
- [Provide Interval context with node descriptor endpoints](https://github.com/nymtech/nym/pull/5456): add current interval context information to existing enpoints using `build_skimmed_nodes_response` under the hood. This allows clients checking for a refresh to send the `epoch_uid` as a query parameter when fetching updates so the server can tell it that there have been no changes, instead of sending duplicate data over and over. The changes in this PR should be backwards compatible and never interfere with existing clients. The additions to the `NodeParams` are optional, so there is no error if a client does not send them. Old clients will not send `epoch_uid` by accident so they cannot accidentally clear their set of known nodes. In the response the status field is optional so if it is missing (e.g. if a new client talks to an old server) the connection still works. For old clients speaking to new servers, the json parsing will simply ignore extra information not included in the objects json spec.
|
||||
|
||||
<AccordionTemplate name={<TestingSteps/>}>
|
||||
|
||||
Made a request to the `/api/v1/unstable/nym-nodes/semi-skimmed` endpoint:
|
||||
- without entering an epoch_id
|
||||
- with entering the current epoch_id
|
||||
- with entering an old epoch_id
|
||||
- with entering a future epoch_id
|
||||
All results returned the same data, as expected
|
||||
|
||||
</AccordionTemplate>
|
||||
|
||||
- [Feature/add gbp currency](https://github.com/nymtech/nym/pull/5453)
|
||||
|
||||
- [Add helper to extract a list of sqlite files with journal files wal/shm](https://github.com/nymtech/nym/pull/5452)
|
||||
|
||||
- [Add a middleware layer to the nym api allowing for data compression](https://github.com/nymtech/nym/pull/5451): after Testing in a minimal example, this does work as expected. Routes still default to plain encoding, however if a client indicates support for a compressed encoding using the `Accept-Encoding` header then the served response will be compressed with the appropriate `Content-Encoding` header. ([Proof-of-Concept](https://gist.github.com/jmwample/c15a983e804fc338fee3d1b037d216b0))
|
||||
|
||||
<AccordionTemplate name={<TestingSteps/>}>
|
||||
Sent requests with and without Accept-Encoding: gzip to observe response behaviour
|
||||
Verified if responses included content-encoding: gzip when compression was requested
|
||||
Checked response file output to confirm actual compression occurred
|
||||
</AccordionTemplate>
|
||||
|
||||
- [Condense core API functionalities and enable gzip decompression for reqwest payloads](https://github.com/nymtech/nym/pull/5450): this PR is intended to take out variables from our usage of HTTP requests in the `nym-http-api-client`. To do this the PR: (1) adds the `ApiClientCore` trait with the minimal feature required to send a request, (2) turns all request sending into trait extension automatically implemented for any type that implements `ApiClientCore`. This has the benefit of consistent expected behavior for all clients using `nym-http-api-client`, including features added going forward. FOR EXAMPLE this pr adds a default header `"accept-encoding: gzip;q=1.0, *;q=0.5"` which indicates that compression is preferred whenever available. Other features to keep in mind here are things like configurable retries, domain fallbacks, etc. Note: the `Apiclient` interface could be simplified, but that would require refactoring our downstream usages of the API. For now this isn't necessary as `ApiClient` is implemented automatically so it costs nothing to have it this way. It just allows divergent usage in downstream crates.
|
||||
|
||||
<AccordionTemplate name={<TestingSteps/>}>
|
||||
Measured nym-api response times before and after updating the API client using curl
|
||||
Checked if the client automatically decompressed Gzip responses
|
||||
</AccordionTemplate>
|
||||
|
||||
- [Seedable clients](https://github.com/nymtech/nym/pull/5440): Adds `DerivationMaterial` and accompanying methods to builders. `DerivationMaterial` encapsulates parameters for deterministic key derivation using HKDF (SHA-512). Use the `derive_secret()` method to generate a 32-byte secret. To prepare for a new derivation, call the `next()` method which increments the index. **It is the caller's responsibility to track and persist the derivation index if keys need to be rederived.**
|
||||
|
||||
<AccordionTemplate name="Example">
|
||||
|
||||
```rust
|
||||
let master_key = [0u8; 32]; // your secret master key
|
||||
let salt = "unique-salt-value".to_string();
|
||||
let material = DerivationMaterial::new(master_key, 0, salt.as_bytes());
|
||||
|
||||
// Derive a secret
|
||||
let secret = material.derive_secret().expect("Failed to derive secret");
|
||||
|
||||
// Prepare for the next derivation
|
||||
let next_material = material.next();
|
||||
```
|
||||
|
||||
</AccordionTemplate>
|
||||
|
||||
- [Remove all recv_with_delay and add shutdown condition to loops in client-core](https://github.com/nymtech/nym/pull/5435): inside client-core we want to prepare the ground for moving a behaviour close to what we have in the vpn client. Remove all the recv_with_delay since we want to just stop. Add shutdown condition to all select loops to guard against the shutdown listener being polled inside the select blocks. Remove unwraps when sending on unbounded channels in case the receiver exits before the sender. Move `TaskClient` to be a member field so make it easier to wrap log errors in a shutdown check. Update all fork names to use underscore consistently, since the task separator is hyphen
|
||||
|
||||
<AccordionTemplate name={<TestingSteps/>}>
|
||||
Validated that all binaries including `nym-node`, `nym-client`, `nym-network-requester`, and `nym-socks5-client` are behaving well without indicating the presence of any unexpected errors or crashes
|
||||
</AccordionTemplate>
|
||||
|
||||
- [Disable the test for checking the remaining bandwidth in nym-node-status-api](https://github.com/nymtech/nym/pull/5425): this check fails almost every time on CI, possibly due to rate limiting? It's not good to disable the check, but it's blocking CI as it stands now. Given that we have the check above for locating the ip, we at least have a little coverage.
|
||||
|
||||
- [Dz nym node stats](https://github.com/nymtech/nym/pull/5418): removed obsolete fields from stats (blacklisted mixnodes, blacklisted gateways, bonded mixnodes, bonded gateways). Introduced nym-node scraping, beside just mixnodes. `/mixnodes/stats` now returns data for nym-nodes as well, which results in much more accurate "packets mixed" stats.
|
||||
|
||||
- [Nymnode entrypoint docker](https://github.com/nymtech/nym/pull/5300)
|
||||
|
||||
### Bugfix
|
||||
- [bugfix: dont query for ecash apis unless necessary when spending ticketbooks](https://github.com/nymtech/nym/pull/5508)
|
||||
|
||||
- [bugfix: bound check when recovering a reply SURB](https://github.com/nymtech/nym/pull/5502)
|
||||
|
||||
- [fix: update fx average rate calcs to ignore 0 values](https://github.com/nymtech/nym/pull/5454)
|
||||
|
||||
### Chore
|
||||
- [chore: workspace global panic preventing lints](https://github.com/nymtech/nym/pull/5512)
|
||||
|
||||
- [chore: removed all old coconut code](https://github.com/nymtech/nym/pull/5500)
|
||||
|
||||
- [build(deps): bump the patch-updates group across 1 directory with 3 updates](https://github.com/nymtech/nym/pull/5482): updates `clap` from 4.5.28 to 4.5.30, updates `clap` from 4.5.28 to 4.5.30, updates `prost` from 0.13.4 to 0.13.5.
|
||||
|
||||
- [build(deps): bump http from 1.1.0 to 1.2.0](https://github.com/nymtech/nym/pull/5472)
|
||||
|
||||
- [build(deps): bump utoipa-swagger-ui from 8.0.3 to 8.1.0](https://github.com/nymtech/nym/pull/5471)
|
||||
|
||||
- [build(deps): bump colored from 2.1.0 to 2.2.0](https://github.com/nymtech/nym/pull/5470)
|
||||
|
||||
- [build(deps): bump celes from 2.4.0 to 2.5.0](https://github.com/nymtech/nym/pull/5469)
|
||||
|
||||
- [build(deps): bump the patch-updates group with 2 updates](https://github.com/nymtech/nym/pull/5467): updates `clap` from 4.5.28 to 4.5.29, updates `prost` from 0.13.4 to 0.13.5.
|
||||
|
||||
- [build(deps): bump elliptic from 6.5.4 to 6.6.1 in /docker/typescript_client/upload_contract](https://github.com/nymtech/nym/pull/5463): bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.4 to 6.6.1.
|
||||
|
||||
- [build(deps): bump uniffi_build from 0.25.3 to 0.29.0](https://github.com/nymtech/nym/pull/5448)
|
||||
|
||||
- [Upgrade tower to 0.5.2](https://github.com/nymtech/nym/pull/5446)
|
||||
|
||||
- [build(deps): bump hickory-proto from 0.24.2 to 0.24.3 in /nym-wallet](https://github.com/nymtech/nym/pull/5445)
|
||||
|
||||
- [build(deps): bump hickory-proto from 0.24.2 to 0.24.3](https://github.com/nymtech/nym/pull/5444)
|
||||
|
||||
- [build(deps): bump the patch-updates group across 1 directory with 10 updates](https://github.com/nymtech/nym/pull/5439):
|
||||
Bumps the patch-updates group with 10 updates in the directory:
|
||||
|
||||
| Package | From | To |
|
||||
| --- | --- | --- |
|
||||
| [async-trait](https://github.com/dtolnay/async-trait) | `0.1.85` | `0.1.86` |
|
||||
| [clap](https://github.com/clap-rs/clap) | `4.5.27` | `4.5.28` |
|
||||
| [comfy-table](https://github.com/nukesor/comfy-table) | `7.1.3` | `7.1.4` |
|
||||
| [hickory-resolver](https://github.com/hickory-dns/hickory-dns) | `0.24.2` | `0.24.3` |
|
||||
| [once_cell](https://github.com/matklad/once_cell) | `1.20.2` | `1.20.3` |
|
||||
| [pin-project](https://github.com/taiki-e/pin-project) | `1.1.8` | `1.1.9` |
|
||||
| [serde_json_path](https://github.com/hiltontj/serde_json_path) | `0.7.1` | `0.7.2` |
|
||||
| [toml](https://github.com/toml-rs/toml) | `0.8.19` | `0.8.20` |
|
||||
| [cosmrs](https://github.com/cosmos/cosmos-rust) | `0.21.0` | `0.21.1` |
|
||||
| [tokio-postgres](https://github.com/sfackler/rust-postgres) | `0.7.12` | `0.7.13` |
|
||||
|
||||
- [build(deps): bump openssl from 0.10.56 to 0.10.70 in /nym-wallet](https://github.com/nymtech/nym/pull/5422)
|
||||
|
||||
- [build(deps): bump hyper from 1.4.1 to 1.6.0](https://github.com/nymtech/nym/pull/5416)
|
||||
|
||||
- [build(deps): bump publicsuffix from 2.2.3 to 2.3.0](https://github.com/nymtech/nym/pull/5367)
|
||||
|
||||
## `v2025.3-ruta`
|
||||
|
||||
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.3-ruta)
|
||||
@@ -97,7 +309,7 @@ As we announced in [`hu` release notes](#service-grant-program-v2), we are opene
|
||||
|
||||
##### Locations
|
||||
|
||||
These locations and slots are approximate and constantly change based on new operators submissions. Please keep in mind that we are going to chose people with respect to empty slots in the given location at the time of their submission. In case of competing submissions in the same location we will take the one with better specs vs price ratio.
|
||||
These locations and slots are approximate and constantly change based on new operators submissions. Please keep in mind that we are going to chose people with respect to empty slots in the given location at the time of their submission. In case of competing submissions in the same location we will take the one with better specs vs price ratio.
|
||||
|
||||
| LOCATION | SLOTS |
|
||||
| :-- | --: |
|
||||
@@ -151,7 +363,7 @@ These locations and slots are approximate and constantly change based on new ope
|
||||
|
||||
- [Send shutdown instead of panic when reaching max fail](https://github.com/nymtech/nym/pull/5398): Remove a panic and an unwrap inside `client-core` that is hit occasionally in the vpn client. This is a change that can have wide ranging impact since it changes the task handling and it's inside `client-core`, which is used in many components and services, so preventing regressions is important.
|
||||
|
||||
- [Relocate a validator api function](https://github.com/nymtech/nym/pull/5401): Adds a function to the `nym-validator-client` crate that hits the network details endpoint and returns an object parsed from `json`. This was floating loose in the `nym-vpn-client` repo.
|
||||
- [Relocate a validator api function](https://github.com/nymtech/nym/pull/5401): Adds a function to the `nym-validator-client` crate that hits the network details endpoint and returns an object parsed from `json`. This was floating loose in the `nym-vpn-client` repo.
|
||||
|
||||
- [Bump the patch-updates group across 1 directory with 9 updates](https://github.com/nymtech/nym/pull/5406)
|
||||
|
||||
@@ -199,7 +411,7 @@ From `nym-node v1.3.0` operators can technically choose multiple functionalities
|
||||
|
||||
- Updated maintenance guides to [backup](nodes/maintenance#backup-a-node), [restore](nodes/maintenance#restoring-a-node) and [move](nodes/maintenance#moving-a-node) a node, containing a new and important commands to backup and restore `clients.sqlite` database.
|
||||
|
||||
- [New explanation of `nym-node` functionalities](nodes/nym-node/setup#functionality-mode) describing how Gateways get selected in Mixnet mode and Wireguard mode.
|
||||
- [New explanation of `nym-node` functionalities](nodes/nym-node/setup#functionality-mode) describing how Gateways get selected in Mixnet mode and Wireguard mode.
|
||||
|
||||
<Callout type="warning">
|
||||
Wireguard nodes route data directly to the open internet. Therefore it exposes IP of operators server (VPS) to abuse complains. Before you decide to run a node with active wireguard routing, please read our [Community Counsel pages](community-counsel/exit-gateway) containing more information and some legal content.
|
||||
@@ -223,7 +435,7 @@ We have been notified that a handful of nodes have been taken down by abuse repo
|
||||
- Join [Community legal counsel](https://nym.com/docs/operators/community-counsel) - our collective knowledge hub. Add your findings by opening a [Pull Request](https://nym.com/docs/operators/add-content)
|
||||
|
||||
- While we are working on a new list of more friendly providers, consider to move away from these provides as soon as possible:
|
||||
|
||||
|
||||
- Servinga / VPS2day (AS39378)
|
||||
- Frantech / Ponynet / BuyVM (AS53667)
|
||||
- OVH SAS / OVHcloud (AS16276)
|
||||
@@ -233,7 +445,7 @@ We have been notified that a handful of nodes have been taken down by abuse repo
|
||||
- Psychz Networks (AS40676)
|
||||
- 1337 Services GmbH / RDP.sh (AS210558)
|
||||
|
||||
- Backup your nodes to have access to `.nym` directory locally. Follow [node](nodes/maintenance#backup-a-node) and [proxy configuration](nodes/maintenance#backup-proxy-configuration) backup guides to be able to [restore your node](nodes/maintenance#restoring-a-node) later on on another machine, without losing your delegation.
|
||||
- Backup your nodes to have access to `.nym` directory locally. Follow [node](nodes/maintenance#backup-a-node) and [proxy configuration](nodes/maintenance#backup-proxy-configuration) backup guides to be able to [restore your node](nodes/maintenance#restoring-a-node) later on on another machine, without losing your delegation.
|
||||
|
||||
- We would like to ask operators who use reverse proxy and a domain (required for Gateways) to start using a common convention starting with `nym-exit` for their nodes URL. The entire address should have this new format:
|
||||
```
|
||||
@@ -258,7 +470,7 @@ nym-exit.mysquad.org
|
||||
|
||||
**The `NYM-EXIT` part in the beginning is what's important.**
|
||||
|
||||
- When registering a domain, check [Top Level Domain (TLD)](https://www.techopedia.com/definition/1348/top-level-domain-tld) terms and conditions. For example `.icu` is a no go. Having a wrong TLD may lead to your domain being taken away from you when facing a DMCA report.
|
||||
- When registering a domain, check [Top Level Domain (TLD)](https://www.techopedia.com/definition/1348/top-level-domain-tld) terms and conditions. For example `.icu` is a no go. Having a wrong TLD may lead to your domain being taken away from you when facing a DMCA report.
|
||||
|
||||
- Write a message to your provider and introduce your intention to run a Nym Node on their service
|
||||
<AccordionTemplate name="Email template: Introduce yourself to your VPS provider">
|
||||
@@ -322,7 +534,7 @@ Undelegated due to high saturation:
|
||||
|
||||
#### Service Grant Program v2
|
||||
|
||||
Aside from delegating on top of nodes, Nym runs a Service Grant Program (SGP) to support Exit Gateway operators before they will be rewarded by collecting [zk-nym tickets](../network/cryptography/zk-nym) from users subscription. Operators included in SGP are long term active community members with the highest requirements on the technical setup and upgrading pace. We are about to start a second iteration of SGP very soon (SGPv2). The final slots and locations are yet to be concluded. Priority to participate in SGPv2 will be given to the current operators in SGP. Based on the number of slots, we will then determine how many more operators can sign up.
|
||||
Aside from delegating on top of nodes, Nym runs a Service Grant Program (SGP) to support Exit Gateway operators before they will be rewarded by collecting [zk-nym tickets](../network/cryptography/zk-nym) from users subscription. Operators included in SGP are long term active community members with the highest requirements on the technical setup and upgrading pace. We are about to start a second iteration of SGP very soon (SGPv2). The final slots and locations are yet to be concluded. Priority to participate in SGPv2 will be given to the current operators in SGP. Based on the number of slots, we will then determine how many more operators can sign up.
|
||||
|
||||
##### Rules of SGPv2
|
||||
|
||||
@@ -330,7 +542,7 @@ Aside from delegating on top of nodes, Nym runs a Service Grant Program (SGP) to
|
||||
**We will share more info soon in the channels. The rules are not set in stone and could potentially be altered or updated in the future! Do *not* purchase new servers neither migrate your nodes just yet.**
|
||||
</Callout>
|
||||
|
||||
As we finalising last details of *"Project Smoosh"*, where one binary - `nym-node` - can run as an `entry-gateway`, `mixnode` or `exit-gateway` in Mixnet mode as well as `entry-gateway` or `exit-gateway` in Wireguard mode, we plan to step up the game. SGPv2 grants will be higher if operators can meet new requirements.
|
||||
As we finalising last details of *"Project Smoosh"*, where one binary - `nym-node` - can run as an `entry-gateway`, `mixnode` or `exit-gateway` in Mixnet mode as well as `entry-gateway` or `exit-gateway` in Wireguard mode, we plan to step up the game. SGPv2 grants will be higher if operators can meet new requirements.
|
||||
|
||||
**Minimum Specs & Requirements**
|
||||
|
||||
@@ -402,7 +614,7 @@ These are minimum requirements to become a part of SGPv2. We aim to have nodes o
|
||||
|
||||
- [build(deps): bump criterion from `0.4.0` to `0.5.1`](https://github.com/nymtech/nym/pull/4911): Bumps [criterion](https://github.com/bheisler/criterion.rs) from `0.4.0` to `0.5.1`.
|
||||
|
||||
- [NS API: add mixnet scraper](https://github.com/nymtech/nym/pull/5200)
|
||||
- [NS API: add mixnet scraper](https://github.com/nymtech/nym/pull/5200)
|
||||
|
||||
- [build(deps): bump http from `1.1.0` to `1.2.0`](https://github.com/nymtech/nym/pull/5228): Bumps [http](https://github.com/hyperium/http) from `1.1.0` to `1.2.0`.
|
||||
|
||||
@@ -410,41 +622,41 @@ These are minimum requirements to become a part of SGPv2. We aim to have nodes o
|
||||
|
||||
- [Add windows to CI builds](https://github.com/nymtech/nym/pull/5269)
|
||||
|
||||
- [nym topology revamp](https://github.com/nymtech/nym/pull/5271): This PR changes the internals of the `NymTopology` to blur the lines between explicit mixnodes and gateways so that what used to be considered a "mixnode" could be a valid egress point of the network. `NymTopology` is no longer divided into `BTreeMap<MixLayer, Vec<mix::Node>>` and `Vec<gateway::Node>`. instead there's information about the current rewarded set (to support future VRF work) and a simple map of `HashMap<NodeId, RoutingNode>`. The new features are mostly controlled via 2 new flags/config values:
|
||||
- [nym topology revamp](https://github.com/nymtech/nym/pull/5271): This PR changes the internals of the `NymTopology` to blur the lines between explicit mixnodes and gateways so that what used to be considered a "mixnode" could be a valid egress point of the network. `NymTopology` is no longer divided into `BTreeMap<MixLayer, Vec<mix::Node>>` and `Vec<gateway::Node>`. instead there's information about the current rewarded set (to support future VRF work) and a simple map of `HashMap<NodeId, RoutingNode>`. The new features are mostly controlled via 2 new flags/config values:
|
||||
- `use_extended_topology` that tells the client to retrieve **all** network nodes rather than the ones that got assigned "active" mixnode role (or support being a gateway)
|
||||
- `ignore_egress_epoch_role` that tells the client it's fine to construct egress packets to nodes that are **not** assigned entry or exit gateway role
|
||||
|
||||
|
||||
- [Nyx Chain Watcher](https://github.com/nymtech/nym/pull/5274)
|
||||
|
||||
- [Include `IPINFO_API_TOKEN` in nightly CI](https://github.com/nymtech/nym/pull/5285)
|
||||
|
||||
- [Move tun constants to network defaults](https://github.com/nymtech/nym/pull/5286):
|
||||
- [Move tun constants to network defaults](https://github.com/nymtech/nym/pull/5286):
|
||||
<AccordionTemplate name={<TestingSteps/>}>
|
||||
1. **Regression Testing**:
|
||||
- Verified no issues arose when running tests for the affected files.
|
||||
- Tested TUN behaviour with new nym-nodes in the hu branch.
|
||||
|
||||
**Results**:
|
||||
- Verified no issues arose when running tests for the affected files.
|
||||
- Tested TUN behaviour with new nym-nodes in the hu branch.
|
||||
|
||||
- **No bugs detected**.
|
||||
- Tunnels are functioning as expected, with traffic routing and IP generation working seamlessly.
|
||||
</AccordionTemplate>
|
||||
**Results**:
|
||||
|
||||
- **No bugs detected**.
|
||||
- Tunnels are functioning as expected, with traffic routing and IP generation working seamlessly.
|
||||
</AccordionTemplate>
|
||||
|
||||
- [Add dependabot assignes for the root cargo ecosystem](https://github.com/nymtech/nym/pull/5297)
|
||||
|
||||
- [build(deps): bump the patch-updates group across 1 directory with 35 updates](https://github.com/nymtech/nym/pull/5310): Bumps the `patch-updates` group with 33 updates
|
||||
|
||||
- [Periodically remove stale gateway messages](https://github.com/nymtech/nym/pull/5312): This PR introduces a simple task that removes gateway messages that haven't been retrieved in (by default) 24h.
|
||||
- [Periodically remove stale gateway messages](https://github.com/nymtech/nym/pull/5312): This PR introduces a simple task that removes gateway messages that haven't been retrieved in (by default) 24h.
|
||||
<AccordionTemplate name={<TestingSteps/>}>
|
||||
**Automation Script for Data Cleanup Validation**
|
||||
|
||||
Test Objective: Validate that the stale message cleanup mechanism in the database correctly removes records older than the configured threshold (24 hours).
|
||||
|
||||
|
||||
Test Setup:
|
||||
1. Environment:
|
||||
* SQLite database
|
||||
* Bash script: used to insert data.
|
||||
|
||||
|
||||
Steps Performed:
|
||||
1. Ran the insert_data.sh script to populate the database with test data:
|
||||
* Recent records inserted successfully.
|
||||
@@ -452,8 +664,8 @@ Steps Performed:
|
||||
3. Confirmed that all 20 records (10 recent + 10 stale) were present.
|
||||
4. Allowed the system to run for 24 hours to trigger the cleanup mechanism.
|
||||
5. Queried the database again after 24 hours: sqlite3 gateway_storage.db "SELECT * FROM message_store;"
|
||||
6.
|
||||
|
||||
6.
|
||||
|
||||
Expected Result:
|
||||
* All stale records (older than 24 hours) should be removed.
|
||||
* Recent records should remain in the database.
|
||||
@@ -464,18 +676,18 @@ Actual Result:
|
||||
|
||||
- [Use expect in geodata test to give error message on failure](https://github.com/nymtech/nym/pull/5314): Keep hitting this error on CI, from what I think is network hickup. But it's hard to tell form the log since the error is swallowed. Explicitly unwrap the result so we get a more detailed error output.
|
||||
<AccordionTemplate name={<TestingSteps/>}>
|
||||
**Quick Code Review**
|
||||
|
||||
**Summary**
|
||||
**Quick Code Review**
|
||||
|
||||
**Summary**
|
||||
1. **CI Workflow**: Adjusted paths to optimise build triggers, avoiding unnecessary CI runs while ensuring coverage for key directories
|
||||
2. **Geolocation Test**: Improved error handling by replacing assertions with `.expect` for clearer debugging in API regression tests
|
||||
|
||||
**Conclusion**
|
||||
|
||||
**Conclusion**
|
||||
|
||||
Regression testing confirms everything works as intended. **Approved**.
|
||||
</AccordionTemplate>
|
||||
|
||||
- [`CancellationToken`-based shutdowns](https://github.com/nymtech/nym/pull/5325): This PR introduces scaffolding for using `CancellationToken` and `TaskTracker` for our graceful shutdowns rather than the existing `TaskClient` and `TaskManager`.
|
||||
- [`CancellationToken`-based shutdowns](https://github.com/nymtech/nym/pull/5325): This PR introduces scaffolding for using `CancellationToken` and `TaskTracker` for our graceful shutdowns rather than the existing `TaskClient` and `TaskManager`.
|
||||
|
||||
- [Introduce `/load` endpoint for self-reported quantised Nym Node load](https://github.com/nymtech/nym/pull/5326): This PR introduces a new `/load` endpoint on a `NymNode` to return its current load. It returns the following data:
|
||||
```rust
|
||||
@@ -505,7 +717,7 @@ The actual values for`NodeLoad` are determined as follows:
|
||||
- Thus we calculate two additional auxiliary `Load` values, for memory usage and swap usage, i.e.: `used_memory / total_memory` and `used_swap / total_swap` respectively.
|
||||
- Then we check whether either of the `MemoryLoad` or `SwapLoad` is bigger than the current base `Load` of the machine we have determined, if so, it's increased by one tier / bucket. For example, say the current machine load is `Load::Low`, but the memory usage is at 90% (`Load::VeryHigh`). that would result in the reported `Load` being bumped up to `Load::Medium` instead. The same logic applies with swap load, **however, only if the total swap > 1GB**. this is to prevent weird edge cases where the machine has hardly any swap.
|
||||
- Finally, the `.total` `Load` uses the same "tier bumping" behaviour using the `.total` and `.network` loads, i.e. `if network > machine`, then `total = machine + 1`. for example if `machine` `Load` is `Load::Low`, but `network` `Load` is `Load::Medium`, then the `total` `Load` is set to `Load::Medium` instead.
|
||||
</AccordionTemplate>
|
||||
</AccordionTemplate>
|
||||
|
||||
- [Bump the `patch-updates` group with 8 updates](https://github.com/nymtech/nym/pull/5336)
|
||||
|
||||
@@ -517,29 +729,29 @@ The actual values for`NodeLoad` are determined as follows:
|
||||
|
||||
- [Bump mikefarah/yq from `4.44.6` to `4.45.1`](https://github.com/nymtech/nym/pull/5342)
|
||||
|
||||
- [Update `indexed_db_futures`](https://github.com/nymtech/nym/pull/5347): Updates the `indexed_db_futures` dependency to stop relying on the fork.
|
||||
- [Update `indexed_db_futures`](https://github.com/nymtech/nym/pull/5347): Updates the `indexed_db_futures` dependency to stop relying on the fork.
|
||||
|
||||
- [Refresh wasm sdk](https://github.com/nymtech/nym/pull/5353): This PR refreshes the wasm clients to make them usable in the current network
|
||||
|
||||
- [Client gateway selection](https://github.com/nymtech/nym/pull/5358): Changed how gateway is selected.
|
||||
- For init the target gateway can't support mixing
|
||||
- For egress, unless `ignore_epoch_roles` is specified, the gateway can't be currently assigned to a mixing layer. But it can be standby or inactive
|
||||
|
||||
|
||||
- [Exposed `NymApiClient` method for obtaining node performance history](https://github.com/nymtech/nym/pull/5360)
|
||||
|
||||
- [Bind to `[::]` on `nym-node` for both IP versions](https://github.com/nymtech/nym/pull/5361)
|
||||
<AccordionTemplate name={<TestingSteps/>}>
|
||||
**IPv4 Configuration and Migration Testing**
|
||||
|
||||
|
||||
Testing Steps:
|
||||
- Initiated and ran a nym-node with version 1.3.1 on an IPv4-only machine, then updated it to the new 'hu', 1.4.0 version.
|
||||
- Initiated and ran a new nym-node with 'hu', 1.4.0 always on a machine with only IPv4.
|
||||
- Initiated and ran a new nym-node with 'hu', 1.4.0 on a machine with both IPv4 and IPv6 for regression testing.
|
||||
|
||||
|
||||
Results:
|
||||
- No functional issues during version updates.
|
||||
- Logged message on all versions: "no registered client for destination: ff02::2"
|
||||
|
||||
|
||||
Status: Pass
|
||||
</ AccordionTemplate>
|
||||
|
||||
@@ -554,10 +766,10 @@ Status: Pass
|
||||
- Ensured a client is able to select a node which aside from a gateway, can also act as a mixnode
|
||||
- Verified 'ignore_epoch_roles' is the default mode
|
||||
- _note; this is most likely not going to be a permanent solution_
|
||||
|
||||
|
||||
**Results:**
|
||||
- Clients and topology are behaving correctly
|
||||
|
||||
- Clients and topology are behaving correctly
|
||||
|
||||
Status: Pass
|
||||
</ AccordionTemplate>
|
||||
|
||||
@@ -612,7 +824,7 @@ We are developing a design where operators can enable multiple modes, and let th
|
||||
- Moved top level `authenticator` section to `service_providers` so that it'd live alongside NR and IPR
|
||||
- Added general `debug` section
|
||||
- Added `metrics` section
|
||||
- All documentation migrated to a new URL [nym.com/docs](https://nym.com/docs) alongside the rebranding of Nym organisation.
|
||||
- All documentation migrated to a new URL [nym.com/docs](https://nym.com/docs) alongside the rebranding of Nym organisation.
|
||||
- Updated [network architecture diagrams](https://nym.com/docs/network/architecture)
|
||||
- New blow-by-blow mixnet [traffic flow](https://nym.com/docs/network/architecture) section
|
||||
- [Winter Nym Squad League started](https://forum.nym.com/t/nym-squad-league-farewell-fall-welcome-winter/977)
|
||||
@@ -648,11 +860,11 @@ We are developing a design where operators can enable multiple modes, and let th
|
||||
- Everything in `mixnode` directory has been removed because there was nothing really left there. The mixing socket listener was unified in `nym-node` and similarly `verloc` was also moved there
|
||||
- `gateway` directory was similarly reduced in size. Now it also creates appropriate tasks as opposed to the whole gateway process. eventually it might also be further stripped, but today is not that day.
|
||||
- Removed the generic parameter on the `GatewayStorage` to simplify all the generics down the stack. it wasn't used anyway
|
||||
|
||||
|
||||
CLI:
|
||||
- Added `--modes` argument to specify all node modes with a single command (or `env` variable). for example: `--modes="mixnode,entry"`. Can't be used alongside `--modes`
|
||||
- Extended `--mode` argument to allow specifying it multiple times, for example: `--mode mixnode --mode entry`. can't be used alongside `--mode`
|
||||
|
||||
|
||||
Config changes:
|
||||
- Replaced `mode` with `modes` to allow setting the node to run with say, `entry` + `mixnode` roles simultaneously
|
||||
- Added `maximum_forward_packet_delay` to `mixnet.debug` section
|
||||
@@ -677,17 +889,17 @@ Config changes:
|
||||
- [Remove unneeded async function annotation](https://github.com/nymtech/nym/pull/5246)
|
||||
- [Add control messages to `GatewayTransciver`](https://github.com/nymtech/nym/pull/5247)
|
||||
<AccordionTemplate name={<TestingSteps/>}>
|
||||
**Review and Testing: Forget Me Implementation**
|
||||
**Review and Testing: Forget Me Implementation**
|
||||
|
||||
- Validated the encryption and delivery of `ForgetMe` control messages to the gateway
|
||||
|
||||
**Testing: MixTrafficController Integration**
|
||||
- Validated the encryption and delivery of `ForgetMe` control messages to the gateway
|
||||
|
||||
- Verified that the `MixTrafficController` invokes `ForgetMe` logic correctly during shutdown
|
||||
- Tested behaviour for gateway transceiver failures while sending control messages
|
||||
|
||||
**Testing: Gateway Storage Updates**
|
||||
- Confirmed successful deletion of client data (e.g., inbox messages, bandwidth allocations) from persistent storage
|
||||
**Testing: MixTrafficController Integration**
|
||||
|
||||
- Verified that the `MixTrafficController` invokes `ForgetMe` logic correctly during shutdown
|
||||
- Tested behaviour for gateway transceiver failures while sending control messages
|
||||
|
||||
**Testing: Gateway Storage Updates**
|
||||
- Confirmed successful deletion of client data (e.g., inbox messages, bandwidth allocations) from persistent storage
|
||||
</AccordionTemplate>
|
||||
|
||||
- [Add conversion unit tests for auth msg](https://github.com/nymtech/nym/pull/5251)
|
||||
@@ -704,13 +916,13 @@ Config changes:
|
||||
<AccordionTemplate name={<TestingSteps/>}>
|
||||
1. **Review File: `common/credential-storage/src/backends/sqlite.rs`**
|
||||
- Verified addition of `close` method for the SQLite backend
|
||||
|
||||
|
||||
2. **Review File: `common/credential-storage/src/ephemeral_storage.rs`**
|
||||
- Confirmed addition of `close` method for ephemeral storage with no action required
|
||||
|
||||
|
||||
3. **Review File: `common/credential-storage/src/persistent_storage/mod.rs`**
|
||||
- Ensured `close` method integration for persistent storage
|
||||
|
||||
|
||||
4. **Review File: `common/credential-storage/src/storage.rs`**
|
||||
- Verified updates to the `Storage` trait to include `close` and `cleanup_expired` methods
|
||||
</AccordionTemplate>
|
||||
@@ -719,15 +931,15 @@ Config changes:
|
||||
<AccordionTemplate name={<TestingSteps/>}>
|
||||
1. **Review File: `common/network-defaults/src/constants.rs`**
|
||||
- Confirmed updated `mixnet_vpn` constants were added.
|
||||
|
||||
|
||||
2. **Review File: `service-providers/ip-packet-router/src/constants.rs`**
|
||||
- Checked replacement of legacy `TUN_*` constants with new `mixnet_vpn` constants.
|
||||
- Validated alignment of routing traffic configurations.
|
||||
|
||||
|
||||
3. **Review File: `service-providers/ip-packet-router/src/ip_packet_router.rs`**
|
||||
- Ensured new `nym_network_defaults::constants::mixnet_vpn` constants replaced old references.
|
||||
- Verified `TunDeviceConfig` consistency.
|
||||
|
||||
|
||||
4. **Review File: `service-providers/ip-packet-router/src/util/generate_new_ip.rs`**
|
||||
- Confirmed substitution of `TUN_DEVICE_*` constants with `NYM_TUN_DEVICE_*` constants.
|
||||
- Tested functionality for generating random IPs within subnet.
|
||||
@@ -743,54 +955,54 @@ Config changes:
|
||||
- `nym_node_mixnet_ingress_excessive_delay_packets`
|
||||
- `nym_node_mixnet_ingress_forward_hop_packets_dropped`
|
||||
- `nym_node_mixnet_ingress_final_hop_packets_dropped`
|
||||
|
||||
|
||||
- `nym_node_mixnet_ingress_forward_hop_packets_received_rate`
|
||||
- `nym_node_mixnet_ingress_final_hop_packets_received_rate`
|
||||
- `nym_node_mixnet_ingress_malformed_packets_received_rate`
|
||||
- `nym_node_mixnet_ingress_excessive_delay_packets_rate`
|
||||
- `nym_node_mixnet_ingress_final_hop_packets_received_rate`
|
||||
- `nym_node_mixnet_ingress_malformed_packets_received_rate`
|
||||
- `nym_node_mixnet_ingress_excessive_delay_packets_rate`
|
||||
- `nym_node_mixnet_ingress_forward_hop_packets_dropped_rate`
|
||||
- `nym_node_mixnet_ingress_final_hop_packets_dropped_rate`
|
||||
|
||||
|
||||
- egress:
|
||||
- `nym_node_mixnet_egress_stored_on_disk_final_hop_packets`
|
||||
- `nym_node_mixnet_egress_forward_hop_packets_sent`
|
||||
- `nym_node_mixnet_egress_ack_packets_sent`
|
||||
- `nym_node_mixnet_egress_forward_hop_packets_dropped`
|
||||
|
||||
|
||||
- `nym_node_mixnet_egress_forward_hop_packets_sent_rate`
|
||||
- `nym_node_mixnet_egress_ack_packets_sent_rate`
|
||||
- `nym_node_mixnet_egress_forward_hop_packets_dropped_rate`
|
||||
|
||||
|
||||
- client sessions
|
||||
- `nym_node_entry_client_sessions_unique_users`
|
||||
- `nym_node_entry_client_sessions_sessions_started`
|
||||
- `nym_node_entry_client_sessions_finished_sessions`
|
||||
- `nym_node_entry_client_sessions_durations_{TYP}` (histogram), for example `nym_node_entry_client_sessions_durations_vpn`
|
||||
|
||||
|
||||
- wireguard:
|
||||
- `nym_node_wireguard_bytes_rx`
|
||||
- `nym_node_wireguard_bytes_tx`
|
||||
- `nym_node_wireguard_bytes_total_peers`
|
||||
- `nym_node_wireguard_bytes_active_peers`
|
||||
|
||||
|
||||
- `nym_node_wireguard_bytes_rx_rate`
|
||||
- `nym_node_wireguard_bytes_tx_rate`
|
||||
|
||||
|
||||
|
||||
|
||||
- network
|
||||
- `nym_node_network_active_ingress_mixnet_connections`
|
||||
- `nym_node_network_active_ingress_web_socket_connections`
|
||||
- `nym_node_network_active_egress_mixnet_connections`
|
||||
|
||||
|
||||
- process
|
||||
- `nym_node_process_forward_hop_packets_being_delayed`
|
||||
- `nym_node_process_packet_forwarder_queue_size`
|
||||
- `nym_node_process_topology_query_resolution_latency` (histogram)
|
||||
- `nym_node_process_final_hop_packets_pending_delivery`
|
||||
- `nym_node_process_forward_hop_packets_pending_delivery`
|
||||
- `nym_node_process_forward_hop_packets_pending_delivery`
|
||||
</AccordionTemplate>
|
||||
|
||||
- [Amend 250gb limit](https://github.com/nymtech/nym/pull/5313): Change bandwidth cap to 250gb
|
||||
- [Amend 250gb limit](https://github.com/nymtech/nym/pull/5313): Change bandwidth cap to 250gb
|
||||
- [Warn users if node is run in exit mode only](https://github.com/nymtech/nym/pull/5320): Throws a warning if node is run in "exit" mode only as by default, this will **NOT** enable entry capabilities, i.e. opening the websocket. thus making it ineligible for rewarded set selection (and rewards)
|
||||
- [Reduce log severity for number of packets being delayed](https://github.com/nymtech/nym/pull/5321)
|
||||
- [Apply 1.84 linter suggestions](https://github.com/nymtech/nym/pull/5330)
|
||||
@@ -806,7 +1018,7 @@ Config changes:
|
||||
- [Make sure to apply gateway score filtering when choosing initial node](https://github.com/nymtech/nym/pull/5256)
|
||||
- [Fixed client session histogram buckets](https://github.com/nymtech/nym/pull/5316)
|
||||
- [Contract version assignment](https://github.com/nymtech/nym/pull/5318): This PR fixes updates to current nym-node version as well as introduces migration to fix the existing state of the mainnet contract
|
||||
- [Make sure refresh data key matches bond info](https://github.com/nymtech/nym/pull/5329): This is to forbid operators from reusing the same underlying identity key for multiple nodes by overwriting the describe data. the reported key has to always match what the node has bonded with (and the contract enforces uniqueness)
|
||||
- [Make sure refresh data key matches bond info](https://github.com/nymtech/nym/pull/5329): This is to forbid operators from reusing the same underlying identity key for multiple nodes by overwriting the describe data. the reported key has to always match what the node has bonded with (and the contract enforces uniqueness)
|
||||
|
||||
## Archived Changelog
|
||||
|
||||
|
||||
@@ -128,10 +128,15 @@ mkdir -pv <PATH_TO_TARGET_DIRECTORY>
|
||||
|
||||
###### 2. Backup `clients.sqlite` database
|
||||
|
||||
|
||||
- Install `sqlite3`
|
||||
```sh
|
||||
apt install sqlite3
|
||||
```
|
||||
- **Stop your node**
|
||||
```sh
|
||||
service nym-node stop
|
||||
```
|
||||
- Open sqlite CLI shell inside `clients.sqlite` database
|
||||
```sh
|
||||
sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite
|
||||
@@ -151,10 +156,14 @@ sqlite3 ~/.nym/nym-nodes/default-nym-node/data/clients.sqlite
|
||||
scp -r <SOURCE_USER_NAME>@<SOURCE_HOST_ADDRESS>:~/.nym/nym-nodes/<ID> <PATH_TO_TARGET_DIRECTORY>
|
||||
```
|
||||
|
||||
###### 4. Verify the success of the backup
|
||||
###### 4. Verify the success of the backup & start your node
|
||||
|
||||
The `scp` command should print logs, an operator can see directly whether it was successful or if it encountered any error. However, double check that all your needed configuration is in the backup target directory.
|
||||
|
||||
- Start your node
|
||||
```sh
|
||||
service nym-node start && journalctl -u nym-node -f
|
||||
```
|
||||
</Steps>
|
||||
|
||||
Now you have everything needed to restore your `nym-node` on another server. If you are in a need of doing so, follow the steps in [*Restoring a node*](#restoring-a-node) chapter below.
|
||||
@@ -360,7 +369,7 @@ mkdir ~/.nym/nym-nodes
|
||||
```
|
||||
###### 2. Backup `clients.sqlite` database
|
||||
|
||||
- Stop your node
|
||||
- **Stop your node**
|
||||
```sh
|
||||
service nym-node stop
|
||||
```
|
||||
|
||||
@@ -20,12 +20,12 @@ This documentation page provides a guide on how to set up and run a [NYM NODE](.
|
||||
```sh
|
||||
nym-node
|
||||
Binary Name: nym-node
|
||||
Build Timestamp: 2025-02-13T11:49:34.670488195Z
|
||||
Build Version: 1.5.0
|
||||
Commit SHA: a3e19b4563843055b305ea9a397eb1ad84b5c378
|
||||
Commit Date: 2025-02-10T18:14:47.000000000+01:00
|
||||
Build Timestamp: 2025-03-06T20:32:36.922212778Z
|
||||
Build Version: 1.6.2
|
||||
Commit SHA: 247ebb7c4339de0a298a7fcb2574122a8306c3b8
|
||||
Commit Date: 2025-03-06T21:26:16.000000000+01:00
|
||||
Commit Branch: HEAD
|
||||
rustc Version: 1.84.1
|
||||
rustc Version: 1.85.0
|
||||
rustc Channel: stable
|
||||
cargo Profile: release
|
||||
```
|
||||
|
||||
@@ -3,8 +3,9 @@ import { VarInfo } from 'components/variable-info.tsx';
|
||||
import { Steps } from 'nextra/components';import { Tabs } from 'nextra/components';
|
||||
import { MyTab } from 'components/generic-tabs.tsx';
|
||||
import PortsNymNode from 'components/operators/snippets/ports-nym-node.mdx';
|
||||
import PortsValidator from 'components/operators/snippets/ports-validator.mdx'
|
||||
import NymNodeSpecs from 'components/operators/snippets/nym-node-specs.mdx'
|
||||
import PortsValidator from 'components/operators/snippets/ports-validator.mdx';
|
||||
import NymNodeSpecs from 'components/operators/snippets/nym-node-specs.mdx';
|
||||
import NTPSync from 'components/operators/snippets/ntp-time-sync.mdx'
|
||||
|
||||
# VPS Setup & Configuration
|
||||
|
||||
@@ -42,14 +43,18 @@ apt update -y && apt --fix-broken install
|
||||
```
|
||||
- Install dependencies
|
||||
```sh
|
||||
apt -y install ca-certificates jq curl wget ufw jq tmux pkg-config build-essential libssl-dev git
|
||||
apt -y install ca-certificates jq curl wget ufw jq tmux pkg-config build-essential libssl-dev git ntp ntpdate
|
||||
```
|
||||
- Double check ufw is installed correctly
|
||||
```sh
|
||||
apt install ufw --fix-missing
|
||||
```
|
||||
|
||||
###### 2. Configure your firewall using Uncomplicated Firewall (UFW)
|
||||
###### 2. Synchronize time of your server
|
||||
|
||||
<NTPSync />
|
||||
|
||||
###### 3. Configure your firewall using Uncomplicated Firewall (UFW)
|
||||
|
||||
For a `nym-node` or Nyx validator to recieve traffic, you need to open ports on the server. The following commands will allow you to set up a firewall using `ufw`.
|
||||
|
||||
@@ -70,7 +75,7 @@ ufw enable
|
||||
ufw status
|
||||
```
|
||||
|
||||
###### 3. Open all needed ports to have your firewall for `nym-node` working correctly
|
||||
###### 4. Open all needed ports to have your firewall for `nym-node` working correctly
|
||||
|
||||
<div>
|
||||
<Tabs items={[
|
||||
|
||||
@@ -0,0 +1,746 @@
|
||||
|
||||
import { Callout } from 'nextra/components';
|
||||
import { Tabs } from 'nextra/components';
|
||||
import { VarInfo } from 'components/variable-info.tsx';
|
||||
import { Steps } from 'nextra/components';
|
||||
import {Accordion, AccordionItem} from "@nextui-org/react";
|
||||
import { MyTab } from 'components/generic-tabs.tsx';
|
||||
import { AccordionTemplate } from 'components/accordion-template.tsx';
|
||||
|
||||
# Advanced Server Administration
|
||||
|
||||
This page is for experienced operators and aspiring sys-admins who seek for higher optimisation and better efficiency of their work managing Nym infrastructure. The steps shared on this page cannot be simply copy-pasted, they ask you for more attention and consideration all the way from choosing server and OS to specs per VM allocation.
|
||||
|
||||
<VarInfo />
|
||||
|
||||
## Virtualising a Dedicated Server
|
||||
|
||||
Some operators or squads of operators orchestrate multiple Nym nodes. Among other benefits (which are out of scope of this page), these operators can decide to acquire one larger dedicated (or bare-metal) server with enough specs (CPU, RAM, storage, bandwidth and port speed) to meet [minimum requirements](../../../nodes#minimum-requirements) for multiple nodes run in parallel.
|
||||
|
||||
This guide explains how to prepare your server in order to be able to host multiple nodes running on separated VMs.
|
||||
|
||||
<Callout type="info">
|
||||
This guide is based on Ubuntu 22.04, in case you prefer another OS, you may have to do a bit of your own research to troubleshoot networking configuration and other parameters.
|
||||
</Callout>
|
||||
|
||||
### Installing KVM on a Server with Ubuntu 22.04
|
||||
|
||||
**KVM** stands for **Kernel-based Virtual Machine**. It is a virtualization technology for Linux that allows a user to run multiple virtual machines (VMs) on a single physical machine. KVM turns the Linux kernel into a hypervisor, enabling it to manage multiple virtualised systems.
|
||||
|
||||
Follow the steps below to install KVM on Ubuntu 22.04 LTS.
|
||||
|
||||
#### Prerequisites
|
||||
|
||||
<Callout type="warning">
|
||||
Operators aiming to run Nym node as mixnet [Exit Gateway](../../../community-counsel/exit-gateway) or with wireguard enabled should familiarize themselves with the challenges possibly coming along `nym-node` operation, described in our [community counsel](../../../community-counsel) and follow up with [legal suggestions](../../../community-counsel/legal). Particularly important is to [introduce yourself](../../../community-counsel/legal#introduce-nym-node-to-your-provider) and your intentions to run a Nym node to your provider.
|
||||
|
||||
This step is essential part of legal self defense because it may prevent your provider immediately shutting down your entire service (with all the VMs on it) when receiving first abuse report.
|
||||
|
||||
Additionally, before purchasing a large server, **contact the provider and ask if the offered CPU supports Virtualization Technology (VT)**, without this feature you will not be able to proceed.
|
||||
</Callout>
|
||||
|
||||
Start with obtaining a server with Ubuntu 22.04 LTS:
|
||||
- Make sure that your server meets [minimum requirements](../vps-setup#nym-node---dedicated-server) multiplied by number of `nym-node` instance you aim to run on it.
|
||||
- Most people rent a server from a provider and it comes with a pre-installed OS (in this guide we use Ubuntu 22.04). In case your choice is a bare-metal machine, you probably know what you are doing, there are some useful guides to install a new OS, like [this one on ostechnix.com](https://ostechnix.com/install-ubuntu-server/).
|
||||
|
||||
Make sure thay your system actually supports hardware virtualisation:
|
||||
- Check out the methods documented in [this guide by ostechnix.com](https://ostechnix.com/how-to-find-if-a-cpu-supports-virtualization-technology-vt/).
|
||||
|
||||
Order enough IPv4 and IPv6 (static and public) addresses to have one of each for each planned VM plus one extra for the main machine.
|
||||
|
||||
|
||||
When you have your OS installed, validated CPU virtualisation support and obtained IP addresses, you can start configuring your VMs, following the steps below.
|
||||
|
||||
> Note that the commands below require root permission. You can either go through the setup as `root` or use `sudo` prefix with the commands used in the guide. You can switch to `root` shell by entering one of these commands `sudo su` or `sudo -i`.
|
||||
<Steps>
|
||||
|
||||
##### 1. Install KVM
|
||||
|
||||
- Install KVM and required components:
|
||||
```sh
|
||||
apt install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virtinst
|
||||
```
|
||||
<br/>
|
||||
<AccordionTemplate name="Component breakdown">
|
||||
- `qemu-kvm`: Provides the core **KVM virtualization** support using QEMU.
|
||||
- `libvirt-daemon-system`: Manages virtual machines via the **libvirt daemon**.
|
||||
- `libvirt-clients` Provides command-line tools like `virsh` to manage VMs.
|
||||
- `bridge-utils`: Enables **network bridging**, allowing VMs to communicate over the network.
|
||||
- `virtinst`: Includes `virt-install` for **creating virtual machines** via CLI.
|
||||
</AccordionTemplate>
|
||||
|
||||
- Start the `libvertd` service:
|
||||
```sh
|
||||
systemctl enable libvirtd
|
||||
systemctl start libvirtd
|
||||
```
|
||||
- Validate by checking status of `libvirt` service:
|
||||
```sh
|
||||
systemctl status libvirtd
|
||||
```
|
||||
<br/>
|
||||
<AccordionTemplate name="Console output">
|
||||
The command output should look similar to this one:
|
||||
```
|
||||
root@nym-exit:~# systemctl status libvirtd
|
||||
● libvirtd.service - Virtualization daemon
|
||||
Loaded: loaded (/lib/systemd/system/libvirtd.service; enabled; vendor preset: enabled)
|
||||
Active: active (running) since Thu 2025-02-27 14:25:28 MSK; 2min 1s ago
|
||||
TriggeredBy: ● libvirtd-ro.socket
|
||||
● libvirtd.socket
|
||||
● libvirtd-admin.socket
|
||||
Docs: man:libvirtd(8)
|
||||
https://libvirt.org
|
||||
Main PID: 6232 (libvirtd)
|
||||
Tasks: 21 (limit: 32768)
|
||||
Memory: 11.8M
|
||||
CPU: 852ms
|
||||
CGroup: /system.slice/libvirtd.service
|
||||
├─6232 /usr/sbin/libvirtd
|
||||
├─6460 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf --leasefile-ro --dhcp-script=/usr/lib/libvirt/libvirt_leaseshelper
|
||||
└─6461 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf --leasefile-ro --dhcp-script=/usr/lib/libvirt/libvirt_leaseshelper
|
||||
|
||||
Feb 27 14:25:28 nym-exit.example.com systemd[1]: Started Virtualization daemon.
|
||||
Feb 27 14:25:30 nym-exit.example.com dnsmasq[6460]: started, version 2.90 cachesize 150
|
||||
Feb 27 14:25:30 nym-exit.example.com dnsmasq[6460]: compile time options: IPv6 GNU-getopt DBus no-UBus i18n IDN2 DHCP DHCPv6 no-Lua TFTP conntrack ipset no-nftset auth cryptohash DNSSEC loop-detect inotify dump>
|
||||
Feb 27 14:25:30 nym-exit.example.com dnsmasq-dhcp[6460]: DHCP, IP range 192.168.122.2 -- 192.168.122.254, lease time 1h
|
||||
Feb 27 14:25:30 nym-exit.example.com dnsmasq-dhcp[6460]: DHCP, sockets bound exclusively to interface virbr0
|
||||
Feb 27 14:25:30 nym-exit.example.com dnsmasq[6460]: reading /etc/resolv.conf
|
||||
Feb 27 14:25:30 nym-exit.example.com dnsmasq[6460]: using nameserver 127.0.0.53#53
|
||||
Feb 27 14:25:30 nym-exit.example.com dnsmasq[6460]: read /etc/hosts - 8 names
|
||||
Feb 27 14:25:30 nym-exit.example.com dnsmasq[6460]: read /var/lib/libvirt/dnsmasq/default.addnhosts - 0 names
|
||||
Feb 27 14:25:30 nym-exit.example.com dnsmasq-dhcp[6460]: read /var/lib/libvirt/dnsmasq/default.hostsfile
|
||||
```
|
||||
</AccordionTemplate>
|
||||
|
||||
- In case you don't configure KVM as `root`, add your current user to the `kvm` and `libvirt` groups to enable VM creation and management using the `virsh` command-line tool or the `virt-manager` GUI:
|
||||
```bash
|
||||
usermod -aG kvm $USER
|
||||
usermod -aG libvirt $USER
|
||||
```
|
||||
|
||||
##### 2. Setup Bridge Networking with KVM
|
||||
|
||||
A **bridged network** lets VMs share the host’s network interface, allowing direct IPv4/IPv6 access like a physical machine.
|
||||
|
||||
By default, KVM sets up a **private virtual bridge**, enabling VM-to-VM communication within the host. It provides its own subnet, DHCP, and NAT for external access.
|
||||
|
||||
Check the IP of KVM’s default virtual interfaces with:
|
||||
|
||||
```bash
|
||||
ip a
|
||||
```
|
||||
<br/>
|
||||
<AccordionTemplate name="Console output">
|
||||
The command output should look similar to this one:
|
||||
```
|
||||
root@nym-exit:~# ip a
|
||||
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
|
||||
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
|
||||
inet 127.0.0.1/8 scope host lo
|
||||
valid_lft forever preferred_lft forever
|
||||
inet6 ::1/128 scope host
|
||||
valid_lft forever preferred_lft forever
|
||||
2: eno1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
|
||||
link/ether 14:02:ec:35:2e:14 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp2s0f0
|
||||
3: eno49: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
|
||||
link/ether 38:63:bb:2e:9d:20 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp4s0f0
|
||||
inet 31.222.238.222/24 brd 31.222.238.255 scope global eno49
|
||||
valid_lft forever preferred_lft forever
|
||||
inet6 fe80::3a63:bbff:fe2e:9d20/64 scope link
|
||||
valid_lft forever preferred_lft forever
|
||||
4: eno2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
|
||||
link/ether 14:02:ec:35:2e:15 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp2s0f1
|
||||
5: eno3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
|
||||
link/ether 14:02:ec:35:2e:16 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp2s0f2
|
||||
6: eno50: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
|
||||
link/ether 38:63:bb:2e:9d:24 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp4s0f1
|
||||
7: eno4: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
|
||||
link/ether 14:02:ec:35:2e:17 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp2s0f3
|
||||
8: virbr0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
|
||||
link/ether 52:54:00:ac:d3:ba brd ff:ff:ff:ff:ff:ff
|
||||
inet 192.168.122.1/24 brd 192.168.122.255 scope global virbr0
|
||||
valid_lft forever preferred_lft forever
|
||||
```
|
||||
</AccordionTemplate>
|
||||
|
||||
|
||||
By default, KVM uses the `virbr0` network with `<IPv4_ADDRESS>.1/24`, assigning guest VMs IPs in the `<IPv4_ADDRESS>.0/24` range. The host OS is reachable at `<IPv4_ADDRESS>.1`, allowing SSH and file transfers (`scp`) between the host and guests.
|
||||
|
||||
This setup works if you only access VMs from the host. However, remote systems on a different subnet (e.g., `<IPv4_ADDRESS_ALT>.0/24`) **cannot** reach the VMs.
|
||||
|
||||
To enable external access, we need a *public bridge* that connects VMs to the host’s main network, using its DHCP. This ensures VMs get IPs in the same range as the host.
|
||||
|
||||
Before configuring a public bridge, **disable Netfilter** on bridges for better performance and security, as it is enabled by default.
|
||||
|
||||
- Create a file located at `/etc/sysctl.d/bridge.conf`:
|
||||
```bash
|
||||
nano /etc/sysctl.d/bridge.conf
|
||||
|
||||
# in case of using custom editor, replace nano in the syntax
|
||||
```
|
||||
|
||||
- Paste inside the following block, save and exit:
|
||||
```ini
|
||||
net.bridge.bridge-nf-call-ip6tables=0
|
||||
net.bridge.bridge-nf-call-iptables=0
|
||||
net.bridge.bridge-nf-call-arptables=0
|
||||
```
|
||||
|
||||
- Create a file `/etc/udev/rules.d/99-bridge.rules`:
|
||||
```bash
|
||||
nano /etc/udev/rules.d/99-bridge.rules
|
||||
```
|
||||
|
||||
- Paste this line, save and exit:
|
||||
```bash
|
||||
ACTION=="add", SUBSYSTEM=="module", KERNEL=="br_netfilter", RUN+="/sbin/sysctl -p /etc/sysctl.d/bridge.conf"
|
||||
```
|
||||
|
||||
This disables Netfilter on bridges at startup. Save, exit, and reboot to apply changes.
|
||||
|
||||
- Disable KVM’s default networking. Find the default network interface with:
|
||||
```bash
|
||||
ip link
|
||||
```
|
||||
|
||||
<br/>
|
||||
<AccordionTemplate name="Console output">
|
||||
The command output should look similar to this one:
|
||||
```
|
||||
root@nym-exit:~# ip link
|
||||
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
|
||||
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
|
||||
2: eno1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 14:02:ec:35:2e:14 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp2s0f0
|
||||
3: eno2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 14:02:ec:35:2e:15 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp2s0f1
|
||||
4: eno49: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
|
||||
link/ether 38:63:bb:2e:9d:20 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp4s0f0
|
||||
5: eno3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 14:02:ec:35:2e:16 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp2s0f2
|
||||
6: eno50: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 38:63:bb:2e:9d:24 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp4s0f1
|
||||
7: eno4: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 14:02:ec:35:2e:17 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp2s0f3
|
||||
8: virbr0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 52:54:00:ac:d3:ba brd ff:ff:ff:ff:ff:ff
|
||||
```
|
||||
|
||||
The `virbr0` interface is KVM’s default network. Note your physical interface’s MAC address (e.g., `eno49`). It's the only interface that is currently `UP` and running (`LOWER_UP` state). Other interfaces are `DOWN` and not in use.
|
||||
</AccordionTemplate>
|
||||
|
||||
- Remove the default KVM network:
|
||||
```bash
|
||||
virsh net-destroy default
|
||||
```
|
||||
|
||||
- Remove the default network configuration:
|
||||
```bash
|
||||
virsh net-undefine default
|
||||
```
|
||||
|
||||
- In case last two commands didn't work, try this:
|
||||
```bash
|
||||
ip link delete virbr0 type bridge
|
||||
```
|
||||
- Verify that the `virbr0` and `virbr0-nic` interfaces are deleted:
|
||||
```bash
|
||||
ip link
|
||||
```
|
||||
<AccordionTemplate name="Console output">
|
||||
The command output should look similar to this one:
|
||||
```
|
||||
root@nym-exit:~# ip link
|
||||
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
|
||||
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
|
||||
2: eno1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 14:02:ec:35:2e:14 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp2s0f0
|
||||
3: eno2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 14:02:ec:35:2e:15 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp2s0f1
|
||||
4: eno49: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
|
||||
link/ether 38:63:bb:2e:9d:20 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp4s0f0
|
||||
5: eno3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 14:02:ec:35:2e:16 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp2s0f2
|
||||
6: eno50: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 38:63:bb:2e:9d:24 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp4s0f1
|
||||
7: eno4: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
|
||||
link/ether 14:02:ec:35:2e:17 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp2s0f3
|
||||
```
|
||||
KVM network is gone.
|
||||
</AccordionTemplate>
|
||||
|
||||
|
||||
##### 3. Setup KVM public bridge for new VMs
|
||||
|
||||
To create a KVM network bridge on Ubuntu, edit a config file located in `/etc/netplan/` either called `00-installer.yaml` or `00-installer-config.yaml` and add the bridge details.
|
||||
|
||||
- Before you edit the file, make a backup to stay on the save side:
|
||||
```bash
|
||||
cp /etc/netplan/00-installer-config.yaml /etc/netplan/00-installer-config.yaml.bak
|
||||
# or
|
||||
cp /etc/netplan/00-installer.yaml /etc/netplan/00-installer.yaml.bak
|
||||
```
|
||||
|
||||
- Open `00-installer-config.yaml` or `00-installer.yaml.`config in a text editor:
|
||||
```bash
|
||||
nano /etc/netplan/00-installer.yaml
|
||||
# or
|
||||
nano /etc/netplan/00-installer-config.yaml
|
||||
```
|
||||
|
||||
- Edit the block below and paste it to the config file, save and exit:
|
||||
```ini
|
||||
#####################################################
|
||||
######## CHANGE ALL VARIABLES IN <> BRACKETS ########
|
||||
#####################################################
|
||||
|
||||
# <INTERFACE> is your own one, you can get with command ip link show
|
||||
# <HOST> is your server main IPv4 address
|
||||
# <GATEWAY> value can be found by running: ip route | grep default
|
||||
|
||||
|
||||
# This is the network config written by 'subiquity'
|
||||
network:
|
||||
version: 2
|
||||
ethernets:
|
||||
<INTERFACE>:
|
||||
dhcp4: false
|
||||
dhcp6: false
|
||||
|
||||
# Bridge interface configuration
|
||||
bridges:
|
||||
br0:
|
||||
interfaces: [<INTERFACE>]
|
||||
addresses: [<HOST>/24]
|
||||
routes:
|
||||
- to: default
|
||||
via: <GATEWAY>
|
||||
mtu: 1500
|
||||
nameservers:
|
||||
addresses:
|
||||
- 8.8.8.8
|
||||
- 1.1.1.1
|
||||
- 77.88.8.8
|
||||
parameters:
|
||||
stp: false # Disable STP unless multiple bridges exist
|
||||
forward-delay: 15 # Can be shortened, 15 sec is a common default
|
||||
```
|
||||
|
||||
<Callout type="warning">
|
||||
Ensure the indentation matches exactly as shown above. Incorrect spacing will prevent the bridged network interface from activating.
|
||||
</Callout>
|
||||
|
||||
- Validate `netplan` configuration without applying to prevent breaking network changes:
|
||||
```bash
|
||||
netplan generate
|
||||
|
||||
# Correct configuration output will show nothing
|
||||
```
|
||||
|
||||
- Safety test your changes to catch syntax errors before applying:
|
||||
```bash
|
||||
netplan try
|
||||
```
|
||||
|
||||
- Apply your changes:
|
||||
```bash
|
||||
netplan --debug apply
|
||||
```
|
||||
|
||||
- In case of proubems try some of these steps:
|
||||
<AccordionTemplate name="Netplan configuration troubleshooting">
|
||||
- Validate YAML configuration, given that YAML is syntax sensitive:
|
||||
```bash
|
||||
apt install yamllint -y
|
||||
|
||||
yamllint /etc/netplan/00-installer.yaml
|
||||
# or
|
||||
yamllint /etc/netplan/00-installer-config.yaml
|
||||
|
||||
|
||||
```
|
||||
- Apply correct permissions:
|
||||
```bash
|
||||
chmod 600 /etc/netplan/00-installer.yaml
|
||||
chown root:root /etc/netplan/00-installer.yaml
|
||||
```
|
||||
|
||||
- Manually bring up the bridge:
|
||||
```bash
|
||||
ip link add name br0 type bridge
|
||||
ip link set br0 up
|
||||
ip a show br0
|
||||
```
|
||||
|
||||
- ensure `systemd-networkd` is enabled:
|
||||
```bash
|
||||
systemctl restart systemd-networkd
|
||||
systemctl status systemd-networkd
|
||||
# if inactive, enable it:
|
||||
systemctl enable --now systemd-networkd
|
||||
```
|
||||
</AccordionTemplate>
|
||||
|
||||
- If things went wrong, you can always revert from the backed up file:
|
||||
```bash
|
||||
cp /etc/netplan/00-installer-config.yaml.bak /etc/netplan/00-installer-config.yaml
|
||||
# or
|
||||
cp /etc/netplan/00-installer.yaml.bak /etc/netplan/00-installer.yaml
|
||||
# and
|
||||
netplan apply
|
||||
```
|
||||
|
||||
<Callout type="warning">
|
||||
Using different IPs for your physical NIC and KVM bridge will disconnect SSH when applying changes. Reconnect using the bridge's new IP. If both share the same IP, no disruption occurs.
|
||||
</Callout>
|
||||
|
||||
|
||||
- Verify that the IP address has been assigned to the bridge interface:
|
||||
```bash
|
||||
ip a
|
||||
```
|
||||
<AccordionTemplate name="Console output">
|
||||
The command output should look similar to this one:
|
||||
```
|
||||
root@nym-exit:~# ip a
|
||||
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
|
||||
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
|
||||
inet 127.0.0.1/8 scope host lo
|
||||
valid_lft forever preferred_lft forever
|
||||
inet6 ::1/128 scope host
|
||||
valid_lft forever preferred_lft forever
|
||||
2: eno1: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
|
||||
link/ether 14:02:ec:35:2e:14 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp2s0f0
|
||||
3: eno2: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
|
||||
link/ether 14:02:ec:35:2e:15 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp2s0f1
|
||||
4: eno3: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
|
||||
link/ether 14:02:ec:35:2e:16 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp2s0f2
|
||||
5: eno49: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq master br0 state UP group default qlen 1000
|
||||
link/ether 38:63:bb:2e:9d:20 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp4s0f0
|
||||
6: eno4: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
|
||||
link/ether 14:02:ec:35:2e:17 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp2s0f3
|
||||
7: eno50: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
|
||||
link/ether 38:63:bb:2e:9d:24 brd ff:ff:ff:ff:ff:ff
|
||||
altname enp4s0f1
|
||||
8: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
|
||||
link/ether 46:50:aa:c0:49:a5 brd ff:ff:ff:ff:ff:ff
|
||||
inet 31.222.238.222/24 brd 31.222.238.255 scope global br0
|
||||
valid_lft forever preferred_lft forever
|
||||
inet6 fe80::4450:aaff:fec0:49a5/64 scope link
|
||||
valid_lft forever preferred_lft forever
|
||||
```
|
||||
The bridged interface `br0` now has the IP `<HOST>`, and `<INTERFACE>` shows `master br0`, indicating it is part of the bridge.
|
||||
</AccordionTemplate>
|
||||
|
||||
Alternatively you can use `brctl` command to display the KVM bridge network status:
|
||||
```bash
|
||||
brctl show br0
|
||||
```
|
||||
|
||||
##### 4. Add Bridge Network to KVM
|
||||
|
||||
- Configure KVM to use the bridge by creating `host-bridge.xml`, open a text editor and pate the block below:
|
||||
```bash
|
||||
nano host-bridge.xml
|
||||
```
|
||||
|
||||
```xml
|
||||
<network>
|
||||
<name>host-bridge</name>
|
||||
<forward mode="bridge"/>
|
||||
<bridge name="br0"/>
|
||||
</network>
|
||||
```
|
||||
|
||||
- Start the new bridge and set it as the default for VMs:
|
||||
```bash
|
||||
virsh net-define host-bridge.xml
|
||||
virsh net-start host-bridge
|
||||
virsh net-autostart host-bridge
|
||||
```
|
||||
|
||||
- Verify that the KVM bridge is active:
|
||||
```bash
|
||||
virsh net-list --all
|
||||
```
|
||||
<AccordionTemplate name="Console output">
|
||||
```bash
|
||||
root@nym-exit:~# virsh net-list --all
|
||||
Name State Autostart Persistent
|
||||
------------------------------------------------
|
||||
host-bridge active yes yes
|
||||
```
|
||||
</AccordionTemplate>
|
||||
|
||||
KVM bridge networking is successfully set up and active!
|
||||
|
||||
Your KVM installation is now ready to deploy and manage VMs.
|
||||
|
||||
</Steps>
|
||||
|
||||
### Setting Up Virtual Machines
|
||||
|
||||
After finishing the [installation of KVM](#installing-kvm-on-a-server-with-ubuntu-2204), we can move to the virtualisation configuration.
|
||||
|
||||
> **The steps below will guide you through a setup of one VM, therefore you will have to repeat this process for each VM**. That also means that you have to be mindful of space and memory allocation.
|
||||
|
||||
<Steps>
|
||||
##### 1. Install OS for VMs
|
||||
|
||||
This is the OS on which the nodes themselves will run. You can chose any GNU/Linux of your preference. For this guide we are going to be using Ubuntu 24.04 LTS (Noble Numbat) cloud image from [cloud-images.ubuntu.com](https://cloud-images.ubuntu.com/noble/current/).
|
||||
|
||||
- Download Ubuntu Cloud image:
|
||||
```bash
|
||||
wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
|
||||
```
|
||||
- Copy the image to to `/var/lib/libvirt/images/` asigning to it a name your VM
|
||||
```bash
|
||||
cp noble-server-cloudimg-amd64.img /var/lib/libvirt/images/<VM_NAME>.img
|
||||
|
||||
# for example:
|
||||
# cp noble-server-cloudimg-amd64.img /var/lib/libvirt/images/ubuntu-1.img
|
||||
```
|
||||
|
||||
##### 2. Create and resize a virtual machine
|
||||
|
||||
- Get `guestfs-tools` to be able to customize your login credentials:
|
||||
```bash
|
||||
apt install guestfs-tools
|
||||
```
|
||||
|
||||
- Define login credentials:
|
||||
```bash
|
||||
virt-customize -a /var/lib/libvirt/images/<VM_NAME>.img --root-password password:<PASSWORD>
|
||||
# for example
|
||||
# virt-customize -a /var/lib/libvirt/images/ubuntu-1.img --root-password password:makesuretosaveyourpasswordslocallytoapasswordmanager
|
||||
```
|
||||
|
||||
- Use `qemu-img` tool with a command `resize` to create a VM according your needs. You can see `qemu` [documentation page`](https://www.qemu.org/docs/master/tools/qemu-img.html) for more info on how to use it correctly.
|
||||
```bash
|
||||
qemu-img resize /var/lib/libvirt/images/<VM_NAME>.img +<SIZE_IN_GB>G
|
||||
# for example
|
||||
# qemu-img resize /var/lib/libvirt/images/ubuntu-1.img +100G
|
||||
```
|
||||
|
||||
- Resize it from within it after `virt-install` command:
|
||||
```bash
|
||||
virt-install \
|
||||
--name <VM_NAME> \
|
||||
--ram=<SIZE_IN_MB> \
|
||||
--vcpus=<NUMBER_OF_VIRTUAL_CPUS> \
|
||||
--cpu host \
|
||||
--hvm \
|
||||
--disk bus=virtio,path=/var/lib/libvirt/images/<VM_NAME>.img \
|
||||
--network bridge=br0 \
|
||||
--graphics none \
|
||||
--console pty,target_type=serial \
|
||||
--osinfo <YOUR_CHOSEN_OS_NAME> \
|
||||
--import
|
||||
```
|
||||
|
||||
- In our example we go with 4 GB RAM on the same machine as before:
|
||||
<br/>
|
||||
<AccordionTemplate name="Command example">
|
||||
```bash
|
||||
virt-install \
|
||||
--name ubuntu-1 \
|
||||
--ram=4096 \
|
||||
--vcpus=4 \
|
||||
--cpu host \
|
||||
--hvm \
|
||||
--disk bus=virtio,path=/var/lib/libvirt/images/ubuntu-1.img \
|
||||
--network bridge=br0 \
|
||||
--graphics none \
|
||||
--console pty,target_type=serial \
|
||||
--osinfo ubuntunoble \
|
||||
--import
|
||||
```
|
||||
</AccordionTemplate>
|
||||
|
||||
- After loading you should see a login console, you can also initiate it by:
|
||||
```bash
|
||||
virsh console <VM_NAME>
|
||||
# for example
|
||||
# virsh console ubuntu-1
|
||||
```
|
||||
|
||||
- Log in to your new VM using your credentials.
|
||||
|
||||
##### 3. Validate your setup
|
||||
|
||||
- Make sure the `root` disk has the expected space by running:
|
||||
```bash
|
||||
df -h
|
||||
```
|
||||
|
||||
- If not, run:
|
||||
```bash
|
||||
growpart /dev/vda 1
|
||||
resize2fs /dev/vda1
|
||||
```
|
||||
|
||||
##### 4. Configure networking for the VM
|
||||
|
||||
As this guide is based on a newer Ubuntu, we use `netplan`, this may be different on different OS.
|
||||
|
||||
- Open `/etc/netplan/01-network-config.yaml` in your favourite text editor:
|
||||
```bash
|
||||
nano /etc/netplan/01-network-config.yaml
|
||||
```
|
||||
|
||||
- Insert this config, using your correct IP configuration, save and exit:
|
||||
```ini
|
||||
network:
|
||||
version: 2
|
||||
renderer: networkd
|
||||
ethernets:
|
||||
<INTERFACE>:
|
||||
dhcp4: false
|
||||
dhcp6: false # Set to true if you want automatic IPv6 assignment
|
||||
addresses:
|
||||
- <IPv4_VM>/24 # Assign IPv4 address to the VM
|
||||
- <IPv6_VM>/64 # Assign IPv6 address to the VM
|
||||
routes:
|
||||
- to: default
|
||||
via: <IPv4_GATEWAY_HOST_SERVER> # IPv4 gateway (host machine)
|
||||
- to: default
|
||||
via: <IPv6_GATEWAY_HOST_SERVER> # IPv6 gateway (host machine)
|
||||
nameservers:
|
||||
addresses:
|
||||
- 1.1.1.1 # Cloudflare IPv4 DNS
|
||||
- 8.8.8.8 # Google IPv4 DNS
|
||||
- 2606:4700:4700::1111 # Cloudflare IPv6 DNS
|
||||
- 2001:4860:4860::8888 # Google IPv6 DNS
|
||||
```
|
||||
- Fix wide permissions on the config file:
|
||||
```bash
|
||||
chmod 600 /etc/netplan/01-network-config.yaml
|
||||
```
|
||||
|
||||
- Check if the config has any errors:
|
||||
```bash
|
||||
netplan generate
|
||||
```
|
||||
|
||||
- Apply the configuration:
|
||||
```bash
|
||||
netplan --debug apply
|
||||
```
|
||||
|
||||
- Verify by checking if IPv4 and IPv6 are assigned correctly and if they route:
|
||||
```bash
|
||||
ip -4 a
|
||||
ip -6 a
|
||||
```
|
||||
```bash
|
||||
ip -4 r
|
||||
ip -6 r
|
||||
```
|
||||
```bash
|
||||
# to ping through IPv6, use:
|
||||
ping6 nym.com
|
||||
```
|
||||
- You should be able to ping your new VM from a local machine:
|
||||
```bash
|
||||
ping <IPv4_VM>
|
||||
ping6 <IPv6_VM>
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
Your VM should be working and fully routable. To be able to use it properly, we will create a direct SSH access to the VM.
|
||||
|
||||
#### Configure VM SSH access
|
||||
|
||||
<Steps>
|
||||
|
||||
##### 1. Log in to your VM, update and upgrade your OS:
|
||||
- Log in to your server using as `root` or as a non-root user with `sudo` privileges
|
||||
```bash
|
||||
apt update; apt upgrade
|
||||
```
|
||||
|
||||
##### 2. Generate new host SSH keys
|
||||
|
||||
Since we used a `cloud-init` image without an SSH server, we need to generate SSH host keys for client authentication and server identity verification. All of them will be saved to this location: `/etc/ssh/<KEY>`.
|
||||
|
||||
- Generate a new RSA host key:
|
||||
```bash
|
||||
ssh-keygen -t rsa -f /etc/ssh/ssh_host_rsa_key
|
||||
```
|
||||
- Generate a new DSA host key:
|
||||
```bash
|
||||
ssh-keygen -t dsa -f /etc/ssh/ssh_host_dsa_key
|
||||
```
|
||||
- Generate a new ECDSA host key:
|
||||
```bash
|
||||
ssh-keygen -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key
|
||||
```
|
||||
- Finally, generate a new ED25519 host key:
|
||||
```bash
|
||||
ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key
|
||||
```
|
||||
##### 3. Restart the SSH service on the server
|
||||
- Run:
|
||||
```bash
|
||||
systemctl restart ssh.service
|
||||
```
|
||||
|
||||
##### 4. Check if the SSH serice is active
|
||||
- Run:
|
||||
```bash
|
||||
systemctl status ssh.service
|
||||
```
|
||||
|
||||
##### 5. Create file `~/.ssh/authorized_keys` and add you public key:
|
||||
- Create `.ssh` directory:
|
||||
```bash
|
||||
mkdir ~/.ssh
|
||||
```
|
||||
|
||||
- Open with your favourite text editor:
|
||||
```bash
|
||||
nano ~/.ssh/authorized_keys
|
||||
```
|
||||
- Paste your SSH public key, save and exit
|
||||
|
||||
- In case of non-root, setup a correct ownership and permissions:
|
||||
```bash
|
||||
chmod 600 ~/.ssh/authorized_keys
|
||||
chmod 700 ~/.ssh
|
||||
chown : ~/.ssh
|
||||
```
|
||||
|
||||
##### 5. Test by connecting via SSH
|
||||
|
||||
- Now you should be able to connect to the VM directly from your local terminal
|
||||
```bash
|
||||
ssh root@<IPv4> -i ~/.ssh/your_ssh_key
|
||||
```
|
||||
</Steps>
|
||||
|
||||
Now your VM is almost ready for `nym-node` [setup](../../nym-node/setup). Before you proceed, ssh in and [configure all prerequisities](../vps-setup#vps-configuration) needed for `nym-node` installation and operation.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"git": {
|
||||
"deploymentEnabled": {
|
||||
"master": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "explorer-api"
|
||||
version = "1.1.47"
|
||||
version = "1.1.48"
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
|
||||
|
||||
@@ -102,8 +102,8 @@ pub struct Debug {
|
||||
|
||||
pub zk_nym_tickets: ZkNymTicketHandlerDebug,
|
||||
|
||||
/// Defines the maximum age of a signed authentication request before it's deemed too stale to process.
|
||||
pub maximum_auth_request_age: Duration,
|
||||
/// Defines the timestamp skew of a signed authentication request before it's deemed too excessive to process.
|
||||
pub max_request_timestamp_skew: Duration,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
@@ -14,12 +14,11 @@ use std::time::Duration;
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Config {
|
||||
pub(crate) enforce_zk_nym: bool,
|
||||
pub(crate) max_auth_request_age: Duration,
|
||||
pub(crate) max_request_timestamp_skew: Duration,
|
||||
|
||||
pub(crate) bandwidth: BandwidthFlushingBehaviourConfig,
|
||||
}
|
||||
|
||||
// I can see this being possible expanded with say storage or client store
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct CommonHandlerState {
|
||||
pub(crate) cfg: Config,
|
||||
|
||||
@@ -131,9 +131,6 @@ impl<R, S> FreshHandler<R, S> {
|
||||
|
||||
// for time being we assume handle is always constructed from raw socket.
|
||||
// if we decide we want to change it, that's not too difficult
|
||||
// also at this point I'm not entirely sure how to deal with this warning without
|
||||
// some considerable refactoring
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
rng: R,
|
||||
conn: S,
|
||||
@@ -426,6 +423,11 @@ impl<R, S> FreshHandler<R, S> {
|
||||
return Ok(2);
|
||||
}
|
||||
|
||||
// a v4 gateway will understand v3 requests (aes256gcm-siv)
|
||||
if client_protocol_version == 3 {
|
||||
return Ok(3);
|
||||
}
|
||||
|
||||
// we can't handle clients with higher protocol than ours
|
||||
// (perhaps we could try to negotiate downgrade on our end? sounds like a nice future improvement)
|
||||
if client_protocol_version <= CURRENT_PROTOCOL_VERSION {
|
||||
@@ -636,7 +638,7 @@ impl<R, S> FreshHandler<R, S> {
|
||||
|
||||
// do cheap checks first
|
||||
// is the provided timestamp relatively recent (and not in the future?)
|
||||
request.verify_timestamp(self.shared_state.cfg.max_auth_request_age)?;
|
||||
request.verify_timestamp(self.shared_state.cfg.max_request_timestamp_skew)?;
|
||||
|
||||
// does the message signature verify?
|
||||
request.verify_signature()?;
|
||||
|
||||
@@ -6,9 +6,8 @@ use crate::node::client_handling::websocket::connection_handler::FreshHandler;
|
||||
use nym_task::TaskClient;
|
||||
use rand::rngs::OsRng;
|
||||
use std::net::SocketAddr;
|
||||
use std::process;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::{io, process};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::*;
|
||||
|
||||
@@ -34,6 +33,76 @@ impl Listener {
|
||||
}
|
||||
}
|
||||
|
||||
fn active_connections(&self) -> usize {
|
||||
self.shared_state
|
||||
.metrics
|
||||
.network
|
||||
.active_ingress_websocket_connections_count()
|
||||
}
|
||||
|
||||
fn prepare_connection_handler(
|
||||
&self,
|
||||
socket: TcpStream,
|
||||
remote_address: SocketAddr,
|
||||
) -> FreshHandler<OsRng, TcpStream> {
|
||||
let shutdown = self
|
||||
.shutdown
|
||||
.fork(format!("websocket_handler_{remote_address}"));
|
||||
FreshHandler::new(
|
||||
OsRng,
|
||||
socket,
|
||||
self.shared_state.clone(),
|
||||
remote_address,
|
||||
shutdown,
|
||||
)
|
||||
}
|
||||
|
||||
fn try_handle_accepted_connection(&self, accepted: io::Result<(TcpStream, SocketAddr)>) {
|
||||
match accepted {
|
||||
Ok((socket, remote_address)) => {
|
||||
trace!("received a socket connection from {remote_address}");
|
||||
|
||||
let active = self.active_connections();
|
||||
|
||||
// 1. check if we're within the connection limit
|
||||
if active >= self.maximum_open_connections {
|
||||
warn!(
|
||||
"connection limit exceeded ({}). can't accept request from {remote_address}",
|
||||
self.maximum_open_connections
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
debug!("there are currently {active} connected clients on the gateway websocket");
|
||||
|
||||
// 2. prepare shared data for the new connection handler
|
||||
let handle = self.prepare_connection_handler(socket, remote_address);
|
||||
|
||||
// 3. increment the connection counter.
|
||||
// make sure to do it before spawning the task,
|
||||
// as another connection might get accepted before the task is scheduled
|
||||
// for execution
|
||||
self.shared_state
|
||||
.metrics
|
||||
.network
|
||||
.new_ingress_websocket_client();
|
||||
|
||||
// 4. spawn the task handling the client connection
|
||||
tokio::spawn(async move {
|
||||
// TODO: refactor it similarly to the mixnet listener on the nym-node
|
||||
let metrics_ref = handle.shared_state.metrics.clone();
|
||||
|
||||
// 4.1. handle all client requests until connection gets terminated
|
||||
handle.start_handling().await;
|
||||
|
||||
// 4.2. decrement the connection counter
|
||||
metrics_ref.network.disconnected_ingress_websocket_client();
|
||||
});
|
||||
}
|
||||
Err(err) => warn!("failed to accept client connection: {err}"),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: change the signature to pub(crate) async fn run(&self, handler: Handler)
|
||||
|
||||
pub(crate) async fn run(&mut self) {
|
||||
@@ -46,8 +115,6 @@ impl Listener {
|
||||
}
|
||||
};
|
||||
|
||||
let open_connections = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
while !self.shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
@@ -55,38 +122,7 @@ impl Listener {
|
||||
trace!("client_handling::Listener: received shutdown");
|
||||
}
|
||||
connection = tcp_listener.accept() => {
|
||||
match connection {
|
||||
Ok((socket, remote_addr)) => {
|
||||
let shutdown = self.shutdown.fork(format!("websocket_handler_{remote_addr}"));
|
||||
trace!("received a socket connection from {remote_addr}");
|
||||
|
||||
if open_connections.fetch_add(1, Ordering::SeqCst) >= self.maximum_open_connections {
|
||||
warn!("connection limit exceeded ({}). can't accept request from {remote_addr}", self.maximum_open_connections);
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: I think we *REALLY* need a mechanism for having a maximum number of connected
|
||||
// clients or spawned tokio tasks -> perhaps a worker system?
|
||||
let handle = FreshHandler::new(
|
||||
OsRng,
|
||||
socket,
|
||||
self.shared_state.clone(),
|
||||
remote_addr,
|
||||
shutdown,
|
||||
);
|
||||
let open_connections = open_connections.clone();
|
||||
tokio::spawn(async move {
|
||||
// TODO: refactor it similarly to the mixnet listener on the nym-node
|
||||
let metrics_ref = handle.shared_state.metrics.clone();
|
||||
metrics_ref.network.new_ingress_websocket_client();
|
||||
open_connections.fetch_add(1, Ordering::SeqCst);
|
||||
handle.start_handling().await;
|
||||
metrics_ref.network.disconnected_ingress_websocket_client();
|
||||
open_connections.fetch_sub(1, Ordering::SeqCst);
|
||||
});
|
||||
}
|
||||
Err(err) => warn!("failed to get client: {err}"),
|
||||
}
|
||||
self.try_handle_accepted_connection(connection)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ impl GatewayTasksBuilder {
|
||||
let shared_state = websocket::CommonHandlerState {
|
||||
cfg: websocket::Config {
|
||||
enforce_zk_nym: self.config.gateway.enforce_zk_nyms,
|
||||
max_auth_request_age: self.config.debug.maximum_auth_request_age,
|
||||
max_request_timestamp_skew: self.config.debug.max_request_timestamp_skew,
|
||||
bandwidth: (&self.config).into(),
|
||||
},
|
||||
ecash_verifier: self.ecash_manager().await?,
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
FROM rust:latest AS builder
|
||||
# this will only work with VPN, otherwise remove the harbor part
|
||||
FROM harbor.nymte.ch/dockerhub/rust:latest AS builder
|
||||
|
||||
COPY ./ /usr/src/nym
|
||||
WORKDIR /usr/src/nym/nym-api
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-api"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.52"
|
||||
version = "1.1.54"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
rust-version.workspace = true
|
||||
@@ -141,7 +141,7 @@ tempfile = { workspace = true }
|
||||
cw3 = { workspace = true }
|
||||
cw-utils = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
sha2 = "0.9"
|
||||
sha2 = { workspace = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -17,6 +17,7 @@ humantime-serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
sha2.workspace = true
|
||||
tendermint = { workspace = true }
|
||||
tendermint-rpc = { workspace = true }
|
||||
thiserror.workspace = true
|
||||
time = { workspace = true, features = ["serde", "parsing", "formatting"] }
|
||||
ts-rs = { workspace = true, optional = true }
|
||||
|
||||
@@ -27,9 +27,7 @@ use nym_network_defaults::{DEFAULT_MIX_LISTENING_PORT, DEFAULT_VERLOC_LISTENING_
|
||||
use nym_node_requests::api::v1::authenticator::models::Authenticator;
|
||||
use nym_node_requests::api::v1::gateway::models::Wireguard;
|
||||
use nym_node_requests::api::v1::ip_packet_router::models::IpPacketRouter;
|
||||
use nym_node_requests::api::v1::node::models::{
|
||||
AuxiliaryDetails, BinaryBuildInformationOwned, NodeRoles,
|
||||
};
|
||||
use nym_node_requests::api::v1::node::models::{AuxiliaryDetails, NodeRoles};
|
||||
use schemars::gen::SchemaGenerator;
|
||||
use schemars::schema::{InstanceType, Schema, SchemaObject};
|
||||
use schemars::JsonSchema;
|
||||
@@ -43,6 +41,8 @@ use thiserror::Error;
|
||||
use time::{Date, OffsetDateTime};
|
||||
use utoipa::{IntoParams, ToResponse, ToSchema};
|
||||
|
||||
pub use nym_node_requests::api::v1::node::models::BinaryBuildInformationOwned;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
pub struct RequestError {
|
||||
message: String,
|
||||
@@ -1215,6 +1215,7 @@ impl From<Wireguard> for WireguardDetails {
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
|
||||
pub struct ApiHealthResponse {
|
||||
pub status: ApiStatus,
|
||||
#[serde(default)]
|
||||
pub chain_status: ChainStatus,
|
||||
pub uptime: u64,
|
||||
}
|
||||
@@ -1225,10 +1226,11 @@ pub enum ApiStatus {
|
||||
Up,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, schemars::JsonSchema, ToSchema)]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Default, schemars::JsonSchema, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ChainStatus {
|
||||
Synced,
|
||||
#[default]
|
||||
Unknown,
|
||||
Stalled {
|
||||
#[serde(
|
||||
@@ -1428,7 +1430,297 @@ impl From<nym_mixnet_contract_common::EpochRewardedSet> for RewardedSetResponse
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct ChainStatusResponse {
|
||||
pub connected_nyxd: String,
|
||||
pub status: DetailedChainStatus,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct DetailedChainStatus {
|
||||
pub abci: crate::models::tendermint_types::AbciInfo,
|
||||
pub latest_block: BlockInfo,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct BlockInfo {
|
||||
pub block_id: BlockId,
|
||||
pub block: FullBlockInfo,
|
||||
// if necessary we might put block data here later too
|
||||
}
|
||||
|
||||
impl From<tendermint_rpc::endpoint::block::Response> for BlockInfo {
|
||||
fn from(value: tendermint_rpc::endpoint::block::Response) -> Self {
|
||||
BlockInfo {
|
||||
block_id: value.block_id.into(),
|
||||
block: FullBlockInfo {
|
||||
header: value.block.header.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct FullBlockInfo {
|
||||
pub header: BlockHeader,
|
||||
}
|
||||
|
||||
// copy tendermint types definitions whilst deriving schema types on them and dropping unwanted fields
|
||||
pub mod tendermint_types {
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tendermint::abci::response::Info;
|
||||
use tendermint::block::header::Version;
|
||||
use tendermint::{block, Hash};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct AbciInfo {
|
||||
/// Some arbitrary information.
|
||||
pub data: String,
|
||||
|
||||
/// The application software semantic version.
|
||||
pub version: String,
|
||||
|
||||
/// The application protocol version.
|
||||
pub app_version: u64,
|
||||
|
||||
/// The latest block for which the app has called [`Commit`].
|
||||
pub last_block_height: u64,
|
||||
|
||||
/// The latest result of [`Commit`].
|
||||
pub last_block_app_hash: String,
|
||||
}
|
||||
|
||||
impl From<Info> for AbciInfo {
|
||||
fn from(value: Info) -> Self {
|
||||
AbciInfo {
|
||||
data: value.data,
|
||||
version: value.version,
|
||||
app_version: value.app_version,
|
||||
last_block_height: value.last_block_height.value(),
|
||||
last_block_app_hash: value.last_block_app_hash.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `Version` contains the protocol version for the blockchain and the
|
||||
/// application.
|
||||
///
|
||||
/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#version>
|
||||
#[derive(
|
||||
Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, ToSchema,
|
||||
)]
|
||||
pub struct HeaderVersion {
|
||||
/// Block version
|
||||
pub block: u64,
|
||||
|
||||
/// App version
|
||||
pub app: u64,
|
||||
}
|
||||
|
||||
impl From<tendermint::block::header::Version> for HeaderVersion {
|
||||
fn from(value: Version) -> Self {
|
||||
HeaderVersion {
|
||||
block: value.block,
|
||||
app: value.app,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Block identifiers which contain two distinct Merkle roots of the block,
|
||||
/// as well as the number of parts in the block.
|
||||
///
|
||||
/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#blockid>
|
||||
///
|
||||
/// Default implementation is an empty Id as defined by the Go implementation in
|
||||
/// <https://github.com/tendermint/tendermint/blob/1635d1339c73ae6a82e062cd2dc7191b029efa14/types/block.go#L1204>.
|
||||
///
|
||||
/// If the Hash is empty in BlockId, the BlockId should be empty (encoded to None).
|
||||
/// This is implemented outside of this struct. Use the Default trait to check for an empty BlockId.
|
||||
/// See: <https://github.com/informalsystems/tendermint-rs/issues/663>
|
||||
#[derive(
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Default,
|
||||
Hash,
|
||||
Eq,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
JsonSchema,
|
||||
ToSchema,
|
||||
)]
|
||||
pub struct BlockId {
|
||||
/// The block's main hash is the Merkle root of all the fields in the
|
||||
/// block header.
|
||||
#[schemars(with = "String")]
|
||||
#[schema(value_type = String)]
|
||||
pub hash: Hash,
|
||||
|
||||
/// Parts header (if available) is used for secure gossipping of the block
|
||||
/// during consensus. It is the Merkle root of the complete serialized block
|
||||
/// cut into parts.
|
||||
///
|
||||
/// PartSet is used to split a byteslice of data into parts (pieces) for
|
||||
/// transmission. By splitting data into smaller parts and computing a
|
||||
/// Merkle root hash on the list, you can verify that a part is
|
||||
/// legitimately part of the complete data, and the part can be forwarded
|
||||
/// to other peers before all the parts are known. In short, it's a fast
|
||||
/// way to propagate a large file over a gossip network.
|
||||
///
|
||||
/// <https://github.com/tendermint/tendermint/wiki/Block-Structure#partset>
|
||||
///
|
||||
/// PartSetHeader in protobuf is defined as never nil using the gogoproto
|
||||
/// annotations. This does not translate to Rust, but we can indicate this
|
||||
/// in the domain type.
|
||||
pub part_set_header: PartSetHeader,
|
||||
}
|
||||
|
||||
impl From<block::Id> for BlockId {
|
||||
fn from(value: block::Id) -> Self {
|
||||
BlockId {
|
||||
hash: value.hash,
|
||||
part_set_header: value.part_set_header.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Block parts header
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Default,
|
||||
Hash,
|
||||
Eq,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Deserialize,
|
||||
Serialize,
|
||||
JsonSchema,
|
||||
ToSchema,
|
||||
)]
|
||||
#[non_exhaustive]
|
||||
pub struct PartSetHeader {
|
||||
/// Number of parts in this block
|
||||
pub total: u32,
|
||||
|
||||
/// Hash of the parts set header,
|
||||
#[schemars(with = "String")]
|
||||
#[schema(value_type = String)]
|
||||
pub hash: Hash,
|
||||
}
|
||||
|
||||
impl From<tendermint::block::parts::Header> for PartSetHeader {
|
||||
fn from(value: block::parts::Header) -> Self {
|
||||
PartSetHeader {
|
||||
total: value.total,
|
||||
hash: value.hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Block `Header` values contain metadata about the block and about the
|
||||
/// consensus, as well as commitments to the data in the current block, the
|
||||
/// previous block, and the results returned by the application.
|
||||
///
|
||||
/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#header>
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct BlockHeader {
|
||||
/// Header version
|
||||
pub version: HeaderVersion,
|
||||
|
||||
/// Chain ID
|
||||
pub chain_id: String,
|
||||
|
||||
/// Current block height
|
||||
pub height: u64,
|
||||
|
||||
/// Current timestamp
|
||||
#[schemars(with = "String")]
|
||||
#[schema(value_type = String)]
|
||||
pub time: tendermint::Time,
|
||||
|
||||
/// Previous block info
|
||||
pub last_block_id: Option<BlockId>,
|
||||
|
||||
/// Commit from validators from the last block
|
||||
#[schemars(with = "Option<String>")]
|
||||
#[schema(value_type = Option<String>)]
|
||||
pub last_commit_hash: Option<Hash>,
|
||||
|
||||
/// Merkle root of transaction hashes
|
||||
#[schemars(with = "Option<String>")]
|
||||
#[schema(value_type = Option<String>)]
|
||||
pub data_hash: Option<Hash>,
|
||||
|
||||
/// Validators for the current block
|
||||
#[schemars(with = "String")]
|
||||
#[schema(value_type = String)]
|
||||
pub validators_hash: Hash,
|
||||
|
||||
/// Validators for the next block
|
||||
#[schemars(with = "String")]
|
||||
#[schema(value_type = String)]
|
||||
pub next_validators_hash: Hash,
|
||||
|
||||
/// Consensus params for the current block
|
||||
#[schemars(with = "String")]
|
||||
#[schema(value_type = String)]
|
||||
pub consensus_hash: Hash,
|
||||
|
||||
/// State after txs from the previous block
|
||||
#[schemars(with = "String")]
|
||||
#[schema(value_type = String)]
|
||||
pub app_hash: Hash,
|
||||
|
||||
/// Root hash of all results from the txs from the previous block
|
||||
#[schemars(with = "Option<String>")]
|
||||
#[schema(value_type = Option<String>)]
|
||||
pub last_results_hash: Option<Hash>,
|
||||
|
||||
/// Hash of evidence included in the block
|
||||
#[schemars(with = "Option<String>")]
|
||||
#[schema(value_type = Option<String>)]
|
||||
pub evidence_hash: Option<Hash>,
|
||||
|
||||
/// Original proposer of the block
|
||||
#[serde(with = "nym_serde_helpers::hex")]
|
||||
#[schemars(with = "String")]
|
||||
#[schema(value_type = String)]
|
||||
pub proposer_address: Vec<u8>,
|
||||
}
|
||||
|
||||
impl From<block::Header> for BlockHeader {
|
||||
fn from(value: block::Header) -> Self {
|
||||
BlockHeader {
|
||||
version: value.version.into(),
|
||||
chain_id: value.chain_id.to_string(),
|
||||
height: value.height.value(),
|
||||
time: value.time,
|
||||
last_block_id: value.last_block_id.map(Into::into),
|
||||
last_commit_hash: value.last_commit_hash,
|
||||
data_hash: value.data_hash,
|
||||
validators_hash: value.validators_hash,
|
||||
next_validators_hash: value.next_validators_hash,
|
||||
consensus_hash: value.consensus_hash,
|
||||
app_hash: Hash::try_from(value.app_hash.as_bytes().to_vec()).unwrap_or_default(),
|
||||
last_results_hash: value.last_results_hash,
|
||||
evidence_hash: value.evidence_hash,
|
||||
proposer_address: value.proposer_address.as_bytes().to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use crate::models::tendermint_types::{BlockHeader, BlockId};
|
||||
pub use config_score::*;
|
||||
|
||||
pub mod config_score {
|
||||
use nym_contracts_common::NaiveFloat;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -10,6 +10,7 @@ use nym_mixnet_contract_common::nym_node::Role;
|
||||
use nym_mixnet_contract_common::reward_params::Performance;
|
||||
use nym_mixnet_contract_common::{Interval, NodeId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::net::IpAddr;
|
||||
use time::OffsetDateTime;
|
||||
use utoipa::ToSchema;
|
||||
@@ -212,3 +213,15 @@ pub struct FullFatNode {
|
||||
// kinda temporary for now to make as few changes as possible for now
|
||||
pub self_described: Option<NymNodeData>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, ToSchema)]
|
||||
pub struct NodesByAddressesRequestBody {
|
||||
#[schema(value_type = Vec<String>)]
|
||||
pub addresses: Vec<IpAddr>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, ToSchema)]
|
||||
pub struct NodesByAddressesResponse {
|
||||
#[schema(value_type = HashMap<String, Option<u32>>)]
|
||||
pub existence: HashMap<IpAddr, Option<NodeId>>,
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use nym_dkg::bte::keys::KeyPair as DkgKeyPair;
|
||||
use rand::{CryptoRng, RngCore};
|
||||
use std::path::Path;
|
||||
use thiserror::__private::AsDisplay;
|
||||
use tracing::warn;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
pub(crate) fn init_bte_keypair<R: RngCore + CryptoRng>(
|
||||
rng: &mut R,
|
||||
@@ -39,17 +39,20 @@ pub(crate) fn load_bte_keypair(config: &config::EcashSigner) -> anyhow::Result<D
|
||||
pub(crate) fn load_ecash_keypair_if_exists(
|
||||
config: &config::EcashSigner,
|
||||
) -> anyhow::Result<Option<KeyPairWithEpoch>> {
|
||||
let storage_path = &config.storage_paths.ecash_key_path;
|
||||
debug!(
|
||||
"attempting to ecash keypair from {}",
|
||||
storage_path.display()
|
||||
);
|
||||
if !config.storage_paths.ecash_key_path.exists() {
|
||||
debug!("the provided filepath doesn't exist - the key won't be loaded");
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if let Ok(ecash_key) =
|
||||
let ecash_key =
|
||||
nym_pemstore::load_key::<KeyPairWithEpoch, _>(&config.storage_paths.ecash_key_path)
|
||||
{
|
||||
return Ok(Some(ecash_key));
|
||||
}
|
||||
|
||||
bail!("ecash key load failure")
|
||||
.context("failed to load ecash key")?;
|
||||
Ok(Some(ecash_key))
|
||||
}
|
||||
|
||||
// the keys can be considered valid if they were generated for the current dkg epoch
|
||||
|
||||
@@ -109,7 +109,7 @@ impl InternalCounters {
|
||||
|
||||
// just hash the current counter
|
||||
self.tx_hash_counter += 1;
|
||||
Hash::Sha256(sha2::Sha256::digest(&self.tx_hash_counter.to_be_bytes()).into())
|
||||
Hash::Sha256(sha2::Sha256::digest(self.tx_hash_counter.to_be_bytes()).into())
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::network::models::{ChainStatusResponse, ContractInformation, NetworkDetails};
|
||||
use crate::network::models::{ContractInformation, NetworkDetails};
|
||||
use crate::node_status_api::models::AxumResult;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::extract::State;
|
||||
use axum::{extract, Json, Router};
|
||||
use nym_api_requests::models::ChainStatusResponse;
|
||||
use nym_contracts_common::ContractBuildInformation;
|
||||
use std::collections::HashMap;
|
||||
use tower_http::compression::CompressionLayer;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
// Copyright 2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::network::models::tendermint_types::{AbciInfo, BlockHeader, BlockId};
|
||||
use nym_config::defaults::NymNetworkDetails;
|
||||
use nym_validator_client::nyxd::BlockResponse;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
@@ -29,292 +27,3 @@ pub struct ContractInformation<T> {
|
||||
pub(crate) address: Option<String>,
|
||||
pub(crate) details: Option<T>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct ChainStatusResponse {
|
||||
pub connected_nyxd: String,
|
||||
pub status: ChainStatus,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct ChainStatus {
|
||||
pub abci: AbciInfo,
|
||||
pub latest_block: BlockInfo,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct BlockInfo {
|
||||
pub block_id: BlockId,
|
||||
pub block: FullBlockInfo,
|
||||
// if necessary we might put block data here later too
|
||||
}
|
||||
|
||||
impl From<BlockResponse> for BlockInfo {
|
||||
fn from(value: BlockResponse) -> Self {
|
||||
BlockInfo {
|
||||
block_id: value.block_id.into(),
|
||||
block: FullBlockInfo {
|
||||
header: value.block.header.into(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct FullBlockInfo {
|
||||
pub header: BlockHeader,
|
||||
}
|
||||
|
||||
// copy tendermint types definitions whilst deriving schema types on them and dropping unwanted fields
|
||||
pub mod tendermint_types {
|
||||
use nym_validator_client::nyxd::Hash;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tendermint::abci::response::Info;
|
||||
use tendermint::block;
|
||||
use tendermint::block::header::Version;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct AbciInfo {
|
||||
/// Some arbitrary information.
|
||||
pub data: String,
|
||||
|
||||
/// The application software semantic version.
|
||||
pub version: String,
|
||||
|
||||
/// The application protocol version.
|
||||
pub app_version: u64,
|
||||
|
||||
/// The latest block for which the app has called [`Commit`].
|
||||
pub last_block_height: u64,
|
||||
|
||||
/// The latest result of [`Commit`].
|
||||
pub last_block_app_hash: String,
|
||||
}
|
||||
|
||||
impl From<Info> for AbciInfo {
|
||||
fn from(value: Info) -> Self {
|
||||
AbciInfo {
|
||||
data: value.data,
|
||||
version: value.version,
|
||||
app_version: value.app_version,
|
||||
last_block_height: value.last_block_height.value(),
|
||||
last_block_app_hash: value.last_block_app_hash.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// `Version` contains the protocol version for the blockchain and the
|
||||
/// application.
|
||||
///
|
||||
/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#version>
|
||||
#[derive(
|
||||
Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema, ToSchema,
|
||||
)]
|
||||
pub struct HeaderVersion {
|
||||
/// Block version
|
||||
pub block: u64,
|
||||
|
||||
/// App version
|
||||
pub app: u64,
|
||||
}
|
||||
|
||||
impl From<tendermint::block::header::Version> for HeaderVersion {
|
||||
fn from(value: Version) -> Self {
|
||||
HeaderVersion {
|
||||
block: value.block,
|
||||
app: value.app,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Block identifiers which contain two distinct Merkle roots of the block,
|
||||
/// as well as the number of parts in the block.
|
||||
///
|
||||
/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#blockid>
|
||||
///
|
||||
/// Default implementation is an empty Id as defined by the Go implementation in
|
||||
/// <https://github.com/tendermint/tendermint/blob/1635d1339c73ae6a82e062cd2dc7191b029efa14/types/block.go#L1204>.
|
||||
///
|
||||
/// If the Hash is empty in BlockId, the BlockId should be empty (encoded to None).
|
||||
/// This is implemented outside of this struct. Use the Default trait to check for an empty BlockId.
|
||||
/// See: <https://github.com/informalsystems/tendermint-rs/issues/663>
|
||||
#[derive(
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
Default,
|
||||
Hash,
|
||||
Eq,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
JsonSchema,
|
||||
ToSchema,
|
||||
)]
|
||||
pub struct BlockId {
|
||||
/// The block's main hash is the Merkle root of all the fields in the
|
||||
/// block header.
|
||||
#[schemars(with = "String")]
|
||||
#[schema(value_type = String)]
|
||||
pub hash: Hash,
|
||||
|
||||
/// Parts header (if available) is used for secure gossipping of the block
|
||||
/// during consensus. It is the Merkle root of the complete serialized block
|
||||
/// cut into parts.
|
||||
///
|
||||
/// PartSet is used to split a byteslice of data into parts (pieces) for
|
||||
/// transmission. By splitting data into smaller parts and computing a
|
||||
/// Merkle root hash on the list, you can verify that a part is
|
||||
/// legitimately part of the complete data, and the part can be forwarded
|
||||
/// to other peers before all the parts are known. In short, it's a fast
|
||||
/// way to propagate a large file over a gossip network.
|
||||
///
|
||||
/// <https://github.com/tendermint/tendermint/wiki/Block-Structure#partset>
|
||||
///
|
||||
/// PartSetHeader in protobuf is defined as never nil using the gogoproto
|
||||
/// annotations. This does not translate to Rust, but we can indicate this
|
||||
/// in the domain type.
|
||||
pub part_set_header: PartSetHeader,
|
||||
}
|
||||
|
||||
impl From<block::Id> for BlockId {
|
||||
fn from(value: block::Id) -> Self {
|
||||
BlockId {
|
||||
hash: value.hash,
|
||||
part_set_header: value.part_set_header.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Block parts header
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Default,
|
||||
Hash,
|
||||
Eq,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Deserialize,
|
||||
Serialize,
|
||||
JsonSchema,
|
||||
ToSchema,
|
||||
)]
|
||||
#[non_exhaustive]
|
||||
pub struct PartSetHeader {
|
||||
/// Number of parts in this block
|
||||
pub total: u32,
|
||||
|
||||
/// Hash of the parts set header,
|
||||
#[schemars(with = "String")]
|
||||
#[schema(value_type = String)]
|
||||
pub hash: Hash,
|
||||
}
|
||||
|
||||
impl From<tendermint::block::parts::Header> for PartSetHeader {
|
||||
fn from(value: block::parts::Header) -> Self {
|
||||
PartSetHeader {
|
||||
total: value.total,
|
||||
hash: value.hash,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Block `Header` values contain metadata about the block and about the
|
||||
/// consensus, as well as commitments to the data in the current block, the
|
||||
/// previous block, and the results returned by the application.
|
||||
///
|
||||
/// <https://github.com/tendermint/spec/blob/d46cd7f573a2c6a2399fcab2cde981330aa63f37/spec/core/data_structures.md#header>
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, ToSchema)]
|
||||
pub struct BlockHeader {
|
||||
/// Header version
|
||||
pub version: HeaderVersion,
|
||||
|
||||
/// Chain ID
|
||||
pub chain_id: String,
|
||||
|
||||
/// Current block height
|
||||
pub height: u64,
|
||||
|
||||
/// Current timestamp
|
||||
#[schemars(with = "String")]
|
||||
#[schema(value_type = String)]
|
||||
pub time: tendermint::Time,
|
||||
|
||||
/// Previous block info
|
||||
pub last_block_id: Option<BlockId>,
|
||||
|
||||
/// Commit from validators from the last block
|
||||
#[schemars(with = "Option<String>")]
|
||||
#[schema(value_type = Option<String>)]
|
||||
pub last_commit_hash: Option<Hash>,
|
||||
|
||||
/// Merkle root of transaction hashes
|
||||
#[schemars(with = "Option<String>")]
|
||||
#[schema(value_type = Option<String>)]
|
||||
pub data_hash: Option<Hash>,
|
||||
|
||||
/// Validators for the current block
|
||||
#[schemars(with = "String")]
|
||||
#[schema(value_type = String)]
|
||||
pub validators_hash: Hash,
|
||||
|
||||
/// Validators for the next block
|
||||
#[schemars(with = "String")]
|
||||
#[schema(value_type = String)]
|
||||
pub next_validators_hash: Hash,
|
||||
|
||||
/// Consensus params for the current block
|
||||
#[schemars(with = "String")]
|
||||
#[schema(value_type = String)]
|
||||
pub consensus_hash: Hash,
|
||||
|
||||
/// State after txs from the previous block
|
||||
#[schemars(with = "String")]
|
||||
#[schema(value_type = String)]
|
||||
pub app_hash: Hash,
|
||||
|
||||
/// Root hash of all results from the txs from the previous block
|
||||
#[schemars(with = "Option<String>")]
|
||||
#[schema(value_type = Option<String>)]
|
||||
pub last_results_hash: Option<Hash>,
|
||||
|
||||
/// Hash of evidence included in the block
|
||||
#[schemars(with = "Option<String>")]
|
||||
#[schema(value_type = Option<String>)]
|
||||
pub evidence_hash: Option<Hash>,
|
||||
|
||||
/// Original proposer of the block
|
||||
#[serde(with = "nym_serde_helpers::hex")]
|
||||
#[schemars(with = "String")]
|
||||
#[schema(value_type = String)]
|
||||
pub proposer_address: Vec<u8>,
|
||||
}
|
||||
|
||||
impl From<block::Header> for BlockHeader {
|
||||
fn from(value: block::Header) -> Self {
|
||||
BlockHeader {
|
||||
version: value.version.into(),
|
||||
chain_id: value.chain_id.to_string(),
|
||||
height: value.height.value(),
|
||||
time: value.time,
|
||||
last_block_id: value.last_block_id.map(Into::into),
|
||||
last_commit_hash: value.last_commit_hash,
|
||||
data_hash: value.data_hash,
|
||||
validators_hash: value.validators_hash,
|
||||
next_validators_hash: value.next_validators_hash,
|
||||
consensus_hash: value.consensus_hash,
|
||||
app_hash: Hash::try_from(value.app_hash.as_bytes().to_vec()).unwrap_or_default(),
|
||||
last_results_hash: value.last_results_hash,
|
||||
evidence_hash: value.evidence_hash,
|
||||
proposer_address: value.proposer_address.as_bytes().to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ use nym_mixnet_contract_common::{NodeId, NymNodeDetails};
|
||||
use nym_node_requests::api::client::{NymNodeApiClientError, NymNodeApiClientExt};
|
||||
use nym_topology::node::{RoutingNode, RoutingNodeError};
|
||||
use std::collections::HashMap;
|
||||
use std::net::IpAddr;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
use tracing::{debug, error, info};
|
||||
@@ -84,10 +85,14 @@ impl NodeDescriptionTopologyExt for NymNodeDescription {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DescribedNodes {
|
||||
nodes: HashMap<NodeId, NymNodeDescription>,
|
||||
addresses_cache: HashMap<IpAddr, NodeId>,
|
||||
}
|
||||
|
||||
impl DescribedNodes {
|
||||
pub fn force_update(&mut self, node: NymNodeDescription) {
|
||||
for ip in &node.description.host_information.ip_address {
|
||||
self.addresses_cache.insert(*ip, node.node_id);
|
||||
}
|
||||
self.nodes.insert(node.node_id, node);
|
||||
}
|
||||
|
||||
@@ -129,6 +134,10 @@ impl DescribedNodes {
|
||||
.filter(|n| n.contract_node_type == DescribedNodeType::NymNode)
|
||||
.filter(|n| n.description.declared_role.can_operate_exit_gateway())
|
||||
}
|
||||
|
||||
pub fn node_with_address(&self, address: IpAddr) -> Option<NodeId> {
|
||||
self.addresses_cache.get(&address).copied()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NodeDescriptionProvider {
|
||||
@@ -182,6 +191,7 @@ async fn try_get_client(
|
||||
// if provided host was malformed, no point in continuing
|
||||
let client = match nym_node_requests::api::Client::builder(address).and_then(|b| {
|
||||
b.with_timeout(Duration::from_secs(5))
|
||||
.no_hickory_dns()
|
||||
.with_user_agent("nym-api-describe-cache")
|
||||
.build()
|
||||
}) {
|
||||
@@ -396,9 +406,20 @@ impl CacheItemProvider for NodeDescriptionProvider {
|
||||
.collect::<HashMap<_, _>>()
|
||||
.await;
|
||||
|
||||
info!("refreshed self described data for {} nodes", nodes.len());
|
||||
let mut addresses_cache = HashMap::new();
|
||||
for node in nodes.values() {
|
||||
for ip in &node.description.host_information.ip_address {
|
||||
addresses_cache.insert(*ip, node.node_id);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(DescribedNodes { nodes })
|
||||
info!("refreshed self described data for {} nodes", nodes.len());
|
||||
info!("with {} unique ip addresses", addresses_cache.len());
|
||||
|
||||
Ok(DescribedNodes {
|
||||
nodes,
|
||||
addresses_cache,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
//! - `/mixnodes/<tier>` => only returns mixnode role data
|
||||
//! - `/gateway/<tier>` => only returns (entry) gateway role data
|
||||
|
||||
use crate::node_status_api::models::{AxumErrorResponse, AxumResult};
|
||||
use crate::nym_nodes::handlers::unstable::full_fat::nodes_detailed;
|
||||
use crate::nym_nodes::handlers::unstable::semi_skimmed::nodes_expanded;
|
||||
use crate::nym_nodes::handlers::unstable::skimmed::{
|
||||
@@ -29,10 +30,14 @@ use crate::nym_nodes::handlers::unstable::skimmed::{
|
||||
};
|
||||
use crate::support::http::helpers::PaginationRequest;
|
||||
use crate::support::http::state::AppState;
|
||||
use axum::routing::get;
|
||||
use axum::Router;
|
||||
use nym_api_requests::nym_nodes::NodeRoleQueryParam;
|
||||
use axum::extract::State;
|
||||
use axum::routing::{get, post};
|
||||
use axum::{Json, Router};
|
||||
use nym_api_requests::nym_nodes::{
|
||||
NodeRoleQueryParam, NodesByAddressesRequestBody, NodesByAddressesResponse,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::collections::HashMap;
|
||||
use tower_http::compression::CompressionLayer;
|
||||
|
||||
pub(crate) mod full_fat;
|
||||
@@ -74,6 +79,7 @@ pub(crate) fn nym_node_routes_unstable() -> Router<AppState> {
|
||||
.nest("/full-fat", Router::new().route("/", get(nodes_detailed)))
|
||||
.route("/gateways/skimmed", get(skimmed::deprecated_gateways_basic))
|
||||
.route("/mixnodes/skimmed", get(skimmed::deprecated_mixnodes_basic))
|
||||
.route("/by-addresses", post(nodes_by_addresses))
|
||||
.layer(CompressionLayer::new())
|
||||
}
|
||||
|
||||
@@ -129,3 +135,36 @@ impl<'a> From<&'a NodesParams> for PaginationRequest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
tag = "Unstable Nym Nodes",
|
||||
post,
|
||||
request_body = NodesByAddressesRequestBody,
|
||||
path = "/by-addresses",
|
||||
context_path = "/v1/unstable/nym-nodes",
|
||||
responses(
|
||||
(status = 200, body = NodesByAddressesResponse)
|
||||
)
|
||||
)]
|
||||
async fn nodes_by_addresses(
|
||||
state: State<AppState>,
|
||||
Json(body): Json<NodesByAddressesRequestBody>,
|
||||
) -> AxumResult<Json<NodesByAddressesResponse>> {
|
||||
// if the request is too big, simply reject it
|
||||
if body.addresses.len() > 100 {
|
||||
return Err(AxumErrorResponse::bad_request(
|
||||
"requested too many addresses",
|
||||
));
|
||||
}
|
||||
|
||||
// TODO: perhaps introduce different cache because realistically nym-api will receive
|
||||
// request for the same couple addresses from all nodes in quick succession
|
||||
let describe_cache = state.describe_nodes_cache_data().await?;
|
||||
|
||||
let mut existence = HashMap::new();
|
||||
for address in body.addresses {
|
||||
existence.insert(address, describe_cache.node_with_address(address));
|
||||
}
|
||||
|
||||
Ok(Json(NodesByAddressesResponse { existence }))
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::circulating_supply_api::cache::CirculatingSupplyCache;
|
||||
use crate::ecash::state::EcashState;
|
||||
use crate::network::models::{ChainStatus, NetworkDetails};
|
||||
use crate::network::models::NetworkDetails;
|
||||
use crate::node_describe_cache::DescribedNodes;
|
||||
use crate::node_status_api::handlers::unstable;
|
||||
use crate::node_status_api::models::AxumErrorResponse;
|
||||
@@ -15,7 +15,9 @@ use crate::support::caching::Cache;
|
||||
use crate::support::nyxd::Client;
|
||||
use crate::support::storage;
|
||||
use axum::extract::FromRef;
|
||||
use nym_api_requests::models::{GatewayBondAnnotated, MixNodeBondAnnotated, NodeAnnotation};
|
||||
use nym_api_requests::models::{
|
||||
DetailedChainStatus, GatewayBondAnnotated, MixNodeBondAnnotated, NodeAnnotation,
|
||||
};
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use nym_task::TaskManager;
|
||||
use nym_topology::CachedEpochRewardedSet;
|
||||
@@ -151,7 +153,7 @@ impl ChainStatusCache {
|
||||
|
||||
struct ChainStatusCacheInner {
|
||||
last_refreshed_at: OffsetDateTime,
|
||||
cache_value: ChainStatus,
|
||||
cache_value: DetailedChainStatus,
|
||||
}
|
||||
|
||||
impl ChainStatusCacheInner {
|
||||
@@ -167,7 +169,7 @@ impl ChainStatusCache {
|
||||
pub(crate) async fn get_or_refresh(
|
||||
&self,
|
||||
client: &Client,
|
||||
) -> Result<ChainStatus, AxumErrorResponse> {
|
||||
) -> Result<DetailedChainStatus, AxumErrorResponse> {
|
||||
if let Some(cached) = self.check_cache().await {
|
||||
return Ok(cached);
|
||||
}
|
||||
@@ -175,7 +177,7 @@ impl ChainStatusCache {
|
||||
self.refresh(client).await
|
||||
}
|
||||
|
||||
async fn check_cache(&self) -> Option<ChainStatus> {
|
||||
async fn check_cache(&self) -> Option<DetailedChainStatus> {
|
||||
let guard = self.inner.read().await;
|
||||
let inner = guard.as_ref()?;
|
||||
if inner.is_valid(self.cache_ttl) {
|
||||
@@ -184,7 +186,7 @@ impl ChainStatusCache {
|
||||
None
|
||||
}
|
||||
|
||||
async fn refresh(&self, client: &Client) -> Result<ChainStatus, AxumErrorResponse> {
|
||||
async fn refresh(&self, client: &Client) -> Result<DetailedChainStatus, AxumErrorResponse> {
|
||||
// 1. attempt to get write lock permit
|
||||
let mut guard = self.inner.write().await;
|
||||
|
||||
@@ -201,7 +203,7 @@ impl ChainStatusCache {
|
||||
.block_info(abci.last_block_height.value() as u32)
|
||||
.await?;
|
||||
|
||||
let status = ChainStatus {
|
||||
let status = DetailedChainStatus {
|
||||
abci: abci.into(),
|
||||
latest_block: block.into(),
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
FROM rust:latest AS builder
|
||||
# this will only work with VPN, otherwise remove the harbor part
|
||||
FROM harbor.nymte.ch/dockerhub/rust:latest AS builder
|
||||
|
||||
COPY ./ /usr/src/nym
|
||||
WORKDIR /usr/src/nym/nym-credential-proxy/nym-credential-proxy
|
||||
@@ -24,7 +25,7 @@ RUN cargo build --release
|
||||
# see https://github.com/nymtech/nym/blob/develop/nym-credential-proxy/nym-credential-proxy/src/cli.rs for details
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
FROM ubuntu:24.04
|
||||
FROM harbor.nymte.ch/dockerhub/ubuntu:24.04
|
||||
|
||||
RUN apt update && apt install -yy curl ca-certificates
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
FROM rust:latest AS builder
|
||||
# this will only work with VPN, otherwise remove the harbor part
|
||||
FROM harbor.nymte.ch/dockerhub/rust:latest AS builder
|
||||
|
||||
COPY ./ /usr/src/nym
|
||||
WORKDIR /usr/src/nym/nym-network-monitor
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# Nym Node Status API
|
||||
The Node Status API serves information about individual `nym-nodes` in the Mixnet, such as which role they are operating in, statistics about them, services such as Network Requesters, as well as summaries of the state of the Mixnet.
|
||||
|
||||
We recommend that developers building applications such as explorers or analytics interfaces about the Mixnet run their own instance of the API, in order to promote a robust network of downstream services, and spread the load of API calls amongst as many endpoints as possible.
|
||||
|
||||
You can find build and operation instructions in the [docs](https://nym.com/docs/apis/ns-api).
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node-status-agent"
|
||||
version = "1.0.0-rc.1"
|
||||
version = "1.0.0-rc.2"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
FROM rust:latest AS builder
|
||||
# this will only work with VPN, otherwise remove the harbor part
|
||||
FROM harbor.nymte.ch/dockerhub/rust:latest AS builder
|
||||
|
||||
ARG GIT_REF=main
|
||||
|
||||
@@ -27,7 +28,7 @@ RUN cargo build --release
|
||||
# see https://github.com/nymtech/nym/blob/develop/nym-node-status-agent/src/cli.rs for details
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
FROM ubuntu:24.04
|
||||
FROM harbor.nymte.ch/dockerhub/ubuntu:24.04
|
||||
|
||||
RUN apt-get update && apt-get install -y ca-certificates
|
||||
|
||||
@@ -37,4 +38,4 @@ COPY --from=builder /usr/src/nym/target/release/nym-node-status-agent ./
|
||||
COPY --from=builder /usr/src/nym-vpn-client/nym-vpn-core/target/release/nym-gateway-probe ./
|
||||
|
||||
ENV NODE_STATUS_AGENT_PROBE_PATH=/nym/nym-gateway-probe
|
||||
ENTRYPOINT [ "/nym/nym-node-status-agent", "run-probe" ]
|
||||
ENTRYPOINT [ "/nym/nym-node-status-agent", "run-probe" ]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eu
|
||||
export ENVIRONMENT=${ENVIRONMENT:-"sandbox"}
|
||||
export ENVIRONMENT=${ENVIRONMENT:-"mainnet"}
|
||||
|
||||
probe_git_ref="nym-vpn-core-v1.1.0"
|
||||
probe_git_ref="nym-vpn-core-v1.3.2"
|
||||
|
||||
crate_root=$(dirname $(realpath "$0"))
|
||||
monorepo_root=$(realpath "${crate_root}/../..")
|
||||
@@ -21,6 +21,7 @@ export NODE_STATUS_AGENT_SERVER_ADDRESS="http://127.0.0.1"
|
||||
export NODE_STATUS_AGENT_SERVER_PORT="8000"
|
||||
export NODE_STATUS_AGENT_PROBE_PATH="$crate_root/nym-gateway-probe"
|
||||
export NODE_STATUS_AGENT_AUTH_KEY="BjyC9SsHAZUzPRkQR4sPTvVrp4GgaquTh5YfSJksvvWT"
|
||||
export NODE_STATUS_AGENT_PROBE_EXTRA_ARGS="netstack-download-timeout-sec=30,netstack-num-ping=2,netstack-send-timeout-sec=1,netstack-recv-timeout-sec=1"
|
||||
|
||||
workers=${1:-1}
|
||||
echo "Running $workers workers in parallel"
|
||||
@@ -54,7 +55,7 @@ function swarm() {
|
||||
echo "All agents completed"
|
||||
}
|
||||
|
||||
# copy_gw_probe
|
||||
copy_gw_probe
|
||||
build_agent
|
||||
|
||||
swarm $workers
|
||||
|
||||
@@ -10,7 +10,8 @@ pub(crate) fn generate_key_pair(path: impl AsRef<Path>) -> anyhow::Result<()> {
|
||||
let mut private_key_file = File::create(priv_key_path)?;
|
||||
private_key_file.write_all(keypair.private_key().to_base58_string().as_bytes())?;
|
||||
|
||||
let pub_key_path = priv_key_path.with_extension("public");
|
||||
let pub_key_path = priv_key_path.with_file_name("public-key");
|
||||
|
||||
let mut public_key_file = File::create(&pub_key_path)?;
|
||||
public_key_file.write_all(keypair.public_key().to_base58_string().as_bytes())?;
|
||||
|
||||
|
||||
@@ -35,6 +35,13 @@ pub(crate) enum Command {
|
||||
/// path of binary to run
|
||||
#[arg(long, env = "NODE_STATUS_AGENT_PROBE_PATH")]
|
||||
probe_path: String,
|
||||
|
||||
#[arg(
|
||||
long,
|
||||
env = "NODE_STATUS_AGENT_PROBE_EXTRA_ARGS",
|
||||
value_delimiter = ','
|
||||
)]
|
||||
probe_extra_args: Vec<String>,
|
||||
},
|
||||
|
||||
GenerateKeypair {
|
||||
@@ -51,11 +58,13 @@ impl Args {
|
||||
server_port,
|
||||
ns_api_auth_key,
|
||||
probe_path,
|
||||
probe_extra_args,
|
||||
} => run_probe::run_probe(
|
||||
server_address,
|
||||
server_port.to_owned(),
|
||||
ns_api_auth_key,
|
||||
probe_path,
|
||||
probe_extra_args,
|
||||
)
|
||||
.await
|
||||
.inspect_err(|err| {
|
||||
|
||||
@@ -7,6 +7,7 @@ pub(crate) async fn run_probe(
|
||||
server_port: u16,
|
||||
ns_api_auth_key: &str,
|
||||
probe_path: &str,
|
||||
probe_extra_args: &Vec<String>,
|
||||
) -> anyhow::Result<()> {
|
||||
let auth_key = PrivateKey::from_base58_string(ns_api_auth_key)
|
||||
.context("Couldn't parse auth key, exiting")?;
|
||||
@@ -19,7 +20,7 @@ pub(crate) async fn run_probe(
|
||||
tracing::info!("Probe version:\n{}", version);
|
||||
|
||||
if let Some(testrun) = ns_api_client.request_testrun().await? {
|
||||
let log = probe.run_and_get_log(&Some(testrun.gateway_identity_key));
|
||||
let log = probe.run_and_get_log(&Some(testrun.gateway_identity_key), probe_extra_args);
|
||||
|
||||
ns_api_client
|
||||
.submit_results(testrun.testrun_id, log, testrun.assigned_at_utc)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use tracing::error;
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
pub(crate) struct GwProbe {
|
||||
path: String,
|
||||
@@ -10,26 +10,71 @@ impl GwProbe {
|
||||
}
|
||||
|
||||
pub(crate) async fn version(&self) -> String {
|
||||
debug!("Attempting to execute binary at: {}", &self.path);
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
match tokio::fs::metadata(&self.path).await {
|
||||
Ok(metadata) => {
|
||||
let perms = metadata.permissions();
|
||||
let mode = perms.mode();
|
||||
if mode & 0o111 == 0 {
|
||||
error!(
|
||||
"Binary is not executable: {} (mode: {:o})",
|
||||
&self.path, mode
|
||||
);
|
||||
return "Binary is not executable".to_string();
|
||||
}
|
||||
debug!("Binary exists with permissions: {:o}", mode);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to stat binary at {}: {}", &self.path, e);
|
||||
return format!("Failed to access binary: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut command = tokio::process::Command::new(&self.path);
|
||||
command.stdout(std::process::Stdio::piped());
|
||||
command.arg("--version");
|
||||
|
||||
info!("Executing command: {:?} --version", &self.path);
|
||||
|
||||
match command.spawn() {
|
||||
Ok(child) => {
|
||||
if let Ok(output) = child.wait_with_output().await {
|
||||
return String::from_utf8(output.stdout)
|
||||
.unwrap_or("Unable to get log from test run".to_string());
|
||||
Ok(child) => match child.wait_with_output().await {
|
||||
Ok(output) => {
|
||||
if output.status.success() {
|
||||
String::from_utf8(output.stdout)
|
||||
.unwrap_or_else(|_| "Unable to parse version output".to_string())
|
||||
} else {
|
||||
let stderr = String::from_utf8(output.stderr)
|
||||
.unwrap_or_else(|_| "Unable to parse error output".to_string());
|
||||
error!(
|
||||
"Command failed with exit code {}: {}",
|
||||
output.status.code().unwrap_or(-1),
|
||||
stderr
|
||||
);
|
||||
format!("Command failed: {}", stderr)
|
||||
}
|
||||
}
|
||||
"Unable to get probe version".to_string()
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to get command output: {}", e);
|
||||
format!("Failed to get command output: {}", e)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Failed to get probe version: {}", e);
|
||||
"Failed to get probe version".to_string()
|
||||
error!("Failed to spawn process: {}", e);
|
||||
format!("Failed to spawn process: {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn run_and_get_log(&self, gateway_key: &Option<String>) -> String {
|
||||
pub(crate) fn run_and_get_log(
|
||||
&self,
|
||||
gateway_key: &Option<String>,
|
||||
probe_extra_args: &Vec<String>,
|
||||
) -> String {
|
||||
let mut command = std::process::Command::new(&self.path);
|
||||
command.stdout(std::process::Stdio::piped());
|
||||
|
||||
@@ -37,6 +82,16 @@ impl GwProbe {
|
||||
command.arg("--gateway").arg(gateway_id);
|
||||
}
|
||||
|
||||
tracing::info!("Extra args for the probe:");
|
||||
for arg in probe_extra_args {
|
||||
let mut split = arg.splitn(2, '=');
|
||||
let name = split.next().unwrap_or_default();
|
||||
let value = split.next().unwrap_or_default();
|
||||
tracing::info!("{} {}", name, value);
|
||||
|
||||
command.arg(format!("--{name}")).arg(value);
|
||||
}
|
||||
|
||||
match command.spawn() {
|
||||
Ok(child) => {
|
||||
if let Ok(output) = child.wait_with_output() {
|
||||
|
||||
@@ -16,23 +16,28 @@ rust-version.workspace = true
|
||||
ammonia = { workspace = true }
|
||||
anyhow = { workspace = true }
|
||||
axum = { workspace = true, features = ["tokio", "macros"] }
|
||||
bip39 = { workspace = true }
|
||||
chrono = { workspace = true }
|
||||
clap = { workspace = true, features = ["cargo", "derive", "env", "string"] }
|
||||
cosmwasm-std = { workspace = true }
|
||||
envy = { workspace = true }
|
||||
futures-util = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
moka = { workspace = true, features = ["future"] }
|
||||
nym-contracts-common = { path = "../../common/cosmwasm-smart-contracts/contracts-common" }
|
||||
nym-bin-common = { path = "../../common/bin-common", features = ["models"] }
|
||||
nym-node-status-client = { path = "../nym-node-status-client" }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric", "serde"] }
|
||||
nym-explorer-client = { path = "../../explorer-api/explorer-client" }
|
||||
nym-http-api-client = { path = "../../common/http-api-client" }
|
||||
nym-network-defaults = { path = "../../common/network-defaults" }
|
||||
nym-serde-helpers = { path = "../../common/serde-helpers"}
|
||||
nym-statistics-common = { path = "../../common/statistics" }
|
||||
nym-validator-client = { path = "../../common/client-libs/validator-client" }
|
||||
nym-task = { path = "../../common/task" }
|
||||
nym-node-requests = { path = "../../nym-node/nym-node-requests", features = ["openapi"] }
|
||||
rand = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
regex = { workspace = true }
|
||||
reqwest = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
FROM rust:latest AS builder
|
||||
# this will only work with VPN, otherwise remove the harbor part
|
||||
FROM harbor.nymte.ch/dockerhub/rust:latest AS builder
|
||||
|
||||
COPY ./ /usr/src/nym
|
||||
WORKDIR /usr/src/nym/nym-node-status-api
|
||||
@@ -26,7 +27,7 @@ RUN cargo build --release
|
||||
# see https://github.com/nymtech/nym/blob/develop/nym-node-status-api/src/cli.rs for details
|
||||
#-------------------------------------------------------------------
|
||||
|
||||
FROM ubuntu:24.04
|
||||
FROM harbor.nymte.ch/dockerhub/ubuntu:24.04
|
||||
|
||||
RUN apt-get update && apt-get install -y ca-certificates
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:22.04
|
||||
FROM harbor.nymte.ch/dockerhub/ubuntu:22.04
|
||||
|
||||
RUN apt-get update && apt-get install -y ca-certificates
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user