Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 351acb7875 | |||
| 7f4ef7f772 | |||
| 5025c49a0e |
@@ -54,7 +54,7 @@ jobs:
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --lib --manifest-path contracts/Cargo.toml --all-features
|
||||
args: --lib --manifest-path contracts/Cargo.toml
|
||||
|
||||
- name: Check formatting
|
||||
uses: actions-rs/cargo@v1
|
||||
|
||||
@@ -10,7 +10,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: arc-linux-latest
|
||||
runs-on: arc-ubuntu-22.04
|
||||
env:
|
||||
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||
defaults:
|
||||
|
||||
@@ -4,50 +4,6 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.18-jarlsberg] (2025-10-14)
|
||||
|
||||
- ns-api: add descriptions to dVPN gateway responses ([#6102])
|
||||
- NS API: use new probe download filesize and milliseconds field ([#6097])
|
||||
- ns-api: use download files size from probes instead of parsing filenames ([#6095])
|
||||
- ns-api: add new fields for probe output for query_metadata and download file size and duration in ms ([#6091])
|
||||
- Bugfix/bloomfilters purge ([#6089])
|
||||
- Hotfix: Update API source in node ping tester script ([#6082])
|
||||
- Get wireguard keypair as arg instead of reading it from disk ([#6078])
|
||||
- Feature: Ping probe all nodes /described nodes from a server ([#6074])
|
||||
- Node Status API: add bridge information to dVPN endpoint ([#6069])
|
||||
- frontdoor typo fix ([#6067])
|
||||
- Feature: Node rewards tracker ([#6064])
|
||||
- [chore] Clippy fix ([#6060])
|
||||
- Registration Client ([#6059])
|
||||
- Bugfix: Nym node CLI download nym-node exception ([#6058])
|
||||
- Feature: Nym node html landing page ([#6053])
|
||||
- feat: DKG contract method for updating announce address ([#6050])
|
||||
- feat: NS ticket faucet ([#6047])
|
||||
- Bridge proto client params in Self-Described ([#6035])
|
||||
- Node Status API: remove sqlite support ([#6004])
|
||||
- Benny/ci contract fix ([#5962])
|
||||
|
||||
[#6102]: https://github.com/nymtech/nym/pull/6102
|
||||
[#6097]: https://github.com/nymtech/nym/pull/6097
|
||||
[#6095]: https://github.com/nymtech/nym/pull/6095
|
||||
[#6091]: https://github.com/nymtech/nym/pull/6091
|
||||
[#6089]: https://github.com/nymtech/nym/pull/6089
|
||||
[#6082]: https://github.com/nymtech/nym/pull/6082
|
||||
[#6078]: https://github.com/nymtech/nym/pull/6078
|
||||
[#6074]: https://github.com/nymtech/nym/pull/6074
|
||||
[#6069]: https://github.com/nymtech/nym/pull/6069
|
||||
[#6067]: https://github.com/nymtech/nym/pull/6067
|
||||
[#6064]: https://github.com/nymtech/nym/pull/6064
|
||||
[#6060]: https://github.com/nymtech/nym/pull/6060
|
||||
[#6059]: https://github.com/nymtech/nym/pull/6059
|
||||
[#6058]: https://github.com/nymtech/nym/pull/6058
|
||||
[#6053]: https://github.com/nymtech/nym/pull/6053
|
||||
[#6050]: https://github.com/nymtech/nym/pull/6050
|
||||
[#6047]: https://github.com/nymtech/nym/pull/6047
|
||||
[#6035]: https://github.com/nymtech/nym/pull/6035
|
||||
[#6004]: https://github.com/nymtech/nym/pull/6004
|
||||
[#5962]: https://github.com/nymtech/nym/pull/5962
|
||||
|
||||
## [2025.17-isabirra] (2025-09-29)
|
||||
|
||||
- Bugfix | Fix the registration handshake ([#6062])
|
||||
|
||||
Generated
+811
-701
File diff suppressed because it is too large
Load Diff
+13
-2
@@ -215,6 +215,7 @@ base64 = "0.22.1"
|
||||
base85rs = "0.1.3"
|
||||
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.7.0"
|
||||
bloomfilter = "3.0.1"
|
||||
@@ -242,11 +243,13 @@ criterion = "0.5"
|
||||
csv = "1.3.1"
|
||||
ctr = "0.9.1"
|
||||
cupid = "0.6.1"
|
||||
curve25519-dalek = "4.1"
|
||||
dashmap = "5.5.3"
|
||||
# We want https://github.com/DefGuard/wireguard-rs/pull/64 , but there's no crates.io release being pushed out anymore
|
||||
defguard_wireguard_rs = { git = "https://github.com/DefGuard/wireguard-rs.git", rev = "v0.4.7" }
|
||||
digest = "0.10.7"
|
||||
dirs = "6.0"
|
||||
dirs = "5.0"
|
||||
doc-comment = "0.3"
|
||||
dotenvy = "0.15.6"
|
||||
dyn-clone = "1.0.19"
|
||||
ecdsa = "0.16"
|
||||
@@ -262,8 +265,11 @@ futures = "0.3.31"
|
||||
futures-util = "0.3"
|
||||
generic-array = "0.14.7"
|
||||
getrandom = "0.2.10"
|
||||
getset = "0.1.5"
|
||||
handlebars = "3.5.5"
|
||||
headers = "0.4.0"
|
||||
hex = "0.4.3"
|
||||
hex-literal = "0.3.3"
|
||||
hickory-resolver = "0.25"
|
||||
hkdf = "0.12.3"
|
||||
hmac = "0.12.1"
|
||||
@@ -287,10 +293,12 @@ lazy_static = "1.5.0"
|
||||
ledger-transport = "0.10.0"
|
||||
ledger-transport-hid = "0.10.0"
|
||||
log = "0.4"
|
||||
maxminddb = "0.23.0"
|
||||
mime = "0.3.17"
|
||||
moka = { version = "0.12", features = ["future"] }
|
||||
nix = "0.27.1"
|
||||
notify = "5.1.0"
|
||||
okapi = "0.7.0"
|
||||
once_cell = "1.21.3"
|
||||
opentelemetry = "0.19.0"
|
||||
opentelemetry-jaeger = "0.18.0"
|
||||
@@ -299,6 +307,7 @@ pem = "0.8"
|
||||
petgraph = "0.6.5"
|
||||
pin-project = "1.1"
|
||||
pnet_packet = "0.35.0"
|
||||
pin-project-lite = "0.2.16"
|
||||
publicsuffix = "2.3.0"
|
||||
proc_pidinfo = "0.1.3"
|
||||
quote = "1"
|
||||
@@ -306,10 +315,13 @@ rand = "0.8.5"
|
||||
rand_chacha = "0.3"
|
||||
rand_core = "0.6.3"
|
||||
rand_distr = "0.4"
|
||||
rand_pcg = "0.3.1"
|
||||
rand_seeder = "0.2.3"
|
||||
rayon = "1.5.1"
|
||||
regex = "1.10.6"
|
||||
reqwest = { version = "0.12.15", default-features = false }
|
||||
rs_merkle = "1.5.0"
|
||||
safer-ffi = "0.1.13"
|
||||
schemars = "0.8.22"
|
||||
semver = "1.0.26"
|
||||
serde = "1.0.219"
|
||||
@@ -356,7 +368,6 @@ tracing-indicatif = "0.3.9"
|
||||
tracing-test = "0.2.5"
|
||||
ts-rs = "10.1.0"
|
||||
tungstenite = { version = "0.20.1", default-features = false }
|
||||
typed-builder = "0.23.0"
|
||||
uniffi = "0.29.2"
|
||||
uniffi_build = "0.29.0"
|
||||
url = "2.5"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.64"
|
||||
version = "1.1.63"
|
||||
authors = ["Dave Hrycyszyn <futurechimp@users.noreply.github.com>", "Jędrzej Stuczyński <andrew@nymtech.net>"]
|
||||
description = "Implementation of the Nym Client"
|
||||
edition = "2021"
|
||||
|
||||
@@ -60,7 +60,6 @@ impl SocketClient {
|
||||
let ClientInput {
|
||||
connection_command_sender,
|
||||
input_sender,
|
||||
..
|
||||
} = client_input;
|
||||
|
||||
let ClientOutput {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.64"
|
||||
version = "1.1.63"
|
||||
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,12 +114,13 @@ 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,
|
||||
core.debug.topology.ignore_ingress_epoch_role,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
@@ -173,12 +173,13 @@ 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,
|
||||
core.debug.topology.ignore_ingress_epoch_role,
|
||||
None,
|
||||
)
|
||||
.await?
|
||||
};
|
||||
|
||||
@@ -7,12 +7,11 @@ use super::statistics_control::StatisticsControl;
|
||||
use crate::client::base_client::storage::helpers::store_client_keys;
|
||||
use crate::client::base_client::storage::MixnetClientStorage;
|
||||
use crate::client::cover_traffic_stream::LoopCoverTrafficStream;
|
||||
use crate::client::event_control::EventControl;
|
||||
use crate::client::inbound_messages::{InputMessage, InputMessageReceiver, InputMessageSender};
|
||||
use crate::client::key_manager::persistence::KeyStore;
|
||||
use crate::client::key_manager::ClientKeys;
|
||||
use crate::client::mix_traffic::transceiver::{GatewayReceiver, GatewayTransceiver, RemoteGateway};
|
||||
use crate::client::mix_traffic::{BatchMixMessageSender, MixTrafficController, MixTrafficEvent};
|
||||
use crate::client::mix_traffic::{BatchMixMessageSender, MixTrafficController};
|
||||
use crate::client::real_messages_control;
|
||||
use crate::client::real_messages_control::RealMessagesController;
|
||||
use crate::client::received_buffer::{
|
||||
@@ -67,16 +66,13 @@ use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use time::OffsetDateTime;
|
||||
use tokio::sync::mpsc::Sender;
|
||||
use tracing::*;
|
||||
use url::Url;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[cfg(debug_assertions)]
|
||||
use wasm_utils::console_log;
|
||||
|
||||
/// Default number of retries for Nym API requests when using network details with domain fronting.
|
||||
/// This allows the client to try alternative URLs if the primary endpoint is unavailable.
|
||||
const DEFAULT_NYM_API_RETRIES: usize = 3;
|
||||
|
||||
#[cfg(all(
|
||||
not(target_arch = "wasm32"),
|
||||
feature = "fs-surb-storage",
|
||||
@@ -87,28 +83,10 @@ pub mod non_wasm_helpers;
|
||||
pub mod helpers;
|
||||
pub mod storage;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum MixnetClientEvent {
|
||||
Traffic(MixTrafficEvent),
|
||||
}
|
||||
|
||||
pub type EventReceiver = mpsc::UnboundedReceiver<MixnetClientEvent>;
|
||||
#[derive(Clone)]
|
||||
pub struct EventSender(pub mpsc::UnboundedSender<MixnetClientEvent>);
|
||||
|
||||
impl EventSender {
|
||||
pub fn send(&self, event: MixnetClientEvent) {
|
||||
if let Err(err) = self.0.unbounded_send(event) {
|
||||
tracing::warn!("Failed to send error event. The caller event reader was closed: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ClientInput {
|
||||
pub connection_command_sender: ConnectionCommandSender,
|
||||
pub input_sender: InputMessageSender,
|
||||
pub client_request_sender: ClientRequestSender,
|
||||
}
|
||||
|
||||
impl ClientInput {
|
||||
@@ -216,14 +194,10 @@ pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
|
||||
client_store: S,
|
||||
dkg_query_client: Option<C>,
|
||||
|
||||
// Optional API URLs for domain fronting support
|
||||
nym_api_urls: Option<Vec<nym_network_defaults::ApiUrl>>,
|
||||
|
||||
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>,
|
||||
|
||||
setup_method: GatewaySetup,
|
||||
@@ -248,12 +222,10 @@ where
|
||||
config: base_config,
|
||||
client_store,
|
||||
dkg_query_client,
|
||||
nym_api_urls: None,
|
||||
wait_for_gateway: false,
|
||||
custom_topology_provider: None,
|
||||
custom_gateway_transceiver: None,
|
||||
shutdown: None,
|
||||
event_tx: None,
|
||||
user_agent: None,
|
||||
setup_method: GatewaySetup::MustLoad { gateway_id: None },
|
||||
#[cfg(unix)]
|
||||
@@ -271,16 +243,6 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
/// Set Nym API URLs for domain fronting support.
|
||||
///
|
||||
/// When provided, the client will use these API URLs (which include front_hosts)
|
||||
/// to construct HTTP clients with domain fronting enabled.
|
||||
#[must_use]
|
||||
pub fn with_nym_api_urls(mut self, nym_api_urls: Vec<nym_network_defaults::ApiUrl>) -> Self {
|
||||
self.nym_api_urls = Some(nym_api_urls);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_forget_me(mut self, forget_me: &ForgetMe) -> Self {
|
||||
self.config.debug.forget_me = *forget_me;
|
||||
@@ -326,12 +288,6 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_event_tx(mut self, event_tx: EventSender) -> Self {
|
||||
self.event_tx = Some(event_tx);
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_user_agent(mut self, user_agent: UserAgent) -> Self {
|
||||
self.user_agent = Some(user_agent);
|
||||
@@ -362,18 +318,6 @@ where
|
||||
details.client_address()
|
||||
}
|
||||
|
||||
fn start_event_control(
|
||||
parent_event_tx: Option<EventSender>,
|
||||
children_event_rx: EventReceiver,
|
||||
shutdown_tracker: &ShutdownTracker,
|
||||
) {
|
||||
let event_control = EventControl::new(parent_event_tx, children_event_rx);
|
||||
shutdown_tracker.try_spawn_named_with_shutdown(
|
||||
async move { event_control.run().await },
|
||||
"EventControl",
|
||||
);
|
||||
}
|
||||
|
||||
// future constantly pumping loop cover traffic at some specified average rate
|
||||
// the pumped traffic goes to the MixTrafficController
|
||||
fn start_cover_traffic_stream(
|
||||
@@ -385,7 +329,7 @@ where
|
||||
stats_tx: ClientStatsSender,
|
||||
shutdown_tracker: &ShutdownTracker,
|
||||
) {
|
||||
tracing::info!("Starting loop cover traffic stream...");
|
||||
info!("Starting loop cover traffic stream...");
|
||||
|
||||
let mut stream = LoopCoverTrafficStream::new(
|
||||
ack_key,
|
||||
@@ -397,8 +341,14 @@ where
|
||||
debug_config.cover_traffic,
|
||||
stats_tx,
|
||||
);
|
||||
shutdown_tracker
|
||||
.try_spawn_named_with_shutdown(async move { stream.run().await }, "CoverTrafficStream");
|
||||
let drop_guard = shutdown_tracker.clone_shutdown_token().drop_guard();
|
||||
shutdown_tracker.try_spawn_named_with_shutdown(
|
||||
async move {
|
||||
let _ = drop_guard;
|
||||
stream.run().await
|
||||
},
|
||||
"CoverTrafficStream",
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -417,7 +367,7 @@ where
|
||||
stats_tx: ClientStatsSender,
|
||||
shutdown_tracker: &ShutdownTracker,
|
||||
) {
|
||||
tracing::info!("Starting real traffic stream...");
|
||||
info!("Starting real traffic stream...");
|
||||
|
||||
let real_messages_controller = RealMessagesController::new(
|
||||
controller_config,
|
||||
@@ -475,8 +425,10 @@ where
|
||||
"AcknowledgementController::RetransmissionRequestListener",
|
||||
);
|
||||
|
||||
let drop_guard = shutdown_tracker.clone_shutdown_token().drop_guard();
|
||||
shutdown_tracker.try_spawn_named_with_shutdown(
|
||||
async move {
|
||||
let _ = drop_guard;
|
||||
sent_notification_listener.run().await;
|
||||
},
|
||||
"AcknowledgementController::SentNotificationListener",
|
||||
@@ -487,8 +439,6 @@ where
|
||||
async move { ack_action_controller.run(shutdown_token).await },
|
||||
"AcknowledgementController::ActionController",
|
||||
);
|
||||
|
||||
// .start(packet_type);
|
||||
}
|
||||
|
||||
// buffer controlling all messages fetched from provider
|
||||
@@ -502,7 +452,7 @@ where
|
||||
metrics_reporter: ClientStatsSender,
|
||||
shutdown_tracker: &ShutdownTracker,
|
||||
) {
|
||||
tracing::info!("Starting received messages buffer controller...");
|
||||
info!("Starting received messages buffer controller...");
|
||||
let controller = ReceivedMessagesBufferController::<SphinxMessageReceiver>::new(
|
||||
local_encryption_keypair,
|
||||
query_receiver,
|
||||
@@ -613,7 +563,7 @@ where
|
||||
details_store
|
||||
.upgrade_stored_remote_gateway_key(gateway_client.gateway_identity(), &updated_key)
|
||||
.await.map_err(|err| {
|
||||
tracing::error!("failed to store upgraded gateway key! this connection might be forever broken now: {err}");
|
||||
error!("failed to store upgraded gateway key! this connection might be forever broken now: {err}");
|
||||
ClientCoreError::GatewaysDetailsStoreError { source: Box::new(err) }
|
||||
})?
|
||||
}
|
||||
@@ -710,7 +660,7 @@ where
|
||||
|
||||
if topology_config.disable_refreshing {
|
||||
// if we're not spawning the refresher, don't cause shutdown immediately
|
||||
tracing::info!("The background topology refresher is not going to be started");
|
||||
info!("The background topology refresher is not going to be started");
|
||||
}
|
||||
|
||||
let mut topology_refresher = TopologyRefresher::new(
|
||||
@@ -720,7 +670,7 @@ where
|
||||
);
|
||||
// before returning, block entire runtime to refresh the current network view so that any
|
||||
// components depending on topology would see a non-empty view
|
||||
tracing::info!("Obtaining initial network topology");
|
||||
info!("Obtaining initial network topology");
|
||||
topology_refresher.try_refresh().await;
|
||||
|
||||
if let Err(err) = topology_refresher.ensure_topology_is_routable().await {
|
||||
@@ -746,13 +696,13 @@ where
|
||||
.wait_for_gateway(local_gateway, waiting_timeout)
|
||||
.await
|
||||
{
|
||||
tracing::error!(
|
||||
error!(
|
||||
"the gateway did not come back online within the specified timeout: {err}"
|
||||
);
|
||||
return Err(err.into());
|
||||
}
|
||||
} else {
|
||||
tracing::error!("the gateway we're supposedly connected to does not exist. We'll not be able to send any packets to ourselves: {err}");
|
||||
error!("the gateway we're supposedly connected to does not exist. We'll not be able to send any packets to ourselves: {err}");
|
||||
return Err(err.into());
|
||||
}
|
||||
}
|
||||
@@ -760,9 +710,13 @@ where
|
||||
if !topology_config.disable_refreshing {
|
||||
// don't spawn the refresher if we don't want to be refreshing the topology.
|
||||
// only use the initial values obtained
|
||||
tracing::info!("Starting topology refresher...");
|
||||
info!("Starting topology refresher...");
|
||||
let drop_guard = shutdown_tracker.clone_shutdown_token().drop_guard();
|
||||
shutdown_tracker.try_spawn_named_with_shutdown(
|
||||
async move { topology_refresher.run().await },
|
||||
async move {
|
||||
let _ = drop_guard;
|
||||
topology_refresher.run().await
|
||||
},
|
||||
"TopologyRefresher",
|
||||
);
|
||||
}
|
||||
@@ -777,7 +731,7 @@ where
|
||||
input_sender: Sender<InputMessage>,
|
||||
shutdown_tracker: &ShutdownTracker,
|
||||
) -> ClientStatsSender {
|
||||
tracing::info!("Starting statistics control...");
|
||||
info!("Starting statistics control...");
|
||||
StatisticsControl::create_and_start(
|
||||
config.debug.stats_reporting,
|
||||
user_agent
|
||||
@@ -792,17 +746,10 @@ where
|
||||
fn start_mix_traffic_controller(
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
shutdown_tracker: &ShutdownTracker,
|
||||
event_tx: EventSender,
|
||||
) -> (BatchMixMessageSender, ClientRequestSender) {
|
||||
tracing::info!("Starting mix traffic controller...");
|
||||
let mut mix_traffic_controller = MixTrafficController::new(
|
||||
gateway_transceiver,
|
||||
shutdown_tracker.clone_shutdown_token(),
|
||||
event_tx,
|
||||
);
|
||||
|
||||
let mix_tx = mix_traffic_controller.mix_tx();
|
||||
let client_tx = mix_traffic_controller.client_tx();
|
||||
info!("Starting mix traffic controller...");
|
||||
let (mut mix_traffic_controller, mix_tx, client_tx) =
|
||||
MixTrafficController::new(gateway_transceiver, shutdown_tracker.clone_shutdown_token());
|
||||
|
||||
shutdown_tracker.try_spawn_named(
|
||||
async move { mix_traffic_controller.run().await },
|
||||
@@ -866,7 +813,7 @@ where
|
||||
{
|
||||
// if client keys do not exist already, create and persist them
|
||||
if key_store.load_keys().await.is_err() {
|
||||
tracing::info!("could not find valid client keys - a new set will be generated");
|
||||
info!("could not find valid client keys - a new set will be generated");
|
||||
let mut rng = OsRng;
|
||||
let keys = if let Some(derivation_material) = derivation_material {
|
||||
ClientKeys::from_master_key(&mut rng, &derivation_material)
|
||||
@@ -881,67 +828,21 @@ where
|
||||
}
|
||||
|
||||
fn construct_nym_api_client(
|
||||
nym_api_urls: Option<&Vec<nym_network_defaults::ApiUrl>>,
|
||||
config: &Config,
|
||||
user_agent: Option<UserAgent>,
|
||||
) -> Result<nym_http_api_client::Client, ClientCoreError> {
|
||||
tracing::debug!(
|
||||
"construct_nym_api_client called with nym_api_urls: {}",
|
||||
nym_api_urls.is_some()
|
||||
);
|
||||
|
||||
// If API URLs are provided, use new_with_fronted_urls() which handles domain fronting
|
||||
if let Some(nym_api_urls) = nym_api_urls {
|
||||
if nym_api_urls.is_empty() {
|
||||
tracing::warn!("Provided nym_api_urls is empty, falling back to config endpoints");
|
||||
} else {
|
||||
tracing::info!(
|
||||
"Building nym-api client from provided URLs (with domain fronting support): {} URLs",
|
||||
nym_api_urls.len()
|
||||
);
|
||||
|
||||
let mut builder =
|
||||
nym_http_api_client::ClientBuilder::new_with_fronted_urls(nym_api_urls.clone())
|
||||
.map_err(ClientCoreError::from)?
|
||||
.with_retries(DEFAULT_NYM_API_RETRIES);
|
||||
|
||||
if let Some(user_agent) = user_agent {
|
||||
builder = builder.with_user_agent(user_agent);
|
||||
}
|
||||
|
||||
return builder.build().map_err(ClientCoreError::from);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to basic client for backwards compatibility
|
||||
tracing::debug!("Building basic nym-api HTTP client from config endpoints");
|
||||
|
||||
let mut nym_api_urls = config.get_nym_api_endpoints();
|
||||
if nym_api_urls.is_empty() {
|
||||
tracing::warn!("No API endpoints configured in config, this may cause issues");
|
||||
}
|
||||
nym_api_urls.shuffle(&mut thread_rng());
|
||||
|
||||
// Convert config URLs to ApiUrl format for consistency
|
||||
let api_urls: Vec<nym_network_defaults::ApiUrl> = nym_api_urls
|
||||
.into_iter()
|
||||
.map(|url| nym_network_defaults::ApiUrl {
|
||||
url: url.to_string(),
|
||||
front_hosts: None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
tracing::debug!("Using {} config API endpoints", api_urls.len());
|
||||
|
||||
let mut builder = nym_http_api_client::ClientBuilder::new_with_fronted_urls(api_urls)
|
||||
.map_err(ClientCoreError::from)?
|
||||
.with_retries(DEFAULT_NYM_API_RETRIES)
|
||||
.with_bincode();
|
||||
let mut builder = nym_http_api_client::Client::builder(nym_api_urls[0].clone())
|
||||
.map_err(ClientCoreError::from)?;
|
||||
|
||||
if let Some(user_agent) = user_agent {
|
||||
builder = builder.with_user_agent(user_agent);
|
||||
}
|
||||
|
||||
builder = builder.with_bincode();
|
||||
|
||||
builder.build().map_err(ClientCoreError::from)
|
||||
}
|
||||
|
||||
@@ -959,7 +860,7 @@ where
|
||||
<S::CredentialStore as CredentialStorage>::StorageError: Send + Sync + 'static,
|
||||
<S::GatewaysDetailsStore as GatewaysDetailsStore>::StorageError: Sync + Send,
|
||||
{
|
||||
tracing::info!("Starting nym client");
|
||||
info!("Starting nym client");
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
@@ -993,9 +894,6 @@ where
|
||||
// channels responsible for controlling real messages
|
||||
let (input_sender, input_receiver) = tokio::sync::mpsc::channel::<InputMessage>(1);
|
||||
|
||||
// channels responsible for event management
|
||||
let (event_sender, event_receiver) = mpsc::unbounded();
|
||||
|
||||
// channels responsible for controlling ack messages
|
||||
let (ack_sender, ack_receiver) = mpsc::unbounded();
|
||||
let shared_topology_accessor =
|
||||
@@ -1005,11 +903,9 @@ where
|
||||
// or get one from the registry
|
||||
let shutdown_tracker = match self.shutdown {
|
||||
Some(parent_tracker) => parent_tracker.clone(),
|
||||
None => nym_task::create_sdk_shutdown_tracker()?,
|
||||
None => nym_task::get_sdk_shutdown_tracker()?,
|
||||
};
|
||||
|
||||
Self::start_event_control(self.event_tx, event_receiver, &shutdown_tracker);
|
||||
|
||||
// channels responsible for dealing with reply-related fun
|
||||
let (reply_controller_sender, reply_controller_receiver) =
|
||||
reply_controller::requests::new_control_channels();
|
||||
@@ -1025,11 +921,7 @@ where
|
||||
.dkg_query_client
|
||||
.map(|client| BandwidthController::new(credential_store, client));
|
||||
|
||||
let nym_api_client = Self::construct_nym_api_client(
|
||||
self.nym_api_urls.as_ref(),
|
||||
&self.config,
|
||||
self.user_agent.clone(),
|
||||
)?;
|
||||
let nym_api_client = Self::construct_nym_api_client(&self.config, self.user_agent.clone())?;
|
||||
let key_rotation_config = Self::determine_key_rotation_state(&nym_api_client).await?;
|
||||
|
||||
let topology_provider = Self::setup_topology_provider(
|
||||
@@ -1044,7 +936,7 @@ where
|
||||
self.user_agent.clone(),
|
||||
generate_client_stats_id(*self_address.identity()),
|
||||
input_sender.clone(),
|
||||
&shutdown_tracker.clone(),
|
||||
&shutdown_tracker,
|
||||
);
|
||||
|
||||
// needs to be started as the first thing to block if required waiting for the gateway
|
||||
@@ -1054,7 +946,7 @@ where
|
||||
shared_topology_accessor.clone(),
|
||||
self_address.gateway(),
|
||||
self.wait_for_gateway,
|
||||
&shutdown_tracker.clone(),
|
||||
&shutdown_tracker,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1074,7 +966,7 @@ where
|
||||
stats_reporter.clone(),
|
||||
#[cfg(unix)]
|
||||
self.connection_fd_callback,
|
||||
&shutdown_tracker.clone(),
|
||||
&shutdown_tracker,
|
||||
)
|
||||
.await?;
|
||||
let gateway_ws_fd = gateway_transceiver.ws_fd();
|
||||
@@ -1082,7 +974,7 @@ where
|
||||
let reply_storage = Self::setup_persistent_reply_storage(
|
||||
reply_storage_backend,
|
||||
key_rotation_config,
|
||||
&shutdown_tracker.clone(),
|
||||
&shutdown_tracker,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -1093,7 +985,7 @@ where
|
||||
reply_storage.key_storage(),
|
||||
reply_controller_sender.clone(),
|
||||
stats_reporter.clone(),
|
||||
&shutdown_tracker.clone(),
|
||||
&shutdown_tracker,
|
||||
);
|
||||
|
||||
// The message_sender is the transmitter for any component generating sphinx packets
|
||||
@@ -1101,11 +993,8 @@ where
|
||||
// traffic stream.
|
||||
// The MixTrafficController then sends the actual traffic
|
||||
|
||||
let (message_sender, client_request_sender) = Self::start_mix_traffic_controller(
|
||||
gateway_transceiver,
|
||||
&shutdown_tracker.clone(),
|
||||
EventSender(event_sender),
|
||||
);
|
||||
let (message_sender, client_request_sender) =
|
||||
Self::start_mix_traffic_controller(gateway_transceiver, &shutdown_tracker);
|
||||
|
||||
// Channels that the websocket listener can use to signal downstream to the real traffic
|
||||
// controller that connections are closed.
|
||||
@@ -1134,7 +1023,7 @@ where
|
||||
shared_lane_queue_lengths.clone(),
|
||||
client_connection_rx,
|
||||
stats_reporter.clone(),
|
||||
&shutdown_tracker.clone(),
|
||||
&shutdown_tracker,
|
||||
);
|
||||
|
||||
if !self
|
||||
@@ -1150,12 +1039,12 @@ where
|
||||
shared_topology_accessor.clone(),
|
||||
message_sender,
|
||||
stats_reporter.clone(),
|
||||
&shutdown_tracker.clone(),
|
||||
&shutdown_tracker,
|
||||
);
|
||||
}
|
||||
|
||||
tracing::debug!("Core client startup finished!");
|
||||
tracing::debug!("The address of this client is: {self_address}");
|
||||
debug!("Core client startup finished!");
|
||||
debug!("The address of this client is: {self_address}");
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -1171,7 +1060,6 @@ where
|
||||
client_input: ClientInput {
|
||||
connection_command_sender: client_connection_tx,
|
||||
input_sender,
|
||||
client_request_sender,
|
||||
},
|
||||
},
|
||||
client_output: ClientOutputStatus::AwaitingConsumer {
|
||||
@@ -1187,6 +1075,7 @@ where
|
||||
},
|
||||
stats_reporter,
|
||||
shutdown_handle: shutdown_tracker, // The primary tracker for this client
|
||||
client_request_sender,
|
||||
forget_me: self.config.debug.forget_me,
|
||||
remember_me: self.config.debug.remember_me,
|
||||
})
|
||||
@@ -1200,57 +1089,8 @@ pub struct BaseClient {
|
||||
pub client_output: ClientOutputStatus,
|
||||
pub client_state: ClientState,
|
||||
pub stats_reporter: ClientStatsSender,
|
||||
pub client_request_sender: ClientRequestSender,
|
||||
pub shutdown_handle: ShutdownTracker,
|
||||
pub forget_me: ForgetMe,
|
||||
pub remember_me: RememberMe,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use nym_network_defaults::{ApiUrl, NymNetworkDetails};
|
||||
|
||||
#[test]
|
||||
fn test_network_details_with_multiple_urls() {
|
||||
// Verify that network details can be configured with multiple API URLs
|
||||
let mut network_details = NymNetworkDetails::new_empty();
|
||||
network_details.nym_api_urls = Some(vec![
|
||||
ApiUrl {
|
||||
url: "https://validator.nymtech.net/api/".to_string(),
|
||||
front_hosts: None,
|
||||
},
|
||||
ApiUrl {
|
||||
url: "https://nym-frontdoor.vercel.app/api/".to_string(),
|
||||
front_hosts: Some(vec!["vercel.app".to_string(), "vercel.com".to_string()]),
|
||||
},
|
||||
]);
|
||||
|
||||
assert_eq!(network_details.nym_api_urls.as_ref().unwrap().len(), 2);
|
||||
assert!(network_details.nym_api_urls.as_ref().unwrap()[1]
|
||||
.front_hosts
|
||||
.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_network_details_with_front_hosts() {
|
||||
// Verify that ApiUrl can store domain fronting configuration
|
||||
let api_url = ApiUrl {
|
||||
url: "https://nym-frontdoor.vercel.app/api/".to_string(),
|
||||
front_hosts: Some(vec!["vercel.app".to_string(), "vercel.com".to_string()]),
|
||||
};
|
||||
|
||||
assert_eq!(api_url.url, "https://nym-frontdoor.vercel.app/api/");
|
||||
assert_eq!(api_url.front_hosts.as_ref().unwrap().len(), 2);
|
||||
assert!(api_url
|
||||
.front_hosts
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.contains(&"vercel.app".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_nym_api_retries_constant() {
|
||||
// Verify the retry constant is set correctly
|
||||
assert_eq!(DEFAULT_NYM_API_RETRIES, 3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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::trace!("Failed to send cover message - channel full");
|
||||
tracing::debug!("Failed to send cover message - channel full");
|
||||
}
|
||||
TrySendError::Closed(_) => {
|
||||
tracing::warn!("Failed to send cover message - channel closed");
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use futures::StreamExt;
|
||||
|
||||
use crate::client::base_client::{EventReceiver, EventSender, MixnetClientEvent};
|
||||
|
||||
/// Launches and manages task events, propagating upwards what is not strictly internal.
|
||||
pub(crate) struct EventControl {
|
||||
parent_event_tx: Option<EventSender>,
|
||||
children_event_rx: EventReceiver,
|
||||
}
|
||||
|
||||
impl EventControl {
|
||||
pub(crate) fn new(
|
||||
parent_event_tx: Option<EventSender>,
|
||||
children_event_rx: EventReceiver,
|
||||
) -> Self {
|
||||
EventControl {
|
||||
parent_event_tx,
|
||||
children_event_rx,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_internal(event: MixnetClientEvent) -> bool {
|
||||
match event {
|
||||
MixnetClientEvent::Traffic(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn run(mut self) {
|
||||
while let Some(event) = self.children_event_rx.next().await {
|
||||
if let Some(parent_event_tx) = &self.parent_event_tx {
|
||||
if !Self::is_internal(event) {
|
||||
parent_event_tx.send(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
// Copyright 2021 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::{
|
||||
base_client::{EventSender, MixnetClientEvent},
|
||||
mix_traffic::transceiver::GatewayTransceiver,
|
||||
};
|
||||
use crate::client::mix_traffic::transceiver::GatewayTransceiver;
|
||||
use nym_gateway_requests::ClientRequest;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_task::ShutdownToken;
|
||||
@@ -20,41 +17,33 @@ 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;
|
||||
|
||||
/// 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;
|
||||
const MAX_FAILURE_COUNT: usize = 100;
|
||||
|
||||
// that's also disgusting.
|
||||
pub struct Empty;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum MixTrafficEvent {
|
||||
FailedSendingSphinx,
|
||||
}
|
||||
|
||||
pub struct MixTrafficController {
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
|
||||
mix_tx: BatchMixMessageSender,
|
||||
mix_rx: BatchMixMessageReceiver,
|
||||
client_rx: ClientRequestReceiver,
|
||||
client_tx: ClientRequestSender,
|
||||
|
||||
// TODO: this is temporary work-around.
|
||||
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
|
||||
consecutive_gateway_failure_count: usize,
|
||||
|
||||
shutdown_token: ShutdownToken,
|
||||
event_tx: EventSender,
|
||||
}
|
||||
|
||||
impl MixTrafficController {
|
||||
pub fn new<T>(
|
||||
gateway_transceiver: T,
|
||||
shutdown_token: ShutdownToken,
|
||||
event_tx: EventSender,
|
||||
) -> MixTrafficController
|
||||
) -> (
|
||||
MixTrafficController,
|
||||
BatchMixMessageSender,
|
||||
ClientRequestSender,
|
||||
)
|
||||
where
|
||||
T: GatewayTransceiver + Send + 'static,
|
||||
{
|
||||
@@ -63,32 +52,41 @@ impl MixTrafficController {
|
||||
|
||||
let (client_sender, client_receiver) = tokio::sync::mpsc::channel(8);
|
||||
|
||||
MixTrafficController {
|
||||
gateway_transceiver: Box::new(gateway_transceiver),
|
||||
mix_tx: message_sender,
|
||||
mix_rx: message_receiver,
|
||||
client_rx: client_receiver,
|
||||
client_tx: client_sender,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
shutdown_token,
|
||||
event_tx,
|
||||
}
|
||||
(
|
||||
MixTrafficController {
|
||||
gateway_transceiver: Box::new(gateway_transceiver),
|
||||
mix_rx: message_receiver,
|
||||
client_rx: client_receiver,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
shutdown_token,
|
||||
},
|
||||
message_sender,
|
||||
client_sender,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_dynamic(
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
shutdown_token: ShutdownToken,
|
||||
event_tx: EventSender,
|
||||
) -> MixTrafficController {
|
||||
Self::new(gateway_transceiver, shutdown_token, event_tx)
|
||||
}
|
||||
|
||||
pub fn client_tx(&self) -> ClientRequestSender {
|
||||
self.client_tx.clone()
|
||||
}
|
||||
|
||||
pub fn mix_tx(&self) -> BatchMixMessageSender {
|
||||
self.mix_tx.clone()
|
||||
) -> (
|
||||
MixTrafficController,
|
||||
BatchMixMessageSender,
|
||||
ClientRequestSender,
|
||||
) {
|
||||
let (message_sender, message_receiver) =
|
||||
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
|
||||
let (client_sender, client_receiver) = tokio::sync::mpsc::channel(8);
|
||||
(
|
||||
MixTrafficController {
|
||||
gateway_transceiver,
|
||||
mix_rx: message_receiver,
|
||||
client_rx: client_receiver,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
shutdown_token,
|
||||
},
|
||||
message_sender,
|
||||
client_sender,
|
||||
)
|
||||
}
|
||||
|
||||
async fn on_messages(
|
||||
@@ -140,6 +138,7 @@ impl MixTrafficController {
|
||||
|
||||
pub async fn run(&mut self) {
|
||||
debug!("Started MixTrafficController with graceful shutdown support");
|
||||
let _drop_guard = self.shutdown_token.clone().drop_guard();
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
@@ -147,31 +146,34 @@ impl MixTrafficController {
|
||||
trace!("MixTrafficController: Received shutdown");
|
||||
break;
|
||||
}
|
||||
// mix_rx should never error out as we're holding one instance of the sender
|
||||
|
||||
Some(mix_packets) = self.mix_rx.recv() => {
|
||||
if let Err(err) = self.on_messages(mix_packets).await {
|
||||
error!("Failed to send sphinx packet(s) to the gateway: {err}");
|
||||
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
|
||||
// Disconnect from the gateway. If we should try to re-connect
|
||||
// is handled at a higher layer.
|
||||
error!("Failed to send sphinx packet to the gateway {MAX_FAILURE_COUNT} times in a row - assuming the gateway is dead");
|
||||
// Do we need to handle the embedded mixnet client case
|
||||
// separately?
|
||||
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;
|
||||
mix_packets = self.mix_rx.recv() => match mix_packets {
|
||||
Some(mix_packets) => {
|
||||
if let Err(err) = self.on_messages(mix_packets).await {
|
||||
error!("Failed to send sphinx packet(s) to the gateway: {err}");
|
||||
if self.consecutive_gateway_failure_count == MAX_FAILURE_COUNT {
|
||||
// Disconnect from the gateway. If we should try to re-connect
|
||||
// is handled at a higher layer.
|
||||
error!("Failed to send sphinx packet to the gateway {MAX_FAILURE_COUNT} times in a row - assuming the gateway is dead");
|
||||
// Do we need to handle the embedded mixnet client case
|
||||
// separately?
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
trace!("MixTrafficController: Stopping since channel closed");
|
||||
break;
|
||||
}
|
||||
},
|
||||
client_request = self.client_rx.recv() => match client_request {
|
||||
Some(client_request) => {
|
||||
self.on_client_request(client_request).await;
|
||||
},
|
||||
None => {
|
||||
trace!("MixTrafficController, client request channel closed");
|
||||
break
|
||||
}
|
||||
},
|
||||
// client_rx should never error out as we're holding one instance of the sender
|
||||
Some(client_request) = self.client_rx.recv() => {
|
||||
self.on_client_request(client_request).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
debug!("MixTrafficController: Exiting");
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
pub mod base_client;
|
||||
pub mod cover_traffic_stream;
|
||||
pub(crate) mod event_control;
|
||||
pub(crate) mod helpers;
|
||||
pub mod inbound_messages;
|
||||
pub mod key_manager;
|
||||
|
||||
+1
@@ -80,6 +80,7 @@ impl AcknowledgementListener {
|
||||
pub(crate) async fn run(&mut self, shutdown_token: ShutdownToken) {
|
||||
debug!("Started AcknowledgementListener with graceful shutdown support");
|
||||
|
||||
let _drop_guard = shutdown_token.clone().drop_guard();
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
|
||||
+1
@@ -245,6 +245,7 @@ impl ActionController {
|
||||
pub(crate) async fn run(&mut self, shutdown_token: ShutdownToken) {
|
||||
debug!("Started ActionController with graceful shutdown support");
|
||||
|
||||
let _drop_guard = shutdown_token.clone().drop_guard();
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
|
||||
+1
@@ -216,6 +216,7 @@ where
|
||||
pub(crate) async fn run(&mut self, shutdown_token: ShutdownToken) {
|
||||
debug!("Started InputMessageListener with graceful shutdown support");
|
||||
|
||||
let _drop_guard = shutdown_token.clone().drop_guard();
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
|
||||
+1
@@ -167,6 +167,7 @@ where
|
||||
pub(crate) async fn run(&mut self, shutdown_token: ShutdownToken) {
|
||||
debug!("Started RetransmissionRequestListener with graceful shutdown support");
|
||||
|
||||
let _drop_guard = shutdown_token.clone().drop_guard();
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
|
||||
@@ -298,8 +298,6 @@ 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() {
|
||||
@@ -587,6 +585,7 @@ where
|
||||
|
||||
// avoid borrow on self
|
||||
let shutdown_token = self.shutdown_token.clone();
|
||||
let _drop_guard = shutdown_token.clone().drop_guard();
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let mut status_timer = tokio::time::interval(Duration::from_secs(5));
|
||||
|
||||
@@ -497,6 +497,8 @@ impl<R: MessageReceiver> RequestReceiver<R> {
|
||||
|
||||
pub(crate) async fn run(&mut self) {
|
||||
debug!("Started RequestReceiver with graceful shutdown support");
|
||||
|
||||
let _drop_guard = self.shutdown_token.clone().drop_guard();
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
@@ -540,6 +542,8 @@ impl<R: MessageReceiver> FragmentedMessageReceiver<R> {
|
||||
|
||||
pub(crate) async fn run(&mut self) -> Result<(), MessageRecoveryError> {
|
||||
debug!("Started FragmentedMessageReceiver with graceful shutdown support");
|
||||
|
||||
let _drop_guard = self.shutdown_token.clone().drop_guard();
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
|
||||
@@ -152,6 +152,7 @@ where
|
||||
let polling_rate = self.config.key_rotation.epoch_duration / 8;
|
||||
let mut invalidation_inspection = new_interval_stream(polling_rate);
|
||||
|
||||
let _drop_guard = shutdown_token.clone().drop_guard();
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
|
||||
@@ -119,6 +119,8 @@ impl StatisticsControl {
|
||||
let mut snapshot_interval =
|
||||
gloo_timers::future::IntervalStream::new(SNAPSHOT_INTERVAL.as_millis() as u32);
|
||||
|
||||
let _drop_guard = shutdown_token.clone().drop_guard();
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
|
||||
@@ -45,7 +45,6 @@ type WsConn = JSWebsocket;
|
||||
|
||||
const CONCURRENT_GATEWAYS_MEASURED: usize = 20;
|
||||
const MEASUREMENTS: usize = 3;
|
||||
const DEFAULT_NYM_API_RETRIES: usize = 3;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
const CONN_TIMEOUT: Duration = Duration::from_millis(1500);
|
||||
@@ -133,27 +132,25 @@ 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,
|
||||
ignore_epoch_roles: bool,
|
||||
retry_count: Option<usize>,
|
||||
) -> Result<Vec<RoutingNode>, ClientCoreError> {
|
||||
// Build client with ALL URLs for fallback support
|
||||
let nym_api_urls: Vec<nym_http_api_client::Url> = nym_apis
|
||||
.iter()
|
||||
.map(|url| nym_http_api_client::Url::from(url.clone()))
|
||||
.collect();
|
||||
let nym_api = nym_apis
|
||||
.choose(rng)
|
||||
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
|
||||
|
||||
if nym_api_urls.is_empty() {
|
||||
return Err(ClientCoreError::ListOfNymApisIsEmpty);
|
||||
}
|
||||
|
||||
let retry_count = retry_count.unwrap_or(DEFAULT_NYM_API_RETRIES);
|
||||
let mut builder = nym_http_api_client::ClientBuilder::new_with_urls(nym_api_urls.clone())?
|
||||
.with_retries(retry_count)
|
||||
.with_bincode();
|
||||
// Use the unified HTTP client directly with optional user agent
|
||||
let mut builder = nym_http_api_client::Client::builder(nym_api.clone())
|
||||
.map_err(|e| {
|
||||
ClientCoreError::ValidatorClientError(nym_validator_client::ValidatorClientError::from(
|
||||
e,
|
||||
))
|
||||
})?
|
||||
.with_bincode(); // Use bincode for better performance
|
||||
|
||||
if let Some(user_agent) = user_agent {
|
||||
builder = builder.with_user_agent(user_agent);
|
||||
@@ -163,7 +160,7 @@ pub async fn gateways_for_init(
|
||||
ClientCoreError::ValidatorClientError(nym_validator_client::ValidatorClientError::from(e))
|
||||
})?;
|
||||
|
||||
tracing::debug!("Fetching list of gateways from: {:?}", nym_api_urls);
|
||||
tracing::debug!("Fetching list of gateways from: {nym_api}");
|
||||
|
||||
// Use our helper to handle pagination
|
||||
let gateways = get_all_basic_entry_nodes_with_metadata(&client, true)
|
||||
@@ -175,15 +172,17 @@ 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 valid_gateways: Vec<RoutingNode> = gateways
|
||||
let valid_gateways = 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();
|
||||
.collect::<Vec<_>>();
|
||||
tracing::debug!("After checking validity: {}", 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()
|
||||
);
|
||||
|
||||
@@ -346,20 +345,13 @@ pub(super) fn get_specified_gateway(
|
||||
must_use_tls: bool,
|
||||
) -> Result<RoutingNode, ClientCoreError> {
|
||||
tracing::debug!("Requesting specified gateway: {gateway_identity}");
|
||||
|
||||
let user_gateway = ed25519::PublicKey::from_base58_string(gateway_identity)
|
||||
.map_err(ClientCoreError::UnableToCreatePublicKeyFromGatewayId)?;
|
||||
|
||||
let gateway = gateways
|
||||
.iter()
|
||||
.find(|gateway| gateway.identity_key == user_gateway)
|
||||
.ok_or_else(|| {
|
||||
tracing::debug!(
|
||||
"Gateway {gateway_identity} not found in {} available gateways",
|
||||
gateways.len()
|
||||
);
|
||||
ClientCoreError::NoGatewayWithId(gateway_identity.to_string())
|
||||
})?;
|
||||
.ok_or_else(|| ClientCoreError::NoGatewayWithId(gateway_identity.to_string()))?;
|
||||
|
||||
let Some(entry_details) = gateway.entry.as_ref() else {
|
||||
return Err(ClientCoreError::UnsupportedEntry {
|
||||
@@ -422,52 +414,3 @@ pub(super) async fn register_with_gateway(
|
||||
authenticated_ephemeral_client: gateway_client,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use url::Url;
|
||||
|
||||
#[test]
|
||||
fn test_single_url_builds_without_retries() {
|
||||
let urls = [Url::parse("https://api.nym.com").unwrap()];
|
||||
|
||||
let nym_api_urls: Vec<nym_http_api_client::Url> = urls
|
||||
.iter()
|
||||
.map(|url| nym_http_api_client::Url::from(url.clone()))
|
||||
.collect();
|
||||
|
||||
assert_eq!(nym_api_urls.len(), 1, "Should have exactly one URL");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_urls_prepared_for_retries() {
|
||||
let urls = vec![
|
||||
Url::parse("https://api1.nym.com").unwrap(),
|
||||
Url::parse("https://api2.nym.com").unwrap(),
|
||||
Url::parse("https://api3.nym.com").unwrap(),
|
||||
];
|
||||
|
||||
let nym_api_urls: Vec<nym_http_api_client::Url> = urls
|
||||
.iter()
|
||||
.map(|url| nym_http_api_client::Url::from(url.clone()))
|
||||
.collect();
|
||||
|
||||
assert_eq!(nym_api_urls.len(), 3, "Should have all three URLs");
|
||||
assert!(
|
||||
nym_api_urls.len() > 1,
|
||||
"Multiple URLs trigger retry behavior"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_url_list_is_detected() {
|
||||
let urls: Vec<Url> = vec![];
|
||||
|
||||
let nym_api_urls: Vec<nym_http_api_client::Url> = urls
|
||||
.iter()
|
||||
.map(|url| nym_http_api_client::Url::from(url.clone()))
|
||||
.collect();
|
||||
|
||||
assert!(nym_api_urls.is_empty(), "Empty list should remain empty");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ where
|
||||
|
||||
debug!("Started PersistentReplyStorage");
|
||||
if let Err(err) = self.backend.start_storage_session().await {
|
||||
shutdown.cancel();
|
||||
error!("failed to start the storage session - {err}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -89,6 +89,8 @@ impl PartiallyDelegatedRouter {
|
||||
|
||||
async fn run(mut self, mut split_stream: SplitStream<WsConn>, shutdown_token: ShutdownToken) {
|
||||
let mut chunked_stream = (&mut split_stream).ready_chunks(8);
|
||||
|
||||
let drop_guard = shutdown_token.clone().drop_guard();
|
||||
let ret: Result<_, GatewayClientError> = loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
@@ -101,6 +103,7 @@ impl PartiallyDelegatedRouter {
|
||||
// received request to stop the task and return the stream
|
||||
_ = &mut self.stream_return_requester => {
|
||||
log::debug!("received request to return the split ws stream");
|
||||
drop_guard.disarm();
|
||||
break Ok(())
|
||||
}
|
||||
socket_msgs = chunked_stream.next() => {
|
||||
|
||||
@@ -10,7 +10,7 @@ use cosmrs::tx;
|
||||
use cosmrs::tx::SignDoc;
|
||||
use nym_config::defaults;
|
||||
use thiserror::Error;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
type Secp256k1Keypair = (SigningKey, PublicKey);
|
||||
|
||||
@@ -128,20 +128,9 @@ impl DirectSecp256k1HdWallet {
|
||||
Ok(accounts)
|
||||
}
|
||||
|
||||
pub fn secret(&self) -> &bip39::Mnemonic {
|
||||
&self.secret
|
||||
}
|
||||
|
||||
#[deprecated(
|
||||
note = "use either .secret() for obtaining &bip39::Mnemonic or .mnemonic_string() for Zeroizing wrapper around the String"
|
||||
)]
|
||||
pub fn mnemonic(&self) -> String {
|
||||
self.secret.to_string()
|
||||
}
|
||||
|
||||
pub fn mnemonic_string(&self) -> Zeroizing<String> {
|
||||
Zeroizing::new(self.secret.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
||||
@@ -18,6 +18,6 @@ pub fn create_account(args: Args, prefix: &str) {
|
||||
let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic);
|
||||
|
||||
// Output address and mnemonics into separate lines for easier parsing
|
||||
println!("{}", wallet.mnemonic_string().as_str());
|
||||
println!("{}", wallet.mnemonic());
|
||||
println!("{}", wallet.try_derive_accounts().unwrap()[0].address());
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ nym-contracts-common = { path = "../cosmwasm-smart-contracts/contracts-common",
|
||||
bs58 = { workspace = true }
|
||||
|
||||
|
||||
lazy_static = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
rand_core = { workspace = true }
|
||||
|
||||
@@ -13,9 +13,9 @@ use rand::CryptoRng;
|
||||
use rand_core::RngCore;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Neg;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Ciphertexts {
|
||||
pub rr: [G1Projective; NUM_CHUNKS],
|
||||
pub ss: [G1Projective; NUM_CHUNKS],
|
||||
@@ -164,7 +164,8 @@ impl Ciphertexts {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||
#[derive(Zeroize)]
|
||||
#[zeroize(drop)]
|
||||
/// Randomness generated during ciphertext generation that is required for proofs of knowledge.
|
||||
///
|
||||
/// It must be handled with extreme care as its misuse might help malicious parties to recover
|
||||
@@ -398,7 +399,7 @@ pub fn baby_step_giant_step(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::bte::{keygen, setup, BSGS_TABLE};
|
||||
use crate::bte::{keygen, setup, DEFAULT_BSGS_TABLE};
|
||||
use rand_core::SeedableRng;
|
||||
|
||||
fn verify_hazmat_rand(ciphertext: &Ciphertexts, randomness: &HazmatRandomness) {
|
||||
@@ -456,6 +457,8 @@ mod tests {
|
||||
let (decryption_key1, public_key1) = keygen(¶ms, &mut rng);
|
||||
let (decryption_key2, public_key2) = keygen(¶ms, &mut rng);
|
||||
|
||||
let lookup_table = &DEFAULT_BSGS_TABLE;
|
||||
|
||||
for _ in 0..10 {
|
||||
let m1 = Share::random(&mut rng);
|
||||
let m2 = Share::random(&mut rng);
|
||||
@@ -464,12 +467,22 @@ mod tests {
|
||||
let (ciphertext, hazmat) = encrypt_shares(shares, ¶ms, &mut rng);
|
||||
verify_hazmat_rand(&ciphertext, &hazmat);
|
||||
|
||||
let recovered1 =
|
||||
decrypt_share(¶ms, &decryption_key1, 0, &ciphertext, Some(&BSGS_TABLE))
|
||||
.unwrap();
|
||||
let recovered2 =
|
||||
decrypt_share(¶ms, &decryption_key2, 1, &ciphertext, Some(&BSGS_TABLE))
|
||||
.unwrap();
|
||||
let recovered1 = decrypt_share(
|
||||
¶ms,
|
||||
&decryption_key1,
|
||||
0,
|
||||
&ciphertext,
|
||||
Some(lookup_table),
|
||||
)
|
||||
.unwrap();
|
||||
let recovered2 = decrypt_share(
|
||||
¶ms,
|
||||
&decryption_key2,
|
||||
1,
|
||||
&ciphertext,
|
||||
Some(lookup_table),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(m1, recovered1);
|
||||
assert_eq!(m2, recovered2);
|
||||
}
|
||||
@@ -485,6 +498,8 @@ mod tests {
|
||||
let (decryption_key1, public_key1) = keygen(¶ms, &mut rng);
|
||||
let (decryption_key2, public_key2) = keygen(¶ms, &mut rng);
|
||||
|
||||
let lookup_table = &DEFAULT_BSGS_TABLE;
|
||||
|
||||
for _ in 0..10 {
|
||||
let m1 = Share::random(&mut rng);
|
||||
let m2 = Share::random(&mut rng);
|
||||
@@ -493,12 +508,22 @@ mod tests {
|
||||
let (ciphertext, hazmat) = encrypt_shares(shares, ¶ms, &mut rng);
|
||||
verify_hazmat_rand(&ciphertext, &hazmat);
|
||||
|
||||
let recovered1 =
|
||||
decrypt_share(¶ms, &decryption_key1, 0, &ciphertext, Some(&BSGS_TABLE))
|
||||
.unwrap();
|
||||
let recovered2 =
|
||||
decrypt_share(¶ms, &decryption_key2, 1, &ciphertext, Some(&BSGS_TABLE))
|
||||
.unwrap();
|
||||
let recovered1 = decrypt_share(
|
||||
¶ms,
|
||||
&decryption_key1,
|
||||
0,
|
||||
&ciphertext,
|
||||
Some(lookup_table),
|
||||
)
|
||||
.unwrap();
|
||||
let recovered2 = decrypt_share(
|
||||
¶ms,
|
||||
&decryption_key2,
|
||||
1,
|
||||
&ciphertext,
|
||||
Some(lookup_table),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(m1, recovered1);
|
||||
assert_eq!(m2, recovered2);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ use group::GroupEncoding;
|
||||
use nym_pemstore::traits::{PemStorableKey, PemStorableKeyPair};
|
||||
use rand::CryptoRng;
|
||||
use rand_core::RngCore;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
// produces public key and a decryption key for the root of the tree
|
||||
pub fn keygen(
|
||||
@@ -48,7 +48,7 @@ pub fn keygen(
|
||||
(dk, key_with_proof)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Zeroize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct PublicKey(pub(crate) G1Projective);
|
||||
|
||||
impl PublicKey {
|
||||
@@ -57,7 +57,7 @@ impl PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct PublicKeyWithProof {
|
||||
pub(crate) key: PublicKey,
|
||||
pub(crate) proof: ProofOfDiscreteLog,
|
||||
@@ -136,7 +136,8 @@ impl PublicKeyWithProof {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Zeroize, ZeroizeOnDrop)]
|
||||
#[derive(Debug, Zeroize)]
|
||||
#[zeroize(drop)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct DecryptionKey {
|
||||
// g1^rho
|
||||
@@ -241,7 +242,6 @@ impl DecryptionKey {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Zeroize, ZeroizeOnDrop)]
|
||||
pub struct KeyPair {
|
||||
pub(crate) private_key: DecryptionKey,
|
||||
pub(crate) public_key: PublicKeyWithProof,
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
// Copyright 2022 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use crate::bte::encryption::BabyStepGiantStepLookup;
|
||||
use crate::utils::hash_g2;
|
||||
use crate::{Chunk, Share};
|
||||
use bls12_381::{G1Affine, G2Affine, G2Prepared, G2Projective, Gt};
|
||||
use group::Curve;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
pub mod encryption;
|
||||
pub mod keys;
|
||||
@@ -18,12 +16,14 @@ pub mod proof_sharing;
|
||||
pub use encryption::{decrypt_share, encrypt_shares, Ciphertexts};
|
||||
pub use keys::{keygen, DecryptionKey, PublicKey, PublicKeyWithProof};
|
||||
|
||||
pub(crate) static PAIRING_BASE: LazyLock<Gt> =
|
||||
LazyLock::new(|| bls12_381::pairing(&G1Affine::generator(), &G2Affine::generator()));
|
||||
pub(crate) static G2_GENERATOR_PREPARED: LazyLock<G2Prepared> =
|
||||
LazyLock::new(|| G2Prepared::from(G2Affine::generator()));
|
||||
pub static BSGS_TABLE: LazyLock<BabyStepGiantStepLookup> =
|
||||
LazyLock::new(BabyStepGiantStepLookup::default);
|
||||
lazy_static! {
|
||||
pub(crate) static ref PAIRING_BASE: Gt =
|
||||
bls12_381::pairing(&G1Affine::generator(), &G2Affine::generator());
|
||||
pub(crate) static ref G2_GENERATOR_PREPARED: G2Prepared =
|
||||
G2Prepared::from(G2Affine::generator());
|
||||
pub(crate) static ref DEFAULT_BSGS_TABLE: encryption::BabyStepGiantStepLookup =
|
||||
encryption::BabyStepGiantStepLookup::default();
|
||||
}
|
||||
|
||||
// Domain tries to follow guidelines specified by:
|
||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1
|
||||
|
||||
@@ -12,7 +12,6 @@ use ff::Field;
|
||||
use group::{Group, GroupEncoding};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use rand_core::{RngCore, SeedableRng};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
const CHUNKING_ORACLE_DOMAIN: &[u8] =
|
||||
b"NYM_COCONUT_NIDKG_V01_CS01_SHA-256_CHACHA20_CHUNKING_ORACLE";
|
||||
@@ -68,7 +67,7 @@ impl<'a> Instance<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProofOfChunking {
|
||||
y0: G1Projective,
|
||||
bb: Vec<G1Projective>,
|
||||
|
||||
@@ -7,14 +7,14 @@ use ff::Field;
|
||||
use group::GroupEncoding;
|
||||
use rand::CryptoRng;
|
||||
use rand_core::RngCore;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
// Domain tries to follow guidelines specified by:
|
||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1
|
||||
const DISCRETE_LOG_DOMAIN: &[u8] =
|
||||
b"NYM_COCONUT_NIDKG_V01_CS01_WITH_BLS12381_XMD:SHA-256_SSWU_RO_PROOF_DISCRETE_LOG";
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct ProofOfDiscreteLog {
|
||||
pub(crate) rand_commitment: G1Projective,
|
||||
pub(crate) response: Scalar,
|
||||
@@ -52,9 +52,9 @@ impl ProofOfDiscreteLog {
|
||||
let public_bytes = public.to_bytes();
|
||||
let rand_commit_bytes = rand_commit.to_bytes();
|
||||
|
||||
let mut bytes = [0u8; 96];
|
||||
bytes[0..48].copy_from_slice(public_bytes.as_ref());
|
||||
bytes[48..96].copy_from_slice(rand_commit_bytes.as_ref());
|
||||
let mut bytes = Vec::with_capacity(96);
|
||||
bytes.extend_from_slice(public_bytes.as_ref());
|
||||
bytes.extend_from_slice(rand_commit_bytes.as_ref());
|
||||
|
||||
hash_to_scalar(bytes, DISCRETE_LOG_DOMAIN)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ use group::GroupEncoding;
|
||||
use rand::CryptoRng;
|
||||
use rand_core::RngCore;
|
||||
use std::collections::BTreeMap;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
// Domain tries to follow guidelines specified by:
|
||||
// https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-hash-to-curve-11#section-3.1
|
||||
@@ -78,7 +77,7 @@ impl<'a> Instance<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ProofOfSecretSharing {
|
||||
ff: G1Projective,
|
||||
aa: G2Projective,
|
||||
|
||||
@@ -18,7 +18,7 @@ use rand_core::RngCore;
|
||||
use std::collections::BTreeMap;
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[derive(Clone, Debug, Zeroize)]
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(test, derive(PartialEq, Eq))]
|
||||
pub struct RecoveredVerificationKeys {
|
||||
pub recovered_master: G2Projective,
|
||||
@@ -83,7 +83,7 @@ impl RecoveredVerificationKeys {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Zeroize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Dealing {
|
||||
pub public_coefficients: PublicCoefficients,
|
||||
pub ciphertexts: Ciphertexts,
|
||||
|
||||
@@ -9,9 +9,9 @@ use group::GroupEncoding;
|
||||
use rand::CryptoRng;
|
||||
use rand_core::RngCore;
|
||||
use std::ops::{Add, Index, IndexMut};
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct PublicCoefficients {
|
||||
pub(crate) coefficients: Vec<G2Projective>,
|
||||
}
|
||||
@@ -111,7 +111,8 @@ impl IndexMut<usize> for PublicCoefficients {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Zeroize)]
|
||||
#[zeroize(drop)]
|
||||
pub struct Polynomial {
|
||||
coefficients: Vec<Scalar>,
|
||||
}
|
||||
|
||||
@@ -6,13 +6,14 @@ use crate::error::DkgError;
|
||||
use crate::interpolation::perform_lagrangian_interpolation_at_origin;
|
||||
use crate::NodeIndex;
|
||||
use bls12_381::Scalar;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
// if this type is changed, one must ensure all values can fit in it
|
||||
pub type Chunk = u16;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||
#[derive(PartialEq, Eq, Debug, Zeroize)]
|
||||
#[cfg_attr(test, derive(Clone))]
|
||||
#[zeroize(drop)]
|
||||
pub struct Share(pub(crate) Scalar);
|
||||
|
||||
pub fn combine_shares(shares: Vec<Share>, node_indices: &[NodeIndex]) -> Result<Scalar, DkgError> {
|
||||
@@ -65,8 +66,9 @@ impl From<Scalar> for Share {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Zeroize, ZeroizeOnDrop)]
|
||||
#[derive(Default, Zeroize)]
|
||||
#[cfg_attr(test, derive(Clone))]
|
||||
#[zeroize(drop)]
|
||||
pub(crate) struct ChunkedShare {
|
||||
pub(crate) chunks: [Chunk; NUM_CHUNKS],
|
||||
}
|
||||
|
||||
@@ -296,9 +296,6 @@ impl std::error::Error for ReqwestErrorWrapper {}
|
||||
#[derive(Debug, Error)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum HttpClientError {
|
||||
#[error("did not provide any valid client URLs")]
|
||||
NoUrlsProvided,
|
||||
|
||||
#[error("failed to construct inner reqwest client: {source}")]
|
||||
ReqwestBuildError {
|
||||
#[source]
|
||||
@@ -585,29 +582,24 @@ impl ClientBuilder {
|
||||
Self::new(alt)
|
||||
} else {
|
||||
let url = url.to_url()?;
|
||||
Self::new_with_urls(vec![url])
|
||||
Ok(Self::new_with_urls(vec![url]))
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a client builder from network details with sensible defaults
|
||||
#[cfg(feature = "network-defaults")]
|
||||
// deprecating function since it's not clear from its signature whether the client
|
||||
// would be constructed using `nym_api_urls` or `nym_vpn_api_urls`
|
||||
#[deprecated(note = "use explicit Self::new_with_fronted_urls instead")]
|
||||
pub fn from_network(
|
||||
network: &nym_network_defaults::NymNetworkDetails,
|
||||
) -> Result<Self, HttpClientError> {
|
||||
let urls = network.nym_api_urls.as_ref().cloned().unwrap_or_default();
|
||||
Self::new_with_fronted_urls(urls.clone())
|
||||
}
|
||||
|
||||
/// Create a client builder using the provided set of domain-fronted URLs
|
||||
#[cfg(feature = "network-defaults")]
|
||||
pub fn new_with_fronted_urls(
|
||||
urls: Vec<nym_network_defaults::ApiUrl>,
|
||||
) -> Result<Self, HttpClientError> {
|
||||
let urls = urls
|
||||
.into_iter()
|
||||
let urls = network
|
||||
.nym_api_urls
|
||||
.as_ref()
|
||||
.ok_or_else(|| {
|
||||
HttpClientError::GenericRequestFailure(
|
||||
"No API URLs configured in network details".to_string(),
|
||||
)
|
||||
})?
|
||||
.iter()
|
||||
.map(|api_url| {
|
||||
// Convert ApiUrl to our Url type with fronting support
|
||||
let mut url = Url::parse(&api_url.url)?;
|
||||
@@ -619,19 +611,15 @@ impl ClientBuilder {
|
||||
.iter()
|
||||
.map(|host| format!("https://{}", host))
|
||||
.collect();
|
||||
url = Url::new(api_url.url.clone(), Some(fronts)).map_err(|source| {
|
||||
HttpClientError::MalformedUrl {
|
||||
raw: api_url.url.clone(),
|
||||
source,
|
||||
}
|
||||
})?;
|
||||
url = Url::new(api_url.url.clone(), Some(fronts))
|
||||
.map_err(|e| HttpClientError::GenericRequestFailure(e.to_string()))?;
|
||||
}
|
||||
|
||||
Ok(url)
|
||||
})
|
||||
.collect::<Result<Vec<_>, HttpClientError>>()?;
|
||||
|
||||
let mut builder = Self::new_with_urls(urls)?;
|
||||
let mut builder = Self::new_with_urls(urls);
|
||||
|
||||
// Enable domain fronting by default (on retry)
|
||||
#[cfg(feature = "tunneling")]
|
||||
@@ -643,11 +631,7 @@ impl ClientBuilder {
|
||||
}
|
||||
|
||||
/// Constructs a new http `ClientBuilder` from a valid url.
|
||||
pub fn new_with_urls(urls: Vec<Url>) -> Result<Self, HttpClientError> {
|
||||
if urls.is_empty() {
|
||||
return Err(HttpClientError::NoUrlsProvided);
|
||||
}
|
||||
|
||||
pub fn new_with_urls(urls: Vec<Url>) -> Self {
|
||||
let urls = Self::check_urls(urls);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -656,7 +640,7 @@ impl ClientBuilder {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let reqwest_client_builder = default_builder();
|
||||
|
||||
Ok(ClientBuilder {
|
||||
ClientBuilder {
|
||||
urls,
|
||||
timeout: None,
|
||||
custom_user_agent: false,
|
||||
@@ -667,7 +651,7 @@ impl ClientBuilder {
|
||||
|
||||
retry_limit: 0,
|
||||
serialization: SerializationFormat::Json,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an additional URL to the set usable by this constructed `Client`
|
||||
@@ -964,13 +948,13 @@ impl Client {
|
||||
|
||||
return (url.as_str(), url.front_str());
|
||||
} else {
|
||||
tracing::debug!(
|
||||
"Domain fronting is enabled, but no host_url is defined for current URL"
|
||||
warn!(
|
||||
"Domain fronting is enabled, but no host_url is defined! Domain fronting WILL NOT WORK"
|
||||
)
|
||||
}
|
||||
} else {
|
||||
tracing::debug!(
|
||||
"Domain fronting is enabled, but current URL has no front_hosts configured"
|
||||
warn!(
|
||||
"Domain fronting is enabled, but no front_url is defined! Domain fronting WILL NOT WORK"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,6 @@ inventory::collect!(ConfigRecord);
|
||||
/// Returns the default builder with all registered configurations applied.
|
||||
pub fn default_builder() -> ReqwestClientBuilder {
|
||||
let mut b = ReqwestClientBuilder::new();
|
||||
|
||||
#[cfg(feature = "debug-inventory")]
|
||||
let mut test_client = ReqwestClientBuilder::new();
|
||||
|
||||
let mut records: Vec<&'static ConfigRecord> =
|
||||
inventory::iter::<ConfigRecord>.into_iter().collect();
|
||||
records.sort_by_key(|r| r.priority); // lower runs first
|
||||
@@ -39,10 +35,6 @@ pub fn default_builder() -> ReqwestClientBuilder {
|
||||
|
||||
for r in records {
|
||||
b = (r.apply)(b);
|
||||
#[cfg(feature = "debug-inventory")]
|
||||
{
|
||||
test_client = (r.apply)(test_client);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug-inventory")]
|
||||
@@ -55,7 +47,7 @@ pub fn default_builder() -> ReqwestClientBuilder {
|
||||
eprintln!("[HTTP-INVENTORY] Building test client to verify configuration...");
|
||||
|
||||
// Try to build a client to see if it works
|
||||
match test_client.build() {
|
||||
match b.try_clone().unwrap().build() {
|
||||
Ok(client) => {
|
||||
eprintln!("[HTTP-INVENTORY] ✓ Client built successfully");
|
||||
eprintln!("[HTTP-INVENTORY] Client debug info: {:#?}", client);
|
||||
|
||||
@@ -2,77 +2,77 @@ use super::*;
|
||||
|
||||
#[test]
|
||||
fn sanitizing_urls() {
|
||||
let base_url: Url = "http://api.test".parse().unwrap();
|
||||
let base_url: Url = "http://foomp.com".parse().unwrap();
|
||||
|
||||
// works with a full string
|
||||
assert_eq!(
|
||||
"http://api.test/foo/bar",
|
||||
"http://foomp.com/foo/bar",
|
||||
sanitize_url(&base_url, "/foo//bar/", NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// (and leading slash doesn't matter)
|
||||
assert_eq!(
|
||||
"http://api.test/foo/bar",
|
||||
"http://foomp.com/foo/bar",
|
||||
sanitize_url(&base_url, "foo//bar/", NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// works with 1 segment
|
||||
assert_eq!(
|
||||
"http://api.test/foo",
|
||||
"http://foomp.com/foo",
|
||||
sanitize_url(&base_url, &["foo"], NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// works with 2 segments
|
||||
assert_eq!(
|
||||
"http://api.test/foo/bar",
|
||||
"http://foomp.com/foo/bar",
|
||||
sanitize_url(&base_url, &["foo", "bar"], NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// works with leading slash
|
||||
assert_eq!(
|
||||
"http://api.test/foo",
|
||||
"http://foomp.com/foo",
|
||||
sanitize_url(&base_url, &["/foo"], NO_PARAMS).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://api.test/foo/bar",
|
||||
"http://foomp.com/foo/bar",
|
||||
sanitize_url(&base_url, &["/foo", "bar"], NO_PARAMS).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://api.test/foo/bar",
|
||||
"http://foomp.com/foo/bar",
|
||||
sanitize_url(&base_url, &["foo", "/bar"], NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// works with trailing slash
|
||||
assert_eq!(
|
||||
"http://api.test/foo",
|
||||
"http://foomp.com/foo",
|
||||
sanitize_url(&base_url, &["foo/"], NO_PARAMS).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://api.test/foo/bar",
|
||||
"http://foomp.com/foo/bar",
|
||||
sanitize_url(&base_url, &["foo/", "bar"], NO_PARAMS).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://api.test/foo/bar",
|
||||
"http://foomp.com/foo/bar",
|
||||
sanitize_url(&base_url, &["foo", "bar/"], NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// works with both leading and trailing slash
|
||||
assert_eq!(
|
||||
"http://api.test/foo",
|
||||
"http://foomp.com/foo",
|
||||
sanitize_url(&base_url, &["/foo/"], NO_PARAMS).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://api.test/foo/bar",
|
||||
"http://foomp.com/foo/bar",
|
||||
sanitize_url(&base_url, &["/foo/", "/bar/"], NO_PARAMS).as_str()
|
||||
);
|
||||
|
||||
// adds params
|
||||
assert_eq!(
|
||||
"http://api.test/foo/bar?foomp=baz",
|
||||
"http://foomp.com/foo/bar?foomp=baz",
|
||||
sanitize_url(&base_url, &["foo", "bar"], &[("foomp", "baz")]).as_str()
|
||||
);
|
||||
assert_eq!(
|
||||
"http://api.test/foo/bar?arg1=val1&arg2=val2",
|
||||
"http://foomp.com/foo/bar?arg1=val1&arg2=val2",
|
||||
sanitize_url(
|
||||
&base_url,
|
||||
&["/foo/", "/bar/"],
|
||||
@@ -91,87 +91,83 @@ fn sanitizing_urls() {
|
||||
#[tokio::test]
|
||||
async fn api_client_retry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let client = ClientBuilder::new_with_urls(vec![
|
||||
"http://broken.nym.test".parse()?, // This will fail
|
||||
"https://httpbin.org/status/200".parse()?, // This will succeed
|
||||
])?
|
||||
"http://broken.nym.badurl".parse()?,
|
||||
"http://example.com/".parse()?,
|
||||
])
|
||||
.with_retries(3)
|
||||
.build()?;
|
||||
|
||||
let req = client.create_get_request(&[], NO_PARAMS).unwrap();
|
||||
let req = client.create_get_request(&["/"], NO_PARAMS).unwrap();
|
||||
let resp = client.send(req).await?;
|
||||
|
||||
// The main test is that we successfully retried and switched to the working URL
|
||||
// We accept any response from the working endpoint since external services can be unreliable
|
||||
assert_eq!(
|
||||
client.current_url().as_str(),
|
||||
"https://httpbin.org/status/200"
|
||||
);
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
println!("Response status: {}", resp.status());
|
||||
// check that the url was updated
|
||||
assert_eq!(client.current_url().as_str(), "http://example.com/");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn host_updating() {
|
||||
let url = Url::new("http://nym-api1.test", None).unwrap();
|
||||
let url = Url::new("http://example.com", None).unwrap();
|
||||
let mut client = ClientBuilder::new(url).unwrap().build().unwrap();
|
||||
|
||||
// check that the url is set correctly
|
||||
let current_url = client.current_url();
|
||||
assert_eq!(current_url.as_str(), "http://nym-api1.test/");
|
||||
assert_eq!(current_url.as_str(), "http://example.com/");
|
||||
assert_eq!(current_url.front_str(), None);
|
||||
|
||||
// update the url
|
||||
client.update_host();
|
||||
|
||||
// check that the url is still the same since there is one URL
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api1.test/");
|
||||
assert_eq!(client.current_url().as_str(), "http://example.com/");
|
||||
|
||||
// =======================================
|
||||
// we rotate through urls when available
|
||||
|
||||
let new_urls = vec![
|
||||
Url::new("http://nym-api1.test", None).unwrap(),
|
||||
Url::new("http://nym-api2.test", None).unwrap(),
|
||||
Url::new("http://example.com", None).unwrap(),
|
||||
Url::new("http://example.org", None).unwrap(),
|
||||
];
|
||||
client.change_base_urls(new_urls);
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api1.test/");
|
||||
assert_eq!(client.current_url().as_str(), "http://example.com/");
|
||||
|
||||
client.update_host();
|
||||
|
||||
// check that the url got updated now that there are multiple URLs
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api2.test/");
|
||||
assert_eq!(client.current_url().as_str(), "http://example.org/");
|
||||
assert_eq!(client.current_url().front_str(), None);
|
||||
|
||||
client.update_host();
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api1.test/");
|
||||
assert_eq!(client.current_url().as_str(), "http://example.com/");
|
||||
|
||||
// =======================================
|
||||
// we rotate through urls when available if fronting is disabled
|
||||
|
||||
let new_urls = vec![
|
||||
Url::new(
|
||||
"http://nym-api1.test",
|
||||
Some(vec!["http://cdn1.test", "http://cdn2.test"]),
|
||||
"http://example.com",
|
||||
Some(vec!["http://front1.com", "http://front2.com"]),
|
||||
)
|
||||
.unwrap(),
|
||||
Url::new("http://nym-api2.test", None).unwrap(),
|
||||
Url::new("http://example.org", None).unwrap(),
|
||||
];
|
||||
client.change_base_urls(new_urls);
|
||||
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api1.test/");
|
||||
assert_eq!(client.current_url().as_str(), "http://example.com/");
|
||||
|
||||
client.update_host();
|
||||
|
||||
// check that the url got updated now that there are multiple URLs
|
||||
assert_eq!(client.current_url().as_str(), "http://nym-api2.test/");
|
||||
assert_eq!(client.current_url().as_str(), "http://example.org/");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "tunneling")]
|
||||
fn fronted_host_updating() {
|
||||
let url = Url::new("http://nym-api.test", Some(vec!["http://cdn1.test"])).unwrap();
|
||||
let url = Url::new("http://example.com", Some(vec!["http://front1.com"])).unwrap();
|
||||
let mut client = ClientBuilder::new(url)
|
||||
.unwrap()
|
||||
.with_fronting(crate::fronted::FrontPolicy::Always)
|
||||
@@ -180,103 +176,46 @@ fn fronted_host_updating() {
|
||||
|
||||
// check that the url is set correctly
|
||||
let current_url = client.current_url();
|
||||
assert_eq!(current_url.as_str(), "http://nym-api.test/");
|
||||
assert_eq!(current_url.front_str(), Some("cdn1.test"));
|
||||
assert_eq!(current_url.as_str(), "http://example.com/");
|
||||
assert_eq!(current_url.front_str(), Some("front1.com"));
|
||||
|
||||
// update the url
|
||||
client.update_host();
|
||||
|
||||
// check that the url is still the same since there is one URL and one front
|
||||
let current_url = client.current_url();
|
||||
assert_eq!(current_url.as_str(), "http://nym-api.test/");
|
||||
assert_eq!(current_url.front_str(), Some("cdn1.test"));
|
||||
assert_eq!(current_url.as_str(), "http://example.com/");
|
||||
assert_eq!(current_url.front_str(), Some("front1.com"));
|
||||
|
||||
// =======================================
|
||||
// we rotate through front urls when available if fronting is enabled
|
||||
|
||||
let new_urls = vec![
|
||||
Url::new(
|
||||
"http://nym-api.test",
|
||||
Some(vec!["http://cdn1.test", "http://cdn2.test"]),
|
||||
"http://example.com",
|
||||
Some(vec!["http://front1.com", "http://front2.com"]),
|
||||
)
|
||||
.unwrap(),
|
||||
Url::new("http://nym-api2.test", None).unwrap(),
|
||||
Url::new("http://example.org", None).unwrap(),
|
||||
];
|
||||
client.change_base_urls(new_urls);
|
||||
|
||||
let current_url = client.current_url();
|
||||
assert_eq!(current_url.as_str(), "http://nym-api.test/");
|
||||
assert_eq!(current_url.front_str(), Some("cdn1.test"));
|
||||
assert_eq!(current_url.as_str(), "http://example.com/");
|
||||
assert_eq!(current_url.front_str(), Some("front1.com"));
|
||||
|
||||
// update the url - this should keep the same host but change the front
|
||||
client.update_host();
|
||||
|
||||
let current_url = client.current_url();
|
||||
// check that the url is still the same since there is one URL
|
||||
assert_eq!(current_url.as_str(), "http://nym-api.test/");
|
||||
assert_eq!(current_url.front_str(), Some("cdn2.test"));
|
||||
assert_eq!(current_url.as_str(), "http://example.com/");
|
||||
assert_eq!(current_url.front_str(), Some("front2.com"));
|
||||
|
||||
// update the url - this should wrap around to the first front as the second url is not fronted
|
||||
client.update_host();
|
||||
|
||||
let current_url = client.current_url();
|
||||
assert_eq!(current_url.as_str(), "http://nym-api.test/");
|
||||
assert_eq!(current_url.front_str(), Some("cdn1.test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "network-defaults")]
|
||||
fn from_network_configures_multiple_urls_and_retries() {
|
||||
use nym_network_defaults::{ApiUrl, NymNetworkDetails};
|
||||
|
||||
// Create network details with multiple URLs and fronting
|
||||
let mut network_details = NymNetworkDetails::new_empty();
|
||||
network_details.nym_api_urls = Some(vec![
|
||||
ApiUrl {
|
||||
url: "https://validator.nymtech.net/api/".to_string(),
|
||||
front_hosts: None,
|
||||
},
|
||||
ApiUrl {
|
||||
url: "https://nym-frontdoor.vercel.app/api/".to_string(),
|
||||
front_hosts: Some(vec!["vercel.app".to_string(), "vercel.com".to_string()]),
|
||||
},
|
||||
ApiUrl {
|
||||
url: "https://nym-frontdoor.global.ssl.fastly.net/api/".to_string(),
|
||||
front_hosts: Some(vec!["yelp.global.ssl.fastly.net".to_string()]),
|
||||
},
|
||||
]);
|
||||
|
||||
// Build client from network details
|
||||
let client = ClientBuilder::new_with_fronted_urls(
|
||||
network_details.nym_api_urls.clone().unwrap_or_default(),
|
||||
)
|
||||
.expect("Failed to create client from network")
|
||||
.build()
|
||||
.expect("Failed to build client");
|
||||
|
||||
// Verify all URLs were configured
|
||||
assert_eq!(
|
||||
client.base_urls().len(),
|
||||
3,
|
||||
"Expected 3 URLs to be configured from network details"
|
||||
);
|
||||
|
||||
// Verify the URLs have fronting configured where appropriate
|
||||
assert_eq!(
|
||||
client.base_urls()[0].as_str(),
|
||||
"https://validator.nymtech.net/api/"
|
||||
);
|
||||
assert!(client.base_urls()[0].front_str().is_none());
|
||||
|
||||
assert_eq!(
|
||||
client.base_urls()[1].as_str(),
|
||||
"https://nym-frontdoor.vercel.app/api/"
|
||||
);
|
||||
assert!(client.base_urls()[1].front_str().is_some());
|
||||
|
||||
assert_eq!(
|
||||
client.base_urls()[2].as_str(),
|
||||
"https://nym-frontdoor.global.ssl.fastly.net/api/"
|
||||
);
|
||||
assert!(client.base_urls()[2].front_str().is_some());
|
||||
assert_eq!(current_url.as_str(), "http://example.com/");
|
||||
assert_eq!(current_url.front_str(), Some("front1.com"));
|
||||
}
|
||||
|
||||
@@ -183,11 +183,6 @@ impl Url {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the underlying URL
|
||||
pub fn inner_url(&self) -> &url::Url {
|
||||
&self.url
|
||||
}
|
||||
|
||||
/// Returns true if the URL has a front domain set
|
||||
pub fn has_front(&self) -> bool {
|
||||
if let Some(fronts) = &self.fronts {
|
||||
@@ -206,11 +201,6 @@ impl Url {
|
||||
.and_then(|url| url.host_str())
|
||||
}
|
||||
|
||||
/// Returns the fronts
|
||||
pub fn fronts(&self) -> Option<&[url::Url]> {
|
||||
self.fronts.as_deref()
|
||||
}
|
||||
|
||||
/// Return the string representation of the host (domain or IP address) for this URL, if any.
|
||||
pub fn host_str(&self) -> Option<&str> {
|
||||
self.url.host_str()
|
||||
|
||||
@@ -124,8 +124,6 @@ impl NymNetworkDetails {
|
||||
}
|
||||
}
|
||||
|
||||
let nym_api = var(var_names::NYM_API).expect("nym api not set");
|
||||
|
||||
NymNetworkDetails::new_empty()
|
||||
.with_network_name(var(var_names::NETWORK_NAME).expect("network name not set"))
|
||||
.with_bech32_account_prefix(
|
||||
@@ -151,7 +149,7 @@ impl NymNetworkDetails {
|
||||
})
|
||||
.with_additional_validator_endpoint(ValidatorDetails::new(
|
||||
var(var_names::NYXD).expect("nyxd validator not set"),
|
||||
Some(nym_api.clone()),
|
||||
Some(var(var_names::NYM_API).expect("nym api not set")),
|
||||
get_optional_env(var_names::NYXD_WEBSOCKET),
|
||||
))
|
||||
.with_mixnet_contract(get_optional_env(var_names::MIXNET_CONTRACT_ADDRESS))
|
||||
@@ -161,10 +159,6 @@ 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,
|
||||
}]))
|
||||
}
|
||||
|
||||
pub fn new_mainnet() -> Self {
|
||||
@@ -354,12 +348,6 @@ impl NymNetworkDetails {
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_nym_api_urls(mut self, urls: Option<Vec<ApiUrl>>) -> Self {
|
||||
self.nym_api_urls = urls;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn nym_vpn_api_url(&self) -> Option<Url> {
|
||||
self.nym_vpn_api_url.as_ref().map(|url| {
|
||||
url.parse()
|
||||
|
||||
@@ -119,7 +119,6 @@ where
|
||||
let ClientInput {
|
||||
connection_command_sender,
|
||||
input_sender,
|
||||
..
|
||||
} = client_input;
|
||||
|
||||
let ClientOutput {
|
||||
|
||||
@@ -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 create_sdk_shutdown_tracker() -> Result<ShutdownTracker, RegistryAccessError> {
|
||||
Ok(runtime_registry::RuntimeRegistry::create_sdk()?.shutdown_tracker_owned())
|
||||
pub fn get_sdk_shutdown_tracker() -> Result<ShutdownTracker, RegistryAccessError> {
|
||||
Ok(runtime_registry::RuntimeRegistry::get_or_create_sdk()?.shutdown_tracker_owned())
|
||||
}
|
||||
|
||||
@@ -19,45 +19,30 @@ 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 {
|
||||
/// Create a ShutdownManager for SDK use.
|
||||
/// Get or create a ShutdownManager for SDK use.
|
||||
/// This manager doesn't listen to OS signals, making it suitable for library use.
|
||||
/// 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> {
|
||||
pub(crate) fn get_or_create_sdk() -> Result<Arc<ShutdownManager>, RegistryAccessError> {
|
||||
let guard = REGISTRY
|
||||
.sdk_manager
|
||||
.read()
|
||||
.map_err(|_| RegistryAccessError::Poisoned)?;
|
||||
if let Some(manager) = guard.as_ref() {
|
||||
Ok(manager.clone())
|
||||
} else {
|
||||
Err(RegistryAccessError::MissingShutdownManager)
|
||||
return Ok(manager.clone());
|
||||
}
|
||||
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.
|
||||
@@ -100,13 +85,10 @@ mod tests {
|
||||
|
||||
assert!(!RuntimeRegistry::has_sdk_manager().unwrap());
|
||||
|
||||
// Error if nothing was created
|
||||
assert!(RuntimeRegistry::get_sdk().is_err());
|
||||
|
||||
let manager1 = RuntimeRegistry::create_sdk().unwrap();
|
||||
let manager1 = RuntimeRegistry::get_or_create_sdk().unwrap();
|
||||
assert!(RuntimeRegistry::has_sdk_manager().unwrap());
|
||||
|
||||
let manager2 = RuntimeRegistry::get_sdk().unwrap();
|
||||
let manager2 = RuntimeRegistry::get_or_create_sdk().unwrap();
|
||||
// Should return the same instance
|
||||
assert!(Arc::ptr_eq(&manager1, &manager2));
|
||||
|
||||
|
||||
@@ -160,12 +160,13 @@ pub async fn setup_gateway_from_api(
|
||||
minimum_performance: u8,
|
||||
ignore_epoch_roles: bool,
|
||||
) -> Result<InitialisationResult, WasmCoreError> {
|
||||
let mut rng = thread_rng();
|
||||
let gateways = gateways_for_init(
|
||||
&mut rng,
|
||||
nym_apis,
|
||||
None,
|
||||
minimum_performance,
|
||||
ignore_epoch_roles,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
setup_gateway_wasm(client_store, force_tls, chosen_gateway, gateways).await
|
||||
@@ -177,12 +178,13 @@ 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,
|
||||
ignore_epoch_roles,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -255,12 +255,10 @@ pub fn query(deps: Deps<'_>, env: Env, msg: QueryMsg) -> Result<QueryResponse, C
|
||||
}
|
||||
|
||||
#[entry_point]
|
||||
pub fn migrate(deps: DepsMut<'_>, env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
pub fn migrate(deps: DepsMut<'_>, _env: Env, _msg: MigrateMsg) -> Result<Response, ContractError> {
|
||||
set_build_information!(deps.storage)?;
|
||||
cw2::ensure_from_older_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
|
||||
|
||||
crate::queued_migrations::introduce_historical_epochs(deps, env)?;
|
||||
|
||||
Ok(Response::new())
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ use crate::epoch_state::storage::{load_current_epoch, save_epoch};
|
||||
use crate::epoch_state::utils::check_epoch_state;
|
||||
use crate::error::ContractError;
|
||||
use crate::state::storage::STATE;
|
||||
use crate::verification_key_shares::storage::vk_shares;
|
||||
use crate::Dealer;
|
||||
use cosmwasm_std::{Deps, DepsMut, Env, Event, MessageInfo, Response};
|
||||
use nym_coconut_dkg_common::dealer::{DealerRegistrationDetails, OwnershipTransfer};
|
||||
@@ -110,7 +109,7 @@ pub fn try_transfer_ownership(
|
||||
DEALERS_INDICES.save(deps.storage, &transfer_to, ¤t_index)?;
|
||||
DEALERS_INDICES.remove(deps.storage, &info.sender);
|
||||
|
||||
// update registration detail and share information for every epoch the current dealer has participated in the protocol
|
||||
// update registration detail for every epoch the current dealer has participated in the protocol
|
||||
// ideally, we'd have only updated the current epoch, but the way the contract is constructed
|
||||
// forbids that otherwise we'd have introduced inconsistency
|
||||
for epoch_id in 0..=epoch.epoch_id {
|
||||
@@ -118,11 +117,6 @@ pub fn try_transfer_ownership(
|
||||
EPOCH_DEALERS_MAP.remove(deps.storage, (epoch_id, &info.sender));
|
||||
EPOCH_DEALERS_MAP.save(deps.storage, (epoch_id, &transfer_to), &details)?;
|
||||
}
|
||||
if let Some(mut vk_share) = vk_shares().may_load(deps.storage, (&info.sender, epoch_id))? {
|
||||
vk_shares().remove(deps.storage, (&info.sender, epoch_id))?;
|
||||
vk_share.owner = transfer_to.clone();
|
||||
vk_shares().save(deps.storage, (&transfer_to, epoch_id), &vk_share)?;
|
||||
}
|
||||
}
|
||||
|
||||
let Some(transaction_info) = env.transaction else {
|
||||
@@ -167,14 +161,6 @@ pub fn try_update_announce_address(
|
||||
details.announce_address = new_address.clone();
|
||||
EPOCH_DEALERS_MAP.save(deps.storage, (epoch.epoch_id, &info.sender), &details)?;
|
||||
|
||||
let mut contract_share = vk_shares().load(deps.storage, (&info.sender, epoch.epoch_id))?;
|
||||
contract_share.announce_address = new_address.clone();
|
||||
vk_shares().save(
|
||||
deps.storage,
|
||||
(&info.sender, epoch.epoch_id),
|
||||
&contract_share,
|
||||
)?;
|
||||
|
||||
Ok(Response::new().add_event(
|
||||
Event::new("dkg-announce-address-update")
|
||||
.add_attribute("dealer", info.sender)
|
||||
@@ -242,14 +228,9 @@ pub(crate) mod tests {
|
||||
#[cfg(feature = "testable-dkg-contract")]
|
||||
mod tests_with_mock {
|
||||
use super::*;
|
||||
use crate::testable_dkg_contract::{
|
||||
init_contract_tester, init_contract_tester_with_group_members, DkgContractTesterExt,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use crate::testable_dkg_contract::{init_contract_tester, DkgContractTesterExt};
|
||||
use cosmwasm_std::testing::message_info;
|
||||
use nym_coconut_dkg_common::msg::QueryMsg;
|
||||
use nym_coconut_dkg_common::verification_key::PagedVKSharesResponse;
|
||||
use nym_contracts_common_testing::{ChainOpts, ContractOpts};
|
||||
use nym_contracts_common_testing::ContractOpts;
|
||||
|
||||
#[test]
|
||||
fn transferring_ownership() -> anyhow::Result<()> {
|
||||
@@ -267,7 +248,6 @@ mod tests_with_mock {
|
||||
contract.run_initial_dummy_dkg();
|
||||
let old_index = DEALERS_INDICES.load(&contract, &group_member)?;
|
||||
let old_details = EPOCH_DEALERS_MAP.load(&contract, (0, &group_member))?;
|
||||
let old_share = vk_shares().load(&contract, (&group_member, 0))?;
|
||||
|
||||
let not_group_member = contract.addr_make("not_group_member");
|
||||
let (deps, env) = contract.deps_mut_env();
|
||||
@@ -297,20 +277,13 @@ mod tests_with_mock {
|
||||
assert!(EPOCH_DEALERS_MAP
|
||||
.may_load(&contract, (0, &group_member))?
|
||||
.is_none());
|
||||
assert!(vk_shares()
|
||||
.may_load(&contract, (&group_member, 0))?
|
||||
.is_none());
|
||||
|
||||
let new_index = DEALERS_INDICES.load(&contract, &new_group_member)?;
|
||||
let new_details = EPOCH_DEALERS_MAP.load(&contract, (0, &new_group_member))?;
|
||||
let new_share = vk_shares().load(&contract, (&new_group_member, 0))?;
|
||||
|
||||
// the underlying info hasn't changed
|
||||
assert_eq!(old_index, new_index);
|
||||
assert_eq!(old_details, new_details);
|
||||
assert_ne!(old_share, new_share);
|
||||
assert_eq!(old_share.owner, group_member);
|
||||
assert_eq!(new_share.owner, new_group_member);
|
||||
|
||||
assert_eq!(
|
||||
OWNERSHIP_TRANSFER_LOG.load(
|
||||
@@ -463,91 +436,9 @@ mod tests_with_mock {
|
||||
assert_eq!(old_details1, new_details1);
|
||||
assert_eq!(old_details2, new_details2);
|
||||
|
||||
// most recent entry is updated
|
||||
// most recent entry is updated
|
||||
assert_eq!(new_details3.announce_address, new_address);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updating_announce_address_updates_vk_shares() -> anyhow::Result<()> {
|
||||
let mut contract = init_contract_tester_with_group_members(3);
|
||||
let group_member = contract.random_group_member();
|
||||
|
||||
contract.run_initial_dummy_dkg(); // => epoch 0
|
||||
contract.run_reset_dkg(); // => epoch 1
|
||||
|
||||
// LEAVE DKG MEMBERSHIP
|
||||
contract.remove_group_member(group_member.clone());
|
||||
contract.run_reset_dkg(); // => epoch 2
|
||||
|
||||
// COME BACK
|
||||
contract.add_group_member(group_member.clone());
|
||||
contract.run_reset_dkg(); // => epoch 3
|
||||
|
||||
let old_address = EPOCH_DEALERS_MAP
|
||||
.load(&contract, (3, &group_member))?
|
||||
.announce_address;
|
||||
|
||||
let old_share0 = vk_shares().load(&contract, (&group_member, 0))?;
|
||||
let old_share1 = vk_shares().load(&contract, (&group_member, 1))?;
|
||||
let old_share2 = vk_shares().may_load(&contract, (&group_member, 2))?;
|
||||
assert!(old_share2.is_none());
|
||||
let old_share3 = vk_shares().may_load(&contract, (&group_member, 3))?;
|
||||
assert!(old_share3.is_some());
|
||||
|
||||
let new_address = "https://new-address.com".to_string();
|
||||
try_update_announce_address(
|
||||
contract.deps_mut(),
|
||||
message_info(&group_member, &[]),
|
||||
new_address.clone(),
|
||||
)?;
|
||||
|
||||
let new_share0 = vk_shares().load(&contract, (&group_member, 0))?;
|
||||
let new_share1 = vk_shares().load(&contract, (&group_member, 1))?;
|
||||
let new_share2 = vk_shares().may_load(&contract, (&group_member, 2))?;
|
||||
assert!(new_share2.is_none());
|
||||
let new_share3 = vk_shares().load(&contract, (&group_member, 3))?;
|
||||
|
||||
// old epoch data is unchanged
|
||||
assert_eq!(old_share0, new_share0);
|
||||
assert_eq!(old_share1, new_share1);
|
||||
assert_eq!(old_share2, new_share2);
|
||||
|
||||
// most recent entry is updated
|
||||
assert_eq!(new_share3.announce_address, new_address);
|
||||
|
||||
// finally an integration check against query endpoint
|
||||
let epoch0_shares: PagedVKSharesResponse =
|
||||
contract.query(&QueryMsg::GetVerificationKeys {
|
||||
epoch_id: 0,
|
||||
limit: None,
|
||||
start_after: None,
|
||||
})?;
|
||||
assert_eq!(epoch0_shares.shares.len(), 3);
|
||||
|
||||
let member_share = epoch0_shares
|
||||
.shares
|
||||
.iter()
|
||||
.find(|s| s.owner == group_member)
|
||||
.context("failed to find member's share")?;
|
||||
assert_eq!(member_share.announce_address, old_address);
|
||||
|
||||
let epoch0_shares: PagedVKSharesResponse =
|
||||
contract.query(&QueryMsg::GetVerificationKeys {
|
||||
epoch_id: 3,
|
||||
limit: None,
|
||||
start_after: None,
|
||||
})?;
|
||||
assert_eq!(epoch0_shares.shares.len(), 3);
|
||||
|
||||
let member_share = epoch0_shares
|
||||
.shares
|
||||
.iter()
|
||||
.find(|s| s.owner == group_member)
|
||||
.context("failed to find member's share")?;
|
||||
assert_eq!(member_share.announce_address, new_address);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,2 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::epoch_state::storage::HISTORICAL_EPOCH;
|
||||
use crate::error::ContractError;
|
||||
use cosmwasm_std::{DepsMut, Env};
|
||||
|
||||
pub fn introduce_historical_epochs(deps: DepsMut, env: Env) -> Result<(), ContractError> {
|
||||
if HISTORICAL_EPOCH.may_load(deps.storage)?.is_some() {
|
||||
return Err(ContractError::FailedMigration {
|
||||
comment: "this migration has already been run before".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(deprecated)]
|
||||
let current = crate::epoch_state::storage::CURRENT_EPOCH.load(deps.storage)?;
|
||||
// we won't have information on intermediate states prior to now, but that's not the end of the world
|
||||
HISTORICAL_EPOCH.save(deps.storage, ¤t, env.block.height)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -62,18 +62,12 @@ impl TestableNymContract for DkgContract {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
init_contract_tester()
|
||||
init_contract_tester_with_group_members(DEFAULT_GROUP_MEMBERS)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_contract_tester() -> ContractTester<DkgContract> {
|
||||
init_contract_tester_with_group_members(DEFAULT_GROUP_MEMBERS)
|
||||
}
|
||||
|
||||
pub fn init_contract_tester_with_group_members(members: usize) -> ContractTester<DkgContract> {
|
||||
prepare_contract_tester_builder_with_group_members(members)
|
||||
.build()
|
||||
.with_common_storage_key(CommonStorageKeys::Admin, "dkg-admin")
|
||||
DkgContract::init().with_common_storage_key(CommonStorageKeys::Admin, "dkg-admin")
|
||||
}
|
||||
|
||||
pub fn prepare_contract_tester_builder_with_group_members<C>(
|
||||
@@ -143,6 +137,12 @@ where
|
||||
builder
|
||||
}
|
||||
|
||||
pub fn init_contract_tester_with_group_members(members: usize) -> ContractTester<DkgContract> {
|
||||
prepare_contract_tester_builder_with_group_members(members)
|
||||
.build()
|
||||
.with_common_storage_key(CommonStorageKeys::Admin, "dkg-admin")
|
||||
}
|
||||
|
||||
pub trait DkgContractTesterExt:
|
||||
ContractOpts<ExecuteMsg = ExecuteMsg, QueryMsg = QueryMsg, ContractError = ContractError>
|
||||
+ ChainOpts
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn legacy_mixnode_bonding() {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
@@ -2,3 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub(crate) mod transactions;
|
||||
|
||||
// the purpose of that module is to keep track of tests of legacy features that will eventually be phased out
|
||||
// such as standalone mixnode/gateway bonding
|
||||
pub(crate) mod legacy;
|
||||
|
||||
@@ -11,7 +11,6 @@ import { Alert, AlertTitle } from '@mui/material';
|
||||
import { Wallet } from '@cosmos-kit/core';
|
||||
import { CosmosKitLedger } from './ledger';
|
||||
import { CosmosKitSign } from './sign';
|
||||
import type { SignerOptions } from '@cosmos-kit/core';
|
||||
|
||||
const CosmosKitSetup: FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const assetsFixedUp = React.useMemo(() => {
|
||||
@@ -41,16 +40,14 @@ const CosmosKitSetup: FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
return chains;
|
||||
}, [chains]);
|
||||
|
||||
|
||||
// components/cosmos-kit/index.tsx
|
||||
|
||||
return (
|
||||
<ChainProvider
|
||||
chains={chainsFixedUp}
|
||||
assetLists={assetsFixedUp}
|
||||
wallets={[...ledger, ...keplr]}
|
||||
signerOptions={{ preferredSignType: () => 'amino' }}
|
||||
throwErrors={false}
|
||||
signerOptions={{
|
||||
preferredSignType: () => 'amino',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ChainProvider>
|
||||
|
||||
-142
@@ -1,142 +0,0 @@
|
||||
import { Steps } from 'nextra/components';
|
||||
import { Callout } from 'nextra/components';
|
||||
import { AccordionTemplate } from 'components/accordion-template.tsx';
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
**QUIC bridge is a requirement for all nodes which enable Wireguard functionality. Note that it this feature is compatible with nodes from `v1.18.0` (platform release [`v2025.17-isabirra`](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.17-isabirra)) and newer!**
|
||||
</ Callout>
|
||||
|
||||
Nym Network uses various [transport bridges](https://github.com/nymtech/nym-bridges/blob/main/README.md) for routing the packets. Right now operators need to configure [our implementation](https://github.com/nymtech/nym-bridges/tree/main/nym-bridge) of general-purpose transport layer network protocol called [QUIC](https://en.wikipedia.org/wiki/QUIC).
|
||||
|
||||
Operators can use [Nym Bridge Configuration Tool](https://github.com/nymtech/nym-bridges/tree/main/bridge-cfg) and compile the [Bridge Runner binary](https://github.com/nymtech/nym-bridges/tree/main/nym-bridge) themselves.
|
||||
|
||||
**We recommend a more convenient QUIC bridge deployment using a script [`quic_bridge_deployment.sh`](https://github.com/nymtech/nym/blob/develop/scripts/nym-node-setup/quic_bridge_deployment.sh), following the steps below.**
|
||||
|
||||
<Steps>
|
||||
###### 1. Download [`quic_bridge_deployment.sh`](https://github.com/nymtech/nym/blob/develop/scripts/nym-node-setup/quic_bridge_deployment.sh) script
|
||||
- SSH to your server
|
||||
- **Run as root**
|
||||
- Download the script and make executable
|
||||
```sh
|
||||
wget https://raw.githubusercontent.com/nymtech/nym/refs/heads/develop/scripts/nym-node-setup/quic_bridge_deployment.sh && \
|
||||
chmod +x quic_bridge_deployment.sh
|
||||
```
|
||||
|
||||
|
||||
###### 2. Run the script with `full_bridge_setup` command
|
||||
- Optional: open `tmux` in case you will need to run another commands on the VPS
|
||||
- Run the script with a command `full_bridge_setup`
|
||||
```sh
|
||||
./quic_bridge_deployment.sh full_bridge_setup
|
||||
```
|
||||
|
||||
###### 3. Follow the interactive prompts
|
||||
- When you are asked for bridge binary URL, look here for one to match your system: [builds.ci.nymte.ch/QUIC](https://builds.ci.nymte.ch/QUIC/)
|
||||
- When you are asked to enter forward address, it's your IPv4 used for bonding the node, alongside port `:51822` (an example: `172.232.238.161:51822`)
|
||||
- To find out your IP address you can always run:
|
||||
- IPv4: `curl -4 https://ifconfig.co/ip`
|
||||
- IPv6: `curl -6 https://ifconfig.co/ip`
|
||||
- **For all prompts with default options, we highly recommend to stick to default (press enter)**
|
||||
|
||||
###### 4. Restart the node service
|
||||
- When done with the deployment, please restart your node systemd service
|
||||
```sh
|
||||
service nym-node restart && journalctl -u nym-node.service -f --all
|
||||
```
|
||||
</Steps>
|
||||
|
||||
Congratulation, you deployed QUIC transport bridge! The script offers a standalone tweaks and checks, you can always run it without any argument to see all the options:
|
||||
```sh
|
||||
./quic_bridge_deployment.sh
|
||||
```
|
||||
<br/>
|
||||
<AccordionTemplate name="Command output">
|
||||
```shell
|
||||
root@localhost:~# ./quic_bridge_deployment.sh
|
||||
iptables-persistent is already installed.
|
||||
Usage: ./quic_bridge_deployment.sh [command] [options]
|
||||
|
||||
Nym QUIC Bridge Deployment Helper Script
|
||||
|
||||
Bridge Installation & Configuration:
|
||||
check_bridge_installation - Check bridge installation status
|
||||
show_bridge_config - Display bridge configuration files
|
||||
show_bridge_keys - Display bridge key information
|
||||
show_bridge_info - Show comprehensive bridge information
|
||||
verify_bridge_prerequisites - Verify all prerequisites are met
|
||||
|
||||
Bridge Setup Commands:
|
||||
install_bridge_binary - Download and install nym-bridge binary
|
||||
generate_bridge_keys - Generate ED25519 bridge identity keys
|
||||
create_client_params - Create client_bridge_params.json
|
||||
create_bridge_config - Create bridges.toml configuration
|
||||
create_bridge_service - Create systemd service file
|
||||
full_bridge_setup - Interactive full bridge setup wizard
|
||||
|
||||
Network Configuration Commands:
|
||||
adjust_ip_forwarding - Enable IPv4 and IPv6 forwarding
|
||||
apply_bridge_iptables_rules - Apply iptables rules for QUIC bridge (nymwg)
|
||||
configure_dns_and_icmp - Allow ICMP ping tests and configure DNS
|
||||
remove_duplicate_bridge_rules - Remove duplicate iptables rules for nymwg
|
||||
|
||||
Network Inspection Commands:
|
||||
fetch_and_display_ipv6 - Show IPv6 on default network device
|
||||
fetch_wg_ipv6_address - Fetch IPv6 for nymwg interface
|
||||
check_bridge_iptables - Check iptables rules for nymwg
|
||||
check_ipv6_ipv4_forwarding - Check IPv4 and IPv6 forwarding status
|
||||
check_ip_routing - Display IP routing tables
|
||||
|
||||
Testing Commands:
|
||||
perform_pings - Test IPv4 and IPv6 connectivity
|
||||
test_bridge_connectivity - Comprehensive bridge connectivity test
|
||||
|
||||
Service Management Commands:
|
||||
check_bridge_service_status - Check nym-bridge and nym-node service status
|
||||
show_bridge_logs [lines] - Show recent nym-bridge logs (default: 50 lines)
|
||||
|
||||
Quick Start:
|
||||
1. Run 'verify_bridge_prerequisites' to check prerequisites
|
||||
2. Run 'check_bridge_installation' to verify installation
|
||||
3. Run 'test_bridge_connectivity' to test connectivity
|
||||
```
|
||||
</AccordionTemplate>
|
||||
|
||||
### Fixing Metadata Port Showing Not Open in Probe Results
|
||||
If you have followed the steps outlined above, but the metadata port is not shown as open in either the Node Status API's probe results or an explorer that gets its data from the API, see below:
|
||||
|
||||
<Steps>
|
||||
###### 1.
|
||||
Ensure that in your `config.toml` file, this value is set to the default one - any other value here will cause the metadata endpoint to fail:
|
||||
|
||||
```
|
||||
# Private IP address of the wireguard gateway.
|
||||
# default: '10.1.0.1'
|
||||
private_ipv4 = '10.1.0.1'
|
||||
```
|
||||
|
||||
Then restart your node.
|
||||
|
||||
###### 2.
|
||||
Run this command if not already done:
|
||||
```
|
||||
ufw allow in on nymwg to any port 51830 proto tcp
|
||||
```
|
||||
|
||||
Check if the port is open with:
|
||||
```
|
||||
iptables -S | grep 51830
|
||||
```
|
||||
|
||||
Then ensure the metadata endpoint is listening from the correct address with:
|
||||
```
|
||||
netstat -an | egrep LISTEN | egrep "51830"
|
||||
```
|
||||
|
||||
###### 3.
|
||||
Once the Node Status API has run a probe on your node, the probe results will reflect this - `can_query_metadata_v4` will have `true` as a value.
|
||||
|
||||
The quickest way to check this is by using the [NymVPN API](https://nymvpn.com/api/public/v1/directory/gateways?show_vpn_only=true) and checking the same field:
|
||||
|
||||

|
||||
|
||||
</Steps>
|
||||
@@ -1 +1 @@
|
||||
Tuesday, October 14th 2025, 11:34:14 UTC
|
||||
Sunday, October 5th 2025, 09:29:02 UTC
|
||||
|
||||
@@ -24,11 +24,11 @@
|
||||
"@cosmjs/encoding": "^0.32.2",
|
||||
"@cosmjs/proto-signing": "^0.32.2",
|
||||
"@cosmjs/stargate": "^0.32.2",
|
||||
"@cosmos-kit/core": "^2.16.3",
|
||||
"@cosmos-kit/core": "^2.8.9",
|
||||
"@cosmos-kit/keplr": "^2.6.9",
|
||||
"@cosmos-kit/keplr-extension": "^2.7.9",
|
||||
"@cosmos-kit/ledger": "^2.6.9",
|
||||
"@cosmos-kit/react": "^2.22.3",
|
||||
"@cosmos-kit/react": "^2.10.11",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@interchain-ui/react": "^1.8.0",
|
||||
@@ -54,15 +54,12 @@
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/swc-linux-x64-gnu": "15.5.0",
|
||||
"@types/node": "18.11.10",
|
||||
"@types/react": "^18.3.26",
|
||||
"@types/react-dom": "^18.3.7",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"eslint": "8.46.0",
|
||||
"eslint-config-next": "13.4.13",
|
||||
"raw-loader": "^4.0.2",
|
||||
"typescript": "^5.9.3"
|
||||
"typescript": "^4.9.3"
|
||||
},
|
||||
"private": false
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { AccordionTemplate } from 'components/accordion-template.tsx';
|
||||
import { Steps } from 'nextra/components';
|
||||
import AbuseResponse from 'components/operators/templates/dmca_response.md';
|
||||
import OperatorIntroduction from 'components/operators/templates/provider_introduction.md';
|
||||
import QuicDeploymentSteps from 'components/operators/snippets/quic-bridge-deployment-script-setup.mdx';
|
||||
|
||||
|
||||
export const TestingSteps = () => (
|
||||
@@ -49,323 +48,6 @@ This page displays a full list of all the changes during our release cycle from
|
||||
|
||||
<VarInfo />
|
||||
|
||||
## `v2025.18-jarlsberg`
|
||||
|
||||
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.18-jarlsberg)
|
||||
- [`nym-node`](nodes/nym-node.mdx) version `1.19.0`
|
||||
|
||||
```sh
|
||||
nym-node
|
||||
Binary Name: nym-node
|
||||
Build Timestamp: 2025-10-15T09:04:32.043934599Z
|
||||
Build Version: 1.19.0
|
||||
Commit SHA: 2235a6e1477bea7368ee5443a298f544deb63504
|
||||
Commit Date: 2025-10-15T10:22:16.000000000+02:00
|
||||
Commit Branch: master
|
||||
rustc Version: 1.92.0-nightly
|
||||
rustc Channel: nightly
|
||||
cargo Profile: release
|
||||
```
|
||||
|
||||
### API Changes
|
||||
There have been a few updates to the Node Status API (used by the NymVPN API) to do with Nodes' metadata endpoints, which are used to determine if they are running a QUIC bridge.
|
||||
|
||||
- [Node Status API: add bridge information to dVPN endpoint](https://github.com/nymtech/nym/pull/6069)
|
||||
Scrape the `/api/v1/bridges/client-params` endpoint from nodes to get bridge information and add to the dVPN output:
|
||||
|
||||
```
|
||||
{
|
||||
"identity_key": "3wqfp9ebaajgV8HRKYHeZuZCNXgitnW8BbytxyBH65xZ",
|
||||
"name": "middle winner wing",
|
||||
"authenticator": {
|
||||
"address": "6CQMtm9DqUj7mPVkSD9YarjUuPh7mJaZQnnHWxNpgByh.AGXiTivVieBULeDhL9tuyMKgRydoT67sFCjeoERDN84k@3wqfp9ebaajgV8HRKYHeZuZCNXgitnW8BbytxyBH65xZ"
|
||||
},
|
||||
"ip_packet_router": {
|
||||
"address": "GA47h8294m7f6ciyFuDkjk3mmqrvALqboL2o22jkqFhi.22SdTGBWKFrrBM31hMgzjmgduSH1nosnbE9dgNcY2CXz@3wqfp9ebaajgV8HRKYHeZuZCNXgitnW8BbytxyBH65xZ"
|
||||
},
|
||||
"location": {
|
||||
"two_letter_iso_country_code": "GB",
|
||||
"latitude": 51.5085,
|
||||
"longitude": -0.1257
|
||||
},
|
||||
"last_probe": {
|
||||
"last_updated_utc": "2025-09-02T18:19:10Z",
|
||||
"outcome": {
|
||||
"as_entry": {
|
||||
"can_connect": true,
|
||||
"can_route": true
|
||||
},
|
||||
"as_exit": {
|
||||
"can_connect": true,
|
||||
"can_route_ip_external_v4": true,
|
||||
"can_route_ip_external_v6": true,
|
||||
"can_route_ip_v4": true,
|
||||
"can_route_ip_v6": true
|
||||
},
|
||||
"wg": {
|
||||
"can_handshake_v4": true,
|
||||
"can_handshake_v6": true,
|
||||
"can_register": true,
|
||||
"can_resolve_dns_v4": true,
|
||||
"can_resolve_dns_v6": true,
|
||||
"download_duration_sec_v4": 0,
|
||||
"download_duration_sec_v6": 5,
|
||||
"download_error_v4": "",
|
||||
"download_error_v6": "",
|
||||
"downloaded_file_v4": "https://proof.ovh.net/files/1Mb.dat",
|
||||
"downloaded_file_v6": "https://proof.ovh.net/files/10Mb.dat",
|
||||
"ping_hosts_performance_v4": 1,
|
||||
"ping_hosts_performance_v6": 1,
|
||||
"ping_ips_performance_v4": 1,
|
||||
"ping_ips_performance_v6": 0.6666667,
|
||||
"can_handshake": true,
|
||||
"can_resolve_dns": true,
|
||||
"ping_hosts_performance": 1,
|
||||
"ping_ips_performance": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"ip_addresses": [
|
||||
"178.79.168.250",
|
||||
"2a01:7e00::f03c:95ff:fef8:77f"
|
||||
],
|
||||
"mix_port": 1789,
|
||||
"role": "EntryGateway",
|
||||
"entry": {
|
||||
"hostname": "nym-circ.anonym.tech",
|
||||
"ws_port": 9000,
|
||||
"wss_port": 9443
|
||||
},
|
||||
+ "bridges":{
|
||||
+ "version": 0,
|
||||
+ "transports": [
|
||||
+ {
|
||||
+ "transport_type": "quic_plain",
|
||||
+ "args": {
|
||||
+ "addresses": ["[2a01:7e00::f03c:95ff:fef8:77f]:4443", "178.79.168.250:4443"],
|
||||
+ "id_pubkey": "gyKl6DN9hgdPGhEzdf9gY4Ha2GzrOwSzLCguxeTVTJU=",
|
||||
+ "host": "netdna.bootstrapcdn.com"
|
||||
+ }
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
"performance": "1",
|
||||
"build_information": {
|
||||
"build_version": "1.16.0",
|
||||
"commit_branch": "build",
|
||||
"commit_sha": "7f97f13799342f864e1b106e8cafc9f6d6c24c0f"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ns-api: add new fields for probe output for query_metadata and download file size and duration in ms](https://github.com/nymtech/nym/pull/6091)
|
||||
|
||||
This PR adds new fields to the Node Status API:
|
||||
|
||||
```json
|
||||
{
|
||||
"node": "ByxGq9hpDQu6Wc8augEh22w7CRWJHPNfDshB1b8nfWkh",
|
||||
"used_entry": "ByxGq9hpDQu6Wc8augEh22w7CRWJHPNfDshB1b8nfWkh",
|
||||
"outcome": {
|
||||
"as_entry": {
|
||||
"can_connect": true,
|
||||
"can_route": true
|
||||
},
|
||||
"as_exit": {
|
||||
"can_connect": true,
|
||||
"can_route_ip_v4": true,
|
||||
"can_route_ip_external_v4": true,
|
||||
"can_route_ip_v6": true,
|
||||
"can_route_ip_external_v6": true
|
||||
},
|
||||
"wg": {
|
||||
"can_register": true,
|
||||
"can_query_metadata_v4": true, // <--------------------------------
|
||||
"can_handshake_v4": true,
|
||||
"can_resolve_dns_v4": true,
|
||||
"ping_hosts_performance_v4": 1.0,
|
||||
"ping_ips_performance_v4": 1.0,
|
||||
"can_handshake_v6": true,
|
||||
"can_resolve_dns_v6": true,
|
||||
"ping_hosts_performance_v6": 1.0,
|
||||
"ping_ips_performance_v6": 0.93333334,
|
||||
"download_duration_sec_v4": 2,
|
||||
"download_duration_milliseconds_v4": 2034, // <--------------------------------
|
||||
"downloaded_file_size_bytes_v4": 1048576, // <--------------------------------
|
||||
"downloaded_file_v4": "https://nym-bandwidth-monitoring.ops-d86.workers.dev/1mb.dat",
|
||||
"download_error_v4": "",
|
||||
"download_duration_sec_v6": 5,
|
||||
"downloaded_file_size_bytes_v6": 1048576,
|
||||
"download_duration_milliseconds_v6": 5501,
|
||||
"downloaded_file_v6": "https://proof.ovh.net/files/1Mb.dat",
|
||||
"download_error_v6": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ns-api: add descriptions to dVPN gateway responses](https://github.com/nymtech/nym/pull/6102)
|
||||
This PR adds the `description` field to dVPN gateways in `/dvpn/v1/directory/gateways`.
|
||||
|
||||
- [NS API: use new probe download filesize and milliseconds field](https://github.com/nymtech/nym/pull/6097)
|
||||
This PR uses the new fields in mainnet to calculate the probe download score.
|
||||
|
||||
- [ns-api: use download files size from probes instead of parsing filenames](https://github.com/nymtech/nym/pull/6095) This PR uses the new field in the probe results that says how many bytes were downloaded to calculate the speed of download. It only uses downloads on ipv4 and ignores ipv6 for now. This might change in the future.
|
||||
|
||||
- [Node Status API: remove sqlite support](https://github.com/nymtech/nym/pull/6004)
|
||||
This PR removes sqlite support, requiring pgsql to run the NS API.
|
||||
|
||||
It also fixes the following issues:
|
||||
|
||||
- deserialisation of `NodeDescription`
|
||||
- defaults for `WireguardDetails` for deserialisation
|
||||
|
||||
It also bumps the version to v4.0.0.
|
||||
|
||||
### Operators Updates & Tools
|
||||
|
||||
- [Node rewards tracker](https://github.com/nymtech/nym/pull/6064)
|
||||
This PR introduces a script fetching operators rewards based on provided Nyx account addresses provided in `data/wallet-addresses.csv`.
|
||||
|
||||
<AccordionTemplate name="Info">
|
||||
**Output is:**
|
||||
1. Printed table in terminal
|
||||
3. Sheet with complete info stored in `data/node-balances.csv`
|
||||
4. Historical data yaml file stored in `data/data.yaml` - this file should not be changed manually, as
|
||||
all values older than 30 days get auto-removed
|
||||
|
||||
**RUN**
|
||||
|
||||
Before you start fill first column of `data/wallet-addresses.csv` called `addresses` with your Nyx account addresses and (optionally) second column called `tag` with an entity, for example *"mysquad"* and *"personal"* to get sorted output per entity.
|
||||
|
||||
- Csv example with `tag`s:
|
||||
```
|
||||
n1foofoofoo, personal
|
||||
n1barbarbar, personal
|
||||
n1bazbazbaz, mysquad
|
||||
n1lollollol, mysquad
|
||||
```
|
||||
- For operators having all nodes under one entity, the tag field will be left empty. Example:
|
||||
```csv
|
||||
n1foofoofoo
|
||||
n1barbarbar
|
||||
n1bazbazbaz
|
||||
```
|
||||
|
||||
Documentation coming soon.
|
||||
</AccordionTemplate>
|
||||
|
||||
- [Bugfix/bloomfilters purge](https://github.com/nymtech/nym/pull/6089)
|
||||
This PR fixes bug where old replay protection bloomfilters were never getting removed.
|
||||
|
||||
|
||||
### Features
|
||||
- [Get wireguard keypair as arg instead of reading it from disk](https://github.com/nymtech/nym/pull/6078)
|
||||
|
||||
- [Registration Client](https://github.com/nymtech/nym/pull/6059)
|
||||
This PR introduces the `RegistrationClient` whose eventual job will be to handle registration with gateway and bandwidth control. This is step 1, where it only handles registration and then hands back the control channel to the vpn-client.
|
||||
|
||||
<AccordionTemplate name="Info">
|
||||
|
||||
**nym-wg-gateway-client**
|
||||
This crate has been smooshed with the nym-authenticator-client as they were doing the same thing : talking with the Authenticator.
|
||||
|
||||
**nym-authenticator-client**
|
||||
The job of the `AuthenticatorClient` is to talk to the `Authenticator`s via the mixnet. They both make use of a `AuthClientMixnetListener` that handles interaction with the mixnet client. No more `SharedMixnetClient`, only clear owners. That component could be turned into an actual multiplexer, but that's out of scope.
|
||||
|
||||
It is designed to be able to shut down, since it won't be necessary for bandwidth top up in the future.
|
||||
|
||||
Lots of types and traits were copied in both repos, some of them are sadly still there. Further work could be done to improve messaging ( `ClientMessage` and `AuthenticatorRequests` for example)
|
||||
|
||||
**nym-ip-packet-client**
|
||||
This crate has minor changes, focused on getting rid of the `SharedMixnetClient`. It still talks to the `IpPacketRouter` but it owns the `MixnetClient`
|
||||
|
||||
**Nym-registration-client**
|
||||
Brand new crates, whose current job is to run a `MixnetClient` with the given options, register with the component related to the tunnel type, and hand back the necessary component for running the tunnel.
|
||||
|
||||
**authenticator-requests**
|
||||
Mostly refactoring, lots of code was duplicated in the vpn-client repo
|
||||
|
||||
**misc**
|
||||
The rest are qol changes that might not be needed right away but that is preparing the future improvements coming soon™
|
||||
</AccordionTemplate>
|
||||
|
||||
- [Feature: Ping probe all nodes /described nodes from a server](https://github.com/nymtech/nym/pull/6074)
|
||||
This script should be ran from a node hosting server. It pings all IPs listed in /described endpoint and returns a file with unreachable IPs. Such list gives operator an idea on IPs potentially blocking their IP.
|
||||
|
||||
- [Feature: Nym node html landing page](https://github.com/nymtech/nym/pull/6053)
|
||||
This PR introduces a new landing page which contains:
|
||||
- no more deprecated tornul
|
||||
- new nym theme
|
||||
- bold text about DMCA
|
||||
- hook for nym-node-cli to use it and add $EMAIL prompted to the operator
|
||||
|
||||
- [feat: DKG contract method for updating announce address](https://github.com/nymtech/nym/pull/6050)
|
||||
|
||||
- [feat: NS ticket faucet](https://github.com/nymtech/nym/pull/6047)
|
||||
Overview: modifies the Node Status API so that it keeps a buffer of tickets inside its storage that it gives out when new test runs get requested. it also slightly adjusts the ticketbook API in a bit hacky way to allow importing ticketbooks with specific index ranges. However, those changes also involve modifying cli arguments passed to both NS API and gateway probes. The associated vpn-client repo branch is `feature/ticket-faucet-probe` which for the same reason is not yet ready
|
||||
|
||||
<AccordionTemplate name="Info">
|
||||
**Node Status API**
|
||||
**Added**
|
||||
- `--config-env-file` / `-c` (optional) - helper allowing testing locally on non-mainnet networks without passing everything through env variables
|
||||
- `--mnemonic` (env: `NYM_NODE_STATUS_API_MNEMONIC`) - account used for obtaining ticketbooks
|
||||
- `--max-concurrent-deposits` (env: `NYM_NODE_STATUS_API_MAX_CONCURRENT_DEPOSITS`) (optional; default: 5) - Specifies the maximum number of deposits the node status api can make in a single transaction. Note that each deposit batch is followed by the same number of sequential signing requests
|
||||
- `--tickets-buffer-size` (env: `NYM_NODE_STATUS_API_TICKETS_BUFFER`) (optional; default: 50) - Specifies the size of the tickets buffer the node status api should have available at any time for each ticket type.
|
||||
- `--tickets-buffer-check-interval` (env: `NYM_NODE_STATUS_API_TICKETS_CHECK_INTERVAL`) (optional; default: 1min) - Specifies interval at which the node status api should check if it has sufficient number of tickets buffered
|
||||
- `--quorum-check-interval` (env: `NYM_NODE_STATUS_API_QUORUM_CHECK_INTERVAL`) (optional; default: 5min) - Specifies interval at which the node status api should check if signing quorum is available
|
||||
- `--buffered-ticket-types` (env: `NYM_NODE_STATUS_BUFFERED_TICKET_TYPES`) (optional; default: `[V1MixnetEntry, V1WireguardEntry, V1WireguardExit]`) - Specifies types of tickets to buffer
|
||||
- `--ecash-client-identifier-bs58` (env: `NYM_NODE_STATUS_API_ECASH_CLIENT_IDENTIFIER_BS58`) - Identifier used for deriving keys embedded in the issued ticketbooks (i.e. seed for the client identity). It can be a random string, but make sure it has sufficient entropy. it has to be base58 encoded.
|
||||
|
||||
**Node Status Agent**
|
||||
**Removed**
|
||||
- `--mnemonic` - no longer needed as tickets are obtained throught the faucet
|
||||
|
||||
**Gateway Probe (vpn-client repo)**
|
||||
**Added**
|
||||
- `--ticket-materials` - all the encoded generated tickets (and global data) needed by the probe
|
||||
- `--ticket-materials-revision` - revision of the serialisation to help with decoding (not strictly needed, but it was already available)
|
||||
**Removed**
|
||||
- `--mnemonic` - no longer needed as tickets are obtained throught the faucet
|
||||
</AccordionTemplate>
|
||||
|
||||
- [Bridge proto client params in Self-Described](https://github.com/nymtech/nym/pull/6035)
|
||||
This PR gives the nym-node a way to expose information about the bridge protocols that the node supports, and the parameters that are necessary to connect using those protocols.
|
||||
|
||||
<AccordionTemplate name="Info">
|
||||
This is meant to be usable by the node status API to be be included into node descriptors that are compiled for the vpn client.
|
||||
|
||||
- Adds a new field to the nym-node config `gateway_tasks.storage_paths.bridge_client_params`
|
||||
- IF the new config field is present a new self-described endpoint is available at `/v1/bridges/client-params`
|
||||
- IF the new config field is NOT present the endpoint is not exposed.
|
||||
|
||||
I arbitrarily chose config v8 as the oldest nym-node configuration version that supports the option. This can probably be propogated further backwards if necessary.
|
||||
|
||||
NOTE: The new `/bridges/client-params` endpoint does not have swagger / utopia docs associated. This interface will likely change in several upcoming iterations and serving from file (for now) means that the types are not defined internally.
|
||||
|
||||
tested as working on node `3wqfp9eb` both when file is provided in config (sucessful response) and when file is not specified in config (path gives 404).
|
||||
|
||||
</AccordionTemplate>
|
||||
|
||||
### Refactors & Maintenance
|
||||
- [[chore] Clippy fix](https://github.com/nymtech/nym/pull/6060)
|
||||
|
||||
- [Bugfix: Nym node CLI download nym-node exception](https://github.com/nymtech/nym/pull/6058)
|
||||
This PR fixes a case when the "Latest" platform release doesn't include `nym-node` by prompting user to insert binary URL instead of failing. Additionally it fixes fetching new landing page script in the CLI.
|
||||
|
||||
- [Benny/ci contract fix](https://github.com/nymtech/nym/pull/5962)
|
||||
|
||||
- [frontdoor typo fix](https://github.com/nymtech/nym/pull/6067)
|
||||
|
||||
- [Hotfix: Update API source in node ping tester script](https://github.com/nymtech/nym/pull/6082)
|
||||
This PR fixes initial development bug where a wrong API endpoint was used.
|
||||
`https://validator.nymtech.net/api/v1/nym-nodes/described` gets all nym nodes, not just gateways.
|
||||
Code is simplified accordingly.
|
||||
|
||||
## QUIC Transport Bridge Deployment
|
||||
|
||||
<QuicDeploymentSteps />
|
||||
|
||||
## `v2025.17-isabirra`
|
||||
|
||||
- [Release Binaries](https://github.com/nymtech/nym/releases/tag/nym-binaries-v2025.17-isabirra)
|
||||
@@ -421,7 +103,7 @@ All of the routes removed had already been deprecated over a year ago. This is m
|
||||
<AccordionTemplate name="Removed API routes">
|
||||
|
||||
### Legacy mixnodes related:
|
||||
|
||||
|
||||
- `/v1/mixnodes`
|
||||
- `/v1/mixnodes/active`
|
||||
- `/v1/mixnodes/active/detailed`
|
||||
@@ -443,9 +125,9 @@ All of the routes removed had already been deprecated over a year ago. This is m
|
||||
- `/v1/status/mixnodes/detailed-unfiltered`
|
||||
- `/v1/status/mixnode/{mix_id}/report`
|
||||
- `/v1/status/mixnode/{mix_id}/avg_uptime`
|
||||
|
||||
|
||||
### Legacy gateways related:
|
||||
|
||||
|
||||
- `/v1/gateways`
|
||||
- `/v1/gateways/described`
|
||||
- `/v1/gateways/blacklisted`
|
||||
@@ -457,21 +139,21 @@ All of the routes removed had already been deprecated over a year ago. This is m
|
||||
</AccordionTemplate>
|
||||
|
||||
#### Structs changes:
|
||||
|
||||
|
||||
- `MixnodeUptimeHistoryResponse` no longer has `owner` field
|
||||
- `GatewayUptimeHistoryResponse` no longer has `owner` field
|
||||
|
||||
|
||||
|
||||
|
||||
#### New Routes Added
|
||||
|
||||
- `/v1/nym-nodes/stake-saturation/{node_id}` - as a better replacement for `/v1/status/mixnode/{mix_id}/stake-saturation` as this information might be potentially useful and can be applied to any nym-node, not just a legacy mixnode.
|
||||
- `/v1/legacy/mixnodes` - returns a list of bonded legacy mixnodes that haven't migrated to nym-nodes
|
||||
- `/v1/legacy/gateways` - returns a list of bonded legacy gateways that haven't migrated to nym-nodes
|
||||
|
||||
|
||||
#### Node Status API
|
||||
|
||||
|
||||
Furthermore the changes remove all scraping of legacy mixnodes from NS and the following routes are removed:
|
||||
|
||||
|
||||
- `/v2/mixnodes/{mix_id}`
|
||||
- `/v2/mixnodes`
|
||||
|
||||
@@ -542,7 +224,7 @@ Furthermore the changes remove all scraping of legacy mixnodes from NS and the f
|
||||
### Operators Updates & Tools
|
||||
|
||||
<Callout type="info" emoji="ℹ️">
|
||||
Nodes receiving stake as a part of [**Nym Delegation Program**](https://nym.com/network/DP) are updated weekly based on the [rules](https://forms.nym.com/form/#/2/form/view/BRh8QroXFinjOF4D3FHgYiX76zbiRvUV2Sy+czaoKFQ) without prior notification given to the operators.
|
||||
Nodes receiving stake as a part of [**Nym Delegation Program**](https://nym.com/network/DP) are updated weekly based on the [rules](https://forms.nym.com/form/#/2/form/view/BRh8QroXFinjOF4D3FHgYiX76zbiRvUV2Sy+czaoKFQ) without prior notification given to the operators.
|
||||
|
||||
[**Nym Delegation account**](https://explorer.nym.spectredao.net/account/n1rnxpdpx3kldygsklfft0gech7fhfcux4zst5lw) `n1rnxpdpx3kldygsklfft0gech7fhfcux4zst5lw` is a single source of truth. If you expect your node to have Nym team stake and it doesn't, please reach out in in the [**Node Operators Matrix channel**](https://matrix.to/#/#operators:nymtech.chat).
|
||||
</Callout>
|
||||
@@ -565,32 +247,32 @@ nym-vpnc connect --enable-two-hop --entry-gateway-id 7CWjY3QFoA9dgE535u9bQiXCfz
|
||||
|
||||
- [Feature/testing utils](https://github.com/nymtech/nym/pull/5963): This PR introduces a couple of general helpers, in particular some mocks for sending across values using Stream/Sink and AsyncRead/AsyncWrite without actual underlying networking. Example implementation are with NymNoise (which was the original inspiration) and gateway handshake.
|
||||
|
||||
- [Backport metadata endpoint](https://github.com/nymtech/nym/pull/6010)
|
||||
- [Backport metadata endpoint](https://github.com/nymtech/nym/pull/6010)
|
||||
|
||||
### Bugfix
|
||||
|
||||
- [Fix rust `1.89` `clippy` issues](https://github.com/nymtech/nym/pull/5944)
|
||||
|
||||
- [`http api` client adjustment](https://github.com/nymtech/nym/pull/5953): It fixes missing `feature-lock` when cloning the client and adds helper macro for user agent creation
|
||||
- [`http api` client adjustment](https://github.com/nymtech/nym/pull/5953): It fixes missing `feature-lock` when cloning the client and adds helper macro for user agent creation
|
||||
|
||||
- [Fix `ci-build` for linux (and use updated runner)](https://github.com/nymtech/nym/pull/5958): This PR fixes our build pipeline by using correct (updated) linux runner and updates all the conditional steps that were behind `ubuntu` runners (which no longer exist)
|
||||
|
||||
- [Fixing the ci for ns agent](https://github.com/nymtech/nym/pull/5965)
|
||||
- [Fixing the ci for ns agent](https://github.com/nymtech/nym/pull/5965)
|
||||
|
||||
- [Manually calculate per node work on rewarded set changes](https://github.com/nymtech/nym/pull/5972): This PR fixes:
|
||||
1. Nym rewarded set was set to X, for argument sake say 200
|
||||
1. Nym rewarded set was set to X, for argument sake say 200
|
||||
2. We sent transaction to update it to Y, say 100
|
||||
3. This internally updated the interval rewarding parameters inside the mixnet contract including the default active and standby node work factors. Note that the rewarded set itself stayed the same, as it only changes after epoch rolls over and new one is assigned (by the `nym-api`)
|
||||
4. Epoch has finished and `nym-api` wanted to do the rewarding. It grabbed the **current** rewarded set (of X, 200) and started calculating the total work in the system. But since the contract already had new parameters (adjusted for size of Y, 100), the result was greater than 1 thus `nym-api` was preventably blowing up.
|
||||
To fix it we introduce additional checks, so that if the current rewarded set does not match the specification defined in the contract rewarding parameters, `nym-api` will attempt to do its best to manually calculate work factors for this epoch.
|
||||
|
||||
- [Fix the ns api ci workflow](https://github.com/nymtech/nym/pull/5981)
|
||||
- [Fix the ns api ci workflow](https://github.com/nymtech/nym/pull/5981)
|
||||
|
||||
- [Make sure tables are removed in correct order to not trigger FK constraint issue](https://github.com/nymtech/nym/pull/5987)
|
||||
- [Make sure tables are removed in correct order to not trigger FK constraint issue](https://github.com/nymtech/nym/pull/5987)
|
||||
|
||||
### Refactors & Maintenance
|
||||
|
||||
- [Move credential verifier in peer controller](https://github.com/nymtech/nym/pull/5938): This PR is to not duplicate the verifier code (minus the actual verification operation, which is harder to unit test because of expiration checks)
|
||||
- [Move credential verifier in peer controller](https://github.com/nymtech/nym/pull/5938): This PR is to not duplicate the verifier code (minus the actual verification operation, which is harder to unit test because of expiration checks)
|
||||
|
||||
- [Remove unused import](https://github.com/nymtech/nym/pull/5942)
|
||||
|
||||
@@ -601,7 +283,7 @@ nym-vpnc connect --enable-two-hop --entry-gateway-id 7CWjY3QFoA9dgE535u9bQiXCfz
|
||||
- [Remove freshness check on testrun submit](https://github.com/nymtech/nym/pull/5977):
|
||||
- Freshness is enforced by a background task that marks test runs as stale after a configured amount of time
|
||||
- Make existing freshness period configurable to avoid code changes in the future
|
||||
- Added `humantime` for parsing
|
||||
- Added `humantime` for parsing
|
||||
|
||||
- [Move authenticator into gateway crate](https://github.com/nymtech/nym/pull/5982)
|
||||
|
||||
@@ -624,7 +306,7 @@ nym-vpnc connect --enable-two-hop --entry-gateway-id 7CWjY3QFoA9dgE535u9bQiXCfz
|
||||
|
||||
- [Ecash liveness check](https://github.com/nymtech/nym/pull/5890)
|
||||
|
||||
- [Basic zulip client for sending messages](https://github.com/nymtech/nym/pull/5913): In order to be able to send zulip notifications about *emergency* upgrade mode being activated, we need some sort of client. Unfortunately there isn't any rust library that's maintained (the only one had last commit 4 years ago). This simple thing now currently only supports message sending
|
||||
- [Basic zulip client for sending messages](https://github.com/nymtech/nym/pull/5913): In order to be able to send zulip notifications about *emergency* upgrade mode being activated, we need some sort of client. Unfortunately there isn't any rust library that's maintained (the only one had last commit 4 years ago). This simple thing now currently only supports message sending
|
||||
|
||||
- [`nym-node` debug command to reset providers db](https://github.com/nymtech/nym/pull/5914)
|
||||
|
||||
@@ -634,7 +316,7 @@ nym-vpnc connect --enable-two-hop --entry-gateway-id 7CWjY3QFoA9dgE535u9bQiXCfz
|
||||
|
||||
### Refactors & Maintenance
|
||||
|
||||
- [Allow compatibility with 'CDLA-Permissive-2.0'](https://github.com/nymtech/nym/pull/5910): This license is present in the included `webpki-roots`
|
||||
- [Allow compatibility with 'CDLA-Permissive-2.0'](https://github.com/nymtech/nym/pull/5910): This license is present in the included `webpki-roots`
|
||||
|
||||
- [Migrate strum to `0.27.2`](https://github.com/nymtech/nym/pull/5960): This PR migrates strum to the latest. Notably all macros' were moved into `strum_macros`. The rest stays the same.
|
||||
|
||||
@@ -659,9 +341,9 @@ cargo Profile: release
|
||||
|
||||
### Operators Updates & Tools
|
||||
|
||||
- Stark Industries is on a sanction list by EU. IP addresses managed by Stark Ind. and their subsidies (ASN 44477 / ASN 33993) had been put on [spamhaus.org](http://spamhaus.org/) [list](https://www.spamhaus.org/drop/asndrop.json). The effect on NymVPN user experience is that Exit Gateways IPs hosted on Stark Ind. are seen as a spam proxies by many online services.
|
||||
- Stark Industries is on a sanction list by EU. IP addresses managed by Stark Ind. and their subsidies (ASN 44477 / ASN 33993) had been put on [spamhaus.org](http://spamhaus.org/) [list](https://www.spamhaus.org/drop/asndrop.json). The effect on NymVPN user experience is that Exit Gateways IPs hosted on Stark Ind. are seen as a spam proxies by many online services.
|
||||
|
||||
- We ask operators - especially Exit Gateways - to consider moving to another ISP. Visit an updated [ISP list](community-counsel/isp-list) and feel free to add more providers, following [these steps](community-counsel/add-content).
|
||||
- We ask operators - especially Exit Gateways - to consider moving to another ISP. Visit an updated [ISP list](community-counsel/isp-list) and feel free to add more providers, following [these steps](community-counsel/add-content).
|
||||
|
||||
### Features
|
||||
|
||||
@@ -675,13 +357,13 @@ cargo Profile: release
|
||||
- [`sqlx-pool-guard`: allocate more memory on windows](https://github.com/nymtech/nym/pull/5896):
|
||||
- Allocate 1.5x more memory than reported by the system to provide a safety margin
|
||||
- Increase number of retry attempts to 5
|
||||
|
||||
|
||||
|
||||
- [dkg epoch dealers query](https://github.com/nymtech/nym/pull/5899)
|
||||
|
||||
- [dkg snapshot epoch](https://github.com/nymtech/nym/pull/5900): In order to determine if signer quorum has been down at particular height, we need to know with certainty the dkg epoch id corresponding to given block height. This PR makes it possible. Every time epoch state is changed (due to DKG progress), snapshot is saved and can be queried. This doesn't work for past data, but given mainnet has only had a single DKG instance, that's not an issue.
|
||||
|
||||
- [`sqlx-pool-guard`: obtain filename from connect options](https://github.com/nymtech/nym/pull/5905):
|
||||
- [`sqlx-pool-guard`: obtain filename from connect options](https://github.com/nymtech/nym/pull/5905):
|
||||
|
||||
### Refactors & Maintenance
|
||||
|
||||
@@ -737,7 +419,7 @@ cargo Profile: release
|
||||
|
||||
- [Remove old explorer references](https://github.com/nymtech/nym/pull/5846)
|
||||
|
||||
- [Listen for shutdown signals during nym-node startup](https://github.com/nymtech/nym/pull/5879): This is to avoid situation where the process can't be killed without 'kill -9' because the logic to listen to shutdown signals hasn't been hit yet
|
||||
- [Listen for shutdown signals during nym-node startup](https://github.com/nymtech/nym/pull/5879): This is to avoid situation where the process can't be killed without 'kill -9' because the logic to listen to shutdown signals hasn't been hit yet
|
||||
|
||||
### Bugfixes
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import ExitPolicyStatusOutput from 'components/operators/snippets/wg-exit-policy
|
||||
import ExitPolicyTestOutput from 'components/operators/snippets/wg-exit-policy-test-output.mdx';
|
||||
import ExitPolicyTestServer from 'components/operators/snippets/wg-exit-policy-testing-from-server.mdx';
|
||||
import ExitPolicyTestOutside from 'components/operators/snippets/wg-exit-policy-testing-from-outside.mdx';
|
||||
import QuicDeploymentSteps from 'components/operators/snippets/quic-bridge-deployment-script-setup.mdx';
|
||||
|
||||
|
||||
export const ManagerIPOutput = () => (
|
||||
@@ -535,9 +534,6 @@ You can validate the application of the IP tables routes on your `nym-node` by c
|
||||
|
||||
Your node has successfully implemented wireguard exit policy with the same routing permissions like [Nym exit policy](https://nymtech.net/.wellknown/network-requester/exit-policy.txt) used on 5-hop (Mixnet).
|
||||
|
||||
## QUIC Transport Bridge Deployment
|
||||
|
||||
<QuicDeploymentSteps />
|
||||
|
||||
## Running `nym-node` as a non-root
|
||||
|
||||
|
||||
@@ -21,13 +21,13 @@ 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-10-15T09:04:32.043934599Z
|
||||
Build Version: 1.19.0
|
||||
Commit SHA: 2235a6e1477bea7368ee5443a298f544deb63504
|
||||
Commit Date: 2025-10-15T10:22:16.000000000+02:00
|
||||
Commit Branch: master
|
||||
rustc Version: 1.92.0-nightly
|
||||
rustc Channel: nightly
|
||||
Build Timestamp: 2025-10-01T10:42:58.647419869Z
|
||||
Build Version: 1.18.0
|
||||
Commit SHA: bbea2ff9e913f49cb7bf6c7bafa9d9b158c80de5
|
||||
Commit Date: 2025-10-01T12:06:07.000000000+02:00
|
||||
Commit Branch: HEAD
|
||||
rustc Version: 1.88.0
|
||||
rustc Channel: stable
|
||||
cargo Profile: release
|
||||
```
|
||||
|
||||
|
||||
Generated
+138
-194
@@ -36,11 +36,11 @@ importers:
|
||||
specifier: ^0.32.2
|
||||
version: 0.32.4
|
||||
'@cosmos-kit/core':
|
||||
specifier: ^2.16.3
|
||||
specifier: ^2.8.9
|
||||
version: 2.16.3
|
||||
'@cosmos-kit/keplr':
|
||||
specifier: ^2.6.9
|
||||
version: 2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@walletconnect/sign-client@2.21.8(typescript@5.9.3)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)(typescript@5.9.3)(zod@3.25.76)
|
||||
version: 2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@walletconnect/sign-client@2.21.8(typescript@4.9.5)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)(typescript@4.9.5)(zod@3.25.76)
|
||||
'@cosmos-kit/keplr-extension':
|
||||
specifier: ^2.7.9
|
||||
version: 2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(starknet@7.6.4)
|
||||
@@ -48,41 +48,38 @@ importers:
|
||||
specifier: ^2.6.9
|
||||
version: 2.14.3(@cosmjs/amino@0.32.4)(@cosmjs/crypto@0.32.4)(@cosmjs/encoding@0.32.4)(@cosmjs/proto-signing@0.32.4)
|
||||
'@cosmos-kit/react':
|
||||
specifier: ^2.22.3
|
||||
version: 2.22.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@interchain-ui/react@1.26.3(@types/react@18.3.26)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
specifier: ^2.10.11
|
||||
version: 2.22.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@interchain-ui/react@1.26.3(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@emotion/react':
|
||||
specifier: ^11.11.1
|
||||
version: 11.14.0(@types/react@18.3.26)(react@18.3.1)
|
||||
version: 11.14.0(react@18.3.1)
|
||||
'@emotion/styled':
|
||||
specifier: ^11.11.0
|
||||
version: 11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)
|
||||
version: 11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1)
|
||||
'@interchain-ui/react':
|
||||
specifier: ^1.8.0
|
||||
version: 1.26.3(@types/react@18.3.26)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
version: 1.26.3(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/icons-material':
|
||||
specifier: ^5.14.9
|
||||
version: 5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)
|
||||
version: 5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
'@mui/lab':
|
||||
specifier: ^5.0.0-alpha.145
|
||||
version: 5.0.0-alpha.177(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
version: 5.0.0-alpha.177(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/material':
|
||||
specifier: ^5.14.8
|
||||
version: 5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
version: 5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/accordion':
|
||||
specifier: ^2.0.40
|
||||
version: 2.2.7(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/react':
|
||||
specifier: ^2.4.8
|
||||
version: 2.6.11(@types/react@18.3.26)(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.12)
|
||||
'@nymproject/contract-clients':
|
||||
specifier: '>=1.2.4-rc.2 || ^1'
|
||||
version: 1.4.1
|
||||
version: 2.6.11(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.12)
|
||||
'@nymproject/mix-fetch-full-fat':
|
||||
specifier: '>=1.2.4-rc.2 || ^1'
|
||||
version: 1.2.3
|
||||
specifier: file:../../dist/wasm/mix-fetch
|
||||
version: '@nymproject/mix-fetch-wasm@file:../../dist/wasm/mix-fetch'
|
||||
'@nymproject/sdk-full-fat':
|
||||
specifier: '>=1.2.4-rc.2 || ^1'
|
||||
version: 1.2.3
|
||||
specifier: file:../../dist/wasm/client
|
||||
version: '@nymproject/nym-client-wasm@file:../../dist/wasm/client'
|
||||
'@redocly/cli':
|
||||
specifier: ^1.25.15
|
||||
version: 1.34.5(ajv@8.17.1)
|
||||
@@ -120,18 +117,9 @@ importers:
|
||||
specifier: ^9.0.0
|
||||
version: 9.0.1
|
||||
devDependencies:
|
||||
'@next/swc-linux-x64-gnu':
|
||||
specifier: 15.5.0
|
||||
version: 15.5.0
|
||||
'@types/node':
|
||||
specifier: 18.11.10
|
||||
version: 18.11.10
|
||||
'@types/react':
|
||||
specifier: ^18.3.26
|
||||
version: 18.3.26
|
||||
'@types/react-dom':
|
||||
specifier: ^18.3.7
|
||||
version: 18.3.7(@types/react@18.3.26)
|
||||
copy-webpack-plugin:
|
||||
specifier: ^11.0.0
|
||||
version: 11.0.0(webpack@5.101.3)
|
||||
@@ -140,13 +128,13 @@ importers:
|
||||
version: 8.46.0
|
||||
eslint-config-next:
|
||||
specifier: 13.4.13
|
||||
version: 13.4.13(eslint@8.46.0)(typescript@5.9.3)
|
||||
version: 13.4.13(eslint@8.46.0)(typescript@4.9.5)
|
||||
raw-loader:
|
||||
specifier: ^4.0.2
|
||||
version: 4.0.2(webpack@5.101.3)
|
||||
typescript:
|
||||
specifier: ^5.9.3
|
||||
version: 5.9.3
|
||||
specifier: ^4.9.3
|
||||
version: 4.9.5
|
||||
|
||||
packages:
|
||||
|
||||
@@ -1676,14 +1664,11 @@ packages:
|
||||
resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==}
|
||||
engines: {node: '>=12.4.0'}
|
||||
|
||||
'@nymproject/contract-clients@1.4.1':
|
||||
resolution: {integrity: sha512-HuJZ4Hv+Rl6ZZEtCHKgurNLJapM+QQRJlGkevFH2a4UdqUqF9omUkUi3AVes4679dPoSFgvA7plyVSDBdbgV6w==}
|
||||
'@nymproject/mix-fetch-wasm@file:../../dist/wasm/mix-fetch':
|
||||
resolution: {directory: ../../dist/wasm/mix-fetch, type: directory}
|
||||
|
||||
'@nymproject/mix-fetch-full-fat@1.2.3':
|
||||
resolution: {integrity: sha512-5yAQhw33LC0cNsyV0Rj+LIa2kvJY+8jsfgOSr1aeKNfQ7dG4fngWkKnGUbPGzhUTJjqgXPTwNVeJSd6hwGim+g==}
|
||||
|
||||
'@nymproject/sdk-full-fat@1.2.3':
|
||||
resolution: {integrity: sha512-lheA1Zk020o2t95FPVZfdUR0aSI52aD0aJQs074fjH16QkoNNEMotzhJGAnsnnczVg3bpddKghNpCLyDOWvg9A==}
|
||||
'@nymproject/nym-client-wasm@file:../../dist/wasm/client':
|
||||
resolution: {directory: ../../dist/wasm/client, type: directory}
|
||||
|
||||
'@opentelemetry/api-logs@0.53.0':
|
||||
resolution: {integrity: sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==}
|
||||
@@ -2859,18 +2844,13 @@ packages:
|
||||
'@types/prop-types@15.7.15':
|
||||
resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
|
||||
|
||||
'@types/react-dom@18.3.7':
|
||||
resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
|
||||
peerDependencies:
|
||||
'@types/react': ^18.0.0
|
||||
|
||||
'@types/react-transition-group@4.4.12':
|
||||
resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
|
||||
'@types/react@18.3.26':
|
||||
resolution: {integrity: sha512-RFA/bURkcKzx/X9oumPG9Vp3D3JUgus/d0b67KB0t5S/raciymilkOa66olh78MUI92QLbEJevO7rvqU/kjwKA==}
|
||||
'@types/react@19.1.10':
|
||||
resolution: {integrity: sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==}
|
||||
|
||||
'@types/stylis@4.2.5':
|
||||
resolution: {integrity: sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==}
|
||||
@@ -6406,9 +6386,9 @@ packages:
|
||||
typeforce@1.18.0:
|
||||
resolution: {integrity: sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==}
|
||||
|
||||
typescript@5.9.3:
|
||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||
engines: {node: '>=14.17'}
|
||||
typescript@4.9.5:
|
||||
resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==}
|
||||
engines: {node: '>=4.2.0'}
|
||||
hasBin: true
|
||||
|
||||
ufo@1.6.1:
|
||||
@@ -7179,16 +7159,16 @@ snapshots:
|
||||
- uploadthing
|
||||
- utf-8-validate
|
||||
|
||||
'@cosmos-kit/keplr-mobile@2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@walletconnect/sign-client@2.21.8(typescript@5.9.3)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)(typescript@5.9.3)(zod@3.25.76)':
|
||||
'@cosmos-kit/keplr-mobile@2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@walletconnect/sign-client@2.21.8(typescript@4.9.5)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)(typescript@4.9.5)(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@chain-registry/keplr': 1.74.313
|
||||
'@cosmjs/amino': 0.32.4
|
||||
'@cosmjs/proto-signing': 0.32.4
|
||||
'@cosmos-kit/core': 2.16.3
|
||||
'@cosmos-kit/keplr-extension': 2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(starknet@7.6.4)
|
||||
'@cosmos-kit/walletconnect': 2.13.3(@cosmjs/amino@0.32.4)(@walletconnect/types@2.21.8)(typescript@5.9.3)(zod@3.25.76)
|
||||
'@cosmos-kit/walletconnect': 2.13.3(@cosmjs/amino@0.32.4)(@walletconnect/types@2.21.8)(typescript@4.9.5)(zod@3.25.76)
|
||||
'@keplr-wallet/provider-extension': 0.12.264(starknet@7.6.4)
|
||||
'@keplr-wallet/wc-client': 0.12.264(@walletconnect/sign-client@2.21.8(typescript@5.9.3)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)
|
||||
'@keplr-wallet/wc-client': 0.12.264(@walletconnect/sign-client@2.21.8(typescript@4.9.5)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)
|
||||
transitivePeerDependencies:
|
||||
- '@azure/app-configuration'
|
||||
- '@azure/cosmos'
|
||||
@@ -7219,10 +7199,10 @@ snapshots:
|
||||
- utf-8-validate
|
||||
- zod
|
||||
|
||||
'@cosmos-kit/keplr@2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@walletconnect/sign-client@2.21.8(typescript@5.9.3)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)(typescript@5.9.3)(zod@3.25.76)':
|
||||
'@cosmos-kit/keplr@2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@walletconnect/sign-client@2.21.8(typescript@4.9.5)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)(typescript@4.9.5)(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@cosmos-kit/keplr-extension': 2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(starknet@7.6.4)
|
||||
'@cosmos-kit/keplr-mobile': 2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@walletconnect/sign-client@2.21.8(typescript@5.9.3)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)(typescript@5.9.3)(zod@3.25.76)
|
||||
'@cosmos-kit/keplr-mobile': 2.15.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@walletconnect/sign-client@2.21.8(typescript@4.9.5)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)(typescript@4.9.5)(zod@3.25.76)
|
||||
transitivePeerDependencies:
|
||||
- '@azure/app-configuration'
|
||||
- '@azure/cosmos'
|
||||
@@ -7290,13 +7270,11 @@ snapshots:
|
||||
- uploadthing
|
||||
- utf-8-validate
|
||||
|
||||
'@cosmos-kit/react-lite@2.16.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
'@cosmos-kit/react-lite@2.16.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@chain-registry/types': 0.46.15
|
||||
'@cosmos-kit/core': 2.16.3
|
||||
'@dao-dao/cosmiframe': 1.0.0(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)
|
||||
'@types/react': 18.3.26
|
||||
'@types/react-dom': 18.3.7(@types/react@18.3.26)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
transitivePeerDependencies:
|
||||
@@ -7326,15 +7304,13 @@ snapshots:
|
||||
- uploadthing
|
||||
- utf-8-validate
|
||||
|
||||
'@cosmos-kit/react@2.22.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@interchain-ui/react@1.26.3(@types/react@18.3.26)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
'@cosmos-kit/react@2.22.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@interchain-ui/react@1.26.3(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@chain-registry/types': 0.46.15
|
||||
'@cosmos-kit/core': 2.16.3
|
||||
'@cosmos-kit/react-lite': 2.16.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(@types/react-dom@18.3.7(@types/react@18.3.26))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@interchain-ui/react': 1.26.3(@types/react@18.3.26)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@cosmos-kit/react-lite': 2.16.3(@cosmjs/amino@0.32.4)(@cosmjs/proto-signing@0.32.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@interchain-ui/react': 1.26.3(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@react-icons/all-files': 4.1.0(react@18.3.1)
|
||||
'@types/react': 18.3.26
|
||||
'@types/react-dom': 18.3.7(@types/react@18.3.26)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
transitivePeerDependencies:
|
||||
@@ -7364,14 +7340,14 @@ snapshots:
|
||||
- uploadthing
|
||||
- utf-8-validate
|
||||
|
||||
'@cosmos-kit/walletconnect@2.13.3(@cosmjs/amino@0.32.4)(@walletconnect/types@2.21.8)(typescript@5.9.3)(zod@3.25.76)':
|
||||
'@cosmos-kit/walletconnect@2.13.3(@cosmjs/amino@0.32.4)(@walletconnect/types@2.21.8)(typescript@4.9.5)(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@cosmjs/amino': 0.32.4
|
||||
'@cosmjs/proto-signing': 0.32.4
|
||||
'@cosmos-kit/core': 2.16.3
|
||||
'@walletconnect/sign-client': 2.21.8(typescript@5.9.3)(zod@3.25.76)
|
||||
'@walletconnect/sign-client': 2.21.8(typescript@4.9.5)(zod@3.25.76)
|
||||
'@walletconnect/types': 2.21.8
|
||||
'@walletconnect/utils': 2.21.8(typescript@5.9.3)(zod@3.25.76)
|
||||
'@walletconnect/utils': 2.21.8(typescript@4.9.5)(zod@3.25.76)
|
||||
events: 3.3.0
|
||||
transitivePeerDependencies:
|
||||
- '@azure/app-configuration'
|
||||
@@ -7460,7 +7436,7 @@ snapshots:
|
||||
|
||||
'@emotion/memoize@0.9.0': {}
|
||||
|
||||
'@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1)':
|
||||
'@emotion/react@11.14.0(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.3
|
||||
'@emotion/babel-plugin': 11.13.5
|
||||
@@ -7471,8 +7447,6 @@ snapshots:
|
||||
'@emotion/weak-memoize': 0.4.0
|
||||
hoist-non-react-statics: 3.3.2
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.26
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -7486,18 +7460,16 @@ snapshots:
|
||||
|
||||
'@emotion/sheet@1.4.0': {}
|
||||
|
||||
'@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)':
|
||||
'@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.3
|
||||
'@emotion/babel-plugin': 11.13.5
|
||||
'@emotion/is-prop-valid': 1.3.1
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.26)(react@18.3.1)
|
||||
'@emotion/react': 11.14.0(react@18.3.1)
|
||||
'@emotion/serialize': 1.3.3
|
||||
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1)
|
||||
'@emotion/utils': 1.4.2
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.26
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -7730,7 +7702,7 @@ snapshots:
|
||||
'@img/sharp-win32-x64@0.34.3':
|
||||
optional: true
|
||||
|
||||
'@interchain-ui/react@1.26.3(@types/react@18.3.26)(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
'@interchain-ui/react@1.26.3(babel-plugin-macros@3.1.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.7.3
|
||||
'@floating-ui/dom': 1.7.3
|
||||
@@ -7759,7 +7731,7 @@ snapshots:
|
||||
react-aria: 3.42.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-stately: 3.40.0(react@18.3.1)
|
||||
zustand: 4.5.7(@types/react@18.3.26)(immer@10.1.1)(react@18.3.1)
|
||||
zustand: 4.5.7(immer@10.1.1)(react@18.3.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
- babel-plugin-macros
|
||||
@@ -7892,11 +7864,11 @@ snapshots:
|
||||
big-integer: 1.6.52
|
||||
utility-types: 3.11.0
|
||||
|
||||
'@keplr-wallet/wc-client@0.12.264(@walletconnect/sign-client@2.21.8(typescript@5.9.3)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)':
|
||||
'@keplr-wallet/wc-client@0.12.264(@walletconnect/sign-client@2.21.8(typescript@4.9.5)(zod@3.25.76))(@walletconnect/types@2.21.8)(starknet@7.6.4)':
|
||||
dependencies:
|
||||
'@keplr-wallet/provider': 0.12.264(starknet@7.6.4)
|
||||
'@keplr-wallet/types': 0.12.264(starknet@7.6.4)
|
||||
'@walletconnect/sign-client': 2.21.8(typescript@5.9.3)(zod@3.25.76)
|
||||
'@walletconnect/sign-client': 2.21.8(typescript@4.9.5)(zod@3.25.76)
|
||||
'@walletconnect/types': 2.21.8
|
||||
buffer: 6.0.3
|
||||
deepmerge: 4.3.1
|
||||
@@ -7966,61 +7938,56 @@ snapshots:
|
||||
'@mdx-js/react@2.3.0(react@18.3.1)':
|
||||
dependencies:
|
||||
'@types/mdx': 2.0.13
|
||||
'@types/react': 18.3.26
|
||||
'@types/react': 19.1.10
|
||||
react: 18.3.1
|
||||
|
||||
'@msgpack/msgpack@3.1.2': {}
|
||||
|
||||
'@mui/base@5.0.0-beta.40-1(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
'@mui/base@5.0.0-beta.40-1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.3
|
||||
'@floating-ui/react-dom': 2.1.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/types': 7.2.24(@types/react@18.3.26)
|
||||
'@mui/utils': 5.17.1(@types/react@18.3.26)(react@18.3.1)
|
||||
'@mui/types': 7.2.24
|
||||
'@mui/utils': 5.17.1(react@18.3.1)
|
||||
'@popperjs/core': 2.11.8
|
||||
clsx: 2.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.26
|
||||
|
||||
'@mui/core-downloads-tracker@5.18.0': {}
|
||||
|
||||
'@mui/icons-material@5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)':
|
||||
'@mui/icons-material@5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.3
|
||||
'@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/material': 5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.26
|
||||
|
||||
'@mui/lab@5.0.0-alpha.177(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
'@mui/lab@5.0.0-alpha.177(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.3
|
||||
'@mui/base': 5.0.0-beta.40-1(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)
|
||||
'@mui/types': 7.2.24(@types/react@18.3.26)
|
||||
'@mui/utils': 5.17.1(@types/react@18.3.26)(react@18.3.1)
|
||||
'@mui/base': 5.0.0-beta.40-1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/material': 5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/system': 5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
'@mui/types': 7.2.24
|
||||
'@mui/utils': 5.17.1(react@18.3.1)
|
||||
clsx: 2.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.26)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)
|
||||
'@types/react': 18.3.26
|
||||
'@emotion/react': 11.14.0(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1)
|
||||
|
||||
'@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
'@mui/material@5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.3
|
||||
'@mui/core-downloads-tracker': 5.18.0
|
||||
'@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)
|
||||
'@mui/types': 7.2.24(@types/react@18.3.26)
|
||||
'@mui/utils': 5.17.1(@types/react@18.3.26)(react@18.3.1)
|
||||
'@mui/system': 5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
'@mui/types': 7.2.24
|
||||
'@mui/utils': 5.17.1(react@18.3.1)
|
||||
'@popperjs/core': 2.11.8
|
||||
'@types/react-transition-group': 4.4.12(@types/react@18.3.26)
|
||||
'@types/react-transition-group': 4.4.12
|
||||
clsx: 2.1.1
|
||||
csstype: 3.1.3
|
||||
prop-types: 15.8.1
|
||||
@@ -8029,20 +7996,17 @@ snapshots:
|
||||
react-is: 19.1.1
|
||||
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.26)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)
|
||||
'@types/react': 18.3.26
|
||||
'@emotion/react': 11.14.0(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1)
|
||||
|
||||
'@mui/private-theming@5.17.1(@types/react@18.3.26)(react@18.3.1)':
|
||||
'@mui/private-theming@5.17.1(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.3
|
||||
'@mui/utils': 5.17.1(@types/react@18.3.26)(react@18.3.1)
|
||||
'@mui/utils': 5.17.1(react@18.3.1)
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.26
|
||||
|
||||
'@mui/styled-engine@5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(react@18.3.1)':
|
||||
'@mui/styled-engine@5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.3
|
||||
'@emotion/cache': 11.14.0
|
||||
@@ -8051,40 +8015,35 @@ snapshots:
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.26)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)
|
||||
'@emotion/react': 11.14.0(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1)
|
||||
|
||||
'@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)':
|
||||
'@mui/system@5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.3
|
||||
'@mui/private-theming': 5.17.1(@types/react@18.3.26)(react@18.3.1)
|
||||
'@mui/styled-engine': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1))(react@18.3.1)
|
||||
'@mui/types': 7.2.24(@types/react@18.3.26)
|
||||
'@mui/utils': 5.17.1(@types/react@18.3.26)(react@18.3.1)
|
||||
'@mui/private-theming': 5.17.1(react@18.3.1)
|
||||
'@mui/styled-engine': 5.18.0(@emotion/react@11.14.0(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1))(react@18.3.1)
|
||||
'@mui/types': 7.2.24
|
||||
'@mui/utils': 5.17.1(react@18.3.1)
|
||||
clsx: 2.1.1
|
||||
csstype: 3.1.3
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.26)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.26)(react@18.3.1))(@types/react@18.3.26)(react@18.3.1)
|
||||
'@types/react': 18.3.26
|
||||
'@emotion/react': 11.14.0(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(react@18.3.1))(react@18.3.1)
|
||||
|
||||
'@mui/types@7.2.24(@types/react@18.3.26)':
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.26
|
||||
'@mui/types@7.2.24': {}
|
||||
|
||||
'@mui/utils@5.17.1(@types/react@18.3.26)(react@18.3.1)':
|
||||
'@mui/utils@5.17.1(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.3
|
||||
'@mui/types': 7.2.24(@types/react@18.3.26)
|
||||
'@mui/types': 7.2.24
|
||||
'@types/prop-types': 15.7.15
|
||||
clsx: 2.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
react-is: 19.1.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.26
|
||||
|
||||
'@napi-rs/simple-git-android-arm-eabi@0.1.22':
|
||||
optional: true
|
||||
@@ -8174,7 +8133,8 @@ snapshots:
|
||||
'@next/swc-linux-arm64-musl@15.5.0':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-gnu@15.5.0': {}
|
||||
'@next/swc-linux-x64-gnu@15.5.0':
|
||||
optional: true
|
||||
|
||||
'@next/swc-linux-x64-musl@15.5.0':
|
||||
optional: true
|
||||
@@ -8239,12 +8199,12 @@ snapshots:
|
||||
- '@nextui-org/theme'
|
||||
- framer-motion
|
||||
|
||||
'@nextui-org/autocomplete@2.3.9(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(@types/react@18.3.26)(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
'@nextui-org/autocomplete@2.3.9(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@nextui-org/aria-utils': 2.2.7(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/button': 2.2.9(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/form': 2.1.8(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/input': 2.4.8(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/input': 2.4.8(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/listbox': 2.3.9(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/popover': 2.3.9(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/react-utils': 2.1.3(react@18.3.1)
|
||||
@@ -8556,7 +8516,7 @@ snapshots:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
'@nextui-org/input@2.4.8(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
'@nextui-org/input@2.4.8(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@nextui-org/form': 2.1.8(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/react-utils': 2.1.3(react@18.3.1)
|
||||
@@ -8574,7 +8534,7 @@ snapshots:
|
||||
'@react-types/textfield': 3.10.0(react@18.3.1)
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-textarea-autosize: 8.5.9(@types/react@18.3.26)(react@18.3.1)
|
||||
react-textarea-autosize: 8.5.9(react@18.3.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
@@ -8774,11 +8734,11 @@ snapshots:
|
||||
'@nextui-org/shared-utils': 2.1.2
|
||||
react: 18.3.1
|
||||
|
||||
'@nextui-org/react@2.6.11(@types/react@18.3.26)(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.12)':
|
||||
'@nextui-org/react@2.6.11(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tailwindcss@4.1.12)':
|
||||
dependencies:
|
||||
'@nextui-org/accordion': 2.2.7(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/alert': 2.2.9(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/autocomplete': 2.3.9(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(@types/react@18.3.26)(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/autocomplete': 2.3.9(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/avatar': 2.2.6(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/badge': 2.2.5(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/breadcrumbs': 2.2.6(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
@@ -8796,7 +8756,7 @@ snapshots:
|
||||
'@nextui-org/form': 2.1.8(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/framer-utils': 2.1.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/image': 2.2.5(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/input': 2.4.8(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(@types/react@18.3.26)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/input': 2.4.8(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/input-otp': 2.1.8(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/kbd': 2.2.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@nextui-org/link': 2.2.7(@nextui-org/system@2.4.6(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(framer-motion@12.23.12(@emotion/is-prop-valid@1.3.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@nextui-org/theme@2.4.5(tailwindcss@4.1.12))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
@@ -9245,11 +9205,9 @@ snapshots:
|
||||
|
||||
'@nolyfill/is-core-module@1.0.39': {}
|
||||
|
||||
'@nymproject/contract-clients@1.4.1': {}
|
||||
'@nymproject/mix-fetch-wasm@file:../../dist/wasm/mix-fetch': {}
|
||||
|
||||
'@nymproject/mix-fetch-full-fat@1.2.3': {}
|
||||
|
||||
'@nymproject/sdk-full-fat@1.2.3': {}
|
||||
'@nymproject/nym-client-wasm@file:../../dist/wasm/client': {}
|
||||
|
||||
'@opentelemetry/api-logs@0.53.0':
|
||||
dependencies:
|
||||
@@ -11211,17 +11169,10 @@ snapshots:
|
||||
|
||||
'@types/prop-types@15.7.15': {}
|
||||
|
||||
'@types/react-dom@18.3.7(@types/react@18.3.26)':
|
||||
dependencies:
|
||||
'@types/react': 18.3.26
|
||||
'@types/react-transition-group@4.4.12': {}
|
||||
|
||||
'@types/react-transition-group@4.4.12(@types/react@18.3.26)':
|
||||
'@types/react@19.1.10':
|
||||
dependencies:
|
||||
'@types/react': 18.3.26
|
||||
|
||||
'@types/react@18.3.26':
|
||||
dependencies:
|
||||
'@types/prop-types': 15.7.15
|
||||
csstype: 3.1.3
|
||||
|
||||
'@types/stylis@4.2.5': {}
|
||||
@@ -11233,16 +11184,16 @@ snapshots:
|
||||
|
||||
'@types/unist@3.0.3': {}
|
||||
|
||||
'@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@5.9.3)':
|
||||
'@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5)':
|
||||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 6.21.0
|
||||
'@typescript-eslint/types': 6.21.0
|
||||
'@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3)
|
||||
'@typescript-eslint/typescript-estree': 6.21.0(typescript@4.9.5)
|
||||
'@typescript-eslint/visitor-keys': 6.21.0
|
||||
debug: 4.4.1
|
||||
eslint: 8.46.0
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
typescript: 4.9.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -11253,7 +11204,7 @@ snapshots:
|
||||
|
||||
'@typescript-eslint/types@6.21.0': {}
|
||||
|
||||
'@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.3)':
|
||||
'@typescript-eslint/typescript-estree@6.21.0(typescript@4.9.5)':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 6.21.0
|
||||
'@typescript-eslint/visitor-keys': 6.21.0
|
||||
@@ -11262,9 +11213,9 @@ snapshots:
|
||||
is-glob: 4.0.3
|
||||
minimatch: 9.0.3
|
||||
semver: 7.7.2
|
||||
ts-api-utils: 1.4.3(typescript@5.9.3)
|
||||
ts-api-utils: 1.4.3(typescript@4.9.5)
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
typescript: 4.9.5
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -11363,7 +11314,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@vanilla-extract/css': 1.17.4(babel-plugin-macros@3.1.0)
|
||||
|
||||
'@walletconnect/core@2.21.8(typescript@5.9.3)(zod@3.25.76)':
|
||||
'@walletconnect/core@2.21.8(typescript@4.9.5)(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@walletconnect/heartbeat': 1.2.2
|
||||
'@walletconnect/jsonrpc-provider': 1.0.14
|
||||
@@ -11377,7 +11328,7 @@ snapshots:
|
||||
'@walletconnect/safe-json': 1.0.2
|
||||
'@walletconnect/time': 1.0.2
|
||||
'@walletconnect/types': 2.21.8
|
||||
'@walletconnect/utils': 2.21.8(typescript@5.9.3)(zod@3.25.76)
|
||||
'@walletconnect/utils': 2.21.8(typescript@4.9.5)(zod@3.25.76)
|
||||
'@walletconnect/window-getters': 1.0.1
|
||||
es-toolkit: 1.39.3
|
||||
events: 3.3.0
|
||||
@@ -11504,16 +11455,16 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 1.14.1
|
||||
|
||||
'@walletconnect/sign-client@2.21.8(typescript@5.9.3)(zod@3.25.76)':
|
||||
'@walletconnect/sign-client@2.21.8(typescript@4.9.5)(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@walletconnect/core': 2.21.8(typescript@5.9.3)(zod@3.25.76)
|
||||
'@walletconnect/core': 2.21.8(typescript@4.9.5)(zod@3.25.76)
|
||||
'@walletconnect/events': 1.0.1
|
||||
'@walletconnect/heartbeat': 1.2.2
|
||||
'@walletconnect/jsonrpc-utils': 1.0.8
|
||||
'@walletconnect/logger': 2.1.2
|
||||
'@walletconnect/time': 1.0.2
|
||||
'@walletconnect/types': 2.21.8
|
||||
'@walletconnect/utils': 2.21.8(typescript@5.9.3)(zod@3.25.76)
|
||||
'@walletconnect/utils': 2.21.8(typescript@4.9.5)(zod@3.25.76)
|
||||
events: 3.3.0
|
||||
transitivePeerDependencies:
|
||||
- '@azure/app-configuration'
|
||||
@@ -11599,7 +11550,7 @@ snapshots:
|
||||
- ioredis
|
||||
- uploadthing
|
||||
|
||||
'@walletconnect/utils@2.21.8(typescript@5.9.3)(zod@3.25.76)':
|
||||
'@walletconnect/utils@2.21.8(typescript@4.9.5)(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@msgpack/msgpack': 3.1.2
|
||||
'@noble/ciphers': 1.3.0
|
||||
@@ -11620,7 +11571,7 @@ snapshots:
|
||||
detect-browser: 5.3.0
|
||||
query-string: 7.1.3
|
||||
uint8arrays: 3.1.1
|
||||
viem: 2.31.0(typescript@5.9.3)(zod@3.25.76)
|
||||
viem: 2.31.0(typescript@4.9.5)(zod@3.25.76)
|
||||
transitivePeerDependencies:
|
||||
- '@azure/app-configuration'
|
||||
- '@azure/cosmos'
|
||||
@@ -11741,9 +11692,9 @@ snapshots:
|
||||
fs-extra: 10.1.0
|
||||
yargs: 17.7.2
|
||||
|
||||
abitype@1.0.8(typescript@5.9.3)(zod@3.25.76):
|
||||
abitype@1.0.8(typescript@4.9.5)(zod@3.25.76):
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
typescript: 4.9.5
|
||||
zod: 3.25.76
|
||||
|
||||
abort-controller@3.0.0:
|
||||
@@ -12759,20 +12710,20 @@ snapshots:
|
||||
|
||||
escape-string-regexp@5.0.0: {}
|
||||
|
||||
eslint-config-next@13.4.13(eslint@8.46.0)(typescript@5.9.3):
|
||||
eslint-config-next@13.4.13(eslint@8.46.0)(typescript@4.9.5):
|
||||
dependencies:
|
||||
'@next/eslint-plugin-next': 13.4.13
|
||||
'@rushstack/eslint-patch': 1.12.0
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.46.0)(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.46.0)(typescript@4.9.5)
|
||||
eslint: 8.46.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(eslint@8.46.0))(eslint@8.46.0)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@8.46.0))(eslint@8.46.0))(eslint@8.46.0)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1)(eslint@8.46.0)
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.46.0)
|
||||
eslint-plugin-react: 7.37.5(eslint@8.46.0)
|
||||
eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.46.0)
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
typescript: 4.9.5
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-webpack
|
||||
- eslint-plugin-import-x
|
||||
@@ -12797,22 +12748,22 @@ snapshots:
|
||||
tinyglobby: 0.2.14
|
||||
unrs-resolver: 1.11.1
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@8.46.0))(eslint@8.46.0))(eslint@8.46.0)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1)(eslint@8.46.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@8.46.0))(eslint@8.46.0))(eslint@8.46.0):
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@8.46.0))(eslint@8.46.0))(eslint@8.46.0):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.46.0)(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.46.0)(typescript@4.9.5)
|
||||
eslint: 8.46.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(eslint@8.46.0))(eslint@8.46.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@8.46.0))(eslint@8.46.0))(eslint@8.46.0):
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-typescript@3.10.1)(eslint@8.46.0):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.9
|
||||
@@ -12823,7 +12774,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 8.46.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@8.46.0))(eslint@8.46.0))(eslint@8.46.0)
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@6.21.0(eslint@8.46.0)(typescript@4.9.5))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(eslint@8.46.0))(eslint@8.46.0))(eslint@8.46.0)
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
@@ -12835,7 +12786,7 @@ snapshots:
|
||||
string.prototype.trimend: 1.0.9
|
||||
tsconfig-paths: 3.15.0
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.46.0)(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 6.21.0(eslint@8.46.0)(typescript@4.9.5)
|
||||
transitivePeerDependencies:
|
||||
- eslint-import-resolver-typescript
|
||||
- eslint-import-resolver-webpack
|
||||
@@ -14704,7 +14655,7 @@ snapshots:
|
||||
object-keys: 1.1.1
|
||||
safe-push-apply: 1.0.0
|
||||
|
||||
ox@0.7.1(typescript@5.9.3)(zod@3.25.76):
|
||||
ox@0.7.1(typescript@4.9.5)(zod@3.25.76):
|
||||
dependencies:
|
||||
'@adraffy/ens-normalize': 1.11.0
|
||||
'@noble/ciphers': 1.3.0
|
||||
@@ -14712,10 +14663,10 @@ snapshots:
|
||||
'@noble/hashes': 1.8.0
|
||||
'@scure/bip32': 1.7.0
|
||||
'@scure/bip39': 1.6.0
|
||||
abitype: 1.0.8(typescript@5.9.3)(zod@3.25.76)
|
||||
abitype: 1.0.8(typescript@4.9.5)(zod@3.25.76)
|
||||
eventemitter3: 5.0.1
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
typescript: 4.9.5
|
||||
transitivePeerDependencies:
|
||||
- zod
|
||||
|
||||
@@ -15027,12 +14978,12 @@ snapshots:
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
|
||||
react-textarea-autosize@8.5.9(@types/react@18.3.26)(react@18.3.1):
|
||||
react-textarea-autosize@8.5.9(react@18.3.1):
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.3
|
||||
react: 18.3.1
|
||||
use-composed-ref: 1.4.0(@types/react@18.3.26)(react@18.3.1)
|
||||
use-latest: 1.3.0(@types/react@18.3.26)(react@18.3.1)
|
||||
use-composed-ref: 1.4.0(react@18.3.1)
|
||||
use-latest: 1.3.0(react@18.3.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
@@ -15759,9 +15710,9 @@ snapshots:
|
||||
|
||||
trough@2.2.0: {}
|
||||
|
||||
ts-api-utils@1.4.3(typescript@5.9.3):
|
||||
ts-api-utils@1.4.3(typescript@4.9.5):
|
||||
dependencies:
|
||||
typescript: 5.9.3
|
||||
typescript: 4.9.5
|
||||
|
||||
ts-dedent@2.2.0: {}
|
||||
|
||||
@@ -15825,7 +15776,7 @@ snapshots:
|
||||
|
||||
typeforce@1.18.0: {}
|
||||
|
||||
typescript@5.9.3: {}
|
||||
typescript@4.9.5: {}
|
||||
|
||||
ufo@1.6.1: {}
|
||||
|
||||
@@ -15994,24 +15945,18 @@ snapshots:
|
||||
|
||||
url-template@2.0.8: {}
|
||||
|
||||
use-composed-ref@1.4.0(@types/react@18.3.26)(react@18.3.1):
|
||||
use-composed-ref@1.4.0(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.26
|
||||
|
||||
use-isomorphic-layout-effect@1.2.1(@types/react@18.3.26)(react@18.3.1):
|
||||
use-isomorphic-layout-effect@1.2.1(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.26
|
||||
|
||||
use-latest@1.3.0(@types/react@18.3.26)(react@18.3.1):
|
||||
use-latest@1.3.0(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
use-isomorphic-layout-effect: 1.2.1(@types/react@18.3.26)(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.26
|
||||
use-isomorphic-layout-effect: 1.2.1(react@18.3.1)
|
||||
|
||||
use-sync-external-store@1.5.0(react@18.3.1):
|
||||
dependencies:
|
||||
@@ -16065,18 +16010,18 @@ snapshots:
|
||||
'@types/unist': 3.0.3
|
||||
vfile-message: 4.0.3
|
||||
|
||||
viem@2.31.0(typescript@5.9.3)(zod@3.25.76):
|
||||
viem@2.31.0(typescript@4.9.5)(zod@3.25.76):
|
||||
dependencies:
|
||||
'@noble/curves': 1.9.1
|
||||
'@noble/hashes': 1.8.0
|
||||
'@scure/bip32': 1.7.0
|
||||
'@scure/bip39': 1.6.0
|
||||
abitype: 1.0.8(typescript@5.9.3)(zod@3.25.76)
|
||||
abitype: 1.0.8(typescript@4.9.5)(zod@3.25.76)
|
||||
isows: 1.0.7(ws@8.18.2)
|
||||
ox: 0.7.1(typescript@5.9.3)(zod@3.25.76)
|
||||
ox: 0.7.1(typescript@4.9.5)(zod@3.25.76)
|
||||
ws: 8.18.2
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
typescript: 4.9.5
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- utf-8-validate
|
||||
@@ -16250,11 +16195,10 @@ snapshots:
|
||||
|
||||
zod@3.25.76: {}
|
||||
|
||||
zustand@4.5.7(@types/react@18.3.26)(immer@10.1.1)(react@18.3.1):
|
||||
zustand@4.5.7(immer@10.1.1)(react@18.3.1):
|
||||
dependencies:
|
||||
use-sync-external-store: 1.5.0(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.26
|
||||
immer: 10.1.1
|
||||
react: 18.3.1
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 70 KiB |
+1
-1
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-api"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.67"
|
||||
version = "1.1.66"
|
||||
authors.workspace = true
|
||||
edition = "2021"
|
||||
rust-version.workspace = true
|
||||
|
||||
@@ -124,7 +124,6 @@ pub struct Config {
|
||||
pub node_status_api: NodeStatusAPI,
|
||||
|
||||
#[serde(alias = "topology_cacher")]
|
||||
#[serde(default)]
|
||||
pub describe_cache: DescribeCache,
|
||||
|
||||
#[serde(default)]
|
||||
|
||||
@@ -70,6 +70,30 @@ per_node_test_packets = {{ network_monitor.debug.per_node_test_packets }}
|
||||
# Path to the database file containing uptime statuses for all mixnodes and gateways.
|
||||
database_path = '{{ node_status_api.storage_paths.database_path }}'
|
||||
|
||||
[node_status_api.debug]
|
||||
|
||||
caching_interval = '{{ node_status_api.debug.caching_interval }}'
|
||||
|
||||
|
||||
##### topology cacher config options #####
|
||||
|
||||
[topology_cacher.debug]
|
||||
|
||||
caching_interval = '{{ topology_cacher.debug.caching_interval }}'
|
||||
|
||||
|
||||
##### circulating supply cacher config options #####
|
||||
|
||||
[circulating_supply_cacher]
|
||||
|
||||
# Specifies whether circulating supply caching service is enabled in this process.
|
||||
enabled = {{ circulating_supply_cacher.enabled }}
|
||||
|
||||
[circulating_supply_cacher.debug]
|
||||
|
||||
caching_interval = '{{ circulating_supply_cacher.debug.caching_interval }}'
|
||||
|
||||
|
||||
##### rewarding config options #####
|
||||
|
||||
[rewarding]
|
||||
|
||||
@@ -50,7 +50,7 @@ impl AuthClientMixnetListener {
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(mut self) {
|
||||
async fn run(mut self) -> Self {
|
||||
let mixnet_cancel_token = self.mixnet_client.cancellation_token();
|
||||
self.shutdown_token.run_until_cancelled(async {
|
||||
loop {
|
||||
@@ -95,8 +95,12 @@ impl AuthClientMixnetListener {
|
||||
tracing::debug!("AuthClientMixnetListener is shutting down");
|
||||
}).await;
|
||||
|
||||
tracing::debug!("AuthClientMixnetListener: Disconnect mixnet client");
|
||||
if !mixnet_cancel_token.is_cancelled() {
|
||||
self
|
||||
}
|
||||
|
||||
// Disconnects the mixnet client and effectively drop itself, since it doesn't work without one, and reconnecting isn't supported
|
||||
pub async fn disconnect_mixnet_client(self) {
|
||||
if !self.mixnet_client.cancellation_token().is_cancelled() {
|
||||
self.mixnet_client.disconnect().await;
|
||||
}
|
||||
}
|
||||
@@ -124,7 +128,7 @@ pub struct AuthClientMixnetListenerHandle {
|
||||
message_sender: MixnetMessageInputSender,
|
||||
cancellation_token: CancellationToken,
|
||||
mixnet_cancellation_token: CancellationToken,
|
||||
handle: JoinHandle<()>,
|
||||
handle: JoinHandle<AuthClientMixnetListener>,
|
||||
}
|
||||
|
||||
impl AuthClientMixnetListenerHandle {
|
||||
@@ -144,8 +148,13 @@ impl AuthClientMixnetListenerHandle {
|
||||
// If shutdown was externally called, that call is a no-op
|
||||
// If we're only stopping this, it is very much needed
|
||||
self.cancellation_token.cancel();
|
||||
if let Err(e) = self.handle.await {
|
||||
tracing::error!("Error waiting for auth clients mixnet listener to stop: {e}")
|
||||
match self.handle.await {
|
||||
Ok(auth_client_mixnet_listener) => {
|
||||
auth_client_mixnet_listener.disconnect_mixnet_client().await;
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("Error waiting for auth clients mixnet listener to stop: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ schemars = { workspace = true, features = ["preserve_order", "uuid1"] }
|
||||
uuid = { workspace = true, features = ["serde"] }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json.workspace = true
|
||||
serde_with = { workspace = true }
|
||||
time = { workspace = true, features = ["serde", "formatting", "parsing"] }
|
||||
tsify = { workspace = true, optional = true }
|
||||
reqwest = { workspace = true, features = ["json", "rustls-tls"] }
|
||||
|
||||
@@ -9,7 +9,6 @@ use schemars::JsonSchema;
|
||||
use schemars::r#gen::SchemaGenerator;
|
||||
use schemars::schema::Schema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_with::{DisplayFromStr, serde_as};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use time::{Date, OffsetDateTime};
|
||||
|
||||
@@ -265,14 +264,12 @@ pub struct WebhookTicketbookWalletSharesRequest {
|
||||
pub secret: String,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema, utoipa::IntoParams))]
|
||||
#[cfg(feature = "query-types")]
|
||||
#[serde(default, rename_all = "kebab-case")]
|
||||
pub struct TicketbookObtainParams {
|
||||
#[serde(default)]
|
||||
#[serde_as(as = "DisplayFromStr")]
|
||||
pub skip_webhook: bool,
|
||||
|
||||
#[serde(default)]
|
||||
@@ -280,19 +277,15 @@ pub struct TicketbookObtainParams {
|
||||
pub global: GlobalDataParams,
|
||||
}
|
||||
|
||||
#[serde_as]
|
||||
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
|
||||
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema, utoipa::IntoParams))]
|
||||
#[cfg(feature = "query-types")]
|
||||
#[serde(default, rename_all = "kebab-case")]
|
||||
pub struct GlobalDataParams {
|
||||
#[serde_as(as = "DisplayFromStr")]
|
||||
pub include_master_verification_key: bool,
|
||||
|
||||
#[serde_as(as = "DisplayFromStr")]
|
||||
pub include_coin_index_signatures: bool,
|
||||
|
||||
#[serde_as(as = "DisplayFromStr")]
|
||||
pub include_expiration_date_signatures: bool,
|
||||
}
|
||||
|
||||
|
||||
@@ -35,13 +35,15 @@ import (
|
||||
)
|
||||
|
||||
var fileUrls = []string{
|
||||
"https://proof.ovh.net/files/10Mb.dat",
|
||||
"https://proof.ovh.net/files/1Mb.dat",
|
||||
"https://nym-bandwidth-monitoring.ops-d86.workers.dev/1mb.dat",
|
||||
"https://nym-bandwidth-monitoring.ops-d86.workers.dev/10mb.dat",
|
||||
// "https://nym-bandwidth-monitoring.ops-d86.workers.dev/100mb.dat", to be introduced later
|
||||
}
|
||||
|
||||
var fileUrlsV6 = []string{
|
||||
"https://proof.ovh.net/files/10Mb.dat",
|
||||
"https://proof.ovh.net/files/1Mb.dat",
|
||||
"https://nym-bandwidth-monitoring.ops-d86.workers.dev/1mb.dat",
|
||||
"https://nym-bandwidth-monitoring.ops-d86.workers.dev/10mb.dat",
|
||||
// "https://nym-bandwidth-monitoring.ops-d86.workers.dev/100mb.dat", to be introduced later
|
||||
}
|
||||
@@ -192,20 +194,14 @@ func ping(req NetstackRequestGo) (NetstackResponse, error) {
|
||||
|
||||
response.CanHandshake = true
|
||||
|
||||
// Skip metadata query if endpoint is empty (e.g., for IPv6 where the IPv4 metadata endpoint is not reachable)
|
||||
if req.MetadataEndpoint != "" {
|
||||
version, duration, err := queryMetadata(req.MetadataEndpoint, req.MetadataTimeoutSec, tnet)
|
||||
if err != nil {
|
||||
log.Printf("Failed to query metadata URLs: %v\n", err)
|
||||
response.CanQueryMetadata = false
|
||||
} else {
|
||||
log.Printf("Queried metadata endpoint with version: %v\n", version)
|
||||
log.Printf("Query duration: %v\n", duration)
|
||||
response.CanQueryMetadata = true
|
||||
}
|
||||
} else {
|
||||
log.Printf("Skipping metadata query (no endpoint provided)")
|
||||
version, duration, err := queryMetadata(req.MetadataEndpoint, req.MetadataTimeoutSec, tnet)
|
||||
if err != nil {
|
||||
log.Printf("Failed to query metadata URLs: %v\n", err)
|
||||
response.CanQueryMetadata = false
|
||||
} else {
|
||||
log.Printf("Queried metadata endpoint with version: %v\n", version)
|
||||
log.Printf("Query duration: %v\n", duration)
|
||||
response.CanQueryMetadata = true
|
||||
}
|
||||
|
||||
for _, host := range req.PingHosts {
|
||||
@@ -546,25 +542,25 @@ func queryMetadata(url string, timeoutSecs uint64, tnet *netstack.Net) (int, tim
|
||||
func main() {
|
||||
// uncomment the lines below to run locally and see README.md for how to get the Wireguard config
|
||||
/* var _, err = ping(NetstackRequestGo{
|
||||
WgIp: "10.1.155.153",
|
||||
PrivateKey: "...",
|
||||
PublicKey: "...",
|
||||
Endpoint: "13.245.9.123:51822",
|
||||
MetadataEndpoint: "http://10.1.0.1:51830",
|
||||
Dns: "1.1.1.1",
|
||||
IpVersion: 4,
|
||||
//PingHosts: nil,
|
||||
//PingIps: nil,
|
||||
//NumPing: 0,
|
||||
//SendTimeoutSec: 0,
|
||||
//RecvTimeoutSec: 0,
|
||||
//DownloadTimeoutSec: 0,
|
||||
MetadataTimeoutSec: 5,
|
||||
//AwgArgs: "",
|
||||
})
|
||||
WgIp: "10.1.155.153",
|
||||
PrivateKey: "...",
|
||||
PublicKey: "...",
|
||||
Endpoint: "13.245.9.123:51822",
|
||||
MetadataEndpoint: "http://10.1.0.1:51830",
|
||||
Dns: "1.1.1.1",
|
||||
IpVersion: 4,
|
||||
//PingHosts: nil,
|
||||
//PingIps: nil,
|
||||
//NumPing: 0,
|
||||
//SendTimeoutSec: 0,
|
||||
//RecvTimeoutSec: 0,
|
||||
//DownloadTimeoutSec: 0,
|
||||
MetadataTimeoutSec: 5,
|
||||
//AwgArgs: "",
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -215,8 +215,8 @@ func TestPingFunction(t *testing.T) {
|
||||
// Create a request with valid IP but will fail due to network setup
|
||||
req := NetstackRequestGo{
|
||||
WgIp: "10.0.0.1",
|
||||
PrivateKey: "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
PublicKey: "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
PrivateKey: "test-key",
|
||||
PublicKey: "test-pub-key",
|
||||
Endpoint: "1.1.1.1:51820",
|
||||
Dns: "1.1.1.1",
|
||||
IpVersion: 4,
|
||||
@@ -275,11 +275,10 @@ func TestResultStructs(t *testing.T) {
|
||||
// TestConsecutiveFailureExit validates that the ping loop exits cleanly after consecutive failures
|
||||
func TestConsecutiveFailureExit(t *testing.T) {
|
||||
// Create a test request that will trigger consecutive failures
|
||||
// Using valid hex-encoded keys (32 bytes = 64 hex chars)
|
||||
req := NetstackRequestGo{
|
||||
WgIp: "10.0.0.1",
|
||||
PrivateKey: "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
PublicKey: "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
PrivateKey: "test-key",
|
||||
PublicKey: "test-pub-key",
|
||||
Endpoint: "1.1.1.1:51820",
|
||||
Dns: "1.1.1.1",
|
||||
IpVersion: 4,
|
||||
|
||||
@@ -765,7 +765,7 @@ async fn connect_exit(
|
||||
);
|
||||
// The IPR supports cancellation, but it's unused in the gateway probe
|
||||
let cancel_token = CancellationToken::new();
|
||||
let mut ipr_client = IprClientConnect::new(mixnet_client, cancel_token);
|
||||
let mut ipr_client = IprClientConnect::new(mixnet_client, cancel_token).await;
|
||||
|
||||
let maybe_ip_pair = ipr_client.connect(exit_router_address).await;
|
||||
let mixnet_client = ipr_client.into_mixnet_client();
|
||||
|
||||
@@ -145,8 +145,7 @@ impl NetstackRequestGo {
|
||||
private_key: req.private_key.clone(),
|
||||
public_key: req.public_key.clone(),
|
||||
endpoint: req.endpoint.clone(),
|
||||
// Skip metadata endpoint for IPv6 as it's an IPv4-only address (10.1.0.1)
|
||||
metadata_endpoint: String::new(),
|
||||
metadata_endpoint: req.metadata_endpoint.clone(),
|
||||
dns: req.v6_ping_config.dns.clone(),
|
||||
ip_version: 6,
|
||||
ping_hosts: req.v6_ping_config.ping_hosts.clone(),
|
||||
|
||||
@@ -7,7 +7,6 @@ use nym_config::defaults::setup_env;
|
||||
use nym_gateway_probe::nodes::NymApiDirectory;
|
||||
use nym_gateway_probe::{CredentialArgs, NetstackArgs, ProbeResult, TestedNode};
|
||||
use nym_sdk::mixnet::NodeIdentity;
|
||||
use std::path::Path;
|
||||
use std::{path::PathBuf, sync::OnceLock};
|
||||
use tracing::*;
|
||||
|
||||
@@ -70,8 +69,6 @@ struct CliArgs {
|
||||
credential_args: CredentialArgs,
|
||||
}
|
||||
|
||||
const DEFAULT_CONFIG_DIR: &str = "/tmp/nym-gateway-probe/config/";
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Commands {
|
||||
/// Run the probe locally
|
||||
@@ -80,8 +77,8 @@ enum Commands {
|
||||
#[arg(long)]
|
||||
mnemonic: String,
|
||||
|
||||
#[arg(long)]
|
||||
config_dir: Option<PathBuf>,
|
||||
#[arg(long, default_value = "/tmp/nym-gateway-probe/config/")]
|
||||
config_dir: PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -147,17 +144,8 @@ pub(crate) async fn run() -> anyhow::Result<ProbeResult> {
|
||||
mnemonic,
|
||||
config_dir,
|
||||
}) => {
|
||||
let config_dir = config_dir
|
||||
.clone()
|
||||
.unwrap_or_else(|| Path::new(DEFAULT_CONFIG_DIR).join(&network.network_name));
|
||||
|
||||
info!(
|
||||
"using the following directory for the probe config: {}",
|
||||
config_dir.display()
|
||||
);
|
||||
|
||||
Box::pin(trial.probe_run_locally(
|
||||
&config_dir,
|
||||
config_dir,
|
||||
mnemonic,
|
||||
directory,
|
||||
nyxd_url,
|
||||
|
||||
@@ -43,7 +43,7 @@ pub struct IprClientConnect {
|
||||
}
|
||||
|
||||
impl IprClientConnect {
|
||||
pub fn new(mixnet_client: MixnetClient, cancel_token: CancellationToken) -> Self {
|
||||
pub async fn new(mixnet_client: MixnetClient, cancel_token: CancellationToken) -> Self {
|
||||
Self {
|
||||
mixnet_client,
|
||||
connected: ConnectionState::Disconnected,
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "UPDATE ecash_ticketbook SET used_tickets = used_tickets + 1 WHERE id = $1",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "8c92a413a2853a2508c0e8a17ae8723c400930663c4c76e96dfdc7e8c98501ca"
|
||||
}
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n UPDATE ecash_ticketbook\n SET used_tickets = used_tickets + 1\n WHERE id = (\n SELECT id\n FROM ecash_ticketbook\n WHERE used_tickets < total_tickets\n AND expiration_date >= $1\n AND ticketbook_type = $2\n ORDER BY expiration_date ASC\n LIMIT 1\n FOR UPDATE\n )\n RETURNING *\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "serialization_revision",
|
||||
"type_info": "Int2"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "ticketbook_type",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "ticketbook_data",
|
||||
"type_info": "Bytea"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "expiration_date",
|
||||
"type_info": "Date"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "epoch_id",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "total_tickets",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 7,
|
||||
"name": "used_tickets",
|
||||
"type_info": "Int4"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Date",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "af5c78ef980e38d81f58f72f21c9cd410f83b8750196e0cf5fa5af23883e76df"
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node-status-api"
|
||||
version = "4.0.10"
|
||||
version = "4.0.7"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use anyhow::{Result, anyhow};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::{str::FromStr, time::Duration};
|
||||
|
||||
pub(crate) mod models;
|
||||
@@ -7,7 +8,9 @@ pub(crate) mod queries;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use sqlx::{ConnectOptions, PgPool, Postgres, migrate::Migrator, postgres::PgConnectOptions};
|
||||
use sqlx::{
|
||||
ConnectOptions, PgPool, Postgres, Transaction, migrate::Migrator, postgres::PgConnectOptions,
|
||||
};
|
||||
|
||||
static MIGRATOR: Migrator = sqlx::migrate!("./migrations_pg");
|
||||
|
||||
@@ -15,6 +18,35 @@ pub(crate) type DbPool = PgPool;
|
||||
|
||||
pub(crate) type DbConnection = sqlx::pool::PoolConnection<Postgres>;
|
||||
|
||||
pub(crate) struct StorageTransaction<'a> {
|
||||
inner: Transaction<'a, Postgres>,
|
||||
}
|
||||
|
||||
impl<'a> StorageTransaction<'a> {
|
||||
pub(crate) async fn commit(self) -> Result<(), sqlx::Error> {
|
||||
self.inner.commit().await
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Transaction<'a, Postgres>> for StorageTransaction<'a> {
|
||||
fn from(inner: Transaction<'a, Postgres>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for StorageTransaction<'a> {
|
||||
type Target = Transaction<'a, Postgres>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.inner
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> DerefMut for StorageTransaction<'a> {
|
||||
fn deref_mut(&mut self) -> &mut Transaction<'a, Postgres> {
|
||||
&mut self.inner
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Storage {
|
||||
pool: DbPool,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::db::Storage;
|
||||
use crate::db::{Storage, StorageTransaction};
|
||||
use crate::ticketbook_manager::storage::auxiliary_models::StoredIssuedTicketbook;
|
||||
use nym_credential_proxy_lib::storage::models::{
|
||||
RawCoinIndexSignatures, RawExpirationDateSignatures, RawVerificationKey,
|
||||
@@ -10,6 +10,10 @@ use time::Date;
|
||||
use tracing::error;
|
||||
|
||||
impl Storage {
|
||||
pub(crate) async fn begin_storage_tx(&self) -> Result<StorageTransaction<'_>, sqlx::Error> {
|
||||
self.pool.begin().await.map(Into::into)
|
||||
}
|
||||
|
||||
pub(crate) async fn available_tickets_of_type(&self, typ: &str) -> Result<i64, sqlx::Error> {
|
||||
let count = sqlx::query!(
|
||||
r#"
|
||||
@@ -213,38 +217,46 @@ impl Storage {
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> StorageTransaction<'a> {
|
||||
pub(crate) async fn get_next_unspent_ticketbook(
|
||||
&self,
|
||||
&mut self,
|
||||
ticket_type: String,
|
||||
deadline: Date,
|
||||
) -> Result<Option<StoredIssuedTicketbook>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
StoredIssuedTicketbook,
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
UPDATE ecash_ticketbook
|
||||
SET used_tickets = used_tickets + 1
|
||||
WHERE id = (
|
||||
SELECT id
|
||||
FROM ecash_ticketbook
|
||||
WHERE used_tickets < total_tickets
|
||||
AND expiration_date >= $1
|
||||
AND ticketbook_type = $2
|
||||
ORDER BY expiration_date ASC
|
||||
LIMIT 1
|
||||
FOR UPDATE
|
||||
)
|
||||
RETURNING *
|
||||
SELECT *
|
||||
FROM ecash_ticketbook
|
||||
WHERE used_tickets + 1 <= total_tickets
|
||||
AND expiration_date >= $1
|
||||
AND ticketbook_type = $2
|
||||
ORDER BY expiration_date ASC
|
||||
LIMIT 1
|
||||
"#,
|
||||
deadline,
|
||||
ticket_type
|
||||
)
|
||||
.fetch_optional(&self.pool)
|
||||
.bind(deadline)
|
||||
.bind(ticket_type)
|
||||
.fetch_optional(&mut ***self)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn increase_used_ticketbook_tickets(
|
||||
&mut self,
|
||||
ticketbook_id: i32,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"UPDATE ecash_ticketbook SET used_tickets = used_tickets + 1 WHERE id = $1",
|
||||
ticketbook_id
|
||||
)
|
||||
.execute(&mut ***self)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn set_distributed_ticketbook(
|
||||
&self,
|
||||
&mut self,
|
||||
testrun_id: i32,
|
||||
ticketbook_id: i32,
|
||||
assigned_index: i32,
|
||||
@@ -259,7 +271,7 @@ impl Storage {
|
||||
ticketbook_id,
|
||||
assigned_index
|
||||
)
|
||||
.execute(&self.pool)
|
||||
.execute(&mut ***self)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -99,7 +99,6 @@ pub struct DVpnGatewayPerformance {
|
||||
pub struct DVpnGateway {
|
||||
pub identity_key: String,
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
pub ip_packet_router: Option<IpPacketRouterDetails>,
|
||||
pub authenticator: Option<AuthenticatorDetails>,
|
||||
pub location: Location,
|
||||
@@ -327,7 +326,6 @@ impl DVpnGateway {
|
||||
Ok(Self {
|
||||
identity_key: gateway.gateway_identity_key,
|
||||
name: gateway.description.moniker,
|
||||
description: Some(gateway.description.details),
|
||||
ip_packet_router: self_described.ip_packet_router,
|
||||
authenticator: self_described.authenticator,
|
||||
location: Location {
|
||||
@@ -397,23 +395,20 @@ fn calculate_score(gateway: &Gateway, probe_outcome: &LastProbeResult) -> ScoreV
|
||||
.map(|p| {
|
||||
let ping_ips_performance = p.ping_ips_performance_v4 as f64;
|
||||
|
||||
let duration_sec =
|
||||
p.download_duration_milliseconds_v4
|
||||
.unwrap_or_else(|| p.download_duration_sec_v4 * 1000) as f64
|
||||
/ 1000f64;
|
||||
let duration = p.download_duration_sec_v4 as f64;
|
||||
|
||||
// get the file size downloaded in bytes and convert to MB, or default to 1MB
|
||||
let file_size_mb =
|
||||
p.downloaded_file_size_bytes_v4.unwrap_or(1048576) as f64 / 1024f64 / 1024f64;
|
||||
let speed_mbps = file_size_mb / duration_sec;
|
||||
let speed_mbps = file_size_mb / duration;
|
||||
|
||||
let file_download_score = if speed_mbps > 5.0 {
|
||||
let file_download_score = if speed_mbps > 100.0 {
|
||||
1.0
|
||||
} else if speed_mbps > 2.0 {
|
||||
} else if speed_mbps > 50.0 {
|
||||
0.75
|
||||
} else if speed_mbps > 1.0 {
|
||||
} else if speed_mbps > 20.0 {
|
||||
0.5
|
||||
} else if speed_mbps > 0.5 {
|
||||
} else if speed_mbps > 10.0 {
|
||||
0.25
|
||||
} else {
|
||||
0.1
|
||||
|
||||
@@ -57,7 +57,7 @@ async fn run(
|
||||
.clone()
|
||||
.expect("rust sdk mainnet default missing api_url");
|
||||
|
||||
let nym_api = nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])?
|
||||
let nym_api = nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])
|
||||
.no_hickory_dns()
|
||||
.with_timeout(nym_api_client_timeout)
|
||||
.build()?;
|
||||
|
||||
@@ -98,7 +98,7 @@ impl Monitor {
|
||||
.expect("rust sdk mainnet default missing api_url");
|
||||
|
||||
let nym_api =
|
||||
nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])?
|
||||
nym_http_api_client::ClientBuilder::new_with_urls(vec![default_api_url.into()])
|
||||
.no_hickory_dns()
|
||||
.with_timeout(self.nym_api_client_timeout)
|
||||
.build()?;
|
||||
|
||||
@@ -91,14 +91,15 @@ impl TicketbookManagerStorage {
|
||||
testrun_id: i32,
|
||||
) -> anyhow::Result<Option<RetrievedTicketbook>> {
|
||||
let deadline = ecash_today().ecash_date();
|
||||
let mut tx = self.storage.begin_storage_tx().await?;
|
||||
|
||||
// we don't want ticketbooks with expiration in the past
|
||||
// note: this query updates the spent tickets atomically
|
||||
let Some(raw) = self
|
||||
.storage
|
||||
let Some(raw) = tx
|
||||
.get_next_unspent_ticketbook(ticket_type.to_string(), deadline)
|
||||
.await?
|
||||
else {
|
||||
// make sure to finish our tx
|
||||
tx.commit().await?;
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
@@ -109,9 +110,10 @@ impl TicketbookManagerStorage {
|
||||
)
|
||||
.map_err(|err| anyhow!("failed to deserialise stored ticketbook: {err}"))?;
|
||||
|
||||
self.storage
|
||||
.set_distributed_ticketbook(testrun_id, raw.id, raw.used_tickets)
|
||||
tx.set_distributed_ticketbook(testrun_id, raw.id, raw.used_tickets)
|
||||
.await?;
|
||||
tx.increase_used_ticketbook_tickets(raw.id).await?;
|
||||
tx.commit().await?;
|
||||
|
||||
deserialised.update_spent_tickets(raw.used_tickets as u64);
|
||||
Ok(Some(RetrievedTicketbook {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
|
||||
[package]
|
||||
name = "nym-node"
|
||||
version = "1.19.0"
|
||||
version = "1.16.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::config::persistence::DEFAULT_RD_BLOOMFILTER_FLUSH_FILE_EXT;
|
||||
use crate::config::persistence::{
|
||||
DEFAULT_RD_BLOOMFILTER_FILE_EXT, DEFAULT_RD_BLOOMFILTER_FLUSH_FILE_EXT,
|
||||
};
|
||||
use crate::error::NymNodeError;
|
||||
use crate::node::replay_protection::bloomfilter::RotationFilter;
|
||||
use crate::node::replay_protection::helpers::parse_rotation_id_from_filename;
|
||||
@@ -22,6 +24,7 @@ use tracing::{debug, error, info, trace, warn};
|
||||
|
||||
// background task responsible for periodically flushing the bloomfilters to disk
|
||||
pub struct ReplayProtectionDiskFlush {
|
||||
bloomfilters_directory: PathBuf,
|
||||
disk_flushing_rate: Duration,
|
||||
|
||||
filters_manager: ReplayProtectionBloomfiltersManager,
|
||||
@@ -121,25 +124,8 @@ impl ReplayProtectionDiskFlush {
|
||||
None
|
||||
};
|
||||
|
||||
// if we have any other stored bloomfilters that are neither primary nor secondary,
|
||||
// remove them - they are an artifact from an old version that had a bug in purging code
|
||||
for (rotation_id, path) in filter_files {
|
||||
if rotation_id == primary_key_rotation_id {
|
||||
continue;
|
||||
}
|
||||
if let Some(secondary_key_rotation_id) = secondary_key_rotation_id
|
||||
&& secondary_key_rotation_id == rotation_id
|
||||
{
|
||||
continue;
|
||||
}
|
||||
info!(
|
||||
"stale bloomfilter for rotation {rotation_id} found at: {path:?}. it is going to get removed"
|
||||
);
|
||||
fs::remove_file(&path)
|
||||
.map_err(|source| NymNodeError::BloomfilterIoFailure { source, path })?;
|
||||
}
|
||||
|
||||
Ok(ReplayProtectionDiskFlush {
|
||||
bloomfilters_directory,
|
||||
disk_flushing_rate: config
|
||||
.mixnet
|
||||
.replay_protection
|
||||
@@ -156,12 +142,15 @@ impl ReplayProtectionDiskFlush {
|
||||
}
|
||||
|
||||
fn bloomfilter_filepath(&self, rotation_id: u32) -> PathBuf {
|
||||
self.filters_manager.bloomfilter_filepath(rotation_id)
|
||||
self.bloomfilters_directory
|
||||
.join(format!("rot-{rotation_id}"))
|
||||
.with_extension(DEFAULT_RD_BLOOMFILTER_FILE_EXT)
|
||||
}
|
||||
|
||||
fn current_bloomfilter_being_flushed_filepath(&self, rotation_id: u32) -> PathBuf {
|
||||
self.filters_manager
|
||||
.current_bloomfilter_being_flushed_filepath(rotation_id)
|
||||
self.bloomfilters_directory
|
||||
.join(format!("rot-{rotation_id}"))
|
||||
.with_extension(DEFAULT_RD_BLOOMFILTER_FLUSH_FILE_EXT)
|
||||
}
|
||||
|
||||
pub(crate) fn bloomfilters_manager(&self) -> ReplayProtectionBloomfiltersManager {
|
||||
@@ -224,7 +213,7 @@ impl ReplayProtectionDiskFlush {
|
||||
}
|
||||
|
||||
async fn flush_filters_to_disk(&self) -> Result<(), NymNodeError> {
|
||||
if let Some(parent) = self.filters_manager.bloomfilters_directory().parent() {
|
||||
if let Some(parent) = self.bloomfilters_directory.parent() {
|
||||
fs::create_dir_all(parent).map_err(|source| NymNodeError::BloomfilterIoFailure {
|
||||
source,
|
||||
path: parent.to_path_buf(),
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
use crate::error::NymNodeError;
|
||||
use bloomfilter::Bloom;
|
||||
use nym_sphinx_types::REPLAY_TAG_SIZE;
|
||||
use nym_validator_client::models::KeyRotationId;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
@@ -13,6 +12,7 @@ use std::path::Path;
|
||||
use std::sync::{Arc, Mutex, PoisonError, TryLockError};
|
||||
use time::OffsetDateTime;
|
||||
use tracing::{error, info, warn};
|
||||
|
||||
// auxiliary data associated with the bloomfilter to get some statistics from the time of its creation
|
||||
// this is needed in order to more accurately resize it upon reset
|
||||
|
||||
@@ -180,16 +180,15 @@ impl ReplayProtectionBloomfilters {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn purge_secondary(&self) -> Result<Option<KeyRotationId>, NymNodeError> {
|
||||
pub(crate) fn purge_secondary(&self) -> Result<(), NymNodeError> {
|
||||
let mut guard = self
|
||||
.inner
|
||||
.lock()
|
||||
.map_err(|_| NymNodeError::BloomfilterFailure {
|
||||
message: "mutex got poisoned",
|
||||
})?;
|
||||
|
||||
let id = guard.overlap.take().map(|f| f.metadata.rotation_id);
|
||||
Ok(id)
|
||||
guard.overlap = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn primary_metadata(
|
||||
|
||||
@@ -2,25 +2,18 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::config::persistence::{
|
||||
DEFAULT_RD_BLOOMFILTER_FILE_EXT, DEFAULT_RD_BLOOMFILTER_FLUSH_FILE_EXT,
|
||||
};
|
||||
use crate::error::NymNodeError;
|
||||
use crate::node::replay_protection::bloomfilter::{ReplayProtectionBloomfilters, RotationFilter};
|
||||
use crate::node::replay_protection::items_in_bloomfilter;
|
||||
use human_repr::HumanCount;
|
||||
use nym_node_metrics::NymNodeMetrics;
|
||||
use std::cmp::max;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
use time::OffsetDateTime;
|
||||
use tracing::info;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ReplayProtectionBloomfiltersManager {
|
||||
bloomfilters_directory: PathBuf,
|
||||
|
||||
target_fp_p: f64,
|
||||
minimum_bloomfilter_packets_per_second: usize,
|
||||
bloomfilter_size_multiplier: f64,
|
||||
@@ -33,7 +26,6 @@ impl ReplayProtectionBloomfiltersManager {
|
||||
pub(crate) fn new_disabled(metrics: NymNodeMetrics) -> Self {
|
||||
// the exact config values are irrelevant as the filters will never be recreated
|
||||
ReplayProtectionBloomfiltersManager {
|
||||
bloomfilters_directory: Default::default(),
|
||||
target_fp_p: 0.001,
|
||||
minimum_bloomfilter_packets_per_second: 1,
|
||||
bloomfilter_size_multiplier: 1.0,
|
||||
@@ -49,12 +41,6 @@ impl ReplayProtectionBloomfiltersManager {
|
||||
metrics: NymNodeMetrics,
|
||||
) -> Self {
|
||||
ReplayProtectionBloomfiltersManager {
|
||||
bloomfilters_directory: config
|
||||
.mixnet
|
||||
.replay_protection
|
||||
.storage_paths
|
||||
.current_bloomfilters_directory
|
||||
.clone(),
|
||||
target_fp_p: config.mixnet.replay_protection.debug.false_positive_rate,
|
||||
minimum_bloomfilter_packets_per_second: config
|
||||
.mixnet
|
||||
@@ -71,22 +57,6 @@ impl ReplayProtectionBloomfiltersManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn bloomfilters_directory(&self) -> &PathBuf {
|
||||
&self.bloomfilters_directory
|
||||
}
|
||||
|
||||
pub(crate) fn bloomfilter_filepath(&self, rotation_id: u32) -> PathBuf {
|
||||
self.bloomfilters_directory
|
||||
.join(format!("rot-{rotation_id}"))
|
||||
.with_extension(DEFAULT_RD_BLOOMFILTER_FILE_EXT)
|
||||
}
|
||||
|
||||
pub(crate) fn current_bloomfilter_being_flushed_filepath(&self, rotation_id: u32) -> PathBuf {
|
||||
self.bloomfilters_directory
|
||||
.join(format!("rot-{rotation_id}"))
|
||||
.with_extension(DEFAULT_RD_BLOOMFILTER_FLUSH_FILE_EXT)
|
||||
}
|
||||
|
||||
pub(crate) fn bloomfilters(&self) -> ReplayProtectionBloomfilters {
|
||||
self.filters.clone()
|
||||
}
|
||||
@@ -100,14 +70,7 @@ impl ReplayProtectionBloomfiltersManager {
|
||||
}
|
||||
|
||||
pub(crate) fn purge_secondary(&self) -> Result<(), NymNodeError> {
|
||||
// remove data in memory
|
||||
if let Some(secondary_id) = self.filters.purge_secondary()? {
|
||||
// remove data on disk (if applicable)
|
||||
let path = self.bloomfilter_filepath(secondary_id);
|
||||
fs::remove_file(&path)
|
||||
.map_err(|source| NymNodeError::BloomfilterIoFailure { source, path })?;
|
||||
}
|
||||
Ok(())
|
||||
self.filters.purge_secondary()
|
||||
}
|
||||
|
||||
pub(crate) fn promote_pre_announced(&self) -> Result<(), NymNodeError> {
|
||||
|
||||
@@ -12,12 +12,10 @@ license.workspace = true
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
futures.workspace = true
|
||||
thiserror.workspace = true
|
||||
tokio.workspace = true
|
||||
tokio-util.workspace = true
|
||||
tracing.workspace = true
|
||||
typed-builder.workspace = true
|
||||
url.workspace = true
|
||||
|
||||
nym-authenticator-client = { path = "../nym-authenticator-client" }
|
||||
|
||||
@@ -15,12 +15,10 @@ use nym_sdk::{
|
||||
use std::os::fd::RawFd;
|
||||
use std::{path::PathBuf, sync::Arc, time::Duration};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use typed_builder::TypedBuilder;
|
||||
|
||||
use crate::error::RegistrationClientError;
|
||||
|
||||
const VPN_AVERAGE_PACKET_DELAY: Duration = Duration::from_millis(15);
|
||||
const MIXNET_CLIENT_STARTUP_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NymNodeWithKeys {
|
||||
@@ -28,14 +26,11 @@ pub struct NymNodeWithKeys {
|
||||
pub keys: Arc<KeyPair>,
|
||||
}
|
||||
|
||||
#[derive(TypedBuilder)]
|
||||
pub struct BuilderConfig {
|
||||
pub entry_node: NymNodeWithKeys,
|
||||
pub exit_node: NymNodeWithKeys,
|
||||
pub data_path: Option<PathBuf>,
|
||||
pub mixnet_client_config: MixnetClientConfig,
|
||||
#[builder(default = MIXNET_CLIENT_STARTUP_TIMEOUT)]
|
||||
pub mixnet_client_startup_timeout: Duration,
|
||||
pub two_hops: bool,
|
||||
pub user_agent: UserAgent,
|
||||
pub custom_topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
@@ -211,17 +206,3 @@ fn log_mixnet_client_config(debug_config: &DebugConfig) {
|
||||
fn true_to_disabled(val: bool) -> &'static str {
|
||||
if val { "disabled" } else { "enabled" }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_mixnet_client_config_default_values() {
|
||||
let config = MixnetClientConfig::default();
|
||||
assert!(!config.disable_poisson_rate);
|
||||
assert!(!config.disable_background_cover_traffic);
|
||||
assert_eq!(config.min_mixnode_performance, None);
|
||||
assert_eq!(config.min_gateway_performance, None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use futures::channel::mpsc;
|
||||
use nym_bandwidth_controller::{BandwidthController, BandwidthTicketProvider};
|
||||
use nym_credential_storage::ephemeral_storage::EphemeralCredentialStorage;
|
||||
use nym_sdk::{
|
||||
NymNetworkDetails,
|
||||
mixnet::{EventSender, MixnetClient, MixnetClientBuilder},
|
||||
mixnet::{MixnetClient, MixnetClientBuilder},
|
||||
};
|
||||
use nym_validator_client::{
|
||||
QueryHttpRpcNyxdClient,
|
||||
nyxd::{Config as NyxdClientConfig, NyxdClient},
|
||||
};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{RegistrationClient, config::RegistrationClientConfig, error::RegistrationClientError};
|
||||
use config::BuilderConfig;
|
||||
|
||||
pub(crate) mod config;
|
||||
|
||||
pub(crate) const MIXNET_CLIENT_STARTUP_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
|
||||
pub struct RegistrationClientBuilder {
|
||||
pub config: BuilderConfig,
|
||||
}
|
||||
@@ -35,7 +37,6 @@ impl RegistrationClientBuilder {
|
||||
two_hops: self.config.two_hops,
|
||||
};
|
||||
let cancel_token = self.config.cancel_token.clone();
|
||||
let (event_tx, event_rx) = mpsc::unbounded();
|
||||
|
||||
let nyxd_client = get_nyxd_client(&self.config.network_env)?;
|
||||
|
||||
@@ -43,10 +44,9 @@ impl RegistrationClientBuilder {
|
||||
MixnetClient,
|
||||
Box<dyn BandwidthTicketProvider>,
|
||||
) = if let Some((mixnet_client_storage, credential_storage)) = storage {
|
||||
let builder = MixnetClientBuilder::new_with_storage(mixnet_client_storage)
|
||||
.event_tx(EventSender(event_tx));
|
||||
let builder = MixnetClientBuilder::new_with_storage(mixnet_client_storage);
|
||||
let mixnet_client = tokio::time::timeout(
|
||||
self.config.mixnet_client_startup_timeout,
|
||||
MIXNET_CLIENT_STARTUP_TIMEOUT,
|
||||
self.config.build_and_connect_mixnet_client(builder),
|
||||
)
|
||||
.await??;
|
||||
@@ -54,9 +54,9 @@ impl RegistrationClientBuilder {
|
||||
Box::new(BandwidthController::new(credential_storage, nyxd_client));
|
||||
(mixnet_client, bandwidth_controller)
|
||||
} else {
|
||||
let builder = MixnetClientBuilder::new_ephemeral().event_tx(EventSender(event_tx));
|
||||
let builder = MixnetClientBuilder::new_ephemeral();
|
||||
let mixnet_client = tokio::time::timeout(
|
||||
self.config.mixnet_client_startup_timeout,
|
||||
MIXNET_CLIENT_STARTUP_TIMEOUT,
|
||||
self.config.build_and_connect_mixnet_client(builder),
|
||||
)
|
||||
.await??;
|
||||
@@ -74,7 +74,6 @@ impl RegistrationClientBuilder {
|
||||
cancel_token,
|
||||
mixnet_client_address,
|
||||
bandwidth_controller,
|
||||
event_rx,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,7 @@ use nym_bandwidth_controller::BandwidthTicketProvider;
|
||||
use nym_credentials_interface::TicketType;
|
||||
use nym_ip_packet_client::IprClientConnect;
|
||||
use nym_registration_common::AssignedAddresses;
|
||||
use nym_sdk::mixnet::{EventReceiver, MixnetClient, Recipient};
|
||||
use tracing::debug;
|
||||
use nym_sdk::mixnet::{MixnetClient, Recipient};
|
||||
|
||||
use crate::config::RegistrationClientConfig;
|
||||
|
||||
@@ -32,52 +31,25 @@ pub struct RegistrationClient {
|
||||
mixnet_client_address: Recipient,
|
||||
bandwidth_controller: Box<dyn BandwidthTicketProvider>,
|
||||
cancel_token: CancellationToken,
|
||||
event_rx: EventReceiver,
|
||||
}
|
||||
|
||||
// Bundle of an actual error and the underlying mixnet client so it can be shutdown correctly if needed
|
||||
struct RegistrationError {
|
||||
mixnet_client: Option<MixnetClient>,
|
||||
source: crate::RegistrationClientError,
|
||||
}
|
||||
|
||||
impl RegistrationClient {
|
||||
async fn register_mix_exit(self) -> Result<RegistrationResult, RegistrationError> {
|
||||
async fn register_mix_exit(self) -> Result<RegistrationResult, RegistrationClientError> {
|
||||
let entry_mixnet_gateway_ip = self.config.entry.node.ip_address;
|
||||
|
||||
let exit_mixnet_gateway_ip = self.config.exit.node.ip_address;
|
||||
|
||||
let Some(ipr_address) = self.config.exit.node.ipr_address else {
|
||||
return Err(RegistrationError {
|
||||
mixnet_client: Some(self.mixnet_client),
|
||||
source: RegistrationClientError::NoIpPacketRouterAddress {
|
||||
node_id: self.config.exit.node.identity.to_base58_string(),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
let ipr_address = self.config.exit.node.ipr_address.ok_or(
|
||||
RegistrationClientError::NoIpPacketRouterAddress {
|
||||
node_id: self.config.exit.node.identity.to_base58_string(),
|
||||
},
|
||||
)?;
|
||||
let mut ipr_client =
|
||||
IprClientConnect::new(self.mixnet_client, self.cancel_token.child_token());
|
||||
|
||||
let interface_addresses = match self
|
||||
.cancel_token
|
||||
.run_until_cancelled(ipr_client.connect(ipr_address))
|
||||
IprClientConnect::new(self.mixnet_client, self.cancel_token.clone()).await;
|
||||
let interface_addresses = ipr_client
|
||||
.connect(ipr_address)
|
||||
.await
|
||||
{
|
||||
Some(Ok(addr)) => addr,
|
||||
Some(Err(e)) => {
|
||||
return Err(RegistrationError {
|
||||
mixnet_client: Some(ipr_client.into_mixnet_client()),
|
||||
source: RegistrationClientError::ConnectToIpPacketRouter(e),
|
||||
});
|
||||
}
|
||||
None => {
|
||||
return Err(RegistrationError {
|
||||
mixnet_client: Some(ipr_client.into_mixnet_client()),
|
||||
source: RegistrationClientError::Cancelled,
|
||||
});
|
||||
}
|
||||
};
|
||||
.map_err(RegistrationClientError::ConnectToIpPacketRouter)?;
|
||||
|
||||
Ok(RegistrationResult::Mixnet(Box::new(
|
||||
MixnetRegistrationResult {
|
||||
@@ -89,29 +61,22 @@ impl RegistrationClient {
|
||||
entry_mixnet_gateway_ip,
|
||||
exit_mixnet_gateway_ip,
|
||||
},
|
||||
event_rx: self.event_rx,
|
||||
},
|
||||
)))
|
||||
}
|
||||
|
||||
async fn register_wg(self) -> Result<RegistrationResult, RegistrationError> {
|
||||
let Some(entry_auth_address) = self.config.entry.node.authenticator_address else {
|
||||
return Err(RegistrationError {
|
||||
mixnet_client: Some(self.mixnet_client),
|
||||
source: RegistrationClientError::AuthenticationNotPossible {
|
||||
node_id: self.config.entry.node.identity.to_base58_string(),
|
||||
},
|
||||
});
|
||||
};
|
||||
async fn register_wg(self) -> Result<RegistrationResult, RegistrationClientError> {
|
||||
let entry_auth_address = self.config.entry.node.authenticator_address.ok_or(
|
||||
RegistrationClientError::AuthenticationNotPossible {
|
||||
node_id: self.config.entry.node.identity.to_base58_string(),
|
||||
},
|
||||
)?;
|
||||
|
||||
let Some(exit_auth_address) = self.config.exit.node.authenticator_address else {
|
||||
return Err(RegistrationError {
|
||||
mixnet_client: Some(self.mixnet_client),
|
||||
source: RegistrationClientError::AuthenticationNotPossible {
|
||||
node_id: self.config.exit.node.identity.to_base58_string(),
|
||||
},
|
||||
});
|
||||
};
|
||||
let exit_auth_address = self.config.exit.node.authenticator_address.ok_or(
|
||||
RegistrationClientError::AuthenticationNotPossible {
|
||||
node_id: self.config.exit.node.identity.to_base58_string(),
|
||||
},
|
||||
)?;
|
||||
|
||||
let entry_version = self.config.entry.node.version;
|
||||
tracing::debug!("Entry gateway version: {entry_version}");
|
||||
@@ -120,10 +85,8 @@ impl RegistrationClient {
|
||||
|
||||
// Start the auth client mixnet listener, which will listen for incoming messages from the
|
||||
// mixnet and rebroadcast them to the auth clients.
|
||||
// From this point on, we don't need to care about the mixnet client anymore
|
||||
let mixnet_listener =
|
||||
AuthClientMixnetListener::new(self.mixnet_client, self.cancel_token.child_token())
|
||||
.start();
|
||||
AuthClientMixnetListener::new(self.mixnet_client, self.cancel_token.clone()).start();
|
||||
|
||||
let mut entry_auth_client = AuthenticatorClient::new(
|
||||
mixnet_listener.subscribe(),
|
||||
@@ -150,33 +113,24 @@ impl RegistrationClient {
|
||||
let exit_fut = exit_auth_client
|
||||
.register_wireguard(&*self.bandwidth_controller, TicketType::V1WireguardExit);
|
||||
|
||||
let (entry, exit) = Box::pin(
|
||||
self.cancel_token
|
||||
.run_until_cancelled(async { tokio::join!(entry_fut, exit_fut) }),
|
||||
)
|
||||
.await
|
||||
.ok_or(RegistrationError {
|
||||
mixnet_client: None,
|
||||
source: RegistrationClientError::Cancelled,
|
||||
})?;
|
||||
let (entry, exit) = Box::pin(async { tokio::join!(entry_fut, exit_fut) }).await;
|
||||
|
||||
let entry = entry.map_err(|source| RegistrationError {
|
||||
mixnet_client: None,
|
||||
source: RegistrationClientError::EntryGatewayRegisterWireguard {
|
||||
gateway_id: self.config.entry.node.identity.to_base58_string(),
|
||||
authenticator_address: Box::new(entry_auth_address),
|
||||
source: Box::new(source),
|
||||
},
|
||||
})?;
|
||||
|
||||
let exit = exit.map_err(|source| RegistrationError {
|
||||
mixnet_client: None,
|
||||
source: RegistrationClientError::EntryGatewayRegisterWireguard {
|
||||
gateway_id: self.config.exit.node.identity.to_base58_string(),
|
||||
authenticator_address: Box::new(exit_auth_address),
|
||||
source: Box::new(source),
|
||||
},
|
||||
})?;
|
||||
let entry =
|
||||
entry.map_err(
|
||||
|source| RegistrationClientError::EntryGatewayRegisterWireguard {
|
||||
gateway_id: self.config.entry.node.identity.to_base58_string(),
|
||||
authenticator_address: Box::new(entry_auth_address),
|
||||
source: Box::new(source),
|
||||
},
|
||||
)?;
|
||||
let exit =
|
||||
exit.map_err(
|
||||
|source| RegistrationClientError::ExitGatewayRegisterWireguard {
|
||||
gateway_id: self.config.exit.node.identity.to_base58_string(),
|
||||
authenticator_address: Box::new(exit_auth_address),
|
||||
source: Box::new(source),
|
||||
},
|
||||
)?;
|
||||
|
||||
Ok(RegistrationResult::Wireguard(Box::new(
|
||||
WireguardRegistrationResult {
|
||||
@@ -191,23 +145,16 @@ impl RegistrationClient {
|
||||
}
|
||||
|
||||
pub async fn register(self) -> Result<RegistrationResult, RegistrationClientError> {
|
||||
let registration_result = if self.config.two_hops {
|
||||
self.register_wg().await
|
||||
} else {
|
||||
self.register_mix_exit().await
|
||||
};
|
||||
|
||||
// If we failed to register, and we were the owner of the mixnet client, shut it down
|
||||
match registration_result {
|
||||
Ok(result) => Ok(result),
|
||||
Err(error) => {
|
||||
debug!("Registration failed");
|
||||
if let Some(mixnet_client) = error.mixnet_client {
|
||||
debug!("Shutting down mixnet client");
|
||||
mixnet_client.disconnect().await;
|
||||
self.cancel_token
|
||||
.clone()
|
||||
.run_until_cancelled(async {
|
||||
if self.config.two_hops {
|
||||
self.register_wg().await
|
||||
} else {
|
||||
self.register_mix_exit().await
|
||||
}
|
||||
Err(error.source)
|
||||
}
|
||||
}
|
||||
})
|
||||
.await
|
||||
.ok_or(RegistrationClientError::Cancelled)?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use nym_authenticator_client::{AuthClientMixnetListenerHandle, AuthenticatorClient};
|
||||
use nym_bandwidth_controller::BandwidthTicketProvider;
|
||||
use nym_registration_common::{AssignedAddresses, GatewayData};
|
||||
use nym_sdk::mixnet::{EventReceiver, MixnetClient};
|
||||
use nym_sdk::mixnet::MixnetClient;
|
||||
|
||||
pub enum RegistrationResult {
|
||||
Mixnet(Box<MixnetRegistrationResult>),
|
||||
@@ -14,7 +14,6 @@ pub enum RegistrationResult {
|
||||
pub struct MixnetRegistrationResult {
|
||||
pub assigned_addresses: AssignedAddresses,
|
||||
pub mixnet_client: MixnetClient,
|
||||
pub event_rx: EventReceiver,
|
||||
}
|
||||
|
||||
pub struct WireguardRegistrationResult {
|
||||
|
||||
Generated
+24
-3
@@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "NymWallet"
|
||||
@@ -773,7 +773,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "bls12_381"
|
||||
version = "0.8.0"
|
||||
source = "git+https://github.com/jstuczyn/bls12_381?branch=temp%2Fexperimental-serdect-updated#9bf520059cb28323fc51469cae86868ef4fa6fbd"
|
||||
source = "git+https://github.com/jstuczyn/bls12_381?branch=temp/experimental-serdect-updated#9bf520059cb28323fc51469cae86868ef4fa6fbd"
|
||||
dependencies = [
|
||||
"digest 0.10.7",
|
||||
"ff",
|
||||
@@ -1723,6 +1723,15 @@ dependencies = [
|
||||
"dirs-sys 0.3.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "5.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
|
||||
dependencies = [
|
||||
"dirs-sys 0.4.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
@@ -1743,6 +1752,18 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users 0.4.6",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.5.0"
|
||||
@@ -4079,7 +4100,7 @@ dependencies = [
|
||||
name = "nym-config"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dirs 6.0.0",
|
||||
"dirs 5.0.1",
|
||||
"handlebars",
|
||||
"log",
|
||||
"nym-network-defaults",
|
||||
|
||||
@@ -1,540 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Nym QUIC Bridge Deployment Helper Script
|
||||
# Uses bridge-cfg to generate a correct bridges.toml and keys
|
||||
# installs/maintains the nym-bridge service, and provides network helpers
|
||||
# read about nym-bridges: https://github.com/nymtech/nym-bridges
|
||||
# read about bridge-cfg: https://github.com/nymtech/nym-bridges/tree/main/bridge-cfg
|
||||
# RUN AS ROOT
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Colors
|
||||
RED="\033[0;31m"
|
||||
GREEN="\033[0;32m"
|
||||
YELLOW="\033[0;33m"
|
||||
CYAN="\033[0;36m"
|
||||
BOLD="\033[1m"
|
||||
RESET="\033[0m"
|
||||
|
||||
# Logging
|
||||
LOG_FILE="/var/log/nym-bridge-helper.log"
|
||||
mkdir -p "$(dirname "$LOG_FILE")"
|
||||
touch "$LOG_FILE"
|
||||
chmod 640 "$LOG_FILE"
|
||||
echo -e "${CYAN}Logs are being saved locally to:${RESET} $LOG_FILE"
|
||||
echo -e "${CYAN}These logs never leave your machine.${RESET}"
|
||||
echo "" | tee -a "$LOG_FILE"
|
||||
|
||||
# safe logger
|
||||
log() {
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
|
||||
}
|
||||
|
||||
# simple redirection that keeps function scope intact
|
||||
add_log_redirection() {
|
||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||
}
|
||||
add_log_redirection
|
||||
|
||||
# Constants / Paths
|
||||
REQUIRED_CMDS=(ip jq curl openssl wg dpkg)
|
||||
BRIDGE_BIN="/usr/local/bin/nym-bridge"
|
||||
BRIDGE_CFG_BIN="/usr/local/bin/bridge-cfg"
|
||||
|
||||
NYM_ETC_DIR="/etc/nym"
|
||||
NYM_ETC_KEYS_DIR="$NYM_ETC_DIR/keys"
|
||||
NYM_ETC_BRIDGES="$NYM_ETC_DIR/bridges.toml"
|
||||
NYM_ETC_CLIENT_PARAMS_DEFAULT="$NYM_ETC_DIR/client_bridge_params.json"
|
||||
SERVICE_FILE="/etc/systemd/system/nym-bridge.service"
|
||||
|
||||
NET_DEV="$(ip route show default 2>/dev/null | awk '/default/ {print $5}' || true)"
|
||||
WG_IFACE="nymwg"
|
||||
|
||||
# Root check
|
||||
if [[ "$(id -u)" -ne 0 ]]; then
|
||||
echo -e "\n${RED}This script must be run as root.${RESET}\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# UI helpers
|
||||
hr() { echo -e "${YELLOW}----------------------------------------${RESET}"; }
|
||||
title() { echo -e "\n${YELLOW}==========================================${RESET}\n${YELLOW} $1${RESET}\n${YELLOW}==========================================${RESET}\n"; }
|
||||
ok() { echo -e "${GREEN}$1${RESET}"; }
|
||||
warn() { echo -e "${YELLOW}$1${RESET}"; }
|
||||
err() { echo -e "${RED}$1${RESET}"; }
|
||||
info() { echo -e "${CYAN}$1${RESET}"; }
|
||||
press_enter() { read -r -p "$1"; }
|
||||
|
||||
# Helper: detect dpkg dependency failure for libc6>=2.34
|
||||
deb_depends_libc_too_old() {
|
||||
# Grep dpkg -i output in log or run a dry call to dpkg -i with --unpack to observe code
|
||||
# Simpler heuristic: check installed libc6 version
|
||||
local v
|
||||
v="$(dpkg-query -W -f='${Version}\n' libc6 2>/dev/null || true)"
|
||||
# If no libc6, say "too old" to trigger source build
|
||||
if [[ -z "$v" ]]; then return 0; fi
|
||||
# Compare minimalistically: 2.34 vs current (2.31 typical on Debian 11)
|
||||
dpkg --compare-versions "$v" ge "2.34" && return 1 || return 0
|
||||
}
|
||||
|
||||
# Helper: ensure rust toolchain (for local build fallback)
|
||||
ensure_rustup() {
|
||||
if ! command -v cargo >/dev/null 2>&1; then
|
||||
info "Installing Rust toolchain (rustup)..."
|
||||
apt-get update -y
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates curl build-essential pkg-config libssl-dev git
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
else
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
fi
|
||||
}
|
||||
|
||||
# Helper: clone and build from source at latest release tag
|
||||
build_from_source_latest() {
|
||||
local repo_url="https://github.com/nymtech/nym-bridges.git"
|
||||
local workdir="/tmp/nym-bridges"
|
||||
local tag
|
||||
info "Determining latest release tag from GitHub..."
|
||||
tag="$(curl -fsSL https://api.github.com/repos/nymtech/nym-bridges/releases/latest | jq -r .tag_name 2>/dev/null || true)"
|
||||
if [[ -z "$tag" || "$tag" == "null" ]]; then
|
||||
warn "Could not detect tag automatically. Falling back to 'main'."
|
||||
tag="main"
|
||||
fi
|
||||
|
||||
info "Cloning $repo_url (tag/branch: $tag) into $workdir ..."
|
||||
rm -rf "$workdir"
|
||||
git clone --depth 1 --branch "$tag" "$repo_url" "$workdir"
|
||||
(cd "$workdir" && cargo fetch)
|
||||
|
||||
info "Building from source (release)..."
|
||||
(
|
||||
cd "$workdir"
|
||||
cargo build --release -p nym-bridge
|
||||
cargo build --release -p bridge-cfg
|
||||
)
|
||||
|
||||
# After build, binaries are typically in workspace target dir:
|
||||
# /tmp/nym-bridges/target/release/nym-bridge
|
||||
# /tmp/nym-bridges/target/release/bridge-cfg
|
||||
}
|
||||
|
||||
# Helper: robustly locate and install a built binary from /tmp/nym-bridges
|
||||
install_built_binary() {
|
||||
local name="$1" # e.g., bridge-cfg or nym-bridge
|
||||
local preferred="/tmp/nym-bridges/target/release/$name"
|
||||
|
||||
# Prefer the common workspace path first:
|
||||
if [[ -x "$preferred" ]]; then
|
||||
install -m 0755 "$preferred" "/usr/local/bin/$name"
|
||||
ok "Installed $name from $preferred to /usr/local/bin/$name"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Try expected crate subpaths:
|
||||
local alt1="/tmp/nym-bridges/$name/target/release/$name"
|
||||
if [[ -x "$alt1" ]]; then
|
||||
install -m 0755 "$alt1" "/usr/local/bin/$name"
|
||||
ok "Installed $name from $alt1 to /usr/local/bin/$name"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Broader search within 8 levels:
|
||||
local found
|
||||
found="$(find /tmp/nym-bridges -maxdepth 8 -type f -name "$name" -perm -111 2>/dev/null | head -n1 || true)"
|
||||
if [[ -n "$found" ]]; then
|
||||
install -m 0755 "$found" "/usr/local/bin/$name"
|
||||
ok "Installed $name from $found to /usr/local/bin/$name"
|
||||
return 0
|
||||
fi
|
||||
|
||||
err "Built $name not found under /tmp/nym-bridges after build."
|
||||
return 1
|
||||
}
|
||||
|
||||
# Prerequisites
|
||||
verify_bridge_prerequisites() {
|
||||
title "Verifying Bridge Prerequisites"
|
||||
local all_good=true
|
||||
|
||||
for c in "${REQUIRED_CMDS[@]}"; do
|
||||
if command -v "$c" >/dev/null 2>&1; then ok "✓ $c installed"; else err "$c missing"; all_good=false; fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
if ! dpkg -s iptables-persistent >/dev/null 2>&1; then
|
||||
warn "iptables-persistent not installed"
|
||||
info "Installing iptables-persistent..."
|
||||
apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y iptables-persistent
|
||||
else
|
||||
ok "✓ iptables-persistent installed"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
local v4=$(cat /proc/sys/net/ipv4/ip_forward 2>/dev/null || echo 0)
|
||||
local v6=$(cat /proc/sys/net/ipv6/conf/all/forwarding 2>/dev/null || echo 0)
|
||||
[[ "$v4" == "1" ]] && ok "✓ IPv4 forwarding enabled" || warn "IPv4 forwarding disabled"
|
||||
[[ "$v6" == "1" ]] && ok "✓ IPv6 forwarding enabled" || warn "IPv6 forwarding disabled"
|
||||
echo ""
|
||||
|
||||
[[ "$all_good" == true ]] && ok "All prerequisites satisfied!" || warn "Some prerequisites missing."
|
||||
}
|
||||
|
||||
adjust_ip_forwarding() {
|
||||
title "Adjusting IP Forwarding"
|
||||
sed -i '/^net\.ipv4\.ip_forward=/d' /etc/sysctl.conf
|
||||
sed -i '/^net\.ipv6\.conf\.all\.forwarding=/d' /etc/sysctl.conf
|
||||
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
|
||||
echo "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.conf
|
||||
sysctl -p /etc/sysctl.conf
|
||||
ok "IPv4/IPv6 forwarding enabled."
|
||||
}
|
||||
|
||||
# Install nym-bridge
|
||||
install_bridge_binary() {
|
||||
title "Installing nym-bridge Binary"
|
||||
|
||||
info "Fetching latest nym-bridge .deb from GitHub..."
|
||||
local deb_url
|
||||
deb_url="$(curl -fsSL https://api.github.com/repos/nymtech/nym-bridges/releases/latest \
|
||||
| grep -Eo 'https://[^"]*/nym-bridge_[0-9.]+-1_amd64.deb' | head -n1 || true)"
|
||||
|
||||
if [[ -z "$deb_url" ]]; then
|
||||
warn "Falling back to known version (v0.1.2)"
|
||||
deb_url="https://github.com/nymtech/nym-bridges/releases/download/bridge-binaries-v0.1.2/nym-bridge_0.1.2-1_amd64.deb"
|
||||
fi
|
||||
|
||||
info "Downloading from: $deb_url"
|
||||
curl -fL -o /tmp/nym-bridge.deb "$deb_url" || true
|
||||
|
||||
if [[ -s /tmp/nym-bridge.deb ]]; then
|
||||
set +e
|
||||
dpkg -i /tmp/nym-bridge.deb
|
||||
local dpkg_rc=$?
|
||||
set -e
|
||||
if [[ $dpkg_rc -ne 0 ]]; then
|
||||
warn "dpkg reported errors; checking for libc6>=2.34 requirement..."
|
||||
if deb_depends_libc_too_old; then
|
||||
warn "System libc6 appears older than 2.34. Building nym-bridge from source."
|
||||
ensure_rustup
|
||||
build_from_source_latest
|
||||
install_built_binary "nym-bridge"
|
||||
else
|
||||
err "Failed to install nym-bridge .deb for non-libc reason; attempting source build."
|
||||
ensure_rustup
|
||||
build_from_source_latest
|
||||
install_built_binary "nym-bridge"
|
||||
fi
|
||||
else
|
||||
ok "nym-bridge installed via .deb."
|
||||
fi
|
||||
else
|
||||
warn "Download failed or empty. Building nym-bridge from source."
|
||||
ensure_rustup
|
||||
build_from_source_latest
|
||||
install_built_binary "nym-bridge"
|
||||
fi
|
||||
}
|
||||
|
||||
# Install bridge-cfg
|
||||
install_bridge_cfg_tool() {
|
||||
title "Installing bridge-cfg Tool"
|
||||
|
||||
info "Attempting to fetch latest bridge-cfg from GitHub..."
|
||||
local cfg_url
|
||||
cfg_url="$(curl -fsSL https://api.github.com/repos/nymtech/nym-bridges/releases/latest \
|
||||
| grep -Eo 'https://[^"]*/bridge-cfg' | head -n1 || true)"
|
||||
|
||||
if [[ -z "$cfg_url" ]]; then
|
||||
warn "Falling back to v0.1.2"
|
||||
cfg_url="https://github.com/nymtech/nym-bridges/releases/download/bridge-binaries-v0.1.2/bridge-cfg"
|
||||
fi
|
||||
|
||||
info "Downloading: $cfg_url"
|
||||
if curl -fL -o "$BRIDGE_CFG_BIN" "$cfg_url"; then
|
||||
chmod +x "$BRIDGE_CFG_BIN"
|
||||
if "$BRIDGE_CFG_BIN" --help >/dev/null 2>&1; then
|
||||
ok "bridge-cfg installed at $BRIDGE_CFG_BIN"
|
||||
return 0
|
||||
else
|
||||
warn "Prebuilt bridge-cfg is incompatible (likely GLIBC too old). Building locally..."
|
||||
fi
|
||||
else
|
||||
warn "Failed to download bridge-cfg; building locally..."
|
||||
fi
|
||||
|
||||
# Build from source and install robustly
|
||||
ensure_rustup
|
||||
build_from_source_latest
|
||||
install_built_binary "bridge-cfg"
|
||||
}
|
||||
|
||||
# Generate config via bridge-cfg (with backup)
|
||||
run_bridge_cfg_generate() {
|
||||
title "Generating Bridge Configuration with bridge-cfg"
|
||||
|
||||
# Detect a likely nym-node configuration path
|
||||
local HOME_DIR="${HOME:-/root}"
|
||||
local NODE_CFG
|
||||
NODE_CFG="$(find "$HOME_DIR/.nym/nym-nodes" -type f -name config.toml 2>/dev/null | head -n1 || true)"
|
||||
|
||||
if [[ -z "$NODE_CFG" ]]; then
|
||||
NODE_CFG="$HOME_DIR/.nym/nym-nodes/default-nym-node/config/config.toml"
|
||||
fi
|
||||
|
||||
echo -n "Path to your nym-node config.toml [default: $NODE_CFG]: "
|
||||
read -r input
|
||||
[[ -n "$input" ]] && NODE_CFG="$input"
|
||||
|
||||
if [[ ! -f "$NODE_CFG" ]]; then
|
||||
err "nym-node config not found: $NODE_CFG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Backup before modification
|
||||
local NODE_ID
|
||||
NODE_ID="$(basename "$(dirname "$(dirname "$NODE_CFG")")")"
|
||||
local BACKUP_DIR="$HOME/.nym/backup/$NODE_ID/config"
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
local TS
|
||||
TS="$(date +%Y%m%d_%H%M%S)"
|
||||
local BACKUP_FILE="$BACKUP_DIR/config.toml.bak$TS"
|
||||
cp "$NODE_CFG" "$BACKUP_FILE"
|
||||
ok "Backup created: $BACKUP_FILE"
|
||||
|
||||
# Ensure directories exist before running bridge-cfg to prevent "os error 2"
|
||||
mkdir -p "$NYM_ETC_DIR" "$NYM_ETC_KEYS_DIR"
|
||||
mkdir -p "$(dirname "$NYM_ETC_CLIENT_PARAMS_DEFAULT")"
|
||||
chmod 700 "$NYM_ETC_DIR" "$NYM_ETC_KEYS_DIR"
|
||||
touch "$NYM_ETC_CLIENT_PARAMS_DEFAULT" || true
|
||||
|
||||
info "Running: bridge-cfg --gen -n \"$NODE_CFG\" -d \"$NYM_ETC_DIR\" -o \"$NYM_ETC_BRIDGES\""
|
||||
set +e
|
||||
"$BRIDGE_CFG_BIN" --gen -n "$NODE_CFG" -d "$NYM_ETC_DIR" -o "$NYM_ETC_BRIDGES"
|
||||
|
||||
|
||||
local rc=$?
|
||||
set -e
|
||||
if [[ $rc -ne 0 ]]; then
|
||||
err "bridge-cfg failed to generate config. Aborting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
chmod 600 "$NYM_ETC_BRIDGES"
|
||||
mkdir -p "$NYM_ETC_KEYS_DIR" && chmod 700 "$NYM_ETC_KEYS_DIR"
|
||||
chmod 600 "$NYM_ETC_KEYS_DIR"/* 2>/dev/null || true
|
||||
|
||||
ok "bridge-cfg completed. bridges.toml generated at $NYM_ETC_BRIDGES"
|
||||
echo ""
|
||||
hr
|
||||
head -n 50 "$NYM_ETC_BRIDGES"
|
||||
hr
|
||||
|
||||
export LAST_BACKUP_FILE="$BACKUP_FILE"
|
||||
}
|
||||
|
||||
# Systemd service
|
||||
create_bridge_service() {
|
||||
title "Creating nym-bridge systemd Service"
|
||||
|
||||
# Respect a service provided by .deb postinst if present
|
||||
if systemctl list-unit-files | grep -q '^nym-bridge\.service'; then
|
||||
warn "Detected existing nym-bridge service (likely from .deb). Not overwriting."
|
||||
systemctl daemon-reload || true
|
||||
systemctl enable nym-bridge >/dev/null || true
|
||||
systemctl restart nym-bridge || true
|
||||
ok "nym-bridge service managed by package; restarted."
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ ! -x "$BRIDGE_BIN" ]]; then err "Missing $BRIDGE_BIN"; exit 1; fi
|
||||
if [[ ! -f "$NYM_ETC_BRIDGES" ]]; then err "Missing $NYM_ETC_BRIDGES"; exit 1; fi
|
||||
|
||||
mkdir -p /etc/systemd/system
|
||||
|
||||
cat > "$SERVICE_FILE" <<EOF
|
||||
[Unit]
|
||||
Description=Nym QUIC Bridge
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
ExecStart=$BRIDGE_BIN --config $NYM_ETC_BRIDGES
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable nym-bridge >/dev/null || true
|
||||
systemctl restart nym-bridge || true
|
||||
ok "nym-bridge service deployed."
|
||||
}
|
||||
|
||||
# IPTABLES & helpers
|
||||
apply_bridge_iptables_rules() {
|
||||
title "Applying iptables rules"
|
||||
iptables -I INPUT -i "$WG_IFACE" -j ACCEPT || true
|
||||
ip6tables -I INPUT -i "$WG_IFACE" -j ACCEPT || true
|
||||
iptables -t nat -A POSTROUTING -o "$NET_DEV" -j MASQUERADE || true
|
||||
ip6tables -t nat -A POSTROUTING -o "$NET_DEV" -j MASQUERADE || true
|
||||
iptables-save > /etc/iptables/rules.v4
|
||||
ip6tables-save > /etc/iptables/rules.v6
|
||||
ok "iptables rules applied."
|
||||
}
|
||||
|
||||
configure_dns_and_icmp() {
|
||||
title "Allow ICMP and DNS"
|
||||
iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT || true
|
||||
ip6tables -A INPUT -p ipv6-icmp -j ACCEPT || true
|
||||
ok "ICMP and DNS rules applied."
|
||||
}
|
||||
|
||||
# Full interactive setup (safe exit + backup notice)
|
||||
full_bridge_setup() {
|
||||
title "Nym QUIC Bridge - Full Setup"
|
||||
|
||||
echo -e "This will guide you through complete bridge setup.\n"
|
||||
|
||||
for fn in verify_bridge_prerequisites install_bridge_binary install_bridge_cfg_tool \
|
||||
run_bridge_cfg_generate create_bridge_service adjust_ip_forwarding \
|
||||
apply_bridge_iptables_rules configure_dns_and_icmp; do
|
||||
if ! declare -f "$fn" >/dev/null; then
|
||||
err "Internal error: required function '$fn' is missing."
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Step 1/6: Checking prerequisites..."
|
||||
verify_bridge_prerequisites
|
||||
press_enter "Press Enter to continue..."
|
||||
|
||||
echo ""
|
||||
echo "Step 2/6: Installing bridge binary..."
|
||||
install_bridge_binary
|
||||
press_enter "Press Enter to continue..."
|
||||
|
||||
echo ""
|
||||
echo "Step 3/6: Installing bridge-cfg tool..."
|
||||
install_bridge_cfg_tool
|
||||
press_enter "Press Enter to continue..."
|
||||
|
||||
echo ""
|
||||
echo "Step 4/6: Generating configuration with bridge-cfg..."
|
||||
run_bridge_cfg_generate
|
||||
press_enter "Press Enter to continue..."
|
||||
|
||||
echo ""
|
||||
echo "Step 5/6: Creating and starting systemd service..."
|
||||
create_bridge_service
|
||||
press_enter "Press Enter to continue..."
|
||||
|
||||
echo ""
|
||||
echo "Step 6/6: Configuring network rules (optional but recommended)..."
|
||||
adjust_ip_forwarding
|
||||
apply_bridge_iptables_rules
|
||||
configure_dns_and_icmp
|
||||
|
||||
title "Bridge Setup Complete!"
|
||||
|
||||
if systemctl --quiet is-active nym-bridge; then
|
||||
ok "nym-bridge service is running."
|
||||
else
|
||||
warn "nym-bridge failed to start. Check logs with:"
|
||||
echo " journalctl -u nym-bridge -n 50 --no-pager"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
ok "Setup completed successfully."
|
||||
|
||||
echo ""
|
||||
echo -e "${YELLOW}------------------------------------------${RESET}"
|
||||
echo -e "All done! You can safely close this session."
|
||||
echo -e "${YELLOW}------------------------------------------${RESET}"
|
||||
echo ""
|
||||
echo "Logs saved locally at: $LOG_FILE"
|
||||
echo "Operation 'full_bridge_setup' completed."
|
||||
echo ""
|
||||
|
||||
hr
|
||||
echo -e "${CYAN}Next steps and verification:${RESET}"
|
||||
hr
|
||||
echo ""
|
||||
echo -e "${YELLOW}To verify that the Nym Bridge service is active:${RESET}"
|
||||
echo " systemctl status nym-bridge --no-pager"
|
||||
echo " journalctl -u nym-bridge -n 50 --no-pager"
|
||||
echo ""
|
||||
echo -e "${YELLOW}To view live logs in real time:${RESET}"
|
||||
echo " journalctl -u nym-bridge -f"
|
||||
echo ""
|
||||
echo -e "${YELLOW}To restart or reload the bridge service later:${RESET}"
|
||||
echo " systemctl restart nym-bridge"
|
||||
echo ""
|
||||
echo -e "${YELLOW}To ensure your nym-node is properly aligned with the bridge:${RESET}"
|
||||
echo " systemctl restart nym-node"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Optional network diagnostics:${RESET}"
|
||||
echo " ip addr show nymwg"
|
||||
echo " ping -c 3 google.com"
|
||||
echo " ping6 -c 3 google.com"
|
||||
echo ""
|
||||
|
||||
if [[ -n "${LAST_BACKUP_FILE:-}" ]]; then
|
||||
echo ""
|
||||
echo -e "${GREEN}Backup of your nym-node config created at:${RESET} ${LAST_BACKUP_FILE}"
|
||||
fi
|
||||
|
||||
hr
|
||||
echo -e "${GREEN}Bridge and node setup complete. Both services are ready to use.${RESET}"
|
||||
hr
|
||||
echo ""
|
||||
}
|
||||
|
||||
graceful_exit() {
|
||||
local exit_code=$?
|
||||
echo ""
|
||||
echo -e "${YELLOW}------------------------------------------${RESET}"
|
||||
if [[ $exit_code -eq 0 ]]; then
|
||||
echo -e "${GREEN}Setup completed successfully. Exiting cleanly.${RESET}"
|
||||
else
|
||||
echo -e "${RED}Script exited with errors (code: $exit_code).${RESET}"
|
||||
echo "Check the log at: $LOG_FILE"
|
||||
fi
|
||||
echo -e "${YELLOW}------------------------------------------${RESET}"
|
||||
echo ""
|
||||
exec >&- 2>&-
|
||||
exit $exit_code
|
||||
}
|
||||
trap graceful_exit EXIT
|
||||
|
||||
# Command menu
|
||||
case "${1:-}" in
|
||||
full_bridge_setup) full_bridge_setup ;;
|
||||
install_bridge_binary) install_bridge_binary ;;
|
||||
install_bridge_cfg_tool) install_bridge_cfg_tool ;;
|
||||
run_bridge_cfg_generate) run_bridge_cfg_generate ;;
|
||||
create_bridge_service) create_bridge_service ;;
|
||||
adjust_ip_forwarding) adjust_ip_forwarding ;;
|
||||
apply_bridge_iptables_rules) apply_bridge_iptables_rules ;;
|
||||
configure_dns_and_icmp) configure_dns_and_icmp ;;
|
||||
*)
|
||||
echo -e "\nUsage: $0 [command]\n"
|
||||
echo "Commands:"
|
||||
echo " full_bridge_setup - Run full setup"
|
||||
echo " install_bridge_binary - Install nym-bridge (.deb; falls back to source build if libc too old)"
|
||||
echo " install_bridge_cfg_tool - Install bridge-cfg (prebuilt; falls back to source build if libc too old)"
|
||||
echo " run_bridge_cfg_generate - Generate bridges.toml"
|
||||
echo " create_bridge_service - Setup systemd service (respects .deb-provided service)"
|
||||
echo " adjust_ip_forwarding - Enable forwarding"
|
||||
echo " apply_bridge_iptables_rules - NAT rules"
|
||||
echo " configure_dns_and_icmp - Allow ICMP/DNS"
|
||||
echo ""
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "Operation '${1:-help}' completed."
|
||||
@@ -45,21 +45,15 @@ pub use native_client::MixnetClient;
|
||||
pub use native_client::MixnetClientSender;
|
||||
#[allow(deprecated)]
|
||||
pub use nym_client_core::client::{
|
||||
base_client::{
|
||||
storage::{
|
||||
gateways_storage::{
|
||||
ActiveGateway, BadGateway, GatewayRegistration, GatewaysDetailsStore,
|
||||
},
|
||||
Ephemeral, MixnetClientStorage, OnDiskPersistent,
|
||||
},
|
||||
EventReceiver, EventSender, MixnetClientEvent,
|
||||
base_client::storage::{
|
||||
gateways_storage::{ActiveGateway, BadGateway, GatewayRegistration, GatewaysDetailsStore},
|
||||
Ephemeral, MixnetClientStorage, OnDiskPersistent,
|
||||
},
|
||||
inbound_messages::InputMessage,
|
||||
key_manager::{
|
||||
persistence::{InMemEphemeralKeys, KeyStore, OnDiskKeys},
|
||||
ClientKeys,
|
||||
},
|
||||
mix_traffic::MixTrafficEvent,
|
||||
replies::reply_storage::{
|
||||
fs_backend::Backend as ReplyStorage, CombinedReplyStorage, Empty as EmptyReplyStorage,
|
||||
ReplyStorageBackend,
|
||||
|
||||
@@ -16,7 +16,7 @@ use nym_client_core::client::base_client::storage::helpers::{
|
||||
use nym_client_core::client::base_client::storage::{
|
||||
Ephemeral, GatewaysDetailsStore, MixnetClientStorage, OnDiskPersistent,
|
||||
};
|
||||
use nym_client_core::client::base_client::{BaseClient, EventSender};
|
||||
use nym_client_core::client::base_client::BaseClient;
|
||||
use nym_client_core::client::key_manager::persistence::KeyStore;
|
||||
use nym_client_core::client::{
|
||||
base_client::BaseClientBuilder, replies::reply_storage::ReplyStorageBackend,
|
||||
@@ -31,7 +31,6 @@ use nym_crypto::hkdf::DerivationMaterial;
|
||||
use nym_socks5_client_core::config::Socks5;
|
||||
use nym_task::ShutdownTracker;
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_topology::RoutingNode;
|
||||
use nym_validator_client::{nyxd, QueryHttpRpcNyxdClient, UserAgent};
|
||||
use rand::rngs::OsRng;
|
||||
use std::path::Path;
|
||||
@@ -54,7 +53,6 @@ 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>,
|
||||
event_tx: Option<EventSender>,
|
||||
force_tls: bool,
|
||||
user_agent: Option<UserAgent>,
|
||||
#[cfg(unix)]
|
||||
@@ -98,7 +96,6 @@ impl MixnetClientBuilder<OnDiskPersistent> {
|
||||
.await?,
|
||||
gateway_endpoint_config_path: None,
|
||||
custom_shutdown: None,
|
||||
event_tx: None,
|
||||
custom_gateway_transceiver: None,
|
||||
force_tls: false,
|
||||
user_agent: None,
|
||||
@@ -132,7 +129,6 @@ where
|
||||
custom_topology_provider: None,
|
||||
custom_gateway_transceiver: None,
|
||||
custom_shutdown: None,
|
||||
event_tx: None,
|
||||
force_tls: false,
|
||||
user_agent: None,
|
||||
#[cfg(unix)]
|
||||
@@ -156,7 +152,6 @@ where
|
||||
custom_topology_provider: self.custom_topology_provider,
|
||||
custom_gateway_transceiver: self.custom_gateway_transceiver,
|
||||
custom_shutdown: self.custom_shutdown,
|
||||
event_tx: self.event_tx,
|
||||
force_tls: self.force_tls,
|
||||
user_agent: self.user_agent,
|
||||
#[cfg(unix)]
|
||||
@@ -274,13 +269,6 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
/// Use an externally managed shutdown mechanism.
|
||||
#[must_use]
|
||||
pub fn event_tx(mut self, event_tx: EventSender) -> Self {
|
||||
self.event_tx = Some(event_tx);
|
||||
self
|
||||
}
|
||||
|
||||
/// Attempt to wait for the selected gateway (if applicable) to come online if its currently not bonded.
|
||||
#[must_use]
|
||||
pub fn with_wait_for_gateway(mut self, wait_for_gateway: bool) -> Self {
|
||||
@@ -329,12 +317,8 @@ where
|
||||
|
||||
/// Construct a [`DisconnectedMixnetClient`] from the setup specified.
|
||||
pub fn build(self) -> Result<DisconnectedMixnetClient<S>> {
|
||||
let mut client = DisconnectedMixnetClient::new(
|
||||
self.config,
|
||||
self.socks5_config,
|
||||
self.storage,
|
||||
self.event_tx,
|
||||
)?;
|
||||
let mut client =
|
||||
DisconnectedMixnetClient::new(self.config, self.socks5_config, self.storage)?;
|
||||
|
||||
client.custom_gateway_transceiver = self.custom_gateway_transceiver;
|
||||
client.custom_topology_provider = self.custom_topology_provider;
|
||||
@@ -396,9 +380,6 @@ where
|
||||
/// Allows passing an externally controlled shutdown handle.
|
||||
custom_shutdown: Option<ShutdownTracker>,
|
||||
|
||||
/// Sender of mixnet client events to the SDK caller
|
||||
event_tx: Option<EventSender>,
|
||||
|
||||
user_agent: Option<UserAgent>,
|
||||
|
||||
/// Callback on the websocket fd as soon as the connection has been established
|
||||
@@ -433,7 +414,6 @@ where
|
||||
config: Config,
|
||||
socks5_config: Option<Socks5>,
|
||||
storage: S,
|
||||
event_tx: Option<EventSender>,
|
||||
) -> Result<DisconnectedMixnetClient<S>> {
|
||||
// don't create dkg client for the bandwidth controller if credentials are disabled
|
||||
let dkg_query_client = if config.enabled_credentials_mode {
|
||||
@@ -462,7 +442,6 @@ where
|
||||
wait_for_gateway: false,
|
||||
force_tls: false,
|
||||
custom_shutdown: None,
|
||||
event_tx,
|
||||
user_agent: None,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
@@ -551,38 +530,27 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to retrieve list of all gateways available for registration
|
||||
async fn available_gateways(&mut self) -> Result<Vec<RoutingNode>, ClientCoreError> {
|
||||
if let Some(ref mut custom_provider) = self.custom_topology_provider {
|
||||
if let Some(topology) = custom_provider.get_new_topology().await {
|
||||
// Use entry_capable_nodes() instead of entry_gateways() to include
|
||||
// all entry-capable nodes, not just actively assigned ones
|
||||
return Ok(topology.entry_capable_nodes().cloned().collect());
|
||||
}
|
||||
}
|
||||
|
||||
async fn new_gateway_setup(&self) -> Result<GatewaySetup, ClientCoreError> {
|
||||
let nym_api_endpoints = self.get_api_endpoints();
|
||||
let topology_cfg = &self.config.debug_config.topology;
|
||||
let user_agent = self.user_agent.clone();
|
||||
|
||||
gateways_for_init(
|
||||
&nym_api_endpoints,
|
||||
user_agent,
|
||||
topology_cfg.minimum_gateway_performance,
|
||||
topology_cfg.ignore_ingress_epoch_role,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn new_gateway_setup(&mut self) -> Result<GatewaySetup, ClientCoreError> {
|
||||
let selection_spec = GatewaySelectionSpecification::new(
|
||||
self.config.user_chosen_gateway.clone(),
|
||||
None,
|
||||
self.force_tls,
|
||||
);
|
||||
|
||||
let available_gateways = self.available_gateways().await?;
|
||||
let user_agent = self.user_agent.clone();
|
||||
|
||||
let topology_cfg = &self.config.debug_config.topology;
|
||||
let mut rng = OsRng;
|
||||
let available_gateways = gateways_for_init(
|
||||
&mut rng,
|
||||
&nym_api_endpoints,
|
||||
user_agent,
|
||||
topology_cfg.minimum_gateway_performance,
|
||||
topology_cfg.ignore_ingress_epoch_role,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(GatewaySetup::New {
|
||||
specification: selection_spec,
|
||||
@@ -706,16 +674,6 @@ where
|
||||
.config
|
||||
.as_base_client_config(nyxd_endpoints, nym_api_endpoints.clone());
|
||||
|
||||
tracing::debug!(
|
||||
"SDK: Passing nym_api_urls to BaseClientBuilder (has {} nym_api_urls)",
|
||||
self.config
|
||||
.network_details
|
||||
.nym_api_urls
|
||||
.as_ref()
|
||||
.map(|urls| urls.len())
|
||||
.unwrap_or(0)
|
||||
);
|
||||
|
||||
let mut base_builder: BaseClientBuilder<_, _> =
|
||||
BaseClientBuilder::new(base_config, self.storage, self.dkg_query_client)
|
||||
.with_wait_for_gateway(self.wait_for_gateway)
|
||||
@@ -723,11 +681,6 @@ where
|
||||
.with_remember_me(&self.remember_me)
|
||||
.with_derivation_material(self.derivation_material);
|
||||
|
||||
// Add nym_api_urls if available in network_details
|
||||
if let Some(nym_api_urls) = self.config.network_details.nym_api_urls.clone() {
|
||||
base_builder = base_builder.with_nym_api_urls(nym_api_urls);
|
||||
}
|
||||
|
||||
if let Some(user_agent) = self.user_agent {
|
||||
base_builder = base_builder.with_user_agent(user_agent);
|
||||
}
|
||||
@@ -736,14 +689,15 @@ where
|
||||
base_builder = base_builder.with_topology_provider(topology_provider);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
// 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);
|
||||
|
||||
if let Some(gateway_transceiver) = self.custom_gateway_transceiver {
|
||||
base_builder = base_builder.with_gateway_transceiver(gateway_transceiver);
|
||||
@@ -861,6 +815,7 @@ where
|
||||
stats_events_reporter,
|
||||
started_client.shutdown_handle,
|
||||
None,
|
||||
started_client.client_request_sender,
|
||||
started_client.forget_me,
|
||||
started_client.remember_me,
|
||||
))
|
||||
@@ -892,17 +847,3 @@ impl IncludedSurbs {
|
||||
Self::ExposeSelfAddress
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_mixnet_builder_default_no_custom_client() {
|
||||
let builder = MixnetClientBuilder::new_ephemeral();
|
||||
assert!(
|
||||
builder.build().is_ok(),
|
||||
"Builder should succeed without custom client"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
use tokio::sync::RwLockReadGuard;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tokio_util::sync::WaitForCancellationFutureOwned;
|
||||
|
||||
/// Client connected to the Nym mixnet.
|
||||
pub struct MixnetClient {
|
||||
@@ -58,6 +59,7 @@ pub struct MixnetClient {
|
||||
|
||||
// internal state used for the `Stream` implementation
|
||||
_buffered: Vec<ReconstructedMessage>,
|
||||
pub(crate) client_request_sender: ClientRequestSender,
|
||||
pub(crate) forget_me: ForgetMe,
|
||||
pub(crate) remember_me: RememberMe,
|
||||
}
|
||||
@@ -74,6 +76,7 @@ impl MixnetClient {
|
||||
stats_events_reporter: ClientStatsSender,
|
||||
task_handle: ShutdownTracker,
|
||||
packet_type: Option<PacketType>,
|
||||
client_request_sender: ClientRequestSender,
|
||||
forget_me: ForgetMe,
|
||||
remember_me: RememberMe,
|
||||
) -> Self {
|
||||
@@ -88,6 +91,7 @@ impl MixnetClient {
|
||||
shutdown_handle: task_handle,
|
||||
packet_type,
|
||||
_buffered: Vec::new(),
|
||||
client_request_sender,
|
||||
forget_me,
|
||||
remember_me,
|
||||
}
|
||||
@@ -126,7 +130,7 @@ impl MixnetClient {
|
||||
}
|
||||
|
||||
pub fn client_request_sender(&self) -> ClientRequestSender {
|
||||
self.client_input.client_request_sender.clone()
|
||||
self.client_request_sender.clone()
|
||||
}
|
||||
|
||||
/// Get the client's identity keys.
|
||||
@@ -249,12 +253,7 @@ impl MixnetClient {
|
||||
client: self.forget_me.client(),
|
||||
stats: self.forget_me.stats(),
|
||||
};
|
||||
match self
|
||||
.client_input
|
||||
.client_request_sender
|
||||
.send(client_request)
|
||||
.await
|
||||
{
|
||||
match self.client_request_sender.send(client_request).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
error!("Failed to send forget me request: {e}");
|
||||
@@ -267,12 +266,7 @@ impl MixnetClient {
|
||||
let client_request = ClientRequest::RememberMe {
|
||||
session_type: self.remember_me.session_type(),
|
||||
};
|
||||
match self
|
||||
.client_input
|
||||
.client_request_sender
|
||||
.send(client_request)
|
||||
.await
|
||||
{
|
||||
match self.client_request_sender.send(client_request).await {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => {
|
||||
error!("Failed to send forget me request: {e}");
|
||||
@@ -280,6 +274,12 @@ impl MixnetClient {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cancelled(&self) -> WaitForCancellationFutureOwned {
|
||||
self.shutdown_handle
|
||||
.clone_shutdown_token()
|
||||
.cancelled_owned()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[package]
|
||||
name = "nym-network-requester"
|
||||
license = "GPL-3.0"
|
||||
version = "1.1.65"
|
||||
version = "1.1.64"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version = "1.85"
|
||||
|
||||
@@ -226,8 +226,7 @@ mod tests {
|
||||
error!("{err}");
|
||||
// this is not an ideal way of checking it, but if test fails due to networking failures
|
||||
// it should be fine to progress
|
||||
let err_str = err.to_string();
|
||||
if err_str.contains("nym api") || err_str.contains("failed to connect") {
|
||||
if err.to_string().contains("nym api request failed") {
|
||||
return Ok(());
|
||||
}
|
||||
return Err(err);
|
||||
@@ -292,8 +291,7 @@ mod tests {
|
||||
error!("{err}");
|
||||
// this is not an ideal way of checking it, but if test fails due to networking failures
|
||||
// it should be fine to progress
|
||||
let err_str = err.to_string();
|
||||
if err_str.contains("nym api") || err_str.contains("failed to connect") {
|
||||
if err.to_string().contains("nym api request failed") {
|
||||
return Ok(());
|
||||
}
|
||||
return Err(err);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user