From 618caa117111b9fed6a792b6816fc724483eb349 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 6 Mar 2024 21:00:34 +0100 Subject: move denom selection to separate file --- packages/taler-wallet-core/src/coinSelection.ts | 205 ++++++------------------ 1 file changed, 52 insertions(+), 153 deletions(-) (limited to 'packages/taler-wallet-core/src/coinSelection.ts') diff --git a/packages/taler-wallet-core/src/coinSelection.ts b/packages/taler-wallet-core/src/coinSelection.ts index c44ca3d17..1208e7c37 100644 --- a/packages/taler-wallet-core/src/coinSelection.ts +++ b/packages/taler-wallet-core/src/coinSelection.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2021 Taler Systems S.A. + (C) 2021-2024 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -39,10 +39,8 @@ import { CoinPublicKeyString, CoinStatus, DenominationInfo, - DenomSelectionState, Duration, ForcedCoinSel, - ForcedDenomSel, InternationalizedString, j2s, Logger, @@ -59,7 +57,6 @@ import { } from "./balance.js"; import { getAutoRefreshExecuteThreshold } from "./common.js"; import { DenominationRecord, WalletDbReadOnlyTransaction } from "./db.js"; -import { isWithdrawableDenom } from "./denominations.js"; import { ExchangeWireDetails, getExchangeWireDetailsInTx, @@ -292,49 +289,65 @@ export async function selectPayCoins( } satisfies SelectPayCoinsResult; } - const finalSel = selectedDenom; - - logger.trace(`coin selection request ${j2s(req)}`); - logger.trace(`selected coins (via denoms) for payment: ${j2s(finalSel)}`); - - for (const dph of Object.keys(finalSel)) { - const selInfo = finalSel[dph]; - const numRequested = selInfo.contributions.length; - const query = [ - selInfo.exchangeBaseUrl, - selInfo.denomPubHash, - selInfo.maxAge, - CoinStatus.Fresh, - ]; - logger.trace(`query: ${j2s(query)}`); - const coins = - await tx.coins.indexes.byExchangeDenomPubHashAndAgeAndStatus.getAll( - query, - numRequested, - ); - if (coins.length != numRequested) { - throw Error( - `coin selection failed (not available anymore, got only ${coins.length}/${numRequested})`, - ); - } - coinPubs.push(...coins.map((x) => x.coinPub)); - coinContributions.push(...selInfo.contributions); - } + const coinSel = await assembleSelectPayCoinsSuccessResult( + tx, + selectedDenom, + coinPubs, + coinContributions, + req.contractTermsAmount, + tally, + ); return { type: "success", - coinSel: { - paymentAmount: Amounts.stringify(contractTermsAmount), - coinContributions: coinContributions.map((x) => Amounts.stringify(x)), - coinPubs, - customerDepositFees: Amounts.stringify(tally.customerDepositFees), - customerWireFees: Amounts.stringify(tally.customerWireFees), - }, + coinSel, }; }, ); } +async function assembleSelectPayCoinsSuccessResult( + tx: WalletDbReadOnlyTransaction<["coins"]>, + finalSel: SelResult, + coinPubs: string[], + coinContributions: AmountJson[], + contractTermsAmount: AmountJson, + tally: CoinSelectionTally, +): Promise { + for (const dph of Object.keys(finalSel)) { + const selInfo = finalSel[dph]; + const numRequested = selInfo.contributions.length; + const query = [ + selInfo.exchangeBaseUrl, + selInfo.denomPubHash, + selInfo.maxAge, + CoinStatus.Fresh, + ]; + logger.trace(`query: ${j2s(query)}`); + const coins = + await tx.coins.indexes.byExchangeDenomPubHashAndAgeAndStatus.getAll( + query, + numRequested, + ); + if (coins.length != numRequested) { + throw Error( + `coin selection failed (not available anymore, got only ${coins.length}/${numRequested})`, + ); + } + coinPubs.push(...coins.map((x) => x.coinPub)); + coinContributions.push(...selInfo.contributions); + } + + return { + // FIXME: Why do we return this?! + paymentAmount: Amounts.stringify(contractTermsAmount), + coinContributions: coinContributions.map((x) => Amounts.stringify(x)), + coinPubs, + customerDepositFees: Amounts.stringify(tally.customerDepositFees), + customerWireFees: Amounts.stringify(tally.customerWireFees), + }; +} + interface ReportInsufficientBalanceRequest { instructedAmount: AmountJson; requiredMinimumAge: number | undefined; @@ -783,120 +796,6 @@ async function selectPayCandidates( return [denoms, wfPerExchange]; } -/** - * Get a list of denominations (with repetitions possible) - * whose total value is as close as possible to the available - * amount, but never larger. - */ -export function selectWithdrawalDenominations( - amountAvailable: AmountJson, - denoms: DenominationRecord[], - denomselAllowLate: boolean = false, -): DenomSelectionState { - let remaining = Amounts.copy(amountAvailable); - - const selectedDenoms: { - count: number; - denomPubHash: string; - }[] = []; - - let totalCoinValue = Amounts.zeroOfCurrency(amountAvailable.currency); - let totalWithdrawCost = Amounts.zeroOfCurrency(amountAvailable.currency); - - denoms = denoms.filter((d) => isWithdrawableDenom(d, denomselAllowLate)); - denoms.sort((d1, d2) => Amounts.cmp(d2.value, d1.value)); - - 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; - remaining = Amounts.sub(remaining, Amounts.mult(cost, count).amount).amount; - if (count > 0) { - totalCoinValue = Amounts.add( - totalCoinValue, - Amounts.mult(d.value, count).amount, - ).amount; - totalWithdrawCost = Amounts.add( - totalWithdrawCost, - Amounts.mult(cost, count).amount, - ).amount; - selectedDenoms.push({ - count, - denomPubHash: d.denomPubHash, - }); - } - - if (Amounts.isZero(remaining)) { - break; - } - } - - if (logger.shouldLogTrace()) { - logger.trace( - `selected withdrawal denoms for ${Amounts.stringify(totalCoinValue)}`, - ); - for (const sd of selectedDenoms) { - logger.trace(`denom_pub_hash=${sd.denomPubHash}, count=${sd.count}`); - } - logger.trace("(end of withdrawal denom list)"); - } - - return { - selectedDenoms, - totalCoinValue: Amounts.stringify(totalCoinValue), - totalWithdrawCost: Amounts.stringify(totalWithdrawCost), - }; -} - -export function selectForcedWithdrawalDenominations( - amountAvailable: AmountJson, - denoms: DenominationRecord[], - forcedDenomSel: ForcedDenomSel, - denomselAllowLate: boolean, -): DenomSelectionState { - const selectedDenoms: { - count: number; - denomPubHash: string; - }[] = []; - - let totalCoinValue = Amounts.zeroOfCurrency(amountAvailable.currency); - let totalWithdrawCost = Amounts.zeroOfCurrency(amountAvailable.currency); - - denoms = denoms.filter((d) => isWithdrawableDenom(d, denomselAllowLate)); - denoms.sort((d1, d2) => Amounts.cmp(d2.value, d1.value)); - - for (const fds of forcedDenomSel.denoms) { - const count = fds.count; - const denom = denoms.find((x) => { - return Amounts.cmp(x.value, fds.value) == 0; - }); - if (!denom) { - throw Error( - `unable to find denom for forced selection (value ${fds.value})`, - ); - } - const cost = Amounts.add(denom.value, denom.fees.feeWithdraw).amount; - totalCoinValue = Amounts.add( - totalCoinValue, - Amounts.mult(denom.value, count).amount, - ).amount; - totalWithdrawCost = Amounts.add( - totalWithdrawCost, - Amounts.mult(cost, count).amount, - ).amount; - selectedDenoms.push({ - count, - denomPubHash: denom.denomPubHash, - }); - } - - return { - selectedDenoms, - totalCoinValue: Amounts.stringify(totalCoinValue), - totalWithdrawCost: Amounts.stringify(totalWithdrawCost), - }; -} - export interface CoinInfo { id: string; value: AmountJson; -- cgit v1.2.3