Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 291544c311 | |||
| b4f51baf94 | |||
| a3f3d83c1b | |||
| 84d7004cb2 | |||
| be063a36eb | |||
| 0a712b9fce | |||
| 88d6fb4e22 | |||
| 04c2045d94 |
@@ -30,7 +30,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ arc-ubuntu-20.04, custom-runner-mac-m1 ]
|
||||
os: [ arc-ubuntu-20.04, custom-windows-11, custom-runner-mac-m1 ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
+3
-1
@@ -51,4 +51,6 @@ ppa-private-key.b64
|
||||
ppa-private-key.asc
|
||||
nym-network-monitor/topology.json
|
||||
nym-network-monitor/__pycache__
|
||||
nym-network-monitor/*.key
|
||||
nym-network-monitor/*.key
|
||||
nym-network-monitor/.envrc
|
||||
nym-network-monitor/.envrc
|
||||
|
||||
Generated
+82
@@ -2475,6 +2475,12 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
|
||||
|
||||
[[package]]
|
||||
name = "fancy_constructor"
|
||||
version = "1.2.2"
|
||||
@@ -5782,6 +5788,7 @@ dependencies = [
|
||||
"nym-bin-common",
|
||||
"nym-client-core",
|
||||
"nym-crypto",
|
||||
"nym-gateway-requests",
|
||||
"nym-network-defaults",
|
||||
"nym-sdk",
|
||||
"nym-sphinx",
|
||||
@@ -5795,6 +5802,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tokio-postgres",
|
||||
"tokio-util",
|
||||
"utoipa",
|
||||
"utoipa-swagger-ui",
|
||||
@@ -7250,6 +7258,24 @@ dependencies = [
|
||||
"indexmap 2.2.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
|
||||
dependencies = [
|
||||
"siphasher 0.3.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.6"
|
||||
@@ -7388,6 +7414,35 @@ version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0"
|
||||
|
||||
[[package]]
|
||||
name = "postgres-protocol"
|
||||
version = "0.6.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acda0ebdebc28befa84bee35e651e4c5f09073d668c7aed4cf7e23c3cda84b23"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"fallible-iterator",
|
||||
"hmac",
|
||||
"md-5",
|
||||
"memchr",
|
||||
"rand",
|
||||
"sha2 0.10.8",
|
||||
"stringprep",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "postgres-types"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f66ea23a2d0e5734297357705193335e0a957696f34bed2f2faefacb2fec336f"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fallible-iterator",
|
||||
"postgres-protocol",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
@@ -9722,6 +9777,32 @@ dependencies = [
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-postgres"
|
||||
version = "0.7.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b5d3742945bc7d7f210693b0c58ae542c6fd47b17adbbda0885f3dcb34a6bdb"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"fallible-iterator",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"log",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"phf",
|
||||
"pin-project-lite",
|
||||
"postgres-protocol",
|
||||
"postgres-types",
|
||||
"rand",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"whoami",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.24.1"
|
||||
@@ -10912,6 +10993,7 @@ checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d"
|
||||
dependencies = [
|
||||
"redox_syscall 0.5.1",
|
||||
"wasite",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -8,7 +8,10 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use log::{debug, error};
|
||||
use sqlx::ConnectOptions;
|
||||
use sqlx::{
|
||||
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
|
||||
ConnectOptions,
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -30,6 +33,9 @@ impl StorageManager {
|
||||
}
|
||||
|
||||
let opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(database_path)
|
||||
.create_if_missing(true)
|
||||
.disable_statement_logging();
|
||||
@@ -110,7 +116,7 @@ impl StorageManager {
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO registered_gateway(gateway_id_bs58, registration_timestamp, gateway_type)
|
||||
INSERT INTO registered_gateway(gateway_id_bs58, registration_timestamp, gateway_type)
|
||||
VALUES (?, ?, ?)
|
||||
"#,
|
||||
registered_gateway.gateway_id_bs58,
|
||||
@@ -224,7 +230,7 @@ impl StorageManager {
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO custom_gateway_details(gateway_id_bs58, data)
|
||||
INSERT INTO custom_gateway_details(gateway_id_bs58, data)
|
||||
VALUES (?, ?)
|
||||
"#,
|
||||
custom.gateway_id_bs58,
|
||||
|
||||
@@ -32,7 +32,7 @@ use crate::init::{
|
||||
setup_gateway,
|
||||
types::{GatewaySetup, InitialisationResult},
|
||||
};
|
||||
use crate::{config, spawn_future};
|
||||
use crate::{config, spawn_future, ForgetMe};
|
||||
use futures::channel::mpsc;
|
||||
use log::*;
|
||||
use nym_bandwidth_controller::BandwidthController;
|
||||
@@ -188,6 +188,11 @@ pub struct BaseClientBuilder<'a, C, S: MixnetClientStorage> {
|
||||
user_agent: Option<UserAgent>,
|
||||
|
||||
setup_method: GatewaySetup,
|
||||
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
|
||||
forget_me: ForgetMe,
|
||||
}
|
||||
|
||||
impl<'a, C, S> BaseClientBuilder<'a, C, S>
|
||||
@@ -210,9 +215,18 @@ where
|
||||
shutdown: None,
|
||||
user_agent: None,
|
||||
setup_method: GatewaySetup::MustLoad { gateway_id: None },
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
forget_me: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_forget_me(mut self, forget_me: &ForgetMe) -> Self {
|
||||
self.forget_me = forget_me.clone();
|
||||
self
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_gateway_setup(mut self, setup: GatewaySetup) -> Self {
|
||||
self.setup_method = setup;
|
||||
@@ -261,6 +275,15 @@ where
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn with_connection_fd_callback(
|
||||
mut self,
|
||||
callback: Arc<dyn Fn(RawFd) + Send + Sync>,
|
||||
) -> Self {
|
||||
self.connection_fd_callback = Some(callback);
|
||||
self
|
||||
}
|
||||
|
||||
// note: do **NOT** make this method public as its only valid usage is from within `start_base`
|
||||
// because it relies on the crypto keys being already loaded
|
||||
fn mix_address(details: &InitialisationResult) -> Recipient {
|
||||
@@ -352,6 +375,7 @@ where
|
||||
controller.start_with_shutdown(shutdown)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn start_gateway_client(
|
||||
config: &Config,
|
||||
initialisation_result: InitialisationResult,
|
||||
@@ -359,6 +383,7 @@ where
|
||||
details_store: &S::GatewaysDetailsStore,
|
||||
packet_router: PacketRouter,
|
||||
stats_reporter: ClientStatsSender,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
shutdown: TaskClient,
|
||||
) -> Result<GatewayClient<C, S::CredentialStore>, ClientCoreError>
|
||||
where
|
||||
@@ -401,6 +426,8 @@ where
|
||||
packet_router,
|
||||
bandwidth_controller,
|
||||
stats_reporter,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
shutdown,
|
||||
)
|
||||
};
|
||||
@@ -462,6 +489,7 @@ where
|
||||
details_store: &S::GatewaysDetailsStore,
|
||||
packet_router: PacketRouter,
|
||||
stats_reporter: ClientStatsSender,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
mut shutdown: TaskClient,
|
||||
) -> Result<Box<dyn GatewayTransceiver + Send>, ClientCoreError>
|
||||
where
|
||||
@@ -493,6 +521,8 @@ where
|
||||
details_store,
|
||||
packet_router,
|
||||
stats_reporter,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
shutdown,
|
||||
)
|
||||
.await?;
|
||||
@@ -615,9 +645,11 @@ where
|
||||
fn start_mix_traffic_controller(
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
shutdown: TaskClient,
|
||||
forget_me: ForgetMe,
|
||||
) -> BatchMixMessageSender {
|
||||
info!("Starting mix traffic controller...");
|
||||
let (mix_traffic_controller, mix_tx) = MixTrafficController::new(gateway_transceiver);
|
||||
let (mix_traffic_controller, mix_tx) =
|
||||
MixTrafficController::new(gateway_transceiver, forget_me);
|
||||
mix_traffic_controller.start_with_shutdown(shutdown);
|
||||
mix_tx
|
||||
}
|
||||
@@ -772,6 +804,8 @@ where
|
||||
&details_store,
|
||||
gateway_packet_router,
|
||||
stats_reporter.clone(),
|
||||
#[cfg(unix)]
|
||||
self.connection_fd_callback,
|
||||
shutdown.fork("gateway_transceiver"),
|
||||
)
|
||||
.await?;
|
||||
@@ -797,9 +831,11 @@ where
|
||||
// that are to be sent to the mixnet. They are used by cover traffic stream and real
|
||||
// traffic stream.
|
||||
// The MixTrafficController then sends the actual traffic
|
||||
|
||||
let message_sender = Self::start_mix_traffic_controller(
|
||||
gateway_transceiver,
|
||||
shutdown.fork("mix_traffic_controller"),
|
||||
self.forget_me,
|
||||
);
|
||||
|
||||
// Channels that the websocket listener can use to signal downstream to the real traffic
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use crate::client::mix_traffic::transceiver::GatewayTransceiver;
|
||||
use crate::spawn_future;
|
||||
use crate::{spawn_future, ForgetMe};
|
||||
use log::*;
|
||||
use nym_gateway_requests::ClientRequest;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
|
||||
pub type BatchMixMessageSender = tokio::sync::mpsc::Sender<Vec<MixPacket>>;
|
||||
@@ -26,10 +27,14 @@ pub struct MixTrafficController {
|
||||
// TODO: this is temporary work-around.
|
||||
// in long run `gateway_client` will be moved away from `MixTrafficController` anyway.
|
||||
consecutive_gateway_failure_count: usize,
|
||||
forget_me: ForgetMe,
|
||||
}
|
||||
|
||||
impl MixTrafficController {
|
||||
pub fn new<T>(gateway_transceiver: T) -> (MixTrafficController, BatchMixMessageSender)
|
||||
pub fn new<T>(
|
||||
gateway_transceiver: T,
|
||||
forget_me: ForgetMe,
|
||||
) -> (MixTrafficController, BatchMixMessageSender)
|
||||
where
|
||||
T: GatewayTransceiver + Send + 'static,
|
||||
{
|
||||
@@ -40,6 +45,7 @@ impl MixTrafficController {
|
||||
gateway_transceiver: Box::new(gateway_transceiver),
|
||||
mix_rx: message_receiver,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
forget_me,
|
||||
},
|
||||
message_sender,
|
||||
)
|
||||
@@ -47,6 +53,7 @@ impl MixTrafficController {
|
||||
|
||||
pub fn new_dynamic(
|
||||
gateway_transceiver: Box<dyn GatewayTransceiver + Send>,
|
||||
forget_me: ForgetMe,
|
||||
) -> (MixTrafficController, BatchMixMessageSender) {
|
||||
let (message_sender, message_receiver) =
|
||||
tokio::sync::mpsc::channel(MIX_MESSAGE_RECEIVER_BUFFER_SIZE);
|
||||
@@ -55,6 +62,7 @@ impl MixTrafficController {
|
||||
gateway_transceiver,
|
||||
mix_rx: message_receiver,
|
||||
consecutive_gateway_failure_count: 0,
|
||||
forget_me,
|
||||
},
|
||||
message_sender,
|
||||
)
|
||||
@@ -111,7 +119,27 @@ impl MixTrafficController {
|
||||
}
|
||||
}
|
||||
shutdown.recv_timeout().await;
|
||||
|
||||
if self.forget_me.any() {
|
||||
log::info!("Sending forget me request to the gateway");
|
||||
match self
|
||||
.gateway_transceiver
|
||||
.send_client_request(ClientRequest::ForgetMe {
|
||||
client: self.forget_me.client(),
|
||||
stats: self.forget_me.stats(),
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
log::info!("Successfully sent forget me request to the gateway");
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to send forget me request to the gateway: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log::debug!("MixTrafficController: Exiting");
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ use async_trait::async_trait;
|
||||
use log::{debug, error};
|
||||
use nym_credential_storage::storage::Storage as CredentialStorage;
|
||||
use nym_crypto::asymmetric::identity;
|
||||
use nym_gateway_client::error::GatewayClientError;
|
||||
use nym_gateway_client::GatewayClient;
|
||||
pub use nym_gateway_client::{GatewayPacketRouter, PacketRouter};
|
||||
use nym_gateway_requests::ClientRequest;
|
||||
use nym_sphinx::forwarding::packet::MixPacket;
|
||||
use nym_validator_client::nyxd::contract_traits::DkgQueryClient;
|
||||
use std::fmt::Debug;
|
||||
@@ -26,9 +28,14 @@ fn erase_err<E: std::error::Error + Send + Sync + 'static>(err: E) -> ErasedGate
|
||||
}
|
||||
|
||||
/// This combines combines the functionalities of being able to send and receive mix packets.
|
||||
#[async_trait]
|
||||
pub trait GatewayTransceiver: GatewaySender + GatewayReceiver {
|
||||
fn gateway_identity(&self) -> identity::PublicKey;
|
||||
fn ws_fd(&self) -> Option<RawFd>;
|
||||
async fn send_client_request(
|
||||
&mut self,
|
||||
message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError>;
|
||||
}
|
||||
|
||||
/// This trait defines the functionality of sending `MixPacket` into the mixnet,
|
||||
@@ -65,6 +72,7 @@ pub trait GatewayReceiver {
|
||||
}
|
||||
|
||||
// to allow for dynamic dispatch
|
||||
#[async_trait]
|
||||
impl<G: GatewayTransceiver + ?Sized + Send> GatewayTransceiver for Box<G> {
|
||||
#[inline]
|
||||
fn gateway_identity(&self) -> identity::PublicKey {
|
||||
@@ -73,6 +81,13 @@ impl<G: GatewayTransceiver + ?Sized + Send> GatewayTransceiver for Box<G> {
|
||||
fn ws_fd(&self) -> Option<RawFd> {
|
||||
(**self).ws_fd()
|
||||
}
|
||||
|
||||
async fn send_client_request(
|
||||
&mut self,
|
||||
message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
(**self).send_client_request(message).await
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
@@ -91,7 +106,6 @@ impl<G: GatewaySender + ?Sized + Send> GatewaySender for Box<G> {
|
||||
(**self).batch_send_mix_packets(packets).await
|
||||
}
|
||||
}
|
||||
|
||||
impl<G: GatewayReceiver + ?Sized> GatewayReceiver for Box<G> {
|
||||
#[inline]
|
||||
fn set_packet_router(&mut self, packet_router: PacketRouter) -> Result<(), ErasedGatewayError> {
|
||||
@@ -111,6 +125,7 @@ impl<C, St> RemoteGateway<C, St> {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<C, St> GatewayTransceiver for RemoteGateway<C, St>
|
||||
where
|
||||
C: DkgQueryClient + Send + Sync,
|
||||
@@ -123,6 +138,20 @@ where
|
||||
fn ws_fd(&self) -> Option<RawFd> {
|
||||
self.gateway_client.ws_fd()
|
||||
}
|
||||
|
||||
async fn send_client_request(
|
||||
&mut self,
|
||||
message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
if let Some(shared_key) = self.gateway_client.shared_key() {
|
||||
self.gateway_client
|
||||
.send_websocket_message(message.encrypt(&*shared_key)?)
|
||||
.await?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(GatewayClientError::ConnectionInInvalidState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
|
||||
@@ -195,6 +224,7 @@ impl LocalGateway {
|
||||
mod nonwasm_sealed {
|
||||
use super::*;
|
||||
|
||||
#[async_trait]
|
||||
impl GatewayTransceiver for LocalGateway {
|
||||
fn gateway_identity(&self) -> identity::PublicKey {
|
||||
self.local_identity
|
||||
@@ -202,6 +232,13 @@ mod nonwasm_sealed {
|
||||
fn ws_fd(&self) -> Option<RawFd> {
|
||||
None
|
||||
}
|
||||
|
||||
async fn send_client_request(
|
||||
&mut self,
|
||||
_message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@@ -269,6 +306,7 @@ impl GatewaySender for MockGateway {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl GatewayTransceiver for MockGateway {
|
||||
fn gateway_identity(&self) -> identity::PublicKey {
|
||||
self.dummy_identity
|
||||
@@ -276,4 +314,11 @@ impl GatewayTransceiver for MockGateway {
|
||||
fn ws_fd(&self) -> Option<RawFd> {
|
||||
None
|
||||
}
|
||||
|
||||
async fn send_client_request(
|
||||
&mut self,
|
||||
_message: ClientRequest,
|
||||
) -> Result<(), GatewayClientError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,3 +34,48 @@ where
|
||||
{
|
||||
tokio::spawn(future);
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct ForgetMe {
|
||||
client: bool,
|
||||
stats: bool,
|
||||
}
|
||||
|
||||
impl ForgetMe {
|
||||
pub fn new_all() -> Self {
|
||||
Self {
|
||||
client: true,
|
||||
stats: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_client() -> Self {
|
||||
Self {
|
||||
client: true,
|
||||
stats: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_stats() -> Self {
|
||||
Self {
|
||||
client: false,
|
||||
stats: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(client: bool, stats: bool) -> Self {
|
||||
Self { client, stats }
|
||||
}
|
||||
|
||||
pub fn any(&self) -> bool {
|
||||
self.client || self.stats
|
||||
}
|
||||
|
||||
pub fn client(&self) -> bool {
|
||||
self.client
|
||||
}
|
||||
|
||||
pub fn stats(&self) -> bool {
|
||||
self.stats
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,10 @@ use crate::backend::fs_backend::{
|
||||
},
|
||||
};
|
||||
use log::{error, info};
|
||||
use sqlx::ConnectOptions;
|
||||
use sqlx::{
|
||||
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
|
||||
ConnectOptions,
|
||||
};
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -31,6 +34,9 @@ impl StorageManager {
|
||||
}
|
||||
|
||||
let opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(database_path)
|
||||
.create_if_missing(fresh)
|
||||
.disable_statement_logging();
|
||||
|
||||
@@ -101,6 +101,10 @@ pub struct GatewayClient<C, St = EphemeralCredentialStorage> {
|
||||
// currently unused (but populated)
|
||||
negotiated_protocol: Option<u8>,
|
||||
|
||||
// Callback on the fd as soon as the connection has been established
|
||||
#[cfg(unix)]
|
||||
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,
|
||||
}
|
||||
@@ -116,6 +120,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
packet_router: PacketRouter,
|
||||
bandwidth_controller: Option<BandwidthController<C, St>>,
|
||||
stats_reporter: ClientStatsSender,
|
||||
#[cfg(unix)] connection_fd_callback: Option<Arc<dyn Fn(RawFd) + Send + Sync>>,
|
||||
task_client: TaskClient,
|
||||
) -> Self {
|
||||
GatewayClient {
|
||||
@@ -131,6 +136,8 @@ impl<C, St> GatewayClient<C, St> {
|
||||
bandwidth_controller,
|
||||
stats_reporter,
|
||||
negotiated_protocol: None,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
@@ -205,6 +212,12 @@ impl<C, St> GatewayClient<C, St> {
|
||||
};
|
||||
|
||||
self.connection = SocketState::Available(Box::new(ws_stream));
|
||||
|
||||
#[cfg(unix)]
|
||||
if let (Some(callback), Some(fd)) = (self.connection_fd_callback.as_ref(), self.ws_fd()) {
|
||||
callback.as_ref()(fd);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -311,7 +324,7 @@ impl<C, St> GatewayClient<C, St> {
|
||||
|
||||
// If we want to send a message (with response), we need to have a full control over the socket,
|
||||
// as we need to be able to write the request and read the subsequent response
|
||||
async fn send_websocket_message(
|
||||
pub async fn send_websocket_message(
|
||||
&mut self,
|
||||
msg: impl Into<Message>,
|
||||
) -> Result<ServerResponse, GatewayClientError> {
|
||||
@@ -1034,6 +1047,8 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
bandwidth_controller: None,
|
||||
stats_reporter: ClientStatsSender::new(None),
|
||||
negotiated_protocol: None,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
@@ -1064,6 +1079,8 @@ impl GatewayClient<InitOnly, EphemeralCredentialStorage> {
|
||||
bandwidth_controller,
|
||||
stats_reporter,
|
||||
negotiated_protocol: self.negotiated_protocol,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: self.connection_fd_callback,
|
||||
task_client,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,10 @@ use nym_credentials::{
|
||||
IssuanceTicketBook, IssuedTicketBook,
|
||||
};
|
||||
use nym_ecash_time::{ecash_today, Date, EcashTime};
|
||||
use sqlx::ConnectOptions;
|
||||
use sqlx::{
|
||||
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
|
||||
ConnectOptions,
|
||||
};
|
||||
use std::path::Path;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
@@ -56,6 +59,9 @@ impl PersistentStorage {
|
||||
);
|
||||
|
||||
let opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(database_path)
|
||||
.create_if_missing(true)
|
||||
.disable_statement_logging();
|
||||
|
||||
@@ -20,6 +20,10 @@ pub enum ClientRequest {
|
||||
hkdf_salt: Vec<u8>,
|
||||
derived_key_digest: Vec<u8>,
|
||||
},
|
||||
ForgetMe {
|
||||
client: bool,
|
||||
stats: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl ClientRequest {
|
||||
|
||||
@@ -11,6 +11,7 @@ use tungstenite::Message;
|
||||
#[non_exhaustive]
|
||||
pub enum SensitiveServerResponse {
|
||||
KeyUpgradeAck {},
|
||||
ForgetMeAck {},
|
||||
}
|
||||
|
||||
impl SensitiveServerResponse {
|
||||
|
||||
@@ -6,7 +6,10 @@ use models::StoredFinishedSession;
|
||||
use nym_node_metrics::entry::{ActiveSession, FinishedSession, SessionType};
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use sessions::SessionManager;
|
||||
use sqlx::ConnectOptions;
|
||||
use sqlx::{
|
||||
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
|
||||
ConnectOptions,
|
||||
};
|
||||
use std::path::Path;
|
||||
use time::Date;
|
||||
use tracing::{debug, error};
|
||||
@@ -36,6 +39,9 @@ impl PersistentStatsStorage {
|
||||
// TODO: we can inject here more stuff based on our gateway global config
|
||||
// struct. Maybe different pool size or timeout intervals?
|
||||
let opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(database_path)
|
||||
.create_if_missing(true)
|
||||
.disable_statement_logging();
|
||||
@@ -116,6 +122,16 @@ impl PersistentStatsStorage {
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn delete_unique_user(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
Ok(self
|
||||
.session_manager
|
||||
.delete_unique_user(client_address.as_base58_string())
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn insert_active_session(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
|
||||
@@ -71,6 +71,16 @@ impl SessionManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn delete_unique_user(&self, client_address_b58: String) -> Result<()> {
|
||||
sqlx::query!(
|
||||
"DELETE FROM sessions_unique_users WHERE client_address = ?",
|
||||
client_address_b58
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn get_unique_users(&self, date: Date) -> Result<Vec<String>> {
|
||||
sqlx::query_scalar!(
|
||||
"SELECT client_address as count FROM sessions_unique_users WHERE day = ?",
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "DELETE FROM message_store WHERE client_address_bs58 = ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "3ea5542b21a41b14276a8fd6b870c61aa0ddd30fee2565803b88c6086bd2a734"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "DELETE FROM available_bandwidth WHERE client_id = ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "a3cc707995b8215fa77738cd1a55f9e8d251a3e764104d2a54153895dee1a118"
|
||||
}
|
||||
@@ -49,6 +49,16 @@ impl BandwidthManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn remove_client(&self, client_id: i64) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"DELETE FROM available_bandwidth WHERE client_id = ?",
|
||||
client_id
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Set the expiration date of the particular client to the provided date.
|
||||
pub(crate) async fn set_expiration(
|
||||
&self,
|
||||
|
||||
@@ -133,4 +133,17 @@ impl InboxManager {
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn remove_messages_for_client(
|
||||
&self,
|
||||
client_address_bs58: &str,
|
||||
) -> Result<(), sqlx::Error> {
|
||||
sqlx::query!(
|
||||
"DELETE FROM message_store WHERE client_address_bs58 = ?",
|
||||
client_address_bs58
|
||||
)
|
||||
.execute(&self.connection_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,10 @@ use nym_credentials_interface::ClientTicket;
|
||||
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
||||
use nym_sphinx::DestinationAddressBytes;
|
||||
use shared_keys::SharedKeysManager;
|
||||
use sqlx::ConnectOptions;
|
||||
use sqlx::{
|
||||
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
|
||||
ConnectOptions,
|
||||
};
|
||||
use std::path::Path;
|
||||
use tickets::TicketStorageManager;
|
||||
use time::OffsetDateTime;
|
||||
@@ -41,6 +44,33 @@ pub struct GatewayStorage {
|
||||
}
|
||||
|
||||
impl GatewayStorage {
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn client_manager(&self) -> &ClientManager {
|
||||
&self.client_manager
|
||||
}
|
||||
|
||||
pub(crate) fn shared_key_manager(&self) -> &SharedKeysManager {
|
||||
&self.shared_key_manager
|
||||
}
|
||||
|
||||
pub(crate) fn inbox_manager(&self) -> &InboxManager {
|
||||
&self.inbox_manager
|
||||
}
|
||||
|
||||
pub(crate) fn bandwidth_manager(&self) -> &BandwidthManager {
|
||||
&self.bandwidth_manager
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn ticket_manager(&self) -> &TicketStorageManager {
|
||||
&self.ticket_manager
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn wireguard_peer_manager(&self) -> &wireguard_peers::WgPeerManager {
|
||||
&self.wireguard_peer_manager
|
||||
}
|
||||
|
||||
/// Initialises `PersistentStorage` using the provided path.
|
||||
///
|
||||
/// # Arguments
|
||||
@@ -59,6 +89,9 @@ impl GatewayStorage {
|
||||
// TODO: we can inject here more stuff based on our gateway global config
|
||||
// struct. Maybe different pool size or timeout intervals?
|
||||
let opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(database_path)
|
||||
.create_if_missing(true)
|
||||
.disable_statement_logging();
|
||||
@@ -101,6 +134,21 @@ impl GatewayStorage {
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn handle_forget_me(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
) -> Result<(), GatewayStorageError> {
|
||||
let client_id = self.get_mixnet_client_id(client_address).await?;
|
||||
self.inbox_manager()
|
||||
.remove_messages_for_client(&client_address.as_base58_string())
|
||||
.await?;
|
||||
self.bandwidth_manager().remove_client(client_id).await?;
|
||||
self.shared_key_manager()
|
||||
.remove_shared_keys(&client_address.as_base58_string())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn insert_shared_keys(
|
||||
&self,
|
||||
client_address: DestinationAddressBytes,
|
||||
|
||||
@@ -324,6 +324,56 @@ impl Client {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_patch_request<B, K, V>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> RequestBuilder
|
||||
where
|
||||
B: Serialize + ?Sized,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let url = sanitize_url(&self.base_url, path, params);
|
||||
self.reqwest_client.patch(url).json(json_body)
|
||||
}
|
||||
|
||||
pub async fn send_patch_request<B, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<Response, HttpClientError<E>>
|
||||
where
|
||||
B: Serialize + ?Sized,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
E: Display,
|
||||
{
|
||||
let url = sanitize_url(&self.base_url, path, params);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
Ok(wasmtimer::tokio::timeout(
|
||||
self.request_timeout,
|
||||
self.reqwest_client.patch(url).json(json_body).send(),
|
||||
)
|
||||
.await
|
||||
.map_err(|_timeout| HttpClientError::RequestTimeout)??)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
Ok(self
|
||||
.reqwest_client
|
||||
.patch(url)
|
||||
.json(json_body)
|
||||
.send()
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub async fn get_json<T, K, V, E>(
|
||||
&self,
|
||||
@@ -372,6 +422,23 @@ impl Client {
|
||||
parse_response(res, false).await
|
||||
}
|
||||
|
||||
pub async fn patch_json<B, T, K, V, E>(
|
||||
&self,
|
||||
path: PathSegments<'_>,
|
||||
params: Params<'_, K, V>,
|
||||
json_body: &B,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
B: Serialize + ?Sized,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
K: AsRef<str>,
|
||||
V: AsRef<str>,
|
||||
E: Display + DeserializeOwned,
|
||||
{
|
||||
let res = self.send_patch_request(path, params, json_body).await?;
|
||||
parse_response(res, true).await
|
||||
}
|
||||
|
||||
#[instrument(level = "debug", skip_all)]
|
||||
pub async fn get_json_endpoint<T, S, E>(&self, endpoint: S) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
@@ -466,6 +533,42 @@ impl Client {
|
||||
|
||||
parse_response(res, false).await
|
||||
}
|
||||
|
||||
pub async fn patch_json_endpoint<B, T, S, E>(
|
||||
&self,
|
||||
endpoint: S,
|
||||
json_body: &B,
|
||||
) -> Result<T, HttpClientError<E>>
|
||||
where
|
||||
B: Serialize + ?Sized,
|
||||
for<'a> T: Deserialize<'a>,
|
||||
E: Display + DeserializeOwned,
|
||||
S: AsRef<str>,
|
||||
{
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let res = {
|
||||
wasmtimer::tokio::timeout(
|
||||
self.request_timeout,
|
||||
self.reqwest_client
|
||||
.patch(self.base_url.join(endpoint.as_ref())?)
|
||||
.json(json_body)
|
||||
.send(),
|
||||
)
|
||||
.await
|
||||
.map_err(|_timeout| HttpClientError::RequestTimeout)??
|
||||
};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let res = {
|
||||
self.reqwest_client
|
||||
.patch(self.base_url.join(endpoint.as_ref())?)
|
||||
.json(json_body)
|
||||
.send()
|
||||
.await?
|
||||
};
|
||||
|
||||
parse_response(res, true).await
|
||||
}
|
||||
}
|
||||
|
||||
// define those methods on the trait for nicer extensions (and not having to type the thing twice)
|
||||
|
||||
@@ -13,7 +13,11 @@ use crate::{
|
||||
models::{CommitSignature, Validator},
|
||||
},
|
||||
};
|
||||
use sqlx::{types::time::OffsetDateTime, ConnectOptions, Sqlite, Transaction};
|
||||
use sqlx::{
|
||||
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
|
||||
types::time::OffsetDateTime,
|
||||
ConnectOptions, Sqlite, Transaction,
|
||||
};
|
||||
use std::{fmt::Debug, path::Path};
|
||||
use tendermint::{
|
||||
block::{Commit, CommitSig},
|
||||
@@ -51,6 +55,9 @@ impl ScraperStorage {
|
||||
#[instrument]
|
||||
pub async fn init<P: AsRef<Path> + Debug>(database_path: P) -> Result<Self, ScraperError> {
|
||||
let opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(database_path)
|
||||
.create_if_missing(true)
|
||||
.disable_statement_logging();
|
||||
|
||||
@@ -58,6 +58,10 @@ pub enum GatewaySessionEvent {
|
||||
/// Address of the remote client opening the connection
|
||||
client: DestinationAddressBytes,
|
||||
},
|
||||
SessionDelete {
|
||||
/// Address of the remote client opening the connection
|
||||
client: DestinationAddressBytes,
|
||||
},
|
||||
}
|
||||
|
||||
impl GatewaySessionEvent {
|
||||
@@ -87,4 +91,8 @@ impl GatewaySessionEvent {
|
||||
client,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_session_delete(client: DestinationAddressBytes) -> GatewaySessionEvent {
|
||||
GatewaySessionEvent::SessionDelete { client }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
---
|
||||
description: Interactive APIs generated from the OpenAPI specs of various API endpoints offered by bits of Nym infrastructure run both by Nym and community operators for both Mainnet and the Sandbox testnet.
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
This site contains interactive APIs generated from the OpenAPI specs of various API endpoints offered by bits of Nym infrastructure run both by Nym and community operators for both Mainnet and the Sandbox testnet.
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
---
|
||||
description: Nym's developer documentation covering core concepts of integrating with the Mixnet, interacting with the Nyx blockchain, an overview of the avaliable tools, and our SDK docs.
|
||||
---
|
||||
|
||||
# Introduction
|
||||
Nym's developer documentation covering core concepts of integrating with the Mixnet, interacting with the Nyx blockchain, an overview of the avaliable tools, and our SDK docs.
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
---
|
||||
description: Nym's network documentation covering network architecture, node types, tokenomics, and cryptography.
|
||||
---
|
||||
|
||||
# Introduction
|
||||
Nym's network documentation covering network architecture, node types, tokenomics, and crypto systems.
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
---
|
||||
description: Nym's Operators guide containing information and setup guides for the various components of Nym network and Nyx blockchain validators.
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
This is **Nym's Operators guide**, containing information and setup guides for the various components of Nym network and Nyx blockchain validators.
|
||||
|
||||
@@ -222,7 +222,7 @@ Good performance is much more essential than [total stake](../tokenomics.mdx#sta
|
||||
For a comparison we made an example with 5 nodes, where first number is node performance and second stake saturation (assuming all of them `config_score` = 1 for simplification):
|
||||
|
||||
<br />
|
||||
<AccordionTemplate name="✏️ Example: Performance ^ 20 \* total_stake calculation">
|
||||
<AccordionTemplate name="✏️ Example: Performance ^ 20 * total_stake calculation">
|
||||
> node_1 = 1.00 ^ 20 \* 1.0 = 1 <br />
|
||||
> node_2 = 1.00 ^ 20 \* 0.5 = 0.5 <br />
|
||||
> node_3 = 0.99 ^ 20 \* 1.0 = 0.818 <br />
|
||||
|
||||
@@ -11,10 +11,23 @@ const config: DocsThemeConfig = {
|
||||
const url = process.env.NEXT_PUBLIC_SITE_URL
|
||||
const image = url + '/nym_logo.jpg'
|
||||
|
||||
// Define descriptions for different "books"
|
||||
const bookDescriptions: Record<string, string> = {
|
||||
'/developers': "Nym's developer documentation covering core concepts of integrating with the Mixnet, interacting with the Nyx blockchain, an overview of the avaliable tools, and our SDK docs.",
|
||||
'/network': "Nym's network documentation covering network architecture, node types, tokenomics, and cryptography.",
|
||||
'/operators': "Nym's Operators guide containing information and setup guides for the various components of Nym network and Nyx blockchain validators.",
|
||||
'/apis': "Interactive APIs generated from the OpenAPI specs of various API endpoints offered by bits of Nym infrastructure run both by Nym and community operators for both Mainnet and the Sandbox testnet."
|
||||
}
|
||||
|
||||
const defaultDescription = 'Nym is a privacy platform. It provides strong network-level privacy against sophisticated end-to-end attackers, and anonymous access control using blinded, re-randomizable, decentralized credentials.'
|
||||
|
||||
const topLevel = '/' + route.split('/')[1]
|
||||
const description =
|
||||
config.frontMatter.description ||
|
||||
'Nym is a privacy platform. It provides strong network-level privacy against sophisticated end-to-end attackers, and anonymous access control using blinded, re-randomizable, decentralized credentials.'
|
||||
const title = config.title + (route === '/' ? '' : ' - Nym docs')
|
||||
bookDescriptions[topLevel] ||
|
||||
defaultDescription
|
||||
|
||||
const title = config.title + (route === '/' ? '' : ' - Nym docs')
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -21,3 +21,9 @@ pub(crate) struct CommonHandlerState {
|
||||
pub(crate) outbound_mix_sender: MixForwardingSender,
|
||||
pub(crate) active_clients_store: ActiveClientsStore,
|
||||
}
|
||||
|
||||
impl CommonHandlerState {
|
||||
pub(crate) fn storage(&self) -> &GatewayStorage {
|
||||
&self.storage
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +157,10 @@ impl<R, S> Drop for AuthenticatedHandler<R, S> {
|
||||
}
|
||||
|
||||
impl<R, S> AuthenticatedHandler<R, S> {
|
||||
pub(crate) fn inner(&self) -> &FreshHandler<R, S> {
|
||||
&self.inner
|
||||
}
|
||||
|
||||
/// Upgrades `FreshHandler` into the Authenticated variant implying the client is now authenticated
|
||||
/// and thus allowed to perform more actions with the gateway, such as redeeming bandwidth or
|
||||
/// sending sphinx packets.
|
||||
@@ -327,6 +331,24 @@ impl<R, S> AuthenticatedHandler<R, S> {
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_forget_me(
|
||||
&mut self,
|
||||
client: bool,
|
||||
stats: bool,
|
||||
) -> Result<ServerResponse, RequestHandlingError> {
|
||||
if client {
|
||||
self.inner()
|
||||
.shared_state()
|
||||
.storage()
|
||||
.handle_forget_me(self.client.address)
|
||||
.await?;
|
||||
}
|
||||
if stats {
|
||||
self.send_metrics(GatewaySessionEvent::new_session_delete(self.client.address));
|
||||
}
|
||||
Ok(SensitiveServerResponse::ForgetMeAck {}.encrypt(&self.client.shared_keys)?)
|
||||
}
|
||||
|
||||
async fn handle_key_upgrade(
|
||||
&mut self,
|
||||
hkdf_salt: Vec<u8>,
|
||||
@@ -370,6 +392,7 @@ impl<R, S> AuthenticatedHandler<R, S> {
|
||||
hkdf_salt,
|
||||
derived_key_digest,
|
||||
} => self.handle_key_upgrade(hkdf_salt, derived_key_digest).await,
|
||||
ClientRequest::ForgetMe { client, stats } => self.handle_forget_me(client, stats).await,
|
||||
_ => Err(RequestHandlingError::UnknownEncryptedTextRequest),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,6 +114,10 @@ pub(crate) struct FreshHandler<R, S> {
|
||||
}
|
||||
|
||||
impl<R, S> FreshHandler<R, S> {
|
||||
pub(crate) fn shared_state(&self) -> &CommonHandlerState {
|
||||
&self.shared_state
|
||||
}
|
||||
|
||||
// for time being we assume handle is always constructed from raw socket.
|
||||
// if we decide we want to change it, that's not too difficult
|
||||
// also at this point I'm not entirely sure how to deal with this warning without
|
||||
|
||||
@@ -183,6 +183,8 @@ impl PacketSender {
|
||||
gateway_packet_router,
|
||||
Some(fresh_gateway_client_data.bandwidth_controller.clone()),
|
||||
nym_statistics_common::clients::ClientStatsSender::new(None),
|
||||
#[cfg(unix)]
|
||||
None,
|
||||
task_client,
|
||||
);
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ use crate::support::storage::models::{
|
||||
use dashmap::DashMap;
|
||||
use nym_mixnet_contract_common::NodeId;
|
||||
use nym_types::monitoring::NodeResult;
|
||||
use sqlx::sqlite::{SqliteAutoVacuum, SqliteSynchronous};
|
||||
use sqlx::ConnectOptions;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
@@ -67,6 +68,9 @@ impl NymApiStorage {
|
||||
// TODO: we can inject here more stuff based on our nym-api global config
|
||||
// struct. Maybe different pool size or timeout intervals?
|
||||
let connect_opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(database_path)
|
||||
.create_if_missing(true)
|
||||
.log_statements(LevelFilter::Trace)
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
// Copyright 2024 Nym Technologies SA <contact@nymtech.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use nym_bin_common::bin_info;
|
||||
use time::OffsetDateTime;
|
||||
use tracing::{debug, info, warn};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
use crate::{
|
||||
cli::Cli,
|
||||
deposit_maker::DepositMaker,
|
||||
error::VpnApiError,
|
||||
http::{
|
||||
state::{ApiState, ChainClient},
|
||||
HttpServer,
|
||||
},
|
||||
storage::VpnApiStorage,
|
||||
tasks::StoragePruner,
|
||||
};
|
||||
|
||||
pub struct LockTimer {
|
||||
created: OffsetDateTime,
|
||||
@@ -40,3 +54,88 @@ impl Default for LockTimer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn wait_for_signal() {
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
|
||||
// if we fail to setup the signals, we should just blow up
|
||||
#[allow(clippy::expect_used)]
|
||||
let mut sigterm = signal(SignalKind::terminate()).expect("Failed to setup SIGTERM channel");
|
||||
#[allow(clippy::expect_used)]
|
||||
let mut sigquit = signal(SignalKind::quit()).expect("Failed to setup SIGQUIT channel");
|
||||
|
||||
tokio::select! {
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
info!("Received SIGINT");
|
||||
},
|
||||
_ = sigterm.recv() => {
|
||||
info!("Received SIGTERM");
|
||||
}
|
||||
_ = sigquit.recv() => {
|
||||
info!("Received SIGQUIT");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_sha_short() -> &'static str {
|
||||
let bin_info = bin_info!();
|
||||
if bin_info.commit_sha.len() < 7 {
|
||||
panic!("unavailable build commit sha")
|
||||
}
|
||||
|
||||
if bin_info.commit_sha == "VERGEN_IDEMPOTENT_OUTPUT" {
|
||||
error!("the binary hasn't been built correctly. it doesn't have a commit sha information");
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
&bin_info.commit_sha[..7]
|
||||
}
|
||||
|
||||
pub(crate) async fn run_api(cli: Cli) -> Result<(), VpnApiError> {
|
||||
// create the tasks
|
||||
let bind_address = cli.bind_address();
|
||||
|
||||
let storage = VpnApiStorage::init(cli.persistent_storage_path()).await?;
|
||||
let mnemonic = cli.mnemonic;
|
||||
let auth_token = cli.http_auth_token;
|
||||
let webhook_cfg = cli.webhook;
|
||||
let chain_client = ChainClient::new(mnemonic)?;
|
||||
let cancellation_token = CancellationToken::new();
|
||||
|
||||
let deposit_maker = DepositMaker::new(
|
||||
build_sha_short(),
|
||||
chain_client.clone(),
|
||||
cli.max_concurrent_deposits,
|
||||
cancellation_token.clone(),
|
||||
);
|
||||
|
||||
let deposit_request_sender = deposit_maker.deposit_request_sender();
|
||||
let api_state = ApiState::new(
|
||||
storage.clone(),
|
||||
webhook_cfg,
|
||||
chain_client,
|
||||
deposit_request_sender,
|
||||
cancellation_token.clone(),
|
||||
)
|
||||
.await?;
|
||||
let http_server = HttpServer::new(
|
||||
bind_address,
|
||||
api_state.clone(),
|
||||
auth_token,
|
||||
cancellation_token.clone(),
|
||||
);
|
||||
let storage_pruner = StoragePruner::new(cancellation_token, storage);
|
||||
|
||||
// spawn all the tasks
|
||||
api_state.try_spawn(http_server.run_forever());
|
||||
api_state.try_spawn(storage_pruner.run_forever());
|
||||
api_state.try_spawn(deposit_maker.run_forever());
|
||||
|
||||
// wait for cancel signal (SIGINT, SIGTERM or SIGQUIT)
|
||||
wait_for_signal().await;
|
||||
|
||||
// cancel all the tasks and wait for all task to terminate
|
||||
api_state.cancel_and_wait().await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -6,117 +6,30 @@
|
||||
#![warn(clippy::todo)]
|
||||
#![warn(clippy::dbg_macro)]
|
||||
|
||||
use crate::cli::Cli;
|
||||
use crate::deposit_maker::DepositMaker;
|
||||
use crate::error::VpnApiError;
|
||||
use crate::http::state::{ApiState, ChainClient};
|
||||
use crate::http::HttpServer;
|
||||
use crate::storage::VpnApiStorage;
|
||||
use crate::tasks::StoragePruner;
|
||||
use clap::Parser;
|
||||
use nym_bin_common::logging::setup_tracing_logger;
|
||||
use nym_bin_common::{bin_info, bin_info_owned};
|
||||
use nym_network_defaults::setup_env;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{error, info, trace};
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(unix)] {
|
||||
use crate::cli::Cli;
|
||||
use clap::Parser;
|
||||
use nym_bin_common::bin_info_owned;
|
||||
use nym_bin_common::logging::setup_tracing_logger;
|
||||
use nym_network_defaults::setup_env;
|
||||
use tracing::{info, trace};
|
||||
|
||||
pub mod cli;
|
||||
pub mod config;
|
||||
pub mod credentials;
|
||||
mod deposit_maker;
|
||||
pub mod error;
|
||||
pub mod helpers;
|
||||
pub mod http;
|
||||
pub mod nym_api_helpers;
|
||||
pub mod storage;
|
||||
pub mod tasks;
|
||||
mod webhook;
|
||||
|
||||
pub async fn wait_for_signal() {
|
||||
use tokio::signal::unix::{signal, SignalKind};
|
||||
|
||||
// if we fail to setup the signals, we should just blow up
|
||||
#[allow(clippy::expect_used)]
|
||||
let mut sigterm = signal(SignalKind::terminate()).expect("Failed to setup SIGTERM channel");
|
||||
#[allow(clippy::expect_used)]
|
||||
let mut sigquit = signal(SignalKind::quit()).expect("Failed to setup SIGQUIT channel");
|
||||
|
||||
tokio::select! {
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
info!("Received SIGINT");
|
||||
},
|
||||
_ = sigterm.recv() => {
|
||||
info!("Received SIGTERM");
|
||||
}
|
||||
_ = sigquit.recv() => {
|
||||
info!("Received SIGQUIT");
|
||||
}
|
||||
pub mod cli;
|
||||
pub mod config;
|
||||
pub mod credentials;
|
||||
mod deposit_maker;
|
||||
pub mod error;
|
||||
pub mod helpers;
|
||||
pub mod http;
|
||||
pub mod nym_api_helpers;
|
||||
pub mod storage;
|
||||
pub mod tasks;
|
||||
mod webhook;
|
||||
}
|
||||
}
|
||||
|
||||
fn build_sha_short() -> &'static str {
|
||||
let bin_info = bin_info!();
|
||||
if bin_info.commit_sha.len() < 7 {
|
||||
panic!("unavailable build commit sha")
|
||||
}
|
||||
|
||||
if bin_info.commit_sha == "VERGEN_IDEMPOTENT_OUTPUT" {
|
||||
error!("the binary hasn't been built correctly. it doesn't have a commit sha information");
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
&bin_info.commit_sha[..7]
|
||||
}
|
||||
|
||||
async fn run_api(cli: Cli) -> Result<(), VpnApiError> {
|
||||
// create the tasks
|
||||
let bind_address = cli.bind_address();
|
||||
|
||||
let storage = VpnApiStorage::init(cli.persistent_storage_path()).await?;
|
||||
let mnemonic = cli.mnemonic;
|
||||
let auth_token = cli.http_auth_token;
|
||||
let webhook_cfg = cli.webhook;
|
||||
let chain_client = ChainClient::new(mnemonic)?;
|
||||
let cancellation_token = CancellationToken::new();
|
||||
|
||||
let deposit_maker = DepositMaker::new(
|
||||
build_sha_short(),
|
||||
chain_client.clone(),
|
||||
cli.max_concurrent_deposits,
|
||||
cancellation_token.clone(),
|
||||
);
|
||||
|
||||
let deposit_request_sender = deposit_maker.deposit_request_sender();
|
||||
let api_state = ApiState::new(
|
||||
storage.clone(),
|
||||
webhook_cfg,
|
||||
chain_client,
|
||||
deposit_request_sender,
|
||||
cancellation_token.clone(),
|
||||
)
|
||||
.await?;
|
||||
let http_server = HttpServer::new(
|
||||
bind_address,
|
||||
api_state.clone(),
|
||||
auth_token,
|
||||
cancellation_token.clone(),
|
||||
);
|
||||
let storage_pruner = StoragePruner::new(cancellation_token, storage);
|
||||
|
||||
// spawn all the tasks
|
||||
api_state.try_spawn(http_server.run_forever());
|
||||
api_state.try_spawn(storage_pruner.run_forever());
|
||||
api_state.try_spawn(deposit_maker.run_forever());
|
||||
|
||||
// wait for cancel signal (SIGINT, SIGTERM or SIGQUIT)
|
||||
wait_for_signal().await;
|
||||
|
||||
// cancel all the tasks and wait for all task to terminate
|
||||
api_state.cancel_and_wait().await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
// std::env::set_var(
|
||||
@@ -134,6 +47,13 @@ async fn main() -> anyhow::Result<()> {
|
||||
let bin_info = bin_info_owned!();
|
||||
info!("using the following version: {bin_info}");
|
||||
|
||||
run_api(cli).await?;
|
||||
helpers::run_api(cli).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
#[tokio::main]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
eprintln!("This tool is only supported on Unix systems");
|
||||
std::process::exit(1)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ use nym_validator_client::ecash::BlindedSignatureResponse;
|
||||
use nym_validator_client::nym_api::EpochId;
|
||||
use nym_validator_client::nyxd::contract_traits::ecash_query_client::DepositId;
|
||||
use nym_validator_client::nyxd::Coin;
|
||||
use sqlx::sqlite::{SqliteAutoVacuum, SqliteSynchronous};
|
||||
use sqlx::ConnectOptions;
|
||||
use std::fmt::Debug;
|
||||
use std::path::Path;
|
||||
@@ -40,6 +41,9 @@ impl VpnApiStorage {
|
||||
debug!("Attempting to connect to database");
|
||||
|
||||
let opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(database_path)
|
||||
.create_if_missing(true)
|
||||
.log_statements(LevelFilter::Trace)
|
||||
|
||||
@@ -27,12 +27,14 @@ tokio = { workspace = true, features = ["macros", "time"] }
|
||||
tokio-util = { workspace = true }
|
||||
utoipa = { workspace = true, features = ["axum_extras"] }
|
||||
utoipa-swagger-ui = { workspace = true, features = ["axum"] }
|
||||
tokio-postgres = "0.7"
|
||||
|
||||
# internal
|
||||
nym-bin-common = { path = "../common/bin-common" }
|
||||
nym-client-core = { path = "../common/client-core" }
|
||||
nym-crypto = { path = "../common/crypto" }
|
||||
nym-network-defaults = { path = "../common/network-defaults" }
|
||||
nym-gateway-requests = { path = "../common/gateway-requests" }
|
||||
nym-sdk = { path = "../sdk/rust/nym-sdk" }
|
||||
nym-sphinx = { path = "../common/nymsphinx" }
|
||||
nym-topology = { path = "../common/topology" }
|
||||
|
||||
@@ -9,13 +9,14 @@ network=${NYM_NETWORK:-mainnet}
|
||||
timeout=${LOCUST_TIMEOUT:-600}
|
||||
users=${LOCUST_USERS:-10}
|
||||
processes=${LOCUST_PROCESSES:-4}
|
||||
_database_url=${DATABASE_URL}
|
||||
|
||||
RUST_LOG=info nym-network-monitor --env envs/"${network}".env --private-key "${_private_key}" &
|
||||
RUST_LOG=info nym-network-monitor --env envs/"${network}".env --private-key "${_private_key}" --database-url "${_database_url}" &
|
||||
nnm_pid=$!
|
||||
|
||||
sleep 10
|
||||
|
||||
python -m locust -H http://${NYM_NETWORK_MONITOR_HOST}:${NYM_NETWORK_MONITOR_PORT} --processes "${processes}" --autostart --autoquit 60 -u "${users}" -t "${timeout}"s &
|
||||
python -m locust -H http://"${NYM_NETWORK_MONITOR_HOST}":"${NYM_NETWORK_MONITOR_PORT}" --processes "${processes}" --autostart --autoquit 60 -u "${users}" -t "${timeout}"s &
|
||||
locust_pid=$!
|
||||
|
||||
wait $locust_pid
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use anyhow::Result;
|
||||
use futures::{stream::FuturesUnordered, StreamExt};
|
||||
use futures::{pin_mut, stream::FuturesUnordered, StreamExt};
|
||||
use log::{debug, info};
|
||||
use nym_sphinx::chunking::{monitoring, SentFragment};
|
||||
use nym_topology::{gateway, mix, NymTopology};
|
||||
@@ -10,6 +13,7 @@ use nym_validator_client::nym_api::routes::{API_VERSION, STATUS, SUBMIT_GATEWAY,
|
||||
use rand::SeedableRng;
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio_postgres::{binary_copy::BinaryCopyInWriter, types::Type, Client};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::{NYM_API_URL, PRIVATE_KEY, TOPOLOGY};
|
||||
@@ -23,20 +27,20 @@ struct HydratedRoute {
|
||||
struct GatewayStats(u32, u32);
|
||||
|
||||
impl GatewayStats {
|
||||
fn new(sent: u32, recv: u32) -> Self {
|
||||
GatewayStats(sent, recv)
|
||||
fn new(success: u32, failure: u32) -> Self {
|
||||
GatewayStats(success, failure)
|
||||
}
|
||||
|
||||
fn success(&self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn failed(&self) -> u32 {
|
||||
fn failure(&self) -> u32 {
|
||||
self.1
|
||||
}
|
||||
|
||||
fn reliability(&self) -> f64 {
|
||||
self.success() as f64 / (self.success() + self.failed()) as f64
|
||||
self.success() as f64 / (self.success() + self.failure()) as f64
|
||||
}
|
||||
|
||||
fn incr_success(&mut self) {
|
||||
@@ -321,48 +325,125 @@ pub async fn monitor_mixnode_results() -> anyhow::Result<Vec<NodeResult>> {
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn submit_metrics() -> anyhow::Result<()> {
|
||||
let node_stats = monitor_mixnode_results().await?;
|
||||
let gateway_stats = monitor_gateway_results().await?;
|
||||
async fn submit_node_stats_to_db(client: Arc<Client>) -> anyhow::Result<()> {
|
||||
let client = Arc::clone(&client);
|
||||
let node_stats = all_node_stats().await?;
|
||||
|
||||
info!("Submitting metrics to {}", *NYM_API_URL);
|
||||
let client = reqwest::Client::new();
|
||||
let sink = client
|
||||
.copy_in("COPY node_stats (node_id, identity, reliability, complete_routes, incomplete_routes) FROM STDIN BINARY")
|
||||
.await?;
|
||||
|
||||
let node_submit_url = format!("{}/{API_VERSION}/{STATUS}/{SUBMIT_NODE}", &*NYM_API_URL);
|
||||
let gateway_submit_url = format!("{}/{API_VERSION}/{STATUS}/{SUBMIT_GATEWAY}", &*NYM_API_URL);
|
||||
let writer = BinaryCopyInWriter::new(
|
||||
sink,
|
||||
&[Type::INT4, Type::TEXT, Type::FLOAT8, Type::INT8, Type::INT8],
|
||||
);
|
||||
pin_mut!(writer);
|
||||
|
||||
info!("Submitting {} mixnode measurements", node_stats.len());
|
||||
for stat in node_stats {
|
||||
writer
|
||||
.as_mut()
|
||||
.write(&[
|
||||
&(stat.mix_id as i32),
|
||||
&stat.identity,
|
||||
&stat.reliability,
|
||||
&(stat.complete_routes as i64),
|
||||
&(stat.incomplete_routes as i64),
|
||||
])
|
||||
.await?;
|
||||
}
|
||||
|
||||
node_stats
|
||||
.chunks(10)
|
||||
.map(|chunk| {
|
||||
let monitor_message =
|
||||
MonitorMessage::new(chunk.to_vec(), PRIVATE_KEY.get().expect("We've set this!"));
|
||||
client.post(&node_submit_url).json(&monitor_message).send()
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.collect::<Vec<Result<_, _>>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
writer.finish().await?;
|
||||
|
||||
info!("Submitting {} gateway measurements", gateway_stats.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
gateway_stats
|
||||
.chunks(10)
|
||||
.map(|chunk| {
|
||||
let monitor_message =
|
||||
MonitorMessage::new(chunk.to_vec(), PRIVATE_KEY.get().expect("We've set this!"));
|
||||
client
|
||||
.post(&gateway_submit_url)
|
||||
.json(&monitor_message)
|
||||
.send()
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.collect::<Vec<Result<_, _>>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
async fn submit_gateway_stats_to_db(client: Arc<Client>) -> anyhow::Result<()> {
|
||||
let client = Arc::clone(&client);
|
||||
let network_account = NetworkAccount::finalize()?;
|
||||
let gateway_stats = network_account.gateway_stats;
|
||||
|
||||
let sink = client
|
||||
.copy_in("COPY gateway_stats (identity, reliability, success, failure) FROM STDIN BINARY")
|
||||
.await?;
|
||||
|
||||
let writer = BinaryCopyInWriter::new(sink, &[Type::TEXT, Type::FLOAT8, Type::INT8, Type::INT8]);
|
||||
pin_mut!(writer);
|
||||
|
||||
for (key, stats) in gateway_stats {
|
||||
writer
|
||||
.as_mut()
|
||||
.write(&[
|
||||
&key,
|
||||
&stats.reliability(),
|
||||
&(stats.success() as i64),
|
||||
&(stats.failure() as i64),
|
||||
])
|
||||
.await?;
|
||||
}
|
||||
|
||||
writer.finish().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn submit_metrics_to_db(client: Arc<Client>) -> anyhow::Result<()> {
|
||||
let client = Arc::clone(&client);
|
||||
let client2 = Arc::clone(&client);
|
||||
submit_node_stats_to_db(client).await?;
|
||||
submit_gateway_stats_to_db(client2).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn submit_metrics(client: Option<Arc<Client>>) -> anyhow::Result<()> {
|
||||
if let Some(client) = client {
|
||||
submit_metrics_to_db(client).await?;
|
||||
}
|
||||
|
||||
if let Some(private_key) = PRIVATE_KEY.get() {
|
||||
let node_stats = monitor_mixnode_results().await?;
|
||||
let gateway_stats = monitor_gateway_results().await?;
|
||||
|
||||
info!("Submitting metrics to {}", *NYM_API_URL);
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let node_submit_url = format!("{}/{API_VERSION}/{STATUS}/{SUBMIT_NODE}", &*NYM_API_URL);
|
||||
let gateway_submit_url =
|
||||
format!("{}/{API_VERSION}/{STATUS}/{SUBMIT_GATEWAY}", &*NYM_API_URL);
|
||||
|
||||
info!("Submitting {} mixnode measurements", node_stats.len());
|
||||
|
||||
node_stats
|
||||
.chunks(10)
|
||||
.map(|chunk| {
|
||||
let monitor_message = MonitorMessage::new(chunk.to_vec(), private_key);
|
||||
client.post(&node_submit_url).json(&monitor_message).send()
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.collect::<Vec<Result<_, _>>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
info!("Submitting {} gateway measurements", gateway_stats.len());
|
||||
|
||||
gateway_stats
|
||||
.chunks(10)
|
||||
.map(|chunk| {
|
||||
let monitor_message = MonitorMessage::new(
|
||||
chunk.to_vec(),
|
||||
PRIVATE_KEY.get().expect("We've set this!"),
|
||||
);
|
||||
client
|
||||
.post(&gateway_submit_url)
|
||||
.json(&monitor_message)
|
||||
.send()
|
||||
})
|
||||
.collect::<FuturesUnordered<_>>()
|
||||
.collect::<Vec<Result<_, _>>>()
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
}
|
||||
|
||||
NetworkAccount::empty_buffers();
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ use crate::http::HttpServer;
|
||||
use accounting::submit_metrics;
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use log::{info, warn};
|
||||
use log::{error, info, warn};
|
||||
use nym_client_core::ForgetMe;
|
||||
use nym_crypto::asymmetric::ed25519::PrivateKey;
|
||||
use nym_network_defaults::setup_env;
|
||||
use nym_network_defaults::var_names::NYM_API;
|
||||
@@ -21,6 +22,7 @@ use std::{
|
||||
};
|
||||
use tokio::sync::OnceCell;
|
||||
use tokio::{signal::ctrl_c, sync::RwLock};
|
||||
use tokio_postgres::NoTls;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
static NYM_API_URL: LazyLock<String> = LazyLock::new(|| {
|
||||
@@ -56,7 +58,11 @@ async fn make_clients(
|
||||
loop {
|
||||
if Arc::strong_count(&dropped_client) == 1 {
|
||||
if let Some(client) = Arc::into_inner(dropped_client) {
|
||||
client.into_inner().disconnect().await;
|
||||
// let forget_me = ClientRequest::ForgetMe {
|
||||
// also_from_stats: true,
|
||||
// };
|
||||
let client_handle = client.into_inner();
|
||||
client_handle.disconnect().await;
|
||||
} else {
|
||||
warn!("Failed to drop client, client had more then one strong ref")
|
||||
}
|
||||
@@ -89,6 +95,7 @@ async fn make_client(topology: NymTopology) -> Result<MixnetClient> {
|
||||
.network_details(net)
|
||||
.custom_topology_provider(topology_provider)
|
||||
.debug_config(mixnet_debug_config(0))
|
||||
.with_forget_me(ForgetMe::new_all())
|
||||
// .enable_credentials_mode()
|
||||
.build()?;
|
||||
|
||||
@@ -130,7 +137,10 @@ struct Args {
|
||||
generate_key_pair: bool,
|
||||
|
||||
#[arg(long)]
|
||||
private_key: String,
|
||||
private_key: Option<String>,
|
||||
|
||||
#[arg(long, env = "DATABASE_URL")]
|
||||
database_url: Option<String>,
|
||||
}
|
||||
|
||||
fn generate_key_pair() -> Result<()> {
|
||||
@@ -168,8 +178,10 @@ async fn main() -> Result<()> {
|
||||
std::process::exit(0);
|
||||
}
|
||||
|
||||
let pk = PrivateKey::from_base58_string(&args.private_key)?;
|
||||
PRIVATE_KEY.set(pk).ok();
|
||||
if let Some(private_key) = args.private_key {
|
||||
let pk = PrivateKey::from_base58_string(&private_key)?;
|
||||
PRIVATE_KEY.set(pk).ok();
|
||||
}
|
||||
|
||||
TOPOLOGY
|
||||
.set(if let Some(topology_file) = args.topology {
|
||||
@@ -197,16 +209,31 @@ async fn main() -> Result<()> {
|
||||
|
||||
info!("Waiting for message (ctrl-c to exit)");
|
||||
|
||||
let client = if let Some(database_url) = args.database_url {
|
||||
let (client, connection) = tokio_postgres::connect(&database_url, NoTls).await?;
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = connection.await {
|
||||
error!("Postgres connection error: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
Some(Arc::new(client))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
loop {
|
||||
let client = client.as_ref().map(Arc::clone);
|
||||
match tokio::time::timeout(Duration::from_secs(600), ctrl_c()).await {
|
||||
Ok(_) => {
|
||||
info!("Received kill signal, shutting down, submitting final batch of metrics");
|
||||
submit_metrics().await?;
|
||||
submit_metrics(client).await?;
|
||||
break;
|
||||
}
|
||||
Err(_) => {
|
||||
info!("Submitting metrics, cleaning metric buffers");
|
||||
submit_metrics().await?;
|
||||
submit_metrics(client).await?;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
set -eu
|
||||
export ENVIRONMENT=${ENVIRONMENT:-"sandbox"}
|
||||
|
||||
probe_git_ref="nym-vpn-core-v1.0.0-rc.14"
|
||||
probe_git_ref="nym-vpn-core-v1.1.0"
|
||||
|
||||
crate_root=$(dirname $(realpath "$0"))
|
||||
monorepo_root=$(realpath "${crate_root}/../..")
|
||||
@@ -54,7 +54,7 @@ function swarm() {
|
||||
echo "All agents completed"
|
||||
}
|
||||
|
||||
copy_gw_probe
|
||||
# copy_gw_probe
|
||||
build_agent
|
||||
|
||||
swarm $workers
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use sqlx::{Connection, SqliteConnection};
|
||||
#[cfg(target_family = "unix")]
|
||||
use std::fs::Permissions;
|
||||
#[cfg(target_family = "unix")]
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use tokio::{fs::File, io::AsyncWriteExt};
|
||||
|
||||
@@ -39,7 +41,10 @@ async fn write_db_path_to_file(out_dir: &str, db_filename: &str) -> anyhow::Resu
|
||||
file.write_all(format!("sqlite3 {}/{}", out_dir, db_filename).as_bytes())
|
||||
.await?;
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
file.set_permissions(Permissions::from_mode(0o755))
|
||||
.await
|
||||
.map_err(From::from)
|
||||
.map_err(anyhow::Error::from)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
set -e
|
||||
|
||||
user_rust_log_preference=$RUST_LOG
|
||||
export NYM_API_CLIENT_TIMEOUT=60
|
||||
export EXPLORER_CLIENT_TIMEOUT=60
|
||||
export NODE_STATUS_API_TESTRUN_REFRESH_INTERVAL=120
|
||||
@@ -20,7 +21,8 @@ function run_bare() {
|
||||
set -a
|
||||
source "${monorepo_root}/envs/${ENVIRONMENT}.env"
|
||||
set +a
|
||||
export RUST_LOG=debug
|
||||
export RUST_LOG=${user_rust_log_preference:-debug}
|
||||
echo "RUST_LOG=${RUST_LOG}"
|
||||
|
||||
# --conection-url is provided in build.rs
|
||||
cargo run --package nym-node-status-api
|
||||
|
||||
@@ -154,31 +154,39 @@ mod api_regression {
|
||||
use super::*;
|
||||
use std::{env::var, sync::LazyLock};
|
||||
|
||||
static IPINFO_TOKEN: LazyLock<String> = LazyLock::new(|| var("IPINFO_API_TOKEN").unwrap());
|
||||
static IPINFO_TOKEN: LazyLock<Option<String>> = LazyLock::new(|| var("IPINFO_API_TOKEN").ok());
|
||||
static CI: LazyLock<Option<String>> = LazyLock::new(|| var("CI").ok());
|
||||
|
||||
#[tokio::test]
|
||||
async fn should_parse_response() {
|
||||
let client = IpInfoClient::new(&(*IPINFO_TOKEN));
|
||||
let my_ip = reqwest::get("https://api.ipify.org")
|
||||
.await
|
||||
.expect("Couldn't get own IP")
|
||||
.text()
|
||||
.await
|
||||
.unwrap();
|
||||
if CI.is_none() {
|
||||
return;
|
||||
}
|
||||
if let Some(token) = &*IPINFO_TOKEN {
|
||||
let client = IpInfoClient::new(token);
|
||||
let my_ip = reqwest::get("https://api.ipify.org")
|
||||
.await
|
||||
.expect("Couldn't get own IP")
|
||||
.text()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let location_result = client.locate_ip(my_ip).await;
|
||||
assert!(location_result.is_ok(), "Did ipinfo response change?");
|
||||
let location_result = client.locate_ip(my_ip).await;
|
||||
assert!(location_result.is_ok(), "Did ipinfo response change?");
|
||||
|
||||
assert!(
|
||||
client.check_remaining_bandwidth().await.is_ok(),
|
||||
"Failed to check remaining bandwidth?"
|
||||
);
|
||||
assert!(
|
||||
client.check_remaining_bandwidth().await.is_ok(),
|
||||
"Failed to check remaining bandwidth?"
|
||||
);
|
||||
|
||||
// when serialized, these fields should be present because they're exposed over API
|
||||
let location_result = location_result.unwrap();
|
||||
let json = serde_json::to_value(&location_result).unwrap();
|
||||
assert!(json.get("two_letter_iso_country_code").is_some());
|
||||
assert!(json.get("latitude").is_some());
|
||||
assert!(json.get("longitude").is_some());
|
||||
// when serialized, these fields should be present because they're exposed over API
|
||||
let location_result = location_result.unwrap();
|
||||
let json = serde_json::to_value(&location_result).unwrap();
|
||||
assert!(json.get("two_letter_iso_country_code").is_some());
|
||||
assert!(json.get("latitude").is_some());
|
||||
assert!(json.get("longitude").is_some());
|
||||
} else {
|
||||
panic!("IPINFO_API_TOKEN not set");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,6 +73,15 @@ impl GatewaySessionStatsHandler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_session_delete(
|
||||
&mut self,
|
||||
client: DestinationAddressBytes,
|
||||
) -> Result<(), StatsStorageError> {
|
||||
self.storage.delete_active_session(client).await?;
|
||||
self.storage.delete_unique_user(client).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_session_event(
|
||||
&mut self,
|
||||
event: GatewaySessionEvent,
|
||||
@@ -90,6 +99,11 @@ impl GatewaySessionStatsHandler {
|
||||
ticket_type,
|
||||
client,
|
||||
} => self.handle_ecash_ticket(ticket_type, client).await,
|
||||
|
||||
// As long as delete is sent before stop, everything should work as expected
|
||||
GatewaySessionEvent::SessionDelete { client } => {
|
||||
self.handle_session_delete(client).await
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use crate::{
|
||||
rewarder::{epoch::Epoch, storage::manager::StorageManager, RewardingResult},
|
||||
};
|
||||
use nym_contracts_common::types::NaiveFloat;
|
||||
use sqlx::sqlite::{SqliteAutoVacuum, SqliteSynchronous};
|
||||
use sqlx::ConnectOptions;
|
||||
use std::{fmt::Debug, path::Path};
|
||||
use time::{Date, OffsetDateTime};
|
||||
@@ -23,6 +24,9 @@ impl RewarderStorage {
|
||||
#[instrument]
|
||||
pub async fn init<P: AsRef<Path> + Debug>(database_path: P) -> Result<Self, NymRewarderError> {
|
||||
let opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(database_path)
|
||||
.create_if_missing(true)
|
||||
.disable_statement_logging();
|
||||
|
||||
@@ -28,6 +28,7 @@ use nym_client_core::error::ClientCoreError;
|
||||
use nym_client_core::init::helpers::current_gateways;
|
||||
use nym_client_core::init::setup_gateway;
|
||||
use nym_client_core::init::types::{GatewaySelectionSpecification, GatewaySetup};
|
||||
use nym_client_core::ForgetMe;
|
||||
use nym_credentials_interface::TicketType;
|
||||
use nym_socks5_client_core::config::Socks5;
|
||||
use nym_task::{TaskClient, TaskHandle, TaskStatus};
|
||||
@@ -36,6 +37,8 @@ use nym_validator_client::{nyxd, QueryHttpRpcNyxdClient, UserAgent};
|
||||
use rand::rngs::OsRng;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
#[cfg(unix)]
|
||||
use std::sync::Arc;
|
||||
use url::Url;
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
@@ -54,11 +57,14 @@ pub struct MixnetClientBuilder<S: MixnetClientStorage = Ephemeral> {
|
||||
custom_shutdown: Option<TaskClient>,
|
||||
force_tls: bool,
|
||||
user_agent: Option<UserAgent>,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: Option<Arc<dyn Fn(std::os::fd::RawFd) + Send + Sync>>,
|
||||
|
||||
// TODO: incorporate it properly into `MixnetClientStorage` (I will need it in wasm anyway)
|
||||
gateway_endpoint_config_path: Option<PathBuf>,
|
||||
|
||||
storage: S,
|
||||
forget_me: ForgetMe,
|
||||
}
|
||||
|
||||
impl MixnetClientBuilder<Ephemeral> {
|
||||
@@ -93,6 +99,9 @@ impl MixnetClientBuilder<OnDiskPersistent> {
|
||||
custom_gateway_transceiver: None,
|
||||
force_tls: false,
|
||||
user_agent: None,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
forget_me: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -120,8 +129,11 @@ where
|
||||
custom_shutdown: None,
|
||||
force_tls: false,
|
||||
user_agent: None,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
gateway_endpoint_config_path: None,
|
||||
storage,
|
||||
forget_me: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,8 +150,11 @@ where
|
||||
custom_shutdown: self.custom_shutdown,
|
||||
force_tls: self.force_tls,
|
||||
user_agent: self.user_agent,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: self.connection_fd_callback,
|
||||
gateway_endpoint_config_path: self.gateway_endpoint_config_path,
|
||||
storage,
|
||||
forget_me: self.forget_me,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +167,12 @@ where
|
||||
self.set_storage(storage)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn with_forget_me(mut self, forget_me: ForgetMe) -> Self {
|
||||
self.forget_me = forget_me;
|
||||
self
|
||||
}
|
||||
|
||||
/// Request a specific gateway instead of a random one.
|
||||
#[must_use]
|
||||
pub fn request_gateway(mut self, user_chosen_gateway: String) -> Self {
|
||||
@@ -237,6 +258,16 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[must_use]
|
||||
pub fn with_connection_fd_callback(
|
||||
mut self,
|
||||
connection_fd_callback: Arc<dyn Fn(std::os::fd::RawFd) + Send + Sync>,
|
||||
) -> Self {
|
||||
self.connection_fd_callback = Some(connection_fd_callback);
|
||||
self
|
||||
}
|
||||
|
||||
/// Use custom mixnet sender that might not be the default websocket gateway connection.
|
||||
/// only for advanced use
|
||||
#[must_use]
|
||||
@@ -265,7 +296,11 @@ where
|
||||
client.wait_for_gateway = self.wait_for_gateway;
|
||||
client.force_tls = self.force_tls;
|
||||
client.user_agent = self.user_agent;
|
||||
|
||||
#[cfg(unix)]
|
||||
if self.connection_fd_callback.is_some() {
|
||||
client.connection_fd_callback = self.connection_fd_callback;
|
||||
}
|
||||
client.forget_me = self.forget_me;
|
||||
Ok(client)
|
||||
}
|
||||
}
|
||||
@@ -314,6 +349,12 @@ where
|
||||
custom_shutdown: Option<TaskClient>,
|
||||
|
||||
user_agent: Option<UserAgent>,
|
||||
|
||||
/// Callback on the websocket fd as soon as the connection has been established
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: Option<Arc<dyn Fn(std::os::fd::RawFd) + Send + Sync>>,
|
||||
|
||||
forget_me: ForgetMe,
|
||||
}
|
||||
|
||||
impl<S> DisconnectedMixnetClient<S>
|
||||
@@ -363,6 +404,9 @@ where
|
||||
force_tls: false,
|
||||
custom_shutdown: None,
|
||||
user_agent: None,
|
||||
#[cfg(unix)]
|
||||
connection_fd_callback: None,
|
||||
forget_me: Default::default(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -586,7 +630,8 @@ where
|
||||
|
||||
let mut base_builder: BaseClientBuilder<_, _> =
|
||||
BaseClientBuilder::new(&base_config, self.storage, self.dkg_query_client)
|
||||
.with_wait_for_gateway(self.wait_for_gateway);
|
||||
.with_wait_for_gateway(self.wait_for_gateway)
|
||||
.with_forget_me(&self.forget_me);
|
||||
|
||||
if let Some(user_agent) = self.user_agent {
|
||||
base_builder = base_builder.with_user_agent(user_agent);
|
||||
@@ -604,6 +649,11 @@ where
|
||||
base_builder = base_builder.with_gateway_transceiver(gateway_transceiver);
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
if let Some(connection_fd_callback) = self.connection_fd_callback {
|
||||
base_builder = base_builder.with_connection_fd_callback(connection_fd_callback);
|
||||
}
|
||||
|
||||
let started_client = base_builder.start_base().await?;
|
||||
self.state = BuilderState::Registered {};
|
||||
let nym_address = started_client.address;
|
||||
|
||||
@@ -10,7 +10,10 @@ use crate::{
|
||||
storage::manager::StorageManager,
|
||||
},
|
||||
};
|
||||
use sqlx::ConnectOptions;
|
||||
use sqlx::{
|
||||
sqlite::{SqliteAutoVacuum, SqliteSynchronous},
|
||||
ConnectOptions,
|
||||
};
|
||||
use std::path::Path;
|
||||
use tracing::{error, info};
|
||||
use url::Url;
|
||||
@@ -39,6 +42,9 @@ impl NetworkManagerStorage {
|
||||
// TODO: we can inject here more stuff based on our nym-api global config
|
||||
// struct. Maybe different pool size or timeout intervals?
|
||||
let opts = sqlx::sqlite::SqliteConnectOptions::new()
|
||||
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
||||
.synchronous(SqliteSynchronous::Normal)
|
||||
.auto_vacuum(SqliteAutoVacuum::Incremental)
|
||||
.filename(database_path)
|
||||
.create_if_missing(true)
|
||||
.disable_statement_logging();
|
||||
|
||||
Reference in New Issue
Block a user