From c4323d5e949d89b7e413444bd12c499bc8546ab7 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Sat, 29 Aug 2020 15:46:42 +0200 Subject: [PATCH] Add swapRegistry(hash?) to initlize clean registry (#2534) * Add swapRegistry(hash?) to initlize clean registry * Cater for genesisHash queries --- CHANGELOG.md | 1 + .../src/imOnline/receivedHeartbeats.ts | 3 +- packages/api-derive/src/staking/erasPoints.ts | 3 +- packages/api/src/base/Decorate.ts | 35 +++++--- packages/api/src/base/Init.ts | 89 ++++++++++++++++--- packages/rpc-core/src/index.ts | 41 +++++---- packages/types/src/create/registry.ts | 4 +- 7 files changed, 133 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a3c100010..7972da3743 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - **Important** Substrate changed the `RewardDestination` enum with an extra field for payout-to-any account. If on an older chain consider adding `RewardDestination: 'RewardDestinationTo257'` (Newer chains with this requirement met in their Substrate version should update the API for support) - Add `toBigInt()` (JS built-in `BigInt`) on `Int/Uint`, & `Compact<*>` types +- Add `api.swapRegistry(blockHash?)` to cleanly swap to types for a specific block (no param swaps to default) - `derive.democracy.locks` now returns delegated locks for an account as well ## 1.30.1 Aug 24, 2020 diff --git a/packages/api-derive/src/imOnline/receivedHeartbeats.ts b/packages/api-derive/src/imOnline/receivedHeartbeats.ts index 363d600aac..a309acc5e2 100644 --- a/packages/api-derive/src/imOnline/receivedHeartbeats.ts +++ b/packages/api-derive/src/imOnline/receivedHeartbeats.ts @@ -9,6 +9,7 @@ import { Observable, of, combineLatest } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; import { ApiInterfaceRx } from '@polkadot/api/types'; import { Bytes, Option, u32 } from '@polkadot/types'; +import { BN_ZERO } from '@polkadot/util'; import { memo } from '../util'; @@ -34,7 +35,7 @@ export function receivedHeartbeats (api: ApiInterfaceRx): () => Observable points.gtn(0)) + .filter(([, points]) => points.gt(BN_ZERO)) .reduce((result: DeriveEraValPoints, [validatorId, points]): DeriveEraValPoints => { result[validatorId.toString()] = points; diff --git a/packages/api/src/base/Decorate.ts b/packages/api/src/base/Decorate.ts index 5ceaeaa848..9daf102527 100644 --- a/packages/api/src/base/Decorate.ts +++ b/packages/api/src/base/Decorate.ts @@ -45,7 +45,7 @@ const PAGE_SIZE_VALS = PAGE_SIZE_KEYS; const l = logger('api/init'); export default abstract class Decorate extends Events { - public readonly registry: Registry; + #registry: Registry; // HACK Use BN import so decorateDerive works... yes, wtf. protected __phantom = new BN(0); @@ -127,7 +127,7 @@ export default abstract class Decorate extends Events constructor (options: ApiOptions, type: ApiTypes, decorateMethod: DecorateMethod) { super(); - this.registry = options.registry || new TypeRegistry(); + this.#registry = options.registry || new TypeRegistry(); const thisProvider = options.source ? options.source._rpcCore.provider.clone() @@ -136,24 +136,31 @@ export default abstract class Decorate extends Events this._decorateMethod = decorateMethod; this._options = options; this._type = type; - this._rpcCore = new RpcCore(this.registry, thisProvider, this._options.rpc); + this._rpcCore = new RpcCore(this.#registry, thisProvider, this._options.rpc); this._isConnected = new BehaviorSubject(this._rpcCore.provider.isConnected()); this._rx.hasSubscriptions = this._rpcCore.provider.hasSubscriptions; - this._rx.registry = this.registry; + this._rx.registry = this.#registry; + } + + /** + * @description Return the current used registry + */ + public get registry (): Registry { + return this.#registry; } /** * @description Creates an instance of a type as registered */ public createType (type: K, ...params: unknown[]): InterfaceTypes[K] { - return this.registry.createType(type, ...params); + return this.#registry.createType(type, ...params); } /** * @description Register additional user-defined of chain-specific types in the type registry */ public registerTypes (types?: RegistryTypes): void { - types && this.registry.register(types); + types && this.#registry.register(types); } /** @@ -163,8 +170,8 @@ export default abstract class Decorate extends Events return this._rpcCore.provider.hasSubscriptions; } - public injectMetadata (metadata: Metadata, fromEmpty?: boolean): void { - const decoratedMeta = new DecoratedMeta(this.registry, metadata); + public injectMetadata (metadata: Metadata, fromEmpty?: boolean, registry?: Registry): void { + const decoratedMeta = new DecoratedMeta(registry || this.#registry, metadata); if (fromEmpty || !this._extrinsics) { this._extrinsics = this._decorateExtrinsics(decoratedMeta.tx, this._decorateMethod); @@ -183,6 +190,14 @@ export default abstract class Decorate extends Events augmentObject('', decoratedMeta.consts, this._rx.consts, fromEmpty); } + public setRegistry (registry: Registry): Registry { + this.#registry = registry; + + this._rpcCore.setRegistry(registry); + + return this.#registry; + } + private _decorateFunctionMeta (input: MetaDecoration, output: MetaDecoration): MetaDecoration { output.meta = input.meta; output.method = input.method; @@ -461,8 +476,8 @@ export default abstract class Decorate extends Events } const nextResult = vals.length - ? new LinkageResult(this.registry, [keys[0].constructor as any, keys], [vals[0].constructor as any, vals]) - : new LinkageResult(this.registry, [Null, []], [Null, []]); + ? new LinkageResult(this.#registry, [keys[0].constructor as any, keys], [vals[0].constructor as any, vals]) + : new LinkageResult(this.#registry, [Null, []], [Null, []]); // we set our result into a subject so we have a single observable for // which the value changes over time. Initially create, follow-up next diff --git a/packages/api/src/base/Init.ts b/packages/api/src/base/Init.ts index 4ddc5fe976..baa586e6a0 100644 --- a/packages/api/src/base/Init.ts +++ b/packages/api/src/base/Init.ts @@ -3,18 +3,25 @@ // of the Apache-2.0 license. See the LICENSE file for details. import { SignedBlock, RuntimeVersion } from '@polkadot/types/interfaces'; +import { Registry } from '@polkadot/types/types'; import { ApiBase, ApiOptions, ApiTypes, DecorateMethod } from '../types'; import { Observable, Subscription, of } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; -import { Metadata, Text } from '@polkadot/types'; +import { Metadata, Text, u32, TypeRegistry } from '@polkadot/types'; import { LATEST_EXTRINSIC_VERSION } from '@polkadot/types/extrinsic/Extrinsic'; import { getMetadataTypes, getSpecAlias, getSpecTypes } from '@polkadot/types-known'; -import { logger } from '@polkadot/util'; +import { BN_ZERO, assert, logger } from '@polkadot/util'; import { cryptoWaitReady } from '@polkadot/util-crypto'; import Decorate from './Decorate'; +interface VersionedRegistry { + isDefault: boolean; + registry: Registry; + specVersion: u32; +} + const KEEPALIVE_INTERVAL = 15000; const l = logger('api/init'); @@ -22,6 +29,10 @@ const l = logger('api/init'); export default abstract class Init extends Decorate { #healthTimer: NodeJS.Timeout | null = null; + #registries: VersionedRegistry[] = []; + + #registryDefault: Registry; + #updateSub?: Subscription; constructor (options: ApiOptions, type: ApiTypes, decorateMethod: DecorateMethod) { @@ -31,14 +42,10 @@ export default abstract class Init extends Decorate extends Decorate { + if (!blockHash) { + return this.setRegistry(this.#registryDefault); + } + + const { parentHash } = this._genesisHash?.eq(blockHash) + ? { parentHash: this._genesisHash } + : await this._rpcCore.chain.getHeader(blockHash).toPromise(); + + assert(!parentHash.isEmpty, 'Unable to determine parent hash from supplied block'); + + const version = await this._rpcCore.state.getRuntimeVersion(parentHash).toPromise(); + const existing = this.#registries.find(({ specVersion }) => specVersion.eq(version.specVersion)); + + if (existing) { + return this.setRegistry(existing.registry); + } + + const registry = this.setRegistry(new TypeRegistry()); + + registry.setChainProperties(this.#registryDefault.getChainProperties()); + this._setKnowRegistryTypes(this._options); + this.registerTypes(getSpecTypes(registry, this._runtimeChain as Text, version.specName, version.specVersion)); + + if (registry.knownTypes.typesBundle) { + this._adjustBundleTypes(this._runtimeChain as Text, version.specName); + } + + // retrieve the metadata now that we have all types set + const metadata = await this._rpcCore.state.getMetadata(parentHash).toPromise(); + + // TODO: Not convinced (yet) that we really want to re-decorate, keep on ice since it does muddle-up + // this.injectMetadata(metadata, false); + registry.setMetadata(metadata); + + this.#registries.push({ isDefault: false, registry, specVersion: version.specVersion }); + + return registry; + } + protected async _loadMeta (): Promise { const genesisHash = await this._rpcCore.chain.getBlockHash(0).toPromise(); @@ -138,8 +192,14 @@ export default abstract class Init extends Decorate isDefault); + + assert(thisRegistry, 'Initialization error, cannot find the default registry'); + + thisRegistry.specVersion = version.specVersion; + thisRegistry.registry.register(getSpecTypes(thisRegistry.registry, this._runtimeChain as Text, version.specName, version.specVersion)); + this.injectMetadata(metadata, false, thisRegistry.registry); return true; }) @@ -187,6 +247,11 @@ export default abstract class Init extends Decorate extends Decorate(); public readonly mapping = new Map(); public readonly provider: ProviderInterface; - public readonly registry: Registry; - public readonly sections: string[] = []; // Ok, this is quite horrible - we really should not be using the ! here, but we are actually assigning @@ -113,7 +113,7 @@ export default class Rpc implements RpcInterface { // eslint-disable-next-line @typescript-eslint/unbound-method assert(provider && isFunction(provider.send), 'Expected Provider to API create'); - this.registry = registry; + this.#registry = registry; this.provider = provider; const sectionNames = Object.keys(jsonrpc); @@ -131,6 +131,13 @@ export default class Rpc implements RpcInterface { this.provider.disconnect(); } + /** + * @description Set the registry to use for this RPC + */ + public setRegistry (registry: Registry): void { + this.#registry = registry; + } + public addUserInterfaces
(userRpc: Record>): void { // add any extra user-defined sections this.sections.push(...Object.keys(userRpc).filter((key) => !this.sections.includes(key))); @@ -210,7 +217,7 @@ export default class Rpc implements RpcInterface { ), map(([params, result]) => isRaw - ? this.registry.createType('Raw', result) + ? this.#registry.createType('Raw', result) : this._formatOutput(method, def, params, result) ), catchError((error): any => { @@ -274,7 +281,7 @@ export default class Rpc implements RpcInterface { try { observer.next( isRaw - ? this.registry.createType('Raw', result) + ? this.#registry.createType('Raw', result) : this._formatOutput(method, def, params, result) ); } catch (error) { @@ -334,7 +341,7 @@ export default class Rpc implements RpcInterface { assert(inputs.length >= reqArgCount && inputs.length <= def.params.length, `Expected ${def.params.length} parameters${optText}, ${inputs.length} found instead`); return inputs.map((input, index): Codec => - createTypeUnsafe(this.registry, def.params[index].type, [input]) + createTypeUnsafe(this.#registry, def.params[index].type, [input]) ); } @@ -360,10 +367,10 @@ export default class Rpc implements RpcInterface { return keys ? this._formatStorageSet(keys, (result as StorageChangeSetJSON).changes) - : this.registry.createType('StorageChangeSet', result); + : this.#registry.createType('StorageChangeSet', result); } else if (rpc.type === 'Vec') { const mapped = (result as StorageChangeSetJSON[]).map(({ block, changes }): [Hash, Codec[]] => [ - this.registry.createType('Hash', block), + this.#registry.createType('Hash', block), this._formatStorageSet(params[0] as Vec, changes) ]); @@ -373,7 +380,7 @@ export default class Rpc implements RpcInterface { : mapped as unknown as Codec[]; } - return createTypeUnsafe(this.registry, rpc.type, [result]); + return createTypeUnsafe(this.#registry, rpc.type, [result]); } private _formatStorageData (key: StorageKey, value: string | null): Codec { @@ -393,15 +400,15 @@ export default class Rpc implements RpcInterface { if (meta.modifier.isOptional) { return new Option( - this.registry, - createClass(this.registry, type), + this.#registry, + createClass(this.#registry, type), isEmpty ? null - : createTypeUnsafe(this.registry, type, [input], true) + : createTypeUnsafe(this.#registry, type, [input], true) ); } - return createTypeUnsafe(this.registry, type, [ + return createTypeUnsafe(this.#registry, type, [ isEmpty ? meta.fallback ? hexToU8a(meta.fallback.toHex()) @@ -457,15 +464,15 @@ export default class Rpc implements RpcInterface { if (meta.modifier.isOptional) { return new Option( - this.registry, - createClass(this.registry, type), + this.#registry, + createClass(this.#registry, type), isEmpty ? null - : createTypeUnsafe(this.registry, type, [input], true) + : createTypeUnsafe(this.#registry, type, [input], true) ); } - return createTypeUnsafe(this.registry, type, [ + return createTypeUnsafe(this.#registry, type, [ isEmpty ? meta.fallback ? hexToU8a(meta.fallback.toHex()) diff --git a/packages/types/src/create/registry.ts b/packages/types/src/create/registry.ts index b1c7bb3bee..b2531c513f 100644 --- a/packages/types/src/create/registry.ts +++ b/packages/types/src/create/registry.ts @@ -8,7 +8,7 @@ import { ChainProperties, DispatchErrorModule } from '../interfaces/types'; import { CallFunction, Codec, Constructor, InterfaceTypes, RegistryError, RegistryTypes, Registry, RegistryMetadata, RegisteredTypes, TypeDef } from '../types'; import extrinsicsFromMeta from '@polkadot/metadata/Decorated/extrinsics/fromMetadata'; -import { assert, formatBalance, isFunction, isString, isU8a, isUndefined, stringCamelCase, u8aToHex } from '@polkadot/util'; +import { BN_ZERO, assert, formatBalance, isFunction, isString, isU8a, isUndefined, stringCamelCase, u8aToHex } from '@polkadot/util'; import { blake2AsU8a } from '@polkadot/util-crypto'; import Raw from '../codec/Raw'; @@ -341,7 +341,7 @@ export class TypeRegistry implements Registry { // setup the available extensions this.setSignedExtensions( signedExtensions || ( - metadata.asLatest.extrinsic.version.gtn(0) + metadata.asLatest.extrinsic.version.gt(BN_ZERO) ? metadata.asLatest.extrinsic.signedExtensions.map((key) => key.toString()) : defaultExtensions )