summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations/pending.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-core/src/operations/pending.ts')
-rw-r--r--packages/taler-wallet-core/src/operations/pending.ts300
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;
});
}