diff --git a/crates/chain/src/lib.rs b/crates/chain/src/lib.rs index ed167ebf6c..7bd118b54b 100644 --- a/crates/chain/src/lib.rs +++ b/crates/chain/src/lib.rs @@ -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: //! diff --git a/crates/esplora/src/async_ext.rs b/crates/esplora/src/async_ext.rs index b41050533e..490386e134 100644 --- a/crates/esplora/src/async_ext.rs +++ b/crates/esplora/src/async_ext.rs @@ -36,19 +36,19 @@ pub trait EsploraAsyncExt { request_heights: impl IntoIterator + Send> + Send, ) -> Result; - /// 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( + async fn full_scan( &self, keychain_spks: BTreeMap< K, @@ -60,18 +60,31 @@ pub trait EsploraAsyncExt { parallel_requests: usize, ) -> Result<(TxGraph, BTreeMap), Error>; - /// Convenience method to call [`scan_txs_with_keychains`] without requiring a keychain. + /// Sync a set of scripts with with the blockchain (via an Esplora client) for the data + /// specified and return a [`TxGraph`] and a map of last active indices. + /// + /// * `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 + /// + /// The 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. + /// + /// 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. /// - /// [`scan_txs_with_keychains`]: EsploraAsyncExt::scan_txs_with_keychains + /// [`full_scan`]: EsploraAsyncExt::full_scan #[allow(clippy::result_large_err)] - async fn scan_txs( + async fn sync( &self, misc_spks: impl IntoIterator + Send> + Send, txids: impl IntoIterator + Send> + Send, outpoints: impl IntoIterator + Send> + Send, parallel_requests: usize, ) -> Result, Error> { - self.scan_txs_with_keychains( + self.full_scan( [( (), misc_spks @@ -199,7 +212,7 @@ impl EsploraAsyncExt for esplora_client::AsyncClient { }) } - async fn scan_txs_with_keychains( + async fn full_scan( &self, keychain_spks: BTreeMap< K, diff --git a/crates/esplora/src/blocking_ext.rs b/crates/esplora/src/blocking_ext.rs index bde24f832b..7404e5331c 100644 --- a/crates/esplora/src/blocking_ext.rs +++ b/crates/esplora/src/blocking_ext.rs @@ -34,19 +34,19 @@ pub trait EsploraExt { request_heights: impl IntoIterator, ) -> Result; - /// 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( + fn full_scan( &self, keychain_spks: BTreeMap>, txids: impl IntoIterator, @@ -55,18 +55,31 @@ pub trait EsploraExt { parallel_requests: usize, ) -> Result<(TxGraph, BTreeMap), Error>; - /// Convenience method to call [`scan_txs_with_keychains`] without requiring a keychain. + /// Sync a set of scripts with with the blockchain (via an Esplora client) for the data + /// specified and return a [`TxGraph`] and a map of last active indices. + /// + /// * `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 + /// + /// The 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. + /// + /// 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. /// - /// [`scan_txs_with_keychains`]: EsploraExt::scan_txs_with_keychains + /// [`full_scan`]: EsploraExt::full_scan #[allow(clippy::result_large_err)] - fn scan_txs( + fn sync( &self, misc_spks: impl IntoIterator, txids: impl IntoIterator, outpoints: impl IntoIterator, parallel_requests: usize, ) -> Result, Error> { - self.scan_txs_with_keychains( + self.full_scan( [( (), misc_spks @@ -190,7 +203,7 @@ impl EsploraExt for esplora_client::BlockingClient { }) } - fn scan_txs_with_keychains( + fn full_scan( &self, keychain_spks: BTreeMap>, txids: impl IntoIterator, diff --git a/crates/esplora/src/lib.rs b/crates/esplora/src/lib.rs index e8c6672779..727c8c53b2 100644 --- a/crates/esplora/src/lib.rs +++ b/crates/esplora/src/lib.rs @@ -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; diff --git a/crates/esplora/tests/async_ext.rs b/crates/esplora/tests/async_ext.rs index 38833f588e..2a5d4f09b5 100644 --- a/crates/esplora/tests/async_ext.rs +++ b/crates/esplora/tests/async_ext.rs @@ -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(), @@ -168,7 +168,7 @@ pub async fn test_async_update_tx_graph_gap_limit() -> anyhow::Result<()> { // will. let (graph_update, active_indices) = env .client - .scan_txs_with_keychains( + .full_scan( keychains.clone(), vec![].into_iter(), vec![].into_iter(), @@ -180,7 +180,7 @@ pub async fn test_async_update_tx_graph_gap_limit() -> anyhow::Result<()> { assert!(active_indices.is_empty()); let (graph_update, active_indices) = env .client - .scan_txs_with_keychains( + .full_scan( keychains.clone(), vec![].into_iter(), vec![].into_iter(), @@ -211,7 +211,7 @@ pub async fn test_async_update_tx_graph_gap_limit() -> anyhow::Result<()> { // 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( + .full_scan( keychains.clone(), vec![].into_iter(), vec![].into_iter(), @@ -225,7 +225,7 @@ pub async fn test_async_update_tx_graph_gap_limit() -> anyhow::Result<()> { 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) + .full_scan(keychains, vec![].into_iter(), vec![].into_iter(), 5, 1) .await?; let txs: HashSet<_> = graph_update.full_txs().map(|tx| tx.txid).collect(); assert_eq!(txs.len(), 2); diff --git a/crates/esplora/tests/blocking_ext.rs b/crates/esplora/tests/blocking_ext.rs index 5a76172325..4ee894ca8c 100644 --- a/crates/esplora/tests/blocking_ext.rs +++ b/crates/esplora/tests/blocking_ext.rs @@ -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,7 +164,7 @@ 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( + let (graph_update, active_indices) = env.client.full_scan( keychains.clone(), vec![].into_iter(), vec![].into_iter(), @@ -173,7 +173,7 @@ pub fn test_update_tx_graph_gap_limit() -> anyhow::Result<()> { )?; assert!(graph_update.full_txs().next().is_none()); assert!(active_indices.is_empty()); - let (graph_update, active_indices) = env.client.scan_txs_with_keychains( + let (graph_update, active_indices) = env.client.full_scan( keychains.clone(), vec![].into_iter(), vec![].into_iter(), @@ -201,7 +201,7 @@ 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( + let (graph_update, active_indices) = env.client.full_scan( keychains.clone(), vec![].into_iter(), vec![].into_iter(), @@ -212,13 +212,9 @@ pub fn test_update_tx_graph_gap_limit() -> anyhow::Result<()> { 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, vec![].into_iter(), vec![].into_iter(), 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)); diff --git a/example-crates/example_esplora/src/main.rs b/example-crates/example_esplora/src/main.rs index cabd8ea828..81e8bf80a6 100644 --- a/example-crates/example_esplora/src/main.rs +++ b/example-crates/example_esplora/src/main.rs @@ -188,7 +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( + .full_scan( keychain_spks, core::iter::empty(), core::iter::empty(), @@ -312,7 +312,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) } diff --git a/example-crates/wallet_esplora_async/src/main.rs b/example-crates/wallet_esplora_async/src/main.rs index fb8f7b5105..259f98507d 100644 --- a/example-crates/wallet_esplora_async/src/main.rs +++ b/example-crates/wallet_esplora_async/src/main.rs @@ -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, None, None, 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?; diff --git a/example-crates/wallet_esplora_blocking/src/main.rs b/example-crates/wallet_esplora_blocking/src/main.rs index 09e7c3ad4a..cdcb479553 100644 --- a/example-crates/wallet_esplora_blocking/src/main.rs +++ b/example-crates/wallet_esplora_blocking/src/main.rs @@ -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, None, None, 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 {