Skip to content

Commit ab848d6

Browse files
committed
feat(chain)!: KeychainTxOutIndex uses a universal lookahead
Instead of a per-keychain lookahead, we have a universal lookahead for all keychains of a `KeychainTxOutIndex` instance. We restrict where the lookahead can be set to the constructor. This simplifies the API.
1 parent 373d0f8 commit ab848d6

File tree

5 files changed

+58
-148
lines changed

5 files changed

+58
-148
lines changed

crates/chain/src/keychain/txout_index.rs

Lines changed: 31 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use crate::{
55
spk_iter::BIP32_MAX_INDEX,
66
SpkIterator, SpkTxOutIndex,
77
};
8-
use alloc::vec::Vec;
98
use bitcoin::{OutPoint, Script, TxOut};
109
use core::{fmt::Debug, ops::Deref};
1110

@@ -67,17 +66,12 @@ pub struct KeychainTxOutIndex<K> {
6766
// last revealed indexes
6867
last_revealed: BTreeMap<K, u32>,
6968
// lookahead settings for each keychain
70-
lookahead: BTreeMap<K, u32>,
69+
lookahead: u32,
7170
}
7271

7372
impl<K> Default for KeychainTxOutIndex<K> {
7473
fn default() -> Self {
75-
Self {
76-
inner: SpkTxOutIndex::default(),
77-
keychains: BTreeMap::default(),
78-
last_revealed: BTreeMap::default(),
79-
lookahead: BTreeMap::default(),
80-
}
74+
Self::new(DEFAULT_LOOKAHEAD)
8175
}
8276
}
8377

@@ -120,6 +114,24 @@ impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
120114
}
121115
}
122116

117+
impl<K> KeychainTxOutIndex<K> {
118+
/// Construct a [`KeychainTxOutIndex`] with the given `lookahead`.
119+
///
120+
/// The lookahead is the number of scripts to cache ahead of the last revealed script index.
121+
/// This is useful to find outputs you own when processing block data that lie beyond the last
122+
/// revealed index. In certain situations, such as when performing an initial scan of the
123+
/// blockchain during wallet import, it may be uncertain or unknown what the last revealed index
124+
/// is.
125+
pub fn new(lookahead: u32) -> Self {
126+
Self {
127+
inner: SpkTxOutIndex::default(),
128+
keychains: BTreeMap::new(),
129+
last_revealed: BTreeMap::new(),
130+
lookahead,
131+
}
132+
}
133+
}
134+
123135
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
124136
/// Return a reference to the internal [`SpkTxOutIndex`].
125137
pub fn inner(&self) -> &SpkTxOutIndex<(K, u32)> {
@@ -136,38 +148,17 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
136148
&self.keychains
137149
}
138150

139-
/// Add a keychain to the tracker's `txout_index` with a descriptor to derive addresses and a
140-
/// default lookahead of `1_000`.
141-
///
142-
/// See [`add_keychain_with_lookahead`] for details.
143-
///
144-
/// # Panics
145-
///
146-
/// This will panic if a different `descriptor` is introduced to the same `keychain`.
147-
///
148-
/// [`add_keychain_with_lookahead`]: Self::add_keychain_with_lookahead
149-
pub fn add_keychain(&mut self, keychain: K, descriptor: Descriptor<DescriptorPublicKey>) {
150-
self.add_keychain_with_lookahead(keychain, descriptor, DEFAULT_LOOKAHEAD)
151-
}
152-
153151
/// Add a keychain to the tracker's `txout_index` with a descriptor to derive addresses.
154152
///
155153
/// Adding a keychain means you will be able to derive new script pubkeys under that keychain
156154
/// and the txout index will discover transaction outputs with those script pubkeys.
157155
///
158-
/// Refer to [`set_lookahead`] for an explanation of the `lookahead` parameter.
159-
///
160156
/// # Panics
161157
///
162158
/// This will panic if a different `descriptor` is introduced to the same `keychain`.
163159
///
164-
/// [`set_lookahead`]: Self::set_lookahead
165-
pub fn add_keychain_with_lookahead(
166-
&mut self,
167-
keychain: K,
168-
descriptor: Descriptor<DescriptorPublicKey>,
169-
lookahead: u32,
170-
) {
160+
/// [`add_keychain_with_lookahead`]: Self::add_keychain_with_lookahead
161+
pub fn add_keychain(&mut self, keychain: K, descriptor: Descriptor<DescriptorPublicKey>) {
171162
let old_descriptor = &*self
172163
.keychains
173164
.entry(keychain.clone())
@@ -176,54 +167,12 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
176167
&descriptor, old_descriptor,
177168
"keychain already contains a different descriptor"
178169
);
179-
self.set_lookahead(&keychain, lookahead)
180-
}
181-
182-
/// Return the lookahead setting for each keychain.
183-
///
184-
/// Refer to [`set_lookahead`] for a deeper explanation of the `lookahead`.
185-
///
186-
/// [`set_lookahead`]: Self::set_lookahead
187-
pub fn lookaheads(&self) -> &BTreeMap<K, u32> {
188-
&self.lookahead
189-
}
190-
191-
/// Convenience method to call [`set_lookahead`] for all keychains.
192-
///
193-
/// [`set_lookahead`]: Self::set_lookahead
194-
pub fn set_lookahead_for_all(&mut self, lookahead: u32) {
195-
for keychain in &self.keychains.keys().cloned().collect::<Vec<_>>() {
196-
self.set_lookahead(keychain, lookahead);
197-
}
198-
}
199-
200-
/// Set the lookahead count for `keychain`.
201-
///
202-
/// The lookahead is the number of scripts to cache ahead of the last revealed script index. This
203-
/// is useful to find outputs you own when processing block data that lie beyond the last revealed
204-
/// index. In certain situations, such as when performing an initial scan of the blockchain during
205-
/// wallet import, it may be uncertain or unknown what the last revealed index is.
206-
///
207-
/// # Panics
208-
///
209-
/// This will panic if the new `lookahead` value is smaller than the previous value.
210-
/// This will also panic if the `keychain` does not exist.
211-
pub fn set_lookahead(&mut self, keychain: &K, lookahead: u32) {
212-
let old_lookahead = self
213-
.lookahead
214-
.insert(keychain.clone(), lookahead)
215-
.unwrap_or(0);
216-
assert!(old_lookahead <= lookahead, "lookahead must always increase");
217-
self.replenish_lookahead(keychain);
170+
self.replenish_lookahead(&keychain, self.lookahead);
218171
}
219172

220-
/// Convenience method to call [`lookahead_to_target`] for multiple keychains.
221-
///
222-
/// [`lookahead_to_target`]: Self::lookahead_to_target
223-
pub fn lookahead_to_target_multi(&mut self, target_indexes: BTreeMap<K, u32>) {
224-
for (keychain, target_index) in target_indexes {
225-
self.lookahead_to_target(&keychain, target_index)
226-
}
173+
/// Return the lookahead setting.
174+
pub fn lookahead(&self) -> u32 {
175+
self.lookahead
227176
}
228177

229178
/// Store lookahead scripts until `target_index`.
@@ -232,23 +181,14 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
232181
pub fn lookahead_to_target(&mut self, keychain: &K, target_index: u32) {
233182
let next_index = self.next_store_index(keychain);
234183
if let Some(temp_lookahead) = target_index.checked_sub(next_index).filter(|&v| v > 0) {
235-
// We temporarily change the lookahead settings (so we can reuse the
236-
// `replenish_lookahead` logic).
237-
let old_lookahead = self.lookahead.insert(keychain.clone(), temp_lookahead);
238-
self.replenish_lookahead(keychain);
239-
// revert
240-
match old_lookahead {
241-
Some(lookahead) => self.lookahead.insert(keychain.clone(), lookahead),
242-
None => self.lookahead.remove(keychain),
243-
};
184+
self.replenish_lookahead(keychain, temp_lookahead);
244185
}
245186
}
246187

247-
fn replenish_lookahead(&mut self, keychain: &K) {
188+
fn replenish_lookahead(&mut self, keychain: &K, lookahead: u32) {
248189
let descriptor = self.keychains.get(keychain).expect("keychain must exist");
249190
let next_store_index = self.next_store_index(keychain);
250191
let next_reveal_index = self.last_revealed.get(keychain).map_or(0, |v| *v + 1);
251-
let lookahead = self.lookahead.get(keychain).map_or(0, |v| *v);
252192

253193
for (new_index, new_spk) in
254194
SpkIterator::new_with_range(descriptor, next_store_index..next_reveal_index + lookahead)
@@ -420,22 +360,21 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
420360

421361
let target_index = if has_wildcard { target_index } else { 0 };
422362
let next_reveal_index = self.last_revealed.get(keychain).map_or(0, |v| *v + 1);
423-
let lookahead = self.lookahead.get(keychain).map_or(0, |v| *v);
424363

425-
debug_assert!(next_reveal_index + lookahead >= self.next_store_index(keychain));
364+
debug_assert!(next_reveal_index + self.lookahead >= self.next_store_index(keychain));
426365

427366
// if we need to reveal new indices, the latest revealed index goes here
428367
let mut reveal_to_index = None;
429368

430369
// if the target is not yet revealed, but is already stored (due to lookahead), we need to
431370
// set the `reveal_to_index` as target here (as the `for` loop below only updates
432371
// `reveal_to_index` for indexes that are NOT stored)
433-
if next_reveal_index <= target_index && target_index < next_reveal_index + lookahead {
372+
if next_reveal_index <= target_index && target_index < next_reveal_index + self.lookahead {
434373
reveal_to_index = Some(target_index);
435374
}
436375

437376
// we range over indexes that are not stored
438-
let range = next_reveal_index + lookahead..=target_index + lookahead;
377+
let range = next_reveal_index + self.lookahead..=target_index + self.lookahead;
439378
for (new_index, new_spk) in SpkIterator::new_with_range(descriptor, range) {
440379
let _inserted = self
441380
.inner

crates/chain/src/spk_iter.rs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -148,22 +148,14 @@ mod test {
148148
Descriptor<DescriptorPublicKey>,
149149
Descriptor<DescriptorPublicKey>,
150150
) {
151-
let mut txout_index = KeychainTxOutIndex::<TestKeychain>::default();
151+
let mut txout_index = KeychainTxOutIndex::<TestKeychain>::new(0);
152152

153153
let secp = Secp256k1::signing_only();
154154
let (external_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
155155
let (internal_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/*)").unwrap();
156156

157-
txout_index.add_keychain_with_lookahead(
158-
TestKeychain::External,
159-
external_descriptor.clone(),
160-
0,
161-
);
162-
txout_index.add_keychain_with_lookahead(
163-
TestKeychain::Internal,
164-
internal_descriptor.clone(),
165-
0,
166-
);
157+
txout_index.add_keychain(TestKeychain::External, external_descriptor.clone());
158+
txout_index.add_keychain(TestKeychain::Internal, internal_descriptor.clone());
167159

168160
(txout_index, external_descriptor, internal_descriptor)
169161
}

crates/chain/tests/test_indexed_tx_graph.rs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ fn insert_relevant_txs() {
2727
let spk_0 = descriptor.at_derivation_index(0).unwrap().script_pubkey();
2828
let spk_1 = descriptor.at_derivation_index(9).unwrap().script_pubkey();
2929

30-
let mut graph = IndexedTxGraph::<ConfirmationHeightAnchor, KeychainTxOutIndex<()>>::default();
31-
graph.index.add_keychain_with_lookahead((), descriptor, 10);
30+
let mut graph = IndexedTxGraph::<ConfirmationHeightAnchor, KeychainTxOutIndex<()>>::new(
31+
KeychainTxOutIndex::new(10),
32+
);
33+
graph.index.add_keychain((), descriptor);
3234

3335
let tx_a = Transaction {
3436
output: vec![
@@ -117,15 +119,12 @@ fn test_list_owned_txouts() {
117119
let (desc_1, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/0/*)").unwrap();
118120
let (desc_2, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/1/*)").unwrap();
119121

120-
let mut graph =
121-
IndexedTxGraph::<ConfirmationHeightAnchor, KeychainTxOutIndex<String>>::default();
122+
let mut graph = IndexedTxGraph::<ConfirmationHeightAnchor, KeychainTxOutIndex<String>>::new(
123+
KeychainTxOutIndex::new(10),
124+
);
122125

123-
graph
124-
.index
125-
.add_keychain_with_lookahead("keychain_1".into(), desc_1, 10);
126-
graph
127-
.index
128-
.add_keychain_with_lookahead("keychain_2".into(), desc_2, 10);
126+
graph.index.add_keychain("keychain_1".into(), desc_1);
127+
graph.index.add_keychain("keychain_2".into(), desc_2);
129128

130129
// Get trusted and untrusted addresses
131130

crates/chain/tests/test_keychain_txout_index.rs

Lines changed: 13 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,20 @@ enum TestKeychain {
1919
}
2020

2121
fn init_txout_index(
22-
internal_lookahead: u32,
23-
external_lookahead: u32,
22+
lookahead: u32,
2423
) -> (
2524
bdk_chain::keychain::KeychainTxOutIndex<TestKeychain>,
2625
Descriptor<DescriptorPublicKey>,
2726
Descriptor<DescriptorPublicKey>,
2827
) {
29-
let mut txout_index = bdk_chain::keychain::KeychainTxOutIndex::<TestKeychain>::default();
28+
let mut txout_index = bdk_chain::keychain::KeychainTxOutIndex::<TestKeychain>::new(lookahead);
3029

3130
let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only();
3231
let (external_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
3332
let (internal_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/*)").unwrap();
3433

35-
txout_index.add_keychain_with_lookahead(
36-
TestKeychain::External,
37-
external_descriptor.clone(),
38-
internal_lookahead,
39-
);
40-
txout_index.add_keychain_with_lookahead(
41-
TestKeychain::Internal,
42-
internal_descriptor.clone(),
43-
external_lookahead,
44-
);
34+
txout_index.add_keychain(TestKeychain::External, external_descriptor.clone());
35+
txout_index.add_keychain(TestKeychain::Internal, internal_descriptor.clone());
4536

4637
(txout_index, external_descriptor, internal_descriptor)
4738
}
@@ -57,7 +48,7 @@ fn spk_at_index(descriptor: &Descriptor<DescriptorPublicKey>, index: u32) -> Scr
5748
fn test_set_all_derivation_indices() {
5849
use bdk_chain::indexed_tx_graph::Indexer;
5950

60-
let (mut txout_index, _, _) = init_txout_index(0, 0);
51+
let (mut txout_index, _, _) = init_txout_index(0);
6152
let derive_to: BTreeMap<_, _> =
6253
[(TestKeychain::External, 12), (TestKeychain::Internal, 24)].into();
6354
assert_eq!(
@@ -75,11 +66,10 @@ fn test_set_all_derivation_indices() {
7566

7667
#[test]
7768
fn test_lookahead() {
78-
let (mut txout_index, external_desc, internal_desc) = init_txout_index(10, 20);
69+
let (mut txout_index, external_desc, internal_desc) = init_txout_index(10);
7970

8071
// given:
8172
// - external lookahead set to 10
82-
// - internal lookahead set to 20
8373
// when:
8474
// - set external derivation index to value higher than last, but within the lookahead value
8575
// expect:
@@ -100,7 +90,7 @@ fn test_lookahead() {
10090
assert_eq!(
10191
txout_index.inner().all_spks().len(),
10292
10 /* external lookahead */ +
103-
20 /* internal lookahead */ +
93+
10 /* internal lookahead */ +
10494
index as usize + 1 /* `derived` count */
10595
);
10696
assert_eq!(
@@ -130,7 +120,7 @@ fn test_lookahead() {
130120
}
131121

132122
// given:
133-
// - internal lookahead is 20
123+
// - internal lookahead is 10
134124
// - internal derivation index is `None`
135125
// when:
136126
// - derivation index is set ahead of current derivation index + lookahead
@@ -151,7 +141,7 @@ fn test_lookahead() {
151141
assert_eq!(
152142
txout_index.inner().all_spks().len(),
153143
10 /* external lookahead */ +
154-
20 /* internal lookahead */ +
144+
10 /* internal lookahead */ +
155145
20 /* external stored index count */ +
156146
25 /* internal stored index count */
157147
);
@@ -229,7 +219,7 @@ fn test_lookahead() {
229219
// - last used index should change as expected
230220
#[test]
231221
fn test_scan_with_lookahead() {
232-
let (mut txout_index, external_desc, _) = init_txout_index(10, 10);
222+
let (mut txout_index, external_desc, _) = init_txout_index(10);
233223

234224
let spks: BTreeMap<u32, ScriptBuf> = [0, 10, 20, 30]
235225
.into_iter()
@@ -283,7 +273,7 @@ fn test_scan_with_lookahead() {
283273
#[test]
284274
#[rustfmt::skip]
285275
fn test_wildcard_derivations() {
286-
let (mut txout_index, external_desc, _) = init_txout_index(0, 0);
276+
let (mut txout_index, external_desc, _) = init_txout_index(0);
287277
let external_spk_0 = external_desc.at_derivation_index(0).unwrap().script_pubkey();
288278
let external_spk_16 = external_desc.at_derivation_index(16).unwrap().script_pubkey();
289279
let external_spk_26 = external_desc.at_derivation_index(26).unwrap().script_pubkey();
@@ -341,7 +331,7 @@ fn test_wildcard_derivations() {
341331

342332
#[test]
343333
fn test_non_wildcard_derivations() {
344-
let mut txout_index = KeychainTxOutIndex::<TestKeychain>::default();
334+
let mut txout_index = KeychainTxOutIndex::<TestKeychain>::new(0);
345335

346336
let secp = bitcoin::secp256k1::Secp256k1::signing_only();
347337
let (no_wildcard_descriptor, _) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "wpkh([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/0)").unwrap();
@@ -350,7 +340,7 @@ fn test_non_wildcard_derivations() {
350340
.unwrap()
351341
.script_pubkey();
352342

353-
txout_index.add_keychain_with_lookahead(TestKeychain::External, no_wildcard_descriptor, 0);
343+
txout_index.add_keychain(TestKeychain::External, no_wildcard_descriptor);
354344

355345
// given:
356346
// - `txout_index` with no stored scripts

0 commit comments

Comments
 (0)