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::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");
47// Key is hex blinded_message B_ value is blinded_signature
48const 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/// Mint Redbdatabase
63#[derive(Debug, Clone)]
64pub struct MintRedbDatabase {
65    db: Arc<Database>,
66}
67
68impl MintRedbDatabase {
69    /// Create new [`MintRedbDatabase`]
70    pub fn new(path: &Path) -> Result<Self, Error> {
71        {
72            // Check database version
73
74            let db = Arc::new(Database::create(path)?);
75
76            // Check database version
77            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                        // Open all tables to init a new db
150                        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(&quote).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(&quote).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    /// Add melt request
543    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    /// Get melt request
564    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            // Get current timestamp in seconds
601            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                    // Store creation time
615                    time_table.insert(y, current_time).map_err(Error::from)?;
616                }
617
618                if let Some(quote_id) = &quote_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                // First collect current states
787                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        // Check if any proofs are spent
800        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                // If no proofs are spent, proceed with update
811                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            // Get current timestamp in seconds
849            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                // Store creation time
859                time_table
860                    .insert(blinded_message.to_bytes(), current_time)
861                    .map_err(Error::from)?;
862
863                if let Some(quote_id) = &quote_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), // Filter by keyset_id
915                    _ => None, // Exclude non-matching entries
916                }
917            })
918            .collect())
919    }
920
921    /// Get [`BlindSignature`]s for quote
922    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(&quote_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        // Create some test proofs
1021        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        // Add proofs to database
1043        db.add_proofs(proofs.clone(), None).await.unwrap();
1044
1045        // Mark one proof as spent
1046        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        // Try to remove both proofs - should fail because one is spent
1055        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        // Verify both proofs still exist
1066        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        // Create some test proofs
1082        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        // Add proofs to database
1104        db.add_proofs(proofs.clone(), None).await.unwrap();
1105
1106        // Mark one proof as spent
1107        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        // Mark one proof as spent
1116        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        // Verify both proofs still exist
1130        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}