diff --git a/substrate/client/db/src/storage_cache.rs b/substrate/client/db/src/storage_cache.rs index bbbc8413be..317c637333 100644 --- a/substrate/client/db/src/storage_cache.rs +++ b/substrate/client/db/src/storage_cache.rs @@ -16,7 +16,9 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Global cache state. +//! 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 std::collections::{VecDeque, HashSet, HashMap}; use std::sync::Arc; @@ -343,12 +345,27 @@ impl CacheChanges { ); let cache = &mut *cache; // Filter out committing block if any. - let enacted: Vec<_> = enacted + let mut enacted: Vec<_> = enacted .iter() .filter(|h| commit_hash.as_ref().map_or(true, |p| *h != p)) .cloned() .collect(); - cache.sync(&enacted, retracted); + + 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.clone()); + } else { + retracted.to_mut().push(commit_hash.clone()); + } + } + } + } + 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) @@ -1316,6 +1333,71 @@ mod tests { ); 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.lock().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.lock().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)]