]> Untitled Git - bdk/commitdiff
refactor(esplora_ext): rename scan_txs to sync and scan_txs_with_keychains to full_scan
authorSteve Myers <steve@notmandatory.org>
Thu, 7 Dec 2023 03:21:02 +0000 (21:21 -0600)
committerSteve Myers <steve@notmandatory.org>
Fri, 5 Jan 2024 21:32:20 +0000 (15:32 -0600)
removed txids and outpoints params from full_scan

crates/chain/src/lib.rs
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 04ca62c4c628562f56a8d4769126de668f27133f..2065669714e400af4029eb604aa8d9c21d871c42 100644 (file)
@@ -1,4 +1,4 @@
-//! This crate is a collection of core structures for [Bitcoin Dev Kit] (alpha release).
+//! This crate is a collection of core structures for [Bitcoin Dev Kit].
 //!
 //! The goal of this crate is to give wallets the mechanisms needed to:
 //!
index b41050533e357c54c32976b1373f14938ac2a434..649cd6891a981724ae7996bbf83008a4476e68cc 100644 (file)
@@ -36,58 +36,45 @@ pub trait EsploraAsyncExt {
         request_heights: impl IntoIterator<IntoIter = impl Iterator<Item = u32> + Send> + Send,
     ) -> Result<local_chain::Update, Error>;
 
-    /// Scan Esplora for the data specified and return a [`TxGraph`] and a map of last active
-    /// indices.
+    /// Full scan the keychain scripts specified with the blockchain (via an Esplora client) and
+    /// returns a [`TxGraph`] and a map of last active indices.
     ///
     /// * `keychain_spks`: keychains that we want to scan transactions for
-    /// * `txids`: transactions for which we want updated [`ConfirmationTimeHeightAnchor`]s
-    /// * `outpoints`: transactions associated with these outpoints (residing, spending) that we
-    ///     want to include in the update
     ///
-    /// The scan for each keychain stops after a gap of `stop_gap` script pubkeys with no associated
+    /// The full scan for each keychain stops after a gap of `stop_gap` script pubkeys with no associated
     /// transactions. `parallel_requests` specifies the max number of HTTP requests to make in
     /// parallel.
     #[allow(clippy::result_large_err)]
-    async fn scan_txs_with_keychains<K: Ord + Clone + Send>(
+    async fn full_scan<K: Ord + Clone + Send>(
         &self,
         keychain_spks: BTreeMap<
             K,
             impl IntoIterator<IntoIter = impl Iterator<Item = (u32, ScriptBuf)> + Send> + Send,
         >,
-        txids: impl IntoIterator<IntoIter = impl Iterator<Item = Txid> + Send> + Send,
-        outpoints: impl IntoIterator<IntoIter = impl Iterator<Item = OutPoint> + Send> + Send,
         stop_gap: usize,
         parallel_requests: usize,
     ) -> Result<(TxGraph<ConfirmationTimeHeightAnchor>, BTreeMap<K, u32>), Error>;
 
-    /// Convenience method to call [`scan_txs_with_keychains`] without requiring a keychain.
+    /// Sync a set of scripts with the blockchain (via an Esplora client) for the data
+    /// specified and return a [`TxGraph`].
     ///
-    /// [`scan_txs_with_keychains`]: EsploraAsyncExt::scan_txs_with_keychains
+    /// * `misc_spks`: scripts that we want to sync transactions for
+    /// * `txids`: transactions for which we want updated [`ConfirmationTimeHeightAnchor`]s
+    /// * `outpoints`: transactions associated with these outpoints (residing, spending) that we
+    ///     want to include in the update
+    ///
+    /// If the scripts to sync are unknown, such as when restoring or importing a keychain that
+    /// may include scripts that have been used, use [`full_scan`] with the keychain.
+    ///
+    /// [`full_scan`]: EsploraAsyncExt::full_scan
     #[allow(clippy::result_large_err)]
-    async fn scan_txs(
+    async fn sync(
         &self,
         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,
         parallel_requests: usize,
-    ) -> Result<TxGraph<ConfirmationTimeHeightAnchor>, Error> {
-        self.scan_txs_with_keychains(
-            [(
-                (),
-                misc_spks
-                    .into_iter()
-                    .enumerate()
-                    .map(|(i, spk)| (i as u32, spk)),
-            )]
-            .into(),
-            txids,
-            outpoints,
-            usize::MAX,
-            parallel_requests,
-        )
-        .await
-        .map(|(g, _)| g)
-    }
+    ) -> Result<TxGraph<ConfirmationTimeHeightAnchor>, Error>;
 }
 
 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -199,14 +186,12 @@ impl EsploraAsyncExt for esplora_client::AsyncClient {
         })
     }
 
-    async fn scan_txs_with_keychains<K: Ord + Clone + Send>(
+    async fn full_scan<K: Ord + Clone + Send>(
         &self,
         keychain_spks: BTreeMap<
             K,
             impl IntoIterator<IntoIter = impl Iterator<Item = (u32, ScriptBuf)> + Send> + Send,
         >,
-        txids: impl IntoIterator<IntoIter = impl Iterator<Item = Txid> + Send> + Send,
-        outpoints: impl IntoIterator<IntoIter = impl Iterator<Item = OutPoint> + Send> + Send,
         stop_gap: usize,
         parallel_requests: usize,
     ) -> Result<(TxGraph<ConfirmationTimeHeightAnchor>, BTreeMap<K, u32>), Error> {
@@ -275,6 +260,32 @@ impl EsploraAsyncExt for esplora_client::AsyncClient {
             }
         }
 
+        Ok((graph, last_active_indexes))
+    }
+
+    async fn sync(
+        &self,
+        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,
+        parallel_requests: usize,
+    ) -> Result<TxGraph<ConfirmationTimeHeightAnchor>, Error> {
+        let mut graph = self
+            .full_scan(
+                [(
+                    (),
+                    misc_spks
+                        .into_iter()
+                        .enumerate()
+                        .map(|(i, spk)| (i as u32, spk)),
+                )]
+                .into(),
+                usize::MAX,
+                parallel_requests,
+            )
+            .await
+            .map(|(g, _)| g)?;
+
         let mut txids = txids.into_iter();
         loop {
             let handles = txids
@@ -323,7 +334,6 @@ impl EsploraAsyncExt for esplora_client::AsyncClient {
                 }
             }
         }
-
-        Ok((graph, last_active_indexes))
+        Ok(graph)
     }
 }
index bde24f832bfdd4f5456fb6c26585bc1906f2e26c..493c4b8a791ea580106f2687754f1410b99a06f8 100644 (file)
@@ -19,8 +19,8 @@ use crate::{anchor_from_status, ASSUME_FINAL_DEPTH};
 pub trait EsploraExt {
     /// Prepare an [`LocalChain`] update with blocks fetched from Esplora.
     ///
-    /// * `prev_tip` is the previous tip of [`LocalChain::tip`].
-    /// * `get_heights` is the block heights that we are interested in fetching from Esplora.
+    /// * `local_tip` is the previous tip of [`LocalChain::tip`].
+    /// * `request_heights` is the block heights that we are interested in fetching from Esplora.
     ///
     /// The result of this method can be applied to [`LocalChain::apply_update`].
     ///
@@ -34,54 +34,42 @@ pub trait EsploraExt {
         request_heights: impl IntoIterator<Item = u32>,
     ) -> Result<local_chain::Update, Error>;
 
-    /// Scan Esplora for the data specified and return a [`TxGraph`] and a map of last active
-    /// indices.
+    /// Full scan the keychain scripts specified with the blockchain (via an Esplora client) and
+    /// returns a [`TxGraph`] and a map of last active indices.
     ///
     /// * `keychain_spks`: keychains that we want to scan transactions for
-    /// * `txids`: transactions for which we want updated [`ConfirmationTimeHeightAnchor`]s
-    /// * `outpoints`: transactions associated with these outpoints (residing, spending) that we
-    ///     want to include in the update
     ///
-    /// The scan for each keychain stops after a gap of `stop_gap` script pubkeys with no associated
+    /// The full scan for each keychain stops after a gap of `stop_gap` script pubkeys with no associated
     /// transactions. `parallel_requests` specifies the max number of HTTP requests to make in
     /// parallel.
     #[allow(clippy::result_large_err)]
-    fn scan_txs_with_keychains<K: Ord + Clone>(
+    fn full_scan<K: Ord + Clone>(
         &self,
         keychain_spks: BTreeMap<K, impl IntoIterator<Item = (u32, ScriptBuf)>>,
-        txids: impl IntoIterator<Item = Txid>,
-        outpoints: impl IntoIterator<Item = OutPoint>,
         stop_gap: usize,
         parallel_requests: usize,
     ) -> Result<(TxGraph<ConfirmationTimeHeightAnchor>, BTreeMap<K, u32>), Error>;
 
-    /// Convenience method to call [`scan_txs_with_keychains`] without requiring a keychain.
+    /// Sync a set of scripts with the blockchain (via an Esplora client) for the data
+    /// specified and return a [`TxGraph`].
     ///
-    /// [`scan_txs_with_keychains`]: EsploraExt::scan_txs_with_keychains
+    /// * `misc_spks`: scripts that we want to sync transactions for
+    /// * `txids`: transactions for which we want updated [`ConfirmationTimeHeightAnchor`]s
+    /// * `outpoints`: transactions associated with these outpoints (residing, spending) that we
+    ///     want to include in the update
+    ///
+    /// If the scripts to sync are unknown, such as when restoring or importing a keychain that
+    /// may include scripts that have been used, use [`full_scan`] with the keychain.
+    ///
+    /// [`full_scan`]: EsploraExt::full_scan
     #[allow(clippy::result_large_err)]
-    fn scan_txs(
+    fn sync(
         &self,
         misc_spks: impl IntoIterator<Item = ScriptBuf>,
         txids: impl IntoIterator<Item = Txid>,
         outpoints: impl IntoIterator<Item = OutPoint>,
         parallel_requests: usize,
-    ) -> Result<TxGraph<ConfirmationTimeHeightAnchor>, Error> {
-        self.scan_txs_with_keychains(
-            [(
-                (),
-                misc_spks
-                    .into_iter()
-                    .enumerate()
-                    .map(|(i, spk)| (i as u32, spk)),
-            )]
-            .into(),
-            txids,
-            outpoints,
-            usize::MAX,
-            parallel_requests,
-        )
-        .map(|(g, _)| g)
-    }
+    ) -> Result<TxGraph<ConfirmationTimeHeightAnchor>, Error>;
 }
 
 impl EsploraExt for esplora_client::BlockingClient {
@@ -190,11 +178,9 @@ impl EsploraExt for esplora_client::BlockingClient {
         })
     }
 
-    fn scan_txs_with_keychains<K: Ord + Clone>(
+    fn full_scan<K: Ord + Clone>(
         &self,
         keychain_spks: BTreeMap<K, impl IntoIterator<Item = (u32, ScriptBuf)>>,
-        txids: impl IntoIterator<Item = Txid>,
-        outpoints: impl IntoIterator<Item = OutPoint>,
         stop_gap: usize,
         parallel_requests: usize,
     ) -> Result<(TxGraph<ConfirmationTimeHeightAnchor>, BTreeMap<K, u32>), Error> {
@@ -266,6 +252,31 @@ impl EsploraExt for esplora_client::BlockingClient {
             }
         }
 
+        Ok((graph, last_active_indexes))
+    }
+
+    fn sync(
+        &self,
+        misc_spks: impl IntoIterator<Item = ScriptBuf>,
+        txids: impl IntoIterator<Item = Txid>,
+        outpoints: impl IntoIterator<Item = OutPoint>,
+        parallel_requests: usize,
+    ) -> Result<TxGraph<ConfirmationTimeHeightAnchor>, Error> {
+        let mut graph = self
+            .full_scan(
+                [(
+                    (),
+                    misc_spks
+                        .into_iter()
+                        .enumerate()
+                        .map(|(i, spk)| (i as u32, spk)),
+                )]
+                .into(),
+                usize::MAX,
+                parallel_requests,
+            )
+            .map(|(g, _)| g)?;
+
         let mut txids = txids.into_iter();
         loop {
             let handles = txids
@@ -292,7 +303,7 @@ impl EsploraExt for esplora_client::BlockingClient {
             }
         }
 
-        for op in outpoints.into_iter() {
+        for op in outpoints {
             if graph.get_tx(op.txid).is_none() {
                 if let Some(tx) = self.get_tx(&op.txid)? {
                     let _ = graph.insert_tx(tx);
@@ -317,7 +328,6 @@ impl EsploraExt for esplora_client::BlockingClient {
                 }
             }
         }
-
-        Ok((graph, last_active_indexes))
+        Ok(graph)
     }
 }
index e8c6672779a3b685c22f7705eda875fe0ebc25d8..727c8c53b211234ee72253a71416c455b54514f9 100644 (file)
@@ -1,4 +1,21 @@
 #![doc = include_str!("../README.md")]
+
+//! This crate is used for updating structures of [`bdk_chain`] with data from an Esplora server.
+//!
+//! The two primary methods are [`EsploraExt::sync`] and [`EsploraExt::full_scan`]. In most cases
+//! [`EsploraExt::sync`] is used to sync the transaction histories of scripts that the application
+//! cares about, for example the scripts for all the receive addresses of a Wallet's keychain that it
+//! has shown a user. [`EsploraExt::full_scan`] is meant to be used when importing or restoring a
+//! keychain where the range of possibly used scripts is not known. In this case it is necessary to
+//! scan all keychain scripts until a number (the "stop gap") of unused scripts is discovered. For a
+//! sync or full scan the user receives relevant blockchain data and output updates for [`bdk_chain`]
+//! via a new [`TxGraph`] to be appended to any existing [`TxGraph`] data.
+//!
+//! Refer to [`example_esplora`] for a complete example.
+//!
+//! [`TxGraph`]: bdk_chain::tx_graph::TxGraph
+//! [`example_esplora`]: https://github.com/bitcoindevkit/bdk/tree/master/example-crates/example_esplora
+
 use bdk_chain::{BlockId, ConfirmationTimeHeightAnchor};
 use esplora_client::TxStatus;
 
index 38833f588ecc7846c5d55b4a94bfe6e1a4f7cca9..3124bd2d110c753390709bfb600de2c128afdec0 100644 (file)
@@ -101,7 +101,7 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
 
     let graph_update = env
         .client
-        .scan_txs(
+        .sync(
             misc_spks.into_iter(),
             vec![].into_iter(),
             vec![].into_iter(),
@@ -166,28 +166,10 @@ pub async fn test_async_update_tx_graph_gap_limit() -> anyhow::Result<()> {
 
     // A scan with a gap limit of 2 won't find the transaction, but a scan with a gap limit of 3
     // will.
-    let (graph_update, active_indices) = env
-        .client
-        .scan_txs_with_keychains(
-            keychains.clone(),
-            vec![].into_iter(),
-            vec![].into_iter(),
-            2,
-            1,
-        )
-        .await?;
+    let (graph_update, active_indices) = env.client.full_scan(keychains.clone(), 2, 1).await?;
     assert!(graph_update.full_txs().next().is_none());
     assert!(active_indices.is_empty());
-    let (graph_update, active_indices) = env
-        .client
-        .scan_txs_with_keychains(
-            keychains.clone(),
-            vec![].into_iter(),
-            vec![].into_iter(),
-            3,
-            1,
-        )
-        .await?;
+    let (graph_update, active_indices) = env.client.full_scan(keychains.clone(), 3, 1).await?;
     assert_eq!(graph_update.full_txs().next().unwrap().txid, txid_4th_addr);
     assert_eq!(active_indices[&0], 3);
 
@@ -209,24 +191,12 @@ pub async fn test_async_update_tx_graph_gap_limit() -> anyhow::Result<()> {
 
     // A scan with gap limit 4 won't find the second transaction, but a scan with gap limit 5 will.
     // The last active indice won't be updated in the first case but will in the second one.
-    let (graph_update, active_indices) = env
-        .client
-        .scan_txs_with_keychains(
-            keychains.clone(),
-            vec![].into_iter(),
-            vec![].into_iter(),
-            4,
-            1,
-        )
-        .await?;
+    let (graph_update, active_indices) = env.client.full_scan(keychains.clone(), 4, 1).await?;
     let txs: HashSet<_> = graph_update.full_txs().map(|tx| tx.txid).collect();
     assert_eq!(txs.len(), 1);
     assert!(txs.contains(&txid_4th_addr));
     assert_eq!(active_indices[&0], 3);
-    let (graph_update, active_indices) = env
-        .client
-        .scan_txs_with_keychains(keychains, vec![].into_iter(), vec![].into_iter(), 5, 1)
-        .await?;
+    let (graph_update, active_indices) = env.client.full_scan(keychains, 5, 1).await?;
     let txs: HashSet<_> = graph_update.full_txs().map(|tx| tx.txid).collect();
     assert_eq!(txs.len(), 2);
     assert!(txs.contains(&txid_4th_addr) && txs.contains(&txid_last_addr));
index 5a76172325e6196b9301c0e147b7092652e7b1c3..50b19d1ccd686d916df32b1f75ba154e161610c3 100644 (file)
@@ -99,7 +99,7 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
         sleep(Duration::from_millis(10))
     }
 
-    let graph_update = env.client.scan_txs(
+    let graph_update = env.client.sync(
         misc_spks.into_iter(),
         vec![].into_iter(),
         vec![].into_iter(),
@@ -164,22 +164,10 @@ pub fn test_update_tx_graph_gap_limit() -> anyhow::Result<()> {
 
     // A scan with a gap limit of 2 won't find the transaction, but a scan with a gap limit of 3
     // will.
-    let (graph_update, active_indices) = env.client.scan_txs_with_keychains(
-        keychains.clone(),
-        vec![].into_iter(),
-        vec![].into_iter(),
-        2,
-        1,
-    )?;
+    let (graph_update, active_indices) = env.client.full_scan(keychains.clone(), 2, 1)?;
     assert!(graph_update.full_txs().next().is_none());
     assert!(active_indices.is_empty());
-    let (graph_update, active_indices) = env.client.scan_txs_with_keychains(
-        keychains.clone(),
-        vec![].into_iter(),
-        vec![].into_iter(),
-        3,
-        1,
-    )?;
+    let (graph_update, active_indices) = env.client.full_scan(keychains.clone(), 3, 1)?;
     assert_eq!(graph_update.full_txs().next().unwrap().txid, txid_4th_addr);
     assert_eq!(active_indices[&0], 3);
 
@@ -201,24 +189,12 @@ pub fn test_update_tx_graph_gap_limit() -> anyhow::Result<()> {
 
     // A scan with gap limit 4 won't find the second transaction, but a scan with gap limit 5 will.
     // The last active indice won't be updated in the first case but will in the second one.
-    let (graph_update, active_indices) = env.client.scan_txs_with_keychains(
-        keychains.clone(),
-        vec![].into_iter(),
-        vec![].into_iter(),
-        4,
-        1,
-    )?;
+    let (graph_update, active_indices) = env.client.full_scan(keychains.clone(), 4, 1)?;
     let txs: HashSet<_> = graph_update.full_txs().map(|tx| tx.txid).collect();
     assert_eq!(txs.len(), 1);
     assert!(txs.contains(&txid_4th_addr));
     assert_eq!(active_indices[&0], 3);
-    let (graph_update, active_indices) = env.client.scan_txs_with_keychains(
-        keychains,
-        vec![].into_iter(),
-        vec![].into_iter(),
-        5,
-        1,
-    )?;
+    let (graph_update, active_indices) = env.client.full_scan(keychains, 5, 1)?;
     let txs: HashSet<_> = graph_update.full_txs().map(|tx| tx.txid).collect();
     assert_eq!(txs.len(), 2);
     assert!(txs.contains(&txid_4th_addr) && txs.contains(&txid_last_addr));
index cabd8ea828804ea54dcfd73722b978a20660fb77..101fd58ba20f428cc322dda66861d9d12634a115 100644 (file)
@@ -188,13 +188,7 @@ fn main() -> anyhow::Result<()> {
             // represents the last active spk derivation indices of keychains
             // (`keychain_indices_update`).
             let (graph_update, last_active_indices) = client
-                .scan_txs_with_keychains(
-                    keychain_spks,
-                    core::iter::empty(),
-                    core::iter::empty(),
-                    *stop_gap,
-                    scan_options.parallel_requests,
-                )
+                .full_scan(keychain_spks, *stop_gap, scan_options.parallel_requests)
                 .context("scanning for transactions")?;
 
             let mut graph = graph.lock().expect("mutex must not be poisoned");
@@ -312,7 +306,7 @@ fn main() -> anyhow::Result<()> {
             }
 
             let graph_update =
-                client.scan_txs(spks, txids, outpoints, scan_options.parallel_requests)?;
+                client.sync(spks, txids, outpoints, scan_options.parallel_requests)?;
 
             graph.lock().unwrap().apply_update(graph_update)
         }
index fb8f7b5105a9568232ff80155c2c02776872eac2..755b39007b1e8a1d5fab7b6bcb40f373b43ed9b3 100644 (file)
@@ -54,7 +54,7 @@ async fn main() -> Result<(), anyhow::Error> {
         })
         .collect();
     let (update_graph, last_active_indices) = client
-        .scan_txs_with_keychains(keychain_spks, None, None, STOP_GAP, PARALLEL_REQUESTS)
+        .full_scan(keychain_spks, STOP_GAP, PARALLEL_REQUESTS)
         .await?;
     let missing_heights = update_graph.missing_heights(wallet.local_chain());
     let chain_update = client.update_local_chain(prev_tip, missing_heights).await?;
index 09e7c3ad4abea9f96e65c7a5209354486c33679f..d0f35bea8e7dda9d5908e9ed767f4ac047c29fc8 100644 (file)
@@ -54,7 +54,7 @@ fn main() -> Result<(), anyhow::Error> {
         .collect();
 
     let (update_graph, last_active_indices) =
-        client.scan_txs_with_keychains(keychain_spks, None, None, STOP_GAP, PARALLEL_REQUESTS)?;
+        client.full_scan(keychain_spks, STOP_GAP, PARALLEL_REQUESTS)?;
     let missing_heights = update_graph.missing_heights(wallet.local_chain());
     let chain_update = client.update_local_chain(prev_tip, missing_heights)?;
     let update = Update {