From fd725998c6b264e9f2b58a3811d88dba42773b43 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 12 Feb 2020 16:06:46 +0100 Subject: [PATCH 1/4] Avoid throwing error when transaction ID does not exist Before we got "Error: Tx: RPC error -32603 - Internal error: tx (0000000000000000000000000000000000000000000000000000000000000000) not found" --- packages/sdk/src/cosmwasmclient.spec.ts | 8 ++++++++ packages/sdk/src/cosmwasmclient.ts | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/sdk/src/cosmwasmclient.spec.ts b/packages/sdk/src/cosmwasmclient.spec.ts index 69cb70b4..5862ae17 100644 --- a/packages/sdk/src/cosmwasmclient.spec.ts +++ b/packages/sdk/src/cosmwasmclient.spec.ts @@ -202,6 +202,14 @@ describe("CosmWasmClient", () => { ); }); + it("can search by ID (non existent)", async () => { + pendingWithoutCosmos(); + const client = CosmWasmClient.makeReadOnly(httpUrl); + const nonExistentId = "0000000000000000000000000000000000000000000000000000000000000000"; + const result = await client.searchTx({ id: nonExistentId }); + expect(result.length).toEqual(0); + }); + it("can search by height", async () => { pendingWithoutCosmos(); assert(posted, "value must be set in beforeAll()"); diff --git a/packages/sdk/src/cosmwasmclient.ts b/packages/sdk/src/cosmwasmclient.ts index 130f8949..c94232fd 100644 --- a/packages/sdk/src/cosmwasmclient.ts +++ b/packages/sdk/src/cosmwasmclient.ts @@ -164,7 +164,7 @@ export class CosmWasmClient { } if (isSearchByIdQuery(query)) { - return [await this.restClient.txsById(query.id)]; + return (await this.restClient.txs(`tx.hash=${query.id}`)).txs; } else if (isSearchByHeightQuery(query)) { return (await this.restClient.txs(`tx.height=${query.height}`)).txs; } else if (isSearchBySentFromOrToQuery(query)) { From 3e5bd7c3c7262ba75910a133ed49c72f768e6b3b Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 12 Feb 2020 16:08:08 +0100 Subject: [PATCH 2/4] Fix handling of non-existent ID in getTx --- packages/bcp/src/cosmwasmconnection.spec.ts | 16 ++++++++++++++++ packages/bcp/src/cosmwasmconnection.ts | 15 +++++++-------- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/packages/bcp/src/cosmwasmconnection.spec.ts b/packages/bcp/src/cosmwasmconnection.spec.ts index d1777a4a..734cd00d 100644 --- a/packages/bcp/src/cosmwasmconnection.spec.ts +++ b/packages/bcp/src/cosmwasmconnection.spec.ts @@ -10,6 +10,7 @@ import { PubkeyBytes, SendTransaction, TokenTicker, + TransactionId, TransactionState, } from "@iov/bcp"; import { Random, Secp256k1 } from "@iov/crypto"; @@ -264,6 +265,21 @@ describe("CosmWasmConnection", () => { }); }); + describe("getTx", () => { + it("throws for non-existent transaction", async () => { + pendingWithoutCosmos(); + const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig); + + const nonExistentId = "0000000000000000000000000000000000000000000000000000000000000000" as TransactionId; + await connection.getTx(nonExistentId).then( + () => fail("this must not succeed"), + error => expect(error).toMatch(/transaction does not exist/i), + ); + + connection.disconnect(); + }); + }); + describe("integration tests", () => { it("can post and get a transaction", async () => { pendingWithoutCosmos(); diff --git a/packages/bcp/src/cosmwasmconnection.ts b/packages/bcp/src/cosmwasmconnection.ts index d7c9e893..c90047ef 100644 --- a/packages/bcp/src/cosmwasmconnection.ts +++ b/packages/bcp/src/cosmwasmconnection.ts @@ -231,15 +231,14 @@ export class CosmWasmConnection implements BlockchainConnection { public async getTx( id: TransactionId, ): Promise | FailedTransaction> { - try { - // tslint:disable-next-line: deprecation - const response = await this.restClient.txsById(id); - return this.parseAndPopulateTxResponseSigned(response); - } catch (error) { - if (error.response.status === 404) { + const results = await this.cosmWasmClient.searchTx({ id: id }); + switch (results.length) { + case 0: throw new Error("Transaction does not exist"); - } - throw error; + case 1: + return this.parseAndPopulateTxResponseSigned(results[0]); + default: + throw new Error("Got unexpected amount of search results"); } } From d24d6255e1132fd9c9b18c1869ba85b5c159cbd7 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 13 Feb 2020 12:18:57 +0100 Subject: [PATCH 3/4] Improve "can post and get a transaction" test code --- packages/bcp/src/cosmwasmconnection.spec.ts | 37 ++++++++------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/packages/bcp/src/cosmwasmconnection.spec.ts b/packages/bcp/src/cosmwasmconnection.spec.ts index 734cd00d..e858de22 100644 --- a/packages/bcp/src/cosmwasmconnection.spec.ts +++ b/packages/bcp/src/cosmwasmconnection.spec.ts @@ -5,7 +5,6 @@ import { ChainId, isBlockInfoPending, isConfirmedTransaction, - isFailedTransaction, isSendTransaction, PubkeyBytes, SendTransaction, @@ -22,7 +21,7 @@ import { CosmWasmCodec } from "./cosmwasmcodec"; import { CosmWasmConnection, TokenConfiguration } from "./cosmwasmconnection"; import * as testdata from "./testdata.spec"; -const { fromBase64, toHex } = Encoding; +const { fromBase64 } = Encoding; function pendingWithoutCosmos(): void { if (!process.env.COSMOS_ENABLED) { @@ -311,35 +310,25 @@ describe("CosmWasmConnection", () => { expect(blockInfo.state).toEqual(TransactionState.Succeeded); const getResponse = await connection.getTx(transactionId); - expect(getResponse).toBeTruthy(); expect(getResponse.transactionId).toEqual(transactionId); - if (isFailedTransaction(getResponse)) { - throw new Error("Expected transaction to succeed"); - } + assert(isConfirmedTransaction(getResponse), "Expected transaction to succeed"); assert(getResponse.log, "Log must be available"); // we get a json response in the log for each msg, multiple events is good (transfer succeeded) const [firstLog] = JSON.parse(getResponse.log); expect(firstLog.events.length).toEqual(2); - const { transaction, signatures } = getResponse; - if (!isSendTransaction(transaction)) { - throw new Error("Expected send transaction"); - } - expect(transaction.kind).toEqual(unsigned.kind); - expect(transaction.sender).toEqual(unsigned.sender); - expect(transaction.recipient).toEqual(unsigned.recipient); - expect(transaction.memo).toEqual(unsigned.memo); - expect(transaction.amount).toEqual(unsigned.amount); - expect(transaction.chainId).toEqual(unsigned.chainId); + const { transaction, signatures } = getResponse; + assert(isSendTransaction(transaction), "Expected send transaction"); + expect(transaction).toEqual(unsigned); expect(signatures.length).toEqual(1); - expect(signatures[0].nonce).toEqual(signed.signatures[0].nonce); - expect(signatures[0].pubkey.algo).toEqual(signed.signatures[0].pubkey.algo); - expect(toHex(signatures[0].pubkey.data)).toEqual( - toHex(Secp256k1.compressPubkey(signed.signatures[0].pubkey.data)), - ); - expect(toHex(signatures[0].signature)).toEqual( - toHex(Secp256k1.trimRecoveryByte(signed.signatures[0].signature)), - ); + expect(signatures[0]).toEqual({ + nonce: signed.signatures[0].nonce, // This equality check works by pure luck. The implementation is broken. See https://github.com/iov-one/iov-core/pull/1390 + pubkey: { + algo: signed.signatures[0].pubkey.algo, + data: Secp256k1.compressPubkey(signed.signatures[0].pubkey.data), + }, + signature: Secp256k1.trimRecoveryByte(signed.signatures[0].signature), + }); connection.disconnect(); }); From 7d610348c61b19a268e6aee7fc1e12c39e39bfda Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 13 Feb 2020 12:30:24 +0100 Subject: [PATCH 4/4] Remove broken height argument from RestClient.authAccounts --- packages/bcp/src/cosmwasmconnection.spec.ts | 2 +- packages/bcp/src/cosmwasmconnection.ts | 30 ++++----------------- packages/sdk/src/restclient.ts | 5 ++-- packages/sdk/types/restclient.d.ts | 2 +- 4 files changed, 9 insertions(+), 30 deletions(-) diff --git a/packages/bcp/src/cosmwasmconnection.spec.ts b/packages/bcp/src/cosmwasmconnection.spec.ts index e858de22..91fd0495 100644 --- a/packages/bcp/src/cosmwasmconnection.spec.ts +++ b/packages/bcp/src/cosmwasmconnection.spec.ts @@ -322,7 +322,7 @@ describe("CosmWasmConnection", () => { expect(transaction).toEqual(unsigned); expect(signatures.length).toEqual(1); expect(signatures[0]).toEqual({ - nonce: signed.signatures[0].nonce, // This equality check works by pure luck. The implementation is broken. See https://github.com/iov-one/iov-core/pull/1390 + nonce: -1, // Unfortunately this information is unavailable as previous implementation attempt is broken. See https://github.com/iov-one/iov-core/pull/1390 pubkey: { algo: signed.signatures[0].pubkey.algo, data: Secp256k1.compressPubkey(signed.signatures[0].pubkey.data), diff --git a/packages/bcp/src/cosmwasmconnection.ts b/packages/bcp/src/cosmwasmconnection.ts index c90047ef..93e102d1 100644 --- a/packages/bcp/src/cosmwasmconnection.ts +++ b/packages/bcp/src/cosmwasmconnection.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { CosmosAddressBech32Prefix, CosmWasmClient, RestClient, TxsResponse, types } from "@cosmwasm/sdk"; +import { CosmosAddressBech32Prefix, CosmWasmClient, RestClient, TxsResponse } from "@cosmwasm/sdk"; import { Account, AccountQuery, @@ -364,30 +364,10 @@ export class CosmWasmConnection implements BlockchainConnection { private async parseAndPopulateTxResponseSigned( response: TxsResponse, ): 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?"); - - // needed to get the (account_number, sequence) for the primary signature - let primarySignerAddress: string; - if (types.isMsgSend(firstMsg)) { - primarySignerAddress = firstMsg.value.from_address; - } else if ( - types.isMsgStoreCode(firstMsg) || - types.isMsgInstantiateContract(firstMsg) || - types.isMsgExecuteContract(firstMsg) - ) { - primarySignerAddress = firstMsg.value.sender; - } else { - throw new Error(`Got unsupported type of message: ${firstMsg.type}`); - } - - // tslint:disable-next-line: deprecation - const accountForHeight = await this.restClient.authAccounts(primarySignerAddress, response.height); - const accountNumber = accountForHeight.result.value.account_number; - // this is technically not the proper sequence. maybe this causes issues for sig validation? - // leaving for now unless it causes issues - const sequence = accountForHeight.result.value.sequence - 1; - const nonce = accountToNonce(accountNumber, sequence); + // There is no known way to get the nonce that was used for signing a transaction. + // This information is nesessary for signature validation. + // TODO: fix + const nonce = -1 as Nonce; const chainId = this.chainId(); return parseTxsResponseSigned(chainId, parseInt(response.height, 10), nonce, response, this.bankTokens); diff --git a/packages/sdk/src/restclient.ts b/packages/sdk/src/restclient.ts index 8815981d..a8d4b1a5 100644 --- a/packages/sdk/src/restclient.ts +++ b/packages/sdk/src/restclient.ts @@ -228,9 +228,8 @@ export class RestClient { return Encoding.fromBase64((responseData as EncodeTxResponse).tx); } - public async authAccounts(address: string, height?: string): Promise { - const path = - height === undefined ? `/auth/accounts/${address}` : `/auth/accounts/${address}?tx.height=${height}`; + public async authAccounts(address: string): Promise { + const path = `/auth/accounts/${address}`; const responseData = await this.get(path); if ((responseData as any).result.type !== "cosmos-sdk/Account") { throw new Error("Unexpected response data format"); diff --git a/packages/sdk/types/restclient.d.ts b/packages/sdk/types/restclient.d.ts index a7e87e27..2da34281 100644 --- a/packages/sdk/types/restclient.d.ts +++ b/packages/sdk/types/restclient.d.ts @@ -93,7 +93,7 @@ export declare class RestClient { blocks(height: number): Promise; /** returns the amino-encoding of the transaction performed by the server */ encodeTx(tx: CosmosSdkTx): Promise; - authAccounts(address: string, height?: string): Promise; + authAccounts(address: string): Promise; txs(query: string): Promise; txsById(id: string): Promise; postTx(tx: Uint8Array): Promise;