diff --git a/CHANGELOG.md b/CHANGELOG.md index 12f62a2537..9af97a3298 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # 1.6.0-beta.x +- **Breaking change** `api.rpc.state.queryStorage(...)` now properly decodes the `Vec`. this means that results will come back as `[Hash, Codec[]][]` when using this RPC. - `StorageKey` now have an `.args` property, decoded from meta in the cases twox64concat is used on maps - Fix storage `.entries` type conversions to return exact types (not `Option` in some cases) - Full linked map retrievals will now use direct getStorage queries for faster operation diff --git a/packages/api/src/augment/rpc.ts b/packages/api/src/augment/rpc.ts index 3b424e3507..0025ebef1a 100644 --- a/packages/api/src/augment/rpc.ts +++ b/packages/api/src/augment/rpc.ts @@ -7,7 +7,7 @@ import { Bytes, StorageKey, Text, bool, u32, u64 } from '@polkadot/types/primiti import { Metadata } from '@polkadot/types'; import { ContractCallRequest, ContractExecResult } from '@polkadot/types/interfaces/contracts'; import { Extrinsic } from '@polkadot/types/interfaces/extrinsics'; -import { BlockHash, ChainProperties, ExtrinsicOrHash, ExtrinsicStatus, Health, NetworkState, PeerInfo, RpcMethods, RuntimeDispatchInfo, RuntimeVersion, StorageChangeSet } from '@polkadot/types/interfaces/rpc'; +import { BlockHash, ChainProperties, ExtrinsicOrHash, ExtrinsicStatus, Health, NetworkState, PeerInfo, RpcMethods, RuntimeDispatchInfo, RuntimeVersion } from '@polkadot/types/interfaces/rpc'; import { AccountId, BlockNumber, H256, Hash, Header, Index, SignedBlock, StorageData } from '@polkadot/types/interfaces/runtime'; import { Observable } from 'rxjs'; @@ -138,7 +138,10 @@ declare module '@polkadot/rpc-core/types.jsonrpc' { * Get the runtime version **/ getRuntimeVersion: AugmentedRpc<(at?: BlockHash | string | Uint8Array) => Observable>; - getStorage: AugmentedRpc<(key: any, block?: Hash | Uint8Array | string) => Observable>; + /** + * Retrieves the storage for a key + **/ + getStorage: AugmentedRpc<(key: StorageKey | string | Uint8Array | any, block?: Hash | Uint8Array | string) => Observable>; /** * Retrieves the storage hash **/ @@ -150,12 +153,15 @@ declare module '@polkadot/rpc-core/types.jsonrpc' { /** * Query historical storage entries (by key) starting from a start block **/ - queryStorage: AugmentedRpc<(keys: Vec | (StorageKey | string | Uint8Array | any)[], startBlock: Hash | string | Uint8Array, at?: BlockHash | string | Uint8Array) => Observable>>; + queryStorage: AugmentedRpc<(keys: Vec | (StorageKey | string | Uint8Array | any)[], fromBlock?: Hash | Uint8Array | string, toBlock?: Hash | Uint8Array | string) => Observable<[Hash, T][]>>; /** * Retrieves the runtime version via subscription **/ subscribeRuntimeVersion: AugmentedRpc<() => Observable>; - subscribeStorage: AugmentedRpc<(keys: any[]) => Observable>; + /** + * Subscribes to storage changes for the provided keys + **/ + subscribeStorage: AugmentedRpc<(keys: Vec | (StorageKey | string | Uint8Array | any)[]) => Observable>; }; system: { /** diff --git a/packages/api/src/base/Decorate.ts b/packages/api/src/base/Decorate.ts index 6c2975ee3d..462c4f8be9 100644 --- a/packages/api/src/base/Decorate.ts +++ b/packages/api/src/base/Decorate.ts @@ -4,7 +4,7 @@ import { Constants, Storage } from '@polkadot/metadata/Decorated/types'; import { RpcInterface } from '@polkadot/rpc-core/types'; -import { Call, Hash, RuntimeVersion, StorageChangeSet } from '@polkadot/types/interfaces'; +import { Call, Hash, RuntimeVersion } from '@polkadot/types/interfaces'; import { AnyFunction, CallFunction, Codec, CodecArg as Arg, ITuple, InterfaceTypes, ModulesWithCalls, Registry, RegistryTypes } from '@polkadot/types/types'; import { SubmittableExtrinsic } from '../submittable/types'; import { ApiInterfaceRx, ApiOptions, ApiTypes, DecorateMethod, DecoratedRpc, DecoratedRpcSection, QueryableModuleStorage, QueryableStorage, QueryableStorageEntry, QueryableStorageMulti, QueryableStorageMultiArg, SubmittableExtrinsicFunction, SubmittableExtrinsics, SubmittableModuleExtrinsics } from '../types'; @@ -351,14 +351,12 @@ export default abstract class Decorate extends Events } return this._rpcCore.state - .queryStorage([decorated.key(...args)], ...range) - .pipe(map((result: StorageChangeSet[]): [Hash, Codec][] => - result - .filter((change): change is StorageChangeSet => !!change.changes.length) - .map(({ block, changes: [[, value]] }): [Hash, Codec] => [ - block, - this.createType(outputType, value.isSome ? value.unwrap().toHex() : undefined) - ]) + .queryStorage<[Option]>([decorated.key(...args)], ...range) + .pipe(map((result: [Hash, [Option]][]): [Hash, Codec][] => + result.map(([blockHash, [value]]): [Hash, Codec] => [ + blockHash, + this.createType(outputType, value.isSome ? value.unwrap().toHex() : undefined) + ]) )); } diff --git a/packages/api/test/api.spec.ts b/packages/api/test/api.spec.ts index f6a7210afa..b6d1072afb 100644 --- a/packages/api/test/api.spec.ts +++ b/packages/api/test/api.spec.ts @@ -4,10 +4,17 @@ import { ApiPromise, WsProvider } from '@polkadot/api'; +function createApi (): Promise { + process.env.NODE_ENV = 'test'; + + const provider = new WsProvider('wss://kusama-rpc.polkadot.io'); + + return new ApiPromise({ provider }).isReady; +} + describe.skip('misc quick tests', (): void => { it.skip('retrieves balances correctly', async (): Promise => { - const provider = new WsProvider('wss://kusama-rpc.polkadot.io'); - const api = await new ApiPromise({ provider }).isReady; + const api = await createApi(); console.error(JSON.stringify( await api.query.system.account('FPzukZw2mphWsXDqdkNzLaxnanjZEWH9i2vqwobTdtde5me') @@ -18,8 +25,7 @@ describe.skip('misc quick tests', (): void => { }); it.skip('handles map entries', async (): Promise => { - const provider = new WsProvider('wss://kusama-rpc.polkadot.io'); - const api = await new ApiPromise({ provider }).isReady; + const api = await createApi(); console.error(JSON.stringify( await api.query.indices.accounts.entries() @@ -27,7 +33,7 @@ describe.skip('misc quick tests', (): void => { }); it.skip('handles doublemap entries', async (): Promise => { - const api = await new ApiPromise().isReady; + const api = await createApi(); const activeEra = await api.query.staking.activeEra(); console.error(JSON.stringify( @@ -36,11 +42,20 @@ describe.skip('misc quick tests', (): void => { }); it.skip('does something in society', async (): Promise => { - const provider = new WsProvider('wss://kusama-rpc.polkadot.io'); - const api = await new ApiPromise({ provider }).isReady; + const api = await createApi(); console.error(JSON.stringify( await api.query.society.defenderVotes('Dab4bfYTZRUDMWjYAUQuFbDreQ9mt7nULWu3Dw7jodbzVe9') )); }); + + it.skip('allows for range queries', async (): Promise => { + 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()]) + )); + }); }); diff --git a/packages/rpc-core/src/index.ts b/packages/rpc-core/src/index.ts index 42054811ef..38fb7b5c1f 100644 --- a/packages/rpc-core/src/index.ts +++ b/packages/rpc-core/src/index.ts @@ -4,6 +4,7 @@ import { ProviderInterface, ProviderInterfaceCallback } from '@polkadot/rpc-provider/types'; import { RpcMethod, RpcSection, RpcParam } from '@polkadot/jsonrpc/types'; +import { Hash } from '@polkadot/types/interfaces'; import { AnyJson, Codec, Registry } from '@polkadot/types/types'; import { RpcInterface, RpcInterfaceMethod, UserRpc } from './types'; @@ -349,30 +350,12 @@ export default class Rpc implements RpcInterface { throw error; } } else if (method.type === 'StorageChangeSet') { - // For StorageChangeSet, the changes has the [key, value] mappings - const changes: [string, string | null][] = result.changes; - const keys = params[0] as Vec; - const withCache = keys.length !== 1; - - // multiple return values (via state.storage subscription), decode the values - // one at a time, all based on the query types. Three values can be returned - - // - Codec - There is a valid value, non-empty - // - null - The storage key is empty - return keys.reduce((results, key: StorageKey): Codec[] => { - try { - // if (changes.some(([, val]) => val && val.length > 5000)) { - // console.error(`LARGE ${key.section}.${key.method}`); - // } - - results.push(this.formatStorageSet(key, changes, withCache)); - } catch (error) { - console.error(`Unable to decode storage ${key.section}.${key.method}:`, error.message); - - throw error; - } - - return results; - }, [] as Codec[]); + return this.formatStorageSet(params[0] as Vec, result.changes); + } else if (method.type === 'Vec') { + return result.map(({ block, changes }: { block: string; changes: [string, string | null][] }): [Hash, Codec[]] => [ + this.registry.createType('Hash', block), + this.formatStorageSet(params[0] as Vec, changes) + ]); } return createTypeUnsafe(this.registry, method.type, [result]); @@ -406,7 +389,28 @@ export default class Rpc implements RpcInterface { return createTypeUnsafe(this.registry, type, [isEmpty ? meta.fallback : input], true); } - private formatStorageSet (key: StorageKey, changes: [string, string | null][], witCache: boolean): Codec { + private formatStorageSet (keys: Vec, changes: [string, string | null][]): Codec[] { + // For StorageChangeSet, the changes has the [key, value] mappings + const withCache = keys.length !== 1; + + // multiple return values (via state.storage subscription), decode the values + // one at a time, all based on the query types. Three values can be returned - + // - Codec - There is a valid value, non-empty + // - null - The storage key is empty + return keys.reduce((results: Codec[], key: StorageKey): Codec[] => { + try { + results.push(this.formatStorageSetEntry(key, changes, withCache)); + } catch (error) { + console.error(`Unable to decode storage ${key.section}.${key.method}:`, error.message); + + throw error; + } + + return results; + }, []); + } + + private formatStorageSetEntry (key: StorageKey, changes: [string, string | null][], witCache: boolean): Codec { // Fallback to Raw (i.e. just the encoding) if we don't have a specific type const type = key.outputType || 'Raw'; const hexKey = key.toHex(); diff --git a/packages/typegen/src/generate/rpc.ts b/packages/typegen/src/generate/rpc.ts index f93f77f9a7..a8ee637a06 100644 --- a/packages/typegen/src/generate/rpc.ts +++ b/packages/typegen/src/generate/rpc.ts @@ -8,6 +8,8 @@ import { TypeRegistry } from '@polkadot/types/create'; import { FOOTER, HEADER, createDocComments, createImportCode, createImports, getSimilarTypes, setImports, writeFile, indent } from '../util'; +const StorageKeyTye = 'StorageKey | string | Uint8Array | any'; + /** @internal */ export default function generateRpcTypes (dest = 'packages/api/src/augment/rpc.ts'): void { writeFile(dest, (): string => { @@ -21,19 +23,21 @@ export default function generateRpcTypes (dest = 'packages/api/src/augment/rpc.t const allMethods = Object.keys(interfaces[section].methods).sort().map((key): string => { const method = interfaces[section].methods[key]; - setImports(allDefs, imports, [method.type]); - - // FIXME These 2 are too hard to type, I give up + // These are too hard to type with generics, do manual overrides if (section === 'state') { - if (method.method === 'getStorage') { - setImports(allDefs, imports, ['Codec']); + setImports(allDefs, imports, ['Codec', 'Hash', 'StorageKey', 'Vec']); - return indent(6)('getStorage: AugmentedRpc<(key: any, block?: Hash | Uint8Array | string) => Observable>;'); + if (method.method === 'getStorage') { + return createDocComments(6, [method.description]) + indent(6)(`getStorage: AugmentedRpc<(key: ${StorageKeyTye}, block?: Hash | Uint8Array | string) => Observable>;`); + } else if (method.method === 'queryStorage') { + return createDocComments(6, [method.description]) + indent(6)(`queryStorage: AugmentedRpc<(keys: Vec | (${StorageKeyTye})[], fromBlock?: Hash | Uint8Array | string, toBlock?: Hash | Uint8Array | string) => Observable<[Hash, T][]>>;`); } else if (method.method === 'subscribeStorage') { - return indent(6)('subscribeStorage: AugmentedRpc<(keys: any[]) => Observable>;'); + return createDocComments(6, [method.description]) + indent(6)(`subscribeStorage: AugmentedRpc<(keys: Vec | (${StorageKeyTye})[]) => Observable>;`); } } + setImports(allDefs, imports, [method.type]); + const args = method.params.map((param): string => { const similarTypes = getSimilarTypes(definitions, registry, param.type, imports);