Add .args to decoded StorageKeys (#2004)

* Add .args to decoded StorageKeys

* Small opts

* Formatting
This commit is contained in:
Jaco Greeff
2020-03-06 13:47:40 +01:00
committed by GitHub
parent 63c22414ce
commit adee447cd0
7 changed files with 121 additions and 27 deletions
@@ -18,6 +18,8 @@ function queryClipped (api: ApiInterfaceRx): Observable<[EraIndex, [AccountId, E
combineLatest([
of(allPoints),
combineLatest(
// we could just do entries over the full set, however the resultset can be quite large - split it into
// batches - may need to re-visit this, or alternatively use pages keys for exceptionally large sets
allPoints.map(({ era }): Observable<[StorageKey, Exposure][]> =>
api.query.staking.erasStakersClipped.entries(era)
)
@@ -28,7 +30,7 @@ function queryClipped (api: ApiInterfaceRx): Observable<[EraIndex, [AccountId, E
allPoints.map(({ era }, index): [EraIndex, [AccountId, Exposure][]] => [
era,
erasStakers[index].map(([key, exposure]): [AccountId, Exposure] => [
api.registry.createType('AccountId', key.toU8a().slice(-32)),
key.args[1] as AccountId,
exposure
])
])
@@ -20,10 +20,7 @@ function queryNextStakers (api: ApiInterfaceRx): Observable<AccountId[]> {
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))
)
entries.map(([key]): AccountId => key.args[1] as AccountId)
)
);
}
+15 -13
View File
@@ -17,7 +17,7 @@ import DecoratedMeta from '@polkadot/metadata/Decorated';
import getHasher from '@polkadot/metadata/Decorated/storage/fromMetadata/getHasher';
import RpcCore from '@polkadot/rpc-core';
import { WsProvider } from '@polkadot/rpc-provider';
import { Metadata, Null, Option, Text, TypeRegistry, u64, Vec } from '@polkadot/types';
import { Metadata, Null, Option, Text, TypeRegistry, u64 } from '@polkadot/types';
import Linkage, { LinkageResult } from '@polkadot/types/codec/Linkage';
import { DEFAULT_VERSION as EXTRINSIC_DEFAULT_VERSION } from '@polkadot/types/extrinsic/constants';
import StorageKey, { StorageEntry, unwrapStorageType } from '@polkadot/types/primitive/StorageKey';
@@ -429,31 +429,33 @@ export default abstract class Decorate<ApiType extends ApiTypes> 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');
private retrieveMapEntries ({ iterKey, meta }: StorageEntry, arg?: Arg): Observable<[StorageKey, Codec][]> {
assert(iterKey && (meta.type.isMap || meta.type.isDoubleMap), 'entries can only be retrieved on maps, linked maps and double maps');
const outputType: any = creator.meta.type.isMap
? creator.meta.type.asMap.value.toString()
: creator.meta.type.asDoubleMap.value.toString();
let outputType: any = unwrapStorageType(meta.type);
if (meta.modifier.isOptional) {
outputType = `Option<${outputType}>`;
}
return this._rpcCore.state
// 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,
creator.meta.type.isDoubleMap && !isUndefined(arg) && !isNull(arg)
? getHasher(creator.meta.type.asDoubleMap.hasher)(
this.createType(creator.meta.type.asDoubleMap.key1.toString() as 'Raw', arg).toU8a()
iterKey,
meta.type.isDoubleMap && !isUndefined(arg) && !isNull(arg)
? getHasher(meta.type.asDoubleMap.hasher)(
this.createType(meta.type.asDoubleMap.key1.toString() as 'Raw', arg).toU8a()
)
: new Uint8Array([])
))
)
.pipe(
switchMap((keys): Observable<[StorageKey[], Vec<Codec>]> =>
switchMap((keys): Observable<[StorageKey[], Codec[]]> =>
combineLatest([
of(keys),
this._rpcCore.state.subscribeStorage<Vec<Codec>>(keys)
of(keys.map((key): StorageKey => key.decodeArgsFromMeta(meta))),
this._rpcCore.state.subscribeStorage(keys)
])
),
map(([keys, values]): [StorageKey, Codec][] =>
+15 -8
View File
@@ -64,20 +64,27 @@ function renderPage (page: Page): string {
}
// index
page.sections.forEach(section => {
page.sections.forEach((section) => {
md += `- **[${stringCamelCase(section.name)}](#${stringCamelCase(section.name).toLowerCase()})**\n\n`;
});
// contents
page.sections.forEach(section => {
md += `\n___\n\n\n## ${section.name}\n`;
if (section.description) { md += `\n_${section.description}_\n`; }
if (section.description) {
md += `\n_${section.description}_\n`;
}
section.items.forEach((item) => {
md += ` \n### ${item.name}`;
Object.keys(item).filter(i => i !== 'name').forEach(bullet => {
md += `\n- **${bullet}**: ${item[bullet] instanceof Vec ? documentationVecToMarkdown(item[bullet] as Vec<Text>, 2) : item[bullet]}`;
md += `\n- **${bullet}**: ${
item[bullet] instanceof Vec
? documentationVecToMarkdown(item[bullet] as Vec<Text>, 2)
: item[bullet]
}`;
});
md += '\n';
@@ -116,8 +123,8 @@ function addRpc (): string {
return {
name: `${methodName}(${args}): ${type}`,
jsonrpc: `${sectionName}_${methodName}`,
interface: `api.rpc.${sectionName}.${methodName}`,
jsonrpc: '`' + `${sectionName}_${methodName}` + '`',
interface: '`' + `api.rpc.${sectionName}.${methodName}` + '`',
...(method.description && { summary: method.description })
};
})
@@ -146,7 +153,7 @@ function addConstants (metadata: MetadataLatest): string {
return {
name: `${methodName}: ` + '`' + func.type + '`',
interface: `api.consts.${sectionName}.${methodName}`,
interface: '`' + `api.consts.${sectionName}.${methodName}` + '`',
...(func.documentation.length && { summary: func.documentation })
};
})
@@ -182,7 +189,7 @@ function addStorage (metadata: MetadataLatest): string {
return {
name: `${methodName}(${arg}): ` + '`' + result + '`',
interface: `api.query.${sectionName}.${methodName}`,
interface: '`' + `api.query.${sectionName}.${methodName}` + '`',
...(func.documentation.length && { summary: func.documentation })
};
})
@@ -220,7 +227,7 @@ function addExtrinsics (metadata: MetadataLatest): string {
return {
name: `${methodName}(${args})`,
interface: `api.tx.${sectionName}.${methodName}`,
interface: '`' + `api.tx.${sectionName}.${methodName}` + '`',
...(func.documentation.length && { summary: func.documentation })
};
})
@@ -9,6 +9,7 @@ import rpcDataV5 from '@polkadot/metadata/Metadata/v5/static';
import rpcDataV6 from '@polkadot/metadata/Metadata/v6/static';
import rpcDataV7 from '@polkadot/metadata/Metadata/v7/static';
import rpcDataV8 from '@polkadot/metadata/Metadata/v8/static';
import rpcDataV11 from '@polkadot/metadata/Metadata/v11/static';
import { hexToU8a } from '@polkadot/util';
import { TypeRegistry } from '../create';
@@ -230,4 +231,21 @@ describe('StorageKey', (): void => {
).toEqual(Uint8Array.from([1, 1, 48, 7, 201, 255, 112, 39, 246, 89, 0, 171, 205, 252, 164, 253, 177, 7, 234, 212, 126, 42, 158, 53, 88, 224, 27, 105, 27, 15, 74, 95, 133, 24, 212, 135, 50, 102, 20, 240, 102, 65, 99, 8, 191, 106, 164, 229, 4, 29, 25, 73, 146, 142, 75, 38, 237, 233, 142, 60, 235, 179, 106, 59, 23, 38]));
});
});
describe('with MetadataV11', (): void => {
const metadata = new Metadata(registry, rpcDataV11);
it('should allow decoding of a key', (): void => {
const key = new StorageKey(registry, '0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01b66ccada06515787c10000000e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f');
key.setMeta(metadata.query.staking.erasStakers.meta);
expect(
key.args.map((arg) => arg.toHuman())
).toEqual([
'16',
'5GNJqTPyNqANBkUVMN1LPPrxXnFouWXoe2wNSmmEoLctxiZY'
]);
});
});
});
+64 -1
View File
@@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { StorageEntryMetadataLatest, StorageEntryTypeLatest } from '../interfaces/metadata';
import { StorageEntryMetadataLatest, StorageEntryTypeLatest, StorageHasher } from '../interfaces/metadata';
import { AnyU8a, Codec, InterfaceTypes, Registry } from '../types';
import { assert, isFunction, isString, isU8a } from '@polkadot/util';
@@ -82,6 +82,46 @@ function decodeStorageKey (value?: AnyU8a | StorageKey | StorageEntry | [Storage
throw new Error(`Unable to convert input ${value} to StorageKey`);
}
function decodeHashers (registry: Registry, encoded: Uint8Array, hashers: [StorageHasher, string][]): Codec[] {
let offset = 0;
return hashers.reduce((result: Codec[], [hasher, type]): Codec[] => {
const decoded = hasher.isTwox64Concat
? registry.createType(type as any, encoded.subarray(offset + 8))
: registry.createType('Null');
offset += hasher.isTwox64Concat ? (8 + decoded.encodedLength) : 0;
result.push(decoded);
return result;
}, []);
}
/** @internal */
function decodeArgsFromMeta (registry: Registry, value: Uint8Array, meta?: StorageEntryMetadataLatest): Codec[] {
if (!meta || !(meta.type.isDoubleMap || meta.type.isMap)) {
return [];
}
// the prefix is xxhashAsU8a(prefix, 128) + xxhashAsU8a(method, 128), 256 bits total
const encoded = value.subarray(32);
if (meta.type.isMap) {
const mapInfo = meta.type.asMap;
return decodeHashers(registry, encoded, [
[mapInfo.hasher, mapInfo.value.toString()]
]);
}
const mapInfo = meta.type.asDoubleMap;
return decodeHashers(registry, encoded, [
[mapInfo.hasher, mapInfo.key1.toString()],
[mapInfo.key2Hasher, mapInfo.key2.toString()]
]);
}
/**
* @name StorageKey
* @description
@@ -89,6 +129,10 @@ function decodeStorageKey (value?: AnyU8a | StorageKey | StorageEntry | [Storage
* constructed by passing in a raw key or a StorageEntry with (optional) arguments.
*/
export default class StorageKey extends Bytes {
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore This is assigned via this.decodeArgsFromMeta()
private _args: Codec[];
private _meta?: StorageEntryMetadataLatest;
private readonly _method?: string;
@@ -106,6 +150,9 @@ export default class StorageKey extends Bytes {
this._method = override.method || method;
this._outputType = StorageKey.getType(value as StorageKey);
this._section = override.section || section;
// decode the args (as applicable based on the key and the hashers, after all init)
this.decodeArgsFromMeta();
}
public static getMeta (value: StorageKey | StorageEntry | [StorageEntry, any]): StorageEntryMetadataLatest | undefined {
@@ -139,6 +186,13 @@ export default class StorageKey extends Bytes {
return 'Raw';
}
/**
* @description Return the decoded arguments (applicable to map/doublemap with decodable values)
*/
public get args (): Codec[] {
return this._args;
}
/**
* @description The metadata or `undefined` when not available
*/
@@ -173,6 +227,15 @@ export default class StorageKey extends Bytes {
public setMeta (meta?: StorageEntryMetadataLatest): this {
this._meta = meta;
return this.decodeArgsFromMeta();
}
/**
* @description Decode the args embedded in the key (assuming we have decodable hashers)
*/
public decodeArgsFromMeta (meta?: StorageEntryMetadataLatest): this {
this._args = decodeArgsFromMeta(this.registry, this.toU8a(true), meta || this.meta);
return this;
}
}