Merge pull request #260 from CosmWasm/238-protobuf-registry-magic

Improve protobuf registry
This commit is contained in:
Will Clark 2020-06-30 15:46:59 +02:00 committed by GitHub
commit 21fb2acb52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 213 additions and 20 deletions

View File

@ -3,8 +3,12 @@ import { assert } from "@cosmjs/utils";
import { Message } from "protobufjs";
import { cosmosField, cosmosMessage } from "./decorator";
import { cosmos_sdk as cosmosSdk, google } from "./generated/codecimpl";
import { Registry } from "./registry";
const { TxBody } = cosmosSdk.tx.v1;
const { Any } = google.protobuf;
describe("decorator demo", () => {
it("works with a custom msg", () => {
const nestedTypeUrl = "/demo.MsgNestedDemo";
@ -44,8 +48,6 @@ describe("decorator demo", () => {
const MsgNestedDemoT = myRegistry.lookupType(nestedTypeUrl)!;
const MsgDemoT = myRegistry.lookupType(typeUrl)!;
const TxBody = myRegistry.lookupType("/cosmos.tx.TxBody")!;
const Any = myRegistry.lookupType("/google.protobuf.Any")!;
const msgNestedDemoFields = {
foo: "bar",

View File

@ -0,0 +1,89 @@
/* eslint-disable @typescript-eslint/camelcase */
import { Message } from "protobufjs";
import { cosmosField, cosmosMessage } from "./decorator";
import { Registry } from "./registry";
describe("registry magic demo", () => {
it("works with a custom msg", () => {
const nestedTypeUrl = "/demo.MsgNestedMagic";
const typeUrl = "/demo.MsgMagic";
const myRegistry = new Registry();
@cosmosMessage(myRegistry, nestedTypeUrl)
class MsgNestedMagic extends Message<{}> {
@cosmosField.string(1)
public readonly foo?: string;
}
@cosmosMessage(myRegistry, typeUrl)
class MsgMagic extends Message<{}> {
@cosmosField.boolean(1)
public readonly booleanDemo?: boolean;
@cosmosField.string(2)
public readonly stringDemo?: string;
@cosmosField.bytes(3)
public readonly bytesDemo?: Uint8Array;
@cosmosField.int64(4)
public readonly int64Demo?: number;
@cosmosField.uint64(5)
public readonly uint64Demo?: number;
@cosmosField.repeatedString(6)
public readonly listDemo?: readonly string[];
@cosmosField.message(7, MsgNestedMagic)
public readonly nestedDemo?: MsgNestedMagic;
}
const msgNestedDemoFields = {
foo: "bar",
};
const msgDemoFields = {
booleanDemo: true,
stringDemo: "example text",
bytesDemo: Uint8Array.from([1, 2, 3, 4, 5, 6, 7, 8]),
int64Demo: -123,
uint64Demo: 123,
listDemo: ["this", "is", "a", "list"],
nestedDemo: msgNestedDemoFields,
};
const txBodyFields = {
messages: [{ typeUrl: typeUrl, value: msgDemoFields }],
memo: "Some memo",
timeoutHeight: 9999,
extensionOptions: [],
};
const txBodyBytes = myRegistry.encode({
typeUrl: "/cosmos.tx.TxBody",
value: txBodyFields,
});
const txBodyDecoded = myRegistry.decode({
typeUrl: "/cosmos.tx.TxBody",
value: txBodyBytes,
});
expect(txBodyDecoded.memo).toEqual(txBodyFields.memo);
// int64Demo and uint64Demo decode to Long in Node
expect(Number(txBodyDecoded.timeoutHeight)).toEqual(txBodyFields.timeoutHeight);
expect(txBodyDecoded.extensionOptions).toEqual(txBodyFields.extensionOptions);
const msgDemoDecoded = txBodyDecoded.messages[0] as MsgMagic;
expect(msgDemoDecoded).toBeInstanceOf(MsgMagic);
expect(msgDemoDecoded.booleanDemo).toEqual(msgDemoFields.booleanDemo);
expect(msgDemoDecoded.stringDemo).toEqual(msgDemoFields.stringDemo);
// bytesDemo decodes to a Buffer in Node
expect(Uint8Array.from(msgDemoDecoded.bytesDemo!)).toEqual(msgDemoFields.bytesDemo);
// int64Demo and uint64Demo decode to Long in Node
expect(Number(msgDemoDecoded.int64Demo)).toEqual(msgDemoFields.int64Demo);
expect(Number(msgDemoDecoded.uint64Demo)).toEqual(msgDemoFields.uint64Demo);
expect(msgDemoDecoded.listDemo).toEqual(msgDemoFields.listDemo);
expect(msgDemoDecoded.nestedDemo).toBeInstanceOf(MsgNestedMagic);
expect(msgDemoDecoded.nestedDemo!.foo).toEqual(msgDemoFields.nestedDemo.foo);
});
});

View File

@ -2,20 +2,17 @@
import { assert } from "@cosmjs/utils";
import { MsgDemo as MsgDemoType } from "./demo";
import { cosmos_sdk as cosmosSdk } from "./generated/codecimpl";
import { cosmos_sdk as cosmosSdk, google } from "./generated/codecimpl";
import { Registry } from "./registry";
type MsgDemo = {
readonly example: string;
};
const { TxBody } = cosmosSdk.tx.v1;
const { Any } = google.protobuf;
describe("registry demo", () => {
it("works with a default msg", () => {
const registry = new Registry();
const Coin = registry.lookupType("/cosmos.Coin")!;
const MsgSend = registry.lookupType("/cosmos.bank.MsgSend")!;
const TxBody = registry.lookupType("/cosmos.tx.TxBody")!;
const Any = registry.lookupType("/google.protobuf.Any")!;
const coin = Coin.create({
denom: "ucosm",
@ -57,8 +54,6 @@ describe("registry demo", () => {
const typeUrl = "/demo.MsgDemo";
const registry = new Registry([[typeUrl, MsgDemoType]]);
const MsgDemo = registry.lookupType(typeUrl)!;
const TxBody = registry.lookupType("/cosmos.tx.TxBody")!;
const Any = registry.lookupType("/google.protobuf.Any")!;
const msgDemo = MsgDemo.create({
example: "Some example text",

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/camelcase */
import protobuf from "protobufjs";
import { cosmos_sdk as cosmosSdk, google } from "./generated/codecimpl";
@ -8,24 +9,109 @@ export interface GeneratedType {
readonly decode: (reader: protobuf.Reader | Uint8Array, length?: number) => any;
}
export interface EncodeObject {
readonly typeUrl: string;
readonly value: any;
}
export interface DecodeObject {
readonly typeUrl: string;
readonly value: Uint8Array;
}
export interface TxBodyValue {
readonly messages: readonly EncodeObject[];
readonly memo?: string;
readonly timeoutHeight?: number;
readonly extensionOptions?: google.protobuf.IAny[];
readonly nonCriticalExtensionOptions?: google.protobuf.IAny[];
}
const defaultTypeUrls = {
cosmosCoin: "/cosmos.Coin",
cosmosMsgSend: "/cosmos.bank.MsgSend",
cosmosTxBody: "/cosmos.tx.TxBody",
googleAny: "/google.protobuf.Any",
};
export class Registry {
private readonly types: Map<string, GeneratedType>;
constructor(customTypes: Iterable<[string, GeneratedType]> = []) {
const { cosmosCoin, cosmosMsgSend } = defaultTypeUrls;
this.types = new Map<string, GeneratedType>([
["/cosmos.Coin", cosmosSdk.v1.Coin],
["/cosmos.bank.MsgSend", cosmosSdk.x.bank.v1.MsgSend],
["/cosmos.tx.TxBody", cosmosSdk.tx.v1.TxBody],
["/google.protobuf.Any", google.protobuf.Any],
[cosmosCoin, cosmosSdk.v1.Coin],
[cosmosMsgSend, cosmosSdk.x.bank.v1.MsgSend],
...customTypes,
]);
}
public register(name: string, type: GeneratedType): void {
this.types.set(name, type);
public register(typeUrl: string, type: GeneratedType): void {
this.types.set(typeUrl, type);
}
public lookupType(name: string): GeneratedType | undefined {
return this.types.get(name);
public lookupType(typeUrl: string): GeneratedType | undefined {
return this.types.get(typeUrl);
}
private lookupTypeWithError(typeUrl: string): GeneratedType {
const type = this.lookupType(typeUrl);
if (!type) {
throw new Error(`Unregistered type url: ${typeUrl}`);
}
return type;
}
public encode({ typeUrl, value }: EncodeObject): Uint8Array {
if (typeUrl === defaultTypeUrls.cosmosTxBody) {
return this.encodeTxBody(value);
}
const type = this.lookupTypeWithError(typeUrl);
const created = type.create(value);
return type.encode(created).finish();
}
public encodeTxBody(txBodyFields: TxBodyValue): Uint8Array {
const { TxBody } = cosmosSdk.tx.v1;
const { Any } = google.protobuf;
const wrappedMessages = txBodyFields.messages.map((message) => {
const messageBytes = this.encode(message);
return Any.create({
type_url: message.typeUrl,
value: messageBytes,
});
});
const txBody = TxBody.create({
...txBodyFields,
messages: wrappedMessages,
});
return TxBody.encode(txBody).finish();
}
public decode({ typeUrl, value }: DecodeObject): any {
if (typeUrl === defaultTypeUrls.cosmosTxBody) {
return this.decodeTxBody(value);
}
const type = this.lookupTypeWithError(typeUrl);
return type.decode(value);
}
public decodeTxBody(txBody: Uint8Array): cosmosSdk.tx.v1.TxBody {
const { TxBody } = cosmosSdk.tx.v1;
const decodedTxBody = TxBody.decode(txBody);
return {
...decodedTxBody,
messages: decodedTxBody.messages.map(({ type_url: typeUrl, value }: google.protobuf.IAny) => {
if (!typeUrl) {
throw new Error("Missing type_url in Any");
}
if (!value) {
throw new Error("Missing value in Any");
}
return this.decode({ typeUrl, value });
}),
};
}
}

View File

@ -1,4 +1,5 @@
import protobuf from "protobufjs";
import { cosmos_sdk as cosmosSdk, google } from "./generated/codecimpl";
export interface GeneratedType {
readonly create: (properties?: { [k: string]: any }) => any;
readonly encode: (
@ -11,9 +12,29 @@ export interface GeneratedType {
) => protobuf.Writer;
readonly decode: (reader: protobuf.Reader | Uint8Array, length?: number) => any;
}
export interface EncodeObject {
readonly typeUrl: string;
readonly value: any;
}
export interface DecodeObject {
readonly typeUrl: string;
readonly value: Uint8Array;
}
export interface TxBodyValue {
readonly messages: readonly EncodeObject[];
readonly memo?: string;
readonly timeoutHeight?: number;
readonly extensionOptions?: google.protobuf.IAny[];
readonly nonCriticalExtensionOptions?: google.protobuf.IAny[];
}
export declare class Registry {
private readonly types;
constructor(customTypes?: Iterable<[string, GeneratedType]>);
register(name: string, type: GeneratedType): void;
lookupType(name: string): GeneratedType | undefined;
register(typeUrl: string, type: GeneratedType): void;
lookupType(typeUrl: string): GeneratedType | undefined;
private lookupTypeWithError;
encode({ typeUrl, value }: EncodeObject): Uint8Array;
encodeTxBody(txBodyFields: TxBodyValue): Uint8Array;
decode({ typeUrl, value }: DecodeObject): any;
decodeTxBody(txBody: Uint8Array): cosmosSdk.tx.v1.TxBody;
}