Skip to content

Commit

Permalink
Gateway database modifications for different modes (#4868)
Browse files Browse the repository at this point in the history
* Gateway db modifications for different modes

* Add exit mixnet and replace whitespaces
  • Loading branch information
neacsu authored Sep 12, 2024
1 parent 60917ec commit 47303bc
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
* SPDX-License-Identifier: Apache-2.0
*/

CREATE TABLE clients (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
client_type TEXT NOT NULL CHECK(client_type IN ('entry_mixnet', 'exit_mixnet', 'entry_wireguard', 'exit_wireguard'))
);

INSERT INTO clients (id, client_type)
SELECT id, 'entry_mixnet'
FROM shared_keys;

CREATE TABLE shared_keys_tmp (
client_id INTEGER NOT NULL PRIMARY KEY REFERENCES clients(id),
client_address_bs58 TEXT NOT NULL UNIQUE,
derived_aes128_ctr_blake3_hmac_keys_bs58 TEXT NOT NULL
);

INSERT INTO shared_keys_tmp (client_id, client_address_bs58, derived_aes128_ctr_blake3_hmac_keys_bs58)
SELECT id as client_id, client_address_bs58, derived_aes128_ctr_blake3_hmac_keys_bs58 FROM shared_keys;

CREATE TABLE available_bandwidth_tmp (
client_id INTEGER NOT NULL PRIMARY KEY REFERENCES clients(id),
available INTEGER NOT NULL,
expiration TIMESTAMP WITHOUT TIME ZONE
);

INSERT INTO available_bandwidth_tmp
SELECT * FROM available_bandwidth;

DROP TABLE available_bandwidth;
ALTER TABLE available_bandwidth_tmp RENAME TO available_bandwidth;

CREATE TABLE received_ticket_tmp (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
client_id INTEGER NOT NULL REFERENCES clients(id),
received_at TIMESTAMP WITHOUT TIME ZONE NOT NULL,
rejected BOOLEAN
);

INSERT INTO received_ticket_tmp
SELECT * FROM received_ticket;

DROP INDEX received_ticket_index;
CREATE INDEX received_ticket_index ON received_ticket_tmp (received_at);

-- received tickets that are in the process of verifying
CREATE TABLE ticket_data_tmp (
ticket_id INTEGER NOT NULL PRIMARY KEY REFERENCES received_ticket_tmp(id),

-- serial_number, alongside the entire row, will get purged after redemption is complete
serial_number BLOB NOT NULL UNIQUE,

-- data will get purged after 80% of signers verifies it
data BLOB
);

INSERT INTO ticket_data_tmp
SELECT * FROM ticket_data;

DROP TABLE ticket_data;
ALTER TABLE ticket_data_tmp RENAME TO ticket_data;

-- result of a verification from a single signer (API)
CREATE TABLE ticket_verification_tmp (
ticket_id INTEGER NOT NULL REFERENCES received_ticket_tmp(id),
signer_id INTEGER NOT NULL,
verified_at TIMESTAMP WITHOUT TIME ZONE NOT NULL,
accepted BOOLEAN NOT NULL,

PRIMARY KEY (ticket_id, signer_id)
);

DROP INDEX ticket_verification_index;
CREATE INDEX ticket_verification_index ON ticket_verification_tmp (ticket_id);

DROP TABLE ticket_verification;
ALTER TABLE ticket_verification_tmp RENAME TO ticket_verification;

-- verified tickets that are yet to be redeemed
CREATE TABLE verified_tickets_tmp (
ticket_id INTEGER NOT NULL PRIMARY KEY REFERENCES received_ticket_tmp(id),
proposal_id INTEGER REFERENCES redemption_proposals(proposal_id)
);

DROP INDEX verified_tickets_index;
CREATE INDEX verified_tickets_index ON verified_tickets_tmp (proposal_id);

DROP TABLE verified_tickets;
ALTER TABLE verified_tickets_tmp RENAME TO verified_tickets;

DROP TABLE received_ticket;
ALTER TABLE received_ticket_tmp RENAME TO received_ticket;

DROP TABLE shared_keys;
ALTER TABLE shared_keys_tmp RENAME TO shared_keys;
89 changes: 89 additions & 0 deletions common/gateway-storage/src/clients.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2024 - Nym Technologies SA <contact@nymtech.net>
// SPDX-License-Identifier: GPL-3.0-only

use std::str::FromStr;

use crate::models::Client;

#[derive(Debug, PartialEq, sqlx::Type)]
#[sqlx(type_name = "TEXT")] // SQLite TEXT type
pub enum ClientType {
EntryMixnet,
ExitMixnet,
EntryWireguard,
ExitWireguard,
}

impl FromStr for ClientType {
type Err = &'static str;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"entry_mixnet" => Ok(ClientType::EntryMixnet),
"exit_mixnet" => Ok(ClientType::ExitMixnet),
"entry_wireguard" => Ok(ClientType::EntryWireguard),
"exit_wireguard" => Ok(ClientType::ExitWireguard),
_ => Err("Invalid client type"),
}
}
}

impl std::fmt::Display for ClientType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
ClientType::EntryMixnet => "entry_mixnet",
ClientType::ExitMixnet => "exit_mixnet",
ClientType::EntryWireguard => "entry_wireguard",
ClientType::ExitWireguard => "exit_wireguard",
};
write!(f, "{}", s)
}
}

#[derive(Clone)]
pub(crate) struct ClientManager {
connection_pool: sqlx::SqlitePool,
}

impl ClientManager {
/// Creates new instance of the `ClientManager` with the provided sqlite connection pool.
///
/// # Arguments
///
/// * `connection_pool`: database connection pool to use.
pub(crate) fn new(connection_pool: sqlx::SqlitePool) -> Self {
ClientManager { connection_pool }
}

/// Inserts new client to the storage, specifying its type.
///
/// # Arguments
///
/// * `client_type`: Type of the client that gets inserted
pub(crate) async fn insert_client(&self, client_type: ClientType) -> Result<i64, sqlx::Error> {
let client_id = sqlx::query!("INSERT INTO clients(client_type) VALUES (?)", client_type)
.execute(&self.connection_pool)
.await?
.last_insert_rowid();
Ok(client_id)
}

/// Tries to retrieve a particular client.
///
/// # Arguments
///
/// * `id`: The client id
pub(crate) async fn get_client(&self, id: i64) -> Result<Option<Client>, sqlx::Error> {
sqlx::query_as!(
Client,
r#"
SELECT id, client_type as "client_type: ClientType"
FROM clients
WHERE id = ?
"#,
id
)
.fetch_optional(&self.connection_pool)
.await
}
}
33 changes: 27 additions & 6 deletions common/gateway-storage/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@

use async_trait::async_trait;
use bandwidth::BandwidthManager;
use clients::{ClientManager, ClientType};
use error::StorageError;
use inboxes::InboxManager;
use models::{
PersistedBandwidth, PersistedSharedKeys, RedemptionProposal, StoredMessage, VerifiedTicket,
WireguardPeer,
Client, PersistedBandwidth, PersistedSharedKeys, RedemptionProposal, StoredMessage,
VerifiedTicket, WireguardPeer,
};
use nym_credentials_interface::ClientTicket;
use nym_gateway_requests::registration::handshake::SharedKeys;
Expand All @@ -20,6 +21,7 @@ use time::OffsetDateTime;
use tracing::{debug, error};

pub mod bandwidth;
mod clients;
pub mod error;
mod inboxes;
pub mod models;
Expand All @@ -29,7 +31,7 @@ mod wireguard_peers;

#[async_trait]
pub trait Storage: Send + Sync {
async fn get_client_id(
async fn get_mixnet_client_id(
&self,
client_address: DestinationAddressBytes,
) -> Result<i64, StorageError>;
Expand All @@ -39,7 +41,7 @@ pub trait Storage: Send + Sync {
///
/// # Arguments
///
/// * `client_address`: address of the client
/// * `client_address`: base58-encoded address of the client
/// * `shared_keys`: shared encryption (AES128CTR) and mac (hmac-blake3) derived shared keys to store.
async fn insert_shared_keys(
&self,
Expand Down Expand Up @@ -70,6 +72,14 @@ pub trait Storage: Send + Sync {
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
Expand Down Expand Up @@ -246,6 +256,7 @@ pub trait Storage: Send + Sync {
// 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,
Expand Down Expand Up @@ -294,6 +305,7 @@ impl PersistentStorage {

// 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),
Expand All @@ -305,7 +317,7 @@ impl PersistentStorage {

#[async_trait]
impl Storage for PersistentStorage {
async fn get_client_id(
async fn get_mixnet_client_id(
&self,
client_address: DestinationAddressBytes,
) -> Result<i64, StorageError> {
Expand All @@ -321,8 +333,12 @@ impl Storage for PersistentStorage {
shared_keys: &SharedKeys,
) -> Result<i64, StorageError> {
let client_id = self
.shared_key_manager
.client_manager
.insert_client(ClientType::EntryMixnet)
.await?;
self.shared_key_manager
.insert_shared_keys(
client_id,
client_address.as_base58_string(),
shared_keys.to_base58_string(),
)
Expand Down Expand Up @@ -352,6 +368,11 @@ impl Storage for PersistentStorage {
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,
Expand Down
7 changes: 6 additions & 1 deletion common/gateway-storage/src/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ use nym_credentials_interface::{AvailableBandwidth, ClientTicket, CredentialSpen
use sqlx::FromRow;
use time::OffsetDateTime;

pub struct Client {
pub id: i64,
pub client_type: crate::clients::ClientType,
}

pub struct PersistedSharedKeys {
#[allow(dead_code)]
pub id: i64,
pub client_id: i64,

#[allow(dead_code)]
pub client_address_bs58: String,
Expand Down
16 changes: 10 additions & 6 deletions common/gateway-storage/src/shared_keys.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ impl SharedKeysManager {

pub(crate) async fn client_id(&self, client_address_bs58: &str) -> Result<i64, sqlx::Error> {
let client_id = sqlx::query!(
"SELECT id FROM shared_keys WHERE client_address_bs58 = ?",
"SELECT client_id FROM shared_keys WHERE client_address_bs58 = ?",
client_address_bs58
)
.fetch_one(&self.connection_pool)
.await?
.id;
.client_id;
Ok(client_id)
}

Expand All @@ -34,26 +34,30 @@ impl SharedKeysManager {
///
/// # Arguments
///
/// * `shared_keys`: shared encryption (AES128CTR) and mac (hmac-blake3) derived shared keys to store.
/// * `client_id`: The client id for which the shared keys are stored
/// * `client_address_bs58`: base58-encoded address of the client
/// * `derived_aes128_ctr_blake3_hmac_keys_bs58`: shared encryption (AES128CTR) and mac (hmac-blake3) derived shared keys to store.
pub(crate) async fn insert_shared_keys(
&self,
client_id: i64,
client_address_bs58: String,
derived_aes128_ctr_blake3_hmac_keys_bs58: String,
) -> Result<i64, sqlx::Error> {
) -> Result<(), sqlx::Error> {
// https://stackoverflow.com/a/20310838
// we don't want to be using `INSERT OR REPLACE INTO` due to the foreign key on `available_bandwidth` if the entry already exists
sqlx::query!(
r#"
INSERT OR IGNORE INTO shared_keys(client_address_bs58, derived_aes128_ctr_blake3_hmac_keys_bs58) VALUES (?, ?);
INSERT OR IGNORE INTO shared_keys(client_id, client_address_bs58, derived_aes128_ctr_blake3_hmac_keys_bs58) VALUES (?, ?, ?);
UPDATE shared_keys SET derived_aes128_ctr_blake3_hmac_keys_bs58 = ? WHERE client_address_bs58 = ?
"#,
client_id,
client_address_bs58,
derived_aes128_ctr_blake3_hmac_keys_bs58,
derived_aes128_ctr_blake3_hmac_keys_bs58,
client_address_bs58,
).execute(&self.connection_pool).await?;

self.client_id(&client_address_bs58).await
Ok(())
}

/// Tries to retrieve shared keys stored for the particular client.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,11 @@ where
return Ok(InitialAuthResult::new_failed(Some(negotiated_protocol)));
};

let client_id = self.shared_state.storage.get_client_id(address).await?;
let client_id = self
.shared_state
.storage
.get_mixnet_client_id(address)
.await?;

let available_bandwidth: AvailableBandwidth = self
.shared_state
Expand Down

0 comments on commit 47303bc

Please sign in to comment.