From f9318fe0132c55eae489a1d1858d9739a82b416b Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 17 May 2019 14:07:51 +0200 Subject: [PATCH] Codec Fallback on createType (along with pedantic) (#929) * Codec Fallback on createType (along with pedantic) * Updates for numbers (fix failing tests) * Add isPedantic to linked --- packages/api/src/nodeCompat.ts | 33 +++++--------- packages/rpc-core/src/formatting.spec.ts | 10 ++--- packages/rpc-core/src/index.ts | 49 +++++++-------------- packages/types/src/codec/Compact.ts | 4 +- packages/types/src/codec/Int.ts | 4 +- packages/types/src/codec/Option.ts | 4 +- packages/types/src/codec/UInt.ts | 5 ++- packages/types/src/codec/Vector.ts | 4 ++ packages/types/src/codec/createType.ts | 29 ++++++++++-- packages/types/src/primitive/EventRecord.ts | 2 + packages/types/src/types.ts | 6 ++- 11 files changed, 75 insertions(+), 75 deletions(-) diff --git a/packages/api/src/nodeCompat.ts b/packages/api/src/nodeCompat.ts index 796d947f66..f124a8c709 100644 --- a/packages/api/src/nodeCompat.ts +++ b/packages/api/src/nodeCompat.ts @@ -7,34 +7,21 @@ import { Constructor } from '@polkadot/types/types'; import { EventRecord77, RuntimeVersion, getTypeRegistry } from '@polkadot/types'; type Compat = { - nodeSpecs: Array<{ - name: string, - version: number - }>, + nodeSpecs: Array<[string, number]>, types: { [index: string]: Constructor } }; +const ANY_VERSION = 0xffffff; + const types: Array = [ { nodeSpecs: [ - { - name: 'node', - version: 77 - }, - { - name: 'node-template', - version: 0xffff // don't have an updated spec version as of yet - }, - { - name: 'polkadot', - version: 0xffff // don't have an updated spec version as of yet - }, - { - name: 'edgeware', - version: 0xffff // don't have an updated spec version as of yet - } + ['node', 77], + ['node-template', ANY_VERSION], + ['polkadot', ANY_VERSION], + ['edgeware', ANY_VERSION] ], types: { 'EventRecord': EventRecord77 @@ -45,9 +32,9 @@ const types: Array = [ export default function injectNodeCompat ({ specName, specVersion }: RuntimeVersion): void { types .filter(({ nodeSpecs }) => - nodeSpecs.some((spec) => - specName.eq(spec.name) && - specVersion.ltn(spec.version) + nodeSpecs.some(([name, version]) => + specName.eq(name) && + specVersion.ltn(version) ) ) .forEach(({ types }) => { diff --git a/packages/rpc-core/src/formatting.spec.ts b/packages/rpc-core/src/formatting.spec.ts index 157cb37111..0cfef8f5e0 100644 --- a/packages/rpc-core/src/formatting.spec.ts +++ b/packages/rpc-core/src/formatting.spec.ts @@ -24,14 +24,14 @@ function formattingTests (version: string, storage: Storage, encodedValues: [Str beforeEach(() => { provider = { send: jest.fn((method, params) => - Promise.resolve('0x0102') + Promise.resolve('0x01020000000000000000000000000000') ), subscribe: jest.fn((type, method, params, subscription) => subscription(null, { block: '0x1234', changes: [ - [ENC_ONE, '0x0102'], - [ENC_TWO, '0x0201'] + [ENC_ONE, '0x01020000000000000000000000000000'], + [ENC_TWO, '0x02010000000000000000000000000000'] ] }) ) @@ -41,9 +41,7 @@ function formattingTests (version: string, storage: Storage, encodedValues: [Str it('encodes key (with params), decoding response', () => { return api.state - .getStorage( - [storage.balances.freeBalance, ADDR_ONE] - ) + .getStorage([storage.balances.freeBalance, ADDR_ONE]) .then((value) => { expect( provider.send diff --git a/packages/rpc-core/src/index.ts b/packages/rpc-core/src/index.ts index e09b8542b7..99bb226c88 100644 --- a/packages/rpc-core/src/index.ts +++ b/packages/rpc-core/src/index.ts @@ -217,28 +217,23 @@ export default class Rpc implements RpcInterface { // single return value (via state.getStorage), decode the value based on the // outputType that we have specified. Fallback to Data on nothing const type = key.outputType || 'Data'; - const Clazz = createClass(type); const meta = key.meta || EMPTY_META; - let created: Codec; if (meta.type.isMap && meta.type.asMap.isLinked) { - // linked map - created = new Clazz(base); - } else { - created = meta.modifier.isOptional - ? new Option(Clazz, isNull ? null : new Clazz(base)) - : new Clazz(base); + return createType(type, base, true); + } else if (meta.modifier.isOptional) { + return new Option( + createClass(type), + isNull ? null : createType(type, base, true) + ); } - // TODO Check that the encoded created value matches that what was supplied - - return created; + return createType(type, base, true); } private formatStorageSet (key: StorageKey, base: StorageChangeSet): Codec | undefined { // Fallback to Data (i.e. just the encoding) if we don't have a specific type const type = key.outputType || 'Data'; - const Clazz = createClass(type); // see if we have a result value for this specific key const hexKey = key.toHex(); @@ -247,27 +242,17 @@ export default class Rpc implements RpcInterface { ) || { value: null }; const meta = key.meta || EMPTY_META; - // if we don't have a value, do not fill in the entry, it will be up to the - // caller to sort this out, either ignoring or having a cache for older values - let created: Codec | undefined; - - if (value) { - if (meta.type.isMap && meta.type.asMap.isLinked) { - // linked map - created = new Clazz(value.unwrapOr(null)); - } else if (meta.modifier.isOptional) { - // create option either with the existing value, or empty when - // there is no value returned - created = new Option(Clazz, value.isNone ? null : new Clazz(value.unwrap())); - } else { - // for `null` we fallback to the default value, or create an empty type, - // otherwise we return the actual value as retrieved - created = new Clazz(value.unwrapOr(meta.fallback)); - } - - // TODO Check that the encoded created value matches that what was supplied + if (!value) { + return; + } else if (meta.type.isMap && meta.type.asMap.isLinked) { + return createType(type, value.unwrapOr(null), true); + } else if (meta.modifier.isOptional) { + return new Option( + createClass(type), + value.isNone ? null : createType(type, value.unwrap(), true) + ); } - return created; + return createType(type, value.unwrapOr(meta.fallback), true); } } diff --git a/packages/types/src/codec/Compact.ts b/packages/types/src/codec/Compact.ts index a8742fcadf..9e74bb6066 100644 --- a/packages/types/src/codec/Compact.ts +++ b/packages/types/src/codec/Compact.ts @@ -108,8 +108,8 @@ export default class Compact extends Base implements Codec { /** * @description Returns a hex string representation of the value */ - toHex (): any { - return this.raw.toHex(); + toHex (isLe?: boolean): any { + return this.raw.toHex(isLe); } /** diff --git a/packages/types/src/codec/Int.ts b/packages/types/src/codec/Int.ts index 6edfd4e16b..1c0c58d082 100644 --- a/packages/types/src/codec/Int.ts +++ b/packages/types/src/codec/Int.ts @@ -33,10 +33,10 @@ export default class Int extends AbstractInt { /** * @description Returns a hex string representation of the value */ - toHex (): string { + toHex (isLe: boolean = false): string { return bnToHex(this, { bitLength: this._bitLength, - isLe: false, + isLe, isNegative: true }); } diff --git a/packages/types/src/codec/Option.ts b/packages/types/src/codec/Option.ts index c35b174c42..2142691eaa 100644 --- a/packages/types/src/codec/Option.ts +++ b/packages/types/src/codec/Option.ts @@ -23,12 +23,12 @@ export default class Option extends Base implements Codec { ); } - static decodeOption (Type: Constructor, value?: any): Codec { + static decodeOption (Type: Constructor, value?: any): Codec { if (isNull(value) || isUndefined(value) || value instanceof Null) { return new Null(); } else if (value instanceof Option) { return Option.decodeOption(Type, value.value); - } else if (value instanceof Type) { + } else if (value instanceof Type || (Type.Fallback && value instanceof Type.Fallback)) { // don't re-create, use as it (which also caters for derived types) return value; } else if (isU8a(value)) { diff --git a/packages/types/src/codec/UInt.ts b/packages/types/src/codec/UInt.ts index bcf9c00c8a..0ad5264b7c 100644 --- a/packages/types/src/codec/UInt.ts +++ b/packages/types/src/codec/UInt.ts @@ -33,10 +33,11 @@ export default class UInt extends AbstractInt { /** * @description Returns a hex string representation of the value */ - toHex (): string { + toHex (isLe: boolean = false): string { + // For display/JSON, this is BE, for compare, use isLe return bnToHex(this, { bitLength: this._bitLength, - isLe: false, + isLe, isNegative: false }); } diff --git a/packages/types/src/codec/Vector.ts b/packages/types/src/codec/Vector.ts index 9fcc5e1858..3e4b9aa413 100644 --- a/packages/types/src/codec/Vector.ts +++ b/packages/types/src/codec/Vector.ts @@ -57,6 +57,10 @@ export default class Vector extends AbstractArray { constructor (value?: Array) { super(Type, value); } + + static Fallback = Type.Fallback + ? Vector.with(Type.Fallback) + : undefined; }; } diff --git a/packages/types/src/codec/createType.ts b/packages/types/src/codec/createType.ts index ed29f2ab6c..ab311c7411 100644 --- a/packages/types/src/codec/createType.ts +++ b/packages/types/src/codec/createType.ts @@ -226,10 +226,31 @@ export function createClass (type: Text | string): Constructor { ); } -export default function createType (type: Text | string, value?: any): Codec { +function initType (Type: Constructor, value?: any, isPedantic?: boolean): Codec { + try { + const created = new Type(value); + + // in pedantic mode, actually check that the encoding matches that supplied - this + // is much slower, but ensures that we have a 100% grasp on the actual provided value + if (isPedantic && value && value.toHex) { + const inHex = value.toHex(true); + const crHex = created.toHex(true); + + assert(crHex === inHex, `Encoding for input doesn't match output, created ${crHex} from ${inHex}`); + } + + return created; + } catch (error) { + if (Type.Fallback) { + return initType(Type.Fallback, value, isPedantic); + } + + throw error; + } +} + +export default function createType (type: Text | string, value?: any, isPedantic?: boolean): Codec { // l.debug(() => ['createType', { type, value }]); - const Type = createClass(type); - - return new Type(value); + return initType(createClass(type), value, isPedantic); } diff --git a/packages/types/src/primitive/EventRecord.ts b/packages/types/src/primitive/EventRecord.ts index 41e836077c..b049e5acc2 100644 --- a/packages/types/src/primitive/EventRecord.ts +++ b/packages/types/src/primitive/EventRecord.ts @@ -125,6 +125,8 @@ export default class EventRecord extends Struct { }, value); } + static Fallback = EventRecord77; + /** * @description The [[Event]] this record refers to */ diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index 63d4902ec6..d4b878d3ff 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -51,9 +51,9 @@ export interface Codec { eq (other?: any): boolean; /** - * @description Returns a hex string representation of the value + * @description Returns a hex string representation of the value. isLe returns a LE (number-only) repersentation */ - toHex (): string; + toHex (isLe?: boolean): string; /** * @description Converts the Object to JSON, typically used for RPC transfers @@ -80,6 +80,8 @@ export interface Codec { export type CodecTo = 'toHex' | 'toJSON' | 'toString' | 'toU8a'; export interface Constructor { + Fallback?: Constructor; + new(...value: Array): T; }