floresta_chain/pruned_utreexo/
chainstore.rsuse bitcoin::block::Header as BlockHeader;
use bitcoin::consensus::encode;
use bitcoin::consensus::Decodable;
use bitcoin::consensus::Encodable;
use bitcoin::BlockHash;
use crate::prelude::*;
use crate::BlockchainError;
use crate::DatabaseError;
pub trait ChainStore {
type Error: DatabaseError;
fn save_roots_for_block(&mut self, roots: Vec<u8>, height: u32) -> Result<(), Self::Error>;
fn load_roots_for_block(&mut self, height: u32) -> Result<Option<Vec<u8>>, Self::Error>;
fn load_height(&self) -> Result<Option<BestChain>, Self::Error>;
fn save_height(&mut self, height: &BestChain) -> Result<(), Self::Error>;
fn get_header(&self, block_hash: &BlockHash) -> Result<Option<DiskBlockHeader>, Self::Error>;
fn get_header_by_height(&self, height: u32) -> Result<Option<DiskBlockHeader>, Self::Error>;
fn save_header(&mut self, header: &DiskBlockHeader) -> Result<(), Self::Error>;
fn get_block_hash(&self, height: u32) -> Result<Option<BlockHash>, Self::Error>;
fn flush(&mut self) -> Result<(), Self::Error>;
fn update_block_index(&mut self, height: u32, hash: BlockHash) -> Result<(), Self::Error>;
fn check_integrity(&self) -> Result<(), Self::Error>;
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DiskBlockHeader {
FullyValid(BlockHeader, u32),
AssumedValid(BlockHeader, u32),
Orphan(BlockHeader),
HeadersOnly(BlockHeader, u32),
InFork(BlockHeader, u32),
InvalidChain(BlockHeader),
}
impl DiskBlockHeader {
pub fn block_hash(&self) -> BlockHash {
self.deref().block_hash()
}
pub fn height(&self) -> Option<u32> {
match self {
DiskBlockHeader::InFork(_, height) => Some(*height),
DiskBlockHeader::FullyValid(_, height) => Some(*height),
DiskBlockHeader::HeadersOnly(_, height) => Some(*height),
DiskBlockHeader::AssumedValid(_, height) => Some(*height),
DiskBlockHeader::Orphan(_) => None,
DiskBlockHeader::InvalidChain(_) => None,
}
}
pub fn try_height(&self) -> Result<u32, BlockchainError> {
self.height().ok_or(BlockchainError::OrphanOrInvalidBlock)
}
}
impl Deref for DiskBlockHeader {
type Target = BlockHeader;
fn deref(&self) -> &Self::Target {
match self {
DiskBlockHeader::FullyValid(header, _) => header,
DiskBlockHeader::Orphan(header) => header,
DiskBlockHeader::HeadersOnly(header, _) => header,
DiskBlockHeader::InFork(header, _) => header,
DiskBlockHeader::InvalidChain(header) => header,
DiskBlockHeader::AssumedValid(header, _) => header,
}
}
}
impl Decodable for DiskBlockHeader {
fn consensus_decode<R: bitcoin::io::Read + ?Sized>(
reader: &mut R,
) -> Result<Self, encode::Error> {
let tag = u8::consensus_decode(reader)?;
let header = BlockHeader::consensus_decode(reader)?;
match tag {
0x00 => {
let height = u32::consensus_decode(reader)?;
Ok(Self::FullyValid(header, height))
}
0x01 => Ok(Self::Orphan(header)),
0x02 => {
let height = u32::consensus_decode(reader)?;
Ok(Self::HeadersOnly(header, height))
}
0x03 => {
let height = u32::consensus_decode(reader)?;
Ok(Self::InFork(header, height))
}
0x04 => Ok(Self::InvalidChain(header)),
0x05 => {
let height = u32::consensus_decode(reader)?;
Ok(Self::AssumedValid(header, height))
}
_ => Err(encode::Error::ParseFailed("DiskBlockHeader: invalid tag")),
}
}
}
impl Encodable for DiskBlockHeader {
fn consensus_encode<W: bitcoin::io::Write + ?Sized>(
&self,
writer: &mut W,
) -> bitcoin::io::Result<usize> {
let mut len = 80 + 1; match self {
DiskBlockHeader::FullyValid(header, height) => {
0x00_u8.consensus_encode(writer)?;
header.consensus_encode(writer)?;
height.consensus_encode(writer)?;
len += 4;
}
DiskBlockHeader::Orphan(header) => {
0x01_u8.consensus_encode(writer)?;
header.consensus_encode(writer)?;
}
DiskBlockHeader::HeadersOnly(header, height) => {
0x02_u8.consensus_encode(writer)?;
header.consensus_encode(writer)?;
height.consensus_encode(writer)?;
len += 4;
}
DiskBlockHeader::InFork(header, height) => {
0x03_u8.consensus_encode(writer)?;
header.consensus_encode(writer)?;
height.consensus_encode(writer)?;
len += 4;
}
DiskBlockHeader::InvalidChain(header) => {
0x04_u8.consensus_encode(writer)?;
header.consensus_encode(writer)?;
}
DiskBlockHeader::AssumedValid(header, height) => {
0x05_u8.consensus_encode(writer)?;
header.consensus_encode(writer)?;
height.consensus_encode(writer)?;
len += 4;
}
};
Ok(len)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BestChain {
pub best_block: BlockHash,
pub depth: u32,
pub validation_index: BlockHash,
pub alternative_tips: Vec<BlockHash>,
}
impl BestChain {
pub(super) fn new_block(&mut self, block_hash: BlockHash, height: u32) {
self.best_block = block_hash;
self.depth = height;
}
pub(super) fn valid_block(&mut self, block_hash: BlockHash) {
self.validation_index = block_hash;
}
}
impl Encodable for BestChain {
fn consensus_encode<W: bitcoin::io::Write + ?Sized>(
&self,
writer: &mut W,
) -> bitcoin::io::Result<usize> {
let mut len = 0;
len += self.best_block.consensus_encode(writer)?;
len += self.depth.consensus_encode(writer)?;
len += self.validation_index.consensus_encode(writer)?;
len += self.alternative_tips.consensus_encode(writer)?;
Ok(len)
}
}
impl Decodable for BestChain {
fn consensus_decode<R: bitcoin::io::Read + ?Sized>(
reader: &mut R,
) -> Result<Self, encode::Error> {
let best_block = BlockHash::consensus_decode(reader)?;
let depth = u32::consensus_decode(reader)?;
let validation_index = BlockHash::consensus_decode(reader)?;
let alternative_tips = <Vec<BlockHash>>::consensus_decode(reader)?;
Ok(Self {
alternative_tips,
best_block,
depth,
validation_index,
})
}
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use bitcoin::block::Header as BlockHeader;
use bitcoin::block::Version;
use bitcoin::consensus::Decodable;
use bitcoin::consensus::Encodable;
use bitcoin::hashes::Hash;
use bitcoin::BlockHash;
use bitcoin::CompactTarget;
use bitcoin::TxMerkleNode;
use rand;
use rand::Rng;
use super::*;
fn gen_header() -> BlockHeader {
let mut rng = rand::thread_rng();
BlockHeader {
version: Version::from_consensus(2),
prev_blockhash: BlockHash::from_byte_array(rng.gen()),
merkle_root: TxMerkleNode::from_byte_array(rng.gen()),
time: rng.gen(),
bits: CompactTarget::from_consensus(rng.gen()),
nonce: rng.gen(),
}
}
#[test]
fn disk_block_header_height() {
let header = gen_header();
let h = 100;
let fv = DiskBlockHeader::FullyValid(header, h);
assert_eq!(fv.height(), Some(h));
assert_eq!(fv.try_height().unwrap(), h);
let av = DiskBlockHeader::AssumedValid(header, h);
assert_eq!(av.height(), Some(h));
assert_eq!(av.try_height().unwrap(), h);
let ho = DiskBlockHeader::HeadersOnly(header, h);
assert_eq!(ho.height(), Some(h));
assert_eq!(ho.try_height().unwrap(), h);
let ifk = DiskBlockHeader::InFork(header, h);
assert_eq!(ifk.height(), Some(h));
assert_eq!(ifk.try_height().unwrap(), h);
let orp = DiskBlockHeader::Orphan(header);
assert_eq!(orp.height(), None);
assert!(matches!(
orp.try_height(),
Err(BlockchainError::OrphanOrInvalidBlock)
));
let inv = DiskBlockHeader::InvalidChain(header);
assert_eq!(inv.height(), None);
assert!(matches!(
inv.try_height(),
Err(BlockchainError::OrphanOrInvalidBlock)
));
}
#[test]
fn disk_block_header_deref() {
let header = gen_header();
let disk_header = DiskBlockHeader::FullyValid(header, 1);
assert_eq!(*disk_header, header);
assert_eq!(disk_header.block_hash(), header.block_hash());
}
#[test]
fn encode_decode_disk_block_header() {
let header = gen_header();
let h = 123_456;
let all_variants = vec![
DiskBlockHeader::FullyValid(header, h),
DiskBlockHeader::AssumedValid(header, h),
DiskBlockHeader::HeadersOnly(header, h),
DiskBlockHeader::InFork(header, h),
DiskBlockHeader::Orphan(header),
DiskBlockHeader::InvalidChain(header),
];
for original in all_variants {
let mut buf = Vec::new();
let written = original.consensus_encode(&mut buf).unwrap();
assert_eq!(buf.len(), written, "length matches returned size");
let mut cursor = Cursor::new(&buf);
let decoded = DiskBlockHeader::consensus_decode(&mut cursor).unwrap();
assert_eq!(decoded, original);
}
}
#[test]
fn best_chain_methods() {
let hash = BlockHash::from_byte_array(rand::random());
let mut best = BestChain {
best_block: hash,
depth: 10,
validation_index: BlockHash::all_zeros(),
alternative_tips: Vec::new(),
};
let new_hash = BlockHash::from_byte_array(rand::random());
let new_depth = 20;
best.new_block(new_hash, new_depth);
assert_eq!(
best,
BestChain {
best_block: new_hash,
depth: new_depth,
validation_index: BlockHash::all_zeros(),
alternative_tips: Vec::new(),
}
);
best.valid_block(hash);
assert_eq!(
best,
BestChain {
best_block: new_hash,
depth: new_depth,
validation_index: hash,
alternative_tips: Vec::new(),
}
);
}
#[test]
fn encode_decode_best_chain() {
let mut rng = rand::thread_rng();
let alternative_tips = (0..64)
.map(|_| BlockHash::from_byte_array(rng.gen()))
.collect();
let best = BestChain {
best_block: BlockHash::from_byte_array(rng.gen()),
depth: rng.gen(),
validation_index: BlockHash::from_byte_array(rng.gen()),
alternative_tips,
};
let mut buf = Vec::new();
let written = best.consensus_encode(&mut buf).unwrap();
assert_eq!(buf.len(), written, "written length matches buffer");
let mut cursor = Cursor::new(&buf);
let decoded = BestChain::consensus_decode(&mut cursor).unwrap();
assert_eq!(decoded, best);
}
}