mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 07:01:05 +00:00
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:
@@ -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:
|
||||
|
||||
Generated
+413
-385
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
};
|
||||
})
|
||||
});
|
||||
|
||||
@@ -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>());
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user