1use 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
26pub const KVSTORE_NAMESPACE_KEY_ALPHABET: &str =
28 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-";
29
30pub const KVSTORE_NAMESPACE_KEY_MAX_LEN: usize = 120;
32
33pub 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
51pub fn validate_kvstore_params(
53 primary_namespace: &str,
54 secondary_namespace: &str,
55 key: &str,
56) -> Result<(), Error> {
57 validate_kvstore_string(primary_namespace)?;
59
60 validate_kvstore_string(secondary_namespace)?;
62
63 validate_kvstore_string(key)?;
65
66 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 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#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct MeltRequestInfo {
87 pub inputs_amount: Amount,
89 pub inputs_fee: Amount,
91 pub change_outputs: Vec<BlindedMessage>,
93}
94
95#[async_trait]
97pub trait KeysDatabaseTransaction<'a, Error>: DbTransactionFinalizer<Err = Error> {
98 async fn set_active_keyset(&mut self, unit: CurrencyUnit, id: Id) -> Result<(), Error>;
100
101 async fn add_keyset_info(&mut self, keyset: MintKeySetInfo) -> Result<(), Error>;
103}
104
105#[async_trait]
107pub trait KeysDatabase {
108 type Err: Into<Error> + From<Error>;
110
111 async fn begin_transaction<'a>(
113 &'a self,
114 ) -> Result<Box<dyn KeysDatabaseTransaction<'a, Self::Err> + Send + Sync + 'a>, Error>;
115
116 async fn get_active_keyset_id(&self, unit: &CurrencyUnit) -> Result<Option<Id>, Self::Err>;
118
119 async fn get_active_keysets(&self) -> Result<HashMap<CurrencyUnit, Id>, Self::Err>;
121
122 async fn get_keyset_info(&self, id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err>;
124
125 async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err>;
127}
128
129#[async_trait]
131pub trait QuotesTransaction<'a> {
132 type Err: Into<Error> + From<Error>;
134
135 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 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 async fn delete_blinded_messages(
153 &mut self,
154 blinded_secrets: &[PublicKey],
155 ) -> Result<(), Self::Err>;
156
157 async fn get_melt_request_and_blinded_messages(
159 &mut self,
160 quote_id: &QuoteId,
161 ) -> Result<Option<MeltRequestInfo>, Self::Err>;
162
163 async fn delete_melt_request(&mut self, quote_id: &QuoteId) -> Result<(), Self::Err>;
165
166 async fn get_mint_quote(
168 &mut self,
169 quote_id: &QuoteId,
170 ) -> Result<Option<MintMintQuote>, Self::Err>;
171 async fn add_mint_quote(&mut self, quote: MintMintQuote) -> Result<(), Self::Err>;
173 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 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 async fn get_melt_quote(
189 &mut self,
190 quote_id: &QuoteId,
191 ) -> Result<Option<mint::MeltQuote>, Self::Err>;
192 async fn add_melt_quote(&mut self, quote: mint::MeltQuote) -> Result<(), Self::Err>;
194
195 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 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 async fn get_mint_quote_by_request(
214 &mut self,
215 request: &str,
216 ) -> Result<Option<MintMintQuote>, Self::Err>;
217
218 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#[async_trait]
227pub trait QuotesDatabase {
228 type Err: Into<Error> + From<Error>;
230
231 async fn get_mint_quote(&self, quote_id: &QuoteId) -> Result<Option<MintMintQuote>, Self::Err>;
233
234 async fn get_mint_quote_by_request(
236 &self,
237 request: &str,
238 ) -> Result<Option<MintMintQuote>, Self::Err>;
239 async fn get_mint_quote_by_request_lookup_id(
241 &self,
242 request_lookup_id: &PaymentIdentifier,
243 ) -> Result<Option<MintMintQuote>, Self::Err>;
244 async fn get_mint_quotes(&self) -> Result<Vec<MintMintQuote>, Self::Err>;
246 async fn get_melt_quote(
248 &self,
249 quote_id: &QuoteId,
250 ) -> Result<Option<mint::MeltQuote>, Self::Err>;
251 async fn get_melt_quotes(&self) -> Result<Vec<mint::MeltQuote>, Self::Err>;
253}
254
255#[async_trait]
257pub trait ProofsTransaction<'a> {
258 type Err: Into<Error> + From<Error>;
260
261 async fn add_proofs(
266 &mut self,
267 proof: Proofs,
268 quote_id: Option<QuoteId>,
269 operation: &Operation,
270 ) -> Result<(), Self::Err>;
271 async fn update_proofs_states(
273 &mut self,
274 ys: &[PublicKey],
275 proofs_state: State,
276 ) -> Result<Vec<Option<State>>, Self::Err>;
277
278 async fn remove_proofs(
280 &mut self,
281 ys: &[PublicKey],
282 quote_id: Option<QuoteId>,
283 ) -> Result<(), Self::Err>;
284
285 async fn get_proof_ys_by_quote_id(
287 &self,
288 quote_id: &QuoteId,
289 ) -> Result<Vec<PublicKey>, Self::Err>;
290}
291
292#[async_trait]
294pub trait ProofsDatabase {
295 type Err: Into<Error> + From<Error>;
297
298 async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, Self::Err>;
300 async fn get_proof_ys_by_quote_id(
302 &self,
303 quote_id: &QuoteId,
304 ) -> Result<Vec<PublicKey>, Self::Err>;
305 async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err>;
307
308 async fn get_proofs_by_keyset_id(
310 &self,
311 keyset_id: &Id,
312 ) -> Result<(Proofs, Vec<Option<State>>), Self::Err>;
313
314 async fn get_total_redeemed(&self) -> Result<HashMap<Id, Amount>, Self::Err>;
316}
317
318#[async_trait]
319pub trait SignaturesTransaction<'a> {
321 type Err: Into<Error> + From<Error>;
323
324 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 async fn get_blind_signatures(
334 &mut self,
335 blinded_messages: &[PublicKey],
336 ) -> Result<Vec<Option<BlindSignature>>, Self::Err>;
337}
338
339#[async_trait]
340pub trait SignaturesDatabase {
342 type Err: Into<Error> + From<Error>;
344
345 async fn get_blind_signatures(
347 &self,
348 blinded_messages: &[PublicKey],
349 ) -> Result<Vec<Option<BlindSignature>>, Self::Err>;
350
351 async fn get_blind_signatures_for_keyset(
353 &self,
354 keyset_id: &Id,
355 ) -> Result<Vec<BlindSignature>, Self::Err>;
356
357 async fn get_blind_signatures_for_quote(
359 &self,
360 quote_id: &QuoteId,
361 ) -> Result<Vec<BlindSignature>, Self::Err>;
362
363 async fn get_total_issued(&self) -> Result<HashMap<Id, Amount>, Self::Err>;
365}
366
367#[async_trait]
368pub trait SagaTransaction<'a> {
370 type Err: Into<Error> + From<Error>;
372
373 async fn get_saga(
375 &mut self,
376 operation_id: &uuid::Uuid,
377 ) -> Result<Option<mint::Saga>, Self::Err>;
378
379 async fn add_saga(&mut self, saga: &mint::Saga) -> Result<(), Self::Err>;
381
382 async fn update_saga(
384 &mut self,
385 operation_id: &uuid::Uuid,
386 new_state: mint::SagaStateEnum,
387 ) -> Result<(), Self::Err>;
388
389 async fn delete_saga(&mut self, operation_id: &uuid::Uuid) -> Result<(), Self::Err>;
391}
392
393#[async_trait]
394pub trait SagaDatabase {
396 type Err: Into<Error> + From<Error>;
398
399 async fn get_incomplete_sagas(
401 &self,
402 operation_kind: mint::OperationKind,
403 ) -> Result<Vec<mint::Saga>, Self::Err>;
404}
405
406#[async_trait]
407pub trait DbTransactionFinalizer {
409 type Err: Into<Error> + From<Error>;
411
412 async fn commit(self: Box<Self>) -> Result<(), Self::Err>;
414
415 async fn rollback(self: Box<Self>) -> Result<(), Self::Err>;
417}
418
419#[async_trait]
421pub trait KVStoreTransaction<'a, Error>: DbTransactionFinalizer<Err = Error> {
422 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 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 async fn kv_remove(
441 &mut self,
442 primary_namespace: &str,
443 secondary_namespace: &str,
444 key: &str,
445 ) -> Result<(), Error>;
446
447 async fn kv_list(
449 &mut self,
450 primary_namespace: &str,
451 secondary_namespace: &str,
452 ) -> Result<Vec<String>, Error>;
453}
454
455pub 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#[async_trait]
468pub trait KVStoreDatabase {
469 type Err: Into<Error> + From<Error>;
471
472 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 async fn kv_list(
482 &self,
483 primary_namespace: &str,
484 secondary_namespace: &str,
485 ) -> Result<Vec<String>, Self::Err>;
486}
487
488#[async_trait]
490pub trait KVStore: KVStoreDatabase {
491 async fn begin_transaction<'a>(
493 &'a self,
494 ) -> Result<Box<dyn KVStoreTransaction<'a, Self::Err> + Send + Sync + 'a>, Error>;
495}
496
497pub type DynMintKVStore = std::sync::Arc<dyn KVStore<Err = Error> + Send + Sync>;
499
500#[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 async fn begin_transaction<'a>(
511 &'a self,
512 ) -> Result<Box<dyn Transaction<'a, Error> + Send + Sync + 'a>, Error>;
513}
514
515pub type DynMintDatabase = std::sync::Arc<dyn Database<Error> + Send + Sync>;