From 9c06d8c6f4af1b7a51d2a3f608c0c2b8c6ba4e71 Mon Sep 17 00:00:00 2001 From: Shawn Tabrizi Date: Tue, 17 Mar 2020 15:12:04 +0200 Subject: [PATCH] Benchmark Staking and Session Pallet (#5183) * starting bench * More * more * Payout Validator * Give each validator exactly n nominators * Update with test * Try to add accounts to chain spec * Undo changes to chainspec * Payout nominator * Rebond and Reap Stash * Set history depth * fix smelly code * cancel deferred slash * new_era bench * do_slash benchmark * Add features * undo extrinsic move * lower * Update new era * Update benchmarking.rs * whitespace * Apply suggestions from code review Co-Authored-By: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * fixes * nit * Refactor tests, initial code * Move session benchmarks to avoid cyclic deps * Update lib.rs * Fix warnings * Move impl * Update to do random nominator allocation * add feature to benchmark pallet * Remove extra stuff * Update based on feedback * Less intrusive * Remove `transfer_idle_users` * remove again * unused dep * test feature flag * Update to latest substrate Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- substrate/Cargo.lock | 15 + substrate/Cargo.toml | 1 + substrate/bin/node/runtime/Cargo.toml | 7 +- substrate/bin/node/runtime/src/lib.rs | 19 + substrate/frame/balances/src/benchmarking.rs | 2 +- substrate/frame/benchmark/Cargo.toml | 3 +- substrate/frame/benchmark/src/lib.rs | 1 + substrate/frame/session/Cargo.toml | 2 +- .../frame/session/benchmarking/Cargo.toml | 28 ++ .../frame/session/benchmarking/src/lib.rs | 57 +++ substrate/frame/session/src/lib.rs | 311 +----------- substrate/frame/session/src/tests.rs | 319 ++++++++++++ substrate/frame/staking/Cargo.toml | 10 + substrate/frame/staking/src/benchmarking.rs | 464 ++++++++++++++++++ substrate/frame/staking/src/lib.rs | 20 +- substrate/frame/staking/src/mock.rs | 33 +- substrate/frame/staking/src/slashing.rs | 2 +- 17 files changed, 961 insertions(+), 333 deletions(-) create mode 100644 substrate/frame/session/benchmarking/Cargo.toml create mode 100644 substrate/frame/session/benchmarking/src/lib.rs create mode 100644 substrate/frame/session/src/tests.rs create mode 100644 substrate/frame/staking/src/benchmarking.rs diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index cb7d7c713b..f929a3dcbf 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -3543,6 +3543,7 @@ dependencies = [ "pallet-randomness-collective-flip", "pallet-recovery", "pallet-session", + "pallet-session-benchmarking", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", @@ -4396,6 +4397,18 @@ dependencies = [ "sp-trie", ] +[[package]] +name = "pallet-session-benchmarking" +version = "2.0.0-alpha.3" +dependencies = [ + "frame-benchmarking", + "frame-system", + "pallet-session", + "pallet-staking", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-society" version = "2.0.0-alpha.3" @@ -4416,6 +4429,7 @@ dependencies = [ name = "pallet-staking" version = "2.0.0-alpha.3" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", "pallet-authorship", @@ -4424,6 +4438,7 @@ dependencies = [ "pallet-staking-reward-curve", "pallet-timestamp", "parity-scale-codec", + "rand_chacha 0.2.1", "serde", "sp-core", "sp-io", diff --git a/substrate/Cargo.toml b/substrate/Cargo.toml index 0459bc8ebb..5af37839ad 100644 --- a/substrate/Cargo.toml +++ b/substrate/Cargo.toml @@ -88,6 +88,7 @@ members = [ "frame/recovery", "frame/scored-pool", "frame/session", + "frame/session/benchmarking", "frame/society", "frame/staking", "frame/staking/reward-curve", diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index 15672715a4..c279a25b0a 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/bin/node/runtime/Cargo.toml @@ -59,6 +59,7 @@ pallet-offences = { version = "2.0.0-alpha.2", default-features = false, path = pallet-randomness-collective-flip = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/randomness-collective-flip" } pallet-recovery = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/recovery" } pallet-session = { version = "2.0.0-alpha.2", features = ["historical"], path = "../../../frame/session", default-features = false } +pallet-session-benchmarking = { version = "2.0.0-alpha.2", path = "../../../frame/session/benchmarking", default-features = false, optional = true } pallet-staking = { version = "2.0.0-alpha.2", features = ["migrate"], path = "../../../frame/staking", default-features = false } pallet-staking-reward-curve = { version = "2.0.0-alpha.2", path = "../../../frame/staking/reward-curve" } pallet-sudo = { version = "2.0.0-alpha.2", default-features = false, path = "../../../frame/sudo" } @@ -134,8 +135,10 @@ std = [ ] runtime-benchmarks = [ "frame-benchmarking", - "pallet-timestamp/runtime-benchmarks", - "pallet-identity/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + "pallet-identity/runtime-benchmarks", + "pallet-session-benchmarking", + "pallet-timestamp/runtime-benchmarks", "pallet-vesting/runtime-benchmarks", + "pallet-staking/runtime-benchmarks", ] diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index f15dbcbf5e..5468ee0fb3 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -851,6 +851,11 @@ impl_runtime_apis! { repeat: u32, ) -> Result, sp_runtime::RuntimeString> { use frame_benchmarking::Benchmarking; + // Trying to add benchmarks directly to the Session Pallet caused cyclic dependency issues. + // To get around that, we separated the Session benchmarks into its own crate, which is why + // we need these two lines below. + use pallet_session_benchmarking::Module as SessionBench; + impl pallet_session_benchmarking::Trait for Runtime {} let result = match module.as_slice() { b"pallet-balances" | b"balances" => Balances::run_benchmark( @@ -867,6 +872,20 @@ impl_runtime_apis! { steps, repeat, ), + b"pallet-session" | b"session" => SessionBench::::run_benchmark( + extrinsic, + lowest_range_values, + highest_range_values, + steps, + repeat, + ), + b"pallet-staking" | b"staking" => Staking::run_benchmark( + extrinsic, + lowest_range_values, + highest_range_values, + steps, + repeat, + ), b"pallet-timestamp" | b"timestamp" => Timestamp::run_benchmark( extrinsic, lowest_range_values, diff --git a/substrate/frame/balances/src/benchmarking.rs b/substrate/frame/balances/src/benchmarking.rs index 2473ce2920..d35ec7cc85 100644 --- a/substrate/frame/balances/src/benchmarking.rs +++ b/substrate/frame/balances/src/benchmarking.rs @@ -116,4 +116,4 @@ benchmarks! { let balance_amount = existential_deposit.saturating_mul(e.into()); let _ = as Currency<_>>::make_free_balance_be(&user, balance_amount); }: set_balance(RawOrigin::Root, user_lookup, 0.into(), 0.into()) -} \ No newline at end of file +} diff --git a/substrate/frame/benchmark/Cargo.toml b/substrate/frame/benchmark/Cargo.toml index bf237d992c..ac6fdbfe52 100644 --- a/substrate/frame/benchmark/Cargo.toml +++ b/substrate/frame/benchmark/Cargo.toml @@ -13,7 +13,7 @@ sp-io = { version = "2.0.0-alpha.3", default-features = false, path = "../../pri sp-runtime = { version = "2.0.0-alpha.3", default-features = false, path = "../../primitives/runtime" } frame-support = { version = "2.0.0-alpha.3", default-features = false, path = "../support" } frame-system = { version = "2.0.0-alpha.3", default-features = false, path = "../system" } -frame-benchmarking = { version = "2.0.0-alpha.3", default-features = false, path = "../benchmarking" } +frame-benchmarking = { version = "2.0.0-alpha.3", default-features = false, path = "../benchmarking", optional = true } [features] default = ["std"] @@ -27,3 +27,4 @@ std = [ "frame-system/std", "frame-benchmarking/std", ] +runtime-benchmarks = ["frame-benchmarking"] diff --git a/substrate/frame/benchmark/src/lib.rs b/substrate/frame/benchmark/src/lib.rs index a519e98930..ec21f437d5 100644 --- a/substrate/frame/benchmark/src/lib.rs +++ b/substrate/frame/benchmark/src/lib.rs @@ -26,6 +26,7 @@ use frame_system::{self as system, ensure_signed}; use codec::{Encode, Decode}; use sp_std::prelude::Vec; +#[cfg(feature = "runtime-benchmarks")] pub mod benchmarking; /// Type alias for currency balance. diff --git a/substrate/frame/session/Cargo.toml b/substrate/frame/session/Cargo.toml index 74ca9fe67b..5bea3ecb9f 100644 --- a/substrate/frame/session/Cargo.toml +++ b/substrate/frame/session/Cargo.toml @@ -17,7 +17,7 @@ sp-staking = { version = "2.0.0-alpha.2", default-features = false, path = "../. frame-support = { version = "2.0.0-alpha.2", default-features = false, path = "../support" } frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../system" } pallet-timestamp = { version = "2.0.0-alpha.2", default-features = false, path = "../timestamp" } -sp-trie = { optional = true, path = "../../primitives/trie", default-features = false , version = "2.0.0-alpha.2"} +sp-trie = { optional = true, path = "../../primitives/trie", default-features = false, version = "2.0.0-alpha.2"} sp-io ={ path = "../../primitives/io", default-features = false , version = "2.0.0-alpha.2"} impl-trait-for-tuples = "0.1.3" diff --git a/substrate/frame/session/benchmarking/Cargo.toml b/substrate/frame/session/benchmarking/Cargo.toml new file mode 100644 index 0000000000..ec117e03e2 --- /dev/null +++ b/substrate/frame/session/benchmarking/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "pallet-session-benchmarking" +version = "2.0.0-alpha.3" +authors = ["Parity Technologies "] +edition = "2018" +license = "GPL-3.0" +homepage = "https://substrate.dev" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME sessions pallet benchmarking" + +[dependencies] +sp-std = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/std" } +sp-runtime = { version = "2.0.0-alpha.2", default-features = false, path = "../../../primitives/runtime" } +frame-system = { version = "2.0.0-alpha.2", default-features = false, path = "../../system" } +frame-benchmarking = { version = "2.0.0-alpha.2", default-features = false, path = "../../benchmarking" } +pallet-staking = { version = "2.0.0-alpha.2", default-features = false, features = ["runtime-benchmarks"], path = "../../staking" } +pallet-session = { version = "2.0.0-alpha.2", default-features = false, path = "../../session" } + +[features] +default = ["std"] +std = [ + "sp-std/std", + "sp-runtime/std", + "frame-system/std", + "frame-benchmarking/std", + "pallet-staking/std", + "pallet-session/std", +] diff --git a/substrate/frame/session/benchmarking/src/lib.rs b/substrate/frame/session/benchmarking/src/lib.rs new file mode 100644 index 0000000000..db925bd72e --- /dev/null +++ b/substrate/frame/session/benchmarking/src/lib.rs @@ -0,0 +1,57 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Benchmarks for the Session Pallet. +// This is separated into its own crate due to cyclic dependency issues. + +#![cfg_attr(not(feature = "std"), no_std)] + +use sp_std::prelude::*; +use sp_std::vec; + +use frame_system::RawOrigin; +use frame_benchmarking::benchmarks; + +use pallet_session::*; +use pallet_session::Module as Session; + +use pallet_staking::{ + MAX_NOMINATIONS, + benchmarking::create_validator_with_nominators, +}; + +pub struct Module(pallet_session::Module); + +pub trait Trait: pallet_session::Trait + pallet_staking::Trait {} + +benchmarks! { + _ { } + + set_keys { + let n in 1 .. MAX_NOMINATIONS as u32; + let validator = create_validator_with_nominators::(n, MAX_NOMINATIONS as u32)?; + let keys = T::Keys::default(); + let proof: Vec = vec![0,1,2,3]; + }: _(RawOrigin::Signed(validator), keys, proof) + + purge_keys { + let n in 1 .. MAX_NOMINATIONS as u32; + let validator = create_validator_with_nominators::(n, MAX_NOMINATIONS as u32)?; + let keys = T::Keys::default(); + let proof: Vec = vec![0,1,2,3]; + Session::::set_keys(RawOrigin::Signed(validator.clone()).into(), keys, proof)?; + }: _(RawOrigin::Signed(validator)) +} diff --git a/substrate/frame/session/src/lib.rs b/substrate/frame/session/src/lib.rs index e98ea2475d..82cacdb516 100644 --- a/substrate/frame/session/src/lib.rs +++ b/substrate/frame/session/src/lib.rs @@ -113,6 +113,8 @@ use frame_support::traits::MigrateAccount; #[cfg(test)] mod mock; +#[cfg(test)] +mod tests; #[cfg(feature = "historical")] pub mod historical; @@ -466,7 +468,7 @@ decl_module! { /// In this case, purge_keys will need to be called before the account can be removed. /// # #[weight = SimpleDispatchInfo::FixedNormal(150_000)] - fn set_keys(origin, keys: T::Keys, proof: Vec) -> dispatch::DispatchResult { + pub fn set_keys(origin, keys: T::Keys, proof: Vec) -> dispatch::DispatchResult { let who = ensure_signed(origin)?; ensure!(keys.ownership_proof_is_valid(&proof), Error::::InvalidProof); @@ -487,7 +489,7 @@ decl_module! { /// - Reduces system account refs by one on success. /// # #[weight = SimpleDispatchInfo::FixedNormal(150_000)] - fn purge_keys(origin) { + pub fn purge_keys(origin) { let who = ensure_signed(origin)?; Self::do_purge_keys(&who)?; } @@ -742,308 +744,3 @@ impl> FindAuthor validators.get(i as usize).map(|k| k.clone()) } } - -#[cfg(test)] -mod tests { - use super::*; - use frame_support::assert_ok; - use sp_core::crypto::key_types::DUMMY; - use sp_runtime::{traits::OnInitialize, testing::UintAuthorityId}; - use mock::{ - NEXT_VALIDATORS, SESSION_CHANGED, TEST_SESSION_CHANGED, authorities, force_new_session, - set_next_validators, set_session_length, session_changed, Test, Origin, System, Session, - reset_before_session_end_called, before_session_end_called, - }; - - fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - GenesisConfig:: { - keys: NEXT_VALIDATORS.with(|l| - l.borrow().iter().cloned().map(|i| (i, i, UintAuthorityId(i).into())).collect() - ), - }.assimilate_storage(&mut t).unwrap(); - sp_io::TestExternalities::new(t) - } - - fn initialize_block(block: u64) { - SESSION_CHANGED.with(|l| *l.borrow_mut() = false); - System::set_block_number(block); - Session::on_initialize(block); - } - - #[test] - fn simple_setup_should_work() { - new_test_ext().execute_with(|| { - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); - assert_eq!(Session::validators(), vec![1, 2, 3]); - }); - } - - #[test] - fn put_get_keys() { - new_test_ext().execute_with(|| { - Session::put_keys(&10, &UintAuthorityId(10).into()); - assert_eq!(Session::load_keys(&10), Some(UintAuthorityId(10).into())); - }) - } - - #[test] - fn keys_cleared_on_kill() { - let mut ext = new_test_ext(); - ext.execute_with(|| { - assert_eq!(Session::validators(), vec![1, 2, 3]); - assert_eq!(Session::load_keys(&1), Some(UintAuthorityId(1).into())); - - let id = DUMMY; - assert_eq!(Session::key_owner(id, UintAuthorityId(1).get_raw(id)), Some(1)); - - assert!(!System::allow_death(&1)); - assert_ok!(Session::purge_keys(Origin::signed(1))); - assert!(System::allow_death(&1)); - - assert_eq!(Session::load_keys(&1), None); - assert_eq!(Session::key_owner(id, UintAuthorityId(1).get_raw(id)), None); - }) - } - - #[test] - fn authorities_should_track_validators() { - reset_before_session_end_called(); - - new_test_ext().execute_with(|| { - set_next_validators(vec![1, 2]); - force_new_session(); - initialize_block(1); - assert_eq!(Session::queued_keys(), vec![ - (1, UintAuthorityId(1).into()), - (2, UintAuthorityId(2).into()), - ]); - assert_eq!(Session::validators(), vec![1, 2, 3]); - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); - assert!(before_session_end_called()); - reset_before_session_end_called(); - - force_new_session(); - initialize_block(2); - assert_eq!(Session::queued_keys(), vec![ - (1, UintAuthorityId(1).into()), - (2, UintAuthorityId(2).into()), - ]); - assert_eq!(Session::validators(), vec![1, 2]); - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2)]); - assert!(before_session_end_called()); - reset_before_session_end_called(); - - set_next_validators(vec![1, 2, 4]); - assert_ok!(Session::set_keys(Origin::signed(4), UintAuthorityId(4).into(), vec![])); - force_new_session(); - initialize_block(3); - assert_eq!(Session::queued_keys(), vec![ - (1, UintAuthorityId(1).into()), - (2, UintAuthorityId(2).into()), - (4, UintAuthorityId(4).into()), - ]); - assert_eq!(Session::validators(), vec![1, 2]); - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2)]); - assert!(before_session_end_called()); - - force_new_session(); - initialize_block(4); - assert_eq!(Session::queued_keys(), vec![ - (1, UintAuthorityId(1).into()), - (2, UintAuthorityId(2).into()), - (4, UintAuthorityId(4).into()), - ]); - assert_eq!(Session::validators(), vec![1, 2, 4]); - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(4)]); - }); - } - - #[test] - fn should_work_with_early_exit() { - new_test_ext().execute_with(|| { - set_session_length(10); - - initialize_block(1); - assert_eq!(Session::current_index(), 0); - - initialize_block(2); - assert_eq!(Session::current_index(), 0); - - force_new_session(); - initialize_block(3); - assert_eq!(Session::current_index(), 1); - - initialize_block(9); - assert_eq!(Session::current_index(), 1); - - initialize_block(10); - assert_eq!(Session::current_index(), 2); - }); - } - - #[test] - fn session_change_should_work() { - new_test_ext().execute_with(|| { - // Block 1: No change - initialize_block(1); - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); - - // Block 2: Session rollover, but no change. - initialize_block(2); - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); - - // Block 3: Set new key for validator 2; no visible change. - initialize_block(3); - assert_ok!(Session::set_keys(Origin::signed(2), UintAuthorityId(5).into(), vec![])); - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); - - // Block 4: Session rollover; no visible change. - initialize_block(4); - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); - - // Block 5: No change. - initialize_block(5); - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); - - // Block 6: Session rollover; authority 2 changes. - initialize_block(6); - assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(5), UintAuthorityId(3)]); - }); - } - - #[test] - fn duplicates_are_not_allowed() { - new_test_ext().execute_with(|| { - System::set_block_number(1); - Session::on_initialize(1); - assert!(Session::set_keys(Origin::signed(4), UintAuthorityId(1).into(), vec![]).is_err()); - assert!(Session::set_keys(Origin::signed(1), UintAuthorityId(10).into(), vec![]).is_ok()); - - // is fine now that 1 has migrated off. - assert!(Session::set_keys(Origin::signed(4), UintAuthorityId(1).into(), vec![]).is_ok()); - }); - } - - #[test] - fn session_changed_flag_works() { - reset_before_session_end_called(); - - new_test_ext().execute_with(|| { - TEST_SESSION_CHANGED.with(|l| *l.borrow_mut() = true); - - force_new_session(); - initialize_block(1); - assert!(!session_changed()); - assert!(before_session_end_called()); - reset_before_session_end_called(); - - force_new_session(); - initialize_block(2); - assert!(!session_changed()); - assert!(before_session_end_called()); - reset_before_session_end_called(); - - Session::disable_index(0); - force_new_session(); - initialize_block(3); - assert!(!session_changed()); - assert!(before_session_end_called()); - reset_before_session_end_called(); - - force_new_session(); - initialize_block(4); - assert!(session_changed()); - assert!(before_session_end_called()); - reset_before_session_end_called(); - - force_new_session(); - initialize_block(5); - assert!(!session_changed()); - assert!(before_session_end_called()); - reset_before_session_end_called(); - - assert_ok!(Session::set_keys(Origin::signed(2), UintAuthorityId(5).into(), vec![])); - force_new_session(); - initialize_block(6); - assert!(!session_changed()); - assert!(before_session_end_called()); - reset_before_session_end_called(); - - // changing the keys of a validator leads to change. - assert_ok!(Session::set_keys(Origin::signed(69), UintAuthorityId(69).into(), vec![])); - force_new_session(); - initialize_block(7); - assert!(session_changed()); - assert!(before_session_end_called()); - reset_before_session_end_called(); - - // while changing the keys of a non-validator does not. - force_new_session(); - initialize_block(7); - assert!(!session_changed()); - assert!(before_session_end_called()); - reset_before_session_end_called(); - }); - } - - #[test] - fn periodic_session_works() { - struct Period; - struct Offset; - - impl Get for Period { - fn get() -> u64 { 10 } - } - - impl Get for Offset { - fn get() -> u64 { 3 } - } - - - type P = PeriodicSessions; - - for i in 0..3 { - assert!(!P::should_end_session(i)); - } - - assert!(P::should_end_session(3)); - - for i in (1..10).map(|i| 3 + i) { - assert!(!P::should_end_session(i)); - } - - assert!(P::should_end_session(13)); - } - - #[test] - fn session_keys_generate_output_works_as_set_keys_input() { - new_test_ext().execute_with(|| { - let new_keys = mock::MockSessionKeys::generate(None); - assert_ok!( - Session::set_keys( - Origin::signed(2), - ::Keys::decode(&mut &new_keys[..]).expect("Decode keys"), - vec![], - ) - ); - }); - } - - #[test] - fn return_true_if_more_than_third_is_disabled() { - new_test_ext().execute_with(|| { - set_next_validators(vec![1, 2, 3, 4, 5, 6, 7]); - force_new_session(); - initialize_block(1); - // apply the new validator set - force_new_session(); - initialize_block(2); - - assert_eq!(Session::disable_index(0), false); - assert_eq!(Session::disable_index(1), false); - assert_eq!(Session::disable_index(2), true); - assert_eq!(Session::disable_index(3), true); - }); - } -} diff --git a/substrate/frame/session/src/tests.rs b/substrate/frame/session/src/tests.rs new file mode 100644 index 0000000000..052313f21d --- /dev/null +++ b/substrate/frame/session/src/tests.rs @@ -0,0 +1,319 @@ +// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +// Tests for the Session Pallet + +use super::*; +use frame_support::assert_ok; +use sp_core::crypto::key_types::DUMMY; +use sp_runtime::{traits::OnInitialize, testing::UintAuthorityId}; +use mock::{ + NEXT_VALIDATORS, SESSION_CHANGED, TEST_SESSION_CHANGED, authorities, force_new_session, + set_next_validators, set_session_length, session_changed, Test, Origin, System, Session, + reset_before_session_end_called, before_session_end_called, +}; + +fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + GenesisConfig:: { + keys: NEXT_VALIDATORS.with(|l| + l.borrow().iter().cloned().map(|i| (i, i, UintAuthorityId(i).into())).collect() + ), + }.assimilate_storage(&mut t).unwrap(); + sp_io::TestExternalities::new(t) +} + +fn initialize_block(block: u64) { + SESSION_CHANGED.with(|l| *l.borrow_mut() = false); + System::set_block_number(block); + Session::on_initialize(block); +} + +#[test] +fn simple_setup_should_work() { + new_test_ext().execute_with(|| { + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); + assert_eq!(Session::validators(), vec![1, 2, 3]); + }); +} + +#[test] +fn put_get_keys() { + new_test_ext().execute_with(|| { + Session::put_keys(&10, &UintAuthorityId(10).into()); + assert_eq!(Session::load_keys(&10), Some(UintAuthorityId(10).into())); + }) +} + +#[test] +fn keys_cleared_on_kill() { + let mut ext = new_test_ext(); + ext.execute_with(|| { + assert_eq!(Session::validators(), vec![1, 2, 3]); + assert_eq!(Session::load_keys(&1), Some(UintAuthorityId(1).into())); + + let id = DUMMY; + assert_eq!(Session::key_owner(id, UintAuthorityId(1).get_raw(id)), Some(1)); + + assert!(!System::allow_death(&1)); + assert_ok!(Session::purge_keys(Origin::signed(1))); + assert!(System::allow_death(&1)); + + assert_eq!(Session::load_keys(&1), None); + assert_eq!(Session::key_owner(id, UintAuthorityId(1).get_raw(id)), None); + }) +} + +#[test] +fn authorities_should_track_validators() { + reset_before_session_end_called(); + + new_test_ext().execute_with(|| { + set_next_validators(vec![1, 2]); + force_new_session(); + initialize_block(1); + assert_eq!(Session::queued_keys(), vec![ + (1, UintAuthorityId(1).into()), + (2, UintAuthorityId(2).into()), + ]); + assert_eq!(Session::validators(), vec![1, 2, 3]); + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); + assert!(before_session_end_called()); + reset_before_session_end_called(); + + force_new_session(); + initialize_block(2); + assert_eq!(Session::queued_keys(), vec![ + (1, UintAuthorityId(1).into()), + (2, UintAuthorityId(2).into()), + ]); + assert_eq!(Session::validators(), vec![1, 2]); + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2)]); + assert!(before_session_end_called()); + reset_before_session_end_called(); + + set_next_validators(vec![1, 2, 4]); + assert_ok!(Session::set_keys(Origin::signed(4), UintAuthorityId(4).into(), vec![])); + force_new_session(); + initialize_block(3); + assert_eq!(Session::queued_keys(), vec![ + (1, UintAuthorityId(1).into()), + (2, UintAuthorityId(2).into()), + (4, UintAuthorityId(4).into()), + ]); + assert_eq!(Session::validators(), vec![1, 2]); + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2)]); + assert!(before_session_end_called()); + + force_new_session(); + initialize_block(4); + assert_eq!(Session::queued_keys(), vec![ + (1, UintAuthorityId(1).into()), + (2, UintAuthorityId(2).into()), + (4, UintAuthorityId(4).into()), + ]); + assert_eq!(Session::validators(), vec![1, 2, 4]); + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(4)]); + }); +} + +#[test] +fn should_work_with_early_exit() { + new_test_ext().execute_with(|| { + set_session_length(10); + + initialize_block(1); + assert_eq!(Session::current_index(), 0); + + initialize_block(2); + assert_eq!(Session::current_index(), 0); + + force_new_session(); + initialize_block(3); + assert_eq!(Session::current_index(), 1); + + initialize_block(9); + assert_eq!(Session::current_index(), 1); + + initialize_block(10); + assert_eq!(Session::current_index(), 2); + }); +} + +#[test] +fn session_change_should_work() { + new_test_ext().execute_with(|| { + // Block 1: No change + initialize_block(1); + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); + + // Block 2: Session rollover, but no change. + initialize_block(2); + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); + + // Block 3: Set new key for validator 2; no visible change. + initialize_block(3); + assert_ok!(Session::set_keys(Origin::signed(2), UintAuthorityId(5).into(), vec![])); + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); + + // Block 4: Session rollover; no visible change. + initialize_block(4); + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); + + // Block 5: No change. + initialize_block(5); + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); + + // Block 6: Session rollover; authority 2 changes. + initialize_block(6); + assert_eq!(authorities(), vec![UintAuthorityId(1), UintAuthorityId(5), UintAuthorityId(3)]); + }); +} + +#[test] +fn duplicates_are_not_allowed() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + Session::on_initialize(1); + assert!(Session::set_keys(Origin::signed(4), UintAuthorityId(1).into(), vec![]).is_err()); + assert!(Session::set_keys(Origin::signed(1), UintAuthorityId(10).into(), vec![]).is_ok()); + + // is fine now that 1 has migrated off. + assert!(Session::set_keys(Origin::signed(4), UintAuthorityId(1).into(), vec![]).is_ok()); + }); +} + +#[test] +fn session_changed_flag_works() { + reset_before_session_end_called(); + + new_test_ext().execute_with(|| { + TEST_SESSION_CHANGED.with(|l| *l.borrow_mut() = true); + + force_new_session(); + initialize_block(1); + assert!(!session_changed()); + assert!(before_session_end_called()); + reset_before_session_end_called(); + + force_new_session(); + initialize_block(2); + assert!(!session_changed()); + assert!(before_session_end_called()); + reset_before_session_end_called(); + + Session::disable_index(0); + force_new_session(); + initialize_block(3); + assert!(!session_changed()); + assert!(before_session_end_called()); + reset_before_session_end_called(); + + force_new_session(); + initialize_block(4); + assert!(session_changed()); + assert!(before_session_end_called()); + reset_before_session_end_called(); + + force_new_session(); + initialize_block(5); + assert!(!session_changed()); + assert!(before_session_end_called()); + reset_before_session_end_called(); + + assert_ok!(Session::set_keys(Origin::signed(2), UintAuthorityId(5).into(), vec![])); + force_new_session(); + initialize_block(6); + assert!(!session_changed()); + assert!(before_session_end_called()); + reset_before_session_end_called(); + + // changing the keys of a validator leads to change. + assert_ok!(Session::set_keys(Origin::signed(69), UintAuthorityId(69).into(), vec![])); + force_new_session(); + initialize_block(7); + assert!(session_changed()); + assert!(before_session_end_called()); + reset_before_session_end_called(); + + // while changing the keys of a non-validator does not. + force_new_session(); + initialize_block(7); + assert!(!session_changed()); + assert!(before_session_end_called()); + reset_before_session_end_called(); + }); +} + +#[test] +fn periodic_session_works() { + struct Period; + struct Offset; + + impl Get for Period { + fn get() -> u64 { 10 } + } + + impl Get for Offset { + fn get() -> u64 { 3 } + } + + + type P = PeriodicSessions; + + for i in 0..3 { + assert!(!P::should_end_session(i)); + } + + assert!(P::should_end_session(3)); + + for i in (1..10).map(|i| 3 + i) { + assert!(!P::should_end_session(i)); + } + + assert!(P::should_end_session(13)); +} + +#[test] +fn session_keys_generate_output_works_as_set_keys_input() { + new_test_ext().execute_with(|| { + let new_keys = mock::MockSessionKeys::generate(None); + assert_ok!( + Session::set_keys( + Origin::signed(2), + ::Keys::decode(&mut &new_keys[..]).expect("Decode keys"), + vec![], + ) + ); + }); +} + +#[test] +fn return_true_if_more_than_third_is_disabled() { + new_test_ext().execute_with(|| { + set_next_validators(vec![1, 2, 3, 4, 5, 6, 7]); + force_new_session(); + initialize_block(1); + // apply the new validator set + force_new_session(); + initialize_block(2); + + assert_eq!(Session::disable_index(0), false); + assert_eq!(Session::disable_index(1), false); + assert_eq!(Session::disable_index(2), true); + assert_eq!(Session::disable_index(3), true); + }); +} diff --git a/substrate/frame/staking/Cargo.toml b/substrate/frame/staking/Cargo.toml index ef4bb60a29..e83f263285 100644 --- a/substrate/frame/staking/Cargo.toml +++ b/substrate/frame/staking/Cargo.toml @@ -22,12 +22,17 @@ frame-system = { version = "2.0.0-alpha.2", default-features = false, path = ".. pallet-session = { version = "2.0.0-alpha.2", features = ["historical"], path = "../session", default-features = false } pallet-authorship = { version = "2.0.0-alpha.2", default-features = false, path = "../authorship" } +frame-benchmarking = { version = "2.0.0-alpha.2", default-features = false, path = "../benchmarking", optional = true } +rand_chacha = { version = "0.2", default-features = false, optional = true } + [dev-dependencies] sp-core = { version = "2.0.0-alpha.2", path = "../../primitives/core" } pallet-balances = { version = "2.0.0-alpha.2", path = "../balances" } pallet-timestamp = { version = "2.0.0-alpha.2", path = "../timestamp" } pallet-staking-reward-curve = { version = "2.0.0-alpha.2", path = "../staking/reward-curve" } substrate-test-utils = { version = "2.0.0-alpha.2", path = "../../test-utils" } +frame-benchmarking = { version = "2.0.0-alpha.2", path = "../benchmarking" } +rand_chacha = { version = "0.2" } [features] migrate = [] @@ -46,3 +51,8 @@ std = [ "frame-system/std", "pallet-authorship/std", ] +runtime-benchmarks = [ + "rand_chacha", + "frame-benchmarking", + "frame-system/runtime-benchmarks", +] diff --git a/substrate/frame/staking/src/benchmarking.rs b/substrate/frame/staking/src/benchmarking.rs new file mode 100644 index 0000000000..300e77bbda --- /dev/null +++ b/substrate/frame/staking/src/benchmarking.rs @@ -0,0 +1,464 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Staking pallet benchmarking. + +use super::*; + +use rand_chacha::{rand_core::{RngCore, SeedableRng}, ChaChaRng}; + +use sp_runtime::traits::One; +use sp_io::hashing::blake2_256; + +use frame_system::RawOrigin; +use frame_benchmarking::{benchmarks, account}; + +use crate::Module as Staking; +use frame_system::Module as System; + +const SEED: u32 = 0; + +fn create_funded_user(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(n: u32) -> Result<(T::AccountId, T::AccountId), &'static str> { + let stash = create_funded_user::("stash", n); + let controller = create_funded_user::("controller", n); + let controller_lookup: ::Source = T::Lookup::unlookup(controller.clone()); + let reward_destination = RewardDestination::Staked; + let amount = T::Currency::minimum_balance() * 10.into(); + Staking::::bond(RawOrigin::Signed(stash.clone()).into(), controller_lookup, amount, reward_destination)?; + return Ok((stash, controller)) +} + +fn create_validators(max: u32) -> 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)?; + let validator_prefs = ValidatorPrefs { + commission: Perbill::from_percent(50), + }; + Staking::::validate(RawOrigin::Signed(controller).into(), validator_prefs)?; + let stash_lookup: ::Source = T::Lookup::unlookup(stash); + validators.push(stash_lookup); + } + Ok(validators) +} + +// This function generates v validators and n nominators who are randomly nominating up to MAX_NOMINATIONS. +pub fn create_validators_with_nominators_for_era(v: u32, n: u32) -> Result<(), &'static str> { + let mut validators: Vec<::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::(i)?; + let validator_prefs = ValidatorPrefs { + commission: Perbill::from_percent(50), + }; + Staking::::validate(RawOrigin::Signed(v_controller.clone()).into(), validator_prefs)?; + let stash_lookup: ::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::(u32::max_value() - j)?; + + // Have them randomly validate + let mut available_validators = validators.clone(); + let mut selected_validators: Vec<::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::::nominate(RawOrigin::Signed(n_controller.clone()).into(), selected_validators)?; + } + + ValidatorCount::put(v); + + Ok(()) +} + +// This function generates one validator being nominated by n nominators. +// It starts an era and creates pending payouts. +pub fn create_validator_with_nominators(n: u32, upper_bound: u32) -> Result { + let mut points_total = 0; + let mut points_individual = Vec::new(); + + MinimumValidatorCount::put(0); + + let (v_stash, v_controller) = create_stash_controller::(0)?; + let validator_prefs = ValidatorPrefs { + commission: Perbill::from_percent(50), + }; + Staking::::validate(RawOrigin::Signed(v_controller.clone()).into(), validator_prefs)?; + let stash_lookup: ::Source = T::Lookup::unlookup(v_stash.clone()); + + points_total += 10; + points_individual.push((v_stash, 10)); + + // Give the validator n nominators, but keep total users in the system the same. + for i in 0 .. upper_bound { + let (_n_stash, n_controller) = create_stash_controller::(u32::max_value() - i)?; + if i < n { + Staking::::nominate(RawOrigin::Signed(n_controller.clone()).into(), vec![stash_lookup.clone()])?; + } + } + + ValidatorCount::put(1); + + // Start a new Era + let new_validators = Staking::::new_era(SessionIndex::one()).unwrap(); + + assert!(new_validators.len() == 1); + + // Give Era Points + let reward = EraRewardPoints:: { + total: points_total, + individual: points_individual.into_iter().collect(), + }; + + let current_era = CurrentEra::get().unwrap(); + ErasRewardPoints::::insert(current_era, reward); + + // Create reward pool + let total_payout = T::Currency::minimum_balance() * 1000.into(); + >::insert(current_era, total_payout); + + Ok(v_controller) +} + +// This function generates one nominator nominating v validators. +// It starts an era and creates pending payouts. +pub fn create_nominator_with_validators(v: u32) -> Result<(T::AccountId, Vec), &'static str> { + let mut validators = Vec::new(); + let mut points_total = 0; + let mut points_individual = Vec::new(); + + MinimumValidatorCount::put(0); + + // Create v validators + let mut validator_lookups = Vec::new(); + for i in 0 .. v { + let (v_stash, v_controller) = create_stash_controller::(i)?; + let validator_prefs = ValidatorPrefs { + commission: Perbill::from_percent(50), + }; + Staking::::validate(RawOrigin::Signed(v_controller.clone()).into(), validator_prefs)?; + let stash_lookup: ::Source = T::Lookup::unlookup(v_stash.clone()); + + points_total += 10; + points_individual.push((v_stash.clone(), 10)); + validator_lookups.push(stash_lookup); + // Add to the list if it is less than the number we want the nominator to have + if validators.len() < v as usize { + validators.push(v_stash.clone()) + } + } + + // Create a nominator + let (_n_stash, n_controller) = create_stash_controller::(u32::max_value())?; + Staking::::nominate(RawOrigin::Signed(n_controller.clone()).into(), validator_lookups)?; + + ValidatorCount::put(v); + + // Start a new Era + let new_validators = Staking::::new_era(SessionIndex::one()).unwrap(); + + assert!(new_validators.len() == v as usize); + + // Give Era Points + let reward = EraRewardPoints:: { + total: points_total, + individual: points_individual.into_iter().collect(), + }; + + let current_era = CurrentEra::get().unwrap(); + ErasRewardPoints::::insert(current_era, reward); + + // Create reward pool + let total_payout = T::Currency::minimum_balance() * 1000.into(); + >::insert(current_era, total_payout); + + Ok((n_controller, validators)) +} + +benchmarks! { + _{ + // User account seed + let u in 0 .. 1000 => (); + } + + bond { + let u in ...; + let stash = create_funded_user::("stash",u); + let controller = create_funded_user::("controller", u); + let controller_lookup: ::Source = T::Lookup::unlookup(controller); + let reward_destination = RewardDestination::Staked; + let amount = T::Currency::minimum_balance() * 10.into(); + }: _(RawOrigin::Signed(stash), controller_lookup, amount, reward_destination) + + bond_extra { + let u in ...; + let (stash, _) = create_stash_controller::(u)?; + let max_additional = T::Currency::minimum_balance() * 10.into(); + }: _(RawOrigin::Signed(stash), max_additional) + + unbond { + let u in ...; + let (_, controller) = create_stash_controller::(u)?; + let amount = T::Currency::minimum_balance() * 10.into(); + }: _(RawOrigin::Signed(controller), amount) + + // Worst case scenario, everything is removed after the bonding duration + withdraw_unbonded { + let u in ...; + let (stash, controller) = create_stash_controller::(u)?; + let amount = T::Currency::minimum_balance() * 10.into(); + Staking::::unbond(RawOrigin::Signed(controller.clone()).into(), amount)?; + let current_block = System::::block_number(); + // let unbond_block = current_block + T::BondingDuration::get().into() + 10.into(); + // System::::set_block_number(unbond_block); + }: _(RawOrigin::Signed(controller)) + + validate { + let u in ...; + let (_, controller) = create_stash_controller::(u)?; + let prefs = ValidatorPrefs::default(); + }: _(RawOrigin::Signed(controller), prefs) + + // Worst case scenario, MAX_NOMINATIONS + nominate { + let n in 1 .. MAX_NOMINATIONS as u32; + let (_, controller) = create_stash_controller::(n + 1)?; + let validators = create_validators::(n)?; + }: _(RawOrigin::Signed(controller), validators) + + chill { + let u in ...; + let (_, controller) = create_stash_controller::(u)?; + }: _(RawOrigin::Signed(controller)) + + set_payee { + let u in ...; + let (_, controller) = create_stash_controller::(u)?; + }: _(RawOrigin::Signed(controller), RewardDestination::Controller) + + set_controller { + let u in ...; + let (stash, _) = create_stash_controller::(u)?; + let new_controller = create_funded_user::("new_controller", u); + let new_controller_lookup = T::Lookup::unlookup(new_controller); + }: _(RawOrigin::Signed(stash), new_controller_lookup) + + set_validator_count { + let c in 0 .. 1000; + }: _(RawOrigin::Root, c) + + force_no_eras { let i in 1 .. 1; }: _(RawOrigin::Root) + + force_new_era {let i in 1 .. 1; }: _(RawOrigin::Root) + + force_new_era_always { let i in 1 .. 1; }: _(RawOrigin::Root) + + // Worst case scenario, the list of invulnerables is very long. + set_invulnerables { + let v in 0 .. 1000; + let mut invulnerables = Vec::new(); + for i in 0 .. v { + invulnerables.push(account("invulnerable", i, SEED)); + } + }: _(RawOrigin::Root, invulnerables) + + force_unstake { + let u in ...; + let (stash, _) = create_stash_controller::(u)?; + }: _(RawOrigin::Root, stash) + + cancel_deferred_slash { + let s in 1 .. 1000; + let mut unapplied_slashes = Vec::new(); + let era = EraIndex::one(); + for _ in 0 .. 1000 { + unapplied_slashes.push(UnappliedSlash::>::default()); + } + UnappliedSlashes::::insert(era, &unapplied_slashes); + + let slash_indices: Vec = (0 .. s).collect(); + }: _(RawOrigin::Root, era, slash_indices) + + payout_validator { + let n in 1 .. MAX_NOMINATIONS as u32; + let validator = create_validator_with_nominators::(n, MAX_NOMINATIONS as u32)?; + let current_era = CurrentEra::get().unwrap(); + }: _(RawOrigin::Signed(validator), current_era) + + payout_nominator { + let v in 0 .. MAX_NOMINATIONS as u32; + let (nominator, validators) = create_nominator_with_validators::(v)?; + let current_era = CurrentEra::get().unwrap(); + let find_nominator = validators.into_iter().map(|x| (x, 0)).collect(); + }: _(RawOrigin::Signed(nominator), current_era, find_nominator) + + rebond { + let l in 1 .. 1000; + let (_, controller) = create_stash_controller::(u)?; + 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.clone(), staking_ledger); + }: _(RawOrigin::Signed(controller), (l + 100).into()) + + set_history_depth { + let e in 1 .. 100; + HistoryDepth::put(e); + CurrentEra::put(e); + for i in 0 .. e { + >::insert(i, T::AccountId::default(), Exposure::>::default()); + >::insert(i, T::AccountId::default(), Exposure::>::default()); + >::insert(i, T::AccountId::default(), ValidatorPrefs::default()); + >::insert(i, BalanceOf::::one()); + >::insert(i, EraRewardPoints::::default()); + >::insert(i, BalanceOf::::one()); + ErasStartSessionIndex::insert(i, i); + } + }: _(RawOrigin::Root, EraIndex::zero()) + + reap_stash { + let u in 1 .. 1000; + let (stash, controller) = create_stash_controller::(u)?; + T::Currency::make_free_balance_be(&stash, 0.into()); + }: _(RawOrigin::Signed(controller), stash) + + new_era { + let v in 1 .. 10; + let n in 1 .. 100; + MinimumValidatorCount::put(0); + create_validators_with_nominators_for_era::(v, n)?; + let session_index = SessionIndex::one(); + }: { + let validators = Staking::::new_era(session_index).ok_or("`new_era` failed")?; + assert!(validators.len() == v as usize); + } + + do_slash { + let l in 1 .. 1000; + let (stash, controller) = create_stash_controller::(0)?; + 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.clone(), staking_ledger.clone()); + let slash_amount = T::Currency::minimum_balance() * 10.into(); + }: { + crate::slashing::do_slash::( + &stash, + slash_amount, + &mut BalanceOf::::zero(), + &mut NegativeImbalanceOf::::zero() + ); + } +} + +#[cfg(test)] +mod tests { + use crate::*; + use crate::mock::*; + use frame_support::assert_ok; + + use crate::benchmarking::{ + create_validators_with_nominators_for_era, + create_validator_with_nominators, + create_nominator_with_validators, + }; + + #[test] + fn create_validators_with_nominators_for_era_works() { + ExtBuilder::default().stakers(false).build().execute_with(|| { + let v = 10; + let n = 100; + + create_validators_with_nominators_for_era::(v,n).unwrap(); + + let count_validators = Validators::::iter().count(); + let count_nominators = Nominators::::iter().count(); + + assert_eq!(count_validators, v as usize); + assert_eq!(count_nominators, n as usize); + }); + } + + #[test] + fn create_validator_with_nominators_works() { + ExtBuilder::default().stakers(false).build().execute_with(|| { + let n = 10; + + let validator = create_validator_with_nominators::( + n, + MAX_NOMINATIONS as u32, + ).unwrap(); + + let current_era = CurrentEra::get().unwrap(); + let controller = validator; + let ledger = Staking::ledger(&controller).unwrap(); + let stash = &ledger.stash; + + let original_free_balance = Balances::free_balance(stash); + assert_ok!(Staking::payout_validator(Origin::signed(controller), current_era)); + let new_free_balance = Balances::free_balance(stash); + + assert!(original_free_balance < new_free_balance); + }); + } + + #[test] + fn create_nominator_with_validators_works() { + ExtBuilder::default().stakers(false).build().execute_with(|| { + let v = 5; + + let (nominator, validators) = create_nominator_with_validators::(v).unwrap(); + + let current_era = CurrentEra::get().unwrap(); + let controller = nominator; + let ledger = Staking::ledger(&controller).unwrap(); + let stash = &ledger.stash; + + let find_nominator = validators.into_iter().map(|x| (x, 0)).collect(); + + let original_free_balance = Balances::free_balance(stash); + assert_ok!(Staking::payout_nominator(Origin::signed(controller), current_era, find_nominator)); + let new_free_balance = Balances::free_balance(stash); + + assert!(original_free_balance < new_free_balance); + }); + } + +} diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 517069a3f0..312e70963e 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -254,6 +254,8 @@ mod mock; #[cfg(test)] mod tests; mod slashing; +#[cfg(any(feature = "runtime-benchmarks", test))] +pub mod benchmarking; pub mod inflation; @@ -287,7 +289,7 @@ use sp_phragmen::ExtendedBalance; use frame_support::traits::MigrateAccount; const DEFAULT_MINIMUM_VALIDATOR_COUNT: u32 = 4; -const MAX_NOMINATIONS: usize = 16; +pub const MAX_NOMINATIONS: usize = 16; const MAX_UNLOCKING_CHUNKS: usize = 32; const STAKING_ID: LockIdentifier = *b"staking "; @@ -1281,6 +1283,8 @@ decl_module! { } } + // ----- Root calls. + /// The ideal number of validators. #[weight = SimpleDispatchInfo::FixedNormal(5_000)] fn set_validator_count(origin, #[compact] new: u32) { @@ -1288,8 +1292,6 @@ decl_module! { ValidatorCount::put(new); } - // ----- Root calls. - /// Force there to be no new eras indefinitely. /// /// # @@ -1357,7 +1359,7 @@ decl_module! { .or_else(ensure_root)?; ensure!(!slash_indices.is_empty(), Error::::EmptyTargets); - ensure!(Self::is_sorted_and_unique(&slash_indices), Error::::NotSortedAndUnique); + ensure!(is_sorted_and_unique(&slash_indices), Error::::NotSortedAndUnique); let mut unapplied = ::UnappliedSlashes::get(&era); let last_item = slash_indices[slash_indices.len() - 1]; @@ -1510,11 +1512,6 @@ impl Module { Self::bonded(stash).and_then(Self::ledger).map(|l| l.active).unwrap_or_default() } - /// Check that list is sorted and has no duplicates. - fn is_sorted_and_unique(list: &Vec) -> bool { - list.windows(2).all(|w| w[0] < w[1]) - } - // MUTABLES (DANGEROUS) fn do_payout_nominator(who: T::AccountId, era: EraIndex, validators: Vec<(T::AccountId, u32)>) @@ -2220,3 +2217,8 @@ impl ReportOffence } } } + +/// Check that list is sorted and has no duplicates. +fn is_sorted_and_unique(list: &[u32]) -> bool { + list.windows(2).all(|w| w[0] < w[1]) +} diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index a09bd8151f..381ddf6fa9 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -226,6 +226,7 @@ pub struct ExtBuilder { fair: bool, num_validators: Option, invulnerables: Vec, + stakers: bool, } impl Default for ExtBuilder { @@ -240,6 +241,7 @@ impl Default for ExtBuilder { fair: true, num_validators: None, invulnerables: vec![], + stakers: true, } } } @@ -285,6 +287,11 @@ impl ExtBuilder { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); SLASH_DEFER_DURATION.with(|v| *v.borrow_mut() = self.slash_defer_duration); } + + pub fn stakers(mut self, has_stakers: bool) -> Self { + self.stakers = has_stakers; + self + } pub fn build(self) -> sp_io::TestExternalities { self.set_associated_consts(); let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); @@ -320,16 +327,17 @@ impl ExtBuilder { ], }.assimilate_storage(&mut storage); - let stake_21 = if self.fair { 1000 } else { 2000 }; - let stake_31 = if self.validator_pool { balance_factor * 1000 } else { 1 }; - let status_41 = if self.validator_pool { - StakerStatus::::Validator - } else { - StakerStatus::::Idle - }; - let nominated = if self.nominate { vec![11, 21] } else { vec![] }; - let _ = GenesisConfig::{ - stakers: vec![ + let mut stakers = vec![]; + if self.stakers { + let stake_21 = if self.fair { 1000 } else { 2000 }; + let stake_31 = if self.validator_pool { balance_factor * 1000 } else { 1 }; + let status_41 = if self.validator_pool { + StakerStatus::::Validator + } else { + StakerStatus::::Idle + }; + let nominated = if self.nominate { vec![11, 21] } else { vec![] }; + stakers = vec![ // (stash, controller, staked_amount, status) (11, 10, balance_factor * 1000, StakerStatus::::Validator), (21, 20, stake_21, StakerStatus::::Validator), @@ -337,7 +345,10 @@ impl ExtBuilder { (41, 40, balance_factor * 1000, status_41), // nominator (101, 100, balance_factor * 500, StakerStatus::::Nominator(nominated)) - ], + ]; + } + let _ = GenesisConfig::{ + stakers: stakers, validator_count: self.validator_count, minimum_validator_count: self.minimum_validator_count, invulnerables: self.invulnerables, diff --git a/substrate/frame/staking/src/slashing.rs b/substrate/frame/staking/src/slashing.rs index 3c8f39501a..160c08246a 100644 --- a/substrate/frame/staking/src/slashing.rs +++ b/substrate/frame/staking/src/slashing.rs @@ -566,7 +566,7 @@ pub(crate) fn clear_stash_metadata(stash: &T::AccountId) { // apply the slash to a stash account, deducting any missing funds from the reward // payout, saturating at 0. this is mildly unfair but also an edge-case that // can only occur when overlapping locked funds have been slashed. -fn do_slash( +pub fn do_slash( stash: &T::AccountId, value: BalanceOf, reward_payout: &mut BalanceOf,