diff options
Diffstat (limited to 'packages/taler-wallet-core/src/util/coinSelection.ts')
-rw-r--r-- | packages/taler-wallet-core/src/util/coinSelection.ts | 246 |
1 files changed, 0 insertions, 246 deletions
diff --git a/packages/taler-wallet-core/src/util/coinSelection.ts b/packages/taler-wallet-core/src/util/coinSelection.ts index d2f12baf5..12f87a920 100644 --- a/packages/taler-wallet-core/src/util/coinSelection.ts +++ b/packages/taler-wallet-core/src/util/coinSelection.ts @@ -25,15 +25,11 @@ */ import { AgeCommitmentProof, - AgeRestriction, AmountJson, Amounts, DenominationPubKey, - ForcedCoinSel, Logger, - PayCoinSelection, } from "@gnu-taler/taler-util"; -import { checkLogicInvariant } from "./invariants.js"; const logger = new Logger("coinSelection.ts"); @@ -194,245 +190,3 @@ export function tallyFees( wireFeeCoveredForExchange, }; } - -/** - * Given a list of candidate coins, select coins to spend under the merchant's - * constraints. - * - * The prevPayCoins can be specified to "repair" a coin selection - * by adding additional coins, after a broken (e.g. double-spent) coin - * has been removed from the selection. - * - * This function is only exported for the sake of unit tests. - */ -export function selectPayCoinsLegacy( - req: SelectPayCoinRequest, -): PayCoinSelection | undefined { - const { - candidates, - contractTermsAmount, - depositFeeLimit, - wireFeeLimit, - wireFeeAmortization, - } = req; - - if (candidates.candidateCoins.length === 0) { - return undefined; - } - const coinPubs: string[] = []; - const coinContributions: AmountJson[] = []; - const currency = contractTermsAmount.currency; - - let tally: CoinSelectionTally = { - amountPayRemaining: contractTermsAmount, - amountWireFeeLimitRemaining: wireFeeLimit, - amountDepositFeeLimitRemaining: depositFeeLimit, - customerDepositFees: Amounts.getZero(currency), - customerWireFees: Amounts.getZero(currency), - wireFeeCoveredForExchange: new Set(), - }; - - const prevPayCoins = req.prevPayCoins ?? []; - - // Look at existing pay coin selection and tally up - for (const prev of prevPayCoins) { - tally = tallyFees( - tally, - candidates.wireFeesPerExchange, - wireFeeAmortization, - prev.exchangeBaseUrl, - prev.feeDeposit, - ); - tally.amountPayRemaining = Amounts.sub( - tally.amountPayRemaining, - prev.contribution, - ).amount; - - coinPubs.push(prev.coinPub); - coinContributions.push(prev.contribution); - } - - const prevCoinPubs = new Set(prevPayCoins.map((x) => x.coinPub)); - - // Sort by available amount (descending), deposit fee (ascending) and - // denomPub (ascending) if deposit fee is the same - // (to guarantee deterministic results) - const candidateCoins = [...candidates.candidateCoins].sort( - (o1, o2) => - -Amounts.cmp(o1.availableAmount, o2.availableAmount) || - Amounts.cmp(o1.feeDeposit, o2.feeDeposit) || - DenominationPubKey.cmp(o1.denomPub, o2.denomPub), - ); - - // FIXME: Here, we should select coins in a smarter way. - // Instead of always spending the next-largest coin, - // we should try to find the smallest coin that covers the - // amount. - - for (const aci of candidateCoins) { - // Don't use this coin if depositing it is more expensive than - // the amount it would give the merchant. - if (Amounts.cmp(aci.feeDeposit, aci.availableAmount) > 0) { - continue; - } - - if (Amounts.isZero(tally.amountPayRemaining)) { - // We have spent enough! - break; - } - - // The same coin can't contribute twice to the same payment, - // by a fundamental, intentional limitation of the protocol. - if (prevCoinPubs.has(aci.coinPub)) { - continue; - } - - if (req.requiredMinimumAge != null) { - const index = AgeRestriction.getAgeGroupIndex( - aci.denomPub.age_mask, - req.requiredMinimumAge, - ); - // if (!aci.ageCommitmentProof) { - // // No age restriction, can't use for this payment - // continue; - // } - if ( - aci.ageCommitmentProof && - aci.ageCommitmentProof.proof.privateKeys.length < index - ) { - // Available age proofs to low, can't use for this payment - continue; - } - } - - tally = tallyFees( - tally, - candidates.wireFeesPerExchange, - wireFeeAmortization, - aci.exchangeBaseUrl, - aci.feeDeposit, - ); - - let coinSpend = Amounts.max( - Amounts.min(tally.amountPayRemaining, aci.availableAmount), - aci.feeDeposit, - ); - - tally.amountPayRemaining = Amounts.sub( - tally.amountPayRemaining, - coinSpend, - ).amount; - coinPubs.push(aci.coinPub); - coinContributions.push(coinSpend); - } - - if (Amounts.isZero(tally.amountPayRemaining)) { - return { - paymentAmount: contractTermsAmount, - coinContributions, - coinPubs, - customerDepositFees: tally.customerDepositFees, - customerWireFees: tally.customerWireFees, - }; - } - return undefined; -} - -export function selectForcedPayCoins( - forcedCoinSel: ForcedCoinSel, - req: SelectPayCoinRequest, -): PayCoinSelection | undefined { - const { - candidates, - contractTermsAmount, - depositFeeLimit, - wireFeeLimit, - wireFeeAmortization, - } = req; - - if (candidates.candidateCoins.length === 0) { - return undefined; - } - const coinPubs: string[] = []; - const coinContributions: AmountJson[] = []; - const currency = contractTermsAmount.currency; - - let tally: CoinSelectionTally = { - amountPayRemaining: contractTermsAmount, - amountWireFeeLimitRemaining: wireFeeLimit, - amountDepositFeeLimitRemaining: depositFeeLimit, - customerDepositFees: Amounts.getZero(currency), - customerWireFees: Amounts.getZero(currency), - wireFeeCoveredForExchange: new Set(), - }; - - // Not supported by forced coin selection - checkLogicInvariant(!req.prevPayCoins); - - // Sort by available amount (descending), deposit fee (ascending) and - // denomPub (ascending) if deposit fee is the same - // (to guarantee deterministic results) - const candidateCoins = [...candidates.candidateCoins].sort( - (o1, o2) => - -Amounts.cmp(o1.availableAmount, o2.availableAmount) || - Amounts.cmp(o1.feeDeposit, o2.feeDeposit) || - DenominationPubKey.cmp(o1.denomPub, o2.denomPub), - ); - - // FIXME: Here, we should select coins in a smarter way. - // Instead of always spending the next-largest coin, - // we should try to find the smallest coin that covers the - // amount. - - // Set of spent coin indices from candidate coins - const spentSet: Set<number> = new Set(); - - for (const forcedCoin of forcedCoinSel.coins) { - let aci: AvailableCoinInfo | undefined = undefined; - for (let i = 0; i < candidateCoins.length; i++) { - if (spentSet.has(i)) { - continue; - } - if ( - Amounts.cmp(forcedCoin.value, candidateCoins[i].availableAmount) != 0 - ) { - continue; - } - spentSet.add(i); - aci = candidateCoins[i]; - break; - } - - if (!aci) { - throw Error("can't find coin for forced coin selection"); - } - - tally = tallyFees( - tally, - candidates.wireFeesPerExchange, - wireFeeAmortization, - aci.exchangeBaseUrl, - aci.feeDeposit, - ); - - let coinSpend = Amounts.parseOrThrow(forcedCoin.contribution); - - tally.amountPayRemaining = Amounts.sub( - tally.amountPayRemaining, - coinSpend, - ).amount; - coinPubs.push(aci.coinPub); - coinContributions.push(coinSpend); - } - - if (Amounts.isZero(tally.amountPayRemaining)) { - return { - paymentAmount: contractTermsAmount, - coinContributions, - coinPubs, - customerDepositFees: tally.customerDepositFees, - customerWireFees: tally.customerWireFees, - }; - } - return undefined; -} |