diff --git a/crates/chain/src/canonical_iter.rs b/crates/chain/src/canonical_iter.rs index 58f266f89..204ead451 100644 --- a/crates/chain/src/canonical_iter.rs +++ b/crates/chain/src/canonical_iter.rs @@ -230,6 +230,10 @@ impl Iterator for CanonicalIter<'_, A, C> { } if let Some((txid, tx, last_seen)) = self.unprocessed_seen_txs.next() { + debug_assert!( + !tx.is_coinbase(), + "Coinbase txs must not have `last_seen` (in mempool) value" + ); if !self.is_canonicalized(txid) { let observed_in = ObservedIn::Mempool(last_seen); self.mark_canonical(txid, tx, CanonicalReason::from_observed_in(observed_in)); @@ -238,7 +242,7 @@ impl Iterator for CanonicalIter<'_, A, C> { } if let Some((txid, tx, height)) = self.unprocessed_leftover_txs.pop_front() { - if !self.is_canonicalized(txid) { + if !self.is_canonicalized(txid) && !tx.is_coinbase() { let observed_in = ObservedIn::Block(height); self.mark_canonical(txid, tx, CanonicalReason::from_observed_in(observed_in)); } diff --git a/crates/chain/tests/test_tx_graph_conflicts.rs b/crates/chain/tests/test_tx_graph_conflicts.rs index 862eb9b4b..71944e404 100644 --- a/crates/chain/tests/test_tx_graph_conflicts.rs +++ b/crates/chain/tests/test_tx_graph_conflicts.rs @@ -943,6 +943,28 @@ fn test_tx_conflict_handling() { confirmed: Amount::ZERO, }, }, + Scenario { + name: "coinbase tx must not become unconfirmed", + tx_templates: &[ + TxTemplate { + tx_name: "coinbase", + inputs: &[TxInTemplate::Coinbase], + outputs: &[TxOutTemplate::new(21_000, Some(0))], + // Stale block + anchors: &[block_id!(1, "B-prime")], + ..Default::default() + } + ], + exp_chain_txs: HashSet::from([]), + exp_chain_txouts: HashSet::from([]), + exp_unspents: HashSet::from([]), + exp_balance: Balance { + immature: Amount::ZERO, + trusted_pending: Amount::ZERO, + untrusted_pending: Amount::ZERO, + confirmed: Amount::ZERO, + } + } ]; for scenario in scenarios {