Repository: paritytech/polkadot-sdk
Pull request #7657: Rewrite zombienet-polkadot-functional-0012-spam-statement-distribution-requests
Author: login: AndreiEres
Labels: name: R0-no-crate-publish-required
description: The change does not require any crates to be re-published.
name: T10-tests
description: This PR/Issue is related to tests.
Created: 2025-02-21T08:34:44Z | Merged: 2025-03-26T07:25:49Z

Fixes https://github.com/paritytech/polkadot-sdk/issues/5973



Diff:
diff --git a/.github/workflows/zombienet_polkadot.yml b/.github/workflows/zombienet_polkadot.yml
new file mode 100644
index 0000000000000..7588542da1eeb
--- /dev/null
+++ b/.github/workflows/zombienet_polkadot.yml
@@ -0,0 +1,82 @@
+name: Zombienet Polkadot
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+  merge_group:
+concurrency:
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+  cancel-in-progress: true
+
+env:
+  RUN_IN_CONTAINER: 1
+  FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: 1
+  LOCAL_DIR: "./polkadot/zombienet_tests"
+  GHA_CLUSTER_SERVER_ADDR: "https://kubernetes.default:443"
+
+# only run if we have changes in [subtrate, polkadot] directories or this workflow.
+jobs:
+  isdraft:
+    uses: ./.github/workflows/reusable-isdraft.yml
+  preflight:
+    needs: isdraft
+    uses: ./.github/workflows/zombienet-reusable-preflight.yml
+
+  zombienet-polkadot-functional-spam-statement-distribution-requests:
+    needs: [preflight]
+    if: ${{ needs.preflight.outputs.changes_substrate || needs.preflight.outputs.changes_polkadot }}
+    runs-on: ${{ needs.preflight.outputs.ZOMBIENET_RUNNER }} # NOTE: should be zombienet-arc-runner (without quotes)
+    timeout-minutes: 60
+    container:
+      image: ${{ needs.preflight.outputs.ZOMBIENET_IMAGE }}
+    env:
+      # sdk tests are looking for POLKADOT_IMAGE
+      POLKADOT_IMAGE: "${{ needs.preflight.outputs.TEMP_IMAGES_BASE }}/polkadot-debug:${{ needs.preflight.outputs.DOCKER_IMAGES_VERSION }}"
+      COL_IMAGE: "${{ needs.preflight.outputs.TEMP_IMAGES_BASE }}/colander:${{ needs.preflight.outputs.DOCKER_IMAGES_VERSION }}"
+      MALUS_IMAGE: "${{ needs.preflight.outputs.TEMP_IMAGES_BASE }}/malus:${{ needs.preflight.outputs.DOCKER_IMAGES_VERSION }}"
+      RUST_LOG: ${{ needs.preflight.outputs.RUST_LOG }}
+      ZOMBIE_PROVIDER: ${{ needs.preflight.outputs.ZOMBIE_PROVIDER }}
+      # don't retry sdk tests
+      NEXTEST_RETRIES: 0
+
+    steps:
+      - name: k8s_auth
+        shell: bash
+        run: |
+          . /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh
+          k8s_auth
+
+      - name: Checkout
+        uses: actions/checkout@v4
+
+      - uses: actions/download-artifact@v4.1.8
+        with:
+          name: prepare-polkadot-zombienet-artifacts-${{ needs.preflight.outputs.SOURCE_REF_SLUG }}
+          github-token: ${{ secrets.GITHUB_TOKEN }}
+          run-id: ${{ needs.preflight.outputs.BUILD_RUN_ID }}
+
+      - name: tar
+        run: tar -xvf artifacts.tar
+
+      - name: script
+        run: |
+          echo "POLKADOT_IMAGE: $POLKADOT_IMAGE"
+          echo "COL_IMAGE: $COL_IMAGE"
+          echo "MALUS_IMAGE: $MALUS_IMAGE"
+          ls -ltr ./artifacts
+          # use spot by default
+          export X_INFRA_INSTANCE=spot
+          # we want to use `--no-capture` in zombienet tests.
+          unset NEXTEST_FAILURE_OUTPUT
+          unset NEXTEST_SUCCESS_OUTPUT
+          cargo nextest run --archive-file ./artifacts/polkadot-zombienet-tests.tar.zst --no-capture -- functional::spam_statement_distribution_requests::spam_statement_distribution_requests_test
+
+      - name: upload logs
+        uses: actions/upload-artifact@v4
+        with:
+          name: zombienet-logs-${{ github.job }}-${{ github.sha }}
+          path: |
+            /tmp/zombie*/logs/*
diff --git a/.gitlab/pipeline/zombienet/polkadot.yml b/.gitlab/pipeline/zombienet/polkadot.yml
index cfb0f93ef38cb..d0cf40991c95a 100644
--- a/.gitlab/pipeline/zombienet/polkadot.yml
+++ b/.gitlab/pipeline/zombienet/polkadot.yml
@@ -152,14 +152,6 @@ zombienet-polkadot-functional-0010-validator-disabling:
       --local-dir="${LOCAL_DIR}/functional"
       --test="0010-validator-disabling.zndsl"
 
-.zombienet-polkadot-functional-0012-spam-statement-distribution-requests:
-  extends:
-    - .zombienet-polkadot-common
-  script:
-    - /home/nonroot/zombie-net/scripts/ci/run-test-local-env-manager.sh
-      --local-dir="${LOCAL_DIR}/functional"
-      --test="0012-spam-statement-distribution-requests.zndsl"
-
 zombienet-polkadot-functional-0013-systematic-chunk-recovery:
   extends:
     - .zombienet-polkadot-common
diff --git a/polkadot/zombienet-sdk-tests/tests/functional/mod.rs b/polkadot/zombienet-sdk-tests/tests/functional/mod.rs
index 7e5d313ff68dd..64aa5c84a4349 100644
--- a/polkadot/zombienet-sdk-tests/tests/functional/mod.rs
+++ b/polkadot/zombienet-sdk-tests/tests/functional/mod.rs
@@ -3,4 +3,5 @@
 
 mod async_backing_6_seconds_rate;
 mod duplicate_collations;
+mod spam_statement_distribution_requests;
 mod sync_backing;
diff --git a/polkadot/zombienet-sdk-tests/tests/functional/spam_statement_distribution_requests.rs b/polkadot/zombienet-sdk-tests/tests/functional/spam_statement_distribution_requests.rs
new file mode 100644
index 0000000000000..61e5e407f59f2
--- /dev/null
+++ b/polkadot/zombienet-sdk-tests/tests/functional/spam_statement_distribution_requests.rs
@@ -0,0 +1,163 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Test if parachains progress when group is getting spammed by statement distribution requests.
+
+use anyhow::anyhow;
+
+use cumulus_zombienet_sdk_helpers::assert_para_throughput;
+use polkadot_primitives::Id as ParaId;
+use serde_json::json;
+use subxt::{OnlineClient, PolkadotConfig};
+use zombienet_sdk::NetworkConfigBuilder;
+
+#[tokio::test(flavor = "multi_thread")]
+async fn spam_statement_distribution_requests_test() -> Result<(), anyhow::Error> {
+	let _ = env_logger::try_init_from_env(
+		env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"),
+	);
+
+	let images = zombienet_sdk::environment::get_images_from_env();
+
+	let config = NetworkConfigBuilder::new()
+		.with_relaychain(|r| {
+			r.with_chain("rococo-local")
+				.with_default_command("polkadot")
+				.with_default_image(images.polkadot.as_str())
+				.with_default_args(vec![
+					// parachain::statement-distribution=trace to find
+					// "Peer already being served, dropping request"
+					("-lparachain=debug,parachain::statement-distribution=trace").into(),
+				])
+				.with_default_resources(|r| {
+					r.with_limit_cpu("2")
+						.with_limit_memory("4G")
+						.with_request_cpu("1")
+						.with_request_memory("2G")
+				})
+				.with_genesis_overrides(json!({
+					"configuration": {
+						"config": {
+							"needed_approvals": 2,
+							"scheduler_params": {
+								"max_validators_per_core": 5
+							}
+						}
+					}
+				}))
+				.with_node(|node| node.with_name("honest-0"))
+				.with_node(|node| node.with_name("honest-1"))
+				.with_node(|node| node.with_name("honest-2"))
+				.with_node(|node| node.with_name("honest-3"))
+				.with_node(|node| {
+					node.with_name("malus")
+						.with_image(
+							std::env::var("MALUS_IMAGE")
+								.unwrap_or("docker.io/paritypr/malus".to_string())
+								.as_str(),
+						)
+						.with_command("malus")
+						.with_subcommand("spam-statement-requests")
+						.with_args(vec![
+							"--alice".into(),
+							"--spam-factor=1000".into(),
+							"--insecure-validator-i-know-what-i-do".into(),
+							"-lMALUS=trace,parachain=debug".into(),
+						])
+				})
+		})
+		.with_parachain(|p| {
+			p.with_id(2000)
+				.with_default_command("undying-collator")
+				.cumulus_based(false)
+				.with_default_image(
+					std::env::var("COL_IMAGE")
+						.unwrap_or("docker.io/paritypr/colander:latest".to_string())
+						.as_str(),
+				)
+				.with_default_args(vec![("-lparachain=debug").into()])
+				.with_collator(|n| n.with_name("collator-2000"))
+		})
+		.with_parachain(|p| {
+			p.with_id(2001)
+				.with_default_command("undying-collator")
+				.cumulus_based(false)
+				.with_default_image(
+					std::env::var("COL_IMAGE")
+						.unwrap_or("docker.io/paritypr/colander:latest".to_string())
+						.as_str(),
+				)
+				.with_default_args(vec![("-lparachain=debug").into()])
+				.with_collator(|n| n.with_name("collator-2001"))
+		})
+		.build()
+		.map_err(|e| {
+			let errs = e.into_iter().map(|e| e.to_string()).collect::<Vec<_>>().join(" ");
+			anyhow!("config errs: {errs}")
+		})?;
+
+	let spawn_fn = zombienet_sdk::environment::get_spawn_fn();
+	let network = spawn_fn(config).await?;
+
+	let malus = network.get_node("malus")?;
+	let honest = network.get_node("honest-0")?;
+	let relay_client: OnlineClient<PolkadotConfig> = honest.wait_client().await?;
+	let _malus_client: OnlineClient<PolkadotConfig> = malus.wait_client().await?;
+
+	// Check authority status and peers.
+	malus.assert("node_roles", 4.0).await?;
+	honest.assert("node_roles", 4.0).await?;
+
+	// Ensure parachains are registered.
+	assert_para_throughput(
+		&relay_client,
+		2,
+		[(ParaId::from(2000), 2..3), (ParaId::from(2001), 2..3)].into_iter().collect(),
+	)
+	.await?;
+
+	// Ensure that malus is already attempting to DoS
+	malus
+		.wait_log_line_count_with_timeout(
+			"*Duplicating AttestedCandidateV2 request*",
+			true,
+			1,
+			90u64,
+		)
+		.await?;
+
+	// Ensure parachains made progress.
+	assert_para_throughput(
+		&relay_client,
+		10,
+		[(ParaId::from(2000), 9..11), (ParaId::from(2001), 9..11)].into_iter().collect(),
+	)
+	.await?;
+
+	// Ensure that honest nodes drop extra requests.
+	honest
+		.wait_log_line_count_with_timeout(
+			"*Peer already being served, dropping request*",
+			true,
+			1,
+			60u64,
+		)
+		.await?;
+
+	// Check lag - approval
+	honest
+		.assert(
+			"polkadot_parachain_approval_checking_finality_lag{chain=\"rococo_local_testnet\"}",
+			0.0,
+		)
+		.await?;
+
+	// Check lag - dispute conclusion
+	honest
+		.assert("polkadot_parachain_disputes_finality_lag{chain=\"rococo_local_testnet\"}", 0.0)
+		.await?;
+
+	log::info!("Test finished successfully");
+
+	Ok(())
+}
diff --git a/polkadot/zombienet_tests/functional/0012-spam-statement-distribution-requests.toml b/polkadot/zombienet_tests/functional/0012-spam-statement-distribution-requests.toml
deleted file mode 100644
index 14208425d62ba..0000000000000
--- a/polkadot/zombienet_tests/functional/0012-spam-statement-distribution-requests.toml
+++ /dev/null
@@ -1,43 +0,0 @@
-[settings]
-timeout = 1000
-
-[relaychain.genesis.runtimeGenesis.patch.configuration.config]
-  needed_approvals = 2
-
-[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params]
-  max_validators_per_core = 5
-
-[relaychain]
-default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}"
-chain = "rococo-local"
-default_command = "polkadot"
-
-[relaychain.default_resources]
-limits = { memory = "4G", cpu = "2" }
-requests = { memory = "2G", cpu = "1" }
-
-  [[relaychain.node_groups]]
-  name = "honest"
-  count = 4
-  args = ["-lparachain=debug,parachain::statement-distribution=trace"]
-
-  [[relaychain.nodes]]
-  image = "{{MALUS_IMAGE}}"
-  name = "malus"
-  command = "malus spam-statement-requests"
-  args = [ "--alice", "-lparachain=debug,MALUS=trace", "--spam-factor=1000" ]
-
-{% for id in range(2000,2001) %}
-[[parachains]]
-id = {{id}}
-  [parachains.collator]
-  image = "{{COL_IMAGE}}"
-  name = "collator"
-  command = "undying-collator"
-  args = ["-lparachain=debug"]
-{% endfor %}
-
-[types.Header]
-number = "u64"
-parent_hash = "Hash"
-post_state = "Hash"
diff --git a/polkadot/zombienet_tests/functional/0012-spam-statement-distribution-requests.zndsl b/polkadot/zombienet_tests/functional/0012-spam-statement-distribution-requests.zndsl
deleted file mode 100644
index 9985dd24ee38a..0000000000000
--- a/polkadot/zombienet_tests/functional/0012-spam-statement-distribution-requests.zndsl
+++ /dev/null
@@ -1,27 +0,0 @@
-Description: Test if parachains progress when group is getting spammed by statement distribution requests.
-Network: ./0012-spam-statement-distribution-requests.toml
-Creds: config
-
-# Check authority status and peers.
-malus: reports node_roles is 4
-honest: reports node_roles is 4
-
-# Ensure parachains are registered.
-honest: parachain 2000 is registered within 60 seconds
-
-# Ensure that malus is already attempting to DoS
-malus: log line contains "😈 Duplicating AttestedCandidateV2 request" within 90 seconds
-
-# Ensure parachains made progress.
-honest: parachain 2000 block height is at least 10 within 200 seconds
-
-# Ensure that honest nodes drop extra requests
-honest: log line contains "Peer already being served, dropping request" within 60 seconds
-
-# Check lag - approval
-honest: reports polkadot_parachain_approval_checking_finality_lag is 0
-
-# Check lag - dispute conclusion
-honest: reports polkadot_parachain_disputes_finality_lag is 0
-
-


Reviews:
user: login: alvicsam
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-10T13:54:20Z
user: login: sw10pa
body: LGTM
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-18T13:06:28Z
user: login: alexggh
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T09:56:19Z
user: login: sandreim
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-21T12:28:27Z
user: login: AndreiEres
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T08:28:21Z
user: login: pepoviola
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T09:05:34Z
user: login: sw10pa
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T09:07:20Z
user: login: alindima
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T09:09:01Z
user: login: pepoviola
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T09:12:59Z
user: login: sw10pa
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T13:36:34Z
user: login: sw10pa
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T14:56:31Z

Review comments:
diff_hunk: @@ -0,0 +1,163 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Test if parachains progress when group is getting spammed by statement distribution requests.
+
+use anyhow::anyhow;
+
+use crate::helpers::assert_para_throughput;
+use polkadot_primitives::Id as ParaId;
+use serde_json::json;
+use subxt::{OnlineClient, PolkadotConfig};
+use zombienet_sdk::NetworkConfigBuilder;
+
+#[tokio::test(flavor = "multi_thread")]
+async fn spam_statement_distribution_requests_test() -> Result<(), anyhow::Error> {
+	let _ = env_logger::try_init_from_env(
+		env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"),
+	);
+
+	let images = zombienet_sdk::environment::get_images_from_env();
+
+	let config = NetworkConfigBuilder::new()
+		.with_relaychain(|r| {
+			r.with_chain("rococo-local")
+				.with_default_command("polkadot")
+				.with_default_image(images.polkadot.as_str())
+				.with_default_args(vec![
+					// parachain::statement-distribution=trace to find
+					// "Peer already being served, dropping request"
+					("-lparachain=debug,parachain::statement-distribution=trace").into(),
+				])
+				.with_default_resources(|r| {
+					r.with_limit_cpu("2")
+						.with_limit_memory("4G")
+						.with_request_cpu("1")
+						.with_request_memory("2G")
+				})
+				.with_genesis_overrides(json!({
+					"configuration": {
+						"config": {
+							"needed_approvals": 2,
+							"scheduler_params": {
+								"max_validators_per_core": 5
+							}
+						}
+					}
+				}))
+				.with_node(|node| node.with_name("honest-0"))
+				.with_node(|node| node.with_name("honest-1"))
+				.with_node(|node| node.with_name("honest-2"))
+				.with_node(|node| node.with_name("honest-3"))
+				.with_node(|node| {
+					node.with_name("malus")
+						.with_image(
+							std::env::var("MALUS_IMAGE")
+								.unwrap_or("docker.io/paritypr/malus".to_string())
+								.as_str(),
+						)
+						.with_command("malus")
+						.with_subcommand("spam-statement-requests")
+						.with_args(vec![
+							"--alice".into(),
+							"--spam-factor=1000".into(),
+							"--insecure-validator-i-know-what-i-do".into(),
+							"-lMALUS=trace,parachain=debug".into(),
+						])
+				})
+		})
+		.with_parachain(|p| {
+			p.with_id(2000)
+				.with_default_command("undying-collator")
+				.cumulus_based(false)
+				.with_default_image(
+					std::env::var("COL_IMAGE")
+						.unwrap_or("docker.io/paritypr/colander:latest".to_string())
+						.as_str(),
+				)
+				.with_default_args(vec![("-lparachain=debug").into()])
+				.with_collator(|n| n.with_name("collator-2000"))
+		})
+		.with_parachain(|p| {
+			p.with_id(2001)
+				.with_default_command("undying-collator")
+				.cumulus_based(false)
+				.with_default_image(
+					std::env::var("COL_IMAGE")
+						.unwrap_or("docker.io/paritypr/colander:latest".to_string())
+						.as_str(),
+				)
+				.with_default_args(vec![("-lparachain=debug").into()])
+				.with_collator(|n| n.with_name("collator-2001"))
+		})
+		.build()
+		.map_err(|e| {
+			let errs = e.into_iter().map(|e| e.to_string()).collect::<Vec<_>>().join(" ");
+			anyhow!("config errs: {errs}")
+		})?;
+
+	let spawn_fn = zombienet_sdk::environment::get_spawn_fn();
+	let network = spawn_fn(config).await?;
+
+	let malus = network.get_node("malus")?;
+	let honest = network.get_node("honest-0")?;
+	let relay_client: OnlineClient<PolkadotConfig> = honest.wait_client().await?;
+	let _malus_client: OnlineClient<PolkadotConfig> = malus.wait_client().await?;
+
+	// Check authority status and peers.
+	malus.assert("node_roles", 4.0).await?;
+	honest.assert("node_roles", 4.0).await?;
+
+	// Ensure parachains are registered.
+	assert_para_throughput(
path: polkadot/zombienet-sdk-tests/tests/functional/spam_statement_distribution_requests.rs
user: login: sandreim
body: This doesn't check if registered, only checks for progress.
created_at: 2025-03-21T12:28:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,163 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Test if parachains progress when group is getting spammed by statement distribution requests.
+
+use anyhow::anyhow;
+
+use crate::helpers::assert_para_throughput;
+use polkadot_primitives::Id as ParaId;
+use serde_json::json;
+use subxt::{OnlineClient, PolkadotConfig};
+use zombienet_sdk::NetworkConfigBuilder;
+
+#[tokio::test(flavor = "multi_thread")]
+async fn spam_statement_distribution_requests_test() -> Result<(), anyhow::Error> {
+	let _ = env_logger::try_init_from_env(
+		env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"),
+	);
+
+	let images = zombienet_sdk::environment::get_images_from_env();
+
+	let config = NetworkConfigBuilder::new()
+		.with_relaychain(|r| {
+			r.with_chain("rococo-local")
+				.with_default_command("polkadot")
+				.with_default_image(images.polkadot.as_str())
+				.with_default_args(vec![
+					// parachain::statement-distribution=trace to find
+					// "Peer already being served, dropping request"
+					("-lparachain=debug,parachain::statement-distribution=trace").into(),
+				])
+				.with_default_resources(|r| {
+					r.with_limit_cpu("2")
+						.with_limit_memory("4G")
+						.with_request_cpu("1")
+						.with_request_memory("2G")
+				})
+				.with_genesis_overrides(json!({
+					"configuration": {
+						"config": {
+							"needed_approvals": 2,
+							"scheduler_params": {
+								"max_validators_per_core": 5
+							}
+						}
+					}
+				}))
+				.with_node(|node| node.with_name("honest-0"))
+				.with_node(|node| node.with_name("honest-1"))
+				.with_node(|node| node.with_name("honest-2"))
+				.with_node(|node| node.with_name("honest-3"))
+				.with_node(|node| {
+					node.with_name("malus")
+						.with_image(
+							std::env::var("MALUS_IMAGE")
+								.unwrap_or("docker.io/paritypr/malus".to_string())
+								.as_str(),
+						)
+						.with_command("malus")
+						.with_subcommand("spam-statement-requests")
+						.with_args(vec![
+							"--alice".into(),
+							"--spam-factor=1000".into(),
+							"--insecure-validator-i-know-what-i-do".into(),
+							"-lMALUS=trace,parachain=debug".into(),
+						])
+				})
+		})
+		.with_parachain(|p| {
+			p.with_id(2000)
+				.with_default_command("undying-collator")
+				.cumulus_based(false)
+				.with_default_image(
+					std::env::var("COL_IMAGE")
+						.unwrap_or("docker.io/paritypr/colander:latest".to_string())
+						.as_str(),
+				)
+				.with_default_args(vec![("-lparachain=debug").into()])
+				.with_collator(|n| n.with_name("collator-2000"))
+		})
+		.with_parachain(|p| {
+			p.with_id(2001)
+				.with_default_command("undying-collator")
+				.cumulus_based(false)
+				.with_default_image(
+					std::env::var("COL_IMAGE")
+						.unwrap_or("docker.io/paritypr/colander:latest".to_string())
+						.as_str(),
+				)
+				.with_default_args(vec![("-lparachain=debug").into()])
+				.with_collator(|n| n.with_name("collator-2001"))
+		})
+		.build()
+		.map_err(|e| {
+			let errs = e.into_iter().map(|e| e.to_string()).collect::<Vec<_>>().join(" ");
+			anyhow!("config errs: {errs}")
+		})?;
+
+	let spawn_fn = zombienet_sdk::environment::get_spawn_fn();
+	let network = spawn_fn(config).await?;
+
+	let malus = network.get_node("malus")?;
+	let honest = network.get_node("honest-0")?;
+	let relay_client: OnlineClient<PolkadotConfig> = honest.wait_client().await?;
+	let _malus_client: OnlineClient<PolkadotConfig> = malus.wait_client().await?;
+
+	// Check authority status and peers.
+	malus.assert("node_roles", 4.0).await?;
+	honest.assert("node_roles", 4.0).await?;
+
+	// Ensure parachains are registered.
+	assert_para_throughput(
path: polkadot/zombienet-sdk-tests/tests/functional/spam_statement_distribution_requests.rs
user: login: sandreim
body: Do we have an equivalent for `honest: parachain 2000 is registered within 60 seconds` ?
created_at: 2025-03-21T12:28:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,163 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Test if parachains progress when group is getting spammed by statement distribution requests.
+
+use anyhow::anyhow;
+
+use crate::helpers::assert_para_throughput;
+use polkadot_primitives::Id as ParaId;
+use serde_json::json;
+use subxt::{OnlineClient, PolkadotConfig};
+use zombienet_sdk::NetworkConfigBuilder;
+
+#[tokio::test(flavor = "multi_thread")]
+async fn spam_statement_distribution_requests_test() -> Result<(), anyhow::Error> {
+	let _ = env_logger::try_init_from_env(
+		env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"),
+	);
+
+	let images = zombienet_sdk::environment::get_images_from_env();
+
+	let config = NetworkConfigBuilder::new()
+		.with_relaychain(|r| {
+			r.with_chain("rococo-local")
+				.with_default_command("polkadot")
+				.with_default_image(images.polkadot.as_str())
+				.with_default_args(vec![
+					// parachain::statement-distribution=trace to find
+					// "Peer already being served, dropping request"
+					("-lparachain=debug,parachain::statement-distribution=trace").into(),
+				])
+				.with_default_resources(|r| {
+					r.with_limit_cpu("2")
+						.with_limit_memory("4G")
+						.with_request_cpu("1")
+						.with_request_memory("2G")
+				})
+				.with_genesis_overrides(json!({
+					"configuration": {
+						"config": {
+							"needed_approvals": 2,
+							"scheduler_params": {
+								"max_validators_per_core": 5
+							}
+						}
+					}
+				}))
+				.with_node(|node| node.with_name("honest-0"))
+				.with_node(|node| node.with_name("honest-1"))
+				.with_node(|node| node.with_name("honest-2"))
+				.with_node(|node| node.with_name("honest-3"))
+				.with_node(|node| {
+					node.with_name("malus")
+						.with_image(
+							std::env::var("MALUS_IMAGE")
+								.unwrap_or("docker.io/paritypr/malus".to_string())
+								.as_str(),
+						)
+						.with_command("malus")
+						.with_subcommand("spam-statement-requests")
+						.with_args(vec![
+							"--alice".into(),
+							"--spam-factor=1000".into(),
+							"--insecure-validator-i-know-what-i-do".into(),
+							"-lMALUS=trace,parachain=debug".into(),
+						])
+				})
+		})
+		.with_parachain(|p| {
+			p.with_id(2000)
+				.with_default_command("undying-collator")
+				.cumulus_based(false)
+				.with_default_image(
+					std::env::var("COL_IMAGE")
+						.unwrap_or("docker.io/paritypr/colander:latest".to_string())
+						.as_str(),
+				)
+				.with_default_args(vec![("-lparachain=debug").into()])
+				.with_collator(|n| n.with_name("collator-2000"))
+		})
+		.with_parachain(|p| {
+			p.with_id(2001)
+				.with_default_command("undying-collator")
+				.cumulus_based(false)
+				.with_default_image(
+					std::env::var("COL_IMAGE")
+						.unwrap_or("docker.io/paritypr/colander:latest".to_string())
+						.as_str(),
+				)
+				.with_default_args(vec![("-lparachain=debug").into()])
+				.with_collator(|n| n.with_name("collator-2001"))
+		})
+		.build()
+		.map_err(|e| {
+			let errs = e.into_iter().map(|e| e.to_string()).collect::<Vec<_>>().join(" ");
+			anyhow!("config errs: {errs}")
+		})?;
+
+	let spawn_fn = zombienet_sdk::environment::get_spawn_fn();
+	let network = spawn_fn(config).await?;
+
+	let malus = network.get_node("malus")?;
+	let honest = network.get_node("honest-0")?;
+	let relay_client: OnlineClient<PolkadotConfig> = honest.wait_client().await?;
+	let _malus_client: OnlineClient<PolkadotConfig> = malus.wait_client().await?;
+
+	// Check authority status and peers.
+	malus.assert("node_roles", 4.0).await?;
+	honest.assert("node_roles", 4.0).await?;
+
+	// Ensure parachains are registered.
+	assert_para_throughput(
path: polkadot/zombienet-sdk-tests/tests/functional/spam_statement_distribution_requests.rs
user: login: AndreiEres
body: We don't have it yet
created_at: 2025-03-25T08:28:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,163 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Test if parachains progress when group is getting spammed by statement distribution requests.
+
+use anyhow::anyhow;
+
+use crate::helpers::assert_para_throughput;
+use polkadot_primitives::Id as ParaId;
+use serde_json::json;
+use subxt::{OnlineClient, PolkadotConfig};
+use zombienet_sdk::NetworkConfigBuilder;
+
+#[tokio::test(flavor = "multi_thread")]
+async fn spam_statement_distribution_requests_test() -> Result<(), anyhow::Error> {
+	let _ = env_logger::try_init_from_env(
+		env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"),
+	);
+
+	let images = zombienet_sdk::environment::get_images_from_env();
+
+	let config = NetworkConfigBuilder::new()
+		.with_relaychain(|r| {
+			r.with_chain("rococo-local")
+				.with_default_command("polkadot")
+				.with_default_image(images.polkadot.as_str())
+				.with_default_args(vec![
+					// parachain::statement-distribution=trace to find
+					// "Peer already being served, dropping request"
+					("-lparachain=debug,parachain::statement-distribution=trace").into(),
+				])
+				.with_default_resources(|r| {
+					r.with_limit_cpu("2")
+						.with_limit_memory("4G")
+						.with_request_cpu("1")
+						.with_request_memory("2G")
+				})
+				.with_genesis_overrides(json!({
+					"configuration": {
+						"config": {
+							"needed_approvals": 2,
+							"scheduler_params": {
+								"max_validators_per_core": 5
+							}
+						}
+					}
+				}))
+				.with_node(|node| node.with_name("honest-0"))
+				.with_node(|node| node.with_name("honest-1"))
+				.with_node(|node| node.with_name("honest-2"))
+				.with_node(|node| node.with_name("honest-3"))
+				.with_node(|node| {
+					node.with_name("malus")
+						.with_image(
+							std::env::var("MALUS_IMAGE")
+								.unwrap_or("docker.io/paritypr/malus".to_string())
+								.as_str(),
+						)
+						.with_command("malus")
+						.with_subcommand("spam-statement-requests")
+						.with_args(vec![
+							"--alice".into(),
+							"--spam-factor=1000".into(),
+							"--insecure-validator-i-know-what-i-do".into(),
+							"-lMALUS=trace,parachain=debug".into(),
+						])
+				})
+		})
+		.with_parachain(|p| {
+			p.with_id(2000)
+				.with_default_command("undying-collator")
+				.cumulus_based(false)
+				.with_default_image(
+					std::env::var("COL_IMAGE")
+						.unwrap_or("docker.io/paritypr/colander:latest".to_string())
+						.as_str(),
+				)
+				.with_default_args(vec![("-lparachain=debug").into()])
+				.with_collator(|n| n.with_name("collator-2000"))
+		})
+		.with_parachain(|p| {
+			p.with_id(2001)
+				.with_default_command("undying-collator")
+				.cumulus_based(false)
+				.with_default_image(
+					std::env::var("COL_IMAGE")
+						.unwrap_or("docker.io/paritypr/colander:latest".to_string())
+						.as_str(),
+				)
+				.with_default_args(vec![("-lparachain=debug").into()])
+				.with_collator(|n| n.with_name("collator-2001"))
+		})
+		.build()
+		.map_err(|e| {
+			let errs = e.into_iter().map(|e| e.to_string()).collect::<Vec<_>>().join(" ");
+			anyhow!("config errs: {errs}")
+		})?;
+
+	let spawn_fn = zombienet_sdk::environment::get_spawn_fn();
+	let network = spawn_fn(config).await?;
+
+	let malus = network.get_node("malus")?;
+	let honest = network.get_node("honest-0")?;
+	let relay_client: OnlineClient<PolkadotConfig> = honest.wait_client().await?;
+	let _malus_client: OnlineClient<PolkadotConfig> = malus.wait_client().await?;
+
+	// Check authority status and peers.
+	malus.assert("node_roles", 4.0).await?;
+	honest.assert("node_roles", 4.0).await?;
+
+	// Ensure parachains are registered.
+	assert_para_throughput(
path: polkadot/zombienet-sdk-tests/tests/functional/spam_statement_distribution_requests.rs
user: login: pepoviola
body: Hi 👋,  I will add a `fn` into zombienet to make easy to query paras.
created_at: 2025-03-25T09:05:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,82 @@
+name: Zombienet Polkadot
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+  merge_group:
+concurrency:
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+  cancel-in-progress: true
+
+env:
+  RUN_IN_CONTAINER: 1
+  FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: 1
+  LOCAL_DIR: "./cumulus/zombienet/tests"
path: .github/workflows/zombienet_polkadot.yml
user: login: sw10pa
body: Should not it be `LOCAL_DIR: "./polkadot/zombienet-sdk-tests/tests"`?
created_at: 2025-03-25T09:07:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,163 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Test if parachains progress when group is getting spammed by statement distribution requests.
+
+use anyhow::anyhow;
+
+use crate::helpers::assert_para_throughput;
+use polkadot_primitives::Id as ParaId;
+use serde_json::json;
+use subxt::{OnlineClient, PolkadotConfig};
+use zombienet_sdk::NetworkConfigBuilder;
+
+#[tokio::test(flavor = "multi_thread")]
+async fn spam_statement_distribution_requests_test() -> Result<(), anyhow::Error> {
+	let _ = env_logger::try_init_from_env(
+		env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"),
+	);
+
+	let images = zombienet_sdk::environment::get_images_from_env();
+
+	let config = NetworkConfigBuilder::new()
+		.with_relaychain(|r| {
+			r.with_chain("rococo-local")
+				.with_default_command("polkadot")
+				.with_default_image(images.polkadot.as_str())
+				.with_default_args(vec![
+					// parachain::statement-distribution=trace to find
+					// "Peer already being served, dropping request"
+					("-lparachain=debug,parachain::statement-distribution=trace").into(),
+				])
+				.with_default_resources(|r| {
+					r.with_limit_cpu("2")
+						.with_limit_memory("4G")
+						.with_request_cpu("1")
+						.with_request_memory("2G")
+				})
+				.with_genesis_overrides(json!({
+					"configuration": {
+						"config": {
+							"needed_approvals": 2,
+							"scheduler_params": {
+								"max_validators_per_core": 5
+							}
+						}
+					}
+				}))
+				.with_node(|node| node.with_name("honest-0"))
+				.with_node(|node| node.with_name("honest-1"))
+				.with_node(|node| node.with_name("honest-2"))
+				.with_node(|node| node.with_name("honest-3"))
+				.with_node(|node| {
+					node.with_name("malus")
+						.with_image(
+							std::env::var("MALUS_IMAGE")
+								.unwrap_or("docker.io/paritypr/malus".to_string())
+								.as_str(),
+						)
+						.with_command("malus")
+						.with_subcommand("spam-statement-requests")
+						.with_args(vec![
+							"--alice".into(),
+							"--spam-factor=1000".into(),
+							"--insecure-validator-i-know-what-i-do".into(),
+							"-lMALUS=trace,parachain=debug".into(),
+						])
+				})
+		})
+		.with_parachain(|p| {
+			p.with_id(2000)
+				.with_default_command("undying-collator")
+				.cumulus_based(false)
+				.with_default_image(
+					std::env::var("COL_IMAGE")
+						.unwrap_or("docker.io/paritypr/colander:latest".to_string())
+						.as_str(),
+				)
+				.with_default_args(vec![("-lparachain=debug").into()])
+				.with_collator(|n| n.with_name("collator-2000"))
+		})
+		.with_parachain(|p| {
+			p.with_id(2001)
+				.with_default_command("undying-collator")
+				.cumulus_based(false)
+				.with_default_image(
+					std::env::var("COL_IMAGE")
+						.unwrap_or("docker.io/paritypr/colander:latest".to_string())
+						.as_str(),
+				)
+				.with_default_args(vec![("-lparachain=debug").into()])
+				.with_collator(|n| n.with_name("collator-2001"))
+		})
+		.build()
+		.map_err(|e| {
+			let errs = e.into_iter().map(|e| e.to_string()).collect::<Vec<_>>().join(" ");
+			anyhow!("config errs: {errs}")
+		})?;
+
+	let spawn_fn = zombienet_sdk::environment::get_spawn_fn();
+	let network = spawn_fn(config).await?;
+
+	let malus = network.get_node("malus")?;
+	let honest = network.get_node("honest-0")?;
+	let relay_client: OnlineClient<PolkadotConfig> = honest.wait_client().await?;
+	let _malus_client: OnlineClient<PolkadotConfig> = malus.wait_client().await?;
+
+	// Check authority status and peers.
+	malus.assert("node_roles", 4.0).await?;
+	honest.assert("node_roles", 4.0).await?;
+
+	// Ensure parachains are registered.
+	assert_para_throughput(
path: polkadot/zombienet-sdk-tests/tests/functional/spam_statement_distribution_requests.rs
user: login: alindima
body: but why do we care?
This function checks that we get X backed candidates within Y relay chain blocks. If the para is not registered within these Y relay chain blocks, the assertion will fail
created_at: 2025-03-25T09:09:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,82 @@
+name: Zombienet Polkadot
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+  merge_group:
+concurrency:
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+  cancel-in-progress: true
+
+env:
+  RUN_IN_CONTAINER: 1
+  FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: 1
+  LOCAL_DIR: "./cumulus/zombienet/tests"
path: .github/workflows/zombienet_polkadot.yml
user: login: pepoviola
body: Yes, `LOCAL_DIR` is used in old test. Should be `./polkadot/zombienet_tests`.

```suggestion
  LOCAL_DIR: "./polkadot/zombienet_tests"
```

Thx!
created_at: 2025-03-25T09:12:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,82 @@
+name: Zombienet Polkadot
+
+on:
+  push:
+    branches:
+      - master
+  pull_request:
+    types: [opened, synchronize, reopened, ready_for_review]
+  merge_group:
+concurrency:
+  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+  cancel-in-progress: true
+
+env:
+  RUN_IN_CONTAINER: 1
+  FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: 1
+  LOCAL_DIR: "./cumulus/zombienet/tests"
+  GHA_CLUSTER_SERVER_ADDR: "https://kubernetes.default:443"
+
+# only run if we have changes in [subtrate, cumulus, polkadot] directories or this workflow.
path: .github/workflows/zombienet_polkadot.yml
user: login: sw10pa
body: ```suggestion
# only run if we have changes in [subtrate, polkadot] directories or this workflow.
```

Since we have below  `if: ${{ needs.preflight.outputs.changes_substrate || needs.preflight.outputs.changes_polkadot }}`
created_at: 2025-03-25T13:36:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,163 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Test if parachains progress when group is getting spammed by statement distribution requests.
+
+use anyhow::anyhow;
+
+use crate::helpers::assert_para_throughput;
path: polkadot/zombienet-sdk-tests/tests/functional/spam_statement_distribution_requests.rs
user: login: sw10pa
body: You should use it from `cumulus_zombienet_sdk_helpers`, since Alin is removing the helpers from polkadot in #7993 

```suggestion
use cumulus_zombienet_sdk_helpers::assert_para_throughput;
```
created_at: 2025-03-25T14:56:31Z
author_association: CONTRIBUTOR

PR comments:
user: login: alvicsam
created_at: 2025-03-05T10:01:53Z
body: @pepoviola , @AndreiEres is it possible to move this test to GHA?
author_association: CONTRIBUTOR
user: login: AndreiEres
created_at: 2025-03-05T10:11:55Z
body: > @pepoviola , @AndreiEres is it possible to move this test to GHA?

only this one or all zombienet tests?
author_association: CONTRIBUTOR
user: login: alvicsam
created_at: 2025-03-05T10:41:14Z
body: Only `zombienet-polkadot-functional-spam-statement-distribution-requests` and `zombienet-polkadot-functional-spam-statement-distribution-requests` since they are new.
author_association: CONTRIBUTOR
user: login: paritytech-workflow-stopper[bot]
created_at: 2025-03-05T16:42:42Z
body: 
All GitHub workflows were cancelled due to failure one of the required jobs.
**Failed workflow url**: https://github.com/paritytech/polkadot-sdk/actions/runs/13679875703
**Failed job name**: test-linux-stable-no-try-runtime

author_association: NONE
user: login: alexggh
created_at: 2025-03-21T09:57:16Z
body: One thing to note here, is please make sure it is stable over multiple runs(10?)  in CI before merging
author_association: CONTRIBUTOR