diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs
index adb84ca221..9a82785959 100644
--- a/crates/chain/src/tx_graph.rs
+++ b/crates/chain/src/tx_graph.rs
@@ -1040,6 +1040,33 @@ impl ChangeSet {
})
.chain(self.txouts.iter().map(|(op, txout)| (*op, txout)))
}
+
+ /// Iterates over the heights of that the new transaction anchors in this changeset.
+ ///
+ /// This is useful if you want to find which heights you need to fetch data about in order to
+ /// confirm or exclude these anchors.
+ ///
+ /// See also: [`Self::missing_heights`]
+ pub fn relevant_heights(&self) -> impl Iterator- + '_ where A: Anchor {
+ let mut dedup = None;
+ self.anchors.iter().map(|(a, _)| a.anchor_block().height).filter(move |height| {
+ let duplicate = dedup == Some(*height);
+ dedup = Some(*height);
+ !duplicate
+ })
+ }
+
+
+ /// Returns an iterator for the [`relevant_heights`] in this changeset that are not included in
+ /// `local_chain`. This tells you which heights you need to include in `local_chain` in order
+ /// for it to conclusively act as a [`ChainOracle`] for the transaction anchors this changeset
+ /// will add.
+ ///
+ /// [`ChainOracle`]: crate::ChainOracle
+ /// [`relevant_heights`]: Self::relevant_heights
+ pub fn missing_heights_from<'a>(&'a self, local_chain: &'a LocalChain) -> impl Iterator
- + 'a where A: Anchor {
+ self.relevant_heights().filter(move |height| !local_chain.blocks().contains_key(height))
+ }
}
impl Append for ChangeSet {
diff --git a/example-crates/example_esplora/src/main.rs b/example-crates/example_esplora/src/main.rs
index e1a053a923..f33125be8e 100644
--- a/example-crates/example_esplora/src/main.rs
+++ b/example-crates/example_esplora/src/main.rs
@@ -1,12 +1,12 @@
use std::{
- collections::BTreeMap,
+ collections::{BTreeMap, BTreeSet},
io::{self, Write},
sync::Mutex,
};
use bdk_chain::{
bitcoin::{Address, Network, OutPoint, ScriptBuf, Txid},
- indexed_tx_graph::{self, IndexedTxGraph},
+ indexed_tx_graph::IndexedTxGraph,
keychain::WalletChangeSet,
local_chain::{CheckPoint, LocalChain},
Append, ConfirmationTimeAnchor,
@@ -114,17 +114,15 @@ fn main() -> anyhow::Result<()> {
}
};
- // This is where we will accumulate changes of `IndexedTxGraph` and `LocalChain` and persist
- // these changes as a batch at the end.
- let mut changeset = WalletChangeSet::::default();
-
// Prepare the `IndexedTxGraph` update based on whether we are scanning or syncing.
// Scanning: We are iterating through spks of all keychains and scanning for transactions for
// each spk. We start with the lowest derivation index spk and stop scanning after `stop_gap`
- // number of consecutive spks have no transaction history.
+ // number of consecutive spks have no transaction history. A Scan is done in situations of
+ // wallet restoration. It is a special case. Applications should use "sync" style updates
+ // after an initial scan.
// Syncing: We only check for specified spks, utxos and txids to update their confirmation
// status or fetch missing transactions.
- let graph_update = match &esplora_cmd {
+ let indexed_tx_graph_changeset = match &esplora_cmd {
EsploraCommands::Scan {
stop_gap,
scan_options,
@@ -155,7 +153,7 @@ fn main() -> anyhow::Result<()> {
// is reached. It returns a `TxGraph` update (`graph_update`) and a structure that
// represents the last active spk derivation indices of keychains
// (`keychain_indices_update`).
- let (graph_update, keychain_indices_update) = client
+ let (graph_update, last_active_indices) = client
.update_tx_graph(
keychain_spks,
core::iter::empty(),
@@ -165,18 +163,15 @@ fn main() -> anyhow::Result<()> {
)
.context("scanning for transactions")?;
- // Update the index in `IndexedTxGraph` with `keychain_indices_update`. The resultant
- // changes are appended to `changeset`.
- changeset.append({
- let (_, index_additions) = graph
- .lock()
- .expect("mutex must not be poisoned")
- .index
- .reveal_to_target_multi(&keychain_indices_update);
- WalletChangeSet::from(indexed_tx_graph::ChangeSet::from(index_additions))
- });
-
- graph_update
+ let mut graph = graph.lock().expect("mutex must not be poisoned");
+ // Because we did a stop gap based scan we are likely to have some updates to our
+ // deriviation indices. Usually before a scan you are on a fresh wallet with no
+ // addresses derived so we need to derive up to last active addresses the scan found
+ // before adding the transactions.
+ let (_, index_changeset) = graph.index.reveal_to_target_multi(&last_active_indices);
+ let mut indexed_tx_graph_changeset = graph.apply_update(graph_update);
+ indexed_tx_graph_changeset.append(index_changeset.into());
+ indexed_tx_graph_changeset
}
EsploraCommands::Sync {
mut unused_spks,
@@ -281,42 +276,31 @@ fn main() -> anyhow::Result<()> {
}
}
- client.update_tx_graph_without_keychain(
+ let graph_update = client.update_tx_graph_without_keychain(
spks,
txids,
outpoints,
scan_options.parallel_requests,
- )?
+ )?;
+
+ graph.lock().unwrap().apply_update(graph_update)
}
};
println!();
- // We apply the `TxGraph` update, and append the resultant changes to `changeset`
- // (for persistance)
- changeset.append({
- let indexed_graph_additions = graph.lock().unwrap().apply_update(graph_update);
- WalletChangeSet::from(indexed_graph_additions)
- });
-
- // Now that we're done updating the `TxGraph`, it's time to update the `LocalChain`!
- // We want the `LocalChain` to have data about all the anchors in the `TxGraph` - for this
- // reason, we want retrieve the heights of the newly added anchors, and fetch the corresponding
- // blocks.
-
- // We get the heights of all the anchors introduced by the changeset, and the local chain tip.
- // Note that the latter is only used for logging.
+ // Now that we're done updating the `IndexedTxGraph`, it's time to update the `LocalChain`! We
+ // want the `LocalChain` to have data about all the anchors in the `TxGraph` - for this reason,
+ // we want retrieve the blocks at the heights of the newly added anchors that are missing from
+ // our view of the chain.
let (missing_block_heights, tip) = {
let chain = &*chain.lock().unwrap();
- let heights_to_fetch = changeset
- .indexed_tx_graph
+ let missing_block_heights = indexed_tx_graph_changeset
.graph
- .anchors
- .iter()
- .map(|(a, _)| a.confirmation_height)
- .collect::>();
+ .missing_heights_from(chain)
+ .collect::>();
let tip = chain.tip();
- (heights_to_fetch, tip)
+ (missing_block_heights, tip)
};
println!("prev tip: {}", tip.as_ref().map_or(0, CheckPoint::height));
@@ -329,16 +313,12 @@ fn main() -> anyhow::Result<()> {
println!("new tip: {}", chain_update.tip.height());
- // We apply the `LocalChain` update, and append the resultant changes to `changeset`
- // (for persistance).
- changeset.append({
- let chain_additions = chain.lock().unwrap().apply_update(chain_update)?;
- WalletChangeSet::from(chain_additions)
- });
-
- // We persist `changeset`.
+ // We persist the changes
let mut db = db.lock().unwrap();
- db.stage(changeset);
+ db.stage(WalletChangeSet {
+ chain: chain.lock().unwrap().apply_update(chain_update)?,
+ indexed_tx_graph: indexed_tx_graph_changeset,
+ });
db.commit()?;
Ok(())
}