wallet-core/packages/taler-wallet-core/src/operations/pending.ts

368 lines
11 KiB
TypeScript
Raw Normal View History

/*
This file is part of GNU Taler
(C) 2019 GNUnet e.V.
GNU Taler is free software; you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation; either version 3, or (at your option) any later version.
GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
/**
* Derive pending tasks from the wallet database.
*/
2019-12-02 17:35:47 +01:00
/**
* Imports.
*/
import {
ProposalStatus,
2019-12-15 19:08:07 +01:00
ReserveRecordStatus,
AbortStatus,
2021-06-09 15:14:17 +02:00
WalletStoresV1,
BackupProviderStateTag,
2021-08-24 14:25:46 +02:00
RefreshCoinStatus,
2022-01-11 21:00:12 +01:00
OperationStatus,
2021-03-17 17:56:37 +01:00
} from "../db.js";
2019-12-15 19:08:07 +01:00
import {
PendingOperationsResponse,
PendingTaskType,
2020-07-23 15:00:08 +02:00
ReserveType,
2021-06-14 16:08:58 +02:00
} from "../pending-types.js";
import {
getTimestampNow,
Timestamp,
} from "@gnu-taler/taler-util";
import { InternalWalletState } from "../common.js";
2021-06-09 15:14:17 +02:00
import { GetReadOnlyAccess } from "../util/query.js";
2019-12-05 19:38:19 +01:00
async function gatherExchangePending(
2021-06-09 15:14:17 +02:00
tx: GetReadOnlyAccess<{
exchanges: typeof WalletStoresV1.exchanges;
exchangeDetails: typeof WalletStoresV1.exchangeDetails;
}>,
2019-12-05 19:38:19 +01:00
now: Timestamp,
resp: PendingOperationsResponse,
): Promise<void> {
2021-06-09 15:14:17 +02:00
await tx.exchanges.iter().forEachAsync(async (e) => {
let exchangeUpdateTimestampDue: Timestamp;
if (e.lastError) {
exchangeUpdateTimestampDue = e.retryInfo.nextRetry;
} else {
exchangeUpdateTimestampDue = e.nextUpdate;
}
resp.pendingOperations.push({
type: PendingTaskType.ExchangeUpdate,
givesLifeness: false,
timestampDue: exchangeUpdateTimestampDue,
exchangeBaseUrl: e.baseUrl,
lastError: e.lastError,
});
resp.pendingOperations.push({
type: PendingTaskType.ExchangeCheckRefresh,
timestampDue: e.nextRefreshCheck,
givesLifeness: false,
exchangeBaseUrl: e.baseUrl,
});
2019-12-05 19:38:19 +01:00
});
}
async function gatherReservePending(
2021-06-09 15:14:17 +02:00
tx: GetReadOnlyAccess<{ reserves: typeof WalletStoresV1.reserves }>,
2019-12-05 19:38:19 +01:00
now: Timestamp,
resp: PendingOperationsResponse,
): Promise<void> {
2022-01-11 21:00:12 +01:00
await tx.reserves.indexes.byStatus
.iter(OperationStatus.Pending)
.forEach((reserve) => {
const reserveType = reserve.bankInfo
? ReserveType.TalerBankWithdraw
: ReserveType.Manual;
switch (reserve.reserveStatus) {
case ReserveRecordStatus.DORMANT:
// nothing to report as pending
break;
case ReserveRecordStatus.WAIT_CONFIRM_BANK:
case ReserveRecordStatus.QUERYING_STATUS:
case ReserveRecordStatus.REGISTERING_BANK:
resp.pendingOperations.push({
type: PendingTaskType.Reserve,
givesLifeness: true,
timestampDue: reserve.retryInfo.nextRetry,
stage: reserve.reserveStatus,
timestampCreated: reserve.timestampCreated,
reserveType,
reservePub: reserve.reservePub,
retryInfo: reserve.retryInfo,
});
break;
default:
// FIXME: report problem!
break;
}
});
2019-12-05 19:38:19 +01:00
}
async function gatherRefreshPending(
2021-06-09 15:14:17 +02:00
tx: GetReadOnlyAccess<{ refreshGroups: typeof WalletStoresV1.refreshGroups }>,
2019-12-05 19:38:19 +01:00
now: Timestamp,
resp: PendingOperationsResponse,
): Promise<void> {
2022-01-11 21:00:12 +01:00
await tx.refreshGroups.indexes.byStatus
.iter(OperationStatus.Pending)
.forEach((r) => {
if (r.timestampFinished) {
return;
}
if (r.frozen) {
return;
}
resp.pendingOperations.push({
type: PendingTaskType.Refresh,
givesLifeness: true,
timestampDue: r.retryInfo.nextRetry,
refreshGroupId: r.refreshGroupId,
finishedPerCoin: r.statusPerCoin.map(
(x) => x === RefreshCoinStatus.Finished,
),
retryInfo: r.retryInfo,
});
2019-12-05 19:38:19 +01:00
});
}
async function gatherWithdrawalPending(
2021-06-09 15:14:17 +02:00
tx: GetReadOnlyAccess<{
withdrawalGroups: typeof WalletStoresV1.withdrawalGroups;
2021-06-09 15:26:18 +02:00
planchets: typeof WalletStoresV1.planchets;
2021-06-09 15:14:17 +02:00
}>,
2019-12-05 19:38:19 +01:00
now: Timestamp,
resp: PendingOperationsResponse,
): Promise<void> {
2022-01-11 21:00:12 +01:00
await tx.withdrawalGroups.indexes.byStatus
.iter(OperationStatus.Pending)
.forEachAsync(async (wsr) => {
if (wsr.timestampFinish) {
return;
}
let numCoinsWithdrawn = 0;
let numCoinsTotal = 0;
await tx.planchets.indexes.byGroup
.iter(wsr.withdrawalGroupId)
.forEach((x) => {
numCoinsTotal++;
if (x.withdrawalDone) {
numCoinsWithdrawn++;
}
});
resp.pendingOperations.push({
type: PendingTaskType.Withdraw,
givesLifeness: true,
timestampDue: wsr.retryInfo.nextRetry,
withdrawalGroupId: wsr.withdrawalGroupId,
lastError: wsr.lastError,
retryInfo: wsr.retryInfo,
});
2019-12-05 19:38:19 +01:00
});
}
async function gatherProposalPending(
2021-06-09 15:14:17 +02:00
tx: GetReadOnlyAccess<{ proposals: typeof WalletStoresV1.proposals }>,
2019-12-05 19:38:19 +01:00
now: Timestamp,
resp: PendingOperationsResponse,
): Promise<void> {
2021-06-09 15:14:17 +02:00
await tx.proposals.iter().forEach((proposal) => {
2019-12-05 19:38:19 +01:00
if (proposal.proposalStatus == ProposalStatus.PROPOSED) {
// Nothing to do, user needs to choose.
2019-12-05 19:38:19 +01:00
} else if (proposal.proposalStatus == ProposalStatus.DOWNLOADING) {
2021-06-11 11:15:08 +02:00
const timestampDue = proposal.retryInfo?.nextRetry ?? getTimestampNow();
2019-12-05 19:38:19 +01:00
resp.pendingOperations.push({
type: PendingTaskType.ProposalDownload,
2019-12-05 19:38:19 +01:00
givesLifeness: true,
2021-06-11 11:15:08 +02:00
timestampDue,
2019-12-06 12:47:28 +01:00
merchantBaseUrl: proposal.merchantBaseUrl,
orderId: proposal.orderId,
2019-12-05 19:38:19 +01:00
proposalId: proposal.proposalId,
proposalTimestamp: proposal.timestamp,
lastError: proposal.lastError,
retryInfo: proposal.retryInfo,
2019-12-05 19:38:19 +01:00
});
}
});
}
async function gatherDepositPending(
tx: GetReadOnlyAccess<{ depositGroups: typeof WalletStoresV1.depositGroups }>,
now: Timestamp,
resp: PendingOperationsResponse,
): Promise<void> {
2022-01-11 21:00:12 +01:00
await tx.depositGroups.indexes.byStatus
.iter(OperationStatus.Pending)
.forEach((dg) => {
if (dg.timestampFinished) {
return;
}
const timestampDue = dg.retryInfo?.nextRetry ?? getTimestampNow();
resp.pendingOperations.push({
type: PendingTaskType.Deposit,
givesLifeness: true,
timestampDue,
depositGroupId: dg.depositGroupId,
lastError: dg.lastError,
retryInfo: dg.retryInfo,
});
});
}
2019-12-05 19:38:19 +01:00
async function gatherTipPending(
2021-06-09 15:14:17 +02:00
tx: GetReadOnlyAccess<{ tips: typeof WalletStoresV1.tips }>,
2019-12-05 19:38:19 +01:00
now: Timestamp,
resp: PendingOperationsResponse,
): Promise<void> {
2021-06-09 15:14:17 +02:00
await tx.tips.iter().forEach((tip) => {
if (tip.pickedUpTimestamp) {
2019-12-05 19:38:19 +01:00
return;
}
2019-12-16 12:53:22 +01:00
if (tip.acceptedTimestamp) {
2019-12-05 19:38:19 +01:00
resp.pendingOperations.push({
type: PendingTaskType.TipPickup,
2019-12-05 19:38:19 +01:00
givesLifeness: true,
timestampDue: tip.retryInfo.nextRetry,
2019-12-05 19:38:19 +01:00
merchantBaseUrl: tip.merchantBaseUrl,
2020-09-08 14:10:47 +02:00
tipId: tip.walletTipId,
2019-12-05 19:38:19 +01:00
merchantTipId: tip.merchantTipId,
});
}
});
}
async function gatherPurchasePending(
2021-06-09 15:14:17 +02:00
tx: GetReadOnlyAccess<{ purchases: typeof WalletStoresV1.purchases }>,
2019-12-05 19:38:19 +01:00
now: Timestamp,
resp: PendingOperationsResponse,
): Promise<void> {
2021-06-09 15:14:17 +02:00
await tx.purchases.iter().forEach((pr) => {
2021-08-24 15:08:34 +02:00
if (
pr.paymentSubmitPending &&
pr.abortStatus === AbortStatus.None &&
!pr.payFrozen
) {
2021-06-11 13:18:33 +02:00
const timestampDue = pr.payRetryInfo?.nextRetry ?? getTimestampNow();
resp.pendingOperations.push({
type: PendingTaskType.Pay,
givesLifeness: true,
2021-06-11 13:18:33 +02:00
timestampDue,
isReplay: false,
proposalId: pr.proposalId,
retryInfo: pr.payRetryInfo,
lastError: pr.lastPayError,
});
2019-12-05 19:38:19 +01:00
}
if (pr.refundQueryRequested) {
resp.pendingOperations.push({
type: PendingTaskType.RefundQuery,
givesLifeness: true,
timestampDue: pr.refundStatusRetryInfo.nextRetry,
proposalId: pr.proposalId,
retryInfo: pr.refundStatusRetryInfo,
lastError: pr.lastRefundStatusError,
});
}
2019-12-05 19:38:19 +01:00
});
}
async function gatherRecoupPending(
2021-06-09 15:14:17 +02:00
tx: GetReadOnlyAccess<{ recoupGroups: typeof WalletStoresV1.recoupGroups }>,
now: Timestamp,
resp: PendingOperationsResponse,
): Promise<void> {
2021-06-09 15:14:17 +02:00
await tx.recoupGroups.iter().forEach((rg) => {
if (rg.timestampFinished) {
return;
}
resp.pendingOperations.push({
type: PendingTaskType.Recoup,
givesLifeness: true,
timestampDue: rg.retryInfo.nextRetry,
recoupGroupId: rg.recoupGroupId,
retryInfo: rg.retryInfo,
lastError: rg.lastError,
});
});
}
async function gatherBackupPending(
tx: GetReadOnlyAccess<{
backupProviders: typeof WalletStoresV1.backupProviders;
}>,
2021-01-18 23:35:41 +01:00
now: Timestamp,
resp: PendingOperationsResponse,
): Promise<void> {
await tx.backupProviders.iter().forEach((bp) => {
if (bp.state.tag === BackupProviderStateTag.Ready) {
resp.pendingOperations.push({
type: PendingTaskType.Backup,
givesLifeness: false,
timestampDue: bp.state.nextBackupTimestamp,
backupProviderBaseUrl: bp.baseUrl,
lastError: undefined,
});
} else if (bp.state.tag === BackupProviderStateTag.Retrying) {
resp.pendingOperations.push({
type: PendingTaskType.Backup,
givesLifeness: false,
timestampDue: bp.state.retryInfo.nextRetry,
backupProviderBaseUrl: bp.baseUrl,
retryInfo: bp.state.retryInfo,
lastError: bp.state.lastError,
});
2021-01-18 23:35:41 +01:00
}
});
}
export async function getPendingOperations(
ws: InternalWalletState,
): Promise<PendingOperationsResponse> {
2019-12-05 19:38:19 +01:00
const now = getTimestampNow();
2021-06-09 15:26:18 +02:00
return await ws.db
.mktx((x) => ({
backupProviders: x.backupProviders,
2021-06-09 15:26:18 +02:00
exchanges: x.exchanges,
exchangeDetails: x.exchangeDetails,
reserves: x.reserves,
refreshGroups: x.refreshGroups,
coins: x.coins,
withdrawalGroups: x.withdrawalGroups,
proposals: x.proposals,
tips: x.tips,
purchases: x.purchases,
planchets: x.planchets,
depositGroups: x.depositGroups,
recoupGroups: x.recoupGroups,
}))
.runReadWrite(async (tx) => {
const resp: PendingOperationsResponse = {
pendingOperations: [],
};
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 gatherDepositPending(tx, now, resp);
await gatherTipPending(tx, now, resp);
await gatherPurchasePending(tx, now, resp);
await gatherRecoupPending(tx, now, resp);
await gatherBackupPending(tx, now, resp);
return resp;
2021-06-09 15:26:18 +02:00
});
}