Compare commits

...

8 Commits

Author SHA1 Message Date
Bogdan-Ștefan Neacşu 291544c311 Add windows to CI builds (#5269)
* Add windows to CI builds

* Fix win build for node status api

* Fix win build for sdk

* Fix win build for cred proxy
2024-12-17 15:18:56 +02:00
dynco-nym b4f51baf94 Change sqlite journal mode to WAL (#5213)
* Change sqlite journal mode to WAL

* Synchronous mode & auto vacuum

* Bump probe git ref to 1.1.0
2024-12-16 16:40:02 +01:00
Drazen Urch a3f3d83c1b Shipping raw metrics to PG (#5216)
* Shipping raw metrics to PG

* Put cancel token back in its place

* fmt
2024-12-16 16:19:37 +01:00
Drazen Urch 84d7004cb2 Add control messages to GatewayTransciver (#5247)
* Add control messages to GatewayTransciver

* Add forget me flag to clients

* CI gate IPIINFO test

* Handle ForgetMe for client and stats db

* fmt
2024-12-16 15:18:04 +01:00
import this be063a36eb syntax hotfix (#5266) 2024-12-16 13:17:38 +00:00
windy-ux 0a712b9fce Fix/web 615 seo setup (#5265)
* + add header into Packet Mixing docs

* + add head changes for testing

* / updated version of metatags in theme.config

* + add env file

* / theme.config to use NEXT_PUBLIC_SITE_URL from env file

* @ Fix broken link in theme.config

* - remove favicon code

* + add desription for intro pages

* + add default book's desriptions

* Revert "+ add desription for intro pages"

This reverts commit 98c78242d4.
2024-12-16 13:17:25 +00:00
Bogdan-Ștefan Neacşu 88d6fb4e22 Add fd callback to client core (#5230)
* Add fd callback to client core

* Include in sdk

* Fix clippy many args

* Method in builder

* Replace Box with Arc
2024-12-16 13:57:34 +02:00
Jon Häggblad 04c2045d94 Add PATCH support to nym-http-api-client (#5260) 2024-12-16 12:28:44 +01:00
49 changed files with 999 additions and 223 deletions
+1 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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(())
}
}
+45
View File
@@ -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 {
+17 -1
View File
@@ -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 = ?",
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "DELETE FROM message_store WHERE client_address_bs58 = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "3ea5542b21a41b14276a8fd6b870c61aa0ddd30fee2565803b88c6086bd2a734"
}
@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "DELETE FROM available_bandwidth WHERE client_id = ?",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "a3cc707995b8215fa77738cd1a55f9e8d251a3e764104d2a54153895dee1a118"
}
+10
View File
@@ -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,
+13
View File
@@ -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(())
}
}
+49 -1
View File
@@ -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,
+103
View File
@@ -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)
+8 -1
View File
@@ -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();
+8
View File
@@ -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 />
+15 -2
View File
@@ -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,
);
+4
View File
@@ -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)
+2
View File
@@ -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" }
+3 -2
View File
@@ -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
+123 -42
View File
@@ -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();
+34 -7
View File
@@ -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();
+52 -2
View File
@@ -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();