5a07b73375
* removed mnemonic from gateway config struct scaffolding for common mixnet listener running verloc unconditionally in a nym-node remove filtering by mixnode extracted verloc to separate crate integrated nym-node-http-server more tightly with the binary most logic for handling forward packets running all mixnode-related tasks natively inside nymnode removed gateway storage trait in favour of the only concrete implementation most logic for handling final hop packets using nym-node owned socket listener for gateways utility for sending plain message through mixnet + gateway fix using common packet forwarding in both modes nifying nym-node metrics reproduce behaviour of the console logger cleaned up cli args redesigned gateway tasks startup procedure removing dead code scaffolding for old config v6 config migration implemented MixnetMetricsCleaner * clippy * require entry/exit for wireguard * removed dead code in migration code * updated config template * use custom user agent for verloc queries * fixed premature shutdown of gateway tasks * hidden nym-api flag to allow illegal node ips * experiment: final hop handing with wireguard * added additional startup logs * typo * fixed legacy stats endpoint data * additional logs * apply review comments * fixed local testnet manager
510 lines
16 KiB
Rust
510 lines
16 KiB
Rust
// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
use bandwidth::BandwidthManager;
|
|
use clients::{ClientManager, ClientType};
|
|
use inboxes::InboxManager;
|
|
use models::{
|
|
Client, PersistedBandwidth, PersistedSharedKeys, RedemptionProposal, StoredMessage,
|
|
VerifiedTicket, WireguardPeer,
|
|
};
|
|
use nym_credentials_interface::ClientTicket;
|
|
use nym_gateway_requests::shared_key::SharedGatewayKey;
|
|
use nym_sphinx::DestinationAddressBytes;
|
|
use shared_keys::SharedKeysManager;
|
|
use sqlx::ConnectOptions;
|
|
use std::path::Path;
|
|
use tickets::TicketStorageManager;
|
|
use time::OffsetDateTime;
|
|
use tracing::{debug, error};
|
|
|
|
pub mod bandwidth;
|
|
mod clients;
|
|
pub mod error;
|
|
mod inboxes;
|
|
pub mod models;
|
|
mod shared_keys;
|
|
mod tickets;
|
|
mod wireguard_peers;
|
|
|
|
pub use error::GatewayStorageError;
|
|
|
|
// note that clone here is fine as upon cloning the same underlying pool will be used
|
|
#[derive(Clone)]
|
|
pub struct GatewayStorage {
|
|
client_manager: ClientManager,
|
|
shared_key_manager: SharedKeysManager,
|
|
inbox_manager: InboxManager,
|
|
bandwidth_manager: BandwidthManager,
|
|
ticket_manager: TicketStorageManager,
|
|
wireguard_peer_manager: wireguard_peers::WgPeerManager,
|
|
}
|
|
|
|
impl GatewayStorage {
|
|
/// Initialises `PersistentStorage` using the provided path.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `database_path`: path to the database.
|
|
/// * `message_retrieval_limit`: maximum number of stored client messages that can be retrieved at once.
|
|
pub async fn init<P: AsRef<Path> + Send>(
|
|
database_path: P,
|
|
message_retrieval_limit: i64,
|
|
) -> Result<Self, GatewayStorageError> {
|
|
debug!(
|
|
"Attempting to connect to database {:?}",
|
|
database_path.as_ref().as_os_str()
|
|
);
|
|
|
|
// 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()
|
|
.filename(database_path)
|
|
.create_if_missing(true)
|
|
.disable_statement_logging();
|
|
|
|
// TODO: do we want auto_vacuum ?
|
|
|
|
let connection_pool = match sqlx::SqlitePool::connect_with(opts).await {
|
|
Ok(db) => db,
|
|
Err(err) => {
|
|
error!("Failed to connect to SQLx database: {err}");
|
|
return Err(err.into());
|
|
}
|
|
};
|
|
|
|
if let Err(err) = sqlx::migrate!("./migrations").run(&connection_pool).await {
|
|
error!("Failed to perform migration on the SQLx database: {err}");
|
|
return Err(err.into());
|
|
}
|
|
|
|
// the cloning here are cheap as connection pool is stored behind an Arc
|
|
Ok(GatewayStorage {
|
|
client_manager: clients::ClientManager::new(connection_pool.clone()),
|
|
wireguard_peer_manager: wireguard_peers::WgPeerManager::new(connection_pool.clone()),
|
|
shared_key_manager: SharedKeysManager::new(connection_pool.clone()),
|
|
inbox_manager: InboxManager::new(connection_pool.clone(), message_retrieval_limit),
|
|
bandwidth_manager: BandwidthManager::new(connection_pool.clone()),
|
|
ticket_manager: TicketStorageManager::new(connection_pool),
|
|
})
|
|
}
|
|
}
|
|
|
|
impl GatewayStorage {
|
|
pub async fn get_mixnet_client_id(
|
|
&self,
|
|
client_address: DestinationAddressBytes,
|
|
) -> Result<i64, GatewayStorageError> {
|
|
Ok(self
|
|
.shared_key_manager
|
|
.client_id(&client_address.as_base58_string())
|
|
.await?)
|
|
}
|
|
|
|
pub async fn insert_shared_keys(
|
|
&self,
|
|
client_address: DestinationAddressBytes,
|
|
shared_keys: &SharedGatewayKey,
|
|
) -> Result<i64, GatewayStorageError> {
|
|
let client_address_bs58 = client_address.as_base58_string();
|
|
let client_id = match self
|
|
.shared_key_manager
|
|
.client_id(&client_address_bs58)
|
|
.await
|
|
{
|
|
Ok(client_id) => client_id,
|
|
_ => {
|
|
self.client_manager
|
|
.insert_client(ClientType::EntryMixnet)
|
|
.await?
|
|
}
|
|
};
|
|
self.shared_key_manager
|
|
.insert_shared_keys(
|
|
client_id,
|
|
client_address_bs58,
|
|
shared_keys.aes128_ctr_hmac_bs58().as_deref(),
|
|
shared_keys.aes256_gcm_siv().as_deref(),
|
|
)
|
|
.await?;
|
|
Ok(client_id)
|
|
}
|
|
|
|
pub async fn get_shared_keys(
|
|
&self,
|
|
client_address: DestinationAddressBytes,
|
|
) -> Result<Option<PersistedSharedKeys>, GatewayStorageError> {
|
|
let keys = self
|
|
.shared_key_manager
|
|
.get_shared_keys(&client_address.as_base58_string())
|
|
.await?;
|
|
Ok(keys)
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub async fn remove_shared_keys(
|
|
&self,
|
|
client_address: DestinationAddressBytes,
|
|
) -> Result<(), GatewayStorageError> {
|
|
self.shared_key_manager
|
|
.remove_shared_keys(&client_address.as_base58_string())
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn get_client(&self, client_id: i64) -> Result<Option<Client>, GatewayStorageError> {
|
|
let client = self.client_manager.get_client(client_id).await?;
|
|
Ok(client)
|
|
}
|
|
|
|
pub async fn store_message(
|
|
&self,
|
|
client_address: DestinationAddressBytes,
|
|
message: Vec<u8>,
|
|
) -> Result<(), GatewayStorageError> {
|
|
self.inbox_manager
|
|
.insert_message(&client_address.as_base58_string(), message)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn retrieve_messages(
|
|
&self,
|
|
client_address: DestinationAddressBytes,
|
|
start_after: Option<i64>,
|
|
) -> Result<(Vec<StoredMessage>, Option<i64>), GatewayStorageError> {
|
|
let messages = self
|
|
.inbox_manager
|
|
.get_messages(&client_address.as_base58_string(), start_after)
|
|
.await?;
|
|
Ok(messages)
|
|
}
|
|
|
|
pub async fn remove_messages(&self, ids: Vec<i64>) -> Result<(), GatewayStorageError> {
|
|
for id in ids {
|
|
self.inbox_manager.remove_message(id).await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn create_bandwidth_entry(&self, client_id: i64) -> Result<(), GatewayStorageError> {
|
|
self.bandwidth_manager.insert_new_client(client_id).await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn set_expiration(
|
|
&self,
|
|
client_id: i64,
|
|
expiration: OffsetDateTime,
|
|
) -> Result<(), GatewayStorageError> {
|
|
self.bandwidth_manager
|
|
.set_expiration(client_id, expiration)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn reset_bandwidth(&self, client_id: i64) -> Result<(), GatewayStorageError> {
|
|
self.bandwidth_manager.reset_bandwidth(client_id).await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn get_available_bandwidth(
|
|
&self,
|
|
client_id: i64,
|
|
) -> Result<Option<PersistedBandwidth>, GatewayStorageError> {
|
|
Ok(self
|
|
.bandwidth_manager
|
|
.get_available_bandwidth(client_id)
|
|
.await?)
|
|
}
|
|
|
|
pub async fn increase_bandwidth(
|
|
&self,
|
|
client_id: i64,
|
|
amount: i64,
|
|
) -> Result<i64, GatewayStorageError> {
|
|
Ok(self
|
|
.bandwidth_manager
|
|
.increase_bandwidth(client_id, amount)
|
|
.await?)
|
|
}
|
|
|
|
pub async fn revoke_ticket_bandwidth(
|
|
&self,
|
|
ticket_id: i64,
|
|
amount: i64,
|
|
) -> Result<(), GatewayStorageError> {
|
|
Ok(self
|
|
.bandwidth_manager
|
|
.revoke_ticket_bandwidth(ticket_id, amount)
|
|
.await?)
|
|
}
|
|
|
|
pub async fn decrease_bandwidth(
|
|
&self,
|
|
client_id: i64,
|
|
amount: i64,
|
|
) -> Result<i64, GatewayStorageError> {
|
|
Ok(self
|
|
.bandwidth_manager
|
|
.decrease_bandwidth(client_id, amount)
|
|
.await?)
|
|
}
|
|
|
|
pub async fn insert_epoch_signers(
|
|
&self,
|
|
epoch_id: i64,
|
|
signer_ids: Vec<i64>,
|
|
) -> Result<(), GatewayStorageError> {
|
|
self.ticket_manager
|
|
.insert_ecash_signers(epoch_id, signer_ids)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn insert_received_ticket(
|
|
&self,
|
|
client_id: i64,
|
|
received_at: OffsetDateTime,
|
|
serial_number: Vec<u8>,
|
|
data: Vec<u8>,
|
|
) -> Result<i64, GatewayStorageError> {
|
|
// technically if we crash between those 2 calls we'll have a bit of data inconsistency,
|
|
// but nothing too tragic. we just won't get paid for a single ticket
|
|
let ticket_id = self
|
|
.ticket_manager
|
|
.insert_new_ticket(client_id, received_at)
|
|
.await?;
|
|
self.ticket_manager
|
|
.insert_ticket_data(ticket_id, &serial_number, &data)
|
|
.await?;
|
|
|
|
Ok(ticket_id)
|
|
}
|
|
|
|
pub async fn contains_ticket(&self, serial_number: &[u8]) -> Result<bool, GatewayStorageError> {
|
|
Ok(self.ticket_manager.has_ticket_data(serial_number).await?)
|
|
}
|
|
|
|
pub async fn insert_ticket_verification(
|
|
&self,
|
|
ticket_id: i64,
|
|
signer_id: i64,
|
|
verified_at: OffsetDateTime,
|
|
accepted: bool,
|
|
) -> Result<(), GatewayStorageError> {
|
|
self.ticket_manager
|
|
.insert_ticket_verification(ticket_id, signer_id, verified_at, accepted)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn update_rejected_ticket(&self, ticket_id: i64) -> Result<(), GatewayStorageError> {
|
|
// set the ticket as rejected
|
|
self.ticket_manager.set_rejected_ticket(ticket_id).await?;
|
|
|
|
// drop all ticket_data - we no longer need it
|
|
// TODO: or maybe we do as a proof of receiving bad data?
|
|
self.ticket_manager.remove_ticket_data(ticket_id).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn update_verified_ticket(&self, ticket_id: i64) -> Result<(), GatewayStorageError> {
|
|
// 1. insert into verified table
|
|
self.ticket_manager
|
|
.insert_verified_ticket(ticket_id)
|
|
.await?;
|
|
|
|
// TODO: maybe we want to leave that be until ticket gets fully redeemed instead?
|
|
// 2. remove individual verifications
|
|
self.ticket_manager
|
|
.remove_ticket_verification(ticket_id)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn remove_verified_ticket_binary_data(
|
|
&self,
|
|
ticket_id: i64,
|
|
) -> Result<(), GatewayStorageError> {
|
|
self.ticket_manager
|
|
.remove_binary_ticket_data(ticket_id)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn get_all_verified_tickets_with_sn(
|
|
&self,
|
|
) -> Result<Vec<VerifiedTicket>, GatewayStorageError> {
|
|
Ok(self
|
|
.ticket_manager
|
|
.get_all_verified_tickets_with_sn()
|
|
.await?)
|
|
}
|
|
|
|
pub async fn get_all_proposed_tickets_with_sn(
|
|
&self,
|
|
proposal_id: u32,
|
|
) -> Result<Vec<VerifiedTicket>, GatewayStorageError> {
|
|
Ok(self
|
|
.ticket_manager
|
|
.get_all_proposed_tickets_with_sn(proposal_id as i64)
|
|
.await?)
|
|
}
|
|
|
|
pub async fn insert_redemption_proposal(
|
|
&self,
|
|
tickets: &[VerifiedTicket],
|
|
proposal_id: u32,
|
|
created_at: OffsetDateTime,
|
|
) -> Result<(), GatewayStorageError> {
|
|
// if we crash between those, there might a bit of an issue. we should revisit it later
|
|
|
|
// 1. insert the actual proposal
|
|
self.ticket_manager
|
|
.insert_redemption_proposal(proposal_id as i64, created_at)
|
|
.await?;
|
|
|
|
// 2. update all the associated tickets
|
|
self.ticket_manager
|
|
.insert_verified_tickets_proposal_id(
|
|
tickets.iter().map(|t| t.ticket_id),
|
|
proposal_id as i64,
|
|
)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn clear_post_proposal_data(
|
|
&self,
|
|
proposal_id: u32,
|
|
resolved_at: OffsetDateTime,
|
|
rejected: bool,
|
|
) -> Result<(), GatewayStorageError> {
|
|
// 1. update proposal metadata
|
|
self.ticket_manager
|
|
.update_redemption_proposal(proposal_id as i64, resolved_at, rejected)
|
|
.await?;
|
|
|
|
// 2. remove ticket data rows (we can drop serial numbers)
|
|
self.ticket_manager
|
|
.remove_redeemed_tickets_data(proposal_id as i64)
|
|
.await?;
|
|
|
|
// 3. remove verified tickets rows
|
|
self.ticket_manager
|
|
.remove_verified_tickets(proposal_id as i64)
|
|
.await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn latest_proposal(&self) -> Result<Option<RedemptionProposal>, GatewayStorageError> {
|
|
Ok(self.ticket_manager.get_latest_redemption_proposal().await?)
|
|
}
|
|
|
|
pub async fn get_all_unverified_tickets(
|
|
&self,
|
|
) -> Result<Vec<ClientTicket>, GatewayStorageError> {
|
|
self.ticket_manager
|
|
.get_unverified_tickets()
|
|
.await?
|
|
.into_iter()
|
|
.map(TryInto::try_into)
|
|
.collect()
|
|
}
|
|
|
|
pub async fn get_all_unresolved_proposals(&self) -> Result<Vec<i64>, GatewayStorageError> {
|
|
Ok(self
|
|
.ticket_manager
|
|
.get_all_unresolved_redemption_proposal_ids()
|
|
.await?)
|
|
}
|
|
|
|
pub async fn get_votes(&self, ticket_id: i64) -> Result<Vec<i64>, GatewayStorageError> {
|
|
Ok(self
|
|
.ticket_manager
|
|
.get_verification_votes(ticket_id)
|
|
.await?)
|
|
}
|
|
|
|
pub async fn get_signers(&self, epoch_id: i64) -> Result<Vec<i64>, GatewayStorageError> {
|
|
Ok(self.ticket_manager.get_epoch_signers(epoch_id).await?)
|
|
}
|
|
|
|
/// Insert a wireguard peer in the storage.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `peer`: wireguard peer data to be stored
|
|
/// * `with_client_id`: if the peer should have a corresponding client_id
|
|
/// (created with entry wireguard ticket) or live without one (or with an
|
|
/// exiting one), for temporary backwards compatibility.
|
|
pub async fn insert_wireguard_peer(
|
|
&self,
|
|
peer: &defguard_wireguard_rs::host::Peer,
|
|
with_client_id: bool,
|
|
) -> Result<Option<i64>, GatewayStorageError> {
|
|
let client_id = match self
|
|
.wireguard_peer_manager
|
|
.retrieve_peer(&peer.public_key.to_string())
|
|
.await?
|
|
{
|
|
Some(peer) => peer.client_id,
|
|
_ => {
|
|
if with_client_id {
|
|
Some(
|
|
self.client_manager
|
|
.insert_client(ClientType::EntryWireguard)
|
|
.await?,
|
|
)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
};
|
|
let mut peer = WireguardPeer::from(peer.clone());
|
|
peer.client_id = client_id;
|
|
self.wireguard_peer_manager.insert_peer(&peer).await?;
|
|
Ok(client_id)
|
|
}
|
|
|
|
/// Tries to retrieve available bandwidth for the particular peer.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `peer_public_key`: wireguard public key of the peer to be retrieved.
|
|
pub async fn get_wireguard_peer(
|
|
&self,
|
|
peer_public_key: &str,
|
|
) -> Result<Option<WireguardPeer>, GatewayStorageError> {
|
|
let peer = self
|
|
.wireguard_peer_manager
|
|
.retrieve_peer(peer_public_key)
|
|
.await?;
|
|
Ok(peer)
|
|
}
|
|
|
|
/// Retrieves all wireguard peers.
|
|
pub async fn get_all_wireguard_peers(&self) -> Result<Vec<WireguardPeer>, GatewayStorageError> {
|
|
let ret = self.wireguard_peer_manager.retrieve_all_peers().await?;
|
|
Ok(ret)
|
|
}
|
|
|
|
/// Remove a wireguard peer from the storage.
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `peer_public_key`: wireguard public key of the peer to be removed.
|
|
pub async fn remove_wireguard_peer(
|
|
&self,
|
|
peer_public_key: &str,
|
|
) -> Result<(), GatewayStorageError> {
|
|
self.wireguard_peer_manager
|
|
.remove_peer(peer_public_key)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
}
|