Skip to content

Commit 78e3264

Browse files
committed
feat!: change KeychainTxOutIndex methods to always return changesets
Change the out signature of methods `reveal_to_target`, `reveal_next_spk` and `next_unused_spk` to return a tuple of `(Option<spk(s)>, changeset)` instead of `Option<(spk(s), changeset)>`. This makes the API more consistent. Also refactored various helper methods to take in a descriptor instead of a descriptor id. `.expect` calls now exist outside of these helper methods, making it more obvious where they are being called. Docs are updated to reflect the new API.
1 parent 708b916 commit 78e3264

File tree

5 files changed

+217
-210
lines changed

5 files changed

+217
-210
lines changed

crates/chain/src/keychain/txout_index.rs

Lines changed: 129 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ use crate::{
55
spk_iter::BIP32_MAX_INDEX,
66
DescriptorExt, DescriptorId, SpkIterator, SpkTxOutIndex,
77
};
8-
use bitcoin::{hashes::Hash, Amount, OutPoint, Script, SignedAmount, Transaction, TxOut, Txid};
8+
use alloc::borrow::ToOwned;
9+
use bitcoin::{
10+
hashes::Hash, Amount, OutPoint, Script, ScriptBuf, SignedAmount, Transaction, TxOut, Txid,
11+
};
912
use core::{
1013
fmt::Debug,
1114
ops::{Bound, RangeBounds},
@@ -127,8 +130,8 @@ const DEFAULT_LOOKAHEAD: u32 = 25;
127130
///
128131
/// # Change sets
129132
///
130-
/// Methods that can update the last revealed index or add keychains will return [`super::ChangeSet`] to report
131-
/// these changes. This can be persisted for future recovery.
133+
/// Methods that can update the last revealed index or add keychains will return [`ChangeSet`] to
134+
/// report these changes. This can be persisted for future recovery.
132135
///
133136
/// ## Synopsis
134137
///
@@ -235,31 +238,35 @@ impl<K> Default for KeychainTxOutIndex<K> {
235238
}
236239

237240
impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
238-
type ChangeSet = super::ChangeSet<K>;
241+
type ChangeSet = ChangeSet<K>;
239242

240243
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet {
241244
match self.inner.scan_txout(outpoint, txout).cloned() {
242245
Some((descriptor_id, index)) => {
243246
// We want to reveal spks for descriptors that aren't tracked by any keychain, and
244247
// so we call reveal with descriptor_id
245-
let (_, changeset) = self.reveal_to_target_with_id(descriptor_id, index)
246-
.expect("descriptors are added in a monotone manner, there cannot be a descriptor id with no corresponding descriptor");
248+
let desc = self
249+
.descriptor_ids_to_descriptors
250+
.get(&descriptor_id)
251+
.cloned()
252+
.expect("descriptors are added monotonically, scanned txout descriptor ids must have associated descriptors");
253+
let (_, changeset) = self.reveal_to_target_with_descriptor(desc, index);
247254
changeset
248255
}
249-
None => super::ChangeSet::default(),
256+
None => ChangeSet::default(),
250257
}
251258
}
252259

253260
fn index_tx(&mut self, tx: &bitcoin::Transaction) -> Self::ChangeSet {
254-
let mut changeset = super::ChangeSet::<K>::default();
261+
let mut changeset = ChangeSet::<K>::default();
255262
for (op, txout) in tx.output.iter().enumerate() {
256263
changeset.append(self.index_txout(OutPoint::new(tx.txid(), op as u32), txout));
257264
}
258265
changeset
259266
}
260267

261268
fn initial_changeset(&self) -> Self::ChangeSet {
262-
super::ChangeSet {
269+
ChangeSet {
263270
keychains_added: self
264271
.keychains()
265272
.map(|(k, v)| (k.clone(), v.clone()))
@@ -485,8 +492,8 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
485492
&mut self,
486493
keychain: K,
487494
descriptor: Descriptor<DescriptorPublicKey>,
488-
) -> super::ChangeSet<K> {
489-
let mut changeset = super::ChangeSet::<K>::default();
495+
) -> ChangeSet<K> {
496+
let mut changeset = ChangeSet::<K>::default();
490497
let desc_id = descriptor.descriptor_id();
491498

492499
let old_desc_id = self
@@ -708,24 +715,40 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
708715
.descriptor_ids_to_descriptors
709716
.get(descriptor_id)
710717
.expect("descriptor id cannot be associated with keychain without descriptor");
711-
let last_index = self.last_revealed.get(descriptor_id).cloned();
718+
Some(self.next_index_with_descriptor(descriptor))
719+
}
720+
721+
/// Get the next derivation index of given `descriptor`. The next derivation index is the index
722+
/// after the last revealed index.
723+
///
724+
/// The second field of the returned tuple represents whether the next derivation index is new.
725+
/// There are two scenarios where the next derivation index is reused (not new):
726+
///
727+
/// 1. The keychain's descriptor has no wildcard, and a script has already been revealed.
728+
/// 2. The number of revealed scripts has already reached 2^31 (refer to BIP-32).
729+
///
730+
/// Not checking the second field of the tuple may result in address reuse.
731+
fn next_index_with_descriptor(
732+
&self,
733+
descriptor: &Descriptor<DescriptorPublicKey>,
734+
) -> (u32, bool) {
735+
let desc_id = descriptor.descriptor_id();
712736

713-
// we can only get the next index if the wildcard exists.
737+
// we can only get the next index if the wildcard exists
714738
let has_wildcard = descriptor.has_wildcard();
739+
let last_index = self.last_revealed.get(&desc_id).cloned();
715740

716-
Some(match last_index {
717-
// if there is no index, next_index is always 0.
741+
match last_index {
742+
// if there is no index, next_index is always 0
718743
None => (0, true),
719-
// descriptors without wildcards can only have one index.
744+
// descriptors without wildcards can only have one index
720745
Some(_) if !has_wildcard => (0, false),
721-
// derivation index must be < 2^31 (BIP-32).
722-
Some(index) if index > BIP32_MAX_INDEX => {
723-
unreachable!("index is out of bounds")
724-
}
746+
// derivation index must be < 2^31 (BIP-32)
747+
Some(index) if index > BIP32_MAX_INDEX => unreachable!("index out of bounds"),
725748
Some(index) if index == BIP32_MAX_INDEX => (index, false),
726-
// get the next derivation index.
749+
// get the next derivation index
727750
Some(index) => (index + 1, true),
728-
})
751+
}
729752
}
730753

731754
/// Get the last derivation index that is revealed for each keychain.
@@ -754,17 +777,16 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
754777
keychains: &BTreeMap<K, u32>,
755778
) -> (
756779
BTreeMap<K, SpkIterator<Descriptor<DescriptorPublicKey>>>,
757-
super::ChangeSet<K>,
780+
ChangeSet<K>,
758781
) {
759-
let mut changeset = super::ChangeSet::default();
782+
let mut changeset = ChangeSet::default();
760783
let mut spks = BTreeMap::new();
761784

762785
for (keychain, &index) in keychains {
763-
if let Some((new_spks, new_changeset)) = self.reveal_to_target(keychain, index) {
764-
if !new_changeset.is_empty() {
765-
spks.insert(keychain.clone(), new_spks);
766-
changeset.append(new_changeset.clone());
767-
}
786+
let (new_spks, new_changeset) = self.reveal_to_target(keychain, index);
787+
if let Some(new_spks) = new_spks {
788+
changeset.append(new_changeset);
789+
spks.insert(keychain.clone(), new_spks);
768790
}
769791
}
770792

@@ -777,18 +799,12 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
777799
/// Refer to the `reveal_to_target` documentation for more.
778800
///
779801
/// Returns None if the provided `descriptor_id` doesn't correspond to a tracked descriptor.
780-
fn reveal_to_target_with_id(
802+
fn reveal_to_target_with_descriptor(
781803
&mut self,
782-
descriptor_id: DescriptorId,
804+
descriptor: Descriptor<DescriptorPublicKey>,
783805
target_index: u32,
784-
) -> Option<(
785-
SpkIterator<Descriptor<DescriptorPublicKey>>,
786-
super::ChangeSet<K>,
787-
)> {
788-
let descriptor = self
789-
.descriptor_ids_to_descriptors
790-
.get(&descriptor_id)?
791-
.clone();
806+
) -> (SpkIterator<Descriptor<DescriptorPublicKey>>, ChangeSet<K>) {
807+
let descriptor_id = descriptor.descriptor_id();
792808
let has_wildcard = descriptor.has_wildcard();
793809

794810
let target_index = if has_wildcard { target_index } else { 0 };
@@ -801,10 +817,10 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
801817

802818
// If the target_index is already revealed, we are done
803819
if next_reveal_index > target_index {
804-
return Some((
820+
return (
805821
SpkIterator::new_with_range(descriptor, next_reveal_index..next_reveal_index),
806-
super::ChangeSet::default(),
807-
));
822+
ChangeSet::default(),
823+
);
808824
}
809825

810826
// We range over the indexes that are not stored and insert their spks in the index.
@@ -822,13 +838,13 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
822838

823839
let _old_index = self.last_revealed.insert(descriptor_id, target_index);
824840
debug_assert!(_old_index < Some(target_index));
825-
Some((
841+
(
826842
SpkIterator::new_with_range(descriptor, next_reveal_index..target_index + 1),
827-
super::ChangeSet {
843+
ChangeSet {
828844
keychains_added: BTreeMap::new(),
829845
last_revealed: core::iter::once((descriptor_id, target_index)).collect(),
830846
},
831-
))
847+
)
832848
}
833849

834850
/// Reveals script pubkeys of the `keychain`'s descriptor **up to and including** the
@@ -839,76 +855,94 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
839855
/// reveal up to the last possible index.
840856
///
841857
/// This returns an iterator of newly revealed indices (alongside their scripts) and a
842-
/// [`super::ChangeSet`], which reports updates to the latest revealed index. If no new script
843-
/// pubkeys are revealed, then both of these will be empty.
858+
/// [`ChangeSet`], which reports updates to the latest revealed index. If no new script pubkeys
859+
/// are revealed, then both of these will be empty.
844860
///
845861
/// Returns None if the provided `keychain` doesn't exist.
846862
pub fn reveal_to_target(
847863
&mut self,
848864
keychain: &K,
849865
target_index: u32,
850-
) -> Option<(
851-
SpkIterator<Descriptor<DescriptorPublicKey>>,
852-
super::ChangeSet<K>,
853-
)> {
854-
let descriptor_id = self.keychains_to_descriptor_ids.get(keychain)?;
855-
self.reveal_to_target_with_id(*descriptor_id, target_index)
866+
) -> (
867+
Option<SpkIterator<Descriptor<DescriptorPublicKey>>>,
868+
ChangeSet<K>,
869+
) {
870+
let descriptor_id = match self.keychains_to_descriptor_ids.get(keychain) {
871+
Some(desc_id) => desc_id,
872+
None => return (None, ChangeSet::default()),
873+
};
874+
let desc = self
875+
.descriptor_ids_to_descriptors
876+
.get(descriptor_id)
877+
.cloned()
878+
.expect("descriptors are added monotonically, scanned txout descriptor ids must have associated descriptors");
879+
let (spk_iter, changeset) = self.reveal_to_target_with_descriptor(desc, target_index);
880+
(Some(spk_iter), changeset)
856881
}
857882

858883
/// Attempts to reveal the next script pubkey for `keychain`.
859884
///
860-
/// Returns the derivation index of the revealed script pubkey, the revealed script pubkey and a
861-
/// [`super::ChangeSet`] which represents changes in the last revealed index (if any).
862-
/// Returns None if the provided keychain doesn't exist.
885+
/// Returns the next revealed script pubkey (alongside it's derivation index), and a
886+
/// [`ChangeSet`]. The next revealed script pubkey is `None` if the provided `keychain` has no
887+
/// associated descriptor.
863888
///
864-
/// When a new script cannot be revealed, we return the last revealed script and an empty
865-
/// [`super::ChangeSet`]. There are two scenarios when a new script pubkey cannot be derived:
889+
/// When the descriptor has no more script pubkeys to reveal, the last revealed script and an
890+
/// empty [`ChangeSet`] is returned. There are 3 scenarios in which a descriptor can no longer
891+
/// derive scripts:
866892
///
867893
/// 1. The descriptor has no wildcard and already has one script revealed.
868894
/// 2. The descriptor has already revealed scripts up to the numeric bound.
869895
/// 3. There is no descriptor associated with the given keychain.
870-
pub fn reveal_next_spk(
871-
&mut self,
872-
keychain: &K,
873-
) -> Option<((u32, &Script), super::ChangeSet<K>)> {
874-
let descriptor_id = self.keychains_to_descriptor_ids.get(keychain).cloned()?;
875-
let (next_index, _) = self.next_index(keychain).expect("We know keychain exists");
876-
let changeset = self
877-
.reveal_to_target(keychain, next_index)
878-
.expect("We know keychain exists")
879-
.1;
880-
let script = self
881-
.inner
882-
.spk_at_index(&(descriptor_id, next_index))
883-
.expect("script must already be stored");
884-
Some(((next_index, script), changeset))
896+
pub fn reveal_next_spk(&mut self, keychain: &K) -> (Option<(u32, ScriptBuf)>, ChangeSet<K>) {
897+
let descriptor_id = match self.keychains_to_descriptor_ids.get(keychain) {
898+
Some(&desc_id) => desc_id,
899+
None => {
900+
return (None, Default::default());
901+
}
902+
};
903+
let descriptor = self
904+
.descriptor_ids_to_descriptors
905+
.get(&descriptor_id)
906+
.expect("descriptor id cannot be associated with keychain without descriptor");
907+
let (next_index, _) = self.next_index_with_descriptor(descriptor);
908+
let (mut new_spks, changeset) =
909+
self.reveal_to_target_with_descriptor(descriptor.clone(), next_index);
910+
let revealed_spk = new_spks.next().unwrap_or_else(|| {
911+
// if we have exhausted all derivation indicies, the returned `next_index` is reused
912+
// and will not be included in `new_spks` (refer to `next_index_with_descriptor` docs)
913+
let spk = self
914+
.inner
915+
.spk_at_index(&(descriptor_id, next_index))
916+
.expect("script must already be stored")
917+
.to_owned();
918+
(next_index, spk)
919+
});
920+
debug_assert_eq!(new_spks.next(), None, "must only reveal one spk");
921+
(Some(revealed_spk), changeset)
885922
}
886923

887-
/// Gets the next unused script pubkey in the keychain. I.e., the script pubkey with the lowest
888-
/// index that has not been used yet.
924+
/// Gets the next unused script pubkey in the keychain. I.e. the script pubkey with the lowest
925+
/// derivation index that has not been used (no associated transaction outputs).
889926
///
890-
/// This will derive and reveal a new script pubkey if no more unused script pubkeys exist.
927+
/// Returns the unused script pubkey (alongside it's derivation index), and a [`ChangeSet`].
928+
/// The returned script pubkey will be `None` if the provided `keychain` has no associated
929+
/// descriptor.
891930
///
892-
/// If the descriptor has no wildcard and already has a used script pubkey or if a descriptor
893-
/// has used all scripts up to the derivation bounds, then the last derived script pubkey will be
894-
/// returned.
931+
/// This will derive and reveal a new script pubkey if no more unused script pubkeys exists
932+
/// (refer to [`reveal_next_spk`](Self::reveal_next_spk)).
895933
///
896-
/// Returns None if the provided keychain doesn't exist.
897-
pub fn next_unused_spk(
898-
&mut self,
899-
keychain: &K,
900-
) -> Option<((u32, &Script), super::ChangeSet<K>)> {
901-
let need_new = self.unused_keychain_spks(keychain).next().is_none();
902-
// this rather strange branch is needed because of some lifetime issues
903-
if need_new {
904-
self.reveal_next_spk(keychain)
934+
/// If the descriptor has no wildcard and already has a used script pubkey or if a descriptor
935+
/// has used all scripts up to the derivation bounds, then the last derived script pubkey will
936+
/// be returned.
937+
pub fn next_unused_spk(&mut self, keychain: &K) -> (Option<(u32, ScriptBuf)>, ChangeSet<K>) {
938+
let next_unused_spk = self
939+
.unused_keychain_spks(keychain)
940+
.next()
941+
.map(|(i, spk)| (i, spk.to_owned()));
942+
if next_unused_spk.is_some() {
943+
(next_unused_spk, Default::default())
905944
} else {
906-
Some((
907-
self.unused_keychain_spks(keychain)
908-
.next()
909-
.expect("we already know next exists"),
910-
super::ChangeSet::default(),
911-
))
945+
self.reveal_next_spk(keychain)
912946
}
913947
}
914948

@@ -986,7 +1020,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
9861020
/// - Adds new descriptors introduced
9871021
/// - If a descriptor is introduced for a keychain that already had a descriptor, overwrites
9881022
/// the old descriptor
989-
pub fn apply_changeset(&mut self, changeset: super::ChangeSet<K>) {
1023+
pub fn apply_changeset(&mut self, changeset: ChangeSet<K>) {
9901024
let ChangeSet {
9911025
keychains_added,
9921026
last_revealed,

crates/chain/tests/test_indexed_tx_graph.rs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -150,22 +150,19 @@ fn test_list_owned_txouts() {
150150

151151
{
152152
// we need to scope here to take immutanble reference of the graph
153-
for _ in 0..10 {
154-
let ((_, script), _) = graph
155-
.index
156-
.reveal_next_spk(&"keychain_1".to_string())
157-
.unwrap();
158-
// TODO Assert indexes
159-
trusted_spks.push(script.to_owned());
153+
for exp_i in 0_u32..10 {
154+
let (next_spk, _) = graph.index.reveal_next_spk(&"keychain_1".to_string());
155+
let (spk_i, spk) = next_spk.expect("must exist");
156+
assert_eq!(spk_i, exp_i);
157+
trusted_spks.push(spk);
160158
}
161159
}
162160
{
163-
for _ in 0..10 {
164-
let ((_, script), _) = graph
165-
.index
166-
.reveal_next_spk(&"keychain_2".to_string())
167-
.unwrap();
168-
untrusted_spks.push(script.to_owned());
161+
for exp_i in 0_u32..10 {
162+
let (next_spk, _) = graph.index.reveal_next_spk(&"keychain_2".to_string());
163+
let (spk_i, spk) = next_spk.expect("must exist");
164+
assert_eq!(spk_i, exp_i);
165+
untrusted_spks.push(spk);
169166
}
170167
}
171168

0 commit comments

Comments
 (0)