diff --git a/client/db/src/storage_cache.rs b/client/db/src/storage_cache.rs deleted file mode 100644 index 9dada92b066ea..0000000000000 --- a/client/db/src/storage_cache.rs +++ /dev/null @@ -1,1977 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! Global state cache. Maintains recently queried/committed state values -//! Tracks changes over the span of a few recent blocks and handles forks -//! by tracking/removing cache entries for conflicting changes. - -use crate::{stats::StateUsageStats, utils::Meta}; -use hash_db::Hasher; -use linked_hash_map::{Entry, LinkedHashMap}; -use log::trace; -use parking_lot::{RwLock, RwLockUpgradableReadGuard}; -use sp_core::{hexdisplay::HexDisplay, storage::ChildInfo}; -use sp_runtime::{ - traits::{Block as BlockT, HashFor, Header, NumberFor}, - StateVersion, -}; -use sp_state_machine::{ - backend::Backend as StateBackend, ChildStorageCollection, StorageCollection, StorageKey, - StorageValue, TrieBackend, -}; -use std::{ - collections::{HashMap, HashSet, VecDeque}, - hash::Hash as StdHash, - sync::Arc, -}; - -const STATE_CACHE_BLOCKS: usize = 12; - -type ChildStorageKey = (Vec, Vec); - -/// Shared canonical state cache. -pub struct Cache { - /// Storage cache. `None` indicates that key is known to be missing. - lru_storage: LRUMap>, - /// Storage hashes cache. `None` indicates that key is known to be missing. - lru_hashes: LRUMap>, - /// Storage cache for child trie. `None` indicates that key is known to be missing. - lru_child_storage: LRUMap>, - /// Information on the modifications in recently committed blocks; specifically which keys - /// changed in which block. Ordered by block number. - modifications: VecDeque>, -} - -struct LRUMap(LinkedHashMap, usize, usize); - -/// Internal trait similar to `heapsize` but using -/// a simply estimation. -/// -/// This should not be made public, it is implementation -/// detail trait. If it need to become public please -/// consider using `malloc_size_of`. -trait EstimateSize { - /// Return a size estimation of additional size needed - /// to cache this struct (in bytes). - fn estimate_size(&self) -> usize; -} - -impl EstimateSize for Vec { - fn estimate_size(&self) -> usize { - self.capacity() - } -} - -impl EstimateSize for Option> { - fn estimate_size(&self) -> usize { - self.as_ref().map(|v| v.capacity()).unwrap_or(0) - } -} - -struct OptionHOut>(Option); - -impl> EstimateSize for OptionHOut { - fn estimate_size(&self) -> usize { - // capacity would be better - self.0.as_ref().map(|v| v.as_ref().len()).unwrap_or(0) - } -} - -impl EstimateSize for (T, T) { - fn estimate_size(&self) -> usize { - self.0.estimate_size() + self.1.estimate_size() - } -} - -impl LRUMap { - fn remove(&mut self, k: &K) { - let map = &mut self.0; - let storage_used_size = &mut self.1; - if let Some(v) = map.remove(k) { - *storage_used_size -= k.estimate_size(); - *storage_used_size -= v.estimate_size(); - } - } - - fn add(&mut self, k: K, v: V) { - let lmap = &mut self.0; - let storage_used_size = &mut self.1; - let limit = self.2; - let klen = k.estimate_size(); - *storage_used_size += v.estimate_size(); - // TODO assert k v size fit into limit?? to avoid insert remove? - match lmap.entry(k) { - Entry::Occupied(mut entry) => { - // note that in this case we are not running pure lru as - // it would require to remove first - *storage_used_size -= entry.get().estimate_size(); - entry.insert(v); - }, - Entry::Vacant(entry) => { - *storage_used_size += klen; - entry.insert(v); - }, - }; - - while *storage_used_size > limit { - if let Some((k, v)) = lmap.pop_front() { - *storage_used_size -= k.estimate_size(); - *storage_used_size -= v.estimate_size(); - } else { - // can happen fairly often as we get value from multiple lru - // and only remove from a single lru - break - } - } - } - - fn get(&mut self, k: &Q) -> Option<&mut V> - where - K: std::borrow::Borrow, - Q: StdHash + Eq, - { - self.0.get_refresh(k) - } - - fn used_size(&self) -> usize { - self.1 - } - fn clear(&mut self) { - self.0.clear(); - self.1 = 0; - } -} - -impl Cache { - /// Returns the used memory size of the storage cache in bytes. - pub fn used_storage_cache_size(&self) -> usize { - self.lru_storage.used_size() + self.lru_child_storage.used_size() - // ignore small hashes storage and self.lru_hashes.used_size() - } - - /// Synchronize the shared cache with the best block state. - /// - /// This function updates the shared cache by removing entries - /// that are invalidated by chain reorganization. It should be called - /// externally when chain reorg happens without importing a new block. - pub fn sync(&mut self, enacted: &[B::Hash], retracted: &[B::Hash]) { - trace!("Syncing shared cache, enacted = {:?}, retracted = {:?}", enacted, retracted); - - // Purge changes from re-enacted and retracted blocks. - let mut clear = false; - for block in enacted { - clear = clear || { - if let Some(m) = self.modifications.iter_mut().find(|m| &m.hash == block) { - trace!("Reverting enacted block {:?}", block); - m.is_canon = true; - for a in &m.storage { - trace!("Reverting enacted key {:?}", HexDisplay::from(a)); - self.lru_storage.remove(a); - self.lru_hashes.remove(a); - } - for a in &m.child_storage { - trace!("Reverting enacted child key {:?}", a); - self.lru_child_storage.remove(a); - } - false - } else { - true - } - }; - } - - for block in retracted { - clear = clear || { - if let Some(m) = self.modifications.iter_mut().find(|m| &m.hash == block) { - trace!("Retracting block {:?}", block); - m.is_canon = false; - for a in &m.storage { - trace!("Retracted key {:?}", HexDisplay::from(a)); - self.lru_storage.remove(a); - self.lru_hashes.remove(a); - } - for a in &m.child_storage { - trace!("Retracted child key {:?}", a); - self.lru_child_storage.remove(a); - } - false - } else { - true - } - }; - } - if clear { - // We don't know anything about the block; clear everything - trace!("Wiping cache"); - self.lru_storage.clear(); - self.lru_child_storage.clear(); - self.lru_hashes.clear(); - self.modifications.clear(); - } - } -} - -pub type SharedCache = Arc>>; - -/// Fix lru storage size for hash (small 64ko). -const FIX_LRU_HASH_SIZE: usize = 65_536; - -/// Create a new shared cache instance with given max memory usage. -pub fn new_shared_cache( - shared_cache_size: usize, - child_ratio: (usize, usize), -) -> SharedCache { - let top = child_ratio.1.saturating_sub(child_ratio.0); - Arc::new(RwLock::new(Cache { - lru_storage: LRUMap(LinkedHashMap::new(), 0, shared_cache_size * top / child_ratio.1), - lru_hashes: LRUMap(LinkedHashMap::new(), 0, FIX_LRU_HASH_SIZE), - lru_child_storage: LRUMap( - LinkedHashMap::new(), - 0, - shared_cache_size * child_ratio.0 / child_ratio.1, - ), - modifications: VecDeque::new(), - })) -} - -#[derive(Debug)] -/// Accumulates a list of storage changed in a block. -struct BlockChanges { - /// Block number. - number: B::Number, - /// Block hash. - hash: B::Hash, - /// Parent block hash. - parent: B::Hash, - /// A set of modified storage keys. - storage: HashSet, - /// A set of modified child storage keys. - child_storage: HashSet, - /// Block is part of the canonical chain. - is_canon: bool, -} - -/// Cached values specific to a state. -struct LocalCache { - /// Storage cache. - /// - /// `None` indicates that key is known to be missing. - storage: HashMap>, - /// Storage hashes cache. - /// - /// `None` indicates that key is known to be missing. - hashes: HashMap>, - /// Child storage cache. - /// - /// `None` indicates that key is known to be missing. - child_storage: HashMap>, -} - -/// Cache changes. -pub struct CacheChanges { - /// Shared canonical state cache. - shared_cache: SharedCache, - /// Local cache of values for this state. - local_cache: RwLock>>, - /// Hash of the block on top of which this instance was created or - /// `None` if cache is disabled - pub parent_hash: Option, -} - -/// State cache abstraction. -/// -/// Manages shared global state cache which reflects the canonical -/// state as it is on the disk. -/// -/// A instance of `CachingState` may be created as canonical or not. -/// For canonical instances local cache is accumulated and applied -/// in `sync_cache` along with the change overlay. -/// For non-canonical clones local cache and changes are dropped. -pub struct CachingState { - /// Usage statistics - usage: StateUsageStats, - /// State machine registered stats - overlay_stats: sp_state_machine::StateMachineStats, - /// Backing state. - state: S, - /// Cache data. - cache: CacheChanges, -} - -impl std::fmt::Debug for CachingState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Block {:?}", self.cache.parent_hash) - } -} - -impl CacheChanges { - /// Propagate local cache into the shared cache and synchronize - /// the shared cache with the best block state. - /// - /// This function updates the shared cache by removing entries - /// that are invalidated by chain reorganization. `sync_cache` - /// should be called after the block has been committed and the - /// blockchain route has been calculated. - pub fn sync_cache( - &mut self, - enacted: &[B::Hash], - retracted: &[B::Hash], - changes: StorageCollection, - child_changes: ChildStorageCollection, - commit_hash: Option, - commit_number: Option>, - is_best: bool, - ) { - let mut cache = self.shared_cache.write(); - trace!( - "Syncing cache, id = (#{:?}, {:?}), parent={:?}, best={}", - commit_number, - commit_hash, - self.parent_hash, - is_best, - ); - let cache = &mut *cache; - // Filter out committing block if any. - let mut enacted: Vec<_> = enacted - .iter() - .filter(|h| commit_hash.as_ref().map_or(true, |p| *h != p)) - .cloned() - .collect(); - - let mut retracted = std::borrow::Cow::Borrowed(retracted); - if let Some(commit_hash) = &commit_hash { - if let Some(m) = cache.modifications.iter_mut().find(|m| &m.hash == commit_hash) { - if m.is_canon != is_best { - // Same block comitted twice with different state changes. - // Treat it as reenacted/retracted. - if is_best { - enacted.push(*commit_hash); - } else { - retracted.to_mut().push(*commit_hash); - } - } - } - } - cache.sync(&enacted, &retracted); - // Propagate cache only if committing on top of the latest canonical state - // blocks are ordered by number and only one block with a given number is marked as - // canonical (contributed to canonical state cache) - if self.parent_hash.is_some() { - let mut local_cache = self.local_cache.write(); - if is_best { - trace!( - "Committing {} local, {} hashes, {} modified root entries, {} modified child entries", - local_cache.storage.len(), - local_cache.hashes.len(), - changes.len(), - child_changes.iter().map(|v|v.1.len()).sum::(), - ); - for (k, v) in local_cache.storage.drain() { - cache.lru_storage.add(k, v); - } - for (k, v) in local_cache.child_storage.drain() { - cache.lru_child_storage.add(k, v); - } - for (k, v) in local_cache.hashes.drain() { - cache.lru_hashes.add(k, OptionHOut(v)); - } - } - } - - if let (Some(ref number), Some(ref hash), Some(ref parent)) = - (commit_number, commit_hash, self.parent_hash) - { - if cache.modifications.len() == STATE_CACHE_BLOCKS { - cache.modifications.pop_back(); - } - let mut modifications = HashSet::new(); - let mut child_modifications = HashSet::new(); - child_changes.into_iter().for_each(|(sk, changes)| { - for (k, v) in changes.into_iter() { - let k = (sk.clone(), k); - if is_best { - cache.lru_child_storage.add(k.clone(), v); - } - child_modifications.insert(k); - } - }); - for (k, v) in changes.into_iter() { - if is_best { - cache.lru_hashes.remove(&k); - cache.lru_storage.add(k.clone(), v); - } - modifications.insert(k); - } - - // Save modified storage. These are ordered by the block number in reverse. - let block_changes = BlockChanges { - storage: modifications, - child_storage: child_modifications, - number: *number, - hash: *hash, - is_canon: is_best, - parent: *parent, - }; - let insert_at = cache - .modifications - .iter() - .enumerate() - .find(|(_, m)| m.number < *number) - .map(|(i, _)| i); - trace!("Inserting modifications at {:?}", insert_at); - if let Some(insert_at) = insert_at { - cache.modifications.insert(insert_at, block_changes); - } else { - cache.modifications.push_back(block_changes); - } - } - } -} - -impl>, B: BlockT> CachingState { - /// Create a new instance wrapping generic State and shared cache. - pub(crate) fn new( - state: S, - shared_cache: SharedCache, - parent_hash: Option, - ) -> Self { - CachingState { - usage: StateUsageStats::new(), - overlay_stats: sp_state_machine::StateMachineStats::default(), - state, - cache: CacheChanges { - shared_cache, - local_cache: RwLock::new(LocalCache { - storage: Default::default(), - hashes: Default::default(), - child_storage: Default::default(), - }), - parent_hash, - }, - } - } - - /// Check if the key can be returned from cache by matching current block parent hash against - /// canonical state and filtering out entries modified in later blocks. - fn is_allowed( - key: Option<&[u8]>, - child_key: Option<&ChildStorageKey>, - parent_hash: &Option, - modifications: &VecDeque>, - ) -> bool { - let mut parent = match *parent_hash { - None => { - trace!( - "Cache lookup skipped for {:?}: no parent hash", - key.as_ref().map(HexDisplay::from) - ); - return false - }, - Some(ref parent) => parent, - }; - // Ignore all storage entries modified in later blocks. - // Modifications contains block ordered by the number - // We search for our parent in that list first and then for - // all its parents until we hit the canonical block, - // checking against all the intermediate modifications. - for m in modifications { - if &m.hash == parent { - if m.is_canon { - return true - } - parent = &m.parent; - } - if let Some(key) = key { - if m.storage.contains(key) { - trace!( - "Cache lookup skipped for {:?}: modified in a later block", - HexDisplay::from(&key) - ); - return false - } - } - if let Some(child_key) = child_key { - if m.child_storage.contains(child_key) { - trace!("Cache lookup skipped for {:?}: modified in a later block", child_key); - return false - } - } - } - trace!( - "Cache lookup skipped for {:?}: parent hash is unknown", - key.as_ref().map(HexDisplay::from), - ); - false - } -} - -impl>, B: BlockT> StateBackend> for CachingState { - type Error = S::Error; - type Transaction = S::Transaction; - type TrieBackendStorage = S::TrieBackendStorage; - - fn storage(&self, key: &[u8]) -> Result>, Self::Error> { - let local_cache = self.cache.local_cache.upgradable_read(); - // Note that local cache makes that lru is not refreshed - if let Some(entry) = local_cache.storage.get(key).cloned() { - trace!("Found in local cache: {:?}", HexDisplay::from(&key)); - self.usage.tally_key_read(key, entry.as_ref(), true); - - return Ok(entry) - } - { - let cache = self.cache.shared_cache.upgradable_read(); - if Self::is_allowed(Some(key), None, &self.cache.parent_hash, &cache.modifications) { - let mut cache = RwLockUpgradableReadGuard::upgrade(cache); - if let Some(entry) = cache.lru_storage.get(key).map(|a| a.clone()) { - trace!("Found in shared cache: {:?}", HexDisplay::from(&key)); - self.usage.tally_key_read(key, entry.as_ref(), true); - return Ok(entry) - } - } - } - trace!("Cache miss: {:?}", HexDisplay::from(&key)); - let value = self.state.storage(key)?; - RwLockUpgradableReadGuard::upgrade(local_cache) - .storage - .insert(key.to_vec(), value.clone()); - self.usage.tally_key_read(key, value.as_ref(), false); - Ok(value) - } - - fn storage_hash(&self, key: &[u8]) -> Result, Self::Error> { - let local_cache = self.cache.local_cache.upgradable_read(); - if let Some(entry) = local_cache.hashes.get(key).cloned() { - trace!("Found hash in local cache: {:?}", HexDisplay::from(&key)); - return Ok(entry) - } - { - let cache = self.cache.shared_cache.upgradable_read(); - if Self::is_allowed(Some(key), None, &self.cache.parent_hash, &cache.modifications) { - let mut cache = RwLockUpgradableReadGuard::upgrade(cache); - if let Some(entry) = cache.lru_hashes.get(key).map(|a| a.0) { - trace!("Found hash in shared cache: {:?}", HexDisplay::from(&key)); - return Ok(entry) - } - } - } - trace!("Cache hash miss: {:?}", HexDisplay::from(&key)); - let hash = self.state.storage_hash(key)?; - RwLockUpgradableReadGuard::upgrade(local_cache) - .hashes - .insert(key.to_vec(), hash); - Ok(hash) - } - - fn child_storage( - &self, - child_info: &ChildInfo, - key: &[u8], - ) -> Result>, Self::Error> { - let key = (child_info.storage_key().to_vec(), key.to_vec()); - let local_cache = self.cache.local_cache.upgradable_read(); - if let Some(entry) = local_cache.child_storage.get(&key).cloned() { - trace!("Found in local cache: {:?}", key); - return Ok(self.usage.tally_child_key_read(&key, entry, true)) - } - { - let cache = self.cache.shared_cache.upgradable_read(); - if Self::is_allowed(None, Some(&key), &self.cache.parent_hash, &cache.modifications) { - let mut cache = RwLockUpgradableReadGuard::upgrade(cache); - if let Some(entry) = cache.lru_child_storage.get(&key).map(|a| a.clone()) { - trace!("Found in shared cache: {:?}", key); - return Ok(self.usage.tally_child_key_read(&key, entry, true)) - } - } - } - trace!("Cache miss: {:?}", key); - let value = self.state.child_storage(child_info, &key.1[..])?; - - // just pass it through the usage counter - let value = self.usage.tally_child_key_read(&key, value, false); - - RwLockUpgradableReadGuard::upgrade(local_cache) - .child_storage - .insert(key, value.clone()); - Ok(value) - } - - fn exists_storage(&self, key: &[u8]) -> Result { - Ok(self.storage(key)?.is_some()) - } - - fn exists_child_storage( - &self, - child_info: &ChildInfo, - key: &[u8], - ) -> Result { - self.state.exists_child_storage(child_info, key) - } - - fn apply_to_key_values_while, Vec) -> bool>( - &self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - start_at: Option<&[u8]>, - f: F, - allow_missing: bool, - ) -> Result { - self.state - .apply_to_key_values_while(child_info, prefix, start_at, f, allow_missing) - } - - fn apply_to_keys_while bool>( - &self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - f: F, - ) { - self.state.apply_to_keys_while(child_info, prefix, f) - } - - fn next_storage_key(&self, key: &[u8]) -> Result>, Self::Error> { - self.state.next_storage_key(key) - } - - fn next_child_storage_key( - &self, - child_info: &ChildInfo, - key: &[u8], - ) -> Result>, Self::Error> { - self.state.next_child_storage_key(child_info, key) - } - - fn for_keys_with_prefix(&self, prefix: &[u8], f: F) { - self.state.for_keys_with_prefix(prefix, f) - } - - fn for_key_values_with_prefix(&self, prefix: &[u8], f: F) { - self.state.for_key_values_with_prefix(prefix, f) - } - - fn for_child_keys_with_prefix( - &self, - child_info: &ChildInfo, - prefix: &[u8], - f: F, - ) { - self.state.for_child_keys_with_prefix(child_info, prefix, f) - } - - fn storage_root<'a>( - &self, - delta: impl Iterator)>, - state_version: StateVersion, - ) -> (B::Hash, Self::Transaction) - where - B::Hash: Ord, - { - self.state.storage_root(delta, state_version) - } - - fn child_storage_root<'a>( - &self, - child_info: &ChildInfo, - delta: impl Iterator)>, - state_version: StateVersion, - ) -> (B::Hash, bool, Self::Transaction) - where - B::Hash: Ord, - { - self.state.child_storage_root(child_info, delta, state_version) - } - - fn pairs(&self) -> Vec<(Vec, Vec)> { - self.state.pairs() - } - - fn keys(&self, prefix: &[u8]) -> Vec> { - self.state.keys(prefix) - } - - fn child_keys(&self, child_info: &ChildInfo, prefix: &[u8]) -> Vec> { - self.state.child_keys(child_info, prefix) - } - - fn as_trie_backend(&self) -> Option<&TrieBackend>> { - self.state.as_trie_backend() - } - - fn register_overlay_stats(&self, stats: &sp_state_machine::StateMachineStats) { - self.overlay_stats.add(stats); - } - - fn usage_info(&self) -> sp_state_machine::UsageInfo { - let mut info = self.usage.take(); - info.include_state_machine_states(&self.overlay_stats); - info - } -} - -/// Extended [`CachingState`] that will sync the caches on drop. -pub struct SyncingCachingState { - /// The usage statistics of the backend. These will be updated on drop. - state_usage: Arc, - /// Reference to the meta db. - meta: Arc, Block::Hash>>>, - /// Mutex to lock get exlusive access to the backend. - lock: Arc>, - /// The wrapped caching state. - /// - /// This is required to be a `Option`, because sometimes we want to extract - /// the cache changes and Rust does not allow to move fields from types that - /// implement `Drop`. - caching_state: Option>, - /// Disable syncing of the cache. This is by default always `false`. However, - /// we need to disable syncing when this is a state in a - /// [`BlockImportOperation`](crate::BlockImportOperation). The import operation - /// takes care to sync the cache and more importantly we want to prevent a dead - /// lock. - disable_syncing: bool, -} - -impl SyncingCachingState { - /// Create new automatic syncing state. - pub fn new( - caching_state: CachingState, - state_usage: Arc, - meta: Arc, B::Hash>>>, - lock: Arc>, - ) -> Self { - Self { caching_state: Some(caching_state), state_usage, meta, lock, disable_syncing: false } - } - - /// Returns the reference to the internal [`CachingState`]. - fn caching_state(&self) -> &CachingState { - self.caching_state - .as_ref() - .expect("`caching_state` is always valid for the lifetime of the object; qed") - } - - /// Convert `Self` into the cache changes. - pub fn into_cache_changes(mut self) -> CacheChanges { - self.caching_state - .take() - .expect("`caching_state` is always valid for the lifetime of the object; qed") - .cache - } - - /// Disable syncing the cache on drop. - pub fn disable_syncing(&mut self) { - self.disable_syncing = true; - } -} - -impl std::fmt::Debug for SyncingCachingState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.caching_state().fmt(f) - } -} - -impl>, B: BlockT> StateBackend> - for SyncingCachingState -{ - type Error = S::Error; - type Transaction = S::Transaction; - type TrieBackendStorage = S::TrieBackendStorage; - - fn storage(&self, key: &[u8]) -> Result>, Self::Error> { - self.caching_state().storage(key) - } - - fn storage_hash(&self, key: &[u8]) -> Result, Self::Error> { - self.caching_state().storage_hash(key) - } - - fn child_storage( - &self, - child_info: &ChildInfo, - key: &[u8], - ) -> Result>, Self::Error> { - self.caching_state().child_storage(child_info, key) - } - - fn exists_storage(&self, key: &[u8]) -> Result { - self.caching_state().exists_storage(key) - } - - fn exists_child_storage( - &self, - child_info: &ChildInfo, - key: &[u8], - ) -> Result { - self.caching_state().exists_child_storage(child_info, key) - } - - fn apply_to_key_values_while, Vec) -> bool>( - &self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - start_at: Option<&[u8]>, - f: F, - allow_missing: bool, - ) -> Result { - self.caching_state().apply_to_key_values_while( - child_info, - prefix, - start_at, - f, - allow_missing, - ) - } - - fn apply_to_keys_while bool>( - &self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - f: F, - ) { - self.caching_state().apply_to_keys_while(child_info, prefix, f) - } - - fn next_storage_key(&self, key: &[u8]) -> Result>, Self::Error> { - self.caching_state().next_storage_key(key) - } - - fn next_child_storage_key( - &self, - child_info: &ChildInfo, - key: &[u8], - ) -> Result>, Self::Error> { - self.caching_state().next_child_storage_key(child_info, key) - } - - fn for_keys_with_prefix(&self, prefix: &[u8], f: F) { - self.caching_state().for_keys_with_prefix(prefix, f) - } - - fn for_key_values_with_prefix(&self, prefix: &[u8], f: F) { - self.caching_state().for_key_values_with_prefix(prefix, f) - } - - fn for_child_keys_with_prefix( - &self, - child_info: &ChildInfo, - prefix: &[u8], - f: F, - ) { - self.caching_state().for_child_keys_with_prefix(child_info, prefix, f) - } - - fn storage_root<'a>( - &self, - delta: impl Iterator)>, - state_version: StateVersion, - ) -> (B::Hash, Self::Transaction) - where - B::Hash: Ord, - { - self.caching_state().storage_root(delta, state_version) - } - - fn child_storage_root<'a>( - &self, - child_info: &ChildInfo, - delta: impl Iterator)>, - state_version: StateVersion, - ) -> (B::Hash, bool, Self::Transaction) - where - B::Hash: Ord, - { - self.caching_state().child_storage_root(child_info, delta, state_version) - } - - fn pairs(&self) -> Vec<(Vec, Vec)> { - self.caching_state().pairs() - } - - fn keys(&self, prefix: &[u8]) -> Vec> { - self.caching_state().keys(prefix) - } - - fn child_keys(&self, child_info: &ChildInfo, prefix: &[u8]) -> Vec> { - self.caching_state().child_keys(child_info, prefix) - } - - fn as_trie_backend(&self) -> Option<&TrieBackend>> { - self.caching_state - .as_ref() - .expect("`caching_state` is valid for the lifetime of the object; qed") - .as_trie_backend() - } - - fn register_overlay_stats(&self, stats: &sp_state_machine::StateMachineStats) { - self.caching_state().register_overlay_stats(stats); - } - - fn usage_info(&self) -> sp_state_machine::UsageInfo { - self.caching_state().usage_info() - } -} - -impl Drop for SyncingCachingState { - fn drop(&mut self) { - if self.disable_syncing { - return - } - - if let Some(mut caching_state) = self.caching_state.take() { - let _lock = self.lock.read(); - - self.state_usage.merge_sm(caching_state.usage.take()); - if let Some(hash) = caching_state.cache.parent_hash { - let is_best = self.meta.read().best_hash == hash; - caching_state.cache.sync_cache(&[], &[], vec![], vec![], None, None, is_best); - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use sp_runtime::{ - testing::{Block as RawBlock, ExtrinsicWrapper, H256}, - traits::BlakeTwo256, - }; - use sp_state_machine::InMemoryBackend; - - type Block = RawBlock>; - - #[test] - fn smoke() { - // init_log(); - let root_parent = H256::random(); - let key = H256::random()[..].to_vec(); - let h0 = H256::random(); - let h1a = H256::random(); - let h1b = H256::random(); - let h2a = H256::random(); - let h2b = H256::random(); - let h3a = H256::random(); - let h3b = H256::random(); - - let shared = new_shared_cache::(256 * 1024, (0, 1)); - - // blocks [ 3a(c) 2a(c) 2b 1b 1a(c) 0 ] - // state [ 5 5 4 3 2 2 ] - let mut s = CachingState::new( - InMemoryBackend::::default(), - shared.clone(), - Some(root_parent), - ); - s.cache.sync_cache( - &[], - &[], - vec![(key.clone(), Some(vec![2]))], - vec![], - Some(h0), - Some(0), - true, - ); - - let mut s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h0)); - s.cache.sync_cache(&[], &[], vec![], vec![], Some(h1a), Some(1), true); - - let mut s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h0)); - s.cache.sync_cache( - &[], - &[], - vec![(key.clone(), Some(vec![3]))], - vec![], - Some(h1b), - Some(1), - false, - ); - - let mut s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h1b)); - s.cache.sync_cache( - &[], - &[], - vec![(key.clone(), Some(vec![4]))], - vec![], - Some(h2b), - Some(2), - false, - ); - - let mut s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h1a)); - s.cache.sync_cache( - &[], - &[], - vec![(key.clone(), Some(vec![5]))], - vec![], - Some(h2a), - Some(2), - true, - ); - - let mut s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h2a)); - s.cache.sync_cache(&[], &[], vec![], vec![], Some(h3a), Some(3), true); - - let s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h3a)); - assert_eq!(s.storage(&key).unwrap().unwrap(), vec![5]); - - let s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h1a)); - assert!(s.storage(&key).unwrap().is_none()); - - let s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h2b)); - assert!(s.storage(&key).unwrap().is_none()); - - let s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h1b)); - assert!(s.storage(&key).unwrap().is_none()); - - // reorg to 3b - // blocks [ 3b(c) 3a 2a 2b(c) 1b 1a 0 ] - let mut s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h2b)); - s.cache.sync_cache( - &[h1b, h2b, h3b], - &[h1a, h2a, h3a], - vec![], - vec![], - Some(h3b), - Some(3), - true, - ); - let s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h3a)); - assert!(s.storage(&key).unwrap().is_none()); - } - - #[test] - fn simple_fork() { - sp_tracing::try_init_simple(); - - let root_parent = H256::random(); - let key = H256::random()[..].to_vec(); - let h1 = H256::random(); - let h2a = H256::random(); - let h2b = H256::random(); - let h3b = H256::random(); - - let shared = new_shared_cache::(256 * 1024, (0, 1)); - - let mut s = CachingState::new( - InMemoryBackend::::default(), - shared.clone(), - Some(root_parent), - ); - s.cache.sync_cache( - &[], - &[], - vec![(key.clone(), Some(vec![2]))], - vec![], - Some(h1), - Some(1), - true, - ); - - let mut s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h1)); - s.cache.sync_cache(&[], &[], vec![], vec![], Some(h2a), Some(2), true); - - let mut s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h1)); - s.cache.sync_cache( - &[], - &[], - vec![(key.clone(), Some(vec![3]))], - vec![], - Some(h2b), - Some(2), - false, - ); - - let mut s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h2b)); - s.cache.sync_cache( - &[], - &[], - vec![(key.clone(), Some(vec![3]))], - vec![], - Some(h3b), - Some(2), - false, - ); - - let s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h2a)); - assert_eq!(s.storage(&key).unwrap().unwrap(), vec![2]); - } - - #[test] - fn double_fork() { - let root_parent = H256::random(); - let key = H256::random()[..].to_vec(); - let h1 = H256::random(); - let h2a = H256::random(); - let h2b = H256::random(); - let h3a = H256::random(); - let h3b = H256::random(); - - let shared = new_shared_cache::(256 * 1024, (0, 1)); - - let mut s = CachingState::new( - InMemoryBackend::::default(), - shared.clone(), - Some(root_parent), - ); - s.cache.sync_cache(&[], &[], vec![], vec![], Some(h1), Some(1), true); - - let mut s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h1)); - s.cache.sync_cache(&[], &[], vec![], vec![], Some(h2a), Some(2), true); - - let mut s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h2a)); - s.cache.sync_cache( - &[], - &[], - vec![(key.clone(), Some(vec![2]))], - vec![], - Some(h3a), - Some(3), - true, - ); - - let mut s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h1)); - s.cache.sync_cache(&[], &[], vec![], vec![], Some(h2b), Some(2), false); - - let mut s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h2b)); - s.cache.sync_cache( - &[], - &[], - vec![(key.clone(), Some(vec![3]))], - vec![], - Some(h3b), - Some(3), - false, - ); - - let s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h3a)); - assert_eq!(s.storage(&key).unwrap().unwrap(), vec![2]); - } - - #[test] - fn reverts_storage_hash() { - let root_parent = H256::random(); - let key = H256::random()[..].to_vec(); - let h1a = H256::random(); - let h1b = H256::random(); - - let shared = new_shared_cache::(256 * 1024, (0, 1)); - let mut backend = InMemoryBackend::::default(); - backend.insert( - std::iter::once((None, vec![(key.clone(), Some(vec![1]))])), - Default::default(), - ); - - let mut s = CachingState::new(backend.clone(), shared.clone(), Some(root_parent)); - s.cache.sync_cache( - &[], - &[], - vec![(key.clone(), Some(vec![2]))], - vec![], - Some(h1a), - Some(1), - true, - ); - - let mut s = CachingState::new(backend.clone(), shared.clone(), Some(root_parent)); - s.cache.sync_cache(&[], &[h1a], vec![], vec![], Some(h1b), Some(1), true); - - let s = CachingState::new(backend.clone(), shared.clone(), Some(h1b)); - assert_eq!(s.storage_hash(&key).unwrap().unwrap(), BlakeTwo256::hash(&vec![1])); - } - - #[test] - fn should_track_used_size_correctly() { - let root_parent = H256::random(); - let shared = new_shared_cache::(109, ((109 - 36), 109)); - let h0 = H256::random(); - - let mut s = CachingState::new( - InMemoryBackend::::default(), - shared.clone(), - Some(root_parent.clone()), - ); - - let key = H256::random()[..].to_vec(); - let s_key = H256::random()[..].to_vec(); - s.cache.sync_cache( - &[], - &[], - vec![(key.clone(), Some(vec![1, 2, 3]))], - vec![], - Some(h0), - Some(0), - true, - ); - // 32 key, 3 byte size - assert_eq!(shared.read().used_storage_cache_size(), 35 /* bytes */); - - let key = H256::random()[..].to_vec(); - s.cache.sync_cache( - &[], - &[], - vec![], - vec![(s_key.clone(), vec![(key.clone(), Some(vec![1, 2]))])], - Some(h0), - Some(0), - true, - ); - // 35 + (2 * 32) key, 2 byte size - assert_eq!(shared.read().used_storage_cache_size(), 101 /* bytes */); - } - - #[test] - fn should_remove_lru_items_based_on_tracking_used_size() { - let root_parent = H256::random(); - let shared = new_shared_cache::(36 * 3, (2, 3)); - let h0 = H256::random(); - - let mut s = CachingState::new( - InMemoryBackend::::default(), - shared.clone(), - Some(root_parent), - ); - - let key = H256::random()[..].to_vec(); - s.cache.sync_cache( - &[], - &[], - vec![(key.clone(), Some(vec![1, 2, 3, 4]))], - vec![], - Some(h0), - Some(0), - true, - ); - // 32 key, 4 byte size - assert_eq!(shared.read().used_storage_cache_size(), 36 /* bytes */); - - let key = H256::random()[..].to_vec(); - s.cache.sync_cache( - &[], - &[], - vec![(key.clone(), Some(vec![1, 2]))], - vec![], - Some(h0), - Some(0), - true, - ); - // 32 key, 2 byte size - assert_eq!(shared.read().used_storage_cache_size(), 34 /* bytes */); - } - - #[test] - fn fix_storage_mismatch_issue() { - sp_tracing::try_init_simple(); - let root_parent = H256::random(); - - let key = H256::random()[..].to_vec(); - - let h0 = H256::random(); - let h1 = H256::random(); - - let shared = new_shared_cache::(256 * 1024, (0, 1)); - let mut s = CachingState::new( - InMemoryBackend::::default(), - shared.clone(), - Some(root_parent.clone()), - ); - s.cache.sync_cache( - &[], - &[], - vec![(key.clone(), Some(vec![2]))], - vec![], - Some(h0.clone()), - Some(0), - true, - ); - - let mut s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h0)); - s.cache.sync_cache( - &[], - &[], - vec![(key.clone(), Some(vec![3]))], - vec![], - Some(h1), - Some(1), - true, - ); - - let mut s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h1)); - assert_eq!(s.storage(&key).unwrap(), Some(vec![3])); - - // Restart (or unknown block?), clear caches. - { - let mut cache = s.cache.shared_cache.write(); - let cache = &mut *cache; - cache.lru_storage.clear(); - cache.lru_hashes.clear(); - cache.lru_child_storage.clear(); - cache.modifications.clear(); - } - - // New value is written because of cache miss. - s.cache.local_cache.write().storage.insert(key.clone(), Some(vec![42])); - - // New value is propagated. - s.cache.sync_cache(&[], &[], vec![], vec![], None, None, true); - - let s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h1)); - assert_eq!(s.storage(&key).unwrap(), None); - } - - #[test] - fn same_block_no_changes() { - sp_tracing::try_init_simple(); - - let root_parent = H256::random(); - let key = H256::random()[..].to_vec(); - let h1 = H256::random(); - let h2 = H256::random(); - - let shared = new_shared_cache::(256 * 1024, (0, 1)); - - let mut s = CachingState::new( - InMemoryBackend::::default(), - shared.clone(), - Some(root_parent), - ); - s.cache.sync_cache( - &[], - &[], - vec![(key.clone(), Some(vec![1]))], - vec![], - Some(h1), - Some(1), - true, - ); - assert_eq!(shared.write().lru_storage.get(&key).unwrap(), &Some(vec![1])); - - let mut s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h1)); - - // commit as non-best - s.cache.sync_cache( - &[], - &[], - vec![(key.clone(), Some(vec![2]))], - vec![], - Some(h2), - Some(2), - false, - ); - - assert_eq!(shared.write().lru_storage.get(&key).unwrap(), &Some(vec![1])); - - let mut s = - CachingState::new(InMemoryBackend::::default(), shared.clone(), Some(h1)); - - // commit again as best with no changes - s.cache.sync_cache(&[], &[], vec![], vec![], Some(h2), Some(2), true); - assert_eq!(s.storage(&key).unwrap(), None); - } -} - -#[cfg(test)] -mod qc { - use std::collections::{hash_map::Entry, HashMap}; - - use quickcheck::{quickcheck, Arbitrary, TestResult}; - - use super::*; - use sp_runtime::{ - testing::{Block as RawBlock, ExtrinsicWrapper, H256}, - traits::BlakeTwo256, - }; - use sp_state_machine::InMemoryBackend; - - type Block = RawBlock>; - - type KeySet = Vec<(Vec, Option>)>; - - type KeyMap = HashMap, Option>>; - - #[derive(Debug, Clone)] - struct Node { - hash: H256, - #[allow(unused)] - parent: H256, - state: KeyMap, - changes: KeySet, - } - - impl Node { - fn new_next(&self, hash: H256, changes: KeySet) -> Self { - let mut state = self.state.clone(); - - for (k, v) in self.state.iter() { - state.insert(k.clone(), v.clone()); - } - for (k, v) in changes.clone().into_iter() { - state.insert(k, v); - } - - Self { hash, parent: self.hash, changes, state } - } - - fn new(hash: H256, parent: H256, changes: KeySet) -> Self { - let mut state = KeyMap::new(); - - for (k, v) in changes.clone().into_iter() { - state.insert(k, v); - } - - Self { hash, parent, state, changes } - } - - fn purge(&mut self, other_changes: &KeySet) { - for (k, _) in other_changes.iter() { - self.state.remove(k); - } - } - } - - #[derive(Debug, Clone)] - enum Action { - Next { hash: H256, changes: KeySet }, - Fork { depth: usize, hash: H256, changes: KeySet }, - ReorgWithImport { depth: usize, hash: H256 }, - FinalizationReorg { fork_depth: usize, depth: usize }, - } - - impl Arbitrary for Action { - fn arbitrary(gen: &mut quickcheck::Gen) -> Self { - let path = u8::arbitrary(gen); - let buf = (0..32).map(|_| u8::arbitrary(gen)).collect::>(); - - match path { - 0..=175 => Action::Next { - hash: H256::from_slice(&buf[..]), - changes: { - let mut set = Vec::new(); - for _ in 0..::arbitrary(gen) / (64 * 256 * 256 * 256) { - set.push((vec![u8::arbitrary(gen)], Some(vec![u8::arbitrary(gen)]))); - } - set - }, - }, - 176..=220 => Action::Fork { - hash: H256::from_slice(&buf[..]), - depth: ((u8::arbitrary(gen)) / 32) as usize, - changes: { - let mut set = Vec::new(); - for _ in 0..::arbitrary(gen) / (64 * 256 * 256 * 256) { - set.push((vec![u8::arbitrary(gen)], Some(vec![u8::arbitrary(gen)]))); - } - set - }, - }, - 221..=240 => { - Action::ReorgWithImport { - hash: H256::from_slice(&buf[..]), - depth: ((u8::arbitrary(gen)) / 32) as usize, // 0-7 - } - }, - _ => { - Action::FinalizationReorg { - fork_depth: ((u8::arbitrary(gen)) / 32) as usize, // 0-7 - depth: ((u8::arbitrary(gen)) / 64) as usize, // 0-3 - } - }, - } - } - } - - struct Mutator { - shared: SharedCache, - canon: Vec, - forks: HashMap>, - } - - impl Mutator { - fn new_empty() -> Self { - let shared = new_shared_cache::(256 * 1024, (0, 1)); - - Self { shared, canon: vec![], forks: HashMap::new() } - } - - fn head_state(&self, hash: H256) -> CachingState, Block> { - CachingState::new( - InMemoryBackend::::default(), - self.shared.clone(), - Some(hash), - ) - } - - fn canon_head_state(&self) -> CachingState, Block> { - self.head_state(self.canon.last().expect("Expected to be one commit").hash) - } - - fn mutate_static( - &mut self, - action: Action, - ) -> CachingState, Block> { - self.mutate(action) - .expect("Expected to provide only valid actions to the mutate_static") - } - - fn canon_len(&self) -> usize { - return self.canon.len() - } - - fn head_storage_ref(&self) -> &KeyMap { - &self.canon.last().expect("Expected to be one commit").state - } - - fn key_permutations() -> Vec> { - (0u8..255).map(|x| vec![x]).collect() - } - - fn mutate( - &mut self, - action: Action, - ) -> Result, Block>, ()> { - let state = match action { - Action::Fork { depth, hash, changes } => { - let pos = self.canon.len() as isize - depth as isize; - if pos < 0 || self.canon.len() == 0 || pos >= (self.canon.len() - 1) as isize - // no fork on top also, thus len-1 - { - return Err(()) - } - - let pos = pos as usize; - - let fork_at = self.canon[pos].hash; - - let (total_h, parent) = match self.forks.entry(fork_at) { - Entry::Occupied(occupied) => { - let chain = occupied.into_mut(); - let parent = - chain.last().expect("No empty forks are ever created").clone(); - let mut node = parent.new_next(hash, changes.clone()); - - for earlier in chain.iter() { - node.purge(&earlier.changes.clone()); - } - - chain.push(node); - - (pos + chain.len(), parent.hash) - }, - Entry::Vacant(vacant) => { - let canon_parent = &self.canon[pos]; - vacant.insert(vec![canon_parent.new_next(hash, changes.clone())]); - - (pos + 1, fork_at) - }, - }; - - let mut state = CachingState::new( - InMemoryBackend::::default(), - self.shared.clone(), - Some(parent), - ); - - state.cache.sync_cache( - &[], - &[], - changes, - vec![], - Some(hash), - Some(total_h as u64), - false, - ); - - state - }, - Action::Next { hash, changes } => { - let (next, parent_hash) = match self.canon.last() { - None => { - let parent_hash = H256::from(&[0u8; 32]); - (Node::new(hash, parent_hash, changes.clone()), parent_hash) - }, - Some(parent) => (parent.new_next(hash, changes.clone()), parent.hash), - }; - - // delete cache entries for earlier - for node in self.canon.iter_mut() { - node.purge(&next.changes); - if let Some(fork) = self.forks.get_mut(&node.hash) { - for node in fork.iter_mut() { - node.purge(&next.changes); - } - } - } - - let mut state = CachingState::new( - InMemoryBackend::::default(), - self.shared.clone(), - Some(parent_hash), - ); - - state.cache.sync_cache( - &[], - &[], - next.changes.clone(), - vec![], - Some(hash), - Some(self.canon.len() as u64 + 1), - true, - ); - - self.canon.push(next); - - state - }, - Action::ReorgWithImport { depth, hash } => { - let pos = self.canon.len() as isize - depth as isize; - if pos < 0 || pos + 1 >= self.canon.len() as isize { - return Err(()) - } - let fork_at = self.canon[pos as usize].hash; - let pos = pos as usize; - - match self.forks.get_mut(&fork_at) { - Some(chain) => { - let mut new_fork = self.canon.drain(pos + 1..).collect::>(); - - let retracted: Vec = - new_fork.iter().map(|node| node.hash).collect(); - let enacted: Vec = chain.iter().map(|node| node.hash).collect(); - - std::mem::swap(chain, &mut new_fork); - - let mut node = new_fork - .last() - .map(|node| node.new_next(hash, vec![])) - .expect("No empty fork ever created!"); - - for invalidators in chain.iter().chain(new_fork.iter()) { - node.purge(&invalidators.changes); - } - - self.canon.extend(new_fork.into_iter()); - - self.canon.push(node); - - let mut state = CachingState::new( - InMemoryBackend::::default(), - self.shared.clone(), - Some(fork_at), - ); - - let height = pos as u64 + enacted.len() as u64 + 2; - state.cache.sync_cache( - &enacted[..], - &retracted[..], - vec![], - vec![], - Some(hash), - Some(height), - true, - ); - - state - }, - None => { - return Err(()) // no reorg without a fork atm! - }, - } - }, - Action::FinalizationReorg { fork_depth, depth } => { - let pos = self.canon.len() as isize - fork_depth as isize; - if pos < 0 || pos + 1 >= self.canon.len() as isize { - return Err(()) - } - let fork_at = self.canon[pos as usize].hash; - let pos = pos as usize; - - match self.forks.get_mut(&fork_at) { - Some(fork_chain) => { - let sync_pos = fork_chain.len() as isize - - fork_chain.len() as isize - depth as isize; - if sync_pos < 0 || sync_pos >= fork_chain.len() as isize { - return Err(()) - } - let sync_pos = sync_pos as usize; - - let mut new_fork = self.canon.drain(pos + 1..).collect::>(); - - let retracted: Vec = - new_fork.iter().map(|node| node.hash).collect(); - let enacted: Vec = fork_chain - .iter() - .take(sync_pos + 1) - .map(|node| node.hash) - .collect(); - - std::mem::swap(fork_chain, &mut new_fork); - - self.shared.write().sync(&retracted, &enacted); - - self.head_state( - self.canon - .last() - .expect("wasn't forking to emptiness so there should be one!") - .hash, - ) - }, - None => { - return Err(()) // no reorg to nothing pls! - }, - } - }, - }; - - Ok(state) - } - } - - #[test] - fn smoke() { - let key = H256::random()[..].to_vec(); - let h0 = H256::random(); - let h1a = H256::random(); - let h1b = H256::random(); - let h2a = H256::random(); - let h2b = H256::random(); - let h3a = H256::random(); - let h3b = H256::random(); - - let mut mutator = Mutator::new_empty(); - mutator - .mutate_static(Action::Next { hash: h0, changes: vec![(key.clone(), Some(vec![2]))] }); - mutator.mutate_static(Action::Next { hash: h1a, changes: vec![] }); - mutator.mutate_static(Action::Fork { - depth: 2, - hash: h1b, - changes: vec![(key.clone(), Some(vec![3]))], - }); - mutator.mutate_static(Action::Fork { - depth: 2, - hash: h2b, - changes: vec![(key.clone(), Some(vec![4]))], - }); - mutator - .mutate_static(Action::Next { hash: h2a, changes: vec![(key.clone(), Some(vec![5]))] }); - mutator.mutate_static(Action::Next { hash: h3a, changes: vec![] }); - - assert_eq!( - mutator.head_state(h3a).storage(&key).unwrap().expect("there should be a value"), - vec![5] - ); - assert!(mutator.head_state(h1a).storage(&key).unwrap().is_none()); - assert!(mutator.head_state(h2b).storage(&key).unwrap().is_none()); - assert!(mutator.head_state(h1b).storage(&key).unwrap().is_none()); - - mutator.mutate_static(Action::ReorgWithImport { depth: 4, hash: h3b }); - assert!(mutator.head_state(h3a).storage(&key).unwrap().is_none()); - } - - fn is_head_match(mutator: &Mutator) -> bool { - let head_state = mutator.canon_head_state(); - - for key in Mutator::key_permutations() { - match (head_state.storage(&key).unwrap(), mutator.head_storage_ref().get(&key)) { - (Some(x), Some(y)) => - if Some(&x) != y.as_ref() { - eprintln!("{:?} != {:?}", x, y); - return false - }, - (None, Some(_y)) => { - // TODO: cache miss is not tracked atm - }, - (Some(x), None) => { - eprintln!("{:?} != ", x); - return false - }, - _ => continue, - } - } - true - } - - fn is_canon_match(mutator: &Mutator) -> bool { - for node in mutator.canon.iter() { - let head_state = mutator.head_state(node.hash); - for key in Mutator::key_permutations() { - match (head_state.storage(&key).unwrap(), node.state.get(&key)) { - (Some(x), Some(y)) => - if Some(&x) != y.as_ref() { - eprintln!("at [{}]: {:?} != {:?}", node.hash, x, y); - return false - }, - (None, Some(_y)) => { - // cache miss is not tracked atm - }, - (Some(x), None) => { - eprintln!("at [{}]: {:?} != ", node.hash, x); - return false - }, - _ => continue, - } - } - } - true - } - - #[test] - fn reorg() { - let key = H256::random()[..].to_vec(); - let h0 = H256::random(); - let h1 = H256::random(); - let h2 = H256::random(); - let h1b = H256::random(); - let h2b = H256::random(); - - let mut mutator = Mutator::new_empty(); - mutator.mutate_static(Action::Next { hash: h0, changes: vec![] }); - mutator.mutate_static(Action::Next { hash: h1, changes: vec![] }); - mutator - .mutate_static(Action::Next { hash: h2, changes: vec![(key.clone(), Some(vec![2]))] }); - mutator.mutate_static(Action::Fork { - depth: 2, - hash: h1b, - changes: vec![(key.clone(), Some(vec![3]))], - }); - mutator.mutate_static(Action::ReorgWithImport { depth: 2, hash: h2b }); - - assert!(is_head_match(&mutator)) - } - - fn key(k: u8) -> Vec { - vec![k] - } - fn val(v: u8) -> Option> { - Some(vec![v]) - } - fn keyval(k: u8, v: u8) -> KeySet { - vec![(key(k), val(v))] - } - - #[test] - fn reorg2() { - let h0 = H256::random(); - let h1a = H256::random(); - let h1b = H256::random(); - let h2b = H256::random(); - let h2a = H256::random(); - let h3a = H256::random(); - - let mut mutator = Mutator::new_empty(); - mutator.mutate_static(Action::Next { hash: h0, changes: keyval(1, 1) }); - mutator.mutate_static(Action::Next { hash: h1a, changes: keyval(1, 1) }); - mutator.mutate_static(Action::Fork { depth: 2, hash: h1b, changes: keyval(2, 2) }); - - mutator.mutate_static(Action::Next { hash: h2a, changes: keyval(3, 3) }); - mutator.mutate_static(Action::Next { hash: h3a, changes: keyval(4, 4) }); - mutator.mutate_static(Action::ReorgWithImport { depth: 4, hash: h2b }); - - assert!(is_head_match(&mutator)) - } - - #[test] - fn fork2() { - let h1 = H256::random(); - let h2a = H256::random(); - let h2b = H256::random(); - let h3a = H256::random(); - let h3b = H256::random(); - - let mut mutator = Mutator::new_empty(); - mutator.mutate_static(Action::Next { hash: h1, changes: vec![] }); - mutator.mutate_static(Action::Next { hash: h2a, changes: vec![] }); - mutator.mutate_static(Action::Next { hash: h3a, changes: keyval(1, 1) }); - - mutator.mutate_static(Action::Fork { depth: 2, hash: h2b, changes: vec![] }); - mutator.mutate_static(Action::Fork { depth: 2, hash: h3b, changes: keyval(1, 2) }); - - assert!(is_head_match(&mutator)) - } - - #[test] - fn fork3() { - let h1 = H256::random(); - let h2a = H256::random(); - let h2b = H256::random(); - let h3a = H256::random(); - - let mut mutator = Mutator::new_empty(); - mutator.mutate_static(Action::Next { hash: h1, changes: keyval(1, 1) }); - mutator.mutate_static(Action::Next { hash: h2a, changes: keyval(2, 2) }); - mutator.mutate_static(Action::Next { hash: h3a, changes: keyval(3, 3) }); - - mutator.mutate_static(Action::Fork { depth: 2, hash: h2b, changes: keyval(1, 3) }); - - assert!(is_canon_match(&mutator)) - } - - quickcheck! { - fn head_complete(actions: Vec) -> TestResult { - let mut mutator = Mutator::new_empty(); - - for action in actions.into_iter() { - if let Err(_) = mutator.mutate(action) { - return TestResult::discard(); - } - } - - if mutator.canon_len() == 0 { - return TestResult::discard(); - } - - TestResult::from_bool(is_head_match(&mutator)) - } - - fn canon_complete(actions: Vec) -> TestResult { - let mut mutator = Mutator::new_empty(); - - for action in actions.into_iter() { - if let Err(_) = mutator.mutate(action) { - return TestResult::discard(); - } - } - - if mutator.canon_len() == 0 { - return TestResult::discard(); - } - - TestResult::from_bool(is_canon_match(&mutator)) - } - } -}