gnunet-svn
[Top][All Lists]
Advanced

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

[taler-typescript-core] branch master updated: wallet-core: allow purgin


From: Admin
Subject: [taler-typescript-core] branch master updated: wallet-core: allow purging transactions when deleting exchange, refactor tx deletion
Date: Sun, 23 Feb 2025 17:13:33 +0100

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

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

The following commit(s) were added to refs/heads/master by this push:
     new d2bc91139 wallet-core: allow purging transactions when deleting 
exchange, refactor tx deletion
d2bc91139 is described below

commit d2bc91139c49ed59799889252e4670695105bb50
Author: Florian Dold <florian@dold.me>
AuthorDate: Sun Feb 23 17:12:58 2025 +0100

    wallet-core: allow purging transactions when deleting exchange, refactor tx 
deletion
---
 packages/taler-util/src/types-taler-wallet.ts      |  11 ++
 packages/taler-wallet-core/src/db.ts               |  90 +++++++----
 packages/taler-wallet-core/src/deposits.ts         |  50 +++++--
 packages/taler-wallet-core/src/exchanges.ts        | 166 ++++++++++++++++++---
 packages/taler-wallet-core/src/pay-merchant.ts     | 107 +++++++++----
 .../taler-wallet-core/src/pay-peer-pull-credit.ts  |  70 ++++++---
 .../taler-wallet-core/src/pay-peer-pull-debit.ts   |  45 ++++--
 .../taler-wallet-core/src/pay-peer-push-credit.ts  |  71 ++++++---
 .../taler-wallet-core/src/pay-peer-push-debit.ts   |  43 ++++--
 packages/taler-wallet-core/src/recoup.ts           |  35 ++++-
 packages/taler-wallet-core/src/refresh.ts          |  50 +++++--
 packages/taler-wallet-core/src/shepherd.ts         |   1 -
 packages/taler-wallet-core/src/withdraw.ts         |  57 +++++--
 13 files changed, 603 insertions(+), 193 deletions(-)

diff --git a/packages/taler-util/src/types-taler-wallet.ts 
b/packages/taler-util/src/types-taler-wallet.ts
index 8737cf110..2eb918319 100644
--- a/packages/taler-util/src/types-taler-wallet.ts
+++ b/packages/taler-util/src/types-taler-wallet.ts
@@ -1898,13 +1898,24 @@ export interface GetExchangeResourcesResponse {
 
 export interface DeleteExchangeRequest {
   exchangeBaseUrl: string;
+
+  /**
+   * Delete the exchange even if it's in use.
+   */
   purge?: boolean;
+
+  /**
+   * Also purge *all* transactions that involve the exchange,
+   * even ones that also involve other exchanges.
+   */
+  purgeTransactions?: boolean;
 }
 
 export const codecForDeleteExchangeRequest = (): Codec<DeleteExchangeRequest> 
=>
   buildCodecForObject<DeleteExchangeRequest>()
     .property("exchangeBaseUrl", codecForCanonBaseUrl())
     .property("purge", codecOptional(codecForBoolean()))
+    .property("purgeTransactions", codecOptional(codecForBoolean()))
     .build("DeleteExchangeRequest");
 
 export interface ForceExchangeUpdateRequest {
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index fbf1e78f7..627603975 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -157,7 +157,7 @@ export const CURRENT_DB_CONFIG_KEY = "currentMainDbName";
  * backwards-compatible way or object stores and indices
  * are added.
  */
-export const WALLET_DB_MINOR_VERSION = 14;
+export const WALLET_DB_MINOR_VERSION = 15;
 
 declare const symDbProtocolTimestamp: unique symbol;
 
@@ -1077,7 +1077,7 @@ export interface RefreshGroupRecord {
    * Refund requests that might still be necessary
    * before the refresh can work.
    */
-  refundRequests: { [n: number]: ExchangeRefundRequest};
+  refundRequests: { [n: number]: ExchangeRefundRequest };
 
   timestampCreated: DbPreciseTimestamp;
 
@@ -1320,6 +1320,13 @@ export interface PurchaseRecord {
 
   payInfo: PurchasePayInfo | undefined;
 
+  /**
+   * Exchanges involved in this purchase.
+   * Used as a multiEntry index to find all purchases for
+   * an exchange.
+   */
+  exchanges?: string[];
+
   /**
    * Pending removals from pay coin selection.
    *
@@ -2253,10 +2260,14 @@ export enum ReserveRecordStatus {
  * other records to reference the reserve key pair via a small row ID.
  *
  * In the future, we might also store KYC info about a reserve here.
+ *
+ * FIXME: Should reference exchange.
  */
 export interface ReserveRecord {
   rowId?: number;
+
   reservePub: string;
+
   reservePriv: string;
 
   status?: ReserveRecordStatus;
@@ -2435,6 +2446,11 @@ export interface RefundItemRecord {
 
   status: RefundItemStatus;
 
+  /**
+   * Mandatory since DB minor version 15.
+   */
+  proposalId?: string;
+
   refundGroupId: string;
 
   /**
@@ -2748,7 +2764,11 @@ export const WalletStoresV1 = {
     describeContents<RefreshSessionRecord>({
       keyPath: ["refreshGroupId", "coinIndex"],
     }),
-    {},
+    {
+      byRefreshGroupId: describeIndex("byRefreshGroupId", "refreshGroupId", {
+        versionAdded: 15,
+      }),
+    },
   ),
   recoupGroups: describeStore(
     "recoupGroups",
@@ -2759,6 +2779,9 @@ export const WalletStoresV1 = {
       byStatus: describeIndex("byStatus", "operationStatus", {
         versionAdded: 6,
       }),
+      byExchangeBaseUrl: describeIndex("byExchangeBaseUrl", "exchangeBaseUrl", 
{
+        versionAdded: 15,
+      }),
     },
   ),
   purchases: describeStore(
@@ -2774,19 +2797,9 @@ export const WalletStoresV1 = {
         "merchantBaseUrl",
         "orderId",
       ]),
-    },
-  ),
-  // Just a tombstone at this point.
-  rewards: describeStore(
-    "rewards",
-    describeContents<any>({ keyPath: "walletRewardId" }),
-    {
-      byMerchantTipIdAndBaseUrl: describeIndex("byMerchantRewardIdAndBaseUrl", 
[
-        "merchantRewardId",
-        "merchantBaseUrl",
-      ]),
-      byStatus: describeIndex("byStatus", "status", {
-        versionAdded: 8,
+      byExchange: describeIndex("byExchange", "exchanges", {
+        versionAdded: 15,
+        multiEntry: true,
       }),
     },
   ),
@@ -2934,13 +2947,6 @@ export const WalletStoresV1 = {
       byStatus: describeIndex("byStatus", "status"),
     },
   ),
-  _obsolete_bankAccounts: describeStore(
-    "bankAccounts",
-    describeContents<any>({
-      keyPath: "uri",
-    }),
-    {},
-  ),
   bankAccountsV2: describeStore(
     "bankAccountsV2",
     describeContents<BankAccountsRecord>({
@@ -2960,13 +2966,6 @@ export const WalletStoresV1 = {
     }),
     {},
   ),
-  userAttention: describeStore(
-    "userAttention",
-    describeContents<UserAttentionRecord>({
-      keyPath: ["entityId", "info.type"],
-    }),
-    {},
-  ),
   refundGroups: describeStore(
     "refundGroups",
     describeContents<RefundGroupRecord>({
@@ -2999,7 +2998,9 @@ export const WalletStoresV1 = {
     }),
     {},
   ),
-  // Obsolete store, not used anymore
+  //
+  // Obsolete stores, not used anymore
+  //
   _obsolete_transactions: describeStoreV2({
     recordCodec: passthroughCodec<unknown>(),
     storeName: "transactions",
@@ -3015,6 +3016,33 @@ export const WalletStoresV1 = {
       }),
     },
   }),
+  _obsolete_bankAccounts: describeStore(
+    "bankAccounts",
+    describeContents<any>({
+      keyPath: "uri",
+    }),
+    {},
+  ),
+  _obsolete_rewards: describeStore(
+    "rewards",
+    describeContents<any>({ keyPath: "walletRewardId" }),
+    {
+      byMerchantTipIdAndBaseUrl: describeIndex("byMerchantRewardIdAndBaseUrl", 
[
+        "merchantRewardId",
+        "merchantBaseUrl",
+      ]),
+      byStatus: describeIndex("byStatus", "status", {
+        versionAdded: 8,
+      }),
+    },
+  ),
+  userAttention: describeStore(
+    "userAttention",
+    describeContents<UserAttentionRecord>({
+      keyPath: ["entityId", "info.type"],
+    }),
+    {},
+  ),
 };
 
 export type WalletDbStoresArr = Array<StoreNames<typeof WalletStoresV1>>;
diff --git a/packages/taler-wallet-core/src/deposits.ts 
b/packages/taler-wallet-core/src/deposits.ts
index b78dbcae2..5322b8b8f 100644
--- a/packages/taler-wallet-core/src/deposits.ts
+++ b/packages/taler-wallet-core/src/deposits.ts
@@ -42,6 +42,7 @@ import {
   KycAuthTransferInfo,
   Logger,
   MerchantContractTermsV0,
+  NotificationType,
   RefreshReason,
   ScopeInfo,
   SelectedProspectiveCoin,
@@ -59,6 +60,7 @@ import {
   TransactionState,
   TransactionType,
   URL,
+  WalletNotification,
   assertUnreachable,
   canonicalJson,
   checkDbInvariant,
@@ -86,7 +88,6 @@ import {
   PendingTaskType,
   TaskIdStr,
   TaskRunResult,
-  TombstoneTag,
   TransactionContext,
   constructTaskIdentifier,
   runWithClientCancellation,
@@ -306,24 +307,41 @@ export class DepositTransactionContext implements 
TransactionContext {
   }
 
   async deleteTransaction(): Promise<void> {
-    const depositGroupId = this.depositGroupId;
-    const ws = this.wex;
-    // FIXME: We should check first if we are in a final state
-    // where deletion is allowed.
-    await ws.db.runReadWriteTx(
-      { storeNames: ["depositGroups", "tombstones", "transactionsMeta"] },
+    const res = await this.wex.db.runReadWriteTx(
+      {
+        storeNames: ["depositGroups", "tombstones", "transactionsMeta"],
+      },
       async (tx) => {
-        const tipRecord = await tx.depositGroups.get(depositGroupId);
-        if (tipRecord) {
-          await tx.depositGroups.delete(depositGroupId);
-          await tx.tombstones.put({
-            id: TombstoneTag.DeleteDepositGroup + ":" + depositGroupId,
-          });
-          await this.updateTransactionMeta(tx);
-        }
+        return this.deleteTransactionInTx(tx);
       },
     );
-    return;
+    for (const notif of res.notifs) {
+      this.wex.ws.notify(notif);
+    }
+  }
+
+  async deleteTransactionInTx(
+    tx: WalletDbReadWriteTransaction<
+      ["depositGroups", "tombstones", "transactionsMeta"]
+    >,
+  ): Promise<{ notifs: WalletNotification[] }> {
+    const notifs: WalletNotification[] = [];
+    const rec = await tx.depositGroups.get(this.depositGroupId);
+    if (!rec) {
+      return { notifs };
+    }
+    const oldTxState = computeDepositTransactionStatus(rec);
+    await tx.depositGroups.delete(rec.depositGroupId);
+    await this.updateTransactionMeta(tx);
+    notifs.push({
+      type: NotificationType.TransactionStateTransition,
+      transactionId: this.transactionId,
+      oldTxState,
+      newTxState: {
+        major: TransactionMajorState.Deleted,
+      },
+    });
+    return { notifs };
   }
 
   async suspendTransaction(): Promise<void> {
diff --git a/packages/taler-wallet-core/src/exchanges.ts 
b/packages/taler-wallet-core/src/exchanges.ts
index 683abdb49..faa09cc63 100644
--- a/packages/taler-wallet-core/src/exchanges.ts
+++ b/packages/taler-wallet-core/src/exchanges.ts
@@ -167,9 +167,16 @@ import {
   selectBestForOverlappingDenominations,
   selectMinimumFee,
 } from "./denominations.js";
+import { DepositTransactionContext } from "./deposits.js";
+import {
+  PayMerchantTransactionContext,
+  RefundTransactionContext,
+} from "./pay-merchant.js";
+import { PeerPullCreditTransactionContext } from "./pay-peer-pull-credit.js";
+import { PeerPullDebitTransactionContext } from "./pay-peer-pull-debit.js";
 import { DbReadOnlyTransaction } from "./query.js";
-import { createRecoupGroup } from "./recoup.js";
-import { createRefreshGroup } from "./refresh.js";
+import { RecoupTransactionContext, createRecoupGroup } from "./recoup.js";
+import { RefreshTransactionContext, createRefreshGroup } from "./refresh.js";
 import {
   BalanceEffect,
   constructTransactionIdentifier,
@@ -178,6 +185,9 @@ import {
 } from "./transactions.js";
 import { WALLET_EXCHANGE_PROTOCOL_VERSION } from "./versions.js";
 import { InternalWalletState, WalletExecutionContext } from "./wallet.js";
+import { WithdrawTransactionContext } from "./withdraw.js";
+import { PeerPushCreditTransactionContext } from "./pay-peer-push-credit.js";
+import { PeerPushDebitTransactionContext } from "./pay-peer-push-debit.js";
 
 const logger = new Logger("exchanges.ts");
 
@@ -2846,8 +2856,11 @@ async function internalGetExchangeResources(
 async function purgeExchange(
   wex: WalletExecutionContext,
   tx: WalletDbAllStoresReadWriteTransaction,
-  exchangeBaseUrl: string,
-): Promise<void> {
+  exchangeRec: ExchangeEntryRecord,
+  purgeTransactions?: boolean,
+): Promise<{ notifs: WalletNotification[] }> {
+  const exchangeBaseUrl = exchangeRec.baseUrl;
+  const notifs: WalletNotification[] = [];
   const detRecs = await tx.exchangeDetails.indexes.byExchangeBaseUrl.getAll();
   // Remove all exchange detail records for that exchange
   for (const r of detRecs) {
@@ -2865,8 +2878,16 @@ async function purgeExchange(
       await tx.exchangeSignKeys.delete([r.rowId, rec.signkeyPub]);
     }
   }
-  // FIXME: Also remove records related to transactions?
+
+  const oldExchangeState = getExchangeState(exchangeRec);
+
   await tx.exchanges.delete(exchangeBaseUrl);
+  notifs.push({
+    type: NotificationType.ExchangeStateTransition,
+    oldExchangeState,
+    newExchangeState: undefined,
+    exchangeBaseUrl,
+  });
 
   {
     const coinAvailabilityRecs =
@@ -2886,6 +2907,7 @@ async function purgeExchange(
     const coinRecs = await tx.coins.indexes.byBaseUrl.getAll(exchangeBaseUrl);
     for (const rec of coinRecs) {
       await tx.coins.delete(rec.coinPub);
+      await tx.coinHistory.delete(rec.coinPub);
     }
   }
 
@@ -2897,23 +2919,125 @@ async function purgeExchange(
     }
   }
 
+  // Always delete withdrawals, even if no explicit
+  // transaction deletion was requested.
   {
     const withdrawalGroupRecs =
       await tx.withdrawalGroups.indexes.byExchangeBaseUrl.getAll(
         exchangeBaseUrl,
       );
     for (const wg of withdrawalGroupRecs) {
-      await tx.withdrawalGroups.delete(wg.withdrawalGroupId);
-      const planchets = await tx.planchets.indexes.byGroup.getAll(
-        wg.withdrawalGroupId,
-      );
-      for (const p of planchets) {
-        await tx.planchets.delete(p.coinPub);
+      const ctx = new WithdrawTransactionContext(wex, wg.withdrawalGroupId);
+      const res = await ctx.deleteTransactionInTx(tx);
+      notifs.push(...res.notifs);
+    }
+  }
+
+  if (purgeTransactions) {
+    // Remove from refreshGroups and refreshSessions
+    {
+      await tx.refreshGroups.iter().forEachAsync(async (rg) => {
+        if (rg.infoPerExchange && rg.infoPerExchange[exchangeBaseUrl] != null) 
{
+          const ctx = new RefreshTransactionContext(wex, rg.refreshGroupId);
+          const res = await ctx.deleteTransactionInTx(tx);
+          notifs.push(...res.notifs);
+        }
+      });
+    }
+    // Remove from recoupGroups
+    {
+      const recoupGroups =
+        await 
tx.recoupGroups.indexes.byExchangeBaseUrl.getAll(exchangeBaseUrl);
+      for (const rg of recoupGroups) {
+        const ctx = new RecoupTransactionContext(wex, rg.recoupGroupId);
+        const res = await ctx.deleteTransactionInTx(tx);
+        notifs.push(...res.notifs);
+      }
+    }
+    // Remove from deposits
+    {
+      // FIXME: Index would be useful here
+      await tx.depositGroups.iter().forEachAsync(async (dg) => {
+        if (dg.infoPerExchange && dg.infoPerExchange[exchangeBaseUrl]) {
+          const ctx = new DepositTransactionContext(wex, dg.depositGroupId);
+          const res = await ctx.deleteTransactionInTx(tx);
+          notifs.push(...res.notifs);
+        }
+      });
+    }
+    // Remove from purchases, refundGroups, refundItems
+    {
+      const purchases =
+        await tx.purchases.indexes.byExchange.getAll(exchangeBaseUrl);
+      for (const purch of purchases) {
+        const refunds = await tx.refundGroups.indexes.byProposalId.getAll(
+          purch.proposalId,
+        );
+        for (const r of refunds) {
+          const refundCtx = new RefundTransactionContext(wex, r.refundGroupId);
+          const res = await refundCtx.deleteTransactionInTx(tx);
+          notifs.push(...res.notifs);
+        }
+        const payCtx = new PayMerchantTransactionContext(wex, 
purch.proposalId);
+        const res = await payCtx.deleteTransactionInTx(tx);
+        notifs.push(...res.notifs);
       }
     }
+    // Remove from peerPullCredit
+    {
+      await tx.peerPullCredit.iter().forEachAsync(async (rec) => {
+        if (rec.exchangeBaseUrl === exchangeBaseUrl) {
+          const ctx = new PeerPullCreditTransactionContext(wex, rec.pursePub);
+          const res = await ctx.deleteTransactionInTx(tx);
+          notifs.push(...res.notifs);
+        }
+      });
+    }
+    // Remove from peerPullDebit
+    {
+      await tx.peerPullDebit.iter().forEachAsync(async (rec) => {
+        if (rec.exchangeBaseUrl === exchangeBaseUrl) {
+          const ctx = new PeerPullDebitTransactionContext(
+            wex,
+            rec.peerPullDebitId,
+          );
+          const res = await ctx.deleteTransactionInTx(tx);
+          notifs.push(...res.notifs);
+        }
+      });
+    }
+    // Remove from peerPushCredit
+    {
+      await tx.peerPushCredit.iter().forEachAsync(async (rec) => {
+        if (rec.exchangeBaseUrl === exchangeBaseUrl) {
+          const ctx = new PeerPushCreditTransactionContext(
+            wex,
+            rec.peerPushCreditId,
+          );
+          const res = await ctx.deleteTransactionInTx(tx);
+          notifs.push(...res.notifs);
+        }
+      });
+    }
+    // Remove from peerPushDebit
+    {
+      await tx.peerPushDebit.iter().forEachAsync(async (rec) => {
+        if (rec.exchangeBaseUrl === exchangeBaseUrl) {
+          const ctx = new PeerPushDebitTransactionContext(
+            wex,
+            rec.pursePub,
+          );
+          const res = await ctx.deleteTransactionInTx(tx);
+          notifs.push(...res.notifs);
+        }
+      });
+    }
   }
 
+  // FIXME: Is this even necessary? Each deletion should already do it.
   await rematerializeTransactions(wex, tx);
+
+  return { notifs };
 }
 
 export async function deleteExchange(
@@ -2922,28 +3046,26 @@ export async function deleteExchange(
 ): Promise<void> {
   let inUse: boolean = false;
   const exchangeBaseUrl = req.exchangeBaseUrl;
-  const notif = await wex.db.runAllStoresReadWriteTx({}, async (tx) => {
+  const notifs = await wex.db.runAllStoresReadWriteTx({}, async (tx) => {
     const exchangeRec = await tx.exchanges.get(exchangeBaseUrl);
     if (!exchangeRec) {
       // Nothing to delete!
       logger.info("no exchange found to delete");
       return;
     }
-    const oldExchangeState = getExchangeState(exchangeRec);
     const res = await internalGetExchangeResources(wex, tx, exchangeBaseUrl);
     if (res.hasResources && !req.purge) {
       inUse = true;
       return;
     }
-    await purgeExchange(wex, tx, exchangeBaseUrl);
+    const purgeRes = await purgeExchange(
+      wex,
+      tx,
+      exchangeRec,
+      req.purgeTransactions,
+    );
     wex.ws.exchangeCache.clear();
-
-    return {
-      type: NotificationType.ExchangeStateTransition,
-      oldExchangeState,
-      newExchangeState: undefined,
-      exchangeBaseUrl,
-    } satisfies WalletNotification;
+    return purgeRes.notifs;
   });
 
   if (inUse) {
@@ -2952,7 +3074,7 @@ export async function deleteExchange(
       hint: "Exchange in use.",
     });
   }
-  if (notif) {
+  for (const notif of notifs ?? []) {
     wex.ws.notify(notif);
   }
 }
diff --git a/packages/taler-wallet-core/src/pay-merchant.ts 
b/packages/taler-wallet-core/src/pay-merchant.ts
index 00c7611b4..f12265a7a 100644
--- a/packages/taler-wallet-core/src/pay-merchant.ts
+++ b/packages/taler-wallet-core/src/pay-merchant.ts
@@ -96,6 +96,7 @@ import {
   TransactionType,
   URL,
   WalletContractData,
+  WalletNotification,
 } from "@gnu-taler/taler-util";
 import {
   getHttpResponseErrorDetails,
@@ -122,7 +123,6 @@ import {
   TaskIdStr,
   TaskRunResult,
   TaskRunResultType,
-  TombstoneTag,
   TransactionContext,
   TransitionResultType,
 } from "./common.js";
@@ -214,8 +214,7 @@ export class PayMerchantTransactionContext implements 
TransactionContext {
       status: purchaseRec.purchaseStatus,
       timestamp: purchaseRec.timestamp,
       currency: purchaseRec.download?.currency,
-      // FIXME!
-      exchanges: [],
+      exchanges: purchaseRec.exchanges ?? [],
     });
   }
 
@@ -391,24 +390,41 @@ export class PayMerchantTransactionContext implements 
TransactionContext {
   }
 
   async deleteTransaction(): Promise<void> {
-    const { wex: ws, proposalId } = this;
-    await ws.db.runReadWriteTx(
-      { storeNames: ["purchases", "tombstones", "transactionsMeta"] },
+    const res = await this.wex.db.runReadWriteTx(
+      {
+        storeNames: ["purchases", "tombstones", "transactionsMeta"],
+      },
       async (tx) => {
-        let found = false;
-        const purchase = await tx.purchases.get(proposalId);
-        if (purchase) {
-          found = true;
-          await tx.purchases.delete(proposalId);
-          await this.updateTransactionMeta(tx);
-        }
-        if (found) {
-          await tx.tombstones.put({
-            id: TombstoneTag.DeletePayment + ":" + proposalId,
-          });
-        }
+        return this.deleteTransactionInTx(tx);
       },
     );
+    for (const notif of res.notifs) {
+      this.wex.ws.notify(notif);
+    }
+  }
+
+  async deleteTransactionInTx(
+    tx: WalletDbReadWriteTransaction<
+      ["purchases", "tombstones", "transactionsMeta"]
+    >,
+  ): Promise<{ notifs: WalletNotification[] }> {
+    const notifs: WalletNotification[] = [];
+    const rec = await tx.purchases.get(this.proposalId);
+    if (!rec) {
+      return { notifs };
+    }
+    const oldTxState = computePayMerchantTransactionState(rec);
+    await tx.purchases.delete(rec.proposalId);
+    await this.updateTransactionMeta(tx);
+    notifs.push({
+      type: NotificationType.TransactionStateTransition,
+      transactionId: this.transactionId,
+      oldTxState,
+      newTxState: {
+        major: TransactionMajorState.Deleted,
+      },
+    });
+    return { notifs };
   }
 
   async suspendTransaction(): Promise<void> {
@@ -671,18 +687,47 @@ export class RefundTransactionContext implements 
TransactionContext {
 
   async deleteTransaction(): Promise<void> {
     const { wex, refundGroupId, transactionId } = this;
-    await wex.db.runReadWriteTx(
-      { storeNames: ["refundGroups", "tombstones", "transactionsMeta"] },
+
+    const res = await wex.db.runReadWriteTx(
+      {
+        storeNames: [
+          "refundGroups",
+          "refundItems",
+          "tombstones",
+          "transactionsMeta",
+        ],
+      },
       async (tx) => {
-        const refundRecord = await tx.refundGroups.get(refundGroupId);
-        if (!refundRecord) {
-          return;
-        }
-        await tx.refundGroups.delete(refundGroupId);
-        await this.updateTransactionMeta(tx);
-        await tx.tombstones.put({ id: transactionId });
+        return await this.deleteTransactionInTx(tx);
       },
     );
+    for (const notif of res.notifs) {
+      this.wex.ws.notify(notif);
+    }
+  }
+
+  async deleteTransactionInTx(
+    tx: WalletDbReadWriteTransaction<
+      ["refundGroups", "refundItems", "tombstones", "transactionsMeta"]
+    >,
+  ): Promise<{ notifs: WalletNotification[] }> {
+    const notifs: WalletNotification[] = [];
+    const refundRecord = await tx.refundGroups.get(this.refundGroupId);
+    if (!refundRecord) {
+      return { notifs };
+    }
+    await tx.refundGroups.delete(this.refundGroupId);
+    await this.updateTransactionMeta(tx);
+    await tx.tombstones.put({ id: this.transactionId });
+    const items = await tx.refundItems.indexes.byRefundGroupId.getAll([
+      refundRecord.refundGroupId,
+    ]);
+    for (const item of items) {
+      if (item.id != null) {
+        await tx.refundItems.delete(item.id);
+      }
+    }
+    return { notifs };
   }
 
   suspendTransaction(): Promise<void> {
@@ -1508,6 +1553,8 @@ async function handleInsufficientFunds(
       coinPubs: res.coinSel.coins.map((x) => x.coinPub),
     };
     payInfo.payCoinSelectionUid = encodeCrock(getRandomBytes(32));
+    p.exchanges = [...new Set(res.coinSel.coins.map((x) => 
x.exchangeBaseUrl))];
+    p.exchanges.sort();
     await tx.purchases.put(p);
     await ctx.updateTransactionMeta(tx);
     await spendCoins(wex, tx, {
@@ -2275,6 +2322,12 @@ export async function confirmPay(
               ),
               coinPubs: selectCoinsResult.coinSel.coins.map((x) => x.coinPub),
             };
+            p.exchanges = [
+              ...new Set(
+                selectCoinsResult.coinSel.coins.map((x) => x.exchangeBaseUrl),
+              ),
+            ];
+            p.exchanges.sort();
             p.payInfo.payCoinSelectionUid = encodeCrock(getRandomBytes(16));
           }
           p.lastSessionId = sessionId;
diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts 
b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts
index 18981a195..b4933ce17 100644
--- a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts
+++ b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts
@@ -45,6 +45,7 @@ import {
   TransactionState,
   TransactionType,
   WalletAccountMergeFlags,
+  WalletNotification,
   assertUnreachable,
   checkDbInvariant,
   codecForAccountKycStatus,
@@ -66,7 +67,6 @@ import {
   TaskIdStr,
   TaskIdentifiers,
   TaskRunResult,
-  TombstoneTag,
   TransactionContext,
   TransitionResult,
   TransitionResultType,
@@ -110,6 +110,7 @@ import {
 } from "./transactions.js";
 import { WalletExecutionContext } from "./wallet.js";
 import {
+  WithdrawTransactionContext,
   getExchangeWithdrawalInfo,
   internalCreateWithdrawalGroup,
   waitWithdrawalFinal,
@@ -372,41 +373,62 @@ export class PeerPullCreditTransactionContext implements 
TransactionContext {
   }
 
   async deleteTransaction(): Promise<void> {
-    const { wex: ws, pursePub } = this;
-    await ws.db.runReadWriteTx(
+    const res = await this.wex.db.runReadWriteTx(
       {
         storeNames: [
           "withdrawalGroups",
           "peerPullCredit",
+          "planchets",
           "tombstones",
           "transactionsMeta",
         ],
       },
       async (tx) => {
-        const pullIni = await tx.peerPullCredit.get(pursePub);
-        if (!pullIni) {
-          return;
-        }
-        if (pullIni.withdrawalGroupId) {
-          const withdrawalGroupId = pullIni.withdrawalGroupId;
-          const withdrawalGroupRecord =
-            await tx.withdrawalGroups.get(withdrawalGroupId);
-          if (withdrawalGroupRecord) {
-            await tx.withdrawalGroups.delete(withdrawalGroupId);
-            await tx.tombstones.put({
-              id: TombstoneTag.DeleteWithdrawalGroup + ":" + withdrawalGroupId,
-            });
-          }
-        }
-        await tx.peerPullCredit.delete(pursePub);
-        await this.updateTransactionMeta(tx);
-        await tx.tombstones.put({
-          id: TombstoneTag.DeletePeerPullCredit + ":" + pursePub,
-        });
+        return this.deleteTransactionInTx(tx);
       },
     );
+    for (const notif of res.notifs) {
+      this.wex.ws.notify(notif);
+    }
+  }
 
-    return;
+  async deleteTransactionInTx(
+    tx: WalletDbReadWriteTransaction<
+      [
+        "withdrawalGroups",
+        "peerPullCredit",
+        "planchets",
+        "tombstones",
+        "transactionsMeta",
+      ]
+    >,
+  ): Promise<{ notifs: WalletNotification[] }> {
+    const notifs: WalletNotification[] = [];
+    const rec = await tx.peerPullCredit.get(this.pursePub);
+    if (!rec) {
+      return { notifs };
+    }
+    const oldTxState = computePeerPullCreditTransactionState(rec);
+    if (rec.withdrawalGroupId) {
+      const withdrawalGroupId = rec.withdrawalGroupId;
+      const withdrawalCtx = new WithdrawTransactionContext(
+        this.wex,
+        withdrawalGroupId,
+      );
+      const res = await withdrawalCtx.deleteTransactionInTx(tx);
+      notifs.push(...res.notifs);
+    }
+    await tx.peerPullCredit.delete(rec.pursePub);
+    await this.updateTransactionMeta(tx);
+    notifs.push({
+      type: NotificationType.TransactionStateTransition,
+      transactionId: this.transactionId,
+      oldTxState,
+      newTxState: {
+        major: TransactionMajorState.Deleted,
+      },
+    });
+    return { notifs };
   }
 
   async suspendTransaction(): Promise<void> {
diff --git a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts 
b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts
index 33a6a65a7..c66d21b38 100644
--- a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts
+++ b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts
@@ -32,6 +32,7 @@ import {
   ExchangePurseDeposits,
   HttpStatusCode,
   Logger,
+  NotificationType,
   ObservabilityEventType,
   PeerContractTerms,
   PreparePeerPullDebitRequest,
@@ -50,6 +51,7 @@ import {
   TransactionMinorState,
   TransactionState,
   TransactionType,
+  WalletNotification,
   assertUnreachable,
   checkDbInvariant,
   checkLogicInvariant,
@@ -194,20 +196,41 @@ export class PeerPullDebitTransactionContext implements 
TransactionContext {
   }
 
   async deleteTransaction(): Promise<void> {
-    const transactionId = this.transactionId;
-    const ws = this.wex;
-    const peerPullDebitId = this.peerPullDebitId;
-    await ws.db.runReadWriteTx(
-      { storeNames: ["peerPullDebit", "tombstones", "transactionsMeta"] },
+    const res = await this.wex.db.runReadWriteTx(
+      {
+        storeNames: ["peerPullDebit", "tombstones", "transactionsMeta"],
+      },
       async (tx) => {
-        const debit = await tx.peerPullDebit.get(peerPullDebitId);
-        if (debit) {
-          await tx.peerPullDebit.delete(peerPullDebitId);
-          await this.updateTransactionMeta(tx);
-          await tx.tombstones.put({ id: transactionId });
-        }
+        return this.deleteTransactionInTx(tx);
       },
     );
+    for (const notif of res.notifs) {
+      this.wex.ws.notify(notif);
+    }
+  }
+
+  async deleteTransactionInTx(
+    tx: WalletDbReadWriteTransaction<
+      ["peerPullDebit", "tombstones", "transactionsMeta"]
+    >,
+  ): Promise<{ notifs: WalletNotification[] }> {
+    const notifs: WalletNotification[] = [];
+    const rec = await tx.peerPullDebit.get(this.peerPullDebitId);
+    if (!rec) {
+      return { notifs };
+    }
+    const oldTxState = computePeerPullDebitTransactionState(rec);
+    await tx.peerPullDebit.delete(rec.peerPullDebitId);
+    await this.updateTransactionMeta(tx);
+    notifs.push({
+      type: NotificationType.TransactionStateTransition,
+      transactionId: this.transactionId,
+      oldTxState,
+      newTxState: {
+        major: TransactionMajorState.Deleted,
+      },
+    });
+    return { notifs };
   }
 
   async suspendTransaction(): Promise<void> {
diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts 
b/packages/taler-wallet-core/src/pay-peer-push-credit.ts
index 9fd167e4c..66a7e4cdb 100644
--- a/packages/taler-wallet-core/src/pay-peer-push-credit.ts
+++ b/packages/taler-wallet-core/src/pay-peer-push-credit.ts
@@ -39,6 +39,7 @@ import {
   TransactionState,
   TransactionType,
   WalletAccountMergeFlags,
+  WalletNotification,
   assertUnreachable,
   checkDbInvariant,
   codecForAccountKycStatus,
@@ -63,7 +64,6 @@ import {
   TaskIdStr,
   TaskIdentifiers,
   TaskRunResult,
-  TombstoneTag,
   TransactionContext,
   TransitionResult,
   TransitionResultType,
@@ -111,6 +111,7 @@ import {
 import { WalletExecutionContext } from "./wallet.js";
 import {
   PerformCreateWithdrawalGroupResult,
+  WithdrawTransactionContext,
   getExchangeWithdrawalInfo,
   internalPerformCreateWithdrawalGroup,
   internalPrepareCreateWithdrawalGroup,
@@ -348,40 +349,62 @@ export class PeerPushCreditTransactionContext implements 
TransactionContext {
   }
 
   async deleteTransaction(): Promise<void> {
-    const { wex, peerPushCreditId } = this;
-    await wex.db.runReadWriteTx(
+    const res = await this.wex.db.runReadWriteTx(
       {
         storeNames: [
           "withdrawalGroups",
+          "planchets",
           "peerPushCredit",
           "tombstones",
           "transactionsMeta",
         ],
       },
       async (tx) => {
-        const pushInc = await tx.peerPushCredit.get(peerPushCreditId);
-        if (!pushInc) {
-          return;
-        }
-        if (pushInc.withdrawalGroupId) {
-          const withdrawalGroupId = pushInc.withdrawalGroupId;
-          const withdrawalGroupRecord =
-            await tx.withdrawalGroups.get(withdrawalGroupId);
-          if (withdrawalGroupRecord) {
-            await tx.withdrawalGroups.delete(withdrawalGroupId);
-            await tx.tombstones.put({
-              id: TombstoneTag.DeleteWithdrawalGroup + ":" + withdrawalGroupId,
-            });
-          }
-        }
-        await tx.peerPushCredit.delete(peerPushCreditId);
-        await this.updateTransactionMeta(tx);
-        await tx.tombstones.put({
-          id: TombstoneTag.DeletePeerPushCredit + ":" + peerPushCreditId,
-        });
+        return this.deleteTransactionInTx(tx);
       },
     );
-    return;
+    for (const notif of res.notifs) {
+      this.wex.ws.notify(notif);
+    }
+  }
+
+  async deleteTransactionInTx(
+    tx: WalletDbReadWriteTransaction<
+      [
+        "withdrawalGroups",
+        "planchets",
+        "peerPushCredit",
+        "tombstones",
+        "transactionsMeta",
+      ]
+    >,
+  ): Promise<{ notifs: WalletNotification[] }> {
+    const notifs: WalletNotification[] = [];
+    const rec = await tx.peerPushCredit.get(this.peerPushCreditId);
+    if (!rec) {
+      return { notifs };
+    }
+    const oldTxState = computePeerPushCreditTransactionState(rec);
+    if (rec.withdrawalGroupId) {
+      const withdrawalGroupId = rec.withdrawalGroupId;
+      const withdrawalCtx = new WithdrawTransactionContext(
+        this.wex,
+        withdrawalGroupId,
+      );
+      const res = await withdrawalCtx.deleteTransactionInTx(tx);
+      notifs.push(...res.notifs);
+    }
+    await tx.peerPushCredit.delete(rec.peerPushCreditId);
+    await this.updateTransactionMeta(tx);
+    notifs.push({
+      type: NotificationType.TransactionStateTransition,
+      transactionId: this.transactionId,
+      oldTxState,
+      newTxState: {
+        major: TransactionMajorState.Deleted,
+      },
+    });
+    return { notifs };
   }
 
   async suspendTransaction(): Promise<void> {
diff --git a/packages/taler-wallet-core/src/pay-peer-push-debit.ts 
b/packages/taler-wallet-core/src/pay-peer-push-debit.ts
index c58564530..033ecf434 100644
--- a/packages/taler-wallet-core/src/pay-peer-push-debit.ts
+++ b/packages/taler-wallet-core/src/pay-peer-push-debit.ts
@@ -26,6 +26,7 @@ import {
   InitiatePeerPushDebitRequest,
   InitiatePeerPushDebitResponse,
   Logger,
+  NotificationType,
   RefreshReason,
   ScopeInfo,
   ScopeType,
@@ -43,6 +44,7 @@ import {
   TransactionMinorState,
   TransactionState,
   TransactionType,
+  WalletNotification,
   assertUnreachable,
   checkDbInvariant,
   encodeCrock,
@@ -197,18 +199,41 @@ export class PeerPushDebitTransactionContext implements 
TransactionContext {
   }
 
   async deleteTransaction(): Promise<void> {
-    const { wex, pursePub, transactionId } = this;
-    await wex.db.runReadWriteTx(
-      { storeNames: ["peerPushDebit", "tombstones", "transactionsMeta"] },
+    const res = await this.wex.db.runReadWriteTx(
+      {
+        storeNames: ["peerPushDebit", "tombstones", "transactionsMeta"],
+      },
       async (tx) => {
-        const debit = await tx.peerPushDebit.get(pursePub);
-        if (debit) {
-          await tx.peerPushDebit.delete(pursePub);
-          await this.updateTransactionMeta(tx);
-          await tx.tombstones.put({ id: transactionId });
-        }
+        return this.deleteTransactionInTx(tx);
       },
     );
+    for (const notif of res.notifs) {
+      this.wex.ws.notify(notif);
+    }
+  }
+
+  async deleteTransactionInTx(
+    tx: WalletDbReadWriteTransaction<
+      ["peerPushDebit", "tombstones", "transactionsMeta"]
+    >,
+  ): Promise<{ notifs: WalletNotification[] }> {
+    const notifs: WalletNotification[] = [];
+    const rec = await tx.peerPushDebit.get(this.pursePub);
+    if (!rec) {
+      return { notifs };
+    }
+    const oldTxState = computePeerPushDebitTransactionState(rec);
+    await tx.peerPushDebit.delete(rec.pursePub);
+    await this.updateTransactionMeta(tx);
+    notifs.push({
+      type: NotificationType.TransactionStateTransition,
+      transactionId: this.transactionId,
+      oldTxState,
+      newTxState: {
+        major: TransactionMajorState.Deleted,
+      },
+    });
+    return { notifs };
   }
 
   async suspendTransaction(): Promise<void> {
diff --git a/packages/taler-wallet-core/src/recoup.ts 
b/packages/taler-wallet-core/src/recoup.ts
index e74e49118..564255226 100644
--- a/packages/taler-wallet-core/src/recoup.ts
+++ b/packages/taler-wallet-core/src/recoup.ts
@@ -34,6 +34,7 @@ import {
   TransactionIdStr,
   TransactionType,
   URL,
+  WalletNotification,
   checkDbInvariant,
   codecForRecoupConfirmation,
   codecForReserveStatus,
@@ -532,8 +533,38 @@ export class RecoupTransactionContext implements 
TransactionContext {
     throw new Error("Method not implemented.");
   }
 
-  deleteTransaction(): Promise<void> {
-    throw new Error("Method not implemented.");
+  async deleteTransaction(): Promise<void> {
+    const res = await this.wex.db.runReadWriteTx(
+      {
+        storeNames: [
+          "recoupGroups",
+          "tombstones",
+          "exchanges",
+          "transactionsMeta",
+        ],
+      },
+      async (tx) => {
+        return this.deleteTransactionInTx(tx);
+      },
+    );
+    for (const notif of res.notifs) {
+      this.wex.ws.notify(notif);
+    }
+  }
+
+  async deleteTransactionInTx(
+    tx: WalletDbReadWriteTransaction<
+      ["recoupGroups", "tombstones", "exchanges", "transactionsMeta"]
+    >,
+  ): Promise<{ notifs: WalletNotification[] }> {
+    const notifs: WalletNotification[] = [];
+    const rec = await tx.recoupGroups.get(this.recoupGroupId);
+    if (!rec) {
+      return { notifs };
+    }
+    await tx.recoupGroups.delete(this.recoupGroupId);
+    await this.updateTransactionMeta(tx);
+    return { notifs };
   }
 
   lookupFullTransaction(
diff --git a/packages/taler-wallet-core/src/refresh.ts 
b/packages/taler-wallet-core/src/refresh.ts
index 88d7b704e..ae71ab87d 100644
--- a/packages/taler-wallet-core/src/refresh.ts
+++ b/packages/taler-wallet-core/src/refresh.ts
@@ -81,7 +81,6 @@ import {
   TaskIdStr,
   TaskRunResult,
   TaskRunResultType,
-  TombstoneTag,
   TransactionContext,
   TransitionResult,
   TransitionResultType,
@@ -291,20 +290,49 @@ export class RefreshTransactionContext implements 
TransactionContext {
   }
 
   async deleteTransaction(): Promise<void> {
-    await this.transition(
+    const res = await this.wex.db.runReadWriteTx(
       {
-        extraStores: ["tombstones"],
+        storeNames: ["refreshGroups", "refreshSessions", "tombstones"],
       },
-      async (rec, tx) => {
-        if (!rec) {
-          return TransitionResult.stay();
-        }
-        await tx.tombstones.put({
-          id: TombstoneTag.DeleteRefreshGroup + ":" + this.refreshGroupId,
-        });
-        return TransitionResult.delete();
+      async (tx) => {
+        return this.deleteTransactionInTx(tx);
       },
     );
+    for (const notif of res.notifs) {
+      this.wex.ws.notify(notif);
+    }
+  }
+
+  async deleteTransactionInTx(
+    tx: WalletDbReadWriteTransaction<
+      ["refreshGroups", "refreshSessions", "tombstones"]
+    >,
+  ): Promise<{ notifs: WalletNotification[] }> {
+    const notifs: WalletNotification[] = [];
+    const rg = await tx.refreshGroups.get(this.refreshGroupId);
+    if (!rg) {
+      logger.warn(
+        `unable to delete transaction ${this.transactionId}, not found`,
+      );
+      return { notifs };
+    }
+    const oldTxState = computeRefreshTransactionState(rg);
+    const sessions = await tx.refreshSessions.indexes.byRefreshGroupId.getAll(
+      rg.refreshGroupId,
+    );
+    for (const s of sessions) {
+      await tx.refreshSessions.delete([s.refreshGroupId, s.coinIndex]);
+    }
+    await tx.refreshGroups.delete(rg.refreshGroupId);
+    notifs.push({
+      type: NotificationType.TransactionStateTransition,
+      transactionId: this.transactionId,
+      oldTxState,
+      newTxState: {
+        major: TransactionMajorState.Deleted,
+      },
+    });
+    return { notifs };
   }
 
   async suspendTransaction(): Promise<void> {
diff --git a/packages/taler-wallet-core/src/shepherd.ts 
b/packages/taler-wallet-core/src/shepherd.ts
index 8cf88ef68..e092c2b7f 100644
--- a/packages/taler-wallet-core/src/shepherd.ts
+++ b/packages/taler-wallet-core/src/shepherd.ts
@@ -743,7 +743,6 @@ async function getTransactionState(
       "peerPullDebit",
       "peerPushDebit",
       "peerPushCredit",
-      "rewards",
       "refreshGroups",
       "denomLossEvents",
     ]
diff --git a/packages/taler-wallet-core/src/withdraw.ts 
b/packages/taler-wallet-core/src/withdraw.ts
index dc5c1f681..fa6f3a934 100644
--- a/packages/taler-wallet-core/src/withdraw.ts
+++ b/packages/taler-wallet-core/src/withdraw.ts
@@ -117,7 +117,6 @@ import {
   TaskIdStr,
   TaskRunResult,
   TaskRunResultType,
-  TombstoneTag,
   TransactionContext,
   TransitionResult,
   TransitionResultType,
@@ -560,24 +559,52 @@ export class WithdrawTransactionContext implements 
TransactionContext {
   }
 
   async deleteTransaction(): Promise<void> {
-    await this.transition(
+    const res = await this.wex.db.runReadWriteTx(
       {
-        extraStores: ["tombstones"],
-        transactionLabel: "delete-transaction-withdraw",
+        storeNames: [
+          "withdrawalGroups",
+          "planchets",
+          "tombstones",
+          "transactionsMeta",
+        ],
       },
-      async (rec, tx) => {
-        if (!rec) {
-          return TransitionResult.stay();
-        }
-        if (rec) {
-          await tx.tombstones.put({
-            id:
-              TombstoneTag.DeleteWithdrawalGroup + ":" + rec.withdrawalGroupId,
-          });
-        }
-        return TransitionResult.delete();
+      async (tx) => {
+        return this.deleteTransactionInTx(tx);
       },
     );
+    for (const notif of res.notifs) {
+      this.wex.ws.notify(notif);
+    }
+  }
+
+  async deleteTransactionInTx(
+    tx: WalletDbReadWriteTransaction<
+      ["withdrawalGroups", "planchets", "tombstones", "transactionsMeta"]
+    >,
+  ): Promise<{ notifs: WalletNotification[] }> {
+    const notifs: WalletNotification[] = [];
+    const rec = await tx.withdrawalGroups.get(this.withdrawalGroupId);
+    if (!rec) {
+      return { notifs };
+    }
+    const oldTxState = computeWithdrawalTransactionStatus(rec);
+    await tx.withdrawalGroups.delete(rec.withdrawalGroupId);
+    const planchets = await tx.planchets.indexes.byGroup.getAll(
+      rec.withdrawalGroupId,
+    );
+    for (const p of planchets) {
+      await tx.planchets.delete(p.coinPub);
+    }
+    await this.updateTransactionMeta(tx);
+    notifs.push({
+      type: NotificationType.TransactionStateTransition,
+      transactionId: this.transactionId,
+      oldTxState,
+      newTxState: {
+        major: TransactionMajorState.Deleted,
+      },
+    });
+    return { notifs };
   }
 
   async suspendTransaction(): Promise<void> {

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