use std::collections::BTreeMap;
use std::error::Error;
use std::path::Path;
use rocksdb::{IteratorMode, Options, DB};
use crate::db::types::{Decode, Encode, U64ED};
pub struct BlockDatabase<V>
where
V: Encode + Decode + Clone,
{
db: DB,
cache: BTreeMap<u64, V>,
}
impl<V> BlockDatabase<V>
where
V: Encode + Decode + Clone,
{
pub fn new(path: &Path, name: &str) -> Result<Self, Box<dyn Error>> {
let mut opts = Options::default();
opts.create_if_missing(true);
opts.set_max_open_files(256);
let db = DB::open(&opts, &path.join(Path::new(name)))?;
Ok(Self {
db,
cache: BTreeMap::new(),
})
}
pub fn get(&self, key: u64) -> Result<Option<V>, Box<dyn Error>> {
if let Some(value) = self.cache.get(&key) {
return Ok(Some(value.clone()));
}
let Some(value_bytes) = self.db.get(&key.encode_vec())? else {
return Ok(None);
};
let value = V::decode_vec(&value_bytes)?;
Ok(Some(value))
}
pub fn set(&mut self, block_number: u64, value: V) {
self.cache.insert(block_number, value.clone());
}
pub fn commit(&mut self) -> Result<(), Box<dyn Error>> {
for (key, value) in self.cache.iter() {
self.db.put(&key.encode_vec(), &value.encode_vec())?;
}
self.db.flush()?;
Ok(())
}
pub fn clear_cache(&mut self) {
self.cache.clear();
}
pub fn last_key(&self) -> Result<Option<u64>, Box<dyn Error>> {
let db_last_key = match self.db.full_iterator(IteratorMode::End).take(1).last() {
Some(Ok((key, _))) => Some(U64ED::decode_vec(&key.to_vec())?.into()),
_ => None,
};
let cache_last_key = self.cache.keys().last().map(|key| *key);
Ok(std::cmp::max(db_last_key, cache_last_key))
}
pub fn reorg(&mut self, latest_valid_block_number: u64) -> Result<(), Box<dyn Error>> {
let mut current = latest_valid_block_number + 1;
let last_block = self.last_key()?;
if let Some(end) = last_block {
while end >= current {
self.db.delete(&U64ED::from(current).encode_vec())?;
self.cache.remove(¤t);
current += 1;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use alloy_primitives::U256;
use tempfile::TempDir;
use super::*;
use crate::db::types::U256ED;
#[test]
fn test_block_database() {
let tempdir = TempDir::new().unwrap();
let mut db = BlockDatabase::<U256ED>::new(tempdir.path(), "test").unwrap();
let block_number = 1;
let value = U256::from(100).into();
db.set(block_number, value);
let block_number = 2;
let value = U256::from(200).into();
db.set(block_number, value);
let block_number = 3;
let value = U256::from(300).into();
db.set(block_number, value);
assert_eq!(db.last_key().unwrap().unwrap(), 3);
db.commit().unwrap();
db.clear_cache();
assert_eq!(db.get(1).unwrap().unwrap(), U256::from(100).into());
assert_eq!(db.get(2).unwrap().unwrap(), U256::from(200).into());
assert_eq!(db.get(3).unwrap().unwrap(), U256::from(300).into());
assert_eq!(db.last_key().unwrap().unwrap(), 3);
db.reorg(2).unwrap();
assert_eq!(db.get(1).unwrap().unwrap(), U256::from(100).into());
assert_eq!(db.get(2).unwrap().unwrap(), U256::from(200).into());
assert_eq!(db.get(3).unwrap().is_none(), true);
assert_eq!(db.last_key().unwrap().unwrap(), 2);
}
}