bdk_redb/
lib.rs

1#![warn(missing_docs)]
2//! This crate provides an alternative to the sqlite persistence backend for [`BDK`] using [`redb`],
3//! a lightweight key-value store in Rust.
4//!
5//! [`BDK`]: <https://github.com/bitcoindevkit>
6//! [`redb`]: <https://github.com/cberner/redb>
7//!
8//!
9//! # Example
10//!
11//! Add this to your Cargo.toml :
12//!
13//! ```toml
14//! [dependencies]
15//! anyhow = "1.0.98"
16//! bdk_redb = "0.1.1"
17//! bdk_wallet = "2.0.0"
18//! tempfile = "3.20.0"
19//! ```
20//!
21//! Now:
22//!
23//! ```rust
24//! # #[cfg(feature="wallet")]
25//! # mod wrapper {
26//!     use bdk_wallet::bitcoin::Network;
27//!     use bdk_wallet::{KeychainKind, Wallet};
28//!     use std::sync::Arc;
29//!     use tempfile::NamedTempFile;
30//!     
31//!     use anyhow::Result;
32//!     
33//!     // Warning: Descriptors below are on a test network and only serve as an example.
34//!     // MAINNET funds sent to addresses controlled by these descriptors will be LOST!
35//!     const EXTERNAL_DESCRIPTOR: &str = "tr(tprv8ZgxMBicQKsPdrjwWCyXqqJ4YqcyG4DmKtjjsRt29v1PtD3r3PuFJAjWytzcvSTKnZAGAkPSmnrdnuHWxCAwy3i1iPhrtKAfXRH7dVCNGp6/86'/1'/0'/0/*)#g9xn7wf9";
36//!     const INTERNAL_DESCRIPTOR: &str = "tr(tprv8ZgxMBicQKsPdrjwWCyXqqJ4YqcyG4DmKtjjsRt29v1PtD3r3PuFJAjWytzcvSTKnZAGAkPSmnrdnuHWxCAwy3i1iPhrtKAfXRH7dVCNGp6/86'/1'/0'/1/*)#e3rjrmea";
37//!     
38//!     fn main() -> Result<()> {
39//!         let network = Network::Signet;
40//!         let file_path = NamedTempFile::new()?;
41//!         let db = Arc::new(bdk_redb::redb::Database::create(file_path.path())?);
42//!         let mut store = bdk_redb::Store::new(db, "wallet1".to_string())?;
43//!     
44//!         let wallet_opt = Wallet::load()
45//!             .descriptor(KeychainKind::External, Some(EXTERNAL_DESCRIPTOR))
46//!             .descriptor(KeychainKind::Internal, Some(INTERNAL_DESCRIPTOR))
47//!             .extract_keys()
48//!             .check_network(network)
49//!             .load_wallet(&mut store)?;
50//!     
51//!         let mut wallet = match wallet_opt {
52//!             Some(wallet) => {
53//!                 println!("Loaded existing wallet database.");
54//!                 wallet
55//!             }
56//!             None => {
57//!                 println!("Creating new wallet database.");
58//!                 Wallet::create(EXTERNAL_DESCRIPTOR, INTERNAL_DESCRIPTOR)
59//!                     .network(network)
60//!                     .create_wallet(&mut store)?
61//!             }
62//!         };
63//!         // Reveal a new address from your external keychain
64//!         let address = wallet.reveal_next_address(KeychainKind::External);
65//!         wallet.persist(&mut store)?;
66//!         // Only share new address with user after successfully persisting wallet
67//!         println!("Wallet address[{}]: {}", address.index, address.address);
68//!     
69//!         Ok(())
70//!     }
71//! # }
72//! ```
73
74//! Also note that [`BDK`] uses structures called ChangeSets for persistence so while the
75//! documentation of each function links to the structures it is trying to eventually persist, the
76//! function actually uses the corresponding ChangeSets.
77#![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
99/// The following table stores (wallet_name, network) pairs. This is common to all wallets in
100/// a database file.
101const NETWORK: TableDefinition<&str, String> = TableDefinition::new("network");
102
103/// Persists the [`bdk_chain`] and [`bdk_wallet`] structures in a [`redb`] database.
104///
105/// [`bdk_chain`]: <https://docs.rs/bdk_chain/0.23.0/bdk_chain/index.html>
106/// [`bdk_wallet`]: <https://docs.rs/bdk_wallet/2.0.0/bdk_wallet/index.html>
107///
108/// This is the primary struct of this crate. It holds the database corresponding to a wallet.
109/// It also holds the table names of redb tables which are specific to each wallet in a database
110/// file.
111#[derive(Debug)]
112pub struct Store {
113    // We use a reference so as to avoid taking ownership of the Database, allowing other
114    // applications to write to it. Arc is for thread safety.
115    db: Arc<Database>,
116    wallet_name: String,
117
118    // These could be removed if we can find a way to combine a String and an &str to create a
119    // String without using unsafe Rust.
120    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    // This table stores (KeychainKind, Descriptor) pairs on a high level.
134    fn keychains_table_defn(&self) -> TableDefinition<u64, String> {
135        TableDefinition::new(&self.keychain_table_name)
136    }
137
138    // This table stores (height, BlockHash) pairs on a high level.
139    fn blocks_table_defn(&self) -> TableDefinition<u32, [u8; 32]> {
140        TableDefinition::new(&self.blocks_table_name)
141    }
142
143    // This table stores (Txid, Transaction) pairs on a high level.
144    fn txs_table_defn(&self) -> TableDefinition<[u8; 32], Vec<u8>> {
145        TableDefinition::new(&self.txs_table_name)
146    }
147
148    // This table stores (Outpoint, TxOut) pairs on a high level.
149    // where Outpoint = (Txid, vout) and TxOut = (value, script_pubkey)
150    fn txouts_table_defn(&self) -> TableDefinition<([u8; 32], u32), (u64, Vec<u8>)> {
151        TableDefinition::new(&self.txouts_table_name)
152    }
153
154    // This table stores ((Txid, BlockId), Metadata) pairs on a high level where Metadata refers to
155    // extra information stored inside the anchor. For example confirmation time would be metadata
156    // in case of ConfirmationBlockTime and None in case of BlockId.
157    // The key was chosen like this because a transaction can be anchored in multiple Blocks
158    // (in different chains ) and a Block can anchor multiple transactions.
159    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    // This table stores (Txid, last_seen) pairs on a high level.
166    fn last_seen_defn(&self) -> TableDefinition<[u8; 32], u64> {
167        TableDefinition::new(&self.last_seen_table_name)
168    }
169
170    // This table stores (Txid, last_evicted) pairs on a high level.
171    fn last_evicted_table_defn(&self) -> TableDefinition<[u8; 32], u64> {
172        TableDefinition::new(&self.last_evicted_table_name)
173    }
174
175    // This table stores (Txid, first_seen) pairs on a high level.
176    fn first_seen_table_defn(&self) -> TableDefinition<[u8; 32], u64> {
177        TableDefinition::new(&self.first_seen_table_name)
178    }
179
180    // This table stores (DescriptorId, last_revealed_index) pairs on a high level.
181    fn last_revealed_table_defn(&self) -> TableDefinition<[u8; 32], u32> {
182        TableDefinition::new(&self.last_revealed_table_name)
183    }
184
185    // This table stores ((DescriptorId, index), ScriptPubKey) pairs on a high level.
186    fn spk_table_defn(&self) -> TableDefinition<([u8; 32], u32), Vec<u8>> {
187        TableDefinition::new(&self.spk_table_name)
188    }
189
190    /// This function creates a brand new [`Store`].
191    ///
192    /// [`Store`]: crate::Store
193    pub fn new(db: Arc<Database>, wallet_name: String) -> Result<Self, StoreError> {
194        // Create table names to be stored in the Store.
195        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    /// This function creates or opens (if already created) all redb tables corresponding to a
232    /// [`Wallet`].
233    ///
234    /// [`Wallet`]: <https://docs.rs/bdk_wallet/2.0.0/bdk_wallet/struct.Wallet.html>
235    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    /// This function creates or opens (if already created) the redb tables corresponding to
250    /// local_chain.
251    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    /// This function creates or opens (if already created) the redb tables corresponding to
259    /// [`TxGraph`].
260    ///
261    /// [`TxGraph`]: <http://docs.rs/bdk_chain/0.23.0/bdk_chain/tx_graph/struct.TxGraph.html>
262    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    /// This function creates or opens (if already created) the redb tables corresponding to
276    /// [`indexer`].
277    ///
278    /// [`indexer`]: <https://docs.rs/bdk_chain/0.23.0/bdk_chain/indexer/index.html>
279    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    /// This function creates or opens (if already created) the keychains redb table corresponding
289    /// to the wallet.
290    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    /// This function creates or opens (if already created) the [`Network`] redb table. This table
298    /// is common to all wallets persisted in the database file.
299    ///
300    /// [`Network`]: <https://docs.rs/bitcoin/latest/bitcoin/enum.Network.html>
301    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    /// This function persists the [`Wallet`] into our db. It persists each field by calling
310    /// corresponding persistence functions.
311    ///
312    /// [`Wallet`]: <https://docs.rs/bdk_wallet/2.0.0/bdk_wallet/struct.Wallet.html>
313    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    /// This function persists the [`TxGraph`] into our db. It persists each field
330    /// by calling corresponding persistence functions.
331    ///
332    /// [`TxGraph`]: <http://docs.rs/bdk_chain/0.23.0/bdk_chain/tx_graph/struct.TxGraph.html>
333    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    /// This function persists the [`indexer`] structures into our db. It persists each
350    /// field by calling corresponding persistence functions.
351    ///
352    /// [`indexer`]: <https://docs.rs/bdk_chain/0.23.0/bdk_chain/indexer/index.html>
353    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    /// This function persists the descriptors into our db.
362    pub fn persist_keychains(
363        &self,
364        // maps label to descriptor
365        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            // assuming descriptors corresponding to a label(keychain) are never modified.
372            for (label, desc) in changeset {
373                table.insert(label, desc.to_string())?;
374            }
375        }
376        write_tx.commit()?;
377        Ok(())
378    }
379
380    /// This function persists the [`Network`] into our db.
381    /// <div class="warning">Warning: Do Not use with MAINNET</div>
382    ///
383    /// [`Network`]: <https://docs.rs/bitcoin/latest/bitcoin/enum.Network.html>
384    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            // assuming network will be persisted once and only once
389            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    /// This function persists the [`LocalChain`] structure into our db. It persists each
398    /// field by calling corresponding persistence functions.
399    ///
400    /// [`LocalChain`]: <http://docs.rs/bdk_chain/0.23.0/bdk_chain/local_chain/struct.LocalChain.html>
401    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    // This function persists blocks corresponding to a local_chain.
412    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                // remove the block if hash is None
422                // assuming it is guaranteed that (ht, None) => there is an entry of form (ht,_) in
423                // the Table.
424                None => table.remove(*ht)?,
425            };
426        }
427        Ok(())
428    }
429
430    // This function persists txs corresponding to a tx_graph.
431    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    // This function persists txouts corresponding to a tx_graph.
446    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    // This function persists anchors corresponding to a tx_graph.
465    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            // if the corresponding txn exists in Txs table (trying to imitate the
476            // referential behavior in case of sqlite)
477            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    // This function persists last_seen flags corresponding to a tx_graph.
492    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            // if the corresponding txn exists in Txs table (trying to duplicate the
503            // referential behavior in case of sqlite)
504            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    // This function persists last_evicted flags corresponding to a tx_graph .
515    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            // if the corresponding txn exists in Txs table (trying to duplicate the
526            // referential behavior in case of sqlite)
527            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    // This function persists first_seen flags corresponding to a tx_graph .
538    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            // if the corresponding txn exists in Txs table (trying to duplicate the
549            // referential behavior in case of sqlite)
550            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    // This function persists last_revealed corresponding to keychain_txout .
561    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    // This function persists spk_cache corresponding to keychain_txout .
574    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    /// This function loads the [`Wallet`]  by calling corresponding load functions for each of its
592    /// fields.s
593    ///
594    /// [`Wallet`]: <https://docs.rs/bdk_wallet/2.0.0/bdk_wallet/struct.Wallet.html>
595    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    /// This function loads the [`TxGraph`] from db. It loads each field
613    /// by calling corresponding load functions.
614    ///
615    /// [`TxGraph`]: <http://docs.rs/bdk_chain/0.23.0/bdk_chain/tx_graph/struct.TxGraph.html>
616    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    /// This function loads the [`indexer`] structures from our db. It loads each
631    /// field by calling corresponding load functions.
632    ///
633    /// [`indexer`]: <https://docs.rs/bdk_chain/0.23.0/bdk_chain/indexer/index.html>
634    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    /// This function loads descriptors from db.
645    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    /// This function loads the [`Network`] from our db.
665    /// <div class="warning">Warning: Do Not use with MAINNET</div>
666    ///
667    /// [`Network`]: <https://docs.rs/bitcoin/latest/bitcoin/enum.Network.html>
668    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    /// This function loads the [`LocalChain`] structure from our db. It loads each
678    /// field by calling corresponding load functions.
679    ///
680    /// [`LocalChain`]: <http://docs.rs/bdk_chain/0.23.0/bdk_chain/local_chain/struct.LocalChain.html>
681    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    // This function loads blocks corresponding to local_chain .
691    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    // This function loads txs corresponding to tx_graph.
710    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    // This function loads txouts corresponding to tx_graph.
726    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    // This function loads anchors corresponding to tx_graph.
751    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    // This function loads last_seen flags corresponding to tx_graph.
777    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    // This function loads last_evicted flags corresponding to tx_graph .
792    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    // This function loads first_seen flags corresponding to tx_graph.
810    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    // This function loads last_revealed corresponding to keychain_txout .
825    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    // This function loads spk_cache corresponding to keychain_txout .
843    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        // create a local_chain_changeset, persist that and read it
1012        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        // create another local_chain_changeset, persist that and read it
1028        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        // try persisting and reading last_seen
1109        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        // to hit the branch for the case when tx is persisted but not in changeset
1123        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        // persist another last_seen and see if what is read is same as merged one
1138        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        // to hit the branch for the panic case in persist_last_seen
1165        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        // to hit the branch for the case when tx is persisted but not in changeset
1228        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        // to hit the branch for the panic case in persist_last_evicted
1273        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        // to hit the branch for the case when tx is persisted but not in changeset
1336        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        // to hit the branch for the panic case persist_first_seen
1379        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        // to hit the branch for the case when tx is persisted but not in changeset
1545        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        // to hit the branch for the case when tx is persisted and is also in changeset (can this
1580        // happen though?)
1581        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        // to hit the branch for the panic case in persist_anchors
1610        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        // to increase branch coverage for if-let statements
2122        // in persist_wallet functions
2123        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}