From 1e122a7546f512e56677372ad5e70fa812582b16 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 12 Feb 2020 11:27:46 +0100 Subject: [PATCH] Add CosmWasmClient.searchTx --- packages/sdk/src/cosmwasmclient.spec.ts | 132 +++++++++++++++++++++++- packages/sdk/src/cosmwasmclient.ts | 48 ++++++++- packages/sdk/src/index.ts | 11 +- packages/sdk/types/cosmwasmclient.d.ts | 12 +++ packages/sdk/types/index.d.ts | 11 +- 5 files changed, 210 insertions(+), 4 deletions(-) diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts index 80a5b9a2..69cb70b4 100644 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ b/packages/sdk/src/cosmwasmclient.spec.ts @@ -1,3 +1,5 @@ +import { assert } from "@iov/utils"; + import { CosmWasmClient } from "./cosmwasmclient"; import { makeSignBytes, marshalTx } from "./encoding"; import { findAttribute } from "./logs"; @@ -5,7 +7,7 @@ import { Secp256k1Pen } from "./pen"; import { RestClient } from "./restclient"; import cosmoshub from "./testdata/cosmoshub.json"; import { getRandomizedHackatom, makeRandomAddress } from "./testutils.spec"; -import { Coin, MsgSend, StdFee } from "./types"; +import { Coin, CosmosSdkTx, MsgSend, StdFee } from "./types"; const httpUrl = "http://localhost:1317"; @@ -118,6 +120,134 @@ describe("CosmWasmClient", () => { }); }); + describe("searchTx", () => { + let posted: + | { + readonly sender: string; + readonly recipient: string; + readonly hash: string; + readonly height: number; + readonly tx: CosmosSdkTx; + } + | undefined; + + beforeAll(async () => { + if (cosmosEnabled()) { + pendingWithoutCosmos(); + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = CosmWasmClient.makeReadOnly(httpUrl); + + const memo = "My first contract on chain"; + const sendMsg: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + // eslint-disable-next-line @typescript-eslint/camelcase + from_address: faucet.address, + // eslint-disable-next-line @typescript-eslint/camelcase + to_address: makeRandomAddress(), + amount: [ + { + denom: "ucosm", + amount: "1234567", + }, + ], + }, + }; + + const fee: StdFee = { + amount: [ + { + amount: "5000", + denom: "ucosm", + }, + ], + gas: "890000", + }; + + const chainId = await client.chainId(); + const { accountNumber, sequence } = await client.getNonce(faucet.address); + const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); + const signature = await pen.sign(signBytes); + const signedTx = { + msg: [sendMsg], + fee: fee, + memo: memo, + signatures: [signature], + }; + + const result = await client.postTx(marshalTx(signedTx)); + const txDetails = await new RestClient(httpUrl).txsById(result.transactionHash); + posted = { + sender: sendMsg.value.from_address, + recipient: sendMsg.value.to_address, + hash: result.transactionHash, + height: Number.parseInt(txDetails.height, 10), + tx: txDetails.tx, + }; + } + }); + + it("can search by ID", async () => { + pendingWithoutCosmos(); + assert(posted, "value must be set in beforeAll()"); + const client = CosmWasmClient.makeReadOnly(httpUrl); + const result = await client.searchTx({ id: posted.hash }); + expect(result.length).toEqual(1); + expect(result[0]).toEqual( + jasmine.objectContaining({ + height: posted.height.toString(), + txhash: posted.hash, + tx: posted.tx, + }), + ); + }); + + it("can search by height", async () => { + pendingWithoutCosmos(); + assert(posted, "value must be set in beforeAll()"); + const client = CosmWasmClient.makeReadOnly(httpUrl); + const result = await client.searchTx({ height: posted.height }); + expect(result.length).toEqual(1); + expect(result[0]).toEqual( + jasmine.objectContaining({ + height: posted.height.toString(), + txhash: posted.hash, + tx: posted.tx, + }), + ); + }); + + it("can search by sender", async () => { + pendingWithoutCosmos(); + assert(posted, "value must be set in beforeAll()"); + const client = CosmWasmClient.makeReadOnly(httpUrl); + const result = await client.searchTx({ sentFromOrTo: posted.sender }); + expect(result.length).toBeGreaterThanOrEqual(1); + expect(result[result.length - 1]).toEqual( + jasmine.objectContaining({ + height: posted.height.toString(), + txhash: posted.hash, + tx: posted.tx, + }), + ); + }); + + it("can search by recipient", async () => { + pendingWithoutCosmos(); + assert(posted, "value must be set in beforeAll()"); + const client = CosmWasmClient.makeReadOnly(httpUrl); + const result = await client.searchTx({ sentFromOrTo: posted.recipient }); + expect(result.length).toBeGreaterThanOrEqual(1); + expect(result[result.length - 1]).toEqual( + jasmine.objectContaining({ + height: posted.height.toString(), + txhash: posted.hash, + tx: posted.tx, + }), + ); + }); + }); + describe("upload", () => { it("works", async () => { pendingWithoutCosmos(); diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index d0ff8a94..450c090d 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -3,7 +3,7 @@ import { Encoding } from "@iov/encoding"; import { makeSignBytes, marshalTx } from "./encoding"; import { findAttribute, Log, parseLogs } from "./logs"; -import { RestClient } from "./restclient"; +import { RestClient, TxsResponse } from "./restclient"; import { Coin, CosmosSdkTx, @@ -65,6 +65,32 @@ export interface PostTxResult { readonly transactionHash: string; } +export interface SearchByIdQuery { + readonly id: string; +} + +export interface SearchByHeightQuery { + readonly height: number; +} + +export interface SearchBySentFromOrToQuery { + readonly sentFromOrTo: string; +} + +export type SearchTxQuery = SearchByIdQuery | SearchByHeightQuery | SearchBySentFromOrToQuery; + +function isSearchByIdQuery(query: SearchTxQuery): query is SearchByIdQuery { + return (query as SearchByIdQuery).id !== undefined; +} + +function isSearchByHeightQuery(query: SearchTxQuery): query is SearchByHeightQuery { + return (query as SearchByHeightQuery).height !== undefined; +} + +function isSearchBySentFromOrToQuery(query: SearchTxQuery): query is SearchBySentFromOrToQuery { + return (query as SearchBySentFromOrToQuery).sentFromOrTo !== undefined; +} + export interface ExecuteResult { readonly logs: readonly Log[]; } @@ -131,6 +157,26 @@ export class CosmWasmClient { }; } + public async searchTx(query: SearchTxQuery): Promise { + // TODO: we need proper pagination support + function limited(originalQuery: string): string { + return `${originalQuery}&limit=75`; + } + + if (isSearchByIdQuery(query)) { + return [await this.restClient.txsById(query.id)]; + } else if (isSearchByHeightQuery(query)) { + return (await this.restClient.txs(`tx.height=${query.height}`)).txs; + } else if (isSearchBySentFromOrToQuery(query)) { + const sent = (await this.restClient.txs(limited(`message.sender=${query.sentFromOrTo}`))).txs; + const sentHashes = sent.map(t => t.txhash); + const received = (await this.restClient.txs(limited(`transfer.recipient=${query.sentFromOrTo}`))).txs; + return [...sent, ...received.filter(t => !sentHashes.includes(t.txhash))]; + } else { + throw new Error("Unknown query type"); + } + } + public async postTx(tx: Uint8Array): Promise { const result = await this.restClient.postTx(tx); if (result.code) { diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 294c81f8..e144a09e 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -7,7 +7,16 @@ export { unmarshalTx } from "./decoding"; export { makeSignBytes, marshalTx } from "./encoding"; export { RestClient, TxsResponse } from "./restclient"; export { encodeSecp256k1Signature } from "./signature"; -export { CosmWasmClient, ExecuteResult, GetNonceResult, PostTxResult } from "./cosmwasmclient"; +export { + CosmWasmClient, + ExecuteResult, + GetNonceResult, + PostTxResult, + SearchByHeightQuery, + SearchByIdQuery, + SearchBySentFromOrToQuery, + SearchTxQuery, +} from "./cosmwasmclient"; export { makeCosmoshubPath, Pen, PrehashType, Secp256k1Pen } from "./pen"; export { CosmosPubkeyBech32Prefix, diff --git a/packages/sdk/types/cosmwasmclient.d.ts b/packages/sdk/types/cosmwasmclient.d.ts index 4fae10bd..14be80a8 100644 --- a/packages/sdk/types/cosmwasmclient.d.ts +++ b/packages/sdk/types/cosmwasmclient.d.ts @@ -1,4 +1,5 @@ import { Log } from "./logs"; +import { TxsResponse } from "./restclient"; import { Coin, CosmosSdkTx, StdSignature } from "./types"; export interface SigningCallback { (signBytes: Uint8Array): Promise; @@ -13,6 +14,16 @@ export interface PostTxResult { /** Transaction hash (might be used as transaction ID). Guaranteed to be non-exmpty upper-case hex */ readonly transactionHash: string; } +export interface SearchByIdQuery { + readonly id: string; +} +export interface SearchByHeightQuery { + readonly height: number; +} +export interface SearchBySentFromOrToQuery { + readonly sentFromOrTo: string; +} +export declare type SearchTxQuery = SearchByIdQuery | SearchByHeightQuery | SearchBySentFromOrToQuery; export interface ExecuteResult { readonly logs: readonly Log[]; } @@ -35,6 +46,7 @@ export declare class CosmWasmClient { * @param address returns data for this address. When unset, the client's sender adddress is used. */ getNonce(address?: string): Promise; + searchTx(query: SearchTxQuery): Promise; postTx(tx: Uint8Array): Promise; /** Uploads code and returns a code ID */ upload(wasmCode: Uint8Array, memo?: string): Promise; diff --git a/packages/sdk/types/index.d.ts b/packages/sdk/types/index.d.ts index ec6bdaca..0a4c02d7 100644 --- a/packages/sdk/types/index.d.ts +++ b/packages/sdk/types/index.d.ts @@ -6,7 +6,16 @@ export { unmarshalTx } from "./decoding"; export { makeSignBytes, marshalTx } from "./encoding"; export { RestClient, TxsResponse } from "./restclient"; export { encodeSecp256k1Signature } from "./signature"; -export { CosmWasmClient, ExecuteResult, GetNonceResult, PostTxResult } from "./cosmwasmclient"; +export { + CosmWasmClient, + ExecuteResult, + GetNonceResult, + PostTxResult, + SearchByHeightQuery, + SearchByIdQuery, + SearchBySentFromOrToQuery, + SearchTxQuery, +} from "./cosmwasmclient"; export { makeCosmoshubPath, Pen, PrehashType, Secp256k1Pen } from "./pen"; export { CosmosPubkeyBech32Prefix,