use std::sync::atomic::Ordering;
use chia_protocol::Bytes32;
use dig_block::L2Block;
use rocksdb::WriteBatch;
use crate::constants::CF_CANONICAL;
use crate::encoding::{hash_key, height_key};
use crate::error::{BlockStoreError, ERR_MUTATION_READ_ONLY};
use crate::store::BlockStore;
use crate::types::{BlockRecord, ChainTip};
impl BlockStore {
pub fn get_hash_by_height(&self, height: u64) -> Result<Option<Bytes32>, BlockStoreError> {
if height < self.min_retained_height_cached.load(Ordering::Acquire) {
return Ok(None);
}
if let Some(hash) = self.canonical_height_cache.read().get(&height).copied() {
return Ok(Some(hash));
}
if let Some(arr) = self.canonical_bin.read().read_hash_bytes(height) {
let hash = Bytes32::new(arr);
self.insert_canonical_height_cache(height, hash);
return Ok(Some(hash));
}
let cf = self.cf(CF_CANONICAL)?;
let hk = height_key(height);
let Some(hash_bytes) = self.db.get_cf(cf, hk.as_slice())? else {
return Ok(None);
};
let arr: [u8; 32] = hash_bytes.as_slice().try_into().map_err(|_| {
BlockStoreError::Serialization(
"get_hash_by_height: CF_CANONICAL value must be exactly 32 bytes".into(),
)
})?;
let hash = Bytes32::new(arr);
self.insert_canonical_height_cache(height, hash);
Ok(Some(hash))
}
pub(crate) fn insert_canonical_height_cache(&self, height: u64, hash: Bytes32) {
if self.canonical_height_cache_capacity == 0 {
return;
}
let mut cache = self.canonical_height_cache.write();
cache.insert(height, hash);
while cache.len() > self.canonical_height_cache_capacity {
if let Some(&lowest) = cache.keys().next() {
cache.remove(&lowest);
} else {
break;
}
}
}
pub fn set_canonical(&self, hash: &Bytes32) -> Result<(), BlockStoreError> {
if self.read_only {
return Err(BlockStoreError::Serialization(
ERR_MUTATION_READ_ONLY.into(),
));
}
let Some(record) = self.get_record(hash)? else {
return Err(BlockStoreError::BlockNotInStore(*hash));
};
let height = record.height;
let cf = self.cf(CF_CANONICAL)?;
self.db
.put_cf(cf, height_key(height), hash_key(hash).as_slice())?;
self.canonical_bin.write().extend_write(height, hash)?;
self.insert_canonical_height_cache(height, *hash);
self.hash_to_height_cache.insert(*hash, height);
if let Some(r) = self.record_cache.lock().get_mut(hash) {
r.in_canonical_chain = true;
} else {
let mut r = record;
r.in_canonical_chain = true;
self.record_cache.lock().insert(*hash, r);
}
Ok(())
}
pub fn set_canonical_batch(&self, hashes: &[Bytes32]) -> Result<(), BlockStoreError> {
if self.read_only {
return Err(BlockStoreError::Serialization(
ERR_MUTATION_READ_ONLY.into(),
));
}
if hashes.is_empty() {
return Ok(());
}
let mut validated: Vec<(Bytes32, BlockRecord)> = Vec::with_capacity(hashes.len());
for hash in hashes {
let Some(record) = self.get_record(hash)? else {
return Err(BlockStoreError::BlockNotInStore(*hash));
};
validated.push((*hash, record));
}
let cf = self.cf(CF_CANONICAL)?;
let mut batch = WriteBatch::default();
for (hash, record) in &validated {
batch.put_cf(
&cf,
height_key(record.height).as_slice(),
hash_key(hash).as_slice(),
);
}
self.db.write(batch)?;
for (hash, record) in &validated {
self.canonical_bin
.write()
.extend_write(record.height, hash)?;
if let Some(r) = self.record_cache.lock().get_mut(hash) {
r.in_canonical_chain = true;
} else {
let mut r = record.clone();
r.in_canonical_chain = true;
self.record_cache.lock().insert(*hash, r);
}
}
Ok(())
}
pub fn extend_chain(&self, block: &L2Block) -> Result<bool, BlockStoreError> {
let hash = block.hash();
if self.has_block(&hash)? {
return Ok(false);
}
self.put(block, true)?;
self.set_tip(ChainTip {
hash,
height: block.height(),
})?;
Ok(true)
}
}