diff options
author | Florian Dold <florian@dold.me> | 2024-04-08 21:27:33 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2024-04-08 21:27:33 +0200 |
commit | 30dea85f1e2410f974f8c16e4e53d4ba1290442d (patch) | |
tree | 735a68e1ccdd559adc220b800caea565c0cc7fbe /packages | |
parent | 85b6213333b211bbfae9209630d8446108c4ce56 (diff) | |
download | wallet-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.ts | 12 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/denomSelection.ts | 41 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/exchanges.ts | 18 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/pay-peer-common.ts | 1 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/refresh.ts | 15 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/wallet-api-types.ts | 8 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/wallet.ts | 81 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/withdraw.ts | 77 |
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, |