From 91be5b89cd92c53d6aa2f68247f9626c8bc8f64a Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 6 Mar 2024 14:17:31 +0100 Subject: towards refactoring coin selection --- packages/taler-wallet-core/src/balance.ts | 257 +++++++++++++++++++----------- 1 file changed, 162 insertions(+), 95 deletions(-) (limited to 'packages/taler-wallet-core/src/balance.ts') diff --git a/packages/taler-wallet-core/src/balance.ts b/packages/taler-wallet-core/src/balance.ts index 6dc0783c0..a77358363 100644 --- a/packages/taler-wallet-core/src/balance.ts +++ b/packages/taler-wallet-core/src/balance.ts @@ -468,12 +468,16 @@ export interface MerchantPaymentBalanceDetails { balanceAvailable: AmountJson; } -export interface MerchantPaymentRestrictionsForBalance { +export interface PaymentRestrictionsForBalance { currency: string; minAge: number; - acceptedExchanges: AllowedExchangeInfo[]; - acceptedAuditors: AllowedAuditorInfo[]; - acceptedWireMethods: string[]; + restrictExchanges: + | { + exchanges: AllowedExchangeInfo[]; + auditors: AllowedAuditorInfo[]; + } + | undefined; + restrictWireMethods: string[] | undefined; } export interface AcceptableExchanges { @@ -492,69 +496,73 @@ export interface AcceptableExchanges { /** * Get all exchanges that are acceptable for a particular payment. */ -export async function getAcceptableExchangeBaseUrls( +async function getAcceptableExchangeBaseUrlsInTx( wex: WalletExecutionContext, - req: MerchantPaymentRestrictionsForBalance, + tx: WalletDbReadOnlyTransaction<["exchanges", "exchangeDetails"]>, + req: PaymentRestrictionsForBalance, ): Promise { const acceptableExchangeUrls = new Set(); const depositableExchangeUrls = new Set(); - await wex.db.runReadOnlyTx(["exchanges", "exchangeDetails"], async (tx) => { - // FIXME: We should have a DB index to look up all exchanges - // for a particular auditor ... + // FIXME: We should have a DB index to look up all exchanges + // for a particular auditor ... - const canonExchanges = new Set(); - const canonAuditors = new Set(); + const canonExchanges = new Set(); + const canonAuditors = new Set(); - for (const exchangeHandle of req.acceptedExchanges) { + if (req.restrictExchanges) { + for (const exchangeHandle of req.restrictExchanges.exchanges) { const normUrl = canonicalizeBaseUrl(exchangeHandle.exchangeBaseUrl); canonExchanges.add(normUrl); } - for (const auditorHandle of req.acceptedAuditors) { + for (const auditorHandle of req.restrictExchanges.auditors) { const normUrl = canonicalizeBaseUrl(auditorHandle.auditorBaseUrl); canonAuditors.add(normUrl); } + } - await tx.exchanges.iter().forEachAsync(async (exchange) => { - const dp = exchange.detailsPointer; - if (!dp) { - return; - } - const { currency, masterPublicKey } = dp; - const exchangeDetails = await tx.exchangeDetails.indexes.byPointer.get([ - exchange.baseUrl, - currency, - masterPublicKey, - ]); - if (!exchangeDetails) { - return; - } + await tx.exchanges.iter().forEachAsync(async (exchange) => { + const dp = exchange.detailsPointer; + if (!dp) { + return; + } + const { currency, masterPublicKey } = dp; + const exchangeDetails = await tx.exchangeDetails.indexes.byPointer.get([ + exchange.baseUrl, + currency, + masterPublicKey, + ]); + if (!exchangeDetails) { + return; + } - let acceptable = false; + let acceptable = false; - if (canonExchanges.has(exchange.baseUrl)) { + if (canonExchanges.has(exchange.baseUrl)) { + acceptableExchangeUrls.add(exchange.baseUrl); + acceptable = true; + } + for (const exchangeAuditor of exchangeDetails.auditors) { + if (canonAuditors.has(exchangeAuditor.auditor_url)) { acceptableExchangeUrls.add(exchange.baseUrl); acceptable = true; + break; } - for (const exchangeAuditor of exchangeDetails.auditors) { - if (canonAuditors.has(exchangeAuditor.auditor_url)) { - acceptableExchangeUrls.add(exchange.baseUrl); - acceptable = true; - break; - } - } + } - if (!acceptable) { - return; - } - // FIXME: Also consider exchange and auditor public key - // instead of just base URLs? + if (!acceptable) { + return; + } + // FIXME: Also consider exchange and auditor public key + // instead of just base URLs? + + let wireMethodSupported = false; - let wireMethodSupported = false; + if (req.restrictWireMethods) { for (const acc of exchangeDetails.wireInfo.accounts) { const pp = parsePaytoUri(acc.payto_uri); checkLogicInvariant(!!pp); - for (const wm of req.acceptedWireMethods) { + for (const wm of req.restrictWireMethods) { if (pp.targetType === wm) { wireMethodSupported = true; break; @@ -564,12 +572,14 @@ export async function getAcceptableExchangeBaseUrls( } } } + } else { + wireMethodSupported = true; + } - acceptableExchangeUrls.add(exchange.baseUrl); - if (wireMethodSupported) { - depositableExchangeUrls.add(exchange.baseUrl); - } - }); + acceptableExchangeUrls.add(exchange.baseUrl); + if (wireMethodSupported) { + depositableExchangeUrls.add(exchange.baseUrl); + } }); return { acceptableExchanges: [...acceptableExchangeUrls], @@ -606,9 +616,24 @@ export interface MerchantPaymentBalanceDetails { export async function getMerchantPaymentBalanceDetails( wex: WalletExecutionContext, - req: MerchantPaymentRestrictionsForBalance, + req: PaymentRestrictionsForBalance, ): Promise { - const acceptability = await getAcceptableExchangeBaseUrls(wex, req); + return await wex.db.runReadOnlyTx( + ["coinAvailability", "refreshGroups", "exchanges", "exchangeDetails"], + async (tx) => { + return getMerchantPaymentBalanceDetailsInTx(wex, tx, req); + }, + ); +} + +export async function getMerchantPaymentBalanceDetailsInTx( + wex: WalletExecutionContext, + tx: WalletDbReadOnlyTransaction< + ["coinAvailability", "refreshGroups", "exchanges", "exchangeDetails"] + >, + req: PaymentRestrictionsForBalance, +): Promise { + const acceptability = await getAcceptableExchangeBaseUrlsInTx(wex, tx, req); const d: MerchantPaymentBalanceDetails = { balanceAvailable: Amounts.zeroOfCurrency(req.currency), @@ -618,53 +643,46 @@ export async function getMerchantPaymentBalanceDetails( balanceMerchantDepositable: Amounts.zeroOfCurrency(req.currency), }; - await wex.db.runReadOnlyTx( - ["coinAvailability", "refreshGroups"], - async (tx) => { - await tx.coinAvailability.iter().forEach((ca) => { - if (ca.currency != req.currency) { - return; - } - const singleCoinAmount: AmountJson = Amounts.parseOrThrow(ca.value); - const coinAmount: AmountJson = Amounts.mult( - singleCoinAmount, - ca.freshCoinCount, + await tx.coinAvailability.iter().forEach((ca) => { + if (ca.currency != req.currency) { + return; + } + const singleCoinAmount: AmountJson = Amounts.parseOrThrow(ca.value); + const coinAmount: AmountJson = Amounts.mult( + singleCoinAmount, + ca.freshCoinCount, + ).amount; + d.balanceAvailable = Amounts.add(d.balanceAvailable, coinAmount).amount; + d.balanceMaterial = Amounts.add(d.balanceMaterial, coinAmount).amount; + if (ca.maxAge === 0 || ca.maxAge > req.minAge) { + d.balanceAgeAcceptable = Amounts.add( + d.balanceAgeAcceptable, + coinAmount, + ).amount; + if (acceptability.acceptableExchanges.includes(ca.exchangeBaseUrl)) { + d.balanceMerchantAcceptable = Amounts.add( + d.balanceMerchantAcceptable, + coinAmount, ).amount; - d.balanceAvailable = Amounts.add(d.balanceAvailable, coinAmount).amount; - d.balanceMaterial = Amounts.add(d.balanceMaterial, coinAmount).amount; - if (ca.maxAge === 0 || ca.maxAge > req.minAge) { - d.balanceAgeAcceptable = Amounts.add( - d.balanceAgeAcceptable, + if (acceptability.depositableExchanges.includes(ca.exchangeBaseUrl)) { + d.balanceMerchantDepositable = Amounts.add( + d.balanceMerchantDepositable, coinAmount, ).amount; - if (acceptability.acceptableExchanges.includes(ca.exchangeBaseUrl)) { - d.balanceMerchantAcceptable = Amounts.add( - d.balanceMerchantAcceptable, - coinAmount, - ).amount; - if ( - acceptability.depositableExchanges.includes(ca.exchangeBaseUrl) - ) { - d.balanceMerchantDepositable = Amounts.add( - d.balanceMerchantDepositable, - coinAmount, - ).amount; - } - } } - }); + } + } + }); - await tx.refreshGroups.iter().forEach((r) => { - if (r.currency != req.currency) { - return; - } - d.balanceAvailable = Amounts.add( - d.balanceAvailable, - computeRefreshGroupAvailableAmount(r), - ).amount; - }); - }, - ); + await tx.refreshGroups.iter().forEach((r) => { + if (r.currency != req.currency) { + return; + } + d.balanceAvailable = Amounts.add( + d.balanceAvailable, + computeRefreshGroupAvailableAmount(r), + ).amount; + }); return d; } @@ -697,9 +715,11 @@ export async function getBalanceDetail( return await getMerchantPaymentBalanceDetails(wex, { currency: req.currency, - acceptedAuditors: [], - acceptedExchanges: exchanges, - acceptedWireMethods: wires, + restrictExchanges: { + auditors: [], + exchanges, + }, + restrictWireMethods: wires, minAge: 0, }); } @@ -763,3 +783,50 @@ export async function getPeerPaymentBalanceDetailsInTx( balanceMaterial, }; } + +/** + * Get information about the balance at a given exchange + * with certain restrictions. + */ +export async function getExchangePaymentBalanceDetailsInTx( + wex: WalletExecutionContext, + tx: WalletDbReadOnlyTransaction<["coinAvailability", "refreshGroups"]>, + req: PeerPaymentRestrictionsForBalance, +): Promise { + let balanceAvailable = Amounts.zeroOfCurrency(req.currency); + let balanceMaterial = Amounts.zeroOfCurrency(req.currency); + + await tx.coinAvailability.iter().forEach((ca) => { + if (ca.currency != req.currency) { + return; + } + if ( + req.restrictExchangeTo && + req.restrictExchangeTo !== ca.exchangeBaseUrl + ) { + return; + } + const singleCoinAmount: AmountJson = Amounts.parseOrThrow(ca.value); + const coinAmount: AmountJson = Amounts.mult( + singleCoinAmount, + ca.freshCoinCount, + ).amount; + balanceAvailable = Amounts.add(balanceAvailable, coinAmount).amount; + balanceMaterial = Amounts.add(balanceMaterial, coinAmount).amount; + }); + + await tx.refreshGroups.iter().forEach((r) => { + if (r.currency != req.currency) { + return; + } + balanceAvailable = Amounts.add( + balanceAvailable, + computeRefreshGroupAvailableAmount(r), + ).amount; + }); + + return { + balanceAvailable, + balanceMaterial, + }; +} -- cgit v1.2.3