diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b9c4678d7..9d26b4a579 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Contributed: Changes: - Cater for `DispatchError` with `error` as `[u8; 4]` +- Add in-flight cache for parallel same-version metadata queries ## 7.14.3 Mar 28, 2022 diff --git a/packages/api/src/base/Init.ts b/packages/api/src/base/Init.ts index c1297d406d..5a4463a514 100644 --- a/packages/api/src/base/Init.ts +++ b/packages/api/src/base/Init.ts @@ -4,7 +4,7 @@ import type { Observable, Subscription } from 'rxjs'; import type { Text } from '@polkadot/types'; import type { ExtDef } from '@polkadot/types/extrinsic/signedExtensions/types'; -import type { ChainProperties, Hash, RuntimeVersion, RuntimeVersionPartial } from '@polkadot/types/interfaces'; +import type { ChainProperties, Hash, HeaderPartial, RuntimeVersion, RuntimeVersionPartial } from '@polkadot/types/interfaces'; import type { Registry } from '@polkadot/types/types'; import type { BN } from '@polkadot/util'; import type { HexString } from '@polkadot/util/types'; @@ -35,7 +35,7 @@ export abstract class Init extends Decorate { #updateSub?: Subscription | null = null; - #waitingRegistries: Record>> = {}; + #waitingRegistries: Record>> = {}; constructor (options: ApiOptions, type: ApiTypes, decorateMethod: DecorateMethod) { super(options, type, decorateMethod); @@ -120,6 +120,44 @@ export abstract class Init extends Decorate { return this._createDecorated(registry, true, null, u8aHash).decoratedApi; } + private async _createBlockRegistry (blockHash: Uint8Array, header: HeaderPartial, version: RuntimeVersionPartial): Promise> { + const registry = new TypeRegistry(blockHash); + const metadata = new Metadata(registry, + await firstValueFrom(this._rpcCore.state.getMetadata.raw(header.parentHash)) + ); + + this._initRegistry(registry, this._runtimeChain as Text, version, metadata); + + // add our new registry + const result = { lastBlockHash: blockHash, metadata, registry, specName: version.specName, specVersion: version.specVersion }; + + this.#registries.push(result); + + return result; + } + + private _cacheBlockRegistryProgress (key: HexString, creator: () => Promise>): Promise> { + // look for waiting resolves + let waiting = this.#waitingRegistries[key]; + + if (isUndefined(waiting)) { + // nothing waiting, construct new + waiting = this.#waitingRegistries[key] = new Promise>((resolve, reject): void => { + creator() + .then((registry): void => { + delete this.#waitingRegistries[key]; + resolve(registry); + }) + .catch((error): void => { + delete this.#waitingRegistries[key]; + reject(error); + }); + }); + } + + return waiting; + } + private _getBlockRegistryViaVersion (blockHash: Uint8Array, version?: RuntimeVersionPartial): VersionedRegistry | null { if (version) { // check for pre-existing registries. We also check specName, e.g. it @@ -139,7 +177,7 @@ export abstract class Init extends Decorate { return null; } - private async _getBlockRegistry (blockHash: Uint8Array): Promise> { + private async _getBlockRegistryViaHash (blockHash: Uint8Array): Promise> { // ensure we have everything required assert(this._genesisHash && this._runtimeVersion, 'Cannot retrieve data on an uninitialized chain'); @@ -161,62 +199,28 @@ export abstract class Init extends Decorate { : await firstValueFrom(this._rpcCore.state.getRuntimeVersion.raw(header.parentHash)) ); - const existingViaVersion = this._getBlockRegistryViaVersion(blockHash, version); - - if (existingViaVersion) { - return existingViaVersion; - } - - // nothing has been found, construct new - const registry = new TypeRegistry(blockHash); - const metadata = new Metadata(registry, - await firstValueFrom(this._rpcCore.state.getMetadata.raw(header.parentHash)) + return ( + // try to find via version + this._getBlockRegistryViaVersion(blockHash, version) || + // return new or in-flight result + await this._cacheBlockRegistryProgress(version.toHex(), () => this._createBlockRegistry(blockHash, header, version)) ); - - this._initRegistry(registry, this._runtimeChain as Text, version, metadata); - - // add our new registry - const result = { lastBlockHash: blockHash, metadata, registry, specName: version.specName, specVersion: version.specVersion }; - - this.#registries.push(result); - - return result; } /** * @description Sets up a registry based on the block hash defined */ public async getBlockRegistry (blockHash: Uint8Array, knownVersion?: RuntimeVersion): Promise> { - const existingViaHash = this.#registries.find(({ lastBlockHash }) => - lastBlockHash && u8aEq(lastBlockHash, blockHash) + return ( + // try to find via blockHash + this.#registries.find(({ lastBlockHash }) => + lastBlockHash && u8aEq(lastBlockHash, blockHash) + ) || + // try to find via version + this._getBlockRegistryViaVersion(blockHash, knownVersion) || + // return new or in-flight result + await this._cacheBlockRegistryProgress(u8aToHex(blockHash), () => this._getBlockRegistryViaHash(blockHash)) ); - - if (existingViaHash) { - return existingViaHash; - } - - const existingViaVersion = this._getBlockRegistryViaVersion(blockHash, knownVersion); - - if (existingViaVersion) { - return existingViaVersion; - } - - const blockHashHex = u8aToHex(blockHash); - let waiting = this.#waitingRegistries[blockHashHex]; - - if (isUndefined(waiting)) { - waiting = this._getBlockRegistry(blockHash); - this.#waitingRegistries[blockHashHex] = waiting; - - // when we have resolved, remove this from the waiting list - waiting - .then((): void => { - delete this.#waitingRegistries[blockHashHex]; - }) - .catch(() => undefined); - } - - return waiting; } protected async _loadMeta (): Promise {