1use 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
27const MINTS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mints_table");
29const MINT_KEYSETS_TABLE: MultimapTableDefinition<&str, &[u8]> =
31 MultimapTableDefinition::new("mint_keysets");
32const KEYSETS_TABLE: TableDefinition<&[u8], &str> = TableDefinition::new("keysets");
34const MINT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_quotes");
36const MELT_QUOTES_TABLE: TableDefinition<&str, &str> = TableDefinition::new("melt_quotes");
38const MINT_KEYS_TABLE: TableDefinition<&str, &str> = TableDefinition::new("mint_keys");
39const 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#[derive(Debug, Clone)]
48pub struct WalletRedbDatabase {
49 db: Arc<Database>,
50}
51
52impl WalletRedbDatabase {
53 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 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 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 {
235 let proofs = self
236 .get_proofs(Some(old_mint_url.clone()), None, None, None)
237 .await
238 .map_err(Error::from)?;
239
240 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 {
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("e).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("e).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}