From f74c2676dafb41687f5dded5a3b4595da0d95606 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Silva?= <andre.beat@gmail.com>
Date: Tue, 16 Jul 2019 00:50:22 +0100
Subject: [PATCH] client: Reorg to new best when finalizing divergent branch
 (#2869)

* client: add tests for reorging on diverged finality

* client: mark finalized block as best if diverged from current best chain

* client: update meta on set_head

* core: add docs about SelectChain to finalize_block

* client: improve finality reorg test

* client: LongestChain doesn't return client best block

* client: LongestChain searches canonical chain
---
 substrate/core/client/db/src/lib.rs   |   1 +
 substrate/core/client/db/src/light.rs |   1 +
 substrate/core/client/src/client.rs   | 168 +++++++++++++++++++++++---
 3 files changed, 155 insertions(+), 15 deletions(-)

diff --git a/substrate/core/client/db/src/lib.rs b/substrate/core/client/db/src/lib.rs
index 840a62dcd2d..eebd69d88dd 100644
--- a/substrate/core/client/db/src/lib.rs
+++ b/substrate/core/client/db/src/lib.rs
@@ -1118,6 +1118,7 @@ impl<Block: BlockT<Hash=H256>> Backend<Block> {
 					hash.clone(),
 					(number.clone(), hash.clone())
 				)?;
+				meta_updates.push((hash, *number, true, false));
 			} else {
 				return Err(client::error::Error::UnknownBlock(format!("Cannot set head {:?}", set_head)))
 			}
diff --git a/substrate/core/client/db/src/light.rs b/substrate/core/client/db/src/light.rs
index 5bbd3b64cf6..7d2f1e62d31 100644
--- a/substrate/core/client/db/src/light.rs
+++ b/substrate/core/client/db/src/light.rs
@@ -497,6 +497,7 @@ impl<Block> LightBlockchainStorage<Block> for LightStorage<Block>
 			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)?;
+			self.update_meta(hash, header.number().clone(), true, false);
 			Ok(())
 		} else {
 			Err(ClientError::UnknownBlock(format!("Cannot set head {:?}", id)))
diff --git a/substrate/core/client/src/client.rs b/substrate/core/client/src/client.rs
index f2802e75fcf..5122cbac032 100644
--- a/substrate/core/client/src/client.rs
+++ b/substrate/core/client/src/client.rs
@@ -1075,8 +1075,13 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
 		// 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 {
-			// FIXME: #1442 reorganize best block to be the best chain containing
-			// `block`.
+			// NOTE: we're setting the finalized block as best block, this might
+			// be slightly innacurate since we might have a "better" block
+			// further along this chain, but since best chain selection logic is
+			// pluggable we cannot make a better choice here. usages that need
+			// an accurate "best" block need to go through `SelectChain`
+			// instead.
+			operation.op.mark_head(BlockId::Hash(block))?;
 		}
 
 		let enacted = route_from_finalized.enacted();
@@ -1188,6 +1193,11 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
 	/// Mark all blocks up to given as finalized in operation. If a
 	/// justification is provided it is stored with the given finalized
 	/// block (any other finalized blocks are left unjustified).
+	///
+	/// If the block being finalized is on a different fork from the current
+	/// best block the finalized block is set as best, this might be slightly
+	/// innacurate (i.e. outdated), usages that require determining an accurate
+	/// best block should use `SelectChain` instead of the client.
 	pub fn apply_finality(
 		&self,
 		operation: &mut ClientImportOperation<Block, Blake2Hasher, B>,
@@ -1203,6 +1213,11 @@ impl<B, E, Block, RA> Client<B, E, Block, RA> where
 	/// Finalize a block. This will implicitly finalize all blocks up to it and
 	/// fire finality notifications.
 	///
+	/// If the block being finalized is on a different fork from the current
+	/// best block the finalized block is set as best, this might be slightly
+	/// innacurate (i.e. outdated), usages that require determining an accurate
+	/// best block should use `SelectChain` instead of the client.
+	///
 	/// Pass a flag to indicate whether finality notifications should be propagated.
 	/// This is usually tied to some synchronization state, where we don't send notifications
 	/// while performing major synchronization work.
@@ -1589,9 +1604,12 @@ where
 	}
 
 	fn best_block_header(&self) -> error::Result<<Block as BlockT>::Header> {
-		let info : ChainInfo<Block> = self.backend.blockchain().info();
-		Ok(self.backend.blockchain().header(BlockId::Hash(info.best_hash))?
-			.expect("Best block header must always exist"))
+		let info = self.backend.blockchain().info();
+		let best_hash = self.best_containing(info.best_hash, None)?
+			.unwrap_or(info.best_hash);
+
+		Ok(self.backend.blockchain().header(BlockId::Hash(best_hash))?
+			.expect("given block hash was fetched from block in db; qed"))
 	}
 
 	/// Get the most recent block hash of the best (longest) chains
@@ -1625,7 +1643,7 @@ where
 			}
 		}
 
-		let (leaves, best_already_checked) = {
+		let leaves = {
 			// ensure no blocks are imported during this code block.
 			// an import could trigger a reorg which could change the canonical chain.
 			// we depend on the canonical chain staying the same during this code block.
@@ -1637,29 +1655,24 @@ where
 				.ok_or_else(|| error::Error::from(format!("failed to get hash for block number {}", target_header.number())))?;
 
 			if canon_hash == target_hash {
-				// if no block at the given max depth exists fallback to the best block
+				// if a `max_number` is given we try to fetch the block at the
+				// given depth, if it doesn't exist or `max_number` is not
+				// provided, we continue to search from all leaves below.
 				if let Some(max_number) = maybe_max_number {
 					if let Some(header) = self.backend.blockchain().hash(max_number)? {
 						return Ok(Some(header));
 					}
 				}
-
-				return Ok(Some(info.best_hash));
 			} else if info.finalized_number >= *target_header.number() {
 				// header is on a dead fork.
 				return Ok(None);
 			}
 
-			(self.backend.blockchain().leaves()?, info.best_hash)
+			self.backend.blockchain().leaves()?
 		};
 
 		// for each chain. longest chain first. shortest last
 		for leaf_hash in leaves {
-			// ignore canonical chain which we already checked above
-			if leaf_hash == best_already_checked {
-				continue;
-			}
-
 			// start at the leaf
 			let mut current_hash = leaf_hash;
 
@@ -2495,4 +2508,129 @@ pub(crate) mod tests {
 			None,
 		);
 	}
+
+	#[test]
+	fn importing_diverged_finalized_block_should_trigger_reorg() {
+		use test_client::blockchain::HeaderBackend;
+
+		let client = test_client::new();
+
+		// G -> A1 -> A2
+		//   \
+		//    -> B1
+		let a1 = client.new_block_at(&BlockId::Number(0), Default::default()).unwrap().bake().unwrap();
+		client.import(BlockOrigin::Own, a1.clone()).unwrap();
+
+		let a2 = client.new_block_at(&BlockId::Hash(a1.hash()), Default::default()).unwrap().bake().unwrap();
+		client.import(BlockOrigin::Own, a2.clone()).unwrap();
+
+		let mut b1 = client.new_block_at(&BlockId::Number(0), Default::default()).unwrap();
+		// needed to make sure B1 gets a different hash from A1
+		b1.push_transfer(Transfer {
+			from: AccountKeyring::Alice.into(),
+			to: AccountKeyring::Ferdie.into(),
+			amount: 1,
+			nonce: 0,
+		}).unwrap();
+		// create but don't import B1 just yet
+		let b1 = b1.bake().unwrap();
+
+		#[allow(deprecated)]
+		let blockchain = client.backend().blockchain();
+
+		// A2 is the current best since it's the longest chain
+		assert_eq!(
+			blockchain.info().best_hash,
+			a2.hash(),
+		);
+
+		// importing B1 as finalized should trigger a re-org and set it as new best
+		let justification = vec![1, 2, 3];
+		client.import_justified(BlockOrigin::Own, b1.clone(), justification).unwrap();
+
+		assert_eq!(
+			blockchain.info().best_hash,
+			b1.hash(),
+		);
+
+		assert_eq!(
+			blockchain.info().finalized_hash,
+			b1.hash(),
+		);
+	}
+
+	#[test]
+	fn finalizing_diverged_block_should_trigger_reorg() {
+		use test_client::blockchain::HeaderBackend;
+
+		let (client, select_chain) = TestClientBuilder::new().build_with_longest_chain();
+
+		// G -> A1 -> A2
+		//   \
+		//    -> B1 -> B2
+		let a1 = client.new_block_at(&BlockId::Number(0), Default::default()).unwrap().bake().unwrap();
+		client.import(BlockOrigin::Own, a1.clone()).unwrap();
+
+		let a2 = client.new_block_at(&BlockId::Hash(a1.hash()), Default::default()).unwrap().bake().unwrap();
+		client.import(BlockOrigin::Own, a2.clone()).unwrap();
+
+		let mut b1 = client.new_block_at(&BlockId::Number(0), Default::default()).unwrap();
+		// needed to make sure B1 gets a different hash from A1
+		b1.push_transfer(Transfer {
+			from: AccountKeyring::Alice.into(),
+			to: AccountKeyring::Ferdie.into(),
+			amount: 1,
+			nonce: 0,
+		}).unwrap();
+		let b1 = b1.bake().unwrap();
+		client.import(BlockOrigin::Own, b1.clone()).unwrap();
+
+		let b2 = client.new_block_at(&BlockId::Hash(b1.hash()), Default::default()).unwrap().bake().unwrap();
+		client.import(BlockOrigin::Own, b2.clone()).unwrap();
+
+		#[allow(deprecated)]
+		let blockchain = client.backend().blockchain();
+
+		// A2 is the current best since it's the longest chain
+		assert_eq!(
+			blockchain.info().best_hash,
+			a2.hash(),
+		);
+
+		// we finalize block B1 which is on a different branch from current best
+		// which should trigger a re-org.
+		client.finalize_block(BlockId::Hash(b1.hash()), None, false).unwrap();
+
+		// B1 should now be the latest finalized
+		assert_eq!(
+			blockchain.info().finalized_hash,
+			b1.hash(),
+		);
+
+		// and B1 should be the new best block (`finalize_block` as no way of
+		// knowing about B2)
+		assert_eq!(
+			blockchain.info().best_hash,
+			b1.hash(),
+		);
+
+		// `SelectChain` should report B2 as best block though
+		assert_eq!(
+			select_chain.best_chain().unwrap().hash(),
+			b2.hash(),
+		);
+
+		// after we build B3 on top of B2 and import it
+		// it should be the new best block,
+		let b3 = client.new_block_at(
+			&BlockId::Hash(b2.hash()),
+			Default::default(),
+		).unwrap().bake().unwrap();
+		client.import(BlockOrigin::Own, b3.clone()).unwrap();
+
+		assert_eq!(
+			blockchain.info().best_hash,
+			b3.hash(),
+		);
+	}
 }
-- 
GitLab