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