Use Any for account encoding

This commit is contained in:
Simon Warta 2021-03-15 16:20:32 +01:00
parent 6491f8f485
commit 659b9193c0
6 changed files with 59 additions and 38 deletions

View File

@ -10,6 +10,10 @@ and this project adheres to
- @cosmjs/cosmwasm-stargate: Codec adapted to support wasmd 0.16. Older versions - @cosmjs/cosmwasm-stargate: Codec adapted to support wasmd 0.16. Older versions
of wasmd are not supported anymore. of wasmd are not supported anymore.
- @cosmjs/stargate: Let `AuthExtension.account` and
`AuthExtension.unverified.account` return an account of type `Any`. This makes
the caller responsible for decoding the type.
- @cosmjs/stargate: Remove `accountFromProto` in favour of `accountFromAny`.
- @cosmjs/tendermint-rpc: The fields `CommitSignature.validatorAddress`, - @cosmjs/tendermint-rpc: The fields `CommitSignature.validatorAddress`,
`.timestamp` and `.signature` are now optional. They are unset when `.timestamp` and `.signature` are now optional. They are unset when
`blockIdFlag` is `BlockIdFlag.Absent`. The decoding into `CommitSignature` is `blockIdFlag` is `BlockIdFlag.Absent`. The decoding into `CommitSignature` is

View File

@ -19,7 +19,7 @@ import {
import { Uint53 } from "@cosmjs/math"; import { Uint53 } from "@cosmjs/math";
import { import {
Account, Account,
accountFromProto, accountFromAny,
AuthExtension, AuthExtension,
BankExtension, BankExtension,
BroadcastTxResponse, BroadcastTxResponse,
@ -87,7 +87,7 @@ export class CosmWasmClient {
public async getAccount(searchAddress: string): Promise<Account | null> { public async getAccount(searchAddress: string): Promise<Account | null> {
const account = await this.queryClient.auth.account(searchAddress); const account = await this.queryClient.auth.account(searchAddress);
return account ? accountFromProto(account) : null; return account ? accountFromAny(account) : null;
} }
public async getSequence(address: string): Promise<SequenceResponse | null> { public async getSequence(address: string): Promise<SequenceResponse | null> {

View File

@ -18,7 +18,7 @@ export {
} from "./queries"; } from "./queries";
export { export {
Account, Account,
accountFromProto, accountFromAny,
assertIsBroadcastTxSuccess, assertIsBroadcastTxSuccess,
BroadcastTxFailure, BroadcastTxFailure,
BroadcastTxResponse, BroadcastTxResponse,

View File

@ -4,6 +4,7 @@ import { Tendermint34Client } from "@cosmjs/tendermint-rpc";
import { assert } from "@cosmjs/utils"; import { assert } from "@cosmjs/utils";
import Long from "long"; import Long from "long";
import { BaseAccount } from "../codec/cosmos/auth/v1beta1/auth";
import { Any } from "../codec/google/protobuf/any"; import { Any } from "../codec/google/protobuf/any";
import { nonExistentAddress, pendingWithoutSimapp, simapp, unused, validator } from "../testutils.spec"; import { nonExistentAddress, pendingWithoutSimapp, simapp, unused, validator } from "../testutils.spec";
import { AuthExtension, setupAuthExtension } from "./auth"; import { AuthExtension, setupAuthExtension } from "./auth";
@ -24,7 +25,8 @@ describe("AuthExtension", () => {
const account = await client.auth.account(unused.address); const account = await client.auth.account(unused.address);
assert(account); assert(account);
expect(account).toEqual({ expect(account.typeUrl).toEqual("/cosmos.auth.v1beta1.BaseAccount");
expect(BaseAccount.decode(account.value)).toEqual({
address: unused.address, address: unused.address,
// pubKey not set // pubKey not set
accountNumber: Long.fromNumber(unused.accountNumber, true), accountNumber: Long.fromNumber(unused.accountNumber, true),
@ -40,10 +42,10 @@ describe("AuthExtension", () => {
const account = await client.auth.account(validator.delegatorAddress); const account = await client.auth.account(validator.delegatorAddress);
assert(account); assert(account);
const pubkey = encodePubkey(validator.pubkey); expect(account.typeUrl).toEqual("/cosmos.auth.v1beta1.BaseAccount");
expect(account).toEqual({ expect(BaseAccount.decode(account.value)).toEqual({
address: validator.delegatorAddress, address: validator.delegatorAddress,
pubKey: Any.fromPartial(pubkey), pubKey: Any.fromPartial(encodePubkey(validator.pubkey)),
accountNumber: Long.fromNumber(0, true), accountNumber: Long.fromNumber(0, true),
sequence: Long.fromNumber(validator.sequence, true), sequence: Long.fromNumber(validator.sequence, true),
}); });
@ -70,7 +72,8 @@ describe("AuthExtension", () => {
const account = await client.auth.unverified.account(unused.address); const account = await client.auth.unverified.account(unused.address);
assert(account); assert(account);
expect(account).toEqual({ expect(account.typeUrl).toEqual("/cosmos.auth.v1beta1.BaseAccount");
expect(BaseAccount.decode(account.value)).toEqual({
address: unused.address, address: unused.address,
// pubKey not set // pubKey not set
accountNumber: Long.fromNumber(unused.accountNumber, true), accountNumber: Long.fromNumber(unused.accountNumber, true),
@ -86,10 +89,10 @@ describe("AuthExtension", () => {
const account = await client.auth.unverified.account(validator.delegatorAddress); const account = await client.auth.unverified.account(validator.delegatorAddress);
assert(account); assert(account);
const pubkey = encodePubkey(validator.pubkey); expect(account.typeUrl).toEqual("/cosmos.auth.v1beta1.BaseAccount");
expect(account).toEqual({ expect(BaseAccount.decode(account.value)).toEqual({
address: validator.delegatorAddress, address: validator.delegatorAddress,
pubKey: Any.fromPartial(pubkey), pubKey: Any.fromPartial(encodePubkey(validator.pubkey)),
accountNumber: Long.fromNumber(0, true), accountNumber: Long.fromNumber(0, true),
sequence: Long.fromNumber(validator.sequence, true), sequence: Long.fromNumber(validator.sequence, true),
}); });

View File

@ -1,7 +1,3 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { assert } from "@cosmjs/utils";
import { BaseAccount } from "../codec/cosmos/auth/v1beta1/auth";
import { QueryClientImpl } from "../codec/cosmos/auth/v1beta1/query"; import { QueryClientImpl } from "../codec/cosmos/auth/v1beta1/query";
import { Any } from "../codec/google/protobuf/any"; import { Any } from "../codec/google/protobuf/any";
import { QueryClient } from "./queryclient"; import { QueryClient } from "./queryclient";
@ -9,9 +5,23 @@ import { createRpc, toAccAddress } from "./utils";
export interface AuthExtension { export interface AuthExtension {
readonly auth: { readonly auth: {
readonly account: (address: string) => Promise<BaseAccount | null>; /**
* Returns an account if it exists and `null` otherwise.
*
* The account is a protobuf Any in order to be able to support many different
* account types in one API. The caller needs to switch over the expeced and supported
* `typeUrl` and decode the `value` using its own type decoder.
*/
readonly account: (address: string) => Promise<Any | null>;
readonly unverified: { readonly unverified: {
readonly account: (address: string) => Promise<BaseAccount | null>; /**
* Returns an account if it exists and `null` otherwise.
*
* The account is a protobuf Any in order to be able to support many different
* account types in one API. The caller needs to switch over the expeced and supported
* `typeUrl` and decode the `value` using its own type decoder.
*/
readonly account: (address: string) => Promise<Any | null>;
}; };
}; };
} }
@ -29,27 +39,13 @@ export function setupAuthExtension(base: QueryClient): AuthExtension {
const key = Uint8Array.from([0x01, ...toAccAddress(address)]); const key = Uint8Array.from([0x01, ...toAccAddress(address)]);
const responseData = await base.queryVerified("acc", key); const responseData = await base.queryVerified("acc", key);
if (responseData.length === 0) return null; if (responseData.length === 0) return null;
const account = Any.decode(responseData); return Any.decode(responseData);
switch (account.typeUrl) {
case "/cosmos.auth.v1beta1.BaseAccount": {
return BaseAccount.decode(account.value);
}
default:
throw new Error(`Unsupported type: '${account.typeUrl}'`);
}
}, },
unverified: { unverified: {
account: async (address: string) => { account: async (address: string) => {
const { account } = await queryService.Account({ address: address }); const { account } = await queryService.Account({ address: address });
if (!account) return null; if (!account) return null;
switch (account.typeUrl) { return account;
case "/cosmos.auth.v1beta1.BaseAccount": {
assert(account.value);
return BaseAccount.decode(account.value);
}
default:
throw new Error(`Unsupported type: '${account.typeUrl}'`);
}
}, },
}, },
}, },

View File

@ -16,12 +16,13 @@ import {
Tendermint34Client, Tendermint34Client,
toRfc3339WithNanoseconds, toRfc3339WithNanoseconds,
} from "@cosmjs/tendermint-rpc"; } from "@cosmjs/tendermint-rpc";
import { assert, assertDefinedAndNotNull } from "@cosmjs/utils"; import { assertDefinedAndNotNull } from "@cosmjs/utils";
import Long from "long"; import Long from "long";
import { BaseAccount } from "./codec/cosmos/auth/v1beta1/auth"; import { BaseAccount } from "./codec/cosmos/auth/v1beta1/auth";
import { MsgData, TxMsgData } from "./codec/cosmos/base/abci/v1beta1/abci"; import { MsgData, TxMsgData } from "./codec/cosmos/base/abci/v1beta1/abci";
import { Coin } from "./codec/cosmos/base/v1beta1/coin"; import { Coin } from "./codec/cosmos/base/v1beta1/coin";
import { Any } from "./codec/google/protobuf/any";
import { AuthExtension, BankExtension, QueryClient, setupAuthExtension, setupBankExtension } from "./queries"; import { AuthExtension, BankExtension, QueryClient, setupAuthExtension, setupBankExtension } from "./queries";
/** A transaction that is indexed as part of the transaction history */ /** A transaction that is indexed as part of the transaction history */
@ -91,10 +92,9 @@ function uint64FromProto(input: number | Long | null | undefined): Uint64 {
return Uint64.fromString(input.toString()); return Uint64.fromString(input.toString());
} }
export function accountFromProto(input: BaseAccount): Account { function accountFromBaseAccount(input: BaseAccount): Account {
const { address, pubKey, accountNumber, sequence } = input; const { address, pubKey, accountNumber, sequence } = input;
const pubkey = decodePubkey(pubKey); const pubkey = decodePubkey(pubKey);
assert(address);
return { return {
address: address, address: address,
pubkey: pubkey, pubkey: pubkey,
@ -103,6 +103,24 @@ export function accountFromProto(input: BaseAccount): Account {
}; };
} }
/**
* Takes an `Any` encoded account from the chain and extracts some common
* `Account` information from it. This is supposed to support the most relevant
* common Cosmos SDK account types. If you need support for exotix account types,
* you'll need to write your own account decoder.
*/
export function accountFromAny(input: Any): Account {
const { typeUrl, value } = input;
switch (typeUrl) {
case "/cosmos.auth.v1beta1.BaseAccount": {
return accountFromBaseAccount(BaseAccount.decode(value));
}
default:
throw new Error(`Unsupported type: '${typeUrl}'`);
}
}
export function coinFromProto(input: Coin): Coin { export function coinFromProto(input: Coin): Coin {
assertDefinedAndNotNull(input.amount); assertDefinedAndNotNull(input.amount);
assertDefinedAndNotNull(input.denom); assertDefinedAndNotNull(input.denom);
@ -151,14 +169,14 @@ export class StargateClient {
// this is nice to display data to the user, but is slower // this is nice to display data to the user, but is slower
public async getAccount(searchAddress: string): Promise<Account | null> { public async getAccount(searchAddress: string): Promise<Account | null> {
const account = await this.queryClient.auth.account(searchAddress); const account = await this.queryClient.auth.account(searchAddress);
return account ? accountFromProto(account) : null; return account ? accountFromAny(account) : null;
} }
// if we just need to get the sequence for signing a transaction, let's make this faster // if we just need to get the sequence for signing a transaction, let's make this faster
// (no need to wait a block before submitting) // (no need to wait a block before submitting)
public async getAccountUnverified(searchAddress: string): Promise<Account | null> { public async getAccountUnverified(searchAddress: string): Promise<Account | null> {
const account = await this.queryClient.auth.unverified.account(searchAddress); const account = await this.queryClient.auth.unverified.account(searchAddress);
return account ? accountFromProto(account) : null; return account ? accountFromAny(account) : null;
} }
public async getSequence(address: string): Promise<SequenceResponse | null> { public async getSequence(address: string): Promise<SequenceResponse | null> {