Skip to content

Commit cc1a43c

Browse files
fix: SpkIterator::new_with_range takes wildcards..
..into account When you pass a non-wildcard descriptor in `new_with_range`, we make sure that the range length is at most 1; if that's not the case, we shorten it. We would previously use `new_with_range` without this check and with a non-wildcard descriptor in `spks_of_all_keychains`, this meant creating a spkiterator that would go on producing the same spks over and over again, causing some issues with syncing on electrum/esplora. To reproduce the bug, run in `example-crates/example_electrum`: ``` cargo run "sh(wsh(or_d(c:pk_k(cPGudvRLDSgeV4hH9NUofLvYxYBSRjju3cpiXmBg9K8G9k1ikCMp),c:pk_k(cSBSBHRrzqSXFmrBhLkZMzQB9q4P9MnAq92v8d9a5UveBc9sLX32))))#zp9pcfs9" scan ```
1 parent 93e8eaf commit cc1a43c

File tree

2 files changed

+43
-12
lines changed

2 files changed

+43
-12
lines changed

crates/chain/src/spk_iter.rs

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -45,34 +45,41 @@ where
4545
{
4646
/// Creates a new script pubkey iterator starting at 0 from a descriptor.
4747
pub fn new(descriptor: D) -> Self {
48-
let end = if descriptor.borrow().has_wildcard() {
49-
BIP32_MAX_INDEX
50-
} else {
51-
0
52-
};
53-
54-
SpkIterator::new_with_range(descriptor, 0..=end)
48+
SpkIterator::new_with_range(descriptor, 0..=BIP32_MAX_INDEX)
5549
}
5650

5751
// Creates a new script pubkey iterator from a descriptor with a given range.
52+
// If the descriptor doesn't have a wildcard, we shorten whichever range you pass in
53+
// to have length <= 1. This means that if you pass in 0..0 or 0..1 the range will
54+
// remain the same, but if you pass in 0..10, we'll shorten it to 0..1
5855
pub(crate) fn new_with_range<R>(descriptor: D, range: R) -> Self
5956
where
6057
R: RangeBounds<u32>,
6158
{
59+
let start = match range.start_bound() {
60+
Bound::Included(start) => *start,
61+
Bound::Excluded(start) => *start + 1,
62+
Bound::Unbounded => u32::MIN,
63+
};
64+
6265
let mut end = match range.end_bound() {
6366
Bound::Included(end) => *end + 1,
6467
Bound::Excluded(end) => *end,
6568
Bound::Unbounded => u32::MAX,
6669
};
70+
6771
// Because `end` is exclusive, we want the maximum value to be BIP32_MAX_INDEX + 1.
6872
end = end.min(BIP32_MAX_INDEX + 1);
6973

74+
if !descriptor.borrow().has_wildcard() {
75+
// The length of the range should be at most 1
76+
if end != start {
77+
end = start + 1;
78+
}
79+
}
80+
7081
Self {
71-
next_index: match range.start_bound() {
72-
Bound::Included(start) => *start,
73-
Bound::Excluded(start) => *start + 1,
74-
Bound::Unbounded => u32::MIN,
75-
},
82+
next_index: start,
7683
end,
7784
descriptor,
7885
secp: Secp256k1::verification_only(),
@@ -195,6 +202,22 @@ mod test {
195202

196203
let mut external_spk = SpkIterator::new(&no_wildcard_descriptor);
197204

205+
assert_eq!(external_spk.nth(0).unwrap(), (0, external_spk_0.clone()));
206+
assert_eq!(external_spk.nth(0), None);
207+
208+
let mut external_spk = SpkIterator::new_with_range(&no_wildcard_descriptor, 0..0);
209+
210+
assert_eq!(external_spk.next(), None);
211+
212+
let mut external_spk = SpkIterator::new_with_range(&no_wildcard_descriptor, 0..1);
213+
214+
assert_eq!(external_spk.nth(0).unwrap(), (0, external_spk_0.clone()));
215+
assert_eq!(external_spk.next(), None);
216+
217+
// We test that using new_with_range with range_len > 1 gives back an iterator with
218+
// range_len = 1
219+
let mut external_spk = SpkIterator::new_with_range(&no_wildcard_descriptor, 0..10);
220+
198221
assert_eq!(external_spk.nth(0).unwrap(), (0, external_spk_0));
199222
assert_eq!(external_spk.nth(0), None);
200223
}

crates/chain/tests/test_keychain_txout_index.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,4 +384,12 @@ fn test_non_wildcard_derivations() {
384384
txout_index.reveal_to_target(&TestKeychain::External, 200);
385385
assert_eq!(revealed_spks.count(), 0);
386386
assert!(revealed_changeset.is_empty());
387+
388+
// we check that spks_of_keychain returns a SpkIterator with just one element
389+
assert_eq!(
390+
txout_index
391+
.spks_of_keychain(&TestKeychain::External)
392+
.count(),
393+
1,
394+
);
387395
}

0 commit comments

Comments
 (0)