diff --git a/packages/bcp/src/cosmwasmconnection.ts b/packages/bcp/src/cosmwasmconnection.ts index 51b71441..f26fa724 100644 --- a/packages/bcp/src/cosmwasmconnection.ts +++ b/packages/bcp/src/cosmwasmconnection.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { CosmWasmClient, findSequenceForSignedTx, TxsResponse, types } from "@cosmwasm/sdk"; +import { CosmWasmClient, findSequenceForSignedTx, SearchTxFilter, TxsResponse, types } from "@cosmwasm/sdk"; import { Account, AccountQuery, @@ -300,8 +300,8 @@ export class CosmWasmConnection implements BlockchainConnection { public async searchTx({ height, id, - maxHeight: maxHeightOptional, - minHeight: minHeightOptional, + maxHeight, + minHeight, sentFromOrTo, signedBy, tags, @@ -316,32 +316,20 @@ export class CosmWasmConnection implements BlockchainConnection { ); } - const minHeight = minHeightOptional || 0; - const maxHeight = maxHeightOptional || Number.MAX_SAFE_INTEGER; - - if (maxHeight < minHeight) return []; // optional optimization + const filter: SearchTxFilter = { minHeight: minHeight, maxHeight: maxHeight }; let txs: readonly TxsResponse[]; if (id) { - txs = await this.cosmWasmClient.searchTx({ id: id }); + txs = await this.cosmWasmClient.searchTx({ id: id }, filter); } else if (height) { - if (height < minHeight) return []; // optional optimization - if (height > maxHeight) return []; // optional optimization - txs = await this.cosmWasmClient.searchTx({ height: height }); + txs = await this.cosmWasmClient.searchTx({ height: height }, filter); } else if (sentFromOrTo) { - // TODO: pass minHeight/maxHeight to server once we have - // https://github.com/cosmwasm/wasmd/issues/73 - txs = await this.cosmWasmClient.searchTx({ sentFromOrTo: sentFromOrTo }); + txs = await this.cosmWasmClient.searchTx({ sentFromOrTo: sentFromOrTo }, filter); } else { throw new Error("Unsupported query"); } - const filtered = txs.filter(tx => { - const txHeight = parseInt(tx.height, 10); - return txHeight >= minHeight && txHeight <= maxHeight; - }); - - return filtered.map(tx => this.parseAndPopulateTxResponseUnsigned(tx)); + return txs.map(tx => this.parseAndPopulateTxResponseUnsigned(tx)); } public listenTx( diff --git a/packages/bcp/types/cosmwasmconnection.d.ts b/packages/bcp/types/cosmwasmconnection.d.ts index b1161cf6..b4059376 100644 --- a/packages/bcp/types/cosmwasmconnection.d.ts +++ b/packages/bcp/types/cosmwasmconnection.d.ts @@ -73,8 +73,8 @@ export declare class CosmWasmConnection implements BlockchainConnection { searchTx({ height, id, - maxHeight: maxHeightOptional, - minHeight: minHeightOptional, + maxHeight, + minHeight, sentFromOrTo, signedBy, tags, diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts index 0af068e3..34e0143f 100644 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ b/packages/sdk/src/cosmwasmclient.spec.ts @@ -309,6 +309,87 @@ describe("CosmWasmClient", () => { }), ); }); + + it("can search by ID and filter by minHeight", async () => { + pendingWithoutWasmd(); + assert(posted); + const client = new CosmWasmClient(httpUrl); + const query = { id: posted.hash }; + + { + const result = await client.searchTx(query, { minHeight: 0 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: posted.height - 1 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: posted.height }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: posted.height + 1 }); + expect(result.length).toEqual(0); + } + }); + + it("can search by recipient and filter by minHeight", async () => { + pendingWithoutWasmd(); + assert(posted); + const client = new CosmWasmClient(httpUrl); + const query = { sentFromOrTo: posted.recipient }; + + { + const result = await client.searchTx(query, { minHeight: 0 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: posted.height - 1 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: posted.height }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { minHeight: posted.height + 1 }); + expect(result.length).toEqual(0); + } + }); + + it("can search by recipient and filter by maxHeight", async () => { + pendingWithoutWasmd(); + assert(posted); + const client = new CosmWasmClient(httpUrl); + const query = { sentFromOrTo: posted.recipient }; + + { + const result = await client.searchTx(query, { maxHeight: 9999999999999 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { maxHeight: posted.height + 1 }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { maxHeight: posted.height }); + expect(result.length).toEqual(1); + } + + { + const result = await client.searchTx(query, { maxHeight: posted.height - 1 }); + expect(result.length).toEqual(0); + } + }); }); describe("queryContractRaw", () => { diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index 5542eaf8..10a5100d 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -43,6 +43,11 @@ function isSearchBySentFromOrToQuery(query: SearchTxQuery): query is SearchBySen return (query as SearchBySentFromOrToQuery).sentFromOrTo !== undefined; } +export interface SearchTxFilter { + readonly minHeight?: number; + readonly maxHeight?: number; +} + export class CosmWasmClient { protected readonly restClient: RestClient; @@ -104,26 +109,51 @@ export class CosmWasmClient { } } - public async searchTx(query: SearchTxQuery): Promise { - // TODO: we need proper pagination support - function limited(originalQuery: string): string { - return `${originalQuery}&limit=75`; + public async searchTx(query: SearchTxQuery, filter: SearchTxFilter = {}): Promise { + const minHeight = filter.minHeight || 0; + const maxHeight = filter.maxHeight || Number.MAX_SAFE_INTEGER; + + if (maxHeight < minHeight) return []; // optional optimization + + function withFiltersAndLimits(originalQuery: string): string { + const components = [ + "limit=75", // TODO: we need proper pagination support + `tx.minheight=${minHeight}`, + `tx.maxheight=${maxHeight}`, + ]; + return `${originalQuery}&${components.join("&")}`; } + let txs: readonly TxsResponse[]; if (isSearchByIdQuery(query)) { - return (await this.restClient.txsQuery(`tx.hash=${query.id}`)).txs; + txs = (await this.restClient.txsQuery(`tx.hash=${query.id}`)).txs; } else if (isSearchByHeightQuery(query)) { - return (await this.restClient.txsQuery(`tx.height=${query.height}`)).txs; + // optional optimization to avoid network request + if (query.height < minHeight || query.height > maxHeight) { + txs = []; + } else { + txs = (await this.restClient.txsQuery(`tx.height=${query.height}`)).txs; + } } else if (isSearchBySentFromOrToQuery(query)) { // We cannot get both in one request (see https://github.com/cosmos/gaia/issues/75) - const sent = (await this.restClient.txsQuery(limited(`message.sender=${query.sentFromOrTo}`))).txs; - const received = (await this.restClient.txsQuery(limited(`transfer.recipient=${query.sentFromOrTo}`))) - .txs; + const sentQuery = withFiltersAndLimits(`message.sender=${query.sentFromOrTo}`); + const receivedQuery = withFiltersAndLimits(`transfer.recipient=${query.sentFromOrTo}`); + const sent = (await this.restClient.txsQuery(sentQuery)).txs; + const received = (await this.restClient.txsQuery(receivedQuery)).txs; + const sentHashes = sent.map(t => t.txhash); - return [...sent, ...received.filter(t => !sentHashes.includes(t.txhash))]; + txs = [...sent, ...received.filter(t => !sentHashes.includes(t.txhash))]; } else { throw new Error("Unknown query type"); } + + // backend sometimes messes up with min/max height filtering + const filtered = txs.filter(tx => { + const txHeight = parseInt(tx.height, 10); + return txHeight >= minHeight && txHeight <= maxHeight; + }); + + return filtered; } public async postTx(tx: StdTx): Promise { diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index e24b852b..4611ed78 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -14,6 +14,7 @@ export { SearchByIdQuery, SearchBySentFromOrToQuery, SearchTxQuery, + SearchTxFilter, } from "./cosmwasmclient"; export { makeCosmoshubPath, Pen, PrehashType, Secp256k1Pen } from "./pen"; export { diff --git a/packages/sdk/types/cosmwasmclient.d.ts b/packages/sdk/types/cosmwasmclient.d.ts index d4bc36ae..3fd88e88 100644 --- a/packages/sdk/types/cosmwasmclient.d.ts +++ b/packages/sdk/types/cosmwasmclient.d.ts @@ -21,6 +21,10 @@ export interface SearchBySentFromOrToQuery { readonly sentFromOrTo: string; } export declare type SearchTxQuery = SearchByIdQuery | SearchByHeightQuery | SearchBySentFromOrToQuery; +export interface SearchTxFilter { + readonly minHeight?: number; + readonly maxHeight?: number; +} export declare class CosmWasmClient { protected readonly restClient: RestClient; constructor(url: string, broadcastMode?: BroadcastMode); @@ -44,7 +48,7 @@ export declare class CosmWasmClient { * @param height The height of the block. If undefined, the latest height is used. */ getBlock(height?: number): Promise; - searchTx(query: SearchTxQuery): Promise; + searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise; postTx(tx: StdTx): Promise; /** * Returns the data at the key if present (raw contract dependent storage data) diff --git a/packages/sdk/types/index.d.ts b/packages/sdk/types/index.d.ts index ddc5eb94..b74b8428 100644 --- a/packages/sdk/types/index.d.ts +++ b/packages/sdk/types/index.d.ts @@ -13,6 +13,7 @@ export { SearchByIdQuery, SearchBySentFromOrToQuery, SearchTxQuery, + SearchTxFilter, } from "./cosmwasmclient"; export { makeCosmoshubPath, Pen, PrehashType, Secp256k1Pen } from "./pen"; export {