taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 339b7883a7f2d3be9856a440fe74e3546ec5f6a2
parent c798bd1cd67add48b805f512b5072502d558651a
Author: Florian Dold <dold@taler.net>
Date:   Fri, 24 Jul 2026 16:13:46 +0200

wallet-core: add causeHint to transaction transition notification

Diffstat:
Mpackages/taler-util/src/notifications.ts | 7+++++++
Mpackages/taler-wallet-core/src/common.ts | 19+++++++++++++++++--
Mpackages/taler-wallet-core/src/deposits.ts | 19+++++++++++--------
Mpackages/taler-wallet-core/src/exchanges.ts | 4++++
Mpackages/taler-wallet-core/src/pay-merchant.ts | 57++++++++++++++++++++++++++++++---------------------------
Mpackages/taler-wallet-core/src/pay-peer-pull-credit.ts | 69++++++++++++++++++++++++++++++++++++---------------------------------
Mpackages/taler-wallet-core/src/pay-peer-pull-debit.ts | 28++++++++++++++--------------
Mpackages/taler-wallet-core/src/pay-peer-push-credit.ts | 42+++++++++++++++++++++---------------------
Mpackages/taler-wallet-core/src/pay-peer-push-debit.ts | 32++++++++++++++++----------------
Mpackages/taler-wallet-core/src/refresh.ts | 27++++++++++++++-------------
Mpackages/taler-wallet-core/src/shepherd.ts | 1+
Mpackages/taler-wallet-core/src/transactions.ts | 8++++++++
Mpackages/taler-wallet-core/src/withdraw.ts | 50+++++++++++++++++++++++++++++---------------------
13 files changed, 208 insertions(+), 155 deletions(-)

diff --git a/packages/taler-util/src/notifications.ts b/packages/taler-util/src/notifications.ts @@ -64,6 +64,13 @@ export interface TransactionStateTransitionNotification { transactionId: string; /** + * A hint as to why the transition happened. + * + * Should not be relied upon by clients. + */ + causeHint: string | undefined; + + /** * State before the transition. */ oldTxState: TransactionState; diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts @@ -1065,7 +1065,17 @@ export async function cancelableFetch( export interface RecordHandle<T> { rec: T | undefined; - update(newRec: T | undefined, eff?: BalanceEffect): Promise<void>; + /** + * Store the new state of the record and notify about the transition. + * + * The cause hint is a short description of what caused the transition, + * for debugging only. + */ + update( + newRec: T | undefined, + causeHint: string, + eff?: BalanceEffect, + ): Promise<void>; } /** @@ -1091,7 +1101,11 @@ export async function getGenericRecordHandle<T>( oldTxState = { major: TransactionMajorState.None }; oldStId = 0; } - const update = async (newRec: T | undefined, eff?: BalanceEffect) => { + const update = async ( + newRec: T | undefined, + causeHint: string, + eff?: BalanceEffect, + ) => { let newTxState: TransactionState; let newStId: number; if (newRec != null) { @@ -1111,6 +1125,7 @@ export async function getGenericRecordHandle<T>( oldTxState, newTxState, balanceEffect: eff ?? BalanceEffect.Any, + causeHint, oldStId, newStId, }); diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts @@ -367,7 +367,7 @@ export class DepositTransactionContext implements TransactionContext { if (!rec) { return; } - await h.update(undefined); + await h.update(undefined, "delete"); } async userSuspendTransaction(): Promise<void> { @@ -422,7 +422,7 @@ export class DepositTransactionContext implements TransactionContext { return undefined; } dg.operationStatus = newOpStatus; - await h.update(dg); + await h.update(dg, "suspend"); }); wex.taskScheduler.stopShepherdTask(retryTag); } @@ -451,6 +451,7 @@ export class DepositTransactionContext implements TransactionContext { await tx.upsertDepositGroup(dg); await this.updateTransactionMeta(tx); applyNotifyTransition(tx.notify, transactionId, { + causeHint: "abort", oldTxState: oldState, newTxState: computeDepositTransactionStatus(dg), balanceEffect: BalanceEffect.Any, @@ -537,6 +538,7 @@ export class DepositTransactionContext implements TransactionContext { await tx.upsertDepositGroup(dg); await this.updateTransactionMeta(tx); applyNotifyTransition(tx.notify, transactionId, { + causeHint: "resume", oldTxState: oldState, newTxState: computeDepositTransactionStatus(dg), balanceEffect: BalanceEffect.None, @@ -594,6 +596,7 @@ export class DepositTransactionContext implements TransactionContext { await tx.upsertDepositGroup(dg); await this.updateTransactionMeta(tx); applyNotifyTransition(tx.notify, transactionId, { + causeHint: "user-fail", oldTxState: oldState, newTxState: computeDepositTransactionStatus(dg), balanceEffect: BalanceEffect.Any, @@ -1016,7 +1019,7 @@ async function waitForRefreshOnDepositGroup( return false; } newDg.operationStatus = newOpState; - await h.update(newDg); + await h.update(newDg, "refresh-done"); return true; }); if (didTransition) { @@ -1201,7 +1204,7 @@ async function processDepositGroupPendingKyc( return TaskRunResult.backoff(); } newDg.kycInfo = kycInfo; - await h.update(newDg); + await h.update(newDg, "kyc-status"); return algoRes.taskResult; }); } @@ -1417,7 +1420,7 @@ async function transitionToKycRequired( lastBadKycAuth: args.badKycAuth, }; } - await h.update(dg); + await h.update(dg, "kyc-required"); }); return TaskRunResult.progress(); } @@ -1570,7 +1573,7 @@ async function processDepositGroupTrack( await tx.upsertDepositGroup(dg); await ctx.updateTransactionMeta(tx); } - await h.update(dg); + await h.update(dg, "track"); }); if (allWired) { return TaskRunResult.finished(); @@ -1878,7 +1881,7 @@ async function processDepositGroupPendingDeposit( return undefined; } dg.operationStatus = DepositOperationStatus.FinalizingTrack; - await h.update(dg); + await h.update(dg, "deposit"); }); return TaskRunResult.progress(); } @@ -2332,7 +2335,7 @@ async function internalCreateDepositGroup( contractTermsRaw: contractTerms, h: contractTermsHash, }); - await h.update(depositGroup); + await h.update(depositGroup, "create"); return computeDepositTransactionStatus(depositGroup); }); diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts @@ -2408,6 +2408,7 @@ async function handleDenomLoss( await ctx.updateTransactionMeta(tx); tx.notify({ type: NotificationType.TransactionStateTransition, + causeHint: "denom-vanished", transactionId: ctx.transactionId, oldTxState: { major: TransactionMajorState.None, @@ -2440,6 +2441,7 @@ async function handleDenomLoss( await ctx.updateTransactionMeta(tx); tx.notify({ type: NotificationType.TransactionStateTransition, + causeHint: "denom-unoffered", transactionId: ctx.transactionId, oldTxState: { major: TransactionMajorState.None, @@ -2474,6 +2476,7 @@ async function handleDenomLoss( }); tx.notify({ type: NotificationType.TransactionStateTransition, + causeHint: "denom-expired", transactionId, oldTxState: { major: TransactionMajorState.None, @@ -2570,6 +2573,7 @@ export class DenomLossTransactionContext implements TransactionContext { const oldTxState = computeDenomLossTransactionStatus(rec); await tx.deleteDenomLossEvent(this.denomLossEventId); applyNotifyTransition(tx.notify, this.transactionId, { + causeHint: "delete", oldTxState, newTxState: { major: TransactionMajorState.Deleted, diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts @@ -448,7 +448,7 @@ export class PayMerchantTransactionContext implements TransactionContext { ); await otherCtx.deleteTransactionInTx(tx, { keepRelated: true }); } - await h.update(undefined); + await h.update(undefined, "delete"); } async userSuspendTransaction(): Promise<void> { @@ -464,7 +464,7 @@ export class PayMerchantTransactionContext implements TransactionContext { return; } purchase.purchaseStatus = newStatus.next; - await h.update(purchase); + await h.update(purchase, "suspend"); }); } @@ -480,7 +480,7 @@ export class PayMerchantTransactionContext implements TransactionContext { } purchase.purchaseStatus = PurchaseStatus.Failed; purchase.failReason = reason; - await h.update(purchase); + await h.update(purchase, "fail"); }); } @@ -517,7 +517,7 @@ export class PayMerchantTransactionContext implements TransactionContext { default: return; } - await h.update(purchase); + await h.update(purchase, "abort"); }); await wex.taskScheduler.resetTask(this.taskId); } @@ -534,7 +534,7 @@ export class PayMerchantTransactionContext implements TransactionContext { return; } purchase.purchaseStatus = newStatus.next; - await h.update(purchase, BalanceEffect.Any); + await h.update(purchase, "resume", BalanceEffect.Any); }); await wex.taskScheduler.resetTask(this.taskId); } @@ -554,7 +554,7 @@ export class PayMerchantTransactionContext implements TransactionContext { default: return; } - await h.update(purchase, BalanceEffect.Any); + await h.update(purchase, "user-fail", BalanceEffect.Any); tx.scheduleOnCommit(() => wex.taskScheduler.stopShepherdTask(this.taskId), ); @@ -868,7 +868,7 @@ async function failProposalClaimPermanently( } p.purchaseStatus = PurchaseStatus.FailedClaim; p.failReason = err; - await h.update(p); + await h.update(p, "claim-failed"); }); } @@ -1228,7 +1228,7 @@ async function processDownloadProposal( } // Downloading the proposal only presents the offer to the user, no // funds are committed until the payment is confirmed. - await h.update(p, BalanceEffect.None); + await h.update(p, "download-proposal", BalanceEffect.None); }); return TaskRunResult.progress(); @@ -1249,7 +1249,7 @@ async function startPayReplay( } // The coins were already spent by the original payment, replaying it // for a new session does not spend anything on top of that. - await h.update(rec, BalanceEffect.None); + await h.update(rec, "start-replay", BalanceEffect.None); tx.scheduleOnCommit(() => { wex.taskScheduler.resetTask(ctx.taskId).catch((e) => { logger.error(safeStringifyException(e)); @@ -1441,7 +1441,7 @@ async function createOrReusePurchase( return; } rec.purchaseStatus = PurchaseStatus.FailedPaidByOther; - await h.update(rec); + await h.update(rec, "paid-by-other"); }); } } @@ -1510,6 +1510,7 @@ async function createOrReusePurchase( }; const newTxState = computePayMerchantTransactionState(proposalRecord); applyNotifyTransition(tx.notify, ctx.transactionId, { + causeHint: "create", oldTxState, newTxState, balanceEffect: BalanceEffect.None, @@ -1578,7 +1579,7 @@ async function storeFirstPaySuccess( ), ); } - await h.update(purchase); + await h.update(purchase, "pay-success"); }); } @@ -1606,7 +1607,7 @@ async function storePayReplaySuccess( purchase.lastSessionId = sessionId; // A replay only demonstrates the earlier payment to the merchant, // no additional funds are spent. - await h.update(purchase, BalanceEffect.None); + await h.update(purchase, "replay-success", BalanceEffect.None); }); } @@ -2576,7 +2577,7 @@ export async function confirmPay( p.lastSessionId = sessionId; p.timestampAccept = timestampPreciseToDb(TalerPreciseTimestamp.now()); p.purchaseStatus = PurchaseStatus.PendingPaying; - await h.update(p); + await h.update(p, "confirm-pay"); if (p.payInfo.payTokenSelection) { await spendTokens(tx, { tokenPubs: p.payInfo.payTokenSelection.tokenPubs, @@ -2767,7 +2768,7 @@ async function processPurchasePay( return; } p.purchaseStatus = PurchaseStatus.FailedPaidByOther; - await h.update(p); + await h.update(p, "pay-paid-by-other"); }); return { @@ -3307,7 +3308,7 @@ export async function refuseProposal( return; } proposal.purchaseStatus = PurchaseStatus.AbortedProposalRefused; - await h.update(proposal); + await h.update(proposal, "refuse-proposal"); }); } @@ -3661,7 +3662,7 @@ export async function sharePayment( if (p.purchaseStatus === PurchaseStatus.DialogProposed) { p.purchaseStatus = PurchaseStatus.DialogShared; p.shared = true; - await h.update(p); + await h.update(p, "share"); } return { proposalId: p.proposalId, @@ -3754,7 +3755,7 @@ async function processPurchaseDialogProposed( } r2.timestampExpired = timestampPreciseToDb(TalerPreciseTimestamp.now()); r2.purchaseStatus = PurchaseStatus.Expired; - await h.update(r2); + await h.update(r2, "proposal-expired"); }); return TaskRunResult.progress(); } @@ -3829,7 +3830,7 @@ async function processPurchaseDialogShared( default: return; } - await h.update(p); + await h.update(p, "shared-paid-by-other"); }); return TaskRunResult.progress(); } @@ -3891,7 +3892,7 @@ async function processPurchaseAutoRefund( } p.purchaseStatus = PurchaseStatus.Done; p.refundAmountAwaiting = undefined; - await h.update(p); + await h.update(p, "auto-refund-done"); }); return TaskRunResult.progress(); } @@ -3929,7 +3930,7 @@ async function processPurchaseAutoRefund( return; } rec.purchaseStatus = PurchaseStatus.PendingAcceptRefund; - await h.update(rec); + await h.update(rec, "auto-refund-found"); }); return TaskRunResult.progress(); } @@ -3956,7 +3957,7 @@ async function processPurchaseAbortingRefund( return; } rec.purchaseStatus = PurchaseStatus.AbortedOrderDeleted; - await h.update(rec); + await h.update(rec, "abort-unpaid"); }); return TaskRunResult.finished(); } @@ -3985,7 +3986,7 @@ async function processPurchaseAbortingRefund( ctx.transactionId, ); rec.abortRefreshGroupId = res.refreshGroupId; - await h.update(rec); + await h.update(rec, "abort-refresh"); } for (let i = 0; i < payCoinSelection.coinPubs.length; i++) { @@ -4028,7 +4029,7 @@ async function processPurchaseAbortingRefund( return; } rec.purchaseStatus = PurchaseStatus.AbortedOrderDeleted; - await h.update(rec); + await h.update(rec, "abort-order-gone"); }); return TaskRunResult.progress(); } @@ -4108,7 +4109,7 @@ async function processPurchaseQueryRefund( } p.purchaseStatus = PurchaseStatus.Done; p.refundAmountAwaiting = undefined; - await h.update(p); + await h.update(p, "query-refund-done"); }); } else { const refundAwaiting = Amounts.sub( @@ -4123,7 +4124,7 @@ async function processPurchaseQueryRefund( } p.refundAmountAwaiting = Amounts.stringify(refundAwaiting); p.purchaseStatus = PurchaseStatus.PendingAcceptRefund; - await h.update(p); + await h.update(p, "query-refund-found"); }); } return TaskRunResult.progress(); @@ -4212,7 +4213,7 @@ export async function startQueryRefund( return; } p.purchaseStatus = PurchaseStatus.PendingQueryingRefund; - await h.update(p); + await h.update(p, "start-query-refund"); }); wex.taskScheduler.startShepherdTask(ctx.taskId); } @@ -4398,6 +4399,7 @@ async function storeRefunds( await tx.upsertRefundGroup(newGroup); await refundCtx.updateTransactionMeta(tx); applyNotifyTransition(tx.notify, refundCtx.transactionId, { + causeHint: "refund-group-create", oldTxState: { major: TransactionMajorState.None }, newTxState: computeRefundTransactionState(newGroup), balanceEffect: BalanceEffect.Any, @@ -4454,6 +4456,7 @@ async function storeRefunds( computeRefundTransactionState(refundGroup); const newStId = refundGroup.status; applyNotifyTransition(tx.notify, refundCtx.transactionId, { + causeHint: "refund-group-final", oldTxState, newTxState, balanceEffect: BalanceEffect.Any, @@ -4493,7 +4496,7 @@ async function storeRefunds( } myPurchase.refundAmountAwaiting = undefined; } - await h.update(myPurchase); + await h.update(myPurchase, "refunds-stored"); return { numPendingItemsTotal, diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts @@ -151,7 +151,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { ); await withdrawalCtx.deleteTransactionInTx(tx); } - await h.update(undefined); + await h.update(undefined, "delete"); } /** @@ -354,7 +354,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { default: assertUnreachable(rec.status); } - await h.update(rec); + await h.update(rec, "suspend"); }); this.wex.taskScheduler.stopShepherdTask(this.taskId); } @@ -371,7 +371,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { } rec.status = PeerPullPaymentCreditStatus.Failed; rec.failReason = reason; - await h.update(rec); + await h.update(rec, "fail"); }); } @@ -407,7 +407,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { default: assertUnreachable(rec.status); } - await h.update(rec); + await h.update(rec, "user-fail"); }); this.wex.taskScheduler.stopShepherdTask(this.taskId); } @@ -455,7 +455,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { default: assertUnreachable(rec.status); } - await h.update(rec); + await h.update(rec, "resume"); }); await this.wex.taskScheduler.resetTask(this.taskId); } @@ -496,7 +496,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { default: assertUnreachable(rec.status); } - await h.update(rec); + await h.update(rec, "abort"); }); await this.wex.taskScheduler.resetTask(this.taskId); } @@ -530,7 +530,7 @@ async function processPendingReady( default: return; } - await h.update(rec); + await h.update(rec, "ready-expired"); }); return TaskRunResult.finished(); case HttpStatusCode.NotFound: @@ -579,7 +579,7 @@ async function processPendingReady( default: return; } - await h.update(rec); + await h.update(rec, "ready-merged"); }); return TaskRunResult.progress(); } @@ -644,7 +644,7 @@ async function processPendingMergeKycRequired( rec.kycLastDeny = updatedStatus.lastDeny; rec.kycLastRuleGen = updatedStatus.lastRuleGen; rec.kycAccessToken = updatedStatus.accessToken; - await h.update(rec); + await h.update(rec, "merge-kyc-status"); }); return algoRes.taskResult; @@ -676,7 +676,7 @@ async function processPeerPullCreditAbortingDeletePurse( default: return; } - await h.update(rec); + await h.update(rec, "aborting-delete-purse"); }); return TaskRunResult.finished(); case HttpStatusCode.Forbidden: @@ -714,7 +714,7 @@ async function processPeerPullCreditWithdrawing( switch (wg.status) { case WithdrawalGroupStatus.Done: rec.status = PeerPullPaymentCreditStatus.Done; - await h.update(rec); + await h.update(rec, "withdrawing-done"); return TaskRunResult.finished(); case WithdrawalGroupStatus.AbortedBank: case WithdrawalGroupStatus.AbortedExchange: @@ -778,7 +778,7 @@ async function processPeerPullCreditCreatePurse( default: return; } - await h.update(rec); + await h.update(rec, "create-purse-balance-kyc"); }); return TaskRunResult.progress(); } @@ -888,7 +888,7 @@ async function processPeerPullCreditCreatePurse( return; } rec.status = PeerPullPaymentCreditStatus.PendingReady; - await h.update(rec); + await h.update(rec, "create-purse"); }); return TaskRunResult.backoff(); } @@ -1002,7 +1002,7 @@ async function processPeerPullCreditBalanceKyc( default: return; } - await h.update(rec); + await h.update(rec, "balance-kyc-ok"); }); return TaskRunResult.progress(); @@ -1023,7 +1023,7 @@ async function processPeerPullCreditBalanceKyc( default: return; } - await h.update(rec); + await h.update(rec, "balance-kyc-required"); }); return TaskRunResult.progress(); } else { @@ -1047,7 +1047,7 @@ async function handlePeerPullCreditKycRequired( rec.kycLastDeny = timestampPreciseToDb(TalerPreciseTimestamp.now()); rec.kycPaytoHash = kycPaytoHash; rec.status = PeerPullPaymentCreditStatus.PendingMergeKycRequired; - await h.update(rec); + await h.update(rec, "merge-kyc-required"); }); return TaskRunResult.progress(); } @@ -1231,23 +1231,26 @@ async function internalInitiatePeerPullPayment( if (oldRec) { throw Error("peer-pull-credit record already exists"); } - await h.update({ - amount: req.partialContractTerms.amount, - contractTermsHash: hContractTerms, - exchangeBaseUrl: exchangeBaseUrl, - pursePriv: pursePair.priv, - pursePub: pursePair.pub, - mergePriv: mergePair.priv, - mergePub: mergePair.pub, - status: PeerPullPaymentCreditStatus.PendingCreatePurse, - mergeTimestamp: timestampPreciseToDb(mergeTimestamp), - contractEncNonce, - mergeReserveRowId: mergeReserveRowId, - contractPriv: contractKeyPair.priv, - contractPub: contractKeyPair.pub, - withdrawalGroupId, - estimatedAmountEffective: wi.withdrawalAmountEffective, - }); + await h.update( + { + amount: req.partialContractTerms.amount, + contractTermsHash: hContractTerms, + exchangeBaseUrl: exchangeBaseUrl, + pursePriv: pursePair.priv, + pursePub: pursePair.pub, + mergePriv: mergePair.priv, + mergePub: mergePair.pub, + status: PeerPullPaymentCreditStatus.PendingCreatePurse, + mergeTimestamp: timestampPreciseToDb(mergeTimestamp), + contractEncNonce, + mergeReserveRowId: mergeReserveRowId, + contractPriv: contractKeyPair.priv, + contractPub: contractKeyPair.pub, + withdrawalGroupId, + estimatedAmountEffective: wi.withdrawalAmountEffective, + }, + "create", + ); }); wex.taskScheduler.startShepherdTask(ctx.taskId); diff --git a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts @@ -217,7 +217,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { } rec.status = PeerPullDebitRecordStatus.Failed; rec.failReason = reason; - await h.update(rec); + await h.update(rec, "fail"); }); } @@ -229,7 +229,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { return; } rec.status = PeerPullDebitRecordStatus.Expired; - await h.update(rec); + await h.update(rec, "expire"); }); } @@ -244,7 +244,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { if (!rec) { return; } - await h.update(undefined); + await h.update(undefined, "delete"); } async userSuspendTransaction(): Promise<void> { @@ -271,7 +271,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { default: assertUnreachable(rec.status); } - await h.update(rec); + await h.update(rec, "suspend"); }); this.wex.taskScheduler.stopShepherdTask(this.taskId); } @@ -301,7 +301,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { default: assertUnreachable(rec.status); } - await h.update(rec); + await h.update(rec, "resume"); }); await this.wex.taskScheduler.resetTask(this.taskId); } @@ -324,7 +324,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { default: return; } - await h.update(rec); + await h.update(rec, "user-fail"); }); this.wex.taskScheduler.stopShepherdTask(this.taskId); } @@ -371,7 +371,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { pi.abortRefreshGroupId = refresh.refreshGroupId; pi.abortReason = reason; } - await h.update(pi); + await h.update(pi, "abort"); }); await this.wex.taskScheduler.resetTask(this.taskId); } @@ -460,7 +460,7 @@ async function handlePurseCreationConflict( contributions: added.map((x) => Amounts.parseOrThrow(x.contribution)), refreshReason: RefreshReason.PayPeerPull, }); - await h.update(rec); + await h.update(rec, "purse-conflict-repair"); break; } default: @@ -506,7 +506,7 @@ async function processPeerPullDebitDialogProposed( default: return; } - await h.update(rec); + await h.update(rec, "dialog-aborted"); }); return TaskRunResult.finished(); } @@ -585,7 +585,7 @@ async function processPeerPullDebitPendingDeposit( contributions: coinSelRes.result.coins.map((x) => x.contribution), totalCost: Amounts.stringify(totalAmount), }; - await h.update(rec); + await h.update(rec, "select-coins"); return TaskRunResult.progress(); }); } @@ -650,7 +650,7 @@ async function processPeerPullDebitPendingDeposit( default: return; } - await h.update(rec); + await h.update(rec, "deposit-done"); }); return TaskRunResult.finished(); } @@ -687,7 +687,7 @@ async function processPeerPullDebitAbortingRefresh( return; } } - await h.update(rec); + await h.update(rec, "aborting-refresh-failed"); }); // FIXME: Shouldn't this be finished in some cases?! return TaskRunResult.backoff(); @@ -808,7 +808,7 @@ export async function confirmPeerPullDebit( }; } rec.status = PeerPullDebitRecordStatus.PendingDeposit; - await h.update(rec); + await h.update(rec, "confirm"); }); await wex.taskScheduler.resetTask(ctx.taskId); @@ -1042,7 +1042,7 @@ async function internalPreparePeerPullDebit( }; // Only the request is presented to the user, nothing is spent until // the payment is confirmed. - await h.update(newRec, BalanceEffect.None); + await h.update(newRec, "prepare", BalanceEffect.None); return { newRec, scopeInfo: await getExchangeScopeInfo(tx, exchangeBaseUrl, currency), diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts @@ -292,7 +292,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { if (!rec) { return; } - await h.update(undefined); + await h.update(undefined, "delete"); const withdrawalGroupId = rec.withdrawalGroupId; if (withdrawalGroupId != null) { const withdrawalCtx = new WithdrawTransactionContext( @@ -340,7 +340,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { default: assertUnreachable(rec.status); } - await h.update(rec); + await h.update(rec, "suspend"); }); this.wex.taskScheduler.stopShepherdTask(this.taskId); } @@ -373,7 +373,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { default: assertUnreachable(rec.status); } - await h.update(rec); + await h.update(rec, "abort"); }); this.wex.taskScheduler.stopShepherdTask(this.taskId); } @@ -415,7 +415,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { default: assertUnreachable(rec.status); } - await h.update(rec); + await h.update(rec, "resume"); }); await this.wex.taskScheduler.resetTask(this.taskId); } @@ -432,7 +432,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { } rec.status = PeerPushCreditStatus.Failed; rec.failReason = reason; - await h.update(rec); + await h.update(rec, "fail"); }); } @@ -466,7 +466,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { default: assertUnreachable(rec.status); } - await h.update(rec); + await h.update(rec, "user-fail"); }); this.wex.taskScheduler.stopShepherdTask(this.taskId); } @@ -674,7 +674,7 @@ async function internalPreparePeerPushCredit( }; // Only the offer is presented to the user, the incoming funds are not // counted towards the balance until the transfer is confirmed. - await h.update(newRec, BalanceEffect.None); + await h.update(newRec, "prepare", BalanceEffect.None); return { scopeInfo: await getExchangeScopeInfo(tx, exchangeBaseUrl, currency), newRec, @@ -759,7 +759,7 @@ async function processPeerPushDebitMergeKyc( rec.kycLastDeny = updatedStatus.lastDeny; rec.kycLastRuleGen = updatedStatus.lastRuleGen; rec.kycAccessToken = updatedStatus.accessToken; - await h.update(rec); + await h.update(rec, "merge-kyc-status"); }); return algoRes.taskResult; @@ -783,7 +783,7 @@ async function transitionPeerPushCreditKycRequired( peerInc.kycPaytoHash = kycPending.h_payto; peerInc.status = PeerPushCreditStatus.PendingMergeKycRequired; peerInc.kycLastDeny = timestampPreciseToDb(TalerPreciseTimestamp.now()); - await h.update(peerInc); + await h.update(peerInc, "merge-kyc-required"); return TaskRunResult.progress(); }); } @@ -822,7 +822,7 @@ async function processPendingMerge( default: return; } - await h.update(rec); + await h.update(rec, "merge-balance-kyc"); }); return TaskRunResult.progress(); } @@ -902,7 +902,7 @@ async function processPendingMerge( default: return; } - await h.update(rec); + await h.update(rec, "merge-conflict"); }); return TaskRunResult.finished(); case HttpStatusCode.Gone: @@ -923,7 +923,7 @@ async function processPendingMerge( default: return; } - await h.update(rec); + await h.update(rec, "merge-gone"); }); return TaskRunResult.finished(); case HttpStatusCode.Forbidden: @@ -968,7 +968,7 @@ async function processPendingMerge( break; } } - await h.update(peerInc); + await h.update(peerInc, "merge-done"); }); return TaskRunResult.backoff(); } @@ -997,7 +997,7 @@ async function processPendingWithdrawing( const wg = await tx.getWithdrawalGroup(wgId); if (!wg) { ppi.status = PeerPushCreditStatus.Failed; - await h.update(ppi); + await h.update(ppi, "withdrawal-missing"); return TaskRunResult.finished(); } switch (wg.status) { @@ -1009,11 +1009,11 @@ async function processPendingWithdrawing( case WithdrawalGroupStatus.FailedAbortingBank: case WithdrawalGroupStatus.FailedBankAborted: ppi.status = PeerPushCreditStatus.Failed; - await h.update(ppi); + await h.update(ppi, "withdrawal-failed"); return TaskRunResult.finished(); case WithdrawalGroupStatus.Done: ppi.status = PeerPushCreditStatus.Done; - await h.update(ppi); + await h.update(ppi, "withdrawal-done"); return TaskRunResult.finished(); case WithdrawalGroupStatus.PendingQueryingStatus: case WithdrawalGroupStatus.PendingReady: @@ -1075,7 +1075,7 @@ async function processPeerPushDebitDialogProposed( default: return; } - await h.update(rec); + await h.update(rec, "purse-gone"); }); return TaskRunResult.finished(); case HttpStatusCode.NotFound: @@ -1100,7 +1100,7 @@ async function processPeerPushDebitDialogProposed( default: return; } - await h.update(rec); + await h.update(rec, "purse-merged-other"); }); return TaskRunResult.finished(); } @@ -1222,7 +1222,7 @@ async function processPeerPushCreditBalanceKyc( case PeerPushCreditStatus.PendingBalanceKycInit: case PeerPushCreditStatus.PendingBalanceKycRequired: { rec.status = PeerPushCreditStatus.PendingMerge; - await h.update(rec); + await h.update(rec, "balance-kyc-ok"); break; } } @@ -1241,7 +1241,7 @@ async function processPeerPushCreditBalanceKyc( case PeerPushCreditStatus.PendingBalanceKycInit: { rec.status = PeerPushCreditStatus.PendingBalanceKycRequired; rec.kycAccessToken = ret.walletKycAccessToken; - await h.update(rec); + await h.update(rec, "balance-kyc-required"); } } }); @@ -1322,7 +1322,7 @@ async function internalConfirmPeerPushCredit( switch (rec.status) { case PeerPushCreditStatus.DialogProposed: { rec.status = PeerPushCreditStatus.PendingMerge; - await h.update(rec); + await h.update(rec, "confirm"); break; } } diff --git a/packages/taler-wallet-core/src/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/pay-peer-push-debit.ts @@ -232,7 +232,7 @@ export class PeerPushDebitTransactionContext implements TransactionContext { if (!rec) { return; } - await h.update(undefined); + await h.update(undefined, "delete"); } async userSuspendTransaction(): Promise<void> { @@ -263,7 +263,7 @@ export class PeerPushDebitTransactionContext implements TransactionContext { default: assertUnreachable(rec.status); } - await h.update(rec); + await h.update(rec, "suspend"); }); this.wex.taskScheduler.stopShepherdTask(this.taskId); } @@ -297,7 +297,7 @@ export class PeerPushDebitTransactionContext implements TransactionContext { default: assertUnreachable(rec.status); } - await h.update(rec); + await h.update(rec, "abort"); }); await this.wex.taskScheduler.resetTask(this.taskId); } @@ -330,7 +330,7 @@ export class PeerPushDebitTransactionContext implements TransactionContext { default: assertUnreachable(rec.status); } - await h.update(rec); + await h.update(rec, "resume"); }); await this.wex.taskScheduler.resetTask(this.taskId); } @@ -347,7 +347,7 @@ export class PeerPushDebitTransactionContext implements TransactionContext { } rec.status = PeerPushDebitStatus.Failed; rec.failReason = reason; - await h.update(rec); + await h.update(rec, "fail"); }); } @@ -376,7 +376,7 @@ export class PeerPushDebitTransactionContext implements TransactionContext { default: assertUnreachable(rec.status); } - await h.update(rec); + await h.update(rec, "user-fail"); }); await this.wex.taskScheduler.resetTask(this.taskId); } @@ -610,7 +610,7 @@ async function handlePurseCreationConflict( contributions: added.map((x) => Amounts.parseOrThrow(x.contribution)), refreshReason: RefreshReason.PayPeerPush, }); - await h.update(rec); + await h.update(rec, "purse-conflict-repair"); } } }); @@ -688,7 +688,7 @@ async function processPeerPushDebitCreateReserve( ), refreshReason: RefreshReason.PayPeerPush, }); - await h.update(rec); + await h.update(rec, "select-coins"); return TaskRunResult.progress(); }); } @@ -803,7 +803,7 @@ async function processPeerPushDebitCreateReserve( // Do nothing return; } - await h.update(rec); + await h.update(rec, "deposit-purse-gone"); }); return TaskRunResult.progress(); case HttpStatusCode.Conflict: @@ -840,7 +840,7 @@ async function processPeerPushDebitCreateReserve( // Do nothing return; } - await h.update(rec); + await h.update(rec, "create-purse"); }); return TaskRunResult.progress(); case HttpStatusCode.Gone: @@ -858,7 +858,7 @@ async function processPeerPushDebitCreateReserve( // Do nothing return; } - await h.update(rec); + await h.update(rec, "create-purse-gone"); }); return TaskRunResult.progress(); case HttpStatusCode.NotFound: @@ -907,7 +907,7 @@ async function processPeerPushDebitAbortingDeletePurse( // The abort did not happen, so its reason must not be reported. delete rec.abortReason; rec.status = PeerPushDebitStatus.Done; - await h.update(rec); + await h.update(rec, "abort-purse-deposited"); }); return TaskRunResult.finished(); } @@ -954,7 +954,7 @@ async function processPeerPushDebitAbortingDeletePurse( rec.status = PeerPushDebitStatus.Aborted; - await h.update(rec); + await h.update(rec, "abort-purse-deleted"); }); return TaskRunResult.backoff(); @@ -994,7 +994,7 @@ async function processPeerPushDebitReady( // Do nothing return; } - await h.update(rec); + await h.update(rec, "ready-done"); }); return TaskRunResult.progress(); } @@ -1032,7 +1032,7 @@ async function processPeerPushDebitReady( rec.abortRefreshGroupId = refresh.refreshGroupId; } rec.status = PeerPushDebitStatus.Aborted; - await h.update(rec); + await h.update(rec, "ready-aborted"); }); return TaskRunResult.backoff(); case HttpStatusCode.NotFound: @@ -1227,7 +1227,7 @@ export async function initiatePeerPushDebit( if (oldRec) { throw Error("record for peer-push-debit already exists"); } - await h.update(ppi); + await h.update(ppi, "initiate"); }); wex.taskScheduler.startShepherdTask(ctx.taskId); diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts @@ -248,7 +248,7 @@ export class RefreshTransactionContext implements TransactionContext { for (const s of sessions) { await tx.deleteRefreshSession(s.refreshGroupId, s.coinIndex); } - await h.update(undefined); + await h.update(undefined, "delete"); await tx.deleteRefreshGroup(rg.refreshGroupId); } @@ -266,12 +266,12 @@ export class RefreshTransactionContext implements TransactionContext { break; case RefreshOperationStatus.Pending: { rec.operationStatus = RefreshOperationStatus.Suspended; - await h.update(rec); + await h.update(rec, "suspend"); break; } case RefreshOperationStatus.PendingRedenominate: { rec.operationStatus = RefreshOperationStatus.SuspendedRedenominate; - await h.update(rec); + await h.update(rec, "suspend"); break; } default: @@ -299,12 +299,12 @@ export class RefreshTransactionContext implements TransactionContext { break; case RefreshOperationStatus.Suspended: { rec.operationStatus = RefreshOperationStatus.Pending; - await h.update(rec); + await h.update(rec, "resume"); break; } case RefreshOperationStatus.SuspendedRedenominate: { rec.operationStatus = RefreshOperationStatus.PendingRedenominate; - await h.update(rec); + await h.update(rec, "resume"); break; } default: @@ -329,7 +329,7 @@ export class RefreshTransactionContext implements TransactionContext { case RefreshOperationStatus.Suspended: { rec.operationStatus = RefreshOperationStatus.Failed; rec.failReason = reason; - await h.update(rec); + await h.update(rec, "user-fail"); break; } default: @@ -824,7 +824,7 @@ async function handleRefreshMeltGone( } refreshSession.lastError = errDetails; await tx.upsertRefreshSession(refreshSession); - await h.update(rg); + await h.update(rg, "melt-gone"); }); } @@ -912,7 +912,7 @@ async function handleRefreshMeltConflict( } refreshSession.lastError = errDetails; await tx.upsertRefreshSession(refreshSession); - await h.update(rg); + await h.update(rg, "melt-conflict"); } else { // Try again with new denoms! rg.inputPerCoin[coinIndex] = historyJson.balance; @@ -967,7 +967,7 @@ async function handleRefreshMeltNotFound( refreshSession.lastError = errDetails; await tx.upsertRefreshSession(refreshSession); await destroyRefreshSession(ctx.wex, tx, rg, refreshSession); - await h.update(rg); + await h.update(rg, "melt-not-found"); }); } @@ -1180,7 +1180,7 @@ async function refreshReveal( car.freshCoinCount++; await tx.upsertCoinAvailability(car); } - await h.update(rg); + await h.update(rg, "reveal"); }); logger.trace("refresh finished (end of reveal)"); } @@ -1212,7 +1212,7 @@ async function handleRefreshRevealError( refreshSession.lastError = errDetails; await destroyRefreshSession(ctx.wex, tx, rg, refreshSession); await tx.upsertRefreshSession(refreshSession); - await h.update(rg); + await h.update(rg, "reveal-error"); }); } @@ -1345,7 +1345,7 @@ export async function processRefreshGroup( rg.operationStatus = RefreshOperationStatus.Finished; } await makeCoinsVisible(wex, tx, ctx.transactionId); - await h.update(rg); + await h.update(rg, "refresh-done"); return true; } return false; @@ -1665,6 +1665,7 @@ export async function createRefreshGroup( tx.notify({ type: NotificationType.TransactionStateTransition, + causeHint: "create-refresh-group", transactionId: ctx.transactionId, oldTxState: { major: TransactionMajorState.None, @@ -1743,7 +1744,7 @@ async function redenominateRefresh( ); refreshGroup.operationStatus = RefreshOperationStatus.Pending; - await h.update(refreshGroup); + await h.update(refreshGroup, "redenominate"); return TaskRunResult.progress(); }); } diff --git a/packages/taler-wallet-core/src/shepherd.ts b/packages/taler-wallet-core/src/shepherd.ts @@ -958,6 +958,7 @@ async function makeTransactionRetryNotification( } const notif: WalletNotification = { type: NotificationType.TransactionStateTransition, + causeHint: "shepherd-retry", transactionId: txId, oldTxState: txState.txState, newTxState: txState.txState, diff --git a/packages/taler-wallet-core/src/transactions.ts b/packages/taler-wallet-core/src/transactions.ts @@ -898,6 +898,13 @@ export interface TransitionInfo { balanceEffect: BalanceEffect; + /** + * A hint as to why the transition happened. + * + * Should not be relied upon by clients. + */ + causeHint: string | undefined; + newStId: number; oldStId: number; } @@ -962,6 +969,7 @@ export function applyNotifyTransition( oldTxState: transitionInfo.oldTxState, newTxState: transitionInfo.newTxState, transactionId, + causeHint: transitionInfo.causeHint, newStId: transitionInfo.newStId, }); diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts @@ -517,7 +517,7 @@ export class WithdrawTransactionContext implements TransactionContext { for (const p of planchets) { await tx.deletePlanchet(p.coinPub); } - await h.update(undefined); + await h.update(undefined, "delete"); } async userSuspendTransaction(): Promise<void> { @@ -555,7 +555,7 @@ export class WithdrawTransactionContext implements TransactionContext { return; } wg.status = newStatus; - await h.update(wg); + await h.update(wg, "suspend"); }); } @@ -612,7 +612,7 @@ export class WithdrawTransactionContext implements TransactionContext { } wg.abortReason = reason; wg.status = newStatus; - await h.update(wg); + await h.update(wg, "abort"); }); } @@ -651,7 +651,7 @@ export class WithdrawTransactionContext implements TransactionContext { return; } wg.status = newStatus; - await h.update(wg); + await h.update(wg, "resume"); }); } @@ -674,7 +674,7 @@ export class WithdrawTransactionContext implements TransactionContext { } wg.status = newStatus; wg.failReason = reason; - await h.update(wg); + await h.update(wg, "user-fail"); }); } } @@ -965,7 +965,7 @@ async function processWithdrawalGroupRedenominate( return TaskRunResult.backoff(); } rec.status = WithdrawalGroupStatus.PendingReady; - await h.update(rec); + await h.update(rec, "redenominate-done"); return TaskRunResult.progress(); }); } @@ -1042,7 +1042,7 @@ async function processWithdrawalGroupBalanceKyc( case WithdrawalGroupStatus.PendingBalanceKyc: case WithdrawalGroupStatus.PendingBalanceKycInit: { wg.status = WithdrawalGroupStatus.PendingReady; - await h.update(wg); + await h.update(wg, "balance-kyc-ok"); return TaskRunResult.progress(); } default: { @@ -1065,7 +1065,7 @@ async function processWithdrawalGroupBalanceKyc( wg.status = WithdrawalGroupStatus.PendingBalanceKyc; wg.kycAccessToken = ret.walletKycAccessToken; delete wg.kycPaytoHash; - await h.update(wg); + await h.update(wg, "balance-kyc-required"); return TaskRunResult.progress(); }); } else { @@ -1083,13 +1083,14 @@ async function transitionSimple( ctx: WithdrawTransactionContext, from: WithdrawalGroupStatus, to: WithdrawalGroupStatus, + causeHint: string, ): Promise<void> { await ctx.wex.runWalletDbTx(async (tx) => { const [rec, h] = await ctx.getRecordHandle(tx); switch (rec?.status) { case from: { rec.status = to; - await h.update(rec); + await h.update(rec, causeHint); } } }); @@ -1145,6 +1146,7 @@ async function processWithdrawalGroupDialogProposed( ctx, WithdrawalGroupStatus.DialogProposed, WithdrawalGroupStatus.AbortedBank, + "wop-not-found", ); break; } @@ -1157,6 +1159,7 @@ async function processWithdrawalGroupDialogProposed( ctx, WithdrawalGroupStatus.DialogProposed, WithdrawalGroupStatus.AbortedOtherWallet, + "wop-not-pending", ); } break; @@ -1472,7 +1475,7 @@ async function transitionKycRequired( wg2.kycPaytoHash = legiRequiredResp.h_payto; wg2.kycLastDeny = timestampPreciseToDb(TalerPreciseTimestamp.now()); wg2.status = WithdrawalGroupStatus.PendingKyc; - await h.update(wg2); + await h.update(wg2, "kyc-required"); }); } @@ -2155,7 +2158,7 @@ async function processQueryReserve( } wg.status = WithdrawalGroupStatus.PendingReady; wg.reserveBalanceAmount = Amounts.stringify(reserveStatus.balance); - await h.update(wg); + await h.update(wg, "query-reserve"); return TaskRunResult.progress(); }); } @@ -2211,7 +2214,7 @@ async function processWithdrawalGroupAbortingBank( } wg.status = WithdrawalGroupStatus.AbortedBank; wg.timestampFinish = timestampPreciseToDb(TalerPreciseTimestamp.now()); - await h.update(wg); + await h.update(wg, "aborting-bank"); }); return TaskRunResult.finished(); @@ -2285,7 +2288,7 @@ async function processWithdrawalGroupPendingKyc( rec.kycLastDeny = updatedStatus.lastDeny; rec.kycLastRuleGen = updatedStatus.lastRuleGen; rec.kycAccessToken = updatedStatus.accessToken; - await h.update(rec); + await h.update(rec, "kyc-status"); }); return algoRes.taskResult; @@ -2475,7 +2478,7 @@ async function processWithdrawalGroupPendingReady( } rec.status = WithdrawalGroupStatus.Done; rec.timestampFinish = timestampPreciseToDb(TalerPreciseTimestamp.now()); - await h.update(rec); + await h.update(rec, "empty-group-done"); }); return TaskRunResult.finished(); } @@ -2509,7 +2512,7 @@ async function processWithdrawalGroupPendingReady( return; } wg.status = WithdrawalGroupStatus.PendingBalanceKycInit; - await h.update(wg); + await h.update(wg, "balance-kyc-init"); }); return TaskRunResult.progress(); } @@ -2666,7 +2669,7 @@ async function processWithdrawalGroupPendingReady( wg.status = WithdrawalGroupStatus.Done; await makeCoinsVisible(wex, tx, ctx.transactionId); } - await h.update(wg); + await h.update(wg, "planchets-done"); return wg; }); @@ -2708,7 +2711,7 @@ async function startRedenomination( return TaskRunResult.backoff(); } rec.status = WithdrawalGroupStatus.PendingRedenominate; - await h.update(rec); + await h.update(rec, "start-redenomination"); return TaskRunResult.progress(); }); } @@ -3098,6 +3101,7 @@ async function registerReserveWithBank( ctx, WithdrawalGroupStatus.PendingRegisteringBank, WithdrawalGroupStatus.FailedBankAborted, + "devexp-post-wop-failed", ); return TaskRunResult.progress(); } @@ -3123,6 +3127,7 @@ async function registerReserveWithBank( ctx, WithdrawalGroupStatus.PendingRegisteringBank, WithdrawalGroupStatus.FailedBankAborted, + "register-wop-not-found", ); return TaskRunResult.progress(); default: @@ -3132,6 +3137,7 @@ async function registerReserveWithBank( ctx, WithdrawalGroupStatus.PendingRegisteringBank, WithdrawalGroupStatus.FailedBankAborted, + "register-wop-conflict", ); return TaskRunResult.progress(); } @@ -3170,7 +3176,7 @@ async function registerReserveWithBank( r.wgInfo.bankInfo.confirmUrl = bankWopStatus.confirm_transfer_url; break; } - await h.update(r); + await h.update(r, "register-reserve-bank"); return TaskRunResult.progress(); }); } @@ -3197,7 +3203,7 @@ async function transitionBankAborted( const now = AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now()); r.wgInfo.bankInfo.timestampBankConfirmed = timestampPreciseToDb(now); r.status = WithdrawalGroupStatus.FailedBankAborted; - await h.update(r); + await h.update(r, "bank-aborted"); return TaskRunResult.progress(); }); } @@ -3252,6 +3258,7 @@ async function processBankRegisterReserve( ctx, WithdrawalGroupStatus.PendingRegisteringBank, WithdrawalGroupStatus.FailedBankAborted, + "wop-not-found", ); return TaskRunResult.progress(); case HttpStatusCode.Conflict: @@ -3259,6 +3266,7 @@ async function processBankRegisterReserve( ctx, WithdrawalGroupStatus.PendingRegisteringBank, WithdrawalGroupStatus.AbortedOtherWallet, + "wop-conflict", ); return TaskRunResult.progress(); } @@ -3390,7 +3398,7 @@ async function processReserveBankStatus( r.effectiveWithdrawalAmount = denomSel.totalCoinValue; r.instructedAmount = denomSel.totalWithdrawCost; } - await h.update(r); + await h.update(r, "reserve-bank-status"); return TaskRunResult.progress(); } else { return TaskRunResult.backoff(); @@ -3977,7 +3985,7 @@ export async function confirmWithdrawal( rec.status = WithdrawalGroupStatus.PendingRegisteringBank; await internalPerformExchangeWasUsed(wex, tx, exchange.exchangeBaseUrl); - await h.update(rec); + await h.update(rec, "confirm-withdrawal"); return; }