From 8daaad9a8991fa9df363bfe11df3e43a0b15e6ab Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Wed, 8 Apr 2026 11:43:08 +0200 Subject: [PATCH] fix(chain): add missing `continue` in `CanonicalIter` assumed-tx processing Without the `continue`, the loop falls through to the subsequent processing blocks when assumed-canonical transactions are the only remaining unprocessed source. If the anchored, seen, and leftover iterators are all exhausted, the loop reaches `return None` and silently drops the assumed transactions that were just marked canonical. Co-Authored-By: HAL 9000 --- crates/chain/src/canonical_iter.rs | 1 + crates/chain/tests/test_tx_graph_conflicts.rs | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/crates/chain/src/canonical_iter.rs b/crates/chain/src/canonical_iter.rs index 7ff04249b..41f17e527 100644 --- a/crates/chain/src/canonical_iter.rs +++ b/crates/chain/src/canonical_iter.rs @@ -218,6 +218,7 @@ impl Iterator for CanonicalIter<'_, A, C> { if !self.is_canonicalized(txid) { self.mark_canonical(txid, tx, CanonicalReason::assumed()); } + continue; } if let Some((txid, tx, anchors)) = self.unprocessed_anchored_txs.next() { diff --git a/crates/chain/tests/test_tx_graph_conflicts.rs b/crates/chain/tests/test_tx_graph_conflicts.rs index 113a985b4..abfd950c8 100644 --- a/crates/chain/tests/test_tx_graph_conflicts.rs +++ b/crates/chain/tests/test_tx_graph_conflicts.rs @@ -943,6 +943,34 @@ fn test_tx_conflict_handling() { confirmed: Amount::ZERO, }, }, + Scenario { + name: "only assumed-canonical txs are yielded without anchored or seen txs", + tx_templates: &[ + TxTemplate { + tx_name: "assumed_a", + inputs: &[TxInTemplate::Bogus], + outputs: &[TxOutTemplate::new(21_000, Some(0))], + assume_canonical: true, + ..Default::default() + }, + TxTemplate { + tx_name: "assumed_b", + inputs: &[TxInTemplate::Bogus], + outputs: &[TxOutTemplate::new(18_000, Some(1))], + assume_canonical: true, + ..Default::default() + }, + ], + exp_chain_txs: HashSet::from(["assumed_a", "assumed_b"]), + exp_chain_txouts: HashSet::from([("assumed_a", 0), ("assumed_b", 0)]), + exp_unspents: HashSet::from([("assumed_a", 0), ("assumed_b", 0)]), + exp_balance: Balance { + immature: Amount::ZERO, + trusted_pending: Amount::from_sat(21_000 + 18_000), + untrusted_pending: Amount::ZERO, + confirmed: Amount::ZERO, + }, + }, Scenario { name: "coinbase tx must not become unconfirmed", tx_templates: &[