Skip to content

Persist spks derived from KeychainTxOutIndex #1963

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 27, 2025
1 change: 1 addition & 0 deletions crates/bitcoind_rpc/examples/filter_iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ fn main() -> anyhow::Result<()> {
let (descriptor, _) = Descriptor::parse_descriptor(&secp, EXTERNAL)?;
let (change_descriptor, _) = Descriptor::parse_descriptor(&secp, INTERNAL)?;
let (mut chain, _) = LocalChain::from_genesis_hash(genesis_block(NETWORK).block_hash());

let mut graph = IndexedTxGraph::<ConfirmationBlockTime, KeychainTxOutIndex<&str>>::new({
let mut index = KeychainTxOutIndex::default();
index.insert_descriptor("external", descriptor.clone())?;
Expand Down
2 changes: 1 addition & 1 deletion crates/chain/benches/canonicalization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ fn setup<F: Fn(&mut KeychainTxGraph, &LocalChain)>(f: F) -> (KeychainTxGraph, Lo

let (desc, _) =
<Descriptor<DescriptorPublicKey>>::parse_descriptor(&Secp256k1::new(), DESC).unwrap();
let mut index = KeychainTxOutIndex::new(10);
let mut index = KeychainTxOutIndex::new(10, true);
index.insert_descriptor((), desc).unwrap();
let mut tx_graph = KeychainTxGraph::new(index);

Expand Down
103 changes: 92 additions & 11 deletions crates/chain/src/indexed_tx_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ use crate::{
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>,
}
Expand All @@ -35,14 +37,6 @@ impl<A, I: Default> Default for IndexedTxGraph<A, I> {
}

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
Expand Down Expand Up @@ -79,6 +73,87 @@ impl<A: Anchor, I: Indexer> IndexedTxGraph<A, I>
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// e.g. KeychainTxOutIndex needs descriptors that weren’t in its change set.

/// 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>,
Expand Down Expand Up @@ -443,6 +518,12 @@ impl<A, IA: Default> From<tx_graph::ChangeSet<A>> for ChangeSet<A, IA> {
}
}

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 {
Expand Down
Loading
Loading