aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/coinSelection.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-core/src/coinSelection.ts')
-rw-r--r--packages/taler-wallet-core/src/coinSelection.ts373
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);
},
);
}