mirror of
https://github.com/cosmos/cosmjs.git
synced 2025-03-10 21:49:15 +00:00
Handle CheckTx errors properly
This commit is contained in:
parent
efdfd8a733
commit
2d6bbb079f
@ -207,6 +207,17 @@ export class CosmWasmClient {
|
||||
if (this.tmClient) this.tmClient.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a signed transaction to the network and monitors its inclusion in a block.
|
||||
*
|
||||
* If broadcasting is rejected by the node for some reason (e.g. because of a CheckTx failure),
|
||||
* an error is thrown.
|
||||
*
|
||||
* If the transaction is not included in a block before the provided timeout, this errors with a `TimeoutError`.
|
||||
*
|
||||
* If the transaction is included in a block, a `BroadcastTxResponse` is returned. The caller then
|
||||
* usually needs check for execution success or failure.
|
||||
*/
|
||||
// NOTE: This method is tested against slow chains and timeouts in the @cosmjs/stargate package.
|
||||
// Make sure it is kept in sync!
|
||||
public async broadcastTx(
|
||||
@ -240,10 +251,15 @@ export class CosmWasmClient {
|
||||
: pollForTx(txId);
|
||||
};
|
||||
|
||||
const broadcasted = await this.forceGetTmClient().broadcastTxSync({ tx });
|
||||
if (broadcasted.code) {
|
||||
throw new Error(
|
||||
`Broadcasting transaction failed with code ${broadcasted.code} (codespace: ${broadcasted.codeSpace}). Log: ${broadcasted.log}`,
|
||||
);
|
||||
}
|
||||
const transactionId = toHex(broadcasted.hash).toUpperCase();
|
||||
return new Promise((resolve, reject) =>
|
||||
this.forceGetTmClient()
|
||||
.broadcastTxSync({ tx })
|
||||
.then(({ hash }) => pollForTx(toHex(hash).toUpperCase()))
|
||||
pollForTx(transactionId)
|
||||
.then(resolve, reject)
|
||||
.finally(() => clearTimeout(txPollTimeout)),
|
||||
);
|
||||
|
@ -337,6 +337,58 @@ describe("StargateClient", () => {
|
||||
client.disconnect();
|
||||
});
|
||||
|
||||
it("errors immediately for a CheckTx failure", async () => {
|
||||
pendingWithoutSimapp();
|
||||
const client = await StargateClient.connect(simapp.tendermintUrl);
|
||||
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic);
|
||||
const [{ address, pubkey: pubkeyBytes }] = await wallet.getAccounts();
|
||||
const pubkey = encodePubkey({
|
||||
type: "tendermint/PubKeySecp256k1",
|
||||
value: toBase64(pubkeyBytes),
|
||||
});
|
||||
const registry = new Registry();
|
||||
const invalidRecipientAddress = "tgrade1z363ulwcrxged4z5jswyt5dn5v3lzsemwz9ewj"; // wrong bech32 prefix
|
||||
const txBodyFields: TxBodyEncodeObject = {
|
||||
typeUrl: "/cosmos.tx.v1beta1.TxBody",
|
||||
value: {
|
||||
messages: [
|
||||
{
|
||||
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
|
||||
value: {
|
||||
fromAddress: address,
|
||||
toAddress: invalidRecipientAddress,
|
||||
amount: [
|
||||
{
|
||||
denom: "ucosm",
|
||||
amount: "1234567",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
const txBodyBytes = registry.encode(txBodyFields);
|
||||
const { accountNumber, sequence } = (await client.getSequence(address))!;
|
||||
const feeAmount = coins(2000, "ucosm");
|
||||
const gasLimit = 200000;
|
||||
const authInfoBytes = makeAuthInfoBytes([pubkey], feeAmount, gasLimit, sequence);
|
||||
|
||||
const chainId = await client.getChainId();
|
||||
const signDoc = makeSignDoc(txBodyBytes, authInfoBytes, chainId, accountNumber);
|
||||
const { signature } = await wallet.signDirect(address, signDoc);
|
||||
const txRaw = TxRaw.fromPartial({
|
||||
bodyBytes: txBodyBytes,
|
||||
authInfoBytes: authInfoBytes,
|
||||
signatures: [fromBase64(signature.signature)],
|
||||
});
|
||||
const txRawBytes = Uint8Array.from(TxRaw.encode(txRaw).finish());
|
||||
|
||||
await expectAsync(client.broadcastTx(txRawBytes)).toBeRejectedWithError(/invalid recipient address/i);
|
||||
|
||||
client.disconnect();
|
||||
});
|
||||
|
||||
it("respects user timeouts rather than RPC timeouts", async () => {
|
||||
pendingWithoutSlowSimapp();
|
||||
const client = await StargateClient.connect(slowSimapp.tendermintUrl);
|
||||
|
@ -99,6 +99,15 @@ export interface BroadcastTxSuccess {
|
||||
readonly gasWanted: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The response after sucessfully broadcasting a transaction.
|
||||
* Success or failure refer to the execution result.
|
||||
*
|
||||
* The name is a bit misleading as this contains the result of the execution
|
||||
* in a block. Both `BroadcastTxSuccess` and `BroadcastTxFailure` contain a height
|
||||
* field, which is the height of the block that contains the transaction. This means
|
||||
* transactions that were never included in a block cannot be expressed with this type.
|
||||
*/
|
||||
export type BroadcastTxResponse = BroadcastTxSuccess | BroadcastTxFailure;
|
||||
|
||||
export function isBroadcastTxFailure(result: BroadcastTxResponse): result is BroadcastTxFailure {
|
||||
@ -292,6 +301,17 @@ export class StargateClient {
|
||||
if (this.tmClient) this.tmClient.disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcasts a signed transaction to the network and monitors its inclusion in a block.
|
||||
*
|
||||
* If broadcasting is rejected by the node for some reason (e.g. because of a CheckTx failure),
|
||||
* an error is thrown.
|
||||
*
|
||||
* If the transaction is not included in a block before the provided timeout, this errors with a `TimeoutError`.
|
||||
*
|
||||
* If the transaction is included in a block, a `BroadcastTxResponse` is returned. The caller then
|
||||
* usually needs check for execution success or failure.
|
||||
*/
|
||||
public async broadcastTx(
|
||||
tx: Uint8Array,
|
||||
timeoutMs = 60_000,
|
||||
@ -323,10 +343,15 @@ export class StargateClient {
|
||||
: pollForTx(txId);
|
||||
};
|
||||
|
||||
const broadcasted = await this.forceGetTmClient().broadcastTxSync({ tx });
|
||||
if (broadcasted.code) {
|
||||
throw new Error(
|
||||
`Broadcasting transaction failed with code ${broadcasted.code} (codespace: ${broadcasted.codeSpace}). Log: ${broadcasted.log}`,
|
||||
);
|
||||
}
|
||||
const transactionId = toHex(broadcasted.hash).toUpperCase();
|
||||
return new Promise((resolve, reject) =>
|
||||
this.forceGetTmClient()
|
||||
.broadcastTxSync({ tx })
|
||||
.then(({ hash }) => pollForTx(toHex(hash).toUpperCase()))
|
||||
pollForTx(transactionId)
|
||||
.then(resolve, reject)
|
||||
.finally(() => clearTimeout(txPollTimeout)),
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user