]> Untitled Git - bdk/commitdiff
feat(esplora)!: update to use new sync/full-scan structures
author志宇 <hello@evanlinjin.me>
Thu, 25 Apr 2024 02:36:06 +0000 (10:36 +0800)
committer志宇 <hello@evanlinjin.me>
Fri, 26 Apr 2024 07:09:21 +0000 (15:09 +0800)
crates/esplora/src/async_ext.rs
crates/esplora/src/blocking_ext.rs
crates/esplora/src/lib.rs
crates/esplora/tests/async_ext.rs
crates/esplora/tests/blocking_ext.rs
example-crates/example_esplora/src/main.rs
example-crates/wallet_esplora_async/src/main.rs
example-crates/wallet_esplora_blocking/src/main.rs

index 9d02646c62d4a84722873a10fe97bbea21e026fa..2942d27441df3c30712c7c38df6d12ebdcc7bdbf 100644 (file)
@@ -1,6 +1,7 @@
 use std::collections::BTreeSet;
 
 use async_trait::async_trait;
+use bdk_chain::spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult};
 use bdk_chain::Anchor;
 use bdk_chain::{
     bitcoin::{BlockHash, OutPoint, ScriptBuf, TxOut, Txid},
@@ -11,7 +12,7 @@ use bdk_chain::{
 use esplora_client::{Amount, TxStatus};
 use futures::{stream::FuturesOrdered, TryStreamExt};
 
-use crate::{anchor_from_status, FullScanUpdate, SyncUpdate};
+use crate::anchor_from_status;
 
 /// [`esplora_client::Error`]
 type Error = Box<esplora_client::Error>;
@@ -50,14 +51,10 @@ pub trait EsploraAsyncExt {
     /// [`LocalChain::tip`]: bdk_chain::local_chain::LocalChain::tip
     async fn full_scan<K: Ord + Clone + Send>(
         &self,
-        local_tip: CheckPoint,
-        keychain_spks: BTreeMap<
-            K,
-            impl IntoIterator<IntoIter = impl Iterator<Item = (u32, ScriptBuf)> + Send> + Send,
-        >,
+        request: FullScanRequest<K>,
         stop_gap: usize,
         parallel_requests: usize,
-    ) -> Result<FullScanUpdate<K>, Error>;
+    ) -> Result<FullScanResult<K>, Error>;
 
     /// Sync a set of scripts with the blockchain (via an Esplora client) for the data
     /// specified and return a [`TxGraph`].
@@ -75,12 +72,9 @@ pub trait EsploraAsyncExt {
     /// [`full_scan`]: EsploraAsyncExt::full_scan
     async fn sync(
         &self,
-        local_tip: CheckPoint,
-        misc_spks: impl IntoIterator<IntoIter = impl Iterator<Item = ScriptBuf> + Send> + Send,
-        txids: impl IntoIterator<IntoIter = impl Iterator<Item = Txid> + Send> + Send,
-        outpoints: impl IntoIterator<IntoIter = impl Iterator<Item = OutPoint> + Send> + Send,
+        request: SyncRequest,
         parallel_requests: usize,
-    ) -> Result<SyncUpdate, Error>;
+    ) -> Result<SyncResult, Error>;
 }
 
 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -88,42 +82,56 @@ pub trait EsploraAsyncExt {
 impl EsploraAsyncExt for esplora_client::AsyncClient {
     async fn full_scan<K: Ord + Clone + Send>(
         &self,
-        local_tip: CheckPoint,
-        keychain_spks: BTreeMap<
-            K,
-            impl IntoIterator<IntoIter = impl Iterator<Item = (u32, ScriptBuf)> + Send> + Send,
-        >,
+        request: FullScanRequest<K>,
         stop_gap: usize,
         parallel_requests: usize,
-    ) -> Result<FullScanUpdate<K>, Error> {
+    ) -> Result<FullScanResult<K>, Error> {
         let latest_blocks = fetch_latest_blocks(self).await?;
-        let (tx_graph, last_active_indices) =
-            full_scan_for_index_and_graph(self, keychain_spks, stop_gap, parallel_requests).await?;
-        let local_chain =
-            chain_update(self, &latest_blocks, &local_tip, tx_graph.all_anchors()).await?;
-        Ok(FullScanUpdate {
-            local_chain,
-            tx_graph,
+        let (graph_update, last_active_indices) = full_scan_for_index_and_graph(
+            self,
+            request.spks_by_keychain,
+            stop_gap,
+            parallel_requests,
+        )
+        .await?;
+        let chain_update = chain_update(
+            self,
+            &latest_blocks,
+            &request.chain_tip,
+            graph_update.all_anchors(),
+        )
+        .await?;
+        Ok(FullScanResult {
+            chain_update,
+            graph_update,
             last_active_indices,
         })
     }
 
     async fn sync(
         &self,
-        local_tip: CheckPoint,
-        misc_spks: impl IntoIterator<IntoIter = impl Iterator<Item = ScriptBuf> + Send> + Send,
-        txids: impl IntoIterator<IntoIter = impl Iterator<Item = Txid> + Send> + Send,
-        outpoints: impl IntoIterator<IntoIter = impl Iterator<Item = OutPoint> + Send> + Send,
+        request: SyncRequest,
         parallel_requests: usize,
-    ) -> Result<SyncUpdate, Error> {
+    ) -> Result<SyncResult, Error> {
         let latest_blocks = fetch_latest_blocks(self).await?;
-        let tx_graph =
-            sync_for_index_and_graph(self, misc_spks, txids, outpoints, parallel_requests).await?;
-        let local_chain =
-            chain_update(self, &latest_blocks, &local_tip, tx_graph.all_anchors()).await?;
-        Ok(SyncUpdate {
-            tx_graph,
-            local_chain,
+        let graph_update = sync_for_index_and_graph(
+            self,
+            request.spks,
+            request.txids,
+            request.outpoints,
+            parallel_requests,
+        )
+        .await?;
+        let chain_update = chain_update(
+            self,
+            &latest_blocks,
+            &request.chain_tip,
+            graph_update.all_anchors(),
+        )
+        .await?;
+        Ok(SyncResult {
+            chain_update,
+            graph_update,
         })
     }
 }
index 7037385690729540974b4d2e54510a9453e5399c..469ab52e65520bc24382cfb2b1dd9ada2d6d0830 100644 (file)
@@ -3,6 +3,7 @@ use std::thread::JoinHandle;
 use std::usize;
 
 use bdk_chain::collections::BTreeMap;
+use bdk_chain::spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult};
 use bdk_chain::Anchor;
 use bdk_chain::{
     bitcoin::{Amount, BlockHash, OutPoint, ScriptBuf, TxOut, Txid},
@@ -12,8 +13,6 @@ use bdk_chain::{
 use esplora_client::TxStatus;
 
 use crate::anchor_from_status;
-use crate::FullScanUpdate;
-use crate::SyncUpdate;
 
 /// [`esplora_client::Error`]
 pub type Error = Box<esplora_client::Error>;
@@ -50,11 +49,10 @@ pub trait EsploraExt {
     /// [`LocalChain::tip`]: bdk_chain::local_chain::LocalChain::tip
     fn full_scan<K: Ord + Clone>(
         &self,
-        local_tip: CheckPoint,
-        keychain_spks: BTreeMap<K, impl IntoIterator<Item = (u32, ScriptBuf)>>,
+        request: FullScanRequest<K>,
         stop_gap: usize,
         parallel_requests: usize,
-    ) -> Result<FullScanUpdate<K>, Error>;
+    ) -> Result<FullScanResult<K>, Error>;
 
     /// Sync a set of scripts with the blockchain (via an Esplora client) for the data
     /// specified and return a [`TxGraph`].
@@ -70,59 +68,54 @@ pub trait EsploraExt {
     ///
     /// [`LocalChain::tip`]: bdk_chain::local_chain::LocalChain::tip
     /// [`full_scan`]: EsploraExt::full_scan
-    fn sync(
-        &self,
-        local_tip: CheckPoint,
-        misc_spks: impl IntoIterator<Item = ScriptBuf>,
-        txids: impl IntoIterator<Item = Txid>,
-        outpoints: impl IntoIterator<Item = OutPoint>,
-        parallel_requests: usize,
-    ) -> Result<SyncUpdate, Error>;
+    fn sync(&self, request: SyncRequest, parallel_requests: usize) -> Result<SyncResult, Error>;
 }
 
 impl EsploraExt for esplora_client::BlockingClient {
     fn full_scan<K: Ord + Clone>(
         &self,
-        local_tip: CheckPoint,
-        keychain_spks: BTreeMap<K, impl IntoIterator<Item = (u32, ScriptBuf)>>,
+        request: FullScanRequest<K>,
         stop_gap: usize,
         parallel_requests: usize,
-    ) -> Result<FullScanUpdate<K>, Error> {
+    ) -> Result<FullScanResult<K>, Error> {
         let latest_blocks = fetch_latest_blocks(self)?;
-        let (tx_graph, last_active_indices) = full_scan_for_index_and_graph_blocking(
+        let (graph_update, last_active_indices) = full_scan_for_index_and_graph_blocking(
             self,
-            keychain_spks,
+            request.spks_by_keychain,
             stop_gap,
             parallel_requests,
         )?;
-        let local_chain = chain_update(self, &latest_blocks, &local_tip, tx_graph.all_anchors())?;
-        Ok(FullScanUpdate {
-            local_chain,
-            tx_graph,
+        let chain_update = chain_update(
+            self,
+            &latest_blocks,
+            &request.chain_tip,
+            graph_update.all_anchors(),
+        )?;
+        Ok(FullScanResult {
+            chain_update,
+            graph_update,
             last_active_indices,
         })
     }
 
-    fn sync(
-        &self,
-        local_tip: CheckPoint,
-        misc_spks: impl IntoIterator<Item = ScriptBuf>,
-        txids: impl IntoIterator<Item = Txid>,
-        outpoints: impl IntoIterator<Item = OutPoint>,
-        parallel_requests: usize,
-    ) -> Result<SyncUpdate, Error> {
+    fn sync(&self, request: SyncRequest, parallel_requests: usize) -> Result<SyncResult, Error> {
         let latest_blocks = fetch_latest_blocks(self)?;
-        let tx_graph = sync_for_index_and_graph_blocking(
+        let graph_update = sync_for_index_and_graph_blocking(
             self,
-            misc_spks,
-            txids,
-            outpoints,
+            request.spks,
+            request.txids,
+            request.outpoints,
             parallel_requests,
         )?;
-        let local_chain = chain_update(self, &latest_blocks, &local_tip, tx_graph.all_anchors())?;
-        Ok(SyncUpdate {
-            local_chain,
-            tx_graph,
+        let chain_update = chain_update(
+            self,
+            &latest_blocks,
+            &request.chain_tip,
+            graph_update.all_anchors(),
+        )?;
+        Ok(SyncResult {
+            chain_update,
+            graph_update,
         })
     }
 }
index 37d7dd26e5dfb5023d731b233ffcf6561ffb5e88..535167ff25b5bb3908272bb53bd182952d22e1b2 100644 (file)
@@ -16,9 +16,7 @@
 //! [`TxGraph`]: bdk_chain::tx_graph::TxGraph
 //! [`example_esplora`]: https://github.com/bitcoindevkit/bdk/tree/master/example-crates/example_esplora
 
-use std::collections::BTreeMap;
-
-use bdk_chain::{local_chain::CheckPoint, BlockId, ConfirmationTimeHeightAnchor, TxGraph};
+use bdk_chain::{BlockId, ConfirmationTimeHeightAnchor};
 use esplora_client::TxStatus;
 
 pub use esplora_client;
@@ -50,21 +48,3 @@ fn anchor_from_status(status: &TxStatus) -> Option<ConfirmationTimeHeightAnchor>
         None
     }
 }
-
-/// Update returns from a full scan.
-pub struct FullScanUpdate<K> {
-    /// The update to apply to the receiving [`LocalChain`](bdk_chain::local_chain::LocalChain).
-    pub local_chain: CheckPoint,
-    /// The update to apply to the receiving [`TxGraph`].
-    pub tx_graph: TxGraph<ConfirmationTimeHeightAnchor>,
-    /// Last active indices for the corresponding keychains (`K`).
-    pub last_active_indices: BTreeMap<K, u32>,
-}
-
-/// Update returned from a sync.
-pub struct SyncUpdate {
-    /// The update to apply to the receiving [`LocalChain`](bdk_chain::local_chain::LocalChain).
-    pub local_chain: CheckPoint,
-    /// The update to apply to the receiving [`TxGraph`].
-    pub tx_graph: TxGraph<ConfirmationTimeHeightAnchor>,
-}
index f6954fe11f477654b9f17f4746b7b4fc813828ba..6f7956d46e518c9624d3d8f87aba43e1594cfc62 100644 (file)
@@ -1,8 +1,9 @@
+use bdk_chain::spk_client::{FullScanRequest, SyncRequest};
 use bdk_esplora::EsploraAsyncExt;
 use electrsd::bitcoind::anyhow;
 use electrsd::bitcoind::bitcoincore_rpc::RpcApi;
 use esplora_client::{self, Builder};
-use std::collections::{BTreeMap, BTreeSet, HashSet};
+use std::collections::{BTreeSet, HashSet};
 use std::str::FromStr;
 use std::thread::sleep;
 use std::time::Duration;
@@ -55,20 +56,15 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
     // use a full checkpoint linked list (since this is not what we are testing)
     let cp_tip = env.make_checkpoint_tip();
 
-    let sync_update = client
-        .sync(
-            cp_tip.clone(),
-            misc_spks.into_iter(),
-            vec![].into_iter(),
-            vec![].into_iter(),
-            1,
-        )
-        .await?;
+    let sync_update = {
+        let request = SyncRequest::from_chain_tip(cp_tip.clone()).set_spks(misc_spks);
+        client.sync(request, 1).await?
+    };
 
     assert!(
         {
             let update_cps = sync_update
-                .local_chain
+                .chain_update
                 .iter()
                 .map(|cp| cp.block_id())
                 .collect::<BTreeSet<_>>();
@@ -81,7 +77,7 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
         "update should not alter original checkpoint tip since we already started with all checkpoints",
     );
 
-    let graph_update = sync_update.tx_graph;
+    let graph_update = sync_update.graph_update;
     // Check to see if we have the floating txouts available from our two created transactions'
     // previous outputs in order to calculate transaction fees.
     for tx in graph_update.full_txs() {
@@ -142,8 +138,6 @@ pub async fn test_async_update_tx_graph_stop_gap() -> anyhow::Result<()> {
         .enumerate()
         .map(|(i, addr)| (i as u32, addr.script_pubkey()))
         .collect();
-    let mut keychains = BTreeMap::new();
-    keychains.insert(0, spks);
 
     // Then receive coins on the 4th address.
     let txid_4th_addr = env.bitcoind.client.send_to_address(
@@ -166,16 +160,25 @@ pub async fn test_async_update_tx_graph_stop_gap() -> anyhow::Result<()> {
 
     // A scan with a gap limit of 3 won't find the transaction, but a scan with a gap limit of 4
     // will.
-    let full_scan_update = client
-        .full_scan(cp_tip.clone(), keychains.clone(), 3, 1)
-        .await?;
-    assert!(full_scan_update.tx_graph.full_txs().next().is_none());
+    let full_scan_update = {
+        let request =
+            FullScanRequest::from_chain_tip(cp_tip.clone()).set_spks_for_keychain(0, spks.clone());
+        client.full_scan(request, 3, 1).await?
+    };
+    assert!(full_scan_update.graph_update.full_txs().next().is_none());
     assert!(full_scan_update.last_active_indices.is_empty());
-    let full_scan_update = client
-        .full_scan(cp_tip.clone(), keychains.clone(), 4, 1)
-        .await?;
+    let full_scan_update = {
+        let request =
+            FullScanRequest::from_chain_tip(cp_tip.clone()).set_spks_for_keychain(0, spks.clone());
+        client.full_scan(request, 4, 1).await?
+    };
     assert_eq!(
-        full_scan_update.tx_graph.full_txs().next().unwrap().txid,
+        full_scan_update
+            .graph_update
+            .full_txs()
+            .next()
+            .unwrap()
+            .txid,
         txid_4th_addr
     );
     assert_eq!(full_scan_update.last_active_indices[&0], 3);
@@ -198,20 +201,26 @@ pub async fn test_async_update_tx_graph_stop_gap() -> anyhow::Result<()> {
 
     // A scan with gap limit 5 won't find the second transaction, but a scan with gap limit 6 will.
     // The last active indice won't be updated in the first case but will in the second one.
-    let full_scan_update = client
-        .full_scan(cp_tip.clone(), keychains.clone(), 5, 1)
-        .await?;
+    let full_scan_update = {
+        let request =
+            FullScanRequest::from_chain_tip(cp_tip.clone()).set_spks_for_keychain(0, spks.clone());
+        client.full_scan(request, 5, 1).await?
+    };
     let txs: HashSet<_> = full_scan_update
-        .tx_graph
+        .graph_update
         .full_txs()
         .map(|tx| tx.txid)
         .collect();
     assert_eq!(txs.len(), 1);
     assert!(txs.contains(&txid_4th_addr));
     assert_eq!(full_scan_update.last_active_indices[&0], 3);
-    let full_scan_update = client.full_scan(cp_tip, keychains, 6, 1).await?;
+    let full_scan_update = {
+        let request =
+            FullScanRequest::from_chain_tip(cp_tip.clone()).set_spks_for_keychain(0, spks.clone());
+        client.full_scan(request, 6, 1).await?
+    };
     let txs: HashSet<_> = full_scan_update
-        .tx_graph
+        .graph_update
         .full_txs()
         .map(|tx| tx.txid)
         .collect();
index 40e446a4ed944f56e8036762907bab56ba9f6dbc..61c2466d742f2bd6bcf6ab348613d57e4d897d73 100644 (file)
@@ -1,8 +1,9 @@
+use bdk_chain::spk_client::{FullScanRequest, SyncRequest};
 use bdk_esplora::EsploraExt;
 use electrsd::bitcoind::anyhow;
 use electrsd::bitcoind::bitcoincore_rpc::RpcApi;
 use esplora_client::{self, Builder};
-use std::collections::{BTreeMap, BTreeSet, HashSet};
+use std::collections::{BTreeSet, HashSet};
 use std::str::FromStr;
 use std::thread::sleep;
 use std::time::Duration;
@@ -55,18 +56,15 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
     // use a full checkpoint linked list (since this is not what we are testing)
     let cp_tip = env.make_checkpoint_tip();
 
-    let sync_update = client.sync(
-        cp_tip.clone(),
-        misc_spks.into_iter(),
-        vec![].into_iter(),
-        vec![].into_iter(),
-        1,
-    )?;
+    let sync_update = {
+        let request = SyncRequest::from_chain_tip(cp_tip.clone()).set_spks(misc_spks);
+        client.sync(request, 1)?
+    };
 
     assert!(
         {
             let update_cps = sync_update
-                .local_chain
+                .chain_update
                 .iter()
                 .map(|cp| cp.block_id())
                 .collect::<BTreeSet<_>>();
@@ -79,7 +77,7 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
         "update should not alter original checkpoint tip since we already started with all checkpoints",
     );
 
-    let graph_update = sync_update.tx_graph;
+    let graph_update = sync_update.graph_update;
     // Check to see if we have the floating txouts available from our two created transactions'
     // previous outputs in order to calculate transaction fees.
     for tx in graph_update.full_txs() {
@@ -141,8 +139,6 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
         .enumerate()
         .map(|(i, addr)| (i as u32, addr.script_pubkey()))
         .collect();
-    let mut keychains = BTreeMap::new();
-    keychains.insert(0, spks);
 
     // Then receive coins on the 4th address.
     let txid_4th_addr = env.bitcoind.client.send_to_address(
@@ -165,12 +161,25 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
 
     // A scan with a stop_gap of 3 won't find the transaction, but a scan with a gap limit of 4
     // will.
-    let full_scan_update = client.full_scan(cp_tip.clone(), keychains.clone(), 3, 1)?;
-    assert!(full_scan_update.tx_graph.full_txs().next().is_none());
+    let full_scan_update = {
+        let request =
+            FullScanRequest::from_chain_tip(cp_tip.clone()).set_spks_for_keychain(0, spks.clone());
+        client.full_scan(request, 3, 1)?
+    };
+    assert!(full_scan_update.graph_update.full_txs().next().is_none());
     assert!(full_scan_update.last_active_indices.is_empty());
-    let full_scan_update = client.full_scan(cp_tip.clone(), keychains.clone(), 4, 1)?;
+    let full_scan_update = {
+        let request =
+            FullScanRequest::from_chain_tip(cp_tip.clone()).set_spks_for_keychain(0, spks.clone());
+        client.full_scan(request, 4, 1)?
+    };
     assert_eq!(
-        full_scan_update.tx_graph.full_txs().next().unwrap().txid,
+        full_scan_update
+            .graph_update
+            .full_txs()
+            .next()
+            .unwrap()
+            .txid,
         txid_4th_addr
     );
     assert_eq!(full_scan_update.last_active_indices[&0], 3);
@@ -193,18 +202,26 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
 
     // A scan with gap limit 5 won't find the second transaction, but a scan with gap limit 6 will.
     // The last active indice won't be updated in the first case but will in the second one.
-    let full_scan_update = client.full_scan(cp_tip.clone(), keychains.clone(), 5, 1)?;
+    let full_scan_update = {
+        let request =
+            FullScanRequest::from_chain_tip(cp_tip.clone()).set_spks_for_keychain(0, spks.clone());
+        client.full_scan(request, 5, 1)?
+    };
     let txs: HashSet<_> = full_scan_update
-        .tx_graph
+        .graph_update
         .full_txs()
         .map(|tx| tx.txid)
         .collect();
     assert_eq!(txs.len(), 1);
     assert!(txs.contains(&txid_4th_addr));
     assert_eq!(full_scan_update.last_active_indices[&0], 3);
-    let full_scan_update = client.full_scan(cp_tip.clone(), keychains, 6, 1)?;
+    let full_scan_update = {
+        let request =
+            FullScanRequest::from_chain_tip(cp_tip.clone()).set_spks_for_keychain(0, spks.clone());
+        client.full_scan(request, 6, 1)?
+    };
     let txs: HashSet<_> = full_scan_update
-        .tx_graph
+        .graph_update
         .full_txs()
         .map(|tx| tx.txid)
         .collect();
index 33aab27698d93861c837b621c20811dfb469cb8b..46eb18b810334eb36650a4f246145401344db52a 100644 (file)
@@ -1,14 +1,15 @@
 use std::{
-    collections::BTreeMap,
+    collections::BTreeSet,
     io::{self, Write},
     sync::Mutex,
 };
 
 use bdk_chain::{
-    bitcoin::{constants::genesis_block, Address, Network, OutPoint, ScriptBuf, Txid},
+    bitcoin::{constants::genesis_block, Address, Network, Txid},
     indexed_tx_graph::{self, IndexedTxGraph},
     keychain,
     local_chain::{self, LocalChain},
+    spk_client::{FullScanRequest, SyncRequest},
     Append, ConfirmationTimeHeightAnchor,
 };
 
@@ -167,45 +168,34 @@ fn main() -> anyhow::Result<()> {
             scan_options,
             ..
         } => {
-            let local_tip = chain.lock().expect("mutex must not be poisoned").tip();
-            let keychain_spks = graph
-                .lock()
-                .expect("mutex must not be poisoned")
-                .index
-                .all_unbounded_spk_iters()
-                .into_iter()
-                // This `map` is purely for logging.
-                .map(|(keychain, iter)| {
-                    let mut first = true;
-                    let spk_iter = iter.inspect(move |(i, _)| {
-                        if first {
-                            eprint!("\nscanning {}: ", keychain);
-                            first = false;
+            let request = {
+                let chain_tip = chain.lock().expect("mutex must not be poisoned").tip();
+                let indexed_graph = &*graph.lock().expect("mutex must not be poisoned");
+                FullScanRequest::from_keychain_txout_index(chain_tip, &indexed_graph.index)
+                    .inspect_spks_for_all_keychains({
+                        let mut once = BTreeSet::<Keychain>::new();
+                        move |keychain, spk_i, _| {
+                            if once.insert(keychain) {
+                                eprint!("\nscanning {}: ", keychain);
+                            }
+                            eprint!("{} ", spk_i);
+                            // Flush early to ensure we print at every iteration.
+                            let _ = io::stderr().flush();
                         }
-                        eprint!("{} ", i);
-                        // Flush early to ensure we print at every iteration.
-                        let _ = io::stderr().flush();
-                    });
-                    (keychain, spk_iter)
-                })
-                .collect::<BTreeMap<_, _>>();
+                    })
+            };
 
             // The client scans keychain spks for transaction histories, stopping after `stop_gap`
             // is reached. It returns a `TxGraph` update (`graph_update`) and a structure that
             // represents the last active spk derivation indices of keychains
             // (`keychain_indices_update`).
             let mut update = client
-                .full_scan(
-                    local_tip,
-                    keychain_spks,
-                    *stop_gap,
-                    scan_options.parallel_requests,
-                )
+                .full_scan(request, *stop_gap, scan_options.parallel_requests)
                 .context("scanning for transactions")?;
 
             // We want to keep track of the latest time a transaction was seen unconfirmed.
             let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
-            let _ = update.tx_graph.update_last_seen_unconfirmed(now);
+            let _ = update.graph_update.update_last_seen_unconfirmed(now);
 
             let mut graph = graph.lock().expect("mutex must not be poisoned");
             let mut chain = chain.lock().expect("mutex must not be poisoned");
@@ -213,11 +203,11 @@ fn main() -> anyhow::Result<()> {
             // deriviation indices. Usually before a scan you are on a fresh wallet with no
             // addresses derived so we need to derive up to last active addresses the scan found
             // before adding the transactions.
-            (chain.apply_update(update.local_chain)?, {
+            (chain.apply_update(update.chain_update)?, {
                 let (_, index_changeset) = graph
                     .index
                     .reveal_to_target_multi(&update.last_active_indices);
-                let mut indexed_tx_graph_changeset = graph.apply_update(update.tx_graph);
+                let mut indexed_tx_graph_changeset = graph.apply_update(update.graph_update);
                 indexed_tx_graph_changeset.append(index_changeset.into());
                 indexed_tx_graph_changeset
             })
@@ -241,12 +231,9 @@ fn main() -> anyhow::Result<()> {
                 unused_spks = false;
             }
 
-            // Spks, outpoints and txids we want updates on will be accumulated here.
-            let mut spks: Box<dyn Iterator<Item = ScriptBuf>> = Box::new(core::iter::empty());
-            let mut outpoints: Box<dyn Iterator<Item = OutPoint>> = Box::new(core::iter::empty());
-            let mut txids: Box<dyn Iterator<Item = Txid>> = Box::new(core::iter::empty());
-
             let local_tip = chain.lock().expect("mutex must not be poisoned").tip();
+            // Spks, outpoints and txids we want updates on will be accumulated here.
+            let mut request = SyncRequest::from_chain_tip(local_tip.clone());
 
             // Get a short lock on the structures to get spks, utxos, and txs that we are interested
             // in.
@@ -260,12 +247,12 @@ fn main() -> anyhow::Result<()> {
                         .revealed_spks(..)
                         .map(|(k, i, spk)| (k.to_owned(), i, spk.to_owned()))
                         .collect::<Vec<_>>();
-                    spks = Box::new(spks.chain(all_spks.into_iter().map(|(k, i, spk)| {
+                    request = request.chain_spks(all_spks.into_iter().map(|(k, i, spk)| {
                         eprintln!("scanning {}:{}", k, i);
                         // Flush early to ensure we print at every iteration.
                         let _ = io::stderr().flush();
                         spk
-                    })));
+                    }));
                 }
                 if unused_spks {
                     let unused_spks = graph
@@ -273,17 +260,18 @@ fn main() -> anyhow::Result<()> {
                         .unused_spks()
                         .map(|(k, i, spk)| (k, i, spk.to_owned()))
                         .collect::<Vec<_>>();
-                    spks = Box::new(spks.chain(unused_spks.into_iter().map(|(k, i, spk)| {
-                        eprintln!(
-                            "Checking if address {} {}:{} has been used",
-                            Address::from_script(&spk, args.network).unwrap(),
-                            k,
-                            i,
-                        );
-                        // Flush early to ensure we print at every iteration.
-                        let _ = io::stderr().flush();
-                        spk
-                    })));
+                    request =
+                        request.chain_spks(unused_spks.into_iter().map(move |(k, i, spk)| {
+                            eprintln!(
+                                "Checking if address {} {}:{} has been used",
+                                Address::from_script(&spk, args.network).unwrap(),
+                                k,
+                                i,
+                            );
+                            // Flush early to ensure we print at every iteration.
+                            let _ = io::stderr().flush();
+                            spk
+                        }));
                 }
                 if utxos {
                     // We want to search for whether the UTXO is spent, and spent by which
@@ -295,7 +283,7 @@ fn main() -> anyhow::Result<()> {
                         .filter_chain_unspents(&*chain, local_tip.block_id(), init_outpoints)
                         .map(|(_, utxo)| utxo)
                         .collect::<Vec<_>>();
-                    outpoints = Box::new(
+                    request = request.chain_outpoints(
                         utxos
                             .into_iter()
                             .inspect(|utxo| {
@@ -319,7 +307,7 @@ fn main() -> anyhow::Result<()> {
                         .filter(|canonical_tx| !canonical_tx.chain_position.is_confirmed())
                         .map(|canonical_tx| canonical_tx.tx_node.txid)
                         .collect::<Vec<Txid>>();
-                    txids = Box::new(unconfirmed_txids.into_iter().inspect(|txid| {
+                    request = request.chain_txids(unconfirmed_txids.into_iter().inspect(|txid| {
                         eprintln!("Checking if {} is confirmed yet", txid);
                         // Flush early to ensure we print at every iteration.
                         let _ = io::stderr().flush();
@@ -327,21 +315,15 @@ fn main() -> anyhow::Result<()> {
                 }
             }
 
-            let mut update = client.sync(
-                local_tip,
-                spks,
-                txids,
-                outpoints,
-                scan_options.parallel_requests,
-            )?;
+            let mut update = client.sync(request, scan_options.parallel_requests)?;
 
             // Update last seen unconfirmed
             let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
-            let _ = update.tx_graph.update_last_seen_unconfirmed(now);
+            let _ = update.graph_update.update_last_seen_unconfirmed(now);
 
             (
-                chain.lock().unwrap().apply_update(update.local_chain)?,
-                graph.lock().unwrap().apply_update(update.tx_graph),
+                chain.lock().unwrap().apply_update(update.chain_update)?,
+                graph.lock().unwrap().apply_update(update.graph_update),
             )
         }
     };
index c37b6e665062e2364196055577d47fc75c0a4057..d89e5fd2099625bed8f1c370bb2e4d8f0c2921f5 100644 (file)
@@ -1,8 +1,7 @@
-use std::{io::Write, str::FromStr};
+use std::{collections::BTreeSet, io::Write, str::FromStr};
 
 use bdk::{
-    bitcoin::{Address, Network},
-    wallet::Update,
+    bitcoin::{Address, Network, Script},
     KeychainKind, SignOptions, Wallet,
 };
 use bdk_esplora::{esplora_client, EsploraAsyncExt};
@@ -37,34 +36,44 @@ async fn main() -> Result<(), anyhow::Error> {
     let client =
         esplora_client::Builder::new("https://blockstream.info/testnet/api").build_async()?;
 
-    let prev_tip = wallet.latest_checkpoint();
-    let keychain_spks = wallet
-        .all_unbounded_spk_iters()
-        .into_iter()
-        .map(|(k, k_spks)| {
-            let mut once = Some(());
-            let mut stdout = std::io::stdout();
-            let k_spks = k_spks
-                .inspect(move |(spk_i, _)| match once.take() {
-                    Some(_) => print!("\nScanning keychain [{:?}]", k),
-                    None => print!(" {:<3}", spk_i),
-                })
-                .inspect(move |_| stdout.flush().expect("must flush"));
-            (k, k_spks)
+    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")
+            }
         })
-        .collect();
+        .inspect_spks_for_keychain(
+            KeychainKind::External,
+            generate_inspect(KeychainKind::External),
+        )
+        .inspect_spks_for_keychain(
+            KeychainKind::Internal,
+            generate_inspect(KeychainKind::Internal),
+        );
 
     let mut update = client
-        .full_scan(prev_tip, keychain_spks, STOP_GAP, PARALLEL_REQUESTS)
+        .full_scan(request, STOP_GAP, PARALLEL_REQUESTS)
         .await?;
     let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
-    let _ = update.tx_graph.update_last_seen_unconfirmed(now);
+    let _ = update.graph_update.update_last_seen_unconfirmed(now);
 
-    let update = Update {
-        last_active_indices: update.last_active_indices,
-        graph: update.tx_graph,
-        chain: Some(update.local_chain),
-    };
     wallet.apply_update(update)?;
     wallet.commit()?;
     println!();
index 979e272f56bda8d238fc77536a8c27162d4f6e26..6028bb7d0d2b91c597a8a42ea13372c1fa43ea84 100644 (file)
@@ -3,11 +3,10 @@ const SEND_AMOUNT: u64 = 1000;
 const STOP_GAP: usize = 5;
 const PARALLEL_REQUESTS: usize = 1;
 
-use std::{io::Write, str::FromStr};
+use std::{collections::BTreeSet, io::Write, str::FromStr};
 
 use bdk::{
     bitcoin::{Address, Network},
-    wallet::Update,
     KeychainKind, SignOptions, Wallet,
 };
 use bdk_esplora::{esplora_client, EsploraExt};
@@ -36,36 +35,22 @@ fn main() -> Result<(), anyhow::Error> {
     let client =
         esplora_client::Builder::new("https://blockstream.info/testnet/api").build_blocking();
 
-    let keychain_spks = wallet
-        .all_unbounded_spk_iters()
-        .into_iter()
-        .map(|(k, k_spks)| {
-            let mut once = Some(());
-            let mut stdout = std::io::stdout();
-            let k_spks = k_spks
-                .inspect(move |(spk_i, _)| match once.take() {
-                    Some(_) => print!("\nScanning keychain [{:?}]", k),
-                    None => print!(" {:<3}", spk_i),
-                })
-                .inspect(move |_| stdout.flush().expect("must flush"));
-            (k, k_spks)
-        })
-        .collect();
-
-    let mut update = client.full_scan(
-        wallet.latest_checkpoint(),
-        keychain_spks,
-        STOP_GAP,
-        PARALLEL_REQUESTS,
-    )?;
+    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 mut update = client.full_scan(request, STOP_GAP, PARALLEL_REQUESTS)?;
     let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
-    let _ = update.tx_graph.update_last_seen_unconfirmed(now);
+    let _ = update.graph_update.update_last_seen_unconfirmed(now);
 
-    wallet.apply_update(Update {
-        last_active_indices: update.last_active_indices,
-        graph: update.tx_graph,
-        chain: Some(update.local_chain),
-    })?;
+    wallet.apply_update(update)?;
     wallet.commit()?;
     println!();