2019-12-02 00:42:40 +01:00
|
|
|
/*
|
|
|
|
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/>
|
|
|
|
*/
|
|
|
|
|
2019-12-02 17:35:47 +01:00
|
|
|
/**
|
|
|
|
* Imports.
|
|
|
|
*/
|
|
|
|
import {
|
|
|
|
ProposalStatus,
|
2019-12-15 19:08:07 +01:00
|
|
|
ReserveRecordStatus,
|
2020-09-08 22:48:03 +02:00
|
|
|
AbortStatus,
|
2021-06-09 15:14:17 +02:00
|
|
|
WalletStoresV1,
|
2021-03-17 17:56:37 +01:00
|
|
|
} from "../db.js";
|
2019-12-15 19:08:07 +01:00
|
|
|
import {
|
|
|
|
PendingOperationsResponse,
|
|
|
|
PendingOperationType,
|
2020-07-23 15:00:08 +02:00
|
|
|
ReserveType,
|
2021-03-17 17:56:37 +01:00
|
|
|
} from "../pending-types";
|
2021-06-10 16:32:37 +02:00
|
|
|
import { getTimestampNow, Timestamp } from "@gnu-taler/taler-util";
|
2019-12-15 19:08:07 +01:00
|
|
|
import { InternalWalletState } from "./state";
|
2020-04-07 10:07:32 +02:00
|
|
|
import { getBalancesInsideTransaction } from "./balance";
|
2021-06-09 15:14:17 +02:00
|
|
|
import { GetReadOnlyAccess } from "../util/query.js";
|
2019-12-02 00:42:40 +01:00
|
|
|
|
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) => {
|
2021-06-10 16:32:37 +02:00
|
|
|
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,
|
|
|
|
});
|
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> {
|
2021-06-09 15:14:17 +02:00
|
|
|
await tx.reserves.iter().forEach((reserve) => {
|
2020-05-12 10:38:58 +02:00
|
|
|
const reserveType = reserve.bankInfo
|
2020-04-06 09:24:49 +02:00
|
|
|
? ReserveType.TalerBankWithdraw
|
|
|
|
: ReserveType.Manual;
|
2019-12-05 19:38:19 +01:00
|
|
|
switch (reserve.reserveStatus) {
|
|
|
|
case ReserveRecordStatus.DORMANT:
|
|
|
|
// nothing to report as pending
|
|
|
|
break;
|
|
|
|
case ReserveRecordStatus.WAIT_CONFIRM_BANK:
|
2019-12-06 03:23:35 +01:00
|
|
|
case ReserveRecordStatus.QUERYING_STATUS:
|
|
|
|
case ReserveRecordStatus.REGISTERING_BANK:
|
|
|
|
resp.pendingOperations.push({
|
2019-12-15 16:59:00 +01:00
|
|
|
type: PendingOperationType.Reserve,
|
2019-12-06 03:23:35 +01:00
|
|
|
givesLifeness: true,
|
2021-06-10 16:32:37 +02:00
|
|
|
timestampDue: reserve.retryInfo.nextRetry,
|
2019-12-06 03:23:35 +01:00
|
|
|
stage: reserve.reserveStatus,
|
2019-12-16 16:20:45 +01:00
|
|
|
timestampCreated: reserve.timestampCreated,
|
2019-12-06 03:23:35 +01:00
|
|
|
reserveType,
|
|
|
|
reservePub: reserve.reservePub,
|
|
|
|
retryInfo: reserve.retryInfo,
|
|
|
|
});
|
|
|
|
break;
|
2019-12-05 19:38:19 +01:00
|
|
|
default:
|
2021-06-10 16:32:37 +02:00
|
|
|
// FIXME: report problem!
|
2019-12-05 19:38:19 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
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> {
|
2021-06-09 15:14:17 +02:00
|
|
|
await tx.refreshGroups.iter().forEach((r) => {
|
2019-12-16 16:20:45 +01:00
|
|
|
if (r.timestampFinished) {
|
2019-12-05 19:38:19 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
resp.pendingOperations.push({
|
2019-12-15 16:59:00 +01:00
|
|
|
type: PendingOperationType.Refresh,
|
2019-12-05 19:38:19 +01:00
|
|
|
givesLifeness: true,
|
2021-06-10 16:32:37 +02:00
|
|
|
timestampDue: r.retryInfo.nextRetry,
|
2019-12-15 16:59:00 +01:00
|
|
|
refreshGroupId: r.refreshGroupId,
|
2019-12-16 21:10:57 +01:00
|
|
|
finishedPerCoin: r.finishedPerCoin,
|
|
|
|
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> {
|
2021-06-09 15:14:17 +02:00
|
|
|
await tx.withdrawalGroups.iter().forEachAsync(async (wsr) => {
|
2019-12-16 16:20:45 +01:00
|
|
|
if (wsr.timestampFinish) {
|
2019-12-05 19:38:19 +01:00
|
|
|
return;
|
|
|
|
}
|
2020-05-11 14:33:25 +02:00
|
|
|
let numCoinsWithdrawn = 0;
|
|
|
|
let numCoinsTotal = 0;
|
2021-06-09 15:14:17 +02:00
|
|
|
await tx.planchets.indexes.byGroup
|
|
|
|
.iter(wsr.withdrawalGroupId)
|
2020-06-03 13:16:25 +02:00
|
|
|
.forEach((x) => {
|
|
|
|
numCoinsTotal++;
|
|
|
|
if (x.withdrawalDone) {
|
|
|
|
numCoinsWithdrawn++;
|
|
|
|
}
|
|
|
|
});
|
2019-12-05 19:38:19 +01:00
|
|
|
resp.pendingOperations.push({
|
2019-12-15 16:59:00 +01:00
|
|
|
type: PendingOperationType.Withdraw,
|
2019-12-05 19:38:19 +01:00
|
|
|
givesLifeness: true,
|
2021-06-10 16:32:37 +02:00
|
|
|
timestampDue: wsr.retryInfo.nextRetry,
|
2020-04-02 17:03:01 +02:00
|
|
|
withdrawalGroupId: wsr.withdrawalGroupId,
|
|
|
|
lastError: wsr.lastError,
|
2020-09-01 14:30:46 +02:00
|
|
|
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) {
|
2021-06-10 16:32:37 +02:00
|
|
|
// 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({
|
2019-12-15 16:59:00 +01:00
|
|
|
type: PendingOperationType.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,
|
2019-12-06 11:01:39 +01:00
|
|
|
lastError: proposal.lastError,
|
|
|
|
retryInfo: proposal.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) => {
|
2020-09-08 16:24:23 +02:00
|
|
|
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({
|
2019-12-15 16:59:00 +01:00
|
|
|
type: PendingOperationType.TipPickup,
|
2019-12-05 19:38:19 +01:00
|
|
|
givesLifeness: true,
|
2021-06-10 16:32:37 +02:00
|
|
|
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) => {
|
2020-09-08 22:48:03 +02:00
|
|
|
if (pr.paymentSubmitPending && pr.abortStatus === AbortStatus.None) {
|
2021-06-10 16:32:37 +02:00
|
|
|
resp.pendingOperations.push({
|
|
|
|
type: PendingOperationType.Pay,
|
|
|
|
givesLifeness: true,
|
|
|
|
timestampDue: pr.payRetryInfo.nextRetry,
|
|
|
|
isReplay: false,
|
|
|
|
proposalId: pr.proposalId,
|
|
|
|
retryInfo: pr.payRetryInfo,
|
|
|
|
lastError: pr.lastPayError,
|
|
|
|
});
|
2019-12-05 19:38:19 +01:00
|
|
|
}
|
2020-09-08 22:48:03 +02:00
|
|
|
if (pr.refundQueryRequested) {
|
2021-06-10 16:32:37 +02:00
|
|
|
resp.pendingOperations.push({
|
|
|
|
type: PendingOperationType.RefundQuery,
|
|
|
|
givesLifeness: true,
|
|
|
|
timestampDue: pr.refundStatusRetryInfo.nextRetry,
|
|
|
|
proposalId: pr.proposalId,
|
|
|
|
retryInfo: pr.refundStatusRetryInfo,
|
|
|
|
lastError: pr.lastRefundStatusError,
|
|
|
|
});
|
2019-12-06 00:24:34 +01:00
|
|
|
}
|
2019-12-05 19:38:19 +01:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-03-11 20:14:28 +01:00
|
|
|
async function gatherRecoupPending(
|
2021-06-09 15:14:17 +02:00
|
|
|
tx: GetReadOnlyAccess<{ recoupGroups: typeof WalletStoresV1.recoupGroups }>,
|
2020-03-11 20:14:28 +01:00
|
|
|
now: Timestamp,
|
|
|
|
resp: PendingOperationsResponse,
|
|
|
|
): Promise<void> {
|
2021-06-09 15:14:17 +02:00
|
|
|
await tx.recoupGroups.iter().forEach((rg) => {
|
2020-03-11 20:14:28 +01:00
|
|
|
if (rg.timestampFinished) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
resp.pendingOperations.push({
|
|
|
|
type: PendingOperationType.Recoup,
|
|
|
|
givesLifeness: true,
|
2021-06-10 16:32:37 +02:00
|
|
|
timestampDue: rg.retryInfo.nextRetry,
|
2020-03-11 20:14:28 +01:00
|
|
|
recoupGroupId: rg.recoupGroupId,
|
2020-03-12 14:55:38 +01:00
|
|
|
retryInfo: rg.retryInfo,
|
|
|
|
lastError: rg.lastError,
|
2020-03-11 20:14:28 +01:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-01-18 23:35:41 +01:00
|
|
|
async function gatherDepositPending(
|
2021-06-09 15:14:17 +02:00
|
|
|
tx: GetReadOnlyAccess<{ depositGroups: typeof WalletStoresV1.depositGroups }>,
|
2021-01-18 23:35:41 +01:00
|
|
|
now: Timestamp,
|
|
|
|
resp: PendingOperationsResponse,
|
|
|
|
): Promise<void> {
|
2021-06-09 15:14:17 +02:00
|
|
|
await tx.depositGroups.iter().forEach((dg) => {
|
2021-01-18 23:35:41 +01:00
|
|
|
if (dg.timestampFinished) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
resp.pendingOperations.push({
|
|
|
|
type: PendingOperationType.Deposit,
|
|
|
|
givesLifeness: true,
|
2021-06-10 16:32:37 +02:00
|
|
|
timestampDue: dg.retryInfo.nextRetry,
|
2021-01-18 23:35:41 +01:00
|
|
|
depositGroupId: dg.depositGroupId,
|
|
|
|
retryInfo: dg.retryInfo,
|
|
|
|
lastError: dg.lastError,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-12-02 00:42:40 +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) => ({
|
|
|
|
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) => {
|
2020-03-06 15:09:55 +01:00
|
|
|
const walletBalance = await getBalancesInsideTransaction(ws, tx);
|
|
|
|
const resp: PendingOperationsResponse = {
|
|
|
|
walletBalance,
|
|
|
|
pendingOperations: [],
|
|
|
|
};
|
2021-06-10 16:32:37 +02:00
|
|
|
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);
|
2020-03-06 15:09:55 +01:00
|
|
|
return resp;
|
2021-06-09 15:26:18 +02:00
|
|
|
});
|
2019-12-02 00:42:40 +01:00
|
|
|
}
|