From: 志宇 Date: Sat, 2 Aug 2025 06:16:26 +0000 (+0000) Subject: feat(chain): Txs that conflict with relevant txs are also relevant X-Git-Tag: core-0.6.2~9^2~2 X-Git-Url: http://internal-gitweb-vhost/?a=commitdiff_plain;h=7003ad44be288cda0ce447e3fa99f893ce48332e;p=bdk 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. --- diff --git a/crates/chain/src/indexed_tx_graph.rs b/crates/chain/src/indexed_tx_graph.rs index 431ff29e..f0c1d121 100644 --- a/crates/chain/src/indexed_tx_graph.rs +++ b/crates/chain/src/indexed_tx_graph.rs @@ -67,6 +67,16 @@ impl IndexedTxGraph { indexer, } } + + // If `tx` replaces a relevant tx, it should also be considered relevant. + fn is_tx_or_conflict_relevant(&self, tx: &Transaction) -> bool { + self.index.is_tx_relevant(tx) + || self + .graph + .direct_conflicts(tx) + .filter_map(|(_, txid)| self.graph.get_tx(txid)) + .any(|tx| self.index.is_tx_relevant(&tx)) + } } impl IndexedTxGraph @@ -239,8 +249,11 @@ where /// Batch insert transactions, filtering out those that are irrelevant. /// - /// Relevancy is determined by the [`Indexer::is_tx_relevant`] implementation of `I`. Irrelevant - /// transactions in `txs` will be ignored. `txs` do not need to be in topological order. + /// `txs` do not need to be in topological order. + /// + /// Relevancy is determined by the internal [`Indexer::is_tx_relevant`] implementation of `I`. + /// A transaction that conflicts with a relevant transaction is also considered relevant. + /// Irrelevant transactions in `txs` will be ignored. pub fn batch_insert_relevant>>( &mut self, txs: impl IntoIterator)>, @@ -263,7 +276,7 @@ where let mut tx_graph = tx_graph::ChangeSet::default(); for (tx, anchors) in txs { - if self.index.is_tx_relevant(&tx) { + if self.is_tx_or_conflict_relevant(&tx) { let txid = tx.compute_txid(); tx_graph.merge(self.graph.insert_tx(tx.clone())); for anchor in anchors { @@ -278,7 +291,8 @@ where /// Batch insert unconfirmed transactions, filtering out those that are irrelevant. /// /// Relevancy is determined by the internal [`Indexer::is_tx_relevant`] implementation of `I`. - /// Irrelevant transactions in `txs` will be ignored. + /// A transaction that conflicts with a relevant transaction is also considered relevant. + /// Irrelevant transactions in `unconfirmed_txs` will be ignored. /// /// Items of `txs` are tuples containing the transaction and a *last seen* timestamp. The /// *last seen* communicates when the transaction is last seen in the mempool which is used for @@ -305,8 +319,9 @@ where let graph = self.graph.batch_insert_unconfirmed( txs.into_iter() - .filter(|(tx, _)| self.index.is_tx_relevant(tx)) - .map(|(tx, seen_at)| (tx.clone(), seen_at)), + .filter(|(tx, _)| self.is_tx_or_conflict_relevant(tx)) + .map(|(tx, seen_at)| (tx.clone(), seen_at)) + .collect::>(), ); ChangeSet { @@ -350,7 +365,8 @@ where /// Each inserted transaction's anchor will be constructed using [`TxPosInBlock`]. /// /// Relevancy is determined by the internal [`Indexer::is_tx_relevant`] implementation of `I`. - /// Irrelevant transactions in `txs` will be ignored. + /// A transaction that conflicts with a relevant transaction is also considered relevant. + /// Irrelevant transactions in `block` will be ignored. pub fn apply_block_relevant( &mut self, block: &Block, @@ -363,7 +379,7 @@ where let mut changeset = ChangeSet::::default(); for (tx_pos, tx) in block.txdata.iter().enumerate() { changeset.indexer.merge(self.index.index_tx(tx)); - if self.index.is_tx_relevant(tx) { + if self.is_tx_or_conflict_relevant(tx) { let txid = tx.compute_txid(); let anchor = TxPosInBlock { block,