#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
use core::{future::Future, pin::Pin};
use std::collections::BTreeMap;
use std::collections::HashSet;
type FutureResult<'a, T, E> = Pin<Box<dyn Future<Output = Result<T, E>> + Send + 'a>>;
pub use bdk_wallet::Update;
use bdk_wallet::chain::{keychain_txout::KeychainTxOutIndex, local_chain, IndexedTxGraph};
use bdk_wallet::chain::{ConfirmationBlockTime, TxUpdate};
use bdk_wallet::KeychainKind;
pub extern crate kyoto;
pub use kyoto::core::builder::NodeDefault;
pub use kyoto::{
FeeRate, Log, NodeState, RejectPayload, RejectReason, Requester, ScriptBuf, SyncUpdate,
TxBroadcast, TxBroadcastPolicy, Txid, Warning,
};
use kyoto::Receiver;
use kyoto::UnboundedReceiver;
use kyoto::{BlockHash, Event, IndexedBlock};
pub mod builder;
#[derive(Debug)]
pub struct LightClient {
pub requester: Requester,
pub log_subscriber: LogSubscriber,
pub warning_subscriber: WarningSubscriber,
pub update_subscriber: UpdateSubscriber,
pub node: NodeDefault,
}
#[derive(Debug)]
pub struct UpdateSubscriber {
receiver: UnboundedReceiver<Event>,
chain: local_chain::LocalChain,
graph: IndexedTxGraph<ConfirmationBlockTime, KeychainTxOutIndex<KeychainKind>>,
chain_changeset: BTreeMap<u32, Option<BlockHash>>,
}
impl UpdateSubscriber {
pub async fn update(&mut self) -> Option<Update> {
while let Some(message) = self.receiver.recv().await {
match message {
Event::Block(IndexedBlock { height, block }) => {
let hash = block.header.block_hash();
self.chain_changeset.insert(height, Some(hash));
let _ = self.graph.apply_block_relevant(&block, height);
}
Event::BlocksDisconnected(headers) => {
for header in headers {
let height = header.height;
self.chain_changeset.insert(height, None);
}
}
Event::Synced(SyncUpdate {
tip,
recent_history,
}) => {
if self.chain_changeset.is_empty()
&& self.chain.tip().height() == tip.height
&& self.chain.tip().hash() == tip.hash
{
return None;
}
recent_history.into_iter().for_each(|(height, header)| {
self.chain_changeset
.insert(height, Some(header.block_hash()));
});
break;
}
}
}
Some(self.get_scan_response())
}
fn get_scan_response(&mut self) -> Update {
let chain_changeset = core::mem::take(&mut self.chain_changeset);
self.chain
.apply_changeset(&local_chain::ChangeSet::from(chain_changeset))
.expect("chain was initialized with genesis");
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.chain.tip()),
}
}
}
#[derive(Debug)]
pub struct LogSubscriber {
receiver: Receiver<Log>,
}
impl LogSubscriber {
pub(crate) fn new(receiver: Receiver<Log>) -> Self {
Self { receiver }
}
pub async fn next_log(&mut self) -> Log {
loop {
if let Some(log) = self.receiver.recv().await {
return log;
}
}
}
}
#[derive(Debug)]
pub struct WarningSubscriber {
receiver: UnboundedReceiver<Warning>,
}
impl WarningSubscriber {
pub(crate) fn new(receiver: UnboundedReceiver<Warning>) -> Self {
Self { receiver }
}
pub async fn next_warning(&mut self) -> Warning {
loop {
if let Some(warning) = self.receiver.recv().await {
return warning;
}
}
}
}
pub trait WalletExt {
fn peek_revealed_plus_lookahead(&self) -> Box<dyn Iterator<Item = ScriptBuf>>;
}
impl WalletExt for bdk_wallet::Wallet {
fn peek_revealed_plus_lookahead(&self) -> Box<dyn Iterator<Item = ScriptBuf>> {
let mut spks: HashSet<ScriptBuf> = HashSet::new();
for keychain in [KeychainKind::External, KeychainKind::Internal] {
let last_revealed = self.spk_index().last_revealed_index(keychain).unwrap_or(0);
let lookahead_index = last_revealed + self.spk_index().lookahead();
for index in 0..=lookahead_index {
spks.insert(self.peek_address(keychain, index).script_pubkey());
}
}
Box::new(spks.into_iter())
}
}
pub trait RequesterExt {
fn add_revealed_scripts<'a>(
&'a self,
wallet: &'a bdk_wallet::Wallet,
) -> FutureResult<'a, (), kyoto::ClientError>;
}
impl RequesterExt for Requester {
fn add_revealed_scripts<'a>(
&'a self,
wallet: &'a bdk_wallet::Wallet,
) -> FutureResult<'a, (), kyoto::ClientError> {
async fn _add_revealed(
requester: &Requester,
wallet: &bdk_wallet::Wallet,
) -> Result<(), kyoto::ClientError> {
for keychain in [KeychainKind::External, KeychainKind::Internal] {
let scripts = wallet.spk_index().revealed_keychain_spks(keychain);
for (_, script) in scripts {
requester.add_script(script).await?;
}
}
Ok(())
}
Box::pin(_add_revealed(self, wallet))
}
}