Skip to content

Commit 60abd87

Browse files
Merge #1269: Revamp KeychainTxOutIndex API to be safer
71fff16 feat(chain): add txout methods to `KeychainTxOutIndex` (志宇) 83e7b7e docs(chain): improve `KeychainTxOutIndex` docs (志宇) 9294e30 docs(wallet): improve docs for unbounded spk iterator methods (志宇) b74c2e2 fix(wallet): use efficient peek address logic (志宇) 81aeaba feat(chain): add `SpkIterator::descriptor` method (志宇) c7b47af refactor(chain)!: revamp `KeychainTxOutIndex` API (志宇) 705690e feat(chain): make output of `SpkTxOutIndex::unused_spks` cloneable (志宇) Pull request description: Closes #1268 ### Description Previously `SpkTxOutIndex` methods can be called from `KeychainTxOutIndex` due to the `DeRef` implementation. However, the internal `SpkTxOut` will also contain lookahead spks resulting in an error-prone API. `SpkTxOutIndex` methods are now not directly callable from `KeychainTxOutIndex`. Methods of `KeychainTxOutIndex` are renamed for clarity. I.e. methods that return an unbounded spk iter are prefixed with `unbounded`. In addition to this, I also optimized the peek-address logic of `bdk::Wallet` using the optimized `<SpkIterator as Iterator>::nth` implementation. ### Notes to the reviewers This is mostly refactoring, but can also be considered a bug-fix (as the API before was very problematic). ### Changelog notice Changed * Wallet's peek-address logic is optimized by making use of `<SpkIterator as Iterator>::nth`. * `KeychainTxOutIndex` API is refactored to better differentiate between methods that return unbounded vs stored spks. * `KeychainTxOutIndex` no longer directly exposes `SpkTxOutIndex` methods via `DeRef`. This was problematic because `SpkTxOutIndex` also contains lookahead spks which we want to hide. Added * `SpkIterator::descriptor` method which gets a reference to the internal descriptor. ### Checklists #### All Submissions: * [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 #### New Features: ~* [ ] I've added tests for the new feature~ * [x] I've added docs for the new feature #### Bugfixes: * [x] This pull request breaks the existing API * [ ] I've added tests to reproduce the issue which are now passing * [x] I'm linking the issue being fixed by this PR ACKs for top commit: danielabrozzoni: ACK 71fff16 Tree-SHA512: f29c7d2311d0e81c4fe29b8f57c219c24db958194fad5de82bb6d42d562d37fd5d152be7ee03a3f00843be5760569ad29b848250267a548d7d15320fd5292a8f
2 parents 40f0765 + 71fff16 commit 60abd87

File tree

12 files changed

+345
-172
lines changed

12 files changed

+345
-172
lines changed

crates/bdk/src/wallet/mod.rs

Lines changed: 53 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,11 @@ where
262262
/// Infallibly return a derived address using the external descriptor, see [`AddressIndex`] for
263263
/// available address index selection strategies. If none of the keys in the descriptor are derivable
264264
/// (i.e. does not end with /*) then the same address will always be returned for any [`AddressIndex`].
265+
///
266+
/// # Panics
267+
///
268+
/// This panics when the caller requests for an address of derivation index greater than the
269+
/// BIP32 max index.
265270
pub fn get_address(&mut self, address_index: AddressIndex) -> AddressInfo {
266271
self.try_get_address(address_index).unwrap()
267272
}
@@ -273,6 +278,11 @@ where
273278
/// see [`AddressIndex`] for available address index selection strategies. If none of the keys
274279
/// in the descriptor are derivable (i.e. does not end with /*) then the same address will always
275280
/// be returned for any [`AddressIndex`].
281+
///
282+
/// # Panics
283+
///
284+
/// This panics when the caller requests for an address of derivation index greater than the
285+
/// BIP32 max index.
276286
pub fn get_internal_address(&mut self, address_index: AddressIndex) -> AddressInfo {
277287
self.try_get_internal_address(address_index).unwrap()
278288
}
@@ -649,6 +659,11 @@ impl<D> Wallet<D> {
649659
///
650660
/// A `PersistBackend<ChangeSet>::WriteError` will result if unable to persist the new address
651661
/// to the `PersistBackend`.
662+
///
663+
/// # Panics
664+
///
665+
/// This panics when the caller requests for an address of derivation index greater than the
666+
/// BIP32 max index.
652667
pub fn try_get_address(
653668
&mut self,
654669
address_index: AddressIndex,
@@ -669,6 +684,11 @@ impl<D> Wallet<D> {
669684
/// see [`AddressIndex`] for available address index selection strategies. If none of the keys
670685
/// in the descriptor are derivable (i.e. does not end with /*) then the same address will always
671686
/// be returned for any [`AddressIndex`].
687+
///
688+
/// # Panics
689+
///
690+
/// This panics when the caller requests for an address of derivation index greater than the
691+
/// BIP32 max index.
672692
pub fn try_get_internal_address(
673693
&mut self,
674694
address_index: AddressIndex,
@@ -691,6 +711,11 @@ impl<D> Wallet<D> {
691711
/// See [`AddressIndex`] for available address index selection strategies. If none of the keys
692712
/// in the descriptor are derivable (i.e. does not end with /*) then the same address will
693713
/// always be returned for any [`AddressIndex`].
714+
///
715+
/// # Panics
716+
///
717+
/// This panics when the caller requests for an address of derivation index greater than the
718+
/// BIP32 max index.
694719
fn _get_address(
695720
&mut self,
696721
keychain: KeychainKind,
@@ -710,12 +735,14 @@ impl<D> Wallet<D> {
710735
let ((index, spk), index_changeset) = txout_index.next_unused_spk(&keychain);
711736
(index, spk.into(), Some(index_changeset))
712737
}
713-
AddressIndex::Peek(index) => {
714-
let (index, spk) = txout_index
715-
.spks_of_keychain(&keychain)
716-
.take(index as usize + 1)
717-
.last()
718-
.unwrap();
738+
AddressIndex::Peek(mut peek_index) => {
739+
let mut spk_iter = txout_index.unbounded_spk_iter(&keychain);
740+
if !spk_iter.descriptor().has_wildcard() {
741+
peek_index = 0;
742+
}
743+
let (index, spk) = spk_iter
744+
.nth(peek_index as usize)
745+
.expect("derivation index is out of bounds");
719746
(index, spk, None)
720747
}
721748
};
@@ -745,7 +772,7 @@ impl<D> Wallet<D> {
745772
///
746773
/// Will only return `Some(_)` if the wallet has given out the spk.
747774
pub fn derivation_of_spk(&self, spk: &Script) -> Option<(KeychainKind, u32)> {
748-
self.indexed_graph.index.index_of_spk(spk).copied()
775+
self.indexed_graph.index.index_of_spk(spk)
749776
}
750777

751778
/// Return the list of unspent outputs of this wallet
@@ -784,44 +811,44 @@ impl<D> Wallet<D> {
784811
self.chain.tip()
785812
}
786813

787-
/// Returns a iterators of all the script pubkeys for the `Internal` and External` variants in `KeychainKind`.
814+
/// Get unbounded script pubkey iterators for both `Internal` and `External` keychains.
788815
///
789816
/// This is intended to be used when doing a full scan of your addresses (e.g. after restoring
790817
/// from seed words). You pass the `BTreeMap` of iterators to a blockchain data source (e.g.
791818
/// electrum server) which will go through each address until it reaches a *stop gap*.
792819
///
793820
/// Note carefully that iterators go over **all** script pubkeys on the keychains (not what
794821
/// script pubkeys the wallet is storing internally).
795-
pub fn spks_of_all_keychains(
822+
pub fn all_unbounded_spk_iters(
796823
&self,
797824
) -> BTreeMap<KeychainKind, impl Iterator<Item = (u32, ScriptBuf)> + Clone> {
798-
self.indexed_graph.index.spks_of_all_keychains()
825+
self.indexed_graph.index.all_unbounded_spk_iters()
799826
}
800827

801-
/// Gets an iterator over all the script pubkeys in a single keychain.
828+
/// Get an unbounded script pubkey iterator for the given `keychain`.
802829
///
803-
/// See [`spks_of_all_keychains`] for more documentation
830+
/// See [`all_unbounded_spk_iters`] for more documentation
804831
///
805-
/// [`spks_of_all_keychains`]: Self::spks_of_all_keychains
806-
pub fn spks_of_keychain(
832+
/// [`all_unbounded_spk_iters`]: Self::all_unbounded_spk_iters
833+
pub fn unbounded_spk_iter(
807834
&self,
808835
keychain: KeychainKind,
809836
) -> impl Iterator<Item = (u32, ScriptBuf)> + Clone {
810-
self.indexed_graph.index.spks_of_keychain(&keychain)
837+
self.indexed_graph.index.unbounded_spk_iter(&keychain)
811838
}
812839

813840
/// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the
814841
/// wallet's database.
815842
pub fn get_utxo(&self, op: OutPoint) -> Option<LocalOutput> {
816-
let (&spk_i, _) = self.indexed_graph.index.txout(op)?;
843+
let (keychain, index, _) = self.indexed_graph.index.txout(op)?;
817844
self.indexed_graph
818845
.graph()
819846
.filter_chain_unspents(
820847
&self.chain,
821848
self.chain.tip().block_id(),
822-
core::iter::once((spk_i, op)),
849+
core::iter::once(((), op)),
823850
)
824-
.map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo))
851+
.map(|(_, full_txo)| new_local_utxo(keychain, index, full_txo))
825852
.next()
826853
}
827854

@@ -1459,7 +1486,7 @@ impl<D> Wallet<D> {
14591486
let ((index, spk), index_changeset) =
14601487
self.indexed_graph.index.next_unused_spk(&change_keychain);
14611488
let spk = spk.into();
1462-
self.indexed_graph.index.mark_used(&change_keychain, index);
1489+
self.indexed_graph.index.mark_used(change_keychain, index);
14631490
self.persist
14641491
.stage(ChangeSet::from(indexed_tx_graph::ChangeSet::from(
14651492
index_changeset,
@@ -1640,7 +1667,7 @@ impl<D> Wallet<D> {
16401667
.into();
16411668

16421669
let weighted_utxo = match txout_index.index_of_spk(&txout.script_pubkey) {
1643-
Some(&(keychain, derivation_index)) => {
1670+
Some((keychain, derivation_index)) => {
16441671
#[allow(deprecated)]
16451672
let satisfaction_weight = self
16461673
.get_descriptor_for_keychain(keychain)
@@ -1684,7 +1711,7 @@ impl<D> Wallet<D> {
16841711
for (index, txout) in tx.output.iter().enumerate() {
16851712
let change_type = self.map_keychain(KeychainKind::Internal);
16861713
match txout_index.index_of_spk(&txout.script_pubkey) {
1687-
Some(&(keychain, _)) if keychain == change_type => change_index = Some(index),
1714+
Some((keychain, _)) if keychain == change_type => change_index = Some(index),
16881715
_ => {}
16891716
}
16901717
}
@@ -1939,10 +1966,10 @@ impl<D> Wallet<D> {
19391966
pub fn cancel_tx(&mut self, tx: &Transaction) {
19401967
let txout_index = &mut self.indexed_graph.index;
19411968
for txout in &tx.output {
1942-
if let Some(&(keychain, index)) = txout_index.index_of_spk(&txout.script_pubkey) {
1969+
if let Some((keychain, index)) = txout_index.index_of_spk(&txout.script_pubkey) {
19431970
// NOTE: unmark_used will **not** make something unused if it has actually been used
19441971
// by a tx in the tracker. It only removes the superficial marking.
1945-
txout_index.unmark_used(&keychain, index);
1972+
txout_index.unmark_used(keychain, index);
19461973
}
19471974
}
19481975
}
@@ -1958,7 +1985,7 @@ impl<D> Wallet<D> {
19581985
}
19591986

19601987
fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option<DerivedDescriptor> {
1961-
let &(keychain, child) = self
1988+
let (keychain, child) = self
19621989
.indexed_graph
19631990
.index
19641991
.index_of_spk(&txout.script_pubkey)?;
@@ -2172,7 +2199,7 @@ impl<D> Wallet<D> {
21722199
{
21732200
// Try to find the prev_script in our db to figure out if this is internal or external,
21742201
// and the derivation index
2175-
let &(keychain, child) = self
2202+
let (keychain, child) = self
21762203
.indexed_graph
21772204
.index
21782205
.index_of_spk(&utxo.txout.script_pubkey)
@@ -2226,7 +2253,7 @@ impl<D> Wallet<D> {
22262253

22272254
// Try to figure out the keychain and derivation for every input and output
22282255
for (is_input, index, out) in utxos.into_iter() {
2229-
if let Some(&(keychain, child)) =
2256+
if let Some((keychain, child)) =
22302257
self.indexed_graph.index.index_of_spk(&out.script_pubkey)
22312258
{
22322259
let desc = self.get_descriptor_for_keychain(keychain);

0 commit comments

Comments
 (0)