summaryrefslogtreecommitdiff
path: root/packages
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2024-04-08 21:27:33 +0200
committerFlorian Dold <florian@dold.me>2024-04-08 21:27:33 +0200
commit30dea85f1e2410f974f8c16e4e53d4ba1290442d (patch)
tree735a68e1ccdd559adc220b800caea565c0cc7fbe /packages
parent85b6213333b211bbfae9209630d8446108c4ce56 (diff)
downloadwallet-core-30dea85f1e2410f974f8c16e4e53d4ba1290442d.tar.gz
wallet-core-30dea85f1e2410f974f8c16e4e53d4ba1290442d.tar.bz2
wallet-core-30dea85f1e2410f974f8c16e4e53d4ba1290442d.zip
wallet-core: perf, caching
Diffstat (limited to 'packages')
-rw-r--r--packages/taler-util/src/wallet-types.ts12
-rw-r--r--packages/taler-wallet-core/src/denomSelection.ts41
-rw-r--r--packages/taler-wallet-core/src/exchanges.ts18
-rw-r--r--packages/taler-wallet-core/src/pay-peer-common.ts1
-rw-r--r--packages/taler-wallet-core/src/refresh.ts15
-rw-r--r--packages/taler-wallet-core/src/wallet-api-types.ts8
-rw-r--r--packages/taler-wallet-core/src/wallet.ts81
-rw-r--r--packages/taler-wallet-core/src/withdraw.ts77
8 files changed, 173 insertions, 80 deletions
diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts
index 693aa704a..34d91d3d6 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -1588,6 +1588,8 @@ export interface DenomSelectionState {
totalCoinValue: AmountString;
totalWithdrawCost: AmountString;
selectedDenoms: DenomSelItem[];
+ earliestDepositExpiration: TalerProtocolTimestamp;
+ hasDenomWithAgeRestriction: boolean;
}
/**
@@ -1621,16 +1623,6 @@ export interface ExchangeWithdrawalDetails {
earliestDepositExpiration: TalerProtocolTimestamp;
/**
- * Number of currently offered denominations.
- */
- numOfferedDenoms: number;
-
- /**
- * Public keys of trusted auditors for the currency we're withdrawing.
- */
- trustedAuditorPubs: string[];
-
- /**
* Result of checking the wallet's version
* against the exchange's version.
*
diff --git a/packages/taler-wallet-core/src/denomSelection.ts b/packages/taler-wallet-core/src/denomSelection.ts
index dd5ec60d8..ecc1fa881 100644
--- a/packages/taler-wallet-core/src/denomSelection.ts
+++ b/packages/taler-wallet-core/src/denomSelection.ts
@@ -24,13 +24,14 @@
* Imports.
*/
import {
+ AbsoluteTime,
AmountJson,
Amounts,
DenomSelectionState,
ForcedDenomSel,
Logger,
} from "@gnu-taler/taler-util";
-import { DenominationRecord } from "./db.js";
+import { DenominationRecord, timestampAbsoluteFromDb } from "./db.js";
import { isWithdrawableDenom } from "./denominations.js";
const logger = new Logger("denomSelection.ts");
@@ -54,6 +55,8 @@ export function selectWithdrawalDenominations(
let totalCoinValue = Amounts.zeroOfCurrency(amountAvailable.currency);
let totalWithdrawCost = Amounts.zeroOfCurrency(amountAvailable.currency);
+ let earliestDepositExpiration: AbsoluteTime | undefined;
+ let hasDenomWithAgeRestriction = false;
denoms = denoms.filter((d) => isWithdrawableDenom(d, denomselAllowLate));
denoms.sort((d1, d2) => Amounts.cmp(d2.value, d1.value));
@@ -82,6 +85,17 @@ export function selectWithdrawalDenominations(
count,
denomPubHash: d.denomPubHash,
});
+ hasDenomWithAgeRestriction =
+ hasDenomWithAgeRestriction || d.denomPub.age_mask > 0;
+ const expireDeposit = timestampAbsoluteFromDb(d.stampExpireDeposit);
+ if (!earliestDepositExpiration) {
+ earliestDepositExpiration = expireDeposit;
+ } else {
+ earliestDepositExpiration = AbsoluteTime.min(
+ expireDeposit,
+ earliestDepositExpiration,
+ );
+ }
}
if (logger.shouldLogTrace()) {
@@ -103,10 +117,16 @@ export function selectWithdrawalDenominations(
logger.trace("(end of denom selection)");
}
+ earliestDepositExpiration ??= AbsoluteTime.never();
+
return {
selectedDenoms,
totalCoinValue: Amounts.stringify(totalCoinValue),
totalWithdrawCost: Amounts.stringify(totalWithdrawCost),
+ earliestDepositExpiration: AbsoluteTime.toProtocolTimestamp(
+ earliestDepositExpiration,
+ ),
+ hasDenomWithAgeRestriction,
};
}
@@ -123,6 +143,8 @@ export function selectForcedWithdrawalDenominations(
let totalCoinValue = Amounts.zeroOfCurrency(amountAvailable.currency);
let totalWithdrawCost = Amounts.zeroOfCurrency(amountAvailable.currency);
+ let earliestDepositExpiration: AbsoluteTime | undefined;
+ let hasDenomWithAgeRestriction = false;
denoms = denoms.filter((d) => isWithdrawableDenom(d, denomselAllowLate));
denoms.sort((d1, d2) => Amounts.cmp(d2.value, d1.value));
@@ -150,11 +172,28 @@ export function selectForcedWithdrawalDenominations(
count,
denomPubHash: denom.denomPubHash,
});
+ hasDenomWithAgeRestriction =
+ hasDenomWithAgeRestriction || denom.denomPub.age_mask > 0;
+ const expireDeposit = timestampAbsoluteFromDb(denom.stampExpireDeposit);
+ if (!earliestDepositExpiration) {
+ earliestDepositExpiration = expireDeposit;
+ } else {
+ earliestDepositExpiration = AbsoluteTime.min(
+ expireDeposit,
+ earliestDepositExpiration,
+ );
+ }
}
+ earliestDepositExpiration ??= AbsoluteTime.never();
+
return {
selectedDenoms,
totalCoinValue: Amounts.stringify(totalCoinValue),
totalWithdrawCost: Amounts.stringify(totalWithdrawCost),
+ earliestDepositExpiration: AbsoluteTime.toProtocolTimestamp(
+ earliestDepositExpiration,
+ ),
+ hasDenomWithAgeRestriction,
};
}
diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts
index ca8e48c06..6ee579368 100644
--- a/packages/taler-wallet-core/src/exchanges.ts
+++ b/packages/taler-wallet-core/src/exchanges.ts
@@ -1154,13 +1154,24 @@ export async function fetchFreshExchange(
): Promise<ReadyExchangeSummary> {
const canonUrl = canonicalizeBaseUrl(baseUrl);
+ if (!options.forceUpdate) {
+ const cachedResp = wex.ws.exchangeCache.get(canonUrl);
+ if (cachedResp) {
+ return cachedResp;
+ }
+ } else {
+ wex.ws.exchangeCache.clear();
+ }
+
wex.taskScheduler.ensureRunning();
await startUpdateExchangeEntry(wex, canonUrl, {
forceUpdate: options.forceUpdate,
});
- return await waitReadyExchange(wex, canonUrl, options);
+ const resp = await waitReadyExchange(wex, canonUrl, options);
+ wex.ws.exchangeCache.put(canonUrl, resp);
+ return resp;
}
async function waitReadyExchange(
@@ -1453,6 +1464,11 @@ export async function updateExchangeFromUrlHandler(
logger.warn(`exchange ${exchangeBaseUrl} no longer present`);
return;
}
+
+ wex.ws.refreshCostCache.clear();
+ wex.ws.exchangeCache.clear();
+ wex.ws.denomInfoCache.clear();
+
const oldExchangeState = getExchangeState(r);
const existingDetails = await getExchangeRecordsInternal(tx, r.baseUrl);
let detailsPointerChanged = false;
diff --git a/packages/taler-wallet-core/src/pay-peer-common.ts b/packages/taler-wallet-core/src/pay-peer-common.ts
index 05091c5cf..0bb290440 100644
--- a/packages/taler-wallet-core/src/pay-peer-common.ts
+++ b/packages/taler-wallet-core/src/pay-peer-common.ts
@@ -75,7 +75,6 @@ export async function getTotalPeerPaymentCost(
wex: WalletExecutionContext,
pcs: SelectedProspectiveCoin[],
): Promise<AmountJson> {
- const currency = Amounts.currencyOf(pcs[0].contribution);
return wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => {
const costs: AmountJson[] = [];
for (let i = 0; i < pcs.length; i++) {
diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts
index 7c5f75625..99ac5737b 100644
--- a/packages/taler-wallet-core/src/refresh.ts
+++ b/packages/taler-wallet-core/src/refresh.ts
@@ -315,13 +315,26 @@ export async function getTotalRefreshCost(
refreshedDenom: DenominationInfo,
amountLeft: AmountJson,
): Promise<AmountJson> {
+ const cacheKey = `denom=${refreshedDenom.exchangeBaseUrl}/${
+ refreshedDenom.denomPubHash
+ };left=${Amounts.stringify(amountLeft)}`;
+ const cacheRes = wex.ws.refreshCostCache.get(cacheKey);
+ if (cacheRes) {
+ return cacheRes;
+ }
const allDenoms = await getCandidateWithdrawalDenomsTx(
wex,
tx,
refreshedDenom.exchangeBaseUrl,
Amounts.currencyOf(amountLeft),
);
- return getTotalRefreshCostInternal(allDenoms, refreshedDenom, amountLeft);
+ const res = getTotalRefreshCostInternal(
+ allDenoms,
+ refreshedDenom,
+ amountLeft,
+ );
+ wex.ws.refreshCostCache.put(cacheKey, res);
+ return res;
}
/**
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts
index d7f34409f..15803ce8d 100644
--- a/packages/taler-wallet-core/src/wallet-api-types.ts
+++ b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -259,6 +259,7 @@ export enum WalletApiOperation {
ListAssociatedRefreshes = "listAssociatedRefreshes",
TestingListTaskForTransaction = "testingListTasksForTransaction",
TestingGetDenomStats = "testingGetDenomStats",
+ TestingPing = "testingPing",
}
// group: Initialization
@@ -1129,6 +1130,12 @@ export type TestingWaitTransactionStateOp = {
response: EmptyObject;
};
+export type TestingPingOp = {
+ op: WalletApiOperation.TestingPing;
+ request: EmptyObject;
+ response: EmptyObject;
+};
+
/**
* Get stats about an exchange denomination.
*/
@@ -1265,6 +1272,7 @@ export type WalletOperations = {
[WalletApiOperation.ListAssociatedRefreshes]: ListAssociatedRefreshesOp;
[WalletApiOperation.TestingListTaskForTransaction]: TestingListTasksForTransactionOp;
[WalletApiOperation.TestingGetDenomStats]: TestingGetDenomStatsOp;
+ [WalletApiOperation.TestingPing]: TestingPingOp;
};
export type WalletCoreRequestType<
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index 2bafba3af..223272745 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -24,7 +24,9 @@
*/
import { IDBFactory } from "@gnu-taler/idb-bridge";
import {
+ AbsoluteTime,
ActiveTask,
+ AmountJson,
AmountString,
Amounts,
AsyncCondition,
@@ -35,6 +37,7 @@ import {
CreateStoredBackupResponse,
DeleteStoredBackupRequest,
DenominationInfo,
+ Duration,
ExchangesShortListResponse,
GetCurrencySpecificationResponse,
InitResponse,
@@ -193,6 +196,7 @@ import {
} from "./deposits.js";
import { DevExperimentHttpLib, applyDevExperiment } from "./dev-experiments.js";
import {
+ ReadyExchangeSummary,
acceptExchangeTermsOfService,
addPresetExchangeEntry,
deleteExchange,
@@ -356,15 +360,15 @@ export async function getDenomInfo(
exchangeBaseUrl: string,
denomPubHash: string,
): Promise<DenominationInfo | undefined> {
- const key = `${exchangeBaseUrl}:${denomPubHash}`;
- const cached = wex.ws.lookupDenomCache(key);
+ const cacheKey = `${exchangeBaseUrl}:${denomPubHash}`;
+ const cached = wex.ws.denomInfoCache.get(cacheKey);
if (cached) {
return cached;
}
const d = await tx.denominations.get([exchangeBaseUrl, denomPubHash]);
if (d) {
const denomInfo = DenominationRecord.toDenomInfo(d);
- wex.ws.putDenomCache(key, denomInfo);
+ wex.ws.denomInfoCache.put(cacheKey, denomInfo);
return denomInfo;
}
return undefined;
@@ -794,6 +798,9 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
});
return {};
}
+ case WalletApiOperation.TestingPing: {
+ return {};
+ }
case WalletApiOperation.UpdateExchangeEntry: {
const req = codecForUpdateExchangeEntryRequest().decode(payload);
await fetchFreshExchange(wex, req.exchangeBaseUrl, {
@@ -1275,6 +1282,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
if (existingRec) {
return;
}
+ wex.ws.exchangeCache.clear();
await tx.globalCurrencyExchanges.add({
currency: req.currency,
exchangeBaseUrl: req.exchangeBaseUrl,
@@ -1294,6 +1302,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
if (!existingRec) {
return;
}
+ wex.ws.exchangeCache.clear();
checkDbInvariant(!!existingRec.id);
await tx.globalCurrencyExchanges.delete(existingRec.id);
});
@@ -1315,6 +1324,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
auditorBaseUrl: req.auditorBaseUrl,
auditorPub: req.auditorPub,
});
+ wex.ws.exchangeCache.clear();
});
return {};
}
@@ -1331,6 +1341,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
}
checkDbInvariant(!!existingRec.id);
await tx.globalCurrencyAuditors.delete(existingRec.id);
+ wex.ws.exchangeCache.clear();
});
return {};
}
@@ -1664,6 +1675,44 @@ export interface DevExperimentState {
blockRefreshes?: boolean;
}
+export class Cache<T> {
+ private map: Map<string, [AbsoluteTime, T]> = new Map();
+
+ constructor(
+ private maxCapacity: number,
+ private cacheDuration: Duration,
+ ) {}
+
+ get(key: string): T | undefined {
+ const r = this.map.get(key);
+ if (!r) {
+ return undefined;
+ }
+
+ if (AbsoluteTime.isExpired(r[0])) {
+ this.map.delete(key);
+ return undefined;
+ }
+
+ return r[1];
+ }
+
+ clear(): void {
+ this.map.clear();
+ }
+
+ put(key: string, value: T): void {
+ if (this.map.size > this.maxCapacity) {
+ this.map.clear();
+ }
+ const expiry = AbsoluteTime.addDuration(
+ AbsoluteTime.now(),
+ this.cacheDuration,
+ );
+ this.map.set(key, [expiry, value]);
+ }
+}
+
/**
* Internal state of the wallet.
*
@@ -1681,7 +1730,20 @@ export class InternalWalletState {
initCalled = false;
- private denomCache: Map<string, DenominationInfo> = new Map();
+ refreshCostCache: Cache<AmountJson> = new Cache(
+ 1000,
+ Duration.fromSpec({ minutes: 1 }),
+ );
+
+ denomInfoCache: Cache<DenominationInfo> = new Cache(
+ 1000,
+ Duration.fromSpec({ minutes: 1 }),
+ );
+
+ exchangeCache: Cache<ReadyExchangeSummary> = new Cache(
+ 1000,
+ Duration.fromSpec({ minutes: 1 }),
+ );
/**
* Promises that are waiting for a particular resource.
@@ -1710,17 +1772,6 @@ export class InternalWalletState {
devExperimentState: DevExperimentState = {};
- lookupDenomCache(denomCacheKey: string): DenominationInfo | undefined {
- return this.denomCache.get(denomCacheKey);
- }
-
- putDenomCache(denomCacheKey: string, denomInfo: DenominationInfo): void {
- if (this.denomCache.size > 1000) {
- this.denomCache.clear();
- }
- this.denomCache.set(denomCacheKey, denomInfo);
- }
-
initWithConfig(newConfig: WalletRunConfig): void {
this._config = newConfig;
diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts
index 8767c1eca..960ffa525 100644
--- a/packages/taler-wallet-core/src/withdraw.ts
+++ b/packages/taler-wallet-core/src/withdraw.ts
@@ -58,7 +58,6 @@ import {
TalerErrorCode,
TalerErrorDetail,
TalerPreciseTimestamp,
- TalerProtocolTimestamp,
Transaction,
TransactionAction,
TransactionIdStr,
@@ -76,7 +75,6 @@ import {
assertUnreachable,
canonicalizeBaseUrl,
checkDbInvariant,
- checkLogicInvariant,
codeForBankWithdrawalOperationPostResponse,
codecForCashinConversionResponse,
codecForConversionBankConfig,
@@ -129,6 +127,7 @@ import {
WithdrawalGroupRecord,
WithdrawalGroupStatus,
WithdrawalRecordType,
+ timestampAbsoluteFromDb,
timestampPreciseFromDb,
timestampPreciseToDb,
} from "./db.js";
@@ -1274,6 +1273,7 @@ export async function updateWithdrawalDenoms(
wex: WalletExecutionContext,
exchangeBaseUrl: string,
): Promise<void> {
+
logger.trace(
`updating denominations used for withdrawal for ${exchangeBaseUrl}`,
);
@@ -1347,6 +1347,7 @@ export async function updateWithdrawalDenoms(
await tx.denominations.put(denom);
}
});
+ wex.ws.denomInfoCache.clear();
logger.trace("done with DB write");
}
}
@@ -1583,6 +1584,8 @@ async function redenominateWithdrawal(
let amountRemaining = zero;
let prevTotalCoinValue = zero;
let prevTotalWithdrawalCost = zero;
+ let prevHasDenomWithAgeRestriction = false;
+ let prevEarliestDepositExpiration = AbsoluteTime.never();
let prevDenoms: DenomSelItem[] = [];
let coinIndex = 0;
for (let i = 0; i < oldSel.selectedDenoms.length; i++) {
@@ -1615,6 +1618,12 @@ async function redenominateWithdrawal(
denomPubHash: sel.denomPubHash,
skip: sel.skip,
});
+ prevHasDenomWithAgeRestriction =
+ prevHasDenomWithAgeRestriction || denom.denomPub.age_mask > 0;
+ prevEarliestDepositExpiration = AbsoluteTime.min(
+ prevEarliestDepositExpiration,
+ timestampAbsoluteFromDb(denom.stampExpireDeposit),
+ );
} else {
amountRemaining = amountRemaining.add(denomValue, denomFeeWithdraw);
prevDenoms.push({
@@ -1663,6 +1672,16 @@ async function redenominateWithdrawal(
totalWithdrawCost: zero
.add(prevTotalWithdrawalCost, newSel.totalWithdrawCost)
.toString(),
+ hasDenomWithAgeRestriction:
+ prevHasDenomWithAgeRestriction || newSel.hasDenomWithAgeRestriction,
+ earliestDepositExpiration: AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.min(
+ prevEarliestDepositExpiration,
+ AbsoluteTime.fromProtocolTimestamp(
+ newSel.earliestDepositExpiration,
+ ),
+ ),
+ ),
};
wg.denomsSel = mergedSel;
if (logger.shouldLogTrace()) {
@@ -1935,15 +1954,16 @@ export async function getExchangeWithdrawalInfo(
await updateWithdrawalDenoms(wex, exchangeBaseUrl);
logger.trace("getting candidate denoms");
- const denoms = await getCandidateWithdrawalDenoms(
+ const candidateDenoms = await getCandidateWithdrawalDenoms(
wex,
exchangeBaseUrl,
instructedAmount.currency,
);
logger.trace("selecting withdrawal denoms");
+ // FIXME: Why not in a transaction?
const selectedDenoms = selectWithdrawalDenominations(
instructedAmount,
- denoms,
+ candidateDenoms,
wex.ws.config.testing.denomselAllowLate,
);
@@ -1963,48 +1983,6 @@ export async function getExchangeWithdrawalInfo(
exchangeWireAccounts.push(account.payto_uri);
}
- let hasDenomWithAgeRestriction = false;
-
- logger.trace("computing earliest deposit expiration");
-
- let earliestDepositExpiration: TalerProtocolTimestamp | undefined;
-
- await wex.db.runReadOnlyTx(["denominations"], async (tx) => {
- for (let i = 0; i < selectedDenoms.selectedDenoms.length; i++) {
- const ds = selectedDenoms.selectedDenoms[i];
- const denom = await getDenomInfo(
- wex,
- tx,
- exchangeBaseUrl,
- ds.denomPubHash,
- );
- checkDbInvariant(!!denom);
- hasDenomWithAgeRestriction =
- hasDenomWithAgeRestriction || denom.denomPub.age_mask > 0;
- const expireDeposit = denom.stampExpireDeposit;
- if (!earliestDepositExpiration) {
- earliestDepositExpiration = expireDeposit;
- continue;
- }
- if (
- AbsoluteTime.cmp(
- AbsoluteTime.fromProtocolTimestamp(expireDeposit),
- AbsoluteTime.fromProtocolTimestamp(earliestDepositExpiration),
- ) < 0
- ) {
- earliestDepositExpiration = expireDeposit;
- }
- }
- });
-
- checkLogicInvariant(!!earliestDepositExpiration);
-
- const possibleDenoms = await getCandidateWithdrawalDenoms(
- wex,
- exchangeBaseUrl,
- instructedAmount.currency,
- );
-
let versionMatch;
if (exchange.protocolVersionRange) {
versionMatch = LibtoolVersion.compare(
@@ -2037,15 +2015,12 @@ export async function getExchangeWithdrawalInfo(
}
const ret: ExchangeWithdrawalDetails = {
- earliestDepositExpiration,
+ earliestDepositExpiration: selectedDenoms.earliestDepositExpiration,
exchangePaytoUris: paytoUris,
exchangeWireAccounts,
exchangeCreditAccountDetails: withdrawalAccountsList,
exchangeVersion: exchange.protocolVersionRange || "unknown",
- numOfferedDenoms: possibleDenoms.length,
selectedDenoms,
- // FIXME: delete this field / replace by something we can display to the user
- trustedAuditorPubs: [],
versionMatch,
walletVersion: WALLET_EXCHANGE_PROTOCOL_VERSION,
termsOfServiceAccepted: tosAccepted,
@@ -2053,7 +2028,7 @@ export async function getExchangeWithdrawalInfo(
withdrawalAmountRaw: Amounts.stringify(instructedAmount),
// TODO: remove hardcoding, this should be calculated from the denominations info
// force enabled for testing
- ageRestrictionOptions: hasDenomWithAgeRestriction
+ ageRestrictionOptions: selectedDenoms.hasDenomWithAgeRestriction
? AGE_MASK_GROUPS
: undefined,
scopeInfo: exchange.scopeInfo,