new EventRecord with topics (#902)

* new EventRecord with topics

* Add additional decoding checks (from #612)

* Slightly rework storage parsing (future checks)

* Split storage result formatting (set vs data)

* Allow v3 event parsing

* Max Vector length check

* assert during Event parsing

* Well, HACK-y fallback

* Revert "Well, HACK-y fallback"

This reverts commit edbf61b99da2753bec3a535126660e71bdcd9861.

* Node overrides...

* Add implVersion

* Register types

* Update with polkadot as well

* inject compatibility types

* EventRecord77 in testing

* Conversion with DoubleMap

* E2E tested against v3 & v4

* Filter with some

* Split archive/remove alex tests

* beforeAll -> beforeEach

* Remove .only

* EventRecordV0 -> 77
This commit is contained in:
Jaco Greeff
2019-05-15 08:07:23 +02:00
committed by GitHub
parent 3df5672159
commit 7b04ea0bfe
21 changed files with 479 additions and 212 deletions
+72 -49
View File
@@ -14,6 +14,15 @@ import { ExtError, assert, isFunction, isNull, logger } from '@polkadot/util';
const l = logger('rpc-core');
const EMPTY_META = {
fallback: undefined,
modifier: { isOptional: true },
type: {
asMap: { isLinked: false },
isMap: false
}
};
/**
* @name Rpc
* @summary The API may use a HTTP or WebSockets provider.
@@ -187,64 +196,78 @@ export default class Rpc implements RpcInterface {
const base = createType(method.type as string, result);
if (method.type === 'StorageData') {
// single return value (via state.getStorage), decode the value based on the
// outputType that we have specified. Fallback to Data on nothing
const key = params[0] as StorageKey;
const type = key.outputType || 'Data';
const Clazz = createClass(type);
const meta = key.meta || { fallback: undefined, modifier: { isOptional: true } };
if (key.meta && key.meta.type.isMap && key.meta.type.asMap.isLinked) {
// linked map
return new Clazz(base);
} else {
return meta.modifier.isOptional
? new Option(Clazz, isNull(result) ? null : new Clazz(base))
: new Clazz(base);
}
return this.formatStorageData(params[0] as StorageKey, base, isNull(result));
} else if (method.type === 'StorageChangeSet') {
// 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 -
// - Base - There is a valid value, non-empty
// - null - The storage key is empty (but in the resultset)
// - undefined - The storage value is not in the resultset
return (params[0] as Vector<StorageKey>).reduce((result, key: StorageKey) => {
// 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);
return (params[0] as Vector<StorageKey>).reduce((results, key: StorageKey) => {
results.push(this.formatStorageSet(key, base as StorageChangeSet));
// see if we have a result value for this specific key
const hexKey = key.toHex();
const { value } = (base as StorageChangeSet).changes.find((item) =>
item.key.toHex() === hexKey
) || { value: null };
const meta = key.meta || { fallback: undefined, modifier: { isOptional: true } };
if (!value) {
// 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
result.push(undefined);
} else {
if (key.meta && key.meta.type.isMap && key.meta.type.asMap.isLinked) {
// linked map
result.push(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
result.push(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
result.push(new Clazz(value.unwrapOr(meta.fallback)));
}
}
}
return result;
}, [] as Array<Codec | null | undefined>);
return results;
}, [] as Array<Codec | undefined>);
}
return base;
}
private formatStorageData (key: StorageKey, base: Codec, isNull: boolean): Codec {
// 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);
}
// TODO Check that the encoded created value matches that what was supplied
return created;
}
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();
const { value } = base.changes.find((item) =>
item.key.toHex() === hexKey
) || { 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
}
return created;
}
}