cdk_common/database/mint/
mod.rs

1//! CDK Database
2
3use std::collections::HashMap;
4
5use async_trait::async_trait;
6use cashu::quote_id::QuoteId;
7use cashu::Amount;
8
9use super::Error;
10use crate::mint::{self, MintKeySetInfo, MintQuote as MintMintQuote, Operation};
11use crate::nuts::{
12    BlindSignature, BlindedMessage, CurrencyUnit, Id, MeltQuoteState, Proof, Proofs, PublicKey,
13    State,
14};
15use crate::payment::PaymentIdentifier;
16
17#[cfg(feature = "auth")]
18mod auth;
19
20#[cfg(feature = "test")]
21pub mod test;
22
23#[cfg(feature = "auth")]
24pub use auth::{DynMintAuthDatabase, MintAuthDatabase, MintAuthTransaction};
25
26/// Valid ASCII characters for namespace and key strings in KV store
27pub const KVSTORE_NAMESPACE_KEY_ALPHABET: &str =
28    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-";
29
30/// Maximum length for namespace and key strings in KV store
31pub const KVSTORE_NAMESPACE_KEY_MAX_LEN: usize = 120;
32
33/// Validates that a string contains only valid KV store characters and is within length limits
34pub fn validate_kvstore_string(s: &str) -> Result<(), Error> {
35    if s.len() > KVSTORE_NAMESPACE_KEY_MAX_LEN {
36        return Err(Error::KVStoreInvalidKey(format!(
37            "{KVSTORE_NAMESPACE_KEY_MAX_LEN} exceeds maximum length of key characters"
38        )));
39    }
40
41    if !s
42        .chars()
43        .all(|c| KVSTORE_NAMESPACE_KEY_ALPHABET.contains(c))
44    {
45        return Err(Error::KVStoreInvalidKey("key contains invalid characters. Only ASCII letters, numbers, underscore, and hyphen are allowed".to_string()));
46    }
47
48    Ok(())
49}
50
51/// Validates namespace and key parameters for KV store operations
52pub fn validate_kvstore_params(
53    primary_namespace: &str,
54    secondary_namespace: &str,
55    key: &str,
56) -> Result<(), Error> {
57    // Validate primary namespace
58    validate_kvstore_string(primary_namespace)?;
59
60    // Validate secondary namespace
61    validate_kvstore_string(secondary_namespace)?;
62
63    // Validate key
64    validate_kvstore_string(key)?;
65
66    // Check empty namespace rules
67    if primary_namespace.is_empty() && !secondary_namespace.is_empty() {
68        return Err(Error::KVStoreInvalidKey(
69            "If primary_namespace is empty, secondary_namespace must also be empty".to_string(),
70        ));
71    }
72
73    // Check for potential collisions between keys and namespaces in the same namespace
74    let namespace_key = format!("{primary_namespace}/{secondary_namespace}");
75    if key == primary_namespace || key == secondary_namespace || key == namespace_key {
76        return Err(Error::KVStoreInvalidKey(format!(
77            "Key '{key}' conflicts with namespace names"
78        )));
79    }
80
81    Ok(())
82}
83
84/// Information about a melt request stored in the database
85#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct MeltRequestInfo {
87    /// Total amount of all input proofs in the melt request
88    pub inputs_amount: Amount,
89    /// Fee amount associated with the input proofs
90    pub inputs_fee: Amount,
91    /// Blinded messages for change outputs
92    pub change_outputs: Vec<BlindedMessage>,
93}
94
95/// KeysDatabaseWriter
96#[async_trait]
97pub trait KeysDatabaseTransaction<'a, Error>: DbTransactionFinalizer<Err = Error> {
98    /// Add Active Keyset
99    async fn set_active_keyset(&mut self, unit: CurrencyUnit, id: Id) -> Result<(), Error>;
100
101    /// Add [`MintKeySetInfo`]
102    async fn add_keyset_info(&mut self, keyset: MintKeySetInfo) -> Result<(), Error>;
103}
104
105/// Mint Keys Database trait
106#[async_trait]
107pub trait KeysDatabase {
108    /// Mint Keys Database Error
109    type Err: Into<Error> + From<Error>;
110
111    /// Begins a transaction
112    async fn begin_transaction<'a>(
113        &'a self,
114    ) -> Result<Box<dyn KeysDatabaseTransaction<'a, Self::Err> + Send + Sync + 'a>, Error>;
115
116    /// Get Active Keyset
117    async fn get_active_keyset_id(&self, unit: &CurrencyUnit) -> Result<Option<Id>, Self::Err>;
118
119    /// Get all Active Keyset
120    async fn get_active_keysets(&self) -> Result<HashMap<CurrencyUnit, Id>, Self::Err>;
121
122    /// Get [`MintKeySetInfo`]
123    async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err>;
124
125    /// Get [`MintKeySetInfo`]s
126    async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err>;
127}
128
129/// Mint Quote Database writer trait
130#[async_trait]
131pub trait QuotesTransaction<'a> {
132    /// Mint Quotes Database Error
133    type Err: Into<Error> + From<Error>;
134
135    /// Add melt_request with quote_id, inputs_amount, and inputs_fee
136    async fn add_melt_request(
137        &mut self,
138        quote_id: &QuoteId,
139        inputs_amount: Amount,
140        inputs_fee: Amount,
141    ) -> Result<(), Self::Err>;
142
143    /// Add blinded_messages for a quote_id
144    async fn add_blinded_messages(
145        &mut self,
146        quote_id: Option<&QuoteId>,
147        blinded_messages: &[BlindedMessage],
148        operation: &Operation,
149    ) -> Result<(), Self::Err>;
150
151    /// Delete blinded_messages by their blinded secrets
152    async fn delete_blinded_messages(
153        &mut self,
154        blinded_secrets: &[PublicKey],
155    ) -> Result<(), Self::Err>;
156
157    /// Get melt_request and associated blinded_messages by quote_id
158    async fn get_melt_request_and_blinded_messages(
159        &mut self,
160        quote_id: &QuoteId,
161    ) -> Result<Option<MeltRequestInfo>, Self::Err>;
162
163    /// Delete melt_request and associated blinded_messages by quote_id
164    async fn delete_melt_request(&mut self, quote_id: &QuoteId) -> Result<(), Self::Err>;
165
166    /// Get [`MintMintQuote`] and lock it for update in this transaction
167    async fn get_mint_quote(
168        &mut self,
169        quote_id: &QuoteId,
170    ) -> Result<Option<MintMintQuote>, Self::Err>;
171    /// Add [`MintMintQuote`]
172    async fn add_mint_quote(&mut self, quote: MintMintQuote) -> Result<(), Self::Err>;
173    /// Increment amount paid [`MintMintQuote`]
174    async fn increment_mint_quote_amount_paid(
175        &mut self,
176        quote_id: &QuoteId,
177        amount_paid: Amount,
178        payment_id: String,
179    ) -> Result<Amount, Self::Err>;
180    /// Increment amount paid [`MintMintQuote`]
181    async fn increment_mint_quote_amount_issued(
182        &mut self,
183        quote_id: &QuoteId,
184        amount_issued: Amount,
185    ) -> Result<Amount, Self::Err>;
186
187    /// Get [`mint::MeltQuote`] and lock it for update in this transaction
188    async fn get_melt_quote(
189        &mut self,
190        quote_id: &QuoteId,
191    ) -> Result<Option<mint::MeltQuote>, Self::Err>;
192    /// Add [`mint::MeltQuote`]
193    async fn add_melt_quote(&mut self, quote: mint::MeltQuote) -> Result<(), Self::Err>;
194
195    /// Updates the request lookup id for a melt quote
196    async fn update_melt_quote_request_lookup_id(
197        &mut self,
198        quote_id: &QuoteId,
199        new_request_lookup_id: &PaymentIdentifier,
200    ) -> Result<(), Self::Err>;
201
202    /// Update [`mint::MeltQuote`] state
203    ///
204    /// It is expected for this function to fail if the state is already set to the new state
205    async fn update_melt_quote_state(
206        &mut self,
207        quote_id: &QuoteId,
208        new_state: MeltQuoteState,
209        payment_proof: Option<String>,
210    ) -> Result<(MeltQuoteState, mint::MeltQuote), Self::Err>;
211
212    /// Get all [`MintMintQuote`]s and lock it for update in this transaction
213    async fn get_mint_quote_by_request(
214        &mut self,
215        request: &str,
216    ) -> Result<Option<MintMintQuote>, Self::Err>;
217
218    /// Get all [`MintMintQuote`]s
219    async fn get_mint_quote_by_request_lookup_id(
220        &mut self,
221        request_lookup_id: &PaymentIdentifier,
222    ) -> Result<Option<MintMintQuote>, Self::Err>;
223}
224
225/// Mint Quote Database trait
226#[async_trait]
227pub trait QuotesDatabase {
228    /// Mint Quotes Database Error
229    type Err: Into<Error> + From<Error>;
230
231    /// Get [`MintMintQuote`]
232    async fn get_mint_quote(&self, quote_id: &QuoteId) -> Result<Option<MintMintQuote>, Self::Err>;
233
234    /// Get all [`MintMintQuote`]s
235    async fn get_mint_quote_by_request(
236        &self,
237        request: &str,
238    ) -> Result<Option<MintMintQuote>, Self::Err>;
239    /// Get all [`MintMintQuote`]s
240    async fn get_mint_quote_by_request_lookup_id(
241        &self,
242        request_lookup_id: &PaymentIdentifier,
243    ) -> Result<Option<MintMintQuote>, Self::Err>;
244    /// Get Mint Quotes
245    async fn get_mint_quotes(&self) -> Result<Vec<MintMintQuote>, Self::Err>;
246    /// Get [`mint::MeltQuote`]
247    async fn get_melt_quote(
248        &self,
249        quote_id: &QuoteId,
250    ) -> Result<Option<mint::MeltQuote>, Self::Err>;
251    /// Get all [`mint::MeltQuote`]s
252    async fn get_melt_quotes(&self) -> Result<Vec<mint::MeltQuote>, Self::Err>;
253}
254
255/// Mint Proof Transaction trait
256#[async_trait]
257pub trait ProofsTransaction<'a> {
258    /// Mint Proof Database Error
259    type Err: Into<Error> + From<Error>;
260
261    /// Add  [`Proofs`]
262    ///
263    /// Adds proofs to the database. The database should error if the proof already exits, with a
264    /// `AttemptUpdateSpentProof` if the proof is already spent or a `Duplicate` error otherwise.
265    async fn add_proofs(
266        &mut self,
267        proof: Proofs,
268        quote_id: Option<QuoteId>,
269        operation: &Operation,
270    ) -> Result<(), Self::Err>;
271    /// Updates the proofs to a given states and return the previous states
272    async fn update_proofs_states(
273        &mut self,
274        ys: &[PublicKey],
275        proofs_state: State,
276    ) -> Result<Vec<Option<State>>, Self::Err>;
277
278    /// Remove [`Proofs`]
279    async fn remove_proofs(
280        &mut self,
281        ys: &[PublicKey],
282        quote_id: Option<QuoteId>,
283    ) -> Result<(), Self::Err>;
284
285    /// Get ys by quote id
286    async fn get_proof_ys_by_quote_id(
287        &self,
288        quote_id: &QuoteId,
289    ) -> Result<Vec<PublicKey>, Self::Err>;
290}
291
292/// Mint Proof Database trait
293#[async_trait]
294pub trait ProofsDatabase {
295    /// Mint Proof Database Error
296    type Err: Into<Error> + From<Error>;
297
298    /// Get [`Proofs`] by ys
299    async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, Self::Err>;
300    /// Get ys by quote id
301    async fn get_proof_ys_by_quote_id(
302        &self,
303        quote_id: &QuoteId,
304    ) -> Result<Vec<PublicKey>, Self::Err>;
305    /// Get [`Proofs`] state
306    async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err>;
307
308    /// Get [`Proofs`] by state
309    async fn get_proofs_by_keyset_id(
310        &self,
311        keyset_id: &Id,
312    ) -> Result<(Proofs, Vec<Option<State>>), Self::Err>;
313
314    /// Get total proofs redeemed by keyset id
315    async fn get_total_redeemed(&self) -> Result<HashMap<Id, Amount>, Self::Err>;
316}
317
318#[async_trait]
319/// Mint Signatures Transaction trait
320pub trait SignaturesTransaction<'a> {
321    /// Mint Signature Database Error
322    type Err: Into<Error> + From<Error>;
323
324    /// Add [`BlindSignature`]
325    async fn add_blind_signatures(
326        &mut self,
327        blinded_messages: &[PublicKey],
328        blind_signatures: &[BlindSignature],
329        quote_id: Option<QuoteId>,
330    ) -> Result<(), Self::Err>;
331
332    /// Get [`BlindSignature`]s
333    async fn get_blind_signatures(
334        &mut self,
335        blinded_messages: &[PublicKey],
336    ) -> Result<Vec<Option<BlindSignature>>, Self::Err>;
337}
338
339#[async_trait]
340/// Mint Signatures Database trait
341pub trait SignaturesDatabase {
342    /// Mint Signature Database Error
343    type Err: Into<Error> + From<Error>;
344
345    /// Get [`BlindSignature`]s
346    async fn get_blind_signatures(
347        &self,
348        blinded_messages: &[PublicKey],
349    ) -> Result<Vec<Option<BlindSignature>>, Self::Err>;
350
351    /// Get [`BlindSignature`]s for keyset_id
352    async fn get_blind_signatures_for_keyset(
353        &self,
354        keyset_id: &Id,
355    ) -> Result<Vec<BlindSignature>, Self::Err>;
356
357    /// Get [`BlindSignature`]s for quote
358    async fn get_blind_signatures_for_quote(
359        &self,
360        quote_id: &QuoteId,
361    ) -> Result<Vec<BlindSignature>, Self::Err>;
362
363    /// Get total amount issued by keyset id
364    async fn get_total_issued(&self) -> Result<HashMap<Id, Amount>, Self::Err>;
365}
366
367#[async_trait]
368/// Saga Transaction trait
369pub trait SagaTransaction<'a> {
370    /// Saga Database Error
371    type Err: Into<Error> + From<Error>;
372
373    /// Get saga by operation_id
374    async fn get_saga(
375        &mut self,
376        operation_id: &uuid::Uuid,
377    ) -> Result<Option<mint::Saga>, Self::Err>;
378
379    /// Add saga
380    async fn add_saga(&mut self, saga: &mint::Saga) -> Result<(), Self::Err>;
381
382    /// Update saga state (only updates state and updated_at fields)
383    async fn update_saga(
384        &mut self,
385        operation_id: &uuid::Uuid,
386        new_state: mint::SagaStateEnum,
387    ) -> Result<(), Self::Err>;
388
389    /// Delete saga
390    async fn delete_saga(&mut self, operation_id: &uuid::Uuid) -> Result<(), Self::Err>;
391}
392
393#[async_trait]
394/// Saga Database trait
395pub trait SagaDatabase {
396    /// Saga Database Error
397    type Err: Into<Error> + From<Error>;
398
399    /// Get all incomplete sagas for a given operation kind
400    async fn get_incomplete_sagas(
401        &self,
402        operation_kind: mint::OperationKind,
403    ) -> Result<Vec<mint::Saga>, Self::Err>;
404}
405
406#[async_trait]
407/// Commit and Rollback
408pub trait DbTransactionFinalizer {
409    /// Mint Signature Database Error
410    type Err: Into<Error> + From<Error>;
411
412    /// Commits all the changes into the database
413    async fn commit(self: Box<Self>) -> Result<(), Self::Err>;
414
415    /// Rollbacks the write transaction
416    async fn rollback(self: Box<Self>) -> Result<(), Self::Err>;
417}
418
419/// Key-Value Store Transaction trait
420#[async_trait]
421pub trait KVStoreTransaction<'a, Error>: DbTransactionFinalizer<Err = Error> {
422    /// Read value from key-value store
423    async fn kv_read(
424        &mut self,
425        primary_namespace: &str,
426        secondary_namespace: &str,
427        key: &str,
428    ) -> Result<Option<Vec<u8>>, Error>;
429
430    /// Write value to key-value store
431    async fn kv_write(
432        &mut self,
433        primary_namespace: &str,
434        secondary_namespace: &str,
435        key: &str,
436        value: &[u8],
437    ) -> Result<(), Error>;
438
439    /// Remove value from key-value store
440    async fn kv_remove(
441        &mut self,
442        primary_namespace: &str,
443        secondary_namespace: &str,
444        key: &str,
445    ) -> Result<(), Error>;
446
447    /// List keys in a namespace
448    async fn kv_list(
449        &mut self,
450        primary_namespace: &str,
451        secondary_namespace: &str,
452    ) -> Result<Vec<String>, Error>;
453}
454
455/// Base database writer
456pub trait Transaction<'a, Error>:
457    DbTransactionFinalizer<Err = Error>
458    + QuotesTransaction<'a, Err = Error>
459    + SignaturesTransaction<'a, Err = Error>
460    + ProofsTransaction<'a, Err = Error>
461    + KVStoreTransaction<'a, Error>
462    + SagaTransaction<'a, Err = Error>
463{
464}
465
466/// Key-Value Store Database trait
467#[async_trait]
468pub trait KVStoreDatabase {
469    /// KV Store Database Error
470    type Err: Into<Error> + From<Error>;
471
472    /// Read value from key-value store
473    async fn kv_read(
474        &self,
475        primary_namespace: &str,
476        secondary_namespace: &str,
477        key: &str,
478    ) -> Result<Option<Vec<u8>>, Self::Err>;
479
480    /// List keys in a namespace
481    async fn kv_list(
482        &self,
483        primary_namespace: &str,
484        secondary_namespace: &str,
485    ) -> Result<Vec<String>, Self::Err>;
486}
487
488/// Key-Value Store Database trait
489#[async_trait]
490pub trait KVStore: KVStoreDatabase {
491    /// Begins a KV transaction
492    async fn begin_transaction<'a>(
493        &'a self,
494    ) -> Result<Box<dyn KVStoreTransaction<'a, Self::Err> + Send + Sync + 'a>, Error>;
495}
496
497/// Type alias for Mint Kv store
498pub type DynMintKVStore = std::sync::Arc<dyn KVStore<Err = Error> + Send + Sync>;
499
500/// Mint Database trait
501#[async_trait]
502pub trait Database<Error>:
503    KVStoreDatabase<Err = Error>
504    + QuotesDatabase<Err = Error>
505    + ProofsDatabase<Err = Error>
506    + SignaturesDatabase<Err = Error>
507    + SagaDatabase<Err = Error>
508{
509    /// Begins a transaction
510    async fn begin_transaction<'a>(
511        &'a self,
512    ) -> Result<Box<dyn Transaction<'a, Error> + Send + Sync + 'a>, Error>;
513}
514
515/// Type alias for Mint Database
516pub type DynMintDatabase = std::sync::Arc<dyn Database<Error> + Send + Sync>;