From d5af8dd9521f846c36d8fcba2bc02ecc307468b8 Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Mon, 22 Oct 2018 18:09:01 +0300 Subject: [PATCH 1/3] forks support in changes trie storage --- core/client/db/src/lib.rs | 149 ++++++++++++++---- core/client/db/src/utils.rs | 19 +++ core/client/src/client.rs | 22 ++- core/client/src/light/fetcher.rs | 24 +-- core/executor/src/wasm_executor.rs | 13 +- core/sr-io/with_std.rs | 4 +- core/sr-io/without_std.rs | 6 +- core/sr-primitives/src/traits.rs | 6 +- core/state-machine/src/changes_trie/build.rs | 29 ++-- .../src/changes_trie/changes_iterator.rs | 48 +++--- core/state-machine/src/changes_trie/mod.rs | 18 ++- core/state-machine/src/changes_trie/prune.rs | 12 +- .../state-machine/src/changes_trie/storage.rs | 4 +- core/state-machine/src/ext.rs | 16 +- core/state-machine/src/lib.rs | 8 +- core/state-machine/src/testing.rs | 6 +- core/test-runtime/src/system.rs | 4 +- node/executor/src/lib.rs | 4 +- srml/system/src/lib.rs | 2 +- 19 files changed, 265 insertions(+), 129 deletions(-) diff --git a/core/client/db/src/lib.rs b/core/client/db/src/lib.rs index 2bdb9f7500897..83c937a31fe35 100644 --- a/core/client/db/src/lib.rs +++ b/core/client/db/src/lib.rs @@ -148,7 +148,7 @@ impl<'a> state_db::MetaDb for StateMetaDb<'a> { /// Block database pub struct BlockchainDb { db: Arc, - meta: RwLock, Block::Hash>>, + meta: Arc, Block::Hash>>>, leaves: RwLock>>, } @@ -159,7 +159,7 @@ impl BlockchainDb { Ok(BlockchainDb { db, leaves: RwLock::new(leaves), - meta: RwLock::new(meta), + meta: Arc::new(RwLock::new(meta)), }) } @@ -368,6 +368,7 @@ impl state_machine::Storage for DbGenesisStorage { pub struct DbChangesTrieStorage { db: Arc, + meta: Arc, Block::Hash>>>, min_blocks_to_keep: Option, _phantom: ::std::marker::PhantomData, } @@ -381,7 +382,7 @@ impl DbChangesTrieStorage { } /// Prune obsolete changes tries. - pub fn prune(&self, config: Option, tx: &mut DBTransaction, block: NumberFor) { + pub fn prune(&self, config: Option, tx: &mut DBTransaction, block_hash: Block::Hash, block_num: NumberFor) { // never prune on archive nodes let min_blocks_to_keep = match self.min_blocks_to_keep { Some(min_blocks_to_keep) => min_blocks_to_keep, @@ -399,23 +400,44 @@ impl DbChangesTrieStorage { &config, &*self, min_blocks_to_keep, - block.as_(), + &state_machine::ChangesTrieAnchorBlockId { + hash: utils::convert_hash(&block_hash), + number: block_num.as_(), + }, |node| tx.delete(columns::CHANGES_TRIE, node.as_ref())); } } impl state_machine::ChangesTrieRootsStorage for DbChangesTrieStorage { - fn root(&self, block: u64) -> Result, String> { - Ok(read_db::(&*self.db, columns::HASH_LOOKUP, columns::HEADER, BlockId::Number(As::sa(block))) - .map_err(|err| format!("{}", err)) - .and_then(|header| match header { - Some(header) => Block::Header::decode(&mut &header[..]) - .ok_or_else(|| format!("Failed to parse header of block {}", block)) - .map(Some), - None => Ok(None) - })? - .and_then(|header| header.digest().log(DigestItem::as_changes_trie_root) - .map(|root| H256::from_slice(root.as_ref())))) + fn root(&self, anchor: &state_machine::ChangesTrieAnchorBlockId, block: u64) -> Result, String> { + // check API requirement + assert!(block <= anchor.number, "API requirement"); + + // we need to get hash of the block to resolve changes trie root + let block_id = if block <= self.meta.read().finalized_number.as_() { + // if block is finalized, we could just read canonical hash + BlockId::Number(As::sa(block)) + } else { + // if block is not finalized yet, we should find the required block hash by traversing + // back from the anchor to the block with given number + let mut current_num = anchor.number; + let mut current_hash: Block::Hash = ::utils::convert_hash(&anchor.hash); + while current_num != block { + let current_header: Block::Header = ::utils::require_header::( + &*self.db, columns::HASH_LOOKUP, columns::HEADER, BlockId::Hash(current_hash) + ).map_err(|e| e.to_string())?; + + current_hash = *current_header.parent_hash(); + current_num = current_num - 1; + } + + BlockId::Hash(current_hash) + }; + + Ok(::utils::require_header::(&*self.db, columns::HASH_LOOKUP, columns::HEADER, block_id) + .map_err(|e| e.to_string())? + .digest().log(DigestItem::as_changes_trie_root) + .map(|root| H256::from_slice(root.as_ref()))) } } @@ -461,6 +483,7 @@ impl Backend { fn from_kvdb(db: Arc, pruning: PruningMode, canonicalization_delay: u64) -> Result { let is_archive_pruning = pruning.is_archive(); let blockchain = BlockchainDb::new(db.clone())?; + let meta = blockchain.meta.clone(); let map_e = |e: state_db::Error| ::client::error::Error::from(format!("State database error: {:?}", e)); let state_db: StateDb = StateDb::new(pruning, &StateMetaDb(&*db)).map_err(map_e)?; let storage_db = StorageDb { @@ -469,6 +492,7 @@ impl Backend { }; let changes_tries_storage = DbChangesTrieStorage { db, + meta, min_blocks_to_keep: if is_archive_pruning { None } else { Some(MIN_BLOCKS_TO_KEEP_CHANGES_TRIES_FOR) }, _phantom: Default::default(), }; @@ -544,7 +568,7 @@ impl Backend { let changes_trie_config: Option = self.state_at(BlockId::Hash(parent_hash))? .storage(well_known_keys::CHANGES_TRIE_CONFIG)? .and_then(|v| Decode::decode(&mut &*v)); - self.changes_tries_storage.prune(changes_trie_config, transaction, f_num); + self.changes_tries_storage.prune(changes_trie_config, transaction, f_hash, f_num); } Ok(()) @@ -1157,10 +1181,13 @@ mod tests { #[test] fn changes_trie_storage_works() { let backend = Backend::::new_test(1000, 100); + backend.changes_tries_storage.meta.write().finalized_number = 1000; + let check_changes = |backend: &Backend, block: u64, changes: Vec<(Vec, Vec)>| { let (changes_root, mut changes_trie_update) = prepare_changes(changes); - assert_eq!(backend.changes_tries_storage.root(block), Ok(Some(changes_root))); + let anchor = state_machine::ChangesTrieAnchorBlockId { hash: Default::default(), number: block }; + assert_eq!(backend.changes_tries_storage.root(&anchor, block), Ok(Some(changes_root))); for (key, (val, _)) in changes_trie_update.drain() { assert_eq!(backend.changes_trie_storage().unwrap().get(&key), Ok(Some(val))); @@ -1184,9 +1211,66 @@ mod tests { check_changes(&backend, 2, changes2); } + #[test] + fn changes_trie_storage_works_with_forks() { + let backend = Backend::::new_test(1000, 100); + + let changes0 = vec![(b"k0".to_vec(), b"v0".to_vec())]; + let changes1 = vec![(b"k1".to_vec(), b"v1".to_vec())]; + let changes2 = vec![(b"k2".to_vec(), b"v2".to_vec())]; + let block0 = insert_header(&backend, 0, Default::default(), changes0.clone(), Default::default()); + let block1 = insert_header(&backend, 1, block0, changes1.clone(), Default::default()); + let block2 = insert_header(&backend, 2, block1, changes2.clone(), Default::default()); + + let changes2_1_0 = vec![(b"k3".to_vec(), b"v3".to_vec())]; + let changes2_1_1 = vec![(b"k4".to_vec(), b"v4".to_vec())]; + let block2_1_0 = insert_header(&backend, 3, block2, changes2_1_0.clone(), Default::default()); + let block2_1_1 = insert_header(&backend, 4, block2_1_0, changes2_1_1.clone(), Default::default()); + + let changes2_2_0 = vec![(b"k5".to_vec(), b"v5".to_vec())]; + let changes2_2_1 = vec![(b"k6".to_vec(), b"v6".to_vec())]; + let block2_2_0 = insert_header(&backend, 3, block2, changes2_2_0.clone(), Default::default()); + let block2_2_1 = insert_header(&backend, 4, block2_2_0, changes2_2_1.clone(), Default::default()); + + // finalized block1 + backend.changes_tries_storage.meta.write().finalized_number = 1; + + // branch1: when asking for finalized block hash + let (changes1_root, _) = prepare_changes(changes1); + let anchor = state_machine::ChangesTrieAnchorBlockId { hash: block2_1_1, number: 4 }; + assert_eq!(backend.changes_tries_storage.root(&anchor, 1), Ok(Some(changes1_root))); + + // branch2: when asking for finalized block hash + let anchor = state_machine::ChangesTrieAnchorBlockId { hash: block2_2_1, number: 4 }; + assert_eq!(backend.changes_tries_storage.root(&anchor, 1), Ok(Some(changes1_root))); + + // branch1: when asking for non-finalized block hash + let (changes2_1_0_root, _) = prepare_changes(changes2_1_0); + let anchor = state_machine::ChangesTrieAnchorBlockId { hash: block2_1_1, number: 4 }; + assert_eq!(backend.changes_tries_storage.root(&anchor, 3), Ok(Some(changes2_1_0_root))); + + // branch2: when asking for non-finalized block hash + let (changes2_2_0_root, _) = prepare_changes(changes2_2_0); + let anchor = state_machine::ChangesTrieAnchorBlockId { hash: block2_2_1, number: 4 }; + assert_eq!(backend.changes_tries_storage.root(&anchor, 3), Ok(Some(changes2_2_0_root))); + + // finalized first block of branch2 + backend.changes_tries_storage.meta.write().finalized_number = 3; + + // branch1: when asking for finalized block of other branch + // => result is incorrect (returned for the block of branch1), but this is expected, + // because the other fork is abandoned (forked before finalized header) + assert_eq!(backend.changes_tries_storage.root(&anchor, 3), Ok(Some(changes2_2_0_root))); + + // branch2: when asking for finalized block of this branch + let anchor = state_machine::ChangesTrieAnchorBlockId { hash: block2_1_1, number: 4 }; + assert_eq!(backend.changes_tries_storage.root(&anchor, 3), Ok(Some(changes2_2_0_root))); + } + #[test] fn changes_tries_are_pruned_on_finalization() { let mut backend = Backend::::new_test(1000, 100); + backend.changes_tries_storage.meta.write().finalized_number = 1000; backend.changes_tries_storage.min_blocks_to_keep = Some(8); let config = ChangesTrieConfiguration { digest_interval: 2, @@ -1209,26 +1293,27 @@ mod tests { let _ = insert_header(&backend, 12, block11, vec![(b"key_at_12".to_vec(), b"val_at_12".to_vec())], Default::default()); // check that roots of all tries are in the columns::CHANGES_TRIE + let anchor = state_machine::ChangesTrieAnchorBlockId { hash: Default::default(), number: 100 }; fn read_changes_trie_root(backend: &Backend, num: u64) -> H256 { backend.blockchain().header(BlockId::Number(num)).unwrap().unwrap().digest().logs().iter() .find(|i| i.as_changes_trie_root().is_some()).unwrap().as_changes_trie_root().unwrap().clone() } - let root1 = read_changes_trie_root(&backend, 1); assert_eq!(backend.changes_tries_storage.root(1).unwrap(), Some(root1)); - let root2 = read_changes_trie_root(&backend, 2); assert_eq!(backend.changes_tries_storage.root(2).unwrap(), Some(root2)); - let root3 = read_changes_trie_root(&backend, 3); assert_eq!(backend.changes_tries_storage.root(3).unwrap(), Some(root3)); - let root4 = read_changes_trie_root(&backend, 4); assert_eq!(backend.changes_tries_storage.root(4).unwrap(), Some(root4)); - let root5 = read_changes_trie_root(&backend, 5); assert_eq!(backend.changes_tries_storage.root(5).unwrap(), Some(root5)); - let root6 = read_changes_trie_root(&backend, 6); assert_eq!(backend.changes_tries_storage.root(6).unwrap(), Some(root6)); - let root7 = read_changes_trie_root(&backend, 7); assert_eq!(backend.changes_tries_storage.root(7).unwrap(), Some(root7)); - let root8 = read_changes_trie_root(&backend, 8); assert_eq!(backend.changes_tries_storage.root(8).unwrap(), Some(root8)); - let root9 = read_changes_trie_root(&backend, 9); assert_eq!(backend.changes_tries_storage.root(9).unwrap(), Some(root9)); - let root10 = read_changes_trie_root(&backend, 10); assert_eq!(backend.changes_tries_storage.root(10).unwrap(), Some(root10)); - let root11 = read_changes_trie_root(&backend, 11); assert_eq!(backend.changes_tries_storage.root(11).unwrap(), Some(root11)); - let root12 = read_changes_trie_root(&backend, 12); assert_eq!(backend.changes_tries_storage.root(12).unwrap(), Some(root12)); + let root1 = read_changes_trie_root(&backend, 1); assert_eq!(backend.changes_tries_storage.root(&anchor, 1).unwrap(), Some(root1)); + let root2 = read_changes_trie_root(&backend, 2); assert_eq!(backend.changes_tries_storage.root(&anchor, 2).unwrap(), Some(root2)); + let root3 = read_changes_trie_root(&backend, 3); assert_eq!(backend.changes_tries_storage.root(&anchor, 3).unwrap(), Some(root3)); + let root4 = read_changes_trie_root(&backend, 4); assert_eq!(backend.changes_tries_storage.root(&anchor, 4).unwrap(), Some(root4)); + let root5 = read_changes_trie_root(&backend, 5); assert_eq!(backend.changes_tries_storage.root(&anchor, 5).unwrap(), Some(root5)); + let root6 = read_changes_trie_root(&backend, 6); assert_eq!(backend.changes_tries_storage.root(&anchor, 6).unwrap(), Some(root6)); + let root7 = read_changes_trie_root(&backend, 7); assert_eq!(backend.changes_tries_storage.root(&anchor, 7).unwrap(), Some(root7)); + let root8 = read_changes_trie_root(&backend, 8); assert_eq!(backend.changes_tries_storage.root(&anchor, 8).unwrap(), Some(root8)); + let root9 = read_changes_trie_root(&backend, 9); assert_eq!(backend.changes_tries_storage.root(&anchor, 9).unwrap(), Some(root9)); + let root10 = read_changes_trie_root(&backend, 10); assert_eq!(backend.changes_tries_storage.root(&anchor, 10).unwrap(), Some(root10)); + let root11 = read_changes_trie_root(&backend, 11); assert_eq!(backend.changes_tries_storage.root(&anchor, 11).unwrap(), Some(root11)); + let root12 = read_changes_trie_root(&backend, 12); assert_eq!(backend.changes_tries_storage.root(&anchor, 12).unwrap(), Some(root12)); // now simulate finalization of block#12, causing prune of tries at #1..#4 let mut tx = DBTransaction::new(); - backend.changes_tries_storage.prune(Some(config.clone()), &mut tx, 12); + backend.changes_tries_storage.prune(Some(config.clone()), &mut tx, Default::default(), 12); backend.storage.db.write(tx).unwrap(); assert!(backend.changes_tries_storage.get(&root1).unwrap().is_none()); assert!(backend.changes_tries_storage.get(&root2).unwrap().is_none()); @@ -1241,7 +1326,7 @@ mod tests { // now simulate finalization of block#16, causing prune of tries at #5..#8 let mut tx = DBTransaction::new(); - backend.changes_tries_storage.prune(Some(config.clone()), &mut tx, 16); + backend.changes_tries_storage.prune(Some(config.clone()), &mut tx, Default::default(), 16); backend.storage.db.write(tx).unwrap(); assert!(backend.changes_tries_storage.get(&root5).unwrap().is_none()); assert!(backend.changes_tries_storage.get(&root6).unwrap().is_none()); @@ -1252,7 +1337,7 @@ mod tests { // => no changes tries are pruned, because we never prune in archive mode backend.changes_tries_storage.min_blocks_to_keep = None; let mut tx = DBTransaction::new(); - backend.changes_tries_storage.prune(Some(config), &mut tx, 20); + backend.changes_tries_storage.prune(Some(config), &mut tx, Default::default(), 20); backend.storage.db.write(tx).unwrap(); assert!(backend.changes_tries_storage.get(&root9).unwrap().is_some()); assert!(backend.changes_tries_storage.get(&root10).unwrap().is_some()); diff --git a/core/client/db/src/utils.rs b/core/client/db/src/utils.rs index b32b56bccc859..85019ff7ba0a8 100644 --- a/core/client/db/src/utils.rs +++ b/core/client/db/src/utils.rs @@ -186,6 +186,17 @@ pub fn read_header( } } +/// Required header from the database. +pub fn require_header( + db: &KeyValueDB, + col_index: Option, + col: Option, + id: BlockId, +) -> client::error::Result { + read_header(db, col_index, col, id) + .and_then(|header| header.ok_or_else(|| client::error::ErrorKind::UnknownBlock(format!("{}", id)).into())) +} + /// Read meta from the database. pub fn read_meta(db: &KeyValueDB, col_meta: Option, col_header: Option) -> Result< Meta<<::Header as HeaderT>::Number, Block::Hash>, @@ -234,3 +245,11 @@ pub fn read_meta(db: &KeyValueDB, col_meta: Option, col_header: Opti genesis_hash, }) } + +/// Converts one hash type into another. +pub(crate) fn convert_hash, H2: AsRef<[u8]>>(src: &H2) -> H1 { + let mut dest = H1::default(); + let len = ::std::cmp::min(dest.as_mut().len(), src.as_ref().len()); + dest.as_mut().copy_from_slice(&src.as_ref()[..len]); + dest +} diff --git a/core/client/src/client.rs b/core/client/src/client.rs index 9182ab0a7800d..ff70f9e7f6c24 100644 --- a/core/client/src/client.rs +++ b/core/client/src/client.rs @@ -35,8 +35,8 @@ use primitives::storage::well_known_keys; use codec::{Encode, Decode}; use state_machine::{ Backend as StateBackend, CodeExecutor, - ExecutionStrategy, ExecutionManager, prove_read, - key_changes, key_changes_proof, OverlayedChanges + ExecutionStrategy, ExecutionManager, ChangesTrieAnchorBlockId, + prove_read, key_changes, key_changes_proof, OverlayedChanges }; use backend::{self, BlockImportOperation}; @@ -404,7 +404,10 @@ impl Client where config, storage, self.require_block_number_from_id(&BlockId::Hash(first))?.as_(), - self.require_block_number_from_id(&BlockId::Hash(last))?.as_(), + &ChangesTrieAnchorBlockId { + hash: convert_hash(&last), + number: self.require_block_number_from_id(&BlockId::Hash(last))?.as_(), + }, self.backend.blockchain().info()?.best_number.as_(), key) .map_err(|err| error::ErrorKind::ChangesTrieAccessFailed(err).into()) @@ -437,7 +440,10 @@ impl Client where config, storage, self.require_block_number_from_id(&BlockId::Hash(first))?.as_(), - self.require_block_number_from_id(&BlockId::Hash(last))?.as_(), + &ChangesTrieAnchorBlockId { + hash: convert_hash(&last), + number: self.require_block_number_from_id(&BlockId::Hash(last))?.as_(), + }, max_number.as_(), key) .map_err(|err| error::ErrorKind::ChangesTrieAccessFailed(err).into()) @@ -1198,6 +1204,14 @@ impl api::Miscellaneous for Client where } } +/// Converts one hash type into another. +pub(crate) fn convert_hash, H2: AsRef<[u8]>>(src: &H2) -> H1 { + let mut dest = H1::default(); + let len = ::std::cmp::min(dest.as_mut().len(), src.as_ref().len()); + dest.as_mut().copy_from_slice(&src.as_ref()[..len]); + dest +} + #[cfg(test)] pub(crate) mod tests { use std::collections::HashMap; diff --git a/core/client/src/light/fetcher.rs b/core/client/src/light/fetcher.rs index 702c9b703458f..6b1debf6a1aac 100644 --- a/core/client/src/light/fetcher.rs +++ b/core/client/src/light/fetcher.rs @@ -23,11 +23,12 @@ use hash_db::Hasher; use heapsize::HeapSizeOf; use primitives::ChangesTrieConfiguration; use runtime_primitives::traits::{As, Block as BlockT, Header as HeaderT, NumberFor}; -use state_machine::{CodeExecutor, ChangesTrieRootsStorage, read_proof_check, - key_changes_proof_check}; +use state_machine::{CodeExecutor, ChangesTrieRootsStorage, ChangesTrieAnchorBlockId, + read_proof_check, key_changes_proof_check}; use call_executor::CallResult; use cht; +use client::convert_hash; use error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult}; use light::call_executor::check_execution_proof; @@ -192,9 +193,8 @@ impl FetchChecker for LightDataChecker request: &RemoteReadRequest, remote_proof: Vec> ) -> ClientResult>> { - let mut root: H::Out = Default::default(); - root.as_mut().copy_from_slice(request.header.state_root().as_ref()); - read_proof_check::(root, remote_proof, &request.key).map_err(Into::into) + read_proof_check::(convert_hash(request.header.state_root()), remote_proof, &request.key) + .map_err(Into::into) } fn check_execution_proof( @@ -229,7 +229,10 @@ impl FetchChecker for LightDataChecker }, remote_proof, first_number, - request.last_block.0.as_(), + &ChangesTrieAnchorBlockId { + hash: convert_hash(&request.last_block.1), + number: request.last_block.0.as_(), + }, remote_max.as_(), &request.key) .map(|pairs| pairs.into_iter().map(|(b, x)| (As::sa(b), x)).collect()) @@ -248,15 +251,12 @@ impl<'a, H, Hash> ChangesTrieRootsStorage for RootsStorage<'a, Hash> H: Hasher, Hash: 'a + Send + Sync + Clone + AsRef<[u8]>, { - fn root(&self, block: u64) -> Result, String> { + fn root(&self, _anchor: &ChangesTrieAnchorBlockId, block: u64) -> Result, String> { + // we can't ask for roots from parallel forks here => ignore anchor Ok(block.checked_sub(self.first) .and_then(|index| self.roots.get(index as usize)) .cloned() - .map(|root| { - let mut hasher_root: H::Out = Default::default(); - hasher_root.as_mut().copy_from_slice(root.as_ref()); - hasher_root - })) + .map(|root| convert_hash(&root))) } } diff --git a/core/executor/src/wasm_executor.rs b/core/executor/src/wasm_executor.rs index 2d626bf1cfe4f..f1ab971281eec 100644 --- a/core/executor/src/wasm_executor.rs +++ b/core/executor/src/wasm_executor.rs @@ -29,7 +29,7 @@ use wasm_utils::UserError; use primitives::{blake2_256, twox_128, twox_256, ed25519}; use primitives::hexdisplay::HexDisplay; use primitives::sandbox as sandbox_primitives; -use primitives::Blake2Hasher; +use primitives::{H256, Blake2Hasher}; use trie::ordered_trie_root; use sandbox; @@ -264,8 +264,15 @@ impl_function_executor!(this: FunctionExecutor<'e, E>, this.memory.set(result, r.as_ref()).map_err(|_| UserError("Invalid attempt to set memory in ext_storage_root"))?; Ok(()) }, - ext_storage_changes_root(block: u64, result: *mut u8) -> u32 => { - let r = this.ext.storage_changes_root(block); + ext_storage_changes_root(parent_hash_data: *const u8, parent_hash_len: u32, parent_number: u64, result: *mut u8) -> u32 => { + let mut parent_hash = H256::default(); + if parent_hash_len != parent_hash.len() as u32 { + return Err(UserError("Invalid parent_hash_len in ext_storage_changes_root").into()); + } + let raw_parent_hash = this.memory.get(parent_hash_data, parent_hash_len as usize) + .map_err(|_| UserError("Invalid attempt to get parent_hash in ext_storage_changes_root"))?; + parent_hash.copy_from_slice(&raw_parent_hash[..]); + let r = this.ext.storage_changes_root(parent_hash, parent_number); if let Some(ref r) = r { this.memory.set(result, &r[..]).map_err(|_| UserError("Invalid attempt to set memory in ext_storage_changes_root"))?; } diff --git a/core/sr-io/with_std.rs b/core/sr-io/with_std.rs index 0522dd23ac974..3baa27a752552 100644 --- a/core/sr-io/with_std.rs +++ b/core/sr-io/with_std.rs @@ -103,9 +103,9 @@ pub fn storage_root() -> H256 { } /// "Commit" all existing operations and get the resultant storage change root. -pub fn storage_changes_root(block: u64) -> Option { +pub fn storage_changes_root(parent_hash: [u8; 32], parent_num: u64) -> Option { ext::with(|ext| - ext.storage_changes_root(block) + ext.storage_changes_root(parent_hash.into(), parent_num) ).unwrap_or(None) } diff --git a/core/sr-io/without_std.rs b/core/sr-io/without_std.rs index db2a1d35d8def..61df6604c5232 100644 --- a/core/sr-io/without_std.rs +++ b/core/sr-io/without_std.rs @@ -64,7 +64,7 @@ extern "C" { fn ext_get_allocated_storage(key_data: *const u8, key_len: u32, written_out: *mut u32) -> *mut u8; fn ext_get_storage_into(key_data: *const u8, key_len: u32, value_data: *mut u8, value_len: u32, value_offset: u32) -> u32; fn ext_storage_root(result: *mut u8); - fn ext_storage_changes_root(block: u64, result: *mut u8) -> u32; + fn ext_storage_changes_root(parent_hash_data: *const u8, parent_hash_len: u32, parent_num: u64, result: *mut u8) -> u32; fn ext_blake2_256_enumerated_trie_root(values_data: *const u8, lens_data: *const u32, lens_len: u32, result: *mut u8); fn ext_chain_id() -> u64; fn ext_blake2_256(data: *const u8, len: u32, out: *mut u8); @@ -172,10 +172,10 @@ pub fn storage_root() -> [u8; 32] { } /// The current storage' changes root. -pub fn storage_changes_root(block: u64) -> Option<[u8; 32]> { +pub fn storage_changes_root(parent_hash: [u8; 32], parent_num: u64) -> Option<[u8; 32]> { let mut result: [u8; 32] = Default::default(); let is_set = unsafe { - ext_storage_changes_root(block, result.as_mut_ptr()) + ext_storage_changes_root(parent_hash.as_ptr(), parent_hash.len() as u32, parent_num, result.as_mut_ptr()) }; if is_set != 0 { diff --git a/core/sr-primitives/src/traits.rs b/core/sr-primitives/src/traits.rs index 2841708e6e87b..c67e17c5b652e 100644 --- a/core/sr-primitives/src/traits.rs +++ b/core/sr-primitives/src/traits.rs @@ -274,7 +274,7 @@ pub trait Hash: 'static + MaybeSerializeDebug + Clone + Eq + PartialEq { // Stup fn storage_root() -> Self::Output; /// Acquire the global storage changes root. - fn storage_changes_root(block: u64) -> Option; + fn storage_changes_root(parent_hash: Self::Output, parent_number: u64) -> Option; } /// Blake2-256 Hash implementation. @@ -306,8 +306,8 @@ impl Hash for BlakeTwo256 { fn storage_root() -> Self::Output { runtime_io::storage_root().into() } - fn storage_changes_root(block: u64) -> Option { - runtime_io::storage_changes_root(block).map(Into::into) + fn storage_changes_root(parent_hash: Self::Output, parent_number: u64) -> Option { + runtime_io::storage_changes_root(parent_hash.into(), parent_number).map(Into::into) } } diff --git a/core/state-machine/src/changes_trie/build.rs b/core/state-machine/src/changes_trie/build.rs index 8c317d760e919..0ff39c75eb77c 100644 --- a/core/state-machine/src/changes_trie/build.rs +++ b/core/state-machine/src/changes_trie/build.rs @@ -25,7 +25,7 @@ use overlayed_changes::OverlayedChanges; use trie_backend_essence::{TrieBackendStorage, TrieBackendEssence}; use changes_trie::build_iterator::digest_build_iterator; use changes_trie::input::{InputKey, InputPair, DigestIndex, ExtrinsicIndex}; -use changes_trie::{Configuration, Storage}; +use changes_trie::{AnchorBlockId, Configuration, Storage}; /// Prepare input pairs for building a changes trie of given block. /// @@ -37,7 +37,7 @@ pub fn prepare_input<'a, B, S, H>( backend: &B, storage: Option<&'a S>, changes: &OverlayedChanges, - block: u64, + parent: &'a AnchorBlockId, ) -> Result>, String> where B: Backend, @@ -54,10 +54,10 @@ pub fn prepare_input<'a, B, S, H>( let mut input = Vec::new(); input.extend(prepare_extrinsics_input( backend, - block, + parent.number + 1, changes)?); input.extend(prepare_digest_input::<_, H>( - block, + parent, config, storage)?); @@ -73,7 +73,6 @@ fn prepare_extrinsics_input( where B: Backend, H: Hasher, - { let mut extrinsic_map = BTreeMap::, BTreeSet>::new(); for (key, val) in changes.prospective.iter().chain(changes.committed.iter()) { @@ -103,19 +102,19 @@ fn prepare_extrinsics_input( /// Prepare DigestIndex input pairs. fn prepare_digest_input<'a, S, H>( - block: u64, + parent: &'a AnchorBlockId, config: &Configuration, storage: &'a S -) -> Result, String> +) -> Result + 'a, String> where S: Storage, &'a S: TrieBackendStorage, H: Hasher, - H::Out: HeapSizeOf, + H::Out: 'a + HeapSizeOf, { let mut digest_map = BTreeMap::, BTreeSet>::new(); - for digest_build_block in digest_build_iterator(config, block) { - let trie_root = storage.root(digest_build_block)?; + for digest_build_block in digest_build_iterator(config, parent.number + 1) { + let trie_root = storage.root(parent, digest_build_block)?; let trie_root = trie_root.ok_or_else(|| format!("No changes trie root for block {}", digest_build_block))?; let trie_storage = TrieBackendEssence::<_, H>::new(storage, trie_root); @@ -136,7 +135,7 @@ fn prepare_digest_input<'a, S, H>( Ok(digest_map.into_iter() .map(move |(key, set)| InputPair::DigestIndex(DigestIndex { - block, + block: parent.number + 1, key }, set.into_iter().collect()))) } @@ -228,7 +227,7 @@ mod test { #[test] fn build_changes_trie_nodes_on_non_digest_block() { let (backend, storage, changes) = prepare_for_build(); - let changes_trie_nodes = prepare_input(&backend, Some(&storage), &changes, 5).unwrap(); + let changes_trie_nodes = prepare_input(&backend, Some(&storage), &changes, &AnchorBlockId { hash: Default::default(), number: 4 }).unwrap(); assert_eq!(changes_trie_nodes, Some(vec![ InputPair::ExtrinsicIndex(ExtrinsicIndex { block: 5, key: vec![100] }, vec![0, 2, 3]), InputPair::ExtrinsicIndex(ExtrinsicIndex { block: 5, key: vec![101] }, vec![1]), @@ -239,7 +238,7 @@ mod test { #[test] fn build_changes_trie_nodes_on_digest_block_l1() { let (backend, storage, changes) = prepare_for_build(); - let changes_trie_nodes = prepare_input(&backend, Some(&storage), &changes, 4).unwrap(); + let changes_trie_nodes = prepare_input(&backend, Some(&storage), &changes, &AnchorBlockId { hash: Default::default(), number: 3 }).unwrap(); assert_eq!(changes_trie_nodes, Some(vec![ InputPair::ExtrinsicIndex(ExtrinsicIndex { block: 4, key: vec![100] }, vec![0, 2, 3]), InputPair::ExtrinsicIndex(ExtrinsicIndex { block: 4, key: vec![101] }, vec![1]), @@ -255,7 +254,7 @@ mod test { #[test] fn build_changes_trie_nodes_on_digest_block_l2() { let (backend, storage, changes) = prepare_for_build(); - let changes_trie_nodes = prepare_input(&backend, Some(&storage), &changes, 16).unwrap(); + let changes_trie_nodes = prepare_input(&backend, Some(&storage), &changes, &AnchorBlockId { hash: Default::default(), number: 15 }).unwrap(); assert_eq!(changes_trie_nodes, Some(vec![ InputPair::ExtrinsicIndex(ExtrinsicIndex { block: 16, key: vec![100] }, vec![0, 2, 3]), InputPair::ExtrinsicIndex(ExtrinsicIndex { block: 16, key: vec![101] }, vec![1]), @@ -279,7 +278,7 @@ mod test { extrinsics: Some(vec![1].into_iter().collect()) }); - let changes_trie_nodes = prepare_input(&backend, Some(&storage), &changes, 4).unwrap(); + let changes_trie_nodes = prepare_input(&backend, Some(&storage), &changes, &AnchorBlockId { hash: Default::default(), number: 3 }).unwrap(); assert_eq!(changes_trie_nodes, Some(vec![ InputPair::ExtrinsicIndex(ExtrinsicIndex { block: 4, key: vec![100] }, vec![0, 2, 3]), InputPair::ExtrinsicIndex(ExtrinsicIndex { block: 4, key: vec![101] }, vec![1]), diff --git a/core/state-machine/src/changes_trie/changes_iterator.rs b/core/state-machine/src/changes_trie/changes_iterator.rs index 8b4d2bd54070b..e8bd1e7db6b1a 100644 --- a/core/state-machine/src/changes_trie/changes_iterator.rs +++ b/core/state-machine/src/changes_trie/changes_iterator.rs @@ -23,7 +23,7 @@ use codec::{Decode, Encode}; use hash_db::{HashDB, Hasher}; use heapsize::HeapSizeOf; use substrate_trie::{Recorder, MemoryDB}; -use changes_trie::{Configuration, RootsStorage, Storage}; +use changes_trie::{AnchorBlockId, Configuration, RootsStorage, Storage}; use changes_trie::input::{DigestIndex, ExtrinsicIndex, DigestIndexValue, ExtrinsicIndexValue}; use changes_trie::storage::{TrieBackendAdapter, InMemoryStorage}; use proving_backend::ProvingBackendEssence; @@ -35,7 +35,7 @@ pub fn key_changes, H: Hasher>( config: &Configuration, storage: &S, begin: u64, - end: u64, + end: &AnchorBlockId, max: u64, key: &[u8], ) -> Result, String> where H::Out: HeapSizeOf { @@ -46,7 +46,7 @@ pub fn key_changes, H: Hasher>( storage, begin, end, - surface: surface_iterator(config, max, begin, end)?, + surface: surface_iterator(config, max, begin, end.number)?, extrinsics: Default::default(), blocks: Default::default(), @@ -62,7 +62,7 @@ pub fn key_changes_proof, H: Hasher>( config: &Configuration, storage: &S, begin: u64, - end: u64, + end: &AnchorBlockId, max: u64, key: &[u8], ) -> Result>, String> where H::Out: HeapSizeOf { @@ -73,7 +73,7 @@ pub fn key_changes_proof, H: Hasher>( storage, begin, end, - surface: surface_iterator(config, max, begin, end)?, + surface: surface_iterator(config, max, begin, end.number)?, extrinsics: Default::default(), blocks: Default::default(), @@ -98,7 +98,7 @@ pub fn key_changes_proof_check, H: Hasher>( roots_storage: &S, proof: Vec>, begin: u64, - end: u64, + end: &AnchorBlockId, max: u64, key: &[u8] ) -> Result, String> where H::Out: HeapSizeOf { @@ -115,7 +115,7 @@ pub fn key_changes_proof_check, H: Hasher>( storage: &proof_db, begin, end, - surface: surface_iterator(config, max, begin, end)?, + surface: surface_iterator(config, max, begin, end.number)?, extrinsics: Default::default(), blocks: Default::default(), @@ -174,12 +174,12 @@ impl<'a> Iterator for SurfaceIterator<'a> { /// Drilldown iterator - receives 'digest points' from surface iterator and explores /// every point until extrinsic is found. -pub struct DrilldownIteratorEssence<'a, RS: 'a + RootsStorage, S: 'a + Storage, H: Hasher> { +pub struct DrilldownIteratorEssence<'a, RS: 'a + RootsStorage, S: 'a + Storage, H: Hasher> where H::Out: 'a { key: &'a [u8], roots_storage: &'a RS, storage: &'a S, begin: u64, - end: u64, + end: &'a AnchorBlockId, surface: SurfaceIterator<'a>, extrinsics: VecDeque<(u64, u32)>, @@ -213,14 +213,14 @@ impl<'a, RS: 'a + RootsStorage, S: Storage, H: Hasher> DrilldownIteratorEs // not having a changes trie root is an error because: // we never query roots for future blocks // AND trie roots for old blocks are known (both on full + light node) - let trie_root = self.roots_storage.root(block)? + let trie_root = self.roots_storage.root(&self.end, block)? .ok_or_else(|| format!("Changes trie root for block {} is not found", block))?; // only return extrinsics for blocks before self.max // most of blocks will be filtered out beore pushing to `self.blocks` // here we just throwing away changes at digest blocks we're processing debug_assert!(block >= self.begin, "We shall not touch digests earlier than a range' begin"); - if block <= self.end { + if block <= self.end.number { let extrinsics_key = ExtrinsicIndex { block, key: self.key.to_vec() }.encode(); let extrinsics = trie_reader(&self.storage, trie_root, &extrinsics_key); if let Some(extrinsics) = extrinsics? { @@ -239,7 +239,7 @@ impl<'a, RS: 'a + RootsStorage, S: Storage, H: Hasher> DrilldownIteratorEs // filter level0 blocks here because we tend to use digest blocks, // AND digest block changes could also include changes for out-of-range blocks let begin = self.begin; - let end = self.end; + let end = self.end.number; self.blocks.extend(blocks.into_iter() .rev() .filter(|b| level > 1 || (*b >= begin && *b <= end)) @@ -261,7 +261,7 @@ impl<'a, RS: 'a + RootsStorage, S: Storage, H: Hasher> DrilldownIteratorEs } /// Exploring drilldown operator. -struct DrilldownIterator<'a, RS: 'a + RootsStorage, S: 'a + Storage, H: Hasher> { +struct DrilldownIterator<'a, RS: 'a + RootsStorage, S: 'a + Storage, H: Hasher> where H::Out: 'a { essence: DrilldownIteratorEssence<'a, RS, S, H>, } @@ -278,7 +278,7 @@ impl<'a, RS: 'a + RootsStorage, S: Storage, H: Hasher> Iterator } /// Proving drilldown iterator. -struct ProvingDrilldownIterator<'a, RS: 'a + RootsStorage, S: 'a + Storage, H: Hasher> { +struct ProvingDrilldownIterator<'a, RS: 'a + RootsStorage, S: 'a + Storage, H: Hasher> where H::Out: 'a { essence: DrilldownIteratorEssence<'a, RS, S, H>, proof_recorder: RefCell>, } @@ -427,23 +427,23 @@ mod tests { fn drilldown_iterator_works() { let (config, storage) = prepare_for_drilldown(); let drilldown_result = key_changes::, Blake2Hasher>( - &config, &storage, 0, 16, 16, &[42]); + &config, &storage, 0, &AnchorBlockId { hash: Default::default(), number: 16 }, 16, &[42]); assert_eq!(drilldown_result, Ok(vec![(8, 2), (8, 1), (6, 3), (3, 0)])); let drilldown_result = key_changes::, Blake2Hasher>( - &config, &storage, 0, 2, 4, &[42]); + &config, &storage, 0, &AnchorBlockId { hash: Default::default(), number: 2 }, 4, &[42]); assert_eq!(drilldown_result, Ok(vec![])); let drilldown_result = key_changes::, Blake2Hasher>( - &config, &storage, 0, 3, 4, &[42]); + &config, &storage, 0, &AnchorBlockId { hash: Default::default(), number: 3 }, 4, &[42]); assert_eq!(drilldown_result, Ok(vec![(3, 0)])); let drilldown_result = key_changes::, Blake2Hasher>( - &config, &storage, 7, 8, 8, &[42]); + &config, &storage, 7, &AnchorBlockId { hash: Default::default(), number: 8 }, 8, &[42]); assert_eq!(drilldown_result, Ok(vec![(8, 2), (8, 1)])); let drilldown_result = key_changes::, Blake2Hasher>( - &config, &storage, 5, 7, 8, &[42]); + &config, &storage, 5, &AnchorBlockId { hash: Default::default(), number: 7 }, 8, &[42]); assert_eq!(drilldown_result, Ok(vec![(6, 3)])); } @@ -453,16 +453,16 @@ mod tests { storage.clear_storage(); assert!(key_changes::, Blake2Hasher>( - &config, &storage, 0, 100, 1000, &[42]).is_err()); + &config, &storage, 0, &AnchorBlockId { hash: Default::default(), number: 100 }, 1000, &[42]).is_err()); } #[test] fn drilldown_iterator_fails_when_range_is_invalid() { let (config, storage) = prepare_for_drilldown(); assert!(key_changes::, Blake2Hasher>( - &config, &storage, 0, 100, 50, &[42]).is_err()); + &config, &storage, 0, &AnchorBlockId { hash: Default::default(), number: 100 }, 50, &[42]).is_err()); assert!(key_changes::, Blake2Hasher>( - &config, &storage, 20, 10, 100, &[42]).is_err()); + &config, &storage, 20, &AnchorBlockId { hash: Default::default(), number: 10 }, 100, &[42]).is_err()); } @@ -474,7 +474,7 @@ mod tests { let (remote_config, remote_storage) = prepare_for_drilldown(); let remote_proof = key_changes_proof::, Blake2Hasher>( &remote_config, &remote_storage, - 0, 16, 16, &[42]).unwrap(); + 0, &AnchorBlockId { hash: Default::default(), number: 16 }, 16, &[42]).unwrap(); // happens on local light node: @@ -483,7 +483,7 @@ mod tests { local_storage.clear_storage(); let local_result = key_changes_proof_check::, Blake2Hasher>( &local_config, &local_storage, remote_proof, - 0, 16, 16, &[42]); + 0, &AnchorBlockId { hash: Default::default(), number: 16 }, 16, &[42]); // check that drilldown result is the same as if it was happening at the full node assert_eq!(local_result, Ok(vec![(8, 2), (8, 1), (6, 3), (3, 0)])); diff --git a/core/state-machine/src/changes_trie/mod.rs b/core/state-machine/src/changes_trie/mod.rs index 80f3bd55983da..4884336e685bb 100644 --- a/core/state-machine/src/changes_trie/mod.rs +++ b/core/state-machine/src/changes_trie/mod.rs @@ -55,10 +55,20 @@ use trie::{DBValue, trie_root}; /// Changes that are made outside of extrinsics are marked with this index; pub const NO_EXTRINSIC_INDEX: u32 = 0xffffffff; +/// Block identifier that could be used to determine fork of this block. +#[derive(Debug)] +pub struct AnchorBlockId { + /// Hash of this block. + pub hash: Hash, + /// Number of this block. + pub number: u64, +} + /// Changes trie storage. Provides access to trie roots and trie nodes. pub trait RootsStorage: Send + Sync { - /// Get changes trie root for given block. - fn root(&self, block: u64) -> Result, String>; + /// Get changes trie root for the block with given number which is an ancestor (or the block + /// itself) of the anchor_block (i.e. anchor_block.number >= block). + fn root(&self, anchor: &AnchorBlockId, block: u64) -> Result, String>; } /// Changes trie storage. Provides access to trie roots and trie nodes. @@ -76,13 +86,13 @@ pub fn compute_changes_trie_root<'a, B: Backend, S: Storage, H: Hasher>( backend: &B, storage: Option<&'a S>, changes: &OverlayedChanges, - block: u64, + parent: &'a AnchorBlockId, ) -> Option<(H::Out, Vec<(Vec, Vec)>)> where &'a S: TrieBackendStorage, H::Out: Ord + HeapSizeOf, { - let input_pairs = prepare_input::(backend, storage, changes, block) + let input_pairs = prepare_input::(backend, storage, changes, parent) .expect("storage is not allowed to fail within runtime")?; let transaction = input_pairs.into_iter() .map(Into::into) diff --git a/core/state-machine/src/changes_trie/prune.rs b/core/state-machine/src/changes_trie/prune.rs index 8168a0771d315..70bae77b0273f 100644 --- a/core/state-machine/src/changes_trie/prune.rs +++ b/core/state-machine/src/changes_trie/prune.rs @@ -21,7 +21,7 @@ use heapsize::HeapSizeOf; use substrate_trie::Recorder; use proving_backend::ProvingBackendEssence; use trie_backend_essence::TrieBackendEssence; -use changes_trie::{Configuration, Storage}; +use changes_trie::{AnchorBlockId, Configuration, Storage}; use changes_trie::storage::TrieBackendAdapter; /// Prune obslete changes tries. Puning happens at the same block, where highest @@ -33,21 +33,21 @@ pub fn prune, H: Hasher, F: FnMut(H::Out)>( config: &Configuration, storage: &S, min_blocks_to_keep: u64, - current_block: u64, + current_block: &AnchorBlockId, mut remove_trie_node: F, ) where H::Out: HeapSizeOf, { // we only CAN prune at block where max-level-digest is created - let digest_interval = match config.digest_level_at_block(current_block) { + let digest_interval = match config.digest_level_at_block(current_block.number) { Some((digest_level, digest_interval, _)) if digest_level == config.digest_levels => digest_interval, _ => return, }; // select range for pruning - let (first, last) = match pruning_range(min_blocks_to_keep, current_block, digest_interval) { + let (first, last) = match pruning_range(min_blocks_to_keep, current_block.number, digest_interval) { Some((first, last)) => (first, last), None => return, }; @@ -55,7 +55,7 @@ pub fn prune, H: Hasher, F: FnMut(H::Out)>( // delete changes trie for every block in range // TODO: limit `max_digest_interval` so that this cycle won't involve huge ranges for block in first..last+1 { - let root = match storage.root(block) { + let root = match storage.root(current_block, block) { Ok(Some(root)) => root, Ok(None) => continue, Err(error) => { @@ -139,7 +139,7 @@ mod tests { H::Out: HeapSizeOf, { let mut pruned_trie_nodes = HashSet::new(); - prune(config, storage, min_blocks_to_keep, current_block, + prune(config, storage, min_blocks_to_keep, &AnchorBlockId { hash: Default::default(), number: current_block }, |node| { pruned_trie_nodes.insert(node); }); pruned_trie_nodes } diff --git a/core/state-machine/src/changes_trie/storage.rs b/core/state-machine/src/changes_trie/storage.rs index 1cdd03841e1cf..088b605e9cc16 100644 --- a/core/state-machine/src/changes_trie/storage.rs +++ b/core/state-machine/src/changes_trie/storage.rs @@ -22,7 +22,7 @@ use trie::DBValue; use heapsize::HeapSizeOf; use trie::MemoryDB; use parking_lot::RwLock; -use changes_trie::{RootsStorage, Storage}; +use changes_trie::{AnchorBlockId, RootsStorage, Storage}; use trie_backend_essence::TrieBackendStorage; #[cfg(test)] @@ -110,7 +110,7 @@ impl InMemoryStorage where H::Out: HeapSizeOf { } impl RootsStorage for InMemoryStorage where H::Out: HeapSizeOf { - fn root(&self, block: u64) -> Result, String> { + fn root(&self, _anchor_block: &AnchorBlockId, block: u64) -> Result, String> { Ok(self.data.read().roots.get(&block).cloned()) } } diff --git a/core/state-machine/src/ext.rs b/core/state-machine/src/ext.rs index b0fc7347d3e50..8e66da066bd3a 100644 --- a/core/state-machine/src/ext.rs +++ b/core/state-machine/src/ext.rs @@ -18,7 +18,7 @@ use std::{error, fmt, cmp::Ord}; use backend::Backend; -use changes_trie::{Storage as ChangesTrieStorage, compute_changes_trie_root}; +use changes_trie::{AnchorBlockId, Storage as ChangesTrieStorage, compute_changes_trie_root}; use {Externalities, OverlayedChanges}; use hash_db::Hasher; use substrate_trie::{MemoryDB, TrieDBMut, TrieMut}; @@ -196,12 +196,12 @@ where root } - fn storage_changes_root(&mut self, block: u64) -> Option { + fn storage_changes_root(&mut self, parent: H::Out, parent_num: u64) -> Option { let root_and_tx = compute_changes_trie_root::<_, T, H>( self.backend, self.changes_trie_storage.clone(), self.overlay, - block, + &AnchorBlockId { hash: parent, number: parent_num }, ); let root_and_tx = root_and_tx.map(|(root, changes)| { let mut calculated_root = Default::default(); @@ -213,7 +213,7 @@ where } } - (block, mdb, root) + (parent_num + 1, mdb, root) }); let root = root_and_tx.as_ref().map(|(_, _, root)| root.clone()); self.changes_trie_transaction = root_and_tx; @@ -261,7 +261,7 @@ mod tests { let mut overlay = prepare_overlay_with_changes(); let backend = TestBackend::default(); let mut ext = TestExt::new(&mut overlay, &backend, None); - assert_eq!(ext.storage_changes_root(100), None); + assert_eq!(ext.storage_changes_root(Default::default(), 100), None); } #[test] @@ -271,7 +271,7 @@ mod tests { let storage = TestChangesTrieStorage::new(); let backend = TestBackend::default(); let mut ext = TestExt::new(&mut overlay, &backend, Some(&storage)); - assert_eq!(ext.storage_changes_root(100), None); + assert_eq!(ext.storage_changes_root(Default::default(), 100), None); } #[test] @@ -280,7 +280,7 @@ mod tests { let storage = TestChangesTrieStorage::new(); let backend = TestBackend::default(); let mut ext = TestExt::new(&mut overlay, &backend, Some(&storage)); - assert_eq!(ext.storage_changes_root(100), + assert_eq!(ext.storage_changes_root(Default::default(), 99), Some(hex!("5b829920b9c8d554a19ee2a1ba593c4f2ee6fc32822d083e04236d693e8358d5").into())); } @@ -291,7 +291,7 @@ mod tests { let storage = TestChangesTrieStorage::new(); let backend = TestBackend::default(); let mut ext = TestExt::new(&mut overlay, &backend, Some(&storage)); - assert_eq!(ext.storage_changes_root(100), + assert_eq!(ext.storage_changes_root(Default::default(), 99), Some(hex!("bcf494e41e29a15c9ae5caa053fe3cb8b446ee3e02a254efbdec7a19235b76e4").into())); } } diff --git a/core/state-machine/src/lib.rs b/core/state-machine/src/lib.rs index 50a8a83e6f34d..f42e6fb439c21 100644 --- a/core/state-machine/src/lib.rs +++ b/core/state-machine/src/lib.rs @@ -55,7 +55,9 @@ pub use trie::{TrieMut, TrieDBMut, DBValue, MemoryDB}; pub use testing::TestExternalities; pub use ext::Ext; pub use backend::Backend; -pub use changes_trie::{Storage as ChangesTrieStorage, +pub use changes_trie::{ + AnchorBlockId as ChangesTrieAnchorBlockId, + Storage as ChangesTrieStorage, RootsStorage as ChangesTrieRootsStorage, InMemoryStorage as InMemoryChangesTrieStorage, key_changes, key_changes_proof, key_changes_proof_check, @@ -124,8 +126,8 @@ pub trait Externalities { /// Get the trie root of the current storage map. fn storage_root(&mut self) -> H::Out where H::Out: Ord; - /// Get the change trie root of the current storage overlay at given block. - fn storage_changes_root(&mut self, block: u64) -> Option where H::Out: Ord; + /// Get the change trie root of the current storage overlay at a block wth given parent. + fn storage_changes_root(&mut self, parent: H::Out, parent_num: u64) -> Option where H::Out: Ord; } /// Code execution engine. diff --git a/core/state-machine/src/testing.rs b/core/state-machine/src/testing.rs index c26c31dda8c7f..39ce94f7b43c3 100644 --- a/core/state-machine/src/testing.rs +++ b/core/state-machine/src/testing.rs @@ -22,7 +22,7 @@ use hash_db::Hasher; use heapsize::HeapSizeOf; use trie::trie_root; use backend::InMemory; -use changes_trie::{compute_changes_trie_root, InMemoryStorage as ChangesTrieInMemoryStorage}; +use changes_trie::{compute_changes_trie_root, InMemoryStorage as ChangesTrieInMemoryStorage, AnchorBlockId}; use primitives::storage::well_known_keys::CHANGES_TRIE_CONFIG; use super::{Externalities, OverlayedChanges}; @@ -122,12 +122,12 @@ impl Externalities for TestExternalities where H::Out: Ord + He trie_root::(self.inner.clone()) } - fn storage_changes_root(&mut self, block: u64) -> Option { + fn storage_changes_root(&mut self, parent: H::Out, parent_num: u64) -> Option { compute_changes_trie_root::<_, _, H>( &InMemory::default(), Some(&self.changes_trie_storage), &self.changes, - block, + &AnchorBlockId { hash: parent, number: parent_num }, ).map(|(root, _)| root.clone()) } } diff --git a/core/test-runtime/src/system.rs b/core/test-runtime/src/system.rs index 6bc70368a1be0..c08db06f85be3 100644 --- a/core/test-runtime/src/system.rs +++ b/core/test-runtime/src/system.rs @@ -93,7 +93,7 @@ pub fn execute_block(block: Block) { // check digest let mut digest = Digest::default(); - if let Some(storage_changes_root) = storage_changes_root(header.number) { + if let Some(storage_changes_root) = storage_changes_root(header.parent_hash.into(), header.number - 1) { digest.push(generic::DigestItem::ChangesTrieRoot::(storage_changes_root.into())); } assert!(digest == header.digest, "Header digest items must match that calculated."); @@ -160,7 +160,7 @@ pub fn finalise_block() -> Header { let number = ::take(); let parent_hash = ::take(); let storage_root = BlakeTwo256::storage_root(); - let storage_changes_root = BlakeTwo256::storage_changes_root(number); + let storage_changes_root = BlakeTwo256::storage_changes_root(parent_hash, number - 1); let mut digest = Digest::default(); if let Some(storage_changes_root) = storage_changes_root { diff --git a/node/executor/src/lib.rs b/node/executor/src/lib.rs index 6a2b8061c3735..6c9620014c036 100644 --- a/node/executor/src/lib.rs +++ b/node/executor/src/lib.rs @@ -730,7 +730,7 @@ mod tests { let mut t = new_test_ext(true); Executor::new().call(&mut t, 8, COMPACT_CODE, "execute_block", &block1(true).0, true).0.unwrap(); - assert!(t.storage_changes_root(1).is_some()); + assert!(t.storage_changes_root(Default::default(), 0).is_some()); } #[test] @@ -738,6 +738,6 @@ mod tests { let mut t = new_test_ext(true); WasmExecutor::new().call(&mut t, 8, COMPACT_CODE, "execute_block", &block1(true).0).unwrap(); - assert!(t.storage_changes_root(1).is_some()); + assert!(t.storage_changes_root(Default::default(), 0).is_some()); } } diff --git a/srml/system/src/lib.rs b/srml/system/src/lib.rs index ad8957c3491d9..9d3237f0da3f6 100644 --- a/srml/system/src/lib.rs +++ b/srml/system/src/lib.rs @@ -278,7 +278,7 @@ impl Module { let mut digest = >::take(); let extrinsics_root = >::take(); let storage_root = T::Hashing::storage_root(); - let storage_changes_root = T::Hashing::storage_changes_root(number.as_()); + let storage_changes_root = T::Hashing::storage_changes_root(parent_hash, number.as_() - 1); // we can't compute changes trie root earlier && put it to the Digest // because it will include all currently existing temporaries From 69c93c1b6ad9eca14498b69bd6be6523b32de1ec Mon Sep 17 00:00:00 2001 From: Svyatoslav Nikolsky Date: Tue, 6 Nov 2018 10:05:59 +0300 Subject: [PATCH 2/3] moved convert_hash to primitives --- core/client/db/src/lib.rs | 6 +++--- core/client/db/src/utils.rs | 8 -------- core/client/src/cht.rs | 5 ++--- core/client/src/client.rs | 10 +--------- core/client/src/light/call_executor.rs | 4 ++-- core/client/src/light/fetcher.rs | 3 +-- core/primitives/src/hash.rs | 10 ++++++++++ core/primitives/src/lib.rs | 2 +- .../substrate_test_runtime.compact.wasm | Bin 227553 -> 227513 bytes .../release/node_runtime.compact.wasm | Bin 829859 -> 829773 bytes 10 files changed, 20 insertions(+), 28 deletions(-) diff --git a/core/client/db/src/lib.rs b/core/client/db/src/lib.rs index 894337c57a575..82cf9ebcf6cc3 100644 --- a/core/client/db/src/lib.rs +++ b/core/client/db/src/lib.rs @@ -67,7 +67,7 @@ use hash_db::Hasher; use kvdb::{KeyValueDB, DBTransaction}; use trie::MemoryDB; use parking_lot::RwLock; -use primitives::{H256, AuthorityId, Blake2Hasher, ChangesTrieConfiguration}; +use primitives::{H256, AuthorityId, Blake2Hasher, ChangesTrieConfiguration, convert_hash}; use primitives::storage::well_known_keys; use runtime_primitives::{generic::BlockId, Justification, StorageMap, ChildrenStorageMap}; use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, As, NumberFor, Zero, Digest, DigestItem}; @@ -423,7 +423,7 @@ impl DbChangesTrieStorage { &*self, min_blocks_to_keep, &state_machine::ChangesTrieAnchorBlockId { - hash: utils::convert_hash(&block_hash), + hash: convert_hash(&block_hash), number: block_num.as_(), }, |node| tx.delete(columns::CHANGES_TRIE, node.as_ref())); @@ -443,7 +443,7 @@ impl state_machine::ChangesTrieRootsStorage for DbC // if block is not finalized yet, we should find the required block hash by traversing // back from the anchor to the block with given number let mut current_num = anchor.number; - let mut current_hash: Block::Hash = ::utils::convert_hash(&anchor.hash); + let mut current_hash: Block::Hash = convert_hash(&anchor.hash); while current_num != block { let current_header: Block::Header = ::utils::require_header::( &*self.db, columns::HASH_LOOKUP, columns::HEADER, BlockId::Hash(current_hash) diff --git a/core/client/db/src/utils.rs b/core/client/db/src/utils.rs index 6a166b4e9170a..5caebd7e72ec5 100644 --- a/core/client/db/src/utils.rs +++ b/core/client/db/src/utils.rs @@ -246,11 +246,3 @@ pub fn read_meta(db: &KeyValueDB, col_meta: Option, col_header: Opti genesis_hash, }) } - -/// Converts one hash type into another. -pub(crate) fn convert_hash, H2: AsRef<[u8]>>(src: &H2) -> H1 { - let mut dest = H1::default(); - let len = ::std::cmp::min(dest.as_mut().len(), src.as_ref().len()); - dest.as_mut().copy_from_slice(&src.as_ref()[..len]); - dest -} diff --git a/core/client/src/cht.rs b/core/client/src/cht.rs index fd8e07c3dd4bc..39e1aaa95bd25 100644 --- a/core/client/src/cht.rs +++ b/core/client/src/cht.rs @@ -27,7 +27,7 @@ use hash_db; use heapsize::HeapSizeOf; use trie; -use primitives::H256; +use primitives::{H256, convert_hash}; use runtime_primitives::traits::{As, Header as HeaderT, SimpleArithmetic, One}; use state_machine::backend::InMemory as InMemoryState; use state_machine::{prove_read, read_proof_check}; @@ -113,8 +113,7 @@ pub fn check_proof( Hasher: hash_db::Hasher, Hasher::Out: Ord + HeapSizeOf, { - let mut root: Hasher::Out = Default::default(); - root.as_mut().copy_from_slice(local_root.as_ref()); + let root: Hasher::Out = convert_hash(&local_root); let local_cht_key = encode_cht_key(local_number); let local_cht_value = read_proof_check::(root, remote_proof, &local_cht_key).map_err(|e| ClientError::from(e))?; diff --git a/core/client/src/client.rs b/core/client/src/client.rs index 92178ed056378..69b445554b722 100644 --- a/core/client/src/client.rs +++ b/core/client/src/client.rs @@ -30,7 +30,7 @@ use consensus::{ImportBlock, ImportResult, BlockOrigin}; use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Zero, As, NumberFor, CurrentHeight, BlockNumberToHash}; use runtime_primitives::{ApplyResult, BuildStorage}; use runtime_api as api; -use primitives::{Blake2Hasher, H256, ChangesTrieConfiguration}; +use primitives::{Blake2Hasher, H256, ChangesTrieConfiguration, convert_hash}; use primitives::storage::{StorageKey, StorageData}; use primitives::storage::well_known_keys; use codec::{Encode, Decode}; @@ -1143,14 +1143,6 @@ impl api::TaggedTransactionQueue for Client whe } } -/// Converts one hash type into another. -pub(crate) fn convert_hash, H2: AsRef<[u8]>>(src: &H2) -> H1 { - let mut dest = H1::default(); - let len = ::std::cmp::min(dest.as_mut().len(), src.as_ref().len()); - dest.as_mut().copy_from_slice(&src.as_ref()[..len]); - dest -} - #[cfg(test)] pub(crate) mod tests { use std::collections::HashMap; diff --git a/core/client/src/light/call_executor.rs b/core/client/src/light/call_executor.rs index d52883db64f01..732b783b73adc 100644 --- a/core/client/src/light/call_executor.rs +++ b/core/client/src/light/call_executor.rs @@ -21,6 +21,7 @@ use std::marker::PhantomData; use std::sync::Arc; use futures::{IntoFuture, Future}; +use primitives::convert_hash; use runtime_primitives::generic::BlockId; use runtime_primitives::traits::{Block as BlockT, Header as HeaderT}; use state_machine::{Backend as StateBackend, CodeExecutor, OverlayedChanges, @@ -136,8 +137,7 @@ pub fn check_execution_proof( { let local_state_root = request.header.state_root(); - let mut root: H::Out = Default::default(); - root.as_mut().copy_from_slice(local_state_root.as_ref()); + let root: H::Out = convert_hash(&local_state_root); let mut changes = OverlayedChanges::default(); let local_result = execution_proof_check::( diff --git a/core/client/src/light/fetcher.rs b/core/client/src/light/fetcher.rs index 6acf5d85c5f31..2eac7d4f695d5 100644 --- a/core/client/src/light/fetcher.rs +++ b/core/client/src/light/fetcher.rs @@ -21,14 +21,13 @@ use futures::IntoFuture; use hash_db::Hasher; use heapsize::HeapSizeOf; -use primitives::ChangesTrieConfiguration; +use primitives::{ChangesTrieConfiguration, convert_hash}; use runtime_primitives::traits::{As, Block as BlockT, Header as HeaderT, NumberFor}; use state_machine::{CodeExecutor, ChangesTrieRootsStorage, ChangesTrieAnchorBlockId, read_proof_check, key_changes_proof_check}; use call_executor::CallResult; use cht; -use client::convert_hash; use error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult}; use light::call_executor::check_execution_proof; diff --git a/core/primitives/src/hash.rs b/core/primitives/src/hash.rs index 840be7e8a4701..aa3ea2fb2df6f 100644 --- a/core/primitives/src/hash.rs +++ b/core/primitives/src/hash.rs @@ -59,6 +59,16 @@ impl_rest!(H160, 20); impl_rest!(H256, 32); impl_rest!(H512, 64); +/// Hash conversion. Used to convert between unbound associated hash types in traits, +/// implemented by the same hash type. +/// Panics if used to convert between different hash types. +pub fn convert_hash, H2: AsRef<[u8]>>(src: &H2) -> H1 { + let mut dest = H1::default(); + assert_eq!(dest.as_mut().len(), src.as_ref().len()); + dest.as_mut().copy_from_slice(src.as_ref()); + dest +} + #[cfg(test)] mod tests { use super::*; diff --git a/core/primitives/src/lib.rs b/core/primitives/src/lib.rs index affa1c5c00fe3..0a7bf59984d26 100644 --- a/core/primitives/src/lib.rs +++ b/core/primitives/src/lib.rs @@ -109,7 +109,7 @@ mod changes_trie; #[cfg(test)] mod tests; -pub use self::hash::{H160, H256, H512}; +pub use self::hash::{H160, H256, H512, convert_hash}; pub use self::uint::U256; pub use authority_id::AuthorityId; pub use changes_trie::ChangesTrieConfiguration; diff --git a/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm b/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm index 136d02534a4764fd28253ada369eb053e978f011..80da1bf5fdf8b7d065fc038dface6da7b784b419 100644 GIT binary patch delta 5319 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 S!I#B4qFdVTTdinI+kXHc$$F0f delta 5200 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 delta 37753 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 t{-AVnEe(!};> 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 Date: Tue, 6 Nov 2018 10:26:25 +0300 Subject: [PATCH 3/3] optimize ChangesTrieRootsStorage::root when anchor is canonicalized --- core/client/db/src/lib.rs | 44 ++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/core/client/db/src/lib.rs b/core/client/db/src/lib.rs index 82cf9ebcf6cc3..05d55f0c78558 100644 --- a/core/client/db/src/lib.rs +++ b/core/client/db/src/lib.rs @@ -440,20 +440,30 @@ impl state_machine::ChangesTrieRootsStorage for DbC // if block is finalized, we could just read canonical hash BlockId::Number(As::sa(block)) } else { - // if block is not finalized yet, we should find the required block hash by traversing - // back from the anchor to the block with given number + // the block is not finalized let mut current_num = anchor.number; let mut current_hash: Block::Hash = convert_hash(&anchor.hash); - while current_num != block { - let current_header: Block::Header = ::utils::require_header::( - &*self.db, columns::HASH_LOOKUP, columns::HEADER, BlockId::Hash(current_hash) - ).map_err(|e| e.to_string())?; + let maybe_anchor_header: Block::Header = ::utils::require_header::( + &*self.db, columns::HASH_LOOKUP, columns::HEADER, BlockId::Number(As::sa(current_num)) + ).map_err(|e| e.to_string())?; + if maybe_anchor_header.hash() == current_hash { + // if anchor is canonicalized, then the block is also canonicalized + BlockId::Number(As::sa(block)) + } else { + // else (block is not finalized + anchor is not canonicalized): + // => we should find the required block hash by traversing + // back from the anchor to the block with given number + while current_num != block { + let current_header: Block::Header = ::utils::require_header::( + &*self.db, columns::HASH_LOOKUP, columns::HEADER, BlockId::Hash(current_hash) + ).map_err(|e| e.to_string())?; + + current_hash = *current_header.parent_hash(); + current_num = current_num - 1; + } - current_hash = *current_header.parent_hash(); - current_num = current_num - 1; + BlockId::Hash(current_hash) } - - BlockId::Hash(current_hash) }; Ok(::utils::require_header::(&*self.db, columns::HASH_LOOKUP, columns::HEADER, block_id) @@ -1269,7 +1279,7 @@ mod tests { let block2_2_0 = insert_header(&backend, 3, block2, changes2_2_0.clone(), Default::default()); let block2_2_1 = insert_header(&backend, 4, block2_2_0, changes2_2_1.clone(), Default::default()); - // finalized block1 + // finalize block1 backend.changes_tries_storage.meta.write().finalized_number = 1; // branch1: when asking for finalized block hash @@ -1281,25 +1291,25 @@ mod tests { let anchor = state_machine::ChangesTrieAnchorBlockId { hash: block2_2_1, number: 4 }; assert_eq!(backend.changes_tries_storage.root(&anchor, 1), Ok(Some(changes1_root))); - // branch1: when asking for non-finalized block hash + // branch1: when asking for non-finalized block hash (search by traversal) let (changes2_1_0_root, _) = prepare_changes(changes2_1_0); let anchor = state_machine::ChangesTrieAnchorBlockId { hash: block2_1_1, number: 4 }; assert_eq!(backend.changes_tries_storage.root(&anchor, 3), Ok(Some(changes2_1_0_root))); - // branch2: when asking for non-finalized block hash + // branch2: when asking for non-finalized block hash (search using canonicalized hint) let (changes2_2_0_root, _) = prepare_changes(changes2_2_0); let anchor = state_machine::ChangesTrieAnchorBlockId { hash: block2_2_1, number: 4 }; assert_eq!(backend.changes_tries_storage.root(&anchor, 3), Ok(Some(changes2_2_0_root))); - // finalized first block of branch2 + // finalize first block of branch2 (block2_2_0) backend.changes_tries_storage.meta.write().finalized_number = 3; + // branch2: when asking for finalized block of this branch + assert_eq!(backend.changes_tries_storage.root(&anchor, 3), Ok(Some(changes2_2_0_root))); + // branch1: when asking for finalized block of other branch // => result is incorrect (returned for the block of branch1), but this is expected, // because the other fork is abandoned (forked before finalized header) - assert_eq!(backend.changes_tries_storage.root(&anchor, 3), Ok(Some(changes2_2_0_root))); - - // branch2: when asking for finalized block of this branch let anchor = state_machine::ChangesTrieAnchorBlockId { hash: block2_1_1, number: 4 }; assert_eq!(backend.changes_tries_storage.root(&anchor, 3), Ok(Some(changes2_2_0_root))); }