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
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`,
`.timestamp` and `.signature` are now optional. They are unset when
`blockIdFlag` is `BlockIdFlag.Absent`. The decoding into `CommitSignature` is

View File

@ -19,7 +19,7 @@ import {
import { Uint53 } from "@cosmjs/math";
import {
Account,
accountFromProto,
accountFromAny,
AuthExtension,
BankExtension,
BroadcastTxResponse,
@ -87,7 +87,7 @@ export class CosmWasmClient {
public async getAccount(searchAddress: string): Promise<Account | null> {
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> {

View File

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

View File

@ -4,6 +4,7 @@ import { Tendermint34Client } from "@cosmjs/tendermint-rpc";
import { assert } from "@cosmjs/utils";
import Long from "long";
import { BaseAccount } from "../codec/cosmos/auth/v1beta1/auth";
import { Any } from "../codec/google/protobuf/any";
import { nonExistentAddress, pendingWithoutSimapp, simapp, unused, validator } from "../testutils.spec";
import { AuthExtension, setupAuthExtension } from "./auth";
@ -24,7 +25,8 @@ describe("AuthExtension", () => {
const account = await client.auth.account(unused.address);
assert(account);
expect(account).toEqual({
expect(account.typeUrl).toEqual("/cosmos.auth.v1beta1.BaseAccount");
expect(BaseAccount.decode(account.value)).toEqual({
address: unused.address,
// pubKey not set
accountNumber: Long.fromNumber(unused.accountNumber, true),
@ -40,10 +42,10 @@ describe("AuthExtension", () => {
const account = await client.auth.account(validator.delegatorAddress);
assert(account);
const pubkey = encodePubkey(validator.pubkey);
expect(account).toEqual({
expect(account.typeUrl).toEqual("/cosmos.auth.v1beta1.BaseAccount");
expect(BaseAccount.decode(account.value)).toEqual({
address: validator.delegatorAddress,
pubKey: Any.fromPartial(pubkey),
pubKey: Any.fromPartial(encodePubkey(validator.pubkey)),
accountNumber: Long.fromNumber(0, true),
sequence: Long.fromNumber(validator.sequence, true),
});
@ -70,7 +72,8 @@ describe("AuthExtension", () => {
const account = await client.auth.unverified.account(unused.address);
assert(account);
expect(account).toEqual({
expect(account.typeUrl).toEqual("/cosmos.auth.v1beta1.BaseAccount");
expect(BaseAccount.decode(account.value)).toEqual({
address: unused.address,
// pubKey not set
accountNumber: Long.fromNumber(unused.accountNumber, true),
@ -86,10 +89,10 @@ describe("AuthExtension", () => {
const account = await client.auth.unverified.account(validator.delegatorAddress);
assert(account);
const pubkey = encodePubkey(validator.pubkey);
expect(account).toEqual({
expect(account.typeUrl).toEqual("/cosmos.auth.v1beta1.BaseAccount");
expect(BaseAccount.decode(account.value)).toEqual({
address: validator.delegatorAddress,
pubKey: Any.fromPartial(pubkey),
pubKey: Any.fromPartial(encodePubkey(validator.pubkey)),
accountNumber: Long.fromNumber(0, 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 { Any } from "../codec/google/protobuf/any";
import { QueryClient } from "./queryclient";
@ -9,9 +5,23 @@ import { createRpc, toAccAddress } from "./utils";
export interface AuthExtension {
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 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 responseData = await base.queryVerified("acc", key);
if (responseData.length === 0) return null;
const account = Any.decode(responseData);
switch (account.typeUrl) {
case "/cosmos.auth.v1beta1.BaseAccount": {
return BaseAccount.decode(account.value);
}
default:
throw new Error(`Unsupported type: '${account.typeUrl}'`);
}
return Any.decode(responseData);
},
unverified: {
account: async (address: string) => {
const { account } = await queryService.Account({ address: address });
if (!account) return null;
switch (account.typeUrl) {
case "/cosmos.auth.v1beta1.BaseAccount": {
assert(account.value);
return BaseAccount.decode(account.value);
}
default:
throw new Error(`Unsupported type: '${account.typeUrl}'`);
}
return account;
},
},
},

View File

@ -16,12 +16,13 @@ import {
Tendermint34Client,
toRfc3339WithNanoseconds,
} from "@cosmjs/tendermint-rpc";
import { assert, assertDefinedAndNotNull } from "@cosmjs/utils";
import { assertDefinedAndNotNull } from "@cosmjs/utils";
import Long from "long";
import { BaseAccount } from "./codec/cosmos/auth/v1beta1/auth";
import { MsgData, TxMsgData } from "./codec/cosmos/base/abci/v1beta1/abci";
import { Coin } from "./codec/cosmos/base/v1beta1/coin";
import { Any } from "./codec/google/protobuf/any";
import { AuthExtension, BankExtension, QueryClient, setupAuthExtension, setupBankExtension } from "./queries";
/** 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());
}
export function accountFromProto(input: BaseAccount): Account {
function accountFromBaseAccount(input: BaseAccount): Account {
const { address, pubKey, accountNumber, sequence } = input;
const pubkey = decodePubkey(pubKey);
assert(address);
return {
address: address,
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 {
assertDefinedAndNotNull(input.amount);
assertDefinedAndNotNull(input.denom);
@ -151,14 +169,14 @@ export class StargateClient {
// this is nice to display data to the user, but is slower
public async getAccount(searchAddress: string): Promise<Account | null> {
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
// (no need to wait a block before submitting)
public async getAccountUnverified(searchAddress: string): Promise<Account | null> {
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> {