diff options
Diffstat (limited to 'packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts')
-rw-r--r-- | packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts | 458 |
1 files changed, 264 insertions, 194 deletions
diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts index 36606e732..c8cfaac7d 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts @@ -66,6 +66,8 @@ import { checkDbInvariant } from "../util/invariants.js"; import { TaskRunResult, TaskRunResultType, + TombstoneTag, + TransactionContext, constructTaskIdentifier, runLongpollAsync, } from "./common.js"; @@ -90,6 +92,268 @@ import { const logger = new Logger("pay-peer-push-credit.ts"); +export class PeerPushCreditTransactionContext implements TransactionContext { + private transactionId: string; + private retryTag: string; + + constructor( + public ws: InternalWalletState, + public peerPushCreditId: string, + ) { + this.transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushCredit, + peerPushCreditId, + }); + this.retryTag = constructTaskIdentifier({ + tag: PendingTaskType.PeerPushCredit, + peerPushCreditId, + }); + } + + async deleteTransaction(): Promise<void> { + const { ws, peerPushCreditId } = this; + await ws.db.runReadWriteTx( + ["withdrawalGroups", "peerPushCredit", "tombstones"], + async (tx) => { + const pushInc = await tx.peerPushCredit.get(peerPushCreditId); + if (!pushInc) { + return; + } + if (pushInc.withdrawalGroupId) { + const withdrawalGroupId = pushInc.withdrawalGroupId; + const withdrawalGroupRecord = + await tx.withdrawalGroups.get(withdrawalGroupId); + if (withdrawalGroupRecord) { + await tx.withdrawalGroups.delete(withdrawalGroupId); + await tx.tombstones.put({ + id: TombstoneTag.DeleteWithdrawalGroup + ":" + withdrawalGroupId, + }); + } + } + await tx.peerPushCredit.delete(peerPushCreditId); + await tx.tombstones.put({ + id: TombstoneTag.DeletePeerPushCredit + ":" + peerPushCreditId, + }); + }, + ); + return; + } + + async suspendTransaction(): Promise<void> { + const { ws, peerPushCreditId, retryTag, transactionId } = this; + stopLongpolling(ws, retryTag); + const transitionInfo = await ws.db.runReadWriteTx( + ["peerPushCredit"], + async (tx) => { + const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId); + if (!pushCreditRec) { + logger.warn(`peer push credit ${peerPushCreditId} not found`); + return; + } + let newStatus: PeerPushCreditStatus | undefined = undefined; + switch (pushCreditRec.status) { + case PeerPushCreditStatus.DialogProposed: + case PeerPushCreditStatus.Done: + case PeerPushCreditStatus.SuspendedMerge: + case PeerPushCreditStatus.SuspendedMergeKycRequired: + case PeerPushCreditStatus.SuspendedWithdrawing: + break; + case PeerPushCreditStatus.PendingMergeKycRequired: + newStatus = PeerPushCreditStatus.SuspendedMergeKycRequired; + break; + case PeerPushCreditStatus.PendingMerge: + newStatus = PeerPushCreditStatus.SuspendedMerge; + break; + case PeerPushCreditStatus.PendingWithdrawing: + // FIXME: Suspend internal withdrawal transaction! + newStatus = PeerPushCreditStatus.SuspendedWithdrawing; + break; + case PeerPushCreditStatus.Aborted: + break; + case PeerPushCreditStatus.Failed: + break; + default: + assertUnreachable(pushCreditRec.status); + } + if (newStatus != null) { + const oldTxState = + computePeerPushCreditTransactionState(pushCreditRec); + pushCreditRec.status = newStatus; + const newTxState = + computePeerPushCreditTransactionState(pushCreditRec); + await tx.peerPushCredit.put(pushCreditRec); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }, + ); + notifyTransition(ws, transactionId, transitionInfo); + } + + async abortTransaction(): Promise<void> { + const { ws, peerPushCreditId, retryTag, transactionId } = this; + stopLongpolling(ws, retryTag); + const transitionInfo = await ws.db.runReadWriteTx( + ["peerPushCredit"], + async (tx) => { + const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId); + if (!pushCreditRec) { + logger.warn(`peer push credit ${peerPushCreditId} not found`); + return; + } + let newStatus: PeerPushCreditStatus | undefined = undefined; + switch (pushCreditRec.status) { + case PeerPushCreditStatus.DialogProposed: + newStatus = PeerPushCreditStatus.Aborted; + break; + case PeerPushCreditStatus.Done: + break; + case PeerPushCreditStatus.SuspendedMerge: + case PeerPushCreditStatus.SuspendedMergeKycRequired: + case PeerPushCreditStatus.SuspendedWithdrawing: + newStatus = PeerPushCreditStatus.Aborted; + break; + case PeerPushCreditStatus.PendingMergeKycRequired: + newStatus = PeerPushCreditStatus.Aborted; + break; + case PeerPushCreditStatus.PendingMerge: + newStatus = PeerPushCreditStatus.Aborted; + break; + case PeerPushCreditStatus.PendingWithdrawing: + newStatus = PeerPushCreditStatus.Aborted; + break; + case PeerPushCreditStatus.Aborted: + break; + case PeerPushCreditStatus.Failed: + break; + default: + assertUnreachable(pushCreditRec.status); + } + if (newStatus != null) { + const oldTxState = + computePeerPushCreditTransactionState(pushCreditRec); + pushCreditRec.status = newStatus; + const newTxState = + computePeerPushCreditTransactionState(pushCreditRec); + await tx.peerPushCredit.put(pushCreditRec); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }, + ); + notifyTransition(ws, transactionId, transitionInfo); + } + + async resumeTransaction(): Promise<void> { + const { ws, peerPushCreditId, retryTag, transactionId } = this; + stopLongpolling(ws, retryTag); + const transitionInfo = await ws.db.runReadWriteTx( + ["peerPushCredit"], + async (tx) => { + const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId); + if (!pushCreditRec) { + logger.warn(`peer push credit ${peerPushCreditId} not found`); + return; + } + let newStatus: PeerPushCreditStatus | undefined = undefined; + switch (pushCreditRec.status) { + case PeerPushCreditStatus.DialogProposed: + case PeerPushCreditStatus.Done: + case PeerPushCreditStatus.PendingMergeKycRequired: + case PeerPushCreditStatus.PendingMerge: + case PeerPushCreditStatus.PendingWithdrawing: + case PeerPushCreditStatus.SuspendedMerge: + newStatus = PeerPushCreditStatus.PendingMerge; + break; + case PeerPushCreditStatus.SuspendedMergeKycRequired: + newStatus = PeerPushCreditStatus.PendingMergeKycRequired; + break; + case PeerPushCreditStatus.SuspendedWithdrawing: + // FIXME: resume underlying "internal-withdrawal" transaction. + newStatus = PeerPushCreditStatus.PendingWithdrawing; + break; + case PeerPushCreditStatus.Aborted: + break; + case PeerPushCreditStatus.Failed: + break; + default: + assertUnreachable(pushCreditRec.status); + } + if (newStatus != null) { + const oldTxState = + computePeerPushCreditTransactionState(pushCreditRec); + pushCreditRec.status = newStatus; + const newTxState = + computePeerPushCreditTransactionState(pushCreditRec); + await tx.peerPushCredit.put(pushCreditRec); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }, + ); + ws.workAvailable.trigger(); + notifyTransition(ws, transactionId, transitionInfo); + } + + async failTransaction(): Promise<void> { + const { ws, peerPushCreditId, retryTag, transactionId } = this; + stopLongpolling(ws, retryTag); + const transitionInfo = await ws.db.runReadWriteTx( + ["peerPushCredit"], + async (tx) => { + const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId); + if (!pushCreditRec) { + logger.warn(`peer push credit ${peerPushCreditId} not found`); + return; + } + let newStatus: PeerPushCreditStatus | undefined = undefined; + switch (pushCreditRec.status) { + case PeerPushCreditStatus.Done: + case PeerPushCreditStatus.Aborted: + case PeerPushCreditStatus.Failed: + // Already in a final state. + return; + case PeerPushCreditStatus.DialogProposed: + case PeerPushCreditStatus.PendingMergeKycRequired: + case PeerPushCreditStatus.PendingMerge: + case PeerPushCreditStatus.PendingWithdrawing: + case PeerPushCreditStatus.SuspendedMerge: + case PeerPushCreditStatus.SuspendedMergeKycRequired: + case PeerPushCreditStatus.SuspendedWithdrawing: + newStatus = PeerPushCreditStatus.Failed; + break; + default: + assertUnreachable(pushCreditRec.status); + } + if (newStatus != null) { + const oldTxState = + computePeerPushCreditTransactionState(pushCreditRec); + pushCreditRec.status = newStatus; + const newTxState = + computePeerPushCreditTransactionState(pushCreditRec); + await tx.peerPushCredit.put(pushCreditRec); + return { + oldTxState, + newTxState, + }; + } + return undefined; + }, + ); + ws.workAvailable.trigger(); + notifyTransition(ws, transactionId, transitionInfo); + } +} + export async function preparePeerPushCredit( ws: InternalWalletState, req: PreparePeerPushCreditRequest, @@ -688,200 +952,6 @@ export async function confirmPeerPushCredit( }; } -export async function suspendPeerPushCreditTransaction( - ws: InternalWalletState, - peerPushCreditId: string, -) { - const taskId = constructTaskIdentifier({ - tag: PendingTaskType.PeerPushCredit, - peerPushCreditId, - }); - const transactionId = constructTransactionIdentifier({ - tag: TransactionType.PeerPushCredit, - peerPushCreditId, - }); - stopLongpolling(ws, taskId); - const transitionInfo = await ws.db - .mktx((x) => [x.peerPushCredit]) - .runReadWrite(async (tx) => { - const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId); - if (!pushCreditRec) { - logger.warn(`peer push credit ${peerPushCreditId} not found`); - return; - } - let newStatus: PeerPushCreditStatus | undefined = undefined; - switch (pushCreditRec.status) { - case PeerPushCreditStatus.DialogProposed: - case PeerPushCreditStatus.Done: - case PeerPushCreditStatus.SuspendedMerge: - case PeerPushCreditStatus.SuspendedMergeKycRequired: - case PeerPushCreditStatus.SuspendedWithdrawing: - break; - case PeerPushCreditStatus.PendingMergeKycRequired: - newStatus = PeerPushCreditStatus.SuspendedMergeKycRequired; - break; - case PeerPushCreditStatus.PendingMerge: - newStatus = PeerPushCreditStatus.SuspendedMerge; - break; - case PeerPushCreditStatus.PendingWithdrawing: - // FIXME: Suspend internal withdrawal transaction! - newStatus = PeerPushCreditStatus.SuspendedWithdrawing; - break; - case PeerPushCreditStatus.Aborted: - break; - case PeerPushCreditStatus.Failed: - break; - default: - assertUnreachable(pushCreditRec.status); - } - if (newStatus != null) { - const oldTxState = computePeerPushCreditTransactionState(pushCreditRec); - pushCreditRec.status = newStatus; - const newTxState = computePeerPushCreditTransactionState(pushCreditRec); - await tx.peerPushCredit.put(pushCreditRec); - return { - oldTxState, - newTxState, - }; - } - return undefined; - }); - notifyTransition(ws, transactionId, transitionInfo); -} - -export async function abortPeerPushCreditTransaction( - ws: InternalWalletState, - peerPushCreditId: string, -) { - const taskId = constructTaskIdentifier({ - tag: PendingTaskType.PeerPushCredit, - peerPushCreditId, - }); - const transactionId = constructTransactionIdentifier({ - tag: TransactionType.PeerPushCredit, - peerPushCreditId, - }); - stopLongpolling(ws, taskId); - const transitionInfo = await ws.db - .mktx((x) => [x.peerPushCredit]) - .runReadWrite(async (tx) => { - const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId); - if (!pushCreditRec) { - logger.warn(`peer push credit ${peerPushCreditId} not found`); - return; - } - let newStatus: PeerPushCreditStatus | undefined = undefined; - switch (pushCreditRec.status) { - case PeerPushCreditStatus.DialogProposed: - newStatus = PeerPushCreditStatus.Aborted; - break; - case PeerPushCreditStatus.Done: - break; - case PeerPushCreditStatus.SuspendedMerge: - case PeerPushCreditStatus.SuspendedMergeKycRequired: - case PeerPushCreditStatus.SuspendedWithdrawing: - newStatus = PeerPushCreditStatus.Aborted; - break; - case PeerPushCreditStatus.PendingMergeKycRequired: - newStatus = PeerPushCreditStatus.Aborted; - break; - case PeerPushCreditStatus.PendingMerge: - newStatus = PeerPushCreditStatus.Aborted; - break; - case PeerPushCreditStatus.PendingWithdrawing: - newStatus = PeerPushCreditStatus.Aborted; - break; - case PeerPushCreditStatus.Aborted: - break; - case PeerPushCreditStatus.Failed: - break; - default: - assertUnreachable(pushCreditRec.status); - } - if (newStatus != null) { - const oldTxState = computePeerPushCreditTransactionState(pushCreditRec); - pushCreditRec.status = newStatus; - const newTxState = computePeerPushCreditTransactionState(pushCreditRec); - await tx.peerPushCredit.put(pushCreditRec); - return { - oldTxState, - newTxState, - }; - } - return undefined; - }); - notifyTransition(ws, transactionId, transitionInfo); -} - -export async function failPeerPushCreditTransaction( - ws: InternalWalletState, - peerPushCreditId: string, -) { - // We don't have any "aborting" states! - throw Error("can't run cancel-aborting on peer-push-credit transaction"); -} - -export async function resumePeerPushCreditTransaction( - ws: InternalWalletState, - peerPushCreditId: string, -) { - const taskId = constructTaskIdentifier({ - tag: PendingTaskType.PeerPushCredit, - peerPushCreditId, - }); - const transactionId = constructTransactionIdentifier({ - tag: TransactionType.PeerPushCredit, - peerPushCreditId, - }); - stopLongpolling(ws, taskId); - const transitionInfo = await ws.db - .mktx((x) => [x.peerPushCredit]) - .runReadWrite(async (tx) => { - const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId); - if (!pushCreditRec) { - logger.warn(`peer push credit ${peerPushCreditId} not found`); - return; - } - let newStatus: PeerPushCreditStatus | undefined = undefined; - switch (pushCreditRec.status) { - case PeerPushCreditStatus.DialogProposed: - case PeerPushCreditStatus.Done: - case PeerPushCreditStatus.PendingMergeKycRequired: - case PeerPushCreditStatus.PendingMerge: - case PeerPushCreditStatus.PendingWithdrawing: - case PeerPushCreditStatus.SuspendedMerge: - newStatus = PeerPushCreditStatus.PendingMerge; - break; - case PeerPushCreditStatus.SuspendedMergeKycRequired: - newStatus = PeerPushCreditStatus.PendingMergeKycRequired; - break; - case PeerPushCreditStatus.SuspendedWithdrawing: - // FIXME: resume underlying "internal-withdrawal" transaction. - newStatus = PeerPushCreditStatus.PendingWithdrawing; - break; - case PeerPushCreditStatus.Aborted: - break; - case PeerPushCreditStatus.Failed: - break; - default: - assertUnreachable(pushCreditRec.status); - } - if (newStatus != null) { - const oldTxState = computePeerPushCreditTransactionState(pushCreditRec); - pushCreditRec.status = newStatus; - const newTxState = computePeerPushCreditTransactionState(pushCreditRec); - await tx.peerPushCredit.put(pushCreditRec); - return { - oldTxState, - newTxState, - }; - } - return undefined; - }); - ws.workAvailable.trigger(); - notifyTransition(ws, transactionId, transitionInfo); -} - export function computePeerPushCreditTransactionState( pushCreditRecord: PeerPushPaymentIncomingRecord, ): TransactionState { |