diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts index 563c2221..b27a7c59 100644 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ b/packages/sdk/src/cosmwasmclient.spec.ts @@ -4,7 +4,7 @@ import { Bech32, Encoding } from "@iov/encoding"; import { assert, sleep } from "@iov/utils"; import { ReadonlyDate } from "readonly-date"; -import { Code, CosmWasmClient } from "./cosmwasmclient"; +import { Code, CosmWasmClient, PrivateCosmWasmClient } from "./cosmwasmclient"; import { makeSignBytes } from "./encoding"; import { findAttribute } from "./logs"; import { Secp256k1Pen } from "./pen"; @@ -57,14 +57,42 @@ describe("CosmWasmClient", () => { }); describe("getHeight", () => { - it("works", async () => { + it("gets height via last block", async () => { pendingWithoutWasmd(); const client = new CosmWasmClient(wasmdEndpoint); + const openedClient = (client as unknown) as PrivateCosmWasmClient; + const blockLatestSpy = spyOn(openedClient.restClient, "blocksLatest").and.callThrough(); + const height1 = await client.getHeight(); expect(height1).toBeGreaterThan(0); await sleep(1_000); const height2 = await client.getHeight(); expect(height2).toEqual(height1 + 1); + + expect(blockLatestSpy).toHaveBeenCalledTimes(2); + }); + + it("gets height via authAccount once an address is known", async () => { + pendingWithoutWasmd(); + const client = new CosmWasmClient(wasmdEndpoint); + + const openedClient = (client as unknown) as PrivateCosmWasmClient; + const blockLatestSpy = spyOn(openedClient.restClient, "blocksLatest").and.callThrough(); + const authAccountsSpy = spyOn(openedClient.restClient, "authAccounts").and.callThrough(); + + const height1 = await client.getHeight(); + expect(height1).toBeGreaterThan(0); + + await client.getCodes(); // warm up the client + + const height2 = await client.getHeight(); + expect(height2).toBeGreaterThan(0); + await sleep(1_000); + const height3 = await client.getHeight(); + expect(height3).toEqual(height2 + 1); + + expect(blockLatestSpy).toHaveBeenCalledTimes(1); + expect(authAccountsSpy).toHaveBeenCalledTimes(2); }); }); diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index 43c5f580..2fd35a28 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -137,8 +137,15 @@ export interface Block { readonly txs: ReadonlyArray; } +/** Use for testing only */ +export interface PrivateCosmWasmClient { + readonly restClient: RestClient; +} + export class CosmWasmClient { protected readonly restClient: RestClient; + /** Any address the chain considers valid (valid bech32 with proper prefix) */ + protected anyValidAddress: string | undefined; public constructor(url: string, broadcastMode = BroadcastMode.Block) { this.restClient = new RestClient(url, broadcastMode); @@ -150,10 +157,15 @@ export class CosmWasmClient { } public async getHeight(): Promise { - // Note: this gets inefficient when blocks contain a lot of transactions since it - // requires downloading and deserializing all transactions in the block. - const latest = await this.restClient.blocksLatest(); - return parseInt(latest.block.header.height, 10); + if (this.anyValidAddress) { + const { height } = await this.restClient.authAccounts(this.anyValidAddress); + return parseInt(height, 10); + } else { + // Note: this gets inefficient when blocks contain a lot of transactions since it + // requires downloading and deserializing all transactions in the block. + const latest = await this.restClient.blocksLatest(); + return parseInt(latest.block.header.height, 10); + } } /** @@ -189,15 +201,18 @@ export class CosmWasmClient { public async getAccount(address: string): Promise { const account = await this.restClient.authAccounts(address); const value = account.result.value; - return value.address === "" - ? undefined - : { - address: value.address, - balance: value.coins, - pubkey: value.public_key ? decodeBech32Pubkey(value.public_key) : undefined, - accountNumber: value.account_number, - sequence: value.sequence, - }; + if (value.address === "") { + return undefined; + } else { + this.anyValidAddress = value.address; + return { + address: value.address, + balance: value.coins, + pubkey: value.public_key ? decodeBech32Pubkey(value.public_key) : undefined, + accountNumber: value.account_number, + sequence: value.sequence, + }; + } } /** @@ -283,13 +298,16 @@ export class CosmWasmClient { public async getCodes(): Promise { const result = await this.restClient.listCodeInfo(); return result.map( - (entry): Code => ({ - id: entry.id, - creator: entry.creator, - checksum: Encoding.toHex(Encoding.fromHex(entry.data_hash)), - source: entry.source || undefined, - builder: entry.builder || undefined, - }), + (entry): Code => { + this.anyValidAddress = entry.creator; + return { + id: entry.id, + creator: entry.creator, + checksum: Encoding.toHex(Encoding.fromHex(entry.data_hash)), + source: entry.source || undefined, + builder: entry.builder || undefined, + }; + }, ); } diff --git a/packages/sdk/src/signingcosmwasmclient.spec.ts b/packages/sdk/src/signingcosmwasmclient.spec.ts index 79c74fc1..28730f8a 100644 --- a/packages/sdk/src/signingcosmwasmclient.spec.ts +++ b/packages/sdk/src/signingcosmwasmclient.spec.ts @@ -2,6 +2,7 @@ import { Sha256 } from "@iov/crypto"; import { Encoding } from "@iov/encoding"; import { assert } from "@iov/utils"; +import { PrivateCosmWasmClient } from "./cosmwasmclient"; import { Secp256k1Pen } from "./pen"; import { RestClient } from "./restclient"; import { SigningCosmWasmClient, UploadMeta } from "./signingcosmwasmclient"; @@ -31,6 +32,24 @@ describe("SigningCosmWasmClient", () => { }); }); + describe("getHeight", () => { + it("always uses authAccount implementation", async () => { + pendingWithoutWasmd(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmWasmClient(httpUrl, faucet.address, signBytes => pen.sign(signBytes)); + + const openedClient = (client as unknown) as PrivateCosmWasmClient; + const blockLatestSpy = spyOn(openedClient.restClient, "blocksLatest").and.callThrough(); + const authAccountsSpy = spyOn(openedClient.restClient, "authAccounts").and.callThrough(); + + const height = await client.getHeight(); + expect(height).toBeGreaterThan(0); + + expect(blockLatestSpy).toHaveBeenCalledTimes(0); + expect(authAccountsSpy).toHaveBeenCalledTimes(1); + }); + }); + describe("upload", () => { it("works", async () => { pendingWithoutWasmd(); diff --git a/packages/sdk/src/signingcosmwasmclient.ts b/packages/sdk/src/signingcosmwasmclient.ts index db5933f1..2592c04a 100644 --- a/packages/sdk/src/signingcosmwasmclient.ts +++ b/packages/sdk/src/signingcosmwasmclient.ts @@ -111,6 +111,8 @@ export class SigningCosmWasmClient extends CosmWasmClient { broadcastMode = BroadcastMode.Block, ) { super(url, broadcastMode); + this.anyValidAddress = senderAddress; + this.senderAddress = senderAddress; this.signCallback = signCallback; this.fees = { ...defaultFees, ...(customFees || {}) }; diff --git a/packages/sdk/types/cosmwasmclient.d.ts b/packages/sdk/types/cosmwasmclient.d.ts index c9a2f600..67928082 100644 --- a/packages/sdk/types/cosmwasmclient.d.ts +++ b/packages/sdk/types/cosmwasmclient.d.ts @@ -103,8 +103,14 @@ export interface Block { /** Array of raw transactions */ readonly txs: ReadonlyArray; } +/** Use for testing only */ +export interface PrivateCosmWasmClient { + readonly restClient: RestClient; +} export declare class CosmWasmClient { protected readonly restClient: RestClient; + /** Any address the chain considers valid (valid bech32 with proper prefix) */ + protected anyValidAddress: string | undefined; constructor(url: string, broadcastMode?: BroadcastMode); chainId(): Promise; getHeight(): Promise;