Skip to content

Commit

Permalink
chain(fix): conflict resolution for txs with same last_seen
Browse files Browse the repository at this point in the history
  • Loading branch information
LagginTimes committed Sep 1, 2023
1 parent 2867e88 commit 32da7e1
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 3 deletions.
20 changes: 20 additions & 0 deletions crates/chain/src/tx_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,26 @@ impl<A: Anchor> TxGraph<A> {
if conflicting_tx.last_seen_unconfirmed > *last_seen {
return Ok(None);
}
if conflicting_tx.last_seen_unconfirmed == *last_seen {
// Check if conflicting tx has higher absolute fee and fee rate
if let Ok(fee) = self.calculate_fee(tx) {
if let Ok(conflicting_fee) = self.calculate_fee(&conflicting_tx) {
let fee_rate = fee as f32 / tx.weight().to_vbytes_ceil() as f32;
let conflicting_fee_rate = conflicting_fee as f32
/ conflicting_tx.weight().to_vbytes_ceil() as f32;

if conflicting_fee > fee && conflicting_fee_rate > fee_rate {
return Ok(None);
}
}
}

// If fee rates cannot be distinguished, then conflicting tx has priority if txid of
// conflicting tx > txid of original tx
if conflicting_tx.txid() > tx.txid() {
return Ok(None);
}
}
}

Ok(Some(ChainPosition::Unconfirmed(*last_seen)))
Expand Down
132 changes: 129 additions & 3 deletions crates/chain/tests/test_indexed_tx_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ use bdk_chain::{
indexed_tx_graph::{self, IndexedTxGraph},
keychain::{self, Balance, KeychainTxOutIndex},
local_chain::LocalChain,
tx_graph, BlockId, ChainPosition, ConfirmationHeightAnchor,
tx_graph, BlockId, ChainPosition, ConfirmationHeightAnchor, SpkIterator,
};
use bitcoin::{
secp256k1::Secp256k1, BlockHash, OutPoint, Script, ScriptBuf, Transaction, TxIn, TxOut,
hashes::Hash, secp256k1::Secp256k1, BlockHash, OutPoint, Script, ScriptBuf, Transaction, TxIn,
TxOut,
};
use miniscript::Descriptor;
use common::*;
use miniscript::{Descriptor, DescriptorPublicKey};

/// Ensure [`IndexedTxGraph::insert_relevant_txs`] can successfully index transactions NOT presented
/// in topological order.
Expand Down Expand Up @@ -471,3 +473,127 @@ fn test_list_owned_txouts() {
);
}
}

#[allow(unused)]
pub fn single_descriptor_setup() -> (
LocalChain,
IndexedTxGraph<ConfirmationHeightAnchor, KeychainTxOutIndex<()>>,
Descriptor<DescriptorPublicKey>,
) {
let local_chain = (0..10)
.map(|i| (i as u32, BlockHash::hash(format!("Block {}", i).as_bytes())))
.collect::<BTreeMap<u32, BlockHash>>();
let local_chain = LocalChain::from(local_chain);

let (desc_1, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/0/*)").unwrap();

let mut graph = IndexedTxGraph::<ConfirmationHeightAnchor, KeychainTxOutIndex<()>>::default();

graph.index.add_keychain((), desc_1.clone());
graph.index.set_lookahead_for_all(100);

(local_chain, graph, desc_1)
}

#[allow(unused)]
pub fn setup_conflicts(
spk_iter: &mut SpkIterator<&Descriptor<DescriptorPublicKey>>,
) -> (Transaction, Transaction, Transaction) {
let tx1 = Transaction {
output: vec![TxOut {
script_pubkey: spk_iter.next().unwrap().1,
value: 40000,
}],
..new_tx(0)
};

let tx_conflict_1 = Transaction {
input: vec![TxIn {
previous_output: OutPoint::new(tx1.txid(), 0),
..Default::default()
}],
output: vec![TxOut {
script_pubkey: spk_iter.next().unwrap().1,
value: 20000,
}],
..new_tx(0)
};

let tx_conflict_2 = Transaction {
input: vec![TxIn {
previous_output: OutPoint::new(tx1.txid(), 0),
..Default::default()
}],
output: vec![TxOut {
script_pubkey: spk_iter.next().unwrap().1,
value: 30000,
}],
..new_tx(0)
};

(tx1, tx_conflict_1, tx_conflict_2)
}

/// Test conflicts for two mempool tx, with same `seen_at` time.
#[test]
fn test_unconfirmed_conflicts_at_same_last_seen() {
let (local_chain, mut graph, desc) = single_descriptor_setup();
let mut spk_iter = SpkIterator::new(&desc);
let (parent_tx, tx_conflict_1, tx_conflict_2) = setup_conflicts(&mut spk_iter);

// Parent confirms at height 2.
let _ = graph.insert_relevant_txs(
[&parent_tx].iter().map(|tx| {
(
*tx,
[ConfirmationHeightAnchor {
anchor_block: (2, *local_chain.blocks().get(&2).unwrap()).into(),
confirmation_height: 2,
}],
)
}),
None,
);

// Both conflicts are in mempool at same `seen_at`
let _ = graph.insert_relevant_txs(
[&tx_conflict_1, &tx_conflict_2]
.iter()
.map(|tx| (*tx, None)),
Some(100),
);

let txouts = graph
.graph()
.filter_chain_txouts(
&local_chain,
local_chain.tip().unwrap().block_id(),
graph.index.outpoints().iter().cloned(),
)
.collect::<Vec<_>>();

let utxos = graph
.graph()
.filter_chain_unspents(
&local_chain,
local_chain.tip().unwrap().block_id(),
graph.index.outpoints().iter().cloned(),
)
.collect::<Vec<_>>();

assert_eq!(txouts.len(), 2);
assert_eq!(
txouts
.iter()
.filter(|(_, txout)| matches!(txout.chain_position, ChainPosition::Unconfirmed(100)))
.count(),
1
);
assert_eq!(
utxos
.iter()
.filter(|(_, txout)| matches!(txout.chain_position, ChainPosition::Unconfirmed(100)))
.count(),
1
);
}

0 comments on commit 32da7e1

Please sign in to comment.