From 8da08fe4205c1e03eec3d4925c598be0b6769ba5 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 15 Jan 2024 17:36:50 +0100 Subject: wallet-core: uniform transaction interface, cleanup --- .../taler-wallet-core/src/operations/withdraw.ts | 482 +++++++++++---------- 1 file changed, 247 insertions(+), 235 deletions(-) (limited to 'packages/taler-wallet-core/src/operations/withdraw.ts') diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index d02cf0597..58df75964 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -105,6 +105,8 @@ import { TaskIdentifiers, TaskRunResult, TaskRunResultType, + TombstoneTag, + TransactionContext, constructTaskIdentifier, makeCoinAvailable, makeCoinsVisible, @@ -146,246 +148,246 @@ import { */ const logger = new Logger("operations/withdraw.ts"); -export async function suspendWithdrawalTransaction( - ws: InternalWalletState, - withdrawalGroupId: string, -) { - const taskId = constructTaskIdentifier({ - tag: PendingTaskType.Withdraw, - withdrawalGroupId, - }); - stopLongpolling(ws, taskId); - const transitionInfo = await ws.db - .mktx((x) => [x.withdrawalGroups]) - .runReadWrite(async (tx) => { - const wg = await tx.withdrawalGroups.get(withdrawalGroupId); - if (!wg) { - logger.warn(`withdrawal group ${withdrawalGroupId} not found`); - return; - } - let newStatus: WithdrawalGroupStatus | undefined = undefined; - switch (wg.status) { - case WithdrawalGroupStatus.PendingReady: - newStatus = WithdrawalGroupStatus.SuspendedReady; - break; - case WithdrawalGroupStatus.AbortingBank: - newStatus = WithdrawalGroupStatus.SuspendedAbortingBank; - break; - case WithdrawalGroupStatus.PendingWaitConfirmBank: - newStatus = WithdrawalGroupStatus.SuspendedWaitConfirmBank; - break; - case WithdrawalGroupStatus.PendingRegisteringBank: - newStatus = WithdrawalGroupStatus.SuspendedRegisteringBank; - break; - case WithdrawalGroupStatus.PendingQueryingStatus: - newStatus = WithdrawalGroupStatus.SuspendedQueryingStatus; - break; - case WithdrawalGroupStatus.PendingKyc: - newStatus = WithdrawalGroupStatus.SuspendedKyc; - break; - case WithdrawalGroupStatus.PendingAml: - newStatus = WithdrawalGroupStatus.SuspendedAml; - break; - default: - logger.warn( - `Unsupported 'suspend' on withdrawal transaction in status ${wg.status}`, - ); - } - if (newStatus != null) { - const oldTxState = computeWithdrawalTransactionStatus(wg); - wg.status = newStatus; - const newTxState = computeWithdrawalTransactionStatus(wg); - await tx.withdrawalGroups.put(wg); - return { - oldTxState, - newTxState, - }; - } - return undefined; +export class WithdrawTransactionContext implements TransactionContext { + public transactionId: string; + public retryTag: string; + + constructor( + public ws: InternalWalletState, + public withdrawalGroupId: string, + ) { + this.transactionId = constructTransactionIdentifier({ + tag: TransactionType.Withdrawal, + withdrawalGroupId, }); + this.retryTag = constructTaskIdentifier({ + tag: PendingTaskType.Withdraw, + withdrawalGroupId, + }); + } - const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Withdrawal, - withdrawalGroupId, - }); - notifyTransition(ws, transactionId, transitionInfo); -} + async deleteTransaction(): Promise { + const { ws, withdrawalGroupId } = this; + await ws.db + .mktx((x) => [x.withdrawalGroups, x.tombstones]) + .runReadWrite(async (tx) => { + const withdrawalGroupRecord = + await tx.withdrawalGroups.get(withdrawalGroupId); + if (withdrawalGroupRecord) { + await tx.withdrawalGroups.delete(withdrawalGroupId); + await tx.tombstones.put({ + id: TombstoneTag.DeleteWithdrawalGroup + ":" + withdrawalGroupId, + }); + return; + } + }); + } -export async function resumeWithdrawalTransaction( - ws: InternalWalletState, - withdrawalGroupId: string, -) { - const transitionInfo = await ws.db - .mktx((x) => [x.withdrawalGroups]) - .runReadWrite(async (tx) => { - const wg = await tx.withdrawalGroups.get(withdrawalGroupId); - if (!wg) { - logger.warn(`withdrawal group ${withdrawalGroupId} not found`); - return; - } - let newStatus: WithdrawalGroupStatus | undefined = undefined; - switch (wg.status) { - case WithdrawalGroupStatus.SuspendedReady: - newStatus = WithdrawalGroupStatus.PendingReady; - break; - case WithdrawalGroupStatus.SuspendedAbortingBank: - newStatus = WithdrawalGroupStatus.AbortingBank; - break; - case WithdrawalGroupStatus.SuspendedWaitConfirmBank: - newStatus = WithdrawalGroupStatus.PendingWaitConfirmBank; - break; - case WithdrawalGroupStatus.SuspendedQueryingStatus: - newStatus = WithdrawalGroupStatus.PendingQueryingStatus; - break; - case WithdrawalGroupStatus.SuspendedRegisteringBank: - newStatus = WithdrawalGroupStatus.PendingRegisteringBank; - break; - case WithdrawalGroupStatus.SuspendedAml: - newStatus = WithdrawalGroupStatus.PendingAml; - break; - case WithdrawalGroupStatus.SuspendedKyc: - newStatus = WithdrawalGroupStatus.PendingKyc; - break; - default: - logger.warn( - `Unsupported 'resume' on withdrawal transaction in status ${wg.status}`, - ); - } - if (newStatus != null) { - const oldTxState = computeWithdrawalTransactionStatus(wg); - wg.status = newStatus; - const newTxState = computeWithdrawalTransactionStatus(wg); - await tx.withdrawalGroups.put(wg); - return { - oldTxState, - newTxState, - }; - } - return undefined; - }); - ws.workAvailable.trigger(); - const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Withdrawal, - withdrawalGroupId, - }); - notifyTransition(ws, transactionId, transitionInfo); -} + async suspendTransaction(): Promise { + const { ws, withdrawalGroupId, transactionId, retryTag } = this; + stopLongpolling(ws, retryTag); + const transitionInfo = await ws.db + .mktx((x) => [x.withdrawalGroups]) + .runReadWrite(async (tx) => { + const wg = await tx.withdrawalGroups.get(withdrawalGroupId); + if (!wg) { + logger.warn(`withdrawal group ${withdrawalGroupId} not found`); + return; + } + let newStatus: WithdrawalGroupStatus | undefined = undefined; + switch (wg.status) { + case WithdrawalGroupStatus.PendingReady: + newStatus = WithdrawalGroupStatus.SuspendedReady; + break; + case WithdrawalGroupStatus.AbortingBank: + newStatus = WithdrawalGroupStatus.SuspendedAbortingBank; + break; + case WithdrawalGroupStatus.PendingWaitConfirmBank: + newStatus = WithdrawalGroupStatus.SuspendedWaitConfirmBank; + break; + case WithdrawalGroupStatus.PendingRegisteringBank: + newStatus = WithdrawalGroupStatus.SuspendedRegisteringBank; + break; + case WithdrawalGroupStatus.PendingQueryingStatus: + newStatus = WithdrawalGroupStatus.SuspendedQueryingStatus; + break; + case WithdrawalGroupStatus.PendingKyc: + newStatus = WithdrawalGroupStatus.SuspendedKyc; + break; + case WithdrawalGroupStatus.PendingAml: + newStatus = WithdrawalGroupStatus.SuspendedAml; + break; + default: + logger.warn( + `Unsupported 'suspend' on withdrawal transaction in status ${wg.status}`, + ); + } + if (newStatus != null) { + const oldTxState = computeWithdrawalTransactionStatus(wg); + wg.status = newStatus; + const newTxState = computeWithdrawalTransactionStatus(wg); + await tx.withdrawalGroups.put(wg); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }); -export async function abortWithdrawalTransaction( - ws: InternalWalletState, - withdrawalGroupId: string, -) { - const taskId = constructTaskIdentifier({ - tag: PendingTaskType.Withdraw, - withdrawalGroupId, - }); - const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Withdrawal, - withdrawalGroupId, - }); - stopLongpolling(ws, taskId); - const transitionInfo = await ws.db - .mktx((x) => [x.withdrawalGroups]) - .runReadWrite(async (tx) => { - const wg = await tx.withdrawalGroups.get(withdrawalGroupId); - if (!wg) { - logger.warn(`withdrawal group ${withdrawalGroupId} not found`); - return; - } - let newStatus: WithdrawalGroupStatus | undefined = undefined; - switch (wg.status) { - case WithdrawalGroupStatus.SuspendedRegisteringBank: - case WithdrawalGroupStatus.SuspendedWaitConfirmBank: - case WithdrawalGroupStatus.PendingWaitConfirmBank: - case WithdrawalGroupStatus.PendingRegisteringBank: - newStatus = WithdrawalGroupStatus.AbortingBank; - break; - case WithdrawalGroupStatus.SuspendedAml: - case WithdrawalGroupStatus.SuspendedKyc: - case WithdrawalGroupStatus.SuspendedQueryingStatus: - case WithdrawalGroupStatus.SuspendedReady: - case WithdrawalGroupStatus.PendingAml: - case WithdrawalGroupStatus.PendingKyc: - case WithdrawalGroupStatus.PendingQueryingStatus: - newStatus = WithdrawalGroupStatus.AbortedExchange; - break; - case WithdrawalGroupStatus.PendingReady: - newStatus = WithdrawalGroupStatus.SuspendedReady; - break; - case WithdrawalGroupStatus.SuspendedAbortingBank: - case WithdrawalGroupStatus.AbortingBank: - // No transition needed, but not an error - break; - case WithdrawalGroupStatus.Done: - case WithdrawalGroupStatus.FailedBankAborted: - case WithdrawalGroupStatus.AbortedExchange: - case WithdrawalGroupStatus.AbortedBank: - case WithdrawalGroupStatus.FailedAbortingBank: - // Not allowed - throw Error("abort not allowed in current state"); - break; - default: - assertUnreachable(wg.status); - } - if (newStatus != null) { - const oldTxState = computeWithdrawalTransactionStatus(wg); - wg.status = newStatus; - const newTxState = computeWithdrawalTransactionStatus(wg); - await tx.withdrawalGroups.put(wg); - return { - oldTxState, - newTxState, - }; - } - return undefined; - }); - ws.workAvailable.trigger(); - notifyTransition(ws, transactionId, transitionInfo); -} + notifyTransition(ws, transactionId, transitionInfo); + } -export async function failWithdrawalTransaction( - ws: InternalWalletState, - withdrawalGroupId: string, -) { - const taskId = constructTaskIdentifier({ - tag: PendingTaskType.Withdraw, - withdrawalGroupId, - }); - const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Withdrawal, - withdrawalGroupId, - }); - stopLongpolling(ws, taskId); - const stateUpdate = await ws.db - .mktx((x) => [x.withdrawalGroups]) - .runReadWrite(async (tx) => { - const wg = await tx.withdrawalGroups.get(withdrawalGroupId); - if (!wg) { - logger.warn(`withdrawal group ${withdrawalGroupId} not found`); - return; - } - let newStatus: WithdrawalGroupStatus | undefined = undefined; - switch (wg.status) { - case WithdrawalGroupStatus.SuspendedAbortingBank: - case WithdrawalGroupStatus.AbortingBank: - newStatus = WithdrawalGroupStatus.FailedAbortingBank; - break; - default: - break; - } - if (newStatus != null) { - const oldTxState = computeWithdrawalTransactionStatus(wg); - wg.status = newStatus; - const newTxState = computeWithdrawalTransactionStatus(wg); - await tx.withdrawalGroups.put(wg); - return { - oldTxState, - newTxState, - }; - } - return undefined; - }); - notifyTransition(ws, transactionId, stateUpdate); + async abortTransaction(): Promise { + const { ws, withdrawalGroupId, transactionId } = this; + stopLongpolling(ws, this.retryTag); + const transitionInfo = await ws.db + .mktx((x) => [x.withdrawalGroups]) + .runReadWrite(async (tx) => { + const wg = await tx.withdrawalGroups.get(withdrawalGroupId); + if (!wg) { + logger.warn(`withdrawal group ${withdrawalGroupId} not found`); + return; + } + let newStatus: WithdrawalGroupStatus | undefined = undefined; + switch (wg.status) { + case WithdrawalGroupStatus.SuspendedRegisteringBank: + case WithdrawalGroupStatus.SuspendedWaitConfirmBank: + case WithdrawalGroupStatus.PendingWaitConfirmBank: + case WithdrawalGroupStatus.PendingRegisteringBank: + newStatus = WithdrawalGroupStatus.AbortingBank; + break; + case WithdrawalGroupStatus.SuspendedAml: + case WithdrawalGroupStatus.SuspendedKyc: + case WithdrawalGroupStatus.SuspendedQueryingStatus: + case WithdrawalGroupStatus.SuspendedReady: + case WithdrawalGroupStatus.PendingAml: + case WithdrawalGroupStatus.PendingKyc: + case WithdrawalGroupStatus.PendingQueryingStatus: + newStatus = WithdrawalGroupStatus.AbortedExchange; + break; + case WithdrawalGroupStatus.PendingReady: + newStatus = WithdrawalGroupStatus.SuspendedReady; + break; + case WithdrawalGroupStatus.SuspendedAbortingBank: + case WithdrawalGroupStatus.AbortingBank: + // No transition needed, but not an error + break; + case WithdrawalGroupStatus.Done: + case WithdrawalGroupStatus.FailedBankAborted: + case WithdrawalGroupStatus.AbortedExchange: + case WithdrawalGroupStatus.AbortedBank: + case WithdrawalGroupStatus.FailedAbortingBank: + // Not allowed + throw Error("abort not allowed in current state"); + break; + default: + assertUnreachable(wg.status); + } + if (newStatus != null) { + const oldTxState = computeWithdrawalTransactionStatus(wg); + wg.status = newStatus; + const newTxState = computeWithdrawalTransactionStatus(wg); + await tx.withdrawalGroups.put(wg); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }); + ws.workAvailable.trigger(); + notifyTransition(ws, transactionId, transitionInfo); + } + + async resumeTransaction(): Promise { + const { ws, withdrawalGroupId, transactionId } = this; + const transitionInfo = await ws.db + .mktx((x) => [x.withdrawalGroups]) + .runReadWrite(async (tx) => { + const wg = await tx.withdrawalGroups.get(withdrawalGroupId); + if (!wg) { + logger.warn(`withdrawal group ${withdrawalGroupId} not found`); + return; + } + let newStatus: WithdrawalGroupStatus | undefined = undefined; + switch (wg.status) { + case WithdrawalGroupStatus.SuspendedReady: + newStatus = WithdrawalGroupStatus.PendingReady; + break; + case WithdrawalGroupStatus.SuspendedAbortingBank: + newStatus = WithdrawalGroupStatus.AbortingBank; + break; + case WithdrawalGroupStatus.SuspendedWaitConfirmBank: + newStatus = WithdrawalGroupStatus.PendingWaitConfirmBank; + break; + case WithdrawalGroupStatus.SuspendedQueryingStatus: + newStatus = WithdrawalGroupStatus.PendingQueryingStatus; + break; + case WithdrawalGroupStatus.SuspendedRegisteringBank: + newStatus = WithdrawalGroupStatus.PendingRegisteringBank; + break; + case WithdrawalGroupStatus.SuspendedAml: + newStatus = WithdrawalGroupStatus.PendingAml; + break; + case WithdrawalGroupStatus.SuspendedKyc: + newStatus = WithdrawalGroupStatus.PendingKyc; + break; + default: + logger.warn( + `Unsupported 'resume' on withdrawal transaction in status ${wg.status}`, + ); + } + if (newStatus != null) { + const oldTxState = computeWithdrawalTransactionStatus(wg); + wg.status = newStatus; + const newTxState = computeWithdrawalTransactionStatus(wg); + await tx.withdrawalGroups.put(wg); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }); + ws.workAvailable.trigger(); + notifyTransition(ws, transactionId, transitionInfo); + } + + async failTransaction(): Promise { + const { ws, withdrawalGroupId, transactionId, retryTag } = this; + stopLongpolling(ws, retryTag); + const stateUpdate = await ws.db + .mktx((x) => [x.withdrawalGroups]) + .runReadWrite(async (tx) => { + const wg = await tx.withdrawalGroups.get(withdrawalGroupId); + if (!wg) { + logger.warn(`withdrawal group ${withdrawalGroupId} not found`); + return; + } + let newStatus: WithdrawalGroupStatus | undefined = undefined; + switch (wg.status) { + case WithdrawalGroupStatus.SuspendedAbortingBank: + case WithdrawalGroupStatus.AbortingBank: + newStatus = WithdrawalGroupStatus.FailedAbortingBank; + break; + default: + break; + } + if (newStatus != null) { + const oldTxState = computeWithdrawalTransactionStatus(wg); + wg.status = newStatus; + const newTxState = computeWithdrawalTransactionStatus(wg); + await tx.withdrawalGroups.put(wg); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }); + notifyTransition(ws, transactionId, stateUpdate); + } } export function computeWithdrawalTransactionStatus( @@ -2413,6 +2415,16 @@ export async function internalPerformCreateWithdrawalGroup( exchangeNotif: undefined, }; } + const existingWg = await tx.withdrawalGroups.get( + withdrawalGroup.withdrawalGroupId, + ); + if (existingWg) { + return { + withdrawalGroup: existingWg, + exchangeNotif: undefined, + transitionInfo: undefined, + }; + } await tx.withdrawalGroups.add(withdrawalGroup); await tx.reserves.put({ reservePub: withdrawalGroup.reservePub, -- cgit v1.2.3