Benchmark Staking and Session Pallet (#5183)

* starting bench

* More

* more

* Payout Validator

* Give each validator exactly n nominators

* Update with test

* Try to add accounts to chain spec

* Undo changes to chainspec

* Payout nominator

* Rebond and Reap Stash

* Set history depth

* fix smelly code

* cancel deferred slash

* new_era bench

* do_slash benchmark

* Add features

* undo extrinsic move

* lower

* Update new era

* Update benchmarking.rs

* whitespace

* Apply suggestions from code review

Co-Authored-By: Kian Paimani <5588131+kianenigma@users.noreply.github.com>

* fixes

* nit

* Refactor tests, initial code

* Move session benchmarks to avoid cyclic deps

* Update lib.rs

* Fix warnings

* Move impl

* Update to do random nominator allocation

* add feature to benchmark pallet

* Remove extra stuff

* Update based on feedback

* Less intrusive

* Remove `transfer_idle_users`

* remove again

* unused dep

* test feature flag

* Update to latest substrate

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
This commit is contained in:
Shawn Tabrizi
2020-03-17 15:12:04 +02:00
committed by GitHub
parent d146cc3ae3
commit 9c06d8c6f4
17 changed files with 961 additions and 333 deletions
+464
View File
@@ -0,0 +1,464 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Staking pallet benchmarking.
use super::*;
use rand_chacha::{rand_core::{RngCore, SeedableRng}, ChaChaRng};
use sp_runtime::traits::One;
use sp_io::hashing::blake2_256;
use frame_system::RawOrigin;
use frame_benchmarking::{benchmarks, account};
use crate::Module as Staking;
use frame_system::Module as System;
const SEED: u32 = 0;
fn create_funded_user<T: Trait>(string: &'static str, n: u32) -> T::AccountId {
let user = account(string, n, SEED);
let balance = T::Currency::minimum_balance() * 100.into();
T::Currency::make_free_balance_be(&user, balance);
user
}
pub fn create_stash_controller<T: Trait>(n: u32) -> Result<(T::AccountId, T::AccountId), &'static str> {
let stash = create_funded_user::<T>("stash", n);
let controller = create_funded_user::<T>("controller", n);
let controller_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(controller.clone());
let reward_destination = RewardDestination::Staked;
let amount = T::Currency::minimum_balance() * 10.into();
Staking::<T>::bond(RawOrigin::Signed(stash.clone()).into(), controller_lookup, amount, reward_destination)?;
return Ok((stash, controller))
}
fn create_validators<T: Trait>(max: u32) -> Result<Vec<<T::Lookup as StaticLookup>::Source>, &'static str> {
let mut validators: Vec<<T::Lookup as StaticLookup>::Source> = Vec::with_capacity(max as usize);
for i in 0 .. max {
let (stash, controller) = create_stash_controller::<T>(i)?;
let validator_prefs = ValidatorPrefs {
commission: Perbill::from_percent(50),
};
Staking::<T>::validate(RawOrigin::Signed(controller).into(), validator_prefs)?;
let stash_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(stash);
validators.push(stash_lookup);
}
Ok(validators)
}
// This function generates v validators and n nominators who are randomly nominating up to MAX_NOMINATIONS.
pub fn create_validators_with_nominators_for_era<T: Trait>(v: u32, n: u32) -> Result<(), &'static str> {
let mut validators: Vec<<T::Lookup as StaticLookup>::Source> = Vec::with_capacity(v as usize);
let mut rng = ChaChaRng::from_seed(SEED.using_encoded(blake2_256));
// Create v validators
for i in 0 .. v {
let (v_stash, v_controller) = create_stash_controller::<T>(i)?;
let validator_prefs = ValidatorPrefs {
commission: Perbill::from_percent(50),
};
Staking::<T>::validate(RawOrigin::Signed(v_controller.clone()).into(), validator_prefs)?;
let stash_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(v_stash.clone());
validators.push(stash_lookup.clone());
}
// Create n nominators
for j in 0 .. n {
let (_n_stash, n_controller) = create_stash_controller::<T>(u32::max_value() - j)?;
// Have them randomly validate
let mut available_validators = validators.clone();
let mut selected_validators: Vec<<T::Lookup as StaticLookup>::Source> = Vec::with_capacity(MAX_NOMINATIONS);
for _ in 0 .. v.min(MAX_NOMINATIONS as u32) {
let selected = rng.next_u32() as usize % available_validators.len();
let validator = available_validators.remove(selected);
selected_validators.push(validator);
}
Staking::<T>::nominate(RawOrigin::Signed(n_controller.clone()).into(), selected_validators)?;
}
ValidatorCount::put(v);
Ok(())
}
// This function generates one validator being nominated by n nominators.
// It starts an era and creates pending payouts.
pub fn create_validator_with_nominators<T: Trait>(n: u32, upper_bound: u32) -> Result<T::AccountId, &'static str> {
let mut points_total = 0;
let mut points_individual = Vec::new();
MinimumValidatorCount::put(0);
let (v_stash, v_controller) = create_stash_controller::<T>(0)?;
let validator_prefs = ValidatorPrefs {
commission: Perbill::from_percent(50),
};
Staking::<T>::validate(RawOrigin::Signed(v_controller.clone()).into(), validator_prefs)?;
let stash_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(v_stash.clone());
points_total += 10;
points_individual.push((v_stash, 10));
// Give the validator n nominators, but keep total users in the system the same.
for i in 0 .. upper_bound {
let (_n_stash, n_controller) = create_stash_controller::<T>(u32::max_value() - i)?;
if i < n {
Staking::<T>::nominate(RawOrigin::Signed(n_controller.clone()).into(), vec![stash_lookup.clone()])?;
}
}
ValidatorCount::put(1);
// Start a new Era
let new_validators = Staking::<T>::new_era(SessionIndex::one()).unwrap();
assert!(new_validators.len() == 1);
// Give Era Points
let reward = EraRewardPoints::<T::AccountId> {
total: points_total,
individual: points_individual.into_iter().collect(),
};
let current_era = CurrentEra::get().unwrap();
ErasRewardPoints::<T>::insert(current_era, reward);
// Create reward pool
let total_payout = T::Currency::minimum_balance() * 1000.into();
<ErasValidatorReward<T>>::insert(current_era, total_payout);
Ok(v_controller)
}
// This function generates one nominator nominating v validators.
// It starts an era and creates pending payouts.
pub fn create_nominator_with_validators<T: Trait>(v: u32) -> Result<(T::AccountId, Vec<T::AccountId>), &'static str> {
let mut validators = Vec::new();
let mut points_total = 0;
let mut points_individual = Vec::new();
MinimumValidatorCount::put(0);
// Create v validators
let mut validator_lookups = Vec::new();
for i in 0 .. v {
let (v_stash, v_controller) = create_stash_controller::<T>(i)?;
let validator_prefs = ValidatorPrefs {
commission: Perbill::from_percent(50),
};
Staking::<T>::validate(RawOrigin::Signed(v_controller.clone()).into(), validator_prefs)?;
let stash_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(v_stash.clone());
points_total += 10;
points_individual.push((v_stash.clone(), 10));
validator_lookups.push(stash_lookup);
// Add to the list if it is less than the number we want the nominator to have
if validators.len() < v as usize {
validators.push(v_stash.clone())
}
}
// Create a nominator
let (_n_stash, n_controller) = create_stash_controller::<T>(u32::max_value())?;
Staking::<T>::nominate(RawOrigin::Signed(n_controller.clone()).into(), validator_lookups)?;
ValidatorCount::put(v);
// Start a new Era
let new_validators = Staking::<T>::new_era(SessionIndex::one()).unwrap();
assert!(new_validators.len() == v as usize);
// Give Era Points
let reward = EraRewardPoints::<T::AccountId> {
total: points_total,
individual: points_individual.into_iter().collect(),
};
let current_era = CurrentEra::get().unwrap();
ErasRewardPoints::<T>::insert(current_era, reward);
// Create reward pool
let total_payout = T::Currency::minimum_balance() * 1000.into();
<ErasValidatorReward<T>>::insert(current_era, total_payout);
Ok((n_controller, validators))
}
benchmarks! {
_{
// User account seed
let u in 0 .. 1000 => ();
}
bond {
let u in ...;
let stash = create_funded_user::<T>("stash",u);
let controller = create_funded_user::<T>("controller", u);
let controller_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(controller);
let reward_destination = RewardDestination::Staked;
let amount = T::Currency::minimum_balance() * 10.into();
}: _(RawOrigin::Signed(stash), controller_lookup, amount, reward_destination)
bond_extra {
let u in ...;
let (stash, _) = create_stash_controller::<T>(u)?;
let max_additional = T::Currency::minimum_balance() * 10.into();
}: _(RawOrigin::Signed(stash), max_additional)
unbond {
let u in ...;
let (_, controller) = create_stash_controller::<T>(u)?;
let amount = T::Currency::minimum_balance() * 10.into();
}: _(RawOrigin::Signed(controller), amount)
// Worst case scenario, everything is removed after the bonding duration
withdraw_unbonded {
let u in ...;
let (stash, controller) = create_stash_controller::<T>(u)?;
let amount = T::Currency::minimum_balance() * 10.into();
Staking::<T>::unbond(RawOrigin::Signed(controller.clone()).into(), amount)?;
let current_block = System::<T>::block_number();
// let unbond_block = current_block + T::BondingDuration::get().into() + 10.into();
// System::<T>::set_block_number(unbond_block);
}: _(RawOrigin::Signed(controller))
validate {
let u in ...;
let (_, controller) = create_stash_controller::<T>(u)?;
let prefs = ValidatorPrefs::default();
}: _(RawOrigin::Signed(controller), prefs)
// Worst case scenario, MAX_NOMINATIONS
nominate {
let n in 1 .. MAX_NOMINATIONS as u32;
let (_, controller) = create_stash_controller::<T>(n + 1)?;
let validators = create_validators::<T>(n)?;
}: _(RawOrigin::Signed(controller), validators)
chill {
let u in ...;
let (_, controller) = create_stash_controller::<T>(u)?;
}: _(RawOrigin::Signed(controller))
set_payee {
let u in ...;
let (_, controller) = create_stash_controller::<T>(u)?;
}: _(RawOrigin::Signed(controller), RewardDestination::Controller)
set_controller {
let u in ...;
let (stash, _) = create_stash_controller::<T>(u)?;
let new_controller = create_funded_user::<T>("new_controller", u);
let new_controller_lookup = T::Lookup::unlookup(new_controller);
}: _(RawOrigin::Signed(stash), new_controller_lookup)
set_validator_count {
let c in 0 .. 1000;
}: _(RawOrigin::Root, c)
force_no_eras { let i in 1 .. 1; }: _(RawOrigin::Root)
force_new_era {let i in 1 .. 1; }: _(RawOrigin::Root)
force_new_era_always { let i in 1 .. 1; }: _(RawOrigin::Root)
// Worst case scenario, the list of invulnerables is very long.
set_invulnerables {
let v in 0 .. 1000;
let mut invulnerables = Vec::new();
for i in 0 .. v {
invulnerables.push(account("invulnerable", i, SEED));
}
}: _(RawOrigin::Root, invulnerables)
force_unstake {
let u in ...;
let (stash, _) = create_stash_controller::<T>(u)?;
}: _(RawOrigin::Root, stash)
cancel_deferred_slash {
let s in 1 .. 1000;
let mut unapplied_slashes = Vec::new();
let era = EraIndex::one();
for _ in 0 .. 1000 {
unapplied_slashes.push(UnappliedSlash::<T::AccountId, BalanceOf<T>>::default());
}
UnappliedSlashes::<T>::insert(era, &unapplied_slashes);
let slash_indices: Vec<u32> = (0 .. s).collect();
}: _(RawOrigin::Root, era, slash_indices)
payout_validator {
let n in 1 .. MAX_NOMINATIONS as u32;
let validator = create_validator_with_nominators::<T>(n, MAX_NOMINATIONS as u32)?;
let current_era = CurrentEra::get().unwrap();
}: _(RawOrigin::Signed(validator), current_era)
payout_nominator {
let v in 0 .. MAX_NOMINATIONS as u32;
let (nominator, validators) = create_nominator_with_validators::<T>(v)?;
let current_era = CurrentEra::get().unwrap();
let find_nominator = validators.into_iter().map(|x| (x, 0)).collect();
}: _(RawOrigin::Signed(nominator), current_era, find_nominator)
rebond {
let l in 1 .. 1000;
let (_, controller) = create_stash_controller::<T>(u)?;
let mut staking_ledger = Ledger::<T>::get(controller.clone()).unwrap();
let unlock_chunk = UnlockChunk::<BalanceOf<T>> {
value: 1.into(),
era: EraIndex::zero(),
};
for _ in 0 .. l {
staking_ledger.unlocking.push(unlock_chunk.clone())
}
Ledger::<T>::insert(controller.clone(), staking_ledger);
}: _(RawOrigin::Signed(controller), (l + 100).into())
set_history_depth {
let e in 1 .. 100;
HistoryDepth::put(e);
CurrentEra::put(e);
for i in 0 .. e {
<ErasStakers<T>>::insert(i, T::AccountId::default(), Exposure::<T::AccountId, BalanceOf<T>>::default());
<ErasStakersClipped<T>>::insert(i, T::AccountId::default(), Exposure::<T::AccountId, BalanceOf<T>>::default());
<ErasValidatorPrefs<T>>::insert(i, T::AccountId::default(), ValidatorPrefs::default());
<ErasValidatorReward<T>>::insert(i, BalanceOf::<T>::one());
<ErasRewardPoints<T>>::insert(i, EraRewardPoints::<T::AccountId>::default());
<ErasTotalStake<T>>::insert(i, BalanceOf::<T>::one());
ErasStartSessionIndex::insert(i, i);
}
}: _(RawOrigin::Root, EraIndex::zero())
reap_stash {
let u in 1 .. 1000;
let (stash, controller) = create_stash_controller::<T>(u)?;
T::Currency::make_free_balance_be(&stash, 0.into());
}: _(RawOrigin::Signed(controller), stash)
new_era {
let v in 1 .. 10;
let n in 1 .. 100;
MinimumValidatorCount::put(0);
create_validators_with_nominators_for_era::<T>(v, n)?;
let session_index = SessionIndex::one();
}: {
let validators = Staking::<T>::new_era(session_index).ok_or("`new_era` failed")?;
assert!(validators.len() == v as usize);
}
do_slash {
let l in 1 .. 1000;
let (stash, controller) = create_stash_controller::<T>(0)?;
let mut staking_ledger = Ledger::<T>::get(controller.clone()).unwrap();
let unlock_chunk = UnlockChunk::<BalanceOf<T>> {
value: 1.into(),
era: EraIndex::zero(),
};
for _ in 0 .. l {
staking_ledger.unlocking.push(unlock_chunk.clone())
}
Ledger::<T>::insert(controller.clone(), staking_ledger.clone());
let slash_amount = T::Currency::minimum_balance() * 10.into();
}: {
crate::slashing::do_slash::<T>(
&stash,
slash_amount,
&mut BalanceOf::<T>::zero(),
&mut NegativeImbalanceOf::<T>::zero()
);
}
}
#[cfg(test)]
mod tests {
use crate::*;
use crate::mock::*;
use frame_support::assert_ok;
use crate::benchmarking::{
create_validators_with_nominators_for_era,
create_validator_with_nominators,
create_nominator_with_validators,
};
#[test]
fn create_validators_with_nominators_for_era_works() {
ExtBuilder::default().stakers(false).build().execute_with(|| {
let v = 10;
let n = 100;
create_validators_with_nominators_for_era::<Test>(v,n).unwrap();
let count_validators = Validators::<Test>::iter().count();
let count_nominators = Nominators::<Test>::iter().count();
assert_eq!(count_validators, v as usize);
assert_eq!(count_nominators, n as usize);
});
}
#[test]
fn create_validator_with_nominators_works() {
ExtBuilder::default().stakers(false).build().execute_with(|| {
let n = 10;
let validator = create_validator_with_nominators::<Test>(
n,
MAX_NOMINATIONS as u32,
).unwrap();
let current_era = CurrentEra::get().unwrap();
let controller = validator;
let ledger = Staking::ledger(&controller).unwrap();
let stash = &ledger.stash;
let original_free_balance = Balances::free_balance(stash);
assert_ok!(Staking::payout_validator(Origin::signed(controller), current_era));
let new_free_balance = Balances::free_balance(stash);
assert!(original_free_balance < new_free_balance);
});
}
#[test]
fn create_nominator_with_validators_works() {
ExtBuilder::default().stakers(false).build().execute_with(|| {
let v = 5;
let (nominator, validators) = create_nominator_with_validators::<Test>(v).unwrap();
let current_era = CurrentEra::get().unwrap();
let controller = nominator;
let ledger = Staking::ledger(&controller).unwrap();
let stash = &ledger.stash;
let find_nominator = validators.into_iter().map(|x| (x, 0)).collect();
let original_free_balance = Balances::free_balance(stash);
assert_ok!(Staking::payout_nominator(Origin::signed(controller), current_era, find_nominator));
let new_free_balance = Balances::free_balance(stash);
assert!(original_free_balance < new_free_balance);
});
}
}
+11 -9
View File
@@ -254,6 +254,8 @@ mod mock;
#[cfg(test)]
mod tests;
mod slashing;
#[cfg(any(feature = "runtime-benchmarks", test))]
pub mod benchmarking;
pub mod inflation;
@@ -287,7 +289,7 @@ use sp_phragmen::ExtendedBalance;
use frame_support::traits::MigrateAccount;
const DEFAULT_MINIMUM_VALIDATOR_COUNT: u32 = 4;
const MAX_NOMINATIONS: usize = 16;
pub const MAX_NOMINATIONS: usize = 16;
const MAX_UNLOCKING_CHUNKS: usize = 32;
const STAKING_ID: LockIdentifier = *b"staking ";
@@ -1281,6 +1283,8 @@ decl_module! {
}
}
// ----- Root calls.
/// The ideal number of validators.
#[weight = SimpleDispatchInfo::FixedNormal(5_000)]
fn set_validator_count(origin, #[compact] new: u32) {
@@ -1288,8 +1292,6 @@ decl_module! {
ValidatorCount::put(new);
}
// ----- Root calls.
/// Force there to be no new eras indefinitely.
///
/// # <weight>
@@ -1357,7 +1359,7 @@ decl_module! {
.or_else(ensure_root)?;
ensure!(!slash_indices.is_empty(), Error::<T>::EmptyTargets);
ensure!(Self::is_sorted_and_unique(&slash_indices), Error::<T>::NotSortedAndUnique);
ensure!(is_sorted_and_unique(&slash_indices), Error::<T>::NotSortedAndUnique);
let mut unapplied = <Self as Store>::UnappliedSlashes::get(&era);
let last_item = slash_indices[slash_indices.len() - 1];
@@ -1510,11 +1512,6 @@ impl<T: Trait> Module<T> {
Self::bonded(stash).and_then(Self::ledger).map(|l| l.active).unwrap_or_default()
}
/// Check that list is sorted and has no duplicates.
fn is_sorted_and_unique(list: &Vec<u32>) -> bool {
list.windows(2).all(|w| w[0] < w[1])
}
// MUTABLES (DANGEROUS)
fn do_payout_nominator(who: T::AccountId, era: EraIndex, validators: Vec<(T::AccountId, u32)>)
@@ -2220,3 +2217,8 @@ impl<T, Reporter, Offender, R, O> ReportOffence<Reporter, Offender, O>
}
}
}
/// Check that list is sorted and has no duplicates.
fn is_sorted_and_unique(list: &[u32]) -> bool {
list.windows(2).all(|w| w[0] < w[1])
}
+22 -11
View File
@@ -226,6 +226,7 @@ pub struct ExtBuilder {
fair: bool,
num_validators: Option<u32>,
invulnerables: Vec<u64>,
stakers: bool,
}
impl Default for ExtBuilder {
@@ -240,6 +241,7 @@ impl Default for ExtBuilder {
fair: true,
num_validators: None,
invulnerables: vec![],
stakers: true,
}
}
}
@@ -285,6 +287,11 @@ impl ExtBuilder {
EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
SLASH_DEFER_DURATION.with(|v| *v.borrow_mut() = self.slash_defer_duration);
}
pub fn stakers(mut self, has_stakers: bool) -> Self {
self.stakers = has_stakers;
self
}
pub fn build(self) -> sp_io::TestExternalities {
self.set_associated_consts();
let mut storage = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
@@ -320,16 +327,17 @@ impl ExtBuilder {
],
}.assimilate_storage(&mut storage);
let stake_21 = if self.fair { 1000 } else { 2000 };
let stake_31 = if self.validator_pool { balance_factor * 1000 } else { 1 };
let status_41 = if self.validator_pool {
StakerStatus::<AccountId>::Validator
} else {
StakerStatus::<AccountId>::Idle
};
let nominated = if self.nominate { vec![11, 21] } else { vec![] };
let _ = GenesisConfig::<Test>{
stakers: vec![
let mut stakers = vec![];
if self.stakers {
let stake_21 = if self.fair { 1000 } else { 2000 };
let stake_31 = if self.validator_pool { balance_factor * 1000 } else { 1 };
let status_41 = if self.validator_pool {
StakerStatus::<AccountId>::Validator
} else {
StakerStatus::<AccountId>::Idle
};
let nominated = if self.nominate { vec![11, 21] } else { vec![] };
stakers = vec![
// (stash, controller, staked_amount, status)
(11, 10, balance_factor * 1000, StakerStatus::<AccountId>::Validator),
(21, 20, stake_21, StakerStatus::<AccountId>::Validator),
@@ -337,7 +345,10 @@ impl ExtBuilder {
(41, 40, balance_factor * 1000, status_41),
// nominator
(101, 100, balance_factor * 500, StakerStatus::<AccountId>::Nominator(nominated))
],
];
}
let _ = GenesisConfig::<Test>{
stakers: stakers,
validator_count: self.validator_count,
minimum_validator_count: self.minimum_validator_count,
invulnerables: self.invulnerables,
+1 -1
View File
@@ -566,7 +566,7 @@ pub(crate) fn clear_stash_metadata<T: Trait>(stash: &T::AccountId) {
// apply the slash to a stash account, deducting any missing funds from the reward
// payout, saturating at 0. this is mildly unfair but also an edge-case that
// can only occur when overlapping locked funds have been slashed.
fn do_slash<T: Trait>(
pub fn do_slash<T: Trait>(
stash: &T::AccountId,
value: BalanceOf<T>,
reward_payout: &mut BalanceOf<T>,