mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-09 20:11:09 +00:00
Lazy payouts (#4474)
* TODOs * Remove superfluous: * partial implementation * full implementation * fix preferences * update comments * upgrade test WIP * fix more tests * fix cutoff * fix saturation * comment * upgrade mock * upgrade test * WIP migration * WIP migration * remove slot stake stuff * fix merge * migration of ledger * remove equalize from test * add test * fix * update doc * fix compilation * improve test readibility * improve doc * fix most todo * fix migration and test * remove println * WIP * add test and spec * weight * update doc * safer end_era * fix exposure of conversion * Revert "safer end_era" This reverts commit 72ff737d27be67d87308514b13e2574bc5f09fce. * fix useless put * exposure clipped * doc * fix payout with clipped * fix node runtime * add doc * pluggable and generalized staking module * remove print * update doc * refactor * improve documentation and implementation * fix test * Fix test * fix test * fix test * fix remove lowest stake from exposure, not biggest. * nomination index arguments in nominator_payout * add test * try to fix offence * apply slashed and bond eras until active era * doc * update spec version * add test upgrade from previous test environment * Apply suggestions from code review Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com> * nominators upgrade has been cleaned * dynamic history depth implementation * make current_era - history_depth included * Change equality check to start era to less than or equal * Use era specific validator prefs * Add print statement and comment about start era if < * fix next_reward overflow * make more check for bad era claim for zero cost * small refactor * code refactor + fix use of deprecated storage * fix wasm build * add comment * Fix tests * remove outdated comment * Apply suggestions from code review Co-Authored-By: Shawn Tabrizi <shawntabrizi@gmail.com> * gather active era information into one storage Co-authored-by: thiolliere <gui.thiolliere@gmail.com> Co-authored-by: Shawn Tabrizi <shawntabrizi@gmail.com>
This commit is contained in:
@@ -247,7 +247,6 @@ pub fn testnet_genesis(
|
||||
}).collect::<Vec<_>>(),
|
||||
}),
|
||||
pallet_staking: Some(StakingConfig {
|
||||
current_era: 0,
|
||||
validator_count: initial_authorities.len() as u32 * 2,
|
||||
minimum_validator_count: initial_authorities.len() as u32,
|
||||
stakers: initial_authorities.iter().map(|x| {
|
||||
|
||||
@@ -269,6 +269,7 @@ 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 MaxNominatorRewardedPerValidator: u32 = 64;
|
||||
}
|
||||
|
||||
impl pallet_staking::Trait for Runtime {
|
||||
@@ -286,6 +287,7 @@ impl pallet_staking::Trait for Runtime {
|
||||
type SlashCancelOrigin = pallet_collective::EnsureProportionAtLeast<_3, _4, AccountId, CouncilCollective>;
|
||||
type SessionInterface = Self;
|
||||
type RewardCurve = RewardCurve;
|
||||
type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
|
||||
@@ -84,7 +84,6 @@ pub fn config_endowed(
|
||||
]
|
||||
}),
|
||||
pallet_staking: Some(StakingConfig {
|
||||
current_era: 0,
|
||||
stakers: vec![
|
||||
(dave(), alice(), 111 * DOLLARS, pallet_staking::StakerStatus::Validator),
|
||||
(eve(), bob(), 100 * DOLLARS, pallet_staking::StakerStatus::Validator),
|
||||
|
||||
@@ -49,6 +49,7 @@ impl pallet_session::SessionManager<u64> for TestSessionManager {
|
||||
VALIDATORS.with(|l| l.borrow_mut().take())
|
||||
}
|
||||
fn end_session(_: SessionIndex) {}
|
||||
fn start_session(_: SessionIndex) {}
|
||||
}
|
||||
|
||||
impl pallet_session::historical::SessionManager<u64, u64> for TestSessionManager {
|
||||
@@ -62,6 +63,7 @@ impl pallet_session::historical::SessionManager<u64, u64> for TestSessionManager
|
||||
)
|
||||
}
|
||||
fn end_session(_: SessionIndex) {}
|
||||
fn start_session(_: SessionIndex) {}
|
||||
}
|
||||
|
||||
/// An extrinsic type used for tests.
|
||||
|
||||
@@ -108,6 +108,7 @@ pub trait SessionManager<ValidatorId, FullIdentification>: crate::SessionManager
|
||||
/// If there was a validator set change, its returns the set of new validators along with their
|
||||
/// full identifications.
|
||||
fn new_session(new_index: SessionIndex) -> Option<Vec<(ValidatorId, FullIdentification)>>;
|
||||
fn start_session(start_index: SessionIndex);
|
||||
fn end_session(end_index: SessionIndex);
|
||||
}
|
||||
|
||||
@@ -146,6 +147,9 @@ impl<T: Trait, I> crate::SessionManager<T::ValidatorId> for NoteHistoricalRoot<T
|
||||
|
||||
new_validators
|
||||
}
|
||||
fn start_session(start_index: SessionIndex) {
|
||||
<I as SessionManager<_, _>>::start_session(start_index)
|
||||
}
|
||||
fn end_session(end_index: SessionIndex) {
|
||||
<I as SessionManager<_, _>>::end_session(end_index)
|
||||
}
|
||||
|
||||
@@ -162,10 +162,15 @@ pub trait SessionManager<ValidatorId> {
|
||||
/// Because the session pallet can queue validator set the ending session can be lower than the
|
||||
/// last new session index.
|
||||
fn end_session(end_index: SessionIndex);
|
||||
/// Start the session.
|
||||
///
|
||||
/// The session start to be used for validation
|
||||
fn start_session(start_index: SessionIndex);
|
||||
}
|
||||
|
||||
impl<A> SessionManager<A> for () {
|
||||
fn new_session(_: SessionIndex) -> Option<Vec<A>> { None }
|
||||
fn start_session(_: SessionIndex) {}
|
||||
fn end_session(_: SessionIndex) {}
|
||||
}
|
||||
|
||||
@@ -423,6 +428,8 @@ decl_storage! {
|
||||
|
||||
<Validators<T>>::put(initial_validators_0);
|
||||
<QueuedKeys<T>>::put(queued_keys);
|
||||
|
||||
T::SessionManager::start_session(0);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -520,6 +527,8 @@ impl<T: Trait> Module<T> {
|
||||
// Inform the session handlers that a session is going to end.
|
||||
T::SessionHandler::on_before_session_ending();
|
||||
|
||||
T::SessionManager::end_session(session_index);
|
||||
|
||||
// Get queued session keys and validators.
|
||||
let session_keys = <QueuedKeys<T>>::get();
|
||||
let validators = session_keys.iter()
|
||||
@@ -532,12 +541,12 @@ impl<T: Trait> Module<T> {
|
||||
DisabledValidators::take();
|
||||
}
|
||||
|
||||
T::SessionManager::end_session(session_index);
|
||||
|
||||
// Increment session index.
|
||||
let session_index = session_index + 1;
|
||||
CurrentIndex::put(session_index);
|
||||
|
||||
T::SessionManager::start_session(session_index);
|
||||
|
||||
// Get next validator set.
|
||||
let maybe_next_validators = T::SessionManager::new_session(session_index + 1);
|
||||
let (next_validators, next_identities_changed)
|
||||
|
||||
@@ -92,6 +92,7 @@ impl SessionHandler<u64> for TestSessionHandler {
|
||||
pub struct TestSessionManager;
|
||||
impl SessionManager<u64> for TestSessionManager {
|
||||
fn end_session(_: SessionIndex) {}
|
||||
fn start_session(_: SessionIndex) {}
|
||||
fn new_session(_: SessionIndex) -> Option<Vec<u64>> {
|
||||
if !TEST_SESSION_CHANGED.with(|l| *l.borrow()) {
|
||||
VALIDATORS.with(|v| {
|
||||
@@ -112,6 +113,7 @@ impl SessionManager<u64> for TestSessionManager {
|
||||
#[cfg(feature = "historical")]
|
||||
impl crate::historical::SessionManager<u64, u64> for TestSessionManager {
|
||||
fn end_session(_: SessionIndex) {}
|
||||
fn start_session(_: SessionIndex) {}
|
||||
fn new_session(new_index: SessionIndex)
|
||||
-> Option<Vec<(u64, u64)>>
|
||||
{
|
||||
|
||||
+713
-245
File diff suppressed because it is too large
Load Diff
@@ -16,22 +16,23 @@
|
||||
|
||||
//! Test utilities
|
||||
|
||||
use std::{collections::HashSet, cell::RefCell};
|
||||
use std::{collections::{HashSet, HashMap}, cell::RefCell};
|
||||
use sp_runtime::{Perbill, KeyTypeId};
|
||||
use sp_runtime::curve::PiecewiseLinear;
|
||||
use sp_runtime::traits::{IdentityLookup, Convert, OpaqueKeys, OnInitialize, SaturatedConversion};
|
||||
use sp_runtime::traits::{IdentityLookup, Convert, OpaqueKeys, OnInitialize, OnFinalize, SaturatedConversion};
|
||||
use sp_runtime::testing::{Header, UintAuthorityId};
|
||||
use sp_staking::{SessionIndex, offence::{OffenceDetails, OnOffenceHandler}};
|
||||
use sp_core::{H256, crypto::key_types};
|
||||
use sp_io;
|
||||
use frame_support::{
|
||||
assert_ok, impl_outer_origin, parameter_types, StorageLinkedMap, StorageValue,
|
||||
assert_ok, impl_outer_origin, parameter_types, StorageLinkedMap, StorageValue, StorageMap,
|
||||
StorageDoubleMap,
|
||||
traits::{Currency, Get, FindAuthor},
|
||||
weights::Weight,
|
||||
};
|
||||
use crate::{
|
||||
EraIndex, GenesisConfig, Module, Trait, StakerStatus, ValidatorPrefs, RewardDestination,
|
||||
Nominators, inflation
|
||||
Nominators, inflation, SessionInterface, Exposure, ErasStakers, ErasRewardPoints
|
||||
};
|
||||
|
||||
/// The AccountId alias in this test module.
|
||||
@@ -198,6 +199,7 @@ 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 = pallet_balances::Module<Self>;
|
||||
@@ -213,6 +215,7 @@ impl Trait for Test {
|
||||
type BondingDuration = BondingDuration;
|
||||
type SessionInterface = Self;
|
||||
type RewardCurve = RewardCurve;
|
||||
type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator;
|
||||
}
|
||||
|
||||
pub struct ExtBuilder {
|
||||
@@ -328,7 +331,6 @@ impl ExtBuilder {
|
||||
};
|
||||
let nominated = if self.nominate { vec![11, 21] } else { vec![] };
|
||||
let _ = GenesisConfig::<Test>{
|
||||
current_era: 0,
|
||||
stakers: vec![
|
||||
// (stash, controller, staked_amount, status)
|
||||
(11, 10, balance_factor * 1000, StakerStatus::<AccountId>::Validator),
|
||||
@@ -366,35 +368,34 @@ pub type Session = pallet_session::Module<Test>;
|
||||
pub type Timestamp = pallet_timestamp::Module<Test>;
|
||||
pub type Staking = Module<Test>;
|
||||
|
||||
pub fn check_exposure_all() {
|
||||
Staking::current_elected().into_iter().for_each(|acc| check_exposure(acc));
|
||||
pub fn check_exposure_all(era: EraIndex) {
|
||||
ErasStakers::<Test>::iter_prefix(era).for_each(check_exposure)
|
||||
}
|
||||
|
||||
pub fn check_nominator_all() {
|
||||
<Nominators<Test>>::enumerate().for_each(|(acc, _)| check_nominator_exposure(acc));
|
||||
pub fn check_nominator_all(era: EraIndex) {
|
||||
<Nominators<Test>>::enumerate()
|
||||
.for_each(|(acc, _)| check_nominator_exposure(era, acc));
|
||||
}
|
||||
|
||||
/// Check for each selected validator: expo.total = Sum(expo.other) + expo.own
|
||||
pub fn check_exposure(stash: u64) {
|
||||
assert_is_stash(stash);
|
||||
let expo = Staking::stakers(&stash);
|
||||
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 for {:?}: {:?}", stash, expo,
|
||||
"wrong total exposure {:?}", expo,
|
||||
);
|
||||
}
|
||||
|
||||
/// Check that for each nominator: slashable_balance > sum(used_balance)
|
||||
/// Note: we might not consume all of a nominator's balance, but we MUST NOT over spend it.
|
||||
pub fn check_nominator_exposure(stash: u64) {
|
||||
pub fn check_nominator_exposure(era: EraIndex, stash: AccountId) {
|
||||
assert_is_stash(stash);
|
||||
let mut sum = 0;
|
||||
Staking::current_elected()
|
||||
.iter()
|
||||
.map(|v| Staking::stakers(v))
|
||||
.for_each(|e| e.others.iter()
|
||||
.filter(|i| i.who == stash)
|
||||
.for_each(|i| sum += i.value));
|
||||
ErasStakers::<Test>::iter_prefix(era)
|
||||
.for_each(|exposure| {
|
||||
exposure.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!(
|
||||
@@ -403,11 +404,11 @@ pub fn check_nominator_exposure(stash: u64) {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn assert_is_stash(acc: u64) {
|
||||
pub fn assert_is_stash(acc: AccountId) {
|
||||
assert!(Staking::bonded(&acc).is_some(), "Not a stash.");
|
||||
}
|
||||
|
||||
pub fn assert_ledger_consistent(stash: u64) {
|
||||
pub fn assert_ledger_consistent(stash: AccountId) {
|
||||
assert_is_stash(stash);
|
||||
let ledger = Staking::ledger(stash - 1).unwrap();
|
||||
|
||||
@@ -437,9 +438,8 @@ pub fn advance_session() {
|
||||
}
|
||||
|
||||
pub fn start_session(session_index: SessionIndex) {
|
||||
// Compensate for session delay
|
||||
let session_index = session_index + 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());
|
||||
@@ -450,22 +450,21 @@ pub fn start_session(session_index: SessionIndex) {
|
||||
|
||||
pub fn start_era(era_index: EraIndex) {
|
||||
start_session((era_index * 3).into());
|
||||
assert_eq!(Staking::current_era(), era_index);
|
||||
assert_eq!(Staking::active_era().unwrap().index, era_index);
|
||||
}
|
||||
|
||||
pub fn current_total_payout_for_duration(duration: u64) -> u64 {
|
||||
inflation::compute_total_payout(
|
||||
<Test as Trait>::RewardCurve::get(),
|
||||
<Module<Test>>::slot_stake() * 2,
|
||||
Staking::eras_total_stake(Staking::active_era().unwrap().index),
|
||||
Balances::total_issuance(),
|
||||
duration,
|
||||
).0
|
||||
}
|
||||
|
||||
pub fn reward_all_elected() {
|
||||
let rewards = <Module<Test>>::current_elected().iter()
|
||||
.map(|v| (*v, 1))
|
||||
.collect::<Vec<_>>();
|
||||
let rewards = <Test as Trait>::SessionInterface::validators().into_iter()
|
||||
.map(|v| (v, 1));
|
||||
|
||||
<Module<Test>>::reward_by_ids(rewards)
|
||||
}
|
||||
@@ -489,8 +488,8 @@ pub fn on_offence_in_era(
|
||||
}
|
||||
}
|
||||
|
||||
if Staking::current_era() == era {
|
||||
Staking::on_offence(offenders, slash_fraction, Staking::current_era_start_session_index());
|
||||
if Staking::active_era().unwrap().index == era {
|
||||
Staking::on_offence(offenders, slash_fraction, Staking::eras_start_session_index(era).unwrap());
|
||||
} else {
|
||||
panic!("cannot slash in era {}", era);
|
||||
}
|
||||
@@ -500,6 +499,38 @@ pub fn on_offence_now(
|
||||
offenders: &[OffenceDetails<AccountId, pallet_session::historical::IdentificationTuple<Test>>],
|
||||
slash_fraction: &[Perbill],
|
||||
) {
|
||||
let now = Staking::current_era();
|
||||
let now = Staking::active_era().unwrap().index;
|
||||
on_offence_in_era(offenders, slash_fraction, now)
|
||||
}
|
||||
|
||||
/// 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()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// reward nominators
|
||||
let mut nominator_controllers = HashMap::new();
|
||||
for validator in Staking::eras_reward_points(era).individual.keys() {
|
||||
let validator_exposure = Staking::eras_stakers_clipped(era, validator);
|
||||
for (nom_index, nom) in validator_exposure.others.iter().enumerate() {
|
||||
if let Some(nom_ctrl) = Staking::bonded(nom.who) {
|
||||
nominator_controllers.entry(nom_ctrl)
|
||||
.or_insert(vec![])
|
||||
.push((validator.clone(), nom_index as u32));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (nominator_controller, validators_with_nom_index) in nominator_controllers {
|
||||
assert_ok!(Staking::payout_nominator(
|
||||
Origin::signed(nominator_controller),
|
||||
era,
|
||||
validators_with_nom_index,
|
||||
));
|
||||
}
|
||||
|
||||
// reward validators
|
||||
for validator_controller in validators_with_reward.iter().filter_map(Staking::bonded) {
|
||||
assert_ok!(Staking::payout_validator(Origin::signed(validator_controller), era));
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user