cdk_redb/mint/
mod.rs

1//! SQLite Storage for CDK
2
3use 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");
48// Key is hex blinded_message B_ value is blinded_signature
49const 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/// Mint Redbdatabase
64#[derive(Debug, Clone)]
65pub struct MintRedbDatabase {
66    db: Arc<Database>,
67}
68
69impl MintRedbDatabase {
70    /// Create new [`MintRedbDatabase`]
71    pub fn new(path: &Path) -> Result<Self, Error> {
72        {
73            // Check database version
74
75            let db = Arc::new(Database::create(path)?);
76
77            // Check database version
78            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                        // Open all tables to init a new db
151                        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(&quote).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(&quote).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    /// Add melt request
544    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    /// Get melt request
565    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            // Get current timestamp in seconds
602            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                    // Store creation time
616                    time_table.insert(y, current_time).map_err(Error::from)?;
617                }
618
619                if let Some(quote_id) = &quote_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                // First collect current states
788                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                // If no proofs are spent, proceed with update
810                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            // Get current timestamp in seconds
848            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                // Store creation time
858                time_table
859                    .insert(blinded_message.to_bytes(), current_time)
860                    .map_err(Error::from)?;
861
862                if let Some(quote_id) = &quote_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), // Filter by keyset_id
914                    _ => None, // Exclude non-matching entries
915                }
916            })
917            .collect())
918    }
919
920    /// Get [`BlindSignature`]s for quote
921    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(&quote_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        // Create some test proofs
1020        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        // Add proofs to database
1042        db.add_proofs(proofs.clone(), None).await.unwrap();
1043
1044        // Mark one proof as spent
1045        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        // Try to remove both proofs - should fail because one is spent
1054        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        // Verify both proofs still exist
1065        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        // Create some test proofs
1081        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        // Add proofs to database
1103        db.add_proofs(proofs.clone(), None).await.unwrap();
1104
1105        // Mark one proof as spent
1106        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        // Mark one proof as spent
1115        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        // Verify both proofs still exist
1129        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}