use crate::{
chaindef::{Block, BlockHash, BlockHeader},
undo::{BlockUndoer, DummyBlockUndoer},
};
use tokio::sync::RwLock;
use std::collections::HashMap;
pub(crate) struct NewHeader {
header: BlockHeader,
hash: BlockHash,
height: u64,
}
impl NewHeader {
pub(crate) fn from_header_and_height(header: BlockHeader, height: u64) -> Self {
let hash = header.block_hash();
Self {
header,
hash,
height,
}
}
}
pub struct Chain {
headers: RwLock<Vec<(BlockHash, BlockHeader)>>,
heights: RwLock<HashMap<BlockHash, u64>>,
}
impl Chain {
pub fn new(genesis: Block) -> Self {
let genesis_hash = genesis.block_hash();
Self {
headers: RwLock::new(vec![(genesis_hash, genesis.header)]),
heights: RwLock::new(std::iter::once((genesis_hash, 0)).collect()), }
}
#[cfg(bch)]
pub fn new_regtest() -> Self {
Self::new(crate::chaindef::regtest_genesis_block())
}
pub(crate) async fn drop_last_headers(&mut self, block_undoer: impl BlockUndoer, n: u64) {
if n == 0 {
return;
}
let new_height = self.height().await.saturating_sub(n);
info!("Dropping {n} headers, undoing to block {new_height}");
self.update(block_undoer, vec![], Some(new_height)).await
}
pub(crate) async fn load(&mut self, headers: Vec<BlockHeader>, tip: BlockHash) {
let genesis_hash = self.headers.read().await[0].0;
let mut header_map: HashMap<BlockHash, BlockHeader> =
headers.into_iter().map(|h| (h.block_hash(), h)).collect();
let mut blockhash = tip;
let mut new_headers = vec![];
while blockhash != genesis_hash {
let header = match header_map.remove(&blockhash) {
Some(header) => header,
None => panic!("missing header {} while loading from DB", blockhash),
};
blockhash = header.prev_blockhash;
new_headers.push(header);
}
info!("loading {} headers, tip={}", new_headers.len(), tip);
let new_headers = new_headers.into_iter().rev(); let undoer = DummyBlockUndoer::default();
self.update(
undoer,
new_headers
.zip(1..)
.map(|(header, height)| NewHeader::from_header_and_height(header, height))
.collect(),
None,
)
.await
}
pub async fn get_block_hash(&self, height: usize) -> Option<BlockHash> {
self.headers
.read()
.await
.get(height)
.map(|(hash, _header)| *hash)
}
pub(crate) async fn get_block_header(&self, height: usize) -> Option<BlockHeader> {
self.headers
.read()
.await
.get(height)
.map(|(_hash, header)| header.clone())
}
pub(crate) async fn get_block_height(&self, blockhash: &BlockHash) -> Option<u64> {
self.heights.read().await.get(blockhash).copied()
}
pub(crate) async fn get_block_height_and_header(
&self,
blockhash: &BlockHash,
) -> Option<(u64, BlockHeader)> {
let headers = self.headers.read().await;
let heights = self.heights.read().await;
heights.get(blockhash).and_then(|height| {
headers
.get(*height as usize)
.map(|(_, header)| (*height, header.clone()))
})
}
pub(crate) async fn get_mtp(&self, height: usize) -> Option<u64> {
let mut times = Vec::with_capacity(11);
{
let headers = self.headers.read().await;
for h in height.saturating_sub(10)..=height {
let (_, header) = headers.get(h)?;
times.push(header.time as u64);
}
}
if times.is_empty() {
debug_assert!(false, "should always get mtp if block exists");
return None;
}
times.sort_unstable();
let mtp = times[times.len() / 2];
Some(mtp)
}
pub(crate) async fn update(
&self,
block_undoer: impl BlockUndoer,
new_headers: Vec<NewHeader>,
tip_height: Option<u64>,
) {
let gensis = self.genesis_hash().await;
let mut headers = self.headers.write().await;
let mut heights = self.heights.write().await;
let rewind = match new_headers.first() {
Some(first) => first.height,
None => tip_height.expect("empty new_headers and no tip height") + 1,
};
let tip_hash = tip_height.and_then(|h| headers.get(h as usize).map(|(hash, _)| *hash));
let tip_hash = tip_hash.unwrap_or(gensis);
let remove_headers: Vec<_> = headers.drain(rewind as usize..).collect();
for (hash, _header) in remove_headers.into_iter().rev() {
let height = heights.remove(&hash).expect("heights map missing entry");
info!("Chain: Undoing block {} (height {})", hash, height);
if let Err(e) = block_undoer.undo_block(&hash, height, &tip_hash).await {
warn!(
"Failed to remove index entries for block {} (height {}): {}",
hash, height, e
)
};
}
if let Some(first_height) = new_headers.first().map(|h| h.height) {
for (h, height) in new_headers.into_iter().zip(first_height..) {
assert_eq!(h.height, height);
assert_eq!(h.hash, h.header.block_hash());
let existed = heights.insert(h.hash, h.height);
assert!(existed.is_none());
headers.push((h.hash, h.header));
}
info!(
"chain updated: tip={}, height={}",
headers.last().unwrap().0,
headers.len() - 1
);
}
}
pub async fn tip_hash(&self) -> BlockHash {
self.headers.read().await.last().expect("empty chain").0
}
pub(crate) async fn tip(&self) -> BlockHeader {
self.headers
.read()
.await
.last()
.expect("empty chain")
.1
.clone()
}
pub(crate) async fn genesis_hash(&self) -> BlockHash {
self.headers.read().await.first().expect("empty chain").0
}
pub(crate) async fn height(&self) -> u64 {
(self.headers.read().await.len() - 1) as u64
}
pub async fn contains(&self, blockhash: &BlockHash) -> bool {
self.heights.read().await.contains_key(blockhash)
}
}
#[cfg(bch)]
#[cfg(test)]
mod tests {
use super::*;
use bitcoincash::consensus::deserialize;
use bitcoincash::hashes::hex::{FromHex, ToHex};
#[tokio::test]
async fn test_genesis() {
let regtest = Chain::new_regtest();
assert_eq!(regtest.height().await, 0);
assert_eq!(
regtest.tip_hash().await.to_hex(),
"0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"
);
}
#[tokio::test]
async fn test_updates() {
let hex_headers = vec![
"0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f1d14d3c7ff12d6adf494ebbcfba69baa915a066358b68a2b8c37126f74de396b1d61cc60ffff7f2000000000",
"00000020d700ae5d3c705702e0a5d9ababd22ded079f8a63b880b1866321d6bfcb028c3fc816efcf0e84ccafa1dda26be337f58d41b438170c357cda33a68af5550590bc1e61cc60ffff7f2004000000",
"00000020d13731bc59bc0989e06a5e7cab9843a4e17ad65c7ca47cd77f50dfd24f1f55793f7f342526aca9adb6ce8f33d8a07662c97d29d83b9e18117fb3eceecb2ab99b1e61cc60ffff7f2001000000",
"00000020a603def3e1255cadfb6df072946327c58b344f9bfb133e8e3e280d1c2d55b31c731a68f70219472864a7cb010cd53dc7e0f67e57f7d08b97e5e092b0c3942ad51f61cc60ffff7f2001000000",
"0000002041dd202b3b2edcdd3c8582117376347d48ff79ff97c95e5ac814820462012e785142dc360975b982ca43eecd14b4ba6f019041819d4fc5936255d7a2c45a96651f61cc60ffff7f2000000000",
"0000002072e297a2d6b633c44f3c9b1a340d06f3ce4e6bcd79ebd4c4ff1c249a77e1e37c59c7be1ca0964452e1735c0d2740f0d98a11445a6140c36b55770b5c0bcf801f1f61cc60ffff7f2000000000",
"000000200c9eb5889a8e924d1c4e8e79a716514579e41114ef37d72295df8869d6718e4ac5840f28de43ff25c7b9200aaf7873b20587c92827eaa61943484ca828bdd2e11f61cc60ffff7f2000000000",
"000000205873f322b333933e656b07881bb399dae61a6c0fa74188b5fb0e3dd71c9e2442f9e2f433f54466900407cf6a9f676913dd54aad977f7b05afcd6dcd81e98ee752061cc60ffff7f2004000000",
"00000020fd1120713506267f1dba2e1856ca1d4490077d261cde8d3e182677880df0d856bf94cfa5e189c85462813751ab4059643759ed319a81e0617113758f8adf67bc2061cc60ffff7f2000000000",
"000000200030d7f9c11ef35b89a0eefb9a5e449909339b5e7854d99804ea8d6a49bf900a0304d2e55fe0b6415949cff9bca0f88c0717884a5e5797509f89f856af93624a2061cc60ffff7f2002000000",
];
let headers: Vec<BlockHeader> = hex_headers
.iter()
.map(|hex_header| deserialize(&Vec::from_hex(hex_header).unwrap()).unwrap())
.collect();
for chunk_size in 1..hex_headers.len() {
let regtest = Chain::new_regtest();
let mut height = 0;
let mut tip = regtest.tip_hash().await;
for chunk in headers.chunks(chunk_size) {
let mut update = vec![];
for header in chunk {
height += 1;
tip = header.block_hash();
update.push(NewHeader::from_header_and_height(*header, height))
}
regtest
.update(DummyBlockUndoer::default(), update, None)
.await;
assert_eq!(regtest.tip_hash().await, tip);
assert_eq!(regtest.height().await, height);
}
assert_eq!(
regtest.tip_hash().await,
headers.last().unwrap().block_hash()
);
assert_eq!(regtest.height().await, headers.len() as u64);
}
let mut regtest = Chain::new_regtest();
regtest
.load(headers.clone(), headers.last().unwrap().block_hash())
.await;
assert_eq!(regtest.height().await, headers.len() as u64);
for (header, height) in headers.iter().zip(1usize..) {
assert_eq!(regtest.get_block_header(height).await, Some(*header));
assert_eq!(
regtest.get_block_hash(height).await,
Some(header.block_hash())
);
assert_eq!(
regtest.get_block_height(&header.block_hash()).await,
Some(height as u64)
);
}
for i in (0..=headers.len()).rev() {
let hash = regtest.get_block_hash(i).await.unwrap();
assert_eq!(regtest.get_block_height(&hash).await, Some(i as u64));
assert_eq!(regtest.height().await, i as u64);
assert_eq!(regtest.tip_hash().await, hash);
regtest
.drop_last_headers(DummyBlockUndoer::new(true), 1)
.await;
}
assert_eq!(regtest.height().await, 0);
assert_eq!(
regtest.tip_hash().await.to_hex(),
"0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"
);
regtest
.drop_last_headers(DummyBlockUndoer::new(true), 1)
.await;
assert_eq!(regtest.height().await, 0);
assert_eq!(
regtest.tip_hash().await.to_hex(),
"0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206"
);
let mut regtest = Chain::new_regtest();
regtest
.load(headers.clone(), headers.last().unwrap().block_hash())
.await;
let height = regtest.height().await;
let new_header: BlockHeader = deserialize(&Vec::from_hex("000000200030d7f9c11ef35b89a0eefb9a5e449909339b5e7854d99804ea8d6a49bf900a0304d2e55fe0b6415949cff9bca0f88c0717884a5e5797509f89f856af93624a7a6bcc60ffff7f2000000000").unwrap()).unwrap();
regtest
.update(
DummyBlockUndoer::new(true),
vec![NewHeader::from_header_and_height(new_header, height)],
None,
)
.await;
assert_eq!(regtest.height().await, height);
assert_eq!(
regtest.tip_hash().await.to_hex(),
"0e16637fe0700a7c52e9a6eaa58bd6ac7202652103be8f778680c66f51ad2e9b"
);
}
}