diff options
Diffstat (limited to 'packages/taler-wallet-core/src/operations/pending.ts')
-rw-r--r-- | packages/taler-wallet-core/src/operations/pending.ts | 300 |
1 files changed, 50 insertions, 250 deletions
diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts index 4eee85278..b40c33c5c 100644 --- a/packages/taler-wallet-core/src/operations/pending.ts +++ b/packages/taler-wallet-core/src/operations/pending.ts @@ -18,7 +18,6 @@ * Imports. */ import { - ExchangeUpdateStatus, ProposalStatus, ReserveRecordStatus, AbortStatus, @@ -27,31 +26,13 @@ import { import { PendingOperationsResponse, PendingOperationType, - ExchangeUpdateOperationStage, ReserveType, } from "../pending-types"; -import { - Duration, - getTimestampNow, - Timestamp, - getDurationRemaining, - durationMin, -} from "@gnu-taler/taler-util"; +import { getTimestampNow, Timestamp } from "@gnu-taler/taler-util"; import { InternalWalletState } from "./state"; import { getBalancesInsideTransaction } from "./balance"; -import { getExchangeDetails } from "./exchanges.js"; import { GetReadOnlyAccess } from "../util/query.js"; -function updateRetryDelay( - oldDelay: Duration, - now: Timestamp, - retryTimestamp: Timestamp, -): Duration { - const remaining = getDurationRemaining(retryTimestamp, now); - const nextDelay = durationMin(oldDelay, remaining); - return nextDelay; -} - async function gatherExchangePending( tx: GetReadOnlyAccess<{ exchanges: typeof WalletStoresV1.exchanges; @@ -59,97 +40,22 @@ async function gatherExchangePending( }>, now: Timestamp, resp: PendingOperationsResponse, - onlyDue = false, ): Promise<void> { await tx.exchanges.iter().forEachAsync(async (e) => { - switch (e.updateStatus) { - case ExchangeUpdateStatus.Finished: - if (e.lastError) { - resp.pendingOperations.push({ - type: PendingOperationType.Bug, - givesLifeness: false, - message: - "Exchange record is in FINISHED state but has lastError set", - details: { - exchangeBaseUrl: e.baseUrl, - }, - }); - } - const details = await getExchangeDetails(tx, e.baseUrl); - const keysUpdateRequired = - details && details.nextUpdateTime.t_ms < now.t_ms; - if (keysUpdateRequired) { - resp.pendingOperations.push({ - type: PendingOperationType.ExchangeUpdate, - givesLifeness: false, - stage: ExchangeUpdateOperationStage.FetchKeys, - exchangeBaseUrl: e.baseUrl, - lastError: e.lastError, - reason: "scheduled", - }); - } - if ( - details && - (!e.nextRefreshCheck || e.nextRefreshCheck.t_ms < now.t_ms) - ) { - resp.pendingOperations.push({ - type: PendingOperationType.ExchangeCheckRefresh, - exchangeBaseUrl: e.baseUrl, - givesLifeness: false, - }); - } - break; - case ExchangeUpdateStatus.FetchKeys: - if (onlyDue && e.retryInfo.nextRetry.t_ms > now.t_ms) { - return; - } - resp.pendingOperations.push({ - type: PendingOperationType.ExchangeUpdate, - givesLifeness: false, - stage: ExchangeUpdateOperationStage.FetchKeys, - exchangeBaseUrl: e.baseUrl, - lastError: e.lastError, - reason: e.updateReason || "unknown", - }); - break; - case ExchangeUpdateStatus.FetchWire: - if (onlyDue && e.retryInfo.nextRetry.t_ms > now.t_ms) { - return; - } - resp.pendingOperations.push({ - type: PendingOperationType.ExchangeUpdate, - givesLifeness: false, - stage: ExchangeUpdateOperationStage.FetchWire, - exchangeBaseUrl: e.baseUrl, - lastError: e.lastError, - reason: e.updateReason || "unknown", - }); - break; - case ExchangeUpdateStatus.FinalizeUpdate: - if (onlyDue && e.retryInfo.nextRetry.t_ms > now.t_ms) { - return; - } - resp.pendingOperations.push({ - type: PendingOperationType.ExchangeUpdate, - givesLifeness: false, - stage: ExchangeUpdateOperationStage.FinalizeUpdate, - exchangeBaseUrl: e.baseUrl, - lastError: e.lastError, - reason: e.updateReason || "unknown", - }); - break; - default: - resp.pendingOperations.push({ - type: PendingOperationType.Bug, - givesLifeness: false, - message: "Unknown exchangeUpdateStatus", - details: { - exchangeBaseUrl: e.baseUrl, - exchangeUpdateStatus: e.updateStatus, - }, - }); - break; - } + resp.pendingOperations.push({ + type: PendingOperationType.ExchangeUpdate, + givesLifeness: false, + timestampDue: e.nextUpdate, + exchangeBaseUrl: e.baseUrl, + lastError: e.lastError, + }); + + resp.pendingOperations.push({ + type: PendingOperationType.ExchangeCheckRefresh, + timestampDue: e.nextRefreshCheck, + givesLifeness: false, + exchangeBaseUrl: e.baseUrl, + }); }); } @@ -157,16 +63,11 @@ async function gatherReservePending( tx: GetReadOnlyAccess<{ reserves: typeof WalletStoresV1.reserves }>, now: Timestamp, resp: PendingOperationsResponse, - onlyDue = false, ): Promise<void> { - // FIXME: this should be optimized by using an index for "onlyDue==true". await tx.reserves.iter().forEach((reserve) => { const reserveType = reserve.bankInfo ? ReserveType.TalerBankWithdraw : ReserveType.Manual; - if (!reserve.retryInfo.active) { - return; - } switch (reserve.reserveStatus) { case ReserveRecordStatus.DORMANT: // nothing to report as pending @@ -174,17 +75,10 @@ async function gatherReservePending( case ReserveRecordStatus.WAIT_CONFIRM_BANK: case ReserveRecordStatus.QUERYING_STATUS: case ReserveRecordStatus.REGISTERING_BANK: - resp.nextRetryDelay = updateRetryDelay( - resp.nextRetryDelay, - now, - reserve.retryInfo.nextRetry, - ); - if (onlyDue && reserve.retryInfo.nextRetry.t_ms > now.t_ms) { - return; - } resp.pendingOperations.push({ type: PendingOperationType.Reserve, givesLifeness: true, + timestampDue: reserve.retryInfo.nextRetry, stage: reserve.reserveStatus, timestampCreated: reserve.timestampCreated, reserveType, @@ -193,15 +87,7 @@ async function gatherReservePending( }); break; default: - resp.pendingOperations.push({ - type: PendingOperationType.Bug, - givesLifeness: false, - message: "Unknown reserve record status", - details: { - reservePub: reserve.reservePub, - reserveStatus: reserve.reserveStatus, - }, - }); + // FIXME: report problem! break; } }); @@ -211,24 +97,15 @@ async function gatherRefreshPending( tx: GetReadOnlyAccess<{ refreshGroups: typeof WalletStoresV1.refreshGroups }>, now: Timestamp, resp: PendingOperationsResponse, - onlyDue = false, ): Promise<void> { await tx.refreshGroups.iter().forEach((r) => { if (r.timestampFinished) { return; } - resp.nextRetryDelay = updateRetryDelay( - resp.nextRetryDelay, - now, - r.retryInfo.nextRetry, - ); - if (onlyDue && r.retryInfo.nextRetry.t_ms > now.t_ms) { - return; - } - resp.pendingOperations.push({ type: PendingOperationType.Refresh, givesLifeness: true, + timestampDue: r.retryInfo.nextRetry, refreshGroupId: r.refreshGroupId, finishedPerCoin: r.finishedPerCoin, retryInfo: r.retryInfo, @@ -243,20 +120,11 @@ async function gatherWithdrawalPending( }>, now: Timestamp, resp: PendingOperationsResponse, - onlyDue = false, ): Promise<void> { await tx.withdrawalGroups.iter().forEachAsync(async (wsr) => { if (wsr.timestampFinish) { return; } - resp.nextRetryDelay = updateRetryDelay( - resp.nextRetryDelay, - now, - wsr.retryInfo.nextRetry, - ); - if (onlyDue && wsr.retryInfo.nextRetry.t_ms > now.t_ms) { - return; - } let numCoinsWithdrawn = 0; let numCoinsTotal = 0; await tx.planchets.indexes.byGroup @@ -270,8 +138,7 @@ async function gatherWithdrawalPending( resp.pendingOperations.push({ type: PendingOperationType.Withdraw, givesLifeness: true, - numCoinsTotal, - numCoinsWithdrawn, + timestampDue: wsr.retryInfo.nextRetry, withdrawalGroupId: wsr.withdrawalGroupId, lastError: wsr.lastError, retryInfo: wsr.retryInfo, @@ -283,42 +150,15 @@ async function gatherProposalPending( tx: GetReadOnlyAccess<{ proposals: typeof WalletStoresV1.proposals }>, now: Timestamp, resp: PendingOperationsResponse, - onlyDue = false, ): Promise<void> { await tx.proposals.iter().forEach((proposal) => { if (proposal.proposalStatus == ProposalStatus.PROPOSED) { - if (onlyDue) { - return; - } - const dl = proposal.download; - if (!dl) { - resp.pendingOperations.push({ - type: PendingOperationType.Bug, - message: "proposal is in invalid state", - details: {}, - givesLifeness: false, - }); - } else { - resp.pendingOperations.push({ - type: PendingOperationType.ProposalChoice, - givesLifeness: false, - merchantBaseUrl: dl.contractData.merchantBaseUrl, - proposalId: proposal.proposalId, - proposalTimestamp: proposal.timestamp, - }); - } + // Nothing to do, user needs to choose. } else if (proposal.proposalStatus == ProposalStatus.DOWNLOADING) { - resp.nextRetryDelay = updateRetryDelay( - resp.nextRetryDelay, - now, - proposal.retryInfo.nextRetry, - ); - if (onlyDue && proposal.retryInfo.nextRetry.t_ms > now.t_ms) { - return; - } resp.pendingOperations.push({ type: PendingOperationType.ProposalDownload, givesLifeness: true, + timestampDue: proposal.retryInfo.nextRetry, merchantBaseUrl: proposal.merchantBaseUrl, orderId: proposal.orderId, proposalId: proposal.proposalId, @@ -334,24 +174,16 @@ async function gatherTipPending( tx: GetReadOnlyAccess<{ tips: typeof WalletStoresV1.tips }>, now: Timestamp, resp: PendingOperationsResponse, - onlyDue = false, ): Promise<void> { await tx.tips.iter().forEach((tip) => { if (tip.pickedUpTimestamp) { return; } - resp.nextRetryDelay = updateRetryDelay( - resp.nextRetryDelay, - now, - tip.retryInfo.nextRetry, - ); - if (onlyDue && tip.retryInfo.nextRetry.t_ms > now.t_ms) { - return; - } if (tip.acceptedTimestamp) { resp.pendingOperations.push({ type: PendingOperationType.TipPickup, givesLifeness: true, + timestampDue: tip.retryInfo.nextRetry, merchantBaseUrl: tip.merchantBaseUrl, tipId: tip.walletTipId, merchantTipId: tip.merchantTipId, @@ -364,41 +196,28 @@ async function gatherPurchasePending( tx: GetReadOnlyAccess<{ purchases: typeof WalletStoresV1.purchases }>, now: Timestamp, resp: PendingOperationsResponse, - onlyDue = false, ): Promise<void> { await tx.purchases.iter().forEach((pr) => { if (pr.paymentSubmitPending && pr.abortStatus === AbortStatus.None) { - resp.nextRetryDelay = updateRetryDelay( - resp.nextRetryDelay, - now, - pr.payRetryInfo.nextRetry, - ); - if (!onlyDue || pr.payRetryInfo.nextRetry.t_ms <= now.t_ms) { - resp.pendingOperations.push({ - type: PendingOperationType.Pay, - givesLifeness: true, - isReplay: false, - proposalId: pr.proposalId, - retryInfo: pr.payRetryInfo, - lastError: pr.lastPayError, - }); - } + resp.pendingOperations.push({ + type: PendingOperationType.Pay, + givesLifeness: true, + timestampDue: pr.payRetryInfo.nextRetry, + isReplay: false, + proposalId: pr.proposalId, + retryInfo: pr.payRetryInfo, + lastError: pr.lastPayError, + }); } if (pr.refundQueryRequested) { - resp.nextRetryDelay = updateRetryDelay( - resp.nextRetryDelay, - now, - pr.refundStatusRetryInfo.nextRetry, - ); - if (!onlyDue || pr.refundStatusRetryInfo.nextRetry.t_ms <= now.t_ms) { - resp.pendingOperations.push({ - type: PendingOperationType.RefundQuery, - givesLifeness: true, - proposalId: pr.proposalId, - retryInfo: pr.refundStatusRetryInfo, - lastError: pr.lastRefundStatusError, - }); - } + resp.pendingOperations.push({ + type: PendingOperationType.RefundQuery, + givesLifeness: true, + timestampDue: pr.refundStatusRetryInfo.nextRetry, + proposalId: pr.proposalId, + retryInfo: pr.refundStatusRetryInfo, + lastError: pr.lastRefundStatusError, + }); } }); } @@ -407,23 +226,15 @@ async function gatherRecoupPending( tx: GetReadOnlyAccess<{ recoupGroups: typeof WalletStoresV1.recoupGroups }>, now: Timestamp, resp: PendingOperationsResponse, - onlyDue = false, ): Promise<void> { await tx.recoupGroups.iter().forEach((rg) => { if (rg.timestampFinished) { return; } - resp.nextRetryDelay = updateRetryDelay( - resp.nextRetryDelay, - now, - rg.retryInfo.nextRetry, - ); - if (onlyDue && rg.retryInfo.nextRetry.t_ms > now.t_ms) { - return; - } resp.pendingOperations.push({ type: PendingOperationType.Recoup, givesLifeness: true, + timestampDue: rg.retryInfo.nextRetry, recoupGroupId: rg.recoupGroupId, retryInfo: rg.retryInfo, lastError: rg.lastError, @@ -435,23 +246,15 @@ async function gatherDepositPending( tx: GetReadOnlyAccess<{ depositGroups: typeof WalletStoresV1.depositGroups }>, now: Timestamp, resp: PendingOperationsResponse, - onlyDue = false, ): Promise<void> { await tx.depositGroups.iter().forEach((dg) => { if (dg.timestampFinished) { return; } - resp.nextRetryDelay = updateRetryDelay( - resp.nextRetryDelay, - now, - dg.retryInfo.nextRetry, - ); - if (onlyDue && dg.retryInfo.nextRetry.t_ms > now.t_ms) { - return; - } resp.pendingOperations.push({ type: PendingOperationType.Deposit, givesLifeness: true, + timestampDue: dg.retryInfo.nextRetry, depositGroupId: dg.depositGroupId, retryInfo: dg.retryInfo, lastError: dg.lastError, @@ -461,7 +264,6 @@ async function gatherDepositPending( export async function getPendingOperations( ws: InternalWalletState, - { onlyDue = false } = {}, ): Promise<PendingOperationsResponse> { const now = getTimestampNow(); return await ws.db @@ -482,20 +284,18 @@ export async function getPendingOperations( .runReadWrite(async (tx) => { const walletBalance = await getBalancesInsideTransaction(ws, tx); const resp: PendingOperationsResponse = { - nextRetryDelay: { d_ms: Number.MAX_SAFE_INTEGER }, - onlyDue: onlyDue, walletBalance, pendingOperations: [], }; - await gatherExchangePending(tx, now, resp, onlyDue); - await gatherReservePending(tx, now, resp, onlyDue); - await gatherRefreshPending(tx, now, resp, onlyDue); - await gatherWithdrawalPending(tx, now, resp, onlyDue); - await gatherProposalPending(tx, now, resp, onlyDue); - await gatherTipPending(tx, now, resp, onlyDue); - await gatherPurchasePending(tx, now, resp, onlyDue); - await gatherRecoupPending(tx, now, resp, onlyDue); - await gatherDepositPending(tx, now, resp, onlyDue); + await gatherExchangePending(tx, now, resp); + await gatherReservePending(tx, now, resp); + await gatherRefreshPending(tx, now, resp); + await gatherWithdrawalPending(tx, now, resp); + await gatherProposalPending(tx, now, resp); + await gatherTipPending(tx, now, resp); + await gatherPurchasePending(tx, now, resp); + await gatherRecoupPending(tx, now, resp); + await gatherDepositPending(tx, now, resp); return resp; }); } |