Compare commits
96 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a19a6e3348 | |||
| e9135d54fc | |||
| 32f1ebebf9 | |||
| 12f7f7b669 | |||
| f3f75ae751 | |||
| 20c1717a65 | |||
| 6ae78d9f4d | |||
| afb2467afc | |||
| 44a7df511a | |||
| c89ca8075f | |||
| 5f9f4fb1ab | |||
| 75e146c301 | |||
| 2ed1a0000b | |||
| ee138fe751 | |||
| 6b988e591d | |||
| 6cc12a4aad | |||
| e1398f4422 | |||
| 20b0be351d | |||
| af824b7043 | |||
| d32332a82e | |||
| 5e8f402c5d | |||
| ad4ae0d00d | |||
| aed16fcacd | |||
| 14f983c481 | |||
| 587e2f4b53 | |||
| 4d8348b304 | |||
| 750df1e0bd | |||
| 94614e9d90 | |||
| 002055cb9f | |||
| a117d9089b | |||
| ed8ca4e4ea | |||
| 587f6bf8a8 | |||
| 55a7f67407 | |||
| e5c88e30b4 | |||
| dd4945e269 | |||
| 0adeb6e677 | |||
| b7b399d71c | |||
| 65b5c258e8 | |||
| da1463924f | |||
| 749ceec727 | |||
| c639a7e1c1 | |||
| e0876a6238 | |||
| 89f5ab11a5 | |||
| 4063071e08 | |||
| 99a0f4ea30 | |||
| dfa969d754 | |||
| 428dd0543f | |||
| b90088148c | |||
| 08a45c188d | |||
| 806e8629fe | |||
| c22bb99da6 | |||
| 91ee54e5a7 | |||
| 572fc331b0 | |||
| c5590bddc5 | |||
| c9aed048d0 | |||
| b8f7d54f18 | |||
| c04e173ee6 | |||
| 34143f1d58 | |||
| 47b88737e6 | |||
| 76ddef285c | |||
| 89fe17f19c | |||
| 57d2824521 | |||
| b8058dd7ba | |||
| 95d08c7f21 | |||
| a29df08463 | |||
| 6a028417ad | |||
| 4cd4dc2d1c | |||
| 983cba21ba | |||
| b9fb2c4e0a | |||
| 7bcd3fe754 | |||
| eeb0278d13 | |||
| 7147ba56e2 | |||
| 7bf5553fd1 | |||
| 810b0628bb | |||
| 8d28016e08 | |||
| fb0b55d540 | |||
| 1bb973e4a7 | |||
| aa1cad4422 | |||
| e388e67357 | |||
| 44ac5e1ced | |||
| 78d3d78a8c | |||
| 05734e6fe9 | |||
| d2d90160be | |||
| 6731b89714 | |||
| 55aea37b89 | |||
| 748478c89e | |||
| 1286842c6d | |||
| b6213bc016 | |||
| 737c4d79e0 | |||
| b105e5a15d | |||
| 90e9e3cff8 | |||
| 87a188ca06 | |||
| 0ee387d983 | |||
| 4d0898c633 | |||
| d984d085a7 | |||
| 2e22cad074 |
@@ -21,7 +21,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [ arc-ubuntu-22.04 ]
|
||||
platform: [ arc-linux-latest ]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
|
||||
@@ -9,7 +9,7 @@ on:
|
||||
|
||||
jobs:
|
||||
wasm:
|
||||
runs-on: arc-ubuntu-22.04
|
||||
runs-on: arc-linux-latest
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||
|
||||
@@ -11,7 +11,7 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: arc-ubuntu-22.04
|
||||
runs-on: arc-linux-latest
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||
|
||||
@@ -11,7 +11,7 @@ on:
|
||||
|
||||
jobs:
|
||||
wasm:
|
||||
runs-on: arc-ubuntu-22.04
|
||||
runs-on: arc-linux-latest
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTUP_PERMIT_COPY_RENAME: 1
|
||||
|
||||
@@ -30,13 +30,11 @@ jobs:
|
||||
release_date: ${{ fromJSON(steps.create-release.outputs.assets)[0].published_at }}
|
||||
client_hash: ${{ steps.binary-hashes.outputs.client_hash }}
|
||||
nymvisor_hash: ${{ steps.binary-hashes.outputs.nymvisor_hash }}
|
||||
nymnode_hash: ${{ steps.binary-hashes.outputs.nymnode_hash }}
|
||||
socks5_hash: ${{ steps.binary-hashes.outputs.socks5_hash }}
|
||||
netreq_hash: ${{ steps.binary-hashes.outputs.netreq_hash }}
|
||||
cli_hash: ${{ steps.binary-hashes.outputs.cli_hash }}
|
||||
client_version: ${{ steps.binary-versions.outputs.client_version }}
|
||||
nymvisor_version: ${{ steps.binary-versions.outputs.nymvisor_version }}
|
||||
nymnode_version: ${{ steps.binary-versions.outputs.nymnode_version }}
|
||||
socks5_version: ${{ steps.binary-versions.outputs.socks5_version }}
|
||||
netreq_version: ${{ steps.binary-versions.outputs.netreq_version }}
|
||||
cli_version: ${{ steps.binary-versions.outputs.cli_version }}
|
||||
@@ -56,7 +54,7 @@ jobs:
|
||||
- name: Install Rust stable
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: 1.86.0
|
||||
toolchain: 1.88.0
|
||||
override: true
|
||||
|
||||
- name: Build all binaries
|
||||
@@ -76,7 +74,6 @@ jobs:
|
||||
target/release/nym-network-requester
|
||||
target/release/nym-cli
|
||||
target/release/nymvisor
|
||||
target/release/nym-node
|
||||
retention-days: 30
|
||||
|
||||
- id: create-release
|
||||
@@ -91,7 +88,6 @@ jobs:
|
||||
target/release/nym-network-requester
|
||||
target/release/nym-cli
|
||||
target/release/nymvisor
|
||||
target/release/nym-node
|
||||
|
||||
push-release-data-client:
|
||||
if: ${{ (startsWith(github.ref, 'refs/tags/nym-binaries-') && github.event_name == 'release') || github.event_name == 'workflow_dispatch' }}
|
||||
|
||||
@@ -69,6 +69,6 @@ jobs:
|
||||
|
||||
- name: BuildAndPushImageOnHarbor
|
||||
run: |
|
||||
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile-pg . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
|
||||
docker build -f ${{ env.WORKING_DIRECTORY }}/Dockerfile . -t harbor.nymte.ch/nym/${{ env.IMAGE_NAME_AND_TAGS }} -t harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }}:latest
|
||||
docker push harbor.nymte.ch/nym/${{ env.CONTAINER_NAME }} --all-tags
|
||||
|
||||
|
||||
@@ -4,6 +4,48 @@ Post 1.0.0 release, the changelog format is based on [Keep a Changelog](https://
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [2025.16-halloumi] (2025-09-16)
|
||||
|
||||
- Backport metadata endpoint ([#6010])
|
||||
- bugfix: make sure tables are removed in correct order to not trigger FK constraint issue ([#5987])
|
||||
- chore: move authenticator into gateway crate ([#5982])
|
||||
- Fix the ns api ci workflow ([#5981])
|
||||
- Remove freshness check on testrun submit ([#5977])
|
||||
- Update sysinfo to the latest ([#5976])
|
||||
- bugfix: manually calculate per node work on rewarded set changes ([#5972])
|
||||
- fixing the ci for ns agent ([#5965])
|
||||
- Feature/testing utils ([#5963])
|
||||
- bugfix: fix ci-build for linux (and use updated runner) ([#5958])
|
||||
- chore: updated refs to cheddar rev of nym repo ([#5955])
|
||||
- http api client adjustment ([#5953])
|
||||
- chore: fix rust 1.89 clippy issues ([#5944])
|
||||
- Wireguard metadata client library ([#5943])
|
||||
- chore: remove unused import ([#5942])
|
||||
- feat: introduce additional checks when attempting to send to bounded channels ([#5941])
|
||||
- Move credential verifier in peer controller ([#5938])
|
||||
- change PK/FK on expiration date signatures tables ([#5934])
|
||||
- Wireguard private metadata ([#5915])
|
||||
|
||||
[#6010]: https://github.com/nymtech/nym/pull/6010
|
||||
[#5987]: https://github.com/nymtech/nym/pull/5987
|
||||
[#5982]: https://github.com/nymtech/nym/pull/5982
|
||||
[#5981]: https://github.com/nymtech/nym/pull/5981
|
||||
[#5977]: https://github.com/nymtech/nym/pull/5977
|
||||
[#5976]: https://github.com/nymtech/nym/pull/5976
|
||||
[#5972]: https://github.com/nymtech/nym/pull/5972
|
||||
[#5965]: https://github.com/nymtech/nym/pull/5965
|
||||
[#5963]: https://github.com/nymtech/nym/pull/5963
|
||||
[#5958]: https://github.com/nymtech/nym/pull/5958
|
||||
[#5955]: https://github.com/nymtech/nym/pull/5955
|
||||
[#5953]: https://github.com/nymtech/nym/pull/5953
|
||||
[#5944]: https://github.com/nymtech/nym/pull/5944
|
||||
[#5943]: https://github.com/nymtech/nym/pull/5943
|
||||
[#5942]: https://github.com/nymtech/nym/pull/5942
|
||||
[#5941]: https://github.com/nymtech/nym/pull/5941
|
||||
[#5938]: https://github.com/nymtech/nym/pull/5938
|
||||
[#5934]: https://github.com/nymtech/nym/pull/5934
|
||||
[#5915]: https://github.com/nymtech/nym/pull/5915
|
||||
|
||||
## [2025.15-gruyere] (2025-08-20)
|
||||
|
||||
- Migrate strum to 0.27.2 ([#5960])
|
||||
|
||||
Generated
+1022
-1016
File diff suppressed because it is too large
Load Diff
+11
-4
@@ -58,7 +58,7 @@ members = [
|
||||
"common/gateway-requests",
|
||||
"common/gateway-stats-storage",
|
||||
"common/gateway-storage",
|
||||
"common/http-api-client",
|
||||
"common/http-api-client", "common/http-api-client-macro",
|
||||
"common/http-api-common",
|
||||
"common/inclusion-probability",
|
||||
"common/ip-packet-requests",
|
||||
@@ -275,6 +275,7 @@ hyper = "1.6.0"
|
||||
hyper-util = "0.1"
|
||||
indicatif = "0.18.0"
|
||||
inquire = "0.6.2"
|
||||
inventory = "0.3.21"
|
||||
ip_network = "0.4.1"
|
||||
ipnetwork = "0.20"
|
||||
itertools = "0.14.0"
|
||||
@@ -291,8 +292,11 @@ 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"
|
||||
opentelemetry = "0.30.0"
|
||||
opentelemetry-otlp = "0.30.0"
|
||||
opentelemetry-semantic-conventions = "0.30.0"
|
||||
opentelemetry_sdk = "0.30.0"
|
||||
opentelemetry-stdout = "0.30.0"
|
||||
parking_lot = "0.12.3"
|
||||
pem = "0.8"
|
||||
petgraph = "0.6.5"
|
||||
@@ -322,6 +326,7 @@ serde_json_path = "0.7.2"
|
||||
serde_repr = "0.1"
|
||||
serde_with = "3.9.0"
|
||||
serde_yaml = "0.9.25"
|
||||
serde_plain = "1.0.2"
|
||||
sha2 = "0.10.9"
|
||||
si-scale = "0.2.3"
|
||||
snow = "0.9.6"
|
||||
@@ -349,8 +354,10 @@ toml = "0.8.22"
|
||||
tower = "0.5.2"
|
||||
tower-http = "0.5.2"
|
||||
tracing = "0.1.41"
|
||||
tracing-core = "0.1.33"
|
||||
tracing-log = "0.2"
|
||||
tracing-opentelemetry = "0.19.0"
|
||||
tracing-opentelemetry = "0.31.0"
|
||||
tracing-serde = "0.2.0"
|
||||
tracing-subscriber = "0.3.19"
|
||||
tracing-tree = "0.2.2"
|
||||
tracing-indicatif = "0.3.9"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-client"
|
||||
version = "1.1.61"
|
||||
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"
|
||||
@@ -46,7 +46,6 @@ nym-bandwidth-controller = { path = "../../common/bandwidth-controller" }
|
||||
nym-bin-common = { path = "../../common/bin-common", features = [
|
||||
"output_format",
|
||||
"clap",
|
||||
"basic_tracing",
|
||||
] }
|
||||
nym-client-core = { path = "../../common/client-core", features = [
|
||||
"fs-credentials-storage",
|
||||
|
||||
@@ -11,7 +11,7 @@ use nym_client_core::client::base_client::{
|
||||
BaseClientBuilder, ClientInput, ClientOutput, ClientState,
|
||||
};
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_task::TaskHandle;
|
||||
use nym_task::ShutdownManager;
|
||||
use nym_validator_client::QueryHttpRpcNyxdClient;
|
||||
use std::error::Error;
|
||||
use std::path::PathBuf;
|
||||
@@ -29,6 +29,8 @@ pub struct SocketClient {
|
||||
|
||||
/// Optional path to a .json file containing standalone network details.
|
||||
custom_mixnet: Option<PathBuf>,
|
||||
|
||||
shutdown_manager: ShutdownManager,
|
||||
}
|
||||
|
||||
impl SocketClient {
|
||||
@@ -40,6 +42,7 @@ impl SocketClient {
|
||||
SocketClient {
|
||||
config,
|
||||
custom_mixnet,
|
||||
shutdown_manager: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +52,7 @@ impl SocketClient {
|
||||
client_output: ClientOutput,
|
||||
client_state: ClientState,
|
||||
self_address: &Recipient,
|
||||
task_client: nym_task::TaskClient,
|
||||
shutdown_token: nym_task::ShutdownToken,
|
||||
packet_type: PacketType,
|
||||
) {
|
||||
info!("Starting websocket listener...");
|
||||
@@ -77,24 +80,24 @@ impl SocketClient {
|
||||
shared_lane_queue_lengths,
|
||||
reply_controller_sender,
|
||||
Some(packet_type),
|
||||
task_client.fork("websocket_handler"),
|
||||
shutdown_token.clone(),
|
||||
);
|
||||
|
||||
websocket::Listener::new(
|
||||
config.socket.host,
|
||||
config.socket.listening_port,
|
||||
task_client.with_suffix("websocket_listener"),
|
||||
shutdown_token.child_token(),
|
||||
)
|
||||
.start(websocket_handler);
|
||||
}
|
||||
|
||||
/// blocking version of `start_socket` method. Will run forever (or until SIGINT is sent)
|
||||
pub async fn run_socket_forever(self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let shutdown = self.start_socket().await?;
|
||||
let mut shutdown = self.start_socket().await?;
|
||||
|
||||
let res = shutdown.wait_for_shutdown().await;
|
||||
shutdown.run_until_shutdown().await;
|
||||
log::info!("Stopping nym-client");
|
||||
res
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn initialise_storage(&self) -> Result<OnDiskPersistent, ClientError> {
|
||||
@@ -119,6 +122,7 @@ impl SocketClient {
|
||||
|
||||
let mut base_client =
|
||||
BaseClientBuilder::new(self.config().base(), storage, dkg_query_client)
|
||||
.with_shutdown(self.shutdown_manager.shutdown_tracker_owned())
|
||||
.with_user_agent(user_agent);
|
||||
|
||||
if let Some(custom_mixnet) = &self.custom_mixnet {
|
||||
@@ -128,7 +132,7 @@ impl SocketClient {
|
||||
Ok(base_client)
|
||||
}
|
||||
|
||||
pub async fn start_socket(self) -> Result<TaskHandle, ClientError> {
|
||||
pub async fn start_socket(self) -> Result<ShutdownManager, ClientError> {
|
||||
if !self.config.socket.socket_type.is_websocket() {
|
||||
return Err(ClientError::InvalidSocketMode);
|
||||
}
|
||||
@@ -147,13 +151,13 @@ impl SocketClient {
|
||||
client_output,
|
||||
client_state,
|
||||
&self_address,
|
||||
started_client.task_handle.get_handle(),
|
||||
self.shutdown_manager.child_shutdown_token(),
|
||||
packet_type,
|
||||
);
|
||||
|
||||
info!("Client startup finished!");
|
||||
info!("The address of this client is: {self_address}");
|
||||
|
||||
Ok(started_client.task_handle)
|
||||
Ok(self.shutdown_manager)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use std::error::Error;
|
||||
|
||||
use clap::{crate_name, crate_version, Parser};
|
||||
use nym_bin_common::logging::{maybe_print_banner, setup_tracing_logger};
|
||||
use nym_bin_common::logging::{maybe_print_banner, setup_no_otel_logger};
|
||||
use nym_network_defaults::setup_env;
|
||||
|
||||
pub mod client;
|
||||
@@ -20,7 +20,7 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
if !args.no_banner {
|
||||
maybe_print_banner(crate_name!(), crate_version!());
|
||||
}
|
||||
setup_tracing_logger();
|
||||
setup_no_otel_logger().expect("failed to initialize logging");
|
||||
|
||||
if let Err(err) = commands::execute(args).await {
|
||||
log::error!("{err}");
|
||||
|
||||
@@ -19,7 +19,7 @@ use nym_sphinx::receiver::ReconstructedMessage;
|
||||
use nym_task::connections::{
|
||||
ConnectionCommand, ConnectionCommandSender, ConnectionId, LaneQueueLengths, TransmissionLane,
|
||||
};
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use std::time::Duration;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio::time::Instant;
|
||||
@@ -44,7 +44,7 @@ pub(crate) struct HandlerBuilder {
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
packet_type: Option<PacketType>,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
}
|
||||
|
||||
impl HandlerBuilder {
|
||||
@@ -57,7 +57,7 @@ impl HandlerBuilder {
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
packet_type: Option<PacketType>,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> Self {
|
||||
Self {
|
||||
msg_input,
|
||||
@@ -67,14 +67,13 @@ impl HandlerBuilder {
|
||||
lane_queue_lengths,
|
||||
reply_controller_sender,
|
||||
packet_type,
|
||||
task_client,
|
||||
shutdown_token,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: make sure we only ever have one active handler
|
||||
pub fn create_active_handler(&self) -> Handler {
|
||||
let mut task_client = self.task_client.fork("active_handler");
|
||||
task_client.disarm();
|
||||
let shutdown_token = self.shutdown_token.clone();
|
||||
Handler {
|
||||
msg_input: self.msg_input.clone(),
|
||||
client_connection_tx: self.client_connection_tx.clone(),
|
||||
@@ -85,7 +84,7 @@ impl HandlerBuilder {
|
||||
lane_queue_lengths: self.lane_queue_lengths.clone(),
|
||||
reply_controller_sender: self.reply_controller_sender.clone(),
|
||||
packet_type: self.packet_type,
|
||||
task_client,
|
||||
shutdown_token,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,19 +99,14 @@ pub(crate) struct Handler {
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
packet_type: Option<PacketType>,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
}
|
||||
|
||||
impl Drop for Handler {
|
||||
fn drop(&mut self) {
|
||||
if let Err(err) = self
|
||||
let _ = self
|
||||
.buffer_requester
|
||||
.unbounded_send(ReceivedBufferMessage::ReceiverDisconnect)
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("failed to disconnect the receiver from the buffer: {err}");
|
||||
}
|
||||
}
|
||||
.unbounded_send(ReceivedBufferMessage::ReceiverDisconnect);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +136,7 @@ impl Handler {
|
||||
{
|
||||
Ok(length) => length,
|
||||
Err(err) => {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
if !self.shutdown_token.is_cancelled() {
|
||||
error!(
|
||||
"Failed to get reply queue length for connection {connection_id}: {err}"
|
||||
);
|
||||
@@ -190,9 +184,9 @@ impl Handler {
|
||||
});
|
||||
|
||||
// the ack control is now responsible for chunking, etc.
|
||||
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type);
|
||||
let input_msg = InputMessage::new_regular(recipient, message, lane, self.packet_type, None);
|
||||
if let Err(err) = self.msg_input.send(input_msg).await {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
if !self.shutdown_token.is_cancelled() {
|
||||
error!("Failed to send message to the input buffer: {err}");
|
||||
}
|
||||
}
|
||||
@@ -223,9 +217,9 @@ impl Handler {
|
||||
});
|
||||
|
||||
let input_msg =
|
||||
InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type);
|
||||
InputMessage::new_anonymous(recipient, message, reply_surbs, lane, self.packet_type, None);
|
||||
if let Err(err) = self.msg_input.send(input_msg).await {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
if !self.shutdown_token.is_cancelled() {
|
||||
error!("Failed to send anonymous message to the input buffer: {err}");
|
||||
}
|
||||
}
|
||||
@@ -253,7 +247,7 @@ impl Handler {
|
||||
|
||||
let input_msg = InputMessage::new_reply(recipient_tag, message, lane, self.packet_type);
|
||||
if let Err(err) = self.msg_input.send(input_msg).await {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
if !self.shutdown_token.is_cancelled() {
|
||||
error!("Failed to send reply message to the input buffer: {err}");
|
||||
}
|
||||
}
|
||||
@@ -275,7 +269,7 @@ impl Handler {
|
||||
.client_connection_tx
|
||||
.unbounded_send(ConnectionCommand::Close(connection_id))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
if !self.shutdown_token.is_cancelled() {
|
||||
error!("Failed to send close connection command: {err}");
|
||||
}
|
||||
}
|
||||
@@ -394,11 +388,14 @@ impl Handler {
|
||||
}
|
||||
|
||||
async fn listen_for_requests(&mut self, mut msg_receiver: ReconstructedMessagesReceiver) {
|
||||
let mut task_client = self.task_client.fork("select");
|
||||
task_client.disarm();
|
||||
let shutdown_token = self.shutdown_token.clone();
|
||||
|
||||
while !task_client.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = shutdown_token.cancelled() => {
|
||||
log::trace!("Websocket handler: Received shutdown");
|
||||
break;
|
||||
}
|
||||
// we can either get a client request from the websocket
|
||||
socket_msg = self.next_websocket_request() => {
|
||||
if socket_msg.is_none() {
|
||||
@@ -436,9 +433,6 @@ impl Handler {
|
||||
break;
|
||||
}
|
||||
}
|
||||
_ = task_client.recv() => {
|
||||
log::trace!("Websocket handler: Received shutdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
log::debug!("Websocket handler: Exiting");
|
||||
@@ -464,7 +458,7 @@ impl Handler {
|
||||
reconstructed_sender,
|
||||
))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
if !self.shutdown_token.is_cancelled() {
|
||||
error!("failed to announce the receiver to the buffer: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use super::handler::HandlerBuilder;
|
||||
use log::*;
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use std::net::IpAddr;
|
||||
use std::{net::SocketAddr, process, sync::Arc};
|
||||
use tokio::io::AsyncWriteExt;
|
||||
@@ -23,15 +23,15 @@ impl State {
|
||||
pub(crate) struct Listener {
|
||||
address: SocketAddr,
|
||||
state: State,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
}
|
||||
|
||||
impl Listener {
|
||||
pub(crate) fn new(host: IpAddr, port: u16, task_client: TaskClient) -> Self {
|
||||
pub(crate) fn new(host: IpAddr, port: u16, shutdown_token: ShutdownToken) -> Self {
|
||||
Listener {
|
||||
address: SocketAddr::new(host, port),
|
||||
state: State::AwaitingConnection,
|
||||
task_client,
|
||||
shutdown_token,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,11 +46,11 @@ impl Listener {
|
||||
|
||||
let notify = Arc::new(Notify::new());
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
while !self.shutdown_token.is_cancelled() {
|
||||
tokio::select! {
|
||||
// When the handler finishes we check if shutdown is signalled
|
||||
_ = notify.notified() => {
|
||||
if self.task_client.is_shutdown() {
|
||||
if self.shutdown_token.is_cancelled() {
|
||||
log::trace!("Websocket listener: detected shutdown after connection closed");
|
||||
break;
|
||||
}
|
||||
@@ -59,7 +59,7 @@ impl Listener {
|
||||
}
|
||||
// ... but when there is no connected client at the time of shutdown being
|
||||
// signalled, we handle it here.
|
||||
_ = self.task_client.recv() => {
|
||||
_ = self.shutdown_token.cancelled() => {
|
||||
if !self.state.is_connected() {
|
||||
log::trace!("Not connected: shutting down");
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "nym-socks5-client"
|
||||
version = "1.1.61"
|
||||
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"
|
||||
@@ -27,7 +27,6 @@ zeroize = { workspace = true }
|
||||
nym-bin-common = { path = "../../common/bin-common", features = [
|
||||
"output_format",
|
||||
"clap",
|
||||
"basic_tracing",
|
||||
] }
|
||||
nym-client-core = { path = "../../common/client-core", features = [
|
||||
"fs-credentials-storage",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use std::error::Error;
|
||||
|
||||
use clap::{crate_name, crate_version, Parser};
|
||||
use nym_bin_common::logging::{maybe_print_banner, setup_tracing_logger};
|
||||
use nym_bin_common::logging::{maybe_print_banner, setup_no_otel_logger};
|
||||
use nym_network_defaults::setup_env;
|
||||
|
||||
mod commands;
|
||||
@@ -19,7 +19,7 @@ async fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
if !args.no_banner {
|
||||
maybe_print_banner(crate_name!(), crate_version!());
|
||||
}
|
||||
setup_tracing_logger();
|
||||
setup_no_otel_logger().expect("failed to initialize logging");
|
||||
|
||||
if let Err(err) = commands::execute(args).await {
|
||||
log::error!("{err}");
|
||||
|
||||
@@ -13,7 +13,7 @@ use nym_credentials_interface::{
|
||||
};
|
||||
use nym_ecash_time::Date;
|
||||
use nym_validator_client::coconut::all_ecash_api_clients;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nym_api::{EpochId, NymApiClientExt};
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use nym_validator_client::EcashApiClient;
|
||||
use rand::prelude::SliceRandom;
|
||||
|
||||
@@ -8,24 +8,30 @@ license = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
chrono = { workspace = true, optional = true }
|
||||
cfg-if = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive"], optional = true }
|
||||
clap_complete = { workspace = true, optional = true }
|
||||
clap_complete_fig = { workspace = true, optional = true }
|
||||
const-str = { workspace = true }
|
||||
log = { workspace = true }
|
||||
opentelemetry = { workspace = true, optional = true }
|
||||
opentelemetry-otlp = { workspace = true,features=["metrics", "grpc-tonic", "tls",
|
||||
"tls-webpki-roots"], optional = true }
|
||||
opentelemetry-semantic-conventions = { workspace = true, features = ["semconv_experimental"], optional = true }
|
||||
opentelemetry-stdout = { workspace = true, features = ["trace", "metrics"], optional = true }
|
||||
opentelemetry_sdk = { workspace = true, optional = true }
|
||||
rand = { workspace = true, optional = true }
|
||||
schemars = { workspace = true, features = ["preserve_order"], optional = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true, optional = true }
|
||||
|
||||
## tracing
|
||||
tracing-subscriber = { workspace = true, features = ["env-filter"], optional = true }
|
||||
tracing-tree = { workspace = true, optional = true }
|
||||
tracing = { workspace = true, optional = true }
|
||||
opentelemetry-jaeger = { workspace = true, features = ["rt-tokio", "collector_client", "isahc_collector_client"], optional = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
tracing-core = { workspace = true }
|
||||
tracing-opentelemetry = { workspace = true, optional = true }
|
||||
tracing-serde = { workspace = true }
|
||||
tracing-subscriber = { workspace = true, features = ["env-filter", "json"] }
|
||||
tracing-tree = { workspace = true }
|
||||
utoipa = { workspace = true, optional = true }
|
||||
opentelemetry = { workspace = true, features = ["rt-tokio"], optional = true }
|
||||
|
||||
|
||||
[build-dependencies]
|
||||
vergen = { workspace = true, features = ["build", "git", "gitcl", "rustc", "cargo"] }
|
||||
@@ -35,13 +41,17 @@ default = []
|
||||
openapi = ["utoipa"]
|
||||
output_format = ["serde_json", "dep:clap"]
|
||||
bin_info_schema = ["schemars"]
|
||||
basic_tracing = ["dep:tracing", "tracing-subscriber"]
|
||||
tracing = [
|
||||
"basic_tracing",
|
||||
"tracing-tree",
|
||||
"opentelemetry-jaeger",
|
||||
tokio-console = ["otel"]
|
||||
otel = [
|
||||
"chrono",
|
||||
"tracing-opentelemetry",
|
||||
"opentelemetry",
|
||||
"opentelemetry-otlp",
|
||||
"opentelemetry-semantic-conventions",
|
||||
"opentelemetry-stdout",
|
||||
"opentelemetry_sdk",
|
||||
"serde_json",
|
||||
"rand",
|
||||
]
|
||||
clap = ["dep:clap", "dep:clap_complete", "dep:clap_complete_fig"]
|
||||
models = []
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
pub mod build_information;
|
||||
pub mod logging;
|
||||
|
||||
#[cfg(feature = "otel")]
|
||||
pub mod opentelemetry;
|
||||
|
||||
#[cfg(feature = "clap")]
|
||||
pub mod completions;
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#[cfg(feature = "otel")]
|
||||
use opentelemetry_otlp::ExporterBuildError;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum TracingError {
|
||||
#[error("tracing logger already initialised")]
|
||||
TracingLoggerAlreadyInitialised,
|
||||
|
||||
#[error("Logging error: {0}")]
|
||||
TracingTryInitError(tracing_subscriber::util::TryInitError),
|
||||
|
||||
#[cfg(feature = "otel")]
|
||||
#[error("{0}")]
|
||||
TracingExporterBuildError(#[from] ExporterBuildError),
|
||||
|
||||
#[error("{0}")]
|
||||
TracingFilterParseError(#[from] tracing_subscriber::filter::ParseError),
|
||||
}
|
||||
@@ -1,19 +1,12 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
pub mod error;
|
||||
|
||||
use error::TracingError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::IsTerminal;
|
||||
|
||||
#[cfg(feature = "tracing")]
|
||||
pub use opentelemetry;
|
||||
#[cfg(feature = "tracing")]
|
||||
pub use opentelemetry_jaeger;
|
||||
#[cfg(feature = "tracing")]
|
||||
pub use tracing_opentelemetry;
|
||||
#[cfg(feature = "tracing")]
|
||||
pub use tracing_subscriber;
|
||||
#[cfg(feature = "tracing")]
|
||||
pub use tracing_tree;
|
||||
use tracing_subscriber::{filter::Directive, layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
#[derive(Debug, Default, Copy, Clone, Deserialize, PartialEq, Eq, Serialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
@@ -22,7 +15,6 @@ pub struct LoggingSettings {
|
||||
}
|
||||
|
||||
// don't call init so that we could attach additional layers
|
||||
#[cfg(feature = "basic_tracing")]
|
||||
pub fn build_tracing_logger() -> impl tracing_subscriber::layer::SubscriberExt {
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
@@ -31,7 +23,6 @@ pub fn build_tracing_logger() -> impl tracing_subscriber::layer::SubscriberExt {
|
||||
.with(default_tracing_env_filter())
|
||||
}
|
||||
|
||||
#[cfg(feature = "basic_tracing")]
|
||||
pub fn default_tracing_env_filter() -> tracing_subscriber::filter::EnvFilter {
|
||||
if ::std::env::var("RUST_LOG").is_ok() {
|
||||
tracing_subscriber::filter::EnvFilter::from_default_env()
|
||||
@@ -43,7 +34,6 @@ pub fn default_tracing_env_filter() -> tracing_subscriber::filter::EnvFilter {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "basic_tracing")]
|
||||
pub fn default_tracing_fmt_layer<S, W>(
|
||||
writer: W,
|
||||
) -> impl tracing_subscriber::Layer<S> + Sync + Send + 'static
|
||||
@@ -63,45 +53,47 @@ where
|
||||
.with_target(false)
|
||||
}
|
||||
|
||||
#[cfg(feature = "basic_tracing")]
|
||||
pub fn setup_tracing_logger() {
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
build_tracing_logger().init()
|
||||
/// Creates a tracing filter that sets more granular log levels for specific crates.
|
||||
/// This allows for finer control over logging verbosity.
|
||||
pub(crate) fn granual_filtered_env() -> Result<tracing_subscriber::filter::EnvFilter, TracingError>
|
||||
{
|
||||
fn directive_checked(directive: impl Into<String>) -> Result<Directive, TracingError> {
|
||||
directive.into().parse().map_err(From::from)
|
||||
}
|
||||
|
||||
let mut filter = default_tracing_env_filter();
|
||||
|
||||
// these crates are more granularly filtered
|
||||
let filter_crates = ["defguard_wireguard_rs"];
|
||||
for crate_name in filter_crates {
|
||||
filter = filter.add_directive(directive_checked(format!("{crate_name}=warn"))?);
|
||||
}
|
||||
Ok(filter)
|
||||
}
|
||||
|
||||
pub fn setup_no_otel_logger() -> Result<(), TracingError> {
|
||||
// Only set up if not already initialized
|
||||
if tracing::dispatcher::has_been_set() {
|
||||
// It shouldn't be - this is really checking that it is torn down between async command executions
|
||||
return Err(TracingError::TracingLoggerAlreadyInitialised);
|
||||
}
|
||||
|
||||
let registry = tracing_subscriber::registry()
|
||||
.with(default_tracing_fmt_layer(std::io::stderr))
|
||||
.with(granual_filtered_env()?);
|
||||
|
||||
registry
|
||||
.try_init()
|
||||
.map_err(|e| TracingError::TracingTryInitError(e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: This has to be a macro, running it as a function does not work for the file_appender for some reason
|
||||
#[cfg(feature = "tracing")]
|
||||
#[macro_export]
|
||||
macro_rules! setup_tracing {
|
||||
($service_name: expr) => {
|
||||
use nym_bin_common::logging::tracing_subscriber::layer::SubscriberExt;
|
||||
use nym_bin_common::logging::tracing_subscriber::util::SubscriberInitExt;
|
||||
|
||||
let registry = nym_bin_common::logging::tracing_subscriber::Registry::default()
|
||||
.with(nym_bin_common::logging::tracing_subscriber::EnvFilter::from_default_env())
|
||||
.with(
|
||||
nym_bin_common::logging::tracing_tree::HierarchicalLayer::new(4)
|
||||
.with_targets(true)
|
||||
.with_bracketed_fields(true),
|
||||
);
|
||||
|
||||
let tracer = nym_bin_common::logging::opentelemetry_jaeger::new_collector_pipeline()
|
||||
.with_endpoint("http://44.199.230.10:14268/api/traces")
|
||||
.with_service_name($service_name)
|
||||
.with_isahc()
|
||||
.with_trace_config(
|
||||
nym_bin_common::logging::opentelemetry::sdk::trace::config().with_sampler(
|
||||
nym_bin_common::logging::opentelemetry::sdk::trace::Sampler::TraceIdRatioBased(
|
||||
0.1,
|
||||
),
|
||||
),
|
||||
)
|
||||
.install_batch(nym_bin_common::logging::opentelemetry::runtime::Tokio)
|
||||
.expect("Could not init tracer");
|
||||
|
||||
let telemetry = nym_bin_common::logging::tracing_opentelemetry::layer().with_tracer(tracer);
|
||||
|
||||
registry.with(telemetry).init();
|
||||
setup_no_otel_logger()
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
use opentelemetry_sdk::trace::IdGenerator;
|
||||
use opentelemetry::trace::{TraceId, SpanId};
|
||||
use rand::RngCore;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Compact13BytesIdGenerator;
|
||||
|
||||
impl IdGenerator for Compact13BytesIdGenerator {
|
||||
fn new_trace_id(&self) -> TraceId {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut bytes = [0u8; 16];
|
||||
|
||||
// Fill the first 13 bytes with random data
|
||||
rng.fill_bytes(&mut bytes[0..12]);
|
||||
// Set the last 4 bytes to zero
|
||||
bytes[12] = 0;
|
||||
bytes[13] = 0;
|
||||
bytes[14] = 0;
|
||||
bytes[15] = 0;
|
||||
|
||||
TraceId::from_bytes(bytes)
|
||||
}
|
||||
|
||||
fn new_span_id(&self) -> SpanId {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut bytes = [0u8; 8];
|
||||
rng.fill_bytes(&mut bytes);
|
||||
|
||||
SpanId::from_bytes(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compress_trace_id(trace_id: &TraceId) -> [u8; 12] {
|
||||
let bytes = trace_id.to_bytes();
|
||||
|
||||
let mut compressed = [0u8; 12];
|
||||
compressed.copy_from_slice(&bytes[0..12]);
|
||||
|
||||
compressed
|
||||
}
|
||||
|
||||
pub fn decompress_trace_id(compressed: &[u8; 12]) -> [u8; 16] {
|
||||
let mut bytes = [0u8; 16];
|
||||
bytes[0..12].copy_from_slice(compressed);
|
||||
bytes[12..].copy_from_slice(&[0u8; 4]);
|
||||
bytes
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
use opentelemetry::{Context, TraceFlags};
|
||||
use opentelemetry::propagation::{Injector, Extractor, TextMapPropagator};
|
||||
use opentelemetry::trace::{SpanContext, TraceContextExt, TraceId};
|
||||
use opentelemetry_sdk::{propagation::TraceContextPropagator, trace::IdGenerator};
|
||||
use tracing_opentelemetry::OpenTelemetrySpanExt;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::Display;
|
||||
use tracing::instrument;
|
||||
|
||||
/// Make a Carrier for context propagation
|
||||
pub struct ContextCarrier {
|
||||
data: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl ContextCarrier {
|
||||
pub fn new_empty() -> Self {
|
||||
ContextCarrier {
|
||||
data: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_data(data: HashMap<String, String>) -> Self {
|
||||
if data.is_empty() {
|
||||
return ContextCarrier::new_empty();
|
||||
}
|
||||
|
||||
ContextCarrier { data }
|
||||
}
|
||||
|
||||
pub fn new_with_current_context(context: Context) -> Self {
|
||||
let mut carrier = ContextCarrier::new_empty();
|
||||
let propagator = TraceContextPropagator::new();
|
||||
propagator.inject_context(&context, &mut carrier);
|
||||
carrier
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&String, &String)> {
|
||||
self.data.iter()
|
||||
}
|
||||
|
||||
pub fn from_map(data: HashMap<String, String>) -> Self {
|
||||
ContextCarrier { data }
|
||||
}
|
||||
|
||||
pub fn into_map(self) -> HashMap<String, String> {
|
||||
self.data
|
||||
}
|
||||
|
||||
pub fn extract_trace_id(&self) -> Option<TraceId> {
|
||||
self.get("traceparent").and_then(|tp| {
|
||||
let parts: Vec<&str> = tp.split('-').collect();
|
||||
if parts.len() == 4 {
|
||||
TraceId::from_hex(parts[1]).ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn extract_trace_id_into_bytes(&self) -> Option<[u8; 16]> {
|
||||
self.extract_trace_id().map(|id| id.to_bytes())
|
||||
}
|
||||
|
||||
pub fn extract_traceparent(&self) -> Option<String> {
|
||||
self.get("traceparent").map(|s| s.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl Injector for ContextCarrier {
|
||||
fn set(&mut self, key: &str, value: String) {
|
||||
self.data.insert(key.to_string(), value);
|
||||
}
|
||||
}
|
||||
|
||||
impl Extractor for ContextCarrier {
|
||||
fn get(&self, key: &str) -> Option<&str> {
|
||||
self.data.get(key).map(|s| s.as_str())
|
||||
}
|
||||
|
||||
fn keys(&self) -> Vec<&str> {
|
||||
self.data.keys().map(|k| k.as_str()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ContextCarrier {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{:?}", self.data)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ManualContextPropagator {
|
||||
pub root_span: tracing::Span,
|
||||
pub trace_id: TraceId,
|
||||
}
|
||||
|
||||
impl ManualContextPropagator {
|
||||
#[instrument(skip_all, level = "debug")]
|
||||
pub fn new(name: &str, context: HashMap<String, String>) -> Self {
|
||||
let carrier = ContextCarrier::new_with_data(context);
|
||||
let trace_id = match carrier.extract_trace_id() {
|
||||
Some(id) => id,
|
||||
None => Context::current().span().span_context().trace_id(),
|
||||
};
|
||||
|
||||
let root_span_builder = new_span_context_with_id(trace_id.clone());
|
||||
|
||||
let root_span = tracing::info_span!("trace_root", name = %name, trace_id = %trace_id);
|
||||
root_span.set_parent(root_span_builder);
|
||||
|
||||
ManualContextPropagator {
|
||||
root_span,
|
||||
trace_id,
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip_all, level = "debug")]
|
||||
pub fn new_from_tid(name: &str, trace_id: TraceId) -> Self {
|
||||
let root_span_builder = new_span_context_with_id(trace_id.clone());
|
||||
|
||||
let root_span = tracing::info_span!("trace_root", name = %name, trace_id = %trace_id);
|
||||
root_span.set_parent(root_span_builder);
|
||||
|
||||
ManualContextPropagator {
|
||||
root_span,
|
||||
trace_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn root_span(&self) -> &tracing::Span {
|
||||
&self.root_span
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[instrument(skip_all, level = "debug")]
|
||||
pub fn new_span_context_with_id(trace_id: TraceId) -> Context {
|
||||
let id_gen = opentelemetry_sdk::trace::RandomIdGenerator::default();
|
||||
let span_id = id_gen.new_span_id();
|
||||
let span_context = SpanContext::new(
|
||||
trace_id,
|
||||
span_id,
|
||||
TraceFlags::SAMPLED,
|
||||
true,
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
Context::current().with_remote_span_context(span_context)
|
||||
}
|
||||
|
||||
#[instrument(skip_all, level = "debug")]
|
||||
pub fn extract_trace_id_from_tracing_cx() -> TraceId {
|
||||
let cx = tracing::Span::current().context();
|
||||
let binding = cx.span();
|
||||
let trace_id = binding.span_context().trace_id();
|
||||
trace_id
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
pub mod context;
|
||||
pub mod compact_id_generator;
|
||||
mod trace_id_format;
|
||||
|
||||
use tracing::{info, Level};
|
||||
use tracing_subscriber::filter::Directive;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::util::SubscriberInitExt;
|
||||
use tracing_subscriber::fmt;
|
||||
|
||||
use crate::logging::default_tracing_env_filter;
|
||||
use crate::logging::error::TracingError;
|
||||
use crate::opentelemetry::compact_id_generator::Compact13BytesIdGenerator;
|
||||
use opentelemetry::trace::TracerProvider;
|
||||
use opentelemetry::{global, KeyValue};
|
||||
use opentelemetry_otlp::tonic_types::metadata::MetadataMap;
|
||||
use opentelemetry_otlp::tonic_types::transport::ClientTlsConfig;
|
||||
use opentelemetry_otlp::{WithExportConfig, WithTonicConfig};
|
||||
use opentelemetry_sdk::metrics::{MeterProviderBuilder, PeriodicReader, SdkMeterProvider};
|
||||
use opentelemetry_sdk::trace::SdkTracerProvider;
|
||||
use opentelemetry_sdk::{trace::Sampler, Resource};
|
||||
use opentelemetry_semantic_conventions::resource::{DEPLOYMENT_ENVIRONMENT_NAME, SERVICE_VERSION};
|
||||
use opentelemetry_semantic_conventions::SCHEMA_URL;
|
||||
use tracing_opentelemetry::{MetricsLayer, OpenTelemetryLayer};
|
||||
use tracing_subscriber::fmt::format::FmtSpan;
|
||||
|
||||
pub struct TracerProviderGuard(Option<SdkTracerProvider>);
|
||||
|
||||
impl Drop for TracerProviderGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Some(tracer_provider) = self.0.take() {
|
||||
// Ensure all spans are flushed before exit
|
||||
if let Err(e) = tracer_provider.shutdown() {
|
||||
eprintln!("Error shutting down tracer provider: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn granual_filtered_env() -> Result<tracing_subscriber::filter::EnvFilter, TracingError>
|
||||
{
|
||||
fn directive_checked(directive: impl Into<String>) -> Result<Directive, TracingError> {
|
||||
directive.into().parse().map_err(From::from)
|
||||
}
|
||||
|
||||
let mut filter = default_tracing_env_filter();
|
||||
|
||||
// these crates are more granularly filtered
|
||||
let filter_crates = ["defguard_wireguard_rs"];
|
||||
for crate_name in filter_crates {
|
||||
filter = filter.add_directive(directive_checked(format!("{crate_name}=warn"))?);
|
||||
}
|
||||
Ok(filter)
|
||||
}
|
||||
|
||||
pub fn setup_tracing_logger(service_name: String) -> Result<TracerProviderGuard, TracingError> {
|
||||
if tracing::dispatcher::has_been_set() {
|
||||
// It shouldn't be - this is really checking that it is torn down between async command executions
|
||||
return Err(TracingError::TracingLoggerAlreadyInitialised);
|
||||
}
|
||||
|
||||
// define ingestion points
|
||||
let endpoint = std::env::var("SIGNOZ_ENDPOINT").expect("SIGNOZ_ENDPOINT not set");
|
||||
let key = std::env::var("SIGNOZ_INGESTION_KEY").expect("SIGNOZ_INGESTION_KEY not set");
|
||||
let mut metadata = MetadataMap::new();
|
||||
metadata.insert(
|
||||
"signoz-ingestion-key",
|
||||
key.parse().expect("Could not parse signoz ingestion key"),
|
||||
);
|
||||
|
||||
// Build resources
|
||||
let resource = build_resource(&service_name);
|
||||
|
||||
// Initialize tracer and meter providers
|
||||
let tracer_provider = init_tracer_provider(&endpoint, metadata.clone(), resource.clone())?;
|
||||
let meter_provider = init_meter_provider(&endpoint, metadata.clone(), resource.clone())?;
|
||||
|
||||
// Bridge tracing and opentelemetry
|
||||
let tracer = tracer_provider.tracer("otel-subscriber");
|
||||
let fmt_layer = fmt::layer()
|
||||
.json()
|
||||
.with_writer(std::io::stderr)
|
||||
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
|
||||
.with_span_list(false)
|
||||
.with_current_span(true)
|
||||
.event_format(trace_id_format::TraceIdFormat);
|
||||
|
||||
let registry = tracing_subscriber::registry()
|
||||
.with(fmt_layer)
|
||||
.with(granual_filtered_env()?)
|
||||
.with(tracing_subscriber::filter::LevelFilter::from_level(Level::INFO))
|
||||
.with(MetricsLayer::new(meter_provider.clone()))
|
||||
.with(OpenTelemetryLayer::new(tracer));
|
||||
|
||||
registry.try_init().map_err(TracingError::TracingTryInitError)?;
|
||||
|
||||
global::set_tracer_provider(tracer_provider.clone());
|
||||
global::set_meter_provider(meter_provider.clone());
|
||||
|
||||
info!("Tracing initialized with service name: {}", service_name);
|
||||
|
||||
Ok(TracerProviderGuard(Some(tracer_provider)))
|
||||
}
|
||||
|
||||
fn build_resource(service_name: &str) -> Resource {
|
||||
Resource::builder()
|
||||
.with_service_name(service_name.to_string())
|
||||
.with_schema_url(
|
||||
[
|
||||
KeyValue::new(SERVICE_VERSION, env!("CARGO_PKG_VERSION")),
|
||||
KeyValue::new(DEPLOYMENT_ENVIRONMENT_NAME, "develop"),
|
||||
],
|
||||
SCHEMA_URL,
|
||||
)
|
||||
.build()
|
||||
}
|
||||
|
||||
fn init_tracer_provider(
|
||||
endpoint: &str,
|
||||
metadata: MetadataMap,
|
||||
resource: Resource,
|
||||
) -> Result<SdkTracerProvider, TracingError> {
|
||||
let mut exporter_builder = opentelemetry_otlp::SpanExporter::builder()
|
||||
.with_tonic()
|
||||
.with_metadata(metadata)
|
||||
.with_endpoint(endpoint);
|
||||
|
||||
if endpoint.starts_with("https://") {
|
||||
exporter_builder =
|
||||
exporter_builder.with_tls_config(ClientTlsConfig::new().with_enabled_roots());
|
||||
}
|
||||
|
||||
let exporter = exporter_builder.build()?;
|
||||
|
||||
let tracer = SdkTracerProvider::builder()
|
||||
.with_sampler(Sampler::ParentBased(Box::new(Sampler::TraceIdRatioBased(
|
||||
1.0,
|
||||
))))
|
||||
.with_id_generator(Compact13BytesIdGenerator)
|
||||
.with_resource(resource)
|
||||
.with_batch_exporter(exporter)
|
||||
.build();
|
||||
|
||||
global::set_tracer_provider(tracer.clone());
|
||||
Ok(tracer)
|
||||
}
|
||||
|
||||
fn init_meter_provider(
|
||||
endpoint: &str,
|
||||
metadata: MetadataMap,
|
||||
resource: Resource,
|
||||
) -> Result<SdkMeterProvider, TracingError> {
|
||||
let mut exporter_builder = opentelemetry_otlp::MetricExporter::builder()
|
||||
.with_tonic()
|
||||
.with_metadata(metadata)
|
||||
.with_endpoint(endpoint)
|
||||
.with_temporality(opentelemetry_sdk::metrics::Temporality::default());
|
||||
|
||||
if endpoint.starts_with("https://") {
|
||||
exporter_builder = exporter_builder.with_tls_config(ClientTlsConfig::new().with_enabled_roots());
|
||||
}
|
||||
|
||||
let exporter = exporter_builder.build()?;
|
||||
|
||||
let reader = PeriodicReader::builder(exporter)
|
||||
.with_interval(std::time::Duration::from_secs(30))
|
||||
.build();
|
||||
|
||||
let stdout_reader =
|
||||
PeriodicReader::builder(opentelemetry_stdout::MetricExporter::default()).build();
|
||||
|
||||
let meter_provider = MeterProviderBuilder::default()
|
||||
.with_resource(resource)
|
||||
.with_reader(reader)
|
||||
.with_reader(stdout_reader)
|
||||
.build();
|
||||
|
||||
global::set_meter_provider(meter_provider.clone());
|
||||
|
||||
Ok(meter_provider)
|
||||
}
|
||||
|
||||
// pub fn setup_tracing_logger(service_name: String) -> Result<(), TracingError> {
|
||||
// if tracing::dispatcher::has_been_set() {
|
||||
// // It shouldn't be - this is really checking that it is torn down between async command executions
|
||||
// return Err(TracingError::TracingLoggerAlreadyInitialised);
|
||||
// }
|
||||
|
||||
// let key =
|
||||
// std::env::var("SIGNOZ_INGESTION_KEY".to_string()).expect("SIGNOZ_INGESTION_KEY not set");
|
||||
// let mut metadata = MetadataMap::new();
|
||||
// metadata.insert(
|
||||
// "signoz-ingestion-key",
|
||||
// key.parse().expect("Could not parse signoz ingestion key"),
|
||||
// );
|
||||
|
||||
// let tracer_provider = init_tracer_provider(metadata.clone(), service_name.clone())?;
|
||||
// let meter_provider = init_meter_provider(metadata.clone(), service_name.clone())?;
|
||||
// let tracer = tracer_provider.tracer("tracing-otel-subscriber");
|
||||
// let fmt_layer = fmt::layer()
|
||||
// .json()
|
||||
// .with_writer(std::io::stderr)
|
||||
// .with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
|
||||
// .with_span_list(false)
|
||||
// .with_current_span(true)
|
||||
// .event_format(trace_id_format::TraceIdFormat);
|
||||
|
||||
// cfg_if::cfg_if! {if #[cfg(feature = "tokio-console")] {
|
||||
// // instrument tokio console subscriber needs RUSTFLAGS="--cfg tokio_unstable" at build time
|
||||
// let console_layer = console_subscriber::spawn();
|
||||
|
||||
// tracing_subscriber::registry()
|
||||
// .with(console_layer)
|
||||
// .with(fmt_layer)
|
||||
// .with(granual_filtered_env()?)
|
||||
// .with(tracing_subscriber::filter::LevelFilter::from_level(Level::INFO))
|
||||
// .with(MetricsLayer::new(meter_provider))
|
||||
// .with(OpenTelemetryLayer::new(tracer))
|
||||
// .try_init()
|
||||
// .map_err(|e| TracingError::TracingTryInitError(e))?;
|
||||
// } else {
|
||||
// tracing_subscriber::registry()
|
||||
// .with(fmt_layer)
|
||||
// .with(granual_filtered_env()?)
|
||||
// .with(tracing_subscriber::filter::LevelFilter::from_level(Level::INFO))
|
||||
// .with(MetricsLayer::new(meter_provider))
|
||||
// .with(OpenTelemetryLayer::new(tracer))
|
||||
// .try_init()
|
||||
// .map_err(|e| TracingError::TracingTryInitError(e))?;
|
||||
// }}
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
// fn resource(service_name: String) -> Resource {
|
||||
// Resource::builder()
|
||||
// .with_service_name(service_name)
|
||||
// .with_schema_url(
|
||||
// [
|
||||
// KeyValue::new(SERVICE_VERSION, env!("CARGO_PKG_VERSION")),
|
||||
// KeyValue::new(DEPLOYMENT_ENVIRONMENT_NAME, "develop"),
|
||||
// ],
|
||||
// SCHEMA_URL,
|
||||
// )
|
||||
// .build()
|
||||
// }
|
||||
|
||||
// fn init_tracer_provider(metadata: MetadataMap, service_name: String) -> Result<SdkTracerProvider, TracingError> {
|
||||
// let endpoint = std::env::var("SIGNOZ_ENDPOINT".to_string()).expect("SIGNOZ_ENDPOINT not set");
|
||||
// info!("SIGNOZ_ENDPOINT = {}", endpoint);
|
||||
|
||||
// let mut exporter_builder = opentelemetry_otlp::SpanExporter::builder()
|
||||
// .with_tonic()
|
||||
// .with_metadata(metadata)
|
||||
// .with_endpoint(&endpoint);
|
||||
|
||||
// if endpoint.starts_with("https://") {
|
||||
// exporter_builder =
|
||||
// exporter_builder.with_tls_config(ClientTlsConfig::new().with_enabled_roots());
|
||||
// }
|
||||
|
||||
// let exporter = exporter_builder.build()?;
|
||||
|
||||
// let tracer = SdkTracerProvider::builder()
|
||||
// .with_sampler(Sampler::ParentBased(Box::new(Sampler::TraceIdRatioBased(
|
||||
// 1.0,
|
||||
// ))))
|
||||
// .with_id_generator(Compact13BytesIdGenerator)
|
||||
// .with_resource(resource(service_name))
|
||||
// .with_batch_exporter(exporter)
|
||||
// .build();
|
||||
|
||||
// global::set_tracer_provider(tracer.clone());
|
||||
// Ok(tracer)
|
||||
// }
|
||||
|
||||
// fn init_meter_provider(metadata: MetadataMap, service_name: String) -> Result<SdkMeterProvider, TracingError> {
|
||||
// let endpoint = std::env::var("SIGNOZ_ENDPOINT".to_string()).expect("SIGNOZ_ENDPOINT not set");
|
||||
|
||||
// let mut exporter_builder = opentelemetry_otlp::MetricExporter::builder()
|
||||
// .with_tonic()
|
||||
// .with_metadata(metadata)
|
||||
// .with_endpoint(&endpoint)
|
||||
// .with_temporality(opentelemetry_sdk::metrics::Temporality::default());
|
||||
|
||||
// if endpoint.starts_with("https://") {
|
||||
// exporter_builder = exporter_builder.with_tls_config(ClientTlsConfig::new().with_enabled_roots());
|
||||
// }
|
||||
|
||||
// let exporter = exporter_builder.build()?;
|
||||
|
||||
// let reader = PeriodicReader::builder(exporter)
|
||||
// .with_interval(std::time::Duration::from_secs(30))
|
||||
// .build();
|
||||
|
||||
// let stdout_reader =
|
||||
// PeriodicReader::builder(opentelemetry_stdout::MetricExporter::default()).build();
|
||||
|
||||
// let meter_provider = MeterProviderBuilder::default()
|
||||
// .with_resource(resource(service_name))
|
||||
// .with_reader(reader)
|
||||
// .with_reader(stdout_reader)
|
||||
// .build();
|
||||
|
||||
// global::set_meter_provider(meter_provider.clone());
|
||||
|
||||
// Ok(meter_provider)
|
||||
// }
|
||||
@@ -0,0 +1,88 @@
|
||||
use chrono::Utc;
|
||||
use opentelemetry::trace::TraceContextExt;
|
||||
use opentelemetry::{SpanId, TraceId};
|
||||
use serde::ser::{SerializeMap, Serializer as _};
|
||||
use std::io;
|
||||
use tracing::{Event, Subscriber};
|
||||
use tracing_opentelemetry::OpenTelemetrySpanExt;
|
||||
use tracing_serde::fields::AsMap;
|
||||
use tracing_serde::AsSerde;
|
||||
use tracing_subscriber::fmt::format::Writer;
|
||||
use tracing_subscriber::fmt::{FmtContext, FormatEvent, FormatFields};
|
||||
use tracing_subscriber::registry::LookupSpan;
|
||||
|
||||
pub struct WriteAdaptor<'a> {
|
||||
fmt_write: &'a mut dyn std::fmt::Write,
|
||||
}
|
||||
|
||||
impl<'a> WriteAdaptor<'a> {
|
||||
pub fn new(fmt_write: &'a mut dyn std::fmt::Write) -> Self {
|
||||
Self { fmt_write }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> io::Write for WriteAdaptor<'a> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
let s =
|
||||
std::str::from_utf8(buf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
|
||||
|
||||
self.fmt_write
|
||||
.write_str(s)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
|
||||
Ok(s.as_bytes().len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TraceIdFormat;
|
||||
|
||||
impl<S, N> FormatEvent<S, N> for TraceIdFormat
|
||||
where
|
||||
S: Subscriber + for<'lookup> LookupSpan<'lookup>,
|
||||
N: for<'writer> FormatFields<'writer> + 'static,
|
||||
{
|
||||
fn format_event(
|
||||
&self,
|
||||
_ctx: &FmtContext<'_, S, N>,
|
||||
mut writer: Writer<'_>,
|
||||
event: &Event<'_>,
|
||||
) -> std::fmt::Result
|
||||
where
|
||||
S: Subscriber + for<'a> LookupSpan<'a>,
|
||||
{
|
||||
let meta = event.metadata();
|
||||
|
||||
let mut visit = || {
|
||||
let mut serializer = serde_json::Serializer::new(WriteAdaptor::new(&mut writer));
|
||||
let mut serializer = serializer.serialize_map(None)?;
|
||||
serializer.serialize_entry("timestamp", &Utc::now().to_rfc3339())?;
|
||||
serializer.serialize_entry("level", &meta.level().as_serde())?;
|
||||
serializer.serialize_entry("fields", &event.field_map())?;
|
||||
serializer.serialize_entry("target", meta.target())?;
|
||||
|
||||
let current_span = tracing::Span::current();
|
||||
let context = current_span.context();
|
||||
let span_ref = context.span();
|
||||
let span_context = span_ref.span_context();
|
||||
|
||||
let trace_id = span_context.trace_id();
|
||||
if trace_id != TraceId::INVALID {
|
||||
serializer.serialize_entry("trace_id", &trace_id.to_string())?;
|
||||
|
||||
let span_id = span_context.span_id();
|
||||
if span_id != SpanId::INVALID {
|
||||
serializer.serialize_entry("span_id", &span_id.to_string())?;
|
||||
}
|
||||
}
|
||||
|
||||
serializer.end()
|
||||
};
|
||||
|
||||
visit().map_err(|_| std::fmt::Error)?;
|
||||
writeln!(writer)
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,7 @@ nym-client-core-config-types = { path = "./config-types", features = [
|
||||
nym-client-core-surb-storage = { path = "./surb-storage" }
|
||||
nym-client-core-gateways-storage = { path = "./gateways-storage" }
|
||||
nym-ecash-time = { path = "../ecash-time" }
|
||||
nym-mixnet-contract-common = { path = "../cosmwasm-smart-contracts/mixnet-contract" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
|
||||
nym-mixnet-client = { path = "../client-libs/mixnet-client", default-features = false }
|
||||
@@ -122,6 +123,7 @@ cli = ["clap", "comfy-table"]
|
||||
fs-credentials-storage = ["nym-credential-storage/persistent-storage"]
|
||||
fs-surb-storage = ["nym-client-core-surb-storage/fs-surb-storage"]
|
||||
fs-gateways-storage = ["nym-client-core-gateways-storage/fs-gateways-storage"]
|
||||
otel = ["nym-sphinx/otel"]
|
||||
wasm = ["nym-gateway-client/wasm"]
|
||||
metrics-server = []
|
||||
|
||||
|
||||
@@ -27,13 +27,13 @@ use crate::client::topology_control::nym_api_provider::NymApiTopologyProvider;
|
||||
use crate::client::topology_control::{
|
||||
TopologyAccessor, TopologyRefresher, TopologyRefresherConfig,
|
||||
};
|
||||
use crate::config;
|
||||
use crate::config::{Config, DebugConfig};
|
||||
use crate::error::ClientCoreError;
|
||||
use crate::init::{
|
||||
setup_gateway,
|
||||
types::{GatewaySetup, InitialisationResult},
|
||||
};
|
||||
use crate::{config, spawn_future};
|
||||
use futures::channel::mpsc;
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_client_core_config_types::{ForgetMe, RememberMe};
|
||||
@@ -48,16 +48,15 @@ use nym_gateway_client::{
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::addressing::nodes::NodeIdentity;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_sphinx::receiver::{ReconstructedMessage, SphinxMessageReceiver};
|
||||
use nym_statistics_common::clients::ClientStatsSender;
|
||||
use nym_statistics_common::generate_client_stats_id;
|
||||
use nym_task::connections::{ConnectionCommandReceiver, ConnectionCommandSender, LaneQueueLengths};
|
||||
use nym_task::{TaskClient, TaskHandle};
|
||||
use nym_task::ShutdownTracker;
|
||||
use nym_topology::provider_trait::TopologyProvider;
|
||||
use nym_topology::HardcodedTopologyProvider;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::{nyxd::contract_traits::DkgQueryClient, NymApiClient, UserAgent};
|
||||
use nym_validator_client::{nyxd::contract_traits::DkgQueryClient, UserAgent};
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::thread_rng;
|
||||
@@ -95,12 +94,12 @@ impl ClientInput {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ClientOutput {
|
||||
pub received_buffer_request_sender: ReceivedBufferRequestSender,
|
||||
}
|
||||
|
||||
impl ClientOutput {
|
||||
#[instrument(name = "ClientOutput::register_receiver", skip_all)]
|
||||
pub fn register_receiver(
|
||||
&mut self,
|
||||
) -> Result<mpsc::UnboundedReceiver<Vec<ReconstructedMessage>>, ClientCoreError> {
|
||||
@@ -195,7 +194,7 @@ pub struct BaseClientBuilder<C, S: MixnetClientStorage> {
|
||||
wait_for_gateway: bool,
|
||||
custom_topology_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send>>,
|
||||
shutdown: Option<TaskClient>,
|
||||
shutdown: Option<ShutdownTracker>,
|
||||
user_agent: Option<UserAgent>,
|
||||
|
||||
setup_method: GatewaySetup,
|
||||
@@ -281,7 +280,7 @@ where
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_shutdown(mut self, shutdown: TaskClient) -> Self {
|
||||
pub fn with_shutdown(mut self, shutdown: ShutdownTracker) -> Self {
|
||||
self.shutdown = Some(shutdown);
|
||||
self
|
||||
}
|
||||
@@ -325,11 +324,11 @@ where
|
||||
topology_accessor: TopologyAccessor,
|
||||
mix_tx: BatchMixMessageSender,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
shutdown_tracker: &ShutdownTracker,
|
||||
) {
|
||||
info!("Starting loop cover traffic stream...");
|
||||
|
||||
let stream = LoopCoverTrafficStream::new(
|
||||
let mut stream = LoopCoverTrafficStream::new(
|
||||
ack_key,
|
||||
debug_config.acknowledgements.average_ack_delay,
|
||||
mix_tx,
|
||||
@@ -338,10 +337,9 @@ where
|
||||
debug_config.traffic,
|
||||
debug_config.cover_traffic,
|
||||
stats_tx,
|
||||
task_client,
|
||||
);
|
||||
|
||||
stream.start();
|
||||
shutdown_tracker
|
||||
.try_spawn_named_with_shutdown(async move { stream.run().await }, "CoverTrafficStream");
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -357,13 +355,12 @@ where
|
||||
reply_controller_receiver: ReplyControllerReceiver,
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
client_connection_rx: ConnectionCommandReceiver,
|
||||
task_client: TaskClient,
|
||||
packet_type: PacketType,
|
||||
stats_tx: ClientStatsSender,
|
||||
shutdown_tracker: &ShutdownTracker,
|
||||
) {
|
||||
info!("Starting real traffic stream...");
|
||||
|
||||
RealMessagesController::new(
|
||||
let real_messages_controller = RealMessagesController::new(
|
||||
controller_config,
|
||||
key_rotation_config,
|
||||
ack_receiver,
|
||||
@@ -376,37 +373,101 @@ where
|
||||
lane_queue_lengths,
|
||||
client_connection_rx,
|
||||
stats_tx,
|
||||
task_client,
|
||||
)
|
||||
.start(packet_type);
|
||||
shutdown_tracker.clone_shutdown_token(),
|
||||
);
|
||||
|
||||
// break out all the subtasks
|
||||
let (mut out_queue_control, mut reply_controller, ack_controller) =
|
||||
real_messages_controller.into_tasks();
|
||||
let (
|
||||
mut ack_listener,
|
||||
mut input_listener,
|
||||
mut retransmission_listener,
|
||||
mut sent_notification_listener,
|
||||
mut ack_action_controller,
|
||||
) = ack_controller.into_tasks();
|
||||
|
||||
shutdown_tracker.try_spawn_named(
|
||||
async move { out_queue_control.run().await },
|
||||
"RealMessagesController::OutQueueControl",
|
||||
);
|
||||
|
||||
let shutdown_token = shutdown_tracker.clone_shutdown_token();
|
||||
shutdown_tracker.try_spawn_named(
|
||||
async move { reply_controller.run(shutdown_token).await },
|
||||
"RealMessagesController::ReplyController",
|
||||
);
|
||||
|
||||
let shutdown_token = shutdown_tracker.clone_shutdown_token();
|
||||
shutdown_tracker.try_spawn_named(
|
||||
async move { ack_listener.run(shutdown_token).await },
|
||||
"AcknowledgementController::AcknowledgementListener",
|
||||
);
|
||||
|
||||
let shutdown_token = shutdown_tracker.clone_shutdown_token();
|
||||
shutdown_tracker.try_spawn_named(
|
||||
async move { input_listener.run(shutdown_token).await },
|
||||
"AcknowledgementController::InputMessageListener",
|
||||
);
|
||||
|
||||
let shutdown_token = shutdown_tracker.clone_shutdown_token();
|
||||
shutdown_tracker.try_spawn_named(
|
||||
async move { retransmission_listener.run(shutdown_token).await },
|
||||
"AcknowledgementController::RetransmissionRequestListener",
|
||||
);
|
||||
|
||||
shutdown_tracker.try_spawn_named_with_shutdown(
|
||||
async move {
|
||||
sent_notification_listener.run().await;
|
||||
},
|
||||
"AcknowledgementController::SentNotificationListener",
|
||||
);
|
||||
|
||||
let shutdown_token = shutdown_tracker.clone_shutdown_token();
|
||||
shutdown_tracker.try_spawn_named(
|
||||
async move { ack_action_controller.run(shutdown_token).await },
|
||||
"AcknowledgementController::ActionController",
|
||||
);
|
||||
|
||||
// .start(packet_type);
|
||||
}
|
||||
|
||||
// buffer controlling all messages fetched from provider
|
||||
// required so that other components would be able to use them (say the websocket)
|
||||
#[instrument(skip_all)]
|
||||
fn start_received_messages_buffer_controller(
|
||||
local_encryption_keypair: Arc<x25519::KeyPair>,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
mixnet_receiver: MixnetMessageReceiver,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
shutdown: TaskClient,
|
||||
metrics_reporter: ClientStatsSender,
|
||||
shutdown_tracker: &ShutdownTracker,
|
||||
) {
|
||||
info!("Starting received messages buffer controller...");
|
||||
let controller: ReceivedMessagesBufferController<SphinxMessageReceiver> =
|
||||
ReceivedMessagesBufferController::new(
|
||||
local_encryption_keypair,
|
||||
query_receiver,
|
||||
mixnet_receiver,
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
metrics_reporter,
|
||||
shutdown,
|
||||
);
|
||||
controller.start()
|
||||
let controller = ReceivedMessagesBufferController::<SphinxMessageReceiver>::new(
|
||||
local_encryption_keypair,
|
||||
query_receiver,
|
||||
mixnet_receiver,
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
metrics_reporter,
|
||||
shutdown_tracker.clone_shutdown_token(),
|
||||
);
|
||||
let (mut msg_receiver, mut req_receiver) = controller.into_tasks();
|
||||
|
||||
shutdown_tracker.try_spawn_named(
|
||||
async move { msg_receiver.run().await },
|
||||
"ReceivedMessagesBufferController::FragmentedMessageReceiver",
|
||||
);
|
||||
shutdown_tracker.try_spawn_named(
|
||||
async move { req_receiver.run().await },
|
||||
"ReceivedMessagesBufferController::RequestReceiver",
|
||||
);
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[instrument(skip_all)]
|
||||
async fn start_gateway_client(
|
||||
config: &Config,
|
||||
initialisation_result: InitialisationResult,
|
||||
@@ -415,7 +476,7 @@ where
|
||||
packet_router: PacketRouter,
|
||||
stats_reporter: ClientStatsSender,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
shutdown: TaskClient,
|
||||
shutdown_tracker: &ShutdownTracker,
|
||||
) -> Result<GatewayClient<C, S::CredentialStore>, ClientCoreError>
|
||||
where
|
||||
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
|
||||
@@ -434,7 +495,7 @@ where
|
||||
packet_router,
|
||||
bandwidth_controller,
|
||||
stats_reporter,
|
||||
shutdown,
|
||||
shutdown_tracker.clone_shutdown_token(),
|
||||
)
|
||||
} else {
|
||||
let cfg = GatewayConfig::new(
|
||||
@@ -459,7 +520,7 @@ where
|
||||
stats_reporter,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
shutdown,
|
||||
shutdown_tracker.clone_shutdown_token(),
|
||||
)
|
||||
};
|
||||
|
||||
@@ -513,6 +574,7 @@ where
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[instrument(skip_all)]
|
||||
async fn setup_gateway_transceiver(
|
||||
custom_gateway_transceiver: Option<Box<dyn GatewayTransceiver + Send>>,
|
||||
config: &Config,
|
||||
@@ -522,7 +584,7 @@ where
|
||||
packet_router: PacketRouter,
|
||||
stats_reporter: ClientStatsSender,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
mut shutdown: TaskClient,
|
||||
shutdown_tracker: &ShutdownTracker,
|
||||
) -> Result<Box<dyn GatewayTransceiver + Send>, ClientCoreError>
|
||||
where
|
||||
<S::KeyStore as KeyStore>::StorageError: Send + Sync + 'static,
|
||||
@@ -539,7 +601,6 @@ where
|
||||
Err(ClientCoreError::CustomGatewaySelectionExpected)
|
||||
} else {
|
||||
// and make sure to invalidate the task client, so we wouldn't cause premature shutdown
|
||||
shutdown.disarm();
|
||||
custom_gateway_transceiver.set_packet_router(packet_router)?;
|
||||
Ok(custom_gateway_transceiver)
|
||||
};
|
||||
@@ -555,7 +616,7 @@ where
|
||||
stats_reporter,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
shutdown,
|
||||
shutdown_tracker,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -566,7 +627,7 @@ where
|
||||
custom_provider: Option<Box<dyn TopologyProvider + Send + Sync>>,
|
||||
config_topology: config::Topology,
|
||||
nym_api_urls: Vec<Url>,
|
||||
nym_api_client: NymApiClient,
|
||||
nym_api_client: nym_http_api_client::Client,
|
||||
) -> Box<dyn TopologyProvider + Send + Sync> {
|
||||
// if no custom provider was ... provided ..., create one using nym-api
|
||||
custom_provider.unwrap_or_else(|| {
|
||||
@@ -586,22 +647,20 @@ where
|
||||
topology_accessor: TopologyAccessor,
|
||||
local_gateway: NodeIdentity,
|
||||
wait_for_gateway: bool,
|
||||
mut task_client: TaskClient,
|
||||
shutdown_tracker: &ShutdownTracker,
|
||||
) -> Result<(), ClientCoreError> {
|
||||
let topology_refresher_config =
|
||||
TopologyRefresherConfig::new(topology_config.topology_refresh_rate);
|
||||
|
||||
if topology_config.disable_refreshing {
|
||||
// if we're not spawning the refresher, don't cause shutdown immediately
|
||||
info!("The background topology refesher is not going to be started");
|
||||
task_client.disarm();
|
||||
info!("The background topology refresher is not going to be started");
|
||||
}
|
||||
|
||||
let mut topology_refresher = TopologyRefresher::new(
|
||||
topology_refresher_config,
|
||||
topology_accessor,
|
||||
topology_provider,
|
||||
task_client,
|
||||
);
|
||||
// before returning, block entire runtime to refresh the current network view so that any
|
||||
// components depending on topology would see a non-empty view
|
||||
@@ -646,7 +705,10 @@ where
|
||||
// don't spawn the refresher if we don't want to be refreshing the topology.
|
||||
// only use the initial values obtained
|
||||
info!("Starting topology refresher...");
|
||||
topology_refresher.start();
|
||||
shutdown_tracker.try_spawn_named_with_shutdown(
|
||||
async move { topology_refresher.run().await },
|
||||
"TopologyRefresher",
|
||||
);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -657,7 +719,7 @@ where
|
||||
user_agent: Option<UserAgent>,
|
||||
client_stats_id: String,
|
||||
input_sender: Sender<InputMessage>,
|
||||
task_client: TaskClient,
|
||||
shutdown_tracker: &ShutdownTracker,
|
||||
) -> ClientStatsSender {
|
||||
info!("Starting statistics control...");
|
||||
StatisticsControl::create_and_start(
|
||||
@@ -667,18 +729,23 @@ where
|
||||
.unwrap_or("unknown".to_string()),
|
||||
client_stats_id,
|
||||
input_sender.clone(),
|
||||
task_client,
|
||||
shutdown_tracker,
|
||||
)
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
fn start_mix_traffic_controller(
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
shutdown: TaskClient,
|
||||
shutdown_tracker: &ShutdownTracker,
|
||||
) -> (BatchMixMessageSender, ClientRequestSender) {
|
||||
info!("Starting mix traffic controller...");
|
||||
let (mix_traffic_controller, mix_tx, client_tx) =
|
||||
MixTrafficController::new(gateway_transceiver, shutdown);
|
||||
mix_traffic_controller.start();
|
||||
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 },
|
||||
"MixTrafficController",
|
||||
);
|
||||
|
||||
(mix_tx, client_tx)
|
||||
}
|
||||
|
||||
@@ -686,7 +753,7 @@ where
|
||||
async fn setup_persistent_reply_storage(
|
||||
backend: S::ReplyStore,
|
||||
key_rotation_config: KeyRotationConfig,
|
||||
shutdown: TaskClient,
|
||||
shutdown_tracker: &ShutdownTracker,
|
||||
) -> Result<CombinedReplyStorage, ClientCoreError>
|
||||
where
|
||||
<S::ReplyStore as ReplyStorageBackend>::StorageError: Sync + Send,
|
||||
@@ -711,13 +778,14 @@ where
|
||||
})?;
|
||||
|
||||
let store_clone = mem_store.clone();
|
||||
spawn_future!(
|
||||
let shutdown_token = shutdown_tracker.clone_shutdown_token();
|
||||
shutdown_tracker.try_spawn_named(
|
||||
async move {
|
||||
persistent_storage
|
||||
.flush_on_shutdown(store_clone, shutdown)
|
||||
.flush_on_shutdown(store_clone, shutdown_token)
|
||||
.await
|
||||
},
|
||||
"PersistentReplyStorage::flush_on_shutdown"
|
||||
"PersistentReplyStorage::flush_on_shutdown",
|
||||
);
|
||||
|
||||
Ok(mem_store)
|
||||
@@ -749,23 +817,32 @@ where
|
||||
setup_gateway(setup_method, key_store, details_store).await
|
||||
}
|
||||
|
||||
fn construct_nym_api_client(config: &Config, user_agent: Option<UserAgent>) -> NymApiClient {
|
||||
fn construct_nym_api_client(
|
||||
config: &Config,
|
||||
user_agent: Option<UserAgent>,
|
||||
) -> Result<nym_http_api_client::Client, ClientCoreError> {
|
||||
let mut nym_api_urls = config.get_nym_api_endpoints();
|
||||
nym_api_urls.shuffle(&mut thread_rng());
|
||||
|
||||
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 {
|
||||
NymApiClient::new_with_user_agent(nym_api_urls[0].clone(), user_agent)
|
||||
} else {
|
||||
NymApiClient::new(nym_api_urls[0].clone())
|
||||
builder = builder.with_user_agent(user_agent);
|
||||
}
|
||||
|
||||
builder = builder.with_bincode();
|
||||
|
||||
builder.build().map_err(ClientCoreError::from)
|
||||
}
|
||||
|
||||
async fn determine_key_rotation_state(
|
||||
client: &NymApiClient,
|
||||
client: &nym_http_api_client::Client,
|
||||
) -> Result<KeyRotationConfig, ClientCoreError> {
|
||||
Ok(client.nym_api.get_key_rotation_info().await?.into())
|
||||
Ok(client.get_key_rotation_info().await?.into())
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub async fn start_base(mut self) -> Result<BaseClient, ClientCoreError>
|
||||
where
|
||||
S::ReplyStore: Send + Sync,
|
||||
@@ -808,12 +885,12 @@ where
|
||||
let shared_topology_accessor =
|
||||
TopologyAccessor::new(self.config.debug.topology.ignore_egress_epoch_role);
|
||||
|
||||
// Shutdown notifier for signalling tasks to stop
|
||||
let shutdown = self
|
||||
.shutdown
|
||||
.map(Into::<TaskHandle>::into)
|
||||
.unwrap_or_default()
|
||||
.name_if_unnamed("BaseNymClient");
|
||||
// Create a shutdown tracker for this client - either as a child of provided tracker
|
||||
// or get one from the registry
|
||||
let shutdown_tracker = match self.shutdown {
|
||||
Some(parent_tracker) => parent_tracker.child_tracker(),
|
||||
None => nym_task::get_sdk_shutdown_tracker()?,
|
||||
};
|
||||
|
||||
// channels responsible for dealing with reply-related fun
|
||||
let (reply_controller_sender, reply_controller_receiver) =
|
||||
@@ -830,7 +907,7 @@ where
|
||||
.dkg_query_client
|
||||
.map(|client| BandwidthController::new(credential_store, client));
|
||||
|
||||
let nym_api_client = Self::construct_nym_api_client(&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(
|
||||
@@ -845,7 +922,7 @@ where
|
||||
self.user_agent.clone(),
|
||||
generate_client_stats_id(*self_address.identity()),
|
||||
input_sender.clone(),
|
||||
shutdown.fork("statistics_control"),
|
||||
&shutdown_tracker.child_tracker(),
|
||||
);
|
||||
|
||||
// needs to be started as the first thing to block if required waiting for the gateway
|
||||
@@ -855,14 +932,14 @@ where
|
||||
shared_topology_accessor.clone(),
|
||||
self_address.gateway(),
|
||||
self.wait_for_gateway,
|
||||
shutdown.fork("topology_refresher"),
|
||||
&shutdown_tracker.child_tracker(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let gateway_packet_router = PacketRouter::new(
|
||||
ack_sender,
|
||||
mixnet_messages_sender,
|
||||
shutdown.get_handle().named("gateway-packet-router"),
|
||||
shutdown_tracker.clone_shutdown_token(),
|
||||
);
|
||||
|
||||
let gateway_transceiver = Self::setup_gateway_transceiver(
|
||||
@@ -875,7 +952,7 @@ where
|
||||
stats_reporter.clone(),
|
||||
#[cfg(unix)]
|
||||
self.connection_fd_callback,
|
||||
shutdown.fork("gateway_transceiver"),
|
||||
&shutdown_tracker.child_tracker(),
|
||||
)
|
||||
.await?;
|
||||
let gateway_ws_fd = gateway_transceiver.ws_fd();
|
||||
@@ -883,7 +960,7 @@ where
|
||||
let reply_storage = Self::setup_persistent_reply_storage(
|
||||
reply_storage_backend,
|
||||
key_rotation_config,
|
||||
shutdown.fork("persistent_reply_storage"),
|
||||
&shutdown_tracker.child_tracker(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -893,8 +970,8 @@ where
|
||||
mixnet_messages_receiver,
|
||||
reply_storage.key_storage(),
|
||||
reply_controller_sender.clone(),
|
||||
shutdown.fork("received_messages_buffer"),
|
||||
stats_reporter.clone(),
|
||||
&shutdown_tracker.child_tracker(),
|
||||
);
|
||||
|
||||
// The message_sender is the transmitter for any component generating sphinx packets
|
||||
@@ -904,7 +981,7 @@ where
|
||||
|
||||
let (message_sender, client_request_sender) = Self::start_mix_traffic_controller(
|
||||
gateway_transceiver,
|
||||
shutdown.fork("mix_traffic_controller"),
|
||||
&shutdown_tracker.child_tracker(),
|
||||
);
|
||||
|
||||
// Channels that the websocket listener can use to signal downstream to the real traffic
|
||||
@@ -933,9 +1010,8 @@ where
|
||||
reply_controller_receiver,
|
||||
shared_lane_queue_lengths.clone(),
|
||||
client_connection_rx,
|
||||
shutdown.fork("real_traffic_controller"),
|
||||
self.config.debug.traffic.packet_type,
|
||||
stats_reporter.clone(),
|
||||
&shutdown_tracker.child_tracker(),
|
||||
);
|
||||
|
||||
if !self
|
||||
@@ -951,7 +1027,7 @@ where
|
||||
shared_topology_accessor.clone(),
|
||||
message_sender,
|
||||
stats_reporter.clone(),
|
||||
shutdown.fork("cover_traffic_stream"),
|
||||
&shutdown_tracker.child_tracker(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -979,7 +1055,7 @@ where
|
||||
gateway_connection: GatewayConnection { gateway_ws_fd },
|
||||
},
|
||||
stats_reporter,
|
||||
task_handle: shutdown,
|
||||
shutdown_handle: Some(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,
|
||||
@@ -995,7 +1071,7 @@ pub struct BaseClient {
|
||||
pub client_state: ClientState,
|
||||
pub stats_reporter: ClientStatsSender,
|
||||
pub client_request_sender: ClientRequestSender,
|
||||
pub task_handle: TaskHandle,
|
||||
pub shutdown_handle: Option<ShutdownTracker>,
|
||||
pub forget_me: ForgetMe,
|
||||
pub remember_me: RememberMe,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use crate::client::mix_traffic::BatchMixMessageSender;
|
||||
use crate::client::topology_control::TopologyAccessor;
|
||||
use crate::{config, spawn_future};
|
||||
use crate::config;
|
||||
use futures::task::{Context, Poll};
|
||||
use futures::{Future, Stream, StreamExt};
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
@@ -12,7 +12,6 @@ use nym_sphinx::cover::generate_loop_cover_packet;
|
||||
use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use nym_sphinx::utils::sample_poisson_duration;
|
||||
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
||||
use nym_task::TaskClient;
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
@@ -69,8 +68,6 @@ where
|
||||
packet_type: PacketType,
|
||||
|
||||
stats_tx: ClientStatsSender,
|
||||
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl<R> Stream for LoopCoverTrafficStream<R>
|
||||
@@ -117,7 +114,6 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
traffic_config: config::Traffic,
|
||||
cover_config: config::CoverTraffic,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
let rng = OsRng;
|
||||
|
||||
@@ -137,7 +133,6 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
use_legacy_sphinx_format: traffic_config.use_legacy_sphinx_format,
|
||||
packet_type: traffic_config.packet_type,
|
||||
stats_tx,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,12 +230,13 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
// it's fine if cover traffic stream task gets killed whilst processing next message
|
||||
#[allow(clippy::panic)]
|
||||
pub fn start(mut self) {
|
||||
pub async fn run(&mut self) {
|
||||
if self.cover_traffic.disable_loop_cover_traffic_stream {
|
||||
// we should have never got here in the first place - the task should have never been created to begin with
|
||||
// so panic and review the code that lead to this branch
|
||||
panic!("attempted to start LoopCoverTrafficStream while config explicitly disabled it.")
|
||||
panic!("attempted to run LoopCoverTrafficStream while config explicitly disabled it.")
|
||||
}
|
||||
|
||||
// we should set initial delay only when we actually start the stream
|
||||
@@ -250,32 +246,11 @@ impl LoopCoverTrafficStream<OsRng> {
|
||||
);
|
||||
self.set_next_delay(sampled);
|
||||
|
||||
let mut shutdown = self.task_client.fork("select");
|
||||
while self.next().await.is_some() {
|
||||
self.on_new_message().await;
|
||||
}
|
||||
|
||||
spawn_future!(
|
||||
async move {
|
||||
debug!("Started LoopCoverTrafficStream with graceful shutdown support");
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
tracing::trace!("LoopCoverTrafficStream: Received shutdown");
|
||||
}
|
||||
next = self.next() => {
|
||||
if next.is_some() {
|
||||
self.on_new_message().await;
|
||||
} else {
|
||||
tracing::trace!("LoopCoverTrafficStream: Stopping since channel closed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
shutdown.recv_timeout().await;
|
||||
tracing::debug!("LoopCoverTrafficStream: Exiting");
|
||||
},
|
||||
"LoopCoverTrafficStream"
|
||||
)
|
||||
// this should never get triggered
|
||||
error!("cover traffic stream has been exhausted!")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ pub enum InputMessage {
|
||||
data: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
max_retransmissions: Option<u32>,
|
||||
// add trace_id for optional tracing of individual messages in debug mode
|
||||
trace_id: Option<[u8; 12]>,
|
||||
},
|
||||
|
||||
/// Creates a message used for a duplex anonymous communication where the recipient
|
||||
@@ -45,6 +47,7 @@ pub enum InputMessage {
|
||||
reply_surbs: u32,
|
||||
lane: TransmissionLane,
|
||||
max_retransmissions: Option<u32>,
|
||||
trace_id: Option<[u8; 12]>,
|
||||
},
|
||||
|
||||
/// Attempt to use our internally received and stored `ReplySurb` to send the message back
|
||||
@@ -90,12 +93,14 @@ impl InputMessage {
|
||||
data: Vec<u8>,
|
||||
lane: TransmissionLane,
|
||||
packet_type: Option<PacketType>,
|
||||
trace_id: Option<[u8; 12]>,
|
||||
) -> Self {
|
||||
let message = InputMessage::Regular {
|
||||
recipient,
|
||||
data,
|
||||
lane,
|
||||
max_retransmissions: None,
|
||||
trace_id,
|
||||
};
|
||||
if let Some(packet_type) = packet_type {
|
||||
InputMessage::new_wrapper(message, packet_type)
|
||||
@@ -110,6 +115,7 @@ impl InputMessage {
|
||||
reply_surbs: u32,
|
||||
lane: TransmissionLane,
|
||||
packet_type: Option<PacketType>,
|
||||
trace_id: Option<[u8; 12]>,
|
||||
) -> Self {
|
||||
let message = InputMessage::Anonymous {
|
||||
recipient,
|
||||
@@ -117,6 +123,7 @@ impl InputMessage {
|
||||
reply_surbs,
|
||||
lane,
|
||||
max_retransmissions: None,
|
||||
trace_id,
|
||||
};
|
||||
if let Some(packet_type) = packet_type {
|
||||
InputMessage::new_wrapper(message, packet_type)
|
||||
@@ -185,4 +192,13 @@ impl InputMessage {
|
||||
self.set_max_retransmissions(max_retransmissions);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn trace_id(&self) -> Option<[u8; 12]> {
|
||||
match self {
|
||||
InputMessage::Regular { trace_id, .. } => *trace_id,
|
||||
InputMessage::Anonymous { trace_id, .. } => *trace_id,
|
||||
InputMessage::Premade { .. } | InputMessage::Reply { .. } => None,
|
||||
InputMessage::MessageWrapper { message, .. } => message.trace_id(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::mix_traffic::transceiver::GatewayTransceiver;
|
||||
use crate::error::ClientCoreError;
|
||||
use crate::spawn_future;
|
||||
use nym_gateway_requests::ClientRequest;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use tracing::*;
|
||||
use transceiver::ErasedGatewayError;
|
||||
|
||||
@@ -34,13 +32,13 @@ pub struct MixTrafficController {
|
||||
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
|
||||
consecutive_gateway_failure_count: usize,
|
||||
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
}
|
||||
|
||||
impl MixTrafficController {
|
||||
pub fn new<T>(
|
||||
gateway_transceiver: T,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> (
|
||||
MixTrafficController,
|
||||
BatchMixMessageSender,
|
||||
@@ -60,7 +58,7 @@ impl MixTrafficController {
|
||||
mix_rx: message_receiver,
|
||||
client_rx: client_receiver,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
task_client,
|
||||
shutdown_token,
|
||||
},
|
||||
message_sender,
|
||||
client_sender,
|
||||
@@ -69,7 +67,7 @@ impl MixTrafficController {
|
||||
|
||||
pub fn new_dynamic(
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> (
|
||||
MixTrafficController,
|
||||
BatchMixMessageSender,
|
||||
@@ -84,7 +82,7 @@ impl MixTrafficController {
|
||||
mix_rx: message_receiver,
|
||||
client_rx: client_receiver,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
task_client,
|
||||
shutdown_token,
|
||||
},
|
||||
message_sender,
|
||||
client_sender,
|
||||
@@ -107,7 +105,7 @@ impl MixTrafficController {
|
||||
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.task_client.recv() => {
|
||||
_ = self.shutdown_token.cancelled() => {
|
||||
trace!("received shutdown while handling messages");
|
||||
Ok(())
|
||||
}
|
||||
@@ -127,7 +125,7 @@ impl MixTrafficController {
|
||||
async fn on_client_request(&mut self, client_request: ClientRequest) {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.task_client.recv() => {
|
||||
_ = self.shutdown_token.cancelled() => {
|
||||
trace!("received shutdown while handling client request");
|
||||
}
|
||||
result = self.gateway_transceiver.send_client_request(client_request) => {
|
||||
@@ -138,52 +136,44 @@ impl MixTrafficController {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(mut self) {
|
||||
spawn_future!(
|
||||
async move {
|
||||
debug!("Started MixTrafficController with graceful shutdown support");
|
||||
while !self.task_client.is_shutdown() {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.task_client.recv() => {
|
||||
tracing::trace!("MixTrafficController: Received shutdown");
|
||||
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?
|
||||
self.task_client.send_we_stopped(Box::new(ClientCoreError::GatewayFailedToForwardMessages));
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
None => {
|
||||
tracing::trace!("MixTrafficController: Stopping since channel closed");
|
||||
pub async fn run(&mut self) {
|
||||
debug!("Started MixTrafficController with graceful shutdown support");
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.shutdown_token.cancelled() => {
|
||||
trace!("MixTrafficController: Received shutdown");
|
||||
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;
|
||||
}
|
||||
},
|
||||
client_request = self.client_rx.recv() => match client_request {
|
||||
Some(client_request) => {
|
||||
self.on_client_request(client_request).await;
|
||||
},
|
||||
None => {
|
||||
tracing::trace!("MixTrafficController, client request channel closed");
|
||||
break
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
None => {
|
||||
trace!("MixTrafficController: Stopping since channel closed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
tracing::debug!("MixTrafficController: Exiting");
|
||||
},
|
||||
"MixTrafficController"
|
||||
);
|
||||
},
|
||||
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}
|
||||
},
|
||||
}
|
||||
}
|
||||
debug!("MixTrafficController: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
+12
-18
@@ -10,18 +10,17 @@ use nym_sphinx::{
|
||||
acknowledgements::{identifier::recover_identifier, AckKey},
|
||||
chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID},
|
||||
};
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use std::sync::Arc;
|
||||
use tracing::*;
|
||||
|
||||
/// Module responsible for listening for any data resembling acknowledgements from the network
|
||||
/// and firing actions to remove them from the 'Pending' state.
|
||||
pub(super) struct AcknowledgementListener {
|
||||
pub(crate) struct AcknowledgementListener {
|
||||
ack_key: Arc<AckKey>,
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
action_sender: AckActionSender,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl AcknowledgementListener {
|
||||
@@ -30,14 +29,12 @@ impl AcknowledgementListener {
|
||||
ack_receiver: AcknowledgementReceiver,
|
||||
action_sender: AckActionSender,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
AcknowledgementListener {
|
||||
ack_key,
|
||||
ack_receiver,
|
||||
action_sender,
|
||||
stats_tx,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,14 +65,9 @@ impl AcknowledgementListener {
|
||||
trace!("Received {frag_id} from the mix network");
|
||||
self.stats_tx
|
||||
.report(PacketStatisticsEvent::RealAckReceived(ack_content.len()).into());
|
||||
if let Err(err) = self
|
||||
let _ = self
|
||||
.action_sender
|
||||
.unbounded_send(Action::new_remove(frag_id))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send remove action to action controller: {err}");
|
||||
}
|
||||
}
|
||||
.unbounded_send(Action::new_remove(frag_id));
|
||||
}
|
||||
|
||||
async fn handle_ack_receiver_item(&mut self, item: Vec<Vec<u8>>) {
|
||||
@@ -85,11 +77,16 @@ impl AcknowledgementListener {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
pub(crate) async fn run(&mut self, shutdown_token: ShutdownToken) {
|
||||
debug!("Started AcknowledgementListener with graceful shutdown support");
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown_token.cancelled() => {
|
||||
tracing::trace!("AcknowledgementListener: Received shutdown");
|
||||
break;
|
||||
}
|
||||
acks = self.ack_receiver.next() => match acks {
|
||||
Some(acks) => self.handle_ack_receiver_item(acks).await,
|
||||
None => {
|
||||
@@ -97,12 +94,9 @@ impl AcknowledgementListener {
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.task_client.recv() => {
|
||||
tracing::trace!("AcknowledgementListener: Received shutdown");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
tracing::debug!("AcknowledgementListener: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
+11
-20
@@ -8,7 +8,7 @@ use futures::StreamExt;
|
||||
use nym_nonexhaustive_delayqueue::{Expired, NonExhaustiveDelayQueue, QueueKey};
|
||||
use nym_sphinx::chunking::fragment::FragmentIdentifier;
|
||||
use nym_sphinx::Delay as SphinxDelay;
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -82,7 +82,7 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct ActionController {
|
||||
pub(crate) struct ActionController {
|
||||
/// Configurable parameters of the `ActionController`
|
||||
config: Config,
|
||||
|
||||
@@ -102,8 +102,6 @@ pub(super) struct ActionController {
|
||||
|
||||
/// Channel for notifying `RetransmissionRequestListener` about expired acknowledgements.
|
||||
retransmission_sender: RetransmissionRequestSender,
|
||||
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl ActionController {
|
||||
@@ -111,7 +109,6 @@ impl ActionController {
|
||||
config: Config,
|
||||
retransmission_sender: RetransmissionRequestSender,
|
||||
incoming_actions: AckActionReceiver,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
ActionController {
|
||||
config,
|
||||
@@ -119,7 +116,6 @@ impl ActionController {
|
||||
pending_acks_timers: NonExhaustiveDelayQueue::new(),
|
||||
incoming_actions,
|
||||
retransmission_sender,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,14 +222,9 @@ impl ActionController {
|
||||
// downgrading an arc and then upgrading vs cloning is difference of 30ns vs 15ns
|
||||
// so it's literally a NO difference while it might prevent us from unnecessarily
|
||||
// resending data (in maybe 1 in 1 million cases, but it's something)
|
||||
if let Err(err) = self
|
||||
let _ = self
|
||||
.retransmission_sender
|
||||
.unbounded_send(Arc::downgrade(pending_ack_data))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
tracing::error!("Failed to send pending ack for retransmission: {err}");
|
||||
}
|
||||
}
|
||||
.unbounded_send(Arc::downgrade(pending_ack_data));
|
||||
} else {
|
||||
// this shouldn't cause any issues but shouldn't have happened to begin with!
|
||||
error!("An already removed pending ack has expired")
|
||||
@@ -251,11 +242,16 @@ impl ActionController {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
pub(crate) async fn run(&mut self, shutdown_token: ShutdownToken) {
|
||||
debug!("Started ActionController with graceful shutdown support");
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown_token.cancelled() => {
|
||||
tracing::trace!("ActionController: Received shutdown");
|
||||
break;
|
||||
}
|
||||
action = self.incoming_actions.next() => match action {
|
||||
Some(action) => self.process_action(action),
|
||||
None => {
|
||||
@@ -272,13 +268,8 @@ impl ActionController {
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.task_client.recv() => {
|
||||
tracing::trace!("ActionController: Received shutdown");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
tracing::debug!("ActionController: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
+32
-17
@@ -10,21 +10,20 @@ use nym_sphinx::anonymous_replies::requests::AnonymousSenderTag;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use tracing::*;
|
||||
|
||||
/// Module responsible for dealing with the received messages: splitting them, creating acknowledgements,
|
||||
/// putting everything into sphinx packets, etc.
|
||||
/// It also makes an initial sending attempt for said messages.
|
||||
pub(super) struct InputMessageListener<R>
|
||||
pub(crate) struct InputMessageListener<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
input_receiver: InputMessageReceiver,
|
||||
message_handler: MessageHandler<R>,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl<R> InputMessageListener<R>
|
||||
@@ -38,13 +37,11 @@ where
|
||||
input_receiver: InputMessageReceiver,
|
||||
message_handler: MessageHandler<R>,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
InputMessageListener {
|
||||
input_receiver,
|
||||
message_handler,
|
||||
reply_controller_sender,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,16 +65,12 @@ where
|
||||
max_retransmissions: Option<u32>,
|
||||
) {
|
||||
// offload reply handling to the dedicated task
|
||||
if let Err(err) =
|
||||
let _ =
|
||||
self.reply_controller_sender
|
||||
.send_reply(recipient_tag, data, lane, max_retransmissions)
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("failed to send a reply - {err}");
|
||||
}
|
||||
}
|
||||
.send_reply(recipient_tag, data, lane, max_retransmissions);
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn handle_plain_message(
|
||||
&mut self,
|
||||
recipient: Recipient,
|
||||
@@ -85,16 +78,18 @@ where
|
||||
lane: TransmissionLane,
|
||||
packet_type: PacketType,
|
||||
max_retransmissions: Option<u32>,
|
||||
trace_id: Option<[u8; 12]>,
|
||||
) {
|
||||
if let Err(err) = self
|
||||
.message_handler
|
||||
.try_send_plain_message(recipient, content, lane, packet_type, max_retransmissions)
|
||||
.try_send_plain_message(recipient, content, lane, packet_type, max_retransmissions, trace_id)
|
||||
.await
|
||||
{
|
||||
warn!("failed to send a plain message - {err}")
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn handle_repliable_message(
|
||||
&mut self,
|
||||
recipient: Recipient,
|
||||
@@ -103,6 +98,7 @@ where
|
||||
lane: TransmissionLane,
|
||||
packet_type: PacketType,
|
||||
max_retransmissions: Option<u32>,
|
||||
trace_id: Option<[u8; 12]>,
|
||||
) {
|
||||
if let Err(err) = self
|
||||
.message_handler
|
||||
@@ -113,6 +109,7 @@ where
|
||||
lane,
|
||||
packet_type,
|
||||
max_retransmissions,
|
||||
trace_id,
|
||||
)
|
||||
.await
|
||||
{
|
||||
@@ -121,20 +118,29 @@ where
|
||||
}
|
||||
|
||||
#[allow(clippy::panic)]
|
||||
#[instrument(skip_all)]
|
||||
async fn on_input_message(&mut self, msg: InputMessage) {
|
||||
let trace_id = msg.trace_id();
|
||||
if let Some(tid) = trace_id {
|
||||
tracing::warn!("Processing input message with trace_id: {:?}", tid);
|
||||
}
|
||||
|
||||
match msg {
|
||||
InputMessage::Regular {
|
||||
recipient,
|
||||
data,
|
||||
lane,
|
||||
max_retransmissions,
|
||||
..
|
||||
} => {
|
||||
warn!("Handling regular input message with trace_id: {:?}", trace_id);
|
||||
self.handle_plain_message(
|
||||
recipient,
|
||||
data,
|
||||
lane,
|
||||
PacketType::Mix,
|
||||
max_retransmissions,
|
||||
trace_id
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -144,7 +150,9 @@ where
|
||||
reply_surbs,
|
||||
lane,
|
||||
max_retransmissions,
|
||||
..
|
||||
} => {
|
||||
warn!("Handling anonymous input message with trace_id: {:?}", trace_id);
|
||||
self.handle_repliable_message(
|
||||
recipient,
|
||||
data,
|
||||
@@ -152,6 +160,7 @@ where
|
||||
lane,
|
||||
PacketType::Mix,
|
||||
max_retransmissions,
|
||||
trace_id
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -161,6 +170,7 @@ where
|
||||
lane,
|
||||
max_retransmissions,
|
||||
} => {
|
||||
warn!("Handling reply input message with trace_id: {:?}", trace_id);
|
||||
self.handle_reply(recipient_tag, data, lane, max_retransmissions)
|
||||
.await;
|
||||
}
|
||||
@@ -174,13 +184,16 @@ where
|
||||
data,
|
||||
lane,
|
||||
max_retransmissions,
|
||||
..
|
||||
} => {
|
||||
tracing::warn!("Handling regular input message with trace_id: {:?}", trace_id);
|
||||
self.handle_plain_message(
|
||||
recipient,
|
||||
data,
|
||||
lane,
|
||||
packet_type,
|
||||
max_retransmissions,
|
||||
trace_id
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -190,6 +203,7 @@ where
|
||||
reply_surbs,
|
||||
lane,
|
||||
max_retransmissions,
|
||||
..
|
||||
} => {
|
||||
self.handle_repliable_message(
|
||||
recipient,
|
||||
@@ -198,6 +212,7 @@ where
|
||||
lane,
|
||||
packet_type,
|
||||
max_retransmissions,
|
||||
trace_id
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -221,13 +236,14 @@ where
|
||||
};
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
#[instrument(skip_all)]
|
||||
pub(crate) async fn run(&mut self, shutdown_token: ShutdownToken) {
|
||||
debug!("Started InputMessageListener with graceful shutdown support");
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.task_client.recv() => {
|
||||
_ = shutdown_token.cancelled() => {
|
||||
tracing::trace!("InputMessageListener: Received shutdown");
|
||||
break;
|
||||
}
|
||||
@@ -243,7 +259,6 @@ where
|
||||
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
tracing::debug!("InputMessageListener: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
+25
-60
@@ -10,7 +10,6 @@ use self::{
|
||||
use crate::client::inbound_messages::InputMessageReceiver;
|
||||
use crate::client::real_messages_control::message_handler::MessageHandler;
|
||||
use crate::client::replies::reply_controller::ReplyControllerSender;
|
||||
use crate::spawn_future;
|
||||
use action_controller::AckActionReceiver;
|
||||
use futures::channel::mpsc;
|
||||
use nym_gateway_client::AcknowledgementReceiver;
|
||||
@@ -23,13 +22,11 @@ use nym_sphinx::{
|
||||
Delay as SphinxDelay,
|
||||
};
|
||||
use nym_statistics_common::clients::ClientStatsSender;
|
||||
use nym_task::TaskClient;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::{
|
||||
sync::{Arc, Weak},
|
||||
time::Duration,
|
||||
};
|
||||
use tracing::*;
|
||||
|
||||
pub(crate) use action_controller::{AckActionSender, Action};
|
||||
|
||||
@@ -190,6 +187,9 @@ pub(super) struct Config {
|
||||
|
||||
/// Predefined packet size used for the encapsulated messages.
|
||||
packet_size: PacketSize,
|
||||
|
||||
/// Type of packets used for retransmissions
|
||||
packet_type: PacketType,
|
||||
}
|
||||
|
||||
impl Config {
|
||||
@@ -197,12 +197,14 @@ impl Config {
|
||||
maximum_retransmissions: Option<u32>,
|
||||
ack_wait_addition: Duration,
|
||||
ack_wait_multiplier: f64,
|
||||
packet_type: PacketType,
|
||||
) -> Self {
|
||||
Config {
|
||||
maximum_retransmissions,
|
||||
ack_wait_addition,
|
||||
ack_wait_multiplier,
|
||||
packet_size: Default::default(),
|
||||
packet_type,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +214,7 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct AcknowledgementController<R>
|
||||
pub(crate) struct AcknowledgementController<R>
|
||||
where
|
||||
R: CryptoRng + Rng,
|
||||
{
|
||||
@@ -234,7 +236,6 @@ where
|
||||
message_handler: MessageHandler<R>,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
let (retransmission_tx, retransmission_rx) = mpsc::unbounded();
|
||||
|
||||
@@ -244,7 +245,6 @@ where
|
||||
action_config,
|
||||
retransmission_tx,
|
||||
connectors.ack_action_receiver,
|
||||
task_client.fork("action_controller"),
|
||||
);
|
||||
|
||||
// will listen for any acks coming from the network
|
||||
@@ -253,7 +253,6 @@ where
|
||||
connectors.ack_receiver,
|
||||
connectors.ack_action_sender.clone(),
|
||||
stats_tx,
|
||||
task_client.fork("acknowledgement_listener"),
|
||||
);
|
||||
|
||||
// will listen for any new messages from the client
|
||||
@@ -261,7 +260,6 @@ where
|
||||
connectors.input_receiver,
|
||||
message_handler.clone(),
|
||||
reply_controller_sender.clone(),
|
||||
task_client.fork("input_message_listener"),
|
||||
);
|
||||
|
||||
// will listen for any ack timeouts and trigger retransmission
|
||||
@@ -271,16 +269,13 @@ where
|
||||
message_handler,
|
||||
retransmission_rx,
|
||||
reply_controller_sender,
|
||||
task_client.fork("retransmission_request_listener"),
|
||||
config.packet_type,
|
||||
);
|
||||
|
||||
// will listen for events indicating the packet was sent through the network so that
|
||||
// the retransmission timer should be started.
|
||||
let sent_notification_listener = SentNotificationListener::new(
|
||||
connectors.sent_notifier,
|
||||
connectors.ack_action_sender,
|
||||
task_client.with_suffix("sent_notification_listener"),
|
||||
);
|
||||
let sent_notification_listener =
|
||||
SentNotificationListener::new(connectors.sent_notifier, connectors.ack_action_sender);
|
||||
|
||||
AcknowledgementController {
|
||||
acknowledgement_listener,
|
||||
@@ -291,51 +286,21 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn start(self, packet_type: PacketType) {
|
||||
let mut acknowledgement_listener = self.acknowledgement_listener;
|
||||
let mut input_message_listener = self.input_message_listener;
|
||||
let mut retransmission_request_listener = self.retransmission_request_listener;
|
||||
let mut sent_notification_listener = self.sent_notification_listener;
|
||||
let mut action_controller = self.action_controller;
|
||||
|
||||
spawn_future!(
|
||||
async move {
|
||||
acknowledgement_listener.run().await;
|
||||
debug!("The acknowledgement listener has finished execution!");
|
||||
},
|
||||
"AcknowledgementController::AcknowledgementListener"
|
||||
);
|
||||
|
||||
spawn_future!(
|
||||
async move {
|
||||
input_message_listener.run().await;
|
||||
debug!("The input listener has finished execution!");
|
||||
},
|
||||
"AcknowledgementController::InputMessageListener"
|
||||
);
|
||||
|
||||
spawn_future!(
|
||||
async move {
|
||||
retransmission_request_listener.run(packet_type).await;
|
||||
debug!("The retransmission request listener has finished execution!");
|
||||
},
|
||||
"AcknowledgementController::RetransmissionRequestListener"
|
||||
);
|
||||
|
||||
spawn_future!(
|
||||
async move {
|
||||
sent_notification_listener.run().await;
|
||||
debug!("The sent notification listener has finished execution!");
|
||||
},
|
||||
"AcknowledgementController::SentNotificationListener"
|
||||
);
|
||||
|
||||
spawn_future!(
|
||||
async move {
|
||||
action_controller.run().await;
|
||||
debug!("The controller has finished execution!");
|
||||
},
|
||||
"AcknowledgementController::ActionController"
|
||||
);
|
||||
pub(crate) fn into_tasks(
|
||||
self,
|
||||
) -> (
|
||||
AcknowledgementListener,
|
||||
InputMessageListener<R>,
|
||||
RetransmissionRequestListener<R>,
|
||||
SentNotificationListener,
|
||||
ActionController,
|
||||
) {
|
||||
(
|
||||
self.acknowledgement_listener,
|
||||
self.input_message_listener,
|
||||
self.retransmission_request_listener,
|
||||
self.sent_notification_listener,
|
||||
self.action_controller,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+15
-26
@@ -13,19 +13,19 @@ use futures::StreamExt;
|
||||
use nym_sphinx::chunking::fragment::Fragment;
|
||||
use nym_sphinx::preparer::PreparedFragment;
|
||||
use nym_sphinx::{addressing::clients::Recipient, params::PacketType};
|
||||
use nym_task::{connections::TransmissionLane, TaskClient};
|
||||
use nym_task::{connections::TransmissionLane, ShutdownToken};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::sync::{Arc, Weak};
|
||||
use tracing::*;
|
||||
|
||||
// responsible for packet retransmission upon fired timer
|
||||
pub(super) struct RetransmissionRequestListener<R> {
|
||||
pub(crate) struct RetransmissionRequestListener<R> {
|
||||
maximum_retransmissions: Option<u32>,
|
||||
action_sender: AckActionSender,
|
||||
message_handler: MessageHandler<R>,
|
||||
request_receiver: RetransmissionRequestReceiver,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
task_client: TaskClient,
|
||||
packet_type: PacketType,
|
||||
}
|
||||
|
||||
impl<R> RetransmissionRequestListener<R>
|
||||
@@ -38,7 +38,7 @@ where
|
||||
message_handler: MessageHandler<R>,
|
||||
request_receiver: RetransmissionRequestReceiver,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
task_client: TaskClient,
|
||||
packet_type: PacketType,
|
||||
) -> Self {
|
||||
RetransmissionRequestListener {
|
||||
maximum_retransmissions,
|
||||
@@ -46,7 +46,7 @@ where
|
||||
message_handler,
|
||||
request_receiver,
|
||||
reply_controller_sender,
|
||||
task_client,
|
||||
packet_type,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,14 +60,13 @@ where
|
||||
|
||||
// TODO: Figure out retransmission packet type signaling
|
||||
self.message_handler
|
||||
.try_prepare_single_chunk_for_sending(packet_recipient, chunk_data, packet_type)
|
||||
.try_prepare_single_chunk_for_sending(packet_recipient, chunk_data, packet_type, None)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn on_retransmission_request(
|
||||
&mut self,
|
||||
weak_timed_out_ack: Weak<PendingAcknowledgement>,
|
||||
packet_type: PacketType,
|
||||
) {
|
||||
let timed_out_ack = match weak_timed_out_ack.upgrade() {
|
||||
Some(timed_out_ack) => timed_out_ack,
|
||||
@@ -97,22 +96,18 @@ where
|
||||
} => {
|
||||
// if this is retransmission for reply, offload it to the dedicated task
|
||||
// that deals with all the surbs
|
||||
if let Err(err) = self.reply_controller_sender.send_retransmission_data(
|
||||
let _ = self.reply_controller_sender.send_retransmission_data(
|
||||
*recipient_tag,
|
||||
weak_timed_out_ack,
|
||||
*extra_surb_request,
|
||||
) {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send retransmission data to the reply controller: {err}");
|
||||
}
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
PacketDestination::KnownRecipient(recipient) => {
|
||||
self.prepare_normal_retransmission_chunk(
|
||||
**recipient,
|
||||
timed_out_ack.message_chunk.clone(),
|
||||
packet_type,
|
||||
self.packet_type,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -153,14 +148,9 @@ where
|
||||
// is sent to the `OutQueueControl` and has gone through its internal queue
|
||||
// with the additional poisson delay.
|
||||
// And since Actions are executed in order `UpdateTimer` will HAVE TO be executed before `StartTimer`
|
||||
if let Err(err) = self
|
||||
let _ = self
|
||||
.action_sender
|
||||
.unbounded_send(Action::new_update_pending_ack(frag_id, new_delay))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send update pending ack action to the controller: {err}");
|
||||
}
|
||||
}
|
||||
.unbounded_send(Action::new_update_pending_ack(frag_id, new_delay));
|
||||
|
||||
// send to `OutQueueControl` to eventually send to the mix network
|
||||
self.message_handler
|
||||
@@ -174,18 +164,18 @@ where
|
||||
.await
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self, packet_type: PacketType) {
|
||||
pub(crate) async fn run(&mut self, shutdown_token: ShutdownToken) {
|
||||
debug!("Started RetransmissionRequestListener with graceful shutdown support");
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.task_client.recv() => {
|
||||
_ = shutdown_token.cancelled() => {
|
||||
tracing::trace!("RetransmissionRequestListener: Received shutdown");
|
||||
break;
|
||||
}
|
||||
timed_out_ack = self.request_receiver.next() => match timed_out_ack {
|
||||
Some(timed_out_ack) => self.on_retransmission_request(timed_out_ack, packet_type).await,
|
||||
Some(timed_out_ack) => self.on_retransmission_request(timed_out_ack).await,
|
||||
None => {
|
||||
tracing::trace!("RetransmissionRequestListener: Stopping since channel closed");
|
||||
break;
|
||||
@@ -194,7 +184,6 @@ where
|
||||
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
tracing::debug!("RetransmissionRequestListener: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
+7
-30
@@ -5,29 +5,25 @@ use super::action_controller::{AckActionSender, Action};
|
||||
use super::SentPacketNotificationReceiver;
|
||||
use futures::StreamExt;
|
||||
use nym_sphinx::chunking::fragment::{FragmentIdentifier, COVER_FRAG_ID};
|
||||
use nym_task::TaskClient;
|
||||
use tracing::*;
|
||||
|
||||
/// Module responsible for starting up retransmission timers.
|
||||
/// It is required because when we send our packet to the `real traffic stream` controlled
|
||||
/// by a poisson timer, there's no guarantee the message will be sent immediately, so we might
|
||||
/// accidentally fire retransmission way quicker than we should have.
|
||||
pub(super) struct SentNotificationListener {
|
||||
pub(crate) struct SentNotificationListener {
|
||||
sent_notifier: SentPacketNotificationReceiver,
|
||||
action_sender: AckActionSender,
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl SentNotificationListener {
|
||||
pub(super) fn new(
|
||||
sent_notifier: SentPacketNotificationReceiver,
|
||||
action_sender: AckActionSender,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
SentNotificationListener {
|
||||
sent_notifier,
|
||||
action_sender,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,37 +32,18 @@ impl SentNotificationListener {
|
||||
trace!("sent off a cover message - no need to start retransmission timer!");
|
||||
return;
|
||||
}
|
||||
if let Err(err) = self
|
||||
let _ = self
|
||||
.action_sender
|
||||
.unbounded_send(Action::new_start_timer(frag_id))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
error!("Failed to send start timer action to action controller: {err}");
|
||||
}
|
||||
}
|
||||
.unbounded_send(Action::new_start_timer(frag_id));
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
pub(crate) async fn run(&mut self) {
|
||||
debug!("Started SentNotificationListener with graceful shutdown support");
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
tokio::select! {
|
||||
frag_id = self.sent_notifier.next() => match frag_id {
|
||||
Some(frag_id) => {
|
||||
self.on_sent_message(frag_id).await;
|
||||
}
|
||||
None => {
|
||||
tracing::trace!("SentNotificationListener: Stopping since channel closed");
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.task_client.recv() => {
|
||||
tracing::trace!("SentNotificationListener: Received shutdown");
|
||||
break;
|
||||
}
|
||||
}
|
||||
while let Some(frag_id) = self.sent_notifier.next().await {
|
||||
self.on_sent_message(frag_id).await;
|
||||
}
|
||||
assert!(self.task_client.is_shutdown_poll());
|
||||
|
||||
tracing::debug!("SentNotificationListener: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,14 +20,14 @@ use nym_sphinx::params::{PacketSize, PacketType};
|
||||
use nym_sphinx::preparer::{MessagePreparer, PreparedFragment};
|
||||
use nym_sphinx::Delay;
|
||||
use nym_task::connections::TransmissionLane;
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use nym_topology::{NymRouteProvider, NymTopologyError};
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use thiserror::Error;
|
||||
use tracing::{debug, error, info, trace, warn};
|
||||
use tracing::{debug, error, info, instrument, trace, warn};
|
||||
|
||||
// TODO: move that error elsewhere since it seems to be contaminating different files
|
||||
#[derive(Debug, Error)]
|
||||
@@ -189,7 +189,7 @@ pub(crate) struct MessageHandler<R> {
|
||||
topology_access: TopologyAccessor,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
tag_storage: UsedSenderTags,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
}
|
||||
|
||||
impl<R> MessageHandler<R>
|
||||
@@ -205,7 +205,7 @@ where
|
||||
topology_access: TopologyAccessor,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
tag_storage: UsedSenderTags,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> Self
|
||||
where
|
||||
R: Copy,
|
||||
@@ -228,7 +228,7 @@ where
|
||||
topology_access,
|
||||
reply_key_storage,
|
||||
tag_storage,
|
||||
task_client,
|
||||
shutdown_token,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,6 +476,7 @@ where
|
||||
self.forward_messages(msgs, lane).await;
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub(crate) async fn try_send_plain_message(
|
||||
&mut self,
|
||||
recipient: Recipient,
|
||||
@@ -483,6 +484,7 @@ where
|
||||
lane: TransmissionLane,
|
||||
packet_type: PacketType,
|
||||
max_retransmissions: Option<u32>,
|
||||
trace_id: Option<[u8; 12]>,
|
||||
) -> Result<(), PreparationError> {
|
||||
let message = NymMessage::new_plain(message);
|
||||
self.try_split_and_send_non_reply_message(
|
||||
@@ -491,10 +493,12 @@ where
|
||||
lane,
|
||||
packet_type,
|
||||
max_retransmissions,
|
||||
trace_id,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
pub(crate) async fn try_split_and_send_non_reply_message(
|
||||
&mut self,
|
||||
message: NymMessage,
|
||||
@@ -502,6 +506,7 @@ where
|
||||
lane: TransmissionLane,
|
||||
packet_type: PacketType,
|
||||
max_retransmissions: Option<u32>,
|
||||
trace_id: Option<[u8; 12]>,
|
||||
) -> Result<(), PreparationError> {
|
||||
debug!("Sending non-reply message with packet type {packet_type}");
|
||||
// TODO: I really dislike existence of this assertion, it implies code has to be re-organised
|
||||
@@ -534,6 +539,7 @@ where
|
||||
&self.config.ack_key,
|
||||
&recipient,
|
||||
packet_type,
|
||||
trace_id
|
||||
)?;
|
||||
|
||||
let real_message = RealMessage::new(
|
||||
@@ -585,6 +591,7 @@ where
|
||||
TransmissionLane::AdditionalReplySurbs,
|
||||
packet_type,
|
||||
max_retransmissions,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -602,6 +609,7 @@ where
|
||||
lane: TransmissionLane,
|
||||
packet_type: PacketType,
|
||||
max_retransmissions: Option<u32>,
|
||||
trace_id: Option<[u8; 12]>,
|
||||
) -> Result<(), SurbWrappedPreparationError> {
|
||||
debug!("Sending message with reply SURBs with packet type {packet_type}");
|
||||
let sender_tag = self.get_or_create_sender_tag(&recipient);
|
||||
@@ -625,6 +633,7 @@ where
|
||||
lane,
|
||||
packet_type,
|
||||
max_retransmissions,
|
||||
trace_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -639,6 +648,7 @@ where
|
||||
recipient: Recipient,
|
||||
chunk: Fragment,
|
||||
packet_type: PacketType,
|
||||
trace_id: Option<[u8; 12]>,
|
||||
) -> Result<PreparedFragment, PreparationError> {
|
||||
debug!("Sending single chunk with packet type {packet_type}");
|
||||
let topology_permit = self.topology_access.get_read_permit().await;
|
||||
@@ -650,6 +660,7 @@ where
|
||||
&self.config.ack_key,
|
||||
&recipient,
|
||||
packet_type,
|
||||
trace_id,
|
||||
)?;
|
||||
|
||||
Ok(prepared_fragment)
|
||||
@@ -712,7 +723,7 @@ where
|
||||
.action_sender
|
||||
.unbounded_send(Action::UpdatePendingAck(id, new_delay))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
if !self.shutdown_token.is_cancelled() {
|
||||
error!("Failed to send update action to the controller: {err}");
|
||||
}
|
||||
}
|
||||
@@ -723,7 +734,7 @@ where
|
||||
.action_sender
|
||||
.unbounded_send(Action::new_insert(pending_acks))
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
if !self.shutdown_token.is_cancelled() {
|
||||
error!("Failed to send insert action to the controller: {err}");
|
||||
}
|
||||
}
|
||||
@@ -737,7 +748,7 @@ where
|
||||
) {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.task_client.recv() => {
|
||||
_ = self.shutdown_token.cancelled() => {
|
||||
trace!("received shutdown while attempting to forward mixnet messages");
|
||||
}
|
||||
sending_res = self.real_message_sender.send((messages, transmission_lane)) => {
|
||||
|
||||
@@ -14,26 +14,21 @@ use crate::client::replies::reply_controller::{
|
||||
ReplyController, ReplyControllerReceiver, ReplyControllerSender,
|
||||
};
|
||||
use crate::client::replies::reply_storage::CombinedReplyStorage;
|
||||
use crate::config;
|
||||
use crate::{
|
||||
client::{
|
||||
inbound_messages::InputMessageReceiver, mix_traffic::BatchMixMessageSender,
|
||||
real_messages_control::acknowledgement_control::AcknowledgementControllerConnectors,
|
||||
topology_control::TopologyAccessor,
|
||||
},
|
||||
spawn_future,
|
||||
use crate::client::{
|
||||
inbound_messages::InputMessageReceiver, mix_traffic::BatchMixMessageSender,
|
||||
real_messages_control::acknowledgement_control::AcknowledgementControllerConnectors,
|
||||
topology_control::TopologyAccessor,
|
||||
};
|
||||
use crate::config;
|
||||
use futures::channel::mpsc;
|
||||
use nym_gateway_client::AcknowledgementReceiver;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::addressing::clients::Recipient;
|
||||
use nym_sphinx::params::PacketType;
|
||||
use nym_statistics_common::clients::ClientStatsSender;
|
||||
use nym_task::connections::{ConnectionCommandReceiver, LaneQueueLengths};
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use rand::{rngs::OsRng, CryptoRng, Rng};
|
||||
use std::sync::Arc;
|
||||
use tracing::*;
|
||||
|
||||
use crate::client::replies::reply_controller::key_rotation_helpers::KeyRotationConfig;
|
||||
pub(crate) use acknowledgement_control::{AckActionSender, Action};
|
||||
@@ -69,6 +64,7 @@ impl<'a> From<&'a Config> for acknowledgement_control::Config {
|
||||
cfg.traffic.maximum_number_of_retransmissions,
|
||||
cfg.acks.ack_wait_addition,
|
||||
cfg.acks.ack_wait_multiplier,
|
||||
cfg.traffic.packet_type,
|
||||
)
|
||||
.with_custom_packet_size(cfg.traffic.primary_packet_size)
|
||||
}
|
||||
@@ -146,7 +142,7 @@ impl RealMessagesController<OsRng> {
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
client_connection_rx: ConnectionCommandReceiver,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> Self {
|
||||
let rng = OsRng;
|
||||
|
||||
@@ -178,7 +174,7 @@ impl RealMessagesController<OsRng> {
|
||||
topology_access.clone(),
|
||||
reply_storage.key_storage(),
|
||||
reply_storage.tags_storage(),
|
||||
task_client.fork("message_handler"),
|
||||
shutdown_token.clone(),
|
||||
);
|
||||
|
||||
let ack_control = AcknowledgementController::new(
|
||||
@@ -188,7 +184,6 @@ impl RealMessagesController<OsRng> {
|
||||
message_handler.clone(),
|
||||
reply_controller_sender,
|
||||
stats_tx.clone(),
|
||||
task_client.fork("ack_control"),
|
||||
);
|
||||
|
||||
let reply_control = ReplyController::new(
|
||||
@@ -196,7 +191,6 @@ impl RealMessagesController<OsRng> {
|
||||
message_handler,
|
||||
reply_storage,
|
||||
reply_controller_receiver,
|
||||
task_client.fork("reply_controller"),
|
||||
);
|
||||
|
||||
let out_queue_control = OutQueueControl::new(
|
||||
@@ -209,7 +203,7 @@ impl RealMessagesController<OsRng> {
|
||||
lane_queue_lengths,
|
||||
client_connection_rx,
|
||||
stats_tx,
|
||||
task_client.with_suffix("out_queue_control"),
|
||||
shutdown_token.clone(),
|
||||
);
|
||||
|
||||
RealMessagesController {
|
||||
@@ -219,26 +213,13 @@ impl RealMessagesController<OsRng> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(self, packet_type: PacketType) {
|
||||
let mut out_queue_control = self.out_queue_control;
|
||||
let ack_control = self.ack_control;
|
||||
let mut reply_control = self.reply_control;
|
||||
|
||||
spawn_future!(
|
||||
async move {
|
||||
out_queue_control.run().await;
|
||||
debug!("The out queue controller has finished execution!");
|
||||
},
|
||||
"RealMessagesController::OutQueueControl)"
|
||||
);
|
||||
spawn_future!(
|
||||
async move {
|
||||
reply_control.run().await;
|
||||
debug!("The reply controller has finished execution!");
|
||||
},
|
||||
"RealMessagesController::ReplyController"
|
||||
);
|
||||
|
||||
ack_control.start(packet_type);
|
||||
pub fn into_tasks(
|
||||
self,
|
||||
) -> (
|
||||
OutQueueControl<OsRng>,
|
||||
ReplyController<OsRng>,
|
||||
AcknowledgementController<OsRng>,
|
||||
) {
|
||||
(self.out_queue_control, self.reply_control, self.ack_control)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, C
|
||||
use nym_task::connections::{
|
||||
ConnectionCommand, ConnectionCommandReceiver, ConnectionId, LaneQueueLengths, TransmissionLane,
|
||||
};
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
@@ -119,7 +119,7 @@ where
|
||||
/// Channel used for sending metrics events (specifically `PacketStatistics` events) to the metrics tracker.
|
||||
stats_tx: ClientStatsSender,
|
||||
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -179,7 +179,7 @@ where
|
||||
lane_queue_lengths: LaneQueueLengths,
|
||||
client_connection_rx: ConnectionCommandReceiver,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> Self {
|
||||
OutQueueControl {
|
||||
config,
|
||||
@@ -194,7 +194,7 @@ where
|
||||
client_connection_rx,
|
||||
lane_queue_lengths,
|
||||
stats_tx,
|
||||
task_client,
|
||||
shutdown_token,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ where
|
||||
|
||||
let sending_res = tokio::select! {
|
||||
biased;
|
||||
_ = self.task_client.recv() => {
|
||||
_ = self.shutdown_token.cancelled() => {
|
||||
trace!("received shutdown signal while attempting to send mix message");
|
||||
return
|
||||
}
|
||||
@@ -293,7 +293,7 @@ where
|
||||
|
||||
match sending_res {
|
||||
Err(_) => {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
if !self.shutdown_token.is_cancelled() {
|
||||
tracing::error!(
|
||||
"failed to send mixnet packet due to closed channel (outside of shutdown!)"
|
||||
);
|
||||
@@ -536,9 +536,7 @@ where
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn log_status(&self, shutdown: &mut TaskClient) {
|
||||
use crate::error::ClientCoreStatusMessage;
|
||||
|
||||
fn log_status(&self) {
|
||||
let packets = self.transmission_buffer.total_size();
|
||||
let lanes = self.transmission_buffer.lanes();
|
||||
let mult = self.sending_delay_controller.current_multiplier();
|
||||
@@ -567,32 +565,33 @@ where
|
||||
tracing::debug!("{status_str}");
|
||||
}
|
||||
|
||||
// Send status message to whoever is listening (possibly UI)
|
||||
if mult == self.sending_delay_controller.max_multiplier() {
|
||||
shutdown.send_status_msg(Box::new(ClientCoreStatusMessage::GatewayIsVerySlow));
|
||||
} else if mult > self.sending_delay_controller.min_multiplier() {
|
||||
shutdown.send_status_msg(Box::new(ClientCoreStatusMessage::GatewayIsSlow));
|
||||
}
|
||||
// leave the code commented in case somebody wanted to restore this logic with a different channel
|
||||
// // Send status message to whoever is listening (possibly UI)
|
||||
// if mult == self.sending_delay_controller.max_multiplier() {
|
||||
// shutdown.send_status_msg(Box::new(ClientCoreStatusMessage::GatewayIsVerySlow));
|
||||
// } else if mult > self.sending_delay_controller.min_multiplier() {
|
||||
// shutdown.send_status_msg(Box::new(ClientCoreStatusMessage::GatewayIsSlow));
|
||||
// }
|
||||
}
|
||||
|
||||
pub(super) async fn run(&mut self) {
|
||||
pub(crate) async fn run(&mut self) {
|
||||
debug!("Started OutQueueControl with graceful shutdown support");
|
||||
|
||||
let mut shutdown = self.task_client.fork("select");
|
||||
|
||||
// avoid borrow on self
|
||||
let shutdown_token = self.shutdown_token.clone();
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
let mut status_timer = tokio::time::interval(Duration::from_secs(5));
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
_ = shutdown_token.cancelled() => {
|
||||
tracing::trace!("OutQueueControl: Received shutdown");
|
||||
break;
|
||||
}
|
||||
_ = status_timer.tick() => {
|
||||
self.log_status(&mut shutdown);
|
||||
self.log_status();
|
||||
}
|
||||
next_message = self.next() => if let Some(next_message) = next_message {
|
||||
self.on_message(next_message).await;
|
||||
@@ -602,16 +601,16 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
shutdown.recv_timeout().await;
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
while !shutdown.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
_ = shutdown_token.cancelled() => {
|
||||
tracing::trace!("OutQueueControl: Received shutdown");
|
||||
break;
|
||||
}
|
||||
next_message = self.next() => if let Some(next_message) = next_message {
|
||||
self.on_message(next_message).await;
|
||||
|
||||
+2
@@ -83,11 +83,13 @@ impl SendingDelayController {
|
||||
self.current_multiplier
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn min_multiplier(&self) -> u32 {
|
||||
self.lower_bound
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub(crate) fn max_multiplier(&self) -> u32 {
|
||||
self.upper_bound
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::client::helpers::get_time_now;
|
||||
use crate::client::replies::{
|
||||
reply_controller::ReplyControllerSender, reply_storage::SentReplyKeys,
|
||||
};
|
||||
use crate::spawn_future;
|
||||
use futures::channel::mpsc;
|
||||
use futures::lock::Mutex;
|
||||
use futures::StreamExt;
|
||||
@@ -20,7 +19,7 @@ use nym_sphinx::message::{NymMessage, PlainMessage};
|
||||
use nym_sphinx::params::ReplySurbKeyDigestAlgorithm;
|
||||
use nym_sphinx::receiver::{MessageReceiver, MessageRecoveryError, ReconstructedMessage};
|
||||
use nym_statistics_common::clients::{packet_statistics::PacketStatisticsEvent, ClientStatsSender};
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
@@ -172,7 +171,7 @@ struct ReceivedMessagesBuffer<R: MessageReceiver> {
|
||||
inner: Arc<Mutex<ReceivedMessagesBufferInner<R>>>,
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
}
|
||||
|
||||
impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
@@ -181,7 +180,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
stats_tx: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> Self {
|
||||
ReceivedMessagesBuffer {
|
||||
inner: Arc::new(Mutex::new(ReceivedMessagesBufferInner {
|
||||
@@ -195,7 +194,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
})),
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
task_client,
|
||||
shutdown_token,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,7 +315,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
reply_surbs,
|
||||
from_surb_request,
|
||||
) {
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
if !self.shutdown_token.is_cancelled() {
|
||||
error!("{err}");
|
||||
}
|
||||
}
|
||||
@@ -339,7 +338,7 @@ impl<R: MessageReceiver> ReceivedMessagesBuffer<R> {
|
||||
.reply_controller_sender
|
||||
.send_additional_surbs_request(*recipient, amount)
|
||||
{
|
||||
if !self.task_client.is_shutdown_poll() {
|
||||
if !self.shutdown_token.is_cancelled() {
|
||||
error!("{err}");
|
||||
}
|
||||
}
|
||||
@@ -466,22 +465,22 @@ pub enum ReceivedBufferMessage {
|
||||
ReceiverDisconnect,
|
||||
}
|
||||
|
||||
struct RequestReceiver<R: MessageReceiver> {
|
||||
pub(crate) struct RequestReceiver<R: MessageReceiver> {
|
||||
received_buffer: ReceivedMessagesBuffer<R>,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
}
|
||||
|
||||
impl<R: MessageReceiver> RequestReceiver<R> {
|
||||
fn new(
|
||||
received_buffer: ReceivedMessagesBuffer<R>,
|
||||
query_receiver: ReceivedBufferRequestReceiver,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> Self {
|
||||
RequestReceiver {
|
||||
received_buffer,
|
||||
query_receiver,
|
||||
task_client,
|
||||
shutdown_token,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -496,66 +495,70 @@ impl<R: MessageReceiver> RequestReceiver<R> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&mut self) {
|
||||
pub(crate) async fn run(&mut self) {
|
||||
debug!("Started RequestReceiver with graceful shutdown support");
|
||||
while !self.task_client.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.task_client.recv() => {
|
||||
_ = self.shutdown_token.cancelled() => {
|
||||
tracing::trace!("RequestReceiver: Received shutdown");
|
||||
break;
|
||||
}
|
||||
request = self.query_receiver.next() => {
|
||||
if let Some(message) = request {
|
||||
self.handle_message(message).await
|
||||
} else {
|
||||
tracing::trace!("RequestReceiver: Stopping since channel closed");
|
||||
self.shutdown_token.cancelled().await;
|
||||
break;
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
self.task_client.recv().await;
|
||||
tracing::debug!("RequestReceiver: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
struct FragmentedMessageReceiver<R: MessageReceiver> {
|
||||
pub(crate) struct FragmentedMessageReceiver<R: MessageReceiver> {
|
||||
received_buffer: ReceivedMessagesBuffer<R>,
|
||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
}
|
||||
|
||||
impl<R: MessageReceiver> FragmentedMessageReceiver<R> {
|
||||
fn new(
|
||||
received_buffer: ReceivedMessagesBuffer<R>,
|
||||
mixnet_packet_receiver: MixnetMessageReceiver,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> Self {
|
||||
FragmentedMessageReceiver {
|
||||
received_buffer,
|
||||
mixnet_packet_receiver,
|
||||
task_client,
|
||||
shutdown_token,
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&mut self) -> Result<(), MessageRecoveryError> {
|
||||
pub(crate) async fn run(&mut self) -> Result<(), MessageRecoveryError> {
|
||||
debug!("Started FragmentedMessageReceiver with graceful shutdown support");
|
||||
while !self.task_client.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.shutdown_token.cancelled() => {
|
||||
tracing::trace!("FragmentedMessageReceiver: Received shutdown");
|
||||
break;
|
||||
}
|
||||
new_messages = self.mixnet_packet_receiver.next() => {
|
||||
if let Some(new_messages) = new_messages {
|
||||
self.received_buffer.handle_new_received(new_messages).await?;
|
||||
} else {
|
||||
tracing::trace!("FragmentedMessageReceiver: Stopping since channel closed");
|
||||
self.shutdown_token.cancelled().await;
|
||||
break;
|
||||
}
|
||||
},
|
||||
_ = self.task_client.recv_with_delay() => {
|
||||
tracing::trace!("FragmentedMessageReceiver: Received shutdown");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
tracing::debug!("FragmentedMessageReceiver: Exiting");
|
||||
Ok(())
|
||||
}
|
||||
@@ -574,48 +577,31 @@ impl<R: MessageReceiver + Clone + Send + 'static> ReceivedMessagesBufferControll
|
||||
reply_key_storage: SentReplyKeys,
|
||||
reply_controller_sender: ReplyControllerSender,
|
||||
metrics_reporter: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> Self {
|
||||
let received_buffer = ReceivedMessagesBuffer::new(
|
||||
local_encryption_keypair,
|
||||
reply_key_storage,
|
||||
reply_controller_sender,
|
||||
metrics_reporter,
|
||||
task_client.fork("received_messages_buffer"),
|
||||
shutdown_token.clone(),
|
||||
);
|
||||
|
||||
ReceivedMessagesBufferController {
|
||||
fragmented_message_receiver: FragmentedMessageReceiver::new(
|
||||
received_buffer.clone(),
|
||||
mixnet_packet_receiver,
|
||||
task_client.fork("fragmented_message_receiver"),
|
||||
shutdown_token.clone(),
|
||||
),
|
||||
request_receiver: RequestReceiver::new(
|
||||
received_buffer,
|
||||
query_receiver,
|
||||
task_client.with_suffix("request_receiver"),
|
||||
shutdown_token.clone(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(self) {
|
||||
let mut fragmented_message_receiver = self.fragmented_message_receiver;
|
||||
let mut request_receiver = self.request_receiver;
|
||||
|
||||
spawn_future!(
|
||||
async move {
|
||||
match fragmented_message_receiver.run().await {
|
||||
Ok(_) => {}
|
||||
Err(e) => error!("{e}"),
|
||||
}
|
||||
},
|
||||
"ReceivedMessagesBufferController::FragmentedMessageReceiver"
|
||||
);
|
||||
spawn_future!(
|
||||
async move {
|
||||
request_receiver.run().await;
|
||||
},
|
||||
"ReceivedMessagesBufferController::RequestReceiver"
|
||||
);
|
||||
pub(crate) fn into_tasks(self) -> (FragmentedMessageReceiver<R>, RequestReceiver<R>) {
|
||||
(self.fragmented_message_receiver, self.request_receiver)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::client::replies::reply_controller::key_rotation_helpers::KeyRotationC
|
||||
use crate::client::replies::reply_storage::CombinedReplyStorage;
|
||||
use crate::config;
|
||||
use futures::StreamExt;
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use rand::rngs::OsRng;
|
||||
use rand::{CryptoRng, Rng};
|
||||
use std::time::Duration;
|
||||
@@ -60,9 +60,6 @@ pub struct ReplyController<R> {
|
||||
receiver_controller: ReceiverReplyController<R>,
|
||||
|
||||
request_receiver: ReplyControllerReceiver,
|
||||
|
||||
// Listen for shutdown signals
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl ReplyController<OsRng> {
|
||||
@@ -71,7 +68,6 @@ impl ReplyController<OsRng> {
|
||||
message_handler: MessageHandler<OsRng>,
|
||||
full_reply_storage: CombinedReplyStorage,
|
||||
request_receiver: ReplyControllerReceiver,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
ReplyController {
|
||||
config,
|
||||
@@ -86,7 +82,6 @@ impl ReplyController<OsRng> {
|
||||
message_handler,
|
||||
),
|
||||
request_receiver,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,22 +143,21 @@ where
|
||||
self.sender_controller.inspect_and_clear_stale_data(now)
|
||||
}
|
||||
|
||||
pub(crate) async fn run(&mut self) {
|
||||
pub(crate) async fn run(&mut self, shutdown_token: ShutdownToken) {
|
||||
debug!("Started ReplyController with graceful shutdown support");
|
||||
|
||||
let mut shutdown = self.task_client.fork("reply-controller");
|
||||
|
||||
let polling_rate = Duration::from_secs(5);
|
||||
let mut stale_inspection = new_interval_stream(polling_rate);
|
||||
|
||||
let polling_rate = self.config.key_rotation.epoch_duration / 8;
|
||||
let mut invalidation_inspection = new_interval_stream(polling_rate);
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
_ = shutdown_token.cancelled() => {
|
||||
tracing::trace!("ReplyController: Received shutdown");
|
||||
break;
|
||||
},
|
||||
req = self.request_receiver.next() => match req {
|
||||
Some(req) => self.handle_request(req).await,
|
||||
@@ -181,7 +175,6 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(shutdown.is_shutdown_poll());
|
||||
tracing::debug!("ReplyController: Exiting");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,21 +16,17 @@
|
||||
#![warn(clippy::todo)]
|
||||
#![warn(clippy::dbg_macro)]
|
||||
|
||||
use crate::client::inbound_messages::{InputMessage, InputMessageSender};
|
||||
use futures::StreamExt;
|
||||
use nym_client_core_config_types::StatsReporting;
|
||||
use nym_sphinx::addressing::Recipient;
|
||||
use nym_statistics_common::clients::{
|
||||
ClientStatsController, ClientStatsReceiver, ClientStatsSender,
|
||||
};
|
||||
use nym_task::{connections::TransmissionLane, TaskClient};
|
||||
use nym_task::{connections::TransmissionLane, ShutdownToken, ShutdownTracker};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::{
|
||||
client::inbound_messages::{InputMessage, InputMessageSender},
|
||||
spawn_future,
|
||||
};
|
||||
|
||||
/// Time interval between reporting statistics locally (logging/task_client)
|
||||
/// Time interval between reporting statistics locally (logging/shutdown_token)
|
||||
const LOCAL_REPORT_INTERVAL: Duration = Duration::from_secs(2);
|
||||
/// Interval for taking snapshots of the statistics
|
||||
const SNAPSHOT_INTERVAL: Duration = Duration::from_millis(500);
|
||||
@@ -51,9 +47,6 @@ pub(crate) struct StatisticsControl {
|
||||
|
||||
/// Config for stats reporting (enabled, address, interval)
|
||||
reporting_config: StatsReporting,
|
||||
|
||||
/// Task client for listening for shutdown
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl StatisticsControl {
|
||||
@@ -62,24 +55,20 @@ impl StatisticsControl {
|
||||
client_type: String,
|
||||
client_stats_id: String,
|
||||
report_tx: InputMessageSender,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> (Self, ClientStatsSender) {
|
||||
let (stats_tx, stats_rx) = tokio::sync::mpsc::unbounded_channel();
|
||||
|
||||
let stats = ClientStatsController::new(client_stats_id, client_type);
|
||||
|
||||
let mut task_client_stats_sender = task_client.fork("stats_sender");
|
||||
task_client_stats_sender.disarm();
|
||||
|
||||
(
|
||||
StatisticsControl {
|
||||
stats,
|
||||
stats_rx,
|
||||
report_tx,
|
||||
reporting_config,
|
||||
task_client,
|
||||
},
|
||||
ClientStatsSender::new(Some(stats_tx), task_client_stats_sender),
|
||||
ClientStatsSender::new(Some(stats_tx), shutdown_token),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -91,6 +80,7 @@ impl StatisticsControl {
|
||||
stats_report.into(),
|
||||
TransmissionLane::General,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
if let Err(err) = self.report_tx.send(report_message).await {
|
||||
tracing::error!("Failed to report client stats: {err:?}");
|
||||
@@ -99,7 +89,8 @@ impl StatisticsControl {
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(&mut self) {
|
||||
// manually control the shutdown mechanism as we don't want to get interrupted mid-snapshot
|
||||
pub async fn run(&mut self, shutdown_token: ShutdownToken) {
|
||||
tracing::debug!("Started StatisticsControl with graceful shutdown support");
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@@ -129,10 +120,10 @@ impl StatisticsControl {
|
||||
let mut snapshot_interval =
|
||||
gloo_timers::future::IntervalStream::new(SNAPSHOT_INTERVAL.as_millis() as u32);
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.task_client.recv() => {
|
||||
_ = shutdown_token.cancelled() => {
|
||||
tracing::trace!("StatisticsControl: Received shutdown");
|
||||
break;
|
||||
},
|
||||
@@ -157,37 +148,34 @@ impl StatisticsControl {
|
||||
}
|
||||
|
||||
_ = local_report_interval.next() => {
|
||||
self.stats.local_report(&mut self.task_client);
|
||||
self.stats.local_report();
|
||||
}
|
||||
}
|
||||
}
|
||||
tracing::debug!("StatisticsControl: Exiting");
|
||||
}
|
||||
|
||||
pub(crate) fn start(mut self) {
|
||||
spawn_future!(
|
||||
async move {
|
||||
self.run().await;
|
||||
},
|
||||
"StatisticsControl"
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn create_and_start(
|
||||
reporting_config: StatsReporting,
|
||||
client_type: String,
|
||||
client_stats_id: String,
|
||||
report_tx: InputMessageSender,
|
||||
task_client: TaskClient,
|
||||
shutdown_tracker: &ShutdownTracker,
|
||||
) -> ClientStatsSender {
|
||||
let (controller, sender) = Self::create(
|
||||
let (mut controller, sender) = Self::create(
|
||||
reporting_config,
|
||||
client_type,
|
||||
client_stats_id,
|
||||
report_tx,
|
||||
task_client,
|
||||
shutdown_tracker.child_shutdown_token(),
|
||||
);
|
||||
let shutdown_token = shutdown_tracker.clone_shutdown_token();
|
||||
shutdown_tracker.try_spawn_named(
|
||||
async move {
|
||||
controller.run(shutdown_token).await;
|
||||
},
|
||||
"StatisticsControl",
|
||||
);
|
||||
controller.start();
|
||||
sender
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
// Copyright 2021-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::spawn_future;
|
||||
pub(crate) use accessor::{TopologyAccessor, TopologyReadPermit};
|
||||
use futures::StreamExt;
|
||||
use nym_sphinx::addressing::nodes::NodeIdentity;
|
||||
use nym_task::TaskClient;
|
||||
use nym_topology::NymTopologyError;
|
||||
use std::time::Duration;
|
||||
use tracing::*;
|
||||
@@ -41,8 +39,6 @@ pub struct TopologyRefresher {
|
||||
|
||||
refresh_rate: Duration,
|
||||
consecutive_failure_count: usize,
|
||||
|
||||
task_client: TaskClient,
|
||||
}
|
||||
|
||||
impl TopologyRefresher {
|
||||
@@ -50,14 +46,12 @@ impl TopologyRefresher {
|
||||
cfg: TopologyRefresherConfig,
|
||||
topology_accessor: TopologyAccessor,
|
||||
topology_provider: Box<dyn TopologyProvider + Send + Sync>,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
TopologyRefresher {
|
||||
topology_provider,
|
||||
topology_accessor,
|
||||
refresh_rate: cfg.refresh_rate,
|
||||
consecutive_failure_count: 0,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,40 +138,30 @@ impl TopologyRefresher {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start(mut self) {
|
||||
spawn_future!(
|
||||
async move {
|
||||
debug!("Started TopologyRefresher with graceful shutdown support");
|
||||
// it's perfectly fine if task is interrupted mid-refresh
|
||||
// there's no data to persist or send over
|
||||
pub async fn run(&mut self) {
|
||||
debug!("Started TopologyRefresher with graceful shutdown support");
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let mut interval = tokio_stream::wrappers::IntervalStream::new(
|
||||
tokio::time::interval(self.refresh_rate),
|
||||
);
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let mut interval =
|
||||
tokio_stream::wrappers::IntervalStream::new(tokio::time::interval(self.refresh_rate));
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let mut interval =
|
||||
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let mut interval =
|
||||
gloo_timers::future::IntervalStream::new(self.refresh_rate.as_millis() as u32);
|
||||
|
||||
// We already have an initial topology, so no need to refresh it immediately.
|
||||
// My understanding is that js setInterval does not fire immediately, so it's not
|
||||
// needed there.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
interval.next().await;
|
||||
// We already have an initial topology, so no need to refresh it immediately.
|
||||
// My understanding is that js setInterval does not fire immediately, so it's not
|
||||
// needed there.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
interval.next().await;
|
||||
|
||||
while !self.task_client.is_shutdown() {
|
||||
tokio::select! {
|
||||
_ = interval.next() => {
|
||||
self.try_refresh().await;
|
||||
},
|
||||
_ = self.task_client.recv() => {
|
||||
tracing::trace!("TopologyRefresher: Received shutdown");
|
||||
},
|
||||
}
|
||||
}
|
||||
self.task_client.recv_timeout().await;
|
||||
tracing::debug!("TopologyRefresher: Exiting");
|
||||
},
|
||||
"TopologyRefresher"
|
||||
)
|
||||
while interval.next().await.is_some() {
|
||||
self.try_refresh().await;
|
||||
}
|
||||
|
||||
// this should never get triggered
|
||||
error!("topology refresher interval has been exhausted!")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use async_trait::async_trait;
|
||||
use nym_mixnet_contract_common::EpochRewardedSet;
|
||||
use nym_topology::provider_trait::{ToTopologyMetadata, TopologyProvider};
|
||||
use nym_topology::NymTopology;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use std::cmp::min;
|
||||
@@ -39,30 +41,43 @@ impl Config {
|
||||
pub struct NymApiTopologyProvider {
|
||||
config: Config,
|
||||
|
||||
validator_client: nym_validator_client::client::NymApiClient,
|
||||
validator_client: nym_http_api_client::Client,
|
||||
nym_api_urls: Vec<Url>,
|
||||
currently_used_api: usize,
|
||||
use_bincode: bool,
|
||||
}
|
||||
|
||||
impl NymApiTopologyProvider {
|
||||
pub fn new(
|
||||
config: impl Into<Config>,
|
||||
mut nym_api_urls: Vec<Url>,
|
||||
mut validator_client: nym_validator_client::client::NymApiClient,
|
||||
validator_client: nym_http_api_client::Client,
|
||||
) -> Self {
|
||||
nym_api_urls.shuffle(&mut thread_rng());
|
||||
validator_client.change_nym_api(nym_api_urls[0].clone());
|
||||
|
||||
NymApiTopologyProvider {
|
||||
let mut provider = NymApiTopologyProvider {
|
||||
config: config.into(),
|
||||
validator_client,
|
||||
nym_api_urls,
|
||||
currently_used_api: 0,
|
||||
}
|
||||
use_bincode: true,
|
||||
};
|
||||
// Set all API URLs - the client will try them in order with automatic failover
|
||||
provider.validator_client.change_base_urls(
|
||||
provider
|
||||
.nym_api_urls
|
||||
.iter()
|
||||
.map(|u| u.clone().into())
|
||||
.collect(),
|
||||
);
|
||||
provider
|
||||
}
|
||||
|
||||
pub fn disable_bincode(&mut self) {
|
||||
self.validator_client.use_bincode = false;
|
||||
self.use_bincode = false;
|
||||
// Note: The unified client doesn't support toggling bincode after creation.
|
||||
// This would require recreating the client without bincode.
|
||||
// For now, we'll track the preference but it won't take effect.
|
||||
warn!("Disabling bincode on existing client is not currently supported");
|
||||
}
|
||||
|
||||
fn use_next_nym_api(&mut self) {
|
||||
@@ -72,8 +87,19 @@ impl NymApiTopologyProvider {
|
||||
}
|
||||
|
||||
self.currently_used_api = (self.currently_used_api + 1) % self.nym_api_urls.len();
|
||||
self.validator_client
|
||||
.change_nym_api(self.nym_api_urls[self.currently_used_api].clone())
|
||||
|
||||
// Provide all URLs starting from the next one in rotation order
|
||||
// This enables automatic failover to other endpoints
|
||||
let rotated_urls: Vec<_> = self
|
||||
.nym_api_urls
|
||||
.iter()
|
||||
.cycle()
|
||||
.skip(self.currently_used_api)
|
||||
.take(self.nym_api_urls.len())
|
||||
.map(|u| u.clone().into())
|
||||
.collect();
|
||||
|
||||
self.validator_client.change_base_urls(rotated_urls)
|
||||
}
|
||||
|
||||
async fn get_current_compatible_topology(&mut self) -> Option<NymTopology> {
|
||||
@@ -99,8 +125,13 @@ impl NymApiTopologyProvider {
|
||||
.filter(|n| n.performance.round_to_integer() >= self.config.min_node_performance())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
NymTopology::new(metadata.to_topology_metadata(), rewarded_set, Vec::new())
|
||||
.with_skimmed_nodes(&nodes_filtered)
|
||||
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
|
||||
NymTopology::new(
|
||||
metadata.to_topology_metadata(),
|
||||
epoch_rewarded_set,
|
||||
Vec::new(),
|
||||
)
|
||||
.with_skimmed_nodes(&nodes_filtered)
|
||||
} else {
|
||||
// if we're not using extended topology, we're only getting active set mixnodes and gateways
|
||||
|
||||
@@ -148,8 +179,13 @@ impl NymApiTopologyProvider {
|
||||
}
|
||||
}
|
||||
|
||||
NymTopology::new(metadata.to_topology_metadata(), rewarded_set, Vec::new())
|
||||
.with_skimmed_nodes(&nodes)
|
||||
let epoch_rewarded_set: EpochRewardedSet = rewarded_set.into();
|
||||
NymTopology::new(
|
||||
metadata.to_topology_metadata(),
|
||||
epoch_rewarded_set,
|
||||
Vec::new(),
|
||||
)
|
||||
.with_skimmed_nodes(&nodes)
|
||||
};
|
||||
|
||||
if !topology.is_minimally_routable() {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use crate::client::mix_traffic::transceiver::ErasedGatewayError;
|
||||
use nym_crypto::asymmetric::ed25519::Ed25519RecoveryError;
|
||||
use nym_gateway_client::error::GatewayClientError;
|
||||
use nym_task::RegistryAccessError;
|
||||
use nym_topology::node::RoutingNodeError;
|
||||
use nym_topology::{NodeId, NymTopologyError};
|
||||
use nym_validator_client::nym_api::error::NymAPIError;
|
||||
@@ -56,10 +57,7 @@ pub enum ClientCoreError {
|
||||
ListOfNymApisIsEmpty,
|
||||
|
||||
#[error("failed to resolve a query to nym API: {source}")]
|
||||
NymApiQueryFailure {
|
||||
#[from]
|
||||
source: NymAPIError,
|
||||
},
|
||||
NymApiQueryFailure { source: Box<NymAPIError> },
|
||||
|
||||
#[error(
|
||||
"the current network topology seem to be insufficient to route any packets through:\n\t{0}"
|
||||
@@ -245,6 +243,9 @@ pub enum ClientCoreError {
|
||||
|
||||
#[error("failed to select valid gateway due to incomputable latency")]
|
||||
GatewaySelectionFailure { source: WeightedError },
|
||||
|
||||
#[error("Could not access task registry, {0}")]
|
||||
RegistryAccess(#[from] RegistryAccessError),
|
||||
}
|
||||
|
||||
impl From<tungstenite::Error> for ClientCoreError {
|
||||
@@ -255,6 +256,14 @@ impl From<tungstenite::Error> for ClientCoreError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NymAPIError> for ClientCoreError {
|
||||
fn from(err: NymAPIError) -> ClientCoreError {
|
||||
ClientCoreError::NymApiQueryFailure {
|
||||
source: Box::new(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set of messages that the client can send to listeners via the task manager
|
||||
#[derive(Debug)]
|
||||
pub enum ClientCoreStatusMessage {
|
||||
|
||||
@@ -7,7 +7,8 @@ use futures::{SinkExt, StreamExt};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
use nym_topology::node::RoutingNode;
|
||||
use nym_validator_client::client::IdentityKeyRef;
|
||||
use nym_validator_client::client::{IdentityKeyRef, NymApiClientExt};
|
||||
use nym_validator_client::nym_nodes::SkimmedNodesWithMetadata;
|
||||
use nym_validator_client::UserAgent;
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
#[cfg(unix)]
|
||||
@@ -83,6 +84,48 @@ struct GatewayWithLatency<'a, G: ConnectableGateway> {
|
||||
latency: Duration,
|
||||
}
|
||||
|
||||
// Helper to collect all pages of entry nodes - replicates NymApiClient's convenience method
|
||||
async fn get_all_basic_entry_nodes_with_metadata(
|
||||
client: &nym_http_api_client::Client,
|
||||
use_bincode: bool,
|
||||
) -> Result<SkimmedNodesWithMetadata, ClientCoreError> {
|
||||
// Get first page to obtain metadata
|
||||
let mut page = 0;
|
||||
let res = client
|
||||
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, use_bincode)
|
||||
.await?;
|
||||
let mut nodes = res.nodes.data;
|
||||
let metadata = res.metadata;
|
||||
|
||||
if res.nodes.pagination.total == nodes.len() {
|
||||
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
|
||||
}
|
||||
|
||||
page += 1;
|
||||
|
||||
// Collect remaining pages
|
||||
loop {
|
||||
let mut res = client
|
||||
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, use_bincode)
|
||||
.await?;
|
||||
|
||||
if !metadata.consistency_check(&res.metadata) {
|
||||
return Err(ClientCoreError::ValidatorClientError(
|
||||
nym_validator_client::ValidatorClientError::InconsistentPagedMetadata,
|
||||
));
|
||||
}
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
if nodes.len() < res.nodes.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
}
|
||||
|
||||
impl<'a, G: ConnectableGateway> GatewayWithLatency<'a, G> {
|
||||
fn new(gateway: &'a G, latency: Duration) -> Self {
|
||||
GatewayWithLatency { gateway, latency }
|
||||
@@ -99,16 +142,28 @@ pub async fn gateways_for_init<R: Rng>(
|
||||
let nym_api = nym_apis
|
||||
.choose(rng)
|
||||
.ok_or(ClientCoreError::ListOfNymApisIsEmpty)?;
|
||||
let client = if let Some(user_agent) = user_agent {
|
||||
nym_validator_client::client::NymApiClient::new_with_user_agent(nym_api.clone(), user_agent)
|
||||
} else {
|
||||
nym_validator_client::client::NymApiClient::new(nym_api.clone())
|
||||
};
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
let client = builder.build().map_err(|e| {
|
||||
ClientCoreError::ValidatorClientError(nym_validator_client::ValidatorClientError::from(e))
|
||||
})?;
|
||||
|
||||
tracing::debug!("Fetching list of gateways from: {nym_api}");
|
||||
|
||||
let gateways = client
|
||||
.get_all_basic_entry_assigned_nodes_with_metadata()
|
||||
// Use our helper to handle pagination
|
||||
let gateways = get_all_basic_entry_nodes_with_metadata(&client, true)
|
||||
.await?
|
||||
.nodes;
|
||||
info!("nym api reports {} gateways", gateways.len());
|
||||
|
||||
@@ -17,7 +17,9 @@ pub use nym_topology::{
|
||||
HardcodedTopologyProvider, NymRouteProvider, NymTopology, NymTopologyError, TopologyProvider,
|
||||
};
|
||||
|
||||
#[deprecated(note = "use spawn_future from nym_task crate instead")]
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[track_caller]
|
||||
pub fn spawn_future<F>(future: F)
|
||||
where
|
||||
F: Future<Output = ()> + 'static,
|
||||
@@ -25,9 +27,7 @@ where
|
||||
wasm_bindgen_futures::spawn_local(future);
|
||||
}
|
||||
|
||||
// TODO: expose similar API to the rest of the codebase,
|
||||
// perhaps with some simple trait for a task to define its name
|
||||
|
||||
#[deprecated(note = "use spawn_future from nym_task crate instead")]
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[track_caller]
|
||||
pub fn spawn_future<F>(future: F)
|
||||
@@ -37,35 +37,3 @@ where
|
||||
{
|
||||
tokio::spawn(future);
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[track_caller]
|
||||
pub fn spawn_named_future<F>(future: F, name: &str)
|
||||
where
|
||||
F: Future + Send + 'static,
|
||||
F::Output: Send + 'static,
|
||||
{
|
||||
cfg_if::cfg_if! {if #[cfg(tokio_unstable)] {
|
||||
#[allow(clippy::expect_used)]
|
||||
tokio::task::Builder::new().name(name).spawn(future).expect("failed to spawn future");
|
||||
} else {
|
||||
let _ = name;
|
||||
tracing::debug!(r#"the underlying binary hasn't been built with `RUSTFLAGS="--cfg tokio_unstable"` - the future naming won't do anything"#);
|
||||
spawn_future(future);
|
||||
}}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! spawn_future {
|
||||
($future:expr) => {{
|
||||
$crate::spawn_future($future)
|
||||
}};
|
||||
($future:expr, $name:expr) => {{
|
||||
cfg_if::cfg_if! {if #[cfg(not(target_arch = "wasm32"))] {
|
||||
$crate::spawn_named_future($future, $name)
|
||||
} else {
|
||||
let _ = $name;
|
||||
$crate::spawn_future($future)
|
||||
}}
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ where
|
||||
pub async fn flush_on_shutdown(
|
||||
mut self,
|
||||
mem_state: CombinedReplyStorage,
|
||||
mut shutdown: nym_task::TaskClient,
|
||||
shutdown: nym_task::ShutdownToken,
|
||||
) {
|
||||
use tracing::{debug, error, info};
|
||||
|
||||
@@ -50,7 +50,7 @@ where
|
||||
return;
|
||||
}
|
||||
|
||||
shutdown.recv().await;
|
||||
shutdown.cancelled().await;
|
||||
|
||||
info!("PersistentReplyStorage is flushing all reply-related data to underlying storage");
|
||||
if let Err(err) = self.backend.flush_surb_storage(&mem_state).await {
|
||||
|
||||
@@ -12,7 +12,7 @@ use crate::socket_state::{ws_fd, PartiallyDelegatedHandle, SocketState};
|
||||
use crate::traits::GatewayPacketRouter;
|
||||
use crate::{cleanup_socket_message, try_decrypt_binary_message};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use nym_bandwidth_controller::{BandwidthController, BandwidthStatusMessage};
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
use nym_credential_storage::ephemeral_storage::EphemeralStorage as EphemeralCredentialStorage;
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_credentials::CredentialSpendingData;
|
||||
@@ -27,7 +27,7 @@ use nym_gateway_requests::{
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_statistics_common::clients::connection::ConnectionStatsEvent;
|
||||
use nym_statistics_common::clients::ClientStatsSender;
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use rand::rngs::OsRng;
|
||||
use std::sync::Arc;
|
||||
@@ -109,7 +109,7 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
|
||||
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
|
||||
/// Listen to shutdown messages and send notifications back to the task manager
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
}
|
||||
|
||||
impl<C, St> GatewayClient<C, St> {
|
||||
@@ -124,7 +124,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
stats_reporter: ClientStatsSender,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> Self {
|
||||
GatewayClient {
|
||||
cfg,
|
||||
@@ -141,7 +141,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
negotiated_protocol: None,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
task_client,
|
||||
shutdown_token,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = self.task_client.recv() => {
|
||||
_ = self.shutdown_token.cancelled() => {
|
||||
log::trace!("GatewayClient control response: Received shutdown");
|
||||
log::debug!("GatewayClient control response: Exiting");
|
||||
break Err(GatewayClientError::ConnectionClosedGatewayShutdown);
|
||||
@@ -514,7 +514,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.cfg.bandwidth.require_tickets,
|
||||
derive_aes256_gcm_siv_key,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
self.task_client.clone(),
|
||||
self.shutdown_token.clone(),
|
||||
)
|
||||
.await
|
||||
.map_err(GatewayClientError::RegistrationFailure),
|
||||
@@ -631,9 +631,6 @@ impl<C, St> GatewayClient<C, St> {
|
||||
self.negotiated_protocol = protocol_version;
|
||||
log::debug!("authenticated: {status}, bandwidth remaining: {bandwidth_remaining}");
|
||||
|
||||
self.task_client.send_status_msg(Box::new(
|
||||
BandwidthStatusMessage::RemainingBandwidth(bandwidth_remaining),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
ServerResponse::Error { message } => Err(GatewayClientError::GatewayError(message)),
|
||||
@@ -1046,6 +1043,12 @@ impl<C, St> GatewayClient<C, St> {
|
||||
}
|
||||
|
||||
// Note: this requires prior authentication
|
||||
#[instrument(skip_all,
|
||||
fields(
|
||||
gateway = %self.gateway_identity,
|
||||
gateway_address = %self.gateway_address
|
||||
)
|
||||
)]
|
||||
pub fn start_listening_for_mixnet_messages(&mut self) -> Result<(), GatewayClientError> {
|
||||
if !self.authenticated {
|
||||
return Err(GatewayClientError::NotAuthenticated);
|
||||
@@ -1069,7 +1072,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
.expect("no shared key present even though we're authenticated!"),
|
||||
),
|
||||
self.bandwidth.clone(),
|
||||
self.task_client.clone(),
|
||||
self.shutdown_token.clone(),
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
@@ -1143,8 +1146,8 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
// perfectly fine here, because it's not meant to be used
|
||||
let (ack_tx, _) = mpsc::unbounded();
|
||||
let (mix_tx, _) = mpsc::unbounded();
|
||||
let task_client = TaskClient::dummy();
|
||||
let packet_router = PacketRouter::new(ack_tx, mix_tx, task_client.clone());
|
||||
let shutdown_token = ShutdownToken::default();
|
||||
let packet_router = PacketRouter::new(ack_tx, mix_tx, shutdown_token.clone());
|
||||
|
||||
GatewayClient {
|
||||
cfg: GatewayClientConfig::default().with_disabled_credentials_mode(true),
|
||||
@@ -1157,11 +1160,11 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
connection: SocketState::NotConnected,
|
||||
packet_router,
|
||||
bandwidth_controller: None,
|
||||
stats_reporter: ClientStatsSender::new(None, task_client.clone()),
|
||||
stats_reporter: ClientStatsSender::new(None, shutdown_token.clone()),
|
||||
negotiated_protocol: None,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
task_client,
|
||||
shutdown_token,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1170,7 +1173,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
packet_router: PacketRouter,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
stats_reporter: ClientStatsSender,
|
||||
task_client: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> GatewayClient<C, St> {
|
||||
// invariants that can't be broken
|
||||
// (unless somebody decided to expose some field that wasn't meant to be exposed)
|
||||
@@ -1193,7 +1196,7 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
negotiated_protocol: self.negotiated_protocol,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: self.connection_fd_callback,
|
||||
task_client,
|
||||
shutdown_token,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
use crate::error::GatewayClientError;
|
||||
use crate::GatewayPacketRouter;
|
||||
use futures::channel::mpsc;
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
|
||||
pub type MixnetMessageSender = mpsc::UnboundedSender<Vec<Vec<u8>>>;
|
||||
pub type MixnetMessageReceiver = mpsc::UnboundedReceiver<Vec<Vec<u8>>>;
|
||||
@@ -19,14 +19,14 @@ pub type AcknowledgementReceiver = mpsc::UnboundedReceiver<Vec<Vec<u8>>>;
|
||||
pub struct PacketRouter {
|
||||
ack_sender: AcknowledgementSender,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
shutdown: TaskClient,
|
||||
shutdown: ShutdownToken,
|
||||
}
|
||||
|
||||
impl PacketRouter {
|
||||
pub fn new(
|
||||
ack_sender: AcknowledgementSender,
|
||||
mixnet_message_sender: MixnetMessageSender,
|
||||
shutdown: TaskClient,
|
||||
shutdown: ShutdownToken,
|
||||
) -> Self {
|
||||
PacketRouter {
|
||||
ack_sender,
|
||||
@@ -42,7 +42,7 @@ impl PacketRouter {
|
||||
if let Err(err) = self.mixnet_message_sender.unbounded_send(received_messages) {
|
||||
// check if the failure is due to the shutdown being in progress and thus the receiver channel
|
||||
// having already been dropped
|
||||
if self.shutdown.is_shutdown_poll() || self.shutdown.is_dummy() {
|
||||
if self.shutdown.is_cancelled() {
|
||||
// This should ideally not happen, but it's ok
|
||||
tracing::warn!("Failed to send mixnet messages due to receiver task shutdown");
|
||||
return Err(GatewayClientError::ShutdownInProgress);
|
||||
@@ -58,7 +58,7 @@ impl PacketRouter {
|
||||
if let Err(err) = self.ack_sender.unbounded_send(received_acks) {
|
||||
// check if the failure is due to the shutdown being in progress and thus the receiver channel
|
||||
// having already been dropped
|
||||
if self.shutdown.is_shutdown_poll() || self.shutdown.is_dummy() {
|
||||
if self.shutdown.is_cancelled() {
|
||||
// This should ideally not happen, but it's ok
|
||||
tracing::warn!("Failed to send acks due to receiver task shutdown");
|
||||
return Err(GatewayClientError::ShutdownInProgress);
|
||||
@@ -69,10 +69,6 @@ impl PacketRouter {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn disarm(&mut self) {
|
||||
self.shutdown.disarm();
|
||||
}
|
||||
}
|
||||
|
||||
impl GatewayPacketRouter for PacketRouter {
|
||||
|
||||
@@ -11,7 +11,7 @@ use futures::stream::{SplitSink, SplitStream};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
||||
use nym_gateway_requests::{SensitiveServerResponse, ServerResponse, SimpleGatewayRequestsError};
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use si_scale::helpers::bibytes2;
|
||||
use std::os::raw::c_int as RawFd;
|
||||
use std::sync::Arc;
|
||||
@@ -87,13 +87,13 @@ impl PartiallyDelegatedRouter {
|
||||
}
|
||||
}
|
||||
|
||||
async fn run(mut self, mut split_stream: SplitStream<WsConn>, mut task_client: TaskClient) {
|
||||
async fn run(mut self, mut split_stream: SplitStream<WsConn>, shutdown_token: ShutdownToken) {
|
||||
let mut chunked_stream = (&mut split_stream).ready_chunks(8);
|
||||
let ret: Result<_, GatewayClientError> = loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
// received system-wide shutdown
|
||||
_ = task_client.recv() => {
|
||||
_ = shutdown_token.cancelled() => {
|
||||
log::trace!("GatewayClient listener: Received shutdown");
|
||||
log::debug!("GatewayClient listener: Exiting");
|
||||
return;
|
||||
@@ -118,11 +118,7 @@ impl PartiallyDelegatedRouter {
|
||||
|
||||
let return_res = match ret {
|
||||
Err(err) => self.stream_return.send(Err(err)),
|
||||
Ok(_) => {
|
||||
self.packet_router.disarm();
|
||||
task_client.disarm();
|
||||
self.stream_return.send(Ok(split_stream))
|
||||
}
|
||||
Ok(_) => self.stream_return.send(Ok(split_stream)),
|
||||
};
|
||||
|
||||
if return_res.is_err() {
|
||||
@@ -266,8 +262,8 @@ impl PartiallyDelegatedRouter {
|
||||
Ok(plaintexts)
|
||||
}
|
||||
|
||||
fn spawn(self, split_stream: SplitStream<WsConn>, task_client: TaskClient) {
|
||||
let fut = async move { self.run(split_stream, task_client).await };
|
||||
fn spawn(self, split_stream: SplitStream<WsConn>, shutdown_token: ShutdownToken) {
|
||||
let fut = async move { self.run(split_stream, shutdown_token).await };
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
wasm_bindgen_futures::spawn_local(fut);
|
||||
@@ -283,7 +279,7 @@ impl PartiallyDelegatedHandle {
|
||||
packet_router: PacketRouter,
|
||||
shared_key: Arc<SharedGatewayKey>,
|
||||
client_bandwidth: ClientBandwidth,
|
||||
shutdown: TaskClient,
|
||||
shutdown: ShutdownToken,
|
||||
) -> Self {
|
||||
// when called for, it NEEDS TO yield back the stream so that we could merge it and
|
||||
// read control request responses.
|
||||
|
||||
@@ -5,8 +5,8 @@ use crate::nyxd::{self, NyxdClient};
|
||||
use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
use crate::signing::signer::{NoSigner, OfflineSigner};
|
||||
use crate::{
|
||||
nym_api, DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient,
|
||||
ReqwestRpcClient, ValidatorClientError,
|
||||
DirectSigningReqwestRpcValidatorClient, QueryReqwestRpcValidatorClient, ReqwestRpcClient,
|
||||
ValidatorClientError,
|
||||
};
|
||||
use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
@@ -20,11 +20,9 @@ use nym_api_requests::ecash::{
|
||||
PartialExpirationDateSignatureResponse, VerificationKeyResponse,
|
||||
};
|
||||
use nym_api_requests::models::{
|
||||
ApiHealthResponse, GatewayBondAnnotated, GatewayCoreStatusResponse,
|
||||
HistoricalPerformanceResponse, MixnodeCoreStatusResponse, MixnodeStatusResponse,
|
||||
NymNodeDescription, RewardEstimationResponse, StakeSaturationResponse,
|
||||
ApiHealthResponse, GatewayCoreStatusResponse, HistoricalPerformanceResponse,
|
||||
MixnodeCoreStatusResponse, NymNodeDescription,
|
||||
};
|
||||
use nym_api_requests::models::{LegacyDescribedGateway, MixNodeBondAnnotated};
|
||||
use nym_api_requests::nym_nodes::{
|
||||
NodesByAddressesResponse, SemiSkimmedNodesWithMetadata, SkimmedNode, SkimmedNodesWithMetadata,
|
||||
};
|
||||
@@ -153,7 +151,7 @@ impl Config {
|
||||
pub struct Client<C, S = NoSigner> {
|
||||
// ideally they would have been read-only, but unfortunately rust doesn't have such features
|
||||
// #[deprecated(note = "please use `nym_api_client` instead")]
|
||||
pub nym_api: nym_api::Client,
|
||||
pub nym_api: nym_http_api_client::Client,
|
||||
// pub nym_api_client: NymApiClient,
|
||||
pub nyxd: NyxdClient<C, S>,
|
||||
}
|
||||
@@ -214,7 +212,7 @@ impl Client<ReqwestRpcClient> {
|
||||
|
||||
impl<C> Client<C> {
|
||||
pub fn new_with_rpc_client(config: Config, rpc_client: C) -> Self {
|
||||
let nym_api_client = nym_api::Client::new(config.api_url.clone(), None);
|
||||
let nym_api_client = nym_http_api_client::Client::new(config.api_url.clone(), None);
|
||||
|
||||
Client {
|
||||
nym_api: nym_api_client,
|
||||
@@ -228,7 +226,7 @@ impl<C, S> Client<C, S> {
|
||||
where
|
||||
S: OfflineSigner,
|
||||
{
|
||||
let nym_api_client = nym_api::Client::new(config.api_url.clone(), None);
|
||||
let nym_api_client = nym_http_api_client::Client::new(config.api_url.clone(), None);
|
||||
|
||||
Client {
|
||||
nym_api: nym_api_client,
|
||||
@@ -249,65 +247,6 @@ impl<C, S> Client<C, S> {
|
||||
self.nym_api.change_base_urls(vec![new_endpoint.into()])
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_mixnodes().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_mixnodes_detailed(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_mixnodes_detailed().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_mixnodes_detailed_unfiltered(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_mixnodes_detailed_unfiltered().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_rewarded_mixnodes(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_rewarded_mixnodes().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_rewarded_mixnodes_detailed(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_rewarded_mixnodes_detailed().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_active_mixnodes(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_active_mixnodes().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_active_mixnodes_detailed(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_active_mixnodes_detailed().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_gateways().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_gateways_detailed_unfiltered(
|
||||
&self,
|
||||
) -> Result<Vec<GatewayBondAnnotated>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_gateways_detailed_unfiltered().await?)
|
||||
}
|
||||
|
||||
pub async fn get_full_node_performance_history(
|
||||
&self,
|
||||
node_id: NodeId,
|
||||
@@ -385,38 +324,25 @@ impl<C, S> Client<C, S> {
|
||||
}
|
||||
}
|
||||
|
||||
/// DEPRECATED: Use nym_http_api_client::Client with from_network() or with_bincode() instead
|
||||
#[deprecated(
|
||||
since = "1.2.0",
|
||||
note = "Use nym_http_api_client::Client::from_network() or ClientBuilder::with_bincode() instead"
|
||||
)]
|
||||
#[derive(Clone)]
|
||||
pub struct NymApiClient {
|
||||
pub use_bincode: bool,
|
||||
pub nym_api: nym_api::Client,
|
||||
pub nym_api: nym_http_api_client::Client,
|
||||
// TODO: perhaps if we really need it at some (currently I don't see any reasons for it)
|
||||
// we could re-implement the communication with the REST API on port 1317
|
||||
}
|
||||
|
||||
impl From<nym_api::Client> for NymApiClient {
|
||||
fn from(nym_api: nym_api::Client) -> Self {
|
||||
NymApiClient {
|
||||
use_bincode: false,
|
||||
nym_api,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we have to allow the use of deprecated method here as they're calling the deprecated trait methods
|
||||
#[allow(deprecated)]
|
||||
impl NymApiClient {
|
||||
pub fn new(api_url: Url) -> Self {
|
||||
let nym_api = nym_api::Client::new(api_url, None);
|
||||
|
||||
NymApiClient {
|
||||
use_bincode: true,
|
||||
nym_api,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn new_with_timeout(api_url: Url, timeout: std::time::Duration) -> Self {
|
||||
let nym_api = nym_api::Client::new(api_url, Some(timeout));
|
||||
let nym_api = nym_http_api_client::Client::new(api_url, Some(timeout));
|
||||
|
||||
NymApiClient {
|
||||
use_bincode: true,
|
||||
@@ -431,10 +357,10 @@ impl NymApiClient {
|
||||
}
|
||||
|
||||
pub fn new_with_user_agent(api_url: Url, user_agent: impl Into<UserAgent>) -> Self {
|
||||
let nym_api = nym_api::Client::builder::<_, ValidatorClientError>(api_url)
|
||||
let nym_api = nym_http_api_client::Client::builder(api_url)
|
||||
.expect("invalid api url")
|
||||
.with_user_agent(user_agent.into())
|
||||
.build::<ValidatorClientError>()
|
||||
.build()
|
||||
.expect("failed to build nym api client");
|
||||
|
||||
NymApiClient {
|
||||
@@ -571,37 +497,6 @@ impl NymApiClient {
|
||||
Ok(self.nym_api.health().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_active_mixnodes(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_active_mixnodes().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_rewarded_mixnodes(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_rewarded_mixnodes().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_mixnodes(&self) -> Result<Vec<MixNodeDetails>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_mixnodes().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_gateways(&self) -> Result<Vec<GatewayBond>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_gateways().await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_cached_described_gateways(
|
||||
&self,
|
||||
) -> Result<Vec<LegacyDescribedGateway>, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_gateways_described().await?)
|
||||
}
|
||||
|
||||
pub async fn get_all_described_nodes(
|
||||
&self,
|
||||
) -> Result<Vec<NymNodeDescription>, ValidatorClientError> {
|
||||
@@ -668,30 +563,6 @@ impl NymApiClient {
|
||||
.await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_mixnode_status(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
) -> Result<MixnodeStatusResponse, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_mixnode_status(mix_id).await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_mixnode_reward_estimation(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
) -> Result<RewardEstimationResponse, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_mixnode_reward_estimation(mix_id).await?)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
pub async fn get_mixnode_stake_saturation(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
) -> Result<StakeSaturationResponse, ValidatorClientError> {
|
||||
Ok(self.nym_api.get_mixnode_stake_saturation(mix_id).await?)
|
||||
}
|
||||
|
||||
pub async fn blind_sign(
|
||||
&self,
|
||||
request_body: &BlindSignRequestBody,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
use crate::nyxd::contract_traits::{DkgQueryClient, PagedDkgQueryClient};
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::NymApiClient;
|
||||
use nym_coconut_dkg_common::types::{EpochId, NodeIndex};
|
||||
use nym_coconut_dkg_common::verification_key::ContractVKShare;
|
||||
use nym_compact_ecash::error::CompactEcashError;
|
||||
@@ -15,7 +14,7 @@ use url::Url;
|
||||
// TODO: it really doesn't feel like this should live in this crate.
|
||||
#[derive(Clone)]
|
||||
pub struct EcashApiClient {
|
||||
pub api_client: NymApiClient,
|
||||
pub api_client: nym_http_api_client::Client,
|
||||
pub verification_key: VerificationKeyAuth,
|
||||
pub node_id: NodeIndex,
|
||||
pub cosmos_address: cosmrs::AccountId,
|
||||
@@ -25,10 +24,15 @@ impl Display for EcashApiClient {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"[id: {}] {} @ {}",
|
||||
"[id: {}] {} @ ({})",
|
||||
self.node_id,
|
||||
self.cosmos_address,
|
||||
self.api_client.api_url()
|
||||
self.api_client
|
||||
.base_urls()
|
||||
.iter()
|
||||
.map(|url| url.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -60,6 +64,9 @@ pub enum EcashApiError {
|
||||
source: CompactEcashError,
|
||||
},
|
||||
|
||||
#[error("failed to create API client: {0}")]
|
||||
ClientError(String),
|
||||
|
||||
#[error("the provided account address is malformed: {source}")]
|
||||
MalformedAccountAddress {
|
||||
#[from]
|
||||
@@ -89,8 +96,13 @@ impl TryFrom<ContractVKShare> for EcashApiClient {
|
||||
// In non-client applications this resolver can cause warning logs about H2 connection
|
||||
// failure. This indicates that the long lived https connection was closed by the remote
|
||||
// peer and the resolver will have to reconnect. It should not impact actual functionality
|
||||
let api_client = nym_http_api_client::Client::builder(url_address)
|
||||
.map_err(|e| EcashApiError::ClientError(e.to_string()))?
|
||||
.build()
|
||||
.map_err(|e| EcashApiError::ClientError(e.to_string()))?;
|
||||
|
||||
Ok(EcashApiClient {
|
||||
api_client: NymApiClient::new(url_address),
|
||||
api_client,
|
||||
verification_key: VerificationKeyAuth::try_from_bs58(&share.share)?,
|
||||
node_id: share.node_index,
|
||||
cosmos_address: share.owner.as_str().parse()?,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use crate::nym_api::NymApiClientExt;
|
||||
use crate::nyxd::contract_traits::MixnetQueryClient;
|
||||
use crate::nyxd::error::NyxdError;
|
||||
use crate::nyxd::Config as ClientConfig;
|
||||
use crate::{NymApiClient, QueryHttpRpcNyxdClient, ValidatorClientError};
|
||||
use crate::{QueryHttpRpcNyxdClient, ValidatorClientError};
|
||||
use colored::Colorize;
|
||||
use core::fmt;
|
||||
use itertools::Itertools;
|
||||
@@ -87,8 +88,17 @@ fn setup_connection_tests<H: BuildHasher + 'static>(
|
||||
}
|
||||
});
|
||||
|
||||
let api_connection_test_clients = api_urls.map(|(network, url)| {
|
||||
ClientForConnectionTest::Api(network, url.clone(), NymApiClient::new(url))
|
||||
let api_connection_test_clients = api_urls.filter_map(|(network, url)| {
|
||||
match nym_http_api_client::Client::builder(url.clone()).and_then(|b| b.build()) {
|
||||
Ok(client) => Some(ClientForConnectionTest::Api(network, url, client)),
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"Failed to create API client for {}: {err}",
|
||||
network.network_name
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
nyxd_connection_test_clients.chain(api_connection_test_clients)
|
||||
@@ -160,7 +170,7 @@ async fn test_nyxd_connection(
|
||||
async fn test_nym_api_connection(
|
||||
network: NymNetworkDetails,
|
||||
url: &Url,
|
||||
client: &NymApiClient,
|
||||
client: &nym_http_api_client::Client,
|
||||
) -> ConnectionResult {
|
||||
let result = match timeout(
|
||||
Duration::from_secs(CONNECTION_TEST_TIMEOUT_SEC),
|
||||
@@ -186,7 +196,7 @@ async fn test_nym_api_connection(
|
||||
|
||||
enum ClientForConnectionTest {
|
||||
Nyxd(NymNetworkDetails, Url, Box<QueryHttpRpcNyxdClient>),
|
||||
Api(NymNetworkDetails, Url, NymApiClient),
|
||||
Api(NymNetworkDetails, Url, nym_http_api_client::Client),
|
||||
}
|
||||
|
||||
impl ClientForConnectionTest {
|
||||
|
||||
@@ -9,8 +9,7 @@ use thiserror::Error;
|
||||
pub enum ValidatorClientError {
|
||||
#[error("nym api request failed: {source}")]
|
||||
NymAPIError {
|
||||
#[from]
|
||||
source: nym_api::error::NymAPIError,
|
||||
source: Box<nym_api::error::NymAPIError>,
|
||||
},
|
||||
|
||||
#[error("Tendermint RPC request failure: {0}")]
|
||||
@@ -28,3 +27,11 @@ pub enum ValidatorClientError {
|
||||
#[error("No validator API url has been provided")]
|
||||
NoAPIUrlAvailable,
|
||||
}
|
||||
|
||||
impl From<nym_api::error::NymAPIError> for ValidatorClientError {
|
||||
fn from(source: nym_api::error::NymAPIError) -> Self {
|
||||
ValidatorClientError::NymAPIError {
|
||||
source: Box::new(source),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ pub mod signing;
|
||||
pub use crate::error::ValidatorClientError;
|
||||
pub use crate::rpc::reqwest::ReqwestRpcClient;
|
||||
pub use crate::signing::direct_wallet::DirectSecp256k1HdWallet;
|
||||
pub use client::NymApiClient;
|
||||
pub use client::{Client, Config, EcashApiClient};
|
||||
pub use nym_api_requests::*;
|
||||
pub use nym_http_api_client::UserAgent;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2022-2023 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_api_requests::models::RequestError;
|
||||
use nym_http_api_client::HttpClientError;
|
||||
|
||||
pub type NymAPIError = HttpClientError<RequestError>;
|
||||
pub type NymAPIError = HttpClientError;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use crate::nym_api::error::NymAPIError;
|
||||
use crate::nym_api::routes::{ecash, CORE_STATUS_COUNT, SINCE_ARG};
|
||||
use crate::nym_nodes::SkimmedNodesWithMetadata;
|
||||
use async_trait::async_trait;
|
||||
use nym_api_requests::ecash::models::{
|
||||
AggregatedCoinIndicesSignatureResponse, AggregatedExpirationDateSignatureResponse,
|
||||
@@ -15,9 +16,8 @@ use nym_api_requests::ecash::models::{
|
||||
use nym_api_requests::ecash::VerificationKeyResponse;
|
||||
use nym_api_requests::models::{
|
||||
AnnotationResponse, ApiHealthResponse, BinaryBuildInformationOwned, ChainBlocksStatusResponse,
|
||||
ChainStatusResponse, KeyRotationInfoResponse, LegacyDescribedMixNode, NodePerformanceResponse,
|
||||
NodeRefreshBody, NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse,
|
||||
SignerInformationResponse,
|
||||
ChainStatusResponse, KeyRotationInfoResponse, NodePerformanceResponse, NodeRefreshBody,
|
||||
NymNodeDescription, PerformanceHistoryResponse, RewardedSetResponse, SignerInformationResponse,
|
||||
};
|
||||
use nym_api_requests::nym_nodes::{
|
||||
NodesByAddressesRequestBody, NodesByAddressesResponse, PaginatedCachedNodesResponseV1,
|
||||
@@ -31,26 +31,22 @@ pub use nym_api_requests::{
|
||||
VerifyEcashCredentialBody,
|
||||
},
|
||||
models::{
|
||||
ComputeRewardEstParam, GatewayBondAnnotated, GatewayCoreStatusResponse,
|
||||
GatewayStatusReportResponse, GatewayUptimeHistoryResponse, LegacyDescribedGateway,
|
||||
MixNodeBondAnnotated, MixnodeCoreStatusResponse, MixnodeStatusReportResponse,
|
||||
MixnodeStatusResponse, MixnodeUptimeHistoryResponse, RewardEstimationResponse,
|
||||
StakeSaturationResponse, UptimeResponse,
|
||||
GatewayCoreStatusResponse, GatewayStatusReportResponse, GatewayUptimeHistoryResponse,
|
||||
MixnodeCoreStatusResponse, MixnodeStatusReportResponse, MixnodeStatusResponse,
|
||||
MixnodeUptimeHistoryResponse, StakeSaturationResponse, UptimeResponse,
|
||||
},
|
||||
nym_nodes::{CachedNodesResponse, SemiSkimmedNode, SkimmedNode},
|
||||
nym_nodes::{CachedNodesResponse, SemiSkimmedNode, SemiSkimmedNodesWithMetadata, SkimmedNode},
|
||||
NymNetworkDetailsResponse,
|
||||
};
|
||||
use nym_contracts_common::IdentityKey;
|
||||
use nym_http_api_client::{ApiClient, NO_PARAMS};
|
||||
use nym_mixnet_contract_common::mixnode::MixNodeDetails;
|
||||
use nym_mixnet_contract_common::{GatewayBond, IdentityKeyRef, NodeId, NymNodeDetails};
|
||||
use nym_mixnet_contract_common::{IdentityKeyRef, NodeId, NymNodeDetails};
|
||||
use std::net::IpAddr;
|
||||
use time::format_description::BorrowedFormatItem;
|
||||
use time::Date;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::ValidatorClientError;
|
||||
pub use nym_coconut_dkg_common::types::EpochId;
|
||||
pub use nym_http_api_client::Client;
|
||||
|
||||
pub mod error;
|
||||
pub mod routes;
|
||||
@@ -62,6 +58,9 @@ pub fn rfc_3339_date() -> Vec<BorrowedFormatItem<'static>> {
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
pub trait NymApiClientExt: ApiClient {
|
||||
/// Get the current API URL being used by the client
|
||||
fn api_url(&self) -> &url::Url;
|
||||
|
||||
async fn health(&self) -> Result<ApiHealthResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
@@ -87,104 +86,6 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.get_json(&[routes::V1_API_VERSION, routes::MIXNODES], NO_PARAMS)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::MIXNODES,
|
||||
routes::DETAILED,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateways_detailed(&self) -> Result<Vec<GatewayBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::GATEWAYS,
|
||||
routes::DETAILED,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateways_detailed_unfiltered(
|
||||
&self,
|
||||
) -> Result<Vec<GatewayBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::GATEWAYS,
|
||||
routes::DETAILED_UNFILTERED,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes_detailed_unfiltered(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::MIXNODES,
|
||||
routes::DETAILED_UNFILTERED,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateways(&self) -> Result<Vec<GatewayBond>, NymAPIError> {
|
||||
self.get_json(&[routes::V1_API_VERSION, routes::GATEWAYS], NO_PARAMS)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateways_described(&self) -> Result<Vec<LegacyDescribedGateway>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::V1_API_VERSION, routes::GATEWAYS, routes::DESCRIBED],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes_described(&self) -> Result<Vec<LegacyDescribedMixNode>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::V1_API_VERSION, routes::MIXNODES, routes::DESCRIBED],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn get_node_performance_history(
|
||||
&self,
|
||||
@@ -241,6 +142,156 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_current_rewarded_set(&self) -> Result<RewardedSetResponse, NymAPIError> {
|
||||
self.get_rewarded_set().await
|
||||
}
|
||||
|
||||
async fn get_all_basic_nodes_with_metadata(
|
||||
&self,
|
||||
) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
|
||||
// unroll first loop iteration in order to obtain the metadata
|
||||
let mut page = 0;
|
||||
let res = self
|
||||
.get_basic_nodes_v2(false, Some(page), None, true)
|
||||
.await?;
|
||||
let mut nodes = res.nodes.data;
|
||||
let metadata = res.metadata;
|
||||
|
||||
if res.nodes.pagination.total == nodes.len() {
|
||||
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
|
||||
}
|
||||
|
||||
page += 1;
|
||||
|
||||
loop {
|
||||
let mut res = self
|
||||
.get_basic_nodes_v2(false, Some(page), None, true)
|
||||
.await?;
|
||||
|
||||
if !metadata.consistency_check(&res.metadata) {
|
||||
// Create a custom error for inconsistent metadata
|
||||
return Err(NymAPIError::InternalResponseInconsistency {
|
||||
url: self.api_url().clone(),
|
||||
details: "Inconsistent paged metadata".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
if nodes.len() >= res.nodes.pagination.total {
|
||||
break;
|
||||
} else {
|
||||
page += 1
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
}
|
||||
|
||||
async fn get_all_basic_active_mixing_assigned_nodes_with_metadata(
|
||||
&self,
|
||||
) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
|
||||
// Get all mixing nodes that are in the active/rewarded set
|
||||
let mut page = 0;
|
||||
let res = self
|
||||
.get_basic_active_mixing_assigned_nodes_v2(false, Some(page), None, false)
|
||||
.await?;
|
||||
|
||||
let metadata = res.metadata;
|
||||
let mut nodes = res.nodes.data;
|
||||
|
||||
if res.nodes.pagination.total == nodes.len() {
|
||||
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
|
||||
}
|
||||
|
||||
page += 1;
|
||||
|
||||
loop {
|
||||
let res = self
|
||||
.get_basic_active_mixing_assigned_nodes_v2(false, Some(page), None, false)
|
||||
.await?;
|
||||
|
||||
if !metadata.consistency_check(&res.metadata) {
|
||||
return Err(NymAPIError::InternalResponseInconsistency {
|
||||
url: self.api_url().clone(),
|
||||
details: "Inconsistent paged metadata".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
nodes.append(&mut res.nodes.data.clone());
|
||||
|
||||
// Check if we've got all nodes
|
||||
if nodes.len() >= res.nodes.pagination.total {
|
||||
break;
|
||||
} else {
|
||||
page += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
}
|
||||
|
||||
async fn get_all_basic_entry_assigned_nodes_with_metadata(
|
||||
&self,
|
||||
) -> Result<SkimmedNodesWithMetadata, NymAPIError> {
|
||||
// Get all nodes that can act as entry gateways
|
||||
let mut page = 0;
|
||||
let res = self
|
||||
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, false)
|
||||
.await?;
|
||||
|
||||
let metadata = res.metadata;
|
||||
let mut nodes = res.nodes.data;
|
||||
|
||||
if res.nodes.pagination.total == nodes.len() {
|
||||
return Ok(SkimmedNodesWithMetadata::new(nodes, metadata));
|
||||
}
|
||||
|
||||
page += 1;
|
||||
|
||||
loop {
|
||||
let res = self
|
||||
.get_basic_entry_assigned_nodes_v2(false, Some(page), None, false)
|
||||
.await?;
|
||||
|
||||
if !metadata.consistency_check(&res.metadata) {
|
||||
return Err(NymAPIError::InternalResponseInconsistency {
|
||||
url: self.api_url().clone(),
|
||||
details: "Inconsistent paged metadata".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
nodes.append(&mut res.nodes.data.clone());
|
||||
|
||||
// Check if we've got all nodes
|
||||
if nodes.len() >= res.nodes.pagination.total {
|
||||
break;
|
||||
} else {
|
||||
page += 1;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
}
|
||||
|
||||
async fn get_all_described_nodes(&self) -> Result<Vec<NymNodeDescription>, NymAPIError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
let mut descriptions = Vec::new();
|
||||
|
||||
loop {
|
||||
let mut res = self.get_nodes_described(Some(page), None).await?;
|
||||
|
||||
descriptions.append(&mut res.data);
|
||||
if descriptions.len() < res.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(descriptions)
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn get_nym_nodes(
|
||||
&self,
|
||||
@@ -268,6 +319,25 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_all_bonded_nym_nodes(&self) -> Result<Vec<NymNodeDetails>, ValidatorClientError> {
|
||||
// TODO: deal with paging in macro or some helper function or something, because it's the same pattern everywhere
|
||||
let mut page = 0;
|
||||
let mut bonds = Vec::new();
|
||||
|
||||
loop {
|
||||
let mut res = self.get_nym_nodes(Some(page), None).await?;
|
||||
|
||||
bonds.append(&mut res.data);
|
||||
if bonds.len() < res.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(bonds)
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[tracing::instrument(level = "debug", skip_all)]
|
||||
async fn get_basic_mixnodes(&self) -> Result<CachedNodesResponse<SkimmedNode>, NymAPIError> {
|
||||
@@ -677,42 +747,6 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_active_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::V1_API_VERSION, routes::MIXNODES, routes::ACTIVE],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_active_mixnodes_detailed(&self) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::MIXNODES,
|
||||
routes::ACTIVE,
|
||||
routes::DETAILED,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_rewarded_mixnodes(&self) -> Result<Vec<MixNodeDetails>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[routes::V1_API_VERSION, routes::MIXNODES, routes::REWARDED],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_report(
|
||||
@@ -789,24 +823,6 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_rewarded_mixnodes_detailed(
|
||||
&self,
|
||||
) -> Result<Vec<MixNodeBondAnnotated>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS,
|
||||
routes::MIXNODES,
|
||||
routes::REWARDED,
|
||||
routes::DETAILED,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateway_core_status_count(
|
||||
@@ -874,104 +890,6 @@ pub trait NymApiClientExt: ApiClient {
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_status(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
) -> Result<MixnodeStatusResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
routes::MIXNODE,
|
||||
&mix_id.to_string(),
|
||||
routes::STATUS,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_reward_estimation(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
) -> Result<RewardEstimationResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
routes::MIXNODE,
|
||||
&mix_id.to_string(),
|
||||
routes::REWARD_ESTIMATION,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn compute_mixnode_reward_estimation(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
request_body: &ComputeRewardEstParam,
|
||||
) -> Result<RewardEstimationResponse, NymAPIError> {
|
||||
self.post_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
routes::MIXNODE,
|
||||
&mix_id.to_string(),
|
||||
routes::COMPUTE_REWARD_ESTIMATION,
|
||||
],
|
||||
NO_PARAMS,
|
||||
request_body,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_stake_saturation(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
) -> Result<StakeSaturationResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
routes::MIXNODE,
|
||||
&mix_id.to_string(),
|
||||
routes::STAKE_SATURATION,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[allow(deprecated)]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnode_inclusion_probability(
|
||||
&self,
|
||||
mix_id: NodeId,
|
||||
) -> Result<nym_api_requests::models::InclusionProbabilityResponse, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
routes::STATUS_ROUTES,
|
||||
routes::MIXNODE,
|
||||
&mix_id.to_string(),
|
||||
routes::INCLUSION_CHANCE,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_current_node_performance(
|
||||
&self,
|
||||
@@ -1020,34 +938,6 @@ pub trait NymApiClientExt: ApiClient {
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_mixnodes_blacklisted(&self) -> Result<Vec<NodeId>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
routes::MIXNODES,
|
||||
routes::BLACKLISTED,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[deprecated]
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
async fn get_gateways_blacklisted(&self) -> Result<Vec<IdentityKey>, NymAPIError> {
|
||||
self.get_json(
|
||||
&[
|
||||
routes::V1_API_VERSION,
|
||||
routes::GATEWAYS,
|
||||
routes::BLACKLISTED,
|
||||
],
|
||||
NO_PARAMS,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self, request_body))]
|
||||
async fn blind_sign(
|
||||
&self,
|
||||
@@ -1371,8 +1261,49 @@ pub trait NymApiClientExt: ApiClient {
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Method to change the base API URLs being used by the client
|
||||
fn change_base_urls(&mut self, urls: Vec<url::Url>);
|
||||
|
||||
/// Retrieve expanded information for all bonded nodes on the network
|
||||
async fn get_all_expanded_nodes(&self) -> Result<SemiSkimmedNodesWithMetadata, NymAPIError> {
|
||||
// Unroll the first iteration to get the metadata
|
||||
let mut page = 0;
|
||||
|
||||
let res = self.get_expanded_nodes(false, Some(page), None).await?;
|
||||
let mut nodes = res.nodes.data;
|
||||
let metadata = res.metadata;
|
||||
|
||||
if res.nodes.pagination.total == nodes.len() {
|
||||
return Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata));
|
||||
}
|
||||
|
||||
page += 1;
|
||||
|
||||
loop {
|
||||
let mut res = self.get_expanded_nodes(false, Some(page), None).await?;
|
||||
|
||||
nodes.append(&mut res.nodes.data);
|
||||
if nodes.len() < res.nodes.pagination.total {
|
||||
page += 1
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SemiSkimmedNodesWithMetadata::new(nodes, metadata))
|
||||
}
|
||||
}
|
||||
|
||||
// Client is already nym_http_api_client::Client (re-exported above), so just one impl needed
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
|
||||
impl NymApiClientExt for Client {}
|
||||
impl NymApiClientExt for nym_http_api_client::Client {
|
||||
fn api_url(&self) -> &url::Url {
|
||||
self.current_url().as_ref()
|
||||
}
|
||||
|
||||
fn change_base_urls(&mut self, urls: Vec<url::Url>) {
|
||||
self.change_base_urls(urls.into_iter().map(|u| u.into()).collect());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ cosmrs = { workspace = true }
|
||||
cosmwasm-std = { workspace = true }
|
||||
|
||||
nym-validator-client = { path = "../client-libs/validator-client" }
|
||||
nym-http-api-client = { path = "../http-api-client" }
|
||||
nym-bin-common = { path = "../../common/bin-common", features = ["output_format"] }
|
||||
nym-crypto = { path = "../../common/crypto", features = ["asymmetric"] }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::context::errors::ContextError;
|
||||
pub use nym_http_api_client::Client as NymApiClient;
|
||||
use nym_network_defaults::{
|
||||
setup_env,
|
||||
var_names::{MIXNET_CONTRACT_ADDRESS, NYM_API, NYXD, VESTING_CONTRACT_ADDRESS},
|
||||
NymNetworkDetails,
|
||||
};
|
||||
pub use nym_validator_client::nym_api::Client as NymApiClient;
|
||||
use nym_validator_client::nyxd::{self, AccountId, NyxdClient};
|
||||
use nym_validator_client::{
|
||||
DirectSigningHttpRpcNyxdClient, DirectSigningHttpRpcValidatorClient, QueryHttpRpcNyxdClient,
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type Role = "EntryGateway" | "Layer1" | "Layer2" | "Layer3" | "ExitGateway" | "Standby";
|
||||
export type Role = "entry_gateway" | "layer1" | "layer2" | "layer3" | "exit_gateway" | "standby";
|
||||
|
||||
@@ -23,6 +23,7 @@ use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
|
||||
/// Full details associated with given mixnode.
|
||||
#[cw_serde]
|
||||
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||
pub struct MixNodeDetails {
|
||||
/// Basic bond information of this mixnode, such as owner address, original pledge, etc.
|
||||
pub bond_information: MixNodeBond,
|
||||
@@ -695,6 +696,7 @@ impl From<LegacyMixLayer> for u8 {
|
||||
Copy,
|
||||
)]
|
||||
#[schemars(crate = "::cosmwasm_schema::schemars")]
|
||||
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
|
||||
pub struct PendingMixNodeChanges {
|
||||
pub pledge_change: Option<EpochEventId>,
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use nym_ecash_signer_check::SignerCheckError;
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nym_api::{error::NymAPIError, EpochId};
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
@@ -71,6 +71,9 @@ pub enum CredentialProxyError {
|
||||
source: EcashApiError,
|
||||
},
|
||||
|
||||
#[error("Nym API request failed: {source}")]
|
||||
NymApiFailure { source: Box<NymAPIError> },
|
||||
|
||||
#[error("Compact ecash internal error: {0}")]
|
||||
CompactEcashInternalError(#[from] nym_compact_ecash::error::CompactEcashError),
|
||||
|
||||
@@ -154,6 +157,14 @@ pub enum CredentialProxyError {
|
||||
},
|
||||
}
|
||||
|
||||
impl From<NymAPIError> for CredentialProxyError {
|
||||
fn from(source: NymAPIError) -> Self {
|
||||
CredentialProxyError::NymApiFailure {
|
||||
source: Box::new(source),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CredentialProxyError {
|
||||
pub fn database_inconsistency<S: Into<String>>(reason: S) -> CredentialProxyError {
|
||||
CredentialProxyError::DatabaseInconsistency {
|
||||
|
||||
@@ -23,6 +23,7 @@ use nym_credentials::{
|
||||
AggregatedCoinIndicesSignatures, AggregatedExpirationDateSignatures, EpochVerificationKey,
|
||||
};
|
||||
use nym_ecash_contract_common::deposit::DepositId;
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::contract_traits::dkg_query_client::Epoch;
|
||||
|
||||
@@ -11,6 +11,7 @@ use nym_credential_proxy_requests::api::v1::ticketbook::models::{
|
||||
TicketbookWalletSharesResponse, WalletShare, WebhookTicketbookWalletShares,
|
||||
WebhookTicketbookWalletSharesRequest,
|
||||
};
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use nym_validator_client::ecash::BlindSignRequestBody;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -14,7 +14,7 @@ use nym_api_requests::ecash::models::{BatchRedeemTicketsBody, VerifyEcashTicketB
|
||||
use nym_credentials_interface::Bandwidth;
|
||||
use nym_credentials_interface::{ClientTicket, TicketType};
|
||||
use nym_validator_client::coconut::EcashApiError;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nym_api::{EpochId, NymApiClientExt};
|
||||
use nym_validator_client::nyxd::contract_traits::{
|
||||
EcashSigningClient, MultisigQueryClient, MultisigSigningClient, PagedMultisigQueryClient,
|
||||
};
|
||||
@@ -126,7 +126,7 @@ pub struct CredentialHandlerConfig {
|
||||
pub maximum_time_between_redemption: Duration,
|
||||
}
|
||||
|
||||
pub(crate) struct CredentialHandler {
|
||||
pub struct CredentialHandler {
|
||||
config: CredentialHandlerConfig,
|
||||
multisig_threshold: f32,
|
||||
ticket_receiver: UnboundedReceiver<ClientTicket>,
|
||||
@@ -354,7 +354,7 @@ impl CredentialHandler {
|
||||
Err(err) => {
|
||||
error!("failed to send ticket {ticket_id} for verification to ecash signer '{client}': {err}. if we don't reach quorum, we'll retry later");
|
||||
Err(EcashTicketError::ApiFailure(EcashApiError::NymApi {
|
||||
source: err,
|
||||
source: nym_validator_client::ValidatorClientError::from(err),
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -907,7 +907,7 @@ impl CredentialHandler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run(mut self, mut shutdown: nym_task::TaskClient) {
|
||||
pub async fn run(mut self, shutdown: nym_task::ShutdownToken) {
|
||||
info!("Starting Ecash CredentialSender");
|
||||
|
||||
// attempt to clear any pending operations
|
||||
@@ -919,11 +919,12 @@ impl CredentialHandler {
|
||||
let start = Instant::now() + self.config.pending_poller;
|
||||
let mut resolver_interval = interval_at(start, self.config.pending_poller);
|
||||
|
||||
while !shutdown.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = shutdown.recv() => {
|
||||
_ = shutdown.cancelled() => {
|
||||
trace!("client_handling::credentialSender : received shutdown");
|
||||
break
|
||||
},
|
||||
Some(ticket) = self.ticket_receiver.next() => {
|
||||
let (queued_up, _) = self.ticket_receiver.size_hint();
|
||||
@@ -946,8 +947,4 @@ impl CredentialHandler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start(self, shutdown: nym_task::TaskClient) {
|
||||
tokio::spawn(async move { self.run(shutdown).await });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,9 +82,8 @@ impl EcashManager {
|
||||
credential_handler_cfg: CredentialHandlerConfig,
|
||||
nyxd_client: DirectSigningHttpRpcNyxdClient,
|
||||
pk_bytes: [u8; 32],
|
||||
shutdown: nym_task::TaskClient,
|
||||
storage: GatewayStorage,
|
||||
) -> Result<Self, Error> {
|
||||
) -> Result<(Self, CredentialHandler), Error> {
|
||||
let shared_state = SharedState::new(nyxd_client, Box::new(storage)).await?;
|
||||
|
||||
let (cred_sender, cred_receiver) = mpsc::unbounded();
|
||||
@@ -92,14 +91,16 @@ impl EcashManager {
|
||||
let cs =
|
||||
CredentialHandler::new(credential_handler_cfg, cred_receiver, shared_state.clone())
|
||||
.await?;
|
||||
cs.start(shutdown);
|
||||
|
||||
Ok(EcashManager {
|
||||
shared_state,
|
||||
pk_bytes,
|
||||
pay_infos: Default::default(),
|
||||
cred_sender,
|
||||
})
|
||||
Ok((
|
||||
EcashManager {
|
||||
shared_state,
|
||||
pk_bytes,
|
||||
pay_infos: Default::default(),
|
||||
cred_sender,
|
||||
},
|
||||
cs,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn verify_pay_info(&self, pay_info: NymPayInfo) -> Result<usize, EcashTicketError> {
|
||||
|
||||
@@ -22,6 +22,7 @@ nym-ecash-time = { path = "../ecash-time", features = ["expiration"] }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
nym-crypto = { path = "../crypto" }
|
||||
nym-api-requests = { path = "../../nym-api/nym-api-requests" }
|
||||
nym-http-api-client = { path = "../http-api-client" }
|
||||
nym-validator-client = { path = "../client-libs/validator-client", default-features = false }
|
||||
nym-ecash-contract-common = { path = "../cosmwasm-smart-contracts/ecash-contract" }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
|
||||
@@ -15,7 +15,7 @@ use nym_credentials_interface::{
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_ecash_contract_common::deposit::DepositId;
|
||||
use nym_ecash_time::{ecash_default_expiration_date, ecash_today, EcashTime};
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nym_api::{EpochId, NymApiClientExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use time::Date;
|
||||
|
||||
@@ -116,7 +116,7 @@ impl IssuanceTicketBook {
|
||||
|
||||
pub async fn obtain_blinded_credential(
|
||||
&self,
|
||||
client: &nym_validator_client::client::NymApiClient,
|
||||
client: &nym_http_api_client::Client,
|
||||
request_body: &BlindSignRequestBody,
|
||||
) -> Result<BlindedSignature, Error> {
|
||||
let server_response = client.blind_sign(request_body).await?;
|
||||
@@ -179,7 +179,7 @@ impl IssuanceTicketBook {
|
||||
// ideally this would have been generic over credential type, but we really don't need secp256k1 keys for bandwidth vouchers
|
||||
pub async fn obtain_partial_ticketbook_credential(
|
||||
&self,
|
||||
client: &nym_validator_client::client::NymApiClient,
|
||||
client: &nym_http_api_client::Client,
|
||||
signer_index: u64,
|
||||
validator_vk: &VerificationKeyAuth,
|
||||
signing_data: CredentialSigningData,
|
||||
|
||||
@@ -10,6 +10,7 @@ use nym_credentials_interface::{
|
||||
VerificationKeyAuth, WalletSignatures,
|
||||
};
|
||||
use nym_validator_client::client::EcashApiClient;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
|
||||
// so we wouldn't break all the existing imports
|
||||
pub use nym_ecash_time::{cred_exp_date, ecash_date_offset, ecash_today, EcashTime};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use crate::ecash::bandwidth::issued::CURRENT_SERIALIZATION_REVISION;
|
||||
use nym_credentials_interface::CompactEcashError;
|
||||
use nym_crypto::asymmetric::x25519::KeyRecoveryError;
|
||||
use nym_validator_client::ValidatorClientError;
|
||||
use nym_validator_client::{nym_api::error::NymAPIError, ValidatorClientError};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@@ -37,6 +37,9 @@ pub enum Error {
|
||||
#[error("Ran into a validator client error - {0}")]
|
||||
ValidatorClientError(#[from] ValidatorClientError),
|
||||
|
||||
#[error("Nym API request failed - {0}")]
|
||||
NymAPIError(Box<NymAPIError>),
|
||||
|
||||
#[error("Bandwidth operation overflowed. {0}")]
|
||||
BandwidthOverflow(String),
|
||||
|
||||
@@ -61,3 +64,9 @@ pub enum Error {
|
||||
#[error("failed to create a secp256k1 signature")]
|
||||
Secp256k1SignFailure,
|
||||
}
|
||||
|
||||
impl From<NymAPIError> for Error {
|
||||
fn from(e: NymAPIError) -> Self {
|
||||
Error::NymAPIError(Box::new(e))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ url = { workspace = true }
|
||||
nym-validator-client = { path = "../client-libs/validator-client" }
|
||||
nym-network-defaults = { path = "../network-defaults" }
|
||||
nym-ecash-signer-check-types = { path = "../ecash-signer-check-types" }
|
||||
nym-http-api-client = { path = "../http-api-client" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::{LocalChainStatus, SigningStatus, TypedSignerResult};
|
||||
use crate::{LocalChainStatus, SignerCheckError, SigningStatus, TypedSignerResult};
|
||||
use nym_ecash_signer_check_types::dealer_information::RawDealerInformation;
|
||||
use nym_ecash_signer_check_types::status::{SignerStatus, SignerTestResult};
|
||||
use nym_validator_client::client::NymApiClientExt;
|
||||
use nym_validator_client::models::BinaryBuildInformationOwned;
|
||||
use nym_validator_client::nym_api::NymApiClientExt;
|
||||
use nym_validator_client::nyxd::contract_traits::dkg_query_client::{
|
||||
ContractVKShare, DealerDetails,
|
||||
};
|
||||
use nym_validator_client::NymApiClient;
|
||||
use std::time::Duration;
|
||||
use tracing::{error, warn};
|
||||
use url::Url;
|
||||
@@ -32,37 +31,38 @@ pub(crate) mod signing_status {
|
||||
}
|
||||
|
||||
struct ClientUnderTest {
|
||||
api_client: NymApiClient,
|
||||
api_client: nym_http_api_client::Client,
|
||||
build_info: Option<BinaryBuildInformationOwned>,
|
||||
}
|
||||
|
||||
impl ClientUnderTest {
|
||||
pub(crate) fn new(api_url: &Url) -> Self {
|
||||
ClientUnderTest {
|
||||
api_client: NymApiClient::new(api_url.clone()),
|
||||
pub(crate) fn new(api_url: &Url) -> Result<Self, SignerCheckError> {
|
||||
// The builder should not fail with a valid URL that's already parsed
|
||||
// If it does fail, it's an internal error that we can't recover from
|
||||
let api_client = nym_http_api_client::Client::builder(api_url.clone())?.build()?;
|
||||
|
||||
Ok(ClientUnderTest {
|
||||
api_client,
|
||||
build_info: None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn try_retrieve_build_information(&mut self) -> bool {
|
||||
match tokio::time::timeout(
|
||||
Duration::from_secs(5),
|
||||
self.api_client.nym_api.build_information(),
|
||||
)
|
||||
.await
|
||||
match tokio::time::timeout(Duration::from_secs(5), self.api_client.build_information())
|
||||
.await
|
||||
{
|
||||
Ok(Ok(build_information)) => {
|
||||
self.build_info = Some(build_information);
|
||||
true
|
||||
}
|
||||
Ok(Err(err)) => {
|
||||
warn!("{}: failed to retrieve build information: {err}. the signer is most likely down", self.api_client.api_url());
|
||||
warn!("{}: failed to retrieve build information: {err}. the signer is most likely down", self.api_client.current_url());
|
||||
false
|
||||
}
|
||||
Err(_timeout) => {
|
||||
warn!(
|
||||
"{}: timed out while attempting to retrieve build information",
|
||||
self.api_client.api_url()
|
||||
self.api_client.current_url()
|
||||
);
|
||||
false
|
||||
}
|
||||
@@ -77,7 +77,7 @@ impl ClientUnderTest {
|
||||
.inspect_err(|err| {
|
||||
error!(
|
||||
"ecash signer '{}' reports invalid version {}: {err}",
|
||||
self.api_client.api_url(),
|
||||
self.api_client.current_url(),
|
||||
build_info.build_version
|
||||
)
|
||||
})
|
||||
@@ -121,14 +121,14 @@ impl ClientUnderTest {
|
||||
|
||||
// check if it supports the current query
|
||||
if self.supports_chain_status_query() {
|
||||
return match self.api_client.nym_api.get_chain_blocks_status().await {
|
||||
return match self.api_client.get_chain_blocks_status().await {
|
||||
Ok(status) => LocalChainStatus::Reachable {
|
||||
response: Box::new(status),
|
||||
},
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"{}: failed to retrieve local chain status: {err}",
|
||||
self.api_client.api_url()
|
||||
self.api_client.current_url()
|
||||
);
|
||||
LocalChainStatus::Unreachable
|
||||
}
|
||||
@@ -136,14 +136,14 @@ impl ClientUnderTest {
|
||||
}
|
||||
|
||||
// fallback to the legacy query
|
||||
match self.api_client.nym_api.get_chain_status().await {
|
||||
match self.api_client.get_chain_status().await {
|
||||
Ok(status) => LocalChainStatus::ReachableLegacy {
|
||||
response: Box::new(status),
|
||||
},
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"{}: failed to retrieve [legacy] local chain status: {err}",
|
||||
self.api_client.api_url()
|
||||
self.api_client.current_url()
|
||||
);
|
||||
LocalChainStatus::Unreachable
|
||||
}
|
||||
@@ -158,14 +158,14 @@ impl ClientUnderTest {
|
||||
|
||||
// check if it supports the current query
|
||||
if self.supports_signing_status_query() {
|
||||
return match self.api_client.nym_api.get_signer_status().await {
|
||||
return match self.api_client.get_signer_status().await {
|
||||
Ok(response) => SigningStatus::Reachable {
|
||||
response: Box::new(response),
|
||||
},
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"{}: failed to retrieve signer chain status: {err}",
|
||||
self.api_client.api_url()
|
||||
self.api_client.current_url()
|
||||
);
|
||||
SigningStatus::Unreachable
|
||||
}
|
||||
@@ -173,14 +173,14 @@ impl ClientUnderTest {
|
||||
}
|
||||
|
||||
// fallback to the legacy query
|
||||
match self.api_client.nym_api.get_signer_information().await {
|
||||
match self.api_client.get_signer_information().await {
|
||||
Ok(status) => SigningStatus::ReachableLegacy {
|
||||
response: Box::new(status),
|
||||
},
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"{}: failed to retrieve [legacy] signer chain status: {err}",
|
||||
self.api_client.api_url()
|
||||
self.api_client.current_url()
|
||||
);
|
||||
// NOTE: this might equally mean the signing is disabled
|
||||
SigningStatus::Unreachable
|
||||
@@ -201,7 +201,13 @@ pub(crate) async fn check_client(
|
||||
return SignerStatus::ProvidedInvalidDetails.with_details(dealer_information, dkg_epoch);
|
||||
};
|
||||
|
||||
let mut client = ClientUnderTest::new(&parsed_information.announce_address);
|
||||
let mut client = match ClientUnderTest::new(&parsed_information.announce_address) {
|
||||
Ok(client) => client,
|
||||
Err(err) => {
|
||||
error!("failed to create client instance: {err}");
|
||||
return SignerStatus::Unreachable.with_details(dealer_information, dkg_epoch);
|
||||
}
|
||||
};
|
||||
|
||||
// 8. check basic connection status - can you retrieve build information?
|
||||
if !client.try_retrieve_build_information().await {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2025 - Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use nym_http_api_client::HttpClientError;
|
||||
use nym_validator_client::nyxd::error::NyxdError;
|
||||
use thiserror::Error;
|
||||
|
||||
@@ -11,6 +12,17 @@ pub enum SignerCheckError {
|
||||
|
||||
#[error("failed to query the DKG contract: {source}")]
|
||||
DKGContractQueryFailure { source: NyxdError },
|
||||
|
||||
#[error("failed to build client: {source}")]
|
||||
HttpClient { source: Box<HttpClientError> },
|
||||
}
|
||||
|
||||
impl From<HttpClientError> for SignerCheckError {
|
||||
fn from(e: HttpClientError) -> Self {
|
||||
SignerCheckError::HttpClient {
|
||||
source: Box::new(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SignerCheckError {
|
||||
|
||||
@@ -19,11 +19,11 @@ serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
strum = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true, features = ["log"] }
|
||||
time = { workspace = true }
|
||||
subtle = { workspace = true }
|
||||
zeroize = { workspace = true }
|
||||
|
||||
nym-bin-common = { path = "../bin-common" }
|
||||
nym-crypto = { path = "../crypto", features = ["aead", "hashing"] }
|
||||
nym-pemstore = { path = "../pemstore" }
|
||||
nym-sphinx = { path = "../nymsphinx" }
|
||||
@@ -34,6 +34,11 @@ nym-task = { path = "../task" }
|
||||
nym-credentials = { path = "../credentials" }
|
||||
nym-credentials-interface = { path = "../credentials-interface" }
|
||||
|
||||
opentelemetry = { workspace = true, features = ["trace"], optional = true }
|
||||
opentelemetry_sdk = { workspace = true, optional = true }
|
||||
tracing-opentelemetry = { workspace = true, optional = true }
|
||||
tracing = { workspace = true, features = ["std", "attributes", "tracing-attributes"] }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies.tokio]
|
||||
workspace = true
|
||||
features = ["time"]
|
||||
@@ -51,3 +56,12 @@ anyhow = { workspace = true }
|
||||
nym-compact-ecash = { path = "../nym_offline_compact_ecash" } # we need specific imports in tests
|
||||
nym-test-utils = { path = "../test-utils" }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
otel = [
|
||||
"nym-bin-common/otel",
|
||||
"opentelemetry",
|
||||
"opentelemetry_sdk",
|
||||
"tracing-opentelemetry",
|
||||
]
|
||||
|
||||
@@ -14,7 +14,7 @@ use std::task::{Context, Poll};
|
||||
use tungstenite::{Error as WsError, Message as WsMessage};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
|
||||
pub(crate) type WsItem = Result<WsMessage, WsError>;
|
||||
|
||||
@@ -52,7 +52,7 @@ pub fn client_handshake<'a, S, R>(
|
||||
gateway_pubkey: ed25519::PublicKey,
|
||||
expects_credential_usage: bool,
|
||||
derive_aes256_gcm_siv_key: bool,
|
||||
#[cfg(not(target_arch = "wasm32"))] shutdown: TaskClient,
|
||||
#[cfg(not(target_arch = "wasm32"))] shutdown_token: ShutdownToken,
|
||||
) -> GatewayHandshake<'a>
|
||||
where
|
||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin + Send + 'a,
|
||||
@@ -64,7 +64,7 @@ where
|
||||
identity,
|
||||
Some(gateway_pubkey),
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
shutdown,
|
||||
shutdown_token,
|
||||
)
|
||||
.with_credential_usage(expects_credential_usage)
|
||||
.with_aes256_gcm_siv_key(derive_aes256_gcm_siv_key);
|
||||
@@ -80,13 +80,13 @@ pub fn gateway_handshake<'a, S, R>(
|
||||
ws_stream: &'a mut S,
|
||||
identity: &'a ed25519::KeyPair,
|
||||
received_init_payload: Vec<u8>,
|
||||
shutdown: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
) -> GatewayHandshake<'a>
|
||||
where
|
||||
S: Stream<Item = WsItem> + Sink<WsMessage> + Unpin + Send + 'a,
|
||||
R: CryptoRng + RngCore + Send,
|
||||
{
|
||||
let state = State::new(rng, ws_stream, identity, None, shutdown);
|
||||
let state = State::new(rng, ws_stream, identity, None, shutdown_token);
|
||||
GatewayHandshake {
|
||||
handshake_future: Box::pin(state.perform_gateway_handshake(received_init_payload)),
|
||||
}
|
||||
@@ -149,7 +149,7 @@ mod tests {
|
||||
*gateway_keys.public_key(),
|
||||
false,
|
||||
true,
|
||||
TaskClient::dummy(),
|
||||
ShutdownToken::default(),
|
||||
);
|
||||
|
||||
let client_fut = handshake_client.spawn_timeboxed();
|
||||
@@ -176,7 +176,7 @@ mod tests {
|
||||
gateway_ws,
|
||||
gateway_keys,
|
||||
init_msg,
|
||||
TaskClient::dummy(),
|
||||
ShutdownToken::default(),
|
||||
);
|
||||
|
||||
let gateway_fut = handshake_gateway.spawn_timeboxed();
|
||||
|
||||
@@ -24,7 +24,7 @@ use tracing::log::*;
|
||||
use tungstenite::Message as WsMessage;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use tokio::time::timeout;
|
||||
@@ -63,7 +63,7 @@ pub(crate) struct State<'a, S, R> {
|
||||
|
||||
// channel to receive shutdown signal
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
shutdown: TaskClient,
|
||||
shutdown_token: ShutdownToken,
|
||||
}
|
||||
|
||||
impl<'a, S, R> State<'a, S, R> {
|
||||
@@ -72,7 +72,7 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
ws_stream: &'a mut S,
|
||||
identity: &'a ed25519::KeyPair,
|
||||
remote_pubkey: Option<ed25519::PublicKey>,
|
||||
#[cfg(not(target_arch = "wasm32"))] shutdown: TaskClient,
|
||||
#[cfg(not(target_arch = "wasm32"))] shutdown_token: ShutdownToken,
|
||||
) -> Self
|
||||
where
|
||||
R: CryptoRng + RngCore,
|
||||
@@ -89,7 +89,7 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
expects_credential_usage: false,
|
||||
derive_aes256_gcm_siv_key: false,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
shutdown,
|
||||
shutdown_token,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,7 +306,7 @@ impl<'a, S, R> State<'a, S, R> {
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.shutdown.recv() => return Err(HandshakeError::ReceivedShutdown),
|
||||
_ = self.shutdown_token.cancelled() => return Err(HandshakeError::ReceivedShutdown),
|
||||
msg = self.ws_stream.next() => {
|
||||
let Some(ret) = Self::on_wg_msg(msg)? else {
|
||||
continue;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
use crate::{AuthenticationFailure, GatewayRequestsError, SharedGatewayKey};
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::iter;
|
||||
use std::time::Duration;
|
||||
use subtle::ConstantTimeEq;
|
||||
@@ -16,6 +17,9 @@ pub struct AuthenticateRequest {
|
||||
pub content: AuthenticateRequestContent,
|
||||
|
||||
pub request_signature: ed25519::Signature,
|
||||
|
||||
#[serde(default)]
|
||||
pub otel_context: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
impl AuthenticateRequest {
|
||||
@@ -23,6 +27,7 @@ impl AuthenticateRequest {
|
||||
protocol_version: u8,
|
||||
shared_key: &SharedGatewayKey,
|
||||
identity_keys: &ed25519::KeyPair,
|
||||
otel_context: Option<HashMap<String, String>>,
|
||||
) -> Result<AuthenticateRequest, GatewayRequestsError> {
|
||||
let content = AuthenticateRequestContent::new(
|
||||
protocol_version,
|
||||
@@ -35,6 +40,7 @@ impl AuthenticateRequest {
|
||||
Ok(AuthenticateRequest {
|
||||
content,
|
||||
request_signature,
|
||||
otel_context,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -8,12 +8,16 @@ use crate::{
|
||||
AUTHENTICATE_V2_PROTOCOL_VERSION, CREDENTIAL_UPDATE_V2_PROTOCOL_VERSION,
|
||||
INITIAL_PROTOCOL_VERSION,
|
||||
};
|
||||
#[cfg(feature = "otel")]
|
||||
use nym_bin_common::opentelemetry::context::ContextCarrier;
|
||||
use nym_credentials_interface::CredentialSpendingData;
|
||||
use nym_crypto::asymmetric::ed25519;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use nym_statistics_common::types::SessionType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::str::FromStr;
|
||||
use tracing::{instrument, warn};
|
||||
use tungstenite::Message;
|
||||
|
||||
pub mod authenticate;
|
||||
@@ -76,8 +80,13 @@ pub enum ClientControlRequest {
|
||||
address: String,
|
||||
enc_address: String,
|
||||
iv: String,
|
||||
/// this is a trace id that is used in testing and performance verification
|
||||
/// in mainnet, this will always be set to None
|
||||
#[serde(default)]
|
||||
otel_context: Option<HashMap<String, String>>,
|
||||
},
|
||||
|
||||
|
||||
AuthenticateV2(Box<AuthenticateRequest>),
|
||||
|
||||
#[serde(alias = "handshakePayload")]
|
||||
@@ -127,14 +136,25 @@ impl ClientControlRequest {
|
||||
let nonce = shared_key.random_nonce_or_iv();
|
||||
let ciphertext = shared_key.encrypt_naive(address.as_bytes_ref(), Some(&nonce))?;
|
||||
|
||||
#[cfg(feature = "otel")]
|
||||
let context_carrier = {
|
||||
let context = opentelemetry::Context::current();
|
||||
ContextCarrier::new_with_current_context(context).into_map()
|
||||
};
|
||||
|
||||
Ok(ClientControlRequest::Authenticate {
|
||||
protocol_version,
|
||||
address: address.as_base58_string(),
|
||||
enc_address: bs58::encode(&ciphertext).into_string(),
|
||||
iv: bs58::encode(&nonce).into_string(),
|
||||
#[cfg(feature = "otel")]
|
||||
otel_context: Some(context_carrier),
|
||||
#[cfg(not(feature = "otel"))]
|
||||
otel_context: None,
|
||||
})
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
pub fn new_authenticate_v2(
|
||||
shared_key: &SharedGatewayKey,
|
||||
identity_keys: &ed25519::KeyPair,
|
||||
@@ -142,8 +162,27 @@ impl ClientControlRequest {
|
||||
// if we're using v2 authentication, we must announce at least that protocol version
|
||||
let protocol_version = AUTHENTICATE_V2_PROTOCOL_VERSION;
|
||||
|
||||
#[cfg(feature = "otel")]
|
||||
let context_carrier = {
|
||||
use nym_bin_common::opentelemetry::context::extract_trace_id_from_tracing_cx;
|
||||
let trace_id = extract_trace_id_from_tracing_cx();
|
||||
|
||||
use tracing_opentelemetry::OpenTelemetrySpanExt;
|
||||
|
||||
let current_span = tracing::Span::current();
|
||||
let otel_context = current_span.context();
|
||||
ContextCarrier::new_with_current_context(otel_context).into_map()
|
||||
};
|
||||
#[cfg(not(feature = "otel"))]
|
||||
let context_carrier: HashMap<String, String> = HashMap::new();
|
||||
|
||||
Ok(ClientControlRequest::AuthenticateV2(Box::new(
|
||||
AuthenticateRequest::new(protocol_version, shared_key, identity_keys)?,
|
||||
AuthenticateRequest::new(
|
||||
protocol_version,
|
||||
shared_key,
|
||||
identity_keys,
|
||||
Some(context_carrier)
|
||||
)?,
|
||||
)))
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "nym-http-api-client-macro"
|
||||
version = "0.1.0"
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
homepage.workspace = true
|
||||
documentation.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
readme.workspace = true
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1"
|
||||
syn = { workspace = true, features = ["full"] }
|
||||
quote = "1.0.40"
|
||||
proc-macro-crate = "3"
|
||||
uuid = { version = "1.0", features = ["v4"] }
|
||||
|
||||
[dev-dependencies]
|
||||
nym-http-api-client = { path = "../http-api-client" }
|
||||
reqwest = { workspace = true }
|
||||
|
||||
[features]
|
||||
debug-inventory = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,13 @@
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
// Enable debug output during build
|
||||
if env::var("CARGO_FEATURE_DEBUG_INVENTORY").is_ok() || env::var("DEBUG_HTTP_INVENTORY").is_ok()
|
||||
{
|
||||
println!("cargo:warning=HTTP Client Inventory Debug Enabled");
|
||||
println!("cargo:rustc-cfg=debug_inventory");
|
||||
}
|
||||
|
||||
// Force rebuild when this environment variable changes
|
||||
println!("cargo:rerun-if-env-changed=DEBUG_HTTP_INVENTORY");
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
//! Proc-macros for configuring HTTP clients globally via the `inventory` crate.
|
||||
//!
|
||||
//! This crate provides macros that allow any crate in the workspace to contribute
|
||||
//! configuration modifications to `reqwest::ClientBuilder` instances through a
|
||||
//! compile-time registry pattern.
|
||||
//!
|
||||
//! # Overview
|
||||
//!
|
||||
//! The macros work by:
|
||||
//! 1. Collecting configuration functions from across all crates at compile time
|
||||
//! 2. Sorting them by priority (lower numbers run first)
|
||||
//! 3. Applying them sequentially to build HTTP clients with consistent settings
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ## Basic Usage with `client_defaults!`
|
||||
//!
|
||||
//! ```ignore
|
||||
//! use nym_http_api_client_macro::client_defaults;
|
||||
//!
|
||||
//! // Register default configurations with priority
|
||||
//! client_defaults!(
|
||||
//! priority = 10; // Optional, defaults to 0
|
||||
//! timeout = std::time::Duration::from_secs(30),
|
||||
//! gzip = true,
|
||||
//! user_agent = "MyApp/1.0"
|
||||
//! );
|
||||
//! ```
|
||||
//!
|
||||
//! ## Using `client_cfg!` for one-off configurations
|
||||
//!
|
||||
//! ```ignore
|
||||
//! use nym_http_api_client_macro::client_cfg;
|
||||
//!
|
||||
//! let configure = client_cfg!(
|
||||
//! timeout = std::time::Duration::from_secs(60),
|
||||
//! default_headers {
|
||||
//! "X-Custom-Header" => "value",
|
||||
//! "Authorization" => "auth_token"
|
||||
//! }
|
||||
//! );
|
||||
//!
|
||||
//! let builder = reqwest::ClientBuilder::new();
|
||||
//! let configured = configure(builder);
|
||||
//! ```
|
||||
//!
|
||||
//! # DSL Reference
|
||||
//!
|
||||
//! The macro DSL supports several patterns:
|
||||
//! - `key = value` - Calls `builder.key(value)`
|
||||
//! - `key(arg1, arg2)` - Calls `builder.key(arg1, arg2)`
|
||||
//! - `flag` - Calls `builder.flag()` with no arguments
|
||||
//! - `default_headers { "name" => "value", ... }` - Sets default headers
|
||||
//!
|
||||
//! # Priority System
|
||||
//!
|
||||
//! Configurations are applied in priority order (lower numbers first):
|
||||
//! - Negative priorities: Early configuration (e.g., -100 for base settings)
|
||||
//! - Zero (default): Standard configuration
|
||||
//! - Positive priorities: Late configuration (e.g., 100 for overrides)
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
use proc_macro_crate::{crate_name, FoundCrate};
|
||||
use quote::{format_ident, quote};
|
||||
use syn::{
|
||||
braced,
|
||||
parse::{Parse, ParseStream},
|
||||
parse_macro_input,
|
||||
punctuated::Punctuated,
|
||||
token, Expr, Ident, LitInt, Result, Token,
|
||||
};
|
||||
|
||||
// ------------------ core crate path resolution ------------------
|
||||
|
||||
fn core_path() -> TokenStream2 {
|
||||
match crate_name("nym-http-api-client") {
|
||||
Ok(FoundCrate::Itself) => quote!(crate),
|
||||
Ok(FoundCrate::Name(name)) => {
|
||||
let ident = Ident::new(&name, Span::call_site());
|
||||
quote!( ::#ident )
|
||||
}
|
||||
Err(_) => {
|
||||
// Fallback if the crate is not found by name (unlikely if deps set up correctly)
|
||||
quote!(::nym_http_api_client)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------ DSL parsing ------------------
|
||||
|
||||
struct Items(Punctuated<Item, Token![,]>);
|
||||
impl Parse for Items {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
Ok(Self(Punctuated::parse_terminated(input)?))
|
||||
}
|
||||
}
|
||||
|
||||
enum Item {
|
||||
Assign {
|
||||
key: Ident,
|
||||
_eq: Token![=],
|
||||
value: Expr,
|
||||
}, // foo = EXPR
|
||||
Call {
|
||||
key: Ident,
|
||||
args: Punctuated<Expr, Token![,]>,
|
||||
_p: token::Paren,
|
||||
}, // foo(a,b)
|
||||
DefaultHeaders {
|
||||
_key: Ident,
|
||||
map: HeaderMapInit,
|
||||
}, // default_headers { ... }
|
||||
Flag {
|
||||
key: Ident,
|
||||
}, // foo
|
||||
}
|
||||
|
||||
impl Parse for Item {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let key: Ident = input.parse()?;
|
||||
|
||||
if input.peek(Token![=]) {
|
||||
let _eq: Token![=] = input.parse()?;
|
||||
let value: Expr = input.parse()?;
|
||||
return Ok(Self::Assign { key, _eq, value });
|
||||
}
|
||||
if input.peek(token::Paren) {
|
||||
let content;
|
||||
let _p = syn::parenthesized!(content in input);
|
||||
let args = Punctuated::<Expr, Token![,]>::parse_terminated(&content)?;
|
||||
return Ok(Self::Call { key, args, _p });
|
||||
}
|
||||
if input.peek(token::Brace) && key == format_ident!("default_headers") {
|
||||
let map = input.parse::<HeaderMapInit>()?;
|
||||
return Ok(Self::DefaultHeaders { _key: key, map });
|
||||
}
|
||||
Ok(Self::Flag { key })
|
||||
}
|
||||
}
|
||||
|
||||
struct HeaderPair {
|
||||
k: Expr,
|
||||
_arrow: Token![=>],
|
||||
v: Expr,
|
||||
}
|
||||
impl Parse for HeaderPair {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
Ok(Self {
|
||||
k: input.parse()?,
|
||||
_arrow: input.parse()?,
|
||||
v: input.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct HeaderMapInit {
|
||||
_brace: token::Brace,
|
||||
pairs: Punctuated<HeaderPair, Token![,]>,
|
||||
}
|
||||
impl Parse for HeaderMapInit {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
let content;
|
||||
let _brace = braced!(content in input);
|
||||
let pairs = Punctuated::<HeaderPair, Token![,]>::parse_terminated(&content)?;
|
||||
Ok(Self { _brace, pairs })
|
||||
}
|
||||
}
|
||||
|
||||
// Generate statements that mutate a builder named `b` using the resolved core path.
|
||||
fn to_stmts(items: Items, core: &TokenStream2) -> TokenStream2 {
|
||||
let mut stmts = Vec::new();
|
||||
for it in items.0 {
|
||||
match it {
|
||||
Item::Assign { key, value, .. } => {
|
||||
let m = key;
|
||||
stmts.push(quote! { b = b.#m(#value); });
|
||||
}
|
||||
Item::Call { key, args, .. } => {
|
||||
let m = key;
|
||||
let args = args.iter();
|
||||
stmts.push(quote! { b = b.#m( #( #args ),* ); });
|
||||
}
|
||||
Item::DefaultHeaders { map, .. } => {
|
||||
let (ks, vs): (Vec<_>, Vec<_>) = map.pairs.into_iter().map(|p| (p.k, p.v)).unzip();
|
||||
stmts.push(quote! {
|
||||
let mut __cm = #core::reqwest::header::HeaderMap::new();
|
||||
#(
|
||||
{
|
||||
use #core::reqwest::header::{HeaderName, HeaderValue};
|
||||
let __k = HeaderName::try_from(#ks)
|
||||
.unwrap_or_else(|e| panic!("Invalid header name: {}", e));
|
||||
let __v = HeaderValue::try_from(#vs)
|
||||
.unwrap_or_else(|e| panic!("Invalid header value: {}", e));
|
||||
__cm.insert(__k, __v);
|
||||
}
|
||||
)*
|
||||
b = b.default_headers(__cm);
|
||||
});
|
||||
}
|
||||
Item::Flag { key } => {
|
||||
let m = key;
|
||||
stmts.push(quote! { b = b.#m(); });
|
||||
}
|
||||
}
|
||||
}
|
||||
quote! { #(#stmts)* }
|
||||
}
|
||||
|
||||
// ------------------ client_cfg! ------------------
|
||||
|
||||
/// Creates a closure that configures a `ReqwestClientBuilder`.
|
||||
///
|
||||
/// This macro generates a closure that can be used to configure a single
|
||||
/// `reqwest::ClientBuilder` instance without affecting global defaults.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// use nym_http_api_client_macro::client_cfg;
|
||||
///
|
||||
/// let config = client_cfg!(
|
||||
/// timeout = std::time::Duration::from_secs(30),
|
||||
/// gzip = true
|
||||
/// );
|
||||
/// let client = config(reqwest::ClientBuilder::new()).build().unwrap();
|
||||
/// ```
|
||||
#[proc_macro]
|
||||
pub fn client_cfg(input: TokenStream) -> TokenStream {
|
||||
let items = parse_macro_input!(input as Items);
|
||||
let core = core_path();
|
||||
let body = to_stmts(items, &core);
|
||||
let out = quote! {
|
||||
|mut b: #core::ReqwestClientBuilder| { #body b }
|
||||
};
|
||||
out.into()
|
||||
}
|
||||
|
||||
// ------------------ client_defaults! with optional priority header ------------------
|
||||
|
||||
struct MaybePrioritized {
|
||||
priority: i32,
|
||||
items: Items,
|
||||
}
|
||||
impl Parse for MaybePrioritized {
|
||||
fn parse(input: ParseStream<'_>) -> Result<Self> {
|
||||
// Optional header: `priority = <int> ;`
|
||||
let fork = input.fork();
|
||||
let mut priority = 0i32;
|
||||
if fork.peek(Ident) && fork.parse::<Ident>()? == "priority" && fork.peek(Token![=]) {
|
||||
// commit
|
||||
let _ = input.parse::<Ident>()?; // priority
|
||||
let _ = input.parse::<Token![=]>()?;
|
||||
let lit: LitInt = input.parse()?;
|
||||
priority = lit.base10_parse()?;
|
||||
let _ = input.parse::<Token![;]>()?;
|
||||
}
|
||||
let items = input.parse::<Items>()?;
|
||||
Ok(Self { priority, items })
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers global default configurations for HTTP clients.
|
||||
///
|
||||
/// This macro submits a configuration record to the global registry that will
|
||||
/// be applied to all HTTP clients created with `default_builder()`.
|
||||
///
|
||||
/// # Parameters
|
||||
///
|
||||
/// - `priority` (optional): Integer priority for ordering (lower runs first, default: 0)
|
||||
/// - Configuration items: Any valid `reqwest::ClientBuilder` method calls
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// use nym_http_api_client_macro::client_defaults;
|
||||
///
|
||||
/// client_defaults!(
|
||||
/// priority = -50; // Run early in the configuration chain
|
||||
/// connect_timeout = std::time::Duration::from_secs(10),
|
||||
/// pool_max_idle_per_host = 32,
|
||||
/// default_headers {
|
||||
/// "User-Agent" => "MyApp/1.0",
|
||||
/// "Accept" => "application/json"
|
||||
/// }
|
||||
/// );
|
||||
/// ```
|
||||
#[proc_macro]
|
||||
pub fn client_defaults(input: TokenStream) -> TokenStream {
|
||||
let MaybePrioritized { priority, items } = parse_macro_input!(input as MaybePrioritized);
|
||||
let core = core_path();
|
||||
|
||||
// Generate a description of what this config does (before consuming items)
|
||||
let config_description = if cfg!(feature = "debug-inventory") {
|
||||
let descriptions = items
|
||||
.0
|
||||
.iter()
|
||||
.map(|item| match item {
|
||||
Item::Assign { key, value, .. } => {
|
||||
format!("{}={:?}", quote!(#key), quote!(#value).to_string())
|
||||
}
|
||||
Item::Call { key, args, .. } => {
|
||||
let args_str = args
|
||||
.iter()
|
||||
.map(|a| quote!(#a).to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
format!("{}({})", quote!(#key), args_str)
|
||||
}
|
||||
Item::Flag { key } => {
|
||||
format!("{}()", quote!(#key))
|
||||
}
|
||||
Item::DefaultHeaders { .. } => "default_headers{{...}}".to_string(),
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
|
||||
quote! {
|
||||
pub const __CONFIG_DESC: &str = #descriptions;
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
// Now consume items to generate the body
|
||||
let body = to_stmts(items, &core);
|
||||
|
||||
// Generate a unique identifier for this submission
|
||||
let submission_id = format!("__client_defaults_{}", uuid::Uuid::new_v4().simple());
|
||||
let submission_ident = syn::Ident::new(&submission_id, proc_macro2::Span::call_site());
|
||||
|
||||
// Debug output at compile time if enabled
|
||||
if std::env::var("DEBUG_HTTP_INVENTORY").is_ok() {
|
||||
eprintln!(
|
||||
"cargo:warning=[HTTP-INVENTORY] Registering config with priority={} from {}",
|
||||
priority,
|
||||
std::env::var("CARGO_PKG_NAME").unwrap_or_else(|_| "unknown".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
// Add debug_print_inventory call if the feature is enabled
|
||||
let debug_call = if cfg!(feature = "debug-inventory") {
|
||||
quote! {
|
||||
#config_description
|
||||
|
||||
// Ensure the debug function gets called when config is applied
|
||||
pub fn __cfg_with_debug(
|
||||
b: #core::ReqwestClientBuilder
|
||||
) -> #core::ReqwestClientBuilder {
|
||||
eprintln!("[HTTP-INVENTORY] Applying: {} (priority={})", __CONFIG_DESC, #priority);
|
||||
__cfg(b)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! {}
|
||||
};
|
||||
|
||||
// Use the debug wrapper if feature is enabled
|
||||
let apply_fn = if cfg!(feature = "debug-inventory") {
|
||||
quote! { __cfg_with_debug }
|
||||
} else {
|
||||
quote! { __cfg }
|
||||
};
|
||||
|
||||
let out = quote! {
|
||||
#[allow(non_snake_case)]
|
||||
mod #submission_ident {
|
||||
use super::*;
|
||||
#[allow(unused)]
|
||||
pub fn __cfg(
|
||||
mut b: #core::ReqwestClientBuilder
|
||||
) -> #core::ReqwestClientBuilder {
|
||||
#body
|
||||
b
|
||||
}
|
||||
|
||||
#debug_call
|
||||
|
||||
#core::inventory::submit! {
|
||||
#core::registry::ConfigRecord {
|
||||
priority: #priority,
|
||||
apply: #apply_fn,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
out.into()
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
use nym_http_api_client_macro::{client_cfg, client_defaults};
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn test_client_cfg_basic() {
|
||||
// Test that the macro compiles with basic configuration
|
||||
let _config = client_cfg!(timeout = Duration::from_secs(30), gzip = true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_client_cfg_with_headers() {
|
||||
// Test that the macro compiles with default headers
|
||||
let _config = client_cfg!(
|
||||
timeout = Duration::from_secs(30),
|
||||
default_headers {
|
||||
"User-Agent" => "TestApp/1.0",
|
||||
"Accept" => "application/json"
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_client_cfg_with_method_calls() {
|
||||
// Test that the macro compiles with method calls
|
||||
let _config = client_cfg!(
|
||||
pool_max_idle_per_host = 32,
|
||||
tcp_nodelay = true,
|
||||
danger_accept_invalid_certs = true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_client_defaults_with_priority() {
|
||||
// Test that client_defaults macro compiles with priority
|
||||
client_defaults!(
|
||||
priority = -100;
|
||||
gzip = true,
|
||||
deflate = true
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_client_defaults_without_priority() {
|
||||
// Test that client_defaults macro compiles without priority (defaults to 0)
|
||||
client_defaults!(brotli = true, zstd = true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_client_cfg() {
|
||||
// Test that empty configuration compiles
|
||||
let _config = client_cfg!();
|
||||
}
|
||||
|
||||
// Integration test to verify the closure actually works
|
||||
#[test]
|
||||
fn test_client_cfg_closure_application() {
|
||||
let config = client_cfg!(gzip = true);
|
||||
|
||||
// Apply the configuration to a new builder
|
||||
let builder = reqwest::ClientBuilder::new();
|
||||
let _configured_builder = config(builder);
|
||||
// Note: We can't easily test the internal state of the builder,
|
||||
// but we verify it compiles and runs without panic
|
||||
}
|
||||
@@ -11,21 +11,31 @@ license.workspace = true
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
# TODO: Remove otel from default before release
|
||||
default=["tunneling"]
|
||||
tunneling=[]
|
||||
network-defaults = ["dep:nym-network-defaults"]
|
||||
otel = ["nym-bin-common/otel", "opentelemetry", "opentelemetry_sdk"]
|
||||
debug-inventory = ["nym-http-api-client-macro/debug-inventory"]
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
bincode = { workspace = true }
|
||||
cfg-if = { workspace = true}
|
||||
reqwest = { workspace = true, features = ["json", "gzip", "deflate", "brotli", "zstd", "rustls-tls"] }
|
||||
http.workspace = true
|
||||
url = { workspace = true }
|
||||
once_cell = { workspace = true }
|
||||
opentelemetry = { workspace = true, optional = true }
|
||||
opentelemetry_sdk = { workspace = true, optional = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_yaml = { workspace = true}
|
||||
serde_plain = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
inventory = { workspace = true }
|
||||
|
||||
# used for decoding text responses (they were already implicitly included)
|
||||
bytes = { workspace = true }
|
||||
@@ -34,6 +44,8 @@ mime = { workspace = true }
|
||||
|
||||
nym-http-api-common = { path = "../http-api-common", default-features = false }
|
||||
nym-bin-common = { path = "../bin-common" }
|
||||
nym-network-defaults = { path = "../network-defaults", optional = true }
|
||||
nym-http-api-client-macro = { path = "../http-api-client-macro" }
|
||||
|
||||
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
|
||||
hickory-resolver = { workspace = true, features = ["https-ring", "tls-ring", "webpki-roots"] }
|
||||
@@ -45,4 +57,3 @@ features = ["tokio"]
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { workspace = true, features = ["rt", "macros"] }
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
use nym_http_api_client::registry;
|
||||
|
||||
fn main() {
|
||||
println!("Debugging HTTP Client Inventory");
|
||||
println!("================================");
|
||||
|
||||
// Print all registered configurations
|
||||
registry::debug_print_inventory();
|
||||
|
||||
// Also print the count
|
||||
println!(
|
||||
"\nTotal registered configs: {}",
|
||||
registry::registered_config_count()
|
||||
);
|
||||
|
||||
// Show the detailed breakdown
|
||||
println!("\nDetailed configuration list:");
|
||||
for (i, (priority, ptr)) in registry::inspect_registered_configs().iter().enumerate() {
|
||||
println!(
|
||||
" Config #{}: priority={}, function=0x{:x}",
|
||||
i + 1,
|
||||
priority,
|
||||
ptr
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
use nym_http_api_client::registry;
|
||||
use nym_http_api_client::{inventory, ReqwestClientBuilder};
|
||||
use nym_http_api_client_macro::client_defaults;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
println!("Testing HTTP Client Timeout Configuration");
|
||||
println!("==========================================\n");
|
||||
|
||||
client_defaults!(timeout = std::time::Duration::from_secs(300),);
|
||||
|
||||
// Build a client using the registry (should have 300s timeout)
|
||||
let client = registry::build_client().expect("Failed to build client");
|
||||
|
||||
println!("Testing timeout behavior...");
|
||||
println!("The inventory should have set timeout to 300 seconds");
|
||||
|
||||
// Test 1: Try a request to a slow endpoint that delays for 5 seconds
|
||||
// This should succeed since timeout is 300s
|
||||
println!("\nTest 1: Request with 5 second delay (should succeed)");
|
||||
let start = Instant::now();
|
||||
match client.get("https://httpbin.org/delay/5").send().await {
|
||||
Ok(_) => {
|
||||
let elapsed = start.elapsed();
|
||||
println!("✓ Request succeeded after {:?}", elapsed);
|
||||
}
|
||||
Err(e) => {
|
||||
let elapsed = start.elapsed();
|
||||
if e.is_timeout() {
|
||||
println!(
|
||||
"✗ Request timed out after {:?} - timeout might be shorter than expected!",
|
||||
elapsed
|
||||
);
|
||||
} else {
|
||||
println!("✗ Request failed after {:?}: {}", elapsed, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test 2: Try to inspect the client's actual configuration
|
||||
println!("\nTest 2: Client debug information");
|
||||
println!("Client Debug: {:?}", client);
|
||||
|
||||
// Test 3: Create a client with explicit short timeout to compare behavior
|
||||
println!("\nTest 3: Control test with 2 second timeout");
|
||||
let short_timeout_client = reqwest::Client::builder()
|
||||
.timeout(Duration::from_secs(2))
|
||||
.build()
|
||||
.expect("Failed to build short timeout client");
|
||||
|
||||
let start = Instant::now();
|
||||
match short_timeout_client
|
||||
.get("https://httpbin.org/delay/5")
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
let elapsed = start.elapsed();
|
||||
println!(
|
||||
"✗ Request succeeded after {:?} - timeout not working!",
|
||||
elapsed
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
let elapsed = start.elapsed();
|
||||
if e.is_timeout() {
|
||||
println!("✓ Request timed out as expected after {:?}", elapsed);
|
||||
} else {
|
||||
println!("? Request failed after {:?}: {}", elapsed, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test 4: Create a client through the registry and verify timeout on a hanging connection
|
||||
println!("\nTest 4: Testing with a connection that hangs");
|
||||
println!("Making request to an endpoint that will hang...");
|
||||
|
||||
let start = Instant::now();
|
||||
// This IP is reserved for documentation and will hang
|
||||
match client.get("http://192.0.2.1:81").send().await {
|
||||
Ok(_) => {
|
||||
println!("✗ Request succeeded - unexpected!");
|
||||
}
|
||||
Err(e) => {
|
||||
let elapsed = start.elapsed();
|
||||
if e.is_timeout() {
|
||||
println!("✓ Request timed out after {:?}", elapsed);
|
||||
if elapsed < Duration::from_secs(290) {
|
||||
println!(" Note: Timeout occurred faster than 300s, might be connection timeout not total timeout");
|
||||
}
|
||||
} else if e.is_connect() {
|
||||
println!("✓ Connection failed after {:?} (connect timeout)", elapsed);
|
||||
} else {
|
||||
println!("? Request failed after {:?}: {}", elapsed, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,10 +54,14 @@ impl Front {
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Clone)]
|
||||
#[cfg(feature = "tunneling")]
|
||||
/// Policy for when to use domain fronting for HTTP requests.
|
||||
pub enum FrontPolicy {
|
||||
/// Always use domain fronting for all requests.
|
||||
Always,
|
||||
/// Only use domain fronting when retrying failed requests.
|
||||
OnRetry,
|
||||
#[default]
|
||||
/// Never use domain fronting.
|
||||
Off,
|
||||
}
|
||||
|
||||
@@ -96,14 +100,14 @@ mod tests {
|
||||
// Some(vec!["https://cdn77.com"]),
|
||||
// ).unwrap(); // cdn77
|
||||
|
||||
let client = ClientBuilder::new::<_, &str>(url1)
|
||||
let client = ClientBuilder::new(url1)
|
||||
.expect("bad url")
|
||||
.with_fronting(FrontPolicy::Always)
|
||||
.build::<&str>()
|
||||
.build()
|
||||
.expect("failed to build client");
|
||||
|
||||
let response = client
|
||||
.send_request::<_, (), &str, &str, &str>(
|
||||
.send_request::<_, (), &str, &str>(
|
||||
reqwest::Method::GET,
|
||||
&["api", "v1", "network", "details"],
|
||||
NO_PARAMS,
|
||||
|
||||
+453
-163
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,101 @@
|
||||
//! Global registry for HTTP client configurations.
|
||||
//!
|
||||
//! This module provides a compile-time registry system that allows any crate
|
||||
//! in the workspace to contribute configuration modifications to HTTP clients.
|
||||
|
||||
use crate::ReqwestClientBuilder;
|
||||
|
||||
/// A configuration record that modifies a `ReqwestClientBuilder`.
|
||||
///
|
||||
/// Records are collected at compile-time via the `inventory` crate and
|
||||
/// applied in priority order when building HTTP clients.
|
||||
pub struct ConfigRecord {
|
||||
/// Lower numbers run earlier.
|
||||
pub priority: i32,
|
||||
/// A function that takes a builder and returns a mutated builder.
|
||||
pub apply: fn(ReqwestClientBuilder) -> ReqwestClientBuilder,
|
||||
}
|
||||
|
||||
inventory::collect!(ConfigRecord);
|
||||
|
||||
/// Returns the default builder with all registered configurations applied.
|
||||
pub fn default_builder() -> ReqwestClientBuilder {
|
||||
let mut b = ReqwestClientBuilder::new();
|
||||
let mut records: Vec<&'static ConfigRecord> =
|
||||
inventory::iter::<ConfigRecord>.into_iter().collect();
|
||||
records.sort_by_key(|r| r.priority); // lower runs first
|
||||
|
||||
#[cfg(feature = "debug-inventory")]
|
||||
{
|
||||
eprintln!(
|
||||
"[HTTP-INVENTORY] Building client with {} registered configurations",
|
||||
records.len()
|
||||
);
|
||||
}
|
||||
|
||||
for r in records {
|
||||
b = (r.apply)(b);
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug-inventory")]
|
||||
{
|
||||
eprintln!("[HTTP-INVENTORY] Final builder state (Debug):");
|
||||
eprintln!("{:#?}", b);
|
||||
eprintln!(
|
||||
"[HTTP-INVENTORY] Note: reqwest::ClientBuilder doesn't expose all internal state"
|
||||
);
|
||||
eprintln!("[HTTP-INVENTORY] Building test client to verify configuration...");
|
||||
|
||||
// Try to build a client to see if it works
|
||||
match b.try_clone().unwrap().build() {
|
||||
Ok(client) => {
|
||||
eprintln!("[HTTP-INVENTORY] ✓ Client built successfully");
|
||||
eprintln!("[HTTP-INVENTORY] Client debug info: {:#?}", client);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[HTTP-INVENTORY] ✗ Failed to build client: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
b
|
||||
}
|
||||
|
||||
/// Builds a client using the default builder with all registered configurations.
|
||||
pub fn build_client() -> reqwest::Result<reqwest::Client> {
|
||||
default_builder().build()
|
||||
}
|
||||
|
||||
/// Debug function to inspect registered configurations.
|
||||
/// Returns a vector of (priority, function_pointer) tuples for debugging.
|
||||
pub fn inspect_registered_configs() -> Vec<(i32, usize)> {
|
||||
let mut configs: Vec<(i32, usize)> = inventory::iter::<ConfigRecord>
|
||||
.into_iter()
|
||||
.map(|record| (record.priority, record.apply as usize))
|
||||
.collect();
|
||||
configs.sort_by_key(|(priority, _)| *priority);
|
||||
configs
|
||||
}
|
||||
|
||||
/// Print all registered configurations to stderr for debugging.
|
||||
/// This shows the priority and function pointer address of each registered config.
|
||||
pub fn debug_print_inventory() {
|
||||
eprintln!("[HTTP-INVENTORY] Registered configurations:");
|
||||
let configs = inspect_registered_configs();
|
||||
if configs.is_empty() {
|
||||
eprintln!(" (none)");
|
||||
} else {
|
||||
for (i, (priority, ptr)) in configs.iter().enumerate() {
|
||||
eprintln!(
|
||||
" [{:2}] Priority: {:4}, Function: 0x{:016x}",
|
||||
i, priority, ptr
|
||||
);
|
||||
}
|
||||
eprintln!(" Total: {} configurations", configs.len());
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the count of registered configuration records.
|
||||
pub fn registered_config_count() -> usize {
|
||||
inventory::iter::<ConfigRecord>.into_iter().count()
|
||||
}
|
||||
@@ -95,10 +95,10 @@ async fn api_client_retry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
"http://example.com/".parse()?,
|
||||
])
|
||||
.with_retries(3)
|
||||
.build::<HttpClientError>()?;
|
||||
.build()?;
|
||||
|
||||
let req = client.create_get_request(&["/"], NO_PARAMS);
|
||||
let resp = client.send::<HttpClientError>(req).await?;
|
||||
let req = client.create_get_request(&["/"], NO_PARAMS).unwrap();
|
||||
let resp = client.send(req).await?;
|
||||
|
||||
assert_eq!(resp.status(), 200);
|
||||
|
||||
@@ -111,10 +111,7 @@ async fn api_client_retry() -> Result<(), Box<dyn std::error::Error>> {
|
||||
#[test]
|
||||
fn host_updating() {
|
||||
let url = Url::new("http://example.com", None).unwrap();
|
||||
let mut client = ClientBuilder::new::<_, &str>(url)
|
||||
.unwrap()
|
||||
.build::<&str>()
|
||||
.unwrap();
|
||||
let mut client = ClientBuilder::new(url).unwrap().build().unwrap();
|
||||
|
||||
// check that the url is set correctly
|
||||
let current_url = client.current_url();
|
||||
@@ -171,10 +168,10 @@ fn host_updating() {
|
||||
#[cfg(feature = "tunneling")]
|
||||
fn fronted_host_updating() {
|
||||
let url = Url::new("http://example.com", Some(vec!["http://front1.com"])).unwrap();
|
||||
let mut client = ClientBuilder::new::<_, &str>(url)
|
||||
let mut client = ClientBuilder::new(url)
|
||||
.unwrap()
|
||||
.with_fronting(crate::fronted::FrontPolicy::Always)
|
||||
.build::<&str>()
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
// check that the url is set correctly
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
use nym_http_api_client::registry;
|
||||
|
||||
// Create separate modules to avoid name conflicts
|
||||
mod config_early {
|
||||
use nym_http_api_client_macro::client_defaults;
|
||||
|
||||
client_defaults!(
|
||||
priority = -200;
|
||||
tcp_nodelay = true
|
||||
);
|
||||
}
|
||||
|
||||
mod config_late {
|
||||
use nym_http_api_client_macro::client_defaults;
|
||||
|
||||
client_defaults!(
|
||||
priority = 100;
|
||||
pool_idle_timeout = std::time::Duration::from_secs(90)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_registry_collects_configs() {
|
||||
// Verify that configurations are being registered
|
||||
let count = registry::registered_config_count();
|
||||
// Should have at least the ones we registered above plus the default from lib.rs
|
||||
assert!(
|
||||
count >= 3,
|
||||
"Expected at least 3 registered configs, got {}",
|
||||
count
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_builder_applies_configs() {
|
||||
// Test that default_builder returns a configured builder
|
||||
let _builder = registry::default_builder();
|
||||
// The builder should have all configurations applied
|
||||
// We can't easily inspect the internals, but we verify it doesn't panic
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_client_works() {
|
||||
// Test that we can successfully build a client with all configurations
|
||||
let result = registry::build_client();
|
||||
assert!(result.is_ok(), "Failed to build client: {:?}", result.err());
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
#[test]
|
||||
fn test_inspect_configs() {
|
||||
// In debug mode, test that we can inspect registered configurations
|
||||
let configs = registry::inspect_registered_configs();
|
||||
|
||||
// Verify configs are sorted by priority
|
||||
for window in configs.windows(2) {
|
||||
assert!(window[0].0 <= window[1].0, "Configs not sorted by priority");
|
||||
}
|
||||
|
||||
// Verify we have configs at different priority levels
|
||||
let priorities: Vec<i32> = configs.iter().map(|(p, _)| *p).collect();
|
||||
assert!(
|
||||
priorities.iter().any(|&p| p < 0),
|
||||
"Expected negative priority configs"
|
||||
);
|
||||
assert!(
|
||||
priorities.iter().any(|&p| p >= 0),
|
||||
"Expected non-negative priority configs"
|
||||
);
|
||||
}
|
||||
@@ -18,6 +18,7 @@ bytes = { workspace = true, optional = true }
|
||||
colored = { workspace = true, optional = true }
|
||||
futures = { workspace = true, optional = true }
|
||||
mime = { workspace = true, optional = true }
|
||||
nym-bin-common = { path = "../bin-common" }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
serde_yaml = { workspace = true, optional = true }
|
||||
@@ -49,6 +50,8 @@ middleware = [
|
||||
"zeroize"
|
||||
]
|
||||
|
||||
otel = ["nym-bin-common/otel"]
|
||||
|
||||
utoipa = ["dep:utoipa"]
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -56,6 +56,16 @@ async fn log_request(
|
||||
|
||||
let host = header_map(request.headers().get(HOST), "Unknown Host".to_string());
|
||||
|
||||
// Extract traceparent from headers if it exists
|
||||
#[cfg(feature = "otel")]
|
||||
let traceparent = request
|
||||
.headers()
|
||||
.get("traceparent")
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.map(|s| s.to_string());
|
||||
#[cfg(not(feature = "otel"))]
|
||||
let traceparent: Option<String> = None;
|
||||
|
||||
let start = Instant::now();
|
||||
// run request through all middleware, incl. extractors
|
||||
let res = next.run(request).await;
|
||||
@@ -82,10 +92,10 @@ async fn log_request(
|
||||
|
||||
match level {
|
||||
LogLevel::Debug => debug!(
|
||||
"[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}"
|
||||
"[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent} traceparent: {traceparent:?}",
|
||||
),
|
||||
LogLevel::Info => info!(
|
||||
"[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent}"
|
||||
"[{addr} -> {host}] {method} '{uri}': {print_status} {time_taken} {agent_str}: {agent} traceparent: {traceparent:?}"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ pub const NYM_APIS: &[ApiUrlConst] = &[
|
||||
front_hosts: None,
|
||||
},
|
||||
ApiUrlConst {
|
||||
url: "https://nym-fronntdoor.vercel.app/api/",
|
||||
url: "https://nym-frontdoor.vercel.app/api/",
|
||||
front_hosts: Some(&["vercel.app", "vercel.com"]),
|
||||
},
|
||||
ApiUrlConst {
|
||||
|
||||
@@ -55,6 +55,7 @@ pub struct ApiUrl {
|
||||
pub front_hosts: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ApiUrlConst<'a> {
|
||||
pub url: &'a str,
|
||||
pub front_hosts: Option<&'a [&'a str]>,
|
||||
@@ -188,8 +189,14 @@ impl NymNetworkDetails {
|
||||
),
|
||||
},
|
||||
nym_vpn_api_url: parse_optional_str(mainnet::NYM_VPN_API),
|
||||
nym_api_urls: None,
|
||||
nym_vpn_api_urls: None,
|
||||
nym_api_urls: Some(mainnet::NYM_APIS.iter().copied().map(Into::into).collect()),
|
||||
nym_vpn_api_urls: Some(
|
||||
mainnet::NYM_VPN_APIS
|
||||
.iter()
|
||||
.copied()
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use futures::StreamExt;
|
||||
use nym_crypto::asymmetric::x25519;
|
||||
use nym_sphinx::acknowledgements::AckKey;
|
||||
use nym_sphinx::receiver::{MessageReceiver, SphinxMessageReceiver};
|
||||
use nym_task::TaskClient;
|
||||
use nym_task::ShutdownToken;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -24,7 +24,7 @@ pub struct SimpleMessageReceiver<T, R: MessageReceiver = SphinxMessageReceiver>
|
||||
acks_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
|
||||
|
||||
received_sender: ReceivedSender<T>,
|
||||
shutdown: TaskClient,
|
||||
shutdown: ShutdownToken,
|
||||
}
|
||||
|
||||
impl<T> SimpleMessageReceiver<T, SphinxMessageReceiver> {
|
||||
@@ -34,7 +34,7 @@ impl<T> SimpleMessageReceiver<T, SphinxMessageReceiver> {
|
||||
mixnet_message_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
|
||||
acks_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
|
||||
received_sender: ReceivedSender<T>,
|
||||
shutdown: TaskClient,
|
||||
shutdown: ShutdownToken,
|
||||
) -> Self {
|
||||
Self::new(
|
||||
local_encryption_keypair,
|
||||
@@ -54,7 +54,7 @@ impl<T, R: MessageReceiver> SimpleMessageReceiver<T, R> {
|
||||
mixnet_message_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
|
||||
acks_receiver: mpsc::UnboundedReceiver<Vec<Vec<u8>>>,
|
||||
received_sender: ReceivedSender<T>,
|
||||
shutdown: TaskClient,
|
||||
shutdown: ShutdownToken,
|
||||
) -> Self {
|
||||
SimpleMessageReceiver {
|
||||
message_processor: TestPacketProcessor::new(local_encryption_keypair, ack_key),
|
||||
@@ -91,11 +91,12 @@ impl<T, R: MessageReceiver> SimpleMessageReceiver<T, R> {
|
||||
where
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
while !self.shutdown.is_shutdown() {
|
||||
loop {
|
||||
tokio::select! {
|
||||
biased;
|
||||
_ = self.shutdown.recv() => {
|
||||
log_info!("SimpleMessageReceiver: received shutdown")
|
||||
_ = self.shutdown.cancelled() => {
|
||||
log_info!("SimpleMessageReceiver: received shutdown");
|
||||
break
|
||||
}
|
||||
mixnet_messages = self.mixnet_message_receiver.next() => {
|
||||
let Some(mixnet_messages) = mixnet_messages else {
|
||||
|
||||
@@ -231,6 +231,7 @@ where
|
||||
&address,
|
||||
&address,
|
||||
PacketType::Mix,
|
||||
None
|
||||
)?)
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ impl TryFrom<&[u8]> for SecretKeyAuth {
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<SecretKeyAuth> {
|
||||
// There should be x and at least one y
|
||||
if bytes.len() < 32 * 2 + 8 || (bytes.len() - 8) % 32 != 0 {
|
||||
if bytes.len() < 32 * 2 + 8 || !(bytes.len() - 8).is_multiple_of(32) {
|
||||
return Err(CompactEcashError::DeserializationInvalidLength {
|
||||
actual: bytes.len(),
|
||||
modulus_target: bytes.len() - 8,
|
||||
@@ -152,7 +152,7 @@ impl TryFrom<&[u8]> for VerificationKeyAuth {
|
||||
|
||||
fn try_from(bytes: &[u8]) -> Result<VerificationKeyAuth> {
|
||||
// There should be at least alpha, one betaG1 and one betaG2 and their length
|
||||
if bytes.len() < 96 * 2 + 48 + 8 || (bytes.len() - 8 - 96) % (96 + 48) != 0 {
|
||||
if bytes.len() < 96 * 2 + 48 + 8 || !(bytes.len() - 8 - 96).is_multiple_of(96 + 48) {
|
||||
return Err(CompactEcashError::DeserializationInvalidLength {
|
||||
actual: bytes.len(),
|
||||
modulus_target: bytes.len() - 8 - 96,
|
||||
|
||||
@@ -8,12 +8,14 @@ license = { workspace = true }
|
||||
repository = { workspace = true }
|
||||
|
||||
[dependencies]
|
||||
sphinx-packet = { workspace = true }
|
||||
tracing = { workspace = true }
|
||||
rand = { workspace = true }
|
||||
rand_distr = { workspace = true }
|
||||
rand_chacha = { workspace = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
nym-bin-common = { path = "../bin-common" }
|
||||
nym-sphinx-acknowledgements = { path = "acknowledgements" }
|
||||
nym-sphinx-addressing = { path = "addressing" }
|
||||
nym-sphinx-anonymous-replies = { path = "anonymous-replies" }
|
||||
@@ -55,3 +57,7 @@ outfox = [
|
||||
"nym-sphinx-params/outfox",
|
||||
"nym-sphinx-types/outfox",
|
||||
]
|
||||
|
||||
otel = [
|
||||
"nym-bin-common/otel",
|
||||
]
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user