]> Untitled Git - bdk/commitdiff
fix(chain): filter coinbase tx not in best chain
authorWei Chen <wzc110@gmail.com>
Thu, 9 Nov 2023 21:34:08 +0000 (05:34 +0800)
committerWei Chen <wzc110@gmail.com>
Fri, 10 Nov 2023 18:55:58 +0000 (02:55 +0800)
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
crates/chain/tests/test_tx_graph_conflicts.rs

index 6fb1ee4fa7a5d1725dd43ecc0aab92eaf83000ae..802c74dc763fc585b0d6b524b952f988723a2ec0 100644 (file)
@@ -718,7 +718,14 @@ impl<A: Anchor> TxGraph<A> {
         // 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);
index 53a19b0222f90560e72b2c8450339e73bd6fd0f7..9660e3cb1f0b170d54c6d5113558b2c892ee67ba 100644 (file)
@@ -45,6 +45,49 @@ 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: "unconfirmed_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("unconfirmed_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: &[