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