#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
use std::collections::HashSet;
use bdk_wallet::chain::BlockId;
use bdk_wallet::chain::CheckPoint;
pub use bdk_wallet::Update;
use bdk_wallet::chain::{keychain_txout::KeychainTxOutIndex, IndexedTxGraph};
use bdk_wallet::chain::{ConfirmationBlockTime, TxUpdate};
use bdk_wallet::KeychainKind;
pub extern crate bip157;
use bip157::chain::BlockHeaderChanges;
use bip157::ScriptBuf;
#[doc(inline)]
pub use bip157::{
BlockHash, ClientError, FeeRate, HeaderCheckpoint, Info, Node, RejectPayload, RejectReason,
Requester, TrustedPeer, TxBroadcast, TxBroadcastPolicy, Warning, Wtxid,
};
use bip157::{Event, SyncUpdate};
#[doc(inline)]
pub use bip157::Receiver;
#[doc(inline)]
pub use bip157::UnboundedReceiver;
#[doc(inline)]
pub use builder::BuilderExt;
pub mod builder;
#[derive(Debug)]
pub struct LightClient {
pub requester: Requester,
pub info_subscriber: Receiver<Info>,
pub warning_subscriber: UnboundedReceiver<Warning>,
pub update_subscriber: UpdateSubscriber,
pub node: Node,
}
#[derive(Debug)]
pub struct UpdateSubscriber {
requester: Requester,
receiver: UnboundedReceiver<Event>,
cp: CheckPoint,
graph: IndexedTxGraph<ConfirmationBlockTime, KeychainTxOutIndex<KeychainKind>>,
queued_blocks: Vec<BlockHash>,
spk_cache: HashSet<ScriptBuf>,
}
impl UpdateSubscriber {
fn new(
requester: Requester,
scan_type: ScanType,
receiver: UnboundedReceiver<Event>,
cp: CheckPoint,
graph: IndexedTxGraph<ConfirmationBlockTime, KeychainTxOutIndex<KeychainKind>>,
) -> Self {
let spk_cache = match scan_type {
ScanType::Sync => Self::peek_scripts(&graph.index, graph.index.lookahead()),
ScanType::Recovery {
used_script_index,
checkpoint: _,
} => Self::peek_scripts(&graph.index, used_script_index),
};
Self {
requester,
receiver,
cp,
graph,
queued_blocks: Vec::new(),
spk_cache,
}
}
pub async fn update(&mut self) -> Result<Update, UpdateError> {
let mut cp = self.cp.clone();
while let Some(message) = self.receiver.recv().await {
match message {
Event::IndexedFilter(filter) => {
let block_hash = filter.block_hash();
if filter.contains_any(self.spk_cache.iter()) {
self.queued_blocks.push(block_hash);
}
}
Event::ChainUpdate(BlockHeaderChanges::Connected(at)) => {
let block_id = BlockId {
hash: at.block_hash(),
height: at.height,
};
cp = cp.insert(block_id);
}
Event::ChainUpdate(BlockHeaderChanges::Reorganized {
accepted,
reorganized: _,
}) => {
for header in accepted {
let block_id = BlockId {
hash: header.block_hash(),
height: header.height,
};
cp = cp.insert(block_id);
}
}
Event::FiltersSynced(SyncUpdate {
tip: _,
recent_history: _,
}) => {
for hash in core::mem::take(&mut self.queued_blocks) {
let block = self
.requester
.get_block(hash)
.await
.map_err(|_| UpdateError::NodeStopped)?;
let height = block.height;
let block = block.block;
let _ = self.graph.apply_block_relevant(&block, height);
}
self.cp = cp;
Self::peek_scripts(&self.graph.index, self.graph.index.lookahead());
return Ok(self.get_scan_response());
}
_ => (),
}
}
Err(UpdateError::NodeStopped)
}
fn get_scan_response(&mut self) -> Update {
let tx_update = TxUpdate::from(self.graph.graph().clone());
let graph = core::mem::take(&mut self.graph);
let last_active_indices = graph.index.last_used_indices();
self.graph = IndexedTxGraph::new(graph.index);
Update {
tx_update,
last_active_indices,
chain: Some(self.cp.clone()),
}
}
fn peek_scripts(
keychain: &KeychainTxOutIndex<KeychainKind>,
to_index: u32,
) -> HashSet<ScriptBuf> {
let mut spk_cache = HashSet::new();
let last_revealed = keychain.last_revealed_indices();
let ext_index = last_revealed
.get(&KeychainKind::External)
.copied()
.unwrap_or(0);
let unbounded_ext_spk_iter = keychain
.unbounded_spk_iter(KeychainKind::External)
.expect("wallet must have external keychain");
let bound = (ext_index + to_index) as usize;
let bounded_ext_iter = unbounded_ext_spk_iter.take(bound).map(|(_, script)| script);
spk_cache.extend(bounded_ext_iter);
let int_index = last_revealed
.get(&KeychainKind::Internal)
.copied()
.unwrap_or(0);
let unbounded_int_spk_iter = keychain.unbounded_spk_iter(KeychainKind::Internal);
if let Some(int_spk_iter) = unbounded_int_spk_iter {
let bound = (int_index + to_index) as usize;
let bounded_int_iter = int_spk_iter.take(bound).map(|(_, script)| script);
spk_cache.extend(bounded_int_iter);
}
spk_cache
}
}
#[derive(Debug, Clone, Copy)]
pub enum UpdateError {
NodeStopped,
}
impl std::fmt::Display for UpdateError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UpdateError::NodeStopped => write!(f, "the node halted execution."),
}
}
}
impl std::error::Error for UpdateError {}
#[derive(Debug, Clone, Copy, Default)]
pub enum ScanType {
#[default]
Sync,
Recovery {
used_script_index: u32,
checkpoint: HeaderCheckpoint,
},
}