diff options
Diffstat (limited to 'packages/taler-wallet-core/src/coinSelection.ts')
-rw-r--r-- | packages/taler-wallet-core/src/coinSelection.ts | 373 |
1 files changed, 200 insertions, 173 deletions
diff --git a/packages/taler-wallet-core/src/coinSelection.ts b/packages/taler-wallet-core/src/coinSelection.ts index db6384c93..51316a21f 100644 --- a/packages/taler-wallet-core/src/coinSelection.ts +++ b/packages/taler-wallet-core/src/coinSelection.ts @@ -252,6 +252,88 @@ async function internalSelectPayCoins( }; } +export async function selectPayCoinsInTx( + wex: WalletExecutionContext, + tx: WalletDbReadOnlyTransaction< + [ + "coinAvailability", + "denominations", + "refreshGroups", + "exchanges", + "exchangeDetails", + "coins", + ] + >, + req: SelectPayCoinRequestNg, +): Promise<SelectPayCoinsResult> { + if (logger.shouldLogTrace()) { + logger.trace(`selecting coins for ${j2s(req)}`); + } + + const materialAvSel = await internalSelectPayCoins(wex, tx, req, false); + + if (!materialAvSel) { + const prospectiveAvSel = await internalSelectPayCoins(wex, tx, req, true); + + if (prospectiveAvSel) { + const prospectiveCoins: SelectedProspectiveCoin[] = []; + for (const avKey of Object.keys(prospectiveAvSel.sel)) { + const mySel = prospectiveAvSel.sel[avKey]; + for (const contrib of mySel.contributions) { + prospectiveCoins.push({ + denomPubHash: mySel.denomPubHash, + contribution: Amounts.stringify(contrib), + exchangeBaseUrl: mySel.exchangeBaseUrl, + }); + } + } + return { + type: "prospective", + result: { + prospectiveCoins, + customerDepositFees: Amounts.stringify( + prospectiveAvSel.tally.customerDepositFees, + ), + customerWireFees: Amounts.stringify( + prospectiveAvSel.tally.customerWireFees, + ), + }, + } satisfies SelectPayCoinsResult; + } + + return { + type: "failure", + insufficientBalanceDetails: await reportInsufficientBalanceDetails( + wex, + tx, + { + restrictExchanges: req.restrictExchanges, + instructedAmount: req.contractTermsAmount, + requiredMinimumAge: req.requiredMinimumAge, + wireMethod: req.restrictWireMethod, + depositPaytoUri: req.depositPaytoUri, + }, + ), + } satisfies SelectPayCoinsResult; + } + + const coinSel = await assembleSelectPayCoinsSuccessResult( + tx, + materialAvSel.sel, + materialAvSel.coinRes, + materialAvSel.tally, + ); + + if (logger.shouldLogTrace()) { + logger.trace(`coin selection: ${j2s(coinSel)}`); + } + + return { + type: "success", + coinSel, + }; +} + /** * Select coins to spend under the merchant's constraints. * @@ -263,10 +345,6 @@ export async function selectPayCoins( wex: WalletExecutionContext, req: SelectPayCoinRequestNg, ): Promise<SelectPayCoinsResult> { - if (logger.shouldLogTrace()) { - logger.trace(`selecting coins for ${j2s(req)}`); - } - return await wex.db.runReadOnlyTx( { storeNames: [ @@ -279,73 +357,7 @@ export async function selectPayCoins( ], }, async (tx) => { - const materialAvSel = await internalSelectPayCoins(wex, tx, req, false); - - if (!materialAvSel) { - const prospectiveAvSel = await internalSelectPayCoins( - wex, - tx, - req, - true, - ); - - if (prospectiveAvSel) { - const prospectiveCoins: SelectedProspectiveCoin[] = []; - for (const avKey of Object.keys(prospectiveAvSel.sel)) { - const mySel = prospectiveAvSel.sel[avKey]; - for (const contrib of mySel.contributions) { - prospectiveCoins.push({ - denomPubHash: mySel.denomPubHash, - contribution: Amounts.stringify(contrib), - exchangeBaseUrl: mySel.exchangeBaseUrl, - }); - } - } - return { - type: "prospective", - result: { - prospectiveCoins, - customerDepositFees: Amounts.stringify( - prospectiveAvSel.tally.customerDepositFees, - ), - customerWireFees: Amounts.stringify( - prospectiveAvSel.tally.customerWireFees, - ), - }, - } satisfies SelectPayCoinsResult; - } - - return { - type: "failure", - insufficientBalanceDetails: await reportInsufficientBalanceDetails( - wex, - tx, - { - restrictExchanges: req.restrictExchanges, - instructedAmount: req.contractTermsAmount, - requiredMinimumAge: req.requiredMinimumAge, - wireMethod: req.restrictWireMethod, - depositPaytoUri: req.depositPaytoUri, - }, - ), - } satisfies SelectPayCoinsResult; - } - - const coinSel = await assembleSelectPayCoinsSuccessResult( - tx, - materialAvSel.sel, - materialAvSel.coinRes, - materialAvSel.tally, - ); - - if (logger.shouldLogTrace()) { - logger.trace(`coin selection: ${j2s(coinSel)}`); - } - - return { - type: "success", - coinSel, - }; + return selectPayCoinsInTx(wex, tx, req); }, ); } @@ -910,7 +922,10 @@ async function selectPayCandidates( coinAvail.exchangeBaseUrl, coinAvail.denomPubHash, ]); - checkDbInvariant(!!denom, `denomination of a coin is missing hash: ${coinAvail.denomPubHash}`); + checkDbInvariant( + !!denom, + `denomination of a coin is missing hash: ${coinAvail.denomPubHash}`, + ); if (denom.isRevoked) { logger.trace("denom is revoked"); continue; @@ -1131,17 +1146,127 @@ async function internalSelectPeerCoins( }; } -export async function selectPeerCoins( +export async function selectPeerCoinsInTx( wex: WalletExecutionContext, + tx: WalletDbReadOnlyTransaction< + [ + "exchanges", + "contractTerms", + "coins", + "coinAvailability", + "denominations", + "refreshGroups", + "exchangeDetails", + ] + >, req: PeerCoinSelectionRequest, ): Promise<SelectPeerCoinsResult> { const instructedAmount = req.instructedAmount; if (Amounts.isZero(instructedAmount)) { // Other parts of the code assume that we have at least // one coin to spend. - throw new Error("amount of zero not allowed"); + throw new Error("peer-to-peer payment with amount of zero not supported"); } + const exchanges = await tx.exchanges.iter().toArray(); + const currency = Amounts.currencyOf(instructedAmount); + for (const exch of exchanges) { + if (exch.detailsPointer?.currency !== currency) { + continue; + } + const exchWire = await getExchangeWireDetailsInTx(tx, exch.baseUrl); + if (!exchWire) { + continue; + } + const globalFees = getGlobalFees(exchWire); + if (!globalFees) { + continue; + } + + const avRes = await internalSelectPeerCoins(wex, tx, req, exchWire, false); + + if (!avRes) { + // Try to see if we can do a prospective selection + const prospectiveAvRes = await internalSelectPeerCoins( + wex, + tx, + req, + exchWire, + true, + ); + if (prospectiveAvRes) { + const prospectiveCoins: SelectedProspectiveCoin[] = []; + for (const avKey of Object.keys(prospectiveAvRes.sel)) { + const mySel = prospectiveAvRes.sel[avKey]; + for (const contrib of mySel.contributions) { + prospectiveCoins.push({ + denomPubHash: mySel.denomPubHash, + contribution: Amounts.stringify(contrib), + exchangeBaseUrl: mySel.exchangeBaseUrl, + }); + } + } + const maxExpirationDate = await computeCoinSelMaxExpirationDate( + wex, + tx, + prospectiveAvRes.sel, + ); + return { + type: "prospective", + result: { + prospectiveCoins, + depositFees: prospectiveAvRes.tally.customerDepositFees, + exchangeBaseUrl: exch.baseUrl, + maxExpirationDate, + }, + }; + } + } else if (avRes) { + const r = await assembleSelectPayCoinsSuccessResult( + tx, + avRes.sel, + avRes.resCoins, + avRes.tally, + ); + + const maxExpirationDate = await computeCoinSelMaxExpirationDate( + wex, + tx, + avRes.sel, + ); + + return { + type: "success", + result: { + coins: r.coins, + depositFees: Amounts.parseOrThrow(r.customerDepositFees), + exchangeBaseUrl: exch.baseUrl, + maxExpirationDate, + }, + }; + } + } + const insufficientBalanceDetails = await reportInsufficientBalanceDetails( + wex, + tx, + { + restrictExchanges: undefined, + instructedAmount: req.instructedAmount, + requiredMinimumAge: undefined, + wireMethod: undefined, + depositPaytoUri: undefined, + }, + ); + return { + type: "failure", + insufficientBalanceDetails, + }; +} + +export async function selectPeerCoins( + wex: WalletExecutionContext, + req: PeerCoinSelectionRequest, +): Promise<SelectPeerCoinsResult> { return await wex.db.runReadWriteTx( { storeNames: [ @@ -1155,105 +1280,7 @@ export async function selectPeerCoins( ], }, async (tx): Promise<SelectPeerCoinsResult> => { - const exchanges = await tx.exchanges.iter().toArray(); - const currency = Amounts.currencyOf(instructedAmount); - for (const exch of exchanges) { - if (exch.detailsPointer?.currency !== currency) { - continue; - } - const exchWire = await getExchangeWireDetailsInTx(tx, exch.baseUrl); - if (!exchWire) { - continue; - } - const globalFees = getGlobalFees(exchWire); - if (!globalFees) { - continue; - } - - const avRes = await internalSelectPeerCoins( - wex, - tx, - req, - exchWire, - false, - ); - - if (!avRes) { - // Try to see if we can do a prospective selection - const prospectiveAvRes = await internalSelectPeerCoins( - wex, - tx, - req, - exchWire, - true, - ); - if (prospectiveAvRes) { - const prospectiveCoins: SelectedProspectiveCoin[] = []; - for (const avKey of Object.keys(prospectiveAvRes.sel)) { - const mySel = prospectiveAvRes.sel[avKey]; - for (const contrib of mySel.contributions) { - prospectiveCoins.push({ - denomPubHash: mySel.denomPubHash, - contribution: Amounts.stringify(contrib), - exchangeBaseUrl: mySel.exchangeBaseUrl, - }); - } - } - const maxExpirationDate = await computeCoinSelMaxExpirationDate( - wex, - tx, - prospectiveAvRes.sel, - ); - return { - type: "prospective", - result: { - prospectiveCoins, - depositFees: prospectiveAvRes.tally.customerDepositFees, - exchangeBaseUrl: exch.baseUrl, - maxExpirationDate, - }, - }; - } - } else if (avRes) { - const r = await assembleSelectPayCoinsSuccessResult( - tx, - avRes.sel, - avRes.resCoins, - avRes.tally, - ); - - const maxExpirationDate = await computeCoinSelMaxExpirationDate( - wex, - tx, - avRes.sel, - ); - - return { - type: "success", - result: { - coins: r.coins, - depositFees: Amounts.parseOrThrow(r.customerDepositFees), - exchangeBaseUrl: exch.baseUrl, - maxExpirationDate, - }, - }; - } - } - const insufficientBalanceDetails = await reportInsufficientBalanceDetails( - wex, - tx, - { - restrictExchanges: undefined, - instructedAmount: req.instructedAmount, - requiredMinimumAge: undefined, - wireMethod: undefined, - depositPaytoUri: undefined, - }, - ); - return { - type: "failure", - insufficientBalanceDetails, - }; + return selectPeerCoinsInTx(wex, tx, req); }, ); } |