Add standalone set head operations (#1600)

* Refactor head setting operation to a separate function

* Fix commit issue and implement set_head standalone in db

* Add standalone set head operations

* Address grumbles

* Change number_and_hash_to_lookup key in light mod to take reference

* Fix bug in set head commit

* Add a convenience fn

* Fix a deadlock

* Fix missing argument
This commit is contained in:
Wei Tang
2019-02-12 17:21:25 +01:00
committed by Gav Wood
parent 6122f7d6b6
commit 22048dba60
7 changed files with 321 additions and 175 deletions
+123 -89
View File
@@ -257,6 +257,7 @@ pub struct BlockImportOperation<Block: BlockT, H: Hasher> {
pending_block: Option<PendingBlock<Block>>,
aux_ops: Vec<(Vec<u8>, Option<Vec<u8>>)>,
finalized_blocks: Vec<(BlockId<Block>, Option<Justification>)>,
set_head: Option<BlockId<Block>>,
}
impl<Block: BlockT, H: Hasher> BlockImportOperation<Block, H> {
@@ -355,6 +356,12 @@ where Block: BlockT<Hash=H256>,
self.finalized_blocks.push((block, justification));
Ok(())
}
fn mark_head(&mut self, block: BlockId<Block>) -> Result<(), client::error::Error> {
assert!(self.set_head.is_none(), "Only one set head per operation is allowed");
self.set_head = Some(block);
Ok(())
}
}
struct StorageDb<Block: BlockT> {
@@ -563,6 +570,71 @@ impl<Block: BlockT<Hash=H256>> Backend<Block> {
})
}
/// Handle setting head within a transaction. `route_to` should be the last
/// block that existed in the database. `best_to` should be the best block
/// to be set.
///
/// 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<(Vec<Block::Hash>, Vec<Block::Hash>), client::error::Error> {
let mut enacted = Vec::default();
let mut retracted = Vec::default();
let meta = self.blockchain.meta.read();
// cannot find tree route with empty DB.
if meta.best_hash != Default::default() {
let tree_route = ::client::blockchain::tree_route(
&self.blockchain,
BlockId::Hash(meta.best_hash),
BlockId::Hash(route_to),
)?;
// uncanonicalize: check safety violations and ensure the numbers no longer
// point to these block hashes in the key mapping.
for r in tree_route.retracted() {
if r.hash == meta.finalized_hash {
warn!(
"Potential safety failure: reverting finalized block {:?}",
(&r.number, &r.hash)
);
return Err(::client::error::ErrorKind::NotInFinalizedChain.into());
}
retracted.push(r.hash.clone());
utils::remove_number_to_key_mapping(
transaction,
columns::KEY_LOOKUP,
r.number
);
}
// canonicalize: set the number lookup to map to this block's hash.
for e in tree_route.enacted() {
enacted.push(e.hash.clone());
utils::insert_number_to_key_mapping(
transaction,
columns::KEY_LOOKUP,
e.number,
e.hash
);
}
}
let lookup_key = utils::number_and_hash_to_lookup_key(best_to.0, &best_to.1);
transaction.put(columns::META, meta_keys::BEST_BLOCK, &lookup_key);
utils::insert_number_to_key_mapping(
transaction,
columns::KEY_LOOKUP,
best_to.0,
best_to.1,
);
Ok((enacted, retracted))
}
fn ensure_sequential_finalization(
&self,
header: &Block::Header,
@@ -592,7 +664,7 @@ impl<Block: BlockT<Hash=H256>> Backend<Block> {
if let Some(justification) = justification {
transaction.put(
columns::JUSTIFICATION,
&utils::number_and_hash_to_lookup_key(number, *hash),
&utils::number_and_hash_to_lookup_key(number, hash),
&justification.encode(),
);
}
@@ -657,7 +729,7 @@ impl<Block: BlockT<Hash=H256>> Backend<Block> {
}
}
if let Some(pending_block) = operation.pending_block {
let imported = if let Some(pending_block) = operation.pending_block {
let hash = pending_block.header.hash();
let parent_hash = *pending_block.header.parent_hash();
let number = pending_block.header.number().clone();
@@ -665,58 +737,11 @@ impl<Block: BlockT<Hash=H256>> Backend<Block> {
// blocks are keyed by number + hash.
let lookup_key = utils::number_and_hash_to_lookup_key(number, hash);
let mut enacted = Vec::default();
let mut retracted = Vec::default();
if pending_block.leaf_state.is_best() {
let meta = self.blockchain.meta.read();
// cannot find tree route with empty DB.
if meta.best_hash != Default::default() {
let tree_route = ::client::blockchain::tree_route(
&self.blockchain,
BlockId::Hash(meta.best_hash),
BlockId::Hash(parent_hash),
)?;
// uncanonicalize: check safety violations and ensure the numbers no longer
// point to these block hashes in the key mapping.
for r in tree_route.retracted() {
retracted.push(r.hash.clone());
if r.hash == meta.finalized_hash {
warn!("Potential safety failure: reverting finalized block {:?}",
(&r.number, &r.hash));
return Err(::client::error::ErrorKind::NotInFinalizedChain.into());
}
utils::remove_number_to_key_mapping(
&mut transaction,
columns::KEY_LOOKUP,
r.number
);
}
// canonicalize: set the number lookup to map to this block's hash.
for e in tree_route.enacted() {
enacted.push(e.hash.clone());
utils::insert_number_to_key_mapping(
&mut transaction,
columns::KEY_LOOKUP,
e.number,
e.hash
);
}
}
transaction.put(columns::META, meta_keys::BEST_BLOCK, &lookup_key);
utils::insert_number_to_key_mapping(
&mut transaction,
columns::KEY_LOOKUP,
number,
hash,
);
}
let (enacted, retracted) = if pending_block.leaf_state.is_best() {
self.set_head_with_transaction(&mut transaction, parent_hash, (number, hash))?
} else {
(Default::default(), Default::default())
};
utils::insert_hash_to_key_mapping(
&mut transaction,
@@ -752,15 +777,12 @@ impl<Block: BlockT<Hash=H256>> Backend<Block> {
apply_state_commit(&mut transaction, commit);
// Check if need to finalize. Genesis is always finalized instantly.
let finalized = number_u64 == 0 || match pending_block.leaf_state {
NewBlockState::Final => true,
_ => false,
};
let finalized = number_u64 == 0 || pending_block.leaf_state.is_final();
let header = &pending_block.header;
let is_best = pending_block.leaf_state.is_best();
let changes_trie_updates = operation.changes_trie_updates;
self.changes_tries_storage.commit(&mut transaction, changes_trie_updates);
if finalized {
@@ -774,49 +796,60 @@ impl<Block: BlockT<Hash=H256>> Backend<Block> {
debug!(target: "db", "DB Commit {:?} ({}), best = {}", hash, number, is_best);
{
let displaced_leaf = {
let mut leaves = self.blockchain.leaves.write();
let displaced_leaf = leaves.import(hash, number, parent_hash);
leaves.prepare_transaction(&mut transaction, columns::META, meta_keys::LEAF_PREFIX);
let write_result = self.storage.db.write(transaction).map_err(db_err);
if let Err(e) = write_result {
// revert leaves set update, if there was one.
if let Some(displaced_leaf) = displaced_leaf {
leaves.undo(displaced_leaf);
}
return Err(e);
displaced_leaf
};
meta_updates.push((hash, number, pending_block.leaf_state.is_best(), finalized));
Some((number, hash, enacted, retracted, displaced_leaf, is_best))
} else {
None
};
if let Some(set_head) = operation.set_head {
if let Some(header) = ::client::blockchain::HeaderBackend::header(&self.blockchain, set_head)? {
let number = header.number();
let hash = header.hash();
self.set_head_with_transaction(
&mut transaction,
hash.clone(),
(number.clone(), hash.clone())
)?;
} else {
return Err(client::error::ErrorKind::UnknownBlock(format!("Cannot set head {:?}", set_head)).into())
}
}
let write_result = self.storage.db.write(transaction).map_err(db_err);
if let Some((number, hash, enacted, retracted, displaced_leaf, is_best)) = imported {
if let Err(e) = write_result {
if let Some(displaced_leaf) = displaced_leaf {
self.blockchain.leaves.write().undo(displaced_leaf);
}
drop(leaves);
return Err(e)
}
for (hash, number, is_best, is_finalized) in meta_updates {
self.blockchain.update_meta(hash, number, is_best, is_finalized);
}
self.blockchain.update_meta(
hash.clone(),
number.clone(),
is_best,
finalized,
);
// sync canonical state cache
operation.old_state.sync_cache(
&enacted,
&retracted,
operation.storage_updates,
Some(hash),
Some(number),
|| is_best
|| is_best,
);
} else {
// No pending block, just write the transaction and apply meta changes
self.storage.db.write(transaction).map_err(db_err)?;
for (hash, number, is_best, is_finalized) in meta_updates {
self.blockchain.update_meta(hash, number, is_best, is_finalized);
}
}
for (hash, number, is_best, is_finalized) in meta_updates {
self.blockchain.update_meta(hash, number, is_best, is_finalized);
}
Ok(())
}
@@ -911,6 +944,7 @@ impl<Block> client::backend::Backend<Block, Blake2Hasher> for Backend<Block> whe
changes_trie_updates: MemoryDB::default(),
aux_ops: Vec::new(),
finalized_blocks: Vec::new(),
set_head: None,
})
}
@@ -990,7 +1024,7 @@ impl<Block> client::backend::Backend<Block, Blake2Hasher> for Backend<Block> whe
let hash = self.blockchain.hash(best)?.ok_or_else(
|| client::error::ErrorKind::UnknownBlock(
format!("Error reverting to {}. Block hash not found.", best)))?;
let key = utils::number_and_hash_to_lookup_key(best.clone(), hash.clone());
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());
self.storage.db.write(transaction).map_err(db_err)?;
@@ -1032,7 +1066,7 @@ impl<Block> client::backend::Backend<Block, Blake2Hasher> for Backend<Block> whe
Err(client::error::ErrorKind::UnknownBlock(format!("State already discarded for {:?}", block)).into())
}
},
Ok(None) => Err(client::error::ErrorKind::UnknownBlock(format!("{:?}", block)).into()),
Ok(None) => Err(client::error::ErrorKind::UnknownBlock(format!("Unknown state for block {:?}", block)).into()),
Err(e) => Err(e),
}
}
+71 -43
View File
@@ -189,6 +189,61 @@ impl<Block: BlockT> LightStorage<Block> {
.cloned()))
}
/// Handle setting head within a transaction. `route_to` should be the last
/// block that existed in the database. `best_to` should be the best block
/// to be set.
///
/// 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> {
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(
self,
BlockId::Hash(meta.best_hash),
BlockId::Hash(route_to),
)?;
// 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 {:?}",
(&retracted.number, &retracted.hash));
}
utils::remove_number_to_key_mapping(
transaction,
columns::KEY_LOOKUP,
retracted.number
);
}
for enacted in tree_route.enacted() {
utils::insert_number_to_key_mapping(
transaction,
columns::KEY_LOOKUP,
enacted.number,
enacted.hash
);
}
}
transaction.put(columns::META, meta_keys::BEST_BLOCK, &lookup_key);
utils::insert_number_to_key_mapping(
transaction,
columns::KEY_LOOKUP,
best_to.0,
best_to.1,
);
Ok(())
}
// Note that a block is finalized. Only call with child of last finalized block.
fn note_finalized(
&self,
@@ -325,51 +380,10 @@ impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
}
// blocks are keyed by number + hash.
let lookup_key = utils::number_and_hash_to_lookup_key(number, hash);
let lookup_key = utils::number_and_hash_to_lookup_key(number, &hash);
if leaf_state.is_best() {
// handle reorg.
{
let meta = self.meta.read();
if meta.best_hash != Default::default() {
let tree_route = ::client::blockchain::tree_route(
self,
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 {
warn!("Safety failure: reverting finalized block {:?}",
(&retracted.number, &retracted.hash));
}
utils::remove_number_to_key_mapping(
&mut transaction,
columns::KEY_LOOKUP,
retracted.number
);
}
for enacted in tree_route.enacted() {
utils::insert_number_to_key_mapping(
&mut transaction,
columns::KEY_LOOKUP,
enacted.number,
enacted.hash
);
}
}
}
transaction.put(columns::META, meta_keys::BEST_BLOCK, &lookup_key);
utils::insert_number_to_key_mapping(
&mut transaction,
columns::KEY_LOOKUP,
number,
hash,
);
self.set_head_with_transaction(&mut transaction, parent_hash, (number, hash))?;
}
utils::insert_hash_to_key_mapping(
@@ -426,6 +440,20 @@ impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
Ok(())
}
fn set_head(&self, id: BlockId<Block>) -> ClientResult<()> {
if let Some(header) = self.header(id)? {
let hash = header.hash();
let number = header.number();
let mut transaction = DBTransaction::new();
self.set_head_with_transaction(&mut transaction, hash.clone(), (number.clone(), hash.clone()))?;
self.db.write(transaction).map_err(db_err)?;
Ok(())
} else {
Err(ClientErrorKind::UnknownBlock(format!("Cannot set head {:?}", id)).into())
}
}
fn header_cht_root(&self, cht_size: u64, block: NumberFor<Block>) -> ClientResult<Block::Hash> {
self.read_cht_root(HEADER_CHT_PREFIX, cht_size, block)
}