diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 078060297..8ed4fe85e 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -62,6 +62,8 @@ import { Event, IDBDatabase } from "@gnu-taler/idb-bridge"; * will have an index. * - Amounts are stored as strings, except when they are needed for * indexing. + * - Every record that has a corresponding transaction item must have + * an index for a mandatory timestamp field. * - Optional fields should be avoided, use "T | undefined" instead. * * @author Florian Dold @@ -94,38 +96,45 @@ export const CURRENT_DB_CONFIG_KEY = "currentMainDbName"; */ export const WALLET_DB_MINOR_VERSION = 1; +export namespace OperationStatusRange { + export const ACTIVE_START = 10; + export const ACTIVE_END = 29; + export const DORMANT_START = 40; + export const DORMANT_END = 59; +} + /** * Status of a withdrawal. */ -export enum ReserveRecordStatus { +export enum WithdrawalGroupStatus { /** * Reserve must be registered with the bank. */ - RegisteringBank = "registering-bank", + RegisteringBank = OperationStatusRange.ACTIVE_START, /** * We've registered reserve's information with the bank * and are now waiting for the user to confirm the withdraw * with the bank (typically 2nd factor auth). */ - WaitConfirmBank = "wait-confirm-bank", + WaitConfirmBank = OperationStatusRange.ACTIVE_START + 1, /** * Querying reserve status with the exchange. */ - QueryingStatus = "querying-status", + QueryingStatus = OperationStatusRange.ACTIVE_START + 2, /** * The corresponding withdraw record has been created. * No further processing is done, unless explicitly requested * by the user. */ - Dormant = "dormant", + Finished = OperationStatusRange.DORMANT_START, /** * The bank aborted the withdrawal. */ - BankAborted = "bank-aborted", + BankAborted = OperationStatusRange.DORMANT_START + 1, } /** @@ -1354,20 +1363,12 @@ export interface WithdrawalGroupRecord { */ timestampFinish?: TalerProtocolTimestamp; - /** - * Operation status of the withdrawal group. - * Used for indexing in the database. - * - * FIXME: Redundant with reserveStatus - */ - operationStatus: OperationStatus; - /** * Current status of the reserve. * * FIXME: Wrong name! */ - reserveStatus: ReserveRecordStatus; + status: WithdrawalGroupStatus; /** * Amount that was sent by the user to fund the reserve. @@ -1947,7 +1948,7 @@ export const WalletStoresV1 = { }), { byReservePub: describeIndex("byReservePub", "reservePub"), - byStatus: describeIndex("byStatus", "operationStatus"), + byStatus: describeIndex("byStatus", "status"), byTalerWithdrawUri: describeIndex( "byTalerWithdrawUri", "wgInfo.bankInfo.talerWithdrawUri", diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts b/packages/taler-wallet-core/src/operations/backup/export.ts index b39e6dc27..c8454a62f 100644 --- a/packages/taler-wallet-core/src/operations/backup/export.ts +++ b/packages/taler-wallet-core/src/operations/backup/export.ts @@ -71,6 +71,7 @@ import { RefreshCoinStatus, RefundState, WALLET_BACKUP_STATE_KEY, + WithdrawalGroupStatus, WithdrawalRecordType, } from "../../db.js"; import { InternalWalletState } from "../../internal-wallet-state.js"; @@ -167,8 +168,9 @@ export async function exportBackup( instructed_amount: Amounts.stringify(wg.instructedAmount), reserve_priv: wg.reservePriv, restrict_age: wg.restrictAge, + // FIXME: proper status conversion! operation_status: - wg.operationStatus == OperationStatus.Finished + wg.status == WithdrawalGroupStatus.Finished ? BackupOperationStatus.Finished : BackupOperationStatus.Pending, selected_denoms_uid: wg.denomSelUid, diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts index 20c7316c1..3a92273df 100644 --- a/packages/taler-wallet-core/src/operations/backup/import.ts +++ b/packages/taler-wallet-core/src/operations/backup/import.ts @@ -52,7 +52,7 @@ import { RefreshSessionRecord, RefundState, ReserveBankInfo, - ReserveRecordStatus, + WithdrawalGroupStatus, WalletContractData, WalletRefundItem, WalletStoresV1, @@ -531,9 +531,6 @@ export async function importBackup( exchangeBaseUrl: backupWg.exchange_base_url, instructedAmount: Amounts.parseOrThrow(backupWg.instructed_amount), secretSeed: backupWg.secret_seed, - operationStatus: backupWg.timestamp_finish - ? OperationStatus.Finished - : OperationStatus.Pending, denomsSel: await getDenomSelStateFromBackup( tx, backupWg.exchange_base_url, @@ -545,9 +542,9 @@ export async function importBackup( ), reservePriv: backupWg.reserve_priv, reservePub, - reserveStatus: backupWg.timestamp_finish - ? ReserveRecordStatus.Dormant - : ReserveRecordStatus.QueryingStatus, // FIXME! + status: backupWg.timestamp_finish + ? WithdrawalGroupStatus.Finished + : WithdrawalGroupStatus.QueryingStatus, // FIXME! timestampStart: backupWg.timestamp_created, wgInfo, restrictAge: backupWg.restrict_age, diff --git a/packages/taler-wallet-core/src/operations/peer-to-peer.ts b/packages/taler-wallet-core/src/operations/peer-to-peer.ts index 48d422e0b..d30cb294d 100644 --- a/packages/taler-wallet-core/src/operations/peer-to-peer.ts +++ b/packages/taler-wallet-core/src/operations/peer-to-peer.ts @@ -65,7 +65,7 @@ import { import { CoinStatus, MergeReserveInfo, - ReserveRecordStatus, + WithdrawalGroupStatus, WalletStoresV1, WithdrawalRecordType, } from "../db.js"; @@ -544,7 +544,7 @@ export async function acceptPeerPushPayment( contractTerms: peerInc.contractTerms, }, exchangeBaseUrl: peerInc.exchangeBaseUrl, - reserveStatus: ReserveRecordStatus.QueryingStatus, + reserveStatus: WithdrawalGroupStatus.QueryingStatus, reserveKeyPair: { priv: mergeReserveInfo.reservePriv, pub: mergeReserveInfo.reservePub, @@ -828,7 +828,7 @@ export async function initiatePeerRequestForPay( contractPriv: econtractResp.contractPriv, }, exchangeBaseUrl: req.exchangeBaseUrl, - reserveStatus: ReserveRecordStatus.QueryingStatus, + reserveStatus: WithdrawalGroupStatus.QueryingStatus, reserveKeyPair: { priv: mergeReserveInfo.reservePriv, pub: mergeReserveInfo.reservePub, diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts index 9ba532ab7..18e8ec83b 100644 --- a/packages/taler-wallet-core/src/operations/pending.ts +++ b/packages/taler-wallet-core/src/operations/pending.ts @@ -28,6 +28,9 @@ import { BackupProviderStateTag, RefreshCoinStatus, OperationStatus, + WithdrawalGroupRecord, + WithdrawalGroupStatus, + OperationStatusRange, } from "../db.js"; import { PendingOperationsResponse, @@ -38,6 +41,7 @@ import { InternalWalletState } from "../internal-wallet-state.js"; import { GetReadOnlyAccess } from "../util/query.js"; import { RetryTags } from "../util/retries.js"; import { Wallet } from "../wallet.js"; +import { GlobalIDB } from "@gnu-taler/idb-bridge"; async function gatherExchangePending( tx: GetReadOnlyAccess<{ @@ -120,7 +124,10 @@ async function gatherWithdrawalPending( resp: PendingOperationsResponse, ): Promise { const wsrs = await tx.withdrawalGroups.indexes.byStatus.getAll( - OperationStatus.Pending, + GlobalIDB.KeyRange.bound( + OperationStatusRange.ACTIVE_START, + OperationStatusRange.ACTIVE_END, + ), ); for (const wsr of wsrs) { if (wsr.timestampFinish) { diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts index 119119035..6d899b947 100644 --- a/packages/taler-wallet-core/src/operations/recoup.ts +++ b/packages/taler-wallet-core/src/operations/recoup.ts @@ -44,7 +44,7 @@ import { CoinStatus, RecoupGroupRecord, RefreshCoinSource, - ReserveRecordStatus, + WithdrawalGroupStatus, WalletStoresV1, WithdrawalRecordType, WithdrawCoinSource, @@ -382,7 +382,7 @@ export async function processRecoupGroupHandler( await internalCreateWithdrawalGroup(ws, { amount: Amounts.parseOrThrow(result.balance), exchangeBaseUrl: recoupGroup.exchangeBaseUrl, - reserveStatus: ReserveRecordStatus.QueryingStatus, + reserveStatus: WithdrawalGroupStatus.QueryingStatus, reserveKeyPair: { pub: reservePub, priv: reservePrivMap[reservePub], diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index 19f6aee64..be1233d2c 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -31,11 +31,9 @@ import { TalerProtocolTimestamp, Transaction, TransactionByIdRequest, - TransactionRefund, TransactionsRequest, TransactionsResponse, TransactionType, - WithdrawalDetails, WithdrawalType, } from "@gnu-taler/taler-util"; import { diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index ad9875400..ce910363f 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -71,7 +71,7 @@ import { ExchangeRecord, OperationStatus, PlanchetRecord, - ReserveRecordStatus, + WithdrawalGroupStatus, WalletStoresV1, WgInfo, WithdrawalGroupRecord, @@ -91,7 +91,11 @@ import { readSuccessResponseJsonOrThrow, throwUnexpectedRequestError, } from "../util/http.js"; -import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js"; +import { + checkDbInvariant, + checkLogicInvariant, + InvariantViolatedError, +} from "../util/invariants.js"; import { DbAccess, GetReadOnlyAccess } from "../util/query.js"; import { OperationAttemptResult, @@ -962,7 +966,7 @@ async function queryReserve( withdrawalGroupId, }); checkDbInvariant(!!withdrawalGroup); - if (withdrawalGroup.reserveStatus !== ReserveRecordStatus.QueryingStatus) { + if (withdrawalGroup.status !== WithdrawalGroupStatus.QueryingStatus) { return { ready: true }; } const reservePub = withdrawalGroup.reservePub; @@ -1010,7 +1014,7 @@ async function queryReserve( logger.warn(`withdrawal group ${withdrawalGroupId} not found`); return; } - wg.reserveStatus = ReserveRecordStatus.Dormant; + wg.status = WithdrawalGroupStatus.Finished; await tx.withdrawalGroups.put(wg); }); @@ -1039,13 +1043,13 @@ export async function processWithdrawalGroup( throw Error(`withdrawal group ${withdrawalGroupId} not found`); } - switch (withdrawalGroup.reserveStatus) { - case ReserveRecordStatus.RegisteringBank: + switch (withdrawalGroup.status) { + case WithdrawalGroupStatus.RegisteringBank: await processReserveBankStatus(ws, withdrawalGroupId); return await processWithdrawalGroup(ws, withdrawalGroupId, { forceNow: true, }); - case ReserveRecordStatus.QueryingStatus: { + case WithdrawalGroupStatus.QueryingStatus: { const res = await queryReserve(ws, withdrawalGroupId); if (res.ready) { return await processWithdrawalGroup(ws, withdrawalGroupId, { @@ -1057,7 +1061,7 @@ export async function processWithdrawalGroup( result: undefined, }; } - case ReserveRecordStatus.WaitConfirmBank: { + case WithdrawalGroupStatus.WaitConfirmBank: { const res = await processReserveBankStatus(ws, withdrawalGroupId); switch (res.status) { case BankStatusResultCode.Aborted: @@ -1075,23 +1079,20 @@ export async function processWithdrawalGroup( } break; } - case ReserveRecordStatus.BankAborted: { + case WithdrawalGroupStatus.BankAborted: { // FIXME return { type: OperationAttemptResultType.Pending, result: undefined, }; } - case ReserveRecordStatus.Dormant: + case WithdrawalGroupStatus.Finished: // We can try to withdraw, nothing needs to be done with the reserve. break; default: - logger.warn( - "unknown reserve record status:", - withdrawalGroup.reserveStatus, + throw new InvariantViolatedError( + `unknown reserve record status: ${withdrawalGroup.status}`, ); - assertUnreachable(withdrawalGroup.reserveStatus); - break; } await ws.exchangeOps.updateExchangeFromUrl( @@ -1108,7 +1109,7 @@ export async function processWithdrawalGroup( if (!wg) { return; } - wg.operationStatus = OperationStatus.Finished; + wg.status = WithdrawalGroupStatus.Finished; wg.timestampFinish = TalerProtocolTimestamp.now(); await tx.withdrawalGroups.put(wg); }); @@ -1192,7 +1193,7 @@ export async function processWithdrawalGroup( if (wg.timestampFinish === undefined && numFinished === numTotalCoins) { finishedForFirstTime = true; wg.timestampFinish = TalerProtocolTimestamp.now(); - wg.operationStatus = OperationStatus.Finished; + wg.status = WithdrawalGroupStatus.Finished; } await tx.withdrawalGroups.put(wg); @@ -1508,9 +1509,9 @@ async function registerReserveWithBank( .runReadOnly(async (tx) => { return await tx.withdrawalGroups.get(withdrawalGroupId); }); - switch (withdrawalGroup?.reserveStatus) { - case ReserveRecordStatus.WaitConfirmBank: - case ReserveRecordStatus.RegisteringBank: + switch (withdrawalGroup?.status) { + case WithdrawalGroupStatus.WaitConfirmBank: + case WithdrawalGroupStatus.RegisteringBank: break; default: return; @@ -1544,9 +1545,9 @@ async function registerReserveWithBank( if (!r) { return; } - switch (r.reserveStatus) { - case ReserveRecordStatus.RegisteringBank: - case ReserveRecordStatus.WaitConfirmBank: + switch (r.status) { + case WithdrawalGroupStatus.RegisteringBank: + case WithdrawalGroupStatus.WaitConfirmBank: break; default: return; @@ -1557,8 +1558,7 @@ async function registerReserveWithBank( r.wgInfo.bankInfo.timestampReserveInfoPosted = AbsoluteTime.toTimestamp( AbsoluteTime.now(), ); - r.reserveStatus = ReserveRecordStatus.WaitConfirmBank; - r.operationStatus = OperationStatus.Pending; + r.status = WithdrawalGroupStatus.WaitConfirmBank; await tx.withdrawalGroups.put(r); }); ws.notify({ type: NotificationType.ReserveRegisteredWithBank }); @@ -1575,9 +1575,9 @@ async function processReserveBankStatus( const withdrawalGroup = await getWithdrawalGroupRecordTx(ws.db, { withdrawalGroupId, }); - switch (withdrawalGroup?.reserveStatus) { - case ReserveRecordStatus.WaitConfirmBank: - case ReserveRecordStatus.RegisteringBank: + switch (withdrawalGroup?.status) { + case WithdrawalGroupStatus.WaitConfirmBank: + case WithdrawalGroupStatus.RegisteringBank: break; default: return { @@ -1616,9 +1616,9 @@ async function processReserveBankStatus( if (!r) { return; } - switch (r.reserveStatus) { - case ReserveRecordStatus.RegisteringBank: - case ReserveRecordStatus.WaitConfirmBank: + switch (r.status) { + case WithdrawalGroupStatus.RegisteringBank: + case WithdrawalGroupStatus.WaitConfirmBank: break; default: return; @@ -1628,8 +1628,7 @@ async function processReserveBankStatus( } const now = AbsoluteTime.toTimestamp(AbsoluteTime.now()); r.wgInfo.bankInfo.timestampBankConfirmed = now; - r.reserveStatus = ReserveRecordStatus.BankAborted; - r.operationStatus = OperationStatus.Finished; + r.status = WithdrawalGroupStatus.BankAborted; await tx.withdrawalGroups.put(r); }); return { @@ -1644,7 +1643,7 @@ async function processReserveBankStatus( } // FIXME: Why do we do this?! - if (withdrawalGroup.reserveStatus === ReserveRecordStatus.RegisteringBank) { + if (withdrawalGroup.status === WithdrawalGroupStatus.RegisteringBank) { await registerReserveWithBank(ws, withdrawalGroupId); return await processReserveBankStatus(ws, withdrawalGroupId); } @@ -1657,9 +1656,9 @@ async function processReserveBankStatus( return; } // Re-check reserve status within transaction - switch (r.reserveStatus) { - case ReserveRecordStatus.RegisteringBank: - case ReserveRecordStatus.WaitConfirmBank: + switch (r.status) { + case WithdrawalGroupStatus.RegisteringBank: + case WithdrawalGroupStatus.WaitConfirmBank: break; default: return; @@ -1671,8 +1670,7 @@ async function processReserveBankStatus( logger.info("withdrawal: transfer confirmed by bank."); const now = AbsoluteTime.toTimestamp(AbsoluteTime.now()); r.wgInfo.bankInfo.timestampBankConfirmed = now; - r.reserveStatus = ReserveRecordStatus.QueryingStatus; - r.operationStatus = OperationStatus.Pending; + r.status = WithdrawalGroupStatus.QueryingStatus; } else { logger.info("withdrawal: transfer not yet confirmed by bank"); r.wgInfo.bankInfo.confirmUrl = status.confirm_transfer_url; @@ -1689,7 +1687,7 @@ async function processReserveBankStatus( export async function internalCreateWithdrawalGroup( ws: InternalWalletState, args: { - reserveStatus: ReserveRecordStatus; + reserveStatus: WithdrawalGroupStatus; amount: AmountJson; exchangeBaseUrl: string; forcedDenomSel?: ForcedDenomSel; @@ -1728,12 +1726,11 @@ export async function internalCreateWithdrawalGroup( exchangeBaseUrl: canonExchange, instructedAmount: amount, timestampStart: now, - operationStatus: OperationStatus.Pending, rawWithdrawalAmount: initialDenomSel.totalWithdrawCost, secretSeed, reservePriv: reserveKeyPair.priv, reservePub: reserveKeyPair.pub, - reserveStatus: args.reserveStatus, + status: args.reserveStatus, withdrawalGroupId, restrictAge: args.restrictAge, senderWire: undefined, @@ -1839,7 +1836,7 @@ export async function acceptWithdrawalFromUri( }, restrictAge: req.restrictAge, forcedDenomSel: req.forcedDenomSel, - reserveStatus: ReserveRecordStatus.RegisteringBank, + reserveStatus: WithdrawalGroupStatus.RegisteringBank, }); const withdrawalGroupId = withdrawalGroup.withdrawalGroupId; @@ -1850,9 +1847,7 @@ export async function acceptWithdrawalFromUri( const processedWithdrawalGroup = await getWithdrawalGroupRecordTx(ws.db, { withdrawalGroupId, }); - if ( - processedWithdrawalGroup?.reserveStatus === ReserveRecordStatus.BankAborted - ) { + if (processedWithdrawalGroup?.status === WithdrawalGroupStatus.BankAborted) { throw TalerError.fromDetail( TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK, {}, @@ -1898,7 +1893,7 @@ export async function createManualWithdrawal( exchangeBaseUrl: req.exchangeBaseUrl, forcedDenomSel: req.forcedDenomSel, restrictAge: req.restrictAge, - reserveStatus: ReserveRecordStatus.QueryingStatus, + reserveStatus: WithdrawalGroupStatus.QueryingStatus, }); const withdrawalGroupId = withdrawalGroup.withdrawalGroupId; diff --git a/packages/taler-wallet-core/src/util/invariants.ts b/packages/taler-wallet-core/src/util/invariants.ts index b788d044e..3598d857c 100644 --- a/packages/taler-wallet-core/src/util/invariants.ts +++ b/packages/taler-wallet-core/src/util/invariants.ts @@ -14,6 +14,13 @@ GNU Taler; see the file COPYING. If not, see */ +export class InvariantViolatedError extends Error { + constructor(message?: string) { + super(message); + Object.setPrototypeOf(this, InvariantViolatedError.prototype); + } +} + /** * Helpers for invariants. */ diff --git a/packages/taler-wallet-core/src/util/query.ts b/packages/taler-wallet-core/src/util/query.ts index d1aae6fd6..3d4ff79cb 100644 --- a/packages/taler-wallet-core/src/util/query.ts +++ b/packages/taler-wallet-core/src/util/query.ts @@ -36,8 +36,6 @@ import { IDBKeyRange, } from "@gnu-taler/idb-bridge"; import { Logger } from "@gnu-taler/taler-util"; -import { performanceNow } from "./timer.js"; -import { access } from "fs"; const logger = new Logger("query.ts");