use crate::{
alloc::{boxed::Box, collections::VecDeque, vec::Vec},
collections::{BTreeMap, HashMap, HashSet},
CheckPoint, ConfirmationBlockTime, Indexed,
};
use bitcoin::{OutPoint, Script, ScriptBuf, Txid};
type InspectSync<I> = dyn FnMut(SyncItem<I>, SyncProgress) + Send + 'static;
type InspectFullScan<K> = dyn FnMut(K, u32, &Script) + Send + 'static;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SyncItem<'i, I> {
Spk(I, &'i Script),
Txid(Txid),
OutPoint(OutPoint),
}
impl<I: core::fmt::Debug + core::any::Any> core::fmt::Display for SyncItem<'_, I> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
SyncItem::Spk(i, spk) => {
if (i as &dyn core::any::Any).is::<()>() {
write!(f, "script '{spk}'")
} else {
write!(f, "script {i:?} '{spk}'")
}
}
SyncItem::Txid(txid) => write!(f, "txid '{txid}'"),
SyncItem::OutPoint(op) => write!(f, "outpoint '{op}'"),
}
}
}
#[derive(Debug, Clone)]
pub struct SyncProgress {
pub spks_consumed: usize,
pub spks_remaining: usize,
pub txids_consumed: usize,
pub txids_remaining: usize,
pub outpoints_consumed: usize,
pub outpoints_remaining: usize,
}
impl SyncProgress {
pub fn total(&self) -> usize {
self.total_spks() + self.total_txids() + self.total_outpoints()
}
pub fn total_spks(&self) -> usize {
self.spks_consumed + self.spks_remaining
}
pub fn total_txids(&self) -> usize {
self.txids_consumed + self.txids_remaining
}
pub fn total_outpoints(&self) -> usize {
self.outpoints_consumed + self.outpoints_remaining
}
pub fn consumed(&self) -> usize {
self.spks_consumed + self.txids_consumed + self.outpoints_consumed
}
pub fn remaining(&self) -> usize {
self.spks_remaining + self.txids_remaining + self.outpoints_remaining
}
}
#[derive(Debug, Clone)]
pub struct SpkWithExpectedTxids {
pub spk: ScriptBuf,
pub expected_txids: HashSet<Txid>,
}
impl From<ScriptBuf> for SpkWithExpectedTxids {
fn from(spk: ScriptBuf) -> Self {
Self {
spk,
expected_txids: HashSet::new(),
}
}
}
#[must_use]
pub struct SyncRequestBuilder<I = ()> {
inner: SyncRequest<I>,
}
impl SyncRequestBuilder<()> {
pub fn spks(self, spks: impl IntoIterator<Item = ScriptBuf>) -> Self {
self.spks_with_indexes(spks.into_iter().map(|spk| ((), spk)))
}
}
impl<I> SyncRequestBuilder<I> {
pub fn chain_tip(mut self, cp: CheckPoint) -> Self {
self.inner.chain_tip = Some(cp);
self
}
pub fn spks_with_indexes(mut self, spks: impl IntoIterator<Item = (I, ScriptBuf)>) -> Self {
self.inner.spks.extend(spks);
self
}
pub fn expected_spk_txids(mut self, txs: impl IntoIterator<Item = (ScriptBuf, Txid)>) -> Self {
for (spk, txid) in txs {
self.inner
.spk_expected_txids
.entry(spk)
.or_default()
.insert(txid);
}
self
}
pub fn txids(mut self, txids: impl IntoIterator<Item = Txid>) -> Self {
self.inner.txids.extend(txids);
self
}
pub fn outpoints(mut self, outpoints: impl IntoIterator<Item = OutPoint>) -> Self {
self.inner.outpoints.extend(outpoints);
self
}
pub fn inspect<F>(mut self, inspect: F) -> Self
where
F: FnMut(SyncItem<I>, SyncProgress) + Send + 'static,
{
self.inner.inspect = Box::new(inspect);
self
}
pub fn build(self) -> SyncRequest<I> {
self.inner
}
}
#[must_use]
pub struct SyncRequest<I = ()> {
start_time: u64,
chain_tip: Option<CheckPoint>,
spks: VecDeque<(I, ScriptBuf)>,
spks_consumed: usize,
spk_expected_txids: HashMap<ScriptBuf, HashSet<Txid>>,
txids: VecDeque<Txid>,
txids_consumed: usize,
outpoints: VecDeque<OutPoint>,
outpoints_consumed: usize,
inspect: Box<InspectSync<I>>,
}
impl<I> From<SyncRequestBuilder<I>> for SyncRequest<I> {
fn from(builder: SyncRequestBuilder<I>) -> Self {
builder.inner
}
}
impl<I> SyncRequest<I> {
pub fn builder_at(start_time: u64) -> SyncRequestBuilder<I> {
SyncRequestBuilder {
inner: Self {
start_time,
chain_tip: None,
spks: VecDeque::new(),
spks_consumed: 0,
spk_expected_txids: HashMap::new(),
txids: VecDeque::new(),
txids_consumed: 0,
outpoints: VecDeque::new(),
outpoints_consumed: 0,
inspect: Box::new(|_, _| ()),
},
}
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn builder() -> SyncRequestBuilder<I> {
let start_time = std::time::UNIX_EPOCH
.elapsed()
.expect("failed to get current timestamp")
.as_secs();
Self::builder_at(start_time)
}
pub fn start_time(&self) -> u64 {
self.start_time
}
pub fn progress(&self) -> SyncProgress {
SyncProgress {
spks_consumed: self.spks_consumed,
spks_remaining: self.spks.len(),
txids_consumed: self.txids_consumed,
txids_remaining: self.txids.len(),
outpoints_consumed: self.outpoints_consumed,
outpoints_remaining: self.outpoints.len(),
}
}
pub fn chain_tip(&self) -> Option<CheckPoint> {
self.chain_tip.clone()
}
pub fn next_spk_with_expected_txids(&mut self) -> Option<SpkWithExpectedTxids> {
let (i, next_spk) = self.spks.pop_front()?;
self.spks_consumed += 1;
self._call_inspect(SyncItem::Spk(i, next_spk.as_script()));
let spk_history = self
.spk_expected_txids
.get(&next_spk)
.cloned()
.unwrap_or_default();
Some(SpkWithExpectedTxids {
spk: next_spk,
expected_txids: spk_history,
})
}
pub fn next_txid(&mut self) -> Option<Txid> {
let txid = self.txids.pop_front()?;
self.txids_consumed += 1;
self._call_inspect(SyncItem::Txid(txid));
Some(txid)
}
pub fn next_outpoint(&mut self) -> Option<OutPoint> {
let outpoint = self.outpoints.pop_front()?;
self.outpoints_consumed += 1;
self._call_inspect(SyncItem::OutPoint(outpoint));
Some(outpoint)
}
pub fn iter_spks_with_expected_txids(
&mut self,
) -> impl ExactSizeIterator<Item = SpkWithExpectedTxids> + '_ {
SyncIter::<I, SpkWithExpectedTxids>::new(self)
}
pub fn iter_txids(&mut self) -> impl ExactSizeIterator<Item = Txid> + '_ {
SyncIter::<I, Txid>::new(self)
}
pub fn iter_outpoints(&mut self) -> impl ExactSizeIterator<Item = OutPoint> + '_ {
SyncIter::<I, OutPoint>::new(self)
}
fn _call_inspect(&mut self, item: SyncItem<I>) {
let progress = self.progress();
(*self.inspect)(item, progress);
}
}
#[must_use]
#[derive(Debug)]
pub struct SyncResponse<A = ConfirmationBlockTime> {
pub tx_update: crate::TxUpdate<A>,
pub chain_update: Option<CheckPoint>,
}
impl<A> Default for SyncResponse<A> {
fn default() -> Self {
Self {
tx_update: Default::default(),
chain_update: Default::default(),
}
}
}
impl<A> SyncResponse<A> {
pub fn is_empty(&self) -> bool {
self.tx_update.is_empty() && self.chain_update.is_none()
}
}
#[must_use]
pub struct FullScanRequestBuilder<K> {
inner: FullScanRequest<K>,
}
impl<K: Ord> FullScanRequestBuilder<K> {
pub fn chain_tip(mut self, tip: CheckPoint) -> Self {
self.inner.chain_tip = Some(tip);
self
}
pub fn spks_for_keychain(
mut self,
keychain: K,
spks: impl IntoIterator<IntoIter = impl Iterator<Item = Indexed<ScriptBuf>> + Send + 'static>,
) -> Self {
self.inner
.spks_by_keychain
.insert(keychain, Box::new(spks.into_iter()));
self
}
pub fn inspect<F>(mut self, inspect: F) -> Self
where
F: FnMut(K, u32, &Script) + Send + 'static,
{
self.inner.inspect = Box::new(inspect);
self
}
pub fn build(self) -> FullScanRequest<K> {
self.inner
}
}
#[must_use]
pub struct FullScanRequest<K> {
start_time: u64,
chain_tip: Option<CheckPoint>,
spks_by_keychain: BTreeMap<K, Box<dyn Iterator<Item = Indexed<ScriptBuf>> + Send>>,
inspect: Box<InspectFullScan<K>>,
}
impl<K> From<FullScanRequestBuilder<K>> for FullScanRequest<K> {
fn from(builder: FullScanRequestBuilder<K>) -> Self {
builder.inner
}
}
impl<K: Ord + Clone> FullScanRequest<K> {
pub fn builder_at(start_time: u64) -> FullScanRequestBuilder<K> {
FullScanRequestBuilder {
inner: Self {
start_time,
chain_tip: None,
spks_by_keychain: BTreeMap::new(),
inspect: Box::new(|_, _, _| ()),
},
}
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn builder() -> FullScanRequestBuilder<K> {
let start_time = std::time::UNIX_EPOCH
.elapsed()
.expect("failed to get current timestamp")
.as_secs();
Self::builder_at(start_time)
}
pub fn start_time(&self) -> u64 {
self.start_time
}
pub fn chain_tip(&self) -> Option<CheckPoint> {
self.chain_tip.clone()
}
pub fn keychains(&self) -> Vec<K> {
self.spks_by_keychain.keys().cloned().collect()
}
pub fn next_spk(&mut self, keychain: K) -> Option<Indexed<ScriptBuf>> {
self.iter_spks(keychain).next()
}
pub fn iter_spks(&mut self, keychain: K) -> impl Iterator<Item = Indexed<ScriptBuf>> + '_ {
let spks = self.spks_by_keychain.get_mut(&keychain);
let inspect = &mut self.inspect;
KeychainSpkIter {
keychain,
spks,
inspect,
}
}
}
#[must_use]
#[derive(Debug)]
pub struct FullScanResponse<K, A = ConfirmationBlockTime> {
pub tx_update: crate::TxUpdate<A>,
pub last_active_indices: BTreeMap<K, u32>,
pub chain_update: Option<CheckPoint>,
}
impl<K, A> Default for FullScanResponse<K, A> {
fn default() -> Self {
Self {
tx_update: Default::default(),
chain_update: Default::default(),
last_active_indices: Default::default(),
}
}
}
impl<K, A> FullScanResponse<K, A> {
pub fn is_empty(&self) -> bool {
self.tx_update.is_empty()
&& self.last_active_indices.is_empty()
&& self.chain_update.is_none()
}
}
struct KeychainSpkIter<'r, K> {
keychain: K,
spks: Option<&'r mut Box<dyn Iterator<Item = Indexed<ScriptBuf>> + Send>>,
inspect: &'r mut Box<InspectFullScan<K>>,
}
impl<K: Ord + Clone> Iterator for KeychainSpkIter<'_, K> {
type Item = Indexed<ScriptBuf>;
fn next(&mut self) -> Option<Self::Item> {
let (i, spk) = self.spks.as_mut()?.next()?;
(*self.inspect)(self.keychain.clone(), i, &spk);
Some((i, spk))
}
}
struct SyncIter<'r, I, Item> {
request: &'r mut SyncRequest<I>,
marker: core::marker::PhantomData<Item>,
}
impl<'r, I, Item> SyncIter<'r, I, Item> {
fn new(request: &'r mut SyncRequest<I>) -> Self {
Self {
request,
marker: core::marker::PhantomData,
}
}
}
impl<'r, I, Item> ExactSizeIterator for SyncIter<'r, I, Item> where SyncIter<'r, I, Item>: Iterator {}
impl<I> Iterator for SyncIter<'_, I, SpkWithExpectedTxids> {
type Item = SpkWithExpectedTxids;
fn next(&mut self) -> Option<Self::Item> {
self.request.next_spk_with_expected_txids()
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.request.spks.len();
(remaining, Some(remaining))
}
}
impl<I> Iterator for SyncIter<'_, I, Txid> {
type Item = Txid;
fn next(&mut self) -> Option<Self::Item> {
self.request.next_txid()
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.request.txids.len();
(remaining, Some(remaining))
}
}
impl<I> Iterator for SyncIter<'_, I, OutPoint> {
type Item = OutPoint;
fn next(&mut self) -> Option<Self::Item> {
self.request.next_outpoint()
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.request.outpoints.len();
(remaining, Some(remaining))
}
}