use std::borrow::Cow;
use casper_types::{
bytesrepr::{self, Bytes, FromBytes, ToBytes},
Digest, Pointer,
};
use crate::global_state::{
transaction_source::{Readable, Writable},
trie::{PointerBlock, Trie, RADIX},
};
use super::{operations::common_prefix, TrieStore};
#[derive(Clone, Debug, thiserror::Error, Eq, PartialEq)]
pub enum CacheError {
#[error("Root not found: {0:?}")]
RootNotFound(Digest),
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum CachePointer<K, V> {
InMem(TrieCacheNode<K, V>),
Stored(Pointer),
}
impl<K, V> CachePointer<K, V> {
fn load_from_store<T, S, E>(&mut self, txn: &T, store: &S) -> Result<(), E>
where
K: FromBytes,
V: FromBytes,
T: Readable<Handle = S::Handle>,
S: TrieStore<K, V>,
S::Error: From<T::Error>,
E: From<S::Error> + From<bytesrepr::Error> + From<CacheError>,
{
if let CachePointer::Stored(pointer) = self {
let Some(stored_node) = store.get(txn, pointer.hash())? else {
return Err(CacheError::RootNotFound(pointer.into_hash()).into());
};
let trie_cache_node = stored_node.into();
*self = CachePointer::InMem(trie_cache_node);
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum TrieCacheNode<K, V> {
Leaf {
key: K,
value: V,
},
Branch {
pointer_block: Vec<Option<CachePointer<K, V>>>,
},
Extension {
affix: Bytes,
pointer: Box<CachePointer<K, V>>,
},
}
impl<K, V> From<Trie<K, V>> for TrieCacheNode<K, V> {
fn from(node: Trie<K, V>) -> Self {
match node {
Trie::Leaf { key, value } => Self::Leaf { key, value },
Trie::Node { pointer_block } => {
let mut new_pointer_block = Vec::with_capacity(RADIX);
for i in 0..RADIX {
new_pointer_block.push(pointer_block[i].map(|ptr| CachePointer::Stored(ptr)));
}
Self::Branch {
pointer_block: new_pointer_block,
}
}
Trie::Extension { affix, pointer } => Self::Extension {
affix,
pointer: Box::new(CachePointer::Stored(pointer)),
},
}
}
}
pub struct TrieCache<'a, K, V, S> {
root: TrieCacheNode<K, V>,
store: &'a S,
}
impl<'a, K, V, S> TrieCache<'a, K, V, S>
where
K: ToBytes + FromBytes + Clone + Eq,
V: ToBytes + FromBytes + Clone + Eq,
S: TrieStore<K, V> + 'a,
{
pub fn new<T, E>(txn: &T, store: &'a S, root: &Digest) -> Result<Self, E>
where
T: Readable<Handle = S::Handle>,
S::Error: From<T::Error>,
E: From<S::Error> + From<bytesrepr::Error> + From<CacheError>,
{
match store.get(txn, root)? {
Some(node) => Ok(Self {
root: node.into(),
store,
}),
None => Err(CacheError::RootNotFound(*root).into()),
}
}
pub fn insert<T, E>(&mut self, key: K, value: V, txn: &T) -> Result<(), E>
where
T: Readable<Handle = S::Handle>,
S::Error: From<T::Error>,
E: From<S::Error> + From<bytesrepr::Error> + From<CacheError>,
{
let path: Vec<u8> = key.to_bytes()?;
let mut depth: usize = 0;
let mut current = &mut self.root;
while depth < path.len() {
match current {
TrieCacheNode::Branch { pointer_block } => {
let index: usize = {
assert!(depth < path.len(), "depth must be < {}", path.len());
path[depth].into()
};
let pointer = &mut pointer_block[index];
if let Some(next) = pointer {
if depth == path.len() - 1 {
let leaf = TrieCacheNode::Leaf { key, value };
*next = CachePointer::InMem(leaf);
return Ok(());
} else {
depth += 1;
next.load_from_store::<_, _, E>(txn, self.store)?;
if let CachePointer::InMem(next) = next {
current = next;
} else {
unreachable!("Stored pointer should have been converted");
}
}
} else {
let leaf = TrieCacheNode::Leaf { key, value };
let _ = std::mem::replace(pointer, Some(CachePointer::InMem(leaf)));
return Ok(());
}
}
TrieCacheNode::Leaf {
key: old_key,
value: old_value,
} => {
if *old_key == key {
*old_value = value;
} else {
let mut pointer_block = Vec::with_capacity(RADIX);
pointer_block.resize_with(RADIX, || None::<CachePointer<K, V>>);
let old_key_bytes = old_key.to_bytes()?;
let shared_path = common_prefix(&old_key_bytes, &path);
let existing_idx = old_key_bytes[shared_path.len()] as usize;
pointer_block[existing_idx] =
Some(CachePointer::InMem(TrieCacheNode::Leaf {
key: old_key.clone(),
value: old_value.clone(),
}));
let new_idx = path[shared_path.len()] as usize;
pointer_block[new_idx] =
Some(CachePointer::InMem(TrieCacheNode::Leaf { key, value }));
let new_affix = { &shared_path[depth..] };
*current = if !new_affix.is_empty() {
TrieCacheNode::Extension {
affix: Bytes::from(new_affix),
pointer: Box::new(CachePointer::InMem(TrieCacheNode::Branch {
pointer_block,
})),
}
} else {
TrieCacheNode::Branch { pointer_block }
};
}
return Ok(());
}
TrieCacheNode::Extension { affix, ref pointer }
if path.len() < depth + affix.len()
|| affix.as_ref() != &path[depth..depth + affix.len()] =>
{
let shared_prefix = common_prefix(affix, &path[depth..]);
let mut pointer_block = Vec::with_capacity(RADIX);
pointer_block.resize_with(RADIX, || None::<CachePointer<K, V>>);
pointer_block[path[depth + shared_prefix.len()] as usize] =
Some(CachePointer::InMem(TrieCacheNode::Leaf { key, value }));
let post_branch_affix = &affix[shared_prefix.len() + 1..];
if !post_branch_affix.is_empty() {
let post_extension = TrieCacheNode::Extension {
affix: Bytes::from(post_branch_affix),
pointer: pointer.clone(),
};
let existing_idx = affix[shared_prefix.len()] as usize;
pointer_block[existing_idx] = Some(CachePointer::InMem(post_extension));
} else {
let existing_idx = affix[shared_prefix.len()] as usize;
pointer_block[existing_idx] = Some(*pointer.clone());
}
let new_branch = TrieCacheNode::Branch { pointer_block };
let next = if !shared_prefix.is_empty() {
TrieCacheNode::Extension {
affix: Bytes::from(shared_prefix),
pointer: Box::new(CachePointer::InMem(new_branch)),
}
} else {
new_branch
};
*current = next;
return Ok(());
}
TrieCacheNode::Extension {
affix,
ref mut pointer,
} => {
depth += affix.len();
pointer.load_from_store::<_, _, E>(txn, self.store)?;
if let CachePointer::InMem(next) = pointer.as_mut() {
current = next;
} else {
unreachable!("Stored pointer should have been converted");
}
}
}
}
Ok(())
}
fn traverse_and_store<T, E>(
node: TrieCacheNode<K, V>,
txn: &mut T,
store: &S,
) -> Result<Pointer, E>
where
T: Readable<Handle = S::Handle> + Writable<Handle = S::Handle>,
S::Error: From<T::Error>,
E: From<S::Error> + From<bytesrepr::Error> + From<CacheError>,
{
match node {
TrieCacheNode::Leaf { key, value } => {
let trie_leaf = Trie::leaf(key, value);
let (hash, trie_bytes) = trie_leaf.trie_hash_and_bytes()?;
store.put_raw(txn, &hash, Cow::from(trie_bytes))?;
Ok(Pointer::LeafPointer(hash))
}
TrieCacheNode::Branch { mut pointer_block } => {
let mut trie_pointer_block = PointerBlock::new();
for i in 0..RADIX {
trie_pointer_block[i] = Option::take(&mut pointer_block[i])
.map(|child| match child {
CachePointer::InMem(in_mem_child) => {
Self::traverse_and_store::<_, E>(in_mem_child, txn, store)
}
CachePointer::Stored(ptr) => Ok(ptr),
})
.transpose()?;
}
let trie_node = Trie::<K, V>::Node {
pointer_block: Box::new(trie_pointer_block),
};
let (hash, trie_bytes) = trie_node.trie_hash_and_bytes()?;
store.put_raw(txn, &hash, Cow::from(trie_bytes))?;
Ok(Pointer::NodePointer(hash))
}
TrieCacheNode::Extension { pointer, affix } => {
let pointer = match *pointer {
CachePointer::InMem(in_mem_ptr) => {
Self::traverse_and_store::<_, E>(in_mem_ptr, txn, store)
}
CachePointer::Stored(ptr) => Ok(ptr),
}?;
let trie_extension = Trie::<K, V>::extension(affix.to_vec(), pointer);
let (hash, trie_bytes) = trie_extension.trie_hash_and_bytes()?;
store.put_raw(txn, &hash, Cow::from(trie_bytes))?;
Ok(Pointer::NodePointer(hash))
}
}
}
pub fn store_cache<T, E>(self, txn: &mut T) -> Result<Digest, E>
where
T: Readable<Handle = S::Handle> + Writable<Handle = S::Handle>,
S::Error: From<T::Error>,
E: From<S::Error> + From<bytesrepr::Error> + From<CacheError>,
{
Self::traverse_and_store::<_, E>(self.root, txn, self.store)
.map(|root_pointer| root_pointer.into_hash())
}
}
#[cfg(test)]
mod tests {
use super::*;
impl<K, V, S> TrieCache<'_, K, V, S>
where
K: ToBytes + FromBytes + Clone + Eq,
V: ToBytes + FromBytes + Clone + Eq,
S: TrieStore<K, V>,
{
fn traverse(node: TrieCacheNode<K, V>) -> Pointer {
match node {
TrieCacheNode::Leaf { key, value } => {
let trie_leaf = Trie::leaf(key, value);
let hash = trie_leaf.trie_hash().unwrap();
Pointer::LeafPointer(hash)
}
TrieCacheNode::Branch { mut pointer_block } => {
let mut trie_pointer_block = PointerBlock::new();
for i in 0..RADIX {
trie_pointer_block[i] = Option::take(pointer_block.get_mut(i).unwrap())
.map(|child| match child {
CachePointer::InMem(in_mem_child) => Self::traverse(in_mem_child),
CachePointer::Stored(ptr) => ptr,
});
}
let trie_node = Trie::<K, V>::Node {
pointer_block: Box::new(trie_pointer_block),
};
let hash = trie_node.trie_hash().unwrap();
Pointer::NodePointer(hash)
}
TrieCacheNode::Extension { pointer, affix } => {
let pointer = match *pointer {
CachePointer::InMem(in_mem_ptr) => Self::traverse(in_mem_ptr),
CachePointer::Stored(ptr) => ptr,
};
let trie_extension = Trie::<K, V>::extension(affix.to_vec(), pointer);
let hash = trie_extension.trie_hash().unwrap();
Pointer::NodePointer(hash)
}
}
}
pub fn calculate_root_hash(self) -> Digest {
Self::traverse(self.root).into_hash()
}
}
}