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

bitcoindevkit / bdk / 6342271540

28 Sep 2023 05:35PM UTC coverage: 80.112% (-0.8%) from 80.872%
6342271540

Pull #1041

github

web-flow
Merge 08dff106e into 37d5e5319
Pull Request #1041: Add `bitcoind_rpc` chain source module.

242 of 242 new or added lines in 3 files covered. (100.0%)

8334 of 10403 relevant lines covered (80.11%)

624674.16 hits per line

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

85.05
/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, 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)]
2✔
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 {
1,160✔
83
        Self {
1,160✔
84
            txs: Default::default(),
1,160✔
85
            spends: Default::default(),
1,160✔
86
            anchors: Default::default(),
1,160✔
87
            empty_outspends: Default::default(),
1,160✔
88
        }
1,160✔
89
    }
1,160✔
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)]
6✔
118
enum TxNodeInternal {
119
    Whole(Transaction),
120
    Partial(BTreeMap<u32, TxOut>),
121
}
122

123
impl Default for TxNodeInternal {
124
    fn default() -> Self {
884✔
125
        Self::Partial(BTreeMap::new())
884✔
126
    }
884✔
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
/// Errors returned by `TxGraph::calculate_fee`.
139
#[derive(Debug, PartialEq, Eq)]
18✔
140
pub enum CalculateFeeError {
141
    /// Missing `TxOut` for one or more of the inputs of the tx
142
    MissingTxOut(Vec<OutPoint>),
143
    /// When the transaction is invalid according to the graph it has a negative fee
144
    NegativeFee(i64),
145
}
146

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

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

184
    /// Iterate over all full transactions in the graph.
185
    pub fn full_txs(&self) -> impl Iterator<Item = TxNode<'_, Transaction, A>> {
11✔
186
        self.txs
11✔
187
            .iter()
11✔
188
            .filter_map(|(&txid, (tx, anchors, last_seen))| match tx {
18✔
189
                TxNodeInternal::Whole(tx) => Some(TxNode {
16✔
190
                    txid,
16✔
191
                    tx,
16✔
192
                    anchors,
16✔
193
                    last_seen_unconfirmed: *last_seen,
16✔
194
                }),
16✔
195
                TxNodeInternal::Partial(_) => None,
2✔
196
            })
18✔
197
    }
11✔
198

199
    /// Get a transaction by txid. This only returns `Some` for full transactions.
200
    ///
201
    /// Refer to [`get_txout`] for getting a specific [`TxOut`].
202
    ///
203
    /// [`get_txout`]: Self::get_txout
204
    pub fn get_tx(&self, txid: Txid) -> Option<&Transaction> {
608✔
205
        self.get_tx_node(txid).map(|n| n.tx)
608✔
206
    }
608✔
207

208
    /// Get a transaction node by txid. This only returns `Some` for full transactions.
209
    pub fn get_tx_node(&self, txid: Txid) -> Option<TxNode<'_, Transaction, A>> {
1,008✔
210
        match &self.txs.get(&txid)? {
1,008✔
211
            (TxNodeInternal::Whole(tx), anchors, last_seen) => Some(TxNode {
704✔
212
                txid,
704✔
213
                tx,
704✔
214
                anchors,
704✔
215
                last_seen_unconfirmed: *last_seen,
704✔
216
            }),
704✔
217
            _ => None,
×
218
        }
219
    }
1,008✔
220

221
    /// Obtains a single tx output (if any) at the specified outpoint.
222
    pub fn get_txout(&self, outpoint: OutPoint) -> Option<&TxOut> {
90✔
223
        match &self.txs.get(&outpoint.txid)?.0 {
90✔
224
            TxNodeInternal::Whole(tx) => tx.output.get(outpoint.vout as usize),
85✔
225
            TxNodeInternal::Partial(txouts) => txouts.get(&outpoint.vout),
3✔
226
        }
227
    }
90✔
228

229
    /// Returns known outputs of a given `txid`.
230
    ///
231
    /// Returns a [`BTreeMap`] of vout to output of the provided `txid`.
232
    pub fn tx_outputs(&self, txid: Txid) -> Option<BTreeMap<u32, &TxOut>> {
2✔
233
        Some(match &self.txs.get(&txid)?.0 {
2✔
234
            TxNodeInternal::Whole(tx) => tx
1✔
235
                .output
1✔
236
                .iter()
1✔
237
                .enumerate()
1✔
238
                .map(|(vout, txout)| (vout as u32, txout))
1✔
239
                .collect::<BTreeMap<_, _>>(),
1✔
240
            TxNodeInternal::Partial(txouts) => txouts
1✔
241
                .iter()
1✔
242
                .map(|(vout, txout)| (*vout, txout))
2✔
243
                .collect::<BTreeMap<_, _>>(),
1✔
244
        })
245
    }
2✔
246

247
    /// Calculates the fee of a given transaction. Returns 0 if `tx` is a coinbase transaction.
248
    /// Returns `OK(_)` if we have all the [`TxOut`]s being spent by `tx` in the graph (either as
249
    /// the full transactions or individual txouts).
250
    ///
251
    /// To calculate the fee for a [`Transaction`] that depends on foreign [`TxOut`] values you must
252
    /// first manually insert the foreign TxOuts into the tx graph using the [`insert_txout`] function.
253
    /// Only insert TxOuts you trust the values for!
254
    ///
255
    /// Note `tx` does not have to be in the graph for this to work.
256
    ///
257
    /// [`insert_txout`]: Self::insert_txout
258
    pub fn calculate_fee(&self, tx: &Transaction) -> Result<u64, CalculateFeeError> {
71✔
259
        if tx.is_coin_base() {
71✔
260
            return Ok(0);
1✔
261
        }
70✔
262

70✔
263
        let (inputs_sum, missing_outputs) = tx.input.iter().fold(
70✔
264
            (0_i64, Vec::new()),
70✔
265
            |(mut sum, mut missing_outpoints), txin| match self.get_txout(txin.previous_output) {
86✔
266
                None => {
267
                    missing_outpoints.push(txin.previous_output);
2✔
268
                    (sum, missing_outpoints)
2✔
269
                }
270
                Some(txout) => {
84✔
271
                    sum += txout.value as i64;
84✔
272
                    (sum, missing_outpoints)
84✔
273
                }
274
            },
86✔
275
        );
70✔
276
        if !missing_outputs.is_empty() {
70✔
277
            return Err(CalculateFeeError::MissingTxOut(missing_outputs));
2✔
278
        }
68✔
279

68✔
280
        let outputs_sum = tx
68✔
281
            .output
68✔
282
            .iter()
68✔
283
            .map(|txout| txout.value as i64)
108✔
284
            .sum::<i64>();
68✔
285

68✔
286
        let fee = inputs_sum - outputs_sum;
68✔
287
        if fee < 0 {
68✔
288
            Err(CalculateFeeError::NegativeFee(fee))
1✔
289
        } else {
290
            Ok(fee as u64)
67✔
291
        }
292
    }
71✔
293

294
    /// The transactions spending from this output.
295
    ///
296
    /// `TxGraph` allows conflicting transactions within the graph. Obviously the transactions in
297
    /// the returned set will never be in the same active-chain.
298
    pub fn outspends(&self, outpoint: OutPoint) -> &HashSet<Txid> {
4✔
299
        self.spends.get(&outpoint).unwrap_or(&self.empty_outspends)
4✔
300
    }
4✔
301

302
    /// Iterates over the transactions spending from `txid`.
303
    ///
304
    /// The iterator item is a union of `(vout, txid-set)` where:
305
    ///
306
    /// - `vout` is the provided `txid`'s outpoint that is being spent
307
    /// - `txid-set` is the set of txids spending the `vout`.
308
    pub fn tx_spends(
1✔
309
        &self,
1✔
310
        txid: Txid,
1✔
311
    ) -> impl DoubleEndedIterator<Item = (u32, &HashSet<Txid>)> + '_ {
1✔
312
        let start = OutPoint { txid, vout: 0 };
1✔
313
        let end = OutPoint {
1✔
314
            txid,
1✔
315
            vout: u32::MAX,
1✔
316
        };
1✔
317
        self.spends
1✔
318
            .range(start..=end)
1✔
319
            .map(|(outpoint, spends)| (outpoint.vout, spends))
1✔
320
    }
1✔
321

322
    /// Creates an iterator that filters and maps descendants from the starting `txid`.
323
    ///
324
    /// The supplied closure takes in two inputs `(depth, descendant_txid)`:
325
    ///
326
    /// * `depth` is the distance between the starting `txid` and the `descendant_txid`. I.e., if the
327
    ///     descendant is spending an output of the starting `txid`; the `depth` will be 1.
328
    /// * `descendant_txid` is the descendant's txid which we are considering to walk.
329
    ///
330
    /// The supplied closure returns an `Option<T>`, allowing the caller to map each node it vists
331
    /// and decide whether to visit descendants.
332
    pub fn walk_descendants<'g, F, O>(&'g self, txid: Txid, walk_map: F) -> TxDescendants<A, F>
1✔
333
    where
1✔
334
        F: FnMut(usize, Txid) -> Option<O> + 'g,
1✔
335
    {
1✔
336
        TxDescendants::new_exclude_root(self, txid, walk_map)
1✔
337
    }
1✔
338

339
    /// Creates an iterator that both filters and maps conflicting transactions (this includes
340
    /// descendants of directly-conflicting transactions, which are also considered conflicts).
341
    ///
342
    /// Refer to [`Self::walk_descendants`] for `walk_map` usage.
343
    pub fn walk_conflicts<'g, F, O>(
165✔
344
        &'g self,
165✔
345
        tx: &'g Transaction,
165✔
346
        walk_map: F,
165✔
347
    ) -> TxDescendants<A, F>
165✔
348
    where
165✔
349
        F: FnMut(usize, Txid) -> Option<O> + 'g,
165✔
350
    {
165✔
351
        let txids = self.direct_conflicts_of_tx(tx).map(|(_, txid)| txid);
165✔
352
        TxDescendants::from_multiple_include_root(self, txids, walk_map)
165✔
353
    }
165✔
354

355
    /// Given a transaction, return an iterator of txids that directly conflict with the given
356
    /// transaction's inputs (spends). The conflicting txids are returned with the given
357
    /// transaction's vin (in which it conflicts).
358
    ///
359
    /// Note that this only returns directly conflicting txids and does not include descendants of
360
    /// those txids (which are technically also conflicting).
361
    pub fn direct_conflicts_of_tx<'g>(
165✔
362
        &'g self,
165✔
363
        tx: &'g Transaction,
165✔
364
    ) -> impl Iterator<Item = (usize, Txid)> + '_ {
165✔
365
        let txid = tx.txid();
165✔
366
        tx.input
165✔
367
            .iter()
165✔
368
            .enumerate()
165✔
369
            .filter_map(move |(vin, txin)| self.spends.get(&txin.previous_output).zip(Some(vin)))
165✔
370
            .flat_map(|(spends, vin)| core::iter::repeat(vin).zip(spends.iter().cloned()))
165✔
371
            .filter(move |(_, conflicting_txid)| *conflicting_txid != txid)
169✔
372
    }
165✔
373

374
    /// Get all transaction anchors known by [`TxGraph`].
375
    pub fn all_anchors(&self) -> &BTreeSet<(A, Txid)> {
×
376
        &self.anchors
×
377
    }
×
378

379
    /// Whether the graph has any transactions or outputs in it.
380
    pub fn is_empty(&self) -> bool {
×
381
        self.txs.is_empty()
×
382
    }
×
383
}
384

385
impl<A: Clone + Ord> TxGraph<A> {
386
    /// Construct a new [`TxGraph`] from a list of transactions.
387
    pub fn new(txs: impl IntoIterator<Item = Transaction>) -> Self {
×
388
        let mut new = Self::default();
×
389
        for tx in txs.into_iter() {
×
390
            let _ = new.insert_tx(tx);
×
391
        }
×
392
        new
×
393
    }
×
394

395
    /// Inserts the given [`TxOut`] at [`OutPoint`].
396
    ///
397
    /// Inserting floating txouts are useful for determining fee/feerate of transactions we care
398
    /// about.
399
    ///
400
    /// The [`ChangeSet`] result will be empty if the `outpoint` (or a full transaction containing
401
    /// the `outpoint`) already existed in `self`.
402
    ///
403
    /// [`apply_changeset`]: Self::apply_changeset
404
    pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<A> {
10✔
405
        let mut update = Self::default();
10✔
406
        update.txs.insert(
10✔
407
            outpoint.txid,
10✔
408
            (
10✔
409
                TxNodeInternal::Partial([(outpoint.vout, txout)].into()),
10✔
410
                BTreeSet::new(),
10✔
411
                0,
10✔
412
            ),
10✔
413
        );
10✔
414
        self.apply_update(update)
10✔
415
    }
10✔
416

417
    /// Inserts the given transaction into [`TxGraph`].
418
    ///
419
    /// The [`ChangeSet`] returned will be empty if `tx` already exists.
420
    pub fn insert_tx(&mut self, tx: Transaction) -> ChangeSet<A> {
343✔
421
        let mut update = Self::default();
343✔
422
        update
343✔
423
            .txs
343✔
424
            .insert(tx.txid(), (TxNodeInternal::Whole(tx), BTreeSet::new(), 0));
343✔
425
        self.apply_update(update)
343✔
426
    }
343✔
427

428
    /// Inserts the given `anchor` into [`TxGraph`].
429
    ///
430
    /// The [`ChangeSet`] returned will be empty if graph already knows that `txid` exists in
431
    /// `anchor`.
432
    pub fn insert_anchor(&mut self, txid: Txid, anchor: A) -> ChangeSet<A> {
298✔
433
        let mut update = Self::default();
298✔
434
        update.anchors.insert((anchor, txid));
298✔
435
        self.apply_update(update)
298✔
436
    }
298✔
437

438
    /// Inserts the given `seen_at` for `txid` into [`TxGraph`].
439
    ///
440
    /// Note that [`TxGraph`] only keeps track of the lastest `seen_at`.
441
    pub fn insert_seen_at(&mut self, txid: Txid, seen_at: u64) -> ChangeSet<A> {
31✔
442
        let mut update = Self::default();
31✔
443
        let (_, _, update_last_seen) = update.txs.entry(txid).or_default();
31✔
444
        *update_last_seen = seen_at;
31✔
445
        self.apply_update(update)
31✔
446
    }
31✔
447

448
    /// Extends this graph with another so that `self` becomes the union of the two sets of
449
    /// transactions.
450
    ///
451
    /// The returned [`ChangeSet`] is the set difference between `update` and `self` (transactions that
452
    /// exist in `update` but not in `self`).
453
    pub fn apply_update(&mut self, update: TxGraph<A>) -> ChangeSet<A> {
991✔
454
        let changeset = self.determine_changeset(update);
991✔
455
        self.apply_changeset(changeset.clone());
991✔
456
        changeset
991✔
457
    }
991✔
458

459
    /// Determines the [`ChangeSet`] between `self` and an empty [`TxGraph`].
460
    pub fn initial_changeset(&self) -> ChangeSet<A> {
2✔
461
        Self::default().determine_changeset(self.clone())
2✔
462
    }
2✔
463

464
    /// Applies [`ChangeSet`] to [`TxGraph`].
465
    pub fn apply_changeset(&mut self, changeset: ChangeSet<A>) {
1,136✔
466
        for tx in changeset.txs {
1,785✔
467
            let txid = tx.txid();
650✔
468

650✔
469
            tx.input
650✔
470
                .iter()
650✔
471
                .map(|txin| txin.previous_output)
650✔
472
                // coinbase spends are not to be counted
650✔
473
                .filter(|outpoint| !outpoint.is_null())
650✔
474
                // record spend as this tx has spent this outpoint
650✔
475
                .for_each(|outpoint| {
650✔
476
                    self.spends.entry(outpoint).or_default().insert(txid);
587✔
477
                });
650✔
478

650✔
479
            match self.txs.get_mut(&txid) {
650✔
480
                Some((tx_node @ TxNodeInternal::Partial(_), _, _)) => {
1✔
481
                    *tx_node = TxNodeInternal::Whole(tx);
1✔
482
                }
1✔
483
                Some((TxNodeInternal::Whole(tx), _, _)) => {
1✔
484
                    debug_assert_eq!(
485
                        tx.txid(),
1✔
486
                        txid,
487
                        "tx should produce txid that is same as key"
×
488
                    );
489
                }
490
                None => {
647✔
491
                    self.txs
647✔
492
                        .insert(txid, (TxNodeInternal::Whole(tx), BTreeSet::new(), 0));
647✔
493
                }
647✔
494
            }
495
        }
496

497
        for (outpoint, txout) in changeset.txouts {
1,146✔
498
            let tx_entry = self
11✔
499
                .txs
11✔
500
                .entry(outpoint.txid)
11✔
501
                .or_insert_with(Default::default);
11✔
502

11✔
503
            match tx_entry {
11✔
504
                (TxNodeInternal::Whole(_), _, _) => { /* do nothing since we already have full tx */
×
505
                }
×
506
                (TxNodeInternal::Partial(txouts), _, _) => {
11✔
507
                    txouts.insert(outpoint.vout, txout);
11✔
508
                }
11✔
509
            }
510
        }
511

512
        for (anchor, txid) in changeset.anchors {
1,713✔
513
            if self.anchors.insert((anchor.clone(), txid)) {
578✔
514
                let (_, anchors, _) = self.txs.entry(txid).or_insert_with(Default::default);
576✔
515
                anchors.insert(anchor);
576✔
516
            }
576✔
517
        }
518

519
        for (txid, new_last_seen) in changeset.last_seen {
2,147,483,647✔
520
            let (_, _, last_seen) = self.txs.entry(txid).or_insert_with(Default::default);
2,147,483,647✔
521
            if new_last_seen > *last_seen {
2,147,483,647✔
522
                *last_seen = new_last_seen;
14✔
523
            }
14✔
524
        }
525
    }
1,136✔
526

527
    /// Previews the resultant [`ChangeSet`] when [`Self`] is updated against the `update` graph.
528
    ///
529
    /// The [`ChangeSet`] would be the set difference between `update` and `self` (transactions that
530
    /// exist in `update` but not in `self`).
531
    pub(crate) fn determine_changeset(&self, update: TxGraph<A>) -> ChangeSet<A> {
993✔
532
        let mut changeset = ChangeSet::default();
993✔
533

534
        for (&txid, (update_tx_node, _, update_last_seen)) in &update.txs {
1,694✔
535
            let prev_last_seen: u64 = match (self.txs.get(&txid), update_tx_node) {
701✔
536
                (None, TxNodeInternal::Whole(update_tx)) => {
651✔
537
                    changeset.txs.insert(update_tx.clone());
651✔
538
                    0
651✔
539
                }
540
                (None, TxNodeInternal::Partial(update_txos)) => {
11✔
541
                    changeset.txouts.extend(
11✔
542
                        update_txos
11✔
543
                            .iter()
11✔
544
                            .map(|(&vout, txo)| (OutPoint::new(txid, vout), txo.clone())),
12✔
545
                    );
11✔
546
                    0
11✔
547
                }
548
                (Some((TxNodeInternal::Whole(_), _, last_seen)), _) => *last_seen,
35✔
549
                (
550
                    Some((TxNodeInternal::Partial(_), _, last_seen)),
1✔
551
                    TxNodeInternal::Whole(update_tx),
1✔
552
                ) => {
1✔
553
                    changeset.txs.insert(update_tx.clone());
1✔
554
                    *last_seen
1✔
555
                }
556
                (
557
                    Some((TxNodeInternal::Partial(txos), _, last_seen)),
3✔
558
                    TxNodeInternal::Partial(update_txos),
3✔
559
                ) => {
3✔
560
                    changeset.txouts.extend(
3✔
561
                        update_txos
3✔
562
                            .iter()
3✔
563
                            .filter(|(vout, _)| !txos.contains_key(*vout))
3✔
564
                            .map(|(&vout, txo)| (OutPoint::new(txid, vout), txo.clone())),
3✔
565
                    );
3✔
566
                    *last_seen
3✔
567
                }
568
            };
569

570
            if *update_last_seen > prev_last_seen {
701✔
571
                changeset.last_seen.insert(txid, *update_last_seen);
15✔
572
            }
686✔
573
        }
574

575
        changeset.anchors = update.anchors.difference(&self.anchors).cloned().collect();
994✔
576

994✔
577
        changeset
994✔
578
    }
994✔
579
}
580

581
impl<A: Anchor> TxGraph<A> {
582
    /// Find missing block heights of `chain`.
583
    ///
584
    /// This works by scanning through anchors, and seeing whether the anchor block of the anchor
585
    /// exists in the [`LocalChain`]. The returned iterator does not output duplicate heights.
586
    pub fn missing_heights<'a>(&'a self, chain: &'a LocalChain) -> impl Iterator<Item = u32> + 'a {
7✔
587
        // Map of txids to skip.
7✔
588
        //
7✔
589
        // Usually, if a height of a tx anchor is missing from the chain, we would want to return
7✔
590
        // this height in the iterator. The exception is when the tx is confirmed in chain. All the
7✔
591
        // other missing-height anchors of this tx can be skipped.
7✔
592
        //
7✔
593
        // * Some(true)  => skip all anchors of this txid
7✔
594
        // * Some(false) => do not skip anchors of this txid
7✔
595
        // * None        => we do not know whether we can skip this txid
7✔
596
        let mut txids_to_skip = HashMap::<Txid, bool>::new();
7✔
597

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

7✔
601
        self.anchors
7✔
602
            .iter()
7✔
603
            .filter(move |(_, txid)| {
14✔
604
                let skip = *txids_to_skip.entry(*txid).or_insert_with(|| {
14✔
605
                    let tx_anchors = match self.txs.get(txid) {
9✔
606
                        Some((_, anchors, _)) => anchors,
9✔
607
                        None => return true,
×
608
                    };
609
                    let mut has_missing_height = false;
9✔
610
                    for anchor_block in tx_anchors.iter().map(Anchor::anchor_block) {
14✔
611
                        match chain.blocks().get(&anchor_block.height) {
14✔
612
                            None => {
613
                                has_missing_height = true;
8✔
614
                                continue;
8✔
615
                            }
616
                            Some(chain_hash) => {
6✔
617
                                if chain_hash == &anchor_block.hash {
6✔
618
                                    return true;
2✔
619
                                }
4✔
620
                            }
621
                        }
622
                    }
623
                    !has_missing_height
7✔
624
                });
14✔
625
                #[cfg(feature = "std")]
626
                debug_assert!({
627
                    println!("txid={} skip={}", txid, skip);
14✔
628
                    true
14✔
629
                });
630
                !skip
14✔
631
            })
14✔
632
            .filter_map(move |(a, _)| {
8✔
633
                let anchor_block = a.anchor_block();
8✔
634
                if Some(anchor_block.height) != last_height_emitted
8✔
635
                    && !chain.blocks().contains_key(&anchor_block.height)
6✔
636
                {
637
                    last_height_emitted = Some(anchor_block.height);
5✔
638
                    Some(anchor_block.height)
5✔
639
                } else {
640
                    None
3✔
641
                }
642
            })
8✔
643
    }
7✔
644

645
    /// Get the position of the transaction in `chain` with tip `chain_tip`.
646
    ///
647
    /// If the given transaction of `txid` does not exist in the chain of `chain_tip`, `None` is
648
    /// returned.
649
    ///
650
    /// # Error
651
    ///
652
    /// An error will occur if the [`ChainOracle`] implementation (`chain`) fails. If the
653
    /// [`ChainOracle`] is infallible, [`get_chain_position`] can be used instead.
654
    ///
655
    /// [`get_chain_position`]: Self::get_chain_position
656
    pub fn try_get_chain_position<C: ChainOracle>(
1,173✔
657
        &self,
1,173✔
658
        chain: &C,
1,173✔
659
        chain_tip: BlockId,
1,173✔
660
        txid: Txid,
1,173✔
661
    ) -> Result<Option<ChainPosition<&A>>, C::Error> {
1,173✔
662
        let (tx_node, anchors, last_seen) = match self.txs.get(&txid) {
1,173✔
663
            Some(v) => v,
1,171✔
664
            None => return Ok(None),
2✔
665
        };
666

667
        for anchor in anchors {
1,195✔
668
            match chain.is_block_in_chain(anchor.anchor_block(), chain_tip)? {
1,030✔
669
                Some(true) => return Ok(Some(ChainPosition::Confirmed(anchor))),
1,005✔
670
                _ => continue,
24✔
671
            }
672
        }
673

674
        // The tx is not anchored to a block which is in the best chain, let's check whether we can
675
        // ignore it by checking conflicts!
676
        let tx = match tx_node {
165✔
677
            TxNodeInternal::Whole(tx) => tx,
164✔
678
            TxNodeInternal::Partial(_) => {
679
                // Partial transactions (outputs only) cannot have conflicts.
680
                return Ok(None);
1✔
681
            }
682
        };
683

684
        // If a conflicting tx is in the best chain, or has `last_seen` higher than this tx, then
685
        // this tx cannot exist in the best chain
686
        for conflicting_tx in self.walk_conflicts(tx, |_, txid| self.get_tx_node(txid)) {
164✔
687
            for block in conflicting_tx.anchors.iter().map(A::anchor_block) {
4✔
688
                if chain.is_block_in_chain(block, chain_tip)? == Some(true) {
1✔
689
                    // conflicting tx is in best chain, so the current tx cannot be in best chain!
690
                    return Ok(None);
1✔
691
                }
×
692
            }
693
            if conflicting_tx.last_seen_unconfirmed > *last_seen {
3✔
694
                return Ok(None);
1✔
695
            }
2✔
696
        }
697

698
        Ok(Some(ChainPosition::Unconfirmed(*last_seen)))
162✔
699
    }
1,172✔
700

701
    /// Get the position of the transaction in `chain` with tip `chain_tip`.
702
    ///
703
    /// This is the infallible version of [`try_get_chain_position`].
704
    ///
705
    /// [`try_get_chain_position`]: Self::try_get_chain_position
706
    pub fn get_chain_position<C: ChainOracle<Error = Infallible>>(
212✔
707
        &self,
212✔
708
        chain: &C,
212✔
709
        chain_tip: BlockId,
212✔
710
        txid: Txid,
212✔
711
    ) -> Option<ChainPosition<&A>> {
212✔
712
        self.try_get_chain_position(chain, chain_tip, txid)
212✔
713
            .expect("error is infallible")
212✔
714
    }
212✔
715

716
    /// Get the txid of the spending transaction and where the spending transaction is observed in
717
    /// the `chain` of `chain_tip`.
718
    ///
719
    /// If no in-chain transaction spends `outpoint`, `None` will be returned.
720
    ///
721
    /// # Error
722
    ///
723
    /// An error will occur only if the [`ChainOracle`] implementation (`chain`) fails.
724
    ///
725
    /// If the [`ChainOracle`] is infallible, [`get_chain_spend`] can be used instead.
726
    ///
727
    /// [`get_chain_spend`]: Self::get_chain_spend
728
    pub fn try_get_chain_spend<C: ChainOracle>(
393✔
729
        &self,
393✔
730
        chain: &C,
393✔
731
        chain_tip: BlockId,
393✔
732
        outpoint: OutPoint,
393✔
733
    ) -> Result<Option<(ChainPosition<&A>, Txid)>, C::Error> {
393✔
734
        if self
393✔
735
            .try_get_chain_position(chain, chain_tip, outpoint.txid)?
393✔
736
            .is_none()
393✔
737
        {
738
            return Ok(None);
×
739
        }
393✔
740
        if let Some(spends) = self.spends.get(&outpoint) {
393✔
741
            for &txid in spends {
168✔
742
                if let Some(observed_at) = self.try_get_chain_position(chain, chain_tip, txid)? {
168✔
743
                    return Ok(Some((observed_at, txid)));
168✔
744
                }
×
745
            }
746
        }
225✔
747
        Ok(None)
225✔
748
    }
393✔
749

750
    /// Get the txid of the spending transaction and where the spending transaction is observed in
751
    /// the `chain` of `chain_tip`.
752
    ///
753
    /// This is the infallible version of [`try_get_chain_spend`]
754
    ///
755
    /// [`try_get_chain_spend`]: Self::try_get_chain_spend
756
    pub fn get_chain_spend<C: ChainOracle<Error = Infallible>>(
4✔
757
        &self,
4✔
758
        chain: &C,
4✔
759
        static_block: BlockId,
4✔
760
        outpoint: OutPoint,
4✔
761
    ) -> Option<(ChainPosition<&A>, Txid)> {
4✔
762
        self.try_get_chain_spend(chain, static_block, outpoint)
4✔
763
            .expect("error is infallible")
4✔
764
    }
4✔
765

766
    /// List graph transactions that are in `chain` with `chain_tip`.
767
    ///
768
    /// Each transaction is represented as a [`CanonicalTx`] that contains where the transaction is
769
    /// observed in-chain, and the [`TxNode`].
770
    ///
771
    /// # Error
772
    ///
773
    /// If the [`ChainOracle`] implementation (`chain`) fails, an error will be returned with the
774
    /// returned item.
775
    ///
776
    /// If the [`ChainOracle`] is infallible, [`list_chain_txs`] can be used instead.
777
    ///
778
    /// [`list_chain_txs`]: Self::list_chain_txs
779
    pub fn try_list_chain_txs<'a, C: ChainOracle + 'a>(
8✔
780
        &'a self,
8✔
781
        chain: &'a C,
8✔
782
        chain_tip: BlockId,
8✔
783
    ) -> impl Iterator<Item = Result<CanonicalTx<'a, Transaction, A>, C::Error>> {
8✔
784
        self.full_txs().filter_map(move |tx| {
11✔
785
            self.try_get_chain_position(chain, chain_tip, tx.txid)
11✔
786
                .map(|v| {
11✔
787
                    v.map(|observed_in| CanonicalTx {
11✔
788
                        chain_position: observed_in,
11✔
789
                        tx_node: tx,
11✔
790
                    })
11✔
791
                })
11✔
792
                .transpose()
11✔
793
        })
11✔
794
    }
8✔
795

796
    /// List graph transactions that are in `chain` with `chain_tip`.
797
    ///
798
    /// This is the infallible version of [`try_list_chain_txs`].
799
    ///
800
    /// [`try_list_chain_txs`]: Self::try_list_chain_txs
801
    pub fn list_chain_txs<'a, C: ChainOracle + 'a>(
8✔
802
        &'a self,
8✔
803
        chain: &'a C,
8✔
804
        chain_tip: BlockId,
8✔
805
    ) -> impl Iterator<Item = CanonicalTx<'a, Transaction, A>> {
8✔
806
        self.try_list_chain_txs(chain, chain_tip)
8✔
807
            .map(|r| r.expect("oracle is infallible"))
11✔
808
    }
8✔
809

810
    /// Get a filtered list of outputs from the given `outpoints` that are in `chain` with
811
    /// `chain_tip`.
812
    ///
813
    /// `outpoints` is a list of outpoints we are interested in, coupled with an outpoint identifier
814
    /// (`OI`) for convenience. If `OI` is not necessary, the caller can use `()`, or
815
    /// [`Iterator::enumerate`] over a list of [`OutPoint`]s.
816
    ///
817
    /// Floating outputs are ignored.
818
    ///
819
    /// # Error
820
    ///
821
    /// An [`Iterator::Item`] can be an [`Err`] if the [`ChainOracle`] implementation (`chain`)
822
    /// fails.
823
    ///
824
    /// If the [`ChainOracle`] implementation is infallible, [`filter_chain_txouts`] can be used
825
    /// instead.
826
    ///
827
    /// [`filter_chain_txouts`]: Self::filter_chain_txouts
828
    pub fn try_filter_chain_txouts<'a, C: ChainOracle + 'a, OI: Clone + 'a>(
166✔
829
        &'a self,
166✔
830
        chain: &'a C,
166✔
831
        chain_tip: BlockId,
166✔
832
        outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
166✔
833
    ) -> impl Iterator<Item = Result<(OI, FullTxOut<A>), C::Error>> + 'a {
166✔
834
        outpoints
166✔
835
            .into_iter()
166✔
836
            .map(
166✔
837
                move |(spk_i, op)| -> Result<Option<(OI, FullTxOut<_>)>, C::Error> {
166✔
838
                    let tx_node = match self.get_tx_node(op.txid) {
389✔
839
                        Some(n) => n,
389✔
840
                        None => return Ok(None),
×
841
                    };
842

843
                    let txout = match tx_node.tx.output.get(op.vout as usize) {
389✔
844
                        Some(txout) => txout.clone(),
389✔
845
                        None => return Ok(None),
×
846
                    };
847

848
                    let chain_position =
388✔
849
                        match self.try_get_chain_position(chain, chain_tip, op.txid)? {
389✔
850
                            Some(pos) => pos.cloned(),
388✔
851
                            None => return Ok(None),
×
852
                        };
853

854
                    let spent_by = self
389✔
855
                        .try_get_chain_spend(chain, chain_tip, op)?
388✔
856
                        .map(|(a, txid)| (a.cloned(), txid));
389✔
857

389✔
858
                    Ok(Some((
389✔
859
                        spk_i,
389✔
860
                        FullTxOut {
389✔
861
                            outpoint: op,
389✔
862
                            txout,
389✔
863
                            chain_position,
389✔
864
                            spent_by,
389✔
865
                            is_on_coinbase: tx_node.tx.is_coin_base(),
389✔
866
                        },
389✔
867
                    )))
389✔
868
                },
389✔
869
            )
166✔
870
            .filter_map(Result::transpose)
166✔
871
    }
166✔
872

873
    /// Get a filtered list of outputs from the given `outpoints` that are in `chain` with
874
    /// `chain_tip`.
875
    ///
876
    /// This is the infallible version of [`try_filter_chain_txouts`].
877
    ///
878
    /// [`try_filter_chain_txouts`]: Self::try_filter_chain_txouts
879
    pub fn filter_chain_txouts<'a, C: ChainOracle<Error = Infallible> + 'a, OI: Clone + 'a>(
5✔
880
        &'a self,
5✔
881
        chain: &'a C,
5✔
882
        chain_tip: BlockId,
5✔
883
        outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
5✔
884
    ) -> impl Iterator<Item = (OI, FullTxOut<A>)> + 'a {
5✔
885
        self.try_filter_chain_txouts(chain, chain_tip, outpoints)
5✔
886
            .map(|r| r.expect("oracle is infallible"))
25✔
887
    }
5✔
888

889
    /// Get a filtered list of unspent outputs (UTXOs) from the given `outpoints` that are in
890
    /// `chain` with `chain_tip`.
891
    ///
892
    /// `outpoints` is a list of outpoints we are interested in, coupled with an outpoint identifier
893
    /// (`OI`) for convenience. If `OI` is not necessary, the caller can use `()`, or
894
    /// [`Iterator::enumerate`] over a list of [`OutPoint`]s.
895
    ///
896
    /// Floating outputs are ignored.
897
    ///
898
    /// # Error
899
    ///
900
    /// An [`Iterator::Item`] can be an [`Err`] if the [`ChainOracle`] implementation (`chain`)
901
    /// fails.
902
    ///
903
    /// If the [`ChainOracle`] implementation is infallible, [`filter_chain_unspents`] can be used
904
    /// instead.
905
    ///
906
    /// [`filter_chain_unspents`]: Self::filter_chain_unspents
907
    pub fn try_filter_chain_unspents<'a, C: ChainOracle + 'a, OI: Clone + 'a>(
161✔
908
        &'a self,
161✔
909
        chain: &'a C,
161✔
910
        chain_tip: BlockId,
161✔
911
        outpoints: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
161✔
912
    ) -> impl Iterator<Item = Result<(OI, FullTxOut<A>), C::Error>> + 'a {
161✔
913
        self.try_filter_chain_txouts(chain, chain_tip, outpoints)
161✔
914
            .filter(|r| match r {
364✔
915
                // keep unspents, drop spents
916
                Ok((_, full_txo)) => full_txo.spent_by.is_none(),
364✔
917
                // keep errors
918
                Err(_) => true,
×
919
            })
364✔
920
    }
161✔
921

922
    /// Get a filtered list of unspent outputs (UTXOs) from the given `outpoints` that are in
923
    /// `chain` with `chain_tip`.
924
    ///
925
    /// This is the infallible version of [`try_filter_chain_unspents`].
926
    ///
927
    /// [`try_filter_chain_unspents`]: Self::try_filter_chain_unspents
928
    pub fn filter_chain_unspents<'a, C: ChainOracle<Error = Infallible> + 'a, OI: Clone + 'a>(
153✔
929
        &'a self,
153✔
930
        chain: &'a C,
153✔
931
        chain_tip: BlockId,
153✔
932
        txouts: impl IntoIterator<Item = (OI, OutPoint)> + 'a,
153✔
933
    ) -> impl Iterator<Item = (OI, FullTxOut<A>)> + 'a {
153✔
934
        self.try_filter_chain_unspents(chain, chain_tip, txouts)
153✔
935
            .map(|r| r.expect("oracle is infallible"))
182✔
936
    }
153✔
937

938
    /// Get the total balance of `outpoints` that are in `chain` of `chain_tip`.
939
    ///
940
    /// The output of `trust_predicate` should return `true` for scripts that we trust.
941
    ///
942
    /// `outpoints` is a list of outpoints we are interested in, coupled with an outpoint identifier
943
    /// (`OI`) for convenience. If `OI` is not necessary, the caller can use `()`, or
944
    /// [`Iterator::enumerate`] over a list of [`OutPoint`]s.
945
    ///
946
    /// If the provided [`ChainOracle`] implementation (`chain`) is infallible, [`balance`] can be
947
    /// used instead.
948
    ///
949
    /// [`balance`]: Self::balance
950
    pub fn try_balance<C: ChainOracle, OI: Clone>(
8✔
951
        &self,
8✔
952
        chain: &C,
8✔
953
        chain_tip: BlockId,
8✔
954
        outpoints: impl IntoIterator<Item = (OI, OutPoint)>,
8✔
955
        mut trust_predicate: impl FnMut(&OI, &Script) -> bool,
8✔
956
    ) -> Result<Balance, C::Error> {
8✔
957
        let mut immature = 0;
8✔
958
        let mut trusted_pending = 0;
8✔
959
        let mut untrusted_pending = 0;
8✔
960
        let mut confirmed = 0;
8✔
961

962
        for res in self.try_filter_chain_unspents(chain, chain_tip, outpoints) {
23✔
963
            let (spk_i, txout) = res?;
23✔
964

965
            match &txout.chain_position {
23✔
966
                ChainPosition::Confirmed(_) => {
967
                    if txout.is_confirmed_and_spendable(chain_tip.height) {
11✔
968
                        confirmed += txout.txout.value;
6✔
969
                    } else if !txout.is_mature(chain_tip.height) {
6✔
970
                        immature += txout.txout.value;
5✔
971
                    }
5✔
972
                }
973
                ChainPosition::Unconfirmed(_) => {
974
                    if trust_predicate(&spk_i, &txout.txout.script_pubkey) {
12✔
975
                        trusted_pending += txout.txout.value;
7✔
976
                    } else {
7✔
977
                        untrusted_pending += txout.txout.value;
5✔
978
                    }
5✔
979
                }
980
            }
981
        }
982

983
        Ok(Balance {
8✔
984
            immature,
8✔
985
            trusted_pending,
8✔
986
            untrusted_pending,
8✔
987
            confirmed,
8✔
988
        })
8✔
989
    }
8✔
990

991
    /// Get the total balance of `outpoints` that are in `chain` of `chain_tip`.
992
    ///
993
    /// This is the infallible version of [`try_balance`].
994
    ///
995
    /// [`try_balance`]: Self::try_balance
996
    pub fn balance<C: ChainOracle<Error = Infallible>, OI: Clone>(
8✔
997
        &self,
8✔
998
        chain: &C,
8✔
999
        chain_tip: BlockId,
8✔
1000
        outpoints: impl IntoIterator<Item = (OI, OutPoint)>,
8✔
1001
        trust_predicate: impl FnMut(&OI, &Script) -> bool,
8✔
1002
    ) -> Balance {
8✔
1003
        self.try_balance(chain, chain_tip, outpoints, trust_predicate)
8✔
1004
            .expect("oracle is infallible")
8✔
1005
    }
8✔
1006
}
1007

1008
/// A structure that represents changes to a [`TxGraph`].
1009
///
1010
/// Since [`TxGraph`] is monotone "changeset" can only contain transactions to be added and
1011
/// not removed.
1012
///
1013
/// Refer to [module-level documentation] for more.
1014
///
1015
/// [module-level documentation]: crate::tx_graph
1016
#[derive(Debug, Clone, PartialEq)]
998✔
1017
#[cfg_attr(
1018
    feature = "serde",
1019
    derive(serde::Deserialize, serde::Serialize),
×
1020
    serde(
1021
        crate = "serde_crate",
1022
        bound(
1023
            deserialize = "A: Ord + serde::Deserialize<'de>",
1024
            serialize = "A: Ord + serde::Serialize",
1025
        )
1026
    )
1027
)]
1028
#[must_use]
1029
pub struct ChangeSet<A = ()> {
1030
    /// Added transactions.
1031
    pub txs: BTreeSet<Transaction>,
1032
    /// Added txouts.
1033
    pub txouts: BTreeMap<OutPoint, TxOut>,
1034
    /// Added anchors.
1035
    pub anchors: BTreeSet<(A, Txid)>,
1036
    /// Added last-seen unix timestamps of transactions.
1037
    pub last_seen: BTreeMap<Txid, u64>,
1038
}
1039

1040
impl<A> Default for ChangeSet<A> {
1041
    fn default() -> Self {
22,580✔
1042
        Self {
22,580✔
1043
            txs: Default::default(),
22,580✔
1044
            txouts: Default::default(),
22,580✔
1045
            anchors: Default::default(),
22,580✔
1046
            last_seen: Default::default(),
22,580✔
1047
        }
22,580✔
1048
    }
22,580✔
1049
}
1050

1051
impl<A> ChangeSet<A> {
1052
    /// Returns true if the [`ChangeSet`] is empty (no transactions or txouts).
1053
    pub fn is_empty(&self) -> bool {
4,387✔
1054
        self.txs.is_empty() && self.txouts.is_empty()
4,387✔
1055
    }
4,387✔
1056

1057
    /// Iterates over all outpoints contained within [`ChangeSet`].
1058
    pub fn txouts(&self) -> impl Iterator<Item = (OutPoint, &TxOut)> {
×
1059
        self.txs
×
1060
            .iter()
×
1061
            .flat_map(|tx| {
×
1062
                tx.output
×
1063
                    .iter()
×
1064
                    .enumerate()
×
1065
                    .map(move |(vout, txout)| (OutPoint::new(tx.txid(), vout as _), txout))
×
1066
            })
×
1067
            .chain(self.txouts.iter().map(|(op, txout)| (*op, txout)))
×
1068
    }
×
1069

1070
    /// Iterates over the heights of that the new transaction anchors in this changeset.
1071
    ///
1072
    /// This is useful if you want to find which heights you need to fetch data about in order to
1073
    /// confirm or exclude these anchors.
1074
    ///
1075
    /// See also: [`TxGraph::missing_heights`]
1076
    pub fn anchor_heights(&self) -> impl Iterator<Item = u32> + '_
×
1077
    where
×
1078
        A: Anchor,
×
1079
    {
×
1080
        let mut dedup = None;
×
1081
        self.anchors
×
1082
            .iter()
×
1083
            .map(|(a, _)| a.anchor_block().height)
×
1084
            .filter(move |height| {
×
1085
                let duplicate = dedup == Some(*height);
×
1086
                dedup = Some(*height);
×
1087
                !duplicate
×
1088
            })
×
1089
    }
×
1090

1091
    /// Returns an iterator for the [`anchor_heights`] in this changeset that are not included in
1092
    /// `local_chain`. This tells you which heights you need to include in `local_chain` in order
1093
    /// for it to conclusively act as a [`ChainOracle`] for the transaction anchors this changeset
1094
    /// will add.
1095
    ///
1096
    /// [`ChainOracle`]: crate::ChainOracle
1097
    /// [`anchor_heights`]: Self::anchor_heights
1098
    pub fn missing_heights_from<'a>(
×
1099
        &'a self,
×
1100
        local_chain: &'a LocalChain,
×
1101
    ) -> impl Iterator<Item = u32> + 'a
×
1102
    where
×
1103
        A: Anchor,
×
1104
    {
×
1105
        self.anchor_heights()
×
1106
            .filter(move |height| !local_chain.blocks().contains_key(height))
×
1107
    }
×
1108
}
1109

1110
impl<A: Ord> Append for ChangeSet<A> {
1111
    fn append(&mut self, mut other: Self) {
7,341✔
1112
        self.txs.append(&mut other.txs);
7,341✔
1113
        self.txouts.append(&mut other.txouts);
7,341✔
1114
        self.anchors.append(&mut other.anchors);
7,341✔
1115

7,341✔
1116
        // last_seen timestamps should only increase
7,341✔
1117
        self.last_seen.extend(
7,341✔
1118
            other
7,341✔
1119
                .last_seen
7,341✔
1120
                .into_iter()
7,341✔
1121
                .filter(|(txid, update_ls)| self.last_seen.get(txid) < Some(update_ls))
7,341✔
1122
                .collect::<Vec<_>>(),
7,341✔
1123
        );
7,341✔
1124
    }
7,341✔
1125

1126
    fn is_empty(&self) -> bool {
×
1127
        self.txs.is_empty()
×
1128
            && self.txouts.is_empty()
×
1129
            && self.anchors.is_empty()
×
1130
            && self.last_seen.is_empty()
×
1131
    }
×
1132
}
1133

1134
impl<A> AsRef<TxGraph<A>> for TxGraph<A> {
1135
    fn as_ref(&self) -> &TxGraph<A> {
×
1136
        self
×
1137
    }
×
1138
}
1139

1140
/// An iterator that traverses transaction descendants.
1141
///
1142
/// This `struct` is created by the [`walk_descendants`] method of [`TxGraph`].
1143
///
1144
/// [`walk_descendants`]: TxGraph::walk_descendants
1145
pub struct TxDescendants<'g, A, F> {
1146
    graph: &'g TxGraph<A>,
1147
    visited: HashSet<Txid>,
1148
    stack: Vec<(usize, Txid)>,
1149
    filter_map: F,
1150
}
1151

1152
impl<'g, A, F> TxDescendants<'g, A, F> {
1153
    /// Creates a `TxDescendants` that includes the starting `txid` when iterating.
1154
    #[allow(unused)]
1155
    pub(crate) fn new_include_root(graph: &'g TxGraph<A>, txid: Txid, filter_map: F) -> Self {
×
1156
        Self {
×
1157
            graph,
×
1158
            visited: Default::default(),
×
1159
            stack: [(0, txid)].into(),
×
1160
            filter_map,
×
1161
        }
×
1162
    }
×
1163

1164
    /// Creates a `TxDescendants` that excludes the starting `txid` when iterating.
1165
    pub(crate) fn new_exclude_root(graph: &'g TxGraph<A>, txid: Txid, filter_map: F) -> Self {
1✔
1166
        let mut descendants = Self {
1✔
1167
            graph,
1✔
1168
            visited: Default::default(),
1✔
1169
            stack: Default::default(),
1✔
1170
            filter_map,
1✔
1171
        };
1✔
1172
        descendants.populate_stack(1, txid);
1✔
1173
        descendants
1✔
1174
    }
1✔
1175

1176
    /// Creates a `TxDescendants` from multiple starting transactions that include the starting
1177
    /// `txid`s when iterating.
1178
    pub(crate) fn from_multiple_include_root<I>(
165✔
1179
        graph: &'g TxGraph<A>,
165✔
1180
        txids: I,
165✔
1181
        filter_map: F,
165✔
1182
    ) -> Self
165✔
1183
    where
165✔
1184
        I: IntoIterator<Item = Txid>,
165✔
1185
    {
165✔
1186
        Self {
165✔
1187
            graph,
165✔
1188
            visited: Default::default(),
165✔
1189
            stack: txids.into_iter().map(|txid| (0, txid)).collect(),
165✔
1190
            filter_map,
165✔
1191
        }
165✔
1192
    }
165✔
1193

1194
    /// Creates a `TxDescendants` from multiple starting transactions that excludes the starting
1195
    /// `txid`s when iterating.
1196
    #[allow(unused)]
1197
    pub(crate) fn from_multiple_exclude_root<I>(
×
1198
        graph: &'g TxGraph<A>,
×
1199
        txids: I,
×
1200
        filter_map: F,
×
1201
    ) -> Self
×
1202
    where
×
1203
        I: IntoIterator<Item = Txid>,
×
1204
    {
×
1205
        let mut descendants = Self {
×
1206
            graph,
×
1207
            visited: Default::default(),
×
1208
            stack: Default::default(),
×
1209
            filter_map,
×
1210
        };
×
1211
        for txid in txids {
×
1212
            descendants.populate_stack(1, txid);
×
1213
        }
×
1214
        descendants
×
1215
    }
×
1216
}
1217

1218
impl<'g, A, F> TxDescendants<'g, A, F> {
1219
    fn populate_stack(&mut self, depth: usize, txid: Txid) {
14✔
1220
        let spend_paths = self
14✔
1221
            .graph
14✔
1222
            .spends
14✔
1223
            .range(tx_outpoint_range(txid))
14✔
1224
            .flat_map(|(_, spends)| spends)
14✔
1225
            .map(|&txid| (depth, txid));
14✔
1226
        self.stack.extend(spend_paths);
14✔
1227
    }
14✔
1228
}
1229

1230
impl<'g, A, F, O> Iterator for TxDescendants<'g, A, F>
1231
where
1232
    F: FnMut(usize, Txid) -> Option<O>,
1233
{
1234
    type Item = O;
1235

1236
    fn next(&mut self) -> Option<Self::Item> {
177✔
1237
        let (op_spends, txid, item) = loop {
13✔
1238
            // we have exhausted all paths when stack is empty
1239
            let (op_spends, txid) = self.stack.pop()?;
178✔
1240
            // we do not want to visit the same transaction twice
1241
            if self.visited.insert(txid) {
14✔
1242
                // ignore paths when user filters them out
1243
                if let Some(item) = (self.filter_map)(op_spends, txid) {
13✔
1244
                    break (op_spends, txid, item);
13✔
1245
                }
×
1246
            }
1✔
1247
        };
1248

1249
        self.populate_stack(op_spends + 1, txid);
13✔
1250
        Some(item)
13✔
1251
    }
177✔
1252
}
1253

1254
fn tx_outpoint_range(txid: Txid) -> RangeInclusive<OutPoint> {
238✔
1255
    OutPoint::new(txid, u32::MIN)..=OutPoint::new(txid, u32::MAX)
238✔
1256
}
238✔
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