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:
Gavin Wood
2020-03-03 14:48:20 +01:00
committed by GitHub
parent a1b53280b4
commit 619f64efe9
11 changed files with 1711 additions and 679 deletions
-1
View File
@@ -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| {
+2
View File
@@ -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),
+2
View File
@@ -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)
}
+11 -2
View File
@@ -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)
+2
View File
@@ -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)>>
{
File diff suppressed because it is too large Load Diff
+63 -32
View File
@@ -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