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:
Kian Paimani
2020-04-27 18:51:46 +02:00
committed by GitHub
parent 33d00692d8
commit ee54eff488
20 changed files with 635 additions and 373 deletions
+5 -1
View File
@@ -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
+16
View File
@@ -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))
}
+18 -5
View File
@@ -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,
})
}
+49 -5
View File
@@ -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()