diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index c8804c5ff7..968cbcaf6c 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -40,6 +40,7 @@ use core::fmt; use core::ops::Deref; use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier}; +use bdk_chain::request::{ScanRequest, SyncRequest}; use bdk_chain::tx_graph::CalculateFeeError; #[allow(unused_imports)] use log::{debug, error, info, trace}; @@ -113,9 +114,20 @@ pub struct Update { pub chain: Option, } -impl From<(BTreeMap, TxGraph, local_chain::Update)> for Update +impl + From<( + BTreeMap, + TxGraph, + local_chain::Update, + )> for Update { - fn from((last_active_indices, graph, chain): (BTreeMap, TxGraph, local_chain::Update)) -> Self { + fn from( + (last_active_indices, graph, chain): ( + BTreeMap, + TxGraph, + local_chain::Update, + ), + ) -> Self { Self { last_active_indices, graph, @@ -124,8 +136,7 @@ impl From<(BTreeMap, TxGraph, local_c } } -impl From<(TxGraph, local_chain::Update)> for Update -{ +impl From<(TxGraph, local_chain::Update)> for Update { fn from((graph, chain): (TxGraph, local_chain::Update)) -> Self { Self { graph, @@ -2013,54 +2024,59 @@ impl Wallet { /// /// 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(); + pub fn start_scan( + &self, + ) -> ScanRequest + Clone> { + let spks_by_keychain = self.spks_of_all_keychains(); + let checkpoint = self.latest_checkpoint(); - (keychain_spks, local_chain, prev_tip) + ScanRequest { + spks_by_keychain, + checkpoint, + } } /// 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(); + pub fn start_sync(&self, unused_spks_only: bool) -> SyncRequest { + let checkpoint = 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::>() - }; + 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 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(); + // 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) + SyncRequest { + spks, + txids, + outpoints, + checkpoint, + } } } diff --git a/crates/chain/src/lib.rs b/crates/chain/src/lib.rs index ed167ebf6c..487e440023 100644 --- a/crates/chain/src/lib.rs +++ b/crates/chain/src/lib.rs @@ -53,6 +53,9 @@ mod spk_iter; #[cfg(feature = "miniscript")] pub use spk_iter::*; +/// Structures for requesting data needed to sync or scan the blockchain for chain related data. +pub mod request; + #[allow(unused_imports)] #[macro_use] extern crate alloc; diff --git a/crates/chain/src/request.rs b/crates/chain/src/request.rs new file mode 100644 index 0000000000..3ae270f59e --- /dev/null +++ b/crates/chain/src/request.rs @@ -0,0 +1,24 @@ +use crate::local_chain::CheckPoint; +use alloc::vec::Vec; +use bitcoin::{OutPoint, ScriptBuf, Txid}; +use std::collections::BTreeMap; + +/// A list of blockchain entities for which we want to receive any related transaction data. +pub struct SyncRequest { + /// transactions that spend from or two these script pubkeys + pub spks: Vec, + /// Transactions with these txids + pub txids: Vec, + /// Transactions with these outpoints or spend from these outpoints + pub outpoints: Vec, + /// The local chain checkpoint. The sync process will return a new chain that extends this one. + pub checkpoint: Option, +} + +/// Script pubkeys indexed by their keychain. +pub struct ScanRequest { + /// Iterators of script pubkeys indexed by the keychain index + pub spks_by_keychain: BTreeMap, + /// The local chain checkpoint. The scan process will return a new chain that extends this one. + pub checkpoint: Option, +} diff --git a/crates/esplora/src/async_ext.rs b/crates/esplora/src/async_ext.rs index c1880b1fa9..e6678d4cf7 100644 --- a/crates/esplora/src/async_ext.rs +++ b/crates/esplora/src/async_ext.rs @@ -1,5 +1,7 @@ use async_trait::async_trait; use bdk_chain::collections::btree_map; +use bdk_chain::local_chain::LocalChain; +use bdk_chain::request::{ScanRequest, SyncRequest}; use bdk_chain::{ bitcoin::{BlockHash, OutPoint, ScriptBuf, Txid}, collections::{BTreeMap, BTreeSet}, @@ -8,7 +10,6 @@ use bdk_chain::{ }; use esplora_client::{Error, TxStatus}; use futures::{stream::FuturesOrdered, TryStreamExt}; -use bdk_chain::local_chain::LocalChain; use crate::{anchor_from_status, ASSUME_FINAL_DEPTH}; @@ -33,18 +34,22 @@ pub trait EsploraAsyncExt { /// which is used to update a wallets [`KeychainTxOutIndex`]. /// * graph_update, contains an update to a wallet's internal [`TxGraph`]. /// * chain_update, contains an update to a wallet's internal [`LocalChain`]. - async fn scan( + async fn scan + Send>( &self, - start_scan: (BTreeMap + Send> + Send,>, - &LocalChain, - Option), + scan_request: ScanRequest, stop_gap: usize, parallel_requests: usize, - ) -> Result<(BTreeMap, TxGraph, local_chain::Update), Error> { - let (keychain_spks, local_chain, local_tip) = start_scan; + ) -> Result< + ( + BTreeMap, + TxGraph, + local_chain::Update, + ), + Error, + > { let (graph_update, last_active_indices) = self .scan_txs_with_keychains( - keychain_spks, + scan_request.spks_by_keychain, core::iter::empty(), core::iter::empty(), stop_gap, @@ -52,10 +57,14 @@ pub trait EsploraAsyncExt { ) .await?; - let missing_heights = graph_update.missing_heights(local_chain); + let local_chain = scan_request + .checkpoint + .map(|cp| LocalChain::from_tip(cp)) + .unwrap_or_default(); + let missing_heights = graph_update.missing_heights(&local_chain); let chain_update = self - .update_local_chain(local_tip, missing_heights) + .update_local_chain(local_chain.tip(), missing_heights) .await?; Ok((last_active_indices, graph_update, chain_update)) @@ -69,16 +78,23 @@ pub trait EsploraAsyncExt { /// * chain_update, contains an update to a wallet's internal [`LocalChain`]. async fn sync( &self, - start_sync: (Vec, &LocalChain, Option, Vec, Vec), + sync_request: SyncRequest, 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) + .scan_txs( + sync_request.spks.into_iter(), + sync_request.txids.into_iter(), + sync_request.outpoints.into_iter(), + parallel_requests, + ) .await?; - let missing_heights = graph_update.missing_heights(local_chain); - let chain_update = self.update_local_chain(local_tip, missing_heights).await?; + let local_chain = LocalChain::from_tip(sync_request.checkpoint.unwrap()); + let missing_heights = graph_update.missing_heights(&local_chain); + let chain_update = self + .update_local_chain(local_chain.tip(), missing_heights) + .await?; Ok((graph_update, chain_update)) } diff --git a/example-crates/wallet_esplora_async/src/main.rs b/example-crates/wallet_esplora_async/src/main.rs index 4405f5c247..8f38ec48a3 100644 --- a/example-crates/wallet_esplora_async/src/main.rs +++ b/example-crates/wallet_esplora_async/src/main.rs @@ -42,8 +42,10 @@ async fn main() -> Result<(), Box> { // wallet restoration. It is a special case. Applications should use "sync" style updates // after an initial scan. if prompt("Scan wallet") { - let start_scan = wallet.start_scan(); - let wallet_update = client.scan(start_scan, STOP_GAP, PARALLEL_REQUESTS).await?; + let scan_request = wallet.start_scan(); + let wallet_update = client + .scan(scan_request, STOP_GAP, PARALLEL_REQUESTS) + .await?; wallet.apply_update(wallet_update.into())?; wallet.commit()?; println!("Scan completed."); @@ -52,8 +54,8 @@ async fn main() -> Result<(), Box> { // status or fetch missing transactions. else { 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?; + let sync_request = wallet.start_sync(unused_spks_only); + let wallet_update = client.sync(sync_request, PARALLEL_REQUESTS).await?; wallet.apply_update(wallet_update.into())?; wallet.commit()?; println!("Sync completed.");