diff --git a/crates/bdk/src/wallet/mod.rs b/crates/bdk/src/wallet/mod.rs index f7bc5f49b..5a3918cf0 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::{self, KeychainTxOutIndex}, local_chain::{self, CannotConnectError, CheckPoint, CheckPointIter, LocalChain}, tx_graph::{CanonicalTx, TxGraph}, Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeAnchor, FullTxOut, @@ -95,11 +95,74 @@ pub struct Wallet { secp: SecpCtx, } -/// The update to a [`Wallet`] used in [`Wallet::apply_update`]. This is usually returned from blockchain data sources. -pub type Update = WalletUpdate; +/// An update to [`Wallet`]. +/// +/// It updates [`bdk_chain::keychain::KeychainTxOutIndex`], [`bdk_chain::TxGraph`] and [`local_chain::LocalChain`] atomically. +#[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, + + /// Update for the wallet's internal [`TxGraph`]. + pub graph: TxGraph, + + /// Update for the wallet's internal [`LocalChain`]. + /// + /// [`LocalChain`]: local_chain::LocalChain + pub chain: Option, +} + +/// 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, + + /// Changes to [`IndexedTxGraph`]. + /// + /// [`IndexedTxGraph`]: bdk_chain::indexed_tx_graph::IndexedTxGraph + pub indexed_tx_graph: + indexed_tx_graph::ChangeSet>, +} + +impl Append for ChangeSet { + 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 ChangeSet { + fn from(chain: local_chain::ChangeSet) -> Self { + Self { + chain, + ..Default::default() + } + } +} -/// The changeset produced internally by [`Wallet`] when mutated. -pub type ChangeSet = WalletChangeSet; +impl From>> + for ChangeSet +{ + fn from( + indexed_tx_graph: indexed_tx_graph::ChangeSet< + ConfirmationTimeAnchor, + keychain::ChangeSet, + >, + ) -> Self { + Self { + indexed_tx_graph, + ..Default::default() + } + } +} /// 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`. @@ -1857,7 +1920,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/crates/chain/src/indexed_tx_graph.rs b/crates/chain/src/indexed_tx_graph.rs index 6dc2e9943..4df7e85e6 100644 --- a/crates/chain/src/indexed_tx_graph.rs +++ b/crates/chain/src/indexed_tx_graph.rs @@ -225,7 +225,10 @@ impl From> for ChangeSet> } } -/// Represents a structure that can index transaction data. +/// 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; @@ -233,7 +236,7 @@ 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. fn index_tx(&mut self, tx: &Transaction) -> Self::ChangeSet; /// Apply changeset to itself. diff --git a/crates/chain/src/keychain.rs b/crates/chain/src/keychain.rs index 3a6cddf80..63972a0ad 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, Append}; #[cfg(feature = "miniscript")] mod txout_index; @@ -82,98 +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( - 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/keychain/txout_index.rs b/crates/chain/src/keychain/txout_index.rs index 9b38a7ade..3d642edd4 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}; @@ -91,11 +91,18 @@ impl Indexer for KeychainTxOutIndex { type ChangeSet = super::ChangeSet; fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet { - self.scan_txout(outpoint, txout) + match self.inner.scan_txout(outpoint, txout).cloned() { + Some((keychain, index)) => self.reveal_to_target(&keychain, index).1, + None => super::ChangeSet::default(), + } } 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,38 +119,6 @@ impl Indexer for KeychainTxOutIndex { } impl KeychainTxOutIndex { - /// Scans an object 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`). - /// - /// See [`ForEachTxout`] for the types that support this. - /// - /// [`ForEachTxout`]: crate::ForEachTxOut - pub fn scan(&mut self, txouts: &impl ForEachTxOut) -> super::ChangeSet { - let mut changeset = super::ChangeSet::::default(); - txouts.for_each_txout(|(op, txout)| changeset.append(self.scan_txout(op, 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 @@ -199,15 +174,14 @@ 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`]. + /// 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 /// /// 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 5547f37c6..4047f8fcb 100644 --- a/crates/chain/src/spk_txout_index.rs +++ b/crates/chain/src/spk_txout_index.rs @@ -3,15 +3,15 @@ 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}; /// 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 @@ -25,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 { @@ -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'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. - /// - /// 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: &Transaction) -> BTreeSet { let mut scanned_indices = BTreeSet::new(); - - txouts.for_each_txout(|(op, txout)| { - if let Some(spk_i) = scan_txout!(self, op, txout) { + 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 } @@ -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 43fc73b49..274bae36e 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. /// @@ -124,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) @@ -162,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); diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index 7f58f2031..cfd2de9d9 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`]. diff --git a/crates/chain/tests/test_keychain_txout_index.rs b/crates/chain/tests/test_keychain_txout_index.rs index 817b03499..c4f434715 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 099b4ca88..e8b752146 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), diff --git a/crates/electrum/src/electrum_ext.rs b/crates/electrum/src/electrum_ext.rs index c7859bdfe..7ac16a046 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,90 +14,65 @@ 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 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 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::into_tx_graph`] to +/// fetch the full transactions from Electrum and finalize the update. +#[derive(Debug, Default, Clone)] +pub struct RelevantTxids(HashMap>); +impl RelevantTxids { /// Determine the full transactions that are missing from `graph`. /// - /// Refer to [`ElectrumUpdate`]. - pub fn missing_full_txs(&self, graph: &TxGraph) -> Vec { - self.graph_update + /// 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()) .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`]. - 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_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(WalletUpdate { - last_active_indices: self.keychain_update, - graph: graph_update, - chain: local_chain::Update { - tip: self.new_tip, - introduce_older_blocks: true, - }, - }) + Ok(graph) } -} -impl ElectrumUpdate { - /// Finalizes the [`ElectrumUpdate`] 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_as_confirmation_time( + pub fn into_confirmation_time_tx_graph( self, client: &Client, seen_at: Option, missing: Vec, - ) -> Result, Error> { - let update = self.finalize(client, seen_at, missing)?; + ) -> Result, Error> { + let graph = self.into_tx_graph(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 +92,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); tx_graph::ChangeSet { txs: old_changeset.txs, txouts: old_changeset.txouts, @@ -140,21 +114,28 @@ 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 new_graph = TxGraph::default(); + new_graph.apply_changeset(graph_changeset); + Ok(new_graph) } } +/// 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 { - /// Scan the blockchain (via electrum) for the data specified and returns a [`ElectrumUpdate`]. +pub trait ElectrumExt { + /// 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 @@ -173,7 +154,7 @@ pub trait ElectrumExt { outpoints: impl IntoIterator, stop_gap: usize, batch_size: usize, - ) -> Result, Error>; + ) -> Result<(ElectrumUpdate, BTreeMap), Error>; /// Convenience method to call [`scan`] without requiring a keychain. /// @@ -185,24 +166,26 @@ pub trait ElectrumExt { txids: impl IntoIterator, outpoints: impl IntoIterator, batch_size: usize, - ) -> Result, Error> { + ) -> Result { let spk_iter = misc_spks .into_iter() .enumerate() .map(|(i, spk)| (i as u32, spk)); - self.scan( + let (electrum_update, _) = self.scan( prev_tip, [((), spk_iter)].into(), txids, outpoints, usize::MAX, batch_size, - ) + )?; + + Ok(electrum_update) } } -impl ElectrumExt for Client { +impl ElectrumExt for Client { fn scan( &self, prev_tip: Option, @@ -211,7 +194,7 @@ impl ElectrumExt for Client { outpoints: impl IntoIterator, stop_gap: usize, batch_size: usize, - ) -> Result, Error> { + ) -> Result<(ElectrumUpdate, BTreeMap), Error> { let mut request_spks = keychain_spks .into_iter() .map(|(k, s)| (k, s.into_iter())) @@ -221,11 +204,10 @@ 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 update = ElectrumUpdate::::new(tip.clone()); - let cps = update - .new_tip + let mut relevant_txids = RelevantTxids::default(); + let cps = tip .iter() .take(10) .map(|cp| (cp.height(), cp)) @@ -236,7 +218,7 @@ impl ElectrumExt for Client { scanned_spks.append(&mut populate_with_spks( self, &cps, - &mut update, + &mut relevant_txids, &mut scanned_spks .iter() .map(|(i, (spk, _))| (i.clone(), spk.clone())), @@ -249,7 +231,7 @@ impl ElectrumExt for Client { populate_with_spks( self, &cps, - &mut update, + &mut relevant_txids, keychain_spks, stop_gap, batch_size, @@ -260,10 +242,14 @@ impl ElectrumExt for Client { } } - populate_with_txids(self, &cps, &mut 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 update, &mut outpoints.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(); @@ -271,7 +257,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 @@ -281,10 +272,17 @@ impl ElectrumExt for Client { .map(|((_, i), _)| (k, *i)) }) .collect::>(); - break update; + + break ( + ElectrumUpdate { + chain_update, + relevant_txids, + }, + keychain_update, + ); }; - Ok(update) + Ok((electrum_update, keychain_update)) } } @@ -405,10 +403,10 @@ fn determine_tx_anchor( } } -fn populate_with_outpoints( +fn populate_with_outpoints( client: &Client, cps: &BTreeMap, - update: &mut ElectrumUpdate, + relevant_txids: &mut RelevantTxids, outpoints: &mut impl Iterator, ) -> Result, Error> { let mut full_txs = HashMap::new(); @@ -457,8 +455,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 = relevant_txids.0.entry(res.tx_hash).or_default(); if let Some(anchor) = anchor { tx_entry.insert(anchor); } @@ -467,10 +464,10 @@ fn populate_with_outpoints( Ok(full_txs) } -fn populate_with_txids( +fn populate_with_txids( client: &Client, cps: &BTreeMap, - update: &mut ElectrumUpdate, + relevant_txids: &mut RelevantTxids, txids: &mut impl Iterator, ) -> Result<(), Error> { for txid in txids { @@ -495,7 +492,7 @@ fn populate_with_txids( None => continue, }; - let tx_entry = update.graph_update.entry(txid).or_default(); + let tx_entry = relevant_txids.0.entry(txid).or_default(); if let Some(anchor) = anchor { tx_entry.insert(anchor); } @@ -503,10 +500,10 @@ fn populate_with_txids( Ok(()) } -fn populate_with_spks( +fn populate_with_spks( client: &Client, cps: &BTreeMap, - update: &mut ElectrumUpdate, + relevant_txids: &mut RelevantTxids, spks: &mut impl Iterator, stop_gap: usize, batch_size: usize, @@ -539,7 +536,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 = 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 716c4d3f7..1e4805379 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`], [`RelevantTxids`], `keychain_update`) +//! +//! 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 -//! [`ElectrumUpdate`] 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. @@ -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`]: 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_cli/src/lib.rs b/example-crates/example_cli/src/lib.rs index 18bed2c93..c9459c353 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) => { diff --git a/example-crates/example_electrum/src/main.rs b/example-crates/example_electrum/src/main.rs index 4c5fde6a3..a05e85c57 100644 --- a/example-crates/example_electrum/src/main.rs +++ b/example-crates/example_electrum/src/main.rs @@ -7,8 +7,8 @@ use std::{ use bdk_chain::{ bitcoin::{Address, Network, OutPoint, ScriptBuf, Txid}, indexed_tx_graph::{self, IndexedTxGraph}, - keychain::WalletChangeSet, - local_chain::LocalChain, + keychain, + local_chain::{self, LocalChain}, Append, ConfirmationHeightAnchor, }; use bdk_electrum::{ @@ -60,19 +60,22 @@ 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) = + 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.indexed_tx_graph); + graph.apply_changeset(disk_tx_graph); graph }); - let chain = Mutex::new(LocalChain::from_changeset(init_changeset.chain)); + let chain = Mutex::new(LocalChain::from_changeset(disk_local_chain)); let electrum_url = match args.network { Network::Bitcoin => "ssl://electrum.blockstream.info:50002", @@ -248,20 +251,24 @@ fn main() -> anyhow::Result<()> { // drop lock on graph and chain drop((graph, chain)); - let update = client + let electrum_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(), - } + (electrum_update, BTreeMap::new()) } }; + let ( + ElectrumUpdate { + chain_update, + relevant_txids, + }, + keychain_update, + ) = response; + let missing_txids = { let graph = &*graph.lock().unwrap(); - response.missing_full_txs(graph.graph()) + relevant_txids.missing_full_txs(graph.graph()) }; let now = std::time::UNIX_EPOCH @@ -269,32 +276,27 @@ fn main() -> anyhow::Result<()> { .expect("must get time") .as_secs(); - let final_update = response.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(); let mut graph = graph.lock().unwrap(); - let chain = chain.apply_update(final_update.chain)?; + let chain = chain.apply_update(chain_update)?; 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 }; - 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 1aff0db25..5791fe61a 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 }); @@ -303,18 +308,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(()) } diff --git a/example-crates/wallet_electrum/src/main.rs b/example-crates/wallet_electrum/src/main.rs index 52def58eb..a6d7ca520 100644 --- a/example-crates/wallet_electrum/src/main.rs +++ b/example-crates/wallet_electrum/src/main.rs @@ -7,10 +7,13 @@ use std::io::Write; use std::str::FromStr; use bdk::bitcoin::Address; +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> { @@ -52,14 +55,25 @@ fn main() -> Result<(), Box> { }) .collect(); - let electrum_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 = electrum_update.missing_full_txs(wallet.as_ref()); - let update = electrum_update.finalize_as_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)?; - wallet.apply_update(update)?; + let wallet_update = Update { + last_active_indices: keychain_update, + graph: graph_update, + chain: Some(chain_update), + }; + wallet.apply_update(wallet_update)?; wallet.commit()?; let balance = wallet.get_balance(); diff --git a/example-crates/wallet_esplora_async/src/main.rs b/example-crates/wallet_esplora_async/src/main.rs index b5c171636..c7a729af6 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, Update}, SignOptions, Wallet, }; use bdk_esplora::{esplora_client, EsploraAsyncExt}; @@ -59,10 +58,10 @@ 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, - ..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 4001858de..a93a449c8 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, Update}, SignOptions, Wallet, }; use bdk_esplora::{esplora_client, EsploraExt}; @@ -58,10 +57,10 @@ fn main() -> Result<(), Box> { client.scan_txs_with_keychains(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, - ..WalletUpdate::new(chain_update) + chain: Some(chain_update), }; wallet.apply_update(update)?;