• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bitcoindevkit / bdk / 9475497656

12 Jun 2024 02:00AM UTC coverage: 82.974% (-0.4%) from 83.405%
9475497656

Pull #1468

github

web-flow
Merge 44e446faa into 473ef9714
Pull Request #1468: wip(feat): use `Weight` type instead of `usize`

112 of 190 new or added lines in 19 files covered. (58.95%)

13 existing lines in 3 files now uncovered.

11253 of 13562 relevant lines covered (82.97%)

16764.07 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.46
/crates/chain/src/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
//! Conflicting transactions are allowed to coexist within a [`TxGraph`]. This is useful for
20
//! identifying and traversing conflicts and descendants of a given transaction. Some [`TxGraph`]
21
//! methods only consider transactions that are "canonical" (i.e., in the best chain or in mempool).
22
//! We decide which transactions are canonical based on the transaction's anchors and the
23
//! `last_seen` (as unconfirmed) timestamp; see the [`try_get_chain_position`] documentation for
24
//! more details.
25
//!
26
//! The [`ChangeSet`] reports changes made to a [`TxGraph`]; it can be used to either save to
27
//! persistent storage, or to be applied to another [`TxGraph`].
28
//!
29
//! Lastly, you can use [`TxAncestors`]/[`TxDescendants`] to traverse ancestors and descendants of
30
//! a given transaction, respectively.
31
//!
32
//! # Applying changes
33
//!
34
//! Methods that change the state of [`TxGraph`] will return [`ChangeSet`]s.
35
//! [`ChangeSet`]s can be applied back to a [`TxGraph`] or be used to inform persistent storage
36
//! of the changes to [`TxGraph`].
37
//!
38
//! # Generics
39
//!
40
//! Anchors are represented as generics within `TxGraph<A>`. To make use of all functionality of the
41
//! `TxGraph`, anchors (`A`) should implement [`Anchor`].
42
//!
43
//! Anchors are made generic so that different types of data can be stored with how a transaction is
44
//! *anchored* to a given block. An example of this is storing a merkle proof of the transaction to
45
//! the confirmation block - this can be done with a custom [`Anchor`] type. The minimal [`Anchor`]
46
//! type would just be a [`BlockId`] which just represents the height and hash of the block which
47
//! the transaction is contained in. Note that a transaction can be contained in multiple
48
//! conflicting blocks (by nature of the Bitcoin network).
49
//!
50
//! ```
51
//! # use bdk_chain::BlockId;
52
//! # use bdk_chain::tx_graph::TxGraph;
53
//! # use bdk_chain::example_utils::*;
54
//! # use bitcoin::Transaction;
55
//! # let tx_a = tx_from_hex(RAW_TX_1);
56
//! let mut tx_graph: TxGraph = TxGraph::default();
57
//!
58
//! // insert a transaction
59
//! let changeset = tx_graph.insert_tx(tx_a);
60
//!
61
//! // We can restore the state of the `tx_graph` by applying all
62
//! // the changesets obtained by mutating the original (the order doesn't matter).
63
//! let mut restored_tx_graph: TxGraph = TxGraph::default();
64
//! restored_tx_graph.apply_changeset(changeset);
65
//!
66
//! assert_eq!(tx_graph, restored_tx_graph);
67
//! ```
68
//!
69
//! A [`TxGraph`] can also be updated with another [`TxGraph`] which merges them together.
70
//!
71
//! ```
72
//! # use bdk_chain::{Append, BlockId};
73
//! # use bdk_chain::tx_graph::TxGraph;
74
//! # use bdk_chain::example_utils::*;
75
//! # use bitcoin::Transaction;
76
//! # let tx_a = tx_from_hex(RAW_TX_1);
77
//! # let tx_b = tx_from_hex(RAW_TX_2);
78
//! let mut graph: TxGraph = TxGraph::default();
79
//! let update = TxGraph::new(vec![tx_a, tx_b]);
80
//!
81
//! // apply the update graph
82
//! let changeset = graph.apply_update(update.clone());
83
//!
84
//! // if we apply it again, the resulting changeset will be empty
85
//! let changeset = graph.apply_update(update);
86
//! assert!(changeset.is_empty());
87
//! ```
88
//! [`try_get_chain_position`]: TxGraph::try_get_chain_position
89
//! [`insert_txout`]: TxGraph::insert_txout
90

91
use crate::{
92
    collections::*, keychain::Balance, Anchor, Append, BlockId, ChainOracle, ChainPosition,
93
    FullTxOut,
94
};
95
use alloc::collections::vec_deque::VecDeque;
96
use alloc::sync::Arc;
97
use alloc::vec::Vec;
98
use bitcoin::{Amount, OutPoint, Script, SignedAmount, Transaction, TxOut, Txid};
99
use core::fmt::{self, Formatter};
100
use core::{
101
    convert::Infallible,
102
    ops::{Deref, RangeInclusive},
103
};
104

105
/// A graph of transactions and spends.
106
///
107
/// See the [module-level documentation] for more.
108
///
109
/// [module-level documentation]: crate::tx_graph
110
#[derive(Clone, Debug, PartialEq)]
111
pub struct TxGraph<A = ()> {
112
    // all transactions that the graph is aware of in format: `(tx_node, tx_anchors, tx_last_seen)`
113
    txs: HashMap<Txid, (TxNodeInternal, BTreeSet<A>, u64)>,
114
    spends: BTreeMap<OutPoint, HashSet<Txid>>,
115
    anchors: BTreeSet<(A, Txid)>,
116

117
    // This atrocity exists so that `TxGraph::outspends()` can return a reference.
118
    // FIXME: This can be removed once `HashSet::new` is a const fn.
119
    empty_outspends: HashSet<Txid>,
120
}
121

122
impl<A> Default for TxGraph<A> {
123
    fn default() -> Self {
8,455✔
124
        Self {
8,455✔
125
            txs: Default::default(),
8,455✔
126
            spends: Default::default(),
8,455✔
127
            anchors: Default::default(),
8,455✔
128
            empty_outspends: Default::default(),
8,455✔
129
        }
8,455✔
130
    }
8,455✔
131
}
132

133
/// A transaction node in the [`TxGraph`].
134
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
135
pub struct TxNode<'a, T, A> {
136
    /// Txid of the transaction.
137
    pub txid: Txid,
138
    /// A partial or full representation of the transaction.
139
    pub tx: T,
140
    /// The blocks that the transaction is "anchored" in.
141
    pub anchors: &'a BTreeSet<A>,
142
    /// The last-seen unix timestamp of the transaction as unconfirmed.
143
    pub last_seen_unconfirmed: u64,
144
}
145

146
impl<'a, T, A> Deref for TxNode<'a, T, A> {
147
    type Target = T;
148

149
    fn deref(&self) -> &Self::Target {
38✔
150
        &self.tx
38✔
151
    }
38✔
152
}
153

154
/// Internal representation of a transaction node of a [`TxGraph`].
155
///
156
/// This can either be a whole transaction, or a partial transaction (where we only have select
157
/// outputs).
158
#[derive(Clone, Debug, PartialEq)]
159
enum TxNodeInternal {
160
    Whole(Arc<Transaction>),
161
    Partial(BTreeMap<u32, TxOut>),
162
}
163

164
impl Default for TxNodeInternal {
165
    fn default() -> Self {
6,006✔
166
        Self::Partial(BTreeMap::new())
6,006✔
167
    }
6,006✔
168
}
169

170
/// A transaction that is included in the chain, or is still in mempool.
171
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
172
pub struct CanonicalTx<'a, T, A> {
173
    /// How the transaction is observed as (confirmed or unconfirmed).
174
    pub chain_position: ChainPosition<&'a A>,
175
    /// The transaction node (as part of the graph).
176
    pub tx_node: TxNode<'a, T, A>,
177
}
178

179
/// Errors returned by `TxGraph::calculate_fee`.
180
#[derive(Debug, PartialEq, Eq)]
181
pub enum CalculateFeeError {
182
    /// Missing `TxOut` for one or more of the inputs of the tx
183
    MissingTxOut(Vec<OutPoint>),
184
    /// When the transaction is invalid according to the graph it has a negative fee
185
    NegativeFee(SignedAmount),
186
}
187

188
impl fmt::Display for CalculateFeeError {
189
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
×
190
        match self {
×
191
            CalculateFeeError::MissingTxOut(outpoints) => write!(
×
192
                f,
×
193
                "missing `TxOut` for one or more of the inputs of the tx: {:?}",
×
194
                outpoints
×
195
            ),
×
196
            CalculateFeeError::NegativeFee(fee) => write!(
×
197
                f,
×
198
                "transaction is invalid according to the graph and has negative fee: {}",
×
199
                fee.display_dynamic()
×
200
            ),
×
201
        }
202
    }
×
203
}
204

205
#[cfg(feature = "std")]
206
impl std::error::Error for CalculateFeeError {}
207

208
impl<A> TxGraph<A> {
209
    /// Iterate over all tx outputs known by [`TxGraph`].
210
    ///
211
    /// This includes txouts of both full transactions as well as floating transactions.
212
    pub fn all_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
1✔
213
        self.txs.iter().flat_map(|(txid, (tx, _, _))| match tx {
3✔
214
            TxNodeInternal::Whole(tx) => tx
1✔
215
                .as_ref()
1✔
216
                .output
1✔
217
                .iter()
1✔
218
                .enumerate()
1✔
219
                .map(|(vout, txout)| (OutPoint::new(*txid, vout as _), txout))
1✔
220
                .collect::<Vec<_>>(),
1✔
221
            TxNodeInternal::Partial(txouts) => txouts
2✔
222
                .iter()
2✔
223
                .map(|(vout, txout)| (OutPoint::new(*txid, *vout as _), txout))
3✔
224
                .collect::<Vec<_>>(),
2✔
225
        })
3✔
226
    }
1✔
227

228
    /// Iterate over floating txouts known by [`TxGraph`].
229
    ///
230
    /// Floating txouts are txouts that do not have the residing full transaction contained in the
231
    /// graph.
232
    pub fn floating_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
1✔
233
        self.txs
1✔
234
            .iter()
1✔
235
            .filter_map(|(txid, (tx_node, _, _))| match tx_node {
3✔
236
                TxNodeInternal::Whole(_) => None,
1✔
237
                TxNodeInternal::Partial(txouts) => Some(
2✔
238
                    txouts
2✔
239
                        .iter()
2✔
240
                        .map(|(&vout, txout)| (OutPoint::new(*txid, vout), txout)),
3✔
241
                ),
2✔
242
            })
3✔
243
            .flatten()
1✔
244
    }
1✔
245

246
    /// Iterate over all full transactions in the graph.
247
    pub fn full_txs(&self) -> impl Iterator<Item = TxNode<'_, Arc<Transaction>, A>> {
70✔
248
        self.txs
70✔
249
            .iter()
70✔
250
            .filter_map(|(&txid, (tx, anchors, last_seen))| match tx {
149✔
251
                TxNodeInternal::Whole(tx) => Some(TxNode {
131✔
252
                    txid,
131✔
253
                    tx: tx.clone(),
131✔
254
                    anchors,
131✔
255
                    last_seen_unconfirmed: *last_seen,
131✔
256
                }),
131✔
257
                TxNodeInternal::Partial(_) => None,
18✔
258
            })
149✔
259
    }
70✔
260

261
    /// Get a transaction by txid. This only returns `Some` for full transactions.
262
    ///
263
    /// Refer to [`get_txout`] for getting a specific [`TxOut`].
264
    ///
265
    /// [`get_txout`]: Self::get_txout
266
    pub fn get_tx(&self, txid: Txid) -> Option<Arc<Transaction>> {
3,679✔
267
        self.get_tx_node(txid).map(|n| n.tx)
3,679✔
268
    }
3,679✔
269

270
    /// Get a transaction node by txid. This only returns `Some` for full transactions.
271
    pub fn get_tx_node(&self, txid: Txid) -> Option<TxNode<'_, Arc<Transaction>, A>> {
10,303✔
272
        match &self.txs.get(&txid)? {
10,303✔
273
            (TxNodeInternal::Whole(tx), anchors, last_seen) => Some(TxNode {
10,114✔
274
                txid,
10,114✔
275
                tx: tx.clone(),
10,114✔
276
                anchors,
10,114✔
277
                last_seen_unconfirmed: *last_seen,
10,114✔
278
            }),
10,114✔
279
            _ => None,
×
280
        }
281
    }
10,303✔
282

283
    /// Obtains a single tx output (if any) at the specified outpoint.
284
    pub fn get_txout(&self, outpoint: OutPoint) -> Option<&TxOut> {
641✔
285
        match &self.txs.get(&outpoint.txid)?.0 {
641✔
286
            TxNodeInternal::Whole(tx) => tx.as_ref().output.get(outpoint.vout as usize),
610✔
287
            TxNodeInternal::Partial(txouts) => txouts.get(&outpoint.vout),
22✔
288
        }
289
    }
641✔
290

291
    /// Returns known outputs of a given `txid`.
292
    ///
293
    /// Returns a [`BTreeMap`] of vout to output of the provided `txid`.
294
    pub fn tx_outputs(&self, txid: Txid) -> Option<BTreeMap<u32, &TxOut>> {
2✔
295
        Some(match &self.txs.get(&txid)?.0 {
2✔
296
            TxNodeInternal::Whole(tx) => tx
1✔
297
                .as_ref()
1✔
298
                .output
1✔
299
                .iter()
1✔
300
                .enumerate()
1✔
301
                .map(|(vout, txout)| (vout as u32, txout))
1✔
302
                .collect::<BTreeMap<_, _>>(),
1✔
303
            TxNodeInternal::Partial(txouts) => txouts
1✔
304
                .iter()
1✔
305
                .map(|(vout, txout)| (*vout, txout))
2✔
306
                .collect::<BTreeMap<_, _>>(),
1✔
307
        })
308
    }
2✔
309

310
    /// Calculates the fee of a given transaction. Returns [`Amount::ZERO`] if `tx` is a coinbase transaction.
311
    /// Returns `OK(_)` if we have all the [`TxOut`]s being spent by `tx` in the graph (either as
312
    /// the full transactions or individual txouts).
313
    ///
314
    /// To calculate the fee for a [`Transaction`] that depends on foreign [`TxOut`] values you must
315
    /// first manually insert the foreign TxOuts into the tx graph using the [`insert_txout`] function.
316
    /// Only insert TxOuts you trust the values for!
317
    ///
318
    /// Note `tx` does not have to be in the graph for this to work.
319
    ///
320
    /// [`insert_txout`]: Self::insert_txout
321
    pub fn calculate_fee(&self, tx: &Transaction) -> Result<Amount, CalculateFeeError> {
545✔
322
        if tx.is_coinbase() {
545✔
323
            return Ok(Amount::ZERO);
1✔
324
        }
544✔
325

544✔
326
        let (inputs_sum, missing_outputs) = tx.input.iter().fold(
544✔
327
            (SignedAmount::ZERO, Vec::new()),
544✔
328
            |(mut sum, mut missing_outpoints), txin| match self.get_txout(txin.previous_output) {
637✔
329
                None => {
330
                    missing_outpoints.push(txin.previous_output);
9✔
331
                    (sum, missing_outpoints)
9✔
332
                }
333
                Some(txout) => {
628✔
334
                    sum += txout.value.to_signed().expect("valid `SignedAmount`");
628✔
335
                    (sum, missing_outpoints)
628✔
336
                }
337
            },
637✔
338
        );
544✔
339
        if !missing_outputs.is_empty() {
544✔
340
            return Err(CalculateFeeError::MissingTxOut(missing_outputs));
9✔
341
        }
535✔
342

535✔
343
        let outputs_sum = tx
535✔
344
            .output
535✔
345
            .iter()
535✔
346
            .map(|txout| txout.value.to_signed().expect("valid `SignedAmount`"))
860✔
347
            .sum::<SignedAmount>();
535✔
348

535✔
349
        let fee = inputs_sum - outputs_sum;
535✔
350
        fee.to_unsigned()
535✔
351
            .map_err(|_| CalculateFeeError::NegativeFee(fee))
535✔
352
    }
545✔
353

354
    /// The transactions spending from this output.
355
    ///
356
    /// [`TxGraph`] allows conflicting transactions within the graph. Obviously the transactions in
357
    /// the returned set will never be in the same active-chain.
358
    pub fn outspends(&self, outpoint: OutPoint) -> &HashSet<Txid> {
4✔
359
        self.spends.get(&outpoint).unwrap_or(&self.empty_outspends)
4✔
360
    }
4✔
361

362
    /// Iterates over the transactions spending from `txid`.
363
    ///
364
    /// The iterator item is a union of `(vout, txid-set)` where:
365
    ///
366
    /// - `vout` is the provided `txid`'s outpoint that is being spent
367
    /// - `txid-set` is the set of txids spending the `vout`.
368
    pub fn tx_spends(
1✔
369
        &self,
1✔
370
        txid: Txid,
1✔
371
    ) -> impl DoubleEndedIterator<Item = (u32, &HashSet<Txid>)> + '_ {
1✔
372
        let start = OutPoint::new(txid, 0);
1✔
373
        let end = OutPoint::new(txid, u32::MAX);
1✔
374
        self.spends
1✔
375
            .range(start..=end)
1✔
376
            .map(|(outpoint, spends)| (outpoint.vout, spends))
1✔
377
    }
1✔
378
}
379

380
impl<A: Clone + Ord> TxGraph<A> {
381
    /// Creates an iterator that filters and maps ancestor transactions.
382
    ///
383
    /// The iterator starts with the ancestors of the supplied `tx` (ancestor transactions of `tx`
384
    /// are transactions spent by `tx`). The supplied transaction is excluded from the iterator.
385
    ///
386
    /// The supplied closure takes in two inputs `(depth, ancestor_tx)`:
387
    ///
388
    /// * `depth` is the distance between the starting `Transaction` and the `ancestor_tx`. I.e., if
389
    ///    the `Transaction` is spending an output of the `ancestor_tx` then `depth` will be 1.
390
    /// * `ancestor_tx` is the `Transaction`'s ancestor which we are considering to walk.
391
    ///
392
    /// The supplied closure returns an `Option<T>`, allowing the caller to map each `Transaction`
393
    /// it visits and decide whether to visit ancestors.
394
    pub fn walk_ancestors<'g, T, F, O>(&'g self, tx: T, walk_map: F) -> TxAncestors<'g, A, F>
4✔
395
    where
4✔
396
        T: Into<Arc<Transaction>>,
4✔
397
        F: FnMut(usize, Arc<Transaction>) -> Option<O> + 'g,
4✔
398
    {
4✔
399
        TxAncestors::new_exclude_root(self, tx, walk_map)
4✔
400
    }
4✔
401

402
    /// Creates an iterator that filters and maps descendants from the starting `txid`.
403
    ///
404
    /// The supplied closure takes in two inputs `(depth, descendant_txid)`:
405
    ///
406
    /// * `depth` is the distance between the starting `txid` and the `descendant_txid`. I.e., if the
407
    ///     descendant is spending an output of the starting `txid` then `depth` will be 1.
408
    /// * `descendant_txid` is the descendant's txid which we are considering to walk.
409
    ///
410
    /// The supplied closure returns an `Option<T>`, allowing the caller to map each node it visits
411
    /// and decide whether to visit descendants.
412
    pub fn walk_descendants<'g, F, O>(&'g self, txid: Txid, walk_map: F) -> TxDescendants<A, F>
1✔
413
    where
1✔
414
        F: FnMut(usize, Txid) -> Option<O> + 'g,
1✔
415
    {
1✔
416
        TxDescendants::new_exclude_root(self, txid, walk_map)
1✔
417
    }
1✔
418
}
419

420
impl<A> TxGraph<A> {
421
    /// Creates an iterator that both filters and maps conflicting transactions (this includes
422
    /// descendants of directly-conflicting transactions, which are also considered conflicts).
423
    ///
424
    /// Refer to [`Self::walk_descendants`] for `walk_map` usage.
425
    pub fn walk_conflicts<'g, F, O>(
1,203✔
426
        &'g self,
1,203✔
427
        tx: &'g Transaction,
1,203✔
428
        walk_map: F,
1,203✔
429
    ) -> TxDescendants<A, F>
1,203✔
430
    where
1,203✔
431
        F: FnMut(usize, Txid) -> Option<O> + 'g,
1,203✔
432
    {
1,203✔
433
        let txids = self.direct_conflicts(tx).map(|(_, txid)| txid);
1,203✔
434
        TxDescendants::from_multiple_include_root(self, txids, walk_map)
1,203✔
435
    }
1,203✔
436

437
    /// Given a transaction, return an iterator of txids that directly conflict with the given
438
    /// transaction's inputs (spends). The conflicting txids are returned with the given
439
    /// transaction's vin (in which it conflicts).
440
    ///
441
    /// Note that this only returns directly conflicting txids and won't include:
442
    /// - descendants of conflicting transactions (which are technically also conflicting)
443
    /// - transactions conflicting with the given transaction's ancestors
444
    pub fn direct_conflicts<'g>(
1,203✔
445
        &'g self,
1,203✔
446
        tx: &'g Transaction,
1,203✔
447
    ) -> impl Iterator<Item = (usize, Txid)> + '_ {
1,203✔
448
        let txid = tx.compute_txid();
1,203✔
449
        tx.input
1,203✔
450
            .iter()
1,203✔
451
            .enumerate()
1,203✔
452
            .filter_map(move |(vin, txin)| self.spends.get(&txin.previous_output).zip(Some(vin)))
1,320✔
453
            .flat_map(|(spends, vin)| core::iter::repeat(vin).zip(spends.iter().cloned()))
1,320✔
454
            .filter(move |(_, conflicting_txid)| *conflicting_txid != txid)
1,587✔
455
    }
1,203✔
456

457
    /// Get all transaction anchors known by [`TxGraph`].
458
    pub fn all_anchors(&self) -> &BTreeSet<(A, Txid)> {
40✔
459
        &self.anchors
40✔
460
    }
40✔
461

462
    /// Whether the graph has any transactions or outputs in it.
463
    pub fn is_empty(&self) -> bool {
×
464
        self.txs.is_empty()
×
465
    }
×
466
}
467

468
impl<A: Clone + Ord> TxGraph<A> {
469
    /// Transform the [`TxGraph`] to have [`Anchor`]s of another type.
470
    ///
471
    /// This takes in a closure of signature `FnMut(A) -> A2` which is called for each [`Anchor`] to
472
    /// transform it.
473
    pub fn map_anchors<A2: Clone + Ord, F>(self, f: F) -> TxGraph<A2>
11✔
474
    where
11✔
475
        F: FnMut(A) -> A2,
11✔
476
    {
11✔
477
        let mut new_graph = TxGraph::<A2>::default();
11✔
478
        new_graph.apply_changeset(self.initial_changeset().map_anchors(f));
11✔
479
        new_graph
11✔
480
    }
11✔
481

482
    /// Construct a new [`TxGraph`] from a list of transactions.
483
    pub fn new(txs: impl IntoIterator<Item = Transaction>) -> Self {
1✔
484
        let mut new = Self::default();
1✔
485
        for tx in txs.into_iter() {
11✔
486
            let _ = new.insert_tx(tx);
11✔
487
        }
11✔
488
        new
1✔
489
    }
1✔
490

491
    /// Inserts the given [`TxOut`] at [`OutPoint`].
492
    ///
493
    /// Inserting floating txouts are useful for determining fee/feerate of transactions we care
494
    /// about.
495
    ///
496
    /// The [`ChangeSet`] result will be empty if the `outpoint` (or a full transaction containing
497
    /// the `outpoint`) already existed in `self`.
498
    ///
499
    /// [`apply_changeset`]: Self::apply_changeset
500
    pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<A> {
49✔
501
        let mut update = Self::default();
49✔
502
        update.txs.insert(
49✔
503
            outpoint.txid,
49✔
504
            (
49✔
505
                TxNodeInternal::Partial([(outpoint.vout, txout)].into()),
49✔
506
                BTreeSet::new(),
49✔
507
                0,
49✔
508
            ),
49✔
509
        );
49✔
510
        self.apply_update(update)
49✔
511
    }
49✔
512

513
    /// Inserts the given transaction into [`TxGraph`].
514
    ///
515
    /// The [`ChangeSet`] returned will be empty if `tx` already exists.
516
    pub fn insert_tx<T: Into<Arc<Transaction>>>(&mut self, tx: T) -> ChangeSet<A> {
2,787✔
517
        let tx = tx.into();
2,787✔
518
        let mut update = Self::default();
2,787✔
519
        update.txs.insert(
2,787✔
520
            tx.compute_txid(),
2,787✔
521
            (TxNodeInternal::Whole(tx), BTreeSet::new(), 0),
2,787✔
522
        );
2,787✔
523
        self.apply_update(update)
2,787✔
524
    }
2,787✔
525

526
    /// Batch insert unconfirmed transactions.
527
    ///
528
    /// Items of `txs` are tuples containing the transaction and a *last seen* timestamp. The
529
    /// *last seen* communicates when the transaction is last seen in mempool which is used for
530
    /// conflict-resolution (refer to [`TxGraph::insert_seen_at`] for details).
531
    pub fn batch_insert_unconfirmed(
2✔
532
        &mut self,
2✔
533
        txs: impl IntoIterator<Item = (Transaction, u64)>,
2✔
534
    ) -> ChangeSet<A> {
2✔
535
        let mut changeset = ChangeSet::<A>::default();
2✔
536
        for (tx, seen_at) in txs {
7✔
537
            changeset.append(self.insert_seen_at(tx.compute_txid(), seen_at));
5✔
538
            changeset.append(self.insert_tx(tx));
5✔
539
        }
5✔
540
        changeset
2✔
541
    }
2✔
542

543
    /// Inserts the given `anchor` into [`TxGraph`].
544
    ///
545
    /// The [`ChangeSet`] returned will be empty if graph already knows that `txid` exists in
546
    /// `anchor`.
547
    pub fn insert_anchor(&mut self, txid: Txid, anchor: A) -> ChangeSet<A> {
2,518✔
548
        let mut update = Self::default();
2,518✔
549
        update.anchors.insert((anchor, txid));
2,518✔
550
        self.apply_update(update)
2,518✔
551
    }
2,518✔
552

553
    /// Inserts the given `seen_at` for `txid` into [`TxGraph`].
554
    ///
555
    /// Note that [`TxGraph`] only keeps track of the latest `seen_at`. To batch
556
    /// update all unconfirmed transactions with the latest `seen_at`, see
557
    /// [`update_last_seen_unconfirmed`].
558
    ///
559
    /// [`update_last_seen_unconfirmed`]: Self::update_last_seen_unconfirmed
560
    pub fn insert_seen_at(&mut self, txid: Txid, seen_at: u64) -> ChangeSet<A> {
216✔
561
        let mut update = Self::default();
216✔
562
        let (_, _, update_last_seen) = update.txs.entry(txid).or_default();
216✔
563
        *update_last_seen = seen_at;
216✔
564
        self.apply_update(update)
216✔
565
    }
216✔
566

567
    /// Update the last seen time for all unconfirmed transactions.
568
    ///
569
    /// This method updates the last seen unconfirmed time for this [`TxGraph`] by inserting
570
    /// the given `seen_at` for every transaction not yet anchored to a confirmed block,
571
    /// and returns the [`ChangeSet`] after applying all updates to `self`.
572
    ///
573
    /// This is useful for keeping track of the latest time a transaction was seen
574
    /// unconfirmed, which is important for evaluating transaction conflicts in the same
575
    /// [`TxGraph`]. For details of how [`TxGraph`] resolves conflicts, see the docs for
576
    /// [`try_get_chain_position`].
577
    ///
578
    /// A normal use of this method is to call it with the current system time. Although
579
    /// block headers contain a timestamp, using the header time would be less effective
580
    /// at tracking mempool transactions, because it can drift from actual clock time, plus
581
    /// we may want to update a transaction's last seen time repeatedly between blocks.
582
    ///
583
    /// # Example
584
    ///
585
    /// ```rust
586
    /// # use bdk_chain::example_utils::*;
587
    /// # use std::time::UNIX_EPOCH;
588
    /// # let tx = tx_from_hex(RAW_TX_1);
589
    /// # let mut tx_graph = bdk_chain::TxGraph::<()>::new([tx]);
590
    /// let now = std::time::SystemTime::now()
591
    ///     .duration_since(UNIX_EPOCH)
592
    ///     .expect("valid duration")
593
    ///     .as_secs();
594
    /// let changeset = tx_graph.update_last_seen_unconfirmed(now);
595
    /// assert!(!changeset.last_seen.is_empty());
596
    /// ```
597
    ///
598
    /// Note that [`TxGraph`] only keeps track of the latest `seen_at`, so the given time must
599
    /// by strictly greater than what is currently stored for a transaction to have an effect.
600
    /// To insert a last seen time for a single txid, see [`insert_seen_at`].
601
    ///
602
    /// [`insert_seen_at`]: Self::insert_seen_at
603
    /// [`try_get_chain_position`]: Self::try_get_chain_position
604
    pub fn update_last_seen_unconfirmed(&mut self, seen_at: u64) -> ChangeSet<A> {
3✔
605
        let mut changeset = ChangeSet::default();
3✔
606
        let unanchored_txs: Vec<Txid> = self
3✔
607
            .txs
3✔
608
            .iter()
3✔
609
            .filter_map(
3✔
610
                |(&txid, (_, anchors, _))| {
3✔
611
                    if anchors.is_empty() {
3✔
612
                        Some(txid)
2✔
613
                    } else {
614
                        None
1✔
615
                    }
616
                },
3✔
617
            )
3✔
618
            .collect();
3✔
619

620
        for txid in unanchored_txs {
5✔
621
            changeset.append(self.insert_seen_at(txid, seen_at));
2✔
622
        }
2✔
623
        changeset
3✔
624
    }
3✔
625

626
    /// Extends this graph with another so that `self` becomes the union of the two sets of
627
    /// transactions.
628
    ///
629
    /// The returned [`ChangeSet`] is the set difference between `update` and `self` (transactions that
630
    /// exist in `update` but not in `self`).
631
    pub fn apply_update(&mut self, update: TxGraph<A>) -> ChangeSet<A> {
5,581✔
632
        let changeset = self.determine_changeset(update);
5,581✔
633
        self.apply_changeset(changeset.clone());
5,581✔
634
        changeset
5,581✔
635
    }
5,581✔
636

637
    /// Determines the [`ChangeSet`] between `self` and an empty [`TxGraph`].
638
    pub fn initial_changeset(&self) -> ChangeSet<A> {
164✔
639
        Self::default().determine_changeset(self.clone())
164✔
640
    }
164✔
641

642
    /// Applies [`ChangeSet`] to [`TxGraph`].
643
    pub fn apply_changeset(&mut self, changeset: ChangeSet<A>) {
5,689✔
644
        for wrapped_tx in changeset.txs {
8,532✔
645
            let tx = wrapped_tx.as_ref();
2,843✔
646
            let txid = tx.compute_txid();
2,843✔
647

2,843✔
648
            tx.input
2,843✔
649
                .iter()
2,843✔
650
                .map(|txin| txin.previous_output)
2,855✔
651
                // coinbase spends are not to be counted
2,843✔
652
                .filter(|outpoint| !outpoint.is_null())
2,855✔
653
                // record spend as this tx has spent this outpoint
2,843✔
654
                .for_each(|outpoint| {
2,854✔
655
                    self.spends.entry(outpoint).or_default().insert(txid);
2,478✔
656
                });
2,854✔
657

2,843✔
658
            match self.txs.get_mut(&txid) {
2,843✔
659
                Some((tx_node @ TxNodeInternal::Partial(_), _, _)) => {
196✔
660
                    *tx_node = TxNodeInternal::Whole(wrapped_tx.clone());
196✔
661
                }
196✔
662
                Some((TxNodeInternal::Whole(tx), _, _)) => {
1✔
663
                    debug_assert_eq!(
1✔
664
                        tx.as_ref().compute_txid(),
1✔
665
                        txid,
666
                        "tx should produce txid that is same as key"
×
667
                    );
668
                }
669
                None => {
2,646✔
670
                    self.txs.insert(
2,646✔
671
                        txid,
2,646✔
672
                        (TxNodeInternal::Whole(wrapped_tx), BTreeSet::new(), 0),
2,646✔
673
                    );
2,646✔
674
                }
2,646✔
675
            }
676
        }
677

678
        for (outpoint, txout) in changeset.txouts {
5,739✔
679
            let tx_entry = self.txs.entry(outpoint.txid).or_default();
50✔
680

50✔
681
            match tx_entry {
50✔
682
                (TxNodeInternal::Whole(_), _, _) => { /* do nothing since we already have full tx */
×
683
                }
×
684
                (TxNodeInternal::Partial(txouts), _, _) => {
50✔
685
                    txouts.insert(outpoint.vout, txout);
50✔
686
                }
50✔
687
            }
688
        }
689

690
        for (anchor, txid) in changeset.anchors {
8,261✔
691
            if self.anchors.insert((anchor.clone(), txid)) {
2,572✔
692
                let (_, anchors, _) = self.txs.entry(txid).or_default();
2,570✔
693
                anchors.insert(anchor);
2,570✔
694
            }
2,570✔
695
        }
696

697
        for (txid, new_last_seen) in changeset.last_seen {
5,722✔
698
            let (_, _, last_seen) = self.txs.entry(txid).or_default();
33✔
699
            if new_last_seen > *last_seen {
33✔
700
                *last_seen = new_last_seen;
32✔
701
            }
32✔
702
        }
703
    }
5,689✔
704

705
    /// Previews the resultant [`ChangeSet`] when [`Self`] is updated against the `update` graph.
706
    ///
707
    /// The [`ChangeSet`] would be the set difference between `update` and `self` (transactions that
708
    /// exist in `update` but not in `self`).
709
    pub(crate) fn determine_changeset(&self, update: TxGraph<A>) -> ChangeSet<A> {
6,944✔
710
        let mut changeset = ChangeSet::<A>::default();
6,944✔
711

712
        for (&txid, (update_tx_node, _, update_last_seen)) in &update.txs {
10,099✔
713
            let prev_last_seen: u64 = match (self.txs.get(&txid), update_tx_node) {
3,155✔
714
                (None, TxNodeInternal::Whole(update_tx)) => {
2,650✔
715
                    changeset.txs.insert(update_tx.clone());
2,650✔
716
                    0
2,650✔
717
                }
718
                (None, TxNodeInternal::Partial(update_txos)) => {
55✔
719
                    changeset.txouts.extend(
55✔
720
                        update_txos
55✔
721
                            .iter()
55✔
722
                            .map(|(&vout, txo)| (OutPoint::new(txid, vout), txo.clone())),
56✔
723
                    );
55✔
724
                    0
55✔
725
                }
726
                (Some((TxNodeInternal::Whole(_), _, last_seen)), _) => *last_seen,
251✔
727
                (
728
                    Some((TxNodeInternal::Partial(_), _, last_seen)),
196✔
729
                    TxNodeInternal::Whole(update_tx),
196✔
730
                ) => {
196✔
731
                    changeset.txs.insert(update_tx.clone());
196✔
732
                    *last_seen
196✔
733
                }
734
                (
735
                    Some((TxNodeInternal::Partial(txos), _, last_seen)),
3✔
736
                    TxNodeInternal::Partial(update_txos),
3✔
737
                ) => {
3✔
738
                    changeset.txouts.extend(
3✔
739
                        update_txos
3✔
740
                            .iter()
3✔
741
                            .filter(|(vout, _)| !txos.contains_key(*vout))
3✔
742
                            .map(|(&vout, txo)| (OutPoint::new(txid, vout), txo.clone())),
3✔
743
                    );
3✔
744
                    *last_seen
3✔
745
                }
746
            };
747

748
            if *update_last_seen > prev_last_seen {
3,155✔
749
                changeset.last_seen.insert(txid, *update_last_seen);
33✔
750
            }
3,122✔
751
        }
752

753
        changeset.anchors = update.anchors.difference(&self.anchors).cloned().collect();
6,944✔
754

6,944✔
755
        changeset
6,944✔
756
    }
6,944✔
757
}
758

759
impl<A: Anchor> TxGraph<A> {
760
    /// Get the position of the transaction in `chain` with tip `chain_tip`.
761
    ///
762
    /// Chain data is fetched from `chain`, a [`ChainOracle`] implementation.
763
    ///
764
    /// This method returns `Ok(None)` if the transaction is not found in the chain, and no longer
765
    /// belongs in the mempool. The following factors are used to approximate whether an
766
    /// unconfirmed transaction exists in the mempool (not evicted):
767
    ///
768
    /// 1. Unconfirmed transactions that conflict with confirmed transactions are evicted.
769
    /// 2. Unconfirmed transactions that spend from transactions that are evicted, are also
770
    ///    evicted.
771
    /// 3. Given two conflicting unconfirmed transactions, the transaction with the lower
772
    ///    `last_seen_unconfirmed` parameter is evicted. A transaction's `last_seen_unconfirmed`
773
    ///    parameter is the max of all it's descendants' `last_seen_unconfirmed` parameters. If the
774
    ///    final `last_seen_unconfirmed`s are the same, the transaction with the lower `txid` (by
775
    ///    lexicographical order) is evicted.
776
    ///
777
    /// # Error
778
    ///
779
    /// An error will occur if the [`ChainOracle`] implementation (`chain`) fails. If the
780
    /// [`ChainOracle`] is infallible, [`get_chain_position`] can be used instead.
781
    ///
782
    /// [`get_chain_position`]: Self::get_chain_position
783
    pub fn try_get_chain_position<C: ChainOracle>(
9,359✔
784
        &self,
9,359✔
785
        chain: &C,
9,359✔
786
        chain_tip: BlockId,
9,359✔
787
        txid: Txid,
9,359✔
788
    ) -> Result<Option<ChainPosition<&A>>, C::Error> {
9,359✔
789
        let (tx_node, anchors, last_seen) = match self.txs.get(&txid) {
9,359✔
790
            Some(v) => v,
9,343✔
791
            None => return Ok(None),
16✔
792
        };
793

794
        for anchor in anchors {
9,588✔
795
            match chain.is_block_in_chain(anchor.anchor_block(), chain_tip)? {
8,470✔
796
                Some(true) => return Ok(Some(ChainPosition::Confirmed(anchor))),
8,225✔
797
                _ => continue,
245✔
798
            }
799
        }
800

801
        // The tx is not anchored to a block in the best chain, which means that it
802
        // might be in mempool, or it might have been dropped already.
803
        // Let's check conflicts to find out!
804
        let tx = match tx_node {
1,118✔
805
            TxNodeInternal::Whole(tx) => {
1,110✔
806
                // A coinbase tx that is not anchored in the best chain cannot be unconfirmed and
1,110✔
807
                // should always be filtered out.
1,110✔
808
                if tx.is_coinbase() {
1,110✔
809
                    return Ok(None);
4✔
810
                }
1,106✔
811
                tx.clone()
1,106✔
812
            }
813
            TxNodeInternal::Partial(_) => {
814
                // Partial transactions (outputs only) cannot have conflicts.
815
                return Ok(None);
8✔
816
            }
817
        };
818

819
        // We want to retrieve all the transactions that conflict with us, plus all the
820
        // transactions that conflict with our unconfirmed ancestors, since they conflict with us
821
        // as well.
822
        // We only traverse unconfirmed ancestors since conflicts of confirmed transactions
823
        // cannot be in the best chain.
824

825
        // First of all, we retrieve all our ancestors. Since we're using `new_include_root`, the
826
        // resulting array will also include `tx`
827
        let unconfirmed_ancestor_txs =
1,106✔
828
            TxAncestors::new_include_root(self, tx.clone(), |_, ancestor_tx: Arc<Transaction>| {
2,003✔
829
                let tx_node = self.get_tx_node(ancestor_tx.as_ref().compute_txid())?;
2,003✔
830
                // We're filtering the ancestors to keep only the unconfirmed ones (= no anchors in
831
                // the best chain)
832
                for block in tx_node.anchors {
2,257✔
833
                    match chain.is_block_in_chain(block.anchor_block(), chain_tip) {
1,040✔
834
                        Ok(Some(true)) => return None,
786✔
835
                        Err(e) => return Some(Err(e)),
×
836
                        _ => continue,
254✔
837
                    }
838
                }
839
                Some(Ok(tx_node))
1,217✔
840
            })
2,003✔
841
            .collect::<Result<Vec<_>, C::Error>>()?;
1,106✔
842

843
        // We determine our tx's last seen, which is the max between our last seen,
844
        // and our unconf descendants' last seen.
845
        let unconfirmed_descendants_txs = TxDescendants::new_include_root(
1,106✔
846
            self,
1,106✔
847
            tx.as_ref().compute_txid(),
1,106✔
848
            |_, descendant_txid: Txid| {
1,210✔
849
                let tx_node = self.get_tx_node(descendant_txid)?;
1,210✔
850
                // We're filtering the ancestors to keep only the unconfirmed ones (= no anchors in
851
                // the best chain)
852
                for block in tx_node.anchors {
1,461✔
853
                    match chain.is_block_in_chain(block.anchor_block(), chain_tip) {
251✔
854
                        Ok(Some(true)) => return None,
×
855
                        Err(e) => return Some(Err(e)),
×
856
                        _ => continue,
251✔
857
                    }
858
                }
859
                Some(Ok(tx_node))
1,210✔
860
            },
1,210✔
861
        )
1,106✔
862
        .collect::<Result<Vec<_>, C::Error>>()?;
1,106✔
863

864
        let tx_last_seen = unconfirmed_descendants_txs
1,106✔
865
            .iter()
1,106✔
866
            .max_by_key(|tx| tx.last_seen_unconfirmed)
1,210✔
867
            .map(|tx| tx.last_seen_unconfirmed)
1,106✔
868
            .expect("descendants always includes at least one transaction (the root tx");
1,106✔
869

870
        // Now we traverse our ancestors and consider all their conflicts
871
        for tx_node in unconfirmed_ancestor_txs {
2,186✔
872
            // We retrieve all the transactions conflicting with this specific ancestor
873
            let conflicting_txs =
1,202✔
874
                self.walk_conflicts(tx_node.tx.as_ref(), |_, txid| self.get_tx_node(txid));
1,202✔
875

876
            // If a conflicting tx is in the best chain, or has `last_seen` higher than this ancestor, then
877
            // this tx cannot exist in the best chain
878
            for conflicting_tx in conflicting_txs {
1,333✔
879
                for block in conflicting_tx.anchors {
270✔
880
                    if chain.is_block_in_chain(block.anchor_block(), chain_tip)? == Some(true) {
77✔
881
                        return Ok(None);
60✔
882
                    }
17✔
883
                }
884
                if conflicting_tx.last_seen_unconfirmed > tx_last_seen {
193✔
885
                    return Ok(None);
58✔
886
                }
135✔
887
                if conflicting_tx.last_seen_unconfirmed == *last_seen
135✔
888
                    && conflicting_tx.as_ref().compute_txid() > tx.as_ref().compute_txid()
14✔
889
                {
890
                    // Conflicting tx has priority if txid of conflicting tx > txid of original tx
891
                    return Ok(None);
4✔
892
                }
131✔
893
            }
894
        }
895

896
        Ok(Some(ChainPosition::Unconfirmed(*last_seen)))
984✔
897
    }
9,359✔
898

899
    /// Get the position of the transaction in `chain` with tip `chain_tip`.
900
    ///
901
    /// This is the infallible version of [`try_get_chain_position`].
902
    ///
903
    /// [`try_get_chain_position`]: Self::try_get_chain_position
904
    pub fn get_chain_position<C: ChainOracle<Error = Infallible>>(
1,780✔
905
        &self,
1,780✔
906
        chain: &C,
1,780✔
907
        chain_tip: BlockId,
1,780✔
908
        txid: Txid,
1,780✔
909
    ) -> Option<ChainPosition<&A>> {
1,780✔
910
        self.try_get_chain_position(chain, chain_tip, txid)
1,780✔
911
            .expect("error is infallible")
1,780✔
912
    }
1,780✔
913

914
    /// Get the txid of the spending transaction and where the spending transaction is observed in
915
    /// the `chain` of `chain_tip`.
916
    ///
917
    /// If no in-chain transaction spends `outpoint`, `None` will be returned.
918
    ///
919
    /// # Error
920
    ///
921
    /// An error will occur only if the [`ChainOracle`] implementation (`chain`) fails.
922
    ///
923
    /// If the [`ChainOracle`] is infallible, [`get_chain_spend`] can be used instead.
924
    ///
925
    /// [`get_chain_spend`]: Self::get_chain_spend
926
    pub fn try_get_chain_spend<C: ChainOracle>(
3,035✔
927
        &self,
3,035✔
928
        chain: &C,
3,035✔
929
        chain_tip: BlockId,
3,035✔
930
        outpoint: OutPoint,
3,035✔
931
    ) -> Result<Option<(ChainPosition<&A>, Txid)>, C::Error> {
3,035✔
932
        if self
3,035✔
933
            .try_get_chain_position(chain, chain_tip, outpoint.txid)?
3,035✔
934
            .is_none()
3,035✔
935
        {
936
            return Ok(None);
×
937
        }
3,035✔
938
        if let Some(spends) = self.spends.get(&outpoint) {
3,035✔
939
            for &txid in spends {
1,352✔
940
                if let Some(observed_at) = self.try_get_chain_position(chain, chain_tip, txid)? {
1,343✔
941
                    return Ok(Some((observed_at, txid)));
1,307✔
942
                }
36✔
943
            }
944
        }
1,719✔
945
        Ok(None)
1,728✔
946
    }
3,035✔
947

948
    /// Get the txid of the spending transaction and where the spending transaction is observed in
949
    /// the `chain` of `chain_tip`.
950
    ///
951
    /// This is the infallible version of [`try_get_chain_spend`]
952
    ///
953
    /// [`try_get_chain_spend`]: Self::try_get_chain_spend
954
    pub fn get_chain_spend<C: ChainOracle<Error = Infallible>>(
4✔
955
        &self,
4✔
956
        chain: &C,
4✔
957
        static_block: BlockId,
4✔
958
        outpoint: OutPoint,
4✔
959
    ) -> Option<(ChainPosition<&A>, Txid)> {
4✔
960
        self.try_get_chain_spend(chain, static_block, outpoint)
4✔
961
            .expect("error is infallible")
4✔
962
    }
4✔
963

964
    /// List graph transactions that are in `chain` with `chain_tip`.
965
    ///
966
    /// Each transaction is represented as a [`CanonicalTx`] that contains where the transaction is
967
    /// observed in-chain, and the [`TxNode`].
968
    ///
969
    /// # Error
970
    ///
971
    /// If the [`ChainOracle`] implementation (`chain`) fails, an error will be returned with the
972
    /// returned item.
973
    ///
974
    /// If the [`ChainOracle`] is infallible, [`list_chain_txs`] can be used instead.
975
    ///
976
    /// [`list_chain_txs`]: Self::list_chain_txs
977
    pub fn try_list_chain_txs<'a, C: ChainOracle + 'a>(
51✔
978
        &'a self,
51✔
979
        chain: &'a C,
51✔
980
        chain_tip: BlockId,
51✔
981
    ) -> impl Iterator<Item = Result<CanonicalTx<'a, Arc<Transaction>, A>, C::Error>> {
51✔
982
        self.full_txs().filter_map(move |tx| {
104✔
983
            self.try_get_chain_position(chain, chain_tip, tx.txid)
104✔
984
                .map(|v| {
104✔
985
                    v.map(|observed_in| CanonicalTx {
104✔
986
                        chain_position: observed_in,
82✔
987
                        tx_node: tx,
82✔
988
                    })
104✔
989
                })
104✔
990
                .transpose()
104✔
991
        })
104✔
992
    }
51✔
993

994
    /// List graph transactions that are in `chain` with `chain_tip`.
995
    ///
996
    /// This is the infallible version of [`try_list_chain_txs`].
997
    ///
998
    /// [`try_list_chain_txs`]: Self::try_list_chain_txs
999
    pub fn list_chain_txs<'a, C: ChainOracle + 'a>(
51✔
1000
        &'a self,
51✔
1001
        chain: &'a C,
51✔
1002
        chain_tip: BlockId,
51✔
1003
    ) -> impl Iterator<Item = CanonicalTx<'a, Arc<Transaction>, A>> {
51✔
1004
        self.try_list_chain_txs(chain, chain_tip)
51✔
1005
            .map(|r| r.expect("oracle is infallible"))
82✔
1006
    }
51✔
1007

1008
    /// Get a filtered list of outputs from the given `outpoints` that are in `chain` with
1009
    /// `chain_tip`.
1010
    ///
1011
    /// `outpoints` is a list of outpoints we are interested in, coupled with an outpoint identifier
1012
    /// (`OI`) for convenience. If `OI` is not necessary, the caller can use `()`, or
1013
    /// [`Iterator::enumerate`] over a list of [`OutPoint`]s.
1014
    ///
1015
    /// Floating outputs (i.e., outputs for which we don't have the full transaction in the graph)
1016
    /// are ignored.
1017
    ///
1018
    /// # Error
1019
    ///
1020
    /// An [`Iterator::Item`] can be an [`Err`] if the [`ChainOracle`] implementation (`chain`)
1021
    /// fails.
1022
    ///
1023
    /// If the [`ChainOracle`] implementation is infallible, [`filter_chain_txouts`] can be used
1024
    /// instead.
1025
    ///
1026
    /// [`filter_chain_txouts`]: Self::filter_chain_txouts
1027
    pub fn try_filter_chain_txouts<'a, C: ChainOracle + 'a, OI: Clone + 'a>(
1,388✔
1028
        &'a self,
1,388✔
1029
        chain: &'a C,
1,388✔
1030
        chain_tip: BlockId,
1,388✔
1031
        outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
1,388✔
1032
    ) -> impl Iterator<Item = Result<(OI, FullTxOut<A>), C::Error>> + 'a {
1,388✔
1033
        outpoints
1,388✔
1034
            .into_iter()
1,388✔
1035
            .map(
1,388✔
1036
                move |(spk_i, op)| -> Result<Option<(OI, FullTxOut<_>)>, C::Error> {
3,097✔
1037
                    let tx_node = match self.get_tx_node(op.txid) {
3,097✔
1038
                        Some(n) => n,
3,097✔
1039
                        None => return Ok(None),
×
1040
                    };
1041

1042
                    let txout = match tx_node.tx.as_ref().output.get(op.vout as usize) {
3,097✔
1043
                        Some(txout) => txout.clone(),
3,097✔
1044
                        None => return Ok(None),
×
1045
                    };
1046

1047
                    let chain_position =
3,031✔
1048
                        match self.try_get_chain_position(chain, chain_tip, op.txid)? {
3,097✔
1049
                            Some(pos) => pos.cloned(),
3,031✔
1050
                            None => return Ok(None),
66✔
1051
                        };
1052

1053
                    let spent_by = self
3,031✔
1054
                        .try_get_chain_spend(chain, chain_tip, op)?
3,031✔
1055
                        .map(|(a, txid)| (a.cloned(), txid));
3,031✔
1056

3,031✔
1057
                    Ok(Some((
3,031✔
1058
                        spk_i,
3,031✔
1059
                        FullTxOut {
3,031✔
1060
                            outpoint: op,
3,031✔
1061
                            txout,
3,031✔
1062
                            chain_position,
3,031✔
1063
                            spent_by,
3,031✔
1064
                            is_on_coinbase: tx_node.tx.is_coinbase(),
3,031✔
1065
                        },
3,031✔
1066
                    )))
3,031✔
1067
                },
3,097✔
1068
            )
1,388✔
1069
            .filter_map(Result::transpose)
1,388✔
1070
    }
1,388✔
1071

1072
    /// Get a filtered list of outputs from the given `outpoints` that are in `chain` with
1073
    /// `chain_tip`.
1074
    ///
1075
    /// This is the infallible version of [`try_filter_chain_txouts`].
1076
    ///
1077
    /// [`try_filter_chain_txouts`]: Self::try_filter_chain_txouts
1078
    pub fn filter_chain_txouts<'a, C: ChainOracle<Error = Infallible> + 'a, OI: Clone + 'a>(
26✔
1079
        &'a self,
26✔
1080
        chain: &'a C,
26✔
1081
        chain_tip: BlockId,
26✔
1082
        outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
26✔
1083
    ) -> impl Iterator<Item = (OI, FullTxOut<A>)> + 'a {
26✔
1084
        self.try_filter_chain_txouts(chain, chain_tip, outpoints)
26✔
1085
            .map(|r| r.expect("oracle is infallible"))
63✔
1086
    }
26✔
1087

1088
    /// Get a filtered list of unspent outputs (UTXOs) from the given `outpoints` that are in
1089
    /// `chain` with `chain_tip`.
1090
    ///
1091
    /// `outpoints` is a list of outpoints we are interested in, coupled with an outpoint identifier
1092
    /// (`OI`) for convenience. If `OI` is not necessary, the caller can use `()`, or
1093
    /// [`Iterator::enumerate`] over a list of [`OutPoint`]s.
1094
    ///
1095
    /// Floating outputs are ignored.
1096
    ///
1097
    /// # Error
1098
    ///
1099
    /// An [`Iterator::Item`] can be an [`Err`] if the [`ChainOracle`] implementation (`chain`)
1100
    /// fails.
1101
    ///
1102
    /// If the [`ChainOracle`] implementation is infallible, [`filter_chain_unspents`] can be used
1103
    /// instead.
1104
    ///
1105
    /// [`filter_chain_unspents`]: Self::filter_chain_unspents
1106
    pub fn try_filter_chain_unspents<'a, C: ChainOracle + 'a, OI: Clone + 'a>(
1,362✔
1107
        &'a self,
1,362✔
1108
        chain: &'a C,
1,362✔
1109
        chain_tip: BlockId,
1,362✔
1110
        outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
1,362✔
1111
    ) -> impl Iterator<Item = Result<(OI, FullTxOut<A>), C::Error>> + 'a {
1,362✔
1112
        self.try_filter_chain_txouts(chain, chain_tip, outpoints)
1,362✔
1113
            .filter(|r| match r {
2,961✔
1114
                // keep unspents, drop spents
1115
                Ok((_, full_txo)) => full_txo.spent_by.is_none(),
2,961✔
1116
                // keep errors
1117
                Err(_) => true,
×
1118
            })
2,961✔
1119
    }
1,362✔
1120

1121
    /// Get a filtered list of unspent outputs (UTXOs) from the given `outpoints` that are in
1122
    /// `chain` with `chain_tip`.
1123
    ///
1124
    /// This is the infallible version of [`try_filter_chain_unspents`].
1125
    ///
1126
    /// [`try_filter_chain_unspents`]: Self::try_filter_chain_unspents
1127
    pub fn filter_chain_unspents<'a, C: ChainOracle<Error = Infallible> + 'a, OI: Clone + 'a>(
1,290✔
1128
        &'a self,
1,290✔
1129
        chain: &'a C,
1,290✔
1130
        chain_tip: BlockId,
1,290✔
1131
        txouts: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
1,290✔
1132
    ) -> impl Iterator<Item = (OI, FullTxOut<A>)> + 'a {
1,290✔
1133
        self.try_filter_chain_unspents(chain, chain_tip, txouts)
1,290✔
1134
            .map(|r| r.expect("oracle is infallible"))
1,417✔
1135
    }
1,290✔
1136

1137
    /// Get the total balance of `outpoints` that are in `chain` of `chain_tip`.
1138
    ///
1139
    /// The output of `trust_predicate` should return `true` for scripts that we trust.
1140
    ///
1141
    /// `outpoints` is a list of outpoints we are interested in, coupled with an outpoint identifier
1142
    /// (`OI`) for convenience. If `OI` is not necessary, the caller can use `()`, or
1143
    /// [`Iterator::enumerate`] over a list of [`OutPoint`]s.
1144
    ///
1145
    /// If the provided [`ChainOracle`] implementation (`chain`) is infallible, [`balance`] can be
1146
    /// used instead.
1147
    ///
1148
    /// [`balance`]: Self::balance
1149
    pub fn try_balance<C: ChainOracle, OI: Clone>(
72✔
1150
        &self,
72✔
1151
        chain: &C,
72✔
1152
        chain_tip: BlockId,
72✔
1153
        outpoints: impl IntoIterator<Item = (OI, OutPoint)>,
72✔
1154
        mut trust_predicate: impl FnMut(&OI, &Script) -> bool,
72✔
1155
    ) -> Result<Balance, C::Error> {
72✔
1156
        let mut immature = Amount::ZERO;
72✔
1157
        let mut trusted_pending = Amount::ZERO;
72✔
1158
        let mut untrusted_pending = Amount::ZERO;
72✔
1159
        let mut confirmed = Amount::ZERO;
72✔
1160

1161
        for res in self.try_filter_chain_unspents(chain, chain_tip, outpoints) {
270✔
1162
            let (spk_i, txout) = res?;
270✔
1163

1164
            match &txout.chain_position {
270✔
1165
                ChainPosition::Confirmed(_) => {
1166
                    if txout.is_confirmed_and_spendable(chain_tip.height) {
148✔
1167
                        confirmed += txout.txout.value;
136✔
1168
                    } else if !txout.is_mature(chain_tip.height) {
136✔
1169
                        immature += txout.txout.value;
12✔
1170
                    }
12✔
1171
                }
1172
                ChainPosition::Unconfirmed(_) => {
1173
                    if trust_predicate(&spk_i, &txout.txout.script_pubkey) {
122✔
1174
                        trusted_pending += txout.txout.value;
117✔
1175
                    } else {
117✔
1176
                        untrusted_pending += txout.txout.value;
5✔
1177
                    }
5✔
1178
                }
1179
            }
1180
        }
1181

1182
        Ok(Balance {
72✔
1183
            immature,
72✔
1184
            trusted_pending,
72✔
1185
            untrusted_pending,
72✔
1186
            confirmed,
72✔
1187
        })
72✔
1188
    }
72✔
1189

1190
    /// Get the total balance of `outpoints` that are in `chain` of `chain_tip`.
1191
    ///
1192
    /// This is the infallible version of [`try_balance`].
1193
    ///
1194
    /// [`try_balance`]: Self::try_balance
1195
    pub fn balance<C: ChainOracle<Error = Infallible>, OI: Clone>(
72✔
1196
        &self,
72✔
1197
        chain: &C,
72✔
1198
        chain_tip: BlockId,
72✔
1199
        outpoints: impl IntoIterator<Item = (OI, OutPoint)>,
72✔
1200
        trust_predicate: impl FnMut(&OI, &Script) -> bool,
72✔
1201
    ) -> Balance {
72✔
1202
        self.try_balance(chain, chain_tip, outpoints, trust_predicate)
72✔
1203
            .expect("oracle is infallible")
72✔
1204
    }
72✔
1205
}
1206

1207
/// The [`ChangeSet`] represents changes to a [`TxGraph`].
1208
///
1209
/// Since [`TxGraph`] is monotone, the "changeset" can only contain transactions to be added and
1210
/// not removed.
1211
///
1212
/// Refer to [module-level documentation] for more.
1213
///
1214
/// [module-level documentation]: crate::tx_graph
1215
#[derive(Debug, Clone, PartialEq)]
1216
#[cfg_attr(
1217
    feature = "serde",
1218
    derive(serde::Deserialize, serde::Serialize),
9✔
1219
    serde(
1220
        crate = "serde_crate",
1221
        bound(
1222
            deserialize = "A: Ord + serde::Deserialize<'de>",
1223
            serialize = "A: Ord + serde::Serialize",
1224
        )
1225
    )
1226
)]
1227
#[must_use]
1228
pub struct ChangeSet<A = ()> {
1229
    /// Added transactions.
1230
    pub txs: BTreeSet<Arc<Transaction>>,
1231
    /// Added txouts.
1232
    pub txouts: BTreeMap<OutPoint, TxOut>,
1233
    /// Added anchors.
1234
    pub anchors: BTreeSet<(A, Txid)>,
1235
    /// Added last-seen unix timestamps of transactions.
1236
    pub last_seen: BTreeMap<Txid, u64>,
1237
}
1238

1239
impl<A> Default for ChangeSet<A> {
1240
    fn default() -> Self {
26,981✔
1241
        Self {
26,981✔
1242
            txs: Default::default(),
26,981✔
1243
            txouts: Default::default(),
26,981✔
1244
            anchors: Default::default(),
26,981✔
1245
            last_seen: Default::default(),
26,981✔
1246
        }
26,981✔
1247
    }
26,981✔
1248
}
1249

1250
impl<A> ChangeSet<A> {
1251
    /// Iterates over all outpoints contained within [`ChangeSet`].
1252
    pub fn txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
×
1253
        self.txs
×
1254
            .iter()
×
1255
            .flat_map(|tx| {
×
1256
                tx.output
×
1257
                    .iter()
×
1258
                    .enumerate()
×
NEW
1259
                    .map(move |(vout, txout)| (OutPoint::new(tx.compute_txid(), vout as _), txout))
×
1260
            })
×
1261
            .chain(self.txouts.iter().map(|(op, txout)| (*op, txout)))
×
1262
    }
×
1263

1264
    /// Iterates over the heights of that the new transaction anchors in this changeset.
1265
    ///
1266
    /// This is useful if you want to find which heights you need to fetch data about in order to
1267
    /// confirm or exclude these anchors.
1268
    pub fn anchor_heights(&self) -> impl Iterator<Item = u32> + '_
×
1269
    where
×
1270
        A: Anchor,
×
1271
    {
×
1272
        let mut dedup = None;
×
1273
        self.anchors
×
1274
            .iter()
×
1275
            .map(|(a, _)| a.anchor_block().height)
×
1276
            .filter(move |height| {
×
1277
                let duplicate = dedup == Some(*height);
×
1278
                dedup = Some(*height);
×
1279
                !duplicate
×
1280
            })
×
1281
    }
×
1282
}
1283

1284
impl<A: Ord> Append for ChangeSet<A> {
1285
    fn append(&mut self, other: Self) {
12,939✔
1286
        // We use `extend` instead of `BTreeMap::append` due to performance issues with `append`.
12,939✔
1287
        // Refer to https://github.com/rust-lang/rust/issues/34666#issuecomment-675658420
12,939✔
1288
        self.txs.extend(other.txs);
12,939✔
1289
        self.txouts.extend(other.txouts);
12,939✔
1290
        self.anchors.extend(other.anchors);
12,939✔
1291

12,939✔
1292
        // last_seen timestamps should only increase
12,939✔
1293
        self.last_seen.extend(
12,939✔
1294
            other
12,939✔
1295
                .last_seen
12,939✔
1296
                .into_iter()
12,939✔
1297
                .filter(|(txid, update_ls)| self.last_seen.get(txid) < Some(update_ls))
12,942✔
1298
                .collect::<Vec<_>>(),
12,939✔
1299
        );
12,939✔
1300
    }
12,939✔
1301

1302
    fn is_empty(&self) -> bool {
3,401✔
1303
        self.txs.is_empty()
3,401✔
1304
            && self.txouts.is_empty()
815✔
1305
            && self.anchors.is_empty()
814✔
1306
            && self.last_seen.is_empty()
809✔
1307
    }
3,401✔
1308
}
1309

1310
impl<A: Ord> ChangeSet<A> {
1311
    /// Transform the [`ChangeSet`] to have [`Anchor`]s of another type.
1312
    ///
1313
    /// This takes in a closure of signature `FnMut(A) -> A2` which is called for each [`Anchor`] to
1314
    /// transform it.
1315
    pub fn map_anchors<A2: Ord, F>(self, mut f: F) -> ChangeSet<A2>
11✔
1316
    where
11✔
1317
        F: FnMut(A) -> A2,
11✔
1318
    {
11✔
1319
        ChangeSet {
11✔
1320
            txs: self.txs,
11✔
1321
            txouts: self.txouts,
11✔
1322
            anchors: BTreeSet::<(A2, Txid)>::from_iter(
11✔
1323
                self.anchors.into_iter().map(|(a, txid)| (f(a), txid)),
41✔
1324
            ),
11✔
1325
            last_seen: self.last_seen,
11✔
1326
        }
11✔
1327
    }
11✔
1328
}
1329

1330
impl<A> AsRef<TxGraph<A>> for TxGraph<A> {
1331
    fn as_ref(&self) -> &TxGraph<A> {
×
1332
        self
×
1333
    }
×
1334
}
1335

1336
/// An iterator that traverses ancestors of a given root transaction.
1337
///
1338
/// The iterator excludes partial transactions.
1339
///
1340
/// Returned by the [`walk_ancestors`] method of [`TxGraph`].
1341
///
1342
/// [`walk_ancestors`]: TxGraph::walk_ancestors
1343
pub struct TxAncestors<'g, A, F> {
1344
    graph: &'g TxGraph<A>,
1345
    visited: HashSet<Txid>,
1346
    queue: VecDeque<(usize, Arc<Transaction>)>,
1347
    filter_map: F,
1348
}
1349

1350
impl<'g, A, F> TxAncestors<'g, A, F> {
1351
    /// Creates a `TxAncestors` that includes the starting `Transaction` when iterating.
1352
    pub(crate) fn new_include_root(
1,106✔
1353
        graph: &'g TxGraph<A>,
1,106✔
1354
        tx: impl Into<Arc<Transaction>>,
1,106✔
1355
        filter_map: F,
1,106✔
1356
    ) -> Self {
1,106✔
1357
        Self {
1,106✔
1358
            graph,
1,106✔
1359
            visited: Default::default(),
1,106✔
1360
            queue: [(0, tx.into())].into(),
1,106✔
1361
            filter_map,
1,106✔
1362
        }
1,106✔
1363
    }
1,106✔
1364

1365
    /// Creates a `TxAncestors` that excludes the starting `Transaction` when iterating.
1366
    pub(crate) fn new_exclude_root(
4✔
1367
        graph: &'g TxGraph<A>,
4✔
1368
        tx: impl Into<Arc<Transaction>>,
4✔
1369
        filter_map: F,
4✔
1370
    ) -> Self {
4✔
1371
        let mut ancestors = Self {
4✔
1372
            graph,
4✔
1373
            visited: Default::default(),
4✔
1374
            queue: Default::default(),
4✔
1375
            filter_map,
4✔
1376
        };
4✔
1377
        ancestors.populate_queue(1, tx.into());
4✔
1378
        ancestors
4✔
1379
    }
4✔
1380

1381
    /// Creates a `TxAncestors` from multiple starting `Transaction`s that includes the starting
1382
    /// `Transaction`s when iterating.
1383
    #[allow(unused)]
1384
    pub(crate) fn from_multiple_include_root<I>(
×
1385
        graph: &'g TxGraph<A>,
×
1386
        txs: I,
×
1387
        filter_map: F,
×
1388
    ) -> Self
×
1389
    where
×
1390
        I: IntoIterator,
×
1391
        I::Item: Into<Arc<Transaction>>,
×
1392
    {
×
1393
        Self {
×
1394
            graph,
×
1395
            visited: Default::default(),
×
1396
            queue: txs.into_iter().map(|tx| (0, tx.into())).collect(),
×
1397
            filter_map,
×
1398
        }
×
1399
    }
×
1400

1401
    /// Creates a `TxAncestors` from multiple starting `Transaction`s that excludes the starting
1402
    /// `Transaction`s when iterating.
1403
    #[allow(unused)]
1404
    pub(crate) fn from_multiple_exclude_root<I>(
×
1405
        graph: &'g TxGraph<A>,
×
1406
        txs: I,
×
1407
        filter_map: F,
×
1408
    ) -> Self
×
1409
    where
×
1410
        I: IntoIterator,
×
1411
        I::Item: Into<Arc<Transaction>>,
×
1412
    {
×
1413
        let mut ancestors = Self {
×
1414
            graph,
×
1415
            visited: Default::default(),
×
1416
            queue: Default::default(),
×
1417
            filter_map,
×
1418
        };
×
1419
        for tx in txs {
×
1420
            ancestors.populate_queue(1, tx.into());
×
1421
        }
×
1422
        ancestors
×
1423
    }
×
1424

1425
    fn populate_queue(&mut self, depth: usize, tx: Arc<Transaction>) {
1,236✔
1426
        let ancestors = tx
1,236✔
1427
            .input
1,236✔
1428
            .iter()
1,236✔
1429
            .map(|txin| txin.previous_output.txid)
1,357✔
1430
            .filter(|&prev_txid| self.visited.insert(prev_txid))
1,357✔
1431
            .filter_map(|prev_txid| self.graph.get_tx(prev_txid))
1,337✔
1432
            .map(|tx| (depth, tx));
1,236✔
1433
        self.queue.extend(ancestors);
1,236✔
1434
    }
1,236✔
1435
}
1436

1437
impl<'g, A, F, O> Iterator for TxAncestors<'g, A, F>
1438
where
1439
    F: FnMut(usize, Arc<Transaction>) -> Option<O>,
1440
{
1441
    type Item = O;
1442

1443
    fn next(&mut self) -> Option<Self::Item> {
2,342✔
1444
        loop {
1445
            // we have exhausted all paths when queue is empty
1446
            let (ancestor_depth, tx) = self.queue.pop_front()?;
3,129✔
1447
            // ignore paths when user filters them out
1448
            let item = match (self.filter_map)(ancestor_depth, tx.clone()) {
2,019✔
1449
                Some(item) => item,
1,232✔
1450
                None => continue,
787✔
1451
            };
1452
            self.populate_queue(ancestor_depth + 1, tx);
1,232✔
1453
            return Some(item);
1,232✔
1454
        }
1455
    }
2,342✔
1456
}
1457

1458
/// An iterator that traverses transaction descendants.
1459
///
1460
/// Returned by the [`walk_descendants`] method of [`TxGraph`].
1461
///
1462
/// [`walk_descendants`]: TxGraph::walk_descendants
1463
pub struct TxDescendants<'g, A, F> {
1464
    graph: &'g TxGraph<A>,
1465
    visited: HashSet<Txid>,
1466
    queue: VecDeque<(usize, Txid)>,
1467
    filter_map: F,
1468
}
1469

1470
impl<'g, A, F> TxDescendants<'g, A, F> {
1471
    /// Creates a `TxDescendants` that includes the starting `txid` when iterating.
1472
    #[allow(unused)]
1473
    pub(crate) fn new_include_root(graph: &'g TxGraph<A>, txid: Txid, filter_map: F) -> Self {
1,106✔
1474
        Self {
1,106✔
1475
            graph,
1,106✔
1476
            visited: Default::default(),
1,106✔
1477
            queue: [(0, txid)].into(),
1,106✔
1478
            filter_map,
1,106✔
1479
        }
1,106✔
1480
    }
1,106✔
1481

1482
    /// Creates a `TxDescendants` that excludes the starting `txid` when iterating.
1483
    pub(crate) fn new_exclude_root(graph: &'g TxGraph<A>, txid: Txid, filter_map: F) -> Self {
1✔
1484
        let mut descendants = Self {
1✔
1485
            graph,
1✔
1486
            visited: Default::default(),
1✔
1487
            queue: Default::default(),
1✔
1488
            filter_map,
1✔
1489
        };
1✔
1490
        descendants.populate_queue(1, txid);
1✔
1491
        descendants
1✔
1492
    }
1✔
1493

1494
    /// Creates a `TxDescendants` from multiple starting transactions that includes the starting
1495
    /// `txid`s when iterating.
1496
    pub(crate) fn from_multiple_include_root<I>(
1,203✔
1497
        graph: &'g TxGraph<A>,
1,203✔
1498
        txids: I,
1,203✔
1499
        filter_map: F,
1,203✔
1500
    ) -> Self
1,203✔
1501
    where
1,203✔
1502
        I: IntoIterator<Item = Txid>,
1,203✔
1503
    {
1,203✔
1504
        Self {
1,203✔
1505
            graph,
1,203✔
1506
            visited: Default::default(),
1,203✔
1507
            queue: txids.into_iter().map(|txid| (0, txid)).collect(),
1,203✔
1508
            filter_map,
1,203✔
1509
        }
1,203✔
1510
    }
1,203✔
1511

1512
    /// Creates a `TxDescendants` from multiple starting transactions that excludes the starting
1513
    /// `txid`s when iterating.
1514
    #[allow(unused)]
1515
    pub(crate) fn from_multiple_exclude_root<I>(
×
1516
        graph: &'g TxGraph<A>,
×
1517
        txids: I,
×
1518
        filter_map: F,
×
1519
    ) -> Self
×
1520
    where
×
1521
        I: IntoIterator<Item = Txid>,
×
1522
    {
×
1523
        let mut descendants = Self {
×
1524
            graph,
×
1525
            visited: Default::default(),
×
1526
            queue: Default::default(),
×
1527
            filter_map,
×
1528
        };
×
1529
        for txid in txids {
×
1530
            descendants.populate_queue(1, txid);
×
1531
        }
×
1532
        descendants
×
1533
    }
×
1534
}
1535

1536
impl<'g, A, F> TxDescendants<'g, A, F> {
1537
    fn populate_queue(&mut self, depth: usize, txid: Txid) {
1,473✔
1538
        let spend_paths = self
1,473✔
1539
            .graph
1,473✔
1540
            .spends
1,473✔
1541
            .range(tx_outpoint_range(txid))
1,473✔
1542
            .flat_map(|(_, spends)| spends)
1,473✔
1543
            .map(|&txid| (depth, txid));
1,473✔
1544
        self.queue.extend(spend_paths);
1,473✔
1545
    }
1,473✔
1546
}
1547

1548
impl<'g, A, F, O> Iterator for TxDescendants<'g, A, F>
1549
where
1550
    F: FnMut(usize, Txid) -> Option<O>,
1551
{
1552
    type Item = O;
1553

1554
    fn next(&mut self) -> Option<Self::Item> {
3,660✔
1555
        let (op_spends, txid, item) = loop {
1,472✔
1556
            // we have exhausted all paths when queue is empty
1557
            let (op_spends, txid) = self.queue.pop_front()?;
3,661✔
1558
            // we do not want to visit the same transaction twice
1559
            if self.visited.insert(txid) {
1,473✔
1560
                // ignore paths when user filters them out
1561
                if let Some(item) = (self.filter_map)(op_spends, txid) {
1,472✔
1562
                    break (op_spends, txid, item);
1,472✔
1563
                }
×
1564
            }
1✔
1565
        };
1566

1567
        self.populate_queue(op_spends + 1, txid);
1,472✔
1568
        Some(item)
1,472✔
1569
    }
3,660✔
1570
}
1571

1572
fn tx_outpoint_range(txid: Txid) -> RangeInclusive<OutPoint> {
20,548✔
1573
    OutPoint::new(txid, u32::MIN)..=OutPoint::new(txid, u32::MAX)
20,548✔
1574
}
20,548✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc