Skip to content

Commit 2867e88

Browse files
committed
Merge #1093: fix: spks_of_all_keychains() shouldn't return an infinite iterator for non-wildcard descriptors
e48b911 refactor: Make test errors more readable (Daniela Brozzoni) a7a1d9b fix: non-wildcard descriptors should return an.. ..spk only if index is equal to 0 (Daniela Brozzoni) cc1a43c fix: SpkIterator::new_with_range takes wildcards.. ..into account (Daniela Brozzoni) Pull request description: ### Description 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 ``` ### Changelog notice - Fixed a bug where `KeychainTxOutIndex::spks_of_all_keychains`/`KeychainTxOutIndex::spks_of_keychain` would return an iterator yielding infinite spks even for non-wildcard descriptors. ### 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 fmt` and `cargo clippy` before committing ACKs for top commit: evanlinjin: ACK e48b911 Tree-SHA512: 87627505049eadcec979a05888ec0d8a25c4743c03696a7db68348d457c2bf006d9b3b69c99e208f7812fc5b0234dd5a98b4a923c2486615c7678c3ab523f8cf
2 parents 8321aaa + e48b911 commit 2867e88

File tree

2 files changed

+77
-21
lines changed

2 files changed

+77
-21
lines changed

crates/chain/src/spk_iter.rs

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -45,34 +45,43 @@ 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
55+
// Also note that if the descriptor doesn't have a wildcard, passing in a range starting
56+
// from n > 0, will return an empty iterator.
5857
pub(crate) fn new_with_range<R>(descriptor: D, range: R) -> Self
5958
where
6059
R: RangeBounds<u32>,
6160
{
61+
let start = match range.start_bound() {
62+
Bound::Included(start) => *start,
63+
Bound::Excluded(start) => *start + 1,
64+
Bound::Unbounded => u32::MIN,
65+
};
66+
6267
let mut end = match range.end_bound() {
6368
Bound::Included(end) => *end + 1,
6469
Bound::Excluded(end) => *end,
6570
Bound::Unbounded => u32::MAX,
6671
};
72+
6773
// Because `end` is exclusive, we want the maximum value to be BIP32_MAX_INDEX + 1.
6874
end = end.min(BIP32_MAX_INDEX + 1);
6975

76+
if !descriptor.borrow().has_wildcard() {
77+
// The length of the range should be at most 1
78+
if end != start {
79+
end = start + 1;
80+
}
81+
}
82+
7083
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-
},
84+
next_index: start,
7685
end,
7786
descriptor,
7887
secp: Secp256k1::verification_only(),
@@ -93,6 +102,11 @@ where
93102
return None;
94103
}
95104

105+
// If the descriptor is non-wildcard, only index 0 will return an spk.
106+
if !self.descriptor.borrow().has_wildcard() && self.next_index != 0 {
107+
return None;
108+
}
109+
96110
let script = self
97111
.descriptor
98112
.borrow()
@@ -160,18 +174,18 @@ mod test {
160174
let mut external_spk = SpkIterator::new(&external_desc);
161175
let max_index = BIP32_MAX_INDEX - 22;
162176

163-
assert_eq!(external_spk.next().unwrap(), (0, external_spk_0));
164-
assert_eq!(external_spk.nth(15).unwrap(), (16, external_spk_16));
165-
assert_eq!(external_spk.nth(3).unwrap(), (20, external_spk_20.clone()));
166-
assert_eq!(external_spk.next().unwrap(), (21, external_spk_21));
177+
assert_eq!(external_spk.next(), Some((0, external_spk_0)));
178+
assert_eq!(external_spk.nth(15), Some((16, external_spk_16)));
179+
assert_eq!(external_spk.nth(3), Some((20, external_spk_20.clone())));
180+
assert_eq!(external_spk.next(), Some((21, external_spk_21)));
167181
assert_eq!(
168-
external_spk.nth(max_index as usize).unwrap(),
169-
(BIP32_MAX_INDEX, external_spk_max)
182+
external_spk.nth(max_index as usize),
183+
Some((BIP32_MAX_INDEX, external_spk_max))
170184
);
171185
assert_eq!(external_spk.nth(0), None);
172186

173187
let mut external_spk = SpkIterator::new_with_range(&external_desc, 0..21);
174-
assert_eq!(external_spk.nth(20).unwrap(), (20, external_spk_20));
188+
assert_eq!(external_spk.nth(20), Some((20, external_spk_20)));
175189
assert_eq!(external_spk.next(), None);
176190

177191
let mut external_spk = SpkIterator::new_with_range(&external_desc, 0..21);
@@ -190,13 +204,47 @@ mod test {
190204

191205
let mut external_spk = SpkIterator::new(&no_wildcard_descriptor);
192206

193-
assert_eq!(external_spk.next().unwrap(), (0, external_spk_0.clone()));
207+
assert_eq!(external_spk.next(), Some((0, external_spk_0.clone())));
194208
assert_eq!(external_spk.next(), None);
195209

196210
let mut external_spk = SpkIterator::new(&no_wildcard_descriptor);
197211

198-
assert_eq!(external_spk.nth(0).unwrap(), (0, external_spk_0));
212+
assert_eq!(external_spk.nth(0), Some((0, external_spk_0.clone())));
213+
assert_eq!(external_spk.nth(0), None);
214+
215+
let mut external_spk = SpkIterator::new_with_range(&no_wildcard_descriptor, 0..0);
216+
217+
assert_eq!(external_spk.next(), None);
218+
219+
let mut external_spk = SpkIterator::new_with_range(&no_wildcard_descriptor, 0..1);
220+
221+
assert_eq!(external_spk.nth(0), Some((0, external_spk_0.clone())));
222+
assert_eq!(external_spk.next(), None);
223+
224+
// We test that using new_with_range with range_len > 1 gives back an iterator with
225+
// range_len = 1
226+
let mut external_spk = SpkIterator::new_with_range(&no_wildcard_descriptor, 0..10);
227+
228+
assert_eq!(external_spk.nth(0), Some((0, external_spk_0)));
199229
assert_eq!(external_spk.nth(0), None);
230+
231+
// non index-0 should NOT return an spk
232+
assert_eq!(
233+
SpkIterator::new_with_range(&no_wildcard_descriptor, 1..1).next(),
234+
None
235+
);
236+
assert_eq!(
237+
SpkIterator::new_with_range(&no_wildcard_descriptor, 1..=1).next(),
238+
None
239+
);
240+
assert_eq!(
241+
SpkIterator::new_with_range(&no_wildcard_descriptor, 1..2).next(),
242+
None
243+
);
244+
assert_eq!(
245+
SpkIterator::new_with_range(&no_wildcard_descriptor, 1..=2).next(),
246+
None
247+
);
200248
}
201249

202250
// The following dummy traits were created to test if SpkIterator is working properly.

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)