diff --git a/packages/bcp/src/cosmwasmconnection.spec.ts b/packages/bcp/src/cosmwasmconnection.spec.ts index 3e1c6857..a886e0b7 100644 --- a/packages/bcp/src/cosmwasmconnection.spec.ts +++ b/packages/bcp/src/cosmwasmconnection.spec.ts @@ -1,4 +1,4 @@ -import { CosmosAddressBech32Prefix } from "@cosmwasm/sdk"; +import { CosmosAddressBech32Prefix, decodeSignature } from "@cosmwasm/sdk"; import { Address, Algorithm, @@ -13,12 +13,13 @@ import { TransactionId, TransactionState, } from "@iov/bcp"; -import { Random, Secp256k1 } from "@iov/crypto"; +import { Random, Secp256k1, Secp256k1Signature, Sha256 } from "@iov/crypto"; import { Bech32, Encoding } from "@iov/encoding"; import { HdPaths, Secp256k1HdWallet, UserProfile } from "@iov/keycontrol"; import { assert } from "@iov/utils"; import { CosmWasmConnection, TokenConfiguration } from "./cosmwasmconnection"; +import { encodeFullSignature } from "./encode"; import * as testdata from "./testdata.spec"; const { fromBase64 } = Encoding; @@ -317,6 +318,47 @@ describe("CosmWasmConnection", () => { connection.disconnect(); }); + it("can get an old transaction", async () => { + pendingWithoutCosmos(); + const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig); + + const results = await connection.searchTx({ sentFromOrTo: faucet.address }); + const firstSearchResult = results.find(() => true); + assert(firstSearchResult, "At least one transaction sent by the faucet must be available."); + assert(isConfirmedTransaction(firstSearchResult), "Transaction must be confirmed."); + const { + transaction: searchedTransaction, + transactionId: searchedTransactionId, + height: searchedHeight, + } = firstSearchResult; + + const getResponse = await connection.getTx(searchedTransactionId); + assert(isConfirmedTransaction(getResponse), "Expected transaction to succeed"); + const { height, transactionId, log, transaction, signatures } = getResponse; + + // Test properties of getTx result: height, transactionId, log, transaction + expect(height).toEqual(searchedHeight); + expect(transactionId).toEqual(searchedTransactionId); + assert(log, "Log must be available"); + const [firstLog] = JSON.parse(log); + expect(firstLog.events.length).toEqual(2); + expect(transaction).toEqual(searchedTransaction); + + // Signature test ensures the nonce is correct + expect(signatures.length).toEqual(1); + const signBytes = connection.codec.bytesToSign(getResponse.transaction, signatures[0].nonce).bytes; + const { pubkey, signature } = decodeSignature(encodeFullSignature(signatures[0])); + const prehashed = new Sha256(signBytes).digest(); + const valid = await Secp256k1.verifySignature( + new Secp256k1Signature(signature.slice(0, 32), signature.slice(32, 64)), + prehashed, + pubkey, + ); + expect(valid).toEqual(true); + + connection.disconnect(); + }); + it("throws for non-existent transaction", async () => { pendingWithoutCosmos(); const connection = await CosmWasmConnection.establish(httpUrl, defaultPrefix, defaultConfig); diff --git a/packages/bcp/src/cosmwasmconnection.ts b/packages/bcp/src/cosmwasmconnection.ts index 3f1c21ce..ffe4540a 100644 --- a/packages/bcp/src/cosmwasmconnection.ts +++ b/packages/bcp/src/cosmwasmconnection.ts @@ -1,5 +1,12 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { CosmosAddressBech32Prefix, CosmWasmClient, RestClient, TxsResponse, types } from "@cosmwasm/sdk"; +import { + CosmosAddressBech32Prefix, + CosmWasmClient, + findSequenceForSignedTx, + RestClient, + TxsResponse, + types, +} from "@cosmwasm/sdk"; import { Account, AccountQuery, @@ -376,8 +383,15 @@ export class CosmWasmConnection implements BlockchainConnection { } const { accountNumber, sequence: currentSequence } = await this.cosmWasmClient.getNonce(senderAddress); + const sequenceForTx = await findSequenceForSignedTx( + response.tx, + Caip5.decode(this.chainId), + accountNumber, + currentSequence, + ); + if (!sequenceForTx) throw new Error("Cound not find matching sequence for this transaction"); - const nonce = accountToNonce(accountNumber, currentSequence - 1); + const nonce = accountToNonce(accountNumber, sequenceForTx); return parseTxsResponseSigned( this.chainId, diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 4352341b..4483ad8d 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -6,7 +6,6 @@ export { CosmosAddressBech32Prefix, encodeAddress, isValidAddress } from "./addr export { unmarshalTx } from "./decoding"; export { makeSignBytes, marshalTx } from "./encoding"; export { RestClient, TxsResponse } from "./restclient"; -export { encodeSecp256k1Signature } from "./signature"; export { CosmWasmClient, ExecuteResult, @@ -25,3 +24,4 @@ export { encodeSecp256k1Pubkey, } from "./pubkey"; export { findSequenceForSignedTx } from "./sequence"; +export { encodeSecp256k1Signature, decodeSignature } from "./signature"; diff --git a/packages/sdk/types/index.d.ts b/packages/sdk/types/index.d.ts index dcb618cc..18dd10d2 100644 --- a/packages/sdk/types/index.d.ts +++ b/packages/sdk/types/index.d.ts @@ -5,7 +5,6 @@ export { CosmosAddressBech32Prefix, encodeAddress, isValidAddress } from "./addr export { unmarshalTx } from "./decoding"; export { makeSignBytes, marshalTx } from "./encoding"; export { RestClient, TxsResponse } from "./restclient"; -export { encodeSecp256k1Signature } from "./signature"; export { CosmWasmClient, ExecuteResult, @@ -24,3 +23,4 @@ export { encodeSecp256k1Pubkey, } from "./pubkey"; export { findSequenceForSignedTx } from "./sequence"; +export { encodeSecp256k1Signature, decodeSignature } from "./signature";