From f240e02557d47756542b9b8cd876800b4b64c6d5 Mon Sep 17 00:00:00 2001
From: Marcin S <marcin@realemail.net>
Date: Tue, 5 Dec 2023 12:15:38 +0100
Subject: [PATCH] statement-distribution: Add tests for incoming
 acknowledgements (#2498)

---
 Cargo.lock                                    |   1 -
 .../network/statement-distribution/Cargo.toml |   1 -
 .../network/statement-distribution/src/lib.rs |   2 +-
 .../src/v2/tests/grid.rs                      | 600 +++++++++++++-----
 .../src/v2/tests/mod.rs                       | 150 +++++
 5 files changed, 594 insertions(+), 160 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index be4db2ca626..e9c334efff0 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -13155,7 +13155,6 @@ dependencies = [
  "polkadot-node-primitives",
  "polkadot-node-subsystem",
  "polkadot-node-subsystem-test-helpers",
- "polkadot-node-subsystem-types",
  "polkadot-node-subsystem-util",
  "polkadot-primitives",
  "polkadot-primitives-test-helpers",
diff --git a/polkadot/node/network/statement-distribution/Cargo.toml b/polkadot/node/network/statement-distribution/Cargo.toml
index bf516e7b7ba..e251abc445d 100644
--- a/polkadot/node/network/statement-distribution/Cargo.toml
+++ b/polkadot/node/network/statement-distribution/Cargo.toml
@@ -16,7 +16,6 @@ sp-keystore = { path = "../../../../substrate/primitives/keystore" }
 polkadot-node-subsystem = { path = "../../subsystem" }
 polkadot-node-primitives = { path = "../../primitives" }
 polkadot-node-subsystem-util = { path = "../../subsystem-util" }
-polkadot-node-subsystem-types = { path = "../../subsystem-types" }
 polkadot-node-network-protocol = { path = "../protocol" }
 arrayvec = "0.7.4"
 indexmap = "1.9.1"
diff --git a/polkadot/node/network/statement-distribution/src/lib.rs b/polkadot/node/network/statement-distribution/src/lib.rs
index 0a80c1491a9..ef1fc7cd78b 100644
--- a/polkadot/node/network/statement-distribution/src/lib.rs
+++ b/polkadot/node/network/statement-distribution/src/lib.rs
@@ -19,7 +19,7 @@
 //! This is responsible for distributing signed statements about candidate
 //! validity among validators.
 
-// #![deny(unused_crate_dependencies)]
+#![deny(unused_crate_dependencies)]
 #![warn(missing_docs)]
 
 use error::{log_error, FatalResult};
diff --git a/polkadot/node/network/statement-distribution/src/v2/tests/grid.rs b/polkadot/node/network/statement-distribution/src/v2/tests/grid.rs
index 9802db06082..116116659cb 100644
--- a/polkadot/node/network/statement-distribution/src/v2/tests/grid.rs
+++ b/polkadot/node/network/statement-distribution/src/v2/tests/grid.rs
@@ -44,6 +44,8 @@ fn backed_candidate_leads_to_advertisement() {
 		let local_group_index = local_validator.group_index.unwrap();
 		let local_para = ParaId::from(local_group_index.0);
 
+		let other_group = next_group_index(local_group_index, validator_count, group_size);
+
 		let test_leaf = state.make_dummy_leaf(relay_parent);
 
 		let (candidate, pvd) = make_candidate(
@@ -56,13 +58,12 @@ fn backed_candidate_leads_to_advertisement() {
 		);
 		let candidate_hash = candidate.hash();
 
-		let other_group_validators = state.group_validators(local_group_index, true);
-		let target_group_validators =
-			state.group_validators((local_group_index.0 + 1).into(), true);
-		let v_a = other_group_validators[0];
-		let v_b = other_group_validators[1];
-		let v_c = target_group_validators[0];
-		let v_d = target_group_validators[1];
+		let local_group_validators = state.group_validators(local_group_index, true);
+		let other_group_validators = state.group_validators(other_group, true);
+		let v_a = local_group_validators[0];
+		let v_b = local_group_validators[1];
+		let v_c = other_group_validators[0];
+		let v_d = other_group_validators[1];
 
 		// peer A is in group, has relay parent in view.
 		// peer B is in group, has no relay parent in view.
@@ -274,12 +275,12 @@ fn received_advertisement_before_confirmation_leads_to_request() {
 		);
 		let candidate_hash = candidate.hash();
 
-		let other_group_validators = state.group_validators(local_group_index, true);
-		let target_group_validators = state.group_validators(other_group, true);
-		let v_a = other_group_validators[0];
-		let v_b = other_group_validators[1];
-		let v_c = target_group_validators[0];
-		let v_d = target_group_validators[1];
+		let local_group_validators = state.group_validators(local_group_index, true);
+		let other_group_validators = state.group_validators(other_group, true);
+		let v_a = local_group_validators[0];
+		let v_b = local_group_validators[1];
+		let v_c = other_group_validators[0];
+		let v_d = other_group_validators[1];
 
 		// peer A is in group, has relay parent in view.
 		// peer B is in group, has no relay parent in view.
@@ -429,19 +430,33 @@ fn received_advertisement_after_backing_leads_to_acknowledgement() {
 		async_backing_params: None,
 	};
 
-	let relay_parent = Hash::repeat_byte(1);
-	let peer_c = PeerId::random();
-	let peer_d = PeerId::random();
-	let peer_e = PeerId::random();
-
 	test_harness(config, |state, mut overseer| async move {
-		let local_validator = state.local.clone().unwrap();
-		let local_group_index = local_validator.group_index.unwrap();
-
-		let other_group = next_group_index(local_group_index, validator_count, group_size);
-		let other_para = ParaId::from(other_group.0);
+		let peers_to_connect = [
+			TestPeerToConnect { local: true, relay_parent_in_view: false },
+			TestPeerToConnect { local: true, relay_parent_in_view: false },
+			TestPeerToConnect { local: false, relay_parent_in_view: true },
+			TestPeerToConnect { local: false, relay_parent_in_view: true },
+			TestPeerToConnect { local: false, relay_parent_in_view: true },
+		];
 
-		let test_leaf = state.make_dummy_leaf(relay_parent);
+		let TestSetupInfo {
+			other_group,
+			other_para,
+			relay_parent,
+			test_leaf,
+			peers,
+			validators,
+			..
+		} = setup_test_and_connect_peers(
+			&state,
+			&mut overseer,
+			validator_count,
+			group_size,
+			&peers_to_connect,
+		)
+		.await;
+		let [_, _, peer_c, peer_d, _] = peers[..] else { panic!() };
+		let [_, _, v_c, v_d, v_e] = validators[..] else { panic!() };
 
 		let (candidate, pvd) = make_candidate(
 			relay_parent,
@@ -453,52 +468,6 @@ fn received_advertisement_after_backing_leads_to_acknowledgement() {
 		);
 		let candidate_hash = candidate.hash();
 
-		let target_group_validators = state.group_validators(other_group, true);
-		let v_c = target_group_validators[0];
-		let v_d = target_group_validators[1];
-		let v_e = target_group_validators[2];
-
-		// Connect C, D, E
-		{
-			connect_peer(
-				&mut overseer,
-				peer_c.clone(),
-				Some(vec![state.discovery_id(v_c)].into_iter().collect()),
-			)
-			.await;
-
-			connect_peer(
-				&mut overseer,
-				peer_d.clone(),
-				Some(vec![state.discovery_id(v_d)].into_iter().collect()),
-			)
-			.await;
-
-			connect_peer(
-				&mut overseer,
-				peer_e.clone(),
-				Some(vec![state.discovery_id(v_e)].into_iter().collect()),
-			)
-			.await;
-
-			send_peer_view_change(&mut overseer, peer_c.clone(), view![relay_parent]).await;
-			send_peer_view_change(&mut overseer, peer_d.clone(), view![relay_parent]).await;
-			send_peer_view_change(&mut overseer, peer_e.clone(), view![relay_parent]).await;
-		}
-
-		activate_leaf(&mut overseer, &test_leaf, &state, true).await;
-
-		answer_expected_hypothetical_depth_request(
-			&mut overseer,
-			vec![],
-			Some(relay_parent),
-			false,
-		)
-		.await;
-
-		// Send gossip topology.
-		send_new_topology(&mut overseer, state.make_dummy_topology()).await;
-
 		let manifest = BackedCandidateManifest {
 			relay_parent,
 			candidate_hash,
@@ -530,14 +499,7 @@ fn received_advertisement_after_backing_leads_to_acknowledgement() {
 
 		// Receive an advertisement from C.
 		{
-			send_peer_message(
-				&mut overseer,
-				peer_c.clone(),
-				protocol_v2::StatementDistributionMessage::BackedCandidateManifest(
-					manifest.clone(),
-				),
-			)
-			.await;
+			send_manifest_from_peer(&mut overseer, peer_c, manifest.clone()).await;
 
 			// Should send a request to C.
 			let statements = vec![
@@ -563,37 +525,16 @@ fn received_advertisement_after_backing_leads_to_acknowledgement() {
 			)
 			.await;
 
-			assert_matches!(
-				overseer.recv().await,
-				AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
-					if p == peer_c && r == BENEFIT_VALID_STATEMENT.into()
-			);
-			assert_matches!(
-				overseer.recv().await,
-				AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
-					if p == peer_c && r == BENEFIT_VALID_STATEMENT.into()
-			);
-			assert_matches!(
-				overseer.recv().await,
-				AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
-					if p == peer_c && r == BENEFIT_VALID_STATEMENT.into()
-			);
-
-			assert_matches!(
-				overseer.recv().await,
-				AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
-					if p == peer_c && r == BENEFIT_VALID_RESPONSE.into()
-			);
+			assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_STATEMENT);
+			assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_STATEMENT);
+			assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_STATEMENT);
+			assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_RESPONSE);
 
 			answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await;
 		}
 
 		// Receive Backed message.
-		overseer
-			.send(FromOrchestra::Communication {
-				msg: StatementDistributionMessage::Backed(candidate_hash),
-			})
-			.await;
+		send_backed_message(&mut overseer, candidate_hash).await;
 
 		// Should send an acknowledgement back to C.
 		{
@@ -625,14 +566,7 @@ fn received_advertisement_after_backing_leads_to_acknowledgement() {
 
 		// Receive a manifest about the same candidate from peer D.
 		{
-			send_peer_message(
-				&mut overseer,
-				peer_d.clone(),
-				protocol_v2::StatementDistributionMessage::BackedCandidateManifest(
-					manifest.clone(),
-				),
-			)
-			.await;
+			send_manifest_from_peer(&mut overseer, peer_d, manifest.clone()).await;
 
 			let expected_ack = BackedCandidateAcknowledgement {
 				candidate_hash,
@@ -665,6 +599,360 @@ fn received_advertisement_after_backing_leads_to_acknowledgement() {
 	});
 }
 
+#[test]
+fn receive_ack_for_unconfirmed_candidate() {
+	let validator_count = 6;
+	let group_size = 3;
+	let config = TestConfig {
+		validator_count,
+		group_size,
+		local_validator: LocalRole::Validator,
+		async_backing_params: None,
+	};
+
+	test_harness(config, |state, mut overseer| async move {
+		let peers_to_connect = [
+			TestPeerToConnect { local: true, relay_parent_in_view: true },
+			TestPeerToConnect { local: true, relay_parent_in_view: false },
+			TestPeerToConnect { local: false, relay_parent_in_view: true },
+			TestPeerToConnect { local: false, relay_parent_in_view: false },
+		];
+		let TestSetupInfo { local_para, relay_parent, test_leaf, peers, .. } =
+			setup_test_and_connect_peers(
+				&state,
+				&mut overseer,
+				validator_count,
+				group_size,
+				&peers_to_connect,
+			)
+			.await;
+		let [_, _, peer_c, _] = peers[..] else { panic!() };
+
+		let (candidate, _pvd) = make_candidate(
+			relay_parent,
+			1,
+			local_para,
+			test_leaf.para_data(local_para).head_data.clone(),
+			vec![4, 5, 6].into(),
+			Hash::repeat_byte(42).into(),
+		);
+		let candidate_hash = candidate.hash();
+
+		let ack = BackedCandidateAcknowledgement {
+			candidate_hash,
+			statement_knowledge: StatementFilter {
+				seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1],
+				validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
+			},
+		};
+
+		// Receive an acknowledgement from a peer before the candidate is confirmed.
+		send_ack_from_peer(&mut overseer, peer_c, ack.clone()).await;
+		assert_peer_reported!(
+			&mut overseer,
+			peer_c,
+			COST_UNEXPECTED_ACKNOWLEDGEMENT_UNKNOWN_CANDIDATE,
+		);
+
+		overseer
+	});
+}
+
+// Test receiving unexpected and expected acknowledgements for a locally confirmed candidate.
+#[test]
+fn received_acknowledgements_for_locally_confirmed() {
+	let validator_count = 6;
+	let group_size = 3;
+	let config = TestConfig {
+		validator_count,
+		group_size,
+		local_validator: LocalRole::Validator,
+		async_backing_params: None,
+	};
+
+	test_harness(config, |state, mut overseer| async move {
+		let peers_to_connect = [
+			TestPeerToConnect { local: true, relay_parent_in_view: true },
+			TestPeerToConnect { local: true, relay_parent_in_view: false },
+			TestPeerToConnect { local: false, relay_parent_in_view: true },
+			TestPeerToConnect { local: false, relay_parent_in_view: false },
+		];
+		let TestSetupInfo {
+			local_validator,
+			local_group,
+			local_para,
+			relay_parent,
+			test_leaf,
+			peers,
+			validators,
+			..
+		} = setup_test_and_connect_peers(
+			&state,
+			&mut overseer,
+			validator_count,
+			group_size,
+			&peers_to_connect,
+		)
+		.await;
+		let [peer_a, peer_b, peer_c, peer_d] = peers[..] else { panic!() };
+		let [_, v_b, _, _] = validators[..] else { panic!() };
+
+		let (candidate, pvd) = make_candidate(
+			relay_parent,
+			1,
+			local_para,
+			test_leaf.para_data(local_para).head_data.clone(),
+			vec![4, 5, 6].into(),
+			Hash::repeat_byte(42).into(),
+		);
+		let candidate_hash = candidate.hash();
+
+		let ack = BackedCandidateAcknowledgement {
+			candidate_hash,
+			statement_knowledge: StatementFilter {
+				seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1],
+				validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
+			},
+		};
+
+		// Confirm the candidate locally so that we don't send out requests.
+		{
+			let statement = state
+				.sign_full_statement(
+					local_validator.validator_index,
+					Statement::Seconded(candidate.clone()),
+					&SigningContext { parent_hash: relay_parent, session_index: 1 },
+					pvd.clone(),
+				)
+				.clone();
+
+			send_share_message(&mut overseer, relay_parent, statement).await;
+
+			assert_matches!(
+				overseer.recv().await,
+				AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(peers, _)) if peers == vec![peer_a]
+			);
+
+			answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await;
+		}
+
+		// Receive an unexpected acknowledgement from peer D.
+		send_ack_from_peer(&mut overseer, peer_d, ack.clone()).await;
+		assert_peer_reported!(&mut overseer, peer_d, COST_UNEXPECTED_MANIFEST_DISALLOWED);
+
+		// Send statement from peer B.
+		{
+			let statement = state
+				.sign_statement(
+					v_b,
+					CompactStatement::Seconded(candidate_hash),
+					&SigningContext { parent_hash: relay_parent, session_index: 1 },
+				)
+				.as_unchecked()
+				.clone();
+
+			send_peer_message(
+				&mut overseer,
+				peer_b.clone(),
+				protocol_v2::StatementDistributionMessage::Statement(relay_parent, statement),
+			)
+			.await;
+
+			assert_peer_reported!(&mut overseer, peer_b, BENEFIT_VALID_STATEMENT_FIRST);
+
+			assert_matches!(
+				overseer.recv().await,
+				AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::SendValidationMessage(peers, _)) if peers == vec![peer_a]
+			);
+		}
+
+		// Send Backed notification.
+		{
+			send_backed_message(&mut overseer, candidate_hash).await;
+
+			// We should send out a manifest.
+			assert_matches!(
+				overseer.recv().await,
+				AllMessages:: NetworkBridgeTx(
+					NetworkBridgeTxMessage::SendValidationMessage(
+						peers,
+						Versioned::V2(
+							protocol_v2::ValidationProtocol::StatementDistribution(
+								protocol_v2::StatementDistributionMessage::BackedCandidateManifest(manifest),
+							),
+						),
+					)
+				) => {
+					assert_eq!(peers, vec![peer_c]);
+					assert_eq!(manifest, BackedCandidateManifest {
+						relay_parent,
+						candidate_hash,
+						group_index: local_group,
+						para_id: local_para,
+						parent_head_data_hash: pvd.parent_head.hash(),
+						statement_knowledge: StatementFilter {
+							seconded_in_group: bitvec::bitvec![u8, Lsb0; 0, 1, 1],
+							validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
+						},
+					});
+				}
+			);
+
+			answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await;
+		}
+
+		// Receive an unexpected acknowledgement from peer D.
+		//
+		// It still shouldn't know this manifest.
+		send_ack_from_peer(&mut overseer, peer_d, ack.clone()).await;
+		assert_peer_reported!(&mut overseer, peer_d, COST_UNEXPECTED_MANIFEST_DISALLOWED);
+
+		// Receive an acknowledgement from peer C.
+		//
+		// It's OK, we know they know it because we sent them a manifest.
+		send_ack_from_peer(&mut overseer, peer_c, ack.clone()).await;
+
+		// What happens if we get another valid ack?
+		send_ack_from_peer(&mut overseer, peer_c, ack.clone()).await;
+
+		overseer
+	});
+}
+
+// Test receiving unexpected acknowledgements for a candidate confirmed in a different group.
+#[test]
+fn received_acknowledgements_for_externally_confirmed() {
+	let validator_count = 6;
+	let group_size = 3;
+	let config = TestConfig {
+		validator_count,
+		group_size,
+		local_validator: LocalRole::Validator,
+		async_backing_params: None,
+	};
+
+	test_harness(config, |state, mut overseer| async move {
+		let peers_to_connect = [
+			TestPeerToConnect { local: true, relay_parent_in_view: true },
+			TestPeerToConnect { local: true, relay_parent_in_view: false },
+			TestPeerToConnect { local: false, relay_parent_in_view: true },
+			TestPeerToConnect { local: false, relay_parent_in_view: true },
+			TestPeerToConnect { local: false, relay_parent_in_view: true },
+		];
+		let TestSetupInfo {
+			other_group,
+			other_para,
+			relay_parent,
+			test_leaf,
+			peers,
+			validators,
+			..
+		} = setup_test_and_connect_peers(
+			&state,
+			&mut overseer,
+			validator_count,
+			group_size,
+			&peers_to_connect,
+		)
+		.await;
+		let [peer_a, _, peer_c, peer_d, _] = peers[..] else { panic!() };
+		let [_, _, v_c, v_d, v_e] = validators[..] else { panic!() };
+
+		let (candidate, pvd) = make_candidate(
+			relay_parent,
+			1,
+			other_para,
+			test_leaf.para_data(other_para).head_data.clone(),
+			vec![4, 5, 6].into(),
+			Hash::repeat_byte(42).into(),
+		);
+		let candidate_hash = candidate.hash();
+
+		let manifest = BackedCandidateManifest {
+			relay_parent,
+			candidate_hash,
+			group_index: other_group,
+			para_id: other_para,
+			parent_head_data_hash: pvd.parent_head.hash(),
+			statement_knowledge: StatementFilter {
+				seconded_in_group: bitvec::bitvec![u8, Lsb0; 0, 1, 1],
+				validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
+			},
+		};
+
+		let statement_c = state
+			.sign_statement(
+				v_c,
+				CompactStatement::Seconded(candidate_hash),
+				&SigningContext { parent_hash: relay_parent, session_index: 1 },
+			)
+			.as_unchecked()
+			.clone();
+		let statement_d = state
+			.sign_statement(
+				v_d,
+				CompactStatement::Seconded(candidate_hash),
+				&SigningContext { parent_hash: relay_parent, session_index: 1 },
+			)
+			.as_unchecked()
+			.clone();
+
+		// Receive an advertisement from C, confirming the candidate.
+		{
+			send_manifest_from_peer(&mut overseer, peer_c, manifest.clone()).await;
+
+			// Should send a request to C.
+			let statements = vec![
+				statement_c.clone(),
+				statement_d.clone(),
+				state
+					.sign_statement(
+						v_e,
+						CompactStatement::Seconded(candidate_hash),
+						&SigningContext { parent_hash: relay_parent, session_index: 1 },
+					)
+					.as_unchecked()
+					.clone(),
+			];
+			handle_sent_request(
+				&mut overseer,
+				peer_c,
+				candidate_hash,
+				StatementFilter::blank(group_size),
+				candidate.clone(),
+				pvd.clone(),
+				statements,
+			)
+			.await;
+
+			assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_STATEMENT);
+			assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_STATEMENT);
+			assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_STATEMENT);
+			assert_peer_reported!(&mut overseer, peer_c, BENEFIT_VALID_RESPONSE);
+
+			answer_expected_hypothetical_depth_request(&mut overseer, vec![], None, false).await;
+		}
+
+		let ack = BackedCandidateAcknowledgement {
+			candidate_hash,
+			statement_knowledge: StatementFilter {
+				seconded_in_group: bitvec::bitvec![u8, Lsb0; 1, 1, 1],
+				validated_in_group: bitvec::bitvec![u8, Lsb0; 0, 0, 0],
+			},
+		};
+
+		// Receive an unexpected acknowledgement from peer D.
+		send_ack_from_peer(&mut overseer, peer_d, ack.clone()).await;
+		assert_peer_reported!(&mut overseer, peer_d, COST_UNEXPECTED_MANIFEST_PEER_UNKNOWN);
+
+		// Receive an unexpected acknowledgement from peer A.
+		send_ack_from_peer(&mut overseer, peer_a, ack.clone()).await;
+		assert_peer_reported!(&mut overseer, peer_a, COST_UNEXPECTED_MANIFEST_DISALLOWED);
+
+		overseer
+	});
+}
+
 // Received advertisement after confirmation but before backing leads to nothing.
 #[test]
 fn received_advertisement_after_confirmation_before_backing() {
@@ -701,10 +989,10 @@ fn received_advertisement_after_confirmation_before_backing() {
 		);
 		let candidate_hash = candidate.hash();
 
-		let target_group_validators = state.group_validators(other_group, true);
-		let v_c = target_group_validators[0];
-		let v_d = target_group_validators[1];
-		let v_e = target_group_validators[2];
+		let other_group_validators = state.group_validators(other_group, true);
+		let v_c = other_group_validators[0];
+		let v_d = other_group_validators[1];
+		let v_e = other_group_validators[2];
 
 		// Connect C, D, E
 		{
@@ -887,10 +1175,10 @@ fn additional_statements_are_shared_after_manifest_exchange() {
 		);
 		let candidate_hash = candidate.hash();
 
-		let target_group_validators = state.group_validators(other_group, true);
-		let v_c = target_group_validators[0];
-		let v_d = target_group_validators[1];
-		let v_e = target_group_validators[2];
+		let other_group_validators = state.group_validators(other_group, true);
+		let v_c = other_group_validators[0];
+		let v_d = other_group_validators[1];
+		let v_e = other_group_validators[2];
 
 		// Connect C, D, E
 		{
@@ -1183,13 +1471,12 @@ fn advertisement_sent_when_peer_enters_relay_parent_view() {
 		);
 		let candidate_hash = candidate.hash();
 
-		let other_group_validators = state.group_validators(local_group_index, true);
-		let target_group_validators =
-			state.group_validators((local_group_index.0 + 1).into(), true);
-		let v_a = other_group_validators[0];
-		let v_b = other_group_validators[1];
-		let v_c = target_group_validators[0];
-		let v_d = target_group_validators[1];
+		let local_group_validators = state.group_validators(local_group_index, true);
+		let other_group_validators = state.group_validators((local_group_index.0 + 1).into(), true);
+		let v_a = local_group_validators[0];
+		let v_b = local_group_validators[1];
+		let v_c = other_group_validators[0];
+		let v_d = other_group_validators[1];
 
 		// peer A is in group, has relay parent in view.
 		// peer B is in group, has no relay parent in view.
@@ -1406,13 +1693,12 @@ fn advertisement_not_re_sent_when_peer_re_enters_view() {
 		);
 		let candidate_hash = candidate.hash();
 
-		let other_group_validators = state.group_validators(local_group_index, true);
-		let target_group_validators =
-			state.group_validators((local_group_index.0 + 1).into(), true);
-		let v_a = other_group_validators[0];
-		let v_b = other_group_validators[1];
-		let v_c = target_group_validators[0];
-		let v_d = target_group_validators[1];
+		let local_group_validators = state.group_validators(local_group_index, true);
+		let other_group_validators = state.group_validators((local_group_index.0 + 1).into(), true);
+		let v_a = local_group_validators[0];
+		let v_b = local_group_validators[1];
+		let v_c = other_group_validators[0];
+		let v_d = other_group_validators[1];
 
 		// peer A is in group, has relay parent in view.
 		// peer B is in group, has no relay parent in view.
@@ -1630,10 +1916,10 @@ fn grid_statements_imported_to_backing() {
 		);
 		let candidate_hash = candidate.hash();
 
-		let target_group_validators = state.group_validators(other_group, true);
-		let v_c = target_group_validators[0];
-		let v_d = target_group_validators[1];
-		let v_e = target_group_validators[2];
+		let other_group_validators = state.group_validators(other_group, true);
+		let v_c = other_group_validators[0];
+		let v_d = other_group_validators[1];
+		let v_e = other_group_validators[2];
 
 		// Connect C, D, E
 		{
@@ -1835,12 +2121,12 @@ fn advertisements_rejected_from_incorrect_peers() {
 		);
 		let candidate_hash = candidate.hash();
 
-		let other_group_validators = state.group_validators(local_group_index, true);
-		let target_group_validators = state.group_validators(other_group, true);
-		let v_a = other_group_validators[0];
-		let v_b = other_group_validators[1];
-		let v_c = target_group_validators[0];
-		let v_d = target_group_validators[1];
+		let local_group_validators = state.group_validators(local_group_index, true);
+		let other_group_validators = state.group_validators(other_group, true);
+		let v_a = local_group_validators[0];
+		let v_b = local_group_validators[1];
+		let v_c = other_group_validators[0];
+		let v_d = other_group_validators[1];
 
 		// peer A is in group, has relay parent in view.
 		// peer B is in group, has no relay parent in view.
@@ -1979,9 +2265,9 @@ fn manifest_rejected_with_unknown_relay_parent() {
 		);
 		let candidate_hash = candidate.hash();
 
-		let target_group_validators = state.group_validators(other_group, true);
-		let v_c = target_group_validators[0];
-		let v_d = target_group_validators[1];
+		let other_group_validators = state.group_validators(other_group, true);
+		let v_c = other_group_validators[0];
+		let v_d = other_group_validators[1];
 
 		// peer C is not in group, has relay parent in view.
 		// peer D is not in group, has no relay parent in view.
@@ -2081,9 +2367,9 @@ fn manifest_rejected_when_not_a_validator() {
 		);
 		let candidate_hash = candidate.hash();
 
-		let target_group_validators = state.group_validators(other_group, true);
-		let v_c = target_group_validators[0];
-		let v_d = target_group_validators[1];
+		let other_group_validators = state.group_validators(other_group, true);
+		let v_c = other_group_validators[0];
+		let v_d = other_group_validators[1];
 
 		// peer C is not in group, has relay parent in view.
 		// peer D is not in group, has no relay parent in view.
@@ -2188,9 +2474,9 @@ fn manifest_rejected_when_group_does_not_match_para() {
 		);
 		let candidate_hash = candidate.hash();
 
-		let target_group_validators = state.group_validators(other_group, true);
-		let v_c = target_group_validators[0];
-		let v_d = target_group_validators[1];
+		let other_group_validators = state.group_validators(other_group, true);
+		let v_c = other_group_validators[0];
+		let v_d = other_group_validators[1];
 
 		// peer C is not in group, has relay parent in view.
 		// peer D is not in group, has no relay parent in view.
@@ -2294,10 +2580,10 @@ fn peer_reported_for_advertisement_conflicting_with_confirmed_candidate() {
 		);
 		let candidate_hash = candidate.hash();
 
-		let target_group_validators = state.group_validators(other_group, true);
-		let v_c = target_group_validators[0];
-		let v_d = target_group_validators[1];
-		let v_e = target_group_validators[2];
+		let other_group_validators = state.group_validators(other_group, true);
+		let v_c = other_group_validators[0];
+		let v_d = other_group_validators[1];
+		let v_e = other_group_validators[2];
 
 		// Connect C, D, E
 		{
diff --git a/polkadot/node/network/statement-distribution/src/v2/tests/mod.rs b/polkadot/node/network/statement-distribution/src/v2/tests/mod.rs
index 4e626977524..c34cf20d716 100644
--- a/polkadot/node/network/statement-distribution/src/v2/tests/mod.rs
+++ b/polkadot/node/network/statement-distribution/src/v2/tests/mod.rs
@@ -21,6 +21,7 @@ use crate::*;
 use polkadot_node_network_protocol::{
 	grid_topology::TopologyPeerInfo,
 	request_response::{outgoing::Recipient, ReqProtocolNames},
+	v2::{BackedCandidateAcknowledgement, BackedCandidateManifest},
 	view, ObservedRole,
 };
 use polkadot_node_primitives::Statement;
@@ -377,6 +378,95 @@ impl TestLeaf {
 	}
 }
 
+struct TestSetupInfo {
+	local_validator: TestLocalValidator,
+	local_group: GroupIndex,
+	local_para: ParaId,
+	other_group: GroupIndex,
+	other_para: ParaId,
+	relay_parent: Hash,
+	test_leaf: TestLeaf,
+	peers: Vec<PeerId>,
+	validators: Vec<ValidatorIndex>,
+}
+
+struct TestPeerToConnect {
+	local: bool,
+	relay_parent_in_view: bool,
+}
+
+// TODO: Generalize, use in more places.
+/// Sets up some test info that is common to most tests, and connects the requested peers.
+async fn setup_test_and_connect_peers(
+	state: &TestState,
+	overseer: &mut VirtualOverseer,
+	validator_count: usize,
+	group_size: usize,
+	peers_to_connect: &[TestPeerToConnect],
+) -> TestSetupInfo {
+	let local_validator = state.local.clone().unwrap();
+	let local_group = local_validator.group_index.unwrap();
+	let local_para = ParaId::from(local_group.0);
+
+	let other_group = next_group_index(local_group, validator_count, group_size);
+	let other_para = ParaId::from(other_group.0);
+
+	let relay_parent = Hash::repeat_byte(1);
+	let test_leaf = state.make_dummy_leaf(relay_parent);
+
+	// Because we are testing grid mod, the "target" group (the one we communicate with) is usually
+	// other_group, a non-local group.
+	//
+	// TODO: change based on `LocalRole`?
+	let local_group_validators = state.group_validators(local_group, true);
+	let other_group_validators = state.group_validators(other_group, true);
+
+	let mut peers = vec![];
+	let mut validators = vec![];
+	let mut local_group_idx = 0;
+	let mut other_group_idx = 0;
+	for peer_to_connect in peers_to_connect {
+		let peer = PeerId::random();
+		peers.push(peer);
+
+		let v = if peer_to_connect.local {
+			let v = local_group_validators[local_group_idx];
+			local_group_idx += 1;
+			v
+		} else {
+			let v = other_group_validators[other_group_idx];
+			other_group_idx += 1;
+			v
+		};
+		validators.push(v);
+
+		connect_peer(overseer, peer, Some(vec![state.discovery_id(v)].into_iter().collect())).await;
+
+		if peer_to_connect.relay_parent_in_view {
+			send_peer_view_change(overseer, peer.clone(), view![relay_parent]).await;
+		}
+	}
+
+	activate_leaf(overseer, &test_leaf, &state, true).await;
+
+	answer_expected_hypothetical_depth_request(overseer, vec![], Some(relay_parent), false).await;
+
+	// Send gossip topology.
+	send_new_topology(overseer, state.make_dummy_topology()).await;
+
+	TestSetupInfo {
+		local_validator,
+		local_group,
+		local_para,
+		other_group,
+		other_para,
+		test_leaf,
+		relay_parent,
+		peers,
+		validators,
+	}
+}
+
 async fn activate_leaf(
 	virtual_overseer: &mut VirtualOverseer,
 	leaf: &TestLeaf,
@@ -547,6 +637,66 @@ async fn answer_expected_hypothetical_depth_request(
 	)
 }
 
+#[macro_export]
+macro_rules! assert_peer_reported {
+	($virtual_overseer:expr, $peer_id:expr, $rep_change:expr $(,)*) => {
+		assert_matches!(
+			$virtual_overseer.recv().await,
+			AllMessages::NetworkBridgeTx(NetworkBridgeTxMessage::ReportPeer(ReportPeerMessage::Single(p, r)))
+				if p == $peer_id && r == $rep_change.into()
+		);
+	}
+}
+
+async fn send_share_message(
+	virtual_overseer: &mut VirtualOverseer,
+	relay_parent: Hash,
+	statement: SignedFullStatementWithPVD,
+) {
+	virtual_overseer
+		.send(FromOrchestra::Communication {
+			msg: StatementDistributionMessage::Share(relay_parent, statement),
+		})
+		.await;
+}
+
+async fn send_backed_message(
+	virtual_overseer: &mut VirtualOverseer,
+	candidate_hash: CandidateHash,
+) {
+	virtual_overseer
+		.send(FromOrchestra::Communication {
+			msg: StatementDistributionMessage::Backed(candidate_hash),
+		})
+		.await;
+}
+
+async fn send_manifest_from_peer(
+	virtual_overseer: &mut VirtualOverseer,
+	peer_id: PeerId,
+	manifest: BackedCandidateManifest,
+) {
+	send_peer_message(
+		virtual_overseer,
+		peer_id,
+		protocol_v2::StatementDistributionMessage::BackedCandidateManifest(manifest),
+	)
+	.await;
+}
+
+async fn send_ack_from_peer(
+	virtual_overseer: &mut VirtualOverseer,
+	peer_id: PeerId,
+	ack: BackedCandidateAcknowledgement,
+) {
+	send_peer_message(
+		virtual_overseer,
+		peer_id,
+		protocol_v2::StatementDistributionMessage::BackedCandidateKnown(ack),
+	)
+	.await;
+}
+
 fn validator_pubkeys(val_ids: &[ValidatorPair]) -> IndexedVec<ValidatorIndex, ValidatorId> {
 	val_ids.iter().map(|v| v.public().into()).collect()
 }
-- 
GitLab