From 5bf3a191b3b0faa7a8f888eaa98756a0e79129be Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 2 Mar 2020 13:46:35 +0100 Subject: [PATCH] Create higher level IndexedTx --- packages/bcp/src/cosmwasmconnection.ts | 28 ++++++------- packages/bcp/src/decode.spec.ts | 20 +++++---- packages/bcp/src/decode.ts | 24 +++++------ packages/bcp/types/decode.d.ts | 6 +-- .../sdk/src/cosmwasmclient.searchtx.spec.ts | 38 ++++++++--------- packages/sdk/src/cosmwasmclient.ts | 42 ++++++++++++++----- packages/sdk/src/index.ts | 1 + packages/sdk/types/cosmwasmclient.d.ts | 17 +++++++- packages/sdk/types/index.d.ts | 1 + 9 files changed, 106 insertions(+), 71 deletions(-) diff --git a/packages/bcp/src/cosmwasmconnection.ts b/packages/bcp/src/cosmwasmconnection.ts index 648ea7a0..54d039b2 100644 --- a/packages/bcp/src/cosmwasmconnection.ts +++ b/packages/bcp/src/cosmwasmconnection.ts @@ -1,5 +1,4 @@ -/* eslint-disable @typescript-eslint/camelcase */ -import { CosmWasmClient, findSequenceForSignedTx, SearchTxFilter, TxsResponse, types } from "@cosmwasm/sdk"; +import { CosmWasmClient, findSequenceForSignedTx, IndexedTx, SearchTxFilter, types } from "@cosmwasm/sdk"; import { Account, AccountQuery, @@ -71,11 +70,11 @@ function deduplicate(input: ReadonlyArray, comparator: (a: T, b: T) => num } /** Compares transaxtion by height. If the height is equal, compare by hash to ensure deterministic order */ -function compareByHeightAndHash(a: TxsResponse, b: TxsResponse): number { +function compareByHeightAndHash(a: IndexedTx, b: IndexedTx): number { if (a.height === b.height) { - return a.txhash.localeCompare(b.txhash); + return a.hash.localeCompare(b.hash); } else { - return parseInt(a.height, 10) - parseInt(b.height, 10); + return a.height - b.height; } } @@ -248,9 +247,10 @@ export class CosmWasmConnection implements BlockchainConnection { } public async getBlockHeader(height: number): Promise { - const { block_id, block } = await this.cosmWasmClient.getBlock(height); + // eslint-disable-next-line @typescript-eslint/camelcase + const { block_id: blockId, block } = await this.cosmWasmClient.getBlock(height); return { - id: block_id.hash as BlockId, + id: blockId.hash as BlockId, height: parseInt(block.header.height, 10), time: new ReadonlyDate(block.header.time), transactionCount: block.data.txs?.length || 0, @@ -337,13 +337,13 @@ export class CosmWasmConnection implements BlockchainConnection { const filter: SearchTxFilter = { minHeight: minHeight, maxHeight: maxHeight }; - let txs: readonly TxsResponse[]; + let txs: readonly IndexedTx[]; if (id) { txs = await this.cosmWasmClient.searchTx({ id: id }, filter); } else if (height) { txs = await this.cosmWasmClient.searchTx({ height: height }, filter); } else if (sentFromOrTo) { - const pendingRequests = new Array>(); + const pendingRequests = new Array>(); pendingRequests.push(this.cosmWasmClient.searchTx({ sentFromOrTo: sentFromOrTo }, filter)); for (const contract of this.erc20Tokens.map(token => token.contractAddress)) { const searchBySender = [ @@ -371,7 +371,7 @@ export class CosmWasmConnection implements BlockchainConnection { } const responses = await Promise.all(pendingRequests); const allResults = responses.reduce((accumulator, results) => accumulator.concat(results), []); - txs = deduplicate(allResults, (a, b) => a.txhash.localeCompare(b.txhash)).sort(compareByHeightAndHash); + txs = deduplicate(allResults, (a, b) => a.hash.localeCompare(b.hash)).sort(compareByHeightAndHash); } else { throw new Error("Unsupported query"); } @@ -460,11 +460,11 @@ export class CosmWasmConnection implements BlockchainConnection { } private parseAndPopulateTxResponseUnsigned( - response: TxsResponse, + response: IndexedTx, ): ConfirmedTransaction | FailedTransaction { return parseTxsResponseUnsigned( this.chainId, - parseInt(response.height, 10), + response.height, response, this.bankTokens, this.erc20Tokens, @@ -472,7 +472,7 @@ export class CosmWasmConnection implements BlockchainConnection { } private async parseAndPopulateTxResponseSigned( - response: TxsResponse, + response: IndexedTx, ): Promise | FailedTransaction> { const firstMsg = response.tx.value.msg.find(() => true); if (!firstMsg) throw new Error("Got transaction without a first message. What is going on here?"); @@ -503,7 +503,7 @@ export class CosmWasmConnection implements BlockchainConnection { return parseTxsResponseSigned( this.chainId, - parseInt(response.height, 10), + response.height, nonce, response, this.bankTokens, diff --git a/packages/bcp/src/decode.spec.ts b/packages/bcp/src/decode.spec.ts index 49e5df0d..7ec8c142 100644 --- a/packages/bcp/src/decode.spec.ts +++ b/packages/bcp/src/decode.spec.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { types } from "@cosmwasm/sdk"; +import { IndexedTx, types } from "@cosmwasm/sdk"; import { Address, Algorithm, isSendTransaction, SendTransaction, TokenTicker } from "@iov/bcp"; import { Encoding } from "@iov/encoding"; import { assert } from "@iov/utils"; @@ -281,10 +281,11 @@ describe("decode", () => { describe("parseTxsResponseUnsigned", () => { it("works", () => { const currentHeight = 2923; - const txsResponse = { - height: "2823", - txhash: testdata.txId, - raw_log: '[{"msg_index":0,"success":true,"log":""}]', + const txsResponse: IndexedTx = { + height: 2823, + hash: testdata.txId, + rawLog: '[{"msg_index":0,"success":true,"log":""}]', + logs: [], tx: cosmoshub.tx, timestamp: "2020-02-14T11:35:41Z", }; @@ -310,10 +311,11 @@ describe("decode", () => { describe("parseTxsResponseSigned", () => { it("works", () => { const currentHeight = 2923; - const txsResponse = { - height: "2823", - txhash: testdata.txId, - raw_log: '[{"msg_index":0,"success":true,"log":""}]', + const txsResponse: IndexedTx = { + height: 2823, + hash: testdata.txId, + rawLog: '[{"msg_index":0,"success":true,"log":""}]', + logs: [], tx: cosmoshub.tx, timestamp: "2020-02-14T11:35:41Z", }; diff --git a/packages/bcp/src/decode.ts b/packages/bcp/src/decode.ts index 3279c9d9..aa4edb9c 100644 --- a/packages/bcp/src/decode.ts +++ b/packages/bcp/src/decode.ts @@ -1,4 +1,4 @@ -import { TxsResponse, types } from "@cosmwasm/sdk"; +import { IndexedTx, types } from "@cosmwasm/sdk"; import { Address, Algorithm, @@ -180,17 +180,16 @@ export function parseSignedTx( export function parseTxsResponseUnsigned( chainId: ChainId, currentHeight: number, - response: TxsResponse, + response: IndexedTx, tokens: BankTokens, erc20Tokens: readonly Erc20Token[], ): ConfirmedTransaction { - const height = parseInt(response.height, 10); return { transaction: parseUnsignedTx(response.tx.value, chainId, tokens, erc20Tokens), - height: height, - confirmations: currentHeight - height + 1, - transactionId: response.txhash as TransactionId, - log: response.raw_log, + height: response.height, + confirmations: currentHeight - response.height + 1, + transactionId: response.hash as TransactionId, + log: response.rawLog, }; } @@ -198,16 +197,15 @@ export function parseTxsResponseSigned( chainId: ChainId, currentHeight: number, nonce: Nonce, - response: TxsResponse, + response: IndexedTx, tokens: BankTokens, erc20Tokens: readonly Erc20Token[], ): ConfirmedAndSignedTransaction { - const height = parseInt(response.height, 10); return { ...parseSignedTx(response.tx.value, chainId, nonce, tokens, erc20Tokens), - height: height, - confirmations: currentHeight - height + 1, - transactionId: response.txhash as TransactionId, - log: response.raw_log, + height: response.height, + confirmations: currentHeight - response.height + 1, + transactionId: response.hash as TransactionId, + log: response.rawLog, }; } diff --git a/packages/bcp/types/decode.d.ts b/packages/bcp/types/decode.d.ts index b2fac2c7..906d4862 100644 --- a/packages/bcp/types/decode.d.ts +++ b/packages/bcp/types/decode.d.ts @@ -1,4 +1,4 @@ -import { TxsResponse, types } from "@cosmwasm/sdk"; +import { IndexedTx, types } from "@cosmwasm/sdk"; import { Amount, ChainId, @@ -43,7 +43,7 @@ export declare function parseSignedTx( export declare function parseTxsResponseUnsigned( chainId: ChainId, currentHeight: number, - response: TxsResponse, + response: IndexedTx, tokens: BankTokens, erc20Tokens: readonly Erc20Token[], ): ConfirmedTransaction; @@ -51,7 +51,7 @@ export declare function parseTxsResponseSigned( chainId: ChainId, currentHeight: number, nonce: Nonce, - response: TxsResponse, + response: IndexedTx, tokens: BankTokens, erc20Tokens: readonly Erc20Token[], ): ConfirmedAndSignedTransaction; diff --git a/packages/sdk/src/cosmwasmclient.searchtx.spec.ts b/packages/sdk/src/cosmwasmclient.searchtx.spec.ts index 5079a6bd..799684da 100644 --- a/packages/sdk/src/cosmwasmclient.searchtx.spec.ts +++ b/packages/sdk/src/cosmwasmclient.searchtx.spec.ts @@ -92,8 +92,8 @@ describe("CosmWasmClient.searchTx", () => { expect(result.length).toEqual(1); expect(result[0]).toEqual( jasmine.objectContaining({ - height: postedSend.height.toString(), - txhash: postedSend.hash, + height: postedSend.height, + hash: postedSend.hash, tx: postedSend.tx, }), ); @@ -144,8 +144,8 @@ describe("CosmWasmClient.searchTx", () => { expect(result.length).toEqual(1); expect(result[0]).toEqual( jasmine.objectContaining({ - height: postedSend.height.toString(), - txhash: postedSend.hash, + height: postedSend.height, + hash: postedSend.hash, tx: postedSend.tx, }), ); @@ -163,7 +163,7 @@ describe("CosmWasmClient.searchTx", () => { // 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`); + 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, ).toEqual(true); @@ -172,8 +172,8 @@ describe("CosmWasmClient.searchTx", () => { // Check details of most recent result expect(results[results.length - 1]).toEqual( jasmine.objectContaining({ - height: postedSend.height.toString(), - txhash: postedSend.hash, + height: postedSend.height, + hash: postedSend.hash, tx: postedSend.tx, }), ); @@ -189,7 +189,7 @@ describe("CosmWasmClient.searchTx", () => { // 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`); + 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, ).toEqual(true); @@ -198,8 +198,8 @@ describe("CosmWasmClient.searchTx", () => { // Check details of most recent result expect(results[results.length - 1]).toEqual( jasmine.objectContaining({ - height: postedSend.height.toString(), - txhash: postedSend.hash, + height: postedSend.height, + hash: postedSend.hash, tx: postedSend.tx, }), ); @@ -273,15 +273,15 @@ describe("CosmWasmClient.searchTx", () => { // 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`); + assert(isMsgSend(msg), `${result.hash} (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, + height: postedSend.height, + hash: postedSend.hash, tx: postedSend.tx, }), ); @@ -301,7 +301,7 @@ describe("CosmWasmClient.searchTx", () => { const msg = fromOneElementArray(result.tx.value.msg); assert( isMsgExecuteContract(msg) || isMsgInstantiateContract(msg), - `${result.txhash} (at ${result.height}) not an execute or instantiate msg`, + `${result.hash} (at ${result.height}) not an execute or instantiate msg`, ); } @@ -322,8 +322,8 @@ describe("CosmWasmClient.searchTx", () => { // Check details of most recent result expect(results[results.length - 1]).toEqual( jasmine.objectContaining({ - height: postedExecute.height.toString(), - txhash: postedExecute.hash, + height: postedExecute.height, + hash: postedExecute.hash, tx: postedExecute.tx, }), ); @@ -344,15 +344,15 @@ describe("CosmWasmClient.searchTx", () => { // 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`); + assert(isMsgExecuteContract(msg), `${result.hash} (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, + height: postedExecute.height, + hash: postedExecute.hash, tx: postedExecute.tx, }), ); diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index 5ce22c5f..0ac9cb4b 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -2,7 +2,7 @@ import { Sha256 } from "@iov/crypto"; import { Encoding } from "@iov/encoding"; import { Log, parseLogs } from "./logs"; -import { BlockResponse, BroadcastMode, RestClient, TxsResponse } from "./restclient"; +import { BlockResponse, BroadcastMode, RestClient } from "./restclient"; import { CosmosSdkAccount, CosmosSdkTx, StdTx } from "./types"; export interface GetNonceResult { @@ -92,6 +92,20 @@ export interface ContractDetails extends Contract { readonly initMsg: object; } +/** A transaction that is indexed as part of the transaction history */ +export interface IndexedTx { + readonly height: number; + readonly hash: string; + readonly rawLog: string; + readonly logs: readonly Log[]; + readonly tx: CosmosSdkTx; + /** The gas limit as set by the user */ + readonly gasWanted?: number; + /** The gas used by the execution */ + readonly gasUsed?: number; + readonly timestamp: string; +} + export class CosmWasmClient { protected readonly restClient: RestClient; @@ -153,7 +167,7 @@ export class CosmWasmClient { } } - public async searchTx(query: SearchTxQuery, filter: SearchTxFilter = {}): Promise { + public async searchTx(query: SearchTxQuery, filter: SearchTxFilter = {}): Promise { const minHeight = filter.minHeight || 0; const maxHeight = filter.maxHeight || Number.MAX_SAFE_INTEGER; @@ -163,7 +177,7 @@ export class CosmWasmClient { return `${originalQuery}&tx.minheight=${minHeight}&tx.maxheight=${maxHeight}`; } - let txs: readonly TxsResponse[]; + let txs: readonly IndexedTx[]; if (isSearchByIdQuery(query)) { txs = await this.txsQuery(`tx.hash=${query.id}`); } else if (isSearchByHeightQuery(query)) { @@ -180,8 +194,8 @@ export class CosmWasmClient { const sent = await this.txsQuery(sentQuery); const received = await this.txsQuery(receivedQuery); - const sentHashes = sent.map(t => t.txhash); - txs = [...sent, ...received.filter(t => !sentHashes.includes(t.txhash))]; + const sentHashes = sent.map(t => t.hash); + txs = [...sent, ...received.filter(t => !sentHashes.includes(t.hash))]; } else if (isSearchByTagsQuery(query)) { const rawQuery = withFilters(query.tags.map(t => `${t.key}=${t.value}`).join("&")); txs = await this.txsQuery(rawQuery); @@ -190,10 +204,7 @@ export class CosmWasmClient { } // 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; - }); + const filtered = txs.filter(tx => tx.height >= minHeight && tx.height <= maxHeight); return filtered; } @@ -303,7 +314,7 @@ export class CosmWasmClient { } } - private async txsQuery(query: string): Promise { + private async txsQuery(query: string): Promise { // TODO: we need proper pagination support const limit = 100; const result = await this.restClient.txsQuery(`${query}&limit=${limit}`); @@ -313,6 +324,15 @@ export class CosmWasmClient { `Found more results on the backend than we can process currently. Results: ${result.total_count}, supported: ${limit}`, ); } - return result.txs; + return result.txs.map( + (restItem): IndexedTx => ({ + height: parseInt(restItem.height, 10), + hash: restItem.txhash, + rawLog: restItem.raw_log, + logs: parseLogs(restItem.logs || []), + tx: restItem.tx, + timestamp: restItem.timestamp, + }), + ); } } diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index d6d5d2ef..28400ecf 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -12,6 +12,7 @@ export { Contract, CosmWasmClient, GetNonceResult, + IndexedTx, PostTxResult, SearchByHeightQuery, SearchByIdQuery, diff --git a/packages/sdk/types/cosmwasmclient.d.ts b/packages/sdk/types/cosmwasmclient.d.ts index f544e060..a35d3839 100644 --- a/packages/sdk/types/cosmwasmclient.d.ts +++ b/packages/sdk/types/cosmwasmclient.d.ts @@ -1,5 +1,5 @@ import { Log } from "./logs"; -import { BlockResponse, BroadcastMode, RestClient, TxsResponse } from "./restclient"; +import { BlockResponse, BroadcastMode, RestClient } from "./restclient"; import { CosmosSdkAccount, CosmosSdkTx, StdTx } from "./types"; export interface GetNonceResult { readonly accountNumber: number; @@ -63,6 +63,19 @@ export interface ContractDetails extends Contract { /** Argument passed on initialization of the contract */ readonly initMsg: object; } +/** A transaction that is indexed as part of the transaction history */ +export interface IndexedTx { + readonly height: number; + readonly hash: string; + readonly rawLog: string; + readonly logs: readonly Log[]; + readonly tx: CosmosSdkTx; + /** The gas limit as set by the user */ + readonly gasWanted?: number; + /** The gas used by the execution */ + readonly gasUsed?: number; + readonly timestamp: string; +} export declare class CosmWasmClient { protected readonly restClient: RestClient; constructor(url: string, broadcastMode?: BroadcastMode); @@ -86,7 +99,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, filter?: SearchTxFilter): Promise; + searchTx(query: SearchTxQuery, filter?: SearchTxFilter): Promise; postTx(tx: StdTx): Promise; getCodes(): Promise; getCodeDetails(codeId: number): Promise; diff --git a/packages/sdk/types/index.d.ts b/packages/sdk/types/index.d.ts index a96ec335..6c1aecba 100644 --- a/packages/sdk/types/index.d.ts +++ b/packages/sdk/types/index.d.ts @@ -11,6 +11,7 @@ export { Contract, CosmWasmClient, GetNonceResult, + IndexedTx, PostTxResult, SearchByHeightQuery, SearchByIdQuery,