First draft of offchain phragmen weights (#6032)

* Fist draft of offchain weights

* Round of review feedback

* Update frame/staking/src/lib.rs

* Fix fuzzer

* Remove some redundant comment

* Weight refund for submit solution -- potentially revert.

* First version with custom trimming of the result.

* Update frame/staking/src/benchmarking.rs

Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>

* Update frame/staking/src/benchmarking.rs

Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>

* Apply suggestions from code review

Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>
Co-authored-by: thiolliere <gui.thiolliere@gmail.com>

* Update frame/staking/src/benchmarking.rs

Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>

* Update frame/staking/src/benchmarking.rs

Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>

* Some improvements

* Benchmark submit solution without phragmen (PR for First draft of offchain phragmen weights) (#6073)

* implementation of new benchmark

* address comments

* replace test

* Update frame/staking/src/lib.rs

Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>

* update weight

* Fix refund

* Clean and rady for final bench

* Fix line-wdith

* Fix gitlab build

* Fix line-wdith

* Fix test macro

* Update frame/staking/src/lib.rs

Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>

* Update frame/staking/src/benchmarking.rs

Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>

* Better length check

* Update frame/staking/src/lib.rs

Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>

* Update final weight coefficients

* Update frame/staking/src/lib.rs

* Apply suggestions from code review

* Update frame/staking/src/testing_utils.rs

* Try and fix the line-width

* Revert "Try and fix the line-width"

This reverts commit b4e284727220085b9b3daf7682c4bbf29621da09.

* Try and fix the line-width the correct way

* Revert "Try and fix the line-width the correct way"

This reverts commit 04fce128e851c9584f9f0d708a5a73cae799d8c8.

Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>
Co-authored-by: thiolliere <gui.thiolliere@gmail.com>
Co-authored-by: Gavin Wood <gavin@parity.io>
This commit is contained in:
Kian Paimani
2020-05-23 20:08:42 +02:00
committed by GitHub
parent 82a832bc3a
commit 0133185c81
14 changed files with 1201 additions and 854 deletions
+233 -100
View File
@@ -18,53 +18,17 @@
//! Staking pallet benchmarking.
use super::*;
use rand_chacha::{rand_core::{RngCore, SeedableRng}, ChaChaRng};
use sp_runtime::traits::{Dispatchable, One};
use sp_io::hashing::blake2_256;
use frame_system::RawOrigin;
use frame_benchmarking::{benchmarks, account};
use crate::Module as Staking;
use testing_utils::*;
use sp_runtime::{traits::{Dispatchable, One}};
use frame_system::RawOrigin;
pub use frame_benchmarking::{benchmarks, account};
const SEED: u32 = 0;
const MAX_SPANS: u32 = 100;
const MAX_VALIDATORS: u32 = 1000;
const MAX_SLASHES: u32 = 1000;
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)
}
// Add slashing spans to a user account. Not relevant for actual use, only to benchmark
// read and write operations.
fn add_slashing_spans<T: Trait>(who: &T::AccountId, spans: u32) {
@@ -81,51 +45,15 @@ fn add_slashing_spans<T: Trait>(who: &T::AccountId, spans: u32) {
SlashingSpans::<T>::insert(who, slashing_spans);
}
// 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, and returns
//the validator stash account. It also starts an era and creates pending payouts.
// This function generates one validator being nominated by n nominators, and returns the validator
// stash account. It also 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 (v_stash, v_controller) = create_stash_controller::<T>(0, 100)?;
let validator_prefs = ValidatorPrefs {
commission: Perbill::from_percent(50),
};
@@ -137,7 +65,7 @@ pub fn create_validator_with_nominators<T: Trait>(n: u32, upper_bound: u32) -> R
// 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)?;
let (_n_stash, n_controller) = create_stash_controller::<T>(u32::max_value() - i, 100)?;
if i < n {
Staking::<T>::nominate(RawOrigin::Signed(n_controller.clone()).into(), vec![stash_lookup.clone()])?;
}
@@ -174,8 +102,8 @@ benchmarks! {
bond {
let u in ...;
let stash = create_funded_user::<T>("stash",u);
let controller = create_funded_user::<T>("controller", u);
let stash = create_funded_user::<T>("stash", u, 100);
let controller = create_funded_user::<T>("controller", u, 100);
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();
@@ -187,7 +115,7 @@ benchmarks! {
bond_extra {
let u in ...;
let (stash, controller) = create_stash_controller::<T>(u)?;
let (stash, controller) = create_stash_controller::<T>(u, 100)?;
let max_additional = T::Currency::minimum_balance() * 10.into();
let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created before")?;
let original_bonded: BalanceOf<T> = ledger.active;
@@ -200,7 +128,7 @@ benchmarks! {
unbond {
let u in ...;
let (_, controller) = create_stash_controller::<T>(u)?;
let (_, controller) = create_stash_controller::<T>(u, 100)?;
let amount = T::Currency::minimum_balance() * 10.into();
let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created before")?;
let original_bonded: BalanceOf<T> = ledger.active;
@@ -215,7 +143,7 @@ benchmarks! {
withdraw_unbonded_update {
// Slashing Spans
let s in 0 .. MAX_SPANS;
let (stash, controller) = create_stash_controller::<T>(0)?;
let (stash, controller) = create_stash_controller::<T>(0, 100)?;
add_slashing_spans::<T>(&stash, s);
let amount = T::Currency::minimum_balance() * 5.into(); // Half of total
Staking::<T>::unbond(RawOrigin::Signed(controller.clone()).into(), amount)?;
@@ -233,7 +161,7 @@ benchmarks! {
withdraw_unbonded_kill {
// Slashing Spans
let s in 0 .. MAX_SPANS;
let (stash, controller) = create_stash_controller::<T>(0)?;
let (stash, controller) = create_stash_controller::<T>(0, 100)?;
add_slashing_spans::<T>(&stash, s);
let amount = T::Currency::minimum_balance() * 10.into();
Staking::<T>::unbond(RawOrigin::Signed(controller.clone()).into(), amount)?;
@@ -247,7 +175,7 @@ benchmarks! {
validate {
let u in ...;
let (stash, controller) = create_stash_controller::<T>(u)?;
let (stash, controller) = create_stash_controller::<T>(u, 100)?;
let prefs = ValidatorPrefs::default();
}: _(RawOrigin::Signed(controller), prefs)
verify {
@@ -257,8 +185,8 @@ benchmarks! {
// Worst case scenario, MAX_NOMINATIONS
nominate {
let n in 1 .. MAX_NOMINATIONS as u32;
let (stash, controller) = create_stash_controller::<T>(n + 1)?;
let validators = create_validators::<T>(n)?;
let (stash, controller) = create_stash_controller::<T>(n + 1, 100)?;
let validators = create_validators::<T>(n, 100)?;
}: _(RawOrigin::Signed(controller), validators)
verify {
assert!(Nominators::<T>::contains_key(stash));
@@ -266,12 +194,12 @@ benchmarks! {
chill {
let u in ...;
let (_, controller) = create_stash_controller::<T>(u)?;
let (_, controller) = create_stash_controller::<T>(u, 100)?;
}: _(RawOrigin::Signed(controller))
set_payee {
let u in ...;
let (stash, controller) = create_stash_controller::<T>(u)?;
let (stash, controller) = create_stash_controller::<T>(u, 100)?;
assert_eq!(Payee::<T>::get(&stash), RewardDestination::Staked);
}: _(RawOrigin::Signed(controller), RewardDestination::Controller)
verify {
@@ -280,8 +208,8 @@ benchmarks! {
set_controller {
let u in ...;
let (stash, _) = create_stash_controller::<T>(u)?;
let new_controller = create_funded_user::<T>("new_controller", u);
let (stash, _) = create_stash_controller::<T>(u, 100)?;
let new_controller = create_funded_user::<T>("new_controller", u, 100);
let new_controller_lookup = T::Lookup::unlookup(new_controller.clone());
}: _(RawOrigin::Signed(stash), new_controller_lookup)
verify {
@@ -319,7 +247,7 @@ benchmarks! {
force_unstake {
// Slashing Spans
let s in 0 .. MAX_SPANS;
let (stash, controller) = create_stash_controller::<T>(0)?;
let (stash, controller) = create_stash_controller::<T>(0, 100)?;
add_slashing_spans::<T>(&stash, s);
}: _(RawOrigin::Root, stash, s)
verify {
@@ -356,7 +284,7 @@ benchmarks! {
rebond {
let l in 1 .. MAX_UNLOCKING_CHUNKS as u32;
let (_, controller) = create_stash_controller::<T>(u)?;
let (_, controller) = create_stash_controller::<T>(u, 100)?;
let mut staking_ledger = Ledger::<T>::get(controller.clone()).unwrap();
let unlock_chunk = UnlockChunk::<BalanceOf<T>> {
value: 1.into(),
@@ -394,7 +322,7 @@ benchmarks! {
reap_stash {
let s in 1 .. MAX_SPANS;
let (stash, controller) = create_stash_controller::<T>(0)?;
let (stash, controller) = create_stash_controller::<T>(0, 100)?;
add_slashing_spans::<T>(&stash, s);
T::Currency::make_free_balance_be(&stash, 0.into());
}: _(RawOrigin::Signed(controller), stash.clone(), s)
@@ -406,7 +334,7 @@ benchmarks! {
let v in 1 .. 10;
let n in 1 .. 100;
MinimumValidatorCount::put(0);
create_validators_with_nominators_for_era::<T>(v, n)?;
create_validators_with_nominators_for_era::<T>(v, n, MAX_NOMINATIONS, false, None)?;
let session_index = SessionIndex::one();
}: {
let validators = Staking::<T>::new_era(session_index).ok_or("`new_era` failed")?;
@@ -415,7 +343,7 @@ benchmarks! {
do_slash {
let l in 1 .. MAX_UNLOCKING_CHUNKS as u32;
let (stash, controller) = create_stash_controller::<T>(0)?;
let (stash, controller) = create_stash_controller::<T>(0, 100)?;
let mut staking_ledger = Ledger::<T>::get(controller.clone()).unwrap();
let unlock_chunk = UnlockChunk::<BalanceOf<T>> {
value: 1.into(),
@@ -443,7 +371,7 @@ benchmarks! {
let v in 1 .. 10;
let n in 1 .. 100;
MinimumValidatorCount::put(0);
create_validators_with_nominators_for_era::<T>(v, n)?;
create_validators_with_nominators_for_era::<T>(v, n, MAX_NOMINATIONS, false, None)?;
// Start a new Era
let new_validators = Staking::<T>::new_era(SessionIndex::one()).unwrap();
assert!(new_validators.len() == v as usize);
@@ -477,6 +405,198 @@ benchmarks! {
call.dispatch(RawOrigin::Signed(caller.clone()).into())?;
}
}
// This benchmark create `v` validators intent, `n` nominators intent, each nominator nominate
// MAX_NOMINATIONS in the set of the first `w` validators.
// It builds a solution with `w` winners composed of nominated validators randomly nominated,
// `a` assignment with MAX_NOMINATIONS.
submit_solution_initial {
// number of validator intent
let v in 1000 .. 2000;
// number of nominator intent
let n in 1000 .. 2000;
// number of assignments. Basically, number of active nominators.
let a in 200 .. 500;
// number of winners, also ValidatorCount
let w in 16 .. 100;
ensure!(w as usize >= MAX_NOMINATIONS, "doesn't support lower value");
let winners = create_validators_with_nominators_for_era::<T>(
v,
n,
MAX_NOMINATIONS,
false,
Some(w),
)?;
// needed for the solution to be generates.
assert!(<Staking<T>>::create_stakers_snapshot().0);
// set number of winners
ValidatorCount::put(w);
// create a assignments in total for the w winners.
let (winners, assignments) = create_assignments_for_offchain::<T>(a, winners)?;
let (
winners,
compact,
score,
size
) = offchain_election::prepare_submission::<T>(assignments, winners, false).unwrap();
// needed for the solution to be accepted
<EraElectionStatus<T>>::put(ElectionStatus::Open(T::BlockNumber::from(1u32)));
let era = <Staking<T>>::current_era().unwrap_or(0);
let caller: T::AccountId = account("caller", n, SEED);
}: {
let result = <Staking<T>>::submit_election_solution(
RawOrigin::Signed(caller.clone()).into(),
winners,
compact,
score.clone(),
era,
size,
);
assert!(result.is_ok());
}
verify {
// new solution has been accepted.
assert_eq!(<Staking<T>>::queued_score().unwrap(), score);
}
// same as submit_solution_initial but we place a very weak solution on chian first.
submit_solution_better {
// number of validator intent
let v in 1000 .. 2000;
// number of nominator intent
let n in 1000 .. 2000;
// number of assignments. Basically, number of active nominators.
let a in 200 .. 500;
// number of winners, also ValidatorCount
let w in 16 .. 100;
ensure!(w as usize >= MAX_NOMINATIONS, "doesn't support lower value");
let winners = create_validators_with_nominators_for_era::<T>(
v,
n,
MAX_NOMINATIONS,
false,
Some(w),
)?;
// needed for the solution to be generates.
assert!(<Staking<T>>::create_stakers_snapshot().0);
// set number of winners
ValidatorCount::put(w);
// create a assignments in total for the w winners.
let (winners, assignments) = create_assignments_for_offchain::<T>(a, winners)?;
let single_winner = winners[0].0.clone();
let (
winners,
compact,
score,
size
) = offchain_election::prepare_submission::<T>(assignments, winners, false).unwrap();
// needed for the solution to be accepted
<EraElectionStatus<T>>::put(ElectionStatus::Open(T::BlockNumber::from(1u32)));
let era = <Staking<T>>::current_era().unwrap_or(0);
let caller: T::AccountId = account("caller", n, SEED);
// submit a very bad solution on-chain
{
// this is needed to fool the chain to accept this solution.
ValidatorCount::put(1);
let (winners, compact, score, size) = get_single_winner_solution::<T>(single_winner)?;
assert!(
<Staking<T>>::submit_election_solution(
RawOrigin::Signed(caller.clone()).into(),
winners,
compact,
score.clone(),
era,
size,
).is_ok());
// new solution has been accepted.
assert_eq!(<Staking<T>>::queued_score().unwrap(), score);
ValidatorCount::put(w);
}
}: {
let result = <Staking<T>>::submit_election_solution(
RawOrigin::Signed(caller.clone()).into(),
winners,
compact,
score.clone(),
era,
size,
);
assert!(result.is_ok());
}
verify {
// new solution has been accepted.
assert_eq!(<Staking<T>>::queued_score().unwrap(), score);
}
// This will be early rejected based on the score.
submit_solution_weaker {
// number of validator intent
let v in 1000 .. 2000;
// number of nominator intent
let n in 1000 .. 2000;
MinimumValidatorCount::put(0);
create_validators_with_nominators_for_era::<T>(v, n, MAX_NOMINATIONS, false, None)?;
// needed for the solution to be generates.
assert!(<Staking<T>>::create_stakers_snapshot().0);
// needed for the solution to be accepted
<EraElectionStatus<T>>::put(ElectionStatus::Open(T::BlockNumber::from(1u32)));
let caller: T::AccountId = account("caller", n, SEED);
let era = <Staking<T>>::current_era().unwrap_or(0);
// submit a seq-phragmen with all the good stuff on chain
{
let (winners, compact, score, size) = get_seq_phragmen_solution::<T>(true);
assert!(
<Staking<T>>::submit_election_solution(
RawOrigin::Signed(caller.clone()).into(),
winners,
compact,
score.clone(),
era,
size,
).is_ok()
);
// new solution has been accepted.
assert_eq!(<Staking<T>>::queued_score().unwrap(), score);
}
// prepare a bad solution. This will be very early rejected.
let (winners, compact, score, size) = get_weak_solution::<T>(true);
}: {
assert!(
<Staking<T>>::submit_election_solution(
RawOrigin::Signed(caller.clone()).into(),
winners,
compact,
score.clone(),
era,
size,
).is_err()
);
}
}
#[cfg(test)]
@@ -491,7 +611,8 @@ mod tests {
let v = 10;
let n = 100;
create_validators_with_nominators_for_era::<Test>(v,n).unwrap();
create_validators_with_nominators_for_era::<Test>(v, n, MAX_NOMINATIONS, false, None)
.unwrap();
let count_validators = Validators::<Test>::iter().count();
let count_nominators = Nominators::<Test>::iter().count();
@@ -595,6 +716,18 @@ mod tests {
assert_ok!(test_benchmark_new_era::<Test>());
assert_ok!(test_benchmark_do_slash::<Test>());
assert_ok!(test_benchmark_payout_all::<Test>());
// only run one of them to same time on the CI. ignore the other two.
assert_ok!(test_benchmark_submit_solution_initial::<Test>());
});
}
#[test]
#[ignore]
fn test_benchmarks_offchain() {
ExtBuilder::default().has_stakers(false).build().execute_with(|| {
assert_ok!(test_benchmark_submit_solution_better::<Test>());
assert_ok!(test_benchmark_submit_solution_weaker::<Test>());
});
}
}