316 lines
11 KiB
Rust
316 lines
11 KiB
Rust
|
//! 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<dyn Iterator<Item = ScriptBuf> + Send>,
|
||
|
/// Transactions with these txids.
|
||
|
pub txids: Box<dyn Iterator<Item = Txid> + Send>,
|
||
|
/// Transactions with these outpoints or spent from these outpoints.
|
||
|
pub outpoints: Box<dyn Iterator<Item = OutPoint> + 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<IntoIter = impl Iterator<Item = ScriptBuf> + 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<IntoIter = impl Iterator<Item = Txid> + 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<IntoIter = impl Iterator<Item = OutPoint> + 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<Item = ScriptBuf> + 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<IntoIter = impl Iterator<Item = Txid> + 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<Item = OutPoint> + 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<K: Clone + Ord + Debug + Send + Sync>(
|
||
|
self,
|
||
|
index: &crate::keychain::KeychainTxOutIndex<K>,
|
||
|
spk_range: impl RangeBounds<K>,
|
||
|
) -> Self {
|
||
|
use alloc::borrow::ToOwned;
|
||
|
self.chain_spks(
|
||
|
index
|
||
|
.revealed_spks(spk_range)
|
||
|
.map(|(_, _, spk)| spk.to_owned())
|
||
|
.collect::<Vec<_>>(),
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// Data returned from a spk-based blockchain client sync.
|
||
|
///
|
||
|
/// See also [`SyncRequest`].
|
||
|
pub struct SyncResult<A = ConfirmationTimeHeightAnchor> {
|
||
|
/// The update to apply to the receiving [`TxGraph`].
|
||
|
pub graph_update: TxGraph<A>,
|
||
|
/// 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<K> {
|
||
|
/// 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<K, Box<dyn Iterator<Item = (u32, ScriptBuf)> + Send>>,
|
||
|
}
|
||
|
|
||
|
impl<K: Ord + Clone> FullScanRequest<K> {
|
||
|
/// 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<K>,
|
||
|
) -> 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<IntoIter = impl Iterator<Item = (u32, ScriptBuf)> + 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<IntoIter = impl Iterator<Item = (u32, ScriptBuf)> + 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<K> {
|
||
|
/// The update to apply to the receiving [`LocalChain`](crate::local_chain::LocalChain).
|
||
|
pub graph_update: TxGraph<ConfirmationTimeHeightAnchor>,
|
||
|
/// 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<K, u32>,
|
||
|
}
|