use crate::{
index::{self, IndexedHeader},
Location,
};
use bitcoin::{hashes::Hash, BlockHash};
#[derive(thiserror::Error, Debug)]
pub enum Reorg {
#[error("missing block={0} at height={1}")]
Missing(bitcoin::BlockHash, usize),
#[error("stale block={0} at height={1}")]
Stale(bitcoin::BlockHash, usize),
}
pub struct Headers {
rows: Vec<index::IndexedHeader>,
}
impl Headers {
pub fn new(rows: Vec<index::IndexedHeader>) -> Self {
let mut block_hash = bitcoin::BlockHash::all_zeros();
for row in &rows {
assert_eq!(row.header().prev_blockhash, block_hash);
block_hash = row.hash();
}
Self { rows }
}
pub fn tip_hash(&self) -> bitcoin::BlockHash {
self.rows
.last()
.map(index::IndexedHeader::hash)
.unwrap_or_else(bitcoin::BlockHash::all_zeros)
}
pub fn tip_height(&self) -> Option<usize> {
self.rows.len().checked_sub(1)
}
pub fn add(&mut self, tip: index::IndexedHeader) {
assert_eq!(tip.header().prev_blockhash, self.tip_hash());
self.rows.push(tip)
}
pub fn pop(&mut self) -> Option<index::IndexedHeader> {
self.rows.pop()
}
pub fn tip(&self) -> Option<&index::IndexedHeader> {
self.rows.last()
}
pub fn genesis(&self) -> Option<&index::IndexedHeader> {
self.rows.first()
}
pub fn iter_headers(&self) -> impl Iterator<Item = &IndexedHeader> {
self.rows.iter()
}
pub fn get_header(
&self,
hash: BlockHash,
height: usize,
) -> Result<&index::IndexedHeader, Reorg> {
let header = self.rows.get(height).ok_or(Reorg::Missing(hash, height))?;
if header.hash() == hash {
Ok(header)
} else {
Err(Reorg::Stale(hash, height))
}
}
pub fn find_by_txnum(&self, txnum: index::TxNum) -> Location<'_> {
let block_height = match self
.rows
.binary_search_by_key(&txnum, index::IndexedHeader::next_txnum)
{
Ok(i) => i + 1, Err(i) => i,
};
let indexed_header = self.rows.get(block_height).expect("missing height");
assert!(
txnum < indexed_header.next_txnum(),
"binary search failed to find the correct position"
);
let prev_txnum = self
.rows
.get(block_height - 1)
.map_or_else(index::TxNum::default, index::IndexedHeader::next_txnum);
let block_offset = txnum
.offset_from(prev_txnum)
.expect("binary search failed to find the correct position");
Location {
txnum,
block_height,
block_offset,
indexed_header,
}
}
}