diff --git a/.github/workflows/zombienet_polkadot.yml b/.github/workflows/zombienet_polkadot.yml index 062d0b9b463e8..19c44b2d5d9cb 100644 --- a/.github/workflows/zombienet_polkadot.yml +++ b/.github/workflows/zombienet_polkadot.yml @@ -229,34 +229,6 @@ jobs: build-id: ${{ needs.preflight.outputs.BUILD_RUN_ID }} ref-slug: ${{ needs.preflight.outputs.SOURCE_REF_SLUG }} - - # - zombienet-polkadot-functional-0010-validator-disabling: - needs: [preflight] - if: ${{ (needs.preflight.outputs.changes_substrate || needs.preflight.outputs.changes_polkadot) && ! contains(needs.preflight.outputs.FLAKY_TESTS, 'zombienet-polkadot-functional-0010-validator-disabling') }} - runs-on: ${{ needs.preflight.outputs.ZOMBIENET_LARGE_RUNNER }} - timeout-minutes: 60 - container: - image: ${{ needs.preflight.outputs.ZOMBIENET_IMAGE }} - options: -v /tmp/zombienet:/tmp/zombienet - env: - ZOMBIENET_INTEGRATION_TEST_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 }}" - DEBUG: ${{ needs.preflight.outputs.DEBUG }} - ZOMBIENET_PROVIDER: ${{ needs.preflight.outputs.ZOMBIENET_PROVIDER }} - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: zombienet_test - uses: ./.github/actions/zombienet - with: - test: "0010-validator-disabling.zndsl" - local-dir: "${{ env.LOCAL_DIR }}/functional" - gh-token: ${{ secrets.GITHUB_TOKEN }} - build-id: ${{ needs.preflight.outputs.BUILD_RUN_ID }} - ref-slug: ${{ needs.preflight.outputs.SOURCE_REF_SLUG }} # # zombienet-polkadot-functional-0013-systematic-chunk-recovery: @@ -1024,6 +996,35 @@ jobs: test: "functional::approved_peer_mixed_validators::approved_peer_mixed_validators_test" prefix: "polkadot" + # + # + zombienet-polkadot-functional-validator-disabling: + needs: [preflight] + if: ${{ (needs.preflight.outputs.changes_substrate || needs.preflight.outputs.changes_polkadot) && ! contains(needs.preflight.outputs.FLAKY_TESTS, 'zombienet-polkadot-functional-0010-validator-disabling') }} + runs-on: ${{ needs.preflight.outputs.ZOMBIENET_LARGE_RUNNER }} + timeout-minutes: 60 + container: + image: ${{ needs.preflight.outputs.ZOMBIENET_SDK_IMAGE }} + env: + 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 }}" + CUMULUS_IMAGE: "${{ needs.preflight.outputs.TEMP_IMAGES_BASE }}/polkadot-parachain-debug:${{ 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 }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: zombienet_test + uses: ./.github/actions/zombienet-sdk + with: + gh-token: ${{ secrets.GITHUB_TOKEN }} + build-id: ${{ needs.preflight.outputs.BUILD_RUN_ID }} + ref-slug: ${{ needs.preflight.outputs.SOURCE_REF_SLUG }} + test: "functional::validator_disabling::validator_disabling_test" + prefix: "polkadot" + # # zombienet-polkadot-shared-core-idle-parachain: diff --git a/polkadot/zombienet-sdk-tests/tests/functional/mod.rs b/polkadot/zombienet-sdk-tests/tests/functional/mod.rs index 6bc12935bc884..358657d625a41 100644 --- a/polkadot/zombienet-sdk-tests/tests/functional/mod.rs +++ b/polkadot/zombienet-sdk-tests/tests/functional/mod.rs @@ -8,3 +8,4 @@ mod duplicate_collations; mod shared_core_idle_parachain; mod spam_statement_distribution_requests; mod sync_backing; +mod validator_disabling; diff --git a/polkadot/zombienet-sdk-tests/tests/functional/validator_disabling.rs b/polkadot/zombienet-sdk-tests/tests/functional/validator_disabling.rs new file mode 100644 index 0000000000000..7d39fe3bd674b --- /dev/null +++ b/polkadot/zombienet-sdk-tests/tests/functional/validator_disabling.rs @@ -0,0 +1,164 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Test checks that misbehaving validators disabled. +use anyhow::anyhow; +use cumulus_zombienet_sdk_helpers::assert_para_throughput; +use polkadot_primitives::{ + BlockNumber, CandidateHash, DisputeState, SessionIndex, ValidatorId, ValidatorIndex, +}; +use serde_json::json; +use zombienet_sdk::{ + subxt::{OnlineClient, PolkadotConfig}, + NetworkConfigBuilder, +}; + +#[tokio::test(flavor = "multi_thread")] +async fn validator_disabling_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_builder = NetworkConfigBuilder::new() + .with_relaychain(|r| { + let r = r + .with_chain("westend-local") // Use westend-local so the disabling can take effect. + .with_default_command("polkadot") + .with_default_image(images.polkadot.as_str()) + .with_default_args(vec![("-lparachain=debug").into()]) + .with_genesis_overrides(json!({ + "configuration": { + "config": { + "scheduler_params": { + "group_rotation_frequency": 10, + "max_validators_per_core": 1 + }, + "needed_approvals": 2, + } + } + })) + // Adding malicious validator. + .with_node(|node| { + node.with_name("malus-validator") + .with_image( + std::env::var("MALUS_IMAGE") + .unwrap_or("docker.io/paritypr/malus".to_string()) + .as_str(), + ) + .with_command("malus") + .with_subcommand("suggest-garbage-candidate") + .with_args(vec![ + "-lMALUS=trace".into(), + // Without this the malus validator won't run on macOS. + "--insecure-validator-i-know-what-i-do".into(), + ]) + // Make it vulenrable so disabling really happens + .invulnerable(false) + }); + // Also honest validators. + let r = (0..3).fold(r, |acc, i| { + acc.with_node(|node| { + node.with_name(&format!("honest-validator-{i}")) + .with_args(vec![("-lparachain=debug,runtime::staking=debug".into())]) + .invulnerable(false) + }) + }); + r + }) + .with_parachain(|p| { + p.with_id(1000) + .with_default_command("adder-collator") + .cumulus_based(false) + .with_default_image(images.cumulus.as_str()) + .with_default_args(vec!["-lparachain=debug".into()]) + .with_collator(|n| n.with_name("alice")) + }) + .build() + .map_err(|e| { + let errors = e.into_iter().map(|e| e.to_string()).collect::>().join(" "); + anyhow!("config errors: {errors}") + })?; + + let spawn_fn = zombienet_sdk::environment::get_spawn_fn(); + log::info!("Spawning network"); + let network = spawn_fn(config_builder).await?; + + log::info!("Waiting for parablocks to be produced"); + let honest_validator = network.get_node("honest-validator-0")?; + let relay_client: OnlineClient = honest_validator.wait_client().await?; + assert_para_throughput( + &relay_client, + 20, + [(polkadot_primitives::Id::from(1000), 10..30)].into_iter().collect(), + ) + .await?; + + log::info!("Wait for a dispute to be initialized."); + let mut best_blocks = relay_client.blocks().subscribe_best().await?; + let mut dispute_session: u32 = u32::MAX; + let mut block_hash = None; + // Check next new block from the current best fork + while let Some(block) = best_blocks.next().await { + let current_hash = block?.hash(); + let disputes = relay_client + .runtime_api() + .at(current_hash) + .call_raw::)>>( + "ParachainHost_disputes", + None, + ) + .await?; + if let Some((session, _, _)) = disputes.first() { + block_hash = Some(current_hash); + dispute_session = *session; + break; + } + } + + assert_ne!(dispute_session, u32::MAX); + log::info!("Dispute initiated."); + let concluded_dispute_metric = + "polkadot_parachain_candidate_dispute_concluded{validity=\"invalid\"}"; + let parachain_candidate_dispute_metric = "parachain_candidate_disputes_total"; + // Check that we have at least one dispute + honest_validator + .wait_metric_with_timeout(parachain_candidate_dispute_metric, |d| d >= 1.0, 600_u64) + .await?; + // Check that we have at least one concluded dispute. + honest_validator + .wait_metric_with_timeout(concluded_dispute_metric, |d| d >= 1.0, 200_u64) + .await?; + + let disabled_validators = relay_client + .runtime_api() + .at(block_hash.unwrap()) + .call_raw::>("ParachainHost_disabled_validators", None) + .await?; + // We should have at least one disable validator. + assert!(!disabled_validators.is_empty()); + + let session_validators = relay_client + .runtime_api() + .at(block_hash.unwrap()) + .call_raw::>("ParachainHost_validators", None) + .await?; + // We have a single malicious node, hence the index of the malus-node is the first + // entry in the disabled validators list. + let disabled_node_public_address = &session_validators[(disabled_validators[0].0) as usize]; + + let disabled_node_public_key_hex = disabled_node_public_address + .clone() + .into_inner() + .0 + .iter() + .map(|byte| format!("{byte:02x}")) + .collect::(); + + let json_value: serde_json::Value = + serde_json::to_value(network.get_node("malus-validator")?.spec())?; + let malus_public_address = + json_value.pointer("/accounts/accounts/sr/public_key").unwrap().to_string(); + + assert_eq!(disabled_node_public_key_hex, malus_public_address.trim_matches('"')); + Ok(()) +} diff --git a/polkadot/zombienet_tests/functional/0010-validator-disabling.toml b/polkadot/zombienet_tests/functional/0010-validator-disabling.toml deleted file mode 100644 index 806f34d7f7670..0000000000000 --- a/polkadot/zombienet_tests/functional/0010-validator-disabling.toml +++ /dev/null @@ -1,41 +0,0 @@ -[settings] -timeout = 1000 -bootnode = true - -[relaychain.genesis.runtimeGenesis.patch.configuration.config] - needed_approvals = 2 - -[relaychain.genesis.runtimeGenesis.patch.configuration.config.scheduler_params] - max_validators_per_core = 1 - group_rotation_frequency = 10 - -[relaychain] -default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" -chain = "westend-local" # for the disabling to take an effect -default_command = "polkadot" - -[relaychain.default_resources] -limits = { memory = "4G", cpu = "2" } -requests = { memory = "2G", cpu = "1" } - - [[relaychain.node_groups]] - name = "honest-validator" - count = 3 - args = ["-lparachain=debug,runtime::staking=debug"] - - [[relaychain.node_groups]] - image = "{{MALUS_IMAGE}}" - name = "malus-validator" - command = "malus suggest-garbage-candidate" - args = ["-lMALUS=trace"] - count = 1 - -[[parachains]] -id = 1000 -cumulus_based = true - - [parachains.collator] - name = "alice" - command = "polkadot-parachain" - image = "{{CUMULUS_IMAGE}}" - args = ["-lparachain=debug"] diff --git a/polkadot/zombienet_tests/functional/0010-validator-disabling.zndsl b/polkadot/zombienet_tests/functional/0010-validator-disabling.zndsl deleted file mode 100644 index c810266102061..0000000000000 --- a/polkadot/zombienet_tests/functional/0010-validator-disabling.zndsl +++ /dev/null @@ -1,21 +0,0 @@ -Description: Test validator disabling effects -Network: ./0010-validator-disabling.toml -Creds: config - -# Ensure nodes are up and running -honest-validator: reports node_roles is 4 - -# Ensure parachain is registered -honest-validator: parachain 1000 is registered within 100 seconds - -# Ensure parachain made progress -honest-validator: parachain 1000 block height is at least 1 within 300 seconds - -# Wait for the dispute -honest-validator-1: reports parachain_candidate_disputes_total is at least 1 within 600 seconds - -# Disputes should conclude -honest-validator: reports polkadot_parachain_candidate_dispute_concluded{validity="invalid"} is at least 1 within 200 seconds - -# Wait for a few blocks for the disabling to take place. -honest-validator: log line contains "Disabled validators detected" within 180 seconds diff --git a/prdoc/pr_9128.prdoc b/prdoc/pr_9128.prdoc new file mode 100644 index 0000000000000..c606fe3969615 --- /dev/null +++ b/prdoc/pr_9128.prdoc @@ -0,0 +1,8 @@ +title: Rewrite validator disabling test with zombienet-sdk + +doc: + - audience: Node Dev + description: |- + Migrate validator disabling test to the new version of zombienet + +crates: []