Compare commits

...

10 Commits

Author SHA1 Message Date
benedettadavico ecd678a849 ... 2026-01-26 18:52:36 +01:00
benedettadavico 6c6e473607 aaaa 2026-01-26 18:24:23 +01:00
benedettadavico d52d728ab8 loggggggging 2026-01-26 15:22:32 +01:00
benedetta davico b513a99498 Merge pull request #6364 from nymtech/ack-fix
bugfix: ack fix
2026-01-23 17:36:04 +01:00
benedettadavico b5d1e6a93f ack fix 2026-01-23 17:24:48 +01:00
Tommy Verrall b949d0fb01 Merge pull request #6348 from nymtech/cherry-pick/api-urls-oscypek
Cherry pick/api urls oscypek
2026-01-21 14:52:34 +01:00
jmwample 52c47a950e env feature locking to protect contracts 2026-01-21 12:45:23 +01:00
jmwample 377c22f283 minor fixes 2026-01-21 12:45:23 +01:00
jmwample 036ae5c6dc apply configured api urls via env 2026-01-21 12:45:22 +01:00
benedettadavico fb85de9ab6 bump versions 2026-01-16 10:12:01 +01:00
16 changed files with 190 additions and 57 deletions
Generated
+9 -7
View File
@@ -5205,7 +5205,7 @@ dependencies = [
[[package]]
name = "nym-api"
version = "1.1.71"
version = "1.1.72"
dependencies = [
"anyhow",
"async-trait",
@@ -5428,7 +5428,7 @@ dependencies = [
[[package]]
name = "nym-cli"
version = "1.1.68"
version = "1.1.69"
dependencies = [
"anyhow",
"base64 0.22.1",
@@ -5511,7 +5511,7 @@ dependencies = [
[[package]]
name = "nym-client"
version = "1.1.68"
version = "1.1.69"
dependencies = [
"bs58",
"clap",
@@ -6862,6 +6862,8 @@ dependencies = [
"regex",
"schemars 0.8.22",
"serde",
"serde_json",
"tracing",
"url",
"utoipa",
]
@@ -6903,7 +6905,7 @@ dependencies = [
[[package]]
name = "nym-network-requester"
version = "1.1.69"
version = "1.1.70"
dependencies = [
"addr",
"anyhow",
@@ -6953,7 +6955,7 @@ dependencies = [
[[package]]
name = "nym-node"
version = "1.23.0"
version = "1.24.0"
dependencies = [
"anyhow",
"arc-swap",
@@ -7493,7 +7495,7 @@ dependencies = [
[[package]]
name = "nym-socks5-client"
version = "1.1.68"
version = "1.1.69"
dependencies = [
"bs58",
"clap",
@@ -8234,7 +8236,7 @@ dependencies = [
[[package]]
name = "nymvisor"
version = "0.1.33"
version = "0.1.34"
dependencies = [
"anyhow",
"bytes",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-client"
version = "1.1.68"
version = "1.1.69"
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
description = "Implementation of the Nym Client"
edition = "2021"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-socks5-client"
version = "1.1.68"
version = "1.1.69"
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"
+3 -1
View File
@@ -12,6 +12,8 @@ dotenvy = { workspace = true, optional = true }
log = { workspace = true, optional = true }
schemars = { workspace = true, features = ["preserve_order"], optional = true }
serde = { workspace = true, features = ["derive"], optional = true }
serde_json = {workspace = true, optional = true }
tracing = {workspace = true, optional = true }
url = { workspace = true, optional = true }
utoipa = { workspace = true, optional = true }
@@ -20,7 +22,7 @@ utoipa = { workspace = true, optional = true }
[features]
default = ["env", "network"]
env = ["dotenvy", "log"]
env = ["dotenvy", "log", "serde_json", "tracing"]
network = ["schemars", "serde", "url"]
utoipa = [ "dep:utoipa" ]
+12
View File
@@ -72,6 +72,13 @@ pub const NYM_VPN_APIS: &[ApiUrlConst] = &[
},
];
#[cfg(feature = "env")]
fn serialize_api_urls(urls: &[ApiUrlConst]) -> String {
serde_json::to_string(urls)
.inspect_err(|e| tracing::warn!("failed to serialize nym_api_urls for env: {e}"))
.unwrap_or_default()
}
// I'm making clippy mad on purpose, because that url HAS TO be updated and deployed before merging
pub const EXIT_POLICY_URL: &str =
"https://nymtech.net/.wellknown/network-requester/exit-policy.txt";
@@ -162,9 +169,11 @@ pub fn export_to_env() {
);
set_var_to_default(var_names::NYXD, NYXD_URL);
set_var_to_default(var_names::NYM_API, NYM_API);
set_var_to_default(var_names::NYM_APIS, &serialize_api_urls(NYM_APIS));
set_var_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS);
set_var_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
set_var_to_default(var_names::NYM_VPN_API, NYM_VPN_API);
set_var_to_default(var_names::NYM_VPN_APIS, &serialize_api_urls(NYM_VPN_APIS));
set_var_to_default(
var_names::UPGRADE_MODE_ATTESTATION_URL,
UPGRADE_MODE_ATTESTATION_URL,
@@ -211,6 +220,9 @@ pub fn export_to_env_if_not_set() {
);
set_var_conditionally_to_default(var_names::NYXD, NYXD_URL);
set_var_conditionally_to_default(var_names::NYM_API, NYM_API);
set_var_conditionally_to_default(var_names::NYM_APIS, &serialize_api_urls(NYM_APIS));
set_var_conditionally_to_default(var_names::NYM_VPN_API, NYM_VPN_API);
set_var_conditionally_to_default(var_names::NYM_VPN_APIS, &serialize_api_urls(NYM_VPN_APIS));
set_var_conditionally_to_default(var_names::NYXD_WEBSOCKET, NYXD_WS);
set_var_conditionally_to_default(var_names::EXIT_POLICY_URL, EXIT_POLICY_URL);
set_var_conditionally_to_default(
+51 -13
View File
@@ -7,6 +7,13 @@ use serde::{Deserialize, Serialize};
use std::ops::Not;
use url::Url;
#[cfg(feature = "env")]
use crate::var_names;
#[cfg(feature = "env")]
use std::env::{VarError, var};
#[cfg(feature = "env")]
use std::ffi::OsStr;
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize, JsonSchema)]
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
pub struct ChainDetails {
@@ -55,7 +62,7 @@ pub struct ApiUrl {
pub front_hosts: Option<Vec<String>>,
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Debug, Serialize)]
pub struct ApiUrlConst<'a> {
pub url: &'a str,
pub front_hosts: Option<&'a [&'a str]>,
@@ -106,10 +113,6 @@ impl NymNetworkDetails {
#[cfg(feature = "env")]
pub fn new_from_env() -> Self {
use crate::var_names;
use std::env::{VarError, var};
use std::ffi::OsStr;
fn get_optional_env<K: AsRef<OsStr>>(env: K) -> Option<String> {
match var(env) {
Ok(var) => {
@@ -125,6 +128,11 @@ impl NymNetworkDetails {
}
let nym_api = var(var_names::NYM_API).expect("nym api not set");
let nym_api_urls = try_parse_api_urls(var_names::NYM_APIS).unwrap_or(vec![ApiUrl {
url: nym_api.clone(),
front_hosts: None,
}]);
let nym_vpn_api_urls = try_parse_api_urls(var_names::NYM_VPN_APIS);
NymNetworkDetails::new_empty()
.with_network_name(var(var_names::NETWORK_NAME).expect("network name not set"))
@@ -161,10 +169,8 @@ impl NymNetworkDetails {
.with_multisig_contract(get_optional_env(var_names::MULTISIG_CONTRACT_ADDRESS))
.with_coconut_dkg_contract(get_optional_env(var_names::COCONUT_DKG_CONTRACT_ADDRESS))
.with_nym_vpn_api_url(get_optional_env(var_names::NYM_VPN_API))
.with_nym_api_urls(Some(vec![ApiUrl {
url: nym_api,
front_hosts: None,
}]))
.with_nym_vpn_api_urls(nym_vpn_api_urls)
.with_nym_api_urls(nym_api_urls)
}
pub fn new_mainnet() -> Self {
@@ -218,6 +224,9 @@ impl NymNetworkDetails {
}
}
unsafe {
let nym_api_urls = self.nym_api_urls();
let nym_vpn_api_urls = self.nym_vpn_api_urls();
set_var(var_names::NETWORK_NAME, self.network_name);
set_var(var_names::BECH32_PREFIX, self.chain_details.bech32_account_prefix);
@@ -243,9 +252,10 @@ impl NymNetworkDetails {
set_optional_var(var_names::COCONUT_DKG_CONTRACT_ADDRESS, self.contracts.coconut_dkg_contract_address);
set_optional_var(var_names::NYM_VPN_API, self.nym_vpn_api_url);
set_optional_var(var_names::NYM_VPN_APIS, nym_vpn_api_urls);
set_optional_var(var_names::NYM_APIS, nym_api_urls);
}
}
pub fn default_gas_price_amount(&self) -> f64 {
@@ -355,8 +365,14 @@ impl NymNetworkDetails {
}
#[must_use]
pub fn with_nym_api_urls(mut self, urls: Option<Vec<ApiUrl>>) -> Self {
self.nym_api_urls = urls;
pub fn with_nym_api_urls(mut self, urls: Vec<ApiUrl>) -> Self {
self.nym_api_urls = Some(urls);
self
}
#[must_use]
pub fn with_nym_vpn_api_urls(mut self, urls: Option<Vec<ApiUrl>>) -> Self {
self.nym_vpn_api_urls = urls;
self
}
@@ -366,6 +382,28 @@ impl NymNetworkDetails {
.expect("the provided nym-vpn api url is invalid!")
})
}
#[cfg(feature = "env")]
fn nym_api_urls(&self) -> Option<String> {
serde_json::to_string(self.nym_api_urls.as_deref()?)
.inspect_err(|e| tracing::warn!("failed to serialize nym_api_urls for env: {e}"))
.ok()
}
#[cfg(feature = "env")]
fn nym_vpn_api_urls(&self) -> Option<String> {
serde_json::to_string(self.nym_vpn_api_urls.as_deref()?)
.inspect_err(|e| tracing::warn!("failed to serialize nym_vpn_api_urls for env: {e}"))
.ok()
}
}
#[cfg(feature = "env")]
fn try_parse_api_urls(k: impl AsRef<OsStr>) -> Option<Vec<ApiUrl>> {
let raw = var(k).ok()?;
serde_json::from_str(&raw)
.inspect_err(|e| tracing::warn!("failed to parse api urls from env \"{raw:?}\": {e}"))
.ok()
}
#[derive(Debug, Copy, Serialize, Deserialize, Clone, PartialEq, Eq)]
+2
View File
@@ -21,9 +21,11 @@ pub const COCONUT_DKG_CONTRACT_ADDRESS: &str = "COCONUT_DKG_CONTRACT_ADDRESS";
pub const REWARDING_VALIDATOR_ADDRESS: &str = "REWARDING_VALIDATOR_ADDRESS";
pub const NYXD: &str = "NYXD";
pub const NYM_API: &str = "NYM_API";
pub const NYM_APIS: &str = "NYM_APIS";
pub const NYXD_WEBSOCKET: &str = "NYXD_WS";
pub const EXIT_POLICY_URL: &str = "EXIT_POLICY";
pub const NYM_VPN_API: &str = "NYM_VPN_API";
pub const NYM_VPN_APIS: &str = "NYM_VPN_APIS";
pub const CLIENT_STATS_COLLECTION_PROVIDER: &str = "CLIENT_STATS_COLLECTION_PROVIDER";
pub const UPGRADE_MODE_ATTESTATION_URL: &str = "UPGRADE_MODE_ATTESTATION_URL";
pub const UPGRADE_MODE_ATTESTER_ED25519_BS58_PUBKEY: &str = "UPGRADE_MODE_ATTESTER_ED25519_PUBKEY";
+39 -23
View File
@@ -14,7 +14,7 @@ use nym_sphinx_types::{
};
use std::fmt::Display;
use thiserror::Error;
use tracing::{debug, trace};
use tracing::{debug, info, trace};
#[derive(Debug)]
pub enum MixProcessingResultData {
@@ -335,9 +335,36 @@ fn process_final_hop(
packet_type: PacketType,
key_rotation: SphinxKeyRotation,
) -> Result<MixProcessingResultData, PacketProcessingError> {
let payload_size = payload.len();
debug!(
"process_final_hop: payload_size={}, packet_size={:?}, packet_type={:?}, destination={}",
payload_size, packet_size, packet_type, destination
);
let (forward_ack, message) =
split_into_ack_and_message(payload, packet_size, packet_type, key_rotation)?;
let message_size = message.len();
let ack_present = forward_ack.is_some();
debug!(
"process_final_hop: after split - message_size={}, ack_present={}, payload_size={}",
message_size, ack_present, payload_size
);
// Verify expected message size
if let PacketSize::RegularPacket = packet_size {
use nym_sphinx_addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN;
let sphinx_ack_overhead = PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN;
let expected_message_size = packet_size.plaintext_size() - sphinx_ack_overhead;
if message_size != expected_message_size {
use tracing::warn;
warn!(
"process_final_hop: MESSAGE SIZE MISMATCH! message_size={}, expected={}, payload_size={}, ack_present={}",
message_size, expected_message_size, payload_size, ack_present
);
}
}
Ok(MixProcessingResultData::FinalHop {
final_hop_data: ProcessedFinalHop {
destination,
@@ -364,28 +391,17 @@ fn split_into_ack_and_message(
| PacketSize::ExtendedPacket32
| PacketSize::OutfoxRegularPacket => {
trace!("received a normal packet!");
cfg_if::cfg_if! {
if #[cfg(feature = "no-mix-acks")] {
let _ = packet_type;
let _ = key_rotation;
// AIDEV-NOTE: When no-mix-acks is enabled, skip ack extraction entirely.
// The full payload (including ack portion) is returned as the message.
Ok((None, data))
} else {
let (ack_data, message) = split_hop_data_into_ack_and_message(data, packet_type)?;
let (ack_first_hop, ack_packet) =
match SurbAck::try_recover_first_hop_packet(&ack_data, packet_type) {
Ok((first_hop, packet)) => (first_hop, packet),
Err(err) => {
tracing::info!("Failed to recover first hop from ack data: {err}");
return Err(err.into());
}
};
let forward_ack = MixPacket::new(ack_first_hop, ack_packet, packet_type, key_rotation);
Ok((Some(forward_ack), message))
}
}
let (ack_data, message) = split_hop_data_into_ack_and_message(data, packet_type)?;
let (ack_first_hop, ack_packet) =
match SurbAck::try_recover_first_hop_packet(&ack_data, packet_type) {
Ok((first_hop, packet)) => (first_hop, packet),
Err(err) => {
info!("Failed to recover first hop from ack data: {err}");
return Err(err.into());
}
};
let forward_ack = MixPacket::new(ack_first_hop, ack_packet, packet_type, key_rotation);
Ok((Some(forward_ack), message))
}
}
}
@@ -278,6 +278,31 @@ impl<R, S> FreshHandler<R, S> {
where
S: AsyncRead + AsyncWrite + Unpin,
{
// Log message sizes before sending to client
for (idx, packet) in packets.iter().enumerate() {
use nym_sphinx::addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN;
use nym_sphinx::params::PacketSize;
let sphinx_ack_overhead = PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN;
let expected_size = PacketSize::RegularPacket.plaintext_size() - sphinx_ack_overhead;
debug!(
"push_packets_to_client: packet[{}] size={}, expected_regular_size={}, ack_overhead={}",
idx,
packet.len(),
expected_size,
sphinx_ack_overhead
);
if packet.len() == PacketSize::RegularPacket.plaintext_size() {
warn!(
"push_packets_to_client: WARNING - packet[{}] has full plaintext size ({}), expected {} (after ACK removal)",
idx,
packet.len(),
expected_size
);
}
}
// note: into_ws_message encrypts the requests and adds a MAC on it. Perhaps it should
// be more explicit in the naming?
let messages: Vec<Result<Message, WsError>> = packets
@@ -496,8 +521,9 @@ impl<R, S> FreshHandler<R, S> {
error!("{incompatible_err}");
Err(incompatible_err)
} else {
// Return the client's version for backwards compatibility.. temporary solution.
debug!("the client is using exactly the same (or older) protocol version as we are. We're good to continue!");
Ok(CURRENT_PROTOCOL_VERSION)
Ok(client_protocol_version)
}
}
@@ -717,9 +743,9 @@ impl<R, S> FreshHandler<R, S> {
}
pub(crate) fn handle_supported_protocol_request(&self) -> ServerResponse {
debug!("returning gateway protocol version");
use nym_gateway_requests::EMBEDDED_KEY_ROTATION_INFO_VERSION;
ServerResponse::SupportedProtocol {
version: CURRENT_PROTOCOL_VERSION,
version: EMBEDDED_KEY_ROTATION_INFO_VERSION,
}
}
@@ -846,11 +846,23 @@ impl MixnetListener {
| AuthenticatorVersion::V1
| AuthenticatorVersion::V2
| AuthenticatorVersion::V3
| AuthenticatorVersion::V4
| AuthenticatorVersion::V5 => {
// pre v6 this message hasn't existed
| AuthenticatorVersion::V4 => {
// pre v5 this message hasn't existed
return Err(AuthenticatorError::UnknownVersion);
}
AuthenticatorVersion::V5 => {
// V5 clients shouldn't send this message, but for backward compatibility
// with V5 clients connecting to V6 gateways, we allow it and return
// a V6-style response (V5 clients won't understand it, but at least we don't error)
// This enables V5 clients from mainnet (release/2026.1-kiwi) to work with
// V6 gateways (release/2026.2-oscypek) during the transition period
v6::response::AuthenticatorResponse::new_upgrade_mode_check(
request_id,
self.upgrade_mode_enabled(),
)
.to_bytes()
.map_err(AuthenticatorError::response_serialisation)?
}
AuthenticatorVersion::V6 => {
v6::response::AuthenticatorResponse::new_upgrade_mode_check(
request_id,
+1 -1
View File
@@ -4,7 +4,7 @@
[package]
name = "nym-api"
license = "GPL-3.0"
version = "1.1.71"
version = "1.1.72"
authors.workspace = true
edition = "2021"
rust-version.workspace = true
+1 -1
View File
@@ -3,7 +3,7 @@
[package]
name = "nym-node"
version = "1.23.0"
version = "1.24.0"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
+23
View File
@@ -151,6 +151,29 @@ impl ConnectionHandler {
let client = final_hop_data.destination;
let message = final_hop_data.message;
let message_size = message.len();
// Log message size for debugging
use nym_sphinx_addressing::nodes::MAX_NODE_ADDRESS_UNPADDED_LEN;
use nym_sphinx_params::PacketSize;
let sphinx_ack_overhead = PacketSize::AckPacket.size() + MAX_NODE_ADDRESS_UNPADDED_LEN;
let expected_size = PacketSize::RegularPacket.plaintext_size() - sphinx_ack_overhead;
debug!(
"handle_final_hop: client={}, message_size={}, expected_regular_size={}, ack_overhead={}, forward_ack_present={}",
client,
message_size,
expected_size,
sphinx_ack_overhead,
final_hop_data.forward_ack.is_some()
);
if message_size == PacketSize::RegularPacket.plaintext_size() {
warn!(
"handle_final_hop: WARNING - message has full plaintext size ({}), expected {} (after ACK removal)",
message_size, expected_size
);
}
// if possible attempt to push message directly to the client
match self.shared.try_push_message_to_client(client, message) {
@@ -4,7 +4,7 @@
[package]
name = "nym-network-requester"
license = "GPL-3.0"
version = "1.1.69"
version = "1.1.70"
authors.workspace = true
edition.workspace = true
rust-version = "1.85"
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nym-cli"
version = "1.1.68"
version = "1.1.69"
authors.workspace = true
edition = "2021"
license.workspace = true
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nymvisor"
version = "0.1.33"
version = "0.1.34"
authors.workspace = true
repository.workspace = true
homepage.workspace = true