mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 07:41:08 +00:00
Finality notification streams (#791)
* finalization for in_mem * fetch last finalized block * pruning: use canonical term instead of final * finalize blocks in full node * begin to port light client DB * add tree-route * keep number index consistent in full nodes * fix tests * disable cache and finish porting light client * add AsMut to system module * final leaf is always best * fix all tests * Fix comment and trace * removed unused Into call * add comment on behavior of `finalize_block` * move `tree_route` to client common * tree_route tests * return slices in TreeRoute * apply finality up to parent * add `finalize_block` call * adjust formatting * finality notifications and add last finalized block to chain info * exhaustive match and comments * fix sync tests by using non-instant finality
This commit is contained in:
committed by
Arkadiy Paronyan
parent
ef97973178
commit
b02c274374
+160
-103
@@ -153,6 +153,7 @@ impl<Block: BlockT> BlockchainDb<Block> {
|
||||
let mut meta = self.meta.write();
|
||||
if number == Zero::zero() {
|
||||
meta.genesis_hash = hash;
|
||||
meta.finalized_hash = hash;
|
||||
}
|
||||
|
||||
if is_best {
|
||||
@@ -178,6 +179,7 @@ impl<Block: BlockT> client::blockchain::HeaderBackend<Block> for BlockchainDb<Bl
|
||||
best_hash: meta.best_hash,
|
||||
best_number: meta.best_number,
|
||||
genesis_hash: meta.genesis_hash,
|
||||
finalized_hash: meta.finalized_hash,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -411,59 +413,42 @@ impl<Block: BlockT> Backend<Block> {
|
||||
}
|
||||
|
||||
// write stuff to a transaction after a new block is finalized.
|
||||
//
|
||||
// this manages state pruning and ensuring reorgs don't occur.
|
||||
// this function should only be called if the finalized block is contained
|
||||
// in the best chain.
|
||||
fn note_finalized(&self, transaction: &mut DBTransaction, f_header: &Block::Header, f_hash: Block::Hash) -> Result<(), client::error::Error> {
|
||||
const NOTEWORTHY_FINALIZATION_GAP: u64 = 32;
|
||||
|
||||
// TODO: ensure this doesn't conflict with old finalized block.
|
||||
// this manages state pruning. Fails if called with a block which
|
||||
// was not a child of the last finalized block.
|
||||
fn note_finalized(
|
||||
&self,
|
||||
transaction: &mut DBTransaction,
|
||||
f_header: &Block::Header,
|
||||
f_hash: Block::Hash,
|
||||
) -> Result<(), client::error::Error> {
|
||||
let meta = self.blockchain.meta.read();
|
||||
let f_num = f_header.number().clone();
|
||||
if &meta.finalized_hash != f_header.parent_hash() {
|
||||
return Err(::client::error::ErrorKind::NonSequentialFinalization(
|
||||
format!("Last finalized {:?} not parent of {:?}",
|
||||
meta.finalized_hash, f_hash),
|
||||
).into())
|
||||
}
|
||||
transaction.put(columns::META, meta_keys::FINALIZED_BLOCK, f_hash.as_ref());
|
||||
|
||||
let (last_finalized_hash, last_finalized_number)
|
||||
= (meta.finalized_hash.clone(), meta.finalized_number);
|
||||
|
||||
let finalized_gap = f_num - last_finalized_number;
|
||||
|
||||
if finalized_gap.as_() >= NOTEWORTHY_FINALIZATION_GAP {
|
||||
info!(target: "db", "Finalizing large run of blocks from {:?} to {:?}",
|
||||
(&last_finalized_hash, last_finalized_number), (&f_hash, f_num));
|
||||
} else {
|
||||
debug!(target: "db", "Finalizing blocks from {:?} to {:?}",
|
||||
(&last_finalized_hash, last_finalized_number), (&f_hash, f_num));
|
||||
}
|
||||
|
||||
let mut canonicalize_state = |canonical_hash| {
|
||||
let commit = self.storage.state_db.canonicalize_block(&canonical_hash);
|
||||
apply_state_commit(transaction, commit);
|
||||
};
|
||||
|
||||
// when finalizing a block, we must also implicitly finalize all the blocks
|
||||
// in between the last finalized block and this one. That means canonicalizing
|
||||
// all their states in order.
|
||||
let number_u64 = f_num.as_();
|
||||
if number_u64 > self.pruning_window {
|
||||
let new_canonical = number_u64 - self.pruning_window;
|
||||
let best_canonical = self.storage.state_db.best_canonical();
|
||||
|
||||
for uncanonicalized_number in (best_canonical..new_canonical).map(|x| x + 1) {
|
||||
let hash = if uncanonicalized_number == number_u64 {
|
||||
f_hash
|
||||
} else {
|
||||
read_id::<Block>(
|
||||
&*self.blockchain.db,
|
||||
columns::HASH_LOOKUP,
|
||||
BlockId::Number(As::sa(uncanonicalized_number))
|
||||
)?.expect("existence of block with number `new_canonical` \
|
||||
implies existence of blocks with all nubmers before it; qed")
|
||||
};
|
||||
let hash = if new_canonical == number_u64 {
|
||||
f_hash
|
||||
} else {
|
||||
read_id::<Block>(
|
||||
&*self.blockchain.db,
|
||||
columns::HASH_LOOKUP,
|
||||
BlockId::Number(As::sa(new_canonical))
|
||||
)?.expect("existence of block with number `new_canonical` \
|
||||
implies existence of blocks with all nubmers before it; qed")
|
||||
};
|
||||
|
||||
trace!(target: "db", "Canonicalize block #{} ({:?})", uncanonicalized_number, hash);
|
||||
canonicalize_state(hash);
|
||||
}
|
||||
trace!(target: "db", "Canonicalize block #{} ({:?})", new_canonical, hash);
|
||||
let commit = self.storage.state_db.canonicalize_block(&hash);
|
||||
apply_state_commit(transaction, commit);
|
||||
};
|
||||
|
||||
Ok(())
|
||||
@@ -507,7 +492,9 @@ impl<Block> client::backend::Backend<Block, Blake2Hasher, RlpCodec> for Backend<
|
||||
})
|
||||
}
|
||||
|
||||
fn commit_operation(&self, mut operation: Self::BlockImportOperation) -> Result<(), client::error::Error> {
|
||||
fn commit_operation(&self, mut operation: Self::BlockImportOperation)
|
||||
-> Result<(), client::error::Error>
|
||||
{
|
||||
let mut transaction = DBTransaction::new();
|
||||
if let Some(pending_block) = operation.pending_block {
|
||||
let hash = pending_block.header.hash();
|
||||
@@ -527,19 +514,19 @@ impl<Block> client::backend::Backend<Block, Blake2Hasher, RlpCodec> for Backend<
|
||||
// cannot find tree route with empty DB.
|
||||
if meta.best_hash != Default::default() {
|
||||
let parent_hash = *pending_block.header.parent_hash();
|
||||
let tree_route = ::utils::tree_route::<Block>(
|
||||
&*self.blockchain.db,
|
||||
columns::HEADER,
|
||||
meta.best_hash,
|
||||
parent_hash,
|
||||
let tree_route = ::client::blockchain::tree_route(
|
||||
&self.blockchain,
|
||||
BlockId::Hash(meta.best_hash),
|
||||
BlockId::Hash(parent_hash),
|
||||
)?;
|
||||
|
||||
// update block number to hash lookup entries.
|
||||
for retracted in tree_route.retracted() {
|
||||
if retracted.hash == meta.finalized_hash {
|
||||
// TODO: can we recover here?
|
||||
warn!("Safety failure: reverting finalized block {:?}",
|
||||
warn!("Potential safety failure: reverting finalized block {:?}",
|
||||
(&retracted.number, &retracted.hash));
|
||||
|
||||
return Err(::client::error::ErrorKind::NotInFinalizedChain.into());
|
||||
}
|
||||
|
||||
transaction.delete(
|
||||
@@ -699,6 +686,60 @@ mod tests {
|
||||
|
||||
type Block = RawBlock<u64>;
|
||||
|
||||
fn prepare_changes(changes: Vec<(Vec<u8>, Vec<u8>)>) -> (H256, MemoryDB<Blake2Hasher>) {
|
||||
let mut changes_root = H256::default();
|
||||
let mut changes_trie_update = MemoryDB::<Blake2Hasher>::new();
|
||||
{
|
||||
let mut trie = TrieDBMut::<Blake2Hasher, RlpCodec>::new(
|
||||
&mut changes_trie_update,
|
||||
&mut changes_root
|
||||
);
|
||||
for (key, value) in changes {
|
||||
trie.insert(&key, &value).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
(changes_root, changes_trie_update)
|
||||
}
|
||||
|
||||
fn insert_header(
|
||||
backend: &Backend<Block>,
|
||||
number: u64,
|
||||
parent_hash: H256,
|
||||
changes: Vec<(Vec<u8>, Vec<u8>)>,
|
||||
extrinsics_root: H256,
|
||||
) -> H256 {
|
||||
use runtime_primitives::generic::DigestItem;
|
||||
use runtime_primitives::testing::Digest;
|
||||
|
||||
let (changes_root, changes_trie_update) = prepare_changes(changes);
|
||||
let digest = Digest {
|
||||
logs: vec![
|
||||
DigestItem::ChangesTrieRoot(changes_root),
|
||||
],
|
||||
};
|
||||
let header = Header {
|
||||
number,
|
||||
parent_hash,
|
||||
state_root: Default::default(),
|
||||
digest,
|
||||
extrinsics_root,
|
||||
};
|
||||
let header_hash = header.hash();
|
||||
|
||||
let block_id = if number == 0 {
|
||||
BlockId::Hash(Default::default())
|
||||
} else {
|
||||
BlockId::Number(number - 1)
|
||||
};
|
||||
let mut op = backend.begin_operation(block_id).unwrap();
|
||||
op.set_block_data(header, None, None, NewBlockState::Best).unwrap();
|
||||
op.update_changes_trie(changes_trie_update).unwrap();
|
||||
backend.commit_operation(op).unwrap();
|
||||
|
||||
header_hash
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_hash_inserted_correctly() {
|
||||
let db = Backend::<Block>::new_test(1);
|
||||
@@ -924,6 +965,7 @@ mod tests {
|
||||
assert!(backend.storage.db.get(::columns::STATE, &key.0[..]).unwrap().is_some());
|
||||
}
|
||||
|
||||
backend.finalize_block(BlockId::Number(1)).unwrap();
|
||||
backend.finalize_block(BlockId::Number(2)).unwrap();
|
||||
assert!(backend.storage.db.get(::columns::STATE, &key.0[..]).unwrap().is_none());
|
||||
}
|
||||
@@ -932,54 +974,6 @@ mod tests {
|
||||
fn changes_trie_storage_works() {
|
||||
let backend = Backend::<Block>::new_test(1000);
|
||||
|
||||
let prepare_changes = |changes: Vec<(Vec<u8>, Vec<u8>)>| {
|
||||
let mut changes_root = H256::default();
|
||||
let mut changes_trie_update = MemoryDB::<Blake2Hasher>::new();
|
||||
{
|
||||
let mut trie = TrieDBMut::<Blake2Hasher, RlpCodec>::new(
|
||||
&mut changes_trie_update,
|
||||
&mut changes_root
|
||||
);
|
||||
for (key, value) in changes {
|
||||
trie.insert(&key, &value).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
(changes_root, changes_trie_update)
|
||||
};
|
||||
|
||||
let insert_header = |number: u64, parent_hash: H256, changes: Vec<(Vec<u8>, Vec<u8>)>| {
|
||||
use runtime_primitives::generic::DigestItem;
|
||||
use runtime_primitives::testing::Digest;
|
||||
|
||||
let (changes_root, changes_trie_update) = prepare_changes(changes);
|
||||
let digest = Digest {
|
||||
logs: vec![
|
||||
DigestItem::ChangesTrieRoot(changes_root),
|
||||
],
|
||||
};
|
||||
let header = Header {
|
||||
number,
|
||||
parent_hash,
|
||||
state_root: Default::default(),
|
||||
digest,
|
||||
extrinsics_root: Default::default(),
|
||||
};
|
||||
let header_hash = header.hash();
|
||||
|
||||
let block_id = if number == 0 {
|
||||
BlockId::Hash(Default::default())
|
||||
} else {
|
||||
BlockId::Number(number - 1)
|
||||
};
|
||||
let mut op = backend.begin_operation(block_id).unwrap();
|
||||
op.set_block_data(header, None, None, NewBlockState::Best).unwrap();
|
||||
op.update_changes_trie(changes_trie_update).unwrap();
|
||||
backend.commit_operation(op).unwrap();
|
||||
|
||||
header_hash
|
||||
};
|
||||
|
||||
let check_changes = |backend: &Backend<Block>, block: u64, changes: Vec<(Vec<u8>, Vec<u8>)>| {
|
||||
let (changes_root, mut changes_trie_update) = prepare_changes(changes);
|
||||
assert_eq!(backend.tries_change_storage.root(block), Ok(Some(changes_root)));
|
||||
@@ -996,13 +990,76 @@ mod tests {
|
||||
];
|
||||
let changes2 = vec![(b"key_at_2".to_vec(), b"val_at_2".to_vec())];
|
||||
|
||||
let block0 = insert_header(0, Default::default(), changes0.clone());
|
||||
let block1 = insert_header(1, block0, changes1.clone());
|
||||
let _ = insert_header(2, block1, changes2.clone());
|
||||
let block0 = insert_header(&backend, 0, Default::default(), changes0.clone(), Default::default());
|
||||
let block1 = insert_header(&backend, 1, block0, changes1.clone(), Default::default());
|
||||
let _ = insert_header(&backend, 2, block1, changes2.clone(), Default::default());
|
||||
|
||||
// check that the storage contains tries for all blocks
|
||||
check_changes(&backend, 0, changes0);
|
||||
check_changes(&backend, 1, changes1);
|
||||
check_changes(&backend, 2, changes2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_route_works() {
|
||||
let backend = Backend::<Block>::new_test(1000);
|
||||
let block0 = insert_header(&backend, 0, Default::default(), Vec::new(), Default::default());
|
||||
|
||||
// fork from genesis: 3 prong.
|
||||
let a1 = insert_header(&backend, 1, block0, Vec::new(), Default::default());
|
||||
let a2 = insert_header(&backend, 2, a1, Vec::new(), Default::default());
|
||||
let a3 = insert_header(&backend, 3, a2, Vec::new(), Default::default());
|
||||
|
||||
// fork from genesis: 2 prong.
|
||||
let b1 = insert_header(&backend, 1, block0, Vec::new(), H256::from([1; 32]));
|
||||
let b2 = insert_header(&backend, 2, b1, Vec::new(), Default::default());
|
||||
|
||||
{
|
||||
let tree_route = ::client::blockchain::tree_route(
|
||||
backend.blockchain(),
|
||||
BlockId::Hash(a3),
|
||||
BlockId::Hash(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]);
|
||||
assert_eq!(tree_route.enacted().iter().map(|r| r.hash).collect::<Vec<_>>(), vec![b1, b2]);
|
||||
}
|
||||
|
||||
{
|
||||
let tree_route = ::client::blockchain::tree_route(
|
||||
backend.blockchain(),
|
||||
BlockId::Hash(a1),
|
||||
BlockId::Hash(a3),
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(tree_route.common_block().hash, a1);
|
||||
assert!(tree_route.retracted().is_empty());
|
||||
assert_eq!(tree_route.enacted().iter().map(|r| r.hash).collect::<Vec<_>>(), vec![a2, a3]);
|
||||
}
|
||||
|
||||
{
|
||||
let tree_route = ::client::blockchain::tree_route(
|
||||
backend.blockchain(),
|
||||
BlockId::Hash(a3),
|
||||
BlockId::Hash(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]);
|
||||
assert!(tree_route.enacted().is_empty());
|
||||
}
|
||||
|
||||
{
|
||||
let tree_route = ::client::blockchain::tree_route(
|
||||
backend.blockchain(),
|
||||
BlockId::Hash(a2),
|
||||
BlockId::Hash(a2),
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(tree_route.common_block().hash, a2);
|
||||
assert!(tree_route.retracted().is_empty());
|
||||
assert!(tree_route.enacted().is_empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,20 +118,18 @@ impl<Block> LightStorage<Block>
|
||||
is_finalized: bool,
|
||||
) {
|
||||
let mut meta = self.meta.write();
|
||||
if is_best {
|
||||
if number == <<Block as BlockT>::Header as HeaderT>::Number::zero() {
|
||||
meta.genesis_hash = hash;
|
||||
}
|
||||
|
||||
if number == Zero::zero() {
|
||||
meta.genesis_hash = hash;
|
||||
meta.finalized_hash = hash;
|
||||
}
|
||||
|
||||
if is_best {
|
||||
meta.best_number = number;
|
||||
meta.best_hash = hash;
|
||||
}
|
||||
|
||||
if is_finalized {
|
||||
if number == <<Block as BlockT>::Header as HeaderT>::Number::zero() {
|
||||
meta.genesis_hash = hash;
|
||||
}
|
||||
|
||||
meta.finalized_number = number;
|
||||
meta.finalized_hash = hash;
|
||||
}
|
||||
@@ -152,6 +150,7 @@ impl<Block> BlockchainHeaderBackend<Block> for LightStorage<Block>
|
||||
best_hash: meta.best_hash,
|
||||
best_number: meta.best_number,
|
||||
genesis_hash: meta.genesis_hash,
|
||||
finalized_hash: meta.finalized_hash,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -184,75 +183,43 @@ impl<Block> BlockchainHeaderBackend<Block> for LightStorage<Block>
|
||||
}
|
||||
|
||||
impl<Block: BlockT> LightStorage<Block> {
|
||||
// note that a block is finalized. ensure that best chain contains the finalized
|
||||
// block number first.
|
||||
// note that a block is finalized. only call with child of last finalized block.
|
||||
fn note_finalized(&self, transaction: &mut DBTransaction, header: &Block::Header, hash: Block::Hash) -> ClientResult<()> {
|
||||
const NOTEWORTHY_FINALIZATION_GAP: u64 = 32;
|
||||
|
||||
// TODO: ensure this doesn't conflict with old finalized block.
|
||||
let meta = self.meta.read();
|
||||
let f_num = header.number().clone();
|
||||
let number_u64: u64 = f_num.as_();
|
||||
transaction.put(columns::META, meta_keys::FINALIZED_BLOCK, hash.as_ref());
|
||||
|
||||
let (last_finalized_hash, last_finalized_number)
|
||||
= (meta.finalized_hash.clone(), meta.finalized_number);
|
||||
|
||||
let finalized_gap = f_num - last_finalized_number;
|
||||
|
||||
if finalized_gap.as_() >= NOTEWORTHY_FINALIZATION_GAP {
|
||||
info!(target: "db", "Finalizing large run of blocks from {:?} to {:?}",
|
||||
(&last_finalized_hash, last_finalized_number), (&hash, f_num));
|
||||
} else {
|
||||
debug!(target: "db", "Finalizing blocks from {:?} to {:?}",
|
||||
(&last_finalized_hash, last_finalized_number), (&hash, f_num));
|
||||
if &meta.finalized_hash != header.parent_hash() {
|
||||
return Err(::client::error::ErrorKind::NonSequentialFinalization(
|
||||
format!("Last finalized {:?} not parent of {:?}",
|
||||
meta.finalized_hash, hash),
|
||||
).into())
|
||||
}
|
||||
|
||||
transaction.put(columns::META, meta_keys::FINALIZED_BLOCK, hash.as_ref());
|
||||
|
||||
// build new CHT if required
|
||||
let mut build_cht = |header: &Block::Header| -> ClientResult<()> {
|
||||
if let Some(new_cht_number) = cht::is_build_required(cht::SIZE, *header.number()) {
|
||||
let new_cht_start: NumberFor<Block> = cht::start_number(cht::SIZE, new_cht_number);
|
||||
let new_cht_root = cht::compute_root::<Block::Header, Blake2Hasher, _>(
|
||||
cht::SIZE, new_cht_number, (new_cht_start.as_()..)
|
||||
.map(|num| self.hash(As::sa(num)).unwrap_or_default())
|
||||
);
|
||||
if let Some(new_cht_number) = cht::is_build_required(cht::SIZE, *header.number()) {
|
||||
let new_cht_start: NumberFor<Block> = cht::start_number(cht::SIZE, new_cht_number);
|
||||
let new_cht_root = cht::compute_root::<Block::Header, Blake2Hasher, _>(
|
||||
cht::SIZE, new_cht_number, (new_cht_start.as_()..)
|
||||
.map(|num| self.hash(As::sa(num)).unwrap_or_default())
|
||||
);
|
||||
|
||||
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 let Some(new_cht_root) = new_cht_root {
|
||||
transaction.put(columns::CHT, &number_to_lookup_key(new_cht_start), new_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);
|
||||
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 {
|
||||
let id = read_id::<Block>(&*self.db, columns::HASH_LOOKUP, BlockId::Number(prune_block))?;
|
||||
if let Some(hash) = id {
|
||||
let lookup_key = number_to_lookup_key(prune_block);
|
||||
transaction.delete(columns::HASH_LOOKUP, &lookup_key);
|
||||
transaction.delete(columns::HEADER, hash.as_ref());
|
||||
}
|
||||
prune_block += <<Block as BlockT>::Header as HeaderT>::Number::one();
|
||||
while prune_block <= new_cht_end {
|
||||
let id = read_id::<Block>(&*self.db, columns::HASH_LOOKUP, BlockId::Number(prune_block))?;
|
||||
if let Some(hash) = id {
|
||||
let lookup_key = number_to_lookup_key(prune_block);
|
||||
transaction.delete(columns::HASH_LOOKUP, &lookup_key);
|
||||
transaction.delete(columns::HEADER, hash.as_ref());
|
||||
}
|
||||
prune_block += <<Block as BlockT>::Header as HeaderT>::Number::one();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
};
|
||||
|
||||
// attempt to build CHT for all newly finalized blocks.
|
||||
let last_finalized_u64 = last_finalized_number.as_();
|
||||
for num in (last_finalized_u64..number_u64).map(|x| x + 1) {
|
||||
let num = As::sa(num);
|
||||
if num == f_num {
|
||||
build_cht(header)?;
|
||||
} else {
|
||||
let old_header = match self.header(BlockId::Number(num))? {
|
||||
Some(x) => x,
|
||||
None => panic!("finalizing block {} implies existence of block {}; qed", f_num, num),
|
||||
};
|
||||
|
||||
build_cht(&old_header)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -260,8 +227,7 @@ impl<Block: BlockT> LightStorage<Block> {
|
||||
}
|
||||
|
||||
impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
|
||||
where
|
||||
Block: BlockT,
|
||||
where Block: BlockT,
|
||||
{
|
||||
fn import_header(
|
||||
&self,
|
||||
@@ -285,11 +251,10 @@ impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
|
||||
let meta = self.meta.read();
|
||||
if meta.best_hash != Default::default() {
|
||||
let parent_hash = *header.parent_hash();
|
||||
let tree_route = ::utils::tree_route::<Block>(
|
||||
&*self.db,
|
||||
columns::HEADER,
|
||||
meta.best_hash,
|
||||
parent_hash,
|
||||
let tree_route = ::client::blockchain::tree_route(
|
||||
self,
|
||||
BlockId::Hash(meta.best_hash),
|
||||
BlockId::Hash(parent_hash),
|
||||
)?;
|
||||
|
||||
// update block number to hash lookup entries.
|
||||
@@ -377,6 +342,26 @@ pub(crate) mod tests {
|
||||
|
||||
type Block = RawBlock<u32>;
|
||||
|
||||
pub fn insert_block_with_extrinsics_root(
|
||||
db: &LightStorage<Block>,
|
||||
parent: &Hash,
|
||||
number: u64,
|
||||
authorities: Option<Vec<AuthorityId>>,
|
||||
extrinsics_root: Hash,
|
||||
) -> Hash {
|
||||
let header = Header {
|
||||
number: number.into(),
|
||||
parent_hash: *parent,
|
||||
state_root: Default::default(),
|
||||
digest: Default::default(),
|
||||
extrinsics_root,
|
||||
};
|
||||
|
||||
let hash = header.hash();
|
||||
db.import_header(header, authorities, NewBlockState::Best).unwrap();
|
||||
hash
|
||||
}
|
||||
|
||||
pub fn insert_block(
|
||||
db: &LightStorage<Block>,
|
||||
parent: &Hash,
|
||||
@@ -486,6 +471,9 @@ pub(crate) mod tests {
|
||||
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();
|
||||
assert_eq!(db.db.iter(columns::HEADER).count(), (1 + cht::SIZE + 1) as usize);
|
||||
assert_eq!(db.db.iter(columns::CHT).count(), 1);
|
||||
@@ -507,16 +495,79 @@ 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 = Default::default();
|
||||
for i in 0..1 + cht::SIZE + cht::SIZE + 1 {
|
||||
let mut prev_hash = insert_block(&db, &Default::default(), 0, None);
|
||||
for i in 1..1 + cht::SIZE + cht::SIZE + 1 {
|
||||
prev_hash = insert_block(&db, &prev_hash, i as u64, None);
|
||||
db.finalize_header(BlockId::Hash(prev_hash)).unwrap();
|
||||
}
|
||||
|
||||
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();
|
||||
assert_eq!(cht_root_1, cht_root_2);
|
||||
assert_eq!(cht_root_2, cht_root_3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tree_route_works() {
|
||||
let db = LightStorage::new_test();
|
||||
let block0 = insert_block(&db, &Default::default(), 0, None);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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 tree_route = ::client::blockchain::tree_route(
|
||||
&db,
|
||||
BlockId::Hash(a3),
|
||||
BlockId::Hash(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]);
|
||||
assert_eq!(tree_route.enacted().iter().map(|r| r.hash).collect::<Vec<_>>(), vec![b1, b2]);
|
||||
}
|
||||
|
||||
{
|
||||
let tree_route = ::client::blockchain::tree_route(
|
||||
&db,
|
||||
BlockId::Hash(a1),
|
||||
BlockId::Hash(a3),
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(tree_route.common_block().hash, a1);
|
||||
assert!(tree_route.retracted().is_empty());
|
||||
assert_eq!(tree_route.enacted().iter().map(|r| r.hash).collect::<Vec<_>>(), vec![a2, a3]);
|
||||
}
|
||||
|
||||
{
|
||||
let tree_route = ::client::blockchain::tree_route(
|
||||
&db,
|
||||
BlockId::Hash(a3),
|
||||
BlockId::Hash(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]);
|
||||
assert!(tree_route.enacted().is_empty());
|
||||
}
|
||||
|
||||
{
|
||||
let tree_route = ::client::blockchain::tree_route(
|
||||
&db,
|
||||
BlockId::Hash(a2),
|
||||
BlockId::Hash(a2),
|
||||
).unwrap();
|
||||
|
||||
assert_eq!(tree_route.common_block().hash, a2);
|
||||
assert!(tree_route.retracted().is_empty());
|
||||
assert!(tree_route.enacted().is_empty());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user