First draft of offchain phragmen weights (#6032)

* Fist draft of offchain weights

* Round of review feedback

* Update frame/staking/src/lib.rs

* Fix fuzzer

* Remove some redundant comment

* Weight refund for submit solution -- potentially revert.

* First version with custom trimming of the result.

* Update frame/staking/src/benchmarking.rs

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

* Update frame/staking/src/benchmarking.rs

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

* Apply suggestions from code review

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

* Update frame/staking/src/benchmarking.rs

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

* Update frame/staking/src/benchmarking.rs

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

* Some improvements

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

* implementation of new benchmark

* address comments

* replace test

* Update frame/staking/src/lib.rs

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

* update weight

* Fix refund

* Clean and rady for final bench

* Fix line-wdith

* Fix gitlab build

* Fix line-wdith

* Fix test macro

* Update frame/staking/src/lib.rs

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

* Update frame/staking/src/benchmarking.rs

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

* Better length check

* Update frame/staking/src/lib.rs

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

* Update final weight coefficients

* Update frame/staking/src/lib.rs

* Apply suggestions from code review

* Update frame/staking/src/testing_utils.rs

* Try and fix the line-width

* Revert "Try and fix the line-width"

This reverts commit b4e284727220085b9b3daf7682c4bbf29621da09.

* Try and fix the line-width the correct way

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

This reverts commit 04fce128e851c9584f9f0d708a5a73cae799d8c8.

Co-authored-by: Alexander Popiak <alexander.popiak@parity.io>
Co-authored-by: thiolliere <gui.thiolliere@gmail.com>
Co-authored-by: Gavin Wood <gavin@parity.io>
This commit is contained in:
Kian Paimani
2020-05-23 20:08:42 +02:00
committed by GitHub
parent 82a832bc3a
commit 0133185c81
14 changed files with 1201 additions and 854 deletions
+1 -1
View File
@@ -235,7 +235,7 @@ test-frame-staking:
- $DEPLOY_TAG
script:
- cd frame/staking/
- WASM_BUILD_NO_COLOR=1 time cargo test --release --verbose --no-default-features --features "std testing-utils"
- WASM_BUILD_NO_COLOR=1 time cargo test --release --verbose --no-default-features --features "std"
- sccache -s
test-frame-examples-compile-to-wasm:
+413 -385
View File
File diff suppressed because it is too large Load Diff
+4 -16
View File
@@ -12,24 +12,19 @@ description = "FRAME pallet staking"
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
static_assertions = "1.1.0"
serde = { version = "1.0.101", optional = true }
codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] }
sp-std = { version = "2.0.0-dev", default-features = false, path = "../../primitives/std" }
sp-phragmen = { version = "2.0.0-dev", default-features = false, path = "../../primitives/phragmen" }
sp-io ={ version = "2.0.0-dev", path = "../../primitives/io", default-features = false }
sp-io ={ version = "2.0.0-dev", default-features = false, path = "../../primitives/io" }
sp-runtime = { version = "2.0.0-dev", default-features = false, path = "../../primitives/runtime" }
sp-staking = { version = "2.0.0-dev", default-features = false, path = "../../primitives/staking" }
frame-support = { version = "2.0.0-dev", default-features = false, path = "../support" }
frame-system = { version = "2.0.0-dev", default-features = false, path = "../system" }
pallet-session = { version = "2.0.0-dev", features = ["historical"], path = "../session", default-features = false }
pallet-session = { version = "2.0.0-dev", default-features = false, features = ["historical"], path = "../session" }
pallet-authorship = { version = "2.0.0-dev", default-features = false, path = "../authorship" }
sp-application-crypto = { version = "2.0.0-dev", default-features = false, path = "../../primitives/application-crypto" }
static_assertions = "1.1.0"
# Optional imports for tesing-utils feature
pallet-indices = { version = "2.0.0-dev", optional = true, path = "../indices", default-features = false }
sp-core = { version = "2.0.0-dev", optional = true, path = "../../primitives/core", default-features = false }
rand = { version = "0.7.3", optional = true, default-features = false }
# Optional imports for benchmarking
frame-benchmarking = { version = "2.0.0-dev", default-features = false, path = "../benchmarking", optional = true }
@@ -49,12 +44,6 @@ env_logger = "0.7.1"
hex = "0.4"
[features]
testing-utils = [
"std",
"pallet-indices/std",
"sp-core/std",
"rand/std",
]
default = ["std"]
std = [
"serde",
@@ -69,9 +58,8 @@ std = [
"frame-system/std",
"pallet-authorship/std",
"sp-application-crypto/std",
"sp-core/std",
]
runtime-benchmarks = [
"rand_chacha",
"frame-benchmarking",
"rand_chacha",
]
+1 -1
View File
@@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
honggfuzz = "0.5"
codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] }
pallet-staking = { version = "2.0.0-dev", path = "..", features = ["testing-utils"] }
pallet-staking = { version = "2.0.0-dev", path = "..", features = ["runtime-benchmarks"] }
pallet-staking-reward-curve = { version = "2.0.0-dev", path = "../reward-curve" }
pallet-session = { version = "2.0.0-dev", path = "../../session" }
pallet-indices = { version = "2.0.0-dev", path = "../../indices" }
@@ -16,17 +16,18 @@
// limitations under the License.
//! Fuzzing for staking pallet.
//!
//! HFUZZ_RUN_ARGS="-n 8" cargo hfuzz run submit_solution
use honggfuzz::fuzz;
use mock::Test;
use pallet_staking::testing_utils::{
USER, get_seq_phragmen_solution, get_weak_solution, setup_chain_stakers,
set_validator_count, signed_account,
};
use pallet_staking::testing_utils::*;
use frame_support::{assert_ok, storage::StorageValue};
use frame_system::RawOrigin;
use sp_runtime::{traits::Dispatchable, DispatchError};
use sp_core::offchain::{testing::TestOffchainExt, OffchainExt};
use pallet_staking::{EraElectionStatus, ElectionStatus, Module as Staking, Call as StakingCall};
mod mock;
@@ -88,47 +89,52 @@ fn main() {
ext.execute_with(|| {
// initial setup
set_validator_count::<Test>(to_elect);
pallet_staking::testing_utils::init_active_era();
setup_chain_stakers::<Test>(
init_active_era();
assert_ok!(create_validators_with_nominators_for_era::<Test>(
num_validators,
num_nominators,
edge_per_voter,
);
<pallet_staking::EraElectionStatus<Test>>::put(pallet_staking::ElectionStatus::Open(1));
edge_per_voter as usize,
true,
None,
));
<EraElectionStatus<Test>>::put(ElectionStatus::Open(1));
assert!(<Staking<Test>>::create_stakers_snapshot().0);
let origin = RawOrigin::Signed(create_funded_user::<Test>("fuzzer", 0, 100));
println!("++ Chain setup done.");
// stuff to submit
let (winners, compact, score) = match mode {
let (winners, compact, score, size) = match mode {
Mode::InitialSubmission => {
/* No need to setup anything */
get_seq_phragmen_solution::<Test>(do_reduce)
},
Mode::StrongerSubmission => {
let (winners, compact, score) = get_weak_solution::<Test>(false);
let (winners, compact, score, size) = get_weak_solution::<Test>(false);
println!("Weak on chain score = {:?}", score);
assert_ok!(
<pallet_staking::Module<Test>>::submit_election_solution(
signed_account::<Test>(USER),
<Staking<Test>>::submit_election_solution(
origin.clone().into(),
winners,
compact,
score,
pallet_staking::testing_utils::active_era::<Test>(),
current_era::<Test>(),
size,
)
);
get_seq_phragmen_solution::<Test>(do_reduce)
},
Mode::WeakerSubmission => {
let (winners, compact, score) = get_seq_phragmen_solution::<Test>(do_reduce);
let (winners, compact, score, size) = get_seq_phragmen_solution::<Test>(do_reduce);
println!("Strong on chain score = {:?}", score);
assert_ok!(
<pallet_staking::Module<Test>>::submit_election_solution(
signed_account::<Test>(USER),
<Staking<Test>>::submit_election_solution(
origin.clone().into(),
winners,
compact,
score,
pallet_staking::testing_utils::active_era::<Test>(),
current_era::<Test>(),
size,
)
);
get_weak_solution::<Test>(false)
@@ -138,27 +144,34 @@ fn main() {
println!("++ Submission ready. Score = {:?}", score);
// must have chosen correct number of winners.
assert_eq!(winners.len() as u32, <pallet_staking::Module<Test>>::validator_count());
assert_eq!(winners.len() as u32, <Staking<Test>>::validator_count());
// final call and origin
let call = pallet_staking::Call::<Test>::submit_election_solution(
let call = StakingCall::<Test>::submit_election_solution(
winners,
compact,
score,
pallet_staking::testing_utils::active_era::<Test>(),
current_era::<Test>(),
size,
);
let caller = signed_account::<Test>(USER);
// actually submit
match mode {
Mode::WeakerSubmission => {
assert_eq!(
call.dispatch(caller.into()).unwrap_err().error,
DispatchError::Module { index: 0, error: 16, message: Some("PhragmenWeakSubmission") },
call.dispatch(origin.clone().into()).unwrap_err().error,
DispatchError::Module {
index: 0,
error: 16,
message: Some("PhragmenWeakSubmission"),
},
);
},
// NOTE: so exhaustive pattern doesn't work here.. maybe some rust issue? or due to `#[repr(u32)]`?
Mode::InitialSubmission | Mode::StrongerSubmission => assert!(call.dispatch(caller.into()).is_ok()),
// NOTE: so exhaustive pattern doesn't work here.. maybe some rust issue?
// or due to `#[repr(u32)]`?
Mode::InitialSubmission | Mode::StrongerSubmission => {
assert_ok!(call.dispatch(origin.into()));
}
};
})
});
+233 -100
View File
@@ -18,53 +18,17 @@
//! Staking pallet benchmarking.
use super::*;
use rand_chacha::{rand_core::{RngCore, SeedableRng}, ChaChaRng};
use sp_runtime::traits::{Dispatchable, One};
use sp_io::hashing::blake2_256;
use frame_system::RawOrigin;
use frame_benchmarking::{benchmarks, account};
use crate::Module as Staking;
use testing_utils::*;
use sp_runtime::{traits::{Dispatchable, One}};
use frame_system::RawOrigin;
pub use frame_benchmarking::{benchmarks, account};
const SEED: u32 = 0;
const MAX_SPANS: u32 = 100;
const MAX_VALIDATORS: u32 = 1000;
const MAX_SLASHES: u32 = 1000;
fn create_funded_user<T: Trait>(string: &'static str, n: u32) -> T::AccountId {
let user = account(string, n, SEED);
let balance = T::Currency::minimum_balance() * 100.into();
T::Currency::make_free_balance_be(&user, balance);
user
}
pub fn create_stash_controller<T: Trait>(n: u32) -> Result<(T::AccountId, T::AccountId), &'static str> {
let stash = create_funded_user::<T>("stash", n);
let controller = create_funded_user::<T>("controller", n);
let controller_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(controller.clone());
let reward_destination = RewardDestination::Staked;
let amount = T::Currency::minimum_balance() * 10.into();
Staking::<T>::bond(RawOrigin::Signed(stash.clone()).into(), controller_lookup, amount, reward_destination)?;
return Ok((stash, controller))
}
fn create_validators<T: Trait>(max: u32) -> Result<Vec<<T::Lookup as StaticLookup>::Source>, &'static str> {
let mut validators: Vec<<T::Lookup as StaticLookup>::Source> = Vec::with_capacity(max as usize);
for i in 0 .. max {
let (stash, controller) = create_stash_controller::<T>(i)?;
let validator_prefs = ValidatorPrefs {
commission: Perbill::from_percent(50),
};
Staking::<T>::validate(RawOrigin::Signed(controller).into(), validator_prefs)?;
let stash_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(stash);
validators.push(stash_lookup);
}
Ok(validators)
}
// Add slashing spans to a user account. Not relevant for actual use, only to benchmark
// read and write operations.
fn add_slashing_spans<T: Trait>(who: &T::AccountId, spans: u32) {
@@ -81,51 +45,15 @@ fn add_slashing_spans<T: Trait>(who: &T::AccountId, spans: u32) {
SlashingSpans::<T>::insert(who, slashing_spans);
}
// This function generates v validators and n nominators who are randomly nominating up to MAX_NOMINATIONS.
pub fn create_validators_with_nominators_for_era<T: Trait>(v: u32, n: u32) -> Result<(), &'static str> {
let mut validators: Vec<<T::Lookup as StaticLookup>::Source> = Vec::with_capacity(v as usize);
let mut rng = ChaChaRng::from_seed(SEED.using_encoded(blake2_256));
// Create v validators
for i in 0 .. v {
let (v_stash, v_controller) = create_stash_controller::<T>(i)?;
let validator_prefs = ValidatorPrefs {
commission: Perbill::from_percent(50),
};
Staking::<T>::validate(RawOrigin::Signed(v_controller.clone()).into(), validator_prefs)?;
let stash_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(v_stash.clone());
validators.push(stash_lookup.clone());
}
// Create n nominators
for j in 0 .. n {
let (_n_stash, n_controller) = create_stash_controller::<T>(u32::max_value() - j)?;
// Have them randomly validate
let mut available_validators = validators.clone();
let mut selected_validators: Vec<<T::Lookup as StaticLookup>::Source> = Vec::with_capacity(MAX_NOMINATIONS);
for _ in 0 .. v.min(MAX_NOMINATIONS as u32) {
let selected = rng.next_u32() as usize % available_validators.len();
let validator = available_validators.remove(selected);
selected_validators.push(validator);
}
Staking::<T>::nominate(RawOrigin::Signed(n_controller.clone()).into(), selected_validators)?;
}
ValidatorCount::put(v);
Ok(())
}
// This function generates one validator being nominated by n nominators, and returns
//the validator stash account. It also starts an era and creates pending payouts.
// This function generates one validator being nominated by n nominators, and returns the validator
// stash account. It also starts an era and creates pending payouts.
pub fn create_validator_with_nominators<T: Trait>(n: u32, upper_bound: u32) -> Result<T::AccountId, &'static str> {
let mut points_total = 0;
let mut points_individual = Vec::new();
MinimumValidatorCount::put(0);
let (v_stash, v_controller) = create_stash_controller::<T>(0)?;
let (v_stash, v_controller) = create_stash_controller::<T>(0, 100)?;
let validator_prefs = ValidatorPrefs {
commission: Perbill::from_percent(50),
};
@@ -137,7 +65,7 @@ pub fn create_validator_with_nominators<T: Trait>(n: u32, upper_bound: u32) -> R
// Give the validator n nominators, but keep total users in the system the same.
for i in 0 .. upper_bound {
let (_n_stash, n_controller) = create_stash_controller::<T>(u32::max_value() - i)?;
let (_n_stash, n_controller) = create_stash_controller::<T>(u32::max_value() - i, 100)?;
if i < n {
Staking::<T>::nominate(RawOrigin::Signed(n_controller.clone()).into(), vec![stash_lookup.clone()])?;
}
@@ -174,8 +102,8 @@ benchmarks! {
bond {
let u in ...;
let stash = create_funded_user::<T>("stash",u);
let controller = create_funded_user::<T>("controller", u);
let stash = create_funded_user::<T>("stash", u, 100);
let controller = create_funded_user::<T>("controller", u, 100);
let controller_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(controller.clone());
let reward_destination = RewardDestination::Staked;
let amount = T::Currency::minimum_balance() * 10.into();
@@ -187,7 +115,7 @@ benchmarks! {
bond_extra {
let u in ...;
let (stash, controller) = create_stash_controller::<T>(u)?;
let (stash, controller) = create_stash_controller::<T>(u, 100)?;
let max_additional = T::Currency::minimum_balance() * 10.into();
let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created before")?;
let original_bonded: BalanceOf<T> = ledger.active;
@@ -200,7 +128,7 @@ benchmarks! {
unbond {
let u in ...;
let (_, controller) = create_stash_controller::<T>(u)?;
let (_, controller) = create_stash_controller::<T>(u, 100)?;
let amount = T::Currency::minimum_balance() * 10.into();
let ledger = Ledger::<T>::get(&controller).ok_or("ledger not created before")?;
let original_bonded: BalanceOf<T> = ledger.active;
@@ -215,7 +143,7 @@ benchmarks! {
withdraw_unbonded_update {
// Slashing Spans
let s in 0 .. MAX_SPANS;
let (stash, controller) = create_stash_controller::<T>(0)?;
let (stash, controller) = create_stash_controller::<T>(0, 100)?;
add_slashing_spans::<T>(&stash, s);
let amount = T::Currency::minimum_balance() * 5.into(); // Half of total
Staking::<T>::unbond(RawOrigin::Signed(controller.clone()).into(), amount)?;
@@ -233,7 +161,7 @@ benchmarks! {
withdraw_unbonded_kill {
// Slashing Spans
let s in 0 .. MAX_SPANS;
let (stash, controller) = create_stash_controller::<T>(0)?;
let (stash, controller) = create_stash_controller::<T>(0, 100)?;
add_slashing_spans::<T>(&stash, s);
let amount = T::Currency::minimum_balance() * 10.into();
Staking::<T>::unbond(RawOrigin::Signed(controller.clone()).into(), amount)?;
@@ -247,7 +175,7 @@ benchmarks! {
validate {
let u in ...;
let (stash, controller) = create_stash_controller::<T>(u)?;
let (stash, controller) = create_stash_controller::<T>(u, 100)?;
let prefs = ValidatorPrefs::default();
}: _(RawOrigin::Signed(controller), prefs)
verify {
@@ -257,8 +185,8 @@ benchmarks! {
// Worst case scenario, MAX_NOMINATIONS
nominate {
let n in 1 .. MAX_NOMINATIONS as u32;
let (stash, controller) = create_stash_controller::<T>(n + 1)?;
let validators = create_validators::<T>(n)?;
let (stash, controller) = create_stash_controller::<T>(n + 1, 100)?;
let validators = create_validators::<T>(n, 100)?;
}: _(RawOrigin::Signed(controller), validators)
verify {
assert!(Nominators::<T>::contains_key(stash));
@@ -266,12 +194,12 @@ benchmarks! {
chill {
let u in ...;
let (_, controller) = create_stash_controller::<T>(u)?;
let (_, controller) = create_stash_controller::<T>(u, 100)?;
}: _(RawOrigin::Signed(controller))
set_payee {
let u in ...;
let (stash, controller) = create_stash_controller::<T>(u)?;
let (stash, controller) = create_stash_controller::<T>(u, 100)?;
assert_eq!(Payee::<T>::get(&stash), RewardDestination::Staked);
}: _(RawOrigin::Signed(controller), RewardDestination::Controller)
verify {
@@ -280,8 +208,8 @@ benchmarks! {
set_controller {
let u in ...;
let (stash, _) = create_stash_controller::<T>(u)?;
let new_controller = create_funded_user::<T>("new_controller", u);
let (stash, _) = create_stash_controller::<T>(u, 100)?;
let new_controller = create_funded_user::<T>("new_controller", u, 100);
let new_controller_lookup = T::Lookup::unlookup(new_controller.clone());
}: _(RawOrigin::Signed(stash), new_controller_lookup)
verify {
@@ -319,7 +247,7 @@ benchmarks! {
force_unstake {
// Slashing Spans
let s in 0 .. MAX_SPANS;
let (stash, controller) = create_stash_controller::<T>(0)?;
let (stash, controller) = create_stash_controller::<T>(0, 100)?;
add_slashing_spans::<T>(&stash, s);
}: _(RawOrigin::Root, stash, s)
verify {
@@ -356,7 +284,7 @@ benchmarks! {
rebond {
let l in 1 .. MAX_UNLOCKING_CHUNKS as u32;
let (_, controller) = create_stash_controller::<T>(u)?;
let (_, controller) = create_stash_controller::<T>(u, 100)?;
let mut staking_ledger = Ledger::<T>::get(controller.clone()).unwrap();
let unlock_chunk = UnlockChunk::<BalanceOf<T>> {
value: 1.into(),
@@ -394,7 +322,7 @@ benchmarks! {
reap_stash {
let s in 1 .. MAX_SPANS;
let (stash, controller) = create_stash_controller::<T>(0)?;
let (stash, controller) = create_stash_controller::<T>(0, 100)?;
add_slashing_spans::<T>(&stash, s);
T::Currency::make_free_balance_be(&stash, 0.into());
}: _(RawOrigin::Signed(controller), stash.clone(), s)
@@ -406,7 +334,7 @@ benchmarks! {
let v in 1 .. 10;
let n in 1 .. 100;
MinimumValidatorCount::put(0);
create_validators_with_nominators_for_era::<T>(v, n)?;
create_validators_with_nominators_for_era::<T>(v, n, MAX_NOMINATIONS, false, None)?;
let session_index = SessionIndex::one();
}: {
let validators = Staking::<T>::new_era(session_index).ok_or("`new_era` failed")?;
@@ -415,7 +343,7 @@ benchmarks! {
do_slash {
let l in 1 .. MAX_UNLOCKING_CHUNKS as u32;
let (stash, controller) = create_stash_controller::<T>(0)?;
let (stash, controller) = create_stash_controller::<T>(0, 100)?;
let mut staking_ledger = Ledger::<T>::get(controller.clone()).unwrap();
let unlock_chunk = UnlockChunk::<BalanceOf<T>> {
value: 1.into(),
@@ -443,7 +371,7 @@ benchmarks! {
let v in 1 .. 10;
let n in 1 .. 100;
MinimumValidatorCount::put(0);
create_validators_with_nominators_for_era::<T>(v, n)?;
create_validators_with_nominators_for_era::<T>(v, n, MAX_NOMINATIONS, false, None)?;
// Start a new Era
let new_validators = Staking::<T>::new_era(SessionIndex::one()).unwrap();
assert!(new_validators.len() == v as usize);
@@ -477,6 +405,198 @@ benchmarks! {
call.dispatch(RawOrigin::Signed(caller.clone()).into())?;
}
}
// This benchmark create `v` validators intent, `n` nominators intent, each nominator nominate
// MAX_NOMINATIONS in the set of the first `w` validators.
// It builds a solution with `w` winners composed of nominated validators randomly nominated,
// `a` assignment with MAX_NOMINATIONS.
submit_solution_initial {
// number of validator intent
let v in 1000 .. 2000;
// number of nominator intent
let n in 1000 .. 2000;
// number of assignments. Basically, number of active nominators.
let a in 200 .. 500;
// number of winners, also ValidatorCount
let w in 16 .. 100;
ensure!(w as usize >= MAX_NOMINATIONS, "doesn't support lower value");
let winners = create_validators_with_nominators_for_era::<T>(
v,
n,
MAX_NOMINATIONS,
false,
Some(w),
)?;
// needed for the solution to be generates.
assert!(<Staking<T>>::create_stakers_snapshot().0);
// set number of winners
ValidatorCount::put(w);
// create a assignments in total for the w winners.
let (winners, assignments) = create_assignments_for_offchain::<T>(a, winners)?;
let (
winners,
compact,
score,
size
) = offchain_election::prepare_submission::<T>(assignments, winners, false).unwrap();
// needed for the solution to be accepted
<EraElectionStatus<T>>::put(ElectionStatus::Open(T::BlockNumber::from(1u32)));
let era = <Staking<T>>::current_era().unwrap_or(0);
let caller: T::AccountId = account("caller", n, SEED);
}: {
let result = <Staking<T>>::submit_election_solution(
RawOrigin::Signed(caller.clone()).into(),
winners,
compact,
score.clone(),
era,
size,
);
assert!(result.is_ok());
}
verify {
// new solution has been accepted.
assert_eq!(<Staking<T>>::queued_score().unwrap(), score);
}
// same as submit_solution_initial but we place a very weak solution on chian first.
submit_solution_better {
// number of validator intent
let v in 1000 .. 2000;
// number of nominator intent
let n in 1000 .. 2000;
// number of assignments. Basically, number of active nominators.
let a in 200 .. 500;
// number of winners, also ValidatorCount
let w in 16 .. 100;
ensure!(w as usize >= MAX_NOMINATIONS, "doesn't support lower value");
let winners = create_validators_with_nominators_for_era::<T>(
v,
n,
MAX_NOMINATIONS,
false,
Some(w),
)?;
// needed for the solution to be generates.
assert!(<Staking<T>>::create_stakers_snapshot().0);
// set number of winners
ValidatorCount::put(w);
// create a assignments in total for the w winners.
let (winners, assignments) = create_assignments_for_offchain::<T>(a, winners)?;
let single_winner = winners[0].0.clone();
let (
winners,
compact,
score,
size
) = offchain_election::prepare_submission::<T>(assignments, winners, false).unwrap();
// needed for the solution to be accepted
<EraElectionStatus<T>>::put(ElectionStatus::Open(T::BlockNumber::from(1u32)));
let era = <Staking<T>>::current_era().unwrap_or(0);
let caller: T::AccountId = account("caller", n, SEED);
// submit a very bad solution on-chain
{
// this is needed to fool the chain to accept this solution.
ValidatorCount::put(1);
let (winners, compact, score, size) = get_single_winner_solution::<T>(single_winner)?;
assert!(
<Staking<T>>::submit_election_solution(
RawOrigin::Signed(caller.clone()).into(),
winners,
compact,
score.clone(),
era,
size,
).is_ok());
// new solution has been accepted.
assert_eq!(<Staking<T>>::queued_score().unwrap(), score);
ValidatorCount::put(w);
}
}: {
let result = <Staking<T>>::submit_election_solution(
RawOrigin::Signed(caller.clone()).into(),
winners,
compact,
score.clone(),
era,
size,
);
assert!(result.is_ok());
}
verify {
// new solution has been accepted.
assert_eq!(<Staking<T>>::queued_score().unwrap(), score);
}
// This will be early rejected based on the score.
submit_solution_weaker {
// number of validator intent
let v in 1000 .. 2000;
// number of nominator intent
let n in 1000 .. 2000;
MinimumValidatorCount::put(0);
create_validators_with_nominators_for_era::<T>(v, n, MAX_NOMINATIONS, false, None)?;
// needed for the solution to be generates.
assert!(<Staking<T>>::create_stakers_snapshot().0);
// needed for the solution to be accepted
<EraElectionStatus<T>>::put(ElectionStatus::Open(T::BlockNumber::from(1u32)));
let caller: T::AccountId = account("caller", n, SEED);
let era = <Staking<T>>::current_era().unwrap_or(0);
// submit a seq-phragmen with all the good stuff on chain
{
let (winners, compact, score, size) = get_seq_phragmen_solution::<T>(true);
assert!(
<Staking<T>>::submit_election_solution(
RawOrigin::Signed(caller.clone()).into(),
winners,
compact,
score.clone(),
era,
size,
).is_ok()
);
// new solution has been accepted.
assert_eq!(<Staking<T>>::queued_score().unwrap(), score);
}
// prepare a bad solution. This will be very early rejected.
let (winners, compact, score, size) = get_weak_solution::<T>(true);
}: {
assert!(
<Staking<T>>::submit_election_solution(
RawOrigin::Signed(caller.clone()).into(),
winners,
compact,
score.clone(),
era,
size,
).is_err()
);
}
}
#[cfg(test)]
@@ -491,7 +611,8 @@ mod tests {
let v = 10;
let n = 100;
create_validators_with_nominators_for_era::<Test>(v,n).unwrap();
create_validators_with_nominators_for_era::<Test>(v, n, MAX_NOMINATIONS, false, None)
.unwrap();
let count_validators = Validators::<Test>::iter().count();
let count_nominators = Nominators::<Test>::iter().count();
@@ -595,6 +716,18 @@ mod tests {
assert_ok!(test_benchmark_new_era::<Test>());
assert_ok!(test_benchmark_do_slash::<Test>());
assert_ok!(test_benchmark_payout_all::<Test>());
// only run one of them to same time on the CI. ignore the other two.
assert_ok!(test_benchmark_submit_solution_initial::<Test>());
});
}
#[test]
#[ignore]
fn test_benchmarks_offchain() {
ExtBuilder::default().has_stakers(false).build().execute_with(|| {
assert_ok!(test_benchmark_submit_solution_better::<Test>());
assert_ok!(test_benchmark_submit_solution_weaker::<Test>());
});
}
}
+168 -67
View File
@@ -272,7 +272,7 @@
mod mock;
#[cfg(test)]
mod tests;
#[cfg(feature = "testing-utils")]
#[cfg(any(feature = "runtime-benchmarks", test))]
pub mod testing_utils;
#[cfg(any(feature = "runtime-benchmarks", test))]
pub mod benchmarking;
@@ -293,7 +293,7 @@ use frame_support::{
decl_module, decl_event, decl_storage, ensure, decl_error, debug,
weights::{Weight, constants::{WEIGHT_PER_MICROS, WEIGHT_PER_NANOS}},
storage::IterableStorageMap,
dispatch::{IsSubType, DispatchResult, DispatchResultWithPostInfo},
dispatch::{IsSubType, DispatchResult, DispatchResultWithPostInfo, WithPostDispatchInfo},
traits::{
Currency, LockIdentifier, LockableCurrency, WithdrawReasons, OnUnbalanced, Imbalance, Get,
UnixTime, EstimateNextNewSession, EnsureOrigin,
@@ -680,6 +680,22 @@ pub enum ElectionStatus<BlockNumber> {
Open(BlockNumber),
}
/// Some indications about the size of the election. This must be submitted with the solution.
///
/// Note that these values must reflect the __total__ number, not only those that are present in the
/// solution. In short, these should be the same size as the size of the values dumped in
/// `SnapshotValidators` and `SnapshotNominators`.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default)]
pub struct ElectionSize {
/// Number of validators in the snapshot of the current election round.
#[codec(compact)]
pub validators: ValidatorIndex,
/// Number of nominators in the snapshot of the current election round.
#[codec(compact)]
pub nominators: NominatorIndex,
}
impl<BlockNumber: PartialEq> ElectionStatus<BlockNumber> {
fn is_open_at(&self, n: BlockNumber) -> bool {
*self == Self::Open(n)
@@ -743,6 +759,72 @@ impl<T: Trait> SessionInterface<<T as frame_system::Trait>::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<T: Trait>(
winners: &Vec<ValidatorIndex>,
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<T: Trait>(
winners: &Vec<ValidatorIndex>,
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::<T>(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 Trait: frame_system::Trait + SendTransactionTypes<Call<Self>> {
/// The staking balance.
type Currency: LockableCurrency<Self::AccountId, Moment=Self::BlockNumber>;
@@ -1163,6 +1245,8 @@ decl_error! {
PhragmenBogusEdge,
/// The claimed score does not match with the one computed from the data.
PhragmenBogusScore,
/// The election size is invalid.
PhragmenBogusElectionSize,
/// The call is not allowed at the given time due to restrictions of election period.
CallNotAllowed,
/// Incorrect previous history depth input provided.
@@ -2063,51 +2147,26 @@ decl_module! {
/// minimized (to ensure less variance)
///
/// # <weight>
/// E: number of edges. m: size of winner committee. n: number of nominators. d: edge degree
/// (16 for now) v: number of on-chain validator candidates.
///
/// NOTE: given a solution which is reduced, we can enable a new check the ensure `|E| < n +
/// m`. We don't do this _yet_, but our offchain worker code executes it nonetheless.
///
/// major steps (all done in `check_and_replace_solution`):
///
/// - Storage: O(1) read `ElectionStatus`.
/// - Storage: O(1) read `PhragmenScore`.
/// - Storage: O(1) read `ValidatorCount`.
/// - Storage: O(1) length read from `SnapshotValidators`.
///
/// - Storage: O(v) reads of `AccountId` to fetch `snapshot_validators`.
/// - Memory: O(m) iterations to map winner index to validator id.
/// - Storage: O(n) reads `AccountId` to fetch `snapshot_nominators`.
/// - Memory: O(n + m) reads to map index to `AccountId` for un-compact.
///
/// - Storage: O(e) accountid reads from `Nomination` to read correct nominations.
/// - Storage: O(e) calls into `slashable_balance_of_vote_weight` to convert ratio to staked.
///
/// - Memory: build_support_map. O(e).
/// - Memory: evaluate_support: O(E).
///
/// - Storage: O(e) writes to `QueuedElected`.
/// - Storage: O(1) write to `QueuedScore`
///
/// The weight of this call is 1/10th of the blocks total weight.
/// See `crate::weight` module.
/// # </weight>
#[weight = 100_000_000_000]
#[weight = weight::weight_for_submit_solution::<T>(winners, compact, size)]
pub fn submit_election_solution(
origin,
winners: Vec<ValidatorIndex>,
compact_assignments: CompactAssignments,
compact: CompactAssignments,
score: PhragmenScore,
era: EraIndex,
) {
size: ElectionSize,
) -> DispatchResultWithPostInfo {
let _who = ensure_signed(origin)?;
Self::check_and_replace_solution(
winners,
compact_assignments,
compact,
ElectionCompute::Signed,
score,
era,
)?
size,
)
}
/// Unsigned version of `submit_election_solution`.
@@ -2115,22 +2174,28 @@ decl_module! {
/// Note that this must pass the [`ValidateUnsigned`] check which only allows transactions
/// from the local node to be included. In other words, only the block author can include a
/// transaction in the block.
#[weight = 100_000_000_000]
///
/// # <weight>
/// See `crate::weight` module.
/// # </weight>
#[weight = weight::weight_for_submit_solution::<T>(winners, compact, size)]
pub fn submit_election_solution_unsigned(
origin,
winners: Vec<ValidatorIndex>,
compact_assignments: CompactAssignments,
compact: CompactAssignments,
score: PhragmenScore,
era: EraIndex,
) {
size: ElectionSize,
) -> DispatchResultWithPostInfo {
ensure_none(origin)?;
Self::check_and_replace_solution(
winners,
compact_assignments,
compact,
ElectionCompute::Unsigned,
score,
era,
)?
size,
)
// TODO: instead of returning an error, panic. This makes the entire produced block
// invalid.
// This ensures that block authors will not ever try and submit a solution which is not
@@ -2142,6 +2207,7 @@ decl_module! {
impl<T: Trait> Module<T> {
/// The total balance that can be slashed from a stash account as of right now.
pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf<T> {
// Weight note: consider making the stake accessible through stash.
Self::bonded(stash).and_then(Self::ledger).map(|l| l.active).unwrap_or_default()
}
@@ -2156,7 +2222,7 @@ impl<T: Trait> Module<T> {
///
/// This data is used to efficiently evaluate election results. returns `true` if the operation
/// is successful.
fn create_stakers_snapshot() -> (bool, Weight) {
pub fn create_stakers_snapshot() -> (bool, Weight) {
let mut consumed_weight = 0;
let mut add_db_reads_writes = |reads, writes| {
consumed_weight += T::DbWeight::get().reads_writes(reads, writes);
@@ -2518,19 +2584,24 @@ impl<T: Trait> Module<T> {
}
/// Basic and cheap checks that we perform in validate unsigned, and in the execution.
pub fn pre_dispatch_checks(score: PhragmenScore, era: EraIndex) -> Result<(), Error<T>> {
///
/// State reads: ElectionState, CurrentEr, QueuedScore.
///
/// This function does weight refund in case of errors, which is based upon the fact that it is
/// called at the very beginning of the call site's function.
pub fn pre_dispatch_checks(score: PhragmenScore, era: EraIndex) -> DispatchResultWithPostInfo {
// discard solutions that are not in-time
// check window open
ensure!(
Self::era_election_status().is_open(),
Error::<T>::PhragmenEarlySubmission,
Error::<T>::PhragmenEarlySubmission.with_weight(T::DbWeight::get().reads(1)),
);
// check current era.
if let Some(current_era) = Self::current_era() {
ensure!(
current_era == era,
Error::<T>::PhragmenEarlySubmission,
Error::<T>::PhragmenEarlySubmission.with_weight(T::DbWeight::get().reads(2)),
)
}
@@ -2538,11 +2609,11 @@ impl<T: Trait> Module<T> {
if let Some(queued_score) = Self::queued_score() {
ensure!(
is_score_better(queued_score, score),
Error::<T>::PhragmenWeakSubmission,
Error::<T>::PhragmenWeakSubmission.with_weight(T::DbWeight::get().reads(3)),
)
}
Ok(())
Ok(None.into())
}
/// Checks a given solution and if correct and improved, writes it on chain as the queued result
@@ -2553,21 +2624,46 @@ impl<T: Trait> Module<T> {
compute: ElectionCompute,
claimed_score: PhragmenScore,
era: EraIndex,
) -> Result<(), Error<T>> {
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::<T>(
&winners,
&compact_assignments,
&election_size,
);
// Check that the number of presented winners is sane. Most often we have more candidates
// that we need. Then it should be Self::validator_count(). Else it should be all the
// than we need. Then it should be `Self::validator_count()`. Else it should be all the
// candidates.
let snapshot_length = <SnapshotValidators<T>>::decode_len()
let snapshot_validators_length = <SnapshotValidators<T>>::decode_len()
.map(|l| l as u32)
.ok_or_else(|| Error::<T>::SnapshotUnavailable)?;
// size of the solution must be correct.
ensure!(
snapshot_validators_length == u32::from(election_size.validators),
Error::<T>::PhragmenBogusElectionSize,
);
// check the winner length only here and when we know the length of the snapshot validators
// length.
let desired_winners = Self::validator_count().min(snapshot_length as u32);
let desired_winners = Self::validator_count().min(snapshot_validators_length);
ensure!(winners.len() as u32 == desired_winners, Error::<T>::PhragmenBogusWinnerCount);
let snapshot_nominators_len = <SnapshotNominators<T>>::decode_len()
.map(|l| l as u32)
.ok_or_else(|| Error::<T>::SnapshotUnavailable)?;
// rest of the size of the solution must be correct.
ensure!(
snapshot_nominators_len == election_size.nominators,
Error::<T>::PhragmenBogusElectionSize,
);
// decode snapshot validators.
let snapshot_validators = Self::snapshot_validators()
.ok_or(Error::<T>::SnapshotUnavailable)?;
@@ -2581,7 +2677,7 @@ impl<T: Trait> Module<T> {
}).collect::<Result<Vec<T::AccountId>, Error<T>>>()?;
// decode the rest of the snapshot.
let snapshot_nominators = <Module<T>>::snapshot_nominators()
let snapshot_nominators = Self::snapshot_nominators()
.ok_or(Error::<T>::SnapshotUnavailable)?;
// helpers
@@ -2615,7 +2711,7 @@ impl<T: Trait> Module<T> {
// have bigger problems.
log!(error, "💸 detected an error in the staking locking and snapshot.");
// abort.
return Err(Error::<T>::PhragmenBogusNominator);
return Err(Error::<T>::PhragmenBogusNominator.into());
}
if !is_validator {
@@ -2632,14 +2728,14 @@ impl<T: Trait> Module<T> {
// each target in the provided distribution must be actually nominated by the
// nominator after the last non-zero slash.
if nomination.targets.iter().find(|&tt| tt == t).is_none() {
return Err(Error::<T>::PhragmenBogusNomination);
return Err(Error::<T>::PhragmenBogusNomination.into());
}
if <Self as Store>::SlashingSpans::get(&t).map_or(
false,
|spans| nomination.submitted_in < spans.last_nonzero_slash(),
) {
return Err(Error::<T>::PhragmenSlashedNomination);
return Err(Error::<T>::PhragmenSlashedNomination.into());
}
}
} else {
@@ -2679,8 +2775,9 @@ impl<T: Trait> Module<T> {
let exposures = Self::collect_exposure(supports);
log!(
info,
"💸 A better solution (with compute {:?}) has been validated and stored on chain.",
"💸 A better solution (with compute {:?} and score {:?}) has been validated and stored on chain.",
compute,
submitted_score,
);
// write new results.
@@ -2691,8 +2788,7 @@ impl<T: Trait> Module<T> {
});
QueuedScore::put(submitted_score);
Ok(())
Ok(Some(adjusted_weight).into())
}
/// Start a session potentially starting an era.
@@ -2996,7 +3092,7 @@ impl<T: Trait> Module<T> {
supports.into_iter().map(|(validator, support)| {
// build `struct exposure` from `support`
let mut others = Vec::new();
let mut others = Vec::with_capacity(support.voters.len());
let mut own: BalanceOf<T> = Zero::zero();
let mut total: BalanceOf<T> = Zero::zero();
support.voters
@@ -3381,12 +3477,6 @@ impl<T, Reporter, Offender, R, O> ReportOffence<Reporter, Offender, O>
}
}
impl<T: Trait> From<Error<T>> for InvalidTransaction {
fn from(e: Error<T>) -> Self {
InvalidTransaction::Custom(e.as_u8())
}
}
#[allow(deprecated)]
impl<T: Trait> frame_support::unsigned::ValidateUnsigned for Module<T> {
type Call = Call<T>;
@@ -3396,8 +3486,10 @@ impl<T: Trait> frame_support::unsigned::ValidateUnsigned for Module<T> {
_,
score,
era,
_,
) = call {
use offchain_election::DEFAULT_LONGEVITY;
use sp_runtime::DispatchError;
// discard solution not coming from the local OCW.
match source {
@@ -3408,9 +3500,18 @@ impl<T: Trait> frame_support::unsigned::ValidateUnsigned for Module<T> {
}
}
if let Err(e) = Self::pre_dispatch_checks(*score, *era) {
log!(debug, "validate unsigned pre dispatch checks failed due to {:?}.", e);
return InvalidTransaction::from(e).into();
if let Err(error_with_post_info) = Self::pre_dispatch_checks(*score, *era) {
let error = error_with_post_info.error;
let error_number = match error {
DispatchError::Module { error, ..} => error,
_ => 0,
};
log!(
debug,
"validate unsigned pre dispatch checks failed due to module error #{:?}.",
error,
);
return InvalidTransaction::Custom(error_number).into();
}
log!(debug, "validateUnsigned succeeded for a solution at era {}.", era);
@@ -20,6 +20,7 @@
use codec::Decode;
use crate::{
Call, CompactAssignments, Module, NominatorIndex, OffchainAccuracy, Trait, ValidatorIndex,
ElectionSize,
};
use frame_system::offchain::SubmitTransaction;
use sp_phragmen::{
@@ -113,7 +114,7 @@ pub(crate) fn compute_offchain_election<T: Trait>() -> Result<(), OffchainElecti
.ok_or(OffchainElectionError::ElectionFailed)?;
// process and prepare it for submission.
let (winners, compact, score) = prepare_submission::<T>(assignments, winners, true)?;
let (winners, compact, score, size) = prepare_submission::<T>(assignments, winners, true)?;
// defensive-only: current era can never be none except genesis.
let current_era = <Module<T>>::current_era().unwrap_or_default();
@@ -124,12 +125,14 @@ pub(crate) fn compute_offchain_election<T: Trait>() -> Result<(), OffchainElecti
compact,
score,
current_era,
size,
).into();
SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call)
.map_err(|_| OffchainElectionError::PoolSubmissionFailed)
}
/// Takes a phragmen result and spits out some data that can be submitted to the chain.
///
/// This does a lot of stuff; read the inline comments.
@@ -137,7 +140,12 @@ pub fn prepare_submission<T: Trait>(
assignments: Vec<Assignment<T::AccountId, OffchainAccuracy>>,
winners: Vec<(T::AccountId, ExtendedBalance)>,
do_reduce: bool,
) -> Result<(Vec<ValidatorIndex>, CompactAssignments, PhragmenScore), OffchainElectionError> where
) -> Result<(
Vec<ValidatorIndex>,
CompactAssignments,
PhragmenScore,
ElectionSize,
), OffchainElectionError> where
ExtendedBalance: From<<OffchainAccuracy as PerThing>::Inner>,
{
// make sure that the snapshot is available.
@@ -235,6 +243,12 @@ pub fn prepare_submission<T: Trait>(
}
}
// both conversions are safe; snapshots are not created if they exceed.
let size = ElectionSize {
validators: snapshot_validators.len() as ValidatorIndex,
nominators: snapshot_nominators.len() as NominatorIndex,
};
debug::native::debug!(
target: "staking",
"prepared solution after {} equalization iterations with score {:?}",
@@ -242,5 +256,5 @@ pub fn prepare_submission<T: Trait>(
score,
);
Ok((winners_indexed, compact, score))
Ok((winners_indexed, compact, score, size))
}
+182 -200
View File
@@ -15,142 +15,130 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//! Testing utils for staking. Needs the `testing-utils` feature to be enabled.
//!
//! Note that these helpers should NOT be used with the actual crate tests, but are rather designed
//! for when the module is being externally tested (i.e. fuzzing, benchmarking, e2e tests). Enabling
//! this feature in the current crate's Cargo.toml will leak all of this into a normal release
//! build. Just don't do it.
//! Testing utils for staking. Provides some common functions to setup staking state, such as
//! bonding validators, nominators, and generating different types of solutions.
use crate::*;
use codec::{Decode, Encode};
use frame_support::assert_ok;
use crate::Module as Staking;
use frame_benchmarking::{account};
use frame_system::RawOrigin;
use pallet_indices::address::Address;
use rand::Rng;
use sp_core::hashing::blake2_256;
use sp_phragmen::{
build_support_map, evaluate_support, reduce, Assignment, PhragmenScore, StakedAssignment,
};
use sp_io::hashing::blake2_256;
use rand_chacha::{rand_core::{RngCore, SeedableRng}, ChaChaRng};
use sp_phragmen::*;
const CTRL_PREFIX: u32 = 1000;
const NOMINATOR_PREFIX: u32 = 1_000_000;
const SEED: u32 = 0;
/// A dummy suer.
pub const USER: u32 = 999_999_999;
/// Address type of the `T`
pub type AddressOf<T> = Address<<T as frame_system::Trait>::AccountId, u32>;
/// Random number in the range `[a, b]`.
pub fn random(a: u32, b: u32) -> u32 {
rand::thread_rng().gen_range(a, b)
/// Grab a funded user.
pub fn create_funded_user<T: Trait>(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);
// ensure T::CurrencyToVote will work correctly.
T::Currency::issue(balance);
user
}
/// Set the desired validator count, with related storage items.
pub fn set_validator_count<T: Trait>(to_elect: u32) {
ValidatorCount::put(to_elect);
MinimumValidatorCount::put(to_elect / 2);
<EraElectionStatus<T>>::put(ElectionStatus::Closed);
}
/// Build an account with the given index.
pub fn account<T: Trait>(index: u32) -> T::AccountId {
let entropy = (b"benchmark/staking", index).using_encoded(blake2_256);
T::AccountId::decode(&mut &entropy[..]).unwrap_or_default()
}
/// Build an address given Index
pub fn address<T: Trait>(index: u32) -> AddressOf<T> {
pallet_indices::address::Address::Id(account::<T>(index))
}
/// Generate signed origin from `who`.
pub fn signed<T: Trait>(who: T::AccountId) -> T::Origin {
RawOrigin::Signed(who).into()
}
/// Generate signed origin from `index`.
pub fn signed_account<T: Trait>(index: u32) -> T::Origin {
signed::<T>(account::<T>(index))
}
/// Bond a validator.
pub fn bond_validator<T: Trait>(stash: T::AccountId, ctrl: u32, val: BalanceOf<T>)
where
T::Lookup: StaticLookup<Source = AddressOf<T>>,
/// Create a stash and controller pair.
pub fn create_stash_controller<T: Trait>(n: u32, balance_factor: u32)
-> Result<(T::AccountId, T::AccountId), &'static str>
{
let _ = T::Currency::make_free_balance_be(&stash, val);
assert_ok!(<Module<T>>::bond(
signed::<T>(stash),
address::<T>(ctrl),
val,
RewardDestination::Controller
));
assert_ok!(<Module<T>>::validate(
signed_account::<T>(ctrl),
ValidatorPrefs::default()
));
let stash = create_funded_user::<T>("stash", n, balance_factor);
let controller = create_funded_user::<T>("controller", n, balance_factor);
let controller_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(controller.clone());
let reward_destination = RewardDestination::Staked;
let amount = T::Currency::minimum_balance() * (balance_factor / 10).max(1).into();
Staking::<T>::bond(RawOrigin::Signed(stash.clone()).into(), controller_lookup, amount, reward_destination)?;
return Ok((stash, controller))
}
pub fn bond_nominator<T: Trait>(
stash: T::AccountId,
ctrl: u32,
val: BalanceOf<T>,
target: Vec<AddressOf<T>>,
) where
T::Lookup: StaticLookup<Source = AddressOf<T>>,
{
let _ = T::Currency::make_free_balance_be(&stash, val);
assert_ok!(<Module<T>>::bond(
signed::<T>(stash),
address::<T>(ctrl),
val,
RewardDestination::Controller
));
assert_ok!(<Module<T>>::nominate(signed_account::<T>(ctrl), target));
/// create `max` validators.
pub fn create_validators<T: Trait>(
max: u32,
balance_factor: u32,
) -> Result<Vec<<T::Lookup as StaticLookup>::Source>, &'static str> {
let mut validators: Vec<<T::Lookup as StaticLookup>::Source> = Vec::with_capacity(max as usize);
for i in 0 .. max {
let (stash, controller) = create_stash_controller::<T>(i, balance_factor)?;
let validator_prefs = ValidatorPrefs {
commission: Perbill::from_percent(50),
};
Staking::<T>::validate(RawOrigin::Signed(controller).into(), validator_prefs)?;
let stash_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(stash);
validators.push(stash_lookup);
}
Ok(validators)
}
/// Bond `nun_validators` validators and `num_nominator` nominators with `edge_per_voter` random
/// votes per nominator.
pub fn setup_chain_stakers<T: Trait>(num_validators: u32, num_voters: u32, edge_per_voter: u32)
where
T::Lookup: StaticLookup<Source = AddressOf<T>>,
{
(0..num_validators).for_each(|i| {
bond_validator::<T>(
account::<T>(i),
i + CTRL_PREFIX,
<BalanceOf<T>>::from(random(1, 1000)) * T::Currency::minimum_balance(),
);
});
/// This function generates validators and nominators who are randomly nominating
/// `edge_per_nominator` random validators (until `to_nominate` if provided).
///
/// Parameters:
/// - `validators`: number of bonded validators
/// - `nominators`: number of bonded nominators.
/// - `edge_per_nominator`: number of edge (vote) per nominator.
/// - `randomize_stake`: whether to randomize the stakes.
/// - `to_nominate`: if `Some(n)`, only the first `n` bonded validator are voted upon.
/// Else, all of them are considered and `edge_per_nominator` random validators are voted for.
///
/// Return the validators choosen to be nominated.
pub fn create_validators_with_nominators_for_era<T: Trait>(
validators: u32,
nominators: u32,
edge_per_nominator: usize,
randomize_stake: bool,
to_nominate: Option<u32>,
) -> Result<Vec<<T::Lookup as StaticLookup>::Source>, &'static str> {
let mut validators_stash: Vec<<T::Lookup as StaticLookup>::Source>
= Vec::with_capacity(validators as usize);
let mut rng = ChaChaRng::from_seed(SEED.using_encoded(blake2_256));
(0..num_voters).for_each(|i| {
let mut targets: Vec<AddressOf<T>> = Vec::with_capacity(edge_per_voter as usize);
let mut all_targets = (0..num_validators)
.map(|t| address::<T>(t))
.collect::<Vec<_>>();
assert!(num_validators >= edge_per_voter);
(0..edge_per_voter).for_each(|_| {
let target = all_targets.remove(random(0, all_targets.len() as u32 - 1) as usize);
targets.push(target);
});
bond_nominator::<T>(
account::<T>(i + NOMINATOR_PREFIX),
i + NOMINATOR_PREFIX + CTRL_PREFIX,
<BalanceOf<T>>::from(random(1, 1000)) * T::Currency::minimum_balance(),
targets,
);
});
// 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::<T>(i, balance_factor)?;
let validator_prefs = ValidatorPrefs {
commission: Perbill::from_percent(50),
};
Staking::<T>::validate(RawOrigin::Signed(v_controller.clone()).into(), validator_prefs)?;
let stash_lookup: <T::Lookup as StaticLookup>::Source = T::Lookup::unlookup(v_stash.clone());
validators_stash.push(stash_lookup.clone());
}
<Module<T>>::create_stakers_snapshot();
let to_nominate = to_nominate.unwrap_or(validators_stash.len() as u32) as usize;
let validator_choosen = validators_stash[0..to_nominate].to_vec();
// Create nominators
for j in 0 .. nominators {
let balance_factor = if randomize_stake { rng.next_u32() % 255 + 10 } else { 100u32 };
let (_n_stash, n_controller) = create_stash_controller::<T>(
u32::max_value() - j,
balance_factor,
)?;
// Have them randomly validate
let mut available_validators = validator_choosen.clone();
let mut selected_validators: Vec<<T::Lookup as StaticLookup>::Source> =
Vec::with_capacity(edge_per_nominator);
for _ in 0 .. validators.min(edge_per_nominator as u32) {
let selected = rng.next_u32() as usize % available_validators.len();
let validator = available_validators.remove(selected);
selected_validators.push(validator);
}
Staking::<T>::nominate(RawOrigin::Signed(n_controller.clone()).into(), selected_validators)?;
}
ValidatorCount::put(validators);
Ok(validator_choosen)
}
/// Build a _really bad_ but acceptable solution for election. This should always yield a solution
/// which has a less score than the seq-phragmen.
pub fn get_weak_solution<T: Trait>(
do_reduce: bool,
) -> (Vec<ValidatorIndex>, CompactAssignments, PhragmenScore) {
) -> (Vec<ValidatorIndex>, CompactAssignments, PhragmenScore, ElectionSize) {
let mut backing_stake_of: BTreeMap<T::AccountId, BalanceOf<T>> = BTreeMap::new();
// self stake
@@ -159,68 +147,19 @@ pub fn get_weak_solution<T: Trait>(
<Module<T>>::slashable_balance_of(&who)
});
// add nominator stuff
<Nominators<T>>::iter().for_each(|(who, nomination)| {
nomination.targets.into_iter().for_each(|v| {
*backing_stake_of.entry(v).or_insert(Zero::zero()) +=
<Module<T>>::slashable_balance_of(&who)
})
});
// elect winners
// elect winners. We chose the.. least backed ones.
let mut sorted: Vec<T::AccountId> = backing_stake_of.keys().cloned().collect();
sorted.sort_by_key(|x| backing_stake_of.get(x).unwrap());
let winners: Vec<T::AccountId> = sorted
.iter()
.rev()
.cloned()
.take(<Module<T>>::validator_count() as usize)
.collect();
let mut staked_assignments: Vec<StakedAssignment<T::AccountId>> = Vec::new();
<Nominators<T>>::iter().for_each(|(who, nomination)| {
let mut dist: Vec<(T::AccountId, ExtendedBalance)> = Vec::new();
nomination.targets.into_iter().for_each(|v| {
if winners.iter().find(|&w| *w == v).is_some() {
dist.push((v, ExtendedBalance::zero()));
}
});
if dist.len() == 0 {
return;
}
// assign real stakes. just split the stake.
let stake = <T::CurrencyToVote as Convert<BalanceOf<T>, u64>>::convert(
<Module<T>>::slashable_balance_of(&who),
) as ExtendedBalance;
let mut sum: ExtendedBalance = Zero::zero();
let dist_len = dist.len() as ExtendedBalance;
// assign main portion
// only take the first half into account. This should highly imbalance stuff, which is good.
dist.iter_mut()
.take(if dist_len > 1 {
(dist_len as usize) / 2
} else {
1
})
.for_each(|(_, w)| {
let partial = stake / dist_len;
*w = partial;
sum += partial;
});
// assign the leftover to last.
let leftover = stake - sum;
let last = dist.last_mut().unwrap();
last.1 += leftover;
staked_assignments.push(StakedAssignment {
who,
distribution: dist,
});
});
// you could at this point start adding some of the nominator's stake, but for now we don't.
// This solution must be bad.
// add self support to winners.
winners.iter().for_each(|w| {
@@ -255,10 +194,10 @@ pub fn get_weak_solution<T: Trait>(
.position(|x| x == a)
.and_then(|i| <usize as TryInto<ValidatorIndex>>::try_into(i).ok())
};
let stake_of = |who: &T::AccountId| -> ExtendedBalance {
let stake_of = |who: &T::AccountId| -> VoteWeight {
<T::CurrencyToVote as Convert<BalanceOf<T>, u64>>::convert(
<Module<T>>::slashable_balance_of(who),
) as ExtendedBalance
)
};
// convert back to ratio assignment. This takes less space.
@@ -270,13 +209,10 @@ pub fn get_weak_solution<T: Trait>(
// re-calculate score based on what the chain will decode.
let score = {
let staked: Vec<StakedAssignment<T::AccountId>> = low_accuracy_assignment
.iter()
.map(|a| {
let stake = stake_of(&a.who);
a.clone().into_staked(stake, true)
})
.collect();
let staked = assignment_ratio_to_staked::<_, OffchainAccuracy, _>(
low_accuracy_assignment.clone(),
stake_of
);
let (support_map, _) =
build_support_map::<T::AccountId>(winners.as_slice(), staked.as_slice());
@@ -304,14 +240,19 @@ pub fn get_weak_solution<T: Trait>(
})
.collect::<Vec<ValidatorIndex>>();
(winners, compact, score)
let size = ElectionSize {
validators: snapshot_validators.len() as ValidatorIndex,
nominators: snapshot_nominators.len() as NominatorIndex,
};
(winners, compact, score, size)
}
/// Create a solution for seq-phragmen. This uses the same internal function as used by the offchain
/// worker code.
pub fn get_seq_phragmen_solution<T: Trait>(
do_reduce: bool,
) -> (Vec<ValidatorIndex>, CompactAssignments, PhragmenScore) {
) -> (Vec<ValidatorIndex>, CompactAssignments, PhragmenScore, ElectionSize) {
let sp_phragmen::PhragmenResult {
winners,
assignments,
@@ -320,29 +261,40 @@ pub fn get_seq_phragmen_solution<T: Trait>(
offchain_election::prepare_submission::<T>(assignments, winners, do_reduce).unwrap()
}
/// Remove all validator, nominators, votes and exposures.
pub fn clean<T: Trait>(era: EraIndex)
where
<T as frame_system::Trait>::AccountId: codec::EncodeLike<u32>,
u32: codec::EncodeLike<T::AccountId>,
{
<Validators<T>>::iter().for_each(|(k, _)| {
let ctrl = <Module<T>>::bonded(&k).unwrap();
<Bonded<T>>::remove(&k);
<Validators<T>>::remove(&k);
<Ledger<T>>::remove(&ctrl);
<ErasStakers<T>>::remove(k, era);
});
<Nominators<T>>::iter().for_each(|(k, _)| <Nominators<T>>::remove(k));
<Ledger<T>>::remove_all();
<Bonded<T>>::remove_all();
<QueuedElected<T>>::kill();
QueuedScore::kill();
/// Returns a solution in which only one winner is elected with just a self vote.
pub fn get_single_winner_solution<T: Trait>(
winner: T::AccountId
) -> Result<(Vec<ValidatorIndex>, CompactAssignments, PhragmenScore, ElectionSize), &'static str> {
let snapshot_validators = <Module<T>>::snapshot_validators().unwrap();
let snapshot_nominators = <Module<T>>::snapshot_nominators().unwrap();
let val_index = snapshot_validators.iter().position(|x| *x == winner).ok_or("not a validator")?;
let nom_index = snapshot_nominators.iter().position(|x| *x == winner).ok_or("not a nominator")?;
let stake = <Staking<T>>::slashable_balance_of(&winner);
let stake = <T::CurrencyToVote as Convert<BalanceOf<T>, VoteWeight>>::convert(stake)
as ExtendedBalance;
let val_index = val_index as ValidatorIndex;
let nom_index = nom_index as NominatorIndex;
let winners = vec![val_index];
let compact = CompactAssignments {
votes1: vec![(nom_index, val_index)],
..Default::default()
};
let score = [stake, stake, stake * stake];
let size = ElectionSize {
validators: snapshot_validators.len() as ValidatorIndex,
nominators: snapshot_nominators.len() as NominatorIndex,
};
Ok((winners, compact, score, size))
}
/// get the active era.
pub fn active_era<T: Trait>() -> EraIndex {
<Module<T>>::active_era().unwrap().index
pub fn current_era<T: Trait>() -> EraIndex {
<Module<T>>::current_era().unwrap_or(0)
}
/// initialize the first era.
@@ -352,3 +304,33 @@ pub fn init_active_era() {
start: None,
})
}
/// Create random assignments for the given list of winners. Each assignment will have
/// MAX_NOMINATIONS edges.
pub fn create_assignments_for_offchain<T: Trait>(
num_assignments: u32,
winners: Vec<<T::Lookup as StaticLookup>::Source>,
) -> Result<
(
Vec<(T::AccountId, ExtendedBalance)>,
Vec<Assignment<T::AccountId, OffchainAccuracy>>,
),
&'static str
> {
let ratio = OffchainAccuracy::from_rational_approximation(1, MAX_NOMINATIONS);
let assignments: Vec<Assignment<T::AccountId, OffchainAccuracy>> = <Nominators<T>>::iter()
.take(num_assignments as usize)
.map(|(n, t)| Assignment {
who: n,
distribution: t.targets.iter().map(|v| (v.clone(), ratio)).collect(),
})
.collect();
ensure!(assignments.len() == num_assignments as usize, "must bench for `a` assignments");
let winners = winners.into_iter().map(|v| {
(<T::Lookup as StaticLookup>::lookup(v).unwrap(), 0)
}).collect();
Ok((winners, assignments))
}
+80 -53
View File
@@ -2753,7 +2753,10 @@ fn remove_multi_deferred() {
mod offchain_phragmen {
use crate::*;
use codec::Encode;
use frame_support::{assert_noop, assert_ok};
use frame_support::{
assert_noop, assert_ok, assert_err_with_weight,
dispatch::DispatchResultWithPostInfo,
};
use sp_runtime::transaction_validity::TransactionSource;
use mock::*;
use parking_lot::RwLock;
@@ -2808,6 +2811,29 @@ mod offchain_phragmen {
pool_state
}
fn election_size() -> ElectionSize {
ElectionSize {
validators: Staking::snapshot_validators().unwrap().len() as ValidatorIndex,
nominators: Staking::snapshot_nominators().unwrap().len() as NominatorIndex,
}
}
fn submit_solution(
origin: Origin,
winners: Vec<ValidatorIndex>,
compact: CompactAssignments,
score: PhragmenScore,
) -> DispatchResultWithPostInfo {
Staking::submit_election_solution(
origin,
winners,
compact,
score,
current_era(),
election_size(),
)
}
#[test]
fn is_current_session_final_works() {
ExtBuilder::default()
@@ -2996,12 +3022,11 @@ mod offchain_phragmen {
assert!(Staking::snapshot_validators().is_some());
let (compact, winners, score) = prepare_submission_with(true, 2, |_| {});
assert_ok!(Staking::submit_election_solution(
assert_ok!(submit_solution(
Origin::signed(10),
winners,
compact,
score,
current_era(),
));
let queued_result = Staking::queued_elected().unwrap();
@@ -3039,13 +3064,7 @@ mod offchain_phragmen {
assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12));
let (compact, winners, score) = prepare_submission_with(true, 2, |_| {});
assert_ok!(Staking::submit_election_solution(
Origin::signed(10),
winners,
compact,
score,
current_era(),
));
assert_ok!(submit_solution(Origin::signed(10), winners, compact, score));
let queued_result = Staking::queued_elected().unwrap();
assert_eq!(queued_result.compute, ElectionCompute::Signed);
@@ -3088,15 +3107,17 @@ mod offchain_phragmen {
let (compact, winners, score) = prepare_submission_with(true, 2, |_| {});
Staking::kill_stakers_snapshot();
assert_noop!(
assert_err_with_weight!(
Staking::submit_election_solution(
Origin::signed(10),
winners,
compact,
winners.clone(),
compact.clone(),
score,
current_era(),
ElectionSize::default(),
),
Error::<Test>::PhragmenEarlySubmission,
Some(<Test as frame_system::Trait>::DbWeight::get().reads(1)),
);
})
}
@@ -3115,25 +3136,24 @@ mod offchain_phragmen {
// a good solution
let (compact, winners, score) = prepare_submission_with(true, 2, |_| {});
assert_ok!(Staking::submit_election_solution(
assert_ok!(submit_solution(
Origin::signed(10),
winners,
compact,
score,
current_era(),
));
// a bad solution
let (compact, winners, score) = horrible_phragmen_with_post_processing(false);
assert_noop!(
Staking::submit_election_solution(
assert_err_with_weight!(
submit_solution(
Origin::signed(10),
winners,
compact,
winners.clone(),
compact.clone(),
score,
current_era(),
),
Error::<Test>::PhragmenWeakSubmission,
Some(<Test as frame_system::Trait>::DbWeight::get().reads(3))
);
})
}
@@ -3152,22 +3172,20 @@ mod offchain_phragmen {
// a meeeeh solution
let (compact, winners, score) = horrible_phragmen_with_post_processing(false);
assert_ok!(Staking::submit_election_solution(
assert_ok!(submit_solution(
Origin::signed(10),
winners,
compact,
score,
current_era(),
));
// a better solution
let (compact, winners, score) = prepare_submission_with(true, 2, |_| {});
assert_ok!(Staking::submit_election_solution(
assert_ok!(submit_solution(
Origin::signed(10),
winners,
compact,
score,
current_era(),
));
})
}
@@ -3268,12 +3286,11 @@ mod offchain_phragmen {
run_to_block(12);
// put a good solution on-chain
let (compact, winners, score) = prepare_submission_with(true, 2, |_| {});
assert_ok!(Staking::submit_election_solution(
assert_ok!(submit_solution(
Origin::signed(10),
winners,
compact,
score,
current_era(),
),);
// now run the offchain worker in the same chain state.
@@ -3318,6 +3335,28 @@ mod offchain_phragmen {
assert_eq!(winners.len(), 3);
assert_noop!(
submit_solution(
Origin::signed(10),
winners,
compact,
score,
),
Error::<Test>::PhragmenBogusWinnerCount,
);
})
}
#[test]
fn invalid_phragmen_result_solution_size() {
ExtBuilder::default()
.offchain_phragmen_ext()
.build()
.execute_with(|| {
run_to_block(12);
let (compact, winners, score) = prepare_submission_with(true, 2, |_| {});
assert_noop!(
Staking::submit_election_solution(
Origin::signed(10),
@@ -3325,8 +3364,9 @@ mod offchain_phragmen {
compact,
score,
current_era(),
ElectionSize::default(),
),
Error::<Test>::PhragmenBogusWinnerCount,
Error::<Test>::PhragmenBogusElectionSize,
);
})
}
@@ -3350,12 +3390,11 @@ mod offchain_phragmen {
assert_eq!(winners.len(), 3);
assert_noop!(
Staking::submit_election_solution(
submit_solution(
Origin::signed(10),
winners,
compact,
score,
current_era(),
),
Error::<Test>::PhragmenBogusWinnerCount,
);
@@ -3379,12 +3418,11 @@ mod offchain_phragmen {
assert_eq!(winners.len(), 4);
// all good. We chose 4 and it works.
assert_ok!(Staking::submit_election_solution(
assert_ok!(submit_solution(
Origin::signed(10),
winners,
compact,
score,
current_era(),
),);
})
}
@@ -3410,12 +3448,11 @@ mod offchain_phragmen {
// The error type sadly cannot be more specific now.
assert_noop!(
Staking::submit_election_solution(
submit_solution(
Origin::signed(10),
winners,
compact,
score,
current_era(),
),
Error::<Test>::PhragmenBogusCompact,
);
@@ -3443,12 +3480,11 @@ mod offchain_phragmen {
// The error type sadly cannot be more specific now.
assert_noop!(
Staking::submit_election_solution(
submit_solution(
Origin::signed(10),
winners,
compact,
score,
current_era(),
),
Error::<Test>::PhragmenBogusCompact,
);
@@ -3475,12 +3511,11 @@ mod offchain_phragmen {
let winners = vec![0, 1, 2, 4];
assert_noop!(
Staking::submit_election_solution(
submit_solution(
Origin::signed(10),
winners,
compact,
score,
current_era(),
),
Error::<Test>::PhragmenBogusWinner,
);
@@ -3511,12 +3546,11 @@ mod offchain_phragmen {
});
assert_noop!(
Staking::submit_election_solution(
submit_solution(
Origin::signed(10),
winners,
compact,
score,
current_era(),
),
Error::<Test>::PhragmenBogusEdge,
);
@@ -3547,12 +3581,11 @@ mod offchain_phragmen {
});
assert_noop!(
Staking::submit_election_solution(
submit_solution(
Origin::signed(10),
winners,
compact,
score,
current_era(),
),
Error::<Test>::PhragmenBogusSelfVote,
);
@@ -3583,12 +3616,11 @@ mod offchain_phragmen {
// This raises score issue.
assert_noop!(
Staking::submit_election_solution(
submit_solution(
Origin::signed(10),
winners,
compact,
score,
current_era(),
),
Error::<Test>::PhragmenBogusSelfVote,
);
@@ -3618,12 +3650,11 @@ mod offchain_phragmen {
}
assert_noop!(
Staking::submit_election_solution(
submit_solution(
Origin::signed(10),
winners,
compact,
score,
current_era(),
),
Error::<Test>::PhragmenBogusCompact,
);
@@ -3660,12 +3691,11 @@ mod offchain_phragmen {
});
assert_noop!(
Staking::submit_election_solution(
submit_solution(
Origin::signed(10),
winners,
compact,
score,
current_era(),
),
Error::<Test>::PhragmenBogusNomination,
);
@@ -3723,12 +3753,11 @@ mod offchain_phragmen {
});
// can be submitted.
assert_ok!(Staking::submit_election_solution(
assert_ok!(submit_solution(
Origin::signed(10),
winners,
compact,
score,
current_era(),
));
// a wrong solution.
@@ -3742,12 +3771,11 @@ mod offchain_phragmen {
// is rejected.
assert_noop!(
Staking::submit_election_solution(
submit_solution(
Origin::signed(10),
winners,
compact,
score,
current_era(),
),
Error::<Test>::PhragmenSlashedNomination,
);
@@ -3770,12 +3798,11 @@ mod offchain_phragmen {
score[0] += 1;
assert_noop!(
Staking::submit_election_solution(
submit_solution(
Origin::signed(10),
winners,
compact,
score,
current_era(),
),
Error::<Test>::PhragmenBogusScore,
);
+1
View File
@@ -218,6 +218,7 @@ macro_rules! assert_err_ignore_postinfo {
}
}
/// Assert an expression returns error with the given weight.
#[macro_export]
#[cfg(feature = "std")]
macro_rules! assert_err_with_weight {
@@ -158,6 +158,23 @@ fn struct_def(
)
}).collect::<TokenStream2>();
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::<TokenStream2>();
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::<TokenStream2>();
Ok(quote! (
/// A struct to encode a Phragmen assignment in a compact way.
#[derive(
@@ -181,6 +198,28 @@ fn struct_def(
{
const LIMIT: usize = #count;
}
impl<#voter_type, #target_type, #weight_type> #ident<#voter_type, #target_type, #weight_type> {
/// Get the length of all the assignments that this type is encoding. This is basically
/// the same as the number of assignments, or the number of voters in total.
pub fn len(&self) -> usize {
let mut all_len = 0usize;
#len_impl
all_len
}
/// Get the total count of edges.
pub fn edge_count(&self) -> usize {
let mut all_edges = 0usize;
#edge_count_impl
all_edges
}
/// Get the average edge count.
pub fn average_edge_count(&self) -> usize {
self.edge_count().checked_div(self.len()).unwrap_or(0)
}
}
))
}
@@ -672,6 +672,8 @@ mod compact {
compact,
Decode::decode(&mut &encoded[..]).unwrap(),
);
assert_eq!(compact.len(), 4);
assert_eq!(compact.edge_count(), 2 + 4);
}
fn basic_ratio_test_with<V, T>() where
@@ -747,6 +749,13 @@ mod compact {
target_index,
).unwrap();
// basically number of assignments that it is encoding.
assert_eq!(compacted.len(), assignments.len());
assert_eq!(
compacted.edge_count(),
assignments.iter().fold(0, |a, b| a + b.distribution.len()),
);
assert_eq!(
compacted,
TestCompact {
@@ -844,6 +853,11 @@ mod compact {
voter_index,
target_index,
).unwrap();
assert_eq!(compacted.len(), assignments.len());
assert_eq!(
compacted.edge_count(),
assignments.iter().fold(0, |a, b| a + b.distribution.len()),
);
assert_eq!(
compacted,
@@ -27,7 +27,12 @@ use sc_service::{Configuration, NativeExecutionDispatch};
use sp_runtime::{
traits::{Block as BlockT, Header as HeaderT, NumberFor},
};
use sp_core::{tasks, testing::KeyStore, traits::KeystoreExt};
use sp_core::{
tasks,
testing::KeyStore,
traits::KeystoreExt,
offchain::{OffchainExt, testing::TestOffchainExt},
};
use std::fmt::Debug;
impl BenchmarkCmd {
@@ -56,6 +61,8 @@ impl BenchmarkCmd {
let mut extensions = Extensions::default();
extensions.register(KeystoreExt(KeyStore::new()));
let (offchain, _) = TestOffchainExt::new();
extensions.register(OffchainExt::new(offchain));
let result = StateMachine::<_, _, NumberFor<BB>, _>::new(
&state,