Merge bitcoindevkit/bdk#1093: fix: spks_of_all_keychains() shouldn't return an infinite iterator for non-wildcard descriptors

e48b911c8d23cceab2071cc92085fe6675d55ca9 refactor: Make test errors more readable (Daniela Brozzoni)
a7a1d9b2fb9fda4d86fe17cf90d9c0d46c4d8609 fix: non-wildcard descriptors should return an.. ..spk only if index is equal to 0 (Daniela Brozzoni)
cc1a43c495639f0dbe6da52501cb48f17b8cbbd2 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 e48b911c8d23cceab2071cc92085fe6675d55ca9

Tree-SHA512: 87627505049eadcec979a05888ec0d8a25c4743c03696a7db68348d457c2bf006d9b3b69c99e208f7812fc5b0234dd5a98b4a923c2486615c7678c3ab523f8cf
This commit is contained in:
志宇 2023-09-01 15:47:40 +08:00
commit 2867e88b64
No known key found for this signature in database
GPG Key ID: F6345C9837C2BDE8
2 changed files with 77 additions and 21 deletions

View File

@ -45,34 +45,43 @@ where
{ {
/// Creates a new script pubkey iterator starting at 0 from a descriptor. /// Creates a new script pubkey iterator starting at 0 from a descriptor.
pub fn new(descriptor: D) -> Self { pub fn new(descriptor: D) -> Self {
let end = if descriptor.borrow().has_wildcard() { SpkIterator::new_with_range(descriptor, 0..=BIP32_MAX_INDEX)
BIP32_MAX_INDEX
} else {
0
};
SpkIterator::new_with_range(descriptor, 0..=end)
} }
// Creates a new script pubkey iterator from a descriptor with a given range. // Creates a new script pubkey iterator from a descriptor with a given range.
// If the descriptor doesn't have a wildcard, we shorten whichever range you pass in
// to have length <= 1. This means that if you pass in 0..0 or 0..1 the range will
// remain the same, but if you pass in 0..10, we'll shorten it to 0..1
// Also note that if the descriptor doesn't have a wildcard, passing in a range starting
// from n > 0, will return an empty iterator.
pub(crate) fn new_with_range<R>(descriptor: D, range: R) -> Self pub(crate) fn new_with_range<R>(descriptor: D, range: R) -> Self
where where
R: RangeBounds<u32>, R: RangeBounds<u32>,
{ {
let start = match range.start_bound() {
Bound::Included(start) => *start,
Bound::Excluded(start) => *start + 1,
Bound::Unbounded => u32::MIN,
};
let mut end = match range.end_bound() { let mut end = match range.end_bound() {
Bound::Included(end) => *end + 1, Bound::Included(end) => *end + 1,
Bound::Excluded(end) => *end, Bound::Excluded(end) => *end,
Bound::Unbounded => u32::MAX, Bound::Unbounded => u32::MAX,
}; };
// Because `end` is exclusive, we want the maximum value to be BIP32_MAX_INDEX + 1. // Because `end` is exclusive, we want the maximum value to be BIP32_MAX_INDEX + 1.
end = end.min(BIP32_MAX_INDEX + 1); end = end.min(BIP32_MAX_INDEX + 1);
if !descriptor.borrow().has_wildcard() {
// The length of the range should be at most 1
if end != start {
end = start + 1;
}
}
Self { Self {
next_index: match range.start_bound() { next_index: start,
Bound::Included(start) => *start,
Bound::Excluded(start) => *start + 1,
Bound::Unbounded => u32::MIN,
},
end, end,
descriptor, descriptor,
secp: Secp256k1::verification_only(), secp: Secp256k1::verification_only(),
@ -93,6 +102,11 @@ where
return None; return None;
} }
// If the descriptor is non-wildcard, only index 0 will return an spk.
if !self.descriptor.borrow().has_wildcard() && self.next_index != 0 {
return None;
}
let script = self let script = self
.descriptor .descriptor
.borrow() .borrow()
@ -160,18 +174,18 @@ mod test {
let mut external_spk = SpkIterator::new(&external_desc); let mut external_spk = SpkIterator::new(&external_desc);
let max_index = BIP32_MAX_INDEX - 22; let max_index = BIP32_MAX_INDEX - 22;
assert_eq!(external_spk.next().unwrap(), (0, external_spk_0)); assert_eq!(external_spk.next(), Some((0, external_spk_0)));
assert_eq!(external_spk.nth(15).unwrap(), (16, external_spk_16)); assert_eq!(external_spk.nth(15), Some((16, external_spk_16)));
assert_eq!(external_spk.nth(3).unwrap(), (20, external_spk_20.clone())); assert_eq!(external_spk.nth(3), Some((20, external_spk_20.clone())));
assert_eq!(external_spk.next().unwrap(), (21, external_spk_21)); assert_eq!(external_spk.next(), Some((21, external_spk_21)));
assert_eq!( assert_eq!(
external_spk.nth(max_index as usize).unwrap(), external_spk.nth(max_index as usize),
(BIP32_MAX_INDEX, external_spk_max) Some((BIP32_MAX_INDEX, external_spk_max))
); );
assert_eq!(external_spk.nth(0), None); assert_eq!(external_spk.nth(0), None);
let mut external_spk = SpkIterator::new_with_range(&external_desc, 0..21); let mut external_spk = SpkIterator::new_with_range(&external_desc, 0..21);
assert_eq!(external_spk.nth(20).unwrap(), (20, external_spk_20)); assert_eq!(external_spk.nth(20), Some((20, external_spk_20)));
assert_eq!(external_spk.next(), None); assert_eq!(external_spk.next(), None);
let mut external_spk = SpkIterator::new_with_range(&external_desc, 0..21); let mut external_spk = SpkIterator::new_with_range(&external_desc, 0..21);
@ -190,13 +204,47 @@ mod test {
let mut external_spk = SpkIterator::new(&no_wildcard_descriptor); let mut external_spk = SpkIterator::new(&no_wildcard_descriptor);
assert_eq!(external_spk.next().unwrap(), (0, external_spk_0.clone())); assert_eq!(external_spk.next(), Some((0, external_spk_0.clone())));
assert_eq!(external_spk.next(), None); assert_eq!(external_spk.next(), None);
let mut external_spk = SpkIterator::new(&no_wildcard_descriptor); let mut external_spk = SpkIterator::new(&no_wildcard_descriptor);
assert_eq!(external_spk.nth(0).unwrap(), (0, external_spk_0)); assert_eq!(external_spk.nth(0), Some((0, external_spk_0.clone())));
assert_eq!(external_spk.nth(0), None); assert_eq!(external_spk.nth(0), None);
let mut external_spk = SpkIterator::new_with_range(&no_wildcard_descriptor, 0..0);
assert_eq!(external_spk.next(), None);
let mut external_spk = SpkIterator::new_with_range(&no_wildcard_descriptor, 0..1);
assert_eq!(external_spk.nth(0), Some((0, external_spk_0.clone())));
assert_eq!(external_spk.next(), None);
// We test that using new_with_range with range_len > 1 gives back an iterator with
// range_len = 1
let mut external_spk = SpkIterator::new_with_range(&no_wildcard_descriptor, 0..10);
assert_eq!(external_spk.nth(0), Some((0, external_spk_0)));
assert_eq!(external_spk.nth(0), None);
// non index-0 should NOT return an spk
assert_eq!(
SpkIterator::new_with_range(&no_wildcard_descriptor, 1..1).next(),
None
);
assert_eq!(
SpkIterator::new_with_range(&no_wildcard_descriptor, 1..=1).next(),
None
);
assert_eq!(
SpkIterator::new_with_range(&no_wildcard_descriptor, 1..2).next(),
None
);
assert_eq!(
SpkIterator::new_with_range(&no_wildcard_descriptor, 1..=2).next(),
None
);
} }
// The following dummy traits were created to test if SpkIterator is working properly. // The following dummy traits were created to test if SpkIterator is working properly.

View File

@ -384,4 +384,12 @@ fn test_non_wildcard_derivations() {
txout_index.reveal_to_target(&TestKeychain::External, 200); txout_index.reveal_to_target(&TestKeychain::External, 200);
assert_eq!(revealed_spks.count(), 0); assert_eq!(revealed_spks.count(), 0);
assert!(revealed_changeset.is_empty()); assert!(revealed_changeset.is_empty());
// we check that spks_of_keychain returns a SpkIterator with just one element
assert_eq!(
txout_index
.spks_of_keychain(&TestKeychain::External)
.count(),
1,
);
} }