//! Helper types for spk-based blockchain clients. use core::{fmt::Debug, ops::RangeBounds}; use alloc::{boxed::Box, collections::BTreeMap, vec::Vec}; use bitcoin::{OutPoint, Script, ScriptBuf, Txid}; use crate::{local_chain::CheckPoint, ConfirmationTimeHeightAnchor, TxGraph}; /// Data required to perform a spk-based blockchain client sync. /// /// A client sync fetches relevant chain data for a known list of scripts, transaction ids and /// outpoints. The sync process also updates the chain from the given [`CheckPoint`]. pub struct SyncRequest { /// A checkpoint for the current chain [`LocalChain::tip`]. /// The sync process will return a new chain update that extends this tip. /// /// [`LocalChain::tip`]: crate::local_chain::LocalChain::tip pub chain_tip: CheckPoint, /// Transactions that spend from or to these indexed script pubkeys. pub spks: Box + Send>, /// Transactions with these txids. pub txids: Box + Send>, /// Transactions with these outpoints or spent from these outpoints. pub outpoints: Box + Send>, } impl SyncRequest { /// Construct a new [`SyncRequest`] from a given `cp` tip. pub fn from_chain_tip(cp: CheckPoint) -> Self { Self { chain_tip: cp, spks: Box::new(core::iter::empty()), txids: Box::new(core::iter::empty()), outpoints: Box::new(core::iter::empty()), } } /// Set the [`Script`]s that will be synced against. /// /// This consumes the [`SyncRequest`] and returns the updated one. #[must_use] pub fn set_spks( mut self, spks: impl IntoIterator + Send + 'static>, ) -> Self { self.spks = Box::new(spks.into_iter()); self } /// Set the [`Txid`]s that will be synced against. /// /// This consumes the [`SyncRequest`] and returns the updated one. #[must_use] pub fn set_txids( mut self, txids: impl IntoIterator + Send + 'static>, ) -> Self { self.txids = Box::new(txids.into_iter()); self } /// Set the [`OutPoint`]s that will be synced against. /// /// This consumes the [`SyncRequest`] and returns the updated one. #[must_use] pub fn set_outpoints( mut self, outpoints: impl IntoIterator + Send + 'static>, ) -> Self { self.outpoints = Box::new(outpoints.into_iter()); self } /// Chain on additional [`Script`]s that will be synced against. /// /// This consumes the [`SyncRequest`] and returns the updated one. #[must_use] pub fn chain_spks( mut self, spks: impl IntoIterator< IntoIter = impl Iterator + Send + 'static, Item = ScriptBuf, >, ) -> Self { self.spks = Box::new(self.spks.chain(spks)); self } /// Chain on additional [`Txid`]s that will be synced against. /// /// This consumes the [`SyncRequest`] and returns the updated one. #[must_use] pub fn chain_txids( mut self, txids: impl IntoIterator + Send + 'static, Item = Txid>, ) -> Self { self.txids = Box::new(self.txids.chain(txids)); self } /// Chain on additional [`OutPoint`]s that will be synced against. /// /// This consumes the [`SyncRequest`] and returns the updated one. #[must_use] pub fn chain_outpoints( mut self, outpoints: impl IntoIterator< IntoIter = impl Iterator + Send + 'static, Item = OutPoint, >, ) -> Self { self.outpoints = Box::new(self.outpoints.chain(outpoints)); self } /// Add a closure that will be called for each [`Script`] synced in this request. /// /// This consumes the [`SyncRequest`] and returns the updated one. #[must_use] pub fn inspect_spks(mut self, inspect: impl Fn(&Script) + Send + Sync + 'static) -> Self { self.spks = Box::new(self.spks.inspect(move |spk| inspect(spk))); self } /// Add a closure that will be called for each [`Txid`] synced in this request. /// /// This consumes the [`SyncRequest`] and returns the updated one. #[must_use] pub fn inspect_txids(mut self, inspect: impl Fn(&Txid) + Send + Sync + 'static) -> Self { self.txids = Box::new(self.txids.inspect(move |txid| inspect(txid))); self } /// Add a closure that will be called for each [`OutPoint`] synced in this request. /// /// This consumes the [`SyncRequest`] and returns the updated one. #[must_use] pub fn inspect_outpoints( mut self, inspect: impl Fn(&OutPoint) + Send + Sync + 'static, ) -> Self { self.outpoints = Box::new(self.outpoints.inspect(move |op| inspect(op))); self } /// Populate the request with revealed script pubkeys from `index` with the given `spk_range`. /// /// This consumes the [`SyncRequest`] and returns the updated one. #[cfg(feature = "miniscript")] #[must_use] pub fn populate_with_revealed_spks( self, index: &crate::keychain::KeychainTxOutIndex, spk_range: impl RangeBounds, ) -> Self { use alloc::borrow::ToOwned; self.chain_spks( index .revealed_spks(spk_range) .map(|(_, _, spk)| spk.to_owned()) .collect::>(), ) } } /// Data returned from a spk-based blockchain client sync. /// /// See also [`SyncRequest`]. pub struct SyncResult { /// The update to apply to the receiving [`TxGraph`]. pub graph_update: TxGraph, /// The update to apply to the receiving [`LocalChain`](crate::local_chain::LocalChain). pub chain_update: CheckPoint, } /// Data required to perform a spk-based blockchain client full scan. /// /// A client full scan iterates through all the scripts for the given keychains, fetching relevant /// data until some stop gap number of scripts is found that have no data. This operation is /// generally only used when importing or restoring previously used keychains in which the list of /// used scripts is not known. The full scan process also updates the chain from the given [`CheckPoint`]. pub struct FullScanRequest { /// A checkpoint for the current [`LocalChain::tip`]. /// The full scan process will return a new chain update that extends this tip. /// /// [`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 + Send>>, } impl FullScanRequest { /// Construct a new [`FullScanRequest`] from a given `chain_tip`. #[must_use] pub fn from_chain_tip(chain_tip: CheckPoint) -> Self { Self { chain_tip, spks_by_keychain: BTreeMap::new(), } } /// Construct a new [`FullScanRequest`] from a given `chain_tip` and `index`. /// /// Unbounded script pubkey iterators for each keychain (`K`) are extracted using /// [`KeychainTxOutIndex::all_unbounded_spk_iters`] and is used to populate the /// [`FullScanRequest`]. /// /// [`KeychainTxOutIndex::all_unbounded_spk_iters`]: crate::keychain::KeychainTxOutIndex::all_unbounded_spk_iters #[cfg(feature = "miniscript")] #[must_use] pub fn from_keychain_txout_index( chain_tip: CheckPoint, index: &crate::keychain::KeychainTxOutIndex, ) -> Self where K: Debug, { let mut req = Self::from_chain_tip(chain_tip); for (keychain, spks) in index.all_unbounded_spk_iters() { req = req.set_spks_for_keychain(keychain, spks); } req } /// Set the [`Script`]s for a given `keychain`. /// /// This consumes the [`FullScanRequest`] and returns the updated one. #[must_use] pub fn set_spks_for_keychain( mut self, keychain: K, spks: impl IntoIterator + Send + 'static>, ) -> Self { self.spks_by_keychain .insert(keychain, Box::new(spks.into_iter())); self } /// Chain on additional [`Script`]s that will be synced against. /// /// This consumes the [`FullScanRequest`] and returns the updated one. #[must_use] pub fn chain_spks_for_keychain( mut self, keychain: K, spks: impl IntoIterator + Send + 'static>, ) -> Self { match self.spks_by_keychain.remove(&keychain) { Some(keychain_spks) => self .spks_by_keychain .insert(keychain, Box::new(keychain_spks.chain(spks.into_iter()))), None => self .spks_by_keychain .insert(keychain, Box::new(spks.into_iter())), }; self } /// Add a closure that will be called for every [`Script`] previously added to any keychain in /// this request. /// /// This consumes the [`SyncRequest`] and returns the updated one. #[must_use] pub fn inspect_spks_for_all_keychains( mut self, inspect: impl FnMut(K, u32, &Script) + Send + Sync + Clone + 'static, ) -> Self where K: Send + 'static, { for (keychain, spks) in core::mem::take(&mut self.spks_by_keychain) { let mut inspect = inspect.clone(); self.spks_by_keychain.insert( keychain.clone(), Box::new(spks.inspect(move |(i, spk)| inspect(keychain.clone(), *i, spk))), ); } self } /// Add a closure that will be called for every [`Script`] previously added to a given /// `keychain` in this request. /// /// This consumes the [`SyncRequest`] and returns the updated one. #[must_use] pub fn inspect_spks_for_keychain( mut self, keychain: K, mut inspect: impl FnMut(u32, &Script) + Send + Sync + 'static, ) -> Self where K: Send + 'static, { if let Some(spks) = self.spks_by_keychain.remove(&keychain) { self.spks_by_keychain.insert( keychain, Box::new(spks.inspect(move |(i, spk)| inspect(*i, spk))), ); } self } } /// Data returned from a spk-based blockchain client full scan. /// /// See also [`FullScanRequest`]. pub struct FullScanResult { /// The update to apply to the receiving [`LocalChain`](crate::local_chain::LocalChain). pub graph_update: TxGraph, /// The update to apply to the receiving [`TxGraph`]. pub chain_update: CheckPoint, /// Last active indices for the corresponding keychains (`K`). pub last_active_indices: BTreeMap, }