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