cache denomination lookups

This commit is contained in:
Florian Dold 2022-01-13 12:08:31 +01:00
parent cea0ac02b6
commit cd2473e1ad
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
4 changed files with 210 additions and 117 deletions

View File

@ -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;

View File

@ -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,9 +71,10 @@ async function gatherReservePending(
now: Timestamp,
resp: PendingOperationsResponse,
): Promise<void> {
await tx.reserves.indexes.byStatus
.iter(OperationStatus.Pending)
.forEach((reserve) => {
const reserves = await tx.reserves.indexes.byStatus.getAll(
OperationStatus.Pending,
);
for (const reserve of reserves) {
const reserveType = reserve.bankInfo
? ReserveType.TalerBankWithdraw
: ReserveType.Manual;
@ -102,7 +100,7 @@ async function gatherReservePending(
// FIXME: report problem!
break;
}
});
}
}
async function gatherRefreshPending(
@ -110,9 +108,10 @@ async function gatherRefreshPending(
now: Timestamp,
resp: PendingOperationsResponse,
): Promise<void> {
await tx.refreshGroups.indexes.byStatus
.iter(OperationStatus.Pending)
.forEach((r) => {
const refreshGroups = await tx.refreshGroups.indexes.byStatus.getAll(
OperationStatus.Pending,
);
for (const r of refreshGroups) {
if (r.timestampFinished) {
return;
}
@ -129,7 +128,7 @@ async function gatherRefreshPending(
),
retryInfo: r.retryInfo,
});
});
}
}
async function gatherWithdrawalPending(
@ -140,9 +139,10 @@ async function gatherWithdrawalPending(
now: Timestamp,
resp: PendingOperationsResponse,
): Promise<void> {
await tx.withdrawalGroups.indexes.byStatus
.iter(OperationStatus.Pending)
.forEachAsync(async (wsr) => {
const wsrs = await tx.withdrawalGroups.indexes.byStatus.getAll(
OperationStatus.Pending,
);
for (const wsr of wsrs) {
if (wsr.timestampFinish) {
return;
}
@ -164,7 +164,7 @@ async function gatherWithdrawalPending(
lastError: wsr.lastError,
retryInfo: wsr.retryInfo,
});
});
}
}
async function gatherProposalPending(
@ -197,9 +197,10 @@ async function gatherDepositPending(
now: Timestamp,
resp: PendingOperationsResponse,
): Promise<void> {
await tx.depositGroups.indexes.byStatus
.iter(OperationStatus.Pending)
.forEach((dg) => {
const dgs = await tx.depositGroups.indexes.byStatus.getAll(
OperationStatus.Pending,
);
for (const dg of dgs) {
if (dg.timestampFinished) {
return;
}
@ -212,7 +213,7 @@ async function gatherDepositPending(
lastError: dg.lastError,
retryInfo: dg.retryInfo,
});
});
}
}
async function gatherTipPending(

View File

@ -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;

View File

@ -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,
@ -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) {