diff --git a/substrate/core/client/db/src/lib.rs b/substrate/core/client/db/src/lib.rs index b39fbb9f06adccc4d3de8b97f787814a42748f91..99483b428882c5617746c8864944f6e8468bb4c8 100644 --- a/substrate/core/client/db/src/lib.rs +++ b/substrate/core/client/db/src/lib.rs @@ -52,6 +52,7 @@ use executor::RuntimeInfo; use state_machine::{CodeExecutor, DBValue}; use crate::utils::{Meta, db_err, meta_keys, open_database, read_db, block_id_to_lookup_key, read_meta}; use client::LeafSet; +use client::children; use state_db::StateDb; use crate::storage_cache::{CachingState, SharedCache, new_shared_cache}; use log::{trace, debug, warn}; @@ -249,6 +250,10 @@ impl<Block: BlockT> client::blockchain::Backend<Block> for BlockchainDb<Block> { fn leaves(&self) -> Result<Vec<Block::Hash>, client::error::Error> { Ok(self.leaves.read().hashes()) } + + fn children(&self, parent_hash: Block::Hash) -> Result<Vec<Block::Hash>, client::error::Error> { + children::read_children(&*self.db, columns::META, meta_keys::CHILDREN_PREFIX, parent_hash) + } } /// Database transaction @@ -857,6 +862,10 @@ impl<Block: BlockT<Hash=H256>> Backend<Block> { displaced_leaf }; + let mut children = children::read_children(&*self.storage.db, columns::META, meta_keys::CHILDREN_PREFIX, parent_hash)?; + children.push(hash); + children::write_children(&mut transaction, columns::META, meta_keys::CHILDREN_PREFIX, parent_hash, children); + meta_updates.push((hash, number, pending_block.leaf_state.is_best(), finalized)); Some((number, hash, enacted, retracted, displaced_leaf, is_best)) @@ -1080,6 +1089,7 @@ impl<Block> client::backend::Backend<Block, Blake2Hasher> for Backend<Block> whe let key = utils::number_and_hash_to_lookup_key(best.clone(), &hash); transaction.put(columns::META, meta_keys::BEST_BLOCK, &key); transaction.delete(columns::KEY_LOOKUP, removed.hash().as_ref()); + children::remove_children(&mut transaction, columns::META, meta_keys::CHILDREN_PREFIX, hash); self.storage.db.write(transaction).map_err(db_err)?; self.blockchain.update_meta(hash, best, true, false); self.blockchain.leaves.write().revert(removed.hash().clone(), removed.number().clone(), removed.parent_hash().clone()); @@ -1793,6 +1803,12 @@ mod tests { test_client::trait_tests::test_leaves_for_backend(backend); } + #[test] + fn test_children_with_complex_block_tree() { + let backend: Arc<Backend<test_client::runtime::Block>> = Arc::new(Backend::new_test(20, 20)); + test_client::trait_tests::test_children_for_backend(backend); + } + #[test] fn test_blockchain_query_by_number_gets_canonical() { let backend: Arc<Backend<test_client::runtime::Block>> = Arc::new(Backend::new_test(20, 20)); diff --git a/substrate/core/client/db/src/utils.rs b/substrate/core/client/db/src/utils.rs index f1e4f3d2e23256a92391dbad1d342c268a59aef7..150c1fdd98ccdef5b596735f2e523bd179a3a35f 100644 --- a/substrate/core/client/db/src/utils.rs +++ b/substrate/core/client/db/src/utils.rs @@ -51,6 +51,8 @@ pub mod meta_keys { pub const GENESIS_HASH: &[u8; 3] = b"gen"; /// Leaves prefix list key. pub const LEAF_PREFIX: &[u8; 4] = b"leaf"; + /// Children prefix list key. + pub const CHILDREN_PREFIX: &[u8; 8] = b"children"; } /// Database metadata. diff --git a/substrate/core/client/src/blockchain.rs b/substrate/core/client/src/blockchain.rs index 986360764d3dacf3b2608a9c6bc02ab45e3272d4..eb9ce1342eade2c58ef154d583a302f4ddb54987 100644 --- a/substrate/core/client/src/blockchain.rs +++ b/substrate/core/client/src/blockchain.rs @@ -84,6 +84,9 @@ pub trait Backend<Block: BlockT>: HeaderBackend<Block> { /// in other words, that have no children, are chain heads. /// Results must be ordered best (longest, heighest) chain first. fn leaves(&self) -> Result<Vec<Block::Hash>>; + + /// Return hashes of all blocks that are children of the block with `parent_hash`. + fn children(&self, parent_hash: Block::Hash) -> Result<Vec<Block::Hash>>; } /// Blockchain optional data cache. diff --git a/substrate/core/client/src/children.rs b/substrate/core/client/src/children.rs new file mode 100644 index 0000000000000000000000000000000000000000..48b39d18cdd60d8c11585aae71d30ce1ab9ce335 --- /dev/null +++ b/substrate/core/client/src/children.rs @@ -0,0 +1,121 @@ +// Copyright 2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see <http://www.gnu.org/licenses/>. + +//! Functionality for reading and storing children hashes from db. + +use kvdb::{KeyValueDB, DBTransaction}; +use parity_codec::{Encode, Decode}; +use crate::error; +use std::hash::Hash; + + +/// Returns the hashes of the children blocks of the block with `parent_hash`. +pub fn read_children< + K: Eq + Hash + Clone + Encode + Decode, + V: Eq + Hash + Clone + Encode + Decode, +>(db: &KeyValueDB, column: Option<u32>, prefix: &[u8], parent_hash: K) -> error::Result<Vec<V>> { + let mut buf = prefix.to_vec(); + parent_hash.using_encoded(|s| buf.extend(s)); + + let raw_val_opt = match db.get(column, &buf[..]) { + Ok(raw_val_opt) => raw_val_opt, + Err(_) => return Err(error::ErrorKind::Backend("Error reading value from database".into()).into()), + }; + + let raw_val = match raw_val_opt { + Some(val) => val, + None => return Ok(Vec::new()), + }; + + let children: Vec<V> = match Decode::decode(&mut &raw_val[..]) { + Some(children) => children, + None => return Err(error::ErrorKind::Backend("Error decoding children".into()).into()), + }; + + Ok(children) +} + +/// Insert the key-value pair (`parent_hash`, `children_hashes`) in the transaction. +/// Any existing value is overwritten upon write. +pub fn write_children< + K: Eq + Hash + Clone + Encode + Decode, + V: Eq + Hash + Clone + Encode + Decode, +>( + tx: &mut DBTransaction, + column: Option<u32>, + prefix: &[u8], + parent_hash: K, + children_hashes: V, +) { + let mut key = prefix.to_vec(); + parent_hash.using_encoded(|s| key.extend(s)); + tx.put_vec(column, &key[..], children_hashes.encode()); +} + +/// Prepare transaction to remove the children of `parent_hash`. +pub fn remove_children< + K: Eq + Hash + Clone + Encode + Decode, +>( + tx: &mut DBTransaction, + column: Option<u32>, + prefix: &[u8], + parent_hash: K, +) { + let mut key = prefix.to_vec(); + parent_hash.using_encoded(|s| key.extend(s)); + tx.delete(column, &key[..]); +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn children_write_read_remove() { + const PREFIX: &[u8] = b"children"; + let db = ::kvdb_memorydb::create(0); + + let mut tx = DBTransaction::new(); + + let mut children1 = Vec::new(); + children1.push(1_3); + children1.push(1_5); + write_children(&mut tx, None, PREFIX, 1_1, children1); + + let mut children2 = Vec::new(); + children2.push(1_4); + children2.push(1_6); + write_children(&mut tx, None, PREFIX, 1_2, children2); + + db.write(tx.clone()).unwrap(); + + let r1: Vec<u32> = read_children(&db, None, PREFIX, 1_1).unwrap(); + let r2: Vec<u32> = read_children(&db, None, PREFIX, 1_2).unwrap(); + + assert_eq!(r1, vec![1_3, 1_5]); + assert_eq!(r2, vec![1_4, 1_6]); + + remove_children(&mut tx, None, PREFIX, 1_2); + db.write(tx).unwrap(); + + let r1: Vec<u32> = read_children(&db, None, PREFIX, 1_1).unwrap(); + let r2: Vec<u32> = read_children(&db, None, PREFIX, 1_2).unwrap(); + + assert_eq!(r1, vec![1_3, 1_5]); + assert_eq!(r2.len(), 0); + } +} \ No newline at end of file diff --git a/substrate/core/client/src/client.rs b/substrate/core/client/src/client.rs index 375e36e81f54518c692af192b551e028aabb8fda..99f276ea2bbaf59015c8b8f242cd6b6507324386 100644 --- a/substrate/core/client/src/client.rs +++ b/substrate/core/client/src/client.rs @@ -56,7 +56,7 @@ use executor::{RuntimeVersion, RuntimeInfo}; use crate::notifications::{StorageNotifications, StorageEventStream}; use crate::light::{call_executor::prove_execution, fetcher::ChangesProof}; use crate::cht; -use crate::error; +use crate::error::{self, ErrorKind}; use crate::in_mem; use crate::block_builder::{self, api::BlockBuilder as BlockBuilderAPI}; use crate::genesis; @@ -1230,6 +1230,37 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where Ok(None) } + /// Gets the uncles of the block with `target_hash` going back `max_generation` ancestors. + pub fn uncles(&self, target_hash: Block::Hash, max_generation: NumberFor<Block>) -> error::Result<Vec<Block::Hash>> { + let load_header = |id: Block::Hash| -> error::Result<Block::Header> { + match self.backend.blockchain().header(BlockId::Hash(id))? { + Some(hdr) => Ok(hdr), + None => Err(ErrorKind::UnknownBlock(format!("Unknown block {:?}", id)).into()), + } + }; + + let genesis_hash = self.backend.blockchain().info()?.genesis_hash; + if genesis_hash == target_hash { return Ok(Vec::new()); } + + let mut current_hash = target_hash; + let mut current = load_header(current_hash)?; + let mut ancestor_hash = *current.parent_hash(); + let mut ancestor = load_header(ancestor_hash)?; + let mut uncles = Vec::new(); + + for _generation in 0..max_generation.as_() { + let children = self.backend.blockchain().children(ancestor_hash)?; + uncles.extend(children.into_iter().filter(|h| h != ¤t_hash)); + current_hash = ancestor_hash; + if genesis_hash == current_hash { break; } + current = ancestor; + ancestor_hash = *current.parent_hash(); + ancestor = load_header(ancestor_hash)?; + } + + Ok(uncles) + } + fn changes_trie_config(&self) -> Result<Option<ChangesTrieConfiguration>, Error> { Ok(self.backend.state_at(BlockId::Number(self.backend.blockchain().info()?.best_number))? .storage(well_known_keys::CHANGES_TRIE_CONFIG) @@ -1705,6 +1736,117 @@ pub(crate) mod tests { assert_eq!(None, client.best_containing(uninserted_block.hash().clone(), None).unwrap()); } + #[test] + fn uncles_with_only_ancestors() { + // block tree: + // G -> A1 -> A2 + let client = test_client::new(); + + // G -> A1 + let a1 = client.new_block().unwrap().bake().unwrap(); + client.import(BlockOrigin::Own, a1.clone()).unwrap(); + + // A1 -> A2 + let a2 = client.new_block().unwrap().bake().unwrap(); + client.import(BlockOrigin::Own, a2.clone()).unwrap(); + let v: Vec<H256> = Vec::new(); + assert_eq!(v, client.uncles(a2.hash(), 3).unwrap()); + } + + #[test] + fn uncles_with_multiple_forks() { + // block tree: + // G -> A1 -> A2 -> A3 -> A4 -> A5 + // A1 -> B2 -> B3 -> B4 + // B2 -> C3 + // A1 -> D2 + let client = test_client::new(); + + // G -> A1 + let a1 = client.new_block().unwrap().bake().unwrap(); + client.import(BlockOrigin::Own, a1.clone()).unwrap(); + + // A1 -> A2 + let a2 = client.new_block_at(&BlockId::Hash(a1.hash())).unwrap().bake().unwrap(); + client.import(BlockOrigin::Own, a2.clone()).unwrap(); + + // A2 -> A3 + let a3 = client.new_block_at(&BlockId::Hash(a2.hash())).unwrap().bake().unwrap(); + client.import(BlockOrigin::Own, a3.clone()).unwrap(); + + // A3 -> A4 + let a4 = client.new_block_at(&BlockId::Hash(a3.hash())).unwrap().bake().unwrap(); + client.import(BlockOrigin::Own, a4.clone()).unwrap(); + + // A4 -> A5 + let a5 = client.new_block_at(&BlockId::Hash(a4.hash())).unwrap().bake().unwrap(); + client.import(BlockOrigin::Own, a5.clone()).unwrap(); + + // A1 -> B2 + let mut builder = client.new_block_at(&BlockId::Hash(a1.hash())).unwrap(); + // this push is required as otherwise B2 has the same hash as A2 and won't get imported + builder.push_transfer(Transfer { + from: Keyring::Alice.to_raw_public().into(), + to: Keyring::Ferdie.to_raw_public().into(), + amount: 41, + nonce: 0, + }).unwrap(); + let b2 = builder.bake().unwrap(); + client.import(BlockOrigin::Own, b2.clone()).unwrap(); + + // B2 -> B3 + let b3 = client.new_block_at(&BlockId::Hash(b2.hash())).unwrap().bake().unwrap(); + client.import(BlockOrigin::Own, b3.clone()).unwrap(); + + // B3 -> B4 + let b4 = client.new_block_at(&BlockId::Hash(b3.hash())).unwrap().bake().unwrap(); + client.import(BlockOrigin::Own, b4.clone()).unwrap(); + + // // B2 -> C3 + let mut builder = client.new_block_at(&BlockId::Hash(b2.hash())).unwrap(); + // this push is required as otherwise C3 has the same hash as B3 and won't get imported + builder.push_transfer(Transfer { + from: Keyring::Alice.to_raw_public().into(), + to: Keyring::Ferdie.to_raw_public().into(), + amount: 1, + nonce: 1, + }).unwrap(); + let c3 = builder.bake().unwrap(); + client.import(BlockOrigin::Own, c3.clone()).unwrap(); + + // A1 -> D2 + let mut builder = client.new_block_at(&BlockId::Hash(a1.hash())).unwrap(); + // this push is required as otherwise D2 has the same hash as B2 and won't get imported + builder.push_transfer(Transfer { + from: Keyring::Alice.to_raw_public().into(), + to: Keyring::Ferdie.to_raw_public().into(), + amount: 1, + nonce: 0, + }).unwrap(); + let d2 = builder.bake().unwrap(); + client.import(BlockOrigin::Own, d2.clone()).unwrap(); + + let genesis_hash = client.info().unwrap().chain.genesis_hash; + + let uncles1 = client.uncles(a4.hash(), 10).unwrap(); + assert_eq!(vec![b2.hash(), d2.hash()], uncles1); + + let uncles2 = client.uncles(a4.hash(), 0).unwrap(); + assert_eq!(0, uncles2.len()); + + let uncles3 = client.uncles(a1.hash(), 10).unwrap(); + assert_eq!(0, uncles3.len()); + + let uncles4 = client.uncles(genesis_hash, 10).unwrap(); + assert_eq!(0, uncles4.len()); + + let uncles5 = client.uncles(d2.hash(), 10).unwrap(); + assert_eq!(vec![a2.hash(), b2.hash()], uncles5); + + let uncles6 = client.uncles(b3.hash(), 1).unwrap(); + assert_eq!(vec![c3.hash()], uncles6); + } + #[test] fn best_containing_with_single_chain_3_blocks() { // block tree: diff --git a/substrate/core/client/src/in_mem.rs b/substrate/core/client/src/in_mem.rs index fda962aca01dab32785502ff178574d98f1403fb..6c90d9ae3e84fb5d3947d9b7bc03dbf02059db6c 100644 --- a/substrate/core/client/src/in_mem.rs +++ b/substrate/core/client/src/in_mem.rs @@ -19,22 +19,23 @@ use std::collections::HashMap; use std::sync::Arc; use parking_lot::RwLock; -use crate::error; -use crate::backend::{self, NewBlockState}; -use crate::light; use primitives::{ChangesTrieConfiguration, storage::well_known_keys}; use runtime_primitives::generic::BlockId; use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, Zero, NumberFor, As, Digest, DigestItem, AuthorityIdFor}; use runtime_primitives::{Justification, StorageOverlay, ChildrenStorageOverlay}; -use crate::blockchain::{self, BlockStatus, HeaderBackend}; use state_machine::backend::{Backend as StateBackend, InMemory, Consolidate}; use state_machine::{self, InMemoryChangesTrieStorage, ChangesTrieAnchorBlockId}; use hash_db::Hasher; use heapsize::HeapSizeOf; -use crate::leaves::LeafSet; use trie::MemoryDB; +use crate::error; +use crate::backend::{self, NewBlockState}; +use crate::light; +use crate::leaves::LeafSet; +use crate::blockchain::{self, BlockStatus, HeaderBackend}; + struct PendingBlock<B: BlockT> { block: StoredBlock<B>, state: NewBlockState, @@ -168,7 +169,6 @@ impl<Block: BlockT> Blockchain<Block> { new_state: NewBlockState, ) -> crate::error::Result<()> { let number = header.number().clone(); - if new_state.is_best() { self.apply_head(&header)?; } @@ -362,6 +362,10 @@ impl<Block: BlockT> blockchain::Backend<Block> for Blockchain<Block> { fn leaves(&self) -> error::Result<Vec<Block::Hash>> { Ok(self.storage.read().leaves.hashes()) } + + fn children(&self, _parent_hash: Block::Hash) -> error::Result<Vec<Block::Hash>> { + unimplemented!() + } } impl<Block: BlockT> backend::AuxStore for Blockchain<Block> { diff --git a/substrate/core/client/src/leaves.rs b/substrate/core/client/src/leaves.rs index 83741556216973185492c876b40b5275fbfd501a..92bdfa64ec64096bb1b6cb22e0ed7e0fac12e517 100644 --- a/substrate/core/client/src/leaves.rs +++ b/substrate/core/client/src/leaves.rs @@ -84,7 +84,6 @@ impl<H, N> LeafSet<H, N> where /// Read the leaf list from the DB, using given prefix for keys. pub fn read_from_db(db: &KeyValueDB, column: Option<u32>, prefix: &[u8]) -> error::Result<Self> { let mut storage = BTreeSet::new(); - for (key, value) in db.iter_from_prefix(column, prefix) { if !key.starts_with(prefix) { break } let raw_hash = &mut &key[prefix.len()..]; diff --git a/substrate/core/client/src/lib.rs b/substrate/core/client/src/lib.rs index 7472b358d0d72c51f36eaeb60af51c3ab43bfc50..44eaa449245bb36551e2b3c01959c5e261ba1807 100644 --- a/substrate/core/client/src/lib.rs +++ b/substrate/core/client/src/lib.rs @@ -38,6 +38,8 @@ pub mod block_builder; #[cfg(feature = "std")] pub mod light; #[cfg(feature = "std")] +pub mod children; +#[cfg(feature = "std")] mod leaves; #[cfg(feature = "std")] mod call_executor; @@ -46,6 +48,7 @@ mod client; #[cfg(feature = "std")] mod notifications; + #[cfg(feature = "std")] pub use crate::blockchain::Info as ChainInfo; #[cfg(feature = "std")] diff --git a/substrate/core/client/src/light/blockchain.rs b/substrate/core/client/src/light/blockchain.rs index 36a236f32e2df06e83178e8140447d92c2832894..73d530ce455454f8b4295d4403aee137bfde8a90 100644 --- a/substrate/core/client/src/light/blockchain.rs +++ b/substrate/core/client/src/light/blockchain.rs @@ -163,6 +163,10 @@ impl<S, F, Block> BlockchainBackend<Block> for Blockchain<S, F> where Block: Blo fn leaves(&self) -> ClientResult<Vec<Block::Hash>> { unimplemented!() } + + fn children(&self, _parent_hash: Block::Hash) -> ClientResult<Vec<Block::Hash>> { + unimplemented!() + } } #[cfg(test)] diff --git a/substrate/core/test-client/src/trait_tests.rs b/substrate/core/test-client/src/trait_tests.rs index 7fadc128faf2c544f1f9e00e23150bb509426c88..8242f30d2e23f412477a8bdfb0753dea53469084 100644 --- a/substrate/core/test-client/src/trait_tests.rs +++ b/substrate/core/test-client/src/trait_tests.rs @@ -144,6 +144,96 @@ pub fn test_leaves_for_backend<B: 'static>(backend: Arc<B>) where vec![a5.hash(), b4.hash(), c3.hash(), d2.hash()]); } +/// helper to test the `children` implementation for various backends +pub fn test_children_for_backend<B: 'static>(backend: Arc<B>) where + B: backend::LocalBackend<runtime::Block, Blake2Hasher>, +{ + // block tree: + // G -> A1 -> A2 -> A3 -> A4 -> A5 + // A1 -> B2 -> B3 -> B4 + // B2 -> C3 + // A1 -> D2 + + let client = new_with_backend(backend.clone(), false); + + // G -> A1 + let a1 = client.new_block().unwrap().bake().unwrap(); + client.import(BlockOrigin::Own, a1.clone()).unwrap(); + + // A1 -> A2 + let a2 = client.new_block_at(&BlockId::Hash(a1.hash())).unwrap().bake().unwrap(); + client.import(BlockOrigin::Own, a2.clone()).unwrap(); + + // A2 -> A3 + let a3 = client.new_block_at(&BlockId::Hash(a2.hash())).unwrap().bake().unwrap(); + client.import(BlockOrigin::Own, a3.clone()).unwrap(); + + // A3 -> A4 + let a4 = client.new_block_at(&BlockId::Hash(a3.hash())).unwrap().bake().unwrap(); + client.import(BlockOrigin::Own, a4.clone()).unwrap(); + + // A4 -> A5 + let a5 = client.new_block_at(&BlockId::Hash(a4.hash())).unwrap().bake().unwrap(); + client.import(BlockOrigin::Own, a5.clone()).unwrap(); + + // A1 -> B2 + let mut builder = client.new_block_at(&BlockId::Hash(a1.hash())).unwrap(); + // this push is required as otherwise B2 has the same hash as A2 and won't get imported + builder.push_transfer(Transfer { + from: Keyring::Alice.to_raw_public().into(), + to: Keyring::Ferdie.to_raw_public().into(), + amount: 41, + nonce: 0, + }).unwrap(); + let b2 = builder.bake().unwrap(); + client.import(BlockOrigin::Own, b2.clone()).unwrap(); + + // B2 -> B3 + let b3 = client.new_block_at(&BlockId::Hash(b2.hash())).unwrap().bake().unwrap(); + client.import(BlockOrigin::Own, b3.clone()).unwrap(); + + // B3 -> B4 + let b4 = client.new_block_at(&BlockId::Hash(b3.hash())).unwrap().bake().unwrap(); + client.import(BlockOrigin::Own, b4.clone()).unwrap(); + + // // B2 -> C3 + let mut builder = client.new_block_at(&BlockId::Hash(b2.hash())).unwrap(); + // this push is required as otherwise C3 has the same hash as B3 and won't get imported + builder.push_transfer(Transfer { + from: Keyring::Alice.to_raw_public().into(), + to: Keyring::Ferdie.to_raw_public().into(), + amount: 1, + nonce: 1, + }).unwrap(); + let c3 = builder.bake().unwrap(); + client.import(BlockOrigin::Own, c3.clone()).unwrap(); + + // A1 -> D2 + let mut builder = client.new_block_at(&BlockId::Hash(a1.hash())).unwrap(); + // this push is required as otherwise D2 has the same hash as B2 and won't get imported + builder.push_transfer(Transfer { + from: Keyring::Alice.to_raw_public().into(), + to: Keyring::Ferdie.to_raw_public().into(), + amount: 1, + nonce: 0, + }).unwrap(); + let d2 = builder.bake().unwrap(); + client.import(BlockOrigin::Own, d2.clone()).unwrap(); + + let genesis_hash = client.info().unwrap().chain.genesis_hash; + + let children1 = backend.blockchain().children(a4.hash()).unwrap(); + assert_eq!(vec![a5.hash()], children1); + + let children2 = backend.blockchain().children(a1.hash()).unwrap(); + assert_eq!(vec![a2.hash(), b2.hash(), d2.hash()], children2); + + let children3 = backend.blockchain().children(genesis_hash).unwrap(); + assert_eq!(vec![a1.hash()], children3); + + let children4 = backend.blockchain().children(b2.hash()).unwrap(); + assert_eq!(vec![b3.hash(), c3.hash()], children4); +} pub fn test_blockchain_query_by_number_gets_canonical<B: 'static>(backend: Arc<B>) where B: backend::LocalBackend<runtime::Block, Blake2Hasher>,