//! indexes [`TxOut`]s with them.
use crate::{
+ alloc::boxed::Box,
collections::*,
miniscript::{Descriptor, DescriptorPublicKey},
spk_client::{FullScanRequestBuilder, SyncRequestBuilder},
DescriptorExt, DescriptorId, Indexed, Indexer, KeychainIndexed, SpkIterator,
};
use alloc::{borrow::ToOwned, vec::Vec};
-use bitcoin::{Amount, OutPoint, ScriptBuf, SignedAmount, Transaction, TxOut, Txid};
+use bitcoin::{
+ key::Secp256k1, Amount, OutPoint, ScriptBuf, SignedAmount, Transaction, TxOut, Txid,
+};
use core::{
fmt::Debug,
ops::{Bound, RangeBounds},
descriptors: HashMap<DescriptorId, Descriptor<DescriptorPublicKey>>,
last_revealed: HashMap<DescriptorId, u32>,
lookahead: u32,
+
+ spk_cache: BTreeMap<DescriptorId, HashMap<u32, ScriptBuf>>,
}
impl<K> Default for KeychainTxOutIndex<K> {
if self.last_revealed.get(did) < Some(&index) {
self.last_revealed.insert(*did, index);
changeset.last_revealed.insert(*did, index);
- self.replenish_inner_index(*did, &keychain, self.lookahead);
+ self.replenish_inner_index(
+ *did,
+ &keychain,
+ self.lookahead,
+ changeset.spk_cache.entry(*did).or_default(),
+ );
}
}
changeset
fn initial_changeset(&self) -> Self::ChangeSet {
ChangeSet {
last_revealed: self.last_revealed.clone().into_iter().collect(),
+ spk_cache: self
+ .spk_cache
+ .iter()
+ .map(|(desc, spks)| {
+ (
+ *desc,
+ spks.iter().map(|(i, spk)| (*i, spk.clone())).collect(),
+ )
+ })
+ .collect(),
}
}
descriptor_id_to_keychain: Default::default(),
last_revealed: Default::default(),
lookahead,
+ spk_cache: Default::default(),
}
}
&mut self,
keychain: K,
descriptor: Descriptor<DescriptorPublicKey>,
- ) -> Result<bool, InsertDescriptorError<K>> {
+ ) -> Result<(bool, ChangeSet), InsertDescriptorError<K>> {
+ let mut changeset = ChangeSet::default();
+
let did = descriptor.descriptor_id();
if !self.keychain_to_descriptor_id.contains_key(&keychain)
&& !self.descriptor_id_to_keychain.contains_key(&did)
self.descriptors.insert(did, descriptor.clone());
self.keychain_to_descriptor_id.insert(keychain.clone(), did);
self.descriptor_id_to_keychain.insert(did, keychain.clone());
- self.replenish_inner_index(did, &keychain, self.lookahead);
- return Ok(true);
+ self.replenish_inner_index(
+ did,
+ &keychain,
+ self.lookahead,
+ changeset.spk_cache.entry(did).or_default(),
+ );
+ return Ok((true, changeset));
}
if let Some(existing_desc_id) = self.keychain_to_descriptor_id.get(&keychain) {
let descriptor = self.descriptors.get(existing_desc_id).expect("invariant");
if *existing_desc_id != did {
return Err(InsertDescriptorError::KeychainAlreadyAssigned {
- existing_assignment: descriptor.clone(),
+ existing_assignment: Box::new(descriptor.clone()),
keychain,
});
}
if *existing_keychain != keychain {
return Err(InsertDescriptorError::DescriptorAlreadyAssigned {
existing_assignment: existing_keychain.clone(),
- descriptor,
+ descriptor: Box::new(descriptor),
});
}
}
- Ok(false)
+ Ok((false, changeset))
}
/// Gets the descriptor associated with the keychain. Returns `None` if the keychain doesn't
/// Store lookahead scripts until `target_index` (inclusive).
///
/// This does not change the global `lookahead` setting.
- pub fn lookahead_to_target(&mut self, keychain: K, target_index: u32) {
+ pub fn lookahead_to_target(
+ &mut self,
+ keychain: K,
+ target_index: u32,
+ derived_spks: &mut impl Extend<Indexed<ScriptBuf>>,
+ ) {
if let Some((next_index, _)) = self.next_index(keychain.clone()) {
let temp_lookahead = (target_index + 1)
.checked_sub(next_index)
.filter(|&index| index > 0);
if let Some(temp_lookahead) = temp_lookahead {
- self.replenish_inner_index_keychain(keychain, temp_lookahead);
+ self.replenish_inner_index_keychain(keychain, temp_lookahead, derived_spks);
}
}
}
- fn replenish_inner_index_did(&mut self, did: DescriptorId, lookahead: u32) {
+ fn replenish_inner_index_did(
+ &mut self,
+ did: DescriptorId,
+ lookahead: u32,
+ derived_spks: &mut impl Extend<Indexed<ScriptBuf>>,
+ ) {
if let Some(keychain) = self.descriptor_id_to_keychain.get(&did).cloned() {
- self.replenish_inner_index(did, &keychain, lookahead);
+ self.replenish_inner_index(did, &keychain, lookahead, derived_spks);
}
}
- fn replenish_inner_index_keychain(&mut self, keychain: K, lookahead: u32) {
+ fn replenish_inner_index_keychain(
+ &mut self,
+ keychain: K,
+ lookahead: u32,
+ derived_spks: &mut impl Extend<Indexed<ScriptBuf>>,
+ ) {
if let Some(did) = self.keychain_to_descriptor_id.get(&keychain) {
- self.replenish_inner_index(*did, &keychain, lookahead);
+ self.replenish_inner_index(*did, &keychain, lookahead, derived_spks);
}
}
/// Syncs the state of the inner spk index after changes to a keychain
- fn replenish_inner_index(&mut self, did: DescriptorId, keychain: &K, lookahead: u32) {
+ fn replenish_inner_index(
+ &mut self,
+ did: DescriptorId,
+ keychain: &K,
+ lookahead: u32,
+ derived_spks: &mut impl Extend<Indexed<ScriptBuf>>,
+ ) {
let descriptor = self.descriptors.get(&did).expect("invariant");
- let next_store_index = self
+
+ let mut next_index = self
.inner
.all_spks()
.range(&(keychain.clone(), u32::MIN)..=&(keychain.clone(), u32::MAX))
.last()
.map_or(0, |((_, index), _)| *index + 1);
- let next_reveal_index = self.last_revealed.get(&did).map_or(0, |v| *v + 1);
- for (new_index, new_spk) in
- SpkIterator::new_with_range(descriptor, next_store_index..next_reveal_index + lookahead)
- {
+
+ // Exclusive: index to stop at.
+ let stop_index = if descriptor.has_wildcard() {
+ let next_reveal_index = self.last_revealed.get(&did).map_or(0, |v| *v + 1);
+ (next_reveal_index + lookahead).min(BIP32_MAX_INDEX)
+ } else {
+ 1
+ };
+
+ let cached_spk_iter = core::iter::from_fn({
+ let secp = Secp256k1::verification_only();
+ let _desc = &descriptor;
+ let spk_cache = self.spk_cache.entry(did).or_default();
+ let _i = &mut next_index;
+ move || -> Option<Indexed<ScriptBuf>> {
+ if *_i >= stop_index {
+ return None;
+ }
+ let spk_i = *_i;
+ *_i = spk_i.saturating_add(1);
+
+ if let Some(spk) = spk_cache.get(_i) {
+ return Some((spk_i, spk.clone()));
+ }
+ let spk = _desc
+ .derived_descriptor(&secp, spk_i)
+ .expect("The descriptor cannot have hardened derivation")
+ .script_pubkey();
+ derived_spks.extend(core::iter::once((spk_i, spk.clone())));
+ spk_cache.insert(spk_i, spk.clone());
+ Some((spk_i, spk.clone()))
+ }
+ });
+
+ for (new_index, new_spk) in cached_spk_iter {
let _inserted = self
.inner
.insert_spk((keychain.clone(), new_index), new_spk);
- debug_assert!(_inserted, "replenish lookahead: must not have existing spk: keychain={:?}, lookahead={}, next_store_index={}, next_reveal_index={}", keychain, lookahead, next_store_index, next_reveal_index);
+ debug_assert!(_inserted, "replenish lookahead: must not have existing spk: keychain={:?}, lookahead={}, next_index={}", keychain, lookahead, next_index);
}
}
let did = self.keychain_to_descriptor_id.get(&keychain)?;
self.last_revealed.insert(*did, next_index);
changeset.last_revealed.insert(*did, next_index);
- self.replenish_inner_index(*did, &keychain, self.lookahead);
+ self.replenish_inner_index(
+ *did,
+ &keychain,
+ self.lookahead,
+ changeset.spk_cache.entry(*did).or_default(),
+ );
}
let script = self
.inner
/// Applies the `ChangeSet<K>` to the [`KeychainTxOutIndex<K>`]
pub fn apply_changeset(&mut self, changeset: ChangeSet) {
- for (&desc_id, &index) in &changeset.last_revealed {
- let v = self.last_revealed.entry(desc_id).or_default();
+ for (did, index) in changeset.last_revealed {
+ let v = self.last_revealed.entry(did).or_default();
*v = index.max(*v);
- self.replenish_inner_index_did(desc_id, self.lookahead);
+ self.replenish_inner_index_did(did, self.lookahead, &mut Vec::new());
+ }
+ for (did, spks) in changeset.spk_cache {
+ self.spk_cache.entry(did).or_default().extend(spks);
}
}
}
/// The descriptor has already been assigned to a keychain so you can't assign it to another
DescriptorAlreadyAssigned {
/// The descriptor you have attempted to reassign
- descriptor: Descriptor<DescriptorPublicKey>,
+ descriptor: Box<Descriptor<DescriptorPublicKey>>,
/// The keychain that the descriptor is already assigned to
existing_assignment: K,
},
/// The keychain that you have attempted to reassign
keychain: K,
/// The descriptor that the keychain is already assigned to
- existing_assignment: Descriptor<DescriptorPublicKey>,
+ existing_assignment: Box<Descriptor<DescriptorPublicKey>>,
},
}
pub struct ChangeSet {
/// Contains for each descriptor_id the last revealed index of derivation
pub last_revealed: BTreeMap<DescriptorId, u32>,
+
+ /// Spk cache.
+ #[cfg_attr(feature = "serde", serde(default))]
+ pub spk_cache: BTreeMap<DescriptorId, BTreeMap<u32, ScriptBuf>>,
}
impl Merge for ChangeSet {
}
}
}
+
+ for (did, spks) in other.spk_cache {
+ self.spk_cache.entry(did).or_default().extend(spks);
+ }
}
/// Returns whether the changeset are empty.
fn is_empty(&self) -> bool {
- self.last_revealed.is_empty()
+ self.last_revealed.is_empty() && self.spk_cache.is_empty()
}
}
pub const SCHEMA_NAME: &'static str = "bdk_keychaintxout";
/// Name for table that stores last revealed indices per descriptor id.
pub const LAST_REVEALED_TABLE_NAME: &'static str = "bdk_descriptor_last_revealed";
+ /// Name for table that stores derived spks.
+ pub const DERIVED_SPKS_TABLE_NAME: &'static str = "bdk_descriptor_derived_spks";
/// Get v0 of sqlite [keychain_txout::ChangeSet] schema
pub fn schema_v0() -> String {
)
}
+ /// Get v1 of sqlite [keychain_txout::ChangeSet] schema
+ pub fn schema_v1() -> String {
+ format!(
+ "CREATE TABLE {} ( \
+ descriptor_id TEXT NOT NULL REFERENCES {}, \
+ spk_index INTEGER NOT NULL, \
+ spk BLOB NOT NULL, \
+ PRIMARY KEY (descriptor_id, index) \
+ ) STRICT",
+ Self::DERIVED_SPKS_TABLE_NAME,
+ Self::LAST_REVEALED_TABLE_NAME,
+ )
+ }
+
/// Initialize sqlite tables for persisting
/// [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex).
pub fn init_sqlite_tables(db_tx: &rusqlite::Transaction) -> rusqlite::Result<()> {
- migrate_schema(db_tx, Self::SCHEMA_NAME, &[&Self::schema_v0()])
+ migrate_schema(
+ db_tx,
+ Self::SCHEMA_NAME,
+ &[&Self::schema_v0(), &Self::schema_v1()],
+ )
}
/// Construct [`KeychainTxOutIndex`](keychain_txout::KeychainTxOutIndex) from sqlite database
changeset.last_revealed.insert(descriptor_id, last_revealed);
}
+ let mut statement = db_tx.prepare(&format!(
+ "SELECT descriptor_id, spk_index, spk FROM {}",
+ Self::DERIVED_SPKS_TABLE_NAME
+ ))?;
+ let row_iter = statement.query_map([], |row| {
+ Ok((
+ row.get::<_, Impl<DescriptorId>>("descriptor_id")?,
+ row.get::<_, u32>("spk_index")?,
+ row.get::<_, Impl<bitcoin::ScriptBuf>>("spk")?,
+ ))
+ })?;
+ for row in row_iter {
+ let (Impl(descriptor_id), spk_index, Impl(spk)) = row?;
+ changeset
+ .spk_cache
+ .entry(descriptor_id)
+ .or_default()
+ .insert(spk_index, spk);
+ }
+
Ok(changeset)
}
})?;
}
+ let mut statement = db_tx.prepare_cached(&format!(
+ "REPLACE INTO {}(descriptor_id, spk_index, spk) VALUES(:descriptor_id, :spk_index, :spk)",
+ Self::DERIVED_SPKS_TABLE_NAME,
+ ))?;
+ for (&descriptor_id, spks) in &self.spk_cache {
+ for (&spk_index, spk) in spks {
+ statement.execute(named_params! {
+ ":descriptor_id": Impl(descriptor_id),
+ ":spk_index": spk_index,
+ ":spk": Impl(spk.clone()),
+ })?;
+ }
+ }
+
Ok(())
}
}
indexer::keychain_txout::KeychainTxOutIndex,
local_chain::LocalChain,
tx_graph, Balance, CanonicalizationParams, ChainPosition, ConfirmationBlockTime, DescriptorExt,
+ SpkIterator,
};
use bdk_testenv::{
block_id, hash,
.expect("must be valid");
let spk_0 = descriptor.at_derivation_index(0).unwrap().script_pubkey();
let spk_1 = descriptor.at_derivation_index(9).unwrap().script_pubkey();
+ let lookahead = 10;
let mut graph = IndexedTxGraph::<ConfirmationBlockTime, KeychainTxOutIndex<()>>::new(
- KeychainTxOutIndex::new(10),
+ KeychainTxOutIndex::new(lookahead),
);
- let _ = graph
+ let (is_inserted, changeset) = graph
.index
.insert_descriptor((), descriptor.clone())
.unwrap();
+ assert!(is_inserted);
+ assert_eq!(
+ changeset,
+ keychain_txout::ChangeSet {
+ spk_cache: [(
+ descriptor.descriptor_id(),
+ SpkIterator::new_with_range(&descriptor, 0..lookahead).collect()
+ )]
+ .into(),
+ ..Default::default()
+ }
+ );
let tx_a = Transaction {
output: vec![
},
indexer: keychain_txout::ChangeSet {
last_revealed: [(descriptor.descriptor_id(), 9_u32)].into(),
+ spk_cache: [(descriptor.descriptor_id(), {
+ let index_after_spk_1 = 9 /* index of spk_1 */ + 1;
+ SpkIterator::new_with_range(
+ &descriptor,
+ index_after_spk_1..index_after_spk_1 + lookahead,
+ )
+ .collect()
+ })]
+ .into(),
},
};
tx_graph: changeset.tx_graph,
indexer: keychain_txout::ChangeSet {
last_revealed: changeset.indexer.last_revealed,
+ spk_cache: [(
+ descriptor.descriptor_id(),
+ SpkIterator::new_with_range(&descriptor, 0..=9 /* index of spk_1*/ + lookahead)
+ .collect(),
+ )]
+ .into(),
},
};
KeychainTxOutIndex::new(10),
);
- assert!(graph
- .index
- .insert_descriptor("keychain_1".into(), desc_1)
- .unwrap());
- assert!(graph
- .index
- .insert_descriptor("keychain_2".into(), desc_2)
- .unwrap());
+ assert!(
+ graph
+ .index
+ .insert_descriptor("keychain_1".into(), desc_1)
+ .unwrap()
+ .0
+ );
+ assert!(
+ graph
+ .index
+ .insert_descriptor("keychain_2".into(), desc_2)
+ .unwrap()
+ .0
+ );
// Get trusted and untrusted addresses
use bdk_chain::{
collections::BTreeMap,
indexer::keychain_txout::{ChangeSet, KeychainTxOutIndex},
- DescriptorExt, DescriptorId, Indexer, Merge,
+ DescriptorExt, DescriptorId, Indexer, Merge, SpkIterator,
};
use bdk_testenv::{
hash,
let mut lhs = ChangeSet {
last_revealed: lhs_di,
+ ..Default::default()
};
let rhs = ChangeSet {
last_revealed: rhs_di,
+ ..Default::default()
};
lhs.merge(rhs);
#[test]
fn test_set_all_derivation_indices() {
+ let lookahead = 0;
let external_descriptor = parse_descriptor(DESCRIPTORS[0]);
let internal_descriptor = parse_descriptor(DESCRIPTORS[1]);
- let mut txout_index =
- init_txout_index(external_descriptor.clone(), internal_descriptor.clone(), 0);
+ let mut txout_index = init_txout_index(
+ external_descriptor.clone(),
+ internal_descriptor.clone(),
+ lookahead,
+ );
let derive_to: BTreeMap<_, _> =
[(TestKeychain::External, 12), (TestKeychain::Internal, 24)].into();
let last_revealed: BTreeMap<_, _> = [
(internal_descriptor.descriptor_id(), 24),
]
.into();
+ let spk_cache: BTreeMap<DescriptorId, BTreeMap<u32, ScriptBuf>> = [
+ (
+ external_descriptor.descriptor_id(),
+ SpkIterator::new_with_range(&external_descriptor, 0..=12).collect(),
+ ),
+ (
+ internal_descriptor.descriptor_id(),
+ SpkIterator::new_with_range(&internal_descriptor, 0..=24).collect(),
+ ),
+ ]
+ .into();
assert_eq!(
txout_index.reveal_to_target_multi(&derive_to),
ChangeSet {
- last_revealed: last_revealed.clone()
+ last_revealed: last_revealed.clone(),
+ spk_cache: spk_cache.clone(),
}
);
assert_eq!(txout_index.last_revealed_indices(), derive_to);
}
None => target,
};
- index.lookahead_to_target(keychain.clone(), target);
+ index.lookahead_to_target(keychain.clone(), target, &mut Vec::new());
let keys: Vec<_> = (0..)
.take_while(|&i| index.spk_at_index(keychain.clone(), i).is_some())
.collect();
let changesets: &[ChangeSet] = &[
ChangeSet {
last_revealed: [(desc.descriptor_id(), 10)].into(),
+ ..Default::default()
},
ChangeSet {
last_revealed: [(desc.descriptor_id(), 12)].into(),
+ ..Default::default()
},
];
let mut indexer_a = KeychainTxOutIndex::<TestKeychain>::new(0);
- indexer_a
+ let _ = indexer_a
.insert_descriptor(TestKeychain::External, desc.clone())
.expect("must insert keychain");
for changeset in changesets {
}
let mut indexer_b = KeychainTxOutIndex::<TestKeychain>::new(0);
- indexer_b
+ let _ = indexer_b
.insert_descriptor(TestKeychain::External, desc.clone())
.expect("must insert keychain");
let aggregate_changesets = changesets