diff --git a/.github/workflows/e2e-tests-main-devnet.yml b/.github/workflows/e2e-tests-main-devnet.yml index fd9047eb87..1dab717e3b 100644 --- a/.github/workflows/e2e-tests-main-devnet.yml +++ b/.github/workflows/e2e-tests-main-devnet.yml @@ -518,6 +518,21 @@ jobs: follow-up-finalization-check: true timeout-minutes: 15 + run-e2e-permissionless-ban: + needs: [ build-test-docker, build-test-client ] + name: Run permissionless ban test + runs-on: ubuntu-20.04 + steps: + - name: Checkout source code + uses: actions/checkout@v2 + + - name: Run e2e test + uses: ./.github/actions/run-e2e-test + with: + test-case: permissionless_ban + follow-up-finalization-check: true + timeout-minutes: 15 + run-e2e-version-upgrade: needs: [build-test-docker, build-test-client] name: Run basic (positive) version-upgrade test @@ -642,6 +657,7 @@ jobs: run-e2e-ban-counter-clearing, run-e2e-ban-threshold, run-e2e-version-upgrade, + run-e2e-permissionless-ban, # run-e2e-failing-version-upgrade, # run-e2e-version-upgrade-catchup, ] diff --git a/aleph-client/src/pallets/elections.rs b/aleph-client/src/pallets/elections.rs index b4b770cd65..cb9f1ef5bd 100644 --- a/aleph-client/src/pallets/elections.rs +++ b/aleph-client/src/pallets/elections.rs @@ -6,8 +6,10 @@ use crate::{ pallet_elections::pallet::Call::set_ban_config, primitives::{BanReason, CommitteeSeats, EraValidators}, }, - pallet_elections::pallet::Call::{ban_from_committee, change_validators}, - primitives::{BanConfig, BanInfo}, + pallet_elections::pallet::Call::{ + ban_from_committee, change_validators, set_elections_openness, + }, + primitives::{BanConfig, BanInfo, ElectionOpenness}, AccountId, BlockHash, Call::Elections, Connection, RootConnection, SudoCall, TxStatus, @@ -68,6 +70,11 @@ pub trait ElectionsSudoApi { ban_reason: Vec, status: TxStatus, ) -> anyhow::Result; + async fn set_election_openness( + &self, + mode: ElectionOpenness, + status: TxStatus, + ) -> anyhow::Result; } #[async_trait::async_trait] @@ -212,4 +219,14 @@ impl ElectionsSudoApi for RootConnection { }); self.sudo_unchecked(call, status).await } + + async fn set_election_openness( + &self, + mode: ElectionOpenness, + status: TxStatus, + ) -> anyhow::Result { + let call = Elections(set_elections_openness { openness: mode }); + + self.sudo_unchecked(call, status).await + } } diff --git a/e2e-tests/src/test/ban.rs b/e2e-tests/src/test/ban.rs index c7b8869ff1..b1ddebd621 100644 --- a/e2e-tests/src/test/ban.rs +++ b/e2e-tests/src/test/ban.rs @@ -1,10 +1,12 @@ +use std::collections::HashSet; + use aleph_client::{ pallets::{ elections::{ElectionsApi, ElectionsSudoApi}, session::SessionApi, - staking::StakingApi, + staking::{StakingApi, StakingUserApi}, }, - primitives::{BanInfo, BanReason}, + primitives::{BanInfo, BanReason, CommitteeSeats, ElectionOpenness}, sp_core::bounded::bounded_vec::BoundedVec, waiting::{BlockStatus, WaitingExt}, SignedConnection, TxStatus, @@ -16,7 +18,7 @@ use primitives::{ }; use crate::{ - accounts::{get_validator_seed, NodeKeys}, + accounts::{account_ids_from_keys, get_validator_seed, NodeKeys}, ban::{ check_ban_config, check_ban_event, check_ban_info_for_validator, check_underperformed_count_for_sessions, check_underperformed_validator_reason, @@ -24,6 +26,7 @@ use crate::{ }, config, rewards::set_invalid_keys_for_validator, + validators::get_test_validators, }; const SESSIONS_TO_CHECK: SessionCount = 5; @@ -50,6 +53,17 @@ async fn disable_validator(validator_address: &str, validator_seed: u32) -> anyh set_invalid_keys_for_validator(&connection_to_disable).await } +async fn signed_connection_for_disabled_controller() -> SignedConnection { + let validator_seed = get_validator_seed(VALIDATOR_TO_DISABLE_OVERALL_INDEX); + let stash_controller = NodeKeys::from(validator_seed); + let controller_key_to_disable = stash_controller.controller; + SignedConnection::new( + NODE_TO_DISABLE_ADDRESS.to_string(), + controller_key_to_disable, + ) + .await +} + /// Runs a chain, sets up a committee and validators. Sets an incorrect key for one of the /// validators. Waits for the offending validator to hit the ban threshold of sessions without /// producing blocks. Verifies that the offending validator has in fact been banned out for the @@ -272,6 +286,83 @@ pub async fn clearing_session_count() -> anyhow::Result<()> { Ok(()) } +/// Setup reserved validators, non_reserved are set to empty vec. Set ban config ban_period to 2. +/// Set Openness to Permissionless. +/// Ban manually one validator. Check if the banned validator is out of the non_reserved and is back +/// after ban period. +#[tokio::test] +pub async fn permissionless_ban() -> anyhow::Result<()> { + let config = config::setup_test(); + let root_connection = config.create_root_connection().await; + + let validator_keys = get_test_validators(config); + let reserved_validators = account_ids_from_keys(&validator_keys.reserved); + let non_reserved_validators = account_ids_from_keys(&validator_keys.non_reserved); + + let seats = CommitteeSeats { + reserved_seats: 2, + non_reserved_seats: 2, + }; + + let validator_to_ban = + &non_reserved_validators[VALIDATOR_TO_DISABLE_NON_RESERVED_INDEX as usize]; + let mut non_reserved_without_banned = non_reserved_validators.to_vec(); + non_reserved_without_banned.remove(VALIDATOR_TO_DISABLE_NON_RESERVED_INDEX as usize); + + let ban_period = 2; + root_connection + .change_validators( + Some(reserved_validators), + Some(non_reserved_validators.clone()), + Some(seats), + TxStatus::InBlock, + ) + .await?; + root_connection + .set_election_openness(ElectionOpenness::Permissionless, TxStatus::InBlock) + .await?; + root_connection + .set_ban_config(None, None, None, Some(ban_period), TxStatus::InBlock) + .await?; + root_connection + .ban_from_committee(validator_to_ban.clone(), vec![], TxStatus::InBlock) + .await?; + root_connection + .connection + .wait_for_n_eras(2, BlockStatus::Finalized) + .await; + + let without_banned = HashSet::<_>::from_iter(non_reserved_without_banned); + let non_reserved = HashSet::<_>::from_iter( + root_connection + .connection + .get_current_era_validators(None) + .await + .non_reserved, + ); + assert_eq!(without_banned, non_reserved); + + let signed_connection = signed_connection_for_disabled_controller().await; + // validate again + signed_connection.validate(0, TxStatus::InBlock).await?; + root_connection + .connection + .wait_for_n_eras(2, BlockStatus::Finalized) + .await; + let expected_non_reserved = HashSet::<_>::from_iter(non_reserved_validators); + let non_reserved = HashSet::<_>::from_iter( + root_connection + .connection + .get_current_era_validators(None) + .await + .non_reserved, + ); + + assert_eq!(expected_non_reserved, non_reserved); + + Ok(()) +} + /// Runs a chain, sets up a committee and validators. Changes the ban config to require 100% /// performance. Checks that each validator has all the sessions in which they were chosen for the /// committee marked as ones in which they underperformed. diff --git a/e2e-tests/src/test/mod.rs b/e2e-tests/src/test/mod.rs index 052e5184ea..392a56f4e5 100644 --- a/e2e-tests/src/test/mod.rs +++ b/e2e-tests/src/test/mod.rs @@ -1,4 +1,6 @@ -pub use ban::{ban_automatic, ban_manual, ban_threshold, clearing_session_count}; +pub use ban::{ + ban_automatic, ban_manual, ban_threshold, clearing_session_count, permissionless_ban, +}; pub use electing_validators::authorities_are_staking; pub use era_payout::era_payouts_calculated_correctly; pub use era_validators::era_validators;