use std::{
collections::HashMap,
sync::{Arc, Mutex},
};
use casper_types::{bytesrepr, Key, StoredValue};
use lmdb::{Database, DatabaseFlags, Transaction};
use casper_hashing::Digest;
use crate::storage::{
error,
global_state::CommitError,
store::Store,
transaction_source::{lmdb::LmdbEnvironment, Readable, TransactionSource, Writable},
trie::Trie,
trie_store::{self, TrieStore},
};
#[derive(Debug, Clone)]
pub struct LmdbTrieStore {
db: Database,
}
impl LmdbTrieStore {
pub fn new(
env: &LmdbEnvironment,
maybe_name: Option<&str>,
flags: DatabaseFlags,
) -> Result<Self, error::Error> {
let name = Self::name(maybe_name);
let db = env.env().create_db(Some(&name), flags)?;
Ok(LmdbTrieStore { db })
}
pub fn open(env: &LmdbEnvironment, maybe_name: Option<&str>) -> Result<Self, error::Error> {
let name = Self::name(maybe_name);
let db = env.env().open_db(Some(&name))?;
Ok(LmdbTrieStore { db })
}
fn name(maybe_name: Option<&str>) -> String {
maybe_name
.map(|name| format!("{}-{}", trie_store::NAME, name))
.unwrap_or_else(|| String::from(trie_store::NAME))
}
pub fn get_db(&self) -> Database {
self.db
}
}
impl<K, V> Store<Digest, Trie<K, V>> for LmdbTrieStore {
type Error = error::Error;
type Handle = Database;
fn handle(&self) -> Self::Handle {
self.db
}
}
impl<K, V> TrieStore<K, V> for LmdbTrieStore {}
pub(crate) type Cache = Arc<Mutex<HashMap<Digest, (bool, Trie<Key, StoredValue>)>>>;
#[derive(Clone)]
pub(crate) struct ScratchTrieStore {
pub(crate) cache: Cache,
pub(crate) store: Arc<LmdbTrieStore>,
pub(crate) env: Arc<LmdbEnvironment>,
}
impl ScratchTrieStore {
pub fn new(store: Arc<LmdbTrieStore>, env: Arc<LmdbEnvironment>) -> Self {
Self {
store,
env,
cache: Default::default(),
}
}
pub fn write_root_to_db(self, state_root: Digest) -> Result<(), error::Error> {
let env = self.env;
let store = self.store;
let cache = &mut *self.cache.lock().map_err(|_| error::Error::Poison)?;
let (is_root_dirty, root_trie) = cache
.get(&state_root)
.ok_or(CommitError::TrieNotFoundInCache(state_root))?;
if !is_root_dirty {
return Ok(());
}
let mut txn = env.create_read_write_txn()?;
let mut tries_to_visit = vec![(state_root, root_trie, root_trie.iter_children())];
while let Some((digest, current_trie, mut descendants_iterator)) = tries_to_visit.pop() {
if let Some(descendant) = descendants_iterator.next() {
tries_to_visit.push((digest, current_trie, descendants_iterator));
if let Some((true, child_trie)) = cache.get(&descendant) {
tries_to_visit.push((descendant, child_trie, child_trie.iter_children()));
}
} else {
store.put(&mut txn, &digest, current_trie)?;
}
}
txn.commit()?;
Ok(())
}
}
impl Store<Digest, Trie<Key, StoredValue>> for ScratchTrieStore {
type Error = error::Error;
type Handle = ScratchTrieStore;
fn handle(&self) -> Self::Handle {
self.clone()
}
fn put<T>(
&self,
_txn: &mut T,
digest: &Digest,
trie: &Trie<Key, StoredValue>,
) -> Result<(), Self::Error>
where
T: Writable<Handle = Self::Handle>,
Self::Error: From<T::Error>,
{
self.cache
.lock()
.map_err(|_| error::Error::Poison)?
.insert(*digest, (true, trie.clone()));
Ok(())
}
fn get<T>(
&self,
txn: &T,
digest: &Digest,
) -> Result<Option<Trie<Key, StoredValue>>, Self::Error>
where
T: Readable<Handle = Self::Handle>,
Self::Error: From<T::Error>,
{
let maybe_trie = {
self.cache
.lock()
.map_err(|_| error::Error::Poison)?
.get(digest)
.cloned()
};
match maybe_trie {
Some((_, cached)) => Ok(Some(cached)),
None => {
let raw = self.get_raw(txn, digest)?;
match raw {
Some(bytes) => {
let value: Trie<Key, StoredValue> = bytesrepr::deserialize(bytes.into())?;
{
let store =
&mut *self.cache.lock().map_err(|_| error::Error::Poison)?;
if !store.contains_key(digest) {
store.insert(*digest, (false, value.clone()));
}
}
Ok(Some(value))
}
None => Ok(None),
}
}
}
}
}
impl TrieStore<Key, StoredValue> for ScratchTrieStore {}