taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 1cdd1641c317933b2c935fd8540e06befd492395
parent 2015d76af1721e81cc00fd6640ec07b94fc65cb6
Author: Florian Dold <florian@dold.me>
Date:   Sun,  4 May 2025 00:33:33 +0200

wallet-core: limit output size of refresh sessions

Diffstat:
Mpackages/taler-wallet-core/src/denomSelection.ts | 8+++++++-
Mpackages/taler-wallet-core/src/refresh.ts | 27+++++++++++++++++----------
2 files changed, 24 insertions(+), 11 deletions(-)

diff --git a/packages/taler-wallet-core/src/denomSelection.ts b/packages/taler-wallet-core/src/denomSelection.ts @@ -44,6 +44,7 @@ const logger = new Logger("denomSelection.ts"); export function selectWithdrawalDenominations( amountAvailable: AmountJson, denoms: DenominationRecord[], + opts: { limitCoins?: number } = {}, ): DenomSelectionState { let remaining = Amounts.copy(amountAvailable); @@ -66,10 +67,15 @@ export function selectWithdrawalDenominations( ); } + let totalCount = 0; + for (const d of denoms) { const cost = Amounts.add(d.value, d.fees.feeWithdraw).amount; const res = Amounts.divmod(remaining, cost); - const count = res.quotient; + let count = res.quotient; + if (opts.limitCoins != null && totalCount + count > opts.limitCoins) { + count = opts.limitCoins - totalCount; + } remaining = Amounts.sub(remaining, Amounts.mult(cost, count).amount).amount; if (count > 0) { totalCoinValue = Amounts.add( diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts @@ -133,6 +133,9 @@ import { } from "./wallet.js"; import { getWithdrawableDenomsTx } from "./withdraw.js"; +/** Maximum number of new coins. */ +const maxRefreshSessionSize = 64; + const logger = new Logger("refresh.ts"); export class RefreshTransactionContext implements TransactionContext { @@ -417,7 +420,9 @@ export async function getTotalRefreshCost( amountLeft: AmountJson, ): Promise<AmountJson> { const { exchangeBaseUrl, denomPubHash } = refreshedDenom; - const key = `denom=${exchangeBaseUrl}/${denomPubHash};left=${Amounts.stringify(amountLeft)}`; + const key = `denom=${exchangeBaseUrl}/${denomPubHash};left=${Amounts.stringify( + amountLeft, + )}`; return wex.ws.refreshCostCache.getOrPut(key, async () => { const allDenoms = await getWithdrawableDenomsTx( wex, @@ -425,12 +430,8 @@ export async function getTotalRefreshCost( exchangeBaseUrl, Amounts.currencyOf(amountLeft), ); - return getTotalRefreshCostInternal( - allDenoms, - refreshedDenom, - amountLeft, - ); - }) + return getTotalRefreshCostInternal(allDenoms, refreshedDenom, amountLeft); + }); } /** @@ -450,7 +451,8 @@ export function getTotalRefreshCostInternal( amountLeft: AmountJson, ): AmountJson { logger.trace( - `computing total refresh cost, denom value ${refreshedDenom.value + `computing total refresh cost, denom value ${ + refreshedDenom.value }, amount left ${Amounts.stringify(amountLeft)}`, ); const withdrawAmount = Amounts.sub( @@ -458,7 +460,9 @@ export function getTotalRefreshCostInternal( refreshedDenom.feeRefresh, ).amount; const denomMap = Object.fromEntries(denoms.map((x) => [x.denomPubHash, x])); - const withdrawDenoms = selectWithdrawalDenominations(withdrawAmount, denoms); + const withdrawDenoms = selectWithdrawalDenominations(withdrawAmount, denoms, { + limitCoins: maxRefreshSessionSize, + }); const resultingAmount = Amounts.add( Amounts.zeroOfCurrency(withdrawAmount.currency), ...withdrawDenoms.selectedDenoms.map( @@ -552,6 +556,9 @@ async function initRefreshSession( const newCoinDenoms = selectWithdrawalDenominations( availableAmount, availableDenoms, + { + limitCoins: maxRefreshSessionSize, + }, ); if (newCoinDenoms.selectedDenoms.length === 0) { @@ -1615,7 +1622,7 @@ async function refreshReveal( ); checkDbInvariant( car.pendingRefreshOutputCount != null && - car.pendingRefreshOutputCount > 0, + car.pendingRefreshOutputCount > 0, `no pendingRefreshOutputCount for denom ${coin.denomPubHash} age ${coin.maxAge}`, ); car.pendingRefreshOutputCount--;