Cleanup staking info (#1532)

* Cleanup staking info

* Cache calls

* V1, V2 (Aura & Babe) tested & working

* Hopefully pacify docs build

* Second time lucky?

* Third time lucky?

* Fouth time
This commit is contained in:
Jaco Greeff
2019-10-30 14:15:20 +01:00
committed by GitHub
parent 2676909cd0
commit 8379689eaa
17 changed files with 232 additions and 102 deletions
+31
View File
@@ -0,0 +1,31 @@
// Copyright 2017-2019 @polkadot/api-derive authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { DerivedStaking } from '../types';
import { Observable, combineLatest } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { ApiInterfaceRx } from '@polkadot/api/types';
import { drr } from '../util/drr';
import { controllers } from './controllers';
import { info } from './info';
/**
* @description Retrieve all the staking info available for the chain
*/
export function all (api: ApiInterfaceRx): () => Observable<DerivedStaking[]> {
const infoCall = info(api);
const controllersCall = controllers(api);
return (): Observable<DerivedStaking[]> =>
controllersCall().pipe(
switchMap(([accountIds]): Observable<DerivedStaking[]> =>
combineLatest(
accountIds.map((accountId): Observable<DerivedStaking> => infoCall(accountId))
)
),
drr()
);
}
+1
View File
@@ -2,6 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
export * from './all';
export * from './controllers';
export * from './info';
export * from './recentlyOffline';
+121 -64
View File
@@ -8,7 +8,7 @@ import { Exposure, RewardDestination, StakingLedger, UnlockChunk, ValidatorPrefs
import { Codec } from '@polkadot/types/types';
import { ApiInterfaceRx } from '@polkadot/api/types';
import { DerivedStaking, DerivedUnlocking } from '../types';
import { DerivedRecentlyOffline, DerivedStaking, DerivedUnlocking } from '../types';
import BN from 'bn.js';
import { combineLatest, Observable, of } from 'rxjs';
@@ -22,7 +22,28 @@ import { eraLength } from '../session/eraLength';
import { recentlyOffline } from './recentlyOffline';
import { drr } from '../util/drr';
type MultiResult = [Option<AccountId> | Vec<[AccountId, Keys] & Codec>, Option<StakingLedger>, [AccountId[]], RewardDestination, Exposure, [ValidatorPrefs], Option<Keys>?];
interface Calls {
bestNumberCall (): Observable<BlockNumber>;
eraLengthCall (): Observable<BlockNumber>;
recentlyOfflineCall (): Observable<DerivedRecentlyOffline>;
}
interface ParseInput {
accountId: AccountId;
bestNumber: BlockNumber;
controllerId: AccountId;
eraLength: BlockNumber;
recentlyOffline?: DerivedRecentlyOffline;
nominators: AccountId[];
rewardDestination: RewardDestination;
queuedKeys?: [AccountId, Keys][];
stakers: Exposure;
stakingLedger: Option<StakingLedger>;
stashId: AccountId;
validatorPrefs: ValidatorPrefs;
nextKeys?: Option<Keys>;
nextKeyFor?: Option<AccountId>;
}
// groups the supplied chunks by era, i.e. { [era]: BN(total of values) }
function groupByEra (list: UnlockChunk[]): Record<string, BN> {
@@ -82,7 +103,7 @@ function redeemableSum (stakingLedger: StakingLedger | undefined, eraLength: BN,
}, new BN(0)));
}
function unwrapSessionIds (stashId: AccountId, queuedKeys: Option<AccountId> | Vec<[AccountId, Keys] & Codec>, nextKeys: Option<Keys>): { nextSessionIds: AccountId[]; nextSessionId?: AccountId; sessionIds: AccountId[]; sessionId?: AccountId } {
function unwrapSessionIds (stashId: AccountId, queuedKeys: Option<AccountId> | [AccountId, Keys][], nextKeys: Option<Keys>): { nextSessionIds: AccountId[]; nextSessionId?: AccountId; sessionIds: AccountId[]; sessionId?: AccountId } {
// for 2.x we have a Vec<(ValidatorId,Keys)> of the keys
if (Array.isArray(queuedKeys)) {
const sessionIds = (queuedKeys.find(([currentId]): boolean =>
@@ -111,80 +132,116 @@ function unwrapSessionIds (stashId: AccountId, queuedKeys: Option<AccountId> | V
};
}
function retrieveMulti (api: ApiInterfaceRx, stashId: AccountId, controllerId: AccountId): Observable<MultiResult> {
// A bit horrible, since we push below to forge everything into 1 query - we do this we we end up with one
// query to retrieve everything
const queries: Array<any> = [
api.query.session.queuedKeys
? [api.query.session.queuedKeys]
: [api.query.session.nextKeyFor, controllerId],
[api.query.staking.ledger, controllerId],
[api.query.staking.nominators, stashId],
[api.query.staking.payee, stashId],
[api.query.staking.stakers, stashId],
[api.query.staking.validators, stashId]
];
function parseResult ({ accountId, controllerId, stashId, eraLength, bestNumber, recentlyOffline = {}, nextKeyFor = createType('Option<AccountId>', null), queuedKeys, stakingLedger, nominators, rewardDestination, stakers, validatorPrefs, nextKeys = createType('Option<Keys>', null) }: ParseInput): DerivedStaking {
const _stakingLedger = stakingLedger.unwrapOr(undefined);
//
if (api.query.session.nextKeys) {
queries.push([api.query.session.nextKeys, [api.consts.session.dedupKeyPrefix, stashId]]);
}
return api.queryMulti(queries) as Observable<MultiResult>;
return {
accountId,
controllerId,
nominators,
offline: recentlyOffline[stashId.toString()],
redeemable: redeemableSum(_stakingLedger, eraLength, bestNumber),
rewardDestination,
stakers,
stakingLedger: _stakingLedger,
stashId,
unlocking: calculateUnlocking(_stakingLedger, eraLength, bestNumber),
validatorPrefs,
...unwrapSessionIds(stashId, queuedKeys || nextKeyFor, nextKeys)
};
}
function retrieveInfo (api: ApiInterfaceRx, accountId: AccountId, stashId: AccountId, controllerId: AccountId): Observable<DerivedStaking> {
return combineLatest([
eraLength(api)(),
bestNumber(api)(),
recentlyOffline(api)(),
retrieveMulti(api, stashId, controllerId)
]).pipe(
map(([eraLength, bestNumber, recentlyOffline, [queuedKeys, _stakingLedger, [nominators], rewardDestination, stakers, [validatorPrefs], nextKeys = createType('Option<Keys>', null)]]): DerivedStaking => {
const stakingLedger = _stakingLedger.unwrapOr(undefined);
type MultiResultV1 = [Option<AccountId>, Option<StakingLedger>, [Vec<AccountId>], RewardDestination, Exposure, [ValidatorPrefs] & Codec];
return {
accountId,
controllerId,
nominators,
offline: recentlyOffline[stashId.toString()],
redeemable: redeemableSum(stakingLedger, eraLength, bestNumber),
rewardDestination,
stakers,
stakingLedger,
stashId,
unlocking: calculateUnlocking(stakingLedger, eraLength, bestNumber),
validatorPrefs,
...unwrapSessionIds(stashId, queuedKeys, nextKeys)
};
function retrieveInfoV1 (api: ApiInterfaceRx, { bestNumberCall, eraLengthCall, recentlyOfflineCall }: Calls, accountId: AccountId, stashId: AccountId, controllerId: AccountId): Observable<DerivedStaking> {
return combineLatest([
bestNumberCall(),
eraLengthCall(),
recentlyOfflineCall(),
api.queryMulti([
[api.query.session.nextKeyFor, controllerId],
[api.query.staking.ledger, controllerId],
[api.query.staking.nominators, stashId],
[api.query.staking.payee, stashId],
[api.query.staking.stakers, stashId],
[api.query.staking.validators, stashId]
]) as Observable<MultiResultV1>
]).pipe(map(([
bestNumber, eraLength, recentlyOffline,
[nextKeyFor, stakingLedger, [nominators], rewardDestination, stakers, [validatorPrefs]]
]: [BlockNumber, BlockNumber, DerivedRecentlyOffline, MultiResultV1]): DerivedStaking =>
parseResult({
accountId, controllerId, stashId, eraLength, bestNumber, recentlyOffline, nextKeyFor, stakingLedger, nominators, rewardDestination, stakers, validatorPrefs
})
);
));
}
type MultiResultV2 = [Option<StakingLedger>, [Vec<AccountId>], RewardDestination, Exposure, [ValidatorPrefs], Option<Keys>];
function retrieveInfoV2 (api: ApiInterfaceRx, { bestNumberCall, eraLengthCall }: Calls, accountId: AccountId, stashId: AccountId, controllerId: AccountId): Observable<DerivedStaking> {
return combineLatest([
bestNumberCall(),
eraLengthCall(),
api.query.session.queuedKeys<Vec<[AccountId, Keys] & Codec>>(),
api.queryMulti([
[api.query.staking.ledger, controllerId],
[api.query.staking.nominators, stashId],
[api.query.staking.payee, stashId],
[api.query.staking.stakers, stashId],
[api.query.staking.validators, stashId],
[api.query.session.nextKeys, [api.consts.session.dedupKeyPrefix, stashId]]
]) as Observable<MultiResultV2>
]).pipe(map(([
bestNumber, eraLength, queuedKeys,
[stakingLedger, [nominators], rewardDestination, stakers, [validatorPrefs], nextKeys]
]: [BlockNumber, BlockNumber, [AccountId, Keys][], MultiResultV2]): DerivedStaking =>
parseResult({
accountId, controllerId, stashId, eraLength, bestNumber, queuedKeys, stakingLedger, nominators, rewardDestination, stakers, validatorPrefs, nextKeys
})
));
}
function retrieveV1 (api: ApiInterfaceRx, calls: Calls, controllerId: AccountId): Observable<DerivedStaking> {
return api.query.staking
.ledger<Option<StakingLedger>>(controllerId)
.pipe(
switchMap((stakingLedger): Observable<DerivedStaking> =>
stakingLedger.isSome
? retrieveInfoV1(api, calls, controllerId, stakingLedger.unwrap().stash, controllerId)
: of({ accountId: controllerId, nextSessionIds: [], sessionIds: [] })
),
drr()
);
}
function retrieveV2 (api: ApiInterfaceRx, calls: Calls, stashId: AccountId): Observable<DerivedStaking> {
return api.query.staking
.bonded<Option<AccountId>>(stashId)
.pipe(
switchMap((controllerId): Observable<DerivedStaking> =>
controllerId.isSome
? retrieveInfoV2(api, calls, stashId, stashId, controllerId.unwrap())
: of({ accountId: stashId, nextSessionIds: [], sessionIds: [] })
),
drr()
);
}
/**
* @description From a stash, retrieve the controllerId and fill in all the relevant staking details
*/
export function info (api: ApiInterfaceRx): (_accountId: Uint8Array | string) => Observable<DerivedStaking> {
const calls = {
eraLengthCall: eraLength(api),
bestNumberCall: bestNumber(api),
recentlyOfflineCall: recentlyOffline(api)
};
return (_accountId: Uint8Array | string): Observable<DerivedStaking> => {
const accountId = createType('AccountId', _accountId);
return (
// NOTE For 2.x-only support, only the first path is required, therefore we
// can replace this with `.bonded<Option<AccountId>>(accountId)` - in 2.x
// the session.validators return the stashes (as expected)
api.queryMulti<[Option<AccountId>, Option<StakingLedger>]>([
[api.query.staking.bonded, accountId], // try to map to controller
[api.query.staking.ledger, accountId] // try to map to stash (1.x only)
])
).pipe(
switchMap(([controllerId, stakingLedger]): Observable<DerivedStaking> =>
controllerId.isSome
? retrieveInfo(api, accountId, accountId, controllerId.unwrap())
: stakingLedger.isSome
? retrieveInfo(api, accountId, stakingLedger.unwrap().stash, accountId)
: of({ accountId, nextSessionIds: [], sessionIds: [] })
),
drr()
);
return api.consts.session
? retrieveV2(api, calls, accountId)
: retrieveV1(api, calls, accountId);
};
}