diff --git a/packages/api-derive/src/staking/erasExposure.ts b/packages/api-derive/src/staking/erasExposure.ts index 9eec8e98fb..77be9619a5 100644 --- a/packages/api-derive/src/staking/erasExposure.ts +++ b/packages/api-derive/src/staking/erasExposure.ts @@ -3,53 +3,51 @@ // of the Apache-2.0 license. See the LICENSE file for details. import { ApiInterfaceRx } from '@polkadot/api/types'; -import { EraIndex, Exposure } from '@polkadot/types/interfaces'; -import { DeriveEraExposure } from '../types'; +import { AccountId, EraIndex, Exposure } from '@polkadot/types/interfaces'; +import { DeriveEraExposure, DeriveEraPointsAll } from '../types'; import { Observable, combineLatest, of } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; +import { StorageKey } from '@polkadot/types'; import { memo } from '../util'; -function queryClipped (api: ApiInterfaceRx): Observable<[[EraIndex, string][], Exposure[]]> { +function queryClipped (api: ApiInterfaceRx): Observable<[EraIndex, [AccountId, Exposure][]][]> { return api.derive.staking.erasPoints().pipe( - switchMap((allPoints): Observable<[[EraIndex, string][], Exposure[]]> => { - const indexes: [EraIndex, string][] = []; - - allPoints.forEach(({ all, era }): void => { - Object.keys(all).forEach((validatorId): void => { - indexes.push([era, validatorId]); - }); - }); - - // FIXME use prefix queries, get going first - return combineLatest([ - of(indexes), - indexes.length - ? api.query.staking.erasStakersClipped.multi(indexes) - : of([] as Exposure[]) - ]); - }) + switchMap((allPoints): Observable<[DeriveEraPointsAll[], [StorageKey, Exposure][][]]> => + combineLatest([ + of(allPoints), + combineLatest( + allPoints.map(({ era }): Observable<[StorageKey, Exposure][]> => + api.query.staking.erasStakersClipped.entries(era) + ) + ) + ]) + ), + map(([allPoints, erasStakers]): [EraIndex, [AccountId, Exposure][]][] => + allPoints.map(({ era }, index): [EraIndex, [AccountId, Exposure][]] => [ + era, + erasStakers[index].map(([key, exposure]): [AccountId, Exposure] => [ + api.registry.createType('AccountId', key.toU8a().slice(-32)), + exposure + ]) + ]) + ) ); } export function erasExposure (api: ApiInterfaceRx): () => Observable { return memo((): Observable => queryClipped(api).pipe( - map(([indexes, exposures]): DeriveEraExposure[] => - indexes.reduce((result: DeriveEraExposure[], [era, validatorId], index): DeriveEraExposure[] => { - let entry = result.find((entry): boolean => entry.era.eq(era)); + map((clipped): DeriveEraExposure[] => + clipped.map(([era, validators]): DeriveEraExposure => ({ + all: validators.reduce((all: Record, [validtorId, exposure]): Record => { + all[validtorId.toString()] = exposure; - if (!entry) { - entry = { all: {}, era }; - - result.push(entry); - } - - entry.all[validatorId] = exposures[index]; - - return result; - }, []) + return all; + }, {}), + era + })) ) ) ); diff --git a/packages/api-derive/src/staking/validators.ts b/packages/api-derive/src/staking/validators.ts index 2801ef0a88..5aa1cf68dc 100644 --- a/packages/api-derive/src/staking/validators.ts +++ b/packages/api-derive/src/staking/validators.ts @@ -3,14 +3,31 @@ // of the Apache-2.0 license. See the LICENSE file for details. import { ApiInterfaceRx } from '@polkadot/api/types'; -import { AccountId } from '@polkadot/types/interfaces'; +import { AccountId, Exposure } from '@polkadot/types/interfaces'; import { DeriveStakingValidators } from '../types'; import { Observable, combineLatest, of } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { map, switchMap } from 'rxjs/operators'; +import { StorageKey } from '@polkadot/types'; import { memo } from '../util'; +function queryNextStakers (api: ApiInterfaceRx): Observable { + // 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 + return api.derive.session.indexes().pipe( + switchMap(({ activeEra }): Observable<[StorageKey, Exposure][]> => + api.query.staking.erasStakers.entries(activeEra.addn(1)) + ), + map((entries): AccountId[] => + // key is from concat with AccountId bytes right at the end + entries.map(([key]): AccountId => + api.registry.createType('AccountId', key.toU8a().slice(-32)) + ) + ) + ); +} + /** * @description Retrieve latest list of validators */ @@ -23,15 +40,16 @@ export function validators (api: ApiInterfaceRx): () => Observable() : of([]) ]).pipe( map(([validators, nextElected]): DeriveStakingValidators => ({ - nextElected: nextElected || validators, + nextElected: nextElected.length + ? nextElected + : validators, validators })) )); diff --git a/packages/api/src/base/Decorate.ts b/packages/api/src/base/Decorate.ts index cb0d030005..490c84d15b 100644 --- a/packages/api/src/base/Decorate.ts +++ b/packages/api/src/base/Decorate.ts @@ -432,13 +432,13 @@ export default abstract class Decorate extends Events private retrieveMapEntries (creator: StorageEntry, arg?: Arg): Observable<[StorageKey, Codec][]> { assert(creator.iterKey && (creator.meta.type.isMap || creator.meta.type.isDoubleMap), 'entries can only be retrieved on maps, linked maps and double maps'); - const outputType = creator.meta.type.isMap + const outputType: any = creator.meta.type.isMap ? creator.meta.type.asMap.value.toString() : creator.meta.type.asDoubleMap.value.toString(); return this._rpcCore.state - // FIXME This should really be some sort of subscription, so we can get stuff as - // it changes (as of now it is a one-shot query). Not sure how to do this though... + // TODO This should really be some sort of subscription, so we can get stuff as + // it changes (as of now it is a one-shot query). Not available on Substrate. .getKeys( this.createType('Raw', u8aConcat( creator.iterKey, @@ -453,14 +453,14 @@ export default abstract class Decorate extends Events switchMap((keys): Observable<[StorageKey[], Vec]> => combineLatest([ of(keys), - // Not 100% sure about this, surely this is just a vec? - this._rpcCore.state.subscribeStorage>( - keys.map((key) => key.setOutputType(outputType)) - ) + this._rpcCore.state.subscribeStorage>(keys) ]) ), map(([keys, values]): [StorageKey, Codec][] => - keys.map((key, index): [StorageKey, Codec] => [key, values[index]]) + keys.map((key, index): [StorageKey, Codec] => [ + key, + this.createType(outputType, values[index]) + ]) ) ); } diff --git a/packages/types/src/primitive/StorageKey.ts b/packages/types/src/primitive/StorageKey.ts index cc3002221b..e5737f024a 100644 --- a/packages/types/src/primitive/StorageKey.ts +++ b/packages/types/src/primitive/StorageKey.ts @@ -49,6 +49,39 @@ export function unwrapStorageType (type: StorageEntryTypeLatest): keyof Interfac return map.value.toString() as keyof InterfaceTypes; } +/** @internal */ +function decodeStorageKey (value?: AnyU8a | StorageKey | StorageEntry | [StorageEntry, any]): Decoded { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + if (value instanceof StorageKey) { + return { + key: value, + method: value.method, + section: value.section + }; + } else if (!value || isString(value) || isU8a(value)) { + // let Bytes handle these inputs + return { key: value }; + } else if (isFunction(value)) { + return { + key: value(), + method: value.method, + section: value.section + }; + } else if (Array.isArray(value)) { + const [fn, ...arg]: [StorageEntry, ...any[]] = value as any; + + assert(isFunction(fn), 'Expected function input for key construction'); + + return { + key: fn(...arg), + method: fn.method, + section: fn.section + }; + } + + throw new Error(`Unable to convert input ${value} to StorageKey`); +} + /** * @name StorageKey * @description @@ -58,14 +91,14 @@ export function unwrapStorageType (type: StorageEntryTypeLatest): keyof Interfac export default class StorageKey extends Bytes { private _meta?: StorageEntryMetadataLatest; - private _method?: string; + private readonly _method?: string; - private _outputType?: string; + private readonly _outputType: string; - private _section?: string; + private readonly _section?: string; constructor (registry: Registry, value?: AnyU8a | StorageKey | StorageEntry | [StorageEntry, any], override: Partial = {}) { - const { key, method, section } = StorageKey.decodeStorageKey(value); + const { key, method, section } = decodeStorageKey(value); super(registry, key); @@ -75,38 +108,6 @@ export default class StorageKey extends Bytes { this._section = override.section || section; } - /** @internal */ - public static decodeStorageKey (value?: AnyU8a | StorageKey | StorageEntry | [StorageEntry, any]): Decoded { - if (value instanceof StorageKey) { - return { - key: value, - method: value.method, - section: value.section - }; - } else if (!value || isString(value) || isU8a(value)) { - // let Bytes handle these inputs - return { key: value }; - } else if (isFunction(value)) { - return { - key: value(), - method: value.method, - section: value.section - }; - } else if (Array.isArray(value)) { - const [fn, ...arg]: [StorageEntry, ...any[]] = value as any; - - assert(isFunction(fn), 'Expected function input for key construction'); - - return { - key: fn(...arg), - method: fn.method, - section: fn.section - }; - } - - throw new Error(`Unable to convert input ${value} to StorageKey`); - } - public static getMeta (value: StorageKey | StorageEntry | [StorageEntry, any]): StorageEntryMetadataLatest | undefined { if (value instanceof StorageKey) { return value.meta; @@ -121,7 +122,7 @@ export default class StorageKey extends Bytes { return undefined; } - public static getType (value: StorageKey | StorageEntry | [StorageEntry, any]): string | undefined { + public static getType (value: StorageKey | StorageEntry | [StorageEntry, any]): string { if (value instanceof StorageKey) { return value.outputType; } else if (isFunction(value)) { @@ -134,7 +135,8 @@ export default class StorageKey extends Bytes { } } - return undefined; + // If we have no type set, default to Raw + return 'Raw'; } /** @@ -152,9 +154,9 @@ export default class StorageKey extends Bytes { } /** - * @description The output type, `null` when not available + * @description The output type */ - public get outputType (): string | undefined { + public get outputType (): string { return this._outputType; } @@ -173,13 +175,4 @@ export default class StorageKey extends Bytes { return this; } - - /** - * @description Sets the output type for this storage key - */ - public setOutputType (outputType?: string): this { - this._outputType = outputType; - - return this; - } }