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: exchange managem


From: gnunet
Subject: [taler-wallet-core] branch master updated: wallet-core: exchange management cleanup
Date: Mon, 08 Jan 2024 21:17:09 +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 6f2b03021 wallet-core: exchange management cleanup
6f2b03021 is described below

commit 6f2b03021d7946a61d6b8e53dbba7fc10e5f9a4d
Author: Florian Dold <florian@dold.me>
AuthorDate: Mon Jan 8 21:17:00 2024 +0100

    wallet-core: exchange management cleanup
---
 .../taler-wallet-core/src/internal-wallet-state.ts |  24 ----
 .../taler-wallet-core/src/operations/balance.ts    |   7 +-
 .../taler-wallet-core/src/operations/deposits.ts   |  17 ++-
 .../taler-wallet-core/src/operations/exchanges.ts  | 130 +++++++++++++++++++--
 .../src/operations/pay-peer-push-credit.ts         |  20 ++--
 .../src/operations/transactions.ts                 |  63 +++++-----
 .../taler-wallet-core/src/operations/withdraw.ts   |  91 +++++++--------
 .../taler-wallet-core/src/util/coinSelection.ts    |   7 +-
 .../src/util/instructedAmountConversion.ts         |  18 +--
 packages/taler-wallet-core/src/wallet.ts           |  39 +------
 10 files changed, 242 insertions(+), 174 deletions(-)

diff --git a/packages/taler-wallet-core/src/internal-wallet-state.ts 
b/packages/taler-wallet-core/src/internal-wallet-state.ts
index b1389a359..94f2367e1 100644
--- a/packages/taler-wallet-core/src/internal-wallet-state.ts
+++ b/packages/taler-wallet-core/src/internal-wallet-state.ts
@@ -95,29 +95,6 @@ export interface RefreshOperations {
   ): Promise<RefreshGroupId>;
 }
 
-/**
- * Interface for exchange-related operations.
- */
-export interface ExchangeOperations {
-  // FIXME:  Should other operations maybe always use
-  // updateExchangeFromUrl?
-  getExchangeDetails(
-    tx: GetReadOnlyAccess<{
-      exchanges: typeof WalletStoresV1.exchanges;
-      exchangeDetails: typeof WalletStoresV1.exchangeDetails;
-    }>,
-    exchangeBaseUrl: string,
-  ): Promise<ExchangeDetailsRecord | undefined>;
-  fetchFreshExchange(
-    ws: InternalWalletState,
-    baseUrl: string,
-    options?: {
-      forceNow?: boolean;
-      cancellationToken?: CancellationToken;
-    },
-  ): Promise<ReadyExchangeSummary>;
-}
-
 export interface RecoupOperations {
   createRecoupGroup(
     ws: InternalWalletState,
@@ -176,7 +153,6 @@ export interface InternalWalletState {
 
   merchantInfoCache: Record<string, MerchantInfo>;
 
-  exchangeOps: ExchangeOperations;
   recoupOps: RecoupOperations;
   merchantOps: MerchantOperations;
   refreshOps: RefreshOperations;
diff --git a/packages/taler-wallet-core/src/operations/balance.ts 
b/packages/taler-wallet-core/src/operations/balance.ts
index fdaab0d5f..53ca33fe7 100644
--- a/packages/taler-wallet-core/src/operations/balance.ts
+++ b/packages/taler-wallet-core/src/operations/balance.ts
@@ -49,6 +49,7 @@
 /**
  * Imports.
  */
+import { GlobalIDB } from "@gnu-taler/idb-bridge";
 import {
   AllowedAuditorInfo,
   AllowedExchangeInfo,
@@ -67,7 +68,6 @@ import {
   OPERATION_STATUS_ACTIVE_FIRST,
   OPERATION_STATUS_ACTIVE_LAST,
   RefreshGroupRecord,
-  RefreshOperationStatus,
   WalletStoresV1,
   WithdrawalGroupStatus,
 } from "../db.js";
@@ -75,8 +75,7 @@ import { InternalWalletState } from 
"../internal-wallet-state.js";
 import { assertUnreachable } from "../util/assertUnreachable.js";
 import { checkLogicInvariant } from "../util/invariants.js";
 import { GetReadOnlyAccess } from "../util/query.js";
-import { getExchangeDetails } from "./exchanges.js";
-import { GlobalIDB } from "@gnu-taler/idb-bridge";
+import { getExchangeWireDetailsInTx } from "./exchanges.js";
 
 /**
  * Logger.
@@ -516,7 +515,7 @@ export async function getBalanceDetail(
     .runReadOnly(async (tx) => {
       const allExchanges = await tx.exchanges.iter().toArray();
       for (const e of allExchanges) {
-        const details = await getExchangeDetails(tx, e.baseUrl);
+        const details = await getExchangeWireDetailsInTx(tx, e.baseUrl);
         if (!details || req.currency !== details.currency) {
           continue;
         }
diff --git a/packages/taler-wallet-core/src/operations/deposits.ts 
b/packages/taler-wallet-core/src/operations/deposits.ts
index 8205b7583..f158d9cf9 100644
--- a/packages/taler-wallet-core/src/operations/deposits.ts
+++ b/packages/taler-wallet-core/src/operations/deposits.ts
@@ -75,7 +75,6 @@ import {
   getCandidateWithdrawalDenomsTx,
   getTotalRefreshCost,
   timestampPreciseToDb,
-  timestampProtocolFromDb,
   timestampProtocolToDb,
 } from "../index.js";
 import { InternalWalletState } from "../internal-wallet-state.js";
@@ -89,7 +88,7 @@ import {
   runLongpollAsync,
   spendCoins,
 } from "./common.js";
-import { getExchangeDetails } from "./exchanges.js";
+import { getExchangeWireDetailsInTx } from "./exchanges.js";
 import {
   extractContractData,
   generateDepositPermissions,
@@ -1168,7 +1167,7 @@ export async function prepareDepositGroup(
     .runReadOnly(async (tx) => {
       const allExchanges = await tx.exchanges.iter().toArray();
       for (const e of allExchanges) {
-        const details = await getExchangeDetails(tx, e.baseUrl);
+        const details = await getExchangeWireDetailsInTx(tx, e.baseUrl);
         if (!details || amount.currency !== details.currency) {
           continue;
         }
@@ -1282,7 +1281,7 @@ export async function createDepositGroup(
     .runReadOnly(async (tx) => {
       const allExchanges = await tx.exchanges.iter().toArray();
       for (const e of allExchanges) {
-        const details = await getExchangeDetails(tx, e.baseUrl);
+        const details = await getExchangeWireDetailsInTx(tx, e.baseUrl);
         if (!details || amount.currency !== details.currency) {
           continue;
         }
@@ -1495,7 +1494,10 @@ export async function 
getCounterpartyEffectiveDepositAmount(
       }
 
       for (const exchangeUrl of exchangeSet.values()) {
-        const exchangeDetails = await getExchangeDetails(tx, exchangeUrl);
+        const exchangeDetails = await getExchangeWireDetailsInTx(
+          tx,
+          exchangeUrl,
+        );
         if (!exchangeDetails) {
           continue;
         }
@@ -1574,7 +1576,10 @@ async function getTotalFeesForDepositAmount(
       }
 
       for (const exchangeUrl of exchangeSet.values()) {
-        const exchangeDetails = await getExchangeDetails(tx, exchangeUrl);
+        const exchangeDetails = await getExchangeWireDetailsInTx(
+          tx,
+          exchangeUrl,
+        );
         if (!exchangeDetails) {
           continue;
         }
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts 
b/packages/taler-wallet-core/src/operations/exchanges.ts
index 67d598e70..766af27a8 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -32,6 +32,7 @@ import {
   DenominationInfo,
   DenominationPubKey,
   Duration,
+  EddsaPublicKeyString,
   ExchangeAuditor,
   ExchangeDetailedResponse,
   ExchangeGlobalFees,
@@ -41,6 +42,7 @@ import {
   ExchangeWireAccount,
   ExchangesListResponse,
   FeeDescription,
+  GetExchangeEntryByUrlRequest,
   GetExchangeTosResult,
   GlobalFees,
   LibtoolVersion,
@@ -175,10 +177,8 @@ async function downloadExchangeWithTermsOfService(
 
 /**
  * Get exchange details from the database.
- *
- * FIXME: Should we encapsulate the result better, instead of returning the 
raw DB records here?
  */
-export async function getExchangeDetails(
+async function getExchangeRecordsInternal(
   tx: GetReadOnlyAccess<{
     exchanges: typeof WalletStoresV1.exchanges;
     exchangeDetails: typeof WalletStoresV1.exchangeDetails;
@@ -201,6 +201,67 @@ export async function getExchangeDetails(
   ]);
 }
 
+export interface ExchangeWireDetails {
+  currency: string;
+  masterPublicKey: EddsaPublicKeyString;
+  wireInfo: WireInfo;
+  exchangeBaseUrl: string;
+  auditors: ExchangeAuditor[];
+  globalFees: ExchangeGlobalFees[];
+}
+
+export async function getExchangeWireDetailsInTx(
+  tx: GetReadOnlyAccess<{
+    exchanges: typeof WalletStoresV1.exchanges;
+    exchangeDetails: typeof WalletStoresV1.exchangeDetails;
+  }>,
+  exchangeBaseUrl: string,
+): Promise<ExchangeWireDetails | undefined> {
+  const det = await getExchangeRecordsInternal(tx, exchangeBaseUrl);
+  if (!det) {
+    return undefined;
+  }
+  return {
+    currency: det.currency,
+    masterPublicKey: det.masterPublicKey,
+    wireInfo: det.wireInfo,
+    exchangeBaseUrl: det.exchangeBaseUrl,
+    auditors: det.auditors,
+    globalFees: det.globalFees,
+  };
+}
+
+export async function lookupExchangeByUri(
+  ws: InternalWalletState,
+  req: GetExchangeEntryByUrlRequest,
+): Promise<ExchangeListItem> {
+  return await ws.db
+    .mktx((x) => [
+      x.exchanges,
+      x.exchangeDetails,
+      x.denominations,
+      x.operationRetries,
+    ])
+    .runReadOnly(async (tx) => {
+      const exchangeRec = await tx.exchanges.get(req.exchangeBaseUrl);
+      if (!exchangeRec) {
+        throw Error("exchange not found");
+      }
+      const exchangeDetails = await getExchangeRecordsInternal(
+        tx,
+        exchangeRec.baseUrl,
+      );
+      const opRetryRecord = await tx.operationRetries.get(
+        TaskIdentifiers.forExchangeUpdate(exchangeRec),
+      );
+      return makeExchangeListItem(
+        exchangeRec,
+        exchangeDetails,
+        opRetryRecord?.lastError,
+      );
+    });
+}
+
 /**
  * Mark a ToS version as accepted by the user.
  *
@@ -417,7 +478,7 @@ async function provideExchangeRecordInTx(
       newExchangeState: getExchangeState(r),
     };
   }
-  const exchangeDetails = await getExchangeDetails(tx, baseUrl);
+  const exchangeDetails = await getExchangeRecordsInternal(tx, baseUrl);
   return { exchange, exchangeDetails, notification };
 }
 
@@ -825,7 +886,7 @@ export async function fetchFreshExchange(
     .mktx((x) => [x.exchanges, x.exchangeDetails, x.operationRetries])
     .runReadOnly(async (tx) => {
       const exchange = await tx.exchanges.get(canonUrl);
-      const exchangeDetails = await getExchangeDetails(tx, canonUrl);
+      const exchangeDetails = await getExchangeRecordsInternal(tx, canonUrl);
       const retryInfo = await tx.operationRetries.get(operationId);
       return { exchange, exchangeDetails, retryInfo };
     });
@@ -980,7 +1041,7 @@ export async function updateExchangeFromUrlHandler(
         return;
       }
       const oldExchangeState = getExchangeState(r);
-      const existingDetails = await getExchangeDetails(tx, r.baseUrl);
+      const existingDetails = await getExchangeRecordsInternal(tx, r.baseUrl);
       if (!existingDetails) {
         detailsPointerChanged = true;
       }
@@ -1173,7 +1234,7 @@ export async function getExchangePaytoUri(
   const details = await ws.db
     .mktx((x) => [x.exchangeDetails, x.exchanges])
     .runReadOnly(async (tx) => {
-      return getExchangeDetails(tx, exchangeBaseUrl);
+      return getExchangeRecordsInternal(tx, exchangeBaseUrl);
     });
   const accounts = details?.wireInfo.accounts ?? [];
   for (const account of accounts) {
@@ -1202,7 +1263,6 @@ export async function getExchangeTos(
   acceptedFormat?: string[],
   acceptLanguage?: string,
 ): Promise<GetExchangeTosResult> {
-  // FIXME: download ToS in acceptable format if passed!
   const exch = await fetchFreshExchange(ws, exchangeBaseUrl);
 
   const tosDownload = await downloadTosFromAcceptedFormat(
@@ -1234,6 +1294,10 @@ export async function getExchangeTos(
   };
 }
 
+/**
+ * Parsed information about an exchange,
+ * obtained by requesting /keys.
+ */
 export interface ExchangeInfo {
   keys: ExchangeKeysDownloadResult;
 }
@@ -1273,7 +1337,7 @@ export async function listExchanges(
           tag: PendingTaskType.ExchangeUpdate,
           exchangeBaseUrl: r.baseUrl,
         });
-        const exchangeDetails = await getExchangeDetails(tx, r.baseUrl);
+        const exchangeDetails = await getExchangeRecordsInternal(tx, 
r.baseUrl);
         const opRetryRecord = await tx.operationRetries.get(taskId);
         exchanges.push(
           makeExchangeListItem(r, exchangeDetails, opRetryRecord?.lastError),
@@ -1283,11 +1347,55 @@ export async function listExchanges(
   return { exchanges };
 }
 
+/**
+ * Transition an exchange to the "used" entry state if necessary.
+ *
+ * Should be called whenever the exchange is actively used by the client (for 
withdrawals etc.).
+ */
+export async function markExchangeUsed(
+  ws: InternalWalletState,
+  tx: GetReadWriteAccess<{ exchanges: typeof WalletStoresV1.exchanges }>,
+  exchangeBaseUrl: string,
+): Promise<{ notif: WalletNotification | undefined }> {
+  exchangeBaseUrl = canonicalizeBaseUrl(exchangeBaseUrl);
+  logger.info(`marking exchange ${exchangeBaseUrl} as used`);
+  const exch = await tx.exchanges.get(exchangeBaseUrl);
+  if (!exch) {
+    return {
+      notif: undefined,
+    };
+  }
+  const oldExchangeState = getExchangeState(exch);
+  switch (exch.entryStatus) {
+    case ExchangeEntryDbRecordStatus.Ephemeral:
+    case ExchangeEntryDbRecordStatus.Preset: {
+      exch.entryStatus = ExchangeEntryDbRecordStatus.Used;
+      await tx.exchanges.put(exch);
+      const newExchangeState = getExchangeState(exch);
+      return {
+        notif: {
+          type: NotificationType.ExchangeStateTransition,
+          exchangeBaseUrl,
+          newExchangeState: newExchangeState,
+          oldExchangeState: oldExchangeState,
+        } satisfies WalletNotification,
+      };
+    }
+    default:
+      return {
+        notif: undefined,
+      };
+  }
+}
+
+/**
+ * Get detailed information about the exchange including a timeline
+ * for the fees charged by the exchange.
+ */
 export async function getExchangeDetailedInfo(
   ws: InternalWalletState,
   exchangeBaseurl: string,
 ): Promise<ExchangeDetailedResponse> {
-  // TODO: should we use the forceUpdate parameter?
   const exchange = await ws.db
     .mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations])
     .runReadOnly(async (tx) => {
@@ -1297,7 +1405,7 @@ export async function getExchangeDetailedInfo(
         return;
       }
       const { currency } = dp;
-      const exchangeDetails = await getExchangeDetails(tx, ex.baseUrl);
+      const exchangeDetails = await getExchangeRecordsInternal(tx, ex.baseUrl);
       if (!exchangeDetails) {
         return;
       }
diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts 
b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts
index 78263c4c3..6b7b62393 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts
@@ -69,7 +69,7 @@ import {
   constructTaskIdentifier,
   runLongpollAsync,
 } from "./common.js";
-import { fetchFreshExchange } from "./exchanges.js";
+import { fetchFreshExchange, markExchangeUsed } from "./exchanges.js";
 import {
   codecForExchangePurseStatus,
   getMergeReserveInfo,
@@ -82,6 +82,7 @@ import {
   stopLongpolling,
 } from "./transactions.js";
 import {
+  PerformCreateWithdrawalGroupResult,
   getExchangeWithdrawalInfo,
   internalPerformCreateWithdrawalGroup,
   internalPrepareCreateWithdrawalGroup,
@@ -486,19 +487,20 @@ async function handlePendingMerge(
       if (!peerInc) {
         return undefined;
       }
-      let withdrawalTransition: TransitionInfo | undefined;
       const oldTxState = computePeerPushCreditTransactionState(peerInc);
+      let wgCreateRes: PerformCreateWithdrawalGroupResult | undefined =
+        undefined;
       switch (peerInc.status) {
         case PeerPushCreditStatus.PendingMerge:
         case PeerPushCreditStatus.PendingMergeKycRequired: {
           peerInc.status = PeerPushCreditStatus.PendingWithdrawing;
-          const wgRes = await internalPerformCreateWithdrawalGroup(
+          wgCreateRes = await internalPerformCreateWithdrawalGroup(
             ws,
             tx,
             withdrawalGroupPrep,
           );
-          withdrawalTransition = wgRes.transitionInfo;
-          peerInc.withdrawalGroupId = wgRes.withdrawalGroup.withdrawalGroupId;
+          peerInc.withdrawalGroupId =
+            wgCreateRes.withdrawalGroup.withdrawalGroupId;
           break;
         }
       }
@@ -506,13 +508,17 @@ async function handlePendingMerge(
       const newTxState = computePeerPushCreditTransactionState(peerInc);
       return {
         peerPushCreditTransition: { oldTxState, newTxState },
-        withdrawalTransition,
+        wgCreateRes,
       };
     });
+  // Transaction was commited, now we can emit notifications.
+  if (txRes?.wgCreateRes?.exchangeNotif) {
+    ws.notify(txRes.wgCreateRes.exchangeNotif);
+  }
   notifyTransition(
     ws,
     withdrawalGroupPrep.transactionId,
-    txRes?.withdrawalTransition,
+    txRes?.wgCreateRes?.transitionInfo,
   );
   notifyTransition(ws, transactionId, txRes?.peerPushCreditTransition);
 
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts 
b/packages/taler-wallet-core/src/operations/transactions.ts
index 9deb050d8..3a219b39b 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -47,7 +47,6 @@ import {
 import {
   DepositElementStatus,
   DepositGroupRecord,
-  ExchangeDetailsRecord,
   OperationRetryRecord,
   PeerPullCreditRecord,
   PeerPullDebitRecordStatus,
@@ -91,7 +90,10 @@ import {
   resumeDepositGroup,
   suspendDepositGroup,
 } from "./deposits.js";
-import { getExchangeDetails } from "./exchanges.js";
+import {
+  ExchangeWireDetails,
+  getExchangeWireDetailsInTx,
+} from "./exchanges.js";
 import {
   abortPayMerchant,
   computePayMerchantTransactionActions,
@@ -137,8 +139,8 @@ import {
 } from "./pay-peer-push-debit.js";
 import {
   iterRecordsForDeposit,
-  iterRecordsForPeerPullDebit,
   iterRecordsForPeerPullInitiation as iterRecordsForPeerPullCredit,
+  iterRecordsForPeerPullDebit,
   iterRecordsForPeerPushCredit,
   iterRecordsForPeerPushInitiation as iterRecordsForPeerPushDebit,
   iterRecordsForPurchase,
@@ -240,9 +242,8 @@ export async function getTransactionById(
           x.operationRetries,
         ])
         .runReadWrite(async (tx) => {
-          const withdrawalGroupRecord = await tx.withdrawalGroups.get(
-            withdrawalGroupId,
-          );
+          const withdrawalGroupRecord =
+            await tx.withdrawalGroups.get(withdrawalGroupId);
 
           if (!withdrawalGroupRecord) throw Error("not found");
 
@@ -258,7 +259,7 @@ export async function getTransactionById(
               ort,
             );
           }
-          const exchangeDetails = await getExchangeDetails(
+          const exchangeDetails = await getExchangeWireDetailsInTx(
             tx,
             withdrawalGroupRecord.exchangeBaseUrl,
           );
@@ -290,7 +291,9 @@ export async function getTransactionById(
           const payOpId = TaskIdentifiers.forPay(purchase);
           const payRetryRecord = await tx.operationRetries.get(payOpId);
 
-          const refunds = await 
tx.refundGroups.indexes.byProposalId.getAll(purchase.proposalId)
+          const refunds = await tx.refundGroups.indexes.byProposalId.getAll(
+            purchase.proposalId,
+          );
 
           return buildTransactionForPurchase(
             purchase,
@@ -544,7 +547,7 @@ function buildTransactionForPeerPullCredit(
     const silentWithdrawalErrorForInvoice =
       wsrOrt?.lastError &&
       wsrOrt.lastError.code ===
-      TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE &&
+        TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE &&
       Object.values(wsrOrt.lastError.errorsPerCoin ?? {}).every((e) => {
         return (
           e.code === TalerErrorCode.WALLET_UNEXPECTED_REQUEST_ERROR &&
@@ -574,10 +577,10 @@ function buildTransactionForPeerPullCredit(
       kycUrl: pullCredit.kycUrl,
       ...(wsrOrt?.lastError
         ? {
-          error: silentWithdrawalErrorForInvoice
-            ? undefined
-            : wsrOrt.lastError,
-        }
+            error: silentWithdrawalErrorForInvoice
+              ? undefined
+              : wsrOrt.lastError,
+          }
         : {}),
     };
   }
@@ -698,7 +701,7 @@ function buildTransactionForBankIntegratedWithdraw(
 
 function buildTransactionForManualWithdraw(
   withdrawalGroup: WithdrawalGroupRecord,
-  exchangeDetails: ExchangeDetailsRecord,
+  exchangeDetails: ExchangeWireDetails,
   ort?: OperationRetryRecord,
 ): Transaction {
   if (withdrawalGroup.wgInfo.withdrawalType !== 
WithdrawalRecordType.BankManual)
@@ -725,7 +728,8 @@ function buildTransactionForManualWithdraw(
       type: WithdrawalType.ManualTransfer,
       reservePub: withdrawalGroup.reservePub,
       exchangePaytoUris,
-      exchangeCreditAccountDetails: 
withdrawalGroup.wgInfo.exchangeCreditAccounts,
+      exchangeCreditAccountDetails:
+        withdrawalGroup.wgInfo.exchangeCreditAccounts,
       reserveIsReady:
         withdrawalGroup.status === WithdrawalGroupStatus.Done ||
         withdrawalGroup.status === WithdrawalGroupStatus.PendingReady,
@@ -928,14 +932,16 @@ async function buildTransactionForPurchase(
     info.fulfillmentUrl = contractData.fulfillmentUrl;
   }
 
-  const refunds: RefundInfoShort[] = refundsInfo.map(r => ({
+  const refunds: RefundInfoShort[] = refundsInfo.map((r) => ({
     amountEffective: r.amountEffective,
     amountRaw: r.amountRaw,
-    timestamp: 
TalerPreciseTimestamp.round(timestampPreciseFromDb(r.timestampCreated)),
+    timestamp: TalerPreciseTimestamp.round(
+      timestampPreciseFromDb(r.timestampCreated),
+    ),
     transactionId: constructTransactionIdentifier({
       tag: TransactionType.Refund,
       refundGroupId: r.refundGroupId,
-    })
+    }),
   }));
 
   const timestamp = purchaseRecord.timestampAccept;
@@ -1193,7 +1199,7 @@ export async function getTransactions(
             );
             return;
           case WithdrawalRecordType.BankManual: {
-            const exchangeDetails = await getExchangeDetails(
+            const exchangeDetails = await getExchangeWireDetailsInTx(
               tx,
               wsr.exchangeBaseUrl,
             );
@@ -1258,7 +1264,9 @@ export async function getTransactions(
         const payOpId = TaskIdentifiers.forPay(purchase);
         const payRetryRecord = await tx.operationRetries.get(payOpId);
 
-        const refunds = await 
tx.refundGroups.indexes.byProposalId.getAll(purchase.proposalId)
+        const refunds = await tx.refundGroups.indexes.byProposalId.getAll(
+          purchase.proposalId,
+        );
 
         transactions.push(
           await buildTransactionForPurchase(
@@ -1723,9 +1731,8 @@ export async function deleteTransaction(
           }
           if (pushInc.withdrawalGroupId) {
             const withdrawalGroupId = pushInc.withdrawalGroupId;
-            const withdrawalGroupRecord = await tx.withdrawalGroups.get(
-              withdrawalGroupId,
-            );
+            const withdrawalGroupRecord =
+              await tx.withdrawalGroups.get(withdrawalGroupId);
             if (withdrawalGroupRecord) {
               await tx.withdrawalGroups.delete(withdrawalGroupId);
               await tx.tombstones.put({
@@ -1753,9 +1760,8 @@ export async function deleteTransaction(
           }
           if (pullIni.withdrawalGroupId) {
             const withdrawalGroupId = pullIni.withdrawalGroupId;
-            const withdrawalGroupRecord = await tx.withdrawalGroups.get(
-              withdrawalGroupId,
-            );
+            const withdrawalGroupRecord =
+              await tx.withdrawalGroups.get(withdrawalGroupId);
             if (withdrawalGroupRecord) {
               await tx.withdrawalGroups.delete(withdrawalGroupId);
               await tx.tombstones.put({
@@ -1778,9 +1784,8 @@ export async function deleteTransaction(
       await ws.db
         .mktx((x) => [x.withdrawalGroups, x.tombstones])
         .runReadWrite(async (tx) => {
-          const withdrawalGroupRecord = await tx.withdrawalGroups.get(
-            withdrawalGroupId,
-          );
+          const withdrawalGroupRecord =
+            await tx.withdrawalGroups.get(withdrawalGroupId);
           if (withdrawalGroupRecord) {
             await tx.withdrawalGroups.delete(withdrawalGroupId);
             await tx.tombstones.put({
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts 
b/packages/taler-wallet-core/src/operations/withdraw.ts
index cf4e9a1d5..49c0e4a14 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -57,6 +57,7 @@ import {
   TransactionType,
   URL,
   UnblindedSignature,
+  WalletNotification,
   WithdrawUriInfoResponse,
   WithdrawalExchangeAccountDetails,
   addPaytoQueryParams,
@@ -133,8 +134,10 @@ import {
 import {
   ReadyExchangeSummary,
   fetchFreshExchange,
-  getExchangeDetails,
   getExchangePaytoUri,
+  getExchangeWireDetailsInTx,
+  listExchanges,
+  markExchangeUsed,
 } from "./exchanges.js";
 import {
   TransitionInfo,
@@ -1191,7 +1194,7 @@ export async function updateWithdrawalDenoms(
   const exchangeDetails = await ws.db
     .mktx((x) => [x.exchanges, x.exchangeDetails])
     .runReadOnly(async (tx) => {
-      return ws.exchangeOps.getExchangeDetails(tx, exchangeBaseUrl);
+      return getExchangeWireDetailsInTx(tx, exchangeBaseUrl);
     });
   if (!exchangeDetails) {
     logger.error("exchange details not available");
@@ -1521,7 +1524,7 @@ async function processWithdrawalGroupPendingReady(
     withdrawalGroupId,
   });
 
-  await ws.exchangeOps.fetchFreshExchange(ws, withdrawalGroup.exchangeBaseUrl);
+  await fetchFreshExchange(ws, withdrawalGroup.exchangeBaseUrl);
 
   if (withdrawalGroup.denomsSel.selectedDenoms.length === 0) {
     logger.warn("Finishing empty withdrawal group (no denoms)");
@@ -1768,7 +1771,7 @@ export async function getExchangeWithdrawalInfo(
   ageRestricted: number | undefined,
 ): Promise<ExchangeWithdrawalDetails> {
   logger.trace("updating exchange");
-  const exchange = await ws.exchangeOps.fetchFreshExchange(ws, 
exchangeBaseUrl);
+  const exchange = await fetchFreshExchange(ws, exchangeBaseUrl);
 
   if (exchange.currency != instructedAmount.currency) {
     // Specifiying the amount in the conversion input currency is not yet 
supported.
@@ -1917,7 +1920,7 @@ export interface GetWithdrawalDetailsForUriOpts {
  * Get more information about a taler://withdraw URI.
  *
  * As side effects, the bank (via the bank integration API) is queried
- * and the exchange suggested by the bank is permanently added
+ * and the exchange suggested by the bank is ephemerally added
  * to the wallet's list of known exchanges.
  */
 export async function getWithdrawalDetailsForUri(
@@ -1929,10 +1932,10 @@ export async function getWithdrawalDetailsForUri(
   const info = await getBankWithdrawalInfo(ws.http, talerWithdrawUri);
   logger.trace(`got bank info`);
   if (info.suggestedExchange) {
-    // FIXME: right now the exchange gets permanently added,
-    // we might want to only temporarily add it.
     try {
-      await ws.exchangeOps.fetchFreshExchange(ws, info.suggestedExchange);
+      // If the exchange entry doesn't exist yet,
+      // it'll be created as an ephemeral entry.
+      await fetchFreshExchange(ws, info.suggestedExchange);
     } catch (e) {
       // We still continued if it failed, as other exchanges might be 
available.
       // We don't want to fail if the bank-suggested exchange is 
broken/offline.
@@ -1942,40 +1945,12 @@ export async function getWithdrawalDetailsForUri(
     }
   }
 
-  // Extract information about possible exchanges for the withdrawal
-  // operation from the database.
-
-  const exchanges: ExchangeListItem[] = [];
-
-  await ws.db
-    .mktx((x) => [
-      x.exchanges,
-      x.exchangeDetails,
-      x.denominations,
-      x.operationRetries,
-    ])
-    .runReadOnly(async (tx) => {
-      const exchangeRecords = await tx.exchanges.iter().toArray();
-      for (const r of exchangeRecords) {
-        const exchangeDetails = await ws.exchangeOps.getExchangeDetails(
-          tx,
-          r.baseUrl,
-        );
-        const retryRecord = await tx.operationRetries.get(
-          TaskIdentifiers.forExchangeUpdate(r),
-        );
-        if (exchangeDetails) {
-          exchanges.push(
-            makeExchangeListItem(r, exchangeDetails, retryRecord?.lastError),
-          );
-        }
-      }
-    });
+  const possibleExchangesResp = await listExchanges(ws);
 
   return {
     amount: Amounts.stringify(info.amount),
     defaultExchangeBaseUrl: info.suggestedExchange,
-    possibleExchanges: exchanges,
+    possibleExchanges: possibleExchangesResp.exchanges,
   };
 }
 
@@ -2005,7 +1980,7 @@ export async function getFundingPaytoUris(
 ): Promise<string[]> {
   const withdrawalGroup = await tx.withdrawalGroups.get(withdrawalGroupId);
   checkDbInvariant(!!withdrawalGroup);
-  const exchangeDetails = await getExchangeDetails(
+  const exchangeDetails = await getExchangeWireDetailsInTx(
     tx,
     withdrawalGroup.exchangeBaseUrl,
   );
@@ -2385,7 +2360,7 @@ export async function 
internalPrepareCreateWithdrawalGroup(
     wgInfo: args.wgInfo,
   };
 
-  const exchangeInfo = await fetchFreshExchange(ws, canonExchange);
+  await fetchFreshExchange(ws, canonExchange);
   const transactionId = constructTransactionIdentifier({
     tag: TransactionType.Withdrawal,
     withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
@@ -2404,6 +2379,13 @@ export async function 
internalPrepareCreateWithdrawalGroup(
 export interface PerformCreateWithdrawalGroupResult {
   withdrawalGroup: WithdrawalGroupRecord;
   transitionInfo: TransitionInfo | undefined;
+
+  /**
+   * Notification for the exchange state transition.
+   *
+   * Should be emitted after the transaction has succeeded.
+   */
+  exchangeNotif: WalletNotification | undefined;
 }
 
 export async function internalPerformCreateWithdrawalGroup(
@@ -2417,7 +2399,11 @@ export async function 
internalPerformCreateWithdrawalGroup(
 ): Promise<PerformCreateWithdrawalGroupResult> {
   const { withdrawalGroup } = prep;
   if (!prep.creationInfo) {
-    return { withdrawalGroup, transitionInfo: undefined };
+    return {
+      withdrawalGroup,
+      transitionInfo: undefined,
+      exchangeNotif: undefined,
+    };
   }
   await tx.withdrawalGroups.add(withdrawalGroup);
   await tx.reserves.put({
@@ -2428,7 +2414,6 @@ export async function 
internalPerformCreateWithdrawalGroup(
   const exchange = await tx.exchanges.get(withdrawalGroup.exchangeBaseUrl);
   if (exchange) {
     exchange.lastWithdrawal = 
timestampPreciseToDb(TalerPreciseTimestamp.now());
-    exchange.entryStatus = ExchangeEntryDbRecordStatus.Used;
     await tx.exchanges.put(exchange);
   }
 
@@ -2442,7 +2427,17 @@ export async function 
internalPerformCreateWithdrawalGroup(
     newTxState,
   };
 
-  return { withdrawalGroup, transitionInfo };
+  const exchangeUsedRes = await markExchangeUsed(
+    ws,
+    tx,
+    prep.withdrawalGroup.exchangeBaseUrl,
+  );
+
+  return {
+    withdrawalGroup,
+    transitionInfo,
+    exchangeNotif: exchangeUsedRes.notif,
+  };
 }
 
 /**
@@ -2481,6 +2476,9 @@ export async function internalCreateWithdrawalGroup(
     .runReadWrite(async (tx) => {
       return await internalPerformCreateWithdrawalGroup(ws, tx, prep);
     });
+  if (res.exchangeNotif) {
+    ws.notify(res.exchangeNotif);
+  }
   notifyTransition(ws, transactionId, res.transitionInfo);
   return res.withdrawalGroup;
 }
@@ -2535,10 +2533,7 @@ export async function acceptWithdrawalFromUri(
     withdrawInfo.wireTypes,
   );
 
-  const exchange = await ws.exchangeOps.fetchFreshExchange(
-    ws,
-    selectedExchange,
-  );
+  const exchange = await fetchFreshExchange(ws, selectedExchange);
 
   const withdrawalAccountList = await fetchWithdrawalAccountInfo(ws, {
     exchange,
@@ -2710,7 +2705,7 @@ export async function createManualWithdrawal(
 ): Promise<AcceptManualWithdrawalResult> {
   const { exchangeBaseUrl } = req;
   const amount = Amounts.parseOrThrow(req.amount);
-  const exchange = await ws.exchangeOps.fetchFreshExchange(ws, 
exchangeBaseUrl);
+  const exchange = await fetchFreshExchange(ws, exchangeBaseUrl);
 
   if (exchange.currency != amount.currency) {
     throw Error(
diff --git a/packages/taler-wallet-core/src/util/coinSelection.ts 
b/packages/taler-wallet-core/src/util/coinSelection.ts
index e3fbffe98..f24184609 100644
--- a/packages/taler-wallet-core/src/util/coinSelection.ts
+++ b/packages/taler-wallet-core/src/util/coinSelection.ts
@@ -57,7 +57,7 @@ import {
 import { DenominationRecord } from "../db.js";
 import {
   getAutoRefreshExecuteThreshold,
-  getExchangeDetails,
+  getExchangeWireDetailsInTx,
   isWithdrawableDenom,
   WalletDbReadOnlyTransaction,
 } from "../index.js";
@@ -615,7 +615,10 @@ async function selectPayMerchantCandidates(
       const exchanges = await tx.exchanges.iter().toArray();
       const wfPerExchange: Record<string, AmountJson> = {};
       for (const exchange of exchanges) {
-        const exchangeDetails = await getExchangeDetails(tx, exchange.baseUrl);
+        const exchangeDetails = await getExchangeWireDetailsInTx(
+          tx,
+          exchange.baseUrl,
+        );
         // 1.- exchange has same currency
         if (exchangeDetails?.currency !== req.contractTermsAmount.currency) {
           continue;
diff --git a/packages/taler-wallet-core/src/util/instructedAmountConversion.ts 
b/packages/taler-wallet-core/src/util/instructedAmountConversion.ts
index 4365e6d32..caa3fdca5 100644
--- a/packages/taler-wallet-core/src/util/instructedAmountConversion.ts
+++ b/packages/taler-wallet-core/src/util/instructedAmountConversion.ts
@@ -33,7 +33,7 @@ import {
 import {
   DenominationRecord,
   InternalWalletState,
-  getExchangeDetails,
+  getExchangeWireDetailsInTx,
   timestampProtocolFromDb,
 } from "../index.js";
 import { CoinInfo } from "./coinSelection.js";
@@ -61,8 +61,8 @@ function getOperationType(txType: TransactionType): 
OperationType {
     txType === TransactionType.Withdrawal
       ? OperationType.Credit
       : txType === TransactionType.Deposit
-      ? OperationType.Debit
-      : undefined;
+        ? OperationType.Debit
+        : undefined;
   if (!operationType) {
     throw Error(`operation type ${txType} not yet supported`);
   }
@@ -155,7 +155,10 @@ async function getAvailableDenoms(
         filters.exchanges ?? databaseExchanges.map((e) => e.baseUrl);
 
       for (const exchangeBaseUrl of filteredExchanges) {
-        const exchangeDetails = await getExchangeDetails(tx, exchangeBaseUrl);
+        const exchangeDetails = await getExchangeWireDetailsInTx(
+          tx,
+          exchangeBaseUrl,
+        );
         // 1.- exchange has same currency
         if (exchangeDetails?.currency !== currency) {
           continue;
@@ -221,9 +224,10 @@ async function getAvailableDenoms(
         //4.- filter coins restricted by age
         if (operationType === OperationType.Credit) {
           // FIXME: Use denom groups instead of querying all denominations!
-          const ds = await tx.denominations.indexes.byExchangeBaseUrl.getAll(
-            exchangeBaseUrl,
-          );
+          const ds =
+            await tx.denominations.indexes.byExchangeBaseUrl.getAll(
+              exchangeBaseUrl,
+            );
           for (const denom of ds) {
             const expiresWithdraw = AbsoluteTime.fromProtocolTimestamp(
               timestampProtocolFromDb(denom.stampExpireWithdraw),
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index 154665c47..ff1f991dd 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -41,7 +41,6 @@ import {
   KnownBankAccounts,
   KnownBankAccountsInfo,
   Logger,
-  WithdrawalDetailsForAmount,
   MerchantUsingTemplateDetails,
   NotificationType,
   PrepareWithdrawExchangeRequest,
@@ -61,6 +60,7 @@ import {
   ValidateIbanResponse,
   WalletCoreVersion,
   WalletNotification,
+  WithdrawalDetailsForAmount,
   codecForAbortTransaction,
   codecForAcceptBankIntegratedWithdrawalRequest,
   codecForAcceptExchangeTosRequest,
@@ -157,7 +157,6 @@ import { DevExperimentHttpLib, applyDevExperiment } from 
"./dev-experiments.js";
 import {
   ActiveLongpollInfo,
   CancelFn,
-  ExchangeOperations,
   InternalWalletState,
   MerchantInfo,
   MerchantOperations,
@@ -185,10 +184,8 @@ import {
 } from "./operations/backup/index.js";
 import { getBalanceDetail, getBalances } from "./operations/balance.js";
 import {
-  TaskIdentifiers,
   TaskRunResult,
   TaskRunResultType,
-  makeExchangeListItem,
   runTaskWithErrorReporting,
 } from "./operations/common.js";
 import {
@@ -203,9 +200,9 @@ import {
   addPresetExchangeEntry,
   fetchFreshExchange,
   getExchangeDetailedInfo,
-  getExchangeDetails,
   getExchangeTos,
   listExchanges,
+  lookupExchangeByUri,
   updateExchangeFromUrlHandler,
 } from "./operations/exchanges.js";
 import { getMerchantInfo } from "./operations/merchants.js";
@@ -967,32 +964,7 @@ async function dispatchRequestInternal<Op extends 
WalletApiOperation>(
     }
     case WalletApiOperation.GetExchangeEntryByUrl: {
       const req = codecForGetExchangeEntryByUrlRequest().decode(payload);
-      const exchangeEntry = await ws.db
-        .mktx((x) => [
-          x.exchanges,
-          x.exchangeDetails,
-          x.denominations,
-          x.operationRetries,
-        ])
-        .runReadOnly(async (tx) => {
-          const exchangeRec = await tx.exchanges.get(req.exchangeBaseUrl);
-          if (!exchangeRec) {
-            throw Error("exchange not found");
-          }
-          const exchangeDetails = await getExchangeDetails(
-            tx,
-            exchangeRec.baseUrl,
-          );
-          const opRetryRecord = await tx.operationRetries.get(
-            TaskIdentifiers.forExchangeUpdate(exchangeRec),
-          );
-          return makeExchangeListItem(
-            exchangeRec,
-            exchangeDetails,
-            opRetryRecord?.lastError,
-          );
-        });
-      return exchangeEntry;
+      return lookupExchangeByUri(ws, req);
     }
     case WalletApiOperation.ListExchangesForScopedCurrency: {
       const req =
@@ -1687,11 +1659,6 @@ class InternalWalletStateImpl implements 
InternalWalletState {
 
   initCalled = false;
 
-  exchangeOps: ExchangeOperations = {
-    getExchangeDetails,
-    fetchFreshExchange,
-  };
-
   recoupOps: RecoupOperations = {
     createRecoupGroup,
   };

-- 
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]