From c5f5c9c28c5b6e33701b7714a566b0c7347d57ae Mon Sep 17 00:00:00 2001 From: Arkadiy Paronyan <arkady.paronyan@gmail.com> Date: Wed, 21 Jul 2021 22:35:58 +0200 Subject: [PATCH] Fix db metadata updates for existing headers (#9403) * Fix metadata updates on existing headers * Fail set_head on ancient blocks * Fmt unrelated code --- substrate/client/db/src/lib.rs | 166 ++++++++++++------- substrate/primitives/blockchain/src/error.rs | 3 + 2 files changed, 110 insertions(+), 59 deletions(-) diff --git a/substrate/client/db/src/lib.rs b/substrate/client/db/src/lib.rs index 3369b5fad05..455ec1ef6b9 100644 --- a/substrate/client/db/src/lib.rs +++ b/substrate/client/db/src/lib.rs @@ -1199,8 +1199,17 @@ impl<Block: BlockT> Backend<Block> { let mut enacted = Vec::default(); let mut retracted = Vec::default(); + let (best_number, best_hash) = best_to; + let meta = self.blockchain.meta.read(); + if meta.best_number > best_number && + (meta.best_number - best_number).saturated_into::<u64>() > + self.canonicalization_delay + { + return Err(sp_blockchain::Error::SetHeadTooOld.into()) + } + // cannot find tree route with empty DB. if meta.best_hash != Default::default() { let tree_route = sp_blockchain::tree_route(&self.blockchain, meta.best_hash, route_to)?; @@ -1233,13 +1242,13 @@ impl<Block: BlockT> Backend<Block> { } } - let lookup_key = utils::number_and_hash_to_lookup_key(best_to.0, &best_to.1)?; + let lookup_key = utils::number_and_hash_to_lookup_key(best_number, &best_hash)?; transaction.set_from_vec(columns::META, meta_keys::BEST_BLOCK, lookup_key); utils::insert_number_to_key_mapping( transaction, columns::KEY_LOOKUP, - best_to.0, - best_to.1, + best_number, + best_hash, )?; Ok((enacted, retracted)) @@ -1534,6 +1543,27 @@ impl<Block: BlockT> Backend<Block> { hash, number, is_best, operation.commit_state, existing_header, ); + self.state_usage.merge_sm(operation.old_state.usage_info()); + // release state reference so that it can be finalized + let cache = operation.old_state.into_cache_changes(); + + if finalized { + // TODO: ensure best chain contains this block. + self.ensure_sequential_finalization(header, Some(last_finalized_hash))?; + self.note_finalized( + &mut transaction, + true, + header, + hash, + &mut changes_trie_cache_ops, + &mut finalization_displaced_leaves, + operation.commit_state, + )?; + } else { + // canonicalize blocks which are old enough, regardless of finality. + self.force_delayed_canonicalize(&mut transaction, hash, *header.number())? + } + if !existing_header { let changes_trie_config_update = operation.changes_trie_config_update; changes_trie_cache_ops = Some(self.changes_tries_storage.commit( @@ -1550,37 +1580,14 @@ impl<Block: BlockT> Backend<Block> { changes_trie_cache_ops, )?); - self.state_usage.merge_sm(operation.old_state.usage_info()); - // release state reference so that it can be finalized - let cache = operation.old_state.into_cache_changes(); - - if finalized { - // TODO: ensure best chain contains this block. - self.ensure_sequential_finalization(header, Some(last_finalized_hash))?; - self.note_finalized( - &mut transaction, - true, - header, - hash, - &mut changes_trie_cache_ops, - &mut finalization_displaced_leaves, - operation.commit_state, - )?; - } else { - // canonicalize blocks which are old enough, regardless of finality. - self.force_delayed_canonicalize(&mut transaction, hash, *header.number())? - } - - let displaced_leaf = { + { let mut leaves = self.blockchain.leaves.write(); - let displaced_leaf = leaves.import(hash, number, parent_hash); + leaves.import(hash, number, parent_hash); leaves.prepare_transaction( &mut transaction, columns::META, meta_keys::LEAF_PREFIX, ); - - displaced_leaf }; let mut children = children::read_children( @@ -1599,28 +1606,17 @@ impl<Block: BlockT> Backend<Block> { parent_hash, children, ); + } - meta_updates.push(MetaUpdate { - hash, - number, - is_best: pending_block.leaf_state.is_best(), - is_finalized: finalized, - with_state: operation.commit_state, - }); + meta_updates.push(MetaUpdate { + hash, + number, + is_best: pending_block.leaf_state.is_best(), + is_finalized: finalized, + with_state: operation.commit_state, + }); - Some(( - pending_block.header, - number, - hash, - enacted, - retracted, - displaced_leaf, - is_best, - cache, - )) - } else { - None - } + Some((pending_block.header, number, hash, enacted, retracted, is_best, cache)) } else { None }; @@ -1660,17 +1656,7 @@ impl<Block: BlockT> Backend<Block> { // Apply all in-memory state changes. // Code beyond this point can't fail. - if let Some(( - header, - number, - hash, - enacted, - retracted, - _displaced_leaf, - is_best, - mut cache, - )) = imported - { + if let Some((header, number, hash, enacted, retracted, is_best, mut cache)) = imported { trace!(target: "db", "DB Commit done {:?}", hash); let header_metadata = CachedHeaderMetadata::from(&header); self.blockchain.insert_header_metadata(header_metadata.hash, header_metadata); @@ -3390,4 +3376,66 @@ pub(crate) mod tests { assert_eq!(None, backend.blockchain().header(BlockId::hash(prev_hash.clone())).unwrap()); assert!(!backend.have_state_at(&prev_hash, 1)); } + + #[test] + fn test_import_existing_block_as_new_head() { + let backend: Backend<Block> = Backend::new_test(10, 3); + let block0 = insert_header(&backend, 0, Default::default(), None, Default::default()); + let block1 = insert_header(&backend, 1, block0, None, Default::default()); + let block2 = insert_header(&backend, 2, block1, None, Default::default()); + let block3 = insert_header(&backend, 3, block2, None, Default::default()); + let block4 = insert_header(&backend, 4, block3, None, Default::default()); + let block5 = insert_header(&backend, 5, block4, None, Default::default()); + assert_eq!(backend.blockchain().info().best_hash, block5); + + // Insert 1 as best again. This should fail because canonicalization_delay == 3 and best == 5 + let header = Header { + number: 1, + parent_hash: block0, + state_root: BlakeTwo256::trie_root(Vec::new()), + digest: Default::default(), + extrinsics_root: Default::default(), + }; + let mut op = backend.begin_operation().unwrap(); + op.set_block_data(header, None, None, None, NewBlockState::Best).unwrap(); + assert!(matches!(backend.commit_operation(op), Err(sp_blockchain::Error::SetHeadTooOld))); + + // Insert 2 as best again. + let header = Header { + number: 2, + parent_hash: block1, + state_root: BlakeTwo256::trie_root(Vec::new()), + digest: Default::default(), + extrinsics_root: Default::default(), + }; + let mut op = backend.begin_operation().unwrap(); + op.set_block_data(header, None, None, None, NewBlockState::Best).unwrap(); + backend.commit_operation(op).unwrap(); + assert_eq!(backend.blockchain().info().best_hash, block2); + } + + #[test] + fn test_import_existing_block_as_final() { + let backend: Backend<Block> = Backend::new_test(10, 10); + let block0 = insert_header(&backend, 0, Default::default(), None, Default::default()); + let block1 = insert_header(&backend, 1, block0, None, Default::default()); + let _block2 = insert_header(&backend, 2, block1, None, Default::default()); + // Genesis is auto finalized, the rest are not. + assert_eq!(backend.blockchain().info().finalized_hash, block0); + + // Insert 1 as final again. + let header = Header { + number: 1, + parent_hash: block0, + state_root: BlakeTwo256::trie_root(Vec::new()), + digest: Default::default(), + extrinsics_root: Default::default(), + }; + + let mut op = backend.begin_operation().unwrap(); + op.set_block_data(header, None, None, None, NewBlockState::Final).unwrap(); + backend.commit_operation(op).unwrap(); + + assert_eq!(backend.blockchain().info().finalized_hash, block1); + } } diff --git a/substrate/primitives/blockchain/src/error.rs b/substrate/primitives/blockchain/src/error.rs index bc27c36401e..ef3afa5bce9 100644 --- a/substrate/primitives/blockchain/src/error.rs +++ b/substrate/primitives/blockchain/src/error.rs @@ -156,6 +156,9 @@ pub enum Error { #[error("State Database error: {0}")] StateDatabase(String), + #[error("Failed to set the chain head to a block that's too old.")] + SetHeadTooOld, + #[error(transparent)] Application(#[from] Box<dyn std::error::Error + Send + Sync + 'static>), -- GitLab