From a70d37ef1675b53241f707c6730fab1537bd9d24 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 17 Jun 2021 15:49:05 +0200 Subject: towards factoring out cyclic dependencies --- packages/taler-wallet-core/src/common.ts | 182 +++++++++++++++++++++ packages/taler-wallet-core/src/errors.ts | 132 +++++++++++++++ .../taler-wallet-core/src/headless/NodeHttpLib.ts | 2 +- packages/taler-wallet-core/src/headless/helpers.ts | 2 +- packages/taler-wallet-core/src/index.ts | 6 +- .../taler-wallet-core/src/operations/README.md | 7 + .../src/operations/backup/export.ts | 2 +- .../src/operations/backup/import.ts | 2 +- .../src/operations/backup/index.ts | 2 +- .../src/operations/backup/state.ts | 2 +- .../taler-wallet-core/src/operations/balance.ts | 5 +- .../taler-wallet-core/src/operations/currencies.ts | 81 --------- .../taler-wallet-core/src/operations/deposits.ts | 33 ++-- .../taler-wallet-core/src/operations/errors.ts | 132 --------------- .../taler-wallet-core/src/operations/exchanges.ts | 86 ++++++++-- packages/taler-wallet-core/src/operations/pay.ts | 4 +- .../taler-wallet-core/src/operations/pending.ts | 2 +- .../taler-wallet-core/src/operations/recoup.ts | 4 +- .../taler-wallet-core/src/operations/refresh.ts | 4 +- .../taler-wallet-core/src/operations/refund.ts | 30 ++-- .../taler-wallet-core/src/operations/reserves.ts | 6 +- packages/taler-wallet-core/src/operations/state.ts | 148 ----------------- .../taler-wallet-core/src/operations/testing.ts | 2 +- packages/taler-wallet-core/src/operations/tip.ts | 4 +- .../src/operations/transactions.ts | 2 +- .../taler-wallet-core/src/operations/versions.ts | 45 ----- .../taler-wallet-core/src/operations/withdraw.ts | 95 ++++++----- packages/taler-wallet-core/src/pending-types.ts | 1 - packages/taler-wallet-core/src/util/http.ts | 2 +- packages/taler-wallet-core/src/versions.ts | 45 +++++ packages/taler-wallet-core/src/wallet.ts | 4 +- 31 files changed, 540 insertions(+), 534 deletions(-) create mode 100644 packages/taler-wallet-core/src/common.ts create mode 100644 packages/taler-wallet-core/src/errors.ts create mode 100644 packages/taler-wallet-core/src/operations/README.md delete mode 100644 packages/taler-wallet-core/src/operations/currencies.ts delete mode 100644 packages/taler-wallet-core/src/operations/errors.ts delete mode 100644 packages/taler-wallet-core/src/operations/state.ts delete mode 100644 packages/taler-wallet-core/src/operations/versions.ts create mode 100644 packages/taler-wallet-core/src/versions.ts diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts new file mode 100644 index 000000000..cbbbb1e32 --- /dev/null +++ b/packages/taler-wallet-core/src/common.ts @@ -0,0 +1,182 @@ +/* + 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 + */ + +/** + * Imports. + */ +import { + WalletNotification, + BalancesResponse, + Logger, +} from "@gnu-taler/taler-util"; +import { CryptoApi, CryptoWorkerFactory } from "./crypto/workers/cryptoApi.js"; +import { ExchangeDetailsRecord, ExchangeRecord, WalletStoresV1 } from "./db.js"; +import { PendingOperationsResponse } from "./pending-types.js"; +import { AsyncOpMemoMap, AsyncOpMemoSingle } from "./util/asyncMemo.js"; +import { HttpRequestLibrary } from "./util/http.js"; +import { + AsyncCondition, + OpenedPromise, + openPromise, +} from "./util/promiseUtils.js"; +import { DbAccess, GetReadOnlyAccess } from "./util/query.js"; +import { TimerGroup } from "./util/timer.js"; + +type NotificationListener = (n: WalletNotification) => void; + +const logger = new Logger("state.ts"); + +export const EXCHANGE_COINS_LOCK = "exchange-coins-lock"; +export const EXCHANGE_RESERVES_LOCK = "exchange-reserves-lock"; + +export interface TrustInfo { + isTrusted: boolean; + isAudited: boolean; +} + +/** + * Interface for exchange-related operations. + */ +export interface ExchangeOperations { + // FIXME: Should other operations maybe always use + // updateExchangeFromUrl? + getExchangeDetails( + tx: GetReadOnlyAccess<{ + exchanges: typeof WalletStoresV1.exchanges; + exchangeDetails: typeof WalletStoresV1.exchangeDetails; + }>, + exchangeBaseUrl: string, + ): Promise; + getExchangeTrust( + ws: InternalWalletState, + exchangeInfo: ExchangeRecord, + ): Promise; + updateExchangeFromUrl( + ws: InternalWalletState, + baseUrl: string, + forceNow?: boolean, + ): Promise<{ + exchange: ExchangeRecord; + exchangeDetails: ExchangeDetailsRecord; + }>; +} + +/** + * Internal state of the wallet. + */ +export class InternalWalletState { + memoProcessReserve: AsyncOpMemoMap = new AsyncOpMemoMap(); + memoMakePlanchet: AsyncOpMemoMap = new AsyncOpMemoMap(); + memoGetPending: AsyncOpMemoSingle = new AsyncOpMemoSingle(); + memoGetBalance: AsyncOpMemoSingle = new AsyncOpMemoSingle(); + memoProcessRefresh: AsyncOpMemoMap = new AsyncOpMemoMap(); + memoProcessRecoup: AsyncOpMemoMap = new AsyncOpMemoMap(); + memoProcessDeposit: AsyncOpMemoMap = new AsyncOpMemoMap(); + cryptoApi: CryptoApi; + + timerGroup: TimerGroup = new TimerGroup(); + latch = new AsyncCondition(); + stopped = false; + memoRunRetryLoop = new AsyncOpMemoSingle(); + + listeners: NotificationListener[] = []; + + initCalled: boolean = false; + + exchangeOps: ExchangeOperations; + + /** + * Promises that are waiting for a particular resource. + */ + private resourceWaiters: Record[]> = {}; + + /** + * Resources that are currently locked. + */ + private resourceLocks: Set = new Set(); + + constructor( + // FIXME: Make this a getter and make + // the actual value nullable. + // Check if we are in a DB migration / garbage collection + // and throw an error in that case. + public db: DbAccess, + public http: HttpRequestLibrary, + cryptoWorkerFactory: CryptoWorkerFactory, + ) { + this.cryptoApi = new CryptoApi(cryptoWorkerFactory); + } + + notify(n: WalletNotification): void { + logger.trace("Notification", n); + for (const l of this.listeners) { + const nc = JSON.parse(JSON.stringify(n)); + setTimeout(() => { + l(nc); + }, 0); + } + } + + addNotificationListener(f: (n: WalletNotification) => void): void { + this.listeners.push(f); + } + + /** + * Stop ongoing processing. + */ + stop(): void { + this.stopped = true; + this.timerGroup.stopCurrentAndFutureTimers(); + this.cryptoApi.stop(); + } + + /** + * Run an async function after acquiring a list of locks, identified + * by string tokens. + */ + async runSequentialized(tokens: string[], f: () => Promise) { + // Make sure locks are always acquired in the same order + tokens = [...tokens].sort(); + + for (const token of tokens) { + if (this.resourceLocks.has(token)) { + const p = openPromise(); + let waitList = this.resourceWaiters[token]; + if (!waitList) { + waitList = this.resourceWaiters[token] = []; + } + waitList.push(p); + await p.promise; + } + this.resourceLocks.add(token); + } + + try { + logger.trace(`begin exclusive execution on ${JSON.stringify(tokens)}`); + const result = await f(); + logger.trace(`end exclusive execution on ${JSON.stringify(tokens)}`); + return result; + } finally { + for (const token of tokens) { + this.resourceLocks.delete(token); + let waiter = (this.resourceWaiters[token] ?? []).shift(); + if (waiter) { + waiter.resolve(); + } + } + } + } +} diff --git a/packages/taler-wallet-core/src/errors.ts b/packages/taler-wallet-core/src/errors.ts new file mode 100644 index 000000000..d788405ff --- /dev/null +++ b/packages/taler-wallet-core/src/errors.ts @@ -0,0 +1,132 @@ +/* + This file is part of GNU Taler + (C) 2019-2020 Taler Systems SA + + 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 + */ + +/** + * Classes and helpers for error handling specific to wallet operations. + * + * @author Florian Dold + */ + +/** + * Imports. + */ +import { TalerErrorCode, TalerErrorDetails } from "@gnu-taler/taler-util"; + +/** + * This exception is there to let the caller know that an error happened, + * but the error has already been reported by writing it to the database. + */ +export class OperationFailedAndReportedError extends Error { + static fromCode( + ec: TalerErrorCode, + message: string, + details: Record, + ): OperationFailedAndReportedError { + return new OperationFailedAndReportedError( + makeErrorDetails(ec, message, details), + ); + } + + constructor(public operationError: TalerErrorDetails) { + super(operationError.message); + + // Set the prototype explicitly. + Object.setPrototypeOf(this, OperationFailedAndReportedError.prototype); + } +} + +/** + * This exception is thrown when an error occurred and the caller is + * responsible for recording the failure in the database. + */ +export class OperationFailedError extends Error { + static fromCode( + ec: TalerErrorCode, + message: string, + details: Record, + ): OperationFailedError { + return new OperationFailedError(makeErrorDetails(ec, message, details)); + } + + constructor(public operationError: TalerErrorDetails) { + super(operationError.message); + + // Set the prototype explicitly. + Object.setPrototypeOf(this, OperationFailedError.prototype); + } +} + +export function makeErrorDetails( + ec: TalerErrorCode, + message: string, + details: Record, +): TalerErrorDetails { + return { + code: ec, + hint: `Error: ${TalerErrorCode[ec]}`, + details: details, + message, + }; +} + +/** + * Run an operation and call the onOpError callback + * when there was an exception or operation error that must be reported. + * The cause will be re-thrown to the caller. + */ +export async function guardOperationException( + op: () => Promise, + onOpError: (e: TalerErrorDetails) => Promise, +): Promise { + try { + return await op(); + } catch (e) { + if (e instanceof OperationFailedAndReportedError) { + throw e; + } + if (e instanceof OperationFailedError) { + await onOpError(e.operationError); + throw new OperationFailedAndReportedError(e.operationError); + } + if (e instanceof Error) { + const opErr = makeErrorDetails( + TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, + `unexpected exception (message: ${e.message})`, + { + stack: e.stack, + }, + ); + await onOpError(opErr); + throw new OperationFailedAndReportedError(opErr); + } + // Something was thrown that is not even an exception! + // Try to stringify it. + let excString: string; + try { + excString = e.toString(); + } catch (e) { + // Something went horribly wrong. + excString = "can't stringify exception"; + } + const opErr = makeErrorDetails( + TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, + `unexpected exception (not an exception, ${excString})`, + {}, + ); + await onOpError(opErr); + throw new OperationFailedAndReportedError(opErr); + } +} diff --git a/packages/taler-wallet-core/src/headless/NodeHttpLib.ts b/packages/taler-wallet-core/src/headless/NodeHttpLib.ts index 4d0411081..525ac8557 100644 --- a/packages/taler-wallet-core/src/headless/NodeHttpLib.ts +++ b/packages/taler-wallet-core/src/headless/NodeHttpLib.ts @@ -27,7 +27,7 @@ import { } from "../util/http"; import { RequestThrottler } from "../util/RequestThrottler"; import Axios, { AxiosResponse } from "axios"; -import { OperationFailedError, makeErrorDetails } from "../operations/errors"; +import { OperationFailedError, makeErrorDetails } from "../errors"; import { URL } from "../util/url"; import { Logger } from "@gnu-taler/taler-util"; import { bytesToString } from "../crypto/talerCrypto"; diff --git a/packages/taler-wallet-core/src/headless/helpers.ts b/packages/taler-wallet-core/src/headless/helpers.ts index 8125ef6b0..c05d60497 100644 --- a/packages/taler-wallet-core/src/headless/helpers.ts +++ b/packages/taler-wallet-core/src/headless/helpers.ts @@ -35,7 +35,7 @@ import { Logger } from "@gnu-taler/taler-util"; import { SynchronousCryptoWorkerFactory } from "../crypto/workers/synchronousWorker"; import type { IDBFactory } from "@gnu-taler/idb-bridge"; import { WalletNotification } from "@gnu-taler/taler-util"; -import { InternalWalletState } from "../operations/state.js"; +import { InternalWalletState } from "../common.js"; const logger = new Logger("headless/helpers.ts"); diff --git a/packages/taler-wallet-core/src/index.ts b/packages/taler-wallet-core/src/index.ts index 995af438d..850ee3a16 100644 --- a/packages/taler-wallet-core/src/index.ts +++ b/packages/taler-wallet-core/src/index.ts @@ -19,7 +19,7 @@ */ // Errors -export * from "./operations/errors.js"; +export * from "./errors.js"; // Util functionality export { URL } from "./util/url.js"; @@ -34,7 +34,7 @@ export { DefaultNodeWalletArgs, } from "./headless/helpers.js"; -export * from "./operations/versions.js"; +export * from "./versions.js"; export * from "./db.js"; @@ -48,6 +48,6 @@ export * from "./crypto/talerCrypto.js"; export * from "./pending-types.js"; export * from "./util/debugFlags.js"; -export { InternalWalletState } from "./operations/state.js"; +export { InternalWalletState } from "./common.js"; export * from "./wallet-api-types.js"; export * from "./wallet.js"; diff --git a/packages/taler-wallet-core/src/operations/README.md b/packages/taler-wallet-core/src/operations/README.md new file mode 100644 index 000000000..32e2fbfc8 --- /dev/null +++ b/packages/taler-wallet-core/src/operations/README.md @@ -0,0 +1,7 @@ +# Wallet Operations + +This folder contains the implementations for all wallet operations that operate on the wallet state. + +To avoid cyclic dependencies, these files must **not** reference each other. Instead, other operations should only be accessed via injected dependencies. + +Avoiding cyclic dependencies is important for module bundlers. \ No newline at end of file diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts b/packages/taler-wallet-core/src/operations/backup/export.ts index 4eab9d5ee..42cc9b651 100644 --- a/packages/taler-wallet-core/src/operations/backup/export.ts +++ b/packages/taler-wallet-core/src/operations/backup/export.ts @@ -49,7 +49,7 @@ import { BackupRefreshSession, BackupExchangeDetails, } from "@gnu-taler/taler-util"; -import { InternalWalletState } from "../state.js"; +import { InternalWalletState } from "../../common.js"; import { provideBackupState, getWalletBackupState } from "./state"; import { Amounts, getTimestampNow } from "@gnu-taler/taler-util"; import { diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts index 1caa508ff..ce4d14f1e 100644 --- a/packages/taler-wallet-core/src/operations/backup/import.ts +++ b/packages/taler-wallet-core/src/operations/backup/import.ts @@ -51,7 +51,7 @@ import { j2s } from "@gnu-taler/taler-util"; import { checkDbInvariant, checkLogicInvariant } from "../../util/invariants"; import { Logger } from "@gnu-taler/taler-util"; import { initRetryInfo } from "../../util/retries.js"; -import { InternalWalletState } from "../state.js"; +import { InternalWalletState } from "../../common.js"; import { provideBackupState } from "./state.js"; import { makeEventId, TombstoneTag } from "../transactions.js"; import { getExchangeDetails } from "../exchanges.js"; diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts index 2cc056721..8a0aad560 100644 --- a/packages/taler-wallet-core/src/operations/backup/index.ts +++ b/packages/taler-wallet-core/src/operations/backup/index.ts @@ -24,7 +24,7 @@ /** * Imports. */ -import { InternalWalletState } from "../state.js"; +import { InternalWalletState } from "../../common.js"; import { AmountString, BackupRecovery, diff --git a/packages/taler-wallet-core/src/operations/backup/state.ts b/packages/taler-wallet-core/src/operations/backup/state.ts index bb540d5af..3a7311d14 100644 --- a/packages/taler-wallet-core/src/operations/backup/state.ts +++ b/packages/taler-wallet-core/src/operations/backup/state.ts @@ -23,7 +23,7 @@ import { } from "../../db.js"; import { checkDbInvariant } from "../../util/invariants.js"; import { GetReadOnlyAccess } from "../../util/query.js"; -import { InternalWalletState } from "../state.js"; +import { InternalWalletState } from "../../common.js"; export async function provideBackupState( ws: InternalWalletState, diff --git a/packages/taler-wallet-core/src/operations/balance.ts b/packages/taler-wallet-core/src/operations/balance.ts index 4dba6beb7..298893920 100644 --- a/packages/taler-wallet-core/src/operations/balance.ts +++ b/packages/taler-wallet-core/src/operations/balance.ts @@ -23,12 +23,11 @@ import { Amounts, Logger, } from "@gnu-taler/taler-util"; - import { CoinStatus, WalletStoresV1 } from "../db.js"; import { GetReadOnlyAccess } from "../util/query.js"; -import { InternalWalletState } from "./state.js"; +import { InternalWalletState } from "../common.js"; -const logger = new Logger("withdraw.ts"); +const logger = new Logger("operations/balance.ts"); interface WalletBalance { available: AmountJson; diff --git a/packages/taler-wallet-core/src/operations/currencies.ts b/packages/taler-wallet-core/src/operations/currencies.ts deleted file mode 100644 index e591b50c0..000000000 --- a/packages/taler-wallet-core/src/operations/currencies.ts +++ /dev/null @@ -1,81 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021 Taler Systems S.A. - - 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 - */ - -/** - * Imports. - */ -import { ExchangeRecord } from "../db.js"; -import { Logger } from "@gnu-taler/taler-util"; -import { getExchangeDetails } from "./exchanges.js"; -import { InternalWalletState } from "./state.js"; - -const logger = new Logger("currencies.ts"); - -export interface TrustInfo { - isTrusted: boolean; - isAudited: boolean; -} - -/** - * Check if and how an exchange is trusted and/or audited. - */ -export async function getExchangeTrust( - ws: InternalWalletState, - exchangeInfo: ExchangeRecord, -): Promise { - let isTrusted = false; - let isAudited = false; - - return await ws.db - .mktx((x) => ({ - exchanges: x.exchanges, - exchangeDetails: x.exchangeDetails, - exchangesTrustStore: x.exchangeTrust, - auditorTrust: x.auditorTrust, - })) - .runReadOnly(async (tx) => { - const exchangeDetails = await getExchangeDetails( - tx, - exchangeInfo.baseUrl, - ); - - if (!exchangeDetails) { - throw Error(`exchange ${exchangeInfo.baseUrl} details not available`); - } - const exchangeTrustRecord = await tx.exchangesTrustStore.indexes.byExchangeMasterPub.get( - exchangeDetails.masterPublicKey, - ); - if ( - exchangeTrustRecord && - exchangeTrustRecord.uids.length > 0 && - exchangeTrustRecord.currency === exchangeDetails.currency - ) { - isTrusted = true; - } - - for (const auditor of exchangeDetails.auditors) { - const auditorTrustRecord = await tx.auditorTrust.indexes.byAuditorPub.get( - auditor.auditor_pub, - ); - if (auditorTrustRecord && auditorTrustRecord.uids.length > 0) { - isAudited = true; - break; - } - } - - return { isTrusted, isAudited }; - }); -} diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index c376ae8e0..5051fd32a 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -14,19 +14,10 @@ GNU Taler; see the file COPYING. If not, see */ -import { kdf } from "../crypto/primitives/kdf.js"; -import { - encodeCrock, - getRandomBytes, - stringToBytes, -} from "../crypto/talerCrypto.js"; -import { selectPayCoins } from "../util/coinSelection.js"; -import { canonicalJson } from "@gnu-taler/taler-util"; -import { readSuccessResponseJsonOrThrow } from "../util/http.js"; -import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; import { Amounts, buildCodecForObject, + canonicalJson, Codec, codecForString, codecForTimestamp, @@ -36,6 +27,7 @@ import { CreateDepositGroupResponse, durationFromSpec, getTimestampNow, + Logger, NotificationType, parsePaytoUri, TalerErrorDetails, @@ -45,7 +37,20 @@ import { TrackDepositGroupRequest, TrackDepositGroupResponse, } from "@gnu-taler/taler-util"; -import { URL } from "../util/url"; +import { InternalWalletState } from "../common.js"; +import { kdf } from "../crypto/primitives/kdf.js"; +import { + encodeCrock, + getRandomBytes, + stringToBytes, +} from "../crypto/talerCrypto.js"; +import { DepositGroupRecord } from "../db.js"; +import { guardOperationException } from "../errors.js"; +import { selectPayCoins } from "../util/coinSelection.js"; +import { readSuccessResponseJsonOrThrow } from "../util/http.js"; +import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; +import { URL } from "../util/url.js"; +import { getExchangeDetails } from "./exchanges.js"; import { applyCoinSpend, extractContractData, @@ -54,12 +59,6 @@ import { getEffectiveDepositAmount, getTotalPaymentCost, } from "./pay.js"; -import { InternalWalletState } from "./state.js"; -import { Logger } from "@gnu-taler/taler-util"; -import { DepositGroupRecord } from "../db.js"; - -import { guardOperationException } from "./errors.js"; -import { getExchangeDetails } from "./exchanges.js"; /** * Logger. diff --git a/packages/taler-wallet-core/src/operations/errors.ts b/packages/taler-wallet-core/src/operations/errors.ts deleted file mode 100644 index d788405ff..000000000 --- a/packages/taler-wallet-core/src/operations/errors.ts +++ /dev/null @@ -1,132 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2019-2020 Taler Systems SA - - 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 - */ - -/** - * Classes and helpers for error handling specific to wallet operations. - * - * @author Florian Dold - */ - -/** - * Imports. - */ -import { TalerErrorCode, TalerErrorDetails } from "@gnu-taler/taler-util"; - -/** - * This exception is there to let the caller know that an error happened, - * but the error has already been reported by writing it to the database. - */ -export class OperationFailedAndReportedError extends Error { - static fromCode( - ec: TalerErrorCode, - message: string, - details: Record, - ): OperationFailedAndReportedError { - return new OperationFailedAndReportedError( - makeErrorDetails(ec, message, details), - ); - } - - constructor(public operationError: TalerErrorDetails) { - super(operationError.message); - - // Set the prototype explicitly. - Object.setPrototypeOf(this, OperationFailedAndReportedError.prototype); - } -} - -/** - * This exception is thrown when an error occurred and the caller is - * responsible for recording the failure in the database. - */ -export class OperationFailedError extends Error { - static fromCode( - ec: TalerErrorCode, - message: string, - details: Record, - ): OperationFailedError { - return new OperationFailedError(makeErrorDetails(ec, message, details)); - } - - constructor(public operationError: TalerErrorDetails) { - super(operationError.message); - - // Set the prototype explicitly. - Object.setPrototypeOf(this, OperationFailedError.prototype); - } -} - -export function makeErrorDetails( - ec: TalerErrorCode, - message: string, - details: Record, -): TalerErrorDetails { - return { - code: ec, - hint: `Error: ${TalerErrorCode[ec]}`, - details: details, - message, - }; -} - -/** - * Run an operation and call the onOpError callback - * when there was an exception or operation error that must be reported. - * The cause will be re-thrown to the caller. - */ -export async function guardOperationException( - op: () => Promise, - onOpError: (e: TalerErrorDetails) => Promise, -): Promise { - try { - return await op(); - } catch (e) { - if (e instanceof OperationFailedAndReportedError) { - throw e; - } - if (e instanceof OperationFailedError) { - await onOpError(e.operationError); - throw new OperationFailedAndReportedError(e.operationError); - } - if (e instanceof Error) { - const opErr = makeErrorDetails( - TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, - `unexpected exception (message: ${e.message})`, - { - stack: e.stack, - }, - ); - await onOpError(opErr); - throw new OperationFailedAndReportedError(opErr); - } - // Something was thrown that is not even an exception! - // Try to stringify it. - let excString: string; - try { - excString = e.toString(); - } catch (e) { - // Something went horribly wrong. - excString = "can't stringify exception"; - } - const opErr = makeErrorDetails( - TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION, - `unexpected exception (not an exception, ${excString})`, - {}, - ); - await onOpError(opErr); - throw new OperationFailedAndReportedError(opErr); - } -} diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index c8dfcbc17..65cc8f5e9 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -20,6 +20,7 @@ import { Amounts, Auditor, + canonicalizeBaseUrl, codecForExchangeKeysJson, codecForExchangeWireJson, compare, @@ -30,6 +31,7 @@ import { ExchangeWireJson, getTimestampNow, isTimestampExpired, + j2s, Logger, NotificationType, parsePaytoUri, @@ -38,38 +40,37 @@ import { TalerErrorDetails, Timestamp, } from "@gnu-taler/taler-util"; +import { decodeCrock, encodeCrock, hash } from "../crypto/talerCrypto.js"; +import { CryptoApi } from "../crypto/workers/cryptoApi.js"; import { DenominationRecord, DenominationStatus, + ExchangeDetailsRecord, ExchangeRecord, + WalletStoresV1, WireFee, - ExchangeDetailsRecord, WireInfo, - WalletStoresV1, } from "../db.js"; -import { j2s, canonicalizeBaseUrl } from "@gnu-taler/taler-util"; -import { updateRetryInfoTimeout, initRetryInfo } from "../util/retries.js"; -import { - makeErrorDetails, - guardOperationException, - OperationFailedError, -} from "./errors.js"; -import { createRecoupGroup, processRecoupGroup } from "./recoup.js"; -import { InternalWalletState } from "./state.js"; -import { - WALLET_CACHE_BREAKER_CLIENT_VERSION, - WALLET_EXCHANGE_PROTOCOL_VERSION, -} from "./versions.js"; import { getExpiryTimestamp, HttpRequestLibrary, readSuccessResponseJsonOrThrow, readSuccessResponseTextOrThrow, } from "../util/http.js"; -import { CryptoApi } from "../crypto/workers/cryptoApi.js"; import { DbAccess, GetReadOnlyAccess } from "../util/query.js"; -import { decodeCrock, encodeCrock, hash } from "../crypto/talerCrypto.js"; +import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; import { URL } from "../util/url.js"; +import { + guardOperationException, + makeErrorDetails, + OperationFailedError, +} from "../errors.js"; +import { createRecoupGroup, processRecoupGroup } from "./recoup.js"; +import { InternalWalletState, TrustInfo } from "../common.js"; +import { + WALLET_CACHE_BREAKER_CLIENT_VERSION, + WALLET_EXCHANGE_PROTOCOL_VERSION, +} from "../versions.js"; const logger = new Logger("exchanges.ts"); @@ -605,3 +606,54 @@ export async function getExchangePaytoUri( } throw Error("no matching exchange account found"); } + +/** + * Check if and how an exchange is trusted and/or audited. + */ +export async function getExchangeTrust( + ws: InternalWalletState, + exchangeInfo: ExchangeRecord, +): Promise { + let isTrusted = false; + let isAudited = false; + + return await ws.db + .mktx((x) => ({ + exchanges: x.exchanges, + exchangeDetails: x.exchangeDetails, + exchangesTrustStore: x.exchangeTrust, + auditorTrust: x.auditorTrust, + })) + .runReadOnly(async (tx) => { + const exchangeDetails = await getExchangeDetails( + tx, + exchangeInfo.baseUrl, + ); + + if (!exchangeDetails) { + throw Error(`exchange ${exchangeInfo.baseUrl} details not available`); + } + const exchangeTrustRecord = await tx.exchangesTrustStore.indexes.byExchangeMasterPub.get( + exchangeDetails.masterPublicKey, + ); + if ( + exchangeTrustRecord && + exchangeTrustRecord.uids.length > 0 && + exchangeTrustRecord.currency === exchangeDetails.currency + ) { + isTrusted = true; + } + + for (const auditor of exchangeDetails.auditors) { + const auditorTrustRecord = await tx.auditorTrust.indexes.byAuditorPub.get( + auditor.auditor_pub, + ); + if (auditorTrustRecord && auditorTrustRecord.uids.length > 0) { + isAudited = true; + break; + } + } + + return { isTrusted, isAudited }; + }); +} diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts index c62ad0f25..6d185cae8 100644 --- a/packages/taler-wallet-core/src/operations/pay.ts +++ b/packages/taler-wallet-core/src/operations/pay.ts @@ -68,7 +68,7 @@ import { getRetryDuration, } from "../util/retries.js"; import { getTotalRefreshCost, createRefreshGroup } from "./refresh.js"; -import { InternalWalletState, EXCHANGE_COINS_LOCK } from "./state.js"; +import { InternalWalletState, EXCHANGE_COINS_LOCK } from "../common.js"; import { ContractTermsUtil } from "../util/contractTerms.js"; import { getExchangeDetails } from "./exchanges.js"; import { GetReadWriteAccess } from "../util/query.js"; @@ -98,7 +98,7 @@ import { makeErrorDetails, OperationFailedAndReportedError, OperationFailedError, -} from "./errors.js"; +} from "../errors.js"; import { URL } from "../util/url.js"; /** diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts index 8201e8cb9..fff64739c 100644 --- a/packages/taler-wallet-core/src/operations/pending.ts +++ b/packages/taler-wallet-core/src/operations/pending.ts @@ -29,7 +29,7 @@ import { ReserveType, } from "../pending-types.js"; import { getTimestampNow, Timestamp } from "@gnu-taler/taler-util"; -import { InternalWalletState } from "./state.js"; +import { InternalWalletState } from "../common.js"; import { getBalancesInsideTransaction } from "./balance.js"; import { GetReadOnlyAccess } from "../util/query.js"; diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts index 938422d3b..402111129 100644 --- a/packages/taler-wallet-core/src/operations/recoup.ts +++ b/packages/taler-wallet-core/src/operations/recoup.ts @@ -48,10 +48,10 @@ import { readSuccessResponseJsonOrThrow } from "../util/http.js"; import { Logger } from "@gnu-taler/taler-util"; import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; import { URL } from "../util/url.js"; -import { guardOperationException } from "./errors.js"; +import { guardOperationException } from "../errors.js"; import { createRefreshGroup, processRefreshGroup } from "./refresh.js"; import { getReserveRequestTimeout, processReserve } from "./reserves.js"; -import { InternalWalletState } from "./state.js"; +import { InternalWalletState } from "../common.js"; import { GetReadWriteAccess } from "../util/query.js"; const logger = new Logger("operations/recoup.ts"); diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index 3c81362ce..06d735645 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -52,9 +52,9 @@ import { timestampMin, } from "@gnu-taler/taler-util"; import { URL } from "../util/url.js"; -import { guardOperationException } from "./errors.js"; +import { guardOperationException } from "../errors.js"; import { updateExchangeFromUrl } from "./exchanges.js"; -import { EXCHANGE_COINS_LOCK, InternalWalletState } from "./state.js"; +import { EXCHANGE_COINS_LOCK, InternalWalletState } from "../common.js"; import { isWithdrawableDenom, selectWithdrawalDenominations, diff --git a/packages/taler-wallet-core/src/operations/refund.ts b/packages/taler-wallet-core/src/operations/refund.ts index 6ef55e535..09006b811 100644 --- a/packages/taler-wallet-core/src/operations/refund.ts +++ b/packages/taler-wallet-core/src/operations/refund.ts @@ -23,12 +23,7 @@ /** * Imports. */ -import { InternalWalletState } from "./state.js"; -import { guardOperationException } from "./errors.js"; import { - getTimestampNow, - timestampAddDuration, - TalerErrorDetails, AbortingCoin, AbortRequest, AmountJson, @@ -37,29 +32,34 @@ import { codecForAbortResponse, codecForMerchantOrderRefundPickupResponse, CoinPublicKey, + getTimestampNow, + Logger, MerchantCoinRefundFailureStatus, MerchantCoinRefundStatus, MerchantCoinRefundSuccessStatus, NotificationType, parseRefundUri, RefreshReason, + TalerErrorCode, + TalerErrorDetails, + timestampAddDuration, } from "@gnu-taler/taler-util"; -import { Logger } from "@gnu-taler/taler-util"; -import { readSuccessResponseJsonOrThrow } from "../util/http.js"; -import { URL } from "../util/url.js"; -import { updateRetryInfoTimeout, initRetryInfo } from "../util/retries.js"; -import { checkDbInvariant } from "../util/invariants.js"; -import { TalerErrorCode } from "@gnu-taler/taler-util"; import { - PurchaseRecord, - CoinStatus, - RefundState, AbortStatus, + CoinStatus, + PurchaseRecord, RefundReason, + RefundState, WalletStoresV1, } from "../db.js"; -import { getTotalRefreshCost, createRefreshGroup } from "./refresh.js"; +import { readSuccessResponseJsonOrThrow } from "../util/http.js"; +import { checkDbInvariant } from "../util/invariants.js"; import { GetReadWriteAccess } from "../util/query.js"; +import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; +import { URL } from "../util/url.js"; +import { guardOperationException } from "../errors.js"; +import { createRefreshGroup, getTotalRefreshCost } from "./refresh.js"; +import { InternalWalletState } from "../common.js"; const logger = new Logger("refund.ts"); diff --git a/packages/taler-wallet-core/src/operations/reserves.ts b/packages/taler-wallet-core/src/operations/reserves.ts index 998fbf3e0..dde71f040 100644 --- a/packages/taler-wallet-core/src/operations/reserves.ts +++ b/packages/taler-wallet-core/src/operations/reserves.ts @@ -47,13 +47,14 @@ import { getRetryDuration, updateRetryInfoTimeout, } from "../util/retries.js"; -import { guardOperationException, OperationFailedError } from "./errors.js"; +import { guardOperationException, OperationFailedError } from "../errors.js"; import { updateExchangeFromUrl, getExchangePaytoUri, getExchangeDetails, + getExchangeTrust, } from "./exchanges.js"; -import { InternalWalletState } from "./state.js"; +import { InternalWalletState } from "../common.js"; import { updateWithdrawalDenoms, getCandidateWithdrawalDenoms, @@ -62,7 +63,6 @@ import { processWithdrawGroup, getBankWithdrawalInfo, } from "./withdraw.js"; -import { getExchangeTrust } from "./currencies.js"; import { encodeCrock, getRandomBytes } from "../crypto/talerCrypto.js"; import { Logger } from "@gnu-taler/taler-util"; import { diff --git a/packages/taler-wallet-core/src/operations/state.ts b/packages/taler-wallet-core/src/operations/state.ts deleted file mode 100644 index ee7ceb8af..000000000 --- a/packages/taler-wallet-core/src/operations/state.ts +++ /dev/null @@ -1,148 +0,0 @@ -/* - 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 - */ - -/** - * Imports. - */ -import { - WalletNotification, - BalancesResponse, - Logger, -} from "@gnu-taler/taler-util"; -import { CryptoApi, CryptoWorkerFactory } from "../crypto/workers/cryptoApi.js"; -import { WalletStoresV1 } from "../db.js"; -import { PendingOperationsResponse } from "../pending-types.js"; -import { AsyncOpMemoMap, AsyncOpMemoSingle } from "../util/asyncMemo.js"; -import { HttpRequestLibrary } from "../util/http"; -import { - AsyncCondition, - OpenedPromise, - openPromise, -} from "../util/promiseUtils.js"; -import { DbAccess } from "../util/query.js"; -import { TimerGroup } from "../util/timer.js"; - -type NotificationListener = (n: WalletNotification) => void; - -const logger = new Logger("state.ts"); - -export const EXCHANGE_COINS_LOCK = "exchange-coins-lock"; -export const EXCHANGE_RESERVES_LOCK = "exchange-reserves-lock"; - -/** - * Internal state of the wallet. - */ -export class InternalWalletState { - memoProcessReserve: AsyncOpMemoMap = new AsyncOpMemoMap(); - memoMakePlanchet: AsyncOpMemoMap = new AsyncOpMemoMap(); - memoGetPending: AsyncOpMemoSingle = new AsyncOpMemoSingle(); - memoGetBalance: AsyncOpMemoSingle = new AsyncOpMemoSingle(); - memoProcessRefresh: AsyncOpMemoMap = new AsyncOpMemoMap(); - memoProcessRecoup: AsyncOpMemoMap = new AsyncOpMemoMap(); - memoProcessDeposit: AsyncOpMemoMap = new AsyncOpMemoMap(); - cryptoApi: CryptoApi; - - timerGroup: TimerGroup = new TimerGroup(); - latch = new AsyncCondition(); - stopped = false; - memoRunRetryLoop = new AsyncOpMemoSingle(); - - listeners: NotificationListener[] = []; - - initCalled: boolean = false; - - /** - * Promises that are waiting for a particular resource. - */ - private resourceWaiters: Record[]> = {}; - - /** - * Resources that are currently locked. - */ - private resourceLocks: Set = new Set(); - - constructor( - // FIXME: Make this a getter and make - // the actual value nullable. - // Check if we are in a DB migration / garbage collection - // and throw an error in that case. - public db: DbAccess, - public http: HttpRequestLibrary, - cryptoWorkerFactory: CryptoWorkerFactory, - ) { - this.cryptoApi = new CryptoApi(cryptoWorkerFactory); - } - - notify(n: WalletNotification): void { - logger.trace("Notification", n); - for (const l of this.listeners) { - const nc = JSON.parse(JSON.stringify(n)); - setTimeout(() => { - l(nc); - }, 0); - } - } - - addNotificationListener(f: (n: WalletNotification) => void): void { - this.listeners.push(f); - } - - /** - * Stop ongoing processing. - */ - stop(): void { - this.stopped = true; - this.timerGroup.stopCurrentAndFutureTimers(); - this.cryptoApi.stop(); - } - - /** - * Run an async function after acquiring a list of locks, identified - * by string tokens. - */ - async runSequentialized(tokens: string[], f: () => Promise) { - // Make sure locks are always acquired in the same order - tokens = [...tokens].sort(); - - for (const token of tokens) { - if (this.resourceLocks.has(token)) { - const p = openPromise(); - let waitList = this.resourceWaiters[token]; - if (!waitList) { - waitList = this.resourceWaiters[token] = []; - } - waitList.push(p); - await p.promise; - } - this.resourceLocks.add(token); - } - - try { - logger.trace(`begin exclusive execution on ${JSON.stringify(tokens)}`); - const result = await f(); - logger.trace(`end exclusive execution on ${JSON.stringify(tokens)}`); - return result; - } finally { - for (const token of tokens) { - this.resourceLocks.delete(token); - let waiter = (this.resourceWaiters[token] ?? []).shift(); - if (waiter) { - waiter.resolve(); - } - } - } - } -} diff --git a/packages/taler-wallet-core/src/operations/testing.ts b/packages/taler-wallet-core/src/operations/testing.ts index ce3a47f36..8a0c20200 100644 --- a/packages/taler-wallet-core/src/operations/testing.ts +++ b/packages/taler-wallet-core/src/operations/testing.ts @@ -34,7 +34,7 @@ import { PreparePayResultType, } from "@gnu-taler/taler-util"; import { createTalerWithdrawReserve } from "./reserves.js"; -import { InternalWalletState } from "./state.js"; +import { InternalWalletState } from "../common.js"; import { URL } from "../util/url.js"; import { confirmPay, preparePayForUri } from "./pay.js"; import { getBalances } from "./balance.js"; diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts index d82331632..1da2c887a 100644 --- a/packages/taler-wallet-core/src/operations/tip.ts +++ b/packages/taler-wallet-core/src/operations/tip.ts @@ -40,9 +40,9 @@ import { import { j2s } from "@gnu-taler/taler-util"; import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js"; import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; -import { guardOperationException, makeErrorDetails } from "./errors.js"; +import { guardOperationException, makeErrorDetails } from "../errors.js"; import { updateExchangeFromUrl } from "./exchanges.js"; -import { InternalWalletState } from "./state.js"; +import { InternalWalletState } from "../common.js"; import { getExchangeWithdrawalInfo, updateWithdrawalDenoms, diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index 5836a6ee3..82cfec551 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -17,7 +17,7 @@ /** * Imports. */ -import { InternalWalletState } from "./state.js"; +import { InternalWalletState } from "../common.js"; import { WalletRefundItem, RefundState, diff --git a/packages/taler-wallet-core/src/operations/versions.ts b/packages/taler-wallet-core/src/operations/versions.ts deleted file mode 100644 index b798871c2..000000000 --- a/packages/taler-wallet-core/src/operations/versions.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2019 Taler Systems S.A. - - 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 - */ - -/** - * Protocol version spoken with the exchange. - * - * Uses libtool's current:revision:age versioning. - */ -export const WALLET_EXCHANGE_PROTOCOL_VERSION = "9:0:0"; - -/** - * Protocol version spoken with the merchant. - * - * Uses libtool's current:revision:age versioning. - */ -export const WALLET_MERCHANT_PROTOCOL_VERSION = "1:0:0"; - -/** - * Protocol version spoken with the merchant. - * - * Uses libtool's current:revision:age versioning. - */ -export const WALLET_BANK_INTEGRATION_PROTOCOL_VERSION = "0:0:0"; - -/** - * Cache breaker that is appended to queries such as /keys and /wire - * to break through caching, if it has been accidentally/badly configured - * by the exchange. - * - * This is only a temporary measure. - */ -export const WALLET_CACHE_BREAKER_CLIENT_VERSION = "3"; diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index 3400238ed..6cf20259c 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -17,65 +17,56 @@ /** * Imports. */ +import * as LibtoolVersion from "@gnu-taler/taler-util"; import { AmountJson, Amounts, + BankWithdrawDetails, + codecForTalerConfigResponse, + codecForWithdrawOperationStatusResponse, + codecForWithdrawResponse, + compare, durationFromSpec, + ExchangeListItem, + getDurationRemaining, + getTimestampNow, + Logger, + NotificationType, parseWithdrawUri, + TalerErrorCode, + TalerErrorDetails, Timestamp, + timestampCmp, + timestampSubtractDuraction, + WithdrawResponse, + WithdrawUriInfoResponse, } from "@gnu-taler/taler-util"; import { - DenominationRecord, - DenominationStatus, - CoinStatus, CoinRecord, CoinSourceType, + CoinStatus, + DenominationRecord, DenominationSelectionInfo, - PlanchetRecord, + DenominationStatus, DenomSelectionState, - ExchangeRecord, ExchangeDetailsRecord, + ExchangeRecord, + PlanchetRecord, } from "../db.js"; -import { - BankWithdrawDetails, - TalerErrorDetails, - ExchangeListItem, - WithdrawUriInfoResponse, -} from "@gnu-taler/taler-util"; -import { - codecForWithdrawOperationStatusResponse, - codecForWithdrawResponse, - WithdrawResponse, - codecForTalerConfigResponse, -} from "@gnu-taler/taler-util"; -import { InternalWalletState } from "./state.js"; -import { Logger } from "@gnu-taler/taler-util"; -import { getExchangeDetails, updateExchangeFromUrl } from "./exchanges.js"; -import { - WALLET_EXCHANGE_PROTOCOL_VERSION, - WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, -} from "./versions.js"; - -import * as LibtoolVersion from "@gnu-taler/taler-util"; +import { walletCoreDebugFlags } from "../util/debugFlags.js"; +import { readSuccessResponseJsonOrThrow } from "../util/http.js"; +import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; +import { URL } from "../util/url.js"; import { guardOperationException, makeErrorDetails, OperationFailedError, -} from "./errors.js"; -import { NotificationType } from "@gnu-taler/taler-util"; +} from "../errors.js"; +import { InternalWalletState } from "../common.js"; import { - getTimestampNow, - getDurationRemaining, - timestampCmp, - timestampSubtractDuraction, -} from "@gnu-taler/taler-util"; -import { readSuccessResponseJsonOrThrow } from "../util/http.js"; -import { URL } from "../util/url.js"; -import { TalerErrorCode } from "@gnu-taler/taler-util"; -import { updateRetryInfoTimeout, initRetryInfo } from "../util/retries.js"; -import { compare } from "@gnu-taler/taler-util"; -import { walletCoreDebugFlags } from "../util/debugFlags.js"; -import { getExchangeTrust } from "./currencies.js"; + WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, + WALLET_EXCHANGE_PROTOCOL_VERSION, +} from "../versions.js"; /** * Logger for this file. @@ -690,7 +681,7 @@ export async function updateWithdrawalDenoms( exchangeDetails: x.exchangeDetails, })) .runReadOnly(async (tx) => { - return getExchangeDetails(tx, exchangeBaseUrl); + return ws.exchangeOps.getExchangeDetails(tx, exchangeBaseUrl); }); if (!exchangeDetails) { logger.error("exchange details not available"); @@ -816,7 +807,10 @@ async function processWithdrawGroupImpl( return; } - await updateExchangeFromUrl(ws, withdrawalGroup.exchangeBaseUrl); + await ws.exchangeOps.updateExchangeFromUrl( + ws, + withdrawalGroup.exchangeBaseUrl, + ); const numTotalCoins = withdrawalGroup.denomsSel.selectedDenoms .map((x) => x.count) @@ -910,10 +904,10 @@ export async function getExchangeWithdrawalInfo( baseUrl: string, amount: AmountJson, ): Promise { - const { exchange, exchangeDetails } = await updateExchangeFromUrl( - ws, - baseUrl, - ); + const { + exchange, + exchangeDetails, + } = await ws.exchangeOps.updateExchangeFromUrl(ws, baseUrl); await updateWithdrawalDenoms(ws, baseUrl); const denoms = await getCandidateWithdrawalDenoms(ws, baseUrl); const selectedDenoms = selectWithdrawalDenominations(amount, denoms); @@ -922,7 +916,10 @@ export async function getExchangeWithdrawalInfo( exchangeWireAccounts.push(account.payto_uri); } - const { isTrusted, isAudited } = await getExchangeTrust(ws, exchange); + const { isTrusted, isAudited } = await ws.exchangeOps.getExchangeTrust( + ws, + exchange, + ); let earliestDepositExpiration = selectedDenoms.selectedDenoms[0].denom.stampExpireDeposit; @@ -1009,7 +1006,7 @@ export async function getWithdrawalDetailsForUri( // FIXME: right now the exchange gets permanently added, // we might want to only temporarily add it. try { - await updateExchangeFromUrl(ws, info.suggestedExchange); + await ws.exchangeOps.updateExchangeFromUrl(ws, info.suggestedExchange); } catch (e) { // We still continued if it failed, as other exchanges might be available. // We don't want to fail if the bank-suggested exchange is broken/offline. @@ -1029,7 +1026,7 @@ export async function getWithdrawalDetailsForUri( .runReadOnly(async (tx) => { const exchangeRecords = await tx.exchanges.iter().toArray(); for (const r of exchangeRecords) { - const details = await getExchangeDetails(tx, r.baseUrl); + const details = await ws.exchangeOps.getExchangeDetails(tx, r.baseUrl); if (details) { exchanges.push({ exchangeBaseUrl: details.exchangeBaseUrl, diff --git a/packages/taler-wallet-core/src/pending-types.ts b/packages/taler-wallet-core/src/pending-types.ts index 02b4ec76d..0e26c262b 100644 --- a/packages/taler-wallet-core/src/pending-types.ts +++ b/packages/taler-wallet-core/src/pending-types.ts @@ -27,7 +27,6 @@ import { TalerErrorDetails, BalancesResponse, - Duration, Timestamp, } from "@gnu-taler/taler-util"; import { ReserveRecordStatus } from "./db.js"; diff --git a/packages/taler-wallet-core/src/util/http.ts b/packages/taler-wallet-core/src/util/http.ts index 92a9e4396..68a63e124 100644 --- a/packages/taler-wallet-core/src/util/http.ts +++ b/packages/taler-wallet-core/src/util/http.ts @@ -27,7 +27,7 @@ import { OperationFailedError, makeErrorDetails, -} from "../operations/errors.js"; +} from "../errors.js"; import { Logger, Duration, diff --git a/packages/taler-wallet-core/src/versions.ts b/packages/taler-wallet-core/src/versions.ts new file mode 100644 index 000000000..b798871c2 --- /dev/null +++ b/packages/taler-wallet-core/src/versions.ts @@ -0,0 +1,45 @@ +/* + This file is part of GNU Taler + (C) 2019 Taler Systems S.A. + + 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 + */ + +/** + * Protocol version spoken with the exchange. + * + * Uses libtool's current:revision:age versioning. + */ +export const WALLET_EXCHANGE_PROTOCOL_VERSION = "9:0:0"; + +/** + * Protocol version spoken with the merchant. + * + * Uses libtool's current:revision:age versioning. + */ +export const WALLET_MERCHANT_PROTOCOL_VERSION = "1:0:0"; + +/** + * Protocol version spoken with the merchant. + * + * Uses libtool's current:revision:age versioning. + */ +export const WALLET_BANK_INTEGRATION_PROTOCOL_VERSION = "0:0:0"; + +/** + * Cache breaker that is appended to queries such as /keys and /wire + * to break through caching, if it has been accidentally/badly configured + * by the exchange. + * + * This is only a temporary measure. + */ +export const WALLET_CACHE_BREAKER_CLIENT_VERSION = "3"; diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 4c1715118..a392bf4d7 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -55,7 +55,7 @@ import { makeErrorDetails, OperationFailedAndReportedError, OperationFailedError, -} from "./operations/errors"; +} from "./errors"; import { acceptExchangeTermsOfService, getExchangeDetails, @@ -85,7 +85,7 @@ import { getFundingPaytoUris, processReserve, } from "./operations/reserves"; -import { InternalWalletState } from "./operations/state"; +import { InternalWalletState } from "./common"; import { runIntegrationTest, testPay, -- cgit v1.2.3