diff --git a/core/client/db/src/lib.rs b/core/client/db/src/lib.rs index 05a1dfc97e865..f5ece21fbb710 100644 --- a/core/client/db/src/lib.rs +++ b/core/client/db/src/lib.rs @@ -36,7 +36,7 @@ mod utils; use std::sync::Arc; use std::path::PathBuf; use std::io; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use client::backend::NewBlockState; use client::blockchain::HeaderBackend; @@ -58,7 +58,7 @@ use sr_primitives::traits::{ }; use state_machine::backend::Backend as StateBackend; use executor::RuntimeInfo; -use state_machine::{CodeExecutor, DBValue}; +use state_machine::{CodeExecutor, DBValue, ChangesTrieTransaction, ChangesTrieCacheAction, ChangesTrieBuildCache}; use crate::utils::{Meta, db_err, meta_keys, read_db, block_id_to_lookup_key, read_meta}; use client::leaves::{LeafSet, FinalizationDisplaced}; use client::children; @@ -405,6 +405,7 @@ pub struct BlockImportOperation { storage_updates: StorageCollection, child_storage_updates: ChildStorageCollection, changes_trie_updates: MemoryDB, + changes_trie_cache_update: Option>>, pending_block: Option>, aux_ops: Vec<(Vec, Option>)>, finalized_blocks: Vec<(BlockId, Option)>, @@ -487,8 +488,12 @@ where Block: BlockT, Ok(root) } - fn update_changes_trie(&mut self, update: MemoryDB) -> Result<(), client::error::Error> { - self.changes_trie_updates = update; + fn update_changes_trie( + &mut self, + update: ChangesTrieTransaction>, + ) -> Result<(), client::error::Error> { + self.changes_trie_updates = update.0; + self.changes_trie_cache_update = Some(update.1); Ok(()) } @@ -565,6 +570,7 @@ pub struct DbChangesTrieStorage { db: Arc, meta: Arc, Block::Hash>>>, min_blocks_to_keep: Option, + cache: RwLock>>, _phantom: ::std::marker::PhantomData, } @@ -576,6 +582,11 @@ impl> DbChangesTrieStorage { } } + /// Commit changes into changes trie build cache. + pub fn commit_cache(&self, cache_update: ChangesTrieCacheAction>) { + self.cache.write().perform(cache_update); + } + /// Prune obsolete changes tries. pub fn prune( &self, @@ -699,6 +710,14 @@ where self } + fn with_cached_changed_keys( + &self, + root: &H256, + functor: &mut dyn FnMut(&HashMap>, HashSet>>), + ) -> bool { + self.cache.read().with_changed_keys(root, functor) + } + fn get(&self, key: &H256, _prefix: Prefix) -> Result, String> { self.db.get(columns::CHANGES_TRIE, &key[..]) .map_err(|err| format!("{}", err)) @@ -783,6 +802,7 @@ impl> Backend { db, meta, min_blocks_to_keep: if is_archive_pruning { None } else { Some(MIN_BLOCKS_TO_KEEP_CHANGES_TRIES_FOR) }, + cache: RwLock::new(ChangesTrieBuildCache::new()), _phantom: Default::default(), }; @@ -1098,7 +1118,6 @@ impl> Backend { self.changes_tries_storage.commit(&mut transaction, changes_trie_updates); let cache = operation.old_state.release(); // release state reference so that it can be finalized - if finalized { // TODO: ensure best chain contains this block. self.ensure_sequential_finalization(header, Some(last_finalized_hash))?; @@ -1155,6 +1174,10 @@ impl> Backend { let write_result = self.storage.db.write(transaction).map_err(db_err); + if let Some(changes_trie_cache_update) = operation.changes_trie_cache_update { + self.changes_tries_storage.commit_cache(changes_trie_cache_update); + } + if let Some((number, hash, enacted, retracted, displaced_leaf, is_best, mut cache)) = imported { if let Err(e) = write_result { let mut leaves = self.blockchain.leaves.write(); @@ -1288,6 +1311,7 @@ impl client::backend::Backend for Backend whe storage_updates: Default::default(), child_storage_updates: Default::default(), changes_trie_updates: MemoryDB::default(), + changes_trie_cache_update: None, aux_ops: Vec::new(), finalized_blocks: Vec::new(), set_head: None, @@ -1515,7 +1539,7 @@ mod tests { let mut op = backend.begin_operation().unwrap(); backend.begin_state_operation(&mut op, block_id).unwrap(); op.set_block_data(header, None, None, NewBlockState::Best).unwrap(); - op.update_changes_trie(changes_trie_update).unwrap(); + op.update_changes_trie((changes_trie_update, ChangesTrieCacheAction::Clear)).unwrap(); backend.commit_operation(op).unwrap(); header_hash diff --git a/core/client/src/backend.rs b/core/client/src/backend.rs index a42fdcff240b9..457a1b86eccdf 100644 --- a/core/client/src/backend.rs +++ b/core/client/src/backend.rs @@ -24,10 +24,9 @@ use primitives::ChangesTrieConfiguration; use sr_primitives::{generic::BlockId, Justification, StorageOverlay, ChildrenStorageOverlay}; use sr_primitives::traits::{Block as BlockT, NumberFor}; use state_machine::backend::Backend as StateBackend; -use state_machine::ChangesTrieStorage as StateChangesTrieStorage; +use state_machine::{ChangesTrieStorage as StateChangesTrieStorage, ChangesTrieTransaction}; use consensus::{well_known_cache_keys, BlockOrigin}; use hash_db::Hasher; -use trie::MemoryDB; use parking_lot::Mutex; /// In memory array of storage values. @@ -116,7 +115,7 @@ pub trait BlockImportOperation where child_update: ChildStorageCollection, ) -> error::Result<()>; /// Inject changes trie data into the database. - fn update_changes_trie(&mut self, update: MemoryDB) -> error::Result<()>; + fn update_changes_trie(&mut self, update: ChangesTrieTransaction>) -> error::Result<()>; /// Insert auxiliary keys. Values are `None` if should be deleted. fn insert_aux(&mut self, ops: I) -> error::Result<()> where I: IntoIterator, Option>)>; diff --git a/core/client/src/call_executor.rs b/core/client/src/call_executor.rs index e82bd9a22c91e..0556bc7bffcaa 100644 --- a/core/client/src/call_executor.rs +++ b/core/client/src/call_executor.rs @@ -17,15 +17,15 @@ use std::{sync::Arc, cmp::Ord, panic::UnwindSafe, result, cell::RefCell, rc::Rc}; use codec::{Encode, Decode}; use sr_primitives::{ - generic::BlockId, traits::Block as BlockT, + generic::BlockId, traits::Block as BlockT, traits::NumberFor, }; use state_machine::{ self, OverlayedChanges, Ext, CodeExecutor, ExecutionManager, ExecutionStrategy, NeverOffchainExt, backend::Backend as _, + ChangesTrieTransaction, }; use executor::{RuntimeVersion, RuntimeInfo, NativeVersion}; use hash_db::Hasher; -use trie::MemoryDB; use primitives::{offchain, H256, Blake2Hasher, NativeOrEncoded, NeverNativeValue}; use crate::runtime_api::{ProofRecorder, InitializeBlock}; @@ -111,7 +111,14 @@ where manager: ExecutionManager, native_call: Option, side_effects_handler: Option<&mut O>, - ) -> Result<(NativeOrEncoded, (S::Transaction, H::Out), Option>), error::Error>; + ) -> Result< + ( + NativeOrEncoded, + (S::Transaction, H::Out), + Option>> + ), + error::Error, + >; /// Execute a call to a contract on top of given state, gathering execution proof. /// @@ -345,7 +352,7 @@ where ) -> error::Result<( NativeOrEncoded, (S::Transaction, ::Out), - Option>, + Option>>, )> { state_machine::new( state, diff --git a/core/client/src/client.rs b/core/client/src/client.rs index 3363424854cf4..47a93ac4e39db 100644 --- a/core/client/src/client.rs +++ b/core/client/src/client.rs @@ -43,7 +43,8 @@ use sr_primitives::{ use state_machine::{ DBValue, Backend as StateBackend, CodeExecutor, ChangesTrieAnchorBlockId, ExecutionStrategy, ExecutionManager, prove_read, prove_child_read, - ChangesTrieRootsStorage, ChangesTrieStorage, ChangesTrieConfigurationRange, + ChangesTrieRootsStorage, ChangesTrieStorage, + ChangesTrieTransaction, ChangesTrieConfigurationRange, key_changes, key_changes_proof, OverlayedChanges, NeverOffchainExt, }; use executor::{RuntimeVersion, RuntimeInfo}; @@ -86,7 +87,7 @@ type StorageUpdate = < >::BlockImportOperation as BlockImportOperation >::State as state_machine::Backend>::Transaction; -type ChangesUpdate = trie::MemoryDB; +type ChangesUpdate = ChangesTrieTransaction>; /// Execution strategies settings. #[derive(Debug, Clone)] @@ -635,6 +636,14 @@ impl Client where self } + fn with_cached_changed_keys( + &self, + root: &H256, + functor: &mut dyn FnMut(&HashMap>, HashSet>>), + ) -> bool { + self.storage.with_cached_changed_keys(root, functor) + } + fn get(&self, key: &H256, prefix: Prefix) -> Result, String> { self.storage.get(key, prefix) } @@ -1010,7 +1019,7 @@ impl Client where body: Option>, ) -> error::Result<( Option>, - Option>, + Option>>, Option<( Vec<(Vec, Option>)>, Vec<(Vec, Vec<(Vec, Option>)>)> diff --git a/core/client/src/in_mem.rs b/core/client/src/in_mem.rs index 58bfa05a36166..3c6a1e18c01d6 100644 --- a/core/client/src/in_mem.rs +++ b/core/client/src/in_mem.rs @@ -16,7 +16,7 @@ //! In memory client backend -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::sync::Arc; use parking_lot::{RwLock, Mutex}; use primitives::{ChangesTrieConfiguration, storage::well_known_keys}; @@ -24,7 +24,7 @@ use sr_primitives::generic::{BlockId, DigestItem}; use sr_primitives::traits::{Block as BlockT, Header as HeaderT, Zero, NumberFor}; use sr_primitives::{Justification, StorageOverlay, ChildrenStorageOverlay}; use state_machine::backend::{Backend as StateBackend, InMemory}; -use state_machine::{self, InMemoryChangesTrieStorage, ChangesTrieAnchorBlockId}; +use state_machine::{self, InMemoryChangesTrieStorage, ChangesTrieAnchorBlockId, ChangesTrieTransaction}; use hash_db::{Hasher, Prefix}; use trie::MemoryDB; use consensus::well_known_cache_keys::Id as CacheKeyId; @@ -484,8 +484,8 @@ where Ok(()) } - fn update_changes_trie(&mut self, update: MemoryDB) -> error::Result<()> { - self.changes_trie_update = Some(update); + fn update_changes_trie(&mut self, update: ChangesTrieTransaction>) -> error::Result<()> { + self.changes_trie_update = Some(update.0); Ok(()) } @@ -766,6 +766,14 @@ impl state_machine::ChangesTrieStorage> for Change self } + fn with_cached_changed_keys( + &self, + _root: &H::Out, + _functor: &mut dyn FnMut(&HashMap>, HashSet>>), + ) -> bool { + false + } + fn get(&self, key: &H::Out, prefix: Prefix) -> Result, String> { self.0.get(key, prefix) } diff --git a/core/client/src/light/backend.rs b/core/client/src/light/backend.rs index 7de922f145ba6..6b2f2f5c0af4c 100644 --- a/core/client/src/light/backend.rs +++ b/core/client/src/light/backend.rs @@ -22,7 +22,7 @@ use std::sync::{Arc, Weak}; use parking_lot::{RwLock, Mutex}; use sr_primitives::{generic::BlockId, Justification, StorageOverlay, ChildrenStorageOverlay}; -use state_machine::{Backend as StateBackend, TrieBackend, backend::InMemory as InMemoryState}; +use state_machine::{Backend as StateBackend, TrieBackend, backend::InMemory as InMemoryState, ChangesTrieTransaction}; use sr_primitives::traits::{Block as BlockT, NumberFor, Zero, Header}; use crate::in_mem::{self, check_genesis_storage}; use crate::backend::{ @@ -284,7 +284,7 @@ where Ok(()) } - fn update_changes_trie(&mut self, _update: MemoryDB) -> ClientResult<()> { + fn update_changes_trie(&mut self, _update: ChangesTrieTransaction>) -> ClientResult<()> { // we're not storing anything locally => ignore changes Ok(()) } diff --git a/core/client/src/light/call_executor.rs b/core/client/src/light/call_executor.rs index f41c14fd42992..fd6ae68e8e55e 100644 --- a/core/client/src/light/call_executor.rs +++ b/core/client/src/light/call_executor.rs @@ -25,10 +25,10 @@ use std::{ use codec::{Encode, Decode}; use primitives::{offchain, H256, Blake2Hasher, convert_hash, NativeOrEncoded}; use sr_primitives::generic::BlockId; -use sr_primitives::traits::{One, Block as BlockT, Header as HeaderT}; +use sr_primitives::traits::{One, Block as BlockT, Header as HeaderT, NumberFor}; use state_machine::{ self, Backend as StateBackend, CodeExecutor, OverlayedChanges, - ExecutionStrategy, create_proof_check_backend, + ExecutionStrategy, ChangesTrieTransaction, create_proof_check_backend, execution_proof_check_on_trie_backend, ExecutionManager, NeverOffchainExt }; use hash_db::Hasher; @@ -40,7 +40,6 @@ use crate::call_executor::CallExecutor; use crate::error::{Error as ClientError, Result as ClientResult}; use crate::light::fetcher::{Fetcher, RemoteCallRequest}; use executor::{RuntimeVersion, NativeVersion}; -use trie::MemoryDB; /// Call executor that executes methods on remote node, querying execution proof /// and checking proof by re-executing locally. @@ -185,7 +184,7 @@ where ) -> ClientResult<( NativeOrEncoded, (S::Transaction, ::Out), - Option>, + Option>>, )> { Err(ClientError::NotAvailableOnLightClient.into()) } @@ -365,7 +364,7 @@ impl CallExecutor for ) -> ClientResult<( NativeOrEncoded, (S::Transaction, ::Out), - Option>, + Option>>, )> { // there's no actual way/need to specify native/wasm execution strategy on light node // => we can safely ignore passed values diff --git a/core/client/src/light/fetcher.rs b/core/client/src/light/fetcher.rs index 205a3de476050..14a3c72b05c45 100644 --- a/core/client/src/light/fetcher.rs +++ b/core/client/src/light/fetcher.rs @@ -460,7 +460,7 @@ struct RootsStorage<'a, Number: SimpleArithmetic, Hash: 'a> { impl<'a, H, Number, Hash> ChangesTrieRootsStorage for RootsStorage<'a, Number, Hash> where H: Hasher, - Number: ::std::fmt::Display + Clone + SimpleArithmetic + Encode + Decode + Send + Sync + 'static, + Number: ::std::fmt::Display + ::std::hash::Hash + Clone + SimpleArithmetic + Encode + Decode + Send + Sync + 'static, Hash: 'a + Send + Sync + Clone + AsRef<[u8]>, { fn build_anchor( diff --git a/core/state-machine/src/changes_trie/build.rs b/core/state-machine/src/changes_trie/build.rs index e5f539f483d65..824bdd3542d58 100644 --- a/core/state-machine/src/changes_trie/build.rs +++ b/core/state-machine/src/changes_trie/build.rs @@ -33,7 +33,7 @@ use crate::changes_trie::input::ChildIndex; /// /// Returns Err if storage error has occurred OR if storage haven't returned /// required data. -pub fn prepare_input<'a, B, H, Number>( +pub(crate) fn prepare_input<'a, B, H, Number>( backend: &'a B, storage: &'a dyn Storage, config: ConfigurationRange<'a, Number>, @@ -42,6 +42,7 @@ pub fn prepare_input<'a, B, H, Number>( ) -> Result<( impl Iterator> + 'a, Vec<(ChildIndex, impl Iterator> + 'a)>, + Vec, ), String> where B: Backend, @@ -52,12 +53,14 @@ pub fn prepare_input<'a, B, H, Number>( let (extrinsics_input, children_extrinsics_input) = prepare_extrinsics_input( backend, &number, - changes)?; - let (digest_input, mut children_digest_input) = prepare_digest_input::( + changes, + )?; + let (digest_input, mut children_digest_input, digest_input_blocks) = prepare_digest_input::( parent, config, - &number, - storage)?; + number, + storage, + )?; let mut children_digest = Vec::with_capacity(children_extrinsics_input.len()); for (child_index, ext_iter) in children_extrinsics_input.into_iter() { @@ -79,6 +82,7 @@ pub fn prepare_input<'a, B, H, Number>( Ok(( extrinsics_input.chain(digest_input), children_digest, + digest_input_blocks, )) } /// Prepare ExtrinsicIndex input pairs. @@ -186,12 +190,13 @@ fn prepare_extrinsics_input_inner<'a, B, H, Number>( /// Prepare DigestIndex input pairs. fn prepare_digest_input<'a, H, Number>( parent: &'a AnchorBlockId, - config: ConfigurationRange<'a, Number>, - block: &Number, + config: ConfigurationRange, + block: Number, storage: &'a dyn Storage, ) -> Result<( impl Iterator> + 'a, BTreeMap, impl Iterator> + 'a>, + Vec, ), String> where H: Hasher, @@ -207,16 +212,16 @@ fn prepare_digest_input<'a, H, Number>( block.clone() }; - digest_build_iterator(config, block_for_digest) + let digest_input_blocks = digest_build_iterator(config, block_for_digest).collect::>(); + digest_input_blocks.clone().into_iter() .try_fold( - (BTreeMap::new(), BTreeMap::new()), - move |(mut map, mut child_map), digest_build_block| { + (BTreeMap::new(), BTreeMap::new()), move |(mut map, mut child_map), digest_build_block| { let extrinsic_prefix = ExtrinsicIndex::key_neutral_prefix(digest_build_block.clone()); let digest_prefix = DigestIndex::key_neutral_prefix(digest_build_block.clone()); let child_prefix = ChildIndex::key_neutral_prefix(digest_build_block.clone()); let trie_root = storage.root(parent, digest_build_block.clone())?; let trie_root = trie_root.ok_or_else(|| format!("No changes trie root for block {}", digest_build_block.clone()))?; - + let insert_to_map = |map: &mut BTreeMap<_,_>, key: Vec| { match map.entry(key.clone()) { Entry::Vacant(entry) => { @@ -239,6 +244,30 @@ fn prepare_digest_input<'a, H, Number>( } }; + // try to get all updated keys from cache + let populated_from_cache = storage.with_cached_changed_keys( + &trie_root, + &mut |changed_keys| { + for (storage_key, changed_keys) in changed_keys { + let map = match storage_key { + Some(storage_key) => child_map + .entry(ChildIndex:: { + block: block.clone(), + storage_key: storage_key.clone(), + }) + .or_default(), + None => &mut map, + }; + for changed_key in changed_keys.iter().cloned() { + insert_to_map(map, changed_key); + } + } + } + ); + if populated_from_cache { + return Ok((map, child_map)); + } + let mut children_roots = BTreeMap::, _>::new(); { let trie_storage = TrieBackendEssence::<_, H>::new( @@ -272,7 +301,7 @@ fn prepare_digest_input<'a, H, Number>( storage_key, }; - let mut map = child_map.entry(child_index).or_insert_with(|| BTreeMap::, _>::new()); + let mut map = child_map.entry(child_index).or_default(); let trie_storage = TrieBackendEssence::<_, H>::new( crate::changes_trie::TrieBackendStorageAdapter(storage), trie_root, @@ -288,13 +317,12 @@ fn prepare_digest_input<'a, H, Number>( }); } Ok((map, child_map)) - }) - .map(|(pairs, child_pairs)| ( pairs.into_iter().map(|(_, (k, v))| InputPair::DigestIndex(k, v)), child_pairs.into_iter().map(|(sk, pairs)| (sk, pairs.into_iter().map(|(_, (k, v))| InputPair::DigestIndex(k, v)))).collect(), + digest_input_blocks, )) } @@ -304,8 +332,8 @@ mod test { use primitives::Blake2Hasher; use primitives::storage::well_known_keys::{EXTRINSIC_INDEX}; use crate::backend::InMemory; - use crate::changes_trie::Configuration; - use crate::changes_trie::storage::InMemoryStorage; + use crate::changes_trie::{RootsStorage, Configuration, storage::InMemoryStorage}; + use crate::changes_trie::build_cache::{IncompleteCacheAction, IncompleteCachedBuildData}; use crate::overlayed_changes::{OverlayedValue, OverlayedChangeSet}; use super::*; @@ -662,4 +690,73 @@ mod test { test_with_zero(16); test_with_zero(17); } + + #[test] + fn cache_is_used_when_changes_trie_is_built() { + let (backend, mut storage, changes, _) = prepare_for_build(0); + let config = changes.changes_trie_config.as_ref().unwrap(); + let parent = AnchorBlockId { hash: Default::default(), number: 15 }; + + // override some actual values from storage with values from the cache + // + // top-level storage: + // (keys 100, 101, 103, 105 are now missing from block#4 => they do not appear + // in l2 digest at block 16) + // + // "1" child storage: + // key 102 is now missing from block#4 => it doesn't appear in l2 digest at block 16 + // (keys 103, 104) are now added to block#4 => they appear in l2 digest at block 16 + // + // "2" child storage: + // (keys 105, 106) are now added to block#4 => they appear in l2 digest at block 16 + let trie_root4 = storage.root(&parent, 4).unwrap().unwrap(); + let cached_data4 = IncompleteCacheAction::CacheBuildData(IncompleteCachedBuildData::new()) + .set_digest_input_blocks(vec![1, 2, 3]) + .insert(None, vec![vec![100], vec![102]].into_iter().collect()) + .insert(Some(b"1".to_vec()), vec![vec![103], vec![104]].into_iter().collect()) + .insert(Some(b"2".to_vec()), vec![vec![105], vec![106]].into_iter().collect()) + .complete(4, &trie_root4); + storage.cache_mut().perform(cached_data4); + + let (root_changes_trie_nodes, child_changes_tries_nodes, _) = prepare_input( + &backend, + &storage, + configuration_range(&config, 0), + &changes, + &parent, + ).unwrap(); + assert_eq!(root_changes_trie_nodes.collect::>>(), vec![ + InputPair::ExtrinsicIndex(ExtrinsicIndex { block: 16, key: vec![100] }, vec![0, 2, 3]), + InputPair::ExtrinsicIndex(ExtrinsicIndex { block: 16, key: vec![101] }, vec![1]), + InputPair::ExtrinsicIndex(ExtrinsicIndex { block: 16, key: vec![103] }, vec![0, 1]), + + InputPair::DigestIndex(DigestIndex { block: 16, key: vec![100] }, vec![4]), + InputPair::DigestIndex(DigestIndex { block: 16, key: vec![102] }, vec![4]), + InputPair::DigestIndex(DigestIndex { block: 16, key: vec![105] }, vec![8]), + ]); + + let child_changes_tries_nodes = child_changes_tries_nodes + .into_iter() + .map(|(k, i)| (k, i.collect::>())) + .collect::>(); + assert_eq!( + child_changes_tries_nodes.get(&ChildIndex { block: 16u64, storage_key: b"1".to_vec() }).unwrap(), + &vec![ + InputPair::ExtrinsicIndex(ExtrinsicIndex { block: 16u64, key: vec![100] }, vec![0, 2, 3]), + + InputPair::DigestIndex(DigestIndex { block: 16u64, key: vec![103] }, vec![4]), + InputPair::DigestIndex(DigestIndex { block: 16u64, key: vec![104] }, vec![4]), + ], + ); + assert_eq!( + child_changes_tries_nodes.get(&ChildIndex { block: 16u64, storage_key: b"2".to_vec() }).unwrap(), + &vec![ + InputPair::ExtrinsicIndex(ExtrinsicIndex { block: 16u64, key: vec![100] }, vec![0, 2]), + + InputPair::DigestIndex(DigestIndex { block: 16u64, key: vec![105] }, vec![4]), + InputPair::DigestIndex(DigestIndex { block: 16u64, key: vec![106] }, vec![4]), + ], + ); + + } } diff --git a/core/state-machine/src/changes_trie/build_cache.rs b/core/state-machine/src/changes_trie/build_cache.rs new file mode 100644 index 0000000000000..f5c7c28b6b801 --- /dev/null +++ b/core/state-machine/src/changes_trie/build_cache.rs @@ -0,0 +1,262 @@ +// Copyright 2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate 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. + +// Substrate 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 Substrate. If not, see . + +//! Changes tries build cache. + +use std::collections::{HashMap, HashSet}; + +/// Changes trie build cache. +/// +/// Helps to avoid read of changes tries from the database when digest trie +/// is built. It holds changed keys for every block (indexed by changes trie +/// root) that could be referenced by future digest items. For digest entries +/// it also holds keys covered by this digest. Entries for top level digests +/// are never created, because they'll never be used to build other digests. +/// +/// Entries are pruned from the cache once digest block that is using this entry +/// is inserted (because digest block will includes all keys from this entry). +/// When there's a fork, entries are pruned when first changes trie is inserted. +pub struct BuildCache { + /// Map of block (implies changes true) number => changes trie root. + roots_by_number: HashMap, + /// Map of changes trie root => set of storage keys that are in this trie. + /// The `Option>` in inner `HashMap` stands for the child storage key. + /// If it is `None`, then the `HashSet` contains keys changed in top-level storage. + /// If it is `Some`, then the `HashSet` contains keys changed in child storage, identified by the key. + changed_keys: HashMap>, HashSet>>>, +} + +/// The action to perform when block-with-changes-trie is imported. +#[derive(Debug, PartialEq)] +pub enum CacheAction { + /// Cache data that has been collected when CT has been built. + CacheBuildData(CachedBuildData), + /// Clear cache from all existing entries. + Clear, +} + +/// The data that has been cached during changes trie building. +#[derive(Debug, PartialEq)] +pub struct CachedBuildData { + block: N, + trie_root: H, + digest_input_blocks: Vec, + changed_keys: HashMap>, HashSet>>, +} + +/// The action to perform when block-with-changes-trie is imported. +#[derive(Debug, PartialEq)] +pub(crate) enum IncompleteCacheAction { + /// Cache data that has been collected when CT has been built. + CacheBuildData(IncompleteCachedBuildData), + /// Clear cache from all existing entries. + Clear, +} + +/// The data (without changes trie root) that has been cached during changes trie building. +#[derive(Debug, PartialEq)] +pub(crate) struct IncompleteCachedBuildData { + digest_input_blocks: Vec, + changed_keys: HashMap>, HashSet>>, +} + +impl BuildCache + where + N: Eq + ::std::hash::Hash, + H: Eq + ::std::hash::Hash + Clone, +{ + /// Create new changes trie build cache. + pub fn new() -> Self { + BuildCache { + roots_by_number: HashMap::new(), + changed_keys: HashMap::new(), + } + } + + /// Get cached changed keys for changes trie with given root. + pub fn get(&self, root: &H) -> Option<&HashMap>, HashSet>>> { + self.changed_keys.get(&root) + } + + /// Execute given functor with cached entry for given block. + /// Returns true if the functor has been called and false otherwise. + pub fn with_changed_keys( + &self, + root: &H, + functor: &mut dyn FnMut(&HashMap>, HashSet>>), + ) -> bool { + match self.changed_keys.get(&root) { + Some(changed_keys) => { + functor(changed_keys); + true + }, + None => false, + } + } + + /// Insert data into cache. + pub fn perform(&mut self, action: CacheAction) { + 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 IncompleteCacheAction { + /// Returns true if we need to collect changed keys for this action. + pub fn collects_changed_keys(&self) -> bool { + match *self { + IncompleteCacheAction::CacheBuildData(_) => true, + IncompleteCacheAction::Clear => false, + } + } + + /// Complete cache action with computed changes trie root. + pub(crate) fn complete(self, block: N, trie_root: &H) -> CacheAction { + match self { + IncompleteCacheAction::CacheBuildData(build_data) => + CacheAction::CacheBuildData(build_data.complete(block, trie_root.clone())), + IncompleteCacheAction::Clear => CacheAction::Clear, + } + } + + /// Set numbers of blocks that are superseded by this new entry. + /// + /// If/when this build data is committed to the cache, entries for these blocks + /// will be removed from the cache. + pub(crate) fn set_digest_input_blocks(self, digest_input_blocks: Vec) -> Self { + match self { + IncompleteCacheAction::CacheBuildData(build_data) => + IncompleteCacheAction::CacheBuildData(build_data.set_digest_input_blocks(digest_input_blocks)), + IncompleteCacheAction::Clear => IncompleteCacheAction::Clear, + } + } + + /// Insert changed keys of given storage into cached data. + pub(crate) fn insert( + self, + storage_key: Option>, + changed_keys: HashSet>, + ) -> Self { + match self { + IncompleteCacheAction::CacheBuildData(build_data) => + IncompleteCacheAction::CacheBuildData(build_data.insert(storage_key, changed_keys)), + IncompleteCacheAction::Clear => IncompleteCacheAction::Clear, + } + } +} + +impl IncompleteCachedBuildData { + /// Create new cached data. + pub(crate) fn new() -> Self { + IncompleteCachedBuildData { + digest_input_blocks: Vec::new(), + changed_keys: HashMap::new(), + } + } + + fn complete(self, block: N, trie_root: H) -> CachedBuildData { + 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) -> Self { + self.digest_input_blocks = digest_input_blocks; + self + } + + fn insert( + mut self, + storage_key: Option>, + changed_keys: HashSet>, + ) -> 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::::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::::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); + } +} \ No newline at end of file diff --git a/core/state-machine/src/changes_trie/input.rs b/core/state-machine/src/changes_trie/input.rs index 17f3b41b22f18..e0bcad18be114 100644 --- a/core/state-machine/src/changes_trie/input.rs +++ b/core/state-machine/src/changes_trie/input.rs @@ -78,6 +78,17 @@ pub enum InputKey { ChildIndex(ChildIndex), } +impl InputPair { + /// Extract storage key that this pair corresponds to. + pub fn key(&self) -> Option<&[u8]> { + match *self { + InputPair::ExtrinsicIndex(ref key, _) => Some(&key.key), + InputPair::DigestIndex(ref key, _) => Some(&key.key), + InputPair::ChildIndex(_, _) => None, + } + } +} + impl Into<(Vec, Vec)> for InputPair { fn into(self) -> (Vec, Vec) { match self { diff --git a/core/state-machine/src/changes_trie/mod.rs b/core/state-machine/src/changes_trie/mod.rs index 41234b3428678..f771fddf61964 100644 --- a/core/state-machine/src/changes_trie/mod.rs +++ b/core/state-machine/src/changes_trie/mod.rs @@ -49,6 +49,7 @@ //! are propagated through its storage root on the top level storage. mod build; +mod build_cache; mod build_iterator; mod changes_iterator; mod input; @@ -56,6 +57,7 @@ mod prune; mod storage; mod surface_iterator; +pub use self::build_cache::{BuildCache, CachedBuildData, CacheAction}; pub use self::storage::InMemoryStorage; pub use self::changes_iterator::{ key_changes, key_changes_proof, @@ -63,6 +65,7 @@ pub use self::changes_iterator::{ }; pub use self::prune::{prune, oldest_non_pruned_trie}; +use std::collections::{HashMap, HashSet}; use std::convert::TryInto; use hash_db::{Hasher, Prefix}; use crate::backend::Backend; @@ -70,6 +73,7 @@ use num_traits::{One, Zero}; use codec::{Decode, Encode}; use primitives; use crate::changes_trie::build::prepare_input; +use crate::changes_trie::build_cache::{IncompleteCachedBuildData, IncompleteCacheAction}; use crate::overlayed_changes::OverlayedChanges; use trie::{MemoryDB, DBValue, TrieMut}; use trie::trie_types::TrieDBMut; @@ -84,6 +88,7 @@ pub trait BlockNumber: Clone + From + TryInto + One + Zero + PartialEq + Ord + + ::std::hash::Hash + ::std::ops::Add + ::std::ops::Sub + ::std::ops::Mul + ::std::ops::Div + ::std::ops::Rem + @@ -98,6 +103,7 @@ impl BlockNumber for T where T: Clone + From + TryInto + One + Zero + PartialEq + Ord + + ::std::hash::Hash + ::std::ops::Add + ::std::ops::Sub + ::std::ops::Mul + ::std::ops::Div + ::std::ops::Rem + @@ -128,6 +134,13 @@ pub trait RootsStorage: Send + Sync { pub trait Storage: RootsStorage { /// Casts from self reference to RootsStorage reference. fn as_roots_storage(&self) -> &dyn RootsStorage; + /// Execute given functor with cached entry for given trie root. + /// Returns true if the functor has been called (cache entry exists) and false otherwise. + fn with_cached_changed_keys( + &self, + root: &H::Out, + functor: &mut dyn FnMut(&HashMap>, HashSet>>), + ) -> bool; /// Get a trie node. fn get(&self, key: &H::Out, prefix: Prefix) -> Result, String>; } @@ -166,7 +179,7 @@ pub fn build_changes_trie<'a, B: Backend, S: Storage, H: Hasher, N storage: Option<&'a S>, changes: &OverlayedChanges, parent_hash: H::Out, -) -> Result, H::Out)>, ()> +) -> Result, H::Out, CacheAction)>, ()> where H::Out: Ord + 'static, { @@ -184,15 +197,22 @@ pub fn build_changes_trie<'a, B: Backend, S: Storage, H: Hasher, N // build_anchor error should not be considered fatal let parent = storage.build_anchor(parent_hash).map_err(|_| ())?; + let block = parent.number.clone() + One::one(); // storage errors are considered fatal (similar to situations when runtime fetches values from storage) - let (input_pairs, child_input_pairs) = prepare_input::( + let (input_pairs, child_input_pairs, digest_input_blocks) = prepare_input::( backend, storage, - config, + config.clone(), changes, &parent, ).expect("changes trie: storage access is not allowed to fail within runtime"); + + // prepare cached data + let mut cache_action = prepare_cached_build_data(config, block.clone()); + let needs_changed_keys = cache_action.collects_changed_keys(); + cache_action = cache_action.set_digest_input_blocks(digest_input_blocks); + let mut mdb = MemoryDB::default(); let mut child_roots = Vec::with_capacity(child_input_pairs.len()); for (child_index, input_pairs) in child_input_pairs { @@ -200,11 +220,24 @@ pub fn build_changes_trie<'a, B: Backend, S: Storage, H: Hasher, N let mut root = Default::default(); { let mut trie = TrieDBMut::::new(&mut mdb, &mut root); - for (key, value) in input_pairs.map(Into::into) { + let mut storage_changed_keys = HashSet::new(); + for input_pair in input_pairs { + if needs_changed_keys { + if let Some(key) = input_pair.key() { + storage_changed_keys.insert(key.to_vec()); + } + } + + let (key, value) = input_pair.into(); not_empty = true; trie.insert(&key, &value) .expect("changes trie: insertion to trie is not allowed to fail within runtime"); } + + cache_action = cache_action.insert( + Some(child_index.storage_key.clone()), + storage_changed_keys, + ); } if not_empty { child_roots.push(input::InputPair::ChildIndex(child_index, root.as_ref().to_vec())); @@ -213,11 +246,91 @@ pub fn build_changes_trie<'a, B: Backend, S: Storage, H: Hasher, N let mut root = Default::default(); { let mut trie = TrieDBMut::::new(&mut mdb, &mut root); - for (key, value) in input_pairs.chain(child_roots.into_iter()).map(Into::into) { + for (key, value) in child_roots.into_iter().map(Into::into) { + trie.insert(&key, &value) + .expect("changes trie: insertion to trie is not allowed to fail within runtime"); + } + + let mut storage_changed_keys = HashSet::new(); + for input_pair in input_pairs { + if needs_changed_keys { + if let Some(key) = input_pair.key() { + storage_changed_keys.insert(key.to_vec()); + } + } + + let (key, value) = input_pair.into(); trie.insert(&key, &value) .expect("changes trie: insertion to trie is not allowed to fail within runtime"); } + cache_action = cache_action.insert( + None, + storage_changed_keys, + ); + } + + let cache_action = cache_action.complete(block, &root); + Ok(Some((mdb, root, cache_action))) +} + +/// Prepare empty cached build data for given block. +fn prepare_cached_build_data( + config: ConfigurationRange, + block: Number, +) -> IncompleteCacheAction { + // when digests are not enabled in configuration, we do not need to cache anything + // because it'll never be used again for building other tries + // => let's clear the cache + if !config.config.is_digest_build_enabled() { + return IncompleteCacheAction::Clear; + } + + // when this is the last block where current configuration is active + // => let's clear the cache + if config.end.as_ref() == Some(&block) { + return IncompleteCacheAction::Clear; + } + + // we do not need to cache anything when top-level digest trie is created, because + // it'll never be used again for building other tries + // => let's clear the cache + match config.config.digest_level_at_block(config.zero.clone(), block) { + Some((digest_level, _, _)) if digest_level == config.config.digest_levels => IncompleteCacheAction::Clear, + _ => IncompleteCacheAction::CacheBuildData(IncompleteCachedBuildData::new()), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn cache_is_cleared_when_digests_are_disabled() { + let config = Configuration { digest_interval: 0, digest_levels: 0 }; + let config_range = ConfigurationRange { zero: 0, end: None, config: &config }; + assert_eq!(prepare_cached_build_data(config_range, 8u32), IncompleteCacheAction::Clear); + } + + #[test] + fn build_data_is_cached_when_digests_are_enabled() { + let config = Configuration { digest_interval: 8, digest_levels: 2 }; + let config_range = ConfigurationRange { zero: 0, end: None, config: &config }; + assert!(prepare_cached_build_data(config_range.clone(), 4u32).collects_changed_keys()); + assert!(prepare_cached_build_data(config_range.clone(), 7u32).collects_changed_keys()); + assert!(prepare_cached_build_data(config_range, 8u32).collects_changed_keys()); } - Ok(Some((mdb, root))) + #[test] + fn cache_is_cleared_when_digests_are_enabled_and_top_level_digest_is_built() { + let config = Configuration { digest_interval: 8, digest_levels: 2 }; + let config_range = ConfigurationRange { zero: 0, end: None, config: &config }; + assert_eq!(prepare_cached_build_data(config_range, 64u32), IncompleteCacheAction::Clear); + } + + #[test] + fn cache_is_cleared_when_end_block_of_configuration_is_built() { + let config = Configuration { digest_interval: 8, digest_levels: 2 }; + let config_range = ConfigurationRange { zero: 0, end: Some(4u32), config: &config }; + assert_eq!(prepare_cached_build_data(config_range.clone(), 4u32), IncompleteCacheAction::Clear); + } } diff --git a/core/state-machine/src/changes_trie/storage.rs b/core/state-machine/src/changes_trie/storage.rs index 65287241c19aa..f8c556a46c61b 100644 --- a/core/state-machine/src/changes_trie/storage.rs +++ b/core/state-machine/src/changes_trie/storage.rs @@ -16,16 +16,14 @@ //! Changes trie storage utilities. -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashSet, HashMap}; use hash_db::{Hasher, Prefix, EMPTY_PREFIX}; use trie::DBValue; use trie::MemoryDB; use parking_lot::RwLock; -use crate::changes_trie::{RootsStorage, Storage, AnchorBlockId, BlockNumber}; +use crate::changes_trie::{BuildCache, RootsStorage, Storage, AnchorBlockId, BlockNumber}; use crate::trie_backend_essence::TrieBackendStorage; -#[cfg(test)] -use std::collections::HashSet; #[cfg(test)] use crate::backend::insert_into_memory_db; #[cfg(test)] @@ -34,6 +32,7 @@ use crate::changes_trie::input::{InputPair, ChildIndex}; /// In-memory implementation of changes trie storage. pub struct InMemoryStorage { data: RwLock>, + cache: BuildCache, } /// Adapter for using changes trie storage as a TrieBackendEssence' storage. @@ -55,6 +54,7 @@ impl InMemoryStorage { roots: BTreeMap::new(), mdb, }), + cache: BuildCache::new(), } } @@ -74,6 +74,11 @@ impl InMemoryStorage { Self::with_db(proof_db) } + /// Get mutable cache reference. + pub fn cache_mut(&mut self) -> &mut BuildCache { + &mut self.cache + } + /// Create the storage with given blocks. pub fn with_blocks(blocks: Vec<(Number, H::Out)>) -> Self { Self { @@ -81,6 +86,7 @@ impl InMemoryStorage { roots: blocks.into_iter().collect(), mdb: MemoryDB::default(), }), + cache: BuildCache::new(), } } @@ -122,6 +128,7 @@ impl InMemoryStorage { roots, mdb, }), + cache: BuildCache::new(), } } @@ -169,6 +176,14 @@ impl Storage for InMemoryStorage>, HashSet>>), + ) -> bool { + self.cache.with_changed_keys(root, functor) + } + fn get(&self, key: &H::Out, prefix: Prefix) -> Result, String> { MemoryDB::::get(&self.data.read().mdb, key, prefix) } diff --git a/core/state-machine/src/ext.rs b/core/state-machine/src/ext.rs index 8281f0f9c2f4b..fde3ba2b01953 100644 --- a/core/state-machine/src/ext.rs +++ b/core/state-machine/src/ext.rs @@ -19,7 +19,10 @@ use std::{error, fmt, cmp::Ord}; use log::warn; use crate::backend::Backend; -use crate::changes_trie::{Storage as ChangesTrieStorage, build_changes_trie}; +use crate::changes_trie::{ + Storage as ChangesTrieStorage, CacheAction as ChangesTrieCacheAction, + build_changes_trie, +}; use crate::{Externalities, OverlayedChanges, ChildStorageKey}; use hash_db::Hasher; use primitives::{offchain, storage::well_known_keys::is_child_storage_key, traits::BareCryptoStorePtr}; @@ -78,7 +81,7 @@ where /// This differs from `storage_transaction` behavior, because the moment when /// `storage_changes_root` is called matters + we need to remember additional /// data at this moment (block number). - changes_trie_transaction: Option<(MemoryDB, H::Out)>, + changes_trie_transaction: Option<(MemoryDB, H::Out, ChangesTrieCacheAction)>, /// Additional externalities for offchain workers. /// /// If None, some methods from the trait might not be supported. @@ -119,14 +122,14 @@ where } /// Get the transaction necessary to update the backend. - pub fn transaction(mut self) -> ((B::Transaction, H::Out), Option>) { + pub fn transaction(mut self) -> ((B::Transaction, H::Out), Option>) { let _ = self.storage_root(); let (storage_transaction, changes_trie_transaction) = ( self.storage_transaction .expect("storage_transaction always set after calling storage root; qed"), self.changes_trie_transaction - .map(|(tx, _)| tx), + .map(|(tx, _, cache)| (tx, cache)), ); ( @@ -355,7 +358,7 @@ where self.overlay, parent_hash, )?; - Ok(self.changes_trie_transaction.as_ref().map(|(_, root)| root.clone())) + Ok(self.changes_trie_transaction.as_ref().map(|(_, root, _)| root.clone())) } fn offchain(&mut self) -> Option<&mut dyn offchain::Externalities> { diff --git a/core/state-machine/src/lib.rs b/core/state-machine/src/lib.rs index acc7c366b81e4..70a7f3b1462b0 100644 --- a/core/state-machine/src/lib.rs +++ b/core/state-machine/src/lib.rs @@ -50,10 +50,12 @@ pub use changes_trie::{ Storage as ChangesTrieStorage, RootsStorage as ChangesTrieRootsStorage, InMemoryStorage as InMemoryChangesTrieStorage, + BuildCache as ChangesTrieBuildCache, + CacheAction as ChangesTrieCacheAction, ConfigurationRange as ChangesTrieConfigurationRange, key_changes, key_changes_proof, key_changes_proof_check, prune as prune_changes_tries, - oldest_non_pruned_trie as oldest_non_pruned_changes_trie + oldest_non_pruned_trie as oldest_non_pruned_changes_trie, }; pub use overlayed_changes::OverlayedChanges; pub use proving_backend::{ @@ -63,6 +65,11 @@ pub use proving_backend::{ pub use trie_backend_essence::{TrieBackendStorage, Storage}; pub use trie_backend::TrieBackend; +/// Type of changes trie transaction. +pub type ChangesTrieTransaction = ( + MemoryDB, + ChangesTrieCacheAction<::Out, N>, +); /// A wrapper around a child storage key. /// @@ -513,7 +520,7 @@ impl<'a, H, N, B, T, O, Exec> StateMachine<'a, H, N, B, T, O, Exec> where pub fn execute( &mut self, strategy: ExecutionStrategy, - ) -> Result<(Vec, (B::Transaction, H::Out), Option>), Box> { + ) -> Result<(Vec, (B::Transaction, H::Out), Option>), Box> { // We are not giving a native call and thus we are sure that the result can never be a native // value. self.execute_using_consensus_failure_handler::<_, NeverNativeValue, fn() -> _>( @@ -537,7 +544,7 @@ impl<'a, H, N, B, T, O, Exec> StateMachine<'a, H, N, B, T, O, Exec> where CallResult, bool, Option<(B::Transaction, H::Out)>, - Option>, + Option>, ) where R: Decode + Encode + PartialEq, NC: FnOnce() -> result::Result + UnwindSafe, @@ -571,7 +578,7 @@ impl<'a, H, N, B, T, O, Exec> StateMachine<'a, H, N, B, T, O, Exec> where mut native_call: Option, orig_prospective: OverlayedChangeSet, on_consensus_failure: Handler, - ) -> (CallResult, Option<(B::Transaction, H::Out)>, Option>) where + ) -> (CallResult, Option<(B::Transaction, H::Out)>, Option>) where R: Decode + Encode + PartialEq, NC: FnOnce() -> result::Result + UnwindSafe, Handler: FnOnce( @@ -602,7 +609,7 @@ impl<'a, H, N, B, T, O, Exec> StateMachine<'a, H, N, B, T, O, Exec> where compute_tx: bool, mut native_call: Option, orig_prospective: OverlayedChangeSet, - ) -> (CallResult, Option<(B::Transaction, H::Out)>, Option>) where + ) -> (CallResult, Option<(B::Transaction, H::Out)>, Option>) where R: Decode + Encode + PartialEq, NC: FnOnce() -> result::Result + UnwindSafe, { @@ -633,7 +640,7 @@ impl<'a, H, N, B, T, O, Exec> StateMachine<'a, H, N, B, T, O, Exec> where ) -> Result<( NativeOrEncoded, Option<(B::Transaction, H::Out)>, - Option> + Option>, ), Box> where R: Decode + Encode + PartialEq, NC: FnOnce() -> result::Result + UnwindSafe, diff --git a/core/state-machine/src/testing.rs b/core/state-machine/src/testing.rs index 1065541e6306d..a40026b271287 100644 --- a/core/state-machine/src/testing.rs +++ b/core/state-machine/src/testing.rs @@ -276,7 +276,7 @@ impl Externalities for TestExternalities Some(&self.changes_trie_storage), &self.overlay, parent, - )?.map(|(_, root)| root)) + )?.map(|(_, root, _)| root)) } fn offchain(&mut self) -> Option<&mut dyn offchain::Externalities> {