Skip to content

Commit 377ee29

Browse files
committed
staking payouts claimed field new logic
1 parent e11955a commit 377ee29

File tree

4 files changed

+92
-42
lines changed

4 files changed

+92
-42
lines changed

src/services/accounts/AccountsStakingPayoutsService.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ describe('AccountsStakingPayoutsService', () => {
9393
era: '1039',
9494
payouts: [
9595
{
96-
claimed: true,
96+
claimed: 'claimed',
9797
nominatorExposure: '0',
9898
nominatorStakingPayout: '1043968334900993560134832959396203124',
9999
totalValidatorExposure: '17302617747768368',
@@ -129,7 +129,7 @@ describe('AccountsStakingPayoutsService', () => {
129129
era: '1039',
130130
payouts: [
131131
{
132-
claimed: true,
132+
claimed: 'claimed',
133133
nominatorExposure: '21133134966048676',
134134
nominatorStakingPayout: '0',
135135
totalValidatorExposure: '21133134966048676',

src/services/accounts/AccountsStakingPayoutsService.ts

Lines changed: 76 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import { CalcPayout } from '@substrate/calc';
4848
import { BadRequest } from 'http-errors';
4949

5050
import type { IAccountStakingPayouts, IEraPayouts, IPayout } from '../../types/responses';
51+
import { IStatus, IStatusPerEra } from '../../types/responses/AccountStakingPayouts';
5152
import { AbstractService } from '../AbstractService';
5253
import kusamaEarlyErasBlockInfo from './kusamaEarlyErasBlockInfo.json';
5354

@@ -82,11 +83,12 @@ interface IAdjustedDeriveEraExposure extends DeriveEraExposure {
8283
}
8384

8485
/**
85-
* Commission and staking ledger of a validator
86+
* Commission, staking ledger & claimed information of a validator
8687
*/
87-
interface ICommissionAndLedger {
88+
interface ICommissionLedgerAndClaimed {
8889
commission: Perbill;
8990
validatorLedger?: PalletStakingStakingLedger;
91+
claimedRewards?: IStatusPerEra;
9092
}
9193

9294
/**
@@ -96,7 +98,7 @@ interface IEraData {
9698
deriveEraExposure: IAdjustedDeriveEraExposure;
9799
eraRewardPoints: PalletStakingEraRewardPoints | EraPoints;
98100
erasValidatorRewardOption: Option<BalanceOf>;
99-
exposuresWithCommission?: (ICommissionAndLedger & {
101+
exposuresWithCommission?: (ICommissionLedgerAndClaimed & {
100102
validatorId: string;
101103
nominatorIndex: number;
102104
})[];
@@ -264,7 +266,7 @@ export class AccountsStakingPayoutsService extends AbstractService {
264266
blockNumber: IBlockInfo,
265267
isKusama: boolean,
266268
): Promise<IErasGeneral[]> {
267-
const allDeriveQuerys: Promise<IErasGeneral>[] = [];
269+
const allDeriveQueries: Promise<IErasGeneral>[] = [];
268270
let nextEraStartBlock: number = Number(blockNumber.height);
269271
let eraDurationInBlocks: number = 0;
270272
const earlyErasBlockInfo: IEarlyErasBlockInfo = kusamaEarlyErasBlockInfo;
@@ -277,7 +279,7 @@ export class AccountsStakingPayoutsService extends AbstractService {
277279
historicApi.query.staking.erasRewardPoints(eraIndex),
278280
historicApi.query.staking.erasValidatorReward(eraIndex),
279281
]);
280-
allDeriveQuerys.push(eraGeneralTuple);
282+
allDeriveQueries.push(eraGeneralTuple);
281283
} else {
282284
// We check if we are in the Kusama chain since currently we have
283285
// the block info for the early eras only for Kusama.
@@ -290,6 +292,7 @@ export class AccountsStakingPayoutsService extends AbstractService {
290292
const epochDuration = historicApi.consts.babe.epochDuration.toNumber();
291293
eraDurationInBlocks = sessionDuration * epochDuration;
292294
}
295+
293296
const nextEraStartBlockHash: BlockHash = await this.api.rpc.chain.getBlockHash(nextEraStartBlock);
294297
const currentEraEndBlockHash: BlockHash =
295298
era === 0
@@ -323,10 +326,10 @@ export class AccountsStakingPayoutsService extends AbstractService {
323326

324327
const eraGeneralTuple = Promise.all([this.deriveEraExposure(historicApi, eraIndex), points, rewardPromise]);
325328

326-
allDeriveQuerys.push(eraGeneralTuple);
329+
allDeriveQueries.push(eraGeneralTuple);
327330
}
328331
}
329-
return Promise.all(allDeriveQuerys);
332+
return Promise.all(allDeriveQueries);
330333
}
331334

332335
private async fetchHistoricRewardPoints(hash: BlockHash): Promise<EraPoints> {
@@ -348,7 +351,7 @@ export class AccountsStakingPayoutsService extends AbstractService {
348351
startEra: number,
349352
deriveErasExposures: IAdjustedDeriveEraExposure[],
350353
isKusama: boolean,
351-
): Promise<ICommissionAndLedger[][]> {
354+
): Promise<ICommissionLedgerAndClaimed[][]> {
352355
// Cache StakingLedger to reduce redundant queries to node
353356
const validatorLedgerCache: { [id: string]: PalletStakingStakingLedger } = {};
354357

@@ -362,7 +365,7 @@ export class AccountsStakingPayoutsService extends AbstractService {
362365
}
363366

364367
const singleEraCommissions = nominatedExposures.map(({ validatorId }) =>
365-
this.fetchCommissionAndLedger(historicApi, validatorId, currEra, validatorLedgerCache, isKusama),
368+
this.fetchCommissionLedgerAndClaimed(historicApi, validatorId, currEra, validatorLedgerCache, isKusama),
366369
);
367370

368371
return Promise.all(singleEraCommissions);
@@ -408,6 +411,7 @@ export class AccountsStakingPayoutsService extends AbstractService {
408411
commission: validatorCommission,
409412
validatorLedger,
410413
nominatorIndex,
414+
claimedRewards,
411415
} of exposuresWithCommission) {
412416
const totalValidatorRewardPoints = deriveEraExposure.validatorIndex
413417
? this.extractTotalValidatorRewardPoints(eraRewardPoints, validatorId, deriveEraExposure.validatorIndex)
@@ -432,32 +436,16 @@ export class AccountsStakingPayoutsService extends AbstractService {
432436
continue;
433437
}
434438

435-
/**
436-
* Check if the reward has already been claimed.
437-
*
438-
* It is important to note that the following examines types that are both current and historic.
439-
* When going back far enough in certain chains types such as `StakingLedgerTo240` are necessary for grabbing
440-
* any reward data.
441-
*/
442-
let indexOfEra: number;
443-
if (validatorLedger.legacyClaimedRewards) {
444-
indexOfEra = validatorLedger.legacyClaimedRewards.indexOf(eraIndex);
445-
} else if ((validatorLedger as unknown as StakingLedger).claimedRewards) {
446-
indexOfEra = (validatorLedger as unknown as StakingLedger).claimedRewards.indexOf(eraIndex);
447-
} else if ((validatorLedger as unknown as StakingLedgerTo240).lastReward) {
448-
const lastReward = (validatorLedger as unknown as StakingLedgerTo240).lastReward;
449-
if (lastReward.isSome) {
450-
indexOfEra = lastReward.unwrap().toNumber();
451-
} else {
452-
indexOfEra = -1;
453-
}
439+
// Setting the value of `claimed` based on `claimedRewards`
440+
let claimed;
441+
if (claimedRewards && claimedRewards[eraIndex.toNumber()]) {
442+
claimed = claimedRewards[eraIndex.toNumber()];
454443
} else if (eraIndex.toNumber() < 518 && isKusama) {
455-
indexOfEra = eraIndex.toNumber();
444+
claimed = IStatus.claimed;
456445
} else {
457-
continue;
446+
claimed = IStatus.undefined;
458447
}
459-
const claimed: boolean = Number.isInteger(indexOfEra) && indexOfEra !== -1;
460-
if (unclaimedOnly && claimed) {
448+
if (unclaimedOnly && claimed === IStatus.claimed) {
461449
continue;
462450
}
463451

@@ -496,17 +484,18 @@ export class AccountsStakingPayoutsService extends AbstractService {
496484
* @param era the era to query
497485
* @param validatorLedgerCache object mapping validatorId => StakingLedger to limit redundant queries
498486
*/
499-
private async fetchCommissionAndLedger(
487+
private async fetchCommissionLedgerAndClaimed(
500488
historicApi: ApiDecoration<'promise'>,
501489
validatorId: string,
502490
era: number,
503491
validatorLedgerCache: { [id: string]: PalletStakingStakingLedger },
504492
isKusama: boolean,
505-
): Promise<ICommissionAndLedger> {
493+
): Promise<ICommissionLedgerAndClaimed> {
506494
let commission: Perbill;
507495
let validatorLedger;
508496
let commissionPromise;
509497
const ancient: boolean = era < 518;
498+
const claimedRewards: IStatusPerEra = {};
510499
if (validatorId in validatorLedgerCache) {
511500
validatorLedger = validatorLedgerCache[validatorId];
512501
let prefs: PalletStakingValidatorPrefs | ValidatorPrefsWithCommission;
@@ -544,13 +533,62 @@ export class AccountsStakingPayoutsService extends AbstractService {
544533
return {
545534
commission,
546535
};
536+
} else {
537+
validatorLedger = validatorLedgerOption.unwrap();
538+
/**
539+
* Check if the reward has already been claimed.
540+
*
541+
* It is important to note that the following examines types that are both current and historic.
542+
* When going back far enough in certain chains types such as `StakingLedgerTo240` are necessary for grabbing
543+
* any reward data.
544+
*/
545+
let claimedRewardsEras: u32[] = [];
546+
if ((validatorLedger as unknown as StakingLedgerTo240)?.lastReward) {
547+
const lastReward = (validatorLedger as unknown as StakingLedgerTo240).lastReward;
548+
if (lastReward.isSome) {
549+
const e = (validatorLedger as unknown as StakingLedgerTo240)?.lastReward?.unwrap().toNumber();
550+
if (e) {
551+
claimedRewards[e] = IStatus.claimed;
552+
}
553+
}
554+
}
555+
if (validatorLedger?.legacyClaimedRewards) {
556+
claimedRewardsEras = validatorLedger?.legacyClaimedRewards;
557+
} else {
558+
claimedRewardsEras = (validatorLedger as unknown as StakingLedger)?.claimedRewards as Vec<u32>;
559+
}
560+
if (claimedRewardsEras) {
561+
claimedRewardsEras.forEach((era) => {
562+
claimedRewards[era.toNumber()] = IStatus.claimed;
563+
});
564+
}
565+
if (historicApi.query.staking?.claimedRewards) {
566+
const claimedRewardsPerEra = await historicApi.query.staking.claimedRewards(era, validatorId);
567+
const erasStakersOverview = await historicApi.query.staking.erasStakersOverview(era, validatorId);
568+
let erasStakers = null;
569+
if (historicApi.query.staking?.erasStakers) {
570+
erasStakers = await historicApi.query.staking.erasStakers(era, validatorId);
571+
}
572+
if (erasStakersOverview.isSome) {
573+
const pageCount = erasStakersOverview.unwrap().pageCount.toNumber();
574+
const eraStatus =
575+
claimedRewardsPerEra.length === 0
576+
? IStatus.unclaimed
577+
: claimedRewardsPerEra.length === pageCount
578+
? IStatus.claimed
579+
: IStatus.partiallyClaimed;
580+
claimedRewards[era] = eraStatus;
581+
} else if (erasStakers && erasStakers.total.toBigInt() > 0) {
582+
// if erasStakers.total > 0, then the pageCount is always 1
583+
// https://github.com/polkadot-js/api/issues/5859#issuecomment-2077011825
584+
const eraStatus = claimedRewardsPerEra.length === 1 ? IStatus.claimed : IStatus.unclaimed;
585+
claimedRewards[era] = eraStatus;
586+
}
587+
}
547588
}
548-
549-
validatorLedger = validatorLedgerOption.unwrap();
550-
validatorLedgerCache[validatorId] = validatorLedger;
551589
}
552590

553-
return { commission, validatorLedger };
591+
return { commission, validatorLedger, claimedRewards };
554592
}
555593

556594
/**
@@ -576,7 +614,6 @@ export class AccountsStakingPayoutsService extends AbstractService {
576614
const nominators: DeriveEraNominatorExposure = {};
577615
const validators: DeriveEraValidatorExposure = {};
578616
const validatorsOverview: Record<string, Option<SpStakingPagedExposureMetadata>> = {};
579-
580617
stakers.forEach(([key, exposure]): void => {
581618
const validatorId = key.args[1].toString();
582619

src/types/responses/AccountStakingPayouts.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@
1616

1717
import { IAt, IEraPayouts } from '.';
1818

19+
export enum IStatus {
20+
claimed = 'claimed',
21+
partiallyClaimed = 'partially claimed',
22+
unclaimed = 'unclaimed',
23+
undefined = 'undefined',
24+
}
25+
26+
export interface IStatusPerEra {
27+
[era: number]: IStatus;
28+
}
29+
1930
export interface IAccountStakingPayouts {
2031
at: IAt;
2132
erasPayouts: (IEraPayouts | { message: string })[];

src/types/responses/Payout.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616

1717
import { Balance, Perbill, RewardPoint } from '@polkadot/types/interfaces';
1818

19+
import { IStatus } from './AccountStakingPayouts';
20+
1921
export interface IPayout {
2022
validatorId: string;
2123
nominatorStakingPayout: string;
22-
claimed: boolean;
24+
claimed: IStatus;
2325
validatorCommission: Perbill;
2426
totalValidatorRewardPoints: RewardPoint;
2527
totalValidatorExposure: Balance;

0 commit comments

Comments
 (0)