//! CDK Database use std::collections::HashMap; use async_trait::async_trait; use cashu::quote_id::QuoteId; use cashu::Amount; use super::Error; use crate::mint::{self, MintKeySetInfo, MintQuote as MintMintQuote}; use crate::nuts::{ BlindSignature, BlindedMessage, CurrencyUnit, Id, MeltQuoteState, Proof, Proofs, PublicKey, State, }; use crate::payment::PaymentIdentifier; #[cfg(feature = "auth")] mod auth; #[cfg(feature = "test")] pub mod test; #[cfg(feature = "auth")] pub use auth::{DynMintAuthDatabase, MintAuthDatabase, MintAuthTransaction}; /// Valid ASCII characters for namespace and key strings in KV store pub const KVSTORE_NAMESPACE_KEY_ALPHABET: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"; /// Maximum length for namespace and key strings in KV store pub const KVSTORE_NAMESPACE_KEY_MAX_LEN: usize = 120; /// Validates that a string contains only valid KV store characters and is within length limits pub fn validate_kvstore_string(s: &str) -> Result<(), Error> { if s.len() > KVSTORE_NAMESPACE_KEY_MAX_LEN { return Err(Error::KVStoreInvalidKey(format!( "{KVSTORE_NAMESPACE_KEY_MAX_LEN} exceeds maximum length of key characters" ))); } if !s .chars() .all(|c| KVSTORE_NAMESPACE_KEY_ALPHABET.contains(c)) { return Err(Error::KVStoreInvalidKey("key contains invalid characters. Only ASCII letters, numbers, underscore, and hyphen are allowed".to_string())); } Ok(()) } /// Validates namespace and key parameters for KV store operations pub fn validate_kvstore_params( primary_namespace: &str, secondary_namespace: &str, key: &str, ) -> Result<(), Error> { // Validate primary namespace validate_kvstore_string(primary_namespace)?; // Validate secondary namespace validate_kvstore_string(secondary_namespace)?; // Validate key validate_kvstore_string(key)?; // Check empty namespace rules if primary_namespace.is_empty() && !secondary_namespace.is_empty() { return Err(Error::KVStoreInvalidKey( "If primary_namespace is empty, secondary_namespace must also be empty".to_string(), )); } // Check for potential collisions between keys and namespaces in the same namespace let namespace_key = format!("{primary_namespace}/{secondary_namespace}"); if key == primary_namespace || key == secondary_namespace || key == namespace_key { return Err(Error::KVStoreInvalidKey(format!( "Key '{key}' conflicts with namespace names" ))); } Ok(()) } /// Information about a melt request stored in the database #[derive(Debug, Clone, PartialEq, Eq)] pub struct MeltRequestInfo { /// Total amount of all input proofs in the melt request pub inputs_amount: Amount, /// Fee amount associated with the input proofs pub inputs_fee: Amount, /// Blinded messages for change outputs pub change_outputs: Vec, } /// KeysDatabaseWriter #[async_trait] pub trait KeysDatabaseTransaction<'a, Error>: DbTransactionFinalizer { /// Add Active Keyset async fn set_active_keyset(&mut self, unit: CurrencyUnit, id: Id) -> Result<(), Error>; /// Add [`MintKeySetInfo`] async fn add_keyset_info(&mut self, keyset: MintKeySetInfo) -> Result<(), Error>; } /// Mint Keys Database trait #[async_trait] pub trait KeysDatabase { /// Mint Keys Database Error type Err: Into + From; /// Beings a transaction async fn begin_transaction<'a>( &'a self, ) -> Result + Send + Sync + 'a>, Error>; /// Get Active Keyset async fn get_active_keyset_id(&self, unit: &CurrencyUnit) -> Result, Self::Err>; /// Get all Active Keyset async fn get_active_keysets(&self) -> Result, Self::Err>; /// Get [`MintKeySetInfo`] async fn get_keyset_info(&self, id: &Id) -> Result, Self::Err>; /// Get [`MintKeySetInfo`]s async fn get_keyset_infos(&self) -> Result, Self::Err>; } /// Mint Quote Database writer trait #[async_trait] pub trait QuotesTransaction<'a> { /// Mint Quotes Database Error type Err: Into + From; /// Add melt_request with quote_id, inputs_amount, and inputs_fee async fn add_melt_request( &mut self, quote_id: &QuoteId, inputs_amount: Amount, inputs_fee: Amount, ) -> Result<(), Self::Err>; /// Add blinded_messages for a quote_id async fn add_blinded_messages( &mut self, quote_id: Option<&QuoteId>, blinded_messages: &[BlindedMessage], ) -> Result<(), Self::Err>; /// Delete blinded_messages by their blinded secrets async fn delete_blinded_messages( &mut self, blinded_secrets: &[PublicKey], ) -> Result<(), Self::Err>; /// Get melt_request and associated blinded_messages by quote_id async fn get_melt_request_and_blinded_messages( &mut self, quote_id: &QuoteId, ) -> Result, Self::Err>; /// Delete melt_request and associated blinded_messages by quote_id async fn delete_melt_request(&mut self, quote_id: &QuoteId) -> Result<(), Self::Err>; /// Get [`MintMintQuote`] and lock it for update in this transaction async fn get_mint_quote( &mut self, quote_id: &QuoteId, ) -> Result, Self::Err>; /// Add [`MintMintQuote`] async fn add_mint_quote(&mut self, quote: MintMintQuote) -> Result<(), Self::Err>; /// Increment amount paid [`MintMintQuote`] async fn increment_mint_quote_amount_paid( &mut self, quote_id: &QuoteId, amount_paid: Amount, payment_id: String, ) -> Result; /// Increment amount paid [`MintMintQuote`] async fn increment_mint_quote_amount_issued( &mut self, quote_id: &QuoteId, amount_issued: Amount, ) -> Result; /// Get [`mint::MeltQuote`] and lock it for update in this transaction async fn get_melt_quote( &mut self, quote_id: &QuoteId, ) -> Result, Self::Err>; /// Add [`mint::MeltQuote`] async fn add_melt_quote(&mut self, quote: mint::MeltQuote) -> Result<(), Self::Err>; /// Updates the request lookup id for a melt quote async fn update_melt_quote_request_lookup_id( &mut self, quote_id: &QuoteId, new_request_lookup_id: &PaymentIdentifier, ) -> Result<(), Self::Err>; /// Update [`mint::MeltQuote`] state /// /// It is expected for this function to fail if the state is already set to the new state async fn update_melt_quote_state( &mut self, quote_id: &QuoteId, new_state: MeltQuoteState, payment_proof: Option, ) -> Result<(MeltQuoteState, mint::MeltQuote), Self::Err>; /// Get all [`MintMintQuote`]s and lock it for update in this transaction async fn get_mint_quote_by_request( &mut self, request: &str, ) -> Result, Self::Err>; /// Get all [`MintMintQuote`]s async fn get_mint_quote_by_request_lookup_id( &mut self, request_lookup_id: &PaymentIdentifier, ) -> Result, Self::Err>; } /// Mint Quote Database trait #[async_trait] pub trait QuotesDatabase { /// Mint Quotes Database Error type Err: Into + From; /// Get [`MintMintQuote`] async fn get_mint_quote(&self, quote_id: &QuoteId) -> Result, Self::Err>; /// Get all [`MintMintQuote`]s async fn get_mint_quote_by_request( &self, request: &str, ) -> Result, Self::Err>; /// Get all [`MintMintQuote`]s async fn get_mint_quote_by_request_lookup_id( &self, request_lookup_id: &PaymentIdentifier, ) -> Result, Self::Err>; /// Get Mint Quotes async fn get_mint_quotes(&self) -> Result, Self::Err>; /// Get [`mint::MeltQuote`] async fn get_melt_quote( &self, quote_id: &QuoteId, ) -> Result, Self::Err>; /// Get all [`mint::MeltQuote`]s async fn get_melt_quotes(&self) -> Result, Self::Err>; } /// Mint Proof Transaction trait #[async_trait] pub trait ProofsTransaction<'a> { /// Mint Proof Database Error type Err: Into + From; /// Add [`Proofs`] /// /// Adds proofs to the database. The database should error if the proof already exits, with a /// `AttemptUpdateSpentProof` if the proof is already spent or a `Duplicate` error otherwise. async fn add_proofs( &mut self, proof: Proofs, quote_id: Option, ) -> Result<(), Self::Err>; /// Updates the proofs to a given states and return the previous states async fn update_proofs_states( &mut self, ys: &[PublicKey], proofs_state: State, ) -> Result>, Self::Err>; /// Remove [`Proofs`] async fn remove_proofs( &mut self, ys: &[PublicKey], quote_id: Option, ) -> Result<(), Self::Err>; /// Get ys by quote id async fn get_proof_ys_by_quote_id( &self, quote_id: &QuoteId, ) -> Result, Self::Err>; } /// Mint Proof Database trait #[async_trait] pub trait ProofsDatabase { /// Mint Proof Database Error type Err: Into + From; /// Get [`Proofs`] by ys async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result>, Self::Err>; /// Get ys by quote id async fn get_proof_ys_by_quote_id( &self, quote_id: &QuoteId, ) -> Result, Self::Err>; /// Get [`Proofs`] state async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result>, Self::Err>; /// Get [`Proofs`] by state async fn get_proofs_by_keyset_id( &self, keyset_id: &Id, ) -> Result<(Proofs, Vec>), Self::Err>; } #[async_trait] /// Mint Signatures Transaction trait pub trait SignaturesTransaction<'a> { /// Mint Signature Database Error type Err: Into + From; /// Add [`BlindSignature`] async fn add_blind_signatures( &mut self, blinded_messages: &[PublicKey], blind_signatures: &[BlindSignature], quote_id: Option, ) -> Result<(), Self::Err>; /// Get [`BlindSignature`]s async fn get_blind_signatures( &mut self, blinded_messages: &[PublicKey], ) -> Result>, Self::Err>; } #[async_trait] /// Mint Signatures Database trait pub trait SignaturesDatabase { /// Mint Signature Database Error type Err: Into + From; /// Get [`BlindSignature`]s async fn get_blind_signatures( &self, blinded_messages: &[PublicKey], ) -> Result>, Self::Err>; /// Get [`BlindSignature`]s for keyset_id async fn get_blind_signatures_for_keyset( &self, keyset_id: &Id, ) -> Result, Self::Err>; /// Get [`BlindSignature`]s for quote async fn get_blind_signatures_for_quote( &self, quote_id: &QuoteId, ) -> Result, Self::Err>; } #[async_trait] /// Commit and Rollback pub trait DbTransactionFinalizer { /// Mint Signature Database Error type Err: Into + From; /// Commits all the changes into the database async fn commit(self: Box) -> Result<(), Self::Err>; /// Rollbacks the write transaction async fn rollback(self: Box) -> Result<(), Self::Err>; } /// Key-Value Store Transaction trait #[async_trait] pub trait KVStoreTransaction<'a, Error>: DbTransactionFinalizer { /// Read value from key-value store async fn kv_read( &mut self, primary_namespace: &str, secondary_namespace: &str, key: &str, ) -> Result>, Error>; /// Write value to key-value store async fn kv_write( &mut self, primary_namespace: &str, secondary_namespace: &str, key: &str, value: &[u8], ) -> Result<(), Error>; /// Remove value from key-value store async fn kv_remove( &mut self, primary_namespace: &str, secondary_namespace: &str, key: &str, ) -> Result<(), Error>; /// List keys in a namespace async fn kv_list( &mut self, primary_namespace: &str, secondary_namespace: &str, ) -> Result, Error>; } /// Base database writer pub trait Transaction<'a, Error>: DbTransactionFinalizer + QuotesTransaction<'a, Err = Error> + SignaturesTransaction<'a, Err = Error> + ProofsTransaction<'a, Err = Error> + KVStoreTransaction<'a, Error> { } /// Key-Value Store Database trait #[async_trait] pub trait KVStoreDatabase { /// KV Store Database Error type Err: Into + From; /// Read value from key-value store async fn kv_read( &self, primary_namespace: &str, secondary_namespace: &str, key: &str, ) -> Result>, Self::Err>; /// List keys in a namespace async fn kv_list( &self, primary_namespace: &str, secondary_namespace: &str, ) -> Result, Self::Err>; } /// Key-Value Store Database trait #[async_trait] pub trait KVStore: KVStoreDatabase { /// Beings a KV transaction async fn begin_transaction<'a>( &'a self, ) -> Result + Send + Sync + 'a>, Error>; } /// Type alias for Mint Kv store pub type DynMintKVStore = std::sync::Arc + Send + Sync>; /// Mint Database trait #[async_trait] pub trait Database: KVStoreDatabase + QuotesDatabase + ProofsDatabase + SignaturesDatabase { /// Beings a transaction async fn begin_transaction<'a>( &'a self, ) -> Result + Send + Sync + 'a>, Error>; } /// Type alias for Mint Database pub type DynMintDatabase = std::sync::Arc + Send + Sync>;