Skip to content

Commit

Permalink
feat: Improve transaction error logs (#443)
Browse files Browse the repository at this point in the history
* feat: allow hiding error logs

* misc utils

* adds formatInstructionLogs to solana-contrib

* simulation error stringify cleanup
  • Loading branch information
macalinao committed Jan 5, 2022
1 parent 900ea18 commit f967790
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 20 deletions.
6 changes: 3 additions & 3 deletions packages/chai-solana/src/expectTXTable.ts
@@ -1,7 +1,7 @@
import type { TransactionEnvelope } from "@saberhq/solana-contrib";
import { parseTransactionLogs, printTXTable } from "@saberhq/solana-contrib";

import { formatInstructionLogs } from "./printInstructionLogs";
import { formatInstructionLogsForConsole } from "./printInstructionLogs";
import { expectTX } from "./utils";

/**
Expand Down Expand Up @@ -92,7 +92,7 @@ export const expectTXTable = (
) {
if (formatLogs) {
const parsed = parseTransactionLogs(logs, simulation.value.err);
const fmt = formatInstructionLogs(parsed);
const fmt = formatInstructionLogsForConsole(parsed);
console.log(fmt);
} else {
console.log(logs.join("\n"));
Expand Down Expand Up @@ -120,7 +120,7 @@ export const expectTXTable = (
lastLine = curLine;
}
}
console.log(" ", simulation.value.err);
console.log(" ", JSON.stringify(simulation.value.err, null, 2));
}
}
})
Expand Down
15 changes: 13 additions & 2 deletions packages/chai-solana/src/printInstructionLogs.ts
@@ -1,12 +1,13 @@
import type { InstructionLogs } from "@saberhq/solana-contrib";
import { formatLogEntry } from "@saberhq/solana-contrib";
import { formatLogEntry, parseTransactionLogs } from "@saberhq/solana-contrib";
import type { SendTransactionError } from "@solana/web3.js";
import colors from "colors/safe";

/**
* Formats instruction logs to be printed to the console.
* @param logs
*/
export const formatInstructionLogs = (
export const formatInstructionLogsForConsole = (
logs: readonly InstructionLogs[]
): string =>
logs
Expand Down Expand Up @@ -39,3 +40,13 @@ export const formatInstructionLogs = (
].join("\n");
})
.join("\n");

export const printSendTransactionError = (err: SendTransactionError) => {
try {
const parsed = parseTransactionLogs(err.logs ?? null, err);
console.error(formatInstructionLogsForConsole(parsed));
} catch (e) {
console.error(`Could not parse transaction error`, e);
console.error("SendTransactionError", err);
}
};
11 changes: 10 additions & 1 deletion packages/chai-solana/src/utils.ts
Expand Up @@ -6,8 +6,11 @@ import type {
TransactionReceipt,
} from "@saberhq/solana-contrib";
import { PendingTransaction } from "@saberhq/solana-contrib";
import { SendTransactionError } from "@solana/web3.js";
import { assert, expect } from "chai";

import { printSendTransactionError } from "./printInstructionLogs";

export const expectTX = (
tx:
| TransactionEnvelope
Expand Down Expand Up @@ -44,7 +47,13 @@ export const expectTX = (
} else {
return expect(
tx
?.send()
?.send({ printLogs: false })
.catch((err) => {
if (err instanceof SendTransactionError) {
printSendTransactionError(err);
}
throw err;
})
.then((res) => res.wait())
.then(handleReceipt),
msg
Expand Down
34 changes: 28 additions & 6 deletions packages/solana-contrib/src/broadcaster.ts
Expand Up @@ -9,9 +9,20 @@ import type {
} from "@solana/web3.js";

import type { Broadcaster } from ".";
import { DEFAULT_PROVIDER_OPTIONS, PendingTransaction } from ".";
import {
DEFAULT_PROVIDER_OPTIONS,
PendingTransaction,
suppressConsoleErrorAsync,
} from ".";
import { simulateTransactionWithCommitment } from "./utils/simulateTransactionWithCommitment";

export interface BroadcastOptions extends ConfirmOptions {
/**
* Prints the transaction logs as emitted by @solana/web3.js. Defaults to true.
*/
printLogs?: boolean;
}

/**
* Broadcasts transactions to a single connection.
*/
Expand All @@ -38,16 +49,27 @@ export class SingleConnectionBroadcaster implements Broadcaster {
*/
async broadcast(
tx: Transaction,
opts: ConfirmOptions = this.opts
{ printLogs = true, ...opts }: BroadcastOptions = this.opts
): Promise<PendingTransaction> {
if (tx.signatures.length === 0) {
throw new Error("Transaction must be signed before broadcasting.");
}
const rawTx = tx.serialize();
return new PendingTransaction(
this.sendConnection,
await this.sendConnection.sendRawTransaction(rawTx, opts)
);

if (printLogs) {
return new PendingTransaction(
this.sendConnection,
await this.sendConnection.sendRawTransaction(rawTx, opts)
);
}

return await suppressConsoleErrorAsync(async () => {
// hide the logs of TX errors if printLogs = false
return new PendingTransaction(
this.sendConnection,
await this.sendConnection.sendRawTransaction(rawTx, opts)
);
});
}

/**
Expand Down
Expand Up @@ -9,6 +9,7 @@ import type {
} from "@solana/web3.js";
import { Transaction } from "@solana/web3.js";

import type { BroadcastOptions } from "..";
import { printTXTable } from "..";
import type { Provider } from "../interfaces";
import type { PendingTransaction } from "./PendingTransaction";
Expand Down Expand Up @@ -142,7 +143,7 @@ export class TransactionEnvelope {
* @param opts
* @returns
*/
async send(opts?: ConfirmOptions): Promise<PendingTransaction> {
async send(opts?: BroadcastOptions): Promise<PendingTransaction> {
const signed = await this.provider.signer.sign(
this.build(),
this.signers,
Expand All @@ -155,7 +156,7 @@ export class TransactionEnvelope {
* Sends the transaction and waits for confirmation.
* @param opts
*/
async confirm(opts?: ConfirmOptions): Promise<TransactionReceipt> {
async confirm(opts?: BroadcastOptions): Promise<TransactionReceipt> {
return (await this.send(opts)).wait();
}

Expand Down
18 changes: 18 additions & 0 deletions packages/solana-contrib/src/transaction/parseTransactionLogs.ts
Expand Up @@ -206,3 +206,21 @@ export const formatLogEntry = (
const prefixString = prefix ? buildPrefix(entry.depth) : "";
return `${prefixString}${formatLogEntryString(entry)}`;
};

/**
* Formats instruction logs.
* @param logs
*/
export const formatInstructionLogs = (
logs: readonly InstructionLogs[]
): string =>
logs
.map((log, i) => {
return [
`=> Instruction #${i}: ${
log.programAddress ? `Program ${log.programAddress}` : "System"
}`,
...log.logs.map((entry) => formatLogEntry(entry, true)),
].join("\n");
})
.join("\n");
1 change: 1 addition & 0 deletions packages/solana-contrib/src/utils/index.ts
@@ -1,4 +1,5 @@
export * from "./instructions";
export * from "./misc";
export * from "./printAccountOwners";
export * from "./printTXTable";
export * from "./publicKey";
Expand Down
18 changes: 18 additions & 0 deletions packages/solana-contrib/src/utils/misc.ts
@@ -0,0 +1,18 @@
/**
* Hide the console.error because @solana/web3.js often emits noisy errors as a
* side effect. There are use cases of estimateTransactionSize where we
* frequently build transactions that are likely too big.
*/
export const suppressConsoleErrorAsync = async <T>(
fn: () => Promise<T>
): Promise<T> => {
const oldConsoleError = console.error;
try {
const result = await fn();
console.error = oldConsoleError;
return result;
} catch (e) {
console.error = oldConsoleError;
throw e;
}
};
7 changes: 2 additions & 5 deletions packages/solana-contrib/src/utils/printTXTable.ts
Expand Up @@ -184,14 +184,11 @@ const instructionsSize = (

return estimateTransactionSize(instructionedTx);
};

let fakeSigner: Signer | undefined = undefined;
const getFakeSigner = (): Signer => {
if (!fakeSigner) {
const fakeSignerKp = Keypair.generate();
fakeSigner = {
publicKey: fakeSignerKp.publicKey,
secretKey: fakeSignerKp.secretKey,
};
fakeSigner = Keypair.generate();
}
return fakeSigner;
};
Expand Up @@ -5,6 +5,7 @@ import type {
SimulatedTransactionResponse,
Transaction,
} from "@solana/web3.js";
import { SendTransactionError } from "@solana/web3.js";

/**
* Copy of Connection.simulateTransaction that takes a commitment parameter.
Expand Down Expand Up @@ -50,7 +51,10 @@ export async function simulateTransactionWithCommitment(
config,
]);
if (res.error) {
throw new Error("failed to simulate transaction: " + res.error.message);
throw new SendTransactionError(
"failed to simulate transaction: " + res.error.message,
res.result.value.logs ?? undefined
);
}
return res.result;
}

0 comments on commit f967790

Please sign in to comment.