[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.
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [taler-typescript-core] branch master updated: wallet-core: allow purging transactions when deleting exchange, refactor tx deletion,
Admin <=