diff --git a/.github/workflows/e2e-tests-main-devnet.yml b/.github/workflows/e2e-tests-main-devnet.yml index 66c7e62f54..2c4bf8d040 100644 --- a/.github/workflows/e2e-tests-main-devnet.yml +++ b/.github/workflows/e2e-tests-main-devnet.yml @@ -402,9 +402,9 @@ jobs: follow-up-finalization-check: true timeout-minutes: 15 - run-e2e-kick-out-automatic: + run-e2e-ban-automatic: needs: [build-test-docker, build-test-client] - name: Run kick out automatic test + name: Run ban automatic test runs-on: ubuntu-20.04 steps: - name: Checkout source code @@ -413,7 +413,7 @@ jobs: - name: Run e2e test uses: ./.github/actions/run-e2e-test with: - test-case: kick_out_automatic + test-case: ban_automatic follow-up-finalization-check: true timeout-minutes: 15 @@ -455,7 +455,7 @@ jobs: run-e2e-rewards-change-stake-force-new-era, run-e2e-rewards-points-basic, run-e2e-authorities-are-staking, - run-e2e-kick-out-automatic, + run-e2e-ban-automatic, run-e2e-version-upgrade, ] name: Check e2e test suite completion diff --git a/aleph-client/Cargo.lock b/aleph-client/Cargo.lock index dc74ac7694..e290c25710 100644 --- a/aleph-client/Cargo.lock +++ b/aleph-client/Cargo.lock @@ -98,7 +98,7 @@ dependencies = [ [[package]] name = "aleph_client" -version = "1.10.0" +version = "1.11.0" dependencies = [ "ac-node-api", "ac-primitives", diff --git a/aleph-client/Cargo.toml b/aleph-client/Cargo.toml index f3f0e8b9f6..7f71b82ebe 100644 --- a/aleph-client/Cargo.toml +++ b/aleph-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aleph_client" -version = "1.10.0" +version = "1.11.0" edition = "2021" license = "Apache 2.0" diff --git a/aleph-client/src/elections.rs b/aleph-client/src/elections.rs index 282ffde26b..31e8865b48 100644 --- a/aleph-client/src/elections.rs +++ b/aleph-client/src/elections.rs @@ -1,7 +1,4 @@ -use primitives::{ - CommitteeKickOutConfig, CommitteeSeats, EraValidators, KickOutReason, SessionCount, - SessionIndex, -}; +use primitives::{BanConfig, BanInfo, CommitteeSeats, EraValidators, SessionCount, SessionIndex}; use sp_core::H256; use crate::{get_session_first_block, AccountId, ReadStorage}; @@ -66,8 +63,8 @@ pub fn get_era_validators( connection.read_storage_value_at_block(PALLET, "CurrentEraValidators", Some(block_hash)) } -pub fn get_committee_kick_out_config(connection: &C) -> CommitteeKickOutConfig { - connection.read_storage_value(PALLET, "CommitteeKickOutConfig") +pub fn get_ban_config(connection: &C) -> BanConfig { + connection.read_storage_value(PALLET, "BanConfig") } pub fn get_underperformed_validator_session_count( @@ -84,9 +81,9 @@ pub fn get_underperformed_validator_session_count( .unwrap_or(0) } -pub fn get_kick_out_reason_for_validator( +pub fn get_ban_reason_for_validator( connection: &C, account_id: &AccountId, -) -> Option { - connection.read_storage_map(PALLET, "ToBeKickedOutFromCommittee", account_id, None) +) -> Option { + connection.read_storage_map(PALLET, "Banned", account_id, None) } diff --git a/aleph-client/src/lib.rs b/aleph-client/src/lib.rs index 63e94cb6e5..8467675ae7 100644 --- a/aleph-client/src/lib.rs +++ b/aleph-client/src/lib.rs @@ -6,9 +6,9 @@ pub use balances::total_issuance; use codec::{Decode, Encode}; pub use debug::print_storages; pub use elections::{ - get_committee_kick_out_config, get_committee_seats, get_current_era_non_reserved_validators, - get_current_era_reserved_validators, get_current_era_validators, get_era_validators, - get_kick_out_reason_for_validator, get_next_era_committee_seats, + get_ban_config, get_ban_reason_for_validator, get_committee_seats, + get_current_era_non_reserved_validators, get_current_era_reserved_validators, + get_current_era_validators, get_era_validators, get_next_era_committee_seats, get_next_era_non_reserved_validators, get_next_era_reserved_validators, get_next_era_validators, get_underperformed_validator_session_count, get_validator_block_count, }; @@ -33,9 +33,9 @@ pub use staking::{ batch_bond as staking_batch_bond, batch_nominate as staking_batch_nominate, bond as staking_bond, bond_extra_stake, bonded as staking_bonded, chill_validator as staking_chill_validator, chill_validators as staking_chill_validators, - force_new_era as staking_force_new_era, get_current_era, get_era, get_era_reward_points, - get_eras_stakers_storage_key, get_exposure, get_minimum_validator_count, get_payout_for_era, - get_sessions_per_era, get_stakers_as_storage_keys, + force_new_era as staking_force_new_era, get_active_era, get_current_era, get_era, + get_era_reward_points, get_eras_stakers_storage_key, get_exposure, get_minimum_validator_count, + get_payout_for_era, get_sessions_per_era, get_stakers_as_storage_keys, get_stakers_as_storage_keys_from_storage_key, ledger as staking_ledger, multi_bond as staking_multi_bond, nominate as staking_nominate, payout_stakers, payout_stakers_and_assert_locked_balance, set_staking_limits as staking_set_staking_limits, diff --git a/aleph-client/src/staking.rs b/aleph-client/src/staking.rs index 5e388605bd..f5b5f443a6 100644 --- a/aleph-client/src/staking.rs +++ b/aleph-client/src/staking.rs @@ -106,11 +106,11 @@ pub fn wait_for_full_era_completion(connection: &C) -> anyhow::R // staking works in such a way, that when we request a controller to be a validator in era N, // then the changes are applied in the era N+1 (so the new validator is receiving points in N+1), // so that we need N+1 to finish in order to claim the reward in era N+2 for the N+1 era - wait_for_era_completion(connection, get_current_era(connection) + 2) + wait_for_era_completion(connection, get_active_era(connection) + 2) } pub fn wait_for_next_era(connection: &C) -> anyhow::Result { - wait_for_era_completion(connection, get_current_era(connection) + 1) + wait_for_era_completion(connection, get_active_era(connection) + 1) } pub fn wait_for_at_least_era( @@ -154,10 +154,18 @@ pub fn get_era(connection: &C, block: Option) -> EraInde .expect("ActiveEra is empty in the storage!") } -pub fn get_current_era(connection: &C) -> EraIndex { +pub fn get_active_era(connection: &C) -> EraIndex { get_era(connection, None) } +pub fn get_current_era(connection: &C) -> EraIndex { + connection + .as_connection() + .get_storage_value(PALLET, "CurrentEra", None) + .expect("Failed to obtain CurrentEra extrinsic!") + .expect("CurrentEra is empty in the storage!") +} + pub fn payout_stakers( stash_connection: &SignedConnection, stash_account: &AccountId, diff --git a/benches/payout-stakers/Cargo.lock b/benches/payout-stakers/Cargo.lock index 4bab4ade56..8d216c22b4 100644 --- a/benches/payout-stakers/Cargo.lock +++ b/benches/payout-stakers/Cargo.lock @@ -98,7 +98,7 @@ dependencies = [ [[package]] name = "aleph_client" -version = "1.9.0" +version = "1.11.0" dependencies = [ "ac-node-api", "ac-primitives", diff --git a/bin/cliain/Cargo.lock b/bin/cliain/Cargo.lock index f8a4073fe9..a32dca162d 100644 --- a/bin/cliain/Cargo.lock +++ b/bin/cliain/Cargo.lock @@ -98,7 +98,7 @@ dependencies = [ [[package]] name = "aleph_client" -version = "1.9.0" +version = "1.11.0" dependencies = [ "ac-node-api", "ac-primitives", diff --git a/bin/node/src/chain_spec.rs b/bin/node/src/chain_spec.rs index 5beca35c44..d654ebb794 100644 --- a/bin/node/src/chain_spec.rs +++ b/bin/node/src/chain_spec.rs @@ -379,7 +379,7 @@ fn generate_genesis_config( reserved_validators: accounts_config.members.clone(), non_reserved_validators: vec![], committee_seats: Default::default(), - committee_kick_out_config: Default::default(), + committee_ban_config: Default::default(), }, session: SessionConfig { keys: accounts_config.keys, diff --git a/bin/runtime/src/lib.rs b/bin/runtime/src/lib.rs index 8dfa9fecd8..b929d4b36b 100644 --- a/bin/runtime/src/lib.rs +++ b/bin/runtime/src/lib.rs @@ -36,7 +36,7 @@ pub use primitives::Balance; use primitives::{ staking::MAX_NOMINATORS_REWARDED_PER_VALIDATOR, wrap_methods, ApiError as AlephApiError, AuthorityId as AlephId, SessionAuthorityData, Version as FinalityVersion, ADDRESSES_ENCODING, - DEFAULT_KICK_OUT_REASON_LENGTH, DEFAULT_SESSIONS_PER_ERA, DEFAULT_SESSION_PERIOD, + DEFAULT_BAN_REASON_LENGTH, DEFAULT_SESSIONS_PER_ERA, DEFAULT_SESSION_PERIOD, MILLISECS_PER_BLOCK, TOKEN, }; use sp_api::impl_runtime_apis; @@ -108,10 +108,10 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("aleph-node"), impl_name: create_runtime_str!("aleph-node"), authoring_version: 1, - spec_version: 37, + spec_version: 38, impl_version: 1, apis: RUNTIME_API_VERSIONS, - transaction_version: 12, + transaction_version: 13, state_version: 0, }; @@ -326,7 +326,7 @@ impl_opaque_keys! { parameter_types! { pub const SessionPeriod: u32 = DEFAULT_SESSION_PERIOD; - pub const MaximumKickOutReasonLength: u32 = DEFAULT_KICK_OUT_REASON_LENGTH; + pub const MaximumBanReasonLength: u32 = DEFAULT_BAN_REASON_LENGTH; } impl pallet_elections::Config for Runtime { @@ -337,7 +337,7 @@ impl pallet_elections::Config for Runtime { type SessionPeriod = SessionPeriod; type SessionManager = pallet_session::historical::NoteHistoricalRoot; type ValidatorRewardsHandler = Staking; - type MaximumKickOutReasonLength = MaximumKickOutReasonLength; + type MaximumBanReasonLength = MaximumBanReasonLength; } impl pallet_randomness_collective_flip::Config for Runtime {} diff --git a/e2e-tests/Cargo.lock b/e2e-tests/Cargo.lock index c63549b634..b7d7d8d828 100644 --- a/e2e-tests/Cargo.lock +++ b/e2e-tests/Cargo.lock @@ -98,7 +98,7 @@ dependencies = [ [[package]] name = "aleph-e2e-client" -version = "0.5.0" +version = "0.6.0" dependencies = [ "aleph_client", "anyhow", @@ -121,7 +121,7 @@ dependencies = [ [[package]] name = "aleph_client" -version = "1.10.0" +version = "1.11.0" dependencies = [ "ac-node-api", "ac-primitives", diff --git a/e2e-tests/Cargo.toml b/e2e-tests/Cargo.toml index b42a4b1090..32429f8f96 100644 --- a/e2e-tests/Cargo.toml +++ b/e2e-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aleph-e2e-client" -version = "0.5.0" +version = "0.6.0" edition = "2021" license = "Apache 2.0" diff --git a/e2e-tests/src/kick_out.rs b/e2e-tests/src/ban.rs similarity index 64% rename from e2e-tests/src/kick_out.rs rename to e2e-tests/src/ban.rs index 62d644298c..c087b322e1 100644 --- a/e2e-tests/src/kick_out.rs +++ b/e2e-tests/src/ban.rs @@ -1,13 +1,11 @@ use aleph_client::{ - change_validators, get_committee_kick_out_config, get_kick_out_reason_for_validator, + change_validators, get_ban_config, get_ban_reason_for_validator, get_underperformed_validator_session_count, wait_for_event, wait_for_full_era_completion, AccountId, AnyConnection, RootConnection, XtStatus, }; use codec::Decode; use log::info; -use primitives::{ - CommitteeKickOutConfig, CommitteeSeats, EraValidators, KickOutReason, SessionCount, -}; +use primitives::{BanConfig, BanInfo, CommitteeSeats, EraValidators, SessionCount}; use sp_runtime::Perbill; use crate::{accounts::account_ids_from_keys, validators::get_test_validators, Config}; @@ -57,28 +55,28 @@ pub fn check_validators( era_validators } -pub fn check_committee_kick_out_config( +pub fn check_ban_config( connection: &RootConnection, expected_minimal_expected_performance: Perbill, expected_session_count_threshold: SessionCount, expected_clean_session_counter_delay: SessionCount, -) -> CommitteeKickOutConfig { - let committee_kick_out_config = get_committee_kick_out_config(connection); +) -> BanConfig { + let ban_config = get_ban_config(connection); assert_eq!( - committee_kick_out_config.minimal_expected_performance, + ban_config.minimal_expected_performance, expected_minimal_expected_performance ); assert_eq!( - committee_kick_out_config.underperformed_session_count_threshold, + ban_config.underperformed_session_count_threshold, expected_session_count_threshold ); assert_eq!( - committee_kick_out_config.clean_session_counter_delay, + ban_config.clean_session_counter_delay, expected_clean_session_counter_delay ); - committee_kick_out_config + ban_config } pub fn check_underperformed_validator_session_count( @@ -100,36 +98,29 @@ pub fn check_underperformed_validator_session_count( pub fn check_underperformed_validator_reason( connection: &C, validator: &AccountId, - expected_reason: Option<&KickOutReason>, -) -> Option { - let validator_kick_out_reason = get_kick_out_reason_for_validator(connection, validator); + expected_info: Option<&BanInfo>, +) -> Option { + let validator_ban_info = get_ban_reason_for_validator(connection, validator); - assert_eq!(validator_kick_out_reason.as_ref(), expected_reason); + assert_eq!(validator_ban_info.as_ref(), expected_info); - validator_kick_out_reason + validator_ban_info } #[derive(Debug, Decode, Clone)] -pub struct KickOutEvent { - kicked_out_validators: Vec<(AccountId, KickOutReason)>, +pub struct BanEvent { + banned_validators: Vec<(AccountId, BanInfo)>, } -pub fn check_kick_out_event( +pub fn check_ban_event( connection: &C, - expected_kicked_out_validators: &[(AccountId, KickOutReason)], -) -> anyhow::Result { - let event = wait_for_event( - connection, - ("Elections", "KickOutValidators"), - |e: KickOutEvent| { - info!( - "Received KickOutValidators event: {:?}", - e.kicked_out_validators - ); - assert_eq!(e.kicked_out_validators, expected_kicked_out_validators); - true - }, - )?; + expected_banned_validators: &[(AccountId, BanInfo)], +) -> anyhow::Result { + let event = wait_for_event(connection, ("Elections", "BanValidators"), |e: BanEvent| { + info!("Received BanValidators event: {:?}", e.banned_validators); + assert_eq!(e.banned_validators, expected_banned_validators); + true + })?; Ok(event) } diff --git a/e2e-tests/src/cases.rs b/e2e-tests/src/cases.rs index 03b5235e46..61e8f68f5c 100644 --- a/e2e-tests/src/cases.rs +++ b/e2e-tests/src/cases.rs @@ -2,14 +2,14 @@ use crate::{ config::Config, test::{ authorities_are_staking as test_authorities_are_staking, - batch_transactions as test_batch_transactions, + ban_automatic as test_ban_automatic, batch_transactions as test_batch_transactions, change_stake_and_force_new_era as test_change_stake_and_force_new_era, change_validators as test_change_validators, channeling_fee_and_tip as test_channeling_fee_and_tip, disable_node as test_disable_node, era_payouts_calculated_correctly as test_era_payout, era_validators as test_era_validators, fee_calculation as test_fee_calculation, finalization as test_finalization, - force_new_era as test_force_new_era, kick_out_automatic as test_kick_out_automatic, - points_basic as test_points_basic, points_stake_change as test_points_stake_change, + force_new_era as test_force_new_era, points_basic as test_points_basic, + points_stake_change as test_points_stake_change, schedule_version_change as test_schedule_version_change, staking_era_payouts as test_staking_era_payouts, staking_new_validator as test_staking_new_validator, token_transfer as test_token_transfer, @@ -57,6 +57,6 @@ pub fn possible_test_cases() -> PossibleTestCases { "authorities_are_staking", test_authorities_are_staking as TestCase, ), - ("kick_out_automatic", test_kick_out_automatic as TestCase), + ("ban_automatic", test_ban_automatic as TestCase), ] } diff --git a/e2e-tests/src/lib.rs b/e2e-tests/src/lib.rs index 86cbb989af..b5f65039a1 100644 --- a/e2e-tests/src/lib.rs +++ b/e2e-tests/src/lib.rs @@ -2,10 +2,10 @@ pub use cases::{possible_test_cases, PossibleTestCases}; pub use config::Config; mod accounts; +mod ban; mod cases; mod config; mod elections; -mod kick_out; mod rewards; mod test; mod transfer; diff --git a/e2e-tests/src/test/kick_out.rs b/e2e-tests/src/test/ban.rs similarity index 65% rename from e2e-tests/src/test/kick_out.rs rename to e2e-tests/src/test/ban.rs index d50dc2a071..261eeccb1c 100644 --- a/e2e-tests/src/test/kick_out.rs +++ b/e2e-tests/src/test/ban.rs @@ -1,18 +1,18 @@ use aleph_client::{ - get_current_era_validators, get_current_session, wait_for_at_least_session, SignedConnection, + get_current_era, get_current_era_validators, get_current_session, wait_for_at_least_session, + SignedConnection, }; use log::info; use primitives::{ - KickOutReason, SessionCount, DEFAULT_CLEAN_SESSION_COUNTER_DELAY, - DEFAULT_KICK_OUT_MINIMAL_EXPECTED_PERFORMANCE, DEFAULT_KICK_OUT_SESSION_COUNT_THRESHOLD, + BanInfo, BanReason, SessionCount, DEFAULT_BAN_MINIMAL_EXPECTED_PERFORMANCE, + DEFAULT_BAN_SESSION_COUNT_THRESHOLD, DEFAULT_CLEAN_SESSION_COUNTER_DELAY, }; use crate::{ accounts::{get_validator_seed, NodeKeys}, - kick_out::{ - check_committee_kick_out_config, check_kick_out_event, - check_underperformed_validator_reason, check_underperformed_validator_session_count, - check_validators, setup_test, + ban::{ + check_ban_config, check_ban_event, check_underperformed_validator_reason, + check_underperformed_validator_session_count, check_validators, setup_test, }, rewards::set_invalid_keys_for_validator, Config, @@ -22,13 +22,13 @@ const VALIDATOR_TO_DISABLE_NON_RESERVED_INDEX: u32 = 0; const VALIDATOR_TO_DISABLE_OVERALL_INDEX: u32 = 2; // Address for //2 (Node2). Depends on the infrastructure setup. const NODE_TO_DISABLE_ADDRESS: &str = "127.0.0.1:9945"; -const SESSIONS_TO_MEET_KICK_OUT_THRESHOLD: SessionCount = 4; +const SESSIONS_TO_MEET_BAN_THRESHOLD: SessionCount = 4; /// 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 kick-out threshold of sessions without -/// producing blocks. Verifies that the offending validator has in fact been kicked out for 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 /// appropriate reason. -pub fn kick_out_automatic(config: &Config) -> anyhow::Result<()> { +pub fn ban_automatic(config: &Config) -> anyhow::Result<()> { let (root_connection, reserved_validators, non_reserved_validators) = setup_test(config)?; // Check current era validators. @@ -39,10 +39,10 @@ pub fn kick_out_automatic(config: &Config) -> anyhow::Result<()> { get_current_era_validators, ); - check_committee_kick_out_config( + check_ban_config( &root_connection, - DEFAULT_KICK_OUT_MINIMAL_EXPECTED_PERFORMANCE, - DEFAULT_KICK_OUT_SESSION_COUNT_THRESHOLD, + DEFAULT_BAN_MINIMAL_EXPECTED_PERFORMANCE, + DEFAULT_BAN_SESSION_COUNT_THRESHOLD, DEFAULT_CLEAN_SESSION_COUNTER_DELAY, ); @@ -68,28 +68,28 @@ pub fn kick_out_automatic(config: &Config) -> anyhow::Result<()> { wait_for_at_least_session( &root_connection, - current_session + SESSIONS_TO_MEET_KICK_OUT_THRESHOLD, + current_session + SESSIONS_TO_MEET_BAN_THRESHOLD, )?; // The session count for underperforming validators is reset to 0 immediately on reaching the // threshold. check_underperformed_validator_session_count(&root_connection, validator_to_disable, &0); - let expected_kick_out_reason = - KickOutReason::InsufficientUptime(DEFAULT_KICK_OUT_SESSION_COUNT_THRESHOLD); + let reason = BanReason::InsufficientUptime(DEFAULT_BAN_SESSION_COUNT_THRESHOLD); + let start = get_current_era(&root_connection) + 1; + let expected_ban_info = BanInfo { reason, start }; check_underperformed_validator_reason( &root_connection, validator_to_disable, - Some(&expected_kick_out_reason), + Some(&expected_ban_info), ); let expected_non_reserved = &non_reserved_validators[(VALIDATOR_TO_DISABLE_NON_RESERVED_INDEX + 1) as usize..]; - let expected_kicked_out_validators = - vec![(validator_to_disable.clone(), expected_kick_out_reason)]; - check_kick_out_event(&root_connection, &expected_kicked_out_validators)?; + let expected_banned_validators = vec![(validator_to_disable.clone(), expected_ban_info)]; + check_ban_event(&root_connection, &expected_banned_validators)?; // Check current validators. check_validators( @@ -99,7 +99,5 @@ pub fn kick_out_automatic(config: &Config) -> anyhow::Result<()> { get_current_era_validators, ); - check_underperformed_validator_reason(&root_connection, validator_to_disable, None); - Ok(()) } diff --git a/e2e-tests/src/test/era_payout.rs b/e2e-tests/src/test/era_payout.rs index e4b57286b8..11c3a191b4 100644 --- a/e2e-tests/src/test/era_payout.rs +++ b/e2e-tests/src/test/era_payout.rs @@ -1,5 +1,5 @@ use aleph_client::{ - create_connection, get_current_era, get_payout_for_era, staking_force_new_era, + create_connection, get_active_era, get_payout_for_era, staking_force_new_era, wait_for_next_era, wait_for_session, ReadStorage, XtStatus, }; use primitives::{ @@ -30,28 +30,28 @@ fn payout_within_two_block_delta(expected_payout: Balance, payout: Balance) { } fn wait_to_second_era(connection: &C) -> EraIndex { - let current_era = get_current_era(connection); - if current_era < 2 { + let active_era = get_active_era(connection); + if active_era < 2 { wait_for_next_era(connection).expect("Era is active"); wait_for_next_era(connection).expect("Era is active"); } - get_current_era(connection) + get_active_era(connection) } fn force_era_payout(config: &Config) -> anyhow::Result<()> { let root_connection = config.create_root_connection(); - let current_era = wait_to_second_era(&root_connection); + let active_era = wait_to_second_era(&root_connection); wait_for_next_era(&root_connection)?; - let current_era = current_era + 1; + let active_era = active_era + 1; - let starting_session = current_era * DEFAULT_SESSIONS_PER_ERA; + let starting_session = active_era * DEFAULT_SESSIONS_PER_ERA; wait_for_session(&root_connection, starting_session + 1)?; // new era will start in the session `starting_session + 3` staking_force_new_era(&root_connection, XtStatus::InBlock); wait_for_session(&root_connection, starting_session + 3)?; - let payout = get_payout_for_era(&root_connection, current_era); + let payout = get_payout_for_era(&root_connection, active_era); let expected_payout = era_payout((3 * DEFAULT_SESSION_PERIOD) as u64 * MILLISECS_PER_BLOCK).0; payout_within_two_block_delta(expected_payout, payout); @@ -62,8 +62,8 @@ fn force_era_payout(config: &Config) -> anyhow::Result<()> { fn normal_era_payout(config: &Config) -> anyhow::Result<()> { let connection = create_connection(&config.node); - let current_era = wait_to_second_era(&connection); - let payout = get_payout_for_era(&connection, current_era - 1); + let active_era = wait_to_second_era(&connection); + let payout = get_payout_for_era(&connection, active_era - 1); let expected_payout = era_payout( (DEFAULT_SESSIONS_PER_ERA * DEFAULT_SESSION_PERIOD) as u64 * MILLISECS_PER_BLOCK, ) diff --git a/e2e-tests/src/test/mod.rs b/e2e-tests/src/test/mod.rs index 8295c7eebe..736fe43559 100644 --- a/e2e-tests/src/test/mod.rs +++ b/e2e-tests/src/test/mod.rs @@ -1,9 +1,9 @@ +pub use ban::ban_automatic; pub use electing_validators::authorities_are_staking; pub use era_payout::era_payouts_calculated_correctly; pub use era_validators::era_validators; pub use fee::fee_calculation; pub use finalization::finalization; -pub use kick_out::kick_out_automatic; pub use rewards::{ change_stake_and_force_new_era, disable_node, force_new_era, points_basic, points_stake_change, }; @@ -15,12 +15,12 @@ pub use validators_change::change_validators; pub use validators_rotate::validators_rotate; pub use version_upgrade::schedule_version_change; +mod ban; mod electing_validators; mod era_payout; mod era_validators; mod fee; mod finalization; -mod kick_out; mod rewards; mod staking; mod transfer; diff --git a/e2e-tests/src/test/rewards.rs b/e2e-tests/src/test/rewards.rs index 20a5e2392d..4e749e8fd6 100644 --- a/e2e-tests/src/test/rewards.rs +++ b/e2e-tests/src/test/rewards.rs @@ -1,5 +1,5 @@ use aleph_client::{ - get_current_era, get_current_session, staking_force_new_era, wait_for_full_era_completion, + get_active_era, get_current_session, staking_force_new_era, wait_for_full_era_completion, wait_for_next_era, wait_for_session, AccountId, SignedConnection, XtStatus, }; use log::info; @@ -161,11 +161,11 @@ pub fn force_new_era(config: &Config) -> anyhow::Result<()> { staking_force_new_era(&root_connection, XtStatus::Finalized); wait_for_session(&connection, start_session + 2)?; - let current_era = get_current_era(&connection); + let active_era = get_active_era(&connection); let current_session = get_current_session(&connection); info!( "After ForceNewEra | era: {}, session: {}", - current_era, current_session + active_era, current_session ); check_points_after_force_new_era( @@ -208,11 +208,11 @@ pub fn change_stake_and_force_new_era(config: &Config) -> anyhow::Result<()> { staking_force_new_era(&root_connection, XtStatus::Finalized); wait_for_session(&connection, start_session + 2)?; - let current_era = get_current_era(&connection); + let active_era = get_active_era(&connection); let current_session = get_current_session(&connection); info!( "After ForceNewEra | era: {}, session: {}", - current_era, current_session + active_era, current_session ); check_points_after_force_new_era( diff --git a/flooder/Cargo.lock b/flooder/Cargo.lock index ae2cef88cf..39ec0dda25 100644 --- a/flooder/Cargo.lock +++ b/flooder/Cargo.lock @@ -98,7 +98,7 @@ dependencies = [ [[package]] name = "aleph_client" -version = "1.9.0" +version = "1.11.0" dependencies = [ "ac-node-api", "ac-primitives", diff --git a/pallets/elections/src/impls.rs b/pallets/elections/src/impls.rs index 1080a0eff4..ee7585b478 100644 --- a/pallets/elections/src/impls.rs +++ b/pallets/elections/src/impls.rs @@ -3,10 +3,7 @@ use frame_support::{ log::{debug, info}, pallet_prelude::Get, }; -use primitives::{ - CommitteeKickOutConfig as CommitteeKickOutConfigStruct, CommitteeSeats, EraValidators, - KickOutReason, -}; +use primitives::{BanConfig as BanConfigStruct, BanInfo, BanReason, CommitteeSeats, EraValidators}; use sp_runtime::Perbill; use sp_staking::{EraIndex, SessionIndex}; use sp_std::{ @@ -16,10 +13,9 @@ use sp_std::{ use crate::{ traits::{EraInfoProvider, SessionInfoProvider, ValidatorRewardsHandler}, - CommitteeKickOutConfig, CommitteeSize, Config, CurrentEraValidators, NextEraCommitteeSize, + BanConfig, Banned, CommitteeSize, Config, CurrentEraValidators, NextEraCommitteeSize, NextEraNonReservedValidators, NextEraReservedValidators, Pallet, SessionValidatorBlockCount, - ToBeKickedOutFromCommittee, UnderperformedValidatorSessionCount, ValidatorEraTotalReward, - ValidatorTotalRewards, + UnderperformedValidatorSessionCount, ValidatorEraTotalReward, ValidatorTotalRewards, }; const MAX_REWARD: u32 = 1_000_000_000; @@ -32,12 +28,15 @@ pub const LENIENT_THRESHOLD: Perquintill = Perquintill::from_percent(90); /// * Based on block count we might mark the session for a given validator as underperformed /// * We update rewards and clear block count for the session `S`. /// 3. `start_session(S + 1)` is called. -/// * if session `S+1` starts new era we populate totals. -/// * if session `S+1` % [`CommitteeKickOutConfig::clean_session_counter_delay`] == 0, we +/// * if session `S+1` starts new era we populate totals and unban all validators whose ban expired. +/// * if session `S+1` % [`BanConfig::clean_session_counter_delay`] == 0, we /// clean up underperformed session counter /// 4. `new_session(S + 2)` is called. /// * If session `S+2` starts new era: -/// * during elections, we kick out underperforming validators from non_reserved set, +/// * during elections, we choose validators eligible for elections depending on the openness of the process +/// * `permsionless`: all validators that bonded sufficient amount are chosen +/// * `permissioned`: we choose only validators from allow lists +/// * in both cases, we exclude banned validators from the elections /// * then we update the reserved and non reserved validators. /// * We rotate the validators for session `S + 2` using the information about reserved and non reserved validators. /// @@ -226,6 +225,10 @@ where ) } + pub fn ban_expired(start: EraIndex, period: EraIndex, active_era: EraIndex) -> bool { + start + period <= active_era + } + fn if_era_starts_do(era: EraIndex, start_index: SessionIndex, on_era_start: F) { if let Some(era_start_index) = T::EraInfoProvider::era_start_session_index(era) { if era_start_index == start_index { @@ -233,6 +236,25 @@ where } } } + + fn clear_expired_bans_on_new_era_start(session: SessionIndex) { + let active_era = match T::EraInfoProvider::active_era() { + Some(ae) => ae, + _ => return, + }; + + Self::if_era_starts_do(active_era, session, || { + let ban_period = BanConfig::::get().ban_period; + let unban = Banned::::iter().filter_map(|(v, ban_info)| { + if Self::ban_expired(ban_info.start, ban_period, active_era) { + return Some(v); + } + None + }); + unban.for_each(Banned::::remove); + }); + } + fn populate_next_era_validators_on_next_era_start(session: SessionIndex) { let active_era = match T::EraInfoProvider::active_era() { Some(ae) => ae, @@ -307,7 +329,7 @@ where } fn calculate_underperforming_validators() { - let thresholds = CommitteeKickOutConfig::::get(); + let thresholds = BanConfig::::get(); let current_committee = T::SessionInfoProvider::current_committee(); let expected_blocks_per_validator = Self::blocks_to_produce_per_session(); for validator in current_committee { @@ -324,26 +346,29 @@ where } } - fn mark_validator_underperformance( - thresholds: &CommitteeKickOutConfigStruct, - validator: &T::AccountId, - ) { + pub fn ban_validator(validator: &T::AccountId, reason: BanReason) { + // current era is the latest planned era for which validators are already chosen + // so we ban from the next era + let start: EraIndex = T::EraInfoProvider::current_era() + .unwrap_or(0) + .saturating_add(1); + Banned::::insert(validator, BanInfo { reason, start }); + } + + fn mark_validator_underperformance(thresholds: &BanConfigStruct, validator: &T::AccountId) { let counter = UnderperformedValidatorSessionCount::::mutate(&validator, |count| { *count += 1; *count }); if counter >= thresholds.underperformed_session_count_threshold { - ToBeKickedOutFromCommittee::::insert( - &validator, - KickOutReason::InsufficientUptime(counter), - ); + let reason = BanReason::InsufficientUptime(counter); + Self::ban_validator(validator, reason); UnderperformedValidatorSessionCount::::remove(&validator); } } fn clear_underperformance_session_counter(session: SessionIndex) { - let clean_session_counter_delay = - CommitteeKickOutConfig::::get().clean_session_counter_delay; + let clean_session_counter_delay = BanConfig::::get().clean_session_counter_delay; if session % clean_session_counter_delay == 0 { info!(target: "pallet_elections", "Clearing UnderperformedValidatorSessionCount"); let _result = UnderperformedValidatorSessionCount::::clear(u32::MAX, None); @@ -394,6 +419,7 @@ where ::SessionManager::start_session(start_index); Self::populate_totals_on_new_era_start(start_index); Self::clear_underperformance_session_counter(start_index); + Self::clear_expired_bans_on_new_era_start(start_index); } } diff --git a/pallets/elections/src/lib.rs b/pallets/elections/src/lib.rs index 76da23bc9b..a8b96b8f82 100644 --- a/pallets/elections/src/lib.rs +++ b/pallets/elections/src/lib.rs @@ -1,31 +1,34 @@ //! This pallet manages changes in the committee responsible for producing blocks and establishing consensus. -//! Currently, it's PoA where the validators are set by the root account. In the future, a new -//! version for DPoS elections will replace the current one. //! //! # Terminology //! For definition of session, era, staking see pallet_session and pallet_staking. //! - committee ([`EraValidators`]): Set of nodes that produce and finalize blocks in the session. //! - validator: Node that can become a member of committee (or already is) via rotation. //! - `EraValidators::reserved`: immutable validators, ie they cannot be removed from that list. -//! - `EraValidators::non_reserved`: validators that can be kicked out from that list +//! - `EraValidators::non_reserved`: validators that can be banned out from that list. //! -//! # Kick out logic +//! # Elections process +//! There are two options for choosing validators during election process governed by ([`Openness`]) storage value: +//! - `Permissionless`: choose all validators that bonded enough amount and are not banned. +//! - `Permissioned`: choose `EraValidators::reserved` and all `EraValidators::non_reserved` that are not banned. +//! +//! # Ban logic //! In case of insufficient validator's uptime, we need to remove such validators from //! the committee, so that the network is as healthy as possible. This is achieved by calculating //! number of _underperformance_ sessions, which means that number of blocks produced by the //! validator is less than some predefined threshold. //! In other words, if a validator: //! * performance in a session is less or equal to a configurable threshold -//! `CommitteeKickOutConfig::minimal_expected_performance` (from 0 to 100%), and, -//! * it happened at least `CommitteeKickOutConfig::underperformed_session_count_threshold` times, -//! then the validator is considered an underperformer and hence removed (ie _kicked out_) from the +//! `BanConfig::minimal_expected_performance` (from 0 to 100%), and, +//! * it happened at least `BanConfig::underperformed_session_count_threshold` times, +//! then the validator is considered an underperformer and hence removed (ie _banned out_) from the //! committee. //! //! ## Thresholds -//! There are two kick-out thresholds described above, see [`CommitteeKickOutConfig`]. +//! There are two ban thresholds described above, see [`BanConfig`]. //! //! ### Next era vs current era -//! Current and next era have distinct thresholds values, as we calculate kicks during elections. +//! Current and next era have distinct thresholds values, as we calculate bans during elections. //! They follow the same logic as next era committee seats: at the time of planning the first //! session of next the era, next values become current ones. @@ -70,8 +73,8 @@ pub mod pallet { }; use pallet_session::SessionManager; use primitives::{ - BlockCount, CommitteeKickOutConfig as CommitteeKickOutConfigStruct, CommitteeSeats, - KickOutReason, SessionCount, + BanConfig as BanConfigStruct, BanInfo, BanReason, BlockCount, CommitteeSeats, + ElectionOpenness, EraIndex, SessionCount, }; use sp_runtime::Perbill; @@ -98,9 +101,9 @@ pub mod pallet { /// Something that handles addition of rewards for validators. type ValidatorRewardsHandler: ValidatorRewardsHandler; - /// Maximum acceptable kick-out reason length. + /// Maximum acceptable ban reason length. #[pallet::constant] - type MaximumKickOutReasonLength: Get; + type MaximumBanReasonLength: Get; } #[pallet::event] @@ -109,11 +112,11 @@ pub mod pallet { /// Committee for the next era has changed ChangeValidators(Vec, Vec, CommitteeSeats), - /// Kick out thresholds for the next era has changed - SetCommitteeKickOutConfig(CommitteeKickOutConfigStruct), + /// Ban thresholds for the next era has changed + SetBanConfig(BanConfigStruct), - /// Validators have been kicked from the committee - KickOutValidators(Vec<(T::AccountId, KickOutReason)>), + /// Validators have been banned from the committee + BanValidators(Vec<(T::AccountId, BanInfo)>), } #[pallet::pallet] @@ -189,16 +192,15 @@ pub mod pallet { pub type ValidatorEraTotalReward = StorageValue<_, ValidatorTotalRewards, OptionQuery>; - /// Default value for kick out config, see [`CommitteeKickOutConfig`] + /// Default value for ban config, see [`BanConfig`] #[pallet::type_value] - pub fn DefaultCommitteeKickOutConfig() -> CommitteeKickOutConfigStruct { - CommitteeKickOutConfigStruct::default() + pub fn DefaultBanConfig() -> BanConfigStruct { + BanConfigStruct::default() } - /// Current era config for kick-out functionality, see [`CommitteeKickOutConfig`] + /// Current era config for ban functionality, see [`BanConfig`] #[pallet::storage] - pub type CommitteeKickOutConfig = - StorageValue<_, CommitteeKickOutConfigStruct, ValueQuery, DefaultCommitteeKickOutConfig>; + pub type BanConfig = StorageValue<_, BanConfigStruct, ValueQuery, DefaultBanConfig>; /// A lookup for a number of underperformance sessions for a given validator #[pallet::storage] @@ -207,8 +209,18 @@ pub mod pallet { /// Validators to be removed from non reserved list in the next era #[pallet::storage] - pub type ToBeKickedOutFromCommittee = - StorageMap<_, Twox64Concat, T::AccountId, KickOutReason>; + pub type Banned = StorageMap<_, Twox64Concat, T::AccountId, BanInfo>; + + /// Default value for elections openness. + #[pallet::type_value] + pub fn DefaultOpenness() -> ElectionOpenness { + ElectionOpenness::Permissioned + } + + /// Openness of the elections, whether we allow all candidates that bonded enough tokens or + /// the validators list is managed by sudo + #[pallet::storage] + pub type Openness = StorageValue<_, ElectionOpenness, ValueQuery, DefaultOpenness>; #[pallet::call] impl Pallet { @@ -245,24 +257,25 @@ pub mod pallet { Ok(()) } - /// Sets kick-out config, it has an immediate effect + /// Sets ban config, it has an immediate effect #[pallet::weight((T::BlockWeights::get().max_block, DispatchClass::Operational))] - pub fn set_kick_out_config( + pub fn set_ban_config( origin: OriginFor, minimal_expected_performance: Option, underperformed_session_count_threshold: Option, clean_session_counter_delay: Option, + ban_period: Option, ) -> DispatchResult { ensure_root(origin)?; - let mut current_committee_kick_out_config = CommitteeKickOutConfig::::get(); + let mut current_committee_ban_config = BanConfig::::get(); if let Some(minimal_expected_performance) = minimal_expected_performance { ensure!( minimal_expected_performance <= 100, - Error::::InvalidCommitteeKickOutConfig + Error::::InvalidBanConfig ); - current_committee_kick_out_config.minimal_expected_performance = + current_committee_ban_config.minimal_expected_performance = Perbill::from_percent(minimal_expected_performance as u32); } if let Some(underperformed_session_count_threshold) = @@ -270,44 +283,66 @@ pub mod pallet { { ensure!( underperformed_session_count_threshold > 0, - Error::::InvalidCommitteeKickOutConfig + Error::::InvalidBanConfig ); - current_committee_kick_out_config.underperformed_session_count_threshold = + current_committee_ban_config.underperformed_session_count_threshold = underperformed_session_count_threshold; } if let Some(clean_session_counter_delay) = clean_session_counter_delay { ensure!( clean_session_counter_delay > 0, - Error::::InvalidCommitteeKickOutConfig + Error::::InvalidBanConfig ); - current_committee_kick_out_config.clean_session_counter_delay = + current_committee_ban_config.clean_session_counter_delay = clean_session_counter_delay; } + if let Some(ban_period) = ban_period { + ensure!(ban_period > 0, Error::::InvalidBanConfig); + current_committee_ban_config.ban_period = ban_period; + } - CommitteeKickOutConfig::::put(current_committee_kick_out_config.clone()); - Self::deposit_event(Event::SetCommitteeKickOutConfig( - current_committee_kick_out_config, - )); + BanConfig::::put(current_committee_ban_config.clone()); + Self::deposit_event(Event::SetBanConfig(current_committee_ban_config)); Ok(()) } - /// Schedule a non-reserved node to be kicked out from the committee at the end of the era + /// Schedule a non-reserved node to be banned out from the committee at the end of the era #[pallet::weight((T::BlockWeights::get().max_block, DispatchClass::Operational))] - pub fn kick_out_from_committee( + pub fn ban_from_committee( origin: OriginFor, - to_be_kicked: T::AccountId, - kick_out_reason: Vec, + banned: T::AccountId, + ban_reason: Vec, ) -> DispatchResult { ensure_root(origin)?; - let bounded_description: BoundedVec<_, _> = kick_out_reason + let bounded_description: BoundedVec<_, _> = ban_reason .try_into() - .map_err(|_| Error::::KickOutReasonTooBig)?; + .map_err(|_| Error::::BanReasonTooBig)?; - ToBeKickedOutFromCommittee::::insert( - to_be_kicked, - KickOutReason::OtherReason(bounded_description), - ); + let reason = BanReason::OtherReason(bounded_description); + Self::ban_validator(&banned, reason); + + Ok(()) + } + + /// Schedule a non-reserved node to be banned out from the committee at the end of the era + #[pallet::weight((T::BlockWeights::get().max_block, DispatchClass::Operational))] + pub fn cancel_ban(origin: OriginFor, banned: T::AccountId) -> DispatchResult { + ensure_root(origin)?; + Banned::::remove(banned); + + Ok(()) + } + + /// Set openness of the elections + #[pallet::weight((T::BlockWeights::get().max_block, DispatchClass::Operational))] + pub fn set_elections_openness( + origin: OriginFor, + openness: ElectionOpenness, + ) -> DispatchResult { + ensure_root(origin)?; + + Openness::::set(openness); Ok(()) } @@ -318,7 +353,7 @@ pub mod pallet { pub non_reserved_validators: Vec, pub reserved_validators: Vec, pub committee_seats: CommitteeSeats, - pub committee_kick_out_config: CommitteeKickOutConfigStruct, + pub committee_ban_config: BanConfigStruct, } #[cfg(feature = "std")] @@ -328,7 +363,7 @@ pub mod pallet { non_reserved_validators: Vec::new(), reserved_validators: Vec::new(), committee_seats: Default::default(), - committee_kick_out_config: Default::default(), + committee_ban_config: Default::default(), } } } @@ -344,7 +379,7 @@ pub mod pallet { reserved: self.reserved_validators.clone(), non_reserved: self.non_reserved_validators.clone(), }); - >::put(&self.committee_kick_out_config.clone()); + >::put(&self.committee_ban_config.clone()); } } @@ -392,24 +427,14 @@ pub mod pallet { Ok(()) } - fn kick_out_underperformed_non_reserved_validators() { - let mut to_be_kicked = BTreeMap::from_iter(ToBeKickedOutFromCommittee::::iter()); - if !to_be_kicked.is_empty() { - let mut to_be_removed = Vec::new(); - let mut filtered_non_reserved_in_order = Vec::new(); - let non_reserved_validators = NextEraNonReservedValidators::::get(); - for non_reserved_validator in non_reserved_validators { - match to_be_kicked.remove_entry(&non_reserved_validator) { - Some(candidate_and_reason) => to_be_removed.push(candidate_and_reason), - None => filtered_non_reserved_in_order.push(non_reserved_validator), - } - } - if !to_be_removed.is_empty() { - info!(target: "pallet_elections", "Removing following validators from non reserved, {:?}", to_be_removed); - NextEraNonReservedValidators::::put(filtered_non_reserved_in_order); - Self::deposit_event(Event::KickOutValidators(to_be_removed)); - } - let _result = ToBeKickedOutFromCommittee::::clear(u32::MAX, None); + fn emit_fresh_bans_event() { + let active_era = ::EraInfoProvider::active_era().unwrap_or(1); + let fresh_bans = Banned::::iter() + .filter(|(_acc, info)| info.start == active_era + 1) + .collect::>(); + if !fresh_bans.is_empty() { + info!(target: "pallet_elections", "Fresh bans in era {}: {:?}",active_era, fresh_bans); + Self::deposit_event(Event::BanValidators(fresh_bans)); } } } @@ -426,15 +451,15 @@ pub mod pallet { NotEnoughNonReservedValidators, NonUniqueListOfValidators, - /// Raised in any scenario [`CommitteeKickOutConfig`] is invalid + /// Raised in any scenario [`BanConfig`] is invalid /// * `performance_ratio_threshold` must be a number in range [0; 100] /// * `underperformed_session_count_threshold` must be a positive number, /// * `clean_session_counter_delay` must be a positive number. - InvalidCommitteeKickOutConfig, + InvalidBanConfig, - /// Kick out reason is too big, ie given vector of bytes is greater than - /// [`Config::MaximumKickOutReasonLength`] - KickOutReasonTooBig, + /// Ban reason is too big, ie given vector of bytes is greater than + /// [`Config::MaximumBanReasonLength`] + BanReasonTooBig, } impl ElectionProvider for Pallet { @@ -443,26 +468,50 @@ pub mod pallet { type Error = ElectionError; type DataProvider = T::DataProvider; - /// The elections are PoA so only the nodes listed in the Validators will be elected as - /// validators. - /// - /// We calculate the supports for them for the sake of eras payouts. + /// We calculate the supports for each validator. The external validators are chosen as: + /// 1) "`NextEraNonReservedValidators` that are staking and are not banned" in case of Permissioned ElectionOpenness + /// 2) "All staking and not banned validators" in case of Permissionless ElectionOpenness fn elect() -> Result, Self::Error> { - Self::kick_out_underperformed_non_reserved_validators(); + Self::emit_fresh_bans_event(); + let active_era = ::EraInfoProvider::active_era().unwrap_or(0); + let ban_period = BanConfig::::get().ban_period; let staking_validators = Self::DataProvider::electable_targets(None) .map_err(Self::Error::DataProvider)? .into_iter() .collect::>(); - let reserved_validators = NextEraReservedValidators::::get() + let staking_reserved_validators = NextEraReservedValidators::::get() .into_iter() + .filter(|v| staking_validators.contains(v)) + .collect::>(); + let banned_validators = Banned::::iter() + .filter(|(_, info)| !Self::ban_expired(info.start, ban_period, active_era + 1)) + .map(|(v, _)| v) .collect::>(); - let non_reserved_validators = NextEraNonReservedValidators::::get() + let old_non_reserved_validators = NextEraNonReservedValidators::::get().into_iter(); + + let eligible_non_reserved = staking_validators .into_iter() + .filter(|v| { + !banned_validators.contains(v) && !staking_reserved_validators.contains(v) + }) .collect::>(); - let eligible_validators = - &(&reserved_validators | &non_reserved_validators) & &staking_validators; + let new_non_reserved_validators: Vec<_> = match Openness::::get() { + ElectionOpenness::Permissioned => old_non_reserved_validators + .filter(|v| eligible_non_reserved.contains(v)) + .collect(), + ElectionOpenness::Permissionless => eligible_non_reserved.into_iter().collect(), + }; + // We store new list here to ensure that validators that end up in the result of the elect + // method are a disjoint union of NextEraReservedValidators and NextEraNonReservedValidators. + // This condition is important since results of elect ends up in pallet staking while the above lists + // are used in our session manager, so we have to ensure consistency between them. + NextEraNonReservedValidators::::put(new_non_reserved_validators.clone()); + + let eligible_validators = staking_reserved_validators + .into_iter() + .chain(new_non_reserved_validators.into_iter()); let mut supports = eligible_validators .into_iter() .map(|id| { diff --git a/pallets/elections/src/mock.rs b/pallets/elections/src/mock.rs index 4e8756c91b..daf629c084 100644 --- a/pallets/elections/src/mock.rs +++ b/pallets/elections/src/mock.rs @@ -7,7 +7,7 @@ use frame_support::{ weights::RuntimeDbWeight, BasicExternalities, BoundedVec, }; -use primitives::{CommitteeKickOutConfig, CommitteeSeats}; +use primitives::{BanConfig, CommitteeSeats}; use sp_core::H256; use sp_runtime::{ testing::{Header, TestXt}, @@ -128,6 +128,7 @@ impl ValidatorRewardsHandler for MockProvider { thread_local! { static ACTIVE_ERA: RefCell = RefCell::new(Default::default()); + static CURRENT_ERA: RefCell = RefCell::new(Default::default()); static ELECTED_VALIDATORS: RefCell>> = RefCell::new(Default::default()); } @@ -135,6 +136,10 @@ pub fn with_active_era(era: EraIndex) { ACTIVE_ERA.with(|ae| *ae.borrow_mut() = era); } +pub fn with_current_era(era: EraIndex) { + CURRENT_ERA.with(|ce| *ce.borrow_mut() = era); +} + pub fn with_elected_validators(era: EraIndex, validators: Vec) { ELECTED_VALIDATORS.with(|ev| *ev.borrow_mut() = BTreeMap::from_iter([(era, validators)])); } @@ -146,6 +151,10 @@ impl EraInfoProvider for MockProvider { Some(ACTIVE_ERA.with(|ae| *ae.borrow())) } + fn current_era() -> Option { + Some(ACTIVE_ERA.with(|ae| *ae.borrow())) + } + fn era_start_session_index(era: EraIndex) -> Option { Some(era * SessionsPerEra::get()) } @@ -167,7 +176,7 @@ impl Config for Test { type SessionManager = (); type SessionInfoProvider = MockProvider; type ValidatorRewardsHandler = MockProvider; - type MaximumKickOutReasonLength = ConstU32<300>; + type MaximumBanReasonLength = ConstU32<300>; } type MaxVotesPerVoter = ConstU32<1>; @@ -214,7 +223,7 @@ pub struct TestExtBuilder { reserved_validators: Vec, non_reserved_validators: Vec, committee_seats: CommitteeSeats, - committee_kick_out_config: CommitteeKickOutConfig, + committee_ban_config: BanConfig, storage_version: StorageVersion, } @@ -230,7 +239,7 @@ impl TestExtBuilder { }, reserved_validators, non_reserved_validators, - committee_kick_out_config: CommitteeKickOutConfig::default(), + committee_ban_config: BanConfig::default(), storage_version: STORAGE_VERSION, } } @@ -270,7 +279,7 @@ impl TestExtBuilder { non_reserved_validators: self.non_reserved_validators, reserved_validators: self.reserved_validators, committee_seats: self.committee_seats, - committee_kick_out_config: self.committee_kick_out_config, + committee_ban_config: self.committee_ban_config, } .assimilate_storage(&mut t) .unwrap(); diff --git a/pallets/elections/src/tests.rs b/pallets/elections/src/tests.rs index b0636b5b34..8b377780a9 100644 --- a/pallets/elections/src/tests.rs +++ b/pallets/elections/src/tests.rs @@ -5,14 +5,14 @@ use frame_support::bounded_vec; use pallet_session::SessionManager; #[cfg(feature = "try-runtime")] use pallets_support::StorageMigration; -use primitives::{CommitteeKickOutConfig as CommitteeKickOutConfigStruct, CommitteeSeats}; +use primitives::{BanConfig as BanConfigStruct, CommitteeSeats}; use crate::{ mock::{ - with_active_era, with_electable_targets, with_elected_validators, with_electing_voters, - AccountId, Balance, Elections, SessionsPerEra, Test, TestExtBuilder, + with_active_era, with_current_era, with_electable_targets, with_elected_validators, + with_electing_voters, AccountId, Balance, Elections, SessionsPerEra, Test, TestExtBuilder, }, - CommitteeKickOutConfig, CommitteeSize, CurrentEraValidators, NextEraCommitteeSize, + BanConfig, CommitteeSize, CurrentEraValidators, NextEraCommitteeSize, NextEraNonReservedValidators, NextEraReservedValidators, }; @@ -46,13 +46,10 @@ fn storage_is_initialized_already_in_genesis() { CurrentEraValidators::::get().non_reserved, NON_RESERVED ); - assert_eq!( - CommitteeKickOutConfig::::get(), - CommitteeKickOutConfigStruct::default() - ); + assert_eq!(BanConfig::::get(), BanConfigStruct::default()); // We do not expect SessionValidatorBlockCount and ValidatorEraTotalReward to be - // populated from genesis, so does the kick-out related storages: - // UnderperformedValidatorSessionCount and ToBeKickedOutFromCommittee + // populated from genesis, so does the ban related storages: + // UnderperformedValidatorSessionCount and Banned }); } @@ -102,6 +99,7 @@ fn session_authorities_must_have_been_elected() { with_active_era(next_era - 1); with_elected_validators(next_era, vec![1, 5]); + with_current_era(next_era); let mut authorities = >::new_session( next_era * SessionsPerEra::get(), diff --git a/pallets/elections/src/traits.rs b/pallets/elections/src/traits.rs index f70f2d6e04..f60172f266 100644 --- a/pallets/elections/src/traits.rs +++ b/pallets/elections/src/traits.rs @@ -46,9 +46,12 @@ where pub trait EraInfoProvider { type AccountId; - /// Returns `Some(idx)` where idx is the current active era index otherwise + /// Returns `Some(idx)` where idx is the active era index otherwise /// if no era is active returns `None`. fn active_era() -> Option; + /// Returns `Some(idx)` where idx is the current era index which is latest + /// planed era otherwise if no era has started returns `None`. + fn current_era() -> Option; /// Returns the index of the starting session of the `era` if possible. Otherwise returns `None`. fn era_start_session_index(era: EraIndex) -> Option; /// Returns how many sessions are in single era. @@ -67,6 +70,10 @@ where pallet_staking::ActiveEra::::get().map(|ae| ae.index) } + fn current_era() -> Option { + pallet_staking::CurrentEra::::get() + } + fn era_start_session_index(era: EraIndex) -> Option { pallet_staking::ErasStartSessionIndex::::get(era) } diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index e3c3892d7e..e59bd722e3 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -60,10 +60,18 @@ pub const DEFAULT_UNIT_CREATION_DELAY: u64 = 300; pub const DEFAULT_COMMITTEE_SIZE: u32 = 4; -pub const DEFAULT_KICK_OUT_MINIMAL_EXPECTED_PERFORMANCE: Perbill = Perbill::from_percent(0); -pub const DEFAULT_KICK_OUT_SESSION_COUNT_THRESHOLD: SessionCount = 3; -pub const DEFAULT_KICK_OUT_REASON_LENGTH: u32 = 300; +pub const DEFAULT_BAN_MINIMAL_EXPECTED_PERFORMANCE: Perbill = Perbill::from_percent(0); +pub const DEFAULT_BAN_SESSION_COUNT_THRESHOLD: SessionCount = 3; +pub const DEFAULT_BAN_REASON_LENGTH: u32 = 300; pub const DEFAULT_CLEAN_SESSION_COUNTER_DELAY: SessionCount = 960; +pub const DEFAULT_BAN_PERIOD: EraIndex = 10; + +/// Openness of the process of the elections +#[derive(Decode, Encode, TypeInfo, Debug, Clone, PartialEq, Eq)] +pub enum ElectionOpenness { + Permissioned, + Permissionless, +} /// Represent desirable size of a committee in a session #[derive(Decode, Encode, TypeInfo, Debug, Clone, Copy, PartialEq, Eq)] @@ -90,10 +98,10 @@ impl Default for CommitteeSeats { } } -/// Configurable parameters for kick-out validator mechanism +/// Configurable parameters for ban validator mechanism #[derive(Decode, Encode, TypeInfo, Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -pub struct CommitteeKickOutConfig { +pub struct BanConfig { /// performance ratio threshold in a session /// calculated as ratio of number of blocks produced to expected number of blocks for a single validator pub minimal_expected_performance: Perbill, @@ -101,27 +109,39 @@ pub struct CommitteeKickOutConfig { pub underperformed_session_count_threshold: SessionCount, /// underperformed session counter is cleared every subsequent `clean_session_counter_delay` sessions pub clean_session_counter_delay: SessionCount, + /// how many eras a validator is banned for + pub ban_period: EraIndex, } -impl Default for CommitteeKickOutConfig { +impl Default for BanConfig { fn default() -> Self { - CommitteeKickOutConfig { - minimal_expected_performance: DEFAULT_KICK_OUT_MINIMAL_EXPECTED_PERFORMANCE, - underperformed_session_count_threshold: DEFAULT_KICK_OUT_SESSION_COUNT_THRESHOLD, + BanConfig { + minimal_expected_performance: DEFAULT_BAN_MINIMAL_EXPECTED_PERFORMANCE, + underperformed_session_count_threshold: DEFAULT_BAN_SESSION_COUNT_THRESHOLD, clean_session_counter_delay: DEFAULT_CLEAN_SESSION_COUNTER_DELAY, + ban_period: DEFAULT_BAN_PERIOD, } } } /// Represent any possible reason a validator can be removed from the committee due to #[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, Debug)] -pub enum KickOutReason { +pub enum BanReason { /// Validator has been removed from the committee due to insufficient uptime in a given number /// of sessions InsufficientUptime(u32), /// Any arbitrary reason - OtherReason(BoundedVec>), + OtherReason(BoundedVec>), +} + +/// Details of why and for how long a validator is removed from the committee +#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, Debug)] +pub struct BanInfo { + /// reason for banning a validator + pub reason: BanReason, + /// index of the first era when a ban starts + pub start: EraIndex, } /// Represent committee, ie set of nodes that produce and finalize blocks in the session @@ -129,7 +149,7 @@ pub enum KickOutReason { pub struct EraValidators { /// Validators that are chosen to be in committee every single session. pub reserved: Vec, - /// Validators that can be kicked out from the committee, under the circumstances + /// Validators that can be banned out from the committee, under the circumstances pub non_reserved: Vec, }