From 80af5aca98bf4e993e6630296d27ab5fb5ec2e62 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Thu, 22 Apr 2021 17:57:03 +0200 Subject: [PATCH] Add decodeTxRaw --- CHANGELOG.md | 2 ++ .../src/cosmwasmclient.searchtx.spec.ts | 15 ++++++----- .../src/signingcosmwasmclient.spec.ts | 20 +++++++------- packages/proto-signing/src/decode.ts | 19 +++++++++++++ packages/proto-signing/src/index.ts | 1 + packages/proto-signing/src/signing.spec.ts | 27 +++++++++---------- .../src/signingstargateclient.spec.ts | 19 ++++++------- .../src/stargateclient.searchtx.spec.ts | 15 ++++++----- packages/stargate/src/stargateclient.ts | 14 ++++++++++ 9 files changed, 85 insertions(+), 47 deletions(-) create mode 100644 packages/proto-signing/src/decode.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 39be5e03e2..f0d7afb6b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -87,6 +87,8 @@ and this project adheres to - @cosmjs/stargate: Add transfer queries codec, as well as transfer query methods to IBC query extension. - @cosmjs/tendermint-rpc: Export `ValidatorSecp256k1Pubkey` interface. +- @cosmjs/proto-signing: Add transaction decoder `decodeTxRaw` for decoding + transaction bytes returned by Tendermint (e.g. in `IndexedTx.tx`). ### Changed diff --git a/packages/cosmwasm-stargate/src/cosmwasmclient.searchtx.spec.ts b/packages/cosmwasm-stargate/src/cosmwasmclient.searchtx.spec.ts index 316c03c31c..ddb92d82b1 100644 --- a/packages/cosmwasm-stargate/src/cosmwasmclient.searchtx.spec.ts +++ b/packages/cosmwasm-stargate/src/cosmwasmclient.searchtx.spec.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { fromBase64, toBase64 } from "@cosmjs/encoding"; import { + decodeTxRaw, DirectSecp256k1HdWallet, encodePubkey, makeAuthInfoBytes, @@ -16,7 +17,7 @@ import { isBroadcastTxSuccess, isMsgSendEncodeObject, } from "@cosmjs/stargate"; -import { Tx, TxRaw } from "@cosmjs/stargate/build/codec/cosmos/tx/v1beta1/tx"; +import { TxRaw } from "@cosmjs/stargate/build/codec/cosmos/tx/v1beta1/tx"; import { assert, sleep } from "@cosmjs/utils"; import { CosmWasmClient } from "./cosmwasmclient"; @@ -232,8 +233,8 @@ describe("CosmWasmClient.getTx and .searchTx", () => { // Check basic structure of all results for (const result of results) { - const tx = Tx.decode(result.tx); - const filteredMsgs = tx.body!.messages.filter((msg) => { + const tx = decodeTxRaw(result.tx); + const filteredMsgs = tx.body.messages.filter((msg) => { if (!isMsgSendEncodeObject(msg)) return false; const decoded = registry.decode(msg); return decoded.fromAddress === sendSuccessful?.sender; @@ -260,8 +261,8 @@ describe("CosmWasmClient.getTx and .searchTx", () => { // Check basic structure of all results for (const result of results) { - const tx = Tx.decode(result.tx); - const filteredMsgs = tx.body!.messages.filter((msg) => { + const tx = decodeTxRaw(result.tx); + const filteredMsgs = tx.body.messages.filter((msg) => { if (!isMsgSendEncodeObject(msg)) return false; const decoded = registry.decode(msg); return decoded.toAddress === sendSuccessful?.recipient; @@ -346,8 +347,8 @@ describe("CosmWasmClient.getTx and .searchTx", () => { // Check basic structure of all results for (const result of results) { - const tx = Tx.decode(result.tx); - const msg = fromOneElementArray(tx.body!.messages); + const tx = decodeTxRaw(result.tx); + const msg = fromOneElementArray(tx.body.messages); expect(msg.typeUrl).toEqual("/cosmos.bank.v1beta1.MsgSend"); const decoded = registry.decode(msg); expect(decoded.toAddress).toEqual(sendSuccessful.recipient); diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts index d092849629..3555411523 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts @@ -3,7 +3,7 @@ import { Secp256k1HdWallet } from "@cosmjs/amino"; import { UploadMeta } from "@cosmjs/cosmwasm-launchpad"; import { sha256 } from "@cosmjs/crypto"; import { toHex } from "@cosmjs/encoding"; -import { DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-signing"; +import { decodeTxRaw, DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-signing"; import { AminoMsgDelegate, AminoTypes, @@ -17,7 +17,7 @@ import { import { DeepPartial, MsgSend } from "@cosmjs/stargate/build/codec/cosmos/bank/v1beta1/tx"; import { Coin } from "@cosmjs/stargate/build/codec/cosmos/base/v1beta1/coin"; import { MsgDelegate } from "@cosmjs/stargate/build/codec/cosmos/staking/v1beta1/tx"; -import { AuthInfo, Tx, TxBody, TxRaw } from "@cosmjs/stargate/build/codec/cosmos/tx/v1beta1/tx"; +import { AuthInfo, TxBody, TxRaw } from "@cosmjs/stargate/build/codec/cosmos/tx/v1beta1/tx"; import { assert, sleep } from "@cosmjs/utils"; import Long from "long"; import pako from "pako"; @@ -601,11 +601,11 @@ describe("SigningCosmWasmClient", () => { const searchResult = await client.getTx(result.transactionHash); assert(searchResult, "Must find transaction"); - const tx = Tx.decode(searchResult.tx); + const tx = decodeTxRaw(searchResult.tx); // From ModifyingDirectSecp256k1HdWallet - expect(tx.body!.memo).toEqual("This was modified"); - expect({ ...tx.authInfo!.fee!.amount[0] }).toEqual(coin(3000, "ucosm")); - expect(tx.authInfo!.fee!.gasLimit.toNumber()).toEqual(333333); + expect(tx.body.memo).toEqual("This was modified"); + expect({ ...tx.authInfo.fee!.amount[0] }).toEqual(coin(3000, "ucosm")); + expect(tx.authInfo.fee!.gasLimit.toNumber()).toEqual(333333); }); }); @@ -830,11 +830,11 @@ describe("SigningCosmWasmClient", () => { const searchResult = await client.getTx(result.transactionHash); assert(searchResult, "Must find transaction"); - const tx = Tx.decode(searchResult.tx); + const tx = decodeTxRaw(searchResult.tx); // From ModifyingSecp256k1HdWallet - expect(tx.body!.memo).toEqual("This was modified"); - expect({ ...tx.authInfo!.fee!.amount[0] }).toEqual(coin(3000, "ucosm")); - expect(tx.authInfo!.fee!.gasLimit.toNumber()).toEqual(333333); + expect(tx.body.memo).toEqual("This was modified"); + expect({ ...tx.authInfo.fee!.amount[0] }).toEqual(coin(3000, "ucosm")); + expect(tx.authInfo.fee!.gasLimit.toNumber()).toEqual(333333); }); }); }); diff --git a/packages/proto-signing/src/decode.ts b/packages/proto-signing/src/decode.ts new file mode 100644 index 0000000000..e55906a32c --- /dev/null +++ b/packages/proto-signing/src/decode.ts @@ -0,0 +1,19 @@ +import { AuthInfo, TxBody, TxRaw } from "./codec/cosmos/tx/v1beta1/tx"; + +export interface DecodedTxRaw { + readonly authInfo: AuthInfo; + readonly body: TxBody; + readonly signatures: readonly Uint8Array[]; +} + +/** + * Takes a serialized TxRaw (the bytes stored in Tendermint) and decodes it into something usable. + */ +export function decodeTxRaw(tx: Uint8Array): DecodedTxRaw { + const txRaw = TxRaw.decode(tx); + return { + authInfo: AuthInfo.decode(txRaw.authInfoBytes), + body: TxBody.decode(txRaw.bodyBytes), + signatures: txRaw.signatures, + }; +} diff --git a/packages/proto-signing/src/index.ts b/packages/proto-signing/src/index.ts index a71e972164..e6490c7d79 100644 --- a/packages/proto-signing/src/index.ts +++ b/packages/proto-signing/src/index.ts @@ -1,6 +1,7 @@ // This type happens to be shared between Amino and Direct sign modes export { Coin, coin, coins, parseCoins } from "@cosmjs/amino"; +export { decodeTxRaw, DecodedTxRaw } from "./decode"; export { DecodeObject, EncodeObject, diff --git a/packages/proto-signing/src/signing.spec.ts b/packages/proto-signing/src/signing.spec.ts index 40c7810452..0267a78baa 100644 --- a/packages/proto-signing/src/signing.spec.ts +++ b/packages/proto-signing/src/signing.spec.ts @@ -2,7 +2,8 @@ import { fromBase64, fromHex, toHex } from "@cosmjs/encoding"; import { SignMode } from "./codec/cosmos/tx/signing/v1beta1/signing"; -import { Tx, TxRaw } from "./codec/cosmos/tx/v1beta1/tx"; +import { TxRaw } from "./codec/cosmos/tx/v1beta1/tx"; +import { decodeTxRaw } from "./decode"; import { DirectSecp256k1HdWallet } from "./directsecp256k1hdwallet"; import { Registry } from "./registry"; import { makeSignBytes, makeSignDoc } from "./signing"; @@ -22,25 +23,23 @@ describe("signing", () => { const prefixedPubkeyBytes = Uint8Array.from([0x0a, pubkeyBytes.length, ...pubkeyBytes]); testVectors.forEach(({ outputs: { signedTxBytes } }) => { - const parsedTestTx = Tx.decode(fromHex(signedTxBytes)); + const parsedTestTx = decodeTxRaw(fromHex(signedTxBytes)); expect(parsedTestTx.signatures.length).toEqual(1); - expect(parsedTestTx.authInfo!.signerInfos.length).toEqual(1); - expect(Uint8Array.from(parsedTestTx.authInfo!.signerInfos[0].publicKey!.value ?? [])).toEqual( + expect(parsedTestTx.authInfo.signerInfos.length).toEqual(1); + expect(Uint8Array.from(parsedTestTx.authInfo.signerInfos[0].publicKey!.value ?? [])).toEqual( prefixedPubkeyBytes, ); - expect(parsedTestTx.authInfo?.signerInfos![0].modeInfo!.single!.mode).toEqual( - SignMode.SIGN_MODE_DIRECT, - ); - expect({ ...parsedTestTx.authInfo!.fee!.amount[0] }).toEqual({ denom: "ucosm", amount: "2000" }); - expect(parsedTestTx.authInfo!.fee!.gasLimit.toString()).toEqual(gasLimit.toString()); - expect(parsedTestTx.body!.extensionOptions).toEqual([]); - expect(parsedTestTx.body!.nonCriticalExtensionOptions).toEqual([]); - expect(parsedTestTx.body!.messages.length).toEqual(1); + expect(parsedTestTx.authInfo.signerInfos[0].modeInfo!.single!.mode).toEqual(SignMode.SIGN_MODE_DIRECT); + expect({ ...parsedTestTx.authInfo.fee!.amount[0] }).toEqual({ denom: "ucosm", amount: "2000" }); + expect(parsedTestTx.authInfo.fee!.gasLimit.toString()).toEqual(gasLimit.toString()); + expect(parsedTestTx.body.extensionOptions).toEqual([]); + expect(parsedTestTx.body.nonCriticalExtensionOptions).toEqual([]); + expect(parsedTestTx.body.messages.length).toEqual(1); const registry = new Registry(); const parsedTestTxMsg = registry.decode({ - typeUrl: parsedTestTx.body!.messages[0].typeUrl, - value: parsedTestTx.body!.messages[0].value, + typeUrl: parsedTestTx.body.messages[0].typeUrl, + value: parsedTestTx.body.messages[0].value, }); expect(parsedTestTxMsg.fromAddress).toEqual(address); expect(parsedTestTxMsg.toAddress).toEqual(toAddress); diff --git a/packages/stargate/src/signingstargateclient.spec.ts b/packages/stargate/src/signingstargateclient.spec.ts index acf5591e5a..79214bd7a1 100644 --- a/packages/stargate/src/signingstargateclient.spec.ts +++ b/packages/stargate/src/signingstargateclient.spec.ts @@ -4,12 +4,13 @@ import { coin, coins, DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-si import { assert, sleep } from "@cosmjs/utils"; import protobuf from "protobufjs/minimal"; +import { decodeTxRaw } from "../../proto-signing/build"; import { AminoMsgDelegate } from "./aminomsgs"; import { AminoTypes } from "./aminotypes"; import { MsgSend } from "./codec/cosmos/bank/v1beta1/tx"; import { Coin } from "./codec/cosmos/base/v1beta1/coin"; import { DeepPartial, MsgDelegate } from "./codec/cosmos/staking/v1beta1/tx"; -import { AuthInfo, Tx, TxBody, TxRaw } from "./codec/cosmos/tx/v1beta1/tx"; +import { AuthInfo, TxBody, TxRaw } from "./codec/cosmos/tx/v1beta1/tx"; import { MsgDelegateEncodeObject, MsgSendEncodeObject } from "./encodeobjects"; import { GasPrice } from "./fee"; import { PrivateSigningStargateClient, SigningStargateClient } from "./signingstargateclient"; @@ -371,11 +372,11 @@ describe("SigningStargateClient", () => { const searchResult = await client.getTx(result.transactionHash); assert(searchResult, "Must find transaction"); - const tx = Tx.decode(searchResult.tx); + const tx = decodeTxRaw(searchResult.tx); // From ModifyingDirectSecp256k1HdWallet - expect(tx.body!.memo).toEqual("This was modified"); - expect({ ...tx.authInfo!.fee!.amount[0] }).toEqual(coin(3000, "ucosm")); - expect(tx.authInfo!.fee!.gasLimit.toNumber()).toEqual(333333); + expect(tx.body.memo).toEqual("This was modified"); + expect({ ...tx.authInfo.fee!.amount[0] }).toEqual(coin(3000, "ucosm")); + expect(tx.authInfo.fee!.gasLimit.toNumber()).toEqual(333333); }); }); @@ -568,11 +569,11 @@ describe("SigningStargateClient", () => { const searchResult = await client.getTx(result.transactionHash); assert(searchResult, "Must find transaction"); - const tx = Tx.decode(searchResult.tx); + const tx = decodeTxRaw(searchResult.tx); // From ModifyingSecp256k1HdWallet - expect(tx.body!.memo).toEqual("This was modified"); - expect({ ...tx.authInfo!.fee!.amount[0] }).toEqual(coin(3000, "ucosm")); - expect(tx.authInfo!.fee!.gasLimit.toNumber()).toEqual(333333); + expect(tx.body.memo).toEqual("This was modified"); + expect({ ...tx.authInfo.fee!.amount[0] }).toEqual(coin(3000, "ucosm")); + expect(tx.authInfo.fee!.gasLimit.toNumber()).toEqual(333333); }); }); }); diff --git a/packages/stargate/src/stargateclient.searchtx.spec.ts b/packages/stargate/src/stargateclient.searchtx.spec.ts index ba684c0d40..967be2d296 100644 --- a/packages/stargate/src/stargateclient.searchtx.spec.ts +++ b/packages/stargate/src/stargateclient.searchtx.spec.ts @@ -10,8 +10,9 @@ import { } from "@cosmjs/proto-signing"; import { assert, sleep } from "@cosmjs/utils"; +import { decodeTxRaw } from "../../proto-signing/build"; import { Coin } from "./codec/cosmos/base/v1beta1/coin"; -import { Tx, TxRaw } from "./codec/cosmos/tx/v1beta1/tx"; +import { TxRaw } from "./codec/cosmos/tx/v1beta1/tx"; import { isMsgSendEncodeObject } from "./encodeobjects"; import { BroadcastTxResponse, @@ -231,8 +232,8 @@ describe("StargateClient.getTx and .searchTx", () => { // Check basic structure of all results for (const result of results) { - const tx = Tx.decode(result.tx); - const filteredMsgs = tx.body!.messages.filter((msg) => { + const tx = decodeTxRaw(result.tx); + const filteredMsgs = tx.body.messages.filter((msg) => { if (!isMsgSendEncodeObject(msg)) return false; const decoded = registry.decode(msg); return decoded.fromAddress === sendSuccessful?.sender; @@ -259,8 +260,8 @@ describe("StargateClient.getTx and .searchTx", () => { // Check basic structure of all results for (const result of results) { - const tx = Tx.decode(result.tx); - const filteredMsgs = tx.body!.messages.filter((msg) => { + const tx = decodeTxRaw(result.tx); + const filteredMsgs = tx.body.messages.filter((msg) => { if (!isMsgSendEncodeObject(msg)) return false; const decoded = registry.decode(msg); return decoded.toAddress === sendSuccessful?.recipient; @@ -345,8 +346,8 @@ describe("StargateClient.getTx and .searchTx", () => { // Check basic structure of all results for (const result of results) { - const tx = Tx.decode(result.tx); - const msg = fromOneElementArray(tx.body!.messages); + const tx = decodeTxRaw(result.tx); + const msg = fromOneElementArray(tx.body.messages); expect(msg.typeUrl).toEqual("/cosmos.bank.v1beta1.MsgSend"); const decoded = registry.decode(msg); expect(decoded.toAddress).toEqual(sendSuccessful.recipient); diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index c442db3e83..1d198d3851 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -58,6 +58,20 @@ export interface IndexedTx { /** Transaction execution error code. 0 on success. */ readonly code: number; readonly rawLog: string; + /** + * Raw transaction bytes stored in Tendermint. + * + * If you hash this, you get the transaction hash (= transaction ID): + * + * ```js + * import { sha256 } from "@cosmjs/crypto"; + * import { toHex } from "@cosmjs/encoding"; + * + * const transactionId = toHex(sha256(indexTx.tx)).toUpperCase(); + * ``` + * + * Use `decodeTxRaw` from @cosmjs/proto-signing to decode this. + */ readonly tx: Uint8Array; readonly gasUsed: number; readonly gasWanted: number;