Skip to content

Commit c61995c

Browse files
authored
Merge pull request #927 from LagginTimes/custom_spk_iterator
Custom spk iterator
2 parents 6bc5f33 + 10fe32e commit c61995c

File tree

3 files changed

+240
-54
lines changed

3 files changed

+240
-54
lines changed

crates/chain/src/keychain/txout_index.rs

Lines changed: 21 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,17 @@ use crate::{
22
collections::*,
33
indexed_tx_graph::{Indexer, OwnedIndexer},
44
miniscript::{Descriptor, DescriptorPublicKey},
5-
ForEachTxOut, SpkTxOutIndex,
5+
spk_iter::BIP32_MAX_INDEX,
6+
ForEachTxOut, SpkIterator, SpkTxOutIndex,
67
};
7-
use alloc::{borrow::Cow, vec::Vec};
8-
use bitcoin::{secp256k1::Secp256k1, OutPoint, Script, TxOut};
8+
use alloc::vec::Vec;
9+
use bitcoin::{OutPoint, Script, TxOut};
910
use core::{fmt::Debug, ops::Deref};
1011

1112
use crate::Append;
1213

1314
use super::DerivationAdditions;
1415

15-
/// Maximum [BIP32](https://bips.xyz/32) derivation index.
16-
pub const BIP32_MAX_INDEX: u32 = (1 << 31) - 1;
17-
1816
/// A convenient wrapper around [`SpkTxOutIndex`] that relates script pubkeys to miniscript public
1917
/// [`Descriptor`]s.
2018
///
@@ -243,10 +241,9 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
243241
let next_reveal_index = self.last_revealed.get(keychain).map_or(0, |v| *v + 1);
244242
let lookahead = self.lookahead.get(keychain).map_or(0, |v| *v);
245243

246-
for (new_index, new_spk) in range_descriptor_spks(
247-
Cow::Borrowed(descriptor),
248-
next_store_index..next_reveal_index + lookahead,
249-
) {
244+
for (new_index, new_spk) in
245+
SpkIterator::new_with_range(descriptor, next_store_index..next_reveal_index + lookahead)
246+
{
250247
let _inserted = self
251248
.inner
252249
.insert_spk((keychain.clone(), new_index), new_spk);
@@ -266,13 +263,13 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
266263
/// derivable script pubkeys.
267264
pub fn spks_of_all_keychains(
268265
&self,
269-
) -> BTreeMap<K, impl Iterator<Item = (u32, Script)> + Clone> {
266+
) -> BTreeMap<K, SpkIterator<Descriptor<DescriptorPublicKey>>> {
270267
self.keychains
271268
.iter()
272269
.map(|(keychain, descriptor)| {
273270
(
274271
keychain.clone(),
275-
range_descriptor_spks(Cow::Owned(descriptor.clone()), 0..),
272+
SpkIterator::new_with_range(descriptor.clone(), 0..),
276273
)
277274
})
278275
.collect()
@@ -284,13 +281,13 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
284281
/// # Panics
285282
///
286283
/// This will panic if the `keychain` does not exist.
287-
pub fn spks_of_keychain(&self, keychain: &K) -> impl Iterator<Item = (u32, Script)> + Clone {
284+
pub fn spks_of_keychain(&self, keychain: &K) -> SpkIterator<Descriptor<DescriptorPublicKey>> {
288285
let descriptor = self
289286
.keychains
290287
.get(keychain)
291288
.expect("keychain must exist")
292289
.clone();
293-
range_descriptor_spks(Cow::Owned(descriptor), 0..)
290+
SpkIterator::new_with_range(descriptor, 0..)
294291
}
295292

296293
/// Convenience method to get [`revealed_spks_of_keychain`] of all keychains.
@@ -370,7 +367,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
370367
&mut self,
371368
keychains: &BTreeMap<K, u32>,
372369
) -> (
373-
BTreeMap<K, impl Iterator<Item = (u32, Script)>>,
370+
BTreeMap<K, SpkIterator<Descriptor<DescriptorPublicKey>>>,
374371
DerivationAdditions<K>,
375372
) {
376373
let mut additions = DerivationAdditions::default();
@@ -380,7 +377,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
380377
let (new_spks, new_additions) = self.reveal_to_target(keychain, index);
381378
if !new_additions.is_empty() {
382379
spks.insert(keychain.clone(), new_spks);
383-
additions.append(new_additions);
380+
additions.append(new_additions.clone());
384381
}
385382
}
386383

@@ -405,7 +402,10 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
405402
&mut self,
406403
keychain: &K,
407404
target_index: u32,
408-
) -> (impl Iterator<Item = (u32, Script)>, DerivationAdditions<K>) {
405+
) -> (
406+
SpkIterator<Descriptor<DescriptorPublicKey>>,
407+
DerivationAdditions<K>,
408+
) {
409409
let descriptor = self.keychains.get(keychain).expect("keychain must exist");
410410
let has_wildcard = descriptor.has_wildcard();
411411

@@ -430,7 +430,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
430430

431431
// we range over indexes that are not stored
432432
let range = next_reveal_index + lookahead..=target_index + lookahead;
433-
for (new_index, new_spk) in range_descriptor_spks(Cow::Borrowed(descriptor), range) {
433+
for (new_index, new_spk) in SpkIterator::new_with_range(descriptor, range) {
434434
let _inserted = self
435435
.inner
436436
.insert_spk((keychain.clone(), new_index), new_spk);
@@ -447,16 +447,13 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
447447
let _old_index = self.last_revealed.insert(keychain.clone(), index);
448448
debug_assert!(_old_index < Some(index));
449449
(
450-
range_descriptor_spks(
451-
Cow::Owned(descriptor.clone()),
452-
next_reveal_index..index + 1,
453-
),
450+
SpkIterator::new_with_range(descriptor.clone(), next_reveal_index..index + 1),
454451
DerivationAdditions(core::iter::once((keychain.clone(), index)).collect()),
455452
)
456453
}
457454
None => (
458-
range_descriptor_spks(
459-
Cow::Owned(descriptor.clone()),
455+
SpkIterator::new_with_range(
456+
descriptor.clone(),
460457
next_reveal_index..next_reveal_index,
461458
),
462459
DerivationAdditions::default(),
@@ -587,33 +584,3 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
587584
let _ = self.reveal_to_target_multi(&additions.0);
588585
}
589586
}
590-
591-
fn range_descriptor_spks<'a, R>(
592-
descriptor: Cow<'a, Descriptor<DescriptorPublicKey>>,
593-
range: R,
594-
) -> impl Iterator<Item = (u32, Script)> + Clone + Send + 'a
595-
where
596-
R: Iterator<Item = u32> + Clone + Send + 'a,
597-
{
598-
let secp = Secp256k1::verification_only();
599-
let has_wildcard = descriptor.has_wildcard();
600-
range
601-
.into_iter()
602-
// non-wildcard descriptors can only have one derivation index (0)
603-
.take_while(move |&index| has_wildcard || index == 0)
604-
// we can only iterate over non-hardened indices
605-
.take_while(|&index| index <= BIP32_MAX_INDEX)
606-
.map(
607-
move |index| -> Result<_, miniscript::descriptor::ConversionError> {
608-
Ok((
609-
index,
610-
descriptor
611-
.at_derivation_index(index)
612-
.derived_descriptor(&secp)?
613-
.script_pubkey(),
614-
))
615-
},
616-
)
617-
.take_while(Result::is_ok)
618-
.map(Result::unwrap)
619-
}

crates/chain/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ pub use miniscript;
4343
mod descriptor_ext;
4444
#[cfg(feature = "miniscript")]
4545
pub use descriptor_ext::DescriptorExt;
46+
#[cfg(feature = "miniscript")]
47+
mod spk_iter;
48+
#[cfg(feature = "miniscript")]
49+
pub use spk_iter::*;
4650

4751
#[allow(unused_imports)]
4852
#[macro_use]

crates/chain/src/spk_iter.rs

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
use crate::{
2+
bitcoin::{secp256k1::Secp256k1, Script},
3+
miniscript::{Descriptor, DescriptorPublicKey},
4+
};
5+
use core::{borrow::Borrow, ops::Bound, ops::RangeBounds};
6+
7+
/// Maximum [BIP32](https://bips.xyz/32) derivation index.
8+
pub const BIP32_MAX_INDEX: u32 = (1 << 31) - 1;
9+
10+
/// An iterator for derived script pubkeys.
11+
///
12+
/// [`SpkIterator`] is an implementation of the [`Iterator`] trait which possesses its own `next()`
13+
/// and `nth()` functions, both of which circumvent the unnecessary intermediate derivations required
14+
/// when using their default implementations.
15+
///
16+
/// ## Examples
17+
///
18+
/// ```
19+
/// use bdk_chain::SpkIterator;
20+
/// # use miniscript::{Descriptor, DescriptorPublicKey};
21+
/// # use bitcoin::{secp256k1::Secp256k1};
22+
/// # use std::str::FromStr;
23+
/// # let secp = bitcoin::secp256k1::Secp256k1::signing_only();
24+
/// # let (descriptor, _) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "wpkh([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/0)").unwrap();
25+
/// # let external_spk_0 = descriptor.at_derivation_index(0).script_pubkey();
26+
/// # let external_spk_3 = descriptor.at_derivation_index(3).script_pubkey();
27+
/// # let external_spk_4 = descriptor.at_derivation_index(4).script_pubkey();
28+
///
29+
/// // Creates a new script pubkey iterator starting at 0 from a descriptor.
30+
/// let mut spk_iter = SpkIterator::new(&descriptor);
31+
/// assert_eq!(spk_iter.next(), Some((0, external_spk_0)));
32+
/// assert_eq!(spk_iter.next(), None);
33+
/// ```
34+
#[derive(Clone)]
35+
pub struct SpkIterator<D> {
36+
next_index: u32,
37+
end: u32,
38+
descriptor: D,
39+
secp: Secp256k1<bitcoin::secp256k1::VerifyOnly>,
40+
}
41+
42+
impl<D> SpkIterator<D>
43+
where
44+
D: Borrow<Descriptor<DescriptorPublicKey>>,
45+
{
46+
/// Creates a new script pubkey iterator starting at 0 from a descriptor.
47+
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)
55+
}
56+
57+
// Creates a new script pubkey iterator from a descriptor with a given range.
58+
pub(crate) fn new_with_range<R>(descriptor: D, range: R) -> Self
59+
where
60+
R: RangeBounds<u32>,
61+
{
62+
let mut end = match range.end_bound() {
63+
Bound::Included(end) => *end + 1,
64+
Bound::Excluded(end) => *end,
65+
Bound::Unbounded => u32::MAX,
66+
};
67+
// Because `end` is exclusive, we want the maximum value to be BIP32_MAX_INDEX + 1.
68+
end = end.min(BIP32_MAX_INDEX + 1);
69+
70+
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+
},
76+
end,
77+
descriptor,
78+
secp: Secp256k1::verification_only(),
79+
}
80+
}
81+
}
82+
83+
impl<D> Iterator for SpkIterator<D>
84+
where
85+
D: Borrow<Descriptor<DescriptorPublicKey>>,
86+
{
87+
type Item = (u32, Script);
88+
89+
fn next(&mut self) -> Option<Self::Item> {
90+
// For non-wildcard descriptors, we expect the first element to be Some((0, spk)), then None after.
91+
// For wildcard descriptors, we expect it to keep iterating until exhausted.
92+
if self.next_index >= self.end {
93+
return None;
94+
}
95+
96+
let script = self
97+
.descriptor
98+
.borrow()
99+
.at_derivation_index(self.next_index)
100+
.derived_descriptor(&self.secp)
101+
.expect("the descriptor cannot need hardened derivation")
102+
.script_pubkey();
103+
let output = (self.next_index, script);
104+
105+
self.next_index += 1;
106+
107+
Some(output)
108+
}
109+
110+
fn nth(&mut self, n: usize) -> Option<Self::Item> {
111+
self.next_index = self
112+
.next_index
113+
.saturating_add(u32::try_from(n).unwrap_or(u32::MAX));
114+
self.next()
115+
}
116+
}
117+
118+
#[cfg(test)]
119+
mod test {
120+
use crate::{
121+
bitcoin::secp256k1::Secp256k1,
122+
keychain::KeychainTxOutIndex,
123+
miniscript::{Descriptor, DescriptorPublicKey},
124+
spk_iter::{SpkIterator, BIP32_MAX_INDEX},
125+
};
126+
127+
#[derive(Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
128+
enum TestKeychain {
129+
External,
130+
Internal,
131+
}
132+
133+
fn init_txout_index() -> (
134+
KeychainTxOutIndex<TestKeychain>,
135+
Descriptor<DescriptorPublicKey>,
136+
Descriptor<DescriptorPublicKey>,
137+
) {
138+
let mut txout_index = KeychainTxOutIndex::<TestKeychain>::default();
139+
140+
let secp = Secp256k1::signing_only();
141+
let (external_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
142+
let (internal_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/*)").unwrap();
143+
144+
txout_index.add_keychain(TestKeychain::External, external_descriptor.clone());
145+
txout_index.add_keychain(TestKeychain::Internal, internal_descriptor.clone());
146+
147+
(txout_index, external_descriptor, internal_descriptor)
148+
}
149+
150+
#[test]
151+
#[allow(clippy::iter_nth_zero)]
152+
fn test_spkiterator_wildcard() {
153+
let (_, external_desc, _) = init_txout_index();
154+
let external_spk_0 = external_desc.at_derivation_index(0).script_pubkey();
155+
let external_spk_16 = external_desc.at_derivation_index(16).script_pubkey();
156+
let external_spk_20 = external_desc.at_derivation_index(20).script_pubkey();
157+
let external_spk_21 = external_desc.at_derivation_index(21).script_pubkey();
158+
let external_spk_max = external_desc
159+
.at_derivation_index(BIP32_MAX_INDEX)
160+
.script_pubkey();
161+
162+
let mut external_spk = SpkIterator::new(&external_desc);
163+
let max_index = BIP32_MAX_INDEX - 22;
164+
165+
assert_eq!(external_spk.next().unwrap(), (0, external_spk_0));
166+
assert_eq!(external_spk.nth(15).unwrap(), (16, external_spk_16));
167+
assert_eq!(external_spk.nth(3).unwrap(), (20, external_spk_20.clone()));
168+
assert_eq!(external_spk.next().unwrap(), (21, external_spk_21));
169+
assert_eq!(
170+
external_spk.nth(max_index as usize).unwrap(),
171+
(BIP32_MAX_INDEX, external_spk_max)
172+
);
173+
assert_eq!(external_spk.nth(0), None);
174+
175+
let mut external_spk = SpkIterator::new_with_range(&external_desc, 0..21);
176+
assert_eq!(external_spk.nth(20).unwrap(), (20, external_spk_20));
177+
assert_eq!(external_spk.next(), None);
178+
179+
let mut external_spk = SpkIterator::new_with_range(&external_desc, 0..21);
180+
assert_eq!(external_spk.nth(21), None);
181+
}
182+
183+
#[test]
184+
#[allow(clippy::iter_nth_zero)]
185+
fn test_spkiterator_non_wildcard() {
186+
let secp = bitcoin::secp256k1::Secp256k1::signing_only();
187+
let (no_wildcard_descriptor, _) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "wpkh([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/0)").unwrap();
188+
let external_spk_0 = no_wildcard_descriptor
189+
.at_derivation_index(0)
190+
.script_pubkey();
191+
192+
let mut external_spk = SpkIterator::new(&no_wildcard_descriptor);
193+
194+
assert_eq!(external_spk.next().unwrap(), (0, external_spk_0.clone()));
195+
assert_eq!(external_spk.next(), None);
196+
197+
let mut external_spk = SpkIterator::new(&no_wildcard_descriptor);
198+
199+
assert_eq!(external_spk.nth(0).unwrap(), (0, external_spk_0));
200+
assert_eq!(external_spk.nth(0), None);
201+
}
202+
203+
// The following dummy traits were created to test if SpkIterator is working properly.
204+
trait TestSendStatic: Send + 'static {
205+
fn test(&self) -> u32 {
206+
20
207+
}
208+
}
209+
210+
impl TestSendStatic for SpkIterator<Descriptor<DescriptorPublicKey>> {
211+
fn test(&self) -> u32 {
212+
20
213+
}
214+
}
215+
}

0 commit comments

Comments
 (0)