diff --git a/substrate/core/client/db/src/light.rs b/substrate/core/client/db/src/light.rs index f336df4d28..cbe42ac52b 100644 --- a/substrate/core/client/db/src/light.rs +++ b/substrate/core/client/db/src/light.rs @@ -31,7 +31,7 @@ use codec::{Decode, Encode}; use primitives::{AuthorityId, Blake2Hasher}; use runtime_primitives::generic::BlockId; use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, - Zero, One, As, NumberFor}; + Zero, One, As, NumberFor, Digest, DigestItem}; use cache::{DbCacheSync, DbCache, ComplexBlockId}; use utils::{meta_keys, Meta, db_err, number_to_lookup_key, open_database, read_db, block_id_to_lookup_key, read_meta}; @@ -45,11 +45,16 @@ pub(crate) mod columns { pub const CHT: Option = Some(4); } +/// Prefix for headers CHT. +const HEADER_CHT_PREFIX: u8 = 0; +/// Prefix for changes tries roots CHT. +const CHANGES_TRIE_CHT_PREFIX: u8 = 1; + /// Light blockchain storage. Stores most recent headers + CHTs for older headers. /// Locks order: meta, leaves, cache. pub struct LightStorage { db: Arc, - meta: RwLock::Header as HeaderT>::Number, Block::Hash>>, + meta: RwLock, Block::Hash>>, leaves: RwLock>>, cache: DbCacheSync, } @@ -109,7 +114,7 @@ impl LightStorage fn update_meta( &self, hash: Block::Hash, - number: <::Header as HeaderT>::Number, + number: NumberFor, is_best: bool, is_finalized: bool, ) { @@ -167,7 +172,7 @@ impl BlockchainHeaderBackend for LightStorage } } - fn number(&self, hash: Block::Hash) -> ClientResult::Header as HeaderT>::Number>> { + fn number(&self, hash: Block::Hash) -> ClientResult>> { if let Some(lookup_key) = block_id_to_lookup_key::(&*self.db, columns::HASH_LOOKUP, BlockId::Hash(hash))? { let number = ::utils::lookup_key_to_number(&lookup_key)?; Ok(Some(number)) @@ -176,12 +181,21 @@ impl BlockchainHeaderBackend for LightStorage } } - fn hash(&self, number: <::Header as HeaderT>::Number) -> ClientResult> { + fn hash(&self, number: NumberFor) -> ClientResult> { Ok(self.header(BlockId::Number(number))?.map(|header| header.hash().clone())) } } impl LightStorage { + // Get block changes trie root, if available. + fn changes_trie_root(&self, block: BlockId) -> ClientResult> { + self.header(block) + .map(|header| header.and_then(|header| + header.digest().log(DigestItem::as_changes_trie_root) + .cloned())) + } + + // Note that a block is finalized. Only call with child of last finalized block. fn note_finalized( &self, transaction: &mut DBTransaction, @@ -199,35 +213,68 @@ impl LightStorage { let lookup_key = ::utils::number_to_lookup_key(header.number().clone()); transaction.put(columns::META, meta_keys::FINALIZED_BLOCK, &lookup_key); - // build new CHT if required + // build new CHT(s) if required if let Some(new_cht_number) = cht::is_build_required(cht::SIZE, *header.number()) { let new_cht_start: NumberFor = cht::start_number(cht::SIZE, new_cht_number); - let new_cht_root = cht::compute_root::( + + let new_header_cht_root = cht::compute_root::( cht::SIZE, new_cht_number, (new_cht_start.as_()..) - .map(|num| self.hash(As::sa(num)).unwrap_or_default()) + .map(|num| self.hash(As::sa(num))) + )?; + transaction.put( + columns::CHT, + &cht_key(HEADER_CHT_PREFIX, new_cht_start), + new_header_cht_root.as_ref() ); - if let Some(new_cht_root) = new_cht_root { - transaction.put(columns::CHT, &number_to_lookup_key(new_cht_start), new_cht_root.as_ref()); + // if the header includes changes trie root, let's build a changes tries roots CHT + if header.digest().log(DigestItem::as_changes_trie_root).is_some() { + let new_changes_trie_cht_root = cht::compute_root::( + cht::SIZE, new_cht_number, (new_cht_start.as_()..) + .map(|num| self.changes_trie_root(BlockId::Number(As::sa(num)))) + )?; + transaction.put( + columns::CHT, + &cht_key(CHANGES_TRIE_CHT_PREFIX, new_cht_start), + new_changes_trie_cht_root.as_ref() + ); + } - let mut prune_block = new_cht_start; - let new_cht_end = cht::end_number(cht::SIZE, new_cht_number); - trace!(target: "db", "Replacing blocks [{}..{}] with CHT#{}", new_cht_start, new_cht_end, new_cht_number); + // prune headers that are replaced with CHT + let mut prune_block = new_cht_start; + let new_cht_end = cht::end_number(cht::SIZE, new_cht_number); + trace!(target: "db", "Replacing blocks [{}..{}] with CHT#{}", + new_cht_start, new_cht_end, new_cht_number); - while prune_block <= new_cht_end { - if let Some(hash) = self.hash(prune_block)? { - let lookup_key = block_id_to_lookup_key::(&*self.db, columns::HASH_LOOKUP, BlockId::Number(prune_block))? - .expect("retrieved hash for `prune_block` right above. therefore retrieving lookup key must succeed. q.e.d."); - transaction.delete(columns::HASH_LOOKUP, hash.as_ref()); - transaction.delete(columns::HEADER, &lookup_key); - } - prune_block += NumberFor::::one(); + while prune_block <= new_cht_end { + if let Some(hash) = self.hash(prune_block)? { + let lookup_key = block_id_to_lookup_key::(&*self.db, columns::HASH_LOOKUP, BlockId::Number(prune_block))? + .expect("retrieved hash for `prune_block` right above. therefore retrieving lookup key must succeed. q.e.d."); + transaction.delete(columns::HASH_LOOKUP, hash.as_ref()); + transaction.delete(columns::HEADER, &lookup_key); } + prune_block += One::one(); } } Ok(()) } + + /// Read CHT root of given type for the block. + fn read_cht_root( + &self, + cht_type: u8, + cht_size: u64, + block: NumberFor + ) -> ClientResult { + let no_cht_for_block = || ClientErrorKind::Backend(format!("CHT for block {} not exists", block)).into(); + + let cht_number = cht::block_to_cht_number(cht_size, block).ok_or_else(no_cht_for_block)?; + let cht_start = cht::start_number(cht_size, cht_number); + self.db.get(columns::CHT, &cht_key(cht_type, cht_start)).map_err(db_err)? + .ok_or_else(no_cht_for_block) + .and_then(|hash| Block::Hash::decode(&mut &*hash).ok_or_else(no_cht_for_block)) + } } impl LightBlockchainStorage for LightStorage @@ -354,14 +401,12 @@ impl LightBlockchainStorage for LightStorage Ok(()) } - fn cht_root(&self, cht_size: u64, block: <::Header as HeaderT>::Number) -> ClientResult { - let no_cht_for_block = || ClientErrorKind::Backend(format!("CHT for block {} not exists", block)).into(); + fn header_cht_root(&self, cht_size: u64, block: NumberFor) -> ClientResult { + self.read_cht_root(HEADER_CHT_PREFIX, cht_size, block) + } - let cht_number = cht::block_to_cht_number(cht_size, block).ok_or_else(no_cht_for_block)?; - let cht_start = cht::start_number(cht_size, cht_number); - self.db.get(columns::CHT, &number_to_lookup_key(cht_start)).map_err(db_err)? - .ok_or_else(no_cht_for_block) - .and_then(|hash| Block::Hash::decode(&mut &*hash).ok_or_else(no_cht_for_block)) + fn changes_trie_cht_root(&self, cht_size: u64, block: NumberFor) -> ClientResult { + self.read_cht_root(CHANGES_TRIE_CHT_PREFIX, cht_size, block) } fn finalize_header(&self, id: BlockId) -> ClientResult<()> { @@ -400,68 +445,72 @@ impl LightBlockchainStorage for LightStorage } } +/// Build the key for inserting header-CHT at given block. +fn cht_key>(cht_type: u8, block: N) -> [u8; 5] { + let mut key = [cht_type; 5]; + key[1..].copy_from_slice(&number_to_lookup_key(block)); + key +} + #[cfg(test)] pub(crate) mod tests { use client::cht; + use runtime_primitives::generic::DigestItem; use runtime_primitives::testing::{H256 as Hash, Header, Block as RawBlock, ExtrinsicWrapper}; use super::*; type Block = RawBlock>; - fn prepare_header(parent: &Hash, number: u64, extrinsics_root: Hash) -> Header { + pub fn default_header(parent: &Hash, number: u64) -> Header { Header { number: number.into(), parent_hash: *parent, state_root: Hash::random(), digest: Default::default(), - extrinsics_root, + extrinsics_root: Default::default(), } } - pub fn insert_block_with_extrinsics_root( + fn header_with_changes_trie(parent: &Hash, number: u64) -> Header { + let mut header = default_header(parent, number); + header.digest.logs.push(DigestItem::ChangesTrieRoot([(number % 256) as u8; 32].into())); + header + } + + fn header_with_extrinsics_root(parent: &Hash, number: u64, extrinsics_root: Hash) -> Header { + let mut header = default_header(parent, number); + header.extrinsics_root = extrinsics_root; + header + } + + pub fn insert_block Header>( db: &LightStorage, - parent: &Hash, - number: u64, authorities: Option>, - extrinsics_root: Hash, + header: F, ) -> Hash { - let header = prepare_header(parent, number, extrinsics_root); + let header = header(); let hash = header.hash(); db.import_header(header, authorities, NewBlockState::Best).unwrap(); hash } - pub fn insert_block( + fn insert_final_block Header>( db: &LightStorage, - parent: &Hash, - number: u64, - authorities: Option> + authorities: Option>, + header: F, ) -> Hash { - let header = prepare_header(parent, number, Default::default()); - let hash = header.hash(); - db.import_header(header, authorities, NewBlockState::Best).unwrap(); - hash - } - - fn insert_final_block( - db: &LightStorage, - parent: &Hash, - number: u64, - authorities: Option> - ) -> Hash { - let header = prepare_header(parent, number, Default::default()); + let header = header(); let hash = header.hash(); db.import_header(header, authorities, NewBlockState::Final).unwrap(); hash } - fn insert_non_best_block( + fn insert_non_best_block Header>( db: &LightStorage, - parent: &Hash, - number: u64, - authorities: Option> + authorities: Option>, + header: F, ) -> Hash { - let header = prepare_header(parent, number, Default::default()); + let header = header(); let hash = header.hash(); db.import_header(header, authorities, NewBlockState::Normal).unwrap(); hash @@ -470,7 +519,7 @@ pub(crate) mod tests { #[test] fn returns_known_header() { let db = LightStorage::new_test(); - let known_hash = insert_block(&db, &Default::default(), 0, None); + let known_hash = insert_block(&db, None, || default_header(&Default::default(), 0)); let header_by_hash = db.header(BlockId::Hash(known_hash)).unwrap().unwrap(); let header_by_number = db.header(BlockId::Number(0)).unwrap().unwrap(); assert_eq!(header_by_hash, header_by_number); @@ -486,12 +535,12 @@ pub(crate) mod tests { #[test] fn returns_info() { let db = LightStorage::new_test(); - let genesis_hash = insert_block(&db, &Default::default(), 0, None); + let genesis_hash = insert_block(&db, None, || default_header(&Default::default(), 0)); let info = db.info().unwrap(); assert_eq!(info.best_hash, genesis_hash); assert_eq!(info.best_number, 0); assert_eq!(info.genesis_hash, genesis_hash); - let best_hash = insert_block(&db, &genesis_hash, 1, None); + let best_hash = insert_block(&db, None, || default_header(&genesis_hash, 1)); let info = db.info().unwrap(); assert_eq!(info.best_hash, best_hash); assert_eq!(info.best_number, 1); @@ -501,7 +550,7 @@ pub(crate) mod tests { #[test] fn returns_block_status() { let db = LightStorage::new_test(); - let genesis_hash = insert_block(&db, &Default::default(), 0, None); + let genesis_hash = insert_block(&db, None, || default_header(&Default::default(), 0)); assert_eq!(db.status(BlockId::Hash(genesis_hash)).unwrap(), BlockStatus::InChain); assert_eq!(db.status(BlockId::Number(0)).unwrap(), BlockStatus::InChain); assert_eq!(db.status(BlockId::Hash(1.into())).unwrap(), BlockStatus::Unknown); @@ -511,7 +560,7 @@ pub(crate) mod tests { #[test] fn returns_block_hash() { let db = LightStorage::new_test(); - let genesis_hash = insert_block(&db, &Default::default(), 0, None); + let genesis_hash = insert_block(&db, None, || default_header(&Default::default(), 0)); assert_eq!(db.hash(0).unwrap(), Some(genesis_hash)); assert_eq!(db.hash(1).unwrap(), None); } @@ -520,64 +569,81 @@ pub(crate) mod tests { fn import_header_works() { let db = LightStorage::new_test(); - let genesis_hash = insert_block(&db, &Default::default(), 0, None); + let genesis_hash = insert_block(&db, None, || default_header(&Default::default(), 0)); assert_eq!(db.db.iter(columns::HEADER).count(), 1); assert_eq!(db.db.iter(columns::HASH_LOOKUP).count(), 1); - let _ = insert_block(&db, &genesis_hash, 1, None); + let _ = insert_block(&db, None, || default_header(&genesis_hash, 1)); assert_eq!(db.db.iter(columns::HEADER).count(), 2); assert_eq!(db.db.iter(columns::HASH_LOOKUP).count(), 2); } #[test] fn finalized_ancient_headers_are_replaced_with_cht() { - let db = LightStorage::new_test(); + fn insert_headers Header>(header_producer: F) -> LightStorage { + let db = LightStorage::new_test(); - // insert genesis block header (never pruned) - let mut prev_hash = insert_final_block(&db, &Default::default(), 0, None); + // insert genesis block header (never pruned) + let mut prev_hash = insert_final_block(&db, None, || header_producer(&Default::default(), 0)); - // insert SIZE blocks && ensure that nothing is pruned - for number in 0..cht::SIZE { - prev_hash = insert_block(&db, &prev_hash, 1 + number, None); + // insert SIZE blocks && ensure that nothing is pruned + for number in 0..cht::SIZE { + prev_hash = insert_block(&db, None, || header_producer(&prev_hash, 1 + number)); + } + assert_eq!(db.db.iter(columns::HEADER).count(), (1 + cht::SIZE) as usize); + assert_eq!(db.db.iter(columns::CHT).count(), 0); + + // insert next SIZE blocks && ensure that nothing is pruned + for number in 0..cht::SIZE { + prev_hash = insert_block(&db, None, || header_producer(&prev_hash, 1 + cht::SIZE + number)); + } + assert_eq!(db.db.iter(columns::HEADER).count(), (1 + cht::SIZE + cht::SIZE) as usize); + assert_eq!(db.db.iter(columns::CHT).count(), 0); + + // insert block #{2 * cht::SIZE + 1} && check that new CHT is created + headers of this CHT are pruned + // nothing is yet finalized, so nothing is pruned. + prev_hash = insert_block(&db, None, || header_producer(&prev_hash, 1 + cht::SIZE + cht::SIZE)); + assert_eq!(db.db.iter(columns::HEADER).count(), (2 + cht::SIZE + cht::SIZE) as usize); + assert_eq!(db.db.iter(columns::CHT).count(), 0); + + // now finalize the block. + for i in (0..(cht::SIZE + cht::SIZE)).map(|i| i + 1) { + db.finalize_header(BlockId::Number(i)).unwrap(); + } + db.finalize_header(BlockId::Hash(prev_hash)).unwrap(); + db } - assert_eq!(db.db.iter(columns::HEADER).count(), (1 + cht::SIZE) as usize); - assert_eq!(db.db.iter(columns::HASH_LOOKUP).count(), (1 + cht::SIZE) as usize); - assert_eq!(db.db.iter(columns::CHT).count(), 0); - // insert next SIZE blocks && ensure that nothing is pruned - for number in 0..cht::SIZE { - prev_hash = insert_block(&db, &prev_hash, 1 + cht::SIZE + number, None); - } - assert_eq!(db.db.iter(columns::HEADER).count(), (1 + cht::SIZE + cht::SIZE) as usize); - assert_eq!(db.db.iter(columns::HASH_LOOKUP).count(), (1 + cht::SIZE + cht::SIZE) as usize); - assert_eq!(db.db.iter(columns::CHT).count(), 0); - - // insert block #{2 * cht::SIZE + 1} && check that new CHT is created + headers of this CHT are pruned - // nothing is yet finalized, so nothing is pruned. - prev_hash = insert_block(&db, &prev_hash, 1 + cht::SIZE + cht::SIZE, None); - assert_eq!(db.db.iter(columns::HEADER).count(), (2 + cht::SIZE + cht::SIZE) as usize); - assert_eq!(db.db.iter(columns::HASH_LOOKUP).count(), (2 + cht::SIZE + cht::SIZE) as usize); - assert_eq!(db.db.iter(columns::CHT).count(), 0); - - // now finalize the block. - for i in (0..(cht::SIZE + cht::SIZE)).map(|i| i + 1) { - db.finalize_header(BlockId::Number(i)).unwrap(); - } - db.finalize_header(BlockId::Hash(prev_hash)).unwrap(); + // when headers are created without changes tries roots + let db = insert_headers(default_header); assert_eq!(db.db.iter(columns::HEADER).count(), (1 + cht::SIZE + 1) as usize); assert_eq!(db.db.iter(columns::HASH_LOOKUP).count(), (1 + cht::SIZE + 1) as usize); assert_eq!(db.db.iter(columns::CHT).count(), 1); assert!((0..cht::SIZE).all(|i| db.db.get(columns::HEADER, &number_to_lookup_key(1 + i)).unwrap().is_none())); + assert!(db.header_cht_root(cht::SIZE, cht::SIZE / 2).is_ok()); + assert!(db.header_cht_root(cht::SIZE, cht::SIZE + cht::SIZE / 2).is_err()); + assert!(db.changes_trie_cht_root(cht::SIZE, cht::SIZE / 2).is_err()); + assert!(db.changes_trie_cht_root(cht::SIZE, cht::SIZE + cht::SIZE / 2).is_err()); + + // when headers are created with changes tries roots + let db = insert_headers(header_with_changes_trie); + assert_eq!(db.db.iter(columns::HEADER).count(), (1 + cht::SIZE + 1) as usize); + assert_eq!(db.db.iter(columns::CHT).count(), 2); + assert!((0..cht::SIZE).all(|i| db.db.get(columns::HEADER, &number_to_lookup_key(1 + i)).unwrap().is_none())); + assert!(db.header_cht_root(cht::SIZE, cht::SIZE / 2).is_ok()); + assert!(db.header_cht_root(cht::SIZE, cht::SIZE + cht::SIZE / 2).is_err()); + assert!(db.changes_trie_cht_root(cht::SIZE, cht::SIZE / 2).is_ok()); + assert!(db.changes_trie_cht_root(cht::SIZE, cht::SIZE + cht::SIZE / 2).is_err()); } #[test] fn get_cht_fails_for_genesis_block() { - assert!(LightStorage::::new_test().cht_root(cht::SIZE, 0).is_err()); + assert!(LightStorage::::new_test().header_cht_root(cht::SIZE, 0).is_err()); } #[test] fn get_cht_fails_for_non_existant_cht() { - assert!(LightStorage::::new_test().cht_root(cht::SIZE, (cht::SIZE / 2) as u64).is_err()); + assert!(LightStorage::::new_test().header_cht_root(cht::SIZE, (cht::SIZE / 2) as u64).is_err()); } #[test] @@ -585,15 +651,21 @@ pub(crate) mod tests { let db = LightStorage::new_test(); // insert 1 + SIZE + SIZE + 1 blocks so that CHT#0 is created - let mut prev_hash = insert_final_block(&db, &Default::default(), 0, None); + let mut prev_hash = insert_final_block(&db, None, || header_with_changes_trie(&Default::default(), 0)); for i in 1..1 + cht::SIZE + cht::SIZE + 1 { - prev_hash = insert_block(&db, &prev_hash, i as u64, None); + prev_hash = insert_block(&db, None, || header_with_changes_trie(&prev_hash, i as u64)); db.finalize_header(BlockId::Hash(prev_hash)).unwrap(); } - let cht_root_1 = db.cht_root(cht::SIZE, cht::start_number(cht::SIZE, 0)).unwrap(); - let cht_root_2 = db.cht_root(cht::SIZE, (cht::start_number(cht::SIZE, 0) + cht::SIZE / 2) as u64).unwrap(); - let cht_root_3 = db.cht_root(cht::SIZE, cht::end_number(cht::SIZE, 0)).unwrap(); + let cht_root_1 = db.header_cht_root(cht::SIZE, cht::start_number(cht::SIZE, 0)).unwrap(); + let cht_root_2 = db.header_cht_root(cht::SIZE, (cht::start_number(cht::SIZE, 0) + cht::SIZE / 2) as u64).unwrap(); + let cht_root_3 = db.header_cht_root(cht::SIZE, cht::end_number(cht::SIZE, 0)).unwrap(); + assert_eq!(cht_root_1, cht_root_2); + assert_eq!(cht_root_2, cht_root_3); + + let cht_root_1 = db.changes_trie_cht_root(cht::SIZE, cht::start_number(cht::SIZE, 0)).unwrap(); + let cht_root_2 = db.changes_trie_cht_root(cht::SIZE, (cht::start_number(cht::SIZE, 0) + cht::SIZE / 2) as u64).unwrap(); + let cht_root_3 = db.changes_trie_cht_root(cht::SIZE, cht::end_number(cht::SIZE, 0)).unwrap(); assert_eq!(cht_root_1, cht_root_2); assert_eq!(cht_root_2, cht_root_3); } @@ -601,16 +673,16 @@ pub(crate) mod tests { #[test] fn tree_route_works() { let db = LightStorage::new_test(); - let block0 = insert_block(&db, &Default::default(), 0, None); + let block0 = insert_block(&db, None, || default_header(&Default::default(), 0)); // fork from genesis: 3 prong. - let a1 = insert_block(&db, &block0, 1, None); - let a2 = insert_block(&db, &a1, 2, None); - let a3 = insert_block(&db, &a2, 3, None); + let a1 = insert_block(&db, None, || default_header(&block0, 1)); + let a2 = insert_block(&db, None, || default_header(&a1, 2)); + let a3 = insert_block(&db, None, || default_header(&a2, 3)); // fork from genesis: 2 prong. - let b1 = insert_block_with_extrinsics_root(&db, &block0, 1, None, Hash::from([1; 32])); - let b2 = insert_block(&db, &b1, 2, None); + let b1 = insert_block(&db, None, || header_with_extrinsics_root(&block0, 1, Hash::from([1; 32]))); + let b2 = insert_block(&db, None, || default_header(&b1, 2)); { let tree_route = ::client::blockchain::tree_route( @@ -686,19 +758,19 @@ pub(crate) mod tests { (7, None), // block will work for 'future' block too ]; - let hash0 = insert_final_block(&db, &Default::default(), 0, None); + let hash0 = insert_final_block(&db, None, || default_header(&Default::default(), 0)); run_checks(&db, 0, &checks); - let hash1 = insert_final_block(&db, &hash0, 1, None); + let hash1 = insert_final_block(&db, None, || default_header(&hash0, 1)); run_checks(&db, 1, &checks); - let hash2 = insert_final_block(&db, &hash1, 2, Some(vec![[1u8; 32].into()])); + let hash2 = insert_final_block(&db, Some(vec![[1u8; 32].into()]), || default_header(&hash1, 2)); run_checks(&db, 2, &checks); - let hash3 = insert_final_block(&db, &hash2, 3, Some(vec![[1u8; 32].into()])); + let hash3 = insert_final_block(&db, Some(vec![[1u8; 32].into()]), || default_header(&hash2, 3)); run_checks(&db, 3, &checks); - let hash4 = insert_final_block(&db, &hash3, 4, Some(vec![[1u8; 32].into(), [2u8; 32].into()])); + let hash4 = insert_final_block(&db, Some(vec![[1u8; 32].into(), [2u8; 32].into()]), || default_header(&hash3, 4)); run_checks(&db, 4, &checks); - let hash5 = insert_final_block(&db, &hash4, 5, Some(vec![[1u8; 32].into(), [2u8; 32].into()])); + let hash5 = insert_final_block(&db, Some(vec![[1u8; 32].into(), [2u8; 32].into()]), || default_header(&hash4, 5)); run_checks(&db, 5, &checks); - let hash6 = insert_final_block(&db, &hash5, 6, None); + let hash6 = insert_final_block(&db, None, || default_header(&hash5, 6)); run_checks(&db, 7, &checks); (hash2, hash6) @@ -708,9 +780,9 @@ pub(crate) mod tests { // some older non-best blocks are inserted // ... -> B2(1) -> B2_1(1) -> B2_2(2) // => the cache ignores all writes before best finalized block - let hash2_1 = insert_non_best_block(&db, &hash2, 3, Some(vec![[1u8; 32].into()])); + let hash2_1 = insert_non_best_block(&db, Some(vec![[1u8; 32].into()]), || default_header(&hash2, 3)); assert_eq!(None, db.cache().authorities_at(BlockId::Hash(hash2_1))); - let hash2_2 = insert_non_best_block(&db, &hash2_1, 4, Some(vec![[1u8; 32].into(), [2u8; 32].into()])); + let hash2_2 = insert_non_best_block(&db, Some(vec![[1u8; 32].into(), [2u8; 32].into()]), || default_header(&hash2_1, 4)); assert_eq!(None, db.cache().authorities_at(BlockId::Hash(hash2_2))); } @@ -721,32 +793,32 @@ pub(crate) mod tests { // \> B6_1_1(5) // \> B6_1_2(6) -> B6_1_3(7) - let hash7 = insert_block(&db, &hash6, 7, Some(vec![[3u8; 32].into()])); + let hash7 = insert_block(&db, Some(vec![[3u8; 32].into()]), || default_header(&hash6, 7)); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), None); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), Some(vec![[3u8; 32].into()])); - let hash8 = insert_block(&db, &hash7, 8, Some(vec![[3u8; 32].into()])); + let hash8 = insert_block(&db, Some(vec![[3u8; 32].into()]), || default_header(&hash7, 8)); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), None); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), Some(vec![[3u8; 32].into()])); - let hash6_1 = insert_block(&db, &hash6, 7, Some(vec![[4u8; 32].into()])); + let hash6_1 = insert_block(&db, Some(vec![[4u8; 32].into()]), || default_header(&hash6, 7)); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), None); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1)), Some(vec![[4u8; 32].into()])); - let hash6_1_1 = insert_non_best_block(&db, &hash6_1, 8, Some(vec![[5u8; 32].into()])); + let hash6_1_1 = insert_non_best_block(&db, Some(vec![[5u8; 32].into()]), || default_header(&hash6_1, 8)); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), None); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1)), Some(vec![[4u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1_1)), Some(vec![[5u8; 32].into()])); - let hash6_1_2 = insert_non_best_block(&db, &hash6_1, 8, Some(vec![[6u8; 32].into()])); + let hash6_1_2 = insert_non_best_block(&db, Some(vec![[6u8; 32].into()]), || default_header(&hash6_1, 8)); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), None); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1)), Some(vec![[4u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1_1)), Some(vec![[5u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6_1_2)), Some(vec![[6u8; 32].into()])); - let hash6_2 = insert_block(&db, &hash6_1, 8, Some(vec![[4u8; 32].into()])); + let hash6_2 = insert_block(&db, Some(vec![[4u8; 32].into()]), || default_header(&hash6_1, 8)); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash6)), None); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash7)), Some(vec![[3u8; 32].into()])); assert_eq!(db.cache().authorities_at(BlockId::Hash(hash8)), Some(vec![[3u8; 32].into()])); diff --git a/substrate/core/client/src/cht.rs b/substrate/core/client/src/cht.rs index 39e1aaa95b..51fb485da6 100644 --- a/substrate/core/client/src/cht.rs +++ b/substrate/core/client/src/cht.rs @@ -23,6 +23,8 @@ //! root has. A correct proof implies that the claimed block is identical to the one //! we discarded. +use std::collections::HashSet; + use hash_db; use heapsize::HeapSizeOf; use trie; @@ -30,7 +32,8 @@ use trie; 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}; +use state_machine::{MemoryDB, TrieBackend, Backend as StateBackend, + prove_read_on_trie_backend, read_proof_check, read_proof_check_on_proving_backend}; use error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult}; @@ -63,41 +66,48 @@ pub fn compute_root( cht_size: u64, cht_num: Header::Number, hashes: I, -) -> Option +) -> ClientResult where Header: HeaderT, Hasher: hash_db::Hasher, Hasher::Out: Ord, - I: IntoIterator>, + I: IntoIterator>>, { - build_pairs::(cht_size, cht_num, hashes) - .map(|pairs| trie::trie_root::(pairs)) + Ok(trie::trie_root::( + build_pairs::(cht_size, cht_num, hashes)? + )) } /// Build CHT-based header proof. -pub fn build_proof( +pub fn build_proof( cht_size: u64, cht_num: Header::Number, - block_num: Header::Number, - hashes: I -) -> Option>> + blocks: BlocksI, + hashes: HashesI +) -> ClientResult>> where Header: HeaderT, Hasher: hash_db::Hasher, Hasher::Out: Ord + HeapSizeOf, - I: IntoIterator>, + BlocksI: IntoIterator, + HashesI: IntoIterator>>, { - let transaction = build_pairs::(cht_size, cht_num, hashes)? + let transaction = build_pairs::(cht_size, cht_num, hashes)? .into_iter() .map(|(k, v)| (None, k, Some(v))) .collect::>(); let storage = InMemoryState::::default().update(transaction); - let (value, proof) = prove_read(storage, &encode_cht_key(block_num)).ok()?; - if value.is_none() { - None - } else { - Some(proof) + let trie_storage = storage.try_into_trie_backend() + .expect("InMemoryState::try_into_trie_backend always returns Some; qed"); + let mut total_proof = HashSet::new(); + for block in blocks.into_iter() { + debug_assert_eq!(block_to_cht_number(cht_size, block), Some(cht_num)); + + let (value, proof) = prove_read_on_trie_backend(&trie_storage, &encode_cht_key(block))?; + assert!(value.is_some(), "we have just built trie that includes the value for block"); + total_proof.extend(proof); } + Ok(total_proof.into_iter().collect()) } /// Check CHT-based header proof. @@ -109,20 +119,104 @@ pub fn check_proof( ) -> ClientResult<()> where Header: HeaderT, - Header::Hash: AsRef<[u8]>, Hasher: hash_db::Hasher, Hasher::Out: Ord + HeapSizeOf, +{ + do_check_proof::(local_root, local_number, remote_hash, move |local_root, local_cht_key| + read_proof_check::(local_root, remote_proof, + local_cht_key).map_err(|e| ClientError::from(e))) +} + +/// Check CHT-based header proof on pre-created proving backend. +pub fn check_proof_on_proving_backend( + local_root: Header::Hash, + local_number: Header::Number, + remote_hash: Header::Hash, + proving_backend: &TrieBackend, Hasher>, +) -> ClientResult<()> + where + Header: HeaderT, + Hasher: hash_db::Hasher, + Hasher::Out: Ord + HeapSizeOf, +{ + do_check_proof::(local_root, local_number, remote_hash, |_, local_cht_key| + read_proof_check_on_proving_backend::( + proving_backend, local_cht_key).map_err(|e| ClientError::from(e))) +} + +/// Check CHT-based header proof using passed checker function. +fn do_check_proof( + local_root: Header::Hash, + local_number: Header::Number, + remote_hash: Header::Hash, + checker: F, +) -> ClientResult<()> + where + Header: HeaderT, + Hasher: hash_db::Hasher, + Hasher::Out: Ord + HeapSizeOf, + F: FnOnce(Hasher::Out, &[u8]) -> ClientResult>>, { 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))?; - let local_cht_value = local_cht_value.ok_or_else(|| ClientErrorKind::InvalidHeaderProof)?; - let local_hash = decode_cht_value(&local_cht_value).ok_or_else(|| ClientErrorKind::InvalidHeaderProof)?; + let local_cht_value = checker(root, &local_cht_key)?; + let local_cht_value = local_cht_value.ok_or_else(|| ClientErrorKind::InvalidCHTProof)?; + let local_hash = decode_cht_value(&local_cht_value).ok_or_else(|| ClientErrorKind::InvalidCHTProof)?; match &local_hash[..] == remote_hash.as_ref() { true => Ok(()), - false => Err(ClientErrorKind::InvalidHeaderProof.into()), + false => Err(ClientErrorKind::InvalidCHTProof.into()), } + +} + +/// Group ordered blocks by CHT number and call functor with blocks of each group. +pub fn for_each_cht_group( + cht_size: u64, + blocks: I, + mut functor: F, + mut functor_param: P, +) -> ClientResult<()> + where + Header: HeaderT, + I: IntoIterator, + F: FnMut(P, Header::Number, Vec) -> ClientResult

, +{ + let mut current_cht_num = None; + let mut current_cht_blocks = Vec::new(); + for block in blocks { + let new_cht_num = match block_to_cht_number(cht_size, block.as_()) { + Some(new_cht_num) => new_cht_num, + None => return Err(ClientErrorKind::Backend(format!( + "Cannot compute CHT root for the block #{}", block)).into() + ), + }; + + let advance_to_next_cht = current_cht_num.is_some() && current_cht_num != Some(new_cht_num); + if advance_to_next_cht { + let current_cht_num = current_cht_num.expect("advance_to_next_cht is true; + it is true only when current_cht_num is Some; qed"); + assert!(new_cht_num > current_cht_num, "for_each_cht_group only supports ordered iterators"); + + functor_param = functor( + functor_param, + As::sa(current_cht_num), + ::std::mem::replace(&mut current_cht_blocks, Vec::new()), + )?; + } + + current_cht_blocks.push(block); + current_cht_num = Some(new_cht_num); + } + + if let Some(current_cht_num) = current_cht_num { + functor( + functor_param, + As::sa(current_cht_num), + ::std::mem::replace(&mut current_cht_blocks, Vec::new()), + )?; + } + + Ok(()) } /// Build pairs for computing CHT. @@ -130,26 +224,29 @@ fn build_pairs( cht_size: u64, cht_num: Header::Number, hashes: I -) -> Option, Vec)>> +) -> ClientResult, Vec)>> where Header: HeaderT, - I: IntoIterator>, + I: IntoIterator>>, { let start_num = start_number(cht_size, cht_num); let mut pairs = Vec::new(); let mut hash_number = start_num; for hash in hashes.into_iter().take(cht_size as usize) { - pairs.push(hash.map(|hash| ( + let hash = hash?.ok_or_else(|| ClientError::from( + ClientErrorKind::MissingHashRequiredForCHT(cht_num.as_(), hash_number.as_()) + ))?; + pairs.push(( encode_cht_key(hash_number).to_vec(), encode_cht_value(hash) - ))?); + )); hash_number += Header::Number::one(); } if pairs.len() as u64 == cht_size { - Some(pairs) + Ok(pairs) } else { - None + Err(ClientErrorKind::MissingHashRequiredForCHT(cht_num.as_(), hash_number.as_()).into()) } } @@ -241,30 +338,59 @@ mod tests { #[test] fn build_pairs_fails_when_no_enough_blocks() { - assert!(build_pairs::(SIZE, 0, vec![Some(1.into()); SIZE as usize / 2]).is_none()); + assert!(build_pairs::(SIZE, 0, + ::std::iter::repeat_with(|| Ok(Some(1.into()))).take(SIZE as usize / 2)).is_err()); } #[test] fn build_pairs_fails_when_missing_block() { - assert!(build_pairs::(SIZE, 0, ::std::iter::repeat(Some(1.into())).take(SIZE as usize / 2) - .chain(::std::iter::once(None)) - .chain(::std::iter::repeat(Some(2.into())).take(SIZE as usize / 2 - 1))).is_none()); + assert!(build_pairs::(SIZE, 0, ::std::iter::repeat_with(|| Ok(Some(1.into()))).take(SIZE as usize / 2) + .chain(::std::iter::once(Ok(None))) + .chain(::std::iter::repeat_with(|| Ok(Some(2.into()))).take(SIZE as usize / 2 - 1))).is_err()); } #[test] fn compute_root_works() { - assert!(compute_root::(SIZE, 42, vec![Some(1.into()); SIZE as usize]).is_some()); + assert!(compute_root::(SIZE, 42, + ::std::iter::repeat_with(|| Ok(Some(1.into()))).take(SIZE as usize)).is_ok()); } #[test] - fn build_proof_fails_when_querying_wrong_block() { - assert!(build_proof::( - SIZE, 0, (SIZE * 1000) as u64, vec![Some(1.into()); SIZE as usize]).is_none()); + #[should_panic] + fn build_proof_panics_when_querying_wrong_block() { + assert!(build_proof::( + SIZE, 0, vec![(SIZE * 1000) as u64], + ::std::iter::repeat_with(|| Ok(Some(1.into()))).take(SIZE as usize)).is_err()); } #[test] fn build_proof_works() { - assert!(build_proof::( - SIZE, 0, (SIZE / 2) as u64, vec![Some(1.into()); SIZE as usize]).is_some()); + assert!(build_proof::( + SIZE, 0, vec![(SIZE / 2) as u64], + ::std::iter::repeat_with(|| Ok(Some(1.into()))).take(SIZE as usize)).is_ok()); + } + + #[test] + #[should_panic] + fn for_each_cht_group_panics() { + let _ = for_each_cht_group::(SIZE, vec![SIZE * 5, SIZE * 2], |_, _, _| Ok(()), ()); + } + + #[test] + fn for_each_cht_group_works() { + let _ = for_each_cht_group::(SIZE, vec![ + SIZE * 2 + 1, SIZE * 2 + 2, SIZE * 2 + 5, + SIZE * 4 + 1, SIZE * 4 + 7, + SIZE * 6 + 1 + ], |_, cht_num, blocks| { + match cht_num { + 2 => assert_eq!(blocks, vec![SIZE * 2 + 1, SIZE * 2 + 2, SIZE * 2 + 5]), + 4 => assert_eq!(blocks, vec![SIZE * 4 + 1, SIZE * 4 + 7]), + 6 => assert_eq!(blocks, vec![SIZE * 6 + 1]), + _ => unreachable!(), + } + + Ok(()) + }, ()); } } diff --git a/substrate/core/client/src/client.rs b/substrate/core/client/src/client.rs index 6cf62dca2c..967da7a589 100644 --- a/substrate/core/client/src/client.rs +++ b/substrate/core/client/src/client.rs @@ -16,7 +16,7 @@ //! Substrate Client -use std::{marker::PhantomData, sync::Arc}; +use std::{marker::PhantomData, collections::{HashSet, BTreeMap}, sync::Arc}; use error::Error; use futures::sync::mpsc; use parking_lot::{Mutex, RwLock}; @@ -29,7 +29,7 @@ use runtime_primitives::{ use consensus::{ImportBlock, ImportResult, BlockOrigin}; use runtime_primitives::traits::{ Block as BlockT, Header as HeaderT, Zero, As, NumberFor, CurrentHeight, BlockNumberToHash, - ApiRef, ProvideRuntimeApi + ApiRef, ProvideRuntimeApi, Digest, DigestItem, }; use runtime_primitives::BuildStorage; use runtime_api::{Core as CoreAPI, CallApiAt, TaggedTransactionQueue, ConstructRuntimeApi}; @@ -38,8 +38,9 @@ use primitives::storage::{StorageKey, StorageData}; use primitives::storage::well_known_keys; use codec::Decode; use state_machine::{ - Backend as StateBackend, CodeExecutor, ChangesTrieAnchorBlockId, + DBValue, Backend as StateBackend, CodeExecutor, ChangesTrieAnchorBlockId, ExecutionStrategy, ExecutionManager, prove_read, + ChangesTrieRootsStorage, ChangesTrieStorage, key_changes, key_changes_proof, OverlayedChanges }; use codec::Encode; @@ -49,6 +50,7 @@ use blockchain::{self, Info as ChainInfo, Backend as ChainBackend, HeaderBackend use call_executor::{CallExecutor, LocalCallExecutor}; use executor::{RuntimeVersion, RuntimeInfo}; use notifications::{StorageNotifications, StorageEventStream}; +use light::fetcher::ChangesProof; use {cht, error, in_mem, block_builder::{self, api::BlockBuilder as BlockBuilderAPI}, genesis, consensus}; /// Type that implements `futures::Stream` of block import events. @@ -366,9 +368,8 @@ impl Client where let block_num = *header.number(); let cht_num = cht::block_to_cht_number(cht_size, block_num).ok_or_else(proof_error)?; let cht_start = cht::start_number(cht_size, cht_num); - let headers = (cht_start.as_()..).map(|num| self.block_hash(As::sa(num)).unwrap_or_default()); - let proof = cht::build_proof::(cht_size, cht_num, block_num, headers) - .ok_or_else(proof_error)?; + let headers = (cht_start.as_()..).map(|num| self.block_hash(As::sa(num))); + let proof = cht::build_proof::(cht_size, cht_num, ::std::iter::once(block_num), headers)?; Ok((header, proof)) } @@ -402,6 +403,8 @@ impl Client where } /// Get proof for computation of (block, extrinsic) pairs where key has been changed at given blocks range. + /// `min` is the hash of the first block, which changes trie root is known to the requester - when we're using + /// changes tries from ascendants of this block, we should provide proofs for changes tries roots /// `max` is the hash of the last block known to the requester - we can't use changes tries from descendants /// of this block. /// Works only for runtimes that are supporting changes tries. @@ -409,9 +412,57 @@ impl Client where &self, first: Block::Hash, last: Block::Hash, + min: Block::Hash, max: Block::Hash, key: &[u8] - ) -> error::Result<(NumberFor, Vec>)> { + ) -> error::Result> { + self.key_changes_proof_with_cht_size( + first, + last, + min, + max, + key, + cht::SIZE, + ) + } + + /// Does the same work as `key_changes_proof`, but assumes that CHTs are of passed size. + pub fn key_changes_proof_with_cht_size( + &self, + first: Block::Hash, + last: Block::Hash, + min: Block::Hash, + max: Block::Hash, + key: &[u8], + cht_size: u64, + ) -> error::Result> { + struct AccessedRootsRecorder<'a, Block: BlockT> { + storage: &'a ChangesTrieStorage, + min: u64, + required_roots_proofs: Mutex, H256>>, + }; + + impl<'a, Block: BlockT> ChangesTrieRootsStorage for AccessedRootsRecorder<'a, Block> { + fn root(&self, anchor: &ChangesTrieAnchorBlockId, block: u64) -> Result, String> { + let root = self.storage.root(anchor, block)?; + if block < self.min { + if let Some(ref root) = root { + self.required_roots_proofs.lock().insert( + As::sa(block), + root.clone() + ); + } + } + Ok(root) + } + } + + impl<'a, Block: BlockT> ChangesTrieStorage for AccessedRootsRecorder<'a, Block> { + fn get(&self, key: &H256) -> Result, String> { + self.storage.get(key) + } + } + let config = self.changes_trie_config.as_ref(); let storage = self.backend.changes_trie_storage(); let (config, storage) = match (config, storage) { @@ -419,22 +470,76 @@ impl Client where _ => return Err(error::ErrorKind::ChangesTriesNotSupported.into()), }; + let min_number = self.require_block_number_from_id(&BlockId::Hash(min))?; + let recording_storage = AccessedRootsRecorder:: { + storage, + min: min_number.as_(), + required_roots_proofs: Mutex::new(BTreeMap::new()), + }; + let max_number = ::std::cmp::min( self.backend.blockchain().info()?.best_number, self.require_block_number_from_id(&BlockId::Hash(max))?, ); - key_changes_proof::<_, Blake2Hasher>( + + // fetch key changes proof + let key_changes_proof = key_changes_proof::<_, Blake2Hasher>( config, - storage, + &recording_storage, self.require_block_number_from_id(&BlockId::Hash(first))?.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()) - .map(|proof| (max_number, proof)) + key + ) + .map_err(|err| error::Error::from(error::ErrorKind::ChangesTrieAccessFailed(err)))?; + + // now gather proofs for all changes tries roots that were touched during key_changes_proof + // execution AND are unknown (i.e. replaced with CHT) to the requester + let roots = recording_storage.required_roots_proofs.into_inner(); + let roots_proof = self.changes_trie_roots_proof(cht_size, roots.keys().cloned())?; + + Ok(ChangesProof { + max_block: max_number, + proof: key_changes_proof, + roots: roots.into_iter().map(|(n, h)| (n, convert_hash(&h))).collect(), + roots_proof, + }) + } + + /// Generate CHT-based proof for roots of changes tries at given blocks. + fn changes_trie_roots_proof>>( + &self, + cht_size: u64, + blocks: I + ) -> error::Result>> { + // most probably we have touched several changes tries that are parts of the single CHT + // => GroupBy changes tries by CHT number and then gather proof for the whole group at once + let mut proof = HashSet::new(); + + cht::for_each_cht_group::(cht_size, blocks, |_, cht_num, cht_blocks| { + let cht_proof = self.changes_trie_roots_proof_at_cht(cht_size, cht_num, cht_blocks)?; + proof.extend(cht_proof); + Ok(()) + }, ())?; + + Ok(proof.into_iter().collect()) + } + + /// Generates CHT-based proof for roots of changes tries at given blocks (that are part of single CHT). + fn changes_trie_roots_proof_at_cht( + &self, + cht_size: u64, + cht_num: NumberFor, + blocks: Vec> + ) -> error::Result>> { + let cht_start = cht::start_number(cht_size, cht_num); + let roots = (cht_start.as_()..).map(|num| self.header(&BlockId::Number(As::sa(num))) + .map(|block| block.and_then(|block| block.digest().log(DigestItem::as_changes_trie_root).cloned()))); + let proof = cht::build_proof::(cht_size, cht_num, blocks, roots)?; + Ok(proof) } /// Create a new block, built on the head of the chain. @@ -1097,7 +1202,7 @@ pub(crate) mod tests { use super::*; use keyring::Keyring; use primitives::twox_128; - use runtime_primitives::traits::{Digest as DigestT, DigestItem as DigestItemT}; + use runtime_primitives::traits::DigestItem as DigestItemT; use runtime_primitives::generic::DigestItem; use test_client::{self, TestClient}; use consensus::BlockOrigin; diff --git a/substrate/core/client/src/error.rs b/substrate/core/client/src/error.rs index aa09344c99..2a793e8049 100644 --- a/substrate/core/client/src/error.rs +++ b/substrate/core/client/src/error.rs @@ -88,8 +88,8 @@ error_chain! { display("This method is not currently available when running in light client mode"), } - /// Invalid remote header proof. - InvalidHeaderProof { + /// Invalid remote CHT-based proof. + InvalidCHTProof { description("invalid header proof"), display("Remote node has responded with invalid header proof"), } @@ -135,6 +135,12 @@ error_chain! { description("Potential long-range attack: block not in finalized chain."), display("Potential long-range attack: block not in finalized chain."), } + + /// Hash that is required for building CHT is missing. + MissingHashRequiredForCHT(cht_num: u64, block_number: u64) { + description("missed hash required for building CHT"), + display("Failed to get hash of block#{} for building CHT#{}", block_number, cht_num), + } } } diff --git a/substrate/core/client/src/in_mem.rs b/substrate/core/client/src/in_mem.rs index 70adb97607..323ca3fdc3 100644 --- a/substrate/core/client/src/in_mem.rs +++ b/substrate/core/client/src/in_mem.rs @@ -94,7 +94,8 @@ struct BlockchainStorage { finalized_hash: Block::Hash, finalized_number: NumberFor, genesis_hash: Block::Hash, - cht_roots: HashMap, Block::Hash>, + header_cht_roots: HashMap, Block::Hash>, + changes_trie_cht_roots: HashMap, Block::Hash>, leaves: LeafSet>, } @@ -142,7 +143,8 @@ impl Blockchain { finalized_hash: Default::default(), finalized_number: Zero::zero(), genesis_hash: Default::default(), - cht_roots: HashMap::new(), + header_cht_roots: HashMap::new(), + changes_trie_cht_roots: HashMap::new(), leaves: LeafSet::new(), })); Blockchain { @@ -233,9 +235,9 @@ impl Blockchain { && this.genesis_hash == other.genesis_hash } - /// Insert CHT root. + /// Insert header CHT root. pub fn insert_cht_root(&self, block: NumberFor, cht_root: Block::Hash) { - self.storage.write().cht_roots.insert(block, cht_root); + self.storage.write().header_cht_roots.insert(block, cht_root); } fn finalize_header(&self, id: BlockId) -> error::Result<()> { @@ -339,9 +341,14 @@ impl light::blockchain::Storage for Blockchain Blockchain::finalize_header(self, id) } - fn cht_root(&self, _cht_size: u64, block: NumberFor) -> error::Result { - self.storage.read().cht_roots.get(&block).cloned() - .ok_or_else(|| error::ErrorKind::Backend(format!("CHT for block {} not exists", block)).into()) + fn header_cht_root(&self, _cht_size: u64, block: NumberFor) -> error::Result { + self.storage.read().header_cht_roots.get(&block).cloned() + .ok_or_else(|| error::ErrorKind::Backend(format!("Header CHT for block {} not exists", block)).into()) + } + + fn changes_trie_cht_root(&self, _cht_size: u64, block: NumberFor) -> error::Result { + self.storage.read().changes_trie_cht_roots.get(&block).cloned() + .ok_or_else(|| error::ErrorKind::Backend(format!("Changes trie CHT for block {} not exists", block)).into()) } fn cache(&self) -> Option<&blockchain::Cache> { diff --git a/substrate/core/client/src/light/blockchain.rs b/substrate/core/client/src/light/blockchain.rs index 97c20ecc90..5e8a81b413 100644 --- a/substrate/core/client/src/light/blockchain.rs +++ b/substrate/core/client/src/light/blockchain.rs @@ -48,8 +48,11 @@ pub trait Storage: BlockchainHeaderBackend { /// Get last finalized header. fn last_finalized(&self) -> ClientResult; - /// Get CHT root for given block. Fails if the block is not pruned (not a part of any CHT). - fn cht_root(&self, cht_size: u64, block: NumberFor) -> ClientResult; + /// Get headers CHT root for given block. Fails if the block is not pruned (not a part of any CHT). + fn header_cht_root(&self, cht_size: u64, block: NumberFor) -> ClientResult; + + /// Get changes trie CHT root for given block. Fails if the block is not pruned (not a part of any CHT). + fn changes_trie_cht_root(&self, cht_size: u64, block: NumberFor) -> ClientResult; /// Get storage cache. fn cache(&self) -> Option<&BlockchainCache>; @@ -106,7 +109,7 @@ impl BlockchainHeaderBackend for Blockchain where Bloc self.fetcher().upgrade().ok_or(ClientErrorKind::NotAvailableOnLightClient)? .remote_header(RemoteHeaderRequest { - cht_root: self.storage.cht_root(cht::SIZE, number)?, + cht_root: self.storage.header_cht_root(cht::SIZE, number)?, block: number, retry_count: None, }) @@ -155,3 +158,84 @@ impl BlockchainBackend for Blockchain where Block: Blo unimplemented!() } } + +#[cfg(test)] +pub mod tests { + use std::collections::HashMap; + use test_client::runtime::{Hash, Block, Header}; + use blockchain::Info; + use light::fetcher::tests::OkCallFetcher; + use super::*; + + pub type DummyBlockchain = Blockchain; + + pub struct DummyStorage { + pub changes_tries_cht_roots: HashMap, + } + + impl DummyStorage { + pub fn new() -> Self { + DummyStorage { + changes_tries_cht_roots: HashMap::new(), + } + } + } + + impl BlockchainHeaderBackend for DummyStorage { + fn header(&self, _id: BlockId) -> ClientResult> { + Err(ClientErrorKind::Backend("Test error".into()).into()) + } + + fn info(&self) -> ClientResult> { + Err(ClientErrorKind::Backend("Test error".into()).into()) + } + + fn status(&self, _id: BlockId) -> ClientResult { + Err(ClientErrorKind::Backend("Test error".into()).into()) + } + + fn number(&self, _hash: Hash) -> ClientResult>> { + Err(ClientErrorKind::Backend("Test error".into()).into()) + } + + fn hash(&self, _number: u64) -> ClientResult> { + Err(ClientErrorKind::Backend("Test error".into()).into()) + } + } + + impl Storage for DummyStorage { + fn import_header( + &self, + _header: Header, + _authorities: Option>, + _state: NewBlockState, + ) -> ClientResult<()> { + Err(ClientErrorKind::Backend("Test error".into()).into()) + } + + fn finalize_header(&self, _block: BlockId) -> ClientResult<()> { + Err(ClientErrorKind::Backend("Test error".into()).into()) + } + + fn last_finalized(&self) -> ClientResult { + Err(ClientErrorKind::Backend("Test error".into()).into()) + } + + fn header_cht_root(&self, _cht_size: u64, _block: u64) -> ClientResult { + Err(ClientErrorKind::Backend("Test error".into()).into()) + } + + fn changes_trie_cht_root(&self, cht_size: u64, block: u64) -> ClientResult { + cht::block_to_cht_number(cht_size, block) + .and_then(|cht_num| self.changes_tries_cht_roots.get(&cht_num)) + .cloned() + .ok_or_else(|| ClientErrorKind::Backend( + format!("Test error: CHT for block #{} not found", block) + ).into()) + } + + fn cache(&self) -> Option<&BlockchainCache> { + None + } + } +} diff --git a/substrate/core/client/src/light/fetcher.rs b/substrate/core/client/src/light/fetcher.rs index b75b74a0a9..6b7938bab7 100644 --- a/substrate/core/client/src/light/fetcher.rs +++ b/substrate/core/client/src/light/fetcher.rs @@ -16,19 +16,22 @@ //! Light client data fetcher. Fetches requested data from remote full nodes. +use std::sync::Arc; +use std::collections::BTreeMap; use std::marker::PhantomData; use futures::IntoFuture; -use hash_db::Hasher; +use hash_db::{HashDB, Hasher}; use heapsize::HeapSizeOf; 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}; + TrieBackend, read_proof_check, key_changes_proof_check, create_proof_check_backend_storage}; use call_executor::CallResult; use cht; use error::{Error as ClientError, ErrorKind as ClientErrorKind, Result as ClientResult}; +use light::blockchain::{Blockchain, Storage as BlockchainStorage}; use light::call_executor::check_execution_proof; /// Remote call request. @@ -83,15 +86,30 @@ pub struct RemoteChangesRequest { /// Only use digests from blocks up to this hash. Should be last_block OR come /// after this block and be the part of the same fork. pub max_block: (Header::Number, Header::Hash), - // TODO: get rid of this + preserve change_trie_roots when replacing headers with CHT!!! - /// Changes trie roots for the range of blocks [first_block..max_block]. - pub tries_roots: Vec, + /// Known changes trie roots for the range of blocks [tries_roots.0..max_block]. + /// Proofs for roots of ascendants of tries_roots.0 are provided by the remote node. + pub tries_roots: (Header::Number, Header::Hash, Vec), /// Storage key to read. pub key: Vec, /// Number of times to retry request. None means that default RETRY_COUNT is used. pub retry_count: Option, } +/// Key changes read proof. +#[derive(Debug, PartialEq, Eq)] +pub struct ChangesProof { + /// Max block that has been used in changes query. + pub max_block: Header::Number, + /// All touched nodes of all changes tries. + pub proof: Vec>, + /// All changes tries roots that have been touched AND are missing from + /// the requester' node. It is a map of block number => changes trie root. + pub roots: BTreeMap, + /// The proofs for all changes tries roots that have been touched AND are + /// missing from the requester' node. It is a map of CHT number => proof. + pub roots_proof: Vec>, +} + /// Light client data fetcher. Implementations of this trait must check if remote data /// is correct (see FetchedDataChecker) and return already checked data. pub trait Fetcher: Send + Sync { @@ -117,8 +135,8 @@ pub trait Fetcher: Send + Sync { /// Light client remote data checker. /// -/// Implementations of this trait should not use any blockchain data except that is -/// passed to its methods. +/// Implementations of this trait should not use any prunable blockchain data +/// except that is passed to its methods. pub trait FetchChecker: Send + Sync { /// Check remote header proof. fn check_header_proof( @@ -143,32 +161,158 @@ pub trait FetchChecker: Send + Sync { fn check_changes_proof( &self, request: &RemoteChangesRequest, - remote_max: NumberFor, - remote_proof: Vec> + proof: ChangesProof ) -> ClientResult, u32)>>; } /// Remote data checker. -pub struct LightDataChecker { +pub struct LightDataChecker, F> { + blockchain: Arc>, executor: E, - _hasher: PhantomData, + _hasher: PhantomData<(B, H)>, } -impl LightDataChecker { +impl, F> LightDataChecker { /// Create new light data checker. - pub fn new(executor: E) -> Self { + pub fn new(blockchain: Arc>, executor: E) -> Self { Self { - executor, _hasher: PhantomData + blockchain, executor, _hasher: PhantomData } } + + /// Check remote changes query proof assuming that CHT-s are of given size. + fn check_changes_proof_with_cht_size( + &self, + request: &RemoteChangesRequest, + remote_proof: ChangesProof, + cht_size: u64, + ) -> ClientResult, u32)>> + where + H: Hasher, + H::Out: Ord + HeapSizeOf, + { + // since we need roots of all changes tries for the range begin..max + // => remote node can't use max block greater that one that we have passed + if remote_proof.max_block > request.max_block.0 || remote_proof.max_block < request.last_block.0 { + return Err(ClientErrorKind::ChangesTrieAccessFailed(format!( + "Invalid max_block used by the remote node: {}. Local: {}..{}..{}", + remote_proof.max_block, request.first_block.0, request.last_block.0, request.max_block.0, + )).into()); + } + + // check if remote node has responded with extra changes trie roots proofs + // all changes tries roots must be in range [request.first_block.0; request.tries_roots.0) + let is_extra_first_root = remote_proof.roots.keys().next() + .map(|first_root| *first_root < request.first_block.0 + || *first_root >= request.tries_roots.0) + .unwrap_or(false); + let is_extra_last_root = remote_proof.roots.keys().next_back() + .map(|last_root| *last_root >= request.tries_roots.0) + .unwrap_or(false); + if is_extra_first_root || is_extra_last_root { + return Err(ClientErrorKind::ChangesTrieAccessFailed(format!( + "Extra changes tries roots proofs provided by the remote node: [{:?}..{:?}]. Expected in range: [{}; {})", + remote_proof.roots.keys().next(), remote_proof.roots.keys().next_back(), + request.first_block.0, request.tries_roots.0, + )).into()); + } + + // if request has been composed when some required headers were already pruned + // => remote node has sent us CHT-based proof of required changes tries roots + // => check that this proof is correct before proceeding with changes proof + let remote_max_block = remote_proof.max_block; + let remote_roots = remote_proof.roots; + let remote_roots_proof = remote_proof.roots_proof; + let remote_proof = remote_proof.proof; + if !remote_roots.is_empty() { + self.check_changes_tries_proof( + cht_size, + &remote_roots, + remote_roots_proof, + )?; + } + + // and now check the key changes proof + get the changes + key_changes_proof_check::<_, H>( + &request.changes_trie_config, + &RootsStorage { + roots: (request.tries_roots.0, &request.tries_roots.2), + prev_roots: remote_roots, + }, + remote_proof, + request.first_block.0.as_(), + &ChangesTrieAnchorBlockId { + hash: convert_hash(&request.last_block.1), + number: request.last_block.0.as_(), + }, + remote_max_block.as_(), + &request.key) + .map(|pairs| pairs.into_iter().map(|(b, x)| (As::sa(b), x)).collect()) + .map_err(|err| ClientErrorKind::ChangesTrieAccessFailed(err).into()) + } + + /// Check CHT-based proof for changes tries roots. + fn check_changes_tries_proof( + &self, + cht_size: u64, + remote_roots: &BTreeMap, B::Hash>, + remote_roots_proof: Vec>, + ) -> ClientResult<()> + where + H: Hasher, + H::Out: Ord + HeapSizeOf, + { + // all the checks are sharing the same storage + let storage = create_proof_check_backend_storage(remote_roots_proof); + + // we remote_roots.keys() are sorted => we can use this to group changes tries roots + // that are belongs to the same CHT + let blocks = remote_roots.keys().cloned(); + cht::for_each_cht_group::(cht_size, blocks, |mut storage, _, cht_blocks| { + // get local changes trie CHT root for given CHT + // it should be there, because it is never pruned AND request has been composed + // when required header has been pruned (=> replaced with CHT) + let first_block = cht_blocks.first().cloned() + .expect("for_each_cht_group never calls callback with empty groups"); + let local_cht_root = self.blockchain.storage().changes_trie_cht_root(cht_size, first_block)?; + + // check changes trie root for every block within CHT range + for block in cht_blocks { + // check if the proofs storage contains the root + // normally this happens in when the proving backend is created, but since + // we share the storage for multiple checks, do it here + let mut cht_root = H::Out::default(); + cht_root.as_mut().copy_from_slice(local_cht_root.as_ref()); + if !storage.contains(&cht_root) { + return Err(ClientErrorKind::InvalidCHTProof.into()); + } + + // check proof for single changes trie root + let proving_backend = TrieBackend::new(storage, cht_root); + let remote_changes_trie_root = remote_roots[&block]; + cht::check_proof_on_proving_backend::( + local_cht_root, + block, + remote_changes_trie_root, + &proving_backend)?; + + // and return the storage to use in following checks + storage = proving_backend.into_storage(); + } + + Ok(storage) + }, storage) + } } -impl FetchChecker for LightDataChecker +impl FetchChecker for LightDataChecker where Block: BlockT, E: CodeExecutor, H: Hasher, H::Out: Ord + HeapSizeOf, + S: BlockchainStorage, + F: Send + Sync, { fn check_header_proof( &self, @@ -177,7 +321,7 @@ impl FetchChecker for LightDataChecker remote_proof: Vec> ) -> ClientResult { let remote_header = remote_header.ok_or_else(|| - ClientError::from(ClientErrorKind::InvalidHeaderProof))?; + ClientError::from(ClientErrorKind::InvalidCHTProof))?; let remote_header_hash = remote_header.hash(); cht::check_proof::( request.cht_root, @@ -207,55 +351,39 @@ impl FetchChecker for LightDataChecker fn check_changes_proof( &self, request: &RemoteChangesRequest, - remote_max: NumberFor, - remote_proof: Vec> + remote_proof: ChangesProof ) -> ClientResult, u32)>> { - // since we need roots of all changes tries for the range begin..max - // => remote node can't use max block greater that one that we have passed - if remote_max > request.max_block.0 || remote_max < request.last_block.0 { - return Err(ClientErrorKind::ChangesTrieAccessFailed(format!( - "Invalid max_block used by the remote node: {}. Local: {}..{}..{}", - remote_max, request.first_block.0, request.last_block.0, request.max_block.0, - )).into()); - } - - let first_number = request.first_block.0.as_(); - key_changes_proof_check::<_, H>( - &request.changes_trie_config, - &RootsStorage { - first: first_number, - roots: &request.tries_roots, - }, - remote_proof, - first_number, - &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()) - .map_err(|err| ClientErrorKind::ChangesTrieAccessFailed(err).into()) + self.check_changes_proof_with_cht_size(request, remote_proof, cht::SIZE) } } -/// A view of HashMap as a changes trie roots storage. -struct RootsStorage<'a, Hash: 'a> { - first: u64, - roots: &'a [Hash], +/// A view of BTreeMap as a changes trie roots storage. +struct RootsStorage<'a, Number: As, Hash: 'a> { + roots: (Number, &'a [Hash]), + prev_roots: BTreeMap, } -impl<'a, H, Hash> ChangesTrieRootsStorage for RootsStorage<'a, Hash> +impl<'a, H, Number, Hash> ChangesTrieRootsStorage for RootsStorage<'a, Number, Hash> where H: Hasher, + Number: Send + Sync + Eq + ::std::cmp::Ord + Copy + As, Hash: 'a + Send + Sync + Clone + AsRef<[u8]>, { 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| convert_hash(&root))) + let root = if block < self.roots.0.as_() { + self.prev_roots.get(&As::sa(block)).cloned() + } else { + block.checked_sub(self.roots.0.as_()) + .and_then(|index| self.roots.1.get(index as usize)) + .cloned() + }; + + Ok(root.map(|root| { + let mut hasher_root: H::Out = Default::default(); + hasher_root.as_mut().copy_from_slice(root.as_ref()); + hasher_root + })) } } @@ -263,18 +391,20 @@ impl<'a, H, Hash> ChangesTrieRootsStorage for RootsStorage<'a, Hash> pub mod tests { use futures::future::{ok, err, FutureResult}; use parking_lot::Mutex; + use keyring::Keyring; use call_executor::CallResult; use client::tests::prepare_client_with_key_changes; use executor::{self, NativeExecutionDispatch}; use error::Error as ClientError; - use test_client::{self, TestClient}; + use test_client::{self, TestClient, blockchain::HeaderBackend}; use test_client::runtime::{self, Hash, Block, Header}; use consensus::BlockOrigin; use in_mem::{Blockchain as InMemoryBlockchain}; use light::fetcher::{Fetcher, FetchChecker, LightDataChecker, RemoteCallRequest, RemoteHeaderRequest}; - use primitives::{Blake2Hasher}; + use light::blockchain::tests::{DummyStorage, DummyBlockchain}; + use primitives::{twox_128, Blake2Hasher}; use primitives::storage::well_known_keys; use runtime_primitives::generic::BlockId; use state_machine::Backend; @@ -305,10 +435,9 @@ pub mod tests { } } - fn prepare_for_read_proof_check() -> ( - LightDataChecker, Blake2Hasher>, - Header, Vec>, usize) - { + type TestChecker = LightDataChecker, Blake2Hasher, Block, DummyStorage, OkCallFetcher>; + + fn prepare_for_read_proof_check() -> (TestChecker, Header, Vec>, usize) { // prepare remote client let remote_client = test_client::new(); let remote_block_id = BlockId::Number(0); @@ -330,21 +459,19 @@ pub mod tests { ::backend::NewBlockState::Final, ).unwrap(); let local_executor = test_client::LocalExecutor::new(); - let local_checker = LightDataChecker::new(local_executor); + let local_checker = LightDataChecker::new(Arc::new(DummyBlockchain::new(DummyStorage::new())), local_executor); (local_checker, remote_block_header, remote_read_proof, authorities_len) } - fn prepare_for_header_proof_check(insert_cht: bool) -> ( - LightDataChecker, Blake2Hasher>, - Hash, Header, Vec>) - { + fn prepare_for_header_proof_check(insert_cht: bool) -> (TestChecker, Hash, Header, Vec>) { // prepare remote client let remote_client = test_client::new(); let mut local_headers_hashes = Vec::new(); for i in 0..4 { let builder = remote_client.new_block().unwrap(); remote_client.justify_and_import(BlockOrigin::Own, builder.bake().unwrap()).unwrap(); - local_headers_hashes.push(remote_client.block_hash(i + 1).unwrap()); + local_headers_hashes.push(remote_client.block_hash(i + 1) + .map_err(|_| ClientErrorKind::Backend("TestError".into()).into())); } // 'fetch' header proof from remote node @@ -353,12 +480,12 @@ pub mod tests { // check remote read proof locally let local_storage = InMemoryBlockchain::::new(); - let local_cht_root = cht::compute_root::(4, 0, local_headers_hashes.into_iter()).unwrap(); + let local_cht_root = cht::compute_root::(4, 0, local_headers_hashes).unwrap(); if insert_cht { local_storage.insert_cht_root(1, local_cht_root); } let local_executor = test_client::LocalExecutor::new(); - let local_checker = LightDataChecker::new(local_executor); + let local_checker = LightDataChecker::new(Arc::new(DummyBlockchain::new(DummyStorage::new())), local_executor); (local_checker, local_cht_root, remote_block_header, remote_header_proof) } @@ -406,10 +533,12 @@ pub mod tests { } #[test] - fn changes_proof_is_generated_and_checked() { + fn changes_proof_is_generated_and_checked_when_headers_are_not_pruned() { let (remote_client, local_roots, test_cases) = prepare_client_with_key_changes(); - let local_checker = LightDataChecker::<_, Blake2Hasher>::new( - test_client::LocalExecutor::new()); + let local_checker = TestChecker::new( + Arc::new(DummyBlockchain::new(DummyStorage::new())), + test_client::LocalExecutor::new() + ); let local_checker = &local_checker as &FetchChecker; let max = remote_client.info().unwrap().chain.best_number; let max_hash = remote_client.info().unwrap().chain.best_hash; @@ -419,8 +548,8 @@ pub mod tests { let end_hash = remote_client.block_hash(end).unwrap().unwrap(); // 'fetch' changes proof from remote node - let (remote_max, remote_proof) = remote_client.key_changes_proof( - begin_hash, end_hash, max_hash, &key + let remote_proof = remote_client.key_changes_proof( + begin_hash, end_hash, begin_hash, max_hash, &key ).unwrap(); // check proof on local client @@ -430,12 +559,16 @@ pub mod tests { first_block: (begin, begin_hash), last_block: (end, end_hash), max_block: (max, max_hash), - tries_roots: local_roots_range, + tries_roots: (begin, begin_hash, local_roots_range), key: key, retry_count: None, }; - let local_result = local_checker.check_changes_proof( - &request, remote_max, remote_proof).unwrap(); + let local_result = local_checker.check_changes_proof(&request, ChangesProof { + max_block: remote_proof.max_block, + proof: remote_proof.proof, + roots: remote_proof.roots, + roots_proof: remote_proof.roots_proof, + }).unwrap(); // ..and ensure that result is the same as on remote node match local_result == expected_result { @@ -446,11 +579,60 @@ pub mod tests { } } + #[test] + fn changes_proof_is_generated_and_checked_when_headers_are_pruned() { + // we're testing this test case here: + // (1, 4, dave.clone(), vec![(4, 0), (1, 1), (1, 0)]), + let (remote_client, remote_roots, _) = prepare_client_with_key_changes(); + let dave = twox_128(&runtime::system::balance_of_key(Keyring::Dave.to_raw_public().into())).to_vec(); + + // 'fetch' changes proof from remote node: + // we're fetching changes for range b1..b4 + // we do not know changes trie roots before b3 (i.e. we only know b3+b4) + // but we have changes trie CHT root for b1...b4 + let b1 = remote_client.block_hash_from_id(&BlockId::Number(1)).unwrap().unwrap(); + let b3 = remote_client.block_hash_from_id(&BlockId::Number(3)).unwrap().unwrap(); + let b4 = remote_client.block_hash_from_id(&BlockId::Number(4)).unwrap().unwrap(); + let remote_proof = remote_client.key_changes_proof_with_cht_size( + b1, b4, b3, b4, &dave, 4 + ).unwrap(); + + // prepare local checker, having a root of changes trie CHT#0 + let local_cht_root = cht::compute_root::(4, 0, remote_roots.iter().cloned().map(|ct| Ok(Some(ct)))).unwrap(); + let mut local_storage = DummyStorage::new(); + local_storage.changes_tries_cht_roots.insert(0, local_cht_root); + let local_checker = TestChecker::new( + Arc::new(DummyBlockchain::new(local_storage)), + test_client::LocalExecutor::new() + ); + + // check proof on local client + let request = RemoteChangesRequest::

{ + changes_trie_config: runtime::changes_trie_config(), + first_block: (1, b1), + last_block: (4, b4), + max_block: (4, b4), + tries_roots: (3, b3, vec![remote_roots[2].clone(), remote_roots[3].clone()]), + key: dave, + retry_count: None, + }; + let local_result = local_checker.check_changes_proof_with_cht_size(&request, ChangesProof { + max_block: remote_proof.max_block, + proof: remote_proof.proof, + roots: remote_proof.roots, + roots_proof: remote_proof.roots_proof, + }, 4).unwrap(); + + assert_eq!(local_result, vec![(4, 0), (1, 1), (1, 0)]); + } + #[test] fn check_changes_proof_fails_if_proof_is_wrong() { let (remote_client, local_roots, test_cases) = prepare_client_with_key_changes(); - let local_checker = LightDataChecker::<_, Blake2Hasher>::new( - test_client::LocalExecutor::new()); + let local_checker = TestChecker::new( + Arc::new(DummyBlockchain::new(DummyStorage::new())), + test_client::LocalExecutor::new() + ); let local_checker = &local_checker as &FetchChecker; let max = remote_client.info().unwrap().chain.best_number; let max_hash = remote_client.info().unwrap().chain.best_hash; @@ -460,24 +642,86 @@ pub mod tests { let end_hash = remote_client.block_hash(end).unwrap().unwrap(); // 'fetch' changes proof from remote node - let (remote_max, mut remote_proof) = remote_client.key_changes_proof( - begin_hash, end_hash, max_hash, &key).unwrap(); + let remote_proof = remote_client.key_changes_proof( + begin_hash, end_hash, begin_hash, max_hash, &key).unwrap(); + let local_roots_range = local_roots.clone()[(begin - 1) as usize..].to_vec(); let request = RemoteChangesRequest::
{ changes_trie_config: runtime::changes_trie_config(), first_block: (begin, begin_hash), last_block: (end, end_hash), max_block: (max, max_hash), - tries_roots: local_roots_range.clone(), + tries_roots: (begin, begin_hash, local_roots_range.clone()), key: key, retry_count: None, }; // check proof on local client using max from the future - assert!(local_checker.check_changes_proof(&request, remote_max + 1, remote_proof.clone()).is_err()); + assert!(local_checker.check_changes_proof(&request, ChangesProof { + max_block: remote_proof.max_block + 1, + proof: remote_proof.proof.clone(), + roots: remote_proof.roots.clone(), + roots_proof: remote_proof.roots_proof.clone(), + }).is_err()); // check proof on local client using broken proof - remote_proof = local_roots_range.into_iter().map(|v| v.as_bytes().to_vec()).collect(); - assert!(local_checker.check_changes_proof(&request, remote_max, remote_proof).is_err()); + assert!(local_checker.check_changes_proof(&request, ChangesProof { + max_block: remote_proof.max_block, + proof: local_roots_range.clone().into_iter().map(|v| v.as_ref().to_vec()).collect(), + roots: remote_proof.roots, + roots_proof: remote_proof.roots_proof, + }).is_err()); + + // extra roots proofs are provided + assert!(local_checker.check_changes_proof(&request, ChangesProof { + max_block: remote_proof.max_block, + proof: remote_proof.proof.clone(), + roots: vec![(begin - 1, Default::default())].into_iter().collect(), + roots_proof: vec![], + }).is_err()); + assert!(local_checker.check_changes_proof(&request, ChangesProof { + max_block: remote_proof.max_block, + proof: remote_proof.proof.clone(), + roots: vec![(end + 1, Default::default())].into_iter().collect(), + roots_proof: vec![], + }).is_err()); + } + + #[test] + fn check_changes_tries_proof_fails_if_proof_is_wrong() { + // we're testing this test case here: + // (1, 4, dave.clone(), vec![(4, 0), (1, 1), (1, 0)]), + let (remote_client, remote_roots, _) = prepare_client_with_key_changes(); + let local_cht_root = cht::compute_root::( + 4, 0, remote_roots.iter().cloned().map(|ct| Ok(Some(ct)))).unwrap(); + let dave = twox_128(&runtime::system::balance_of_key(Keyring::Dave.to_raw_public().into())).to_vec(); + + // 'fetch' changes proof from remote node: + // we're fetching changes for range b1..b4 + // we do not know changes trie roots before b3 (i.e. we only know b3+b4) + // but we have changes trie CHT root for b1...b4 + let b1 = remote_client.block_hash_from_id(&BlockId::Number(1)).unwrap().unwrap(); + let b3 = remote_client.block_hash_from_id(&BlockId::Number(3)).unwrap().unwrap(); + let b4 = remote_client.block_hash_from_id(&BlockId::Number(4)).unwrap().unwrap(); + let remote_proof = remote_client.key_changes_proof_with_cht_size( + b1, b4, b3, b4, &dave, 4 + ).unwrap(); + + // fails when changes trie CHT is missing from the local db + let local_checker = TestChecker::new( + Arc::new(DummyBlockchain::new(DummyStorage::new())), + test_client::LocalExecutor::new() + ); + assert!(local_checker.check_changes_tries_proof(4, &remote_proof.roots, + remote_proof.roots_proof.clone()).is_err()); + + // fails when proof is broken + let mut local_storage = DummyStorage::new(); + local_storage.changes_tries_cht_roots.insert(0, local_cht_root); + let local_checker = TestChecker::new( + Arc::new(DummyBlockchain::new(local_storage)), + test_client::LocalExecutor::new() + ); + assert!(local_checker.check_changes_tries_proof(4, &remote_proof.roots, vec![]).is_err()); } } diff --git a/substrate/core/client/src/light/mod.rs b/substrate/core/client/src/light/mod.rs index 29db81a001..8727455d30 100644 --- a/substrate/core/client/src/light/mod.rs +++ b/substrate/core/client/src/light/mod.rs @@ -65,13 +65,14 @@ where } /// Create an instance of fetch data checker. -pub fn new_fetch_checker( +pub fn new_fetch_checker, F>( + blockchain: Arc>, executor: E, -) -> LightDataChecker +) -> LightDataChecker where E: CodeExecutor, H: Hasher, { - LightDataChecker::new(executor) + LightDataChecker::new(blockchain, executor) } diff --git a/substrate/core/network/src/chain.rs b/substrate/core/network/src/chain.rs index 32cb18f276..8547f9006e 100644 --- a/substrate/core/network/src/chain.rs +++ b/substrate/core/network/src/chain.rs @@ -18,8 +18,9 @@ use client::{self, Client as SubstrateClient, ClientInfo, BlockStatus, CallExecutor}; use client::error::Error; +use client::light::fetcher::ChangesProof; use consensus::BlockImport; -use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, NumberFor}; +use runtime_primitives::traits::{Block as BlockT, Header as HeaderT}; use runtime_primitives::generic::{BlockId}; use consensus::{ImportBlock, ImportResult}; use runtime_primitives::Justification; @@ -63,9 +64,10 @@ pub trait Client: Send + Sync { &self, first: Block::Hash, last: Block::Hash, + min: Block::Hash, max: Block::Hash, key: &[u8] - ) -> Result<(NumberFor, Vec>), Error>; + ) -> Result, Error>; } impl Client for SubstrateClient where @@ -121,9 +123,10 @@ impl Client for SubstrateClient where &self, first: Block::Hash, last: Block::Hash, + min: Block::Hash, max: Block::Hash, key: &[u8] - ) -> Result<(NumberFor, Vec>), Error> { - (self as &SubstrateClient).key_changes_proof(first, last, max, key) + ) -> Result, Error> { + (self as &SubstrateClient).key_changes_proof(first, last, min, max, key) } } diff --git a/substrate/core/network/src/message.rs b/substrate/core/network/src/message.rs index 40cd9a435d..519d7c78eb 100644 --- a/substrate/core/network/src/message.rs +++ b/substrate/core/network/src/message.rs @@ -189,7 +189,7 @@ pub mod generic { /// Remote changes request. RemoteChangesRequest(RemoteChangesRequest), /// Remote changes reponse. - RemoteChangesResponse(RemoteChangesResponse), + RemoteChangesResponse(RemoteChangesResponse), /// Chain-specific message #[codec(index = "255")] ChainSpecific(Vec), @@ -298,6 +298,9 @@ pub mod generic { pub first: H, /// Hash of the last block of the range (including last) where changes are requested. pub last: H, + /// Hash of the first block for which the requester has the changes trie root. All other + /// affected roots must be proved. + pub min: H, /// Hash of the last block that we can use when querying changes. pub max: H, /// Storage key which changes are requested. @@ -306,7 +309,7 @@ pub mod generic { #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode)] /// Remote changes response. - pub struct RemoteChangesResponse { + pub struct RemoteChangesResponse { /// Id of a request this response was made for. pub id: RequestId, /// Proof has been generated using block with this number as a max block. Should be @@ -314,5 +317,9 @@ pub mod generic { pub max: N, /// Changes proof. pub proof: Vec>, + /// Changes tries roots missing on the requester' node. + pub roots: Vec<(N, H)>, + /// Missing changes tries roots proof. + pub roots_proof: Vec>, } } diff --git a/substrate/core/network/src/on_demand.rs b/substrate/core/network/src/on_demand.rs index 5fbf893714..59a5333a47 100644 --- a/substrate/core/network/src/on_demand.rs +++ b/substrate/core/network/src/on_demand.rs @@ -26,7 +26,7 @@ use linked_hash_map::Entry; use parking_lot::Mutex; use client::{self, error::{Error as ClientError, ErrorKind as ClientErrorKind}}; use client::light::fetcher::{Fetcher, FetchChecker, RemoteHeaderRequest, - RemoteCallRequest, RemoteReadRequest, RemoteChangesRequest}; + RemoteCallRequest, RemoteReadRequest, RemoteChangesRequest, ChangesProof}; use io::SyncIo; use message; use network_libp2p::{Severity, NodeIndex}; @@ -72,7 +72,7 @@ pub trait OnDemandService: Send + Sync { &self, io: &mut SyncIo, peer: NodeIndex, - response: message::RemoteChangesResponse> + response: message::RemoteChangesResponse, Block::Hash> ); } @@ -284,11 +284,15 @@ impl OnDemandService for OnDemand where }) } - fn on_remote_changes_response(&self, io: &mut SyncIo, peer: NodeIndex, response: message::RemoteChangesResponse>) { + fn on_remote_changes_response(&self, io: &mut SyncIo, peer: NodeIndex, response: message::RemoteChangesResponse, B::Hash>) { self.accept_response("changes", io, peer, response.id, |request| match request.data { RequestData::RemoteChanges(request, sender) => match self.checker.check_changes_proof( - &request, response.max, response.proof - ) { + &request, ChangesProof { + max_block: response.max, + proof: response.proof, + roots: response.roots.into_iter().collect(), + roots_proof: response.roots_proof, + }) { Ok(response) => { // we do not bother if receiver has been dropped already let _ = sender.send(Ok(response)); @@ -483,6 +487,7 @@ impl Request { id: self.id, first: data.first_block.1.clone(), last: data.last_block.1.clone(), + min: data.tries_roots.1.clone(), max: data.max_block.1.clone(), key: data.key.clone(), }), @@ -509,9 +514,10 @@ pub mod tests { use std::time::Instant; use futures::Future; use parking_lot::RwLock; + use runtime_primitives::traits::NumberFor; use client::{self, error::{ErrorKind as ClientErrorKind, Result as ClientResult}}; use client::light::fetcher::{Fetcher, FetchChecker, RemoteHeaderRequest, - RemoteCallRequest, RemoteReadRequest, RemoteChangesRequest}; + RemoteCallRequest, RemoteReadRequest, RemoteChangesRequest, ChangesProof}; use config::Roles; use message; use network_libp2p::NodeIndex; @@ -557,7 +563,7 @@ pub mod tests { } } - fn check_changes_proof(&self, _: &RemoteChangesRequest
, _: u64, _: Vec>) -> ClientResult> { + fn check_changes_proof(&self, _: &RemoteChangesRequest
, _: ChangesProof
) -> ClientResult, u32)>> { match self.ok { true => Ok(vec![(100, 2)]), false => Err(ClientErrorKind::Backend("Test error".into()).into()), @@ -855,7 +861,7 @@ pub mod tests { first_block: (1, Default::default()), last_block: (100, Default::default()), max_block: (100, Default::default()), - tries_roots: vec![], + tries_roots: (1, Default::default(), vec![]), key: vec![], retry_count: None, }); @@ -868,6 +874,8 @@ pub mod tests { id: 0, max: 1000, proof: vec![vec![2]], + roots: vec![], + roots_proof: vec![], }); thread.join().unwrap(); } diff --git a/substrate/core/network/src/protocol.rs b/substrate/core/network/src/protocol.rs index af20a6b1bc..932e724bfe 100644 --- a/substrate/core/network/src/protocol.rs +++ b/substrate/core/network/src/protocol.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -use std::collections::{HashMap, HashSet}; +use std::collections::{HashMap, HashSet, BTreeMap}; use std::{mem, cmp}; use std::sync::Arc; use std::time; @@ -33,6 +33,7 @@ use service::{TransactionPool, ExHashT}; use import_queue::ImportQueue; use config::{ProtocolConfig, Roles}; use chain::Client; +use client::light::fetcher::ChangesProof; use on_demand::OnDemandService; use io::SyncIo; use error; @@ -673,20 +674,29 @@ impl, H: ExHashT> Protocol { fn on_remote_changes_request(&self, io: &mut SyncIo, who: NodeIndex, request: message::RemoteChangesRequest) { trace!(target: "sync", "Remote changes proof request {} from {} for key {} ({}..{})", request.id, who, request.key.to_hex(), request.first, request.last); - let (max, proof) = match self.context_data.chain.key_changes_proof(request.first, request.last, request.max, &request.key) { - Ok((max, proof)) => (max, proof), + let proof = match self.context_data.chain.key_changes_proof(request.first, request.last, request.min, request.max, &request.key) { + Ok(proof) => proof, Err(error) => { trace!(target: "sync", "Remote changes proof request {} from {} for key {} ({}..{}) failed with: {}", request.id, who, request.key.to_hex(), request.first, request.last, error); - (Zero::zero(), Default::default()) + ChangesProof:: { + max_block: Zero::zero(), + proof: vec![], + roots: BTreeMap::new(), + roots_proof: vec![], + } }, }; self.send_message(io, who, GenericMessage::RemoteChangesResponse(message::RemoteChangesResponse { - id: request.id, max, proof, + id: request.id, + max: proof.max_block, + proof: proof.proof, + roots: proof.roots.into_iter().collect(), + roots_proof: proof.roots_proof, })); } - fn on_remote_changes_response(&self, io: &mut SyncIo, who: NodeIndex, response: message::RemoteChangesResponse>) { + fn on_remote_changes_response(&self, io: &mut SyncIo, who: NodeIndex, response: message::RemoteChangesResponse, B::Hash>) { trace!(target: "sync", "Remote changes proof response {} from {} (max={})", response.id, who, response.max); self.on_demand.as_ref().map(|s| s.on_remote_changes_response(io, who, response)); diff --git a/substrate/core/service/src/components.rs b/substrate/core/service/src/components.rs index 0325b75e9e..379e592238 100644 --- a/substrate/core/service/src/components.rs +++ b/substrate/core/service/src/components.rs @@ -471,7 +471,7 @@ impl Components for LightComponents { }; let db_storage = client_db::light::LightStorage::new(db_settings)?; let light_blockchain = client::light::new_light_blockchain(db_storage); - let fetch_checker = Arc::new(client::light::new_fetch_checker::<_, Blake2Hasher>(executor)); + let fetch_checker = Arc::new(client::light::new_fetch_checker::<_, Blake2Hasher, _, _, _>(light_blockchain.clone(), executor)); let fetcher = Arc::new(network::OnDemand::new(fetch_checker)); let client_backend = client::light::new_light_backend(light_blockchain, fetcher.clone()); let client = client::light::new_light(client_backend, fetcher.clone(), &config.chain_spec)?; diff --git a/substrate/core/state-machine/src/lib.rs b/substrate/core/state-machine/src/lib.rs index e88b3f370a..0b7f1ccd6d 100644 --- a/substrate/core/state-machine/src/lib.rs +++ b/substrate/core/state-machine/src/lib.rs @@ -61,6 +61,7 @@ pub use changes_trie::{ key_changes, key_changes_proof, key_changes_proof_check, prune as prune_changes_tries}; pub use overlayed_changes::OverlayedChanges; +pub use proving_backend::create_proof_check_backend_storage; pub use trie_backend_essence::Storage; pub use trie_backend::TrieBackend; @@ -403,7 +404,7 @@ where { let trie_backend = backend.try_into_trie_backend() .ok_or_else(|| Box::new(ExecutionError::UnableToGenerateProof) as Box)?; - let proving_backend = proving_backend::ProvingBackend::new(trie_backend); + let proving_backend = proving_backend::ProvingBackend::new(&trie_backend); let (result, _, _) = execute::, _>( &proving_backend, None, @@ -444,11 +445,24 @@ pub fn prove_read( where B: Backend, H: Hasher, - H::Out: Ord + HeapSizeOf { + let trie_backend = backend.try_into_trie_backend() .ok_or_else(|| Box::new(ExecutionError::UnableToGenerateProof) as Box)?; + prove_read_on_trie_backend(&trie_backend, key) +} + +/// Generate storage read proof on pre-created trie backend. +pub fn prove_read_on_trie_backend( + trie_backend: &TrieBackend, + key: &[u8] +) -> Result<(Option>, Vec>), Box> +where + S: trie_backend_essence::TrieBackendStorage, + H: Hasher, + H::Out: Ord + HeapSizeOf +{ let proving_backend = proving_backend::ProvingBackend::<_, H>::new(trie_backend); let result = proving_backend.storage(key).map_err(|e| Box::new(e) as Box)?; Ok((result, proving_backend.extract_proof())) @@ -462,11 +476,22 @@ pub fn read_proof_check( ) -> Result>, Box> where H: Hasher, - H::Out: Ord + HeapSizeOf { - let backend = proving_backend::create_proof_check_backend::(root, proof)?; - backend.storage(key).map_err(|e| Box::new(e) as Box) + let proving_backend = proving_backend::create_proof_check_backend::(root, proof)?; + read_proof_check_on_proving_backend(&proving_backend, key) +} + +/// Check storage read proof on pre-created proving backend. +pub fn read_proof_check_on_proving_backend( + proving_backend: &TrieBackend, H>, + key: &[u8], +) -> Result>, Box> +where + H: Hasher, + H::Out: Ord + HeapSizeOf +{ + proving_backend.storage(key).map_err(|e| Box::new(e) as Box) } /// Sets overlayed changes' changes trie configuration. Returns error if configuration diff --git a/substrate/core/state-machine/src/proving_backend.rs b/substrate/core/state-machine/src/proving_backend.rs index a46fd74b5f..85afc0b3dc 100644 --- a/substrate/core/state-machine/src/proving_backend.rs +++ b/substrate/core/state-machine/src/proving_backend.rs @@ -84,14 +84,14 @@ impl<'a, S, H> ProvingBackendEssence<'a, S, H> /// Patricia trie-based backend which also tracks all touched storage trie values. /// These can be sent to remote node and used as a proof of execution. -pub struct ProvingBackend, H: Hasher> { - backend: TrieBackend, +pub struct ProvingBackend<'a, S: 'a + TrieBackendStorage, H: 'a + Hasher> { + backend: &'a TrieBackend, proof_recorder: RefCell>, } -impl, H: Hasher> ProvingBackend { +impl<'a, S: 'a + TrieBackendStorage, H: 'a + Hasher> ProvingBackend<'a, S, H> { /// Create new proving backend. - pub fn new(backend: TrieBackend) -> Self { + pub fn new(backend: &'a TrieBackend) -> Self { ProvingBackend { backend, proof_recorder: RefCell::new(Recorder::new()), @@ -108,10 +108,10 @@ impl, H: Hasher> ProvingBackend { } } -impl Backend for ProvingBackend +impl<'a, S, H> Backend for ProvingBackend<'a, S, H> where - S: TrieBackendStorage, - H: Hasher, + S: 'a + TrieBackendStorage, + H: 'a + Hasher, H::Out: Ord + HeapSizeOf, { type Error = String; @@ -174,10 +174,7 @@ where H: Hasher, H::Out: HeapSizeOf, { - let mut db = MemoryDB::default(); // TODO: use new for correctness - for item in proof { - db.insert(&item); - } + let db = create_proof_check_backend_storage(proof); if !db.contains(&root) { return Err(Box::new(ExecutionError::InvalidProof) as Box); @@ -186,6 +183,21 @@ where Ok(TrieBackend::new(db, root)) } +/// Create in-memory storage of proof check backend. +pub fn create_proof_check_backend_storage( + proof: Vec> +) -> MemoryDB +where + H: Hasher, + H::Out: HeapSizeOf, +{ + let mut db = MemoryDB::default(); // TODO: use new for correctness + for item in proof { + db.insert(&item); + } + db +} + #[cfg(test)] mod tests { use backend::{InMemory}; @@ -193,18 +205,20 @@ mod tests { use super::*; use primitives::{Blake2Hasher}; - fn test_proving() -> ProvingBackend, Blake2Hasher> { - ProvingBackend::new(test_trie()) + fn test_proving<'a>(trie_backend: &'a TrieBackend, Blake2Hasher>) -> ProvingBackend<'a, MemoryDB, Blake2Hasher> { + ProvingBackend::new(trie_backend) } #[test] fn proof_is_empty_until_value_is_read() { - assert!(test_proving().extract_proof().is_empty()); + let trie_backend = test_trie(); + assert!(test_proving(&trie_backend).extract_proof().is_empty()); } #[test] fn proof_is_non_empty_after_value_is_read() { - let backend = test_proving(); + let trie_backend = test_trie(); + let backend = test_proving(&trie_backend); assert_eq!(backend.storage(b"key").unwrap(), Some(b"value".to_vec())); assert!(!backend.extract_proof().is_empty()); } @@ -218,7 +232,7 @@ mod tests { #[test] fn passes_throgh_backend_calls() { let trie_backend = test_trie(); - let proving_backend = test_proving(); + let proving_backend = test_proving(&trie_backend); assert_eq!(trie_backend.storage(b"key").unwrap(), proving_backend.storage(b"key").unwrap()); assert_eq!(trie_backend.pairs(), proving_backend.pairs()); @@ -241,7 +255,7 @@ mod tests { assert_eq!(in_memory_root, trie_root); (0..64).for_each(|i| assert_eq!(trie.storage(&[i]).unwrap().unwrap(), vec![i])); - let proving = ProvingBackend::new(trie); + let proving = ProvingBackend::new(&trie); assert_eq!(proving.storage(&[42]).unwrap().unwrap(), vec![42]); let proof = proving.extract_proof(); diff --git a/substrate/core/state-machine/src/trie_backend.rs b/substrate/core/state-machine/src/trie_backend.rs index 6dcaf3868e..f29608b5d1 100644 --- a/substrate/core/state-machine/src/trie_backend.rs +++ b/substrate/core/state-machine/src/trie_backend.rs @@ -49,6 +49,11 @@ impl, H: Hasher> TrieBackend where H::Out: HeapSi pub fn root(&self) -> &H::Out { self.essence.root() } + + /// Consumes self and returns underlying storage. + pub fn into_storage(self) -> S { + self.essence.into_storage() + } } impl super::Error for String {} diff --git a/substrate/core/state-machine/src/trie_backend_essence.rs b/substrate/core/state-machine/src/trie_backend_essence.rs index 56424ae022..2c2281b730 100644 --- a/substrate/core/state-machine/src/trie_backend_essence.rs +++ b/substrate/core/state-machine/src/trie_backend_essence.rs @@ -56,6 +56,11 @@ impl, H: Hasher> TrieBackendEssence where H::Out: &self.root } + /// Consumes self and returns underlying storage. + pub fn into_storage(self) -> S { + self.storage + } + /// Get the value of storage at given key. pub fn storage(&self, key: &[u8]) -> Result>, String> { let mut read_overlay = MemoryDB::default(); diff --git a/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm b/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm index 76ec888e14..27b4e0e4ca 100644 Binary files a/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm and b/substrate/core/test-runtime/wasm/target/wasm32-unknown-unknown/release/substrate_test_runtime.compact.wasm differ diff --git a/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm b/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm index 766de4bda3..488086fdbd 100644 Binary files a/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm and b/substrate/node/runtime/wasm/target/wasm32-unknown-unknown/release/node_runtime.compact.wasm differ