use std::sync::Arc;
use dig_block::{L2Block, L2BlockHeader};
use rand::seq::SliceRandom;
use rocksdb::{IteratorMode, DB};
use crate::constants::{
CF_BLOCKS, CF_METADATA, DICT_TARGET_SIZE, DICT_TRAINING_THRESHOLD, META_ZSTD_DICT,
};
use crate::error::BlockStoreError;
use crate::store::BlockStore;
impl BlockStore {
pub fn serialize_header(header: &L2BlockHeader) -> Result<Vec<u8>, BlockStoreError> {
bincode::serialize(header).map_err(|e| BlockStoreError::Serialization(e.to_string()))
}
pub fn deserialize_header(bytes: &[u8]) -> Result<L2BlockHeader, BlockStoreError> {
bincode::deserialize(bytes).map_err(|e| BlockStoreError::Serialization(e.to_string()))
}
pub fn serialize_block(&self, block: &L2Block) -> Result<Vec<u8>, BlockStoreError> {
let raw = bincode::serialize(block)?;
if self.use_compression_dict {
let dict_guard = self.zstd_dict.read();
if let Some(dict) = dict_guard.as_ref() {
let mut compressor = zstd::bulk::Compressor::with_dictionary(
self.compression_level,
dict.as_slice(),
)
.map_err(|e| BlockStoreError::Compression(e.to_string()))?;
return compressor
.compress(raw.as_slice())
.map_err(BlockStoreError::compression_from_io);
}
}
zstd::encode_all(raw.as_slice(), self.compression_level)
.map_err(BlockStoreError::compression_from_io)
}
pub fn deserialize_block(&self, compressed: &[u8]) -> Result<L2Block, BlockStoreError> {
let raw = self.decompress_block_payload(compressed).map_err(|e| {
BlockStoreError::Serialization(format!("deserialize_block: decompress failed: {e}"))
})?;
bincode::deserialize(&raw).map_err(|e| BlockStoreError::Serialization(e.to_string()))
}
pub(crate) fn decompress_block_payload(&self, compressed: &[u8]) -> std::io::Result<Vec<u8>> {
if self.use_compression_dict {
if let Some(dict) = self.zstd_dict.read().as_ref() {
let mut decompressor = zstd::bulk::Decompressor::with_dictionary(dict.as_slice())?;
return match decompressor.decompress(compressed, self.max_decompressed_block_bytes)
{
Ok(bytes) => Ok(bytes),
Err(_) => zstd::decode_all(compressed),
};
}
}
zstd::decode_all(compressed)
}
pub fn block_count(&self) -> Result<u64, BlockStoreError> {
let cf = self.cf(CF_BLOCKS)?;
let iter = self.db.iterator_cf(cf, IteratorMode::Start);
let mut n = 0u64;
for item in iter {
let (_k, _v) = item?;
n = n.saturating_add(1);
}
Ok(n)
}
pub fn init_dictionary(&self) -> Result<(), BlockStoreError> {
let loaded = load_zstd_dict_from_db(&self.db, self.use_compression_dict)?;
*self.zstd_dict.write() = loaded;
Ok(())
}
pub(crate) fn sample_block_bodies(
&self,
sample_count: usize,
) -> Result<Vec<Vec<u8>>, BlockStoreError> {
let cf = self.cf(CF_BLOCKS)?;
let mut blobs: Vec<Vec<u8>> = Vec::new();
let iter = self.db.iterator_cf(cf, IteratorMode::Start);
for item in iter {
let (_key, value) = item?;
blobs.push(value.to_vec());
}
if blobs.len() < sample_count {
return Err(BlockStoreError::Serialization(format!(
"dictionary training: need at least {sample_count} blocks in {CF_BLOCKS}, have {}",
blobs.len()
)));
}
blobs.shuffle(&mut rand::thread_rng());
blobs.truncate(sample_count);
let mut samples = Vec::with_capacity(sample_count);
for compressed in blobs {
let raw = self.decompress_block_payload(&compressed).map_err(|e| {
BlockStoreError::Serialization(format!(
"dictionary training sample decompress: {e}"
))
})?;
samples.push(raw);
}
Ok(samples)
}
pub(crate) fn train_dictionary(&self) -> Result<Vec<u8>, BlockStoreError> {
let meta = self.cf(CF_METADATA)?;
if let Some(blob) = self.db.get_cf(meta, META_ZSTD_DICT.as_bytes())? {
if !blob.is_empty() {
return Ok(blob);
}
}
let n = DICT_TRAINING_THRESHOLD as usize;
let samples = self.sample_block_bodies(n)?;
let refs: Vec<&[u8]> = samples.iter().map(Vec::as_slice).collect();
let dict = zstd::dict::from_samples(&refs, DICT_TARGET_SIZE).map_err(|e| {
BlockStoreError::Serialization(format!("dictionary training failed: {e}"))
})?;
self.db
.put_cf(meta, META_ZSTD_DICT.as_bytes(), dict.as_slice())?;
Ok(dict)
}
pub(crate) fn maybe_train_dictionary(&self) -> Result<(), BlockStoreError> {
if !self.use_compression_dict {
return Ok(());
}
if self.zstd_dict.read().is_some() {
return Ok(());
}
let meta = self.cf(CF_METADATA)?;
if self
.db
.get_cf(meta, META_ZSTD_DICT.as_bytes())?
.filter(|b| !b.is_empty())
.is_some()
{
self.init_dictionary()?;
return Ok(());
}
if self.block_count()? < DICT_TRAINING_THRESHOLD {
return Ok(());
}
let dict = self.train_dictionary()?;
*self.zstd_dict.write() = Some(Arc::new(dict));
Ok(())
}
}
pub(crate) fn resolve_zstd_dictionary(
db: &DB,
use_compression_dict: bool,
override_bytes: Option<Vec<u8>>,
) -> Result<Option<Arc<Vec<u8>>>, BlockStoreError> {
if let Some(bytes) = override_bytes {
return if bytes.is_empty() {
Ok(None)
} else {
Ok(Some(Arc::new(bytes)))
};
}
load_zstd_dict_from_db(db, use_compression_dict)
}
pub(crate) fn load_zstd_dict_from_db(
db: &DB,
use_compression_dict: bool,
) -> Result<Option<Arc<Vec<u8>>>, BlockStoreError> {
if !use_compression_dict {
return Ok(None);
}
let meta = db
.cf_handle(CF_METADATA)
.ok_or_else(|| BlockStoreError::Serialization("missing CF_METADATA".into()))?;
let Some(blob) = db.get_cf(meta, META_ZSTD_DICT.as_bytes())? else {
return Ok(None);
};
if blob.is_empty() {
return Ok(None);
}
Ok(Some(Arc::new(blob)))
}