mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 18:41:03 +00:00
[Staking] Runtime api if era rewards are pending to be claimed (#4301)
closes https://github.com/paritytech/polkadot-sdk/issues/426. related to https://github.com/paritytech/polkadot-sdk/pull/1189. Would help offchain programs to query if there are unclaimed pages of rewards for a given era. The logic could look like below ```js // loop as long as all era pages are claimed. while (api.call.stakingApi.pendingRewards(era, validator_stash)) { api.tx.staking.payout_stakers(validator_stash, era) } ```
This commit is contained in:
@@ -30,7 +30,10 @@ sp_api::decl_runtime_apis! {
|
||||
/// Returns the nominations quota for a nominator with a given balance.
|
||||
fn nominations_quota(balance: Balance) -> u32;
|
||||
|
||||
/// Returns the page count of exposures for a validator in a given era.
|
||||
/// Returns the page count of exposures for a validator `account` in a given era.
|
||||
fn eras_stakers_page_count(era: sp_staking::EraIndex, account: AccountId) -> sp_staking::Page;
|
||||
|
||||
/// Returns true if validator `account` has pages to be claimed for the given era.
|
||||
fn pending_rewards(era: sp_staking::EraIndex, account: AccountId) -> bool;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1035,11 +1035,37 @@ where
|
||||
/// can and add more functions to it as needed.
|
||||
pub struct EraInfo<T>(sp_std::marker::PhantomData<T>);
|
||||
impl<T: Config> EraInfo<T> {
|
||||
/// Returns true if validator has one or more page of era rewards not claimed yet.
|
||||
// Also looks at legacy storage that can be cleaned up after #433.
|
||||
pub fn pending_rewards(era: EraIndex, validator: &T::AccountId) -> bool {
|
||||
let page_count = if let Some(overview) = <ErasStakersOverview<T>>::get(&era, validator) {
|
||||
overview.page_count
|
||||
} else {
|
||||
if <ErasStakers<T>>::contains_key(era, validator) {
|
||||
// this means non paged exposure, and we treat them as single paged.
|
||||
1
|
||||
} else {
|
||||
// if no exposure, then no rewards to claim.
|
||||
return false
|
||||
}
|
||||
};
|
||||
|
||||
// check if era is marked claimed in legacy storage.
|
||||
if <Ledger<T>>::get(validator)
|
||||
.map(|l| l.legacy_claimed_rewards.contains(&era))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return false
|
||||
}
|
||||
|
||||
ClaimedRewards::<T>::get(era, validator).len() < page_count as usize
|
||||
}
|
||||
|
||||
/// Temporary function which looks at both (1) passed param `T::StakingLedger` for legacy
|
||||
/// non-paged rewards, and (2) `T::ClaimedRewards` for paged rewards. This function can be
|
||||
/// removed once `T::HistoryDepth` eras have passed and none of the older non-paged rewards
|
||||
/// are relevant/claimable.
|
||||
// Refer tracker issue for cleanup: #13034
|
||||
// Refer tracker issue for cleanup: https://github.com/paritytech/polkadot-sdk/issues/433
|
||||
pub(crate) fn is_rewards_claimed_with_legacy_fallback(
|
||||
era: EraIndex,
|
||||
ledger: &StakingLedger<T>,
|
||||
|
||||
@@ -1183,6 +1183,10 @@ impl<T: Config> Pallet<T> {
|
||||
pub fn api_eras_stakers_page_count(era: EraIndex, account: T::AccountId) -> Page {
|
||||
EraInfo::<T>::get_page_count(era, &account)
|
||||
}
|
||||
|
||||
pub fn api_pending_rewards(era: EraIndex, account: T::AccountId) -> bool {
|
||||
EraInfo::<T>::pending_rewards(era, &account)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> ElectionDataProvider for Pallet<T> {
|
||||
|
||||
@@ -6796,6 +6796,113 @@ fn test_validator_exposure_is_backward_compatible_with_non_paged_rewards_payout(
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_runtime_api_pending_rewards() {
|
||||
ExtBuilder::default().build_and_execute(|| {
|
||||
// GIVEN
|
||||
let err_weight = <Test as Config>::WeightInfo::payout_stakers_alive_staked(0);
|
||||
let stake = 100;
|
||||
|
||||
// validator with non-paged exposure, rewards marked in legacy claimed rewards.
|
||||
let validator_one = 301;
|
||||
// validator with non-paged exposure, rewards marked in paged claimed rewards.
|
||||
let validator_two = 302;
|
||||
// validator with paged exposure.
|
||||
let validator_three = 303;
|
||||
|
||||
// Set staker
|
||||
for v in validator_one..=validator_three {
|
||||
let _ = Balances::make_free_balance_be(&v, stake);
|
||||
assert_ok!(Staking::bond(RuntimeOrigin::signed(v), stake, RewardDestination::Staked));
|
||||
}
|
||||
|
||||
// Add reward points
|
||||
let reward = EraRewardPoints::<AccountId> {
|
||||
total: 1,
|
||||
individual: vec![(validator_one, 1), (validator_two, 1), (validator_three, 1)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
};
|
||||
ErasRewardPoints::<Test>::insert(0, reward);
|
||||
|
||||
// build exposure
|
||||
let mut individual_exposures: Vec<IndividualExposure<AccountId, Balance>> = vec![];
|
||||
for i in 0..=MaxExposurePageSize::get() {
|
||||
individual_exposures.push(IndividualExposure { who: i.into(), value: stake });
|
||||
}
|
||||
let exposure = Exposure::<AccountId, Balance> {
|
||||
total: stake * (MaxExposurePageSize::get() as Balance + 2),
|
||||
own: stake,
|
||||
others: individual_exposures,
|
||||
};
|
||||
|
||||
// add non-paged exposure for one and two.
|
||||
<ErasStakers<Test>>::insert(0, validator_one, exposure.clone());
|
||||
<ErasStakers<Test>>::insert(0, validator_two, exposure.clone());
|
||||
// add paged exposure for third validator
|
||||
EraInfo::<Test>::set_exposure(0, &validator_three, exposure);
|
||||
|
||||
// add some reward to be distributed
|
||||
ErasValidatorReward::<Test>::insert(0, 1000);
|
||||
|
||||
// mark rewards claimed for validator_one in legacy claimed rewards
|
||||
<Ledger<Test>>::insert(
|
||||
validator_one,
|
||||
StakingLedgerInspect {
|
||||
stash: validator_one,
|
||||
total: stake,
|
||||
active: stake,
|
||||
unlocking: Default::default(),
|
||||
legacy_claimed_rewards: bounded_vec![0],
|
||||
},
|
||||
);
|
||||
|
||||
// SCENARIO ONE: rewards already marked claimed in legacy storage.
|
||||
// runtime api should return false for pending rewards for validator_one.
|
||||
assert!(!EraInfo::<Test>::pending_rewards(0, &validator_one));
|
||||
// and if we try to pay, we get an error.
|
||||
assert_noop!(
|
||||
Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_one, 0),
|
||||
Error::<Test>::AlreadyClaimed.with_weight(err_weight)
|
||||
);
|
||||
|
||||
// SCENARIO TWO: non-paged exposure
|
||||
// validator two has not claimed rewards, so pending rewards is true.
|
||||
assert!(EraInfo::<Test>::pending_rewards(0, &validator_two));
|
||||
// and payout works
|
||||
assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_two, 0));
|
||||
// now pending rewards is false.
|
||||
assert!(!EraInfo::<Test>::pending_rewards(0, &validator_two));
|
||||
// and payout fails
|
||||
assert_noop!(
|
||||
Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_two, 0),
|
||||
Error::<Test>::AlreadyClaimed.with_weight(err_weight)
|
||||
);
|
||||
|
||||
// SCENARIO THREE: validator with paged exposure (two pages).
|
||||
// validator three has not claimed rewards, so pending rewards is true.
|
||||
assert!(EraInfo::<Test>::pending_rewards(0, &validator_three));
|
||||
// and payout works
|
||||
assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_three, 0));
|
||||
// validator three has two pages of exposure, so pending rewards is still true.
|
||||
assert!(EraInfo::<Test>::pending_rewards(0, &validator_three));
|
||||
// payout again
|
||||
assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_three, 0));
|
||||
// now pending rewards is false.
|
||||
assert!(!EraInfo::<Test>::pending_rewards(0, &validator_three));
|
||||
// and payout fails
|
||||
assert_noop!(
|
||||
Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_three, 0),
|
||||
Error::<Test>::AlreadyClaimed.with_weight(err_weight)
|
||||
);
|
||||
|
||||
// for eras with no exposure, pending rewards is false.
|
||||
assert!(!EraInfo::<Test>::pending_rewards(0, &validator_one));
|
||||
assert!(!EraInfo::<Test>::pending_rewards(0, &validator_two));
|
||||
assert!(!EraInfo::<Test>::pending_rewards(0, &validator_three));
|
||||
});
|
||||
}
|
||||
|
||||
mod staking_interface {
|
||||
use frame_support::storage::with_storage_layer;
|
||||
use sp_staking::StakingInterface;
|
||||
|
||||
Reference in New Issue
Block a user