Skip to content

Conversation

@jacogr
Copy link
Member

@jacogr jacogr commented Sep 23, 2018

Fails on CI since e2e is active. Currently being tested with e2e -

yarn test packages/api-provider/test/e2e.basics.test.js

While it decodes fully, there are a couple of issues with the implementation as it stands from a usability perspective.

  • ...

@jacogr jacogr added the WIP Work in Progress label Sep 23, 2018

import BaseNumber from './base/Number';

export default class U16 extends BaseNumber {
Copy link
Member Author

@jacogr jacogr Sep 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only have u16 implemented here since it is all we need for this specific one. (u8, u32, u64, u128 is basically exactly the same as this one, just with the bitlength updated)

Should probablybring those in, however wanted to keep this specific to this specific need.

constructor () {
super({
name: String,
arguments: class extends CodecArray<String> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach looks funny, however have kept strictly the the structures defined in the Rust codebase. So instead of extra classes, use them inline for arrays and (some) structures. Naming of each class here maps 1-to-1 with Rust.

import String from './String';
import U16 from './U16';

class EventMetadata extends CodecStruct {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file you probably want to read from the bottom-up, i.e. the RuntimeMetadata is exported right at the end and building the structures using the other classes in here.

@jacogr
Copy link
Member Author

jacogr commented Sep 23, 2018

cc @amaurymartiny

I need to add the comments in this one, i.e. thinking behind stuff. (Added some comments in the PR, but should probably just expand specifically in the base classes). I think it is a start that will solve the short-term goals, but do believe there is an lot of room for improvement here.


export default class StringCompact extends String {
constructor (value: string = '') {
super(value, LengthCompact);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same a string, however using the new compact length encoding - this should go in in the next day or so and then this should be the default. Same with Arrays.

return this;
}

map <O> (fn: (entry: T, index?: number) => O): Array<O> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We implement the base Array functions, length, map, filter, reduce, forEach so this class can be used like an array. So our own Array-like. (Probably need some more, so it can be used an an iterator)


export default class CodecArrayCompact <T extends Base<any>> extends BaseArray<T> {
constructor (Type: { new(): T }, value: Array<T> = [] as Array<T>) {
super(Type, value, LengthCompact);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per StringCompact comment, this will become the default.


import { Base } from '../types';

export default class CodecEnum implements Base<number> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This takes a byte and maps it to a number. We actually really should do a TS-like enum impl for these. Not sure how that looks though. Probably pass enum as type and then do some magic?


import { Base } from '../types';

export default class CodecEnumType implements Base<Base<any>> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something that based on an enum, it decodes to a different type. The same comments on the enum applies, but it really should probably actually include enum instead of re-doing the logic. (composition?)


import LengthCompact from './LengthCompact';

describe('Compact', () => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO. Port the Rust tests...


import { Base } from '../types';

export default class CodecOption implements Base<Base<any> | null> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on the length of the first byte, it determines if something follows or not. If 0, nothing follows, if 1, we have actual data f a specific type.

const metaDecode = (input: Array<number>) => {
const metadata = new Metadata();

try {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try/catch block here for coding debugging. To be removed.

});
});

it('retrieves the wasm metadata', () => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is where we test :)

}

byteLength (): number {
return 4;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

u32 LE, always 4 bytes.

return bnToHex(this.raw, this.bitLength);
}

toBn (): BN {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need some functions get the get values out, toBn and toNumber does that. (We don't really want to encourage access to raw directly, it should be moved to _raw and made at the very least protected)

);
}

static with <O extends Base<any>> (Type: { new(value?: any): O }): { new(value?: any): CodecArray<O> } {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shortcut function so we can have less noise when actually constructing these inline

this.raw = value;
}

byteLength (): number {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As with Arrays, we don't have length, in any of the classes. (Well, we do have in String and Array). byteLength has the total length including the length of the prefixes, where length is just the number of elements. (So in coding use byteLength, when working with data, just a straight length is fine)

arguments: CodecArray<String>,
documentation: CodecArray<String>
}> {
constructor () {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really tried to not have to pass through the type and a structure that defines the classes (that will be used upon construction). I failed at my first attempt. So there is room for improvement, since what gets passed in the constructor is the same (more-or-less) as the generic type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Somewhere there must be a solution between key and lookup types, to not pass the type in, but just the struct in the constructor - https://github.com/Microsoft/TypeScript/wiki/What's-new-in-TypeScript#keyof-and-lookup-types

constructor (value?: any) {
super({
outerEvent: OuterEventMetadata,
modules: CodecArray.with(RuntimeModuleMetadata)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodecArray.with are helper functions that just makes it simpler to define an array (and friends) in-place without having to do yet another class definition for a nested type.

"modifier": 0,
"type": {
"key": "PropIndex",
"value": "(T::Balance, Vec<T::AccountId>)"
Copy link
Member Author

@jacogr jacogr Sep 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This effective translates to -

{
  balance: Balance,
  vecAccountId: Array<AccountId>
}

or rather as a CodecStruct -

extends class CodecStruct<{
  balance: Balance,
  vecAccountId: CodecArray<AccountId>
}> {
  constructor (value?: any) {
    super({
      balance: Balance,
      vecAccountId: CodecArray.with(AccountId)
    });
  }
}

And sadly, looking at the above it is pretty straight forward to generate, however... since we stll have the issue earlier with the getters not being automagically defined, it is a bit of an issue. Definition is one thing, but we really don't want people to go instance.raw.balance.toNumber() when they can do instance.balance.toNumber() - if no other way, maybe trim raw to something like v, i.e. shorter.

Dunno, just thinking through.

{
"name": "LaunchPeriod",
"modifier": 2,
"type": "T::BlockNumber",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These T::Type stuff is driving me a bit batty. The Rust basically bleeds through. I'm in favour of stripping any T:: stuff and just not having it at all. (Especially with events not having them and even having things duplicated as T::. And then even in the modules, there are some without T, and really T::AccountId === AccountId for the API purposes)

"type": {
"key": "(ReferendumIndex, T::AccountId)",
"value": "bool"
},
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where these type: { key: ..., value: ... } are found, it is actually a Map. so key becomes the param that is passed, and value is the result type, i.e. type.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oooh, and I picked an interesting one - the map key is actually a struct as well (it is ok, when we call it gets encoded, so ends up just being a u8a)

this._Type = Type;
}

static with <O extends CodecBase> (Type: { new(value?: any): O }): { new(value?: any): CodecArray<O> } {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, didn't understand the DecodeDifferent on the rust side, but this syntax is clear.

// i.e. while it wraps an array, it provides a `length` property to get the actual length, `at(index)`
// to retrieve a specific item. Additionally the helper functions `map`, `filter`, `forEach` and
// `reduce` is exposed on the interface.
export default class CodecArray <
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this extend Array and remove this.raw?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would love that. No idea how to implement it though. (It would make it much easier since we won't need to do the map, filter, reduce, etc.

return this.raw.forEach(fn);
}

fromJSON (input: any): CodecArray<T> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Really small detail about syntax:

  • Uint8Array.from(...) -> returns new Uint8Array()
  • Array.from(...) -> returns new Array()

So the natural (for me) syntax is to have a static method here that returns a new instance.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed actually. However the reason I went down this route is twofold -

  1. For the stuff where we need to know the actual type, it is probably easier constructing with the class and then where we from it can be used.

  2. ... erm, I started with it that way around. However TS basically doesn't allow statics in interfaces (where I started) and it became one big hack to try and work around this.

Not 100% crazy about the above, happy to see it changed.

//
// TODO
// - This could probably be abstract, as long as we have the functions abstract as well
export default class CodecBase <T = any> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Abstract class?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeap, needs to be done as per the TODO.

@amaury1093 amaury1093 removed the WIP Work in Progress label Sep 24, 2018
@jacogr jacogr merged commit d9b05cf into master Sep 24, 2018
@jacogr jacogr deleted the jg-state-getMeta-opaque branch September 24, 2018 11:51
@jacogr
Copy link
Member Author

jacogr commented Sep 25, 2018

Related to #161

@polkadot-js-bot
Copy link

This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@polkadot-js polkadot-js locked as resolved and limited conversation to collaborators Jun 9, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants