use bdk_core::bitcoin;
use bdk_core::{BlockId, CheckPoint};
use bitcoin::{bip158::BlockFilter, Block, ScriptBuf};
use bitcoincore_rpc;
use bitcoincore_rpc::{json::GetBlockHeaderResult, RpcApi};
#[derive(Debug)]
pub struct FilterIter<'a> {
client: &'a bitcoincore_rpc::Client,
spks: Vec<ScriptBuf>,
cp: CheckPoint,
header: Option<GetBlockHeaderResult>,
}
impl<'a> FilterIter<'a> {
pub fn new(
client: &'a bitcoincore_rpc::Client,
cp: CheckPoint,
spks: impl IntoIterator<Item = ScriptBuf>,
) -> Self {
Self {
client,
spks: spks.into_iter().collect(),
cp,
header: None,
}
}
fn find_base(&self) -> Result<GetBlockHeaderResult, Error> {
for cp in self.cp.iter() {
match self.client.get_block_header_info(&cp.hash()) {
Err(e) if is_not_found(&e) => continue,
Ok(header) if header.confirmations <= 0 => continue,
Ok(header) => return Ok(header),
Err(e) => return Err(Error::Rpc(e)),
}
}
Err(Error::ReorgDepthExceeded)
}
}
#[derive(Debug, Clone)]
pub struct Event {
pub cp: CheckPoint,
pub block: Option<Block>,
}
impl Event {
pub fn is_match(&self) -> bool {
self.block.is_some()
}
pub fn height(&self) -> u32 {
self.cp.height()
}
}
impl Iterator for FilterIter<'_> {
type Item = Result<Event, Error>;
fn next(&mut self) -> Option<Self::Item> {
(|| -> Result<Option<_>, Error> {
let mut cp = self.cp.clone();
let header = match self.header.take() {
Some(header) => header,
None => self.find_base()?,
};
let mut next_hash = match header.next_block_hash {
Some(hash) => hash,
None => return Ok(None),
};
let mut next_header = self.client.get_block_header_info(&next_hash)?;
while next_header.confirmations < 0 {
let prev_hash = next_header
.previous_block_hash
.ok_or(Error::ReorgDepthExceeded)?;
let prev_header = self.client.get_block_header_info(&prev_hash)?;
next_header = prev_header;
}
next_hash = next_header.hash;
let next_height: u32 = next_header.height.try_into()?;
cp = cp.insert(BlockId {
height: next_height,
hash: next_hash,
});
let mut block = None;
let filter =
BlockFilter::new(self.client.get_block_filter(&next_hash)?.filter.as_slice());
if filter
.match_any(&next_hash, self.spks.iter().map(ScriptBuf::as_ref))
.map_err(Error::Bip158)?
{
block = Some(self.client.get_block(&next_hash)?);
}
self.header = Some(next_header);
self.cp = cp.clone();
Ok(Some(Event { cp, block }))
})()
.transpose()
}
}
#[derive(Debug)]
pub enum Error {
Rpc(bitcoincore_rpc::Error),
Bip158(bitcoin::bip158::Error),
ReorgDepthExceeded,
TryFromInt(core::num::TryFromIntError),
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Rpc(e) => write!(f, "{e}"),
Self::Bip158(e) => write!(f, "{e}"),
Self::ReorgDepthExceeded => write!(f, "maximum reorg depth exceeded"),
Self::TryFromInt(e) => write!(f, "{e}"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
impl From<bitcoincore_rpc::Error> for Error {
fn from(e: bitcoincore_rpc::Error) -> Self {
Self::Rpc(e)
}
}
impl From<core::num::TryFromIntError> for Error {
fn from(e: core::num::TryFromIntError) -> Self {
Self::TryFromInt(e)
}
}
fn is_not_found(e: &bitcoincore_rpc::Error) -> bool {
matches!(
e,
bitcoincore_rpc::Error::JsonRpc(bitcoincore_rpc::jsonrpc::Error::Rpc(e))
if e.code == -5
)
}