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();
|
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.
|
// NOTE: This method is tested against slow chains and timeouts in the @cosmjs/stargate package.
|
||||||
// Make sure it is kept in sync!
|
// Make sure it is kept in sync!
|
||||||
public async broadcastTx(
|
public async broadcastTx(
|
||||||
@ -240,10 +251,15 @@ export class CosmWasmClient {
|
|||||||
: pollForTx(txId);
|
: 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) =>
|
return new Promise((resolve, reject) =>
|
||||||
this.forceGetTmClient()
|
pollForTx(transactionId)
|
||||||
.broadcastTxSync({ tx })
|
|
||||||
.then(({ hash }) => pollForTx(toHex(hash).toUpperCase()))
|
|
||||||
.then(resolve, reject)
|
.then(resolve, reject)
|
||||||
.finally(() => clearTimeout(txPollTimeout)),
|
.finally(() => clearTimeout(txPollTimeout)),
|
||||||
);
|
);
|
||||||
|
@ -337,6 +337,58 @@ describe("StargateClient", () => {
|
|||||||
client.disconnect();
|
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 () => {
|
it("respects user timeouts rather than RPC timeouts", async () => {
|
||||||
pendingWithoutSlowSimapp();
|
pendingWithoutSlowSimapp();
|
||||||
const client = await StargateClient.connect(slowSimapp.tendermintUrl);
|
const client = await StargateClient.connect(slowSimapp.tendermintUrl);
|
||||||
|
@ -99,6 +99,15 @@ export interface BroadcastTxSuccess {
|
|||||||
readonly gasWanted: number;
|
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 type BroadcastTxResponse = BroadcastTxSuccess | BroadcastTxFailure;
|
||||||
|
|
||||||
export function isBroadcastTxFailure(result: BroadcastTxResponse): result is BroadcastTxFailure {
|
export function isBroadcastTxFailure(result: BroadcastTxResponse): result is BroadcastTxFailure {
|
||||||
@ -292,6 +301,17 @@ export class StargateClient {
|
|||||||
if (this.tmClient) this.tmClient.disconnect();
|
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(
|
public async broadcastTx(
|
||||||
tx: Uint8Array,
|
tx: Uint8Array,
|
||||||
timeoutMs = 60_000,
|
timeoutMs = 60_000,
|
||||||
@ -323,10 +343,15 @@ export class StargateClient {
|
|||||||
: pollForTx(txId);
|
: 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) =>
|
return new Promise((resolve, reject) =>
|
||||||
this.forceGetTmClient()
|
pollForTx(transactionId)
|
||||||
.broadcastTxSync({ tx })
|
|
||||||
.then(({ hash }) => pollForTx(toHex(hash).toUpperCase()))
|
|
||||||
.then(resolve, reject)
|
.then(resolve, reject)
|
||||||
.finally(() => clearTimeout(txPollTimeout)),
|
.finally(() => clearTimeout(txPollTimeout)),
|
||||||
);
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user