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 --- .../src/operations/transactions.ts | 416 +++------------------ 1 file changed, 44 insertions(+), 372 deletions(-) (limited to 'packages/taler-wallet-core/src/operations/transactions.ts') diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index 142eff7c1..908aa540a 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -79,61 +79,45 @@ import { constructTaskIdentifier, resetPendingTaskTimeout, TaskIdentifiers, - TombstoneTag, + TransactionContext, } from "./common.js"; import { - abortDepositGroup, computeDepositTransactionActions, computeDepositTransactionStatus, - deleteDepositGroup, - failDepositTransaction, - resumeDepositGroup, - suspendDepositGroup, + DepositTransactionContext, } from "./deposits.js"; import { ExchangeWireDetails, getExchangeWireDetailsInTx, } from "./exchanges.js"; import { - abortPayMerchant, computePayMerchantTransactionActions, computePayMerchantTransactionState, computeRefundTransactionState, expectProposalDownload, extractContractData, - failPaymentTransaction, - resumePayMerchant, - suspendPayMerchant, + PayMerchantTransactionContext, + RefundTransactionContext, } from "./pay-merchant.js"; import { - abortPeerPullCreditTransaction, computePeerPullCreditTransactionActions, computePeerPullCreditTransactionState, - failPeerPullCreditTransaction, - resumePeerPullCreditTransaction, - suspendPeerPullCreditTransaction, + PeerPullCreditTransactionContext, } from "./pay-peer-pull-credit.js"; import { computePeerPullDebitTransactionActions, computePeerPullDebitTransactionState, PeerPullDebitTransactionContext, - suspendPeerPullDebitTransaction, } from "./pay-peer-pull-debit.js"; import { - abortPeerPushCreditTransaction, computePeerPushCreditTransactionActions, computePeerPushCreditTransactionState, - failPeerPushCreditTransaction, - resumePeerPushCreditTransaction, - suspendPeerPushCreditTransaction, + PeerPushCreditTransactionContext, } from "./pay-peer-push-credit.js"; import { - abortPeerPushDebitTransaction, computePeerPushDebitTransactionActions, computePeerPushDebitTransactionState, - failPeerPushDebitTransaction, - resumePeerPushDebitTransaction, - suspendPeerPushDebitTransaction, + PeerPushDebitTransactionContext, } from "./pay-peer-push-debit.js"; import { iterRecordsForDeposit, @@ -148,29 +132,20 @@ import { iterRecordsForWithdrawal, } from "./pending.js"; import { - abortRefreshGroup, computeRefreshTransactionActions, computeRefreshTransactionState, - failRefreshGroup, - resumeRefreshGroup, - suspendRefreshGroup, + RefreshTransactionContext, } from "./refresh.js"; import { - abortTipTransaction, computeRewardTransactionStatus, computeTipTransactionActions, - failTipTransaction, - resumeTipTransaction, - suspendRewardTransaction, + RewardTransactionContext, } from "./reward.js"; import { - abortWithdrawalTransaction, augmentPaytoUrisForWithdrawal, computeWithdrawalTransactionActions, computeWithdrawalTransactionStatus, - failWithdrawalTransaction, - resumeWithdrawalTransaction, - suspendWithdrawalTransaction, + WithdrawTransactionContext, } from "./withdraw.js"; const logger = new Logger("taler-wallet-core:transactions.ts"); @@ -1565,100 +1540,61 @@ export async function retryTransaction( } } -/** - * Suspends a pending transaction, stopping any associated network activities, - * but with a chance of trying again at a later time. This could be useful if - * a user needs to save battery power or bandwidth and an operation is expected - * to take longer (such as a backup, recovery or very large withdrawal operation). - */ -export async function suspendTransaction( +async function getContextForTransaction( ws: InternalWalletState, transactionId: string, -): Promise { +): Promise { const tx = parseTransactionIdentifier(transactionId); if (!tx) { throw Error("invalid transaction ID"); } switch (tx.tag) { case TransactionType.Deposit: - await suspendDepositGroup(ws, tx.depositGroupId); - return; + return new DepositTransactionContext(ws, tx.depositGroupId); case TransactionType.Refresh: - await suspendRefreshGroup(ws, tx.refreshGroupId); - return; + return new RefreshTransactionContext(ws, tx.refreshGroupId); case TransactionType.InternalWithdrawal: case TransactionType.Withdrawal: - await suspendWithdrawalTransaction(ws, tx.withdrawalGroupId); - return; + return new WithdrawTransactionContext(ws, tx.withdrawalGroupId); case TransactionType.Payment: - await suspendPayMerchant(ws, tx.proposalId); - return; + return new PayMerchantTransactionContext(ws, tx.proposalId); case TransactionType.PeerPullCredit: - await suspendPeerPullCreditTransaction(ws, tx.pursePub); - break; + return new PeerPullCreditTransactionContext(ws, tx.pursePub); case TransactionType.PeerPushDebit: - await suspendPeerPushDebitTransaction(ws, tx.pursePub); - break; + return new PeerPushDebitTransactionContext(ws, tx.pursePub); case TransactionType.PeerPullDebit: - await suspendPeerPullDebitTransaction(ws, tx.peerPullDebitId); - break; + return new PeerPullDebitTransactionContext(ws, tx.peerPullDebitId); case TransactionType.PeerPushCredit: - await suspendPeerPushCreditTransaction(ws, tx.peerPushCreditId); - break; + return new PeerPushCreditTransactionContext(ws, tx.peerPushCreditId); case TransactionType.Refund: - throw Error("refund transactions can't be suspended or resumed"); + return new RefundTransactionContext(ws, tx.refundGroupId); case TransactionType.Reward: - await suspendRewardTransaction(ws, tx.walletRewardId); - break; + return new RewardTransactionContext(ws, tx.walletRewardId); default: assertUnreachable(tx); } } +/** + * Suspends a pending transaction, stopping any associated network activities, + * but with a chance of trying again at a later time. This could be useful if + * a user needs to save battery power or bandwidth and an operation is expected + * to take longer (such as a backup, recovery or very large withdrawal operation). + */ +export async function suspendTransaction( + ws: InternalWalletState, + transactionId: string, +): Promise { + const ctx = await getContextForTransaction(ws, transactionId); + await ctx.suspendTransaction(); +} + export async function failTransaction( ws: InternalWalletState, transactionId: string, ): Promise { - const tx = parseTransactionIdentifier(transactionId); - if (!tx) { - throw Error("invalid transaction ID"); - } - switch (tx.tag) { - case TransactionType.Deposit: - await failDepositTransaction(ws, tx.depositGroupId); - return; - case TransactionType.InternalWithdrawal: - case TransactionType.Withdrawal: - await failWithdrawalTransaction(ws, tx.withdrawalGroupId); - return; - case TransactionType.Payment: - await failPaymentTransaction(ws, tx.proposalId); - return; - case TransactionType.Refund: - throw Error("can't do cancel-aborting on refund transaction"); - case TransactionType.Reward: - await failTipTransaction(ws, tx.walletRewardId); - return; - case TransactionType.Refresh: - await failRefreshGroup(ws, tx.refreshGroupId); - return; - case TransactionType.PeerPullCredit: - await failPeerPullCreditTransaction(ws, tx.pursePub); - return; - case TransactionType.PeerPullDebit: { - const ctx = new PeerPullDebitTransactionContext(ws, tx.peerPullDebitId); - await ctx.failTransaction(); - return; - } - case TransactionType.PeerPushCredit: - await failPeerPushCreditTransaction(ws, tx.peerPushCreditId); - return; - case TransactionType.PeerPushDebit: - await failPeerPushDebitTransaction(ws, tx.pursePub); - return; - default: - assertUnreachable(tx); - } + const ctx = await getContextForTransaction(ws, transactionId); + await ctx.failTransaction(); } /** @@ -1668,44 +1604,8 @@ export async function resumeTransaction( ws: InternalWalletState, transactionId: string, ): Promise { - const tx = parseTransactionIdentifier(transactionId); - if (!tx) { - throw Error("invalid transaction ID"); - } - switch (tx.tag) { - case TransactionType.Deposit: - await resumeDepositGroup(ws, tx.depositGroupId); - return; - case TransactionType.Refresh: - await resumeRefreshGroup(ws, tx.refreshGroupId); - return; - case TransactionType.InternalWithdrawal: - case TransactionType.Withdrawal: - await resumeWithdrawalTransaction(ws, tx.withdrawalGroupId); - return; - case TransactionType.Payment: - await resumePayMerchant(ws, tx.proposalId); - return; - case TransactionType.PeerPullCredit: - await resumePeerPullCreditTransaction(ws, tx.pursePub); - break; - case TransactionType.PeerPushDebit: - await resumePeerPushDebitTransaction(ws, tx.pursePub); - break; - case TransactionType.PeerPullDebit: { - const ctx = new PeerPullDebitTransactionContext(ws, tx.peerPullDebitId); - await ctx.resumeTransaction(); - return; - } - case TransactionType.PeerPushCredit: - await resumePeerPushCreditTransaction(ws, tx.peerPushCreditId); - break; - case TransactionType.Refund: - throw Error("refund transactions can't be suspended or resumed"); - case TransactionType.Reward: - await resumeTipTransaction(ws, tx.walletRewardId); - break; - } + const ctx = await getContextForTransaction(ws, transactionId); + await ctx.resumeTransaction(); } /** @@ -1715,244 +1615,16 @@ export async function deleteTransaction( ws: InternalWalletState, transactionId: string, ): Promise { - const parsedTx = parseTransactionIdentifier(transactionId); - - if (!parsedTx) { - throw Error("invalid transaction ID"); - } - - switch (parsedTx.tag) { - case TransactionType.PeerPushCredit: { - const peerPushCreditId = parsedTx.peerPushCreditId; - await ws.db - .mktx((x) => [x.withdrawalGroups, x.peerPushCredit, x.tombstones]) - .runReadWrite(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; - } - - case TransactionType.PeerPullCredit: { - const pursePub = parsedTx.pursePub; - await ws.db - .mktx((x) => [x.withdrawalGroups, x.peerPullCredit, x.tombstones]) - .runReadWrite(async (tx) => { - const pullIni = await tx.peerPullCredit.get(pursePub); - if (!pullIni) { - return; - } - if (pullIni.withdrawalGroupId) { - const withdrawalGroupId = pullIni.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.peerPullCredit.delete(pursePub); - await tx.tombstones.put({ - id: TombstoneTag.DeletePeerPullCredit + ":" + pursePub, - }); - }); - - return; - } - - case TransactionType.Withdrawal: { - const withdrawalGroupId = parsedTx.withdrawalGroupId; - 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; - } - }); - return; - } - - case TransactionType.Payment: { - const proposalId = parsedTx.proposalId; - await ws.db - .mktx((x) => [x.purchases, x.tombstones]) - .runReadWrite(async (tx) => { - let found = false; - const purchase = await tx.purchases.get(proposalId); - if (purchase) { - found = true; - await tx.purchases.delete(proposalId); - } - if (found) { - await tx.tombstones.put({ - id: TombstoneTag.DeletePayment + ":" + proposalId, - }); - } - }); - return; - } - - case TransactionType.Refresh: { - const refreshGroupId = parsedTx.refreshGroupId; - await ws.db - .mktx((x) => [x.refreshGroups, x.tombstones]) - .runReadWrite(async (tx) => { - const rg = await tx.refreshGroups.get(refreshGroupId); - if (rg) { - await tx.refreshGroups.delete(refreshGroupId); - await tx.tombstones.put({ - id: TombstoneTag.DeleteRefreshGroup + ":" + refreshGroupId, - }); - } - }); - - return; - } - - case TransactionType.Reward: { - const tipId = parsedTx.walletRewardId; - await ws.db - .mktx((x) => [x.rewards, x.tombstones]) - .runReadWrite(async (tx) => { - const tipRecord = await tx.rewards.get(tipId); - if (tipRecord) { - await tx.rewards.delete(tipId); - await tx.tombstones.put({ - id: TombstoneTag.DeleteReward + ":" + tipId, - }); - } - }); - return; - } - - case TransactionType.Deposit: { - const depositGroupId = parsedTx.depositGroupId; - await deleteDepositGroup(ws, depositGroupId); - return; - } - - case TransactionType.Refund: { - const refundGroupId = parsedTx.refundGroupId; - await ws.db - .mktx((x) => [x.refundGroups, x.tombstones]) - .runReadWrite(async (tx) => { - const refundRecord = await tx.refundGroups.get(refundGroupId); - if (!refundRecord) { - return; - } - await tx.refundGroups.delete(refundGroupId); - await tx.tombstones.put({ id: transactionId }); - // FIXME: Also tombstone the refund items, so that they won't reappear. - }); - return; - } - - case TransactionType.PeerPullDebit: { - const peerPullDebitId = parsedTx.peerPullDebitId; - await ws.db - .mktx((x) => [x.peerPullDebit, x.tombstones]) - .runReadWrite(async (tx) => { - const debit = await tx.peerPullDebit.get(peerPullDebitId); - if (debit) { - await tx.peerPullDebit.delete(peerPullDebitId); - await tx.tombstones.put({ id: transactionId }); - } - }); - - return; - } - - case TransactionType.PeerPushDebit: { - const pursePub = parsedTx.pursePub; - await ws.db - .mktx((x) => [x.peerPushDebit, x.tombstones]) - .runReadWrite(async (tx) => { - const debit = await tx.peerPushDebit.get(pursePub); - if (debit) { - await tx.peerPushDebit.delete(pursePub); - await tx.tombstones.put({ id: transactionId }); - } - }); - return; - } - } + const ctx = await getContextForTransaction(ws, transactionId); + await ctx.deleteTransaction(); } export async function abortTransaction( ws: InternalWalletState, transactionId: string, ): Promise { - const txId = parseTransactionIdentifier(transactionId); - if (!txId) { - throw Error("invalid transaction identifier"); - } - - switch (txId.tag) { - case TransactionType.Payment: { - await abortPayMerchant(ws, txId.proposalId); - break; - } - case TransactionType.Withdrawal: - case TransactionType.InternalWithdrawal: { - await abortWithdrawalTransaction(ws, txId.withdrawalGroupId); - break; - } - case TransactionType.Deposit: - await abortDepositGroup(ws, txId.depositGroupId); - break; - case TransactionType.Reward: - await abortTipTransaction(ws, txId.walletRewardId); - break; - case TransactionType.Refund: - throw Error("can't abort refund transactions"); - case TransactionType.Refresh: - await abortRefreshGroup(ws, txId.refreshGroupId); - break; - case TransactionType.PeerPullCredit: - await abortPeerPullCreditTransaction(ws, txId.pursePub); - break; - case TransactionType.PeerPullDebit: { - const ctx = new PeerPullDebitTransactionContext(ws, txId.peerPullDebitId); - await ctx.abortTransaction(); - return; - } - case TransactionType.PeerPushCredit: - await abortPeerPushCreditTransaction(ws, txId.peerPushCreditId); - break; - case TransactionType.PeerPushDebit: - await abortPeerPushDebitTransaction(ws, txId.pursePub); - break; - default: { - assertUnreachable(txId); - } - } + const ctx = await getContextForTransaction(ws, transactionId); + await ctx.abortTransaction(); } export interface TransitionInfo { -- cgit v1.2.3