Implement and test CosmWasmClient.searchTx

This commit is contained in:
Simon Warta 2020-03-02 10:28:54 +01:00
parent b821a969a0
commit 69e2e6b43a
4 changed files with 249 additions and 76 deletions

View File

@ -20,7 +20,15 @@ import {
tendermintIdMatcher,
wasmdEnabled,
} from "./testutils.spec";
import { CosmosSdkTx, isMsgSend, MsgSend, StdFee } from "./types";
import {
Coin,
CosmosSdkTx,
isMsgExecuteContract,
isMsgInstantiateContract,
isMsgSend,
MsgSend,
StdFee,
} from "./types";
const { fromAscii, fromHex, fromUtf8, toAscii, toBase64 } = Encoding;
@ -213,7 +221,7 @@ describe("CosmWasmClient", () => {
});
describe("searchTx", () => {
let posted:
let postedSend:
| {
readonly sender: string;
readonly recipient: string;
@ -222,44 +230,72 @@ describe("CosmWasmClient", () => {
readonly tx: CosmosSdkTx;
}
| undefined;
let postedExecute:
| {
readonly sender: string;
readonly contract: string;
readonly hash: string;
readonly height: number;
readonly tx: CosmosSdkTx;
}
| undefined;
beforeAll(async () => {
if (wasmdEnabled()) {
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
const client = new SigningCosmWasmClient(httpUrl, faucet.address, signBytes => pen.sign(signBytes));
const recipient = makeRandomAddress();
const transferAmount = [
{
{
const recipient = makeRandomAddress();
const transferAmount: Coin = {
denom: "ucosm",
amount: "1234567",
},
];
const result = await client.sendTokens(recipient, transferAmount);
};
const result = await client.sendTokens(recipient, [transferAmount]);
await sleep(50); // wait until tx is indexed
const txDetails = await new RestClient(httpUrl).txsById(result.transactionHash);
postedSend = {
sender: faucet.address,
recipient: recipient,
hash: result.transactionHash,
height: Number.parseInt(txDetails.height, 10),
tx: txDetails.tx,
};
}
await sleep(50); // wait until tx is indexed
const txDetails = await new RestClient(httpUrl).txsById(result.transactionHash);
posted = {
sender: faucet.address,
recipient: recipient,
hash: result.transactionHash,
height: Number.parseInt(txDetails.height, 10),
tx: txDetails.tx,
};
{
const hashInstance = deployedErc20.instances[0];
const msg = {
approve: {
spender: makeRandomAddress(),
amount: "12",
},
};
const result = await client.execute(hashInstance, msg);
await sleep(50); // wait until tx is indexed
const txDetails = await new RestClient(httpUrl).txsById(result.transactionHash);
postedExecute = {
sender: faucet.address,
contract: hashInstance,
hash: result.transactionHash,
height: Number.parseInt(txDetails.height, 10),
tx: txDetails.tx,
};
}
}
});
it("can search by ID", async () => {
pendingWithoutWasmd();
assert(posted, "value must be set in beforeAll()");
assert(postedSend, "value must be set in beforeAll()");
const client = new CosmWasmClient(httpUrl);
const result = await client.searchTx({ id: posted.hash });
const result = await client.searchTx({ id: postedSend.hash });
expect(result.length).toEqual(1);
expect(result[0]).toEqual(
jasmine.objectContaining({
height: posted.height.toString(),
txhash: posted.hash,
tx: posted.tx,
height: postedSend.height.toString(),
txhash: postedSend.hash,
tx: postedSend.tx,
}),
);
});
@ -274,50 +310,24 @@ describe("CosmWasmClient", () => {
it("can search by height", async () => {
pendingWithoutWasmd();
assert(posted, "value must be set in beforeAll()");
assert(postedSend, "value must be set in beforeAll()");
const client = new CosmWasmClient(httpUrl);
const result = await client.searchTx({ height: posted.height });
const result = await client.searchTx({ height: postedSend.height });
expect(result.length).toEqual(1);
expect(result[0]).toEqual(
jasmine.objectContaining({
height: posted.height.toString(),
txhash: posted.hash,
tx: posted.tx,
height: postedSend.height.toString(),
txhash: postedSend.hash,
tx: postedSend.tx,
}),
);
});
it("can search by sender", async () => {
pendingWithoutWasmd();
assert(posted, "value must be set in beforeAll()");
assert(postedSend, "value must be set in beforeAll()");
const client = new CosmWasmClient(httpUrl);
const results = await client.searchTx({ sentFromOrTo: posted.sender });
expect(results.length).toBeGreaterThanOrEqual(1);
// Check basic structure of all results
for (const result of results) {
const msg = fromOneElementArray(result.tx.value.msg);
assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`);
expect(msg.value.to_address === posted.sender || msg.value.from_address == posted.sender).toEqual(
true,
);
}
// Check details of most recent result
expect(results[results.length - 1]).toEqual(
jasmine.objectContaining({
height: posted.height.toString(),
txhash: posted.hash,
tx: posted.tx,
}),
);
});
it("can search by recipient", async () => {
pendingWithoutWasmd();
assert(posted, "value must be set in beforeAll()");
const client = new CosmWasmClient(httpUrl);
const results = await client.searchTx({ sentFromOrTo: posted.recipient });
const results = await client.searchTx({ sentFromOrTo: postedSend.sender });
expect(results.length).toBeGreaterThanOrEqual(1);
// Check basic structure of all results
@ -325,25 +335,51 @@ describe("CosmWasmClient", () => {
const msg = fromOneElementArray(result.tx.value.msg);
assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`);
expect(
msg.value.to_address === posted.recipient || msg.value.from_address == posted.recipient,
msg.value.to_address === postedSend.sender || msg.value.from_address == postedSend.sender,
).toEqual(true);
}
// Check details of most recent result
expect(results[results.length - 1]).toEqual(
jasmine.objectContaining({
height: posted.height.toString(),
txhash: posted.hash,
tx: posted.tx,
height: postedSend.height.toString(),
txhash: postedSend.hash,
tx: postedSend.tx,
}),
);
});
it("can search by recipient", async () => {
pendingWithoutWasmd();
assert(postedSend, "value must be set in beforeAll()");
const client = new CosmWasmClient(httpUrl);
const results = await client.searchTx({ sentFromOrTo: postedSend.recipient });
expect(results.length).toBeGreaterThanOrEqual(1);
// Check basic structure of all results
for (const result of results) {
const msg = fromOneElementArray(result.tx.value.msg);
assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`);
expect(
msg.value.to_address === postedSend.recipient || msg.value.from_address == postedSend.recipient,
).toEqual(true);
}
// Check details of most recent result
expect(results[results.length - 1]).toEqual(
jasmine.objectContaining({
height: postedSend.height.toString(),
txhash: postedSend.hash,
tx: postedSend.tx,
}),
);
});
it("can search by ID and filter by minHeight", async () => {
pendingWithoutWasmd();
assert(posted);
assert(postedSend);
const client = new CosmWasmClient(httpUrl);
const query = { id: posted.hash };
const query = { id: postedSend.hash };
{
const result = await client.searchTx(query, { minHeight: 0 });
@ -351,26 +387,26 @@ describe("CosmWasmClient", () => {
}
{
const result = await client.searchTx(query, { minHeight: posted.height - 1 });
const result = await client.searchTx(query, { minHeight: postedSend.height - 1 });
expect(result.length).toEqual(1);
}
{
const result = await client.searchTx(query, { minHeight: posted.height });
const result = await client.searchTx(query, { minHeight: postedSend.height });
expect(result.length).toEqual(1);
}
{
const result = await client.searchTx(query, { minHeight: posted.height + 1 });
const result = await client.searchTx(query, { minHeight: postedSend.height + 1 });
expect(result.length).toEqual(0);
}
});
it("can search by recipient and filter by minHeight", async () => {
pendingWithoutWasmd();
assert(posted);
assert(postedSend);
const client = new CosmWasmClient(httpUrl);
const query = { sentFromOrTo: posted.recipient };
const query = { sentFromOrTo: postedSend.recipient };
{
const result = await client.searchTx(query, { minHeight: 0 });
@ -378,26 +414,26 @@ describe("CosmWasmClient", () => {
}
{
const result = await client.searchTx(query, { minHeight: posted.height - 1 });
const result = await client.searchTx(query, { minHeight: postedSend.height - 1 });
expect(result.length).toEqual(1);
}
{
const result = await client.searchTx(query, { minHeight: posted.height });
const result = await client.searchTx(query, { minHeight: postedSend.height });
expect(result.length).toEqual(1);
}
{
const result = await client.searchTx(query, { minHeight: posted.height + 1 });
const result = await client.searchTx(query, { minHeight: postedSend.height + 1 });
expect(result.length).toEqual(0);
}
});
it("can search by recipient and filter by maxHeight", async () => {
pendingWithoutWasmd();
assert(posted);
assert(postedSend);
const client = new CosmWasmClient(httpUrl);
const query = { sentFromOrTo: posted.recipient };
const query = { sentFromOrTo: postedSend.recipient };
{
const result = await client.searchTx(query, { maxHeight: 9999999999999 });
@ -405,20 +441,119 @@ describe("CosmWasmClient", () => {
}
{
const result = await client.searchTx(query, { maxHeight: posted.height + 1 });
const result = await client.searchTx(query, { maxHeight: postedSend.height + 1 });
expect(result.length).toEqual(1);
}
{
const result = await client.searchTx(query, { maxHeight: posted.height });
const result = await client.searchTx(query, { maxHeight: postedSend.height });
expect(result.length).toEqual(1);
}
{
const result = await client.searchTx(query, { maxHeight: posted.height - 1 });
const result = await client.searchTx(query, { maxHeight: postedSend.height - 1 });
expect(result.length).toEqual(0);
}
});
describe("with SearchByTagsQuery", () => {
it("can search by transfer.recipient", async () => {
pendingWithoutWasmd();
assert(postedSend, "value must be set in beforeAll()");
const client = new CosmWasmClient(httpUrl);
const results = await client.searchTx({
tags: [{ key: "transfer.recipient", value: postedSend.recipient }],
});
expect(results.length).toBeGreaterThanOrEqual(1);
// Check basic structure of all results
for (const result of results) {
const msg = fromOneElementArray(result.tx.value.msg);
assert(isMsgSend(msg), `${result.txhash} (height ${result.height}) is not a bank send transaction`);
expect(msg.value.to_address).toEqual(postedSend.recipient);
}
// Check details of most recent result
expect(results[results.length - 1]).toEqual(
jasmine.objectContaining({
height: postedSend.height.toString(),
txhash: postedSend.hash,
tx: postedSend.tx,
}),
);
});
it("can search by message.contract_address", async () => {
pendingWithoutWasmd();
assert(postedExecute, "value must be set in beforeAll()");
const client = new CosmWasmClient(httpUrl);
const results = await client.searchTx({
tags: [{ key: "message.contract_address", value: postedExecute.contract }],
});
expect(results.length).toBeGreaterThanOrEqual(1);
// Check basic structure of all results
for (const result of results) {
const msg = fromOneElementArray(result.tx.value.msg);
assert(
isMsgExecuteContract(msg) || isMsgInstantiateContract(msg),
`${result.txhash} (at ${result.height}) not an execute or instantiate msg`,
);
}
// Check that the first result is the instantiation
const first = fromOneElementArray(results[0].tx.value.msg);
assert(isMsgInstantiateContract(first), "First contract search result must be an instantiation");
expect(first).toEqual({
type: "wasm/instantiate",
value: {
sender: faucet.address,
code_id: deployedErc20.codeId.toString(),
label: "HASH",
init_msg: jasmine.objectContaining({ symbol: "HASH" }),
init_funds: [],
},
});
// Check details of most recent result
expect(results[results.length - 1]).toEqual(
jasmine.objectContaining({
height: postedExecute.height.toString(),
txhash: postedExecute.hash,
tx: postedExecute.tx,
}),
);
});
it("can search by message.contract_address + message.action", async () => {
pendingWithoutWasmd();
assert(postedExecute, "value must be set in beforeAll()");
const client = new CosmWasmClient(httpUrl);
const results = await client.searchTx({
tags: [
{ key: "message.contract_address", value: postedExecute.contract },
{ key: "message.action", value: "execute" },
],
});
expect(results.length).toBeGreaterThanOrEqual(1);
// Check basic structure of all results
for (const result of results) {
const msg = fromOneElementArray(result.tx.value.msg);
assert(isMsgExecuteContract(msg), `${result.txhash} (at ${result.height}) not an execute msg`);
expect(msg.value.contract).toEqual(postedExecute.contract);
}
// Check details of most recent result
expect(results[results.length - 1]).toEqual(
jasmine.objectContaining({
height: postedExecute.height.toString(),
txhash: postedExecute.hash,
tx: postedExecute.tx,
}),
);
});
});
});
describe("getCodes", () => {

View File

@ -29,7 +29,19 @@ export interface SearchBySentFromOrToQuery {
readonly sentFromOrTo: string;
}
export type SearchTxQuery = SearchByIdQuery | SearchByHeightQuery | SearchBySentFromOrToQuery;
/**
* This query type allows you to pass arbitrary key/value pairs to the backend. It is
* more powerful and slightly lower level than the other search options.
*/
export interface SearchByTagsQuery {
readonly tags: readonly { readonly key: string; readonly value: string }[];
}
export type SearchTxQuery =
| SearchByIdQuery
| SearchByHeightQuery
| SearchBySentFromOrToQuery
| SearchByTagsQuery;
function isSearchByIdQuery(query: SearchTxQuery): query is SearchByIdQuery {
return (query as SearchByIdQuery).id !== undefined;
@ -43,6 +55,10 @@ function isSearchBySentFromOrToQuery(query: SearchTxQuery): query is SearchBySen
return (query as SearchBySentFromOrToQuery).sentFromOrTo !== undefined;
}
function isSearchByTagsQuery(query: SearchTxQuery): query is SearchByTagsQuery {
return (query as SearchByTagsQuery).tags !== undefined;
}
export interface SearchTxFilter {
readonly minHeight?: number;
readonly maxHeight?: number;
@ -166,6 +182,9 @@ export class CosmWasmClient {
const sentHashes = sent.map(t => t.txhash);
txs = [...sent, ...received.filter(t => !sentHashes.includes(t.txhash))];
} else if (isSearchByTagsQuery(query)) {
const rawQuery = withFilters(query.tags.map(t => `${t.key}=${t.value}`).join("&"));
txs = await this.txsQuery(rawQuery);
} else {
throw new Error("Unknown query type");
}

View File

@ -69,6 +69,11 @@ export const deployedErc20 = {
source: "https://crates.io/api/v1/crates/cw-erc20/0.2.0/download",
builder: "confio/cosmwasm-opt:0.7.0",
checksum: "aff8c8873d79d2153a8b9066a0683fec3c903669267eb806ffa831dcd4b3daae",
instances: [
"cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5", // HASH
"cosmos1hqrdl6wstt8qzshwc6mrumpjk9338k0lr4dqxd", // ISA
"cosmos18r5szma8hm93pvx6lwpjwyxruw27e0k5uw835c", // JADE
],
};
export function wasmdEnabled(): boolean {

View File

@ -20,7 +20,21 @@ export interface SearchByHeightQuery {
export interface SearchBySentFromOrToQuery {
readonly sentFromOrTo: string;
}
export declare type SearchTxQuery = SearchByIdQuery | SearchByHeightQuery | SearchBySentFromOrToQuery;
/**
* This query type allows you to pass arbitrary key/value pairs to the backend. It is
* more powerful and slightly lower level than the other search options.
*/
export interface SearchByTagsQuery {
readonly tags: readonly {
readonly key: string;
readonly value: string;
}[];
}
export declare type SearchTxQuery =
| SearchByIdQuery
| SearchByHeightQuery
| SearchBySentFromOrToQuery
| SearchByTagsQuery;
export interface SearchTxFilter {
readonly minHeight?: number;
readonly maxHeight?: number;