use bitcoin::{Txid, Block, BlockHeader, BlockHash};
use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR;
use crate::*;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlockHashDBValue {
pub block_hash: BlockHash,
}
impl Serialize for BlockHashDBValue {
fn serialize(&self) -> Vec<u8> {
consensus_encode(&self.block_hash)
}
}
impl Deserialize for BlockHashDBValue {
fn deserialize(buf: &[u8]) -> Self {
let block_hash = consensus_decode(buf);
Self {
block_hash,
}
}
}
#[derive(Debug)]
pub struct BlockHashDB {
db: RocksDB<u32, BlockHashDBValue>,
}
impl BlockHashDB {
pub fn path(coin: &str) -> String {
format!("{}/{}/block_hash", data_dir(), coin)
}
pub fn new(coin: &str, temporary: bool) -> Self {
let path = Self::path(coin);
Self {
db: RocksDB::new(&path, temporary),
}
}
pub fn put(&self, height: u32, block: &Block) {
self.db.put(&height, &BlockHashDBValue { block_hash: block.block_hash() });
}
pub fn get(&self, height: u32) -> Option<BlockHash> {
self.db.get(&height).map(|value| value.block_hash)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BlockContentDBValue {
pub height: u32,
pub block_header: BlockHeader,
pub size: u32,
pub strippedsize: u32,
pub weight: u32,
pub txids: Vec<Txid>,
}
impl BlockContentDBValue {
pub fn new(height: u32, block: &Block) -> Self {
let size = block.get_size() as u32;
let weight = block.get_weight() as u32;
Self {
height,
block_header: block.header,
size,
strippedsize: (weight - size) / ((WITNESS_SCALE_FACTOR - 1) as u32),
weight,
txids: block.txdata.iter().map(|tx| tx.txid()).collect(),
}
}
}
const BLOCK_HEADER_LEN: usize = 80;
impl Serialize for BlockContentDBValue {
fn serialize(&self) -> Vec<u8> {
let mut ret = vec![
self.height.to_le_bytes().to_vec(),
consensus_encode(&self.block_header),
self.size.to_le_bytes().to_vec(),
self.strippedsize.to_le_bytes().to_vec(),
self.weight.to_le_bytes().to_vec(),
];
for txid in self.txids.iter() {
ret.push(consensus_encode(txid));
}
ret.concat()
}
}
impl Deserialize for BlockContentDBValue {
fn deserialize(buf: &[u8]) -> Self {
let height = bytes_to_u32(&buf[0..4]);
let mut offset = 4usize;
let block_header = consensus_decode(&buf[offset..BLOCK_HEADER_LEN+offset]);
offset += BLOCK_HEADER_LEN;
let size = bytes_to_u32(&buf[offset..offset+4]);
offset += 4;
let strippedsize = bytes_to_u32(&buf[offset..offset+4]);
offset += 4;
let weight = bytes_to_u32(&buf[offset..offset+4]);
offset += 4;
let mut txids = Vec::new();
while offset < buf.len() {
txids.push(consensus_decode(&buf[offset..offset+32]));
offset += 32;
}
Self {
height,
block_header,
size,
strippedsize,
weight,
txids,
}
}
}
#[derive(Debug)]
pub struct BlockContentDB {
db: RocksDB<BlockHashDBValue, BlockContentDBValue>,
}
impl BlockContentDB {
pub fn path(coin: &str) -> String {
format!("{}/{}/block", data_dir(), coin)
}
pub fn new(coin: &str, temporary: bool) -> Self {
let path = Self::path(coin);
Self {
db: RocksDB::new(&path, temporary),
}
}
pub fn put(&self, height: u32, block: &Block) {
self.db.put(&BlockHashDBValue { block_hash: block.block_hash() }, &BlockContentDBValue::new(height, &block));
}
pub fn get(&self, block_hash: &BlockHash) -> Option<BlockContentDBValue> {
self.db.get(&BlockHashDBValue { block_hash: *block_hash })
}
}
#[derive(Debug)]
pub struct BlockDB {
hash_db: BlockHashDB,
content_db: BlockContentDB,
}
impl BlockDB {
pub fn new(coin: &str, temporary: bool) -> Self {
Self {
hash_db: BlockHashDB::new(coin, temporary),
content_db: BlockContentDB::new(coin, temporary),
}
}
pub fn put(&self, height: u32, block: &Block) {
self.hash_db.put(height, block);
self.content_db.put(height, block);
}
pub fn get(&self, height: u32) -> Option<BlockContentDBValue> {
let block_hash = match self.hash_db.get(height) {
Some(block_hash) => block_hash,
None => return None,
};
self.get_by_hash(&block_hash)
}
pub fn get_by_hash(&self, block_hash: &BlockHash) -> Option<BlockContentDBValue> {
self.content_db.get(block_hash)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn put_and_get_block() {
let block_db = BlockDB::new("test/block", true);
let blocks = fixtures::regtest_blocks();
for (height, block) in blocks.iter().enumerate() {
block_db.put(height as u32, &block);
}
for (height, block) in blocks.iter().enumerate() {
assert_eq!(block_db.get(height as u32), Some(BlockContentDBValue::new(height as u32, &block)));
}
assert_eq!(block_db.get(blocks.len() as u32), None);
}
}