From ba06ccebf09c15e095cc36a025086b02dd071127 Mon Sep 17 00:00:00 2001 From: Steve Myers Date: Wed, 6 Dec 2023 21:14:16 -0600 Subject: [PATCH] refactor(electrum_ext): rename scan_without_keychain to sync and scan to full_scan removed txids and outpoints params from full_scan --- crates/electrum/README.md | 6 +- crates/electrum/src/electrum_ext.rs | 107 +++++++++++--------- crates/electrum/src/lib.rs | 34 +++---- example-crates/example_electrum/src/main.rs | 11 +- example-crates/wallet_electrum/src/main.rs | 2 +- 5 files changed, 83 insertions(+), 77 deletions(-) diff --git a/crates/electrum/README.md b/crates/electrum/README.md index d3ada695e6..1bafe04eb4 100644 --- a/crates/electrum/README.md +++ b/crates/electrum/README.md @@ -1,3 +1,7 @@ # BDK Electrum -BDK Electrum client library for updating the keychain tracker. +BDK Electrum extends [`electrum-client`] to update [`bdk_chain`] structures +from an Electrum server. + +[`electrum-client`]: https://docs.rs/electrum-client/ +[`bdk_chain`]: https://docs.rs/bdk-chain/ diff --git a/crates/electrum/src/electrum_ext.rs b/crates/electrum/src/electrum_ext.rs index e54007cd93..898914b201 100644 --- a/crates/electrum/src/electrum_ext.rs +++ b/crates/electrum/src/electrum_ext.rs @@ -134,64 +134,54 @@ pub struct ElectrumUpdate { /// Trait to extend [`Client`] functionality. pub trait ElectrumExt { - /// Scan the blockchain (via electrum) for the data specified and returns updates for - /// [`bdk_chain`] data structures. + /// Full scan the keychain scripts specified with the blockchain (via an Electrum client) and + /// returns updates for [`bdk_chain`] data structures. /// /// - `prev_tip`: the most recent blockchain tip present locally /// - `keychain_spks`: keychains that we want to scan transactions for - /// - `txids`: transactions for which we want updated [`Anchor`]s - /// - `outpoints`: transactions associated with these outpoints (residing, spending) that we - /// want to included in the update /// - /// The scan for each keychain stops after a gap of `stop_gap` script pubkeys with no associated + /// The full scan for each keychain stops after a gap of `stop_gap` script pubkeys with no associated /// transactions. `batch_size` specifies the max number of script pubkeys to request for in a /// single batch request. - fn scan( + fn full_scan( &self, - prev_tip: CheckPoint, + prev_tip: &CheckPoint, keychain_spks: BTreeMap>, - txids: impl IntoIterator, - outpoints: impl IntoIterator, stop_gap: usize, batch_size: usize, ) -> Result<(ElectrumUpdate, BTreeMap), Error>; - /// Convenience method to call [`scan`] without requiring a keychain. + /// Sync a set of scripts with the blockchain (via an Electrum client) for the data specified + /// and returns updates for [`bdk_chain`] data structures. + /// + /// - `prev_tip`: the most recent blockchain tip present locally + /// - `misc_spks`: an iterator of scripts we want to sync transactions for + /// - `txids`: transactions for which we want updated [`Anchor`]s + /// - `outpoints`: transactions associated with these outpoints (residing, spending) that we + /// want to included in the update + /// + /// `batch_size` specifies the max number of script pubkeys to request for in a single batch + /// request. /// - /// [`scan`]: ElectrumExt::scan - fn scan_without_keychain( + /// If the scripts to sync are unknown, such as when restoring or importing a keychain that + /// may include scripts that have been used, use [`full_scan`] with the keychain. + /// + /// [`full_scan`]: ElectrumExt::full_scan + fn sync( &self, - prev_tip: CheckPoint, + prev_tip: &CheckPoint, misc_spks: impl IntoIterator, txids: impl IntoIterator, outpoints: impl IntoIterator, batch_size: usize, - ) -> Result { - let spk_iter = misc_spks - .into_iter() - .enumerate() - .map(|(i, spk)| (i as u32, spk)); - - let (electrum_update, _) = self.scan( - prev_tip, - [((), spk_iter)].into(), - txids, - outpoints, - usize::MAX, - batch_size, - )?; - - Ok(electrum_update) - } + ) -> Result; } impl ElectrumExt for Client { - fn scan( + fn full_scan( &self, - prev_tip: CheckPoint, + prev_tip: &CheckPoint, keychain_spks: BTreeMap>, - txids: impl IntoIterator, - outpoints: impl IntoIterator, stop_gap: usize, batch_size: usize, ) -> Result<(ElectrumUpdate, BTreeMap), Error> { @@ -201,9 +191,6 @@ impl ElectrumExt for Client { .collect::>(); let mut scanned_spks = BTreeMap::<(K, u32), (ScriptBuf, bool)>::new(); - let txids = txids.into_iter().collect::>(); - let outpoints = outpoints.into_iter().collect::>(); - let (electrum_update, keychain_update) = loop { let (tip, _) = construct_update_tip(self, prev_tip.clone())?; let mut relevant_txids = RelevantTxids::default(); @@ -242,15 +229,6 @@ impl ElectrumExt for Client { } } - populate_with_txids(self, &cps, &mut relevant_txids, &mut txids.iter().cloned())?; - - let _txs = populate_with_outpoints( - self, - &cps, - &mut relevant_txids, - &mut outpoints.iter().cloned(), - )?; - // check for reorgs during scan process let server_blockhash = self.block_header(tip.height() as usize)?.block_hash(); if tip.hash() != server_blockhash { @@ -284,6 +262,37 @@ impl ElectrumExt for Client { Ok((electrum_update, keychain_update)) } + + fn sync( + &self, + prev_tip: &CheckPoint, + misc_spks: impl IntoIterator, + txids: impl IntoIterator, + outpoints: impl IntoIterator, + batch_size: usize, + ) -> Result { + let spk_iter = misc_spks + .into_iter() + .enumerate() + .map(|(i, spk)| (i as u32, spk)); + + let (mut electrum_update, _) = + self.full_scan(prev_tip, [((), spk_iter)].into(), usize::MAX, batch_size)?; + + let (tip, _) = construct_update_tip(self, prev_tip.clone())?; + let cps = tip + .iter() + .take(10) + .map(|cp| (cp.height(), cp)) + .collect::>(); + + populate_with_txids(self, &cps, &mut electrum_update.relevant_txids, txids)?; + + let _txs = + populate_with_outpoints(self, &cps, &mut electrum_update.relevant_txids, outpoints)?; + + Ok(electrum_update) + } } /// Return a [`CheckPoint`] of the latest tip, that connects with `prev_tip`. @@ -405,7 +414,7 @@ fn populate_with_outpoints( client: &Client, cps: &BTreeMap, relevant_txids: &mut RelevantTxids, - outpoints: &mut impl Iterator, + outpoints: impl IntoIterator, ) -> Result, Error> { let mut full_txs = HashMap::new(); for outpoint in outpoints { @@ -466,7 +475,7 @@ fn populate_with_txids( client: &Client, cps: &BTreeMap, relevant_txids: &mut RelevantTxids, - txids: &mut impl Iterator, + txids: impl IntoIterator, ) -> Result<(), Error> { for txid in txids { let tx = match client.transaction_get(&txid) { diff --git a/crates/electrum/src/lib.rs b/crates/electrum/src/lib.rs index 1e4805379c..87c0e46188 100644 --- a/crates/electrum/src/lib.rs +++ b/crates/electrum/src/lib.rs @@ -1,26 +1,26 @@ -//! This crate is used for updating structures of the [`bdk_chain`] crate with data from electrum. +//! This crate is used for updating structures of [`bdk_chain`] with data from an Electrum server. //! -//! The star of the show is the [`ElectrumExt::scan`] method, which scans for relevant blockchain -//! data (via electrum) and outputs updates for [`bdk_chain`] structures as a tuple of form: +//! The two primary methods are [`ElectrumExt::sync`] and [`ElectrumExt::full_scan`]. In most cases +//! [`ElectrumExt::sync`] is used to sync the transaction histories of scripts that the application +//! cares about, for example the scripts for all the receive addresses of a Wallet's keychain that it +//! has shown a user. [`ElectrumExt::full_scan`] is meant to be used when importing or restoring a +//! keychain where the range of possibly used scripts is not known. In this case it is necessary to +//! scan all keychain scripts until a number (the "stop gap") of unused scripts is discovered. For a +//! sync or full scan the user receives relevant blockchain data and output updates for +//! [`bdk_chain`] including [`RelevantTxids`]. //! -//! ([`bdk_chain::local_chain::Update`], [`RelevantTxids`], `keychain_update`) +//! The [`RelevantTxids`] only includes `txid`s and not full transactions. The caller is responsible +//! for obtaining full transactions before applying new data to their [`bdk_chain`]. This can be +//! done with these steps: //! -//! An [`RelevantTxids`] only includes `txid`s and no full transactions. The caller is -//! responsible for obtaining full transactions before applying. This can be done with -//! these steps: +//! 1. Determine which full transactions are missing. Use [`RelevantTxids::missing_full_txs`]. //! -//! 1. Determine which full transactions are missing. The method [`missing_full_txs`] of -//! [`RelevantTxids`] can be used. +//! 2. Obtaining the full transactions. To do this via electrum use [`ElectrumApi::batch_transaction_get`]. //! -//! 2. Obtaining the full transactions. To do this via electrum, the method -//! [`batch_transaction_get`] can be used. +//! Refer to [`example_electrum`] for a complete example. //! -//! Refer to [`bdk_electrum_example`] for a complete example. -//! -//! [`ElectrumClient::scan`]: electrum_client::ElectrumClient::scan -//! [`missing_full_txs`]: RelevantTxids::missing_full_txs -//! [`batch_transaction_get`]: electrum_client::ElectrumApi::batch_transaction_get -//! [`bdk_electrum_example`]: https://github.com/LLFourn/bdk_core_staging/tree/master/bdk_electrum_example +//! [`ElectrumApi::batch_transaction_get`]: electrum_client::ElectrumApi::batch_transaction_get +//! [`example_electrum`]: https://github.com/bitcoindevkit/bdk/tree/master/example-crates/example_electrum #![warn(missing_docs)] diff --git a/example-crates/example_electrum/src/main.rs b/example-crates/example_electrum/src/main.rs index a96378f647..b3d7fc0d02 100644 --- a/example-crates/example_electrum/src/main.rs +++ b/example-crates/example_electrum/src/main.rs @@ -172,14 +172,7 @@ fn main() -> anyhow::Result<()> { }; client - .scan( - tip, - keychain_spks, - core::iter::empty(), - core::iter::empty(), - stop_gap, - scan_options.batch_size, - ) + .full_scan(&tip, keychain_spks, stop_gap, scan_options.batch_size) .context("scanning the blockchain")? } ElectrumCommands::Sync { @@ -279,7 +272,7 @@ fn main() -> anyhow::Result<()> { drop((graph, chain)); let electrum_update = client - .scan_without_keychain(tip, spks, txids, outpoints, scan_options.batch_size) + .sync(&tip, spks, txids, outpoints, scan_options.batch_size) .context("scanning the blockchain")?; (electrum_update, BTreeMap::new()) } diff --git a/example-crates/wallet_electrum/src/main.rs b/example-crates/wallet_electrum/src/main.rs index 9d4c6c5a4f..829c8750dd 100644 --- a/example-crates/wallet_electrum/src/main.rs +++ b/example-crates/wallet_electrum/src/main.rs @@ -61,7 +61,7 @@ fn main() -> Result<(), anyhow::Error> { relevant_txids, }, keychain_update, - ) = client.scan(prev_tip, keychain_spks, None, None, STOP_GAP, BATCH_SIZE)?; + ) = client.full_scan(&prev_tip, keychain_spks, STOP_GAP, BATCH_SIZE)?; println!();