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