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

bitcoindevkit / bdk / 6009337347

29 Aug 2023 07:39AM UTC coverage: 78.345% (-0.3%) from 78.671%
6009337347

Pull #1100

github

web-flow
Merge eaefc3e16 into e5fb1ec7f
Pull Request #1100: Refactor `TxGraph::walk_conflicts` to check for ancestors

85 of 85 new or added lines in 1 file covered. (100.0%)

8057 of 10284 relevant lines covered (78.35%)

4999.29 hits per line

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

82.35
/crates/chain/src/tx_graph.rs
1
//! Module for structures that store and traverse transactions.
2
//!
3
//! [`TxGraph`] is a monotone structure that inserts transactions and indexes the spends. The
4
//! [`ChangeSet`] structure reports changes of [`TxGraph`] but can also be applied to a
5
//! [`TxGraph`] as well. Lastly, [`TxDescendants`] is an [`Iterator`] that traverses descendants of
6
//! a given transaction.
7
//!
8
//! Conflicting transactions are allowed to coexist within a [`TxGraph`]. This is useful for
9
//! identifying and traversing conflicts and descendants of a given transaction.
10
//!
11
//! # Applying changes
12
//!
13
//! Methods that apply changes to [`TxGraph`] will return [`ChangeSet`].
14
//! [`ChangeSet`] can be applied back to a [`TxGraph`] or be used to inform persistent storage
15
//! of the changes to [`TxGraph`].
16
//!
17
//! ```
18
//! # use bdk_chain::BlockId;
19
//! # use bdk_chain::tx_graph::TxGraph;
20
//! # use bdk_chain::example_utils::*;
21
//! # use bitcoin::Transaction;
22
//! # let tx_a = tx_from_hex(RAW_TX_1);
23
//! let mut graph: TxGraph = TxGraph::default();
24
//! let mut another_graph: TxGraph = TxGraph::default();
25
//!
26
//! // insert a transaction
27
//! let changeset = graph.insert_tx(tx_a);
28
//!
29
//! // the resulting changeset can be applied to another tx graph
30
//! another_graph.apply_changeset(changeset);
31
//! ```
32
//!
33
//! A [`TxGraph`] can also be updated with another [`TxGraph`].
34
//!
35
//! ```
36
//! # use bdk_chain::BlockId;
37
//! # use bdk_chain::tx_graph::TxGraph;
38
//! # use bdk_chain::example_utils::*;
39
//! # use bitcoin::Transaction;
40
//! # let tx_a = tx_from_hex(RAW_TX_1);
41
//! # let tx_b = tx_from_hex(RAW_TX_2);
42
//! let mut graph: TxGraph = TxGraph::default();
43
//! let update = TxGraph::new(vec![tx_a, tx_b]);
44
//!
45
//! // apply the update graph
46
//! let changeset = graph.apply_update(update.clone());
47
//!
48
//! // if we apply it again, the resulting changeset will be empty
49
//! let changeset = graph.apply_update(update);
50
//! assert!(changeset.is_empty());
51
//! ```
52

53
use crate::{
54
    collections::*, keychain::Balance, local_chain::LocalChain, Anchor, Append, BlockId,
55
    ChainOracle, ChainPosition, ForEachTxOut, FullTxOut,
56
};
57
use alloc::vec::Vec;
58
use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid};
59
use core::{
60
    convert::Infallible,
61
    ops::{Deref, RangeInclusive},
62
};
63

64
/// A graph of transactions and spends.
65
///
66
/// See the [module-level documentation] for more.
67
///
68
/// [module-level documentation]: crate::tx_graph
69
#[derive(Clone, Debug, PartialEq)]
1✔
70
pub struct TxGraph<A = ()> {
71
    // all transactions that the graph is aware of in format: `(tx_node, tx_anchors, tx_last_seen)`
72
    txs: HashMap<Txid, (TxNodeInternal, BTreeSet<A>, u64)>,
73
    spends: BTreeMap<OutPoint, HashSet<Txid>>,
74
    anchors: BTreeSet<(A, Txid)>,
75

76
    // This atrocity exists so that `TxGraph::outspends()` can return a reference.
77
    // FIXME: This can be removed once `HashSet::new` is a const fn.
78
    empty_outspends: HashSet<Txid>,
79
}
80

81
impl<A> Default for TxGraph<A> {
82
    fn default() -> Self {
850✔
83
        Self {
850✔
84
            txs: Default::default(),
850✔
85
            spends: Default::default(),
850✔
86
            anchors: Default::default(),
850✔
87
            empty_outspends: Default::default(),
850✔
88
        }
850✔
89
    }
850✔
90
}
91

92
/// An outward-facing view of a (transaction) node in the [`TxGraph`].
93
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
×
94
pub struct TxNode<'a, T, A> {
95
    /// Txid of the transaction.
96
    pub txid: Txid,
97
    /// A partial or full representation of the transaction.
98
    pub tx: &'a T,
99
    /// The blocks that the transaction is "anchored" in.
100
    pub anchors: &'a BTreeSet<A>,
101
    /// The last-seen unix timestamp of the transaction as unconfirmed.
102
    pub last_seen_unconfirmed: u64,
103
}
104

105
impl<'a, T, A> Deref for TxNode<'a, T, A> {
106
    type Target = T;
107

108
    fn deref(&self) -> &Self::Target {
×
109
        self.tx
×
110
    }
×
111
}
112

113
/// Internal representation of a transaction node of a [`TxGraph`].
114
///
115
/// This can either be a whole transaction, or a partial transaction (where we only have select
116
/// outputs).
117
#[derive(Clone, Debug, PartialEq)]
3✔
118
enum TxNodeInternal {
119
    Whole(Transaction),
120
    Partial(BTreeMap<u32, TxOut>),
121
}
122

123
impl Default for TxNodeInternal {
124
    fn default() -> Self {
832✔
125
        Self::Partial(BTreeMap::new())
832✔
126
    }
832✔
127
}
128

129
/// An outwards-facing view of a transaction that is part of the *best chain*'s history.
130
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
×
131
pub struct CanonicalTx<'a, T, A> {
132
    /// How the transaction is observed as (confirmed or unconfirmed).
133
    pub chain_position: ChainPosition<&'a A>,
134
    /// The transaction node (as part of the graph).
135
    pub tx_node: TxNode<'a, T, A>,
136
}
137

138
impl<A> TxGraph<A> {
139
    /// Iterate over all tx outputs known by [`TxGraph`].
140
    ///
141
    /// This includes txouts of both full transactions as well as floating transactions.
142
    pub fn all_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
1✔
143
        self.txs.iter().flat_map(|(txid, (tx, _, _))| match tx {
3✔
144
            TxNodeInternal::Whole(tx) => tx
1✔
145
                .output
1✔
146
                .iter()
1✔
147
                .enumerate()
1✔
148
                .map(|(vout, txout)| (OutPoint::new(*txid, vout as _), txout))
1✔
149
                .collect::<Vec<_>>(),
1✔
150
            TxNodeInternal::Partial(txouts) => txouts
2✔
151
                .iter()
2✔
152
                .map(|(vout, txout)| (OutPoint::new(*txid, *vout as _), txout))
3✔
153
                .collect::<Vec<_>>(),
2✔
154
        })
3✔
155
    }
1✔
156

157
    /// Iterate over floating txouts known by [`TxGraph`].
158
    ///
159
    /// Floating txouts are txouts that do not have the residing full transaction contained in the
160
    /// graph.
161
    pub fn floating_txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
1✔
162
        self.txs
1✔
163
            .iter()
1✔
164
            .filter_map(|(txid, (tx_node, _, _))| match tx_node {
3✔
165
                TxNodeInternal::Whole(_) => None,
1✔
166
                TxNodeInternal::Partial(txouts) => Some(
2✔
167
                    txouts
2✔
168
                        .iter()
2✔
169
                        .map(|(&vout, txout)| (OutPoint::new(*txid, vout), txout)),
3✔
170
                ),
2✔
171
            })
3✔
172
            .flatten()
1✔
173
    }
1✔
174

175
    /// Iterate over all full transactions in the graph.
176
    pub fn full_txs(&self) -> impl Iterator<Item = TxNode<'_, Transaction, A>> {
8✔
177
        self.txs
8✔
178
            .iter()
8✔
179
            .filter_map(|(&txid, (tx, anchors, last_seen))| match tx {
10✔
180
                TxNodeInternal::Whole(tx) => Some(TxNode {
8✔
181
                    txid,
8✔
182
                    tx,
8✔
183
                    anchors,
8✔
184
                    last_seen_unconfirmed: *last_seen,
8✔
185
                }),
8✔
186
                TxNodeInternal::Partial(_) => None,
2✔
187
            })
10✔
188
    }
8✔
189

190
    /// Get a transaction by txid. This only returns `Some` for full transactions.
191
    ///
192
    /// Refer to [`get_txout`] for getting a specific [`TxOut`].
193
    ///
194
    /// [`get_txout`]: Self::get_txout
195
    pub fn get_tx(&self, txid: Txid) -> Option<&Transaction> {
480✔
196
        self.get_tx_node(txid).map(|n| n.tx)
480✔
197
    }
480✔
198

199
    /// Get a transaction node by txid. This only returns `Some` for full transactions.
200
    pub fn get_tx_node(&self, txid: Txid) -> Option<TxNode<'_, Transaction, A>> {
1,080✔
201
        match &self.txs.get(&txid)? {
1,080✔
202
            (TxNodeInternal::Whole(tx), anchors, last_seen) => Some(TxNode {
899✔
203
                txid,
899✔
204
                tx,
899✔
205
                anchors,
899✔
206
                last_seen_unconfirmed: *last_seen,
899✔
207
            }),
899✔
208
            _ => None,
×
209
        }
210
    }
1,079✔
211

212
    /// Obtains a single tx output (if any) at the specified outpoint.
213
    pub fn get_txout(&self, outpoint: OutPoint) -> Option<&TxOut> {
156✔
214
        match &self.txs.get(&outpoint.txid)?.0 {
156✔
215
            TxNodeInternal::Whole(tx) => tx.output.get(outpoint.vout as usize),
154✔
216
            TxNodeInternal::Partial(txouts) => txouts.get(&outpoint.vout),
1✔
217
        }
218
    }
156✔
219

220
    /// Returns known outputs of a given `txid`.
221
    ///
222
    /// Returns a [`BTreeMap`] of vout to output of the provided `txid`.
223
    pub fn tx_outputs(&self, txid: Txid) -> Option<BTreeMap<u32, &TxOut>> {
2✔
224
        Some(match &self.txs.get(&txid)?.0 {
2✔
225
            TxNodeInternal::Whole(tx) => tx
1✔
226
                .output
1✔
227
                .iter()
1✔
228
                .enumerate()
1✔
229
                .map(|(vout, txout)| (vout as u32, txout))
1✔
230
                .collect::<BTreeMap<_, _>>(),
1✔
231
            TxNodeInternal::Partial(txouts) => txouts
1✔
232
                .iter()
1✔
233
                .map(|(vout, txout)| (*vout, txout))
2✔
234
                .collect::<BTreeMap<_, _>>(),
1✔
235
        })
236
    }
2✔
237

238
    /// Calculates the fee of a given transaction. Returns 0 if `tx` is a coinbase transaction.
239
    /// Returns `Some(_)` if we have all the `TxOut`s being spent by `tx` in the graph (either as
240
    /// the full transactions or individual txouts). If the returned value is negative, then the
241
    /// transaction is invalid according to the graph.
242
    ///
243
    /// Returns `None` if we're missing an input for the tx in the graph.
244
    ///
245
    /// Note `tx` does not have to be in the graph for this to work.
246
    pub fn calculate_fee(&self, tx: &Transaction) -> Option<i64> {
21✔
247
        if tx.is_coin_base() {
21✔
248
            return Some(0);
1✔
249
        }
20✔
250
        let inputs_sum = tx
20✔
251
            .input
20✔
252
            .iter()
20✔
253
            .map(|txin| {
26✔
254
                self.get_txout(txin.previous_output)
26✔
255
                    .map(|txout| txout.value as i64)
26✔
256
            })
26✔
257
            .sum::<Option<i64>>()?;
20✔
258

259
        let outputs_sum = tx
19✔
260
            .output
19✔
261
            .iter()
19✔
262
            .map(|txout| txout.value as i64)
29✔
263
            .sum::<i64>();
19✔
264

19✔
265
        Some(inputs_sum - outputs_sum)
19✔
266
    }
21✔
267

268
    /// The transactions spending from this output.
269
    ///
270
    /// `TxGraph` allows conflicting transactions within the graph. Obviously the transactions in
271
    /// the returned set will never be in the same active-chain.
272
    pub fn outspends(&self, outpoint: OutPoint) -> &HashSet<Txid> {
4✔
273
        self.spends.get(&outpoint).unwrap_or(&self.empty_outspends)
4✔
274
    }
4✔
275

276
    /// Iterates over the transactions spending from `txid`.
277
    ///
278
    /// The iterator item is a union of `(vout, txid-set)` where:
279
    ///
280
    /// - `vout` is the provided `txid`'s outpoint that is being spent
281
    /// - `txid-set` is the set of txids spending the `vout`.
282
    pub fn tx_spends(
1✔
283
        &self,
1✔
284
        txid: Txid,
1✔
285
    ) -> impl DoubleEndedIterator<Item = (u32, &HashSet<Txid>)> + '_ {
1✔
286
        let start = OutPoint { txid, vout: 0 };
1✔
287
        let end = OutPoint {
1✔
288
            txid,
1✔
289
            vout: u32::MAX,
1✔
290
        };
1✔
291
        self.spends
1✔
292
            .range(start..=end)
1✔
293
            .map(|(outpoint, spends)| (outpoint.vout, spends))
1✔
294
    }
1✔
295

296
    /// Creates an iterator that filters and maps descendants from the starting `txid`.
297
    ///
298
    /// The supplied closure takes in two inputs `(depth, descendant_txid)`:
299
    ///
300
    /// * `depth` is the distance between the starting `txid` and the `descendant_txid`. I.e., if the
301
    ///     descendant is spending an output of the starting `txid`; the `depth` will be 1.
302
    /// * `descendant_txid` is the descendant's txid which we are considering to walk.
303
    ///
304
    /// The supplied closure returns an `Option<T>`, allowing the caller to map each node it vists
305
    /// and decide whether to visit descendants.
306
    pub fn walk_descendants<'g, F, O>(&'g self, txid: Txid, walk_map: F) -> TxDescendants<A, F>
1✔
307
    where
1✔
308
        F: FnMut(usize, Txid) -> Option<O> + 'g,
1✔
309
    {
1✔
310
        TxDescendants::new_exclude_root(self, txid, walk_map)
1✔
311
    }
1✔
312

313
    /// Creates an iterator that both filters and maps conflicting transactions (this includes
314
    /// ancestors and descendants of directly-conflicting transactions, which are also considered
315
    /// conflicts).
316
    ///
317
    /// Refer to [`Self::walk_descendants`] for `walk_map` usage.
318
    pub fn walk_conflicts<'g, F, O>(
337✔
319
        &'g self,
337✔
320
        tx: &'g Transaction,
337✔
321
        depth_limit: usize,
337✔
322
        walk_map: F,
337✔
323
    ) -> TxDescendants<A, F>
337✔
324
    where
337✔
325
        F: FnMut(usize, Txid) -> Option<O> + 'g,
337✔
326
    {
337✔
327
        let mut txids = HashSet::new();
337✔
328
        for (_, tx) in TxAncestors::new_include_root(self, tx.clone(), depth_limit) {
668✔
329
            txids.extend(self.direct_conflicts_of_tx(&tx).map(|(_, txid)| txid));
668✔
330
        }
668✔
331
        TxDescendants::from_multiple_include_root(self, txids, walk_map)
337✔
332
    }
337✔
333

334
    /// Given a transaction, return an iterator of txids that directly conflict with the given
335
    /// transaction's inputs (spends). The conflicting txids are returned with the given
336
    /// transaction's vin (in which it conflicts).
337
    ///
338
    /// Note that this only returns directly conflicting txids and does not include descendants of
339
    /// those txids or conflicting txids of the given transaction's ancestors (which are technically
340
    /// also conflicting).
341
    pub fn direct_conflicts_of_tx<'g>(
668✔
342
        &'g self,
668✔
343
        tx: &'g Transaction,
668✔
344
    ) -> impl Iterator<Item = (usize, Txid)> + '_ {
668✔
345
        let txid = tx.txid();
668✔
346
        tx.input
668✔
347
            .iter()
668✔
348
            .enumerate()
668✔
349
            .filter_map(move |(vin, txin)| self.spends.get(&txin.previous_output).zip(Some(vin)))
668✔
350
            .flat_map(|(spends, vin)| core::iter::repeat(vin).zip(spends.iter().cloned()))
668✔
351
            .filter(move |(_, conflicting_txid)| *conflicting_txid != txid)
881✔
352
    }
668✔
353

354
    /// Get all transaction anchors known by [`TxGraph`].
355
    pub fn all_anchors(&self) -> &BTreeSet<(A, Txid)> {
×
356
        &self.anchors
×
357
    }
×
358

359
    /// Whether the graph has any transactions or outputs in it.
360
    pub fn is_empty(&self) -> bool {
×
361
        self.txs.is_empty()
×
362
    }
×
363
}
364

365
impl<A: Clone + Ord> TxGraph<A> {
366
    /// Construct a new [`TxGraph`] from a list of transactions.
367
    pub fn new(txs: impl IntoIterator<Item = Transaction>) -> Self {
×
368
        let mut new = Self::default();
×
369
        for tx in txs.into_iter() {
×
370
            let _ = new.insert_tx(tx);
×
371
        }
×
372
        new
×
373
    }
×
374

375
    /// Inserts the given [`TxOut`] at [`OutPoint`].
376
    ///
377
    /// Inserting floating txouts are useful for determining fee/feerate of transactions we care
378
    /// about.
379
    ///
380
    /// The [`ChangeSet`] result will be empty if the `outpoint` (or a full transaction containing
381
    /// the `outpoint`) already existed in `self`.
382
    ///
383
    /// [`apply_changeset`]: Self::apply_changeset
384
    pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<A> {
8✔
385
        let mut update = Self::default();
8✔
386
        update.txs.insert(
8✔
387
            outpoint.txid,
8✔
388
            (
8✔
389
                TxNodeInternal::Partial([(outpoint.vout, txout)].into()),
8✔
390
                BTreeSet::new(),
8✔
391
                0,
8✔
392
            ),
8✔
393
        );
8✔
394
        self.apply_update(update)
8✔
395
    }
8✔
396

397
    /// Inserts the given transaction into [`TxGraph`].
398
    ///
399
    /// The [`ChangeSet`] returned will be empty if `tx` already exists.
400
    pub fn insert_tx(&mut self, tx: Transaction) -> ChangeSet<A> {
255✔
401
        let mut update = Self::default();
255✔
402
        update
255✔
403
            .txs
255✔
404
            .insert(tx.txid(), (TxNodeInternal::Whole(tx), BTreeSet::new(), 0));
255✔
405
        self.apply_update(update)
255✔
406
    }
255✔
407

408
    /// Inserts the given `anchor` into [`TxGraph`].
409
    ///
410
    /// The [`ChangeSet`] returned will be empty if graph already knows that `txid` exists in
411
    /// `anchor`.
412
    pub fn insert_anchor(&mut self, txid: Txid, anchor: A) -> ChangeSet<A> {
181✔
413
        let mut update = Self::default();
181✔
414
        update.anchors.insert((anchor, txid));
181✔
415
        self.apply_update(update)
181✔
416
    }
181✔
417

418
    /// Inserts the given `seen_at` for `txid` into [`TxGraph`].
419
    ///
420
    /// Note that [`TxGraph`] only keeps track of the lastest `seen_at`.
421
    pub fn insert_seen_at(&mut self, txid: Txid, seen_at: u64) -> ChangeSet<A> {
50✔
422
        let mut update = Self::default();
50✔
423
        let (_, _, update_last_seen) = update.txs.entry(txid).or_default();
50✔
424
        *update_last_seen = seen_at;
50✔
425
        self.apply_update(update)
50✔
426
    }
50✔
427

428
    /// Extends this graph with another so that `self` becomes the union of the two sets of
429
    /// transactions.
430
    ///
431
    /// The returned [`ChangeSet`] is the set difference between `update` and `self` (transactions that
432
    /// exist in `update` but not in `self`).
433
    pub fn apply_update(&mut self, update: TxGraph<A>) -> ChangeSet<A> {
676✔
434
        let changeset = self.determine_changeset(update);
676✔
435
        self.apply_changeset(changeset.clone());
676✔
436
        changeset
676✔
437
    }
676✔
438

439
    /// Determines the [`ChangeSet`] between `self` and an empty [`TxGraph`].
440
    pub fn initial_changeset(&self) -> ChangeSet<A> {
1✔
441
        Self::default().determine_changeset(self.clone())
1✔
442
    }
1✔
443

444
    /// Applies [`ChangeSet`] to [`TxGraph`].
445
    pub fn apply_changeset(&mut self, changeset: ChangeSet<A>) {
816✔
446
        for tx in changeset.txs {
1,253✔
447
            let txid = tx.txid();
437✔
448

437✔
449
            tx.input
437✔
450
                .iter()
437✔
451
                .map(|txin| txin.previous_output)
437✔
452
                // coinbase spends are not to be counted
437✔
453
                .filter(|outpoint| !outpoint.is_null())
437✔
454
                // record spend as this tx has spent this outpoint
437✔
455
                .for_each(|outpoint| {
437✔
456
                    self.spends.entry(outpoint).or_default().insert(txid);
136✔
457
                });
437✔
458

437✔
459
            match self.txs.get_mut(&txid) {
437✔
460
                Some((tx_node @ TxNodeInternal::Partial(_), _, _)) => {
1✔
461
                    *tx_node = TxNodeInternal::Whole(tx);
1✔
462
                }
1✔
463
                Some((TxNodeInternal::Whole(tx), _, _)) => {
1✔
464
                    debug_assert_eq!(
465
                        tx.txid(),
1✔
466
                        txid,
467
                        "tx should produce txid that is same as key"
×
468
                    );
469
                }
470
                None => {
435✔
471
                    self.txs
435✔
472
                        .insert(txid, (TxNodeInternal::Whole(tx), BTreeSet::new(), 0));
435✔
473
                }
435✔
474
            }
475
        }
476

477
        for (outpoint, txout) in changeset.txouts {
823✔
478
            let tx_entry = self
7✔
479
                .txs
7✔
480
                .entry(outpoint.txid)
7✔
481
                .or_insert_with(Default::default);
7✔
482

7✔
483
            match tx_entry {
7✔
484
                (TxNodeInternal::Whole(_), _, _) => { /* do nothing since we already have full tx */
×
485
                }
×
486
                (TxNodeInternal::Partial(txouts), _, _) => {
7✔
487
                    txouts.insert(outpoint.vout, txout);
7✔
488
                }
7✔
489
            }
490
        }
491

492
        for (anchor, txid) in changeset.anchors {
1,149✔
493
            if self.anchors.insert((anchor.clone(), txid)) {
333✔
494
                let (_, anchors, _) = self.txs.entry(txid).or_insert_with(Default::default);
331✔
495
                anchors.insert(anchor);
331✔
496
            }
331✔
497
        }
498

499
        for (txid, new_last_seen) in changeset.last_seen {
853✔
500
            let (_, _, last_seen) = self.txs.entry(txid).or_insert_with(Default::default);
39✔
501
            if new_last_seen > *last_seen {
39✔
502
                *last_seen = new_last_seen;
36✔
503
            }
36✔
504
        }
505
    }
814✔
506

507
    /// Previews the resultant [`ChangeSet`] when [`Self`] is updated against the `update` graph.
508
    ///
509
    /// The [`ChangeSet`] would be the set difference between `update` and `self` (transactions that
510
    /// exist in `update` but not in `self`).
511
    pub(crate) fn determine_changeset(&self, update: TxGraph<A>) -> ChangeSet<A> {
678✔
512
        let mut changeset = ChangeSet::default();
678✔
513

514
        for (&txid, (update_tx_node, _, update_last_seen)) in &update.txs {
1,176✔
515
            let prev_last_seen: u64 = match (self.txs.get(&txid), update_tx_node) {
499✔
516
                (None, TxNodeInternal::Whole(update_tx)) => {
437✔
517
                    changeset.txs.insert(update_tx.clone());
437✔
518
                    0
437✔
519
                }
520
                (None, TxNodeInternal::Partial(update_txos)) => {
7✔
521
                    changeset.txouts.extend(
7✔
522
                        update_txos
7✔
523
                            .iter()
7✔
524
                            .map(|(&vout, txo)| (OutPoint::new(txid, vout), txo.clone())),
8✔
525
                    );
7✔
526
                    0
7✔
527
                }
528
                (Some((TxNodeInternal::Whole(_), _, last_seen)), _) => *last_seen,
51✔
529
                (
530
                    Some((TxNodeInternal::Partial(_), _, last_seen)),
1✔
531
                    TxNodeInternal::Whole(update_tx),
1✔
532
                ) => {
1✔
533
                    changeset.txs.insert(update_tx.clone());
1✔
534
                    *last_seen
1✔
535
                }
536
                (
537
                    Some((TxNodeInternal::Partial(txos), _, last_seen)),
3✔
538
                    TxNodeInternal::Partial(update_txos),
3✔
539
                ) => {
3✔
540
                    changeset.txouts.extend(
3✔
541
                        update_txos
3✔
542
                            .iter()
3✔
543
                            .filter(|(vout, _)| !txos.contains_key(*vout))
3✔
544
                            .map(|(&vout, txo)| (OutPoint::new(txid, vout), txo.clone())),
3✔
545
                    );
3✔
546
                    *last_seen
3✔
547
                }
548
            };
549

550
            if *update_last_seen > prev_last_seen {
499✔
551
                changeset.last_seen.insert(txid, *update_last_seen);
37✔
552
            }
461✔
553
        }
554

555
        changeset.anchors = update.anchors.difference(&self.anchors).cloned().collect();
677✔
556

677✔
557
        changeset
677✔
558
    }
677✔
559
}
560

561
impl<A: Anchor> TxGraph<A> {
562
    /// Find missing block heights of `chain`.
563
    ///
564
    /// This works by scanning through anchors, and seeing whether the anchor block of the anchor
565
    /// exists in the [`LocalChain`]. The returned iterator does not output duplicate heights.
566
    pub fn missing_heights<'a>(&'a self, chain: &'a LocalChain) -> impl Iterator<Item = u32> + 'a {
7✔
567
        // Map of txids to skip.
7✔
568
        //
7✔
569
        // Usually, if a height of a tx anchor is missing from the chain, we would want to return
7✔
570
        // this height in the iterator. The exception is when the tx is confirmed in chain. All the
7✔
571
        // other missing-height anchors of this tx can be skipped.
7✔
572
        //
7✔
573
        // * Some(true)  => skip all anchors of this txid
7✔
574
        // * Some(false) => do not skip anchors of this txid
7✔
575
        // * None        => we do not know whether we can skip this txid
7✔
576
        let mut txids_to_skip = HashMap::<Txid, bool>::new();
7✔
577

7✔
578
        // Keeps track of the last height emitted so we don't double up.
7✔
579
        let mut last_height_emitted = Option::<u32>::None;
7✔
580

7✔
581
        self.anchors
7✔
582
            .iter()
7✔
583
            .filter(move |(_, txid)| {
14✔
584
                let skip = *txids_to_skip.entry(*txid).or_insert_with(|| {
14✔
585
                    let tx_anchors = match self.txs.get(txid) {
9✔
586
                        Some((_, anchors, _)) => anchors,
9✔
587
                        None => return true,
×
588
                    };
589
                    let mut has_missing_height = false;
9✔
590
                    for anchor_block in tx_anchors.iter().map(Anchor::anchor_block) {
14✔
591
                        match chain.blocks().get(&anchor_block.height) {
14✔
592
                            None => {
593
                                has_missing_height = true;
8✔
594
                                continue;
8✔
595
                            }
596
                            Some(chain_hash) => {
6✔
597
                                if chain_hash == &anchor_block.hash {
6✔
598
                                    return true;
2✔
599
                                }
4✔
600
                            }
601
                        }
602
                    }
603
                    !has_missing_height
7✔
604
                });
14✔
605
                #[cfg(feature = "std")]
606
                debug_assert!({
607
                    println!("txid={} skip={}", txid, skip);
14✔
608
                    true
14✔
609
                });
610
                !skip
14✔
611
            })
14✔
612
            .filter_map(move |(a, _)| {
8✔
613
                let anchor_block = a.anchor_block();
8✔
614
                if Some(anchor_block.height) != last_height_emitted
8✔
615
                    && !chain.blocks().contains_key(&anchor_block.height)
6✔
616
                {
617
                    last_height_emitted = Some(anchor_block.height);
5✔
618
                    Some(anchor_block.height)
5✔
619
                } else {
620
                    None
3✔
621
                }
622
            })
8✔
623
    }
7✔
624

625
    /// Get the position of the transaction in `chain` with tip `chain_tip`.
626
    ///
627
    /// If the given transaction of `txid` does not exist in the chain of `chain_tip`, `None` is
628
    /// returned.
629
    ///
630
    /// # Error
631
    ///
632
    /// An error will occur if the [`ChainOracle`] implementation (`chain`) fails. If the
633
    /// [`ChainOracle`] is infallible, [`get_chain_position`] can be used instead.
634
    ///
635
    /// [`get_chain_position`]: Self::get_chain_position
636
    pub fn try_get_chain_position<C: ChainOracle>(
1,058✔
637
        &self,
1,058✔
638
        chain: &C,
1,058✔
639
        chain_tip: BlockId,
1,058✔
640
        txid: Txid,
1,058✔
641
    ) -> Result<Option<ChainPosition<&A>>, C::Error> {
1,058✔
642
        let (tx_node, anchors, last_seen) = match self.txs.get(&txid) {
1,058✔
643
            Some(v) => v,
1,055✔
644
            None => return Ok(None),
3✔
645
        };
646

647
        for anchor in anchors {
1,096✔
648
            match chain.is_block_in_chain(anchor.anchor_block(), chain_tip)? {
760✔
649
                Some(true) => return Ok(Some(ChainPosition::Confirmed(anchor))),
719✔
650
                _ => continue,
41✔
651
            }
652
        }
653

654
        // The tx is not anchored to a block which is in the best chain, let's check whether we can
655
        // ignore it by checking conflicts!
656
        let tx = match tx_node {
336✔
657
            TxNodeInternal::Whole(tx) => tx, // check_tx_conflicts.push((tx, last_seen, 0)),
336✔
658
            TxNodeInternal::Partial(_) => {
659
                // Partial transactions (outputs only) cannot have conflicts.
660
                return Ok(None);
×
661
            }
662
        };
663

664
        // If a conflicting tx is in the best chain, or has `last_seen` higher than this tx, then
665
        // this tx cannot exist in the best chain
666
        for conflicting_tx in self.walk_conflicts(tx, 25, |_, txid| self.get_tx_node(txid)) {
336✔
667
            for block in conflicting_tx.anchors.iter().map(A::anchor_block) {
202✔
668
                if chain.is_block_in_chain(block, chain_tip)? == Some(true) {
65✔
669
                    // conflicting tx is in best chain, so the current tx cannot be in best chain!
670
                    return Ok(None);
47✔
671
                }
18✔
672
            }
673
            if conflicting_tx.last_seen_unconfirmed > *last_seen {
155✔
674
                return Ok(None);
51✔
675
            }
104✔
676
        }
677

678
        Ok(Some(ChainPosition::Unconfirmed(*last_seen)))
238✔
679
    }
1,058✔
680

681
    /// Get the position of the transaction in `chain` with tip `chain_tip`.
682
    ///
683
    /// This is the infallible version of [`try_get_chain_position`].
684
    ///
685
    /// [`try_get_chain_position`]: Self::try_get_chain_position
686
    pub fn get_chain_position<C: ChainOracle<Error = Infallible>>(
208✔
687
        &self,
208✔
688
        chain: &C,
208✔
689
        chain_tip: BlockId,
208✔
690
        txid: Txid,
208✔
691
    ) -> Option<ChainPosition<&A>> {
208✔
692
        self.try_get_chain_position(chain, chain_tip, txid)
208✔
693
            .expect("error is infallible")
208✔
694
    }
208✔
695

696
    /// Get the txid of the spending transaction and where the spending transaction is observed in
697
    /// the `chain` of `chain_tip`.
698
    ///
699
    /// If no in-chain transaction spends `outpoint`, `None` will be returned.
700
    ///
701
    /// # Error
702
    ///
703
    /// An error will occur only if the [`ChainOracle`] implementation (`chain`) fails.
704
    ///
705
    /// If the [`ChainOracle`] is infallible, [`get_chain_spend`] can be used instead.
706
    ///
707
    /// [`get_chain_spend`]: Self::get_chain_spend
708
    pub fn try_get_chain_spend<C: ChainOracle>(
338✔
709
        &self,
338✔
710
        chain: &C,
338✔
711
        chain_tip: BlockId,
338✔
712
        outpoint: OutPoint,
338✔
713
    ) -> Result<Option<(ChainPosition<&A>, Txid)>, C::Error> {
338✔
714
        if self
338✔
715
            .try_get_chain_position(chain, chain_tip, outpoint.txid)?
338✔
716
            .is_none()
338✔
717
        {
718
            return Ok(None);
×
719
        }
338✔
720
        if let Some(spends) = self.spends.get(&outpoint) {
338✔
721
            for &txid in spends {
120✔
722
                if let Some(observed_at) = self.try_get_chain_position(chain, chain_tip, txid)? {
111✔
723
                    return Ok(Some((observed_at, txid)));
75✔
724
                }
36✔
725
            }
726
        }
254✔
727
        Ok(None)
263✔
728
    }
338✔
729

730
    /// Get the txid of the spending transaction and where the spending transaction is observed in
731
    /// the `chain` of `chain_tip`.
732
    ///
733
    /// This is the infallible version of [`try_get_chain_spend`]
734
    ///
735
    /// [`try_get_chain_spend`]: Self::try_get_chain_spend
736
    pub fn get_chain_spend<C: ChainOracle<Error = Infallible>>(
4✔
737
        &self,
4✔
738
        chain: &C,
4✔
739
        static_block: BlockId,
4✔
740
        outpoint: OutPoint,
4✔
741
    ) -> Option<(ChainPosition<&A>, Txid)> {
4✔
742
        self.try_get_chain_spend(chain, static_block, outpoint)
4✔
743
            .expect("error is infallible")
4✔
744
    }
4✔
745

746
    /// List graph transactions that are in `chain` with `chain_tip`.
747
    ///
748
    /// Each transaction is represented as a [`CanonicalTx`] that contains where the transaction is
749
    /// observed in-chain, and the [`TxNode`].
750
    ///
751
    /// # Error
752
    ///
753
    /// If the [`ChainOracle`] implementation (`chain`) fails, an error will be returned with the
754
    /// returned item.
755
    ///
756
    /// If the [`ChainOracle`] is infallible, [`list_chain_txs`] can be used instead.
757
    ///
758
    /// [`list_chain_txs`]: Self::list_chain_txs
759
    pub fn try_list_chain_txs<'a, C: ChainOracle + 'a>(
7✔
760
        &'a self,
7✔
761
        chain: &'a C,
7✔
762
        chain_tip: BlockId,
7✔
763
    ) -> impl Iterator<Item = Result<CanonicalTx<'a, Transaction, A>, C::Error>> {
7✔
764
        self.full_txs().filter_map(move |tx| {
7✔
765
            self.try_get_chain_position(chain, chain_tip, tx.txid)
7✔
766
                .map(|v| {
7✔
767
                    v.map(|observed_in| CanonicalTx {
7✔
768
                        chain_position: observed_in,
7✔
769
                        tx_node: tx,
7✔
770
                    })
7✔
771
                })
7✔
772
                .transpose()
7✔
773
        })
7✔
774
    }
7✔
775

776
    /// List graph transactions that are in `chain` with `chain_tip`.
777
    ///
778
    /// This is the infallible version of [`try_list_chain_txs`].
779
    ///
780
    /// [`try_list_chain_txs`]: Self::try_list_chain_txs
781
    pub fn list_chain_txs<'a, C: ChainOracle + 'a>(
7✔
782
        &'a self,
7✔
783
        chain: &'a C,
7✔
784
        chain_tip: BlockId,
7✔
785
    ) -> impl Iterator<Item = CanonicalTx<'a, Transaction, A>> {
7✔
786
        self.try_list_chain_txs(chain, chain_tip)
7✔
787
            .map(|r| r.expect("oracle is infallible"))
7✔
788
    }
7✔
789

790
    /// Get a filtered list of outputs from the given `outpoints` that are in `chain` with
791
    /// `chain_tip`.
792
    ///
793
    /// `outpoints` is a list of outpoints we are interested in, coupled with an outpoint identifier
794
    /// (`OI`) for convenience. If `OI` is not necessary, the caller can use `()`, or
795
    /// [`Iterator::enumerate`] over a list of [`OutPoint`]s.
796
    ///
797
    /// Floating outputs are ignored.
798
    ///
799
    /// # Error
800
    ///
801
    /// An [`Iterator::Item`] can be an [`Err`] if the [`ChainOracle`] implementation (`chain`)
802
    /// fails.
803
    ///
804
    /// If the [`ChainOracle`] implementation is infallible, [`filter_chain_txouts`] can be used
805
    /// instead.
806
    ///
807
    /// [`filter_chain_txouts`]: Self::filter_chain_txouts
808
    pub fn try_filter_chain_txouts<'a, C: ChainOracle + 'a, OI: Clone + 'a>(
202✔
809
        &'a self,
202✔
810
        chain: &'a C,
202✔
811
        chain_tip: BlockId,
202✔
812
        outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
202✔
813
    ) -> impl Iterator<Item = Result<(OI, FullTxOut<A>), C::Error>> + 'a {
202✔
814
        outpoints
202✔
815
            .into_iter()
202✔
816
            .map(
202✔
817
                move |(spk_i, op)| -> Result<Option<(OI, FullTxOut<_>)>, C::Error> {
202✔
818
                    let tx_node = match self.get_tx_node(op.txid) {
394✔
819
                        Some(n) => n,
394✔
820
                        None => return Ok(None),
×
821
                    };
822

823
                    let txout = match tx_node.tx.output.get(op.vout as usize) {
394✔
824
                        Some(txout) => txout.clone(),
394✔
825
                        None => return Ok(None),
×
826
                    };
827

828
                    let chain_position =
334✔
829
                        match self.try_get_chain_position(chain, chain_tip, op.txid)? {
394✔
830
                            Some(pos) => pos.cloned(),
334✔
831
                            None => return Ok(None),
60✔
832
                        };
833

834
                    let spent_by = self
334✔
835
                        .try_get_chain_spend(chain, chain_tip, op)?
334✔
836
                        .map(|(a, txid)| (a.cloned(), txid));
334✔
837

334✔
838
                    Ok(Some((
334✔
839
                        spk_i,
334✔
840
                        FullTxOut {
334✔
841
                            outpoint: op,
334✔
842
                            txout,
334✔
843
                            chain_position,
334✔
844
                            spent_by,
334✔
845
                            is_on_coinbase: tx_node.tx.is_coin_base(),
334✔
846
                        },
334✔
847
                    )))
334✔
848
                },
394✔
849
            )
202✔
850
            .filter_map(Result::transpose)
202✔
851
    }
202✔
852

853
    /// Get a filtered list of outputs from the given `outpoints` that are in `chain` with
854
    /// `chain_tip`.
855
    ///
856
    /// This is the infallible version of [`try_filter_chain_txouts`].
857
    ///
858
    /// [`try_filter_chain_txouts`]: Self::try_filter_chain_txouts
859
    pub fn filter_chain_txouts<'a, C: ChainOracle<Error = Infallible> + 'a, OI: Clone + 'a>(
19✔
860
        &'a self,
19✔
861
        chain: &'a C,
19✔
862
        chain_tip: BlockId,
19✔
863
        outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
19✔
864
    ) -> impl Iterator<Item = (OI, FullTxOut<A>)> + 'a {
19✔
865
        self.try_filter_chain_txouts(chain, chain_tip, outpoints)
19✔
866
            .map(|r| r.expect("oracle is infallible"))
55✔
867
    }
19✔
868

869
    /// Get a filtered list of unspent outputs (UTXOs) from the given `outpoints` that are in
870
    /// `chain` with `chain_tip`.
871
    ///
872
    /// `outpoints` is a list of outpoints we are interested in, coupled with an outpoint identifier
873
    /// (`OI`) for convenience. If `OI` is not necessary, the caller can use `()`, or
874
    /// [`Iterator::enumerate`] over a list of [`OutPoint`]s.
875
    ///
876
    /// Floating outputs are ignored.
877
    ///
878
    /// # Error
879
    ///
880
    /// An [`Iterator::Item`] can be an [`Err`] if the [`ChainOracle`] implementation (`chain`)
881
    /// fails.
882
    ///
883
    /// If the [`ChainOracle`] implementation is infallible, [`filter_chain_unspents`] can be used
884
    /// instead.
885
    ///
886
    /// [`filter_chain_unspents`]: Self::filter_chain_unspents
887
    pub fn try_filter_chain_unspents<'a, C: ChainOracle + 'a, OI: Clone + 'a>(
183✔
888
        &'a self,
183✔
889
        chain: &'a C,
183✔
890
        chain_tip: BlockId,
183✔
891
        outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
183✔
892
    ) -> impl Iterator<Item = Result<(OI, FullTxOut<A>), C::Error>> + 'a {
183✔
893
        self.try_filter_chain_txouts(chain, chain_tip, outpoints)
183✔
894
            .filter(|r| match r {
279✔
895
                // keep unspents, drop spents
896
                Ok((_, full_txo)) => full_txo.spent_by.is_none(),
279✔
897
                // keep errors
898
                Err(_) => true,
×
899
            })
279✔
900
    }
183✔
901

902
    /// Get a filtered list of unspent outputs (UTXOs) from the given `outpoints` that are in
903
    /// `chain` with `chain_tip`.
904
    ///
905
    /// This is the infallible version of [`try_filter_chain_unspents`].
906
    ///
907
    /// [`try_filter_chain_unspents`]: Self::try_filter_chain_unspents
908
    pub fn filter_chain_unspents<'a, C: ChainOracle<Error = Infallible> + 'a, OI: Clone + 'a>(
165✔
909
        &'a self,
165✔
910
        chain: &'a C,
165✔
911
        chain_tip: BlockId,
165✔
912
        txouts: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
165✔
913
    ) -> impl Iterator<Item = (OI, FullTxOut<A>)> + 'a {
165✔
914
        self.try_filter_chain_unspents(chain, chain_tip, txouts)
165✔
915
            .map(|r| r.expect("oracle is infallible"))
195✔
916
    }
165✔
917

918
    /// Get the total balance of `outpoints` that are in `chain` of `chain_tip`.
919
    ///
920
    /// The output of `trust_predicate` should return `true` for scripts that we trust.
921
    ///
922
    /// `outpoints` is a list of outpoints we are interested in, coupled with an outpoint identifier
923
    /// (`OI`) for convenience. If `OI` is not necessary, the caller can use `()`, or
924
    /// [`Iterator::enumerate`] over a list of [`OutPoint`]s.
925
    ///
926
    /// If the provided [`ChainOracle`] implementation (`chain`) is infallible, [`balance`] can be
927
    /// used instead.
928
    ///
929
    /// [`balance`]: Self::balance
930
    pub fn try_balance<C: ChainOracle, OI: Clone>(
18✔
931
        &self,
18✔
932
        chain: &C,
18✔
933
        chain_tip: BlockId,
18✔
934
        outpoints: impl IntoIterator<Item = (OI, OutPoint)>,
18✔
935
        mut trust_predicate: impl FnMut(&OI, &Script) -> bool,
18✔
936
    ) -> Result<Balance, C::Error> {
18✔
937
        let mut immature = 0;
18✔
938
        let mut trusted_pending = 0;
18✔
939
        let mut untrusted_pending = 0;
18✔
940
        let mut confirmed = 0;
18✔
941

942
        for res in self.try_filter_chain_unspents(chain, chain_tip, outpoints) {
33✔
943
            let (spk_i, txout) = res?;
33✔
944

945
            match &txout.chain_position {
33✔
946
                ChainPosition::Confirmed(_) => {
947
                    if txout.is_confirmed_and_spendable(chain_tip.height) {
15✔
948
                        confirmed += txout.txout.value;
10✔
949
                    } else if !txout.is_mature(chain_tip.height) {
10✔
950
                        immature += txout.txout.value;
5✔
951
                    }
5✔
952
                }
953
                ChainPosition::Unconfirmed(_) => {
954
                    if trust_predicate(&spk_i, &txout.txout.script_pubkey) {
18✔
955
                        trusted_pending += txout.txout.value;
13✔
956
                    } else {
13✔
957
                        untrusted_pending += txout.txout.value;
5✔
958
                    }
5✔
959
                }
960
            }
961
        }
962

963
        Ok(Balance {
18✔
964
            immature,
18✔
965
            trusted_pending,
18✔
966
            untrusted_pending,
18✔
967
            confirmed,
18✔
968
        })
18✔
969
    }
18✔
970

971
    /// Get the total balance of `outpoints` that are in `chain` of `chain_tip`.
972
    ///
973
    /// This is the infallible version of [`try_balance`].
974
    ///
975
    /// [`try_balance`]: Self::try_balance
976
    pub fn balance<C: ChainOracle<Error = Infallible>, OI: Clone>(
18✔
977
        &self,
18✔
978
        chain: &C,
18✔
979
        chain_tip: BlockId,
18✔
980
        outpoints: impl IntoIterator<Item = (OI, OutPoint)>,
18✔
981
        trust_predicate: impl FnMut(&OI, &Script) -> bool,
18✔
982
    ) -> Balance {
18✔
983
        self.try_balance(chain, chain_tip, outpoints, trust_predicate)
18✔
984
            .expect("oracle is infallible")
18✔
985
    }
18✔
986
}
987

988
/// A structure that represents changes to a [`TxGraph`].
989
///
990
/// Since [`TxGraph`] is monotone "changeset" can only contain transactions to be added and
991
/// not removed.
992
///
993
/// Refer to [module-level documentation] for more.
994
///
995
/// [module-level documentation]: crate::tx_graph
996
#[derive(Debug, Clone, PartialEq)]
676✔
997
#[cfg_attr(
998
    feature = "serde",
999
    derive(serde::Deserialize, serde::Serialize),
×
1000
    serde(
1001
        crate = "serde_crate",
1002
        bound(
1003
            deserialize = "A: Ord + serde::Deserialize<'de>",
1004
            serialize = "A: Ord + serde::Serialize",
1005
        )
1006
    )
1007
)]
1008
#[must_use]
1009
pub struct ChangeSet<A = ()> {
1010
    /// Added transactions.
1011
    pub txs: BTreeSet<Transaction>,
1012
    /// Added txouts.
1013
    pub txouts: BTreeMap<OutPoint, TxOut>,
1014
    /// Added anchors.
1015
    pub anchors: BTreeSet<(A, Txid)>,
1016
    /// Added last-seen unix timestamps of transactions.
1017
    pub last_seen: BTreeMap<Txid, u64>,
1018
}
1019

1020
impl<A> Default for ChangeSet<A> {
1021
    fn default() -> Self {
2,207✔
1022
        Self {
2,207✔
1023
            txs: Default::default(),
2,207✔
1024
            txouts: Default::default(),
2,207✔
1025
            anchors: Default::default(),
2,207✔
1026
            last_seen: Default::default(),
2,207✔
1027
        }
2,207✔
1028
    }
2,207✔
1029
}
1030

1031
impl<A> ChangeSet<A> {
1032
    /// Returns true if the [`ChangeSet`] is empty (no transactions or txouts).
1033
    pub fn is_empty(&self) -> bool {
373✔
1034
        self.txs.is_empty() && self.txouts.is_empty()
373✔
1035
    }
373✔
1036

1037
    /// Iterates over all outpoints contained within [`ChangeSet`].
1038
    pub fn txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
×
1039
        self.txs
×
1040
            .iter()
×
1041
            .flat_map(|tx| {
×
1042
                tx.output
×
1043
                    .iter()
×
1044
                    .enumerate()
×
1045
                    .map(move |(vout, txout)| (OutPoint::new(tx.txid(), vout as _), txout))
×
1046
            })
×
1047
            .chain(self.txouts.iter().map(|(op, txout)| (*op, txout)))
×
1048
    }
×
1049
}
1050

1051
impl<A: Ord> Append for ChangeSet<A> {
1052
    fn append(&mut self, mut other: Self) {
652✔
1053
        self.txs.append(&mut other.txs);
652✔
1054
        self.txouts.append(&mut other.txouts);
652✔
1055
        self.anchors.append(&mut other.anchors);
652✔
1056

652✔
1057
        // last_seen timestamps should only increase
652✔
1058
        self.last_seen.extend(
652✔
1059
            other
652✔
1060
                .last_seen
652✔
1061
                .into_iter()
652✔
1062
                .filter(|(txid, update_ls)| self.last_seen.get(txid) < Some(update_ls))
652✔
1063
                .collect::<Vec<_>>(),
652✔
1064
        );
652✔
1065
    }
652✔
1066

1067
    fn is_empty(&self) -> bool {
×
1068
        self.txs.is_empty()
×
1069
            && self.txouts.is_empty()
×
1070
            && self.anchors.is_empty()
×
1071
            && self.last_seen.is_empty()
×
1072
    }
×
1073
}
1074

1075
impl<A> AsRef<TxGraph<A>> for TxGraph<A> {
1076
    fn as_ref(&self) -> &TxGraph<A> {
×
1077
        self
×
1078
    }
×
1079
}
1080

1081
impl<A> ForEachTxOut for ChangeSet<A> {
1082
    fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut))) {
×
1083
        self.txouts().for_each(f)
×
1084
    }
×
1085
}
1086

1087
impl<A> ForEachTxOut for TxGraph<A> {
1088
    fn for_each_txout(&self, f: impl FnMut((OutPoint, &TxOut))) {
×
1089
        self.all_txouts().for_each(f)
×
1090
    }
×
1091
}
1092

1093
/// An iterator that traverses transaction ancestors to a specified depth.
1094
pub struct TxAncestors<'g, A> {
1095
    graph: &'g TxGraph<A>,
1096
    visited: HashSet<Txid>,
1097
    stack: Vec<(usize, Transaction)>,
1098
    depth_limit: usize,
1099
}
1100

1101
impl<'g, A> TxAncestors<'g, A> {
1102
    /// Creates a `TxAncestors` that includes the starting `Transaction` when iterating.
1103
    pub(crate) fn new_include_root(
337✔
1104
        graph: &'g TxGraph<A>,
337✔
1105
        tx: Transaction,
337✔
1106
        depth_limit: usize,
337✔
1107
    ) -> Self {
337✔
1108
        Self {
337✔
1109
            graph,
337✔
1110
            visited: Default::default(),
337✔
1111
            stack: [(0, tx)].into(),
337✔
1112
            depth_limit,
337✔
1113
        }
337✔
1114
    }
337✔
1115

1116
    /// Creates a `TxAncestors` that excludes the starting `Transaction` when iterating.
1117
    #[allow(unused)]
1118
    pub(crate) fn new_exclude_root(
×
1119
        graph: &'g TxGraph<A>,
×
1120
        tx: Transaction,
×
1121
        depth_limit: usize,
×
1122
    ) -> Self {
×
1123
        let mut ancestors = Self {
×
1124
            graph,
×
1125
            visited: Default::default(),
×
1126
            stack: Default::default(),
×
1127
            depth_limit,
×
1128
        };
×
1129
        ancestors.populate_stack(1, tx);
×
1130
        ancestors
×
1131
    }
×
1132

1133
    /// Creates a `TxAncestors` from multiple starting `Transaction`s that includes the starting
1134
    /// `Transaction`s when iterating.
1135
    #[allow(unused)]
1136
    pub(crate) fn from_multiple_include_root<I>(
×
1137
        graph: &'g TxGraph<A>,
×
1138
        txs: I,
×
1139
        depth_limit: usize,
×
1140
    ) -> Self
×
1141
    where
×
1142
        I: IntoIterator<Item = Transaction>,
×
1143
    {
×
1144
        Self {
×
1145
            graph,
×
1146
            visited: Default::default(),
×
1147
            stack: txs.into_iter().map(|tx| (0, tx)).collect(),
×
1148
            depth_limit,
×
1149
        }
×
1150
    }
×
1151

1152
    /// Creates a `TxAncestors` from multiple starting `Transaction`s that excludes the starting
1153
    /// `Transaction`s when iterating.
1154
    #[allow(unused)]
1155
    pub(crate) fn from_multiple_exclude_root<I>(
×
1156
        graph: &'g TxGraph<A>,
×
1157
        txs: I,
×
1158
        depth_limit: usize,
×
1159
    ) -> Self
×
1160
    where
×
1161
        I: IntoIterator<Item = Transaction>,
×
1162
    {
×
1163
        let mut ancestors = Self {
×
1164
            graph,
×
1165
            visited: Default::default(),
×
1166
            stack: Default::default(),
×
1167
            depth_limit,
×
1168
        };
×
1169
        for tx in txs {
×
1170
            ancestors.populate_stack(1, tx);
×
1171
        }
×
1172
        ancestors
×
1173
    }
×
1174
}
1175

1176
impl<'g, A> TxAncestors<'g, A> {
1177
    fn populate_stack(&mut self, ancestor_depth: usize, tx: Transaction) {
668✔
1178
        if ancestor_depth <= self.depth_limit && self.visited.insert(tx.txid()) {
668✔
1179
            tx.input
647✔
1180
                .iter()
647✔
1181
                .map(|txin| txin.previous_output.txid)
647✔
1182
                .for_each(|prev_txid| {
647✔
1183
                    if let Some((TxNodeInternal::Whole(tx), _, _)) = self.graph.txs.get(&prev_txid)
533✔
1184
                    {
331✔
1185
                        self.stack.push((ancestor_depth, tx.clone()));
331✔
1186
                    };
331✔
1187
                });
647✔
1188
        }
647✔
1189
    }
668✔
1190
}
1191

1192
impl<'g, A> Iterator for TxAncestors<'g, A> {
1193
    type Item = (usize, Transaction);
1194

1195
    fn next(&mut self) -> Option<Self::Item> {
1,005✔
1196
        let (ancestor_depth, tx) = self.stack.pop()?;
1,005✔
1197
        self.populate_stack(ancestor_depth + 1, tx.clone());
668✔
1198
        Some((ancestor_depth, tx))
668✔
1199
    }
1,005✔
1200
}
1201

1202
/// An iterator that traverses transaction descendants.
1203
///
1204
/// This `struct` is created by the [`walk_descendants`] method of [`TxGraph`].
1205
///
1206
/// [`walk_descendants`]: TxGraph::walk_descendants
1207
pub struct TxDescendants<'g, A, F> {
1208
    graph: &'g TxGraph<A>,
1209
    visited: HashSet<Txid>,
1210
    stack: Vec<(usize, Txid)>,
1211
    filter_map: F,
1212
}
1213

1214
impl<'g, A, F> TxDescendants<'g, A, F> {
1215
    /// Creates a `TxDescendants` that includes the starting `txid` when iterating.
1216
    #[allow(unused)]
1217
    pub(crate) fn new_include_root(graph: &'g TxGraph<A>, txid: Txid, filter_map: F) -> Self {
×
1218
        Self {
×
1219
            graph,
×
1220
            visited: Default::default(),
×
1221
            stack: [(0, txid)].into(),
×
1222
            filter_map,
×
1223
        }
×
1224
    }
×
1225

1226
    /// Creates a `TxDescendants` that excludes the starting `txid` when iterating.
1227
    pub(crate) fn new_exclude_root(graph: &'g TxGraph<A>, txid: Txid, filter_map: F) -> Self {
1✔
1228
        let mut descendants = Self {
1✔
1229
            graph,
1✔
1230
            visited: Default::default(),
1✔
1231
            stack: Default::default(),
1✔
1232
            filter_map,
1✔
1233
        };
1✔
1234
        descendants.populate_stack(1, txid);
1✔
1235
        descendants
1✔
1236
    }
1✔
1237

1238
    /// Creates a `TxDescendants` from multiple starting transactions that includes the starting
1239
    /// `txid`s when iterating.
1240
    pub(crate) fn from_multiple_include_root<I>(
337✔
1241
        graph: &'g TxGraph<A>,
337✔
1242
        txids: I,
337✔
1243
        filter_map: F,
337✔
1244
    ) -> Self
337✔
1245
    where
337✔
1246
        I: IntoIterator<Item = Txid>,
337✔
1247
    {
337✔
1248
        Self {
337✔
1249
            graph,
337✔
1250
            visited: Default::default(),
337✔
1251
            stack: txids.into_iter().map(|txid| (0, txid)).collect(),
337✔
1252
            filter_map,
337✔
1253
        }
337✔
1254
    }
337✔
1255

1256
    /// Creates a `TxDescendants` from multiple starting transactions that excludes the starting
1257
    /// `txid`s when iterating.
1258
    #[allow(unused)]
1259
    pub(crate) fn from_multiple_exclude_root<I>(
×
1260
        graph: &'g TxGraph<A>,
×
1261
        txids: I,
×
1262
        filter_map: F,
×
1263
    ) -> Self
×
1264
    where
×
1265
        I: IntoIterator<Item = Txid>,
×
1266
    {
×
1267
        let mut descendants = Self {
×
1268
            graph,
×
1269
            visited: Default::default(),
×
1270
            stack: Default::default(),
×
1271
            filter_map,
×
1272
        };
×
1273
        for txid in txids {
×
1274
            descendants.populate_stack(1, txid);
×
1275
        }
×
1276
        descendants
×
1277
    }
×
1278
}
1279

1280
impl<'g, A, F> TxDescendants<'g, A, F> {
1281
    fn populate_stack(&mut self, depth: usize, txid: Txid) {
212✔
1282
        let spend_paths = self
212✔
1283
            .graph
212✔
1284
            .spends
212✔
1285
            .range(tx_outpoint_range(txid))
212✔
1286
            .flat_map(|(_, spends)| spends)
212✔
1287
            .map(|&txid| (depth, txid));
212✔
1288
        self.stack.extend(spend_paths);
212✔
1289
    }
212✔
1290
}
1291

1292
impl<'g, A, F, O> Iterator for TxDescendants<'g, A, F>
1293
where
1294
    F: FnMut(usize, Txid) -> Option<O>,
1295
{
1296
    type Item = O;
1297

1298
    fn next(&mut self) -> Option<Self::Item> {
451✔
1299
        let (op_spends, txid, item) = loop {
211✔
1300
            // we have exhausted all paths when stack is empty
1301
            let (op_spends, txid) = self.stack.pop()?;
452✔
1302
            // we do not want to visit the same transaction twice
1303
            if self.visited.insert(txid) {
212✔
1304
                // ignore paths when user filters them out
1305
                if let Some(item) = (self.filter_map)(op_spends, txid) {
211✔
1306
                    break (op_spends, txid, item);
211✔
1307
                }
×
1308
            }
1✔
1309
        };
1310

1311
        self.populate_stack(op_spends + 1, txid);
211✔
1312
        Some(item)
211✔
1313
    }
451✔
1314
}
1315

1316
fn tx_outpoint_range(txid: Txid) -> RangeInclusive<OutPoint> {
2,756✔
1317
    OutPoint::new(txid, u32::MIN)..=OutPoint::new(txid, u32::MAX)
2,756✔
1318
}
2,756✔
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