From 1e9dfc15a5fbdfaa1a7f0f0101e227aff951e9eb Mon Sep 17 00:00:00 2001 From: Bram Borggreve Date: Mon, 23 Jan 2023 23:09:39 +0000 Subject: [PATCH] fix: update the stale transaction logic to try and confirm the signature --- .../core/data-access/src/prisma/schema.prisma | 4 + .../src/lib/api-kinetic.service.ts | 133 +++++++++--------- .../api-transaction-data-access.service.ts | 38 +++-- .../src/lib/web-toolbox-ui-close-account.tsx | 2 +- .../src/lib/web-toolbox-ui-create-account.tsx | 2 +- .../src/lib/web-toolbox-ui-make-transfer.tsx | 3 +- 6 files changed, 107 insertions(+), 75 deletions(-) diff --git a/libs/api/core/data-access/src/prisma/schema.prisma b/libs/api/core/data-access/src/prisma/schema.prisma index 1f4b95b03..293645697 100644 --- a/libs/api/core/data-access/src/prisma/schema.prisma +++ b/libs/api/core/data-access/src/prisma/schema.prisma @@ -148,12 +148,16 @@ model Transaction { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt amount String? + appKey String? + blockhash String? commitment TransactionCommitment? decimals Int? destination String? errors TransactionError[] feePayer String? + headers Json? ip String? + lastValidBlockHeight Int? mint String? processingDuration Int? referenceId String? diff --git a/libs/api/kinetic/data-access/src/lib/api-kinetic.service.ts b/libs/api/kinetic/data-access/src/lib/api-kinetic.service.ts index 5a6f29020..fd7cbb94d 100644 --- a/libs/api/kinetic/data-access/src/lib/api-kinetic.service.ts +++ b/libs/api/kinetic/data-access/src/lib/api-kinetic.service.ts @@ -114,6 +114,71 @@ export class ApiKineticService implements OnModuleInit { }) } + async confirmSignature({ + appEnv, + appKey, + transactionId, + blockhash, + headers, + lastValidBlockHeight, + signature, + solanaStart, + transactionStart, + }: { + appEnv: AppEnv & { app: App } + appKey: string + transactionId: string + blockhash: string + headers?: Record + lastValidBlockHeight: number + signature: string + solanaStart: Date + transactionStart: Date + }): Promise { + const solana = await this.solana.getConnection(appKey) + this.logger.verbose(`${appKey}: confirmSignature: confirming ${signature}`) + + const finalized = await solana.confirmTransaction( + { + blockhash, + lastValidBlockHeight, + signature: signature as string, + }, + Commitment.Finalized, + ) + if (finalized) { + const solanaFinalized = new Date() + const solanaFinalizedDuration = solanaFinalized.getTime() - solanaStart.getTime() + const totalDuration = solanaFinalized.getTime() - transactionStart.getTime() + this.logger.verbose(`${appKey}: confirmSignature: ${Commitment.Finalized} ${signature}`) + const solanaTransaction = await solana.connection.getParsedTransaction(signature, 'finalized') + const transaction = await this.updateTransaction(transactionId, { + solanaFinalized, + solanaFinalizedDuration, + solanaTransaction: solanaTransaction ? JSON.parse(JSON.stringify(solanaTransaction)) : undefined, + status: TransactionStatus.Finalized, + totalDuration, + }) + this.confirmSignatureFinalizedCounter.add(1, { appKey }) + // Send Event Webhook + if (appEnv.webhookEventEnabled && appEnv.webhookEventUrl && transaction) { + const eventWebhookTransaction = await this.sendEventWebhook(appKey, appEnv, transaction, headers) + if (eventWebhookTransaction.status === TransactionStatus.Failed) { + this.logger.error( + `Transaction ${transaction.id} sendEventWebhook failed:${eventWebhookTransaction.errors + .map((e) => e.message) + .join(', ')}`, + eventWebhookTransaction.errors, + ) + return eventWebhookTransaction + } + } + + this.logger.verbose(`${appKey}: confirmSignature: finished ${signature}`) + return transaction + } + } + deleteSolanaConnection(appKey: string): void { return this.solana.deleteConnection(appKey) } @@ -323,10 +388,14 @@ export class ApiKineticService implements OnModuleInit { // Create the transaction and link it to the app environment const transaction: TransactionWithErrors = await this.createAppEnvTransaction(appEnv.id, { amount: amount ? removeDecimals(amount.toString(), decimals)?.toString() : undefined, + appKey, + blockhash, commitment, decimals, destination, feePayer, + headers, + lastValidBlockHeight, ip, mint: mintPublicKey, referenceId, @@ -434,70 +503,6 @@ export class ApiKineticService implements OnModuleInit { return { ip, ua } } - private async confirmSignature({ - appEnv, - appKey, - transactionId, - blockhash, - headers, - lastValidBlockHeight, - signature, - solanaStart, - transactionStart, - }: { - appEnv: AppEnv & { app: App } - appKey: string - transactionId: string - blockhash: string - headers?: Record - lastValidBlockHeight: number - signature: string - solanaStart: Date - transactionStart: Date - }) { - const solana = await this.solana.getConnection(appKey) - this.logger.verbose(`${appKey}: confirmSignature: confirming ${signature}`) - - const finalized = await solana.confirmTransaction( - { - blockhash, - lastValidBlockHeight, - signature: signature as string, - }, - Commitment.Finalized, - ) - if (finalized) { - const solanaFinalized = new Date() - const solanaFinalizedDuration = solanaFinalized.getTime() - solanaStart.getTime() - const totalDuration = solanaFinalized.getTime() - transactionStart.getTime() - this.logger.verbose(`${appKey}: confirmSignature: ${Commitment.Finalized} ${signature}`) - const solanaTransaction = await solana.connection.getParsedTransaction(signature, 'finalized') - const transaction = await this.updateTransaction(transactionId, { - solanaFinalized, - solanaFinalizedDuration, - solanaTransaction: solanaTransaction ? JSON.parse(JSON.stringify(solanaTransaction)) : undefined, - status: TransactionStatus.Finalized, - totalDuration, - }) - this.confirmSignatureFinalizedCounter.add(1, { appKey }) - // Send Event Webhook - if (appEnv.webhookEventEnabled && appEnv.webhookEventUrl && transaction) { - const eventWebhookTransaction = await this.sendEventWebhook(appKey, appEnv, transaction, headers) - if (eventWebhookTransaction.status === TransactionStatus.Failed) { - this.logger.error( - `Transaction ${transaction.id} sendEventWebhook failed:${eventWebhookTransaction.errors - .map((e) => e.message) - .join(', ')}`, - eventWebhookTransaction.errors, - ) - return eventWebhookTransaction - } - } - - this.logger.verbose(`${appKey}: confirmSignature: finished ${signature}`) - } - } - private async sendEventWebhook( appKey: string, appEnv: AppEnv & { app: App }, diff --git a/libs/api/transaction/data-access/src/lib/api-transaction-data-access.service.ts b/libs/api/transaction/data-access/src/lib/api-transaction-data-access.service.ts index 98569b622..2a93ba647 100644 --- a/libs/api/transaction/data-access/src/lib/api-transaction-data-access.service.ts +++ b/libs/api/transaction/data-access/src/lib/api-transaction-data-access.service.ts @@ -24,7 +24,7 @@ export class ApiTransactionDataAccessService implements OnModuleInit { async cleanupStaleTransactions() { const stale = await this.getExpiredTransactions() if (!stale.length) return - this.timeoutTransactions(stale.map((item) => item.id)).then((res) => { + this.timeoutTransactions(stale).then((res) => { this.logger.verbose( `cleanupStaleTransactions set ${stale?.length} stale transactions: ${res.map((item) => item.id)} `, ) @@ -32,7 +32,7 @@ export class ApiTransactionDataAccessService implements OnModuleInit { } private getExpiredTransactions(): Promise { - const expiredMinutes = 5 + const expiredMinutes = 1 const expired = getExpiredTime(expiredMinutes) return this.data.transaction.findMany({ where: { @@ -42,23 +42,45 @@ export class ApiTransactionDataAccessService implements OnModuleInit { }) } - private timeoutTransactions(ids: string[]): Promise { - return Promise.all(ids.map((id) => this.timeoutTransaction(id))) + private timeoutTransactions(transactions: Transaction[]): Promise { + return Promise.all(transactions.map((transaction) => this.verifyTransaction(transaction))) } - private timeoutTransaction(id: string): Promise { - return this.data.transaction.update({ - where: { id: id }, + private async verifyTransaction(transaction: Transaction): Promise { + if (transaction?.appKey && transaction.signature) { + const appEnv = await this.data.getAppEnvironmentByAppKey(transaction.appKey) + const tx = await this.kinetic.confirmSignature({ + appEnv, + appKey: transaction.appKey, + transactionId: transaction.id, + blockhash: transaction.blockhash, + headers: transaction.headers as Record, + lastValidBlockHeight: transaction.lastValidBlockHeight, + signature: transaction.signature, + solanaStart: transaction.solanaStart, + transactionStart: transaction.createdAt, + }) + + if (tx?.status === 'Finalized') { + this.logger.verbose(`verifyTransaction: set ${transaction.id} to Finalized`) + return tx + } + } + + const failed = await this.data.transaction.update({ + where: { id: transaction.id }, data: { status: TransactionStatus.Failed, errors: { create: { type: TransactionErrorType.Timeout, - message: `Transaction timed out`, + message: transaction.signature ? `Transaction timed out` : 'Transaction never signed', }, }, }, }) + this.logger.verbose(`verifyTransaction: set ${transaction.id} to Failed`) + return failed } onModuleInit() { diff --git a/libs/web/toolbox/ui/src/lib/web-toolbox-ui-close-account.tsx b/libs/web/toolbox/ui/src/lib/web-toolbox-ui-close-account.tsx index 7ca8a6b9d..11a7f17ce 100644 --- a/libs/web/toolbox/ui/src/lib/web-toolbox-ui-close-account.tsx +++ b/libs/web/toolbox/ui/src/lib/web-toolbox-ui-close-account.tsx @@ -28,7 +28,7 @@ export function WebToolboxUiCloseAccount({ setLoading(true) sdk - .closeAccount({ account, referenceType: 'Toolbox Close', commitment }) + .closeAccount({ account, referenceType: 'Toolbox', referenceId: 'Close', commitment }) .then((res) => { setResponse(res) setLoading(false) diff --git a/libs/web/toolbox/ui/src/lib/web-toolbox-ui-create-account.tsx b/libs/web/toolbox/ui/src/lib/web-toolbox-ui-create-account.tsx index fedf9f9a6..9e5719c99 100644 --- a/libs/web/toolbox/ui/src/lib/web-toolbox-ui-create-account.tsx +++ b/libs/web/toolbox/ui/src/lib/web-toolbox-ui-create-account.tsx @@ -28,7 +28,7 @@ export function WebToolboxUiCreateAccount({ setLoading(true) sdk - .createAccount({ owner: keypair, referenceType: 'Toolbox Create', commitment }) + .createAccount({ owner: keypair, referenceType: 'Toolbox', referenceId: 'Create', commitment }) .then((res) => { setResponse(res) if (res.errors?.length) { diff --git a/libs/web/toolbox/ui/src/lib/web-toolbox-ui-make-transfer.tsx b/libs/web/toolbox/ui/src/lib/web-toolbox-ui-make-transfer.tsx index 6ca68fbee..7ff2001d2 100644 --- a/libs/web/toolbox/ui/src/lib/web-toolbox-ui-make-transfer.tsx +++ b/libs/web/toolbox/ui/src/lib/web-toolbox-ui-make-transfer.tsx @@ -34,7 +34,8 @@ export function WebToolboxUiMakeTransfer({ destination, mint: selectedMint?.publicKey, owner: keypair, - referenceType: 'Toolbox Transfer', + referenceType: 'Toolbox', + referenceId: 'Transfer', }) .then((res) => { setResponse(res)