Merge pull request #210 from CosmWasm/199-client-error-handling

Reorganise error handling
This commit is contained in:
Simon Warta 2020-06-10 11:54:50 +02:00 committed by GitHub
commit 0c6410612c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 131 additions and 79 deletions

View File

@ -1,9 +1,8 @@
/* eslint-disable @typescript-eslint/camelcase */
import { Uint53 } from "@cosmjs/math";
import { Coin, CosmosSdkTx, isMsgSend, makeSignBytes, MsgSend, Secp256k1Pen } from "@cosmjs/sdk38";
import { assert, sleep } from "@cosmjs/utils";
import { CosmWasmClient } from "./cosmwasmclient";
import { CosmWasmClient, isPostTxFailure } from "./cosmwasmclient";
import { isMsgExecuteContract, isMsgInstantiateContract } from "./msgs";
import { RestClient } from "./restclient";
import { SigningCosmWasmClient } from "./signingcosmwasmclient";
@ -113,19 +112,13 @@ describe("CosmWasmClient.searchTx", () => {
},
};
const transactionId = await client.getIdentifier(tx);
try {
await client.postTx(tx.value);
} catch (error) {
// postTx() throws on execution failures, which is a questionable design. Ignore for now.
// console.log(error);
const errorMessage: string = error.toString();
const [_, heightMatch] = errorMessage.match(/at height ([0-9]+)/) || ["", ""];
const result = await client.postTx(tx.value);
if (isPostTxFailure(result)) {
sendUnsuccessful = {
sender: alice.address0,
recipient: recipient,
hash: transactionId,
height: Uint53.fromString(heightMatch).toNumber(),
height: result.height,
tx: tx,
};
}

View File

@ -5,7 +5,7 @@ import { makeSignBytes, MsgSend, Secp256k1Pen, StdFee } from "@cosmjs/sdk38";
import { assert, sleep } from "@cosmjs/utils";
import { ReadonlyDate } from "readonly-date";
import { Code, CosmWasmClient, PrivateCosmWasmClient } from "./cosmwasmclient";
import { Code, CosmWasmClient, isPostTxFailure, PrivateCosmWasmClient } from "./cosmwasmclient";
import { findAttribute } from "./logs";
import { SigningCosmWasmClient } from "./signingcosmwasmclient";
import cosmoshub from "./testdata/cosmoshub.json";
@ -242,7 +242,9 @@ describe("CosmWasmClient", () => {
memo: memo,
signatures: [signature],
};
const { logs, transactionHash } = await client.postTx(signedTx);
const result = await client.postTx(signedTx);
assert(!isPostTxFailure(result));
const { logs, transactionHash } = result;
const amountAttr = findAttribute(logs, "transfer", "amount");
expect(amountAttr.value).toEqual("1234567ucosm");
expect(transactionHash).toMatch(/^[0-9A-F]{64}$/);

View File

@ -1,5 +1,6 @@
import { Sha256 } from "@cosmjs/crypto";
import { fromBase64, fromHex, toHex } from "@cosmjs/encoding";
import { Uint53 } from "@cosmjs/math";
import {
BroadcastMode,
Coin,
@ -28,11 +29,26 @@ export interface Account {
readonly sequence: number;
}
export interface PostTxResult {
export interface PostTxFailure {
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
readonly height: number;
readonly code: number;
readonly rawLog: string;
}
export interface PostTxSuccess {
readonly logs: readonly Log[];
readonly rawLog: string;
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
readonly data?: Uint8Array;
}
export type PostTxResult = PostTxSuccess | PostTxFailure;
export function isPostTxFailure(postTxResult: PostTxResult): postTxResult is PostTxFailure {
return !!(postTxResult as PostTxFailure).code;
}
export interface SearchByIdQuery {
@ -294,17 +310,19 @@ export class CosmWasmClient {
throw new Error("Received ill-formatted txhash. Must be non-empty upper-case hex");
}
if (result.code) {
throw new Error(
`Error when posting tx ${result.txhash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.raw_log}`,
);
}
return {
logs: result.logs ? parseLogs(result.logs) : [],
rawLog: result.raw_log || "",
transactionHash: result.txhash,
};
return result.code !== undefined
? {
height: Uint53.fromString(result.height).toNumber(),
transactionHash: result.txhash,
code: result.code,
rawLog: result.raw_log || "",
}
: {
logs: result.logs ? parseLogs(result.logs) : [],
rawLog: result.raw_log || "",
transactionHash: result.txhash,
data: result.data ? fromHex(result.data) : undefined,
};
}
public async getCodes(): Promise<readonly Code[]> {

View File

@ -19,6 +19,7 @@ import {
import { assert, sleep } from "@cosmjs/utils";
import { ReadonlyDate } from "readonly-date";
import { isPostTxFailure } from "./cosmwasmclient";
import { findAttribute, parseLogs } from "./logs";
import {
isMsgInstantiateContract,
@ -400,12 +401,8 @@ describe("RestClient", () => {
signatures: [signature],
};
const transactionId = await client.getIdentifier({ type: "cosmos-sdk/StdTx", value: signedTx });
try {
await client.postTx(signedTx);
} catch (error) {
// postTx() throws on execution failures, which is a questionable design. Ignore for now.
// console.log(error);
}
const result = await client.postTx(signedTx);
assert(isPostTxFailure(result));
unsuccessful = {
sender: alice.address0,
recipient: recipient,
@ -858,7 +855,6 @@ describe("RestClient", () => {
signatures: [signature1, signature2, signature3],
};
const postResult = await client.postTx(signedTx);
// console.log(postResult.raw_log);
expect(postResult.code).toEqual(4);
expect(postResult.raw_log).toContain("wrong number of signers");
});
@ -918,7 +914,6 @@ describe("RestClient", () => {
signatures: [signature1],
};
const postResult = await client.postTx(signedTx);
// console.log(postResult.raw_log);
expect(postResult.code).toBeUndefined();
});
@ -982,7 +977,6 @@ describe("RestClient", () => {
signatures: [signature2, signature1],
};
const postResult = await client.postTx(signedTx);
// console.log(postResult.raw_log);
expect(postResult.code).toBeUndefined();
await sleep(500);
@ -1051,7 +1045,6 @@ describe("RestClient", () => {
signatures: [signature2, signature1],
};
const postResult = await client.postTx(signedTx);
// console.log(postResult.raw_log);
expect(postResult.code).toEqual(8);
});
@ -1115,7 +1108,6 @@ describe("RestClient", () => {
signatures: [signature1, signature2],
};
const postResult = await client.postTx(signedTx);
// console.log(postResult.raw_log);
expect(postResult.code).toEqual(8);
});

View File

@ -3,7 +3,7 @@ import { toHex } from "@cosmjs/encoding";
import { Coin, Secp256k1Pen } from "@cosmjs/sdk38";
import { assert } from "@cosmjs/utils";
import { PrivateCosmWasmClient } from "./cosmwasmclient";
import { isPostTxFailure, PrivateCosmWasmClient } from "./cosmwasmclient";
import { RestClient } from "./restclient";
import { SigningCosmWasmClient, UploadMeta } from "./signingcosmwasmclient";
import { alice, getHackatom, makeRandomAddress, pendingWithoutWasmd } from "./testutils.spec";
@ -205,6 +205,7 @@ describe("SigningCosmWasmClient", () => {
// send
const result = await client.sendTokens(beneficiaryAddress, transferAmount, "for dinner");
assert(!isPostTxFailure(result));
const [firstLog] = result.logs;
expect(firstLog).toBeTruthy();

View File

@ -4,7 +4,14 @@ import { BroadcastMode, Coin, coins, makeSignBytes, MsgSend, StdFee, StdSignatur
import pako from "pako";
import { isValidBuilder } from "./builder";
import { Account, CosmWasmClient, GetNonceResult, PostTxResult } from "./cosmwasmclient";
import {
Account,
CosmWasmClient,
GetNonceResult,
isPostTxFailure,
PostTxFailure,
PostTxResult,
} from "./cosmwasmclient";
import { findAttribute, Log } from "./logs";
import { MsgExecuteContract, MsgInstantiateContract, MsgStoreCode } from "./msgs";
@ -84,6 +91,10 @@ export interface ExecuteResult {
readonly transactionHash: string;
}
function createPostTxErrorMessage(result: PostTxFailure): string {
return `Error when posting tx ${result.transactionHash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.rawLog}`;
}
export class SigningCosmWasmClient extends CosmWasmClient {
public readonly senderAddress: string;
@ -154,6 +165,9 @@ export class SigningCosmWasmClient extends CosmWasmClient {
};
const result = await this.postTx(signedTx);
if (isPostTxFailure(result)) {
throw new Error(createPostTxErrorMessage(result));
}
const codeIdAttr = findAttribute(result.logs, "message", "code_id");
return {
originalSize: wasmCode.length,
@ -200,6 +214,9 @@ export class SigningCosmWasmClient extends CosmWasmClient {
};
const result = await this.postTx(signedTx);
if (isPostTxFailure(result)) {
throw new Error(createPostTxErrorMessage(result));
}
const contractAddressAttr = findAttribute(result.logs, "message", "contract_address");
return {
contractAddress: contractAddressAttr.value,
@ -237,6 +254,9 @@ export class SigningCosmWasmClient extends CosmWasmClient {
};
const result = await this.postTx(signedTx);
if (isPostTxFailure(result)) {
throw new Error(createPostTxErrorMessage(result));
}
return {
logs: result.logs,
transactionHash: result.transactionHash,

View File

@ -14,12 +14,22 @@ export interface Account {
readonly accountNumber: number;
readonly sequence: number;
}
export interface PostTxResult {
export interface PostTxFailure {
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
readonly height: number;
readonly code: number;
readonly rawLog: string;
}
export interface PostTxSuccess {
readonly logs: readonly Log[];
readonly rawLog: string;
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
readonly data?: Uint8Array;
}
export declare type PostTxResult = PostTxSuccess | PostTxFailure;
export declare function isPostTxFailure(postTxResult: PostTxResult): postTxResult is PostTxFailure;
export interface SearchByIdQuery {
readonly id: string;
}

View File

@ -1,9 +1,8 @@
/* eslint-disable @typescript-eslint/camelcase */
import { Uint53 } from "@cosmjs/math";
import { assert, sleep } from "@cosmjs/utils";
import { Coin } from "./coins";
import { CosmosClient } from "./cosmosclient";
import { CosmosClient, isPostTxFailure } from "./cosmosclient";
import { makeSignBytes } from "./encoding";
import { Secp256k1Pen } from "./pen";
import { RestClient } from "./restclient";
@ -105,19 +104,13 @@ describe("CosmosClient.searchTx", () => {
},
};
const transactionId = await client.getIdentifier(tx);
try {
await client.postTx(tx.value);
} catch (error) {
// postTx() throws on execution failures, which is a questionable design. Ignore for now.
// console.log(error);
const errorMessage: string = error.toString();
const [_, heightMatch] = errorMessage.match(/at height ([0-9]+)/) || ["", ""];
const result = await client.postTx(tx.value);
if (isPostTxFailure(result)) {
sendUnsuccessful = {
sender: faucet.address,
recipient: recipient,
hash: transactionId,
height: Uint53.fromString(heightMatch).toNumber(),
height: result.height,
tx: tx,
};
}

View File

@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/camelcase */
import { sleep } from "@cosmjs/utils";
import { assert, sleep } from "@cosmjs/utils";
import { ReadonlyDate } from "readonly-date";
import { CosmosClient, PrivateCosmWasmClient } from "./cosmosclient";
import { CosmosClient, isPostTxFailure, PrivateCosmWasmClient } from "./cosmosclient";
import { makeSignBytes } from "./encoding";
import { findAttribute } from "./logs";
import { Secp256k1Pen } from "./pen";
@ -230,7 +230,9 @@ describe("CosmosClient", () => {
memo: memo,
signatures: [signature],
};
const { logs, transactionHash } = await client.postTx(signedTx);
const txResult = await client.postTx(signedTx);
assert(!isPostTxFailure(txResult));
const { logs, transactionHash } = txResult;
const amountAttr = findAttribute(logs, "transfer", "amount");
expect(amountAttr.value).toEqual("1234567ucosm");
expect(transactionHash).toMatch(/^[0-9A-F]{64}$/);

View File

@ -1,5 +1,6 @@
import { Sha256 } from "@cosmjs/crypto";
import { fromBase64, toHex } from "@cosmjs/encoding";
import { fromBase64, fromHex, toHex } from "@cosmjs/encoding";
import { Uint53 } from "@cosmjs/math";
import { Coin } from "./coins";
import { Log, parseLogs } from "./logs";
@ -21,11 +22,26 @@ export interface Account {
readonly sequence: number;
}
export interface PostTxResult {
export interface PostTxFailure {
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
readonly height: number;
readonly code: number;
readonly rawLog: string;
}
export interface PostTxSuccess {
readonly logs: readonly Log[];
readonly rawLog: string;
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
readonly data?: Uint8Array;
}
export type PostTxResult = PostTxSuccess | PostTxFailure;
export function isPostTxFailure(postTxResult: PostTxResult): postTxResult is PostTxFailure {
return !!(postTxResult as PostTxFailure).code;
}
export interface SearchByIdQuery {
@ -276,17 +292,19 @@ export class CosmosClient {
throw new Error("Received ill-formatted txhash. Must be non-empty upper-case hex");
}
if (result.code) {
throw new Error(
`Error when posting tx ${result.txhash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.raw_log}`,
);
}
return {
logs: result.logs ? parseLogs(result.logs) : [],
rawLog: result.raw_log || "",
transactionHash: result.txhash,
};
return result.code !== undefined
? {
height: Uint53.fromString(result.height).toNumber(),
transactionHash: result.txhash,
code: result.code,
rawLog: result.raw_log || "",
}
: {
logs: result.logs ? parseLogs(result.logs) : [],
rawLog: result.raw_log || "",
transactionHash: result.txhash,
data: result.data ? fromHex(result.data) : undefined,
};
}
private async txsQuery(query: string): Promise<readonly IndexedTx[]> {

View File

@ -4,6 +4,7 @@ import { assert, sleep } from "@cosmjs/utils";
import { ReadonlyDate } from "readonly-date";
import { rawSecp256k1PubkeyToAddress } from "./address";
import { isPostTxFailure } from "./cosmosclient";
import { makeSignBytes } from "./encoding";
import { parseLogs } from "./logs";
import { makeCosmoshubPath, Secp256k1Pen } from "./pen";
@ -275,12 +276,8 @@ describe("RestClient", () => {
signatures: [signature],
};
const transactionId = await client.getIdentifier({ type: "cosmos-sdk/StdTx", value: signedTx });
try {
await client.postTx(signedTx);
} catch (error) {
// postTx() throws on execution failures, which is a questionable design. Ignore for now.
// console.log(error);
}
const result = await client.postTx(signedTx);
assert(isPostTxFailure(result));
unsuccessful = {
sender: faucet.address,
recipient: recipient,
@ -631,7 +628,6 @@ describe("RestClient", () => {
signatures: [signature1, signature2, signature3],
};
const postResult = await client.postTx(signedTx);
// console.log(postResult.raw_log);
expect(postResult.code).toEqual(4);
expect(postResult.raw_log).toContain("wrong number of signers");
});
@ -691,7 +687,6 @@ describe("RestClient", () => {
signatures: [signature1],
};
const postResult = await client.postTx(signedTx);
// console.log(postResult.raw_log);
expect(postResult.code).toBeUndefined();
});
@ -755,7 +750,6 @@ describe("RestClient", () => {
signatures: [signature2, signature1],
};
const postResult = await client.postTx(signedTx);
// console.log(postResult.raw_log);
expect(postResult.code).toBeUndefined();
await sleep(500);
@ -824,7 +818,6 @@ describe("RestClient", () => {
signatures: [signature2, signature1],
};
const postResult = await client.postTx(signedTx);
// console.log(postResult.raw_log);
expect(postResult.code).toEqual(8);
});
@ -888,7 +881,6 @@ describe("RestClient", () => {
signatures: [signature1, signature2],
};
const postResult = await client.postTx(signedTx);
// console.log(postResult.raw_log);
expect(postResult.code).toEqual(8);
});
});

View File

@ -1,7 +1,7 @@
import { assert } from "@cosmjs/utils";
import { Coin } from "./coins";
import { PrivateCosmWasmClient } from "./cosmosclient";
import { isPostTxFailure, PrivateCosmWasmClient } from "./cosmosclient";
import { Secp256k1Pen } from "./pen";
import { SigningCosmosClient } from "./signingcosmosclient";
import { makeRandomAddress, pendingWithoutWasmd } from "./testutils.spec";
@ -66,6 +66,7 @@ describe("SigningCosmosClient", () => {
// send
const result = await client.sendTokens(beneficiaryAddress, transferAmount, "for dinner");
assert(!isPostTxFailure(result));
const [firstLog] = result.logs;
expect(firstLog).toBeTruthy();

View File

@ -14,12 +14,22 @@ export interface Account {
readonly accountNumber: number;
readonly sequence: number;
}
export interface PostTxResult {
export interface PostTxFailure {
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
readonly height: number;
readonly code: number;
readonly rawLog: string;
}
export interface PostTxSuccess {
readonly logs: readonly Log[];
readonly rawLog: string;
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
readonly data?: Uint8Array;
}
export declare type PostTxResult = PostTxSuccess | PostTxFailure;
export declare function isPostTxFailure(postTxResult: PostTxResult): postTxResult is PostTxFailure;
export interface SearchByIdQuery {
readonly id: string;
}