From c97ddefb450a573aea974e319f41f99d0861f00e Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Fri, 3 Nov 2023 19:51:16 -0500 Subject: [PATCH] feat(wallet): add start_scan and start_sync functions --- crates/bdk/src/wallet/mod.rs | 54 ++++++++ crates/esplora/src/async_ext.rs | 16 +-- .../wallet_esplora_async/src/main.rs | 125 +----------------- 3 files changed, 66 insertions(+), 129 deletions(-) diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 8129eee6f5..c8804c5ff7 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -2008,6 +2008,60 @@ impl Wallet { pub fn local_chain(&self) -> &LocalChain { &self.chain } + + /// Get data needed to start a wallet scan + /// + /// Collect the wallet keychain script pub keys, local chain, and previous chain tip data needed + /// to start a blockchain scan. + pub fn start_scan(&self) -> (BTreeMap + Clone>, + &LocalChain, Option) { + + let keychain_spks = self.spks_of_all_keychains(); + let local_chain = self.local_chain(); + let prev_tip = self.latest_checkpoint(); + + (keychain_spks, local_chain, prev_tip) + } + + /// Get data needed to start a wallet sync + /// + /// Collect the wallet keychain script pub keys, local chain, previous chain tip, UTXOs, and + /// unconfirmed transaction id data needed to start a blockchain sync. + pub fn start_sync(&self, unused_spks_only: bool) -> (Vec, &LocalChain, Option, Vec, Vec) { + + let local_chain = self.local_chain(); + let prev_tip = self.latest_checkpoint(); + + // Sync only unused SPKs + let spks = if unused_spks_only { + self.spk_index() + .unused_spks(..) + .map(|((_keychain, _index), script)| ScriptBuf::from(script)) + .collect::>() + } + // Sync all SPKs + else { + self.spk_index() + .all_spks() + .into_iter() + .map(|((_keychain, _index), script)| (*script).clone()) + .collect::>() + }; + + // Sync UTXOs + // We want to search for whether our UTXOs are spent, and spent by which transaction. + let outpoints: Vec = self.list_unspent().map(|utxo| utxo.outpoint).collect(); + + // Sync unconfirmed TX + // We want to search for whether our unconfirmed transactions are now confirmed. + let txids: Vec = self + .transactions() + .filter(|canonical_tx| !canonical_tx.chain_position.is_confirmed()) + .map(|canonical_tx| canonical_tx.tx_node.txid) + .collect(); + + (spks, local_chain, prev_tip, outpoints, txids) + } } impl AsRef> for Wallet { diff --git a/crates/esplora/src/async_ext.rs b/crates/esplora/src/async_ext.rs index 33bbdf832b..c1880b1fa9 100644 --- a/crates/esplora/src/async_ext.rs +++ b/crates/esplora/src/async_ext.rs @@ -35,13 +35,13 @@ pub trait EsploraAsyncExt { /// * chain_update, contains an update to a wallet's internal [`LocalChain`]. async fn scan( &self, - keychain_spks: BTreeMap + Send> + Send,>, - local_chain: &LocalChain, - local_tip: Option, + start_scan: (BTreeMap + Send> + Send,>, + &LocalChain, + Option), stop_gap: usize, parallel_requests: usize, ) -> Result<(BTreeMap, TxGraph, local_chain::Update), Error> { - + let (keychain_spks, local_chain, local_tip) = start_scan; let (graph_update, last_active_indices) = self .scan_txs_with_keychains( keychain_spks, @@ -69,14 +69,10 @@ pub trait EsploraAsyncExt { /// * chain_update, contains an update to a wallet's internal [`LocalChain`]. async fn sync( &self, - spks: Vec, - local_chain: &LocalChain, - local_tip: Option, - outpoints: Vec, - txids: Vec, + start_sync: (Vec, &LocalChain, Option, Vec, Vec), parallel_requests: usize, ) -> Result<(TxGraph, local_chain::Update), Error> { - + let (spks, local_chain, local_tip, outpoints, txids) = start_sync; let graph_update = self .scan_txs(spks.into_iter(), txids.into_iter(), outpoints.into_iter(), parallel_requests) .await?; diff --git a/example-crates/wallet_esplora_async/src/main.rs b/example-crates/wallet_esplora_async/src/main.rs index 39f9479fa0..4405f5c247 100644 --- a/example-crates/wallet_esplora_async/src/main.rs +++ b/example-crates/wallet_esplora_async/src/main.rs @@ -1,6 +1,5 @@ use std::{io::Write, str::FromStr}; -use bdk::bitcoin::{OutPoint, ScriptBuf, Txid}; use bdk::{ bitcoin::{Address, Network}, wallet::AddressIndex, @@ -37,45 +36,14 @@ async fn main() -> Result<(), Box> { // Create an async esplora client let client = esplora_client::Builder::new(ESPLORA_SERVER_URL).build_async()?; - // Get wallet's previous chain tip - let prev_tip = wallet.latest_checkpoint(); - // Scanning: We are iterating through spks of all keychains and scanning for transactions for // each spk. We start with the lowest derivation index spk and stop scanning after `stop_gap` // number of consecutive spks have no transaction history. A Scan is done in situations of // wallet restoration. It is a special case. Applications should use "sync" style updates // after an initial scan. if prompt("Scan wallet") { - let keychain_spks = wallet - .spks_of_all_keychains() - .into_iter() - // This `map` is purely for logging. - .map(|(keychain, iter)| { - let mut first = true; - let spk_iter = iter.inspect(move |(i, _)| { - if first { - print!("\nScanning keychain [{:?}]", keychain); - first = false; - } - print!("{} ", i); - // Flush early to ensure we print at every iteration. - let _ = std::io::stdout().flush(); - }); - (keychain, spk_iter) - }) - .collect(); - println!(); - - let wallet_update = client - .scan( - keychain_spks, - wallet.local_chain(), - prev_tip, - STOP_GAP, - PARALLEL_REQUESTS, - ) - .await?; - + let start_scan = wallet.start_scan(); + let wallet_update = client.scan(start_scan, STOP_GAP, PARALLEL_REQUESTS).await?; wallet.apply_update(wallet_update.into())?; wallet.commit()?; println!("Scan completed."); @@ -83,90 +51,9 @@ async fn main() -> Result<(), Box> { // Syncing: We only check for specified spks, utxos and txids to update their confirmation // status or fetch missing transactions. else { - // Sync only unused SPKs - let spks = if prompt("Sync only unused SPKs") { - println!("Syncing unused SPKs..."); - let unused_spks: Vec = wallet - .spk_index() - .unused_spks(..) - .map(|((_keychain, _index), script)| ScriptBuf::from(script)) - .collect(); - - // print unused spks that will be checked - unused_spks.iter().for_each(|script| { - eprintln!( - "Checking if unused address: {} has been used", - Address::from_script(script.as_script(), network).unwrap(), - ); - // Flush early to ensure we print at every iteration. - let _ = std::io::stderr().flush(); - }); - unused_spks - } - // Sync all SPKs - else if prompt("Sync all SPKs") { - println!("Syncing all SPKs..."); - let all_spks: Vec = wallet - .spk_index() - .all_spks() - .into_iter() - .map(|((_keychain, _index), script)| (*script).clone()) - .collect(); - - // print all spks that will be checked - all_spks.iter().for_each(|script| { - eprintln!( - "Checking if address: {} has been used", - Address::from_script(script.as_script(), network).unwrap(), - ); - // Flush early to ensure we print at every iteration. - let _ = std::io::stderr().flush(); - }); - - all_spks - } else { - Vec::new() - }; - - // Sync UTXOs - - // We want to search for whether our UTXOs are spent, and spent by which transaction. - let outpoints: Vec = wallet.list_unspent().map(|utxo| utxo.outpoint).collect(); - - // print unspent outpoints - outpoints.iter().for_each(|outpoint| { - eprintln!("Checking if outpoint {} has been spent", outpoint); - // Flush early to ensure we print at every iteration. - let _ = std::io::stderr().flush(); - }); - - // Sync unconfirmed TX - - // We want to search for whether our unconfirmed transactions are now confirmed. - let txids: Vec = wallet - .transactions() - .filter(|canonical_tx| !canonical_tx.chain_position.is_confirmed()) - .map(|canonical_tx| canonical_tx.tx_node.txid) - .collect(); - - // print unconfirmed tx ids - txids.iter().for_each(|txid| { - eprintln!("Checking if {} is confirmed yet", txid); - // Flush early to ensure we print at every iteration. - let _ = std::io::stderr().flush(); - }); - - let wallet_update = client - .sync( - spks, - wallet.local_chain(), - prev_tip, - outpoints, - txids, - PARALLEL_REQUESTS, - ) - .await?; - + let unused_spks_only = prompt("Sync only unused SPKs"); + let start_sync = wallet.start_sync(unused_spks_only); + let wallet_update = client.sync(start_sync, PARALLEL_REQUESTS).await?; wallet.apply_update(wallet_update.into())?; wallet.commit()?; println!("Sync completed."); @@ -179,7 +66,7 @@ async fn main() -> Result<(), Box> { if balance.total() < SEND_AMOUNT { println!( - "Please send at least {} sats to the receiving address", + "Please send at least {} sats to the above generated receiving address", SEND_AMOUNT ); std::process::exit(0);