// This file is part of Substrate. // Copyright (C) 2017-2020 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! The overlayed changes to state. mod changeset; use crate::{ backend::Backend, ChangesTrieTransaction, changes_trie::{ NO_EXTRINSIC_INDEX, BlockNumber, build_changes_trie, State as ChangesTrieState, }, stats::StateMachineStats, }; use self::changeset::OverlayedChangeSet; use std::collections::HashMap; use codec::{Decode, Encode}; use sp_core::storage::{well_known_keys::EXTRINSIC_INDEX, ChildInfo}; use sp_core::offchain::storage::OffchainOverlayedChanges; use hash_db::Hasher; pub use self::changeset::{OverlayedValue, NoOpenTransaction, AlreadyInRuntime, NotInRuntime}; /// Storage key. pub type StorageKey = Vec; /// Storage value. pub type StorageValue = Vec; /// In memory array of storage values. pub type StorageCollection = Vec<(StorageKey, Option)>; /// In memory arrays of storage values for multiple child tries. pub type ChildStorageCollection = Vec<(StorageKey, StorageCollection)>; /// The set of changes that are overlaid onto the backend. /// /// It allows changes to be modified using nestable transactions. #[derive(Debug, Default, Clone)] pub struct OverlayedChanges { /// Top level storage changes. top: OverlayedChangeSet, /// Child storage changes. The map key is the child storage key without the common prefix. children: HashMap, /// True if extrinsics stats must be collected. collect_extrinsics: bool, /// Collect statistic on this execution. stats: StateMachineStats, } /// A storage changes structure that can be generated by the data collected in [`OverlayedChanges`]. /// /// This contains all the changes to the storage and transactions to apply theses changes to the /// backend. pub struct StorageChanges { /// All changes to the main storage. /// /// A value of `None` means that it was deleted. pub main_storage_changes: StorageCollection, /// All changes to the child storages. pub child_storage_changes: ChildStorageCollection, /// Offchain state changes to write to the offchain database. pub offchain_storage_changes: OffchainOverlayedChanges, /// A transaction for the backend that contains all changes from /// [`main_storage_changes`](StorageChanges::main_storage_changes) and from /// [`child_storage_changes`](StorageChanges::child_storage_changes). /// [`offchain_storage_changes`](StorageChanges::offchain_storage_changes). pub transaction: Transaction, /// The storage root after applying the transaction. pub transaction_storage_root: H::Out, /// Contains the transaction for the backend for the changes trie. /// /// If changes trie is disabled the value is set to `None`. pub changes_trie_transaction: Option>, } impl StorageChanges { /// Deconstruct into the inner values pub fn into_inner(self) -> ( StorageCollection, ChildStorageCollection, OffchainOverlayedChanges, Transaction, H::Out, Option>, ) { ( self.main_storage_changes, self.child_storage_changes, self.offchain_storage_changes, self.transaction, self.transaction_storage_root, self.changes_trie_transaction, ) } } /// The storage transaction are calculated as part of the `storage_root` and /// `changes_trie_storage_root`. These transactions can be reused for importing the block into the /// storage. So, we cache them to not require a recomputation of those transactions. pub struct StorageTransactionCache { /// Contains the changes for the main and the child storages as one transaction. pub(crate) transaction: Option, /// The storage root after applying the transaction. pub(crate) transaction_storage_root: Option, /// Contains the changes trie transaction. pub(crate) changes_trie_transaction: Option>>, /// The storage root after applying the changes trie transaction. pub(crate) changes_trie_transaction_storage_root: Option>, } impl StorageTransactionCache { /// Reset the cached transactions. pub fn reset(&mut self) { *self = Self::default(); } } impl Default for StorageTransactionCache { fn default() -> Self { Self { transaction: None, transaction_storage_root: None, changes_trie_transaction: None, changes_trie_transaction_storage_root: None, } } } impl Default for StorageChanges { fn default() -> Self { Self { main_storage_changes: Default::default(), child_storage_changes: Default::default(), offchain_storage_changes: Default::default(), transaction: Default::default(), transaction_storage_root: Default::default(), changes_trie_transaction: None, } } } impl OverlayedChanges { /// Whether no changes are contained in the top nor in any of the child changes. pub fn is_empty(&self) -> bool { self.top.is_empty() && self.children.is_empty() } /// Ask to collect/not to collect extrinsics indices where key(s) has been changed. pub fn set_collect_extrinsics(&mut self, collect_extrinsics: bool) { self.collect_extrinsics = collect_extrinsics; } /// Returns a double-Option: None if the key is unknown (i.e. and the query should be referred /// to the backend); Some(None) if the key has been deleted. Some(Some(...)) for a key whose /// value has been set. pub fn storage(&self, key: &[u8]) -> Option> { self.top.get(key).map(|x| { let value = x.value(); let size_read = value.map(|x| x.len() as u64).unwrap_or(0); self.stats.tally_read_modified(size_read); value.map(AsRef::as_ref) }) } /// Returns mutable reference to current value. /// If there is no value in the overlay, the given callback is used to initiate the value. /// Warning this function registers a change, so the mutable reference MUST be modified. /// /// Can be rolled back or committed when called inside a transaction. #[must_use = "A change was registered, so this value MUST be modified."] pub fn value_mut_or_insert_with( &mut self, key: &[u8], init: impl Fn() -> StorageValue, ) -> &mut StorageValue { let value = self.top.modify(key.to_owned(), init, self.extrinsic_index()); // if the value was deleted initialise it back with an empty vec value.get_or_insert_with(StorageValue::default) } /// Returns a double-Option: None if the key is unknown (i.e. and the query should be referred /// to the backend); Some(None) if the key has been deleted. Some(Some(...)) for a key whose /// value has been set. pub fn child_storage(&self, child_info: &ChildInfo, key: &[u8]) -> Option> { let map = self.children.get(child_info.storage_key())?; let value = map.0.get(key)?.value(); let size_read = value.map(|x| x.len() as u64).unwrap_or(0); self.stats.tally_read_modified(size_read); Some(value.map(AsRef::as_ref)) } /// Set a new value for the specified key. /// /// Can be rolled back or committed when called inside a transaction. pub(crate) fn set_storage(&mut self, key: StorageKey, val: Option) { let size_write = val.as_ref().map(|x| x.len() as u64).unwrap_or(0); self.stats.tally_write_overlay(size_write); self.top.set(key, val, self.extrinsic_index()); } /// Set a new value for the specified key and child. /// /// `None` can be used to delete a value specified by the given key. /// /// Can be rolled back or committed when called inside a transaction. pub(crate) fn set_child_storage( &mut self, child_info: &ChildInfo, key: StorageKey, val: Option, ) { let extrinsic_index = self.extrinsic_index(); let size_write = val.as_ref().map(|x| x.len() as u64).unwrap_or(0); self.stats.tally_write_overlay(size_write); let storage_key = child_info.storage_key().to_vec(); let top = &self.top; let (changeset, info) = self.children.entry(storage_key).or_insert_with(|| ( top.spawn_child(), child_info.to_owned() ) ); let updatable = info.try_update(child_info); debug_assert!(updatable); changeset.set(key, val, extrinsic_index); } /// Clear child storage of given storage key. /// /// Can be rolled back or committed when called inside a transaction. pub(crate) fn clear_child_storage( &mut self, child_info: &ChildInfo, ) { let extrinsic_index = self.extrinsic_index(); let storage_key = child_info.storage_key().to_vec(); let top = &self.top; let (changeset, info) = self.children.entry(storage_key).or_insert_with(|| ( top.spawn_child(), child_info.to_owned() ) ); let updatable = info.try_update(child_info); debug_assert!(updatable); changeset.clear_where(|_, _| true, extrinsic_index); } /// Removes all key-value pairs which keys share the given prefix. /// /// Can be rolled back or committed when called inside a transaction. pub(crate) fn clear_prefix(&mut self, prefix: &[u8]) { self.top.clear_where(|key, _| key.starts_with(prefix), self.extrinsic_index()); } /// Removes all key-value pairs which keys share the given prefix. /// /// Can be rolled back or committed when called inside a transaction pub(crate) fn clear_child_prefix( &mut self, child_info: &ChildInfo, prefix: &[u8], ) { let extrinsic_index = self.extrinsic_index(); let storage_key = child_info.storage_key().to_vec(); let top = &self.top; let (changeset, info) = self.children.entry(storage_key).or_insert_with(|| ( top.spawn_child(), child_info.to_owned() ) ); let updatable = info.try_update(child_info); debug_assert!(updatable); changeset.clear_where(|key, _| key.starts_with(prefix), extrinsic_index); } /// Returns the current nesting depth of the transaction stack. /// /// A value of zero means that no transaction is open and changes are committed on write. pub fn transaction_depth(&self) -> usize { // The top changeset and all child changesets transact in lockstep and are // therefore always at the same transaction depth. self.top.transaction_depth() } /// Start a new nested transaction. /// /// This allows to either commit or roll back all changes that where made while this /// transaction was open. Any transaction must be closed by either `rollback_transaction` or /// `commit_transaction` before this overlay can be converted into storage changes. /// /// Changes made without any open transaction are committed immediatly. pub fn start_transaction(&mut self) { self.top.start_transaction(); for (_, (changeset, _)) in self.children.iter_mut() { changeset.start_transaction(); } } /// Rollback the last transaction started by `start_transaction`. /// /// Any changes made during that transaction are discarded. Returns an error if /// there is no open transaction that can be rolled back. pub fn rollback_transaction(&mut self) -> Result<(), NoOpenTransaction> { self.top.rollback_transaction()?; self.children.retain(|_, (changeset, _)| { changeset.rollback_transaction() .expect("Top and children changesets are started in lockstep; qed"); !changeset.is_empty() }); Ok(()) } /// Commit the last transaction started by `start_transaction`. /// /// Any changes made during that transaction are committed. Returns an error if there /// is no open transaction that can be committed. pub fn commit_transaction(&mut self) -> Result<(), NoOpenTransaction> { self.top.commit_transaction()?; for (_, (changeset, _)) in self.children.iter_mut() { changeset.commit_transaction() .expect("Top and children changesets are started in lockstep; qed"); } Ok(()) } /// Call this before transfering control to the runtime. /// /// This protects all existing transactions from being removed by the runtime. /// Calling this while already inside the runtime will return an error. pub fn enter_runtime(&mut self) -> Result<(), AlreadyInRuntime> { self.top.enter_runtime()?; for (_, (changeset, _)) in self.children.iter_mut() { changeset.enter_runtime() .expect("Top and children changesets are entering runtime in lockstep; qed") } Ok(()) } /// Call this when control returns from the runtime. /// /// This commits all dangling transaction left open by the runtime. /// Calling this while outside the runtime will return an error. pub fn exit_runtime(&mut self) -> Result<(), NotInRuntime> { self.top.exit_runtime()?; for (_, (changeset, _)) in self.children.iter_mut() { changeset.exit_runtime() .expect("Top and children changesets are entering runtime in lockstep; qed"); } Ok(()) } /// Consume all changes (top + children) and return them. /// /// After calling this function no more changes are contained in this changeset. /// /// Panics: /// Panics if `transaction_depth() > 0` fn drain_committed(&mut self) -> ( impl Iterator)>, impl Iterator)>, ChildInfo))>, ) { use std::mem::take; ( take(&mut self.top).drain_commited(), take(&mut self.children).into_iter() .map(|(key, (val, info))| ( key, (val.drain_commited(), info) ) ), ) } /// Get an iterator over all child changes as seen by the current transaction. pub fn children(&self) -> impl Iterator, &ChildInfo)> { self.children.iter().map(|(_, v)| (v.0.changes(), &v.1)) } /// Get an iterator over all top changes as been by the current transaction. pub fn changes(&self) -> impl Iterator { self.top.changes() } /// Get an optional iterator over all child changes stored under the supplied key. pub fn child_changes(&self, key: &[u8]) -> Option<(impl Iterator, &ChildInfo)> { self.children.get(key).map(|(overlay, info)| (overlay.changes(), info)) } /// Convert this instance with all changes into a [`StorageChanges`] instance. pub fn into_storage_changes< B: Backend, H: Hasher, N: BlockNumber >( mut self, backend: &B, changes_trie_state: Option<&ChangesTrieState>, parent_hash: H::Out, mut cache: StorageTransactionCache, ) -> Result, String> where H::Out: Ord + Encode + 'static { self.drain_storage_changes(backend, changes_trie_state, parent_hash, &mut cache) } /// Drain all changes into a [`StorageChanges`] instance. Leave empty overlay in place. pub fn drain_storage_changes, H: Hasher, N: BlockNumber>( &mut self, backend: &B, changes_trie_state: Option<&ChangesTrieState>, parent_hash: H::Out, mut cache: &mut StorageTransactionCache, ) -> Result, String> where H::Out: Ord + Encode + 'static { // If the transaction does not exist, we generate it. if cache.transaction.is_none() { self.storage_root(backend, &mut cache); } let (transaction, transaction_storage_root) = cache.transaction.take() .and_then(|t| cache.transaction_storage_root.take().map(|tr| (t, tr))) .expect("Transaction was be generated as part of `storage_root`; qed"); // If the transaction does not exist, we generate it. if cache.changes_trie_transaction.is_none() { self.changes_trie_root( backend, changes_trie_state, parent_hash, false, &mut cache, ).map_err(|_| "Failed to generate changes trie transaction")?; } let changes_trie_transaction = cache.changes_trie_transaction .take() .expect("Changes trie transaction was generated by `changes_trie_root`; qed"); let offchain_storage_changes = Default::default(); let (main_storage_changes, child_storage_changes) = self.drain_committed(); Ok(StorageChanges { main_storage_changes: main_storage_changes.collect(), child_storage_changes: child_storage_changes.map(|(sk, it)| (sk, it.0.collect())).collect(), offchain_storage_changes, transaction, transaction_storage_root, changes_trie_transaction, }) } /// Inserts storage entry responsible for current extrinsic index. #[cfg(test)] pub(crate) fn set_extrinsic_index(&mut self, extrinsic_index: u32) { self.top.set(EXTRINSIC_INDEX.to_vec(), Some(extrinsic_index.encode()), None); } /// Returns current extrinsic index to use in changes trie construction. /// None is returned if it is not set or changes trie config is not set. /// Persistent value (from the backend) can be ignored because runtime must /// set this index before first and unset after last extrinsic is executed. /// Changes that are made outside of extrinsics, are marked with /// `NO_EXTRINSIC_INDEX` index. fn extrinsic_index(&self) -> Option { match self.collect_extrinsics { true => Some( self.storage(EXTRINSIC_INDEX) .and_then(|idx| idx.and_then(|idx| Decode::decode(&mut &*idx).ok())) .unwrap_or(NO_EXTRINSIC_INDEX)), false => None, } } /// Generate the storage root using `backend` and all changes /// as seen by the current transaction. /// /// Returns the storage root and caches storage transaction in the given `cache`. pub fn storage_root>( &self, backend: &B, cache: &mut StorageTransactionCache, ) -> H::Out where H::Out: Ord + Encode, { let delta = self.changes().map(|(k, v)| (&k[..], v.value().map(|v| &v[..]))); let child_delta = self.children() .map(|(changes, info)| (info, changes.map( |(k, v)| (&k[..], v.value().map(|v| &v[..])) ))); let (root, transaction) = backend.full_storage_root(delta, child_delta); cache.transaction = Some(transaction); cache.transaction_storage_root = Some(root); root } /// Generate the changes trie root. /// /// Returns the changes trie root and caches the storage transaction into the given `cache`. /// /// # Panics /// /// Panics on storage error, when `panic_on_storage_error` is set. pub fn changes_trie_root<'a, H: Hasher, N: BlockNumber, B: Backend>( &self, backend: &B, changes_trie_state: Option<&'a ChangesTrieState<'a, H, N>>, parent_hash: H::Out, panic_on_storage_error: bool, cache: &mut StorageTransactionCache, ) -> Result, ()> where H::Out: Ord + Encode + 'static { build_changes_trie::<_, H, N>( backend, changes_trie_state, self, parent_hash, panic_on_storage_error, ).map(|r| { let root = r.as_ref().map(|r| r.1).clone(); cache.changes_trie_transaction = Some(r.map(|(db, _, cache)| (db, cache))); cache.changes_trie_transaction_storage_root = Some(root); root }) } /// Returns the next (in lexicographic order) storage key in the overlayed alongside its value. /// If no value is next then `None` is returned. pub fn next_storage_key_change(&self, key: &[u8]) -> Option<(&[u8], &OverlayedValue)> { self.top.next_change(key) } /// Returns the next (in lexicographic order) child storage key in the overlayed alongside its /// value. If no value is next then `None` is returned. pub fn next_child_storage_key_change( &self, storage_key: &[u8], key: &[u8] ) -> Option<(&[u8], &OverlayedValue)> { self.children .get(storage_key) .and_then(|(overlay, _)| overlay.next_change(key) ) } } #[cfg(test)] mod tests { use hex_literal::hex; use sp_core::{Blake2Hasher, traits::Externalities}; use crate::InMemoryBackend; use crate::ext::Ext; use super::*; use std::collections::BTreeMap; fn assert_extrinsics( overlay: &OverlayedChangeSet, key: impl AsRef<[u8]>, expected: Vec, ) { assert_eq!( overlay.get(key.as_ref()).unwrap().extrinsics().cloned().collect::>(), expected ) } #[test] fn overlayed_storage_works() { let mut overlayed = OverlayedChanges::default(); let key = vec![42, 69, 169, 142]; assert!(overlayed.storage(&key).is_none()); overlayed.start_transaction(); overlayed.set_storage(key.clone(), Some(vec![1, 2, 3])); assert_eq!(overlayed.storage(&key).unwrap(), Some(&[1, 2, 3][..])); overlayed.commit_transaction().unwrap(); assert_eq!(overlayed.storage(&key).unwrap(), Some(&[1, 2, 3][..])); overlayed.start_transaction(); overlayed.set_storage(key.clone(), Some(vec![])); assert_eq!(overlayed.storage(&key).unwrap(), Some(&[][..])); overlayed.set_storage(key.clone(), None); assert!(overlayed.storage(&key).unwrap().is_none()); overlayed.rollback_transaction().unwrap(); assert_eq!(overlayed.storage(&key).unwrap(), Some(&[1, 2, 3][..])); overlayed.set_storage(key.clone(), None); assert!(overlayed.storage(&key).unwrap().is_none()); } #[test] fn overlayed_storage_root_works() { let initial: BTreeMap<_, _> = vec![ (b"doe".to_vec(), b"reindeer".to_vec()), (b"dog".to_vec(), b"puppyXXX".to_vec()), (b"dogglesworth".to_vec(), b"catXXX".to_vec()), (b"doug".to_vec(), b"notadog".to_vec()), ].into_iter().collect(); let backend = InMemoryBackend::::from(initial); let mut overlay = OverlayedChanges::default(); overlay.set_collect_extrinsics(false); overlay.start_transaction(); overlay.set_storage(b"dog".to_vec(), Some(b"puppy".to_vec())); overlay.set_storage(b"dogglesworth".to_vec(), Some(b"catYYY".to_vec())); overlay.set_storage(b"doug".to_vec(), Some(vec![])); overlay.commit_transaction().unwrap(); overlay.start_transaction(); overlay.set_storage(b"dogglesworth".to_vec(), Some(b"cat".to_vec())); overlay.set_storage(b"doug".to_vec(), None); let mut offchain_overlay = Default::default(); let mut cache = StorageTransactionCache::default(); let mut ext = Ext::new( &mut overlay, &mut offchain_overlay, &mut cache, &backend, crate::changes_trie::disabled_state::<_, u64>(), None, ); const ROOT: [u8; 32] = hex!("39245109cef3758c2eed2ccba8d9b370a917850af3824bc8348d505df2c298fa"); assert_eq!(&ext.storage_root()[..], &ROOT); } #[test] fn extrinsic_changes_are_collected() { let mut overlay = OverlayedChanges::default(); overlay.set_collect_extrinsics(true); overlay.start_transaction(); overlay.set_storage(vec![100], Some(vec![101])); overlay.set_extrinsic_index(0); overlay.set_storage(vec![1], Some(vec![2])); overlay.set_extrinsic_index(1); overlay.set_storage(vec![3], Some(vec![4])); overlay.set_extrinsic_index(2); overlay.set_storage(vec![1], Some(vec![6])); assert_extrinsics(&overlay.top, vec![1], vec![0, 2]); assert_extrinsics(&overlay.top, vec![3], vec![1]); assert_extrinsics(&overlay.top, vec![100], vec![NO_EXTRINSIC_INDEX]); overlay.start_transaction(); overlay.set_extrinsic_index(3); overlay.set_storage(vec![3], Some(vec![7])); overlay.set_extrinsic_index(4); overlay.set_storage(vec![1], Some(vec![8])); assert_extrinsics(&overlay.top, vec![1], vec![0, 2, 4]); assert_extrinsics(&overlay.top, vec![3], vec![1, 3]); assert_extrinsics(&overlay.top, vec![100], vec![NO_EXTRINSIC_INDEX]); overlay.rollback_transaction().unwrap(); assert_extrinsics(&overlay.top, vec![1], vec![0, 2]); assert_extrinsics(&overlay.top, vec![3], vec![1]); assert_extrinsics(&overlay.top, vec![100], vec![NO_EXTRINSIC_INDEX]); } #[test] fn next_storage_key_change_works() { let mut overlay = OverlayedChanges::default(); overlay.start_transaction(); overlay.set_storage(vec![20], Some(vec![20])); overlay.set_storage(vec![30], Some(vec![30])); overlay.set_storage(vec![40], Some(vec![40])); overlay.commit_transaction().unwrap(); overlay.set_storage(vec![10], Some(vec![10])); overlay.set_storage(vec![30], None); // next_prospective < next_committed let next_to_5 = overlay.next_storage_key_change(&[5]).unwrap(); assert_eq!(next_to_5.0.to_vec(), vec![10]); assert_eq!(next_to_5.1.value(), Some(&vec![10])); // next_committed < next_prospective let next_to_10 = overlay.next_storage_key_change(&[10]).unwrap(); assert_eq!(next_to_10.0.to_vec(), vec![20]); assert_eq!(next_to_10.1.value(), Some(&vec![20])); // next_committed == next_prospective let next_to_20 = overlay.next_storage_key_change(&[20]).unwrap(); assert_eq!(next_to_20.0.to_vec(), vec![30]); assert_eq!(next_to_20.1.value(), None); // next_committed, no next_prospective let next_to_30 = overlay.next_storage_key_change(&[30]).unwrap(); assert_eq!(next_to_30.0.to_vec(), vec![40]); assert_eq!(next_to_30.1.value(), Some(&vec![40])); overlay.set_storage(vec![50], Some(vec![50])); // next_prospective, no next_committed let next_to_40 = overlay.next_storage_key_change(&[40]).unwrap(); assert_eq!(next_to_40.0.to_vec(), vec![50]); assert_eq!(next_to_40.1.value(), Some(&vec![50])); } #[test] fn next_child_storage_key_change_works() { let child_info = ChildInfo::new_default(b"Child1"); let child_info = &child_info; let child = child_info.storage_key(); let mut overlay = OverlayedChanges::default(); overlay.start_transaction(); overlay.set_child_storage(child_info, vec![20], Some(vec![20])); overlay.set_child_storage(child_info, vec![30], Some(vec![30])); overlay.set_child_storage(child_info, vec![40], Some(vec![40])); overlay.commit_transaction().unwrap(); overlay.set_child_storage(child_info, vec![10], Some(vec![10])); overlay.set_child_storage(child_info, vec![30], None); // next_prospective < next_committed let next_to_5 = overlay.next_child_storage_key_change(child, &[5]).unwrap(); assert_eq!(next_to_5.0.to_vec(), vec![10]); assert_eq!(next_to_5.1.value(), Some(&vec![10])); // next_committed < next_prospective let next_to_10 = overlay.next_child_storage_key_change(child, &[10]).unwrap(); assert_eq!(next_to_10.0.to_vec(), vec![20]); assert_eq!(next_to_10.1.value(), Some(&vec![20])); // next_committed == next_prospective let next_to_20 = overlay.next_child_storage_key_change(child, &[20]).unwrap(); assert_eq!(next_to_20.0.to_vec(), vec![30]); assert_eq!(next_to_20.1.value(), None); // next_committed, no next_prospective let next_to_30 = overlay.next_child_storage_key_change(child, &[30]).unwrap(); assert_eq!(next_to_30.0.to_vec(), vec![40]); assert_eq!(next_to_30.1.value(), Some(&vec![40])); overlay.set_child_storage(child_info, vec![50], Some(vec![50])); // next_prospective, no next_committed let next_to_40 = overlay.next_child_storage_key_change(child, &[40]).unwrap(); assert_eq!(next_to_40.0.to_vec(), vec![50]); assert_eq!(next_to_40.1.value(), Some(&vec![50])); } }