Skip to content

Commit d9c2b95

Browse files
committed
Merge #1976: Disallow unconfirmed coinbase
c84035e test(chain): Add scenario: coinbase tx must not become unconfirmed (志宇) e8da007 fix(chain): Unconfirmed coinbase txs should never be canonical (志宇) Pull request description: ### Description The logic in `CanonicalIter` includes transactions anchored to blocks outside the best chain, since they may still appear in the mempool. However, coinbase transactions can never be unconfirmed—a case the previous logic failed to exclude. ### Notes to the reviewers Sorry for my previous oversight on this. ### Changelog notice ```md Fixed: - During canonicalization, exclude coinbase transactions when considering txs that are anchored in stale blocks. ``` ### Checklists #### All Submissions: * [x] I've signed all my commits * [x] I followed the [contribution guidelines](https://github.com/bitcoindevkit/bdk/blob/master/CONTRIBUTING.md) * [x] I ran `cargo +nightly fmt` and `cargo clippy` before committing #### Bugfixes: ~* [ ] This pull request breaks the existing API~ * [x] I've added tests to reproduce the issue which are now passing * [ ] I'm linking the issue being fixed by this PR ACKs for top commit: evanlinjin: self-ACK c84035e Tree-SHA512: 3aaff032ce390a7a8845c8704727daccab1afa2b0437f6706802f6ec37f7ffd79729407f1cba91b7e5045974a3a627c78a150242e2aaacb04a03b8f47528046b
2 parents a7c1bcc + c84035e commit d9c2b95

File tree

2 files changed

+27
-1
lines changed

2 files changed

+27
-1
lines changed

crates/chain/src/canonical_iter.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,10 @@ impl<A: Anchor, C: ChainOracle> Iterator for CanonicalIter<'_, A, C> {
230230
}
231231

232232
if let Some((txid, tx, last_seen)) = self.unprocessed_seen_txs.next() {
233+
debug_assert!(
234+
!tx.is_coinbase(),
235+
"Coinbase txs must not have `last_seen` (in mempool) value"
236+
);
233237
if !self.is_canonicalized(txid) {
234238
let observed_in = ObservedIn::Mempool(last_seen);
235239
self.mark_canonical(txid, tx, CanonicalReason::from_observed_in(observed_in));
@@ -238,7 +242,7 @@ impl<A: Anchor, C: ChainOracle> Iterator for CanonicalIter<'_, A, C> {
238242
}
239243

240244
if let Some((txid, tx, height)) = self.unprocessed_leftover_txs.pop_front() {
241-
if !self.is_canonicalized(txid) {
245+
if !self.is_canonicalized(txid) && !tx.is_coinbase() {
242246
let observed_in = ObservedIn::Block(height);
243247
self.mark_canonical(txid, tx, CanonicalReason::from_observed_in(observed_in));
244248
}

crates/chain/tests/test_tx_graph_conflicts.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -943,6 +943,28 @@ fn test_tx_conflict_handling() {
943943
confirmed: Amount::ZERO,
944944
},
945945
},
946+
Scenario {
947+
name: "coinbase tx must not become unconfirmed",
948+
tx_templates: &[
949+
TxTemplate {
950+
tx_name: "coinbase",
951+
inputs: &[TxInTemplate::Coinbase],
952+
outputs: &[TxOutTemplate::new(21_000, Some(0))],
953+
// Stale block
954+
anchors: &[block_id!(1, "B-prime")],
955+
..Default::default()
956+
}
957+
],
958+
exp_chain_txs: HashSet::from([]),
959+
exp_chain_txouts: HashSet::from([]),
960+
exp_unspents: HashSet::from([]),
961+
exp_balance: Balance {
962+
immature: Amount::ZERO,
963+
trusted_pending: Amount::ZERO,
964+
untrusted_pending: Amount::ZERO,
965+
confirmed: Amount::ZERO,
966+
}
967+
}
946968
];
947969

948970
for scenario in scenarios {

0 commit comments

Comments
 (0)