aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2022-01-13 12:08:31 +0100
committerFlorian Dold <florian@dold.me>2022-01-13 12:08:40 +0100
commitcd2473e1ade13ca43d8f6fafaa2e8d3c3675bfd8 (patch)
treeb0766e5716ec5a53072843a2057c1c9eb6810966
parentcea0ac02b64c2a575a5788552e813d315e3f3096 (diff)
cache denomination lookups
-rw-r--r--packages/taler-wallet-core/src/common.ts77
-rw-r--r--packages/taler-wallet-core/src/operations/pending.ts181
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts15
-rw-r--r--packages/taler-wallet-core/src/wallet.ts54
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) {