Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 53700b2fca | |||
| 4229f20690 | |||
| 576310fc49 | |||
| a839269df1 | |||
| c9b50dd979 | |||
| 74cdfd5d94 | |||
| 953e813f0e | |||
| 29cf5058a6 | |||
| a2856552d8 | |||
| a33c603471 | |||
| a9f9266992 | |||
| cf34d0d24a | |||
| 5fa7b0a709 | |||
| e232b4fd24 | |||
| 609f174e8d | |||
| a0f4627647 | |||
| 258f8f5f5d | |||
| 38220e05f1 | |||
| 6250ebe235 | |||
| a55323c0e2 | |||
| baa8ac3610 | |||
| 933da11e8f | |||
| 0469036da4 | |||
| 63f9a856fa | |||
| c068948c62 | |||
| 0105f9fa5e | |||
| 004c737965 | |||
| 549121ca32 | |||
| 3f2278dafc | |||
| 25ce0ac814 | |||
| 38d313a101 | |||
| f67bc0ead5 | |||
| 90aaa3572d | |||
| ecc61e4a4a | |||
| 22ac4919e5 | |||
| a1e7cc8e87 | |||
| 57df00637c | |||
| c7eb3bdb7b | |||
| 8f9b704541 | |||
| 6a956c790a | |||
| 2ff5c7221a | |||
| 2235a6e147 | |||
| db6defa122 | |||
| df7768dec0 | |||
| f3a449b7cc | |||
| cf21593ffa | |||
| f0d8dabb9f | |||
| f105bcbafe | |||
| dc0f4af2c1 | |||
| 2a621e07a8 | |||
| 485aeebabd | |||
| 3b726bada9 | |||
| 1d1b2e17d2 | |||
| b5b2dbdfd8 | |||
| 82806f47d8 | |||
| c6f85cf23e | |||
| ed8de7234d | |||
| e25d83b047 | |||
| 9974d480b5 | |||
| 2211f13cdd | |||
| 4505f18a02 | |||
| a717a18948 |
@@ -3,11 +3,6 @@ name: Build and upload Node Status agent container to harbor.nymte.ch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
gateway_probe_git_ref:
|
||||
type: string
|
||||
default: nym-vpn-core-v1.4.0
|
||||
required: true
|
||||
description: Which gateway probe git ref to build the image with
|
||||
release_image:
|
||||
description: 'Tag image as a release'
|
||||
required: true
|
||||
@@ -43,16 +38,6 @@ jobs:
|
||||
VERSION=$(yq -oy '.package.version' ${{ env.WORKING_DIRECTORY }}/Cargo.toml)
|
||||
echo "result=$VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: cleanup-gateway-probe-ref
|
||||
id: cleanup_gateway_probe_ref
|
||||
run: |
|
||||
GATEWAY_PROBE_GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }}
|
||||
GIT_REF_SLUG="${GATEWAY_PROBE_GIT_REF//\//-}"
|
||||
echo "git_ref=${GIT_REF_SLUG}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set GIT_TAG variable
|
||||
run: echo "GIT_TAG=${{ env.WORKING_DIRECTORY }}-${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Initialize RELEASE_TAG
|
||||
run: echo "RELEASE_TAG=" >> $GITHUB_ENV
|
||||
|
||||
@@ -61,24 +46,12 @@ jobs:
|
||||
run: echo "RELEASE_TAG=golden-" >> $GITHUB_ENV
|
||||
|
||||
- name: Set IMAGE_NAME_AND_TAGS variable
|
||||
run: echo "IMAGE_NAME_AND_TAGS=${{ env.CONTAINER_NAME }}:${{ env.RELEASE_TAG }}${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}" >> $GITHUB_ENV
|
||||
run: echo "IMAGE_NAME_AND_TAGS=${{ env.CONTAINER_NAME }}:${{ env.RELEASE_TAG }}${{ steps.get_version.outputs.result }}" >> $GITHUB_ENV
|
||||
|
||||
- name: New env vars
|
||||
run: echo "RELEASE_TAG='$RELEASE_TAG' GIT_TAG='$GIT_TAG' IMAGE_NAME_AND_TAGS='$IMAGE_NAME_AND_TAGS'"
|
||||
|
||||
# - name: Remove existing tag if exists
|
||||
# run: |
|
||||
# if git rev-parse $${{ env.GIT_TAG }} >/dev/null 2>&1; then
|
||||
# git push --delete origin $${{ env.GIT_TAG }}
|
||||
# git tag -d $${{ env.GIT_TAG }}
|
||||
# fi
|
||||
|
||||
# - name: Create tag
|
||||
# run: |
|
||||
# git tag -a $${{ env.GIT_TAG }} -m "Version ${{ steps.get_version.outputs.result }}-${{ steps.cleanup_gateway_probe_ref.outputs.git_ref }}"
|
||||
# git push origin $${{ env.GIT_TAG }}
|
||||
run: echo "RELEASE_TAG='$RELEASE_TAG' IMAGE_NAME_AND_TAGS='$IMAGE_NAME_AND_TAGS'"
|
||||
|
||||
- name: BuildAndPushImageOnHarbor
|
||||
run: |
|
||||
docker build --build-arg GIT_REF=${{ github.event.inputs.gateway_probe_git_ref }} -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }}
|
||||
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }}
|
||||
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
|
||||
|
||||
@@ -4,6 +4,46 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.19-kase] (2025-10-30)
|
||||
|
||||
- update ns agent workflow ([#6154])
|
||||
- Cherry pick - request #6143 from nymtech/bugfix/mix-tx-closed-v2 ([#6153])
|
||||
- bugfix: nym-credential-proxy query params parsing regression ([#6121])
|
||||
- bugfix: revert some dep updates introduced in #6043 ([#6120])
|
||||
- Skip ipv6 metadata endpoint request ([#6118])
|
||||
- update to no longer use 1mb files ([#6117])
|
||||
- chore: restore pending dkg contract state migration ([#6116])
|
||||
- Revert "Propagate cancel token to mixnet client" ([#6115])
|
||||
- Update dirs to 6.0 ([#6109])
|
||||
- Propagate cancel token to mixnet client ([#6105])
|
||||
- bugfix: retrieve and update ticketbook in the same query ([#6101])
|
||||
- bugfix: include network name in the default gateway probe config path ([#6100])
|
||||
- Bugfix/incompatibility fixes ([#6099])
|
||||
- [DOCs/operators] QUIC deployment script & docs ([#6098])
|
||||
- bugfix: testnet manager 02sql migration ([#6096])
|
||||
- feat: move gateway probe to monorepo (and update to rust edition 2024) ([#6094])
|
||||
- bugfix: use custom topology provider for list of init gateways ([#6092])
|
||||
- Max/fix wasm client + build commands ([#6043])
|
||||
|
||||
[#6154]: https://github.com/nymtech/nym/pull/6154
|
||||
[#6153]: https://github.com/nymtech/nym/pull/6153
|
||||
[#6121]: https://github.com/nymtech/nym/pull/6121
|
||||
[#6120]: https://github.com/nymtech/nym/pull/6120
|
||||
[#6118]: https://github.com/nymtech/nym/pull/6118
|
||||
[#6117]: https://github.com/nymtech/nym/pull/6117
|
||||
[#6116]: https://github.com/nymtech/nym/pull/6116
|
||||
[#6115]: https://github.com/nymtech/nym/pull/6115
|
||||
[#6109]: https://github.com/nymtech/nym/pull/6109
|
||||
[#6105]: https://github.com/nymtech/nym/pull/6105
|
||||
[#6101]: https://github.com/nymtech/nym/pull/6101
|
||||
[#6100]: https://github.com/nymtech/nym/pull/6100
|
||||
[#6099]: https://github.com/nymtech/nym/pull/6099
|
||||
[#6098]: https://github.com/nymtech/nym/pull/6098
|
||||
[#6096]: https://github.com/nymtech/nym/pull/6096
|
||||
[#6094]: https://github.com/nymtech/nym/pull/6094
|
||||
[#6092]: https://github.com/nymtech/nym/pull/6092
|
||||
[#6043]: https://github.com/nymtech/nym/pull/6043
|
||||
|
||||
## [2025.18-jarlsberg] (2025-10-14)
|
||||
|
||||
- ns-api: add descriptions to dVPN gateway responses ([#6102])
|
||||
|
||||
Generated
+7
-7
@@ -4824,7 +4824,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-api"
|
||||
version = "1.1.67"
|
||||
version = "1.1.68"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@@ -5050,7 +5050,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-cli"
|
||||
version = "1.1.64"
|
||||
version = "1.1.65"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
@@ -5133,7 +5133,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-client"
|
||||
version = "1.1.64"
|
||||
version = "1.1.65"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
@@ -6354,7 +6354,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-network-requester"
|
||||
version = "1.1.65"
|
||||
version = "1.1.66"
|
||||
dependencies = [
|
||||
"addr",
|
||||
"anyhow",
|
||||
@@ -6404,7 +6404,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-node"
|
||||
version = "1.19.0"
|
||||
version = "1.20.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"arc-swap",
|
||||
@@ -6930,7 +6930,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.64"
|
||||
version = "1.1.65"
|
||||
dependencies = [
|
||||
"bs58",
|
||||
"clap",
|
||||
@@ -7669,7 +7669,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "nymvisor"
|
||||
version = "0.1.29"
|
||||
version = "0.1.30"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.64"
|
||||
version = "1.1.65"
|
||||
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.64"
|
||||
version = "1.1.65"
|
||||
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"
|
||||
|
||||
@@ -36,7 +36,7 @@ nym-bandwidth-controller = { path = "../bandwidth-controller" }
|
||||
nym-crypto = { path = "../crypto" }
|
||||
nym-gateway-client = { path = "../client-libs/gateway-client" }
|
||||
nym-gateway-requests = { path = "../gateway-requests" }
|
||||
nym-http-api-client = { path = "../http-api-client", features = ["network-defaults"] }
|
||||
nym-http-api-client = { path = "../http-api-client" }
|
||||
nym-nonexhaustive-delayqueue = { path = "../nonexhaustive-delayqueue" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
nym-statistics-common = { path = "../statistics" }
|
||||
|
||||
@@ -114,7 +114,9 @@ where
|
||||
})?;
|
||||
hardcoded_topology.entry_capable_nodes().cloned().collect()
|
||||
} else {
|
||||
let mut rng = rand::thread_rng();
|
||||
crate::init::helpers::gateways_for_init(
|
||||
&mut rng,
|
||||
&core.client.nym_api_urls,
|
||||
user_agent,
|
||||
core.debug.topology.minimum_gateway_performance,
|
||||
|
||||
@@ -173,7 +173,9 @@ where
|
||||
})?;
|
||||
hardcoded_topology.entry_capable_nodes().cloned().collect()
|
||||
} else {
|
||||
let mut rng = rand::thread_rng();
|
||||
crate::init::helpers::gateways_for_init(
|
||||
&mut rng,
|
||||
&core.client.nym_api_urls,
|
||||
user_agent,
|
||||
core.debug.topology.minimum_gateway_performance,
|
||||
|
||||
@@ -46,7 +46,6 @@ use nym_gateway_client::client::config::GatewayClientConfig;
|
||||
use nym_gateway_client::{
|
||||
AcknowledgementReceiver, GatewayClient, GatewayConfig, MixnetMessageReceiver, PacketRouter,
|
||||
};
|
||||
use nym_network_defaults::NymNetworkDetails;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::addressing::nodes::NodeIdentity;
|
||||
@@ -213,15 +212,13 @@ pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
|
||||
client_store: S,
|
||||
dkg_query_client: Option<C>,
|
||||
|
||||
// Optional network details for domain fronting support
|
||||
network_details: Option<NymNetworkDetails>,
|
||||
|
||||
wait_for_gateway: bool,
|
||||
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send>>,
|
||||
shutdown: Option<ShutdownTracker>,
|
||||
event_tx: Option<EventSender>,
|
||||
user_agent: Option<UserAgent>,
|
||||
custom_nym_api_client: Option<nym_http_api_client::Client>,
|
||||
|
||||
setup_method: GatewaySetup,
|
||||
|
||||
@@ -245,13 +242,13 @@ where
|
||||
config: base_config,
|
||||
client_store,
|
||||
dkg_query_client,
|
||||
network_details: None,
|
||||
wait_for_gateway: false,
|
||||
custom_topology_provider: None,
|
||||
custom_gateway_transceiver: None,
|
||||
shutdown: None,
|
||||
event_tx: None,
|
||||
user_agent: None,
|
||||
custom_nym_api_client: None,
|
||||
setup_method: GatewaySetup::MustLoad { gateway_id: None },
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
@@ -259,13 +256,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide network details for domain fronting support
|
||||
///
|
||||
/// When provided, the SDK will use `from_network()` to build the nym-api client,
|
||||
/// which automatically handles domain fronting if configured in the network details.
|
||||
#[must_use]
|
||||
pub fn with_network_details(mut self, network_details: NymNetworkDetails) -> Self {
|
||||
self.network_details = Some(network_details);
|
||||
pub fn with_nym_api_client(mut self, client: nym_http_api_client::Client) -> Self {
|
||||
self.custom_nym_api_client = Some(client);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -798,7 +791,7 @@ where
|
||||
event_tx,
|
||||
);
|
||||
|
||||
let mix_tx = mix_traffic_controller.mix_rx();
|
||||
let mix_tx = mix_traffic_controller.mix_tx();
|
||||
let client_tx = mix_traffic_controller.client_tx();
|
||||
|
||||
shutdown_tracker.try_spawn_named(
|
||||
@@ -878,29 +871,19 @@ where
|
||||
}
|
||||
|
||||
fn construct_nym_api_client(
|
||||
network_details: Option<&NymNetworkDetails>,
|
||||
config: &Config,
|
||||
user_agent: Option<UserAgent>,
|
||||
custom_client: Option<nym_http_api_client::Client>,
|
||||
) -> Result<nym_http_api_client::Client, ClientCoreError> {
|
||||
// If network details are provided, use from_network() which handles domain fronting
|
||||
if let Some(network_details) = network_details {
|
||||
tracing::debug!(
|
||||
"Building nym-api client from network details (with domain fronting support)"
|
||||
);
|
||||
|
||||
let mut builder = nym_http_api_client::ClientBuilder::from_network(network_details)
|
||||
.map_err(ClientCoreError::from)?;
|
||||
|
||||
if let Some(user_agent) = user_agent {
|
||||
builder = builder.with_user_agent(user_agent);
|
||||
}
|
||||
|
||||
return builder.build().map_err(ClientCoreError::from);
|
||||
// If a custom client was provided (e.g., with domain fronting support), use it
|
||||
if let Some(client) = custom_client {
|
||||
tracing::info!("Using CUSTOM nym-api HTTP client (with domain fronting support)");
|
||||
return Ok(client);
|
||||
}
|
||||
|
||||
// Fallback to basic client for backwards compatibility
|
||||
tracing::debug!("Building basic nym-api HTTP client from config endpoints");
|
||||
tracing::warn!("No custom HTTP client provided - creating DEFAULT client from config");
|
||||
|
||||
// Otherwise, create a basic client
|
||||
let mut nym_api_urls = config.get_nym_api_endpoints();
|
||||
nym_api_urls.shuffle(&mut thread_rng());
|
||||
|
||||
@@ -975,8 +958,8 @@ where
|
||||
// Create a shutdown tracker for this client - either as a child of provided tracker
|
||||
// or get one from the registry
|
||||
let shutdown_tracker = match self.shutdown {
|
||||
Some(parent_tracker) => parent_tracker.child_tracker(),
|
||||
None => nym_task::get_sdk_shutdown_tracker()?,
|
||||
Some(parent_tracker) => parent_tracker.clone(),
|
||||
None => nym_task::create_sdk_shutdown_tracker()?,
|
||||
};
|
||||
|
||||
Self::start_event_control(self.event_tx, event_receiver, &shutdown_tracker);
|
||||
@@ -997,9 +980,9 @@ where
|
||||
.map(|client| BandwidthController::new(credential_store, client));
|
||||
|
||||
let nym_api_client = Self::construct_nym_api_client(
|
||||
self.network_details.as_ref(),
|
||||
&self.config,
|
||||
self.user_agent.clone(),
|
||||
self.custom_nym_api_client,
|
||||
)?;
|
||||
let key_rotation_config = Self::determine_key_rotation_state(&nym_api_client).await?;
|
||||
|
||||
@@ -1015,7 +998,7 @@ where
|
||||
self.user_agent.clone(),
|
||||
generate_client_stats_id(*self_address.identity()),
|
||||
input_sender.clone(),
|
||||
&shutdown_tracker.child_tracker(),
|
||||
&shutdown_tracker.clone(),
|
||||
);
|
||||
|
||||
// needs to be started as the first thing to block if required waiting for the gateway
|
||||
@@ -1025,7 +1008,7 @@ where
|
||||
shared_topology_accessor.clone(),
|
||||
self_address.gateway(),
|
||||
self.wait_for_gateway,
|
||||
&shutdown_tracker.child_tracker(),
|
||||
&shutdown_tracker.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1045,7 +1028,7 @@ where
|
||||
stats_reporter.clone(),
|
||||
#[cfg(unix)]
|
||||
self.connection_fd_callback,
|
||||
&shutdown_tracker.child_tracker(),
|
||||
&shutdown_tracker.clone(),
|
||||
)
|
||||
.await?;
|
||||
let gateway_ws_fd = gateway_transceiver.ws_fd();
|
||||
@@ -1053,7 +1036,7 @@ where
|
||||
let reply_storage = Self::setup_persistent_reply_storage(
|
||||
reply_storage_backend,
|
||||
key_rotation_config,
|
||||
&shutdown_tracker.child_tracker(),
|
||||
&shutdown_tracker.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1064,7 +1047,7 @@ where
|
||||
reply_storage.key_storage(),
|
||||
reply_controller_sender.clone(),
|
||||
stats_reporter.clone(),
|
||||
&shutdown_tracker.child_tracker(),
|
||||
&shutdown_tracker.clone(),
|
||||
);
|
||||
|
||||
// The message_sender is the transmitter for any component generating sphinx packets
|
||||
@@ -1074,7 +1057,7 @@ where
|
||||
|
||||
let (message_sender, client_request_sender) = Self::start_mix_traffic_controller(
|
||||
gateway_transceiver,
|
||||
&shutdown_tracker.child_tracker(),
|
||||
&shutdown_tracker.clone(),
|
||||
EventSender(event_sender),
|
||||
);
|
||||
|
||||
@@ -1105,7 +1088,7 @@ where
|
||||
shared_lane_queue_lengths.clone(),
|
||||
client_connection_rx,
|
||||
stats_reporter.clone(),
|
||||
&shutdown_tracker.child_tracker(),
|
||||
&shutdown_tracker.clone(),
|
||||
);
|
||||
|
||||
if !self
|
||||
@@ -1121,7 +1104,7 @@ where
|
||||
shared_topology_accessor.clone(),
|
||||
message_sender,
|
||||
stats_reporter.clone(),
|
||||
&shutdown_tracker.child_tracker(),
|
||||
&shutdown_tracker.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
TrySendError::Full(_) => {
|
||||
// This isn't a problem, if the channel is full means we're already sending the
|
||||
// max amount of messages downstream can handle.
|
||||
tracing::debug!("Failed to send cover message - channel full");
|
||||
tracing::trace!("Failed to send cover message - channel full");
|
||||
}
|
||||
TrySendError::Closed(_) => {
|
||||
tracing::warn!("Failed to send cover message - channel closed");
|
||||
|
||||
@@ -20,7 +20,10 @@ pub mod transceiver;
|
||||
|
||||
// We remind ourselves that 32 x 32kb = 1024kb, a reasonable size for a network buffer.
|
||||
pub const MIX_MESSAGE_RECEIVER_BUFFER_SIZE: usize = 32;
|
||||
const MAX_FAILURE_COUNT: usize = 100;
|
||||
|
||||
/// Reduced from 100 to 20 to fail fast (~1-2 seconds instead of ~6 seconds).
|
||||
/// If we can't send 20 packets in a row, the gateway is unreachable.
|
||||
const MAX_FAILURE_COUNT: usize = 20;
|
||||
|
||||
// that's also disgusting.
|
||||
pub struct Empty;
|
||||
@@ -84,7 +87,7 @@ impl MixTrafficController {
|
||||
self.client_tx.clone()
|
||||
}
|
||||
|
||||
pub fn mix_rx(&self) -> BatchMixMessageSender {
|
||||
pub fn mix_tx(&self) -> BatchMixMessageSender {
|
||||
self.mix_tx.clone()
|
||||
}
|
||||
|
||||
@@ -156,6 +159,11 @@ impl MixTrafficController {
|
||||
// Do we need to handle the embedded mixnet client case
|
||||
// separately?
|
||||
self.event_tx.send(MixnetClientEvent::Traffic(MixTrafficEvent::FailedSendingSphinx));
|
||||
// IMO it shouldn't be signalled from there but it is how it is
|
||||
// TODO : report the failure upwards and shutdown from upwards
|
||||
// Gateway is dead, we have to shut down currently
|
||||
error!("Signalling shutdown from the MixTrafficController");
|
||||
self.shutdown_token.cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,6 +298,8 @@ where
|
||||
"failed to send mixnet packet due to closed channel (outside of shutdown!)"
|
||||
);
|
||||
}
|
||||
// Early return to avoid further processing when channel is closed
|
||||
return;
|
||||
}
|
||||
Ok(_) => {
|
||||
let event = if fragment_id.is_some() {
|
||||
|
||||
@@ -6,6 +6,7 @@ use nym_mixnet_contract_common::EpochRewardedSet;
|
||||
use nym_topology::provider_trait::{ToTopologyMetadata, TopologyProvider};
|
||||
use nym_topology::NymTopology;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::nym_nodes::NodeRole;
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::cmp::min;
|
||||
@@ -135,31 +136,31 @@ impl NymApiTopologyProvider {
|
||||
} else {
|
||||
// if we're not using extended topology, we're only getting active set mixnodes and gateways
|
||||
|
||||
let mixnodes_fut = self
|
||||
.validator_client
|
||||
.get_all_basic_active_mixing_assigned_nodes_with_metadata();
|
||||
// Get ALL bonded nodes and filter for both mixnodes and gateways
|
||||
// This ensures consistent metadata between both calls
|
||||
let all_nodes_fut = self.validator_client.get_all_basic_nodes_with_metadata();
|
||||
|
||||
// TODO: we really should be getting ACTIVE gateways only
|
||||
let gateways_fut = self
|
||||
.validator_client
|
||||
.get_all_basic_entry_assigned_nodes_with_metadata();
|
||||
let (rewarded_set, all_nodes_res) = futures::try_join!(rewarded_set_fut, all_nodes_fut)
|
||||
.inspect_err(|err| {
|
||||
error!("failed to get network nodes: {err}");
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
let (rewarded_set, mixnodes_res, gateways_res) =
|
||||
futures::try_join!(rewarded_set_fut, mixnodes_fut, gateways_fut)
|
||||
.inspect_err(|err| {
|
||||
error!("failed to get network nodes: {err}");
|
||||
})
|
||||
.ok()?;
|
||||
let metadata = all_nodes_res.metadata;
|
||||
let all_nodes = all_nodes_res.nodes;
|
||||
|
||||
let metadata = mixnodes_res.metadata;
|
||||
let mixnodes = mixnodes_res.nodes;
|
||||
// Filter for active mixing nodes (role=Mixnode)
|
||||
let mixnodes: Vec<_> = all_nodes
|
||||
.iter()
|
||||
.filter(|n| n.supported_roles.mixnode && matches!(n.role, NodeRole::Mixnode { .. }))
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
if !gateways_res.metadata.consistency_check(&metadata) {
|
||||
warn!("inconsistent nodes metadata between mixnodes and gateways calls! {metadata:?} and {:?}", gateways_res.metadata);
|
||||
return None;
|
||||
}
|
||||
|
||||
let gateways = gateways_res.nodes;
|
||||
// Filter for entry-capable nodes (supported_roles.entry == true)
|
||||
let gateways: Vec<_> = all_nodes
|
||||
.into_iter()
|
||||
.filter(|n| n.supported_roles.entry)
|
||||
.collect();
|
||||
|
||||
debug!(
|
||||
"there are {} mixnodes and {} gateways in total (before performance filtering)",
|
||||
@@ -221,3 +222,27 @@ impl TopologyProvider for NymApiTopologyProvider {
|
||||
Some(topology)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn test_filtering_logic() {
|
||||
// This test verifies the filtering logic used in get_current_compatible_topology
|
||||
// We test that the role matching pattern works correctly
|
||||
use nym_validator_client::nym_nodes::NodeRole;
|
||||
|
||||
// Test that the pattern matching for mixnodes works
|
||||
let mixnode_role = NodeRole::Mixnode { layer: 1 };
|
||||
assert!(
|
||||
matches!(mixnode_role, NodeRole::Mixnode { .. }),
|
||||
"Should match mixnode role pattern"
|
||||
);
|
||||
|
||||
// Test that other roles don't match
|
||||
let entry_role = NodeRole::EntryGateway;
|
||||
assert!(
|
||||
!matches!(entry_role, NodeRole::Mixnode { .. }),
|
||||
"Entry gateway should not match mixnode pattern"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,16 +89,22 @@ async fn get_all_basic_entry_nodes_with_metadata(
|
||||
client: &nym_http_api_client::Client,
|
||||
use_bincode: bool,
|
||||
) -> Result<SkimmedNodesWithMetadata, ClientCoreError> {
|
||||
// Get first page to obtain metadata
|
||||
// Get ALL nodes (not just entry-assigned) because in some environments (like sandbox),
|
||||
// nodes may be capable of entry gateway role but not currently assigned to it
|
||||
let mut page = 0;
|
||||
let res = client
|
||||
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, use_bincode)
|
||||
.get_basic_nodes_v2(false, Some(page), None, use_bincode)
|
||||
.await?;
|
||||
let mut nodes = res.nodes.data;
|
||||
let metadata = res.metadata;
|
||||
|
||||
if res.nodes.pagination.total == nodes.len() {
|
||||
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
|
||||
// Filter for entry-capable nodes (nodes with supported_roles.entry == true)
|
||||
let entry_nodes: Vec<_> = nodes
|
||||
.into_iter()
|
||||
.filter(|n| n.supported_roles.entry)
|
||||
.collect();
|
||||
return Ok(SkimmedNodesWithMetadata::new(entry_nodes, metadata));
|
||||
}
|
||||
|
||||
page += 1;
|
||||
@@ -106,7 +112,7 @@ async fn get_all_basic_entry_nodes_with_metadata(
|
||||
// Collect remaining pages
|
||||
loop {
|
||||
let mut res = client
|
||||
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, use_bincode)
|
||||
.get_basic_nodes_v2(false, Some(page), None, use_bincode)
|
||||
.await?;
|
||||
|
||||
if !metadata.consistency_check(&res.metadata) {
|
||||
@@ -123,7 +129,13 @@ async fn get_all_basic_entry_nodes_with_metadata(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
// Filter for entry-capable nodes (nodes with supported_roles.entry == true)
|
||||
let entry_nodes: Vec<_> = nodes
|
||||
.into_iter()
|
||||
.filter(|n| n.supported_roles.entry)
|
||||
.collect();
|
||||
|
||||
Ok(SkimmedNodesWithMetadata::new(entry_nodes, metadata))
|
||||
}
|
||||
|
||||
impl<'a, G: ConnectableGateway> GatewayWithLatency<'a, G> {
|
||||
@@ -132,7 +144,8 @@ impl<'a, G: ConnectableGateway> GatewayWithLatency<'a, G> {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn gateways_for_init(
|
||||
pub async fn gateways_for_init<R: Rng>(
|
||||
_rng: &mut R,
|
||||
nym_apis: &[Url],
|
||||
user_agent: Option<UserAgent>,
|
||||
minimum_performance: u8,
|
||||
@@ -148,9 +161,18 @@ pub async fn gateways_for_init(
|
||||
return Err(ClientCoreError::ListOfNymApisIsEmpty);
|
||||
}
|
||||
|
||||
let mut builder = nym_http_api_client::ClientBuilder::new_with_urls(nym_api_urls.clone())
|
||||
.with_retries(3)
|
||||
.with_bincode();
|
||||
let mut builder = if nym_api_urls.len() == 1 {
|
||||
nym_http_api_client::Client::builder(nym_api_urls[0].clone()).map_err(|e| {
|
||||
ClientCoreError::ValidatorClientError(nym_validator_client::ValidatorClientError::from(
|
||||
e,
|
||||
))
|
||||
})?
|
||||
} else {
|
||||
nym_http_api_client::ClientBuilder::new_with_urls(nym_api_urls.clone()).with_retries(3)
|
||||
// Enable URL rotation on failure
|
||||
};
|
||||
|
||||
builder = builder.with_bincode();
|
||||
|
||||
if let Some(user_agent) = user_agent {
|
||||
builder = builder.with_user_agent(user_agent);
|
||||
@@ -172,18 +194,56 @@ pub async fn gateways_for_init(
|
||||
|
||||
// filter out gateways below minimum performance and ones that could operate as a mixnode
|
||||
// (we don't want instability)
|
||||
let total_fetched = gateways.len();
|
||||
|
||||
let after_role_filter_count = gateways
|
||||
.iter()
|
||||
.filter(|g| ignore_epoch_roles || !g.supported_roles.mixnode)
|
||||
.count();
|
||||
tracing::debug!(
|
||||
"After role filter: {} (removed {} mixnode-capable)",
|
||||
after_role_filter_count,
|
||||
total_fetched - after_role_filter_count
|
||||
);
|
||||
|
||||
let after_performance_filter_count = gateways
|
||||
.iter()
|
||||
.filter(|g| ignore_epoch_roles || !g.supported_roles.mixnode)
|
||||
.filter(|g| g.performance.round_to_integer() >= minimum_performance)
|
||||
.count();
|
||||
tracing::debug!(
|
||||
"After performance filter (>= {}%): {} (removed {} low-performance)",
|
||||
minimum_performance,
|
||||
after_performance_filter_count,
|
||||
after_role_filter_count - after_performance_filter_count
|
||||
);
|
||||
|
||||
let valid_gateways: Vec<RoutingNode> = gateways
|
||||
.iter()
|
||||
.filter(|g| ignore_epoch_roles || !g.supported_roles.mixnode)
|
||||
.filter(|g| g.performance.round_to_integer() >= minimum_performance)
|
||||
.filter_map(|gateway| gateway.try_into().ok())
|
||||
.collect();
|
||||
tracing::debug!(
|
||||
"After conversion to RoutingNode: {} (removed {} invalid)",
|
||||
valid_gateways.len(),
|
||||
after_performance_filter_count - valid_gateways.len()
|
||||
);
|
||||
tracing::trace!("Valid gateways: {valid_gateways:#?}");
|
||||
|
||||
tracing::info!(
|
||||
"Found {} valid gateways after filtering",
|
||||
"and {} after validity and performance filtering",
|
||||
valid_gateways.len()
|
||||
);
|
||||
|
||||
for gw in &valid_gateways {
|
||||
tracing::debug!(
|
||||
"Available gateway: {} (node_id: {})",
|
||||
gw.identity_key.to_base58_string(),
|
||||
gw.node_id
|
||||
);
|
||||
}
|
||||
|
||||
Ok(valid_gateways)
|
||||
}
|
||||
|
||||
@@ -343,6 +403,14 @@ pub(super) fn get_specified_gateway(
|
||||
must_use_tls: bool,
|
||||
) -> Result<RoutingNode, ClientCoreError> {
|
||||
tracing::debug!("Requesting specified gateway: {gateway_identity}");
|
||||
tracing::debug!("Searching in {} available gateways", gateways.len());
|
||||
for gw in gateways {
|
||||
tracing::debug!(
|
||||
"Gateway in list: {} (node_id: {})",
|
||||
gw.identity_key.to_base58_string(),
|
||||
gw.node_id
|
||||
);
|
||||
}
|
||||
|
||||
let user_gateway = ed25519::PublicKey::from_base58_string(gateway_identity)
|
||||
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
|
||||
@@ -351,10 +419,7 @@ pub(super) fn get_specified_gateway(
|
||||
.iter()
|
||||
.find(|gateway| gateway.identity_key == user_gateway)
|
||||
.ok_or_else(|| {
|
||||
tracing::debug!(
|
||||
"Gateway {gateway_identity} not found in {} available gateways",
|
||||
gateways.len()
|
||||
);
|
||||
tracing::error!("Gateway {gateway_identity} NOT FOUND in available gateways list!");
|
||||
ClientCoreError::NoGatewayWithId(gateway_identity.to_string())
|
||||
})?;
|
||||
|
||||
@@ -426,7 +491,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_single_url_builds_without_retries() {
|
||||
let urls = [Url::parse("https://api.nym.com").unwrap()];
|
||||
let urls = vec![Url::parse("https://api.nym.com").unwrap()];
|
||||
|
||||
let nym_api_urls: Vec<nym_http_api_client::Url> = urls
|
||||
.iter()
|
||||
@@ -467,4 +532,113 @@ mod tests {
|
||||
|
||||
assert!(nym_api_urls.is_empty(), "Empty list should remain empty");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gateway_filtering_logic() {
|
||||
// NOTE: This test validates the filtering logic in isolation.
|
||||
// It does NOT test the actual implementation in get_all_basic_entry_nodes_with_metadata
|
||||
// or gateways_for_init (which would require mocking the HTTP client).
|
||||
// The real proof is building and running the daemon.
|
||||
//
|
||||
// Test the core filtering logic used in gateways_for_init:
|
||||
// 1. Filter by supported_roles.entry (not by epoch role assignment)
|
||||
// 2. Filter by performance
|
||||
//
|
||||
// This test verifies the fix where nodes have role=Inactive
|
||||
// but supported_roles.entry=true
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestNode {
|
||||
id: u32,
|
||||
entry_capable: bool, // supported_roles.entry
|
||||
mixnode_capable: bool, // supported_roles.mixnode
|
||||
performance: u8, // 0-100
|
||||
}
|
||||
|
||||
let nodes = vec![
|
||||
// Node 53: entry-capable, good performance
|
||||
TestNode {
|
||||
id: 53,
|
||||
entry_capable: true,
|
||||
mixnode_capable: false,
|
||||
performance: 100,
|
||||
},
|
||||
// Node 97: entry-capable (but role=Inactive in sandbox), good performance
|
||||
TestNode {
|
||||
id: 97,
|
||||
entry_capable: true,
|
||||
mixnode_capable: false,
|
||||
performance: 100,
|
||||
},
|
||||
// Node 75: NOT entry-capable (mixnode only)
|
||||
TestNode {
|
||||
id: 75,
|
||||
entry_capable: false,
|
||||
mixnode_capable: true,
|
||||
performance: 100,
|
||||
},
|
||||
// Node 99: entry-capable but low performance
|
||||
TestNode {
|
||||
id: 99,
|
||||
entry_capable: true,
|
||||
mixnode_capable: false,
|
||||
performance: 0,
|
||||
},
|
||||
];
|
||||
|
||||
let minimum_performance = 50;
|
||||
let ignore_epoch_roles = true;
|
||||
|
||||
// Step 1: Filter by supported_roles.entry (this is what the fix enables)
|
||||
let entry_capable: Vec<_> = nodes.iter().filter(|n| n.entry_capable).collect();
|
||||
|
||||
assert_eq!(
|
||||
entry_capable.len(),
|
||||
3,
|
||||
"Should have 3 entry-capable nodes (53, 97, 99) - this includes Inactive nodes!"
|
||||
);
|
||||
|
||||
// Step 2: Filter by role (exclude mixnode-capable if not ignoring epoch roles)
|
||||
let after_role_filter: Vec<_> = entry_capable
|
||||
.iter()
|
||||
.filter(|g| ignore_epoch_roles || !g.mixnode_capable)
|
||||
.collect();
|
||||
|
||||
assert_eq!(
|
||||
after_role_filter.len(),
|
||||
3,
|
||||
"All entry-capable nodes pass role filter with ignore_epoch_roles=true"
|
||||
);
|
||||
|
||||
// Step 3: Filter by performance
|
||||
let after_performance_filter: Vec<_> = after_role_filter
|
||||
.iter()
|
||||
.filter(|g| g.performance >= minimum_performance)
|
||||
.collect();
|
||||
|
||||
assert_eq!(
|
||||
after_performance_filter.len(),
|
||||
2,
|
||||
"Should have 2 nodes after performance filter (53 and 97, excluding 99 with 0%)"
|
||||
);
|
||||
|
||||
// Verify the correct nodes made it through
|
||||
let node_ids: Vec<u32> = after_performance_filter.iter().map(|n| n.id).collect();
|
||||
assert!(
|
||||
node_ids.contains(&53),
|
||||
"Node 53 (actively assigned entry gateway) should be included"
|
||||
);
|
||||
assert!(
|
||||
node_ids.contains(&97),
|
||||
"Node 97 (Inactive but entry-capable) should be included - THIS IS THE FIX!"
|
||||
);
|
||||
assert!(
|
||||
!node_ids.contains(&75),
|
||||
"Node 75 (mixnode-only, not entry-capable) should be excluded"
|
||||
);
|
||||
assert!(
|
||||
!node_ids.contains(&99),
|
||||
"Node 99 (low performance) should be excluded"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,9 +594,10 @@ impl ClientBuilder {
|
||||
let urls = network
|
||||
.nym_api_urls
|
||||
.as_ref()
|
||||
.ok_or_else(|| HttpClientError::InternalResponseInconsistency {
|
||||
url: ::url::Url::parse("about:blank").unwrap(),
|
||||
details: "No API URLs configured in network details".to_string(),
|
||||
.ok_or_else(|| {
|
||||
HttpClientError::GenericRequestFailure(
|
||||
"No API URLs configured in network details".to_string(),
|
||||
)
|
||||
})?
|
||||
.iter()
|
||||
.map(|api_url| {
|
||||
@@ -610,12 +611,8 @@ impl ClientBuilder {
|
||||
.iter()
|
||||
.map(|host| format!("https://{}", host))
|
||||
.collect();
|
||||
url = Url::new(api_url.url.clone(), Some(fronts)).map_err(|e| {
|
||||
HttpClientError::MalformedUrl {
|
||||
raw: api_url.url.clone(),
|
||||
source: e,
|
||||
}
|
||||
})?;
|
||||
url = Url::new(api_url.url.clone(), Some(fronts))
|
||||
.map_err(|e| HttpClientError::GenericRequestFailure(e.to_string()))?;
|
||||
}
|
||||
|
||||
Ok(url)
|
||||
|
||||
@@ -24,6 +24,6 @@ pub use crate::runtime_registry::RegistryAccessError;
|
||||
|
||||
/// Get or create a ShutdownTracker for SDK use.
|
||||
/// This provides automatic task management without requiring manual setup.
|
||||
pub fn get_sdk_shutdown_tracker() -> Result<ShutdownTracker, RegistryAccessError> {
|
||||
Ok(runtime_registry::RuntimeRegistry::get_or_create_sdk()?.shutdown_tracker_owned())
|
||||
pub fn create_sdk_shutdown_tracker() -> Result<ShutdownTracker, RegistryAccessError> {
|
||||
Ok(runtime_registry::RuntimeRegistry::create_sdk()?.shutdown_tracker_owned())
|
||||
}
|
||||
|
||||
@@ -19,30 +19,45 @@ pub(crate) struct RuntimeRegistry {
|
||||
pub enum RegistryAccessError {
|
||||
#[error("the runtime registry is poisoned")]
|
||||
Poisoned,
|
||||
|
||||
#[error("The SDK ShutdownManager already exists")]
|
||||
ExistingShutdownManager,
|
||||
|
||||
#[error("No existing SDK ShutdownManager")]
|
||||
MissingShutdownManager,
|
||||
}
|
||||
|
||||
impl RuntimeRegistry {
|
||||
/// Get or create a ShutdownManager for SDK use.
|
||||
/// Create a ShutdownManager for SDK use.
|
||||
/// This manager doesn't listen to OS signals, making it suitable for library use.
|
||||
pub(crate) fn get_or_create_sdk() -> Result<Arc<ShutdownManager>, RegistryAccessError> {
|
||||
/// This function overwrite any existing manager!
|
||||
pub(crate) fn create_sdk() -> Result<Arc<ShutdownManager>, RegistryAccessError> {
|
||||
let mut guard = REGISTRY
|
||||
.sdk_manager
|
||||
.write()
|
||||
.map_err(|_| RegistryAccessError::Poisoned)?;
|
||||
|
||||
Ok(guard
|
||||
.insert(Arc::new(
|
||||
ShutdownManager::new_without_signals().with_cancel_on_panic(),
|
||||
))
|
||||
.clone())
|
||||
}
|
||||
|
||||
/// Get the ShutdownManager for SDK use.
|
||||
/// This manager doesn't listen to OS signals, making it suitable for library use.
|
||||
/// Not yet used, but maybe in the future
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn get_sdk() -> Result<Arc<ShutdownManager>, RegistryAccessError> {
|
||||
let guard = REGISTRY
|
||||
.sdk_manager
|
||||
.read()
|
||||
.map_err(|_| RegistryAccessError::Poisoned)?;
|
||||
if let Some(manager) = guard.as_ref() {
|
||||
return Ok(manager.clone());
|
||||
Ok(manager.clone())
|
||||
} else {
|
||||
Err(RegistryAccessError::MissingShutdownManager)
|
||||
}
|
||||
drop(guard);
|
||||
|
||||
let mut guard = REGISTRY
|
||||
.sdk_manager
|
||||
.write()
|
||||
.map_err(|_| RegistryAccessError::Poisoned)?;
|
||||
Ok(guard
|
||||
.get_or_insert_with(|| {
|
||||
Arc::new(ShutdownManager::new_without_signals().with_cancel_on_panic())
|
||||
})
|
||||
.clone())
|
||||
}
|
||||
|
||||
/// Check if an SDK manager has been created.
|
||||
@@ -85,10 +100,13 @@ mod tests {
|
||||
|
||||
assert!(!RuntimeRegistry::has_sdk_manager().unwrap());
|
||||
|
||||
let manager1 = RuntimeRegistry::get_or_create_sdk().unwrap();
|
||||
// Error if nothing was created
|
||||
assert!(RuntimeRegistry::get_sdk().is_err());
|
||||
|
||||
let manager1 = RuntimeRegistry::create_sdk().unwrap();
|
||||
assert!(RuntimeRegistry::has_sdk_manager().unwrap());
|
||||
|
||||
let manager2 = RuntimeRegistry::get_or_create_sdk().unwrap();
|
||||
let manager2 = RuntimeRegistry::get_sdk().unwrap();
|
||||
// Should return the same instance
|
||||
assert!(Arc::ptr_eq(&manager1, &manager2));
|
||||
|
||||
|
||||
@@ -160,8 +160,15 @@ pub async fn setup_gateway_from_api(
|
||||
minimum_performance: u8,
|
||||
ignore_epoch_roles: bool,
|
||||
) -> Result<InitialisationResult, WasmCoreError> {
|
||||
let gateways =
|
||||
gateways_for_init(nym_apis, None, minimum_performance, ignore_epoch_roles).await?;
|
||||
let mut rng = thread_rng();
|
||||
let gateways = gateways_for_init(
|
||||
&mut rng,
|
||||
nym_apis,
|
||||
None,
|
||||
minimum_performance,
|
||||
ignore_epoch_roles,
|
||||
)
|
||||
.await?;
|
||||
setup_gateway_wasm(client_store, force_tls, chosen_gateway, gateways).await
|
||||
}
|
||||
|
||||
@@ -171,7 +178,9 @@ pub async fn current_gateways_wasm(
|
||||
minimum_performance: u8,
|
||||
ignore_epoch_roles: bool,
|
||||
) -> Result<Vec<RoutingNode>, ClientCoreError> {
|
||||
let mut rng = thread_rng();
|
||||
gateways_for_init(
|
||||
&mut rng,
|
||||
nym_apis,
|
||||
user_agent,
|
||||
minimum_performance,
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-api"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.67"
|
||||
version = "1.1.68"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
rust-version.workspace = true
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
# this will only work with VPN, otherwise remove the harbor part
|
||||
FROM harbor.nymte.ch/dockerhub/rust:latest AS builder
|
||||
|
||||
ARG GIT_REF=main
|
||||
|
||||
RUN apt update && apt install -yy libdbus-1-dev pkg-config libclang-dev
|
||||
|
||||
# Install go
|
||||
RUN wget https://go.dev/dl/go1.22.5.linux-amd64.tar.gz -O go.tar.gz
|
||||
RUN tar -xzvf go.tar.gz -C /usr/local
|
||||
|
||||
RUN git clone https://github.com/nymtech/nym-vpn-client /usr/src/nym-vpn-client
|
||||
RUN cd /usr/src/nym-vpn-client && git checkout $GIT_REF
|
||||
ENV PATH=/go/bin:/usr/local/go/bin:$PATH
|
||||
WORKDIR /usr/src/nym-vpn-client/nym-vpn-core
|
||||
RUN cargo build --release --package nym-gateway-probe
|
||||
|
||||
COPY ./ /usr/src/nym
|
||||
WORKDIR /usr/src/nym
|
||||
RUN cargo build --release --package nym-gateway-probe
|
||||
|
||||
WORKDIR /usr/src/nym/nym-node-status-api/nym-node-status-agent
|
||||
RUN cargo build --release
|
||||
|
||||
@@ -35,7 +31,7 @@ RUN apt-get update && apt-get install -y ca-certificates
|
||||
WORKDIR /nym
|
||||
|
||||
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 ./
|
||||
COPY --from=builder /usr/src/nym/target/release/nym-gateway-probe ./
|
||||
COPY --from=builder /usr/src/nym/nym-node-status-api/nym-node-status-agent/entrypoint.sh ./
|
||||
RUN chmod +x /nym/entrypoint.sh
|
||||
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node"
|
||||
version = "1.19.0"
|
||||
version = "1.20.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -23,6 +23,7 @@ nym-authenticator-client = { path = "../nym-authenticator-client" }
|
||||
nym-bandwidth-controller = { path = "../common/bandwidth-controller" }
|
||||
nym-credential-storage = { path = "../common/credential-storage" }
|
||||
nym-credentials-interface = { path = "../common/credentials-interface" }
|
||||
nym-http-api-client = { path = "../common/http-api-client" }
|
||||
nym-ip-packet-client = { path = "../nym-ip-packet-client" }
|
||||
nym-registration-common = { path = "../common/registration" }
|
||||
nym-sdk = { path = "../sdk/rust/nym-sdk" }
|
||||
|
||||
@@ -34,6 +34,7 @@ pub struct BuilderConfig {
|
||||
pub two_hops: bool,
|
||||
pub user_agent: UserAgent,
|
||||
pub custom_topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
pub custom_nym_api_client: Option<nym_http_api_client::Client>,
|
||||
pub network_env: NymNetworkDetails,
|
||||
pub cancel_token: CancellationToken,
|
||||
#[cfg(unix)]
|
||||
@@ -56,6 +57,9 @@ pub struct MixnetClientConfig {
|
||||
}
|
||||
|
||||
impl BuilderConfig {
|
||||
/// Create a new BuilderConfig without domain fronting support
|
||||
///
|
||||
/// For domain fronting support, set `custom_nym_api_client` to Some(client) after creation
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
entry_node: NymNodeWithKeys,
|
||||
@@ -77,6 +81,7 @@ impl BuilderConfig {
|
||||
two_hops,
|
||||
user_agent,
|
||||
custom_topology_provider,
|
||||
custom_nym_api_client: None,
|
||||
network_env,
|
||||
cancel_token,
|
||||
#[cfg(unix)]
|
||||
@@ -84,6 +89,12 @@ impl BuilderConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Set a custom nym-api HTTP client (for domain fronting support)
|
||||
pub fn with_nym_api_client(mut self, client: nym_http_api_client::Client) -> Self {
|
||||
self.custom_nym_api_client = Some(client);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn mixnet_client_debug_config(&self) -> DebugConfig {
|
||||
if self.two_hops {
|
||||
two_hop_debug_config(&self.mixnet_client_config)
|
||||
@@ -136,7 +147,7 @@ impl BuilderConfig {
|
||||
RememberMe::new_mixnet()
|
||||
};
|
||||
|
||||
let builder = builder
|
||||
let mut builder = builder
|
||||
.with_user_agent(self.user_agent)
|
||||
.request_gateway(self.entry_node.node.identity.to_string())
|
||||
.network_details(self.network_env)
|
||||
@@ -145,6 +156,13 @@ impl BuilderConfig {
|
||||
.with_remember_me(remember_me)
|
||||
.custom_topology_provider(self.custom_topology_provider);
|
||||
|
||||
if let Some(nym_api_client) = self.custom_nym_api_client {
|
||||
tracing::info!("Registration client: Passing custom nym-api HTTP client to SDK");
|
||||
builder = builder.with_nym_api_client(nym_api_client);
|
||||
} else {
|
||||
tracing::warn!("Registration client: No custom nym-api HTTP client provided");
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
let builder = builder.with_connection_fd_callback(self.connection_fd_callback);
|
||||
|
||||
@@ -247,4 +265,23 @@ mod tests {
|
||||
assert_eq!(config.min_mixnode_performance, None);
|
||||
assert_eq!(config.min_gateway_performance, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builder_config_has_custom_client_field() {
|
||||
// Verify that BuilderConfig has the custom_nym_api_client field
|
||||
// by creating a simple HTTP client
|
||||
let http_client = nym_http_api_client::Client::builder(
|
||||
nym_http_api_client::Url::parse("https://validator.nymtech.net/api").unwrap(),
|
||||
)
|
||||
.expect("Failed to create client builder")
|
||||
.build()
|
||||
.expect("Failed to build client");
|
||||
|
||||
// Verify the client works
|
||||
let urls = http_client.base_urls();
|
||||
assert!(
|
||||
!urls.is_empty() && urls[0].as_str().contains("validator.nymtech.net"),
|
||||
"HTTP client should be configured with correct URL"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,7 @@ pub struct MixnetClientBuilder<S: MixnetClientStorage = Ephemeral> {
|
||||
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send + Sync>>,
|
||||
custom_shutdown: Option<ShutdownTracker>,
|
||||
custom_nym_api_client: Option<nym_http_api_client::Client>,
|
||||
event_tx: Option<EventSender>,
|
||||
force_tls: bool,
|
||||
user_agent: Option<UserAgent>,
|
||||
@@ -93,6 +94,7 @@ impl MixnetClientBuilder<OnDiskPersistent> {
|
||||
socks5_config: None,
|
||||
wait_for_gateway: false,
|
||||
custom_topology_provider: None,
|
||||
custom_nym_api_client: None,
|
||||
storage: storage_paths
|
||||
.initialise_default_persistent_storage()
|
||||
.await?,
|
||||
@@ -131,6 +133,7 @@ where
|
||||
wait_for_gateway: false,
|
||||
custom_topology_provider: None,
|
||||
custom_gateway_transceiver: None,
|
||||
custom_nym_api_client: None,
|
||||
custom_shutdown: None,
|
||||
event_tx: None,
|
||||
force_tls: false,
|
||||
@@ -155,6 +158,7 @@ where
|
||||
wait_for_gateway: self.wait_for_gateway,
|
||||
custom_topology_provider: self.custom_topology_provider,
|
||||
custom_gateway_transceiver: self.custom_gateway_transceiver,
|
||||
custom_nym_api_client: self.custom_nym_api_client,
|
||||
custom_shutdown: self.custom_shutdown,
|
||||
event_tx: self.event_tx,
|
||||
force_tls: self.force_tls,
|
||||
@@ -294,6 +298,12 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_nym_api_client(mut self, client: nym_http_api_client::Client) -> Self {
|
||||
self.custom_nym_api_client = Some(client);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_statistics_reporting(mut self, config: StatsReporting) -> Self {
|
||||
self.config.debug_config.stats_reporting = config;
|
||||
@@ -338,6 +348,7 @@ where
|
||||
|
||||
client.custom_gateway_transceiver = self.custom_gateway_transceiver;
|
||||
client.custom_topology_provider = self.custom_topology_provider;
|
||||
client.custom_nym_api_client = self.custom_nym_api_client;
|
||||
client.custom_shutdown = self.custom_shutdown;
|
||||
client.wait_for_gateway = self.wait_for_gateway;
|
||||
client.force_tls = self.force_tls;
|
||||
@@ -387,6 +398,9 @@ where
|
||||
/// advanced usage of custom gateways
|
||||
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send + Sync>>,
|
||||
|
||||
/// Custom nym-api HTTP client (for domain fronting support)
|
||||
custom_nym_api_client: Option<nym_http_api_client::Client>,
|
||||
|
||||
/// Attempt to wait for the selected gateway (if applicable) to come online if its currently not bonded.
|
||||
wait_for_gateway: bool,
|
||||
|
||||
@@ -459,6 +473,7 @@ where
|
||||
storage,
|
||||
custom_topology_provider: None,
|
||||
custom_gateway_transceiver: None,
|
||||
custom_nym_api_client: None,
|
||||
wait_for_gateway: false,
|
||||
force_tls: false,
|
||||
custom_shutdown: None,
|
||||
@@ -564,8 +579,10 @@ where
|
||||
let nym_api_endpoints = self.get_api_endpoints();
|
||||
let topology_cfg = &self.config.debug_config.topology;
|
||||
let user_agent = self.user_agent.clone();
|
||||
let mut rng = OsRng;
|
||||
|
||||
gateways_for_init(
|
||||
&mut rng,
|
||||
&nym_api_endpoints,
|
||||
user_agent,
|
||||
topology_cfg.minimum_gateway_performance,
|
||||
@@ -707,7 +724,6 @@ where
|
||||
|
||||
let mut base_builder: BaseClientBuilder<_, _> =
|
||||
BaseClientBuilder::new(base_config, self.storage, self.dkg_query_client)
|
||||
.with_network_details(self.config.network_details.clone())
|
||||
.with_wait_for_gateway(self.wait_for_gateway)
|
||||
.with_forget_me(&self.forget_me)
|
||||
.with_remember_me(&self.remember_me)
|
||||
@@ -717,19 +733,22 @@ where
|
||||
base_builder = base_builder.with_user_agent(user_agent);
|
||||
}
|
||||
|
||||
if let Some(nym_api_client) = self.custom_nym_api_client {
|
||||
tracing::info!("SDK: Passing custom nym-api HTTP client to BaseClientBuilder");
|
||||
base_builder = base_builder.with_nym_api_client(nym_api_client);
|
||||
} else {
|
||||
tracing::warn!("SDK: No custom nym-api HTTP client configured");
|
||||
}
|
||||
|
||||
if let Some(topology_provider) = self.custom_topology_provider {
|
||||
base_builder = base_builder.with_topology_provider(topology_provider);
|
||||
}
|
||||
|
||||
// Use custom shutdown if provided, otherwise get from registry
|
||||
let shutdown_tracker = match self.custom_shutdown {
|
||||
Some(custom) => custom,
|
||||
None => {
|
||||
// Auto-create from registry for SDK use
|
||||
nym_task::get_sdk_shutdown_tracker()?
|
||||
}
|
||||
};
|
||||
base_builder = base_builder.with_shutdown(shutdown_tracker);
|
||||
// Use custom shutdown if provided, otherwise the sdk one will be used later down the line
|
||||
if let Some(shutdown_tracker) = self.custom_shutdown {
|
||||
base_builder = base_builder.with_shutdown(shutdown_tracker);
|
||||
}
|
||||
|
||||
if let Some(event_tx) = self.event_tx {
|
||||
base_builder = base_builder.with_event_tx(event_tx);
|
||||
}
|
||||
@@ -794,7 +813,7 @@ where
|
||||
client_output,
|
||||
client_state.clone(),
|
||||
nym_address,
|
||||
started_client.shutdown_handle.child_tracker(),
|
||||
started_client.shutdown_handle.clone(),
|
||||
packet_type,
|
||||
);
|
||||
|
||||
@@ -887,24 +906,70 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_mixnet_builder_default() {
|
||||
fn test_mixnet_builder_default_no_custom_client() {
|
||||
let builder = MixnetClientBuilder::new_ephemeral();
|
||||
assert!(
|
||||
builder.build().is_ok(),
|
||||
"Builder should succeed with default configuration"
|
||||
"Builder should succeed without custom client"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mixnet_builder_with_network_details() {
|
||||
use nym_network_defaults::NymNetworkDetails;
|
||||
fn test_mixnet_builder_with_custom_client() {
|
||||
let http_client = nym_http_api_client::Client::builder(
|
||||
nym_http_api_client::Url::parse("https://validator.nymtech.net/api").unwrap(),
|
||||
)
|
||||
.expect("Failed to create client builder")
|
||||
.build()
|
||||
.expect("Failed to build client");
|
||||
|
||||
let builder = MixnetClientBuilder::new_ephemeral();
|
||||
let builder = MixnetClientBuilder::new_ephemeral().with_nym_api_client(http_client);
|
||||
|
||||
assert!(
|
||||
builder.build().is_ok(),
|
||||
"Builder should succeed with custom client"
|
||||
);
|
||||
}
|
||||
|
||||
// Note: Tests for entry_capable_nodes() vs entry_gateways() are in nym-topology crate
|
||||
// These tests verify the builder functionality only
|
||||
|
||||
#[test]
|
||||
fn test_custom_client_transfer_through_build() {
|
||||
let http_client = nym_http_api_client::Client::builder(
|
||||
nym_http_api_client::Url::parse("https://validator.nymtech.net/api").unwrap(),
|
||||
)
|
||||
.expect("Failed to create client builder")
|
||||
.build()
|
||||
.expect("Failed to build client");
|
||||
|
||||
let builder = MixnetClientBuilder::new_ephemeral().with_nym_api_client(http_client);
|
||||
let disconnected_client = builder.build();
|
||||
|
||||
assert!(
|
||||
disconnected_client.is_ok(),
|
||||
"Builder should succeed and domain fronting is handled via network_details"
|
||||
"Build should transfer custom_nym_api_client successfully"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_builder_storage_transfer_includes_custom_client() {
|
||||
use nym_client_core::client::base_client::storage::Ephemeral;
|
||||
|
||||
let http_client = nym_http_api_client::Client::builder(
|
||||
nym_http_api_client::Url::parse("https://validator.nymtech.net/api").unwrap(),
|
||||
)
|
||||
.expect("Failed to create client builder")
|
||||
.build()
|
||||
.expect("Failed to build client");
|
||||
|
||||
let builder = MixnetClientBuilder::new_ephemeral().with_nym_api_client(http_client);
|
||||
let new_storage = Ephemeral::default();
|
||||
let builder_with_new_storage = builder.set_storage(new_storage);
|
||||
|
||||
assert!(
|
||||
builder_with_new_storage.build().is_ok(),
|
||||
"set_storage should preserve custom_nym_api_client"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-network-requester"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.65"
|
||||
version = "1.1.66"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version = "1.85"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-cli"
|
||||
version = "1.1.64"
|
||||
version = "1.1.65"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
license.workspace = true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nymvisor"
|
||||
version = "0.1.29"
|
||||
version = "0.1.30"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
Reference in New Issue
Block a user