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: use materialized


From: gnunet
Subject: [taler-wallet-core] branch master updated: wallet-core: use materialized transaction, migrate
Date: Tue, 30 Jul 2024 22:16:50 +0200

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 f61b66937 wallet-core: use materialized transaction, migrate
f61b66937 is described below

commit f61b66937c046a75728b974ce0f8ac906b3face4
Author: Florian Dold <florian@dold.me>
AuthorDate: Tue Jul 30 22:10:34 2024 +0200

    wallet-core: use materialized transaction, migrate
---
 packages/taler-wallet-core/src/db.ts              |   4 +-
 packages/taler-wallet-core/src/dev-experiments.ts |  13 +-
 packages/taler-wallet-core/src/pay-merchant.ts    |  14 +-
 packages/taler-wallet-core/src/transactions.ts    | 575 ++++------------------
 packages/taler-wallet-core/src/wallet.ts          |  46 ++
 5 files changed, 157 insertions(+), 495 deletions(-)

diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index df99f7ee6..4d562dc60 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -1353,6 +1353,7 @@ export enum ConfigRecordKey {
   // Only for testing, do not use!
   TestLoopTx = "testTxLoop",
   LastInitInfo = "lastInitInfo",
+  MaterializedTransactionsVersion = "materializedTransactionsVersion",
 }
 
 /**
@@ -1366,7 +1367,8 @@ export type ConfigRecord =
     }
   | { key: ConfigRecordKey.CurrencyDefaultsApplied; value: boolean }
   | { key: ConfigRecordKey.TestLoopTx; value: number }
-  | { key: ConfigRecordKey.LastInitInfo; value: DbProtocolTimestamp };
+  | { key: ConfigRecordKey.LastInitInfo; value: DbProtocolTimestamp }
+  | { key: ConfigRecordKey.MaterializedTransactionsVersion; value: number };
 
 export interface WalletBackupConfState {
   deviceId: string;
diff --git a/packages/taler-wallet-core/src/dev-experiments.ts 
b/packages/taler-wallet-core/src/dev-experiments.ts
index 5cb9400be..65837f207 100644
--- a/packages/taler-wallet-core/src/dev-experiments.ts
+++ b/packages/taler-wallet-core/src/dev-experiments.ts
@@ -47,6 +47,8 @@ import {
   RefreshOperationStatus,
   timestampPreciseToDb,
 } from "./db.js";
+import { DenomLossTransactionContext } from "./exchanges.js";
+import { RefreshTransactionContext } from "./refresh.js";
 import { WalletExecutionContext } from "./wallet.js";
 
 const logger = new Logger("dev-experiments.ts");
@@ -80,7 +82,7 @@ export async function applyDevExperiment(
     case "insert-pending-refresh": {
       const refreshGroupId = encodeCrock(getRandomBytes(32));
       await wex.db.runReadWriteTx(
-        { storeNames: ["refreshGroups"] },
+        { storeNames: ["refreshGroups", "transactionsMeta"] },
         async (tx) => {
           const newRg: RefreshGroupRecord = {
             currency: "TESTKUDOS",
@@ -97,6 +99,8 @@ export async function applyDevExperiment(
             infoPerExchange: {},
           };
           await tx.refreshGroups.put(newRg);
+          const ctx = new RefreshTransactionContext(wex, refreshGroupId);
+          await ctx.updateTransactionMeta(tx);
         },
       );
       wex.taskScheduler.startShepherdTask(
@@ -109,7 +113,7 @@ export async function applyDevExperiment(
     }
     case "insert-denom-loss": {
       await wex.db.runReadWriteTx(
-        { storeNames: ["denomLossEvents"] },
+        { storeNames: ["denomLossEvents", "transactionsMeta"] },
         async (tx) => {
           const eventId = encodeCrock(getRandomBytes(32));
           const newRg: DenomLossEventRecord = {
@@ -126,6 +130,11 @@ export async function applyDevExperiment(
             timestampCreated: 
timestampPreciseToDb(TalerPreciseTimestamp.now()),
           };
           await tx.denomLossEvents.put(newRg);
+          const ctx = new DenomLossTransactionContext(
+            wex,
+            newRg.denomLossEventId,
+          );
+          await ctx.updateTransactionMeta(tx);
         },
       );
       return;
diff --git a/packages/taler-wallet-core/src/pay-merchant.ts 
b/packages/taler-wallet-core/src/pay-merchant.ts
index bfcf7e072..f74e47213 100644
--- a/packages/taler-wallet-core/src/pay-merchant.ts
+++ b/packages/taler-wallet-core/src/pay-merchant.ts
@@ -265,14 +265,12 @@ export class PayMerchantTransactionContext implements 
TransactionContext {
     }));
 
     const timestamp = purchaseRec.timestampAccept;
-    checkDbInvariant(
-      !!timestamp,
-      `purchase ${purchaseRec.orderId} without accepted time`,
-    );
-    checkDbInvariant(
-      !!purchaseRec.payInfo,
-      `purchase ${purchaseRec.orderId} without payinfo`,
-    );
+    if (!timestamp) {
+      return undefined;
+    }
+    if (!purchaseRec.payInfo) {
+      return undefined;
+    }
 
     const txState = computePayMerchantTransactionState(purchaseRec);
     return {
diff --git a/packages/taler-wallet-core/src/transactions.ts 
b/packages/taler-wallet-core/src/transactions.ts
index cd902bfb6..e314c0e46 100644
--- a/packages/taler-wallet-core/src/transactions.ts
+++ b/packages/taler-wallet-core/src/transactions.ts
@@ -17,7 +17,7 @@
 /**
  * Imports.
  */
-import { GlobalIDB } from "@gnu-taler/idb-bridge";
+import { GlobalIDB, IDBKeyRange } from "@gnu-taler/idb-bridge";
 import {
   AbsoluteTime,
   Amounts,
@@ -30,7 +30,6 @@ import {
   TransactionByIdRequest,
   TransactionIdStr,
   TransactionMajorState,
-  TransactionRecordFilter,
   TransactionsRequest,
   TransactionsResponse,
   TransactionState,
@@ -39,28 +38,13 @@ import {
 import {
   constructTaskIdentifier,
   PendingTaskType,
-  TaskIdentifiers,
   TaskIdStr,
   TransactionContext,
 } from "./common.js";
 import {
-  DenomLossEventRecord,
-  DepositGroupRecord,
   OPERATION_STATUS_NONFINAL_FIRST,
   OPERATION_STATUS_NONFINAL_LAST,
-  PeerPullCreditRecord,
-  PeerPullDebitRecordStatus,
-  PeerPullPaymentIncomingRecord,
-  PeerPushCreditStatus,
-  PeerPushDebitRecord,
-  PeerPushPaymentIncomingRecord,
-  PurchaseRecord,
-  RefreshGroupRecord,
-  RefreshOperationStatus,
-  RefundGroupRecord,
-  WalletDbReadOnlyTransaction,
-  WithdrawalGroupRecord,
-  WithdrawalRecordType,
+  WalletDbAllStoresReadWriteTransaction,
 } from "./db.js";
 import { DepositTransactionContext } from "./deposits.js";
 import { DenomLossTransactionContext } from "./exchanges.js";
@@ -116,22 +100,6 @@ function shouldSkipCurrency(
   return false;
 }
 
-function shouldSkipSearch(
-  transactionsRequest: TransactionsRequest | undefined,
-  fields: string[],
-): boolean {
-  if (!transactionsRequest?.search) {
-    return false;
-  }
-  const needle = transactionsRequest.search.trim();
-  for (const f of fields) {
-    if (f.indexOf(needle) >= 0) {
-      return false;
-    }
-  }
-  return true;
-}
-
 /**
  * Fallback order of transactions that have the same timestamp.
  */
@@ -204,288 +172,44 @@ export async function getTransactions(
 ): Promise<TransactionsResponse> {
   const transactions: Transaction[] = [];
 
-  const filter: TransactionRecordFilter = {};
-  if (transactionsRequest?.filterByState) {
-    filter.onlyState = transactionsRequest.filterByState;
+  let keyRange: IDBKeyRange | undefined = undefined;
+
+  if (transactionsRequest?.filterByState === "nonfinal") {
+    keyRange = GlobalIDB.KeyRange.bound(
+      OPERATION_STATUS_NONFINAL_FIRST,
+      OPERATION_STATUS_NONFINAL_LAST,
+    );
   }
 
   await wex.db.runAllStoresReadOnlyTx({}, async (tx) => {
-    await iterRecordsForPeerPushDebit(tx, filter, async (pi) => {
-      const amount = Amounts.parseOrThrow(pi.amount);
-      const exchangesInTx = [pi.exchangeBaseUrl];
-      if (
-        shouldSkipCurrency(transactionsRequest, amount.currency, exchangesInTx)
-      ) {
-        return;
-      }
-      if (shouldSkipSearch(transactionsRequest, [])) {
-        return;
-      }
-      const ctx = new PeerPushDebitTransactionContext(wex, pi.pursePub);
-      const txDetails = await ctx.lookupFullTransaction(tx);
-      if (txDetails) {
-        transactions.push(txDetails);
-      }
-    });
-
-    await iterRecordsForPeerPullDebit(tx, filter, async (pi) => {
-      const amount = Amounts.parseOrThrow(pi.amount);
-      const exchangesInTx = [pi.exchangeBaseUrl];
-      if (
-        shouldSkipCurrency(transactionsRequest, amount.currency, exchangesInTx)
-      ) {
-        return;
-      }
-      if (shouldSkipSearch(transactionsRequest, [])) {
-        return;
-      }
-      if (
-        pi.status !== PeerPullDebitRecordStatus.PendingDeposit &&
-        pi.status !== PeerPullDebitRecordStatus.Done
-      ) {
-        // FIXME: Why?!
-        return;
-      }
-
-      const ctx = new PeerPullDebitTransactionContext(wex, pi.peerPullDebitId);
-      const txDetails = await ctx.lookupFullTransaction(tx);
-      if (txDetails) {
-        transactions.push(txDetails);
-      }
-    });
-
-    await iterRecordsForPeerPushCredit(tx, filter, async (pi) => {
-      if (!pi.currency) {
-        // Legacy transaction
-        return;
-      }
-      const exchangesInTx = [pi.exchangeBaseUrl];
-      if (shouldSkipCurrency(transactionsRequest, pi.currency, exchangesInTx)) 
{
-        return;
-      }
-      if (shouldSkipSearch(transactionsRequest, [])) {
-        return;
-      }
-      if (pi.status === PeerPushCreditStatus.DialogProposed) {
-        // We don't report proposed push credit transactions, user needs
-        // to scan URI again and confirm to see it.
-        return;
-      }
-
-      const ctx = new PeerPushCreditTransactionContext(
-        wex,
-        pi.peerPushCreditId,
-      );
-      const txDetails = await ctx.lookupFullTransaction(tx);
-      if (txDetails) {
-        transactions.push(txDetails);
-      }
-    });
-
-    await iterRecordsForPeerPullCredit(tx, filter, async (pi) => {
-      const currency = Amounts.currencyOf(pi.amount);
-      const exchangesInTx = [pi.exchangeBaseUrl];
-      if (shouldSkipCurrency(transactionsRequest, currency, exchangesInTx)) {
-        return;
-      }
-      if (shouldSkipSearch(transactionsRequest, [])) {
-        return;
-      }
-
-      const ctx = new PeerPullCreditTransactionContext(wex, pi.pursePub);
-      const txDetails = await ctx.lookupFullTransaction(tx);
-      if (txDetails) {
-        transactions.push(txDetails);
-      }
-    });
-
-    await iterRecordsForRefund(tx, filter, async (refundGroup) => {
-      const currency = Amounts.currencyOf(refundGroup.amountRaw);
-
-      const exchangesInTx: string[] = [];
-      const p = await tx.purchases.get(refundGroup.proposalId);
-      if (!p || !p.payInfo || !p.payInfo.payCoinSelection) {
-        //refund with no payment
-        return;
-      }
-
-      // FIXME: This is very slow, should become obsolete with materialized 
transactions.
-      for (const cp of p.payInfo.payCoinSelection.coinPubs) {
-        const c = await tx.coins.get(cp);
-        if (c?.exchangeBaseUrl) {
-          exchangesInTx.push(c.exchangeBaseUrl);
-        }
-      }
-
-      if (shouldSkipCurrency(transactionsRequest, currency, exchangesInTx)) {
-        return;
-      }
-
-      const ctx = new RefundTransactionContext(wex, refundGroup.refundGroupId);
-      const txDetails = await ctx.lookupFullTransaction(tx);
-      if (txDetails) {
-        transactions.push(txDetails);
-      }
-    });
-
-    await iterRecordsForRefresh(tx, filter, async (rg) => {
-      const exchangesInTx = rg.infoPerExchange
-        ? Object.keys(rg.infoPerExchange)
-        : [];
-      if (shouldSkipCurrency(transactionsRequest, rg.currency, exchangesInTx)) 
{
-        return;
-      }
-      let required = false;
-      const opId = TaskIdentifiers.forRefresh(rg);
-      if (transactionsRequest?.includeRefreshes) {
-        required = true;
-      } else if (rg.operationStatus !== RefreshOperationStatus.Finished) {
-        const ort = await tx.operationRetries.get(opId);
-        if (ort) {
-          required = true;
-        }
-      }
-      if (required) {
-        const ctx = new RefreshTransactionContext(wex, rg.refreshGroupId);
-        const txDetails = await ctx.lookupFullTransaction(tx);
-        if (txDetails) {
-          transactions.push(txDetails);
-        }
-      }
-    });
-
-    await iterRecordsForWithdrawal(tx, filter, async (wsr) => {
-      if (
-        wsr.rawWithdrawalAmount === undefined ||
-        wsr.exchangeBaseUrl == undefined
-      ) {
-        // skip prepared withdrawals which has not been confirmed
-        return;
-      }
-      const exchangesInTx = [wsr.exchangeBaseUrl];
+    const allMetaTransactions =
+      await tx.transactionsMeta.indexes.byStatus.getAll(keyRange);
+    for (const metaTx of allMetaTransactions) {
       if (
         shouldSkipCurrency(
           transactionsRequest,
-          Amounts.currencyOf(wsr.rawWithdrawalAmount),
-          exchangesInTx,
+          metaTx.currency,
+          metaTx.exchanges,
         )
       ) {
-        return;
-      }
-
-      if (shouldSkipSearch(transactionsRequest, [])) {
-        return;
-      }
-
-      switch (wsr.wgInfo.withdrawalType) {
-        case WithdrawalRecordType.PeerPullCredit:
-          // Will be reported by the corresponding p2p transaction.
-          // FIXME: If this is an orphan withdrawal, still report it as a 
withdrawal!
-          // FIXME: Still report if requested with verbose option?
-          return;
-        case WithdrawalRecordType.PeerPushCredit:
-          // Will be reported by the corresponding p2p transaction.
-          // FIXME: If this is an orphan withdrawal, still report it as a 
withdrawal!
-          // FIXME: Still report if requested with verbose option?
-          return;
-        case WithdrawalRecordType.BankIntegrated:
-        case WithdrawalRecordType.BankManual: {
-          const ctx = new WithdrawTransactionContext(
-            wex,
-            wsr.withdrawalGroupId,
-          );
-          const dbTxn = await ctx.lookupFullTransaction(tx);
-          if (!dbTxn) {
-            return;
-          }
-          transactions.push(dbTxn);
-          return;
-        }
-        case WithdrawalRecordType.Recoup:
-          // FIXME: Do we also report a transaction here?
-          return;
-      }
-    });
-
-    await iterRecordsForDenomLoss(tx, filter, async (rec) => {
-      const amount = Amounts.parseOrThrow(rec.amount);
-      const exchangesInTx = [rec.exchangeBaseUrl];
-      if (
-        shouldSkipCurrency(transactionsRequest, amount.currency, exchangesInTx)
-      ) {
-        return;
-      }
-      const ctx = new DenomLossTransactionContext(wex, rec.denomLossEventId);
-      const txDetails = await ctx.lookupFullTransaction(tx);
-      if (txDetails) {
-        transactions.push(txDetails);
-      }
-    });
-
-    await iterRecordsForDeposit(tx, filter, async (dg) => {
-      const amount = Amounts.parseOrThrow(dg.amount);
-      const exchangesInTx = dg.infoPerExchange
-        ? Object.keys(dg.infoPerExchange)
-        : [];
-      if (
-        shouldSkipCurrency(transactionsRequest, amount.currency, exchangesInTx)
-      ) {
-        return;
-      }
-
-      const ctx = new DepositTransactionContext(wex, dg.depositGroupId);
-      const txDetails = await ctx.lookupFullTransaction(tx);
-      if (txDetails) {
-        transactions.push(txDetails);
-      }
-    });
-
-    await iterRecordsForPurchase(tx, filter, async (purchase) => {
-      const download = purchase.download;
-      if (!download) {
-        return;
-      }
-      if (!purchase.payInfo) {
-        return;
-      }
-
-      const exchangesInTx: string[] = [];
-      for (const cp of purchase.payInfo.payCoinSelection?.coinPubs ?? []) {
-        const c = await tx.coins.get(cp);
-        if (c?.exchangeBaseUrl) {
-          exchangesInTx.push(c.exchangeBaseUrl);
-        }
+        continue;
       }
 
+      const parsedTx = parseTransactionIdentifier(metaTx.transactionId);
       if (
-        shouldSkipCurrency(
-          transactionsRequest,
-          download.currency,
-          exchangesInTx,
-        )
+        parsedTx?.tag === TransactionType.Refresh &&
+        !transactionsRequest?.includeRefreshes
       ) {
-        return;
-      }
-      const contractTermsRecord = await tx.contractTerms.get(
-        download.contractTermsHash,
-      );
-      if (!contractTermsRecord) {
-        return;
-      }
-      if (
-        shouldSkipSearch(transactionsRequest, [
-          contractTermsRecord?.contractTermsRaw?.summary || "",
-        ])
-      ) {
-        return;
+        continue;
       }
 
-      const ctx = new PayMerchantTransactionContext(wex, purchase.proposalId);
+      const ctx = await getContextForTransaction(wex, metaTx.transactionId);
       const txDetails = await ctx.lookupFullTransaction(tx);
-      if (txDetails) {
-        transactions.push(txDetails);
+      if (!txDetails) {
+        continue;
       }
-    });
+      transactions.push(txDetails);
+    }
   });
 
   // One-off checks, because of a bug where the wallet previously
@@ -542,6 +266,73 @@ export async function getTransactions(
   return { transactions: [...txPending, ...txNotPending] };
 }
 
+/**
+ * Re-create materialized transactions from scratch.
+ *
+ * Used for migrations.
+ */
+export async function rematerializeTransactions(
+  wex: WalletExecutionContext,
+  tx: WalletDbAllStoresReadWriteTransaction,
+): Promise<void> {
+  logger.info("re-materializing transactions");
+
+  const allTxMeta = await tx.transactionsMeta.getAll();
+  for (const txMeta of allTxMeta) {
+    await tx.transactionsMeta.delete(txMeta.transactionId);
+  }
+
+  await tx.peerPushDebit.iter().forEachAsync(async (x) => {
+    const ctx = new PeerPushDebitTransactionContext(wex, x.pursePub);
+    await ctx.updateTransactionMeta(tx);
+  });
+
+  await tx.peerPushCredit.iter().forEachAsync(async (x) => {
+    const ctx = new PeerPushCreditTransactionContext(wex, x.peerPushCreditId);
+    await ctx.updateTransactionMeta(tx);
+  });
+
+  await tx.peerPullCredit.iter().forEachAsync(async (x) => {
+    const ctx = new PeerPullCreditTransactionContext(wex, x.pursePub);
+    await ctx.updateTransactionMeta(tx);
+  });
+
+  await tx.peerPullDebit.iter().forEachAsync(async (x) => {
+    const ctx = new PeerPullDebitTransactionContext(wex, x.peerPullDebitId);
+    await ctx.updateTransactionMeta(tx);
+  });
+
+  await tx.refundGroups.iter().forEachAsync(async (x) => {
+    const ctx = new RefundTransactionContext(wex, x.refundGroupId);
+    await ctx.updateTransactionMeta(tx);
+  });
+
+  await tx.refreshGroups.iter().forEachAsync(async (x) => {
+    const ctx = new RefreshTransactionContext(wex, x.refreshGroupId);
+    await ctx.updateTransactionMeta(tx);
+  });
+
+  await tx.withdrawalGroups.iter().forEachAsync(async (x) => {
+    const ctx = new WithdrawTransactionContext(wex, x.withdrawalGroupId);
+    await ctx.updateTransactionMeta(tx);
+  });
+
+  await tx.denomLossEvents.iter().forEachAsync(async (x) => {
+    const ctx = new DenomLossTransactionContext(wex, x.denomLossEventId);
+    await ctx.updateTransactionMeta(tx);
+  });
+
+  await tx.depositGroups.iter().forEachAsync(async (x) => {
+    const ctx = new DepositTransactionContext(wex, x.depositGroupId);
+    await ctx.updateTransactionMeta(tx);
+  });
+
+  await tx.purchases.iter().forEachAsync(async (x) => {
+    const ctx = new PayMerchantTransactionContext(wex, x.proposalId);
+    await ctx.updateTransactionMeta(tx);
+  });
+}
+
 export type ParsedTransactionIdentifier =
   | { tag: TransactionType.Deposit; depositGroupId: string }
   | { tag: TransactionType.Payment; proposalId: string }
@@ -873,187 +664,3 @@ export function notifyTransition(
     });
   }
 }
-
-/**
- * Iterate refresh records based on a filter.
- */
-async function iterRecordsForRefresh(
-  tx: WalletDbReadOnlyTransaction<["refreshGroups"]>,
-  filter: TransactionRecordFilter,
-  f: (r: RefreshGroupRecord) => Promise<void>,
-): Promise<void> {
-  let refreshGroups: RefreshGroupRecord[];
-  if (filter.onlyState === "nonfinal") {
-    const keyRange = GlobalIDB.KeyRange.bound(
-      RefreshOperationStatus.Pending,
-      RefreshOperationStatus.Suspended,
-    );
-    refreshGroups = await tx.refreshGroups.indexes.byStatus.getAll(keyRange);
-  } else {
-    refreshGroups = await tx.refreshGroups.indexes.byStatus.getAll();
-  }
-
-  for (const r of refreshGroups) {
-    await f(r);
-  }
-}
-
-async function iterRecordsForWithdrawal(
-  tx: WalletDbReadOnlyTransaction<["withdrawalGroups"]>,
-  filter: TransactionRecordFilter,
-  f: (r: WithdrawalGroupRecord) => Promise<void>,
-): Promise<void> {
-  let withdrawalGroupRecords: WithdrawalGroupRecord[];
-  if (filter.onlyState === "nonfinal") {
-    const keyRange = GlobalIDB.KeyRange.bound(
-      OPERATION_STATUS_NONFINAL_FIRST,
-      OPERATION_STATUS_NONFINAL_LAST,
-    );
-    withdrawalGroupRecords =
-      await tx.withdrawalGroups.indexes.byStatus.getAll(keyRange);
-  } else {
-    withdrawalGroupRecords =
-      await tx.withdrawalGroups.indexes.byStatus.getAll();
-  }
-  for (const wgr of withdrawalGroupRecords) {
-    await f(wgr);
-  }
-}
-
-async function iterRecordsForDeposit(
-  tx: WalletDbReadOnlyTransaction<["depositGroups"]>,
-  filter: TransactionRecordFilter,
-  f: (r: DepositGroupRecord) => Promise<void>,
-): Promise<void> {
-  let dgs: DepositGroupRecord[];
-  if (filter.onlyState === "nonfinal") {
-    const keyRange = GlobalIDB.KeyRange.bound(
-      OPERATION_STATUS_NONFINAL_FIRST,
-      OPERATION_STATUS_NONFINAL_LAST,
-    );
-    dgs = await tx.depositGroups.indexes.byStatus.getAll(keyRange);
-  } else {
-    dgs = await tx.depositGroups.indexes.byStatus.getAll();
-  }
-
-  for (const dg of dgs) {
-    await f(dg);
-  }
-}
-
-async function iterRecordsForDenomLoss(
-  tx: WalletDbReadOnlyTransaction<["denomLossEvents"]>,
-  filter: TransactionRecordFilter,
-  f: (r: DenomLossEventRecord) => Promise<void>,
-): Promise<void> {
-  let dgs: DenomLossEventRecord[];
-  if (filter.onlyState === "nonfinal") {
-    const keyRange = GlobalIDB.KeyRange.bound(
-      OPERATION_STATUS_NONFINAL_FIRST,
-      OPERATION_STATUS_NONFINAL_LAST,
-    );
-    dgs = await tx.denomLossEvents.indexes.byStatus.getAll(keyRange);
-  } else {
-    dgs = await tx.denomLossEvents.indexes.byStatus.getAll();
-  }
-
-  for (const dg of dgs) {
-    await f(dg);
-  }
-}
-
-async function iterRecordsForRefund(
-  tx: WalletDbReadOnlyTransaction<["refundGroups"]>,
-  filter: TransactionRecordFilter,
-  f: (r: RefundGroupRecord) => Promise<void>,
-): Promise<void> {
-  if (filter.onlyState === "nonfinal") {
-    const keyRange = GlobalIDB.KeyRange.bound(
-      OPERATION_STATUS_NONFINAL_FIRST,
-      OPERATION_STATUS_NONFINAL_LAST,
-    );
-    await tx.refundGroups.indexes.byStatus.iter(keyRange).forEachAsync(f);
-  } else {
-    await tx.refundGroups.iter().forEachAsync(f);
-  }
-}
-
-async function iterRecordsForPurchase(
-  tx: WalletDbReadOnlyTransaction<["purchases"]>,
-  filter: TransactionRecordFilter,
-  f: (r: PurchaseRecord) => Promise<void>,
-): Promise<void> {
-  if (filter.onlyState === "nonfinal") {
-    const keyRange = GlobalIDB.KeyRange.bound(
-      OPERATION_STATUS_NONFINAL_FIRST,
-      OPERATION_STATUS_NONFINAL_LAST,
-    );
-    await tx.purchases.indexes.byStatus.iter(keyRange).forEachAsync(f);
-  } else {
-    await tx.purchases.indexes.byStatus.iter().forEachAsync(f);
-  }
-}
-
-async function iterRecordsForPeerPullCredit(
-  tx: WalletDbReadOnlyTransaction<["peerPullCredit"]>,
-  filter: TransactionRecordFilter,
-  f: (r: PeerPullCreditRecord) => Promise<void>,
-): Promise<void> {
-  if (filter.onlyState === "nonfinal") {
-    const keyRange = GlobalIDB.KeyRange.bound(
-      OPERATION_STATUS_NONFINAL_FIRST,
-      OPERATION_STATUS_NONFINAL_LAST,
-    );
-    await tx.peerPullCredit.indexes.byStatus.iter(keyRange).forEachAsync(f);
-  } else {
-    await tx.peerPullCredit.indexes.byStatus.iter().forEachAsync(f);
-  }
-}
-
-async function iterRecordsForPeerPullDebit(
-  tx: WalletDbReadOnlyTransaction<["peerPullDebit"]>,
-  filter: TransactionRecordFilter,
-  f: (r: PeerPullPaymentIncomingRecord) => Promise<void>,
-): Promise<void> {
-  if (filter.onlyState === "nonfinal") {
-    const keyRange = GlobalIDB.KeyRange.bound(
-      OPERATION_STATUS_NONFINAL_FIRST,
-      OPERATION_STATUS_NONFINAL_LAST,
-    );
-    await tx.peerPullDebit.indexes.byStatus.iter(keyRange).forEachAsync(f);
-  } else {
-    await tx.peerPullDebit.indexes.byStatus.iter().forEachAsync(f);
-  }
-}
-
-async function iterRecordsForPeerPushDebit(
-  tx: WalletDbReadOnlyTransaction<["peerPushDebit"]>,
-  filter: TransactionRecordFilter,
-  f: (r: PeerPushDebitRecord) => Promise<void>,
-): Promise<void> {
-  if (filter.onlyState === "nonfinal") {
-    const keyRange = GlobalIDB.KeyRange.bound(
-      OPERATION_STATUS_NONFINAL_FIRST,
-      OPERATION_STATUS_NONFINAL_LAST,
-    );
-    await tx.peerPushDebit.indexes.byStatus.iter(keyRange).forEachAsync(f);
-  } else {
-    await tx.peerPushDebit.indexes.byStatus.iter().forEachAsync(f);
-  }
-}
-
-async function iterRecordsForPeerPushCredit(
-  tx: WalletDbReadOnlyTransaction<["peerPushCredit"]>,
-  filter: TransactionRecordFilter,
-  f: (r: PeerPushPaymentIncomingRecord) => Promise<void>,
-): Promise<void> {
-  if (filter.onlyState === "nonfinal") {
-    const keyRange = GlobalIDB.KeyRange.bound(
-      OPERATION_STATUS_NONFINAL_FIRST,
-      OPERATION_STATUS_NONFINAL_LAST,
-    );
-    await tx.peerPushCredit.indexes.byStatus.iter(keyRange).forEachAsync(f);
-  } else {
-    await tx.peerPushCredit.indexes.byStatus.iter().forEachAsync(f);
-  }
-}
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index 3164ea40d..3ef8c5657 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -336,6 +336,7 @@ import {
   getTransactionById,
   getTransactions,
   parseTransactionIdentifier,
+  rematerializeTransactions,
   restartAll as restartAllRunningTasks,
   resumeTransaction,
   retryAll,
@@ -427,6 +428,41 @@ async function fillDefaults(wex: WalletExecutionContext): 
Promise<void> {
   }
 }
 
+/**
+ * Incremented each time we want to re-materialize transactions.
+ */
+const MATERIALIZED_TRANSACTIONS_VERSION = 1;
+
+async function migrateMaterializedTransactions(
+  wex: WalletExecutionContext,
+): Promise<void> {
+  await wex.db.runAllStoresReadWriteTx({}, async (tx) => {
+    const ver = await tx.config.get("materializedTransactionsVersion");
+    if (ver) {
+      if (ver.key !== ConfigRecordKey.MaterializedTransactionsVersion) {
+        logger.error("invalid configuration 
(materializedTransactionsVersion)");
+        return;
+      }
+      if (ver.value == MATERIALIZED_TRANSACTIONS_VERSION) {
+        return;
+      }
+      if (ver.value > MATERIALIZED_TRANSACTIONS_VERSION) {
+        logger.error(
+          "database is newer than code (materializedTransactionsVersion)",
+        );
+        return;
+      }
+    }
+
+    await rematerializeTransactions(wex, tx);
+
+    await tx.config.put({
+      key: ConfigRecordKey.MaterializedTransactionsVersion,
+      value: MATERIALIZED_TRANSACTIONS_VERSION,
+    });
+  });
+}
+
 export async function getDenomInfo(
   wex: WalletExecutionContext,
   tx: WalletDbReadOnlyTransaction<["denominations"]>,
@@ -707,6 +743,9 @@ async function recoverStoredBackup(
   });
   logger.info(`backup found, now importing`);
   await importDb(wex.db.idbHandle(), bd);
+  await wex.db.runAllStoresReadWriteTx({}, async (tx) => {
+    await rematerializeTransactions(wex, tx);
+  });
   logger.info(`import done`);
 }
 
@@ -796,6 +835,9 @@ async function handleSetWalletRunConfig(
     logger.trace("filling defaults");
     await fillDefaults(wex);
   }
+
+  await migrateMaterializedTransactions(wex);
+
   const resp: InitResponse = {
     versionInfo: await handleGetVersion(wex),
   };
@@ -1894,7 +1936,11 @@ const handlers: { [T in WalletApiOperation]: 
HandlerWithValidator<T> } = {
   [WalletApiOperation.ImportDb]: {
     codec: codecForImportDbRequest(),
     handler: async (wex, req) => {
+      // FIXME: This should atomically re-materialize transactions!
       await importDb(wex.db.idbHandle(), req.dump);
+      await wex.db.runAllStoresReadWriteTx({}, async (tx) => {
+        await rematerializeTransactions(wex, tx);
+      });
       return {};
     },
   },

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