cdk_redb/wallet/
mod.rs

1//! Redb Wallet
2
3use std::cmp::Ordering;
4use std::collections::HashMap;
5use std::path::Path;
6use std::str::FromStr;
7use std::sync::Arc;
8
9use async_trait::async_trait;
10use cdk_common::common::ProofInfo;
11use cdk_common::database::WalletDatabase;
12use cdk_common::mint_url::MintUrl;
13use cdk_common::util::unix_time;
14use cdk_common::wallet::{self, MintQuote};
15use cdk_common::{
16    database, CurrencyUnit, Id, KeySetInfo, Keys, MintInfo, PublicKey, SpendingConditions, State,
17};
18use redb::{Database, MultimapTableDefinition, ReadableTable, TableDefinition};
19use tracing::instrument;
20
21use super::error::Error;
22use crate::migrations::migrate_00_to_01;
23use crate::wallet::migrations::migrate_01_to_02;
24
25mod migrations;
26
27// <Mint_url, Info>
28const MINTS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mints_table");
29// <Mint_Url, Keyset_id>
30const MINT_KEYSETS_TABLE: MultimapTableDefinition<&str, &[u8]> =
31    MultimapTableDefinition::new("mint_keysets");
32// <Keyset_id, KeysetInfo>
33const KEYSETS_TABLE: TableDefinition<&[u8], &str> = TableDefinition::new("keysets");
34// <Quote_id, quote>
35const MINT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_quotes");
36// <Quote_id, quote>
37const MELT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("melt_quotes");
38const MINT_KEYS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_keys");
39// <Y, Proof Info>
40const PROOFS_TABLE: TableDefinition<&[u8], &str> = TableDefinition::new("proofs");
41const CONFIG_TABLE: TableDefinition<&str, &str> = TableDefinition::new("config");
42const KEYSET_COUNTER: TableDefinition<&str, u32> = TableDefinition::new("keyset_counter");
43
44const DATABASE_VERSION: u32 = 2;
45
46/// Wallet Redb Database
47#[derive(Debug, Clone)]
48pub struct WalletRedbDatabase {
49    db: Arc<Database>,
50}
51
52impl WalletRedbDatabase {
53    /// Create new [`WalletRedbDatabase`]
54    pub fn new(path: &Path) -> Result<Self, Error> {
55        {
56            let db = Arc::new(Database::create(path)?);
57
58            let db_version: Option<String>;
59            {
60                // Check database version
61                let read_txn = db.begin_read()?;
62                let table = read_txn.open_table(CONFIG_TABLE);
63
64                db_version = match table {
65                    Ok(table) => table.get("db_version")?.map(|v| v.value().to_string()),
66                    Err(_) => None,
67                };
68            }
69
70            match db_version {
71                Some(db_version) => {
72                    let mut current_file_version = u32::from_str(&db_version)?;
73                    tracing::info!("Current file version {}", current_file_version);
74
75                    match current_file_version.cmp(&DATABASE_VERSION) {
76                        Ordering::Less => {
77                            tracing::info!(
78                                "Database needs to be upgraded at {} current is {}",
79                                current_file_version,
80                                DATABASE_VERSION
81                            );
82                            if current_file_version == 0 {
83                                current_file_version = migrate_00_to_01(Arc::clone(&db))?;
84                            }
85
86                            if current_file_version == 1 {
87                                current_file_version = migrate_01_to_02(Arc::clone(&db))?;
88                            }
89
90                            if current_file_version != DATABASE_VERSION {
91                                tracing::warn!(
92                                    "Database upgrade did not complete at {} current is {}",
93                                    current_file_version,
94                                    DATABASE_VERSION
95                                );
96                                return Err(Error::UnknownDatabaseVersion);
97                            }
98
99                            let write_txn = db.begin_write()?;
100                            {
101                                let mut table = write_txn.open_table(CONFIG_TABLE)?;
102
103                                table
104                                    .insert("db_version", DATABASE_VERSION.to_string().as_str())?;
105                            }
106
107                            write_txn.commit()?;
108                        }
109                        Ordering::Equal => {
110                            tracing::info!("Database is at current version {}", DATABASE_VERSION);
111                        }
112                        Ordering::Greater => {
113                            tracing::warn!(
114                                "Database upgrade did not complete at {} current is {}",
115                                current_file_version,
116                                DATABASE_VERSION
117                            );
118                            return Err(Error::UnknownDatabaseVersion);
119                        }
120                    }
121                }
122                None => {
123                    let write_txn = db.begin_write()?;
124                    {
125                        let mut table = write_txn.open_table(CONFIG_TABLE)?;
126                        // Open all tables to init a new db
127                        let _ = write_txn.open_table(MINTS_TABLE)?;
128                        let _ = write_txn.open_multimap_table(MINT_KEYSETS_TABLE)?;
129                        let _ = write_txn.open_table(KEYSETS_TABLE)?;
130                        let _ = write_txn.open_table(MINT_QUOTES_TABLE)?;
131                        let _ = write_txn.open_table(MELT_QUOTES_TABLE)?;
132                        let _ = write_txn.open_table(MINT_KEYS_TABLE)?;
133                        let _ = write_txn.open_table(PROOFS_TABLE)?;
134                        let _ = write_txn.open_table(KEYSET_COUNTER)?;
135                        table.insert("db_version", DATABASE_VERSION.to_string().as_str())?;
136                    }
137
138                    write_txn.commit()?;
139                }
140            }
141            drop(db);
142        }
143
144        let db = Database::create(path)?;
145
146        Ok(Self { db: Arc::new(db) })
147    }
148}
149
150#[async_trait]
151impl WalletDatabase for WalletRedbDatabase {
152    type Err = database::Error;
153
154    #[instrument(skip(self))]
155    async fn add_mint(
156        &self,
157        mint_url: MintUrl,
158        mint_info: Option<MintInfo>,
159    ) -> Result<(), Self::Err> {
160        let write_txn = self.db.begin_write().map_err(Error::from)?;
161
162        {
163            let mut table = write_txn.open_table(MINTS_TABLE).map_err(Error::from)?;
164            table
165                .insert(
166                    mint_url.to_string().as_str(),
167                    serde_json::to_string(&mint_info)
168                        .map_err(Error::from)?
169                        .as_str(),
170                )
171                .map_err(Error::from)?;
172        }
173        write_txn.commit().map_err(Error::from)?;
174
175        Ok(())
176    }
177
178    #[instrument(skip(self))]
179    async fn remove_mint(&self, mint_url: MintUrl) -> Result<(), Self::Err> {
180        let write_txn = self.db.begin_write().map_err(Error::from)?;
181
182        {
183            let mut table = write_txn.open_table(MINTS_TABLE).map_err(Error::from)?;
184            table
185                .remove(mint_url.to_string().as_str())
186                .map_err(Error::from)?;
187        }
188        write_txn.commit().map_err(Error::from)?;
189
190        Ok(())
191    }
192
193    #[instrument(skip(self))]
194    async fn get_mint(&self, mint_url: MintUrl) -> Result<Option<MintInfo>, Self::Err> {
195        let read_txn = self.db.begin_read().map_err(Into::<Error>::into)?;
196        let table = read_txn.open_table(MINTS_TABLE).map_err(Error::from)?;
197
198        if let Some(mint_info) = table
199            .get(mint_url.to_string().as_str())
200            .map_err(Error::from)?
201        {
202            return Ok(serde_json::from_str(mint_info.value()).map_err(Error::from)?);
203        }
204
205        Ok(None)
206    }
207
208    #[instrument(skip(self))]
209    async fn get_mints(&self) -> Result<HashMap<MintUrl, Option<MintInfo>>, Self::Err> {
210        let read_txn = self.db.begin_read().map_err(Error::from)?;
211        let table = read_txn.open_table(MINTS_TABLE).map_err(Error::from)?;
212        let mints = table
213            .iter()
214            .map_err(Error::from)?
215            .flatten()
216            .map(|(mint, mint_info)| {
217                (
218                    MintUrl::from_str(mint.value()).unwrap(),
219                    serde_json::from_str(mint_info.value()).ok(),
220                )
221            })
222            .collect();
223
224        Ok(mints)
225    }
226
227    #[instrument(skip(self))]
228    async fn update_mint_url(
229        &self,
230        old_mint_url: MintUrl,
231        new_mint_url: MintUrl,
232    ) -> Result<(), Self::Err> {
233        // Update proofs table
234        {
235            let proofs = self
236                .get_proofs(Some(old_mint_url.clone()), None, None, None)
237                .await
238                .map_err(Error::from)?;
239
240            // Proofs with new url
241            let updated_proofs: Vec<ProofInfo> = proofs
242                .clone()
243                .into_iter()
244                .map(|mut p| {
245                    p.mint_url = new_mint_url.clone();
246                    p
247                })
248                .collect();
249
250            if !updated_proofs.is_empty() {
251                self.update_proofs(updated_proofs, vec![]).await?;
252            }
253        }
254
255        // Update mint quotes
256        {
257            let quotes = self.get_mint_quotes().await?;
258
259            let unix_time = unix_time();
260
261            let quotes: Vec<MintQuote> = quotes
262                .into_iter()
263                .filter_map(|mut q| {
264                    if q.expiry < unix_time {
265                        q.mint_url = new_mint_url.clone();
266                        Some(q)
267                    } else {
268                        None
269                    }
270                })
271                .collect();
272
273            for quote in quotes {
274                self.add_mint_quote(quote).await?;
275            }
276        }
277
278        Ok(())
279    }
280
281    #[instrument(skip(self))]
282    async fn add_mint_keysets(
283        &self,
284        mint_url: MintUrl,
285        keysets: Vec<KeySetInfo>,
286    ) -> Result<(), Self::Err> {
287        let write_txn = self.db.begin_write().map_err(Error::from)?;
288
289        {
290            let mut table = write_txn
291                .open_multimap_table(MINT_KEYSETS_TABLE)
292                .map_err(Error::from)?;
293            let mut keysets_table = write_txn.open_table(KEYSETS_TABLE).map_err(Error::from)?;
294
295            for keyset in keysets {
296                table
297                    .insert(
298                        mint_url.to_string().as_str(),
299                        keyset.id.to_bytes().as_slice(),
300                    )
301                    .map_err(Error::from)?;
302
303                keysets_table
304                    .insert(
305                        keyset.id.to_bytes().as_slice(),
306                        serde_json::to_string(&keyset)
307                            .map_err(Error::from)?
308                            .as_str(),
309                    )
310                    .map_err(Error::from)?;
311            }
312        }
313        write_txn.commit().map_err(Error::from)?;
314
315        Ok(())
316    }
317
318    #[instrument(skip(self))]
319    async fn get_mint_keysets(
320        &self,
321        mint_url: MintUrl,
322    ) -> Result<Option<Vec<KeySetInfo>>, Self::Err> {
323        let read_txn = self.db.begin_read().map_err(Into::<Error>::into)?;
324        let table = read_txn
325            .open_multimap_table(MINT_KEYSETS_TABLE)
326            .map_err(Error::from)?;
327
328        let keyset_ids = table
329            .get(mint_url.to_string().as_str())
330            .map_err(Error::from)?
331            .flatten()
332            .map(|k| Id::from_bytes(k.value()))
333            .collect::<Result<Vec<_>, _>>()?;
334
335        let mut keysets = vec![];
336
337        let keysets_t = read_txn.open_table(KEYSETS_TABLE).map_err(Error::from)?;
338
339        for keyset_id in keyset_ids {
340            if let Some(keyset) = keysets_t
341                .get(keyset_id.to_bytes().as_slice())
342                .map_err(Error::from)?
343            {
344                let keyset = serde_json::from_str(keyset.value()).map_err(Error::from)?;
345
346                keysets.push(keyset);
347            }
348        }
349
350        match keysets.is_empty() {
351            true => Ok(None),
352            false => Ok(Some(keysets)),
353        }
354    }
355
356    #[instrument(skip(self), fields(keyset_id = %keyset_id))]
357    async fn get_keyset_by_id(&self, keyset_id: &Id) -> Result<Option<KeySetInfo>, Self::Err> {
358        let read_txn = self.db.begin_read().map_err(Into::<Error>::into)?;
359        let table = read_txn.open_table(KEYSETS_TABLE).map_err(Error::from)?;
360
361        match table
362            .get(keyset_id.to_bytes().as_slice())
363            .map_err(Error::from)?
364        {
365            Some(keyset) => {
366                let keyset: KeySetInfo =
367                    serde_json::from_str(keyset.value()).map_err(Error::from)?;
368
369                Ok(Some(keyset))
370            }
371            None => Ok(None),
372        }
373    }
374
375    #[instrument(skip_all)]
376    async fn add_mint_quote(&self, quote: MintQuote) -> Result<(), Self::Err> {
377        let write_txn = self.db.begin_write().map_err(Error::from)?;
378
379        {
380            let mut table = write_txn
381                .open_table(MINT_QUOTES_TABLE)
382                .map_err(Error::from)?;
383            table
384                .insert(
385                    quote.id.as_str(),
386                    serde_json::to_string(&quote).map_err(Error::from)?.as_str(),
387                )
388                .map_err(Error::from)?;
389        }
390
391        write_txn.commit().map_err(Error::from)?;
392
393        Ok(())
394    }
395
396    #[instrument(skip_all)]
397    async fn get_mint_quote(&self, quote_id: &str) -> Result<Option<MintQuote>, Self::Err> {
398        let read_txn = self.db.begin_read().map_err(Into::<Error>::into)?;
399        let table = read_txn
400            .open_table(MINT_QUOTES_TABLE)
401            .map_err(Error::from)?;
402
403        if let Some(mint_info) = table.get(quote_id).map_err(Error::from)? {
404            return Ok(serde_json::from_str(mint_info.value()).map_err(Error::from)?);
405        }
406
407        Ok(None)
408    }
409
410    #[instrument(skip_all)]
411    async fn get_mint_quotes(&self) -> Result<Vec<MintQuote>, Self::Err> {
412        let read_txn = self.db.begin_read().map_err(Into::<Error>::into)?;
413        let table = read_txn
414            .open_table(MINT_QUOTES_TABLE)
415            .map_err(Error::from)?;
416
417        Ok(table
418            .iter()
419            .map_err(Error::from)?
420            .flatten()
421            .flat_map(|(_id, quote)| serde_json::from_str(quote.value()))
422            .collect())
423    }
424
425    #[instrument(skip_all)]
426    async fn remove_mint_quote(&self, quote_id: &str) -> Result<(), Self::Err> {
427        let write_txn = self.db.begin_write().map_err(Error::from)?;
428
429        {
430            let mut table = write_txn
431                .open_table(MINT_QUOTES_TABLE)
432                .map_err(Error::from)?;
433            table.remove(quote_id).map_err(Error::from)?;
434        }
435
436        write_txn.commit().map_err(Error::from)?;
437
438        Ok(())
439    }
440
441    #[instrument(skip_all)]
442    async fn add_melt_quote(&self, quote: wallet::MeltQuote) -> Result<(), Self::Err> {
443        let write_txn = self.db.begin_write().map_err(Error::from)?;
444
445        {
446            let mut table = write_txn
447                .open_table(MELT_QUOTES_TABLE)
448                .map_err(Error::from)?;
449            table
450                .insert(
451                    quote.id.as_str(),
452                    serde_json::to_string(&quote).map_err(Error::from)?.as_str(),
453                )
454                .map_err(Error::from)?;
455        }
456
457        write_txn.commit().map_err(Error::from)?;
458
459        Ok(())
460    }
461
462    #[instrument(skip_all)]
463    async fn get_melt_quote(&self, quote_id: &str) -> Result<Option<wallet::MeltQuote>, Self::Err> {
464        let read_txn = self.db.begin_read().map_err(Error::from)?;
465        let table = read_txn
466            .open_table(MELT_QUOTES_TABLE)
467            .map_err(Error::from)?;
468
469        if let Some(mint_info) = table.get(quote_id).map_err(Error::from)? {
470            return Ok(serde_json::from_str(mint_info.value()).map_err(Error::from)?);
471        }
472
473        Ok(None)
474    }
475
476    #[instrument(skip_all)]
477    async fn remove_melt_quote(&self, quote_id: &str) -> Result<(), Self::Err> {
478        let write_txn = self.db.begin_write().map_err(Error::from)?;
479
480        {
481            let mut table = write_txn
482                .open_table(MELT_QUOTES_TABLE)
483                .map_err(Error::from)?;
484            table.remove(quote_id).map_err(Error::from)?;
485        }
486
487        write_txn.commit().map_err(Error::from)?;
488
489        Ok(())
490    }
491
492    #[instrument(skip_all)]
493    async fn add_keys(&self, keys: Keys) -> Result<(), Self::Err> {
494        let write_txn = self.db.begin_write().map_err(Error::from)?;
495
496        {
497            let mut table = write_txn.open_table(MINT_KEYS_TABLE).map_err(Error::from)?;
498            table
499                .insert(
500                    Id::from(&keys).to_string().as_str(),
501                    serde_json::to_string(&keys).map_err(Error::from)?.as_str(),
502                )
503                .map_err(Error::from)?;
504        }
505
506        write_txn.commit().map_err(Error::from)?;
507
508        Ok(())
509    }
510
511    #[instrument(skip(self), fields(keyset_id = %keyset_id))]
512    async fn get_keys(&self, keyset_id: &Id) -> Result<Option<Keys>, Self::Err> {
513        let read_txn = self.db.begin_read().map_err(Error::from)?;
514        let table = read_txn.open_table(MINT_KEYS_TABLE).map_err(Error::from)?;
515
516        if let Some(mint_info) = table
517            .get(keyset_id.to_string().as_str())
518            .map_err(Error::from)?
519        {
520            return Ok(serde_json::from_str(mint_info.value()).map_err(Error::from)?);
521        }
522
523        Ok(None)
524    }
525
526    #[instrument(skip(self), fields(keyset_id = %keyset_id))]
527    async fn remove_keys(&self, keyset_id: &Id) -> 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(MINT_KEYS_TABLE).map_err(Error::from)?;
532
533            table
534                .remove(keyset_id.to_string().as_str())
535                .map_err(Error::from)?;
536        }
537
538        write_txn.commit().map_err(Error::from)?;
539
540        Ok(())
541    }
542
543    #[instrument(skip(self, added, deleted_ys))]
544    async fn update_proofs(
545        &self,
546        added: Vec<ProofInfo>,
547        deleted_ys: Vec<PublicKey>,
548    ) -> Result<(), Self::Err> {
549        let write_txn = self.db.begin_write().map_err(Error::from)?;
550
551        {
552            let mut table = write_txn.open_table(PROOFS_TABLE).map_err(Error::from)?;
553
554            for proof_info in added.iter() {
555                table
556                    .insert(
557                        proof_info.y.to_bytes().as_slice(),
558                        serde_json::to_string(&proof_info)
559                            .map_err(Error::from)?
560                            .as_str(),
561                    )
562                    .map_err(Error::from)?;
563            }
564
565            for y in deleted_ys.iter() {
566                table.remove(y.to_bytes().as_slice()).map_err(Error::from)?;
567            }
568        }
569        write_txn.commit().map_err(Error::from)?;
570
571        Ok(())
572    }
573
574    #[instrument(skip_all)]
575    async fn get_proofs(
576        &self,
577        mint_url: Option<MintUrl>,
578        unit: Option<CurrencyUnit>,
579        state: Option<Vec<State>>,
580        spending_conditions: Option<Vec<SpendingConditions>>,
581    ) -> Result<Vec<ProofInfo>, Self::Err> {
582        let read_txn = self.db.begin_read().map_err(Error::from)?;
583
584        let table = read_txn.open_table(PROOFS_TABLE).map_err(Error::from)?;
585
586        let proofs: Vec<ProofInfo> = table
587            .iter()
588            .map_err(Error::from)?
589            .flatten()
590            .filter_map(|(_k, v)| {
591                let mut proof = None;
592
593                if let Ok(proof_info) = serde_json::from_str::<ProofInfo>(v.value()) {
594                    if proof_info.matches_conditions(&mint_url, &unit, &state, &spending_conditions)
595                    {
596                        proof = Some(proof_info)
597                    }
598                }
599
600                proof
601            })
602            .collect();
603
604        Ok(proofs)
605    }
606
607    async fn update_proofs_state(
608        &self,
609        ys: Vec<PublicKey>,
610        state: State,
611    ) -> Result<(), database::Error> {
612        let read_txn = self.db.begin_read().map_err(Error::from)?;
613        let table = read_txn.open_table(PROOFS_TABLE).map_err(Error::from)?;
614
615        let write_txn = self.db.begin_write().map_err(Error::from)?;
616
617        for y in ys {
618            let y_slice = y.to_bytes();
619            let proof = table
620                .get(y_slice.as_slice())
621                .map_err(Error::from)?
622                .ok_or(Error::UnknownY)?;
623
624            let mut proof_info =
625                serde_json::from_str::<ProofInfo>(proof.value()).map_err(Error::from)?;
626
627            proof_info.state = state;
628
629            {
630                let mut table = write_txn.open_table(PROOFS_TABLE).map_err(Error::from)?;
631                table
632                    .insert(
633                        y_slice.as_slice(),
634                        serde_json::to_string(&proof_info)
635                            .map_err(Error::from)?
636                            .as_str(),
637                    )
638                    .map_err(Error::from)?;
639            }
640        }
641
642        write_txn.commit().map_err(Error::from)?;
643
644        Ok(())
645    }
646
647    #[instrument(skip(self), fields(keyset_id = %keyset_id))]
648    async fn increment_keyset_counter(&self, keyset_id: &Id, count: u32) -> Result<(), Self::Err> {
649        let write_txn = self.db.begin_write().map_err(Error::from)?;
650
651        let current_counter;
652        {
653            let table = write_txn.open_table(KEYSET_COUNTER).map_err(Error::from)?;
654            let counter = table
655                .get(keyset_id.to_string().as_str())
656                .map_err(Error::from)?;
657
658            current_counter = match counter {
659                Some(c) => c.value(),
660                None => 0,
661            };
662        }
663
664        {
665            let mut table = write_txn.open_table(KEYSET_COUNTER).map_err(Error::from)?;
666            let new_counter = current_counter + count;
667
668            table
669                .insert(keyset_id.to_string().as_str(), new_counter)
670                .map_err(Error::from)?;
671        }
672        write_txn.commit().map_err(Error::from)?;
673
674        Ok(())
675    }
676
677    #[instrument(skip(self), fields(keyset_id = %keyset_id))]
678    async fn get_keyset_counter(&self, keyset_id: &Id) -> Result<Option<u32>, Self::Err> {
679        let read_txn = self.db.begin_read().map_err(Error::from)?;
680        let table = read_txn.open_table(KEYSET_COUNTER).map_err(Error::from)?;
681
682        let counter = table
683            .get(keyset_id.to_string().as_str())
684            .map_err(Error::from)?;
685
686        Ok(counter.map(|c| c.value()))
687    }
688}