diff --git a/frame/session/benchmarking/src/lib.rs b/frame/session/benchmarking/src/lib.rs index cc471893356d5..ee66223fc0b04 100644 --- a/frame/session/benchmarking/src/lib.rs +++ b/frame/session/benchmarking/src/lib.rs @@ -35,7 +35,7 @@ use frame_system::RawOrigin; use pallet_session::{historical::Module as Historical, Module as Session, *}; use pallet_staking::{ benchmarking::create_validator_with_nominators, testing_utils::create_validators, - MAX_NOMINATIONS, + MAX_NOMINATIONS, RewardDestination, }; use sp_runtime::traits::{One, StaticLookup}; @@ -55,7 +55,12 @@ benchmarks! { set_keys { let n in 1 .. MAX_NOMINATIONS as u32; - let v_stash = create_validator_with_nominators::(n, MAX_NOMINATIONS as u32, false)?; + let v_stash = create_validator_with_nominators::( + n, + MAX_NOMINATIONS as u32, + false, + RewardDestination::Staked, + )?; let v_controller = pallet_staking::Module::::bonded(&v_stash).ok_or("not stash")?; let keys = T::Keys::default(); let proof: Vec = vec![0,1,2,3]; @@ -63,7 +68,7 @@ benchmarks! { purge_keys { let n in 1 .. MAX_NOMINATIONS as u32; - let v_stash = create_validator_with_nominators::(n, MAX_NOMINATIONS as u32, false)?; + let v_stash = create_validator_with_nominators::(n, MAX_NOMINATIONS as u32, false, RewardDestination::Staked)?; let v_controller = pallet_staking::Module::::bonded(&v_stash).ok_or("not stash")?; let keys = T::Keys::default(); let proof: Vec = vec![0,1,2,3]; diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index 156b2f81c8429..afda58db4672f 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -29,6 +29,14 @@ const MAX_SPANS: u32 = 100; const MAX_VALIDATORS: u32 = 1000; const MAX_SLASHES: u32 = 1000; +macro_rules! do_whitelist { + ($acc:ident) => { + frame_benchmarking::benchmarking::add_to_whitelist( + frame_system::Account::::hashed_key_for(&$acc).into() + ); + } +} + // Add slashing spans to a user account. Not relevant for actual use, only to benchmark // read and write operations. fn add_slashing_spans(who: &T::AccountId, spans: u32) { @@ -51,11 +59,12 @@ pub fn create_validator_with_nominators( n: u32, upper_bound: u32, dead: bool, + destination: RewardDestination ) -> Result { let mut points_total = 0; let mut points_individual = Vec::new(); - let (v_stash, v_controller) = create_stash_controller::(0, 100)?; + let (v_stash, v_controller) = create_stash_controller::(0, 100, destination.clone())?; let validator_prefs = ValidatorPrefs { commission: Perbill::from_percent(50), }; @@ -68,9 +77,9 @@ pub fn create_validator_with_nominators( // 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 { - create_stash_controller::(u32::max_value() - i, 100)? + create_stash_controller::(u32::max_value() - i, 100, destination.clone())? } else { - create_stash_and_dead_controller::(u32::max_value() - i, 100)? + create_stash_and_dead_controller::(u32::max_value() - i, 100, destination.clone())? }; if i < n { Staking::::nominate(RawOrigin::Signed(n_controller.clone()).into(), vec![stash_lookup.clone()])?; @@ -100,19 +109,18 @@ pub fn create_validator_with_nominators( Ok(v_stash) } +const USER_SEED: u32 = 999666; + benchmarks! { - _{ - // User account seed - let u in 0 .. 1000 => (); - } + _{} bond { - let u in ...; - let stash = create_funded_user::("stash", u, 100); - let controller = create_funded_user::("controller", u, 100); + let stash = create_funded_user::("stash", USER_SEED, 100); + let controller = create_funded_user::("controller", USER_SEED, 100); let controller_lookup: ::Source = T::Lookup::unlookup(controller.clone()); let reward_destination = RewardDestination::Staked; let amount = T::Currency::minimum_balance() * 10.into(); + do_whitelist!(stash); }: _(RawOrigin::Signed(stash.clone()), controller_lookup, amount, reward_destination) verify { assert!(Bonded::::contains_key(stash)); @@ -120,11 +128,11 @@ benchmarks! { } bond_extra { - let u in ...; - let (stash, controller) = create_stash_controller::(u, 100)?; + let (stash, controller) = create_stash_controller::(USER_SEED, 100, Default::default())?; let max_additional = T::Currency::minimum_balance() * 10.into(); let ledger = Ledger::::get(&controller).ok_or("ledger not created before")?; let original_bonded: BalanceOf = ledger.active; + do_whitelist!(stash); }: _(RawOrigin::Signed(stash), max_additional) verify { let ledger = Ledger::::get(&controller).ok_or("ledger not created after")?; @@ -133,11 +141,11 @@ benchmarks! { } unbond { - let u in ...; - let (_, controller) = create_stash_controller::(u, 100)?; + let (_, controller) = create_stash_controller::(USER_SEED, 100, Default::default())?; let amount = T::Currency::minimum_balance() * 10.into(); let ledger = Ledger::::get(&controller).ok_or("ledger not created before")?; let original_bonded: BalanceOf = ledger.active; + do_whitelist!(controller); }: _(RawOrigin::Signed(controller.clone()), amount) verify { let ledger = Ledger::::get(&controller).ok_or("ledger not created after")?; @@ -149,13 +157,14 @@ benchmarks! { withdraw_unbonded_update { // Slashing Spans let s in 0 .. MAX_SPANS; - let (stash, controller) = create_stash_controller::(0, 100)?; + let (stash, controller) = create_stash_controller::(0, 100, Default::default())?; add_slashing_spans::(&stash, s); let amount = T::Currency::minimum_balance() * 5.into(); // Half of total Staking::::unbond(RawOrigin::Signed(controller.clone()).into(), amount)?; CurrentEra::put(EraIndex::max_value()); let ledger = Ledger::::get(&controller).ok_or("ledger not created before")?; let original_total: BalanceOf = ledger.total; + do_whitelist!(controller); }: withdraw_unbonded(RawOrigin::Signed(controller.clone()), s) verify { let ledger = Ledger::::get(&controller).ok_or("ledger not created after")?; @@ -167,22 +176,23 @@ benchmarks! { withdraw_unbonded_kill { // Slashing Spans let s in 0 .. MAX_SPANS; - let (stash, controller) = create_stash_controller::(0, 100)?; + let (stash, controller) = create_stash_controller::(0, 100, Default::default())?; add_slashing_spans::(&stash, s); let amount = T::Currency::minimum_balance() * 10.into(); Staking::::unbond(RawOrigin::Signed(controller.clone()).into(), amount)?; CurrentEra::put(EraIndex::max_value()); let ledger = Ledger::::get(&controller).ok_or("ledger not created before")?; let original_total: BalanceOf = ledger.total; + do_whitelist!(controller); }: withdraw_unbonded(RawOrigin::Signed(controller.clone()), s) verify { assert!(!Ledger::::contains_key(controller)); } validate { - let u in ...; - let (stash, controller) = create_stash_controller::(u, 100)?; + let (stash, controller) = create_stash_controller::(USER_SEED, 100, Default::default())?; let prefs = ValidatorPrefs::default(); + do_whitelist!(controller); }: _(RawOrigin::Signed(controller), prefs) verify { assert!(Validators::::contains_key(stash)); @@ -191,51 +201,52 @@ benchmarks! { // Worst case scenario, MAX_NOMINATIONS nominate { let n in 1 .. MAX_NOMINATIONS as u32; - let (stash, controller) = create_stash_controller::(n + 1, 100)?; + let (stash, controller) = create_stash_controller::(n + 1, 100, Default::default())?; let validators = create_validators::(n, 100)?; + do_whitelist!(controller); }: _(RawOrigin::Signed(controller), validators) verify { assert!(Nominators::::contains_key(stash)); } chill { - let u in ...; - let (_, controller) = create_stash_controller::(u, 100)?; + let (_, controller) = create_stash_controller::(USER_SEED, 100, Default::default())?; + do_whitelist!(controller); }: _(RawOrigin::Signed(controller)) set_payee { - let u in ...; - let (stash, controller) = create_stash_controller::(u, 100)?; + let (stash, controller) = create_stash_controller::(USER_SEED, 100, Default::default())?; assert_eq!(Payee::::get(&stash), RewardDestination::Staked); + do_whitelist!(controller); }: _(RawOrigin::Signed(controller), RewardDestination::Controller) verify { assert_eq!(Payee::::get(&stash), RewardDestination::Controller); } set_controller { - let u in ...; - let (stash, _) = create_stash_controller::(u, 100)?; - let new_controller = create_funded_user::("new_controller", u, 100); + let (stash, _) = create_stash_controller::(USER_SEED, 100, Default::default())?; + let new_controller = create_funded_user::("new_controller", USER_SEED, 100); let new_controller_lookup = T::Lookup::unlookup(new_controller.clone()); + do_whitelist!(stash); }: _(RawOrigin::Signed(stash), new_controller_lookup) verify { assert!(Ledger::::contains_key(&new_controller)); } set_validator_count { - let c in 0 .. MAX_VALIDATORS; - }: _(RawOrigin::Root, c) + let validator_count = MAX_VALIDATORS; + }: _(RawOrigin::Root, validator_count) verify { - assert_eq!(ValidatorCount::get(), c); + assert_eq!(ValidatorCount::get(), validator_count); } - force_no_eras { let i in 0 .. 1; }: _(RawOrigin::Root) + force_no_eras {}: _(RawOrigin::Root) verify { assert_eq!(ForceEra::get(), Forcing::ForceNone); } - force_new_era {let i in 0 .. 1; }: _(RawOrigin::Root) + force_new_era {}: _(RawOrigin::Root) verify { assert_eq!(ForceEra::get(), Forcing::ForceNew); } - force_new_era_always { let i in 0 .. 1; }: _(RawOrigin::Root) + force_new_era_always {}: _(RawOrigin::Root) verify { assert_eq!(ForceEra::get(), Forcing::ForceAlways); } // Worst case scenario, the list of invulnerables is very long. @@ -253,7 +264,7 @@ benchmarks! { force_unstake { // Slashing Spans let s in 0 .. MAX_SPANS; - let (stash, controller) = create_stash_controller::(0, 100)?; + let (stash, controller) = create_stash_controller::(0, 100, Default::default())?; add_slashing_spans::(&stash, s); }: _(RawOrigin::Root, stash, s) verify { @@ -275,37 +286,60 @@ benchmarks! { assert_eq!(UnappliedSlashes::::get(&era).len(), (MAX_SLASHES - s) as usize); } - payout_stakers { + payout_stakers_dead_controller { let n in 1 .. T::MaxNominatorRewardedPerValidator::get() as u32; - let validator = create_validator_with_nominators::(n, T::MaxNominatorRewardedPerValidator::get() as u32, true)?; + let validator = create_validator_with_nominators::( + n, + T::MaxNominatorRewardedPerValidator::get() as u32, + true, + RewardDestination::Controller, + )?; let current_era = CurrentEra::get().unwrap(); + // set the commission for this particular era as well. + >::insert(current_era, validator.clone(), >::validators(&validator)); + let caller = whitelisted_caller(); - let balance_before = T::Currency::free_balance(&validator); - }: _(RawOrigin::Signed(caller), validator.clone(), current_era) + let validator_controller = >::get(&validator).unwrap(); + let balance_before = T::Currency::free_balance(&validator_controller); + }: payout_stakers(RawOrigin::Signed(caller), validator.clone(), current_era) verify { - // Validator has been paid! - let balance_after = T::Currency::free_balance(&validator); - assert!(balance_before < balance_after); + let balance_after = T::Currency::free_balance(&validator_controller); + assert!( + balance_before < balance_after, + "Balance of controller {:?} should have increased after payout.", + validator, + ); } - payout_stakers_alive_controller { + payout_stakers_alive_staked { let n in 1 .. T::MaxNominatorRewardedPerValidator::get() as u32; - let validator = create_validator_with_nominators::(n, T::MaxNominatorRewardedPerValidator::get() as u32, false)?; + let validator = create_validator_with_nominators::( + n, + T::MaxNominatorRewardedPerValidator::get() as u32, + false, + RewardDestination::Staked, + )?; let current_era = CurrentEra::get().unwrap(); + // set the commission for this particular era as well. + >::insert(current_era, validator.clone(), >::validators(&validator)); + let caller = whitelisted_caller(); let balance_before = T::Currency::free_balance(&validator); }: payout_stakers(RawOrigin::Signed(caller), validator.clone(), current_era) verify { - // Validator has been paid! let balance_after = T::Currency::free_balance(&validator); - assert!(balance_before < balance_after); + assert!( + balance_before < balance_after, + "Balance of stash {:?} should have increased after payout.", + validator, + ); } rebond { let l in 1 .. MAX_UNLOCKING_CHUNKS as u32; - let (_, controller) = create_stash_controller::(u, 100)?; + let (_, controller) = create_stash_controller::(USER_SEED, 100, Default::default())?; let mut staking_ledger = Ledger::::get(controller.clone()).unwrap(); let unlock_chunk = UnlockChunk::> { value: 1.into(), @@ -316,6 +350,7 @@ benchmarks! { } Ledger::::insert(controller.clone(), staking_ledger.clone()); let original_bonded: BalanceOf = staking_ledger.active; + do_whitelist!(controller); }: _(RawOrigin::Signed(controller.clone()), (l + 100).into()) verify { let ledger = Ledger::::get(&controller).ok_or("ledger not created after")?; @@ -343,9 +378,10 @@ benchmarks! { reap_stash { let s in 1 .. MAX_SPANS; - let (stash, controller) = create_stash_controller::(0, 100)?; + let (stash, controller) = create_stash_controller::(0, 100, Default::default())?; add_slashing_spans::(&stash, s); T::Currency::make_free_balance_be(&stash, 0.into()); + do_whitelist!(controller); }: _(RawOrigin::Signed(controller), stash.clone(), s) verify { assert!(!Bonded::::contains_key(&stash)); @@ -362,32 +398,7 @@ benchmarks! { assert!(validators.len() == v as usize); } - do_slash { - let l in 1 .. MAX_UNLOCKING_CHUNKS as u32; - let (stash, controller) = create_stash_controller::(0, 100)?; - let mut staking_ledger = Ledger::::get(controller.clone()).unwrap(); - let unlock_chunk = UnlockChunk::> { - value: 1.into(), - era: EraIndex::zero(), - }; - for _ in 0 .. l { - staking_ledger.unlocking.push(unlock_chunk.clone()) - } - Ledger::::insert(controller, staking_ledger); - let slash_amount = T::Currency::minimum_balance() * 10.into(); - let balance_before = T::Currency::free_balance(&stash); - }: { - crate::slashing::do_slash::( - &stash, - slash_amount, - &mut BalanceOf::::zero(), - &mut NegativeImbalanceOf::::zero() - ); - } verify { - let balance_after = T::Currency::free_balance(&stash); - assert!(balance_before > balance_after); - } - + #[extra] payout_all { let v in 1 .. 10; let n in 1 .. 100; @@ -426,18 +437,45 @@ benchmarks! { } } - // 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. + #[extra] + do_slash { + let l in 1 .. MAX_UNLOCKING_CHUNKS as u32; + let (stash, controller) = create_stash_controller::(0, 100, Default::default())?; + let mut staking_ledger = Ledger::::get(controller.clone()).unwrap(); + let unlock_chunk = UnlockChunk::> { + value: 1.into(), + era: EraIndex::zero(), + }; + for _ in 0 .. l { + staking_ledger.unlocking.push(unlock_chunk.clone()) + } + Ledger::::insert(controller, staking_ledger); + let slash_amount = T::Currency::minimum_balance() * 10.into(); + let balance_before = T::Currency::free_balance(&stash); + }: { + crate::slashing::do_slash::( + &stash, + slash_amount, + &mut BalanceOf::::zero(), + &mut NegativeImbalanceOf::::zero() + ); + } verify { + let balance_after = T::Currency::free_balance(&stash); + assert!(balance_before > balance_after); + } + + // This benchmark create `v` validators intent, `n` nominators intent, in total creating `e` + // edges. + #[extra] 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 + // number of validator intention. This will be equal to `ElectionSize::validators`. + let v in 200 .. 400; + // number of nominator intention. This will be equal to `ElectionSize::nominators`. + let n in 500 .. 1000; + // number of assignments. Basically, number of active nominators. This will be equal to + // `compact.len()`. + let a in 200 .. 400; + // number of winners, also ValidatorCount. This will be equal to `winner.len()`. let w in 16 .. 100; ensure!(w as usize >= MAX_NOMINATIONS, "doesn't support lower value"); @@ -466,15 +504,19 @@ benchmarks! { size ) = offchain_election::prepare_submission::(assignments, winners, false).unwrap(); + assert_eq!( + winners.len(), compact.unique_targets().len(), + "unique targets ({}) and winners ({}) count not same. This solution is not valid.", + compact.unique_targets().len(), + winners.len(), + ); + // needed for the solution to be accepted >::put(ElectionStatus::Open(T::BlockNumber::from(1u32))); let era = >::current_era().unwrap_or(0); let caller: T::AccountId = account("caller", n, SEED); - - // Whitelist caller account from further DB operations. - let caller_key = frame_system::Account::::hashed_key_for(&caller); - frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); + do_whitelist!(caller); }: { let result = >::submit_election_solution( RawOrigin::Signed(caller.clone()).into(), @@ -493,13 +535,13 @@ benchmarks! { // 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 validator intention. + let v in 200 .. 400; + // number of nominator intention. + let n in 500 .. 1000; // number of assignments. Basically, number of active nominators. - let a in 200 .. 500; - // number of winners, also ValidatorCount + let a in 200 .. 400; + // number of winners, also ValidatorCount. let w in 16 .. 100; ensure!(w as usize >= MAX_NOMINATIONS, "doesn't support lower value"); @@ -530,15 +572,19 @@ benchmarks! { size ) = offchain_election::prepare_submission::(assignments, winners, false).unwrap(); + assert_eq!( + winners.len(), compact.unique_targets().len(), + "unique targets ({}) and winners ({}) count not same. This solution is not valid.", + compact.unique_targets().len(), + winners.len(), + ); + // needed for the solution to be accepted >::put(ElectionStatus::Open(T::BlockNumber::from(1u32))); let era = >::current_era().unwrap_or(0); let caller: T::AccountId = account("caller", n, SEED); - - // Whitelist caller account from further DB operations. - let caller_key = frame_system::Account::::hashed_key_for(&caller); - frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); + do_whitelist!(caller); // submit a very bad solution on-chain { @@ -576,11 +622,12 @@ benchmarks! { } // This will be early rejected based on the score. + #[extra] submit_solution_weaker { - // number of validator intent - let v in 1000 .. 2000; - // number of nominator intent - let n in 1000 .. 2000; + // number of validator intention. + let v in 200 .. 400; + // number of nominator intention. + let n in 500 .. 1000; create_validators_with_nominators_for_era::(v, n, MAX_NOMINATIONS, false, None)?; @@ -589,16 +636,19 @@ benchmarks! { // needed for the solution to be accepted >::put(ElectionStatus::Open(T::BlockNumber::from(1u32))); - let caller: T::AccountId = account("caller", n, SEED); let era = >::current_era().unwrap_or(0); - - // Whitelist caller account from further DB operations. - let caller_key = frame_system::Account::::hashed_key_for(&caller); - frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); + let caller: T::AccountId = account("caller", n, SEED); + do_whitelist!(caller); // submit a seq-phragmen with all the good stuff on chain. { let (winners, compact, score, size) = get_seq_phragmen_solution::(true); + assert_eq!( + winners.len(), compact.unique_targets().len(), + "unique targets ({}) and winners ({}) count not same. This solution is not valid.", + compact.unique_targets().len(), + winners.len(), + ); assert!( >::submit_election_solution( RawOrigin::Signed(caller.clone()).into(), @@ -662,6 +712,7 @@ mod tests { n, ::MaxNominatorRewardedPerValidator::get() as u32, false, + RewardDestination::Staked, ).unwrap(); let current_era = CurrentEra::get().unwrap(); @@ -683,6 +734,7 @@ mod tests { n, ::MaxNominatorRewardedPerValidator::get() as u32, false, + RewardDestination::Staked, ).unwrap(); // Add 20 slashing spans @@ -743,8 +795,8 @@ mod tests { assert_ok!(test_benchmark_set_invulnerables::()); assert_ok!(test_benchmark_force_unstake::()); assert_ok!(test_benchmark_cancel_deferred_slash::()); - assert_ok!(test_benchmark_payout_stakers::()); - assert_ok!(test_benchmark_payout_stakers_alive_controller::()); + assert_ok!(test_benchmark_payout_stakers_dead_controller::()); + assert_ok!(test_benchmark_payout_stakers_alive_staked::()); assert_ok!(test_benchmark_rebond::()); assert_ok!(test_benchmark_set_history_depth::()); assert_ok!(test_benchmark_reap_stash::()); diff --git a/frame/staking/src/default_weights.rs b/frame/staking/src/default_weights.rs new file mode 100644 index 0000000000000..fa5a05f63824e --- /dev/null +++ b/frame/staking/src/default_weights.rs @@ -0,0 +1,169 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2020 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Default weights of pallet-staking. +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 2.0.0-rc6 + +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::weights::{Weight, constants::RocksDbWeight as DbWeight}; + +impl crate::WeightInfo for () { + fn bond() -> Weight { + (144278000 as Weight) + .saturating_add(DbWeight::get().reads(5 as Weight)) + .saturating_add(DbWeight::get().writes(4 as Weight)) + } + fn bond_extra() -> Weight { + (110715000 as Weight) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn unbond() -> Weight { + (99840000 as Weight) + .saturating_add(DbWeight::get().reads(5 as Weight)) + .saturating_add(DbWeight::get().writes(3 as Weight)) + } + fn withdraw_unbonded_update(s: u32, ) -> Weight { + (100728000 as Weight) + .saturating_add((63000 as Weight).saturating_mul(s as Weight)) + .saturating_add(DbWeight::get().reads(5 as Weight)) + .saturating_add(DbWeight::get().writes(3 as Weight)) + } + fn withdraw_unbonded_kill(s: u32, ) -> Weight { + (168879000 as Weight) + .saturating_add((6666000 as Weight).saturating_mul(s as Weight)) + .saturating_add(DbWeight::get().reads(7 as Weight)) + .saturating_add(DbWeight::get().writes(8 as Weight)) + .saturating_add(DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) + } + fn validate() -> Weight { + (35539000 as Weight) + .saturating_add(DbWeight::get().reads(2 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn nominate(n: u32, ) -> Weight { + (48596000 as Weight) + .saturating_add((308000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(3 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn chill() -> Weight { + (35144000 as Weight) + .saturating_add(DbWeight::get().reads(2 as Weight)) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } + fn set_payee() -> Weight { + (24255000 as Weight) + .saturating_add(DbWeight::get().reads(1 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn set_controller() -> Weight { + (52294000 as Weight) + .saturating_add(DbWeight::get().reads(3 as Weight)) + .saturating_add(DbWeight::get().writes(3 as Weight)) + } + fn set_validator_count() -> Weight { + (5185000 as Weight) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn force_no_eras() -> Weight { + (5907000 as Weight) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn force_new_era() -> Weight { + (5917000 as Weight) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn force_new_era_always() -> Weight { + (5952000 as Weight) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn set_invulnerables(v: u32, ) -> Weight { + (6324000 as Weight) + .saturating_add((9000 as Weight).saturating_mul(v as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn force_unstake(s: u32, ) -> Weight { + (119691000 as Weight) + .saturating_add((6681000 as Weight).saturating_mul(s as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().writes(8 as Weight)) + .saturating_add(DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) + } + fn cancel_deferred_slash(s: u32, ) -> Weight { + (5820201000 as Weight) + .saturating_add((34672000 as Weight).saturating_mul(s as Weight)) + .saturating_add(DbWeight::get().reads(1 as Weight)) + .saturating_add(DbWeight::get().writes(1 as Weight)) + } + fn payout_stakers_dead_controller(n: u32, ) -> Weight { + (0 as Weight) + .saturating_add((92486000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().reads((3 as Weight).saturating_mul(n as Weight))) + .saturating_add(DbWeight::get().writes((1 as Weight).saturating_mul(n as Weight))) + } + fn payout_stakers_alive_staked(n: u32, ) -> Weight { + (0 as Weight) + .saturating_add((117324000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads((5 as Weight).saturating_mul(n as Weight))) + .saturating_add(DbWeight::get().writes((3 as Weight).saturating_mul(n as Weight))) + } + fn rebond(l: u32, ) -> Weight { + (71316000 as Weight) + .saturating_add((142000 as Weight).saturating_mul(l as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().writes(3 as Weight)) + } + fn set_history_depth(e: u32, ) -> Weight { + (0 as Weight) + .saturating_add((51901000 as Weight).saturating_mul(e as Weight)) + .saturating_add(DbWeight::get().reads(2 as Weight)) + .saturating_add(DbWeight::get().writes(4 as Weight)) + .saturating_add(DbWeight::get().writes((7 as Weight).saturating_mul(e as Weight))) + } + fn reap_stash(s: u32, ) -> Weight { + (147166000 as Weight) + .saturating_add((6661000 as Weight).saturating_mul(s as Weight)) + .saturating_add(DbWeight::get().reads(4 as Weight)) + .saturating_add(DbWeight::get().writes(8 as Weight)) + .saturating_add(DbWeight::get().writes((1 as Weight).saturating_mul(s as Weight))) + } + fn new_era(v: u32, n: u32, ) -> Weight { + (0 as Weight) + .saturating_add((1440459000 as Weight).saturating_mul(v as Weight)) + .saturating_add((182580000 as Weight).saturating_mul(n as Weight)) + .saturating_add(DbWeight::get().reads(10 as Weight)) + .saturating_add(DbWeight::get().reads((4 as Weight).saturating_mul(v as Weight))) + .saturating_add(DbWeight::get().reads((3 as Weight).saturating_mul(n as Weight))) + .saturating_add(DbWeight::get().writes(8 as Weight)) + .saturating_add(DbWeight::get().writes((3 as Weight).saturating_mul(v as Weight))) + } + fn submit_solution_better(v: u32, n: u32, a: u32, w: u32, ) -> Weight { + (0 as Weight) + .saturating_add((964000 as Weight).saturating_mul(v as Weight)) + .saturating_add((432000 as Weight).saturating_mul(n as Weight)) + .saturating_add((204294000 as Weight).saturating_mul(a as Weight)) + .saturating_add((9546000 as Weight).saturating_mul(w as Weight)) + .saturating_add(DbWeight::get().reads(6 as Weight)) + .saturating_add(DbWeight::get().reads((4 as Weight).saturating_mul(a as Weight))) + .saturating_add(DbWeight::get().reads((1 as Weight).saturating_mul(w as Weight))) + .saturating_add(DbWeight::get().writes(2 as Weight)) + } +} diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 279b6bb1dec7d..7061832b0460c 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -279,6 +279,7 @@ pub mod benchmarking; pub mod slashing; pub mod offchain_election; pub mod inflation; +pub mod default_weights; use sp_std::{ result, @@ -356,6 +357,8 @@ pub type ValidatorIndex = u16; // Ensure the size of both ValidatorIndex and NominatorIndex. They both need to be well below usize. static_assertions::const_assert!(size_of::() <= size_of::()); static_assertions::const_assert!(size_of::() <= size_of::()); +static_assertions::const_assert!(size_of::() <= size_of::()); +static_assertions::const_assert!(size_of::() <= size_of::()); /// Maximum number of stakers that can be stored in a snapshot. pub(crate) const MAX_VALIDATORS: usize = ValidatorIndex::max_value() as usize; @@ -766,132 +769,31 @@ impl SessionInterface<::AccountId> for T whe } } -pub mod weight { - use super::*; - - /// All weight notes are pertaining to the case of a better solution, in which we execute - /// the longest code path. - /// Weight: 0 + (0.63 μs * v) + (0.36 μs * n) + (96.53 μs * a ) + (8 μs * w ) with: - /// * v validators in snapshot validators, - /// * n nominators in snapshot nominators, - /// * a assignment in the submitted solution - /// * w winners in the submitted solution - /// - /// State reads: - /// - Initial checks: - /// - ElectionState, CurrentEra, QueuedScore - /// - SnapshotValidators.len() + SnapShotNominators.len() - /// - ValidatorCount - /// - SnapshotValidators - /// - SnapshotNominators - /// - Iterate over nominators: - /// - compact.len() * Nominators(who) - /// - (non_self_vote_edges) * SlashingSpans - /// - For `assignment_ratio_to_staked`: Basically read the staked value of each stash. - /// - (winners.len() + compact.len()) * (Ledger + Bonded) - /// - TotalIssuance (read a gzillion times potentially, but well it is cached.) - /// - State writes: - /// - QueuedElected, QueuedScore - pub fn weight_for_submit_solution( - winners: &Vec, - compact: &CompactAssignments, - size: &ElectionSize, - ) -> Weight { - (630 * WEIGHT_PER_NANOS).saturating_mul(size.validators as Weight) - .saturating_add((360 * WEIGHT_PER_NANOS).saturating_mul(size.nominators as Weight)) - .saturating_add((96 * WEIGHT_PER_MICROS).saturating_mul(compact.len() as Weight)) - .saturating_add((8 * WEIGHT_PER_MICROS).saturating_mul(winners.len() as Weight)) - // Initial checks - .saturating_add(T::DbWeight::get().reads(8)) - // Nominators - .saturating_add(T::DbWeight::get().reads(compact.len() as Weight)) - // SlashingSpans (upper bound for invalid solution) - .saturating_add(T::DbWeight::get().reads(compact.edge_count() as Weight)) - // `assignment_ratio_to_staked` - .saturating_add(T::DbWeight::get().reads(2 * ((winners.len() + compact.len()) as Weight))) - .saturating_add(T::DbWeight::get().reads(1)) - // write queued score and elected - .saturating_add(T::DbWeight::get().writes(2)) - } - - /// Weight of `submit_solution` in case of a correct submission. - /// - /// refund: we charged compact.len() * read(1) for SlashingSpans. A valid solution only reads - /// winners.len(). - pub fn weight_for_correct_submit_solution( - winners: &Vec, - compact: &CompactAssignments, - size: &ElectionSize, - ) -> Weight { - // NOTE: for consistency, we re-compute the original weight to maintain their relation and - // prevent any foot-guns. - let original_weight = weight_for_submit_solution::(winners, compact, size); - original_weight - .saturating_sub(T::DbWeight::get().reads(compact.edge_count() as Weight)) - .saturating_add(T::DbWeight::get().reads(winners.len() as Weight)) - } -} - pub trait WeightInfo { - fn bond(u: u32, ) -> Weight; - fn bond_extra(u: u32, ) -> Weight; - fn unbond(u: u32, ) -> Weight; + fn bond() -> Weight; + fn bond_extra() -> Weight; + fn unbond() -> Weight; fn withdraw_unbonded_update(s: u32, ) -> Weight; fn withdraw_unbonded_kill(s: u32, ) -> Weight; - fn validate(u: u32, ) -> Weight; + fn validate() -> Weight; fn nominate(n: u32, ) -> Weight; - fn chill(u: u32, ) -> Weight; - fn set_payee(u: u32, ) -> Weight; - fn set_controller(u: u32, ) -> Weight; - fn set_validator_count(c: u32, ) -> Weight; - fn force_no_eras(i: u32, ) -> Weight; - fn force_new_era(i: u32, ) -> Weight; - fn force_new_era_always(i: u32, ) -> Weight; + fn chill() -> Weight; + fn set_payee() -> Weight; + fn set_controller() -> Weight; + fn set_validator_count() -> Weight; + fn force_no_eras() -> Weight; + fn force_new_era() -> Weight; + fn force_new_era_always() -> Weight; fn set_invulnerables(v: u32, ) -> Weight; fn force_unstake(s: u32, ) -> Weight; fn cancel_deferred_slash(s: u32, ) -> Weight; - fn payout_stakers(n: u32, ) -> Weight; - fn payout_stakers_alive_controller(n: u32, ) -> Weight; + fn payout_stakers_alive_staked(n: u32, ) -> Weight; + fn payout_stakers_dead_controller(n: u32, ) -> Weight; fn rebond(l: u32, ) -> Weight; fn set_history_depth(e: u32, ) -> Weight; fn reap_stash(s: u32, ) -> Weight; fn new_era(v: u32, n: u32, ) -> Weight; - fn do_slash(l: u32, ) -> Weight; - fn payout_all(v: u32, n: u32, ) -> Weight; - fn submit_solution_initial(v: u32, n: u32, a: u32, w: u32, ) -> Weight; fn submit_solution_better(v: u32, n: u32, a: u32, w: u32, ) -> Weight; - fn submit_solution_weaker(v: u32, n: u32, ) -> Weight; -} - -impl WeightInfo for () { - fn bond(_u: u32, ) -> Weight { 1_000_000_000 } - fn bond_extra(_u: u32, ) -> Weight { 1_000_000_000 } - fn unbond(_u: u32, ) -> Weight { 1_000_000_000 } - fn withdraw_unbonded_update(_s: u32, ) -> Weight { 1_000_000_000 } - fn withdraw_unbonded_kill(_s: u32, ) -> Weight { 1_000_000_000 } - fn validate(_u: u32, ) -> Weight { 1_000_000_000 } - fn nominate(_n: u32, ) -> Weight { 1_000_000_000 } - fn chill(_u: u32, ) -> Weight { 1_000_000_000 } - fn set_payee(_u: u32, ) -> Weight { 1_000_000_000 } - fn set_controller(_u: u32, ) -> Weight { 1_000_000_000 } - fn set_validator_count(_c: u32, ) -> Weight { 1_000_000_000 } - fn force_no_eras(_i: u32, ) -> Weight { 1_000_000_000 } - fn force_new_era(_i: u32, ) -> Weight { 1_000_000_000 } - fn force_new_era_always(_i: u32, ) -> Weight { 1_000_000_000 } - fn set_invulnerables(_v: u32, ) -> Weight { 1_000_000_000 } - fn force_unstake(_s: u32, ) -> Weight { 1_000_000_000 } - fn cancel_deferred_slash(_s: u32, ) -> Weight { 1_000_000_000 } - fn payout_stakers(_n: u32, ) -> Weight { 1_000_000_000 } - fn payout_stakers_alive_controller(_n: u32, ) -> Weight { 1_000_000_000 } - fn rebond(_l: u32, ) -> Weight { 1_000_000_000 } - fn set_history_depth(_e: u32, ) -> Weight { 1_000_000_000 } - fn reap_stash(_s: u32, ) -> Weight { 1_000_000_000 } - fn new_era(_v: u32, _n: u32, ) -> Weight { 1_000_000_000 } - fn do_slash(_l: u32, ) -> Weight { 1_000_000_000 } - fn payout_all(_v: u32, _n: u32, ) -> Weight { 1_000_000_000 } - fn submit_solution_initial(_v: u32, _n: u32, _a: u32, _w: u32, ) -> Weight { 1_000_000_000 } - fn submit_solution_better(_v: u32, _n: u32, _a: u32, _w: u32, ) -> Weight { 1_000_000_000 } - fn submit_solution_weaker(_v: u32, _n: u32, ) -> Weight { 1_000_000_000 } } pub trait Trait: frame_system::Trait + SendTransactionTypes> { @@ -1489,12 +1391,12 @@ decl_module! { /// NOTE: Two of the storage writes (`Self::bonded`, `Self::payee`) are _never_ cleaned /// unless the `origin` falls below _existential deposit_ and gets removed as dust. /// ------------------ - /// Base Weight: 67.87 µs + /// Weight: O(1) /// DB Weight: /// - Read: Bonded, Ledger, [Origin Account], Current Era, History Depth, Locks /// - Write: Bonded, Payee, [Origin Account], Locks, Ledger /// # - #[weight = 67 * WEIGHT_PER_MICROS + T::DbWeight::get().reads_writes(5, 4)] + #[weight = T::WeightInfo::bond()] pub fn bond(origin, controller: ::Source, #[compact] value: BalanceOf, @@ -1558,12 +1460,11 @@ decl_module! { /// - O(1). /// - One DB entry. /// ------------ - /// Base Weight: 54.88 µs /// DB Weight: /// - Read: Era Election Status, Bonded, Ledger, [Origin Account], Locks /// - Write: [Origin Account], Locks, Ledger /// # - #[weight = 55 * WEIGHT_PER_MICROS + T::DbWeight::get().reads_writes(4, 2)] + #[weight = T::WeightInfo::bond_extra()] fn bond_extra(origin, #[compact] max_additional: BalanceOf) { ensure!(Self::era_election_status().is_closed(), Error::::CallNotAllowed); let stash = ensure_signed(origin)?; @@ -1609,12 +1510,12 @@ decl_module! { /// `withdraw_unbonded`. /// - One DB entry. /// ---------- - /// Base Weight: 50.34 µs + /// Weight: O(1) /// DB Weight: - /// - Read: Era Election Status, Ledger, Current Era, Locks, [Origin Account] - /// - Write: [Origin Account], Locks, Ledger + /// - Read: EraElectionStatus, Ledger, CurrentEra, Locks, BalanceOf Stash, + /// - Write: Locks, Ledger, BalanceOf Stash, /// - #[weight = 50 * WEIGHT_PER_MICROS + T::DbWeight::get().reads_writes(4, 2)] + #[weight = T::WeightInfo::unbond()] fn unbond(origin, #[compact] value: BalanceOf) { ensure!(Self::era_election_status().is_closed(), Error::::CallNotAllowed); let controller = ensure_signed(origin)?; @@ -1663,25 +1564,18 @@ decl_module! { /// - Writes are limited to the `origin` account key. /// --------------- /// Complexity O(S) where S is the number of slashing spans to remove - /// Base Weight: - /// Update: 50.52 + .028 * S µs + /// Update: /// - Reads: EraElectionStatus, Ledger, Current Era, Locks, [Origin Account] /// - Writes: [Origin Account], Locks, Ledger - /// Kill: 79.41 + 2.366 * S µs - /// - Reads: EraElectionStatus, Ledger, Current Era, Bonded, Slashing Spans, [Origin Account], Locks - /// - Writes: Bonded, Slashing Spans (if S > 0), Ledger, Payee, Validators, Nominators, [Origin Account], Locks + /// Kill: + /// - Reads: EraElectionStatus, Ledger, Current Era, Bonded, Slashing Spans, [Origin + /// Account], Locks, BalanceOf stash + /// - Writes: Bonded, Slashing Spans (if S > 0), Ledger, Payee, Validators, Nominators, + /// [Origin Account], Locks, BalanceOf stash. /// - Writes Each: SpanSlash * S /// NOTE: Weight annotation is the kill scenario, we refund otherwise. /// # - #[weight = T::DbWeight::get().reads_writes(6, 6) - .saturating_add(80 * WEIGHT_PER_MICROS) - .saturating_add( - (2 * WEIGHT_PER_MICROS).saturating_mul(Weight::from(*num_slashing_spans)) - ) - .saturating_add(T::DbWeight::get().writes(Weight::from(*num_slashing_spans))) - // if slashing spans is non-zero, add 1 more write - .saturating_add(T::DbWeight::get().writes(Weight::from(*num_slashing_spans).min(1))) - ] + #[weight = T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans)] fn withdraw_unbonded(origin, num_slashing_spans: u32) -> DispatchResultWithPostInfo { ensure!(Self::era_election_status().is_closed(), Error::::CallNotAllowed); let controller = ensure_signed(origin)?; @@ -1703,8 +1597,9 @@ decl_module! { } else { // This was the consequence of a partial unbond. just update the ledger and move on. Self::update_ledger(&controller, &ledger); - // This is only an update, so we use less overall weight - Some(50 * WEIGHT_PER_MICROS + T::DbWeight::get().reads_writes(4, 2)) + + // This is only an update, so we use less overall weight. + Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) }; // `old_total` should never be less than the new total because @@ -1730,12 +1625,12 @@ decl_module! { /// - Contains a limited number of reads. /// - Writes are limited to the `origin` account key. /// ----------- - /// Base Weight: 17.13 µs + /// Weight: O(1) /// DB Weight: /// - Read: Era Election Status, Ledger /// - Write: Nominators, Validators /// # - #[weight = 17 * WEIGHT_PER_MICROS + T::DbWeight::get().reads_writes(2, 2)] + #[weight = T::WeightInfo::validate()] pub fn validate(origin, prefs: ValidatorPrefs) { ensure!(Self::era_election_status().is_closed(), Error::::CallNotAllowed); let controller = ensure_signed(origin)?; @@ -1758,16 +1653,13 @@ decl_module! { /// which is capped at CompactAssignments::LIMIT (MAX_NOMINATIONS). /// - Both the reads and writes follow a similar pattern. /// --------- - /// Base Weight: 22.34 + .36 * N µs + /// Weight: O(N) /// where N is the number of targets /// DB Weight: /// - Reads: Era Election Status, Ledger, Current Era /// - Writes: Validators, Nominators /// # - #[weight = T::DbWeight::get().reads_writes(3, 2) - .saturating_add(22 * WEIGHT_PER_MICROS) - .saturating_add((360 * WEIGHT_PER_NANOS).saturating_mul(targets.len() as Weight)) - ] + #[weight = T::WeightInfo::nominate(targets.len() as u32)] pub fn nominate(origin, targets: Vec<::Source>) { ensure!(Self::era_election_status().is_closed(), Error::::CallNotAllowed); let controller = ensure_signed(origin)?; @@ -1802,12 +1694,12 @@ decl_module! { /// - Contains one read. /// - Writes are limited to the `origin` account key. /// -------- - /// Base Weight: 16.53 µs + /// Weight: O(1) /// DB Weight: /// - Read: EraElectionStatus, Ledger /// - Write: Validators, Nominators /// # - #[weight = 16 * WEIGHT_PER_MICROS + T::DbWeight::get().reads_writes(2, 2)] + #[weight = T::WeightInfo::chill()] fn chill(origin) { ensure!(Self::era_election_status().is_closed(), Error::::CallNotAllowed); let controller = ensure_signed(origin)?; @@ -1826,12 +1718,12 @@ decl_module! { /// - Contains a limited number of reads. /// - Writes are limited to the `origin` account key. /// --------- - /// - Base Weight: 11.33 µs + /// - Weight: O(1) /// - DB Weight: /// - Read: Ledger /// - Write: Payee /// # - #[weight = 11 * WEIGHT_PER_MICROS + T::DbWeight::get().reads_writes(1, 1)] + #[weight = T::WeightInfo::set_payee()] fn set_payee(origin, payee: RewardDestination) { let controller = ensure_signed(origin)?; let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; @@ -1850,12 +1742,12 @@ decl_module! { /// - Contains a limited number of reads. /// - Writes are limited to the `origin` account key. /// ---------- - /// Base Weight: 25.22 µs + /// Weight: O(1) /// DB Weight: /// - Read: Bonded, Ledger New Controller, Ledger Old Controller /// - Write: Bonded, Ledger New Controller, Ledger Old Controller /// # - #[weight = 25 * WEIGHT_PER_MICROS + T::DbWeight::get().reads_writes(3, 3)] + #[weight = T::WeightInfo::set_controller()] fn set_controller(origin, controller: ::Source) { let stash = ensure_signed(origin)?; let old_controller = Self::bonded(&stash).ok_or(Error::::NotStash)?; @@ -1876,10 +1768,10 @@ decl_module! { /// The dispatch origin must be Root. /// /// # - /// Base Weight: 1.717 µs + /// Weight: O(1) /// Write: Validator Count /// # - #[weight = 2 * WEIGHT_PER_MICROS + T::DbWeight::get().writes(1)] + #[weight = T::WeightInfo::set_validator_count()] fn set_validator_count(origin, #[compact] new: u32) { ensure_root(origin)?; ValidatorCount::put(new); @@ -1890,10 +1782,9 @@ decl_module! { /// The dispatch origin must be Root. /// /// # - /// Base Weight: 1.717 µs - /// Read/Write: Validator Count + /// Same as [`set_validator_count`]. /// # - #[weight = 2 * WEIGHT_PER_MICROS + T::DbWeight::get().reads_writes(1, 1)] + #[weight = T::WeightInfo::set_validator_count()] fn increase_validator_count(origin, #[compact] additional: u32) { ensure_root(origin)?; ValidatorCount::mutate(|n| *n += additional); @@ -1904,10 +1795,9 @@ decl_module! { /// The dispatch origin must be Root. /// /// # - /// Base Weight: 1.717 µs - /// Read/Write: Validator Count + /// Same as [`set_validator_count`]. /// # - #[weight = 2 * WEIGHT_PER_MICROS + T::DbWeight::get().reads_writes(1, 1)] + #[weight = T::WeightInfo::set_validator_count()] fn scale_validator_count(origin, factor: Percent) { ensure_root(origin)?; ValidatorCount::mutate(|n| *n += factor * *n); @@ -1919,10 +1809,10 @@ decl_module! { /// /// # /// - No arguments. - /// - Base Weight: 1.857 µs + /// - Weight: O(1) /// - Write: ForceEra /// # - #[weight = 2 * WEIGHT_PER_MICROS + T::DbWeight::get().writes(1)] + #[weight = T::WeightInfo::force_no_eras()] fn force_no_eras(origin) { ensure_root(origin)?; ForceEra::put(Forcing::ForceNone); @@ -1935,10 +1825,10 @@ decl_module! { /// /// # /// - No arguments. - /// - Base Weight: 1.959 µs + /// - Weight: O(1) /// - Write ForceEra /// # - #[weight = 2 * WEIGHT_PER_MICROS + T::DbWeight::get().writes(1)] + #[weight = T::WeightInfo::force_new_era()] fn force_new_era(origin) { ensure_root(origin)?; ForceEra::put(Forcing::ForceNew); @@ -1950,16 +1840,12 @@ decl_module! { /// /// # /// - O(V) - /// - Base Weight: 2.208 + .006 * V µs /// - Write: Invulnerables /// # - #[weight = T::DbWeight::get().writes(1) - .saturating_add(2 * WEIGHT_PER_MICROS) - .saturating_add((6 * WEIGHT_PER_NANOS).saturating_mul(validators.len() as Weight)) - ] - fn set_invulnerables(origin, validators: Vec) { + #[weight = T::WeightInfo::set_invulnerables(invulnerables.len() as u32)] + fn set_invulnerables(origin, invulnerables: Vec) { ensure_root(origin)?; - >::put(validators); + >::put(invulnerables); } /// Force a current staker to become completely unstaked, immediately. @@ -1968,20 +1854,11 @@ decl_module! { /// /// # /// O(S) where S is the number of slashing spans to be removed - /// Base Weight: 53.07 + 2.365 * S µs /// Reads: Bonded, Slashing Spans, Account, Locks /// Writes: Bonded, Slashing Spans (if S > 0), Ledger, Payee, Validators, Nominators, Account, Locks /// Writes Each: SpanSlash * S /// # - #[weight = T::DbWeight::get().reads_writes(4, 7) - .saturating_add(53 * WEIGHT_PER_MICROS) - .saturating_add( - WEIGHT_PER_MICROS.saturating_mul(2).saturating_mul(Weight::from(*num_slashing_spans)) - ) - .saturating_add(T::DbWeight::get().writes(Weight::from(*num_slashing_spans))) - // if slashing spans is non-zero, add 1 more write - .saturating_add(T::DbWeight::get().writes(Weight::from(*num_slashing_spans > 0))) - ] + #[weight = T::WeightInfo::force_unstake(*num_slashing_spans)] fn force_unstake(origin, stash: T::AccountId, num_slashing_spans: u32) { ensure_root(origin)?; @@ -1997,10 +1874,10 @@ decl_module! { /// The dispatch origin must be Root. /// /// # - /// - Base Weight: 2.05 µs + /// - Weight: O(1) /// - Write: ForceEra /// # - #[weight = 2 * WEIGHT_PER_MICROS + T::DbWeight::get().writes(1)] + #[weight = T::WeightInfo::force_new_era_always()] fn force_new_era_always(origin) { ensure_root(origin)?; ForceEra::put(Forcing::ForceAlways); @@ -2016,14 +1893,10 @@ decl_module! { /// Complexity: O(U + S) /// with U unapplied slashes weighted with U=1000 /// and S is the number of slash indices to be canceled. - /// - Base: 5870 + 34.61 * S µs /// - Read: Unapplied Slashes /// - Write: Unapplied Slashes /// # - #[weight = T::DbWeight::get().reads_writes(1, 1) - .saturating_add(5_870 * WEIGHT_PER_MICROS) - .saturating_add((35 * WEIGHT_PER_MICROS).saturating_mul(slash_indices.len() as Weight)) - ] + #[weight = T::WeightInfo::cancel_deferred_slash(slash_indices.len() as u32)] fn cancel_deferred_slash(origin, era: EraIndex, slash_indices: Vec) { T::SlashCancelOrigin::ensure_origin(origin)?; @@ -2058,22 +1931,19 @@ decl_module! { /// - Contains a limited number of reads and writes. /// ----------- /// N is the Number of payouts for the validator (including the validator) - /// Base Weight: - /// - Reward Destination Staked: 110 + 54.2 * N µs (Median Slopes) - /// - Reward Destination Controller (Creating): 120 + 41.95 * N µs (Median Slopes) + /// Weight: + /// - Reward Destination Staked: O(N) + /// - Reward Destination Controller (Creating): O(N) /// DB Weight: /// - Read: EraElectionStatus, CurrentEra, HistoryDepth, ErasValidatorReward, /// ErasStakersClipped, ErasRewardPoints, ErasValidatorPrefs (8 items) /// - Read Each: Bonded, Ledger, Payee, Locks, System Account (5 items) /// - Write Each: System Account, Locks, Ledger (3 items) + /// + /// NOTE: weights are assuming that payouts are made to alive stash account (Staked). + /// Paying even a dead controller is cheaper weight-wise. We don't do any refunds here. /// # - #[weight = - 120 * WEIGHT_PER_MICROS - + 54 * WEIGHT_PER_MICROS * Weight::from(T::MaxNominatorRewardedPerValidator::get()) - + T::DbWeight::get().reads(7) - + T::DbWeight::get().reads(5) * Weight::from(T::MaxNominatorRewardedPerValidator::get() + 1) - + T::DbWeight::get().writes(3) * Weight::from(T::MaxNominatorRewardedPerValidator::get() + 1) - ] + #[weight = T::WeightInfo::payout_stakers_alive_staked(T::MaxNominatorRewardedPerValidator::get())] fn payout_stakers(origin, validator_stash: T::AccountId, era: EraIndex) -> DispatchResult { ensure!(Self::era_election_status().is_closed(), Error::::CallNotAllowed); ensure_signed(origin)?; @@ -2090,16 +1960,11 @@ decl_module! { /// - Bounded by `MAX_UNLOCKING_CHUNKS`. /// - Storage changes: Can't increase storage, only decrease it. /// --------------- - /// - Base Weight: 34.51 µs * .048 L µs /// - DB Weight: /// - Reads: EraElectionStatus, Ledger, Locks, [Origin Account] /// - Writes: [Origin Account], Locks, Ledger /// # - #[weight = - 35 * WEIGHT_PER_MICROS - + 50 * WEIGHT_PER_NANOS * (MAX_UNLOCKING_CHUNKS as Weight) - + T::DbWeight::get().reads_writes(3, 2) - ] + #[weight = T::WeightInfo::rebond(MAX_UNLOCKING_CHUNKS as u32)] fn rebond(origin, #[compact] value: BalanceOf) -> DispatchResultWithPostInfo { ensure!(Self::era_election_status().is_closed(), Error::::CallNotAllowed); let controller = ensure_signed(origin)?; @@ -2129,19 +1994,14 @@ decl_module! { /// /// # /// - E: Number of history depths removed, i.e. 10 -> 7 = 3 - /// - Base Weight: 29.13 * E µs + /// - Weight: O(E) /// - DB Weight: /// - Reads: Current Era, History Depth /// - Writes: History Depth /// - Clear Prefix Each: Era Stakers, EraStakersClipped, ErasValidatorPrefs /// - Writes Each: ErasValidatorReward, ErasRewardPoints, ErasTotalStake, ErasStartSessionIndex /// # - #[weight = { - let items = Weight::from(*_era_items_deleted); - T::DbWeight::get().reads_writes(2, 1) - .saturating_add(T::DbWeight::get().reads_writes(items, items)) - - }] + #[weight = T::WeightInfo::set_history_depth(*_era_items_deleted)] fn set_history_depth(origin, #[compact] new_history_depth: EraIndex, #[compact] _era_items_deleted: u32, @@ -2169,21 +2029,12 @@ decl_module! { /// /// # /// Complexity: O(S) where S is the number of slashing spans on the account. - /// Base Weight: 75.94 + 2.396 * S µs /// DB Weight: /// - Reads: Stash Account, Bonded, Slashing Spans, Locks /// - Writes: Bonded, Slashing Spans (if S > 0), Ledger, Payee, Validators, Nominators, Stash Account, Locks /// - Writes Each: SpanSlash * S /// # - #[weight = T::DbWeight::get().reads_writes(4, 7) - .saturating_add(76 * WEIGHT_PER_MICROS) - .saturating_add( - WEIGHT_PER_MICROS.saturating_mul(2).saturating_mul(Weight::from(*num_slashing_spans)) - ) - .saturating_add(T::DbWeight::get().writes(Weight::from(*num_slashing_spans))) - // if slashing spans is non-zero, add 1 more write - .saturating_add(T::DbWeight::get().writes(Weight::from(*num_slashing_spans).min(1))) - ] + #[weight = T::WeightInfo::reap_stash(*num_slashing_spans)] fn reap_stash(_origin, stash: T::AccountId, num_slashing_spans: u32) { ensure!(T::Currency::total_balance(&stash).is_zero(), Error::::FundedTarget); Self::kill_stash(&stash, num_slashing_spans)?; @@ -2235,9 +2086,16 @@ decl_module! { /// minimized (to ensure less variance) /// /// # - /// See `crate::weight` module. + /// The transaction is assumed to be the longest path, a better solution. + /// - Initial solution is almost the same. + /// - Worse solution is retraced in pre-dispatch-checks which sets its own weight. /// # - #[weight = weight::weight_for_submit_solution::(winners, compact, size)] + #[weight = T::WeightInfo::submit_solution_better( + size.validators.into(), + size.nominators.into(), + compact.len() as u32, + winners.len() as u32, + )] pub fn submit_election_solution( origin, winners: Vec, @@ -2266,7 +2124,12 @@ decl_module! { /// # /// See `crate::weight` module. /// # - #[weight = weight::weight_for_submit_solution::(winners, compact, size)] + #[weight = T::WeightInfo::submit_solution_better( + size.validators.into(), + size.nominators.into(), + compact.len() as u32, + winners.len() as u32, + )] pub fn submit_election_solution_unsigned( origin, winners: Vec, @@ -2580,13 +2443,16 @@ impl Module { election_size: ElectionSize, ) -> DispatchResultWithPostInfo { // Do the basic checks. era, claimed score and window open. - Self::pre_dispatch_checks(claimed_score, era)?; - // the weight that we will refund in case of a correct submission. We compute this now - // because the data needed for it will be consumed further down. - let adjusted_weight = weight::weight_for_correct_submit_solution::( - &winners, - &compact_assignments, - &election_size, + let _ = Self::pre_dispatch_checks(claimed_score, era)?; + + // before we read any further state, we check that the unique targets in compact is same as + // compact. is a all in-memory check and easy to do. Moreover, it ensures that the solution + // is not full of bogus edges that can cause lots of reads to SlashingSpans. Thus, we can + // assume that the storage access of this function is always O(|winners|), not + // O(|compact.edge_count()|). + ensure!( + compact_assignments.unique_targets().len() == winners.len(), + Error::::OffchainElectionBogusWinnerCount, ); // Check that the number of presented winners is sane. Most often we have more candidates @@ -2744,7 +2610,7 @@ impl Module { // emit event. Self::deposit_event(RawEvent::SolutionStored(compute)); - Ok(Some(adjusted_weight).into()) + Ok(None.into()) } /// Start a session potentially starting an era. @@ -2862,7 +2728,6 @@ impl Module { maybe_new_validators } - /// Remove all the storage items associated with the election. fn close_election_window() { // Close window. diff --git a/frame/staking/src/testing_utils.rs b/frame/staking/src/testing_utils.rs index 02acd135e63e4..6354014232d50 100644 --- a/frame/staking/src/testing_utils.rs +++ b/frame/staking/src/testing_utils.rs @@ -20,7 +20,7 @@ use crate::*; use crate::Module as Staking; -use frame_benchmarking::{account}; +use frame_benchmarking::account; use frame_system::RawOrigin; use sp_io::hashing::blake2_256; use rand_chacha::{rand_core::{RngCore, SeedableRng}, ChaChaRng}; @@ -29,7 +29,11 @@ use sp_npos_elections::*; const SEED: u32 = 0; /// Grab a funded user. -pub fn create_funded_user(string: &'static str, n: u32, balance_factor: u32) -> T::AccountId { +pub fn create_funded_user( + string: &'static str, + n: u32, + balance_factor: u32, +) -> T::AccountId { let user = account(string, n, SEED); let balance = T::Currency::minimum_balance() * balance_factor.into(); T::Currency::make_free_balance_be(&user, balance); @@ -39,30 +43,36 @@ pub fn create_funded_user(string: &'static str, n: u32, balance_factor } /// Create a stash and controller pair. -pub fn create_stash_controller(n: u32, balance_factor: u32) +pub fn create_stash_controller( + n: u32, + balance_factor: u32, + destination: RewardDestination, +) -> Result<(T::AccountId, T::AccountId), &'static str> { let stash = create_funded_user::("stash", n, balance_factor); let controller = create_funded_user::("controller", n, balance_factor); let controller_lookup: ::Source = T::Lookup::unlookup(controller.clone()); - let reward_destination = RewardDestination::Staked; let amount = T::Currency::minimum_balance() * (balance_factor / 10).max(1).into(); - Staking::::bond(RawOrigin::Signed(stash.clone()).into(), controller_lookup, amount, reward_destination)?; + Staking::::bond(RawOrigin::Signed(stash.clone()).into(), controller_lookup, amount, destination)?; return Ok((stash, controller)) } /// Create a stash and controller pair, where the controller is dead, and payouts go to controller. /// This is used to test worst case payout scenarios. -pub fn create_stash_and_dead_controller(n: u32, balance_factor: u32) +pub fn create_stash_and_dead_controller( + n: u32, + balance_factor: u32, + destination: RewardDestination, +) -> Result<(T::AccountId, T::AccountId), &'static str> { let stash = create_funded_user::("stash", n, balance_factor); // controller has no funds let controller = create_funded_user::("controller", n, 0); let controller_lookup: ::Source = T::Lookup::unlookup(controller.clone()); - let reward_destination = RewardDestination::Controller; let amount = T::Currency::minimum_balance() * (balance_factor / 10).max(1).into(); - Staking::::bond(RawOrigin::Signed(stash.clone()).into(), controller_lookup, amount, reward_destination)?; + Staking::::bond(RawOrigin::Signed(stash.clone()).into(), controller_lookup, amount, destination)?; return Ok((stash, controller)) } @@ -73,7 +83,7 @@ pub fn create_validators( ) -> Result::Source>, &'static str> { let mut validators: Vec<::Source> = Vec::with_capacity(max as usize); for i in 0 .. max { - let (stash, controller) = create_stash_controller::(i, balance_factor)?; + let (stash, controller) = create_stash_controller::(i, balance_factor, RewardDestination::Staked)?; let validator_prefs = ValidatorPrefs { commission: Perbill::from_percent(50), }; @@ -110,7 +120,7 @@ pub fn create_validators_with_nominators_for_era( // Create validators for i in 0 .. validators { let balance_factor = if randomize_stake { rng.next_u32() % 255 + 10 } else { 100u32 }; - let (v_stash, v_controller) = create_stash_controller::(i, balance_factor)?; + let (v_stash, v_controller) = create_stash_controller::(i, balance_factor, RewardDestination::Staked)?; let validator_prefs = ValidatorPrefs { commission: Perbill::from_percent(50), }; @@ -128,6 +138,7 @@ pub fn create_validators_with_nominators_for_era( let (_n_stash, n_controller) = create_stash_controller::( u32::max_value() - j, balance_factor, + RewardDestination::Staked, )?; // Have them randomly validate diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index d27654d1feae6..0f5d08a3a8ccc 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -3615,7 +3615,7 @@ mod offchain_election { // A validator index which is out of bound ExtBuilder::default() .offchain_election_ext() - .validator_count(4) + .validator_count(2) .has_stakers(false) .build() .execute_with(|| { @@ -3627,7 +3627,7 @@ mod offchain_election { let (mut compact, winners, score) = prepare_submission_with(true, 2, |_| {}); // index 4 doesn't exist. - compact.votes1.push((3, 4)); + compact.votes1.iter_mut().for_each(|(_, vidx)| if *vidx == 1 { *vidx = 4 }); // The error type sadly cannot be more specific now. assert_noop!( @@ -3688,12 +3688,57 @@ mod offchain_election { assert_eq!(Staking::snapshot_nominators().unwrap().len(), 5 + 4); assert_eq!(Staking::snapshot_validators().unwrap().len(), 4); + let (compact, winners, score) = prepare_submission_with(true, 2, |a| { + // swap all 11 and 41s in the distribution with non-winners. Note that it is + // important that the count of winners and the count of unique targets remain + // valid. + a.iter_mut().for_each(| StakedAssignment { who, distribution } | + distribution.iter_mut().for_each(|(t, _)| { + if *t == 41 { *t = 31 } else { *t = 21 } + // if it is self vote, correct that. + if *who == 41 { *who = 31 } + if *who == 11 { *who = 21 } + }) + ); + }); + + assert_noop!( + submit_solution( + Origin::signed(10), + winners, + compact, + score, + ), + Error::::OffchainElectionBogusNomination, + ); + }) + } + + #[test] + fn offchain_election_unique_target_count_is_checked() { + // Number of unique targets and and winners.len must match. + ExtBuilder::default() + .offchain_election_ext() + .validator_count(2) // we select only 2. + .has_stakers(false) + .build() + .execute_with(|| { + build_offchain_election_test_ext(); + run_to_block(12); + + assert_eq!(Staking::snapshot_nominators().unwrap().len(), 5 + 4); + assert_eq!(Staking::snapshot_validators().unwrap().len(), 4); + let (compact, winners, score) = prepare_submission_with(true, 2, |a| { a.iter_mut() .find(|x| x.who == 5) - // all 3 cannot be among the winners. Although, all of them are validator - // candidates. - .map(|x| x.distribution = vec![(21, 50), (41, 30), (31, 20)]); + // just add any new target. + .map(|x| { + // old value. + assert_eq!(x.distribution, vec![(41, 100)]); + // new value. + x.distribution = vec![(21, 50), (41, 50)] + }); }); assert_noop!( @@ -3703,7 +3748,7 @@ mod offchain_election { compact, score, ), - Error::::OffchainElectionBogusEdge, + Error::::OffchainElectionBogusWinnerCount, ); }) } diff --git a/primitives/npos-elections/compact/src/lib.rs b/primitives/npos-elections/compact/src/lib.rs index 54c94b6df65c2..134f3f59ff177 100644 --- a/primitives/npos-elections/compact/src/lib.rs +++ b/primitives/npos-elections/compact/src/lib.rs @@ -149,22 +149,9 @@ fn struct_def( ) }).collect::(); - - let len_impl = (1..=count).map(|c| { - let field_name = field_name_for(c); - quote!( - all_len = all_len.saturating_add(self.#field_name.len()); - ) - }).collect::(); - - let edge_count_impl = (1..=count).map(|c| { - let field_name = field_name_for(c); - quote!( - all_edges = all_edges.saturating_add( - self.#field_name.len().saturating_mul(#c as usize) - ); - ) - }).collect::(); + let len_impl = len_impl(count); + let edge_count_impl = edge_count_impl(count); + let unique_targets_impl = unique_targets_impl(count); let derives_and_maybe_compact_encoding = if compact_encoding { // custom compact encoding. @@ -209,6 +196,26 @@ fn struct_def( all_edges } + /// Get the number of unique targets in the whole struct. + /// + /// Once presented with a list of winners, this set and the set of winners must be + /// equal. + /// + /// The resulting indices are sorted. + pub fn unique_targets(&self) -> Vec<#target_type> { + let mut all_targets: Vec<#target_type> = Vec::with_capacity(self.average_edge_count()); + let mut maybe_insert_target = |t: #target_type| { + match all_targets.binary_search(&t) { + Ok(_) => (), + Err(pos) => all_targets.insert(pos, t) + } + }; + + #unique_targets_impl + + all_targets + } + /// Get the average edge count. pub fn average_edge_count(&self) -> usize { self.edge_count().checked_div(self.len()).unwrap_or(0) @@ -217,6 +224,65 @@ fn struct_def( )) } +fn len_impl(count: usize) -> TokenStream2 { + (1..=count).map(|c| { + let field_name = field_name_for(c); + quote!( + all_len = all_len.saturating_add(self.#field_name.len()); + ) + }).collect::() +} + +fn edge_count_impl(count: usize) -> TokenStream2 { + (1..=count).map(|c| { + let field_name = field_name_for(c); + quote!( + all_edges = all_edges.saturating_add( + self.#field_name.len().saturating_mul(#c as usize) + ); + ) + }).collect::() +} + +fn unique_targets_impl(count: usize) -> TokenStream2 { + let unique_targets_impl_single = { + let field_name = field_name_for(1); + quote! { + self.#field_name.iter().for_each(|(_, t)| { + maybe_insert_target(*t); + }); + } + }; + + let unique_targets_impl_double = { + let field_name = field_name_for(2); + quote! { + self.#field_name.iter().for_each(|(_, (t1, _), t2)| { + maybe_insert_target(*t1); + maybe_insert_target(*t2); + }); + } + }; + + let unique_targets_impl_rest = (3..=count).map(|c| { + let field_name = field_name_for(c); + quote! { + self.#field_name.iter().for_each(|(_, inners, t_last)| { + inners.iter().for_each(|(t, _)| { + maybe_insert_target(*t); + }); + maybe_insert_target(*t_last); + }); + } + }).collect::(); + + quote! { + #unique_targets_impl_single + #unique_targets_impl_double + #unique_targets_impl_rest + } +} + fn imports() -> Result { if std::env::var("CARGO_PKG_NAME").unwrap() == "sp-npos-elections" { Ok(quote! { diff --git a/primitives/npos-elections/src/tests.rs b/primitives/npos-elections/src/tests.rs index 8e99d2222e885..d1769acd08144 100644 --- a/primitives/npos-elections/src/tests.rs +++ b/primitives/npos-elections/src/tests.rs @@ -1002,6 +1002,7 @@ mod solution_type { ); assert_eq!(compact.len(), 4); assert_eq!(compact.edge_count(), 2 + 4); + assert_eq!(compact.unique_targets(), vec![10, 11, 20, 40, 50, 51]); } #[test] @@ -1097,6 +1098,11 @@ mod solution_type { } ); + assert_eq!( + compacted.unique_targets(), + vec![0, 1, 2, 3, 4, 5, 6, 7, 8], + ); + let voter_at = |a: u32| -> Option { voters.get(>::try_into(a).unwrap()).cloned() }; @@ -1110,6 +1116,69 @@ mod solution_type { ); } + #[test] + fn unique_targets_len_edge_count_works() { + const ACC: TestAccuracy = TestAccuracy::from_percent(10); + + // we don't really care about voters here so all duplicates. This is not invalid per se. + let compact = TestSolutionCompact { + votes1: vec![(99, 1), (99, 2)], + votes2: vec![ + (99, (3, ACC.clone()), 7), + (99, (4, ACC.clone()), 8), + ], + votes3: vec![ + (99, [(11, ACC.clone()), (12, ACC.clone())], 13), + ], + // ensure the last one is also counted. + votes16: vec![ + ( + 99, + [ + (66, ACC.clone()), + (66, ACC.clone()), + (66, ACC.clone()), + (66, ACC.clone()), + (66, ACC.clone()), + (66, ACC.clone()), + (66, ACC.clone()), + (66, ACC.clone()), + (66, ACC.clone()), + (66, ACC.clone()), + (66, ACC.clone()), + (66, ACC.clone()), + (66, ACC.clone()), + (66, ACC.clone()), + (66, ACC.clone()), + ], + 67, + ) + ], + ..Default::default() + }; + + assert_eq!(compact.unique_targets(), vec![1, 2, 3, 4, 7, 8, 11, 12, 13, 66, 67]); + assert_eq!(compact.edge_count(), 2 + (2 * 2) + 3 + 16); + assert_eq!(compact.len(), 6); + + // this one has some duplicates. + let compact = TestSolutionCompact { + votes1: vec![(99, 1), (99, 1)], + votes2: vec![ + (99, (3, ACC.clone()), 7), + (99, (4, ACC.clone()), 8), + ], + votes3: vec![ + (99, [(11, ACC.clone()), (11, ACC.clone())], 13), + ], + ..Default::default() + }; + + assert_eq!(compact.unique_targets(), vec![1, 3, 4, 7, 8, 11, 13]); + assert_eq!(compact.edge_count(), 2 + (2 * 2) + 3); + assert_eq!(compact.len(), 5); + } + #[test] fn compact_into_assignment_must_report_overflow() { // in votes2 @@ -1165,7 +1234,7 @@ mod solution_type { assert_eq!(compacted.unwrap_err(), PhragmenError::CompactTargetOverflow); } - #[test] + #[test] fn zero_target_count_is_ignored() { let voters = vec![1 as AccountId, 2]; let targets = vec![10 as AccountId, 11];