summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2024-03-06 21:00:34 +0100
committerFlorian Dold <florian@dold.me>2024-03-06 21:00:34 +0100
commit618caa117111b9fed6a792b6816fc724483eb349 (patch)
tree45cfdf5ef3ad13b351980209d0e77d2f6ab82e3e /packages/taler-wallet-core/src
parent91be5b89cd92c53d6aa2f68247f9626c8bc8f64a (diff)
downloadwallet-core-618caa117111b9fed6a792b6816fc724483eb349.tar.gz
wallet-core-618caa117111b9fed6a792b6816fc724483eb349.tar.bz2
wallet-core-618caa117111b9fed6a792b6816fc724483eb349.zip
move denom selection to separate file
Diffstat (limited to 'packages/taler-wallet-core/src')
-rw-r--r--packages/taler-wallet-core/src/coinSelection.ts205
-rw-r--r--packages/taler-wallet-core/src/denomSelection.ts150
-rw-r--r--packages/taler-wallet-core/src/refresh.ts2
-rw-r--r--packages/taler-wallet-core/src/withdraw.test.ts2
-rw-r--r--packages/taler-wallet-core/src/withdraw.ts2
5 files changed, 205 insertions, 156 deletions
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<PayCoinSelection> {
+ 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;
diff --git a/packages/taler-wallet-core/src/denomSelection.ts b/packages/taler-wallet-core/src/denomSelection.ts
new file mode 100644
index 000000000..12f8f8971
--- /dev/null
+++ b/packages/taler-wallet-core/src/denomSelection.ts
@@ -0,0 +1,150 @@
+/*
+ This file is part of GNU Taler
+ (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
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+ * Selection of denominations for withdrawals.
+ *
+ * @author Florian Dold
+ */
+
+/**
+ * Imports.
+ */
+import {
+ AmountJson,
+ Amounts,
+ DenomSelectionState,
+ ForcedDenomSel,
+ Logger,
+} from "@gnu-taler/taler-util";
+import { DenominationRecord } from "./db.js";
+import { isWithdrawableDenom } from "./denominations.js";
+
+const logger = new Logger("denomSelection.ts");
+
+/**
+ * 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),
+ };
+}
diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts
index 9c272ad18..c6a7b768d 100644
--- a/packages/taler-wallet-core/src/refresh.ts
+++ b/packages/taler-wallet-core/src/refresh.ts
@@ -61,7 +61,7 @@ import {
readSuccessResponseJsonOrThrow,
readUnexpectedResponseDetails,
} from "@gnu-taler/taler-util/http";
-import { selectWithdrawalDenominations } from "./coinSelection.js";
+import { selectWithdrawalDenominations } from "./denomSelection.js";
import {
constructTaskIdentifier,
makeCoinAvailable,
diff --git a/packages/taler-wallet-core/src/withdraw.test.ts b/packages/taler-wallet-core/src/withdraw.test.ts
index 3e92b1717..d8757d0cf 100644
--- a/packages/taler-wallet-core/src/withdraw.test.ts
+++ b/packages/taler-wallet-core/src/withdraw.test.ts
@@ -21,7 +21,7 @@ import {
DenominationVerificationStatus,
timestampProtocolToDb,
} from "./db.js";
-import { selectWithdrawalDenominations } from "./coinSelection.js";
+import { selectWithdrawalDenominations } from "./denomSelection.js";
test("withdrawal selection bug repro", (t) => {
const amount = {
diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts
index 853a5e0df..0f70479a5 100644
--- a/packages/taler-wallet-core/src/withdraw.ts
+++ b/packages/taler-wallet-core/src/withdraw.ts
@@ -97,7 +97,7 @@ import {
import {
selectForcedWithdrawalDenominations,
selectWithdrawalDenominations,
-} from "./coinSelection.js";
+} from "./denomSelection.js";
import {
PendingTaskType,
TaskIdStr,