Skip to content

Commit 4fd539b

Browse files
darosiorevanlinjin
andcommitted
feat(chain)!: KeychainTxOutIndex uses a universal lookahead
The wallet is currently created without setting any lookahead value for the keychain. This implicitly makes it a lookahead of 0. As this is a high-level interface we should avoid footguns and aim for a reasonable default. Instead of simply patching it for wallet, we alter `KeychainTxOutIndex` to have a default lookahead value. Additionally, instead of a per-keychain lookahead, the constructor asks for a `lookahead` value. This avoids the footguns of having methods which allows the caller the decrease the `lookahead` (and therefore panicing). This also simplifies the API. Co-authored-by: Antoine Poisot <[email protected]> Co-authored-by: 志宇 <[email protected]>
1 parent 01698ae commit 4fd539b

File tree

5 files changed

+56
-103
lines changed

5 files changed

+56
-103
lines changed

crates/chain/src/keychain/txout_index.rs

Lines changed: 35 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ 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

1211
use crate::Append;
1312

13+
const DEFAULT_LOOKAHEAD: u32 = 1_000;
14+
1415
/// A convenient wrapper around [`SpkTxOutIndex`] that relates script pubkeys to miniscript public
1516
/// [`Descriptor`]s.
1617
///
@@ -46,7 +47,7 @@ use crate::Append;
4647
/// # let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only();
4748
/// # let (external_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
4849
/// # let (internal_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/*)").unwrap();
49-
/// # let descriptor_for_user_42 = external_descriptor.clone();
50+
/// # let (descriptor_for_user_42, _) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/2/*)").unwrap();
5051
/// txout_index.add_keychain(MyKeychain::External, external_descriptor);
5152
/// txout_index.add_keychain(MyKeychain::Internal, internal_descriptor);
5253
/// txout_index.add_keychain(MyKeychain::MyAppUser { user_id: 42 }, descriptor_for_user_42);
@@ -65,17 +66,12 @@ pub struct KeychainTxOutIndex<K> {
6566
// last revealed indexes
6667
last_revealed: BTreeMap<K, u32>,
6768
// lookahead settings for each keychain
68-
lookahead: BTreeMap<K, u32>,
69+
lookahead: u32,
6970
}
7071

7172
impl<K> Default for KeychainTxOutIndex<K> {
7273
fn default() -> Self {
73-
Self {
74-
inner: SpkTxOutIndex::default(),
75-
keychains: BTreeMap::default(),
76-
last_revealed: BTreeMap::default(),
77-
lookahead: BTreeMap::default(),
78-
}
74+
Self::new(DEFAULT_LOOKAHEAD)
7975
}
8076
}
8177

@@ -118,6 +114,24 @@ impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
118114
}
119115
}
120116

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+
121135
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
122136
/// Return a reference to the internal [`SpkTxOutIndex`].
123137
pub fn inner(&self) -> &SpkTxOutIndex<(K, u32)> {
@@ -145,54 +159,22 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
145159
pub fn add_keychain(&mut self, keychain: K, descriptor: Descriptor<DescriptorPublicKey>) {
146160
let old_descriptor = &*self
147161
.keychains
148-
.entry(keychain)
162+
.entry(keychain.clone())
149163
.or_insert_with(|| descriptor.clone());
150164
assert_eq!(
151165
&descriptor, old_descriptor,
152166
"keychain already contains a different descriptor"
153167
);
168+
self.replenish_lookahead(&keychain, self.lookahead);
154169
}
155170

156-
/// Return the lookahead setting for each keychain.
171+
/// Get the lookahead setting.
157172
///
158-
/// Refer to [`set_lookahead`] for a deeper explanation of the `lookahead`.
173+
/// Refer to [`new`] for more information on the `lookahead`.
159174
///
160-
/// [`set_lookahead`]: Self::set_lookahead
161-
pub fn lookaheads(&self) -> &BTreeMap<K, u32> {
162-
&self.lookahead
163-
}
164-
165-
/// Convenience method to call [`set_lookahead`] for all keychains.
166-
///
167-
/// [`set_lookahead`]: Self::set_lookahead
168-
pub fn set_lookahead_for_all(&mut self, lookahead: u32) {
169-
for keychain in &self.keychains.keys().cloned().collect::<Vec<_>>() {
170-
self.set_lookahead(keychain, lookahead);
171-
}
172-
}
173-
174-
/// Set the lookahead count for `keychain`.
175-
///
176-
/// The lookahead is the number of scripts to cache ahead of the last revealed script index. This
177-
/// is useful to find outputs you own when processing block data that lie beyond the last revealed
178-
/// index. In certain situations, such as when performing an initial scan of the blockchain during
179-
/// wallet import, it may be uncertain or unknown what the last revealed index is.
180-
///
181-
/// # Panics
182-
///
183-
/// This will panic if the `keychain` does not exist.
184-
pub fn set_lookahead(&mut self, keychain: &K, lookahead: u32) {
185-
self.lookahead.insert(keychain.clone(), lookahead);
186-
self.replenish_lookahead(keychain);
187-
}
188-
189-
/// Convenience method to call [`lookahead_to_target`] for multiple keychains.
190-
///
191-
/// [`lookahead_to_target`]: Self::lookahead_to_target
192-
pub fn lookahead_to_target_multi(&mut self, target_indexes: BTreeMap<K, u32>) {
193-
for (keychain, target_index) in target_indexes {
194-
self.lookahead_to_target(&keychain, target_index)
195-
}
175+
/// [`new`]: Self::new
176+
pub fn lookahead(&self) -> u32 {
177+
self.lookahead
196178
}
197179

198180
/// Store lookahead scripts until `target_index`.
@@ -201,22 +183,14 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
201183
pub fn lookahead_to_target(&mut self, keychain: &K, target_index: u32) {
202184
let next_index = self.next_store_index(keychain);
203185
if let Some(temp_lookahead) = target_index.checked_sub(next_index).filter(|&v| v > 0) {
204-
let old_lookahead = self.lookahead.insert(keychain.clone(), temp_lookahead);
205-
self.replenish_lookahead(keychain);
206-
207-
// revert
208-
match old_lookahead {
209-
Some(lookahead) => self.lookahead.insert(keychain.clone(), lookahead),
210-
None => self.lookahead.remove(keychain),
211-
};
186+
self.replenish_lookahead(keychain, temp_lookahead);
212187
}
213188
}
214189

215-
fn replenish_lookahead(&mut self, keychain: &K) {
190+
fn replenish_lookahead(&mut self, keychain: &K, lookahead: u32) {
216191
let descriptor = self.keychains.get(keychain).expect("keychain must exist");
217192
let next_store_index = self.next_store_index(keychain);
218193
let next_reveal_index = self.last_revealed.get(keychain).map_or(0, |v| *v + 1);
219-
let lookahead = self.lookahead.get(keychain).map_or(0, |v| *v);
220194

221195
for (new_index, new_spk) in
222196
SpkIterator::new_with_range(descriptor, next_store_index..next_reveal_index + lookahead)
@@ -388,25 +362,21 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
388362

389363
let target_index = if has_wildcard { target_index } else { 0 };
390364
let next_reveal_index = self.last_revealed.get(keychain).map_or(0, |v| *v + 1);
391-
let lookahead = self.lookahead.get(keychain).map_or(0, |v| *v);
392365

393-
debug_assert_eq!(
394-
next_reveal_index + lookahead,
395-
self.next_store_index(keychain)
396-
);
366+
debug_assert!(next_reveal_index + self.lookahead >= self.next_store_index(keychain));
397367

398368
// if we need to reveal new indices, the latest revealed index goes here
399369
let mut reveal_to_index = None;
400370

401371
// if the target is not yet revealed, but is already stored (due to lookahead), we need to
402372
// set the `reveal_to_index` as target here (as the `for` loop below only updates
403373
// `reveal_to_index` for indexes that are NOT stored)
404-
if next_reveal_index <= target_index && target_index < next_reveal_index + lookahead {
374+
if next_reveal_index <= target_index && target_index < next_reveal_index + self.lookahead {
405375
reveal_to_index = Some(target_index);
406376
}
407377

408378
// we range over indexes that are not stored
409-
let range = next_reveal_index + lookahead..=target_index + lookahead;
379+
let range = next_reveal_index + self.lookahead..=target_index + self.lookahead;
410380
for (new_index, new_spk) in SpkIterator::new_with_range(descriptor, range) {
411381
let _inserted = self
412382
.inner

crates/chain/src/spk_iter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ 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();

crates/chain/tests/test_indexed_tx_graph.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +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();
30+
let mut graph = IndexedTxGraph::<ConfirmationHeightAnchor, KeychainTxOutIndex<()>>::new(
31+
KeychainTxOutIndex::new(10),
32+
);
3133
graph.index.add_keychain((), descriptor);
32-
graph.index.set_lookahead(&(), 10);
3334

3435
let tx_a = Transaction {
3536
output: vec![
@@ -118,12 +119,12 @@ fn test_list_owned_txouts() {
118119
let (desc_1, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/0/*)").unwrap();
119120
let (desc_2, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/1/*)").unwrap();
120121

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

124126
graph.index.add_keychain("keychain_1".into(), desc_1);
125127
graph.index.add_keychain("keychain_2".into(), desc_2);
126-
graph.index.set_lookahead_for_all(10);
127128

128129
// Get trusted and untrusted addresses
129130

crates/chain/tests/test_keychain_txout_index.rs

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,14 @@ enum TestKeychain {
1818
Internal,
1919
}
2020

21-
fn init_txout_index() -> (
21+
fn init_txout_index(
22+
lookahead: u32,
23+
) -> (
2224
bdk_chain::keychain::KeychainTxOutIndex<TestKeychain>,
2325
Descriptor<DescriptorPublicKey>,
2426
Descriptor<DescriptorPublicKey>,
2527
) {
26-
let mut txout_index = bdk_chain::keychain::KeychainTxOutIndex::<TestKeychain>::default();
28+
let mut txout_index = bdk_chain::keychain::KeychainTxOutIndex::<TestKeychain>::new(lookahead);
2729

2830
let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only();
2931
let (external_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
@@ -46,7 +48,7 @@ fn spk_at_index(descriptor: &Descriptor<DescriptorPublicKey>, index: u32) -> Scr
4648
fn test_set_all_derivation_indices() {
4749
use bdk_chain::indexed_tx_graph::Indexer;
4850

49-
let (mut txout_index, _, _) = init_txout_index();
51+
let (mut txout_index, _, _) = init_txout_index(0);
5052
let derive_to: BTreeMap<_, _> =
5153
[(TestKeychain::External, 12), (TestKeychain::Internal, 24)].into();
5254
assert_eq!(
@@ -64,19 +66,10 @@ fn test_set_all_derivation_indices() {
6466

6567
#[test]
6668
fn test_lookahead() {
67-
let (mut txout_index, external_desc, internal_desc) = init_txout_index();
68-
69-
// ensure it does not break anything if lookahead is set multiple times
70-
(0..=10).for_each(|lookahead| txout_index.set_lookahead(&TestKeychain::External, lookahead));
71-
(0..=20)
72-
.filter(|v| v % 2 == 0)
73-
.for_each(|lookahead| txout_index.set_lookahead(&TestKeychain::Internal, lookahead));
74-
75-
assert_eq!(txout_index.inner().all_spks().len(), 30);
69+
let (mut txout_index, external_desc, internal_desc) = init_txout_index(10);
7670

7771
// given:
7872
// - external lookahead set to 10
79-
// - internal lookahead set to 20
8073
// when:
8174
// - set external derivation index to value higher than last, but within the lookahead value
8275
// expect:
@@ -97,7 +90,7 @@ fn test_lookahead() {
9790
assert_eq!(
9891
txout_index.inner().all_spks().len(),
9992
10 /* external lookahead */ +
100-
20 /* internal lookahead */ +
93+
10 /* internal lookahead */ +
10194
index as usize + 1 /* `derived` count */
10295
);
10396
assert_eq!(
@@ -127,7 +120,7 @@ fn test_lookahead() {
127120
}
128121

129122
// given:
130-
// - internal lookahead is 20
123+
// - internal lookahead is 10
131124
// - internal derivation index is `None`
132125
// when:
133126
// - derivation index is set ahead of current derivation index + lookahead
@@ -148,7 +141,7 @@ fn test_lookahead() {
148141
assert_eq!(
149142
txout_index.inner().all_spks().len(),
150143
10 /* external lookahead */ +
151-
20 /* internal lookahead */ +
144+
10 /* internal lookahead */ +
152145
20 /* external stored index count */ +
153146
25 /* internal stored index count */
154147
);
@@ -226,8 +219,7 @@ fn test_lookahead() {
226219
// - last used index should change as expected
227220
#[test]
228221
fn test_scan_with_lookahead() {
229-
let (mut txout_index, external_desc, _) = init_txout_index();
230-
txout_index.set_lookahead_for_all(10);
222+
let (mut txout_index, external_desc, _) = init_txout_index(10);
231223

232224
let spks: BTreeMap<u32, ScriptBuf> = [0, 10, 20, 30]
233225
.into_iter()
@@ -281,7 +273,7 @@ fn test_scan_with_lookahead() {
281273
#[test]
282274
#[rustfmt::skip]
283275
fn test_wildcard_derivations() {
284-
let (mut txout_index, external_desc, _) = init_txout_index();
276+
let (mut txout_index, external_desc, _) = init_txout_index(0);
285277
let external_spk_0 = external_desc.at_derivation_index(0).unwrap().script_pubkey();
286278
let external_spk_16 = external_desc.at_derivation_index(16).unwrap().script_pubkey();
287279
let external_spk_26 = external_desc.at_derivation_index(26).unwrap().script_pubkey();
@@ -339,7 +331,7 @@ fn test_wildcard_derivations() {
339331

340332
#[test]
341333
fn test_non_wildcard_derivations() {
342-
let mut txout_index = KeychainTxOutIndex::<TestKeychain>::default();
334+
let mut txout_index = KeychainTxOutIndex::<TestKeychain>::new(0);
343335

344336
let secp = bitcoin::secp256k1::Secp256k1::signing_only();
345337
let (no_wildcard_descriptor, _) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "wpkh([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/0)").unwrap();

example-crates/example_bitcoind_rpc_polling/src/main.rs

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,6 @@ struct RpcArgs {
6464
/// Starting block height to fallback to if no point of agreement if found
6565
#[clap(env = "FALLBACK_HEIGHT", long, default_value = "0")]
6666
fallback_height: u32,
67-
/// The unused-scripts lookahead will be kept at this size
68-
#[clap(long, default_value = "10")]
69-
lookahead: u32,
7067
}
7168

7269
impl From<RpcArgs> for Auth {
@@ -161,13 +158,9 @@ fn main() -> anyhow::Result<()> {
161158
match rpc_cmd {
162159
RpcCommands::Sync { rpc_args } => {
163160
let RpcArgs {
164-
fallback_height,
165-
lookahead,
166-
..
161+
fallback_height, ..
167162
} = rpc_args;
168163

169-
graph.lock().unwrap().index.set_lookahead_for_all(lookahead);
170-
171164
let chain_tip = chain.lock().unwrap().tip();
172165
let rpc_client = rpc_args.new_client()?;
173166
let mut emitter = Emitter::new(&rpc_client, chain_tip, fallback_height);
@@ -233,13 +226,10 @@ fn main() -> anyhow::Result<()> {
233226
}
234227
RpcCommands::Live { rpc_args } => {
235228
let RpcArgs {
236-
fallback_height,
237-
lookahead,
238-
..
229+
fallback_height, ..
239230
} = rpc_args;
240231
let sigterm_flag = start_ctrlc_handler();
241232

242-
graph.lock().unwrap().index.set_lookahead_for_all(lookahead);
243233
let last_cp = chain.lock().unwrap().tip();
244234

245235
println!(

0 commit comments

Comments
 (0)