use std::collections::{HashMap, HashSet};
use crate::StorageKey;
use sp_core::storage::PrefixedStorageKey;
pub struct BuildCache<H, N> {
roots_by_number: HashMap<N, H>,
changed_keys: HashMap<H, HashMap<Option<PrefixedStorageKey>, HashSet<StorageKey>>>,
}
#[derive(Debug, PartialEq)]
pub enum CacheAction<H, N> {
CacheBuildData(CachedBuildData<H, N>),
Clear,
}
#[derive(Debug, PartialEq)]
pub struct CachedBuildData<H, N> {
block: N,
trie_root: H,
digest_input_blocks: Vec<N>,
changed_keys: HashMap<Option<PrefixedStorageKey>, HashSet<StorageKey>>,
}
#[derive(Debug, PartialEq)]
pub(crate) enum IncompleteCacheAction<N> {
CacheBuildData(IncompleteCachedBuildData<N>),
Clear,
}
#[derive(Debug, PartialEq)]
pub(crate) struct IncompleteCachedBuildData<N> {
digest_input_blocks: Vec<N>,
changed_keys: HashMap<Option<PrefixedStorageKey>, HashSet<StorageKey>>,
}
impl<H, N> BuildCache<H, N>
where
N: Eq + ::std::hash::Hash,
H: Eq + ::std::hash::Hash + Clone,
{
pub fn new() -> Self {
BuildCache {
roots_by_number: HashMap::new(),
changed_keys: HashMap::new(),
}
}
pub fn get(&self, root: &H) -> Option<&HashMap<Option<PrefixedStorageKey>, HashSet<StorageKey>>> {
self.changed_keys.get(&root)
}
pub fn with_changed_keys(
&self,
root: &H,
functor: &mut dyn FnMut(&HashMap<Option<PrefixedStorageKey>, HashSet<StorageKey>>),
) -> bool {
match self.changed_keys.get(&root) {
Some(changed_keys) => {
functor(changed_keys);
true
},
None => false,
}
}
pub fn perform(&mut self, action: CacheAction<H, N>) {
match action {
CacheAction::CacheBuildData(data) => {
self.roots_by_number.insert(data.block, data.trie_root.clone());
self.changed_keys.insert(data.trie_root, data.changed_keys);
for digest_input_block in data.digest_input_blocks {
let digest_input_block_hash = self.roots_by_number.remove(&digest_input_block);
if let Some(digest_input_block_hash) = digest_input_block_hash {
self.changed_keys.remove(&digest_input_block_hash);
}
}
},
CacheAction::Clear => {
self.roots_by_number.clear();
self.changed_keys.clear();
},
}
}
}
impl<N> IncompleteCacheAction<N> {
pub fn collects_changed_keys(&self) -> bool {
match *self {
IncompleteCacheAction::CacheBuildData(_) => true,
IncompleteCacheAction::Clear => false,
}
}
pub(crate) fn complete<H: Clone>(self, block: N, trie_root: &H) -> CacheAction<H, N> {
match self {
IncompleteCacheAction::CacheBuildData(build_data) =>
CacheAction::CacheBuildData(build_data.complete(block, trie_root.clone())),
IncompleteCacheAction::Clear => CacheAction::Clear,
}
}
pub(crate) fn set_digest_input_blocks(self, digest_input_blocks: Vec<N>) -> Self {
match self {
IncompleteCacheAction::CacheBuildData(build_data) =>
IncompleteCacheAction::CacheBuildData(build_data.set_digest_input_blocks(digest_input_blocks)),
IncompleteCacheAction::Clear => IncompleteCacheAction::Clear,
}
}
pub(crate) fn insert(
self,
storage_key: Option<PrefixedStorageKey>,
changed_keys: HashSet<StorageKey>,
) -> Self {
match self {
IncompleteCacheAction::CacheBuildData(build_data) =>
IncompleteCacheAction::CacheBuildData(build_data.insert(storage_key, changed_keys)),
IncompleteCacheAction::Clear => IncompleteCacheAction::Clear,
}
}
}
impl<N> IncompleteCachedBuildData<N> {
pub(crate) fn new() -> Self {
IncompleteCachedBuildData {
digest_input_blocks: Vec::new(),
changed_keys: HashMap::new(),
}
}
fn complete<H>(self, block: N, trie_root: H) -> CachedBuildData<H, N> {
CachedBuildData {
block,
trie_root,
digest_input_blocks: self.digest_input_blocks,
changed_keys: self.changed_keys,
}
}
fn set_digest_input_blocks(mut self, digest_input_blocks: Vec<N>) -> Self {
self.digest_input_blocks = digest_input_blocks;
self
}
fn insert(
mut self,
storage_key: Option<PrefixedStorageKey>,
changed_keys: HashSet<StorageKey>,
) -> Self {
self.changed_keys.insert(storage_key, changed_keys);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn updated_keys_are_stored_when_non_top_level_digest_is_built() {
let mut data = IncompleteCachedBuildData::<u32>::new();
data = data.insert(None, vec![vec![1]].into_iter().collect());
assert_eq!(data.changed_keys.len(), 1);
let mut cache = BuildCache::new();
cache.perform(CacheAction::CacheBuildData(data.complete(1, 1)));
assert_eq!(cache.changed_keys.len(), 1);
assert_eq!(
cache.get(&1).unwrap().clone(),
vec![(None, vec![vec![1]].into_iter().collect())].into_iter().collect(),
);
}
#[test]
fn obsolete_entries_are_purged_when_new_ct_is_built() {
let mut cache = BuildCache::<u32, u32>::new();
cache.perform(CacheAction::CacheBuildData(IncompleteCachedBuildData::new()
.insert(None, vec![vec![1]].into_iter().collect())
.complete(1, 1)));
cache.perform(CacheAction::CacheBuildData(IncompleteCachedBuildData::new()
.insert(None, vec![vec![2]].into_iter().collect())
.complete(2, 2)));
cache.perform(CacheAction::CacheBuildData(IncompleteCachedBuildData::new()
.insert(None, vec![vec![3]].into_iter().collect())
.complete(3, 3)));
assert_eq!(cache.changed_keys.len(), 3);
cache.perform(CacheAction::CacheBuildData(IncompleteCachedBuildData::new()
.set_digest_input_blocks(vec![1, 2, 3])
.complete(4, 4)));
assert_eq!(cache.changed_keys.len(), 1);
cache.perform(CacheAction::CacheBuildData(IncompleteCachedBuildData::new()
.insert(None, vec![vec![8]].into_iter().collect())
.complete(8, 8)));
cache.perform(CacheAction::CacheBuildData(IncompleteCachedBuildData::new()
.insert(None, vec![vec![12]].into_iter().collect())
.complete(12, 12)));
assert_eq!(cache.changed_keys.len(), 3);
cache.perform(CacheAction::Clear);
assert_eq!(cache.changed_keys.len(), 0);
}
}