Skip to content

Commit 991cb77

Browse files
committed
fix(chain): filter coinbase tx not in best chain
Coinbase transactions cannot exist in the mempool and be unconfirmed. `TxGraph::try_get_chain_position` should always return `None` for coinbase transactions not anchored in best chain.
1 parent 0a7b60f commit 991cb77

File tree

2 files changed

+51
-1
lines changed

2 files changed

+51
-1
lines changed

crates/chain/src/tx_graph.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,14 @@ impl<A: Anchor> TxGraph<A> {
718718
// might be in mempool, or it might have been dropped already.
719719
// Let's check conflicts to find out!
720720
let tx = match tx_node {
721-
TxNodeInternal::Whole(tx) => tx,
721+
TxNodeInternal::Whole(tx) => {
722+
// A coinbase tx that is not anchored in the best chain cannot be unconfirmed and
723+
// should always be filtered out.
724+
if tx.is_coin_base() {
725+
return Ok(None);
726+
}
727+
tx
728+
}
722729
TxNodeInternal::Partial(_) => {
723730
// Partial transactions (outputs only) cannot have conflicts.
724731
return Ok(None);

crates/chain/tests/test_tx_graph_conflicts.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,49 @@ fn test_tx_conflict_handling() {
4545
.unwrap_or_default();
4646

4747
let scenarios = [
48+
Scenario {
49+
name: "coinbase tx cannot be in mempool and be unconfirmed",
50+
tx_templates: &[
51+
TxTemplate {
52+
tx_name: "unconfirmed_coinbase",
53+
inputs: &[TxInTemplate::Coinbase],
54+
outputs: &[TxOutTemplate::new(5000, Some(0))],
55+
..Default::default()
56+
},
57+
TxTemplate {
58+
tx_name: "confirmed_genesis",
59+
inputs: &[TxInTemplate::Bogus],
60+
outputs: &[TxOutTemplate::new(10000, Some(1))],
61+
anchors: &[block_id!(1, "B")],
62+
last_seen: None,
63+
},
64+
TxTemplate {
65+
tx_name: "unconfirmed_conflict",
66+
inputs: &[
67+
TxInTemplate::PrevTx("confirmed_genesis", 0),
68+
TxInTemplate::PrevTx("unconfirmed_coinbase", 0)
69+
],
70+
outputs: &[TxOutTemplate::new(20000, Some(2))],
71+
..Default::default()
72+
},
73+
TxTemplate {
74+
tx_name: "confirmed_conflict",
75+
inputs: &[TxInTemplate::PrevTx("confirmed_genesis", 0)],
76+
outputs: &[TxOutTemplate::new(20000, Some(3))],
77+
anchors: &[block_id!(4, "E")],
78+
..Default::default()
79+
},
80+
],
81+
exp_chain_txs: HashSet::from(["confirmed_genesis", "confirmed_conflict"]),
82+
exp_chain_txouts: HashSet::from([("confirmed_genesis", 0), ("confirmed_conflict", 0)]),
83+
exp_unspents: HashSet::from([("confirmed_conflict", 0)]),
84+
exp_balance: Balance {
85+
immature: 0,
86+
trusted_pending: 0,
87+
untrusted_pending: 0,
88+
confirmed: 20000,
89+
},
90+
},
4891
Scenario {
4992
name: "2 unconfirmed txs with same last_seens conflict",
5093
tx_templates: &[

0 commit comments

Comments
 (0)