From 6bcbb93233824ec391689191b8ca1f5459cec930 Mon Sep 17 00:00:00 2001 From: Vladimir Fomene Date: Mon, 21 Aug 2023 11:20:38 +0300 Subject: [PATCH 01/11] refactor: Edit ElectrumExt not to use WalletUpdate --- crates/electrum/src/electrum_ext.rs | 45 +++++++++------------ example-crates/example_electrum/src/main.rs | 16 ++++---- example-crates/wallet_electrum/src/main.rs | 14 ++++++- 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/crates/electrum/src/electrum_ext.rs b/crates/electrum/src/electrum_ext.rs index c7859bdf..e81ef1d3 100644 --- a/crates/electrum/src/electrum_ext.rs +++ b/crates/electrum/src/electrum_ext.rs @@ -1,6 +1,5 @@ use bdk_chain::{ bitcoin::{OutPoint, ScriptBuf, Transaction, Txid}, - keychain::WalletUpdate, local_chain::{self, CheckPoint}, tx_graph::{self, TxGraph}, Anchor, BlockId, ConfirmationHeightAnchor, ConfirmationTimeAnchor, @@ -15,7 +14,8 @@ use std::{ /// We assume that a block of this depth and deeper cannot be reorged. const ASSUME_FINAL_DEPTH: u32 = 8; -/// Represents an update fetched from an Electrum server, but excludes full transactions. +/// Represents an update fetched from an Electrum server, but excludes full +/// transactions. /// /// To provide a complete update to [`TxGraph`], you'll need to call [`Self::missing_full_txs`] to /// determine the full transactions missing from [`TxGraph`]. Then call [`Self::finalize`] to fetch @@ -58,7 +58,7 @@ impl ElectrumUpdate { client: &Client, seen_at: Option, missing: Vec, - ) -> Result, Error> { + ) -> Result<(TxGraph, BTreeMap, local_chain::CheckPoint), Error> { let new_txs = client.batch_transaction_get(&missing)?; let mut graph_update = TxGraph::::new(new_txs); for (txid, anchors) in self.graph_update { @@ -69,14 +69,7 @@ impl ElectrumUpdate { let _ = graph_update.insert_anchor(txid, anchor); } } - Ok(WalletUpdate { - last_active_indices: self.keychain_update, - graph: graph_update, - chain: local_chain::Update { - tip: self.new_tip, - introduce_older_blocks: true, - }, - }) + Ok((graph_update, self.keychain_update, self.new_tip)) } } @@ -92,13 +85,19 @@ impl ElectrumUpdate { client: &Client, seen_at: Option, missing: Vec, - ) -> Result, Error> { - let update = self.finalize(client, seen_at, missing)?; + ) -> Result< + ( + TxGraph, + BTreeMap, + local_chain::CheckPoint, + ), + Error, + > { + let (graph, keychain_update, update_tip) = self.finalize(client, seen_at, missing)?; let relevant_heights = { let mut visited_heights = HashSet::new(); - update - .graph + graph .all_anchors() .iter() .map(|(a, _)| a.confirmation_height_upper_bound()) @@ -118,7 +117,7 @@ impl ElectrumUpdate { .collect::>(); let graph_changeset = { - let old_changeset = TxGraph::default().apply_update(update.graph.clone()); + let old_changeset = TxGraph::default().apply_update(graph.clone()); tx_graph::ChangeSet { txs: old_changeset.txs, txouts: old_changeset.txouts, @@ -140,15 +139,10 @@ impl ElectrumUpdate { } }; - Ok(WalletUpdate { - last_active_indices: update.last_active_indices, - graph: { - let mut graph = TxGraph::default(); - graph.apply_changeset(graph_changeset); - graph - }, - chain: update.chain, - }) + let mut update = TxGraph::default(); + update.apply_changeset(graph_changeset); + + Ok((update, keychain_update, update_tip)) } } @@ -457,7 +451,6 @@ fn populate_with_outpoints( }; let anchor = determine_tx_anchor(cps, res.height, res.tx_hash); - let tx_entry = update.graph_update.entry(res.tx_hash).or_default(); if let Some(anchor) = anchor { tx_entry.insert(anchor); diff --git a/example-crates/example_electrum/src/main.rs b/example-crates/example_electrum/src/main.rs index 4c5fde6a..56cc144c 100644 --- a/example-crates/example_electrum/src/main.rs +++ b/example-crates/example_electrum/src/main.rs @@ -8,7 +8,7 @@ use bdk_chain::{ bitcoin::{Address, Network, OutPoint, ScriptBuf, Txid}, indexed_tx_graph::{self, IndexedTxGraph}, keychain::WalletChangeSet, - local_chain::LocalChain, + local_chain::{self, LocalChain}, Append, ConfirmationHeightAnchor, }; use bdk_electrum::{ @@ -269,25 +269,27 @@ fn main() -> anyhow::Result<()> { .expect("must get time") .as_secs(); - let final_update = response.finalize(&client, Some(now), missing_txids)?; + let (graph_update, keychain_update, update_tip) = + response.finalize(&client, Some(now), missing_txids)?; let db_changeset = { let mut chain = chain.lock().unwrap(); let mut graph = graph.lock().unwrap(); - let chain = chain.apply_update(final_update.chain)?; + let chain = chain.apply_update(local_chain::Update { + tip: update_tip, + introduce_older_blocks: true, + })?; let indexed_tx_graph = { let mut changeset = indexed_tx_graph::ChangeSet::::default(); - let (_, indexer) = graph - .index - .reveal_to_target_multi(&final_update.last_active_indices); + let (_, indexer) = graph.index.reveal_to_target_multi(&keychain_update); changeset.append(indexed_tx_graph::ChangeSet { indexer, ..Default::default() }); - changeset.append(graph.apply_update(final_update.graph)); + changeset.append(graph.apply_update(graph_update)); changeset }; diff --git a/example-crates/wallet_electrum/src/main.rs b/example-crates/wallet_electrum/src/main.rs index 52def58e..3ba7e5f4 100644 --- a/example-crates/wallet_electrum/src/main.rs +++ b/example-crates/wallet_electrum/src/main.rs @@ -9,6 +9,7 @@ use std::str::FromStr; use bdk::bitcoin::Address; use bdk::SignOptions; use bdk::{bitcoin::Network, Wallet}; +use bdk_electrum::bdk_chain::{keychain::WalletUpdate, local_chain}; use bdk_electrum::electrum_client::{self, ElectrumApi}; use bdk_electrum::ElectrumExt; use bdk_file_store::Store; @@ -57,9 +58,18 @@ fn main() -> Result<(), Box> { println!(); let missing = electrum_update.missing_full_txs(wallet.as_ref()); - let update = electrum_update.finalize_as_confirmation_time(&client, None, missing)?; + let (graph_update, keychain_update, update_tip) = + electrum_update.finalize_as_confirmation_time(&client, None, missing)?; - wallet.apply_update(update)?; + let wallet_update = WalletUpdate { + last_active_indices: keychain_update, + graph: graph_update, + chain: local_chain::Update { + tip: update_tip, + introduce_older_blocks: true, + }, + }; + wallet.apply_update(wallet_update)?; wallet.commit()?; let balance = wallet.get_balance(); From 7c12dc994242bf2d7e35c2723f6e7000de97a388 Mon Sep 17 00:00:00 2001 From: Vladimir Fomene Date: Mon, 21 Aug 2023 11:45:42 +0300 Subject: [PATCH 02/11] refactor: Remove ForEachTxout trait --- crates/chain/src/keychain/txout_index.rs | 14 ++++----- crates/chain/src/spk_txout_index.rs | 39 ++++++++---------------- crates/chain/src/tx_data_traits.rs | 33 -------------------- crates/chain/src/tx_graph.rs | 14 +-------- 4 files changed, 20 insertions(+), 80 deletions(-) diff --git a/crates/chain/src/keychain/txout_index.rs b/crates/chain/src/keychain/txout_index.rs index 9b38a7ad..0376473f 100644 --- a/crates/chain/src/keychain/txout_index.rs +++ b/crates/chain/src/keychain/txout_index.rs @@ -3,7 +3,7 @@ use crate::{ indexed_tx_graph::Indexer, miniscript::{Descriptor, DescriptorPublicKey}, spk_iter::BIP32_MAX_INDEX, - ForEachTxOut, SpkIterator, SpkTxOutIndex, + SpkIterator, SpkTxOutIndex, }; use alloc::vec::Vec; use bitcoin::{OutPoint, Script, TxOut}; @@ -112,7 +112,7 @@ impl Indexer for KeychainTxOutIndex { } impl KeychainTxOutIndex { - /// Scans an object for relevant outpoints, which are stored and indexed internally. + /// Scans a transaction for relevant outpoints, which are stored and indexed internally. /// /// If the matched script pubkey is part of the lookahead, the last stored index is updated for /// the script pubkey's keychain and the [`super::ChangeSet`] returned will reflect the @@ -124,13 +124,11 @@ impl KeychainTxOutIndex { /// your txouts. /// 2. When getting new data from the chain, you usually scan it before incorporating it into /// your chain state (i.e., `SparseChain`, `ChainGraph`). - /// - /// See [`ForEachTxout`] for the types that support this. - /// - /// [`ForEachTxout`]: crate::ForEachTxOut - pub fn scan(&mut self, txouts: &impl ForEachTxOut) -> super::ChangeSet { + pub fn scan(&mut self, tx: &bitcoin::Transaction) -> super::ChangeSet { let mut changeset = super::ChangeSet::::default(); - txouts.for_each_txout(|(op, txout)| changeset.append(self.scan_txout(op, txout))); + for (op, txout) in tx.output.iter().enumerate() { + changeset.append(self.scan_txout(OutPoint::new(tx.txid(), op as u32), txout)); + } changeset } diff --git a/crates/chain/src/spk_txout_index.rs b/crates/chain/src/spk_txout_index.rs index 5547f37c..a4d6d7c2 100644 --- a/crates/chain/src/spk_txout_index.rs +++ b/crates/chain/src/spk_txout_index.rs @@ -3,7 +3,6 @@ use core::ops::RangeBounds; use crate::{ collections::{hash_map::Entry, BTreeMap, BTreeSet, HashMap}, indexed_tx_graph::Indexer, - ForEachTxOut, }; use bitcoin::{self, OutPoint, Script, ScriptBuf, Transaction, TxOut, Txid}; @@ -77,41 +76,23 @@ impl Indexer for SpkTxOutIndex { } } -/// This macro is used instead of a member function of `SpkTxOutIndex`, which would result in a -/// compiler error[E0521]: "borrowed data escapes out of closure" when we attempt to take a -/// reference out of the `ForEachTxOut` closure during scanning. -macro_rules! scan_txout { - ($self:ident, $op:expr, $txout:expr) => {{ - let spk_i = $self.spk_indices.get(&$txout.script_pubkey); - if let Some(spk_i) = spk_i { - $self.txouts.insert($op, (spk_i.clone(), $txout.clone())); - $self.spk_txouts.insert((spk_i.clone(), $op)); - $self.unused.remove(&spk_i); - } - spk_i - }}; -} - impl SpkTxOutIndex { - /// Scans an object containing many txouts. + /// Scans a transaction containing many txouts. /// /// Typically, this is used in two situations: /// /// 1. After loading transaction data from the disk, you may scan over all the txouts to restore all /// your txouts. /// 2. When getting new data from the chain, you usually scan it before incorporating it into your chain state. - /// - /// See [`ForEachTxout`] for the types that support this. - /// - /// [`ForEachTxout`]: crate::ForEachTxOut - pub fn scan(&mut self, txouts: &impl ForEachTxOut) -> BTreeSet { + pub fn scan(&mut self, tx: &bitcoin::Transaction) -> BTreeSet { let mut scanned_indices = BTreeSet::new(); - txouts.for_each_txout(|(op, txout)| { - if let Some(spk_i) = scan_txout!(self, op, txout) { + for (i, txout) in tx.output.iter().enumerate() { + let op = OutPoint::new(tx.txid(), i as u32); + if let Some(spk_i) = self.scan_txout(op, txout) { scanned_indices.insert(spk_i.clone()); } - }); + } scanned_indices } @@ -119,7 +100,13 @@ impl SpkTxOutIndex { /// Scan a single `TxOut` for a matching script pubkey and returns the index that matches the /// script pubkey (if any). pub fn scan_txout(&mut self, op: OutPoint, txout: &TxOut) -> Option<&I> { - scan_txout!(self, op, txout) + let spk_i = self.spk_indices.get(&txout.script_pubkey); + if let Some(spk_i) = spk_i { + self.txouts.insert(op, (spk_i.clone(), txout.clone())); + self.spk_txouts.insert((spk_i.clone(), op)); + self.unused.remove(spk_i); + } + spk_i } /// Get a reference to the set of indexed outpoints. diff --git a/crates/chain/src/tx_data_traits.rs b/crates/chain/src/tx_data_traits.rs index 43fc73b4..f28044c6 100644 --- a/crates/chain/src/tx_data_traits.rs +++ b/crates/chain/src/tx_data_traits.rs @@ -2,39 +2,6 @@ use crate::collections::BTreeMap; use crate::collections::BTreeSet; use crate::BlockId; use alloc::vec::Vec; -use bitcoin::{Block, OutPoint, Transaction, TxOut}; - -/// Trait to do something with every txout contained in a structure. -/// -/// We would prefer to just work with things that can give us an `Iterator` -/// here, but rust's type system makes it extremely hard to do this (without trait objects). -pub trait ForEachTxOut { - /// The provided closure `f` will be called with each `outpoint/txout` pair. - fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut))); -} - -impl ForEachTxOut for Block { - fn for_each_txout(&self, mut f: impl FnMut((OutPoint, &TxOut))) { - for tx in self.txdata.iter() { - tx.for_each_txout(&mut f) - } - } -} - -impl ForEachTxOut for Transaction { - fn for_each_txout(&self, mut f: impl FnMut((OutPoint, &TxOut))) { - let txid = self.txid(); - for (i, txout) in self.output.iter().enumerate() { - f(( - OutPoint { - txid, - vout: i as u32, - }, - txout, - )) - } - } -} /// Trait that "anchors" blockchain data to a specific block of height and hash. /// diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index 7f58f203..cfd2de9d 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -52,7 +52,7 @@ use crate::{ collections::*, keychain::Balance, local_chain::LocalChain, Anchor, Append, BlockId, - ChainOracle, ChainPosition, ForEachTxOut, FullTxOut, + ChainOracle, ChainPosition, FullTxOut, }; use alloc::vec::Vec; use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid}; @@ -1137,18 +1137,6 @@ impl AsRef> for TxGraph { } } -impl ForEachTxOut for ChangeSet { - fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut))) { - self.txouts().for_each(f) - } -} - -impl ForEachTxOut for TxGraph { - fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut))) { - self.all_txouts().for_each(f) - } -} - /// An iterator that traverses transaction descendants. /// /// This `struct` is created by the [`walk_descendants`] method of [`TxGraph`]. From 2392e50fd9793902d480556caa4ec225085c82d6 Mon Sep 17 00:00:00 2001 From: Vladimir Fomene Date: Mon, 21 Aug 2023 12:13:58 +0300 Subject: [PATCH 03/11] refactor: Move WalletUpdate to wallet module --- crates/bdk/src/wallet/mod.rs | 31 ++++++++++++++++- crates/chain/src/keychain.rs | 33 +------------------ example-crates/wallet_electrum/src/main.rs | 4 +-- .../wallet_esplora_async/src/main.rs | 3 +- .../wallet_esplora_blocking/src/main.rs | 3 +- 5 files changed, 35 insertions(+), 39 deletions(-) diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index f7bc5f49..1fe16147 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -22,7 +22,7 @@ use alloc::{ pub use bdk_chain::keychain::Balance; use bdk_chain::{ indexed_tx_graph, - keychain::{KeychainTxOutIndex, WalletChangeSet, WalletUpdate}, + keychain::{KeychainTxOutIndex, WalletChangeSet}, local_chain::{self, CannotConnectError, CheckPoint, CheckPointIter, LocalChain}, tx_graph::{CanonicalTx, TxGraph}, Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeAnchor, FullTxOut, @@ -95,6 +95,35 @@ pub struct Wallet { secp: SecpCtx, } +/// A structure to update [`KeychainTxOutIndex`], [`TxGraph`] and [`LocalChain`] atomically. +/// +/// [`LocalChain`]: local_chain::LocalChain +#[derive(Debug, Clone)] +pub struct WalletUpdate { + /// Contains the last active derivation indices per keychain (`K`), which is used to update the + /// [`KeychainTxOutIndex`]. + pub last_active_indices: BTreeMap, + + /// Update for the [`TxGraph`]. + pub graph: TxGraph, + + /// Update for the [`LocalChain`]. + /// + /// [`LocalChain`]: local_chain::LocalChain + pub chain: local_chain::Update, +} + +impl WalletUpdate { + /// Construct a [`WalletUpdate`] with a given [`local_chain::Update`]. + pub fn new(chain_update: local_chain::Update) -> Self { + Self { + last_active_indices: BTreeMap::new(), + graph: TxGraph::default(), + chain: chain_update, + } + } +} + /// The update to a [`Wallet`] used in [`Wallet::apply_update`]. This is usually returned from blockchain data sources. pub type Update = WalletUpdate; diff --git a/crates/chain/src/keychain.rs b/crates/chain/src/keychain.rs index 3a6cddf8..74e98242 100644 --- a/crates/chain/src/keychain.rs +++ b/crates/chain/src/keychain.rs @@ -10,9 +10,7 @@ //! //! [`SpkTxOutIndex`]: crate::SpkTxOutIndex -use crate::{ - collections::BTreeMap, indexed_tx_graph, local_chain, tx_graph::TxGraph, Anchor, Append, -}; +use crate::{collections::BTreeMap, indexed_tx_graph, local_chain, Anchor, Append}; #[cfg(feature = "miniscript")] mod txout_index; @@ -82,35 +80,6 @@ impl AsRef> for ChangeSet { } } -/// A structure to update [`KeychainTxOutIndex`], [`TxGraph`] and [`LocalChain`] atomically. -/// -/// [`LocalChain`]: local_chain::LocalChain -#[derive(Debug, Clone)] -pub struct WalletUpdate { - /// Contains the last active derivation indices per keychain (`K`), which is used to update the - /// [`KeychainTxOutIndex`]. - pub last_active_indices: BTreeMap, - - /// Update for the [`TxGraph`]. - pub graph: TxGraph, - - /// Update for the [`LocalChain`]. - /// - /// [`LocalChain`]: local_chain::LocalChain - pub chain: local_chain::Update, -} - -impl WalletUpdate { - /// Construct a [`WalletUpdate`] with a given [`local_chain::Update`]. - pub fn new(chain_update: local_chain::Update) -> Self { - Self { - last_active_indices: BTreeMap::new(), - graph: TxGraph::default(), - chain: chain_update, - } - } -} - /// A structure that records the corresponding changes as result of applying an [`WalletUpdate`]. #[derive(Debug, Clone, PartialEq)] #[cfg_attr( diff --git a/example-crates/wallet_electrum/src/main.rs b/example-crates/wallet_electrum/src/main.rs index 3ba7e5f4..86ea8a7c 100644 --- a/example-crates/wallet_electrum/src/main.rs +++ b/example-crates/wallet_electrum/src/main.rs @@ -8,8 +8,8 @@ use std::str::FromStr; use bdk::bitcoin::Address; use bdk::SignOptions; -use bdk::{bitcoin::Network, Wallet}; -use bdk_electrum::bdk_chain::{keychain::WalletUpdate, local_chain}; +use bdk::{bitcoin::Network, wallet::WalletUpdate, Wallet}; +use bdk_electrum::bdk_chain::local_chain; use bdk_electrum::electrum_client::{self, ElectrumApi}; use bdk_electrum::ElectrumExt; use bdk_file_store::Store; diff --git a/example-crates/wallet_esplora_async/src/main.rs b/example-crates/wallet_esplora_async/src/main.rs index 343a0976..34c3928b 100644 --- a/example-crates/wallet_esplora_async/src/main.rs +++ b/example-crates/wallet_esplora_async/src/main.rs @@ -2,8 +2,7 @@ use std::{io::Write, str::FromStr}; use bdk::{ bitcoin::{Address, Network}, - chain::keychain::WalletUpdate, - wallet::AddressIndex, + wallet::{AddressIndex, WalletUpdate}, SignOptions, Wallet, }; use bdk_esplora::{esplora_client, EsploraAsyncExt}; diff --git a/example-crates/wallet_esplora_blocking/src/main.rs b/example-crates/wallet_esplora_blocking/src/main.rs index d108742a..0fb6b4bd 100644 --- a/example-crates/wallet_esplora_blocking/src/main.rs +++ b/example-crates/wallet_esplora_blocking/src/main.rs @@ -7,8 +7,7 @@ use std::{io::Write, str::FromStr}; use bdk::{ bitcoin::{Address, Network}, - chain::keychain::WalletUpdate, - wallet::AddressIndex, + wallet::{AddressIndex, WalletUpdate}, SignOptions, Wallet, }; use bdk_esplora::{esplora_client, EsploraExt}; From 68572bfd2e32efdeefaa46618e8e248d3a87f143 Mon Sep 17 00:00:00 2001 From: Vladimir Fomene Date: Mon, 21 Aug 2023 15:18:16 +0300 Subject: [PATCH 04/11] refactor: move WalletChangeset to wallet module Consequently, remove the `WalletChangeset` dependency from `example_electrum` and `example_esplora` examples. --- crates/bdk/src/wallet/mod.rs | 62 +++++++++++++++++++- crates/chain/src/keychain.rs | 65 +-------------------- crates/chain/src/lib.rs | 8 +++ example-crates/example_electrum/src/main.rs | 16 ++--- example-crates/example_esplora/src/main.rs | 40 +++++++------ 5 files changed, 98 insertions(+), 93 deletions(-) diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 1fe16147..47dd03de 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -22,10 +22,10 @@ use alloc::{ pub use bdk_chain::keychain::Balance; use bdk_chain::{ indexed_tx_graph, - keychain::{KeychainTxOutIndex, WalletChangeSet}, + keychain::{self, KeychainTxOutIndex}, local_chain::{self, CannotConnectError, CheckPoint, CheckPointIter, LocalChain}, tx_graph::{CanonicalTx, TxGraph}, - Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeAnchor, FullTxOut, + Anchor, Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeAnchor, FullTxOut, IndexedTxGraph, Persist, PersistBackend, }; use bitcoin::consensus::encode::serialize; @@ -124,6 +124,62 @@ impl WalletUpdate { } } +/// A structure that records the corresponding changes as result of applying an [`WalletUpdate`]. +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] +pub struct WalletChangeSet { + /// Changes to the [`LocalChain`]. + /// + /// [`LocalChain`]: local_chain::LocalChain + pub chain: local_chain::ChangeSet, + + /// ChangeSet to [`IndexedTxGraph`]. + /// + /// [`IndexedTxGraph`]: bdk_chain::indexed_tx_graph::IndexedTxGraph + #[serde(bound( + deserialize = "K: Ord + serde::Deserialize<'de>, A: Ord + serde::Deserialize<'de>", + serialize = "K: Ord + serde::Serialize, A: Ord + serde::Serialize", + ))] + pub index_tx_graph: indexed_tx_graph::ChangeSet>, +} + +impl Default for WalletChangeSet { + fn default() -> Self { + Self { + chain: Default::default(), + index_tx_graph: Default::default(), + } + } +} + +impl Append for WalletChangeSet { + fn append(&mut self, other: Self) { + Append::append(&mut self.chain, other.chain); + Append::append(&mut self.index_tx_graph, other.index_tx_graph); + } + + fn is_empty(&self) -> bool { + self.chain.is_empty() && self.index_tx_graph.is_empty() + } +} + +impl From for WalletChangeSet { + fn from(chain: local_chain::ChangeSet) -> Self { + Self { + chain, + ..Default::default() + } + } +} + +impl From>> for WalletChangeSet { + fn from(index_tx_graph: indexed_tx_graph::ChangeSet>) -> Self { + Self { + index_tx_graph, + ..Default::default() + } + } +} + /// The update to a [`Wallet`] used in [`Wallet::apply_update`]. This is usually returned from blockchain data sources. pub type Update = WalletUpdate; @@ -277,7 +333,7 @@ impl Wallet { let changeset = db.load_from_persistence().map_err(NewError::Persist)?; chain.apply_changeset(&changeset.chain); - indexed_graph.apply_changeset(changeset.indexed_tx_graph); + indexed_graph.apply_changeset(changeset.index_tx_graph); let persist = Persist::new(db); diff --git a/crates/chain/src/keychain.rs b/crates/chain/src/keychain.rs index 74e98242..63972a0a 100644 --- a/crates/chain/src/keychain.rs +++ b/crates/chain/src/keychain.rs @@ -10,7 +10,7 @@ //! //! [`SpkTxOutIndex`]: crate::SpkTxOutIndex -use crate::{collections::BTreeMap, indexed_tx_graph, local_chain, Anchor, Append}; +use crate::{collections::BTreeMap, Append}; #[cfg(feature = "miniscript")] mod txout_index; @@ -80,69 +80,6 @@ impl AsRef> for ChangeSet { } } -/// A structure that records the corresponding changes as result of applying an [`WalletUpdate`]. -#[derive(Debug, Clone, PartialEq)] -#[cfg_attr( - feature = "serde", - derive(serde::Deserialize, serde::Serialize), - serde( - crate = "serde_crate", - bound( - deserialize = "K: Ord + serde::Deserialize<'de>, A: Ord + serde::Deserialize<'de>", - serialize = "K: Ord + serde::Serialize, A: Ord + serde::Serialize", - ) - ) -)] -pub struct WalletChangeSet { - /// Changes to the [`LocalChain`]. - /// - /// [`LocalChain`]: local_chain::LocalChain - pub chain: local_chain::ChangeSet, - - /// ChangeSet to [`IndexedTxGraph`]. - /// - /// [`IndexedTxGraph`]: crate::indexed_tx_graph::IndexedTxGraph - pub indexed_tx_graph: indexed_tx_graph::ChangeSet>, -} - -impl Default for WalletChangeSet { - fn default() -> Self { - Self { - chain: Default::default(), - indexed_tx_graph: Default::default(), - } - } -} - -impl Append for WalletChangeSet { - fn append(&mut self, other: Self) { - Append::append(&mut self.chain, other.chain); - Append::append(&mut self.indexed_tx_graph, other.indexed_tx_graph); - } - - fn is_empty(&self) -> bool { - self.chain.is_empty() && self.indexed_tx_graph.is_empty() - } -} - -impl From for WalletChangeSet { - fn from(chain: local_chain::ChangeSet) -> Self { - Self { - chain, - ..Default::default() - } - } -} - -impl From>> for WalletChangeSet { - fn from(indexed_tx_graph: indexed_tx_graph::ChangeSet>) -> Self { - Self { - indexed_tx_graph, - ..Default::default() - } - } -} - /// Balance, differentiated into various categories. #[derive(Debug, PartialEq, Eq, Clone, Default)] #[cfg_attr( diff --git a/crates/chain/src/lib.rs b/crates/chain/src/lib.rs index ed167ebf..f38b7ee5 100644 --- a/crates/chain/src/lib.rs +++ b/crates/chain/src/lib.rs @@ -100,3 +100,11 @@ pub mod collections { /// How many confirmations are needed f or a coinbase output to be spent. pub const COINBASE_MATURITY: u32 = 100; + +impl From> + for (local_chain::ChangeSet, indexed_tx_graph::ChangeSet) +{ + fn from(indexed_changeset: indexed_tx_graph::ChangeSet) -> Self { + (local_chain::ChangeSet::default(), indexed_changeset) + } +} diff --git a/example-crates/example_electrum/src/main.rs b/example-crates/example_electrum/src/main.rs index 56cc144c..f8bb10b1 100644 --- a/example-crates/example_electrum/src/main.rs +++ b/example-crates/example_electrum/src/main.rs @@ -7,7 +7,7 @@ use std::{ use bdk_chain::{ bitcoin::{Address, Network, OutPoint, ScriptBuf, Txid}, indexed_tx_graph::{self, IndexedTxGraph}, - keychain::WalletChangeSet, + keychain, local_chain::{self, LocalChain}, Append, ConfirmationHeightAnchor, }; @@ -60,7 +60,10 @@ pub struct ScanOptions { pub batch_size: usize, } -type ChangeSet = WalletChangeSet; +type ChangeSet = ( + local_chain::ChangeSet, + indexed_tx_graph::ChangeSet>, +); fn main() -> anyhow::Result<()> { let (args, keymap, index, db, init_changeset) = @@ -68,11 +71,11 @@ fn main() -> anyhow::Result<()> { let graph = Mutex::new({ let mut graph = IndexedTxGraph::new(index); - graph.apply_changeset(init_changeset.indexed_tx_graph); + graph.apply_changeset(init_changeset.1); graph }); - let chain = Mutex::new(LocalChain::from_changeset(init_changeset.chain)); + let chain = Mutex::new(LocalChain::from_changeset(init_changeset.0)); let electrum_url = match args.network { Network::Bitcoin => "ssl://electrum.blockstream.info:50002", @@ -293,10 +296,7 @@ fn main() -> anyhow::Result<()> { changeset }; - ChangeSet { - indexed_tx_graph, - chain, - } + (chain, indexed_tx_graph) }; let mut db = db.lock().unwrap(); diff --git a/example-crates/example_esplora/src/main.rs b/example-crates/example_esplora/src/main.rs index f33125be..9559e1d3 100644 --- a/example-crates/example_esplora/src/main.rs +++ b/example-crates/example_esplora/src/main.rs @@ -6,9 +6,9 @@ use std::{ use bdk_chain::{ bitcoin::{Address, Network, OutPoint, ScriptBuf, Txid}, - indexed_tx_graph::IndexedTxGraph, - keychain::WalletChangeSet, - local_chain::{CheckPoint, LocalChain}, + indexed_tx_graph::{self, IndexedTxGraph}, + keychain, + local_chain::{self, CheckPoint, LocalChain}, Append, ConfirmationTimeAnchor, }; @@ -23,6 +23,11 @@ use example_cli::{ const DB_MAGIC: &[u8] = b"bdk_example_esplora"; const DB_PATH: &str = ".bdk_esplora_example.db"; +type ChangeSet = ( + local_chain::ChangeSet, + indexed_tx_graph::ChangeSet>, +); + #[derive(Subcommand, Debug, Clone)] enum EsploraCommands { /// Scans the addresses in the wallet using the esplora API. @@ -60,22 +65,22 @@ pub struct ScanOptions { } fn main() -> anyhow::Result<()> { - let (args, keymap, index, db, init_changeset) = example_cli::init::< - EsploraCommands, - WalletChangeSet, - >(DB_MAGIC, DB_PATH)?; + let (args, keymap, index, db, init_changeset) = + example_cli::init::(DB_MAGIC, DB_PATH)?; + + let (init_chain_changeset, init_indexed_tx_graph_changeset) = init_changeset; // Contruct `IndexedTxGraph` and `LocalChain` with our initial changeset. They are wrapped in // `Mutex` to display how they can be used in a multithreaded context. Technically the mutexes // aren't strictly needed here. let graph = Mutex::new({ let mut graph = IndexedTxGraph::new(index); - graph.apply_changeset(init_changeset.indexed_tx_graph); + graph.apply_changeset(init_indexed_tx_graph_changeset); graph }); let chain = Mutex::new({ let mut chain = LocalChain::default(); - chain.apply_changeset(&init_changeset.chain); + chain.apply_changeset(&init_chain_changeset); chain }); @@ -307,18 +312,17 @@ fn main() -> anyhow::Result<()> { println!("missing block heights: {:?}", missing_block_heights); // Here, we actually fetch the missing blocks and create a `local_chain::Update`. - let chain_update = client - .update_local_chain(tip, missing_block_heights) - .context("scanning for blocks")?; - - println!("new tip: {}", chain_update.tip.height()); + let chain_changeset = { + let chain_update = client + .update_local_chain(tip, missing_block_heights) + .context("scanning for blocks")?; + println!("new tip: {}", chain_update.tip.height()); + chain.lock().unwrap().apply_update(chain_update)? + }; // We persist the changes let mut db = db.lock().unwrap(); - db.stage(WalletChangeSet { - chain: chain.lock().unwrap().apply_update(chain_update)?, - indexed_tx_graph: indexed_tx_graph_changeset, - }); + db.stage((chain_changeset, indexed_tx_graph_changeset)); db.commit()?; Ok(()) } From f42f8b8ff19c2e67888b476487e4e5c9edb0d0ff Mon Sep 17 00:00:00 2001 From: Vladimir Fomene Date: Thu, 24 Aug 2023 16:03:47 +0300 Subject: [PATCH 05/11] refactor: Allow for no chain update --- crates/bdk/src/wallet/mod.rs | 10 +++++++--- example-crates/wallet_electrum/src/main.rs | 4 ++-- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 47dd03de..21ba5ee6 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -110,7 +110,7 @@ pub struct WalletUpdate { /// Update for the [`LocalChain`]. /// /// [`LocalChain`]: local_chain::LocalChain - pub chain: local_chain::Update, + pub chain: Option, } impl WalletUpdate { @@ -119,7 +119,7 @@ impl WalletUpdate { Self { last_active_indices: BTreeMap::new(), graph: TxGraph::default(), - chain: chain_update, + chain: Some(chain_update), } } } @@ -1942,7 +1942,11 @@ impl Wallet { where D: PersistBackend, { - let mut changeset = ChangeSet::from(self.chain.apply_update(update.chain)?); + let mut changeset = match update.chain { + Some(chain_update) => ChangeSet::from(self.chain.apply_update(chain_update)?), + None => ChangeSet::default(), + }; + let (_, index_changeset) = self .indexed_graph .index diff --git a/example-crates/wallet_electrum/src/main.rs b/example-crates/wallet_electrum/src/main.rs index 86ea8a7c..0ea7df48 100644 --- a/example-crates/wallet_electrum/src/main.rs +++ b/example-crates/wallet_electrum/src/main.rs @@ -64,10 +64,10 @@ fn main() -> Result<(), Box> { let wallet_update = WalletUpdate { last_active_indices: keychain_update, graph: graph_update, - chain: local_chain::Update { + chain: Some(local_chain::Update { tip: update_tip, introduce_older_blocks: true, - }, + }), }; wallet.apply_update(wallet_update)?; wallet.commit()?; From a28748c33976312b9e6671636ab7e305323efb03 Mon Sep 17 00:00:00 2001 From: Vladimir Fomene Date: Fri, 25 Aug 2023 12:49:29 +0300 Subject: [PATCH 06/11] refactor: Implement Default for WalletUpdate --- crates/bdk/src/wallet/mod.rs | 11 +++++------ example-crates/wallet_esplora_async/src/main.rs | 2 +- example-crates/wallet_esplora_blocking/src/main.rs | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index 21ba5ee6..a7fe4d42 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -95,9 +95,9 @@ pub struct Wallet { secp: SecpCtx, } -/// A structure to update [`KeychainTxOutIndex`], [`TxGraph`] and [`LocalChain`] atomically. +/// A structure to update [`Wallet`]. /// -/// [`LocalChain`]: local_chain::LocalChain +/// It updates [`bdk_chain::keychain::KeychainTxOutIndex`], [`bdk_chain::TxGraph`] and [`local_chain::LocalChain`] atomically. #[derive(Debug, Clone)] pub struct WalletUpdate { /// Contains the last active derivation indices per keychain (`K`), which is used to update the @@ -113,13 +113,12 @@ pub struct WalletUpdate { pub chain: Option, } -impl WalletUpdate { - /// Construct a [`WalletUpdate`] with a given [`local_chain::Update`]. - pub fn new(chain_update: local_chain::Update) -> Self { +impl Default for WalletUpdate { + fn default() -> Self { Self { last_active_indices: BTreeMap::new(), graph: TxGraph::default(), - chain: Some(chain_update), + chain: None, } } } diff --git a/example-crates/wallet_esplora_async/src/main.rs b/example-crates/wallet_esplora_async/src/main.rs index 34c3928b..49692b5c 100644 --- a/example-crates/wallet_esplora_async/src/main.rs +++ b/example-crates/wallet_esplora_async/src/main.rs @@ -61,7 +61,7 @@ async fn main() -> Result<(), Box> { let update = WalletUpdate { last_active_indices, graph: update_graph, - ..WalletUpdate::new(chain_update) + chain: Some(chain_update), }; wallet.apply_update(update)?; wallet.commit()?; diff --git a/example-crates/wallet_esplora_blocking/src/main.rs b/example-crates/wallet_esplora_blocking/src/main.rs index 0fb6b4bd..fb6e0e87 100644 --- a/example-crates/wallet_esplora_blocking/src/main.rs +++ b/example-crates/wallet_esplora_blocking/src/main.rs @@ -60,7 +60,7 @@ fn main() -> Result<(), Box> { let update = WalletUpdate { last_active_indices, graph: update_graph, - ..WalletUpdate::new(chain_update) + chain: Some(chain_update), }; wallet.apply_update(update)?; From 32c40ac939bc514ac7f1d3f1d7cd1080011e20ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Sat, 26 Aug 2023 20:29:46 +0800 Subject: [PATCH 07/11] feat(electrum)!: change signature of `ElectrumExt` We remove `ElectrumUpdate` and return tuples instead for `ElectrumExt` methods. We introduce the `IncompleteTxGraph` structure to specifically hodl the incomplete `TxGraph`. This change is motivated by @LLFourn's comment: https://github.com/bitcoindevkit/bdk/pull/1084/commits/794bf37e637d3266b75827678f015e14f827d318#r1305432603 --- crates/electrum/src/electrum_ext.rs | 144 ++++++++++---------- crates/electrum/src/lib.rs | 12 +- example-crates/example_electrum/src/main.rs | 22 ++- example-crates/wallet_electrum/src/main.rs | 18 ++- 4 files changed, 93 insertions(+), 103 deletions(-) diff --git a/crates/electrum/src/electrum_ext.rs b/crates/electrum/src/electrum_ext.rs index e81ef1d3..b7435862 100644 --- a/crates/electrum/src/electrum_ext.rs +++ b/crates/electrum/src/electrum_ext.rs @@ -14,86 +14,63 @@ use std::{ /// We assume that a block of this depth and deeper cannot be reorged. const ASSUME_FINAL_DEPTH: u32 = 8; -/// Represents an update fetched from an Electrum server, but excludes full -/// transactions. +/// Represents a [`TxGraph`] update fetched from an Electrum server, but excludes full transactions. /// /// To provide a complete update to [`TxGraph`], you'll need to call [`Self::missing_full_txs`] to -/// determine the full transactions missing from [`TxGraph`]. Then call [`Self::finalize`] to fetch -/// the full transactions from Electrum and finalize the update. -#[derive(Debug, Clone)] -pub struct ElectrumUpdate { - /// Map of [`Txid`]s to associated [`Anchor`]s. - pub graph_update: HashMap>, - /// The latest chain tip, as seen by the Electrum server. - pub new_tip: local_chain::CheckPoint, - /// Last-used index update for [`KeychainTxOutIndex`](bdk_chain::keychain::KeychainTxOutIndex). - pub keychain_update: BTreeMap, -} - -impl ElectrumUpdate { - fn new(new_tip: local_chain::CheckPoint) -> Self { - Self { - new_tip, - graph_update: HashMap::new(), - keychain_update: BTreeMap::new(), - } - } +/// determine the full transactions missing from [`TxGraph`]. Then call [`Self::finalize`] to +/// fetch the full transactions from Electrum and finalize the update. +#[derive(Debug, Default, Clone)] +pub struct IncompleteTxGraph(HashMap>); +impl IncompleteTxGraph { /// Determine the full transactions that are missing from `graph`. /// - /// Refer to [`ElectrumUpdate`]. + /// Refer to [`IncompleteTxGraph`] for more. pub fn missing_full_txs(&self, graph: &TxGraph) -> Vec { - self.graph_update + self.0 .keys() .filter(move |&&txid| graph.as_ref().get_tx(txid).is_none()) .cloned() .collect() } - /// Finalizes update with `missing` txids to fetch from `client`. + /// Finalizes the [`TxGraph`] update by fetching `missing` txids from the `client`. /// - /// Refer to [`ElectrumUpdate`]. + /// Refer to [`IncompleteTxGraph`] for more. pub fn finalize( self, client: &Client, seen_at: Option, missing: Vec, - ) -> Result<(TxGraph, BTreeMap, local_chain::CheckPoint), Error> { + ) -> Result, Error> { let new_txs = client.batch_transaction_get(&missing)?; - let mut graph_update = TxGraph::::new(new_txs); - for (txid, anchors) in self.graph_update { + let mut graph = TxGraph::::new(new_txs); + for (txid, anchors) in self.0 { if let Some(seen_at) = seen_at { - let _ = graph_update.insert_seen_at(txid, seen_at); + let _ = graph.insert_seen_at(txid, seen_at); } for anchor in anchors { - let _ = graph_update.insert_anchor(txid, anchor); + let _ = graph.insert_anchor(txid, anchor); } } - Ok((graph_update, self.keychain_update, self.new_tip)) + Ok(graph) } } -impl ElectrumUpdate { - /// Finalizes the [`ElectrumUpdate`] with `new_txs` and anchors of type +impl IncompleteTxGraph { + /// Finalizes the [`IncompleteTxGraph`] with `new_txs` and anchors of type /// [`ConfirmationTimeAnchor`]. /// /// **Note:** The confirmation time might not be precisely correct if there has been a reorg. /// Electrum's API intends that we use the merkle proof API, we should change `bdk_electrum` to /// use it. - pub fn finalize_as_confirmation_time( + pub fn finalize_with_confirmation_time( self, client: &Client, seen_at: Option, missing: Vec, - ) -> Result< - ( - TxGraph, - BTreeMap, - local_chain::CheckPoint, - ), - Error, - > { - let (graph, keychain_update, update_tip) = self.finalize(client, seen_at, missing)?; + ) -> Result, Error> { + let graph = self.finalize(client, seen_at, missing)?; let relevant_heights = { let mut visited_heights = HashSet::new(); @@ -117,7 +94,7 @@ impl ElectrumUpdate { .collect::>(); let graph_changeset = { - let old_changeset = TxGraph::default().apply_update(graph.clone()); + let old_changeset = TxGraph::default().apply_update(graph); tx_graph::ChangeSet { txs: old_changeset.txs, txouts: old_changeset.txouts, @@ -139,16 +116,16 @@ impl ElectrumUpdate { } }; - let mut update = TxGraph::default(); - update.apply_changeset(graph_changeset); - - Ok((update, keychain_update, update_tip)) + let mut new_graph = TxGraph::default(); + new_graph.apply_changeset(graph_changeset); + Ok(new_graph) } } /// Trait to extend [`Client`] functionality. pub trait ElectrumExt { - /// Scan the blockchain (via electrum) for the data specified and returns a [`ElectrumUpdate`]. + /// Scan the blockchain (via electrum) for the data specified 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 @@ -159,6 +136,7 @@ pub trait ElectrumExt { /// The 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. + #[allow(clippy::type_complexity)] fn scan( &self, prev_tip: Option, @@ -167,7 +145,7 @@ pub trait ElectrumExt { outpoints: impl IntoIterator, stop_gap: usize, batch_size: usize, - ) -> Result, Error>; + ) -> Result<(local_chain::Update, IncompleteTxGraph, BTreeMap), Error>; /// Convenience method to call [`scan`] without requiring a keychain. /// @@ -179,20 +157,22 @@ pub trait ElectrumExt { txids: impl IntoIterator, outpoints: impl IntoIterator, batch_size: usize, - ) -> Result, Error> { + ) -> Result<(local_chain::Update, IncompleteTxGraph), Error> { let spk_iter = misc_spks .into_iter() .enumerate() .map(|(i, spk)| (i as u32, spk)); - self.scan( + let (chain, graph, _) = self.scan( prev_tip, [((), spk_iter)].into(), txids, outpoints, usize::MAX, batch_size, - ) + )?; + + Ok((chain, graph)) } } @@ -205,7 +185,14 @@ impl ElectrumExt for Client { outpoints: impl IntoIterator, stop_gap: usize, batch_size: usize, - ) -> Result, Error> { + ) -> Result< + ( + local_chain::Update, + IncompleteTxGraph, + BTreeMap, + ), + Error, + > { let mut request_spks = keychain_spks .into_iter() .map(|(k, s)| (k, s.into_iter())) @@ -217,9 +204,8 @@ impl ElectrumExt for Client { let update = loop { let (tip, _) = construct_update_tip(self, prev_tip.clone())?; - let mut update = ElectrumUpdate::::new(tip.clone()); - let cps = update - .new_tip + let mut graph_update = IncompleteTxGraph::::default(); + let cps = tip .iter() .take(10) .map(|cp| (cp.height(), cp)) @@ -230,7 +216,7 @@ impl ElectrumExt for Client { scanned_spks.append(&mut populate_with_spks( self, &cps, - &mut update, + &mut graph_update, &mut scanned_spks .iter() .map(|(i, (spk, _))| (i.clone(), spk.clone())), @@ -243,7 +229,7 @@ impl ElectrumExt for Client { populate_with_spks( self, &cps, - &mut update, + &mut graph_update, keychain_spks, stop_gap, batch_size, @@ -254,10 +240,14 @@ impl ElectrumExt for Client { } } - populate_with_txids(self, &cps, &mut update, &mut txids.iter().cloned())?; + populate_with_txids(self, &cps, &mut graph_update, &mut txids.iter().cloned())?; - let _txs = - populate_with_outpoints(self, &cps, &mut update, &mut outpoints.iter().cloned())?; + let _txs = populate_with_outpoints( + self, + &cps, + &mut graph_update, + &mut outpoints.iter().cloned(), + )?; // check for reorgs during scan process let server_blockhash = self.block_header(tip.height() as usize)?.block_hash(); @@ -265,7 +255,12 @@ impl ElectrumExt for Client { continue; // reorg } - update.keychain_update = request_spks + let chain_update = local_chain::Update { + tip, + introduce_older_blocks: true, + }; + + let keychain_update = request_spks .into_keys() .filter_map(|k| { scanned_spks @@ -275,7 +270,8 @@ impl ElectrumExt for Client { .map(|((_, i), _)| (k, *i)) }) .collect::>(); - break update; + + break (chain_update, graph_update, keychain_update); }; Ok(update) @@ -399,10 +395,10 @@ fn determine_tx_anchor( } } -fn populate_with_outpoints( +fn populate_with_outpoints( client: &Client, cps: &BTreeMap, - update: &mut ElectrumUpdate, + graph_update: &mut IncompleteTxGraph, outpoints: &mut impl Iterator, ) -> Result, Error> { let mut full_txs = HashMap::new(); @@ -451,7 +447,7 @@ fn populate_with_outpoints( }; let anchor = determine_tx_anchor(cps, res.height, res.tx_hash); - let tx_entry = update.graph_update.entry(res.tx_hash).or_default(); + let tx_entry = graph_update.0.entry(res.tx_hash).or_default(); if let Some(anchor) = anchor { tx_entry.insert(anchor); } @@ -460,10 +456,10 @@ fn populate_with_outpoints( Ok(full_txs) } -fn populate_with_txids( +fn populate_with_txids( client: &Client, cps: &BTreeMap, - update: &mut ElectrumUpdate, + graph_update: &mut IncompleteTxGraph, txids: &mut impl Iterator, ) -> Result<(), Error> { for txid in txids { @@ -488,7 +484,7 @@ fn populate_with_txids( None => continue, }; - let tx_entry = update.graph_update.entry(txid).or_default(); + let tx_entry = graph_update.0.entry(txid).or_default(); if let Some(anchor) = anchor { tx_entry.insert(anchor); } @@ -496,10 +492,10 @@ fn populate_with_txids( Ok(()) } -fn populate_with_spks( +fn populate_with_spks( client: &Client, cps: &BTreeMap, - update: &mut ElectrumUpdate, + graph_update: &mut IncompleteTxGraph, spks: &mut impl Iterator, stop_gap: usize, batch_size: usize, @@ -532,7 +528,7 @@ fn populate_with_spks( } for tx in spk_history { - let tx_entry = update.graph_update.entry(tx.tx_hash).or_default(); + let tx_entry = graph_update.0.entry(tx.tx_hash).or_default(); if let Some(anchor) = determine_tx_anchor(cps, tx.height, tx.tx_hash) { tx_entry.insert(anchor); } diff --git a/crates/electrum/src/lib.rs b/crates/electrum/src/lib.rs index 716c4d3f..09772626 100644 --- a/crates/electrum/src/lib.rs +++ b/crates/electrum/src/lib.rs @@ -1,14 +1,16 @@ //! This crate is used for updating structures of the [`bdk_chain`] crate with data from electrum. //! //! The star of the show is the [`ElectrumExt::scan`] method, which scans for relevant blockchain -//! data (via electrum) and outputs an [`ElectrumUpdate`]. +//! data (via electrum) and outputs updates for [`bdk_chain`] structures as a tuple of form: //! -//! An [`ElectrumUpdate`] only includes `txid`s and no full transactions. The caller is responsible -//! for obtaining full transactions before applying. This can be done with +//! ([`bdk_chain::local_chain::Update`], [`IncompleteTxGraph`], `keychain_update`) +//! +//! An [`IncompleteTxGraph`] 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. The method [`missing_full_txs`] of -//! [`ElectrumUpdate`] can be used. +//! [`IncompleteTxGraph`] can be used. //! //! 2. Obtaining the full transactions. To do this via electrum, the method //! [`batch_transaction_get`] can be used. @@ -16,7 +18,7 @@ //! Refer to [`bdk_electrum_example`] for a complete example. //! //! [`ElectrumClient::scan`]: electrum_client::ElectrumClient::scan -//! [`missing_full_txs`]: ElectrumUpdate::missing_full_txs +//! [`missing_full_txs`]: IncompleteTxGraph::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 diff --git a/example-crates/example_electrum/src/main.rs b/example-crates/example_electrum/src/main.rs index f8bb10b1..84501358 100644 --- a/example-crates/example_electrum/src/main.rs +++ b/example-crates/example_electrum/src/main.rs @@ -13,7 +13,7 @@ use bdk_chain::{ }; use bdk_electrum::{ electrum_client::{self, ElectrumApi}, - ElectrumExt, ElectrumUpdate, + ElectrumExt, }; use example_cli::{ anyhow::{self, Context}, @@ -251,20 +251,18 @@ fn main() -> anyhow::Result<()> { // drop lock on graph and chain drop((graph, chain)); - let update = client + let (chain_update, graph_update) = client .scan_without_keychain(tip, spks, txids, outpoints, scan_options.batch_size) .context("scanning the blockchain")?; - ElectrumUpdate { - graph_update: update.graph_update, - new_tip: update.new_tip, - keychain_update: BTreeMap::new(), - } + (chain_update, graph_update, BTreeMap::new()) } }; + let (chain_update, incomplete_graph_update, keychain_update) = response; + let missing_txids = { let graph = &*graph.lock().unwrap(); - response.missing_full_txs(graph.graph()) + incomplete_graph_update.missing_full_txs(graph.graph()) }; let now = std::time::UNIX_EPOCH @@ -272,17 +270,13 @@ fn main() -> anyhow::Result<()> { .expect("must get time") .as_secs(); - let (graph_update, keychain_update, update_tip) = - response.finalize(&client, Some(now), missing_txids)?; + let graph_update = incomplete_graph_update.finalize(&client, Some(now), missing_txids)?; let db_changeset = { let mut chain = chain.lock().unwrap(); let mut graph = graph.lock().unwrap(); - let chain = chain.apply_update(local_chain::Update { - tip: update_tip, - introduce_older_blocks: true, - })?; + let chain = chain.apply_update(chain_update)?; let indexed_tx_graph = { let mut changeset = diff --git a/example-crates/wallet_electrum/src/main.rs b/example-crates/wallet_electrum/src/main.rs index 0ea7df48..f723d665 100644 --- a/example-crates/wallet_electrum/src/main.rs +++ b/example-crates/wallet_electrum/src/main.rs @@ -7,9 +7,9 @@ use std::io::Write; use std::str::FromStr; use bdk::bitcoin::Address; +use bdk::wallet::WalletUpdate; use bdk::SignOptions; -use bdk::{bitcoin::Network, wallet::WalletUpdate, Wallet}; -use bdk_electrum::bdk_chain::local_chain; +use bdk::{bitcoin::Network, Wallet}; use bdk_electrum::electrum_client::{self, ElectrumApi}; use bdk_electrum::ElectrumExt; use bdk_file_store::Store; @@ -53,21 +53,19 @@ fn main() -> Result<(), Box> { }) .collect(); - let electrum_update = client.scan(prev_tip, keychain_spks, None, None, STOP_GAP, BATCH_SIZE)?; + let (chain_update, incomplete_graph_update, keychain_update) = + client.scan(prev_tip, keychain_spks, None, None, STOP_GAP, BATCH_SIZE)?; println!(); - let missing = electrum_update.missing_full_txs(wallet.as_ref()); - let (graph_update, keychain_update, update_tip) = - electrum_update.finalize_as_confirmation_time(&client, None, missing)?; + let missing = incomplete_graph_update.missing_full_txs(wallet.as_ref()); + let graph_update = + incomplete_graph_update.finalize_with_confirmation_time(&client, None, missing)?; let wallet_update = WalletUpdate { last_active_indices: keychain_update, graph: graph_update, - chain: Some(local_chain::Update { - tip: update_tip, - introduce_older_blocks: true, - }), + chain: Some(chain_update), }; wallet.apply_update(wallet_update)?; wallet.commit()?; From c56728ff1315e0deaf256af07fd1ff5e18fced8a Mon Sep 17 00:00:00 2001 From: Vladimir Fomene Date: Fri, 25 Aug 2023 12:52:09 +0300 Subject: [PATCH 08/11] refactor: Remove `scan` and `scan_txout` from SpkTxoutIndex and KeychainTxoutIndex --- crates/chain/src/indexed_tx_graph.rs | 13 +++- crates/chain/src/keychain/txout_index.rs | 47 ++++--------- crates/chain/src/spk_txout_index.rs | 66 +++++++------------ .../chain/tests/test_keychain_txout_index.rs | 7 +- crates/chain/tests/test_spk_txout_index.rs | 6 +- 5 files changed, 55 insertions(+), 84 deletions(-) diff --git a/crates/chain/src/indexed_tx_graph.rs b/crates/chain/src/indexed_tx_graph.rs index 6dc2e994..4643a93e 100644 --- a/crates/chain/src/indexed_tx_graph.rs +++ b/crates/chain/src/indexed_tx_graph.rs @@ -233,7 +233,18 @@ pub trait Indexer { /// Scan and index the given `outpoint` and `txout`. fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet; - /// Scan and index the given transaction. + /// Scans a transaction for relevant outpoints, which are stored and indexed internally. + /// + /// If the matched script pubkey is part of the lookahead, the last stored index is updated for + /// the script pubkey's keychain and the [`ChangeSet`] returned will reflect the + /// change. + /// + /// Typically, this method is used in two situations: + /// + /// 1. After loading transaction data from the disk, you may scan over all the txouts to restore all + /// your txouts. + /// 2. When getting new data from the chain, you usually scan it before incorporating it into + /// your chain state. fn index_tx(&mut self, tx: &Transaction) -> Self::ChangeSet; /// Apply changeset to itself. diff --git a/crates/chain/src/keychain/txout_index.rs b/crates/chain/src/keychain/txout_index.rs index 0376473f..5996d4d4 100644 --- a/crates/chain/src/keychain/txout_index.rs +++ b/crates/chain/src/keychain/txout_index.rs @@ -91,11 +91,19 @@ impl Indexer for KeychainTxOutIndex { type ChangeSet = super::ChangeSet; fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet { - self.scan_txout(outpoint, txout) + let mut changeset = super::ChangeSet::::default(); + for (keychain, index) in self.inner.index_txout(outpoint, txout) { + changeset.append(self.reveal_to_target(&keychain, index).1); + } + changeset } fn index_tx(&mut self, tx: &bitcoin::Transaction) -> Self::ChangeSet { - self.scan(tx) + let mut changeset = super::ChangeSet::::default(); + for (op, txout) in tx.output.iter().enumerate() { + changeset.append(self.index_txout(OutPoint::new(tx.txid(), op as u32), txout)); + } + changeset } fn initial_changeset(&self) -> Self::ChangeSet { @@ -112,36 +120,6 @@ impl Indexer for KeychainTxOutIndex { } impl KeychainTxOutIndex { - /// Scans a transaction for relevant outpoints, which are stored and indexed internally. - /// - /// If the matched script pubkey is part of the lookahead, the last stored index is updated for - /// the script pubkey's keychain and the [`super::ChangeSet`] returned will reflect the - /// change. - /// - /// Typically, this method is used in two situations: - /// - /// 1. After loading transaction data from the disk, you may scan over all the txouts to restore all - /// your txouts. - /// 2. When getting new data from the chain, you usually scan it before incorporating it into - /// your chain state (i.e., `SparseChain`, `ChainGraph`). - pub fn scan(&mut self, tx: &bitcoin::Transaction) -> super::ChangeSet { - let mut changeset = super::ChangeSet::::default(); - for (op, txout) in tx.output.iter().enumerate() { - changeset.append(self.scan_txout(OutPoint::new(tx.txid(), op as u32), txout)); - } - changeset - } - - /// Scan a single outpoint for a matching script pubkey. - /// - /// If it matches, this will store and index it. - pub fn scan_txout(&mut self, op: OutPoint, txout: &TxOut) -> super::ChangeSet { - match self.inner.scan_txout(op, txout).cloned() { - Some((keychain, index)) => self.reveal_to_target(&keychain, index).1, - None => super::ChangeSet::default(), - } - } - /// Return a reference to the internal [`SpkTxOutIndex`]. pub fn inner(&self) -> &SpkTxOutIndex<(K, u32)> { &self.inner @@ -198,14 +176,11 @@ impl KeychainTxOutIndex { /// Set the lookahead count for `keychain`. /// /// The lookahead is the number of scripts to cache ahead of the last stored script index. This - /// is useful during a scan via [`scan`] or [`scan_txout`]. + /// is useful during a scan via [`Indexer::index_tx`] or [`Indexer::index_txout`]. /// /// # Panics /// /// This will panic if the `keychain` does not exist. - /// - /// [`scan`]: Self::scan - /// [`scan_txout`]: Self::scan_txout pub fn set_lookahead(&mut self, keychain: &K, lookahead: u32) { self.lookahead.insert(keychain.clone(), lookahead); self.replenish_lookahead(keychain); diff --git a/crates/chain/src/spk_txout_index.rs b/crates/chain/src/spk_txout_index.rs index a4d6d7c2..8bac7413 100644 --- a/crates/chain/src/spk_txout_index.rs +++ b/crates/chain/src/spk_txout_index.rs @@ -9,8 +9,9 @@ use bitcoin::{self, OutPoint, Script, ScriptBuf, Transaction, TxOut, Txid}; /// An index storing [`TxOut`]s that have a script pubkey that matches those in a list. /// /// The basic idea is that you insert script pubkeys you care about into the index with -/// [`insert_spk`] and then when you call [`scan`], the index will look at any txouts you pass in and -/// store and index any txouts matching one of its script pubkeys. +/// [`insert_spk`] and then when you call [`Indexer::index_tx`] or [`Indexer::index_txout`], the +/// index will look at any txouts you pass in and store and index any txouts matching one of its +/// script pubkeys. /// /// Each script pubkey is associated with an application-defined index script index `I`, which must be /// [`Ord`]. Usually, this is used to associate the derivation index of the script pubkey or even a @@ -24,7 +25,6 @@ use bitcoin::{self, OutPoint, Script, ScriptBuf, Transaction, TxOut, Txid}; /// [`TxOut`]: bitcoin::TxOut /// [`insert_spk`]: Self::insert_spk /// [`Ord`]: core::cmp::Ord -/// [`scan`]: Self::scan /// [`TxGraph`]: crate::tx_graph::TxGraph #[derive(Clone, Debug)] pub struct SpkTxOutIndex { @@ -53,19 +53,35 @@ impl Default for SpkTxOutIndex { } impl Indexer for SpkTxOutIndex { - type ChangeSet = (); + type ChangeSet = BTreeSet; fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet { - self.scan_txout(outpoint, txout); - Default::default() + let spk_i = self.spk_indices.get(&txout.script_pubkey); + let mut scanned_indices = BTreeSet::new(); + if let Some(spk_i) = spk_i { + self.txouts.insert(outpoint, (spk_i.clone(), txout.clone())); + self.spk_txouts.insert((spk_i.clone(), outpoint)); + self.unused.remove(spk_i); + scanned_indices.insert(spk_i.clone()); + } + scanned_indices } fn index_tx(&mut self, tx: &Transaction) -> Self::ChangeSet { - self.scan(tx); - Default::default() + let mut scanned_indices = BTreeSet::new(); + + for (i, txout) in tx.output.iter().enumerate() { + let op = OutPoint::new(tx.txid(), i as u32); + let mut txout_indices = self.index_txout(op, txout); + scanned_indices.append(&mut txout_indices); + } + + scanned_indices } - fn initial_changeset(&self) -> Self::ChangeSet {} + fn initial_changeset(&self) -> Self::ChangeSet { + self.spks.keys().cloned().collect() + } fn apply_changeset(&mut self, _changeset: Self::ChangeSet) { // This applies nothing. @@ -77,38 +93,6 @@ impl Indexer for SpkTxOutIndex { } impl SpkTxOutIndex { - /// Scans a transaction containing many txouts. - /// - /// Typically, this is used in two situations: - /// - /// 1. After loading transaction data from the disk, you may scan over all the txouts to restore all - /// your txouts. - /// 2. When getting new data from the chain, you usually scan it before incorporating it into your chain state. - pub fn scan(&mut self, tx: &bitcoin::Transaction) -> BTreeSet { - let mut scanned_indices = BTreeSet::new(); - - for (i, txout) in tx.output.iter().enumerate() { - let op = OutPoint::new(tx.txid(), i as u32); - if let Some(spk_i) = self.scan_txout(op, txout) { - scanned_indices.insert(spk_i.clone()); - } - } - - scanned_indices - } - - /// Scan a single `TxOut` for a matching script pubkey and returns the index that matches the - /// script pubkey (if any). - pub fn scan_txout(&mut self, op: OutPoint, txout: &TxOut) -> Option<&I> { - let spk_i = self.spk_indices.get(&txout.script_pubkey); - if let Some(spk_i) = spk_i { - self.txouts.insert(op, (spk_i.clone(), txout.clone())); - self.spk_txouts.insert((spk_i.clone(), op)); - self.unused.remove(spk_i); - } - spk_i - } - /// Get a reference to the set of indexed outpoints. pub fn outpoints(&self) -> &BTreeSet<(I, OutPoint)> { &self.spk_txouts diff --git a/crates/chain/tests/test_keychain_txout_index.rs b/crates/chain/tests/test_keychain_txout_index.rs index 817b0349..c4f43471 100644 --- a/crates/chain/tests/test_keychain_txout_index.rs +++ b/crates/chain/tests/test_keychain_txout_index.rs @@ -4,6 +4,7 @@ mod common; use bdk_chain::{ collections::BTreeMap, + indexed_tx_graph::Indexer, keychain::{self, KeychainTxOutIndex}, Append, }; @@ -194,7 +195,7 @@ fn test_lookahead() { ], ..common::new_tx(external_index) }; - assert_eq!(txout_index.scan(&tx), keychain::ChangeSet::default()); + assert_eq!(txout_index.index_tx(&tx), keychain::ChangeSet::default()); assert_eq!( txout_index.last_revealed_index(&TestKeychain::External), Some(last_external_index) @@ -248,7 +249,7 @@ fn test_scan_with_lookahead() { value: 0, }; - let changeset = txout_index.scan_txout(op, &txout); + let changeset = txout_index.index_txout(op, &txout); assert_eq!( changeset.as_inner(), &[(TestKeychain::External, spk_i)].into() @@ -273,7 +274,7 @@ fn test_scan_with_lookahead() { script_pubkey: spk_41, value: 0, }; - let changeset = txout_index.scan_txout(op, &txout); + let changeset = txout_index.index_txout(op, &txout); assert!(changeset.is_empty()); } diff --git a/crates/chain/tests/test_spk_txout_index.rs b/crates/chain/tests/test_spk_txout_index.rs index 099b4ca8..e8b75214 100644 --- a/crates/chain/tests/test_spk_txout_index.rs +++ b/crates/chain/tests/test_spk_txout_index.rs @@ -1,4 +1,4 @@ -use bdk_chain::SpkTxOutIndex; +use bdk_chain::{indexed_tx_graph::Indexer, SpkTxOutIndex}; use bitcoin::{absolute, OutPoint, ScriptBuf, Transaction, TxIn, TxOut}; #[test] @@ -22,7 +22,7 @@ fn spk_txout_sent_and_received() { assert_eq!(index.sent_and_received(&tx1), (0, 42_000)); assert_eq!(index.net_value(&tx1), 42_000); - index.scan(&tx1); + index.index_tx(&tx1); assert_eq!( index.sent_and_received(&tx1), (0, 42_000), @@ -82,7 +82,7 @@ fn mark_used() { }], }; - spk_index.scan(&tx1); + spk_index.index_tx(&tx1); spk_index.unmark_used(&1); assert!( spk_index.is_used(&1), From 41042069809e3eeb4a8cc8a5a8db1af2c57c4a11 Mon Sep 17 00:00:00 2001 From: LLFourn Date: Tue, 5 Sep 2023 07:53:50 +0800 Subject: [PATCH 09/11] feat: impl Append for lots of tuples --- crates/chain/src/tx_data_traits.rs | 39 ++++++++++++++++++------------ 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/crates/chain/src/tx_data_traits.rs b/crates/chain/src/tx_data_traits.rs index f28044c6..274bae36 100644 --- a/crates/chain/src/tx_data_traits.rs +++ b/crates/chain/src/tx_data_traits.rs @@ -91,14 +91,6 @@ pub trait Append { fn is_empty(&self) -> bool; } -impl Append for () { - fn append(&mut self, _other: Self) {} - - fn is_empty(&self) -> bool { - true - } -} - impl Append for BTreeMap { fn append(&mut self, mut other: Self) { BTreeMap::append(self, &mut other) @@ -129,13 +121,30 @@ impl Append for Vec { } } -impl Append for (A, B) { - fn append(&mut self, other: Self) { - Append::append(&mut self.0, other.0); - Append::append(&mut self.1, other.1); - } +macro_rules! impl_append_for_tuple { + ($($a:ident $b:tt)*) => { + impl<$($a),*> Append for ($($a,)*) where $($a: Append),* { - fn is_empty(&self) -> bool { - Append::is_empty(&self.0) && Append::is_empty(&self.1) + fn append(&mut self, _other: Self) { + $(Append::append(&mut self.$b, _other.$b) );* + } + + fn is_empty(&self) -> bool { + $(Append::is_empty(&self.$b) && )* true + } + } } } + +impl_append_for_tuple!(); +impl_append_for_tuple!(T0 0); +impl_append_for_tuple!(T0 0 T1 1); +impl_append_for_tuple!(T0 0 T1 1 T2 2); +impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3); +impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4); +impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5); +impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6); +impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6 T7 7); +impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6 T7 7 T8 8); +impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6 T7 7 T8 8 T9 9); +impl_append_for_tuple!(T0 0 T1 1 T2 2 T3 3 T4 4 T5 5 T6 6 T7 7 T8 8 T9 9 T10 10); From d43ae0231fa4670b98780cad84466c14ae087292 Mon Sep 17 00:00:00 2001 From: Vladimir Fomene Date: Wed, 6 Sep 2023 09:47:45 +0300 Subject: [PATCH 10/11] refactor: improve docs, cleanup unnecessary types and improve code --- crates/bdk/src/wallet/mod.rs | 79 ++++++--------- crates/chain/src/indexed_tx_graph.rs | 24 +++-- crates/chain/src/keychain/txout_index.rs | 13 +-- crates/chain/src/lib.rs | 8 -- crates/chain/src/spk_txout_index.rs | 60 +++++++----- crates/electrum/src/electrum_ext.rs | 96 ++++++++++--------- crates/electrum/src/lib.rs | 8 +- example-crates/example_electrum/src/main.rs | 24 +++-- example-crates/wallet_electrum/src/main.rs | 24 +++-- .../wallet_esplora_async/src/main.rs | 4 +- .../wallet_esplora_blocking/src/main.rs | 4 +- 11 files changed, 175 insertions(+), 169 deletions(-) diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index a7fe4d42..5a3918cf 100644 --- a/crates/bdk/src/wallet/mod.rs +++ b/crates/bdk/src/wallet/mod.rs @@ -25,7 +25,7 @@ use bdk_chain::{ keychain::{self, KeychainTxOutIndex}, local_chain::{self, CannotConnectError, CheckPoint, CheckPointIter, LocalChain}, tx_graph::{CanonicalTx, TxGraph}, - Anchor, Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeAnchor, FullTxOut, + Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeAnchor, FullTxOut, IndexedTxGraph, Persist, PersistBackend, }; use bitcoin::consensus::encode::serialize; @@ -95,73 +95,51 @@ pub struct Wallet { secp: SecpCtx, } -/// A structure to update [`Wallet`]. +/// An update to [`Wallet`]. /// /// It updates [`bdk_chain::keychain::KeychainTxOutIndex`], [`bdk_chain::TxGraph`] and [`local_chain::LocalChain`] atomically. -#[derive(Debug, Clone)] -pub struct WalletUpdate { +#[derive(Debug, Clone, Default)] +pub struct Update { /// Contains the last active derivation indices per keychain (`K`), which is used to update the /// [`KeychainTxOutIndex`]. - pub last_active_indices: BTreeMap, + pub last_active_indices: BTreeMap, - /// Update for the [`TxGraph`]. - pub graph: TxGraph, + /// Update for the wallet's internal [`TxGraph`]. + pub graph: TxGraph, - /// Update for the [`LocalChain`]. + /// Update for the wallet's internal [`LocalChain`]. /// /// [`LocalChain`]: local_chain::LocalChain pub chain: Option, } -impl Default for WalletUpdate { - fn default() -> Self { - Self { - last_active_indices: BTreeMap::new(), - graph: TxGraph::default(), - chain: None, - } - } -} - -/// A structure that records the corresponding changes as result of applying an [`WalletUpdate`]. -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct WalletChangeSet { +/// The changes made to a wallet by applying an [`Update`]. +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, Default)] +pub struct ChangeSet { /// Changes to the [`LocalChain`]. /// /// [`LocalChain`]: local_chain::LocalChain pub chain: local_chain::ChangeSet, - /// ChangeSet to [`IndexedTxGraph`]. + /// Changes to [`IndexedTxGraph`]. /// /// [`IndexedTxGraph`]: bdk_chain::indexed_tx_graph::IndexedTxGraph - #[serde(bound( - deserialize = "K: Ord + serde::Deserialize<'de>, A: Ord + serde::Deserialize<'de>", - serialize = "K: Ord + serde::Serialize, A: Ord + serde::Serialize", - ))] - pub index_tx_graph: indexed_tx_graph::ChangeSet>, + pub indexed_tx_graph: + indexed_tx_graph::ChangeSet>, } -impl Default for WalletChangeSet { - fn default() -> Self { - Self { - chain: Default::default(), - index_tx_graph: Default::default(), - } - } -} - -impl Append for WalletChangeSet { +impl Append for ChangeSet { fn append(&mut self, other: Self) { Append::append(&mut self.chain, other.chain); - Append::append(&mut self.index_tx_graph, other.index_tx_graph); + Append::append(&mut self.indexed_tx_graph, other.indexed_tx_graph); } fn is_empty(&self) -> bool { - self.chain.is_empty() && self.index_tx_graph.is_empty() + self.chain.is_empty() && self.indexed_tx_graph.is_empty() } } -impl From for WalletChangeSet { +impl From for ChangeSet { fn from(chain: local_chain::ChangeSet) -> Self { Self { chain, @@ -170,21 +148,22 @@ impl From for WalletChangeSet { } } -impl From>> for WalletChangeSet { - fn from(index_tx_graph: indexed_tx_graph::ChangeSet>) -> Self { +impl From>> + for ChangeSet +{ + fn from( + indexed_tx_graph: indexed_tx_graph::ChangeSet< + ConfirmationTimeAnchor, + keychain::ChangeSet, + >, + ) -> Self { Self { - index_tx_graph, + indexed_tx_graph, ..Default::default() } } } -/// The update to a [`Wallet`] used in [`Wallet::apply_update`]. This is usually returned from blockchain data sources. -pub type Update = WalletUpdate; - -/// The changeset produced internally by [`Wallet`] when mutated. -pub type ChangeSet = WalletChangeSet; - /// The address index selection strategy to use to derived an address from the wallet's external /// descriptor. See [`Wallet::get_address`]. If you're unsure which one to use use `WalletIndex::New`. #[derive(Debug)] @@ -332,7 +311,7 @@ impl Wallet { let changeset = db.load_from_persistence().map_err(NewError::Persist)?; chain.apply_changeset(&changeset.chain); - indexed_graph.apply_changeset(changeset.index_tx_graph); + indexed_graph.apply_changeset(changeset.indexed_tx_graph); let persist = Persist::new(db); diff --git a/crates/chain/src/indexed_tx_graph.rs b/crates/chain/src/indexed_tx_graph.rs index 4643a93e..c9091eef 100644 --- a/crates/chain/src/indexed_tx_graph.rs +++ b/crates/chain/src/indexed_tx_graph.rs @@ -6,7 +6,7 @@ use alloc::vec::Vec; use bitcoin::{OutPoint, Transaction, TxOut}; use crate::{ - keychain, + keychain, local_chain, tx_graph::{self, TxGraph}, Anchor, Append, }; @@ -225,7 +225,16 @@ impl From> for ChangeSet> } } -/// Represents a structure that can index transaction data. +impl From> for (local_chain::ChangeSet, ChangeSet) { + fn from(indexed_changeset: ChangeSet) -> Self { + (local_chain::ChangeSet::default(), indexed_changeset) + } +} + +/// Utilities for indexing transaction data. +/// +/// Types which implement this trait can be used to construct an [`IndexedTxGraph`]. +/// This trait's methods should rarely be called directly. pub trait Indexer { /// The resultant "changeset" when new transaction data is indexed. type ChangeSet; @@ -234,17 +243,6 @@ pub trait Indexer { fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet; /// Scans a transaction for relevant outpoints, which are stored and indexed internally. - /// - /// If the matched script pubkey is part of the lookahead, the last stored index is updated for - /// the script pubkey's keychain and the [`ChangeSet`] returned will reflect the - /// change. - /// - /// Typically, this method is used in two situations: - /// - /// 1. After loading transaction data from the disk, you may scan over all the txouts to restore all - /// your txouts. - /// 2. When getting new data from the chain, you usually scan it before incorporating it into - /// your chain state. fn index_tx(&mut self, tx: &Transaction) -> Self::ChangeSet; /// Apply changeset to itself. diff --git a/crates/chain/src/keychain/txout_index.rs b/crates/chain/src/keychain/txout_index.rs index 5996d4d4..3d642edd 100644 --- a/crates/chain/src/keychain/txout_index.rs +++ b/crates/chain/src/keychain/txout_index.rs @@ -91,11 +91,10 @@ impl Indexer for KeychainTxOutIndex { type ChangeSet = super::ChangeSet; fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet { - let mut changeset = super::ChangeSet::::default(); - for (keychain, index) in self.inner.index_txout(outpoint, txout) { - changeset.append(self.reveal_to_target(&keychain, index).1); + match self.inner.scan_txout(outpoint, txout).cloned() { + Some((keychain, index)) => self.reveal_to_target(&keychain, index).1, + None => super::ChangeSet::default(), } - changeset } fn index_tx(&mut self, tx: &bitcoin::Transaction) -> Self::ChangeSet { @@ -175,8 +174,10 @@ impl KeychainTxOutIndex { /// Set the lookahead count for `keychain`. /// - /// The lookahead is the number of scripts to cache ahead of the last stored script index. This - /// is useful during a scan via [`Indexer::index_tx`] or [`Indexer::index_txout`]. + /// The lookahead is the number of scripts to cache ahead of the last revealed script index. This + /// is useful to find outputs you own when processing block data that lie beyond the last revealed + /// index. In certain situations, such as when performing an initial scan of the blockchain during + /// wallet import, it may be uncertain or unknown what the last revealed index is. /// /// # Panics /// diff --git a/crates/chain/src/lib.rs b/crates/chain/src/lib.rs index f38b7ee5..ed167ebf 100644 --- a/crates/chain/src/lib.rs +++ b/crates/chain/src/lib.rs @@ -100,11 +100,3 @@ pub mod collections { /// How many confirmations are needed f or a coinbase output to be spent. pub const COINBASE_MATURITY: u32 = 100; - -impl From> - for (local_chain::ChangeSet, indexed_tx_graph::ChangeSet) -{ - fn from(indexed_changeset: indexed_tx_graph::ChangeSet) -> Self { - (local_chain::ChangeSet::default(), indexed_changeset) - } -} diff --git a/crates/chain/src/spk_txout_index.rs b/crates/chain/src/spk_txout_index.rs index 8bac7413..4047f8fc 100644 --- a/crates/chain/src/spk_txout_index.rs +++ b/crates/chain/src/spk_txout_index.rs @@ -53,35 +53,19 @@ impl Default for SpkTxOutIndex { } impl Indexer for SpkTxOutIndex { - type ChangeSet = BTreeSet; + type ChangeSet = (); fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet { - let spk_i = self.spk_indices.get(&txout.script_pubkey); - let mut scanned_indices = BTreeSet::new(); - if let Some(spk_i) = spk_i { - self.txouts.insert(outpoint, (spk_i.clone(), txout.clone())); - self.spk_txouts.insert((spk_i.clone(), outpoint)); - self.unused.remove(spk_i); - scanned_indices.insert(spk_i.clone()); - } - scanned_indices + self.scan_txout(outpoint, txout); + Default::default() } fn index_tx(&mut self, tx: &Transaction) -> Self::ChangeSet { - let mut scanned_indices = BTreeSet::new(); - - for (i, txout) in tx.output.iter().enumerate() { - let op = OutPoint::new(tx.txid(), i as u32); - let mut txout_indices = self.index_txout(op, txout); - scanned_indices.append(&mut txout_indices); - } - - scanned_indices + self.scan(tx); + Default::default() } - fn initial_changeset(&self) -> Self::ChangeSet { - self.spks.keys().cloned().collect() - } + fn initial_changeset(&self) -> Self::ChangeSet {} fn apply_changeset(&mut self, _changeset: Self::ChangeSet) { // This applies nothing. @@ -93,6 +77,38 @@ impl Indexer for SpkTxOutIndex { } impl SpkTxOutIndex { + /// Scans a transaction's outputs for matching script pubkeys. + /// + /// Typically, this is used in two situations: + /// + /// 1. After loading transaction data from the disk, you may scan over all the txouts to restore all + /// your txouts. + /// 2. When getting new data from the chain, you usually scan it before incorporating it into your chain state. + pub fn scan(&mut self, tx: &Transaction) -> BTreeSet { + let mut scanned_indices = BTreeSet::new(); + let txid = tx.txid(); + for (i, txout) in tx.output.iter().enumerate() { + let op = OutPoint::new(txid, i as u32); + if let Some(spk_i) = self.scan_txout(op, txout) { + scanned_indices.insert(spk_i.clone()); + } + } + + scanned_indices + } + + /// Scan a single `TxOut` for a matching script pubkey and returns the index that matches the + /// script pubkey (if any). + pub fn scan_txout(&mut self, op: OutPoint, txout: &TxOut) -> Option<&I> { + let spk_i = self.spk_indices.get(&txout.script_pubkey); + if let Some(spk_i) = spk_i { + self.txouts.insert(op, (spk_i.clone(), txout.clone())); + self.spk_txouts.insert((spk_i.clone(), op)); + self.unused.remove(spk_i); + } + spk_i + } + /// Get a reference to the set of indexed outpoints. pub fn outpoints(&self) -> &BTreeSet<(I, OutPoint)> { &self.spk_txouts diff --git a/crates/electrum/src/electrum_ext.rs b/crates/electrum/src/electrum_ext.rs index b7435862..7ac16a04 100644 --- a/crates/electrum/src/electrum_ext.rs +++ b/crates/electrum/src/electrum_ext.rs @@ -14,19 +14,19 @@ use std::{ /// We assume that a block of this depth and deeper cannot be reorged. const ASSUME_FINAL_DEPTH: u32 = 8; -/// Represents a [`TxGraph`] update fetched from an Electrum server, but excludes full transactions. +/// Represents updates fetched from an Electrum server, but excludes full transactions. /// /// To provide a complete update to [`TxGraph`], you'll need to call [`Self::missing_full_txs`] to -/// determine the full transactions missing from [`TxGraph`]. Then call [`Self::finalize`] to +/// determine the full transactions missing from [`TxGraph`]. Then call [`Self::into_tx_graph`] to /// fetch the full transactions from Electrum and finalize the update. #[derive(Debug, Default, Clone)] -pub struct IncompleteTxGraph(HashMap>); +pub struct RelevantTxids(HashMap>); -impl IncompleteTxGraph { +impl RelevantTxids { /// Determine the full transactions that are missing from `graph`. /// - /// Refer to [`IncompleteTxGraph`] for more. - pub fn missing_full_txs(&self, graph: &TxGraph) -> Vec { + /// Refer to [`RelevantTxids`] for more details. + pub fn missing_full_txs(&self, graph: &TxGraph) -> Vec { self.0 .keys() .filter(move |&&txid| graph.as_ref().get_tx(txid).is_none()) @@ -36,15 +36,15 @@ impl IncompleteTxGraph { /// Finalizes the [`TxGraph`] update by fetching `missing` txids from the `client`. /// - /// Refer to [`IncompleteTxGraph`] for more. - pub fn finalize( + /// Refer to [`RelevantTxids`] for more details. + pub fn into_tx_graph( self, client: &Client, seen_at: Option, missing: Vec, - ) -> Result, Error> { + ) -> Result, Error> { let new_txs = client.batch_transaction_get(&missing)?; - let mut graph = TxGraph::::new(new_txs); + let mut graph = TxGraph::::new(new_txs); for (txid, anchors) in self.0 { if let Some(seen_at) = seen_at { let _ = graph.insert_seen_at(txid, seen_at); @@ -55,22 +55,20 @@ impl IncompleteTxGraph { } Ok(graph) } -} -impl IncompleteTxGraph { - /// Finalizes the [`IncompleteTxGraph`] with `new_txs` and anchors of type + /// Finalizes [`RelevantTxids`] with `new_txs` and anchors of type /// [`ConfirmationTimeAnchor`]. /// /// **Note:** The confirmation time might not be precisely correct if there has been a reorg. /// Electrum's API intends that we use the merkle proof API, we should change `bdk_electrum` to /// use it. - pub fn finalize_with_confirmation_time( + pub fn into_confirmation_time_tx_graph( self, client: &Client, seen_at: Option, missing: Vec, ) -> Result, Error> { - let graph = self.finalize(client, seen_at, missing)?; + let graph = self.into_tx_graph(client, seen_at, missing)?; let relevant_heights = { let mut visited_heights = HashSet::new(); @@ -122,8 +120,20 @@ impl IncompleteTxGraph { } } +/// Combination of chain and transactions updates from electrum +/// +/// We have to update the chain and the txids at the same time since we anchor the txids to +/// the same chain tip that we check before and after we gather the txids. +#[derive(Debug)] +pub struct ElectrumUpdate { + /// Chain update + pub chain_update: local_chain::Update, + /// Transaction updates from electrum + pub relevant_txids: RelevantTxids, +} + /// Trait to extend [`Client`] functionality. -pub trait ElectrumExt { +pub trait ElectrumExt { /// Scan the blockchain (via electrum) for the data specified and returns updates for /// [`bdk_chain`] data structures. /// @@ -136,7 +146,6 @@ pub trait ElectrumExt { /// The 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. - #[allow(clippy::type_complexity)] fn scan( &self, prev_tip: Option, @@ -145,7 +154,7 @@ pub trait ElectrumExt { outpoints: impl IntoIterator, stop_gap: usize, batch_size: usize, - ) -> Result<(local_chain::Update, IncompleteTxGraph, BTreeMap), Error>; + ) -> Result<(ElectrumUpdate, BTreeMap), Error>; /// Convenience method to call [`scan`] without requiring a keychain. /// @@ -157,13 +166,13 @@ pub trait ElectrumExt { txids: impl IntoIterator, outpoints: impl IntoIterator, batch_size: usize, - ) -> Result<(local_chain::Update, IncompleteTxGraph), Error> { + ) -> Result { let spk_iter = misc_spks .into_iter() .enumerate() .map(|(i, spk)| (i as u32, spk)); - let (chain, graph, _) = self.scan( + let (electrum_update, _) = self.scan( prev_tip, [((), spk_iter)].into(), txids, @@ -172,11 +181,11 @@ pub trait ElectrumExt { batch_size, )?; - Ok((chain, graph)) + Ok(electrum_update) } } -impl ElectrumExt for Client { +impl ElectrumExt for Client { fn scan( &self, prev_tip: Option, @@ -185,14 +194,7 @@ impl ElectrumExt for Client { outpoints: impl IntoIterator, stop_gap: usize, batch_size: usize, - ) -> Result< - ( - local_chain::Update, - IncompleteTxGraph, - BTreeMap, - ), - Error, - > { + ) -> Result<(ElectrumUpdate, BTreeMap), Error> { let mut request_spks = keychain_spks .into_iter() .map(|(k, s)| (k, s.into_iter())) @@ -202,9 +204,9 @@ impl ElectrumExt for Client { let txids = txids.into_iter().collect::>(); let outpoints = outpoints.into_iter().collect::>(); - let update = loop { + let (electrum_update, keychain_update) = loop { let (tip, _) = construct_update_tip(self, prev_tip.clone())?; - let mut graph_update = IncompleteTxGraph::::default(); + let mut relevant_txids = RelevantTxids::default(); let cps = tip .iter() .take(10) @@ -216,7 +218,7 @@ impl ElectrumExt for Client { scanned_spks.append(&mut populate_with_spks( self, &cps, - &mut graph_update, + &mut relevant_txids, &mut scanned_spks .iter() .map(|(i, (spk, _))| (i.clone(), spk.clone())), @@ -229,7 +231,7 @@ impl ElectrumExt for Client { populate_with_spks( self, &cps, - &mut graph_update, + &mut relevant_txids, keychain_spks, stop_gap, batch_size, @@ -240,12 +242,12 @@ impl ElectrumExt for Client { } } - populate_with_txids(self, &cps, &mut graph_update, &mut txids.iter().cloned())?; + populate_with_txids(self, &cps, &mut relevant_txids, &mut txids.iter().cloned())?; let _txs = populate_with_outpoints( self, &cps, - &mut graph_update, + &mut relevant_txids, &mut outpoints.iter().cloned(), )?; @@ -271,10 +273,16 @@ impl ElectrumExt for Client { }) .collect::>(); - break (chain_update, graph_update, keychain_update); + break ( + ElectrumUpdate { + chain_update, + relevant_txids, + }, + keychain_update, + ); }; - Ok(update) + Ok((electrum_update, keychain_update)) } } @@ -398,7 +406,7 @@ fn determine_tx_anchor( fn populate_with_outpoints( client: &Client, cps: &BTreeMap, - graph_update: &mut IncompleteTxGraph, + relevant_txids: &mut RelevantTxids, outpoints: &mut impl Iterator, ) -> Result, Error> { let mut full_txs = HashMap::new(); @@ -447,7 +455,7 @@ fn populate_with_outpoints( }; let anchor = determine_tx_anchor(cps, res.height, res.tx_hash); - let tx_entry = graph_update.0.entry(res.tx_hash).or_default(); + let tx_entry = relevant_txids.0.entry(res.tx_hash).or_default(); if let Some(anchor) = anchor { tx_entry.insert(anchor); } @@ -459,7 +467,7 @@ fn populate_with_outpoints( fn populate_with_txids( client: &Client, cps: &BTreeMap, - graph_update: &mut IncompleteTxGraph, + relevant_txids: &mut RelevantTxids, txids: &mut impl Iterator, ) -> Result<(), Error> { for txid in txids { @@ -484,7 +492,7 @@ fn populate_with_txids( None => continue, }; - let tx_entry = graph_update.0.entry(txid).or_default(); + let tx_entry = relevant_txids.0.entry(txid).or_default(); if let Some(anchor) = anchor { tx_entry.insert(anchor); } @@ -495,7 +503,7 @@ fn populate_with_txids( fn populate_with_spks( client: &Client, cps: &BTreeMap, - graph_update: &mut IncompleteTxGraph, + relevant_txids: &mut RelevantTxids, spks: &mut impl Iterator, stop_gap: usize, batch_size: usize, @@ -528,7 +536,7 @@ fn populate_with_spks( } for tx in spk_history { - let tx_entry = graph_update.0.entry(tx.tx_hash).or_default(); + let tx_entry = relevant_txids.0.entry(tx.tx_hash).or_default(); if let Some(anchor) = determine_tx_anchor(cps, tx.height, tx.tx_hash) { tx_entry.insert(anchor); } diff --git a/crates/electrum/src/lib.rs b/crates/electrum/src/lib.rs index 09772626..1e480537 100644 --- a/crates/electrum/src/lib.rs +++ b/crates/electrum/src/lib.rs @@ -3,14 +3,14 @@ //! 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: //! -//! ([`bdk_chain::local_chain::Update`], [`IncompleteTxGraph`], `keychain_update`) +//! ([`bdk_chain::local_chain::Update`], [`RelevantTxids`], `keychain_update`) //! -//! An [`IncompleteTxGraph`] only includes `txid`s and no full transactions. The caller is +//! 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. The method [`missing_full_txs`] of -//! [`IncompleteTxGraph`] can be used. +//! [`RelevantTxids`] can be used. //! //! 2. Obtaining the full transactions. To do this via electrum, the method //! [`batch_transaction_get`] can be used. @@ -18,7 +18,7 @@ //! Refer to [`bdk_electrum_example`] for a complete example. //! //! [`ElectrumClient::scan`]: electrum_client::ElectrumClient::scan -//! [`missing_full_txs`]: IncompleteTxGraph::missing_full_txs +//! [`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 diff --git a/example-crates/example_electrum/src/main.rs b/example-crates/example_electrum/src/main.rs index 84501358..a05e85c5 100644 --- a/example-crates/example_electrum/src/main.rs +++ b/example-crates/example_electrum/src/main.rs @@ -13,7 +13,7 @@ use bdk_chain::{ }; use bdk_electrum::{ electrum_client::{self, ElectrumApi}, - ElectrumExt, + ElectrumExt, ElectrumUpdate, }; use example_cli::{ anyhow::{self, Context}, @@ -66,16 +66,16 @@ type ChangeSet = ( ); fn main() -> anyhow::Result<()> { - let (args, keymap, index, db, init_changeset) = + let (args, keymap, index, db, (disk_local_chain, disk_tx_graph)) = example_cli::init::(DB_MAGIC, DB_PATH)?; let graph = Mutex::new({ let mut graph = IndexedTxGraph::new(index); - graph.apply_changeset(init_changeset.1); + graph.apply_changeset(disk_tx_graph); graph }); - let chain = Mutex::new(LocalChain::from_changeset(init_changeset.0)); + let chain = Mutex::new(LocalChain::from_changeset(disk_local_chain)); let electrum_url = match args.network { Network::Bitcoin => "ssl://electrum.blockstream.info:50002", @@ -251,18 +251,24 @@ fn main() -> anyhow::Result<()> { // drop lock on graph and chain drop((graph, chain)); - let (chain_update, graph_update) = client + let electrum_update = client .scan_without_keychain(tip, spks, txids, outpoints, scan_options.batch_size) .context("scanning the blockchain")?; - (chain_update, graph_update, BTreeMap::new()) + (electrum_update, BTreeMap::new()) } }; - let (chain_update, incomplete_graph_update, keychain_update) = response; + let ( + ElectrumUpdate { + chain_update, + relevant_txids, + }, + keychain_update, + ) = response; let missing_txids = { let graph = &*graph.lock().unwrap(); - incomplete_graph_update.missing_full_txs(graph.graph()) + relevant_txids.missing_full_txs(graph.graph()) }; let now = std::time::UNIX_EPOCH @@ -270,7 +276,7 @@ fn main() -> anyhow::Result<()> { .expect("must get time") .as_secs(); - let graph_update = incomplete_graph_update.finalize(&client, Some(now), missing_txids)?; + let graph_update = relevant_txids.into_tx_graph(&client, Some(now), missing_txids)?; let db_changeset = { let mut chain = chain.lock().unwrap(); diff --git a/example-crates/wallet_electrum/src/main.rs b/example-crates/wallet_electrum/src/main.rs index f723d665..a6d7ca52 100644 --- a/example-crates/wallet_electrum/src/main.rs +++ b/example-crates/wallet_electrum/src/main.rs @@ -7,11 +7,13 @@ use std::io::Write; use std::str::FromStr; use bdk::bitcoin::Address; -use bdk::wallet::WalletUpdate; +use bdk::wallet::Update; use bdk::SignOptions; use bdk::{bitcoin::Network, Wallet}; -use bdk_electrum::electrum_client::{self, ElectrumApi}; -use bdk_electrum::ElectrumExt; +use bdk_electrum::{ + electrum_client::{self, ElectrumApi}, + ElectrumExt, ElectrumUpdate, +}; use bdk_file_store::Store; fn main() -> Result<(), Box> { @@ -53,16 +55,20 @@ fn main() -> Result<(), Box> { }) .collect(); - let (chain_update, incomplete_graph_update, keychain_update) = - client.scan(prev_tip, keychain_spks, None, None, STOP_GAP, BATCH_SIZE)?; + let ( + ElectrumUpdate { + chain_update, + relevant_txids, + }, + keychain_update, + ) = client.scan(prev_tip, keychain_spks, None, None, STOP_GAP, BATCH_SIZE)?; println!(); - let missing = incomplete_graph_update.missing_full_txs(wallet.as_ref()); - let graph_update = - incomplete_graph_update.finalize_with_confirmation_time(&client, None, missing)?; + let missing = relevant_txids.missing_full_txs(wallet.as_ref()); + let graph_update = relevant_txids.into_confirmation_time_tx_graph(&client, None, missing)?; - let wallet_update = WalletUpdate { + let wallet_update = Update { last_active_indices: keychain_update, graph: graph_update, chain: Some(chain_update), diff --git a/example-crates/wallet_esplora_async/src/main.rs b/example-crates/wallet_esplora_async/src/main.rs index 49692b5c..87a48b1b 100644 --- a/example-crates/wallet_esplora_async/src/main.rs +++ b/example-crates/wallet_esplora_async/src/main.rs @@ -2,7 +2,7 @@ use std::{io::Write, str::FromStr}; use bdk::{ bitcoin::{Address, Network}, - wallet::{AddressIndex, WalletUpdate}, + wallet::{AddressIndex, Update}, SignOptions, Wallet, }; use bdk_esplora::{esplora_client, EsploraAsyncExt}; @@ -58,7 +58,7 @@ async fn main() -> Result<(), Box> { .await?; let missing_heights = wallet.tx_graph().missing_heights(wallet.local_chain()); let chain_update = client.update_local_chain(prev_tip, missing_heights).await?; - let update = WalletUpdate { + let update = Update { last_active_indices, graph: update_graph, chain: Some(chain_update), diff --git a/example-crates/wallet_esplora_blocking/src/main.rs b/example-crates/wallet_esplora_blocking/src/main.rs index fb6e0e87..8034dabf 100644 --- a/example-crates/wallet_esplora_blocking/src/main.rs +++ b/example-crates/wallet_esplora_blocking/src/main.rs @@ -7,7 +7,7 @@ use std::{io::Write, str::FromStr}; use bdk::{ bitcoin::{Address, Network}, - wallet::{AddressIndex, WalletUpdate}, + wallet::{AddressIndex, Update}, SignOptions, Wallet, }; use bdk_esplora::{esplora_client, EsploraExt}; @@ -57,7 +57,7 @@ fn main() -> Result<(), Box> { client.update_tx_graph(keychain_spks, None, None, STOP_GAP, PARALLEL_REQUESTS)?; let missing_heights = wallet.tx_graph().missing_heights(wallet.local_chain()); let chain_update = client.update_local_chain(prev_tip, missing_heights)?; - let update = WalletUpdate { + let update = Update { last_active_indices, graph: update_graph, chain: Some(chain_update), From 1ff806c67f4da9ba58b7c7689fde0fe41a34a6f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BF=97=E5=AE=87?= Date: Thu, 14 Sep 2023 20:14:42 +0800 Subject: [PATCH 11/11] fix(chain)!: rm weird `From` impl And signature of `example_cli::KeychainChangeSet` is changed. --- crates/chain/src/indexed_tx_graph.rs | 8 +------- example-crates/example_cli/src/lib.rs | 21 +++++++++++++++++---- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/crates/chain/src/indexed_tx_graph.rs b/crates/chain/src/indexed_tx_graph.rs index c9091eef..4df7e85e 100644 --- a/crates/chain/src/indexed_tx_graph.rs +++ b/crates/chain/src/indexed_tx_graph.rs @@ -6,7 +6,7 @@ use alloc::vec::Vec; use bitcoin::{OutPoint, Transaction, TxOut}; use crate::{ - keychain, local_chain, + keychain, tx_graph::{self, TxGraph}, Anchor, Append, }; @@ -225,12 +225,6 @@ impl From> for ChangeSet> } } -impl From> for (local_chain::ChangeSet, ChangeSet) { - fn from(indexed_changeset: ChangeSet) -> Self { - (local_chain::ChangeSet::default(), indexed_changeset) - } -} - /// Utilities for indexing transaction data. /// /// Types which implement this trait can be used to construct an [`IndexedTxGraph`]. diff --git a/example-crates/example_cli/src/lib.rs b/example-crates/example_cli/src/lib.rs index 18bed2c9..c9459c35 100644 --- a/example-crates/example_cli/src/lib.rs +++ b/example-crates/example_cli/src/lib.rs @@ -12,6 +12,7 @@ use bdk_chain::{ }, indexed_tx_graph::{self, IndexedTxGraph}, keychain::{self, KeychainTxOutIndex}, + local_chain, miniscript::{ descriptor::{DescriptorSecretKey, KeyMap}, Descriptor, DescriptorPublicKey, @@ -24,7 +25,10 @@ pub use clap; use clap::{Parser, Subcommand}; pub type KeychainTxGraph = IndexedTxGraph>; -pub type KeychainChangeSet = indexed_tx_graph::ChangeSet>; +pub type KeychainChangeSet = ( + local_chain::ChangeSet, + indexed_tx_graph::ChangeSet>, +); pub type Database<'m, C> = Persist, C>; #[derive(Parser)] @@ -200,7 +204,10 @@ where let ((spk_i, spk), index_changeset) = spk_chooser(index, &Keychain::External); let db = &mut *db.lock().unwrap(); - db.stage(C::from(KeychainChangeSet::from(index_changeset))); + db.stage(C::from(( + local_chain::ChangeSet::default(), + indexed_tx_graph::ChangeSet::from(index_changeset), + ))); db.commit()?; let addr = Address::from_script(spk, network).context("failed to derive address")?; println!("[address @ {}] {}", spk_i, addr); @@ -353,7 +360,10 @@ where // If we're unable to persist this, then we don't want to broadcast. { let db = &mut *db.lock().unwrap(); - db.stage(C::from(KeychainChangeSet::from(index_changeset))); + db.stage(C::from(( + local_chain::ChangeSet::default(), + indexed_tx_graph::ChangeSet::from(index_changeset), + ))); db.commit()?; } @@ -376,7 +386,10 @@ where // We know the tx is at least unconfirmed now. Note if persisting here fails, // it's not a big deal since we can always find it again form // blockchain. - db.lock().unwrap().stage(C::from(keychain_changeset)); + db.lock().unwrap().stage(C::from(( + local_chain::ChangeSet::default(), + keychain_changeset, + ))); Ok(()) } Err(e) => {