Skip to content

Commit

Permalink
fix: indexing error reporting
Browse files Browse the repository at this point in the history
  • Loading branch information
typedarray committed Jan 22, 2025
1 parent 178bcbf commit 21d62f0
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 61 deletions.
5 changes: 5 additions & 0 deletions .changeset/fluffy-pumas-wink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"ponder": patch
---

Fixed a bug where indexing errors would sometimes be obfuscated by an internal error like `Cannot read properties of undefined (reading 'hash')`.
75 changes: 75 additions & 0 deletions packages/core/src/indexing/service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,81 @@ test("processEvents() error", async (context) => {
await cleanup();
});

test("processEvents() error with missing event object properties", async (context) => {
const { common } = context;
const { syncStore, indexingStore, cleanup } = await setupDatabaseServices(
context,
{ schema },
);

const sync = await createSync({
common,
syncStore,
networks,
sources,
onRealtimeEvent: () => Promise.resolve(),
onFatalError: () => {},
initialCheckpoint: encodeCheckpoint(zeroCheckpoint),
});

const throwError = async ({ event }: { event: any; context: Context }) => {
// biome-ignore lint/performance/noDelete: <explanation>
delete event.transaction;
throw new Error("empty transaction");
};

const indexingFunctions = {
"Erc20:Transfer(address indexed from, address indexed to, uint256 amount)":
throwError,
};

const indexingService = create({
indexingFunctions,
common,
sources,
networks,
sync,
});

setIndexingStore(indexingService, indexingStore);

const topics = encodeEventTopics({
abi: erc20ABI,
eventName: "Transfer",
args: {
from: zeroAddress,
to: ALICE,
},
});

const data = padHex(toHex(parseEther("1")), { size: 32 });

const rawEvent = {
chainId: 1,
sourceIndex: 0,
checkpoint: encodeCheckpoint(zeroCheckpoint),
block: {} as RawEvent["block"],
transaction: {} as RawEvent["transaction"],
log: {
id: "test",
data,
topics,
},
} as RawEvent;

const events = decodeEvents(common, sources, [rawEvent]);
const result = await processEvents(indexingService, { events });

expect(result).toMatchInlineSnapshot(`
{
"error": [Error: empty transaction],
"status": "error",
}
`);

await cleanup();
});

test("execute() error after killed", async (context) => {
const { common } = context;
const { syncStore, indexingStore, cleanup } = await setupDatabaseServices(
Expand Down
131 changes: 71 additions & 60 deletions packages/core/src/indexing/service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { IndexingFunctions } from "@/build/configAndIndexingFunctions.js";
import type { Common } from "@/common/common.js";
import { BaseError } from "@/common/errors.js";
import type { Network } from "@/config/networks.js";
import type { Schema } from "@/drizzle/index.js";
import type { IndexingStore } from "@/indexing-store/index.js";
Expand All @@ -12,6 +11,7 @@ import {
} from "@/sync/source.js";
import type { Db } from "@/types/db.js";
import type { Block, Log, Trace, Transaction } from "@/types/eth.js";
import type { DeepPartial } from "@/types/utils.js";
import {
type Checkpoint,
decodeCheckpoint,
Expand Down Expand Up @@ -385,13 +385,7 @@ const executeSetup = async (
const error = _error instanceof Error ? _error : new Error(String(_error));

addStackTrace(error, common.options);

if (error instanceof BaseError) {
error.meta.push(toErrorMeta(event));
} else {
// @ts-expect-error
error.meta = [toErrorMeta(event)];
}
addErrorMeta(error, toErrorMeta(event));

const decodedCheckpoint = decodeCheckpoint(event.checkpoint);
common.logger.error({
Expand Down Expand Up @@ -451,13 +445,7 @@ const executeEvent = async (
const error = _error instanceof Error ? _error : new Error(String(_error));

addStackTrace(error, common.options);

if (error instanceof BaseError) {
error.meta.push(toErrorMeta(event));
} else {
// @ts-expect-error
error.meta = [toErrorMeta(event)];
}
addErrorMeta(error, toErrorMeta(event));

const decodedCheckpoint = decodeCheckpoint(event.checkpoint);

Expand All @@ -475,77 +463,100 @@ const executeEvent = async (
return { status: "success" };
};

const blockText = (block: Block) =>
`Block:\n${prettyPrint({
hash: block.hash,
number: block.number,
timestamp: block.timestamp,
})}`;

const transactionText = (transaction: Transaction) =>
`Transaction:\n${prettyPrint({
hash: transaction.hash,
from: transaction.from,
to: transaction.to,
})}`;

const logText = (log: Log) =>
`Log:\n${prettyPrint({
index: log.logIndex,
address: log.address,
})}`;

const traceText = (trace: Trace) =>
`Trace:\n${prettyPrint({
traceIndex: trace.traceIndex,
from: trace.from,
to: trace.to,
})}`;

const toErrorMeta = (event: Event | SetupEvent) => {
switch (event.type) {
const toErrorMeta = (event: DeepPartial<Event> | DeepPartial<SetupEvent>) => {
switch (event?.type) {
case "setup": {
return `Block:\n${prettyPrint({
number: event.block,
number: event?.block,
})}`;
}

case "log": {
return [
`Event arguments:\n${prettyPrint(event.event.args)}`,
logText(event.event.log),
transactionText(event.event.transaction),
blockText(event.event.block),
`Event arguments:\n${prettyPrint(event?.event?.args)}`,
logText(event?.event?.log),
transactionText(event?.event?.transaction),
blockText(event?.event?.block),
].join("\n");
}

case "trace": {
return [
`Call trace arguments:\n${prettyPrint(event.event.args)}`,
traceText(event.event.trace),
transactionText(event.event.transaction),
blockText(event.event.block),
`Call trace arguments:\n${prettyPrint(event?.event?.args)}`,
traceText(event?.event?.trace),
transactionText(event?.event?.transaction),
blockText(event?.event?.block),
].join("\n");
}

case "transfer": {
return [
`Transfer arguments:\n${prettyPrint(event.event.transfer)}`,
traceText(event.event.trace),
transactionText(event.event.transaction),
blockText(event.event.block),
`Transfer arguments:\n${prettyPrint(event?.event?.transfer)}`,
traceText(event?.event?.trace),
transactionText(event?.event?.transaction),
blockText(event?.event?.block),
].join("\n");
}

case "block": {
return blockText(event.event.block);
return blockText(event?.event?.block);
}

case "transaction": {
return [
transactionText(event.event.transaction),
blockText(event.event.block),
transactionText(event?.event?.transaction),
blockText(event?.event?.block),
].join("\n");
}

default: {
return undefined;
}
}
};

const addErrorMeta = (error: unknown, meta: string | undefined) => {
// If error isn't an object we can modify, do nothing
if (typeof error !== "object" || error === null) return;
if (meta === undefined) return;

try {
const errorObj = error as { meta?: unknown };
// If meta exists and is an array, try to add to it
if (Array.isArray(errorObj.meta)) {
errorObj.meta = [...errorObj.meta, meta];
} else {
// Otherwise set meta to be a new array with the meta string
errorObj.meta = [meta];
}
} catch {
// Ignore errors
}
};

const blockText = (block?: DeepPartial<Block>) =>
`Block:\n${prettyPrint({
hash: block?.hash,
number: block?.number,
timestamp: block?.timestamp,
})}`;

const transactionText = (transaction?: DeepPartial<Transaction>) =>
`Transaction:\n${prettyPrint({
hash: transaction?.hash,
from: transaction?.from,
to: transaction?.to,
})}`;

const logText = (log?: DeepPartial<Log>) =>
`Log:\n${prettyPrint({
index: log?.logIndex,
address: log?.address,
})}`;

const traceText = (trace?: DeepPartial<Trace>) =>
`Trace:\n${prettyPrint({
traceIndex: trace?.traceIndex,
from: trace?.from,
to: trace?.to,
})}`;
8 changes: 8 additions & 0 deletions packages/core/src/types/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,11 @@ export type NonNull<T> = {
};

export type PonderTypeError<error extends string> = error;

export type DeepPartial<T> = {
[P in keyof T]?: T[P] extends (infer U)[]
? DeepPartial<U>[]
: T[P] extends object
? DeepPartial<T[P]>
: T[P];
};
4 changes: 3 additions & 1 deletion packages/core/src/utils/print.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
// Adapted from viem.
// https://github.com/wagmi-dev/viem/blob/021ce8e5a3fb02db6139564345a91fc77cba08a6/src/errors/transaction.ts#L6-L19
export function prettyPrint(
args: Record<string, bigint | number | string | undefined | false | unknown>,
args?: Record<string, bigint | number | string | undefined | false | unknown>,
) {
if (args === undefined) return "(undefined)";

const entries = Object.entries(args)
.map(([key, value]) => {
if (value === undefined) return null;
Expand Down

0 comments on commit 21d62f0

Please sign in to comment.