Skip to content

Instantly share code, notes, and snippets.

@aaronc
Last active August 22, 2019 18:26
Show Gist options
  • Save aaronc/45d2f6ef19ee8193a1ac831503f36881 to your computer and use it in GitHub Desktop.
Save aaronc/45d2f6ef19ee8193a1ac831503f36881 to your computer and use it in GitHub Desktop.
Cosmos client ideas

Codec

It seems like one of the core issues with client side support is an implementation of Amino in some language other than golang.

A couple work-arounds that occur to me are:

  • Create a REST server endpoint that decodes JSON with Amino and re-encodes it in binary Amino.
  • Compile Amino using gopherjs. I have tried this and it appears to be possible, although I haven't tested the generated JS. This is still a work-around, however, because to natively use from JS you would still need to emit JS, have the gopherjs Amino build decode the JS then re-encode it into binary (because the gopherjs compiled go structs aren't native JS structs). For iOS & Android, it may be possible to do the same with gomobile.

Long term solutions:

  • Implementing Amino in JS and other frontend languages. If one were going to implement Amino for just one more language I would suggest Kotlin specifically aims to be multiplatform and compiles to JS, is official for Android, and native/Obj-C compatible for iOS.
  • Protobuf - sounds like this has been considered and the issue would be generating the protobuf spec files from golang code. This is possible but I think it would require adding tags to the golang struct fields with protobuf specific stuff. Might not be best from the developer ergonomics point of view
  • CBOR - could probably be used just like Amino is so good for developer ergonomics, but already widely implemented on other platforms. The main thing would be code generation as discussed below

Types

It would be great if we could code generate Typescript definitions, JSON Schema, etc. types from the golang structs used in transaction messages and in the store. This is totally possible with go reflection and all of these types get registered with the codec. This should be pretty straightforward and could create a CLI command for generating the wrappers (i.e. gaiacli codegen js).

There are tools like quicktype but I would recommend against relying on something like this because the code won't be a perfect mapping from how things work in go. It's probably easy enough to generate code for each client (browser, iOS, Android) and generate JSON Schema just as a catch all for everything else.

Light Client

It may be possible to use gopherjs and gomobile to port the light client. I ran into some issues compiling with gopherjs but my guess is that they're surmountable with some refactoring. If one were to reimplement the light client in other languages, I would suggest again that Kotlin be the first language to consider because of its multiplatform support.

Store Queries

So one big motivation of the light client (as I understand it) is to be able to verify merkle proofs directly on the client. In order to do this, we need to expose direct store queries to clients (not just custom routes). Improving support for this shouldn't be too hard and would probably improve developer ergonomics in golang, although with a bit of learning/migration curve.

What I propose is registering the routes that are used in each store and the types each route stores using some sort of StoreRouter.

Current Approach

This is currently what I see in a lot of Cosmos code:

func SomeDataKey(addr sdk.AccAddress, someInt int) []byte {
  fmt.Sprintf("someData/%x/%d", addr, someInt)
}

func (k Keeper) StoreSomeData(ctx sdk.Context, addr sdk.AccAddress, someInt int, someData MyDataType) {
  store := ctx.KVStore(k.storeKey)
  bz := k.cdc.MustMarshalBinaryBare(someData)
  store.Set(SomeDataKey(addr, someInt, bz)
}

And then to retrieve that data we have some logic like this that actually gets implemented both for the Keeper and for the CLI and REST endpoints:

func (k Keeper) GetSomeData(ctx sdk.Context, addr sdk.AccAddress, someInt int) (someData MyDataType, err sdk.Error) {
  store := ctx.KVStore(k.storeKey)
  bz := store.Get(SomeDataKey(addr, someInt, bz))
  if bz == nil {
    ...
  }
  k.cdc.MustUnmarshalBinaryBare(bz, &someData)
  ...
}

This logic will need to get reimplemented for every client that wants to pull store data and get a merkle proof - in particular the logic in SomeDataKey and the fact that it maps to the type SomeData.

StoreRouter Approach

What I propose is registering the store key -> type mappings using the StoreRouter so that client-side routes can automatically be generated.

So what we might do is register store routes like this:

const MyRoute = "someData/{addr:%x}/{someInt:%d}"

func ConfigureStoreRoutes(storeRouter StoreRouter) {
  storeRouter.RegisterRoute(MyRoute, SomeData{})
}

And then in our keepers we would do something like:

func (k Keeper) StoreSomeData(ctx sdk.Context, addr sdk.AccAddress, someInt int, someData MyDataType) {
  k.storeRouter.Set(ctx, someData, MyRoute, addr, someInt) // here the value comes before the key so we can have a vararg function to take the route params
}

To retrieve the data from the keeper we would write:

func (k Keeper) GetSomeData(ctx sdk.Context, addr sdk.AccAddress, someInt int) (someData MyDataType, err sdk.Error) {
  err := k.storeRouter.Get(ctx, &someData, MyRoute, addr, someInt)
  return someData, err
}

Already that's much more terser code.

And with this we could automatically generate a Javascript (or whatever client-side language) function that looks like:

function getSomeData(ctx: ClientContext, addr: AccAddress, someInt: number): SomeData {
...
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment