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>
This commit is contained in:
Davide Galassi
2023-06-07 11:17:45 +02:00
committed by GitHub
parent 6f79a9e941
commit b14238cb7d
2 changed files with 67 additions and 25 deletions
+29 -25
View File
@@ -52,7 +52,7 @@ use sp_api::{
}; };
use sp_blockchain::{ use sp_blockchain::{
self as blockchain, Backend as ChainBackend, CachedHeaderMetadata, Error, 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}; use sp_consensus::{BlockOrigin, BlockStatus, Error as ConsensusError};
@@ -717,7 +717,7 @@ where
operation, operation,
parent_hash, parent_hash,
None, None,
info.best_hash, &info,
make_notifications, make_notifications,
)?; )?;
} }
@@ -907,52 +907,56 @@ where
fn apply_finality_with_block_hash( fn apply_finality_with_block_hash(
&self, &self,
operation: &mut ClientImportOperation<Block, B>, operation: &mut ClientImportOperation<Block, B>,
block: Block::Hash, hash: Block::Hash,
justification: Option<Justification>, justification: Option<Justification>,
best_block: Block::Hash, info: &BlockchainInfo<Block>,
notify: bool, notify: bool,
) -> sp_blockchain::Result<()> { ) -> sp_blockchain::Result<()> {
// find tree route from last finalized to given block. if hash == info.finalized_hash {
let last_finalized = self.backend.blockchain().last_finalized()?;
if block == last_finalized {
warn!( warn!(
"Possible safety violation: attempted to re-finalize last finalized block {:?} ", "Possible safety violation: attempted to re-finalize last finalized block {:?} ",
last_finalized hash,
); );
return Ok(()) return Ok(())
} }
// Find tree route from last finalized to given block.
let route_from_finalized = 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) { if let Some(retracted) = route_from_finalized.retracted().get(0) {
warn!( warn!(
"Safety violation: attempted to revert finalized block {:?} which is not in the \ "Safety violation: attempted to revert finalized block {:?} which is not in the \
same chain as last finalized {:?}", same chain as last finalized {:?}",
retracted, last_finalized retracted, info.finalized_hash
); );
return Err(sp_blockchain::Error::NotInFinalizedChain) return Err(sp_blockchain::Error::NotInFinalizedChain)
} }
// If there is only one leaf, best block is guaranteed to be // We may need to coercively update the best block if there is more than one
// a descendant of the new finalized block. If not, // leaf or if the finalized block number is greater than last best number recorded
// we need to check. // by the backend. This last condition may apply in case of consensus implementations
if self.backend.blockchain().leaves()?.len() > 1 { // 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 = 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. // 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 // NOTE: we're setting the finalized block as best block, this might
// be slightly inaccurate since we might have a "better" block // be slightly inaccurate since we might have a "better" block
// further along this chain, but since best chain selection logic is // further along this chain, but since best chain selection logic is
// plugable we cannot make a better choice here. usages that need // plugable we cannot make a better choice here. usages that need
// an accurate "best" block need to go through `SelectChain` // an accurate "best" block need to go through `SelectChain`
// instead. // instead.
operation.op.mark_head(block)?; operation.op.mark_head(hash)?;
} }
} }
@@ -962,8 +966,8 @@ where
operation.op.mark_finalized(finalize_new.hash, None)?; operation.op.mark_finalized(finalize_new.hash, None)?;
} }
assert_eq!(enacted.last().map(|e| e.hash), Some(block)); assert_eq!(enacted.last().map(|e| e.hash), Some(hash));
operation.op.mark_finalized(block, justification)?; operation.op.mark_finalized(hash, justification)?;
if notify { if notify {
let finalized = let finalized =
@@ -985,7 +989,7 @@ where
let header = self let header = self
.backend .backend
.blockchain() .blockchain()
.header(block)? .header(hash)?
.expect("Block to finalize expected to be onchain; qed"); .expect("Block to finalize expected to be onchain; qed");
operation.notify_finalized = Some(FinalizeSummary { header, finalized, stale_heads }); operation.notify_finalized = Some(FinalizeSummary { header, finalized, stale_heads });
@@ -1129,7 +1133,7 @@ where
} }
/// Get blockchain info. /// Get blockchain info.
pub fn chain_info(&self) -> blockchain::Info<Block> { pub fn chain_info(&self) -> BlockchainInfo<Block> {
self.backend.blockchain().info() self.backend.blockchain().info()
} }
@@ -1896,8 +1900,8 @@ where
justification: Option<Justification>, justification: Option<Justification>,
notify: bool, notify: bool,
) -> sp_blockchain::Result<()> { ) -> sp_blockchain::Result<()> {
let last_best = self.backend.blockchain().info().best_hash; let info = self.backend.blockchain().info();
self.apply_finality_with_block_hash(operation, hash, justification, last_best, notify) self.apply_finality_with_block_hash(operation, hash, justification, &info, notify)
} }
fn finalize_block( fn finalize_block(
@@ -2001,3 +2001,41 @@ fn use_dalek_ext_works() {
.verify_ed25519(a1.hash(), zero_ed_sig(), zero_ed_pub(), vec![]) .verify_ed25519(a1.hash(), zero_ed_sig(), zero_ed_pub(), vec![])
.unwrap()); .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());
}