Files
nym/common/gateway-storage/src/lib.rs
T
Jędrzej Stuczyński 5a07b73375 feature: hopefully final steps of the smoosh™️ (#5201)
* 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
2024-12-05 17:21:36 +00:00

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(())
}
}