diff options
Diffstat (limited to 'packages/taler-wallet-core/src/operations/deposits.ts')
-rw-r--r-- | packages/taler-wallet-core/src/operations/deposits.ts | 404 |
1 files changed, 193 insertions, 211 deletions
diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index f158d9cf9..62c1e406c 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -15,6 +15,10 @@ */ /** + * Implementation of the deposit transaction. + */ + +/** * Imports. */ import { @@ -84,6 +88,7 @@ import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js"; import { TaskRunResult, TombstoneTag, + TransactionContext, constructTaskIdentifier, runLongpollAsync, spendCoins, @@ -106,6 +111,194 @@ import { */ const logger = new Logger("deposits.ts"); +export class DepositTransactionContext implements TransactionContext { + private transactionId: string; + private retryTag: string; + constructor( + public ws: InternalWalletState, + public depositGroupId: string, + ) { + this.transactionId = constructTransactionIdentifier({ + tag: TransactionType.Deposit, + depositGroupId, + }); + this.retryTag = constructTaskIdentifier({ + tag: PendingTaskType.Deposit, + depositGroupId, + }); + } + + async deleteTransaction(): Promise<void> { + const depositGroupId = this.depositGroupId; + const ws = this.ws; + // FIXME: We should check first if we are in a final state + // where deletion is allowed. + await ws.db + .mktx((x) => [x.depositGroups, x.tombstones]) + .runReadWrite(async (tx) => { + const tipRecord = await tx.depositGroups.get(depositGroupId); + if (tipRecord) { + await tx.depositGroups.delete(depositGroupId); + await tx.tombstones.put({ + id: TombstoneTag.DeleteDepositGroup + ":" + depositGroupId, + }); + } + }); + return; + } + + async suspendTransaction(): Promise<void> { + const { ws, depositGroupId, transactionId, retryTag } = this; + const transitionInfo = await ws.db + .mktx((x) => [x.depositGroups]) + .runReadWrite(async (tx) => { + const dg = await tx.depositGroups.get(depositGroupId); + if (!dg) { + logger.warn( + `can't suspend deposit group, depositGroupId=${depositGroupId} not found`, + ); + return undefined; + } + const oldState = computeDepositTransactionStatus(dg); + let newOpStatus: DepositOperationStatus | undefined; + switch (dg.operationStatus) { + case DepositOperationStatus.PendingDeposit: + newOpStatus = DepositOperationStatus.SuspendedDeposit; + break; + case DepositOperationStatus.PendingKyc: + newOpStatus = DepositOperationStatus.SuspendedKyc; + break; + case DepositOperationStatus.PendingTrack: + newOpStatus = DepositOperationStatus.SuspendedTrack; + break; + case DepositOperationStatus.Aborting: + newOpStatus = DepositOperationStatus.SuspendedAborting; + break; + } + if (!newOpStatus) { + return undefined; + } + dg.operationStatus = newOpStatus; + await tx.depositGroups.put(dg); + return { + oldTxState: oldState, + newTxState: computeDepositTransactionStatus(dg), + }; + }); + stopLongpolling(ws, retryTag); + notifyTransition(ws, transactionId, transitionInfo); + } + + async abortTransaction(): Promise<void> { + const { ws, depositGroupId, transactionId, retryTag } = this; + const transitionInfo = await ws.db + .mktx((x) => [x.depositGroups]) + .runReadWrite(async (tx) => { + const dg = await tx.depositGroups.get(depositGroupId); + if (!dg) { + logger.warn( + `can't suspend deposit group, depositGroupId=${depositGroupId} not found`, + ); + return undefined; + } + const oldState = computeDepositTransactionStatus(dg); + switch (dg.operationStatus) { + case DepositOperationStatus.Finished: + return undefined; + case DepositOperationStatus.PendingDeposit: { + dg.operationStatus = DepositOperationStatus.Aborting; + await tx.depositGroups.put(dg); + return { + oldTxState: oldState, + newTxState: computeDepositTransactionStatus(dg), + }; + } + case DepositOperationStatus.SuspendedDeposit: + // FIXME: Can we abort a suspended transaction?! + return undefined; + } + return undefined; + }); + stopLongpolling(ws, retryTag); + // Need to process the operation again. + ws.workAvailable.trigger(); + notifyTransition(ws, transactionId, transitionInfo); + } + + async resumeTransaction(): Promise<void> { + const { ws, depositGroupId, transactionId, retryTag } = this; + const transitionInfo = await ws.db + .mktx((x) => [x.depositGroups]) + .runReadWrite(async (tx) => { + const dg = await tx.depositGroups.get(depositGroupId); + if (!dg) { + logger.warn( + `can't resume deposit group, depositGroupId=${depositGroupId} not found`, + ); + return; + } + const oldState = computeDepositTransactionStatus(dg); + let newOpStatus: DepositOperationStatus | undefined; + switch (dg.operationStatus) { + case DepositOperationStatus.SuspendedDeposit: + newOpStatus = DepositOperationStatus.PendingDeposit; + break; + case DepositOperationStatus.SuspendedAborting: + newOpStatus = DepositOperationStatus.Aborting; + break; + case DepositOperationStatus.SuspendedKyc: + newOpStatus = DepositOperationStatus.PendingKyc; + break; + case DepositOperationStatus.SuspendedTrack: + newOpStatus = DepositOperationStatus.PendingTrack; + break; + } + if (!newOpStatus) { + return undefined; + } + dg.operationStatus = newOpStatus; + await tx.depositGroups.put(dg); + return { + oldTxState: oldState, + newTxState: computeDepositTransactionStatus(dg), + }; + }); + ws.workAvailable.trigger(); + notifyTransition(ws, transactionId, transitionInfo); + } + + async failTransaction(): Promise<void> { + const { ws, depositGroupId, transactionId, retryTag } = this; + const transitionInfo = await ws.db + .mktx((x) => [x.depositGroups]) + .runReadWrite(async (tx) => { + const dg = await tx.depositGroups.get(depositGroupId); + if (!dg) { + logger.warn( + `can't cancel aborting deposit group, depositGroupId=${depositGroupId} not found`, + ); + return undefined; + } + const oldState = computeDepositTransactionStatus(dg); + switch (dg.operationStatus) { + case DepositOperationStatus.SuspendedAborting: + case DepositOperationStatus.Aborting: { + dg.operationStatus = DepositOperationStatus.Failed; + await tx.depositGroups.put(dg); + return { + oldTxState: oldState, + newTxState: computeDepositTransactionStatus(dg), + }; + } + } + return undefined; + }); + // FIXME: Also cancel ongoing work (via cancellation token, once implemented) + stopLongpolling(ws, retryTag); + notifyTransition(ws, transactionId, transitionInfo); + } +} + /** * Get the (DD37-style) transaction status based on the * database record of a deposit group. @@ -204,217 +397,6 @@ export function computeDepositTransactionActions( } /** - * Put a deposit group in a suspended state. - * While the deposit group is suspended, no network requests - * will be made to advance the transaction status. - */ -export async function suspendDepositGroup( - ws: InternalWalletState, - depositGroupId: string, -): Promise<void> { - const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Deposit, - depositGroupId, - }); - const retryTag = constructTaskIdentifier({ - tag: PendingTaskType.Deposit, - depositGroupId, - }); - const transitionInfo = await ws.db - .mktx((x) => [x.depositGroups]) - .runReadWrite(async (tx) => { - const dg = await tx.depositGroups.get(depositGroupId); - if (!dg) { - logger.warn( - `can't suspend deposit group, depositGroupId=${depositGroupId} not found`, - ); - return undefined; - } - const oldState = computeDepositTransactionStatus(dg); - let newOpStatus: DepositOperationStatus | undefined; - switch (dg.operationStatus) { - case DepositOperationStatus.PendingDeposit: - newOpStatus = DepositOperationStatus.SuspendedDeposit; - break; - case DepositOperationStatus.PendingKyc: - newOpStatus = DepositOperationStatus.SuspendedKyc; - break; - case DepositOperationStatus.PendingTrack: - newOpStatus = DepositOperationStatus.SuspendedTrack; - break; - case DepositOperationStatus.Aborting: - newOpStatus = DepositOperationStatus.SuspendedAborting; - break; - } - if (!newOpStatus) { - return undefined; - } - dg.operationStatus = newOpStatus; - await tx.depositGroups.put(dg); - return { - oldTxState: oldState, - newTxState: computeDepositTransactionStatus(dg), - }; - }); - stopLongpolling(ws, retryTag); - notifyTransition(ws, transactionId, transitionInfo); -} - -export async function resumeDepositGroup( - ws: InternalWalletState, - depositGroupId: string, -): Promise<void> { - const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Deposit, - depositGroupId, - }); - const transitionInfo = await ws.db - .mktx((x) => [x.depositGroups]) - .runReadWrite(async (tx) => { - const dg = await tx.depositGroups.get(depositGroupId); - if (!dg) { - logger.warn( - `can't resume deposit group, depositGroupId=${depositGroupId} not found`, - ); - return; - } - const oldState = computeDepositTransactionStatus(dg); - let newOpStatus: DepositOperationStatus | undefined; - switch (dg.operationStatus) { - case DepositOperationStatus.SuspendedDeposit: - newOpStatus = DepositOperationStatus.PendingDeposit; - break; - case DepositOperationStatus.SuspendedAborting: - newOpStatus = DepositOperationStatus.Aborting; - break; - case DepositOperationStatus.SuspendedKyc: - newOpStatus = DepositOperationStatus.PendingKyc; - break; - case DepositOperationStatus.SuspendedTrack: - newOpStatus = DepositOperationStatus.PendingTrack; - break; - } - if (!newOpStatus) { - return undefined; - } - dg.operationStatus = newOpStatus; - await tx.depositGroups.put(dg); - return { - oldTxState: oldState, - newTxState: computeDepositTransactionStatus(dg), - }; - }); - ws.workAvailable.trigger(); - notifyTransition(ws, transactionId, transitionInfo); -} - -export async function abortDepositGroup( - ws: InternalWalletState, - depositGroupId: string, -): Promise<void> { - const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Deposit, - depositGroupId, - }); - const retryTag = constructTaskIdentifier({ - tag: PendingTaskType.Deposit, - depositGroupId, - }); - const transitionInfo = await ws.db - .mktx((x) => [x.depositGroups]) - .runReadWrite(async (tx) => { - const dg = await tx.depositGroups.get(depositGroupId); - if (!dg) { - logger.warn( - `can't suspend deposit group, depositGroupId=${depositGroupId} not found`, - ); - return undefined; - } - const oldState = computeDepositTransactionStatus(dg); - switch (dg.operationStatus) { - case DepositOperationStatus.Finished: - return undefined; - case DepositOperationStatus.PendingDeposit: { - dg.operationStatus = DepositOperationStatus.Aborting; - await tx.depositGroups.put(dg); - return { - oldTxState: oldState, - newTxState: computeDepositTransactionStatus(dg), - }; - } - case DepositOperationStatus.SuspendedDeposit: - // FIXME: Can we abort a suspended transaction?! - return undefined; - } - return undefined; - }); - stopLongpolling(ws, retryTag); - // Need to process the operation again. - ws.workAvailable.trigger(); - notifyTransition(ws, transactionId, transitionInfo); -} - -export async function failDepositTransaction( - ws: InternalWalletState, - depositGroupId: string, -): Promise<void> { - const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Deposit, - depositGroupId, - }); - const retryTag = constructTaskIdentifier({ - tag: PendingTaskType.Deposit, - depositGroupId, - }); - const transitionInfo = await ws.db - .mktx((x) => [x.depositGroups]) - .runReadWrite(async (tx) => { - const dg = await tx.depositGroups.get(depositGroupId); - if (!dg) { - logger.warn( - `can't cancel aborting deposit group, depositGroupId=${depositGroupId} not found`, - ); - return undefined; - } - const oldState = computeDepositTransactionStatus(dg); - switch (dg.operationStatus) { - case DepositOperationStatus.SuspendedAborting: - case DepositOperationStatus.Aborting: { - dg.operationStatus = DepositOperationStatus.Failed; - await tx.depositGroups.put(dg); - return { - oldTxState: oldState, - newTxState: computeDepositTransactionStatus(dg), - }; - } - } - return undefined; - }); - // FIXME: Also cancel ongoing work (via cancellation token, once implemented) - stopLongpolling(ws, retryTag); - notifyTransition(ws, transactionId, transitionInfo); -} - -export async function deleteDepositGroup( - ws: InternalWalletState, - depositGroupId: string, -) { - // FIXME: We should check first if we are in a final state - // where deletion is allowed. - await ws.db - .mktx((x) => [x.depositGroups, x.tombstones]) - .runReadWrite(async (tx) => { - const tipRecord = await tx.depositGroups.get(depositGroupId); - if (tipRecord) { - await tx.depositGroups.delete(depositGroupId); - await tx.tombstones.put({ - id: TombstoneTag.DeleteDepositGroup + ":" + depositGroupId, - }); - } - }); -} - -/** * Check whether the refresh associated with the * aborting deposit group is done. * |