From 2bf0e05a1bd8822ece38624cf5ab404b5f963be8 Mon Sep 17 00:00:00 2001 From: janfabian Date: Mon, 16 Oct 2023 16:41:58 +0200 Subject: [PATCH 1/5] feat(timeout-height): allow define optional timeout height parameter --- packages/amino/src/signdoc.ts | 3 + .../src/signingcosmwasmclient.spec.ts | 76 +++++++++++++++++++ .../src/signingcosmwasmclient.ts | 19 ++++- .../src/signingstargateclient.spec.ts | 76 +++++++++++++++++++ .../stargate/src/signingstargateclient.ts | 19 ++++- 5 files changed, 187 insertions(+), 6 deletions(-) diff --git a/packages/amino/src/signdoc.ts b/packages/amino/src/signdoc.ts index 3a6f56c1dc..ba319b4ad8 100644 --- a/packages/amino/src/signdoc.ts +++ b/packages/amino/src/signdoc.ts @@ -30,6 +30,7 @@ export interface StdSignDoc { readonly fee: StdFee; readonly msgs: readonly AminoMsg[]; readonly memo: string; + readonly timeout_height?: string; } function sortedObject(obj: any): any { @@ -61,6 +62,7 @@ export function makeSignDoc( memo: string | undefined, accountNumber: number | string, sequence: number | string, + timeout_height?: string, ): StdSignDoc { return { chain_id: chainId, @@ -69,6 +71,7 @@ export function makeSignDoc( fee: fee, msgs: msgs, memo: memo || "", + ...(timeout_height && { timeout_height }), }; } diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts index 156dfe8031..131be57d49 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts @@ -1195,6 +1195,44 @@ describe("SigningCosmWasmClient", () => { client.disconnect(); }); + + it("works with a custom timeout height", async () => { + pendingWithoutWasmd(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, { prefix: wasmd.prefix }); + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, { + ...defaultSigningClientOptions, + }); + + const msg = MsgSend.fromPartial({ + fromAddress: alice.address0, + toAddress: alice.address0, + amount: [coin(1, "ucosm")], + }); + const msgAny: MsgSendEncodeObject = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: msg, + }; + const fee = { + amount: coins(2000, "ucosm"), + gas: "222000", // 222k + }; + const memo = "Use your power wisely"; + const height = await client.getHeight(); + const signed = await client.sign( + alice.address0, + [msgAny], + fee, + memo, + undefined, + Long.fromNumber(height + 1), + ); + + // ensure signature is valid + const result = await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); + assertIsDeliverTxSuccess(result); + + client.disconnect(); + }); }); describe("legacy Amino mode", () => { @@ -1413,6 +1451,44 @@ describe("SigningCosmWasmClient", () => { client.disconnect(); }); + + it("works with custom timeoutHeight", async () => { + pendingWithoutWasmd(); + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, { prefix: wasmd.prefix }); + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, { + ...defaultSigningClientOptions, + }); + + const msg = MsgSend.fromPartial({ + fromAddress: alice.address0, + toAddress: alice.address0, + amount: [coin(1, "ucosm")], + }); + const msgAny: MsgSendEncodeObject = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: msg, + }; + const fee = { + amount: coins(2000, "ucosm"), + gas: "200000", + }; + const memo = "Use your tokens wisely"; + const height = await client.getHeight(); + const signed = await client.sign( + alice.address0, + [msgAny], + fee, + memo, + undefined, + Long.fromNumber(height + 1), + ); + + // ensure signature is valid + const result = await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); + assertIsDeliverTxSuccess(result); + + client.disconnect(); + }); }); }); }); diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts index c1670d5ef2..e277ed04d7 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts @@ -644,6 +644,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { fee: StdFee, memo: string, explicitSignerData?: SignerData, + timeoutHeight?: Long, ): Promise { let signerData: SignerData; if (explicitSignerData) { @@ -659,8 +660,8 @@ export class SigningCosmWasmClient extends CosmWasmClient { } return isOfflineDirectSigner(this.signer) - ? this.signDirect(signerAddress, messages, fee, memo, signerData) - : this.signAmino(signerAddress, messages, fee, memo, signerData); + ? this.signDirect(signerAddress, messages, fee, memo, signerData, timeoutHeight) + : this.signAmino(signerAddress, messages, fee, memo, signerData, timeoutHeight); } private async signAmino( @@ -669,6 +670,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { fee: StdFee, memo: string, { accountNumber, sequence, chainId }: SignerData, + timeoutHeight?: Long, ): Promise { assert(!isOfflineDirectSigner(this.signer)); const accountFromSigner = (await this.signer.getAccounts()).find( @@ -680,13 +682,22 @@ export class SigningCosmWasmClient extends CosmWasmClient { const pubkey = encodePubkey(encodeSecp256k1Pubkey(accountFromSigner.pubkey)); const signMode = SignMode.SIGN_MODE_LEGACY_AMINO_JSON; const msgs = messages.map((msg) => this.aminoTypes.toAmino(msg)); - const signDoc = makeSignDocAmino(msgs, fee, chainId, memo, accountNumber, sequence); + const signDoc = makeSignDocAmino( + msgs, + fee, + chainId, + memo, + accountNumber, + sequence, + timeoutHeight?.toString(), + ); const { signature, signed } = await this.signer.signAmino(signerAddress, signDoc); const signedTxBody: TxBodyEncodeObject = { typeUrl: "/cosmos.tx.v1beta1.TxBody", value: { messages: signed.msgs.map((msg) => this.aminoTypes.fromAmino(msg)), memo: signed.memo, + timeoutHeight: timeoutHeight, }, }; const signedTxBodyBytes = this.registry.encode(signedTxBody); @@ -713,6 +724,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { fee: StdFee, memo: string, { accountNumber, sequence, chainId }: SignerData, + timeoutHeight?: Long, ): Promise { assert(isOfflineDirectSigner(this.signer)); const accountFromSigner = (await this.signer.getAccounts()).find( @@ -727,6 +739,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { value: { messages: messages, memo: memo, + timeoutHeight: timeoutHeight, }, }; const txBodyBytes = this.registry.encode(txBody); diff --git a/packages/stargate/src/signingstargateclient.spec.ts b/packages/stargate/src/signingstargateclient.spec.ts index 959aef7a6e..132ca76c73 100644 --- a/packages/stargate/src/signingstargateclient.spec.ts +++ b/packages/stargate/src/signingstargateclient.spec.ts @@ -912,6 +912,44 @@ describe("SigningStargateClient", () => { const result = await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); assertIsDeliverTxSuccess(result); }); + + it("works with custom timeoutHeight", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); + const client = await SigningStargateClient.connectWithSigner( + simapp.tendermintUrl, + wallet, + defaultSigningClientOptions, + ); + + const msg = MsgSend.fromPartial({ + fromAddress: faucet.address0, + toAddress: faucet.address0, + amount: [coin(1, "ucosm")], + }); + const msgAny: MsgSendEncodeObject = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: msg, + }; + const fee = { + amount: coins(2000, "ucosm"), + gas: "222000", // 222k + }; + const memo = "Use your power wisely"; + const height = await client.getHeight(); + const signed = await client.sign( + faucet.address0, + [msgAny], + fee, + memo, + undefined, + Long.fromNumber(height + 1), + ); + + // ensure signature is valid + const result = await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); + assertIsDeliverTxSuccess(result); + }); }); describe("legacy Amino mode", () => { @@ -1124,6 +1162,44 @@ describe("SigningStargateClient", () => { const result = await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); assertIsDeliverTxSuccess(result); }); + + it("works with custom timeoutHeight", async () => { + pendingWithoutSimapp(); + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic); + const client = await SigningStargateClient.connectWithSigner( + simapp.tendermintUrl, + wallet, + defaultSigningClientOptions, + ); + + const msg = MsgSend.fromPartial({ + fromAddress: faucet.address0, + toAddress: faucet.address0, + amount: [coin(1, "ucosm")], + }); + const msgAny: MsgSendEncodeObject = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: msg, + }; + const fee = { + amount: coins(2000, "ucosm"), + gas: "200000", + }; + const memo = "Use your tokens wisely"; + const height = await client.getHeight(); + const signed = await client.sign( + faucet.address0, + [msgAny], + fee, + memo, + undefined, + Long.fromNumber(height + 1), + ); + + // ensure signature is valid + const result = await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); + assertIsDeliverTxSuccess(result); + }); }); }); }); diff --git a/packages/stargate/src/signingstargateclient.ts b/packages/stargate/src/signingstargateclient.ts index a064d1649a..5965013905 100644 --- a/packages/stargate/src/signingstargateclient.ts +++ b/packages/stargate/src/signingstargateclient.ts @@ -372,6 +372,7 @@ export class SigningStargateClient extends StargateClient { fee: StdFee, memo: string, explicitSignerData?: SignerData, + timeoutHeight?: Long, ): Promise { let signerData: SignerData; if (explicitSignerData) { @@ -387,8 +388,8 @@ export class SigningStargateClient extends StargateClient { } return isOfflineDirectSigner(this.signer) - ? this.signDirect(signerAddress, messages, fee, memo, signerData) - : this.signAmino(signerAddress, messages, fee, memo, signerData); + ? this.signDirect(signerAddress, messages, fee, memo, signerData, timeoutHeight) + : this.signAmino(signerAddress, messages, fee, memo, signerData, timeoutHeight); } private async signAmino( @@ -397,6 +398,7 @@ export class SigningStargateClient extends StargateClient { fee: StdFee, memo: string, { accountNumber, sequence, chainId }: SignerData, + timeoutHeight?: Long, ): Promise { assert(!isOfflineDirectSigner(this.signer)); const accountFromSigner = (await this.signer.getAccounts()).find( @@ -408,11 +410,20 @@ export class SigningStargateClient extends StargateClient { const pubkey = encodePubkey(encodeSecp256k1Pubkey(accountFromSigner.pubkey)); const signMode = SignMode.SIGN_MODE_LEGACY_AMINO_JSON; const msgs = messages.map((msg) => this.aminoTypes.toAmino(msg)); - const signDoc = makeSignDocAmino(msgs, fee, chainId, memo, accountNumber, sequence); + const signDoc = makeSignDocAmino( + msgs, + fee, + chainId, + memo, + accountNumber, + sequence, + timeoutHeight?.toString(), + ); const { signature, signed } = await this.signer.signAmino(signerAddress, signDoc); const signedTxBody = { messages: signed.msgs.map((msg) => this.aminoTypes.fromAmino(msg)), memo: signed.memo, + timeoutHeight: timeoutHeight, }; const signedTxBodyEncodeObject: TxBodyEncodeObject = { typeUrl: "/cosmos.tx.v1beta1.TxBody", @@ -442,6 +453,7 @@ export class SigningStargateClient extends StargateClient { fee: StdFee, memo: string, { accountNumber, sequence, chainId }: SignerData, + timeoutHeight?: Long, ): Promise { assert(isOfflineDirectSigner(this.signer)); const accountFromSigner = (await this.signer.getAccounts()).find( @@ -456,6 +468,7 @@ export class SigningStargateClient extends StargateClient { value: { messages: messages, memo: memo, + timeoutHeight: timeoutHeight, }, }; const txBodyBytes = this.registry.encode(txBodyEncodeObject); From efeb501b69189fa5adb286eb429e5f7c9e1a3038 Mon Sep 17 00:00:00 2001 From: janfabian Date: Wed, 25 Oct 2023 16:50:37 +0200 Subject: [PATCH 2/5] feat(timeout-height): use bigint instead long --- packages/amino/src/signdoc.ts | 4 ++-- .../src/signingcosmwasmclient.spec.ts | 18 ++---------------- .../src/signingcosmwasmclient.ts | 16 ++++------------ .../stargate/src/signingstargateclient.spec.ts | 18 ++---------------- packages/stargate/src/signingstargateclient.ts | 16 ++++------------ 5 files changed, 14 insertions(+), 58 deletions(-) diff --git a/packages/amino/src/signdoc.ts b/packages/amino/src/signdoc.ts index ba319b4ad8..fe5f1fb780 100644 --- a/packages/amino/src/signdoc.ts +++ b/packages/amino/src/signdoc.ts @@ -62,7 +62,7 @@ export function makeSignDoc( memo: string | undefined, accountNumber: number | string, sequence: number | string, - timeout_height?: string, + timeout_height?: bigint, ): StdSignDoc { return { chain_id: chainId, @@ -71,7 +71,7 @@ export function makeSignDoc( fee: fee, msgs: msgs, memo: memo || "", - ...(timeout_height && { timeout_height }), + ...(timeout_height && { timeout_height: timeout_height.toString() }), }; } diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts index 1f50c1627f..64e5a1136f 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts @@ -1214,14 +1214,7 @@ describe("SigningCosmWasmClient", () => { }; const memo = "Use your power wisely"; const height = await client.getHeight(); - const signed = await client.sign( - alice.address0, - [msgAny], - fee, - memo, - undefined, - Long.fromNumber(height + 1), - ); + const signed = await client.sign(alice.address0, [msgAny], fee, memo, undefined, BigInt(height + 3)); // ensure signature is valid const result = await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); @@ -1468,14 +1461,7 @@ describe("SigningCosmWasmClient", () => { }; const memo = "Use your tokens wisely"; const height = await client.getHeight(); - const signed = await client.sign( - alice.address0, - [msgAny], - fee, - memo, - undefined, - Long.fromNumber(height + 1), - ); + const signed = await client.sign(alice.address0, [msgAny], fee, memo, undefined, BigInt(height + 3)); // ensure signature is valid const result = await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts index 9627cf9dc8..626a1b6a4e 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts @@ -643,7 +643,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { fee: StdFee, memo: string, explicitSignerData?: SignerData, - timeoutHeight?: Long, + timeoutHeight?: bigint, ): Promise { let signerData: SignerData; if (explicitSignerData) { @@ -669,7 +669,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { fee: StdFee, memo: string, { accountNumber, sequence, chainId }: SignerData, - timeoutHeight?: Long, + timeoutHeight?: bigint, ): Promise { assert(!isOfflineDirectSigner(this.signer)); const accountFromSigner = (await this.signer.getAccounts()).find( @@ -681,15 +681,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { const pubkey = encodePubkey(encodeSecp256k1Pubkey(accountFromSigner.pubkey)); const signMode = SignMode.SIGN_MODE_LEGACY_AMINO_JSON; const msgs = messages.map((msg) => this.aminoTypes.toAmino(msg)); - const signDoc = makeSignDocAmino( - msgs, - fee, - chainId, - memo, - accountNumber, - sequence, - timeoutHeight?.toString(), - ); + const signDoc = makeSignDocAmino(msgs, fee, chainId, memo, accountNumber, sequence, timeoutHeight); const { signature, signed } = await this.signer.signAmino(signerAddress, signDoc); const signedTxBody: TxBodyEncodeObject = { typeUrl: "/cosmos.tx.v1beta1.TxBody", @@ -723,7 +715,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { fee: StdFee, memo: string, { accountNumber, sequence, chainId }: SignerData, - timeoutHeight?: Long, + timeoutHeight?: bigint, ): Promise { assert(isOfflineDirectSigner(this.signer)); const accountFromSigner = (await this.signer.getAccounts()).find( diff --git a/packages/stargate/src/signingstargateclient.spec.ts b/packages/stargate/src/signingstargateclient.spec.ts index 6da21f6d3f..22e709b0fb 100644 --- a/packages/stargate/src/signingstargateclient.spec.ts +++ b/packages/stargate/src/signingstargateclient.spec.ts @@ -936,14 +936,7 @@ describe("SigningStargateClient", () => { }; const memo = "Use your power wisely"; const height = await client.getHeight(); - const signed = await client.sign( - faucet.address0, - [msgAny], - fee, - memo, - undefined, - Long.fromNumber(height + 1), - ); + const signed = await client.sign(faucet.address0, [msgAny], fee, memo, undefined, BigInt(height + 3)); // ensure signature is valid const result = await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); @@ -1184,14 +1177,7 @@ describe("SigningStargateClient", () => { }; const memo = "Use your tokens wisely"; const height = await client.getHeight(); - const signed = await client.sign( - faucet.address0, - [msgAny], - fee, - memo, - undefined, - Long.fromNumber(height + 1), - ); + const signed = await client.sign(faucet.address0, [msgAny], fee, memo, undefined, BigInt(height + 3)); // ensure signature is valid const result = await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); diff --git a/packages/stargate/src/signingstargateclient.ts b/packages/stargate/src/signingstargateclient.ts index a9d9175815..f2e94818a5 100644 --- a/packages/stargate/src/signingstargateclient.ts +++ b/packages/stargate/src/signingstargateclient.ts @@ -371,7 +371,7 @@ export class SigningStargateClient extends StargateClient { fee: StdFee, memo: string, explicitSignerData?: SignerData, - timeoutHeight?: Long, + timeoutHeight?: bigint, ): Promise { let signerData: SignerData; if (explicitSignerData) { @@ -397,7 +397,7 @@ export class SigningStargateClient extends StargateClient { fee: StdFee, memo: string, { accountNumber, sequence, chainId }: SignerData, - timeoutHeight?: Long, + timeoutHeight?: bigint, ): Promise { assert(!isOfflineDirectSigner(this.signer)); const accountFromSigner = (await this.signer.getAccounts()).find( @@ -409,15 +409,7 @@ export class SigningStargateClient extends StargateClient { const pubkey = encodePubkey(encodeSecp256k1Pubkey(accountFromSigner.pubkey)); const signMode = SignMode.SIGN_MODE_LEGACY_AMINO_JSON; const msgs = messages.map((msg) => this.aminoTypes.toAmino(msg)); - const signDoc = makeSignDocAmino( - msgs, - fee, - chainId, - memo, - accountNumber, - sequence, - timeoutHeight?.toString(), - ); + const signDoc = makeSignDocAmino(msgs, fee, chainId, memo, accountNumber, sequence, timeoutHeight); const { signature, signed } = await this.signer.signAmino(signerAddress, signDoc); const signedTxBody = { messages: signed.msgs.map((msg) => this.aminoTypes.fromAmino(msg)), @@ -452,7 +444,7 @@ export class SigningStargateClient extends StargateClient { fee: StdFee, memo: string, { accountNumber, sequence, chainId }: SignerData, - timeoutHeight?: Long, + timeoutHeight?: bigint, ): Promise { assert(isOfflineDirectSigner(this.signer)); const accountFromSigner = (await this.signer.getAccounts()).find( From 5bdd1215f5a230cdefe01e3c4758b3bf155264d6 Mon Sep 17 00:00:00 2001 From: janfabian Date: Wed, 25 Oct 2023 18:15:05 +0200 Subject: [PATCH 3/5] feat(timeout-height): add past timeout height test --- .../src/signingcosmwasmclient.spec.ts | 74 +++++++++++++++++- .../src/signingstargateclient.spec.ts | 76 +++++++++++++++++++ 2 files changed, 149 insertions(+), 1 deletion(-) diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts index 64e5a1136f..9693e35046 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts @@ -1192,7 +1192,7 @@ describe("SigningCosmWasmClient", () => { client.disconnect(); }); - it("works with a custom timeout height", async () => { + it("works with a custom timeoutHeight", async () => { pendingWithoutWasmd(); const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, { prefix: wasmd.prefix }); const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, { @@ -1222,6 +1222,42 @@ describe("SigningCosmWasmClient", () => { client.disconnect(); }); + + it("fails with past timeoutHeight", async () => { + pendingWithoutWasmd(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, { prefix: wasmd.prefix }); + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, { + ...defaultSigningClientOptions, + }); + + const msg = MsgSend.fromPartial({ + fromAddress: alice.address0, + toAddress: alice.address0, + amount: [coin(1, "ucosm")], + }); + const msgAny: MsgSendEncodeObject = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: msg, + }; + const fee = { + amount: coins(2000, "ucosm"), + gas: "222000", // 222k + }; + const memo = "Use your power wisely"; + const height = await client.getHeight(); + const signed = await client.sign(alice.address0, [msgAny], fee, memo, undefined, BigInt(height - 1)); + + try { + await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); + } catch (e: any) { + assert(e.code === 30); + return; + } finally { + client.disconnect(); + } + + throw new Error("tx should have failed because of past timeoutHeight"); + }); }); describe("legacy Amino mode", () => { @@ -1469,6 +1505,42 @@ describe("SigningCosmWasmClient", () => { client.disconnect(); }); + + it("fails with past timeoutHeight", async () => { + pendingWithoutWasmd(); + const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, { prefix: wasmd.prefix }); + const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, { + ...defaultSigningClientOptions, + }); + + const msg = MsgSend.fromPartial({ + fromAddress: alice.address0, + toAddress: alice.address0, + amount: [coin(1, "ucosm")], + }); + const msgAny: MsgSendEncodeObject = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: msg, + }; + const fee = { + amount: coins(2000, "ucosm"), + gas: "200000", + }; + const memo = "Use your tokens wisely"; + const height = await client.getHeight(); + const signed = await client.sign(alice.address0, [msgAny], fee, memo, undefined, BigInt(height - 1)); + + try { + await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); + } catch (e: any) { + assert(e.code === 30); + return; + } finally { + client.disconnect(); + } + + throw new Error("tx should have failed because of past timeoutHeight"); + }); }); }); }); diff --git a/packages/stargate/src/signingstargateclient.spec.ts b/packages/stargate/src/signingstargateclient.spec.ts index 22e709b0fb..ef26449e44 100644 --- a/packages/stargate/src/signingstargateclient.spec.ts +++ b/packages/stargate/src/signingstargateclient.spec.ts @@ -942,6 +942,44 @@ describe("SigningStargateClient", () => { const result = await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); assertIsDeliverTxSuccess(result); }); + + it("fails with past timeoutHeight", async () => { + pendingWithoutSimapp(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); + const client = await SigningStargateClient.connectWithSigner( + simapp.tendermintUrl, + wallet, + defaultSigningClientOptions, + ); + + const msg = MsgSend.fromPartial({ + fromAddress: faucet.address0, + toAddress: faucet.address0, + amount: [coin(1, "ucosm")], + }); + const msgAny: MsgSendEncodeObject = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: msg, + }; + const fee = { + amount: coins(2000, "ucosm"), + gas: "222000", // 222k + }; + const memo = "Use your power wisely"; + const height = await client.getHeight(); + const signed = await client.sign(faucet.address0, [msgAny], fee, memo, undefined, BigInt(height - 1)); + + try { + await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); + } catch (e: any) { + assert(e.code === 30); + return; + } finally { + client.disconnect(); + } + + throw new Error("tx should have failed because of past timeoutHeight"); + }); }); describe("legacy Amino mode", () => { @@ -1183,6 +1221,44 @@ describe("SigningStargateClient", () => { const result = await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); assertIsDeliverTxSuccess(result); }); + + it("fails with past timeoutHeight", async () => { + pendingWithoutSimapp(); + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic); + const client = await SigningStargateClient.connectWithSigner( + simapp.tendermintUrl, + wallet, + defaultSigningClientOptions, + ); + + const msg = MsgSend.fromPartial({ + fromAddress: faucet.address0, + toAddress: faucet.address0, + amount: [coin(1, "ucosm")], + }); + const msgAny: MsgSendEncodeObject = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: msg, + }; + const fee = { + amount: coins(2000, "ucosm"), + gas: "200000", + }; + const memo = "Use your tokens wisely"; + const height = await client.getHeight(); + const signed = await client.sign(faucet.address0, [msgAny], fee, memo, undefined, BigInt(height - 1)); + + try { + await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); + } catch (e: any) { + assert(e.code === 30); + return; + } finally { + client.disconnect(); + } + + throw new Error("tx should have failed because of past timeoutHeight"); + }); }); }); }); From af78d546ec300125dbb04f7462626a922650ba13 Mon Sep 17 00:00:00 2001 From: janfabian Date: Thu, 26 Oct 2023 15:54:42 +0200 Subject: [PATCH 4/5] feat(timeout-height): expectAsync fail test --- .../src/signingcosmwasmclient.spec.ts | 34 +++++++++---------- .../src/signingstargateclient.spec.ts | 34 +++++++++---------- 2 files changed, 32 insertions(+), 36 deletions(-) diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts index 9693e35046..307fc626a4 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts @@ -1247,16 +1247,15 @@ describe("SigningCosmWasmClient", () => { const height = await client.getHeight(); const signed = await client.sign(alice.address0, [msgAny], fee, memo, undefined, BigInt(height - 1)); - try { - await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); - } catch (e: any) { - assert(e.code === 30); - return; - } finally { - client.disconnect(); - } + await expectAsync( + client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())), + ).toBeRejectedWith( + jasmine.objectContaining({ + code: 30, + }), + ); - throw new Error("tx should have failed because of past timeoutHeight"); + client.disconnect(); }); }); @@ -1530,16 +1529,15 @@ describe("SigningCosmWasmClient", () => { const height = await client.getHeight(); const signed = await client.sign(alice.address0, [msgAny], fee, memo, undefined, BigInt(height - 1)); - try { - await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); - } catch (e: any) { - assert(e.code === 30); - return; - } finally { - client.disconnect(); - } + await expectAsync( + client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())), + ).toBeRejectedWith( + jasmine.objectContaining({ + code: 30, + }), + ); - throw new Error("tx should have failed because of past timeoutHeight"); + client.disconnect(); }); }); }); diff --git a/packages/stargate/src/signingstargateclient.spec.ts b/packages/stargate/src/signingstargateclient.spec.ts index ef26449e44..a676631e46 100644 --- a/packages/stargate/src/signingstargateclient.spec.ts +++ b/packages/stargate/src/signingstargateclient.spec.ts @@ -969,16 +969,15 @@ describe("SigningStargateClient", () => { const height = await client.getHeight(); const signed = await client.sign(faucet.address0, [msgAny], fee, memo, undefined, BigInt(height - 1)); - try { - await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); - } catch (e: any) { - assert(e.code === 30); - return; - } finally { - client.disconnect(); - } + await expectAsync( + client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())), + ).toBeRejectedWith( + jasmine.objectContaining({ + code: 30, + }), + ); - throw new Error("tx should have failed because of past timeoutHeight"); + client.disconnect(); }); }); @@ -1248,16 +1247,15 @@ describe("SigningStargateClient", () => { const height = await client.getHeight(); const signed = await client.sign(faucet.address0, [msgAny], fee, memo, undefined, BigInt(height - 1)); - try { - await client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())); - } catch (e: any) { - assert(e.code === 30); - return; - } finally { - client.disconnect(); - } + await expectAsync( + client.broadcastTx(Uint8Array.from(TxRaw.encode(signed).finish())), + ).toBeRejectedWith( + jasmine.objectContaining({ + code: 30, + }), + ); - throw new Error("tx should have failed because of past timeoutHeight"); + client.disconnect(); }); }); }); From 423b30949d46c83a859b9d4fb02c267fb38a4156 Mon Sep 17 00:00:00 2001 From: janfabian Date: Thu, 26 Oct 2023 21:24:17 +0200 Subject: [PATCH 5/5] feat(timeout-height): signAndBroadcast --- .../cosmwasm-stargate/src/signingcosmwasmclient.ts | 12 ++++++++---- packages/stargate/src/signingstargateclient.ts | 6 ++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts index 626a1b6a4e..12430cc1f2 100644 --- a/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts +++ b/packages/cosmwasm-stargate/src/signingcosmwasmclient.ts @@ -574,18 +574,20 @@ export class SigningCosmWasmClient extends CosmWasmClient { } /** - * Creates a transaction with the given messages, fee and memo. Then signs and broadcasts the transaction. + * Creates a transaction with the given messages, fee, memo and timeout height. Then signs and broadcasts the transaction. * * @param signerAddress The address that will sign transactions using this instance. The signer must be able to sign with this address. * @param messages * @param fee * @param memo + * @param timeoutHeight (optional) timeout height to prevent the tx from being committed past a certain height */ public async signAndBroadcast( signerAddress: string, messages: readonly EncodeObject[], fee: StdFee | "auto" | number, memo = "", + timeoutHeight?: bigint, ): Promise { let usedFee: StdFee; if (fee == "auto" || typeof fee === "number") { @@ -598,13 +600,13 @@ export class SigningCosmWasmClient extends CosmWasmClient { } else { usedFee = fee; } - const txRaw = await this.sign(signerAddress, messages, usedFee, memo); + const txRaw = await this.sign(signerAddress, messages, usedFee, memo, undefined, timeoutHeight); const txBytes = TxRaw.encode(txRaw).finish(); return this.broadcastTx(txBytes, this.broadcastTimeoutMs, this.broadcastPollIntervalMs); } /** - * Creates a transaction with the given messages, fee and memo. Then signs and broadcasts the transaction. + * Creates a transaction with the given messages, fee, memo and timeout height. Then signs and broadcasts the transaction. * * This method is useful if you want to send a transaction in broadcast, * without waiting for it to be placed inside a block, because for example @@ -614,6 +616,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { * @param messages * @param fee * @param memo + * @param timeoutHeight (optional) timeout height to prevent the tx from being committed past a certain height * * @returns Returns the hash of the transaction */ @@ -622,6 +625,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { messages: readonly EncodeObject[], fee: StdFee | "auto" | number, memo = "", + timeoutHeight?: bigint, ): Promise { let usedFee: StdFee; if (fee == "auto" || typeof fee === "number") { @@ -632,7 +636,7 @@ export class SigningCosmWasmClient extends CosmWasmClient { } else { usedFee = fee; } - const txRaw = await this.sign(signerAddress, messages, usedFee, memo); + const txRaw = await this.sign(signerAddress, messages, usedFee, memo, undefined, timeoutHeight); const txBytes = TxRaw.encode(txRaw).finish(); return this.broadcastTxSync(txBytes); } diff --git a/packages/stargate/src/signingstargateclient.ts b/packages/stargate/src/signingstargateclient.ts index f2e94818a5..7d8d75e018 100644 --- a/packages/stargate/src/signingstargateclient.ts +++ b/packages/stargate/src/signingstargateclient.ts @@ -312,6 +312,7 @@ export class SigningStargateClient extends StargateClient { messages: readonly EncodeObject[], fee: StdFee | "auto" | number, memo = "", + timeoutHeight?: bigint, ): Promise { let usedFee: StdFee; if (fee == "auto" || typeof fee === "number") { @@ -324,7 +325,7 @@ export class SigningStargateClient extends StargateClient { } else { usedFee = fee; } - const txRaw = await this.sign(signerAddress, messages, usedFee, memo); + const txRaw = await this.sign(signerAddress, messages, usedFee, memo, undefined, timeoutHeight); const txBytes = TxRaw.encode(txRaw).finish(); return this.broadcastTx(txBytes, this.broadcastTimeoutMs, this.broadcastPollIntervalMs); } @@ -340,6 +341,7 @@ export class SigningStargateClient extends StargateClient { messages: readonly EncodeObject[], fee: StdFee | "auto" | number, memo = "", + timeoutHeight?: bigint, ): Promise { let usedFee: StdFee; if (fee == "auto" || typeof fee === "number") { @@ -350,7 +352,7 @@ export class SigningStargateClient extends StargateClient { } else { usedFee = fee; } - const txRaw = await this.sign(signerAddress, messages, usedFee, memo); + const txRaw = await this.sign(signerAddress, messages, usedFee, memo, undefined, timeoutHeight); const txBytes = TxRaw.encode(txRaw).finish(); return this.broadcastTxSync(txBytes); }