Skip to content

Commit

Permalink
Finalized should be before best (paritytech#14308)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
davxy and koute authored Jun 7, 2023
1 parent 55bb629 commit bc4055b
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 25 deletions.
54 changes: 29 additions & 25 deletions client/service/src/client/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -717,7 +717,7 @@ where
operation,
parent_hash,
None,
info.best_hash,
&info,
make_notifications,
)?;
}
Expand Down Expand Up @@ -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)?;
}
}

Expand All @@ -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 =
Expand All @@ -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 });
Expand Down Expand Up @@ -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()
}

Expand Down Expand Up @@ -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(
Expand Down
38 changes: 38 additions & 0 deletions client/service/test/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}

0 comments on commit bc4055b

Please sign in to comment.