Skip to content

Commit 7003ad4

Browse files
committed
feat(chain): Txs that conflict with relevant txs are also relevant
Change behavior of {insert|apply}-if-relevant methods of `IndexedTxGraph` to also consider txs that conflict with relevant txs as relevant. Rationale: It is useful to determine why something is evicted from the mempool. For example, an incoming transaction may be evicted from the mempool due to insufficient fees or a conflicting transaction is confirmed. * Insufficient fees - the user may want to CPFP the tx. * Conflicting tx is confirmed - the sender probably purposefully cancelled the tx. The user may want to forget about this tx once it reaches x confirmations. The `IntentTracker` will make use of these relevant-conflicts. A note about chain sources: For some chain sources, obtaining relevant-conflicts is extremely costly or downright impossible (i.e. Electrum, BIP-158 filters). `bdk_bitcoind_rpc::Emitter` is still the most robust chain source to use.
1 parent dcf8860 commit 7003ad4

File tree

1 file changed

+24
-8
lines changed

1 file changed

+24
-8
lines changed

crates/chain/src/indexed_tx_graph.rs

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ impl<A: Anchor, I: Indexer> IndexedTxGraph<A, I> {
6767
indexer,
6868
}
6969
}
70+
71+
// If `tx` replaces a relevant tx, it should also be considered relevant.
72+
fn is_tx_or_conflict_relevant(&self, tx: &Transaction) -> bool {
73+
self.index.is_tx_relevant(tx)
74+
|| self
75+
.graph
76+
.direct_conflicts(tx)
77+
.filter_map(|(_, txid)| self.graph.get_tx(txid))
78+
.any(|tx| self.index.is_tx_relevant(&tx))
79+
}
7080
}
7181

7282
impl<A: Anchor, I: Indexer> IndexedTxGraph<A, I>
@@ -239,8 +249,11 @@ where
239249

240250
/// Batch insert transactions, filtering out those that are irrelevant.
241251
///
242-
/// Relevancy is determined by the [`Indexer::is_tx_relevant`] implementation of `I`. Irrelevant
243-
/// transactions in `txs` will be ignored. `txs` do not need to be in topological order.
252+
/// `txs` do not need to be in topological order.
253+
///
254+
/// Relevancy is determined by the internal [`Indexer::is_tx_relevant`] implementation of `I`.
255+
/// A transaction that conflicts with a relevant transaction is also considered relevant.
256+
/// Irrelevant transactions in `txs` will be ignored.
244257
pub fn batch_insert_relevant<T: Into<Arc<Transaction>>>(
245258
&mut self,
246259
txs: impl IntoIterator<Item = (T, impl IntoIterator<Item = A>)>,
@@ -263,7 +276,7 @@ where
263276

264277
let mut tx_graph = tx_graph::ChangeSet::default();
265278
for (tx, anchors) in txs {
266-
if self.index.is_tx_relevant(&tx) {
279+
if self.is_tx_or_conflict_relevant(&tx) {
267280
let txid = tx.compute_txid();
268281
tx_graph.merge(self.graph.insert_tx(tx.clone()));
269282
for anchor in anchors {
@@ -278,7 +291,8 @@ where
278291
/// Batch insert unconfirmed transactions, filtering out those that are irrelevant.
279292
///
280293
/// Relevancy is determined by the internal [`Indexer::is_tx_relevant`] implementation of `I`.
281-
/// Irrelevant transactions in `txs` will be ignored.
294+
/// A transaction that conflicts with a relevant transaction is also considered relevant.
295+
/// Irrelevant transactions in `unconfirmed_txs` will be ignored.
282296
///
283297
/// Items of `txs` are tuples containing the transaction and a *last seen* timestamp. The
284298
/// *last seen* communicates when the transaction is last seen in the mempool which is used for
@@ -305,8 +319,9 @@ where
305319

306320
let graph = self.graph.batch_insert_unconfirmed(
307321
txs.into_iter()
308-
.filter(|(tx, _)| self.index.is_tx_relevant(tx))
309-
.map(|(tx, seen_at)| (tx.clone(), seen_at)),
322+
.filter(|(tx, _)| self.is_tx_or_conflict_relevant(tx))
323+
.map(|(tx, seen_at)| (tx.clone(), seen_at))
324+
.collect::<Vec<_>>(),
310325
);
311326

312327
ChangeSet {
@@ -350,7 +365,8 @@ where
350365
/// Each inserted transaction's anchor will be constructed using [`TxPosInBlock`].
351366
///
352367
/// Relevancy is determined by the internal [`Indexer::is_tx_relevant`] implementation of `I`.
353-
/// Irrelevant transactions in `txs` will be ignored.
368+
/// A transaction that conflicts with a relevant transaction is also considered relevant.
369+
/// Irrelevant transactions in `block` will be ignored.
354370
pub fn apply_block_relevant(
355371
&mut self,
356372
block: &Block,
@@ -363,7 +379,7 @@ where
363379
let mut changeset = ChangeSet::<A, I::ChangeSet>::default();
364380
for (tx_pos, tx) in block.txdata.iter().enumerate() {
365381
changeset.indexer.merge(self.index.index_tx(tx));
366-
if self.index.is_tx_relevant(tx) {
382+
if self.is_tx_or_conflict_relevant(tx) {
367383
let txid = tx.compute_txid();
368384
let anchor = TxPosInBlock {
369385
block,

0 commit comments

Comments
 (0)