mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
Enable Offchain Equalise (#5683)
* Master.into() * Remove debug stuff * Better license * Migrate away from SimpleDispatchInfo * Fix test * Revert "Migrate away from SimpleDispatchInfo" This reverts commit dbdd27fa19948f16bd17defdc01d3dd32986df11. * Move to offchain randomness * Fix tests * Fix tests more
This commit is contained in:
Generated
+21
@@ -4547,6 +4547,27 @@ dependencies = [
|
||||
"substrate-test-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-staking-fuzz"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
"honggfuzz",
|
||||
"pallet-balances",
|
||||
"pallet-indices",
|
||||
"pallet-session",
|
||||
"pallet-staking",
|
||||
"pallet-staking-reward-curve",
|
||||
"pallet-timestamp",
|
||||
"parity-scale-codec",
|
||||
"sp-core",
|
||||
"sp-io",
|
||||
"sp-phragmen",
|
||||
"sp-runtime",
|
||||
"sp-std",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pallet-staking-reward-curve"
|
||||
version = "2.0.0-dev"
|
||||
|
||||
@@ -95,6 +95,7 @@ members = [
|
||||
"frame/society",
|
||||
"frame/staking",
|
||||
"frame/staking/reward-curve",
|
||||
"frame/staking/fuzzer",
|
||||
"frame/sudo",
|
||||
"frame/support",
|
||||
"frame/support/procedural",
|
||||
|
||||
@@ -300,8 +300,9 @@ parameter_types! {
|
||||
pub const BondingDuration: pallet_staking::EraIndex = 24 * 28;
|
||||
pub const SlashDeferDuration: pallet_staking::EraIndex = 24 * 7; // 1/4 the bonding duration.
|
||||
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
|
||||
pub const ElectionLookahead: BlockNumber = 25; // 10 minutes per session => 100 block.
|
||||
pub const ElectionLookahead: BlockNumber = EPOCH_DURATION_IN_BLOCKS / 4;
|
||||
pub const MaxNominatorRewardedPerValidator: u32 = 64;
|
||||
pub const MaxIterations: u32 = 5;
|
||||
}
|
||||
|
||||
impl pallet_staking::Trait for Runtime {
|
||||
@@ -322,6 +323,7 @@ impl pallet_staking::Trait for Runtime {
|
||||
type NextNewSession = Session;
|
||||
type ElectionLookahead = ElectionLookahead;
|
||||
type Call = Call;
|
||||
type MaxIterations = MaxIterations;
|
||||
type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator;
|
||||
type UnsignedPriority = StakingUnsignedPriority;
|
||||
}
|
||||
|
||||
@@ -180,6 +180,7 @@ impl pallet_staking::Trait for Test {
|
||||
type Call = Call;
|
||||
type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator;
|
||||
type UnsignedPriority = UnsignedPriority;
|
||||
type MaxIterations = ();
|
||||
}
|
||||
|
||||
impl crate::Trait for Test {}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
|
||||
target
|
||||
corpus
|
||||
artifacts
|
||||
@@ -1,38 +0,0 @@
|
||||
[package]
|
||||
name = "pallet-staking-fuzz"
|
||||
version = "0.0.0"
|
||||
authors = ["Automatically generated"]
|
||||
publish = false
|
||||
edition = "2018"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.3"
|
||||
codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] }
|
||||
pallet-staking = { version = "2.0.0-alpha.2", path = "..", features = ["testing-utils"] }
|
||||
pallet-staking-reward-curve = { version = "2.0.0-alpha.2", path = "../reward-curve" }
|
||||
pallet-session = { version = "2.0.0-alpha.2", path = "../../session" }
|
||||
pallet-indices = { version = "2.0.0-alpha.2", path = "../../indices" }
|
||||
pallet-balances = { version = "2.0.0-alpha.2", path = "../../balances" }
|
||||
pallet-timestamp = { version = "2.0.0-alpha.2", path = "../../timestamp" }
|
||||
frame-system = { version = "2.0.0-alpha.2", path = "../../system" }
|
||||
frame-support = { version = "2.0.0-alpha.2", path = "../../support" }
|
||||
sp-std = { version = "2.0.0-alpha.2", path = "../../../primitives/std" }
|
||||
sp-io ={ version = "2.0.0-alpha.2", path = "../../../primitives/io" }
|
||||
sp-core = { version = "2.0.0-alpha.2", path = "../../../primitives/core" }
|
||||
sp-phragmen = { version = "2.0.0-alpha.2", path = "../../../primitives/phragmen" }
|
||||
sp-runtime = { version = "2.0.0-alpha.2", path = "../../../primitives/runtime" }
|
||||
rand = "0.7.3"
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
||||
|
||||
[[bin]]
|
||||
name = "submit_solution"
|
||||
path = "fuzz_targets/submit_solution.rs"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
@@ -1,130 +0,0 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Fuzzing for staking pallet.
|
||||
|
||||
#![no_main]
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
use mock::Test;
|
||||
use pallet_staking::testing_utils::{
|
||||
self, USER, get_seq_phragmen_solution, get_weak_solution, setup_chain_stakers,
|
||||
set_validator_count, signed_account,
|
||||
};
|
||||
use frame_support::assert_ok;
|
||||
use sp_runtime::{traits::Dispatchable, DispatchError};
|
||||
|
||||
mod mock;
|
||||
|
||||
#[repr(u32)]
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum Mode {
|
||||
/// Initial submission. This will be rather cheap.
|
||||
InitialSubmission,
|
||||
/// A better submission that will replace the previous ones. This is the most expensive.
|
||||
StrongerSubmission,
|
||||
/// A weak submission that will be rejected. This will be rather cheap.
|
||||
WeakerSubmission,
|
||||
}
|
||||
|
||||
pub fn new_test_ext() -> Result<sp_io::TestExternalities, std::string::String> {
|
||||
frame_system::GenesisConfig::default().build_storage::<mock::Test>().map(Into::into)
|
||||
}
|
||||
|
||||
fuzz_target!(|do_reduce: bool| {
|
||||
let ext = new_test_ext();
|
||||
let mode: Mode = unsafe { std::mem::transmute(testing_utils::random(0, 2)) };
|
||||
let num_validators = testing_utils::random(50, 500);
|
||||
let num_nominators = testing_utils::random(200, 2000);
|
||||
let edge_per_voter = testing_utils::random(1, 16);
|
||||
let to_elect = testing_utils::random(10, num_validators);
|
||||
|
||||
println!("+++ instance with params {} / {} / {} / {:?} / {}",
|
||||
num_nominators,
|
||||
num_validators,
|
||||
edge_per_voter,
|
||||
mode,
|
||||
to_elect,
|
||||
);
|
||||
|
||||
ext.unwrap_or_default().execute_with(|| {
|
||||
// initial setup
|
||||
set_validator_count::<Test>(to_elect);
|
||||
setup_chain_stakers::<Test>(
|
||||
num_validators,
|
||||
num_nominators,
|
||||
edge_per_voter,
|
||||
);
|
||||
|
||||
println!("++ Chain setup done.");
|
||||
|
||||
// stuff to submit
|
||||
let (winners, compact, score) = 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);
|
||||
assert_ok!(
|
||||
<pallet_staking::Module<Test>>::submit_election_solution(
|
||||
signed_account::<Test>(USER),
|
||||
winners,
|
||||
compact,
|
||||
score,
|
||||
)
|
||||
);
|
||||
get_seq_phragmen_solution::<Test>(do_reduce)
|
||||
},
|
||||
Mode::WeakerSubmission => {
|
||||
let (winners, compact, score) = get_seq_phragmen_solution::<Test>(do_reduce);
|
||||
assert_ok!(
|
||||
<pallet_staking::Module<Test>>::submit_election_solution(
|
||||
signed_account::<Test>(USER),
|
||||
winners,
|
||||
compact,
|
||||
score,
|
||||
)
|
||||
);
|
||||
get_weak_solution::<Test>(false)
|
||||
}
|
||||
};
|
||||
|
||||
println!("++ Submission ready.");
|
||||
|
||||
// must have chosen correct number of winners.
|
||||
assert_eq!(winners.len() as u32, <pallet_staking::Module<Test>>::validator_count());
|
||||
|
||||
// final call and origin
|
||||
let call = pallet_staking::Call::<Test>::submit_election_solution(
|
||||
winners,
|
||||
compact,
|
||||
score,
|
||||
);
|
||||
let caller = signed_account::<Test>(USER);
|
||||
|
||||
// actually submit
|
||||
match mode {
|
||||
Mode::WeakerSubmission => {
|
||||
assert_eq!(
|
||||
call.dispatch(caller.into()).unwrap_err(),
|
||||
DispatchError::Module { index: 0, error: 11, message: Some("PhragmenWeakSubmission") },
|
||||
);
|
||||
},
|
||||
_ => assert_ok!(call.dispatch(caller.into())),
|
||||
};
|
||||
})
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
hfuzz_target
|
||||
hfuzz_workspace
|
||||
+271
-166
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,34 @@
|
||||
[package]
|
||||
name = "pallet-staking-fuzz"
|
||||
version = "0.0.0"
|
||||
authors = ["Automatically generated"]
|
||||
publish = false
|
||||
edition = "2018"
|
||||
license = "GPL-3.0"
|
||||
homepage = "https://substrate.dev"
|
||||
repository = "https://github.com/paritytech/substrate/"
|
||||
description = "FRAME pallet staking fuzzing"
|
||||
|
||||
[dependencies]
|
||||
honggfuzz = "0.5"
|
||||
codec = { package = "parity-scale-codec", version = "1.3.0", default-features = false, features = ["derive"] }
|
||||
pallet-staking = { version = "2.0.0-alpha.5", path = "..", features = ["testing-utils"] }
|
||||
pallet-staking-reward-curve = { version = "2.0.0-alpha.5", path = "../reward-curve" }
|
||||
pallet-session = { version = "2.0.0-alpha.5", path = "../../session" }
|
||||
pallet-indices = { version = "2.0.0-alpha.5", path = "../../indices" }
|
||||
pallet-balances = { version = "2.0.0-alpha.5", path = "../../balances" }
|
||||
pallet-timestamp = { version = "2.0.0-alpha.5", path = "../../timestamp" }
|
||||
frame-system = { version = "2.0.0-alpha.5", path = "../../system" }
|
||||
frame-support = { version = "2.0.0-alpha.5", path = "../../support" }
|
||||
sp-std = { version = "2.0.0-alpha.5", path = "../../../primitives/std" }
|
||||
sp-io ={ version = "2.0.0-alpha.5", path = "../../../primitives/io" }
|
||||
sp-core = { version = "2.0.0-alpha.5", path = "../../../primitives/core" }
|
||||
sp-phragmen = { version = "2.0.0-alpha.5", path = "../../../primitives/phragmen" }
|
||||
sp-runtime = { version = "2.0.0-alpha.5", path = "../../../primitives/runtime" }
|
||||
|
||||
[[bin]]
|
||||
name = "submit_solution"
|
||||
path = "src/submit_solution.rs"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
+19
-13
@@ -24,11 +24,11 @@ type AccountIndex = u32;
|
||||
type BlockNumber = u64;
|
||||
type Balance = u64;
|
||||
|
||||
type System = frame_system::Module<Test>;
|
||||
type Balances = pallet_balances::Module<Test>;
|
||||
type Staking = pallet_staking::Module<Test>;
|
||||
type Indices = pallet_indices::Module<Test>;
|
||||
type Session = pallet_session::Module<Test>;
|
||||
pub type System = frame_system::Module<Test>;
|
||||
pub type Balances = pallet_balances::Module<Test>;
|
||||
pub type Staking = pallet_staking::Module<Test>;
|
||||
pub type Indices = pallet_indices::Module<Test>;
|
||||
pub type Session = pallet_session::Module<Test>;
|
||||
|
||||
impl_outer_origin! {
|
||||
pub enum Origin for Test where system = frame_system {}
|
||||
@@ -57,6 +57,9 @@ pub struct Test;
|
||||
|
||||
impl frame_system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type DbWeight = ();
|
||||
type BlockExecutionWeight = ();
|
||||
type ExtrinsicBaseWeight = ();
|
||||
type Index = AccountIndex;
|
||||
type BlockNumber = BlockNumber;
|
||||
type Call = Call;
|
||||
@@ -150,18 +153,21 @@ pallet_staking_reward_curve::build! {
|
||||
parameter_types! {
|
||||
pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS;
|
||||
pub const MaxNominatorRewardedPerValidator: u32 = 64;
|
||||
pub const MaxIterations: u32 = 20;
|
||||
}
|
||||
|
||||
pub type Extrinsic = sp_runtime::testing::TestXt<Call, ()>;
|
||||
type SubmitTransaction = frame_system::offchain::TransactionSubmitter<
|
||||
sp_runtime::testing::UintAuthorityId,
|
||||
Test,
|
||||
Extrinsic,
|
||||
>;
|
||||
|
||||
impl<C> frame_system::offchain::SendTransactionTypes<C> for Test where
|
||||
Call: From<C>,
|
||||
{
|
||||
type OverarchingCall = Call;
|
||||
type Extrinsic = Extrinsic;
|
||||
}
|
||||
|
||||
impl pallet_staking::Trait for Test {
|
||||
type Currency = Balances;
|
||||
type Time = pallet_timestamp::Module<Self>;
|
||||
type UnixTime = pallet_timestamp::Module<Self>;
|
||||
type CurrencyToVote = CurrencyToVoteHandler;
|
||||
type RewardRemainder = ();
|
||||
type Event = ();
|
||||
@@ -176,7 +182,7 @@ impl pallet_staking::Trait for Test {
|
||||
type NextNewSession = Session;
|
||||
type ElectionLookahead = ();
|
||||
type Call = Call;
|
||||
type SubmitTransaction = SubmitTransaction;
|
||||
type KeyType = sp_runtime::testing::UintAuthorityId;
|
||||
type MaxIterations = MaxIterations;
|
||||
type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator;
|
||||
type UnsignedPriority = ();
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// Copyright 2020 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Fuzzing for staking pallet.
|
||||
|
||||
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 frame_support::assert_ok;
|
||||
use sp_runtime::{traits::Dispatchable, DispatchError};
|
||||
|
||||
mod mock;
|
||||
|
||||
#[repr(u32)]
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Mode {
|
||||
/// Initial submission. This will be rather cheap.
|
||||
InitialSubmission,
|
||||
/// A better submission that will replace the previous ones. This is the most expensive.
|
||||
StrongerSubmission,
|
||||
/// A weak submission that will be rejected. This will be rather cheap.
|
||||
WeakerSubmission,
|
||||
}
|
||||
|
||||
pub fn new_test_ext() -> Result<sp_io::TestExternalities, std::string::String> {
|
||||
frame_system::GenesisConfig::default().build_storage::<mock::Test>().map(Into::into)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let to_range = |x: u32, a: u32, b: u32| {
|
||||
let collapsed = x % b;
|
||||
if collapsed >= a {
|
||||
collapsed
|
||||
} else {
|
||||
collapsed + a
|
||||
}
|
||||
};
|
||||
loop {
|
||||
fuzz!(|data: (u32, u32, u32, u32, u32)| {
|
||||
let (mut num_validators, mut num_nominators, mut edge_per_voter, mut to_elect, mode_u32) = data;
|
||||
let ext = new_test_ext();
|
||||
let mode: Mode = unsafe { std::mem::transmute(mode_u32) };
|
||||
num_validators = to_range(num_validators, 50, 1000);
|
||||
num_nominators = to_range(num_nominators, 50, 2000);
|
||||
edge_per_voter = to_range(edge_per_voter, 1, 16);
|
||||
to_elect = to_range(to_elect, 20, num_validators);
|
||||
let do_reduce = true;
|
||||
|
||||
println!("+++ instance with params {} / {} / {} / {:?}({}) / {}",
|
||||
num_nominators,
|
||||
num_validators,
|
||||
edge_per_voter,
|
||||
mode,
|
||||
mode_u32,
|
||||
to_elect,
|
||||
);
|
||||
|
||||
ext.unwrap_or_default().execute_with(|| {
|
||||
// initial setup
|
||||
set_validator_count::<Test>(to_elect);
|
||||
pallet_staking::testing_utils::init_active_era();
|
||||
setup_chain_stakers::<Test>(
|
||||
num_validators,
|
||||
num_nominators,
|
||||
edge_per_voter,
|
||||
);
|
||||
|
||||
println!("++ Chain setup done.");
|
||||
|
||||
// stuff to submit
|
||||
let (winners, compact, score) = 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);
|
||||
println!("Weak on chain score = {:?}", score);
|
||||
assert_ok!(
|
||||
<pallet_staking::Module<Test>>::submit_election_solution(
|
||||
signed_account::<Test>(USER),
|
||||
winners,
|
||||
compact,
|
||||
score,
|
||||
pallet_staking::testing_utils::active_era::<Test>(),
|
||||
)
|
||||
);
|
||||
get_seq_phragmen_solution::<Test>(do_reduce)
|
||||
},
|
||||
Mode::WeakerSubmission => {
|
||||
let (winners, compact, score) = 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),
|
||||
winners,
|
||||
compact,
|
||||
score,
|
||||
pallet_staking::testing_utils::active_era::<Test>(),
|
||||
)
|
||||
);
|
||||
get_weak_solution::<Test>(false)
|
||||
}
|
||||
};
|
||||
|
||||
println!("++ Submission ready. Score = {:?}", score);
|
||||
|
||||
// must have chosen correct number of winners.
|
||||
assert_eq!(winners.len() as u32, <pallet_staking::Module<Test>>::validator_count());
|
||||
|
||||
// final call and origin
|
||||
let call = pallet_staking::Call::<Test>::submit_election_solution(
|
||||
winners,
|
||||
compact,
|
||||
score,
|
||||
pallet_staking::testing_utils::active_era::<Test>(),
|
||||
);
|
||||
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: 15, 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()),
|
||||
};
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -370,7 +370,7 @@ generate_compact_solution_type!(pub GenericCompactAssignments, 16);
|
||||
#[derive(Encode, Decode, RuntimeDebug)]
|
||||
pub struct ActiveEraInfo {
|
||||
/// Index of era.
|
||||
index: EraIndex,
|
||||
pub index: EraIndex,
|
||||
/// Moment of start expresed as millisecond from `$UNIX_EPOCH`.
|
||||
///
|
||||
/// Start can be none if start hasn't been set for the era yet,
|
||||
@@ -803,6 +803,10 @@ pub trait Trait: frame_system::Trait + SendTransactionTypes<Call<Self>> {
|
||||
/// The overarching call type.
|
||||
type Call: Dispatchable + From<Call<Self>> + IsSubType<Module<Self>, Self> + Clone;
|
||||
|
||||
/// Maximum number of equalise iterations to run in the offchain submission. If set to 0,
|
||||
/// equalize will not be executed at all.
|
||||
type MaxIterations: Get<u32>;
|
||||
|
||||
/// The maximum number of nominator rewarded for each validator.
|
||||
///
|
||||
/// For each validator only the `$MaxNominatorRewardedPerValidator` biggest stakers can claim
|
||||
|
||||
@@ -64,6 +64,7 @@ thread_local! {
|
||||
static SLASH_DEFER_DURATION: RefCell<EraIndex> = RefCell::new(0);
|
||||
static ELECTION_LOOKAHEAD: RefCell<BlockNumber> = RefCell::new(0);
|
||||
static PERIOD: RefCell<BlockNumber> = RefCell::new(1);
|
||||
static MAX_ITERATIONS: RefCell<u32> = RefCell::new(0);
|
||||
}
|
||||
|
||||
/// Another session handler struct to test on_disabled.
|
||||
@@ -143,6 +144,13 @@ impl Get<EraIndex> for SlashDeferDuration {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MaxIterations;
|
||||
impl Get<u32> for MaxIterations {
|
||||
fn get() -> u32 {
|
||||
MAX_ITERATIONS.with(|v| *v.borrow())
|
||||
}
|
||||
}
|
||||
|
||||
impl_outer_origin! {
|
||||
pub enum Origin for Test where system = frame_system {}
|
||||
}
|
||||
@@ -310,6 +318,7 @@ impl Trait for Test {
|
||||
type NextNewSession = Session;
|
||||
type ElectionLookahead = ElectionLookahead;
|
||||
type Call = Call;
|
||||
type MaxIterations = MaxIterations;
|
||||
type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator;
|
||||
type UnsignedPriority = UnsignedPriority;
|
||||
}
|
||||
@@ -337,6 +346,7 @@ pub struct ExtBuilder {
|
||||
num_validators: Option<u32>,
|
||||
invulnerables: Vec<AccountId>,
|
||||
has_stakers: bool,
|
||||
max_offchain_iterations: u32,
|
||||
}
|
||||
|
||||
impl Default for ExtBuilder {
|
||||
@@ -355,6 +365,7 @@ impl Default for ExtBuilder {
|
||||
num_validators: None,
|
||||
invulnerables: vec![],
|
||||
has_stakers: true,
|
||||
max_offchain_iterations: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -412,6 +423,10 @@ impl ExtBuilder {
|
||||
self.has_stakers = has;
|
||||
self
|
||||
}
|
||||
pub fn max_offchain_iterations(mut self, iterations: u32) -> Self {
|
||||
self.max_offchain_iterations = iterations;
|
||||
self
|
||||
}
|
||||
pub fn offchain_phragmen_ext(self) -> Self {
|
||||
self.session_per_era(4)
|
||||
.session_length(5)
|
||||
@@ -423,6 +438,7 @@ impl ExtBuilder {
|
||||
SESSION_PER_ERA.with(|v| *v.borrow_mut() = self.session_per_era);
|
||||
ELECTION_LOOKAHEAD.with(|v| *v.borrow_mut() = self.election_lookahead);
|
||||
PERIOD.with(|v| *v.borrow_mut() = self.session_length);
|
||||
MAX_ITERATIONS.with(|v| *v.borrow_mut() = self.max_offchain_iterations);
|
||||
}
|
||||
pub fn build(self) -> sp_io::TestExternalities {
|
||||
let _ = env_logger::try_init();
|
||||
|
||||
@@ -16,17 +16,18 @@
|
||||
|
||||
//! Helpers for offchain worker election.
|
||||
|
||||
use codec::Decode;
|
||||
use crate::{
|
||||
Call, CompactAssignments, Module, NominatorIndex, OffchainAccuracy, Trait, ValidatorIndex,
|
||||
};
|
||||
use frame_system::offchain::SubmitTransaction;
|
||||
use sp_phragmen::{
|
||||
build_support_map, evaluate_support, reduce, Assignment, ExtendedBalance, PhragmenResult,
|
||||
PhragmenScore,
|
||||
PhragmenScore, equalize,
|
||||
};
|
||||
use sp_runtime::offchain::storage::StorageValueRef;
|
||||
use sp_runtime::PerThing;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_runtime::{PerThing, RuntimeDebug, traits::{TrailingZeroInput, Zero}};
|
||||
use frame_support::{debug, traits::Get};
|
||||
use sp_std::{convert::TryInto, prelude::*};
|
||||
|
||||
/// Error types related to the offchain election machinery.
|
||||
@@ -159,10 +160,7 @@ pub fn prepare_submission<T: Trait>(
|
||||
};
|
||||
|
||||
// Clean winners.
|
||||
let winners = winners
|
||||
.into_iter()
|
||||
.map(|(w, _)| w)
|
||||
.collect::<Vec<T::AccountId>>();
|
||||
let winners = sp_phragmen::to_without_backing(winners);
|
||||
|
||||
// convert into absolute value and to obtain the reduced version.
|
||||
let mut staked = sp_phragmen::assignment_ratio_to_staked(
|
||||
@@ -170,10 +168,33 @@ pub fn prepare_submission<T: Trait>(
|
||||
<Module<T>>::slashable_balance_of_vote_weight,
|
||||
);
|
||||
|
||||
// reduce
|
||||
if do_reduce {
|
||||
reduce(&mut staked);
|
||||
}
|
||||
|
||||
let (mut support_map, _) = build_support_map::<T::AccountId>(&winners, &staked);
|
||||
|
||||
// equalize a random number of times.
|
||||
let iterations_executed = match T::MaxIterations::get() {
|
||||
0 => {
|
||||
// Don't run equalize at all
|
||||
0
|
||||
}
|
||||
iterations @ _ => {
|
||||
let seed = sp_io::offchain::random_seed();
|
||||
let iterations = <u32>::decode(&mut TrailingZeroInput::new(seed.as_ref()))
|
||||
.expect("input is padded with zeroes; qed") % iterations.saturating_add(1);
|
||||
equalize(
|
||||
&mut staked,
|
||||
&mut support_map,
|
||||
Zero::zero(),
|
||||
iterations as usize,
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// Convert back to ratio assignment. This takes less space.
|
||||
let low_accuracy_assignment = sp_phragmen::assignment_staked_to_ratio(staked);
|
||||
|
||||
@@ -215,5 +236,12 @@ pub fn prepare_submission<T: Trait>(
|
||||
}
|
||||
}
|
||||
|
||||
debug::native::debug!(
|
||||
target: "staking",
|
||||
"prepared solution after {} equalization iterations with score {:?}",
|
||||
iterations_executed,
|
||||
score,
|
||||
);
|
||||
|
||||
Ok((winners_indexed, compact, score))
|
||||
}
|
||||
|
||||
@@ -153,13 +153,13 @@ pub fn get_weak_solution<T: Trait>(
|
||||
let mut backing_stake_of: BTreeMap<T::AccountId, BalanceOf<T>> = BTreeMap::new();
|
||||
|
||||
// self stake
|
||||
<Validators<T>>::enumerate().for_each(|(who, _p)| {
|
||||
<Validators<T>>::iter().for_each(|(who, _p)| {
|
||||
*backing_stake_of.entry(who.clone()).or_insert(Zero::zero()) +=
|
||||
<Module<T>>::slashable_balance_of(&who)
|
||||
});
|
||||
|
||||
// add nominator stuff
|
||||
<Nominators<T>>::enumerate().for_each(|(who, nomination)| {
|
||||
<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)
|
||||
@@ -176,7 +176,7 @@ pub fn get_weak_solution<T: Trait>(
|
||||
.collect();
|
||||
|
||||
let mut staked_assignments: Vec<StakedAssignment<T::AccountId>> = Vec::new();
|
||||
<Nominators<T>>::enumerate().for_each(|(who, nomination)| {
|
||||
<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() {
|
||||
@@ -325,16 +325,29 @@ pub fn clean<T: Trait>(era: EraIndex)
|
||||
<T as frame_system::Trait>::AccountId: codec::EncodeLike<u32>,
|
||||
u32: codec::EncodeLike<T::AccountId>,
|
||||
{
|
||||
<Validators<T>>::enumerate().for_each(|(k, _)| {
|
||||
<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>>::enumerate().for_each(|(k, _)| <Nominators<T>>::remove(k));
|
||||
<Nominators<T>>::iter().for_each(|(k, _)| <Nominators<T>>::remove(k));
|
||||
<Ledger<T>>::remove_all();
|
||||
<Bonded<T>>::remove_all();
|
||||
<QueuedElected<T>>::kill();
|
||||
QueuedScore::kill();
|
||||
}
|
||||
|
||||
/// get the active era.
|
||||
pub fn active_era<T: Trait>() -> EraIndex {
|
||||
<Module<T>>::active_era().unwrap().index
|
||||
}
|
||||
|
||||
/// initialize the first era.
|
||||
pub fn init_active_era() {
|
||||
ActiveEra::put(ActiveEraInfo {
|
||||
index: 1,
|
||||
start: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2901,8 +2901,8 @@ mod offchain_phragmen {
|
||||
|
||||
#[test]
|
||||
fn signed_result_can_be_submitted() {
|
||||
// should check that we have a new validator set normally,
|
||||
// event says that it comes from offchain.
|
||||
// should check that we have a new validator set normally, event says that it comes from
|
||||
// offchain.
|
||||
ExtBuilder::default()
|
||||
.offchain_phragmen_ext()
|
||||
.build()
|
||||
@@ -2989,8 +2989,8 @@ mod offchain_phragmen {
|
||||
|
||||
#[test]
|
||||
fn early_solution_submission_is_rejected() {
|
||||
// should check that we have a new validator set normally,
|
||||
// event says that it comes from offchain.
|
||||
// should check that we have a new validator set normally, event says that it comes from
|
||||
// offchain.
|
||||
ExtBuilder::default()
|
||||
.offchain_phragmen_ext()
|
||||
.build()
|
||||
@@ -3119,7 +3119,7 @@ mod offchain_phragmen {
|
||||
&inner,
|
||||
),
|
||||
TransactionValidity::Ok(ValidTransaction {
|
||||
priority: (1 << 20) + 1125, // the proposed slot stake.
|
||||
priority: UnsignedPriority::get() + 1125, // the proposed slot stake.
|
||||
requires: vec![],
|
||||
provides: vec![("StakingOffchain", current_era()).encode()],
|
||||
longevity: 3,
|
||||
@@ -3129,6 +3129,50 @@ mod offchain_phragmen {
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn offchain_worker_runs_with_equalise() {
|
||||
// Offchain worker equalises based on the number provided by randomness. See the difference
|
||||
// in the priority, which comes from the computed score.
|
||||
let mut ext = ExtBuilder::default()
|
||||
.offchain_phragmen_ext()
|
||||
.validator_count(2)
|
||||
.max_offchain_iterations(2)
|
||||
.build();
|
||||
let state = offchainify(&mut ext);
|
||||
ext.execute_with(|| {
|
||||
run_to_block(12);
|
||||
|
||||
// local key 11 is in the elected set.
|
||||
assert_eq_uvec!(Session::validators(), vec![11, 21]);
|
||||
assert_eq!(state.read().transactions.len(), 0);
|
||||
Staking::offchain_worker(12);
|
||||
assert_eq!(state.read().transactions.len(), 1);
|
||||
|
||||
let encoded = state.read().transactions[0].clone();
|
||||
let extrinsic: Extrinsic = Decode::decode(&mut &*encoded).unwrap();
|
||||
|
||||
let call = extrinsic.call;
|
||||
let inner = match call {
|
||||
mock::Call::Staking(inner) => inner,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
<Staking as sp_runtime::traits::ValidateUnsigned>::validate_unsigned(
|
||||
TransactionSource::Local,
|
||||
&inner,
|
||||
),
|
||||
TransactionValidity::Ok(ValidTransaction {
|
||||
// the proposed slot stake, with equalize.
|
||||
priority: UnsignedPriority::get() + 1250,
|
||||
requires: vec![],
|
||||
provides: vec![("StakingOffchain", active_era()).encode()],
|
||||
longevity: 3,
|
||||
propagate: false,
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mediocre_submission_from_authority_is_early_rejected() {
|
||||
let mut ext = ExtBuilder::default()
|
||||
|
||||
@@ -24,7 +24,7 @@ wasmi = { version = "0.6.2", optional = true }
|
||||
hash-db = { version = "0.15.2", default-features = false }
|
||||
hash256-std-hasher = { version = "0.15.2", default-features = false }
|
||||
base58 = { version = "0.1.0", optional = true }
|
||||
rand = { version = "0.7.2", optional = true }
|
||||
rand = { version = "0.7.3", optional = true, features = ["small_rng"] }
|
||||
substrate-bip39 = { version = "0.4.1", optional = true }
|
||||
tiny-bip39 = { version = "0.7", optional = true }
|
||||
regex = { version = "1.3.1", optional = true }
|
||||
|
||||
@@ -165,7 +165,12 @@ impl offchain::Externalities for TestOffchainExt {
|
||||
}
|
||||
|
||||
fn random_seed(&mut self) -> [u8; 32] {
|
||||
unimplemented!("not needed in tests so far")
|
||||
use rand::{SeedableRng, RngCore};
|
||||
use rand::rngs::SmallRng;
|
||||
let mut seed = [0u8; 32];
|
||||
let mut small_rng = SmallRng::from_entropy();
|
||||
small_rng.fill_bytes(&mut seed);
|
||||
seed
|
||||
}
|
||||
|
||||
fn local_storage_set(&mut self, kind: StorageKind, key: &[u8], value: &[u8]) {
|
||||
|
||||
@@ -158,7 +158,7 @@ pub struct PhragmenResult<AccountId, T: PerThing> {
|
||||
#[derive(RuntimeDebug, Clone, Default)]
|
||||
#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))]
|
||||
pub struct Assignment<AccountId, T: PerThing> {
|
||||
/// Voter's identifier
|
||||
/// Voter's identifier.
|
||||
pub who: AccountId,
|
||||
/// The distribution of the voter's stake.
|
||||
pub distribution: Vec<(AccountId, T)>,
|
||||
|
||||
Reference in New Issue
Block a user