Offchain Phragmén BREAKING. (#4517)

* Initial skeleton for offchain phragmen

* Basic compact encoding decoding for results

* add compact files

* Bring back Self::ensure_storage_upgraded();

* Make staking use compact stuff.

* First seemingly working version of reduce, full of todos

* Everything phragmen related works again.

* Signing made easier, still issues.

* Signing from offchain compile fine 😎

* make compact work with staked asssignment

* Evaluation basics are in place.

* Move reduce into crate. Document stuff

* move reduce into no_std

* Add files

* Remove other std deps. Runtime compiles

* Seemingly it is al stable; cycle implemented but not integrated.

* Add fuzzing code.

* Cleanup reduce a bit more.

* a metric ton of tests for staking; wip 🔨

* Implement a lot more of the tests.

* wip getting the unsigned stuff to work

* A bit gleanup for unsigned debug

* Clean and finalize compact code.

* Document reduce.

* Still problems with signing

* We officaly duct taped the transaction submission stuff. 🤓

* Deadlock with keys again

* Runtime builds

* Unsigned test works 🙌

* Some cleanups

* Make all the tests compile and stuff

* Minor cleanup

* fix more merge stuff

* Most tests work again.

* a very nasty bug in reduce

* Fix all integrations

* Fix more todos

* Revamp everything and everything

* Remove bogus test

* Some review grumbles.

* Some fixes

* Fix doc test

* loop for submission

* Fix cli, keyring etc.

* some cleanup

* Fix staking tests again

* fix per-things; bring patches from benchmarking

* better score prediction

* Add fuzzer, more patches.

* Some fixes

* More docs

* Remove unused generics

* Remove max-nominator footgun

* Better fuzzer

* Disable it 

* Bump.

* Another round of self-review

* Refactor a lot

* More major fixes in perThing

* Add new fuzz file

* Update lock

* fix fuzzing code.

* Fix nominator retain test

* Add slashing check

* Update frame/staking/src/tests.rs

Co-Authored-By: Joshy Orndorff <JoshOrndorff@users.noreply.github.com>

* Some formatting nits

* Review comments.

* Fix cargo file

* Almost all tests work again

* Update frame/staking/src/tests.rs

Co-Authored-By: thiolliere <gui.thiolliere@gmail.com>

* Fix review comments

* More review stuff

* Some nits

* Fix new staking / session / babe relation

* Update primitives/phragmen/src/lib.rs

Co-Authored-By: thiolliere <gui.thiolliere@gmail.com>

* Update primitives/phragmen/src/lib.rs

Co-Authored-By: thiolliere <gui.thiolliere@gmail.com>

* Update primitives/phragmen/compact/src/lib.rs

Co-Authored-By: thiolliere <gui.thiolliere@gmail.com>

* Some doc updates to slashing

* Fix derive

* Remove imports

* Remove unimplemented tests

* nits

* Remove dbg

* Better fuzzing params

* Remove unused pref map

* Deferred Slashing/Offence for offchain Phragmen  (#5151)

* Some boilerplate

* Add test

* One more test

* Review comments

* Fix build

* review comments

* fix more

* fix build

* Some cleanups and self-reviews

* More minor self reviews

* Final nits

* Some merge fixes.

* opt comment

* Fix build

* Fix build again.

* Update frame/staking/fuzz/fuzz_targets/submit_solution.rs

Co-Authored-By: Gavin Wood <gavin@parity.io>

* Update frame/staking/src/slashing.rs

Co-Authored-By: Gavin Wood <gavin@parity.io>

* Update frame/staking/src/offchain_election.rs

Co-Authored-By: Gavin Wood <gavin@parity.io>

* Fix review comments

* fix test

* === 🔑 Revamp without staking key.

* final round of changes.

* Fix cargo-deny

* Update frame/staking/src/lib.rs

Co-Authored-By: Gavin Wood <gavin@parity.io>

Co-authored-by: Joshy Orndorff <JoshOrndorff@users.noreply.github.com>
Co-authored-by: thiolliere <gui.thiolliere@gmail.com>
Co-authored-by: Gavin Wood <gavin@parity.io>
This commit is contained in:
Kian Paimani
2020-03-26 15:37:40 +01:00
committed by GitHub
parent 2a67e6c437
commit 970c5f94f2
64 changed files with 11953 additions and 892 deletions
+453 -91
View File
@@ -17,57 +17,74 @@
//! Test utilities
use std::{collections::{HashSet, HashMap}, cell::RefCell};
use sp_runtime::{Perbill, KeyTypeId};
use sp_runtime::Perbill;
use sp_runtime::curve::PiecewiseLinear;
use sp_runtime::traits::{IdentityLookup, Convert, OpaqueKeys, SaturatedConversion};
use sp_runtime::testing::{Header, UintAuthorityId};
use sp_runtime::traits::{IdentityLookup, Convert, SaturatedConversion, Zero};
use sp_runtime::testing::{Header, UintAuthorityId, TestXt};
use sp_staking::{SessionIndex, offence::{OffenceDetails, OnOffenceHandler}};
use sp_core::{H256, crypto::key_types};
use sp_io;
use sp_core::H256;
use frame_support::{
assert_ok, impl_outer_origin, parameter_types, StorageValue, StorageMap,
StorageDoubleMap, IterableStorageMap,
traits::{Currency, Get, FindAuthor, OnFinalize, OnInitialize}, weights::Weight,
assert_ok, impl_outer_origin, parameter_types, impl_outer_dispatch, impl_outer_event,
StorageValue, StorageMap, StorageDoubleMap, IterableStorageMap,
traits::{Currency, Get, FindAuthor, OnFinalize, OnInitialize},
weights::Weight,
};
use frame_system::offchain::TransactionSubmitter;
use sp_io;
use sp_phragmen::{
build_support_map, evaluate_support, reduce, ExtendedBalance, StakedAssignment, PhragmenScore,
};
use crate::{
EraIndex, GenesisConfig, Module, Trait, StakerStatus, ValidatorPrefs, RewardDestination,
Nominators, inflation, SessionInterface, Exposure, ErasStakers, ErasRewardPoints
Nominators, inflation, SessionInterface, Exposure, ErasStakers, ErasRewardPoints,
CompactAssignments, ValidatorIndex, NominatorIndex, Validators, OffchainAccuracy,
};
/// The AccountId alias in this test module.
pub type AccountId = u64;
pub type BlockNumber = u64;
pub type Balance = u64;
pub(crate) type AccountId = u64;
pub(crate) type AccountIndex = u64;
pub(crate) type BlockNumber = u64;
pub(crate) type Balance = u64;
/// Simple structure that exposes how u64 currency can be represented as... u64.
pub struct CurrencyToVoteHandler;
impl Convert<u64, u64> for CurrencyToVoteHandler {
fn convert(x: u64) -> u64 { x }
fn convert(x: u64) -> u64 {
x
}
}
impl Convert<u128, u64> for CurrencyToVoteHandler {
fn convert(x: u128) -> u64 { x.saturated_into() }
fn convert(x: u128) -> u64 {
x.saturated_into()
}
}
thread_local! {
static SESSION: RefCell<(Vec<AccountId>, HashSet<AccountId>)> = RefCell::new(Default::default());
static SESSION_PER_ERA: RefCell<SessionIndex> = RefCell::new(3);
static EXISTENTIAL_DEPOSIT: RefCell<u64> = RefCell::new(0);
static SLASH_DEFER_DURATION: RefCell<EraIndex> = RefCell::new(0);
static ELECTION_LOOKAHEAD: RefCell<BlockNumber> = RefCell::new(0);
static PERIOD: RefCell<BlockNumber> = RefCell::new(1);
}
pub struct TestSessionHandler;
impl pallet_session::SessionHandler<AccountId> for TestSessionHandler {
const KEY_TYPE_IDS: &'static [KeyTypeId] = &[key_types::DUMMY];
/// Another session handler struct to test on_disabled.
pub struct OtherSessionHandler;
impl pallet_session::OneSessionHandler<AccountId> for OtherSessionHandler {
type Key = UintAuthorityId;
fn on_genesis_session<Ks: OpaqueKeys>(_validators: &[(AccountId, Ks)]) {}
fn on_genesis_session<'a, I: 'a>(_: I)
where I: Iterator<Item=(&'a AccountId, Self::Key)>, AccountId: 'a {}
fn on_new_session<Ks: OpaqueKeys>(
_changed: bool,
validators: &[(AccountId, Ks)],
_queued_validators: &[(AccountId, Ks)],
) {
SESSION.with(|x|
*x.borrow_mut() = (validators.iter().map(|x| x.0.clone()).collect(), HashSet::new())
);
fn on_new_session<'a, I: 'a>(_: bool, validators: I, _: I,)
where I: Iterator<Item=(&'a AccountId, Self::Key)>, AccountId: 'a
{
SESSION.with(|x| {
*x.borrow_mut() = (
validators.map(|x| x.0.clone()).collect(),
HashSet::new(),
)
});
}
fn on_disabled(validator_index: usize) {
@@ -79,6 +96,10 @@ impl pallet_session::SessionHandler<AccountId> for TestSessionHandler {
}
}
impl sp_runtime::BoundToRuntimeAppPublic for OtherSessionHandler {
type Public = UintAuthorityId;
}
pub fn is_disabled(controller: AccountId) -> bool {
let stash = Staking::ledger(&controller).unwrap().stash;
SESSION.with(|d| d.borrow().1.contains(&stash))
@@ -91,6 +112,32 @@ impl Get<u64> for ExistentialDeposit {
}
}
pub struct SessionsPerEra;
impl Get<SessionIndex> for SessionsPerEra {
fn get() -> SessionIndex {
SESSION_PER_ERA.with(|v| *v.borrow())
}
}
impl Get<BlockNumber> for SessionsPerEra {
fn get() -> BlockNumber {
SESSION_PER_ERA.with(|v| *v.borrow() as BlockNumber)
}
}
pub struct ElectionLookahead;
impl Get<BlockNumber> for ElectionLookahead {
fn get() -> BlockNumber {
ELECTION_LOOKAHEAD.with(|v| *v.borrow())
}
}
pub struct Period;
impl Get<BlockNumber> for Period {
fn get() -> BlockNumber {
PERIOD.with(|v| *v.borrow())
}
}
pub struct SlashDeferDuration;
impl Get<EraIndex> for SlashDeferDuration {
fn get() -> EraIndex {
@@ -98,23 +145,47 @@ impl Get<EraIndex> for SlashDeferDuration {
}
}
impl_outer_origin!{
impl_outer_origin! {
pub enum Origin for Test where system = frame_system {}
}
impl_outer_dispatch! {
pub enum Call for Test where origin: Origin {
staking::Staking,
}
}
mod staking {
// Re-export needed for `impl_outer_event!`.
pub use super::super::*;
}
use frame_system as system;
use pallet_balances as balances;
use pallet_session as session;
impl_outer_event! {
pub enum MetaEvent for Test {
system<T>,
balances<T>,
session,
staking<T>,
}
}
/// Author of block is always 11
pub struct Author11;
impl FindAuthor<u64> for Author11 {
fn find_author<'a, I>(_digests: I) -> Option<u64>
where I: 'a + IntoIterator<Item=(frame_support::ConsensusEngineId, &'a [u8])>
where I: 'a + IntoIterator<Item = (frame_support::ConsensusEngineId, &'a [u8])>,
{
Some(11)
}
}
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
#[derive(Clone, PartialEq, Eq, Debug)]
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct Test;
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const MaximumBlockWeight: Weight = 1024;
@@ -123,15 +194,15 @@ parameter_types! {
}
impl frame_system::Trait for Test {
type Origin = Origin;
type Index = u64;
type Index = AccountIndex;
type BlockNumber = BlockNumber;
type Call = ();
type Call = Call;
type Hash = H256;
type Hashing = ::sp_runtime::traits::BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = ();
type Event = MetaEvent;
type BlockHashCount = BlockHashCount;
type MaximumBlockWeight = MaximumBlockWeight;
type AvailableBlockRatio = AvailableBlockRatio;
@@ -144,26 +215,31 @@ impl frame_system::Trait for Test {
}
impl pallet_balances::Trait for Test {
type Balance = Balance;
type Event = MetaEvent;
type DustRemoval = ();
type Event = ();
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
}
parameter_types! {
pub const Period: BlockNumber = 1;
pub const Offset: BlockNumber = 0;
pub const UncleGenerations: u64 = 0;
pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(25);
}
sp_runtime::impl_opaque_keys! {
pub struct SessionKeys {
pub other: OtherSessionHandler,
}
}
impl pallet_session::Trait for Test {
type Event = ();
type SessionManager = pallet_session::historical::NoteHistoricalRoot<Test, Staking>;
type Keys = SessionKeys;
type ShouldEndSession = pallet_session::PeriodicSessions<Period, Offset>;
type SessionHandler = (OtherSessionHandler,);
type Event = MetaEvent;
type ValidatorId = AccountId;
type ValidatorIdOf = crate::StashOf<Test>;
type ShouldEndSession = pallet_session::PeriodicSessions<Period, Offset>;
type SessionManager = pallet_session::historical::NoteHistoricalRoot<Test, Staking>;
type SessionHandler = TestSessionHandler;
type Keys = UintAuthorityId;
type DisabledValidatorsThreshold = DisabledValidatorsThreshold;
type NextSessionRotation = pallet_session::PeriodicSessions<Period, Offset>;
}
impl pallet_session::historical::Trait for Test {
@@ -195,17 +271,17 @@ pallet_staking_reward_curve::build! {
);
}
parameter_types! {
pub const SessionsPerEra: SessionIndex = 3;
pub const BondingDuration: EraIndex = 3;
pub const RewardCurve: &'static PiecewiseLinear<'static> = &I_NPOS;
pub const MaxNominatorRewardedPerValidator: u32 = 64;
}
impl Trait for Test {
type Currency = Balances;
type UnixTime = Timestamp;
type CurrencyToVote = CurrencyToVoteHandler;
type RewardRemainder = ();
type Event = ();
type Event = MetaEvent;
type Slash = ();
type Reward = ();
type SessionsPerEra = SessionsPerEra;
@@ -214,11 +290,21 @@ impl Trait for Test {
type BondingDuration = BondingDuration;
type SessionInterface = Self;
type RewardCurve = RewardCurve;
type NextNewSession = Session;
type ElectionLookahead = ElectionLookahead;
type Call = Call;
type SubmitTransaction = SubmitTransaction;
type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator;
}
pub type Extrinsic = TestXt<Call, ()>;
type SubmitTransaction = TransactionSubmitter<(), Test, Extrinsic>;
pub struct ExtBuilder {
existential_deposit: u64,
session_length: BlockNumber,
election_lookahead: BlockNumber,
session_per_era: SessionIndex,
existential_deposit: Balance,
validator_pool: bool,
nominate: bool,
validator_count: u32,
@@ -226,13 +312,16 @@ pub struct ExtBuilder {
slash_defer_duration: EraIndex,
fair: bool,
num_validators: Option<u32>,
invulnerables: Vec<u64>,
stakers: bool,
invulnerables: Vec<AccountId>,
has_stakers: bool,
}
impl Default for ExtBuilder {
fn default() -> Self {
Self {
session_length: 1,
election_lookahead: 0,
session_per_era: 3,
existential_deposit: 1,
validator_pool: false,
nominate: true,
@@ -242,7 +331,7 @@ impl Default for ExtBuilder {
fair: true,
num_validators: None,
invulnerables: vec![],
stakers: true,
has_stakers: true,
}
}
}
@@ -284,18 +373,40 @@ impl ExtBuilder {
self.invulnerables = invulnerables;
self
}
pub fn set_associated_consts(&self) {
EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
SLASH_DEFER_DURATION.with(|v| *v.borrow_mut() = self.slash_defer_duration);
}
pub fn stakers(mut self, has_stakers: bool) -> Self {
self.stakers = has_stakers;
pub fn session_per_era(mut self, length: SessionIndex) -> Self {
self.session_per_era = length;
self
}
pub fn election_lookahead(mut self, look: BlockNumber) -> Self {
self.election_lookahead = look;
self
}
pub fn session_length(mut self, length: BlockNumber) -> Self {
self.session_length = length;
self
}
pub fn has_stakers(mut self, has: bool) -> Self {
self.has_stakers = has;
self
}
pub fn offchain_phragmen_ext(self) -> Self {
self.session_per_era(4)
.session_length(5)
.election_lookahead(3)
}
pub fn set_associated_constants(&self) {
EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit);
SLASH_DEFER_DURATION.with(|v| *v.borrow_mut() = self.slash_defer_duration);
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);
}
pub fn build(self) -> sp_io::TestExternalities {
self.set_associated_consts();
let mut storage = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
let _ = env_logger::try_init();
self.set_associated_constants();
let mut storage = frame_system::GenesisConfig::default()
.build_storage::<Test>()
.unwrap();
let balance_factor = if self.existential_deposit > 1 {
256
} else {
@@ -307,7 +418,7 @@ impl ExtBuilder {
.map(|x| ((x + 1) * 10 + 1) as u64)
.collect::<Vec<_>>();
let _ = pallet_balances::GenesisConfig::<Test>{
let _ = pallet_balances::GenesisConfig::<Test> {
balances: vec![
(1, 10 * balance_factor),
(2, 20 * balance_factor),
@@ -329,7 +440,7 @@ impl ExtBuilder {
}.assimilate_storage(&mut storage);
let mut stakers = vec![];
if self.stakers {
if self.has_stakers {
let stake_21 = if self.fair { 1000 } else { 2000 };
let stake_31 = if self.validator_pool { balance_factor * 1000 } else { 1 };
let status_41 = if self.validator_pool {
@@ -355,18 +466,21 @@ impl ExtBuilder {
invulnerables: self.invulnerables,
slash_reward_fraction: Perbill::from_percent(10),
..Default::default()
}.assimilate_storage(&mut storage);
}
.assimilate_storage(&mut storage);
let _ = pallet_session::GenesisConfig::<Test> {
keys: validators.iter().map(|x| (*x, *x, UintAuthorityId(*x))).collect(),
keys: validators.iter().map(|x| (
*x,
*x,
SessionKeys { other: UintAuthorityId(*x) }
)).collect(),
}.assimilate_storage(&mut storage);
let mut ext = sp_io::TestExternalities::from(storage);
ext.execute_with(|| {
let validators = Session::validators();
SESSION.with(|x|
*x.borrow_mut() = (validators.clone(), HashSet::new())
);
SESSION.with(|x| *x.borrow_mut() = (validators.clone(), HashSet::new()));
});
ext
}
@@ -378,6 +492,10 @@ pub type Session = pallet_session::Module<Test>;
pub type Timestamp = pallet_timestamp::Module<Test>;
pub type Staking = Module<Test>;
pub fn active_era() -> EraIndex {
Staking::active_era().unwrap().index
}
pub fn check_exposure_all(era: EraIndex) {
ErasStakers::<Test>::iter_prefix(era).for_each(check_exposure)
}
@@ -390,8 +508,9 @@ pub fn check_nominator_all(era: EraIndex) {
/// Check for each selected validator: expo.total = Sum(expo.other) + expo.own
pub fn check_exposure(expo: Exposure<AccountId, Balance>) {
assert_eq!(
expo.total as u128, expo.own as u128 + expo.others.iter().map(|e| e.value as u128).sum::<u128>(),
"wrong total exposure {:?}", expo,
expo.total as u128,
expo.own as u128 + expo.others.iter().map(|e| e.value as u128).sum::<u128>(),
"wrong total exposure",
);
}
@@ -400,17 +519,18 @@ pub fn check_exposure(expo: Exposure<AccountId, Balance>) {
pub fn check_nominator_exposure(era: EraIndex, stash: AccountId) {
assert_is_stash(stash);
let mut sum = 0;
ErasStakers::<Test>::iter_prefix(era)
.for_each(|exposure| {
exposure.others.iter()
.filter(|i| i.who == stash)
.for_each(|i| sum += i.value)
});
Session::validators()
.iter()
.map(|v| Staking::eras_stakers(era, v))
.for_each(|e| e.others.iter().filter(|i| i.who == stash).for_each(|i| sum += i.value));
let nominator_stake = Staking::slashable_balance_of(&stash);
// a nominator cannot over-spend.
assert!(
nominator_stake >= sum,
"failed: Nominator({}) stake({}) >= sum divided({})", stash, nominator_stake, sum,
"failed: Nominator({}) stake({}) >= sum divided({})",
stash,
nominator_stake,
sum,
);
}
@@ -426,20 +546,41 @@ pub fn assert_ledger_consistent(stash: AccountId) {
assert_eq!(real_total, ledger.total);
}
pub fn bond_validator(acc: u64, val: u64) {
// a = controller
// a + 1 = stash
let _ = Balances::make_free_balance_be(&(acc + 1), val);
assert_ok!(Staking::bond(Origin::signed(acc + 1), acc, val, RewardDestination::Controller));
assert_ok!(Staking::validate(Origin::signed(acc), ValidatorPrefs::default()));
pub fn bond_validator(stash: u64, ctrl: u64, val: u64) {
let _ = Balances::make_free_balance_be(&stash, val);
assert_ok!(Staking::bond(
Origin::signed(stash),
ctrl,
val,
RewardDestination::Controller,
));
assert_ok!(Staking::validate(
Origin::signed(ctrl),
ValidatorPrefs::default()
));
}
pub fn bond_nominator(acc: u64, val: u64, target: Vec<u64>) {
// a = controller
// a + 1 = stash
let _ = Balances::make_free_balance_be(&(acc + 1), val);
assert_ok!(Staking::bond(Origin::signed(acc + 1), acc, val, RewardDestination::Controller));
assert_ok!(Staking::nominate(Origin::signed(acc), target));
pub fn bond_nominator(stash: u64, ctrl: u64, val: u64, target: Vec<u64>) {
let _ = Balances::make_free_balance_be(&stash, val);
assert_ok!(Staking::bond(
Origin::signed(stash),
ctrl,
val,
RewardDestination::Controller,
));
assert_ok!(Staking::nominate(Origin::signed(ctrl), target));
}
pub fn run_to_block(n: BlockNumber) {
Staking::on_finalize(System::block_number());
for b in System::block_number() + 1..=n {
System::set_block_number(b);
Session::on_initialize(b);
Staking::on_initialize(b);
if b != n {
Staking::on_finalize(System::block_number());
}
}
}
pub fn advance_session() {
@@ -448,19 +589,21 @@ pub fn advance_session() {
}
pub fn start_session(session_index: SessionIndex) {
assert_eq!(<Period as Get<BlockNumber>>::get(), 1, "start_session can only be used with session length 1.");
for i in Session::current_index()..session_index {
Staking::on_finalize(System::block_number());
System::set_block_number((i + 1).into());
Timestamp::set_timestamp(System::block_number() * 1000);
Session::on_initialize(System::block_number());
Staking::on_initialize(System::block_number());
}
assert_eq!(Session::current_index(), session_index);
}
pub fn start_era(era_index: EraIndex) {
start_session((era_index * 3).into());
assert_eq!(Staking::active_era().unwrap().index, era_index);
start_session((era_index * <SessionsPerEra as Get<u32>>::get()).into());
assert_eq!(Staking::current_era().unwrap(), era_index);
}
pub fn current_total_payout_for_duration(duration: u64) -> u64 {
@@ -473,33 +616,45 @@ pub fn current_total_payout_for_duration(duration: u64) -> u64 {
}
pub fn reward_all_elected() {
let rewards = <Test as Trait>::SessionInterface::validators().into_iter()
let rewards = <Test as Trait>::SessionInterface::validators()
.into_iter()
.map(|v| (v, 1));
<Module<Test>>::reward_by_ids(rewards)
}
pub fn validator_controllers() -> Vec<AccountId> {
Session::validators().into_iter().map(|s| Staking::bonded(&s).expect("no controller for validator")).collect()
Session::validators()
.into_iter()
.map(|s| Staking::bonded(&s).expect("no controller for validator"))
.collect()
}
pub fn on_offence_in_era(
offenders: &[OffenceDetails<AccountId, pallet_session::historical::IdentificationTuple<Test>>],
offenders: &[OffenceDetails<
AccountId,
pallet_session::historical::IdentificationTuple<Test>,
>],
slash_fraction: &[Perbill],
era: EraIndex,
) {
let bonded_eras = crate::BondedEras::get();
for &(bonded_era, start_session) in bonded_eras.iter() {
if bonded_era == era {
Staking::on_offence(offenders, slash_fraction, start_session);
return
let _ = Staking::on_offence(offenders, slash_fraction, start_session).unwrap();
return;
} else if bonded_era > era {
break
break;
}
}
if Staking::active_era().unwrap().index == era {
Staking::on_offence(offenders, slash_fraction, Staking::eras_start_session_index(era).unwrap());
let _ =
Staking::on_offence(
offenders,
slash_fraction,
Staking::eras_start_session_index(era).unwrap()
).unwrap();
} else {
panic!("cannot slash in era {}", era);
}
@@ -513,6 +668,193 @@ pub fn on_offence_now(
on_offence_in_era(offenders, slash_fraction, now)
}
// winners will be chosen by simply their unweighted total backing stake. Nominator stake is
// distributed evenly.
pub fn horrible_phragmen_with_post_processing(
do_reduce: bool,
) -> (CompactAssignments, Vec<ValidatorIndex>, PhragmenScore) {
use std::collections::BTreeMap;
let mut backing_stake_of: BTreeMap<AccountId, Balance> = BTreeMap::new();
// self stake
<Validators<Test>>::iter().for_each(|(who, _p)| {
*backing_stake_of.entry(who).or_insert(Zero::zero()) += Staking::slashable_balance_of(&who)
});
// add nominator stuff
<Nominators<Test>>::iter().for_each(|(who, nomination)| {
nomination.targets.iter().for_each(|v| {
*backing_stake_of.entry(*v).or_insert(Zero::zero()) +=
Staking::slashable_balance_of(&who)
})
});
// elect winners
let mut sorted: Vec<AccountId> = backing_stake_of.keys().cloned().collect();
sorted.sort_by_key(|x| backing_stake_of.get(x).unwrap());
let winners: Vec<AccountId> = sorted
.iter()
.cloned()
.take(Staking::validator_count() as usize)
.collect();
// create assignments
let mut staked_assignment: Vec<StakedAssignment<AccountId>> = Vec::new();
<Nominators<Test>>::iter().for_each(|(who, nomination)| {
let mut dist: Vec<(AccountId, ExtendedBalance)> = Vec::new();
nomination.targets.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 = Staking::slashable_balance_of(&who) as ExtendedBalance;
let mut sum: ExtendedBalance = Zero::zero();
let dist_len = dist.len();
{
dist.iter_mut().for_each(|(_, w)| {
let partial = stake / (dist_len as ExtendedBalance);
*w = partial;
sum += partial;
});
}
// assign the leftover to last.
{
let leftover = stake - sum;
let last = dist.last_mut().unwrap();
last.1 += leftover;
}
staked_assignment.push(StakedAssignment {
who,
distribution: dist,
});
});
// Ensure that this result is worse than seq-phragmen. Otherwise, it should not have been used
// for testing.
let score = {
let (_, _, better_score) = prepare_submission_with(true, |_| {});
let support = build_support_map::<AccountId>(&winners, &staked_assignment).0;
let score = evaluate_support(&support);
assert!(sp_phragmen::is_score_better(score, better_score));
score
};
if do_reduce {
reduce(&mut staked_assignment);
}
let snapshot_validators = Staking::snapshot_validators().unwrap();
let snapshot_nominators = Staking::snapshot_nominators().unwrap();
let nominator_index = |a: &AccountId| -> Option<NominatorIndex> {
snapshot_nominators.iter().position(|x| x == a).map(|i| i as NominatorIndex)
};
let validator_index = |a: &AccountId| -> Option<ValidatorIndex> {
snapshot_validators.iter().position(|x| x == a).map(|i| i as ValidatorIndex)
};
// convert back to ratio assignment. This takes less space.
let assignments_reduced =
sp_phragmen::assignment_staked_to_ratio::<AccountId, OffchainAccuracy>(staked_assignment);
let compact =
CompactAssignments::from_assignment(assignments_reduced, nominator_index, validator_index)
.unwrap();
// winner ids to index
let winners = winners.into_iter().map(|w| validator_index(&w).unwrap()).collect::<Vec<_>>();
(compact, winners, score)
}
// Note: this should always logically reproduce [`offchain_election::prepare_submission`], yet we
// cannot do it since we want to have `tweak` injected into the process.
pub fn prepare_submission_with(
do_reduce: bool,
tweak: impl FnOnce(&mut Vec<StakedAssignment<AccountId>>),
) -> (CompactAssignments, Vec<ValidatorIndex>, PhragmenScore) {
// run phragmen on the default stuff.
let sp_phragmen::PhragmenResult {
winners,
assignments,
} = Staking::do_phragmen::<OffchainAccuracy>().unwrap();
let winners = winners.into_iter().map(|(w, _)| w).collect::<Vec<AccountId>>();
let stake_of = |who: &AccountId| -> ExtendedBalance {
<CurrencyToVoteHandler as Convert<Balance, u64>>::convert(
Staking::slashable_balance_of(&who)
) as ExtendedBalance
};
let mut staked = sp_phragmen::assignment_ratio_to_staked(assignments, stake_of);
// apply custom tweaks. awesome for testing.
tweak(&mut staked);
if do_reduce {
reduce(&mut staked);
}
// convert back to ratio assignment. This takes less space.
let snapshot_validators = Staking::snapshot_validators().expect("snapshot not created.");
let snapshot_nominators = Staking::snapshot_nominators().expect("snapshot not created.");
let nominator_index = |a: &AccountId| -> Option<NominatorIndex> {
snapshot_nominators
.iter()
.position(|x| x == a)
.map_or_else(
|| { println!("unable to find nominator index for {:?}", a); None },
|i| Some(i as NominatorIndex),
)
};
let validator_index = |a: &AccountId| -> Option<ValidatorIndex> {
snapshot_validators
.iter()
.position(|x| x == a)
.map_or_else(
|| { println!("unable to find validator index for {:?}", a); None },
|i| Some(i as ValidatorIndex),
)
};
let assignments_reduced = sp_phragmen::assignment_staked_to_ratio(staked);
// re-compute score by converting, yet again, into staked type
let score = {
let staked = sp_phragmen::assignment_ratio_to_staked(
assignments_reduced.clone(),
Staking::slashable_balance_of_extended,
);
let (support_map, _) = build_support_map::<AccountId>(
winners.as_slice(),
staked.as_slice(),
);
evaluate_support::<AccountId>(&support_map)
};
let compact =
CompactAssignments::from_assignment(assignments_reduced, nominator_index, validator_index)
.map_err(|e| { println!("error in compact: {:?}", e); e })
.expect("Failed to create compact");
// winner ids to index
let winners = winners.into_iter().map(|w| validator_index(&w).unwrap()).collect::<Vec<_>>();
(compact, winners, score)
}
/// Make all validator and nominator request their payment
pub fn make_all_reward_payment(era: EraIndex) {
let validators_with_reward = ErasRewardPoints::<Test>::get(era).individual.keys()
@@ -544,3 +886,23 @@ pub fn make_all_reward_payment(era: EraIndex) {
assert_ok!(Staking::payout_validator(Origin::signed(validator_controller), era));
}
}
#[macro_export]
macro_rules! assert_session_era {
($session:expr, $era:expr) => {
assert_eq!(
Session::current_index(),
$session,
"wrong session {} != {}",
Session::current_index(),
$session,
);
assert_eq!(
Staking::active_era().unwrap().index,
$era,
"wrong active era {} != {}",
Staking::active_era().unwrap().index,
$era,
);
};
}