Merge bitcoindevkit/bdk#1229: Use a universal lookahead value for KeychainTxOutIndex and have a reasonable default

1def76f1f17fc876534c798883099c58de57c0fd chore: make clippy happy and bump clippy msrv (志宇)
c9467dcbb27bd497006846dcfcefb0c2cf8823c4 chore: improve documentation of lookahead (LLFourn)
bc796f412acdc3d2cd96f7aec0f24fa47c1fe889 fix(example): bitcoind_rpc_polling now initializes local_chain properly (志宇)
4fd539b6470f7f771e3b5e09e3287952fa7a1825 feat(chain)!: `KeychainTxOutIndex` uses a universal lookahead (Antoine Poinsot)

Pull request description:

  ### Description

  The `bdk::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.

  To fix this, we have also decided to change `KeychainTxOutIndex` to have a default lookahead. Additionally, we have simplified the `KeychainTxOutIndex` API to have a single `lookahead` that is ONLY set at construction `KeychainTxOutIndex::new(lookahead: u32) -> Self`. This avoids the footguns of having methods which allows the caller to decrease the `lookahead` (which will panic).

  ### Notes to the reviewers

  ~A way to set this value externally is introduced in #1172. This PR only aims to use a saner default than 0. `1_000` is the value used by the Bitcoin Core wallet, and that seems reasonable to me.~

  Edit: we should NOT allow setting the `lookahead` value after-the-fact. Instead, the `lookahead` should be provided to the wallet's constructor.

  @evanlinjin: I don't think additional tests are necessary as no additional features are added, and the surface area of the API is decreased. The original tests already thoroughly test the `lookahead` concept.

  ### Checklists

  #### All Submissions:

  *(This section was updated as this PR changed from being a simple setting to introducing a new method.)*

  * [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
  ~* [ ] I've added tests~
  * [x] I've added docs

ACKs for top commit:
  LLFourn:
    ACK 1def76f1f17fc876534c798883099c58de57c0fd

Tree-SHA512: b4c3be8a4f2ac4877cf3f05852147e7dd1daeb02d3bc40895f02fd2a58e584f1dc0735b524153ff0875380ac93c0b4c31e516873d7a9b0027fdbbb5fe7970ff2
This commit is contained in:
志宇 2023-12-29 20:38:53 +08:00
commit 7eff024213
No known key found for this signature in database
GPG Key ID: F6345C9837C2BDE8
14 changed files with 86 additions and 141 deletions

View File

@ -118,7 +118,7 @@ jobs:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
- uses: actions-rs/toolchain@v1 - uses: actions-rs/toolchain@v1
with: with:
toolchain: "stable" toolchain: stable
components: clippy components: clippy
override: true override: true
- name: Rust Cache - name: Rust Cache

View File

@ -1 +1 @@
msrv="1.57.0" msrv="1.63.0"

View File

@ -575,7 +575,7 @@ mod test {
if let ExtendedDescriptor::Pkh(pkh) = xdesc.0 { if let ExtendedDescriptor::Pkh(pkh) = xdesc.0 {
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().unwrap().into(); let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().unwrap().into();
let purpose = path.get(0).unwrap(); let purpose = path.first().unwrap();
assert_matches!(purpose, Hardened { index: 44 }); assert_matches!(purpose, Hardened { index: 44 });
let coin_type = path.get(1).unwrap(); let coin_type = path.get(1).unwrap();
assert_matches!(coin_type, Hardened { index: 0 }); assert_matches!(coin_type, Hardened { index: 0 });
@ -589,7 +589,7 @@ mod test {
if let ExtendedDescriptor::Pkh(pkh) = tdesc.0 { if let ExtendedDescriptor::Pkh(pkh) = tdesc.0 {
let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().unwrap().into(); let path: Vec<ChildNumber> = pkh.into_inner().full_derivation_path().unwrap().into();
let purpose = path.get(0).unwrap(); let purpose = path.first().unwrap();
assert_matches!(purpose, Hardened { index: 44 }); assert_matches!(purpose, Hardened { index: 44 });
let coin_type = path.get(1).unwrap(); let coin_type = path.get(1).unwrap();
assert_matches!(coin_type, Hardened { index: 1 }); assert_matches!(coin_type, Hardened { index: 1 });

View File

@ -812,9 +812,10 @@ pub struct SignOptions {
} }
/// Customize which taproot script-path leaves the signer should sign. /// Customize which taproot script-path leaves the signer should sign.
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Default, Debug, Clone, PartialEq, Eq)]
pub enum TapLeavesOptions { pub enum TapLeavesOptions {
/// The signer will sign all the leaves it has a key for. /// The signer will sign all the leaves it has a key for.
#[default]
All, All,
/// The signer won't sign leaves other than the ones specified. Note that it could still ignore /// The signer won't sign leaves other than the ones specified. Note that it could still ignore
/// some of the specified leaves, if it doesn't have the right key to sign them. /// some of the specified leaves, if it doesn't have the right key to sign them.
@ -825,12 +826,6 @@ pub enum TapLeavesOptions {
None, None,
} }
impl Default for TapLeavesOptions {
fn default() -> Self {
TapLeavesOptions::All
}
}
#[allow(clippy::derivable_impls)] #[allow(clippy::derivable_impls)]
impl Default for SignOptions { impl Default for SignOptions {
fn default() -> Self { fn default() -> Self {

View File

@ -811,9 +811,10 @@ impl<'a, D> TxBuilder<'a, D, DefaultCoinSelectionAlgorithm, BumpFee> {
} }
/// Ordering of the transaction's inputs and outputs /// Ordering of the transaction's inputs and outputs
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)] #[derive(Default, Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
pub enum TxOrdering { pub enum TxOrdering {
/// Randomized (default) /// Randomized (default)
#[default]
Shuffle, Shuffle,
/// Unchanged /// Unchanged
Untouched, Untouched,
@ -821,12 +822,6 @@ pub enum TxOrdering {
Bip69Lexicographic, Bip69Lexicographic,
} }
impl Default for TxOrdering {
fn default() -> Self {
TxOrdering::Shuffle
}
}
impl TxOrdering { impl TxOrdering {
/// Sort transaction inputs and outputs by [`TxOrdering`] variant /// Sort transaction inputs and outputs by [`TxOrdering`] variant
pub fn sort_tx(&self, tx: &mut Transaction) { pub fn sort_tx(&self, tx: &mut Transaction) {
@ -880,9 +875,10 @@ impl RbfValue {
} }
/// Policy regarding the use of change outputs when creating a transaction /// Policy regarding the use of change outputs when creating a transaction
#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)] #[derive(Default, Debug, Ord, PartialOrd, Eq, PartialEq, Hash, Clone, Copy)]
pub enum ChangeSpendPolicy { pub enum ChangeSpendPolicy {
/// Use both change and non-change outputs (default) /// Use both change and non-change outputs (default)
#[default]
ChangeAllowed, ChangeAllowed,
/// Only use change outputs (see [`TxBuilder::only_spend_change`]) /// Only use change outputs (see [`TxBuilder::only_spend_change`])
OnlyChange, OnlyChange,
@ -890,12 +886,6 @@ pub enum ChangeSpendPolicy {
ChangeForbidden, ChangeForbidden,
} }
impl Default for ChangeSpendPolicy {
fn default() -> Self {
ChangeSpendPolicy::ChangeAllowed
}
}
impl ChangeSpendPolicy { impl ChangeSpendPolicy {
pub(crate) fn is_satisfied_by(&self, utxo: &LocalOutput) -> bool { pub(crate) fn is_satisfied_by(&self, utxo: &LocalOutput) -> bool {
match self { match self {

View File

@ -5,12 +5,13 @@ use crate::{
spk_iter::BIP32_MAX_INDEX, spk_iter::BIP32_MAX_INDEX,
SpkIterator, SpkTxOutIndex, SpkIterator, SpkTxOutIndex,
}; };
use alloc::vec::Vec;
use bitcoin::{OutPoint, Script, TxOut}; use bitcoin::{OutPoint, Script, TxOut};
use core::{fmt::Debug, ops::Deref}; use core::{fmt::Debug, ops::Deref};
use crate::Append; use crate::Append;
const DEFAULT_LOOKAHEAD: u32 = 1_000;
/// A convenient wrapper around [`SpkTxOutIndex`] that relates script pubkeys to miniscript public /// A convenient wrapper around [`SpkTxOutIndex`] that relates script pubkeys to miniscript public
/// [`Descriptor`]s. /// [`Descriptor`]s.
/// ///
@ -46,7 +47,7 @@ use crate::Append;
/// # let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only(); /// # 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 (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 (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::External, external_descriptor);
/// txout_index.add_keychain(MyKeychain::Internal, internal_descriptor); /// txout_index.add_keychain(MyKeychain::Internal, internal_descriptor);
/// txout_index.add_keychain(MyKeychain::MyAppUser { user_id: 42 }, descriptor_for_user_42); /// txout_index.add_keychain(MyKeychain::MyAppUser { user_id: 42 }, descriptor_for_user_42);
@ -65,17 +66,12 @@ pub struct KeychainTxOutIndex<K> {
// last revealed indexes // last revealed indexes
last_revealed: BTreeMap<K, u32>, last_revealed: BTreeMap<K, u32>,
// lookahead settings for each keychain // lookahead settings for each keychain
lookahead: BTreeMap<K, u32>, lookahead: u32,
} }
impl<K> Default for KeychainTxOutIndex<K> { impl<K> Default for KeychainTxOutIndex<K> {
fn default() -> Self { fn default() -> Self {
Self { Self::new(DEFAULT_LOOKAHEAD)
inner: SpkTxOutIndex::default(),
keychains: BTreeMap::default(),
last_revealed: BTreeMap::default(),
lookahead: BTreeMap::default(),
}
} }
} }
@ -118,6 +114,25 @@ impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
} }
} }
impl<K> KeychainTxOutIndex<K> {
/// Construct a [`KeychainTxOutIndex`] with the given `lookahead`.
///
/// The `lookahead` is the number of script pubkeys to derive and cache from the internal
/// descriptors over and above the last revealed script index. Without a lookahead the index
/// will miss outputs you own when processing transactions whose output script pubkeys lie
/// beyond the last revealed index. In certain situations, such as when performing an initial
/// scan of the blockchain during wallet import, it may be uncertain or unknown what the index
/// of the last revealed script pubkey actually is.
pub fn new(lookahead: u32) -> Self {
Self {
inner: SpkTxOutIndex::default(),
keychains: BTreeMap::new(),
last_revealed: BTreeMap::new(),
lookahead,
}
}
}
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> { impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
/// Return a reference to the internal [`SpkTxOutIndex`]. /// Return a reference to the internal [`SpkTxOutIndex`].
pub fn inner(&self) -> &SpkTxOutIndex<(K, u32)> { pub fn inner(&self) -> &SpkTxOutIndex<(K, u32)> {
@ -145,54 +160,22 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
pub fn add_keychain(&mut self, keychain: K, descriptor: Descriptor<DescriptorPublicKey>) { pub fn add_keychain(&mut self, keychain: K, descriptor: Descriptor<DescriptorPublicKey>) {
let old_descriptor = &*self let old_descriptor = &*self
.keychains .keychains
.entry(keychain) .entry(keychain.clone())
.or_insert_with(|| descriptor.clone()); .or_insert_with(|| descriptor.clone());
assert_eq!( assert_eq!(
&descriptor, old_descriptor, &descriptor, old_descriptor,
"keychain already contains a different descriptor" "keychain already contains a different descriptor"
); );
self.replenish_lookahead(&keychain, self.lookahead);
} }
/// Return the lookahead setting for each keychain. /// Get the lookahead setting.
/// ///
/// Refer to [`set_lookahead`] for a deeper explanation of the `lookahead`. /// Refer to [`new`] for more information on the `lookahead`.
/// ///
/// [`set_lookahead`]: Self::set_lookahead /// [`new`]: Self::new
pub fn lookaheads(&self) -> &BTreeMap<K, u32> { pub fn lookahead(&self) -> u32 {
&self.lookahead self.lookahead
}
/// Convenience method to call [`set_lookahead`] for all keychains.
///
/// [`set_lookahead`]: Self::set_lookahead
pub fn set_lookahead_for_all(&mut self, lookahead: u32) {
for keychain in &self.keychains.keys().cloned().collect::<Vec<_>>() {
self.set_lookahead(keychain, lookahead);
}
}
/// Set the lookahead count for `keychain`.
///
/// The lookahead is the number of scripts to cache ahead of the last revealed script index. This
/// is useful to find outputs you own when processing block data that lie beyond the last revealed
/// index. In certain situations, such as when performing an initial scan of the blockchain during
/// wallet import, it may be uncertain or unknown what the last revealed index is.
///
/// # Panics
///
/// This will panic if the `keychain` does not exist.
pub fn set_lookahead(&mut self, keychain: &K, lookahead: u32) {
self.lookahead.insert(keychain.clone(), lookahead);
self.replenish_lookahead(keychain);
}
/// Convenience method to call [`lookahead_to_target`] for multiple keychains.
///
/// [`lookahead_to_target`]: Self::lookahead_to_target
pub fn lookahead_to_target_multi(&mut self, target_indexes: BTreeMap<K, u32>) {
for (keychain, target_index) in target_indexes {
self.lookahead_to_target(&keychain, target_index)
}
} }
/// Store lookahead scripts until `target_index`. /// Store lookahead scripts until `target_index`.
@ -201,22 +184,14 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
pub fn lookahead_to_target(&mut self, keychain: &K, target_index: u32) { pub fn lookahead_to_target(&mut self, keychain: &K, target_index: u32) {
let next_index = self.next_store_index(keychain); let next_index = self.next_store_index(keychain);
if let Some(temp_lookahead) = target_index.checked_sub(next_index).filter(|&v| v > 0) { if let Some(temp_lookahead) = target_index.checked_sub(next_index).filter(|&v| v > 0) {
let old_lookahead = self.lookahead.insert(keychain.clone(), temp_lookahead); self.replenish_lookahead(keychain, temp_lookahead);
self.replenish_lookahead(keychain);
// revert
match old_lookahead {
Some(lookahead) => self.lookahead.insert(keychain.clone(), lookahead),
None => self.lookahead.remove(keychain),
};
} }
} }
fn replenish_lookahead(&mut self, keychain: &K) { fn replenish_lookahead(&mut self, keychain: &K, lookahead: u32) {
let descriptor = self.keychains.get(keychain).expect("keychain must exist"); let descriptor = self.keychains.get(keychain).expect("keychain must exist");
let next_store_index = self.next_store_index(keychain); let next_store_index = self.next_store_index(keychain);
let next_reveal_index = self.last_revealed.get(keychain).map_or(0, |v| *v + 1); 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);
for (new_index, new_spk) in for (new_index, new_spk) in
SpkIterator::new_with_range(descriptor, next_store_index..next_reveal_index + lookahead) SpkIterator::new_with_range(descriptor, next_store_index..next_reveal_index + lookahead)
@ -388,12 +363,8 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
let target_index = if has_wildcard { target_index } else { 0 }; let target_index = if has_wildcard { target_index } else { 0 };
let next_reveal_index = self.last_revealed.get(keychain).map_or(0, |v| *v + 1); 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!( debug_assert!(next_reveal_index + self.lookahead >= self.next_store_index(keychain));
next_reveal_index + lookahead,
self.next_store_index(keychain)
);
// if we need to reveal new indices, the latest revealed index goes here // if we need to reveal new indices, the latest revealed index goes here
let mut reveal_to_index = None; let mut reveal_to_index = None;
@ -401,12 +372,12 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
// if the target is not yet revealed, but is already stored (due to lookahead), we need to // if the target is not yet revealed, but is already stored (due to lookahead), we need to
// set the `reveal_to_index` as target here (as the `for` loop below only updates // set the `reveal_to_index` as target here (as the `for` loop below only updates
// `reveal_to_index` for indexes that are NOT stored) // `reveal_to_index` for indexes that are NOT stored)
if next_reveal_index <= target_index && target_index < next_reveal_index + lookahead { if next_reveal_index <= target_index && target_index < next_reveal_index + self.lookahead {
reveal_to_index = Some(target_index); reveal_to_index = Some(target_index);
} }
// we range over indexes that are not stored // we range over indexes that are not stored
let range = next_reveal_index + lookahead..=target_index + lookahead; let range = next_reveal_index + self.lookahead..=target_index + self.lookahead;
for (new_index, new_spk) in SpkIterator::new_with_range(descriptor, range) { for (new_index, new_spk) in SpkIterator::new_with_range(descriptor, range) {
let _inserted = self let _inserted = self
.inner .inner

View File

@ -148,7 +148,7 @@ mod test {
Descriptor<DescriptorPublicKey>, Descriptor<DescriptorPublicKey>,
Descriptor<DescriptorPublicKey>, Descriptor<DescriptorPublicKey>,
) { ) {
let mut txout_index = KeychainTxOutIndex::<TestKeychain>::default(); let mut txout_index = KeychainTxOutIndex::<TestKeychain>::new(0);
let secp = Secp256k1::signing_only(); let secp = Secp256k1::signing_only();
let (external_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap(); let (external_descriptor,_) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)").unwrap();

View File

@ -168,9 +168,7 @@ impl<I: Clone + Ord> SpkTxOutIndex<I> {
/// ///
/// Returns `None` if the `TxOut` hasn't been scanned or if nothing matching was found there. /// Returns `None` if the `TxOut` hasn't been scanned or if nothing matching was found there.
pub fn txout(&self, outpoint: OutPoint) -> Option<(&I, &TxOut)> { pub fn txout(&self, outpoint: OutPoint) -> Option<(&I, &TxOut)> {
self.txouts self.txouts.get(&outpoint).map(|v| (&v.0, &v.1))
.get(&outpoint)
.map(|(spk_i, txout)| (spk_i, txout))
} }
/// Returns the script that has been inserted at the `index`. /// Returns the script that has been inserted at the `index`.

View File

@ -581,10 +581,7 @@ impl<A: Clone + Ord> TxGraph<A> {
} }
for (outpoint, txout) in changeset.txouts { for (outpoint, txout) in changeset.txouts {
let tx_entry = self let tx_entry = self.txs.entry(outpoint.txid).or_default();
.txs
.entry(outpoint.txid)
.or_insert_with(Default::default);
match tx_entry { match tx_entry {
(TxNodeInternal::Whole(_), _, _) => { /* do nothing since we already have full tx */ (TxNodeInternal::Whole(_), _, _) => { /* do nothing since we already have full tx */
@ -597,13 +594,13 @@ impl<A: Clone + Ord> TxGraph<A> {
for (anchor, txid) in changeset.anchors { for (anchor, txid) in changeset.anchors {
if self.anchors.insert((anchor.clone(), txid)) { if self.anchors.insert((anchor.clone(), txid)) {
let (_, anchors, _) = self.txs.entry(txid).or_insert_with(Default::default); let (_, anchors, _) = self.txs.entry(txid).or_default();
anchors.insert(anchor); anchors.insert(anchor);
} }
} }
for (txid, new_last_seen) in changeset.last_seen { for (txid, new_last_seen) in changeset.last_seen {
let (_, _, last_seen) = self.txs.entry(txid).or_insert_with(Default::default); let (_, _, last_seen) = self.txs.entry(txid).or_default();
if new_last_seen > *last_seen { if new_last_seen > *last_seen {
*last_seen = new_last_seen; *last_seen = new_last_seen;
} }

View File

@ -1,4 +1,5 @@
mod tx_template; mod tx_template;
#[allow(unused_imports)]
pub use tx_template::*; pub use tx_template::*;
#[allow(unused_macros)] #[allow(unused_macros)]

View File

@ -27,9 +27,10 @@ fn insert_relevant_txs() {
let spk_0 = descriptor.at_derivation_index(0).unwrap().script_pubkey(); let spk_0 = descriptor.at_derivation_index(0).unwrap().script_pubkey();
let spk_1 = descriptor.at_derivation_index(9).unwrap().script_pubkey(); let spk_1 = descriptor.at_derivation_index(9).unwrap().script_pubkey();
let mut graph = IndexedTxGraph::<ConfirmationHeightAnchor, KeychainTxOutIndex<()>>::default(); let mut graph = IndexedTxGraph::<ConfirmationHeightAnchor, KeychainTxOutIndex<()>>::new(
KeychainTxOutIndex::new(10),
);
graph.index.add_keychain((), descriptor); graph.index.add_keychain((), descriptor);
graph.index.set_lookahead(&(), 10);
let tx_a = Transaction { let tx_a = Transaction {
output: vec![ output: vec![
@ -118,12 +119,12 @@ fn test_list_owned_txouts() {
let (desc_1, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/0/*)").unwrap(); let (desc_1, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/0/*)").unwrap();
let (desc_2, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/1/*)").unwrap(); let (desc_2, _) = Descriptor::parse_descriptor(&Secp256k1::signing_only(), "tr(tprv8ZgxMBicQKsPd3krDUsBAmtnRsK3rb8u5yi1zhQgMhF1tR8MW7xfE4rnrbbsrbPR52e7rKapu6ztw1jXveJSCGHEriUGZV7mCe88duLp5pj/86'/1'/0'/1/*)").unwrap();
let mut graph = let mut graph = IndexedTxGraph::<ConfirmationHeightAnchor, KeychainTxOutIndex<String>>::new(
IndexedTxGraph::<ConfirmationHeightAnchor, KeychainTxOutIndex<String>>::default(); KeychainTxOutIndex::new(10),
);
graph.index.add_keychain("keychain_1".into(), desc_1); graph.index.add_keychain("keychain_1".into(), desc_1);
graph.index.add_keychain("keychain_2".into(), desc_2); graph.index.add_keychain("keychain_2".into(), desc_2);
graph.index.set_lookahead_for_all(10);
// Get trusted and untrusted addresses // Get trusted and untrusted addresses

View File

@ -18,12 +18,14 @@ enum TestKeychain {
Internal, Internal,
} }
fn init_txout_index() -> ( fn init_txout_index(
lookahead: u32,
) -> (
bdk_chain::keychain::KeychainTxOutIndex<TestKeychain>, bdk_chain::keychain::KeychainTxOutIndex<TestKeychain>,
Descriptor<DescriptorPublicKey>, Descriptor<DescriptorPublicKey>,
Descriptor<DescriptorPublicKey>, Descriptor<DescriptorPublicKey>,
) { ) {
let mut txout_index = bdk_chain::keychain::KeychainTxOutIndex::<TestKeychain>::default(); let mut txout_index = bdk_chain::keychain::KeychainTxOutIndex::<TestKeychain>::new(lookahead);
let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only(); 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 (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
fn test_set_all_derivation_indices() { fn test_set_all_derivation_indices() {
use bdk_chain::indexed_tx_graph::Indexer; use bdk_chain::indexed_tx_graph::Indexer;
let (mut txout_index, _, _) = init_txout_index(); let (mut txout_index, _, _) = init_txout_index(0);
let derive_to: BTreeMap<_, _> = let derive_to: BTreeMap<_, _> =
[(TestKeychain::External, 12), (TestKeychain::Internal, 24)].into(); [(TestKeychain::External, 12), (TestKeychain::Internal, 24)].into();
assert_eq!( assert_eq!(
@ -64,19 +66,10 @@ fn test_set_all_derivation_indices() {
#[test] #[test]
fn test_lookahead() { fn test_lookahead() {
let (mut txout_index, external_desc, internal_desc) = init_txout_index(); let (mut txout_index, external_desc, internal_desc) = init_txout_index(10);
// 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);
// given: // given:
// - external lookahead set to 10 // - external lookahead set to 10
// - internal lookahead set to 20
// when: // when:
// - set external derivation index to value higher than last, but within the lookahead value // - set external derivation index to value higher than last, but within the lookahead value
// expect: // expect:
@ -97,7 +90,7 @@ fn test_lookahead() {
assert_eq!( assert_eq!(
txout_index.inner().all_spks().len(), txout_index.inner().all_spks().len(),
10 /* external lookahead */ + 10 /* external lookahead */ +
20 /* internal lookahead */ + 10 /* internal lookahead */ +
index as usize + 1 /* `derived` count */ index as usize + 1 /* `derived` count */
); );
assert_eq!( assert_eq!(
@ -127,7 +120,7 @@ fn test_lookahead() {
} }
// given: // given:
// - internal lookahead is 20 // - internal lookahead is 10
// - internal derivation index is `None` // - internal derivation index is `None`
// when: // when:
// - derivation index is set ahead of current derivation index + lookahead // - derivation index is set ahead of current derivation index + lookahead
@ -148,7 +141,7 @@ fn test_lookahead() {
assert_eq!( assert_eq!(
txout_index.inner().all_spks().len(), txout_index.inner().all_spks().len(),
10 /* external lookahead */ + 10 /* external lookahead */ +
20 /* internal lookahead */ + 10 /* internal lookahead */ +
20 /* external stored index count */ + 20 /* external stored index count */ +
25 /* internal stored index count */ 25 /* internal stored index count */
); );
@ -226,8 +219,7 @@ fn test_lookahead() {
// - last used index should change as expected // - last used index should change as expected
#[test] #[test]
fn test_scan_with_lookahead() { fn test_scan_with_lookahead() {
let (mut txout_index, external_desc, _) = init_txout_index(); let (mut txout_index, external_desc, _) = init_txout_index(10);
txout_index.set_lookahead_for_all(10);
let spks: BTreeMap<u32, ScriptBuf> = [0, 10, 20, 30] let spks: BTreeMap<u32, ScriptBuf> = [0, 10, 20, 30]
.into_iter() .into_iter()
@ -281,7 +273,7 @@ fn test_scan_with_lookahead() {
#[test] #[test]
#[rustfmt::skip] #[rustfmt::skip]
fn test_wildcard_derivations() { fn test_wildcard_derivations() {
let (mut txout_index, external_desc, _) = init_txout_index(); let (mut txout_index, external_desc, _) = init_txout_index(0);
let external_spk_0 = external_desc.at_derivation_index(0).unwrap().script_pubkey(); 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_16 = external_desc.at_derivation_index(16).unwrap().script_pubkey();
let external_spk_26 = external_desc.at_derivation_index(26).unwrap().script_pubkey(); let external_spk_26 = external_desc.at_derivation_index(26).unwrap().script_pubkey();
@ -339,7 +331,7 @@ fn test_wildcard_derivations() {
#[test] #[test]
fn test_non_wildcard_derivations() { fn test_non_wildcard_derivations() {
let mut txout_index = KeychainTxOutIndex::<TestKeychain>::default(); let mut txout_index = KeychainTxOutIndex::<TestKeychain>::new(0);
let secp = bitcoin::secp256k1::Secp256k1::signing_only(); let secp = bitcoin::secp256k1::Secp256k1::signing_only();
let (no_wildcard_descriptor, _) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "wpkh([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/0)").unwrap(); let (no_wildcard_descriptor, _) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, "wpkh([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/1/0)").unwrap();

View File

@ -477,7 +477,7 @@ fn populate_with_txids(
let spk = tx let spk = tx
.output .output
.get(0) .first()
.map(|txo| &txo.script_pubkey) .map(|txo| &txo.script_pubkey)
.expect("tx must have an output"); .expect("tx must have an output");

View File

@ -12,7 +12,7 @@ use bdk_bitcoind_rpc::{
Emitter, Emitter,
}; };
use bdk_chain::{ use bdk_chain::{
bitcoin::{Block, Transaction}, bitcoin::{constants::genesis_block, Block, Transaction},
indexed_tx_graph, keychain, indexed_tx_graph, keychain,
local_chain::{self, CheckPoint, LocalChain}, local_chain::{self, CheckPoint, LocalChain},
ConfirmationTimeHeightAnchor, IndexedTxGraph, ConfirmationTimeHeightAnchor, IndexedTxGraph,
@ -64,9 +64,6 @@ struct RpcArgs {
/// Starting block height to fallback to if no point of agreement if found /// Starting block height to fallback to if no point of agreement if found
#[clap(env = "FALLBACK_HEIGHT", long, default_value = "0")] #[clap(env = "FALLBACK_HEIGHT", long, default_value = "0")]
fallback_height: u32, fallback_height: u32,
/// The unused-scripts lookahead will be kept at this size
#[clap(long, default_value = "10")]
lookahead: u32,
} }
impl From<RpcArgs> for Auth { impl From<RpcArgs> for Auth {
@ -120,10 +117,11 @@ fn main() -> anyhow::Result<()> {
"[{:>10}s] loaded initial changeset from db", "[{:>10}s] loaded initial changeset from db",
start.elapsed().as_secs_f32() start.elapsed().as_secs_f32()
); );
let (init_chain_changeset, init_graph_changeset) = init_changeset;
let graph = Mutex::new({ let graph = Mutex::new({
let mut graph = IndexedTxGraph::new(index); let mut graph = IndexedTxGraph::new(index);
graph.apply_changeset(init_changeset.1); graph.apply_changeset(init_graph_changeset);
graph graph
}); });
println!( println!(
@ -131,7 +129,16 @@ fn main() -> anyhow::Result<()> {
start.elapsed().as_secs_f32() start.elapsed().as_secs_f32()
); );
let chain = Mutex::new(LocalChain::from_changeset(init_changeset.0)?); let chain = Mutex::new(if init_chain_changeset.is_empty() {
let genesis_hash = genesis_block(args.network).block_hash();
let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash);
let mut db = db.lock().unwrap();
db.stage((chain_changeset, Default::default()));
db.commit()?;
chain
} else {
LocalChain::from_changeset(init_chain_changeset)?
});
println!( println!(
"[{:>10}s] loaded local chain from changeset", "[{:>10}s] loaded local chain from changeset",
start.elapsed().as_secs_f32() start.elapsed().as_secs_f32()
@ -161,13 +168,9 @@ fn main() -> anyhow::Result<()> {
match rpc_cmd { match rpc_cmd {
RpcCommands::Sync { rpc_args } => { RpcCommands::Sync { rpc_args } => {
let RpcArgs { let RpcArgs {
fallback_height, fallback_height, ..
lookahead,
..
} = rpc_args; } = rpc_args;
graph.lock().unwrap().index.set_lookahead_for_all(lookahead);
let chain_tip = chain.lock().unwrap().tip(); let chain_tip = chain.lock().unwrap().tip();
let rpc_client = rpc_args.new_client()?; let rpc_client = rpc_args.new_client()?;
let mut emitter = Emitter::new(&rpc_client, chain_tip, fallback_height); let mut emitter = Emitter::new(&rpc_client, chain_tip, fallback_height);
@ -233,13 +236,10 @@ fn main() -> anyhow::Result<()> {
} }
RpcCommands::Live { rpc_args } => { RpcCommands::Live { rpc_args } => {
let RpcArgs { let RpcArgs {
fallback_height, fallback_height, ..
lookahead,
..
} = rpc_args; } = rpc_args;
let sigterm_flag = start_ctrlc_handler(); let sigterm_flag = start_ctrlc_handler();
graph.lock().unwrap().index.set_lookahead_for_all(lookahead);
let last_cp = chain.lock().unwrap().tip(); let last_cp = chain.lock().unwrap().tip();
println!( println!(