Skip to content

Commit 642baa6

Browse files
jkczyzclaude
andcommitted
f - Avoid fuzz hash collisions in outpoint spent checks
Under fuzz hashing, txids have only 8 effective bits, so outpoints from unrelated transactions in old blocks frequently collide. This causes is_outpoint_spent to produce false positives, silently preventing legitimate transactions from being added to the pending pool or confirmed. Limit is_outpoint_spent to the last 6 blocks. Confirmed transactions are always followed by 5 empty blocks, so the scan window covers exactly one block with transactions. This catches real double-spends (same-channel splice RBF candidates) while avoiding false positives from older blocks. Also remove the is_outpoint_spent check from add_pending_tx so that filtering decisions are made at confirmation time (in confirm_pending_txs) rather than at add time. This ensures all candidates participate in the deterministic sort before any are rejected. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c972bf2 commit 642baa6

1 file changed

Lines changed: 6 additions & 7 deletions

File tree

fuzz/src/chanmon_consistency.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,11 @@ impl ChainState {
207207
}
208208

209209
fn is_outpoint_spent(&self, outpoint: &bitcoin::OutPoint) -> bool {
210-
self.blocks.iter().any(|(_, txs)| {
210+
// Only check the last 6 blocks (1 confirmation block + 5 post-confirmation) to avoid
211+
// false positives from hash collisions in older blocks. Under fuzz hashing, txids have
212+
// only 8 effective bits, so unrelated outpoints in old blocks frequently collide.
213+
let start = self.blocks.len().saturating_sub(6);
214+
self.blocks[start..].iter().any(|(_, txs)| {
211215
txs.iter().any(|tx| {
212216
tx.input.iter().any(|input| input.previous_output == *outpoint)
213217
})
@@ -237,13 +241,8 @@ impl ChainState {
237241
}
238242

239243
/// Add a transaction to the pending pool (mempool). Multiple conflicting transactions (RBF
240-
/// candidates) may coexist; `confirm_pending_txs` selects which one to confirm. If the
241-
/// conflicting transaction was already confirmed, the new transaction is dropped since a
242-
/// confirmed transaction cannot be replaced on chain.
244+
/// candidates) may coexist; `confirm_pending_txs` selects which one to confirm.
243245
fn add_pending_tx(&mut self, tx: Transaction) {
244-
if tx.input.iter().any(|i| self.is_outpoint_spent(&i.previous_output)) {
245-
return;
246-
}
247246
self.pending_txs.push(tx);
248247
}
249248

0 commit comments

Comments
 (0)