From 728a29a07242ad2d68c39f94003eb9eee4647a6f Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 11 Mar 2020 12:25:01 +0100 Subject: [PATCH 01/11] Improve documentation of RestClient, CosmWasmClient, SigningCosmWasmClient --- packages/sdk/src/cosmwasmclient.ts | 13 ++++++++++-- packages/sdk/src/restclient.ts | 21 ++++++++++++++----- packages/sdk/src/signingcosmwasmclient.ts | 16 ++++++++++++-- packages/sdk/types/cosmwasmclient.d.ts | 11 +++++++++- packages/sdk/types/restclient.d.ts | 15 +++++++++++-- packages/sdk/types/signingcosmwasmclient.d.ts | 14 ++++++++++++- 6 files changed, 77 insertions(+), 13 deletions(-) diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index 1413b11c..2d4c09be 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -150,8 +150,17 @@ export class CosmWasmClient { private readonly codesCache = new Map(); private chainId: string | undefined; - public constructor(url: string, broadcastMode = BroadcastMode.Block) { - this.restClient = new RestClient(url, broadcastMode); + /** + * Creates a new client to interact with a CosmWasm blockchain. + * + * This instance does a lot of caching. In order to benefit from that you should try to use one instance + * for the lifetime of your application. When switching backends, a new instance must be created. + * + * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) + * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns + */ + public constructor(apiUrl: string, broadcastMode = BroadcastMode.Block) { + this.restClient = new RestClient(apiUrl, broadcastMode); } public async getChainId(): Promise { diff --git a/packages/sdk/src/restclient.ts b/packages/sdk/src/restclient.ts index 96ed584f..0db2f078 100644 --- a/packages/sdk/src/restclient.ts +++ b/packages/sdk/src/restclient.ts @@ -263,17 +263,28 @@ function parseAxiosError(err: AxiosError): never { export class RestClient { private readonly client: AxiosInstance; - private readonly mode: BroadcastMode; + private readonly broadcastMode: BroadcastMode; - public constructor(url: string, mode = BroadcastMode.Block) { + /** + * Creates a new client to interact with a Cosmos SDK light client daemon. + * This class tries to be a direct mapping onto the API. Some basic decoding and normalizatin is done + * but things like caching are done at a higher level. + * + * When building apps, you should not need to use this class directly. If you do, this indicates a missing feature + * in higher level components. Feel free to raise an issue in this case. + * + * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) + * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns + */ + public constructor(apiUrl: string, broadcastMode = BroadcastMode.Block) { const headers = { post: { "Content-Type": "application/json" }, }; this.client = axios.create({ - baseURL: url, + baseURL: apiUrl, headers: headers, }); - this.mode = mode; + this.broadcastMode = broadcastMode; } public async get(path: string): Promise { @@ -368,7 +379,7 @@ export class RestClient { public async postTx(tx: StdTx): Promise { const params = { tx: tx, - mode: this.mode, + mode: this.broadcastMode, }; const responseData = await this.post("/txs", params); if (!(responseData as any).txhash) { diff --git a/packages/sdk/src/signingcosmwasmclient.ts b/packages/sdk/src/signingcosmwasmclient.ts index 44a80c73..8b21422c 100644 --- a/packages/sdk/src/signingcosmwasmclient.ts +++ b/packages/sdk/src/signingcosmwasmclient.ts @@ -103,14 +103,26 @@ export class SigningCosmWasmClient extends CosmWasmClient { private readonly signCallback: SigningCallback; private readonly fees: FeeTable; + /** + * Creates a new client with signing capability to interact with a CosmWasm blockchain. This is the bigger brother of CosmWasmClient. + * + * This instance does a lot of caching. In order to benefit from that you should try to use one instance + * for the lifetime of your application. When switching backends, a new instance must be created. + * + * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) + * @param senderAddress The address that will sign and send transactions using this instance + * @param signCallback An asynchonous callback to create a signature for a given transaction. This can be implemented using secure key stores that require user interaction. + * @param customFees The fees that are paid for transactions + * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns + */ public constructor( - url: string, + apiUrl: string, senderAddress: string, signCallback: SigningCallback, customFees?: Partial, broadcastMode = BroadcastMode.Block, ) { - super(url, broadcastMode); + super(apiUrl, broadcastMode); this.anyValidAddress = senderAddress; this.senderAddress = senderAddress; diff --git a/packages/sdk/types/cosmwasmclient.d.ts b/packages/sdk/types/cosmwasmclient.d.ts index b83ff2f5..63a31464 100644 --- a/packages/sdk/types/cosmwasmclient.d.ts +++ b/packages/sdk/types/cosmwasmclient.d.ts @@ -113,7 +113,16 @@ export declare class CosmWasmClient { protected anyValidAddress: string | undefined; private readonly codesCache; private chainId; - constructor(url: string, broadcastMode?: BroadcastMode); + /** + * Creates a new client to interact with a CosmWasm blockchain. + * + * This instance does a lot of caching. In order to benefit from that you should try to use one instance + * for the lifetime of your application. When switching backends, a new instance must be created. + * + * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) + * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns + */ + constructor(apiUrl: string, broadcastMode?: BroadcastMode); getChainId(): Promise; getHeight(): Promise; /** diff --git a/packages/sdk/types/restclient.d.ts b/packages/sdk/types/restclient.d.ts index c2d7232c..4ef9b5e6 100644 --- a/packages/sdk/types/restclient.d.ts +++ b/packages/sdk/types/restclient.d.ts @@ -178,8 +178,19 @@ export declare enum BroadcastMode { } export declare class RestClient { private readonly client; - private readonly mode; - constructor(url: string, mode?: BroadcastMode); + private readonly broadcastMode; + /** + * Creates a new client to interact with a Cosmos SDK light client daemon. + * This class tries to be a direct mapping onto the API. Some basic decoding and normalizatin is done + * but things like caching are done at a higher level. + * + * When building apps, you should not need to use this class directly. If you do, this indicates a missing feature + * in higher level components. Feel free to raise an issue in this case. + * + * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) + * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns + */ + constructor(apiUrl: string, broadcastMode?: BroadcastMode); get(path: string): Promise; post(path: string, params: PostTxsParams): Promise; authAccounts(address: string): Promise; diff --git a/packages/sdk/types/signingcosmwasmclient.d.ts b/packages/sdk/types/signingcosmwasmclient.d.ts index 9c8f5b0a..cb27ffdf 100644 --- a/packages/sdk/types/signingcosmwasmclient.d.ts +++ b/packages/sdk/types/signingcosmwasmclient.d.ts @@ -48,8 +48,20 @@ export declare class SigningCosmWasmClient extends CosmWasmClient { readonly senderAddress: string; private readonly signCallback; private readonly fees; + /** + * Creates a new client with signing capability to interact with a CosmWasm blockchain. This is the bigger brother of CosmWasmClient. + * + * This instance does a lot of caching. In order to benefit from that you should try to use one instance + * for the lifetime of your application. When switching backends, a new instance must be created. + * + * @param apiUrl The URL of a Cosmos SDK light client daemon API (sometimes called REST server or REST API) + * @param senderAddress The address that will sign and send transactions using this instance + * @param signCallback An asynchonous callback to create a signature for a given transaction. This can be implemented using secure key stores that require user interaction. + * @param customFees The fees that are paid for transactions + * @param broadcastMode Defines at which point of the transaction processing the postTx method (i.e. transaction broadcasting) returns + */ constructor( - url: string, + apiUrl: string, senderAddress: string, signCallback: SigningCallback, customFees?: Partial, From 72e7638c197a197f2bc20eca8266d0e599586e1a Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 11 Mar 2020 12:39:29 +0100 Subject: [PATCH 02/11] Remove misleading PostTxsParams which is not for postTx() --- packages/sdk/src/restclient.ts | 7 +++---- packages/sdk/types/restclient.d.ts | 3 +-- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/sdk/src/restclient.ts b/packages/sdk/src/restclient.ts index 0db2f078..e66b0906 100644 --- a/packages/sdk/src/restclient.ts +++ b/packages/sdk/src/restclient.ts @@ -1,4 +1,4 @@ -import { Encoding } from "@iov/encoding"; +import { Encoding, isNonNullObject } from "@iov/encoding"; import axios, { AxiosError, AxiosInstance } from "axios"; import { Coin, CosmosSdkTx, Model, parseWasmData, StdTx, WasmData } from "./types"; @@ -138,8 +138,6 @@ interface SearchTxsResponse { readonly txs: readonly TxsResponse[]; } -interface PostTxsParams {} - export interface PostTxsResponse { readonly height: string; readonly txhash: string; @@ -295,7 +293,8 @@ export class RestClient { return data; } - public async post(path: string, params: PostTxsParams): Promise { + public async post(path: string, params: any): Promise { + if (!isNonNullObject(params)) throw new Error("Got unexpected type of params. Expected object."); const { data } = await this.client.post(path, params).catch(parseAxiosError); if (data === null) { throw new Error("Received null response from server"); diff --git a/packages/sdk/types/restclient.d.ts b/packages/sdk/types/restclient.d.ts index 4ef9b5e6..90a28789 100644 --- a/packages/sdk/types/restclient.d.ts +++ b/packages/sdk/types/restclient.d.ts @@ -110,7 +110,6 @@ interface SearchTxsResponse { readonly limit: string; readonly txs: readonly TxsResponse[]; } -interface PostTxsParams {} export interface PostTxsResponse { readonly height: string; readonly txhash: string; @@ -192,7 +191,7 @@ export declare class RestClient { */ constructor(apiUrl: string, broadcastMode?: BroadcastMode); get(path: string): Promise; - post(path: string, params: PostTxsParams): Promise; + post(path: string, params: any): Promise; authAccounts(address: string): Promise; blocksLatest(): Promise; blocks(height: number): Promise; From 9428bab50afb61a410b328ddd0d49119fed0002c Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 11 Mar 2020 13:19:17 +0100 Subject: [PATCH 03/11] Test PostTxsResponse --- packages/sdk/src/restclient.spec.ts | 16 ++++++++++++---- packages/sdk/src/testutils.spec.ts | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/sdk/src/restclient.spec.ts b/packages/sdk/src/restclient.spec.ts index 194a5204..9f3b1fd5 100644 --- a/packages/sdk/src/restclient.spec.ts +++ b/packages/sdk/src/restclient.spec.ts @@ -18,10 +18,10 @@ import { fromOneElementArray, getHackatom, makeRandomAddress, + nonNegativeIntegerMatcher, pendingWithoutWasmd, semverMatcher, tendermintAddressMatcher, - tendermintHeightMatcher, tendermintIdMatcher, tendermintOptionalIdMatcher, tendermintShortHashMatcher, @@ -178,7 +178,7 @@ describe("RestClient", () => { pendingWithoutWasmd(); const client = new RestClient(wasmd.endpoint); const { height, result } = await client.authAccounts(unusedAccount.address); - expect(height).toMatch(tendermintHeightMatcher); + expect(height).toMatch(nonNegativeIntegerMatcher); expect(result).toEqual({ type: "cosmos-sdk/Account", value: { @@ -649,8 +649,16 @@ describe("RestClient", () => { const signature = await pen.sign(signBytes); const signedTx = makeSignedTx(theMsg, fee, memo, signature); const result = await client.postTx(signedTx); - // console.log("Raw log:", result.raw_log); - expect(result.code).toBeFalsy(); + expect(result.code).toBeUndefined(); + expect(result).toEqual({ + height: jasmine.stringMatching(nonNegativeIntegerMatcher), + txhash: jasmine.stringMatching(tendermintIdMatcher), + // code is not set + raw_log: jasmine.stringMatching(/^\[.+\]$/i), + logs: jasmine.any(Array), + gas_wanted: jasmine.stringMatching(nonNegativeIntegerMatcher), + gas_used: jasmine.stringMatching(nonNegativeIntegerMatcher), + }); }); it("can upload, instantiate and execute wasm", async () => { diff --git a/packages/sdk/src/testutils.spec.ts b/packages/sdk/src/testutils.spec.ts index a204f5c5..11b0e359 100644 --- a/packages/sdk/src/testutils.spec.ts +++ b/packages/sdk/src/testutils.spec.ts @@ -11,7 +11,7 @@ export function makeRandomAddress(): string { return Bech32.encode("cosmos", Random.getBytes(20)); } -export const tendermintHeightMatcher = /^[0-9]+$/; +export const nonNegativeIntegerMatcher = /^[0-9]+$/; export const tendermintIdMatcher = /^[0-9A-F]{64}$/; export const tendermintOptionalIdMatcher = /^([0-9A-F]{64}|)$/; export const tendermintAddressMatcher = /^[0-9A-F]{40}$/; From 05d941788c3ae1b47bf9f01aab080b3daad696ce Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 11 Mar 2020 13:47:31 +0100 Subject: [PATCH 04/11] Use wasmd.chainId in RestClient tests --- packages/sdk/src/cosmwasmclient.spec.ts | 6 +++--- packages/sdk/src/restclient.spec.ts | 17 ++++++++--------- packages/sdk/src/testutils.spec.ts | 2 +- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts index 7e2ddb80..9491a6e0 100644 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ b/packages/sdk/src/cosmwasmclient.spec.ts @@ -52,7 +52,7 @@ describe("CosmWasmClient", () => { it("works", async () => { pendingWithoutWasmd(); const client = new CosmWasmClient(wasmd.endpoint); - expect(await client.getChainId()).toEqual(wasmd.expectedChainId); + expect(await client.getChainId()).toEqual(wasmd.chainId); }); it("caches chain ID", async () => { @@ -61,8 +61,8 @@ describe("CosmWasmClient", () => { const openedClient = (client as unknown) as PrivateCosmWasmClient; const getCodeSpy = spyOn(openedClient.restClient, "nodeInfo").and.callThrough(); - expect(await client.getChainId()).toEqual(wasmd.expectedChainId); // from network - expect(await client.getChainId()).toEqual(wasmd.expectedChainId); // from cache + expect(await client.getChainId()).toEqual(wasmd.chainId); // from network + expect(await client.getChainId()).toEqual(wasmd.chainId); // from cache expect(getCodeSpy).toHaveBeenCalledTimes(1); }); diff --git a/packages/sdk/src/restclient.spec.ts b/packages/sdk/src/restclient.spec.ts index 9f3b1fd5..354702a0 100644 --- a/packages/sdk/src/restclient.spec.ts +++ b/packages/sdk/src/restclient.spec.ts @@ -44,7 +44,6 @@ import { const { fromAscii, fromBase64, fromHex, toAscii, toBase64, toHex } = Encoding; -const defaultNetworkId = "testing"; const emptyAddress = "cosmos1ltkhnmdcqemmd2tkhnx7qx66tq7e0wykw2j85k"; const unusedAccount = { address: "cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u", @@ -85,7 +84,7 @@ async function uploadCustomContract( }; const { account_number, sequence } = (await client.authAccounts(faucet.address)).result.value; - const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account_number, sequence); + const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); const signature = await pen.sign(signBytes); const signedTx = makeSignedTx(theMsg, fee, memo, signature); return client.postTx(signedTx); @@ -127,7 +126,7 @@ async function instantiateContract( }; const { account_number, sequence } = (await client.authAccounts(faucet.address)).result.value; - const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account_number, sequence); + const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); const signature = await pen.sign(signBytes); const signedTx = makeSignedTx(theMsg, fee, memo, signature); return client.postTx(signedTx); @@ -159,7 +158,7 @@ async function executeContract( }; const { account_number, sequence } = (await client.authAccounts(faucet.address)).result.value; - const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account_number, sequence); + const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); const signature = await pen.sign(signBytes); const signedTx = makeSignedTx(theMsg, fee, memo, signature); return client.postTx(signedTx); @@ -239,7 +238,7 @@ describe("RestClient", () => { // header expect(response.block.header.version).toEqual({ block: "10", app: "0" }); expect(parseInt(response.block.header.height, 10)).toBeGreaterThanOrEqual(1); - expect(response.block.header.chain_id).toEqual(defaultNetworkId); + expect(response.block.header.chain_id).toEqual(wasmd.chainId); expect(new ReadonlyDate(response.block.header.time).getTime()).toBeLessThan(ReadonlyDate.now()); expect(new ReadonlyDate(response.block.header.time).getTime()).toBeGreaterThanOrEqual( ReadonlyDate.now() - 5_000, @@ -273,7 +272,7 @@ describe("RestClient", () => { // header expect(response.block.header.version).toEqual({ block: "10", app: "0" }); expect(response.block.header.height).toEqual(`${height - 1}`); - expect(response.block.header.chain_id).toEqual(defaultNetworkId); + expect(response.block.header.chain_id).toEqual(wasmd.chainId); expect(new ReadonlyDate(response.block.header.time).getTime()).toBeLessThan(ReadonlyDate.now()); expect(new ReadonlyDate(response.block.header.time).getTime()).toBeGreaterThanOrEqual( ReadonlyDate.now() - 5_000, @@ -306,10 +305,10 @@ describe("RestClient", () => { protocol_version: { p2p: "7", block: "10", app: "0" }, id: jasmine.stringMatching(tendermintShortHashMatcher), listen_addr: "tcp://0.0.0.0:26656", - network: defaultNetworkId, + network: wasmd.chainId, version: "0.33.0", channels: "4020212223303800", - moniker: defaultNetworkId, + moniker: wasmd.chainId, other: { tx_index: "on", rpc_address: "tcp://0.0.0.0:26657" }, }); expect(application_version).toEqual({ @@ -645,7 +644,7 @@ describe("RestClient", () => { const client = new RestClient(wasmd.endpoint); const { account_number, sequence } = (await client.authAccounts(faucet.address)).result.value; - const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account_number, sequence); + const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence); const signature = await pen.sign(signBytes); const signedTx = makeSignedTx(theMsg, fee, memo, signature); const result = await client.postTx(signedTx); diff --git a/packages/sdk/src/testutils.spec.ts b/packages/sdk/src/testutils.spec.ts index 11b0e359..ac6980af 100644 --- a/packages/sdk/src/testutils.spec.ts +++ b/packages/sdk/src/testutils.spec.ts @@ -36,7 +36,7 @@ export const deployedErc20 = { export const wasmd = { endpoint: "http://localhost:1317", - expectedChainId: "testing", + chainId: "testing", }; export const faucet = { From 7a327168acc9e09b7871aee4f9d8fd406c831e2f Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Tue, 17 Mar 2020 23:21:17 +0100 Subject: [PATCH 05/11] Test full txsQuery result --- packages/sdk/src/restclient.spec.ts | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/sdk/src/restclient.spec.ts b/packages/sdk/src/restclient.spec.ts index 354702a0..40c93ef3 100644 --- a/packages/sdk/src/restclient.spec.ts +++ b/packages/sdk/src/restclient.spec.ts @@ -369,12 +369,14 @@ describe("RestClient", () => { assert(posted); const client = new RestClient(wasmd.endpoint); const result = await client.txsQuery(`tx.height=${posted.height}&limit=26`); - expect(parseInt(result.count, 10)).toEqual(1); - expect(parseInt(result.limit, 10)).toEqual(26); - expect(parseInt(result.page_number, 10)).toEqual(1); - expect(parseInt(result.page_total, 10)).toEqual(1); - expect(parseInt(result.total_count, 10)).toEqual(1); - expect(result.txs).toEqual([posted.tx]); + expect(result).toEqual({ + count: "1", + limit: "26", + page_number: "1", + page_total: "1", + total_count: "1", + txs: [posted.tx], + }); }); it("can query transactions by ID", async () => { @@ -382,12 +384,14 @@ describe("RestClient", () => { assert(posted); const client = new RestClient(wasmd.endpoint); const result = await client.txsQuery(`tx.hash=${posted.hash}&limit=26`); - expect(parseInt(result.count, 10)).toEqual(1); - expect(parseInt(result.limit, 10)).toEqual(26); - expect(parseInt(result.page_number, 10)).toEqual(1); - expect(parseInt(result.page_total, 10)).toEqual(1); - expect(parseInt(result.total_count, 10)).toEqual(1); - expect(result.txs).toEqual([posted.tx]); + expect(result).toEqual({ + count: "1", + limit: "26", + page_number: "1", + page_total: "1", + total_count: "1", + txs: [posted.tx], + }); }); it("can query transactions by sender", async () => { From 9b609d3c96dcb642447845f78f713d37681133e3 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 18 Mar 2020 00:13:59 +0100 Subject: [PATCH 06/11] Test txsById --- packages/sdk/src/restclient.spec.ts | 69 +++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/packages/sdk/src/restclient.spec.ts b/packages/sdk/src/restclient.spec.ts index 40c93ef3..0c216d1e 100644 --- a/packages/sdk/src/restclient.spec.ts +++ b/packages/sdk/src/restclient.spec.ts @@ -325,6 +325,75 @@ describe("RestClient", () => { // The /txs endpoints + describe("txsById", () => { + let posted: + | { + readonly sender: string; + readonly recipient: string; + readonly hash: string; + } + | undefined; + + beforeAll(async () => { + if (wasmdEnabled()) { + const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmWasmClient(wasmd.endpoint, faucet.address, signBytes => + pen.sign(signBytes), + ); + + const recipient = makeRandomAddress(); + const transferAmount = [ + { + denom: "ucosm", + amount: "1234567", + }, + ]; + const result = await client.sendTokens(recipient, transferAmount); + + await sleep(50); // wait until tx is indexed + posted = { + sender: faucet.address, + recipient: recipient, + hash: result.transactionHash, + }; + } + }); + + it("works", async () => { + pendingWithoutWasmd(); + assert(posted); + const client = new RestClient(wasmd.endpoint); + const result = await client.txsById(posted.hash); + expect(result.height).toBeGreaterThanOrEqual(1); + expect(result.txhash).toEqual(posted.hash); + const logs = parseLogs(result.logs); + expect(logs).toEqual([ + { + msg_index: 0, + log: "", + events: [ + { + type: "message", + attributes: [ + { key: "action", value: "send" }, + { key: "sender", value: posted.sender }, + { key: "module", value: "bank" }, + ], + }, + { + type: "transfer", + attributes: [ + { key: "recipient", value: posted.recipient }, + { key: "sender", value: posted.sender }, + { key: "amount", value: "1234567ucosm" }, + ], + }, + ], + }, + ]); + }); + }); + describe("txsQuery", () => { let posted: | { From eb03360b933c1182c325ea861b1de5514b4fcc4c Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 18 Mar 2020 00:27:48 +0100 Subject: [PATCH 07/11] Rename to RestClient.txById and move above search --- packages/sdk/src/cosmwasmclient.searchtx.spec.ts | 4 ++-- packages/sdk/src/restclient.spec.ts | 6 +++--- packages/sdk/src/restclient.ts | 16 ++++++++-------- packages/sdk/types/restclient.d.ts | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/sdk/src/cosmwasmclient.searchtx.spec.ts b/packages/sdk/src/cosmwasmclient.searchtx.spec.ts index be9cd8b3..64d0edaa 100644 --- a/packages/sdk/src/cosmwasmclient.searchtx.spec.ts +++ b/packages/sdk/src/cosmwasmclient.searchtx.spec.ts @@ -51,7 +51,7 @@ describe("CosmWasmClient.searchTx", () => { }; const result = await client.sendTokens(recipient, [transferAmount]); await sleep(50); // wait until tx is indexed - const txDetails = await new RestClient(wasmd.endpoint).txsById(result.transactionHash); + const txDetails = await new RestClient(wasmd.endpoint).txById(result.transactionHash); postedSend = { sender: faucet.address, recipient: recipient, @@ -71,7 +71,7 @@ describe("CosmWasmClient.searchTx", () => { }; const result = await client.execute(hashInstance, msg); await sleep(50); // wait until tx is indexed - const txDetails = await new RestClient(wasmd.endpoint).txsById(result.transactionHash); + const txDetails = await new RestClient(wasmd.endpoint).txById(result.transactionHash); postedExecute = { sender: faucet.address, contract: hashInstance, diff --git a/packages/sdk/src/restclient.spec.ts b/packages/sdk/src/restclient.spec.ts index 0c216d1e..2e1ff474 100644 --- a/packages/sdk/src/restclient.spec.ts +++ b/packages/sdk/src/restclient.spec.ts @@ -325,7 +325,7 @@ describe("RestClient", () => { // The /txs endpoints - describe("txsById", () => { + describe("txById", () => { let posted: | { readonly sender: string; @@ -363,7 +363,7 @@ describe("RestClient", () => { pendingWithoutWasmd(); assert(posted); const client = new RestClient(wasmd.endpoint); - const result = await client.txsById(posted.hash); + const result = await client.txById(posted.hash); expect(result.height).toBeGreaterThanOrEqual(1); expect(result.txhash).toEqual(posted.hash); const logs = parseLogs(result.logs); @@ -422,7 +422,7 @@ describe("RestClient", () => { const result = await client.sendTokens(recipient, transferAmount); await sleep(50); // wait until tx is indexed - const txDetails = await new RestClient(wasmd.endpoint).txsById(result.transactionHash); + const txDetails = await new RestClient(wasmd.endpoint).txById(result.transactionHash); posted = { sender: faucet.address, recipient: recipient, diff --git a/packages/sdk/src/restclient.ts b/packages/sdk/src/restclient.ts index e66b0906..9b12c95f 100644 --- a/packages/sdk/src/restclient.ts +++ b/packages/sdk/src/restclient.ts @@ -343,6 +343,14 @@ export class RestClient { // The /txs endpoints + public async txById(id: string): Promise { + const responseData = await this.get(`/txs/${id}`); + if (!(responseData as any).tx) { + throw new Error("Unexpected response data format"); + } + return responseData as TxsResponse; + } + public async txsQuery(query: string): Promise { const responseData = await this.get(`/txs?${query}`); if (!(responseData as any).txs) { @@ -351,14 +359,6 @@ export class RestClient { return responseData as SearchTxsResponse; } - public async txsById(id: string): Promise { - const responseData = await this.get(`/txs/${id}`); - if (!(responseData as any).tx) { - throw new Error("Unexpected response data format"); - } - return responseData as TxsResponse; - } - /** returns the amino-encoding of the transaction performed by the server */ public async encodeTx(tx: CosmosSdkTx): Promise { const responseData = await this.post("/txs/encode", tx); diff --git a/packages/sdk/types/restclient.d.ts b/packages/sdk/types/restclient.d.ts index 90a28789..97dda92a 100644 --- a/packages/sdk/types/restclient.d.ts +++ b/packages/sdk/types/restclient.d.ts @@ -196,8 +196,8 @@ export declare class RestClient { blocksLatest(): Promise; blocks(height: number): Promise; nodeInfo(): Promise; + txById(id: string): Promise; txsQuery(query: string): Promise; - txsById(id: string): Promise; /** returns the amino-encoding of the transaction performed by the server */ encodeTx(tx: CosmosSdkTx): Promise; /** From a46a88c5c2a78bc08a4389efbb7b7ce2babdaf6f Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 18 Mar 2020 10:21:03 +0100 Subject: [PATCH 08/11] Add and test missing TxsResponse.code --- packages/sdk/src/restclient.spec.ts | 112 +++++++++++++++++++++++----- packages/sdk/src/restclient.ts | 2 + packages/sdk/types/restclient.d.ts | 2 + 3 files changed, 96 insertions(+), 20 deletions(-) diff --git a/packages/sdk/src/restclient.spec.ts b/packages/sdk/src/restclient.spec.ts index 2e1ff474..da8f37f3 100644 --- a/packages/sdk/src/restclient.spec.ts +++ b/packages/sdk/src/restclient.spec.ts @@ -326,7 +326,14 @@ describe("RestClient", () => { // The /txs endpoints describe("txById", () => { - let posted: + let successful: + | { + readonly sender: string; + readonly recipient: string; + readonly hash: string; + } + | undefined; + let unsuccessful: | { readonly sender: string; readonly recipient: string; @@ -341,31 +348,84 @@ describe("RestClient", () => { pen.sign(signBytes), ); - const recipient = makeRandomAddress(); - const transferAmount = [ - { + { + const recipient = makeRandomAddress(); + const transferAmount = { denom: "ucosm", amount: "1234567", - }, - ]; - const result = await client.sendTokens(recipient, transferAmount); + }; + const result = await client.sendTokens(recipient, [transferAmount]); + successful = { + sender: faucet.address, + recipient: recipient, + hash: result.transactionHash, + }; + } - await sleep(50); // wait until tx is indexed - posted = { - sender: faucet.address, - recipient: recipient, - hash: result.transactionHash, - }; + { + const memo = "Sending more than I can afford"; + const recipient = makeRandomAddress(); + const transferAmount = [ + { + denom: "ucosm", + amount: "123456700000000", + }, + ]; + 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: recipient, + amount: transferAmount, + }, + }; + const fee = { + amount: [ + { + denom: "ucosm", + amount: "2000", + }, + ], + gas: "80000", // 80k + }; + const { accountNumber, sequence } = await client.getNonce(); + const chainId = await client.getChainId(); + 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 transactionId = await client.getIdentifier({ type: "cosmos-sdk/StdTx", value: signedTx }); + try { + await client.postTx(signedTx); + } catch (error) { + // postTx() throws on execution failures, which is a questionable design. Ignore for now. + // console.log(error); + } + unsuccessful = { + sender: faucet.address, + recipient: recipient, + hash: transactionId, + }; + } + + await sleep(50); // wait until transactions are indexed } }); - it("works", async () => { + it("works for successful transaction", async () => { pendingWithoutWasmd(); - assert(posted); + assert(successful); const client = new RestClient(wasmd.endpoint); - const result = await client.txById(posted.hash); + const result = await client.txById(successful.hash); expect(result.height).toBeGreaterThanOrEqual(1); - expect(result.txhash).toEqual(posted.hash); + expect(result.txhash).toEqual(successful.hash); + expect(result.code).toBeUndefined(); const logs = parseLogs(result.logs); expect(logs).toEqual([ { @@ -376,15 +436,15 @@ describe("RestClient", () => { type: "message", attributes: [ { key: "action", value: "send" }, - { key: "sender", value: posted.sender }, + { key: "sender", value: successful.sender }, { key: "module", value: "bank" }, ], }, { type: "transfer", attributes: [ - { key: "recipient", value: posted.recipient }, - { key: "sender", value: posted.sender }, + { key: "recipient", value: successful.recipient }, + { key: "sender", value: successful.sender }, { key: "amount", value: "1234567ucosm" }, ], }, @@ -392,6 +452,18 @@ describe("RestClient", () => { }, ]); }); + + it("works for unsuccessful transaction", async () => { + pendingWithoutWasmd(); + assert(unsuccessful); + const client = new RestClient(wasmd.endpoint); + const result = await client.txById(unsuccessful.hash); + expect(result.height).toBeGreaterThanOrEqual(1); + expect(result.txhash).toEqual(unsuccessful.hash); + expect(result.code).toEqual(5); + expect(result.logs).toBeUndefined(); + expect(result.raw_log).toContain("insufficient funds"); + }); }); describe("txsQuery", () => { diff --git a/packages/sdk/src/restclient.ts b/packages/sdk/src/restclient.ts index 9b12c95f..2951b7ff 100644 --- a/packages/sdk/src/restclient.ts +++ b/packages/sdk/src/restclient.ts @@ -119,6 +119,8 @@ interface WasmError { export interface TxsResponse { readonly height: string; readonly txhash: string; + /** Falsy when transaction execution succeeded. Contains error code on error. */ + readonly code?: number; readonly raw_log: string; readonly logs?: object; readonly tx: CosmosSdkTx; diff --git a/packages/sdk/types/restclient.d.ts b/packages/sdk/types/restclient.d.ts index 97dda92a..89862123 100644 --- a/packages/sdk/types/restclient.d.ts +++ b/packages/sdk/types/restclient.d.ts @@ -93,6 +93,8 @@ interface WasmError { export interface TxsResponse { readonly height: string; readonly txhash: string; + /** Falsy when transaction execution succeeded. Contains error code on error. */ + readonly code?: number; readonly raw_log: string; readonly logs?: object; readonly tx: CosmosSdkTx; From fa41bfbf059e9353a81e952c30063536896e02f3 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 18 Mar 2020 10:35:15 +0100 Subject: [PATCH 09/11] Add and test TxsResponse.codespace --- packages/sdk/src/restclient.spec.ts | 2 ++ packages/sdk/src/restclient.ts | 2 ++ packages/sdk/types/restclient.d.ts | 2 ++ 3 files changed, 6 insertions(+) diff --git a/packages/sdk/src/restclient.spec.ts b/packages/sdk/src/restclient.spec.ts index da8f37f3..a1af8fc1 100644 --- a/packages/sdk/src/restclient.spec.ts +++ b/packages/sdk/src/restclient.spec.ts @@ -425,6 +425,7 @@ describe("RestClient", () => { const result = await client.txById(successful.hash); expect(result.height).toBeGreaterThanOrEqual(1); expect(result.txhash).toEqual(successful.hash); + expect(result.codespace).toBeUndefined(); expect(result.code).toBeUndefined(); const logs = parseLogs(result.logs); expect(logs).toEqual([ @@ -460,6 +461,7 @@ describe("RestClient", () => { const result = await client.txById(unsuccessful.hash); expect(result.height).toBeGreaterThanOrEqual(1); expect(result.txhash).toEqual(unsuccessful.hash); + expect(result.codespace).toEqual("sdk"); expect(result.code).toEqual(5); expect(result.logs).toBeUndefined(); expect(result.raw_log).toContain("insufficient funds"); diff --git a/packages/sdk/src/restclient.ts b/packages/sdk/src/restclient.ts index 2951b7ff..1de4b47f 100644 --- a/packages/sdk/src/restclient.ts +++ b/packages/sdk/src/restclient.ts @@ -119,6 +119,8 @@ interface WasmError { export interface TxsResponse { readonly height: string; readonly txhash: string; + /** 🤷‍♂️ */ + readonly codespace?: string; /** Falsy when transaction execution succeeded. Contains error code on error. */ readonly code?: number; readonly raw_log: string; diff --git a/packages/sdk/types/restclient.d.ts b/packages/sdk/types/restclient.d.ts index 89862123..f082b4ba 100644 --- a/packages/sdk/types/restclient.d.ts +++ b/packages/sdk/types/restclient.d.ts @@ -93,6 +93,8 @@ interface WasmError { export interface TxsResponse { readonly height: string; readonly txhash: string; + /** 🤷‍♂️ */ + readonly codespace?: string; /** Falsy when transaction execution succeeded. Contains error code on error. */ readonly code?: number; readonly raw_log: string; From 48c754f3253f2ba62dbd00b9789833dc60e03fc5 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 18 Mar 2020 10:51:24 +0100 Subject: [PATCH 10/11] Add IndexedTx.code --- .../sdk/src/cosmwasmclient.searchtx.spec.ts | 206 ++++++++++++++---- packages/sdk/src/cosmwasmclient.ts | 3 + packages/sdk/types/cosmwasmclient.d.ts | 2 + 3 files changed, 163 insertions(+), 48 deletions(-) diff --git a/packages/sdk/src/cosmwasmclient.searchtx.spec.ts b/packages/sdk/src/cosmwasmclient.searchtx.spec.ts index 64d0edaa..f5e1d72f 100644 --- a/packages/sdk/src/cosmwasmclient.searchtx.spec.ts +++ b/packages/sdk/src/cosmwasmclient.searchtx.spec.ts @@ -2,6 +2,7 @@ import { assert, sleep } from "@iov/utils"; import { CosmWasmClient } from "./cosmwasmclient"; +import { makeSignBytes } from "./encoding"; import { Secp256k1Pen } from "./pen"; import { RestClient } from "./restclient"; import { SigningCosmWasmClient } from "./signingcosmwasmclient"; @@ -14,10 +15,26 @@ import { wasmd, wasmdEnabled, } from "./testutils.spec"; -import { Coin, CosmosSdkTx, isMsgExecuteContract, isMsgInstantiateContract, isMsgSend } from "./types"; +import { + Coin, + CosmosSdkTx, + isMsgExecuteContract, + isMsgInstantiateContract, + isMsgSend, + MsgSend, +} from "./types"; describe("CosmWasmClient.searchTx", () => { - let postedSend: + let sendSuccessful: + | { + readonly sender: string; + readonly recipient: string; + readonly hash: string; + readonly height: number; + readonly tx: CosmosSdkTx; + } + | undefined; + let sendUnsuccessful: | { readonly sender: string; readonly recipient: string; @@ -52,7 +69,7 @@ describe("CosmWasmClient.searchTx", () => { const result = await client.sendTokens(recipient, [transferAmount]); await sleep(50); // wait until tx is indexed const txDetails = await new RestClient(wasmd.endpoint).txById(result.transactionHash); - postedSend = { + sendSuccessful = { sender: faucet.address, recipient: recipient, hash: result.transactionHash, @@ -61,6 +78,64 @@ describe("CosmWasmClient.searchTx", () => { }; } + { + const memo = "Sending more than I can afford"; + const recipient = makeRandomAddress(); + const transferAmount = [ + { + denom: "ucosm", + amount: "123456700000000", + }, + ]; + 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: recipient, + amount: transferAmount, + }, + }; + const fee = { + amount: [ + { + denom: "ucosm", + amount: "2000", + }, + ], + gas: "80000", // 80k + }; + const { accountNumber, sequence } = await client.getNonce(); + const chainId = await client.getChainId(); + const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence); + const signature = await pen.sign(signBytes); + const tx: CosmosSdkTx = { + type: "cosmos-sdk/StdTx", + value: { + msg: [sendMsg], + fee: fee, + memo: memo, + signatures: [signature], + }, + }; + const transactionId = await client.getIdentifier(tx); + const heightBeforeThis = await client.getHeight(); + try { + await client.postTx(tx.value); + } catch (error) { + // postTx() throws on execution failures, which is a questionable design. Ignore for now. + // console.log(error); + } + sendUnsuccessful = { + sender: faucet.address, + recipient: recipient, + hash: transactionId, + height: heightBeforeThis + 1, + tx: tx, + }; + } + { const hashInstance = deployedErc20.instances[0]; const msg = { @@ -84,17 +159,34 @@ describe("CosmWasmClient.searchTx", () => { }); describe("with SearchByIdQuery", () => { - it("can search by ID", async () => { + it("can search successful tx by ID", async () => { pendingWithoutWasmd(); - assert(postedSend, "value must be set in beforeAll()"); + assert(sendSuccessful, "value must be set in beforeAll()"); const client = new CosmWasmClient(wasmd.endpoint); - const result = await client.searchTx({ id: postedSend.hash }); + const result = await client.searchTx({ id: sendSuccessful.hash }); expect(result.length).toEqual(1); expect(result[0]).toEqual( jasmine.objectContaining({ - height: postedSend.height, - hash: postedSend.hash, - tx: postedSend.tx, + height: sendSuccessful.height, + hash: sendSuccessful.hash, + code: 0, + tx: sendSuccessful.tx, + }), + ); + }); + + it("can search unsuccessful tx by ID", async () => { + pendingWithoutWasmd(); + assert(sendUnsuccessful, "value must be set in beforeAll()"); + const client = new CosmWasmClient(wasmd.endpoint); + const result = await client.searchTx({ id: sendUnsuccessful.hash }); + expect(result.length).toEqual(1); + expect(result[0]).toEqual( + jasmine.objectContaining({ + height: sendUnsuccessful.height, + hash: sendUnsuccessful.hash, + code: 5, + tx: sendUnsuccessful.tx, }), ); }); @@ -109,9 +201,9 @@ describe("CosmWasmClient.searchTx", () => { it("can search by ID and filter by minHeight", async () => { pendingWithoutWasmd(); - assert(postedSend); + assert(sendSuccessful); const client = new CosmWasmClient(wasmd.endpoint); - const query = { id: postedSend.hash }; + const query = { id: sendSuccessful.hash }; { const result = await client.searchTx(query, { minHeight: 0 }); @@ -119,34 +211,51 @@ describe("CosmWasmClient.searchTx", () => { } { - const result = await client.searchTx(query, { minHeight: postedSend.height - 1 }); + const result = await client.searchTx(query, { minHeight: sendSuccessful.height - 1 }); expect(result.length).toEqual(1); } { - const result = await client.searchTx(query, { minHeight: postedSend.height }); + const result = await client.searchTx(query, { minHeight: sendSuccessful.height }); expect(result.length).toEqual(1); } { - const result = await client.searchTx(query, { minHeight: postedSend.height + 1 }); + const result = await client.searchTx(query, { minHeight: sendSuccessful.height + 1 }); expect(result.length).toEqual(0); } }); }); describe("with SearchByHeightQuery", () => { - it("can search by height", async () => { + it("can search successful tx by height", async () => { pendingWithoutWasmd(); - assert(postedSend, "value must be set in beforeAll()"); + assert(sendSuccessful, "value must be set in beforeAll()"); const client = new CosmWasmClient(wasmd.endpoint); - const result = await client.searchTx({ height: postedSend.height }); + const result = await client.searchTx({ height: sendSuccessful.height }); expect(result.length).toEqual(1); expect(result[0]).toEqual( jasmine.objectContaining({ - height: postedSend.height, - hash: postedSend.hash, - tx: postedSend.tx, + height: sendSuccessful.height, + hash: sendSuccessful.hash, + code: 0, + tx: sendSuccessful.tx, + }), + ); + }); + + it("can search unsuccessful tx by height", async () => { + pendingWithoutWasmd(); + assert(sendUnsuccessful, "value must be set in beforeAll()"); + const client = new CosmWasmClient(wasmd.endpoint); + const result = await client.searchTx({ height: sendUnsuccessful.height }); + expect(result.length).toEqual(1); + expect(result[0]).toEqual( + jasmine.objectContaining({ + height: sendUnsuccessful.height, + hash: sendUnsuccessful.hash, + code: 5, + tx: sendUnsuccessful.tx, }), ); }); @@ -155,9 +264,9 @@ describe("CosmWasmClient.searchTx", () => { describe("with SearchBySentFromOrToQuery", () => { it("can search by sender", async () => { pendingWithoutWasmd(); - assert(postedSend, "value must be set in beforeAll()"); + assert(sendSuccessful, "value must be set in beforeAll()"); const client = new CosmWasmClient(wasmd.endpoint); - const results = await client.searchTx({ sentFromOrTo: postedSend.sender }); + const results = await client.searchTx({ sentFromOrTo: sendSuccessful.sender }); expect(results.length).toBeGreaterThanOrEqual(1); // Check basic structure of all results @@ -165,25 +274,25 @@ describe("CosmWasmClient.searchTx", () => { const msg = fromOneElementArray(result.tx.value.msg); assert(isMsgSend(msg), `${result.hash} (height ${result.height}) is not a bank send transaction`); expect( - msg.value.to_address === postedSend.sender || msg.value.from_address == postedSend.sender, + msg.value.to_address === sendSuccessful.sender || msg.value.from_address == sendSuccessful.sender, ).toEqual(true); } // Check details of most recent result expect(results[results.length - 1]).toEqual( jasmine.objectContaining({ - height: postedSend.height, - hash: postedSend.hash, - tx: postedSend.tx, + height: sendSuccessful.height, + hash: sendSuccessful.hash, + tx: sendSuccessful.tx, }), ); }); it("can search by recipient", async () => { pendingWithoutWasmd(); - assert(postedSend, "value must be set in beforeAll()"); + assert(sendSuccessful, "value must be set in beforeAll()"); const client = new CosmWasmClient(wasmd.endpoint); - const results = await client.searchTx({ sentFromOrTo: postedSend.recipient }); + const results = await client.searchTx({ sentFromOrTo: sendSuccessful.recipient }); expect(results.length).toBeGreaterThanOrEqual(1); // Check basic structure of all results @@ -191,25 +300,26 @@ describe("CosmWasmClient.searchTx", () => { const msg = fromOneElementArray(result.tx.value.msg); assert(isMsgSend(msg), `${result.hash} (height ${result.height}) is not a bank send transaction`); expect( - msg.value.to_address === postedSend.recipient || msg.value.from_address == postedSend.recipient, + msg.value.to_address === sendSuccessful.recipient || + msg.value.from_address == sendSuccessful.recipient, ).toEqual(true); } // Check details of most recent result expect(results[results.length - 1]).toEqual( jasmine.objectContaining({ - height: postedSend.height, - hash: postedSend.hash, - tx: postedSend.tx, + height: sendSuccessful.height, + hash: sendSuccessful.hash, + tx: sendSuccessful.tx, }), ); }); it("can search by recipient and filter by minHeight", async () => { pendingWithoutWasmd(); - assert(postedSend); + assert(sendSuccessful); const client = new CosmWasmClient(wasmd.endpoint); - const query = { sentFromOrTo: postedSend.recipient }; + const query = { sentFromOrTo: sendSuccessful.recipient }; { const result = await client.searchTx(query, { minHeight: 0 }); @@ -217,26 +327,26 @@ describe("CosmWasmClient.searchTx", () => { } { - const result = await client.searchTx(query, { minHeight: postedSend.height - 1 }); + const result = await client.searchTx(query, { minHeight: sendSuccessful.height - 1 }); expect(result.length).toEqual(1); } { - const result = await client.searchTx(query, { minHeight: postedSend.height }); + const result = await client.searchTx(query, { minHeight: sendSuccessful.height }); expect(result.length).toEqual(1); } { - const result = await client.searchTx(query, { minHeight: postedSend.height + 1 }); + const result = await client.searchTx(query, { minHeight: sendSuccessful.height + 1 }); expect(result.length).toEqual(0); } }); it("can search by recipient and filter by maxHeight", async () => { pendingWithoutWasmd(); - assert(postedSend); + assert(sendSuccessful); const client = new CosmWasmClient(wasmd.endpoint); - const query = { sentFromOrTo: postedSend.recipient }; + const query = { sentFromOrTo: sendSuccessful.recipient }; { const result = await client.searchTx(query, { maxHeight: 9999999999999 }); @@ -244,17 +354,17 @@ describe("CosmWasmClient.searchTx", () => { } { - const result = await client.searchTx(query, { maxHeight: postedSend.height + 1 }); + const result = await client.searchTx(query, { maxHeight: sendSuccessful.height + 1 }); expect(result.length).toEqual(1); } { - const result = await client.searchTx(query, { maxHeight: postedSend.height }); + const result = await client.searchTx(query, { maxHeight: sendSuccessful.height }); expect(result.length).toEqual(1); } { - const result = await client.searchTx(query, { maxHeight: postedSend.height - 1 }); + const result = await client.searchTx(query, { maxHeight: sendSuccessful.height - 1 }); expect(result.length).toEqual(0); } }); @@ -263,10 +373,10 @@ describe("CosmWasmClient.searchTx", () => { describe("with SearchByTagsQuery", () => { it("can search by transfer.recipient", async () => { pendingWithoutWasmd(); - assert(postedSend, "value must be set in beforeAll()"); + assert(sendSuccessful, "value must be set in beforeAll()"); const client = new CosmWasmClient(wasmd.endpoint); const results = await client.searchTx({ - tags: [{ key: "transfer.recipient", value: postedSend.recipient }], + tags: [{ key: "transfer.recipient", value: sendSuccessful.recipient }], }); expect(results.length).toBeGreaterThanOrEqual(1); @@ -274,15 +384,15 @@ describe("CosmWasmClient.searchTx", () => { for (const result of results) { const msg = fromOneElementArray(result.tx.value.msg); assert(isMsgSend(msg), `${result.hash} (height ${result.height}) is not a bank send transaction`); - expect(msg.value.to_address).toEqual(postedSend.recipient); + expect(msg.value.to_address).toEqual(sendSuccessful.recipient); } // Check details of most recent result expect(results[results.length - 1]).toEqual( jasmine.objectContaining({ - height: postedSend.height, - hash: postedSend.hash, - tx: postedSend.tx, + height: sendSuccessful.height, + hash: sendSuccessful.hash, + tx: sendSuccessful.tx, }), ); }); diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index 2d4c09be..9fdcfec2 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -107,6 +107,8 @@ export interface IndexedTx { readonly height: number; /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ readonly hash: string; + /** Transaction execution error code. 0 on success. */ + readonly code: number; readonly rawLog: string; readonly logs: readonly Log[]; readonly tx: CosmosSdkTx; @@ -422,6 +424,7 @@ export class CosmWasmClient { (restItem): IndexedTx => ({ height: parseInt(restItem.height, 10), hash: restItem.txhash, + code: restItem.code || 0, rawLog: restItem.raw_log, logs: parseLogs(restItem.logs || []), tx: restItem.tx, diff --git a/packages/sdk/types/cosmwasmclient.d.ts b/packages/sdk/types/cosmwasmclient.d.ts index 63a31464..05cd33cc 100644 --- a/packages/sdk/types/cosmwasmclient.d.ts +++ b/packages/sdk/types/cosmwasmclient.d.ts @@ -76,6 +76,8 @@ export interface IndexedTx { readonly height: number; /** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */ readonly hash: string; + /** Transaction execution error code. 0 on success. */ + readonly code: number; readonly rawLog: string; readonly logs: readonly Log[]; readonly tx: CosmosSdkTx; From 7b62f40ef542ea6b7e2890d4a476592b6fb640a2 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 18 Mar 2020 11:09:02 +0100 Subject: [PATCH 11/11] Add code property to IndexedTx in bcp package --- packages/bcp/src/decode.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/bcp/src/decode.spec.ts b/packages/bcp/src/decode.spec.ts index 7ec8c142..5ff2d3ff 100644 --- a/packages/bcp/src/decode.spec.ts +++ b/packages/bcp/src/decode.spec.ts @@ -284,6 +284,7 @@ describe("decode", () => { const txsResponse: IndexedTx = { height: 2823, hash: testdata.txId, + code: 0, rawLog: '[{"msg_index":0,"success":true,"log":""}]', logs: [], tx: cosmoshub.tx, @@ -314,6 +315,7 @@ describe("decode", () => { const txsResponse: IndexedTx = { height: 2823, hash: testdata.txId, + code: 0, rawLog: '[{"msg_index":0,"success":true,"log":""}]', logs: [], tx: cosmoshub.tx,