1use std::cmp::Ordering;
4use std::collections::{HashMap, HashSet};
5use std::path::Path;
6use std::str::FromStr;
7use std::sync::Arc;
8
9use async_trait::async_trait;
10use cdk_common::common::{PaymentProcessorKey, QuoteTTL};
11use cdk_common::database::{
12 self, MintDatabase, MintKeysDatabase, MintProofsDatabase, MintQuotesDatabase,
13 MintSignaturesDatabase,
14};
15use cdk_common::dhke::hash_to_curve;
16use cdk_common::mint::{self, MintKeySetInfo, MintQuote};
17use cdk_common::nut00::ProofsMethods;
18use cdk_common::state::check_state_transition;
19use cdk_common::util::unix_time;
20use cdk_common::{
21 BlindSignature, CurrencyUnit, Id, MeltBolt11Request, MeltQuoteState, MintInfo, MintQuoteState,
22 Proof, Proofs, PublicKey, State,
23};
24use migrations::{migrate_01_to_02, migrate_04_to_05};
25use redb::{Database, MultimapTableDefinition, ReadableTable, TableDefinition};
26use uuid::Uuid;
27
28use super::error::Error;
29use crate::migrations::migrate_00_to_01;
30use crate::mint::migrations::{migrate_02_to_03, migrate_03_to_04};
31
32#[cfg(feature = "auth")]
33mod auth;
34mod migrations;
35
36#[cfg(feature = "auth")]
37pub use auth::MintRedbAuthDatabase;
38
39const ACTIVE_KEYSETS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("active_keysets");
40const KEYSETS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("keysets");
41const MINT_QUOTES_TABLE: TableDefinition<[u8; 16], &str> = TableDefinition::new("mint_quotes");
42const MELT_QUOTES_TABLE: TableDefinition<[u8; 16], &str> = TableDefinition::new("melt_quotes");
43const PROOFS_TABLE: TableDefinition<[u8; 33], &str> = TableDefinition::new("proofs");
44const PROOFS_STATE_TABLE: TableDefinition<[u8; 33], &str> = TableDefinition::new("proofs_state");
45const PROOF_CREATED_TIME: TableDefinition<[u8; 33], u64> =
46 TableDefinition::new("proof_created_time");
47const CONFIG_TABLE: TableDefinition<&str, &str> = TableDefinition::new("config");
48const BLINDED_SIGNATURES: TableDefinition<[u8; 33], &str> =
50 TableDefinition::new("blinded_signatures");
51const BLIND_SIGNATURE_CREATED_TIME: TableDefinition<[u8; 33], u64> =
52 TableDefinition::new("blind_signature_created_time");
53const QUOTE_PROOFS_TABLE: MultimapTableDefinition<[u8; 16], [u8; 33]> =
54 MultimapTableDefinition::new("quote_proofs");
55const QUOTE_SIGNATURES_TABLE: MultimapTableDefinition<[u8; 16], [u8; 33]> =
56 MultimapTableDefinition::new("quote_signatures");
57
58const MELT_REQUESTS: TableDefinition<[u8; 16], (&str, &str)> =
59 TableDefinition::new("melt_requests");
60
61const DATABASE_VERSION: u32 = 5;
62
63#[derive(Debug, Clone)]
65pub struct MintRedbDatabase {
66 db: Arc<Database>,
67}
68
69impl MintRedbDatabase {
70 pub fn new(path: &Path) -> Result<Self, Error> {
72 {
73 let db = Arc::new(Database::create(path)?);
76
77 let read_txn = db.begin_read()?;
79 let table = read_txn.open_table(CONFIG_TABLE);
80
81 let db_version = match table {
82 Ok(table) => table.get("db_version")?.map(|v| v.value().to_owned()),
83 Err(_) => None,
84 };
85 match db_version {
86 Some(db_version) => {
87 let mut current_file_version = u32::from_str(&db_version)?;
88 match current_file_version.cmp(&DATABASE_VERSION) {
89 Ordering::Less => {
90 tracing::info!(
91 "Database needs to be upgraded at {} current is {}",
92 current_file_version,
93 DATABASE_VERSION
94 );
95 if current_file_version == 0 {
96 current_file_version = migrate_00_to_01(Arc::clone(&db))?;
97 }
98
99 if current_file_version == 1 {
100 current_file_version = migrate_01_to_02(Arc::clone(&db))?;
101 }
102
103 if current_file_version == 2 {
104 current_file_version = migrate_02_to_03(Arc::clone(&db))?;
105 }
106
107 if current_file_version == 3 {
108 current_file_version = migrate_03_to_04(Arc::clone(&db))?;
109 }
110
111 if current_file_version == 4 {
112 current_file_version = migrate_04_to_05(Arc::clone(&db))?;
113 }
114
115 if current_file_version != DATABASE_VERSION {
116 tracing::warn!(
117 "Database upgrade did not complete at {} current is {}",
118 current_file_version,
119 DATABASE_VERSION
120 );
121 return Err(Error::UnknownDatabaseVersion);
122 }
123
124 let write_txn = db.begin_write()?;
125 {
126 let mut table = write_txn.open_table(CONFIG_TABLE)?;
127
128 table
129 .insert("db_version", DATABASE_VERSION.to_string().as_str())?;
130 }
131
132 write_txn.commit()?;
133 }
134 Ordering::Equal => {
135 tracing::info!("Database is at current version {}", DATABASE_VERSION);
136 }
137 Ordering::Greater => {
138 tracing::warn!(
139 "Database upgrade did not complete at {} current is {}",
140 current_file_version,
141 DATABASE_VERSION
142 );
143 return Err(Error::UnknownDatabaseVersion);
144 }
145 }
146 }
147 None => {
148 let write_txn = db.begin_write()?;
149 {
150 let mut table = write_txn.open_table(CONFIG_TABLE)?;
152 let _ = write_txn.open_table(ACTIVE_KEYSETS_TABLE)?;
153 let _ = write_txn.open_table(KEYSETS_TABLE)?;
154 let _ = write_txn.open_table(MINT_QUOTES_TABLE)?;
155 let _ = write_txn.open_table(MELT_QUOTES_TABLE)?;
156 let _ = write_txn.open_table(PROOFS_TABLE)?;
157 let _ = write_txn.open_table(PROOFS_STATE_TABLE)?;
158 let _ = write_txn.open_table(PROOF_CREATED_TIME)?;
159 let _ = write_txn.open_table(BLINDED_SIGNATURES)?;
160 let _ = write_txn.open_table(BLIND_SIGNATURE_CREATED_TIME)?;
161 let _ = write_txn.open_multimap_table(QUOTE_PROOFS_TABLE)?;
162 let _ = write_txn.open_multimap_table(QUOTE_SIGNATURES_TABLE)?;
163
164 table.insert("db_version", DATABASE_VERSION.to_string().as_str())?;
165 }
166
167 write_txn.commit()?;
168 }
169 }
170 drop(db);
171 }
172
173 let db = Database::create(path)?;
174 Ok(Self { db: Arc::new(db) })
175 }
176}
177
178#[async_trait]
179impl MintKeysDatabase for MintRedbDatabase {
180 type Err = database::Error;
181
182 async fn set_active_keyset(&self, unit: CurrencyUnit, id: Id) -> Result<(), Self::Err> {
183 let write_txn = self.db.begin_write().map_err(Error::from)?;
184
185 {
186 let mut table = write_txn
187 .open_table(ACTIVE_KEYSETS_TABLE)
188 .map_err(Error::from)?;
189 table
190 .insert(unit.to_string().as_str(), id.to_string().as_str())
191 .map_err(Error::from)?;
192 }
193 write_txn.commit().map_err(Error::from)?;
194
195 Ok(())
196 }
197
198 async fn get_active_keyset_id(&self, unit: &CurrencyUnit) -> Result<Option<Id>, Self::Err> {
199 let read_txn = self.db.begin_read().map_err(Error::from)?;
200 let table = read_txn
201 .open_table(ACTIVE_KEYSETS_TABLE)
202 .map_err(Error::from)?;
203
204 if let Some(id) = table.get(unit.to_string().as_str()).map_err(Error::from)? {
205 return Ok(Some(Id::from_str(id.value()).map_err(Error::from)?));
206 }
207
208 Ok(None)
209 }
210
211 async fn get_active_keysets(&self) -> Result<HashMap<CurrencyUnit, Id>, Self::Err> {
212 let read_txn = self.db.begin_read().map_err(Error::from)?;
213 let table = read_txn
214 .open_table(ACTIVE_KEYSETS_TABLE)
215 .map_err(Error::from)?;
216
217 let mut active_keysets = HashMap::new();
218
219 for (unit, id) in (table.iter().map_err(Error::from)?).flatten() {
220 let unit = CurrencyUnit::from_str(unit.value())?;
221 let id = Id::from_str(id.value()).map_err(Error::from)?;
222
223 active_keysets.insert(unit, id);
224 }
225
226 Ok(active_keysets)
227 }
228
229 async fn add_keyset_info(&self, keyset: MintKeySetInfo) -> Result<(), Self::Err> {
230 let write_txn = self.db.begin_write().map_err(Error::from)?;
231
232 {
233 let mut table = write_txn.open_table(KEYSETS_TABLE).map_err(Error::from)?;
234 table
235 .insert(
236 keyset.id.to_string().as_str(),
237 serde_json::to_string(&keyset)
238 .map_err(Error::from)?
239 .as_str(),
240 )
241 .map_err(Error::from)?;
242 }
243 write_txn.commit().map_err(Error::from)?;
244
245 Ok(())
246 }
247
248 async fn get_keyset_info(&self, keyset_id: &Id) -> Result<Option<MintKeySetInfo>, Self::Err> {
249 let read_txn = self.db.begin_read().map_err(Error::from)?;
250 let table = read_txn.open_table(KEYSETS_TABLE).map_err(Error::from)?;
251
252 match table
253 .get(keyset_id.to_string().as_str())
254 .map_err(Error::from)?
255 {
256 Some(keyset) => Ok(serde_json::from_str(keyset.value()).map_err(Error::from)?),
257 None => Ok(None),
258 }
259 }
260
261 async fn get_keyset_infos(&self) -> Result<Vec<MintKeySetInfo>, Self::Err> {
262 let read_txn = self.db.begin_read().map_err(Error::from)?;
263 let table = read_txn.open_table(KEYSETS_TABLE).map_err(Error::from)?;
264
265 let mut keysets = Vec::new();
266
267 for (_id, keyset) in (table.iter().map_err(Error::from)?).flatten() {
268 let keyset = serde_json::from_str(keyset.value()).map_err(Error::from)?;
269
270 keysets.push(keyset)
271 }
272
273 Ok(keysets)
274 }
275}
276
277#[async_trait]
278impl MintQuotesDatabase for MintRedbDatabase {
279 type Err = database::Error;
280
281 async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), Self::Err> {
282 let write_txn = self.db.begin_write().map_err(Error::from)?;
283
284 {
285 let mut table = write_txn
286 .open_table(MINT_QUOTES_TABLE)
287 .map_err(Error::from)?;
288 table
289 .insert(
290 quote.id.as_bytes(),
291 serde_json::to_string("e).map_err(Error::from)?.as_str(),
292 )
293 .map_err(Error::from)?;
294 }
295 write_txn.commit().map_err(Error::from)?;
296
297 Ok(())
298 }
299
300 async fn get_mint_quote(&self, quote_id: &Uuid) -> Result<Option<MintQuote>, Self::Err> {
301 let read_txn = self.db.begin_read().map_err(Error::from)?;
302 let table = read_txn
303 .open_table(MINT_QUOTES_TABLE)
304 .map_err(Error::from)?;
305
306 match table.get(quote_id.as_bytes()).map_err(Error::from)? {
307 Some(quote) => Ok(serde_json::from_str(quote.value()).map_err(Error::from)?),
308 None => Ok(None),
309 }
310 }
311
312 async fn update_mint_quote_state(
313 &self,
314 quote_id: &Uuid,
315 state: MintQuoteState,
316 ) -> Result<MintQuoteState, Self::Err> {
317 let write_txn = self.db.begin_write().map_err(Error::from)?;
318
319 let current_state;
320 {
321 let mut mint_quote: MintQuote;
322 let mut table = write_txn
323 .open_table(MINT_QUOTES_TABLE)
324 .map_err(Error::from)?;
325 {
326 let quote_guard = table
327 .get(quote_id.as_bytes())
328 .map_err(Error::from)?
329 .ok_or(Error::UnknownQuote)?;
330
331 let quote = quote_guard.value();
332
333 mint_quote = serde_json::from_str(quote).map_err(Error::from)?;
334 }
335
336 current_state = mint_quote.state;
337 mint_quote.state = state;
338
339 {
340 table
341 .insert(
342 quote_id.as_bytes(),
343 serde_json::to_string(&mint_quote)
344 .map_err(Error::from)?
345 .as_str(),
346 )
347 .map_err(Error::from)?;
348 }
349 }
350 write_txn.commit().map_err(Error::from)?;
351
352 Ok(current_state)
353 }
354 async fn get_mint_quote_by_request(
355 &self,
356 request: &str,
357 ) -> Result<Option<MintQuote>, Self::Err> {
358 let quotes = self.get_mint_quotes().await?;
359
360 let quote = quotes
361 .into_iter()
362 .filter(|q| q.request.eq(request))
363 .collect::<Vec<MintQuote>>()
364 .first()
365 .cloned();
366
367 Ok(quote)
368 }
369
370 async fn get_mint_quote_by_request_lookup_id(
371 &self,
372 request_lookup_id: &str,
373 ) -> Result<Option<MintQuote>, Self::Err> {
374 let quotes = self.get_mint_quotes().await?;
375
376 let quote = quotes
377 .into_iter()
378 .filter(|q| q.request_lookup_id.eq(request_lookup_id))
379 .collect::<Vec<MintQuote>>()
380 .first()
381 .cloned();
382
383 Ok(quote)
384 }
385
386 async fn get_mint_quotes(&self) -> Result<Vec<MintQuote>, Self::Err> {
387 let read_txn = self.db.begin_read().map_err(Error::from)?;
388 let table = read_txn
389 .open_table(MINT_QUOTES_TABLE)
390 .map_err(Error::from)?;
391
392 let mut quotes = Vec::new();
393
394 for (_id, quote) in (table.iter().map_err(Error::from)?).flatten() {
395 let quote = serde_json::from_str(quote.value()).map_err(Error::from)?;
396
397 quotes.push(quote)
398 }
399
400 Ok(quotes)
401 }
402
403 async fn get_mint_quotes_with_state(
404 &self,
405 state: MintQuoteState,
406 ) -> Result<Vec<MintQuote>, Self::Err> {
407 let read_txn = self.db.begin_read().map_err(Error::from)?;
408 let table = read_txn
409 .open_table(MINT_QUOTES_TABLE)
410 .map_err(Error::from)?;
411
412 let mut quotes = Vec::new();
413
414 for (_id, quote) in (table.iter().map_err(Error::from)?).flatten() {
415 let quote: MintQuote = serde_json::from_str(quote.value()).map_err(Error::from)?;
416
417 if quote.state == state {
418 quotes.push(quote)
419 }
420 }
421
422 Ok(quotes)
423 }
424
425 async fn remove_mint_quote(&self, quote_id: &Uuid) -> Result<(), Self::Err> {
426 let write_txn = self.db.begin_write().map_err(Error::from)?;
427
428 {
429 let mut table = write_txn
430 .open_table(MINT_QUOTES_TABLE)
431 .map_err(Error::from)?;
432 table.remove(quote_id.as_bytes()).map_err(Error::from)?;
433 }
434 write_txn.commit().map_err(Error::from)?;
435
436 Ok(())
437 }
438
439 async fn add_melt_quote(&self, quote: mint::MeltQuote) -> Result<(), Self::Err> {
440 let write_txn = self.db.begin_write().map_err(Error::from)?;
441
442 {
443 let mut table = write_txn
444 .open_table(MELT_QUOTES_TABLE)
445 .map_err(Error::from)?;
446 table
447 .insert(
448 quote.id.as_bytes(),
449 serde_json::to_string("e).map_err(Error::from)?.as_str(),
450 )
451 .map_err(Error::from)?;
452 }
453 write_txn.commit().map_err(Error::from)?;
454
455 Ok(())
456 }
457
458 async fn get_melt_quote(&self, quote_id: &Uuid) -> Result<Option<mint::MeltQuote>, Self::Err> {
459 let read_txn = self.db.begin_read().map_err(Error::from)?;
460 let table = read_txn
461 .open_table(MELT_QUOTES_TABLE)
462 .map_err(Error::from)?;
463
464 let quote = table.get(quote_id.as_bytes()).map_err(Error::from)?;
465
466 Ok(quote.map(|q| serde_json::from_str(q.value()).unwrap()))
467 }
468
469 async fn update_melt_quote_state(
470 &self,
471 quote_id: &Uuid,
472 state: MeltQuoteState,
473 ) -> Result<MeltQuoteState, Self::Err> {
474 let write_txn = self.db.begin_write().map_err(Error::from)?;
475
476 let current_state;
477 {
478 let mut melt_quote: mint::MeltQuote;
479 let mut table = write_txn
480 .open_table(MELT_QUOTES_TABLE)
481 .map_err(Error::from)?;
482 {
483 let quote_guard = table
484 .get(quote_id.as_bytes())
485 .map_err(Error::from)?
486 .ok_or(Error::UnknownQuote)?;
487
488 let quote = quote_guard.value();
489
490 melt_quote = serde_json::from_str(quote).map_err(Error::from)?;
491 }
492
493 current_state = melt_quote.state;
494 melt_quote.state = state;
495
496 {
497 table
498 .insert(
499 quote_id.as_bytes(),
500 serde_json::to_string(&melt_quote)
501 .map_err(Error::from)?
502 .as_str(),
503 )
504 .map_err(Error::from)?;
505 }
506 }
507 write_txn.commit().map_err(Error::from)?;
508
509 Ok(current_state)
510 }
511
512 async fn get_melt_quotes(&self) -> Result<Vec<mint::MeltQuote>, Self::Err> {
513 let read_txn = self.db.begin_read().map_err(Error::from)?;
514 let table = read_txn
515 .open_table(MELT_QUOTES_TABLE)
516 .map_err(Error::from)?;
517
518 let mut quotes = Vec::new();
519
520 for (_id, quote) in (table.iter().map_err(Error::from)?).flatten() {
521 let quote = serde_json::from_str(quote.value()).map_err(Error::from)?;
522
523 quotes.push(quote)
524 }
525
526 Ok(quotes)
527 }
528
529 async fn remove_melt_quote(&self, quote_id: &Uuid) -> Result<(), Self::Err> {
530 let write_txn = self.db.begin_write().map_err(Error::from)?;
531
532 {
533 let mut table = write_txn
534 .open_table(MELT_QUOTES_TABLE)
535 .map_err(Error::from)?;
536 table.remove(quote_id.as_bytes()).map_err(Error::from)?;
537 }
538 write_txn.commit().map_err(Error::from)?;
539
540 Ok(())
541 }
542
543 async fn add_melt_request(
545 &self,
546 melt_request: MeltBolt11Request<Uuid>,
547 ln_key: PaymentProcessorKey,
548 ) -> Result<(), Self::Err> {
549 let write_txn = self.db.begin_write().map_err(Error::from)?;
550 let mut table = write_txn.open_table(MELT_REQUESTS).map_err(Error::from)?;
551
552 table
553 .insert(
554 melt_request.quote().as_bytes(),
555 (
556 serde_json::to_string(&melt_request)?.as_str(),
557 serde_json::to_string(&ln_key)?.as_str(),
558 ),
559 )
560 .map_err(Error::from)?;
561
562 Ok(())
563 }
564 async fn get_melt_request(
566 &self,
567 quote_id: &Uuid,
568 ) -> Result<Option<(MeltBolt11Request<Uuid>, PaymentProcessorKey)>, Self::Err> {
569 let read_txn = self.db.begin_read().map_err(Error::from)?;
570 let table = read_txn.open_table(MELT_REQUESTS).map_err(Error::from)?;
571
572 match table.get(quote_id.as_bytes()).map_err(Error::from)? {
573 Some(melt_request) => {
574 let (melt_request_str, ln_key_str) = melt_request.value();
575 let melt_request = serde_json::from_str(melt_request_str)?;
576 let ln_key = serde_json::from_str(ln_key_str)?;
577
578 Ok(Some((melt_request, ln_key)))
579 }
580 None => Ok(None),
581 }
582 }
583}
584
585#[async_trait]
586impl MintProofsDatabase for MintRedbDatabase {
587 type Err = database::Error;
588
589 async fn add_proofs(&self, proofs: Proofs, quote_id: Option<Uuid>) -> Result<(), Self::Err> {
590 let write_txn = self.db.begin_write().map_err(Error::from)?;
591
592 {
593 let mut table = write_txn.open_table(PROOFS_TABLE).map_err(Error::from)?;
594 let mut time_table = write_txn
595 .open_table(PROOF_CREATED_TIME)
596 .map_err(Error::from)?;
597 let mut quote_proofs_table = write_txn
598 .open_multimap_table(QUOTE_PROOFS_TABLE)
599 .map_err(Error::from)?;
600
601 let current_time = unix_time();
603
604 for proof in proofs {
605 let y: PublicKey = hash_to_curve(&proof.secret.to_bytes()).map_err(Error::from)?;
606 let y = y.to_bytes();
607 if table.get(y).map_err(Error::from)?.is_none() {
608 table
609 .insert(
610 y,
611 serde_json::to_string(&proof).map_err(Error::from)?.as_str(),
612 )
613 .map_err(Error::from)?;
614
615 time_table.insert(y, current_time).map_err(Error::from)?;
617 }
618
619 if let Some(quote_id) = "e_id {
620 quote_proofs_table
621 .insert(quote_id.as_bytes(), y)
622 .map_err(Error::from)?;
623 }
624 }
625 }
626 write_txn.commit().map_err(Error::from)?;
627
628 Ok(())
629 }
630
631 async fn remove_proofs(
632 &self,
633 ys: &[PublicKey],
634 quote_id: Option<Uuid>,
635 ) -> Result<(), Self::Err> {
636 let write_txn = self.db.begin_write().map_err(Error::from)?;
637
638 let mut states: HashSet<State> = HashSet::new();
639
640 {
641 let mut proof_state_table = write_txn
642 .open_table(PROOFS_STATE_TABLE)
643 .map_err(Error::from)?;
644 for y in ys {
645 let state = proof_state_table
646 .remove(&y.to_bytes())
647 .map_err(Error::from)?;
648
649 if let Some(state) = state {
650 let state: State = serde_json::from_str(state.value()).map_err(Error::from)?;
651
652 states.insert(state);
653 }
654 }
655 }
656
657 if states.contains(&State::Spent) {
658 tracing::warn!("Db attempted to remove spent proof");
659 write_txn.abort().map_err(Error::from)?;
660 return Err(Self::Err::AttemptRemoveSpentProof);
661 }
662
663 {
664 let mut proofs_table = write_txn.open_table(PROOFS_TABLE).map_err(Error::from)?;
665 let mut time_table = write_txn
666 .open_table(PROOF_CREATED_TIME)
667 .map_err(Error::from)?;
668
669 for y in ys {
670 proofs_table.remove(&y.to_bytes()).map_err(Error::from)?;
671 time_table.remove(&y.to_bytes()).map_err(Error::from)?;
672 }
673 }
674
675 if let Some(quote_id) = quote_id {
676 let mut quote_proofs_table = write_txn
677 .open_multimap_table(QUOTE_PROOFS_TABLE)
678 .map_err(Error::from)?;
679
680 quote_proofs_table
681 .remove_all(quote_id.as_bytes())
682 .map_err(Error::from)?;
683 }
684
685 write_txn.commit().map_err(Error::from)?;
686
687 Ok(())
688 }
689
690 async fn get_proofs_by_ys(&self, ys: &[PublicKey]) -> Result<Vec<Option<Proof>>, Self::Err> {
691 let read_txn = self.db.begin_read().map_err(Error::from)?;
692 let table = read_txn.open_table(PROOFS_TABLE).map_err(Error::from)?;
693
694 let mut proofs = Vec::with_capacity(ys.len());
695
696 for y in ys {
697 match table.get(y.to_bytes()).map_err(Error::from)? {
698 Some(proof) => proofs.push(Some(
699 serde_json::from_str(proof.value()).map_err(Error::from)?,
700 )),
701 None => proofs.push(None),
702 }
703 }
704
705 Ok(proofs)
706 }
707
708 async fn get_proof_ys_by_quote_id(&self, quote_id: &Uuid) -> Result<Vec<PublicKey>, Self::Err> {
709 let read_txn = self.db.begin_read().map_err(Error::from)?;
710 let table = read_txn
711 .open_multimap_table(QUOTE_PROOFS_TABLE)
712 .map_err(Error::from)?;
713
714 let ys = table.get(quote_id.as_bytes()).map_err(Error::from)?;
715
716 let proof_ys = ys.fold(Vec::new(), |mut acc, y| {
717 if let Ok(y) = y {
718 if let Ok(pubkey) = PublicKey::from_slice(&y.value()) {
719 acc.push(pubkey);
720 }
721 }
722 acc
723 });
724
725 Ok(proof_ys)
726 }
727
728 async fn get_proofs_states(&self, ys: &[PublicKey]) -> Result<Vec<Option<State>>, Self::Err> {
729 let read_txn = self.db.begin_read().map_err(Error::from)?;
730 let table = read_txn
731 .open_table(PROOFS_STATE_TABLE)
732 .map_err(Error::from)?;
733
734 let mut states = Vec::with_capacity(ys.len());
735
736 for y in ys {
737 match table.get(y.to_bytes()).map_err(Error::from)? {
738 Some(state) => states.push(Some(
739 serde_json::from_str(state.value()).map_err(Error::from)?,
740 )),
741 None => states.push(None),
742 }
743 }
744
745 Ok(states)
746 }
747
748 async fn get_proofs_by_keyset_id(
749 &self,
750 keyset_id: &Id,
751 ) -> Result<(Proofs, Vec<Option<State>>), Self::Err> {
752 let read_txn = self.db.begin_read().map_err(Error::from)?;
753 let table = read_txn.open_table(PROOFS_TABLE).map_err(Error::from)?;
754
755 let proofs_for_id = table
756 .iter()
757 .map_err(Error::from)?
758 .flatten()
759 .map(|(_, p)| serde_json::from_str::<Proof>(p.value()))
760 .collect::<Result<Proofs, _>>()?
761 .into_iter()
762 .filter(|p| &p.keyset_id == keyset_id)
763 .collect::<Proofs>();
764
765 let proof_ys = proofs_for_id.ys()?;
766
767 assert_eq!(proofs_for_id.len(), proof_ys.len());
768
769 let states = self.get_proofs_states(&proof_ys).await?;
770
771 Ok((proofs_for_id, states))
772 }
773
774 async fn update_proofs_states(
775 &self,
776 ys: &[PublicKey],
777 proofs_state: State,
778 ) -> Result<Vec<Option<State>>, Self::Err> {
779 let write_txn = self.db.begin_write().map_err(Error::from)?;
780
781 let mut states = Vec::with_capacity(ys.len());
782 {
783 let table = write_txn
784 .open_table(PROOFS_STATE_TABLE)
785 .map_err(Error::from)?;
786 {
787 for y in ys {
789 let current_state = match table.get(y.to_bytes()).map_err(Error::from)? {
790 Some(state) => {
791 let current_state =
792 serde_json::from_str(state.value()).map_err(Error::from)?;
793 check_state_transition(current_state, proofs_state)?;
794 Some(current_state)
795 }
796 None => None,
797 };
798
799 states.push(current_state);
800 }
801 }
802 }
803
804 {
805 let mut table = write_txn
806 .open_table(PROOFS_STATE_TABLE)
807 .map_err(Error::from)?;
808 {
809 let state_str = serde_json::to_string(&proofs_state).map_err(Error::from)?;
811 for y in ys {
812 table
813 .insert(y.to_bytes(), state_str.as_str())
814 .map_err(Error::from)?;
815 }
816 }
817 }
818 write_txn.commit().map_err(Error::from)?;
819
820 Ok(states)
821 }
822}
823
824#[async_trait]
825impl MintSignaturesDatabase for MintRedbDatabase {
826 type Err = database::Error;
827
828 async fn add_blind_signatures(
829 &self,
830 blinded_messages: &[PublicKey],
831 blind_signatures: &[BlindSignature],
832 quote_id: Option<Uuid>,
833 ) -> Result<(), Self::Err> {
834 let write_txn = self.db.begin_write().map_err(Error::from)?;
835
836 {
837 let mut table = write_txn
838 .open_table(BLINDED_SIGNATURES)
839 .map_err(Error::from)?;
840 let mut time_table = write_txn
841 .open_table(BLIND_SIGNATURE_CREATED_TIME)
842 .map_err(Error::from)?;
843 let mut quote_sigs_table = write_txn
844 .open_multimap_table(QUOTE_SIGNATURES_TABLE)
845 .map_err(Error::from)?;
846
847 let current_time = unix_time();
849
850 for (blinded_message, blind_signature) in blinded_messages.iter().zip(blind_signatures)
851 {
852 let blind_sig = serde_json::to_string(&blind_signature).map_err(Error::from)?;
853 table
854 .insert(blinded_message.to_bytes(), blind_sig.as_str())
855 .map_err(Error::from)?;
856
857 time_table
859 .insert(blinded_message.to_bytes(), current_time)
860 .map_err(Error::from)?;
861
862 if let Some(quote_id) = "e_id {
863 quote_sigs_table
864 .insert(quote_id.as_bytes(), blinded_message.to_bytes())
865 .map_err(Error::from)?;
866 }
867 }
868 }
869
870 write_txn.commit().map_err(Error::from)?;
871
872 Ok(())
873 }
874
875 async fn get_blind_signatures(
876 &self,
877 blinded_messages: &[PublicKey],
878 ) -> Result<Vec<Option<BlindSignature>>, Self::Err> {
879 let read_txn = self.db.begin_read().map_err(Error::from)?;
880 let table = read_txn
881 .open_table(BLINDED_SIGNATURES)
882 .map_err(Error::from)?;
883
884 let mut signatures = Vec::with_capacity(blinded_messages.len());
885
886 for blinded_message in blinded_messages {
887 match table.get(blinded_message.to_bytes()).map_err(Error::from)? {
888 Some(blind_signature) => signatures.push(Some(
889 serde_json::from_str(blind_signature.value()).map_err(Error::from)?,
890 )),
891 None => signatures.push(None),
892 }
893 }
894
895 Ok(signatures)
896 }
897
898 async fn get_blind_signatures_for_keyset(
899 &self,
900 keyset_id: &Id,
901 ) -> Result<Vec<BlindSignature>, Self::Err> {
902 let read_txn = self.db.begin_read().map_err(Error::from)?;
903 let table = read_txn
904 .open_table(BLINDED_SIGNATURES)
905 .map_err(Error::from)?;
906
907 Ok(table
908 .iter()
909 .map_err(Error::from)?
910 .flatten()
911 .filter_map(|(_m, s)| {
912 match serde_json::from_str::<BlindSignature>(s.value()).ok() {
913 Some(signature) if &signature.keyset_id == keyset_id => Some(signature), _ => None, }
916 })
917 .collect())
918 }
919
920 async fn get_blind_signatures_for_quote(
922 &self,
923 quote_id: &Uuid,
924 ) -> Result<Vec<BlindSignature>, Self::Err> {
925 let read_txn = self.db.begin_read().map_err(Error::from)?;
926 let quote_proofs_table = read_txn
927 .open_multimap_table(QUOTE_SIGNATURES_TABLE)
928 .map_err(Error::from)?;
929
930 let ys = quote_proofs_table.get(quote_id.as_bytes()).unwrap();
931
932 let ys: Vec<[u8; 33]> = ys.into_iter().flatten().map(|v| v.value()).collect();
933
934 let mut signatures = Vec::new();
935
936 let signatures_table = read_txn
937 .open_table(BLINDED_SIGNATURES)
938 .map_err(Error::from)?;
939
940 for y in ys {
941 if let Some(sig) = signatures_table.get(y).map_err(Error::from)? {
942 let sig = serde_json::from_str(sig.value())?;
943 signatures.push(sig);
944 }
945 }
946
947 Ok(signatures)
948 }
949}
950
951#[async_trait]
952impl MintDatabase<database::Error> for MintRedbDatabase {
953 async fn set_mint_info(&self, mint_info: MintInfo) -> Result<(), database::Error> {
954 let write_txn = self.db.begin_write().map_err(Error::from)?;
955
956 {
957 let mut table = write_txn.open_table(CONFIG_TABLE).map_err(Error::from)?;
958 table
959 .insert("mint_info", serde_json::to_string(&mint_info)?.as_str())
960 .map_err(Error::from)?;
961 }
962 write_txn.commit().map_err(Error::from)?;
963
964 Ok(())
965 }
966 async fn get_mint_info(&self) -> Result<MintInfo, database::Error> {
967 let read_txn = self.db.begin_read().map_err(Error::from)?;
968 let table = read_txn.open_table(CONFIG_TABLE).map_err(Error::from)?;
969
970 if let Some(mint_info) = table.get("mint_info").map_err(Error::from)? {
971 let mint_info = serde_json::from_str(mint_info.value())?;
972
973 return Ok(mint_info);
974 }
975
976 Err(Error::UnknownMintInfo.into())
977 }
978
979 async fn set_quote_ttl(&self, quote_ttl: QuoteTTL) -> Result<(), database::Error> {
980 let write_txn = self.db.begin_write().map_err(Error::from)?;
981
982 {
983 let mut table = write_txn.open_table(CONFIG_TABLE).map_err(Error::from)?;
984 table
985 .insert("quote_ttl", serde_json::to_string("e_ttl)?.as_str())
986 .map_err(Error::from)?;
987 }
988 write_txn.commit().map_err(Error::from)?;
989
990 Ok(())
991 }
992 async fn get_quote_ttl(&self) -> Result<QuoteTTL, database::Error> {
993 let read_txn = self.db.begin_read().map_err(Error::from)?;
994 let table = read_txn.open_table(CONFIG_TABLE).map_err(Error::from)?;
995
996 if let Some(quote_ttl) = table.get("quote_ttl").map_err(Error::from)? {
997 let quote_ttl = serde_json::from_str(quote_ttl.value())?;
998
999 return Ok(quote_ttl);
1000 }
1001
1002 Err(Error::UnknownQuoteTTL.into())
1003 }
1004}
1005
1006#[cfg(test)]
1007mod tests {
1008 use cdk_common::secret::Secret;
1009 use cdk_common::{mint_db_test, Amount, SecretKey};
1010 use tempfile::tempdir;
1011
1012 use super::*;
1013
1014 #[tokio::test]
1015 async fn test_remove_spent_proofs() {
1016 let tmp_dir = tempdir().unwrap();
1017
1018 let db = MintRedbDatabase::new(&tmp_dir.path().join("mint.redb")).unwrap();
1019 let keyset_id = Id::from_str("00916bbf7ef91a36").unwrap();
1021
1022 let proofs = vec![
1023 Proof {
1024 amount: Amount::from(100),
1025 keyset_id,
1026 secret: Secret::generate(),
1027 c: SecretKey::generate().public_key(),
1028 witness: None,
1029 dleq: None,
1030 },
1031 Proof {
1032 amount: Amount::from(200),
1033 keyset_id,
1034 secret: Secret::generate(),
1035 c: SecretKey::generate().public_key(),
1036 witness: None,
1037 dleq: None,
1038 },
1039 ];
1040
1041 db.add_proofs(proofs.clone(), None).await.unwrap();
1043
1044 db.update_proofs_states(&[proofs[0].y().unwrap()], State::Spent)
1046 .await
1047 .unwrap();
1048
1049 db.update_proofs_states(&[proofs[1].y().unwrap()], State::Unspent)
1050 .await
1051 .unwrap();
1052
1053 let result = db
1055 .remove_proofs(&[proofs[0].y().unwrap(), proofs[1].y().unwrap()], None)
1056 .await;
1057
1058 assert!(result.is_err());
1059 assert!(matches!(
1060 result.unwrap_err(),
1061 database::Error::AttemptRemoveSpentProof
1062 ));
1063
1064 let states = db
1066 .get_proofs_states(&[proofs[0].y().unwrap(), proofs[1].y().unwrap()])
1067 .await
1068 .unwrap();
1069
1070 assert_eq!(states.len(), 2);
1071 assert_eq!(states[0], Some(State::Spent));
1072 assert_eq!(states[1], Some(State::Unspent));
1073 }
1074
1075 #[tokio::test]
1076 async fn test_update_spent_proofs() {
1077 let tmp_dir = tempdir().unwrap();
1078
1079 let db = MintRedbDatabase::new(&tmp_dir.path().join("mint.redb")).unwrap();
1080 let keyset_id = Id::from_str("00916bbf7ef91a36").unwrap();
1082
1083 let proofs = vec![
1084 Proof {
1085 amount: Amount::from(100),
1086 keyset_id,
1087 secret: Secret::generate(),
1088 c: SecretKey::generate().public_key(),
1089 witness: None,
1090 dleq: None,
1091 },
1092 Proof {
1093 amount: Amount::from(200),
1094 keyset_id,
1095 secret: Secret::generate(),
1096 c: SecretKey::generate().public_key(),
1097 witness: None,
1098 dleq: None,
1099 },
1100 ];
1101
1102 db.add_proofs(proofs.clone(), None).await.unwrap();
1104
1105 db.update_proofs_states(&[proofs[0].y().unwrap()], State::Spent)
1107 .await
1108 .unwrap();
1109
1110 db.update_proofs_states(&[proofs[1].y().unwrap()], State::Unspent)
1111 .await
1112 .unwrap();
1113
1114 let result = db
1116 .update_proofs_states(
1117 &[proofs[0].y().unwrap(), proofs[1].y().unwrap()],
1118 State::Unspent,
1119 )
1120 .await;
1121
1122 assert!(result.is_err());
1123 assert!(matches!(
1124 result.unwrap_err(),
1125 database::Error::AttemptUpdateSpentProof
1126 ));
1127
1128 let states = db
1130 .get_proofs_states(&[proofs[0].y().unwrap(), proofs[1].y().unwrap()])
1131 .await
1132 .unwrap();
1133
1134 assert_eq!(states.len(), 2);
1135 assert_eq!(states[0], Some(State::Spent));
1136 assert_eq!(states[1], Some(State::Unspent));
1137 }
1138
1139 async fn provide_db() -> MintRedbDatabase {
1140 let tmp_dir = tempdir().unwrap();
1141
1142 MintRedbDatabase::new(&tmp_dir.path().join("mint.redb")).unwrap()
1143 }
1144
1145 mint_db_test!(provide_db);
1146}