use std::fmt;
use bsv_rs::transaction::MerklePath;
use crate::{Hash, Height, TxId};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Header {
pub hash: Hash,
pub merkle_root: Hash,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct HeaderError(pub String);
impl fmt::Display for HeaderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for HeaderError {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CheckError {
InvalidProof(String),
Headers(HeaderError),
Unavailable(Height),
RootMismatch(Height),
TxidMismatch,
}
impl fmt::Display for CheckError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}")
}
}
impl std::error::Error for CheckError {}
#[derive(Clone, Debug)]
pub struct Proof {
txid: TxId,
path: MerklePath,
}
impl PartialEq for Proof {
fn eq(&self, other: &Self) -> bool {
self.txid == other.txid
&& self.path.block_height == other.path.block_height
&& self.path.path == other.path.path
}
}
impl Eq for Proof {}
impl Proof {
pub fn new(txid: impl Into<TxId>, path: MerklePath) -> Result<Self, CheckError> {
let txid = txid.into().to_ascii_lowercase();
if txid.len() != 64 || !txid.bytes().all(|c| c.is_ascii_hexdigit()) {
return Err(CheckError::InvalidProof(
"txid must be 32 bytes of hex".into(),
));
}
let path = MerklePath::new_unchecked(path.block_height, path.path)
.map_err(|e| CheckError::InvalidProof(e.to_string()))?;
path.compute_root(Some(&txid))
.map_err(|e| CheckError::InvalidProof(e.to_string()))?;
Ok(Self { txid, path })
}
pub fn txid(&self) -> &str {
&self.txid
}
pub fn height(&self) -> Height {
self.path.block_height
}
pub fn path(&self) -> &MerklePath {
&self.path
}
pub fn root(&self) -> Result<Hash, CheckError> {
self.path
.compute_root(Some(&self.txid))
.map_err(|e| CheckError::InvalidProof(e.to_string()))
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CheckedProof {
proof: Proof,
header: Header,
root: Hash,
depth: u64,
}
impl CheckedProof {
pub fn proof(&self) -> &Proof {
&self.proof
}
pub fn header(&self) -> &Header {
&self.header
}
pub fn root(&self) -> &str {
&self.root
}
pub fn height(&self) -> Height {
self.proof.height()
}
pub fn depth(&self) -> u64 {
self.depth
}
}
pub trait Headers {
fn header_at(&self, height: Height) -> Result<Option<Header>, HeaderError>;
fn tip_height(&self) -> Result<Height, HeaderError>;
fn check(&self, proof: Proof) -> Result<CheckedProof, CheckError> {
let root = proof.root()?;
let height = proof.height();
let header = self
.header_at(height)
.map_err(CheckError::Headers)?
.ok_or(CheckError::Unavailable(height))?;
let tip = self.tip_height().map_err(CheckError::Headers)?;
if tip < height {
return Err(CheckError::Unavailable(height));
}
if header.merkle_root != root {
return Err(CheckError::RootMismatch(height));
}
Ok(CheckedProof {
proof,
header,
root,
depth: u64::from(tip) - u64::from(height) + 1,
})
}
}
pub(crate) fn check_snapshot<H: Headers + ?Sized>(
headers: &H,
proof: Proof,
) -> Result<CheckedProof, CheckError> {
struct Snapshot<'a, H: ?Sized>(&'a H);
impl<H: Headers + ?Sized> Headers for Snapshot<'_, H> {
fn header_at(&self, height: Height) -> Result<Option<Header>, HeaderError> {
self.0.header_at(height)
}
fn tip_height(&self) -> Result<Height, HeaderError> {
self.0.tip_height()
}
}
Headers::check(&Snapshot(headers), proof)
}