Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions prdoc/pr_10822.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
title: 'staking-async: improve benchmarking'
doc:
- audience: Runtime Dev
description: |-
Avoid bulk deletion of validators and nominators when not needed.
crates:
- name: pallet-staking-async
bump: none
131 changes: 12 additions & 119 deletions substrate/frame/staking-async/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,104 +29,19 @@ pub use frame_benchmarking::{
};
use frame_election_provider_support::SortedListProvider;
use frame_support::{
assert_ok,
pallet_prelude::*,
storage::bounded_vec::BoundedVec,
traits::{fungible::Inspect, TryCollect},
assert_ok, pallet_prelude::*, storage::bounded_vec::BoundedVec, traits::fungible::Inspect,
};
use frame_system::RawOrigin;
use pallet_staking_async_rc_client as rc_client;
use sp_runtime::{
traits::{Bounded, One, StaticLookup, Zero},
Perbill, Percent, Saturating,
Perbill, Percent,
};
use sp_staking::currency_to_vote::CurrencyToVote;
use testing_utils::*;

const SEED: u32 = 0;

// This function clears all existing validators and nominators from the set, and generates one new
// validator being nominated by n nominators, and returns the validator stash account and the
// nominators' stash and controller. It also starts plans a new era with this new stakers, and
// returns the planned era index.
pub(crate) fn create_validator_with_nominators<T: Config>(
n: u32,
upper_bound: u32,
dead_controller: bool,
unique_controller: bool,
destination: RewardDestination<T::AccountId>,
) -> Result<(T::AccountId, Vec<(T::AccountId, T::AccountId)>, EraIndex), &'static str> {
// TODO: this can be replaced with `testing_utils` version?
// Clean up any existing state.
clear_validators_and_nominators::<T>();
let mut points_total = 0;
let mut points_individual = Vec::new();

let (v_stash, v_controller) = if unique_controller {
create_unique_stash_controller::<T>(0, 100, destination.clone(), false)?
} else {
create_stash_controller::<T>(0, 100, destination.clone())?
};

let validator_prefs =
ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() };
Staking::<T>::validate(RawOrigin::Signed(v_controller).into(), validator_prefs)?;
let stash_lookup = T::Lookup::unlookup(v_stash.clone());

points_total += 10;
points_individual.push((v_stash.clone(), 10));

let original_nominator_count = Nominators::<T>::count();
let mut nominators = Vec::new();

// 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) = if !dead_controller {
create_stash_controller::<T>(u32::MAX - i, 100, destination.clone())?
} else {
create_unique_stash_controller::<T>(u32::MAX - i, 100, destination.clone(), true)?
};
if i < n {
Staking::<T>::nominate(
RawOrigin::Signed(n_controller.clone()).into(),
vec![stash_lookup.clone()],
)?;
nominators.push((n_stash, n_controller));
}
}

ValidatorCount::<T>::put(1);

// Start a new Era
let new_validators = Rotator::<T>::legacy_insta_plan_era();
let planned_era = CurrentEra::<T>::get().unwrap_or_default();

assert_eq!(new_validators.len(), 1, "New validators is not 1");
assert_eq!(new_validators[0], v_stash, "Our validator was not selected");
assert_ne!(Validators::<T>::count(), 0, "New validators count wrong");
assert_eq!(
Nominators::<T>::count(),
original_nominator_count + nominators.len() as u32,
"New nominators count wrong"
);

// Give Era Points
let reward = EraRewardPoints::<T> {
total: points_total,
individual: points_individual.into_iter().try_collect()?,
};

ErasRewardPoints::<T>::insert(planned_era, reward);

// Create reward pool
let total_payout = asset::existential_deposit::<T>()
.saturating_mul(upper_bound.into())
.saturating_mul(1000u32.into());
<ErasValidatorReward<T>>::insert(planned_era, total_payout);

Ok((v_stash, nominators, planned_era))
}

struct ListScenario<T: Config> {
/// Stash that is expected to be moved.
origin_stash1: T::AccountId,
Expand Down Expand Up @@ -240,9 +155,6 @@ mod benchmarks {

#[benchmark]
fn bond_extra() -> Result<(), BenchmarkError> {
// clean up any existing state.
clear_validators_and_nominators::<T>();

let origin_weight = Staking::<T>::min_nominator_bond();

// setup the worst case list scenario.
Expand Down Expand Up @@ -278,9 +190,6 @@ mod benchmarks {

#[benchmark]
fn unbond() -> Result<(), BenchmarkError> {
// clean up any existing state.
clear_validators_and_nominators::<T>();

// the weight the nominator will start at. The value used here is expected to be
// significantly higher than the first position in a list (e.g. the first bag threshold).
let origin_weight = BalanceOf::<T>::try_from(952_994_955_240_703u128)
Expand Down Expand Up @@ -329,9 +238,6 @@ mod benchmarks {
#[benchmark]
// Worst case scenario, everything is removed after the bonding duration
fn withdraw_unbonded_kill() -> Result<(), BenchmarkError> {
// clean up any existing state.
clear_validators_and_nominators::<T>();

let origin_weight = Staking::<T>::min_nominator_bond();

// setup a worst case list scenario. Note that we don't care about the setup of the
Expand Down Expand Up @@ -453,9 +359,6 @@ mod benchmarks {
#[benchmark]
// Worst case scenario, T::MaxNominations::get()
fn nominate(n: Linear<1, { MaxNominationsOf::<T>::get() }>) -> Result<(), BenchmarkError> {
// clean up any existing state.
clear_validators_and_nominators::<T>();

let origin_weight = Staking::<T>::min_nominator_bond();

// setup a worst case list scenario. Note we don't care about the destination position,
Expand Down Expand Up @@ -486,9 +389,6 @@ mod benchmarks {

#[benchmark]
fn chill() -> Result<(), BenchmarkError> {
// clean up any existing state.
clear_validators_and_nominators::<T>();

let origin_weight = Staking::<T>::min_nominator_bond();

// setup a worst case list scenario. Note that we don't care about the setup of the
Expand Down Expand Up @@ -630,9 +530,6 @@ mod benchmarks {

#[benchmark]
fn force_unstake() -> Result<(), BenchmarkError> {
// Clean up any existing state.
clear_validators_and_nominators::<T>();

let origin_weight = Staking::<T>::min_nominator_bond();

// setup a worst case list scenario. Note that we don't care about the setup of the
Expand Down Expand Up @@ -736,9 +633,6 @@ mod benchmarks {

#[benchmark]
fn rebond(l: Linear<1, { T::MaxUnlockingChunks::get() as u32 }>) -> Result<(), BenchmarkError> {
// clean up any existing state.
clear_validators_and_nominators::<T>();

let origin_weight = Pallet::<T>::min_nominator_bond()
// we use 100 to play friendly with the list threshold values in the mock
.max(100u32.into());
Expand Down Expand Up @@ -782,9 +676,6 @@ mod benchmarks {

#[benchmark]
fn reap_stash() -> Result<(), BenchmarkError> {
// clean up any existing state.
clear_validators_and_nominators::<T>();

let origin_weight = Staking::<T>::min_nominator_bond();

// setup a worst case list scenario. Note that we don't care about the setup of the
Expand Down Expand Up @@ -863,9 +754,6 @@ mod benchmarks {

#[benchmark]
fn chill_other() -> Result<(), BenchmarkError> {
// clean up any existing state.
clear_validators_and_nominators::<T>();

let origin_weight = Staking::<T>::min_nominator_bond();

// setup a worst case list scenario. Note that we don't care about the setup of the
Expand Down Expand Up @@ -898,11 +786,16 @@ mod benchmarks {

#[benchmark]
fn force_apply_min_commission() -> Result<(), BenchmarkError> {
// Clean up any existing state
clear_validators_and_nominators::<T>();

// Create a validator with a commission of 50%
let (stash, controller) = create_stash_controller::<T>(1, 1, RewardDestination::Staked)?;
// Use existing validator or create new one - no clearing needed
let (stash, controller) = if let Some(stash) = Validators::<T>::iter_keys().next() {
let controller = Bonded::<T>::get(&stash).ok_or(
"validator not
bonded",
)?;
(stash, controller)
} else {
create_stash_controller::<T>(1, 1, RewardDestination::Staked)?
};
let validator_prefs =
ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() };
Staking::<T>::validate(RawOrigin::Signed(controller).into(), validator_prefs)?;
Expand Down
143 changes: 140 additions & 3 deletions substrate/frame/staking-async/src/testing_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,23 @@

use crate::{Pallet as Staking, *};
use frame_benchmarking::account;
use frame_election_provider_support::SortedListProvider;
use frame_support::pallet_prelude::*;
use frame_system::RawOrigin;
use rand_chacha::{
rand_core::{RngCore, SeedableRng},
ChaChaRng,
};
use sp_io::hashing::blake2_256;

use frame_election_provider_support::SortedListProvider;
use frame_support::pallet_prelude::*;
use sp_runtime::{traits::StaticLookup, Perbill};

#[cfg(feature = "runtime-benchmarks")]
use crate::session_rotation::{Eras, Rotator};
#[cfg(feature = "runtime-benchmarks")]
use frame_support::traits::TryCollect;
#[cfg(feature = "runtime-benchmarks")]
use sp_runtime::traits::Zero;

const SEED: u32 = 0;

/// This function removes all validators and nominators from storage.
Expand Down Expand Up @@ -237,6 +243,137 @@ pub fn create_validators_with_nominators_for_era<T: Config>(
Ok(validator_chosen)
}

/// This function clears all existing validators and nominators from the set, and generates one new
/// validator being nominated by n nominators, and returns the validator stash account and the
/// nominators' stash and controller. It also starts plans a new era with this new stakers, and
/// returns the planned era index.
#[cfg(feature = "runtime-benchmarks")]
pub fn create_validator_with_nominators<T: Config>(
n: u32,
upper_bound: u32,
dead_controller: bool,
unique_controller: bool,
destination: RewardDestination<T::AccountId>,
) -> Result<(T::AccountId, Vec<(T::AccountId, T::AccountId)>, EraIndex), &'static str> {
// For payout to work, we need an era that has ended (< ActiveEra).
// Check if there's a claimable era with valid exposure.
let active_era = ActiveEra::<T>::get().map(|e| e.index).unwrap_or(0);

// Try to find a claimable era (active_era - 1 if it exists)
if active_era > 0 {
let claimable_era = active_era - 1;

// Try to find an existing validator with sufficient exposure in the claimable era
// Must have actual stake (not just an empty default exposure)
let existing_validator = Validators::<T>::iter_keys().find(|v| {
let exposure = Eras::<T>::get_full_exposure(claimable_era, v);
!exposure.total.is_zero() && exposure.others.len() >= n as usize
});

if let Some(v_stash) = existing_validator {
// Use existing validator and its nominators
let exposure = Eras::<T>::get_full_exposure(claimable_era, &v_stash);
let nominators: Vec<(T::AccountId, T::AccountId)> = exposure
.others
.iter()
.take(n as usize)
.map(|ind| {
let controller = Bonded::<T>::get(&ind.who).unwrap_or_else(|| ind.who.clone());
(ind.who.clone(), controller)
})
.collect();

// Set up era points if not already present
if ErasRewardPoints::<T>::get(claimable_era).total == 0 {
let reward = EraRewardPoints::<T> {
total: 10,
individual: vec![(v_stash.clone(), 10)].into_iter().try_collect()?,
};
ErasRewardPoints::<T>::insert(claimable_era, reward);
}

// Set up validator reward if not already present
if ErasValidatorReward::<T>::get(claimable_era).is_none() {
let total_payout = asset::existential_deposit::<T>()
.saturating_mul(upper_bound.into())
.saturating_mul(1000u32.into());
<ErasValidatorReward<T>>::insert(claimable_era, total_payout);
}

return Ok((v_stash, nominators, claimable_era));
}
}

// Fall back to clearing and creating fresh state
clear_validators_and_nominators::<T>();
let mut points_total = 0;
let mut points_individual = Vec::new();

let (v_stash, v_controller) = if unique_controller {
create_unique_stash_controller::<T>(0, 100, destination.clone(), false)?
} else {
create_stash_controller::<T>(0, 100, destination.clone())?
};

let validator_prefs =
ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() };
Staking::<T>::validate(RawOrigin::Signed(v_controller).into(), validator_prefs)?;
let stash_lookup = T::Lookup::unlookup(v_stash.clone());

points_total += 10;
points_individual.push((v_stash.clone(), 10));

let original_nominator_count = Nominators::<T>::count();
let mut nominators = Vec::new();

// 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) = if !dead_controller {
create_stash_controller::<T>(u32::MAX - i, 100, destination.clone())?
} else {
create_unique_stash_controller::<T>(u32::MAX - i, 100, destination.clone(), true)?
};
if i < n {
Staking::<T>::nominate(
RawOrigin::Signed(n_controller.clone()).into(),
vec![stash_lookup.clone()],
)?;
nominators.push((n_stash, n_controller));
}
}

ValidatorCount::<T>::put(1);

// Start a new Era
let new_validators = Rotator::<T>::legacy_insta_plan_era();
let new_planned_era = CurrentEra::<T>::get().unwrap_or_default();

assert_eq!(new_validators.len(), 1, "New validators is not 1");
assert_eq!(new_validators[0], v_stash, "Our validator was not selected");
assert_ne!(Validators::<T>::count(), 0, "New validators count wrong");
assert_eq!(
Nominators::<T>::count(),
original_nominator_count + nominators.len() as u32,
"New nominators count wrong"
);

// Give Era Points
let reward = EraRewardPoints::<T> {
total: points_total,
individual: points_individual.into_iter().try_collect()?,
};

ErasRewardPoints::<T>::insert(new_planned_era, reward);

// Create reward pool
let total_payout = asset::existential_deposit::<T>()
.saturating_mul(upper_bound.into())
.saturating_mul(1000u32.into());
<ErasValidatorReward<T>>::insert(new_planned_era, total_payout);

Ok((v_stash, nominators, new_planned_era))
}

/// get the current era.
pub fn current_era<T: Config>() -> EraIndex {
CurrentEra::<T>::get().unwrap_or(0)
Expand Down
Loading