Skip to content

Commit

Permalink
ApiDerive.staking backwards compatibility. (#5868)
Browse files Browse the repository at this point in the history
* Start of backwards compatibility for api.derive.staking

* Update nextElected to be compatible

* lint

* Comments

* remove clipped from query

* Ensure backwards compat for ownExposure

* Fix query
  • Loading branch information
TarikGul authored May 6, 2024
1 parent e0e9829 commit 15c8859
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 17 deletions.
24 changes: 19 additions & 5 deletions packages/api-derive/src/staking/ownExposure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import type { Observable } from 'rxjs';
import type { Option, u32 } from '@polkadot/types';
import type { EraIndex } from '@polkadot/types/interfaces';
import type { SpStakingExposurePage } from '@polkadot/types/lookup';
import type { SpStakingExposure, SpStakingExposurePage, SpStakingPagedExposureMetadata } from '@polkadot/types/lookup';
import type { AnyNumber } from '@polkadot/types-codec/types';
import type { DeriveApi, DeriveOwnExposure } from '../types.js';

Expand All @@ -15,13 +15,27 @@ import { erasHistoricApplyAccount } from './util.js';

export function _ownExposures (instanceId: string, api: DeriveApi): (accountId: Uint8Array | string, eras: EraIndex[], withActive: boolean, page: u32 | AnyNumber) => Observable<DeriveOwnExposure[]> {
return memo(instanceId, (accountId: Uint8Array | string, eras: EraIndex[], _withActive: boolean, page: u32 | AnyNumber): Observable<DeriveOwnExposure[]> => {
const emptyStakingExposure = api.registry.createType<SpStakingExposure>('Exposure');
// The reason we don't explicitly make the actual types is for compatibility. If the chain doesn't have the noted type it will fail
// on construction. Therefore we just make an empty option.
const emptyOptionPage = api.registry.createType<Option<SpStakingExposurePage>>('Option<Null>');
const emptyOptionMeta = api.registry.createType<Option<SpStakingPagedExposureMetadata>>('Option<Null>');

return eras.length
? combineLatest([
// Backwards and forward compat for historical integrity when using `erasHistoricApplyAccount`
combineLatest(eras.map((e) => api.query.staking.erasStakersClipped(e, accountId))),
combineLatest(eras.map((e) => api.query.staking.erasStakers(e, accountId))),
combineLatest(eras.map((e) => api.query.staking.erasStakersPaged<Option<SpStakingExposurePage>>(e, accountId, page))),
combineLatest(eras.map((e) => api.query.staking.erasStakersOverview(e, accountId)))
api.query.staking.erasStakersClipped
? combineLatest(eras.map((e) => api.query.staking.erasStakersClipped(e, accountId)))
: of(eras.map((_) => emptyStakingExposure)),
api.query.staking.erasStakers
? combineLatest(eras.map((e) => api.query.staking.erasStakers(e, accountId)))
: of(eras.map((_) => emptyStakingExposure)),
api.query.staking.erasStakersPaged
? combineLatest(eras.map((e) => api.query.staking.erasStakersPaged<Option<SpStakingExposurePage>>(e, accountId, page)))
: of(eras.map((_) => emptyOptionPage)),
api.query.staking.erasStakersOverview
? combineLatest(eras.map((e) => api.query.staking.erasStakersOverview(e, accountId)))
: of(eras.map((_) => emptyOptionMeta))
]).pipe(
map(([clp, exp, paged, expMeta]): DeriveOwnExposure[] =>
eras.map((era, index) => ({ clipped: clp[index], era, exposure: exp[index], exposureMeta: expMeta[index], exposurePaged: paged[index] }))
Expand Down
29 changes: 18 additions & 11 deletions packages/api-derive/src/staking/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import type { Observable } from 'rxjs';
import type { Option, u32, Vec } from '@polkadot/types';
import type { AccountId, EraIndex } from '@polkadot/types/interfaces';
import type { PalletStakingNominations, PalletStakingRewardDestination, PalletStakingStakingLedger, PalletStakingValidatorPrefs, SpStakingExposurePage, SpStakingPagedExposureMetadata } from '@polkadot/types/lookup';
import type { PalletStakingNominations, PalletStakingRewardDestination, PalletStakingStakingLedger, PalletStakingValidatorPrefs, SpStakingExposure, SpStakingExposurePage, SpStakingPagedExposureMetadata } from '@polkadot/types/lookup';
import type { AnyNumber } from '@polkadot/types-codec/types';
import type { DeriveApi, DeriveStakingQuery, StakingQueryFlags } from '../types.js';

Expand All @@ -24,11 +24,12 @@ function filterClaimedRewards (api: DeriveApi, cl: number[]): Vec<u32> {
return api.registry.createType('Vec<u32>', cl.filter((c) => c !== -1));
}

function parseDetails (api: DeriveApi, stashId: AccountId, controllerIdOpt: Option<AccountId> | null, nominatorsOpt: Option<PalletStakingNominations>, rewardDestinationOpts: Option<PalletStakingRewardDestination> | PalletStakingRewardDestination, validatorPrefs: PalletStakingValidatorPrefs, exposure: Option<SpStakingExposurePage>, stakingLedgerOpt: Option<PalletStakingStakingLedger>, exposureMeta: Option<SpStakingPagedExposureMetadata>, claimedRewards: number[]): DeriveStakingQuery {
function parseDetails (api: DeriveApi, stashId: AccountId, controllerIdOpt: Option<AccountId> | null, nominatorsOpt: Option<PalletStakingNominations>, rewardDestinationOpts: Option<PalletStakingRewardDestination> | PalletStakingRewardDestination, validatorPrefs: PalletStakingValidatorPrefs, exposure: Option<SpStakingExposurePage>, stakingLedgerOpt: Option<PalletStakingStakingLedger>, exposureMeta: Option<SpStakingPagedExposureMetadata>, claimedRewards: number[], exposureEraStakers: SpStakingExposure): DeriveStakingQuery {
return {
accountId: stashId,
claimedRewardsEras: filterClaimedRewards(api, claimedRewards),
controllerId: controllerIdOpt?.unwrapOr(null) || null,
exposureEraStakers,
exposureMeta,
exposurePaged: exposure,
nominators: nominatorsOpt.isSome
Expand Down Expand Up @@ -64,12 +65,15 @@ function getLedgers (api: DeriveApi, optIds: (Option<AccountId> | null)[], { wit
);
}

function getStashInfo (api: DeriveApi, stashIds: AccountId[], activeEra: EraIndex, { withClaimedRewardsEras, withController, withDestination, withExposure, withExposureMeta, withLedger, withNominations, withPrefs }: StakingQueryFlags, page: u32 | AnyNumber): Observable<[(Option<AccountId> | null)[], Option<PalletStakingNominations>[], Option<PalletStakingRewardDestination>[], PalletStakingValidatorPrefs[], Option<SpStakingExposurePage>[], Option<SpStakingPagedExposureMetadata>[], number[][]]> {
function getStashInfo (api: DeriveApi, stashIds: AccountId[], activeEra: EraIndex, { withClaimedRewardsEras, withController, withDestination, withExposure, withExposureErasStakersLegacy, withExposureMeta, withLedger, withNominations, withPrefs }: StakingQueryFlags, page: u32 | AnyNumber): Observable<[(Option<AccountId> | null)[], Option<PalletStakingNominations>[], Option<PalletStakingRewardDestination>[], PalletStakingValidatorPrefs[], Option<SpStakingExposurePage>[], Option<SpStakingPagedExposureMetadata>[], number[][], SpStakingExposure[]]> {
const emptyNoms = api.registry.createType<Option<PalletStakingNominations>>('Option<Nominations>');
const emptyRewa = api.registry.createType<Option<PalletStakingRewardDestination>>('RewardDestination');
const emptyExpo = api.registry.createType<Option<SpStakingExposurePage>>('Option<SpStakingExposurePage>');
const emptyExpoEraStakers = api.registry.createType<SpStakingExposure>('Exposure');
const emptyPrefs = api.registry.createType<PalletStakingValidatorPrefs>('ValidatorPrefs');
const emptyExpoMeta = api.registry.createType<Option<SpStakingPagedExposureMetadata>>('Option<SpStakingPagedExposureMetadata>');
// The reason we don't explicitly make the actual types is for compatibility. If the chain doesn't have the noted type it will fail
// on construction. Therefore we just make an empty option.
const emptyExpo = api.registry.createType<Option<SpStakingExposurePage>>('Option<Null>');
const emptyExpoMeta = api.registry.createType<Option<SpStakingPagedExposureMetadata>>('Option<Null>');
const emptyClaimedRewards = [-1];

const depth = Number(api.consts.staking.historyDepth.toNumber());
Expand All @@ -94,13 +98,13 @@ function getStashInfo (api: DeriveApi, stashIds: AccountId[], activeEra: EraInde
withPrefs
? combineLatest(stashIds.map((s) => api.query.staking.validators(s)))
: of(stashIds.map(() => emptyPrefs)),
withExposure
withExposure && api.query.staking.erasStakersPaged
? combineLatest(stashIds.map((s) => api.query.staking.erasStakersPaged<Option<SpStakingExposurePage>>(activeEra, s, page)))
: of(stashIds.map(() => emptyExpo)),
withExposureMeta
withExposureMeta && api.query.staking.erasStakersOverview
? combineLatest(stashIds.map((s) => api.query.staking.erasStakersOverview(activeEra, s)))
: of(stashIds.map(() => emptyExpoMeta)),
withClaimedRewardsEras
withClaimedRewardsEras && api.query.staking.claimedRewards
? combineLatest(stashIds.map((s) =>
combineLatest([
combineLatest(eras.map((e) => api.query.staking.claimedRewards(e, s))),
Expand All @@ -122,17 +126,20 @@ function getStashInfo (api: DeriveApi, stashIds: AccountId[], activeEra: EraInde
});
})
)
: of(stashIds.map(() => emptyClaimedRewards))
: of(stashIds.map(() => emptyClaimedRewards)),
withExposureErasStakersLegacy && api.query.staking.erasStakers
? combineLatest(stashIds.map((s) => api.query.staking.erasStakers(activeEra, s)))
: of(stashIds.map(() => emptyExpoEraStakers))
]);
}

function getBatch (api: DeriveApi, activeEra: EraIndex, stashIds: AccountId[], flags: StakingQueryFlags, page: u32 | AnyNumber): Observable<DeriveStakingQuery[]> {
return getStashInfo(api, stashIds, activeEra, flags, page).pipe(
switchMap(([controllerIdOpt, nominatorsOpt, rewardDestination, validatorPrefs, exposure, exposureMeta, claimedRewardsEras]): Observable<DeriveStakingQuery[]> =>
switchMap(([controllerIdOpt, nominatorsOpt, rewardDestination, validatorPrefs, exposure, exposureMeta, claimedRewardsEras, exposureEraStakers]): Observable<DeriveStakingQuery[]> =>
getLedgers(api, controllerIdOpt, flags).pipe(
map((stakingLedgerOpts) =>
stashIds.map((stashId, index) =>
parseDetails(api, stashId, controllerIdOpt[index], nominatorsOpt[index], rewardDestination[index], validatorPrefs[index], exposure[index], stakingLedgerOpts[index], exposureMeta[index], claimedRewardsEras[index])
parseDetails(api, stashId, controllerIdOpt[index], nominatorsOpt[index], rewardDestination[index], validatorPrefs[index], exposure[index], stakingLedgerOpts[index], exposureMeta[index], claimedRewardsEras[index], exposureEraStakers[index])
)
)
)
Expand Down
3 changes: 3 additions & 0 deletions packages/api-derive/src/staking/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ export interface DeriveStakingValidators {

export interface DeriveStakingStash {
controllerId: AccountId | null;
// Legacy Support for erasStakers
exposureEraStakers: SpStakingExposure;
exposurePaged: Option<SpStakingExposurePage>;
exposureMeta: Option<SpStakingPagedExposureMetadata>;
nominators: AccountId[];
Expand Down Expand Up @@ -162,6 +164,7 @@ export interface StakingQueryFlags {
withController?: boolean;
withDestination?: boolean;
withExposure?: boolean;
withExposureErasStakersLegacy?: boolean,
withLedger?: boolean;
withNominations?: boolean;
withPrefs?: boolean;
Expand Down
10 changes: 9 additions & 1 deletion packages/api-derive/src/staking/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,22 @@ import { memo } from '../util/index.js';

export function nextElected (instanceId: string, api: DeriveApi): () => Observable<AccountId[]> {
return memo(instanceId, (): Observable<AccountId[]> =>
// Compatibility for future generation changes in staking.
api.query.staking.erasStakersPaged
? api.derive.session.indexes().pipe(
// only populate for next era in the last session, so track both here - entries are not
// subscriptions, so we need a trigger - currentIndex acts as that trigger to refresh
switchMap(({ currentEra }) => api.query.staking.erasStakersPaged.keys(currentEra)),
map((keys) => keys.map(({ args: [, accountId] }) => accountId))
)
: api.query.staking['currentElected']<AccountId[]>()
: api.query.staking.erasStakers
? api.derive.session.indexes().pipe(
// only populate for next era in the last session, so track both here - entries are not
// subscriptions, so we need a trigger - currentIndex acts as that trigger to refresh
switchMap(({ currentEra }) => api.query.staking.erasStakers.keys(currentEra)),
map((keys) => keys.map(({ args: [, accountId] }) => accountId))
)
: api.query.staking['currentElected']<AccountId[]>()
);
}

Expand Down

0 comments on commit 15c8859

Please sign in to comment.