Skip to content

Commit 62de55f

Browse files
fix(chain): Consider conflicting ancestors in...
...try_get_chain_pos In try_get_chain_pos, when we notice that a transaction is not included in the best chain, we check the transactions in mempool to find conflicting ones, and decide based on that if our transaction is still in mempool or has been dropped. This commit adds a check for transactions conflicting with the unconfirmed ancestors of our tx. Co-authored-by: Wei Chen <[email protected]>
1 parent a3e8480 commit 62de55f

File tree

1 file changed

+66
-12
lines changed

1 file changed

+66
-12
lines changed

crates/chain/src/tx_graph.rs

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ use crate::{
5454
collections::*, keychain::Balance, local_chain::LocalChain, Anchor, Append, BlockId,
5555
ChainOracle, ChainPosition, FullTxOut,
5656
};
57-
use alloc::vec::Vec;
5857
use alloc::collections::vec_deque::VecDeque;
58+
use alloc::vec::Vec;
5959
use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid};
6060
use core::{
6161
convert::Infallible,
@@ -697,8 +697,9 @@ impl<A: Anchor> TxGraph<A> {
697697
}
698698
}
699699

700-
// The tx is not anchored to a block which is in the best chain, let's check whether we can
701-
// ignore it by checking conflicts!
700+
// The tx is not anchored to a block which is in the best chain, which means that it
701+
// might be in mempool, or it might have been dropped already.
702+
// Let's check conflicts to find out!
702703
let tx = match tx_node {
703704
TxNodeInternal::Whole(tx) => tx,
704705
TxNodeInternal::Partial(_) => {
@@ -707,18 +708,71 @@ impl<A: Anchor> TxGraph<A> {
707708
}
708709
};
709710

710-
// If a conflicting tx is in the best chain, or has `last_seen` higher than this tx, then
711-
// this tx cannot exist in the best chain
712-
for conflicting_tx in self.walk_conflicts(tx, |_, txid| self.get_tx_node(txid)) {
713-
for block in conflicting_tx.anchors.iter().map(A::anchor_block) {
714-
if chain.is_block_in_chain(block, chain_tip)? == Some(true) {
715-
// conflicting tx is in best chain, so the current tx cannot be in best chain!
711+
// We want to retrieve all the transactions that conflict with us, plus all the
712+
// transactions that conflict with our unconfirmed ancestors, since they conflict with us
713+
// as well.
714+
// We only traverse unconfirmed ancestors since conflicts of confirmed transactions
715+
// cannot be in the best chain.
716+
717+
// First of all, we retrieve all our ancestors. Since we're using `new_include_root`, the
718+
// resulting array will also include `tx`
719+
let unconfirmed_ancestor_txs =
720+
TxAncestors::new_include_root(self, tx, |_, ancestor_tx: &Transaction| {
721+
let tx_node = self.get_tx_node(ancestor_tx.txid())?;
722+
// We're filtering the ancestors to keep only the unconfirmed ones (= no anchors in
723+
// the best chain)
724+
for block in tx_node.anchors {
725+
match chain.is_block_in_chain(block.anchor_block(), chain_tip) {
726+
Ok(Some(true)) => return None,
727+
Err(e) => return Some(Err(e)),
728+
_ => continue,
729+
}
730+
}
731+
Some(Ok(tx_node))
732+
})
733+
.collect::<Result<Vec<_>, C::Error>>()?;
734+
735+
// We determine our tx's last seen, which is the max between our last seen,
736+
// and our unconf descendants' last seen.
737+
let unconfirmed_descendants_txs =
738+
TxDescendants::new_include_root(self, tx.txid(), |_, descendant_txid: Txid| {
739+
let tx_node = self.get_tx_node(descendant_txid)?;
740+
// We're filtering the ancestors to keep only the unconfirmed ones (= no anchors in
741+
// the best chain)
742+
for block in tx_node.anchors {
743+
match chain.is_block_in_chain(block.anchor_block(), chain_tip) {
744+
Ok(Some(true)) => return None,
745+
Err(e) => return Some(Err(e)),
746+
_ => continue,
747+
}
748+
}
749+
Some(Ok(tx_node))
750+
})
751+
.collect::<Result<Vec<_>, C::Error>>()?;
752+
753+
let tx_last_seen = unconfirmed_descendants_txs
754+
.iter()
755+
.max_by_key(|tx| tx.last_seen_unconfirmed)
756+
.map(|tx| tx.last_seen_unconfirmed)
757+
.expect("descendants always includes at least one transaction (the root tx");
758+
759+
// Now we traverse our ancestors and consider all their conflicts
760+
for tx_node in unconfirmed_ancestor_txs {
761+
// We retrieve all the transactions conflicting with this specific ancestor
762+
let conflicting_txs = self.walk_conflicts(tx_node.tx, |_, txid| self.get_tx_node(txid));
763+
764+
// If a conflicting tx is in the best chain, or has `last_seen` higher than this ancestor, then
765+
// this tx cannot exist in the best chain
766+
for conflicting_tx in conflicting_txs {
767+
for block in conflicting_tx.anchors {
768+
if chain.is_block_in_chain(block.anchor_block(), chain_tip)? == Some(true) {
769+
return Ok(None);
770+
}
771+
}
772+
if conflicting_tx.last_seen_unconfirmed > tx_last_seen {
716773
return Ok(None);
717774
}
718775
}
719-
if conflicting_tx.last_seen_unconfirmed > *last_seen {
720-
return Ok(None);
721-
}
722776
}
723777

724778
Ok(Some(ChainPosition::Unconfirmed(*last_seen)))

0 commit comments

Comments
 (0)