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

bitcoindevkit / bdk / 9713429358

28 Jun 2024 01:15PM UTC coverage: 83.028% (+0.004%) from 83.024%
9713429358

Pull #1493

github

web-flow
Merge eae3ffe96 into 5c7cc3097
Pull Request #1493: [chain] Create module `keychain_txout_index`

15 of 17 new or added lines in 5 files covered. (88.24%)

1 existing line in 1 file now uncovered.

11051 of 13310 relevant lines covered (83.03%)

16853.48 hits per line

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

93.09
/crates/chain/src/indexed_tx_graph.rs
1
//! Contains the [`IndexedTxGraph`] and associated types. Refer to the
2
//! [`IndexedTxGraph`] documentation for more.
3
use alloc::vec::Vec;
4
use bitcoin::{Block, OutPoint, Transaction, TxOut, Txid};
5

6
use crate::{
7
    tx_graph::{self, TxGraph},
8
    Anchor, AnchorFromBlockPosition, Append, BlockId, Indexer,
9
};
10

11
/// The [`IndexedTxGraph`] combines a [`TxGraph`] and an [`Indexer`] implementation.
12
///
13
/// It ensures that [`TxGraph`] and [`Indexer`] are updated atomically.
14
#[derive(Debug)]
15
pub struct IndexedTxGraph<A, I> {
16
    /// Transaction index.
17
    pub index: I,
18
    graph: TxGraph<A>,
19
}
20

21
impl<A, I: Default> Default for IndexedTxGraph<A, I> {
22
    fn default() -> Self {
×
23
        Self {
×
24
            graph: Default::default(),
×
25
            index: Default::default(),
×
26
        }
×
27
    }
×
28
}
29

30
impl<A, I> IndexedTxGraph<A, I> {
31
    /// Construct a new [`IndexedTxGraph`] with a given `index`.
32
    pub fn new(index: I) -> Self {
1,444✔
33
        Self {
1,444✔
34
            index,
1,444✔
35
            graph: TxGraph::default(),
1,444✔
36
        }
1,444✔
37
    }
1,444✔
38

39
    /// Get a reference of the internal transaction graph.
40
    pub fn graph(&self) -> &TxGraph<A> {
5,781✔
41
        &self.graph
5,781✔
42
    }
5,781✔
43
}
44

45
impl<A: Anchor, I: Indexer> IndexedTxGraph<A, I> {
46
    /// Applies the [`ChangeSet`] to the [`IndexedTxGraph`].
47
    pub fn apply_changeset(&mut self, changeset: ChangeSet<A, I::ChangeSet>) {
96✔
48
        self.index.apply_changeset(changeset.indexer);
96✔
49

50
        for tx in &changeset.graph.txs {
96✔
51
            self.index.index_tx(tx);
×
52
        }
×
53
        for (&outpoint, txout) in &changeset.graph.txouts {
96✔
54
            self.index.index_txout(outpoint, txout);
×
55
        }
×
56

57
        self.graph.apply_changeset(changeset.graph);
96✔
58
    }
96✔
59

60
    /// Determines the [`ChangeSet`] between `self` and an empty [`IndexedTxGraph`].
61
    pub fn initial_changeset(&self) -> ChangeSet<A, I::ChangeSet> {
151✔
62
        let graph = self.graph.initial_changeset();
151✔
63
        let indexer = self.index.initial_changeset();
151✔
64
        ChangeSet { graph, indexer }
151✔
65
    }
151✔
66
}
67

68
impl<A: Anchor, I: Indexer> IndexedTxGraph<A, I>
69
where
70
    I::ChangeSet: Default + Append,
71
{
72
    fn index_tx_graph_changeset(
2,604✔
73
        &mut self,
2,604✔
74
        tx_graph_changeset: &tx_graph::ChangeSet<A>,
2,604✔
75
    ) -> I::ChangeSet {
2,604✔
76
        let mut changeset = I::ChangeSet::default();
2,604✔
77
        for added_tx in &tx_graph_changeset.txs {
5,204✔
78
            changeset.append(self.index.index_tx(added_tx));
2,600✔
79
        }
2,600✔
80
        for (&added_outpoint, added_txout) in &tx_graph_changeset.txouts {
2,621✔
81
            changeset.append(self.index.index_txout(added_outpoint, added_txout));
17✔
82
        }
17✔
83
        changeset
2,604✔
84
    }
2,604✔
85

86
    /// Apply an `update` directly.
87
    ///
88
    /// `update` is a [`TxGraph<A>`] and the resultant changes is returned as [`ChangeSet`].
89
    pub fn apply_update(&mut self, update: TxGraph<A>) -> ChangeSet<A, I::ChangeSet> {
10✔
90
        let graph = self.graph.apply_update(update);
10✔
91
        let indexer = self.index_tx_graph_changeset(&graph);
10✔
92
        ChangeSet { graph, indexer }
10✔
93
    }
10✔
94

95
    /// Insert a floating `txout` of given `outpoint`.
96
    pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<A, I::ChangeSet> {
16✔
97
        let graph = self.graph.insert_txout(outpoint, txout);
16✔
98
        let indexer = self.index_tx_graph_changeset(&graph);
16✔
99
        ChangeSet { graph, indexer }
16✔
100
    }
16✔
101

102
    /// Insert and index a transaction into the graph.
103
    pub fn insert_tx(&mut self, tx: Transaction) -> ChangeSet<A, I::ChangeSet> {
2,398✔
104
        let graph = self.graph.insert_tx(tx);
2,398✔
105
        let indexer = self.index_tx_graph_changeset(&graph);
2,398✔
106
        ChangeSet { graph, indexer }
2,398✔
107
    }
2,398✔
108

109
    /// Insert an `anchor` for a given transaction.
110
    pub fn insert_anchor(&mut self, txid: Txid, anchor: A) -> ChangeSet<A, I::ChangeSet> {
2,214✔
111
        self.graph.insert_anchor(txid, anchor).into()
2,214✔
112
    }
2,214✔
113

114
    /// Insert a unix timestamp of when a transaction is seen in the mempool.
115
    ///
116
    /// This is used for transaction conflict resolution in [`TxGraph`] where the transaction with
117
    /// the later last-seen is prioritized.
118
    pub fn insert_seen_at(&mut self, txid: Txid, seen_at: u64) -> ChangeSet<A, I::ChangeSet> {
184✔
119
        self.graph.insert_seen_at(txid, seen_at).into()
184✔
120
    }
184✔
121

122
    /// Batch insert transactions, filtering out those that are irrelevant.
123
    ///
124
    /// Relevancy is determined by the [`Indexer::is_tx_relevant`] implementation of `I`. Irrelevant
125
    /// transactions in `txs` will be ignored. `txs` do not need to be in topological order.
126
    pub fn batch_insert_relevant<'t>(
2✔
127
        &mut self,
2✔
128
        txs: impl IntoIterator<Item = (&'t Transaction, impl IntoIterator<Item = A>)>,
2✔
129
    ) -> ChangeSet<A, I::ChangeSet> {
2✔
130
        // The algorithm below allows for non-topologically ordered transactions by using two loops.
2✔
131
        // This is achieved by:
2✔
132
        // 1. insert all txs into the index. If they are irrelevant then that's fine it will just
2✔
133
        //    not store anything about them.
2✔
134
        // 2. decide whether to insert them into the graph depending on whether `is_tx_relevant`
2✔
135
        //    returns true or not. (in a second loop).
2✔
136
        let txs = txs.into_iter().collect::<Vec<_>>();
2✔
137

2✔
138
        let mut indexer = I::ChangeSet::default();
2✔
139
        for (tx, _) in &txs {
9✔
140
            indexer.append(self.index.index_tx(tx));
7✔
141
        }
7✔
142

143
        let mut graph = tx_graph::ChangeSet::default();
2✔
144
        for (tx, anchors) in txs {
9✔
145
            if self.index.is_tx_relevant(tx) {
7✔
146
                let txid = tx.compute_txid();
6✔
147
                graph.append(self.graph.insert_tx(tx.clone()));
6✔
148
                for anchor in anchors {
9✔
149
                    graph.append(self.graph.insert_anchor(txid, anchor));
3✔
150
                }
3✔
151
            }
1✔
152
        }
153

154
        ChangeSet { graph, indexer }
2✔
155
    }
2✔
156

157
    /// Batch insert unconfirmed transactions, filtering out those that are irrelevant.
158
    ///
159
    /// Relevancy is determined by the internal [`Indexer::is_tx_relevant`] implementation of `I`.
160
    /// Irrelevant transactions in `txs` will be ignored.
161
    ///
162
    /// Items of `txs` are tuples containing the transaction and a *last seen* timestamp. The
163
    /// *last seen* communicates when the transaction is last seen in the mempool which is used for
164
    /// conflict-resolution in [`TxGraph`] (refer to [`TxGraph::insert_seen_at`] for details).
165
    pub fn batch_insert_relevant_unconfirmed<'t>(
1✔
166
        &mut self,
1✔
167
        unconfirmed_txs: impl IntoIterator<Item = (&'t Transaction, u64)>,
1✔
168
    ) -> ChangeSet<A, I::ChangeSet> {
1✔
169
        // The algorithm below allows for non-topologically ordered transactions by using two loops.
1✔
170
        // This is achieved by:
1✔
171
        // 1. insert all txs into the index. If they are irrelevant then that's fine it will just
1✔
172
        //    not store anything about them.
1✔
173
        // 2. decide whether to insert them into the graph depending on whether `is_tx_relevant`
1✔
174
        //    returns true or not. (in a second loop).
1✔
175
        let txs = unconfirmed_txs.into_iter().collect::<Vec<_>>();
1✔
176

1✔
177
        let mut indexer = I::ChangeSet::default();
1✔
178
        for (tx, _) in &txs {
3✔
179
            indexer.append(self.index.index_tx(tx));
2✔
180
        }
2✔
181

182
        let graph = self.graph.batch_insert_unconfirmed(
1✔
183
            txs.into_iter()
1✔
184
                .filter(|(tx, _)| self.index.is_tx_relevant(tx))
2✔
185
                .map(|(tx, seen_at)| (tx.clone(), seen_at)),
2✔
186
        );
1✔
187

1✔
188
        ChangeSet { graph, indexer }
1✔
189
    }
1✔
190

191
    /// Batch insert unconfirmed transactions.
192
    ///
193
    /// Items of `txs` are tuples containing the transaction and a *last seen* timestamp. The
194
    /// *last seen* communicates when the transaction is last seen in the mempool which is used for
195
    /// conflict-resolution in [`TxGraph`] (refer to [`TxGraph::insert_seen_at`] for details).
196
    ///
197
    /// To filter out irrelevant transactions, use [`batch_insert_relevant_unconfirmed`] instead.
198
    ///
199
    /// [`batch_insert_relevant_unconfirmed`]: IndexedTxGraph::batch_insert_relevant_unconfirmed
200
    pub fn batch_insert_unconfirmed(
1✔
201
        &mut self,
1✔
202
        txs: impl IntoIterator<Item = (Transaction, u64)>,
1✔
203
    ) -> ChangeSet<A, I::ChangeSet> {
1✔
204
        let graph = self.graph.batch_insert_unconfirmed(txs);
1✔
205
        let indexer = self.index_tx_graph_changeset(&graph);
1✔
206
        ChangeSet { graph, indexer }
1✔
207
    }
1✔
208
}
209

210
/// Methods are available if the anchor (`A`) implements [`AnchorFromBlockPosition`].
211
impl<A: Anchor, I: Indexer> IndexedTxGraph<A, I>
212
where
213
    I::ChangeSet: Default + Append,
214
    A: AnchorFromBlockPosition,
215
{
216
    /// Batch insert all transactions of the given `block` of `height`, filtering out those that are
217
    /// irrelevant.
218
    ///
219
    /// Each inserted transaction's anchor will be constructed from
220
    /// [`AnchorFromBlockPosition::from_block_position`].
221
    ///
222
    /// Relevancy is determined by the internal [`Indexer::is_tx_relevant`] implementation of `I`.
223
    /// Irrelevant transactions in `txs` will be ignored.
224
    pub fn apply_block_relevant(
103✔
225
        &mut self,
103✔
226
        block: &Block,
103✔
227
        height: u32,
103✔
228
    ) -> ChangeSet<A, I::ChangeSet> {
103✔
229
        let block_id = BlockId {
103✔
230
            hash: block.block_hash(),
103✔
231
            height,
103✔
232
        };
103✔
233
        let mut changeset = ChangeSet::<A, I::ChangeSet>::default();
103✔
234
        for (tx_pos, tx) in block.txdata.iter().enumerate() {
106✔
235
            changeset.indexer.append(self.index.index_tx(tx));
106✔
236
            if self.index.is_tx_relevant(tx) {
106✔
237
                let txid = tx.compute_txid();
3✔
238
                let anchor = A::from_block_position(block, block_id, tx_pos);
3✔
239
                changeset.graph.append(self.graph.insert_tx(tx.clone()));
3✔
240
                changeset
3✔
241
                    .graph
3✔
242
                    .append(self.graph.insert_anchor(txid, anchor));
3✔
243
            }
103✔
244
        }
245
        changeset
103✔
246
    }
103✔
247

248
    /// Batch insert all transactions of the given `block` of `height`.
249
    ///
250
    /// Each inserted transaction's anchor will be constructed from
251
    /// [`AnchorFromBlockPosition::from_block_position`].
252
    ///
253
    /// To only insert relevant transactions, use [`apply_block_relevant`] instead.
254
    ///
255
    /// [`apply_block_relevant`]: IndexedTxGraph::apply_block_relevant
256
    pub fn apply_block(&mut self, block: Block, height: u32) -> ChangeSet<A, I::ChangeSet> {
179✔
257
        let block_id = BlockId {
179✔
258
            hash: block.block_hash(),
179✔
259
            height,
179✔
260
        };
179✔
261
        let mut graph = tx_graph::ChangeSet::default();
179✔
262
        for (tx_pos, tx) in block.txdata.iter().enumerate() {
190✔
263
            let anchor = A::from_block_position(&block, block_id, tx_pos);
190✔
264
            graph.append(self.graph.insert_anchor(tx.compute_txid(), anchor));
190✔
265
            graph.append(self.graph.insert_tx(tx.clone()));
190✔
266
        }
190✔
267
        let indexer = self.index_tx_graph_changeset(&graph);
179✔
268
        ChangeSet { graph, indexer }
179✔
269
    }
179✔
270
}
271

272
/// Represents changes to an [`IndexedTxGraph`].
273
#[derive(Clone, Debug, PartialEq)]
274
#[cfg_attr(
275
    feature = "serde",
276
    derive(serde::Deserialize, serde::Serialize),
6✔
277
    serde(
278
        crate = "serde_crate",
279
        bound(
280
            deserialize = "A: Ord + serde::Deserialize<'de>, IA: serde::Deserialize<'de>",
281
            serialize = "A: Ord + serde::Serialize, IA: serde::Serialize"
282
        )
283
    )
284
)]
285
#[must_use]
286
pub struct ChangeSet<A, IA> {
287
    /// [`TxGraph`] changeset.
288
    pub graph: tx_graph::ChangeSet<A>,
289
    /// [`Indexer`] changeset.
290
    pub indexer: IA,
291
}
292

293
impl<A, IA: Default> Default for ChangeSet<A, IA> {
294
    fn default() -> Self {
14,432✔
295
        Self {
14,432✔
296
            graph: Default::default(),
14,432✔
297
            indexer: Default::default(),
14,432✔
298
        }
14,432✔
299
    }
14,432✔
300
}
301

302
impl<A: Anchor, IA: Append> Append for ChangeSet<A, IA> {
303
    fn append(&mut self, other: Self) {
11,089✔
304
        self.graph.append(other.graph);
11,089✔
305
        self.indexer.append(other.indexer);
11,089✔
306
    }
11,089✔
307

308
    fn is_empty(&self) -> bool {
2,506✔
309
        self.graph.is_empty() && self.indexer.is_empty()
2,506✔
310
    }
2,506✔
311
}
312

313
impl<A, IA: Default> From<tx_graph::ChangeSet<A>> for ChangeSet<A, IA> {
314
    fn from(graph: tx_graph::ChangeSet<A>) -> Self {
2,398✔
315
        Self {
2,398✔
316
            graph,
2,398✔
317
            ..Default::default()
2,398✔
318
        }
2,398✔
319
    }
2,398✔
320
}
321

322
#[cfg(feature = "miniscript")]
323
impl<A, K> From<crate::keychain_txout::ChangeSet<K>>
324
    for ChangeSet<A, crate::keychain_txout::ChangeSet<K>>
325
{
326
    fn from(indexer: crate::keychain_txout::ChangeSet<K>) -> Self {
1,008✔
327
        Self {
1,008✔
328
            graph: Default::default(),
1,008✔
329
            indexer,
1,008✔
330
        }
1,008✔
331
    }
1,008✔
332
}
333

334
impl<A, I> AsRef<TxGraph<A>> for IndexedTxGraph<A, I> {
UNCOV
335
    fn as_ref(&self) -> &TxGraph<A> {
×
336
        &self.graph
×
337
    }
×
338
}
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