Merge bitcoindevkit/bdk#1514: refactor(wallet)!: rework persistence, changeset, and construction
64eb5763487df3c6cbb626e51f96ab6e47c5de22 chore(wallet): Fix ChangeSet::merge (LLFourn) 8875c92ec114ba9ca0ea8402d65d7a236566b618 chore(wallet): Fix descriptor mismatch error keychain (LLFourn) 2cf07d686b19787b5e97e0d5ed0b4cce7f56549f refactor(chain,wallet)!: move rusqlite things into it's own file (志宇) 93f9b83e275e8ab67630c925a39de6c01611a01b chore(chain): rm unused `sqlite` types (志宇) 892b97d4416d9d38ebf3d7e0c52636f110042302 feat(chain,wallet)!: Change persist-traits to be "safer" (志宇) 3aed4cf1798d7bd0189de315f5d7eacf212edb40 test(wallet): ensure checks work when loading wallet (志宇) af4ee0fa4b82ddafa10c6b7fc35a2532351cd003 refactor(wallet)!: Make `bdk_wallet::ChangeSet` non-exhaustive (志宇) 22d02ed3d19e763d1ff54e3ec3657ed39c4ad66c feat!: improve wallet building methods (志宇) eb73f0659e0046f39f5bf6a07d4145a19f865794 refactor!: move `WalletChangeSet` to `bdk_wallet` and fix import paths (志宇) 6b43001951db7738f788bcdbb46c8c1cd3e24a66 feat!: Rework sqlite, changesets, persistence and wallet-construction (志宇) Pull request description: Closes #1496 Closes #1498 Closes #1500 ### Description Rework sqlite: Instead of only supported one schema (defined in `bdk_sqlite`), we have a schema per changeset type for more flexiblity. * rm `bdk_sqlite` crate (as we don't need `bdk_sqlite::Store` anymore). * add `sqlite` feature on `bdk_chain` which adds methods on each changeset type for initializing tables, loading the changeset and writing. Rework changesets: Some callers may want to use `KeychainTxOutIndex` where `K` may change per descriptor on every run. So we only want to persist the last revealed indices by `DescriptorId` (which uniquely-ish identifies the descriptor). * rm `keychain_added` field from `keychain_txout`'s changeset. * Add `keychain_added` to `CombinedChangeSet` (which is renamed to `WalletChangeSet`). Rework persistence: add back some safety and convenience when persisting our types. Working with changeset directly (as we were doing before) can be cumbersome. * Intoduce `struct Persisted<T>` which wraps a type `T` which stores staged changes to it. This adds safety when creating and or loading `T` from db. * `struct Persisted<T>` methods, `create`, `load` and `persist`, are available if `trait PersistWith<Db>` is implemented for `T`. `Db` represents the database connection and `PersistWith` should be implemented per database-type. * For async, we have `trait PersistedAsyncWith<Db>`. * `Wallet` has impls of `PersistedWith<rusqlite::Connection>`, `PersistedWith<rusqlite::Transaction>` and `PersistedWith<bdk_file_store::Store>` by default. Rework wallet-construction: Before, we had multiple methods for loading and creating with different input-counts so it would be unwieldly to add more parameters in the future. This also makes it difficult to impl `PersistWith` (which has a single method for `load` that takes in `PersistWith::LoadParams` and a single method for `create` that takes in `PersistWith::CreateParams`). * Introduce a builder pattern when constructing a `Wallet`. For loading from persistence or `ChangeSet`, we have `LoadParams`. For creating a new wallet, we have `CreateParams`. ### Notes to the reviewers TODO ### Changelog notice ``` ### Added - Add `sqlite` feature to `bdk_chain` which introduces methods on changeset types that encode/decode changesets to SQLite database. * Introduce `PersistWith<Db>` and `PersistAsyncWith<Db>` traits and a `Persisted` wrapper. This ergonomically makes sure user inits the db before reading/writing to it. ### Changed - Moved `bdk_chain::CombinedChangeSet` to `bdk_wallet::ChangeSet` and added `keychain_added` field. - `bdk_wallet::Wallet` construction now uses a builder API using the newly introduced `CreateParams` and `LoadParams`. ### Removed - Remove `keychains_added` field from `bdk_chain::keychain_txout::ChangeSet`. ``` ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo fmt` and `cargo clippy` before committing #### New Features: * [ ] I've added tests for the new feature * [x] I've added docs for the new feature ACKs for top commit: LLFourn: ACK: 64eb5763487df3c6cbb626e51f96ab6e47c5de22 notmandatory: Re ACK 64eb5763487df3c6cbb626e51f96ab6e47c5de22 Tree-SHA512: b8a1d48aea26d9fa293a8387a3533cd16c8ddae890f94d61fb91efa492fb05ac5e0a66200d64d7c857774368d5f0f8991a98684307029c25f50a1d8fceee8e67
This commit is contained in:
		
						commit
						0c8ee1dfe2
					
				@ -4,7 +4,6 @@ members = [
 | 
			
		||||
    "crates/wallet",
 | 
			
		||||
    "crates/chain",
 | 
			
		||||
    "crates/file_store",
 | 
			
		||||
    "crates/sqlite",
 | 
			
		||||
    "crates/electrum",
 | 
			
		||||
    "crates/esplora",
 | 
			
		||||
    "crates/bitcoind_rpc",
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,8 @@ use bdk_bitcoind_rpc::Emitter;
 | 
			
		||||
use bdk_chain::{
 | 
			
		||||
    bitcoin::{Address, Amount, Txid},
 | 
			
		||||
    local_chain::{CheckPoint, LocalChain},
 | 
			
		||||
    Balance, BlockId, IndexedTxGraph, Merge, SpkTxOutIndex,
 | 
			
		||||
    spk_txout::SpkTxOutIndex,
 | 
			
		||||
    Balance, BlockId, IndexedTxGraph, Merge,
 | 
			
		||||
};
 | 
			
		||||
use bdk_testenv::{anyhow, TestEnv};
 | 
			
		||||
use bitcoin::{hashes::Hash, Block, OutPoint, ScriptBuf, WScriptHash};
 | 
			
		||||
@ -47,7 +48,7 @@ pub fn test_sync_local_chain() -> anyhow::Result<()> {
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            local_chain.apply_update(emission.checkpoint,)?,
 | 
			
		||||
            BTreeMap::from([(height, Some(hash))]),
 | 
			
		||||
            [(height, Some(hash))].into(),
 | 
			
		||||
            "chain update changeset is unexpected",
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
@ -93,11 +94,13 @@ pub fn test_sync_local_chain() -> anyhow::Result<()> {
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            local_chain.apply_update(emission.checkpoint,)?,
 | 
			
		||||
            if exp_height == exp_hashes.len() - reorged_blocks.len() {
 | 
			
		||||
                core::iter::once((height, Some(hash)))
 | 
			
		||||
                    .chain((height + 1..exp_hashes.len() as u32).map(|h| (h, None)))
 | 
			
		||||
                    .collect::<bdk_chain::local_chain::ChangeSet>()
 | 
			
		||||
                bdk_chain::local_chain::ChangeSet {
 | 
			
		||||
                    blocks: core::iter::once((height, Some(hash)))
 | 
			
		||||
                        .chain((height + 1..exp_hashes.len() as u32).map(|h| (h, None)))
 | 
			
		||||
                        .collect(),
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                BTreeMap::from([(height, Some(hash))])
 | 
			
		||||
                [(height, Some(hash))].into()
 | 
			
		||||
            },
 | 
			
		||||
            "chain update changeset is unexpected",
 | 
			
		||||
        );
 | 
			
		||||
@ -193,7 +196,7 @@ fn test_into_tx_graph() -> anyhow::Result<()> {
 | 
			
		||||
        let indexed_additions = indexed_tx_graph.batch_insert_unconfirmed(mempool_txs);
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            indexed_additions
 | 
			
		||||
                .graph
 | 
			
		||||
                .tx_graph
 | 
			
		||||
                .txs
 | 
			
		||||
                .iter()
 | 
			
		||||
                .map(|tx| tx.compute_txid())
 | 
			
		||||
@ -201,7 +204,7 @@ fn test_into_tx_graph() -> anyhow::Result<()> {
 | 
			
		||||
            exp_txids,
 | 
			
		||||
            "changeset should have the 3 mempool transactions",
 | 
			
		||||
        );
 | 
			
		||||
        assert!(indexed_additions.graph.anchors.is_empty());
 | 
			
		||||
        assert!(indexed_additions.tx_graph.anchors.is_empty());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // mine a block that confirms the 3 txs
 | 
			
		||||
@ -224,9 +227,9 @@ fn test_into_tx_graph() -> anyhow::Result<()> {
 | 
			
		||||
        let height = emission.block_height();
 | 
			
		||||
        let _ = chain.apply_update(emission.checkpoint)?;
 | 
			
		||||
        let indexed_additions = indexed_tx_graph.apply_block_relevant(&emission.block, height);
 | 
			
		||||
        assert!(indexed_additions.graph.txs.is_empty());
 | 
			
		||||
        assert!(indexed_additions.graph.txouts.is_empty());
 | 
			
		||||
        assert_eq!(indexed_additions.graph.anchors, exp_anchors);
 | 
			
		||||
        assert!(indexed_additions.tx_graph.txs.is_empty());
 | 
			
		||||
        assert!(indexed_additions.tx_graph.txouts.is_empty());
 | 
			
		||||
        assert_eq!(indexed_additions.tx_graph.anchors, exp_anchors);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,10 @@ serde_crate = { package = "serde", version = "1", optional = true, features = ["
 | 
			
		||||
hashbrown = { version = "0.9.1", optional = true, features = ["serde"] }
 | 
			
		||||
miniscript = { version = "12.0.0", optional = true, default-features = false }
 | 
			
		||||
 | 
			
		||||
# Feature dependencies
 | 
			
		||||
rusqlite_crate = { package = "rusqlite", version = "0.31.0", features = ["bundled"], optional = true }
 | 
			
		||||
serde_json = {version = "1", optional = true }
 | 
			
		||||
 | 
			
		||||
[dev-dependencies]
 | 
			
		||||
rand = "0.8"
 | 
			
		||||
proptest = "1.2.0"
 | 
			
		||||
@ -28,3 +32,4 @@ proptest = "1.2.0"
 | 
			
		||||
default = ["std", "miniscript"]
 | 
			
		||||
std = ["bitcoin/std", "miniscript?/std"]
 | 
			
		||||
serde = ["serde_crate", "bitcoin/serde", "miniscript?/serde"]
 | 
			
		||||
rusqlite = ["std", "rusqlite_crate", "serde", "serde_json"]
 | 
			
		||||
 | 
			
		||||
@ -1,93 +0,0 @@
 | 
			
		||||
/// A changeset containing [`crate`] structures typically persisted together.
 | 
			
		||||
#[cfg(feature = "miniscript")]
 | 
			
		||||
#[derive(Debug, Clone, PartialEq)]
 | 
			
		||||
#[cfg_attr(
 | 
			
		||||
    feature = "serde",
 | 
			
		||||
    derive(crate::serde::Deserialize, crate::serde::Serialize),
 | 
			
		||||
    serde(
 | 
			
		||||
        crate = "crate::serde",
 | 
			
		||||
        bound(
 | 
			
		||||
            deserialize = "A: Ord + crate::serde::Deserialize<'de>, K: Ord + crate::serde::Deserialize<'de>",
 | 
			
		||||
            serialize = "A: Ord + crate::serde::Serialize, K: Ord + crate::serde::Serialize",
 | 
			
		||||
        ),
 | 
			
		||||
    )
 | 
			
		||||
)]
 | 
			
		||||
pub struct CombinedChangeSet<K, A> {
 | 
			
		||||
    /// Changes to the [`LocalChain`](crate::local_chain::LocalChain).
 | 
			
		||||
    pub chain: crate::local_chain::ChangeSet,
 | 
			
		||||
    /// Changes to [`IndexedTxGraph`](crate::indexed_tx_graph::IndexedTxGraph).
 | 
			
		||||
    pub indexed_tx_graph:
 | 
			
		||||
        crate::indexed_tx_graph::ChangeSet<A, crate::indexer::keychain_txout::ChangeSet<K>>,
 | 
			
		||||
    /// Stores the network type of the transaction data.
 | 
			
		||||
    pub network: Option<bitcoin::Network>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "miniscript")]
 | 
			
		||||
impl<K, A> core::default::Default for CombinedChangeSet<K, A> {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            chain: core::default::Default::default(),
 | 
			
		||||
            indexed_tx_graph: core::default::Default::default(),
 | 
			
		||||
            network: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "miniscript")]
 | 
			
		||||
impl<K: Ord, A: crate::Anchor> crate::Merge for CombinedChangeSet<K, A> {
 | 
			
		||||
    fn merge(&mut self, other: Self) {
 | 
			
		||||
        crate::Merge::merge(&mut self.chain, other.chain);
 | 
			
		||||
        crate::Merge::merge(&mut self.indexed_tx_graph, other.indexed_tx_graph);
 | 
			
		||||
        if other.network.is_some() {
 | 
			
		||||
            debug_assert!(
 | 
			
		||||
                self.network.is_none() || self.network == other.network,
 | 
			
		||||
                "network type must either be just introduced or remain the same"
 | 
			
		||||
            );
 | 
			
		||||
            self.network = other.network;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn is_empty(&self) -> bool {
 | 
			
		||||
        self.chain.is_empty() && self.indexed_tx_graph.is_empty() && self.network.is_none()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "miniscript")]
 | 
			
		||||
impl<K, A> From<crate::local_chain::ChangeSet> for CombinedChangeSet<K, A> {
 | 
			
		||||
    fn from(chain: crate::local_chain::ChangeSet) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            chain,
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "miniscript")]
 | 
			
		||||
impl<K, A> From<crate::indexed_tx_graph::ChangeSet<A, crate::indexer::keychain_txout::ChangeSet<K>>>
 | 
			
		||||
    for CombinedChangeSet<K, A>
 | 
			
		||||
{
 | 
			
		||||
    fn from(
 | 
			
		||||
        indexed_tx_graph: crate::indexed_tx_graph::ChangeSet<
 | 
			
		||||
            A,
 | 
			
		||||
            crate::indexer::keychain_txout::ChangeSet<K>,
 | 
			
		||||
        >,
 | 
			
		||||
    ) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            indexed_tx_graph,
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "miniscript")]
 | 
			
		||||
impl<K, A> From<crate::indexer::keychain_txout::ChangeSet<K>> for CombinedChangeSet<K, A> {
 | 
			
		||||
    fn from(indexer: crate::indexer::keychain_txout::ChangeSet<K>) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            indexed_tx_graph: crate::indexed_tx_graph::ChangeSet {
 | 
			
		||||
                indexer,
 | 
			
		||||
                ..Default::default()
 | 
			
		||||
            },
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,5 +1,7 @@
 | 
			
		||||
//! Contains the [`IndexedTxGraph`] and associated types. Refer to the
 | 
			
		||||
//! [`IndexedTxGraph`] documentation for more.
 | 
			
		||||
use core::fmt::Debug;
 | 
			
		||||
 | 
			
		||||
use alloc::vec::Vec;
 | 
			
		||||
use bitcoin::{Block, OutPoint, Transaction, TxOut, Txid};
 | 
			
		||||
 | 
			
		||||
@ -47,21 +49,24 @@ impl<A: Anchor, I: Indexer> IndexedTxGraph<A, I> {
 | 
			
		||||
    pub fn apply_changeset(&mut self, changeset: ChangeSet<A, I::ChangeSet>) {
 | 
			
		||||
        self.index.apply_changeset(changeset.indexer);
 | 
			
		||||
 | 
			
		||||
        for tx in &changeset.graph.txs {
 | 
			
		||||
        for tx in &changeset.tx_graph.txs {
 | 
			
		||||
            self.index.index_tx(tx);
 | 
			
		||||
        }
 | 
			
		||||
        for (&outpoint, txout) in &changeset.graph.txouts {
 | 
			
		||||
        for (&outpoint, txout) in &changeset.tx_graph.txouts {
 | 
			
		||||
            self.index.index_txout(outpoint, txout);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.graph.apply_changeset(changeset.graph);
 | 
			
		||||
        self.graph.apply_changeset(changeset.tx_graph);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Determines the [`ChangeSet`] between `self` and an empty [`IndexedTxGraph`].
 | 
			
		||||
    pub fn initial_changeset(&self) -> ChangeSet<A, I::ChangeSet> {
 | 
			
		||||
        let graph = self.graph.initial_changeset();
 | 
			
		||||
        let indexer = self.index.initial_changeset();
 | 
			
		||||
        ChangeSet { graph, indexer }
 | 
			
		||||
        ChangeSet {
 | 
			
		||||
            tx_graph: graph,
 | 
			
		||||
            indexer,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -89,21 +94,30 @@ where
 | 
			
		||||
    pub fn apply_update(&mut self, update: TxGraph<A>) -> ChangeSet<A, I::ChangeSet> {
 | 
			
		||||
        let graph = self.graph.apply_update(update);
 | 
			
		||||
        let indexer = self.index_tx_graph_changeset(&graph);
 | 
			
		||||
        ChangeSet { graph, indexer }
 | 
			
		||||
        ChangeSet {
 | 
			
		||||
            tx_graph: graph,
 | 
			
		||||
            indexer,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Insert a floating `txout` of given `outpoint`.
 | 
			
		||||
    pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<A, I::ChangeSet> {
 | 
			
		||||
        let graph = self.graph.insert_txout(outpoint, txout);
 | 
			
		||||
        let indexer = self.index_tx_graph_changeset(&graph);
 | 
			
		||||
        ChangeSet { graph, indexer }
 | 
			
		||||
        ChangeSet {
 | 
			
		||||
            tx_graph: graph,
 | 
			
		||||
            indexer,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Insert and index a transaction into the graph.
 | 
			
		||||
    pub fn insert_tx(&mut self, tx: Transaction) -> ChangeSet<A, I::ChangeSet> {
 | 
			
		||||
        let graph = self.graph.insert_tx(tx);
 | 
			
		||||
        let indexer = self.index_tx_graph_changeset(&graph);
 | 
			
		||||
        ChangeSet { graph, indexer }
 | 
			
		||||
        ChangeSet {
 | 
			
		||||
            tx_graph: graph,
 | 
			
		||||
            indexer,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Insert an `anchor` for a given transaction.
 | 
			
		||||
@ -151,7 +165,10 @@ where
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ChangeSet { graph, indexer }
 | 
			
		||||
        ChangeSet {
 | 
			
		||||
            tx_graph: graph,
 | 
			
		||||
            indexer,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Batch insert unconfirmed transactions, filtering out those that are irrelevant.
 | 
			
		||||
@ -185,7 +202,10 @@ where
 | 
			
		||||
                .map(|(tx, seen_at)| (tx.clone(), seen_at)),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        ChangeSet { graph, indexer }
 | 
			
		||||
        ChangeSet {
 | 
			
		||||
            tx_graph: graph,
 | 
			
		||||
            indexer,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Batch insert unconfirmed transactions.
 | 
			
		||||
@ -203,7 +223,10 @@ where
 | 
			
		||||
    ) -> ChangeSet<A, I::ChangeSet> {
 | 
			
		||||
        let graph = self.graph.batch_insert_unconfirmed(txs);
 | 
			
		||||
        let indexer = self.index_tx_graph_changeset(&graph);
 | 
			
		||||
        ChangeSet { graph, indexer }
 | 
			
		||||
        ChangeSet {
 | 
			
		||||
            tx_graph: graph,
 | 
			
		||||
            indexer,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -236,9 +259,9 @@ where
 | 
			
		||||
            if self.index.is_tx_relevant(tx) {
 | 
			
		||||
                let txid = tx.compute_txid();
 | 
			
		||||
                let anchor = A::from_block_position(block, block_id, tx_pos);
 | 
			
		||||
                changeset.graph.merge(self.graph.insert_tx(tx.clone()));
 | 
			
		||||
                changeset.tx_graph.merge(self.graph.insert_tx(tx.clone()));
 | 
			
		||||
                changeset
 | 
			
		||||
                    .graph
 | 
			
		||||
                    .tx_graph
 | 
			
		||||
                    .merge(self.graph.insert_anchor(txid, anchor));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@ -265,7 +288,16 @@ where
 | 
			
		||||
            graph.merge(self.graph.insert_tx(tx.clone()));
 | 
			
		||||
        }
 | 
			
		||||
        let indexer = self.index_tx_graph_changeset(&graph);
 | 
			
		||||
        ChangeSet { graph, indexer }
 | 
			
		||||
        ChangeSet {
 | 
			
		||||
            tx_graph: graph,
 | 
			
		||||
            indexer,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<A, I> AsRef<TxGraph<A>> for IndexedTxGraph<A, I> {
 | 
			
		||||
    fn as_ref(&self) -> &TxGraph<A> {
 | 
			
		||||
        &self.graph
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -285,7 +317,7 @@ where
 | 
			
		||||
#[must_use]
 | 
			
		||||
pub struct ChangeSet<A, IA> {
 | 
			
		||||
    /// [`TxGraph`] changeset.
 | 
			
		||||
    pub graph: tx_graph::ChangeSet<A>,
 | 
			
		||||
    pub tx_graph: tx_graph::ChangeSet<A>,
 | 
			
		||||
    /// [`Indexer`] changeset.
 | 
			
		||||
    pub indexer: IA,
 | 
			
		||||
}
 | 
			
		||||
@ -293,7 +325,7 @@ pub struct ChangeSet<A, IA> {
 | 
			
		||||
impl<A, IA: Default> Default for ChangeSet<A, IA> {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            graph: Default::default(),
 | 
			
		||||
            tx_graph: Default::default(),
 | 
			
		||||
            indexer: Default::default(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -301,38 +333,30 @@ impl<A, IA: Default> Default for ChangeSet<A, IA> {
 | 
			
		||||
 | 
			
		||||
impl<A: Anchor, IA: Merge> Merge for ChangeSet<A, IA> {
 | 
			
		||||
    fn merge(&mut self, other: Self) {
 | 
			
		||||
        self.graph.merge(other.graph);
 | 
			
		||||
        self.tx_graph.merge(other.tx_graph);
 | 
			
		||||
        self.indexer.merge(other.indexer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn is_empty(&self) -> bool {
 | 
			
		||||
        self.graph.is_empty() && self.indexer.is_empty()
 | 
			
		||||
        self.tx_graph.is_empty() && self.indexer.is_empty()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<A, IA: Default> From<tx_graph::ChangeSet<A>> for ChangeSet<A, IA> {
 | 
			
		||||
    fn from(graph: tx_graph::ChangeSet<A>) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            graph,
 | 
			
		||||
            tx_graph: graph,
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "miniscript")]
 | 
			
		||||
impl<A, K> From<crate::indexer::keychain_txout::ChangeSet<K>>
 | 
			
		||||
    for ChangeSet<A, crate::indexer::keychain_txout::ChangeSet<K>>
 | 
			
		||||
{
 | 
			
		||||
    fn from(indexer: crate::indexer::keychain_txout::ChangeSet<K>) -> Self {
 | 
			
		||||
impl<A> From<crate::keychain_txout::ChangeSet> for ChangeSet<A, crate::keychain_txout::ChangeSet> {
 | 
			
		||||
    fn from(indexer: crate::keychain_txout::ChangeSet) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            graph: Default::default(),
 | 
			
		||||
            tx_graph: Default::default(),
 | 
			
		||||
            indexer,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<A, I> AsRef<TxGraph<A>> for IndexedTxGraph<A, I> {
 | 
			
		||||
    fn as_ref(&self) -> &TxGraph<A> {
 | 
			
		||||
        &self.graph
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -5,7 +5,8 @@ use crate::{
 | 
			
		||||
    collections::*,
 | 
			
		||||
    miniscript::{Descriptor, DescriptorPublicKey},
 | 
			
		||||
    spk_iter::BIP32_MAX_INDEX,
 | 
			
		||||
    DescriptorExt, DescriptorId, Indexed, Indexer, KeychainIndexed, SpkIterator, SpkTxOutIndex,
 | 
			
		||||
    spk_txout::SpkTxOutIndex,
 | 
			
		||||
    DescriptorExt, DescriptorId, Indexed, Indexer, KeychainIndexed, SpkIterator,
 | 
			
		||||
};
 | 
			
		||||
use alloc::{borrow::ToOwned, vec::Vec};
 | 
			
		||||
use bitcoin::{Amount, OutPoint, Script, ScriptBuf, SignedAmount, Transaction, TxOut, Txid};
 | 
			
		||||
@ -135,7 +136,7 @@ impl<K> Default for KeychainTxOutIndex<K> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
 | 
			
		||||
    type ChangeSet = ChangeSet<K>;
 | 
			
		||||
    type ChangeSet = ChangeSet;
 | 
			
		||||
 | 
			
		||||
    fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet {
 | 
			
		||||
        let mut changeset = ChangeSet::default();
 | 
			
		||||
@ -154,7 +155,7 @@ impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn index_tx(&mut self, tx: &bitcoin::Transaction) -> Self::ChangeSet {
 | 
			
		||||
        let mut changeset = ChangeSet::<K>::default();
 | 
			
		||||
        let mut changeset = ChangeSet::default();
 | 
			
		||||
        let txid = tx.compute_txid();
 | 
			
		||||
        for (op, txout) in tx.output.iter().enumerate() {
 | 
			
		||||
            changeset.merge(self.index_txout(OutPoint::new(txid, op as u32), txout));
 | 
			
		||||
@ -164,10 +165,6 @@ impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
 | 
			
		||||
 | 
			
		||||
    fn initial_changeset(&self) -> Self::ChangeSet {
 | 
			
		||||
        ChangeSet {
 | 
			
		||||
            keychains_added: self
 | 
			
		||||
                .keychains()
 | 
			
		||||
                .map(|(k, v)| (k.clone(), v.clone()))
 | 
			
		||||
                .collect(),
 | 
			
		||||
            last_revealed: self.last_revealed.clone().into_iter().collect(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -354,7 +351,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
 | 
			
		||||
    /// keychain <-> descriptor is a one-to-one mapping that cannot be changed. Attempting to do so
 | 
			
		||||
    /// will return a [`InsertDescriptorError<K>`].
 | 
			
		||||
    ///
 | 
			
		||||
    /// `[KeychainTxOutIndex]` will prevent you from inserting two descriptors which derive the same
 | 
			
		||||
    /// [`KeychainTxOutIndex`] will prevent you from inserting two descriptors which derive the same
 | 
			
		||||
    /// script pubkey at index 0, but it's up to you to ensure that descriptors don't collide at
 | 
			
		||||
    /// other indices. If they do nothing catastrophic happens at the `KeychainTxOutIndex` level
 | 
			
		||||
    /// (one keychain just becomes the defacto owner of that spk arbitrarily) but this may have
 | 
			
		||||
@ -364,8 +361,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
 | 
			
		||||
        &mut self,
 | 
			
		||||
        keychain: K,
 | 
			
		||||
        descriptor: Descriptor<DescriptorPublicKey>,
 | 
			
		||||
    ) -> Result<ChangeSet<K>, InsertDescriptorError<K>> {
 | 
			
		||||
        let mut changeset = ChangeSet::<K>::default();
 | 
			
		||||
    ) -> Result<bool, InsertDescriptorError<K>> {
 | 
			
		||||
        let did = descriptor.descriptor_id();
 | 
			
		||||
        if !self.keychain_to_descriptor_id.contains_key(&keychain)
 | 
			
		||||
            && !self.descriptor_id_to_keychain.contains_key(&did)
 | 
			
		||||
@ -374,33 +370,31 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
 | 
			
		||||
            self.keychain_to_descriptor_id.insert(keychain.clone(), did);
 | 
			
		||||
            self.descriptor_id_to_keychain.insert(did, keychain.clone());
 | 
			
		||||
            self.replenish_inner_index(did, &keychain, self.lookahead);
 | 
			
		||||
            changeset
 | 
			
		||||
                .keychains_added
 | 
			
		||||
                .insert(keychain.clone(), descriptor);
 | 
			
		||||
        } else {
 | 
			
		||||
            if let Some(existing_desc_id) = self.keychain_to_descriptor_id.get(&keychain) {
 | 
			
		||||
                let descriptor = self.descriptors.get(existing_desc_id).expect("invariant");
 | 
			
		||||
                if *existing_desc_id != did {
 | 
			
		||||
                    return Err(InsertDescriptorError::KeychainAlreadyAssigned {
 | 
			
		||||
                        existing_assignment: descriptor.clone(),
 | 
			
		||||
                        keychain,
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return Ok(true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
            if let Some(existing_keychain) = self.descriptor_id_to_keychain.get(&did) {
 | 
			
		||||
                let descriptor = self.descriptors.get(&did).expect("invariant").clone();
 | 
			
		||||
 | 
			
		||||
                if *existing_keychain != keychain {
 | 
			
		||||
                    return Err(InsertDescriptorError::DescriptorAlreadyAssigned {
 | 
			
		||||
                        existing_assignment: existing_keychain.clone(),
 | 
			
		||||
                        descriptor,
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
        if let Some(existing_desc_id) = self.keychain_to_descriptor_id.get(&keychain) {
 | 
			
		||||
            let descriptor = self.descriptors.get(existing_desc_id).expect("invariant");
 | 
			
		||||
            if *existing_desc_id != did {
 | 
			
		||||
                return Err(InsertDescriptorError::KeychainAlreadyAssigned {
 | 
			
		||||
                    existing_assignment: descriptor.clone(),
 | 
			
		||||
                    keychain,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(changeset)
 | 
			
		||||
        if let Some(existing_keychain) = self.descriptor_id_to_keychain.get(&did) {
 | 
			
		||||
            let descriptor = self.descriptors.get(&did).expect("invariant").clone();
 | 
			
		||||
 | 
			
		||||
            if *existing_keychain != keychain {
 | 
			
		||||
                return Err(InsertDescriptorError::DescriptorAlreadyAssigned {
 | 
			
		||||
                    existing_assignment: existing_keychain.clone(),
 | 
			
		||||
                    descriptor,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(false)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Gets the descriptor associated with the keychain. Returns `None` if the keychain doesn't
 | 
			
		||||
@ -627,7 +621,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Convenience method to call [`Self::reveal_to_target`] on multiple keychains.
 | 
			
		||||
    pub fn reveal_to_target_multi(&mut self, keychains: &BTreeMap<K, u32>) -> ChangeSet<K> {
 | 
			
		||||
    pub fn reveal_to_target_multi(&mut self, keychains: &BTreeMap<K, u32>) -> ChangeSet {
 | 
			
		||||
        let mut changeset = ChangeSet::default();
 | 
			
		||||
 | 
			
		||||
        for (keychain, &index) in keychains {
 | 
			
		||||
@ -656,7 +650,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
 | 
			
		||||
        &mut self,
 | 
			
		||||
        keychain: &K,
 | 
			
		||||
        target_index: u32,
 | 
			
		||||
    ) -> Option<(Vec<Indexed<ScriptBuf>>, ChangeSet<K>)> {
 | 
			
		||||
    ) -> Option<(Vec<Indexed<ScriptBuf>>, ChangeSet)> {
 | 
			
		||||
        let mut changeset = ChangeSet::default();
 | 
			
		||||
        let mut spks: Vec<Indexed<ScriptBuf>> = vec![];
 | 
			
		||||
        while let Some((i, new)) = self.next_index(keychain) {
 | 
			
		||||
@ -687,7 +681,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
 | 
			
		||||
    ///  1. The descriptor has no wildcard and already has one script revealed.
 | 
			
		||||
    ///  2. The descriptor has already revealed scripts up to the numeric bound.
 | 
			
		||||
    ///  3. There is no descriptor associated with the given keychain.
 | 
			
		||||
    pub fn reveal_next_spk(&mut self, keychain: &K) -> Option<(Indexed<ScriptBuf>, ChangeSet<K>)> {
 | 
			
		||||
    pub fn reveal_next_spk(&mut self, keychain: &K) -> Option<(Indexed<ScriptBuf>, ChangeSet)> {
 | 
			
		||||
        let (next_index, new) = self.next_index(keychain)?;
 | 
			
		||||
        let mut changeset = ChangeSet::default();
 | 
			
		||||
 | 
			
		||||
@ -717,7 +711,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
 | 
			
		||||
    /// could be revealed (see [`reveal_next_spk`] for when this happens).
 | 
			
		||||
    ///
 | 
			
		||||
    /// [`reveal_next_spk`]: Self::reveal_next_spk
 | 
			
		||||
    pub fn next_unused_spk(&mut self, keychain: &K) -> Option<(Indexed<ScriptBuf>, ChangeSet<K>)> {
 | 
			
		||||
    pub fn next_unused_spk(&mut self, keychain: &K) -> Option<(Indexed<ScriptBuf>, ChangeSet)> {
 | 
			
		||||
        let next_unused = self
 | 
			
		||||
            .unused_keychain_spks(keychain)
 | 
			
		||||
            .next()
 | 
			
		||||
@ -780,26 +774,11 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Applies the `ChangeSet<K>` to the [`KeychainTxOutIndex<K>`]
 | 
			
		||||
    ///
 | 
			
		||||
    /// Keychains added by the `keychains_added` field of `ChangeSet<K>` respect the one-to-one
 | 
			
		||||
    /// keychain <-> descriptor invariant by silently ignoring attempts to violate it (but will
 | 
			
		||||
    /// panic if `debug_assertions` are enabled).
 | 
			
		||||
    pub fn apply_changeset(&mut self, changeset: ChangeSet<K>) {
 | 
			
		||||
        let ChangeSet {
 | 
			
		||||
            keychains_added,
 | 
			
		||||
            last_revealed,
 | 
			
		||||
        } = changeset;
 | 
			
		||||
        for (keychain, descriptor) in keychains_added {
 | 
			
		||||
            let _ignore_invariant_violation = self.insert_descriptor(keychain, descriptor);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (&desc_id, &index) in &last_revealed {
 | 
			
		||||
    pub fn apply_changeset(&mut self, changeset: ChangeSet) {
 | 
			
		||||
        for (&desc_id, &index) in &changeset.last_revealed {
 | 
			
		||||
            let v = self.last_revealed.entry(desc_id).or_default();
 | 
			
		||||
            *v = index.max(*v);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for did in last_revealed.keys() {
 | 
			
		||||
            self.replenish_inner_index_did(*did, self.lookahead);
 | 
			
		||||
            self.replenish_inner_index_did(desc_id, self.lookahead);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -860,49 +839,24 @@ impl<K: core::fmt::Debug> std::error::Error for InsertDescriptorError<K> {}
 | 
			
		||||
/// `keychains_added` is *not* monotone, once it is set any attempt to change it is subject to the
 | 
			
		||||
/// same *one-to-one* keychain <-> descriptor mapping invariant as [`KeychainTxOutIndex`] itself.
 | 
			
		||||
///
 | 
			
		||||
/// [`apply_changeset`]: KeychainTxOutIndex::apply_changeset
 | 
			
		||||
/// [`Merge`]: Self::merge
 | 
			
		||||
#[derive(Clone, Debug, PartialEq)]
 | 
			
		||||
/// [`KeychainTxOutIndex`]: crate::keychain_txout::KeychainTxOutIndex
 | 
			
		||||
/// [`apply_changeset`]: crate::keychain_txout::KeychainTxOutIndex::apply_changeset
 | 
			
		||||
/// [`merge`]: Self::merge
 | 
			
		||||
#[derive(Clone, Debug, Default, PartialEq)]
 | 
			
		||||
#[cfg_attr(
 | 
			
		||||
    feature = "serde",
 | 
			
		||||
    derive(serde::Deserialize, serde::Serialize),
 | 
			
		||||
    serde(
 | 
			
		||||
        crate = "serde_crate",
 | 
			
		||||
        bound(
 | 
			
		||||
            deserialize = "K: Ord + serde::Deserialize<'de>",
 | 
			
		||||
            serialize = "K: Ord + serde::Serialize"
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    serde(crate = "serde_crate")
 | 
			
		||||
)]
 | 
			
		||||
#[must_use]
 | 
			
		||||
pub struct ChangeSet<K> {
 | 
			
		||||
    /// Contains the keychains that have been added and their respective descriptor
 | 
			
		||||
    pub keychains_added: BTreeMap<K, Descriptor<DescriptorPublicKey>>,
 | 
			
		||||
pub struct ChangeSet {
 | 
			
		||||
    /// Contains for each descriptor_id the last revealed index of derivation
 | 
			
		||||
    pub last_revealed: BTreeMap<DescriptorId, u32>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<K: Ord> Merge for ChangeSet<K> {
 | 
			
		||||
    /// Merge another [`ChangeSet<K>`] into self.
 | 
			
		||||
    ///
 | 
			
		||||
    /// For the `keychains_added` field this method respects the invariants of
 | 
			
		||||
    /// [`insert_descriptor`]. `last_revealed` always becomes the larger of the two.
 | 
			
		||||
    ///
 | 
			
		||||
    /// [`insert_descriptor`]: KeychainTxOutIndex::insert_descriptor
 | 
			
		||||
impl Merge for ChangeSet {
 | 
			
		||||
    /// Merge another [`ChangeSet`] into self.
 | 
			
		||||
    fn merge(&mut self, other: Self) {
 | 
			
		||||
        for (new_keychain, new_descriptor) in other.keychains_added {
 | 
			
		||||
            // enforce 1-to-1 invariance
 | 
			
		||||
            if !self.keychains_added.contains_key(&new_keychain)
 | 
			
		||||
                // FIXME: very inefficient
 | 
			
		||||
                && self
 | 
			
		||||
                    .keychains_added
 | 
			
		||||
                    .values()
 | 
			
		||||
                    .all(|descriptor| descriptor != &new_descriptor)
 | 
			
		||||
            {
 | 
			
		||||
                self.keychains_added.insert(new_keychain, new_descriptor);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // for `last_revealed`, entries of `other` will take precedence ONLY if it is greater than
 | 
			
		||||
        // what was originally in `self`.
 | 
			
		||||
        for (desc_id, index) in other.last_revealed {
 | 
			
		||||
@ -922,25 +876,6 @@ impl<K: Ord> Merge for ChangeSet<K> {
 | 
			
		||||
 | 
			
		||||
    /// Returns whether the changeset are empty.
 | 
			
		||||
    fn is_empty(&self) -> bool {
 | 
			
		||||
        self.last_revealed.is_empty() && self.keychains_added.is_empty()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<K> Default for ChangeSet<K> {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            last_revealed: BTreeMap::default(),
 | 
			
		||||
            keychains_added: BTreeMap::default(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Clone, Debug, Eq, PartialEq)]
 | 
			
		||||
/// The keychain doesn't exist. Most likley hasn't been inserted with [`KeychainTxOutIndex::insert_descriptor`].
 | 
			
		||||
pub struct NoSuchKeychain<K>(K);
 | 
			
		||||
 | 
			
		||||
impl<K: Debug> core::fmt::Display for NoSuchKeychain<K> {
 | 
			
		||||
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
 | 
			
		||||
        write!(f, "no such keychain {:?} exists", &self.0)
 | 
			
		||||
        self.last_revealed.is_empty()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -208,7 +208,7 @@ impl<I: Clone + Ord + core::fmt::Debug> SpkTxOutIndex<I> {
 | 
			
		||||
    /// # Example
 | 
			
		||||
    ///
 | 
			
		||||
    /// ```rust
 | 
			
		||||
    /// # use bdk_chain::SpkTxOutIndex;
 | 
			
		||||
    /// # use bdk_chain::spk_txout::SpkTxOutIndex;
 | 
			
		||||
    ///
 | 
			
		||||
    /// // imagine our spks are indexed like (keychain, derivation_index).
 | 
			
		||||
    /// let txout_index = SpkTxOutIndex::<(u32, u32)>::default();
 | 
			
		||||
 | 
			
		||||
@ -28,7 +28,7 @@ pub use chain_data::*;
 | 
			
		||||
pub mod indexed_tx_graph;
 | 
			
		||||
pub use indexed_tx_graph::IndexedTxGraph;
 | 
			
		||||
pub mod indexer;
 | 
			
		||||
pub use indexer::spk_txout::*;
 | 
			
		||||
pub use indexer::spk_txout;
 | 
			
		||||
pub use indexer::Indexer;
 | 
			
		||||
pub mod local_chain;
 | 
			
		||||
mod tx_data_traits;
 | 
			
		||||
@ -37,6 +37,8 @@ pub use tx_data_traits::*;
 | 
			
		||||
pub use tx_graph::TxGraph;
 | 
			
		||||
mod chain_oracle;
 | 
			
		||||
pub use chain_oracle::*;
 | 
			
		||||
mod persist;
 | 
			
		||||
pub use persist::*;
 | 
			
		||||
 | 
			
		||||
#[doc(hidden)]
 | 
			
		||||
pub mod example_utils;
 | 
			
		||||
@ -50,15 +52,18 @@ pub use descriptor_ext::{DescriptorExt, DescriptorId};
 | 
			
		||||
#[cfg(feature = "miniscript")]
 | 
			
		||||
mod spk_iter;
 | 
			
		||||
#[cfg(feature = "miniscript")]
 | 
			
		||||
pub use indexer::keychain_txout;
 | 
			
		||||
#[cfg(feature = "miniscript")]
 | 
			
		||||
pub use spk_iter::*;
 | 
			
		||||
mod changeset;
 | 
			
		||||
pub use changeset::*;
 | 
			
		||||
#[cfg(feature = "rusqlite")]
 | 
			
		||||
pub mod rusqlite_impl;
 | 
			
		||||
pub mod spk_client;
 | 
			
		||||
 | 
			
		||||
#[allow(unused_imports)]
 | 
			
		||||
#[macro_use]
 | 
			
		||||
extern crate alloc;
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "rusqlite")]
 | 
			
		||||
pub extern crate rusqlite_crate as rusqlite;
 | 
			
		||||
#[cfg(feature = "serde")]
 | 
			
		||||
pub extern crate serde_crate as serde;
 | 
			
		||||
 | 
			
		||||
@ -104,3 +109,20 @@ pub const COINBASE_MATURITY: u32 = 100;
 | 
			
		||||
pub type Indexed<T> = (u32, T);
 | 
			
		||||
/// A tuple of keychain `K`, derivation index (`u32`) and a `T` associated with them.
 | 
			
		||||
pub type KeychainIndexed<K, T> = ((K, u32), T);
 | 
			
		||||
 | 
			
		||||
/// A wrapper that we use to impl remote traits for types in our crate or dependency crates.
 | 
			
		||||
pub struct Impl<T>(pub T);
 | 
			
		||||
 | 
			
		||||
impl<T> From<T> for Impl<T> {
 | 
			
		||||
    fn from(value: T) -> Self {
 | 
			
		||||
        Self(value)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T> core::ops::Deref for Impl<T> {
 | 
			
		||||
    type Target = T;
 | 
			
		||||
 | 
			
		||||
    fn deref(&self) -> &Self::Target {
 | 
			
		||||
        &self.0
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -4,17 +4,11 @@ use core::convert::Infallible;
 | 
			
		||||
use core::ops::RangeBounds;
 | 
			
		||||
 | 
			
		||||
use crate::collections::BTreeMap;
 | 
			
		||||
use crate::{BlockId, ChainOracle};
 | 
			
		||||
use crate::{BlockId, ChainOracle, Merge};
 | 
			
		||||
use alloc::sync::Arc;
 | 
			
		||||
use bitcoin::block::Header;
 | 
			
		||||
use bitcoin::BlockHash;
 | 
			
		||||
 | 
			
		||||
/// The [`ChangeSet`] represents changes to [`LocalChain`].
 | 
			
		||||
///
 | 
			
		||||
/// The key represents the block height, and the value either represents added a new [`CheckPoint`]
 | 
			
		||||
/// (if [`Some`]), or removing a [`CheckPoint`] (if [`None`]).
 | 
			
		||||
pub type ChangeSet = BTreeMap<u32, Option<BlockHash>>;
 | 
			
		||||
 | 
			
		||||
/// A [`LocalChain`] checkpoint is used to find the agreement point between two chains and as a
 | 
			
		||||
/// transaction anchor.
 | 
			
		||||
///
 | 
			
		||||
@ -216,7 +210,7 @@ impl CheckPoint {
 | 
			
		||||
 | 
			
		||||
    /// Apply `changeset` to the checkpoint.
 | 
			
		||||
    fn apply_changeset(mut self, changeset: &ChangeSet) -> Result<CheckPoint, MissingGenesisError> {
 | 
			
		||||
        if let Some(start_height) = changeset.keys().next().cloned() {
 | 
			
		||||
        if let Some(start_height) = changeset.blocks.keys().next().cloned() {
 | 
			
		||||
            // changes after point of agreement
 | 
			
		||||
            let mut extension = BTreeMap::default();
 | 
			
		||||
            // point of agreement
 | 
			
		||||
@ -231,7 +225,7 @@ impl CheckPoint {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            for (&height, &hash) in changeset {
 | 
			
		||||
            for (&height, &hash) in &changeset.blocks {
 | 
			
		||||
                match hash {
 | 
			
		||||
                    Some(hash) => {
 | 
			
		||||
                        extension.insert(height, hash);
 | 
			
		||||
@ -331,7 +325,7 @@ impl LocalChain {
 | 
			
		||||
 | 
			
		||||
    /// Construct a [`LocalChain`] from an initial `changeset`.
 | 
			
		||||
    pub fn from_changeset(changeset: ChangeSet) -> Result<Self, MissingGenesisError> {
 | 
			
		||||
        let genesis_entry = changeset.get(&0).copied().flatten();
 | 
			
		||||
        let genesis_entry = changeset.blocks.get(&0).copied().flatten();
 | 
			
		||||
        let genesis_hash = match genesis_entry {
 | 
			
		||||
            Some(hash) => hash,
 | 
			
		||||
            None => return Err(MissingGenesisError),
 | 
			
		||||
@ -521,12 +515,14 @@ impl LocalChain {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut changeset = ChangeSet::default();
 | 
			
		||||
        changeset.insert(block_id.height, Some(block_id.hash));
 | 
			
		||||
        changeset
 | 
			
		||||
            .blocks
 | 
			
		||||
            .insert(block_id.height, Some(block_id.hash));
 | 
			
		||||
        self.apply_changeset(&changeset)
 | 
			
		||||
            .map_err(|_| AlterCheckPointError {
 | 
			
		||||
                height: 0,
 | 
			
		||||
                original_hash: self.genesis_hash(),
 | 
			
		||||
                update_hash: changeset.get(&0).cloned().flatten(),
 | 
			
		||||
                update_hash: changeset.blocks.get(&0).cloned().flatten(),
 | 
			
		||||
            })?;
 | 
			
		||||
        Ok(changeset)
 | 
			
		||||
    }
 | 
			
		||||
@ -548,7 +544,7 @@ impl LocalChain {
 | 
			
		||||
            if cp_id.height < block_id.height {
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            changeset.insert(cp_id.height, None);
 | 
			
		||||
            changeset.blocks.insert(cp_id.height, None);
 | 
			
		||||
            if cp_id == block_id {
 | 
			
		||||
                remove_from = Some(cp);
 | 
			
		||||
            }
 | 
			
		||||
@ -569,13 +565,16 @@ impl LocalChain {
 | 
			
		||||
    /// Derives an initial [`ChangeSet`], meaning that it can be applied to an empty chain to
 | 
			
		||||
    /// recover the current chain.
 | 
			
		||||
    pub fn initial_changeset(&self) -> ChangeSet {
 | 
			
		||||
        self.tip
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|cp| {
 | 
			
		||||
                let block_id = cp.block_id();
 | 
			
		||||
                (block_id.height, Some(block_id.hash))
 | 
			
		||||
            })
 | 
			
		||||
            .collect()
 | 
			
		||||
        ChangeSet {
 | 
			
		||||
            blocks: self
 | 
			
		||||
                .tip
 | 
			
		||||
                .iter()
 | 
			
		||||
                .map(|cp| {
 | 
			
		||||
                    let block_id = cp.block_id();
 | 
			
		||||
                    (block_id.height, Some(block_id.hash))
 | 
			
		||||
                })
 | 
			
		||||
                .collect(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Iterate over checkpoints in descending height order.
 | 
			
		||||
@ -587,7 +586,7 @@ impl LocalChain {
 | 
			
		||||
 | 
			
		||||
    fn _check_changeset_is_applied(&self, changeset: &ChangeSet) -> bool {
 | 
			
		||||
        let mut curr_cp = self.tip.clone();
 | 
			
		||||
        for (height, exp_hash) in changeset.iter().rev() {
 | 
			
		||||
        for (height, exp_hash) in changeset.blocks.iter().rev() {
 | 
			
		||||
            match curr_cp.get(*height) {
 | 
			
		||||
                Some(query_cp) => {
 | 
			
		||||
                    if query_cp.height() != *height || Some(query_cp.hash()) != *exp_hash {
 | 
			
		||||
@ -630,6 +629,58 @@ impl LocalChain {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// The [`ChangeSet`] represents changes to [`LocalChain`].
 | 
			
		||||
#[derive(Debug, Default, Clone, PartialEq)]
 | 
			
		||||
#[cfg_attr(
 | 
			
		||||
    feature = "serde",
 | 
			
		||||
    derive(serde::Deserialize, serde::Serialize),
 | 
			
		||||
    serde(crate = "serde_crate")
 | 
			
		||||
)]
 | 
			
		||||
pub struct ChangeSet {
 | 
			
		||||
    /// Changes to the [`LocalChain`] blocks.
 | 
			
		||||
    ///
 | 
			
		||||
    /// The key represents the block height, and the value either represents added a new [`CheckPoint`]
 | 
			
		||||
    /// (if [`Some`]), or removing a [`CheckPoint`] (if [`None`]).
 | 
			
		||||
    pub blocks: BTreeMap<u32, Option<BlockHash>>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Merge for ChangeSet {
 | 
			
		||||
    fn merge(&mut self, other: Self) {
 | 
			
		||||
        Merge::merge(&mut self.blocks, other.blocks)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn is_empty(&self) -> bool {
 | 
			
		||||
        self.blocks.is_empty()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<B: IntoIterator<Item = (u32, Option<BlockHash>)>> From<B> for ChangeSet {
 | 
			
		||||
    fn from(blocks: B) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            blocks: blocks.into_iter().collect(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromIterator<(u32, Option<BlockHash>)> for ChangeSet {
 | 
			
		||||
    fn from_iter<T: IntoIterator<Item = (u32, Option<BlockHash>)>>(iter: T) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            blocks: iter.into_iter().collect(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromIterator<(u32, BlockHash)> for ChangeSet {
 | 
			
		||||
    fn from_iter<T: IntoIterator<Item = (u32, BlockHash)>>(iter: T) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            blocks: iter
 | 
			
		||||
                .into_iter()
 | 
			
		||||
                .map(|(height, hash)| (height, Some(hash)))
 | 
			
		||||
                .collect(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// An error which occurs when a [`LocalChain`] is constructed without a genesis checkpoint.
 | 
			
		||||
#[derive(Clone, Debug, PartialEq)]
 | 
			
		||||
pub struct MissingGenesisError;
 | 
			
		||||
@ -761,7 +812,7 @@ fn merge_chains(
 | 
			
		||||
        match (curr_orig.as_ref(), curr_update.as_ref()) {
 | 
			
		||||
            // Update block that doesn't exist in the original chain
 | 
			
		||||
            (o, Some(u)) if Some(u.height()) > o.map(|o| o.height()) => {
 | 
			
		||||
                changeset.insert(u.height(), Some(u.hash()));
 | 
			
		||||
                changeset.blocks.insert(u.height(), Some(u.hash()));
 | 
			
		||||
                prev_update = curr_update.take();
 | 
			
		||||
            }
 | 
			
		||||
            // Original block that isn't in the update
 | 
			
		||||
@ -813,9 +864,9 @@ fn merge_chains(
 | 
			
		||||
                } else {
 | 
			
		||||
                    // We have an invalidation height so we set the height to the updated hash and
 | 
			
		||||
                    // also purge all the original chain block hashes above this block.
 | 
			
		||||
                    changeset.insert(u.height(), Some(u.hash()));
 | 
			
		||||
                    changeset.blocks.insert(u.height(), Some(u.hash()));
 | 
			
		||||
                    for invalidated_height in potentially_invalidated_heights.drain(..) {
 | 
			
		||||
                        changeset.insert(invalidated_height, None);
 | 
			
		||||
                        changeset.blocks.insert(invalidated_height, None);
 | 
			
		||||
                    }
 | 
			
		||||
                    prev_orig_was_invalidated = true;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										169
									
								
								crates/chain/src/persist.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								crates/chain/src/persist.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,169 @@
 | 
			
		||||
use core::{
 | 
			
		||||
    future::Future,
 | 
			
		||||
    ops::{Deref, DerefMut},
 | 
			
		||||
    pin::Pin,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use alloc::boxed::Box;
 | 
			
		||||
 | 
			
		||||
use crate::Merge;
 | 
			
		||||
 | 
			
		||||
/// Represents a type that contains staged changes.
 | 
			
		||||
pub trait Staged {
 | 
			
		||||
    /// Type for staged changes.
 | 
			
		||||
    type ChangeSet: Merge;
 | 
			
		||||
 | 
			
		||||
    /// Get mutable reference of staged changes.
 | 
			
		||||
    fn staged(&mut self) -> &mut Self::ChangeSet;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Trait that persists the type with `Db`.
 | 
			
		||||
///
 | 
			
		||||
/// Methods of this trait should not be called directly.
 | 
			
		||||
pub trait PersistWith<Db>: Staged + Sized {
 | 
			
		||||
    /// Parameters for [`PersistWith::create`].
 | 
			
		||||
    type CreateParams;
 | 
			
		||||
    /// Parameters for [`PersistWith::load`].
 | 
			
		||||
    type LoadParams;
 | 
			
		||||
    /// Error type of [`PersistWith::create`].
 | 
			
		||||
    type CreateError;
 | 
			
		||||
    /// Error type of [`PersistWith::load`].
 | 
			
		||||
    type LoadError;
 | 
			
		||||
    /// Error type of [`PersistWith::persist`].
 | 
			
		||||
    type PersistError;
 | 
			
		||||
 | 
			
		||||
    /// Initialize the `Db` and create `Self`.
 | 
			
		||||
    fn create(db: &mut Db, params: Self::CreateParams) -> Result<Self, Self::CreateError>;
 | 
			
		||||
 | 
			
		||||
    /// Initialize the `Db` and load a previously-persisted `Self`.
 | 
			
		||||
    fn load(db: &mut Db, params: Self::LoadParams) -> Result<Option<Self>, Self::LoadError>;
 | 
			
		||||
 | 
			
		||||
    /// Persist changes to the `Db`.
 | 
			
		||||
    fn persist(
 | 
			
		||||
        db: &mut Db,
 | 
			
		||||
        changeset: &<Self as Staged>::ChangeSet,
 | 
			
		||||
    ) -> Result<(), Self::PersistError>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type FutureResult<'a, T, E> = Pin<Box<dyn Future<Output = Result<T, E>> + Send + 'a>>;
 | 
			
		||||
 | 
			
		||||
/// Trait that persists the type with an async `Db`.
 | 
			
		||||
pub trait PersistAsyncWith<Db>: Staged + Sized {
 | 
			
		||||
    /// Parameters for [`PersistAsyncWith::create`].
 | 
			
		||||
    type CreateParams;
 | 
			
		||||
    /// Parameters for [`PersistAsyncWith::load`].
 | 
			
		||||
    type LoadParams;
 | 
			
		||||
    /// Error type of [`PersistAsyncWith::create`].
 | 
			
		||||
    type CreateError;
 | 
			
		||||
    /// Error type of [`PersistAsyncWith::load`].
 | 
			
		||||
    type LoadError;
 | 
			
		||||
    /// Error type of [`PersistAsyncWith::persist`].
 | 
			
		||||
    type PersistError;
 | 
			
		||||
 | 
			
		||||
    /// Initialize the `Db` and create `Self`.
 | 
			
		||||
    fn create(db: &mut Db, params: Self::CreateParams) -> FutureResult<Self, Self::CreateError>;
 | 
			
		||||
 | 
			
		||||
    /// Initialize the `Db` and load a previously-persisted `Self`.
 | 
			
		||||
    fn load(db: &mut Db, params: Self::LoadParams) -> FutureResult<Option<Self>, Self::LoadError>;
 | 
			
		||||
 | 
			
		||||
    /// Persist changes to the `Db`.
 | 
			
		||||
    fn persist<'a>(
 | 
			
		||||
        db: &'a mut Db,
 | 
			
		||||
        changeset: &'a <Self as Staged>::ChangeSet,
 | 
			
		||||
    ) -> FutureResult<'a, (), Self::PersistError>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Represents a persisted `T`.
 | 
			
		||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
 | 
			
		||||
pub struct Persisted<T> {
 | 
			
		||||
    inner: T,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T> Persisted<T> {
 | 
			
		||||
    /// Create a new persisted `T`.
 | 
			
		||||
    pub fn create<Db>(db: &mut Db, params: T::CreateParams) -> Result<Self, T::CreateError>
 | 
			
		||||
    where
 | 
			
		||||
        T: PersistWith<Db>,
 | 
			
		||||
    {
 | 
			
		||||
        T::create(db, params).map(|inner| Self { inner })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Create a new persisted `T` with async `Db`.
 | 
			
		||||
    pub async fn create_async<Db>(
 | 
			
		||||
        db: &mut Db,
 | 
			
		||||
        params: T::CreateParams,
 | 
			
		||||
    ) -> Result<Self, T::CreateError>
 | 
			
		||||
    where
 | 
			
		||||
        T: PersistAsyncWith<Db>,
 | 
			
		||||
    {
 | 
			
		||||
        T::create(db, params).await.map(|inner| Self { inner })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Construct a persisted `T` from `Db`.
 | 
			
		||||
    pub fn load<Db>(db: &mut Db, params: T::LoadParams) -> Result<Option<Self>, T::LoadError>
 | 
			
		||||
    where
 | 
			
		||||
        T: PersistWith<Db>,
 | 
			
		||||
    {
 | 
			
		||||
        Ok(T::load(db, params)?.map(|inner| Self { inner }))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Contruct a persisted `T` from an async `Db`.
 | 
			
		||||
    pub async fn load_async<Db>(
 | 
			
		||||
        db: &mut Db,
 | 
			
		||||
        params: T::LoadParams,
 | 
			
		||||
    ) -> Result<Option<Self>, T::LoadError>
 | 
			
		||||
    where
 | 
			
		||||
        T: PersistAsyncWith<Db>,
 | 
			
		||||
    {
 | 
			
		||||
        Ok(T::load(db, params).await?.map(|inner| Self { inner }))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Persist staged changes of `T` into `Db`.
 | 
			
		||||
    ///
 | 
			
		||||
    /// If the database errors, the staged changes will not be cleared.
 | 
			
		||||
    pub fn persist<Db>(&mut self, db: &mut Db) -> Result<bool, T::PersistError>
 | 
			
		||||
    where
 | 
			
		||||
        T: PersistWith<Db>,
 | 
			
		||||
    {
 | 
			
		||||
        let stage = T::staged(&mut self.inner);
 | 
			
		||||
        if stage.is_empty() {
 | 
			
		||||
            return Ok(false);
 | 
			
		||||
        }
 | 
			
		||||
        T::persist(db, &*stage)?;
 | 
			
		||||
        stage.take();
 | 
			
		||||
        Ok(true)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Persist staged changes of `T` into an async `Db`.
 | 
			
		||||
    ///
 | 
			
		||||
    /// If the database errors, the staged changes will not be cleared.
 | 
			
		||||
    pub async fn persist_async<'a, Db>(
 | 
			
		||||
        &'a mut self,
 | 
			
		||||
        db: &'a mut Db,
 | 
			
		||||
    ) -> Result<bool, T::PersistError>
 | 
			
		||||
    where
 | 
			
		||||
        T: PersistAsyncWith<Db>,
 | 
			
		||||
    {
 | 
			
		||||
        let stage = T::staged(&mut self.inner);
 | 
			
		||||
        if stage.is_empty() {
 | 
			
		||||
            return Ok(false);
 | 
			
		||||
        }
 | 
			
		||||
        T::persist(db, &*stage).await?;
 | 
			
		||||
        stage.take();
 | 
			
		||||
        Ok(true)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T> Deref for Persisted<T> {
 | 
			
		||||
    type Target = T;
 | 
			
		||||
 | 
			
		||||
    fn deref(&self) -> &Self::Target {
 | 
			
		||||
        &self.inner
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T> DerefMut for Persisted<T> {
 | 
			
		||||
    fn deref_mut(&mut self) -> &mut Self::Target {
 | 
			
		||||
        &mut self.inner
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										530
									
								
								crates/chain/src/rusqlite_impl.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										530
									
								
								crates/chain/src/rusqlite_impl.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,530 @@
 | 
			
		||||
//! Module for stuff
 | 
			
		||||
 | 
			
		||||
use crate::*;
 | 
			
		||||
use core::str::FromStr;
 | 
			
		||||
 | 
			
		||||
use alloc::{borrow::ToOwned, boxed::Box, string::ToString, sync::Arc, vec::Vec};
 | 
			
		||||
use bitcoin::consensus::{Decodable, Encodable};
 | 
			
		||||
use rusqlite;
 | 
			
		||||
use rusqlite::named_params;
 | 
			
		||||
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef};
 | 
			
		||||
use rusqlite::OptionalExtension;
 | 
			
		||||
use rusqlite::Transaction;
 | 
			
		||||
 | 
			
		||||
/// Table name for schemas.
 | 
			
		||||
pub const SCHEMAS_TABLE_NAME: &str = "bdk_schemas";
 | 
			
		||||
 | 
			
		||||
/// Initialize the schema table.
 | 
			
		||||
fn init_schemas_table(db_tx: &Transaction) -> rusqlite::Result<()> {
 | 
			
		||||
    let sql = format!("CREATE TABLE IF NOT EXISTS {}( name TEXT PRIMARY KEY NOT NULL, version INTEGER NOT NULL ) STRICT", SCHEMAS_TABLE_NAME);
 | 
			
		||||
    db_tx.execute(&sql, ())?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get schema version of `schema_name`.
 | 
			
		||||
fn schema_version(db_tx: &Transaction, schema_name: &str) -> rusqlite::Result<Option<u32>> {
 | 
			
		||||
    let sql = format!(
 | 
			
		||||
        "SELECT version FROM {} WHERE name=:name",
 | 
			
		||||
        SCHEMAS_TABLE_NAME
 | 
			
		||||
    );
 | 
			
		||||
    db_tx
 | 
			
		||||
        .query_row(&sql, named_params! { ":name": schema_name }, |row| {
 | 
			
		||||
            row.get::<_, u32>("version")
 | 
			
		||||
        })
 | 
			
		||||
        .optional()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Set the `schema_version` of `schema_name`.
 | 
			
		||||
fn set_schema_version(
 | 
			
		||||
    db_tx: &Transaction,
 | 
			
		||||
    schema_name: &str,
 | 
			
		||||
    schema_version: u32,
 | 
			
		||||
) -> rusqlite::Result<()> {
 | 
			
		||||
    let sql = format!(
 | 
			
		||||
        "REPLACE INTO {}(name, version) VALUES(:name, :version)",
 | 
			
		||||
        SCHEMAS_TABLE_NAME,
 | 
			
		||||
    );
 | 
			
		||||
    db_tx.execute(
 | 
			
		||||
        &sql,
 | 
			
		||||
        named_params! { ":name": schema_name, ":version": schema_version },
 | 
			
		||||
    )?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Runs logic that initializes/migrates the table schemas.
 | 
			
		||||
pub fn migrate_schema(
 | 
			
		||||
    db_tx: &Transaction,
 | 
			
		||||
    schema_name: &str,
 | 
			
		||||
    versioned_scripts: &[&[&str]],
 | 
			
		||||
) -> rusqlite::Result<()> {
 | 
			
		||||
    init_schemas_table(db_tx)?;
 | 
			
		||||
    let current_version = schema_version(db_tx, schema_name)?;
 | 
			
		||||
    let exec_from = current_version.map_or(0_usize, |v| v as usize + 1);
 | 
			
		||||
    let scripts_to_exec = versioned_scripts.iter().enumerate().skip(exec_from);
 | 
			
		||||
    for (version, &script) in scripts_to_exec {
 | 
			
		||||
        set_schema_version(db_tx, schema_name, version as u32)?;
 | 
			
		||||
        for statement in script {
 | 
			
		||||
            db_tx.execute(statement, ())?;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromSql for Impl<bitcoin::Txid> {
 | 
			
		||||
    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
 | 
			
		||||
        bitcoin::Txid::from_str(value.as_str()?)
 | 
			
		||||
            .map(Self)
 | 
			
		||||
            .map_err(from_sql_error)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ToSql for Impl<bitcoin::Txid> {
 | 
			
		||||
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
 | 
			
		||||
        Ok(self.to_string().into())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromSql for Impl<bitcoin::BlockHash> {
 | 
			
		||||
    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
 | 
			
		||||
        bitcoin::BlockHash::from_str(value.as_str()?)
 | 
			
		||||
            .map(Self)
 | 
			
		||||
            .map_err(from_sql_error)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ToSql for Impl<bitcoin::BlockHash> {
 | 
			
		||||
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
 | 
			
		||||
        Ok(self.to_string().into())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "miniscript")]
 | 
			
		||||
impl FromSql for Impl<DescriptorId> {
 | 
			
		||||
    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
 | 
			
		||||
        DescriptorId::from_str(value.as_str()?)
 | 
			
		||||
            .map(Self)
 | 
			
		||||
            .map_err(from_sql_error)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "miniscript")]
 | 
			
		||||
impl ToSql for Impl<DescriptorId> {
 | 
			
		||||
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
 | 
			
		||||
        Ok(self.to_string().into())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromSql for Impl<bitcoin::Transaction> {
 | 
			
		||||
    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
 | 
			
		||||
        bitcoin::Transaction::consensus_decode_from_finite_reader(&mut value.as_bytes()?)
 | 
			
		||||
            .map(Self)
 | 
			
		||||
            .map_err(from_sql_error)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ToSql for Impl<bitcoin::Transaction> {
 | 
			
		||||
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
 | 
			
		||||
        let mut bytes = Vec::<u8>::new();
 | 
			
		||||
        self.consensus_encode(&mut bytes).map_err(to_sql_error)?;
 | 
			
		||||
        Ok(bytes.into())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromSql for Impl<bitcoin::ScriptBuf> {
 | 
			
		||||
    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
 | 
			
		||||
        Ok(bitcoin::Script::from_bytes(value.as_bytes()?)
 | 
			
		||||
            .to_owned()
 | 
			
		||||
            .into())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ToSql for Impl<bitcoin::ScriptBuf> {
 | 
			
		||||
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
 | 
			
		||||
        Ok(self.as_bytes().into())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromSql for Impl<bitcoin::Amount> {
 | 
			
		||||
    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
 | 
			
		||||
        Ok(bitcoin::Amount::from_sat(value.as_i64()?.try_into().map_err(from_sql_error)?).into())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ToSql for Impl<bitcoin::Amount> {
 | 
			
		||||
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
 | 
			
		||||
        let amount: i64 = self.to_sat().try_into().map_err(to_sql_error)?;
 | 
			
		||||
        Ok(amount.into())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<A: Anchor + serde_crate::de::DeserializeOwned> FromSql for Impl<A> {
 | 
			
		||||
    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
 | 
			
		||||
        serde_json::from_str(value.as_str()?)
 | 
			
		||||
            .map(Impl)
 | 
			
		||||
            .map_err(from_sql_error)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<A: Anchor + serde_crate::Serialize> ToSql for Impl<A> {
 | 
			
		||||
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
 | 
			
		||||
        serde_json::to_string(&self.0)
 | 
			
		||||
            .map(Into::into)
 | 
			
		||||
            .map_err(to_sql_error)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "miniscript")]
 | 
			
		||||
impl FromSql for Impl<miniscript::Descriptor<miniscript::DescriptorPublicKey>> {
 | 
			
		||||
    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
 | 
			
		||||
        miniscript::Descriptor::from_str(value.as_str()?)
 | 
			
		||||
            .map(Self)
 | 
			
		||||
            .map_err(from_sql_error)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "miniscript")]
 | 
			
		||||
impl ToSql for Impl<miniscript::Descriptor<miniscript::DescriptorPublicKey>> {
 | 
			
		||||
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
 | 
			
		||||
        Ok(self.to_string().into())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl FromSql for Impl<bitcoin::Network> {
 | 
			
		||||
    fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
 | 
			
		||||
        bitcoin::Network::from_str(value.as_str()?)
 | 
			
		||||
            .map(Self)
 | 
			
		||||
            .map_err(from_sql_error)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ToSql for Impl<bitcoin::Network> {
 | 
			
		||||
    fn to_sql(&self) -> rusqlite::Result<ToSqlOutput<'_>> {
 | 
			
		||||
        Ok(self.to_string().into())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn from_sql_error<E: std::error::Error + Send + Sync + 'static>(err: E) -> FromSqlError {
 | 
			
		||||
    FromSqlError::Other(Box::new(err))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn to_sql_error<E: std::error::Error + Send + Sync + 'static>(err: E) -> rusqlite::Error {
 | 
			
		||||
    rusqlite::Error::ToSqlConversionFailure(Box::new(err))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<A> tx_graph::ChangeSet<A>
 | 
			
		||||
where
 | 
			
		||||
    A: Anchor + Clone + Ord + serde::Serialize + serde::de::DeserializeOwned,
 | 
			
		||||
{
 | 
			
		||||
    /// Schema name for [`tx_graph::ChangeSet`].
 | 
			
		||||
    pub const SCHEMA_NAME: &'static str = "bdk_txgraph";
 | 
			
		||||
    /// Name of table that stores full transactions and `last_seen` timestamps.
 | 
			
		||||
    pub const TXS_TABLE_NAME: &'static str = "bdk_txs";
 | 
			
		||||
    /// Name of table that stores floating txouts.
 | 
			
		||||
    pub const TXOUTS_TABLE_NAME: &'static str = "bdk_txouts";
 | 
			
		||||
    /// Name of table that stores [`Anchor`]s.
 | 
			
		||||
    pub const ANCHORS_TABLE_NAME: &'static str = "bdk_anchors";
 | 
			
		||||
 | 
			
		||||
    /// Initialize sqlite tables.
 | 
			
		||||
    fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
 | 
			
		||||
        let schema_v0: &[&str] = &[
 | 
			
		||||
            // full transactions
 | 
			
		||||
            &format!(
 | 
			
		||||
                "CREATE TABLE {} ( \
 | 
			
		||||
                txid TEXT PRIMARY KEY NOT NULL, \
 | 
			
		||||
                raw_tx BLOB, \
 | 
			
		||||
                last_seen INTEGER \
 | 
			
		||||
                ) STRICT",
 | 
			
		||||
                Self::TXS_TABLE_NAME,
 | 
			
		||||
            ),
 | 
			
		||||
            // floating txouts
 | 
			
		||||
            &format!(
 | 
			
		||||
                "CREATE TABLE {} ( \
 | 
			
		||||
                txid TEXT NOT NULL, \
 | 
			
		||||
                vout INTEGER NOT NULL, \
 | 
			
		||||
                value INTEGER NOT NULL, \
 | 
			
		||||
                script BLOB NOT NULL, \
 | 
			
		||||
                PRIMARY KEY (txid, vout) \
 | 
			
		||||
                ) STRICT",
 | 
			
		||||
                Self::TXOUTS_TABLE_NAME,
 | 
			
		||||
            ),
 | 
			
		||||
            // anchors
 | 
			
		||||
            &format!(
 | 
			
		||||
                "CREATE TABLE {} ( \
 | 
			
		||||
                txid TEXT NOT NULL REFERENCES {} (txid), \
 | 
			
		||||
                block_height INTEGER NOT NULL, \
 | 
			
		||||
                block_hash TEXT NOT NULL, \
 | 
			
		||||
                anchor BLOB NOT NULL, \
 | 
			
		||||
                PRIMARY KEY (txid, block_height, block_hash) \
 | 
			
		||||
                ) STRICT",
 | 
			
		||||
                Self::ANCHORS_TABLE_NAME,
 | 
			
		||||
                Self::TXS_TABLE_NAME,
 | 
			
		||||
            ),
 | 
			
		||||
        ];
 | 
			
		||||
        migrate_schema(db_tx, Self::SCHEMA_NAME, &[schema_v0])
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Construct a [`TxGraph`] from an sqlite database.
 | 
			
		||||
    pub fn from_sqlite(db_tx: &rusqlite::Transaction) -> rusqlite::Result<Self> {
 | 
			
		||||
        Self::init_sqlite_tables(db_tx)?;
 | 
			
		||||
 | 
			
		||||
        let mut changeset = Self::default();
 | 
			
		||||
 | 
			
		||||
        let mut statement = db_tx.prepare(&format!(
 | 
			
		||||
            "SELECT txid, raw_tx, last_seen FROM {}",
 | 
			
		||||
            Self::TXS_TABLE_NAME,
 | 
			
		||||
        ))?;
 | 
			
		||||
        let row_iter = statement.query_map([], |row| {
 | 
			
		||||
            Ok((
 | 
			
		||||
                row.get::<_, Impl<bitcoin::Txid>>("txid")?,
 | 
			
		||||
                row.get::<_, Option<Impl<bitcoin::Transaction>>>("raw_tx")?,
 | 
			
		||||
                row.get::<_, Option<u64>>("last_seen")?,
 | 
			
		||||
            ))
 | 
			
		||||
        })?;
 | 
			
		||||
        for row in row_iter {
 | 
			
		||||
            let (Impl(txid), tx, last_seen) = row?;
 | 
			
		||||
            if let Some(Impl(tx)) = tx {
 | 
			
		||||
                changeset.txs.insert(Arc::new(tx));
 | 
			
		||||
            }
 | 
			
		||||
            if let Some(last_seen) = last_seen {
 | 
			
		||||
                changeset.last_seen.insert(txid, last_seen);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut statement = db_tx.prepare(&format!(
 | 
			
		||||
            "SELECT txid, vout, value, script FROM {}",
 | 
			
		||||
            Self::TXOUTS_TABLE_NAME,
 | 
			
		||||
        ))?;
 | 
			
		||||
        let row_iter = statement.query_map([], |row| {
 | 
			
		||||
            Ok((
 | 
			
		||||
                row.get::<_, Impl<bitcoin::Txid>>("txid")?,
 | 
			
		||||
                row.get::<_, u32>("vout")?,
 | 
			
		||||
                row.get::<_, Impl<bitcoin::Amount>>("value")?,
 | 
			
		||||
                row.get::<_, Impl<bitcoin::ScriptBuf>>("script")?,
 | 
			
		||||
            ))
 | 
			
		||||
        })?;
 | 
			
		||||
        for row in row_iter {
 | 
			
		||||
            let (Impl(txid), vout, Impl(value), Impl(script_pubkey)) = row?;
 | 
			
		||||
            changeset.txouts.insert(
 | 
			
		||||
                bitcoin::OutPoint { txid, vout },
 | 
			
		||||
                bitcoin::TxOut {
 | 
			
		||||
                    value,
 | 
			
		||||
                    script_pubkey,
 | 
			
		||||
                },
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut statement = db_tx.prepare(&format!(
 | 
			
		||||
            "SELECT json(anchor), txid FROM {}",
 | 
			
		||||
            Self::ANCHORS_TABLE_NAME,
 | 
			
		||||
        ))?;
 | 
			
		||||
        let row_iter = statement.query_map([], |row| {
 | 
			
		||||
            Ok((
 | 
			
		||||
                row.get::<_, Impl<A>>("json(anchor)")?,
 | 
			
		||||
                row.get::<_, Impl<bitcoin::Txid>>("txid")?,
 | 
			
		||||
            ))
 | 
			
		||||
        })?;
 | 
			
		||||
        for row in row_iter {
 | 
			
		||||
            let (Impl(anchor), Impl(txid)) = row?;
 | 
			
		||||
            changeset.anchors.insert((anchor, txid));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(changeset)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Persist `changeset` to the sqlite database.
 | 
			
		||||
    pub fn persist_to_sqlite(&self, db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
 | 
			
		||||
        Self::init_sqlite_tables(db_tx)?;
 | 
			
		||||
 | 
			
		||||
        let mut statement = db_tx.prepare_cached(&format!(
 | 
			
		||||
            "INSERT INTO {}(txid, raw_tx) VALUES(:txid, :raw_tx) ON CONFLICT(txid) DO UPDATE SET raw_tx=:raw_tx",
 | 
			
		||||
            Self::TXS_TABLE_NAME,
 | 
			
		||||
        ))?;
 | 
			
		||||
        for tx in &self.txs {
 | 
			
		||||
            statement.execute(named_params! {
 | 
			
		||||
                ":txid": Impl(tx.compute_txid()),
 | 
			
		||||
                ":raw_tx": Impl(tx.as_ref().clone()),
 | 
			
		||||
            })?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut statement = db_tx
 | 
			
		||||
            .prepare_cached(&format!(
 | 
			
		||||
                "INSERT INTO {}(txid, last_seen) VALUES(:txid, :last_seen) ON CONFLICT(txid) DO UPDATE SET last_seen=:last_seen",
 | 
			
		||||
                Self::TXS_TABLE_NAME,
 | 
			
		||||
            ))?;
 | 
			
		||||
        for (&txid, &last_seen) in &self.last_seen {
 | 
			
		||||
            statement.execute(named_params! {
 | 
			
		||||
                ":txid": Impl(txid),
 | 
			
		||||
                ":last_seen": Some(last_seen),
 | 
			
		||||
            })?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut statement = db_tx.prepare_cached(&format!(
 | 
			
		||||
            "REPLACE INTO {}(txid, vout, value, script) VALUES(:txid, :vout, :value, :script)",
 | 
			
		||||
            Self::TXOUTS_TABLE_NAME,
 | 
			
		||||
        ))?;
 | 
			
		||||
        for (op, txo) in &self.txouts {
 | 
			
		||||
            statement.execute(named_params! {
 | 
			
		||||
                ":txid": Impl(op.txid),
 | 
			
		||||
                ":vout": op.vout,
 | 
			
		||||
                ":value": Impl(txo.value),
 | 
			
		||||
                ":script": Impl(txo.script_pubkey.clone()),
 | 
			
		||||
            })?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut statement = db_tx.prepare_cached(&format!(
 | 
			
		||||
            "REPLACE INTO {}(txid, block_height, block_hash, anchor) VALUES(:txid, :block_height, :block_hash, jsonb(:anchor))",
 | 
			
		||||
            Self::ANCHORS_TABLE_NAME,
 | 
			
		||||
        ))?;
 | 
			
		||||
        for (anchor, txid) in &self.anchors {
 | 
			
		||||
            let anchor_block = anchor.anchor_block();
 | 
			
		||||
            statement.execute(named_params! {
 | 
			
		||||
                ":txid": Impl(*txid),
 | 
			
		||||
                ":block_height": anchor_block.height,
 | 
			
		||||
                ":block_hash": Impl(anchor_block.hash),
 | 
			
		||||
                ":anchor": Impl(anchor.clone()),
 | 
			
		||||
            })?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl local_chain::ChangeSet {
 | 
			
		||||
    /// Schema name for the changeset.
 | 
			
		||||
    pub const SCHEMA_NAME: &'static str = "bdk_localchain";
 | 
			
		||||
    /// Name of sqlite table that stores blocks of [`LocalChain`](local_chain::LocalChain).
 | 
			
		||||
    pub const BLOCKS_TABLE_NAME: &'static str = "bdk_blocks";
 | 
			
		||||
 | 
			
		||||
    /// Initialize sqlite tables for persisting [`local_chain::LocalChain`].
 | 
			
		||||
    fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
 | 
			
		||||
        let schema_v0: &[&str] = &[
 | 
			
		||||
            // blocks
 | 
			
		||||
            &format!(
 | 
			
		||||
                "CREATE TABLE {} ( \
 | 
			
		||||
                block_height INTEGER PRIMARY KEY NOT NULL, \
 | 
			
		||||
                block_hash TEXT NOT NULL \
 | 
			
		||||
                ) STRICT",
 | 
			
		||||
                Self::BLOCKS_TABLE_NAME,
 | 
			
		||||
            ),
 | 
			
		||||
        ];
 | 
			
		||||
        migrate_schema(db_tx, Self::SCHEMA_NAME, &[schema_v0])
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Construct a [`LocalChain`](local_chain::LocalChain) from sqlite database.
 | 
			
		||||
    pub fn from_sqlite(db_tx: &rusqlite::Transaction) -> rusqlite::Result<Self> {
 | 
			
		||||
        Self::init_sqlite_tables(db_tx)?;
 | 
			
		||||
 | 
			
		||||
        let mut changeset = Self::default();
 | 
			
		||||
 | 
			
		||||
        let mut statement = db_tx.prepare(&format!(
 | 
			
		||||
            "SELECT block_height, block_hash FROM {}",
 | 
			
		||||
            Self::BLOCKS_TABLE_NAME,
 | 
			
		||||
        ))?;
 | 
			
		||||
        let row_iter = statement.query_map([], |row| {
 | 
			
		||||
            Ok((
 | 
			
		||||
                row.get::<_, u32>("block_height")?,
 | 
			
		||||
                row.get::<_, Impl<bitcoin::BlockHash>>("block_hash")?,
 | 
			
		||||
            ))
 | 
			
		||||
        })?;
 | 
			
		||||
        for row in row_iter {
 | 
			
		||||
            let (height, Impl(hash)) = row?;
 | 
			
		||||
            changeset.blocks.insert(height, Some(hash));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(changeset)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Persist `changeset` to the sqlite database.
 | 
			
		||||
    pub fn persist_to_sqlite(&self, db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
 | 
			
		||||
        Self::init_sqlite_tables(db_tx)?;
 | 
			
		||||
 | 
			
		||||
        let mut replace_statement = db_tx.prepare_cached(&format!(
 | 
			
		||||
            "REPLACE INTO {}(block_height, block_hash) VALUES(:block_height, :block_hash)",
 | 
			
		||||
            Self::BLOCKS_TABLE_NAME,
 | 
			
		||||
        ))?;
 | 
			
		||||
        let mut delete_statement = db_tx.prepare_cached(&format!(
 | 
			
		||||
            "DELETE FROM {} WHERE block_height=:block_height",
 | 
			
		||||
            Self::BLOCKS_TABLE_NAME,
 | 
			
		||||
        ))?;
 | 
			
		||||
        for (&height, &hash) in &self.blocks {
 | 
			
		||||
            match hash {
 | 
			
		||||
                Some(hash) => replace_statement.execute(named_params! {
 | 
			
		||||
                    ":block_height": height,
 | 
			
		||||
                    ":block_hash": Impl(hash),
 | 
			
		||||
                })?,
 | 
			
		||||
                None => delete_statement.execute(named_params! {
 | 
			
		||||
                    ":block_height": height,
 | 
			
		||||
                })?,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "miniscript")]
 | 
			
		||||
impl keychain_txout::ChangeSet {
 | 
			
		||||
    /// Schema name for the changeset.
 | 
			
		||||
    pub const SCHEMA_NAME: &'static str = "bdk_keychaintxout";
 | 
			
		||||
    /// Name for table that stores last revealed indices per descriptor id.
 | 
			
		||||
    pub const LAST_REVEALED_TABLE_NAME: &'static str = "bdk_descriptor_last_revealed";
 | 
			
		||||
 | 
			
		||||
    /// Initialize sqlite tables for persisting
 | 
			
		||||
    /// [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex).
 | 
			
		||||
    fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
 | 
			
		||||
        let schema_v0: &[&str] = &[
 | 
			
		||||
            // last revealed
 | 
			
		||||
            &format!(
 | 
			
		||||
                "CREATE TABLE {} ( \
 | 
			
		||||
                descriptor_id TEXT PRIMARY KEY NOT NULL, \
 | 
			
		||||
                last_revealed INTEGER NOT NULL \
 | 
			
		||||
                ) STRICT",
 | 
			
		||||
                Self::LAST_REVEALED_TABLE_NAME,
 | 
			
		||||
            ),
 | 
			
		||||
        ];
 | 
			
		||||
        migrate_schema(db_tx, Self::SCHEMA_NAME, &[schema_v0])
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Construct [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex) from sqlite database
 | 
			
		||||
    /// and given parameters.
 | 
			
		||||
    pub fn from_sqlite(db_tx: &rusqlite::Transaction) -> rusqlite::Result<Self> {
 | 
			
		||||
        Self::init_sqlite_tables(db_tx)?;
 | 
			
		||||
 | 
			
		||||
        let mut changeset = Self::default();
 | 
			
		||||
 | 
			
		||||
        let mut statement = db_tx.prepare(&format!(
 | 
			
		||||
            "SELECT descriptor_id, last_revealed FROM {}",
 | 
			
		||||
            Self::LAST_REVEALED_TABLE_NAME,
 | 
			
		||||
        ))?;
 | 
			
		||||
        let row_iter = statement.query_map([], |row| {
 | 
			
		||||
            Ok((
 | 
			
		||||
                row.get::<_, Impl<DescriptorId>>("descriptor_id")?,
 | 
			
		||||
                row.get::<_, u32>("last_revealed")?,
 | 
			
		||||
            ))
 | 
			
		||||
        })?;
 | 
			
		||||
        for row in row_iter {
 | 
			
		||||
            let (Impl(descriptor_id), last_revealed) = row?;
 | 
			
		||||
            changeset.last_revealed.insert(descriptor_id, last_revealed);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(changeset)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Persist `changeset` to the sqlite database.
 | 
			
		||||
    pub fn persist_to_sqlite(&self, db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
 | 
			
		||||
        Self::init_sqlite_tables(db_tx)?;
 | 
			
		||||
 | 
			
		||||
        let mut statement = db_tx.prepare_cached(&format!(
 | 
			
		||||
            "REPLACE INTO {}(descriptor_id, last_revealed) VALUES(:descriptor_id, :last_revealed)",
 | 
			
		||||
            Self::LAST_REVEALED_TABLE_NAME,
 | 
			
		||||
        ))?;
 | 
			
		||||
        for (&descriptor_id, &last_revealed) in &self.last_revealed {
 | 
			
		||||
            statement.execute(named_params! {
 | 
			
		||||
                ":descriptor_id": Impl(descriptor_id),
 | 
			
		||||
                ":last_revealed": last_revealed,
 | 
			
		||||
            })?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -3,7 +3,7 @@
 | 
			
		||||
use rand::distributions::{Alphanumeric, DistString};
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
 | 
			
		||||
use bdk_chain::{tx_graph::TxGraph, Anchor, SpkTxOutIndex};
 | 
			
		||||
use bdk_chain::{spk_txout::SpkTxOutIndex, tx_graph::TxGraph, Anchor};
 | 
			
		||||
use bitcoin::{
 | 
			
		||||
    locktime::absolute::LockTime, secp256k1::Secp256k1, transaction, Amount, OutPoint, ScriptBuf,
 | 
			
		||||
    Sequence, Transaction, TxIn, TxOut, Txid, Witness,
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ use bdk_chain::{
 | 
			
		||||
    indexed_tx_graph::{self, IndexedTxGraph},
 | 
			
		||||
    indexer::keychain_txout::KeychainTxOutIndex,
 | 
			
		||||
    local_chain::LocalChain,
 | 
			
		||||
    tx_graph, Balance, ChainPosition, ConfirmationBlockTime, DescriptorExt, Merge,
 | 
			
		||||
    tx_graph, Balance, ChainPosition, ConfirmationBlockTime, DescriptorExt,
 | 
			
		||||
};
 | 
			
		||||
use bitcoin::{
 | 
			
		||||
    secp256k1::Secp256k1, Amount, OutPoint, Script, ScriptBuf, Transaction, TxIn, TxOut,
 | 
			
		||||
@ -73,13 +73,12 @@ fn insert_relevant_txs() {
 | 
			
		||||
    let txs = [tx_c, tx_b, tx_a];
 | 
			
		||||
 | 
			
		||||
    let changeset = indexed_tx_graph::ChangeSet {
 | 
			
		||||
        graph: tx_graph::ChangeSet {
 | 
			
		||||
        tx_graph: tx_graph::ChangeSet {
 | 
			
		||||
            txs: txs.iter().cloned().map(Arc::new).collect(),
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
        },
 | 
			
		||||
        indexer: keychain_txout::ChangeSet {
 | 
			
		||||
            last_revealed: [(descriptor.descriptor_id(), 9_u32)].into(),
 | 
			
		||||
            keychains_added: [].into(),
 | 
			
		||||
        },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@ -90,10 +89,9 @@ fn insert_relevant_txs() {
 | 
			
		||||
 | 
			
		||||
    // The initial changeset will also contain info about the keychain we added
 | 
			
		||||
    let initial_changeset = indexed_tx_graph::ChangeSet {
 | 
			
		||||
        graph: changeset.graph,
 | 
			
		||||
        tx_graph: changeset.tx_graph,
 | 
			
		||||
        indexer: keychain_txout::ChangeSet {
 | 
			
		||||
            last_revealed: changeset.indexer.last_revealed,
 | 
			
		||||
            keychains_added: [((), descriptor)].into(),
 | 
			
		||||
        },
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@ -144,16 +142,14 @@ fn test_list_owned_txouts() {
 | 
			
		||||
        KeychainTxOutIndex::new(10),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    assert!(!graph
 | 
			
		||||
    assert!(graph
 | 
			
		||||
        .index
 | 
			
		||||
        .insert_descriptor("keychain_1".into(), desc_1)
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .is_empty());
 | 
			
		||||
    assert!(!graph
 | 
			
		||||
        .unwrap());
 | 
			
		||||
    assert!(graph
 | 
			
		||||
        .index
 | 
			
		||||
        .insert_descriptor("keychain_2".into(), desc_2)
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .is_empty());
 | 
			
		||||
        .unwrap());
 | 
			
		||||
 | 
			
		||||
    // Get trusted and untrusted addresses
 | 
			
		||||
 | 
			
		||||
@ -532,8 +528,8 @@ fn test_list_owned_txouts() {
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_get_chain_position() {
 | 
			
		||||
    use bdk_chain::local_chain::CheckPoint;
 | 
			
		||||
    use bdk_chain::spk_txout::SpkTxOutIndex;
 | 
			
		||||
    use bdk_chain::BlockId;
 | 
			
		||||
    use bdk_chain::SpkTxOutIndex;
 | 
			
		||||
 | 
			
		||||
    struct TestCase<A> {
 | 
			
		||||
        name: &'static str,
 | 
			
		||||
 | 
			
		||||
@ -81,11 +81,9 @@ fn merge_changesets_check_last_revealed() {
 | 
			
		||||
    lhs_di.insert(descriptor_ids[3], 4); // key doesn't exist in lhs
 | 
			
		||||
 | 
			
		||||
    let mut lhs = ChangeSet {
 | 
			
		||||
        keychains_added: BTreeMap::<(), _>::new(),
 | 
			
		||||
        last_revealed: lhs_di,
 | 
			
		||||
    };
 | 
			
		||||
    let rhs = ChangeSet {
 | 
			
		||||
        keychains_added: BTreeMap::<(), _>::new(),
 | 
			
		||||
        last_revealed: rhs_di,
 | 
			
		||||
    };
 | 
			
		||||
    lhs.merge(rhs);
 | 
			
		||||
@ -100,49 +98,6 @@ fn merge_changesets_check_last_revealed() {
 | 
			
		||||
    assert_eq!(lhs.last_revealed.get(&descriptor_ids[3]), Some(&4));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn when_apply_contradictory_changesets_they_are_ignored() {
 | 
			
		||||
    let external_descriptor = parse_descriptor(DESCRIPTORS[0]);
 | 
			
		||||
    let internal_descriptor = parse_descriptor(DESCRIPTORS[1]);
 | 
			
		||||
    let mut txout_index =
 | 
			
		||||
        init_txout_index(external_descriptor.clone(), internal_descriptor.clone(), 0);
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
        txout_index.keychains().collect::<Vec<_>>(),
 | 
			
		||||
        vec![
 | 
			
		||||
            (&TestKeychain::External, &external_descriptor),
 | 
			
		||||
            (&TestKeychain::Internal, &internal_descriptor)
 | 
			
		||||
        ]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    let changeset = ChangeSet {
 | 
			
		||||
        keychains_added: [(TestKeychain::External, internal_descriptor.clone())].into(),
 | 
			
		||||
        last_revealed: [].into(),
 | 
			
		||||
    };
 | 
			
		||||
    txout_index.apply_changeset(changeset);
 | 
			
		||||
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
        txout_index.keychains().collect::<Vec<_>>(),
 | 
			
		||||
        vec![
 | 
			
		||||
            (&TestKeychain::External, &external_descriptor),
 | 
			
		||||
            (&TestKeychain::Internal, &internal_descriptor)
 | 
			
		||||
        ]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    let changeset = ChangeSet {
 | 
			
		||||
        keychains_added: [(TestKeychain::Internal, external_descriptor.clone())].into(),
 | 
			
		||||
        last_revealed: [].into(),
 | 
			
		||||
    };
 | 
			
		||||
    txout_index.apply_changeset(changeset);
 | 
			
		||||
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
        txout_index.keychains().collect::<Vec<_>>(),
 | 
			
		||||
        vec![
 | 
			
		||||
            (&TestKeychain::External, &external_descriptor),
 | 
			
		||||
            (&TestKeychain::Internal, &internal_descriptor)
 | 
			
		||||
        ]
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_set_all_derivation_indices() {
 | 
			
		||||
    let external_descriptor = parse_descriptor(DESCRIPTORS[0]);
 | 
			
		||||
@ -159,7 +114,6 @@ fn test_set_all_derivation_indices() {
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
        txout_index.reveal_to_target_multi(&derive_to),
 | 
			
		||||
        ChangeSet {
 | 
			
		||||
            keychains_added: BTreeMap::new(),
 | 
			
		||||
            last_revealed: last_revealed.clone()
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
@ -633,46 +587,29 @@ fn lookahead_to_target() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn insert_descriptor_no_change() {
 | 
			
		||||
    let secp = Secp256k1::signing_only();
 | 
			
		||||
    let (desc, _) =
 | 
			
		||||
        Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, DESCRIPTORS[0]).unwrap();
 | 
			
		||||
    let mut txout_index = KeychainTxOutIndex::<()>::default();
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
        txout_index.insert_descriptor((), desc.clone()),
 | 
			
		||||
        Ok(ChangeSet {
 | 
			
		||||
            keychains_added: [((), desc.clone())].into(),
 | 
			
		||||
            last_revealed: Default::default()
 | 
			
		||||
        }),
 | 
			
		||||
    );
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
        txout_index.insert_descriptor((), desc.clone()),
 | 
			
		||||
        Ok(ChangeSet::default()),
 | 
			
		||||
        "inserting the same descriptor for keychain should return an empty changeset",
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
#[cfg(not(debug_assertions))]
 | 
			
		||||
fn applying_changesets_one_by_one_vs_aggregate_must_have_same_result() {
 | 
			
		||||
    let desc = parse_descriptor(DESCRIPTORS[0]);
 | 
			
		||||
    let changesets: &[ChangeSet<TestKeychain>] = &[
 | 
			
		||||
    let changesets: &[ChangeSet] = &[
 | 
			
		||||
        ChangeSet {
 | 
			
		||||
            keychains_added: [(TestKeychain::Internal, desc.clone())].into(),
 | 
			
		||||
            last_revealed: [].into(),
 | 
			
		||||
            last_revealed: [(desc.descriptor_id(), 10)].into(),
 | 
			
		||||
        },
 | 
			
		||||
        ChangeSet {
 | 
			
		||||
            keychains_added: [(TestKeychain::External, desc.clone())].into(),
 | 
			
		||||
            last_revealed: [(desc.descriptor_id(), 12)].into(),
 | 
			
		||||
        },
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    let mut indexer_a = KeychainTxOutIndex::<TestKeychain>::new(0);
 | 
			
		||||
    indexer_a
 | 
			
		||||
        .insert_descriptor(TestKeychain::External, desc.clone())
 | 
			
		||||
        .expect("must insert keychain");
 | 
			
		||||
    for changeset in changesets {
 | 
			
		||||
        indexer_a.apply_changeset(changeset.clone());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let mut indexer_b = KeychainTxOutIndex::<TestKeychain>::new(0);
 | 
			
		||||
    indexer_b
 | 
			
		||||
        .insert_descriptor(TestKeychain::External, desc.clone())
 | 
			
		||||
        .expect("must insert keychain");
 | 
			
		||||
    let aggregate_changesets = changesets
 | 
			
		||||
        .iter()
 | 
			
		||||
        .cloned()
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
use bdk_chain::{Indexer, SpkTxOutIndex};
 | 
			
		||||
use bdk_chain::{spk_txout::SpkTxOutIndex, Indexer};
 | 
			
		||||
use bitcoin::{
 | 
			
		||||
    absolute, transaction, Amount, OutPoint, ScriptBuf, SignedAmount, Transaction, TxIn, TxOut,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,8 @@ use bdk_chain::{
 | 
			
		||||
    bitcoin::{hashes::Hash, Address, Amount, ScriptBuf, Txid, WScriptHash},
 | 
			
		||||
    local_chain::LocalChain,
 | 
			
		||||
    spk_client::{FullScanRequest, SyncRequest},
 | 
			
		||||
    Balance, ConfirmationBlockTime, IndexedTxGraph, SpkTxOutIndex,
 | 
			
		||||
    spk_txout::SpkTxOutIndex,
 | 
			
		||||
    Balance, ConfirmationBlockTime, IndexedTxGraph,
 | 
			
		||||
};
 | 
			
		||||
use bdk_electrum::BdkElectrumClient;
 | 
			
		||||
use bdk_testenv::{anyhow, bitcoincore_rpc::RpcApi, TestEnv};
 | 
			
		||||
 | 
			
		||||
@ -4,11 +4,13 @@
 | 
			
		||||
//! used with hardware wallets.
 | 
			
		||||
//! ```no_run
 | 
			
		||||
//! # use bdk_wallet::bitcoin::Network;
 | 
			
		||||
//! # use bdk_wallet::descriptor::Descriptor;
 | 
			
		||||
//! # use bdk_wallet::signer::SignerOrdering;
 | 
			
		||||
//! # use bdk_hwi::HWISigner;
 | 
			
		||||
//! # use bdk_wallet::{KeychainKind, SignOptions, Wallet};
 | 
			
		||||
//! # use hwi::HWIClient;
 | 
			
		||||
//! # use std::sync::Arc;
 | 
			
		||||
//! # use std::str::FromStr;
 | 
			
		||||
//! #
 | 
			
		||||
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
 | 
			
		||||
//! let mut devices = HWIClient::enumerate()?;
 | 
			
		||||
@ -18,11 +20,7 @@
 | 
			
		||||
//! let first_device = devices.remove(0)?;
 | 
			
		||||
//! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?;
 | 
			
		||||
//!
 | 
			
		||||
//! # let mut wallet = Wallet::new(
 | 
			
		||||
//! #     "",
 | 
			
		||||
//! #     "",
 | 
			
		||||
//! #     Network::Testnet,
 | 
			
		||||
//! # )?;
 | 
			
		||||
//! # let mut wallet = Wallet::create("", "").network(Network::Testnet).create_wallet_no_persist()?;
 | 
			
		||||
//! #
 | 
			
		||||
//! // Adding the hardware signer to the BDK wallet
 | 
			
		||||
//! wallet.add_signer(
 | 
			
		||||
@ -35,7 +33,7 @@
 | 
			
		||||
//! # }
 | 
			
		||||
//! ```
 | 
			
		||||
//!
 | 
			
		||||
//! [`TransactionSigner`]: bdk_wallet::wallet::signer::TransactionSigner
 | 
			
		||||
//! [`TransactionSigner`]: bdk_wallet::signer::TransactionSigner
 | 
			
		||||
 | 
			
		||||
mod signer;
 | 
			
		||||
pub use signer::*;
 | 
			
		||||
 | 
			
		||||
@ -83,7 +83,7 @@ impl TransactionSigner for HWISigner {
 | 
			
		||||
//             Arc::new(custom_signer),
 | 
			
		||||
//         );
 | 
			
		||||
//
 | 
			
		||||
//         let addr = wallet.get_address(bdk_wallet::wallet::AddressIndex::LastUnused);
 | 
			
		||||
//         let addr = wallet.get_address(bdk_wallet::AddressIndex::LastUnused);
 | 
			
		||||
//         let mut builder = wallet.build_tx();
 | 
			
		||||
//         builder.drain_to(addr.script_pubkey()).drain_wallet();
 | 
			
		||||
//         let (mut psbt, _) = builder.finish().unwrap();
 | 
			
		||||
 | 
			
		||||
@ -1,17 +0,0 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "bdk_sqlite"
 | 
			
		||||
version = "0.2.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
license = "MIT OR Apache-2.0"
 | 
			
		||||
repository = "https://github.com/bitcoindevkit/bdk"
 | 
			
		||||
documentation = "https://docs.rs/bdk_sqlite"
 | 
			
		||||
description = "A simple SQLite relational database client for persisting bdk_chain data."
 | 
			
		||||
keywords = ["bitcoin", "persist", "persistence", "bdk", "sqlite"]
 | 
			
		||||
authors = ["Bitcoin Dev Kit Developers"]
 | 
			
		||||
readme = "README.md"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
bdk_chain = { path = "../chain", version = "0.16.0", features = ["serde", "miniscript"] }
 | 
			
		||||
rusqlite = { version = "0.31.0", features = ["bundled"] }
 | 
			
		||||
serde = { version = "1", features = ["derive"] }
 | 
			
		||||
serde_json = "1"
 | 
			
		||||
@ -1,8 +0,0 @@
 | 
			
		||||
# BDK SQLite
 | 
			
		||||
 | 
			
		||||
This is a simple [SQLite] relational database client for persisting [`bdk_chain`] changesets.
 | 
			
		||||
 | 
			
		||||
The main structure is `Store` which persists `CombinedChangeSet` data into a SQLite database file.
 | 
			
		||||
 | 
			
		||||
[`bdk_chain`]:https://docs.rs/bdk_chain/latest/bdk_chain/
 | 
			
		||||
[SQLite]: https://www.sqlite.org/index.html
 | 
			
		||||
@ -1,69 +0,0 @@
 | 
			
		||||
-- schema version control
 | 
			
		||||
CREATE TABLE version
 | 
			
		||||
(
 | 
			
		||||
    version INTEGER
 | 
			
		||||
) STRICT;
 | 
			
		||||
INSERT INTO version
 | 
			
		||||
VALUES (1);
 | 
			
		||||
 | 
			
		||||
-- network is the valid network for all other table data
 | 
			
		||||
CREATE TABLE network
 | 
			
		||||
(
 | 
			
		||||
    name TEXT UNIQUE NOT NULL
 | 
			
		||||
) STRICT;
 | 
			
		||||
 | 
			
		||||
-- keychain is the json serialized keychain structure as JSONB,
 | 
			
		||||
-- descriptor is the complete descriptor string,
 | 
			
		||||
-- descriptor_id is a sha256::Hash id of the descriptor string w/o the checksum,
 | 
			
		||||
-- last revealed index is a u32
 | 
			
		||||
CREATE TABLE keychain
 | 
			
		||||
(
 | 
			
		||||
    keychain      BLOB PRIMARY KEY NOT NULL,
 | 
			
		||||
    descriptor    TEXT             NOT NULL,
 | 
			
		||||
    descriptor_id BLOB             NOT NULL,
 | 
			
		||||
    last_revealed INTEGER
 | 
			
		||||
) STRICT;
 | 
			
		||||
 | 
			
		||||
-- hash is block hash hex string,
 | 
			
		||||
-- block height is a u32,
 | 
			
		||||
CREATE TABLE block
 | 
			
		||||
(
 | 
			
		||||
    hash   TEXT PRIMARY KEY NOT NULL,
 | 
			
		||||
    height INTEGER          NOT NULL
 | 
			
		||||
) STRICT;
 | 
			
		||||
 | 
			
		||||
-- txid is transaction hash hex string (reversed)
 | 
			
		||||
-- whole_tx is a consensus encoded transaction,
 | 
			
		||||
-- last seen is a u64 unix epoch seconds
 | 
			
		||||
CREATE TABLE tx
 | 
			
		||||
(
 | 
			
		||||
    txid      TEXT PRIMARY KEY NOT NULL,
 | 
			
		||||
    whole_tx  BLOB,
 | 
			
		||||
    last_seen INTEGER
 | 
			
		||||
) STRICT;
 | 
			
		||||
 | 
			
		||||
-- Outpoint txid hash hex string (reversed)
 | 
			
		||||
-- Outpoint vout
 | 
			
		||||
-- TxOut value as SATs
 | 
			
		||||
-- TxOut script consensus encoded
 | 
			
		||||
CREATE TABLE txout
 | 
			
		||||
(
 | 
			
		||||
    txid   TEXT    NOT NULL,
 | 
			
		||||
    vout   INTEGER NOT NULL,
 | 
			
		||||
    value  INTEGER NOT NULL,
 | 
			
		||||
    script BLOB    NOT NULL,
 | 
			
		||||
    PRIMARY KEY (txid, vout)
 | 
			
		||||
) STRICT;
 | 
			
		||||
 | 
			
		||||
-- join table between anchor and tx
 | 
			
		||||
-- block hash hex string
 | 
			
		||||
-- anchor is a json serialized Anchor structure as JSONB,
 | 
			
		||||
-- txid is transaction hash hex string (reversed)
 | 
			
		||||
CREATE TABLE anchor_tx
 | 
			
		||||
(
 | 
			
		||||
    block_hash          TEXT NOT NULL,
 | 
			
		||||
    anchor              BLOB NOT NULL,
 | 
			
		||||
    txid                TEXT NOT NULL REFERENCES tx (txid),
 | 
			
		||||
    UNIQUE (anchor, txid),
 | 
			
		||||
    FOREIGN KEY (block_hash) REFERENCES block(hash)
 | 
			
		||||
) STRICT;
 | 
			
		||||
@ -1,34 +0,0 @@
 | 
			
		||||
#![doc = include_str!("../README.md")]
 | 
			
		||||
// only enables the `doc_cfg` feature when the `docsrs` configuration attribute is defined
 | 
			
		||||
#![cfg_attr(docsrs, feature(doc_cfg))]
 | 
			
		||||
 | 
			
		||||
mod schema;
 | 
			
		||||
mod store;
 | 
			
		||||
 | 
			
		||||
use bdk_chain::bitcoin::Network;
 | 
			
		||||
pub use rusqlite;
 | 
			
		||||
pub use store::Store;
 | 
			
		||||
 | 
			
		||||
/// Error that occurs while reading or writing change sets with the SQLite database.
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub enum Error {
 | 
			
		||||
    /// Invalid network, cannot change the one already stored in the database.
 | 
			
		||||
    Network { expected: Network, given: Network },
 | 
			
		||||
    /// SQLite error.
 | 
			
		||||
    Sqlite(rusqlite::Error),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl core::fmt::Display for Error {
 | 
			
		||||
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            Self::Network { expected, given } => write!(
 | 
			
		||||
                f,
 | 
			
		||||
                "network error trying to read or write change set, expected {}, given {}",
 | 
			
		||||
                expected, given
 | 
			
		||||
            ),
 | 
			
		||||
            Self::Sqlite(e) => write!(f, "sqlite error reading or writing changeset: {}", e),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl std::error::Error for Error {}
 | 
			
		||||
@ -1,96 +0,0 @@
 | 
			
		||||
use crate::Store;
 | 
			
		||||
use rusqlite::{named_params, Connection, Error};
 | 
			
		||||
 | 
			
		||||
const SCHEMA_0: &str = include_str!("../schema/schema_0.sql");
 | 
			
		||||
const MIGRATIONS: &[&str] = &[SCHEMA_0];
 | 
			
		||||
 | 
			
		||||
/// Schema migration related functions.
 | 
			
		||||
impl<K, A> Store<K, A> {
 | 
			
		||||
    /// Migrate sqlite db schema to latest version.
 | 
			
		||||
    pub(crate) fn migrate(conn: &mut Connection) -> Result<(), Error> {
 | 
			
		||||
        let stmts = &MIGRATIONS
 | 
			
		||||
            .iter()
 | 
			
		||||
            .flat_map(|stmt| {
 | 
			
		||||
                // remove comment lines
 | 
			
		||||
                let s = stmt
 | 
			
		||||
                    .split('\n')
 | 
			
		||||
                    .filter(|l| !l.starts_with("--") && !l.is_empty())
 | 
			
		||||
                    .collect::<Vec<_>>()
 | 
			
		||||
                    .join(" ");
 | 
			
		||||
                // split into statements
 | 
			
		||||
                s.split(';')
 | 
			
		||||
                    // remove extra spaces
 | 
			
		||||
                    .map(|s| {
 | 
			
		||||
                        s.trim()
 | 
			
		||||
                            .split(' ')
 | 
			
		||||
                            .filter(|s| !s.is_empty())
 | 
			
		||||
                            .collect::<Vec<_>>()
 | 
			
		||||
                            .join(" ")
 | 
			
		||||
                    })
 | 
			
		||||
                    .collect::<Vec<_>>()
 | 
			
		||||
            })
 | 
			
		||||
            // remove empty statements
 | 
			
		||||
            .filter(|s| !s.is_empty())
 | 
			
		||||
            .collect::<Vec<String>>();
 | 
			
		||||
 | 
			
		||||
        let version = Self::get_schema_version(conn)?;
 | 
			
		||||
        let stmts = &stmts[(version as usize)..];
 | 
			
		||||
 | 
			
		||||
        // begin transaction, all migration statements and new schema version commit or rollback
 | 
			
		||||
        let tx = conn.transaction()?;
 | 
			
		||||
 | 
			
		||||
        // execute every statement and return `Some` new schema version
 | 
			
		||||
        // if execution fails, return `Error::Rusqlite`
 | 
			
		||||
        // if no statements executed returns `None`
 | 
			
		||||
        let new_version = stmts
 | 
			
		||||
            .iter()
 | 
			
		||||
            .enumerate()
 | 
			
		||||
            .map(|version_stmt| {
 | 
			
		||||
                tx.execute(version_stmt.1.as_str(), [])
 | 
			
		||||
                    // map result value to next migration version
 | 
			
		||||
                    .map(|_| version_stmt.0 as i32 + version + 1)
 | 
			
		||||
            })
 | 
			
		||||
            .last()
 | 
			
		||||
            .transpose()?;
 | 
			
		||||
 | 
			
		||||
        // if `Some` new statement version, set new schema version
 | 
			
		||||
        if let Some(version) = new_version {
 | 
			
		||||
            Self::set_schema_version(&tx, version)?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // commit transaction
 | 
			
		||||
        tx.commit()?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn get_schema_version(conn: &Connection) -> rusqlite::Result<i32> {
 | 
			
		||||
        let statement = conn.prepare_cached("SELECT version FROM version");
 | 
			
		||||
        match statement {
 | 
			
		||||
            Err(Error::SqliteFailure(e, Some(msg))) => {
 | 
			
		||||
                if msg == "no such table: version" {
 | 
			
		||||
                    Ok(0)
 | 
			
		||||
                } else {
 | 
			
		||||
                    Err(Error::SqliteFailure(e, Some(msg)))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Ok(mut stmt) => {
 | 
			
		||||
                let mut rows = stmt.query([])?;
 | 
			
		||||
                match rows.next()? {
 | 
			
		||||
                    Some(row) => {
 | 
			
		||||
                        let version: i32 = row.get(0)?;
 | 
			
		||||
                        Ok(version)
 | 
			
		||||
                    }
 | 
			
		||||
                    None => Ok(0),
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            _ => Ok(0),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn set_schema_version(conn: &Connection, version: i32) -> rusqlite::Result<usize> {
 | 
			
		||||
        conn.execute(
 | 
			
		||||
            "UPDATE version SET version=:version",
 | 
			
		||||
            named_params! {":version": version},
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,734 +0,0 @@
 | 
			
		||||
use bdk_chain::bitcoin::consensus::{deserialize, serialize};
 | 
			
		||||
use bdk_chain::bitcoin::hashes::Hash;
 | 
			
		||||
use bdk_chain::bitcoin::{Amount, Network, OutPoint, ScriptBuf, Transaction, TxOut};
 | 
			
		||||
use bdk_chain::bitcoin::{BlockHash, Txid};
 | 
			
		||||
use bdk_chain::miniscript::descriptor::{Descriptor, DescriptorPublicKey};
 | 
			
		||||
use rusqlite::{named_params, Connection};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use std::collections::{BTreeMap, BTreeSet};
 | 
			
		||||
use std::fmt::Debug;
 | 
			
		||||
use std::marker::PhantomData;
 | 
			
		||||
use std::str::FromStr;
 | 
			
		||||
use std::sync::{Arc, Mutex};
 | 
			
		||||
 | 
			
		||||
use crate::Error;
 | 
			
		||||
use bdk_chain::CombinedChangeSet;
 | 
			
		||||
use bdk_chain::{
 | 
			
		||||
    indexed_tx_graph, indexer::keychain_txout, local_chain, tx_graph, Anchor, DescriptorExt,
 | 
			
		||||
    DescriptorId, Merge,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Persists data in to a relational schema based [SQLite] database file.
 | 
			
		||||
///
 | 
			
		||||
/// The changesets loaded or stored represent changes to keychain and blockchain data.
 | 
			
		||||
///
 | 
			
		||||
/// [SQLite]: https://www.sqlite.org/index.html
 | 
			
		||||
pub struct Store<K, A> {
 | 
			
		||||
    // A rusqlite connection to the SQLite database. Uses a Mutex for thread safety.
 | 
			
		||||
    conn: Mutex<Connection>,
 | 
			
		||||
    keychain_marker: PhantomData<K>,
 | 
			
		||||
    anchor_marker: PhantomData<A>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<K, A> Debug for Store<K, A> {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        Debug::fmt(&self.conn, f)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<K, A> Store<K, A>
 | 
			
		||||
where
 | 
			
		||||
    K: Ord + for<'de> Deserialize<'de> + Serialize + Send,
 | 
			
		||||
    A: Anchor + for<'de> Deserialize<'de> + Serialize + Send,
 | 
			
		||||
{
 | 
			
		||||
    /// Creates a new store from a [`Connection`].
 | 
			
		||||
    pub fn new(mut conn: Connection) -> Result<Self, rusqlite::Error> {
 | 
			
		||||
        Self::migrate(&mut conn)?;
 | 
			
		||||
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            conn: Mutex::new(conn),
 | 
			
		||||
            keychain_marker: Default::default(),
 | 
			
		||||
            anchor_marker: Default::default(),
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub(crate) fn db_transaction(&mut self) -> Result<rusqlite::Transaction, Error> {
 | 
			
		||||
        let connection = self.conn.get_mut().expect("unlocked connection mutex");
 | 
			
		||||
        connection.transaction().map_err(Error::Sqlite)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Network table related functions.
 | 
			
		||||
impl<K, A> Store<K, A> {
 | 
			
		||||
    /// Insert [`Network`] for which all other tables data is valid.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Error if trying to insert different network value.
 | 
			
		||||
    fn insert_network(
 | 
			
		||||
        current_network: &Option<Network>,
 | 
			
		||||
        db_transaction: &rusqlite::Transaction,
 | 
			
		||||
        network_changeset: &Option<Network>,
 | 
			
		||||
    ) -> Result<(), Error> {
 | 
			
		||||
        if let Some(network) = network_changeset {
 | 
			
		||||
            match current_network {
 | 
			
		||||
                // if no network change do nothing
 | 
			
		||||
                Some(current_network) if current_network == network => Ok(()),
 | 
			
		||||
                // if new network not the same as current, error
 | 
			
		||||
                Some(current_network) => Err(Error::Network {
 | 
			
		||||
                    expected: *current_network,
 | 
			
		||||
                    given: *network,
 | 
			
		||||
                }),
 | 
			
		||||
                // insert network if none exists
 | 
			
		||||
                None => {
 | 
			
		||||
                    let insert_network_stmt = &mut db_transaction
 | 
			
		||||
                        .prepare_cached("INSERT INTO network (name) VALUES (:name)")
 | 
			
		||||
                        .expect("insert network statement");
 | 
			
		||||
                    let name = network.to_string();
 | 
			
		||||
                    insert_network_stmt
 | 
			
		||||
                        .execute(named_params! {":name": name })
 | 
			
		||||
                        .map_err(Error::Sqlite)?;
 | 
			
		||||
                    Ok(())
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            Ok(())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Select the valid [`Network`] for this database, or `None` if not set.
 | 
			
		||||
    fn select_network(db_transaction: &rusqlite::Transaction) -> Result<Option<Network>, Error> {
 | 
			
		||||
        let mut select_network_stmt = db_transaction
 | 
			
		||||
            .prepare_cached("SELECT name FROM network WHERE rowid = 1")
 | 
			
		||||
            .expect("select network statement");
 | 
			
		||||
 | 
			
		||||
        let network = select_network_stmt
 | 
			
		||||
            .query_row([], |row| {
 | 
			
		||||
                let network = row.get_unwrap::<usize, String>(0);
 | 
			
		||||
                let network = Network::from_str(network.as_str()).expect("valid network");
 | 
			
		||||
                Ok(network)
 | 
			
		||||
            })
 | 
			
		||||
            .map_err(Error::Sqlite);
 | 
			
		||||
        match network {
 | 
			
		||||
            Ok(network) => Ok(Some(network)),
 | 
			
		||||
            Err(Error::Sqlite(rusqlite::Error::QueryReturnedNoRows)) => Ok(None),
 | 
			
		||||
            Err(e) => Err(e),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Block table related functions.
 | 
			
		||||
impl<K, A> Store<K, A> {
 | 
			
		||||
    /// Insert or delete local chain blocks.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Error if trying to insert existing block hash.
 | 
			
		||||
    fn insert_or_delete_blocks(
 | 
			
		||||
        db_transaction: &rusqlite::Transaction,
 | 
			
		||||
        chain_changeset: &local_chain::ChangeSet,
 | 
			
		||||
    ) -> Result<(), Error> {
 | 
			
		||||
        for (height, hash) in chain_changeset.iter() {
 | 
			
		||||
            match hash {
 | 
			
		||||
                // add new hash at height
 | 
			
		||||
                Some(hash) => {
 | 
			
		||||
                    let insert_block_stmt = &mut db_transaction
 | 
			
		||||
                        .prepare_cached("INSERT INTO block (hash, height) VALUES (:hash, :height)")
 | 
			
		||||
                        .expect("insert block statement");
 | 
			
		||||
                    let hash = hash.to_string();
 | 
			
		||||
                    insert_block_stmt
 | 
			
		||||
                        .execute(named_params! {":hash": hash, ":height": height })
 | 
			
		||||
                        .map_err(Error::Sqlite)?;
 | 
			
		||||
                }
 | 
			
		||||
                // delete block at height
 | 
			
		||||
                None => {
 | 
			
		||||
                    let delete_block_stmt = &mut db_transaction
 | 
			
		||||
                        .prepare_cached("DELETE FROM block WHERE height IS :height")
 | 
			
		||||
                        .expect("delete block statement");
 | 
			
		||||
                    delete_block_stmt
 | 
			
		||||
                        .execute(named_params! {":height": height })
 | 
			
		||||
                        .map_err(Error::Sqlite)?;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Select all blocks.
 | 
			
		||||
    fn select_blocks(
 | 
			
		||||
        db_transaction: &rusqlite::Transaction,
 | 
			
		||||
    ) -> Result<BTreeMap<u32, Option<BlockHash>>, Error> {
 | 
			
		||||
        let mut select_blocks_stmt = db_transaction
 | 
			
		||||
            .prepare_cached("SELECT height, hash FROM block")
 | 
			
		||||
            .expect("select blocks statement");
 | 
			
		||||
 | 
			
		||||
        let blocks = select_blocks_stmt
 | 
			
		||||
            .query_map([], |row| {
 | 
			
		||||
                let height = row.get_unwrap::<usize, u32>(0);
 | 
			
		||||
                let hash = row.get_unwrap::<usize, String>(1);
 | 
			
		||||
                let hash = Some(BlockHash::from_str(hash.as_str()).expect("block hash"));
 | 
			
		||||
                Ok((height, hash))
 | 
			
		||||
            })
 | 
			
		||||
            .map_err(Error::Sqlite)?;
 | 
			
		||||
        blocks
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .map(|row| row.map_err(Error::Sqlite))
 | 
			
		||||
            .collect()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Keychain table related functions.
 | 
			
		||||
///
 | 
			
		||||
/// The keychain objects are stored as [`JSONB`] data.
 | 
			
		||||
/// [`JSONB`]: https://sqlite.org/json1.html#jsonb
 | 
			
		||||
impl<K, A> Store<K, A>
 | 
			
		||||
where
 | 
			
		||||
    K: Ord + for<'de> Deserialize<'de> + Serialize + Send,
 | 
			
		||||
    A: Anchor + Send,
 | 
			
		||||
{
 | 
			
		||||
    /// Insert keychain with descriptor and last active index.
 | 
			
		||||
    ///
 | 
			
		||||
    /// If keychain exists only update last active index.
 | 
			
		||||
    fn insert_keychains(
 | 
			
		||||
        db_transaction: &rusqlite::Transaction,
 | 
			
		||||
        tx_graph_changeset: &indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<K>>,
 | 
			
		||||
    ) -> Result<(), Error> {
 | 
			
		||||
        let keychain_changeset = &tx_graph_changeset.indexer;
 | 
			
		||||
        for (keychain, descriptor) in keychain_changeset.keychains_added.iter() {
 | 
			
		||||
            let insert_keychain_stmt = &mut db_transaction
 | 
			
		||||
                .prepare_cached("INSERT INTO keychain (keychain, descriptor, descriptor_id) VALUES (jsonb(:keychain), :descriptor, :descriptor_id)")
 | 
			
		||||
                .expect("insert keychain statement");
 | 
			
		||||
            let keychain_json = serde_json::to_string(keychain).expect("keychain json");
 | 
			
		||||
            let descriptor_id = descriptor.descriptor_id().to_byte_array();
 | 
			
		||||
            let descriptor = descriptor.to_string();
 | 
			
		||||
            insert_keychain_stmt.execute(named_params! {":keychain": keychain_json, ":descriptor": descriptor, ":descriptor_id": descriptor_id })
 | 
			
		||||
                .map_err(Error::Sqlite)?;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Update descriptor last revealed index.
 | 
			
		||||
    fn update_last_revealed(
 | 
			
		||||
        db_transaction: &rusqlite::Transaction,
 | 
			
		||||
        tx_graph_changeset: &indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<K>>,
 | 
			
		||||
    ) -> Result<(), Error> {
 | 
			
		||||
        let keychain_changeset = &tx_graph_changeset.indexer;
 | 
			
		||||
        for (descriptor_id, last_revealed) in keychain_changeset.last_revealed.iter() {
 | 
			
		||||
            let update_last_revealed_stmt = &mut db_transaction
 | 
			
		||||
                .prepare_cached(
 | 
			
		||||
                    "UPDATE keychain SET last_revealed = :last_revealed
 | 
			
		||||
                              WHERE descriptor_id = :descriptor_id",
 | 
			
		||||
                )
 | 
			
		||||
                .expect("update last revealed statement");
 | 
			
		||||
            let descriptor_id = descriptor_id.to_byte_array();
 | 
			
		||||
            update_last_revealed_stmt.execute(named_params! {":descriptor_id": descriptor_id, ":last_revealed": * last_revealed })
 | 
			
		||||
                .map_err(Error::Sqlite)?;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Select keychains added.
 | 
			
		||||
    fn select_keychains(
 | 
			
		||||
        db_transaction: &rusqlite::Transaction,
 | 
			
		||||
    ) -> Result<BTreeMap<K, Descriptor<DescriptorPublicKey>>, Error> {
 | 
			
		||||
        let mut select_keychains_added_stmt = db_transaction
 | 
			
		||||
            .prepare_cached("SELECT json(keychain), descriptor FROM keychain")
 | 
			
		||||
            .expect("select keychains statement");
 | 
			
		||||
 | 
			
		||||
        let keychains = select_keychains_added_stmt
 | 
			
		||||
            .query_map([], |row| {
 | 
			
		||||
                let keychain = row.get_unwrap::<usize, String>(0);
 | 
			
		||||
                let keychain = serde_json::from_str::<K>(keychain.as_str()).expect("keychain");
 | 
			
		||||
                let descriptor = row.get_unwrap::<usize, String>(1);
 | 
			
		||||
                let descriptor = Descriptor::from_str(descriptor.as_str()).expect("descriptor");
 | 
			
		||||
                Ok((keychain, descriptor))
 | 
			
		||||
            })
 | 
			
		||||
            .map_err(Error::Sqlite)?;
 | 
			
		||||
        keychains
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .map(|row| row.map_err(Error::Sqlite))
 | 
			
		||||
            .collect()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Select descriptor last revealed indexes.
 | 
			
		||||
    fn select_last_revealed(
 | 
			
		||||
        db_transaction: &rusqlite::Transaction,
 | 
			
		||||
    ) -> Result<BTreeMap<DescriptorId, u32>, Error> {
 | 
			
		||||
        let mut select_last_revealed_stmt = db_transaction
 | 
			
		||||
            .prepare_cached(
 | 
			
		||||
                "SELECT descriptor, last_revealed FROM keychain WHERE last_revealed IS NOT NULL",
 | 
			
		||||
            )
 | 
			
		||||
            .expect("select last revealed statement");
 | 
			
		||||
 | 
			
		||||
        let last_revealed = select_last_revealed_stmt
 | 
			
		||||
            .query_map([], |row| {
 | 
			
		||||
                let descriptor = row.get_unwrap::<usize, String>(0);
 | 
			
		||||
                let descriptor = Descriptor::from_str(descriptor.as_str()).expect("descriptor");
 | 
			
		||||
                let descriptor_id = descriptor.descriptor_id();
 | 
			
		||||
                let last_revealed = row.get_unwrap::<usize, u32>(1);
 | 
			
		||||
                Ok((descriptor_id, last_revealed))
 | 
			
		||||
            })
 | 
			
		||||
            .map_err(Error::Sqlite)?;
 | 
			
		||||
        last_revealed
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .map(|row| row.map_err(Error::Sqlite))
 | 
			
		||||
            .collect()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Tx (transaction) and txout (transaction output) table related functions.
 | 
			
		||||
impl<K, A> Store<K, A> {
 | 
			
		||||
    /// Insert transactions.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Error if trying to insert existing txid.
 | 
			
		||||
    fn insert_txs(
 | 
			
		||||
        db_transaction: &rusqlite::Transaction,
 | 
			
		||||
        tx_graph_changeset: &indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<K>>,
 | 
			
		||||
    ) -> Result<(), Error> {
 | 
			
		||||
        for tx in tx_graph_changeset.graph.txs.iter() {
 | 
			
		||||
            let insert_tx_stmt = &mut db_transaction
 | 
			
		||||
                .prepare_cached("INSERT INTO tx (txid, whole_tx) VALUES (:txid, :whole_tx) ON CONFLICT (txid) DO UPDATE SET whole_tx = :whole_tx WHERE txid = :txid")
 | 
			
		||||
                .expect("insert or update tx whole_tx statement");
 | 
			
		||||
            let txid = tx.compute_txid().to_string();
 | 
			
		||||
            let whole_tx = serialize(&tx);
 | 
			
		||||
            insert_tx_stmt
 | 
			
		||||
                .execute(named_params! {":txid": txid, ":whole_tx": whole_tx })
 | 
			
		||||
                .map_err(Error::Sqlite)?;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Select all transactions.
 | 
			
		||||
    fn select_txs(
 | 
			
		||||
        db_transaction: &rusqlite::Transaction,
 | 
			
		||||
    ) -> Result<BTreeSet<Arc<Transaction>>, Error> {
 | 
			
		||||
        let mut select_tx_stmt = db_transaction
 | 
			
		||||
            .prepare_cached("SELECT whole_tx FROM tx WHERE whole_tx IS NOT NULL")
 | 
			
		||||
            .expect("select tx statement");
 | 
			
		||||
 | 
			
		||||
        let txs = select_tx_stmt
 | 
			
		||||
            .query_map([], |row| {
 | 
			
		||||
                let whole_tx = row.get_unwrap::<usize, Vec<u8>>(0);
 | 
			
		||||
                let whole_tx: Transaction = deserialize(&whole_tx).expect("transaction");
 | 
			
		||||
                Ok(Arc::new(whole_tx))
 | 
			
		||||
            })
 | 
			
		||||
            .map_err(Error::Sqlite)?;
 | 
			
		||||
 | 
			
		||||
        txs.into_iter()
 | 
			
		||||
            .map(|row| row.map_err(Error::Sqlite))
 | 
			
		||||
            .collect()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Select all transactions with last_seen values.
 | 
			
		||||
    fn select_last_seen(
 | 
			
		||||
        db_transaction: &rusqlite::Transaction,
 | 
			
		||||
    ) -> Result<BTreeMap<Txid, u64>, Error> {
 | 
			
		||||
        // load tx last_seen
 | 
			
		||||
        let mut select_last_seen_stmt = db_transaction
 | 
			
		||||
            .prepare_cached("SELECT txid, last_seen FROM tx WHERE last_seen IS NOT NULL")
 | 
			
		||||
            .expect("select tx last seen statement");
 | 
			
		||||
 | 
			
		||||
        let last_seen = select_last_seen_stmt
 | 
			
		||||
            .query_map([], |row| {
 | 
			
		||||
                let txid = row.get_unwrap::<usize, String>(0);
 | 
			
		||||
                let txid = Txid::from_str(&txid).expect("txid");
 | 
			
		||||
                let last_seen = row.get_unwrap::<usize, u64>(1);
 | 
			
		||||
                Ok((txid, last_seen))
 | 
			
		||||
            })
 | 
			
		||||
            .map_err(Error::Sqlite)?;
 | 
			
		||||
        last_seen
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .map(|row| row.map_err(Error::Sqlite))
 | 
			
		||||
            .collect()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Insert txouts.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Error if trying to insert existing outpoint.
 | 
			
		||||
    fn insert_txouts(
 | 
			
		||||
        db_transaction: &rusqlite::Transaction,
 | 
			
		||||
        tx_graph_changeset: &indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<K>>,
 | 
			
		||||
    ) -> Result<(), Error> {
 | 
			
		||||
        for txout in tx_graph_changeset.graph.txouts.iter() {
 | 
			
		||||
            let insert_txout_stmt = &mut db_transaction
 | 
			
		||||
                .prepare_cached("INSERT INTO txout (txid, vout, value, script) VALUES (:txid, :vout, :value, :script)")
 | 
			
		||||
                .expect("insert txout statement");
 | 
			
		||||
            let txid = txout.0.txid.to_string();
 | 
			
		||||
            let vout = txout.0.vout;
 | 
			
		||||
            let value = txout.1.value.to_sat();
 | 
			
		||||
            let script = txout.1.script_pubkey.as_bytes();
 | 
			
		||||
            insert_txout_stmt.execute(named_params! {":txid": txid, ":vout": vout, ":value": value, ":script": script })
 | 
			
		||||
                .map_err(Error::Sqlite)?;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Select all transaction outputs.
 | 
			
		||||
    fn select_txouts(
 | 
			
		||||
        db_transaction: &rusqlite::Transaction,
 | 
			
		||||
    ) -> Result<BTreeMap<OutPoint, TxOut>, Error> {
 | 
			
		||||
        // load tx outs
 | 
			
		||||
        let mut select_txout_stmt = db_transaction
 | 
			
		||||
            .prepare_cached("SELECT txid, vout, value, script FROM txout")
 | 
			
		||||
            .expect("select txout statement");
 | 
			
		||||
 | 
			
		||||
        let txouts = select_txout_stmt
 | 
			
		||||
            .query_map([], |row| {
 | 
			
		||||
                let txid = row.get_unwrap::<usize, String>(0);
 | 
			
		||||
                let txid = Txid::from_str(&txid).expect("txid");
 | 
			
		||||
                let vout = row.get_unwrap::<usize, u32>(1);
 | 
			
		||||
                let outpoint = OutPoint::new(txid, vout);
 | 
			
		||||
                let value = row.get_unwrap::<usize, u64>(2);
 | 
			
		||||
                let script_pubkey = row.get_unwrap::<usize, Vec<u8>>(3);
 | 
			
		||||
                let script_pubkey = ScriptBuf::from_bytes(script_pubkey);
 | 
			
		||||
                let txout = TxOut {
 | 
			
		||||
                    value: Amount::from_sat(value),
 | 
			
		||||
                    script_pubkey,
 | 
			
		||||
                };
 | 
			
		||||
                Ok((outpoint, txout))
 | 
			
		||||
            })
 | 
			
		||||
            .map_err(Error::Sqlite)?;
 | 
			
		||||
        txouts
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .map(|row| row.map_err(Error::Sqlite))
 | 
			
		||||
            .collect()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Update transaction last seen times.
 | 
			
		||||
    fn update_last_seen(
 | 
			
		||||
        db_transaction: &rusqlite::Transaction,
 | 
			
		||||
        tx_graph_changeset: &indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<K>>,
 | 
			
		||||
    ) -> Result<(), Error> {
 | 
			
		||||
        for tx_last_seen in tx_graph_changeset.graph.last_seen.iter() {
 | 
			
		||||
            let insert_or_update_tx_stmt = &mut db_transaction
 | 
			
		||||
                .prepare_cached("INSERT INTO tx (txid, last_seen) VALUES (:txid, :last_seen) ON CONFLICT (txid) DO UPDATE SET last_seen = :last_seen WHERE txid = :txid")
 | 
			
		||||
                .expect("insert or update tx last_seen statement");
 | 
			
		||||
            let txid = tx_last_seen.0.to_string();
 | 
			
		||||
            let last_seen = *tx_last_seen.1;
 | 
			
		||||
            insert_or_update_tx_stmt
 | 
			
		||||
                .execute(named_params! {":txid": txid, ":last_seen": last_seen })
 | 
			
		||||
                .map_err(Error::Sqlite)?;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Anchor table related functions.
 | 
			
		||||
impl<K, A> Store<K, A>
 | 
			
		||||
where
 | 
			
		||||
    K: Ord + for<'de> Deserialize<'de> + Serialize + Send,
 | 
			
		||||
    A: Anchor + for<'de> Deserialize<'de> + Serialize + Send,
 | 
			
		||||
{
 | 
			
		||||
    /// Insert anchors.
 | 
			
		||||
    fn insert_anchors(
 | 
			
		||||
        db_transaction: &rusqlite::Transaction,
 | 
			
		||||
        tx_graph_changeset: &indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<K>>,
 | 
			
		||||
    ) -> Result<(), Error> {
 | 
			
		||||
        // serde_json::to_string
 | 
			
		||||
        for anchor in tx_graph_changeset.graph.anchors.iter() {
 | 
			
		||||
            let insert_anchor_stmt = &mut db_transaction
 | 
			
		||||
                .prepare_cached("INSERT INTO anchor_tx (block_hash, anchor, txid) VALUES (:block_hash, jsonb(:anchor), :txid)")
 | 
			
		||||
                .expect("insert anchor statement");
 | 
			
		||||
            let block_hash = anchor.0.anchor_block().hash.to_string();
 | 
			
		||||
            let anchor_json = serde_json::to_string(&anchor.0).expect("anchor json");
 | 
			
		||||
            let txid = anchor.1.to_string();
 | 
			
		||||
            insert_anchor_stmt.execute(named_params! {":block_hash": block_hash, ":anchor": anchor_json, ":txid": txid })
 | 
			
		||||
                .map_err(Error::Sqlite)?;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Select all anchors.
 | 
			
		||||
    fn select_anchors(
 | 
			
		||||
        db_transaction: &rusqlite::Transaction,
 | 
			
		||||
    ) -> Result<BTreeSet<(A, Txid)>, Error> {
 | 
			
		||||
        // serde_json::from_str
 | 
			
		||||
        let mut select_anchor_stmt = db_transaction
 | 
			
		||||
            .prepare_cached("SELECT block_hash, json(anchor), txid FROM anchor_tx")
 | 
			
		||||
            .expect("select anchor statement");
 | 
			
		||||
        let anchors = select_anchor_stmt
 | 
			
		||||
            .query_map([], |row| {
 | 
			
		||||
                let hash = row.get_unwrap::<usize, String>(0);
 | 
			
		||||
                let hash = BlockHash::from_str(hash.as_str()).expect("block hash");
 | 
			
		||||
                let anchor = row.get_unwrap::<usize, String>(1);
 | 
			
		||||
                let anchor: A = serde_json::from_str(anchor.as_str()).expect("anchor");
 | 
			
		||||
                // double check anchor blob block hash matches
 | 
			
		||||
                assert_eq!(hash, anchor.anchor_block().hash);
 | 
			
		||||
                let txid = row.get_unwrap::<usize, String>(2);
 | 
			
		||||
                let txid = Txid::from_str(&txid).expect("txid");
 | 
			
		||||
                Ok((anchor, txid))
 | 
			
		||||
            })
 | 
			
		||||
            .map_err(Error::Sqlite)?;
 | 
			
		||||
        anchors
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .map(|row| row.map_err(Error::Sqlite))
 | 
			
		||||
            .collect()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Functions to read and write all [`CombinedChangeSet`] data.
 | 
			
		||||
impl<K, A> Store<K, A>
 | 
			
		||||
where
 | 
			
		||||
    K: Ord + for<'de> Deserialize<'de> + Serialize + Send,
 | 
			
		||||
    A: Anchor + for<'de> Deserialize<'de> + Serialize + Send,
 | 
			
		||||
{
 | 
			
		||||
    /// Write the given `changeset` atomically.
 | 
			
		||||
    pub fn write(&mut self, changeset: &CombinedChangeSet<K, A>) -> Result<(), Error> {
 | 
			
		||||
        // no need to write anything if changeset is empty
 | 
			
		||||
        if changeset.is_empty() {
 | 
			
		||||
            return Ok(());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let db_transaction = self.db_transaction()?;
 | 
			
		||||
 | 
			
		||||
        let network_changeset = &changeset.network;
 | 
			
		||||
        let current_network = Self::select_network(&db_transaction)?;
 | 
			
		||||
        Self::insert_network(¤t_network, &db_transaction, network_changeset)?;
 | 
			
		||||
 | 
			
		||||
        let chain_changeset = &changeset.chain;
 | 
			
		||||
        Self::insert_or_delete_blocks(&db_transaction, chain_changeset)?;
 | 
			
		||||
 | 
			
		||||
        let tx_graph_changeset = &changeset.indexed_tx_graph;
 | 
			
		||||
        Self::insert_keychains(&db_transaction, tx_graph_changeset)?;
 | 
			
		||||
        Self::update_last_revealed(&db_transaction, tx_graph_changeset)?;
 | 
			
		||||
        Self::insert_txs(&db_transaction, tx_graph_changeset)?;
 | 
			
		||||
        Self::insert_txouts(&db_transaction, tx_graph_changeset)?;
 | 
			
		||||
        Self::insert_anchors(&db_transaction, tx_graph_changeset)?;
 | 
			
		||||
        Self::update_last_seen(&db_transaction, tx_graph_changeset)?;
 | 
			
		||||
        db_transaction.commit().map_err(Error::Sqlite)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Read the entire database and return the aggregate [`CombinedChangeSet`].
 | 
			
		||||
    pub fn read(&mut self) -> Result<Option<CombinedChangeSet<K, A>>, Error> {
 | 
			
		||||
        let db_transaction = self.db_transaction()?;
 | 
			
		||||
 | 
			
		||||
        let network = Self::select_network(&db_transaction)?;
 | 
			
		||||
        let chain = Self::select_blocks(&db_transaction)?;
 | 
			
		||||
        let keychains_added = Self::select_keychains(&db_transaction)?;
 | 
			
		||||
        let last_revealed = Self::select_last_revealed(&db_transaction)?;
 | 
			
		||||
        let txs = Self::select_txs(&db_transaction)?;
 | 
			
		||||
        let last_seen = Self::select_last_seen(&db_transaction)?;
 | 
			
		||||
        let txouts = Self::select_txouts(&db_transaction)?;
 | 
			
		||||
        let anchors = Self::select_anchors(&db_transaction)?;
 | 
			
		||||
 | 
			
		||||
        let graph: tx_graph::ChangeSet<A> = tx_graph::ChangeSet {
 | 
			
		||||
            txs,
 | 
			
		||||
            txouts,
 | 
			
		||||
            anchors,
 | 
			
		||||
            last_seen,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let indexer = keychain_txout::ChangeSet {
 | 
			
		||||
            keychains_added,
 | 
			
		||||
            last_revealed,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let indexed_tx_graph: indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<K>> =
 | 
			
		||||
            indexed_tx_graph::ChangeSet { graph, indexer };
 | 
			
		||||
 | 
			
		||||
        if network.is_none() && chain.is_empty() && indexed_tx_graph.is_empty() {
 | 
			
		||||
            Ok(None)
 | 
			
		||||
        } else {
 | 
			
		||||
            Ok(Some(CombinedChangeSet {
 | 
			
		||||
                chain,
 | 
			
		||||
                indexed_tx_graph,
 | 
			
		||||
                network,
 | 
			
		||||
            }))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod test {
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::store::Merge;
 | 
			
		||||
    use bdk_chain::bitcoin::consensus::encode::deserialize;
 | 
			
		||||
    use bdk_chain::bitcoin::constants::genesis_block;
 | 
			
		||||
    use bdk_chain::bitcoin::hashes::hex::FromHex;
 | 
			
		||||
    use bdk_chain::bitcoin::transaction::Transaction;
 | 
			
		||||
    use bdk_chain::bitcoin::Network::Testnet;
 | 
			
		||||
    use bdk_chain::bitcoin::{secp256k1, BlockHash, OutPoint};
 | 
			
		||||
    use bdk_chain::miniscript::Descriptor;
 | 
			
		||||
    use bdk_chain::CombinedChangeSet;
 | 
			
		||||
    use bdk_chain::{indexed_tx_graph, tx_graph, BlockId, ConfirmationBlockTime, DescriptorExt};
 | 
			
		||||
    use std::str::FromStr;
 | 
			
		||||
    use std::sync::Arc;
 | 
			
		||||
 | 
			
		||||
    #[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash, Debug, Serialize, Deserialize)]
 | 
			
		||||
    enum Keychain {
 | 
			
		||||
        External { account: u32, name: String },
 | 
			
		||||
        Internal { account: u32, name: String },
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn insert_and_load_aggregate_changesets_with_confirmation_block_time_anchor() {
 | 
			
		||||
        let (test_changesets, agg_test_changesets) =
 | 
			
		||||
            create_test_changesets(&|height, time, hash| ConfirmationBlockTime {
 | 
			
		||||
                confirmation_time: time,
 | 
			
		||||
                block_id: (height, hash).into(),
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        let conn = Connection::open_in_memory().expect("in memory connection");
 | 
			
		||||
        let mut store = Store::<Keychain, ConfirmationBlockTime>::new(conn)
 | 
			
		||||
            .expect("create new memory db store");
 | 
			
		||||
 | 
			
		||||
        test_changesets.iter().for_each(|changeset| {
 | 
			
		||||
            store.write(changeset).expect("write changeset");
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let agg_changeset = store.read().expect("aggregated changeset");
 | 
			
		||||
 | 
			
		||||
        assert_eq!(agg_changeset, Some(agg_test_changesets));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn insert_and_load_aggregate_changesets_with_blockid_anchor() {
 | 
			
		||||
        let (test_changesets, agg_test_changesets) =
 | 
			
		||||
            create_test_changesets(&|height, _time, hash| BlockId { height, hash });
 | 
			
		||||
 | 
			
		||||
        let conn = Connection::open_in_memory().expect("in memory connection");
 | 
			
		||||
        let mut store = Store::<Keychain, BlockId>::new(conn).expect("create new memory db store");
 | 
			
		||||
 | 
			
		||||
        test_changesets.iter().for_each(|changeset| {
 | 
			
		||||
            store.write(changeset).expect("write changeset");
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let agg_changeset = store.read().expect("aggregated changeset");
 | 
			
		||||
 | 
			
		||||
        assert_eq!(agg_changeset, Some(agg_test_changesets));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn create_test_changesets<A: Anchor + Copy>(
 | 
			
		||||
        anchor_fn: &dyn Fn(u32, u64, BlockHash) -> A,
 | 
			
		||||
    ) -> (
 | 
			
		||||
        Vec<CombinedChangeSet<Keychain, A>>,
 | 
			
		||||
        CombinedChangeSet<Keychain, A>,
 | 
			
		||||
    ) {
 | 
			
		||||
        let secp = &secp256k1::Secp256k1::signing_only();
 | 
			
		||||
 | 
			
		||||
        let network_changeset = Some(Testnet);
 | 
			
		||||
 | 
			
		||||
        let block_hash_0: BlockHash = genesis_block(Testnet).block_hash();
 | 
			
		||||
        let block_hash_1 =
 | 
			
		||||
            BlockHash::from_str("00000000b873e79784647a6c82962c70d228557d24a747ea4d1b8bbe878e1206")
 | 
			
		||||
                .unwrap();
 | 
			
		||||
        let block_hash_2 =
 | 
			
		||||
            BlockHash::from_str("000000006c02c8ea6e4ff69651f7fcde348fb9d557a06e6957b65552002a7820")
 | 
			
		||||
                .unwrap();
 | 
			
		||||
 | 
			
		||||
        let block_changeset = [
 | 
			
		||||
            (0, Some(block_hash_0)),
 | 
			
		||||
            (1, Some(block_hash_1)),
 | 
			
		||||
            (2, Some(block_hash_2)),
 | 
			
		||||
        ]
 | 
			
		||||
        .into();
 | 
			
		||||
 | 
			
		||||
        let ext_keychain = Keychain::External {
 | 
			
		||||
            account: 0,
 | 
			
		||||
            name: "ext test".to_string(),
 | 
			
		||||
        };
 | 
			
		||||
        let (ext_desc, _ext_keymap) = Descriptor::parse_descriptor(secp, "wpkh(tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy/0/*)").unwrap();
 | 
			
		||||
        let ext_desc_id = ext_desc.descriptor_id();
 | 
			
		||||
        let int_keychain = Keychain::Internal {
 | 
			
		||||
            account: 0,
 | 
			
		||||
            name: "int test".to_string(),
 | 
			
		||||
        };
 | 
			
		||||
        let (int_desc, _int_keymap) = Descriptor::parse_descriptor(secp, "wpkh(tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy/1/*)").unwrap();
 | 
			
		||||
        let int_desc_id = int_desc.descriptor_id();
 | 
			
		||||
 | 
			
		||||
        let tx0_hex = Vec::<u8>::from_hex("01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e206272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a01000000434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000").unwrap();
 | 
			
		||||
        let tx0: Arc<Transaction> = Arc::new(deserialize(tx0_hex.as_slice()).unwrap());
 | 
			
		||||
        let tx1_hex = Vec::<u8>::from_hex("010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff025151feffffff0200f2052a010000001600149243f727dd5343293eb83174324019ec16c2630f0000000000000000776a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf94c4fecc7daa2490047304402205e423a8754336ca99dbe16509b877ef1bf98d008836c725005b3c787c41ebe46022047246e4467ad7cc7f1ad98662afcaf14c115e0095a227c7b05c5182591c23e7e01000120000000000000000000000000000000000000000000000000000000000000000000000000").unwrap();
 | 
			
		||||
        let tx1: Arc<Transaction> = Arc::new(deserialize(tx1_hex.as_slice()).unwrap());
 | 
			
		||||
        let tx2_hex = Vec::<u8>::from_hex("01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0e0432e7494d010e062f503253482fffffffff0100f2052a010000002321038a7f6ef1c8ca0c588aa53fa860128077c9e6c11e6830f4d7ee4e763a56b7718fac00000000").unwrap();
 | 
			
		||||
        let tx2: Arc<Transaction> = Arc::new(deserialize(tx2_hex.as_slice()).unwrap());
 | 
			
		||||
 | 
			
		||||
        let outpoint0_0 = OutPoint::new(tx0.compute_txid(), 0);
 | 
			
		||||
        let txout0_0 = tx0.output.first().unwrap().clone();
 | 
			
		||||
        let outpoint1_0 = OutPoint::new(tx1.compute_txid(), 0);
 | 
			
		||||
        let txout1_0 = tx1.output.first().unwrap().clone();
 | 
			
		||||
 | 
			
		||||
        let anchor1 = anchor_fn(1, 1296667328, block_hash_1);
 | 
			
		||||
        let anchor2 = anchor_fn(2, 1296688946, block_hash_2);
 | 
			
		||||
 | 
			
		||||
        let tx_graph_changeset = tx_graph::ChangeSet::<A> {
 | 
			
		||||
            txs: [tx0.clone(), tx1.clone()].into(),
 | 
			
		||||
            txouts: [(outpoint0_0, txout0_0), (outpoint1_0, txout1_0)].into(),
 | 
			
		||||
            anchors: [(anchor1, tx0.compute_txid()), (anchor1, tx1.compute_txid())].into(),
 | 
			
		||||
            last_seen: [
 | 
			
		||||
                (tx0.compute_txid(), 1598918400),
 | 
			
		||||
                (tx1.compute_txid(), 1598919121),
 | 
			
		||||
                (tx2.compute_txid(), 1608919121),
 | 
			
		||||
            ]
 | 
			
		||||
            .into(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let keychain_changeset = keychain_txout::ChangeSet {
 | 
			
		||||
            keychains_added: [(ext_keychain, ext_desc), (int_keychain, int_desc)].into(),
 | 
			
		||||
            last_revealed: [(ext_desc_id, 124), (int_desc_id, 421)].into(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let graph_changeset: indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<Keychain>> =
 | 
			
		||||
            indexed_tx_graph::ChangeSet {
 | 
			
		||||
                graph: tx_graph_changeset,
 | 
			
		||||
                indexer: keychain_changeset,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
        // test changesets to write to db
 | 
			
		||||
        let mut changesets = Vec::new();
 | 
			
		||||
 | 
			
		||||
        changesets.push(CombinedChangeSet {
 | 
			
		||||
            chain: block_changeset,
 | 
			
		||||
            indexed_tx_graph: graph_changeset,
 | 
			
		||||
            network: network_changeset,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // create changeset that sets the whole tx2 and updates it's lastseen where before there was only the txid and last_seen
 | 
			
		||||
        let tx_graph_changeset2 = tx_graph::ChangeSet::<A> {
 | 
			
		||||
            txs: [tx2.clone()].into(),
 | 
			
		||||
            txouts: BTreeMap::default(),
 | 
			
		||||
            anchors: BTreeSet::default(),
 | 
			
		||||
            last_seen: [(tx2.compute_txid(), 1708919121)].into(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let graph_changeset2: indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<Keychain>> =
 | 
			
		||||
            indexed_tx_graph::ChangeSet {
 | 
			
		||||
                graph: tx_graph_changeset2,
 | 
			
		||||
                indexer: keychain_txout::ChangeSet::default(),
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
        changesets.push(CombinedChangeSet {
 | 
			
		||||
            chain: local_chain::ChangeSet::default(),
 | 
			
		||||
            indexed_tx_graph: graph_changeset2,
 | 
			
		||||
            network: None,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // create changeset that adds a new anchor2 for tx0 and tx1
 | 
			
		||||
        let tx_graph_changeset3 = tx_graph::ChangeSet::<A> {
 | 
			
		||||
            txs: BTreeSet::default(),
 | 
			
		||||
            txouts: BTreeMap::default(),
 | 
			
		||||
            anchors: [(anchor2, tx0.compute_txid()), (anchor2, tx1.compute_txid())].into(),
 | 
			
		||||
            last_seen: BTreeMap::default(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let graph_changeset3: indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<Keychain>> =
 | 
			
		||||
            indexed_tx_graph::ChangeSet {
 | 
			
		||||
                graph: tx_graph_changeset3,
 | 
			
		||||
                indexer: keychain_txout::ChangeSet::default(),
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
        changesets.push(CombinedChangeSet {
 | 
			
		||||
            chain: local_chain::ChangeSet::default(),
 | 
			
		||||
            indexed_tx_graph: graph_changeset3,
 | 
			
		||||
            network: None,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        // aggregated test changesets
 | 
			
		||||
        let agg_test_changesets =
 | 
			
		||||
            changesets
 | 
			
		||||
                .iter()
 | 
			
		||||
                .fold(CombinedChangeSet::<Keychain, A>::default(), |mut i, cs| {
 | 
			
		||||
                    i.merge(cs.clone());
 | 
			
		||||
                    i
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
        (changesets, agg_test_changesets)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -19,6 +19,7 @@ bitcoin = { version = "0.32.0", features = ["serde", "base64"], default-features
 | 
			
		||||
serde = { version = "^1.0", features = ["derive"] }
 | 
			
		||||
serde_json = { version = "^1.0" }
 | 
			
		||||
bdk_chain = { path = "../chain", version = "0.16.0", features = ["miniscript", "serde"], default-features = false }
 | 
			
		||||
bdk_file_store = { path = "../file_store", version = "0.13.0", optional = true }
 | 
			
		||||
 | 
			
		||||
# Optional dependencies
 | 
			
		||||
bip39 = { version = "2.0", optional = true }
 | 
			
		||||
@ -29,12 +30,15 @@ std = ["bitcoin/std", "bitcoin/rand-std", "miniscript/std", "bdk_chain/std"]
 | 
			
		||||
compiler = ["miniscript/compiler"]
 | 
			
		||||
all-keys = ["keys-bip39"]
 | 
			
		||||
keys-bip39 = ["bip39"]
 | 
			
		||||
rusqlite = ["bdk_chain/rusqlite"]
 | 
			
		||||
file_store = ["bdk_file_store"]
 | 
			
		||||
 | 
			
		||||
[dev-dependencies]
 | 
			
		||||
lazy_static = "1.4"
 | 
			
		||||
assert_matches = "1.5.0"
 | 
			
		||||
tempfile = "3"
 | 
			
		||||
bdk_sqlite = { path = "../sqlite" }
 | 
			
		||||
bdk_chain = { path = "../chain", features = ["rusqlite"] }
 | 
			
		||||
bdk_wallet = { path = ".", features = ["rusqlite", "file_store"] }
 | 
			
		||||
bdk_file_store = { path = "../file_store" }
 | 
			
		||||
anyhow = "1"
 | 
			
		||||
rand = "^0.8"
 | 
			
		||||
 | 
			
		||||
@ -57,18 +57,17 @@ that the `Wallet` can use to update its view of the chain.
 | 
			
		||||
 | 
			
		||||
## Persistence
 | 
			
		||||
 | 
			
		||||
To persist `Wallet` state data use a data store crate that reads and writes [`bdk_chain::CombinedChangeSet`].
 | 
			
		||||
To persist `Wallet` state data use a data store crate that reads and writes [`ChangeSet`].
 | 
			
		||||
 | 
			
		||||
**Implementations**
 | 
			
		||||
 | 
			
		||||
* [`bdk_file_store`]: Stores wallet changes in a simple flat file.
 | 
			
		||||
* [`bdk_sqlite`]: Stores wallet changes in a SQLite relational database file.
 | 
			
		||||
 | 
			
		||||
**Example**
 | 
			
		||||
 | 
			
		||||
<!-- compile_fail because outpoint and txout are fake variables -->
 | 
			
		||||
```rust,no_run
 | 
			
		||||
use bdk_wallet::{bitcoin::Network, KeychainKind, wallet::{ChangeSet, Wallet}};
 | 
			
		||||
use bdk_wallet::{bitcoin::Network, KeychainKind, ChangeSet, Wallet};
 | 
			
		||||
 | 
			
		||||
// Open or create a new file store for wallet data.
 | 
			
		||||
let mut db =
 | 
			
		||||
@ -76,21 +75,26 @@ let mut db =
 | 
			
		||||
        .expect("create store");
 | 
			
		||||
 | 
			
		||||
// Create a wallet with initial wallet data read from the file store.
 | 
			
		||||
let network = Network::Testnet;
 | 
			
		||||
let descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/0/*)";
 | 
			
		||||
let change_descriptor = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/1/*)";
 | 
			
		||||
let changeset = db.aggregate_changesets().expect("changeset loaded");
 | 
			
		||||
let mut wallet =
 | 
			
		||||
    Wallet::new_or_load(descriptor, change_descriptor, changeset, Network::Testnet)
 | 
			
		||||
        .expect("create or load wallet");
 | 
			
		||||
let wallet_opt = Wallet::load()
 | 
			
		||||
    .descriptors(descriptor, change_descriptor)
 | 
			
		||||
    .network(network)
 | 
			
		||||
    .load_wallet(&mut db)
 | 
			
		||||
    .expect("wallet");
 | 
			
		||||
let mut wallet = match wallet_opt {
 | 
			
		||||
    Some(wallet) => wallet,
 | 
			
		||||
    None => Wallet::create(descriptor, change_descriptor)
 | 
			
		||||
        .network(network)
 | 
			
		||||
        .create_wallet(&mut db)
 | 
			
		||||
        .expect("wallet"),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Get a new address to receive bitcoin.
 | 
			
		||||
let receive_address = wallet.reveal_next_address(KeychainKind::External);
 | 
			
		||||
// Persist staged wallet data changes to the file store.
 | 
			
		||||
let staged_changeset = wallet.take_staged();
 | 
			
		||||
if let Some(changeset) = staged_changeset {
 | 
			
		||||
    db.append_changeset(&changeset)
 | 
			
		||||
        .expect("must commit changes to database");
 | 
			
		||||
}
 | 
			
		||||
wallet.persist(&mut db).expect("persist");
 | 
			
		||||
println!("Your new receive address is: {}", receive_address.address);
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
@ -122,7 +126,7 @@ println!("Your new receive address is: {}", receive_address.address);
 | 
			
		||||
 | 
			
		||||
<!-- ```rust -->
 | 
			
		||||
<!-- use bdk_wallet::Wallet; -->
 | 
			
		||||
<!-- use bdk_wallet::wallet::AddressIndex::New; -->
 | 
			
		||||
<!-- use bdk_wallet::AddressIndex::New; -->
 | 
			
		||||
<!-- use bdk_wallet::bitcoin::Network; -->
 | 
			
		||||
 | 
			
		||||
<!-- fn main() -> Result<(), bdk_wallet::Error> { -->
 | 
			
		||||
@ -147,7 +151,7 @@ println!("Your new receive address is: {}", receive_address.address);
 | 
			
		||||
<!-- use bdk_wallet::blockchain::ElectrumBlockchain; -->
 | 
			
		||||
 | 
			
		||||
<!-- use bdk_wallet::electrum_client::Client; -->
 | 
			
		||||
<!-- use bdk_wallet::wallet::AddressIndex::New; -->
 | 
			
		||||
<!-- use bdk_wallet::AddressIndex::New; -->
 | 
			
		||||
 | 
			
		||||
<!-- use bitcoin::base64; -->
 | 
			
		||||
<!-- use bdk_wallet::bitcoin::consensus::serialize; -->
 | 
			
		||||
@ -233,7 +237,6 @@ conditions.
 | 
			
		||||
[`Wallet`]: https://docs.rs/bdk_wallet/latest/bdk_wallet/wallet/struct.Wallet.html
 | 
			
		||||
[`bdk_chain`]: https://docs.rs/bdk_chain/latest
 | 
			
		||||
[`bdk_file_store`]: https://docs.rs/bdk_file_store/latest
 | 
			
		||||
[`bdk_sqlite`]: https://docs.rs/bdk_sqlite/latest
 | 
			
		||||
[`bdk_electrum`]: https://docs.rs/bdk_electrum/latest
 | 
			
		||||
[`bdk_esplora`]: https://docs.rs/bdk_esplora/latest
 | 
			
		||||
[`bdk_bitcoind_rpc`]: https://docs.rs/bdk_bitcoind_rpc/latest
 | 
			
		||||
 | 
			
		||||
@ -77,7 +77,9 @@ fn main() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Create a new wallet from descriptors
 | 
			
		||||
    let mut wallet = Wallet::new(&descriptor, &internal_descriptor, Network::Regtest)?;
 | 
			
		||||
    let mut wallet = Wallet::create(descriptor, internal_descriptor)
 | 
			
		||||
        .network(Network::Regtest)
 | 
			
		||||
        .create_wallet_no_persist()?;
 | 
			
		||||
 | 
			
		||||
    println!(
 | 
			
		||||
        "First derived address from the descriptor: \n{}",
 | 
			
		||||
 | 
			
		||||
@ -14,7 +14,7 @@ use std::error::Error;
 | 
			
		||||
 | 
			
		||||
use bdk_wallet::bitcoin::Network;
 | 
			
		||||
use bdk_wallet::descriptor::{policy::BuildSatisfaction, ExtractPolicy, IntoWalletDescriptor};
 | 
			
		||||
use bdk_wallet::wallet::signer::SignersContainer;
 | 
			
		||||
use bdk_wallet::signer::SignersContainer;
 | 
			
		||||
 | 
			
		||||
/// This example describes the use of the BDK's [`bdk_wallet::descriptor::policy`] module.
 | 
			
		||||
///
 | 
			
		||||
@ -38,7 +38,7 @@ fn main() -> Result<(), Box<dyn Error>> {
 | 
			
		||||
    // While the `keymap` can be used to create a `SignerContainer`.
 | 
			
		||||
    //
 | 
			
		||||
    // The `SignerContainer` can sign for `PSBT`s.
 | 
			
		||||
    // a bdk_wallet::wallet internally uses these to handle transaction signing.
 | 
			
		||||
    // a `bdk_wallet::Wallet` internally uses these to handle transaction signing.
 | 
			
		||||
    // But they can be used as independent tools also.
 | 
			
		||||
    let (wallet_desc, keymap) = desc.into_wallet_descriptor(&secp, Network::Testnet)?;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@
 | 
			
		||||
use core::fmt;
 | 
			
		||||
 | 
			
		||||
/// Errors related to the parsing and usage of descriptors
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
#[derive(Debug, PartialEq)]
 | 
			
		||||
pub enum Error {
 | 
			
		||||
    /// Invalid HD Key path, such as having a wildcard but a length != 1
 | 
			
		||||
    InvalidHdKeyPath,
 | 
			
		||||
 | 
			
		||||
@ -112,6 +112,16 @@ impl IntoWalletDescriptor for &String {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IntoWalletDescriptor for String {
 | 
			
		||||
    fn into_wallet_descriptor(
 | 
			
		||||
        self,
 | 
			
		||||
        secp: &SecpCtx,
 | 
			
		||||
        network: Network,
 | 
			
		||||
    ) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
 | 
			
		||||
        self.as_str().into_wallet_descriptor(secp, network)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IntoWalletDescriptor for ExtendedDescriptor {
 | 
			
		||||
    fn into_wallet_descriptor(
 | 
			
		||||
        self,
 | 
			
		||||
@ -281,15 +291,10 @@ impl IntoWalletDescriptor for DescriptorTemplateOut {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Wrapper for `IntoWalletDescriptor` that performs additional checks on the keys contained in the
 | 
			
		||||
/// descriptor
 | 
			
		||||
pub(crate) fn into_wallet_descriptor_checked<T: IntoWalletDescriptor>(
 | 
			
		||||
    inner: T,
 | 
			
		||||
    secp: &SecpCtx,
 | 
			
		||||
    network: Network,
 | 
			
		||||
) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError> {
 | 
			
		||||
    let (descriptor, keymap) = inner.into_wallet_descriptor(secp, network)?;
 | 
			
		||||
 | 
			
		||||
/// Extra checks for [`ExtendedDescriptor`].
 | 
			
		||||
pub(crate) fn check_wallet_descriptor(
 | 
			
		||||
    descriptor: &Descriptor<DescriptorPublicKey>,
 | 
			
		||||
) -> Result<(), DescriptorError> {
 | 
			
		||||
    // Ensure the keys don't contain any hardened derivation steps or hardened wildcards
 | 
			
		||||
    let descriptor_contains_hardened_steps = descriptor.for_any_key(|k| {
 | 
			
		||||
        if let DescriptorPublicKey::XPub(DescriptorXKey {
 | 
			
		||||
@ -316,7 +321,7 @@ pub(crate) fn into_wallet_descriptor_checked<T: IntoWalletDescriptor>(
 | 
			
		||||
    // issues
 | 
			
		||||
    descriptor.sanity_check()?;
 | 
			
		||||
 | 
			
		||||
    Ok((descriptor, keymap))
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[doc(hidden)]
 | 
			
		||||
@ -855,22 +860,31 @@ mod test {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_into_wallet_descriptor_checked() {
 | 
			
		||||
    fn test_check_wallet_descriptor() {
 | 
			
		||||
        let secp = Secp256k1::new();
 | 
			
		||||
 | 
			
		||||
        let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0'/1/2/*)";
 | 
			
		||||
        let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
 | 
			
		||||
        let (descriptor, _) = descriptor
 | 
			
		||||
            .into_wallet_descriptor(&secp, Network::Testnet)
 | 
			
		||||
            .expect("must parse");
 | 
			
		||||
        let result = check_wallet_descriptor(&descriptor);
 | 
			
		||||
 | 
			
		||||
        assert_matches!(result, Err(DescriptorError::HardenedDerivationXpub));
 | 
			
		||||
 | 
			
		||||
        let descriptor = "wpkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/<0;1>/*)";
 | 
			
		||||
        let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
 | 
			
		||||
        let (descriptor, _) = descriptor
 | 
			
		||||
            .into_wallet_descriptor(&secp, Network::Testnet)
 | 
			
		||||
            .expect("must parse");
 | 
			
		||||
        let result = check_wallet_descriptor(&descriptor);
 | 
			
		||||
 | 
			
		||||
        assert_matches!(result, Err(DescriptorError::MultiPath));
 | 
			
		||||
 | 
			
		||||
        // repeated pubkeys
 | 
			
		||||
        let descriptor = "wsh(multi(2,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*,tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/0/*))";
 | 
			
		||||
        let result = into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet);
 | 
			
		||||
        let (descriptor, _) = descriptor
 | 
			
		||||
            .into_wallet_descriptor(&secp, Network::Testnet)
 | 
			
		||||
            .expect("must parse");
 | 
			
		||||
        let result = check_wallet_descriptor(&descriptor);
 | 
			
		||||
 | 
			
		||||
        assert!(result.is_err());
 | 
			
		||||
    }
 | 
			
		||||
@ -882,8 +896,10 @@ mod test {
 | 
			
		||||
        let secp = Secp256k1::new();
 | 
			
		||||
 | 
			
		||||
        let descriptor = "sh(wsh(sortedmulti(3,tpubDEsqS36T4DVsKJd9UH8pAKzrkGBYPLEt9jZMwpKtzh1G6mgYehfHt9WCgk7MJG5QGSFWf176KaBNoXbcuFcuadAFKxDpUdMDKGBha7bY3QM/0/*,tpubDF3cpwfs7fMvXXuoQbohXtLjNM6ehwYT287LWtmLsd4r77YLg6MZg4vTETx5MSJ2zkfigbYWu31VA2Z2Vc1cZugCYXgS7FQu6pE8V6TriEH/0/*,tpubDE1SKfcW76Tb2AASv5bQWMuScYNAdoqLHoexw13sNDXwmUhQDBbCD3QAedKGLhxMrWQdMDKENzYtnXPDRvexQPNuDrLj52wAjHhNEm8sJ4p/0/*,tpubDFLc6oXwJmhm3FGGzXkfJNTh2KitoY3WhmmQvuAjMhD8YbyWn5mAqckbxXfm2etM3p5J6JoTpSrMqRSTfMLtNW46poDaEZJ1kjd3csRSjwH/0/*,tpubDEWD9NBeWP59xXmdqSNt4VYdtTGwbpyP8WS962BuqpQeMZmX9Pur14dhXdZT5a7wR1pK6dPtZ9fP5WR493hPzemnBvkfLLYxnUjAKj1JCQV/0/*,tpubDEHyZkkwd7gZWCTgQuYQ9C4myF2hMEmyHsBCCmLssGqoqUxeT3gzohF5uEVURkf9TtmeepJgkSUmteac38FwZqirjApzNX59XSHLcwaTZCH/0/*,tpubDEqLouCekwnMUWN486kxGzD44qVgeyuqHyxUypNEiQt5RnUZNJe386TKPK99fqRV1vRkZjYAjtXGTECz98MCsdLcnkM67U6KdYRzVubeCgZ/0/*)))";
 | 
			
		||||
        let (descriptor, _) =
 | 
			
		||||
            into_wallet_descriptor_checked(descriptor, &secp, Network::Testnet).unwrap();
 | 
			
		||||
        let (descriptor, _) = descriptor
 | 
			
		||||
            .into_wallet_descriptor(&secp, Network::Testnet)
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        check_wallet_descriptor(&descriptor).expect("descriptor");
 | 
			
		||||
 | 
			
		||||
        let descriptor = descriptor.at_derivation_index(0).unwrap();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@
 | 
			
		||||
//! ```
 | 
			
		||||
//! # use std::sync::Arc;
 | 
			
		||||
//! # use bdk_wallet::descriptor::*;
 | 
			
		||||
//! # use bdk_wallet::wallet::signer::*;
 | 
			
		||||
//! # use bdk_wallet::signer::*;
 | 
			
		||||
//! # use bdk_wallet::bitcoin::secp256k1::Secp256k1;
 | 
			
		||||
//! use bdk_wallet::descriptor::policy::BuildSatisfaction;
 | 
			
		||||
//! let secp = Secp256k1::new();
 | 
			
		||||
 | 
			
		||||
@ -81,7 +81,9 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
 | 
			
		||||
///     bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
 | 
			
		||||
/// let key_internal =
 | 
			
		||||
///     bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
 | 
			
		||||
/// let mut wallet = Wallet::new(P2Pkh(key_external), P2Pkh(key_internal), Network::Testnet)?;
 | 
			
		||||
/// let mut wallet = Wallet::create(P2Pkh(key_external), P2Pkh(key_internal))
 | 
			
		||||
///     .network(Network::Testnet)
 | 
			
		||||
///     .create_wallet_no_persist()?;
 | 
			
		||||
///
 | 
			
		||||
/// assert_eq!(
 | 
			
		||||
///     wallet
 | 
			
		||||
@ -91,6 +93,7 @@ impl<T: DescriptorTemplate> IntoWalletDescriptor for T {
 | 
			
		||||
/// );
 | 
			
		||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
 | 
			
		||||
/// ```
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct P2Pkh<K: IntoDescriptorKey<Legacy>>(pub K);
 | 
			
		||||
 | 
			
		||||
impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
 | 
			
		||||
@ -113,11 +116,9 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
 | 
			
		||||
///     bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
 | 
			
		||||
/// let key_internal =
 | 
			
		||||
///     bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
 | 
			
		||||
/// let mut wallet = Wallet::new(
 | 
			
		||||
///     P2Wpkh_P2Sh(key_external),
 | 
			
		||||
///     P2Wpkh_P2Sh(key_internal),
 | 
			
		||||
///     Network::Testnet,
 | 
			
		||||
/// )?;
 | 
			
		||||
/// let mut wallet = Wallet::create(P2Wpkh_P2Sh(key_external), P2Wpkh_P2Sh(key_internal))
 | 
			
		||||
///     .network(Network::Testnet)
 | 
			
		||||
///     .create_wallet_no_persist()?;
 | 
			
		||||
///
 | 
			
		||||
/// assert_eq!(
 | 
			
		||||
///     wallet
 | 
			
		||||
@ -128,6 +129,7 @@ impl<K: IntoDescriptorKey<Legacy>> DescriptorTemplate for P2Pkh<K> {
 | 
			
		||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
 | 
			
		||||
/// ```
 | 
			
		||||
#[allow(non_camel_case_types)]
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct P2Wpkh_P2Sh<K: IntoDescriptorKey<Segwitv0>>(pub K);
 | 
			
		||||
 | 
			
		||||
impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
 | 
			
		||||
@ -142,7 +144,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
 | 
			
		||||
///
 | 
			
		||||
/// ```
 | 
			
		||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
 | 
			
		||||
/// # use bdk_wallet::{Wallet};
 | 
			
		||||
/// # use bdk_wallet::Wallet;
 | 
			
		||||
/// # use bdk_wallet::KeychainKind;
 | 
			
		||||
/// use bdk_wallet::template::P2Wpkh;
 | 
			
		||||
///
 | 
			
		||||
@ -150,7 +152,9 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
 | 
			
		||||
///     bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
 | 
			
		||||
/// let key_internal =
 | 
			
		||||
///     bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
 | 
			
		||||
/// let mut wallet = Wallet::new(P2Wpkh(key_external), P2Wpkh(key_internal), Network::Testnet)?;
 | 
			
		||||
/// let mut wallet = Wallet::create(P2Wpkh(key_external), P2Wpkh(key_internal))
 | 
			
		||||
///     .network(Network::Testnet)
 | 
			
		||||
///     .create_wallet_no_persist()?;
 | 
			
		||||
///
 | 
			
		||||
/// assert_eq!(
 | 
			
		||||
///     wallet
 | 
			
		||||
@ -160,6 +164,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh_P2Sh<K> {
 | 
			
		||||
/// );
 | 
			
		||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
 | 
			
		||||
/// ```
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct P2Wpkh<K: IntoDescriptorKey<Segwitv0>>(pub K);
 | 
			
		||||
 | 
			
		||||
impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
 | 
			
		||||
@ -182,7 +187,9 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
 | 
			
		||||
///     bitcoin::PrivateKey::from_wif("cTc4vURSzdx6QE6KVynWGomDbLaA75dNALMNyfjh3p8DRRar84Um")?;
 | 
			
		||||
/// let key_internal =
 | 
			
		||||
///     bitcoin::PrivateKey::from_wif("cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW")?;
 | 
			
		||||
/// let mut wallet = Wallet::new(P2TR(key_external), P2TR(key_internal), Network::Testnet)?;
 | 
			
		||||
/// let mut wallet = Wallet::create(P2TR(key_external), P2TR(key_internal))
 | 
			
		||||
///     .network(Network::Testnet)
 | 
			
		||||
///     .create_wallet_no_persist()?;
 | 
			
		||||
///
 | 
			
		||||
/// assert_eq!(
 | 
			
		||||
///     wallet
 | 
			
		||||
@ -192,6 +199,7 @@ impl<K: IntoDescriptorKey<Segwitv0>> DescriptorTemplate for P2Wpkh<K> {
 | 
			
		||||
/// );
 | 
			
		||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
 | 
			
		||||
/// ```
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct P2TR<K: IntoDescriptorKey<Tap>>(pub K);
 | 
			
		||||
 | 
			
		||||
impl<K: IntoDescriptorKey<Tap>> DescriptorTemplate for P2TR<K> {
 | 
			
		||||
@ -208,23 +216,22 @@ impl<K: IntoDescriptorKey<Tap>> DescriptorTemplate for P2TR<K> {
 | 
			
		||||
///
 | 
			
		||||
/// ## Example
 | 
			
		||||
///
 | 
			
		||||
/// ```
 | 
			
		||||
/// ```rust
 | 
			
		||||
/// # use std::str::FromStr;
 | 
			
		||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
 | 
			
		||||
/// # use bdk_wallet::{Wallet,  KeychainKind};
 | 
			
		||||
/// # use bdk_wallet::{Wallet, KeychainKind};
 | 
			
		||||
/// use bdk_wallet::template::Bip44;
 | 
			
		||||
///
 | 
			
		||||
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
 | 
			
		||||
/// let mut wallet = Wallet::new(
 | 
			
		||||
///     Bip44(key.clone(), KeychainKind::External),
 | 
			
		||||
///     Bip44(key, KeychainKind::Internal),
 | 
			
		||||
///     Network::Testnet,
 | 
			
		||||
/// )?;
 | 
			
		||||
/// let mut wallet = Wallet::create(Bip44(key.clone(), KeychainKind::External), Bip44(key, KeychainKind::Internal))
 | 
			
		||||
///     .network(Network::Testnet)
 | 
			
		||||
///     .create_wallet_no_persist()?;
 | 
			
		||||
///
 | 
			
		||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "mmogjc7HJEZkrLqyQYqJmxUqFaC7i4uf89");
 | 
			
		||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "pkh([c55b303f/44'/1'/0']tpubDCuorCpzvYS2LCD75BR46KHE8GdDeg1wsAgNZeNr6DaB5gQK1o14uErKwKLuFmeemkQ6N2m3rNgvctdJLyr7nwu2yia7413Hhg8WWE44cgT/0/*)#5wrnv0xt");
 | 
			
		||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
 | 
			
		||||
/// ```
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct Bip44<K: DerivableKey<Legacy>>(pub K, pub KeychainKind);
 | 
			
		||||
 | 
			
		||||
impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
 | 
			
		||||
@ -247,21 +254,23 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44<K> {
 | 
			
		||||
/// ```
 | 
			
		||||
/// # use std::str::FromStr;
 | 
			
		||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
 | 
			
		||||
/// # use bdk_wallet::{Wallet,  KeychainKind};
 | 
			
		||||
/// # use bdk_wallet::{KeychainKind, Wallet};
 | 
			
		||||
/// use bdk_wallet::template::Bip44Public;
 | 
			
		||||
///
 | 
			
		||||
/// let key = bitcoin::bip32::Xpub::from_str("tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU")?;
 | 
			
		||||
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
 | 
			
		||||
/// let mut wallet = Wallet::new(
 | 
			
		||||
/// let mut wallet = Wallet::create(
 | 
			
		||||
///     Bip44Public(key.clone(), fingerprint, KeychainKind::External),
 | 
			
		||||
///     Bip44Public(key, fingerprint, KeychainKind::Internal),
 | 
			
		||||
///     Network::Testnet,
 | 
			
		||||
/// )?;
 | 
			
		||||
///     )
 | 
			
		||||
///     .network(Network::Testnet)
 | 
			
		||||
/// .create_wallet_no_persist()?;
 | 
			
		||||
///
 | 
			
		||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "miNG7dJTzJqNbFS19svRdTCisC65dsubtR");
 | 
			
		||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "pkh([c55b303f/44'/1'/0']tpubDDDzQ31JkZB7VxUr9bjvBivDdqoFLrDPyLWtLapArAi51ftfmCb2DPxwLQzX65iNcXz1DGaVvyvo6JQ6rTU73r2gqdEo8uov9QKRb7nKCSU/0/*)#cfhumdqz");
 | 
			
		||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
 | 
			
		||||
/// ```
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct Bip44Public<K: DerivableKey<Legacy>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
 | 
			
		||||
 | 
			
		||||
impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
 | 
			
		||||
@ -284,20 +293,22 @@ impl<K: DerivableKey<Legacy>> DescriptorTemplate for Bip44Public<K> {
 | 
			
		||||
/// ```
 | 
			
		||||
/// # use std::str::FromStr;
 | 
			
		||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
 | 
			
		||||
/// # use bdk_wallet::{Wallet,  KeychainKind};
 | 
			
		||||
/// # use bdk_wallet::{Wallet, KeychainKind};
 | 
			
		||||
/// use bdk_wallet::template::Bip49;
 | 
			
		||||
///
 | 
			
		||||
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
 | 
			
		||||
/// let mut wallet = Wallet::new(
 | 
			
		||||
/// let mut wallet = Wallet::create(
 | 
			
		||||
///     Bip49(key.clone(), KeychainKind::External),
 | 
			
		||||
///     Bip49(key, KeychainKind::Internal),
 | 
			
		||||
///     Network::Testnet,
 | 
			
		||||
/// )?;
 | 
			
		||||
/// )
 | 
			
		||||
/// .network(Network::Testnet)
 | 
			
		||||
/// .create_wallet_no_persist()?;
 | 
			
		||||
///
 | 
			
		||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "2N4zkWAoGdUv4NXhSsU8DvS5MB36T8nKHEB");
 | 
			
		||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDDYr4kdnZgjjShzYNjZUZXUUtpXaofdkMaipyS8ThEh45qFmhT4hKYways7UXmg6V7het1QiFo9kf4kYUXyDvV4rHEyvSpys9pjCB3pukxi/0/*))#s9vxlc8e");
 | 
			
		||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
 | 
			
		||||
/// ```
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct Bip49<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
 | 
			
		||||
 | 
			
		||||
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
 | 
			
		||||
@ -320,21 +331,23 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49<K> {
 | 
			
		||||
/// ```
 | 
			
		||||
/// # use std::str::FromStr;
 | 
			
		||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
 | 
			
		||||
/// # use bdk_wallet::{Wallet,  KeychainKind};
 | 
			
		||||
/// # use bdk_wallet::{Wallet, KeychainKind};
 | 
			
		||||
/// use bdk_wallet::template::Bip49Public;
 | 
			
		||||
///
 | 
			
		||||
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L")?;
 | 
			
		||||
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
 | 
			
		||||
/// let mut wallet = Wallet::new(
 | 
			
		||||
/// let mut wallet = Wallet::create(
 | 
			
		||||
///     Bip49Public(key.clone(), fingerprint, KeychainKind::External),
 | 
			
		||||
///     Bip49Public(key, fingerprint, KeychainKind::Internal),
 | 
			
		||||
///     Network::Testnet,
 | 
			
		||||
/// )?;
 | 
			
		||||
/// )
 | 
			
		||||
/// .network(Network::Testnet)
 | 
			
		||||
/// .create_wallet_no_persist()?;
 | 
			
		||||
///
 | 
			
		||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "2N3K4xbVAHoiTQSwxkZjWDfKoNC27pLkYnt");
 | 
			
		||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "sh(wpkh([c55b303f/49'/1'/0']tpubDC49r947KGK52X5rBWS4BLs5m9SRY3pYHnvRrm7HcybZ3BfdEsGFyzCMzayi1u58eT82ZeyFZwH7DD6Q83E3fM9CpfMtmnTygnLfP59jL9L/0/*))#3tka9g0q");
 | 
			
		||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
 | 
			
		||||
/// ```
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct Bip49Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
 | 
			
		||||
 | 
			
		||||
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
 | 
			
		||||
@ -357,20 +370,22 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip49Public<K> {
 | 
			
		||||
/// ```
 | 
			
		||||
/// # use std::str::FromStr;
 | 
			
		||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
 | 
			
		||||
/// # use bdk_wallet::{Wallet,  KeychainKind};
 | 
			
		||||
/// # use bdk_wallet::{Wallet, KeychainKind};
 | 
			
		||||
/// use bdk_wallet::template::Bip84;
 | 
			
		||||
///
 | 
			
		||||
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
 | 
			
		||||
/// let mut wallet = Wallet::new(
 | 
			
		||||
/// let mut wallet = Wallet::create(
 | 
			
		||||
///     Bip84(key.clone(), KeychainKind::External),
 | 
			
		||||
///     Bip84(key, KeychainKind::Internal),
 | 
			
		||||
///     Network::Testnet,
 | 
			
		||||
/// )?;
 | 
			
		||||
/// )
 | 
			
		||||
/// .network(Network::Testnet)
 | 
			
		||||
/// .create_wallet_no_persist()?;
 | 
			
		||||
///
 | 
			
		||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1qhl85z42h7r4su5u37rvvw0gk8j2t3n9y7zsg4n");
 | 
			
		||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "wpkh([c55b303f/84'/1'/0']tpubDDc5mum24DekpNw92t6fHGp8Gr2JjF9J7i4TZBtN6Vp8xpAULG5CFaKsfugWa5imhrQQUZKXe261asP5koDHo5bs3qNTmf3U3o4v9SaB8gg/0/*)#6kfecsmr");
 | 
			
		||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
 | 
			
		||||
/// ```
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct Bip84<K: DerivableKey<Segwitv0>>(pub K, pub KeychainKind);
 | 
			
		||||
 | 
			
		||||
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
 | 
			
		||||
@ -393,21 +408,23 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84<K> {
 | 
			
		||||
/// ```
 | 
			
		||||
/// # use std::str::FromStr;
 | 
			
		||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
 | 
			
		||||
/// # use bdk_wallet::{Wallet,  KeychainKind};
 | 
			
		||||
/// # use bdk_wallet::{Wallet, KeychainKind};
 | 
			
		||||
/// use bdk_wallet::template::Bip84Public;
 | 
			
		||||
///
 | 
			
		||||
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
 | 
			
		||||
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
 | 
			
		||||
/// let mut wallet = Wallet::new(
 | 
			
		||||
/// let mut wallet = Wallet::create(
 | 
			
		||||
///     Bip84Public(key.clone(), fingerprint, KeychainKind::External),
 | 
			
		||||
///     Bip84Public(key, fingerprint, KeychainKind::Internal),
 | 
			
		||||
///     Network::Testnet,
 | 
			
		||||
/// )?;
 | 
			
		||||
/// )
 | 
			
		||||
/// .network(Network::Testnet)
 | 
			
		||||
/// .create_wallet_no_persist()?;
 | 
			
		||||
///
 | 
			
		||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1qedg9fdlf8cnnqfd5mks6uz5w4kgpk2pr6y4qc7");
 | 
			
		||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "wpkh([c55b303f/84'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#dhu402yv");
 | 
			
		||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
 | 
			
		||||
/// ```
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct Bip84Public<K: DerivableKey<Segwitv0>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
 | 
			
		||||
 | 
			
		||||
impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
 | 
			
		||||
@ -430,20 +447,22 @@ impl<K: DerivableKey<Segwitv0>> DescriptorTemplate for Bip84Public<K> {
 | 
			
		||||
/// ```
 | 
			
		||||
/// # use std::str::FromStr;
 | 
			
		||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
 | 
			
		||||
/// # use bdk_wallet::{Wallet,  KeychainKind};
 | 
			
		||||
/// # use bdk_wallet::{Wallet, KeychainKind};
 | 
			
		||||
/// use bdk_wallet::template::Bip86;
 | 
			
		||||
///
 | 
			
		||||
/// let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPeZRHk4rTG6orPS2CRNFX3njhUXx5vj9qGog5ZMH4uGReDWN5kCkY3jmWEtWause41CDvBRXD1shKknAMKxT99o9qUTRVC6m")?;
 | 
			
		||||
/// let mut wallet = Wallet::new(
 | 
			
		||||
/// let mut wallet = Wallet::create(
 | 
			
		||||
///     Bip86(key.clone(), KeychainKind::External),
 | 
			
		||||
///     Bip86(key, KeychainKind::Internal),
 | 
			
		||||
///     Network::Testnet,
 | 
			
		||||
/// )?;
 | 
			
		||||
/// )
 | 
			
		||||
/// .network(Network::Testnet)
 | 
			
		||||
/// .create_wallet_no_persist()?;
 | 
			
		||||
///
 | 
			
		||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1p5unlj09djx8xsjwe97269kqtxqpwpu2epeskgqjfk4lnf69v4tnqpp35qu");
 | 
			
		||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "tr([c55b303f/86'/1'/0']tpubDCiHofpEs47kx358bPdJmTZHmCDqQ8qw32upCSxHrSEdeeBs2T5Mq6QMB2ukeMqhNBiyhosBvJErteVhfURPGXPv3qLJPw5MVpHUewsbP2m/0/*)#dkgvr5hm");
 | 
			
		||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
 | 
			
		||||
/// ```
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct Bip86<K: DerivableKey<Tap>>(pub K, pub KeychainKind);
 | 
			
		||||
 | 
			
		||||
impl<K: DerivableKey<Tap>> DescriptorTemplate for Bip86<K> {
 | 
			
		||||
@ -466,21 +485,23 @@ impl<K: DerivableKey<Tap>> DescriptorTemplate for Bip86<K> {
 | 
			
		||||
/// ```
 | 
			
		||||
/// # use std::str::FromStr;
 | 
			
		||||
/// # use bdk_wallet::bitcoin::{PrivateKey, Network};
 | 
			
		||||
/// # use bdk_wallet::{Wallet,  KeychainKind};
 | 
			
		||||
/// # use bdk_wallet::{Wallet, KeychainKind};
 | 
			
		||||
/// use bdk_wallet::template::Bip86Public;
 | 
			
		||||
///
 | 
			
		||||
/// let key = bitcoin::bip32::Xpub::from_str("tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q")?;
 | 
			
		||||
/// let fingerprint = bitcoin::bip32::Fingerprint::from_str("c55b303f")?;
 | 
			
		||||
/// let mut wallet = Wallet::new(
 | 
			
		||||
/// let mut wallet = Wallet::create(
 | 
			
		||||
///     Bip86Public(key.clone(), fingerprint, KeychainKind::External),
 | 
			
		||||
///     Bip86Public(key, fingerprint, KeychainKind::Internal),
 | 
			
		||||
///     Network::Testnet,
 | 
			
		||||
/// )?;
 | 
			
		||||
/// )
 | 
			
		||||
/// .network(Network::Testnet)
 | 
			
		||||
/// .create_wallet_no_persist()?;
 | 
			
		||||
///
 | 
			
		||||
/// assert_eq!(wallet.next_unused_address(KeychainKind::External).to_string(), "tb1pwjp9f2k5n0xq73ecuu0c5njvgqr3vkh7yaylmpqvsuuaafymh0msvcmh37");
 | 
			
		||||
/// assert_eq!(wallet.public_descriptor(KeychainKind::External).to_string(), "tr([c55b303f/86'/1'/0']tpubDC2Qwo2TFsaNC4ju8nrUJ9mqVT3eSgdmy1yPqhgkjwmke3PRXutNGRYAUo6RCHTcVQaDR3ohNU9we59brGHuEKPvH1ags2nevW5opEE9Z5Q/0/*)#2p65srku");
 | 
			
		||||
/// # Ok::<_, Box<dyn std::error::Error>>(())
 | 
			
		||||
/// ```
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct Bip86Public<K: DerivableKey<Tap>>(pub K, pub bip32::Fingerprint, pub KeychainKind);
 | 
			
		||||
 | 
			
		||||
impl<K: DerivableKey<Tap>> DescriptorTemplate for Bip86Public<K> {
 | 
			
		||||
 | 
			
		||||
@ -935,7 +935,7 @@ impl<Ctx: ScriptContext> IntoDescriptorKey<Ctx> for PrivateKey {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Errors thrown while working with [`keys`](crate::keys)
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
#[derive(Debug, PartialEq)]
 | 
			
		||||
pub enum KeyError {
 | 
			
		||||
    /// The key cannot exist in the given script context
 | 
			
		||||
    InvalidScriptContext,
 | 
			
		||||
 | 
			
		||||
@ -15,33 +15,36 @@ extern crate std;
 | 
			
		||||
#[doc(hidden)]
 | 
			
		||||
#[macro_use]
 | 
			
		||||
pub extern crate alloc;
 | 
			
		||||
 | 
			
		||||
pub extern crate bdk_chain as chain;
 | 
			
		||||
#[cfg(feature = "file_store")]
 | 
			
		||||
pub extern crate bdk_file_store as file_store;
 | 
			
		||||
#[cfg(feature = "keys-bip39")]
 | 
			
		||||
pub extern crate bip39;
 | 
			
		||||
pub extern crate bitcoin;
 | 
			
		||||
pub extern crate miniscript;
 | 
			
		||||
extern crate serde;
 | 
			
		||||
extern crate serde_json;
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "keys-bip39")]
 | 
			
		||||
extern crate bip39;
 | 
			
		||||
pub extern crate serde;
 | 
			
		||||
pub extern crate serde_json;
 | 
			
		||||
 | 
			
		||||
pub mod descriptor;
 | 
			
		||||
pub mod keys;
 | 
			
		||||
pub mod psbt;
 | 
			
		||||
pub(crate) mod types;
 | 
			
		||||
pub mod wallet;
 | 
			
		||||
mod types;
 | 
			
		||||
mod wallet;
 | 
			
		||||
 | 
			
		||||
pub(crate) use bdk_chain::collections;
 | 
			
		||||
#[cfg(feature = "rusqlite")]
 | 
			
		||||
pub use bdk_chain::rusqlite;
 | 
			
		||||
#[cfg(feature = "rusqlite")]
 | 
			
		||||
pub use bdk_chain::rusqlite_impl;
 | 
			
		||||
pub use descriptor::template;
 | 
			
		||||
pub use descriptor::HdKeyPaths;
 | 
			
		||||
pub use signer;
 | 
			
		||||
pub use signer::SignOptions;
 | 
			
		||||
pub use tx_builder::*;
 | 
			
		||||
pub use types::*;
 | 
			
		||||
pub use wallet::signer;
 | 
			
		||||
pub use wallet::signer::SignOptions;
 | 
			
		||||
pub use wallet::tx_builder::TxBuilder;
 | 
			
		||||
pub use wallet::Wallet;
 | 
			
		||||
pub use wallet::*;
 | 
			
		||||
 | 
			
		||||
/// Get the version of BDK at runtime
 | 
			
		||||
/// Get the version of [`bdk_wallet`](crate) at runtime.
 | 
			
		||||
pub fn version() -> &'static str {
 | 
			
		||||
    env!("CARGO_PKG_VERSION", "unknown")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub use bdk_chain as chain;
 | 
			
		||||
pub(crate) use bdk_chain::collections;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										210
									
								
								crates/wallet/src/wallet/changeset.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										210
									
								
								crates/wallet/src/wallet/changeset.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,210 @@
 | 
			
		||||
use bdk_chain::{
 | 
			
		||||
    indexed_tx_graph, keychain_txout, local_chain, tx_graph, ConfirmationBlockTime, Merge,
 | 
			
		||||
};
 | 
			
		||||
use miniscript::{Descriptor, DescriptorPublicKey};
 | 
			
		||||
 | 
			
		||||
type IndexedTxGraphChangeSet =
 | 
			
		||||
    indexed_tx_graph::ChangeSet<ConfirmationBlockTime, keychain_txout::ChangeSet>;
 | 
			
		||||
 | 
			
		||||
/// A changeset for [`Wallet`](crate::Wallet).
 | 
			
		||||
#[derive(Default, Debug, Clone, PartialEq, serde::Deserialize, serde::Serialize)]
 | 
			
		||||
#[non_exhaustive]
 | 
			
		||||
pub struct ChangeSet {
 | 
			
		||||
    /// Descriptor for recipient addresses.
 | 
			
		||||
    pub descriptor: Option<Descriptor<DescriptorPublicKey>>,
 | 
			
		||||
    /// Descriptor for change addresses.
 | 
			
		||||
    pub change_descriptor: Option<Descriptor<DescriptorPublicKey>>,
 | 
			
		||||
    /// Stores the network type of the transaction data.
 | 
			
		||||
    pub network: Option<bitcoin::Network>,
 | 
			
		||||
    /// Changes to the [`LocalChain`](local_chain::LocalChain).
 | 
			
		||||
    pub local_chain: local_chain::ChangeSet,
 | 
			
		||||
    /// Changes to [`TxGraph`](tx_graph::TxGraph).
 | 
			
		||||
    pub tx_graph: tx_graph::ChangeSet<ConfirmationBlockTime>,
 | 
			
		||||
    /// Changes to [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex).
 | 
			
		||||
    pub indexer: keychain_txout::ChangeSet,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Merge for ChangeSet {
 | 
			
		||||
    /// Merge another [`ChangeSet`] into itself.
 | 
			
		||||
    fn merge(&mut self, other: Self) {
 | 
			
		||||
        if other.descriptor.is_some() {
 | 
			
		||||
            debug_assert!(
 | 
			
		||||
                self.descriptor.is_none() || self.descriptor == other.descriptor,
 | 
			
		||||
                "descriptor must never change"
 | 
			
		||||
            );
 | 
			
		||||
            self.descriptor = other.descriptor;
 | 
			
		||||
        }
 | 
			
		||||
        if other.change_descriptor.is_some() {
 | 
			
		||||
            debug_assert!(
 | 
			
		||||
                self.change_descriptor.is_none()
 | 
			
		||||
                    || self.change_descriptor == other.change_descriptor,
 | 
			
		||||
                "change descriptor must never change"
 | 
			
		||||
            );
 | 
			
		||||
            self.change_descriptor = other.change_descriptor;
 | 
			
		||||
        }
 | 
			
		||||
        if other.network.is_some() {
 | 
			
		||||
            debug_assert!(
 | 
			
		||||
                self.network.is_none() || self.network == other.network,
 | 
			
		||||
                "network must never change"
 | 
			
		||||
            );
 | 
			
		||||
            self.network = other.network;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Merge::merge(&mut self.local_chain, other.local_chain);
 | 
			
		||||
        Merge::merge(&mut self.tx_graph, other.tx_graph);
 | 
			
		||||
        Merge::merge(&mut self.indexer, other.indexer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn is_empty(&self) -> bool {
 | 
			
		||||
        self.descriptor.is_none()
 | 
			
		||||
            && self.change_descriptor.is_none()
 | 
			
		||||
            && self.network.is_none()
 | 
			
		||||
            && self.local_chain.is_empty()
 | 
			
		||||
            && self.tx_graph.is_empty()
 | 
			
		||||
            && self.indexer.is_empty()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "rusqlite")]
 | 
			
		||||
impl ChangeSet {
 | 
			
		||||
    /// Schema name for wallet.
 | 
			
		||||
    pub const WALLET_SCHEMA_NAME: &'static str = "bdk_wallet";
 | 
			
		||||
    /// Name of table to store wallet descriptors and network.
 | 
			
		||||
    pub const WALLET_TABLE_NAME: &'static str = "bdk_wallet";
 | 
			
		||||
 | 
			
		||||
    /// Initialize sqlite tables for wallet schema & table.
 | 
			
		||||
    fn init_wallet_sqlite_tables(
 | 
			
		||||
        db_tx: &chain::rusqlite::Transaction,
 | 
			
		||||
    ) -> chain::rusqlite::Result<()> {
 | 
			
		||||
        let schema_v0: &[&str] = &[&format!(
 | 
			
		||||
            "CREATE TABLE {} ( \
 | 
			
		||||
                id INTEGER PRIMARY KEY NOT NULL CHECK (id = 0), \
 | 
			
		||||
                descriptor TEXT, \
 | 
			
		||||
                change_descriptor TEXT, \
 | 
			
		||||
                network TEXT \
 | 
			
		||||
                ) STRICT;",
 | 
			
		||||
            Self::WALLET_TABLE_NAME,
 | 
			
		||||
        )];
 | 
			
		||||
        crate::rusqlite_impl::migrate_schema(db_tx, Self::WALLET_SCHEMA_NAME, &[schema_v0])
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Recover a [`ChangeSet`] from sqlite database.
 | 
			
		||||
    pub fn from_sqlite(db_tx: &chain::rusqlite::Transaction) -> chain::rusqlite::Result<Self> {
 | 
			
		||||
        Self::init_wallet_sqlite_tables(db_tx)?;
 | 
			
		||||
        use chain::rusqlite::OptionalExtension;
 | 
			
		||||
        use chain::Impl;
 | 
			
		||||
        use miniscript::{Descriptor, DescriptorPublicKey};
 | 
			
		||||
 | 
			
		||||
        let mut changeset = Self::default();
 | 
			
		||||
 | 
			
		||||
        let mut wallet_statement = db_tx.prepare(&format!(
 | 
			
		||||
            "SELECT descriptor, change_descriptor, network FROM {}",
 | 
			
		||||
            Self::WALLET_TABLE_NAME,
 | 
			
		||||
        ))?;
 | 
			
		||||
        let row = wallet_statement
 | 
			
		||||
            .query_row([], |row| {
 | 
			
		||||
                Ok((
 | 
			
		||||
                    row.get::<_, Impl<Descriptor<DescriptorPublicKey>>>("descriptor")?,
 | 
			
		||||
                    row.get::<_, Impl<Descriptor<DescriptorPublicKey>>>("change_descriptor")?,
 | 
			
		||||
                    row.get::<_, Impl<bitcoin::Network>>("network")?,
 | 
			
		||||
                ))
 | 
			
		||||
            })
 | 
			
		||||
            .optional()?;
 | 
			
		||||
        if let Some((Impl(desc), Impl(change_desc), Impl(network))) = row {
 | 
			
		||||
            changeset.descriptor = Some(desc);
 | 
			
		||||
            changeset.change_descriptor = Some(change_desc);
 | 
			
		||||
            changeset.network = Some(network);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        changeset.local_chain = local_chain::ChangeSet::from_sqlite(db_tx)?;
 | 
			
		||||
        changeset.tx_graph = tx_graph::ChangeSet::<_>::from_sqlite(db_tx)?;
 | 
			
		||||
        changeset.indexer = keychain_txout::ChangeSet::from_sqlite(db_tx)?;
 | 
			
		||||
 | 
			
		||||
        Ok(changeset)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Persist [`ChangeSet`] to sqlite database.
 | 
			
		||||
    pub fn persist_to_sqlite(
 | 
			
		||||
        &self,
 | 
			
		||||
        db_tx: &chain::rusqlite::Transaction,
 | 
			
		||||
    ) -> chain::rusqlite::Result<()> {
 | 
			
		||||
        Self::init_wallet_sqlite_tables(db_tx)?;
 | 
			
		||||
        use chain::rusqlite::named_params;
 | 
			
		||||
        use chain::Impl;
 | 
			
		||||
 | 
			
		||||
        let mut descriptor_statement = db_tx.prepare_cached(&format!(
 | 
			
		||||
            "INSERT INTO {}(id, descriptor) VALUES(:id, :descriptor) ON CONFLICT(id) DO UPDATE SET descriptor=:descriptor",
 | 
			
		||||
            Self::WALLET_TABLE_NAME,
 | 
			
		||||
        ))?;
 | 
			
		||||
        if let Some(descriptor) = &self.descriptor {
 | 
			
		||||
            descriptor_statement.execute(named_params! {
 | 
			
		||||
                ":id": 0,
 | 
			
		||||
                ":descriptor": Impl(descriptor.clone()),
 | 
			
		||||
            })?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut change_descriptor_statement = db_tx.prepare_cached(&format!(
 | 
			
		||||
            "INSERT INTO {}(id, change_descriptor) VALUES(:id, :change_descriptor) ON CONFLICT(id) DO UPDATE SET change_descriptor=:change_descriptor",
 | 
			
		||||
            Self::WALLET_TABLE_NAME,
 | 
			
		||||
        ))?;
 | 
			
		||||
        if let Some(change_descriptor) = &self.change_descriptor {
 | 
			
		||||
            change_descriptor_statement.execute(named_params! {
 | 
			
		||||
                ":id": 0,
 | 
			
		||||
                ":change_descriptor": Impl(change_descriptor.clone()),
 | 
			
		||||
            })?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mut network_statement = db_tx.prepare_cached(&format!(
 | 
			
		||||
            "INSERT INTO {}(id, network) VALUES(:id, :network) ON CONFLICT(id) DO UPDATE SET network=:network",
 | 
			
		||||
            Self::WALLET_TABLE_NAME,
 | 
			
		||||
        ))?;
 | 
			
		||||
        if let Some(network) = self.network {
 | 
			
		||||
            network_statement.execute(named_params! {
 | 
			
		||||
                ":id": 0,
 | 
			
		||||
                ":network": Impl(network),
 | 
			
		||||
            })?;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.local_chain.persist_to_sqlite(db_tx)?;
 | 
			
		||||
        self.tx_graph.persist_to_sqlite(db_tx)?;
 | 
			
		||||
        self.indexer.persist_to_sqlite(db_tx)?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<local_chain::ChangeSet> for ChangeSet {
 | 
			
		||||
    fn from(chain: local_chain::ChangeSet) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            local_chain: chain,
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<IndexedTxGraphChangeSet> for ChangeSet {
 | 
			
		||||
    fn from(indexed_tx_graph: IndexedTxGraphChangeSet) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            tx_graph: indexed_tx_graph.tx_graph,
 | 
			
		||||
            indexer: indexed_tx_graph.indexer,
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<tx_graph::ChangeSet<ConfirmationBlockTime>> for ChangeSet {
 | 
			
		||||
    fn from(tx_graph: tx_graph::ChangeSet<ConfirmationBlockTime>) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            tx_graph,
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<keychain_txout::ChangeSet> for ChangeSet {
 | 
			
		||||
    fn from(indexer: keychain_txout::ChangeSet) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            indexer,
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -26,10 +26,10 @@
 | 
			
		||||
//! ```
 | 
			
		||||
//! # use std::str::FromStr;
 | 
			
		||||
//! # use bitcoin::*;
 | 
			
		||||
//! # use bdk_wallet::wallet::{self, ChangeSet, coin_selection::*, coin_selection};
 | 
			
		||||
//! # use bdk_wallet::wallet::error::CreateTxError;
 | 
			
		||||
//! # use bdk_wallet::{self, ChangeSet, coin_selection::*, coin_selection};
 | 
			
		||||
//! # use bdk_wallet::error::CreateTxError;
 | 
			
		||||
//! # use bdk_wallet::*;
 | 
			
		||||
//! # use bdk_wallet::wallet::coin_selection::decide_change;
 | 
			
		||||
//! # use bdk_wallet::coin_selection::decide_change;
 | 
			
		||||
//! # use anyhow::Error;
 | 
			
		||||
//! #[derive(Debug)]
 | 
			
		||||
//! struct AlwaysSpendEverything;
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@
 | 
			
		||||
//! ```
 | 
			
		||||
//! # use std::str::FromStr;
 | 
			
		||||
//! # use bitcoin::*;
 | 
			
		||||
//! # use bdk_wallet::wallet::export::*;
 | 
			
		||||
//! # use bdk_wallet::export::*;
 | 
			
		||||
//! # use bdk_wallet::*;
 | 
			
		||||
//! let import = r#"{
 | 
			
		||||
//!     "descriptor": "wpkh([c258d2e4\/84h\/1h\/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe\/0\/*)",
 | 
			
		||||
@ -29,24 +29,26 @@
 | 
			
		||||
//! }"#;
 | 
			
		||||
//!
 | 
			
		||||
//! let import = FullyNodedExport::from_str(import)?;
 | 
			
		||||
//! let wallet = Wallet::new(
 | 
			
		||||
//!     &import.descriptor(),
 | 
			
		||||
//!     &import.change_descriptor().expect("change descriptor"),
 | 
			
		||||
//!     Network::Testnet,
 | 
			
		||||
//! )?;
 | 
			
		||||
//! let wallet = Wallet::create(
 | 
			
		||||
//!     import.descriptor(),
 | 
			
		||||
//!     import.change_descriptor().expect("change descriptor"),
 | 
			
		||||
//! )
 | 
			
		||||
//! .network(Network::Testnet)
 | 
			
		||||
//! .create_wallet_no_persist()?;
 | 
			
		||||
//! # Ok::<_, Box<dyn std::error::Error>>(())
 | 
			
		||||
//! ```
 | 
			
		||||
//!
 | 
			
		||||
//! ### Export a `Wallet`
 | 
			
		||||
//! ```
 | 
			
		||||
//! # use bitcoin::*;
 | 
			
		||||
//! # use bdk_wallet::wallet::export::*;
 | 
			
		||||
//! # use bdk_wallet::export::*;
 | 
			
		||||
//! # use bdk_wallet::*;
 | 
			
		||||
//! let wallet = Wallet::new(
 | 
			
		||||
//! let wallet = Wallet::create(
 | 
			
		||||
//!     "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/0/*)",
 | 
			
		||||
//!     "wpkh([c258d2e4/84h/1h/0h]tpubDD3ynpHgJQW8VvWRzQ5WFDCrs4jqVFGHB3vLC3r49XHJSqP8bHKdK4AriuUKLccK68zfzowx7YhmDN8SiSkgCDENUFx9qVw65YyqM78vyVe/1/*)",
 | 
			
		||||
//!     Network::Testnet,
 | 
			
		||||
//! )?;
 | 
			
		||||
//! )
 | 
			
		||||
//! .network(Network::Testnet)
 | 
			
		||||
//! .create_wallet_no_persist()?;
 | 
			
		||||
//! let export = FullyNodedExport::export_wallet(&wallet, "exported wallet", true).unwrap();
 | 
			
		||||
//!
 | 
			
		||||
//! println!("Exported: {}", export.to_string());
 | 
			
		||||
@ -219,12 +221,15 @@ mod test {
 | 
			
		||||
    use bitcoin::{transaction, BlockHash, Network, Transaction};
 | 
			
		||||
 | 
			
		||||
    use super::*;
 | 
			
		||||
    use crate::wallet::Wallet;
 | 
			
		||||
    use crate::Wallet;
 | 
			
		||||
 | 
			
		||||
    fn get_test_wallet(descriptor: &str, change_descriptor: &str, network: Network) -> Wallet {
 | 
			
		||||
        use crate::wallet::Update;
 | 
			
		||||
        use bdk_chain::TxGraph;
 | 
			
		||||
        let mut wallet = Wallet::new(descriptor, change_descriptor, network).unwrap();
 | 
			
		||||
        let mut wallet = Wallet::create(descriptor.to_string(), change_descriptor.to_string())
 | 
			
		||||
            .network(network)
 | 
			
		||||
            .create_wallet_no_persist()
 | 
			
		||||
            .expect("must create wallet");
 | 
			
		||||
        let transaction = Transaction {
 | 
			
		||||
            input: vec![],
 | 
			
		||||
            output: vec![],
 | 
			
		||||
 | 
			
		||||
@ -16,9 +16,9 @@
 | 
			
		||||
//! ```no_run
 | 
			
		||||
//! # use bdk_wallet::bitcoin::Network;
 | 
			
		||||
//! # use bdk_wallet::signer::SignerOrdering;
 | 
			
		||||
//! # use bdk_wallet::wallet::hardwaresigner::HWISigner;
 | 
			
		||||
//! # use bdk_wallet::wallet::AddressIndex::New;
 | 
			
		||||
//! # use bdk_wallet::{KeychainKind, SignOptions, Wallet};
 | 
			
		||||
//! # use bdk_wallet::hardwaresigner::HWISigner;
 | 
			
		||||
//! # use bdk_wallet::AddressIndex::New;
 | 
			
		||||
//! # use bdk_wallet::{CreateParams, KeychainKind, SignOptions};
 | 
			
		||||
//! # use hwi::HWIClient;
 | 
			
		||||
//! # use std::sync::Arc;
 | 
			
		||||
//! #
 | 
			
		||||
@ -30,11 +30,7 @@
 | 
			
		||||
//! let first_device = devices.remove(0)?;
 | 
			
		||||
//! let custom_signer = HWISigner::from_device(&first_device, Network::Testnet.into())?;
 | 
			
		||||
//!
 | 
			
		||||
//! # let mut wallet = Wallet::new(
 | 
			
		||||
//! #     "",
 | 
			
		||||
//! #     None,
 | 
			
		||||
//! #     Network::Testnet,
 | 
			
		||||
//! # )?;
 | 
			
		||||
//! # let mut wallet = CreateParams::new("", "", Network::Testnet)?.create_wallet_no_persist()?;
 | 
			
		||||
//! #
 | 
			
		||||
//! // Adding the hardware signer to the BDK wallet
 | 
			
		||||
//! wallet.add_signer(
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,10 @@
 | 
			
		||||
//! Wallet
 | 
			
		||||
//!
 | 
			
		||||
//! This module defines the [`Wallet`].
 | 
			
		||||
use crate::collections::{BTreeMap, HashMap};
 | 
			
		||||
use crate::{
 | 
			
		||||
    collections::{BTreeMap, HashMap},
 | 
			
		||||
    descriptor::check_wallet_descriptor,
 | 
			
		||||
};
 | 
			
		||||
use alloc::{
 | 
			
		||||
    boxed::Box,
 | 
			
		||||
    string::{String, ToString},
 | 
			
		||||
@ -28,8 +31,8 @@ use bdk_chain::{
 | 
			
		||||
    },
 | 
			
		||||
    spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult},
 | 
			
		||||
    tx_graph::{CanonicalTx, TxGraph, TxNode},
 | 
			
		||||
    BlockId, ChainPosition, ConfirmationBlockTime, ConfirmationTime, FullTxOut, Indexed,
 | 
			
		||||
    IndexedTxGraph, Merge,
 | 
			
		||||
    BlockId, ChainPosition, ConfirmationBlockTime, ConfirmationTime, DescriptorExt, FullTxOut,
 | 
			
		||||
    Indexed, IndexedTxGraph, Merge,
 | 
			
		||||
};
 | 
			
		||||
use bitcoin::sighash::{EcdsaSighashType, TapSighashType};
 | 
			
		||||
use bitcoin::{
 | 
			
		||||
@ -38,24 +41,31 @@ use bitcoin::{
 | 
			
		||||
};
 | 
			
		||||
use bitcoin::{consensus::encode::serialize, transaction, BlockHash, Psbt};
 | 
			
		||||
use bitcoin::{constants::genesis_block, Amount};
 | 
			
		||||
use bitcoin::{
 | 
			
		||||
    secp256k1::{All, Secp256k1},
 | 
			
		||||
    Weight,
 | 
			
		||||
};
 | 
			
		||||
use bitcoin::{secp256k1::Secp256k1, Weight};
 | 
			
		||||
use chain::Staged;
 | 
			
		||||
use core::fmt;
 | 
			
		||||
use core::mem;
 | 
			
		||||
use core::ops::Deref;
 | 
			
		||||
use rand_core::RngCore;
 | 
			
		||||
 | 
			
		||||
use descriptor::error::Error as DescriptorError;
 | 
			
		||||
use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier};
 | 
			
		||||
use miniscript::{
 | 
			
		||||
    descriptor::KeyMap,
 | 
			
		||||
    psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use bdk_chain::tx_graph::CalculateFeeError;
 | 
			
		||||
 | 
			
		||||
mod changeset;
 | 
			
		||||
pub mod coin_selection;
 | 
			
		||||
pub mod export;
 | 
			
		||||
mod params;
 | 
			
		||||
pub mod signer;
 | 
			
		||||
pub mod tx_builder;
 | 
			
		||||
pub use changeset::*;
 | 
			
		||||
pub use params::*;
 | 
			
		||||
mod persisted;
 | 
			
		||||
pub use persisted::*;
 | 
			
		||||
pub(crate) mod utils;
 | 
			
		||||
 | 
			
		||||
pub mod error;
 | 
			
		||||
@ -69,8 +79,8 @@ use utils::{check_nsequence_rbf, After, Older, SecpCtx};
 | 
			
		||||
 | 
			
		||||
use crate::descriptor::policy::BuildSatisfaction;
 | 
			
		||||
use crate::descriptor::{
 | 
			
		||||
    self, calc_checksum, into_wallet_descriptor_checked, DerivedDescriptor, DescriptorMeta,
 | 
			
		||||
    ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor, Policy, XKeyUtils,
 | 
			
		||||
    self, calc_checksum, DerivedDescriptor, DescriptorMeta, ExtendedDescriptor, ExtractPolicy,
 | 
			
		||||
    IntoWalletDescriptor, Policy, XKeyUtils,
 | 
			
		||||
};
 | 
			
		||||
use crate::psbt::PsbtUtils;
 | 
			
		||||
use crate::signer::SignerError;
 | 
			
		||||
@ -110,6 +120,14 @@ pub struct Wallet {
 | 
			
		||||
    secp: SecpCtx,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Staged for Wallet {
 | 
			
		||||
    type ChangeSet = ChangeSet;
 | 
			
		||||
 | 
			
		||||
    fn staged(&mut self) -> &mut Self::ChangeSet {
 | 
			
		||||
        &mut self.stage
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// An update to [`Wallet`].
 | 
			
		||||
///
 | 
			
		||||
/// It updates [`KeychainTxOutIndex`], [`bdk_chain::TxGraph`] and [`local_chain::LocalChain`] atomically.
 | 
			
		||||
@ -148,9 +166,6 @@ impl From<SyncResult> for Update {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// The changes made to a wallet by applying an [`Update`].
 | 
			
		||||
pub type ChangeSet = bdk_chain::CombinedChangeSet<KeychainKind, ConfirmationBlockTime>;
 | 
			
		||||
 | 
			
		||||
/// A derived address and the index it was found at.
 | 
			
		||||
/// For convenience this automatically derefs to `Address`
 | 
			
		||||
#[derive(Debug, PartialEq, Eq)]
 | 
			
		||||
@ -177,35 +192,8 @@ impl fmt::Display for AddressInfo {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// The error type when constructing a fresh [`Wallet`].
 | 
			
		||||
///
 | 
			
		||||
/// Methods [`new`] and [`new_with_genesis_hash`] may return this error.
 | 
			
		||||
///
 | 
			
		||||
/// [`new`]: Wallet::new
 | 
			
		||||
/// [`new_with_genesis_hash`]: Wallet::new_with_genesis_hash
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub enum NewError {
 | 
			
		||||
    /// There was problem with the passed-in descriptor(s).
 | 
			
		||||
    Descriptor(crate::descriptor::DescriptorError),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl fmt::Display for NewError {
 | 
			
		||||
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            NewError::Descriptor(e) => e.fmt(f),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "std")]
 | 
			
		||||
impl std::error::Error for NewError {}
 | 
			
		||||
 | 
			
		||||
/// The error type when loading a [`Wallet`] from a [`ChangeSet`].
 | 
			
		||||
///
 | 
			
		||||
/// Method [`load_from_changeset`] may return this error.
 | 
			
		||||
///
 | 
			
		||||
/// [`load_from_changeset`]: Wallet::load_from_changeset
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
#[derive(Debug, PartialEq)]
 | 
			
		||||
pub enum LoadError {
 | 
			
		||||
    /// There was a problem with the passed-in descriptor(s).
 | 
			
		||||
    Descriptor(crate::descriptor::DescriptorError),
 | 
			
		||||
@ -215,6 +203,8 @@ pub enum LoadError {
 | 
			
		||||
    MissingGenesis,
 | 
			
		||||
    /// Data loaded from persistence is missing descriptor.
 | 
			
		||||
    MissingDescriptor(KeychainKind),
 | 
			
		||||
    /// Data loaded is unexpected.
 | 
			
		||||
    Mismatch(LoadMismatch),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl fmt::Display for LoadError {
 | 
			
		||||
@ -226,6 +216,7 @@ impl fmt::Display for LoadError {
 | 
			
		||||
            LoadError::MissingDescriptor(k) => {
 | 
			
		||||
                write!(f, "loaded data is missing descriptor for keychain {k:?}")
 | 
			
		||||
            }
 | 
			
		||||
            LoadError::Mismatch(mismatch) => write!(f, "data mismatch: {mismatch:?}"),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -233,62 +224,45 @@ impl fmt::Display for LoadError {
 | 
			
		||||
#[cfg(feature = "std")]
 | 
			
		||||
impl std::error::Error for LoadError {}
 | 
			
		||||
 | 
			
		||||
/// Error type for when we try load a [`Wallet`] from persistence and creating it if non-existent.
 | 
			
		||||
///
 | 
			
		||||
/// Methods [`new_or_load`] and [`new_or_load_with_genesis_hash`] may return this error.
 | 
			
		||||
///
 | 
			
		||||
/// [`new_or_load`]: Wallet::new_or_load
 | 
			
		||||
/// [`new_or_load_with_genesis_hash`]: Wallet::new_or_load_with_genesis_hash
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub enum NewOrLoadError {
 | 
			
		||||
    /// There is a problem with the passed-in descriptor.
 | 
			
		||||
    Descriptor(crate::descriptor::DescriptorError),
 | 
			
		||||
    /// The loaded genesis hash does not match what was provided.
 | 
			
		||||
    LoadedGenesisDoesNotMatch {
 | 
			
		||||
        /// The expected genesis block hash.
 | 
			
		||||
        expected: BlockHash,
 | 
			
		||||
        /// The block hash loaded from persistence.
 | 
			
		||||
        got: Option<BlockHash>,
 | 
			
		||||
    },
 | 
			
		||||
    /// The loaded network type does not match what was provided.
 | 
			
		||||
    LoadedNetworkDoesNotMatch {
 | 
			
		||||
        /// The expected network type.
 | 
			
		||||
/// Represents a mismatch with what is loaded and what is expected from [`LoadParams`].
 | 
			
		||||
#[derive(Debug, PartialEq)]
 | 
			
		||||
pub enum LoadMismatch {
 | 
			
		||||
    /// Network does not match.
 | 
			
		||||
    Network {
 | 
			
		||||
        /// The network that is loaded.
 | 
			
		||||
        loaded: Network,
 | 
			
		||||
        /// The expected network.
 | 
			
		||||
        expected: Network,
 | 
			
		||||
        /// The network type loaded from persistence.
 | 
			
		||||
        got: Option<Network>,
 | 
			
		||||
    },
 | 
			
		||||
    /// The loaded desccriptor does not match what was provided.
 | 
			
		||||
    LoadedDescriptorDoesNotMatch {
 | 
			
		||||
        /// The descriptor loaded from persistence.
 | 
			
		||||
        got: Option<ExtendedDescriptor>,
 | 
			
		||||
        /// The keychain of the descriptor not matching
 | 
			
		||||
    /// Genesis hash does not match.
 | 
			
		||||
    Genesis {
 | 
			
		||||
        /// The genesis hash that is loaded.
 | 
			
		||||
        loaded: BlockHash,
 | 
			
		||||
        /// The expected genesis hash.
 | 
			
		||||
        expected: BlockHash,
 | 
			
		||||
    },
 | 
			
		||||
    /// Descriptor's [`DescriptorId`](bdk_chain::DescriptorId) does not match.
 | 
			
		||||
    Descriptor {
 | 
			
		||||
        /// Keychain identifying the descriptor.
 | 
			
		||||
        keychain: KeychainKind,
 | 
			
		||||
        /// The loaded descriptor.
 | 
			
		||||
        loaded: ExtendedDescriptor,
 | 
			
		||||
        /// The expected descriptor.
 | 
			
		||||
        expected: ExtendedDescriptor,
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl fmt::Display for NewOrLoadError {
 | 
			
		||||
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            NewOrLoadError::Descriptor(e) => e.fmt(f),
 | 
			
		||||
            NewOrLoadError::LoadedGenesisDoesNotMatch { expected, got } => {
 | 
			
		||||
                write!(f, "loaded genesis hash is not {}, got {:?}", expected, got)
 | 
			
		||||
            }
 | 
			
		||||
            NewOrLoadError::LoadedNetworkDoesNotMatch { expected, got } => {
 | 
			
		||||
                write!(f, "loaded network type is not {}, got {:?}", expected, got)
 | 
			
		||||
            }
 | 
			
		||||
            NewOrLoadError::LoadedDescriptorDoesNotMatch { got, keychain } => {
 | 
			
		||||
                write!(
 | 
			
		||||
                    f,
 | 
			
		||||
                    "loaded descriptor is different from what was provided, got {:?} for keychain {:?}",
 | 
			
		||||
                    got, keychain
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
impl From<LoadMismatch> for LoadError {
 | 
			
		||||
    fn from(mismatch: LoadMismatch) -> Self {
 | 
			
		||||
        Self::Mismatch(mismatch)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "std")]
 | 
			
		||||
impl std::error::Error for NewOrLoadError {}
 | 
			
		||||
impl<E> From<LoadMismatch> for LoadWithPersistError<E> {
 | 
			
		||||
    fn from(mismatch: LoadMismatch) -> Self {
 | 
			
		||||
        Self::InvalidChangeSet(LoadError::Mismatch(mismatch))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// An error that may occur when applying a block to [`Wallet`].
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
@ -324,39 +298,82 @@ impl fmt::Display for ApplyBlockError {
 | 
			
		||||
impl std::error::Error for ApplyBlockError {}
 | 
			
		||||
 | 
			
		||||
impl Wallet {
 | 
			
		||||
    /// Initialize an empty [`Wallet`].
 | 
			
		||||
    pub fn new<E: IntoWalletDescriptor>(
 | 
			
		||||
        descriptor: E,
 | 
			
		||||
        change_descriptor: E,
 | 
			
		||||
        network: Network,
 | 
			
		||||
    ) -> Result<Self, NewError> {
 | 
			
		||||
        let genesis_hash = genesis_block(network).block_hash();
 | 
			
		||||
        Self::new_with_genesis_hash(descriptor, change_descriptor, network, genesis_hash)
 | 
			
		||||
    /// Build a new [`Wallet`].
 | 
			
		||||
    ///
 | 
			
		||||
    /// If you have previously created a wallet, use [`load`](Self::load) instead.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Synopsis
 | 
			
		||||
    ///
 | 
			
		||||
    /// ```rust
 | 
			
		||||
    /// # use bdk_wallet::Wallet;
 | 
			
		||||
    /// # use bitcoin::Network;
 | 
			
		||||
    /// # fn main() -> anyhow::Result<()> {
 | 
			
		||||
    /// # const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
 | 
			
		||||
    /// # const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
 | 
			
		||||
    /// // Create a non-persisted wallet.
 | 
			
		||||
    /// let wallet = Wallet::create(EXTERNAL_DESC, INTERNAL_DESC)
 | 
			
		||||
    ///     .network(Network::Testnet)
 | 
			
		||||
    ///     .create_wallet_no_persist()?;
 | 
			
		||||
    ///
 | 
			
		||||
    /// // Create a wallet that is persisted to SQLite database.
 | 
			
		||||
    /// # let temp_dir = tempfile::tempdir().expect("must create tempdir");
 | 
			
		||||
    /// # let file_path = temp_dir.path().join("store.db");
 | 
			
		||||
    /// use bdk_wallet::rusqlite::Connection;
 | 
			
		||||
    /// let mut conn = Connection::open(file_path)?;
 | 
			
		||||
    /// let wallet = Wallet::create(EXTERNAL_DESC, INTERNAL_DESC)
 | 
			
		||||
    ///     .network(Network::Testnet)
 | 
			
		||||
    ///     .create_wallet(&mut conn)?;
 | 
			
		||||
    /// # Ok(())
 | 
			
		||||
    /// # }
 | 
			
		||||
    /// ```
 | 
			
		||||
    pub fn create<D>(descriptor: D, change_descriptor: D) -> CreateParams
 | 
			
		||||
    where
 | 
			
		||||
        D: IntoWalletDescriptor + Clone + 'static,
 | 
			
		||||
    {
 | 
			
		||||
        CreateParams::new(descriptor, change_descriptor)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Initialize an empty [`Wallet`] with a custom genesis hash.
 | 
			
		||||
    /// Create a new [`Wallet`] with given `params`.
 | 
			
		||||
    ///
 | 
			
		||||
    /// This is like [`Wallet::new`] with an additional `genesis_hash` parameter. This is useful
 | 
			
		||||
    /// for syncing from alternative networks.
 | 
			
		||||
    pub fn new_with_genesis_hash<E: IntoWalletDescriptor>(
 | 
			
		||||
        descriptor: E,
 | 
			
		||||
        change_descriptor: E,
 | 
			
		||||
        network: Network,
 | 
			
		||||
        genesis_hash: BlockHash,
 | 
			
		||||
    ) -> Result<Self, NewError> {
 | 
			
		||||
        let secp = Secp256k1::new();
 | 
			
		||||
    /// Refer to [`Wallet::create`] for more.
 | 
			
		||||
    pub fn create_with_params(params: CreateParams) -> Result<Self, DescriptorError> {
 | 
			
		||||
        let secp = SecpCtx::new();
 | 
			
		||||
        let network = params.network;
 | 
			
		||||
        let genesis_hash = params
 | 
			
		||||
            .genesis_hash
 | 
			
		||||
            .unwrap_or(genesis_block(network).block_hash());
 | 
			
		||||
        let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash);
 | 
			
		||||
        let mut index = KeychainTxOutIndex::<KeychainKind>::default();
 | 
			
		||||
 | 
			
		||||
        let (signers, change_signers) =
 | 
			
		||||
            create_signers(&mut index, &secp, descriptor, change_descriptor, network)
 | 
			
		||||
                .map_err(NewError::Descriptor)?;
 | 
			
		||||
        let (descriptor, mut descriptor_keymap) = (params.descriptor)(&secp, network)?;
 | 
			
		||||
        descriptor_keymap.extend(params.descriptor_keymap);
 | 
			
		||||
 | 
			
		||||
        let (change_descriptor, mut change_descriptor_keymap) =
 | 
			
		||||
            (params.change_descriptor)(&secp, network)?;
 | 
			
		||||
        change_descriptor_keymap.extend(params.change_descriptor_keymap);
 | 
			
		||||
 | 
			
		||||
        let signers = Arc::new(SignersContainer::build(
 | 
			
		||||
            descriptor_keymap,
 | 
			
		||||
            &descriptor,
 | 
			
		||||
            &secp,
 | 
			
		||||
        ));
 | 
			
		||||
        let change_signers = Arc::new(SignersContainer::build(
 | 
			
		||||
            change_descriptor_keymap,
 | 
			
		||||
            &change_descriptor,
 | 
			
		||||
            &secp,
 | 
			
		||||
        ));
 | 
			
		||||
        let index = create_indexer(descriptor, change_descriptor, params.lookahead)?;
 | 
			
		||||
 | 
			
		||||
        let descriptor = index.get_descriptor(&KeychainKind::External).cloned();
 | 
			
		||||
        let change_descriptor = index.get_descriptor(&KeychainKind::Internal).cloned();
 | 
			
		||||
        let indexed_graph = IndexedTxGraph::new(index);
 | 
			
		||||
        let indexed_graph_changeset = indexed_graph.initial_changeset();
 | 
			
		||||
 | 
			
		||||
        let staged = ChangeSet {
 | 
			
		||||
            chain: chain_changeset,
 | 
			
		||||
            indexed_tx_graph: indexed_graph.initial_changeset(),
 | 
			
		||||
        let stage = ChangeSet {
 | 
			
		||||
            descriptor,
 | 
			
		||||
            change_descriptor,
 | 
			
		||||
            local_chain: chain_changeset,
 | 
			
		||||
            tx_graph: indexed_graph_changeset.tx_graph,
 | 
			
		||||
            indexer: indexed_graph_changeset.indexer,
 | 
			
		||||
            network: Some(network),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
@ -366,84 +383,149 @@ impl Wallet {
 | 
			
		||||
            network,
 | 
			
		||||
            chain,
 | 
			
		||||
            indexed_graph,
 | 
			
		||||
            stage: staged,
 | 
			
		||||
            stage,
 | 
			
		||||
            secp,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Load [`Wallet`] from the given previously persisted [`ChangeSet`].
 | 
			
		||||
    /// Build [`Wallet`] by loading from persistence or [`ChangeSet`].
 | 
			
		||||
    ///
 | 
			
		||||
    /// Note that the descriptor secret keys are not persisted to the db; this means that after
 | 
			
		||||
    /// calling this method the [`Wallet`] **won't** know the secret keys, and as such, won't be
 | 
			
		||||
    /// able to sign transactions.
 | 
			
		||||
    /// Note that the descriptor secret keys are not persisted to the db. You can either add
 | 
			
		||||
    /// signers after-the-fact with [`Wallet::add_signer`] or [`Wallet::set_keymap`]. Or you can
 | 
			
		||||
    /// add keys when building the wallet using [`LoadParams::keymap`] and/or
 | 
			
		||||
    /// [`LoadParams::descriptors`].
 | 
			
		||||
    ///
 | 
			
		||||
    /// If you wish to use the wallet to sign transactions, you need to add the secret keys
 | 
			
		||||
    /// manually to the [`Wallet`]:
 | 
			
		||||
    /// # Synopsis
 | 
			
		||||
    ///
 | 
			
		||||
    /// ```rust,no_run
 | 
			
		||||
    /// # use bdk_wallet::Wallet;
 | 
			
		||||
    /// # use bdk_wallet::signer::{SignersContainer, SignerOrdering};
 | 
			
		||||
    /// # use bdk_wallet::descriptor::Descriptor;
 | 
			
		||||
    /// # use bitcoin::key::Secp256k1;
 | 
			
		||||
    /// # use bdk_wallet::KeychainKind;
 | 
			
		||||
    /// use bdk_sqlite::{Store, rusqlite::Connection};
 | 
			
		||||
    /// #
 | 
			
		||||
    /// # fn main() -> Result<(), anyhow::Error> {
 | 
			
		||||
    /// # use bdk_wallet::{Wallet, ChangeSet, KeychainKind};
 | 
			
		||||
    /// # use bitcoin::{BlockHash, Network, hashes::Hash};
 | 
			
		||||
    /// # fn main() -> anyhow::Result<()> {
 | 
			
		||||
    /// # const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
 | 
			
		||||
    /// # const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
 | 
			
		||||
    /// # let changeset = ChangeSet::default();
 | 
			
		||||
    /// // Load a wallet from changeset (no persistence).
 | 
			
		||||
    /// let wallet = Wallet::load()
 | 
			
		||||
    ///     .load_wallet_no_persist(changeset)?
 | 
			
		||||
    ///     .expect("must have data to load wallet");
 | 
			
		||||
    ///
 | 
			
		||||
    /// // Load a wallet that is persisted to SQLite database.
 | 
			
		||||
    /// # let temp_dir = tempfile::tempdir().expect("must create tempdir");
 | 
			
		||||
    /// # let file_path = temp_dir.path().join("store.db");
 | 
			
		||||
    /// let conn = Connection::open(file_path).expect("must open connection");
 | 
			
		||||
    /// let mut db = Store::new(conn).expect("must create db");
 | 
			
		||||
    /// let secp = Secp256k1::new();
 | 
			
		||||
    ///
 | 
			
		||||
    /// let (external_descriptor, external_keymap) = Descriptor::parse_descriptor(&secp, "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)").unwrap();
 | 
			
		||||
    /// let (internal_descriptor, internal_keymap) = Descriptor::parse_descriptor(&secp, "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)").unwrap();
 | 
			
		||||
    ///
 | 
			
		||||
    /// let external_signer_container = SignersContainer::build(external_keymap, &external_descriptor, &secp);
 | 
			
		||||
    /// let internal_signer_container = SignersContainer::build(internal_keymap, &internal_descriptor, &secp);
 | 
			
		||||
    /// let changeset = db.read()?.expect("there must be an existing changeset");
 | 
			
		||||
    /// let mut wallet = Wallet::load_from_changeset(changeset)?;
 | 
			
		||||
    ///
 | 
			
		||||
    /// external_signer_container.signers().into_iter()
 | 
			
		||||
    ///     .for_each(|s| wallet.add_signer(KeychainKind::External, SignerOrdering::default(), s.clone()));
 | 
			
		||||
    /// internal_signer_container.signers().into_iter()
 | 
			
		||||
    ///     .for_each(|s| wallet.add_signer(KeychainKind::Internal, SignerOrdering::default(), s.clone()));
 | 
			
		||||
    /// # let external_keymap = Default::default();
 | 
			
		||||
    /// # let internal_keymap = Default::default();
 | 
			
		||||
    /// # let genesis_hash = BlockHash::all_zeros();
 | 
			
		||||
    /// let mut conn = bdk_wallet::rusqlite::Connection::open(file_path)?;
 | 
			
		||||
    /// let mut wallet = Wallet::load()
 | 
			
		||||
    ///     // check loaded descriptors matches these values and extract private keys
 | 
			
		||||
    ///     .descriptors(EXTERNAL_DESC, INTERNAL_DESC)
 | 
			
		||||
    ///     // you can also manually add private keys
 | 
			
		||||
    ///     .keymap(KeychainKind::External, external_keymap)
 | 
			
		||||
    ///     .keymap(KeychainKind::Internal, internal_keymap)
 | 
			
		||||
    ///     // ensure loaded wallet's genesis hash matches this value
 | 
			
		||||
    ///     .genesis_hash(genesis_hash)
 | 
			
		||||
    ///     // set a lookahead for our indexer
 | 
			
		||||
    ///     .lookahead(101)
 | 
			
		||||
    ///     .load_wallet(&mut conn)?
 | 
			
		||||
    ///     .expect("must have data to load wallet");
 | 
			
		||||
    /// # Ok(())
 | 
			
		||||
    /// # }
 | 
			
		||||
    /// ```
 | 
			
		||||
    pub fn load() -> LoadParams {
 | 
			
		||||
        LoadParams::new()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Load [`Wallet`] from the given previously persisted [`ChangeSet`] and `params`.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Alternatively, you can call [`Wallet::new_or_load`], which will add the private keys of the
 | 
			
		||||
    /// passed-in descriptors to the [`Wallet`].
 | 
			
		||||
    pub fn load_from_changeset(changeset: ChangeSet) -> Result<Self, LoadError> {
 | 
			
		||||
    /// Refer to [`Wallet::load`] for more.
 | 
			
		||||
    pub fn load_with_params(
 | 
			
		||||
        changeset: ChangeSet,
 | 
			
		||||
        params: LoadParams,
 | 
			
		||||
    ) -> Result<Option<Self>, LoadError> {
 | 
			
		||||
        if changeset.is_empty() {
 | 
			
		||||
            return Ok(None);
 | 
			
		||||
        }
 | 
			
		||||
        let secp = Secp256k1::new();
 | 
			
		||||
        let network = changeset.network.ok_or(LoadError::MissingNetwork)?;
 | 
			
		||||
        let chain =
 | 
			
		||||
            LocalChain::from_changeset(changeset.chain).map_err(|_| LoadError::MissingGenesis)?;
 | 
			
		||||
        let mut index = KeychainTxOutIndex::<KeychainKind>::default();
 | 
			
		||||
        let descriptor = changeset
 | 
			
		||||
            .indexed_tx_graph
 | 
			
		||||
            .indexer
 | 
			
		||||
            .keychains_added
 | 
			
		||||
            .get(&KeychainKind::External)
 | 
			
		||||
            .ok_or(LoadError::MissingDescriptor(KeychainKind::External))?
 | 
			
		||||
            .clone();
 | 
			
		||||
        let change_descriptor = changeset
 | 
			
		||||
            .indexed_tx_graph
 | 
			
		||||
            .indexer
 | 
			
		||||
            .keychains_added
 | 
			
		||||
            .get(&KeychainKind::Internal)
 | 
			
		||||
            .ok_or(LoadError::MissingDescriptor(KeychainKind::Internal))?
 | 
			
		||||
            .clone();
 | 
			
		||||
        let chain = LocalChain::from_changeset(changeset.local_chain)
 | 
			
		||||
            .map_err(|_| LoadError::MissingGenesis)?;
 | 
			
		||||
 | 
			
		||||
        let (signers, change_signers) =
 | 
			
		||||
            create_signers(&mut index, &secp, descriptor, change_descriptor, network)
 | 
			
		||||
                .expect("Can't fail: we passed in valid descriptors, recovered from the changeset");
 | 
			
		||||
        let mut descriptor_keymap = params.descriptor_keymap;
 | 
			
		||||
        let descriptor = changeset
 | 
			
		||||
            .descriptor
 | 
			
		||||
            .ok_or(LoadError::MissingDescriptor(KeychainKind::External))?;
 | 
			
		||||
        check_wallet_descriptor(&descriptor).map_err(LoadError::Descriptor)?;
 | 
			
		||||
 | 
			
		||||
        let mut change_descriptor_keymap = params.change_descriptor_keymap;
 | 
			
		||||
        let change_descriptor = changeset
 | 
			
		||||
            .change_descriptor
 | 
			
		||||
            .ok_or(LoadError::MissingDescriptor(KeychainKind::Internal))?;
 | 
			
		||||
        check_wallet_descriptor(&change_descriptor).map_err(LoadError::Descriptor)?;
 | 
			
		||||
 | 
			
		||||
        // checks
 | 
			
		||||
        if let Some(exp_network) = params.check_network {
 | 
			
		||||
            if network != exp_network {
 | 
			
		||||
                return Err(LoadError::Mismatch(LoadMismatch::Network {
 | 
			
		||||
                    loaded: network,
 | 
			
		||||
                    expected: exp_network,
 | 
			
		||||
                }));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if let Some(exp_genesis_hash) = params.check_genesis_hash {
 | 
			
		||||
            if chain.genesis_hash() != exp_genesis_hash {
 | 
			
		||||
                return Err(LoadError::Mismatch(LoadMismatch::Genesis {
 | 
			
		||||
                    loaded: chain.genesis_hash(),
 | 
			
		||||
                    expected: exp_genesis_hash,
 | 
			
		||||
                }));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if let Some(exp_descriptor) = params.check_descriptor {
 | 
			
		||||
            let (exp_descriptor, keymap) =
 | 
			
		||||
                (exp_descriptor)(&secp, network).map_err(LoadError::Descriptor)?;
 | 
			
		||||
            descriptor_keymap.extend(keymap);
 | 
			
		||||
 | 
			
		||||
            if descriptor.descriptor_id() != exp_descriptor.descriptor_id() {
 | 
			
		||||
                return Err(LoadError::Mismatch(LoadMismatch::Descriptor {
 | 
			
		||||
                    keychain: KeychainKind::External,
 | 
			
		||||
                    loaded: descriptor,
 | 
			
		||||
                    expected: exp_descriptor,
 | 
			
		||||
                }));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if let Some(exp_change_descriptor) = params.check_change_descriptor {
 | 
			
		||||
            let (exp_change_descriptor, keymap) =
 | 
			
		||||
                (exp_change_descriptor)(&secp, network).map_err(LoadError::Descriptor)?;
 | 
			
		||||
            change_descriptor_keymap.extend(keymap);
 | 
			
		||||
 | 
			
		||||
            if change_descriptor.descriptor_id() != exp_change_descriptor.descriptor_id() {
 | 
			
		||||
                return Err(LoadError::Mismatch(LoadMismatch::Descriptor {
 | 
			
		||||
                    keychain: KeychainKind::Internal,
 | 
			
		||||
                    loaded: change_descriptor,
 | 
			
		||||
                    expected: exp_change_descriptor,
 | 
			
		||||
                }));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let signers = Arc::new(SignersContainer::build(
 | 
			
		||||
            descriptor_keymap,
 | 
			
		||||
            &descriptor,
 | 
			
		||||
            &secp,
 | 
			
		||||
        ));
 | 
			
		||||
        let change_signers = Arc::new(SignersContainer::build(
 | 
			
		||||
            change_descriptor_keymap,
 | 
			
		||||
            &change_descriptor,
 | 
			
		||||
            &secp,
 | 
			
		||||
        ));
 | 
			
		||||
        let index = create_indexer(descriptor, change_descriptor, params.lookahead)
 | 
			
		||||
            .map_err(LoadError::Descriptor)?;
 | 
			
		||||
 | 
			
		||||
        let mut indexed_graph = IndexedTxGraph::new(index);
 | 
			
		||||
        indexed_graph.apply_changeset(changeset.indexed_tx_graph);
 | 
			
		||||
        indexed_graph.apply_changeset(changeset.indexer.into());
 | 
			
		||||
        indexed_graph.apply_changeset(changeset.tx_graph.into());
 | 
			
		||||
 | 
			
		||||
        let stage = ChangeSet::default();
 | 
			
		||||
 | 
			
		||||
        Ok(Wallet {
 | 
			
		||||
        Ok(Some(Wallet {
 | 
			
		||||
            signers,
 | 
			
		||||
            change_signers,
 | 
			
		||||
            chain,
 | 
			
		||||
@ -451,146 +533,7 @@ impl Wallet {
 | 
			
		||||
            stage,
 | 
			
		||||
            network,
 | 
			
		||||
            secp,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Either loads [`Wallet`] from the given [`ChangeSet`] or initializes it if one does not exist.
 | 
			
		||||
    ///
 | 
			
		||||
    /// This method will fail if the loaded [`ChangeSet`] has different parameters to those provided.
 | 
			
		||||
    ///
 | 
			
		||||
    /// ```rust,no_run
 | 
			
		||||
    /// # use bdk_wallet::Wallet;
 | 
			
		||||
    /// use bdk_sqlite::{Store, rusqlite::Connection};
 | 
			
		||||
    /// # use bitcoin::Network::Testnet;
 | 
			
		||||
    /// let conn = Connection::open_in_memory().expect("must open connection");
 | 
			
		||||
    /// let mut db = Store::new(conn).expect("must create db");
 | 
			
		||||
    /// let changeset = db.read()?;
 | 
			
		||||
    ///
 | 
			
		||||
    /// let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
 | 
			
		||||
    /// let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
 | 
			
		||||
    ///
 | 
			
		||||
    /// let mut wallet = Wallet::new_or_load(external_descriptor, internal_descriptor, changeset, Testnet)?;
 | 
			
		||||
    /// # Ok::<(), anyhow::Error>(())
 | 
			
		||||
    /// ```
 | 
			
		||||
    pub fn new_or_load<E: IntoWalletDescriptor>(
 | 
			
		||||
        descriptor: E,
 | 
			
		||||
        change_descriptor: E,
 | 
			
		||||
        changeset: Option<ChangeSet>,
 | 
			
		||||
        network: Network,
 | 
			
		||||
    ) -> Result<Self, NewOrLoadError> {
 | 
			
		||||
        let genesis_hash = genesis_block(network).block_hash();
 | 
			
		||||
        Self::new_or_load_with_genesis_hash(
 | 
			
		||||
            descriptor,
 | 
			
		||||
            change_descriptor,
 | 
			
		||||
            changeset,
 | 
			
		||||
            network,
 | 
			
		||||
            genesis_hash,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Either loads [`Wallet`] from a [`ChangeSet`] or initializes it if one does not exist, using the
 | 
			
		||||
    /// provided descriptor, change descriptor, network, and custom genesis hash.
 | 
			
		||||
    ///
 | 
			
		||||
    /// This method will fail if the loaded [`ChangeSet`] has different parameters to those provided.
 | 
			
		||||
    /// This is like [`Wallet::new_or_load`] with an additional `genesis_hash` parameter. This is
 | 
			
		||||
    /// useful for syncing from alternative networks.
 | 
			
		||||
    pub fn new_or_load_with_genesis_hash<E: IntoWalletDescriptor>(
 | 
			
		||||
        descriptor: E,
 | 
			
		||||
        change_descriptor: E,
 | 
			
		||||
        changeset: Option<ChangeSet>,
 | 
			
		||||
        network: Network,
 | 
			
		||||
        genesis_hash: BlockHash,
 | 
			
		||||
    ) -> Result<Self, NewOrLoadError> {
 | 
			
		||||
        if let Some(changeset) = changeset {
 | 
			
		||||
            let mut wallet = Self::load_from_changeset(changeset).map_err(|e| match e {
 | 
			
		||||
                LoadError::Descriptor(e) => NewOrLoadError::Descriptor(e),
 | 
			
		||||
                LoadError::MissingNetwork => NewOrLoadError::LoadedNetworkDoesNotMatch {
 | 
			
		||||
                    expected: network,
 | 
			
		||||
                    got: None,
 | 
			
		||||
                },
 | 
			
		||||
                LoadError::MissingGenesis => NewOrLoadError::LoadedGenesisDoesNotMatch {
 | 
			
		||||
                    expected: genesis_hash,
 | 
			
		||||
                    got: None,
 | 
			
		||||
                },
 | 
			
		||||
                LoadError::MissingDescriptor(keychain) => {
 | 
			
		||||
                    NewOrLoadError::LoadedDescriptorDoesNotMatch {
 | 
			
		||||
                        got: None,
 | 
			
		||||
                        keychain,
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            })?;
 | 
			
		||||
            if wallet.network != network {
 | 
			
		||||
                return Err(NewOrLoadError::LoadedNetworkDoesNotMatch {
 | 
			
		||||
                    expected: network,
 | 
			
		||||
                    got: Some(wallet.network),
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            if wallet.chain.genesis_hash() != genesis_hash {
 | 
			
		||||
                return Err(NewOrLoadError::LoadedGenesisDoesNotMatch {
 | 
			
		||||
                    expected: genesis_hash,
 | 
			
		||||
                    got: Some(wallet.chain.genesis_hash()),
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let (expected_descriptor, expected_descriptor_keymap) = descriptor
 | 
			
		||||
                .into_wallet_descriptor(&wallet.secp, network)
 | 
			
		||||
                .map_err(NewOrLoadError::Descriptor)?;
 | 
			
		||||
            let wallet_descriptor = wallet.public_descriptor(KeychainKind::External);
 | 
			
		||||
            if wallet_descriptor != &expected_descriptor {
 | 
			
		||||
                return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch {
 | 
			
		||||
                    got: Some(wallet_descriptor.clone()),
 | 
			
		||||
                    keychain: KeychainKind::External,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            // if expected descriptor has private keys add them as new signers
 | 
			
		||||
            if !expected_descriptor_keymap.is_empty() {
 | 
			
		||||
                let signer_container = SignersContainer::build(
 | 
			
		||||
                    expected_descriptor_keymap,
 | 
			
		||||
                    &expected_descriptor,
 | 
			
		||||
                    &wallet.secp,
 | 
			
		||||
                );
 | 
			
		||||
                signer_container.signers().into_iter().for_each(|signer| {
 | 
			
		||||
                    wallet.add_signer(
 | 
			
		||||
                        KeychainKind::External,
 | 
			
		||||
                        SignerOrdering::default(),
 | 
			
		||||
                        signer.clone(),
 | 
			
		||||
                    )
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            let (expected_change_descriptor, expected_change_descriptor_keymap) = change_descriptor
 | 
			
		||||
                .into_wallet_descriptor(&wallet.secp, network)
 | 
			
		||||
                .map_err(NewOrLoadError::Descriptor)?;
 | 
			
		||||
            let wallet_change_descriptor = wallet.public_descriptor(KeychainKind::Internal);
 | 
			
		||||
            if wallet_change_descriptor != &expected_change_descriptor {
 | 
			
		||||
                return Err(NewOrLoadError::LoadedDescriptorDoesNotMatch {
 | 
			
		||||
                    got: Some(wallet_change_descriptor.clone()),
 | 
			
		||||
                    keychain: KeychainKind::Internal,
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
            // if expected change descriptor has private keys add them as new signers
 | 
			
		||||
            if !expected_change_descriptor_keymap.is_empty() {
 | 
			
		||||
                let signer_container = SignersContainer::build(
 | 
			
		||||
                    expected_change_descriptor_keymap,
 | 
			
		||||
                    &expected_change_descriptor,
 | 
			
		||||
                    &wallet.secp,
 | 
			
		||||
                );
 | 
			
		||||
                signer_container.signers().into_iter().for_each(|signer| {
 | 
			
		||||
                    wallet.add_signer(
 | 
			
		||||
                        KeychainKind::Internal,
 | 
			
		||||
                        SignerOrdering::default(),
 | 
			
		||||
                        signer.clone(),
 | 
			
		||||
                    )
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Ok(wallet)
 | 
			
		||||
        } else {
 | 
			
		||||
            Self::new_with_genesis_hash(descriptor, change_descriptor, network, genesis_hash)
 | 
			
		||||
                .map_err(|e| match e {
 | 
			
		||||
                    NewError::Descriptor(e) => NewOrLoadError::Descriptor(e),
 | 
			
		||||
                })
 | 
			
		||||
        }
 | 
			
		||||
        }))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get the Bitcoin network the wallet is using.
 | 
			
		||||
@ -642,17 +585,15 @@ impl Wallet {
 | 
			
		||||
    /// calls to this method before closing the wallet. For example:
 | 
			
		||||
    ///
 | 
			
		||||
    /// ```rust,no_run
 | 
			
		||||
    /// # use bdk_wallet::wallet::{Wallet, ChangeSet};
 | 
			
		||||
    /// # use bdk_wallet::KeychainKind;
 | 
			
		||||
    /// use bdk_sqlite::{rusqlite::Connection, Store};
 | 
			
		||||
    /// let conn = Connection::open_in_memory().expect("must open connection");
 | 
			
		||||
    /// let mut db = Store::new(conn).expect("must create store");
 | 
			
		||||
    /// # let changeset = ChangeSet::default();
 | 
			
		||||
    /// # let mut wallet = Wallet::load_from_changeset(changeset).expect("load wallet");
 | 
			
		||||
    /// # use bdk_wallet::{LoadParams, ChangeSet, KeychainKind};
 | 
			
		||||
    /// use bdk_chain::rusqlite::Connection;
 | 
			
		||||
    /// let mut conn = Connection::open_in_memory().expect("must open connection");
 | 
			
		||||
    /// let mut wallet = LoadParams::new()
 | 
			
		||||
    ///     .load_wallet(&mut conn)
 | 
			
		||||
    ///     .expect("database is okay")
 | 
			
		||||
    ///     .expect("database has data");
 | 
			
		||||
    /// let next_address = wallet.reveal_next_address(KeychainKind::External);
 | 
			
		||||
    /// if let Some(changeset) = wallet.take_staged() {
 | 
			
		||||
    ///     db.write(&changeset)?;
 | 
			
		||||
    /// }
 | 
			
		||||
    /// wallet.persist(&mut conn).expect("write is okay");
 | 
			
		||||
    ///
 | 
			
		||||
    /// // Now it's safe to show the user their next address!
 | 
			
		||||
    /// println!("Next address: {}", next_address.address);
 | 
			
		||||
@ -666,7 +607,7 @@ impl Wallet {
 | 
			
		||||
            .reveal_next_spk(&keychain)
 | 
			
		||||
            .expect("keychain must exist");
 | 
			
		||||
 | 
			
		||||
        stage.merge(indexed_tx_graph::ChangeSet::from(index_changeset).into());
 | 
			
		||||
        stage.merge(index_changeset.into());
 | 
			
		||||
 | 
			
		||||
        AddressInfo {
 | 
			
		||||
            index,
 | 
			
		||||
@ -1110,6 +1051,27 @@ impl Wallet {
 | 
			
		||||
        signers.add_external(signer.id(&self.secp), ordering, signer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the keymap for a given keychain.
 | 
			
		||||
    pub fn set_keymap(&mut self, keychain: KeychainKind, keymap: KeyMap) {
 | 
			
		||||
        let wallet_signers = match keychain {
 | 
			
		||||
            KeychainKind::External => Arc::make_mut(&mut self.signers),
 | 
			
		||||
            KeychainKind::Internal => Arc::make_mut(&mut self.change_signers),
 | 
			
		||||
        };
 | 
			
		||||
        let descriptor = self
 | 
			
		||||
            .indexed_graph
 | 
			
		||||
            .index
 | 
			
		||||
            .get_descriptor(&keychain)
 | 
			
		||||
            .expect("keychain must exist");
 | 
			
		||||
        *wallet_signers = SignersContainer::build(keymap, descriptor, &self.secp);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set the keymap for each keychain.
 | 
			
		||||
    pub fn set_keymaps(&mut self, keymaps: impl IntoIterator<Item = (KeychainKind, KeyMap)>) {
 | 
			
		||||
        for (keychain, keymap) in keymaps {
 | 
			
		||||
            self.set_keymap(keychain, keymap);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get the signers
 | 
			
		||||
    ///
 | 
			
		||||
    /// ## Example
 | 
			
		||||
@ -1119,7 +1081,9 @@ impl Wallet {
 | 
			
		||||
    /// # use bdk_wallet::bitcoin::Network;
 | 
			
		||||
    /// let descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/0/*)";
 | 
			
		||||
    /// let change_descriptor = "wpkh(tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/1'/0'/1/*)";
 | 
			
		||||
    /// let wallet = Wallet::new(descriptor, change_descriptor, Network::Testnet)?;
 | 
			
		||||
    /// let wallet = Wallet::create(descriptor, change_descriptor)
 | 
			
		||||
    ///     .network(Network::Testnet)
 | 
			
		||||
    ///     .create_wallet_no_persist()?;
 | 
			
		||||
    /// for secret_key in wallet.get_signers(KeychainKind::External).signers().iter().filter_map(|s| s.descriptor_secret_key()) {
 | 
			
		||||
    ///     // secret_key: tprv8ZgxMBicQKsPe73PBRSmNbTfbcsZnwWhz5eVmhHpi31HW29Z7mc9B4cWGRQzopNUzZUT391DeDJxL2PefNunWyLgqCKRMDkU1s2s8bAfoSk/84'/0'/0'/0/*
 | 
			
		||||
    ///     println!("secret_key: {}", secret_key);
 | 
			
		||||
@ -1144,8 +1108,8 @@ impl Wallet {
 | 
			
		||||
    /// # use std::str::FromStr;
 | 
			
		||||
    /// # use bitcoin::*;
 | 
			
		||||
    /// # use bdk_wallet::*;
 | 
			
		||||
    /// # use bdk_wallet::wallet::ChangeSet;
 | 
			
		||||
    /// # use bdk_wallet::wallet::error::CreateTxError;
 | 
			
		||||
    /// # use bdk_wallet::ChangeSet;
 | 
			
		||||
    /// # use bdk_wallet::error::CreateTxError;
 | 
			
		||||
    /// # use anyhow::Error;
 | 
			
		||||
    /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
 | 
			
		||||
    /// # let mut wallet = doctest_wallet!();
 | 
			
		||||
@ -1508,8 +1472,8 @@ impl Wallet {
 | 
			
		||||
    /// # use std::str::FromStr;
 | 
			
		||||
    /// # use bitcoin::*;
 | 
			
		||||
    /// # use bdk_wallet::*;
 | 
			
		||||
    /// # use bdk_wallet::wallet::ChangeSet;
 | 
			
		||||
    /// # use bdk_wallet::wallet::error::CreateTxError;
 | 
			
		||||
    /// # use bdk_wallet::ChangeSet;
 | 
			
		||||
    /// # use bdk_wallet::error::CreateTxError;
 | 
			
		||||
    /// # use anyhow::Error;
 | 
			
		||||
    /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
 | 
			
		||||
    /// # let mut wallet = doctest_wallet!();
 | 
			
		||||
@ -1686,8 +1650,8 @@ impl Wallet {
 | 
			
		||||
    /// # use std::str::FromStr;
 | 
			
		||||
    /// # use bitcoin::*;
 | 
			
		||||
    /// # use bdk_wallet::*;
 | 
			
		||||
    /// # use bdk_wallet::wallet::ChangeSet;
 | 
			
		||||
    /// # use bdk_wallet::wallet::error::CreateTxError;
 | 
			
		||||
    /// # use bdk_wallet::ChangeSet;
 | 
			
		||||
    /// # use bdk_wallet::error::CreateTxError;
 | 
			
		||||
    /// # let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/*)";
 | 
			
		||||
    /// # let mut wallet = doctest_wallet!();
 | 
			
		||||
    /// # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
 | 
			
		||||
@ -2424,25 +2388,23 @@ fn new_local_utxo(
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn create_signers<E: IntoWalletDescriptor>(
 | 
			
		||||
    index: &mut KeychainTxOutIndex<KeychainKind>,
 | 
			
		||||
    secp: &Secp256k1<All>,
 | 
			
		||||
    descriptor: E,
 | 
			
		||||
    change_descriptor: E,
 | 
			
		||||
    network: Network,
 | 
			
		||||
) -> Result<(Arc<SignersContainer>, Arc<SignersContainer>), DescriptorError> {
 | 
			
		||||
    let descriptor = into_wallet_descriptor_checked(descriptor, secp, network)?;
 | 
			
		||||
    let change_descriptor = into_wallet_descriptor_checked(change_descriptor, secp, network)?;
 | 
			
		||||
    let (descriptor, keymap) = descriptor;
 | 
			
		||||
    let signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
 | 
			
		||||
    let _ = index
 | 
			
		||||
        .insert_descriptor(KeychainKind::External, descriptor)
 | 
			
		||||
        .expect("this is the first descriptor we're inserting");
 | 
			
		||||
fn create_indexer(
 | 
			
		||||
    descriptor: ExtendedDescriptor,
 | 
			
		||||
    change_descriptor: ExtendedDescriptor,
 | 
			
		||||
    lookahead: u32,
 | 
			
		||||
) -> Result<KeychainTxOutIndex<KeychainKind>, DescriptorError> {
 | 
			
		||||
    let mut indexer = KeychainTxOutIndex::<KeychainKind>::new(lookahead);
 | 
			
		||||
 | 
			
		||||
    let (descriptor, keymap) = change_descriptor;
 | 
			
		||||
    let change_signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
 | 
			
		||||
    let _ = index
 | 
			
		||||
        .insert_descriptor(KeychainKind::Internal, descriptor)
 | 
			
		||||
    // let (descriptor, keymap) = descriptor;
 | 
			
		||||
    // let signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
 | 
			
		||||
    assert!(indexer
 | 
			
		||||
        .insert_descriptor(KeychainKind::External, descriptor)
 | 
			
		||||
        .expect("first descriptor introduced must succeed"));
 | 
			
		||||
 | 
			
		||||
    // let (descriptor, keymap) = change_descriptor;
 | 
			
		||||
    // let change_signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
 | 
			
		||||
    assert!(indexer
 | 
			
		||||
        .insert_descriptor(KeychainKind::Internal, change_descriptor)
 | 
			
		||||
        .map_err(|e| {
 | 
			
		||||
            use bdk_chain::indexer::keychain_txout::InsertDescriptorError;
 | 
			
		||||
            match e {
 | 
			
		||||
@ -2453,9 +2415,9 @@ fn create_signers<E: IntoWalletDescriptor>(
 | 
			
		||||
                    unreachable!("this is the first time we're assigning internal")
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        })?;
 | 
			
		||||
        })?);
 | 
			
		||||
 | 
			
		||||
    Ok((signers, change_signers))
 | 
			
		||||
    Ok(indexer)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Transforms a [`FeeRate`] to `f64` with unit as sat/vb.
 | 
			
		||||
@ -2476,17 +2438,14 @@ macro_rules! doctest_wallet {
 | 
			
		||||
    () => {{
 | 
			
		||||
        use $crate::bitcoin::{BlockHash, Transaction, absolute, TxOut, Network, hashes::Hash};
 | 
			
		||||
        use $crate::chain::{ConfirmationBlockTime, BlockId, TxGraph};
 | 
			
		||||
        use $crate::wallet::{Update, Wallet};
 | 
			
		||||
        use $crate::KeychainKind;
 | 
			
		||||
        use $crate::{Update, KeychainKind, Wallet};
 | 
			
		||||
        let descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/0/*)";
 | 
			
		||||
        let change_descriptor = "tr([73c5da0a/86'/0'/0']tprv8fMn4hSKPRC1oaCPqxDb1JWtgkpeiQvZhsr8W2xuy3GEMkzoArcAWTfJxYb6Wj8XNNDWEjfYKK4wGQXh3ZUXhDF2NcnsALpWTeSwarJt7Vc/1/*)";
 | 
			
		||||
 | 
			
		||||
        let mut wallet = Wallet::new(
 | 
			
		||||
            descriptor,
 | 
			
		||||
            change_descriptor,
 | 
			
		||||
            Network::Regtest,
 | 
			
		||||
        )
 | 
			
		||||
        .unwrap();
 | 
			
		||||
        let mut wallet = Wallet::create(descriptor, change_descriptor)
 | 
			
		||||
            .network(Network::Regtest)
 | 
			
		||||
            .create_wallet_no_persist()
 | 
			
		||||
            .unwrap();
 | 
			
		||||
        let address = wallet.peek_address(KeychainKind::External, 0).address;
 | 
			
		||||
        let tx = Transaction {
 | 
			
		||||
            version: transaction::Version::ONE,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										213
									
								
								crates/wallet/src/wallet/params.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										213
									
								
								crates/wallet/src/wallet/params.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,213 @@
 | 
			
		||||
use alloc::boxed::Box;
 | 
			
		||||
use bdk_chain::{keychain_txout::DEFAULT_LOOKAHEAD, PersistAsyncWith, PersistWith};
 | 
			
		||||
use bitcoin::{BlockHash, Network};
 | 
			
		||||
use miniscript::descriptor::KeyMap;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    descriptor::{DescriptorError, ExtendedDescriptor, IntoWalletDescriptor},
 | 
			
		||||
    utils::SecpCtx,
 | 
			
		||||
    KeychainKind, Wallet,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::{ChangeSet, LoadError, PersistedWallet};
 | 
			
		||||
 | 
			
		||||
/// This atrocity is to avoid having type parameters on [`CreateParams`] and [`LoadParams`].
 | 
			
		||||
///
 | 
			
		||||
/// The better option would be to do `Box<dyn IntoWalletDescriptor>`, but we cannot due to Rust's
 | 
			
		||||
/// [object safety rules](https://doc.rust-lang.org/reference/items/traits.html#object-safety).
 | 
			
		||||
type DescriptorToExtract = Box<
 | 
			
		||||
    dyn FnOnce(&SecpCtx, Network) -> Result<(ExtendedDescriptor, KeyMap), DescriptorError>
 | 
			
		||||
        + 'static,
 | 
			
		||||
>;
 | 
			
		||||
 | 
			
		||||
fn make_descriptor_to_extract<D>(descriptor: D) -> DescriptorToExtract
 | 
			
		||||
where
 | 
			
		||||
    D: IntoWalletDescriptor + 'static,
 | 
			
		||||
{
 | 
			
		||||
    Box::new(|secp, network| descriptor.into_wallet_descriptor(secp, network))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Parameters for [`Wallet::create`] or [`PersistedWallet::create`].
 | 
			
		||||
#[must_use]
 | 
			
		||||
pub struct CreateParams {
 | 
			
		||||
    pub(crate) descriptor: DescriptorToExtract,
 | 
			
		||||
    pub(crate) descriptor_keymap: KeyMap,
 | 
			
		||||
    pub(crate) change_descriptor: DescriptorToExtract,
 | 
			
		||||
    pub(crate) change_descriptor_keymap: KeyMap,
 | 
			
		||||
    pub(crate) network: Network,
 | 
			
		||||
    pub(crate) genesis_hash: Option<BlockHash>,
 | 
			
		||||
    pub(crate) lookahead: u32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl CreateParams {
 | 
			
		||||
    /// Construct parameters with provided `descriptor`, `change_descriptor` and `network`.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Default values: `genesis_hash` = `None`, `lookahead` = [`DEFAULT_LOOKAHEAD`]
 | 
			
		||||
    pub fn new<D: IntoWalletDescriptor + 'static>(descriptor: D, change_descriptor: D) -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            descriptor: make_descriptor_to_extract(descriptor),
 | 
			
		||||
            descriptor_keymap: KeyMap::default(),
 | 
			
		||||
            change_descriptor: make_descriptor_to_extract(change_descriptor),
 | 
			
		||||
            change_descriptor_keymap: KeyMap::default(),
 | 
			
		||||
            network: Network::Bitcoin,
 | 
			
		||||
            genesis_hash: None,
 | 
			
		||||
            lookahead: DEFAULT_LOOKAHEAD,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Extend the given `keychain`'s `keymap`.
 | 
			
		||||
    pub fn keymap(mut self, keychain: KeychainKind, keymap: KeyMap) -> Self {
 | 
			
		||||
        match keychain {
 | 
			
		||||
            KeychainKind::External => &mut self.descriptor_keymap,
 | 
			
		||||
            KeychainKind::Internal => &mut self.change_descriptor_keymap,
 | 
			
		||||
        }
 | 
			
		||||
        .extend(keymap);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Set `network`.
 | 
			
		||||
    pub fn network(mut self, network: Network) -> Self {
 | 
			
		||||
        self.network = network;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Use a custom `genesis_hash`.
 | 
			
		||||
    pub fn genesis_hash(mut self, genesis_hash: BlockHash) -> Self {
 | 
			
		||||
        self.genesis_hash = Some(genesis_hash);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Use custom lookahead value.
 | 
			
		||||
    pub fn lookahead(mut self, lookahead: u32) -> Self {
 | 
			
		||||
        self.lookahead = lookahead;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Create [`PersistedWallet`] with the given `Db`.
 | 
			
		||||
    pub fn create_wallet<Db>(
 | 
			
		||||
        self,
 | 
			
		||||
        db: &mut Db,
 | 
			
		||||
    ) -> Result<PersistedWallet, <Wallet as PersistWith<Db>>::CreateError>
 | 
			
		||||
    where
 | 
			
		||||
        Wallet: PersistWith<Db, CreateParams = Self>,
 | 
			
		||||
    {
 | 
			
		||||
        PersistedWallet::create(db, self)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Create [`PersistedWallet`] with the given async `Db`.
 | 
			
		||||
    pub async fn create_wallet_async<Db>(
 | 
			
		||||
        self,
 | 
			
		||||
        db: &mut Db,
 | 
			
		||||
    ) -> Result<PersistedWallet, <Wallet as PersistAsyncWith<Db>>::CreateError>
 | 
			
		||||
    where
 | 
			
		||||
        Wallet: PersistAsyncWith<Db, CreateParams = Self>,
 | 
			
		||||
    {
 | 
			
		||||
        PersistedWallet::create_async(db, self).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Create [`Wallet`] without persistence.
 | 
			
		||||
    pub fn create_wallet_no_persist(self) -> Result<Wallet, DescriptorError> {
 | 
			
		||||
        Wallet::create_with_params(self)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Parameters for [`Wallet::load`] or [`PersistedWallet::load`].
 | 
			
		||||
#[must_use]
 | 
			
		||||
pub struct LoadParams {
 | 
			
		||||
    pub(crate) descriptor_keymap: KeyMap,
 | 
			
		||||
    pub(crate) change_descriptor_keymap: KeyMap,
 | 
			
		||||
    pub(crate) lookahead: u32,
 | 
			
		||||
    pub(crate) check_network: Option<Network>,
 | 
			
		||||
    pub(crate) check_genesis_hash: Option<BlockHash>,
 | 
			
		||||
    pub(crate) check_descriptor: Option<DescriptorToExtract>,
 | 
			
		||||
    pub(crate) check_change_descriptor: Option<DescriptorToExtract>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl LoadParams {
 | 
			
		||||
    /// Construct parameters with default values.
 | 
			
		||||
    ///
 | 
			
		||||
    /// Default values: `lookahead` = [`DEFAULT_LOOKAHEAD`]
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self {
 | 
			
		||||
            descriptor_keymap: KeyMap::default(),
 | 
			
		||||
            change_descriptor_keymap: KeyMap::default(),
 | 
			
		||||
            lookahead: DEFAULT_LOOKAHEAD,
 | 
			
		||||
            check_network: None,
 | 
			
		||||
            check_genesis_hash: None,
 | 
			
		||||
            check_descriptor: None,
 | 
			
		||||
            check_change_descriptor: None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Extend the given `keychain`'s `keymap`.
 | 
			
		||||
    pub fn keymap(mut self, keychain: KeychainKind, keymap: KeyMap) -> Self {
 | 
			
		||||
        match keychain {
 | 
			
		||||
            KeychainKind::External => &mut self.descriptor_keymap,
 | 
			
		||||
            KeychainKind::Internal => &mut self.change_descriptor_keymap,
 | 
			
		||||
        }
 | 
			
		||||
        .extend(keymap);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Checks that `descriptor` of `keychain` matches this, and extracts private keys (if
 | 
			
		||||
    /// avaliable).
 | 
			
		||||
    pub fn descriptors<D>(mut self, descriptor: D, change_descriptor: D) -> Self
 | 
			
		||||
    where
 | 
			
		||||
        D: IntoWalletDescriptor + 'static,
 | 
			
		||||
    {
 | 
			
		||||
        self.check_descriptor = Some(make_descriptor_to_extract(descriptor));
 | 
			
		||||
        self.check_change_descriptor = Some(make_descriptor_to_extract(change_descriptor));
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Check for `network`.
 | 
			
		||||
    pub fn network(mut self, network: Network) -> Self {
 | 
			
		||||
        self.check_network = Some(network);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Check for a `genesis_hash`.
 | 
			
		||||
    pub fn genesis_hash(mut self, genesis_hash: BlockHash) -> Self {
 | 
			
		||||
        self.check_genesis_hash = Some(genesis_hash);
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Use custom lookahead value.
 | 
			
		||||
    pub fn lookahead(mut self, lookahead: u32) -> Self {
 | 
			
		||||
        self.lookahead = lookahead;
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Load [`PersistedWallet`] with the given `Db`.
 | 
			
		||||
    pub fn load_wallet<Db>(
 | 
			
		||||
        self,
 | 
			
		||||
        db: &mut Db,
 | 
			
		||||
    ) -> Result<Option<PersistedWallet>, <Wallet as PersistWith<Db>>::LoadError>
 | 
			
		||||
    where
 | 
			
		||||
        Wallet: PersistWith<Db, LoadParams = Self>,
 | 
			
		||||
    {
 | 
			
		||||
        PersistedWallet::load(db, self)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Load [`PersistedWallet`] with the given async `Db`.
 | 
			
		||||
    pub async fn load_wallet_async<Db>(
 | 
			
		||||
        self,
 | 
			
		||||
        db: &mut Db,
 | 
			
		||||
    ) -> Result<Option<PersistedWallet>, <Wallet as PersistAsyncWith<Db>>::LoadError>
 | 
			
		||||
    where
 | 
			
		||||
        Wallet: PersistAsyncWith<Db, LoadParams = Self>,
 | 
			
		||||
    {
 | 
			
		||||
        PersistedWallet::load_async(db, self).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Load [`Wallet`] without persistence.
 | 
			
		||||
    pub fn load_wallet_no_persist(self, changeset: ChangeSet) -> Result<Option<Wallet>, LoadError> {
 | 
			
		||||
        Wallet::load_with_params(changeset, self)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Default for LoadParams {
 | 
			
		||||
    fn default() -> Self {
 | 
			
		||||
        Self::new()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										171
									
								
								crates/wallet/src/wallet/persisted.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										171
									
								
								crates/wallet/src/wallet/persisted.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,171 @@
 | 
			
		||||
use core::fmt;
 | 
			
		||||
 | 
			
		||||
use crate::{descriptor::DescriptorError, Wallet};
 | 
			
		||||
 | 
			
		||||
/// Represents a persisted wallet.
 | 
			
		||||
pub type PersistedWallet = bdk_chain::Persisted<Wallet>;
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "rusqlite")]
 | 
			
		||||
impl<'c> chain::PersistWith<bdk_chain::rusqlite::Transaction<'c>> for Wallet {
 | 
			
		||||
    type CreateParams = crate::CreateParams;
 | 
			
		||||
    type LoadParams = crate::LoadParams;
 | 
			
		||||
 | 
			
		||||
    type CreateError = CreateWithPersistError<bdk_chain::rusqlite::Error>;
 | 
			
		||||
    type LoadError = LoadWithPersistError<bdk_chain::rusqlite::Error>;
 | 
			
		||||
    type PersistError = bdk_chain::rusqlite::Error;
 | 
			
		||||
 | 
			
		||||
    fn create(
 | 
			
		||||
        db: &mut bdk_chain::rusqlite::Transaction<'c>,
 | 
			
		||||
        params: Self::CreateParams,
 | 
			
		||||
    ) -> Result<Self, Self::CreateError> {
 | 
			
		||||
        let mut wallet =
 | 
			
		||||
            Self::create_with_params(params).map_err(CreateWithPersistError::Descriptor)?;
 | 
			
		||||
        if let Some(changeset) = wallet.take_staged() {
 | 
			
		||||
            changeset
 | 
			
		||||
                .persist_to_sqlite(db)
 | 
			
		||||
                .map_err(CreateWithPersistError::Persist)?;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(wallet)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn load(
 | 
			
		||||
        conn: &mut bdk_chain::rusqlite::Transaction<'c>,
 | 
			
		||||
        params: Self::LoadParams,
 | 
			
		||||
    ) -> Result<Option<Self>, Self::LoadError> {
 | 
			
		||||
        let changeset =
 | 
			
		||||
            crate::ChangeSet::from_sqlite(conn).map_err(LoadWithPersistError::Persist)?;
 | 
			
		||||
        if chain::Merge::is_empty(&changeset) {
 | 
			
		||||
            return Ok(None);
 | 
			
		||||
        }
 | 
			
		||||
        Self::load_with_params(changeset, params).map_err(LoadWithPersistError::InvalidChangeSet)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn persist(
 | 
			
		||||
        db: &mut bdk_chain::rusqlite::Transaction<'c>,
 | 
			
		||||
        changeset: &<Self as chain::Staged>::ChangeSet,
 | 
			
		||||
    ) -> Result<(), Self::PersistError> {
 | 
			
		||||
        changeset.persist_to_sqlite(db)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "rusqlite")]
 | 
			
		||||
impl chain::PersistWith<bdk_chain::rusqlite::Connection> for Wallet {
 | 
			
		||||
    type CreateParams = crate::CreateParams;
 | 
			
		||||
    type LoadParams = crate::LoadParams;
 | 
			
		||||
 | 
			
		||||
    type CreateError = CreateWithPersistError<bdk_chain::rusqlite::Error>;
 | 
			
		||||
    type LoadError = LoadWithPersistError<bdk_chain::rusqlite::Error>;
 | 
			
		||||
    type PersistError = bdk_chain::rusqlite::Error;
 | 
			
		||||
 | 
			
		||||
    fn create(
 | 
			
		||||
        db: &mut bdk_chain::rusqlite::Connection,
 | 
			
		||||
        params: Self::CreateParams,
 | 
			
		||||
    ) -> Result<Self, Self::CreateError> {
 | 
			
		||||
        let mut db_tx = db.transaction().map_err(CreateWithPersistError::Persist)?;
 | 
			
		||||
        let wallet = chain::PersistWith::create(&mut db_tx, params)?;
 | 
			
		||||
        db_tx.commit().map_err(CreateWithPersistError::Persist)?;
 | 
			
		||||
        Ok(wallet)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn load(
 | 
			
		||||
        db: &mut bdk_chain::rusqlite::Connection,
 | 
			
		||||
        params: Self::LoadParams,
 | 
			
		||||
    ) -> Result<Option<Self>, Self::LoadError> {
 | 
			
		||||
        let mut db_tx = db.transaction().map_err(LoadWithPersistError::Persist)?;
 | 
			
		||||
        let wallet_opt = chain::PersistWith::load(&mut db_tx, params)?;
 | 
			
		||||
        db_tx.commit().map_err(LoadWithPersistError::Persist)?;
 | 
			
		||||
        Ok(wallet_opt)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn persist(
 | 
			
		||||
        db: &mut bdk_chain::rusqlite::Connection,
 | 
			
		||||
        changeset: &<Self as chain::Staged>::ChangeSet,
 | 
			
		||||
    ) -> Result<(), Self::PersistError> {
 | 
			
		||||
        let db_tx = db.transaction()?;
 | 
			
		||||
        changeset.persist_to_sqlite(&db_tx)?;
 | 
			
		||||
        db_tx.commit()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "file_store")]
 | 
			
		||||
impl chain::PersistWith<bdk_file_store::Store<crate::ChangeSet>> for Wallet {
 | 
			
		||||
    type CreateParams = crate::CreateParams;
 | 
			
		||||
    type LoadParams = crate::LoadParams;
 | 
			
		||||
    type CreateError = CreateWithPersistError<std::io::Error>;
 | 
			
		||||
    type LoadError =
 | 
			
		||||
        LoadWithPersistError<bdk_file_store::AggregateChangesetsError<crate::ChangeSet>>;
 | 
			
		||||
    type PersistError = std::io::Error;
 | 
			
		||||
 | 
			
		||||
    fn create(
 | 
			
		||||
        db: &mut bdk_file_store::Store<crate::ChangeSet>,
 | 
			
		||||
        params: Self::CreateParams,
 | 
			
		||||
    ) -> Result<Self, Self::CreateError> {
 | 
			
		||||
        let mut wallet =
 | 
			
		||||
            Self::create_with_params(params).map_err(CreateWithPersistError::Descriptor)?;
 | 
			
		||||
        if let Some(changeset) = wallet.take_staged() {
 | 
			
		||||
            db.append_changeset(&changeset)
 | 
			
		||||
                .map_err(CreateWithPersistError::Persist)?;
 | 
			
		||||
        }
 | 
			
		||||
        Ok(wallet)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn load(
 | 
			
		||||
        db: &mut bdk_file_store::Store<crate::ChangeSet>,
 | 
			
		||||
        params: Self::LoadParams,
 | 
			
		||||
    ) -> Result<Option<Self>, Self::LoadError> {
 | 
			
		||||
        let changeset = db
 | 
			
		||||
            .aggregate_changesets()
 | 
			
		||||
            .map_err(LoadWithPersistError::Persist)?
 | 
			
		||||
            .unwrap_or_default();
 | 
			
		||||
        Self::load_with_params(changeset, params).map_err(LoadWithPersistError::InvalidChangeSet)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn persist(
 | 
			
		||||
        db: &mut bdk_file_store::Store<crate::ChangeSet>,
 | 
			
		||||
        changeset: &<Self as chain::Staged>::ChangeSet,
 | 
			
		||||
    ) -> Result<(), Self::PersistError> {
 | 
			
		||||
        db.append_changeset(changeset)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Error type for [`PersistedWallet::load`].
 | 
			
		||||
#[derive(Debug, PartialEq)]
 | 
			
		||||
pub enum LoadWithPersistError<E> {
 | 
			
		||||
    /// Error from persistence.
 | 
			
		||||
    Persist(E),
 | 
			
		||||
    /// Occurs when the loaded changeset cannot construct [`Wallet`].
 | 
			
		||||
    InvalidChangeSet(crate::LoadError),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<E: fmt::Display> fmt::Display for LoadWithPersistError<E> {
 | 
			
		||||
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            Self::Persist(err) => fmt::Display::fmt(err, f),
 | 
			
		||||
            Self::InvalidChangeSet(err) => fmt::Display::fmt(&err, f),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "std")]
 | 
			
		||||
impl<E: fmt::Debug + fmt::Display> std::error::Error for LoadWithPersistError<E> {}
 | 
			
		||||
 | 
			
		||||
/// Error type for [`PersistedWallet::create`].
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub enum CreateWithPersistError<E> {
 | 
			
		||||
    /// Error from persistence.
 | 
			
		||||
    Persist(E),
 | 
			
		||||
    /// Occurs when the loaded changeset cannot contruct [`Wallet`].
 | 
			
		||||
    Descriptor(DescriptorError),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<E: fmt::Display> fmt::Display for CreateWithPersistError<E> {
 | 
			
		||||
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            Self::Persist(err) => fmt::Display::fmt(err, f),
 | 
			
		||||
            Self::Descriptor(err) => fmt::Display::fmt(&err, f),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(feature = "std")]
 | 
			
		||||
impl<E: fmt::Debug + fmt::Display> std::error::Error for CreateWithPersistError<E> {}
 | 
			
		||||
@ -69,7 +69,9 @@
 | 
			
		||||
//!
 | 
			
		||||
//! let descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/0/*)";
 | 
			
		||||
//! let change_descriptor = "wpkh(tpubD6NzVbkrYhZ4Xferm7Pz4VnjdcDPFyjVu5K4iZXQ4pVN8Cks4pHVowTBXBKRhX64pkRyJZJN5xAKj4UDNnLPb5p2sSKXhewoYx5GbTdUFWq/1/*)";
 | 
			
		||||
//! let mut wallet = Wallet::new(descriptor, change_descriptor, Network::Testnet)?;
 | 
			
		||||
//! let mut wallet = Wallet::create(descriptor, change_descriptor)
 | 
			
		||||
//!     .network(Network::Testnet)
 | 
			
		||||
//!     .create_wallet_no_persist()?;
 | 
			
		||||
//! wallet.add_signer(
 | 
			
		||||
//!     KeychainKind::External,
 | 
			
		||||
//!     SignerOrdering(200),
 | 
			
		||||
 | 
			
		||||
@ -17,8 +17,8 @@
 | 
			
		||||
//! # use std::str::FromStr;
 | 
			
		||||
//! # use bitcoin::*;
 | 
			
		||||
//! # use bdk_wallet::*;
 | 
			
		||||
//! # use bdk_wallet::wallet::ChangeSet;
 | 
			
		||||
//! # use bdk_wallet::wallet::error::CreateTxError;
 | 
			
		||||
//! # use bdk_wallet::ChangeSet;
 | 
			
		||||
//! # use bdk_wallet::error::CreateTxError;
 | 
			
		||||
//! # use anyhow::Error;
 | 
			
		||||
//! # let to_address = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
 | 
			
		||||
//! # let mut wallet = doctest_wallet!();
 | 
			
		||||
@ -69,11 +69,11 @@ use crate::{KeychainKind, LocalOutput, Utxo, WeightedUtxo};
 | 
			
		||||
///
 | 
			
		||||
/// ```
 | 
			
		||||
/// # use bdk_wallet::*;
 | 
			
		||||
/// # use bdk_wallet::wallet::tx_builder::*;
 | 
			
		||||
/// # use bdk_wallet::tx_builder::*;
 | 
			
		||||
/// # use bitcoin::*;
 | 
			
		||||
/// # use core::str::FromStr;
 | 
			
		||||
/// # use bdk_wallet::wallet::ChangeSet;
 | 
			
		||||
/// # use bdk_wallet::wallet::error::CreateTxError;
 | 
			
		||||
/// # use bdk_wallet::ChangeSet;
 | 
			
		||||
/// # use bdk_wallet::error::CreateTxError;
 | 
			
		||||
/// # use anyhow::Error;
 | 
			
		||||
/// # let mut wallet = doctest_wallet!();
 | 
			
		||||
/// # let addr1 = Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt").unwrap().assume_checked();
 | 
			
		||||
@ -641,8 +641,8 @@ impl<'a, Cs> TxBuilder<'a, Cs> {
 | 
			
		||||
    /// # use std::str::FromStr;
 | 
			
		||||
    /// # use bitcoin::*;
 | 
			
		||||
    /// # use bdk_wallet::*;
 | 
			
		||||
    /// # use bdk_wallet::wallet::ChangeSet;
 | 
			
		||||
    /// # use bdk_wallet::wallet::error::CreateTxError;
 | 
			
		||||
    /// # use bdk_wallet::ChangeSet;
 | 
			
		||||
    /// # use bdk_wallet::error::CreateTxError;
 | 
			
		||||
    /// # use anyhow::Error;
 | 
			
		||||
    /// # let to_address =
 | 
			
		||||
    /// Address::from_str("2N4eQYCbKUHCCTUjBJeHcJp9ok6J2GZsTDt")
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,6 @@
 | 
			
		||||
#![allow(unused)]
 | 
			
		||||
use bdk_chain::{BlockId, ConfirmationBlockTime, ConfirmationTime, TxGraph};
 | 
			
		||||
use bdk_wallet::{
 | 
			
		||||
    wallet::{Update, Wallet},
 | 
			
		||||
    KeychainKind, LocalOutput,
 | 
			
		||||
};
 | 
			
		||||
use bdk_wallet::{CreateParams, KeychainKind, LocalOutput, Update, Wallet};
 | 
			
		||||
use bitcoin::{
 | 
			
		||||
    hashes::Hash, transaction, Address, Amount, BlockHash, FeeRate, Network, OutPoint, Transaction,
 | 
			
		||||
    TxIn, TxOut, Txid,
 | 
			
		||||
@ -16,7 +13,11 @@ use std::str::FromStr;
 | 
			
		||||
/// to a foreign address and one returning 50_000 back to the wallet. The remaining 1000
 | 
			
		||||
/// sats are the transaction fee.
 | 
			
		||||
pub fn get_funded_wallet_with_change(descriptor: &str, change: &str) -> (Wallet, bitcoin::Txid) {
 | 
			
		||||
    let mut wallet = Wallet::new(descriptor, change, Network::Regtest).unwrap();
 | 
			
		||||
    let mut wallet = Wallet::create(descriptor.to_string(), change.to_string())
 | 
			
		||||
        .network(Network::Regtest)
 | 
			
		||||
        .create_wallet_no_persist()
 | 
			
		||||
        .expect("descriptors must be valid");
 | 
			
		||||
 | 
			
		||||
    let receive_address = wallet.peek_address(KeychainKind::External, 0).address;
 | 
			
		||||
    let sendto_address = Address::from_str("bcrt1q3qtze4ys45tgdvguj66zrk4fu6hq3a3v9pfly5")
 | 
			
		||||
        .expect("address")
 | 
			
		||||
 | 
			
		||||
@ -3,19 +3,19 @@ extern crate alloc;
 | 
			
		||||
use std::path::Path;
 | 
			
		||||
use std::str::FromStr;
 | 
			
		||||
 | 
			
		||||
use anyhow::Context;
 | 
			
		||||
use assert_matches::assert_matches;
 | 
			
		||||
use bdk_chain::collections::BTreeMap;
 | 
			
		||||
use bdk_chain::COINBASE_MATURITY;
 | 
			
		||||
use bdk_chain::{BlockId, ConfirmationTime};
 | 
			
		||||
use bdk_sqlite::rusqlite::Connection;
 | 
			
		||||
use bdk_chain::{PersistWith, COINBASE_MATURITY};
 | 
			
		||||
use bdk_wallet::coin_selection::{self, LargestFirstCoinSelection};
 | 
			
		||||
use bdk_wallet::descriptor::{calc_checksum, DescriptorError, IntoWalletDescriptor};
 | 
			
		||||
use bdk_wallet::error::CreateTxError;
 | 
			
		||||
use bdk_wallet::psbt::PsbtUtils;
 | 
			
		||||
use bdk_wallet::signer::{SignOptions, SignerError};
 | 
			
		||||
use bdk_wallet::wallet::coin_selection::{self, LargestFirstCoinSelection};
 | 
			
		||||
use bdk_wallet::wallet::error::CreateTxError;
 | 
			
		||||
use bdk_wallet::wallet::tx_builder::AddForeignUtxoError;
 | 
			
		||||
use bdk_wallet::wallet::{AddressInfo, Balance, ChangeSet, NewError, Wallet};
 | 
			
		||||
use bdk_wallet::KeychainKind;
 | 
			
		||||
use bdk_wallet::tx_builder::AddForeignUtxoError;
 | 
			
		||||
use bdk_wallet::{AddressInfo, Balance, CreateParams, LoadParams, Wallet};
 | 
			
		||||
use bdk_wallet::{KeychainKind, LoadError, LoadMismatch, LoadWithPersistError};
 | 
			
		||||
use bitcoin::constants::ChainHash;
 | 
			
		||||
use bitcoin::hashes::Hash;
 | 
			
		||||
use bitcoin::key::Secp256k1;
 | 
			
		||||
use bitcoin::psbt;
 | 
			
		||||
@ -80,7 +80,7 @@ fn receive_output_in_latest_block(wallet: &mut Wallet, value: u64) -> OutPoint {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn insert_seen_at(wallet: &mut Wallet, txid: Txid, seen_at: u64) {
 | 
			
		||||
    use bdk_wallet::wallet::Update;
 | 
			
		||||
    use bdk_wallet::Update;
 | 
			
		||||
    let mut graph = bdk_chain::TxGraph::default();
 | 
			
		||||
    let _ = graph.insert_seen_at(txid, seen_at);
 | 
			
		||||
    wallet
 | 
			
		||||
@ -102,46 +102,46 @@ const P2WPKH_FAKE_WITNESS_SIZE: usize = 106;
 | 
			
		||||
const DB_MAGIC: &[u8] = &[0x21, 0x24, 0x48];
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn load_recovers_wallet() -> anyhow::Result<()> {
 | 
			
		||||
    fn run<Db, New, Recover, Read, Write>(
 | 
			
		||||
fn wallet_is_persisted() -> anyhow::Result<()> {
 | 
			
		||||
    fn run<Db, CreateDb, OpenDb>(
 | 
			
		||||
        filename: &str,
 | 
			
		||||
        create_new: New,
 | 
			
		||||
        recover: Recover,
 | 
			
		||||
        read: Read,
 | 
			
		||||
        write: Write,
 | 
			
		||||
        create_db: CreateDb,
 | 
			
		||||
        open_db: OpenDb,
 | 
			
		||||
    ) -> anyhow::Result<()>
 | 
			
		||||
    where
 | 
			
		||||
        New: Fn(&Path) -> anyhow::Result<Db>,
 | 
			
		||||
        Recover: Fn(&Path) -> anyhow::Result<Db>,
 | 
			
		||||
        Read: Fn(&mut Db) -> anyhow::Result<Option<ChangeSet>>,
 | 
			
		||||
        Write: Fn(&mut Db, &ChangeSet) -> anyhow::Result<()>,
 | 
			
		||||
        CreateDb: Fn(&Path) -> anyhow::Result<Db>,
 | 
			
		||||
        OpenDb: Fn(&Path) -> anyhow::Result<Db>,
 | 
			
		||||
        Wallet: PersistWith<Db, CreateParams = CreateParams, LoadParams = LoadParams>,
 | 
			
		||||
        <Wallet as PersistWith<Db>>::CreateError: std::error::Error + Send + Sync + 'static,
 | 
			
		||||
        <Wallet as PersistWith<Db>>::LoadError: std::error::Error + Send + Sync + 'static,
 | 
			
		||||
        <Wallet as PersistWith<Db>>::PersistError: std::error::Error + Send + Sync + 'static,
 | 
			
		||||
    {
 | 
			
		||||
        let temp_dir = tempfile::tempdir().expect("must create tempdir");
 | 
			
		||||
        let file_path = temp_dir.path().join(filename);
 | 
			
		||||
        let (desc, change_desc) = get_test_tr_single_sig_xprv_with_change_desc();
 | 
			
		||||
        let (external_desc, internal_desc) = get_test_tr_single_sig_xprv_with_change_desc();
 | 
			
		||||
 | 
			
		||||
        // create new wallet
 | 
			
		||||
        let wallet_spk_index = {
 | 
			
		||||
            let mut wallet =
 | 
			
		||||
                Wallet::new(desc, change_desc, Network::Testnet).expect("must init wallet");
 | 
			
		||||
 | 
			
		||||
            let mut db = create_db(&file_path)?;
 | 
			
		||||
            let mut wallet = Wallet::create(external_desc, internal_desc)
 | 
			
		||||
                .network(Network::Testnet)
 | 
			
		||||
                .create_wallet(&mut db)?;
 | 
			
		||||
            wallet.reveal_next_address(KeychainKind::External);
 | 
			
		||||
 | 
			
		||||
            // persist new wallet changes
 | 
			
		||||
            let mut db = create_new(&file_path).expect("must create db");
 | 
			
		||||
            if let Some(changeset) = wallet.take_staged() {
 | 
			
		||||
                write(&mut db, &changeset)?;
 | 
			
		||||
            }
 | 
			
		||||
            assert!(wallet.persist(&mut db)?, "must write");
 | 
			
		||||
            wallet.spk_index().clone()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // recover wallet
 | 
			
		||||
        {
 | 
			
		||||
            // load persisted wallet changes
 | 
			
		||||
            let db = &mut recover(&file_path).expect("must recover db");
 | 
			
		||||
            let changeset = read(db).expect("must recover wallet").expect("changeset");
 | 
			
		||||
            let mut db = open_db(&file_path).context("failed to recover db")?;
 | 
			
		||||
            let wallet = Wallet::load()
 | 
			
		||||
                .descriptors(external_desc, internal_desc)
 | 
			
		||||
                .network(Network::Testnet)
 | 
			
		||||
                .load_wallet(&mut db)?
 | 
			
		||||
                .expect("wallet must exist");
 | 
			
		||||
 | 
			
		||||
            let wallet = Wallet::load_from_changeset(changeset).expect("must recover wallet");
 | 
			
		||||
            assert_eq!(wallet.network(), Network::Testnet);
 | 
			
		||||
            assert_eq!(
 | 
			
		||||
                wallet.spk_index().keychains().collect::<Vec<_>>(),
 | 
			
		||||
@ -154,7 +154,8 @@ fn load_recovers_wallet() -> anyhow::Result<()> {
 | 
			
		||||
            let secp = Secp256k1::new();
 | 
			
		||||
            assert_eq!(
 | 
			
		||||
                *wallet.public_descriptor(KeychainKind::External),
 | 
			
		||||
                desc.into_wallet_descriptor(&secp, wallet.network())
 | 
			
		||||
                external_desc
 | 
			
		||||
                    .into_wallet_descriptor(&secp, wallet.network())
 | 
			
		||||
                    .unwrap()
 | 
			
		||||
                    .0
 | 
			
		||||
            );
 | 
			
		||||
@ -167,166 +168,98 @@ fn load_recovers_wallet() -> anyhow::Result<()> {
 | 
			
		||||
        "store.db",
 | 
			
		||||
        |path| Ok(bdk_file_store::Store::create_new(DB_MAGIC, path)?),
 | 
			
		||||
        |path| Ok(bdk_file_store::Store::open(DB_MAGIC, path)?),
 | 
			
		||||
        |db| Ok(bdk_file_store::Store::aggregate_changesets(db)?),
 | 
			
		||||
        |db, changeset| Ok(bdk_file_store::Store::append_changeset(db, changeset)?),
 | 
			
		||||
    )?;
 | 
			
		||||
    run(
 | 
			
		||||
    run::<bdk_chain::rusqlite::Connection, _, _>(
 | 
			
		||||
        "store.sqlite",
 | 
			
		||||
        |path| Ok(bdk_sqlite::Store::new(Connection::open(path)?)?),
 | 
			
		||||
        |path| Ok(bdk_sqlite::Store::new(Connection::open(path)?)?),
 | 
			
		||||
        |db| Ok(bdk_sqlite::Store::read(db)?),
 | 
			
		||||
        |db, changeset| Ok(bdk_sqlite::Store::write(db, changeset)?),
 | 
			
		||||
        |path| Ok(bdk_chain::rusqlite::Connection::open(path)?),
 | 
			
		||||
        |path| Ok(bdk_chain::rusqlite::Connection::open(path)?),
 | 
			
		||||
    )?;
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn new_or_load() -> anyhow::Result<()> {
 | 
			
		||||
    fn run<Db, NewOrRecover, Read, Write>(
 | 
			
		||||
fn wallet_load_checks() -> anyhow::Result<()> {
 | 
			
		||||
    fn run<Db, CreateDb, OpenDb, LoadDbError>(
 | 
			
		||||
        filename: &str,
 | 
			
		||||
        new_or_load: NewOrRecover,
 | 
			
		||||
        read: Read,
 | 
			
		||||
        write: Write,
 | 
			
		||||
        create_db: CreateDb,
 | 
			
		||||
        open_db: OpenDb,
 | 
			
		||||
    ) -> anyhow::Result<()>
 | 
			
		||||
    where
 | 
			
		||||
        NewOrRecover: Fn(&Path) -> anyhow::Result<Db>,
 | 
			
		||||
        Read: Fn(&mut Db) -> anyhow::Result<Option<ChangeSet>>,
 | 
			
		||||
        Write: Fn(&mut Db, &ChangeSet) -> anyhow::Result<()>,
 | 
			
		||||
        CreateDb: Fn(&Path) -> anyhow::Result<Db>,
 | 
			
		||||
        OpenDb: Fn(&Path) -> anyhow::Result<Db>,
 | 
			
		||||
        Wallet: PersistWith<
 | 
			
		||||
            Db,
 | 
			
		||||
            CreateParams = CreateParams,
 | 
			
		||||
            LoadParams = LoadParams,
 | 
			
		||||
            LoadError = LoadWithPersistError<LoadDbError>,
 | 
			
		||||
        >,
 | 
			
		||||
        <Wallet as PersistWith<Db>>::CreateError: std::error::Error + Send + Sync + 'static,
 | 
			
		||||
        <Wallet as PersistWith<Db>>::LoadError: std::error::Error + Send + Sync + 'static,
 | 
			
		||||
        <Wallet as PersistWith<Db>>::PersistError: std::error::Error + Send + Sync + 'static,
 | 
			
		||||
    {
 | 
			
		||||
        let temp_dir = tempfile::tempdir().expect("must create tempdir");
 | 
			
		||||
        let file_path = temp_dir.path().join(filename);
 | 
			
		||||
        let (desc, change_desc) = get_test_wpkh_with_change_desc();
 | 
			
		||||
        let network = Network::Testnet;
 | 
			
		||||
        let (external_desc, internal_desc) = get_test_tr_single_sig_xprv_with_change_desc();
 | 
			
		||||
 | 
			
		||||
        // init wallet when non-existent
 | 
			
		||||
        let wallet_keychains: BTreeMap<_, _> = {
 | 
			
		||||
            let wallet = &mut Wallet::new_or_load(desc, change_desc, None, Network::Testnet)
 | 
			
		||||
                .expect("must init wallet");
 | 
			
		||||
            let mut db = new_or_load(&file_path).expect("must create db");
 | 
			
		||||
            if let Some(changeset) = wallet.take_staged() {
 | 
			
		||||
                write(&mut db, &changeset)?;
 | 
			
		||||
            }
 | 
			
		||||
            wallet.keychains().map(|(k, v)| (*k, v.clone())).collect()
 | 
			
		||||
        };
 | 
			
		||||
        // create new wallet
 | 
			
		||||
        let _ = Wallet::create(external_desc, internal_desc)
 | 
			
		||||
            .network(network)
 | 
			
		||||
            .create_wallet(&mut create_db(&file_path)?)?;
 | 
			
		||||
 | 
			
		||||
        // wrong network
 | 
			
		||||
        {
 | 
			
		||||
            let mut db = new_or_load(&file_path).expect("must create db");
 | 
			
		||||
            let changeset = read(&mut db)?;
 | 
			
		||||
            let err = Wallet::new_or_load(desc, change_desc, changeset, Network::Bitcoin)
 | 
			
		||||
                .expect_err("wrong network");
 | 
			
		||||
            assert!(
 | 
			
		||||
                matches!(
 | 
			
		||||
                    err,
 | 
			
		||||
                    bdk_wallet::wallet::NewOrLoadError::LoadedNetworkDoesNotMatch {
 | 
			
		||||
                        got: Some(Network::Testnet),
 | 
			
		||||
                        expected: Network::Bitcoin
 | 
			
		||||
                    }
 | 
			
		||||
                ),
 | 
			
		||||
                "err: {}",
 | 
			
		||||
                err,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        assert_matches!(
 | 
			
		||||
            Wallet::load()
 | 
			
		||||
                .network(Network::Regtest)
 | 
			
		||||
                .load_wallet(&mut open_db(&file_path)?),
 | 
			
		||||
            Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch(
 | 
			
		||||
                LoadMismatch::Network {
 | 
			
		||||
                    loaded: Network::Testnet,
 | 
			
		||||
                    expected: Network::Regtest,
 | 
			
		||||
                }
 | 
			
		||||
            ))),
 | 
			
		||||
            "unexpected network check result: Regtest (check) is not Testnet (loaded)",
 | 
			
		||||
        );
 | 
			
		||||
        assert_matches!(
 | 
			
		||||
            Wallet::load()
 | 
			
		||||
                .network(Network::Bitcoin)
 | 
			
		||||
                .load_wallet(&mut open_db(&file_path)?),
 | 
			
		||||
            Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch(
 | 
			
		||||
                LoadMismatch::Network {
 | 
			
		||||
                    loaded: Network::Testnet,
 | 
			
		||||
                    expected: Network::Bitcoin,
 | 
			
		||||
                }
 | 
			
		||||
            ))),
 | 
			
		||||
            "unexpected network check result: Bitcoin (check) is not Testnet (loaded)",
 | 
			
		||||
        );
 | 
			
		||||
        let mainnet_hash = BlockHash::from_byte_array(ChainHash::BITCOIN.to_bytes());
 | 
			
		||||
        assert_matches!(
 | 
			
		||||
            Wallet::load().genesis_hash(mainnet_hash).load_wallet(&mut open_db(&file_path)?),
 | 
			
		||||
            Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch(LoadMismatch::Genesis { .. }))),
 | 
			
		||||
            "unexpected genesis hash check result: mainnet hash (check) is not testnet hash (loaded)",
 | 
			
		||||
        );
 | 
			
		||||
        assert_matches!(
 | 
			
		||||
            Wallet::load()
 | 
			
		||||
                .descriptors(internal_desc, external_desc)
 | 
			
		||||
                .load_wallet(&mut open_db(&file_path)?),
 | 
			
		||||
            Err(LoadWithPersistError::InvalidChangeSet(LoadError::Mismatch(
 | 
			
		||||
                LoadMismatch::Descriptor { .. }
 | 
			
		||||
            ))),
 | 
			
		||||
            "unexpected descriptors check result",
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // wrong genesis hash
 | 
			
		||||
        {
 | 
			
		||||
            let exp_blockhash = BlockHash::all_zeros();
 | 
			
		||||
            let got_blockhash = bitcoin::constants::genesis_block(Network::Testnet).block_hash();
 | 
			
		||||
 | 
			
		||||
            let db = &mut new_or_load(&file_path).expect("must open db");
 | 
			
		||||
            let changeset = read(db)?;
 | 
			
		||||
            let err = Wallet::new_or_load_with_genesis_hash(
 | 
			
		||||
                desc,
 | 
			
		||||
                change_desc,
 | 
			
		||||
                changeset,
 | 
			
		||||
                Network::Testnet,
 | 
			
		||||
                exp_blockhash,
 | 
			
		||||
            )
 | 
			
		||||
            .expect_err("wrong genesis hash");
 | 
			
		||||
            assert!(
 | 
			
		||||
                matches!(
 | 
			
		||||
                    err,
 | 
			
		||||
                    bdk_wallet::wallet::NewOrLoadError::LoadedGenesisDoesNotMatch { got, expected }
 | 
			
		||||
                    if got == Some(got_blockhash) && expected == exp_blockhash
 | 
			
		||||
                ),
 | 
			
		||||
                "err: {}",
 | 
			
		||||
                err,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // wrong external descriptor
 | 
			
		||||
        {
 | 
			
		||||
            let (exp_descriptor, exp_change_desc) = get_test_tr_single_sig_xprv_with_change_desc();
 | 
			
		||||
            let got_descriptor = desc
 | 
			
		||||
                .into_wallet_descriptor(&Secp256k1::new(), Network::Testnet)
 | 
			
		||||
                .unwrap()
 | 
			
		||||
                .0;
 | 
			
		||||
 | 
			
		||||
            let db = &mut new_or_load(&file_path).expect("must open db");
 | 
			
		||||
            let changeset = read(db)?;
 | 
			
		||||
            let err =
 | 
			
		||||
                Wallet::new_or_load(exp_descriptor, exp_change_desc, changeset, Network::Testnet)
 | 
			
		||||
                    .expect_err("wrong external descriptor");
 | 
			
		||||
            assert!(
 | 
			
		||||
                matches!(
 | 
			
		||||
                    err,
 | 
			
		||||
                    bdk_wallet::wallet::NewOrLoadError::LoadedDescriptorDoesNotMatch { ref got, keychain }
 | 
			
		||||
                    if got == &Some(got_descriptor) && keychain == KeychainKind::External
 | 
			
		||||
                ),
 | 
			
		||||
                "err: {}",
 | 
			
		||||
                err,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // wrong internal descriptor
 | 
			
		||||
        {
 | 
			
		||||
            let exp_descriptor = get_test_tr_single_sig();
 | 
			
		||||
            let got_descriptor = change_desc
 | 
			
		||||
                .into_wallet_descriptor(&Secp256k1::new(), Network::Testnet)
 | 
			
		||||
                .unwrap()
 | 
			
		||||
                .0;
 | 
			
		||||
 | 
			
		||||
            let db = &mut new_or_load(&file_path).expect("must open db");
 | 
			
		||||
            let changeset = read(db)?;
 | 
			
		||||
            let err = Wallet::new_or_load(desc, exp_descriptor, changeset, Network::Testnet)
 | 
			
		||||
                .expect_err("wrong internal descriptor");
 | 
			
		||||
            assert!(
 | 
			
		||||
                matches!(
 | 
			
		||||
                    err,
 | 
			
		||||
                    bdk_wallet::wallet::NewOrLoadError::LoadedDescriptorDoesNotMatch { ref got, keychain }
 | 
			
		||||
                    if got == &Some(got_descriptor) && keychain == KeychainKind::Internal
 | 
			
		||||
                ),
 | 
			
		||||
                "err: {}",
 | 
			
		||||
                err,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // all parameters match
 | 
			
		||||
        {
 | 
			
		||||
            let db = &mut new_or_load(&file_path).expect("must open db");
 | 
			
		||||
            let changeset = read(db)?;
 | 
			
		||||
            let wallet = Wallet::new_or_load(desc, change_desc, changeset, Network::Testnet)
 | 
			
		||||
                .expect("must recover wallet");
 | 
			
		||||
            assert_eq!(wallet.network(), Network::Testnet);
 | 
			
		||||
            assert!(wallet
 | 
			
		||||
                .keychains()
 | 
			
		||||
                .map(|(k, v)| (*k, v.clone()))
 | 
			
		||||
                .eq(wallet_keychains));
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    run(
 | 
			
		||||
        "store.db",
 | 
			
		||||
        |path| Ok(bdk_file_store::Store::open_or_create_new(DB_MAGIC, path)?),
 | 
			
		||||
        |db| Ok(bdk_file_store::Store::aggregate_changesets(db)?),
 | 
			
		||||
        |db, changeset| Ok(bdk_file_store::Store::append_changeset(db, changeset)?),
 | 
			
		||||
        |path| Ok(bdk_file_store::Store::create_new(DB_MAGIC, path)?),
 | 
			
		||||
        |path| Ok(bdk_file_store::Store::open(DB_MAGIC, path)?),
 | 
			
		||||
    )?;
 | 
			
		||||
    run(
 | 
			
		||||
        "store.sqlite",
 | 
			
		||||
        |path| Ok(bdk_sqlite::Store::new(Connection::open(path)?)?),
 | 
			
		||||
        |db| Ok(bdk_sqlite::Store::read(db)?),
 | 
			
		||||
        |db, changeset| Ok(bdk_sqlite::Store::write(db, changeset)?),
 | 
			
		||||
        |path| Ok(bdk_chain::rusqlite::Connection::open(path)?),
 | 
			
		||||
        |path| Ok(bdk_chain::rusqlite::Connection::open(path)?),
 | 
			
		||||
    )?;
 | 
			
		||||
 | 
			
		||||
    Ok(())
 | 
			
		||||
@ -336,14 +269,11 @@ fn new_or_load() -> anyhow::Result<()> {
 | 
			
		||||
fn test_error_external_and_internal_are_the_same() {
 | 
			
		||||
    // identical descriptors should fail to create wallet
 | 
			
		||||
    let desc = get_test_wpkh();
 | 
			
		||||
    let err = Wallet::new(desc, desc, Network::Testnet);
 | 
			
		||||
    let err = Wallet::create(desc, desc)
 | 
			
		||||
        .network(Network::Testnet)
 | 
			
		||||
        .create_wallet_no_persist();
 | 
			
		||||
    assert!(
 | 
			
		||||
        matches!(
 | 
			
		||||
            &err,
 | 
			
		||||
            Err(NewError::Descriptor(
 | 
			
		||||
                DescriptorError::ExternalAndInternalAreTheSame
 | 
			
		||||
            ))
 | 
			
		||||
        ),
 | 
			
		||||
        matches!(&err, Err(DescriptorError::ExternalAndInternalAreTheSame)),
 | 
			
		||||
        "expected same descriptors error, got {:?}",
 | 
			
		||||
        err,
 | 
			
		||||
    );
 | 
			
		||||
@ -351,14 +281,11 @@ fn test_error_external_and_internal_are_the_same() {
 | 
			
		||||
    // public + private of same descriptor should fail to create wallet
 | 
			
		||||
    let desc = "wpkh(tprv8ZgxMBicQKsPdcAqYBpzAFwU5yxBUo88ggoBqu1qPcHUfSbKK1sKMLmC7EAk438btHQrSdu3jGGQa6PA71nvH5nkDexhLteJqkM4dQmWF9g/84'/1'/0'/0/*)";
 | 
			
		||||
    let change_desc = "wpkh([3c31d632/84'/1'/0']tpubDCYwFkks2cg78N7eoYbBatsFEGje8vW8arSKW4rLwD1AU1s9KJMDRHE32JkvYERuiFjArrsH7qpWSpJATed5ShZbG9KsskA5Rmi6NSYgYN2/0/*)";
 | 
			
		||||
    let err = Wallet::new(desc, change_desc, Network::Testnet);
 | 
			
		||||
    let err = Wallet::create(desc, change_desc)
 | 
			
		||||
        .network(Network::Testnet)
 | 
			
		||||
        .create_wallet_no_persist();
 | 
			
		||||
    assert!(
 | 
			
		||||
        matches!(
 | 
			
		||||
            err,
 | 
			
		||||
            Err(NewError::Descriptor(
 | 
			
		||||
                DescriptorError::ExternalAndInternalAreTheSame
 | 
			
		||||
            ))
 | 
			
		||||
        ),
 | 
			
		||||
        matches!(err, Err(DescriptorError::ExternalAndInternalAreTheSame)),
 | 
			
		||||
        "expected same descriptors error, got {:?}",
 | 
			
		||||
        err,
 | 
			
		||||
    );
 | 
			
		||||
@ -937,7 +864,7 @@ fn test_create_tx_absolute_high_fee() {
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_create_tx_add_change() {
 | 
			
		||||
    use bdk_wallet::wallet::tx_builder::TxOrdering;
 | 
			
		||||
    use bdk_wallet::tx_builder::TxOrdering;
 | 
			
		||||
    let seed = [0; 32];
 | 
			
		||||
    let mut rng: StdRng = SeedableRng::from_seed(seed);
 | 
			
		||||
    let (mut wallet, _) = get_funded_wallet_wpkh();
 | 
			
		||||
@ -1002,7 +929,7 @@ fn test_create_tx_ordering_respected() {
 | 
			
		||||
        project_utxo(tx_a).cmp(&project_utxo(tx_b))
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let custom_bip69_ordering = bdk_wallet::wallet::tx_builder::TxOrdering::Custom {
 | 
			
		||||
    let custom_bip69_ordering = bdk_wallet::tx_builder::TxOrdering::Custom {
 | 
			
		||||
        input_sort: Arc::new(bip69_txin_cmp),
 | 
			
		||||
        output_sort: Arc::new(bip69_txout_cmp),
 | 
			
		||||
    };
 | 
			
		||||
@ -1316,8 +1243,11 @@ fn test_create_tx_policy_path_required() {
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_create_tx_policy_path_no_csv() {
 | 
			
		||||
    let (desc, change_desc) = get_test_wpkh_with_change_desc();
 | 
			
		||||
    let mut wallet = Wallet::new(desc, change_desc, Network::Regtest).expect("wallet");
 | 
			
		||||
    let (descriptor, change_descriptor) = get_test_wpkh_with_change_desc();
 | 
			
		||||
    let mut wallet = Wallet::create(descriptor, change_descriptor)
 | 
			
		||||
        .network(Network::Regtest)
 | 
			
		||||
        .create_wallet_no_persist()
 | 
			
		||||
        .expect("wallet");
 | 
			
		||||
 | 
			
		||||
    let tx = Transaction {
 | 
			
		||||
        version: transaction::Version::non_standard(0),
 | 
			
		||||
@ -2927,9 +2857,12 @@ fn test_sign_nonstandard_sighash() {
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_unused_address() {
 | 
			
		||||
    let desc = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
 | 
			
		||||
    let change_desc = get_test_wpkh();
 | 
			
		||||
    let mut wallet = Wallet::new(desc, change_desc, Network::Testnet).expect("wallet");
 | 
			
		||||
    let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
 | 
			
		||||
    let change_descriptor = get_test_wpkh();
 | 
			
		||||
    let mut wallet = Wallet::create(descriptor, change_descriptor)
 | 
			
		||||
        .network(Network::Testnet)
 | 
			
		||||
        .create_wallet_no_persist()
 | 
			
		||||
        .expect("wallet");
 | 
			
		||||
 | 
			
		||||
    // `list_unused_addresses` should be empty if we haven't revealed any
 | 
			
		||||
    assert!(wallet
 | 
			
		||||
@ -2956,8 +2889,11 @@ fn test_unused_address() {
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_next_unused_address() {
 | 
			
		||||
    let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
 | 
			
		||||
    let change = get_test_wpkh();
 | 
			
		||||
    let mut wallet = Wallet::new(descriptor, change, Network::Testnet).expect("wallet");
 | 
			
		||||
    let change_descriptor = get_test_wpkh();
 | 
			
		||||
    let mut wallet = Wallet::create(descriptor, change_descriptor)
 | 
			
		||||
        .network(Network::Testnet)
 | 
			
		||||
        .create_wallet_no_persist()
 | 
			
		||||
        .expect("wallet");
 | 
			
		||||
    assert_eq!(wallet.derivation_index(KeychainKind::External), None);
 | 
			
		||||
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
@ -3002,9 +2938,12 @@ fn test_next_unused_address() {
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_peek_address_at_index() {
 | 
			
		||||
    let desc = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
 | 
			
		||||
    let change_desc = get_test_wpkh();
 | 
			
		||||
    let mut wallet = Wallet::new(desc, change_desc, Network::Testnet).unwrap();
 | 
			
		||||
    let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
 | 
			
		||||
    let change_descriptor = get_test_wpkh();
 | 
			
		||||
    let mut wallet = Wallet::create(descriptor, change_descriptor)
 | 
			
		||||
        .network(Network::Testnet)
 | 
			
		||||
        .create_wallet_no_persist()
 | 
			
		||||
        .expect("wallet");
 | 
			
		||||
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
        wallet.peek_address(KeychainKind::External, 1).to_string(),
 | 
			
		||||
@ -3039,8 +2978,11 @@ fn test_peek_address_at_index() {
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_peek_address_at_index_not_derivable() {
 | 
			
		||||
    let wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)",
 | 
			
		||||
                                 get_test_wpkh(), Network::Testnet).unwrap();
 | 
			
		||||
    let descriptor = "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/1)";
 | 
			
		||||
    let wallet = Wallet::create(descriptor, get_test_wpkh())
 | 
			
		||||
        .network(Network::Testnet)
 | 
			
		||||
        .create_wallet_no_persist()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
        wallet.peek_address(KeychainKind::External, 1).to_string(),
 | 
			
		||||
@ -3060,8 +3002,12 @@ fn test_peek_address_at_index_not_derivable() {
 | 
			
		||||
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_returns_index_and_address() {
 | 
			
		||||
    let mut wallet = Wallet::new("wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)",
 | 
			
		||||
                                 get_test_wpkh(), Network::Testnet).unwrap();
 | 
			
		||||
    let descriptor =
 | 
			
		||||
        "wpkh(tpubEBr4i6yk5nf5DAaJpsi9N2pPYBeJ7fZ5Z9rmN4977iYLCGco1VyjB9tvvuvYtfZzjD5A8igzgw3HeWeeKFmanHYqksqZXYXGsw5zjnj7KM9/*)";
 | 
			
		||||
    let mut wallet = Wallet::create(descriptor, get_test_wpkh())
 | 
			
		||||
        .network(Network::Testnet)
 | 
			
		||||
        .create_wallet_no_persist()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    // new index 0
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
@ -3127,11 +3073,12 @@ fn test_sending_to_bip350_bech32m_address() {
 | 
			
		||||
fn test_get_address() {
 | 
			
		||||
    use bdk_wallet::descriptor::template::Bip84;
 | 
			
		||||
    let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
 | 
			
		||||
    let wallet = Wallet::new(
 | 
			
		||||
    let wallet = Wallet::create(
 | 
			
		||||
        Bip84(key, KeychainKind::External),
 | 
			
		||||
        Bip84(key, KeychainKind::Internal),
 | 
			
		||||
        Network::Regtest,
 | 
			
		||||
    )
 | 
			
		||||
    .network(Network::Regtest)
 | 
			
		||||
    .create_wallet_no_persist()
 | 
			
		||||
    .unwrap();
 | 
			
		||||
 | 
			
		||||
    assert_eq!(
 | 
			
		||||
@ -3160,7 +3107,10 @@ fn test_get_address() {
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_reveal_addresses() {
 | 
			
		||||
    let (desc, change_desc) = get_test_tr_single_sig_xprv_with_change_desc();
 | 
			
		||||
    let mut wallet = Wallet::new(desc, change_desc, Network::Signet).unwrap();
 | 
			
		||||
    let mut wallet = Wallet::create(desc, change_desc)
 | 
			
		||||
        .network(Network::Signet)
 | 
			
		||||
        .create_wallet_no_persist()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
    let keychain = KeychainKind::External;
 | 
			
		||||
 | 
			
		||||
    let last_revealed_addr = wallet.reveal_addresses_to(keychain, 9).last().unwrap();
 | 
			
		||||
@ -3181,11 +3131,12 @@ fn test_get_address_no_reuse() {
 | 
			
		||||
    use std::collections::HashSet;
 | 
			
		||||
 | 
			
		||||
    let key = bitcoin::bip32::Xpriv::from_str("tprv8ZgxMBicQKsPcx5nBGsR63Pe8KnRUqmbJNENAfGftF3yuXoMMoVJJcYeUw5eVkm9WBPjWYt6HMWYJNesB5HaNVBaFc1M6dRjWSYnmewUMYy").unwrap();
 | 
			
		||||
    let mut wallet = Wallet::new(
 | 
			
		||||
    let mut wallet = Wallet::create(
 | 
			
		||||
        Bip84(key, KeychainKind::External),
 | 
			
		||||
        Bip84(key, KeychainKind::Internal),
 | 
			
		||||
        Network::Regtest,
 | 
			
		||||
    )
 | 
			
		||||
    .network(Network::Regtest)
 | 
			
		||||
    .create_wallet_no_persist()
 | 
			
		||||
    .unwrap();
 | 
			
		||||
 | 
			
		||||
    let mut used_set = HashSet::new();
 | 
			
		||||
@ -3655,12 +3606,10 @@ fn test_taproot_sign_derive_index_from_psbt() {
 | 
			
		||||
    let mut psbt = builder.finish().unwrap();
 | 
			
		||||
 | 
			
		||||
    // re-create the wallet with an empty db
 | 
			
		||||
    let wallet_empty = Wallet::new(
 | 
			
		||||
        get_test_tr_single_sig_xprv(),
 | 
			
		||||
        get_test_tr_single_sig(),
 | 
			
		||||
        Network::Regtest,
 | 
			
		||||
    )
 | 
			
		||||
    .unwrap();
 | 
			
		||||
    let wallet_empty = Wallet::create(get_test_tr_single_sig_xprv(), get_test_tr_single_sig())
 | 
			
		||||
        .network(Network::Regtest)
 | 
			
		||||
        .create_wallet_no_persist()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    // signing with an empty db means that we will only look at the psbt to infer the
 | 
			
		||||
    // derivation index
 | 
			
		||||
@ -3760,7 +3709,10 @@ fn test_taproot_sign_non_default_sighash() {
 | 
			
		||||
#[test]
 | 
			
		||||
fn test_spend_coinbase() {
 | 
			
		||||
    let (desc, change_desc) = get_test_wpkh_with_change_desc();
 | 
			
		||||
    let mut wallet = Wallet::new(desc, change_desc, Network::Regtest).unwrap();
 | 
			
		||||
    let mut wallet = Wallet::create(desc, change_desc)
 | 
			
		||||
        .network(Network::Regtest)
 | 
			
		||||
        .create_wallet_no_persist()
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    let confirmation_height = 5;
 | 
			
		||||
    wallet
 | 
			
		||||
@ -4014,6 +3966,7 @@ fn test_taproot_load_descriptor_duplicated_keys() {
 | 
			
		||||
/// [#1483]: https://github.com/bitcoindevkit/bdk/issues/1483
 | 
			
		||||
/// [#1486]: https://github.com/bitcoindevkit/bdk/pull/1486
 | 
			
		||||
#[test]
 | 
			
		||||
#[cfg(debug_assertions)]
 | 
			
		||||
#[should_panic(
 | 
			
		||||
    expected = "replenish lookahead: must not have existing spk: keychain=Internal, lookahead=25, next_store_index=0, next_reveal_index=0"
 | 
			
		||||
)]
 | 
			
		||||
 | 
			
		||||
@ -38,7 +38,7 @@ const DB_COMMIT_DELAY: Duration = Duration::from_secs(60);
 | 
			
		||||
 | 
			
		||||
type ChangeSet = (
 | 
			
		||||
    local_chain::ChangeSet,
 | 
			
		||||
    indexed_tx_graph::ChangeSet<ConfirmationBlockTime, keychain_txout::ChangeSet<Keychain>>,
 | 
			
		||||
    indexed_tx_graph::ChangeSet<ConfirmationBlockTime, keychain_txout::ChangeSet>,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
 | 
			
		||||
@ -30,7 +30,7 @@ use clap::{Parser, Subcommand};
 | 
			
		||||
pub type KeychainTxGraph<A> = IndexedTxGraph<A, KeychainTxOutIndex<Keychain>>;
 | 
			
		||||
pub type KeychainChangeSet<A> = (
 | 
			
		||||
    local_chain::ChangeSet,
 | 
			
		||||
    indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet<Keychain>>,
 | 
			
		||||
    indexed_tx_graph::ChangeSet<A, keychain_txout::ChangeSet>,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
#[derive(Parser)]
 | 
			
		||||
@ -191,7 +191,7 @@ impl core::fmt::Display for Keychain {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct CreateTxChange {
 | 
			
		||||
    pub index_changeset: keychain_txout::ChangeSet<Keychain>,
 | 
			
		||||
    pub index_changeset: keychain_txout::ChangeSet,
 | 
			
		||||
    pub change_keychain: Keychain,
 | 
			
		||||
    pub index: u32,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -100,7 +100,7 @@ pub struct ScanOptions {
 | 
			
		||||
 | 
			
		||||
type ChangeSet = (
 | 
			
		||||
    local_chain::ChangeSet,
 | 
			
		||||
    indexed_tx_graph::ChangeSet<ConfirmationBlockTime, keychain_txout::ChangeSet<Keychain>>,
 | 
			
		||||
    indexed_tx_graph::ChangeSet<ConfirmationBlockTime, keychain_txout::ChangeSet>,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
fn main() -> anyhow::Result<()> {
 | 
			
		||||
 | 
			
		||||
@ -22,11 +22,11 @@ use example_cli::{
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const DB_MAGIC: &[u8] = b"bdk_example_esplora";
 | 
			
		||||
const DB_PATH: &str = ".bdk_esplora_example.db";
 | 
			
		||||
const DB_PATH: &str = "bdk_example_esplora.db";
 | 
			
		||||
 | 
			
		||||
type ChangeSet = (
 | 
			
		||||
    local_chain::ChangeSet,
 | 
			
		||||
    indexed_tx_graph::ChangeSet<ConfirmationBlockTime, keychain_txout::ChangeSet<Keychain>>,
 | 
			
		||||
    indexed_tx_graph::ChangeSet<ConfirmationBlockTime, keychain_txout::ChangeSet>,
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
#[derive(Subcommand, Debug, Clone)]
 | 
			
		||||
@ -84,7 +84,7 @@ impl EsploraArgs {
 | 
			
		||||
            Network::Bitcoin => "https://blockstream.info/api",
 | 
			
		||||
            Network::Testnet => "https://blockstream.info/testnet/api",
 | 
			
		||||
            Network::Regtest => "http://localhost:3002",
 | 
			
		||||
            Network::Signet => "https://mempool.space/signet/api",
 | 
			
		||||
            Network::Signet => "http://signet.bitcoindevkit.net",
 | 
			
		||||
            _ => panic!("unsupported network"),
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@ -96,7 +96,7 @@ impl EsploraArgs {
 | 
			
		||||
#[derive(Parser, Debug, Clone, PartialEq)]
 | 
			
		||||
pub struct ScanOptions {
 | 
			
		||||
    /// Max number of concurrent esplora server requests.
 | 
			
		||||
    #[clap(long, default_value = "1")]
 | 
			
		||||
    #[clap(long, default_value = "5")]
 | 
			
		||||
    pub parallel_requests: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,6 @@ version = "0.2.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
bdk_wallet = { path = "../../crates/wallet" }
 | 
			
		||||
bdk_wallet = { path = "../../crates/wallet", features = ["file_store"] }
 | 
			
		||||
bdk_electrum = { path = "../../crates/electrum" }
 | 
			
		||||
bdk_file_store = { path = "../../crates/file_store" }
 | 
			
		||||
anyhow = "1"
 | 
			
		||||
 | 
			
		||||
@ -1,53 +1,54 @@
 | 
			
		||||
const DB_MAGIC: &str = "bdk_wallet_electrum_example";
 | 
			
		||||
const SEND_AMOUNT: Amount = Amount::from_sat(5000);
 | 
			
		||||
const STOP_GAP: usize = 50;
 | 
			
		||||
const BATCH_SIZE: usize = 5;
 | 
			
		||||
 | 
			
		||||
use anyhow::anyhow;
 | 
			
		||||
use bdk_wallet::file_store::Store;
 | 
			
		||||
use bdk_wallet::Wallet;
 | 
			
		||||
use std::io::Write;
 | 
			
		||||
use std::str::FromStr;
 | 
			
		||||
 | 
			
		||||
use bdk_electrum::electrum_client;
 | 
			
		||||
use bdk_electrum::BdkElectrumClient;
 | 
			
		||||
use bdk_file_store::Store;
 | 
			
		||||
use bdk_wallet::bitcoin::Network;
 | 
			
		||||
use bdk_wallet::bitcoin::{Address, Amount};
 | 
			
		||||
use bdk_wallet::chain::collections::HashSet;
 | 
			
		||||
use bdk_wallet::{bitcoin::Network, Wallet};
 | 
			
		||||
use bdk_wallet::{KeychainKind, SignOptions};
 | 
			
		||||
 | 
			
		||||
const DB_MAGIC: &str = "bdk_wallet_electrum_example";
 | 
			
		||||
const SEND_AMOUNT: Amount = Amount::from_sat(5000);
 | 
			
		||||
const STOP_GAP: usize = 50;
 | 
			
		||||
const BATCH_SIZE: usize = 5;
 | 
			
		||||
 | 
			
		||||
const NETWORK: Network = Network::Testnet;
 | 
			
		||||
const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
 | 
			
		||||
const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
 | 
			
		||||
const ELECTRUM_URL: &str = "ssl://electrum.blockstream.info:60002";
 | 
			
		||||
 | 
			
		||||
fn main() -> Result<(), anyhow::Error> {
 | 
			
		||||
    let db_path = std::env::temp_dir().join("bdk-electrum-example");
 | 
			
		||||
    let mut db =
 | 
			
		||||
        Store::<bdk_wallet::wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?;
 | 
			
		||||
    let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
 | 
			
		||||
    let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
 | 
			
		||||
    let changeset = db
 | 
			
		||||
        .aggregate_changesets()
 | 
			
		||||
        .map_err(|e| anyhow!("load changes error: {}", e))?;
 | 
			
		||||
    let mut wallet = Wallet::new_or_load(
 | 
			
		||||
        external_descriptor,
 | 
			
		||||
        internal_descriptor,
 | 
			
		||||
        changeset,
 | 
			
		||||
        Network::Testnet,
 | 
			
		||||
    )?;
 | 
			
		||||
    let db_path = "bdk-electrum-example.db";
 | 
			
		||||
 | 
			
		||||
    let mut db = Store::<bdk_wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?;
 | 
			
		||||
 | 
			
		||||
    let wallet_opt = Wallet::load()
 | 
			
		||||
        .descriptors(EXTERNAL_DESC, INTERNAL_DESC)
 | 
			
		||||
        .network(NETWORK)
 | 
			
		||||
        .load_wallet(&mut db)?;
 | 
			
		||||
    let mut wallet = match wallet_opt {
 | 
			
		||||
        Some(wallet) => wallet,
 | 
			
		||||
        None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC)
 | 
			
		||||
            .network(NETWORK)
 | 
			
		||||
            .create_wallet(&mut db)?,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let address = wallet.next_unused_address(KeychainKind::External);
 | 
			
		||||
    if let Some(changeset) = wallet.take_staged() {
 | 
			
		||||
        db.append_changeset(&changeset)?;
 | 
			
		||||
    }
 | 
			
		||||
    wallet.persist(&mut db)?;
 | 
			
		||||
    println!("Generated Address: {}", address);
 | 
			
		||||
 | 
			
		||||
    let balance = wallet.balance();
 | 
			
		||||
    println!("Wallet balance before syncing: {} sats", balance.total());
 | 
			
		||||
 | 
			
		||||
    print!("Syncing...");
 | 
			
		||||
    let client = BdkElectrumClient::new(electrum_client::Client::new(
 | 
			
		||||
        "ssl://electrum.blockstream.info:60002",
 | 
			
		||||
    )?);
 | 
			
		||||
    let client = BdkElectrumClient::new(electrum_client::Client::new(ELECTRUM_URL)?);
 | 
			
		||||
 | 
			
		||||
    // Populate the electrum client's transaction cache so it doesn't redownload transaction we
 | 
			
		||||
    // already have.
 | 
			
		||||
    client.populate_tx_cache(&wallet);
 | 
			
		||||
    client.populate_tx_cache(wallet.tx_graph());
 | 
			
		||||
 | 
			
		||||
    let request = wallet
 | 
			
		||||
        .start_full_scan()
 | 
			
		||||
@ -71,9 +72,7 @@ fn main() -> Result<(), anyhow::Error> {
 | 
			
		||||
    println!();
 | 
			
		||||
 | 
			
		||||
    wallet.apply_update(update)?;
 | 
			
		||||
    if let Some(changeset) = wallet.take_staged() {
 | 
			
		||||
        db.append_changeset(&changeset)?;
 | 
			
		||||
    }
 | 
			
		||||
    wallet.persist(&mut db)?;
 | 
			
		||||
 | 
			
		||||
    let balance = wallet.balance();
 | 
			
		||||
    println!("Wallet balance after syncing: {} sats", balance.total());
 | 
			
		||||
 | 
			
		||||
@ -6,8 +6,7 @@ edition = "2021"
 | 
			
		||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
bdk_wallet = { path = "../../crates/wallet" }
 | 
			
		||||
bdk_wallet = { path = "../../crates/wallet", features = ["rusqlite"] }
 | 
			
		||||
bdk_esplora = { path = "../../crates/esplora", features = ["async-https"] }
 | 
			
		||||
bdk_sqlite = { path = "../../crates/sqlite" }
 | 
			
		||||
tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] }
 | 
			
		||||
anyhow = "1"
 | 
			
		||||
 | 
			
		||||
@ -1,76 +1,58 @@
 | 
			
		||||
use std::{collections::BTreeSet, io::Write, str::FromStr};
 | 
			
		||||
use std::{collections::BTreeSet, io::Write};
 | 
			
		||||
 | 
			
		||||
use anyhow::Ok;
 | 
			
		||||
use bdk_esplora::{esplora_client, EsploraAsyncExt};
 | 
			
		||||
use bdk_wallet::{
 | 
			
		||||
    bitcoin::{Address, Amount, Network, Script},
 | 
			
		||||
    bitcoin::{Amount, Network},
 | 
			
		||||
    rusqlite::Connection,
 | 
			
		||||
    KeychainKind, SignOptions, Wallet,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use bdk_sqlite::{rusqlite::Connection, Store};
 | 
			
		||||
 | 
			
		||||
const SEND_AMOUNT: Amount = Amount::from_sat(5000);
 | 
			
		||||
const STOP_GAP: usize = 50;
 | 
			
		||||
const STOP_GAP: usize = 5;
 | 
			
		||||
const PARALLEL_REQUESTS: usize = 5;
 | 
			
		||||
 | 
			
		||||
const DB_PATH: &str = "bdk-example-esplora-async.sqlite";
 | 
			
		||||
const NETWORK: Network = Network::Signet;
 | 
			
		||||
const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
 | 
			
		||||
const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
 | 
			
		||||
const ESPLORA_URL: &str = "http://signet.bitcoindevkit.net";
 | 
			
		||||
 | 
			
		||||
#[tokio::main]
 | 
			
		||||
async fn main() -> Result<(), anyhow::Error> {
 | 
			
		||||
    let db_path = "bdk-esplora-async-example.sqlite";
 | 
			
		||||
    let conn = Connection::open(db_path)?;
 | 
			
		||||
    let mut db = Store::new(conn)?;
 | 
			
		||||
    let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
 | 
			
		||||
    let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
 | 
			
		||||
    let changeset = db.read()?;
 | 
			
		||||
    let mut conn = Connection::open(DB_PATH)?;
 | 
			
		||||
 | 
			
		||||
    let mut wallet = Wallet::new_or_load(
 | 
			
		||||
        external_descriptor,
 | 
			
		||||
        internal_descriptor,
 | 
			
		||||
        changeset,
 | 
			
		||||
        Network::Signet,
 | 
			
		||||
    )?;
 | 
			
		||||
    let wallet_opt = Wallet::load()
 | 
			
		||||
        .descriptors(EXTERNAL_DESC, INTERNAL_DESC)
 | 
			
		||||
        .network(NETWORK)
 | 
			
		||||
        .load_wallet(&mut conn)?;
 | 
			
		||||
    let mut wallet = match wallet_opt {
 | 
			
		||||
        Some(wallet) => wallet,
 | 
			
		||||
        None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC)
 | 
			
		||||
            .network(NETWORK)
 | 
			
		||||
            .create_wallet(&mut conn)?,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let address = wallet.next_unused_address(KeychainKind::External);
 | 
			
		||||
    if let Some(changeset) = wallet.take_staged() {
 | 
			
		||||
        db.write(&changeset)?;
 | 
			
		||||
    }
 | 
			
		||||
    println!("Generated Address: {}", address);
 | 
			
		||||
    wallet.persist(&mut conn)?;
 | 
			
		||||
    println!("Next unused address: ({}) {}", address.index, address);
 | 
			
		||||
 | 
			
		||||
    let balance = wallet.balance();
 | 
			
		||||
    println!("Wallet balance before syncing: {} sats", balance.total());
 | 
			
		||||
 | 
			
		||||
    print!("Syncing...");
 | 
			
		||||
    let client = esplora_client::Builder::new("http://signet.bitcoindevkit.net").build_async()?;
 | 
			
		||||
    let client = esplora_client::Builder::new(ESPLORA_URL).build_async()?;
 | 
			
		||||
 | 
			
		||||
    fn generate_inspect(kind: KeychainKind) -> impl FnMut(u32, &Script) + Send + Sync + 'static {
 | 
			
		||||
        let mut once = Some(());
 | 
			
		||||
        let mut stdout = std::io::stdout();
 | 
			
		||||
        move |spk_i, _| {
 | 
			
		||||
            match once.take() {
 | 
			
		||||
                Some(_) => print!("\nScanning keychain [{:?}]", kind),
 | 
			
		||||
                None => print!(" {:<3}", spk_i),
 | 
			
		||||
            };
 | 
			
		||||
            stdout.flush().expect("must flush");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    let request = wallet
 | 
			
		||||
        .start_full_scan()
 | 
			
		||||
        .inspect_spks_for_all_keychains({
 | 
			
		||||
            let mut once = BTreeSet::<KeychainKind>::new();
 | 
			
		||||
            move |keychain, spk_i, _| {
 | 
			
		||||
                match once.insert(keychain) {
 | 
			
		||||
                    true => print!("\nScanning keychain [{:?}]", keychain),
 | 
			
		||||
                    false => print!(" {:<3}", spk_i),
 | 
			
		||||
                }
 | 
			
		||||
                std::io::stdout().flush().expect("must flush")
 | 
			
		||||
    let request = wallet.start_full_scan().inspect_spks_for_all_keychains({
 | 
			
		||||
        let mut once = BTreeSet::<KeychainKind>::new();
 | 
			
		||||
        move |keychain, spk_i, _| {
 | 
			
		||||
            if once.insert(keychain) {
 | 
			
		||||
                print!("\nScanning keychain [{:?}] ", keychain);
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
        .inspect_spks_for_keychain(
 | 
			
		||||
            KeychainKind::External,
 | 
			
		||||
            generate_inspect(KeychainKind::External),
 | 
			
		||||
        )
 | 
			
		||||
        .inspect_spks_for_keychain(
 | 
			
		||||
            KeychainKind::Internal,
 | 
			
		||||
            generate_inspect(KeychainKind::Internal),
 | 
			
		||||
        );
 | 
			
		||||
            print!(" {:<3}", spk_i);
 | 
			
		||||
            std::io::stdout().flush().expect("must flush")
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let mut update = client
 | 
			
		||||
        .full_scan(request, STOP_GAP, PARALLEL_REQUESTS)
 | 
			
		||||
@ -79,9 +61,7 @@ async fn main() -> Result<(), anyhow::Error> {
 | 
			
		||||
    let _ = update.graph_update.update_last_seen_unconfirmed(now);
 | 
			
		||||
 | 
			
		||||
    wallet.apply_update(update)?;
 | 
			
		||||
    if let Some(changeset) = wallet.take_staged() {
 | 
			
		||||
        db.write(&changeset)?;
 | 
			
		||||
    }
 | 
			
		||||
    wallet.persist(&mut conn)?;
 | 
			
		||||
    println!();
 | 
			
		||||
 | 
			
		||||
    let balance = wallet.balance();
 | 
			
		||||
@ -95,12 +75,9 @@ async fn main() -> Result<(), anyhow::Error> {
 | 
			
		||||
        std::process::exit(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let faucet_address = Address::from_str("mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt")?
 | 
			
		||||
        .require_network(Network::Signet)?;
 | 
			
		||||
 | 
			
		||||
    let mut tx_builder = wallet.build_tx();
 | 
			
		||||
    tx_builder
 | 
			
		||||
        .add_recipient(faucet_address.script_pubkey(), SEND_AMOUNT)
 | 
			
		||||
        .add_recipient(address.script_pubkey(), SEND_AMOUNT)
 | 
			
		||||
        .enable_rbf();
 | 
			
		||||
 | 
			
		||||
    let mut psbt = tx_builder.finish()?;
 | 
			
		||||
 | 
			
		||||
@ -7,7 +7,6 @@ publish = false
 | 
			
		||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
bdk_wallet = { path = "../../crates/wallet" }
 | 
			
		||||
bdk_wallet = { path = "../../crates/wallet", features = ["file_store"] }
 | 
			
		||||
bdk_esplora = { path = "../../crates/esplora", features = ["blocking"] }
 | 
			
		||||
bdk_file_store = { path = "../../crates/file_store" }
 | 
			
		||||
anyhow = "1"
 | 
			
		||||
 | 
			
		||||
@ -1,52 +1,57 @@
 | 
			
		||||
const DB_MAGIC: &str = "bdk_wallet_esplora_example";
 | 
			
		||||
const SEND_AMOUNT: Amount = Amount::from_sat(1000);
 | 
			
		||||
const STOP_GAP: usize = 5;
 | 
			
		||||
const PARALLEL_REQUESTS: usize = 1;
 | 
			
		||||
 | 
			
		||||
use std::{collections::BTreeSet, io::Write, str::FromStr};
 | 
			
		||||
use std::{collections::BTreeSet, io::Write};
 | 
			
		||||
 | 
			
		||||
use bdk_esplora::{esplora_client, EsploraExt};
 | 
			
		||||
use bdk_file_store::Store;
 | 
			
		||||
use bdk_wallet::{
 | 
			
		||||
    bitcoin::{Address, Amount, Network},
 | 
			
		||||
    bitcoin::{Amount, Network},
 | 
			
		||||
    file_store::Store,
 | 
			
		||||
    KeychainKind, SignOptions, Wallet,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
fn main() -> Result<(), anyhow::Error> {
 | 
			
		||||
    let db_path = std::env::temp_dir().join("bdk-esplora-example");
 | 
			
		||||
    let mut db =
 | 
			
		||||
        Store::<bdk_wallet::wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), db_path)?;
 | 
			
		||||
    let external_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
 | 
			
		||||
    let internal_descriptor = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
 | 
			
		||||
    let changeset = db.aggregate_changesets()?;
 | 
			
		||||
const DB_MAGIC: &str = "bdk_wallet_esplora_example";
 | 
			
		||||
const DB_PATH: &str = "bdk-example-esplora-blocking.db";
 | 
			
		||||
const SEND_AMOUNT: Amount = Amount::from_sat(5000);
 | 
			
		||||
const STOP_GAP: usize = 5;
 | 
			
		||||
const PARALLEL_REQUESTS: usize = 5;
 | 
			
		||||
 | 
			
		||||
    let mut wallet = Wallet::new_or_load(
 | 
			
		||||
        external_descriptor,
 | 
			
		||||
        internal_descriptor,
 | 
			
		||||
        changeset,
 | 
			
		||||
        Network::Testnet,
 | 
			
		||||
    )?;
 | 
			
		||||
const NETWORK: Network = Network::Signet;
 | 
			
		||||
const EXTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)";
 | 
			
		||||
const INTERNAL_DESC: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)";
 | 
			
		||||
const ESPLORA_URL: &str = "http://signet.bitcoindevkit.net";
 | 
			
		||||
 | 
			
		||||
fn main() -> Result<(), anyhow::Error> {
 | 
			
		||||
    let mut db = Store::<bdk_wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), DB_PATH)?;
 | 
			
		||||
 | 
			
		||||
    let wallet_opt = Wallet::load()
 | 
			
		||||
        .descriptors(EXTERNAL_DESC, INTERNAL_DESC)
 | 
			
		||||
        .network(NETWORK)
 | 
			
		||||
        .load_wallet(&mut db)?;
 | 
			
		||||
    let mut wallet = match wallet_opt {
 | 
			
		||||
        Some(wallet) => wallet,
 | 
			
		||||
        None => Wallet::create(EXTERNAL_DESC, INTERNAL_DESC)
 | 
			
		||||
            .network(NETWORK)
 | 
			
		||||
            .create_wallet(&mut db)?,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let address = wallet.next_unused_address(KeychainKind::External);
 | 
			
		||||
    if let Some(changeset) = wallet.take_staged() {
 | 
			
		||||
        db.append_changeset(&changeset)?;
 | 
			
		||||
    }
 | 
			
		||||
    println!("Generated Address: {}", address);
 | 
			
		||||
    wallet.persist(&mut db)?;
 | 
			
		||||
    println!(
 | 
			
		||||
        "Next unused address: ({}) {}",
 | 
			
		||||
        address.index, address.address
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    let balance = wallet.balance();
 | 
			
		||||
    println!("Wallet balance before syncing: {} sats", balance.total());
 | 
			
		||||
 | 
			
		||||
    print!("Syncing...");
 | 
			
		||||
    let client =
 | 
			
		||||
        esplora_client::Builder::new("https://blockstream.info/testnet/api").build_blocking();
 | 
			
		||||
    let client = esplora_client::Builder::new(ESPLORA_URL).build_blocking();
 | 
			
		||||
 | 
			
		||||
    let request = wallet.start_full_scan().inspect_spks_for_all_keychains({
 | 
			
		||||
        let mut once = BTreeSet::<KeychainKind>::new();
 | 
			
		||||
        move |keychain, spk_i, _| {
 | 
			
		||||
            match once.insert(keychain) {
 | 
			
		||||
                true => print!("\nScanning keychain [{:?}]", keychain),
 | 
			
		||||
                false => print!(" {:<3}", spk_i),
 | 
			
		||||
            };
 | 
			
		||||
            if once.insert(keychain) {
 | 
			
		||||
                print!("\nScanning keychain [{:?}] ", keychain);
 | 
			
		||||
            }
 | 
			
		||||
            print!(" {:<3}", spk_i);
 | 
			
		||||
            std::io::stdout().flush().expect("must flush")
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
@ -72,12 +77,9 @@ fn main() -> Result<(), anyhow::Error> {
 | 
			
		||||
        std::process::exit(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let faucet_address = Address::from_str("mkHS9ne12qx9pS9VojpwU5xtRd4T7X7ZUt")?
 | 
			
		||||
        .require_network(Network::Testnet)?;
 | 
			
		||||
 | 
			
		||||
    let mut tx_builder = wallet.build_tx();
 | 
			
		||||
    tx_builder
 | 
			
		||||
        .add_recipient(faucet_address.script_pubkey(), SEND_AMOUNT)
 | 
			
		||||
        .add_recipient(address.script_pubkey(), SEND_AMOUNT)
 | 
			
		||||
        .enable_rbf();
 | 
			
		||||
 | 
			
		||||
    let mut psbt = tx_builder.finish()?;
 | 
			
		||||
 | 
			
		||||
@ -6,8 +6,7 @@ edition = "2021"
 | 
			
		||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
bdk_wallet = { path = "../../crates/wallet" }
 | 
			
		||||
bdk_file_store = { path = "../../crates/file_store" }
 | 
			
		||||
bdk_wallet = { path = "../../crates/wallet", features = ["file_store"] }
 | 
			
		||||
bdk_bitcoind_rpc = { path = "../../crates/bitcoind_rpc" }
 | 
			
		||||
 | 
			
		||||
anyhow = "1"
 | 
			
		||||
 | 
			
		||||
@ -2,10 +2,10 @@ use bdk_bitcoind_rpc::{
 | 
			
		||||
    bitcoincore_rpc::{Auth, Client, RpcApi},
 | 
			
		||||
    Emitter,
 | 
			
		||||
};
 | 
			
		||||
use bdk_file_store::Store;
 | 
			
		||||
use bdk_wallet::{
 | 
			
		||||
    bitcoin::{Block, Network, Transaction},
 | 
			
		||||
    wallet::Wallet,
 | 
			
		||||
    file_store::Store,
 | 
			
		||||
    Wallet,
 | 
			
		||||
};
 | 
			
		||||
use clap::{self, Parser};
 | 
			
		||||
use std::{path::PathBuf, sync::mpsc::sync_channel, thread::spawn, time::Instant};
 | 
			
		||||
@ -86,18 +86,18 @@ fn main() -> anyhow::Result<()> {
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    let start_load_wallet = Instant::now();
 | 
			
		||||
    let mut db = Store::<bdk_wallet::wallet::ChangeSet>::open_or_create_new(
 | 
			
		||||
        DB_MAGIC.as_bytes(),
 | 
			
		||||
        args.db_path,
 | 
			
		||||
    )?;
 | 
			
		||||
    let changeset = db.aggregate_changesets()?;
 | 
			
		||||
 | 
			
		||||
    let mut wallet = Wallet::new_or_load(
 | 
			
		||||
        &args.descriptor,
 | 
			
		||||
        &args.change_descriptor,
 | 
			
		||||
        changeset,
 | 
			
		||||
        args.network,
 | 
			
		||||
    )?;
 | 
			
		||||
    let mut db =
 | 
			
		||||
        Store::<bdk_wallet::ChangeSet>::open_or_create_new(DB_MAGIC.as_bytes(), args.db_path)?;
 | 
			
		||||
    let wallet_opt = Wallet::load()
 | 
			
		||||
        .descriptors(args.descriptor.clone(), args.change_descriptor.clone())
 | 
			
		||||
        .network(args.network)
 | 
			
		||||
        .load_wallet(&mut db)?;
 | 
			
		||||
    let mut wallet = match wallet_opt {
 | 
			
		||||
        Some(wallet) => wallet,
 | 
			
		||||
        None => Wallet::create(args.descriptor, args.change_descriptor)
 | 
			
		||||
            .network(args.network)
 | 
			
		||||
            .create_wallet(&mut db)?,
 | 
			
		||||
    };
 | 
			
		||||
    println!(
 | 
			
		||||
        "Loaded wallet in {}s",
 | 
			
		||||
        start_load_wallet.elapsed().as_secs_f32()
 | 
			
		||||
@ -146,9 +146,7 @@ fn main() -> anyhow::Result<()> {
 | 
			
		||||
                let connected_to = block_emission.connected_to();
 | 
			
		||||
                let start_apply_block = Instant::now();
 | 
			
		||||
                wallet.apply_block_connected_to(&block_emission.block, height, connected_to)?;
 | 
			
		||||
                if let Some(changeset) = wallet.take_staged() {
 | 
			
		||||
                    db.append_changeset(&changeset)?;
 | 
			
		||||
                }
 | 
			
		||||
                wallet.persist(&mut db)?;
 | 
			
		||||
                let elapsed = start_apply_block.elapsed().as_secs_f32();
 | 
			
		||||
                println!(
 | 
			
		||||
                    "Applied block {} at height {} in {}s",
 | 
			
		||||
@ -158,9 +156,7 @@ fn main() -> anyhow::Result<()> {
 | 
			
		||||
            Emission::Mempool(mempool_emission) => {
 | 
			
		||||
                let start_apply_mempool = Instant::now();
 | 
			
		||||
                wallet.apply_unconfirmed_txs(mempool_emission.iter().map(|(tx, time)| (tx, *time)));
 | 
			
		||||
                if let Some(changeset) = wallet.take_staged() {
 | 
			
		||||
                    db.append_changeset(&changeset)?;
 | 
			
		||||
                }
 | 
			
		||||
                wallet.persist(&mut db)?;
 | 
			
		||||
                println!(
 | 
			
		||||
                    "Applied unconfirmed transactions in {}s",
 | 
			
		||||
                    start_apply_mempool.elapsed().as_secs_f32()
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user