diff --git a/crates/esplora/src/async_ext.rs b/crates/esplora/src/async_ext.rs index 9d02646c62..2942d27441 100644 --- a/crates/esplora/src/async_ext.rs +++ b/crates/esplora/src/async_ext.rs @@ -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; @@ -50,14 +51,10 @@ pub trait EsploraAsyncExt { /// [`LocalChain::tip`]: bdk_chain::local_chain::LocalChain::tip async fn full_scan( &self, - local_tip: CheckPoint, - keychain_spks: BTreeMap< - K, - impl IntoIterator + Send> + Send, - >, + request: FullScanRequest, stop_gap: usize, parallel_requests: usize, - ) -> Result, Error>; + ) -> Result, 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 + Send> + Send, - txids: impl IntoIterator + Send> + Send, - outpoints: impl IntoIterator + Send> + Send, + request: SyncRequest, parallel_requests: usize, - ) -> Result; + ) -> Result; } #[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( &self, - local_tip: CheckPoint, - keychain_spks: BTreeMap< - K, - impl IntoIterator + Send> + Send, - >, + request: FullScanRequest, stop_gap: usize, parallel_requests: usize, - ) -> Result, Error> { + ) -> Result, 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 + Send> + Send, - txids: impl IntoIterator + Send> + Send, - outpoints: impl IntoIterator + Send> + Send, + request: SyncRequest, parallel_requests: usize, - ) -> Result { + ) -> Result { 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, }) } } diff --git a/crates/esplora/src/blocking_ext.rs b/crates/esplora/src/blocking_ext.rs index 7037385690..469ab52e65 100644 --- a/crates/esplora/src/blocking_ext.rs +++ b/crates/esplora/src/blocking_ext.rs @@ -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; @@ -50,11 +49,10 @@ pub trait EsploraExt { /// [`LocalChain::tip`]: bdk_chain::local_chain::LocalChain::tip fn full_scan( &self, - local_tip: CheckPoint, - keychain_spks: BTreeMap>, + request: FullScanRequest, stop_gap: usize, parallel_requests: usize, - ) -> Result, Error>; + ) -> Result, 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, - txids: impl IntoIterator, - outpoints: impl IntoIterator, - parallel_requests: usize, - ) -> Result; + fn sync(&self, request: SyncRequest, parallel_requests: usize) -> Result; } impl EsploraExt for esplora_client::BlockingClient { fn full_scan( &self, - local_tip: CheckPoint, - keychain_spks: BTreeMap>, + request: FullScanRequest, stop_gap: usize, parallel_requests: usize, - ) -> Result, Error> { + ) -> Result, 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, - txids: impl IntoIterator, - outpoints: impl IntoIterator, - parallel_requests: usize, - ) -> Result { + fn sync(&self, request: SyncRequest, parallel_requests: usize) -> Result { 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, }) } } diff --git a/crates/esplora/src/lib.rs b/crates/esplora/src/lib.rs index 37d7dd26e5..535167ff25 100644 --- a/crates/esplora/src/lib.rs +++ b/crates/esplora/src/lib.rs @@ -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 None } } - -/// Update returns from a full scan. -pub struct FullScanUpdate { - /// 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, - /// Last active indices for the corresponding keychains (`K`). - pub last_active_indices: BTreeMap, -} - -/// 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, -} diff --git a/crates/esplora/tests/async_ext.rs b/crates/esplora/tests/async_ext.rs index f6954fe11f..6f7956d46e 100644 --- a/crates/esplora/tests/async_ext.rs +++ b/crates/esplora/tests/async_ext.rs @@ -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::>(); @@ -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(); diff --git a/crates/esplora/tests/blocking_ext.rs b/crates/esplora/tests/blocking_ext.rs index 40e446a4ed..61c2466d74 100644 --- a/crates/esplora/tests/blocking_ext.rs +++ b/crates/esplora/tests/blocking_ext.rs @@ -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::>(); @@ -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(); diff --git a/example-crates/example_esplora/src/main.rs b/example-crates/example_esplora/src/main.rs index 33aab27698..469f2893c1 100644 --- a/example-crates/example_esplora/src/main.rs +++ b/example-crates/example_esplora/src/main.rs @@ -1,14 +1,14 @@ use std::{ - collections::BTreeMap, io::{self, Write}, sync::Mutex, }; use bdk_chain::{ - bitcoin::{constants::genesis_block, Address, Network, OutPoint, ScriptBuf, Txid}, + bitcoin::{constants::genesis_block, Address, Network, Script, Txid}, indexed_tx_graph::{self, IndexedTxGraph}, keychain, local_chain::{self, LocalChain}, + spk_client::{FullScanRequest, SyncRequest}, Append, ConfirmationTimeHeightAnchor, }; @@ -167,45 +167,44 @@ 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; - } - eprint!("{} ", i); - // Flush early to ensure we print at every iteration. - let _ = io::stderr().flush(); - }); - (keychain, spk_iter) - }) - .collect::>(); + fn generate_inspect( + keychain: Keychain, + ) -> impl FnMut(u32, &Script) + Send + Sync + 'static { + let mut once = Some(()); + move |i, _| { + if once.take().is_some() { + eprint!("\nscanning {}: ", keychain); + } + eprint!("{} ", i); + // Flush early to ensure we print at every iteration. + let _ = io::stderr().flush(); + } + } + 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_keychain( + Keychain::External, + generate_inspect(Keychain::External), + ) + .inspect_spks_for_keychain( + Keychain::Internal, + generate_inspect(Keychain::Internal), + ) + }; // 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 +212,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 +240,9 @@ fn main() -> anyhow::Result<()> { unused_spks = false; } - // Spks, outpoints and txids we want updates on will be accumulated here. - let mut spks: Box> = Box::new(core::iter::empty()); - let mut outpoints: Box> = Box::new(core::iter::empty()); - let mut txids: Box> = 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 +256,12 @@ fn main() -> anyhow::Result<()> { .revealed_spks(..) .map(|(k, i, spk)| (k.to_owned(), i, spk.to_owned())) .collect::>(); - 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 +269,18 @@ fn main() -> anyhow::Result<()> { .unused_spks() .map(|(k, i, spk)| (k, i, spk.to_owned())) .collect::>(); - 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 +292,7 @@ fn main() -> anyhow::Result<()> { .filter_chain_unspents(&*chain, local_tip.block_id(), init_outpoints) .map(|(_, utxo)| utxo) .collect::>(); - outpoints = Box::new( + request = request.chain_outpoints( utxos .into_iter() .inspect(|utxo| { @@ -319,7 +316,7 @@ fn main() -> anyhow::Result<()> { .filter(|canonical_tx| !canonical_tx.chain_position.is_confirmed()) .map(|canonical_tx| canonical_tx.tx_node.txid) .collect::>(); - 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 +324,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), ) } }; diff --git a/example-crates/wallet_esplora_async/src/main.rs b/example-crates/wallet_esplora_async/src/main.rs index c37b6e6650..99e54dd66c 100644 --- a/example-crates/wallet_esplora_async/src/main.rs +++ b/example-crates/wallet_esplora_async/src/main.rs @@ -1,8 +1,7 @@ use std::{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,34 @@ 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) - }) - .collect(); + 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_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!(); diff --git a/example-crates/wallet_esplora_blocking/src/main.rs b/example-crates/wallet_esplora_blocking/src/main.rs index 979e272f56..469f38572b 100644 --- a/example-crates/wallet_esplora_blocking/src/main.rs +++ b/example-crates/wallet_esplora_blocking/src/main.rs @@ -6,8 +6,7 @@ const PARALLEL_REQUESTS: usize = 1; use std::{io::Write, str::FromStr}; use bdk::{ - bitcoin::{Address, Network}, - wallet::Update, + bitcoin::{Address, Network, Script}, KeychainKind, SignOptions, Wallet, }; use bdk_esplora::{esplora_client, EsploraExt}; @@ -36,36 +35,33 @@ 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, - )?; + 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_keychain( + KeychainKind::External, + generate_inspect(KeychainKind::External), + ) + .inspect_spks_for_keychain( + KeychainKind::Internal, + generate_inspect(KeychainKind::Internal), + ); + + 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!();