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