From 0504151be9e23c99416987ca54fd7a830bc5cec1 Mon Sep 17 00:00:00 2001 From: Wei Chen Date: Fri, 10 Nov 2023 05:34:08 +0800 Subject: [PATCH] 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. --- crates/chain/src/tx_graph.rs | 9 ++++- crates/chain/tests/test_tx_graph_conflicts.rs | 40 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/crates/chain/src/tx_graph.rs b/crates/chain/src/tx_graph.rs index 6fb1ee4fa7..802c74dc76 100644 --- a/crates/chain/src/tx_graph.rs +++ b/crates/chain/src/tx_graph.rs @@ -718,7 +718,14 @@ impl TxGraph { // might be in mempool, or it might have been dropped already. // Let's check conflicts to find out! let tx = match tx_node { - TxNodeInternal::Whole(tx) => tx, + TxNodeInternal::Whole(tx) => { + // A coinbase tx that is not anchored in the best chain cannot be unconfirmed and + // should always be filtered out. + if tx.is_coin_base() { + return Ok(None); + } + tx + } TxNodeInternal::Partial(_) => { // Partial transactions (outputs only) cannot have conflicts. return Ok(None); diff --git a/crates/chain/tests/test_tx_graph_conflicts.rs b/crates/chain/tests/test_tx_graph_conflicts.rs index 53a19b0222..3dc6cf3d93 100644 --- a/crates/chain/tests/test_tx_graph_conflicts.rs +++ b/crates/chain/tests/test_tx_graph_conflicts.rs @@ -45,6 +45,46 @@ fn test_tx_conflict_handling() { .unwrap_or_default(); let scenarios = [ + Scenario { + name: "coinbase tx cannot be in mempool and be unconfirmed", + tx_templates: &[ + TxTemplate { + tx_name: "coinbase", + inputs: &[TxInTemplate::Coinbase], + outputs: &[TxOutTemplate::new(5000, Some(0))], + ..Default::default() + }, + TxTemplate { + tx_name: "confirmed_genesis", + inputs: &[TxInTemplate::Bogus], + outputs: &[TxOutTemplate::new(10000, Some(1))], + anchors: &[block_id!(1, "B")], + last_seen: None, + }, + TxTemplate { + tx_name: "unconfirmed_conflict", + inputs: &[TxInTemplate::PrevTx("confirmed_genesis", 0), TxInTemplate::PrevTx("coinbase", 0)], + outputs: &[TxOutTemplate::new(20000, Some(2))], + ..Default::default() + }, + TxTemplate { + tx_name: "confirmed_conflict", + inputs: &[TxInTemplate::PrevTx("confirmed_genesis", 0)], + outputs: &[TxOutTemplate::new(20000, Some(3))], + anchors: &[block_id!(4, "E")], + ..Default::default() + }, + ], + exp_chain_txs: HashSet::from(["confirmed_genesis", "confirmed_conflict"]), + exp_chain_txouts: HashSet::from([("confirmed_genesis", 0), ("confirmed_conflict", 0)]), + exp_unspents: HashSet::from([("confirmed_conflict", 0)]), + exp_balance: Balance { + immature: 0, + trusted_pending: 0, + untrusted_pending: 0, + confirmed: 20000, + }, + }, Scenario { name: "2 unconfirmed txs with same last_seens conflict", tx_templates: &[