Skip to content

Commit 4f5695d

Browse files
committed
chain: improvements to IndexedTxGraph and TxGraph APIs
For `IndexedTxGraph`: - Remove `InsertTxItem` type (this is too complex). - `batch_insert_relevant` now uses a simple tuple `(&tx, anchors)`. - `batch_insert` is now also removed, as the same functionality can be done elsewhere. - Add internal helper method `index_tx_graph_changeset` so we don't need to create a seprate `TxGraph` update in each method. - `batch_insert_<relevant>_unconfirmed` no longer takes in an option of last_seen. - `batch_insert_unconfirmed` no longer takes a reference of a transaction (since we apply all transactions anyway, so there is no need to clone). For `TxGraph`: - Add `batch_insert_unconfirmed` method.
1 parent 150d6f8 commit 4f5695d

File tree

6 files changed

+104
-115
lines changed

6 files changed

+104
-115
lines changed

crates/bdk/src/wallet/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ impl<D> Wallet<D> {
509509
where
510510
D: PersistBackend<ChangeSet>,
511511
{
512-
let additions = self.indexed_graph.insert_txout(outpoint, &txout);
512+
let additions = self.indexed_graph.insert_txout(outpoint, txout);
513513
self.persist.stage(ChangeSet::from(additions));
514514
}
515515

crates/bitcoind_rpc/tests/test_emitter.rs

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use std::collections::{BTreeMap, BTreeSet};
33
use bdk_bitcoind_rpc::Emitter;
44
use bdk_chain::{
55
bitcoin::{Address, Amount, BlockHash, Txid},
6-
indexed_tx_graph::InsertTxItem,
76
keychain::Balance,
87
local_chain::{self, CheckPoint, LocalChain},
98
Append, BlockId, IndexedTxGraph, SpkTxOutIndex,
@@ -180,17 +179,6 @@ fn block_to_chain_update(block: &bitcoin::Block, height: u32) -> local_chain::Up
180179
}
181180
}
182181

183-
fn block_to_tx_graph_update(
184-
block: &bitcoin::Block,
185-
height: u32,
186-
) -> impl Iterator<Item = InsertTxItem<'_, Option<BlockId>>> {
187-
let anchor = BlockId {
188-
hash: block.block_hash(),
189-
height,
190-
};
191-
block.txdata.iter().map(move |tx| (tx, Some(anchor), None))
192-
}
193-
194182
/// Ensure that blocks are emitted in order even after reorg.
195183
///
196184
/// 1. Mine 101 blocks.
@@ -321,8 +309,7 @@ fn test_into_tx_graph() -> anyhow::Result<()> {
321309

322310
while let Some((height, block)) = emitter.next_block()? {
323311
let _ = chain.apply_update(block_to_chain_update(&block, height))?;
324-
let indexed_additions =
325-
indexed_tx_graph.batch_insert_relevant(block_to_tx_graph_update(&block, height));
312+
let indexed_additions = indexed_tx_graph.apply_block_relevant(block, height);
326313
assert!(indexed_additions.is_empty());
327314
}
328315

@@ -350,8 +337,7 @@ fn test_into_tx_graph() -> anyhow::Result<()> {
350337
assert!(emitter.next_block()?.is_none());
351338

352339
let mempool_txs = emitter.mempool()?;
353-
let indexed_additions = indexed_tx_graph
354-
.batch_insert_unconfirmed(mempool_txs.iter().map(|(tx, time)| (tx, Some(*time))));
340+
let indexed_additions = indexed_tx_graph.batch_insert_unconfirmed(mempool_txs);
355341
assert_eq!(
356342
indexed_additions
357343
.graph
@@ -383,8 +369,7 @@ fn test_into_tx_graph() -> anyhow::Result<()> {
383369
{
384370
let (height, block) = emitter.next_block()?.expect("must get mined block");
385371
let _ = chain.apply_update(block_to_chain_update(&block, height))?;
386-
let indexed_additions =
387-
indexed_tx_graph.batch_insert_relevant(block_to_tx_graph_update(&block, height));
372+
let indexed_additions = indexed_tx_graph.apply_block_relevant(block, height);
388373
assert!(indexed_additions.graph.txs.is_empty());
389374
assert!(indexed_additions.graph.txouts.is_empty());
390375
assert_eq!(indexed_additions.graph.anchors, exp_anchors);

crates/chain/src/indexed_tx_graph.rs

Lines changed: 79 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -72,32 +72,34 @@ impl<A: Anchor, I: Indexer> IndexedTxGraph<A, I>
7272
where
7373
I::ChangeSet: Default + Append,
7474
{
75+
fn index_tx_graph_changeset(
76+
&mut self,
77+
tx_graph_changeset: &tx_graph::ChangeSet<A>,
78+
) -> I::ChangeSet {
79+
let mut changeset = I::ChangeSet::default();
80+
for added_tx in &tx_graph_changeset.txs {
81+
changeset.append(self.index.index_tx(added_tx));
82+
}
83+
for (&added_outpoint, added_txout) in &tx_graph_changeset.txouts {
84+
changeset.append(self.index.index_txout(added_outpoint, added_txout));
85+
}
86+
changeset
87+
}
88+
7589
/// Apply an `update` directly.
7690
///
7791
/// `update` is a [`TxGraph<A>`] and the resultant changes is returned as [`ChangeSet`].
7892
pub fn apply_update(&mut self, update: TxGraph<A>) -> ChangeSet<A, I::ChangeSet> {
7993
let graph = self.graph.apply_update(update);
80-
81-
let mut indexer = I::ChangeSet::default();
82-
for added_tx in &graph.txs {
83-
indexer.append(self.index.index_tx(added_tx));
84-
}
85-
for (&added_outpoint, added_txout) in &graph.txouts {
86-
indexer.append(self.index.index_txout(added_outpoint, added_txout));
87-
}
88-
94+
let indexer = self.index_tx_graph_changeset(&graph);
8995
ChangeSet { graph, indexer }
9096
}
9197

9298
/// Insert a floating `txout` of given `outpoint`.
93-
pub fn insert_txout(
94-
&mut self,
95-
outpoint: OutPoint,
96-
txout: &TxOut,
97-
) -> ChangeSet<A, I::ChangeSet> {
98-
let mut update = TxGraph::<A>::default();
99-
let _ = update.insert_txout(outpoint, txout.clone());
100-
self.apply_update(update)
99+
pub fn insert_txout(&mut self, outpoint: OutPoint, txout: TxOut) -> ChangeSet<A, I::ChangeSet> {
100+
let graph = self.graph.insert_txout(outpoint, txout);
101+
let indexer = self.index_tx_graph_changeset(&graph);
102+
ChangeSet { graph, indexer }
101103
}
102104

103105
/// Insert and index a transaction into the graph.
@@ -112,18 +114,19 @@ where
112114
) -> ChangeSet<A, I::ChangeSet> {
113115
let txid = tx.txid();
114116

115-
let mut update = TxGraph::<A>::default();
117+
let mut graph = tx_graph::ChangeSet::default();
116118
if self.graph.get_tx(txid).is_none() {
117-
let _ = update.insert_tx(tx.clone());
119+
graph.append(self.graph.insert_tx(tx.clone()));
118120
}
119121
for anchor in anchors.into_iter() {
120-
let _ = update.insert_anchor(txid, anchor);
122+
graph.append(self.graph.insert_anchor(txid, anchor));
121123
}
122124
if let Some(seen_at) = seen_at {
123-
let _ = update.insert_seen_at(txid, seen_at);
125+
graph.append(self.graph.insert_seen_at(txid, seen_at));
124126
}
125127

126-
self.apply_update(update)
128+
let indexer = self.index_tx_graph_changeset(&graph);
129+
ChangeSet { graph, indexer }
127130
}
128131

129132
/// Batch insert transactions, filtering out those that are irrelevant.
@@ -132,85 +135,85 @@ where
132135
/// transactions in `txs` will be ignored. `txs` do not need to be in topological order.
133136
pub fn batch_insert_relevant<'t>(
134137
&mut self,
135-
txs: impl IntoIterator<Item = InsertTxItem<'t, impl IntoIterator<Item = A>>>,
138+
txs: impl IntoIterator<Item = (&'t Transaction, impl IntoIterator<Item = A>)>,
136139
) -> ChangeSet<A, I::ChangeSet> {
137140
// The algorithm below allows for non-topologically ordered transactions by using two loops.
138141
// This is achieved by:
139142
// 1. insert all txs into the index. If they are irrelevant then that's fine it will just
140143
// not store anything about them.
141144
// 2. decide whether to insert them into the graph depending on whether `is_tx_relevant`
142145
// returns true or not. (in a second loop).
143-
let mut changeset = ChangeSet::<A, I::ChangeSet>::default();
146+
let txs = txs.into_iter().collect::<Vec<_>>();
144147

145-
let txs = txs
146-
.into_iter()
147-
.inspect(|(tx, _, _)| changeset.indexer.append(self.index.index_tx(tx)))
148-
.collect::<Vec<_>>();
148+
let mut indexer = I::ChangeSet::default();
149+
for (tx, _) in &txs {
150+
indexer.append(self.index.index_tx(tx));
151+
}
149152

150-
for (tx, anchors, seen_at) in txs {
153+
let mut graph = tx_graph::ChangeSet::default();
154+
for (tx, anchors) in txs {
151155
if self.index.is_tx_relevant(tx) {
152-
changeset.append(self.insert_tx(tx, anchors, seen_at));
156+
let txid = tx.txid();
157+
graph.append(self.graph.insert_tx(tx.clone()));
158+
for anchor in anchors {
159+
graph.append(self.graph.insert_anchor(txid, anchor));
160+
}
153161
}
154162
}
155163

156-
changeset
157-
}
158-
159-
/// Batch insert transactions.
160-
///
161-
/// All transactions in `txs` will be inserted. To filter out irrelevant transactions, use
162-
/// [`batch_insert_relevant`] instead.
163-
///
164-
/// [`batch_insert_relevant`]: IndexedTxGraph::batch_insert_relevant
165-
pub fn batch_insert<'t>(
166-
&mut self,
167-
txs: impl IntoIterator<Item = InsertTxItem<'t, impl IntoIterator<Item = A>>>,
168-
) -> ChangeSet<A, I::ChangeSet> {
169-
let mut changeset = ChangeSet::<A, I::ChangeSet>::default();
170-
for (tx, anchors, seen_at) in txs {
171-
changeset.indexer.append(self.index.index_tx(tx));
172-
changeset.append(self.insert_tx(tx, anchors, seen_at));
173-
}
174-
changeset
164+
ChangeSet { graph, indexer }
175165
}
176166

177167
/// Batch insert unconfirmed transactions, filtering out those that are irrelevant.
178168
///
179169
/// Relevancy is determined by the internal [`Indexer::is_tx_relevant`] implementation of `I`.
180170
/// Irrelevant tansactions in `txs` will be ignored.
181171
///
182-
/// Items of `txs` are tuples containing the transaction and an optional *last seen* timestamp.
183-
/// The *last seen* communicates when the transaction is last seen in the mempool which is used
184-
/// for conflict-resolution in [`TxGraph`] (refer to [`TxGraph::insert_seen_at`] for details).
172+
/// Items of `txs` are tuples containing the transaction and a *last seen* timestamp. The
173+
/// *last seen* communicates when the transaction is last seen in the mempool which is used for
174+
/// conflict-resolution in [`TxGraph`] (refer to [`TxGraph::insert_seen_at`] for details).
185175
pub fn batch_insert_relevant_unconfirmed<'t>(
186176
&mut self,
187-
unconfirmed_txs: impl IntoIterator<Item = (&'t Transaction, Option<u64>)>,
177+
unconfirmed_txs: impl IntoIterator<Item = (&'t Transaction, u64)>,
188178
) -> ChangeSet<A, I::ChangeSet> {
189-
self.batch_insert_relevant(
190-
unconfirmed_txs
191-
.into_iter()
192-
.map(|(tx, last_seen)| (tx, core::iter::empty(), last_seen)),
193-
)
179+
// The algorithm below allows for non-topologically ordered transactions by using two loops.
180+
// This is achieved by:
181+
// 1. insert all txs into the index. If they are irrelevant then that's fine it will just
182+
// not store anything about them.
183+
// 2. decide whether to insert them into the graph depending on whether `is_tx_relevant`
184+
// returns true or not. (in a second loop).
185+
let txs = unconfirmed_txs.into_iter().collect::<Vec<_>>();
186+
187+
let mut indexer = I::ChangeSet::default();
188+
for (tx, _) in &txs {
189+
indexer.append(self.index.index_tx(tx));
190+
}
191+
192+
let graph = self.graph.batch_insert_unconfirmed(
193+
txs.into_iter()
194+
.filter(|(tx, _)| self.index.is_tx_relevant(tx))
195+
.map(|(tx, seen_at)| (tx.clone(), seen_at)),
196+
);
197+
198+
ChangeSet { graph, indexer }
194199
}
195200

196201
/// Batch insert unconfirmed transactions.
197202
///
198-
/// Items of `txs` are tuples containing the transaction and an optional *last seen* timestamp.
199-
/// The *last seen* communicates when the transaction is last seen in the mempool which is used
200-
/// for conflict-resolution in [`TxGraph`] (refer to [`TxGraph::insert_seen_at`] for details).
203+
/// Items of `txs` are tuples containing the transaction and a *last seen* timestamp. The
204+
/// *last seen* communicates when the transaction is last seen in the mempool which is used for
205+
/// conflict-resolution in [`TxGraph`] (refer to [`TxGraph::insert_seen_at`] for details).
201206
///
202207
/// To filter out irrelevant transactions, use [`batch_insert_relevant_unconfirmed`] instead.
203208
///
204209
/// [`batch_insert_relevant_unconfirmed`]: IndexedTxGraph::batch_insert_relevant_unconfirmed
205-
pub fn batch_insert_unconfirmed<'t>(
210+
pub fn batch_insert_unconfirmed(
206211
&mut self,
207-
unconfirmed_txs: impl IntoIterator<Item = (&'t Transaction, Option<u64>)>,
212+
txs: impl IntoIterator<Item = (Transaction, u64)>,
208213
) -> ChangeSet<A, I::ChangeSet> {
209-
self.batch_insert(
210-
unconfirmed_txs
211-
.into_iter()
212-
.map(|(tx, last_seen)| (tx, core::iter::empty(), last_seen)),
213-
)
214+
let graph = self.graph.batch_insert_unconfirmed(txs);
215+
let indexer = self.index_tx_graph_changeset(&graph);
216+
ChangeSet { graph, indexer }
214217
}
215218
}
216219

@@ -241,7 +244,6 @@ where
241244
(
242245
tx,
243246
core::iter::once(A::from_block_position(&block, block_id, tx_pos)),
244-
None,
245247
)
246248
});
247249
self.batch_insert_relevant(txs)
@@ -260,30 +262,17 @@ where
260262
hash: block.block_hash(),
261263
height,
262264
};
263-
let txs = block.txdata.iter().enumerate().map(|(tx_pos, tx)| {
264-
(
265-
tx,
266-
core::iter::once(A::from_block_position(&block, block_id, tx_pos)),
267-
None,
268-
)
269-
});
270-
self.batch_insert(txs)
265+
let mut graph = tx_graph::ChangeSet::default();
266+
for (tx_pos, tx) in block.txdata.iter().enumerate() {
267+
let anchor = A::from_block_position(&block, block_id, tx_pos);
268+
graph.append(self.graph.insert_anchor(tx.txid(), anchor));
269+
graph.append(self.graph.insert_tx(tx.clone()));
270+
}
271+
let indexer = self.index_tx_graph_changeset(&graph);
272+
ChangeSet { graph, indexer }
271273
}
272274
}
273275

274-
/// A tuple of a transaction, and associated metadata, that are to be inserted into [`IndexedTxGraph`].
275-
///
276-
/// This tuple contains fields in the following order:
277-
/// * A reference to the transaction.
278-
/// * A collection of [`Anchor`]s.
279-
/// * An optional last-seen timestamp.
280-
///
281-
/// This is used as a input item of [`batch_insert_relevant`] and [`batch_insert`].
282-
///
283-
/// [`batch_insert_relevant`]: IndexedTxGraph::batch_insert_relevant
284-
/// [`batch_insert`]: IndexedTxGraph::batch_insert
285-
pub type InsertTxItem<'t, A> = (&'t Transaction, A, Option<u64>);
286-
287276
/// A structure that represents changes to an [`IndexedTxGraph`].
288277
#[derive(Clone, Debug, PartialEq)]
289278
#[cfg_attr(

crates/chain/src/tx_graph.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,23 @@ impl<A: Clone + Ord> TxGraph<A> {
451451
self.apply_update(update)
452452
}
453453

454+
/// Batch insert unconfirmed transactions.
455+
///
456+
/// Items of `txs` are tuples containing the transaction and a *last seen* timestamp. The
457+
/// *last seen* communicates when the transaction is last seen in the mempool which is used for
458+
/// conflict-resolution (refer to [`TxGraph::insert_seen_at`] for details).
459+
pub fn batch_insert_unconfirmed(
460+
&mut self,
461+
txs: impl IntoIterator<Item = (Transaction, u64)>,
462+
) -> ChangeSet<A> {
463+
let mut changeset = ChangeSet::<A>::default();
464+
for (tx, seen_at) in txs {
465+
changeset.append(self.insert_seen_at(tx.txid(), seen_at));
466+
changeset.append(self.insert_tx(tx));
467+
}
468+
changeset
469+
}
470+
454471
/// Inserts the given `anchor` into [`TxGraph`].
455472
///
456473
/// The [`ChangeSet`] returned will be empty if graph already knows that `txid` exists in

crates/chain/tests/test_indexed_tx_graph.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ fn insert_relevant_txs() {
7474
};
7575

7676
assert_eq!(
77-
graph.batch_insert_relevant(txs.iter().map(|tx| (tx, None, None))),
77+
graph.batch_insert_relevant(txs.iter().map(|tx| (tx, None))),
7878
changeset,
7979
);
8080

@@ -225,11 +225,10 @@ fn test_list_owned_txouts() {
225225
anchor_block,
226226
confirmation_height: anchor_block.height,
227227
}),
228-
None,
229228
)
230229
}));
231230

232-
let _ = graph.batch_insert_relevant([&tx4, &tx5].iter().map(|tx| (*tx, None, Some(100))));
231+
let _ = graph.batch_insert_relevant_unconfirmed([&tx4, &tx5].iter().map(|tx| (*tx, 100)));
233232

234233
// A helper lambda to extract and filter data from the graph.
235234
let fetch =

example-crates/example_bitcoind_rpc_polling/src/main.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,7 @@ fn main() -> anyhow::Result<()> {
212212

213213
// mempool
214214
let mempool_txs = emitter.mempool()?;
215-
let graph_changeset = graph
216-
.batch_insert_unconfirmed(mempool_txs.iter().map(|(tx, time)| (tx, Some(*time))));
215+
let graph_changeset = graph.batch_insert_unconfirmed(mempool_txs);
217216
db.stage((local_chain::ChangeSet::default(), graph_changeset));
218217

219218
// commit one last time!
@@ -291,7 +290,7 @@ fn main() -> anyhow::Result<()> {
291290
}
292291
Emission::Mempool(mempool_txs) => {
293292
let graph_changeset = graph.batch_insert_relevant_unconfirmed(
294-
mempool_txs.iter().map(|(tx, time)| (tx, Some(*time))),
293+
mempool_txs.iter().map(|(tx, time)| (tx, *time)),
295294
);
296295
(local_chain::ChangeSet::default(), graph_changeset)
297296
}

0 commit comments

Comments
 (0)