1#![warn(missing_docs)]
2#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
78pub use redb;
79
80pub mod anchor_trait;
81pub mod error;
82
83use anchor_trait::AnchorWithMetaData;
84use bdk_chain::bitcoin::{self, Network, OutPoint, Transaction, Txid};
85use bdk_chain::bitcoin::{Amount, BlockHash, ScriptBuf, TxOut, hashes::Hash};
86use bdk_chain::miniscript::descriptor::{Descriptor, DescriptorPublicKey};
87use bdk_chain::{BlockId, DescriptorId, keychain_txout, local_chain, tx_graph};
88#[cfg(feature = "wallet")]
89use bdk_wallet::{ChangeSet, WalletPersister};
90use error::StoreError;
91use redb::{Database, ReadTransaction, ReadableTable, TableDefinition, WriteTransaction};
92use std::collections::{BTreeMap, BTreeSet};
93use std::str::FromStr;
94use std::sync::Arc;
95
96#[cfg(feature = "wallet")]
97use bdk_chain::ConfirmationBlockTime;
98
99const NETWORK: TableDefinition<&str, String> = TableDefinition::new("network");
102
103#[derive(Debug)]
112pub struct Store {
113 db: Arc<Database>,
116 wallet_name: String,
117
118 keychain_table_name: String,
121 last_revealed_table_name: String,
122 blocks_table_name: String,
123 txouts_table_name: String,
124 last_seen_table_name: String,
125 txs_table_name: String,
126 anchors_table_name: String,
127 last_evicted_table_name: String,
128 first_seen_table_name: String,
129 spk_table_name: String,
130}
131
132impl Store {
133 fn keychains_table_defn(&self) -> TableDefinition<u64, String> {
135 TableDefinition::new(&self.keychain_table_name)
136 }
137
138 fn blocks_table_defn(&self) -> TableDefinition<u32, [u8; 32]> {
140 TableDefinition::new(&self.blocks_table_name)
141 }
142
143 fn txs_table_defn(&self) -> TableDefinition<[u8; 32], Vec<u8>> {
145 TableDefinition::new(&self.txs_table_name)
146 }
147
148 fn txouts_table_defn(&self) -> TableDefinition<([u8; 32], u32), (u64, Vec<u8>)> {
151 TableDefinition::new(&self.txouts_table_name)
152 }
153
154 fn anchors_table_defn<A: AnchorWithMetaData>(
160 &self,
161 ) -> TableDefinition<([u8; 32], [u8; 36]), A::MetaDataType> {
162 TableDefinition::new(&self.anchors_table_name)
163 }
164
165 fn last_seen_defn(&self) -> TableDefinition<[u8; 32], u64> {
167 TableDefinition::new(&self.last_seen_table_name)
168 }
169
170 fn last_evicted_table_defn(&self) -> TableDefinition<[u8; 32], u64> {
172 TableDefinition::new(&self.last_evicted_table_name)
173 }
174
175 fn first_seen_table_defn(&self) -> TableDefinition<[u8; 32], u64> {
177 TableDefinition::new(&self.first_seen_table_name)
178 }
179
180 fn last_revealed_table_defn(&self) -> TableDefinition<[u8; 32], u32> {
182 TableDefinition::new(&self.last_revealed_table_name)
183 }
184
185 fn spk_table_defn(&self) -> TableDefinition<([u8; 32], u32), Vec<u8>> {
187 TableDefinition::new(&self.spk_table_name)
188 }
189
190 pub fn new(db: Arc<Database>, wallet_name: String) -> Result<Self, StoreError> {
194 let mut keychain_table_name = wallet_name.clone();
196 keychain_table_name.push_str("_keychain");
197 let mut blocks_table_name = wallet_name.clone();
198 blocks_table_name.push_str("_blocks");
199 let mut txs_table_name = wallet_name.clone();
200 txs_table_name.push_str("_txs");
201 let mut txouts_table_name = wallet_name.clone();
202 txouts_table_name.push_str("_txouts");
203 let mut anchors_table_name = wallet_name.clone();
204 anchors_table_name.push_str("_anchors");
205 let mut last_seen_table_name = wallet_name.clone();
206 last_seen_table_name.push_str("_last_seen");
207 let mut last_evicted_table_name = wallet_name.clone();
208 last_evicted_table_name.push_str("_last_evicted");
209 let mut first_seen_table_name = wallet_name.clone();
210 first_seen_table_name.push_str("_first_seen");
211 let mut last_revealed_table_name = wallet_name.clone();
212 last_revealed_table_name.push_str("_last_revealed");
213 let mut spk_table_name = wallet_name.clone();
214 spk_table_name.push_str("_spk");
215 Ok(Store {
216 db,
217 wallet_name,
218 keychain_table_name,
219 blocks_table_name,
220 txs_table_name,
221 txouts_table_name,
222 anchors_table_name,
223 last_seen_table_name,
224 last_evicted_table_name,
225 first_seen_table_name,
226 last_revealed_table_name,
227 spk_table_name,
228 })
229 }
230
231 pub fn create_tables<A: AnchorWithMetaData>(&self) -> Result<(), StoreError> {
236 let write_tx = self.db.begin_write()?;
237
238 let _ = write_tx.open_table(NETWORK)?;
239 let _ = write_tx.open_table(self.keychains_table_defn())?;
240 write_tx.commit()?;
241
242 self.create_local_chain_tables()?;
243 self.create_tx_graph_tables::<A>()?;
244 self.create_indexer_tables()?;
245
246 Ok(())
247 }
248
249 pub fn create_local_chain_tables(&self) -> Result<(), StoreError> {
252 let write_tx = self.db.begin_write()?;
253 let _ = write_tx.open_table(self.blocks_table_defn())?;
254 write_tx.commit()?;
255 Ok(())
256 }
257
258 pub fn create_tx_graph_tables<A: AnchorWithMetaData>(&self) -> Result<(), StoreError> {
263 let write_tx = self.db.begin_write()?;
264 let _ = write_tx.open_table(self.txs_table_defn())?;
265 let _ = write_tx.open_table(self.txouts_table_defn())?;
266 let _ = write_tx.open_table(self.anchors_table_defn::<A>())?;
267 let _ = write_tx.open_table(self.last_seen_defn())?;
268 let _ = write_tx.open_table(self.last_evicted_table_defn())?;
269 let _ = write_tx.open_table(self.first_seen_table_defn())?;
270
271 write_tx.commit()?;
272 Ok(())
273 }
274
275 pub fn create_indexer_tables(&self) -> Result<(), StoreError> {
280 let write_tx = self.db.begin_write()?;
281 let _ = write_tx.open_table(self.spk_table_defn())?;
282
283 let _ = write_tx.open_table(self.last_revealed_table_defn())?;
284 write_tx.commit()?;
285 Ok(())
286 }
287
288 pub fn create_keychains_table(&self) -> Result<(), StoreError> {
291 let write_tx = self.db.begin_write()?;
292 let _ = write_tx.open_table(self.keychains_table_defn())?;
293 write_tx.commit()?;
294 Ok(())
295 }
296
297 pub fn create_network_table(&self) -> Result<(), StoreError> {
302 let write_tx = self.db.begin_write()?;
303 let _ = write_tx.open_table(NETWORK)?;
304 write_tx.commit()?;
305 Ok(())
306 }
307
308 #[cfg(feature = "wallet")]
309 pub fn persist_wallet(&self, changeset: &ChangeSet) -> Result<(), StoreError> {
314 self.persist_network(&changeset.network)?;
315 let mut desc_changeset: BTreeMap<u64, Descriptor<DescriptorPublicKey>> = BTreeMap::new();
316 if let Some(desc) = &changeset.descriptor {
317 desc_changeset.insert(0, desc.clone());
318 if let Some(change_desc) = &changeset.change_descriptor {
319 desc_changeset.insert(1, change_desc.clone());
320 }
321 }
322 self.persist_keychains(&desc_changeset)?;
323 self.persist_local_chain(&changeset.local_chain)?;
324 self.persist_indexer(&changeset.indexer)?;
325 self.persist_tx_graph::<ConfirmationBlockTime>(&changeset.tx_graph)?;
326 Ok(())
327 }
328
329 pub fn persist_tx_graph<A: AnchorWithMetaData>(
334 &self,
335 changeset: &tx_graph::ChangeSet<A>,
336 ) -> Result<(), StoreError> {
337 let write_tx = self.db.begin_write()?;
338 let read_tx = self.db.begin_read()?;
339 self.persist_txs(&write_tx, &changeset.txs)?;
340 self.persist_txouts(&write_tx, &changeset.txouts)?;
341 self.persist_anchors::<A>(&write_tx, &read_tx, &changeset.anchors, &changeset.txs)?;
342 self.persist_last_seen(&write_tx, &read_tx, &changeset.last_seen, &changeset.txs)?;
343 self.persist_last_evicted(&write_tx, &read_tx, &changeset.last_evicted, &changeset.txs)?;
344 self.persist_first_seen(&write_tx, &read_tx, &changeset.first_seen, &changeset.txs)?;
345 write_tx.commit()?;
346 Ok(())
347 }
348
349 pub fn persist_indexer(&self, changeset: &keychain_txout::ChangeSet) -> Result<(), StoreError> {
354 let write_tx = self.db.begin_write()?;
355 self.persist_last_revealed(&write_tx, &changeset.last_revealed)?;
356 self.persist_spks(&write_tx, &changeset.spk_cache)?;
357 write_tx.commit()?;
358 Ok(())
359 }
360
361 pub fn persist_keychains(
363 &self,
364 changeset: &BTreeMap<u64, Descriptor<DescriptorPublicKey>>,
366 ) -> Result<(), StoreError> {
367 let write_tx = self.db.begin_write()?;
368 {
369 let mut table = write_tx.open_table(self.keychains_table_defn())?;
370
371 for (label, desc) in changeset {
373 table.insert(label, desc.to_string())?;
374 }
375 }
376 write_tx.commit()?;
377 Ok(())
378 }
379
380 pub fn persist_network(&self, network: &Option<bitcoin::Network>) -> Result<(), StoreError> {
385 let write_tx = self.db.begin_write()?;
386 {
387 let mut table = write_tx.open_table(NETWORK)?;
388 if let Some(network) = network {
390 table.insert(&*self.wallet_name, network.to_string())?;
391 }
392 }
393 write_tx.commit()?;
394 Ok(())
395 }
396
397 pub fn persist_local_chain(
402 &self,
403 changeset: &local_chain::ChangeSet,
404 ) -> Result<(), StoreError> {
405 let write_tx = self.db.begin_write()?;
406 self.persist_blocks(&write_tx, &changeset.blocks)?;
407 write_tx.commit()?;
408 Ok(())
409 }
410
411 fn persist_blocks(
413 &self,
414 write_tx: &WriteTransaction,
415 blocks: &BTreeMap<u32, Option<BlockHash>>,
416 ) -> Result<(), StoreError> {
417 let mut table = write_tx.open_table(self.blocks_table_defn())?;
418 for (ht, hash) in blocks {
419 match hash {
420 &Some(hash) => table.insert(*ht, hash.to_byte_array())?,
421 None => table.remove(*ht)?,
425 };
426 }
427 Ok(())
428 }
429
430 fn persist_txs(
432 &self,
433 write_tx: &WriteTransaction,
434 txs: &BTreeSet<Arc<Transaction>>,
435 ) -> Result<(), StoreError> {
436 let mut table = write_tx.open_table(self.txs_table_defn())?;
437 for tx in txs {
438 let mut vec: Vec<u8> = Vec::new();
439 ciborium::into_writer(tx, &mut vec)?;
440 table.insert(tx.compute_txid().to_byte_array(), vec)?;
441 }
442 Ok(())
443 }
444
445 fn persist_txouts(
447 &self,
448 write_tx: &WriteTransaction,
449 txouts: &BTreeMap<OutPoint, TxOut>,
450 ) -> Result<(), StoreError> {
451 let mut table = write_tx.open_table(self.txouts_table_defn())?;
452 for (outpoint, txout) in txouts {
453 table.insert(
454 (outpoint.txid.to_byte_array(), outpoint.vout),
455 (
456 txout.value.to_sat(),
457 txout.script_pubkey.clone().into_bytes(),
458 ),
459 )?;
460 }
461 Ok(())
462 }
463
464 fn persist_anchors<A: AnchorWithMetaData>(
466 &self,
467 write_tx: &WriteTransaction,
468 read_tx: &ReadTransaction,
469 anchors: &BTreeSet<(A, Txid)>,
470 txs: &BTreeSet<Arc<Transaction>>,
471 ) -> Result<(), StoreError> {
472 let mut table = write_tx.open_table(self.anchors_table_defn::<A>())?;
473 let txs_table = read_tx.open_table(self.txs_table_defn())?;
474 for (anchor, txid) in anchors {
475 let found = txs.iter().any(|tx| tx.compute_txid() == *txid);
478 if txs_table.get(txid.to_byte_array())?.is_some() || found {
479 let mut bytes: [u8; 36] = [0; 36];
480 let anchor_block = anchor.anchor_block();
481 bytes[0..4].copy_from_slice(&anchor_block.height.to_le_bytes());
482 bytes[4..].copy_from_slice(&anchor_block.hash.to_byte_array());
483 table.insert((txid.to_byte_array(), bytes), &anchor.metadata())?;
484 } else {
485 return Err(StoreError::TxMissing(*txid));
486 }
487 }
488 Ok(())
489 }
490
491 fn persist_last_seen(
493 &self,
494 write_tx: &WriteTransaction,
495 read_tx: &ReadTransaction,
496 last_seen: &BTreeMap<Txid, u64>,
497 txs: &BTreeSet<Arc<Transaction>>,
498 ) -> Result<(), StoreError> {
499 let mut table = write_tx.open_table(self.last_seen_defn())?;
500 let txs_table = read_tx.open_table(self.txs_table_defn())?;
501 for (txid, last_seen_time) in last_seen {
502 let found = txs.iter().any(|tx| tx.compute_txid() == *txid);
505 if txs_table.get(txid.to_byte_array())?.is_some() || found {
506 table.insert(txid.to_byte_array(), *last_seen_time)?;
507 } else {
508 return Err(StoreError::TxMissing(*txid));
509 }
510 }
511 Ok(())
512 }
513
514 fn persist_last_evicted(
516 &self,
517 write_tx: &WriteTransaction,
518 read_tx: &ReadTransaction,
519 last_evicted: &BTreeMap<Txid, u64>,
520 txs: &BTreeSet<Arc<Transaction>>,
521 ) -> Result<(), StoreError> {
522 let mut table = write_tx.open_table(self.last_evicted_table_defn())?;
523 let txs_table = read_tx.open_table(self.txs_table_defn())?;
524 for (txid, last_evicted_time) in last_evicted {
525 let found = txs.iter().any(|tx| tx.compute_txid() == *txid);
528 if txs_table.get(txid.to_byte_array())?.is_some() || found {
529 table.insert(txid.to_byte_array(), last_evicted_time)?;
530 } else {
531 return Err(StoreError::TxMissing(*txid));
532 }
533 }
534 Ok(())
535 }
536
537 fn persist_first_seen(
539 &self,
540 write_tx: &WriteTransaction,
541 read_tx: &ReadTransaction,
542 first_seen: &BTreeMap<Txid, u64>,
543 txs: &BTreeSet<Arc<Transaction>>,
544 ) -> Result<(), StoreError> {
545 let mut table = write_tx.open_table(self.first_seen_table_defn())?;
546 let txs_table = read_tx.open_table(self.txs_table_defn())?;
547 for (txid, first_seen_time) in first_seen {
548 let found = txs.iter().any(|tx| tx.compute_txid() == *txid);
551 if txs_table.get(txid.to_byte_array())?.is_some() || found {
552 table.insert(txid.to_byte_array(), first_seen_time)?;
553 } else {
554 return Err(StoreError::TxMissing(*txid));
555 }
556 }
557 Ok(())
558 }
559
560 fn persist_last_revealed(
562 &self,
563 write_tx: &WriteTransaction,
564 last_revealed: &BTreeMap<DescriptorId, u32>,
565 ) -> Result<(), StoreError> {
566 let mut table = write_tx.open_table(self.last_revealed_table_defn())?;
567 for (&desc, &idx) in last_revealed {
568 table.insert(desc.to_byte_array(), idx)?;
569 }
570 Ok(())
571 }
572
573 fn persist_spks(
575 &self,
576 write_tx: &WriteTransaction,
577 spk_cache: &BTreeMap<DescriptorId, BTreeMap<u32, ScriptBuf>>,
578 ) -> Result<(), StoreError> {
579 let mut table = write_tx.open_table(self.spk_table_defn())?;
580 for (&desc, map) in spk_cache {
581 map.iter().try_for_each(|entry| {
582 table
583 .insert((desc.to_byte_array(), *entry.0), entry.1.to_bytes())
584 .map(|_| ())
585 })?;
586 }
587 Ok(())
588 }
589
590 #[cfg(feature = "wallet")]
591 pub fn read_wallet(&self, changeset: &mut ChangeSet) -> Result<(), StoreError> {
596 self.read_network(&mut changeset.network)?;
597 let mut desc_changeset: BTreeMap<u64, Descriptor<DescriptorPublicKey>> = BTreeMap::new();
598 self.read_keychains(&mut desc_changeset)?;
599 if let Some(desc) = desc_changeset.get(&0) {
600 changeset.descriptor = Some(desc.clone());
601 if let Some(change_desc) = desc_changeset.get(&1) {
602 changeset.change_descriptor = Some(change_desc.clone());
603 }
604 }
605 self.read_local_chain(&mut changeset.local_chain)?;
606 self.read_tx_graph::<ConfirmationBlockTime>(&mut changeset.tx_graph)?;
607 self.read_indexer(&mut changeset.indexer)?;
608
609 Ok(())
610 }
611
612 pub fn read_tx_graph<A: AnchorWithMetaData>(
617 &self,
618 changeset: &mut tx_graph::ChangeSet<A>,
619 ) -> Result<(), StoreError> {
620 let read_tx = self.db.begin_read()?;
621 self.read_txs(&read_tx, &mut changeset.txs)?;
622 self.read_txouts(&read_tx, &mut changeset.txouts)?;
623 self.read_anchors::<A>(&read_tx, &mut changeset.anchors)?;
624 self.read_last_seen(&read_tx, &mut changeset.last_seen)?;
625 self.read_last_evicted(&read_tx, &mut changeset.last_evicted)?;
626 self.read_first_seen(&read_tx, &mut changeset.first_seen)?;
627 Ok(())
628 }
629
630 pub fn read_indexer(
635 &self,
636 changeset: &mut keychain_txout::ChangeSet,
637 ) -> Result<(), StoreError> {
638 let read_tx = self.db.begin_read()?;
639 self.read_last_revealed(&read_tx, &mut changeset.last_revealed)?;
640 self.read_spks(&read_tx, &mut changeset.spk_cache)?;
641 Ok(())
642 }
643
644 pub fn read_keychains(
646 &self,
647 desc_changeset: &mut BTreeMap<u64, Descriptor<DescriptorPublicKey>>,
648 ) -> Result<(), StoreError> {
649 let read_tx = self.db.begin_read()?;
650 let table = read_tx.open_table(self.keychains_table_defn())?;
651
652 for entry in table.iter()? {
653 let (label, keychain) = entry?;
654 desc_changeset.insert(
655 label.value(),
656 Descriptor::<DescriptorPublicKey>::from_str(keychain.value().as_str())
657 .expect("should be valid descriptors"),
658 );
659 }
660
661 Ok(())
662 }
663
664 pub fn read_network(&self, network: &mut Option<bitcoin::Network>) -> Result<(), StoreError> {
669 let read_tx = self.db.begin_read()?;
670 let table = read_tx.open_table(NETWORK)?;
671 *network = table
672 .get(&*self.wallet_name)?
673 .map(|network| Network::from_str(&network.value()).expect("should be valid network"));
674 Ok(())
675 }
676
677 pub fn read_local_chain(
682 &self,
683 changeset: &mut local_chain::ChangeSet,
684 ) -> Result<(), StoreError> {
685 let read_tx = self.db.begin_read()?;
686 self.read_blocks(&read_tx, &mut changeset.blocks)?;
687 Ok(())
688 }
689
690 fn read_blocks(
692 &self,
693 read_tx: &ReadTransaction,
694 blocks: &mut BTreeMap<u32, Option<BlockHash>>,
695 ) -> Result<(), StoreError> {
696 let table = read_tx.open_table(self.blocks_table_defn())?;
697
698 for entry in table.iter()? {
699 let (height, hash) = entry?;
700 blocks.insert(
701 height.value(),
702 Some(BlockHash::from_byte_array(hash.value())),
703 );
704 }
705
706 Ok(())
707 }
708
709 fn read_txs(
711 &self,
712 read_tx: &ReadTransaction,
713 txs: &mut BTreeSet<Arc<Transaction>>,
714 ) -> Result<(), StoreError> {
715 let table = read_tx.open_table(self.txs_table_defn())?;
716
717 for entry in table.iter()? {
718 let tx_vec = entry?.1.value();
719 let tx = ciborium::from_reader(tx_vec.as_slice())?;
720 txs.insert(Arc::new(tx));
721 }
722 Ok(())
723 }
724
725 fn read_txouts(
727 &self,
728 read_tx: &ReadTransaction,
729 txouts: &mut BTreeMap<OutPoint, TxOut>,
730 ) -> Result<(), StoreError> {
731 let table = read_tx.open_table(self.txouts_table_defn())?;
732
733 for entry in table.iter()? {
734 let (outpoint, txout) = entry?;
735 txouts.insert(
736 OutPoint {
737 txid: Txid::from_byte_array(outpoint.value().0),
738 vout: outpoint.value().1,
739 },
740 TxOut {
741 value: Amount::from_sat(txout.value().0),
742 script_pubkey: ScriptBuf::from_bytes(txout.value().1),
743 },
744 );
745 }
746
747 Ok(())
748 }
749
750 fn read_anchors<A: AnchorWithMetaData>(
752 &self,
753 read_tx: &ReadTransaction,
754 anchors: &mut BTreeSet<(A, Txid)>,
755 ) -> Result<(), StoreError> {
756 let table = read_tx.open_table(self.anchors_table_defn::<A>())?;
757
758 for entry in table.iter()? {
759 let (anchor, metadata) = entry?;
760 let (txid_bytes, block_id_bytes) = anchor.value();
761 let block_id = BlockId {
762 height: u32::from_le_bytes(
763 block_id_bytes[0..4].try_into().expect("slice has length 4"),
764 ),
765 hash: BlockHash::from_slice(&block_id_bytes[4..])?,
766 };
767 anchors.insert((
768 A::from_id(block_id, metadata.value()),
769 Txid::from_byte_array(txid_bytes),
770 ));
771 }
772
773 Ok(())
774 }
775
776 fn read_last_seen(
778 &self,
779 read_tx: &ReadTransaction,
780 last_seen: &mut BTreeMap<Txid, u64>,
781 ) -> Result<(), StoreError> {
782 let table = read_tx.open_table(self.last_seen_defn())?;
783
784 for entry in table.iter()? {
785 let (txid, last_seen_num) = entry?;
786 last_seen.insert(Txid::from_byte_array(txid.value()), last_seen_num.value());
787 }
788 Ok(())
789 }
790
791 fn read_last_evicted(
793 &self,
794 read_tx: &ReadTransaction,
795 last_evicted: &mut BTreeMap<Txid, u64>,
796 ) -> Result<(), StoreError> {
797 let table = read_tx.open_table(self.last_evicted_table_defn())?;
798
799 for entry in table.iter()? {
800 let (txid, last_evicted_num) = entry?;
801 last_evicted.insert(
802 Txid::from_byte_array(txid.value()),
803 last_evicted_num.value(),
804 );
805 }
806 Ok(())
807 }
808
809 fn read_first_seen(
811 &self,
812 read_tx: &ReadTransaction,
813 first_seen: &mut BTreeMap<Txid, u64>,
814 ) -> Result<(), StoreError> {
815 let table = read_tx.open_table(self.first_seen_table_defn())?;
816
817 for entry in table.iter()? {
818 let (txid, first_seen_num) = entry?;
819 first_seen.insert(Txid::from_byte_array(txid.value()), first_seen_num.value());
820 }
821 Ok(())
822 }
823
824 fn read_last_revealed(
826 &self,
827 read_tx: &ReadTransaction,
828 last_revealed: &mut BTreeMap<DescriptorId, u32>,
829 ) -> Result<(), StoreError> {
830 let table = read_tx.open_table(self.last_revealed_table_defn())?;
831
832 for entry in table.iter()? {
833 let (desc, last_revealed_idx) = entry?;
834 last_revealed.insert(
835 DescriptorId::from_byte_array(desc.value()),
836 last_revealed_idx.value(),
837 );
838 }
839 Ok(())
840 }
841
842 fn read_spks(
844 &self,
845 read_tx: &ReadTransaction,
846 spk_cache: &mut BTreeMap<DescriptorId, BTreeMap<u32, ScriptBuf>>,
847 ) -> Result<(), StoreError> {
848 let table = read_tx.open_table(self.spk_table_defn())?;
849
850 for entry in table.iter()? {
851 let (desc, spk) = entry?;
852 spk_cache
853 .entry(DescriptorId::from_byte_array(desc.value().0))
854 .or_default()
855 .insert(desc.value().1, ScriptBuf::from_bytes(spk.value()));
856 }
857 Ok(())
858 }
859}
860
861#[cfg(feature = "wallet")]
862impl WalletPersister for Store {
863 type Error = StoreError;
864 fn initialize(persister: &mut Self) -> Result<ChangeSet, Self::Error> {
865 persister.create_tables::<ConfirmationBlockTime>()?;
866 let mut changeset = ChangeSet::default();
867 persister.read_wallet(&mut changeset)?;
868 Ok(changeset)
869 }
870
871 fn persist(persister: &mut Self, changeset: &ChangeSet) -> Result<(), Self::Error> {
872 persister.persist_wallet(changeset)?;
873 Ok(())
874 }
875}
876
877#[cfg(test)]
878#[cfg_attr(coverage_nightly, coverage(off))]
879mod test {
880 use super::*;
881 use bdk_chain::BlockId;
882 use bdk_chain::ConfirmationBlockTime;
883 use bdk_chain::{
884 DescriptorExt, Merge,
885 bitcoin::{
886 self, Amount, BlockHash, OutPoint, ScriptBuf, Transaction, TxIn, TxOut, absolute,
887 hashes::Hash, transaction, transaction::Txid,
888 },
889 keychain_txout, local_chain,
890 miniscript::descriptor::Descriptor,
891 };
892
893 use std::sync::Arc;
894 use std::{collections::BTreeMap, path::Path};
895 use tempfile::NamedTempFile;
896
897 use bdk_testenv::{block_id, hash, utils};
898
899 const DESCRIPTORS: [&str; 4] = [
900 "tr([5940b9b9/86'/0'/0']tpubDDVNqmq75GNPWQ9UNKfP43UwjaHU4GYfoPavojQbfpyfZp2KetWgjGBRRAy4tYCrAA6SB11mhQAkqxjh1VtQHyKwT4oYxpwLaGHvoKmtxZf/1/*)#ypcpw2dr",
901 "tr([5940b9b9/86'/0'/0']tpubDDVNqmq75GNPWQ9UNKfP43UwjaHU4GYfoPavojQbfpyfZp2KetWgjGBRRAy4tYCrAA6SB11mhQAkqxjh1VtQHyKwT4oYxpwLaGHvoKmtxZf/0/*)#44aqnlam",
902 "wpkh([41f2aed0/84h/1h/0h]tpubDDFSdQWw75hk1ewbwnNpPp5DvXFRKt68ioPoyJDY752cNHKkFxPWqkqCyCf4hxrEfpuxh46QisehL3m8Bi6MsAv394QVLopwbtfvryFQNUH/1/*)#emtwewtk",
903 "wpkh([41f2aed0/84h/1h/0h]tpubDDFSdQWw75hk1ewbwnNpPp5DvXFRKt68ioPoyJDY752cNHKkFxPWqkqCyCf4hxrEfpuxh46QisehL3m8Bi6MsAv394QVLopwbtfvryFQNUH/0/*)#g0w0ymmw",
904 ];
905
906 fn create_db(path: impl AsRef<Path>) -> Database {
907 Database::create(path).unwrap()
908 }
909
910 fn create_test_store(db: Arc<Database>, wallet_name: &str) -> Store {
911 Store::new(db, wallet_name.to_string()).unwrap()
912 }
913
914 #[test]
915 fn test_network_persistence() {
916 let tmpfile = NamedTempFile::new().unwrap();
917 let db = create_db(tmpfile.path());
918 let store = create_test_store(Arc::new(db), "wallet1");
919 store.create_network_table().unwrap();
920 let network_changeset = Some(Network::Bitcoin);
921 store.persist_network(&network_changeset).unwrap();
922
923 let mut network_changeset = Some(Network::Regtest);
924 store.read_network(&mut network_changeset).unwrap();
925
926 assert_eq!(network_changeset, Some(Network::Bitcoin));
927 }
928
929 #[test]
930 fn test_keychains_persistence() {
931 let tmpfile = NamedTempFile::new().unwrap();
932 let db = create_db(tmpfile.path());
933 let store = create_test_store(Arc::new(db), "wallet1");
934 store.create_keychains_table().unwrap();
935
936 let descriptor: Descriptor<DescriptorPublicKey> = DESCRIPTORS[0].parse().unwrap();
937 let change_descriptor: Descriptor<DescriptorPublicKey> = DESCRIPTORS[1].parse().unwrap();
938 let desc_changeset: BTreeMap<u64, Descriptor<DescriptorPublicKey>> =
939 [(0, descriptor.clone()), (1, change_descriptor.clone())].into();
940
941 store.persist_keychains(&desc_changeset).unwrap();
942
943 let mut desc_changeset: BTreeMap<u64, Descriptor<DescriptorPublicKey>> = BTreeMap::new();
944 store.read_keychains(&mut desc_changeset).unwrap();
945
946 assert_eq!(*desc_changeset.get(&0).unwrap(), descriptor);
947 assert_eq!(*desc_changeset.get(&1).unwrap(), change_descriptor);
948 }
949
950 #[test]
951 fn test_keychains_persistence_second() {
952 let tmpfile = NamedTempFile::new().unwrap();
953 let db = create_db(tmpfile.path());
954 let store = create_test_store(Arc::new(db), "wallet1");
955 store.create_keychains_table().unwrap();
956
957 let descriptor: Descriptor<DescriptorPublicKey> = DESCRIPTORS[1].parse().unwrap();
958 let change_descriptor: Descriptor<DescriptorPublicKey> = DESCRIPTORS[0].parse().unwrap();
959
960 let desc_changeset: BTreeMap<u64, Descriptor<DescriptorPublicKey>> =
961 [(0, descriptor.clone()), (1, change_descriptor.clone())].into();
962
963 store.persist_keychains(&desc_changeset).unwrap();
964
965 let mut desc_changeset: BTreeMap<u64, Descriptor<DescriptorPublicKey>> = BTreeMap::new();
966 store.read_keychains(&mut desc_changeset).unwrap();
967
968 assert_eq!(*desc_changeset.get(&0).unwrap(), descriptor);
969 assert_eq!(*desc_changeset.get(&1).unwrap(), change_descriptor);
970 }
971
972 #[test]
973 fn test_single_desc_persistence() {
974 let tmpfile = NamedTempFile::new().unwrap();
975 let db = create_db(tmpfile.path());
976 let store = create_test_store(Arc::new(db), "wallet1");
977 store.create_keychains_table().unwrap();
978
979 let descriptor: Descriptor<DescriptorPublicKey> = DESCRIPTORS[0].parse().unwrap();
980
981 store
982 .persist_keychains(&[(0, descriptor.clone())].into())
983 .unwrap();
984
985 let mut desc_changeset: BTreeMap<u64, Descriptor<DescriptorPublicKey>> = BTreeMap::new();
986 store.read_keychains(&mut desc_changeset).unwrap();
987
988 assert_eq!(*desc_changeset.get(&0).unwrap(), descriptor);
989 assert_eq!(desc_changeset.get(&1), None);
990 }
991
992 #[test]
993 fn test_descriptor_missing() {
994 let tmpfile = NamedTempFile::new().unwrap();
995 let db = create_db(tmpfile.path());
996 let store = create_test_store(Arc::new(db), "wallet1");
997 store.create_keychains_table().unwrap();
998
999 let mut desc_changeset: BTreeMap<u64, Descriptor<DescriptorPublicKey>> = BTreeMap::new();
1000 store.read_keychains(&mut desc_changeset).unwrap();
1001 assert_eq!(desc_changeset.get(&0), None);
1002 assert_eq!(desc_changeset.get(&1), None);
1003 }
1004
1005 #[test]
1006 fn test_local_chain_persistence() {
1007 let tmpfile = NamedTempFile::new().unwrap();
1008 let db = create_db(tmpfile.path());
1009 let store = create_test_store(Arc::new(db), "wallet1");
1010
1011 let local_chain_changeset = local_chain::ChangeSet {
1013 blocks: [
1014 (0, Some(hash!("B"))),
1015 (1, Some(hash!("D"))),
1016 (2, Some(hash!("K"))),
1017 ]
1018 .into(),
1019 };
1020 store.create_local_chain_tables().unwrap();
1021 store.persist_local_chain(&local_chain_changeset).unwrap();
1022
1023 let mut changeset = local_chain::ChangeSet::default();
1024 store.read_local_chain(&mut changeset).unwrap();
1025 assert_eq!(local_chain_changeset, changeset);
1026
1027 let local_chain_changeset = local_chain::ChangeSet {
1029 blocks: [(2, None)].into(),
1030 };
1031
1032 store.persist_local_chain(&local_chain_changeset).unwrap();
1033
1034 let mut changeset = local_chain::ChangeSet::default();
1035 store.read_local_chain(&mut changeset).unwrap();
1036
1037 let local_chain_changeset = local_chain::ChangeSet {
1038 blocks: [(0, Some(hash!("B"))), (1, Some(hash!("D")))].into(),
1039 };
1040
1041 assert_eq!(local_chain_changeset, changeset);
1042 }
1043
1044 #[test]
1045 fn test_blocks_persistence() {
1046 let tmpfile = NamedTempFile::new().unwrap();
1047 let db = create_db(tmpfile.path());
1048 let store = create_test_store(Arc::new(db), "wallet1");
1049 let blocks: BTreeMap<u32, Option<BlockHash>> = [
1050 (0, Some(hash!("B"))),
1051 (1, Some(hash!("D"))),
1052 (2, Some(hash!("K"))),
1053 ]
1054 .into();
1055
1056 let write_tx = store.db.begin_write().unwrap();
1057 let _ = write_tx.open_table(store.blocks_table_defn()).unwrap();
1058 store.persist_blocks(&write_tx, &blocks).unwrap();
1059 write_tx.commit().unwrap();
1060 let read_tx = store.db.begin_read().unwrap();
1061 let mut blocks_new: BTreeMap<u32, Option<BlockHash>> = BTreeMap::new();
1062 store.read_blocks(&read_tx, &mut blocks_new).unwrap();
1063 assert_eq!(blocks_new, blocks);
1064
1065 let blocks: BTreeMap<u32, Option<BlockHash>> = [(2, None)].into();
1066
1067 let write_tx = store.db.begin_write().unwrap();
1068 store.persist_blocks(&write_tx, &blocks).unwrap();
1069 write_tx.commit().unwrap();
1070 let read_tx = store.db.begin_read().unwrap();
1071 let mut blocks_new: BTreeMap<u32, Option<BlockHash>> = BTreeMap::new();
1072 store.read_blocks(&read_tx, &mut blocks_new).unwrap();
1073
1074 let blocks: BTreeMap<u32, Option<BlockHash>> =
1075 [(0, Some(hash!("B"))), (1, Some(hash!("D")))].into();
1076
1077 assert_eq!(blocks, blocks_new);
1078 }
1079
1080 fn create_one_inp_one_out_tx(txid: Txid, amount: u64) -> Transaction {
1081 Transaction {
1082 version: transaction::Version::ONE,
1083 lock_time: absolute::LockTime::ZERO,
1084 input: vec![TxIn {
1085 previous_output: OutPoint::new(txid, 0),
1086 ..TxIn::default()
1087 }],
1088 output: vec![TxOut {
1089 value: Amount::from_sat(amount),
1090 script_pubkey: ScriptBuf::new(),
1091 }],
1092 }
1093 }
1094
1095 #[test]
1096 fn test_persist_last_seen() {
1097 let tmpfile = NamedTempFile::new().unwrap();
1098 let db = create_db(tmpfile.path());
1099 let store = create_test_store(Arc::new(db), "wallet1");
1100
1101 let tx1 = Arc::new(create_one_inp_one_out_tx(
1102 Txid::from_byte_array([0; 32]),
1103 30_000,
1104 ));
1105 let tx2 = Arc::new(create_one_inp_one_out_tx(tx1.compute_txid(), 20_000));
1106 let tx3 = Arc::new(create_one_inp_one_out_tx(tx2.compute_txid(), 19_000));
1107
1108 let txs: BTreeSet<Arc<Transaction>> = [tx1.clone(), tx2.clone()].into();
1110 let mut last_seen: BTreeMap<Txid, u64> =
1111 [(tx1.compute_txid(), 100), (tx2.compute_txid(), 120)].into();
1112
1113 let write_tx = store.db.begin_write().unwrap();
1114 let _ = write_tx.open_table(store.txs_table_defn()).unwrap();
1115 let _ = write_tx.open_table(store.last_seen_defn()).unwrap();
1116 write_tx.commit().unwrap();
1117
1118 let write_tx = store.db.begin_write().unwrap();
1119 store.persist_txs(&write_tx, &txs).unwrap();
1120 write_tx.commit().unwrap();
1121
1122 let txs: BTreeSet<Arc<Transaction>> = BTreeSet::new();
1124
1125 let write_tx = store.db.begin_write().unwrap();
1126 let read_tx = store.db.begin_read().unwrap();
1127 store
1128 .persist_last_seen(&write_tx, &read_tx, &last_seen, &txs)
1129 .unwrap();
1130 write_tx.commit().unwrap();
1131
1132 let read_tx = store.db.begin_read().unwrap();
1133 let mut last_seen_read: BTreeMap<Txid, u64> = BTreeMap::new();
1134 store.read_last_seen(&read_tx, &mut last_seen_read).unwrap();
1135 assert_eq!(last_seen_read, last_seen);
1136
1137 let txs_new: BTreeSet<Arc<Transaction>> = [tx3.clone()].into();
1139 let last_seen_new: BTreeMap<Txid, u64> = [(tx3.compute_txid(), 200)].into();
1140
1141 let write_tx = store.db.begin_write().unwrap();
1142 let _ = write_tx.open_table(store.txs_table_defn()).unwrap();
1143 let _ = write_tx.open_table(store.last_seen_defn()).unwrap();
1144 write_tx.commit().unwrap();
1145
1146 let write_tx = store.db.begin_write().unwrap();
1147 let read_tx = store.db.begin_read().unwrap();
1148 store
1149 .persist_last_seen(&write_tx, &read_tx, &last_seen_new, &txs_new)
1150 .unwrap();
1151 write_tx.commit().unwrap();
1152
1153 let read_tx = store.db.begin_read().unwrap();
1154 let mut last_seen_read_new: BTreeMap<Txid, u64> = BTreeMap::new();
1155 store
1156 .read_last_seen(&read_tx, &mut last_seen_read_new)
1157 .unwrap();
1158 last_seen.merge(last_seen_new);
1159 assert_eq!(last_seen_read_new, last_seen);
1160 }
1161
1162 #[test]
1163 fn test_last_seen_missing_txn() {
1164 let tmpfile = NamedTempFile::new().unwrap();
1166 let db = create_db(tmpfile.path());
1167 let store = create_test_store(Arc::new(db), "wallet1");
1168
1169 let tx1 = Arc::new(create_one_inp_one_out_tx(
1170 Txid::from_byte_array([0; 32]),
1171 30_000,
1172 ));
1173 let tx2 = Arc::new(create_one_inp_one_out_tx(tx1.compute_txid(), 20_000));
1174
1175 let last_seen: BTreeMap<Txid, u64> = [
1176 (hash!("B"), 100),
1177 (tx1.compute_txid(), 120),
1178 (tx2.compute_txid(), 121),
1179 ]
1180 .into();
1181
1182 let write_tx = store.db.begin_write().unwrap();
1183 let _ = write_tx.open_table(store.txs_table_defn()).unwrap();
1184 let _ = write_tx.open_table(store.last_seen_defn()).unwrap();
1185 write_tx.commit().unwrap();
1186
1187 let txs: BTreeSet<Arc<Transaction>> = [tx1, tx2].into();
1188
1189 let write_tx = store.db.begin_write().unwrap();
1190 let read_tx = store.db.begin_read().unwrap();
1191 match store.persist_last_seen(&write_tx, &read_tx, &last_seen, &txs) {
1192 Ok(_) => panic!("should give error since tx missing"),
1193 Err(StoreError::TxMissing(txid)) => assert_eq!(txid, hash!("B")),
1194 Err(_) => panic!("error should only be due to missing tx"),
1195 }
1196 write_tx.commit().unwrap();
1197 }
1198
1199 #[test]
1200 fn test_persist_last_evicted() {
1201 let tmpfile = NamedTempFile::new().unwrap();
1202 let db = create_db(tmpfile.path());
1203 let store = create_test_store(Arc::new(db), "wallet1");
1204
1205 let tx1 = Arc::new(create_one_inp_one_out_tx(
1206 Txid::from_byte_array([0; 32]),
1207 30_000,
1208 ));
1209 let tx2 = Arc::new(create_one_inp_one_out_tx(tx1.compute_txid(), 20_000));
1210 let tx3 = Arc::new(create_one_inp_one_out_tx(tx2.compute_txid(), 19_000));
1211
1212 let txs: BTreeSet<Arc<Transaction>> = [tx1.clone(), tx2.clone()].into();
1213 let mut last_evicted: BTreeMap<Txid, u64> =
1214 [(tx1.compute_txid(), 100), (tx2.compute_txid(), 120)].into();
1215
1216 let write_tx = store.db.begin_write().unwrap();
1217 let _ = write_tx.open_table(store.txs_table_defn()).unwrap();
1218 let _ = write_tx
1219 .open_table(store.last_evicted_table_defn())
1220 .unwrap();
1221 write_tx.commit().unwrap();
1222
1223 let write_tx = store.db.begin_write().unwrap();
1224 store.persist_txs(&write_tx, &txs).unwrap();
1225 write_tx.commit().unwrap();
1226
1227 let txs: BTreeSet<Arc<Transaction>> = BTreeSet::new();
1229
1230 let write_tx = store.db.begin_write().unwrap();
1231 let read_tx = store.db.begin_read().unwrap();
1232 store
1233 .persist_last_evicted(&write_tx, &read_tx, &last_evicted, &txs)
1234 .unwrap();
1235 write_tx.commit().unwrap();
1236
1237 let read_tx = store.db.begin_read().unwrap();
1238 let mut last_evicted_read: BTreeMap<Txid, u64> = BTreeMap::new();
1239 store
1240 .read_last_evicted(&read_tx, &mut last_evicted_read)
1241 .unwrap();
1242 assert_eq!(last_evicted_read, last_evicted);
1243
1244 let txs_new: BTreeSet<Arc<Transaction>> = [tx3.clone()].into();
1245 let last_evicted_new: BTreeMap<Txid, u64> = [(tx3.compute_txid(), 300)].into();
1246
1247 let write_tx = store.db.begin_write().unwrap();
1248 let _ = write_tx.open_table(store.txs_table_defn()).unwrap();
1249 let _ = write_tx
1250 .open_table(store.last_evicted_table_defn())
1251 .unwrap();
1252 write_tx.commit().unwrap();
1253
1254 let write_tx = store.db.begin_write().unwrap();
1255 let read_tx = store.db.begin_read().unwrap();
1256 store
1257 .persist_last_evicted(&write_tx, &read_tx, &last_evicted_new, &txs_new)
1258 .unwrap();
1259 write_tx.commit().unwrap();
1260
1261 let read_tx = store.db.begin_read().unwrap();
1262 let mut last_evicted_read_new: BTreeMap<Txid, u64> = BTreeMap::new();
1263 store
1264 .read_last_evicted(&read_tx, &mut last_evicted_read_new)
1265 .unwrap();
1266 last_evicted.merge(last_evicted_new);
1267 assert_eq!(last_evicted_read_new, last_evicted);
1268 }
1269
1270 #[test]
1271 fn test_last_evicted_missing_txs() {
1272 let tmpfile = NamedTempFile::new().unwrap();
1274 let db = create_db(tmpfile.path());
1275 let store = create_test_store(Arc::new(db), "wallet1");
1276
1277 let tx1 = Arc::new(create_one_inp_one_out_tx(
1278 Txid::from_byte_array([0; 32]),
1279 30_000,
1280 ));
1281 let tx2 = Arc::new(create_one_inp_one_out_tx(tx1.compute_txid(), 20_000));
1282
1283 let last_evicted: BTreeMap<Txid, u64> = [
1284 (hash!("B"), 100),
1285 (tx1.compute_txid(), 120),
1286 (tx2.compute_txid(), 132),
1287 ]
1288 .into();
1289
1290 let write_tx = store.db.begin_write().unwrap();
1291 let _ = write_tx.open_table(store.txs_table_defn()).unwrap();
1292 let _ = write_tx
1293 .open_table(store.last_evicted_table_defn())
1294 .unwrap();
1295 write_tx.commit().unwrap();
1296
1297 let txs: BTreeSet<Arc<Transaction>> = [tx1, tx2].into();
1298
1299 let write_tx = store.db.begin_write().unwrap();
1300 let read_tx = store.db.begin_read().unwrap();
1301 match store.persist_last_evicted(&write_tx, &read_tx, &last_evicted, &txs) {
1302 Ok(_) => panic!("should give error since tx missing"),
1303 Err(StoreError::TxMissing(txid)) => assert_eq!(txid, hash!("B")),
1304 Err(_) => panic!("error should only be due to missing tx"),
1305 }
1306 write_tx.commit().unwrap();
1307 }
1308
1309 #[test]
1310 fn test_persist_first_seen() {
1311 let tmpfile = NamedTempFile::new().unwrap();
1312 let db = create_db(tmpfile.path());
1313 let store = create_test_store(Arc::new(db), "wallet1");
1314
1315 let tx1 = Arc::new(create_one_inp_one_out_tx(
1316 Txid::from_byte_array([0; 32]),
1317 30_000,
1318 ));
1319 let tx2 = Arc::new(create_one_inp_one_out_tx(tx1.compute_txid(), 20_000));
1320 let tx3 = Arc::new(create_one_inp_one_out_tx(tx2.compute_txid(), 19_000));
1321
1322 let txs: BTreeSet<Arc<Transaction>> = [tx1.clone(), tx2.clone()].into();
1323 let mut first_seen: BTreeMap<Txid, u64> =
1324 [(tx1.compute_txid(), 100), (tx2.compute_txid(), 120)].into();
1325
1326 let write_tx = store.db.begin_write().unwrap();
1327 let _ = write_tx.open_table(store.txs_table_defn()).unwrap();
1328 let _ = write_tx.open_table(store.first_seen_table_defn()).unwrap();
1329 write_tx.commit().unwrap();
1330
1331 let write_tx = store.db.begin_write().unwrap();
1332 store.persist_txs(&write_tx, &txs).unwrap();
1333 write_tx.commit().unwrap();
1334
1335 let txs: BTreeSet<Arc<Transaction>> = BTreeSet::new();
1337
1338 let write_tx = store.db.begin_write().unwrap();
1339 let read_tx = store.db.begin_read().unwrap();
1340 store
1341 .persist_first_seen(&write_tx, &read_tx, &first_seen, &txs)
1342 .unwrap();
1343 write_tx.commit().unwrap();
1344
1345 let read_tx = store.db.begin_read().unwrap();
1346 let mut first_seen_read: BTreeMap<Txid, u64> = BTreeMap::new();
1347 store
1348 .read_first_seen(&read_tx, &mut first_seen_read)
1349 .unwrap();
1350 assert_eq!(first_seen_read, first_seen);
1351
1352 let txs_new: BTreeSet<Arc<Transaction>> = [tx3.clone()].into();
1353 let first_seen_new: BTreeMap<Txid, u64> = [(tx3.compute_txid(), 200)].into();
1354
1355 let write_tx = store.db.begin_write().unwrap();
1356 let _ = write_tx.open_table(store.txs_table_defn()).unwrap();
1357 let _ = write_tx.open_table(store.first_seen_table_defn()).unwrap();
1358 write_tx.commit().unwrap();
1359
1360 let write_tx = store.db.begin_write().unwrap();
1361 let read_tx = store.db.begin_read().unwrap();
1362 store
1363 .persist_first_seen(&write_tx, &read_tx, &first_seen_new, &txs_new)
1364 .unwrap();
1365 write_tx.commit().unwrap();
1366
1367 let read_tx = store.db.begin_read().unwrap();
1368 let mut first_seen_read_new: BTreeMap<Txid, u64> = BTreeMap::new();
1369 store
1370 .read_first_seen(&read_tx, &mut first_seen_read_new)
1371 .unwrap();
1372 first_seen.merge(first_seen_new);
1373 assert_eq!(first_seen_read_new, first_seen);
1374 }
1375
1376 #[test]
1377 fn test_first_seen_missing_tx() {
1378 let tmpfile = NamedTempFile::new().unwrap();
1380 let db = create_db(tmpfile.path());
1381 let store = create_test_store(Arc::new(db), "wallet1");
1382
1383 let tx1 = Arc::new(create_one_inp_one_out_tx(
1384 Txid::from_byte_array([0; 32]),
1385 30_000,
1386 ));
1387 let tx2 = Arc::new(create_one_inp_one_out_tx(tx1.compute_txid(), 20_000));
1388
1389 let first_seen: BTreeMap<Txid, u64> = [
1390 (hash!("B"), 100),
1391 (tx1.compute_txid(), 120),
1392 (tx2.compute_txid(), 121),
1393 ]
1394 .into();
1395
1396 let write_tx = store.db.begin_write().unwrap();
1397 let _ = write_tx.open_table(store.txs_table_defn()).unwrap();
1398 let _ = write_tx.open_table(store.first_seen_table_defn()).unwrap();
1399 write_tx.commit().unwrap();
1400
1401 let txs: BTreeSet<Arc<Transaction>> = [tx1, tx2].into();
1402
1403 let write_tx = store.db.begin_write().unwrap();
1404 let read_tx = store.db.begin_read().unwrap();
1405 match store.persist_first_seen(&write_tx, &read_tx, &first_seen, &txs) {
1406 Ok(_) => panic!("should give error since tx missing"),
1407 Err(StoreError::TxMissing(txid)) => assert_eq!(txid, hash!("B")),
1408 Err(_) => panic!("error should only be due to missing tx"),
1409 }
1410 write_tx.commit().unwrap();
1411 }
1412
1413 #[test]
1414 fn test_persist_txouts() {
1415 let tmpfile = NamedTempFile::new().unwrap();
1416 let db = create_db(tmpfile.path());
1417 let store = create_test_store(Arc::new(db), "wallet1");
1418
1419 let mut txouts: BTreeMap<OutPoint, TxOut> = [
1420 (
1421 OutPoint::new(Txid::from_byte_array([0; 32]), 0),
1422 TxOut {
1423 value: Amount::from_sat(1300),
1424 script_pubkey: ScriptBuf::from_bytes(vec![0]),
1425 },
1426 ),
1427 (
1428 OutPoint::new(Txid::from_byte_array([1; 32]), 0),
1429 TxOut {
1430 value: Amount::from_sat(1400),
1431 script_pubkey: ScriptBuf::from_bytes(vec![2]),
1432 },
1433 ),
1434 ]
1435 .into();
1436
1437 let write_tx = store.db.begin_write().unwrap();
1438 let _ = write_tx.open_table(store.txouts_table_defn()).unwrap();
1439 store.persist_txouts(&write_tx, &txouts).unwrap();
1440 write_tx.commit().unwrap();
1441
1442 let read_tx = store.db.begin_read().unwrap();
1443 let mut txouts_read: BTreeMap<OutPoint, TxOut> = BTreeMap::new();
1444 store.read_txouts(&read_tx, &mut txouts_read).unwrap();
1445 assert_eq!(txouts, txouts_read);
1446
1447 let txouts_new: BTreeMap<OutPoint, TxOut> = [(
1448 OutPoint::new(Txid::from_byte_array([2; 32]), 0),
1449 TxOut {
1450 value: Amount::from_sat(10000),
1451 script_pubkey: ScriptBuf::from_bytes(vec![1]),
1452 },
1453 )]
1454 .into();
1455 let write_tx = store.db.begin_write().unwrap();
1456 let _ = write_tx.open_table(store.txouts_table_defn()).unwrap();
1457 store.persist_txouts(&write_tx, &txouts_new).unwrap();
1458 write_tx.commit().unwrap();
1459
1460 let read_tx = store.db.begin_read().unwrap();
1461 let mut txouts_read_new: BTreeMap<OutPoint, TxOut> = BTreeMap::new();
1462 store.read_txouts(&read_tx, &mut txouts_read_new).unwrap();
1463
1464 txouts.merge(txouts_new);
1465 assert_eq!(txouts, txouts_read_new);
1466 }
1467
1468 #[test]
1469 fn test_persist_txs() {
1470 let tmpfile = NamedTempFile::new().unwrap();
1471 let db = create_db(tmpfile.path());
1472 let store = create_test_store(Arc::new(db), "wallet1");
1473
1474 let tx1 = Arc::new(create_one_inp_one_out_tx(
1475 Txid::from_byte_array([0; 32]),
1476 30_000,
1477 ));
1478 let tx2 = Arc::new(create_one_inp_one_out_tx(tx1.compute_txid(), 20_000));
1479 let tx3 = Arc::new(create_one_inp_one_out_tx(tx2.compute_txid(), 19_000));
1480
1481 let mut txs: BTreeSet<Arc<Transaction>> = [tx1, tx2.clone()].into();
1482
1483 let write_tx = store.db.begin_write().unwrap();
1484 let _ = write_tx.open_table(store.txs_table_defn()).unwrap();
1485 store.persist_txs(&write_tx, &txs).unwrap();
1486 write_tx.commit().unwrap();
1487
1488 let read_tx = store.db.begin_read().unwrap();
1489 let mut txs_read: BTreeSet<Arc<Transaction>> = BTreeSet::new();
1490 store.read_txs(&read_tx, &mut txs_read).unwrap();
1491 assert_eq!(txs_read, txs);
1492
1493 let txs_new: BTreeSet<Arc<Transaction>> = [tx3].into();
1494 let write_tx = store.db.begin_write().unwrap();
1495 let _ = write_tx.open_table(store.txs_table_defn()).unwrap();
1496 store.persist_txs(&write_tx, &txs_new).unwrap();
1497 write_tx.commit().unwrap();
1498
1499 let read_tx = store.db.begin_read().unwrap();
1500 let mut txs_read_new: BTreeSet<Arc<Transaction>> = BTreeSet::new();
1501 store.read_txs(&read_tx, &mut txs_read_new).unwrap();
1502
1503 txs.merge(txs_new);
1504 assert_eq!(txs_read_new, txs);
1505 }
1506
1507 #[test]
1508 fn test_persist_anchors() {
1509 let tmpfile = NamedTempFile::new().unwrap();
1510 let db = create_db(tmpfile.path());
1511 let store = create_test_store(Arc::new(db), "wallet1");
1512
1513 let tx1 = Arc::new(create_one_inp_one_out_tx(
1514 Txid::from_byte_array([0; 32]),
1515 30_000,
1516 ));
1517 let tx2 = Arc::new(create_one_inp_one_out_tx(tx1.compute_txid(), 20_000));
1518 let tx3 = Arc::new(create_one_inp_one_out_tx(tx2.compute_txid(), 19_000));
1519
1520 let anchor1 = ConfirmationBlockTime {
1521 block_id: block_id!(23, "BTC"),
1522 confirmation_time: 1756838400,
1523 };
1524
1525 let anchor2 = ConfirmationBlockTime {
1526 block_id: block_id!(25, "BDK"),
1527 confirmation_time: 1756839600,
1528 };
1529
1530 let txs: BTreeSet<Arc<Transaction>> = [tx1.clone(), tx2.clone()].into();
1531 let mut anchors = [(anchor1, tx1.compute_txid()), (anchor2, tx2.compute_txid())].into();
1532
1533 let write_tx = store.db.begin_write().unwrap();
1534 let _ = write_tx.open_table(store.txs_table_defn()).unwrap();
1535 let _ = write_tx
1536 .open_table(store.anchors_table_defn::<ConfirmationBlockTime>())
1537 .unwrap();
1538 write_tx.commit().unwrap();
1539
1540 let write_tx = store.db.begin_write().unwrap();
1541 store.persist_txs(&write_tx, &txs).unwrap();
1542 write_tx.commit().unwrap();
1543
1544 let txs: BTreeSet<Arc<Transaction>> = BTreeSet::new();
1546
1547 let write_tx = store.db.begin_write().unwrap();
1548 let read_tx = store.db.begin_read().unwrap();
1549 store
1550 .persist_anchors(&write_tx, &read_tx, &anchors, &txs)
1551 .unwrap();
1552 read_tx.close().unwrap();
1553 write_tx.commit().unwrap();
1554
1555 let read_tx = store.db.begin_read().unwrap();
1556 let mut anchors_read: BTreeSet<(ConfirmationBlockTime, Txid)> = BTreeSet::new();
1557 store.read_anchors(&read_tx, &mut anchors_read).unwrap();
1558 assert_eq!(anchors_read, anchors);
1559
1560 let txs_new: BTreeSet<Arc<Transaction>> = [tx3.clone()].into();
1561 let anchors_new: BTreeSet<(ConfirmationBlockTime, Txid)> =
1562 [(anchor2, tx3.compute_txid())].into();
1563
1564 let write_tx = store.db.begin_write().unwrap();
1565 let read_tx = store.db.begin_read().unwrap();
1566 store
1567 .persist_anchors(&write_tx, &read_tx, &anchors_new, &txs_new)
1568 .unwrap();
1569 read_tx.close().unwrap();
1570 write_tx.commit().unwrap();
1571
1572 let read_tx = store.db.begin_read().unwrap();
1573 let mut anchors_read_new: BTreeSet<(ConfirmationBlockTime, Txid)> = BTreeSet::new();
1574 store.read_anchors(&read_tx, &mut anchors_read_new).unwrap();
1575
1576 anchors.merge(anchors_new);
1577 assert_eq!(anchors_read_new, anchors);
1578
1579 let tx4 = Arc::new(create_one_inp_one_out_tx(tx3.compute_txid(), 14_000));
1582
1583 let txs_new: BTreeSet<Arc<Transaction>> = [tx4.clone()].into();
1584 let anchors_new: BTreeSet<(ConfirmationBlockTime, Txid)> =
1585 [(anchor2, tx4.compute_txid())].into();
1586
1587 let write_tx = store.db.begin_write().unwrap();
1588 store.persist_txs(&write_tx, &txs_new).unwrap();
1589 write_tx.commit().unwrap();
1590
1591 let write_tx = store.db.begin_write().unwrap();
1592 let read_tx = store.db.begin_read().unwrap();
1593 store
1594 .persist_anchors(&write_tx, &read_tx, &anchors_new, &txs_new)
1595 .unwrap();
1596 read_tx.close().unwrap();
1597 write_tx.commit().unwrap();
1598
1599 let read_tx = store.db.begin_read().unwrap();
1600 let mut anchors_read_new: BTreeSet<(ConfirmationBlockTime, Txid)> = BTreeSet::new();
1601 store.read_anchors(&read_tx, &mut anchors_read_new).unwrap();
1602
1603 anchors.merge(anchors_new);
1604 assert_eq!(anchors_read_new, anchors);
1605 }
1606
1607 #[test]
1608 fn test_anchors_missing_tx() {
1609 let tmpfile = NamedTempFile::new().unwrap();
1611 let db = create_db(tmpfile.path());
1612 let store = create_test_store(Arc::new(db), "wallet1");
1613
1614 let anchor1 = ConfirmationBlockTime {
1615 block_id: block_id!(23, "BTC"),
1616 confirmation_time: 1756838400,
1617 };
1618
1619 let write_tx = store.db.begin_write().unwrap();
1620 let _ = write_tx.open_table(store.txs_table_defn()).unwrap();
1621 let _ = write_tx
1622 .open_table(store.anchors_table_defn::<ConfirmationBlockTime>())
1623 .unwrap();
1624 write_tx.commit().unwrap();
1625
1626 let anchors_missing_txs: BTreeSet<(ConfirmationBlockTime, Txid)> =
1627 [(anchor1, hash!("B"))].into();
1628 let txs: BTreeSet<Arc<Transaction>> = BTreeSet::new();
1629
1630 let write_tx = store.db.begin_write().unwrap();
1631 let read_tx = store.db.begin_read().unwrap();
1632 match store.persist_anchors(&write_tx, &read_tx, &anchors_missing_txs, &txs) {
1633 Ok(_) => panic!("should give error since tx missing"),
1634 Err(StoreError::TxMissing(txid)) => assert_eq!(txid, hash!("B")),
1635 Err(_) => panic!("error should only be due to missing tx"),
1636 }
1637 read_tx.close().unwrap();
1638 write_tx.commit().unwrap();
1639 }
1640
1641 #[test]
1642 fn test_persist_anchors_blockid() {
1643 let tmpfile = NamedTempFile::new().unwrap();
1644 let db = create_db(tmpfile.path());
1645 let store = create_test_store(Arc::new(db), "wallet1");
1646
1647 let tx1 = Arc::new(create_one_inp_one_out_tx(
1648 Txid::from_byte_array([0; 32]),
1649 30_000,
1650 ));
1651 let tx2 = Arc::new(create_one_inp_one_out_tx(tx1.compute_txid(), 20_000));
1652 let tx3 = Arc::new(create_one_inp_one_out_tx(tx2.compute_txid(), 19_000));
1653
1654 let anchor1 = block_id!(23, "BTC");
1655
1656 let anchor2 = block_id!(25, "BDK");
1657
1658 let txs: BTreeSet<Arc<Transaction>> = [tx1.clone(), tx2.clone()].into();
1659 let mut anchors = [(anchor1, tx1.compute_txid()), (anchor2, tx2.compute_txid())].into();
1660
1661 let write_tx = store.db.begin_write().unwrap();
1662 let _ = write_tx.open_table(store.txs_table_defn()).unwrap();
1663 let _ = write_tx
1664 .open_table(store.anchors_table_defn::<BlockId>())
1665 .unwrap();
1666 write_tx.commit().unwrap();
1667
1668 let write_tx = store.db.begin_write().unwrap();
1669 let read_tx = store.db.begin_read().unwrap();
1670 store
1671 .persist_anchors(&write_tx, &read_tx, &anchors, &txs)
1672 .unwrap();
1673 read_tx.close().unwrap();
1674 write_tx.commit().unwrap();
1675
1676 let read_tx = store.db.begin_read().unwrap();
1677 let mut anchors_read: BTreeSet<(BlockId, Txid)> = BTreeSet::new();
1678 store.read_anchors(&read_tx, &mut anchors_read).unwrap();
1679 assert_eq!(anchors_read, anchors);
1680
1681 let txs_new: BTreeSet<Arc<Transaction>> = [tx3.clone()].into();
1682 let anchors_new: BTreeSet<(BlockId, Txid)> = [(anchor2, tx3.compute_txid())].into();
1683
1684 let write_tx = store.db.begin_write().unwrap();
1685 let read_tx = store.db.begin_read().unwrap();
1686 store
1687 .persist_anchors(&write_tx, &read_tx, &anchors_new, &txs_new)
1688 .unwrap();
1689 read_tx.close().unwrap();
1690 write_tx.commit().unwrap();
1691
1692 let read_tx = store.db.begin_read().unwrap();
1693 let mut anchors_read_new: BTreeSet<(BlockId, Txid)> = BTreeSet::new();
1694 store.read_anchors(&read_tx, &mut anchors_read_new).unwrap();
1695
1696 anchors.merge(anchors_new);
1697 assert_eq!(anchors_read_new, anchors);
1698 }
1699
1700 #[test]
1701 fn test_tx_graph_persistence() {
1702 let tmpfile = NamedTempFile::new().unwrap();
1703 let db = create_db(tmpfile.path());
1704 let store = create_test_store(Arc::new(db), "wallet1");
1705 let tx1 = Arc::new(create_one_inp_one_out_tx(
1706 Txid::from_byte_array([0; 32]),
1707 30_000,
1708 ));
1709 let tx2 = Arc::new(create_one_inp_one_out_tx(tx1.compute_txid(), 20_000));
1710 let block_id = block_id!(100, "B");
1711
1712 let conf_anchor: ConfirmationBlockTime = ConfirmationBlockTime {
1713 block_id,
1714 confirmation_time: 1,
1715 };
1716
1717 let mut tx_graph_changeset1 = tx_graph::ChangeSet::<ConfirmationBlockTime> {
1718 txs: [tx1.clone()].into(),
1719 txouts: [].into(),
1720 anchors: [(conf_anchor, tx1.compute_txid())].into(),
1721 last_seen: [(tx1.compute_txid(), 100)].into(),
1722 first_seen: [(tx1.compute_txid(), 50)].into(),
1723 last_evicted: [(tx1.compute_txid(), 150)].into(),
1724 };
1725
1726 store
1727 .create_tx_graph_tables::<ConfirmationBlockTime>()
1728 .unwrap();
1729
1730 store.persist_tx_graph(&tx_graph_changeset1).unwrap();
1731
1732 let mut changeset = tx_graph::ChangeSet::default();
1733 store.read_tx_graph(&mut changeset).unwrap();
1734 assert_eq!(changeset, tx_graph_changeset1);
1735
1736 let block_id = block_id!(101, "REDB");
1737
1738 let conf_anchor: ConfirmationBlockTime = ConfirmationBlockTime {
1739 block_id,
1740 confirmation_time: 1,
1741 };
1742
1743 let tx_graph_changeset2 = tx_graph::ChangeSet::<ConfirmationBlockTime> {
1744 txs: [tx2.clone()].into(),
1745 txouts: [].into(),
1746 anchors: [(conf_anchor, tx2.compute_txid())].into(),
1747 last_seen: [(tx2.compute_txid(), 200)].into(),
1748 first_seen: [(tx2.compute_txid(), 100)].into(),
1749 last_evicted: [(tx2.compute_txid(), 150)].into(),
1750 };
1751
1752 store.persist_tx_graph(&tx_graph_changeset2).unwrap();
1753
1754 let mut changeset = tx_graph::ChangeSet::default();
1755 store.read_tx_graph(&mut changeset).unwrap();
1756
1757 tx_graph_changeset1.merge(tx_graph_changeset2);
1758
1759 assert_eq!(tx_graph_changeset1, changeset);
1760 }
1761
1762 fn parse_descriptor(descriptor: &str) -> Descriptor<DescriptorPublicKey> {
1763 let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only();
1764 Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, descriptor)
1765 .unwrap()
1766 .0
1767 }
1768
1769 #[test]
1770 fn test_last_revealed_persistence() {
1771 let tmpfile = NamedTempFile::new().unwrap();
1772 let db = create_db(tmpfile.path());
1773 let store = create_test_store(Arc::new(db), "wallet1");
1774
1775 let descriptor_ids = utils::DESCRIPTORS.map(|d| parse_descriptor(d).descriptor_id());
1776
1777 let mut last_revealed: BTreeMap<DescriptorId, u32> =
1778 [(descriptor_ids[0], 1), (descriptor_ids[1], 100)].into();
1779
1780 let write_tx = store.db.begin_write().unwrap();
1781 let _ = write_tx
1782 .open_table(store.last_revealed_table_defn())
1783 .unwrap();
1784 store
1785 .persist_last_revealed(&write_tx, &last_revealed)
1786 .unwrap();
1787 write_tx.commit().unwrap();
1788
1789 let mut last_revealed_read: BTreeMap<DescriptorId, u32> = BTreeMap::new();
1790 let read_tx = store.db.begin_read().unwrap();
1791 store
1792 .read_last_revealed(&read_tx, &mut last_revealed_read)
1793 .unwrap();
1794
1795 assert_eq!(last_revealed, last_revealed_read);
1796
1797 let last_revealed_new: BTreeMap<DescriptorId, u32> = [(descriptor_ids[0], 2)].into();
1798
1799 let write_tx = store.db.begin_write().unwrap();
1800 store
1801 .persist_last_revealed(&write_tx, &last_revealed_new)
1802 .unwrap();
1803 write_tx.commit().unwrap();
1804
1805 let mut last_revealed_read_new: BTreeMap<DescriptorId, u32> = BTreeMap::new();
1806 let read_tx = store.db.begin_read().unwrap();
1807 store
1808 .read_last_revealed(&read_tx, &mut last_revealed_read_new)
1809 .unwrap();
1810
1811 last_revealed.merge(last_revealed_new);
1812
1813 assert_eq!(last_revealed, last_revealed_read_new);
1814 }
1815
1816 #[test]
1817 fn test_spks_persistence() {
1818 let tmpfile = NamedTempFile::new().unwrap();
1819 let db = create_db(tmpfile.path());
1820 let store = create_test_store(Arc::new(db), "wallet1");
1821
1822 let descriptor_ids = utils::DESCRIPTORS.map(|d| parse_descriptor(d).descriptor_id());
1823
1824 let spk_cache: BTreeMap<DescriptorId, BTreeMap<u32, ScriptBuf>> = [
1825 (
1826 descriptor_ids[0],
1827 [(0u32, ScriptBuf::from_bytes(vec![1, 2, 3]))].into(),
1828 ),
1829 (
1830 descriptor_ids[1],
1831 [
1832 (100u32, ScriptBuf::from_bytes(vec![3])),
1833 (1000u32, ScriptBuf::from_bytes(vec![5, 6, 8])),
1834 ]
1835 .into(),
1836 ),
1837 ]
1838 .into();
1839
1840 let write_tx = store.db.begin_write().unwrap();
1841 let _ = write_tx.open_table(store.spk_table_defn()).unwrap();
1842 store.persist_spks(&write_tx, &spk_cache).unwrap();
1843 write_tx.commit().unwrap();
1844
1845 let mut spk_cache_read: BTreeMap<DescriptorId, BTreeMap<u32, ScriptBuf>> = BTreeMap::new();
1846 let read_tx = store.db.begin_read().unwrap();
1847 store.read_spks(&read_tx, &mut spk_cache_read).unwrap();
1848
1849 assert_eq!(spk_cache, spk_cache_read);
1850
1851 let spk_cache_new: BTreeMap<DescriptorId, BTreeMap<u32, ScriptBuf>> = [(
1852 descriptor_ids[0],
1853 [(1u32, ScriptBuf::from_bytes(vec![1, 2, 3, 4]))].into(),
1854 )]
1855 .into();
1856
1857 let write_tx = store.db.begin_write().unwrap();
1858 let _ = write_tx.open_table(store.spk_table_defn()).unwrap();
1859 store.persist_spks(&write_tx, &spk_cache_new).unwrap();
1860 write_tx.commit().unwrap();
1861
1862 let mut spk_cache_read_new: BTreeMap<DescriptorId, BTreeMap<u32, ScriptBuf>> =
1863 BTreeMap::new();
1864 let read_tx = store.db.begin_read().unwrap();
1865 store.read_spks(&read_tx, &mut spk_cache_read_new).unwrap();
1866
1867 let spk_cache: BTreeMap<DescriptorId, BTreeMap<u32, ScriptBuf>> = [
1868 (
1869 descriptor_ids[0],
1870 [
1871 (0u32, ScriptBuf::from_bytes(vec![1, 2, 3])),
1872 (1u32, ScriptBuf::from_bytes(vec![1, 2, 3, 4])),
1873 ]
1874 .into(),
1875 ),
1876 (
1877 descriptor_ids[1],
1878 [
1879 (100u32, ScriptBuf::from_bytes(vec![3])),
1880 (1000u32, ScriptBuf::from_bytes(vec![5, 6, 8])),
1881 ]
1882 .into(),
1883 ),
1884 ]
1885 .into();
1886
1887 assert_eq!(spk_cache, spk_cache_read_new);
1888 }
1889
1890 #[test]
1891 fn test_indexer_persistence() {
1892 let tmpfile = NamedTempFile::new().unwrap();
1893 let db = create_db(tmpfile.path());
1894 let store = create_test_store(Arc::new(db), "wallet1");
1895
1896 let descriptor_ids = utils::DESCRIPTORS.map(|d| parse_descriptor(d).descriptor_id());
1897
1898 let mut keychain_txout_changeset = keychain_txout::ChangeSet {
1899 last_revealed: [(descriptor_ids[0], 1), (descriptor_ids[1], 100)].into(),
1900 spk_cache: [
1901 (
1902 descriptor_ids[0],
1903 [(0u32, ScriptBuf::from_bytes(vec![1, 2, 3]))].into(),
1904 ),
1905 (
1906 descriptor_ids[1],
1907 [
1908 (100u32, ScriptBuf::from_bytes(vec![3])),
1909 (1000u32, ScriptBuf::from_bytes(vec![5, 6, 8])),
1910 ]
1911 .into(),
1912 ),
1913 ]
1914 .into(),
1915 };
1916
1917 store.create_indexer_tables().unwrap();
1918 store.persist_indexer(&keychain_txout_changeset).unwrap();
1919
1920 let mut changeset = keychain_txout::ChangeSet::default();
1921 store.read_indexer(&mut changeset).unwrap();
1922
1923 let keychain_txout_changeset_new = keychain_txout::ChangeSet {
1924 last_revealed: [(descriptor_ids[0], 2)].into(),
1925 spk_cache: [(
1926 descriptor_ids[0],
1927 [(1u32, ScriptBuf::from_bytes(vec![1, 2, 3]))].into(),
1928 )]
1929 .into(),
1930 };
1931
1932 store
1933 .persist_indexer(&keychain_txout_changeset_new)
1934 .unwrap();
1935
1936 let mut changeset_new = keychain_txout::ChangeSet::default();
1937 store.read_indexer(&mut changeset_new).unwrap();
1938 keychain_txout_changeset.merge(keychain_txout_changeset_new);
1939
1940 assert_eq!(changeset_new, keychain_txout_changeset);
1941 }
1942
1943 #[cfg(feature = "wallet")]
1944 #[test]
1945 fn test_persist_wallet() {
1946 let tmpfile = NamedTempFile::new().unwrap();
1947 let db = Arc::new(create_db(tmpfile.path()));
1948 let store = create_test_store(db, "wallet1");
1949
1950 let descriptor: Descriptor<DescriptorPublicKey> = DESCRIPTORS[0].parse().unwrap();
1951 let change_descriptor: Descriptor<DescriptorPublicKey> = DESCRIPTORS[1].parse().unwrap();
1952
1953 let mut blocks: BTreeMap<u32, Option<BlockHash>> = BTreeMap::new();
1954 blocks.insert(0u32, Some(hash!("B")));
1955 blocks.insert(1u32, Some(hash!("T")));
1956 blocks.insert(2u32, Some(hash!("C")));
1957 let local_chain_changeset = local_chain::ChangeSet { blocks };
1958
1959 let tx1 = Arc::new(create_one_inp_one_out_tx(
1960 Txid::from_byte_array([0; 32]),
1961 30_000,
1962 ));
1963 let tx2 = Arc::new(create_one_inp_one_out_tx(tx1.compute_txid(), 20_000));
1964
1965 let block_id = block_id!(1, "BDK");
1966
1967 let conf_anchor: ConfirmationBlockTime = ConfirmationBlockTime {
1968 block_id,
1969 confirmation_time: 123,
1970 };
1971
1972 let tx_graph_changeset = tx_graph::ChangeSet::<ConfirmationBlockTime> {
1973 txs: [tx1.clone()].into(),
1974 txouts: [].into(),
1975 anchors: [(conf_anchor, tx1.compute_txid())].into(),
1976 last_seen: [(tx1.compute_txid(), 100)].into(),
1977 first_seen: [(tx1.compute_txid(), 80)].into(),
1978 last_evicted: [(tx1.compute_txid(), 150)].into(),
1979 };
1980
1981 let keychain_txout_changeset = keychain_txout::ChangeSet {
1982 last_revealed: [
1983 (descriptor.descriptor_id(), 12),
1984 (change_descriptor.descriptor_id(), 10),
1985 ]
1986 .into(),
1987 spk_cache: [
1988 (
1989 descriptor.descriptor_id(),
1990 [(0u32, ScriptBuf::from_bytes(vec![245, 123, 112]))].into(),
1991 ),
1992 (
1993 change_descriptor.descriptor_id(),
1994 [
1995 (100u32, ScriptBuf::from_bytes(vec![145, 234, 98])),
1996 (1000u32, ScriptBuf::from_bytes(vec![5, 6, 8])),
1997 ]
1998 .into(),
1999 ),
2000 ]
2001 .into(),
2002 };
2003
2004 let mut changeset = ChangeSet {
2005 descriptor: Some(descriptor.clone()),
2006 change_descriptor: Some(change_descriptor.clone()),
2007 network: Some(Network::Bitcoin),
2008 local_chain: local_chain_changeset,
2009 tx_graph: tx_graph_changeset,
2010 indexer: keychain_txout_changeset,
2011 };
2012
2013 store.create_tables::<ConfirmationBlockTime>().unwrap();
2014
2015 store.persist_wallet(&changeset).unwrap();
2016 let mut changeset_read = ChangeSet::default();
2017 store.read_wallet(&mut changeset_read).unwrap();
2018
2019 assert_eq!(changeset, changeset_read);
2020
2021 let mut blocks: BTreeMap<u32, Option<BlockHash>> = BTreeMap::new();
2022 blocks.insert(4u32, Some(hash!("RE")));
2023 blocks.insert(5u32, Some(hash!("DB")));
2024 let local_chain_changeset = local_chain::ChangeSet { blocks };
2025
2026 let block_id = block_id!(2, "Bitcoin");
2027
2028 let conf_anchor: ConfirmationBlockTime = ConfirmationBlockTime {
2029 block_id,
2030 confirmation_time: 214,
2031 };
2032
2033 let tx_graph_changeset = tx_graph::ChangeSet::<ConfirmationBlockTime> {
2034 txs: [tx2.clone()].into(),
2035 txouts: [].into(),
2036 anchors: [(conf_anchor, tx2.compute_txid())].into(),
2037 last_seen: [(tx2.compute_txid(), 200)].into(),
2038 first_seen: [(tx2.compute_txid(), 160)].into(),
2039 last_evicted: [(tx2.compute_txid(), 300)].into(),
2040 };
2041
2042 let keychain_txout_changeset = keychain_txout::ChangeSet {
2043 last_revealed: [(descriptor.descriptor_id(), 14)].into(),
2044 spk_cache: [(
2045 change_descriptor.descriptor_id(),
2046 [
2047 (102u32, ScriptBuf::from_bytes(vec![8, 45, 78])),
2048 (1001u32, ScriptBuf::from_bytes(vec![29, 56, 47])),
2049 ]
2050 .into(),
2051 )]
2052 .into(),
2053 };
2054
2055 let changeset_new = ChangeSet {
2056 descriptor: Some(descriptor),
2057 change_descriptor: Some(change_descriptor),
2058 network: Some(Network::Bitcoin),
2059 local_chain: local_chain_changeset,
2060 tx_graph: tx_graph_changeset,
2061 indexer: keychain_txout_changeset,
2062 };
2063
2064 store.persist_wallet(&changeset_new).unwrap();
2065 let mut changeset_read_new = ChangeSet::default();
2066 store.read_wallet(&mut changeset_read_new).unwrap();
2067
2068 changeset.merge(changeset_new);
2069
2070 assert_eq!(changeset, changeset_read_new);
2071 }
2072
2073 #[cfg(feature = "wallet")]
2074 #[test]
2075 fn test_persist_multi_wallet() {
2076 let tmpfile = NamedTempFile::new().unwrap();
2077 let db = Arc::new(create_db(tmpfile.path()));
2078
2079 let store1 = create_test_store(db.clone(), "wallet1");
2080
2081 let descriptor: Descriptor<DescriptorPublicKey> = DESCRIPTORS[0].parse().unwrap();
2082 let change_descriptor: Descriptor<DescriptorPublicKey> = DESCRIPTORS[1].parse().unwrap();
2083
2084 let changeset1 = ChangeSet {
2085 descriptor: Some(descriptor.clone()),
2086 change_descriptor: Some(change_descriptor.clone()),
2087 network: Some(Network::Bitcoin),
2088 ..ChangeSet::default()
2089 };
2090
2091 store1.create_tables::<ConfirmationBlockTime>().unwrap();
2092 store1.persist_wallet(&changeset1).unwrap();
2093
2094 let store2 = create_test_store(db, "wallet2");
2095
2096 let descriptor: Descriptor<DescriptorPublicKey> = DESCRIPTORS[2].parse().unwrap();
2097 let change_descriptor: Descriptor<DescriptorPublicKey> = DESCRIPTORS[3].parse().unwrap();
2098
2099 let changeset2 = ChangeSet {
2100 descriptor: Some(descriptor.clone()),
2101 change_descriptor: Some(change_descriptor.clone()),
2102 network: Some(Network::Bitcoin),
2103 ..ChangeSet::default()
2104 };
2105
2106 store2.create_tables::<ConfirmationBlockTime>().unwrap();
2107 store2.persist_wallet(&changeset2).unwrap();
2108
2109 let mut changeset_read = ChangeSet::default();
2110 store1.read_wallet(&mut changeset_read).unwrap();
2111 assert_eq!(changeset_read, changeset1);
2112
2113 let mut changeset_read = ChangeSet::default();
2114 store2.read_wallet(&mut changeset_read).unwrap();
2115 assert_eq!(changeset_read, changeset2);
2116 }
2117
2118 #[cfg(feature = "wallet")]
2119 #[test]
2120 fn wallets_missing_descriptors() {
2121 let tmpfile = NamedTempFile::new().unwrap();
2124 let db = Arc::new(create_db(tmpfile.path()));
2125
2126 let store1 = create_test_store(db.clone(), "wallet1");
2127
2128 let changeset1 = ChangeSet {
2129 network: Some(Network::Bitcoin),
2130 ..ChangeSet::default()
2131 };
2132
2133 store1.create_tables::<ConfirmationBlockTime>().unwrap();
2134 store1.persist_wallet(&changeset1).unwrap();
2135
2136 let store2 = create_test_store(db, "wallet2");
2137
2138 let descriptor: Descriptor<DescriptorPublicKey> = DESCRIPTORS[2].parse().unwrap();
2139
2140 let changeset2 = ChangeSet {
2141 descriptor: Some(descriptor.clone()),
2142 ..ChangeSet::default()
2143 };
2144
2145 store2.create_tables::<ConfirmationBlockTime>().unwrap();
2146 store2.persist_wallet(&changeset2).unwrap();
2147
2148 let mut changeset_read = ChangeSet::default();
2149 store1.read_wallet(&mut changeset_read).unwrap();
2150 assert_eq!(changeset_read, changeset1);
2151
2152 let mut changeset_read = ChangeSet::default();
2153 store2.read_wallet(&mut changeset_read).unwrap();
2154 assert_eq!(changeset_read, changeset2);
2155 }
2156}