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