mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-28 23:47:56 +00:00
Allow Staking tests to run with session length other than 1 (#7719)
* fix periodic session * Allow staking tests to run with session lengths other than 1. * Update frame/staking/src/mock.rs Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> * Fix all tests with session length 5. * Test for active != current * Better doc * Update frame/staking/src/lib.rs Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com> * also set the timestamp properly. * trigger CI * Revert "trigger CI" This reverts commit 0f254944cdad848aa6e63bd8a618db95447a8e68. * Update frame/staking/src/lib.rs Co-authored-by: Guillaume Thiolliere <gui.thiolliere@gmail.com>
This commit is contained in:
@@ -952,11 +952,14 @@ decl_storage! {
|
||||
|
||||
/// The active era information, it holds index and start.
|
||||
///
|
||||
/// The active era is the era currently rewarded.
|
||||
/// Validator set of this era must be equal to `SessionInterface::validators`.
|
||||
/// The active era is the era being currently rewarded. Validator set of this era must be
|
||||
/// equal to [`SessionInterface::validators`].
|
||||
pub ActiveEra get(fn active_era): Option<ActiveEraInfo>;
|
||||
|
||||
/// The session index at which the era start for the last `HISTORY_DEPTH` eras.
|
||||
///
|
||||
/// Note: This tracks the starting session (i.e. session index when era start being active)
|
||||
/// for the eras in `[CurrentEra - HISTORY_DEPTH, CurrentEra]`.
|
||||
pub ErasStartSessionIndex get(fn eras_start_session_index):
|
||||
map hasher(twox_64_concat) EraIndex => Option<SessionIndex>;
|
||||
|
||||
@@ -2630,14 +2633,17 @@ impl<T: Config> Module<T> {
|
||||
/// Start a session potentially starting an era.
|
||||
fn start_session(start_session: SessionIndex) {
|
||||
let next_active_era = Self::active_era().map(|e| e.index + 1).unwrap_or(0);
|
||||
// This is only `Some` when current era has already progressed to the next era, while the
|
||||
// active era is one behind (i.e. in the *last session of the active era*, or *first session
|
||||
// of the new current era*, depending on how you look at it).
|
||||
if let Some(next_active_era_start_session_index) =
|
||||
Self::eras_start_session_index(next_active_era)
|
||||
{
|
||||
if next_active_era_start_session_index == start_session {
|
||||
Self::start_era(start_session);
|
||||
} else if next_active_era_start_session_index < start_session {
|
||||
// This arm should never happen, but better handle it than to stall the
|
||||
// staking pallet.
|
||||
// This arm should never happen, but better handle it than to stall the staking
|
||||
// pallet.
|
||||
frame_support::print("Warning: A session appears to have been skipped.");
|
||||
Self::start_era(start_session);
|
||||
}
|
||||
@@ -2893,9 +2899,11 @@ impl<T: Config> Module<T> {
|
||||
/// Self votes are added and nominations before the most recent slashing span are ignored.
|
||||
///
|
||||
/// No storage item is updated.
|
||||
pub fn do_phragmen<Accuracy: PerThing>(iterations: usize)
|
||||
-> Option<PrimitiveElectionResult<T::AccountId, Accuracy>>
|
||||
where ExtendedBalance: From<InnerOf<Accuracy>>
|
||||
pub fn do_phragmen<Accuracy: PerThing>(
|
||||
iterations: usize,
|
||||
) -> Option<PrimitiveElectionResult<T::AccountId, Accuracy>>
|
||||
where
|
||||
ExtendedBalance: From<InnerOf<Accuracy>>,
|
||||
{
|
||||
let weight_of = Self::slashable_balance_of_fn();
|
||||
let mut all_nominators: Vec<(T::AccountId, VoteWeight, Vec<T::AccountId>)> = Vec::new();
|
||||
@@ -2928,7 +2936,11 @@ impl<T: Config> Module<T> {
|
||||
|
||||
if all_validators.len() < Self::minimum_validator_count().max(1) as usize {
|
||||
// If we don't have enough candidates, nothing to do.
|
||||
log!(error, "💸 Chain does not have enough staking candidates to operate. Era {:?}.", Self::current_era());
|
||||
log!(
|
||||
warn,
|
||||
"💸 Chain does not have enough staking candidates to operate. Era {:?}.",
|
||||
Self::current_era()
|
||||
);
|
||||
None
|
||||
} else {
|
||||
seq_phragmen::<_, Accuracy>(
|
||||
@@ -3090,12 +3102,30 @@ impl<T: Config> Module<T> {
|
||||
/// some session can lag in between the newest session planned and the latest session started.
|
||||
impl<T: Config> pallet_session::SessionManager<T::AccountId> for Module<T> {
|
||||
fn new_session(new_index: SessionIndex) -> Option<Vec<T::AccountId>> {
|
||||
frame_support::debug::native::trace!(
|
||||
target: LOG_TARGET,
|
||||
"[{}] planning new_session({})",
|
||||
<frame_system::Module<T>>::block_number(),
|
||||
new_index
|
||||
);
|
||||
Self::new_session(new_index)
|
||||
}
|
||||
fn start_session(start_index: SessionIndex) {
|
||||
frame_support::debug::native::trace!(
|
||||
target: LOG_TARGET,
|
||||
"[{}] starting start_session({})",
|
||||
<frame_system::Module<T>>::block_number(),
|
||||
start_index
|
||||
);
|
||||
Self::start_session(start_index)
|
||||
}
|
||||
fn end_session(end_index: SessionIndex) {
|
||||
frame_support::debug::native::trace!(
|
||||
target: LOG_TARGET,
|
||||
"[{}] ending end_session({})",
|
||||
<frame_system::Module<T>>::block_number(),
|
||||
end_index
|
||||
);
|
||||
Self::end_session(end_index)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ use sp_staking::offence::{OffenceDetails, OnOffenceHandler};
|
||||
use std::{cell::RefCell, collections::HashSet};
|
||||
|
||||
pub const INIT_TIMESTAMP: u64 = 30_000;
|
||||
pub const BLOCK_TIME: u64 = 1000;
|
||||
|
||||
/// The AccountId alias in this test module.
|
||||
pub(crate) type AccountId = u64;
|
||||
@@ -135,10 +136,11 @@ parameter_types! {
|
||||
);
|
||||
pub const MaxLocks: u32 = 1024;
|
||||
pub static SessionsPerEra: SessionIndex = 3;
|
||||
pub static ExistentialDeposit: Balance = 0;
|
||||
pub static ExistentialDeposit: Balance = 1;
|
||||
pub static SlashDeferDuration: EraIndex = 0;
|
||||
pub static ElectionLookahead: BlockNumber = 0;
|
||||
pub static Period: BlockNumber = 1;
|
||||
pub static Period: BlockNumber = 5;
|
||||
pub static Offset: BlockNumber = 0;
|
||||
pub static MaxIterations: u32 = 0;
|
||||
}
|
||||
|
||||
@@ -175,7 +177,6 @@ impl pallet_balances::Config for Test {
|
||||
type WeightInfo = ();
|
||||
}
|
||||
parameter_types! {
|
||||
pub const Offset: BlockNumber = 0;
|
||||
pub const UncleGenerations: u64 = 0;
|
||||
pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(25);
|
||||
}
|
||||
@@ -286,46 +287,36 @@ where
|
||||
pub type Extrinsic = TestXt<Call, ()>;
|
||||
|
||||
pub struct ExtBuilder {
|
||||
session_length: BlockNumber,
|
||||
election_lookahead: BlockNumber,
|
||||
session_per_era: SessionIndex,
|
||||
existential_deposit: Balance,
|
||||
validator_pool: bool,
|
||||
nominate: bool,
|
||||
validator_count: u32,
|
||||
minimum_validator_count: u32,
|
||||
slash_defer_duration: EraIndex,
|
||||
fair: bool,
|
||||
num_validators: Option<u32>,
|
||||
invulnerables: Vec<AccountId>,
|
||||
has_stakers: bool,
|
||||
max_offchain_iterations: u32,
|
||||
initialize_first_session: 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,
|
||||
validator_count: 2,
|
||||
minimum_validator_count: 0,
|
||||
slash_defer_duration: 0,
|
||||
fair: true,
|
||||
num_validators: None,
|
||||
invulnerables: vec![],
|
||||
has_stakers: true,
|
||||
max_offchain_iterations: 0,
|
||||
initialize_first_session: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtBuilder {
|
||||
pub fn existential_deposit(mut self, existential_deposit: Balance) -> Self {
|
||||
self.existential_deposit = existential_deposit;
|
||||
pub fn existential_deposit(self, existential_deposit: Balance) -> Self {
|
||||
EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = existential_deposit);
|
||||
self
|
||||
}
|
||||
pub fn validator_pool(mut self, validator_pool: bool) -> Self {
|
||||
@@ -344,8 +335,8 @@ impl ExtBuilder {
|
||||
self.minimum_validator_count = count;
|
||||
self
|
||||
}
|
||||
pub fn slash_defer_duration(mut self, eras: EraIndex) -> Self {
|
||||
self.slash_defer_duration = eras;
|
||||
pub fn slash_defer_duration(self, eras: EraIndex) -> Self {
|
||||
SLASH_DEFER_DURATION.with(|v| *v.borrow_mut() = eras);
|
||||
self
|
||||
}
|
||||
pub fn fair(mut self, is_fair: bool) -> Self {
|
||||
@@ -360,46 +351,43 @@ impl ExtBuilder {
|
||||
self.invulnerables = invulnerables;
|
||||
self
|
||||
}
|
||||
pub fn session_per_era(mut self, length: SessionIndex) -> Self {
|
||||
self.session_per_era = length;
|
||||
pub fn session_per_era(self, length: SessionIndex) -> Self {
|
||||
SESSIONS_PER_ERA.with(|v| *v.borrow_mut() = length);
|
||||
self
|
||||
}
|
||||
pub fn election_lookahead(mut self, look: BlockNumber) -> Self {
|
||||
self.election_lookahead = look;
|
||||
pub fn election_lookahead(self, look: BlockNumber) -> Self {
|
||||
ELECTION_LOOKAHEAD.with(|v| *v.borrow_mut() = look);
|
||||
self
|
||||
}
|
||||
pub fn session_length(mut self, length: BlockNumber) -> Self {
|
||||
self.session_length = length;
|
||||
pub fn period(self, length: BlockNumber) -> Self {
|
||||
PERIOD.with(|v| *v.borrow_mut() = length);
|
||||
self
|
||||
}
|
||||
pub fn has_stakers(mut self, has: bool) -> Self {
|
||||
self.has_stakers = has;
|
||||
self
|
||||
}
|
||||
pub fn max_offchain_iterations(mut self, iterations: u32) -> Self {
|
||||
self.max_offchain_iterations = iterations;
|
||||
pub fn max_offchain_iterations(self, iterations: u32) -> Self {
|
||||
MAX_ITERATIONS.with(|v| *v.borrow_mut() = iterations);
|
||||
self
|
||||
}
|
||||
pub fn offchain_election_ext(self) -> Self {
|
||||
self.session_per_era(4)
|
||||
.session_length(5)
|
||||
.election_lookahead(3)
|
||||
self.session_per_era(4).period(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);
|
||||
SESSIONS_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 initialize_first_session(mut self, init: bool) -> Self {
|
||||
self.initialize_first_session = init;
|
||||
self
|
||||
}
|
||||
pub fn offset(self, offset: BlockNumber) -> Self {
|
||||
OFFSET.with(|v| *v.borrow_mut() = offset);
|
||||
self
|
||||
}
|
||||
pub fn build(self) -> sp_io::TestExternalities {
|
||||
sp_tracing::try_init_simple();
|
||||
self.set_associated_constants();
|
||||
let mut storage = frame_system::GenesisConfig::default()
|
||||
.build_storage::<Test>()
|
||||
.unwrap();
|
||||
let balance_factor = if self.existential_deposit > 1 {
|
||||
let balance_factor = if ExistentialDeposit::get() > 1 {
|
||||
256
|
||||
} else {
|
||||
1
|
||||
@@ -475,13 +463,17 @@ impl ExtBuilder {
|
||||
SESSION.with(|x| *x.borrow_mut() = (validators.clone(), HashSet::new()));
|
||||
});
|
||||
|
||||
// We consider all test to start after timestamp is initialized
|
||||
// This must be ensured by having `timestamp::on_initialize` called before
|
||||
// `staking::on_initialize`
|
||||
ext.execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
Timestamp::set_timestamp(INIT_TIMESTAMP);
|
||||
});
|
||||
if self.initialize_first_session {
|
||||
// We consider all test to start after timestamp is initialized This must be ensured by
|
||||
// having `timestamp::on_initialize` called before `staking::on_initialize`. Also, if
|
||||
// session length is 1, then it is already triggered.
|
||||
ext.execute_with(|| {
|
||||
System::set_block_number(1);
|
||||
Session::on_initialize(1);
|
||||
Staking::on_initialize(1);
|
||||
Timestamp::set_timestamp(INIT_TIMESTAMP);
|
||||
});
|
||||
}
|
||||
|
||||
ext
|
||||
}
|
||||
@@ -498,20 +490,12 @@ pub type Session = pallet_session::Module<Test>;
|
||||
pub type Timestamp = pallet_timestamp::Module<Test>;
|
||||
pub type Staking = Module<Test>;
|
||||
|
||||
pub(crate) fn current_era() -> EraIndex {
|
||||
Staking::current_era().unwrap()
|
||||
}
|
||||
|
||||
fn post_conditions() {
|
||||
check_nominators();
|
||||
check_exposures();
|
||||
check_ledgers();
|
||||
}
|
||||
|
||||
pub(crate) fn active_era() -> EraIndex {
|
||||
Staking::active_era().unwrap().index
|
||||
}
|
||||
|
||||
fn check_ledgers() {
|
||||
// check the ledger of all stakers.
|
||||
Bonded::<Test>::iter().for_each(|(_, ctrl)| assert_ledger_consistent(ctrl))
|
||||
@@ -593,6 +577,14 @@ fn assert_ledger_consistent(ctrl: AccountId) {
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn active_era() -> EraIndex {
|
||||
Staking::active_era().unwrap().index
|
||||
}
|
||||
|
||||
pub(crate) fn current_era() -> EraIndex {
|
||||
Staking::current_era().unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn bond_validator(stash: AccountId, ctrl: AccountId, val: Balance) {
|
||||
let _ = Balances::make_free_balance_be(&stash, val);
|
||||
let _ = Balances::make_free_balance_be(&ctrl, val);
|
||||
@@ -625,52 +617,98 @@ pub(crate) fn bond_nominator(
|
||||
assert_ok!(Staking::nominate(Origin::signed(ctrl), target));
|
||||
}
|
||||
|
||||
/// Progress to the given block, triggering session and era changes as we progress.
|
||||
///
|
||||
/// This will finalize the previous block, initialize up to the given block, essentially simulating
|
||||
/// a block import/propose process where we first initialize the block, then execute some stuff (not
|
||||
/// in the function), and then finalize the block.
|
||||
pub(crate) fn run_to_block(n: BlockNumber) {
|
||||
Staking::on_finalize(System::block_number());
|
||||
for b in System::block_number() + 1..=n {
|
||||
for b in (System::block_number() + 1)..=n {
|
||||
System::set_block_number(b);
|
||||
Session::on_initialize(b);
|
||||
Staking::on_initialize(b);
|
||||
Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP);
|
||||
if b != n {
|
||||
Staking::on_finalize(System::block_number());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Progresses from the current block number (whatever that may be) to the `P * session_index + 1`.
|
||||
pub(crate) fn start_session(session_index: SessionIndex) {
|
||||
let end: u64 = if Offset::get().is_zero() {
|
||||
(session_index as u64) * Period::get()
|
||||
} else {
|
||||
Offset::get() + (session_index.saturating_sub(1) as u64) * Period::get()
|
||||
};
|
||||
run_to_block(end);
|
||||
// session must have progressed properly.
|
||||
assert_eq!(
|
||||
Session::current_index(),
|
||||
session_index,
|
||||
"current session index = {}, expected = {}",
|
||||
Session::current_index(),
|
||||
session_index,
|
||||
);
|
||||
}
|
||||
|
||||
/// Go one session forward.
|
||||
pub(crate) fn advance_session() {
|
||||
let current_index = Session::current_index();
|
||||
start_session(current_index + 1);
|
||||
}
|
||||
|
||||
pub(crate) 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 + INIT_TIMESTAMP);
|
||||
Session::on_initialize(System::block_number());
|
||||
Staking::on_initialize(System::block_number());
|
||||
}
|
||||
|
||||
assert_eq!(Session::current_index(), session_index);
|
||||
}
|
||||
|
||||
// This start and activate the era given.
|
||||
// Because the mock use pallet-session which delays session by one, this will be one session after
|
||||
// the election happened, not the first session after the election has happened.
|
||||
pub(crate) fn start_era(era_index: EraIndex) {
|
||||
/// Progress until the given era.
|
||||
pub(crate) fn start_active_era(era_index: EraIndex) {
|
||||
start_session((era_index * <SessionsPerEra as Get<u32>>::get()).into());
|
||||
assert_eq!(Staking::current_era().unwrap(), era_index);
|
||||
assert_eq!(Staking::active_era().unwrap().index, era_index);
|
||||
assert_eq!(active_era(), era_index);
|
||||
// One way or another, current_era must have changed before the active era, so they must match
|
||||
// at this point.
|
||||
assert_eq!(current_era(), active_era());
|
||||
}
|
||||
|
||||
pub(crate) fn current_total_payout_for_duration(duration: u64) -> Balance {
|
||||
inflation::compute_total_payout(
|
||||
let reward = inflation::compute_total_payout(
|
||||
<Test as Config>::RewardCurve::get(),
|
||||
Staking::eras_total_stake(Staking::active_era().unwrap().index),
|
||||
Staking::eras_total_stake(active_era()),
|
||||
Balances::total_issuance(),
|
||||
duration,
|
||||
).0
|
||||
)
|
||||
.0;
|
||||
assert!(reward > 0);
|
||||
reward
|
||||
}
|
||||
|
||||
pub(crate) fn maximum_payout_for_duration(duration: u64) -> Balance {
|
||||
inflation::compute_total_payout(
|
||||
<Test as Config>::RewardCurve::get(),
|
||||
0,
|
||||
Balances::total_issuance(),
|
||||
duration,
|
||||
)
|
||||
.1
|
||||
}
|
||||
|
||||
/// Time it takes to finish a session.
|
||||
///
|
||||
/// Note, if you see `time_per_session() - BLOCK_TIME`, it is fine. This is because we set the
|
||||
/// timestamp after on_initialize, so the timestamp is always one block old.
|
||||
pub(crate) fn time_per_session() -> u64 {
|
||||
Period::get() * BLOCK_TIME
|
||||
}
|
||||
|
||||
/// Time it takes to finish an era.
|
||||
///
|
||||
/// Note, if you see `time_per_era() - BLOCK_TIME`, it is fine. This is because we set the
|
||||
/// timestamp after on_initialize, so the timestamp is always one block old.
|
||||
pub(crate) fn time_per_era() -> u64 {
|
||||
time_per_session() * SessionsPerEra::get() as u64
|
||||
}
|
||||
|
||||
/// Time that will be calculated for the reward per era.
|
||||
pub(crate) fn reward_time_per_era() -> u64 {
|
||||
time_per_era() - BLOCK_TIME
|
||||
}
|
||||
|
||||
pub(crate) fn reward_all_elected() {
|
||||
@@ -939,8 +977,11 @@ pub(crate) fn make_all_reward_payment(era: EraIndex) {
|
||||
// reward validators
|
||||
for validator_controller in validators_with_reward.iter().filter_map(Staking::bonded) {
|
||||
let ledger = <Ledger<Test>>::get(&validator_controller).unwrap();
|
||||
|
||||
assert_ok!(Staking::payout_stakers(Origin::signed(1337), ledger.stash, era));
|
||||
assert_ok!(Staking::payout_stakers(
|
||||
Origin::signed(1337),
|
||||
ledger.stash,
|
||||
era
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user