Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MESH-1876/ When removing a permissioned operator, each of its associated nodes (stash keys) should be chilled #1317

63 changes: 63 additions & 0 deletions pallets/runtime/tests/src/staking/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5819,3 +5819,66 @@ fn test_bond_too_small() {
assert_ok!(both(90, 91, MinimumBond::get() + 1));
});
}

#[test]
fn chill_from_governance() {
ExtBuilder::default()
.validator_count(8)
.minimum_validator_count(1)
.build()
.execute_with(|| {
// 50 stash and 51 controller (corrected)
bond(50, 51, 500000);
let entity_id = Identity::get_identity(&50).unwrap();

// Add a new validator successfully.
bond_validator_with_intended_count(50, 51, 500000, Some(2));

assert_permissioned_identity_prefs!(entity_id, 2, 1);

// Add other stash and controller to the same did.
add_secondary_key(50, 60);
add_secondary_key(50, 61);

// Validate one more validator from the same entity.
// 60 stash and 61 controller.
bond_validator(60, 61, 500000);
assert_permissioned_identity_prefs!(entity_id, 2, 2);

// Removes 50 and 60 from being validators
assert_ok!(Staking::chill_from_governance(
Origin::signed(2000),
entity_id,
vec![50, 60]
));

// No longer permissioned identity
assert_noop!(
Staking::chill_from_governance(Origin::signed(2000), entity_id, vec![50, 60]),
Error::<Test>::NotExists
);

// 70 stash and 71 controller
bond(70, 71, 500000);
let entity_id_2 = Identity::get_identity(&70).unwrap();

// Add a new validator successfully.
bond_validator_with_intended_count(70, 71, 500000, Some(2));

// Add other stash and controller to the same did.
add_secondary_key(70, 80);
add_secondary_key(70, 81);

// Check keys that aren't joined with identity gives error
assert_noop!(
Staking::chill_from_governance(Origin::signed(2000), entity_id_2, vec![90, 95]),
Error::<Test>::NotStash
);

// Check key that is not GC gives error
assert_noop!(
Staking::chill_from_governance(Origin::signed(20), entity_id_2, vec![90, 95]),
BadOrigin
);
});
}
48 changes: 48 additions & 0 deletions pallets/staking/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,15 @@ use polymesh_common_utilities::{
TestUtilsFn,
};
use sp_runtime::traits::One;
use polymesh_primitives::{
Permissions,
};
const SEED: u32 = 0;
const MAX_SPANS: u32 = 100;
const MAX_VALIDATORS: u32 = 1000;
const MAX_SLASHES: u32 = 1000;
const INIT_BALANCE: u32 = 10_000_000;
const MAX_STASHES: u32 = 100;

macro_rules! whitelist_account {
($acc:expr) => {
Expand Down Expand Up @@ -750,6 +754,50 @@ benchmarks! {
verify {
assert_eq!(Staking::<T>::validator_count(), 12);
}

chill_from_governance {
let s in 1 .. MAX_STASHES;
let validator = create_funded_user::<T>("caller", 2, 10000);
let validator_did = validator.did();
let mut signatories = Vec::with_capacity(s as usize);

// Increases the maximum number of validators
emulate_validator_setup::<T>(1, 1000, Perbill::from_percent(60));
assert_eq!(Staking::<T>::validator_count(), 1000);

// Add validator
Staking::<T>::add_permissioned_validator(RawOrigin::Root.into(), validator_did, Some(MAX_STASHES))
.expect("Failed to add permissioned validator");
whitelist_account!(validator);

for x in 0 .. s {
// Create stash key
let key = create_funded_user_without_did::<T>("stash", x, 10000);
// Add key to signatories vec
signatories.push(key.account.clone());
// Add key as secondary key to validator_did
Identity::<T>::unsafe_join_identity(validator_did, Permissions::default(), key.account.clone());
// Use key to bond
Staking::<T>::bond(key.origin().into(), key.lookup(), 2_000_000u32.into(), RewardDestination::Staked).expect("Bond failed.");
// Create ValidatorPrefs
let validator_prefs = ValidatorPrefs {
commission: Perbill::from_percent(50),
..Default::default()
};

whitelist_account!(key);
// Use stash key to validate
Staking::<T>::validate(key.origin().into(), validator_prefs).expect("Validate fails");
// Checks that the stash key is in validators storage
assert_eq!(<Validators<T>>::contains_key(&key.account), true);
}
}: _(RawOrigin::Root, validator_did, signatories.clone())
verify {
for key in signatories {
// Checks that the stash key has been removed from storage
assert_eq!(<Validators<T>>::contains_key(&key), false);
}
}
}

#[cfg(test)]
Expand Down
46 changes: 46 additions & 0 deletions pallets/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2656,6 +2656,27 @@ decl_module! {
.map(|p| p.intended_count = new_intended_count)
})
}


/// GC forcefully chills a validator.
/// Effects will be felt at the beginning of the next era.
/// And, it can be only called when [`EraElectionStatus`] is `Closed`.
///
/// # Arguments
/// * origin which must be a GC.
/// * identity must be permissioned to run operator/validator nodes.
/// * stash_keys contains the secondary keys of the permissioned identity
///
/// # Errors
/// * `BadOrigin` The origin was not a GC member.
/// * `CallNotAllowed` The call is not allowed at the given time due to restrictions of election period.
/// * `NotExists` Permissioned validator doesn't exist.
/// * `NotStash` Not a stash account for the permissioned identity.
#[weight = <T as Config>::WeightInfo::chill_from_governance(stash_keys.len() as u32)]
pub fn chill_from_governance(origin, identity: IdentityId, stash_keys: Vec<T::AccountId>) -> DispatchResult {
Self::base_chill_from_governance(origin, identity, stash_keys)
}

}
}

Expand Down Expand Up @@ -3700,6 +3721,31 @@ impl<T: Config> Module<T> {
* T::ExpectedBlockTime::get().saturated_into::<u64>()
}

fn base_chill_from_governance(origin: T::Origin, identity: IdentityId, stash_keys: Vec<T::AccountId>) -> DispatchResult {
// Checks that the era election status is closed.
ensure!(Self::era_election_status().is_closed(), Error::<T>::CallNotAllowed);
// Required origin for removing a validator.
T::RequiredRemoveOrigin::ensure_origin(origin)?;
// Checks that the identity is allowed to run operator/validator nodes.
ensure!(Self::permissioned_identity(&identity).is_some(), Error::<T>::NotExists);

for key in &stash_keys {
let key_did = Identity::<T>::get_identity(&key);
// Checks if the stash key identity is the same as the identity given.
ensure!(key_did == Some(identity), Error::<T>::NotStash);
// Checks if the key is a validator if not returns an error.
ensure!(<Validators<T>>::contains_key(&key), Error::<T>::NotExists);
}

for key in stash_keys {
Self::chill_stash(&key);
}

// Change identity status to be Non-Permissioned
PermissionedIdentity::remove(&identity);
Ok(())
}

#[cfg(feature = "runtime-benchmarks")]
pub fn add_era_stakers(current_era: EraIndex, controller: T::AccountId, exposure: Exposure<T::AccountId, BalanceOf<T>>) {
<ErasStakers<T>>::insert(&current_era, &controller, &exposure);
Expand Down
4 changes: 4 additions & 0 deletions pallets/staking/src/offchain_election.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,10 @@ mod test {
fn submit_solution_better(v: u32, n: u32, a: u32, w: u32) -> Weight {
(0 * v + 0 * n + 1000 * a + 0 * w) as Weight
}

fn chill_from_governance(s: u32) -> Weight {
unimplemented!()
}
}

#[test]
Expand Down
15 changes: 15 additions & 0 deletions pallets/staking/src/testing_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,3 +463,18 @@ pub fn create_assignments_for_offchain<T: Config>(

Ok((winners, assignments))
}

/// Grab a funded user with the given balance without did.
pub fn create_funded_user_without_did<T: Config + TestUtilsFn<AccountIdOf<T>>>(
string: &'static str,
n: u32,
balance: u32,
) -> User<T> {
let user = UserBuilder::<T>::default()
.balance(balance)
.seed(n)
.build(string);
// ensure T::CurrencyToVote will work correctly.
T::Currency::issue(balance.into());
user
}
1 change: 1 addition & 0 deletions pallets/staking/src/weights.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ pub trait WeightInfo {
fn update_permissioned_validator_intended_count() -> Weight;
fn increase_validator_count() -> Weight;
fn scale_validator_count() -> Weight;
fn chill_from_governance(s: u32) -> Weight;
}
18 changes: 17 additions & 1 deletion pallets/weights/src/pallet_staking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
//! Autogenerated weights for pallet_staking
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2022-06-25, STEPS: `100`, REPEAT: 5, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! DATE: 2022-11-08, STEPS: `100`, REPEAT: 5, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 512

// Executed Command:
Expand Down Expand Up @@ -469,4 +469,20 @@ impl pallet_staking::WeightInfo for WeightInfo {
.saturating_add(DbWeight::get().reads(1 as Weight))
.saturating_add(DbWeight::get().writes(1 as Weight))
}

// Storage: Staking EraElectionStatus (r:1 w:0)
// Storage: Staking PermissionedIdentity (r:1 w:1)
// Storage: Identity KeyRecords (r:1 w:0)
// Storage: Identity IsDidFrozen (r:1 w:0)
// Storage: Staking Validators (r:1 w:1)
// Storage: Staking Nominators (r:0 w:1)
fn chill_from_governance(s: u32) -> Weight {
(5_655_000 as Weight)
// Standard Error: 108_000
.saturating_add((15_307_000 as Weight).saturating_mul(s as Weight))
.saturating_add(DbWeight::get().reads(3 as Weight))
.saturating_add(DbWeight::get().reads((2 as Weight).saturating_mul(s as Weight)))
.saturating_add(DbWeight::get().writes(1 as Weight))
.saturating_add(DbWeight::get().writes((2 as Weight).saturating_mul(s as Weight)))
}
}