Skip to content

Commit f66d53e

Browse files
evanlinjindanielabrozzoni
authored andcommitted
fix(chain): introduce keychain-variant-ranking to KeychainTxOutIndex
This fixes the bug with changesets not being monotone. Previously, the result of applying changesets individually v.s. applying the aggregate of changesets may result in different `KeychainTxOutIndex` states. The nature of the changeset allows different keychain types to share the same descriptor. However, the previous design did not take this into account. To do this properly, we should keep track of all keychains currently associated with a given descriptor. However, the API only allows returning one keychain per spk/txout/outpoint (which is a good API). Therefore, we rank keychain variants by `Ord`. Earlier keychain variants have a higher rank, and the first keychain will be returned.
1 parent 6bb95f8 commit f66d53e

File tree

1 file changed

+82
-81
lines changed

1 file changed

+82
-81
lines changed

crates/chain/src/keychain/txout_index.rs

Lines changed: 82 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,20 @@ const DEFAULT_LOOKAHEAD: u32 = 25;
160160
/// let new_spk_for_user = txout_index.reveal_next_spk(&MyKeychain::MyAppUser{ user_id: 42 });
161161
/// ```
162162
///
163+
/// # Re-assigning descriptors
164+
///
165+
/// Under the hood, the [`KeychainTxOutIndex`] uses a [`SpkTxOutIndex`] that keeps track of spks,
166+
/// indexed by descriptors. Users then assign or unassign keychains to those descriptors. It's
167+
/// important to note that descriptors, once added, never get removed from the [`SpkTxOutIndex`];
168+
/// this is useful in case a user unassigns a keychain from a descriptor and after some time
169+
/// assigns it again.
170+
///
171+
/// Additionally, although a keychain can only be assigned to one descriptor, different keychains
172+
/// can be assigned to the same descriptor. When a method returns spks/outpoints that is associated
173+
/// with a descriptor, it may be associated with multiple keychain variants. The keychain variant
174+
/// with the higher rank will be returned. Rank is determined by the [`Ord`] implementation of the
175+
/// keychain type. Earlier keychain variants have higher rank.
176+
///
163177
/// [`Ord`]: core::cmp::Ord
164178
/// [`SpkTxOutIndex`]: crate::spk_txout_index::SpkTxOutIndex
165179
/// [`Descriptor`]: crate::miniscript::Descriptor
@@ -172,17 +186,17 @@ const DEFAULT_LOOKAHEAD: u32 = 25;
172186
/// [`new`]: KeychainTxOutIndex::new
173187
/// [`unbounded_spk_iter`]: KeychainTxOutIndex::unbounded_spk_iter
174188
/// [`all_unbounded_spk_iters`]: KeychainTxOutIndex::all_unbounded_spk_iters
175-
// Under the hood, the KeychainTxOutIndex uses a SpkTxOutIndex that keeps track of spks, indexed by
176-
// descriptors. Users then assign or unassign keychains to those descriptors. It's important to
177-
// note that descriptors, once added, never get removed from the SpkTxOutIndex; this is useful in
178-
// case a user unassigns a keychain from a descriptor and after some time assigns it again.
179189
#[derive(Clone, Debug)]
180190
pub struct KeychainTxOutIndex<K> {
181191
inner: SpkTxOutIndex<(DescriptorId, u32)>,
182192
// keychain -> (descriptor, descriptor id) map
183193
keychains_to_descriptors: BTreeMap<K, (DescriptorId, Descriptor<DescriptorPublicKey>)>,
184-
// descriptor id -> keychain map
185-
descriptor_ids_to_keychain: BTreeMap<DescriptorId, (K, Descriptor<DescriptorPublicKey>)>,
194+
// descriptor id -> keychain set
195+
// Because different keychains can have the same descriptor, we rank keychains by `Ord` so that
196+
// that the first keychain variant (according to `Ord`) has the highest rank. When associated
197+
// data (such as spks, outpoints) are returned with a keychain, we return the highest-ranked
198+
// keychain with it.
199+
descriptor_ids_to_keychain_set: HashMap<DescriptorId, BTreeSet<K>>,
186200
// descriptor_id -> descriptor map
187201
// This is a "monotone" map, meaning that its size keeps growing, i.e., we never delete
188202
// descriptors from it. This is useful for revealing spks for descriptors that don't have
@@ -208,11 +222,9 @@ impl<K: Clone + Ord + Debug> Indexer for KeychainTxOutIndex<K> {
208222
Some((descriptor_id, index)) => {
209223
// We want to reveal spks for descriptors that aren't tracked by any keychain, and
210224
// so we call reveal with descriptor_id
211-
if let Some((_, changeset)) = self.reveal_to_target_with_id(descriptor_id, index) {
212-
changeset
213-
} else {
214-
super::ChangeSet::default()
215-
}
225+
let (_, changeset) = self.reveal_to_target_with_id(descriptor_id, index)
226+
.expect("descriptors are added in a monotone manner, there cannot be a descriptor id with no corresponding descriptor");
227+
changeset
216228
}
217229
None => super::ChangeSet::default(),
218230
}
@@ -259,9 +271,9 @@ impl<K> KeychainTxOutIndex<K> {
259271
pub fn new(lookahead: u32) -> Self {
260272
Self {
261273
inner: SpkTxOutIndex::default(),
262-
descriptor_ids_to_keychain: BTreeMap::new(),
263-
descriptor_ids_to_descriptors: BTreeMap::new(),
264274
keychains_to_descriptors: BTreeMap::new(),
275+
descriptor_ids_to_keychain_set: HashMap::new(),
276+
descriptor_ids_to_descriptors: BTreeMap::new(),
265277
last_revealed: BTreeMap::new(),
266278
lookahead,
267279
}
@@ -270,6 +282,12 @@ impl<K> KeychainTxOutIndex<K> {
270282

271283
/// Methods that are *re-exposed* from the internal [`SpkTxOutIndex`].
272284
impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
285+
/// Get the highest-ranked keychain that is currently associated with the given `desc_id`.
286+
fn keychain_of_desc_id(&self, desc_id: &DescriptorId) -> Option<&K> {
287+
let keychains = self.descriptor_ids_to_keychain_set.get(desc_id)?;
288+
keychains.iter().next()
289+
}
290+
273291
/// Return a reference to the internal [`SpkTxOutIndex`].
274292
///
275293
/// **WARNING:** The internal index will contain lookahead spks. Refer to
@@ -284,18 +302,16 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
284302
.outpoints()
285303
.iter()
286304
.filter_map(|((desc_id, index), op)| {
287-
self.descriptor_ids_to_keychain
288-
.get(desc_id)
289-
.map(|(k, _)| ((k.clone(), *index), *op))
305+
let keychain = self.keychain_of_desc_id(desc_id)?;
306+
Some(((keychain.clone(), *index), *op))
290307
})
291308
}
292309

293310
/// Iterate over known txouts that spend to tracked script pubkeys.
294311
pub fn txouts(&self) -> impl DoubleEndedIterator<Item = (K, u32, OutPoint, &TxOut)> + '_ {
295312
self.inner.txouts().filter_map(|((desc_id, i), op, txo)| {
296-
self.descriptor_ids_to_keychain
297-
.get(desc_id)
298-
.map(|(k, _)| (k.clone(), *i, op, txo))
313+
let keychain = self.keychain_of_desc_id(desc_id)?;
314+
Some((keychain.clone(), *i, op, txo))
299315
})
300316
}
301317

@@ -307,9 +323,8 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
307323
self.inner
308324
.txouts_in_tx(txid)
309325
.filter_map(|((desc_id, i), op, txo)| {
310-
self.descriptor_ids_to_keychain
311-
.get(desc_id)
312-
.map(|(k, _)| (k.clone(), *i, op, txo))
326+
let keychain = self.keychain_of_desc_id(desc_id)?;
327+
Some((keychain.clone(), *i, op, txo))
313328
})
314329
}
315330

@@ -321,7 +336,7 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
321336
/// This calls [`SpkTxOutIndex::txout`] internally.
322337
pub fn txout(&self, outpoint: OutPoint) -> Option<(K, u32, &TxOut)> {
323338
let ((descriptor_id, index), txo) = self.inner.txout(outpoint)?;
324-
let (keychain, _) = self.descriptor_ids_to_keychain.get(descriptor_id)?;
339+
let keychain = self.keychain_of_desc_id(descriptor_id)?;
325340
Some((keychain.clone(), *index, txo))
326341
}
327342

@@ -338,9 +353,8 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
338353
/// This calls [`SpkTxOutIndex::index_of_spk`] internally.
339354
pub fn index_of_spk(&self, script: &Script) -> Option<(K, u32)> {
340355
let (desc_id, last_index) = self.inner.index_of_spk(script)?;
341-
self.descriptor_ids_to_keychain
342-
.get(desc_id)
343-
.map(|(k, _)| (k.clone(), *last_index))
356+
let keychain = self.keychain_of_desc_id(desc_id)?;
357+
Some((keychain.clone(), *last_index))
344358
}
345359

346360
/// Returns whether the spk under the `keychain`'s `index` has been used.
@@ -448,45 +462,42 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
448462
keychain: K,
449463
descriptor: Descriptor<DescriptorPublicKey>,
450464
) -> super::ChangeSet<K> {
451-
let descriptor_id = descriptor.descriptor_id();
452-
// First, we fill the keychain -> (desc_id, descriptor) map
453-
let old_descriptor_opt = self
465+
let mut changeset = super::ChangeSet::<K>::default();
466+
let desc_id = descriptor.descriptor_id();
467+
468+
let old_desc = self
454469
.keychains_to_descriptors
455-
.insert(keychain.clone(), (descriptor_id, descriptor.clone()));
456-
457-
// Then, we fill the descriptor_id -> (keychain, descriptor) map
458-
let old_keychain_opt = self
459-
.descriptor_ids_to_keychain
460-
.insert(descriptor_id, (keychain.clone(), descriptor.clone()));
461-
462-
// If `keychain` already had a `descriptor` associated, different from the `descriptor`
463-
// passed in, we remove it from the descriptor -> keychain map
464-
if let Some((old_desc_id, _)) = old_descriptor_opt {
465-
if old_desc_id != descriptor_id {
466-
self.descriptor_ids_to_keychain.remove(&old_desc_id);
470+
.insert(keychain.clone(), (desc_id, descriptor.clone()));
471+
472+
if let Some((old_desc_id, _)) = old_desc {
473+
// nothing needs to be done if caller reinsterted the same descriptor under the same
474+
// keychain
475+
if old_desc_id == desc_id {
476+
return changeset;
467477
}
478+
// we should remove old descriptor that is associated with this keychain as the index
479+
// is designed to track one descriptor per keychain (however different keychains can
480+
// share the same descriptor)
481+
let _is_keychain_removed = self
482+
.descriptor_ids_to_keychain_set
483+
.get_mut(&old_desc_id)
484+
.expect("we must have already inserted this descriptor")
485+
.remove(&keychain);
486+
debug_assert!(_is_keychain_removed);
468487
}
469488

470-
// Lastly, we fill the desc_id -> desc map
489+
self.descriptor_ids_to_keychain_set
490+
.entry(desc_id)
491+
.or_default()
492+
.insert(keychain.clone());
471493
self.descriptor_ids_to_descriptors
472-
.insert(descriptor_id, descriptor.clone());
473-
494+
.insert(desc_id, descriptor.clone());
474495
self.replenish_lookahead(&keychain, self.lookahead);
475496

476-
// If both the keychain and descriptor were already inserted and associated, the
477-
// keychains_added changeset must be empty
478-
let keychains_added = if old_keychain_opt.map(|(k, _)| k) == Some(keychain.clone())
479-
&& old_descriptor_opt.map(|(_, d)| d) == Some(descriptor.clone())
480-
{
481-
[].into()
482-
} else {
483-
[(keychain, descriptor)].into()
484-
};
485-
486-
super::ChangeSet {
487-
keychains_added,
488-
last_revealed: [].into(),
489-
}
497+
changeset
498+
.keychains_added
499+
.insert(keychain.clone(), descriptor.clone());
500+
changeset
490501
}
491502

492503
/// Gets the descriptor associated with the keychain. Returns `None` if the keychain doesn't
@@ -584,11 +595,8 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
584595
.range((start, end))
585596
.map(|((descriptor_id, i), spk)| {
586597
(
587-
&self
588-
.descriptor_ids_to_keychain
589-
.get(descriptor_id)
590-
.expect("Must be here")
591-
.0,
598+
self.keychain_of_desc_id(descriptor_id)
599+
.expect("must have keychain"),
592600
*i,
593601
spk.as_script(),
594602
)
@@ -673,10 +681,9 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
673681
pub fn last_revealed_indices(&self) -> BTreeMap<K, u32> {
674682
self.last_revealed
675683
.iter()
676-
.filter_map(|(descriptor_id, index)| {
677-
self.descriptor_ids_to_keychain
678-
.get(descriptor_id)
679-
.map(|(k, _)| (k.clone(), *index))
684+
.filter_map(|(desc_id, index)| {
685+
let keychain = self.keychain_of_desc_id(desc_id)?;
686+
Some((keychain.clone(), *index))
680687
})
681688
.collect()
682689
}
@@ -870,16 +877,11 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
870877
let bounds = self.map_to_inner_bounds(range);
871878
self.inner
872879
.outputs_in_range(bounds)
873-
.map(move |((descriptor_id, i), op)| {
874-
(
875-
&self
876-
.descriptor_ids_to_keychain
877-
.get(descriptor_id)
878-
.expect("must be here")
879-
.0,
880-
*i,
881-
op,
882-
)
880+
.map(move |((desc_id, i), op)| {
881+
let keychain = self
882+
.keychain_of_desc_id(desc_id)
883+
.expect("keychain must exist");
884+
(keychain, *i, op)
883885
})
884886
}
885887

@@ -941,10 +943,9 @@ impl<K: Clone + Ord + Debug> KeychainTxOutIndex<K> {
941943
}
942944
let last_revealed = last_revealed
943945
.into_iter()
944-
.filter_map(|(descriptor_id, index)| {
945-
self.descriptor_ids_to_keychain
946-
.get(&descriptor_id)
947-
.map(|(k, _)| (k.clone(), index))
946+
.filter_map(|(desc_id, index)| {
947+
let keychain = self.keychain_of_desc_id(&desc_id)?;
948+
Some((keychain.clone(), index))
948949
})
949950
.collect();
950951
let _ = self.reveal_to_target_multi(&last_revealed);

0 commit comments

Comments
 (0)