Anchor, BlockId, ChainOracle, Indexer, Merge, TxPosInBlock,
};
-/// The [`IndexedTxGraph`] combines a [`TxGraph`] and an [`Indexer`] implementation.
+/// A [`TxGraph<A>`] paired with an indexer `I`, enforcing that every insertion into the graph is
+/// simultaneously fed through the indexer.
///
-/// It ensures that [`TxGraph`] and [`Indexer`] are updated atomically.
+/// This guarantees that `tx_graph` and `index` remain in sync: any transaction or floating txout
+/// you add to `tx_graph` has already been processed by `index`.
#[derive(Debug, Clone)]
pub struct IndexedTxGraph<A, I> {
- /// Transaction index.
+ /// The indexer used for filtering transactions and floating txouts that we are interested in.
pub index: I,
graph: TxGraph<A>,
}
}
impl<A, I> IndexedTxGraph<A, I> {
- /// Construct a new [`IndexedTxGraph`] with a given `index`.
- pub fn new(index: I) -> Self {
- Self {
- index,
- graph: TxGraph::default(),
- }
- }
-
/// Get a reference of the internal transaction graph.
pub fn graph(&self) -> &TxGraph<A> {
&self.graph
where
I::ChangeSet: Default + Merge,
{
+ /// Create a new, empty [`IndexedTxGraph`].
+ ///
+ /// The underlying `TxGraph` is initialized with `TxGraph::default()`, and the provided
+ /// `index`er is used as‐is (since there are no existing transactions to process).
+ pub fn new(index: I) -> Self {
+ Self {
+ index,
+ graph: TxGraph::default(),
+ }
+ }
+
+ /// Reconstruct an [`IndexedTxGraph`] from persisted graph + indexer state.
+ ///
+ /// 1. Rebuilds the `TxGraph` from `changeset.tx_graph`.
+ /// 2. Calls your `indexer_from_changeset` closure on `changeset.indexer` to restore any state
+ /// your indexer needs beyond its raw changeset.
+ /// 3. Runs a full `.reindex()`, returning its `ChangeSet` to describe any additional updates
+ /// applied.
+ ///
+ /// # Errors
+ ///
+ /// Returns `Err(E)` if `indexer_from_changeset` fails.
+ ///
+ /// # Examples
+ ///
+ /// ```rust,no_run
+ /// use bdk_chain::IndexedTxGraph;
+ /// # use bdk_chain::indexed_tx_graph::ChangeSet;
+ /// # use bdk_chain::indexer::keychain_txout::{KeychainTxOutIndex, DEFAULT_LOOKAHEAD};
+ /// # use bdk_core::BlockId;
+ /// # use bdk_testenv::anyhow;
+ /// # use miniscript::{Descriptor, DescriptorPublicKey};
+ /// # use std::str::FromStr;
+ /// # let persisted_changeset = ChangeSet::<BlockId, _>::default();
+ /// # let persisted_desc = Some(Descriptor::<DescriptorPublicKey>::from_str("")?);
+ /// # let persisted_change_desc = Some(Descriptor::<DescriptorPublicKey>::from_str("")?);
+ ///
+ /// let (graph, reindex_cs) =
+ /// IndexedTxGraph::from_changeset(persisted_changeset, move |idx_cs| -> anyhow::Result<_> {
+ /// // e.g. KeychainTxOutIndex needs descriptors that weren’t in its CS
+ /// let mut idx = KeychainTxOutIndex::from_changeset(DEFAULT_LOOKAHEAD, true, idx_cs);
+ /// if let Some(desc) = persisted_desc {
+ /// idx.insert_descriptor("external", desc)?;
+ /// }
+ /// if let Some(desc) = persisted_change_desc {
+ /// idx.insert_descriptor("internal", desc)?;
+ /// }
+ /// Ok(idx)
+ /// })?;
+ /// # Ok::<(), anyhow::Error>(())
+ /// ```
+ pub fn from_changeset<F, E>(
+ changeset: ChangeSet<A, I::ChangeSet>,
+ indexer_from_changeset: F,
+ ) -> Result<(Self, ChangeSet<A, I::ChangeSet>), E>
+ where
+ F: FnOnce(I::ChangeSet) -> Result<I, E>,
+ {
+ let graph = TxGraph::<A>::from_changeset(changeset.tx_graph);
+ let index = indexer_from_changeset(changeset.indexer)?;
+ let mut out = Self { graph, index };
+ let out_changeset = out.reindex();
+ Ok((out, out_changeset))
+ }
+
+ /// Synchronizes the indexer to reflect every entry in the transaction graph.
+ ///
+ /// Iterates over **all** full transactions and floating outputs in `self.graph`, passing each
+ /// into `self.index`. Any indexer-side changes produced (via `index_tx` or `index_txout`) are
+ /// merged into a fresh `ChangeSet`, which is then returned.
+ pub fn reindex(&mut self) -> ChangeSet<A, I::ChangeSet> {
+ let mut changeset = ChangeSet::<A, I::ChangeSet>::default();
+ for tx in self.graph.full_txs() {
+ changeset.indexer.merge(self.index.index_tx(&tx));
+ }
+ for (op, txout) in self.graph.floating_txouts() {
+ changeset.indexer.merge(self.index.index_txout(op, txout));
+ }
+ changeset
+ }
+
fn index_tx_graph_changeset(
&mut self,
tx_graph_changeset: &tx_graph::ChangeSet<A>,
}
}
+impl<A, IA> From<(tx_graph::ChangeSet<A>, IA)> for ChangeSet<A, IA> {
+ fn from((tx_graph, indexer): (tx_graph::ChangeSet<A>, IA)) -> Self {
+ Self { tx_graph, indexer }
+ }
+}
+
#[cfg(feature = "miniscript")]
impl<A> From<crate::keychain_txout::ChangeSet> for ChangeSet<A, crate::keychain_txout::ChangeSet> {
fn from(indexer: crate::keychain_txout::ChangeSet) -> Self {
let spk_1 = descriptor.at_derivation_index(9).unwrap().script_pubkey();
let lookahead = 10;
- let mut graph = IndexedTxGraph::<ConfirmationBlockTime, KeychainTxOutIndex<()>>::new(
- KeychainTxOutIndex::new(lookahead, true),
- );
- let is_inserted = graph
- .index
- .insert_descriptor((), descriptor.clone())
- .unwrap();
- assert!(is_inserted);
+ let mut graph = IndexedTxGraph::<ConfirmationBlockTime, KeychainTxOutIndex<()>>::new({
+ let mut indexer = KeychainTxOutIndex::new(lookahead, true);
+ let is_inserted = indexer.insert_descriptor((), descriptor.clone()).unwrap();
+ assert!(is_inserted);
+ indexer
+ });
let tx_a = Transaction {
output: vec![
let (desc_2, _) =
Descriptor::parse_descriptor(&Secp256k1::signing_only(), DESCRIPTORS[3]).unwrap();
- let mut graph = IndexedTxGraph::<ConfirmationBlockTime, KeychainTxOutIndex<String>>::new(
- KeychainTxOutIndex::new(10, true),
- );
-
- assert!(graph
- .index
- .insert_descriptor("keychain_1".into(), desc_1)
- .unwrap());
- assert!(graph
- .index
- .insert_descriptor("keychain_2".into(), desc_2)
- .unwrap());
+ let mut graph = IndexedTxGraph::<ConfirmationBlockTime, KeychainTxOutIndex<String>>::new({
+ let mut indexer = KeychainTxOutIndex::new(10, true);
+ assert!(indexer
+ .insert_descriptor("keychain_1".into(), desc_1)
+ .unwrap());
+ assert!(indexer
+ .insert_descriptor("keychain_2".into(), desc_2)
+ .unwrap());
+ indexer
+ });
// Get trusted and untrusted addresses
+use bdk_chain::keychain_txout::DEFAULT_LOOKAHEAD;
use serde_json::json;
use std::cmp;
use std::collections::HashMap;
use bdk_chain::CanonicalizationParams;
use bdk_chain::ConfirmationBlockTime;
use bdk_chain::{
- indexed_tx_graph,
indexer::keychain_txout::{self, KeychainTxOutIndex},
local_chain::{self, LocalChain},
tx_graph, ChainOracle, DescriptorExt, FullTxOut, IndexedTxGraph, Merge,
Commands::Generate { network } => generate_bip86_helper(network).map(|_| None),
// try load
_ => {
- let (db, changeset) =
+ let (mut db, changeset) =
Store::<ChangeSet>::load(db_magic, db_path).context("could not open file store")?;
let changeset = changeset.expect("should not be empty");
-
let network = changeset.network.expect("changeset network");
let chain = Mutex::new({
chain
});
- let graph = Mutex::new({
- // insert descriptors and apply loaded changeset
- let mut index = KeychainTxOutIndex::default();
- if let Some(desc) = changeset.descriptor {
- index.insert_descriptor(Keychain::External, desc)?;
- }
- if let Some(change_desc) = changeset.change_descriptor {
- index.insert_descriptor(Keychain::Internal, change_desc)?;
- }
- let mut graph = KeychainTxGraph::new(index);
- graph.apply_changeset(indexed_tx_graph::ChangeSet {
- tx_graph: changeset.tx_graph,
- indexer: changeset.indexer,
- });
- graph
- });
+ let (graph, changeset) = IndexedTxGraph::from_changeset(
+ (changeset.tx_graph, changeset.indexer).into(),
+ |c| -> anyhow::Result<_> {
+ let mut indexer =
+ KeychainTxOutIndex::from_changeset(DEFAULT_LOOKAHEAD, true, c);
+ if let Some(desc) = changeset.descriptor {
+ indexer.insert_descriptor(Keychain::External, desc)?;
+ }
+ if let Some(change_desc) = changeset.change_descriptor {
+ indexer.insert_descriptor(Keychain::Internal, change_desc)?;
+ }
+ Ok(indexer)
+ },
+ )?;
+ db.append(&ChangeSet {
+ indexer: changeset.indexer,
+ tx_graph: changeset.tx_graph,
+ ..Default::default()
+ })?;
+ let graph = Mutex::new(graph);
let db = Mutex::new(db);
Ok(Some(Init {