From b14238cb7d63bc69fbbca4e3436fb8c323469995 Mon Sep 17 00:00:00 2001 From: Davide Galassi <davxy@datawok.net> Date: Wed, 7 Jun 2023 11:17:45 +0200 Subject: [PATCH] Finalized should be before best (#14308) * Finalized block should not be after best block * Remove unwrap * Apply code review suggestion Co-authored-by: Koute <koute@users.noreply.github.com> * Add test --------- Co-authored-by: Koute <koute@users.noreply.github.com> --- substrate/client/service/src/client/client.rs | 54 ++++++++++--------- .../client/service/test/src/client/mod.rs | 38 +++++++++++++ 2 files changed, 67 insertions(+), 25 deletions(-) diff --git a/substrate/client/service/src/client/client.rs b/substrate/client/service/src/client/client.rs index 91c59cdbac8..3b1a526154d 100644 --- a/substrate/client/service/src/client/client.rs +++ b/substrate/client/service/src/client/client.rs @@ -52,7 +52,7 @@ use sp_api::{ }; use sp_blockchain::{ self as blockchain, Backend as ChainBackend, CachedHeaderMetadata, Error, - HeaderBackend as ChainHeaderBackend, HeaderMetadata, + HeaderBackend as ChainHeaderBackend, HeaderMetadata, Info as BlockchainInfo, }; use sp_consensus::{BlockOrigin, BlockStatus, Error as ConsensusError}; @@ -717,7 +717,7 @@ where operation, parent_hash, None, - info.best_hash, + &info, make_notifications, )?; } @@ -907,52 +907,56 @@ where fn apply_finality_with_block_hash( &self, operation: &mut ClientImportOperation<Block, B>, - block: Block::Hash, + hash: Block::Hash, justification: Option<Justification>, - best_block: Block::Hash, + info: &BlockchainInfo<Block>, notify: bool, ) -> sp_blockchain::Result<()> { - // find tree route from last finalized to given block. - let last_finalized = self.backend.blockchain().last_finalized()?; - - if block == last_finalized { + if hash == info.finalized_hash { warn!( "Possible safety violation: attempted to re-finalize last finalized block {:?} ", - last_finalized + hash, ); return Ok(()) } + // Find tree route from last finalized to given block. let route_from_finalized = - sp_blockchain::tree_route(self.backend.blockchain(), last_finalized, block)?; + sp_blockchain::tree_route(self.backend.blockchain(), info.finalized_hash, hash)?; if let Some(retracted) = route_from_finalized.retracted().get(0) { warn!( "Safety violation: attempted to revert finalized block {:?} which is not in the \ same chain as last finalized {:?}", - retracted, last_finalized + retracted, info.finalized_hash ); return Err(sp_blockchain::Error::NotInFinalizedChain) } - // If there is only one leaf, best block is guaranteed to be - // a descendant of the new finalized block. If not, - // we need to check. - if self.backend.blockchain().leaves()?.len() > 1 { + // We may need to coercively update the best block if there is more than one + // leaf or if the finalized block number is greater than last best number recorded + // by the backend. This last condition may apply in case of consensus implementations + // not always checking this condition. + let block_number = self + .backend + .blockchain() + .number(hash)? + .ok_or(Error::MissingHeader(format!("{hash:?}")))?; + if self.backend.blockchain().leaves()?.len() > 1 || info.best_number < block_number { let route_from_best = - sp_blockchain::tree_route(self.backend.blockchain(), best_block, block)?; + sp_blockchain::tree_route(self.backend.blockchain(), info.best_hash, hash)?; - // if the block is not a direct ancestor of the current best chain, + // If the block is not a direct ancestor of the current best chain, // then some other block is the common ancestor. - if route_from_best.common_block().hash != block { + if route_from_best.common_block().hash != hash { // NOTE: we're setting the finalized block as best block, this might // be slightly inaccurate since we might have a "better" block // further along this chain, but since best chain selection logic is // plugable we cannot make a better choice here. usages that need // an accurate "best" block need to go through `SelectChain` // instead. - operation.op.mark_head(block)?; + operation.op.mark_head(hash)?; } } @@ -962,8 +966,8 @@ where operation.op.mark_finalized(finalize_new.hash, None)?; } - assert_eq!(enacted.last().map(|e| e.hash), Some(block)); - operation.op.mark_finalized(block, justification)?; + assert_eq!(enacted.last().map(|e| e.hash), Some(hash)); + operation.op.mark_finalized(hash, justification)?; if notify { let finalized = @@ -985,7 +989,7 @@ where let header = self .backend .blockchain() - .header(block)? + .header(hash)? .expect("Block to finalize expected to be onchain; qed"); operation.notify_finalized = Some(FinalizeSummary { header, finalized, stale_heads }); @@ -1129,7 +1133,7 @@ where } /// Get blockchain info. - pub fn chain_info(&self) -> blockchain::Info<Block> { + pub fn chain_info(&self) -> BlockchainInfo<Block> { self.backend.blockchain().info() } @@ -1896,8 +1900,8 @@ where justification: Option<Justification>, notify: bool, ) -> sp_blockchain::Result<()> { - let last_best = self.backend.blockchain().info().best_hash; - self.apply_finality_with_block_hash(operation, hash, justification, last_best, notify) + let info = self.backend.blockchain().info(); + self.apply_finality_with_block_hash(operation, hash, justification, &info, notify) } fn finalize_block( diff --git a/substrate/client/service/test/src/client/mod.rs b/substrate/client/service/test/src/client/mod.rs index e71e211767f..9c490a47a3b 100644 --- a/substrate/client/service/test/src/client/mod.rs +++ b/substrate/client/service/test/src/client/mod.rs @@ -2001,3 +2001,41 @@ fn use_dalek_ext_works() { .verify_ed25519(a1.hash(), zero_ed_sig(), zero_ed_pub(), vec![]) .unwrap()); } + +#[test] +fn finalize_after_best_block_updates_best() { + let mut client = substrate_test_runtime_client::new(); + + // G -> A1 + let a1 = client.new_block(Default::default()).unwrap().build().unwrap().block; + block_on(client.import(BlockOrigin::Own, a1.clone())).unwrap(); + + // A1 -> A2 + let a2 = client + .new_block_at(a1.hash(), Default::default(), false) + .unwrap() + .build() + .unwrap() + .block; + block_on(client.import(BlockOrigin::Own, a2.clone())).unwrap(); + + // A2 -> A3 + let a3 = client + .new_block_at(a2.hash(), Default::default(), false) + .unwrap() + .build() + .unwrap() + .block; + let (header, extrinsics) = a3.clone().deconstruct(); + let mut import_params = BlockImportParams::new(BlockOrigin::Own, header); + import_params.body = Some(extrinsics); + import_params.fork_choice = Some(ForkChoiceStrategy::Custom(false)); + block_on(client.import_block(import_params)).unwrap(); + + assert_eq!(client.chain_info().best_hash, a2.hash()); + + client.finalize_block(a3.hash(), None).unwrap(); + + assert_eq!(client.chain_info().finalized_hash, a3.hash()); + assert_eq!(client.chain_info().best_hash, a3.hash()); +} -- GitLab