mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 18:11:10 +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:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user