use std::{
borrow::Cow,
collections::{hash_map::Entry, HashMap},
sync::{Arc, Mutex},
};
use lmdb::{Database, DatabaseFlags, Transaction};
use casper_types::{
bytesrepr::{self, Bytes, ToBytes},
Digest, Key, StoredValue,
};
use crate::global_state::{
error,
state::CommitError,
store::Store,
transaction_source::{lmdb::LmdbEnvironment, Readable, TransactionSource, Writable},
trie::{LazilyDeserializedTrie, 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, Bytes)>>>;
#[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 cache = &*self.cache.lock().map_err(|_| error::Error::Poison)?;
if !cache.contains_key(&state_root) {
return Err(CommitError::TrieNotFoundInCache(state_root).into());
}
let mut tries_to_write = vec![state_root];
let mut txn = self.env.create_read_write_txn()?;
while let Some(trie_hash) = tries_to_write.pop() {
let trie_bytes = if let Some((true, trie_bytes)) = cache.get(&trie_hash) {
trie_bytes
} else {
continue;
};
let lazy_trie: LazilyDeserializedTrie = bytesrepr::deserialize_from_slice(trie_bytes)?;
tries_to_write.extend(lazy_trie.iter_children());
Store::<Digest, Trie<Key, StoredValue>>::put_raw(
&*self.store,
&mut txn,
&trie_hash,
Cow::Borrowed(trie_bytes),
)?;
}
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 get<T>(&self, txn: &T, key: &Digest) -> Result<Option<Trie<Key, StoredValue>>, Self::Error>
where
T: Readable<Handle = Self::Handle>,
Digest: ToBytes,
Trie<Key, StoredValue>: bytesrepr::FromBytes,
Self::Error: From<T::Error>,
{
match self.get_raw(txn, key)? {
None => Ok(None),
Some(value_bytes) => {
let value = bytesrepr::deserialize(value_bytes.into())?;
Ok(Some(value))
}
}
}
fn get_raw<T>(&self, txn: &T, key: &Digest) -> Result<Option<Bytes>, Self::Error>
where
T: Readable<Handle = Self::Handle>,
Digest: AsRef<[u8]>,
Self::Error: From<T::Error>,
{
let mut store = self.cache.lock().map_err(|_| error::Error::Poison)?;
let maybe_trie = store.get(key);
match maybe_trie {
Some((_, trie_bytes)) => Ok(Some(trie_bytes.clone())),
None => {
let handle = self.handle();
match txn.read(handle, key.as_ref())? {
Some(trie_bytes) => {
match store.entry(*key) {
Entry::Occupied(_) => {}
Entry::Vacant(v) => {
v.insert((false, trie_bytes.clone()));
}
}
Ok(Some(trie_bytes))
}
None => Ok(None),
}
}
}
}
fn put<T>(
&self,
txn: &mut T,
key: &Digest,
value: &Trie<Key, StoredValue>,
) -> Result<(), Self::Error>
where
T: Writable<Handle = Self::Handle>,
Trie<Key, StoredValue>: ToBytes,
Self::Error: From<T::Error>,
{
self.put_raw(txn, key, Cow::Owned(value.to_bytes()?))
}
fn put_raw<T>(
&self,
_txn: &mut T,
key: &Digest,
value_bytes: Cow<'_, [u8]>,
) -> Result<(), Self::Error>
where
T: Writable<Handle = Self::Handle>,
Self::Error: From<T::Error>,
{
self.cache
.lock()
.map_err(|_| error::Error::Poison)?
.insert(*key, (true, Bytes::from(value_bytes.into_owned())));
Ok(())
}
}
impl TrieStore<Key, StoredValue> for ScratchTrieStore {}