Skip to content

Commit

Permalink
fix: update the stale transaction logic to try and confirm the signature
Browse files Browse the repository at this point in the history
  • Loading branch information
beeman committed Jan 24, 2023
1 parent 85baca0 commit 1e9dfc1
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 75 deletions.
4 changes: 4 additions & 0 deletions libs/api/core/data-access/src/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
133 changes: 69 additions & 64 deletions libs/api/kinetic/data-access/src/lib/api-kinetic.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string>
lastValidBlockHeight: number
signature: string
solanaStart: Date
transactionStart: Date
}): Promise<Transaction | undefined> {
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)
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<string, string>
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 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ 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)} `,
)
})
}

private getExpiredTransactions(): Promise<Transaction[]> {
const expiredMinutes = 5
const expiredMinutes = 1
const expired = getExpiredTime(expiredMinutes)
return this.data.transaction.findMany({
where: {
Expand All @@ -42,23 +42,45 @@ export class ApiTransactionDataAccessService implements OnModuleInit {
})
}

private timeoutTransactions(ids: string[]): Promise<Transaction[]> {
return Promise.all(ids.map((id) => this.timeoutTransaction(id)))
private timeoutTransactions(transactions: Transaction[]): Promise<Transaction[]> {
return Promise.all(transactions.map((transaction) => this.verifyTransaction(transaction)))
}

private timeoutTransaction(id: string): Promise<Transaction> {
return this.data.transaction.update({
where: { id: id },
private async verifyTransaction(transaction: Transaction): Promise<Transaction> {
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<string, string>,
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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
3 changes: 2 additions & 1 deletion libs/web/toolbox/ui/src/lib/web-toolbox-ui-make-transfer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export function WebToolboxUiMakeTransfer({
destination,
mint: selectedMint?.publicKey,
owner: keypair,
referenceType: 'Toolbox Transfer',
referenceType: 'Toolbox',
referenceId: 'Transfer',
})
.then((res) => {
setResponse(res)
Expand Down

0 comments on commit 1e9dfc1

Please sign in to comment.