Skip to content

Commit c2f9ef7

Browse files
authored
Add .keys on storage entries (#2032)
* Add .keys on storage entries * Pass isOptional to storage type unwrap * Linked maps TS types
1 parent fc477b9 commit c2f9ef7

File tree

11 files changed

+82
-73
lines changed

11 files changed

+82
-73
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22

33
- **Breaking change** `api.rpc.state.queryStorage(...)` now properly decodes the `Vec<StorageChangeSet>`. this means that results will come back as `[Hash, Codec[]][]` when using this RPC.
44
- `StorageKey` now have an `.args` property, decoded from meta in the cases twox64concat is used on maps
5-
- Fix storage `.entries` type conversions to return exact types (not `Option` in some cases)
5+
- Fix `api.query.*.*.entries` type conversions to return exact types (not `Option` in some cases)
6+
- Add `api.query.*.*.keys` to retrieve only the storage keys, similar to `.entries`
67
- Full linked map retrievals will now use direct getStorage queries for faster operation
78
- Underlying rpc-core interfaces now unwraps `Error("...")` when found in responses
89
- Added `derive.eras*` interfaces for queries to new Substrate staking interfaces
910
- Update `derive.account` to cater for new indices module storage (detected, fallbacks)
1011
- Adjust derive queries for session move away from module prefix (DoubleMap -> Map), detected on use
1112
- Add runtime validation for map arguments to `api.query.*`
13+
- TypeScript interfaces for linked maps now correctly generates as `[Type, Linkage<Next>]`
1214

1315
# 1.5.1 Mar 06, 2020
1416

packages/api-derive/src/staking/erasExposure.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,7 @@ import { StorageKey } from '@polkadot/types';
1313

1414
import { memo } from '../util';
1515

16-
interface Mapped {
17-
nominators: DeriveEraNominatorExposure;
18-
validators: DeriveEraValidatorExposure;
19-
}
20-
21-
function mapStakers (stakers: [StorageKey, Exposure][]): Mapped {
16+
function mapStakers (era: EraIndex, stakers: [StorageKey, Exposure][]): DeriveEraExposure {
2217
const nominators: DeriveEraNominatorExposure = {};
2318
const validators: DeriveEraValidatorExposure = {};
2419

@@ -35,7 +30,7 @@ function mapStakers (stakers: [StorageKey, Exposure][]): Mapped {
3530
});
3631
});
3732

38-
return { nominators, validators };
33+
return { era, nominators, validators };
3934
}
4035

4136
export function erasExposure (api: ApiInterfaceRx): (withActive?: boolean | BN | number) => Observable<DeriveEraExposure[]> {
@@ -56,10 +51,9 @@ export function erasExposure (api: ApiInterfaceRx): (withActive?: boolean | BN |
5651
])
5752
),
5853
map(([eras, erasStakers]): DeriveEraExposure[] =>
59-
eras.map((era, index): DeriveEraExposure => ({
60-
era,
61-
...mapStakers(erasStakers[index])
62-
}))
54+
eras.map((era, index): DeriveEraExposure =>
55+
mapStakers(era, erasStakers[index])
56+
)
6357
)
6458
)
6559
);

packages/api-derive/src/staking/validators.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// of the Apache-2.0 license. See the LICENSE file for details.
44

55
import { ApiInterfaceRx } from '@polkadot/api/types';
6-
import { AccountId, Exposure } from '@polkadot/types/interfaces';
6+
import { AccountId } from '@polkadot/types/interfaces';
77
import { DeriveStakingValidators } from '../types';
88

99
import { Observable, combineLatest, of } from 'rxjs';
@@ -16,11 +16,11 @@ function queryNextStakers (api: ApiInterfaceRx): Observable<AccountId[]> {
1616
// only populate for next era in the last session, so track both here - entries are not
1717
// subscriptions, so we need a trigger - currentIndex acts as that trigger to refresh
1818
return api.derive.session.indexes().pipe(
19-
switchMap(({ activeEra }): Observable<[StorageKey, Exposure][]> =>
20-
api.query.staking.erasStakers.entries(activeEra.addn(1)).pipe(take(1))
19+
switchMap(({ activeEra }): Observable<StorageKey[]> =>
20+
api.query.staking.erasStakers.keys(activeEra.addn(1)).pipe(take(1))
2121
),
22-
map((entries): AccountId[] =>
23-
entries.map(([key]): AccountId => key.args[1] as AccountId)
22+
map((keys): AccountId[] =>
23+
keys.map((key): AccountId => key.args[1] as AccountId)
2424
)
2525
);
2626
}

packages/api/src/augment/query.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/* eslint-disable @typescript-eslint/no-empty-interface */
33

44
import { AnyNumber, ITuple } from '@polkadot/types/types';
5-
import { Option, U8aFixed, Vec } from '@polkadot/types/codec';
5+
import { Linkage, Option, U8aFixed, Vec } from '@polkadot/types/codec';
66
import { Bytes, Data, bool, u32, u64 } from '@polkadot/types/primitive';
77
import { UncleEntryItem } from '@polkadot/types/interfaces/authorship';
88
import { BabeAuthorityWeight, MaybeVrf } from '@polkadot/types/interfaces/babe';
@@ -255,11 +255,11 @@ declare module '@polkadot/api/types/storage' {
255255
/**
256256
* The map from (wannabe) validator stash key to the preferences of that validator.
257257
**/
258-
validators: AugmentedQuery<ApiType, (arg: AccountId | string | Uint8Array) => Observable<ValidatorPrefs>> & QueryableStorageEntry<ApiType>;
258+
validators: AugmentedQuery<ApiType, (arg: AccountId | string | Uint8Array) => Observable<ITuple<[ValidatorPrefs, Linkage<AccountId>]>>> & QueryableStorageEntry<ApiType>;
259259
/**
260260
* The map from nominator stash key to the set of stash keys of all validators to nominate.
261261
**/
262-
nominators: AugmentedQuery<ApiType, (arg: AccountId | string | Uint8Array) => Observable<Option<Nominations>>> & QueryableStorageEntry<ApiType>;
262+
nominators: AugmentedQuery<ApiType, (arg: AccountId | string | Uint8Array) => Observable<Option<ITuple<[Nominations, Linkage<AccountId>]>>>> & QueryableStorageEntry<ApiType>;
263263
/**
264264
* The current era index.
265265
* This is the latest planned era, depending on how session module queues the validator
@@ -459,7 +459,7 @@ declare module '@polkadot/api/types/storage' {
459459
/**
460460
* Get the account (and lock periods) to which another account is delegating vote.
461461
**/
462-
delegations: AugmentedQuery<ApiType, (arg: AccountId | string | Uint8Array) => Observable<ITuple<[AccountId, Conviction]>>> & QueryableStorageEntry<ApiType>;
462+
delegations: AugmentedQuery<ApiType, (arg: AccountId | string | Uint8Array) => Observable<ITuple<[ITuple<[AccountId, Conviction]>, Linkage<AccountId>]>>> & QueryableStorageEntry<ApiType>;
463463
/**
464464
* Accounts for which there are locks in action which may be removed at some point in the
465465
* future. The value is the block number at which the lock expires and may be removed.
@@ -560,7 +560,7 @@ declare module '@polkadot/api/types/storage' {
560560
/**
561561
* Votes of a particular voter, with the round index of the votes.
562562
**/
563-
votesOf: AugmentedQuery<ApiType, (arg: AccountId | string | Uint8Array) => Observable<Vec<AccountId>>> & QueryableStorageEntry<ApiType>;
563+
votesOf: AugmentedQuery<ApiType, (arg: AccountId | string | Uint8Array) => Observable<ITuple<[Vec<AccountId>, Linkage<AccountId>]>>> & QueryableStorageEntry<ApiType>;
564564
/**
565565
* Locked stake of a voter.
566566
**/

packages/api/src/base/Decorate.ts

Lines changed: 28 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,9 @@ export default abstract class Decorate<ApiType extends ApiTypes> extends Events
326326
decorated.keyPrefix = (): string =>
327327
u8aToHex(creator.keyPrefix);
328328

329+
decorated.keys = decorateMethod(memo((doubleMapArg?: Arg): Observable<StorageKey[]> =>
330+
this.retrieveMapKeys(creator, doubleMapArg)));
331+
329332
if (this.hasSubscriptions) {
330333
// When using double map storage function, user need to pass double map key as an array
331334
decorated.multi = decorateMethod((args: (Arg | Arg[])[]): Observable<Codec[]> =>
@@ -343,11 +346,7 @@ export default abstract class Decorate<ApiType extends ApiTypes> extends Events
343346
}
344347

345348
private decorateStorageRange<ApiType extends ApiTypes> (decorated: QueryableStorageEntry<ApiType>, args: [Arg?, Arg?], range: [Hash, Hash?]): Observable<[Hash, Codec][]> {
346-
let outputType: any = unwrapStorageType(decorated.creator.meta.type);
347-
348-
if (decorated.creator.meta.modifier.isOptional) {
349-
outputType = `Option<${outputType}>`;
350-
}
349+
const outputType = unwrapStorageType(decorated.creator.meta.type, decorated.creator.meta.modifier.isOptional);
351350

352351
return this._rpcCore.state
353352
.queryStorage<[Option<Raw>]>([decorated.key(...args)], ...range)
@@ -431,34 +430,36 @@ export default abstract class Decorate<ApiType extends ApiTypes> extends Events
431430
);
432431
}
433432

434-
private retrieveMapEntries ({ iterKey, meta }: StorageEntry, arg?: Arg): Observable<[StorageKey, Codec][]> {
435-
assert(iterKey && (meta.type.isMap || meta.type.isDoubleMap), 'entries can only be retrieved on maps, linked maps and double maps');
433+
private retrieveMapKeys ({ iterKey, meta }: StorageEntry, arg?: Arg): Observable<StorageKey[]> {
434+
assert(iterKey && (meta.type.isMap || meta.type.isDoubleMap), 'keys can only be retrieved on maps, linked maps and double maps');
436435

437-
let outputType: any = unwrapStorageType(meta.type);
438-
439-
if (meta.modifier.isOptional) {
440-
outputType = `Option<${outputType}>`;
441-
}
436+
const startKey = this.createType('Raw', u8aConcat(
437+
iterKey,
438+
meta.type.isDoubleMap && !isUndefined(arg) && !isNull(arg)
439+
? getHasher(meta.type.asDoubleMap.hasher)(
440+
this.createType(meta.type.asDoubleMap.key1.toString() as 'Raw', arg).toU8a()
441+
)
442+
: new Uint8Array([])
443+
));
442444

443-
return this._rpcCore.state
444-
// TODO This should really be some sort of subscription, so we can get stuff as
445-
// it changes (as of now it is a one-shot query). Not available on Substrate.
446-
.getKeys(
447-
this.createType('Raw', u8aConcat(
448-
iterKey,
449-
meta.type.isDoubleMap && !isUndefined(arg) && !isNull(arg)
450-
? getHasher(meta.type.asDoubleMap.hasher)(
451-
this.createType(meta.type.asDoubleMap.key1.toString() as 'Raw', arg).toU8a()
452-
)
453-
: new Uint8Array([])
454-
))
445+
return this._rpcCore.state.getKeys(startKey).pipe(
446+
map((keys): StorageKey[] =>
447+
keys.map((key) => key.decodeArgsFromMeta(meta))
455448
)
449+
);
450+
}
451+
452+
private retrieveMapEntries (entry: StorageEntry, arg?: Arg): Observable<[StorageKey, Codec][]> {
453+
const outputType = unwrapStorageType(entry.meta.type, entry.meta.modifier.isOptional);
454+
455+
return this.retrieveMapKeys(entry, arg)
456456
.pipe(
457457
switchMap((keys): Observable<[StorageKey[], Option<Raw>[]]> =>
458458
combineLatest([
459-
of(keys.map((key) => key.decodeArgsFromMeta(meta))),
460-
// since we have a default constructed key, we have Option<Raw> as the result
461-
// TODO Don't keep the subscription here active, retrieve and get out of Dodge
459+
of(keys),
460+
// Since we have a default constructed key, we have Option<Raw> as the result
461+
// Don't keep the subscription here active, retrieve and get out of Dodge
462+
// TODO Once we have a multiple key RPC available, use that via fallback
462463
this._rpcCore.state.subscribeStorage<Option<Raw>[]>(keys).pipe(take(1))
463464
])
464465
),

packages/api/src/types/storage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ export interface StorageEntryBase<ApiType extends ApiTypes, F extends AnyFunctio
3232
hash: (...args: Parameters<F>) => PromiseOrObs<ApiType, Hash>;
3333
key: (...args: Parameters<F>) => string;
3434
keyPrefix: () => string;
35+
keys: (arg?: any) => PromiseOrObs<ApiType, StorageKey[]>;
3536
range: <T extends Codec | any = ObsInnerType<ReturnType<F>>>([from, to]: [Hash | Uint8Array | string, Hash | Uint8Array | string | undefined] | [Hash | Uint8Array | string], ...args: Parameters<F>) => PromiseOrObs<ApiType, [Hash, T][]>;
3637
size: (...args: Parameters<F>) => PromiseOrObs<ApiType, u64>;
3738
multi: ApiType extends 'rxjs' ? StorageEntryObservableMulti : StorageEntryPromiseMulti;

packages/typegen/src/generate/query.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,22 @@ import * as defaultDefs from '@polkadot/types/interfaces/definitions';
88

99
import staticData from '@polkadot/metadata/Metadata/static';
1010
import Metadata from '@polkadot/metadata/Metadata';
11+
import { unwrapStorageType } from '@polkadot/types/primitive/StorageKey';
1112
import { TypeRegistry } from '@polkadot/types/create';
1213
import { stringLowerFirst } from '@polkadot/util';
1314

1415
import { FOOTER, HEADER, TypeImports, createDocComments, createImportCode, createImports, formatType, getSimilarTypes, indent, registerDefinitions, setImports, writeFile } from '../util';
1516

16-
// If the StorageEntry returns T, output `Option<T>` if the modifier is optional
17-
/** @internal */
18-
function addModifier (storageEntry: StorageEntryMetadataLatest, returnType: string): string {
19-
if (storageEntry.modifier.isOptional) {
20-
return `Option<${returnType}>`;
21-
}
22-
23-
return returnType;
24-
}
25-
2617
// From a storage entry metadata, we return [args, returnType]
2718
/** @internal */
2819
function entrySignature (allDefs: object, registry: Registry, storageEntry: StorageEntryMetadataLatest, imports: TypeImports): [string, string] {
2920
const format = (type: string): string => formatType(allDefs, type, imports);
21+
const outputType = unwrapStorageType(storageEntry.type, storageEntry.modifier.isOptional);
3022

3123
if (storageEntry.type.isPlain) {
3224
setImports(allDefs, imports, [storageEntry.type.asPlain.toString()]);
3325

34-
return ['', formatType(allDefs, addModifier(storageEntry, storageEntry.type.asPlain.toString()), imports)];
26+
return ['', formatType(allDefs, outputType, imports)];
3527
} else if (storageEntry.type.isMap) {
3628
// Find similar types of the `key` type
3729
const similarTypes = getSimilarTypes(allDefs, registry, storageEntry.type.asMap.key.toString(), imports);
@@ -43,7 +35,7 @@ function entrySignature (allDefs: object, registry: Registry, storageEntry: Stor
4335

4436
return [
4537
`arg: ${similarTypes.map(format).join(' | ')}`,
46-
formatType(allDefs, addModifier(storageEntry, storageEntry.type.asMap.value.toString()), imports)
38+
formatType(allDefs, outputType, imports)
4739
];
4840
} else if (storageEntry.type.isDoubleMap) {
4941
// Find similar types of `key1` and `key2` types
@@ -61,7 +53,7 @@ function entrySignature (allDefs: object, registry: Registry, storageEntry: Stor
6153

6254
return [
6355
`key1: ${key1Types}, key2: ${key2Types}`,
64-
formatType(allDefs, addModifier(storageEntry, storageEntry.type.asDoubleMap.value.toString()), imports)
56+
formatType(allDefs, outputType, imports)
6557
];
6658
}
6759

packages/typegen/src/metadataMd.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// of the Apache-2.0 license. See the LICENSE file for details.
44

55
import { MetadataLatest } from '@polkadot/types/interfaces/metadata';
6-
import { InterfaceTypes } from '@polkadot/types/types';
76

87
import fs from 'fs';
98
import interfaces from '@polkadot/jsonrpc';
@@ -181,14 +180,10 @@ function addStorage (metadata: MetadataLatest): string {
181180
? ('`' + func.type.asDoubleMap.key1.toString() + ', ' + func.type.asDoubleMap.key2.toString() + '`')
182181
: '';
183182
const methodName = stringLowerFirst(func.name.toString());
184-
let result = unwrapStorageType(func.type);
185-
186-
if (func.modifier.isOptional) {
187-
result = `Option<${result}>` as keyof InterfaceTypes;
188-
}
183+
const outputType = unwrapStorageType(func.type, func.modifier.isOptional);
189184

190185
return {
191-
name: `${methodName}(${arg}): ` + '`' + result + '`',
186+
name: `${methodName}(${arg}): ` + '`' + outputType + '`',
192187
interface: '`' + `api.query.${sectionName}.${methodName}` + '`',
193188
...(func.documentation.length && { summary: func.documentation })
194189
};

packages/typegen/src/util/formatting.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,14 @@ function formatHashMap (key: string, val: string): string {
105105
return `HashMap<${key}, ${val}>`;
106106
}
107107

108+
/**
109+
* Given the inner `T`, return a `Vec<T>` string
110+
*/
111+
/** @internal */
112+
function formatLinkage (inner: string): string {
113+
return paramsNotation('Linkage', inner);
114+
}
115+
108116
/**
109117
* Given the inner `O` & `E`, return a `Result<O, E>` string
110118
*/
@@ -221,6 +229,13 @@ export function formatType (definitions: object, type: string | TypeDef, imports
221229

222230
return formatHashMap(formatType(definitions, keyDef.type, imports), formatType(definitions, valDef.type, imports));
223231
}
232+
case TypeDefInfo.Linkage: {
233+
const type = (typeDef.sub as TypeDef).type;
234+
235+
setImports(definitions, imports, ['Linkage']);
236+
237+
return formatLinkage(formatType(definitions, type, imports));
238+
}
224239
case TypeDefInfo.Result: {
225240
setImports(definitions, imports, ['Result']);
226241

packages/types/src/codec/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export { default as Compact } from './Compact';
1414
// export { default as Date } from './Date';
1515
export { default as Enum } from './Enum';
1616
export { default as HashMap } from './HashMap';
17+
export { default as Linkage } from './Linkage';
1718
export { default as Option } from './Option';
1819
export { default as Result } from './Result';
1920
export { default as Set } from './Set';

0 commit comments

Comments
 (0)