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