Merge bitcoindevkit/bdk#1463: No descriptor ids in spk txout index

8dd174479f9719309663ed979a5b4b86aca0a6e9 refactor(chain): compute txid once for `KeychainTxOutIndex::index_tx` (志宇)
639d735ca0ae54d8b2c3bc28241032154b94d45e refactor(chain): change field names to be more sane (志宇)
5a02f40122f1bfa06c80bac93f68f5799225d133 docs(chain): fix docs (志宇)
c77e12bae7f465ec7fb08b8be16d99793c757cf0 refactor(chain): `KeychainTxOutIndex` use `HashMap` for fields (志宇)
4d3846abf4f59b4a97bb825281655a6b67275603 chore(chain): s/replenish_lookahead/replenish_inner_index/ (LLFourn)
8779afdb0bf4e9b1004f47f86a770d25938d206d chore(chain): document insert_descriptor invariants better (LLFourn)
69f2a695f7dc25478489080598fea0813ea7d93d refactor(chain): improve replenish lookeahd internals (LLFourn)
5a584d0fd8c138757a10c7af93ec9e09523317e1 chore(chain): Fix Indexed and KeychainIndexed documentaion (Lloyd Fournier)
b8ba5a02066fad7ab2ce276ba071385cd1dbbe3a chore(chain): Improve documentation of keychain::ChangeSet (LLFourn)
101a09a97fa5e8d675c13396b9a800665b1b6c22 chore(chain): Standardise KeychainTxOutIndex return types (LLFourn)
bce070b1d662db7ac120e1d236fdda51842ad738 chore(chain): add type IndexSpk, fix clippy type complexity warning (Steve Myers)
4d2442c37f5c1bd822795271a79676d1ffbe7916 chore(chain): misc docs and insert_descriptor fixes (LLFourn)
bc2a8be97919f0d09b61438527bda24796bcec94 refactor(keychain): Fix KeychainTxOutIndex range queries (LLFourn)
3b2ff0cc953204c9925ace8e2f0bbef409c63ad5 Write failing test for keychain range querying (LLFourn)

Pull request description:

  Fixes #1459

  This reverts part of the changes in #1203. There the `SpkTxOutIndex<(K,u32)>` was changed to `SpkTxOutIndex<(DescriptorId, u32>)`. This led to a complicated translation logic in  `KeychainTxOutIndex` (where the API is based on `K`) to transform calls to it to calls to the underlying `SpkTxOutIndex` (which now indexes by `DescriptorId`). The translation layer was broken when it came to translating range queries from the `KeychainTxOutIndex`. My solution was just to revert this part of the change and remove the need for a translation layer (almost) altogether. A thin translation layer remains to ensure that un-revealed spks are filtered out before being returned from the `KeychainTxOutIndex` methods.

  I feel like this PR could be extended to include a bunch of ergonomics improvements that are easier to implement now. But I think that's the point of https://github.com/bitcoindevkit/bdk/pull/1451 so I held off and should probably go and scope creep that one instead.

  ### 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

  #### Bugfixes:

  * [x] This pull request breaks the existing API
  * [x] 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:
  evanlinjin:
    ACK 8dd174479f9719309663ed979a5b4b86aca0a6e9

Tree-SHA512: 283e6b6d4218902298e2e848fe847a6c85e27af4eee3e4337e3dad6eacf9beaa08ac99b1dce7b6fb199ca53931e543ea365728a81c41567a2e510cce77b12ac0
This commit is contained in:
志宇 2024-06-13 23:10:13 +08:00
commit 1c593a34ee
No known key found for this signature in database
GPG Key ID: F6345C9837C2BDE8
17 changed files with 1060 additions and 1062 deletions

View File

@ -12,7 +12,7 @@
#[cfg(feature = "miniscript")]
mod txout_index;
use bitcoin::Amount;
use bitcoin::{Amount, ScriptBuf};
#[cfg(feature = "miniscript")]
pub use txout_index::*;
@ -49,6 +49,11 @@ impl Balance {
}
}
/// A tuple of keychain index and `T` representing the indexed value.
pub type Indexed<T> = (u32, T);
/// A tuple of keychain `K`, derivation index (`u32`) and a `T` associated with them.
pub type KeychainIndexed<K, T> = ((K, u32), T);
impl core::fmt::Display for Balance {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(

File diff suppressed because it is too large Load Diff

View File

@ -28,6 +28,7 @@ pub use chain_data::*;
pub mod indexed_tx_graph;
pub use indexed_tx_graph::IndexedTxGraph;
pub mod keychain;
pub use keychain::{Indexed, KeychainIndexed};
pub mod local_chain;
mod tx_data_traits;
pub mod tx_graph;

View File

@ -1,7 +1,8 @@
//! Helper types for spk-based blockchain clients.
use crate::{
collections::BTreeMap, local_chain::CheckPoint, ConfirmationTimeHeightAnchor, TxGraph,
collections::BTreeMap, keychain::Indexed, local_chain::CheckPoint,
ConfirmationTimeHeightAnchor, TxGraph,
};
use alloc::{boxed::Box, vec::Vec};
use bitcoin::{OutPoint, Script, ScriptBuf, Txid};
@ -166,7 +167,7 @@ impl SyncRequest {
self.chain_spks(
index
.revealed_spks(spk_range)
.map(|(_, _, spk)| spk.to_owned())
.map(|(_, spk)| spk.to_owned())
.collect::<Vec<_>>(),
)
}
@ -195,7 +196,7 @@ pub struct FullScanRequest<K> {
/// [`LocalChain::tip`]: crate::local_chain::LocalChain::tip
pub chain_tip: CheckPoint,
/// Iterators of script pubkeys indexed by the keychain index.
pub spks_by_keychain: BTreeMap<K, Box<dyn Iterator<Item = (u32, ScriptBuf)> + Send>>,
pub spks_by_keychain: BTreeMap<K, Box<dyn Iterator<Item = Indexed<ScriptBuf>> + Send>>,
}
impl<K: Ord + Clone> FullScanRequest<K> {
@ -238,7 +239,7 @@ impl<K: Ord + Clone> FullScanRequest<K> {
pub fn set_spks_for_keychain(
mut self,
keychain: K,
spks: impl IntoIterator<IntoIter = impl Iterator<Item = (u32, ScriptBuf)> + Send + 'static>,
spks: impl IntoIterator<IntoIter = impl Iterator<Item = Indexed<ScriptBuf>> + Send + 'static>,
) -> Self {
self.spks_by_keychain
.insert(keychain, Box::new(spks.into_iter()));
@ -252,7 +253,7 @@ impl<K: Ord + Clone> FullScanRequest<K> {
pub fn chain_spks_for_keychain(
mut self,
keychain: K,
spks: impl IntoIterator<IntoIter = impl Iterator<Item = (u32, ScriptBuf)> + Send + 'static>,
spks: impl IntoIterator<IntoIter = impl Iterator<Item = Indexed<ScriptBuf>> + Send + 'static>,
) -> Self {
match self.spks_by_keychain.remove(&keychain) {
// clippy here suggests to remove `into_iter` from `spks.into_iter()`, but doing so

View File

@ -1,5 +1,6 @@
use crate::{
bitcoin::{secp256k1::Secp256k1, ScriptBuf},
keychain::Indexed,
miniscript::{Descriptor, DescriptorPublicKey},
};
use core::{borrow::Borrow, ops::Bound, ops::RangeBounds};
@ -97,7 +98,7 @@ impl<D> Iterator for SpkIterator<D>
where
D: Borrow<Descriptor<DescriptorPublicKey>>,
{
type Item = (u32, ScriptBuf);
type Item = Indexed<ScriptBuf>;
fn next(&mut self) -> Option<Self::Item> {
// For non-wildcard descriptors, we expect the first element to be Some((0, spk)), then None after.
@ -158,8 +159,12 @@ mod test {
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 _ = txout_index.insert_descriptor(TestKeychain::External, external_descriptor.clone());
let _ = txout_index.insert_descriptor(TestKeychain::Internal, internal_descriptor.clone());
let _ = txout_index
.insert_descriptor(TestKeychain::External, external_descriptor.clone())
.unwrap();
let _ = txout_index
.insert_descriptor(TestKeychain::Internal, internal_descriptor.clone())
.unwrap();
(txout_index, external_descriptor, internal_descriptor)
}

View File

@ -52,7 +52,7 @@ impl<I> Default for SpkTxOutIndex<I> {
}
}
impl<I: Clone + Ord> Indexer for SpkTxOutIndex<I> {
impl<I: Clone + Ord + core::fmt::Debug> Indexer for SpkTxOutIndex<I> {
type ChangeSet = ();
fn index_txout(&mut self, outpoint: OutPoint, txout: &TxOut) -> Self::ChangeSet {
@ -76,7 +76,7 @@ impl<I: Clone + Ord> Indexer for SpkTxOutIndex<I> {
}
}
impl<I: Clone + Ord> SpkTxOutIndex<I> {
impl<I: Clone + Ord + core::fmt::Debug> SpkTxOutIndex<I> {
/// Scans a transaction's outputs for matching script pubkeys.
///
/// Typically, this is used in two situations:

View File

@ -10,7 +10,7 @@ use bdk_chain::{
indexed_tx_graph::{self, IndexedTxGraph},
keychain::{self, Balance, KeychainTxOutIndex},
local_chain::LocalChain,
tx_graph, ChainPosition, ConfirmationHeightAnchor, DescriptorExt,
tx_graph, Append, ChainPosition, ConfirmationHeightAnchor, DescriptorExt,
};
use bitcoin::{
secp256k1::Secp256k1, Amount, OutPoint, Script, ScriptBuf, Transaction, TxIn, TxOut,
@ -34,7 +34,10 @@ fn insert_relevant_txs() {
let mut graph = IndexedTxGraph::<ConfirmationHeightAnchor, KeychainTxOutIndex<()>>::new(
KeychainTxOutIndex::new(10),
);
let _ = graph.index.insert_descriptor((), descriptor.clone());
let _ = graph
.index
.insert_descriptor((), descriptor.clone())
.unwrap();
let tx_a = Transaction {
output: vec![
@ -140,8 +143,16 @@ fn test_list_owned_txouts() {
KeychainTxOutIndex::new(10),
);
let _ = graph.index.insert_descriptor("keychain_1".into(), desc_1);
let _ = graph.index.insert_descriptor("keychain_2".into(), desc_2);
assert!(!graph
.index
.insert_descriptor("keychain_1".into(), desc_1)
.unwrap()
.is_empty());
assert!(!graph
.index
.insert_descriptor("keychain_2".into(), desc_2)
.unwrap()
.is_empty());
// Get trusted and untrusted addresses
@ -257,18 +268,26 @@ fn test_list_owned_txouts() {
.unwrap_or_else(|| panic!("block must exist at {}", height));
let txouts = graph
.graph()
.filter_chain_txouts(&local_chain, chain_tip, graph.index.outpoints())
.filter_chain_txouts(
&local_chain,
chain_tip,
graph.index.outpoints().iter().cloned(),
)
.collect::<Vec<_>>();
let utxos = graph
.graph()
.filter_chain_unspents(&local_chain, chain_tip, graph.index.outpoints())
.filter_chain_unspents(
&local_chain,
chain_tip,
graph.index.outpoints().iter().cloned(),
)
.collect::<Vec<_>>();
let balance = graph.graph().balance(
&local_chain,
chain_tip,
graph.index.outpoints(),
graph.index.outpoints().iter().cloned(),
|_, spk: &Script| trusted_spks.contains(&spk.to_owned()),
);

View File

@ -34,8 +34,12 @@ fn init_txout_index(
) -> bdk_chain::keychain::KeychainTxOutIndex<TestKeychain> {
let mut txout_index = bdk_chain::keychain::KeychainTxOutIndex::<TestKeychain>::new(lookahead);
let _ = txout_index.insert_descriptor(TestKeychain::External, external_descriptor);
let _ = txout_index.insert_descriptor(TestKeychain::Internal, internal_descriptor);
let _ = txout_index
.insert_descriptor(TestKeychain::External, external_descriptor)
.unwrap();
let _ = txout_index
.insert_descriptor(TestKeychain::Internal, internal_descriptor)
.unwrap();
txout_index
}
@ -98,7 +102,7 @@ fn append_changesets_check_last_revealed() {
}
#[test]
fn test_apply_changeset_with_different_descriptors_to_same_keychain() {
fn when_apply_contradictory_changesets_they_are_ignored() {
let external_descriptor = parse_descriptor(DESCRIPTORS[0]);
let internal_descriptor = parse_descriptor(DESCRIPTORS[1]);
let mut txout_index =
@ -120,7 +124,7 @@ fn test_apply_changeset_with_different_descriptors_to_same_keychain() {
assert_eq!(
txout_index.keychains().collect::<Vec<_>>(),
vec![
(&TestKeychain::External, &internal_descriptor),
(&TestKeychain::External, &external_descriptor),
(&TestKeychain::Internal, &internal_descriptor)
]
);
@ -134,8 +138,8 @@ fn test_apply_changeset_with_different_descriptors_to_same_keychain() {
assert_eq!(
txout_index.keychains().collect::<Vec<_>>(),
vec![
(&TestKeychain::External, &internal_descriptor),
(&TestKeychain::Internal, &external_descriptor)
(&TestKeychain::External, &external_descriptor),
(&TestKeychain::Internal, &internal_descriptor)
]
);
}
@ -156,7 +160,7 @@ fn test_set_all_derivation_indices() {
]
.into();
assert_eq!(
txout_index.reveal_to_target_multi(&derive_to).1,
txout_index.reveal_to_target_multi(&derive_to),
ChangeSet {
keychains_added: BTreeMap::new(),
last_revealed: last_revealed.clone()
@ -164,7 +168,7 @@ fn test_set_all_derivation_indices() {
);
assert_eq!(txout_index.last_revealed_indices(), derive_to);
assert_eq!(
txout_index.reveal_to_target_multi(&derive_to).1,
txout_index.reveal_to_target_multi(&derive_to),
keychain::ChangeSet::default(),
"no changes if we set to the same thing"
);
@ -190,7 +194,7 @@ fn test_lookahead() {
.reveal_to_target(&TestKeychain::External, index)
.unwrap();
assert_eq!(
revealed_spks.collect::<Vec<_>>(),
revealed_spks,
vec![(index, spk_at_index(&external_descriptor, index))],
);
assert_eq!(
@ -241,7 +245,7 @@ fn test_lookahead() {
.reveal_to_target(&TestKeychain::Internal, 24)
.unwrap();
assert_eq!(
revealed_spks.collect::<Vec<_>>(),
revealed_spks,
(0..=24)
.map(|index| (index, spk_at_index(&internal_descriptor, index)))
.collect::<Vec<_>>(),
@ -404,10 +408,10 @@ fn test_wildcard_derivations() {
// - next_unused() == ((0, <spk>), keychain::ChangeSet:is_empty())
assert_eq!(txout_index.next_index(&TestKeychain::External).unwrap(), (0, true));
let (spk, changeset) = txout_index.reveal_next_spk(&TestKeychain::External).unwrap();
assert_eq!(spk, (0_u32, external_spk_0.as_script()));
assert_eq!(spk, (0_u32, external_spk_0.clone()));
assert_eq!(&changeset.last_revealed, &[(external_descriptor.descriptor_id(), 0)].into());
let (spk, changeset) = txout_index.next_unused_spk(&TestKeychain::External).unwrap();
assert_eq!(spk, (0_u32, external_spk_0.as_script()));
assert_eq!(spk, (0_u32, external_spk_0.clone()));
assert_eq!(&changeset.last_revealed, &[].into());
// - derived till 25
@ -427,12 +431,12 @@ fn test_wildcard_derivations() {
assert_eq!(txout_index.next_index(&TestKeychain::External).unwrap(), (26, true));
let (spk, changeset) = txout_index.reveal_next_spk(&TestKeychain::External).unwrap();
assert_eq!(spk, (26, external_spk_26.as_script()));
assert_eq!(spk, (26, external_spk_26));
assert_eq!(&changeset.last_revealed, &[(external_descriptor.descriptor_id(), 26)].into());
let (spk, changeset) = txout_index.next_unused_spk(&TestKeychain::External).unwrap();
assert_eq!(spk, (16, external_spk_16.as_script()));
assert_eq!(spk, (16, external_spk_16));
assert_eq!(&changeset.last_revealed, &[].into());
// - Use all the derived till 26.
@ -442,7 +446,7 @@ fn test_wildcard_derivations() {
});
let (spk, changeset) = txout_index.next_unused_spk(&TestKeychain::External).unwrap();
assert_eq!(spk, (27, external_spk_27.as_script()));
assert_eq!(spk, (27, external_spk_27));
assert_eq!(&changeset.last_revealed, &[(external_descriptor.descriptor_id(), 27)].into());
}
@ -458,7 +462,9 @@ fn test_non_wildcard_derivations() {
.unwrap()
.script_pubkey();
let _ = txout_index.insert_descriptor(TestKeychain::External, no_wildcard_descriptor.clone());
let _ = txout_index
.insert_descriptor(TestKeychain::External, no_wildcard_descriptor.clone())
.unwrap();
// given:
// - `txout_index` with no stored scripts
@ -473,7 +479,7 @@ fn test_non_wildcard_derivations() {
let (spk, changeset) = txout_index
.reveal_next_spk(&TestKeychain::External)
.unwrap();
assert_eq!(spk, (0, external_spk.as_script()));
assert_eq!(spk, (0, external_spk.clone()));
assert_eq!(
&changeset.last_revealed,
&[(no_wildcard_descriptor.descriptor_id(), 0)].into()
@ -482,7 +488,7 @@ fn test_non_wildcard_derivations() {
let (spk, changeset) = txout_index
.next_unused_spk(&TestKeychain::External)
.unwrap();
assert_eq!(spk, (0, external_spk.as_script()));
assert_eq!(spk, (0, external_spk.clone()));
assert_eq!(&changeset.last_revealed, &[].into());
// given:
@ -500,18 +506,18 @@ fn test_non_wildcard_derivations() {
let (spk, changeset) = txout_index
.reveal_next_spk(&TestKeychain::External)
.unwrap();
assert_eq!(spk, (0, external_spk.as_script()));
assert_eq!(spk, (0, external_spk.clone()));
assert_eq!(&changeset.last_revealed, &[].into());
let (spk, changeset) = txout_index
.next_unused_spk(&TestKeychain::External)
.unwrap();
assert_eq!(spk, (0, external_spk.as_script()));
assert_eq!(spk, (0, external_spk.clone()));
assert_eq!(&changeset.last_revealed, &[].into());
let (revealed_spks, revealed_changeset) = txout_index
.reveal_to_target(&TestKeychain::External, 200)
.unwrap();
assert_eq!(revealed_spks.count(), 0);
assert_eq!(revealed_spks.len(), 0);
assert!(revealed_changeset.is_empty());
// we check that spks_of_keychain returns a SpkIterator with just one element
@ -591,19 +597,17 @@ fn lookahead_to_target() {
let keychain_test_cases = [
(
external_descriptor.descriptor_id(),
TestKeychain::External,
t.external_last_revealed,
t.external_target,
),
(
internal_descriptor.descriptor_id(),
TestKeychain::Internal,
t.internal_last_revealed,
t.internal_target,
),
];
for (descriptor_id, keychain, last_revealed, target) in keychain_test_cases {
for (keychain, last_revealed, target) in keychain_test_cases {
if let Some(target) = target {
let original_last_stored_index = match last_revealed {
Some(last_revealed) => Some(last_revealed + t.lookahead),
@ -619,10 +623,10 @@ fn lookahead_to_target() {
let keys = index
.inner()
.all_spks()
.range((descriptor_id, 0)..=(descriptor_id, u32::MAX))
.map(|(k, _)| *k)
.range((keychain.clone(), 0)..=(keychain.clone(), u32::MAX))
.map(|(k, _)| k.clone())
.collect::<Vec<_>>();
let exp_keys = core::iter::repeat(descriptor_id)
let exp_keys = core::iter::repeat(keychain)
.zip(0_u32..=exp_last_stored_index)
.collect::<Vec<_>>();
assert_eq!(keys, exp_keys);
@ -631,50 +635,6 @@ fn lookahead_to_target() {
}
}
/// `::index_txout` should still index txouts with spks derived from descriptors without keychains.
/// This includes properly refilling the lookahead for said descriptors.
#[test]
fn index_txout_after_changing_descriptor_under_keychain() {
let secp = bdk_chain::bitcoin::secp256k1::Secp256k1::signing_only();
let (desc_a, _) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, DESCRIPTORS[0])
.expect("descriptor 0 must be valid");
let (desc_b, _) = Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, DESCRIPTORS[1])
.expect("descriptor 1 must be valid");
let desc_id_a = desc_a.descriptor_id();
let mut txout_index = bdk_chain::keychain::KeychainTxOutIndex::<()>::new(10);
// Introduce `desc_a` under keychain `()` and replace the descriptor.
let _ = txout_index.insert_descriptor((), desc_a.clone());
let _ = txout_index.insert_descriptor((), desc_b.clone());
// Loop through spks in intervals of `lookahead` to create outputs with. We should always be
// able to index these outputs if `lookahead` is respected.
let spk_indices = [9, 19, 29, 39];
for i in spk_indices {
let spk_at_index = desc_a
.at_derivation_index(i)
.expect("must derive")
.script_pubkey();
let index_changeset = txout_index.index_txout(
// Use spk derivation index as vout as we just want an unique outpoint.
OutPoint::new(h!("mock_tx"), i as _),
&TxOut {
value: Amount::from_sat(10_000),
script_pubkey: spk_at_index,
},
);
assert_eq!(
index_changeset,
bdk_chain::keychain::ChangeSet {
keychains_added: BTreeMap::default(),
last_revealed: [(desc_id_a, i)].into(),
},
"must always increase last active if impl respects lookahead"
);
}
}
#[test]
fn insert_descriptor_no_change() {
let secp = Secp256k1::signing_only();
@ -683,19 +643,20 @@ fn insert_descriptor_no_change() {
let mut txout_index = KeychainTxOutIndex::<()>::default();
assert_eq!(
txout_index.insert_descriptor((), desc.clone()),
keychain::ChangeSet {
Ok(keychain::ChangeSet {
keychains_added: [((), desc.clone())].into(),
last_revealed: Default::default()
},
}),
);
assert_eq!(
txout_index.insert_descriptor((), desc.clone()),
keychain::ChangeSet::default(),
Ok(keychain::ChangeSet::default()),
"inserting the same descriptor for keychain should return an empty changeset",
);
}
#[test]
#[cfg(not(debug_assertions))]
fn applying_changesets_one_by_one_vs_aggregate_must_have_same_result() {
let desc = parse_descriptor(DESCRIPTORS[0]);
let changesets: &[ChangeSet<TestKeychain>] = &[
@ -743,37 +704,60 @@ fn applying_changesets_one_by_one_vs_aggregate_must_have_same_result() {
);
}
// When the same descriptor is associated with various keychains,
// index methods only return the highest keychain by Ord
#[test]
fn test_only_highest_ord_keychain_is_returned() {
fn assigning_same_descriptor_to_multiple_keychains_should_error() {
let desc = parse_descriptor(DESCRIPTORS[0]);
let mut indexer = KeychainTxOutIndex::<TestKeychain>::new(0);
let _ = indexer.insert_descriptor(TestKeychain::Internal, desc.clone());
let _ = indexer.insert_descriptor(TestKeychain::External, desc);
let _ = indexer
.insert_descriptor(TestKeychain::Internal, desc.clone())
.unwrap();
assert!(indexer
.insert_descriptor(TestKeychain::External, desc)
.is_err())
}
// reveal_next_spk will work with either keychain
let spk0: ScriptBuf = indexer
.reveal_next_spk(&TestKeychain::External)
.unwrap()
.0
.1
.into();
let spk1: ScriptBuf = indexer
.reveal_next_spk(&TestKeychain::Internal)
.unwrap()
.0
.1
.into();
#[test]
fn reassigning_keychain_to_a_new_descriptor_should_error() {
let desc1 = parse_descriptor(DESCRIPTORS[0]);
let desc2 = parse_descriptor(DESCRIPTORS[1]);
let mut indexer = KeychainTxOutIndex::<TestKeychain>::new(0);
let _ = indexer.insert_descriptor(TestKeychain::Internal, desc1);
assert!(indexer
.insert_descriptor(TestKeychain::Internal, desc2)
.is_err());
}
// index_of_spk will always return External
#[test]
fn when_querying_over_a_range_of_keychains_the_utxos_should_show_up() {
let mut indexer = KeychainTxOutIndex::<usize>::new(0);
let mut tx = common::new_tx(0);
for (i, descriptor) in DESCRIPTORS.iter().enumerate() {
let descriptor = parse_descriptor(descriptor);
let _ = indexer.insert_descriptor(i, descriptor.clone()).unwrap();
if i != 4 {
// skip one in the middle to see if uncovers any bugs
indexer.reveal_next_spk(&i);
}
tx.output.push(TxOut {
script_pubkey: descriptor.at_derivation_index(0).unwrap().script_pubkey(),
value: Amount::from_sat(10_000),
});
}
let n_spks = DESCRIPTORS.len() - /*we skipped one*/ 1;
let _ = indexer.index_tx(&tx);
assert_eq!(indexer.outpoints().len(), n_spks);
assert_eq!(indexer.revealed_spks(0..DESCRIPTORS.len()).count(), n_spks);
assert_eq!(indexer.revealed_spks(1..4).count(), 4 - 1);
assert_eq!(
indexer.index_of_spk(&spk0),
Some((TestKeychain::External, 0))
indexer.net_value(&tx, 0..DESCRIPTORS.len()).to_sat(),
(10_000 * n_spks) as i64
);
assert_eq!(
indexer.index_of_spk(&spk1),
Some((TestKeychain::External, 1))
indexer.net_value(&tx, 3..6).to_sat(),
(10_000 * (6 - 3 - /*the skipped one*/ 1)) as i64
);
}

View File

@ -2,13 +2,13 @@ use std::collections::BTreeSet;
use async_trait::async_trait;
use bdk_chain::spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult};
use bdk_chain::Anchor;
use bdk_chain::{
bitcoin::{BlockHash, OutPoint, ScriptBuf, TxOut, Txid},
collections::BTreeMap,
local_chain::CheckPoint,
BlockId, ConfirmationTimeHeightAnchor, TxGraph,
};
use bdk_chain::{Anchor, Indexed};
use esplora_client::{Amount, TxStatus};
use futures::{stream::FuturesOrdered, TryStreamExt};
@ -236,7 +236,7 @@ async fn full_scan_for_index_and_graph<K: Ord + Clone + Send>(
client: &esplora_client::AsyncClient,
keychain_spks: BTreeMap<
K,
impl IntoIterator<IntoIter = impl Iterator<Item = (u32, ScriptBuf)> + Send> + Send,
impl IntoIterator<IntoIter = impl Iterator<Item = Indexed<ScriptBuf>> + Send> + Send,
>,
stop_gap: usize,
parallel_requests: usize,

View File

@ -4,12 +4,12 @@ use std::usize;
use bdk_chain::collections::BTreeMap;
use bdk_chain::spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult};
use bdk_chain::Anchor;
use bdk_chain::{
bitcoin::{Amount, BlockHash, OutPoint, ScriptBuf, TxOut, Txid},
local_chain::CheckPoint,
BlockId, ConfirmationTimeHeightAnchor, TxGraph,
};
use bdk_chain::{Anchor, Indexed};
use esplora_client::TxStatus;
use crate::anchor_from_status;
@ -217,7 +217,7 @@ fn chain_update<A: Anchor>(
/// [`KeychainTxOutIndex`](bdk_chain::keychain::KeychainTxOutIndex).
fn full_scan_for_index_and_graph_blocking<K: Ord + Clone>(
client: &esplora_client::BlockingClient,
keychain_spks: BTreeMap<K, impl IntoIterator<Item = (u32, ScriptBuf)>>,
keychain_spks: BTreeMap<K, impl IntoIterator<Item = Indexed<ScriptBuf>>>,
stop_gap: usize,
parallel_requests: usize,
) -> Result<(TxGraph<ConfirmationTimeHeightAnchor>, BTreeMap<K, u32>), Error> {

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

@ -29,7 +29,7 @@ use bdk_chain::{
spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult},
tx_graph::{CanonicalTx, TxGraph},
Append, BlockId, ChainPosition, ConfirmationTime, ConfirmationTimeHeightAnchor, FullTxOut,
IndexedTxGraph,
Indexed, IndexedTxGraph,
};
use bdk_persist::{Persist, PersistBackend};
use bitcoin::secp256k1::{All, Secp256k1};
@ -752,7 +752,8 @@ impl Wallet {
Ok(AddressInfo {
index,
address: Address::from_script(spk, self.network).expect("must have address form"),
address: Address::from_script(spk.as_script(), self.network)
.expect("must have address form"),
keychain,
})
}
@ -772,7 +773,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 +782,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,
@ -809,7 +810,8 @@ impl Wallet {
Ok(AddressInfo {
index,
address: Address::from_script(spk, self.network).expect("must have address form"),
address: Address::from_script(spk.as_script(), self.network)
.expect("must have address form"),
keychain,
})
}
@ -861,7 +863,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 +873,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 +887,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))
}
@ -910,7 +912,7 @@ impl Wallet {
/// script pubkeys the wallet is storing internally).
pub fn all_unbounded_spk_iters(
&self,
) -> BTreeMap<KeychainKind, impl Iterator<Item = (u32, ScriptBuf)> + Clone> {
) -> BTreeMap<KeychainKind, impl Iterator<Item = Indexed<ScriptBuf>> + Clone> {
self.indexed_graph.index.all_unbounded_spk_iters()
}
@ -922,7 +924,7 @@ impl Wallet {
pub fn unbounded_spk_iter(
&self,
keychain: KeychainKind,
) -> impl Iterator<Item = (u32, ScriptBuf)> + Clone {
) -> impl Iterator<Item = Indexed<ScriptBuf>> + Clone {
self.indexed_graph
.index
.unbounded_spk_iter(&keychain)
@ -932,7 +934,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 +1209,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,
)
}
@ -1511,7 +1513,6 @@ impl Wallet {
.index
.next_unused_spk(&change_keychain)
.expect("keychain must exist");
let spk = spk.into();
self.indexed_graph.index.mark_used(change_keychain, index);
self.persist
.stage(ChangeSet::from(indexed_tx_graph::ChangeSet::from(
@ -1699,7 +1700,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 +1745,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 +2016,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 +2238,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 +2286,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 +2332,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 +2537,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))
}

View File

@ -212,7 +212,7 @@ fn main() -> anyhow::Result<()> {
graph.graph().balance(
&*chain,
synced_to.block_id(),
graph.index.outpoints(),
graph.index.outpoints().iter().cloned(),
|(k, _), _| k == &Keychain::Internal,
)
};
@ -336,7 +336,7 @@ fn main() -> anyhow::Result<()> {
graph.graph().balance(
&*chain,
synced_to.block_id(),
graph.index.outpoints(),
graph.index.outpoints().iter().cloned(),
|(k, _), _| k == &Keychain::Internal,
)
};

View File

@ -265,9 +265,6 @@ where
.expect("Must exist");
changeset.append(change_changeset);
// Clone to drop the immutable reference.
let change_script = change_script.into();
let change_plan = bdk_tmp_plan::plan_satisfaction(
&graph
.index
@ -427,7 +424,7 @@ pub fn planned_utxos<A: Anchor, O: ChainOracle, K: Clone + bdk_tmp_plan::CanDeri
let outpoints = graph.index.outpoints();
graph
.graph()
.try_filter_chain_unspents(chain, chain_tip, outpoints)
.try_filter_chain_unspents(chain, chain_tip, outpoints.iter().cloned())
.filter_map(|r| -> Option<Result<PlannedUtxo<K, A>, _>> {
let (k, i, full_txo) = match r {
Err(err) => return Some(Err(err)),
@ -481,8 +478,8 @@ where
local_chain::ChangeSet::default(),
indexed_tx_graph::ChangeSet::from(index_changeset),
)))?;
let addr =
Address::from_script(spk, network).context("failed to derive address")?;
let addr = Address::from_script(spk.as_script(), network)
.context("failed to derive address")?;
println!("[address @ {}] {}", spk_i, addr);
Ok(())
}
@ -527,7 +524,7 @@ where
let balance = graph.graph().try_balance(
chain,
chain.get_chain_tip()?,
graph.index.outpoints(),
graph.index.outpoints().iter().cloned(),
|(k, _), _| k == &Keychain::Internal,
)?;
@ -568,7 +565,7 @@ where
} => {
let txouts = graph
.graph()
.try_filter_chain_txouts(chain, chain_tip, outpoints)
.try_filter_chain_txouts(chain, chain_tip, outpoints.iter().cloned())
.filter(|r| match r {
Ok((_, full_txo)) => match (spent, unspent) {
(true, false) => full_txo.spent_by.is_some(),
@ -709,7 +706,7 @@ where
// them in the index here. However, the keymap is not stored in the database.
let (descriptor, mut keymap) =
Descriptor::<DescriptorPublicKey>::parse_descriptor(&secp, &args.descriptor)?;
let _ = index.insert_descriptor(Keychain::External, descriptor);
let _ = index.insert_descriptor(Keychain::External, descriptor)?;
if let Some((internal_descriptor, internal_keymap)) = args
.change_descriptor
@ -718,7 +715,7 @@ where
.transpose()?
{
keymap.extend(internal_keymap);
let _ = index.insert_descriptor(Keychain::Internal, internal_descriptor);
let _ = index.insert_descriptor(Keychain::Internal, internal_descriptor)?;
}
let mut db_backend = match Store::<C>::open_or_create_new(db_magic, &args.db_path) {

View File

@ -228,9 +228,9 @@ fn main() -> anyhow::Result<()> {
let all_spks = graph
.index
.revealed_spks(..)
.map(|(k, i, spk)| (k.to_owned(), i, spk.to_owned()))
.map(|(index, spk)| (index, spk.to_owned()))
.collect::<Vec<_>>();
request = request.chain_spks(all_spks.into_iter().map(|(k, spk_i, spk)| {
request = request.chain_spks(all_spks.into_iter().map(|((k, spk_i), spk)| {
eprint!("Scanning {}: {}", k, spk_i);
spk
}));
@ -239,10 +239,10 @@ fn main() -> anyhow::Result<()> {
let unused_spks = graph
.index
.unused_spks()
.map(|(k, i, spk)| (k, i, spk.to_owned()))
.map(|(index, spk)| (index, spk.to_owned()))
.collect::<Vec<_>>();
request =
request.chain_spks(unused_spks.into_iter().map(move |(k, spk_i, spk)| {
request.chain_spks(unused_spks.into_iter().map(move |((k, spk_i), spk)| {
eprint!(
"Checking if address {} {}:{} has been used",
Address::from_script(&spk, args.network).unwrap(),
@ -258,7 +258,11 @@ fn main() -> anyhow::Result<()> {
let utxos = graph
.graph()
.filter_chain_unspents(&*chain, chain_tip.block_id(), init_outpoints)
.filter_chain_unspents(
&*chain,
chain_tip.block_id(),
init_outpoints.iter().cloned(),
)
.map(|(_, utxo)| utxo)
.collect::<Vec<_>>();
request = request.chain_outpoints(utxos.into_iter().map(|utxo| {
@ -338,7 +342,7 @@ fn main() -> anyhow::Result<()> {
let mut indexed_tx_graph_changeset =
indexed_tx_graph::ChangeSet::<ConfirmationHeightAnchor, _>::default();
if let Some(keychain_update) = keychain_update {
let (_, keychain_changeset) = graph.index.reveal_to_target_multi(&keychain_update);
let keychain_changeset = graph.index.reveal_to_target_multi(&keychain_update);
indexed_tx_graph_changeset.append(keychain_changeset.into());
}
indexed_tx_graph_changeset.append(graph.apply_update(graph_update));

View File

@ -204,7 +204,7 @@ fn main() -> anyhow::Result<()> {
// addresses derived so we need to derive up to last active addresses the scan found
// before adding the transactions.
(chain.apply_update(update.chain_update)?, {
let (_, index_changeset) = graph
let index_changeset = graph
.index
.reveal_to_target_multi(&update.last_active_indices);
let mut indexed_tx_graph_changeset = graph.apply_update(update.graph_update);
@ -245,7 +245,7 @@ fn main() -> anyhow::Result<()> {
let all_spks = graph
.index
.revealed_spks(..)
.map(|(k, i, spk)| (k.to_owned(), i, spk.to_owned()))
.map(|((k, i), spk)| (k, i, spk.to_owned()))
.collect::<Vec<_>>();
request = request.chain_spks(all_spks.into_iter().map(|(k, i, spk)| {
eprint!("scanning {}:{}", k, i);
@ -258,10 +258,10 @@ fn main() -> anyhow::Result<()> {
let unused_spks = graph
.index
.unused_spks()
.map(|(k, i, spk)| (k, i, spk.to_owned()))
.map(|(index, spk)| (index, spk.to_owned()))
.collect::<Vec<_>>();
request =
request.chain_spks(unused_spks.into_iter().map(move |(k, i, spk)| {
request.chain_spks(unused_spks.into_iter().map(move |((k, i), spk)| {
eprint!(
"Checking if address {} {}:{} has been used",
Address::from_script(&spk, args.network).unwrap(),
@ -280,7 +280,11 @@ fn main() -> anyhow::Result<()> {
let init_outpoints = graph.index.outpoints();
let utxos = graph
.graph()
.filter_chain_unspents(&*chain, local_tip.block_id(), init_outpoints)
.filter_chain_unspents(
&*chain,
local_tip.block_id(),
init_outpoints.iter().cloned(),
)
.map(|(_, utxo)| utxo)
.collect::<Vec<_>>();
request = request.chain_outpoints(