Optimize tree route to sync faster (#3665)

* Introduce HeaderMetadata and use it for tree_route. Add lowest_common_ancestor.
* Add tests.
This commit is contained in:
Marcio Diaz
2019-10-02 20:30:43 +02:00
committed by GitHub
parent 8646cd158e
commit f6bd58ac1f
22 changed files with 677 additions and 280 deletions
+103 -26
View File
@@ -35,6 +35,7 @@ use codec::{Decode, Encode};
use primitives::Blake2Hasher;
use sr_primitives::generic::{DigestItem, BlockId};
use sr_primitives::traits::{Block as BlockT, Header as HeaderT, Zero, One, NumberFor};
use header_metadata::{CachedHeaderMetadata, HeaderMetadata, HeaderMetadataCache};
use crate::cache::{DbCacheSync, DbCache, ComplexBlockId, EntryType as CacheEntryType};
use crate::utils::{self, meta_keys, Meta, db_err, read_db, block_id_to_lookup_key, read_meta};
use crate::DatabaseSettings;
@@ -60,6 +61,7 @@ pub struct LightStorage<Block: BlockT> {
db: Arc<dyn KeyValueDB>,
meta: RwLock<Meta<NumberFor<Block>, Block::Hash>>,
cache: Arc<DbCacheSync<Block>>,
header_metadata_cache: HeaderMetadataCache<Block>,
}
impl<Block> LightStorage<Block>
@@ -109,6 +111,7 @@ impl<Block> LightStorage<Block>
db,
meta: RwLock::new(meta),
cache: Arc::new(DbCacheSync(RwLock::new(cache))),
header_metadata_cache: HeaderMetadataCache::default(),
})
}
@@ -192,6 +195,31 @@ impl<Block> BlockchainHeaderBackend<Block> for LightStorage<Block>
}
}
impl<Block: BlockT> HeaderMetadata<Block> for LightStorage<Block> {
type Error = ClientError;
fn header_metadata(&self, hash: Block::Hash) -> Result<CachedHeaderMetadata<Block>, Self::Error> {
self.header_metadata_cache.header_metadata(hash).or_else(|_| {
self.header(BlockId::hash(hash))?.map(|header| {
let header_metadata = CachedHeaderMetadata::from(&header);
self.header_metadata_cache.insert_header_metadata(
header_metadata.hash,
header_metadata.clone(),
);
header_metadata
}).ok_or(ClientError::UnknownBlock("header not found in db".to_owned()))
})
}
fn insert_header_metadata(&self, hash: Block::Hash, metadata: CachedHeaderMetadata<Block>) {
self.header_metadata_cache.insert_header_metadata(hash, metadata)
}
fn remove_header_metadata(&self, hash: Block::Hash) {
self.header_metadata_cache.remove_header_metadata(hash);
}
}
impl<Block: BlockT> LightStorage<Block> {
// Get block changes trie root, if available.
fn changes_trie_root(&self, block: BlockId<Block>) -> ClientResult<Option<Block::Hash>> {
@@ -208,17 +236,18 @@ impl<Block: BlockT> LightStorage<Block> {
/// In the case where the new best block is a block to be imported, `route_to`
/// should be the parent of `best_to`. In the case where we set an existing block
/// to be best, `route_to` should equal to `best_to`.
fn set_head_with_transaction(&self, transaction: &mut DBTransaction, route_to: Block::Hash, best_to: (NumberFor<Block>, Block::Hash)) -> Result<(), client::error::Error> {
fn set_head_with_transaction(
&self,
transaction: &mut DBTransaction,
route_to: Block::Hash,
best_to: (NumberFor<Block>, Block::Hash),
) -> ClientResult<()> {
let lookup_key = utils::number_and_hash_to_lookup_key(best_to.0, &best_to.1)?;
// handle reorg.
let meta = self.meta.read();
if meta.best_hash != Default::default() {
let tree_route = ::client::blockchain::tree_route(
|id| self.header(id)?.ok_or_else(|| client::error::Error::UnknownBlock(format!("{:?}", id))),
BlockId::Hash(meta.best_hash),
BlockId::Hash(route_to),
)?;
let tree_route = header_metadata::tree_route(self, meta.best_hash, route_to)?;
// update block number to hash lookup entries.
for retracted in tree_route.retracted() {
@@ -418,6 +447,12 @@ impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
)?;
transaction.put(columns::HEADER, &lookup_key, &header.encode());
let header_metadata = CachedHeaderMetadata::from(&header);
self.header_metadata_cache.insert_header_metadata(
header.hash().clone(),
header_metadata,
);
let is_genesis = number.is_zero();
if is_genesis {
self.cache.0.write().set_genesis_hash(hash);
@@ -537,6 +572,7 @@ pub(crate) mod tests {
use client::cht;
use sr_primitives::generic::DigestItem;
use sr_primitives::testing::{H256 as Hash, Header, Block as RawBlock, ExtrinsicWrapper};
use header_metadata::{lowest_common_ancestor, tree_route};
use super::*;
type Block = RawBlock<ExtrinsicWrapper<u32>>;
@@ -781,11 +817,7 @@ pub(crate) mod tests {
let b2 = insert_block(&db, HashMap::new(), || default_header(&b1, 2));
{
let tree_route = ::client::blockchain::tree_route(
|id| db.header(id)?.ok_or_else(|| client::error::Error::UnknownBlock(format!("{:?}", id))),
BlockId::Hash(a3),
BlockId::Hash(b2)
).unwrap();
let tree_route = tree_route(&db, a3, b2).unwrap();
assert_eq!(tree_route.common_block().hash, block0);
assert_eq!(tree_route.retracted().iter().map(|r| r.hash).collect::<Vec<_>>(), vec![a3, a2, a1]);
@@ -793,11 +825,7 @@ pub(crate) mod tests {
}
{
let tree_route = ::client::blockchain::tree_route(
|id| db.header(id)?.ok_or_else(|| client::error::Error::UnknownBlock(format!("{:?}", id))),
BlockId::Hash(a1),
BlockId::Hash(a3),
).unwrap();
let tree_route = tree_route(&db, a1, a3).unwrap();
assert_eq!(tree_route.common_block().hash, a1);
assert!(tree_route.retracted().is_empty());
@@ -805,11 +833,7 @@ pub(crate) mod tests {
}
{
let tree_route = ::client::blockchain::tree_route(
|id| db.header(id)?.ok_or_else(|| client::error::Error::UnknownBlock(format!("{:?}", id))),
BlockId::Hash(a3),
BlockId::Hash(a1),
).unwrap();
let tree_route = tree_route(&db, a3, a1).unwrap();
assert_eq!(tree_route.common_block().hash, a1);
assert_eq!(tree_route.retracted().iter().map(|r| r.hash).collect::<Vec<_>>(), vec![a3, a2]);
@@ -817,11 +841,7 @@ pub(crate) mod tests {
}
{
let tree_route = ::client::blockchain::tree_route(
|id| db.header(id)?.ok_or_else(|| client::error::Error::UnknownBlock(format!("{:?}", id))),
BlockId::Hash(a2),
BlockId::Hash(a2),
).unwrap();
let tree_route = tree_route(&db, a2, a2).unwrap();
assert_eq!(tree_route.common_block().hash, a2);
assert!(tree_route.retracted().is_empty());
@@ -829,6 +849,63 @@ pub(crate) mod tests {
}
}
#[test]
fn lowest_common_ancestor_works() {
let db = LightStorage::new_test();
let block0 = insert_block(&db, HashMap::new(), || default_header(&Default::default(), 0));
// fork from genesis: 3 prong.
let a1 = insert_block(&db, HashMap::new(), || default_header(&block0, 1));
let a2 = insert_block(&db, HashMap::new(), || default_header(&a1, 2));
let a3 = insert_block(&db, HashMap::new(), || default_header(&a2, 3));
// fork from genesis: 2 prong.
let b1 = insert_block(&db, HashMap::new(), || header_with_extrinsics_root(&block0, 1, Hash::from([1; 32])));
let b2 = insert_block(&db, HashMap::new(), || default_header(&b1, 2));
{
let lca = lowest_common_ancestor(&db, a3, b2).unwrap();
assert_eq!(lca.hash, block0);
assert_eq!(lca.number, 0);
}
{
let lca = lowest_common_ancestor(&db, a1, a3).unwrap();
assert_eq!(lca.hash, a1);
assert_eq!(lca.number, 1);
}
{
let lca = lowest_common_ancestor(&db, a3, a1).unwrap();
assert_eq!(lca.hash, a1);
assert_eq!(lca.number, 1);
}
{
let lca = lowest_common_ancestor(&db, a2, a3).unwrap();
assert_eq!(lca.hash, a2);
assert_eq!(lca.number, 2);
}
{
let lca = lowest_common_ancestor(&db, a2, a1).unwrap();
assert_eq!(lca.hash, a1);
assert_eq!(lca.number, 1);
}
{
let lca = lowest_common_ancestor(&db, a2, a2).unwrap();
assert_eq!(lca.hash, a2);
assert_eq!(lca.number, 2);
}
}
#[test]
fn authorities_are_cached() {
let db = LightStorage::new_test();