Skip to content

Commit

Permalink
chain: add a 'lookahead' parameter to add_keychain
Browse files Browse the repository at this point in the history
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 the wallet, make the interface
foolproof by requiring any caller of `add_keychain` to set a lookahead
value. (h/t evanlinjin for the suggestion.)
  • Loading branch information
darosior authored and evanlinjin committed Dec 21, 2023
1 parent b5612f2 commit ec7d716
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 31 deletions.
38 changes: 31 additions & 7 deletions crates/chain/src/keychain/txout_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ use core::{fmt::Debug, ops::Deref};

use crate::Append;

const DEFAULT_LOOKAHEAD: u32 = 1_000;

/// A convenient wrapper around [`SpkTxOutIndex`] that relates script pubkeys to miniscript public
/// [`Descriptor`]s.
///
Expand Down Expand Up @@ -46,7 +48,7 @@ use crate::Append;
/// # let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only();
/// # let (external_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
/// # let (internal_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/*)").unwrap();
/// # let descriptor_for_user_42 = external_descriptor.clone();
/// # let (descriptor_for_user_42, _) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/2/*)").unwrap();
/// txout_index.add_keychain(MyKeychain::External, external_descriptor);
/// txout_index.add_keychain(MyKeychain::Internal, internal_descriptor);
/// txout_index.add_keychain(MyKeychain::MyAppUser { user_id: 42 }, descriptor_for_user_42);
Expand Down Expand Up @@ -134,23 +136,48 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
&self.keychains
}

/// Add a keychain to the tracker's `txout_index` with a descriptor to derive addresses and a
/// default lookahead of `1_000`.
///
/// See [`add_keychain_with_lookahead`] for details.
///
/// # Panics
///
/// This will panic if a different `descriptor` is introduced to the same `keychain`.
///
/// [`add_keychain_with_lookahead`]: Self::add_keychain_with_lookahead
pub fn add_keychain(&mut self, keychain: K, descriptor: Descriptor<DescriptorPublicKey>) {
self.add_keychain_with_lookahead(keychain, descriptor, DEFAULT_LOOKAHEAD)
}

/// Add a keychain to the tracker's `txout_index` with a descriptor to derive addresses.
///
/// Adding a keychain means you will be able to derive new script pubkeys under that keychain
/// and the txout index will discover transaction outputs with those script pubkeys.
///
/// Refer to [`set_lookahead`] for an explanation of the `lookahead` parameter.
///
/// # Panics
///
/// This will panic if a different `descriptor` is introduced to the same `keychain`.
pub fn add_keychain(&mut self, keychain: K, descriptor: Descriptor<DescriptorPublicKey>) {
///
/// [`set_lookahead`]: Self::set_lookahead
pub fn add_keychain_with_lookahead(
&mut self,
keychain: K,
descriptor: Descriptor<DescriptorPublicKey>,
lookahead: u32,
) {
let old_descriptor = &*self
.keychains
.entry(keychain)
.entry(keychain.clone())
.or_insert_with(|| descriptor.clone());
assert_eq!(
&descriptor, old_descriptor,
"keychain already contains a different descriptor"
);
self.lookahead.insert(keychain.clone(), lookahead);
self.replenish_lookahead(&keychain);
}

/// Return the lookahead setting for each keychain.
Expand Down Expand Up @@ -390,10 +417,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
let next_reveal_index = self.last_revealed.get(keychain).map_or(0, |v| *v + 1);
let lookahead = self.lookahead.get(keychain).map_or(0, |v| *v);

debug_assert_eq!(
next_reveal_index + lookahead,
self.next_store_index(keychain)
);
debug_assert!(next_reveal_index + lookahead >= self.next_store_index(keychain));

// if we need to reveal new indices, the latest revealed index goes here
let mut reveal_to_index = None;
Expand Down
12 changes: 10 additions & 2 deletions crates/chain/src/spk_iter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,16 @@ mod test {
let (external_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
let (internal_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/*)").unwrap();

txout_index.add_keychain(TestKeychain::External, external_descriptor.clone());
txout_index.add_keychain(TestKeychain::Internal, internal_descriptor.clone());
txout_index.add_keychain_with_lookahead(
TestKeychain::External,
external_descriptor.clone(),
0,
);
txout_index.add_keychain_with_lookahead(
TestKeychain::Internal,
internal_descriptor.clone(),
0,
);

(txout_index, external_descriptor, internal_descriptor)
}
Expand Down
12 changes: 7 additions & 5 deletions crates/chain/tests/test_indexed_tx_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ fn insert_relevant_txs() {
let spk_1 = descriptor.at_derivation_index(9).unwrap().script_pubkey();

let mut graph = IndexedTxGraph::<ConfirmationHeightAnchor, KeychainTxOutIndex<()>>::default();
graph.index.add_keychain((), descriptor);
graph.index.set_lookahead(&(), 10);
graph.index.add_keychain_with_lookahead((), descriptor, 10);

let tx_a = Transaction {
output: vec![
Expand Down Expand Up @@ -121,9 +120,12 @@ fn test_list_owned_txouts() {
let mut graph =
IndexedTxGraph::<ConfirmationHeightAnchor, KeychainTxOutIndex<String>>::default();

graph.index.add_keychain("keychain_1".into(), desc_1);
graph.index.add_keychain("keychain_2".into(), desc_2);
graph.index.set_lookahead_for_all(10);
graph
.index
.add_keychain_with_lookahead("keychain_1".into(), desc_1, 10);
graph
.index
.add_keychain_with_lookahead("keychain_2".into(), desc_2, 10);

// Get trusted and untrusted addresses

Expand Down
36 changes: 19 additions & 17 deletions crates/chain/tests/test_keychain_txout_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ enum TestKeychain {
Internal,
}

fn init_txout_index() -> (
fn init_txout_index(
internal_lookahead: u32,
external_lookahead: u32,
) -> (
bdk_chain::keychain::KeychainTxOutIndex<TestKeychain>,
Descriptor<DescriptorPublicKey>,
Descriptor<DescriptorPublicKey>,
Expand All @@ -29,8 +32,16 @@ fn init_txout_index() -> (
let (external_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();
let (internal_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/*)").unwrap();

txout_index.add_keychain(TestKeychain::External, external_descriptor.clone());
txout_index.add_keychain(TestKeychain::Internal, internal_descriptor.clone());
txout_index.add_keychain_with_lookahead(
TestKeychain::External,
external_descriptor.clone(),
internal_lookahead,
);
txout_index.add_keychain_with_lookahead(
TestKeychain::Internal,
internal_descriptor.clone(),
external_lookahead,
);

(txout_index, external_descriptor, internal_descriptor)
}
Expand All @@ -46,7 +57,7 @@ fn spk_at_index(descriptor: &Descriptor<DescriptorPublicKey>, index: u32) -> Scr
fn test_set_all_derivation_indices() {
use bdk_chain::indexed_tx_graph::Indexer;

let (mut txout_index, _, _) = init_txout_index();
let (mut txout_index, _, _) = init_txout_index(0, 0);
let derive_to: BTreeMap<_, _> =
[(TestKeychain::External, 12), (TestKeychain::Internal, 24)].into();
assert_eq!(
Expand All @@ -64,15 +75,7 @@ fn test_set_all_derivation_indices() {

#[test]
fn test_lookahead() {
let (mut txout_index, external_desc, internal_desc) = init_txout_index();

// ensure it does not break anything if lookahead is set multiple times
(0..=10).for_each(|lookahead| txout_index.set_lookahead(&TestKeychain::External, lookahead));
(0..=20)
.filter(|v| v % 2 == 0)
.for_each(|lookahead| txout_index.set_lookahead(&TestKeychain::Internal, lookahead));

assert_eq!(txout_index.inner().all_spks().len(), 30);
let (mut txout_index, external_desc, internal_desc) = init_txout_index(10, 20);

// given:
// - external lookahead set to 10
Expand Down Expand Up @@ -226,8 +229,7 @@ fn test_lookahead() {
// - last used index should change as expected
#[test]
fn test_scan_with_lookahead() {
let (mut txout_index, external_desc, _) = init_txout_index();
txout_index.set_lookahead_for_all(10);
let (mut txout_index, external_desc, _) = init_txout_index(10, 10);

let spks: BTreeMap<u32, ScriptBuf> = [0, 10, 20, 30]
.into_iter()
Expand Down Expand Up @@ -281,7 +283,7 @@ fn test_scan_with_lookahead() {
#[test]
#[rustfmt::skip]
fn test_wildcard_derivations() {
let (mut txout_index, external_desc, _) = init_txout_index();
let (mut txout_index, external_desc, _) = init_txout_index(0, 0);
let external_spk_0 = external_desc.at_derivation_index(0).unwrap().script_pubkey();
let external_spk_16 = external_desc.at_derivation_index(16).unwrap().script_pubkey();
let external_spk_26 = external_desc.at_derivation_index(26).unwrap().script_pubkey();
Expand Down Expand Up @@ -348,7 +350,7 @@ fn test_non_wildcard_derivations() {
.unwrap()
.script_pubkey();

txout_index.add_keychain(TestKeychain::External, no_wildcard_descriptor);
txout_index.add_keychain_with_lookahead(TestKeychain::External, no_wildcard_descriptor, 0);

// given:
// - `txout_index` with no stored scripts
Expand Down

0 comments on commit ec7d716

Please sign in to comment.