mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-20 19:51:02 +00:00
Support block revert operation in blockchain cache (#3401)
* support block revert operation in cache * #[cfg(test)] -> fn unused_sink() * swap conditions * post-merge fix
This commit is contained in:
committed by
Gavin Wood
parent
b618d8f0b2
commit
35f8cd19cb
+140
-1
@@ -39,7 +39,7 @@
|
|||||||
//! Finalized entry E1 is pruned when block B is finalized so that:
|
//! Finalized entry E1 is pruned when block B is finalized so that:
|
||||||
//! EntryAt(B.number - prune_depth).points_to(E1)
|
//! EntryAt(B.number - prune_depth).points_to(E1)
|
||||||
|
|
||||||
use std::collections::BTreeSet;
|
use std::collections::{BTreeSet, BTreeMap};
|
||||||
|
|
||||||
use log::warn;
|
use log::warn;
|
||||||
|
|
||||||
@@ -89,6 +89,9 @@ pub enum CommitOperation<Block: BlockT, T: CacheItemT> {
|
|||||||
/// - new entry is finalized AND/OR
|
/// - new entry is finalized AND/OR
|
||||||
/// - some forks are destroyed
|
/// - some forks are destroyed
|
||||||
BlockFinalized(ComplexBlockId<Block>, Option<Entry<Block, T>>, BTreeSet<usize>),
|
BlockFinalized(ComplexBlockId<Block>, Option<Entry<Block, T>>, BTreeSet<usize>),
|
||||||
|
/// When best block is reverted - contains the forks that have to be updated
|
||||||
|
/// (they're either destroyed, or their best entry is updated to earlier block).
|
||||||
|
BlockReverted(BTreeMap<usize, Option<Fork<Block, T>>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Single fork of list-based cache.
|
/// Single fork of list-based cache.
|
||||||
@@ -333,6 +336,44 @@ impl<Block: BlockT, T: CacheItemT, S: Storage<Block, T>> ListCache<Block, T, S>
|
|||||||
Ok(Some(operation))
|
Ok(Some(operation))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When block is reverted.
|
||||||
|
pub fn on_block_revert<Tx: StorageTransaction<Block, T>>(
|
||||||
|
&self,
|
||||||
|
tx: &mut Tx,
|
||||||
|
reverted_block: &ComplexBlockId<Block>,
|
||||||
|
) -> ClientResult<CommitOperation<Block, T>> {
|
||||||
|
// can't revert finalized blocks
|
||||||
|
debug_assert!(self.best_finalized_block.number < reverted_block.number);
|
||||||
|
|
||||||
|
// iterate all unfinalized forks and truncate/destroy if required
|
||||||
|
let mut updated = BTreeMap::new();
|
||||||
|
for (index, fork) in self.unfinalized.iter().enumerate() {
|
||||||
|
// we only need to truncate fork if its head is ancestor of truncated block
|
||||||
|
if fork.head.valid_from.number < reverted_block.number {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we only need to truncate fork if its head is connected to truncated block
|
||||||
|
if !chain::is_connected_to_block(&self.storage, reverted_block, &fork.head.valid_from)? {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let updated_fork = fork.truncate(
|
||||||
|
&self.storage,
|
||||||
|
tx,
|
||||||
|
reverted_block.number,
|
||||||
|
self.best_finalized_block.number,
|
||||||
|
)?;
|
||||||
|
updated.insert(index, updated_fork);
|
||||||
|
}
|
||||||
|
|
||||||
|
// schedule commit operation and update meta
|
||||||
|
let operation = CommitOperation::BlockReverted(updated);
|
||||||
|
tx.update_meta(self.best_finalized_entry.as_ref(), &self.unfinalized, &operation);
|
||||||
|
|
||||||
|
Ok(operation)
|
||||||
|
}
|
||||||
|
|
||||||
/// When transaction is committed.
|
/// When transaction is committed.
|
||||||
pub fn on_transaction_commit(&mut self, op: CommitOperation<Block, T>) {
|
pub fn on_transaction_commit(&mut self, op: CommitOperation<Block, T>) {
|
||||||
match op {
|
match op {
|
||||||
@@ -366,6 +407,14 @@ impl<Block: BlockT, T: CacheItemT, S: Storage<Block, T>> ListCache<Block, T, S>
|
|||||||
self.unfinalized.remove(*fork_index);
|
self.unfinalized.remove(*fork_index);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
CommitOperation::BlockReverted(forks) => {
|
||||||
|
for (fork_index, updated_fork) in forks.into_iter().rev() {
|
||||||
|
match updated_fork {
|
||||||
|
Some(updated_fork) => self.unfinalized[fork_index] = updated_fork,
|
||||||
|
None => { self.unfinalized.remove(fork_index); },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,6 +582,43 @@ impl<Block: BlockT, T: CacheItemT> Fork<Block, T> {
|
|||||||
best_finalized_block,
|
best_finalized_block,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Truncate fork by deleting all entries that are descendants of given block.
|
||||||
|
pub fn truncate<S: Storage<Block, T>, Tx: StorageTransaction<Block, T>>(
|
||||||
|
&self,
|
||||||
|
storage: &S,
|
||||||
|
tx: &mut Tx,
|
||||||
|
reverting_block: NumberFor<Block>,
|
||||||
|
best_finalized_block: NumberFor<Block>,
|
||||||
|
) -> ClientResult<Option<Fork<Block, T>>> {
|
||||||
|
let mut current = self.head.valid_from.clone();
|
||||||
|
loop {
|
||||||
|
// read pointer to previous entry
|
||||||
|
let entry = storage.require_entry(¤t)?;
|
||||||
|
|
||||||
|
// truncation stops when we have reached the ancestor of truncated block
|
||||||
|
if current.number < reverting_block {
|
||||||
|
// if we have reached finalized block => destroy fork
|
||||||
|
if chain::is_finalized_block(storage, ¤t, best_finalized_block)? {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// else fork needs to be updated
|
||||||
|
return Ok(Some(Fork {
|
||||||
|
best_block: None,
|
||||||
|
head: entry.into_entry(current),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.remove_storage_entry(¤t);
|
||||||
|
|
||||||
|
// truncation also stops when there are no more entries in the list
|
||||||
|
current = match entry.prev_valid_from {
|
||||||
|
Some(prev_valid_from) => prev_valid_from,
|
||||||
|
None => return Ok(None),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Destroy fork by deleting all unfinalized entries.
|
/// Destroy fork by deleting all unfinalized entries.
|
||||||
@@ -1400,4 +1486,57 @@ pub mod tests {
|
|||||||
do_test(PruningStrategy::ByDepth(10));
|
do_test(PruningStrategy::ByDepth(10));
|
||||||
do_test(PruningStrategy::NeverPrune)
|
do_test(PruningStrategy::NeverPrune)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn revert_block_works() {
|
||||||
|
// 1 -> (2) -> 3 -> 4 -> 5
|
||||||
|
// \
|
||||||
|
// -> 5''
|
||||||
|
// \
|
||||||
|
// -> (3') -> 4' -> 5'
|
||||||
|
let mut cache = ListCache::new(
|
||||||
|
DummyStorage::new()
|
||||||
|
.with_meta(Some(correct_id(1)), vec![correct_id(5), fork_id(1, 2, 5), fork_id(2, 4, 5)])
|
||||||
|
.with_id(1, correct_id(1).hash)
|
||||||
|
.with_entry(correct_id(1), StorageEntry { prev_valid_from: None, value: 1 })
|
||||||
|
.with_entry(correct_id(3), StorageEntry { prev_valid_from: Some(correct_id(1)), value: 3 })
|
||||||
|
.with_entry(correct_id(4), StorageEntry { prev_valid_from: Some(correct_id(3)), value: 4 })
|
||||||
|
.with_entry(correct_id(5), StorageEntry { prev_valid_from: Some(correct_id(4)), value: 5 })
|
||||||
|
.with_entry(fork_id(1, 2, 4), StorageEntry { prev_valid_from: Some(correct_id(1)), value: 14 })
|
||||||
|
.with_entry(fork_id(1, 2, 5), StorageEntry { prev_valid_from: Some(fork_id(1, 2, 4)), value: 15 })
|
||||||
|
.with_entry(fork_id(2, 4, 5), StorageEntry { prev_valid_from: Some(correct_id(4)), value: 25 })
|
||||||
|
.with_header(test_header(1))
|
||||||
|
.with_header(test_header(2))
|
||||||
|
.with_header(test_header(3))
|
||||||
|
.with_header(test_header(4))
|
||||||
|
.with_header(test_header(5))
|
||||||
|
.with_header(fork_header(1, 2, 3))
|
||||||
|
.with_header(fork_header(1, 2, 4))
|
||||||
|
.with_header(fork_header(1, 2, 5))
|
||||||
|
.with_header(fork_header(2, 4, 5)),
|
||||||
|
PruningStrategy::ByDepth(1024), correct_id(1)
|
||||||
|
);
|
||||||
|
|
||||||
|
// when 5 is reverted: entry 5 is truncated
|
||||||
|
let op = cache.on_block_revert(&mut DummyTransaction::new(), &correct_id(5)).unwrap();
|
||||||
|
assert_eq!(op, CommitOperation::BlockReverted(vec![
|
||||||
|
(0, Some(Fork { best_block: None, head: Entry { valid_from: correct_id(4), value: 4 } })),
|
||||||
|
].into_iter().collect()));
|
||||||
|
cache.on_transaction_commit(op);
|
||||||
|
|
||||||
|
// when 3 is reverted: entries 4+5' are truncated
|
||||||
|
let op = cache.on_block_revert(&mut DummyTransaction::new(), &correct_id(3)).unwrap();
|
||||||
|
assert_eq!(op, CommitOperation::BlockReverted(vec![
|
||||||
|
(0, None),
|
||||||
|
(2, None),
|
||||||
|
].into_iter().collect()));
|
||||||
|
cache.on_transaction_commit(op);
|
||||||
|
|
||||||
|
// when 2 is reverted: entries 4'+5' are truncated
|
||||||
|
let op = cache.on_block_revert(&mut DummyTransaction::new(), &correct_id(2)).unwrap();
|
||||||
|
assert_eq!(op, CommitOperation::BlockReverted(vec![
|
||||||
|
(0, None),
|
||||||
|
].into_iter().collect()));
|
||||||
|
cache.on_transaction_commit(op);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -227,6 +227,14 @@ mod meta {
|
|||||||
unfinalized.remove(*fork_index);
|
unfinalized.remove(*fork_index);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
CommitOperation::BlockReverted(ref forks) => {
|
||||||
|
for (fork_index, updated_fork) in forks.iter().rev() {
|
||||||
|
match updated_fork {
|
||||||
|
Some(updated_fork) => unfinalized[*fork_index] = &updated_fork.head().valid_from,
|
||||||
|
None => { unfinalized.remove(*fork_index); },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
(finalized, unfinalized).encode()
|
(finalized, unfinalized).encode()
|
||||||
|
|||||||
+21
@@ -268,6 +268,27 @@ impl<'a, Block: BlockT> DbCacheTransaction<'a, Block> {
|
|||||||
|
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// When block is reverted.
|
||||||
|
pub fn on_block_revert(
|
||||||
|
mut self,
|
||||||
|
reverted_block: &ComplexBlockId<Block>,
|
||||||
|
) -> ClientResult<Self> {
|
||||||
|
for (name, cache) in self.cache.cache_at.iter() {
|
||||||
|
let op = cache.on_block_revert(
|
||||||
|
&mut self::list_storage::DbStorageTransaction::new(
|
||||||
|
cache.storage(),
|
||||||
|
&mut self.tx
|
||||||
|
),
|
||||||
|
reverted_block,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
assert!(!self.cache_at_op.contains_key(name));
|
||||||
|
self.cache_at_op.insert(name.to_owned(), op);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Synchronous implementation of database-backed blockchain data cache.
|
/// Synchronous implementation of database-backed blockchain data cache.
|
||||||
|
|||||||
@@ -1518,6 +1518,12 @@ impl<Block> client::backend::Backend<Block, Blake2Hasher> for Backend<Block> whe
|
|||||||
impl<Block> client::backend::LocalBackend<Block, Blake2Hasher> for Backend<Block>
|
impl<Block> client::backend::LocalBackend<Block, Blake2Hasher> for Backend<Block>
|
||||||
where Block: BlockT<Hash=H256> {}
|
where Block: BlockT<Hash=H256> {}
|
||||||
|
|
||||||
|
/// TODO: remove me in #3201
|
||||||
|
pub fn unused_sink<Block: BlockT>(cache_tx: crate::cache::DbCacheTransaction<Block>) {
|
||||||
|
cache_tx.on_block_revert(&crate::cache::ComplexBlockId::new(Default::default(), 0.into())).unwrap();
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use hash_db::{HashDB, EMPTY_PREFIX};
|
use hash_db::{HashDB, EMPTY_PREFIX};
|
||||||
|
|||||||
Reference in New Issue
Block a user