Skip to content

Commit b6e7815

Browse files
committed
tx details debug endpoint
1 parent 29138a8 commit b6e7815

File tree

6 files changed

+153
-51
lines changed

6 files changed

+153
-51
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import { Static, Type } from "@sinclair/typebox";
2+
import { Queue } from "bullmq";
3+
import { FastifyInstance } from "fastify";
4+
import { StatusCodes } from "http-status-codes";
5+
import { stringify } from "thirdweb/utils";
6+
import { TransactionDB } from "../../../db/transactions/db";
7+
import { getConfig } from "../../../utils/cache/getConfig";
8+
import { maybeDate } from "../../../utils/primitiveTypes";
9+
import { redis } from "../../../utils/redis/redis";
10+
import { MineTransactionQueue } from "../../../worker/queues/mineTransactionQueue";
11+
import { SendTransactionQueue } from "../../../worker/queues/sendTransactionQueue";
12+
import { createCustomError } from "../../middleware/error";
13+
import { standardResponseSchema } from "../../schemas/sharedApiSchemas";
14+
15+
const requestSchema = Type.Object({
16+
queueId: Type.String({
17+
description: "Transaction queue ID",
18+
examples: ["9eb88b00-f04f-409b-9df7-7dcc9003bc35"],
19+
}),
20+
});
21+
22+
const jobSchema = Type.Object({
23+
queue: Type.String(),
24+
jobId: Type.String(),
25+
timestamp: Type.String(),
26+
processedOn: Type.Optional(Type.String()),
27+
finishedOn: Type.Optional(Type.String()),
28+
lines: Type.Array(Type.String()),
29+
});
30+
31+
export const responseBodySchema = Type.Object({
32+
result: Type.Object({
33+
raw: Type.Any(),
34+
jobs: Type.Array(jobSchema),
35+
}),
36+
});
37+
38+
responseBodySchema.example = {
39+
result: {
40+
raw: {
41+
queueId: "9eb88b00-f04f-409b-9df7-7dcc9003bc35",
42+
},
43+
logs: ["Log line 1", "Log line 2"],
44+
},
45+
};
46+
47+
export async function getTransactionDetails(fastify: FastifyInstance) {
48+
fastify.route<{
49+
Params: Static<typeof requestSchema>;
50+
Reply: Static<typeof responseBodySchema>;
51+
}>({
52+
method: "GET",
53+
url: "/admin/transaction-details/:queueId",
54+
schema: {
55+
summary: "Get transaction details",
56+
description: "Get raw logs and details for a transaction by queueId.",
57+
tags: ["Admin"],
58+
operationId: "transactionDetails",
59+
params: requestSchema,
60+
response: {
61+
...standardResponseSchema,
62+
[StatusCodes.OK]: responseBodySchema,
63+
},
64+
hide: true,
65+
},
66+
handler: async (request, reply) => {
67+
const { queueId } = request.params;
68+
69+
const transaction = await TransactionDB.get(queueId);
70+
if (!transaction) {
71+
throw createCustomError(
72+
"Transaction not found.",
73+
StatusCodes.BAD_REQUEST,
74+
"TRANSACTION_NOT_FOUND",
75+
);
76+
}
77+
78+
const config = await getConfig();
79+
const jobs: Static<typeof jobSchema>[] = [];
80+
81+
// SentTransaction jobs.
82+
for (
83+
let resendCount = 0;
84+
resendCount < config.maxRetriesPerTx;
85+
resendCount++
86+
) {
87+
const jobDetails = await getJobDetails({
88+
queue: SendTransactionQueue.q,
89+
jobId: SendTransactionQueue.jobId({ queueId, resendCount }),
90+
});
91+
if (jobDetails) {
92+
jobs.push(jobDetails);
93+
}
94+
}
95+
96+
// MineTransaction job.
97+
const jobDetails = await getJobDetails({
98+
queue: MineTransactionQueue.q,
99+
jobId: MineTransactionQueue.jobId({ queueId }),
100+
});
101+
if (jobDetails) {
102+
jobs.push(jobDetails);
103+
}
104+
105+
reply.status(StatusCodes.OK).send({
106+
result: {
107+
raw: JSON.parse(stringify(transaction)),
108+
jobs,
109+
},
110+
});
111+
},
112+
});
113+
}
114+
115+
const getJobDetails = async (args: {
116+
queue: Queue;
117+
jobId: string;
118+
}): Promise<Static<typeof jobSchema> | null> => {
119+
console.log("[DEBUG] args", args);
120+
const { queue, jobId } = args;
121+
const job = await queue.getJob(jobId);
122+
if (!job) {
123+
return null;
124+
}
125+
126+
const key = `bull:${queue.name}:${jobId}:logs`;
127+
const lines = await redis.lrange(key, 0, -1);
128+
return {
129+
queue: queue.name,
130+
jobId,
131+
timestamp: maybeDate(job.timestamp).toISOString(),
132+
processedOn: maybeDate(job.processedOn)?.toISOString(),
133+
finishedOn: maybeDate(job.finishedOn)?.toISOString(),
134+
lines,
135+
};
136+
};

src/server/routes/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@ import { getTxHashReceipt } from "./transaction/blockchain/getTxReceipt";
120120
import { getUserOpReceipt } from "./transaction/blockchain/getUserOpReceipt";
121121
import { sendSignedTransaction } from "./transaction/blockchain/sendSignedTx";
122122
import { sendSignedUserOp } from "./transaction/blockchain/sendSignedUserOp";
123-
import { checkGroupStatus } from "./transaction/group";
124123

125124
// Indexer
125+
import { getTransactionDetails } from "./admin/transaction";
126126
import { setUrlsToCorsConfiguration } from "./configuration/cors/set";
127127
import { getIpAllowlist } from "./configuration/ip/get";
128128
import { setIpAllowlist } from "./configuration/ip/set";
@@ -246,7 +246,6 @@ export const withRoutes = async (fastify: FastifyInstance) => {
246246
await fastify.register(checkTxStatus);
247247
await fastify.register(getAllTx);
248248
await fastify.register(getAllDeployedContracts);
249-
await fastify.register(checkGroupStatus);
250249
await fastify.register(retryTransaction);
251250
await fastify.register(syncRetryTransaction);
252251
await fastify.register(cancelTransaction);
@@ -287,4 +286,7 @@ export const withRoutes = async (fastify: FastifyInstance) => {
287286
await fastify.register(getContractEventLogs);
288287
await fastify.register(getEventLogs);
289288
await fastify.register(pageEventLogs);
289+
290+
// Admin
291+
await fastify.register(getTransactionDetails);
290292
};

src/server/routes/transaction/group.ts

Lines changed: 0 additions & 42 deletions
This file was deleted.

src/utils/primitiveTypes.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@ export const maybeBigInt = (val?: string) => (val ? BigInt(val) : undefined);
44

55
// These overloads hint TS at the response type (ex: Address if `val` is Address).
66
export function normalizeAddress(val: Address): Address;
7-
export function normalizeAddress(val: Address | undefined): Address | undefined;
7+
export function normalizeAddress(val?: Address): Address | undefined;
88
export function normalizeAddress(val: string): Address;
9-
export function normalizeAddress(val: string | undefined): Address | undefined;
9+
export function normalizeAddress(val?: string): Address | undefined;
1010
export function normalizeAddress(val: string | null): undefined;
1111
export function normalizeAddress(
1212
val?: string | Address | null,
1313
): Address | undefined {
1414
return val ? (val.toLowerCase() as Address) : undefined;
1515
}
16+
17+
export function maybeDate(val: number): Date;
18+
export function maybeDate(val?: number): Date | undefined;
19+
export function maybeDate(val?: number): Date | undefined {
20+
return val ? new Date(val) : undefined;
21+
}

src/worker/queues/mineTransactionQueue.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ export class MineTransactionQueue {
2424

2525
// There must be a worker to poll the result for every transaction hash,
2626
// even for the same queueId. This handles if any retried transactions succeed.
27-
private static _jobId = (data: MineTransactionData) => data.queueId;
27+
static jobId = (data: MineTransactionData) => data.queueId;
2828

2929
static add = async (data: MineTransactionData) => {
3030
const serialized = superjson.stringify(data);
31-
const jobId = this._jobId(data);
31+
const jobId = this.jobId(data);
3232
await this.q.add(jobId, serialized, { jobId });
3333
};
3434

src/worker/queues/sendTransactionQueue.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,18 @@ export class SendTransactionQueue {
1818
});
1919

2020
// Allow enqueing the same queueId for multiple retries.
21-
private static _jobId = (data: SendTransactionData) =>
21+
static jobId = (data: SendTransactionData) =>
2222
`${data.queueId}:${data.resendCount}`;
2323

2424
static add = async (data: SendTransactionData) => {
2525
const serialized = superjson.stringify(data);
26-
const jobId = this._jobId(data);
26+
const jobId = this.jobId(data);
2727
await this.q.add(jobId, serialized, { jobId });
2828
};
2929

3030
static remove = async (data: SendTransactionData) => {
3131
try {
32-
await this.q.remove(this._jobId(data));
32+
await this.q.remove(this.jobId(data));
3333
} catch (e) {
3434
// Job is currently running.
3535
}

0 commit comments

Comments
 (0)