gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] branch master updated: wallet-core: implement scope


From: gnunet
Subject: [taler-wallet-core] branch master updated: wallet-core: implement scope restriction and account restriction reporting for getMaxDepositAmount
Date: Wed, 04 Dec 2024 22:46:17 +0100

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-core.

The following commit(s) were added to refs/heads/master by this push:
     new d1cda666c wallet-core: implement scope restriction and account 
restriction reporting for getMaxDepositAmount
d1cda666c is described below

commit d1cda666c629bfc3b5bc0a8f2039f5eaa80a6964
Author: Florian Dold <florian@dold.me>
AuthorDate: Wed Dec 4 22:46:13 2024 +0100

    wallet-core: implement scope restriction and account restriction reporting 
for getMaxDepositAmount
---
 packages/taler-util/src/types-taler-wallet.ts   |  20 +++
 packages/taler-wallet-core/src/coinSelection.ts |  77 ++++++++++--
 packages/taler-wallet-core/src/deposits.ts      |  18 +--
 packages/taler-wallet-core/src/pay-merchant.ts  | 156 ++++++++++--------------
 4 files changed, 151 insertions(+), 120 deletions(-)

diff --git a/packages/taler-util/src/types-taler-wallet.ts 
b/packages/taler-util/src/types-taler-wallet.ts
index ae5aa2021..c48ca5fef 100644
--- a/packages/taler-util/src/types-taler-wallet.ts
+++ b/packages/taler-util/src/types-taler-wallet.ts
@@ -238,14 +238,27 @@ export const codecForConvertAmountRequest =
     .build("ConvertAmountRequest");
 
 export interface GetMaxDepositAmountRequest {
+  /**
+   * Currency to deposit.
+   */
   currency: string;
+
+  /**
+   * Target bank account to deposit into.
+   */
   depositPaytoUri?: string;
+
+  /**
+   * Restrict the deposit to a certain scope.
+   */
+  restrictScope?: ScopeInfo;
 }
 
 export const codecForGetMaxDepositAmountRequest =
   buildCodecForObject<GetMaxDepositAmountRequest>()
     .property("currency", codecForString())
     .property("depositPaytoUri", codecOptional(codecForString()))
+    .property("restrictScope", codecOptional(codecForScopeInfo()))
     .build("GetAmountRequest");
 
 export interface GetMaxPeerPushDebitAmountRequest {
@@ -268,6 +281,13 @@ export const codecForGetMaxPeerPushDebitAmountRequest =
 export interface GetMaxDepositAmountResponse {
   effectiveAmount: AmountString;
   rawAmount: AmountString;
+
+  /**
+   * Account restrictions that affect the max deposit amount.
+   */
+  depositRestrictions?: {
+    [exchangeBaseUrl: string]: { [paytoUri: string]: AccountRestriction[] };
+  };
 }
 
 export interface GetMaxPeerPushDebitAmountResponse {
diff --git a/packages/taler-wallet-core/src/coinSelection.ts 
b/packages/taler-wallet-core/src/coinSelection.ts
index 1d9ccf9ad..df9eaf3f6 100644
--- a/packages/taler-wallet-core/src/coinSelection.ts
+++ b/packages/taler-wallet-core/src/coinSelection.ts
@@ -26,6 +26,7 @@
 import { GlobalIDB } from "@gnu-taler/idb-bridge";
 import {
   AbsoluteTime,
+  AccountRestriction,
   AgeRestriction,
   AllowedAuditorInfo,
   AllowedExchangeInfo,
@@ -184,6 +185,8 @@ async function internalSelectPayCoins(
       "exchanges",
       "exchangeDetails",
       "coins",
+      "globalCurrencyAuditors",
+      "globalCurrencyExchanges",
     ]
   >,
   req: SelectPayCoinRequestNg,
@@ -288,6 +291,8 @@ export async function selectPayCoinsInTx(
       "exchanges",
       "exchangeDetails",
       "coins",
+      "globalCurrencyAuditors",
+      "globalCurrencyExchanges",
     ]
   >,
   req: SelectPayCoinRequestNg,
@@ -380,6 +385,8 @@ export async function selectPayCoins(
         "exchanges",
         "exchangeDetails",
         "coins",
+        "globalCurrencyAuditors",
+        "globalCurrencyExchanges",
       ],
     },
     async (tx) => {
@@ -756,7 +763,11 @@ export function findMatchingWire(
   wireMethod: string,
   depositPaytoUri: string | undefined,
   exchangeWireDetails: ExchangeWireDetails,
-): { wireFee: AmountJson } | undefined {
+):
+  | { ok: true; wireFee: AmountJson }
+  | { ok: false; accountRestrictions: Record<string, AccountRestriction[]> }
+  | undefined {
+  const accountRestrictions: Record<string, AccountRestriction[]> = {};
   for (const acc of exchangeWireDetails.wireInfo.accounts) {
     const pp = parsePaytoUri(acc.payto_uri);
     checkLogicInvariant(!!pp);
@@ -796,10 +807,18 @@ export function findMatchingWire(
     }
 
     return {
+      ok: true,
       wireFee: Amounts.parseOrThrow(wireFeeStr),
     };
   }
-  return undefined;
+  if (Object.keys(accountRestrictions).length > 0) {
+    return {
+      ok: false,
+      accountRestrictions,
+    };
+  } else {
+    return undefined;
+  }
 }
 
 function checkExchangeAccepted(
@@ -831,6 +850,7 @@ interface SelectPayCandidatesRequest {
   currency: string;
   restrictWireMethod: string | undefined;
   depositPaytoUri?: string;
+  restrictScope?: ScopeInfo;
   restrictExchanges: ExchangeRestrictionSpec | undefined;
   requiredMinimumAge?: number;
 
@@ -845,12 +865,20 @@ interface SelectPayCandidatesRequest {
 export interface PayCoinCandidates {
   coinAvailability: AvailableCoinsOfDenom[];
   currentWireFeePerExchange: Record<string, AmountJson>;
+  depositRestrictions: Record<string, Record<string, AccountRestriction[]>>;
 }
 
 async function selectPayCandidates(
   wex: WalletExecutionContext,
   tx: WalletDbReadOnlyTransaction<
-    ["exchanges", "coinAvailability", "exchangeDetails", "denominations"]
+    [
+      "exchanges",
+      "coinAvailability",
+      "exchangeDetails",
+      "denominations",
+      "globalCurrencyAuditors",
+      "globalCurrencyExchanges",
+    ]
   >,
   req: SelectPayCandidatesRequest,
 ): Promise<PayCoinCandidates> {
@@ -861,26 +889,30 @@ async function selectPayCandidates(
   const denoms: AvailableCoinsOfDenom[] = [];
   const exchanges = await tx.exchanges.iter().toArray();
   const wfPerExchange: Record<string, AmountJson> = {};
+  const depositRestrictions: Record<
+    string,
+    Record<string, AccountRestriction[]>
+  > = {};
   for (const exchange of exchanges) {
     const exchangeDetails = await getExchangeWireDetailsInTx(
       tx,
       exchange.baseUrl,
     );
-    // 1. exchange has same currency
+    // Exchange has same currency
     if (exchangeDetails?.currency !== req.currency) {
       logger.shouldLogTrace() &&
         logger.trace(`skipping ${exchange.baseUrl} due to currency mismatch`);
       continue;
     }
 
-    // 2. Exchange supports wire method (only for pay/deposit)
+    // Exchange supports wire method (only for pay/deposit)
     if (req.restrictWireMethod) {
-      const wire = findMatchingWire(
+      const wireMatch = findMatchingWire(
         req.restrictWireMethod,
         req.depositPaytoUri,
         exchangeDetails,
       );
-      if (!wire) {
+      if (!wireMatch) {
         if (logger.shouldLogTrace()) {
           logger.trace(
             `skipping ${exchange.baseUrl} due to missing wire info mismatch`,
@@ -888,10 +920,14 @@ async function selectPayCandidates(
         }
         continue;
       }
-      wfPerExchange[exchange.baseUrl] = wire.wireFee;
+      if (!wireMatch.ok) {
+        depositRestrictions[exchange.baseUrl] = wireMatch.accountRestrictions;
+        continue;
+      }
+      wfPerExchange[exchange.baseUrl] = wireMatch.wireFee;
     }
 
-    // 3. exchange is trusted in the exchange list or auditor list
+    // Exchange is trusted in the exchange list or auditor list
     let accepted = checkExchangeAccepted(
       exchangeDetails,
       req.restrictExchanges,
@@ -903,7 +939,20 @@ async function selectPayCandidates(
       continue;
     }
 
-    // 4. filter coins restricted by age
+    const isInScope = req.restrictScope
+      ? await checkExchangeInScopeTx(
+          wex,
+          tx,
+          exchange.baseUrl,
+          req.restrictScope,
+        )
+      : true;
+
+    if (!isInScope) {
+      continue;
+    }
+
+    // Filter coins restricted by age
     let ageLower = 0;
     let ageUpper = AgeRestriction.AGE_UNRESTRICTED;
     if (req.requiredMinimumAge) {
@@ -926,7 +975,7 @@ async function selectPayCandidates(
 
     let numUsable = 0;
 
-    // 5. save denoms with how many coins are available
+    // Save denoms with how many coins are available
     // FIXME: Check that the individual denomination is audited!
     // FIXME: Should we exclude denominations that are
     // not spendable anymore?
@@ -977,6 +1026,7 @@ async function selectPayCandidates(
   return {
     coinAvailability: denoms,
     currentWireFeePerExchange: wfPerExchange,
+    depositRestrictions: depositRestrictions,
   };
 }
 
@@ -1125,6 +1175,8 @@ async function internalSelectPeerCoins(
       "denominations",
       "refreshGroups",
       "exchangeDetails",
+      "globalCurrencyAuditors",
+      "globalCurrencyExchanges",
     ]
   >,
   req: PeerCoinSelectionRequest,
@@ -1399,6 +1451,8 @@ export async function getMaxDepositAmount(
         "coinAvailability",
         "denominations",
         "exchangeDetails",
+        "globalCurrencyAuditors",
+        "globalCurrencyExchanges",
       ],
     },
     async (tx): Promise<GetMaxDepositAmountResponse> => {
@@ -1414,6 +1468,7 @@ export async function getMaxDepositAmount(
         currency: req.currency,
         restrictExchanges: undefined,
         restrictWireMethod,
+        restrictScope: req.restrictScope,
         depositPaytoUri: req.depositPaytoUri,
         requiredMinimumAge: undefined,
         includePendingCoins: true,
diff --git a/packages/taler-wallet-core/src/deposits.ts 
b/packages/taler-wallet-core/src/deposits.ts
index 3eda5b165..b424c407a 100644
--- a/packages/taler-wallet-core/src/deposits.ts
+++ b/packages/taler-wallet-core/src/deposits.ts
@@ -1473,22 +1473,8 @@ async function processDepositGroupPendingDeposit(
   if (!depositGroup.payCoinSelection) {
     logger.info("missing coin selection for deposit group, selecting now");
 
-    const transitionDone = await wex.db.runReadWriteTx(
-      {
-        storeNames: [
-          "contractTerms",
-          "exchanges",
-          "exchangeDetails",
-          "depositGroups",
-          "coins",
-          "coinAvailability",
-          "coinHistory",
-          "refreshGroups",
-          "refreshSessions",
-          "denominations",
-          "transactionsMeta",
-        ],
-      },
+    const transitionDone = await wex.db.runAllStoresReadWriteTx(
+      {},
       async (tx) => {
         const dg = await tx.depositGroups.get(depositGroupId);
         if (!dg) {
diff --git a/packages/taler-wallet-core/src/pay-merchant.ts 
b/packages/taler-wallet-core/src/pay-merchant.ts
index a81caacff..d6dafac69 100644
--- a/packages/taler-wallet-core/src/pay-merchant.ts
+++ b/packages/taler-wallet-core/src/pay-merchant.ts
@@ -1442,89 +1442,72 @@ async function handleInsufficientFunds(
 
   // FIXME: Above code should go into the transaction.
 
-  await wex.db.runReadWriteTx(
-    {
-      storeNames: [
-        "coinAvailability",
-        "coinHistory",
-        "coins",
-        "contractTerms",
-        "denominations",
-        "exchangeDetails",
-        "exchanges",
-        "purchases",
-        "refreshGroups",
-        "refreshSessions",
-        "transactionsMeta",
-      ],
-    },
-    async (tx) => {
-      const p = await tx.purchases.get(proposalId);
-      if (!p) {
-        return;
-      }
-      const payInfo = p.payInfo;
-      if (!payInfo) {
-        return;
-      }
-
-      const { contractData } = await expectProposalDownloadInTx(
-        wex,
-        tx,
-        proposal,
-      );
+  await wex.db.runAllStoresReadWriteTx({}, async (tx) => {
+    const p = await tx.purchases.get(proposalId);
+    if (!p) {
+      return;
+    }
+    const payInfo = p.payInfo;
+    if (!payInfo) {
+      return;
+    }
 
-      for (let i = 0; i < payCoinSelection.coinPubs.length; i++) {
-        const coinPub = payCoinSelection.coinPubs[i];
-        const contrib = payCoinSelection.coinContributions[i];
-        prevPayCoins.push({
-          coinPub,
-          contribution: Amounts.parseOrThrow(contrib),
-        });
-      }
+    const { contractData } = await expectProposalDownloadInTx(
+      wex,
+      tx,
+      proposal,
+    );
 
-      const res = await selectPayCoinsInTx(wex, tx, {
-        restrictExchanges: {
-          auditors: [],
-          exchanges: contractData.allowedExchanges,
-        },
-        restrictWireMethod: contractData.wireMethod,
-        contractTermsAmount: Amounts.parseOrThrow(contractData.amount),
-        depositFeeLimit: Amounts.parseOrThrow(contractData.maxDepositFee),
-        prevPayCoins,
-        requiredMinimumAge: contractData.minimumAge,
+    for (let i = 0; i < payCoinSelection.coinPubs.length; i++) {
+      const coinPub = payCoinSelection.coinPubs[i];
+      const contrib = payCoinSelection.coinContributions[i];
+      prevPayCoins.push({
+        coinPub,
+        contribution: Amounts.parseOrThrow(contrib),
       });
+    }
 
-      switch (res.type) {
-        case "failure":
-          logger.trace("insufficient funds for coin re-selection");
-          return;
-        case "prospective":
-          return;
-        case "success":
-          break;
-        default:
-          assertUnreachable(res);
-      }
+    const res = await selectPayCoinsInTx(wex, tx, {
+      restrictExchanges: {
+        auditors: [],
+        exchanges: contractData.allowedExchanges,
+      },
+      restrictWireMethod: contractData.wireMethod,
+      contractTermsAmount: Amounts.parseOrThrow(contractData.amount),
+      depositFeeLimit: Amounts.parseOrThrow(contractData.maxDepositFee),
+      prevPayCoins,
+      requiredMinimumAge: contractData.minimumAge,
+    });
 
-      // Convert to DB format
-      payInfo.payCoinSelection = {
-        coinContributions: res.coinSel.coins.map((x) => x.contribution),
-        coinPubs: res.coinSel.coins.map((x) => x.coinPub),
-      };
-      payInfo.payCoinSelectionUid = encodeCrock(getRandomBytes(32));
-      await tx.purchases.put(p);
-      await ctx.updateTransactionMeta(tx);
-      await spendCoins(wex, tx, {
-        transactionId: ctx.transactionId,
-        coinPubs: payInfo.payCoinSelection.coinPubs,
-        contributions: payInfo.payCoinSelection.coinContributions.map((x) =>
-          Amounts.parseOrThrow(x),
-        ),
-        refreshReason: RefreshReason.PayMerchant,
-      });
-    },
-  );
+    switch (res.type) {
+      case "failure":
+        logger.trace("insufficient funds for coin re-selection");
+        return;
+      case "prospective":
+        return;
+      case "success":
+        break;
+      default:
+        assertUnreachable(res);
+    }
+
+    // Convert to DB format
+    payInfo.payCoinSelection = {
+      coinContributions: res.coinSel.coins.map((x) => x.contribution),
+      coinPubs: res.coinSel.coins.map((x) => x.coinPub),
+    };
+    payInfo.payCoinSelectionUid = encodeCrock(getRandomBytes(32));
+    await tx.purchases.put(p);
+    await ctx.updateTransactionMeta(tx);
+    await spendCoins(wex, tx, {
+      transactionId: ctx.transactionId,
+      coinPubs: payInfo.payCoinSelection.coinPubs,
+      contributions: payInfo.payCoinSelection.coinContributions.map((x) =>
+        Amounts.parseOrThrow(x),
+      ),
+      refreshReason: RefreshReason.PayMerchant,
+    });
+  });
 
   wex.ws.notify({
     type: NotificationType.BalanceChange,
@@ -2216,21 +2199,8 @@ export async function confirmPay(
     `recording payment on ${proposal.orderId} with session ID ${sessionId}`,
   );
 
-  const transitionInfo = await wex.db.runReadWriteTx(
-    {
-      storeNames: [
-        "coinAvailability",
-        "coinHistory",
-        "coins",
-        "denominations",
-        "exchangeDetails",
-        "exchanges",
-        "purchases",
-        "refreshGroups",
-        "refreshSessions",
-        "transactionsMeta",
-      ],
-    },
+  const transitionInfo = await wex.db.runAllStoresReadWriteTx(
+    {},
     async (tx) => {
       const p = await tx.purchases.get(proposal.proposalId);
       if (!p) {

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]