From: hassan <144530465+h55n@users.noreply.github.com> Date: Fri, 2 Oct 2026 00:18:20 +0000 (+0530) Subject: fix(chain): reject an occupied index in insert_spk X-Git-Url: http://internal-gitweb-vhost/-client-interface/%22https:/src/struct.EncoderStringWriter.html?a=commitdiff_plain;h=ee1909efdbe53261e268819cf7d1cd9260a03df0;p=bdk fix(chain): reject an occupied index in insert_spk Reject an occupied index before changing either lookup map. Preserve existing script, scanned-output and used-status mappings on a rejected insertion, matching the existing behavior for an occupied script. Signed-off-by: hassan <144530465+h55n@users.noreply.github.com> --- diff --git a/crates/chain/src/indexer/spk_txout.rs b/crates/chain/src/indexer/spk_txout.rs index e63eb120..1bd889cb 100644 --- a/crates/chain/src/indexer/spk_txout.rs +++ b/crates/chain/src/indexer/spk_txout.rs @@ -193,11 +193,15 @@ impl SpkTxOutIndex { &self.spks } - /// Adds a script pubkey to scan for. Returns `false` and does nothing if spk already exists in - /// the map + /// Adds a script pubkey to scan for. /// - /// the index will look for outputs spending to this spk whenever it scans new data. + /// The indexer will look for outputs spending to this spk whenever it scans new data. + /// + /// Returns `false` and does nothing if the script pubkey or index already exists. pub fn insert_spk(&mut self, index: I, spk: ScriptBuf) -> bool { + if self.spks.contains_key(&index) { + return false; + } match self.spk_indices.entry(spk.clone()) { Entry::Vacant(value) => { value.insert(index.clone()); diff --git a/crates/chain/tests/test_spk_txout_index.rs b/crates/chain/tests/test_spk_txout_index.rs index 591a2b1c..9f527760 100644 --- a/crates/chain/tests/test_spk_txout_index.rs +++ b/crates/chain/tests/test_spk_txout_index.rs @@ -430,3 +430,52 @@ fn outputs_in_range_excluded_bounds() { .collect(); assert_eq!(outputs_empty.len(), 0); } + +#[test] +fn insert_spk_rejects_reused_index_and_script() { + let old_script = ScriptBuf::from_bytes(vec![0x51]); + let new_script = ScriptBuf::from_bytes(vec![0x52]); + let mut index = SpkTxOutIndex::::default(); + assert!(index.insert_spk(0, old_script.clone())); + assert!(!index.insert_spk(0, new_script.clone())); + assert!(!index.insert_spk(1, old_script.clone())); + assert!(!index.insert_spk(0, old_script.clone())); + assert_eq!(index.spk_at_index(&0), Some(old_script.clone())); + assert_eq!(index.index_of_spk(&old_script), Some(&0)); + assert_eq!(index.index_of_spk(&new_script), None); + assert_eq!(index.spk_at_index(&1), None); + assert_eq!( + index.unused_spks(..).collect::>(), + vec![(&0, old_script)] + ); + assert!(index.insert_spk(1, new_script.clone())); + assert_eq!(index.index_of_spk(&new_script), Some(&1)); +} + +#[test] +fn insert_spk_preserves_scanned_output_and_used_status() { + let old_script = ScriptBuf::from_bytes(vec![0x51]); + let new_script = ScriptBuf::from_bytes(vec![0x52]); + let mut index = SpkTxOutIndex::::default(); + assert!(index.insert_spk(0, old_script.clone())); + let old_output = TxOut { + value: Amount::from_sat(1000), + script_pubkey: old_script.clone(), + }; + let old_outpoint = OutPoint::null(); + assert_eq!(index.scan_txout(old_outpoint, &old_output), Some(&0)); + assert!(!index.insert_spk(0, new_script.clone())); + assert!(index.is_used(&0)); + assert_eq!(index.txout(old_outpoint), Some((&0, &old_output))); + assert_eq!(index.spk_at_index(&0), Some(old_script)); + let new_output = TxOut { + value: Amount::from_sat(2000), + script_pubkey: new_script, + }; + let new_outpoint = OutPoint { + vout: 1, + ..old_outpoint + }; + assert_eq!(index.scan_txout(new_outpoint, &new_output), None); + assert_eq!(index.txout(new_outpoint), None); +}