From 28e75fc4c8e5f0773898cb8ba70faaa450d0dfdd Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Thu, 21 Dec 2023 11:44:31 -0600 Subject: [PATCH] feat(wallet): add sync_request and full_scan_request functions feat(wallet): add sync_request and full_scan_request functions --- crates/bdk/src/wallet/error.rs | 8 +- crates/bdk/src/wallet/mod.rs | 97 +++++++++++++++++++ .../wallet_esplora_async/.gitignore | 1 + .../wallet_esplora_blocking/.gitignore | 1 + 4 files changed, 102 insertions(+), 5 deletions(-) create mode 100644 example-crates/wallet_esplora_async/.gitignore create mode 100644 example-crates/wallet_esplora_blocking/.gitignore diff --git a/crates/bdk/src/wallet/error.rs b/crates/bdk/src/wallet/error.rs index db58fef06f..a90083fa12 100644 --- a/crates/bdk/src/wallet/error.rs +++ b/crates/bdk/src/wallet/error.rs @@ -44,9 +44,9 @@ impl fmt::Display for MiniscriptPsbtError { impl std::error::Error for MiniscriptPsbtError {} #[derive(Debug)] -/// Error returned from [`TxBuilder::finish`] +/// Error returned by [`TxBuilder::finish`] /// -/// [`TxBuilder::finish`]: crate::wallet::tx_builder::TxBuilder::finish +/// [`TxBuilder::finish`]: super::tx_builder::TxBuilder::finish pub enum CreateTxError

{ /// There was a problem with the descriptors passed in Descriptor(DescriptorError), @@ -246,9 +246,7 @@ impl

From for CreateTxError

{ impl std::error::Error for CreateTxError

{} #[derive(Debug)] -/// Error returned from [`Wallet::build_fee_bump`] -/// -/// [`Wallet::build_fee_bump`]: super::Wallet::build_fee_bump +/// Error returned by [`Wallet::build_fee_bump`] pub enum BuildFeeBumpError { /// Happens when trying to spend an UTXO that is not in the internal database UnknownUtxo(OutPoint), diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 2177a88c47..b7a1f5e07f 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -41,6 +41,7 @@ use core::ops::Deref; use descriptor::error::Error as DescriptorError; use miniscript::psbt::{PsbtExt, PsbtInputExt, PsbtInputSatisfier}; +use bdk_chain::request::{FullScanRequest, SyncRequest}; use bdk_chain::tx_graph::CalculateFeeError; pub mod coin_selection; @@ -109,6 +110,38 @@ pub struct Update { pub chain: Option, } +impl + From<( + BTreeMap, + TxGraph, + local_chain::Update, + )> for Update +{ + fn from( + (last_active_indices, graph, chain): ( + BTreeMap, + TxGraph, + local_chain::Update, + ), + ) -> Self { + Self { + last_active_indices, + graph, + chain: Some(chain), + } + } +} + +impl From<(TxGraph, local_chain::Update)> for Update { + fn from((graph, chain): (TxGraph, local_chain::Update)) -> Self { + Self { + graph, + chain: Some(chain), + ..Default::default() + } + } +} + /// The changes made to a wallet by applying an [`Update`]. #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, Default)] pub struct ChangeSet { @@ -2353,6 +2386,70 @@ impl Wallet { pub fn local_chain(&self) -> &LocalChain { &self.chain } + + /// Create a [`SyncRequest`] for this wallet. + /// + /// This is the first step when performing a spk-based wallet sync, the returned [`SyncRequest`] collects + /// the wallet keychain all or unused script pub keys, unconfirmed transaction id, UTXOs and local + /// chain checkpoint data needed to start a blockchain sync with a blockchain client. For a faster + /// sync set `unused_spks_only` to true to only get updates for unused wallet script pub keys (addresses). + pub fn sync_request(&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() + .revealed_spks() + .map(|(_keychain, _index, script)| ScriptBuf::from(script)) + .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(); + + SyncRequest { + spks, + txids, + outpoints, + checkpoint, + } + } + + /// Create a [`FullScanRequest] for this wallet. + /// + /// This is the first step when performing a spk-based wallet full scan, the returned [`FullScanRequest] + /// collects iterators for the wallet's keychain script pub keys, and local chain checkpoint + /// data needed to start a blockchain full scan with a blockchain client. + /// + /// This operation is generally only used when importing or restoring a previously used wallet + /// in which the list of used scripts is not known. + pub fn full_scan_request( + &self, + ) -> FullScanRequest + Clone> { + let spks_by_keychain = self.all_unbounded_spk_iters(); + let checkpoint = self.latest_checkpoint(); + + FullScanRequest { + spks_by_keychain, + checkpoint, + } + } } impl AsRef> for Wallet { diff --git a/example-crates/wallet_esplora_async/.gitignore b/example-crates/wallet_esplora_async/.gitignore new file mode 100644 index 0000000000..630a732ef2 --- /dev/null +++ b/example-crates/wallet_esplora_async/.gitignore @@ -0,0 +1 @@ +bdk_wallet_esplora_async_example.dat diff --git a/example-crates/wallet_esplora_blocking/.gitignore b/example-crates/wallet_esplora_blocking/.gitignore new file mode 100644 index 0000000000..630a732ef2 --- /dev/null +++ b/example-crates/wallet_esplora_blocking/.gitignore @@ -0,0 +1 @@ +bdk_wallet_esplora_async_example.dat