From 8a944ddf21711121824f0918e8320c0f0f1a2338 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 17 Nov 2021 16:28:16 +0100 Subject: [PATCH] Add query extension tx for simulation --- packages/stargate/src/index.ts | 2 + packages/stargate/src/queries/index.ts | 1 + packages/stargate/src/queries/tx.spec.ts | 104 +++++++++++++++++++++++ packages/stargate/src/queries/tx.ts | 81 ++++++++++++++++++ 4 files changed, 188 insertions(+) create mode 100644 packages/stargate/src/queries/tx.spec.ts create mode 100644 packages/stargate/src/queries/tx.ts diff --git a/packages/stargate/src/index.ts b/packages/stargate/src/index.ts index 6b11c991..ac91862d 100644 --- a/packages/stargate/src/index.ts +++ b/packages/stargate/src/index.ts @@ -78,7 +78,9 @@ export { setupGovExtension, setupIbcExtension, setupStakingExtension, + setupTxExtension, StakingExtension, + TxExtension, } from "./queries"; export { SearchByHeightQuery, diff --git a/packages/stargate/src/queries/index.ts b/packages/stargate/src/queries/index.ts index cc8b1609..8101bbb4 100644 --- a/packages/stargate/src/queries/index.ts +++ b/packages/stargate/src/queries/index.ts @@ -10,4 +10,5 @@ export { DistributionExtension, setupDistributionExtension } from "./distributio export { setupGovExtension, GovExtension, GovProposalId, GovParamsType } from "./gov"; export { IbcExtension, setupIbcExtension } from "./ibc"; export { setupStakingExtension, StakingExtension } from "./staking"; +export { setupTxExtension, TxExtension } from "./tx"; export { createPagination, createProtobufRpcClient, ProtobufRpcClient } from "./utils"; diff --git a/packages/stargate/src/queries/tx.spec.ts b/packages/stargate/src/queries/tx.spec.ts new file mode 100644 index 00000000..7b4351b7 --- /dev/null +++ b/packages/stargate/src/queries/tx.spec.ts @@ -0,0 +1,104 @@ +import { coin, coins, DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-signing"; +import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; +import { assertDefined, sleep } from "@cosmjs/utils"; +import { MsgDelegate } from "cosmjs-types/cosmos/staking/v1beta1/tx"; +import Long from "long"; + +import { defaultRegistryTypes, SigningStargateClient } from "../signingstargateclient"; +import { assertIsBroadcastTxSuccess, StargateClient } from "../stargateclient"; +import { + defaultSigningClientOptions, + faucet, + makeRandomAddress, + pendingWithoutSimapp, + simapp, + simappEnabled, + validator, +} from "../testutils.spec"; +import { QueryClient } from "./queryclient"; +import { setupTxExtension, TxExtension } from "./tx"; +import { longify } from "./utils"; + +async function makeClientWithTx(rpcUrl: string): Promise<[QueryClient & TxExtension, Tendermint34Client]> { + const tmClient = await Tendermint34Client.connect(rpcUrl); + return [QueryClient.withExtensions(tmClient, setupTxExtension), tmClient]; +} + +describe("TxExtension", () => { + const defaultFee = { + amount: coins(25000, "ucosm"), + gas: "1500000", // 1.5 million + }; + let txHash: string | undefined; + let memo: string | undefined; + + beforeAll(async () => { + if (simappEnabled()) { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); + const client = await SigningStargateClient.connectWithSigner( + simapp.tendermintUrl, + wallet, + defaultSigningClientOptions, + ); + + { + const recipient = makeRandomAddress(); + memo = `Test tx ${Date.now()}`; + const result = await client.sendTokens( + faucet.address0, + recipient, + coins(25000, "ucosm"), + defaultFee, + memo, + ); + assertIsBroadcastTxSuccess(result); + txHash = result.transactionHash; + } + + await sleep(75); // wait until transactions are indexed + } + }); + + describe("getTx", () => { + it("works", async () => { + pendingWithoutSimapp(); + assertDefined(txHash); + assertDefined(memo); + const [client, tmClient] = await makeClientWithTx(simapp.tendermintUrl); + + const response = await client.tx.getTx(txHash); + expect(response.tx?.body?.memo).toEqual(memo); + + tmClient.disconnect(); + }); + }); + + describe("simulate", () => { + it("works", async () => { + pendingWithoutSimapp(); + assertDefined(txHash); + assertDefined(memo); + const [client, tmClient] = await makeClientWithTx(simapp.tendermintUrl); + const sequenceClient = await StargateClient.connect(simapp.tendermintUrl); + + const registry = new Registry(defaultRegistryTypes); + const msg: MsgDelegate = { + delegatorAddress: faucet.address0, + validatorAddress: validator.validatorAddress, + amount: coin(25000, "ustake"), + }; + const msgAny = registry.encodeAsAny({ + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: msg, + }); + + const { sequence } = await sequenceClient.getSequence(faucet.address0); + const response = await client.tx.simulate([msgAny], "foo", faucet.pubkey0, sequence); + expect(response.gasInfo?.gasUsed.toNumber()).toBeGreaterThanOrEqual(101_000); + expect(response.gasInfo?.gasUsed.toNumber()).toBeLessThanOrEqual(107_000); + expect(response.gasInfo?.gasWanted).toEqual(longify(Long.UZERO)); + + tmClient.disconnect(); + }); + }); +}); diff --git a/packages/stargate/src/queries/tx.ts b/packages/stargate/src/queries/tx.ts new file mode 100644 index 00000000..596b1f1a --- /dev/null +++ b/packages/stargate/src/queries/tx.ts @@ -0,0 +1,81 @@ +import { Pubkey } from "@cosmjs/amino"; +import { encodePubkey } from "@cosmjs/proto-signing"; +import { SignMode } from "cosmjs-types/cosmos/tx/signing/v1beta1/signing"; +import { + GetTxRequest, + GetTxResponse, + ServiceClientImpl, + SimulateRequest, + SimulateResponse, +} from "cosmjs-types/cosmos/tx/v1beta1/service"; +import { AuthInfo, Fee, Tx, TxBody } from "cosmjs-types/cosmos/tx/v1beta1/tx"; +import { Any } from "cosmjs-types/google/protobuf/any"; +import Long from "long"; + +import { QueryClient } from "./queryclient"; +import { createProtobufRpcClient } from "./utils"; + +export interface TxExtension { + readonly tx: { + getTx: (txId: string) => Promise; + simulate: ( + messages: readonly Any[], + memo: string | undefined, + signer: Pubkey, + sequence: number, + ) => Promise; + // Add here with tests: + // - broadcastTx + // - getTxsEvent + }; +} + +export function setupTxExtension(base: QueryClient): TxExtension { + // Use this service to get easy typed access to query methods + // This cannot be used for proof verification + const rpc = createProtobufRpcClient(base); + const queryService = new ServiceClientImpl(rpc); + + return { + tx: { + getTx: async (txId: string) => { + const request: GetTxRequest = { + hash: txId, + }; + const response = await queryService.GetTx(request); + return response; + }, + simulate: async ( + messages: readonly Any[], + memo: string | undefined, + signer: Pubkey, + sequence: number, + ) => { + const request = SimulateRequest.fromPartial({ + tx: Tx.fromPartial({ + authInfo: AuthInfo.fromPartial({ + fee: Fee.fromPartial({}), + signerInfos: [ + { + publicKey: encodePubkey(signer), + sequence: Long.fromNumber(sequence, true), + modeInfo: { single: { mode: SignMode.SIGN_MODE_DIRECT } }, + }, + ], + }), + body: TxBody.fromPartial({ + messages: Array.from(messages), + memo: memo, + }), + signatures: [new Uint8Array()], + }), + // Sending serialized `txBytes` is the future. But + // this is not available in Comsos SDK 0.42. + txBytes: undefined, + }); + const response = await queryService.Simulate(request); + return response; + }, + }, + }; +}