From 24064f1c5f85945b939c72d0eda648e6467976c0 Mon Sep 17 00:00:00 2001 From: Eiman Date: Tue, 24 Mar 2026 12:41:05 -0500 Subject: [PATCH 1/3] fix: check backfill table before Redis so it can override stuck queued transactions Previously the backfill lookup only ran when a transaction was missing from Redis. Orphaned "queued" transactions that still exist in Redis were unreachable by backfill. Moving the check first lets backfill entries act as intentional overrides for stuck queue IDs. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/server/routes/transaction/status.ts | 53 ++++++++++++------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/src/server/routes/transaction/status.ts b/src/server/routes/transaction/status.ts index 91f286ce..497f1adf 100644 --- a/src/server/routes/transaction/status.ts +++ b/src/server/routes/transaction/status.ts @@ -145,22 +145,21 @@ export async function getTransactionStatusRoute(fastify: FastifyInstance) { handler: async (request, reply) => { const { queueId } = request.params; - const transaction = await TransactionDB.get(queueId); - if (!transaction) { - // SPECIAL LOGIC FOR AMEX - // AMEX uses this endpoint to check transaction status for queue IDs they didn't receive webhooks for. - // The queue ID's were cleaned out of Redis so we backfilled tx data to this backfill table. - // See https://github.com/thirdweb-dev/solutions-customer-scripts/blob/main/amex/scripts/load-backfill-via-api.ts - // Fallback to backfill table if enabled and not found - if (env.ENABLE_TX_BACKFILL_FALLBACK) { - const backfill = await TransactionDB.getBackfill(queueId); - if (backfill) { - return reply.status(StatusCodes.OK).send({ - result: createBackfillResponse(queueId, backfill), - }); - } + // SPECIAL LOGIC FOR AMEX + // Backfill table takes priority — entries are intentional overrides for + // queue IDs that are stuck in Redis (e.g. orphaned "queued" transactions). + // See https://github.com/thirdweb-dev/solutions-customer-scripts/blob/main/amex/scripts/load-backfill-via-api.ts + if (env.ENABLE_TX_BACKFILL_FALLBACK) { + const backfill = await TransactionDB.getBackfill(queueId); + if (backfill) { + return reply.status(StatusCodes.OK).send({ + result: createBackfillResponse(queueId, backfill), + }); } + } + const transaction = await TransactionDB.get(queueId); + if (!transaction) { throw createCustomError( "Transaction not found.", StatusCodes.BAD_REQUEST, @@ -206,22 +205,20 @@ export async function getTransactionStatusQueryParamRoute( ); } - const transaction = await TransactionDB.get(queueId); - if (!transaction) { - // SPECIAL LOGIC FOR AMEX - // AMEX uses this endpoint to check transaction status for queue IDs they didn't receive webhooks for. - // The queue ID's were cleaned out of Redis so we backfilled tx data to this backfill table. - // See https://github.com/thirdweb-dev/solutions-customer-scripts/blob/main/amex/scripts/load-backfill-via-api.ts - // Fallback to backfill table if enabled and not found - if (env.ENABLE_TX_BACKFILL_FALLBACK) { - const backfill = await TransactionDB.getBackfill(queueId); - if (backfill) { - return reply.status(StatusCodes.OK).send({ - result: createBackfillResponse(queueId, backfill), - }); - } + // SPECIAL LOGIC FOR AMEX + // Backfill table takes priority — entries are intentional overrides for + // queue IDs that are stuck in Redis (e.g. orphaned "queued" transactions). + if (env.ENABLE_TX_BACKFILL_FALLBACK) { + const backfill = await TransactionDB.getBackfill(queueId); + if (backfill) { + return reply.status(StatusCodes.OK).send({ + result: createBackfillResponse(queueId, backfill), + }); } + } + const transaction = await TransactionDB.get(queueId); + if (!transaction) { throw createCustomError( "Transaction not found.", StatusCodes.BAD_REQUEST, From fc367a5772e9f81db169b56b9dff34f3a94b0dee Mon Sep 17 00:00:00 2001 From: Eiman Date: Tue, 24 Mar 2026 12:50:20 -0500 Subject: [PATCH 2/3] fix: check backfill before Redis in get-logs endpoint too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Same change as status endpoint — backfill entries should take priority over stale Redis data for consistency across all endpoints. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../routes/transaction/blockchain/get-logs.ts | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/server/routes/transaction/blockchain/get-logs.ts b/src/server/routes/transaction/blockchain/get-logs.ts index 9ce20ef5..35e12729 100644 --- a/src/server/routes/transaction/blockchain/get-logs.ts +++ b/src/server/routes/transaction/blockchain/get-logs.ts @@ -155,23 +155,22 @@ export async function getTransactionLogs(fastify: FastifyInstance) { // Get the transaction hash from the provided input. let hash: Hex | undefined; if (queueId) { - // Primary lookup - const transaction = await TransactionDB.get(queueId); - if (transaction?.status === "mined") { - hash = transaction.transactionHash; - } - // SPECIAL LOGIC FOR AMEX - // AMEX uses this endpoint to get logs for transactions they didn't receive webhooks for - // the queue ID's were cleaned out of REDIS so we backfilled tx hashes to this backfill table - // see https://github.com/thirdweb-dev/solutions-customer-scripts/blob/main/amex/scripts/load-backfill-via-api.ts - // Fallback to backfill table if enabled and not found - if (!hash && env.ENABLE_TX_BACKFILL_FALLBACK) { + // Backfill table takes priority — entries are intentional overrides for + // queue IDs that are stuck in Redis (e.g. orphaned "queued" transactions). + if (env.ENABLE_TX_BACKFILL_FALLBACK) { const backfill = await TransactionDB.getBackfill(queueId); if (backfill?.status === "mined" && backfill.transactionHash && isHex(backfill.transactionHash)) { hash = backfill.transactionHash as Hex; } } + + if (!hash) { + const transaction = await TransactionDB.get(queueId); + if (transaction?.status === "mined") { + hash = transaction.transactionHash; + } + } } else if (transactionHash) { hash = transactionHash as Hex; } From be510beb2684abaae616ed6e28fb74b762539ca3 Mon Sep 17 00:00:00 2001 From: Eiman Date: Tue, 24 Mar 2026 12:59:36 -0500 Subject: [PATCH 3/3] fix: treat backfill entry as authoritative in get-logs endpoint When a backfill entry exists (even if errored), skip the Redis lookup entirely. Previously an errored backfill entry would fall through to Redis, potentially returning stale data for an orphaned transaction. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../routes/transaction/blockchain/get-logs.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/server/routes/transaction/blockchain/get-logs.ts b/src/server/routes/transaction/blockchain/get-logs.ts index 35e12729..5ec55967 100644 --- a/src/server/routes/transaction/blockchain/get-logs.ts +++ b/src/server/routes/transaction/blockchain/get-logs.ts @@ -160,12 +160,20 @@ export async function getTransactionLogs(fastify: FastifyInstance) { // queue IDs that are stuck in Redis (e.g. orphaned "queued" transactions). if (env.ENABLE_TX_BACKFILL_FALLBACK) { const backfill = await TransactionDB.getBackfill(queueId); - if (backfill?.status === "mined" && backfill.transactionHash && isHex(backfill.transactionHash)) { - hash = backfill.transactionHash as Hex; + if (backfill) { + // Backfill entry exists and is authoritative — only set hash if mined. + // If backfill is errored, hash stays undefined and we skip Redis lookup. + if (backfill.status === "mined" && backfill.transactionHash && isHex(backfill.transactionHash)) { + hash = backfill.transactionHash as Hex; + } + } else { + // No backfill entry — fall back to Redis. + const transaction = await TransactionDB.get(queueId); + if (transaction?.status === "mined") { + hash = transaction.transactionHash; + } } - } - - if (!hash) { + } else { const transaction = await TransactionDB.get(queueId); if (transaction?.status === "mined") { hash = transaction.transactionHash;