Allow storage queries with default keys values (#3578)

* Allow storage queries with default keys values

* Cleanups

* Adjust

* Tuple test
This commit is contained in:
Jaco
2021-05-28 08:37:08 +02:00
committed by GitHub
parent ae6a3307b1
commit 77019def08
5 changed files with 80 additions and 122 deletions
+15 -45
View File
@@ -3,34 +3,33 @@
import { ApiPromise, WsProvider } from '@polkadot/api';
function createApi (): Promise<ApiPromise> {
jest.setTimeout(30000);
process.env.NODE_ENV = 'test';
describe('misc online tests', (): void => {
let api: ApiPromise;
const provider = new WsProvider('wss://kusama-rpc.polkadot.io');
// const provider = new WsProvider('wss://westend-rpc.polkadot.io/');
// const provider = new WsProvider('ws://127.0.0.1:9944/');
beforeEach(async () => {
jest.setTimeout(30000);
process.env.NODE_ENV = 'test';
return new ApiPromise({ provider }).isReady;
}
// const provider = new WsProvider('wss://kusama-rpc.polkadot.io');
const provider = new WsProvider('wss://westend-rpc.polkadot.io/');
api = await ApiPromise.create({ provider });
});
afterEach(async () => {
await api.disconnect();
});
describe('misc quick tests', (): void => {
it.skip('retrieves balances correctly', async (): Promise<void> => {
const api = await createApi();
console.error(JSON.stringify(
await api.query.system.account('FPzukZw2mphWsXDqdkNzLaxnanjZEWH9i2vqwobTdtde5me')
));
console.error(JSON.stringify(
await api.query.system.account('HUewJvzVuEeyaxH2vx9XiyAPKrpu1Zj5r5Pi9VrGiBVty7q')
));
await api.disconnect();
});
it.skip('handles map keys', async (): Promise<void> => {
const api = await createApi();
console.time('map.keys');
const keys = await api.query.system.account.keys();
@@ -38,13 +37,9 @@ describe('misc quick tests', (): void => {
console.error('# keys', keys.length);
console.timeEnd('map.keys');
await api.disconnect();
});
it.skip('handles map entries', async (): Promise<void> => {
const api = await createApi();
console.time('map.entries');
const entries = await api.query.system.account.entries(); // api.query.indices.accounts.entries();
@@ -52,50 +47,25 @@ describe('misc quick tests', (): void => {
console.error('# entries', entries.length);
console.timeEnd('map.entries');
await api.disconnect();
});
it.skip('handles doublemap entries', async (): Promise<void> => {
const api = await createApi();
const activeEra = await api.query.staking.activeEra();
console.error(JSON.stringify(
await api.query.staking.erasStakers.entries(activeEra.unwrapOrDefault().index)
));
await api.disconnect();
});
it.skip('does something in society', async (): Promise<void> => {
const api = await createApi();
console.error(JSON.stringify(
await api.query.society.defenderVotes('Dab4bfYTZRUDMWjYAUQuFbDreQ9mt7nULWu3Dw7jodbzVe9')
));
await api.disconnect();
});
it.skip('allows for range queries', async (): Promise<void> => {
const api = await createApi();
const header = await api.rpc.chain.getHeader();
console.error(JSON.stringify(
(await api.query.staking.activeEra.range([header.parentHash, header.hash], 'Dab4bfYTZRUDMWjYAUQuFbDreQ9mt7nULWu3Dw7jodbzVe9'))
.map(([block, value]) => [block, value.toRawType(), value.toHuman()])
));
await api.disconnect();
});
it('expose rpc and rx definition', async (): Promise<void> => {
const api = await createApi();
it('expose rpc and rx definition', (): void => {
console.error(api.rpc.payment.queryFeeDetails.meta);
console.error(api.rx.rpc.chain.getBlock.meta);
await api.disconnect();
});
});
@@ -1,7 +1,7 @@
// Copyright 2017-2021 @polkadot/metadata authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { StorageEntryMetadataLatest } from '@polkadot/types/interfaces/metadata';
import type { StorageEntryMetadataLatest, StorageHasher } from '@polkadot/types/interfaces/metadata';
import type { StorageEntry } from '@polkadot/types/primitive/types';
import type { Codec, Registry } from '@polkadot/types/types';
@@ -9,7 +9,7 @@ import BN from 'bn.js';
import { Raw } from '@polkadot/types/codec';
import { StorageKey, Type } from '@polkadot/types/primitive';
import { assert, compactAddLength, compactStripLength, isNull, isUndefined, stringCamelCase, stringify, stringLowerFirst, u8aConcat, u8aToU8a } from '@polkadot/util';
import { assert, compactAddLength, compactStripLength, isUndefined, stringCamelCase, stringLowerFirst, u8aConcat, u8aToU8a } from '@polkadot/util';
import { xxhashAsU8a } from '@polkadot/util-crypto';
import { getHasher } from './getHasher';
@@ -31,33 +31,27 @@ interface IterFn {
meta: StorageEntryMetadataLatest;
}
type CreateArgType = boolean | string | number | null | BN | BigInt | Uint8Array | Codec;
type U8aHasher = (input: Uint8Array) => Uint8Array;
function createPrefixedKey ({ method, prefix }: CreateItemFn): Uint8Array {
return u8aConcat(xxhashAsU8a(prefix, 128), xxhashAsU8a(method, 128));
}
function createKeyRaw (registry: Registry, itemFn: CreateItemFn, keys: Type[], hashers: U8aHasher[], args: CreateArgType[]): Uint8Array {
const { method, section } = itemFn;
type Arg = boolean | string | number | null | BN | BigInt | Uint8Array | Codec;
/** @internal */
function createKeyRaw (registry: Registry, itemFn: CreateItemFn, keys: Type[], hashers: StorageHasher[], args: Arg[]): Uint8Array {
return u8aConcat(
createPrefixedKey(itemFn),
...keys.map((type, index): Uint8Array => {
const arg = args[index];
assert(!isUndefined(arg) && !isNull(arg), () => `Call to ${stringCamelCase(section || 'unknown')}.${stringCamelCase(method || 'unknown')} has a null or undefined argument at position ${index}`);
return hashers[index](registry.createType(type.toString() as 'Raw', arg).toU8a());
})
xxhashAsU8a(itemFn.prefix, 128),
xxhashAsU8a(itemFn.method, 128),
...keys.map((type, index) =>
getHasher(hashers[index])(
registry.createType(type.toString() as 'Raw', args[index]).toU8a()
)
)
);
}
function createKey (registry: Registry, itemFn: CreateItemFn, keys: Type[], hashers: U8aHasher[], args: CreateArgType[]): Uint8Array {
/** @internal */
function createKey (registry: Registry, itemFn: CreateItemFn, keys: Type[], hashers: StorageHasher[], args: Arg[]): Uint8Array {
const { method, section } = itemFn;
assert(Array.isArray(args) && args.length === keys.length, () => `Call to ${stringCamelCase(section || 'unknown')}.${stringCamelCase(method || 'unknown')} needs ${keys.length} arguments, found ${stringify(args)}`);
assert(Array.isArray(args), () => `Call to ${stringCamelCase(section || 'unknown')}.${stringCamelCase(method || 'unknown')} needs ${keys.length} arguments, provided in tuple format`);
assert(args.filter((a) => !isUndefined(a)).length === keys.length, () => `Call to ${stringCamelCase(section || 'unknown')}.${stringCamelCase(method || 'unknown')} needs ${keys.length} arguments, found [${args.join(', ')}]`);
// as per createKey, always add the length prefix (underlying it is Bytes)
return compactAddLength(
@@ -65,9 +59,8 @@ function createKey (registry: Registry, itemFn: CreateItemFn, keys: Type[], hash
);
}
// attach the metadata to expand to a StorageFunction
/** @internal */
function expandWithMeta ({ meta, method, prefix, section }: CreateItemFn, _storageFn: (arg?: CreateArgType | CreateArgType[]) => Uint8Array): StorageEntry {
function expandWithMeta ({ meta, method, prefix, section }: CreateItemFn, _storageFn: (arg?: Arg | Arg[]) => Uint8Array): StorageEntry {
const storageFn = _storageFn as StorageEntry;
storageFn.meta = meta;
@@ -107,7 +100,6 @@ function extendHeadMeta (registry: Registry, { meta: { documentation, name, type
registry.createType('StorageKey', iterFn(...args), { method, section });
}
// attach the full list hashing for prefixed maps
/** @internal */
function extendPrefixedMap (registry: Registry, itemFn: CreateItemFn, storageFn: StorageEntry): StorageEntry {
const { meta: { type }, method, section } = itemFn;
@@ -124,16 +116,16 @@ function extendPrefixedMap (registry: Registry, itemFn: CreateItemFn, storageFn:
if (args.length) {
if (type.isDoubleMap) {
return new Raw(registry, createKeyRaw(registry, itemFn, [type.asDoubleMap.key1], [getHasher(type.asDoubleMap.hasher)], args as CreateArgType[]));
return new Raw(registry, createKeyRaw(registry, itemFn, [type.asDoubleMap.key1], [type.asDoubleMap.hasher], args as Arg[]));
} else if (type.isNMap) {
const keys = [...type.asNMap.keyVec];
const hashers = type.asNMap.hashers.map((h) => getHasher(h));
const hashers = [...type.asNMap.hashers];
// remove the last entry
keys.pop();
hashers.pop();
return new Raw(registry, createKeyRaw(registry, itemFn, keys, hashers, args as CreateArgType[]));
return new Raw(registry, createKeyRaw(registry, itemFn, keys, hashers, args as Arg[]));
}
}
@@ -151,16 +143,16 @@ export function createFunction (registry: Registry, itemFn: CreateItemFn, option
// - storage.system.account(address)
// - storage.timestamp.blockPeriod()
// For higher-map queries the params are passed in as an tuple, [key1, key2]
const storageFn = expandWithMeta(itemFn, (arg?: CreateArgType | CreateArgType[]) =>
const storageFn = expandWithMeta(itemFn, (arg?: Arg | Arg[]) =>
type.isPlain
? options.skipHashing
? compactAddLength(u8aToU8a(options.key))
: createKey(registry, itemFn, [], [], [])
: type.isMap
? createKey(registry, itemFn, [type.asMap.key], [getHasher(type.asMap.hasher)], [arg as CreateArgType])
? createKey(registry, itemFn, [type.asMap.key], [type.asMap.hasher], [arg as Arg])
: type.isDoubleMap
? createKey(registry, itemFn, [type.asDoubleMap.key1, type.asDoubleMap.key2], [getHasher(type.asDoubleMap.hasher), getHasher(type.asDoubleMap.key2Hasher)], arg as CreateArgType[])
: createKey(registry, itemFn, type.asNMap.keyVec, type.asNMap.hashers.map((h) => getHasher(h)), arg as CreateArgType[])
? createKey(registry, itemFn, [type.asDoubleMap.key1, type.asDoubleMap.key2], [type.asDoubleMap.hasher, type.asDoubleMap.key2Hasher], arg as Arg[])
: createKey(registry, itemFn, type.asNMap.keyVec, type.asNMap.hashers, arg as Arg[])
);
if (type.isMap || type.isDoubleMap || type.isNMap) {
@@ -12,34 +12,36 @@ import { decorateStorage } from '..';
const keyring = createTestPairs({ type: 'ed25519' });
describe('decorateStorage', (): void => {
describe('latest', (): void => {
const registry = new TypeRegistry();
const metadata = new Metadata(registry, rpcMetadata);
const registry = new TypeRegistry();
const metadata = new Metadata(registry, rpcMetadata);
registry.setMetadata(metadata);
registry.setMetadata(metadata);
const query = decorateStorage(registry, metadata.asLatest, metadata.version);
const query = decorateStorage(registry, metadata.asLatest, metadata.version);
it('should throw if the storage function expects an argument', (): void => {
expect(() => query.balances.account()).toThrowError('Call to balances.account has a null or undefined argument at position 0');
});
it('should throw if the storage function expects an argument', (): void => {
expect(() => query.balances.account()).toThrowError('Call to balances.account needs 1 arguments, found []');
});
it('should throw if the storage function expects multiple arguments', (): void => {
expect(() => query.staking.erasStakers([1])).toThrowError('Call to staking.erasStakers needs 2 arguments, found [1]');
});
it('should throw if the storage function expects multiple arguments', (): void => {
expect(() => query.staking.erasStakers([1])).toThrowError('Call to staking.erasStakers needs 2 arguments, found [1]');
});
it('should return a value if the storage function does not expect an argument', (): void => {
expect(() => query.timestamp.now()).not.toThrow();
});
it('should throw if the storage function expects tuple arguments', (): void => {
expect(() => query.staking.erasStakers(1)).toThrowError('Call to staking.erasStakers needs 2 arguments, provided in tuple format');
});
it('should return the correct length-prefixed storage key', (): void => {
expect(
u8aToHex(
query.system.account(keyring.alice.address)
)
).toEqual(
'0x410126aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d'
);
});
it('should return a value if the storage function does not expect an argument', (): void => {
expect(() => query.timestamp.now()).not.toThrow();
});
it('should return the correct length-prefixed storage key', (): void => {
expect(
u8aToHex(
query.system.account(keyring.alice.address)
)
).toEqual(
'0x410126aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d'
);
});
});
+14 -21
View File
@@ -32,30 +32,23 @@ const HASHER_MAP: Record<keyof typeof AllHashers, [number, boolean]> = {
Twox64Concat: [8, true]
};
function getStorageType (type: StorageEntryTypeLatest): [boolean, string] {
if (type.isMap) {
return [false, type.asMap.value.toString()];
} else if (type.isDoubleMap) {
return [false, type.asDoubleMap.value.toString()];
} else if (type.isNMap) {
return [false, type.asNMap.value.toString()];
}
return [false, type.asPlain.toString()];
}
// we unwrap the type here, turning into an output usable for createType
/** @internal */
export function unwrapStorageType (type: StorageEntryTypeLatest, isOptional?: boolean): keyof InterfaceTypes {
const [hasWrapper, outputType] = getStorageType(type);
const outputType = type.isPlain
? type.asPlain.toString()
: type.isMap
? type.asMap.value.toString()
: type.isDoubleMap
? type.asDoubleMap.value.toString()
: type.asNMap.value.toString();
return isOptional && !hasWrapper
return isOptional
? `Option<${outputType}>` as keyof InterfaceTypes
: outputType as keyof InterfaceTypes;
}
/** @internal */
function decodeStorageKey (value?: AnyU8a | StorageKey | StorageEntry | [StorageEntry, any]): Decoded {
function decodeStorageKey (value?: AnyU8a | StorageKey | StorageEntry | [StorageEntry, unknown]): Decoded {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
if (value instanceof StorageKey) {
return {
@@ -73,12 +66,12 @@ function decodeStorageKey (value?: AnyU8a | StorageKey | StorageEntry | [Storage
section: value.section
};
} else if (Array.isArray(value)) {
const [fn, ...arg] = value as [StorageEntry, ...any[]];
const [fn, arg] = value;
assert(isFunction(fn), 'Expected function input for key construction');
return {
key: fn(...arg),
key: fn(arg),
method: fn.method,
section: fn.section
};
@@ -134,7 +127,7 @@ function decodeArgsFromMeta <A extends AnyTuple> (registry: Registry, value: Uin
}
/** @internal */
function getMeta (value: StorageKey | StorageEntry | [StorageEntry, any]): StorageEntryMetadataLatest | undefined {
function getMeta (value: StorageKey | StorageEntry | [StorageEntry, unknown]): StorageEntryMetadataLatest | undefined {
if (value instanceof StorageKey) {
return value.meta;
} else if (isFunction(value)) {
@@ -149,7 +142,7 @@ function getMeta (value: StorageKey | StorageEntry | [StorageEntry, any]): Stora
}
/** @internal */
function getType (value: StorageKey | StorageEntry | [StorageEntry, any]): string {
function getType (value: StorageKey | StorageEntry | [StorageEntry, unknown]): string {
if (value instanceof StorageKey) {
return value.outputType;
} else if (isFunction(value)) {
@@ -185,7 +178,7 @@ export class StorageKey<A extends AnyTuple = AnyTuple> extends Bytes implements
private _section?: string;
constructor (registry: Registry, value?: AnyU8a | StorageKey | StorageEntry | [StorageEntry, any], override: Partial<StorageKeyExtra> = {}) {
constructor (registry: Registry, value?: AnyU8a | StorageKey | StorageEntry | [StorageEntry, unknown], override: Partial<StorageKeyExtra> = {}) {
const { key, method, section } = decodeStorageKey(value);
super(registry, key);