Files
nym/common/gateway-storage/src/lib.rs
2024-09-18 17:43:47 +01:00

674 lines
21 KiB
Rust

// Copyright 2020 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only
use async_trait::async_trait;
use bandwidth::BandwidthManager;
use clients::{ClientManager, ClientType};
use error::StorageError;
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;
#[async_trait]
pub trait Storage: Send + Sync {
async fn get_mixnet_client_id(
&self,
client_address: DestinationAddressBytes,
) -> Result<i64, StorageError>;
/// Inserts provided derived shared keys into the database.
/// If keys previously existed for the provided client, they are overwritten with the new data.
///
/// # Arguments
///
/// * `client_address`: base58-encoded address of the client
/// * `shared_keys`:
/// - legacy: shared encryption (AES128CTR) and mac (hmac-blake3) derived shared keys to store.
/// - current: shared AES256-GCM-SIV keys
async fn insert_shared_keys(
&self,
client_address: DestinationAddressBytes,
shared_keys: &SharedGatewayKey,
) -> Result<i64, StorageError>;
/// Tries to retrieve shared keys stored for the particular client.
///
/// # Arguments
///
/// * `client_address`: address of the client
async fn get_shared_keys(
&self,
client_address: DestinationAddressBytes,
) -> Result<Option<PersistedSharedKeys>, StorageError>;
/// Removes from the database shared keys derived with the particular client.
///
/// # Arguments
///
/// * `client_address`: address of the client
// currently there is no code flow that causes removal (not overwriting)
// of the stored keys. However, retain the function for consistency and completion sake
#[allow(dead_code)]
async fn remove_shared_keys(
&self,
client_address: DestinationAddressBytes,
) -> Result<(), StorageError>;
/// Tries to retrieve a particular client.
///
/// # Arguments
///
/// * `client_id`: id of the client
#[allow(dead_code)]
async fn get_client(&self, client_id: i64) -> Result<Option<Client>, StorageError>;
/// Inserts new message to the storage for an offline client for future retrieval.
///
/// # Arguments
///
/// * `client_address`: address of the client
/// * `message`: raw message to store.
async fn store_message(
&self,
client_address: DestinationAddressBytes,
message: Vec<u8>,
) -> Result<(), StorageError>;
/// Retrieves messages stored for the particular client specified by the provided address.
///
/// # Arguments
///
/// * `client_address`: address of the client
/// * `start_after`: optional starting id of the messages to grab
///
/// returns the retrieved messages alongside optional id of the last message retrieved if
/// there are more messages to retrieve.
async fn retrieve_messages(
&self,
client_address: DestinationAddressBytes,
start_after: Option<i64>,
) -> Result<(Vec<StoredMessage>, Option<i64>), StorageError>;
/// Removes messages with the specified ids
///
/// # Arguments
///
/// * `ids`: ids of the messages to remove
async fn remove_messages(&self, ids: Vec<i64>) -> Result<(), StorageError>;
/// Creates a new bandwidth entry for the particular client.
async fn create_bandwidth_entry(&self, client_id: i64) -> Result<(), StorageError>;
/// Set the freepass expiration date of the particular client to the provided date.
///
/// # Arguments
///
/// * `client_address`: address of the client
/// * `expiration`: the expiration date of the associated free pass.
async fn set_expiration(
&self,
client_id: i64,
expiration: OffsetDateTime,
) -> Result<(), StorageError>;
/// Reset all the bandwidth
///
/// # Arguments
///
/// * `client_address`: address of the client
async fn reset_bandwidth(&self, client_id: i64) -> Result<(), StorageError>;
/// Tries to retrieve available bandwidth for the particular client.
async fn get_available_bandwidth(
&self,
client_id: i64,
) -> Result<Option<PersistedBandwidth>, StorageError>;
/// Increases specified client's bandwidth by the provided amount and returns the current value.
async fn increase_bandwidth(&self, client_id: i64, amount: i64) -> Result<i64, StorageError>;
async fn revoke_ticket_bandwidth(
&self,
ticket_id: i64,
amount: i64,
) -> Result<(), StorageError>;
#[allow(dead_code)]
/// Decreases specified client's bandwidth by the provided amount and returns the current value.
async fn decrease_bandwidth(&self, client_id: i64, amount: i64) -> Result<i64, StorageError>;
async fn insert_epoch_signers(
&self,
epoch_id: i64,
signer_ids: Vec<i64>,
) -> Result<(), StorageError>;
async fn insert_received_ticket(
&self,
client_id: i64,
received_at: OffsetDateTime,
serial_number: Vec<u8>,
data: Vec<u8>,
) -> Result<i64, StorageError>;
// note: this only checks very recent tickets that haven't yet been redeemed
// (but it's better than nothing)
/// Check if the ticket with the provided serial number if already present in the storage.
///
/// # Arguments
///
/// * `serial_number`: the unique serial number embedded in the ticket
async fn contains_ticket(&self, serial_number: &[u8]) -> Result<bool, StorageError>;
async fn insert_ticket_verification(
&self,
ticket_id: i64,
signer_id: i64,
verified_at: OffsetDateTime,
accepted: bool,
) -> Result<(), StorageError>;
async fn update_rejected_ticket(&self, ticket_id: i64) -> Result<(), StorageError>;
async fn update_verified_ticket(&self, ticket_id: i64) -> Result<(), StorageError>;
async fn remove_verified_ticket_binary_data(&self, ticket_id: i64) -> Result<(), StorageError>;
async fn get_all_verified_tickets_with_sn(&self) -> Result<Vec<VerifiedTicket>, StorageError>;
async fn get_all_proposed_tickets_with_sn(
&self,
proposal_id: u32,
) -> Result<Vec<VerifiedTicket>, StorageError>;
async fn insert_redemption_proposal(
&self,
tickets: &[VerifiedTicket],
proposal_id: u32,
created_at: OffsetDateTime,
) -> Result<(), StorageError>;
async fn clear_post_proposal_data(
&self,
proposal_id: u32,
resolved_at: OffsetDateTime,
rejected: bool,
) -> Result<(), StorageError>;
async fn latest_proposal(&self) -> Result<Option<RedemptionProposal>, StorageError>;
async fn get_all_unverified_tickets(&self) -> Result<Vec<ClientTicket>, StorageError>;
async fn get_all_unresolved_proposals(&self) -> Result<Vec<i64>, StorageError>;
async fn get_votes(&self, ticket_id: i64) -> Result<Vec<i64>, StorageError>;
async fn get_signers(&self, epoch_id: i64) -> Result<Vec<i64>, StorageError>;
/// Insert a wireguard peer in the storage.
///
/// # Arguments
///
/// * `peer`: wireguard peer data to be stored
/// * `suspended`: if peer exists, but it's currently suspended
async fn insert_wireguard_peer(
&self,
peer: &defguard_wireguard_rs::host::Peer,
suspended: bool,
) -> Result<(), StorageError>;
/// Tries to retrieve available bandwidth for the particular peer.
///
/// # Arguments
///
/// * `peer_public_key`: wireguard public key of the peer to be retrieved.
async fn get_wireguard_peer(
&self,
peer_public_key: &str,
) -> Result<Option<WireguardPeer>, StorageError>;
/// Retrieves all wireguard peers.
async fn get_all_wireguard_peers(&self) -> Result<Vec<WireguardPeer>, StorageError>;
/// Remove a wireguard peer from the storage.
///
/// # Arguments
///
/// * `peer_public_key`: wireguard public key of the peer to be removed.
async fn remove_wireguard_peer(&self, peer_public_key: &str) -> Result<(), StorageError>;
}
// note that clone here is fine as upon cloning the same underlying pool will be used
#[derive(Clone)]
pub struct PersistentStorage {
client_manager: ClientManager,
shared_key_manager: SharedKeysManager,
inbox_manager: InboxManager,
bandwidth_manager: BandwidthManager,
ticket_manager: TicketStorageManager,
wireguard_peer_manager: wireguard_peers::WgPeerManager,
}
impl PersistentStorage {
/// 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, StorageError> {
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 mut opts = sqlx::sqlite::SqliteConnectOptions::new()
.filename(database_path)
.create_if_missing(true);
// TODO: do we want auto_vacuum ?
opts.disable_statement_logging();
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(PersistentStorage {
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),
})
}
}
#[async_trait]
impl Storage for PersistentStorage {
async fn get_mixnet_client_id(
&self,
client_address: DestinationAddressBytes,
) -> Result<i64, StorageError> {
Ok(self
.shared_key_manager
.client_id(&client_address.as_base58_string())
.await?)
}
async fn insert_shared_keys(
&self,
client_address: DestinationAddressBytes,
shared_keys: &SharedGatewayKey,
) -> Result<i64, StorageError> {
let client_id = self
.client_manager
.insert_client(ClientType::EntryMixnet)
.await?;
self.shared_key_manager
.insert_shared_keys(
client_id,
client_address.as_base58_string(),
shared_keys.aes128_ctr_hmac_bs58().as_deref(),
shared_keys.aes256_gcm_siv().as_deref(),
)
.await?;
Ok(client_id)
}
async fn get_shared_keys(
&self,
client_address: DestinationAddressBytes,
) -> Result<Option<PersistedSharedKeys>, StorageError> {
let keys = self
.shared_key_manager
.get_shared_keys(&client_address.as_base58_string())
.await?;
Ok(keys)
}
#[allow(dead_code)]
async fn remove_shared_keys(
&self,
client_address: DestinationAddressBytes,
) -> Result<(), StorageError> {
self.shared_key_manager
.remove_shared_keys(&client_address.as_base58_string())
.await?;
Ok(())
}
async fn get_client(&self, client_id: i64) -> Result<Option<Client>, StorageError> {
let client = self.client_manager.get_client(client_id).await?;
Ok(client)
}
async fn store_message(
&self,
client_address: DestinationAddressBytes,
message: Vec<u8>,
) -> Result<(), StorageError> {
self.inbox_manager
.insert_message(&client_address.as_base58_string(), message)
.await?;
Ok(())
}
async fn retrieve_messages(
&self,
client_address: DestinationAddressBytes,
start_after: Option<i64>,
) -> Result<(Vec<StoredMessage>, Option<i64>), StorageError> {
let messages = self
.inbox_manager
.get_messages(&client_address.as_base58_string(), start_after)
.await?;
Ok(messages)
}
async fn remove_messages(&self, ids: Vec<i64>) -> Result<(), StorageError> {
for id in ids {
self.inbox_manager.remove_message(id).await?;
}
Ok(())
}
async fn create_bandwidth_entry(&self, client_id: i64) -> Result<(), StorageError> {
self.bandwidth_manager.insert_new_client(client_id).await?;
Ok(())
}
async fn set_expiration(
&self,
client_id: i64,
expiration: OffsetDateTime,
) -> Result<(), StorageError> {
self.bandwidth_manager
.set_expiration(client_id, expiration)
.await?;
Ok(())
}
async fn reset_bandwidth(&self, client_id: i64) -> Result<(), StorageError> {
self.bandwidth_manager.reset_bandwidth(client_id).await?;
Ok(())
}
async fn get_available_bandwidth(
&self,
client_id: i64,
) -> Result<Option<PersistedBandwidth>, StorageError> {
Ok(self
.bandwidth_manager
.get_available_bandwidth(client_id)
.await?)
}
async fn increase_bandwidth(&self, client_id: i64, amount: i64) -> Result<i64, StorageError> {
Ok(self
.bandwidth_manager
.increase_bandwidth(client_id, amount)
.await?)
}
async fn revoke_ticket_bandwidth(
&self,
ticket_id: i64,
amount: i64,
) -> Result<(), StorageError> {
Ok(self
.bandwidth_manager
.revoke_ticket_bandwidth(ticket_id, amount)
.await?)
}
async fn decrease_bandwidth(&self, client_id: i64, amount: i64) -> Result<i64, StorageError> {
Ok(self
.bandwidth_manager
.decrease_bandwidth(client_id, amount)
.await?)
}
async fn insert_epoch_signers(
&self,
epoch_id: i64,
signer_ids: Vec<i64>,
) -> Result<(), StorageError> {
self.ticket_manager
.insert_ecash_signers(epoch_id, signer_ids)
.await?;
Ok(())
}
async fn insert_received_ticket(
&self,
client_id: i64,
received_at: OffsetDateTime,
serial_number: Vec<u8>,
data: Vec<u8>,
) -> Result<i64, StorageError> {
// 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)
}
async fn contains_ticket(&self, serial_number: &[u8]) -> Result<bool, StorageError> {
Ok(self.ticket_manager.has_ticket_data(serial_number).await?)
}
async fn insert_ticket_verification(
&self,
ticket_id: i64,
signer_id: i64,
verified_at: OffsetDateTime,
accepted: bool,
) -> Result<(), StorageError> {
self.ticket_manager
.insert_ticket_verification(ticket_id, signer_id, verified_at, accepted)
.await?;
Ok(())
}
async fn update_rejected_ticket(&self, ticket_id: i64) -> Result<(), StorageError> {
// 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(())
}
async fn update_verified_ticket(&self, ticket_id: i64) -> Result<(), StorageError> {
// 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(())
}
async fn remove_verified_ticket_binary_data(&self, ticket_id: i64) -> Result<(), StorageError> {
self.ticket_manager
.remove_binary_ticket_data(ticket_id)
.await?;
Ok(())
}
async fn get_all_verified_tickets_with_sn(&self) -> Result<Vec<VerifiedTicket>, StorageError> {
Ok(self
.ticket_manager
.get_all_verified_tickets_with_sn()
.await?)
}
async fn get_all_proposed_tickets_with_sn(
&self,
proposal_id: u32,
) -> Result<Vec<VerifiedTicket>, StorageError> {
Ok(self
.ticket_manager
.get_all_proposed_tickets_with_sn(proposal_id as i64)
.await?)
}
async fn insert_redemption_proposal(
&self,
tickets: &[VerifiedTicket],
proposal_id: u32,
created_at: OffsetDateTime,
) -> Result<(), StorageError> {
// 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(())
}
async fn clear_post_proposal_data(
&self,
proposal_id: u32,
resolved_at: OffsetDateTime,
rejected: bool,
) -> Result<(), StorageError> {
// 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(())
}
async fn latest_proposal(&self) -> Result<Option<RedemptionProposal>, StorageError> {
Ok(self.ticket_manager.get_latest_redemption_proposal().await?)
}
async fn get_all_unverified_tickets(&self) -> Result<Vec<ClientTicket>, StorageError> {
self.ticket_manager
.get_unverified_tickets()
.await?
.into_iter()
.map(TryInto::try_into)
.collect()
}
async fn get_all_unresolved_proposals(&self) -> Result<Vec<i64>, StorageError> {
Ok(self
.ticket_manager
.get_all_unresolved_redemption_proposal_ids()
.await?)
}
async fn get_votes(&self, ticket_id: i64) -> Result<Vec<i64>, StorageError> {
Ok(self
.ticket_manager
.get_verification_votes(ticket_id)
.await?)
}
async fn get_signers(&self, epoch_id: i64) -> Result<Vec<i64>, StorageError> {
Ok(self.ticket_manager.get_epoch_signers(epoch_id).await?)
}
async fn insert_wireguard_peer(
&self,
peer: &defguard_wireguard_rs::host::Peer,
suspended: bool,
) -> Result<(), StorageError> {
let mut peer = WireguardPeer::from(peer.clone());
peer.suspended = suspended;
self.wireguard_peer_manager.insert_peer(&peer).await?;
Ok(())
}
async fn get_wireguard_peer(
&self,
peer_public_key: &str,
) -> Result<Option<WireguardPeer>, StorageError> {
let peer = self
.wireguard_peer_manager
.retrieve_peer(peer_public_key)
.await?;
Ok(peer)
}
async fn get_all_wireguard_peers(&self) -> Result<Vec<WireguardPeer>, StorageError> {
let ret = self.wireguard_peer_manager.retrieve_all_peers().await?;
Ok(ret)
}
async fn remove_wireguard_peer(&self, peer_public_key: &str) -> Result<(), StorageError> {
self.wireguard_peer_manager
.remove_peer(peer_public_key)
.await?;
Ok(())
}
}