Skip to content

Commit

Permalink
feat(wallet): add start_scan and start_sync functions
Browse files Browse the repository at this point in the history
  • Loading branch information
notmandatory committed Nov 4, 2023
1 parent ca11728 commit c97ddef
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 129 deletions.
54 changes: 54 additions & 0 deletions crates/bdk/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2008,6 +2008,60 @@ impl<D> Wallet<D> {
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<KeychainKind, impl Iterator<Item = (u32, ScriptBuf)> + Clone>,
&LocalChain, Option<CheckPoint>) {

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<ScriptBuf>, &LocalChain, Option<CheckPoint>, Vec<OutPoint>, Vec<Txid>) {

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::<Vec<ScriptBuf>>()
}
// Sync all SPKs
else {
self.spk_index()
.all_spks()
.into_iter()

Check failure on line 2046 in crates/bdk/src/wallet/mod.rs

View workflow job for this annotation

GitHub Actions / clippy

this `.into_iter()` call is equivalent to `.iter()` and will not consume the `BTreeMap`

error: this `.into_iter()` call is equivalent to `.iter()` and will not consume the `BTreeMap` --> crates/bdk/src/wallet/mod.rs:2046:22 | 2046 | .into_iter() | ^^^^^^^^^ help: call directly: `iter` | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#into_iter_on_ref = note: `-D clippy::into-iter-on-ref` implied by `-D warnings`
.map(|((_keychain, _index), script)| (*script).clone())
.collect::<Vec<ScriptBuf>>()
};

// Sync UTXOs
// We want to search for whether our UTXOs are spent, and spent by which transaction.
let outpoints: Vec<OutPoint> = 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<Txid> = 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<D> AsRef<bdk_chain::tx_graph::TxGraph<ConfirmationTimeAnchor>> for Wallet<D> {
Expand Down
16 changes: 6 additions & 10 deletions crates/esplora/src/async_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ pub trait EsploraAsyncExt {
/// * chain_update, contains an update to a wallet's internal [`LocalChain`].
async fn scan<K: Clone + Ord + Send>(
&self,
keychain_spks: BTreeMap<K, impl IntoIterator<IntoIter = impl Iterator<Item = (u32, ScriptBuf)> + Send> + Send,>,
local_chain: &LocalChain,
local_tip: Option<CheckPoint>,
start_scan: (BTreeMap<K, impl IntoIterator<IntoIter = impl Iterator<Item = (u32, ScriptBuf)> + Send> + Send,>,
&LocalChain,
Option<CheckPoint>),
stop_gap: usize,
parallel_requests: usize,
) -> Result<(BTreeMap<K, u32>, TxGraph<ConfirmationTimeAnchor>, 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,
Expand Down Expand Up @@ -69,14 +69,10 @@ pub trait EsploraAsyncExt {
/// * chain_update, contains an update to a wallet's internal [`LocalChain`].
async fn sync(
&self,
spks: Vec<ScriptBuf>,
local_chain: &LocalChain,
local_tip: Option<CheckPoint>,
outpoints: Vec<OutPoint>,
txids: Vec<Txid>,
start_sync: (Vec<ScriptBuf>, &LocalChain, Option<CheckPoint>, Vec<OutPoint>, Vec<Txid>),
parallel_requests: usize,
) -> Result<(TxGraph<ConfirmationTimeAnchor>, 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?;
Expand Down
125 changes: 6 additions & 119 deletions example-crates/wallet_esplora_async/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::{io::Write, str::FromStr};

use bdk::bitcoin::{OutPoint, ScriptBuf, Txid};
use bdk::{
bitcoin::{Address, Network},
wallet::AddressIndex,
Expand Down Expand Up @@ -37,136 +36,24 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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.");
}
// 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<ScriptBuf> = 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<ScriptBuf> = 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<OutPoint> = 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<Txid> = 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.");
Expand All @@ -179,7 +66,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {

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);
Expand Down

0 comments on commit c97ddef

Please sign in to comment.