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 <wzc110@gmail.com>
This commit is contained in:
Daniela Brozzoni 2023-09-29 16:51:50 +02:00
parent a3e8480ad9
commit 62de55f12d
No known key found for this signature in database
GPG Key ID: 7DE4F1FDCED0AB87

View File

@ -54,8 +54,8 @@ use crate::{
collections::*, keychain::Balance, local_chain::LocalChain, Anchor, Append, BlockId, collections::*, keychain::Balance, local_chain::LocalChain, Anchor, Append, BlockId,
ChainOracle, ChainPosition, FullTxOut, ChainOracle, ChainPosition, FullTxOut,
}; };
use alloc::vec::Vec;
use alloc::collections::vec_deque::VecDeque; use alloc::collections::vec_deque::VecDeque;
use alloc::vec::Vec;
use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid}; use bitcoin::{OutPoint, Script, Transaction, TxOut, Txid};
use core::{ use core::{
convert::Infallible, convert::Infallible,
@ -697,8 +697,9 @@ impl<A: Anchor> TxGraph<A> {
} }
} }
// The tx is not anchored to a block which is in the best chain, let's check whether we can // The tx is not anchored to a block which is in the best chain, which means that it
// ignore it by checking conflicts! // might be in mempool, or it might have been dropped already.
// Let's check conflicts to find out!
let tx = match tx_node { let tx = match tx_node {
TxNodeInternal::Whole(tx) => tx, TxNodeInternal::Whole(tx) => tx,
TxNodeInternal::Partial(_) => { TxNodeInternal::Partial(_) => {
@ -707,18 +708,71 @@ impl<A: Anchor> TxGraph<A> {
} }
}; };
// If a conflicting tx is in the best chain, or has `last_seen` higher than this tx, then // We want to retrieve all the transactions that conflict with us, plus all the
// this tx cannot exist in the best chain // transactions that conflict with our unconfirmed ancestors, since they conflict with us
for conflicting_tx in self.walk_conflicts(tx, |_, txid| self.get_tx_node(txid)) { // as well.
for block in conflicting_tx.anchors.iter().map(A::anchor_block) { // We only traverse unconfirmed ancestors since conflicts of confirmed transactions
if chain.is_block_in_chain(block, chain_tip)? == Some(true) { // cannot be in the best chain.
// conflicting tx is in best chain, so the current tx cannot be in best chain!
// First of all, we retrieve all our ancestors. Since we're using `new_include_root`, the
// resulting array will also include `tx`
let unconfirmed_ancestor_txs =
TxAncestors::new_include_root(self, tx, |_, ancestor_tx: &Transaction| {
let tx_node = self.get_tx_node(ancestor_tx.txid())?;
// We're filtering the ancestors to keep only the unconfirmed ones (= no anchors in
// the best chain)
for block in tx_node.anchors {
match chain.is_block_in_chain(block.anchor_block(), chain_tip) {
Ok(Some(true)) => return None,
Err(e) => return Some(Err(e)),
_ => continue,
}
}
Some(Ok(tx_node))
})
.collect::<Result<Vec<_>, C::Error>>()?;
// We determine our tx's last seen, which is the max between our last seen,
// and our unconf descendants' last seen.
let unconfirmed_descendants_txs =
TxDescendants::new_include_root(self, tx.txid(), |_, descendant_txid: Txid| {
let tx_node = self.get_tx_node(descendant_txid)?;
// We're filtering the ancestors to keep only the unconfirmed ones (= no anchors in
// the best chain)
for block in tx_node.anchors {
match chain.is_block_in_chain(block.anchor_block(), chain_tip) {
Ok(Some(true)) => return None,
Err(e) => return Some(Err(e)),
_ => continue,
}
}
Some(Ok(tx_node))
})
.collect::<Result<Vec<_>, C::Error>>()?;
let tx_last_seen = unconfirmed_descendants_txs
.iter()
.max_by_key(|tx| tx.last_seen_unconfirmed)
.map(|tx| tx.last_seen_unconfirmed)
.expect("descendants always includes at least one transaction (the root tx");
// Now we traverse our ancestors and consider all their conflicts
for tx_node in unconfirmed_ancestor_txs {
// We retrieve all the transactions conflicting with this specific ancestor
let conflicting_txs = self.walk_conflicts(tx_node.tx, |_, txid| self.get_tx_node(txid));
// If a conflicting tx is in the best chain, or has `last_seen` higher than this ancestor, then
// this tx cannot exist in the best chain
for conflicting_tx in conflicting_txs {
for block in conflicting_tx.anchors {
if chain.is_block_in_chain(block.anchor_block(), chain_tip)? == Some(true) {
return Ok(None);
}
}
if conflicting_tx.last_seen_unconfirmed > tx_last_seen {
return Ok(None); return Ok(None);
} }
} }
if conflicting_tx.last_seen_unconfirmed > *last_seen {
return Ok(None);
}
} }
Ok(Some(ChainPosition::Unconfirmed(*last_seen))) Ok(Some(ChainPosition::Unconfirmed(*last_seen)))