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::{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");
41// Key is hex blinded_message B_ value is blinded_signature
42const 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/// Mint Redbdatabase
55#[derive(Debug, Clone)]
56pub struct MintRedbDatabase {
57    db: Arc<Database>,
58}
59
60impl MintRedbDatabase {
61    /// Create new [`MintRedbDatabase`]
62    pub fn new(path: &Path) -> Result<Self, Error> {
63        {
64            // Check database version
65
66            let db = Arc::new(Database::create(path)?);
67
68            // Check database version
69            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                        // Open all tables to init a new db
142                        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(&quote).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(&quote).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) = &quote_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                // First collect current states
712                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        // Check if any proofs are spent
725        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                // If no proofs are spent, proceed with update
736                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) = &quote_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), // Filter by keyset_id
824                    _ => None, // Exclude non-matching entries
825                }
826            })
827            .collect())
828    }
829
830    /// Add melt request
831    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    /// Get melt request
852    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    /// Get [`BlindSignature`]s for quote
872    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(&quote_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        // Create some test proofs
968        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        // Add proofs to database
990        db.add_proofs(proofs.clone(), None).await.unwrap();
991
992        // Mark one proof as spent
993        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        // Try to remove both proofs - should fail because one is spent
1002        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        // Verify both proofs still exist
1013        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        // Create some test proofs
1029        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        // Add proofs to database
1051        db.add_proofs(proofs.clone(), None).await.unwrap();
1052
1053        // Mark one proof as spent
1054        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        // Mark one proof as spent
1063        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        // Verify both proofs still exist
1077        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}