Implement min/max height filters in CosmWasmClient

This commit is contained in:
Simon Warta 2020-02-21 13:33:51 +01:00
parent 798854b002
commit dfe1cccadb
7 changed files with 138 additions and 33 deletions

View File

@ -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(

View File

@ -73,8 +73,8 @@ export declare class CosmWasmConnection implements BlockchainConnection {
searchTx({
height,
id,
maxHeight: maxHeightOptional,
minHeight: minHeightOptional,
maxHeight,
minHeight,
sentFromOrTo,
signedBy,
tags,

View File

@ -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", () => {

View File

@ -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<readonly TxsResponse[]> {
// TODO: we need proper pagination support
function limited(originalQuery: string): string {
return `${originalQuery}&limit=75`;
public async searchTx(query: SearchTxQuery, filter: SearchTxFilter = {}): Promise<readonly TxsResponse[]> {
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<PostTxResult> {

View File

@ -14,6 +14,7 @@ export {
SearchByIdQuery,
SearchBySentFromOrToQuery,
SearchTxQuery,
SearchTxFilter,
} from "./cosmwasmclient";
export { makeCosmoshubPath, Pen, PrehashType, Secp256k1Pen } from "./pen";
export {

View File

@ -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<BlockResponse>;
searchTx(query: SearchTxQuery): Promise<readonly TxsResponse[]>;
searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise<readonly TxsResponse[]>;
postTx(tx: StdTx): Promise<PostTxResult>;
/**
* Returns the data at the key if present (raw contract dependent storage data)

View File

@ -13,6 +13,7 @@ export {
SearchByIdQuery,
SearchBySentFromOrToQuery,
SearchTxQuery,
SearchTxFilter,
} from "./cosmwasmclient";
export { makeCosmoshubPath, Pen, PrehashType, Secp256k1Pen } from "./pen";
export {