bdk_chain/
tx_graph.rs

1//! Module for structures that store and traverse transactions.
2//!
3//! [`TxGraph`] contains transactions and indexes them so you can easily traverse the graph of
4//! those transactions. `TxGraph` is *monotone* in that you can always insert a transaction -- it
5//! does not care whether that transaction is in the current best chain or whether it conflicts with
6//! any of the existing transactions or what order you insert the transactions. This means that you
7//! can always combine two [`TxGraph`]s together, without resulting in inconsistencies. Furthermore,
8//! there is currently no way to delete a transaction.
9//!
10//! Transactions can be either whole or partial (i.e., transactions for which we only know some
11//! outputs, which we usually call "floating outputs"; these are usually inserted using the
12//! [`insert_txout`] method.).
13//!
14//! The graph contains transactions in the form of [`TxNode`]s. Each node contains the txid, the
15//! transaction (whole or partial), the blocks that it is anchored to (see the [`Anchor`]
16//! documentation for more details), and the timestamp of the last time we saw the transaction as
17//! unconfirmed.
18//!
19//! # Canonicalization
20//!
21//! Conflicting transactions are allowed to coexist within a [`TxGraph`]. A process called
22//! canonicalization is required to get a conflict-free view of transactions.
23//!
24//! * [`list_canonical_txs`](TxGraph::list_canonical_txs) lists canonical transactions.
25//! * [`filter_chain_txouts`](TxGraph::filter_chain_txouts) filters out canonical outputs from a
26//!   list of outpoints.
27//! * [`filter_chain_unspents`](TxGraph::filter_chain_unspents) filters out canonical unspent
28//!   outputs from a list of outpoints.
29//! * [`balance`](TxGraph::balance) gets the total sum of unspent outputs filtered from a list of
30//!   outpoints.
31//! * [`canonical_iter`](TxGraph::canonical_iter) returns the [`CanonicalIter`] which contains all
32//!   of the canonicalization logic.
33//!
34//! All these methods require a `chain` and `chain_tip` argument. The `chain` must be a
35//! [`ChainOracle`] implementation (such as [`LocalChain`](crate::local_chain::LocalChain)) which
36//! identifies which blocks exist under a given `chain_tip`.
37//!
38//! The canonicalization algorithm uses the following associated data to determine which
39//! transactions have precedence over others:
40//!
41//! * [`Anchor`] - This bit of data represents that a transaction is anchored in a given block. If
42//!   the transaction is anchored in chain of `chain_tip`, or is an ancestor of a transaction
43//!   anchored in chain of `chain_tip`, then the transaction must be canonical.
44//! * `last_seen` - This is the timestamp of when a transaction is last-seen in the mempool. This
45//!   value is updated by [`insert_seen_at`](TxGraph::insert_seen_at) and
46//!   [`apply_update`](TxGraph::apply_update). Transactions that are seen later have higher priority
47//!   than those that are seen earlier. `last_seen` values are transitive. This means that the
48//!   actual `last_seen` value of a transaction is the max of all the `last_seen` values from it's
49//!   descendants.
50//! * `last_evicted` - This is the timestamp of when a transaction last went missing from the
51//!   mempool. If this value is equal to or higher than the transaction's `last_seen` value, then it
52//!   will not be considered canonical.
53//!
54//! # Graph traversal
55//!
56//! You can use [`TxAncestors`]/[`TxDescendants`] to traverse ancestors and descendants of a given
57//! transaction, respectively.
58//!
59//! # Applying changes
60//!
61//! The [`ChangeSet`] reports changes made to a [`TxGraph`]; it can be used to either save to
62//! persistent storage, or to be applied to another [`TxGraph`].
63//!
64//! Methods that change the state of [`TxGraph`] will return [`ChangeSet`]s.
65//!
66//! # Generics
67//!
68//! Anchors are represented as generics within `TxGraph<A>`. To make use of all functionality of the
69//! `TxGraph`, anchors (`A`) should implement [`Anchor`].
70//!
71//! Anchors are made generic so that different types of data can be stored with how a transaction is
72//! *anchored* to a given block. An example of this is storing a merkle proof of the transaction to
73//! the confirmation block - this can be done with a custom [`Anchor`] type. The minimal [`Anchor`]
74//! type would just be a [`BlockId`] which just represents the height and hash of the block which
75//! the transaction is contained in. Note that a transaction can be contained in multiple
76//! conflicting blocks (by nature of the Bitcoin network).
77//!
78//! ```
79//! # use bdk_chain::BlockId;
80//! # use bdk_chain::tx_graph::TxGraph;
81//! # use bdk_chain::example_utils::*;
82//! # use bitcoin::Transaction;
83//! # let tx_a = tx_from_hex(RAW_TX_1);
84//! let mut tx_graph: TxGraph = TxGraph::default();
85//!
86//! // insert a transaction
87//! let changeset = tx_graph.insert_tx(tx_a);
88//!
89//! // We can restore the state of the `tx_graph` by applying all
90//! // the changesets obtained by mutating the original (the order doesn't matter).
91//! let mut restored_tx_graph: TxGraph = TxGraph::default();
92//! restored_tx_graph.apply_changeset(changeset);
93//!
94//! assert_eq!(tx_graph, restored_tx_graph);
95//! ```
96//!
97//! A [`TxGraph`] can also be updated with another [`TxGraph`] which merges them together.
98//!
99//! ```
100//! # use bdk_chain::{Merge, BlockId};
101//! # use bdk_chain::tx_graph::{self, TxGraph};
102//! # use bdk_chain::example_utils::*;
103//! # use bitcoin::Transaction;
104//! # use std::sync::Arc;
105//! # let tx_a = tx_from_hex(RAW_TX_1);
106//! # let tx_b = tx_from_hex(RAW_TX_2);
107//! let mut graph: TxGraph = TxGraph::default();
108//!
109//! let mut update = tx_graph::TxUpdate::default();
110//! update.txs.push(Arc::new(tx_a));
111//! update.txs.push(Arc::new(tx_b));
112//!
113//! // apply the update graph
114//! let changeset = graph.apply_update(update.clone());
115//!
116//! // if we apply it again, the resulting changeset will be empty
117//! let changeset = graph.apply_update(update);
118//! assert!(changeset.is_empty());
119//! ```
120//! [`insert_txout`]: TxGraph::insert_txout
121
122use crate::collections::*;
123use crate::spk_txout::SpkTxOutIndex;
124use crate::BlockId;
125use crate::CanonicalIter;
126use crate::CanonicalReason;
127use crate::CanonicalizationParams;
128use crate::ObservedIn;
129use crate::{Anchor, Balance, ChainOracle, ChainPosition, FullTxOut, Merge};
130use alloc::collections::vec_deque::VecDeque;
131use alloc::sync::Arc;
132use alloc::vec::Vec;
133use bdk_core::ConfirmationBlockTime;
134pub use bdk_core::TxUpdate;
135use bitcoin::{Amount, OutPoint, ScriptBuf, SignedAmount, Transaction, TxOut, Txid};
136use core::fmt::{self, Formatter};
137use core::ops::RangeBounds;
138use core::{
139    convert::Infallible,
140    ops::{Deref, RangeInclusive},
141};
142
143impl<A: Ord> From<TxGraph<A>> for TxUpdate<A> {
144    fn from(graph: TxGraph<A>) -> Self {
145        let mut tx_update = TxUpdate::default();
146        tx_update.txs = graph.full_txs().map(|tx_node| tx_node.tx).collect();
147        tx_update.txouts = graph
148            .floating_txouts()
149            .map(|(op, txo)| (op, txo.clone()))
150            .collect();
151        tx_update.anchors = graph
152            .anchors
153            .into_iter()
154            .flat_map(|(txid, anchors)| anchors.into_iter().map(move |a| (a, txid)))
155            .collect();
156        tx_update.seen_ats = graph.last_seen.into_iter().collect();
157        tx_update.evicted_ats = graph.last_evicted.into_iter().collect();
158        tx_update
159    }
160}
161
162impl<A: Anchor> From<TxUpdate<A>> for TxGraph<A> {
163    fn from(update: TxUpdate<A>) -> Self {
164        let mut graph = TxGraph::<A>::default();
165        let _ = graph.apply_update(update);
166        graph
167    }
168}
169
170/// A graph of transactions and spends.
171///
172/// See the [module-level documentation] for more.
173///
174/// [module-level documentation]: crate::tx_graph
175#[derive(Clone, Debug, PartialEq)]
176pub struct TxGraph<A = ConfirmationBlockTime> {
177    txs: HashMap<Txid, TxNodeInternal>,
178    spends: BTreeMap<OutPoint, HashSet<Txid>>,
179    anchors: HashMap<Txid, BTreeSet<A>>,
180    first_seen: HashMap<Txid, u64>,
181    last_seen: HashMap<Txid, u64>,
182    last_evicted: HashMap<Txid, u64>,
183
184    txs_by_highest_conf_heights: BTreeSet<(u32, Txid)>,
185    txs_by_last_seen: BTreeSet<(u64, Txid)>,
186
187    // The following fields exist so that methods can return references to empty sets.
188    // FIXME: This can be removed once `HashSet::new` and `BTreeSet::new` are const fns.
189    empty_outspends: HashSet<Txid>,
190    empty_anchors: BTreeSet<A>,
191}
192
193impl<A> Default for TxGraph<A> {
194    fn default() -> Self {
195        Self {
196            txs: Default::default(),
197            spends: Default::default(),
198            anchors: Default::default(),
199            first_seen: Default::default(),
200            last_seen: Default::default(),
201            last_evicted: Default::default(),
202            txs_by_highest_conf_heights: Default::default(),
203            txs_by_last_seen: Default::default(),
204            empty_outspends: Default::default(),
205            empty_anchors: Default::default(),
206        }
207    }
208}
209
210/// A transaction node in the [`TxGraph`].
211#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
212pub struct TxNode<'a, T, A> {
213    /// Txid of the transaction.
214    pub txid: Txid,
215    /// A partial or full representation of the transaction.
216    pub tx: T,
217    /// The blocks that the transaction is "anchored" in.
218    pub anchors: &'a BTreeSet<A>,
219    /// The first-seen unix timestamp of the transaction as unconfirmed.
220    pub first_seen: Option<u64>,
221    /// The last-seen unix timestamp of the transaction as unconfirmed.
222    pub last_seen: Option<u64>,
223}
224
225impl<T, A> Deref for TxNode<'_, T, A> {
226    type Target = T;
227
228    fn deref(&self) -> &Self::Target {
229        &self.tx
230    }
231}
232
233/// Internal representation of a transaction node of a [`TxGraph`].
234///
235/// This can either be a whole transaction, or a partial transaction (where we only have select
236/// outputs).
237#[derive(Clone, Debug, PartialEq)]
238enum TxNodeInternal {
239    Whole(Arc<Transaction>),
240    Partial(BTreeMap<u32, TxOut>),
241}
242
243impl Default for TxNodeInternal {
244    fn default() -> Self {
245        Self::Partial(BTreeMap::new())
246    }
247}
248
249/// A transaction that is deemed to be part of the canonical history.
250#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
251pub struct CanonicalTx<'a, T, A> {
252    /// How the transaction is observed in the canonical chain (confirmed or unconfirmed).
253    pub chain_position: ChainPosition<A>,
254    /// The transaction node (as part of the graph).
255    pub tx_node: TxNode<'a, T, A>,
256}
257
258impl<'a, T, A> From<CanonicalTx<'a, T, A>> for Txid {
259    fn from(tx: CanonicalTx<'a, T, A>) -> Self {
260        tx.tx_node.txid
261    }
262}
263
264impl<'a, A> From<CanonicalTx<'a, Arc<Transaction>, A>> for Arc<Transaction> {
265    fn from(tx: CanonicalTx<'a, Arc<Transaction>, A>) -> Self {
266        tx.tx_node.tx
267    }
268}
269
270/// Errors returned by `TxGraph::calculate_fee`.
271#[derive(Debug, PartialEq, Eq)]
272pub enum CalculateFeeError {
273    /// Missing `TxOut` for one or more of the inputs of the tx
274    MissingTxOut(Vec<OutPoint>),
275    /// When the transaction is invalid according to the graph it has a negative fee
276    NegativeFee(SignedAmount),
277}
278
279impl fmt::Display for CalculateFeeError {
280    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
281        match self {
282            CalculateFeeError::MissingTxOut(outpoints) => write!(
283                f,
284                "missing `TxOut` for one or more of the inputs of the tx: {outpoints:?}",
285            ),
286            CalculateFeeError::NegativeFee(fee) => write!(
287                f,
288                "transaction is invalid according to the graph and has negative fee: {}",
289                fee.display_dynamic()
290            ),
291        }
292    }
293}
294
295#[cfg(feature = "std")]
296impl std::error::Error for CalculateFeeError {}
297
298impl<A> TxGraph<A> {
299    /// Iterate over all tx outputs known by [`TxGraph`].
300    ///
301    /// This includes txouts of both full transactions as well as floating transactions.
302    pub fn all_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
303        self.txs.iter().flat_map(|(txid, tx)| match tx {
304            TxNodeInternal::Whole(tx) => tx
305                .as_ref()
306                .output
307                .iter()
308                .enumerate()
309                .map(|(vout, txout)| (OutPoint::new(*txid, vout as _), txout))
310                .collect::<Vec<_>>(),
311            TxNodeInternal::Partial(txouts) => txouts
312                .iter()
313                .map(|(vout, txout)| (OutPoint::new(*txid, *vout as _), txout))
314                .collect::<Vec<_>>(),
315        })
316    }
317
318    /// Iterate over floating txouts known by [`TxGraph`].
319    ///
320    /// Floating txouts are txouts that do not have the residing full transaction contained in the
321    /// graph.
322    pub fn floating_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
323        self.txs
324            .iter()
325            .filter_map(|(txid, tx_node)| match tx_node {
326                TxNodeInternal::Whole(_) => None,
327                TxNodeInternal::Partial(txouts) => Some(
328                    txouts
329                        .iter()
330                        .map(|(&vout, txout)| (OutPoint::new(*txid, vout), txout)),
331                ),
332            })
333            .flatten()
334    }
335
336    /// Iterate over all full transactions in the graph.
337    pub fn full_txs(&self) -> impl Iterator<Item = TxNode<'_, Arc<Transaction>, A>> {
338        self.txs.iter().filter_map(|(&txid, tx)| match tx {
339            TxNodeInternal::Whole(tx) => Some(TxNode {
340                txid,
341                tx: tx.clone(),
342                anchors: self.anchors.get(&txid).unwrap_or(&self.empty_anchors),
343                first_seen: self.first_seen.get(&txid).copied(),
344                last_seen: self.last_seen.get(&txid).copied(),
345            }),
346            TxNodeInternal::Partial(_) => None,
347        })
348    }
349
350    /// Iterate over graph transactions with no anchors or last-seen.
351    pub fn txs_with_no_anchor_or_last_seen(
352        &self,
353    ) -> impl Iterator<Item = TxNode<'_, Arc<Transaction>, A>> {
354        self.full_txs().filter_map(|tx| {
355            if tx.anchors.is_empty() && tx.last_seen.is_none() {
356                Some(tx)
357            } else {
358                None
359            }
360        })
361    }
362
363    /// Get a transaction by txid. This only returns `Some` for full transactions.
364    ///
365    /// Refer to [`get_txout`] for getting a specific [`TxOut`].
366    ///
367    /// [`get_txout`]: Self::get_txout
368    pub fn get_tx(&self, txid: Txid) -> Option<Arc<Transaction>> {
369        self.get_tx_node(txid).map(|n| n.tx)
370    }
371
372    /// Get a transaction node by txid. This only returns `Some` for full transactions.
373    pub fn get_tx_node(&self, txid: Txid) -> Option<TxNode<'_, Arc<Transaction>, A>> {
374        match &self.txs.get(&txid)? {
375            TxNodeInternal::Whole(tx) => Some(TxNode {
376                txid,
377                tx: tx.clone(),
378                anchors: self.anchors.get(&txid).unwrap_or(&self.empty_anchors),
379                first_seen: self.first_seen.get(&txid).copied(),
380                last_seen: self.last_seen.get(&txid).copied(),
381            }),
382            _ => None,
383        }
384    }
385
386    /// Obtains a single tx output (if any) at the specified outpoint.
387    pub fn get_txout(&self, outpoint: OutPoint) -> Option<&TxOut> {
388        match &self.txs.get(&outpoint.txid)? {
389            TxNodeInternal::Whole(tx) => tx.as_ref().output.get(outpoint.vout as usize),
390            TxNodeInternal::Partial(txouts) => txouts.get(&outpoint.vout),
391        }
392    }
393
394    /// Returns known outputs of a given `txid`.
395    ///
396    /// Returns a [`BTreeMap`] of vout to output of the provided `txid`.
397    pub fn tx_outputs(&self, txid: Txid) -> Option<BTreeMap<u32, &TxOut>> {
398        Some(match &self.txs.get(&txid)? {
399            TxNodeInternal::Whole(tx) => tx
400                .as_ref()
401                .output
402                .iter()
403                .enumerate()
404                .map(|(vout, txout)| (vout as u32, txout))
405                .collect::<BTreeMap<_, _>>(),
406            TxNodeInternal::Partial(txouts) => txouts
407                .iter()
408                .map(|(vout, txout)| (*vout, txout))
409                .collect::<BTreeMap<_, _>>(),
410        })
411    }
412
413    /// Get the `last_evicted` timestamp of the given `txid`.
414    ///
415    /// Ideally, this would be included in [`TxNode`], but that would be a breaking change.
416    pub fn get_last_evicted(&self, txid: Txid) -> Option<u64> {
417        self.last_evicted.get(&txid).copied()
418    }
419
420    /// Calculates the fee of a given transaction. Returns [`Amount::ZERO`] if `tx` is a coinbase
421    /// transaction. Returns `OK(_)` if we have all the [`TxOut`]s being spent by `tx` in the
422    /// graph (either as the full transactions or individual txouts).
423    ///
424    /// To calculate the fee for a [`Transaction`] that depends on foreign [`TxOut`] values you must
425    /// first manually insert the foreign TxOuts into the tx graph using the [`insert_txout`]
426    /// function. Only insert TxOuts you trust the values for!
427    ///
428    /// Note `tx` does not have to be in the graph for this to work.
429    ///
430    /// [`insert_txout`]: Self::insert_txout
431    pub fn calculate_fee(&self, tx: &Transaction) -> Result<Amount, CalculateFeeError> {
432        if tx.is_coinbase() {
433            return Ok(Amount::ZERO);
434        }
435
436        let (inputs_sum, missing_outputs) = tx.input.iter().fold(
437            (SignedAmount::ZERO, Vec::new()),
438            |(mut sum, mut missing_outpoints), txin| match self.get_txout(txin.previous_output) {
439                None => {
440                    missing_outpoints.push(txin.previous_output);
441                    (sum, missing_outpoints)
442                }
443                Some(txout) => {
444                    sum += txout.value.to_signed().expect("valid `SignedAmount`");
445                    (sum, missing_outpoints)
446                }
447            },
448        );
449        if !missing_outputs.is_empty() {
450            return Err(CalculateFeeError::MissingTxOut(missing_outputs));
451        }
452
453        let outputs_sum = tx
454            .output
455            .iter()
456            .map(|txout| txout.value.to_signed().expect("valid `SignedAmount`"))
457            .sum::<SignedAmount>();
458
459        let fee = inputs_sum - outputs_sum;
460        fee.to_unsigned()
461            .map_err(|_| CalculateFeeError::NegativeFee(fee))
462    }
463
464    /// The transactions spending from this output.
465    ///
466    /// [`TxGraph`] allows conflicting transactions within the graph. Obviously the transactions in
467    /// the returned set will never be in the same active-chain.
468    pub fn outspends(&self, outpoint: OutPoint) -> &HashSet<Txid> {
469        self.spends.get(&outpoint).unwrap_or(&self.empty_outspends)
470    }
471
472    /// Iterates over the transactions spending from `txid`.
473    ///
474    /// The iterator item is a union of `(vout, txid-set)` where:
475    ///
476    /// - `vout` is the provided `txid`'s outpoint that is being spent
477    /// - `txid-set` is the set of txids spending the `vout`.
478    pub fn tx_spends(
479        &self,
480        txid: Txid,
481    ) -> impl DoubleEndedIterator<Item = (u32, &HashSet<Txid>)> + '_ {
482        let start = OutPoint::new(txid, 0);
483        let end = OutPoint::new(txid, u32::MAX);
484        self.spends
485            .range(start..=end)
486            .map(|(outpoint, spends)| (outpoint.vout, spends))
487    }
488}
489
490impl<A: Clone + Ord> TxGraph<A> {
491    /// Creates an iterator that filters and maps ancestor transactions.
492    ///
493    /// The iterator starts with the ancestors of the supplied `tx` (ancestor transactions of `tx`
494    /// are transactions spent by `tx`). The supplied transaction is excluded from the iterator.
495    ///
496    /// The supplied closure takes in two inputs `(depth, ancestor_tx)`:
497    ///
498    /// * `depth` is the distance between the starting `Transaction` and the `ancestor_tx`. I.e., if
499    ///   the `Transaction` is spending an output of the `ancestor_tx` then `depth` will be 1.
500    /// * `ancestor_tx` is the `Transaction`'s ancestor which we are considering to walk.
501    ///
502    /// The supplied closure returns an `Option<T>`, allowing the caller to map each `Transaction`
503    /// it visits and decide whether to visit ancestors.
504    pub fn walk_ancestors<'g, T, F, O>(&'g self, tx: T, walk_map: F) -> TxAncestors<'g, A, F, O>
505    where
506        T: Into<Arc<Transaction>>,
507        F: FnMut(usize, Arc<Transaction>) -> Option<O> + 'g,
508    {
509        TxAncestors::new_exclude_root(self, tx, walk_map)
510    }
511
512    /// Creates an iterator that filters and maps descendants from the starting `txid`.
513    ///
514    /// The supplied closure takes in two inputs `(depth, descendant_txid)`:
515    ///
516    /// * `depth` is the distance between the starting `txid` and the `descendant_txid`. I.e., if
517    ///   the descendant is spending an output of the starting `txid` then `depth` will be 1.
518    /// * `descendant_txid` is the descendant's txid which we are considering to walk.
519    ///
520    /// The supplied closure returns an `Option<T>`, allowing the caller to map each node it visits
521    /// and decide whether to visit descendants.
522    pub fn walk_descendants<'g, F, O>(
523        &'g self,
524        txid: Txid,
525        walk_map: F,
526    ) -> TxDescendants<'g, A, F, O>
527    where
528        F: FnMut(usize, Txid) -> Option<O> + 'g,
529    {
530        TxDescendants::new_exclude_root(self, txid, walk_map)
531    }
532}
533
534impl<A> TxGraph<A> {
535    /// Creates an iterator that both filters and maps conflicting transactions (this includes
536    /// descendants of directly-conflicting transactions, which are also considered conflicts).
537    ///
538    /// Refer to [`Self::walk_descendants`] for `walk_map` usage.
539    pub fn walk_conflicts<'g, F, O>(
540        &'g self,
541        tx: &'g Transaction,
542        walk_map: F,
543    ) -> TxDescendants<'g, A, F, O>
544    where
545        F: FnMut(usize, Txid) -> Option<O> + 'g,
546    {
547        let txids = self.direct_conflicts(tx).map(|(_, txid)| txid);
548        TxDescendants::from_multiple_include_root(self, txids, walk_map)
549    }
550
551    /// Given a transaction, return an iterator of txids that directly conflict with the given
552    /// transaction's inputs (spends). The conflicting txids are returned with the given
553    /// transaction's vin (in which it conflicts).
554    ///
555    /// Note that this only returns directly conflicting txids and won't include:
556    /// - descendants of conflicting transactions (which are technically also conflicting)
557    /// - transactions conflicting with the given transaction's ancestors
558    pub fn direct_conflicts<'g>(
559        &'g self,
560        tx: &'g Transaction,
561    ) -> impl Iterator<Item = (usize, Txid)> + 'g {
562        let txid = tx.compute_txid();
563        tx.input
564            .iter()
565            .enumerate()
566            .filter_map(move |(vin, txin)| self.spends.get(&txin.previous_output).zip(Some(vin)))
567            .flat_map(|(spends, vin)| core::iter::repeat(vin).zip(spends.iter().cloned()))
568            .filter(move |(_, conflicting_txid)| *conflicting_txid != txid)
569    }
570
571    /// Get all transaction anchors known by [`TxGraph`].
572    pub fn all_anchors(&self) -> &HashMap<Txid, BTreeSet<A>> {
573        &self.anchors
574    }
575
576    /// Whether the graph has any transactions or outputs in it.
577    pub fn is_empty(&self) -> bool {
578        self.txs.is_empty()
579    }
580}
581
582impl<A: Anchor> TxGraph<A> {
583    /// Transform the [`TxGraph`] to have [`Anchor`]s of another type.
584    ///
585    /// This takes in a closure of signature `FnMut(A) -> A2` which is called for each [`Anchor`] to
586    /// transform it.
587    pub fn map_anchors<A2: Anchor, F>(self, f: F) -> TxGraph<A2>
588    where
589        F: FnMut(A) -> A2,
590    {
591        let mut new_graph = TxGraph::<A2>::default();
592        new_graph.apply_changeset(self.initial_changeset().map_anchors(f));
593        new_graph
594    }
595
596    /// Construct a new [`TxGraph`] from a list of transactions.
597    pub fn new(txs: impl IntoIterator<Item = Transaction>) -> Self {
598        let mut new = Self::default();
599        for tx in txs.into_iter() {
600            let _ = new.insert_tx(tx);
601        }
602        new
603    }
604
605    /// Inserts the given [`TxOut`] at [`OutPoint`].
606    ///
607    /// Inserting floating txouts are useful for determining fee/feerate of transactions we care
608    /// about.
609    ///
610    /// The [`ChangeSet`] result will be empty if the `outpoint` (or a full transaction containing
611    /// the `outpoint`) already existed in `self`.
612    ///
613    /// [`apply_changeset`]: Self::apply_changeset
614    pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<A> {
615        let mut changeset = ChangeSet::<A>::default();
616        let tx_node = self.txs.entry(outpoint.txid).or_default();
617        match tx_node {
618            TxNodeInternal::Whole(_) => {
619                // ignore this txout we have the full one already.
620                // NOTE: You might think putting a debug_assert! here to check the output being
621                // replaced was actually correct is a good idea but the tests have already been
622                // written assuming this never panics.
623            }
624            TxNodeInternal::Partial(partial_tx) => {
625                match partial_tx.insert(outpoint.vout, txout.clone()) {
626                    Some(old_txout) => {
627                        debug_assert_eq!(
628                            txout, old_txout,
629                            "txout of the same outpoint should never change"
630                        );
631                    }
632                    None => {
633                        changeset.txouts.insert(outpoint, txout);
634                    }
635                }
636            }
637        }
638        changeset
639    }
640
641    /// Insert the given transaction into [`TxGraph`].
642    ///
643    /// The [`ChangeSet`] returned will be empty if no changes are made to the graph.
644    ///
645    /// # Updating Existing Transactions
646    ///
647    /// An unsigned transaction can be inserted first and have it's witness fields updated with
648    /// further transaction insertions (given that the newly introduced transaction shares the same
649    /// txid as the original transaction).
650    ///
651    /// The witnesses of the newly introduced transaction will be merged with the witnesses of the
652    /// original transaction in a way where:
653    ///
654    /// * A non-empty witness has precedence over an empty witness.
655    /// * A smaller witness has precedence over a larger witness.
656    /// * If the witness sizes are the same, we prioritize the two witnesses with lexicographical
657    ///   order.
658    pub fn insert_tx<T: Into<Arc<Transaction>>>(&mut self, tx: T) -> ChangeSet<A> {
659        // This returns `Some` only if the merged tx is different to the `original_tx`.
660        fn _merge_tx_witnesses(
661            original_tx: &Arc<Transaction>,
662            other_tx: &Arc<Transaction>,
663        ) -> Option<Arc<Transaction>> {
664            debug_assert_eq!(
665                original_tx.input.len(),
666                other_tx.input.len(),
667                "tx input count must be the same"
668            );
669            let merged_input = Iterator::zip(original_tx.input.iter(), other_tx.input.iter())
670                .map(|(original_txin, other_txin)| {
671                    let original_key = core::cmp::Reverse((
672                        original_txin.witness.is_empty(),
673                        original_txin.witness.size(),
674                        &original_txin.witness,
675                    ));
676                    let other_key = core::cmp::Reverse((
677                        other_txin.witness.is_empty(),
678                        other_txin.witness.size(),
679                        &other_txin.witness,
680                    ));
681                    if original_key > other_key {
682                        original_txin.clone()
683                    } else {
684                        other_txin.clone()
685                    }
686                })
687                .collect::<Vec<_>>();
688            if merged_input == original_tx.input {
689                return None;
690            }
691            if merged_input == other_tx.input {
692                return Some(other_tx.clone());
693            }
694            Some(Arc::new(Transaction {
695                input: merged_input,
696                ..(**original_tx).clone()
697            }))
698        }
699
700        let tx: Arc<Transaction> = tx.into();
701        let txid = tx.compute_txid();
702        let mut changeset = ChangeSet::<A>::default();
703
704        let tx_node = self.txs.entry(txid).or_default();
705        match tx_node {
706            TxNodeInternal::Whole(existing_tx) => {
707                if existing_tx.as_ref() != tx.as_ref() {
708                    // Allowing updating witnesses of txs.
709                    if let Some(merged_tx) = _merge_tx_witnesses(existing_tx, &tx) {
710                        *existing_tx = merged_tx.clone();
711                        changeset.txs.insert(merged_tx);
712                    }
713                }
714            }
715            partial_tx => {
716                for txin in &tx.input {
717                    // this means the tx is coinbase so there is no previous output
718                    if txin.previous_output.is_null() {
719                        continue;
720                    }
721                    self.spends
722                        .entry(txin.previous_output)
723                        .or_default()
724                        .insert(txid);
725                }
726                *partial_tx = TxNodeInternal::Whole(tx.clone());
727                changeset.txs.insert(tx);
728            }
729        }
730
731        changeset
732    }
733
734    /// Batch insert unconfirmed transactions.
735    ///
736    /// Items of `txs` are tuples containing the transaction and a *last seen* timestamp. The
737    /// *last seen* communicates when the transaction is last seen in mempool which is used for
738    /// conflict-resolution (refer to [`TxGraph::insert_seen_at`] for details).
739    pub fn batch_insert_unconfirmed<T: Into<Arc<Transaction>>>(
740        &mut self,
741        txs: impl IntoIterator<Item = (T, u64)>,
742    ) -> ChangeSet<A> {
743        let mut changeset = ChangeSet::<A>::default();
744        for (tx, seen_at) in txs {
745            let tx: Arc<Transaction> = tx.into();
746            changeset.merge(self.insert_seen_at(tx.compute_txid(), seen_at));
747            changeset.merge(self.insert_tx(tx));
748        }
749        changeset
750    }
751
752    /// Inserts the given `anchor` into [`TxGraph`].
753    ///
754    /// The [`ChangeSet`] returned will be empty if graph already knows that `txid` exists in
755    /// `anchor`.
756    pub fn insert_anchor(&mut self, txid: Txid, anchor: A) -> ChangeSet<A> {
757        // These two variables are used to determine how to modify the `txid`'s entry in
758        // `txs_by_highest_conf_heights`.
759        // We want to remove `(old_top_h?, txid)` and insert `(new_top_h?, txid)`.
760        let mut old_top_h = None;
761        let mut new_top_h = anchor.confirmation_height_upper_bound();
762
763        let is_changed = match self.anchors.entry(txid) {
764            hash_map::Entry::Occupied(mut e) => {
765                old_top_h = e
766                    .get()
767                    .iter()
768                    .last()
769                    .map(Anchor::confirmation_height_upper_bound);
770                if let Some(old_top_h) = old_top_h {
771                    if old_top_h > new_top_h {
772                        new_top_h = old_top_h;
773                    }
774                }
775                let is_changed = e.get_mut().insert(anchor.clone());
776                is_changed
777            }
778            hash_map::Entry::Vacant(e) => {
779                e.insert(core::iter::once(anchor.clone()).collect());
780                true
781            }
782        };
783
784        let mut changeset = ChangeSet::<A>::default();
785        if is_changed {
786            let new_top_is_changed = match old_top_h {
787                None => true,
788                Some(old_top_h) if old_top_h != new_top_h => true,
789                _ => false,
790            };
791            if new_top_is_changed {
792                if let Some(prev_top_h) = old_top_h {
793                    self.txs_by_highest_conf_heights.remove(&(prev_top_h, txid));
794                }
795                self.txs_by_highest_conf_heights.insert((new_top_h, txid));
796            }
797            changeset.anchors.insert((anchor, txid));
798        }
799        changeset
800    }
801
802    /// Updates the first-seen and last-seen timestamps for a given `txid` in the [`TxGraph`].
803    ///
804    /// This method records the time a transaction was observed by updating both:
805    /// - the **first-seen** timestamp, which only changes if `seen_at` is earlier than the current
806    ///   value, and
807    /// - the **last-seen** timestamp, which only changes if `seen_at` is later than the current
808    ///   value.
809    ///
810    /// `seen_at` is a UNIX timestamp in seconds.
811    ///
812    /// Returns a [`ChangeSet`] representing any changes applied.
813    pub fn insert_seen_at(&mut self, txid: Txid, seen_at: u64) -> ChangeSet<A> {
814        let mut changeset_first_seen = self.update_first_seen(txid, seen_at);
815        let changeset_last_seen = self.update_last_seen(txid, seen_at);
816        changeset_first_seen.merge(changeset_last_seen);
817        changeset_first_seen
818    }
819
820    /// Updates `first_seen` given a new `seen_at`.
821    fn update_first_seen(&mut self, txid: Txid, seen_at: u64) -> ChangeSet<A> {
822        let is_changed = match self.first_seen.entry(txid) {
823            hash_map::Entry::Occupied(mut e) => {
824                let first_seen = e.get_mut();
825                let change = *first_seen > seen_at;
826                if change {
827                    *first_seen = seen_at;
828                }
829                change
830            }
831            hash_map::Entry::Vacant(e) => {
832                e.insert(seen_at);
833                true
834            }
835        };
836
837        let mut changeset = ChangeSet::<A>::default();
838        if is_changed {
839            changeset.first_seen.insert(txid, seen_at);
840        }
841        changeset
842    }
843
844    /// Updates `last_seen` given a new `seen_at`.
845    fn update_last_seen(&mut self, txid: Txid, seen_at: u64) -> ChangeSet<A> {
846        let mut old_last_seen = None;
847        let is_changed = match self.last_seen.entry(txid) {
848            hash_map::Entry::Occupied(mut e) => {
849                let last_seen = e.get_mut();
850                old_last_seen = Some(*last_seen);
851                let change = *last_seen < seen_at;
852                if change {
853                    *last_seen = seen_at;
854                }
855                change
856            }
857            hash_map::Entry::Vacant(e) => {
858                e.insert(seen_at);
859                true
860            }
861        };
862
863        let mut changeset = ChangeSet::<A>::default();
864        if is_changed {
865            if let Some(old_last_seen) = old_last_seen {
866                self.txs_by_last_seen.remove(&(old_last_seen, txid));
867            }
868            self.txs_by_last_seen.insert((seen_at, txid));
869            changeset.last_seen.insert(txid, seen_at);
870        }
871        changeset
872    }
873
874    /// Inserts the given `evicted_at` for `txid` into [`TxGraph`].
875    ///
876    /// The `evicted_at` timestamp represents the last known time when the transaction was observed
877    /// to be missing from the mempool. If `txid` was previously recorded with an earlier
878    /// `evicted_at` value, it is updated only if the new value is greater.
879    pub fn insert_evicted_at(&mut self, txid: Txid, evicted_at: u64) -> ChangeSet<A> {
880        let is_changed = match self.last_evicted.entry(txid) {
881            hash_map::Entry::Occupied(mut e) => {
882                let last_evicted = e.get_mut();
883                let change = *last_evicted < evicted_at;
884                if change {
885                    *last_evicted = evicted_at;
886                }
887                change
888            }
889            hash_map::Entry::Vacant(e) => {
890                e.insert(evicted_at);
891                true
892            }
893        };
894
895        let mut changeset = ChangeSet::<A>::default();
896        if is_changed {
897            changeset.last_evicted.insert(txid, evicted_at);
898        }
899        changeset
900    }
901
902    /// Batch inserts `(txid, evicted_at)` pairs into [`TxGraph`] for `txid`s that the graph is
903    /// tracking.
904    ///
905    /// The `evicted_at` timestamp represents the last known time when the transaction was observed
906    /// to be missing from the mempool. If `txid` was previously recorded with an earlier
907    /// `evicted_at` value, it is updated only if the new value is greater.
908    pub fn batch_insert_relevant_evicted_at(
909        &mut self,
910        evicted_ats: impl IntoIterator<Item = (Txid, u64)>,
911    ) -> ChangeSet<A> {
912        let mut changeset = ChangeSet::default();
913        for (txid, evicted_at) in evicted_ats {
914            // Only record evictions for transactions the graph is tracking.
915            if self.txs.contains_key(&txid) {
916                changeset.merge(self.insert_evicted_at(txid, evicted_at));
917            }
918        }
919        changeset
920    }
921
922    /// Extends this graph with the given `update`.
923    ///
924    /// The returned [`ChangeSet`] is the set difference between `update` and `self` (transactions
925    /// that exist in `update` but not in `self`).
926    pub fn apply_update(&mut self, update: TxUpdate<A>) -> ChangeSet<A> {
927        let mut changeset = ChangeSet::<A>::default();
928        for tx in update.txs {
929            changeset.merge(self.insert_tx(tx));
930        }
931        for (outpoint, txout) in update.txouts {
932            changeset.merge(self.insert_txout(outpoint, txout));
933        }
934        for (anchor, txid) in update.anchors {
935            changeset.merge(self.insert_anchor(txid, anchor));
936        }
937        for (txid, seen_at) in update.seen_ats {
938            changeset.merge(self.insert_seen_at(txid, seen_at));
939        }
940        for (txid, evicted_at) in update.evicted_ats {
941            changeset.merge(self.insert_evicted_at(txid, evicted_at));
942        }
943        changeset
944    }
945
946    /// Determines the [`ChangeSet`] between `self` and an empty [`TxGraph`].
947    pub fn initial_changeset(&self) -> ChangeSet<A> {
948        ChangeSet {
949            txs: self.full_txs().map(|tx_node| tx_node.tx).collect(),
950            txouts: self
951                .floating_txouts()
952                .map(|(op, txout)| (op, txout.clone()))
953                .collect(),
954            anchors: self
955                .anchors
956                .iter()
957                .flat_map(|(txid, anchors)| anchors.iter().map(|a| (a.clone(), *txid)))
958                .collect(),
959            first_seen: self.first_seen.iter().map(|(&k, &v)| (k, v)).collect(),
960            last_seen: self.last_seen.iter().map(|(&k, &v)| (k, v)).collect(),
961            last_evicted: self.last_evicted.iter().map(|(&k, &v)| (k, v)).collect(),
962        }
963    }
964
965    /// Applies [`ChangeSet`] to [`TxGraph`].
966    pub fn apply_changeset(&mut self, changeset: ChangeSet<A>) {
967        for tx in changeset.txs {
968            let _ = self.insert_tx(tx);
969        }
970        for (outpoint, txout) in changeset.txouts {
971            let _ = self.insert_txout(outpoint, txout);
972        }
973        for (anchor, txid) in changeset.anchors {
974            let _ = self.insert_anchor(txid, anchor);
975        }
976        for (txid, seen_at) in changeset.last_seen {
977            let _ = self.insert_seen_at(txid, seen_at);
978        }
979        for (txid, evicted_at) in changeset.last_evicted {
980            let _ = self.insert_evicted_at(txid, evicted_at);
981        }
982    }
983}
984
985impl<A: Anchor> TxGraph<A> {
986    /// List graph transactions that are in `chain` with `chain_tip`.
987    ///
988    /// Each transaction is represented as a [`CanonicalTx`] that contains where the transaction is
989    /// observed in-chain, and the [`TxNode`].
990    ///
991    /// # Error
992    ///
993    /// If the [`ChainOracle`] implementation (`chain`) fails, an error will be returned with the
994    /// returned item.
995    ///
996    /// If the [`ChainOracle`] is infallible, [`list_canonical_txs`] can be used instead.
997    ///
998    /// [`list_canonical_txs`]: Self::list_canonical_txs
999    pub fn try_list_canonical_txs<'a, C: ChainOracle + 'a>(
1000        &'a self,
1001        chain: &'a C,
1002        chain_tip: BlockId,
1003        params: CanonicalizationParams,
1004    ) -> impl Iterator<Item = Result<CanonicalTx<'a, Arc<Transaction>, A>, C::Error>> {
1005        fn find_direct_anchor<A: Anchor, C: ChainOracle>(
1006            tx_node: &TxNode<'_, Arc<Transaction>, A>,
1007            chain: &C,
1008            chain_tip: BlockId,
1009        ) -> Result<Option<A>, C::Error> {
1010            tx_node
1011                .anchors
1012                .iter()
1013                .find_map(|a| -> Option<Result<A, C::Error>> {
1014                    match chain.is_block_in_chain(a.anchor_block(), chain_tip) {
1015                        Ok(Some(true)) => Some(Ok(a.clone())),
1016                        Ok(Some(false)) | Ok(None) => None,
1017                        Err(err) => Some(Err(err)),
1018                    }
1019                })
1020                .transpose()
1021        }
1022        self.canonical_iter(chain, chain_tip, params)
1023            .flat_map(move |res| {
1024                res.map(|(txid, _, canonical_reason)| {
1025                    let tx_node = self.get_tx_node(txid).expect("must contain tx");
1026                    let chain_position = match canonical_reason {
1027                        CanonicalReason::Assumed { descendant } => match descendant {
1028                            Some(_) => match find_direct_anchor(&tx_node, chain, chain_tip)? {
1029                                Some(anchor) => ChainPosition::Confirmed {
1030                                    anchor,
1031                                    transitively: None,
1032                                },
1033                                None => ChainPosition::Unconfirmed {
1034                                    first_seen: tx_node.first_seen,
1035                                    last_seen: tx_node.last_seen,
1036                                },
1037                            },
1038                            None => ChainPosition::Unconfirmed {
1039                                first_seen: tx_node.first_seen,
1040                                last_seen: tx_node.last_seen,
1041                            },
1042                        },
1043                        CanonicalReason::Anchor { anchor, descendant } => match descendant {
1044                            Some(_) => match find_direct_anchor(&tx_node, chain, chain_tip)? {
1045                                Some(anchor) => ChainPosition::Confirmed {
1046                                    anchor,
1047                                    transitively: None,
1048                                },
1049                                None => ChainPosition::Confirmed {
1050                                    anchor,
1051                                    transitively: descendant,
1052                                },
1053                            },
1054                            None => ChainPosition::Confirmed {
1055                                anchor,
1056                                transitively: None,
1057                            },
1058                        },
1059                        CanonicalReason::ObservedIn { observed_in, .. } => match observed_in {
1060                            ObservedIn::Mempool(last_seen) => ChainPosition::Unconfirmed {
1061                                first_seen: tx_node.first_seen,
1062                                last_seen: Some(last_seen),
1063                            },
1064                            ObservedIn::Block(_) => ChainPosition::Unconfirmed {
1065                                first_seen: tx_node.first_seen,
1066                                last_seen: None,
1067                            },
1068                        },
1069                    };
1070                    Ok(CanonicalTx {
1071                        chain_position,
1072                        tx_node,
1073                    })
1074                })
1075            })
1076    }
1077
1078    /// List graph transactions that are in `chain` with `chain_tip`.
1079    ///
1080    /// This is the infallible version of [`try_list_canonical_txs`].
1081    ///
1082    /// [`try_list_canonical_txs`]: Self::try_list_canonical_txs
1083    pub fn list_canonical_txs<'a, C: ChainOracle<Error = Infallible> + 'a>(
1084        &'a self,
1085        chain: &'a C,
1086        chain_tip: BlockId,
1087        params: CanonicalizationParams,
1088    ) -> impl Iterator<Item = CanonicalTx<'a, Arc<Transaction>, A>> {
1089        self.try_list_canonical_txs(chain, chain_tip, params)
1090            .map(|res| res.expect("infallible"))
1091    }
1092
1093    /// Get a filtered list of outputs from the given `outpoints` that are in `chain` with
1094    /// `chain_tip`.
1095    ///
1096    /// `outpoints` is a list of outpoints we are interested in, coupled with an outpoint identifier
1097    /// (`OI`) for convenience. If `OI` is not necessary, the caller can use `()`, or
1098    /// [`Iterator::enumerate`] over a list of [`OutPoint`]s.
1099    ///
1100    /// Floating outputs (i.e., outputs for which we don't have the full transaction in the graph)
1101    /// are ignored.
1102    ///
1103    /// # Error
1104    ///
1105    /// An [`Iterator::Item`] can be an [`Err`] if the [`ChainOracle`] implementation (`chain`)
1106    /// fails.
1107    ///
1108    /// If the [`ChainOracle`] implementation is infallible, [`filter_chain_txouts`] can be used
1109    /// instead.
1110    ///
1111    /// [`filter_chain_txouts`]: Self::filter_chain_txouts
1112    pub fn try_filter_chain_txouts<'a, C: ChainOracle + 'a, OI: Clone + 'a>(
1113        &'a self,
1114        chain: &'a C,
1115        chain_tip: BlockId,
1116        params: CanonicalizationParams,
1117        outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
1118    ) -> Result<impl Iterator<Item = (OI, FullTxOut<A>)> + 'a, C::Error> {
1119        let mut canon_txs = HashMap::<Txid, CanonicalTx<Arc<Transaction>, A>>::new();
1120        let mut canon_spends = HashMap::<OutPoint, Txid>::new();
1121        for r in self.try_list_canonical_txs(chain, chain_tip, params) {
1122            let canonical_tx = r?;
1123            let txid = canonical_tx.tx_node.txid;
1124
1125            if !canonical_tx.tx_node.tx.is_coinbase() {
1126                for txin in &canonical_tx.tx_node.tx.input {
1127                    let _res = canon_spends.insert(txin.previous_output, txid);
1128                    assert!(_res.is_none(), "tried to replace {_res:?} with {txid:?}",);
1129                }
1130            }
1131            canon_txs.insert(txid, canonical_tx);
1132        }
1133        Ok(outpoints.into_iter().filter_map(move |(spk_i, outpoint)| {
1134            let canon_tx = canon_txs.get(&outpoint.txid)?;
1135            let txout = canon_tx
1136                .tx_node
1137                .tx
1138                .output
1139                .get(outpoint.vout as usize)
1140                .cloned()?;
1141            let chain_position = canon_tx.chain_position.clone();
1142            let spent_by = canon_spends.get(&outpoint).map(|spend_txid| {
1143                let spend_tx = canon_txs
1144                    .get(spend_txid)
1145                    .cloned()
1146                    .expect("must be canonical");
1147                (spend_tx.chain_position, *spend_txid)
1148            });
1149            let is_on_coinbase = canon_tx.tx_node.is_coinbase();
1150            Some((
1151                spk_i,
1152                FullTxOut {
1153                    outpoint,
1154                    txout,
1155                    chain_position,
1156                    spent_by,
1157                    is_on_coinbase,
1158                },
1159            ))
1160        }))
1161    }
1162
1163    /// List txids by descending anchor height order.
1164    ///
1165    /// If multiple anchors exist for a txid, the highest anchor height will be used. Transactions
1166    /// without anchors are excluded.
1167    pub fn txids_by_descending_anchor_height(
1168        &self,
1169    ) -> impl ExactSizeIterator<Item = (u32, Txid)> + '_ {
1170        self.txs_by_highest_conf_heights.iter().copied().rev()
1171    }
1172
1173    /// List txids by descending last-seen order.
1174    ///
1175    /// Transactions without last-seens are excluded. Transactions with a last-evicted timestamp
1176    /// equal or higher than it's last-seen timestamp are excluded.
1177    pub fn txids_by_descending_last_seen(&self) -> impl Iterator<Item = (u64, Txid)> + '_ {
1178        self.txs_by_last_seen
1179            .iter()
1180            .copied()
1181            .rev()
1182            .filter(|(last_seen, txid)| match self.last_evicted.get(txid) {
1183                Some(last_evicted) => last_evicted < last_seen,
1184                None => true,
1185            })
1186    }
1187
1188    /// Returns a [`CanonicalIter`].
1189    pub fn canonical_iter<'a, C: ChainOracle>(
1190        &'a self,
1191        chain: &'a C,
1192        chain_tip: BlockId,
1193        params: CanonicalizationParams,
1194    ) -> CanonicalIter<'a, A, C> {
1195        CanonicalIter::new(self, chain, chain_tip, params)
1196    }
1197
1198    /// Get a filtered list of outputs from the given `outpoints` that are in `chain` with
1199    /// `chain_tip`.
1200    ///
1201    /// This is the infallible version of [`try_filter_chain_txouts`].
1202    ///
1203    /// [`try_filter_chain_txouts`]: Self::try_filter_chain_txouts
1204    pub fn filter_chain_txouts<'a, C: ChainOracle<Error = Infallible> + 'a, OI: Clone + 'a>(
1205        &'a self,
1206        chain: &'a C,
1207        chain_tip: BlockId,
1208        params: CanonicalizationParams,
1209        outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
1210    ) -> impl Iterator<Item = (OI, FullTxOut<A>)> + 'a {
1211        self.try_filter_chain_txouts(chain, chain_tip, params, outpoints)
1212            .expect("oracle is infallible")
1213    }
1214
1215    /// Get a filtered list of unspent outputs (UTXOs) from the given `outpoints` that are in
1216    /// `chain` with `chain_tip`.
1217    ///
1218    /// `outpoints` is a list of outpoints we are interested in, coupled with an outpoint identifier
1219    /// (`OI`) for convenience. If `OI` is not necessary, the caller can use `()`, or
1220    /// [`Iterator::enumerate`] over a list of [`OutPoint`]s.
1221    ///
1222    /// Floating outputs are ignored.
1223    ///
1224    /// # Error
1225    ///
1226    /// An [`Iterator::Item`] can be an [`Err`] if the [`ChainOracle`] implementation (`chain`)
1227    /// fails.
1228    ///
1229    /// If the [`ChainOracle`] implementation is infallible, [`filter_chain_unspents`] can be used
1230    /// instead.
1231    ///
1232    /// [`filter_chain_unspents`]: Self::filter_chain_unspents
1233    pub fn try_filter_chain_unspents<'a, C: ChainOracle + 'a, OI: Clone + 'a>(
1234        &'a self,
1235        chain: &'a C,
1236        chain_tip: BlockId,
1237        params: CanonicalizationParams,
1238        outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
1239    ) -> Result<impl Iterator<Item = (OI, FullTxOut<A>)> + 'a, C::Error> {
1240        Ok(self
1241            .try_filter_chain_txouts(chain, chain_tip, params, outpoints)?
1242            .filter(|(_, full_txo)| full_txo.spent_by.is_none()))
1243    }
1244
1245    /// Get a filtered list of unspent outputs (UTXOs) from the given `outpoints` that are in
1246    /// `chain` with `chain_tip`.
1247    ///
1248    /// This is the infallible version of [`try_filter_chain_unspents`].
1249    ///
1250    /// [`try_filter_chain_unspents`]: Self::try_filter_chain_unspents
1251    pub fn filter_chain_unspents<'a, C: ChainOracle<Error = Infallible> + 'a, OI: Clone + 'a>(
1252        &'a self,
1253        chain: &'a C,
1254        chain_tip: BlockId,
1255        params: CanonicalizationParams,
1256        txouts: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
1257    ) -> impl Iterator<Item = (OI, FullTxOut<A>)> + 'a {
1258        self.try_filter_chain_unspents(chain, chain_tip, params, txouts)
1259            .expect("oracle is infallible")
1260    }
1261
1262    /// Get the total balance of `outpoints` that are in `chain` of `chain_tip`.
1263    ///
1264    /// The output of `trust_predicate` should return `true` for scripts that we trust.
1265    ///
1266    /// `outpoints` is a list of outpoints we are interested in, coupled with an outpoint identifier
1267    /// (`OI`) for convenience. If `OI` is not necessary, the caller can use `()`, or
1268    /// [`Iterator::enumerate`] over a list of [`OutPoint`]s.
1269    ///
1270    /// If the provided [`ChainOracle`] implementation (`chain`) is infallible, [`balance`] can be
1271    /// used instead.
1272    ///
1273    /// [`balance`]: Self::balance
1274    pub fn try_balance<C: ChainOracle, OI: Clone>(
1275        &self,
1276        chain: &C,
1277        chain_tip: BlockId,
1278        params: CanonicalizationParams,
1279        outpoints: impl IntoIterator<Item = (OI, OutPoint)>,
1280        mut trust_predicate: impl FnMut(&OI, ScriptBuf) -> bool,
1281    ) -> Result<Balance, C::Error> {
1282        let mut immature = Amount::ZERO;
1283        let mut trusted_pending = Amount::ZERO;
1284        let mut untrusted_pending = Amount::ZERO;
1285        let mut confirmed = Amount::ZERO;
1286
1287        for (spk_i, txout) in self.try_filter_chain_unspents(chain, chain_tip, params, outpoints)? {
1288            match &txout.chain_position {
1289                ChainPosition::Confirmed { .. } => {
1290                    if txout.is_confirmed_and_spendable(chain_tip.height) {
1291                        confirmed += txout.txout.value;
1292                    } else if !txout.is_mature(chain_tip.height) {
1293                        immature += txout.txout.value;
1294                    }
1295                }
1296                ChainPosition::Unconfirmed { .. } => {
1297                    if trust_predicate(&spk_i, txout.txout.script_pubkey) {
1298                        trusted_pending += txout.txout.value;
1299                    } else {
1300                        untrusted_pending += txout.txout.value;
1301                    }
1302                }
1303            }
1304        }
1305
1306        Ok(Balance {
1307            immature,
1308            trusted_pending,
1309            untrusted_pending,
1310            confirmed,
1311        })
1312    }
1313
1314    /// Get the total balance of `outpoints` that are in `chain` of `chain_tip`.
1315    ///
1316    /// This is the infallible version of [`try_balance`].
1317    ///
1318    /// ### Minimum confirmations
1319    ///
1320    /// To filter for transactions with at least `N` confirmations, pass a `chain_tip` that is
1321    /// `N - 1` blocks below the actual tip. This ensures that only transactions with at least `N`
1322    /// confirmations are counted as confirmed in the returned [`Balance`].
1323    ///
1324    /// ```
1325    /// # use bdk_chain::tx_graph::TxGraph;
1326    /// # use bdk_chain::{local_chain::LocalChain, CanonicalizationParams, ConfirmationBlockTime};
1327    /// # use bdk_testenv::{hash, utils::new_tx};
1328    /// # use bitcoin::{Amount, OutPoint, ScriptBuf, Transaction, TxIn, TxOut};
1329    ///
1330    /// # let spk = ScriptBuf::from_hex("0014c692ecf13534982a9a2834565cbd37add8027140").unwrap();
1331    /// # let chain =
1332    /// #     LocalChain::from_blocks((0..=15).map(|i| (i as u32, hash!("h"))).collect()).unwrap();
1333    /// # let mut graph: TxGraph = TxGraph::default();
1334    /// # let coinbase_tx = Transaction {
1335    /// #     input: vec![TxIn {
1336    /// #         previous_output: OutPoint::null(),
1337    /// #         ..Default::default()
1338    /// #     }],
1339    /// #     output: vec![TxOut {
1340    /// #         value: Amount::from_sat(70000),
1341    /// #         script_pubkey: spk.clone(),
1342    /// #     }],
1343    /// #     ..new_tx(0)
1344    /// # };
1345    /// # let tx = Transaction {
1346    /// #     input: vec![TxIn {
1347    /// #         previous_output: OutPoint::new(coinbase_tx.compute_txid(), 0),
1348    /// #         ..Default::default()
1349    /// #     }],
1350    /// #     output: vec![TxOut {
1351    /// #         value: Amount::from_sat(42_000),
1352    /// #         script_pubkey: spk.clone(),
1353    /// #     }],
1354    /// #     ..new_tx(1)
1355    /// # };
1356    /// # let txid = tx.compute_txid();
1357    /// # let _ = graph.insert_tx(tx.clone());
1358    /// # let _ = graph.insert_anchor(
1359    /// #     txid,
1360    /// #     ConfirmationBlockTime {
1361    /// #         block_id: chain.get(10).unwrap().block_id(),
1362    /// #         confirmation_time: 123456,
1363    /// #     },
1364    /// # );
1365    ///
1366    /// let minimum_confirmations = 6;
1367    /// let target_tip = chain
1368    ///     .tip()
1369    ///     .floor_below(minimum_confirmations - 1)
1370    ///     .expect("checkpoint from local chain must have genesis");
1371    /// let balance = graph.balance(
1372    ///     &chain,
1373    ///     target_tip.block_id(),
1374    ///     CanonicalizationParams::default(),
1375    ///     std::iter::once(((), OutPoint::new(txid, 0))),
1376    ///     |_: &(), _| true,
1377    /// );
1378    /// assert_eq!(balance.confirmed, Amount::from_sat(42_000));
1379    /// ```
1380    ///
1381    /// [`try_balance`]: Self::try_balance
1382    pub fn balance<C: ChainOracle<Error = Infallible>, OI: Clone>(
1383        &self,
1384        chain: &C,
1385        chain_tip: BlockId,
1386        params: CanonicalizationParams,
1387        outpoints: impl IntoIterator<Item = (OI, OutPoint)>,
1388        trust_predicate: impl FnMut(&OI, ScriptBuf) -> bool,
1389    ) -> Balance {
1390        self.try_balance(chain, chain_tip, params, outpoints, trust_predicate)
1391            .expect("oracle is infallible")
1392    }
1393
1394    /// List txids that are expected to exist under the given spks.
1395    ///
1396    /// This is used to fill
1397    /// [`SyncRequestBuilder::expected_spk_txids`](bdk_core::spk_client::SyncRequestBuilder::expected_spk_txids).
1398    ///
1399    ///
1400    /// The spk index range can be constrained with `range`.
1401    ///
1402    /// # Error
1403    ///
1404    /// If the [`ChainOracle`] implementation (`chain`) fails, an error will be returned with the
1405    /// returned item.
1406    ///
1407    /// If the [`ChainOracle`] is infallible,
1408    /// [`list_expected_spk_txids`](Self::list_expected_spk_txids) can be used instead.
1409    pub fn try_list_expected_spk_txids<'a, C, I>(
1410        &'a self,
1411        chain: &'a C,
1412        chain_tip: BlockId,
1413        indexer: &'a impl AsRef<SpkTxOutIndex<I>>,
1414        spk_index_range: impl RangeBounds<I> + 'a,
1415    ) -> impl Iterator<Item = Result<(ScriptBuf, Txid), C::Error>> + 'a
1416    where
1417        C: ChainOracle,
1418        I: fmt::Debug + Clone + Ord + 'a,
1419    {
1420        let indexer = indexer.as_ref();
1421        self.try_list_canonical_txs(chain, chain_tip, CanonicalizationParams::default())
1422            .flat_map(move |res| -> Vec<Result<(ScriptBuf, Txid), C::Error>> {
1423                let range = &spk_index_range;
1424                let c_tx = match res {
1425                    Ok(c_tx) => c_tx,
1426                    Err(err) => return vec![Err(err)],
1427                };
1428                let relevant_spks = indexer.relevant_spks_of_tx(&c_tx.tx_node);
1429                relevant_spks
1430                    .into_iter()
1431                    .filter(|(i, _)| range.contains(i))
1432                    .map(|(_, spk)| Ok((spk, c_tx.tx_node.txid)))
1433                    .collect()
1434            })
1435    }
1436
1437    /// List txids that are expected to exist under the given spks.
1438    ///
1439    /// This is the infallible version of
1440    /// [`try_list_expected_spk_txids`](Self::try_list_expected_spk_txids).
1441    pub fn list_expected_spk_txids<'a, C, I>(
1442        &'a self,
1443        chain: &'a C,
1444        chain_tip: BlockId,
1445        indexer: &'a impl AsRef<SpkTxOutIndex<I>>,
1446        spk_index_range: impl RangeBounds<I> + 'a,
1447    ) -> impl Iterator<Item = (ScriptBuf, Txid)> + 'a
1448    where
1449        C: ChainOracle<Error = Infallible>,
1450        I: fmt::Debug + Clone + Ord + 'a,
1451    {
1452        self.try_list_expected_spk_txids(chain, chain_tip, indexer, spk_index_range)
1453            .map(|r| r.expect("infallible"))
1454    }
1455
1456    /// Construct a `TxGraph` from a `changeset`.
1457    pub fn from_changeset(changeset: ChangeSet<A>) -> Self {
1458        let mut graph = Self::default();
1459        graph.apply_changeset(changeset);
1460        graph
1461    }
1462}
1463
1464/// The [`ChangeSet`] represents changes to a [`TxGraph`].
1465///
1466/// Since [`TxGraph`] is monotone, the "changeset" can only contain transactions to be added and
1467/// not removed.
1468///
1469/// Refer to [module-level documentation] for more.
1470///
1471/// [module-level documentation]: crate::tx_graph
1472#[derive(Debug, Clone, PartialEq)]
1473#[cfg_attr(
1474    feature = "serde",
1475    derive(serde::Deserialize, serde::Serialize),
1476    serde(bound(
1477        deserialize = "A: Ord + serde::Deserialize<'de>",
1478        serialize = "A: Ord + serde::Serialize",
1479    ))
1480)]
1481#[must_use]
1482pub struct ChangeSet<A = ()> {
1483    /// Added transactions.
1484    pub txs: BTreeSet<Arc<Transaction>>,
1485    /// Added txouts.
1486    pub txouts: BTreeMap<OutPoint, TxOut>,
1487    /// Added anchors.
1488    pub anchors: BTreeSet<(A, Txid)>,
1489    /// Added last-seen unix timestamps of transactions.
1490    pub last_seen: BTreeMap<Txid, u64>,
1491    /// Added timestamps of when a transaction is last evicted from the mempool.
1492    #[cfg_attr(feature = "serde", serde(default))]
1493    pub last_evicted: BTreeMap<Txid, u64>,
1494    /// Added first-seen unix timestamps of transactions.
1495    #[cfg_attr(feature = "serde", serde(default))]
1496    pub first_seen: BTreeMap<Txid, u64>,
1497}
1498
1499impl<A> Default for ChangeSet<A> {
1500    fn default() -> Self {
1501        Self {
1502            txs: Default::default(),
1503            txouts: Default::default(),
1504            anchors: Default::default(),
1505            first_seen: Default::default(),
1506            last_seen: Default::default(),
1507            last_evicted: Default::default(),
1508        }
1509    }
1510}
1511
1512impl<A> ChangeSet<A> {
1513    /// Iterates over all outpoints contained within [`ChangeSet`].
1514    pub fn txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
1515        self.txs
1516            .iter()
1517            .flat_map(|tx| {
1518                tx.output
1519                    .iter()
1520                    .enumerate()
1521                    .map(move |(vout, txout)| (OutPoint::new(tx.compute_txid(), vout as _), txout))
1522            })
1523            .chain(self.txouts.iter().map(|(op, txout)| (*op, txout)))
1524    }
1525
1526    /// Iterates over the heights of that the new transaction anchors in this changeset.
1527    ///
1528    /// This is useful if you want to find which heights you need to fetch data about in order to
1529    /// confirm or exclude these anchors.
1530    pub fn anchor_heights(&self) -> impl Iterator<Item = u32> + '_
1531    where
1532        A: Anchor,
1533    {
1534        let mut dedup = None;
1535        self.anchors
1536            .iter()
1537            .map(|(a, _)| a.anchor_block().height)
1538            .filter(move |height| {
1539                let duplicate = dedup == Some(*height);
1540                dedup = Some(*height);
1541                !duplicate
1542            })
1543    }
1544}
1545
1546impl<A: Ord> Merge for ChangeSet<A> {
1547    fn merge(&mut self, other: Self) {
1548        // We use `extend` instead of `BTreeMap::append` due to performance issues with `append`.
1549        // Refer to https://github.com/rust-lang/rust/issues/34666#issuecomment-675658420
1550        self.txs.extend(other.txs);
1551        self.txouts.extend(other.txouts);
1552        self.anchors.extend(other.anchors);
1553
1554        // first_seen timestamps should only decrease
1555        self.first_seen.extend(
1556            other
1557                .first_seen
1558                .into_iter()
1559                .filter(|(txid, update_fs)| match self.first_seen.get(txid) {
1560                    Some(existing) => update_fs < existing,
1561                    None => true,
1562                })
1563                .collect::<Vec<_>>(),
1564        );
1565
1566        // last_seen timestamps should only increase
1567        self.last_seen.extend(
1568            other
1569                .last_seen
1570                .into_iter()
1571                .filter(|(txid, update_ls)| self.last_seen.get(txid) < Some(update_ls))
1572                .collect::<Vec<_>>(),
1573        );
1574        // last_evicted timestamps should only increase
1575        self.last_evicted.extend(
1576            other
1577                .last_evicted
1578                .into_iter()
1579                .filter(|(txid, update_lm)| self.last_evicted.get(txid) < Some(update_lm))
1580                .collect::<Vec<_>>(),
1581        );
1582    }
1583
1584    fn is_empty(&self) -> bool {
1585        self.txs.is_empty()
1586            && self.txouts.is_empty()
1587            && self.anchors.is_empty()
1588            && self.first_seen.is_empty()
1589            && self.last_seen.is_empty()
1590            && self.last_evicted.is_empty()
1591    }
1592}
1593
1594impl<A: Ord> ChangeSet<A> {
1595    /// Transform the [`ChangeSet`] to have [`Anchor`]s of another type.
1596    ///
1597    /// This takes in a closure of signature `FnMut(A) -> A2` which is called for each [`Anchor`] to
1598    /// transform it.
1599    pub fn map_anchors<A2: Ord, F>(self, mut f: F) -> ChangeSet<A2>
1600    where
1601        F: FnMut(A) -> A2,
1602    {
1603        ChangeSet {
1604            txs: self.txs,
1605            txouts: self.txouts,
1606            anchors: BTreeSet::<(A2, Txid)>::from_iter(
1607                self.anchors.into_iter().map(|(a, txid)| (f(a), txid)),
1608            ),
1609            first_seen: self.first_seen,
1610            last_seen: self.last_seen,
1611            last_evicted: self.last_evicted,
1612        }
1613    }
1614}
1615
1616impl<A> AsRef<TxGraph<A>> for TxGraph<A> {
1617    fn as_ref(&self) -> &TxGraph<A> {
1618        self
1619    }
1620}
1621
1622/// An iterator that traverses ancestors of a given root transaction.
1623///
1624/// The iterator excludes partial transactions.
1625///
1626/// Returned by the [`walk_ancestors`] method of [`TxGraph`].
1627///
1628/// [`walk_ancestors`]: TxGraph::walk_ancestors
1629pub struct TxAncestors<'g, A, F, O>
1630where
1631    F: FnMut(usize, Arc<Transaction>) -> Option<O>,
1632{
1633    graph: &'g TxGraph<A>,
1634    visited: HashSet<Txid>,
1635    queue: VecDeque<(usize, Arc<Transaction>)>,
1636    filter_map: F,
1637}
1638
1639impl<'g, A, F, O> TxAncestors<'g, A, F, O>
1640where
1641    F: FnMut(usize, Arc<Transaction>) -> Option<O>,
1642{
1643    /// Creates a `TxAncestors` that includes the starting `Transaction` when iterating.
1644    pub(crate) fn new_include_root(
1645        graph: &'g TxGraph<A>,
1646        tx: impl Into<Arc<Transaction>>,
1647        filter_map: F,
1648    ) -> Self {
1649        Self {
1650            graph,
1651            visited: Default::default(),
1652            queue: [(0, tx.into())].into(),
1653            filter_map,
1654        }
1655    }
1656
1657    /// Creates a `TxAncestors` that excludes the starting `Transaction` when iterating.
1658    pub(crate) fn new_exclude_root(
1659        graph: &'g TxGraph<A>,
1660        tx: impl Into<Arc<Transaction>>,
1661        filter_map: F,
1662    ) -> Self {
1663        let mut ancestors = Self {
1664            graph,
1665            visited: Default::default(),
1666            queue: Default::default(),
1667            filter_map,
1668        };
1669        ancestors.populate_queue(1, tx.into());
1670        ancestors
1671    }
1672
1673    /// Creates a `TxAncestors` from multiple starting `Transaction`s that includes the starting
1674    /// `Transaction`s when iterating.
1675    #[allow(unused)]
1676    pub(crate) fn from_multiple_include_root<I>(
1677        graph: &'g TxGraph<A>,
1678        txs: I,
1679        filter_map: F,
1680    ) -> Self
1681    where
1682        I: IntoIterator,
1683        I::Item: Into<Arc<Transaction>>,
1684    {
1685        Self {
1686            graph,
1687            visited: Default::default(),
1688            queue: txs.into_iter().map(|tx| (0, tx.into())).collect(),
1689            filter_map,
1690        }
1691    }
1692
1693    /// Creates a `TxAncestors` from multiple starting `Transaction`s that excludes the starting
1694    /// `Transaction`s when iterating.
1695    #[allow(unused)]
1696    pub(crate) fn from_multiple_exclude_root<I>(
1697        graph: &'g TxGraph<A>,
1698        txs: I,
1699        filter_map: F,
1700    ) -> Self
1701    where
1702        I: IntoIterator,
1703        I::Item: Into<Arc<Transaction>>,
1704    {
1705        let mut ancestors = Self {
1706            graph,
1707            visited: Default::default(),
1708            queue: Default::default(),
1709            filter_map,
1710        };
1711        for tx in txs {
1712            ancestors.populate_queue(1, tx.into());
1713        }
1714        ancestors
1715    }
1716
1717    /// Traverse all ancestors that are not filtered out by the provided closure.
1718    pub fn run_until_finished(self) {
1719        self.for_each(|_| {})
1720    }
1721
1722    fn populate_queue(&mut self, depth: usize, tx: Arc<Transaction>) {
1723        let ancestors = tx
1724            .input
1725            .iter()
1726            .map(|txin| txin.previous_output.txid)
1727            .filter(|&prev_txid| self.visited.insert(prev_txid))
1728            .filter_map(|prev_txid| self.graph.get_tx(prev_txid))
1729            .map(|tx| (depth, tx));
1730        self.queue.extend(ancestors);
1731    }
1732}
1733
1734impl<A, F, O> Iterator for TxAncestors<'_, A, F, O>
1735where
1736    F: FnMut(usize, Arc<Transaction>) -> Option<O>,
1737{
1738    type Item = O;
1739
1740    fn next(&mut self) -> Option<Self::Item> {
1741        loop {
1742            // we have exhausted all paths when queue is empty
1743            let (ancestor_depth, tx) = self.queue.pop_front()?;
1744            // ignore paths when user filters them out
1745            let item = match (self.filter_map)(ancestor_depth, tx.clone()) {
1746                Some(item) => item,
1747                None => continue,
1748            };
1749            self.populate_queue(ancestor_depth + 1, tx);
1750            return Some(item);
1751        }
1752    }
1753}
1754
1755/// An iterator that traverses transaction descendants.
1756///
1757/// Returned by the [`walk_descendants`] method of [`TxGraph`].
1758///
1759/// [`walk_descendants`]: TxGraph::walk_descendants
1760pub struct TxDescendants<'g, A, F, O>
1761where
1762    F: FnMut(usize, Txid) -> Option<O>,
1763{
1764    graph: &'g TxGraph<A>,
1765    visited: HashSet<Txid>,
1766    queue: VecDeque<(usize, Txid)>,
1767    filter_map: F,
1768}
1769
1770impl<'g, A, F, O> TxDescendants<'g, A, F, O>
1771where
1772    F: FnMut(usize, Txid) -> Option<O>,
1773{
1774    /// Creates a `TxDescendants` that includes the starting `txid` when iterating.
1775    #[allow(unused)]
1776    pub(crate) fn new_include_root(graph: &'g TxGraph<A>, txid: Txid, filter_map: F) -> Self {
1777        Self {
1778            graph,
1779            visited: Default::default(),
1780            queue: [(0, txid)].into(),
1781            filter_map,
1782        }
1783    }
1784
1785    /// Creates a `TxDescendants` that excludes the starting `txid` when iterating.
1786    pub(crate) fn new_exclude_root(graph: &'g TxGraph<A>, txid: Txid, filter_map: F) -> Self {
1787        let mut descendants = Self {
1788            graph,
1789            visited: Default::default(),
1790            queue: Default::default(),
1791            filter_map,
1792        };
1793        descendants.populate_queue(1, txid);
1794        descendants
1795    }
1796
1797    /// Creates a `TxDescendants` from multiple starting transactions that includes the starting
1798    /// `txid`s when iterating.
1799    pub(crate) fn from_multiple_include_root<I>(
1800        graph: &'g TxGraph<A>,
1801        txids: I,
1802        filter_map: F,
1803    ) -> Self
1804    where
1805        I: IntoIterator<Item = Txid>,
1806    {
1807        Self {
1808            graph,
1809            visited: Default::default(),
1810            queue: txids.into_iter().map(|txid| (0, txid)).collect(),
1811            filter_map,
1812        }
1813    }
1814
1815    /// Creates a `TxDescendants` from multiple starting transactions that excludes the starting
1816    /// `txid`s when iterating.
1817    #[allow(unused)]
1818    pub(crate) fn from_multiple_exclude_root<I>(
1819        graph: &'g TxGraph<A>,
1820        txids: I,
1821        filter_map: F,
1822    ) -> Self
1823    where
1824        I: IntoIterator<Item = Txid>,
1825    {
1826        let mut descendants = Self {
1827            graph,
1828            visited: Default::default(),
1829            queue: Default::default(),
1830            filter_map,
1831        };
1832        for txid in txids {
1833            descendants.populate_queue(1, txid);
1834        }
1835        descendants
1836    }
1837
1838    /// Traverse all descendants that are not filtered out by the provided closure.
1839    pub fn run_until_finished(self) {
1840        self.for_each(|_| {})
1841    }
1842
1843    fn populate_queue(&mut self, depth: usize, txid: Txid) {
1844        let spend_paths = self
1845            .graph
1846            .spends
1847            .range(tx_outpoint_range(txid))
1848            .flat_map(|(_, spends)| spends)
1849            .map(|&txid| (depth, txid));
1850        self.queue.extend(spend_paths);
1851    }
1852}
1853
1854impl<A, F, O> Iterator for TxDescendants<'_, A, F, O>
1855where
1856    F: FnMut(usize, Txid) -> Option<O>,
1857{
1858    type Item = O;
1859
1860    fn next(&mut self) -> Option<Self::Item> {
1861        let (op_spends, txid, item) = loop {
1862            // we have exhausted all paths when queue is empty
1863            let (op_spends, txid) = self.queue.pop_front()?;
1864            // we do not want to visit the same transaction twice
1865            if self.visited.insert(txid) {
1866                // ignore paths when user filters them out
1867                if let Some(item) = (self.filter_map)(op_spends, txid) {
1868                    break (op_spends, txid, item);
1869                }
1870            }
1871        };
1872
1873        self.populate_queue(op_spends + 1, txid);
1874        Some(item)
1875    }
1876}
1877
1878fn tx_outpoint_range(txid: Txid) -> RangeInclusive<OutPoint> {
1879    OutPoint::new(txid, u32::MIN)..=OutPoint::new(txid, u32::MAX)
1880}