From 3c9b7de6c7fa214207a914397cdc03ceb96a6695 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Sun, 7 Mar 2021 16:00:30 +0100 Subject: [PATCH] Add createdAtHash to Codec interface (#3276) * Add createdAtHash to Codec interface * Adjust linkedHash (also on ) * createdAtHash --- .../src/chain/subscribeNewBlocks.ts | 14 ++-- .../api-derive/src/chain/subscribeNewHeads.ts | 13 ++-- .../api-derive/src/type/HeaderExtended.ts | 1 + .../src/type/SignedBlockExtended.ts | 5 +- packages/api/src/base/Init.ts | 2 +- packages/rpc-core/src/index.ts | 60 ++++++++-------- packages/types/src/codec/AbstractArray.ts | 4 +- packages/types/src/codec/AbstractInt.ts | 4 +- packages/types/src/codec/BTreeSet.ts | 4 +- packages/types/src/codec/Base.ts | 4 +- packages/types/src/codec/Compact.ts | 4 +- packages/types/src/codec/Date.ts | 4 +- packages/types/src/codec/Enum.ts | 4 +- packages/types/src/codec/Json.ts | 4 +- packages/types/src/codec/Map.ts | 4 +- packages/types/src/codec/Option.ts | 4 +- packages/types/src/codec/Raw.ts | 4 +- packages/types/src/codec/Set.ts | 4 +- packages/types/src/codec/Struct.ts | 4 +- packages/types/src/create/createType.ts | 68 +++++++++---------- packages/types/src/create/registry.ts | 10 ++- packages/types/src/primitive/Bool.ts | 4 +- packages/types/src/primitive/Null.ts | 4 +- packages/types/src/primitive/Text.ts | 4 +- packages/types/src/types/codec.ts | 9 ++- packages/types/src/types/registry.ts | 4 +- 26 files changed, 154 insertions(+), 96 deletions(-) diff --git a/packages/api-derive/src/chain/subscribeNewBlocks.ts b/packages/api-derive/src/chain/subscribeNewBlocks.ts index 5a6d759501..7d3af1cb9c 100644 --- a/packages/api-derive/src/chain/subscribeNewBlocks.ts +++ b/packages/api-derive/src/chain/subscribeNewBlocks.ts @@ -2,8 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 import type { ApiInterfaceRx } from '@polkadot/api/types'; -import type { AccountId, EventRecord, SignedBlock } from '@polkadot/types/interfaces'; +import type { Vec } from '@polkadot/types'; +import type { EventRecord, SignedBlock } from '@polkadot/types/interfaces'; import type { Observable } from '@polkadot/x-rxjs'; +import type { HeaderExtended } from '../type'; import { combineLatest, of } from '@polkadot/x-rxjs'; import { map, switchMap } from '@polkadot/x-rxjs/operators'; @@ -18,17 +20,17 @@ import { memo } from '../util'; export function subscribeNewBlocks (instanceId: string, api: ApiInterfaceRx): () => Observable { return memo(instanceId, (): Observable => api.derive.chain.subscribeNewHeads().pipe( - switchMap((header): Observable<[SignedBlock, EventRecord[], AccountId[] | undefined]> => { - const blockHash = header.hash; + switchMap((header): Observable<[SignedBlock, Vec, HeaderExtended]> => { + const blockHash = header.createdAtHash || header.hash; return combineLatest( api.rpc.chain.getBlock(blockHash), api.query.system.events.at(blockHash), - of(header.validators) + of(header) ); }), - map(([block, events, validators]) => - new SignedBlockExtended(api.registry, block, events, validators) + map(([block, events, header]) => + new SignedBlockExtended(api.registry, block, events, header.validators) ) ) ); diff --git a/packages/api-derive/src/chain/subscribeNewHeads.ts b/packages/api-derive/src/chain/subscribeNewHeads.ts index 39f9b77b3e..f0c8c63842 100644 --- a/packages/api-derive/src/chain/subscribeNewHeads.ts +++ b/packages/api-derive/src/chain/subscribeNewHeads.ts @@ -29,10 +29,13 @@ export function subscribeNewHeads (instanceId: string, api: ApiInterfaceRx): () api.rpc.chain.subscribeNewHeads(), api.query.session ? api.query.session.validators() - : of([]) + : of(undefined) ]).pipe( - map(([header, validators]): HeaderExtended => - new HeaderExtended(api.registry, header, validators) - ) - )); + map(([header, validators]): HeaderExtended => { + header.createdAtHash = header.hash; + + return new HeaderExtended(api.registry, header, validators); + }) + ) + ); } diff --git a/packages/api-derive/src/type/HeaderExtended.ts b/packages/api-derive/src/type/HeaderExtended.ts index b9e53c4e86..1cf6fbcd88 100644 --- a/packages/api-derive/src/type/HeaderExtended.ts +++ b/packages/api-derive/src/type/HeaderExtended.ts @@ -26,6 +26,7 @@ export class HeaderExtended extends _Header { this.#author = extractAuthor(this.digest, validators); this.#validators = validators; + this.createdAtHash = header?.createdAtHash; } /** diff --git a/packages/api-derive/src/type/SignedBlockExtended.ts b/packages/api-derive/src/type/SignedBlockExtended.ts index 0836c67cbd..52d026ac39 100644 --- a/packages/api-derive/src/type/SignedBlockExtended.ts +++ b/packages/api-derive/src/type/SignedBlockExtended.ts @@ -53,12 +53,13 @@ export class SignedBlockExtended extends _SignedBlock { readonly #events: EventRecord[]; readonly #extrinsics: TxWithEvent[]; - constructor (registry: Registry, block?: SignedBlock, events?: EventRecord[], sessionValidators?: AccountId[]) { + constructor (registry: Registry, block?: SignedBlock, events?: EventRecord[], validators?: AccountId[]) { super(registry, block); - this.#author = extractAuthor(this.block.header.digest, sessionValidators); + this.#author = extractAuthor(this.block.header.digest, validators); this.#events = events || ([] as EventRecord[]); this.#extrinsics = mapExtrinsics(this.block.extrinsics, this.#events); + this.createdAtHash = block?.createdAtHash; } /** diff --git a/packages/api/src/base/Init.ts b/packages/api/src/base/Init.ts index 4a54e94345..ab8beac3e0 100644 --- a/packages/api/src/base/Init.ts +++ b/packages/api/src/base/Init.ts @@ -139,7 +139,7 @@ export abstract class Init extends Decorate { // nothing has been found, construct new const metadata = await this._rpcCore.state.getMetadata(header.parentHash).toPromise(); - const registry = this._initRegistry(new TypeRegistry(), this._runtimeChain as Text, version, metadata); + const registry = this._initRegistry(new TypeRegistry(blockHash), this._runtimeChain as Text, version, metadata); // For now, since this is new we ignore the capability lookups (this could be useful once proven) // this._detectCapabilities(registry, blockHash); diff --git a/packages/rpc-core/src/index.ts b/packages/rpc-core/src/index.ts index 7deed71e8f..3a84405f13 100644 --- a/packages/rpc-core/src/index.ts +++ b/packages/rpc-core/src/index.ts @@ -244,17 +244,17 @@ export class RpcCore implements RpcInterface { // execute the RPC call, doing a registry swap for historic as applicable const callWithRegistry = async (outputAs: OutputType, values: any[]): Promise => { - const hash = hashIndex === -1 - ? undefined + const blockHash = hashIndex === -1 + ? null : values[hashIndex] as Uint8Array; - const { registry } = hash && this.#getBlockRegistry - ? await this.#getBlockRegistry(hash) + const { registry } = blockHash && this.#getBlockRegistry + ? await this.#getBlockRegistry(blockHash) : { registry: this.#registryDefault }; - const params = this._formatInputs(registry, def, values); + const params = this._formatInputs(registry, null, def, values); const data = await this.provider.send(rpcName, params.map((param): AnyJson => param.toJSON())) as AnyJson; return outputAs === 'scale' - ? this._formatOutput(registry, method, def, params, data) + ? this._formatOutput(registry, blockHash, method, def, params, data) : registry.createType(outputAs === 'raw' ? 'Raw' : 'Json', data); }; @@ -324,7 +324,7 @@ export class RpcCore implements RpcInterface { }; try { - const params = this._formatInputs(registry, def, values); + const params = this._formatInputs(registry, null, def, values); const paramsJson = params.map((param): AnyJson => param.toJSON()); const update = (error?: Error | null, result?: any): void => { @@ -337,7 +337,7 @@ export class RpcCore implements RpcInterface { try { observer.next( outputAs === 'scale' - ? this._formatOutput(registry, method, def, params, result) + ? this._formatOutput(registry, null, method, def, params, result) : registry.createType(outputAs === 'raw' ? 'Raw' : 'Json', result) ); } catch (error) { @@ -372,8 +372,8 @@ export class RpcCore implements RpcInterface { return memoized; } - private _formatInputs (registry: Registry, def: DefinitionRpc, inputs: any[]): Codec[] { - const reqArgCount = def.params.filter(({ isOptional }): boolean => !isOptional).length; + private _formatInputs (registry: Registry, blockHash: Uint8Array | string | null | undefined, def: DefinitionRpc, inputs: any[]): Codec[] { + const reqArgCount = def.params.filter(({ isOptional }) => !isOptional).length; const optText = reqArgCount === def.params.length ? '' : ` (${def.params.length - reqArgCount} optional)`; @@ -381,25 +381,25 @@ export class RpcCore 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(registry, def.params[index].type, [input]) + createTypeUnsafe(registry, def.params[index].type, [input], { blockHash }) ); } - private _formatOutput (registry: Registry, method: string, rpc: DefinitionRpc, params: Codec[], result?: any): Codec | Codec[] { + private _formatOutput (registry: Registry, blockHash: Uint8Array | string | null | undefined, method: string, rpc: DefinitionRpc, params: Codec[], result?: any): Codec | Codec[] { if (rpc.type === 'StorageData') { const key = params[0] as StorageKey; - return this._formatStorageData(registry, key, result); + return this._formatStorageData(registry, blockHash, key, result); } else if (rpc.type === 'StorageChangeSet') { const keys = params[0] as Vec; return keys - ? this._formatStorageSet(registry, keys, (result as StorageChangeSetJSON).changes) + ? this._formatStorageSet(registry, (result as StorageChangeSetJSON).block, keys, (result as StorageChangeSetJSON).changes) : registry.createType('StorageChangeSet', result); } else if (rpc.type === 'Vec') { const mapped = (result as StorageChangeSetJSON[]).map(({ block, changes }): [Hash, Codec[]] => [ registry.createType('Hash', block), - this._formatStorageSet(registry, params[0] as Vec, changes) + this._formatStorageSet(registry, block, params[0] as Vec, changes) ]); // we only query at a specific block, not a range - flatten @@ -408,10 +408,10 @@ export class RpcCore implements RpcInterface { : mapped as unknown as Codec[]; } - return createTypeUnsafe(registry, rpc.type, [result]); + return createTypeUnsafe(registry, rpc.type, [result], { blockHash }); } - private _formatStorageData (registry: Registry, key: StorageKey, value: string | null): Codec { + private _formatStorageData (registry: Registry, blockHash: Uint8Array | string | null | undefined, key: StorageKey, value: string | null): Codec { const isEmpty = isNull(value); // we convert to Uint8Array since it maps to the raw encoding, all @@ -422,10 +422,10 @@ export class RpcCore implements RpcInterface { ? value : u8aToU8a(value); - return this._newType(registry, key, input, isEmpty); + return this._newType(registry, blockHash, key, input, isEmpty); } - private _formatStorageSet (registry: Registry, keys: Vec, changes: [string, string | null][]): Codec[] { + private _formatStorageSet (registry: Registry, blockHash: string, keys: Vec, changes: [string, string | null][]): Codec[] { // For StorageChangeSet, the changes has the [key, value] mappings const withCache = keys.length !== 1; @@ -434,13 +434,13 @@ export class RpcCore implements RpcInterface { // - Codec - There is a valid value, non-empty // - null - The storage key is empty return keys.reduce((results: Codec[], key: StorageKey, index): Codec[] => { - results.push(this._formatStorageSetEntry(registry, key, changes, withCache, index)); + results.push(this._formatStorageSetEntry(registry, blockHash, key, changes, withCache, index)); return results; }, []); } - private _formatStorageSetEntry (registry: Registry, key: StorageKey, changes: [string, string | null][], witCache: boolean, entryIndex: number): Codec { + private _formatStorageSetEntry (registry: Registry, blockHash: string, key: StorageKey, changes: [string, string | null][], witCache: boolean, entryIndex: number): Codec { const hexKey = key.toHex(); const found = changes.find(([key]) => key === hexKey); @@ -461,10 +461,10 @@ export class RpcCore implements RpcInterface { // will increase memory beyond what is allowed. this.#storageCache.set(hexKey, value); - return this._newType(registry, key, input, isEmpty, entryIndex); + return this._newType(registry, blockHash, key, input, isEmpty, entryIndex); } - private _newType (registry: Registry, key: StorageKey, input: string | Uint8Array | null, isEmpty: boolean, entryIndex = -1): Codec { + private _newType (registry: Registry, blockHash: Uint8Array | string | null | undefined, key: StorageKey, input: string | Uint8Array | null, isEmpty: boolean, entryIndex = -1): Codec { // single return value (via state.getStorage), decode the value based on the // outputType that we have specified. Fallback to Raw on nothing const type = key.outputType || 'Raw'; @@ -478,13 +478,19 @@ export class RpcCore implements RpcInterface { if (!isEmpty) { try { - inner = createTypeUnsafe(registry, type, [input], { isPedantic: true }); + inner = createTypeUnsafe(registry, type, [input], { blockHash, isPedantic: true }); } catch (error) { l.error(`Unable to decode storage ${key.section || 'unknown'}.${key.method || 'unknown'}:${entryNum}`, (error as Error).message); } } - return new Option(registry, createClass(registry, type), inner); + const option = new Option(registry, createClass(registry, type), inner); + + if (blockHash) { + option.createdAtHash = registry.createType('Hash', blockHash); + } + + return option; } try { @@ -494,11 +500,11 @@ export class RpcCore implements RpcInterface { ? hexToU8a(meta.fallback.toHex()) : undefined : input - ], { isPedantic: true }); + ], { blockHash, isPedantic: true }); } catch (error) { l.error(`Unable to decode storage ${key.section || 'unknown'}.${key.method || 'unknown'}:${entryNum}`, (error as Error).message); - return registry.createType('Raw', input); + return createTypeUnsafe(registry, 'Raw', [input], { blockHash }); } } } diff --git a/packages/types/src/codec/AbstractArray.ts b/packages/types/src/codec/AbstractArray.ts index 382f6b98e4..22e9fef91a 100644 --- a/packages/types/src/codec/AbstractArray.ts +++ b/packages/types/src/codec/AbstractArray.ts @@ -1,7 +1,7 @@ // Copyright 2017-2021 @polkadot/types authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { CodecHash } from '../interfaces/runtime'; +import type { CodecHash, Hash } from '../interfaces/runtime'; import type { AnyJson, Codec, Registry } from '../types'; import { compactToU8a, u8aConcat, u8aToHex } from '@polkadot/util'; @@ -18,6 +18,8 @@ import { compareArray } from './utils'; export abstract class AbstractArray extends Array implements Codec { public readonly registry: Registry; + public createdAtHash?: Hash; + protected constructor (registry: Registry, ...values: T[]) { super(...values); diff --git a/packages/types/src/codec/AbstractInt.ts b/packages/types/src/codec/AbstractInt.ts index 5173b50e4c..5e4c8848b3 100644 --- a/packages/types/src/codec/AbstractInt.ts +++ b/packages/types/src/codec/AbstractInt.ts @@ -1,7 +1,7 @@ // Copyright 2017-2021 @polkadot/types authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { CodecHash } from '../interfaces/runtime'; +import type { CodecHash, Hash } from '../interfaces/runtime'; import type { AnyNumber, Codec, Registry } from '../types'; import type { UIntBitLength } from './types'; @@ -67,6 +67,8 @@ function decodeAbstractInt (value: AnyNumber, bitLength: UIntBitLength, isNegati export abstract class AbstractInt extends BN implements Codec { public readonly registry: Registry; + public createdAtHash?: Hash; + readonly #bitLength: UIntBitLength; readonly #isSigned: boolean; diff --git a/packages/types/src/codec/BTreeSet.ts b/packages/types/src/codec/BTreeSet.ts index c448e7115c..b4016a87cc 100644 --- a/packages/types/src/codec/BTreeSet.ts +++ b/packages/types/src/codec/BTreeSet.ts @@ -1,7 +1,7 @@ // Copyright 2017-2021 @polkadot/types authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { CodecHash } from '../interfaces/runtime'; +import type { CodecHash, Hash } from '../interfaces/runtime'; import type { AnyJson, Codec, Constructor, InterfaceTypes, Registry } from '../types'; import { compactFromU8a, compactToU8a, isHex, isU8a, logger, u8aConcat, u8aToHex, u8aToU8a } from '@polkadot/util'; @@ -79,6 +79,8 @@ function decodeSet (registry: Registry, valType: Constr export class BTreeSet extends Set implements Codec { public readonly registry: Registry; + public createdAtHash?: Hash; + readonly #ValClass: Constructor; constructor (registry: Registry, valType: Constructor | keyof InterfaceTypes, rawValue?: Uint8Array | string | string[] | Set) { diff --git a/packages/types/src/codec/Base.ts b/packages/types/src/codec/Base.ts index fe28cee0b5..e874cf86f3 100644 --- a/packages/types/src/codec/Base.ts +++ b/packages/types/src/codec/Base.ts @@ -1,7 +1,7 @@ // Copyright 2017-2021 @polkadot/types authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { CodecHash } from '../interfaces/runtime'; +import type { CodecHash, Hash } from '../interfaces/runtime'; import type { AnyJson, BareOpts, Codec, Registry } from '../types'; /** @@ -11,6 +11,8 @@ import type { AnyJson, BareOpts, Codec, Registry } from '../types'; export abstract class Base implements Codec { public readonly registry: Registry; + public createdAtHash?: Hash; + protected readonly _raw: T; protected constructor (registry: Registry, value: T) { diff --git a/packages/types/src/codec/Compact.ts b/packages/types/src/codec/Compact.ts index 7474b3e130..8c9b862174 100644 --- a/packages/types/src/codec/Compact.ts +++ b/packages/types/src/codec/Compact.ts @@ -1,7 +1,7 @@ // Copyright 2017-2021 @polkadot/types authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { CodecHash } from '../interfaces'; +import type { CodecHash, Hash } from '../interfaces'; import type { AnyJson, AnyNumber, Constructor, ICompact, InterfaceTypes, Registry } from '../types'; import type { CompactEncodable, UIntBitLength } from './types'; @@ -22,6 +22,8 @@ import { typeToConstructor } from './utils'; export class Compact implements ICompact { public readonly registry: Registry; + public createdAtHash?: Hash; + readonly #Type: Constructor; readonly #raw: T; diff --git a/packages/types/src/codec/Date.ts b/packages/types/src/codec/Date.ts index cd09e301f8..5631c9e838 100644 --- a/packages/types/src/codec/Date.ts +++ b/packages/types/src/codec/Date.ts @@ -1,7 +1,7 @@ // Copyright 2017-2021 @polkadot/types authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { CodecHash } from '../interfaces/runtime'; +import type { CodecHash, Hash } from '../interfaces/runtime'; import type { AnyNumber, Codec, Registry } from '../types'; import type { UIntBitLength } from './types'; @@ -24,6 +24,8 @@ const BITLENGTH: UIntBitLength = 64; export class CodecDate extends Date implements Codec { public readonly registry: Registry; + public createdAtHash?: Hash; + constructor (registry: Registry, value: CodecDate | Date | AnyNumber = 0) { super(CodecDate.decodeDate(value)); diff --git a/packages/types/src/codec/Enum.ts b/packages/types/src/codec/Enum.ts index 246814918c..42bbec166a 100644 --- a/packages/types/src/codec/Enum.ts +++ b/packages/types/src/codec/Enum.ts @@ -1,7 +1,7 @@ // Copyright 2017-2021 @polkadot/types authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { CodecHash } from '../interfaces'; +import type { CodecHash, Hash } from '../interfaces'; import type { AnyJson, Codec, Constructor, InterfaceTypes, Registry } from '../types'; import { assert, hexToU8a, isHex, isNumber, isObject, isString, isU8a, isUndefined, stringCamelCase, stringUpperFirst, u8aConcat, u8aToHex } from '@polkadot/util'; @@ -166,6 +166,8 @@ function decodeEnum (registry: Registry, def: TypesDef, value?: any, index?: num export class Enum implements Codec { public readonly registry: Registry; + public createdAtHash?: Hash; + readonly #def: TypesDef; readonly #entryIndex: number; diff --git a/packages/types/src/codec/Json.ts b/packages/types/src/codec/Json.ts index 661d1eabe2..05bbd2e7e2 100644 --- a/packages/types/src/codec/Json.ts +++ b/packages/types/src/codec/Json.ts @@ -1,7 +1,7 @@ // Copyright 2017-2021 @polkadot/types authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { CodecHash } from '../interfaces/runtime'; +import type { CodecHash, Hash } from '../interfaces/runtime'; import type { AnyJson, Codec, Registry } from '../types'; import { isFunction, isUndefined } from '@polkadot/util'; @@ -24,6 +24,8 @@ function decodeJson (value?: Record | null): [string, any][] { export class Json extends Map implements Codec { public readonly registry: Registry; + public createdAtHash?: Hash; + constructor (registry: Registry, value?: Record | null) { const decoded = decodeJson(value); diff --git a/packages/types/src/codec/Map.ts b/packages/types/src/codec/Map.ts index 608a0d5127..8569ba87a3 100644 --- a/packages/types/src/codec/Map.ts +++ b/packages/types/src/codec/Map.ts @@ -1,7 +1,7 @@ // Copyright 2017-2021 @polkadot/types authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { CodecHash } from '../interfaces/runtime'; +import type { CodecHash, Hash } from '../interfaces/runtime'; import type { AnyJson, Codec, Constructor, InterfaceTypes, Registry } from '../types'; import { compactFromU8a, compactToU8a, isHex, isObject, isU8a, logger, u8aConcat, u8aToHex, u8aToU8a } from '@polkadot/util'; @@ -88,6 +88,8 @@ function decodeMap (registry: export class CodecMap extends Map implements Codec { public readonly registry: Registry; + public createdAtHash?: Hash; + readonly #KeyClass: Constructor; readonly #ValClass: Constructor; diff --git a/packages/types/src/codec/Option.ts b/packages/types/src/codec/Option.ts index ff8a6d9f26..24333f70e8 100644 --- a/packages/types/src/codec/Option.ts +++ b/packages/types/src/codec/Option.ts @@ -1,7 +1,7 @@ // Copyright 2017-2021 @polkadot/types authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { CodecHash } from '../interfaces'; +import type { CodecHash, Hash } from '../interfaces'; import type { AnyJson, Codec, Constructor, InterfaceTypes, Registry } from '../types'; import { assert, isNull, isU8a, isUndefined, u8aToHex } from '@polkadot/util'; @@ -50,6 +50,8 @@ function decodeOption (registry: Registry, typeName: Constructor | keyof Interfa export class Option implements Codec { public readonly registry: Registry; + public createdAtHash?: Hash; + readonly #Type: Constructor; readonly #raw: T; diff --git a/packages/types/src/codec/Raw.ts b/packages/types/src/codec/Raw.ts index 0b35fc229c..e050b436bc 100644 --- a/packages/types/src/codec/Raw.ts +++ b/packages/types/src/codec/Raw.ts @@ -1,7 +1,7 @@ // Copyright 2017-2021 @polkadot/types authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { CodecHash } from '../interfaces/runtime'; +import type { CodecHash, Hash } from '../interfaces/runtime'; import type { AnyJson, AnyU8a, IU8a, Registry } from '../types'; import { assert, isAscii, isUndefined, isUtf8, u8aToHex, u8aToString, u8aToU8a } from '@polkadot/util'; @@ -18,6 +18,8 @@ import { assert, isAscii, isUndefined, isUtf8, u8aToHex, u8aToString, u8aToU8a } export class Raw extends Uint8Array implements IU8a { public readonly registry: Registry; + public createdAtHash?: Hash; + constructor (registry: Registry, value?: AnyU8a) { super(u8aToU8a(value)); diff --git a/packages/types/src/codec/Set.ts b/packages/types/src/codec/Set.ts index 24fb0bc309..85e9d881bf 100644 --- a/packages/types/src/codec/Set.ts +++ b/packages/types/src/codec/Set.ts @@ -1,7 +1,7 @@ // Copyright 2017-2021 @polkadot/types authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { CodecHash } from '../interfaces/runtime'; +import type { CodecHash, Hash } from '../interfaces/runtime'; import type { Codec, Constructor, Registry } from '../types'; import BN from 'bn.js'; @@ -80,6 +80,8 @@ function decodeSet (setValues: SetValues, value: string[] | Set | Uint8A export class CodecSet extends Set implements Codec { public readonly registry: Registry; + public createdAtHash?: Hash; + readonly #allowed: SetValues; readonly #byteLength: number; diff --git a/packages/types/src/codec/Struct.ts b/packages/types/src/codec/Struct.ts index 1ab242d0b9..17c3bb0057 100644 --- a/packages/types/src/codec/Struct.ts +++ b/packages/types/src/codec/Struct.ts @@ -1,7 +1,7 @@ // Copyright 2017-2021 @polkadot/types authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { CodecHash } from '../interfaces/runtime'; +import type { CodecHash, Hash } from '../interfaces/runtime'; import type { AnyJson, BareOpts, Codec, Constructor, ConstructorDef, InterfaceTypes, Registry } from '../types'; import { hexToU8a, isBoolean, isFunction, isHex, isObject, isU8a, isUndefined, stringCamelCase, u8aConcat, u8aToHex } from '@polkadot/util'; @@ -135,6 +135,8 @@ export class Struct< E extends { [K in keyof S]: string } = { [K in keyof S]: string }> extends Map implements Codec { public readonly registry: Registry; + public createdAtHash?: Hash; + readonly #jsonMap: Map; readonly #Types: ConstructorDef; diff --git a/packages/types/src/create/createType.ts b/packages/types/src/create/createType.ts index d63a2192c9..740f222372 100644 --- a/packages/types/src/create/createType.ts +++ b/packages/types/src/create/createType.ts @@ -4,72 +4,72 @@ import type { Codec, Constructor, InterfaceTypes, Registry } from '../types'; import type { FromReg } from './types'; -import { isU8a, logger, u8aToHex } from '@polkadot/util'; +import { logger } from '@polkadot/util'; import { createClass } from './createClass'; -interface UnsafeOptions { +interface CreateOptions { + blockHash?: Uint8Array | string | null; isPedantic?: boolean; withoutLog?: boolean; } const l = logger('registry'); -function u8aHasValue (value: Uint8Array): boolean { - return value.some((v): boolean => !!v); -} - +// TODO: Fix and re-enable // With isPedantic, actually check that the encoding matches that supplied. This // is much slower, but verifies that we have the correct types defined -function checkInstance (value: Uint8Array, created: FromReg): void { - // the underlying type created.toRawType() - const rawType = created.toRawType(); +// function checkInstance (value: Uint8Array, created: FromReg): void { +// // the underlying type created.toRawType() +// const rawType = created.toRawType(); - // ignore bytes completely - this is probably a FIXME, since these are somewhat - // breaking for at least online queries - not quite sure wtf is going wrong here - if (rawType === 'Bytes') { - return; - } +// // ignore bytes completely - this is probably a FIXME, since these are somewhat +// // breaking for at least online queries - not quite sure wtf is going wrong here +// if (rawType === 'Bytes') { +// return; +// } - // the hex values for what we have - const inHex = u8aToHex(value); - const crHex = created.toHex(); +// // the hex values for what we have +// const inHex = u8aToHex(value); +// const crHex = created.toHex(); - // Check equality, based on some different approaches (as decoded) - const isEqual = inHex === crHex || // raw hex values, quick path - inHex === created.toHex(true) || // wrapped options - u8aToHex(value.reverse()) === crHex; // reverse (for numbers, which are BE) +// // Check equality, based on some different approaches (as decoded) +// const isEqual = inHex === crHex || // raw hex values, quick path +// inHex === created.toHex(true) || // wrapped options +// u8aToHex(value.reverse()) === crHex; // reverse (for numbers, which are BE) - // if the hex doesn't match and the value for both is non-empty, complain... bitterly - if (!isEqual && (u8aHasValue(value) || u8aHasValue(created.toU8a(true)))) { - l.warn(`${rawType}:: Input doesn't match output, received ${u8aToHex(value)}, created ${crHex}`); - } -} +// // if the hex doesn't match and the value for both is non-empty, complain... bitterly +// if (!isEqual && (u8aHasValue(value) || u8aHasValue(created.toU8a(true)))) { +// l.warn(`${rawType}:: Input doesn't match output, received ${u8aToHex(value)}, created ${crHex}`); +// } +// } // Initializes a type with a value. This also checks for fallbacks and in the cases // where isPedantic is specified (storage decoding), also check the format/structure -function initType (registry: Registry, Type: Constructor>, params: any[] = [], isPedantic?: boolean): FromReg { +function initType (registry: Registry, Type: Constructor>, params: any[] = [], { blockHash }: CreateOptions = {}): FromReg { const created = new Type(registry, ...params); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const [value] = params; - if (isPedantic && isU8a(value)) { - checkInstance(value, created); + if (blockHash) { + created.createdAtHash = createType(registry, 'Hash', blockHash); } + // if (isPedantic && isU8a(value)) { + // checkInstance(value, created); + // } + return created; } // An unsafe version of the `createType` below. It's unsafe because the `type` // argument here can be any string, which, when it cannot parse, will yield a // runtime error. -export function createTypeUnsafe (registry: Registry, type: K, params: any[] = [], { withoutLog }: UnsafeOptions = {}): T { +export function createTypeUnsafe (registry: Registry, type: K, params: any[] = [], options: CreateOptions = {}): T { try { // Circle back to isPedantic when it handles all cases 100% - as of now, // it provides false warning which is more hinderance than help - return initType(registry, createClass(registry, type), params); // , isPedantic); + return initType(registry, createClass(registry, type), params, options); } catch (error) { - !withoutLog && l.error(error); + !options.withoutLog && l.error(error); throw new Error(`createType(${type}):: ${(error as Error).message}`); } diff --git a/packages/types/src/create/registry.ts b/packages/types/src/create/registry.ts index a3a982c2c3..807a387e2c 100644 --- a/packages/types/src/create/registry.ts +++ b/packages/types/src/create/registry.ts @@ -4,7 +4,7 @@ /* eslint-disable @typescript-eslint/no-var-requires */ import type { ExtDef } from '../extrinsic/signedExtensions/types'; -import type { ChainProperties, CodecHash, DispatchErrorModule } from '../interfaces/types'; +import type { ChainProperties, CodecHash, DispatchErrorModule, Hash } from '../interfaces/types'; import type { CallFunction, Codec, CodecHasher, Constructor, InterfaceTypes, RegisteredTypes, Registry, RegistryError, RegistryTypes } from '../types'; // we are attempting to avoid circular refs, hence the Metadata path import @@ -136,11 +136,17 @@ export class TypeRegistry implements Registry { #userExtensions?: ExtDef; - constructor () { + public createdAtHash?: Hash; + + constructor (createdAtHash?: Hash | Uint8Array | string) { this.#knownDefaults = { Json, Metadata, Raw, ...baseTypes }; this.#knownDefinitions = definitions as unknown as Record; this.init(); + + if (createdAtHash) { + this.createdAtHash = this.createType('Hash', createdAtHash); + } } public init (): this { diff --git a/packages/types/src/primitive/Bool.ts b/packages/types/src/primitive/Bool.ts index 275c6113a4..92464f4d23 100644 --- a/packages/types/src/primitive/Bool.ts +++ b/packages/types/src/primitive/Bool.ts @@ -1,7 +1,7 @@ // Copyright 2017-2021 @polkadot/types authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { CodecHash } from '../interfaces/runtime'; +import type { CodecHash, Hash } from '../interfaces/runtime'; import type { Codec, Registry } from '../types'; import { isU8a, u8aToHex } from '@polkadot/util'; @@ -26,6 +26,8 @@ function decodeBool (value: any): boolean { export class bool extends Boolean implements Codec { public readonly registry: Registry; + public createdAtHash?: Hash; + // eslint-disable-next-line @typescript-eslint/ban-types constructor (registry: Registry, value: bool | Boolean | Uint8Array | boolean | number = false) { super(decodeBool(value)); diff --git a/packages/types/src/primitive/Null.ts b/packages/types/src/primitive/Null.ts index 28888a345f..17893c0e55 100644 --- a/packages/types/src/primitive/Null.ts +++ b/packages/types/src/primitive/Null.ts @@ -1,7 +1,7 @@ // Copyright 2017-2021 @polkadot/types authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { CodecHash } from '../interfaces/runtime'; +import type { CodecHash, Hash } from '../interfaces/runtime'; import type { Codec, Registry } from '../types'; import { isNull } from '@polkadot/util'; @@ -14,6 +14,8 @@ import { isNull } from '@polkadot/util'; export class Null implements Codec { public readonly registry: Registry; + public createdAtHash?: Hash; + constructor (registry: Registry) { this.registry = registry; } diff --git a/packages/types/src/primitive/Text.ts b/packages/types/src/primitive/Text.ts index 9390400ba4..9f0267b489 100644 --- a/packages/types/src/primitive/Text.ts +++ b/packages/types/src/primitive/Text.ts @@ -1,7 +1,7 @@ // Copyright 2017-2021 @polkadot/types authors & contributors // SPDX-License-Identifier: Apache-2.0 -import type { CodecHash } from '../interfaces/runtime'; +import type { CodecHash, Hash } from '../interfaces/runtime'; import type { AnyU8a, Codec, Registry } from '../types'; import { assert, compactAddLength, compactFromU8a, hexToU8a, isHex, isString, stringToU8a, u8aToHex, u8aToString } from '@polkadot/util'; @@ -50,6 +50,8 @@ function decodeText (value?: null | Text | string | AnyU8a | { toString: () => s export class Text extends String implements Codec { public readonly registry: Registry; + public createdAtHash?: Hash; + #override: string | null = null; constructor (registry: Registry, value?: null | Text | string | AnyU8a | { toString: () => string }) { diff --git a/packages/types/src/types/codec.ts b/packages/types/src/types/codec.ts index 3fdb238a88..83f3147fca 100644 --- a/packages/types/src/types/codec.ts +++ b/packages/types/src/types/codec.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type BN from 'bn.js'; -import type { H256 } from '../interfaces/runtime'; +import type { Hash } from '../interfaces/runtime'; import type { Registry } from './registry'; export type AnyJson = string | number | boolean | null | undefined | AnyJson[] | { [index: string]: AnyJson }; @@ -46,7 +46,7 @@ export interface Codec { /** * @description Returns a hash of the value */ - readonly hash: H256; + readonly hash: Hash; /** * @description Checks if the value is an empty value @@ -58,6 +58,11 @@ export interface Codec { */ readonly registry: Registry; + /** + * @description The block at which this value was retrieved/created (set to non-empty when retrieved from storage) + */ + createdAtHash?: Hash; + /** * @description Compares the value of the input to see if there is a match */ diff --git a/packages/types/src/types/registry.ts b/packages/types/src/types/registry.ts index 45c970df0c..f85dcc84ec 100644 --- a/packages/types/src/types/registry.ts +++ b/packages/types/src/types/registry.ts @@ -5,7 +5,7 @@ import type BN from 'bn.js'; import type { Metadata } from '@polkadot/metadata'; import type { Observable } from '@polkadot/x-rxjs'; import type { ExtDef } from '../extrinsic/signedExtensions/types'; -import type { CodecHash } from '../interfaces/runtime'; +import type { CodecHash, Hash } from '../interfaces/runtime'; import type { ChainProperties } from '../interfaces/system'; import type { CallFunction } from './calls'; import type { Codec, Constructor } from './codec'; @@ -101,6 +101,8 @@ export interface Registry { readonly unknownTypes: string[]; readonly signedExtensions: string[]; + createdAtHash?: Hash; + findMetaCall (callIndex: Uint8Array): CallFunction; findMetaError (errorIndex: Uint8Array | { error: BN, index: BN }): RegistryError; // due to same circular imports where types don't really want to import from EventData,