diff options
Diffstat (limited to 'packages/taler-wallet-core/src')
-rw-r--r-- | packages/taler-wallet-core/src/common.ts | 77 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/operations/pending.ts | 181 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/operations/withdraw.ts | 15 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/wallet.ts | 54 |
4 files changed, 210 insertions, 117 deletions
diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts index 5b61ef14a..5cbfae4b9 100644 --- a/packages/taler-wallet-core/src/common.ts +++ b/packages/taler-wallet-core/src/common.ts @@ -20,7 +20,7 @@ * management, etc.). * * Some operations can be accessed via this state object. This allows mutual - * recursion between operations, without having cycling dependencies between + * recursion between operations, without having cyclic dependencies between * the respective TypeScript files. * * (You can think of this as a "header file" for the wallet implementation.) @@ -29,7 +29,13 @@ /** * Imports. */ -import { WalletNotification, BalancesResponse } from "@gnu-taler/taler-util"; +import { + WalletNotification, + BalancesResponse, + AmountJson, + DenominationPubKey, + Timestamp, +} from "@gnu-taler/taler-util"; import { CryptoApi } from "./crypto/workers/cryptoApi.js"; import { ExchangeDetailsRecord, ExchangeRecord, WalletStoresV1 } from "./db.js"; import { PendingOperationsResponse } from "./pending-types.js"; @@ -119,6 +125,64 @@ export interface RecoupOperations { ): Promise<void>; } +export interface DenomInfo { + /** + * Value of one coin of the denomination. + */ + value: AmountJson; + + /** + * The denomination public key. + */ + denomPub: DenominationPubKey; + + /** + * Hash of the denomination public key. + * Stored in the database for faster lookups. + */ + denomPubHash: string; + + /** + * Fee for withdrawing. + */ + feeWithdraw: AmountJson; + + /** + * Fee for depositing. + */ + feeDeposit: AmountJson; + + /** + * Fee for refreshing. + */ + feeRefresh: AmountJson; + + /** + * Fee for refunding. + */ + feeRefund: AmountJson; + + /** + * Validity start date of the denomination. + */ + stampStart: Timestamp; + + /** + * Date after which the currency can't be withdrawn anymore. + */ + stampExpireWithdraw: Timestamp; + + /** + * Date after the denomination officially doesn't exist anymore. + */ + stampExpireLegal: Timestamp; + + /** + * Data after which coins of this denomination can't be deposited anymore. + */ + stampExpireDeposit: Timestamp; +} + export type NotificationListener = (n: WalletNotification) => void; /** @@ -162,6 +226,15 @@ export interface InternalWalletState { merchantOps: MerchantOperations; reserveOps: ReserveOperations; + getDenomInfo( + ws: InternalWalletState, + tx: GetReadWriteAccess<{ + denominations: typeof WalletStoresV1.denominations; + }>, + exchangeBaseUrl: string, + denomPubHash: string, + ): Promise<DenomInfo | undefined>; + db: DbAccess<typeof WalletStoresV1>; http: HttpRequestLibrary; diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts index b2f13625a..7e82236f0 100644 --- a/packages/taler-wallet-core/src/operations/pending.ts +++ b/packages/taler-wallet-core/src/operations/pending.ts @@ -35,10 +35,7 @@ import { PendingTaskType, ReserveType, } from "../pending-types.js"; -import { - getTimestampNow, - Timestamp, -} from "@gnu-taler/taler-util"; +import { getTimestampNow, Timestamp } from "@gnu-taler/taler-util"; import { InternalWalletState } from "../common.js"; import { GetReadOnlyAccess } from "../util/query.js"; @@ -74,35 +71,36 @@ async function gatherReservePending( now: Timestamp, resp: PendingOperationsResponse, ): Promise<void> { - 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; - } - }); + const reserves = await tx.reserves.indexes.byStatus.getAll( + OperationStatus.Pending, + ); + for (const reserve of reserves) { + 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; + } + } } async function gatherRefreshPending( @@ -110,26 +108,27 @@ async function gatherRefreshPending( now: Timestamp, resp: PendingOperationsResponse, ): Promise<void> { - 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, - }); + const refreshGroups = await tx.refreshGroups.indexes.byStatus.getAll( + OperationStatus.Pending, + ); + for (const r of refreshGroups) { + 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, }); + } } async function gatherWithdrawalPending( @@ -140,31 +139,32 @@ async function gatherWithdrawalPending( now: Timestamp, resp: PendingOperationsResponse, ): Promise<void> { - 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, + const wsrs = await tx.withdrawalGroups.indexes.byStatus.getAll( + OperationStatus.Pending, + ); + for (const wsr of wsrs) { + 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, }); + } } async function gatherProposalPending( @@ -197,22 +197,23 @@ async function gatherDepositPending( now: Timestamp, resp: PendingOperationsResponse, ): Promise<void> { - 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, - }); + const dgs = await tx.depositGroups.indexes.byStatus.getAll( + OperationStatus.Pending, + ); + for (const dg of dgs) { + 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, }); + } } async function gatherTipPending( diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index dd8a90ad9..f9eeb02c0 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -975,13 +975,13 @@ async function processWithdrawGroupImpl( export async function getExchangeWithdrawalInfo( ws: InternalWalletState, - baseUrl: string, + exchangeBaseUrl: string, amount: AmountJson, ): Promise<ExchangeWithdrawDetails> { const { exchange, exchangeDetails } = - await ws.exchangeOps.updateExchangeFromUrl(ws, baseUrl); - await updateWithdrawalDenoms(ws, baseUrl); - const denoms = await getCandidateWithdrawalDenoms(ws, baseUrl); + await ws.exchangeOps.updateExchangeFromUrl(ws, exchangeBaseUrl); + await updateWithdrawalDenoms(ws, exchangeBaseUrl); + const denoms = await getCandidateWithdrawalDenoms(ws, exchangeBaseUrl); const selectedDenoms = selectWithdrawalDenominations(amount, denoms); const exchangeWireAccounts: string[] = []; for (const account of exchangeDetails.wireInfo.accounts) { @@ -1006,9 +1006,10 @@ export async function getExchangeWithdrawalInfo( const possibleDenoms = await ws.db .mktx((x) => ({ denominations: x.denominations })) .runReadOnly(async (tx) => { - return tx.denominations.indexes.byExchangeBaseUrl - .iter() - .filter((d) => d.isOffered); + const ds = await tx.denominations.indexes.byExchangeBaseUrl.getAll( + exchangeBaseUrl, + ); + return ds.filter((x) => x.isOffered); }); let versionMatch; diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 3d83ec21d..c5eb0e65c 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -103,6 +103,7 @@ import { processReserve, } from "./operations/reserves.js"; import { + DenomInfo, ExchangeOperations, InternalWalletState, MerchantInfo, @@ -186,13 +187,12 @@ import { OpenedPromise, openPromise, } from "./util/promiseUtils.js"; -import { DbAccess } from "./util/query.js"; +import { DbAccess, GetReadWriteAccess } from "./util/query.js"; import { HttpRequestLibrary, readSuccessResponseJsonOrThrow, } from "./util/http.js"; import { getMerchantInfo } from "./operations/merchants.js"; -import { Event, IDBDatabase } from "@gnu-taler/idb-bridge"; const builtinAuditors: AuditorTrustRecord[] = [ { @@ -506,24 +506,24 @@ async function listKnownBankAccounts( ws: InternalWalletState, currency?: string, ): Promise<KnownBankAccounts> { - const accounts: PaytoUri[] = [] + const accounts: PaytoUri[] = []; await ws.db .mktx((x) => ({ reserves: x.reserves, })) .runReadOnly(async (tx) => { - const reservesRecords = await tx.reserves.iter().toArray() + const reservesRecords = await tx.reserves.iter().toArray(); for (const r of reservesRecords) { if (currency && currency !== r.currency) { - continue + continue; } - const payto = r.senderWire ? parsePaytoUri(r.senderWire) : undefined + const payto = r.senderWire ? parsePaytoUri(r.senderWire) : undefined; if (payto) { - accounts.push(payto) + accounts.push(payto); } } - }) - return { accounts } + }); + return { accounts }; } async function getExchanges( @@ -785,9 +785,8 @@ async function dispatchRequestInternal( return res; } case "getWithdrawalDetailsForAmount": { - const req = codecForGetWithdrawalDetailsForAmountRequest().decode( - payload, - ); + const req = + codecForGetWithdrawalDetailsForAmountRequest().decode(payload); return await getWithdrawalDetailsForAmount( ws, req.exchangeBaseUrl, @@ -810,9 +809,8 @@ async function dispatchRequestInternal( return await applyRefund(ws, req.talerRefundUri); } case "acceptBankIntegratedWithdrawal": { - const req = codecForAcceptBankIntegratedWithdrawalRequest().decode( - payload, - ); + const req = + codecForAcceptBankIntegratedWithdrawalRequest().decode(payload); return await acceptWithdrawal( ws, req.talerWithdrawUri, @@ -1048,7 +1046,7 @@ export async function handleCoreApiRequest( try { logger.error("Caught unexpected exception:"); logger.error(e.stack); - } catch (e) { } + } catch (e) {} return { type: "error", operation, @@ -1133,7 +1131,8 @@ export class Wallet { class InternalWalletStateImpl implements InternalWalletState { memoProcessReserve: AsyncOpMemoMap<void> = new AsyncOpMemoMap(); memoMakePlanchet: AsyncOpMemoMap<void> = new AsyncOpMemoMap(); - memoGetPending: AsyncOpMemoSingle<PendingOperationsResponse> = new AsyncOpMemoSingle(); + memoGetPending: AsyncOpMemoSingle<PendingOperationsResponse> = + new AsyncOpMemoSingle(); memoGetBalance: AsyncOpMemoSingle<BalancesResponse> = new AsyncOpMemoSingle(); memoProcessRefresh: AsyncOpMemoMap<void> = new AsyncOpMemoMap(); memoProcessRecoup: AsyncOpMemoMap<void> = new AsyncOpMemoMap(); @@ -1169,7 +1168,10 @@ class InternalWalletStateImpl implements InternalWalletState { reserveOps: ReserveOperations = { processReserve: processReserve, - } + }; + + // FIXME: Use an LRU cache here. + private denomCache: Record<string, DenomInfo> = {}; /** * Promises that are waiting for a particular resource. @@ -1193,6 +1195,22 @@ class InternalWalletStateImpl implements InternalWalletState { this.cryptoApi = new CryptoApi(cryptoWorkerFactory); } + async getDenomInfo( + ws: InternalWalletState, + tx: GetReadWriteAccess<{ + denominations: typeof WalletStoresV1.denominations; + }>, + exchangeBaseUrl: string, + denomPubHash: string, + ): Promise<DenomInfo | undefined> { + const key = `${exchangeBaseUrl}:${denomPubHash}`; + const cached = this.denomCache[key]; + if (cached) { + return cached; + } + return await tx.denominations.get([exchangeBaseUrl, denomPubHash]); + } + notify(n: WalletNotification): void { logger.trace("Notification", n); for (const l of this.listeners) { |