refactor(keychain): Fix KeychainTxOutIndex range queries

The underlying SpkTxOutIndex should not use DescriptorIds to index
because this loses the ordering relationship of the spks so queries on
subranges of keychains work.

Along with that we enforce that there is a strict 1-to-1 relationship
between descriptors and keychains. Violating this leads to an error in
insert_descriptor now.

In general I try to make the translation layer between the SpkTxOutIndex
and the KeychainTxOutIndex thinner. Ergonomics of this will be improved
in next commit.

The test from the previous commit passes.
This commit is contained in:
LLFourn
2024-06-06 10:17:55 +10:00
committed by 志宇
parent 3b2ff0cc95
commit bc2a8be979
12 changed files with 441 additions and 467 deletions

View File

@@ -23,7 +23,6 @@ pub enum Error {
HardenedDerivationXpub,
/// The descriptor contains multipath keys
MultiPath,
/// Error thrown while working with [`keys`](crate::keys)
Key(crate::keys::KeyError),
/// Error while extracting and manipulating policies

View File

@@ -772,7 +772,7 @@ impl Wallet {
keychain: KeychainKind,
index: u32,
) -> anyhow::Result<impl Iterator<Item = AddressInfo> + '_> {
let (spk_iter, index_changeset) = self
let (spks, index_changeset) = self
.indexed_graph
.index
.reveal_to_target(&keychain, index)
@@ -781,7 +781,7 @@ impl Wallet {
self.persist
.stage_and_commit(indexed_tx_graph::ChangeSet::from(index_changeset).into())?;
Ok(spk_iter.map(move |(index, spk)| AddressInfo {
Ok(spks.into_iter().map(move |(index, spk)| AddressInfo {
index,
address: Address::from_script(&spk, self.network).expect("must have address form"),
keychain,
@@ -861,7 +861,7 @@ impl Wallet {
///
/// Will only return `Some(_)` if the wallet has given out the spk.
pub fn derivation_of_spk(&self, spk: &Script) -> Option<(KeychainKind, u32)> {
self.indexed_graph.index.index_of_spk(spk)
self.indexed_graph.index.index_of_spk(spk).cloned()
}
/// Return the list of unspent outputs of this wallet
@@ -871,7 +871,7 @@ impl Wallet {
.filter_chain_unspents(
&self.chain,
self.chain.tip().block_id(),
self.indexed_graph.index.outpoints(),
self.indexed_graph.index.outpoints().iter().cloned(),
)
.map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo))
}
@@ -885,7 +885,7 @@ impl Wallet {
.filter_chain_txouts(
&self.chain,
self.chain.tip().block_id(),
self.indexed_graph.index.outpoints(),
self.indexed_graph.index.outpoints().iter().cloned(),
)
.map(|((k, i), full_txo)| new_local_utxo(k, i, full_txo))
}
@@ -932,7 +932,7 @@ impl Wallet {
/// Returns the utxo owned by this wallet corresponding to `outpoint` if it exists in the
/// wallet's database.
pub fn get_utxo(&self, op: OutPoint) -> Option<LocalOutput> {
let (keychain, index, _) = self.indexed_graph.index.txout(op)?;
let (&(keychain, index), _) = self.indexed_graph.index.txout(op)?;
self.indexed_graph
.graph()
.filter_chain_unspents(
@@ -1207,7 +1207,7 @@ impl Wallet {
self.indexed_graph.graph().balance(
&self.chain,
self.chain.tip().block_id(),
self.indexed_graph.index.outpoints(),
self.indexed_graph.index.outpoints().iter().cloned(),
|&(k, _), _| k == KeychainKind::Internal,
)
}
@@ -1699,7 +1699,7 @@ impl Wallet {
.into();
let weighted_utxo = match txout_index.index_of_spk(&txout.script_pubkey) {
Some((keychain, derivation_index)) => {
Some(&(keychain, derivation_index)) => {
let satisfaction_weight = self
.get_descriptor_for_keychain(keychain)
.max_weight_to_satisfy()
@@ -1744,7 +1744,7 @@ impl Wallet {
for (index, txout) in tx.output.iter().enumerate() {
let change_keychain = KeychainKind::Internal;
match txout_index.index_of_spk(&txout.script_pubkey) {
Some((keychain, _)) if keychain == change_keychain => {
Some((keychain, _)) if *keychain == change_keychain => {
change_index = Some(index)
}
_ => {}
@@ -2015,13 +2015,13 @@ impl Wallet {
if let Some((keychain, index)) = txout_index.index_of_spk(&txout.script_pubkey) {
// NOTE: unmark_used will **not** make something unused if it has actually been used
// by a tx in the tracker. It only removes the superficial marking.
txout_index.unmark_used(keychain, index);
txout_index.unmark_used(*keychain, *index);
}
}
}
fn get_descriptor_for_txout(&self, txout: &TxOut) -> Option<DerivedDescriptor> {
let (keychain, child) = self
let &(keychain, child) = self
.indexed_graph
.index
.index_of_spk(&txout.script_pubkey)?;
@@ -2237,7 +2237,7 @@ impl Wallet {
) -> Result<psbt::Input, CreateTxError> {
// Try to find the prev_script in our db to figure out if this is internal or external,
// and the derivation index
let (keychain, child) = self
let &(keychain, child) = self
.indexed_graph
.index
.index_of_spk(&utxo.txout.script_pubkey)
@@ -2285,7 +2285,7 @@ impl Wallet {
// Try to figure out the keychain and derivation for every input and output
for (is_input, index, out) in utxos.into_iter() {
if let Some((keychain, child)) =
if let Some(&(keychain, child)) =
self.indexed_graph.index.index_of_spk(&out.script_pubkey)
{
let desc = self.get_descriptor_for_keychain(keychain);
@@ -2331,7 +2331,7 @@ impl Wallet {
None => ChangeSet::default(),
};
let (_, index_changeset) = self
let index_changeset = self
.indexed_graph
.index
.reveal_to_target_multi(&update.last_active_indices);
@@ -2536,17 +2536,27 @@ fn create_signers<E: IntoWalletDescriptor>(
) -> Result<(Arc<SignersContainer>, Arc<SignersContainer>), DescriptorError> {
let descriptor = into_wallet_descriptor_checked(descriptor, secp, network)?;
let change_descriptor = into_wallet_descriptor_checked(change_descriptor, secp, network)?;
if descriptor.0 == change_descriptor.0 {
return Err(DescriptorError::ExternalAndInternalAreTheSame);
}
let (descriptor, keymap) = descriptor;
let signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
let _ = index.insert_descriptor(KeychainKind::External, descriptor);
let _ = index
.insert_descriptor(KeychainKind::External, descriptor)
.expect("this is the first descriptor we're inserting");
let (descriptor, keymap) = change_descriptor;
let change_signers = Arc::new(SignersContainer::build(keymap, &descriptor, secp));
let _ = index.insert_descriptor(KeychainKind::Internal, descriptor);
let _ = index
.insert_descriptor(KeychainKind::Internal, descriptor)
.map_err(|e| {
use bdk_chain::keychain::InsertDescriptorError;
match e {
InsertDescriptorError::DescriptorAlreadyAssigned { .. } => {
crate::descriptor::error::Error::ExternalAndInternalAreTheSame
}
InsertDescriptorError::KeychainAlreadyAssigned { .. } => {
unreachable!("this is the first time we're assigning internal")
}
}
})?;
Ok((signers, change_signers))
}