gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 02/02: wallet-core: allow deposits with balance lock


From: gnunet
Subject: [taler-wallet-core] 02/02: wallet-core: allow deposits with balance locked behind refresh
Date: Wed, 03 Apr 2024 16:21:38 +0200

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

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

commit 65a656163797e9dd298b34ec916b982082db7f52
Author: Florian Dold <florian@dold.me>
AuthorDate: Wed Apr 3 16:21:33 2024 +0200

    wallet-core: allow deposits with balance locked behind refresh
---
 ...h-blocked.ts => test-wallet-blocked-deposit.ts} |  44 +++-
 .../src/integrationtests/testrunner.ts             |   4 +-
 packages/taler-wallet-core/src/db.ts               |   6 +-
 packages/taler-wallet-core/src/deposits.ts         | 230 +++++++++++++++------
 packages/taler-wallet-core/src/dev-experiments.ts  |  96 ++++-----
 packages/taler-wallet-core/src/transactions.ts     |  29 ++-
 6 files changed, 282 insertions(+), 127 deletions(-)

diff --git 
a/packages/taler-harness/src/integrationtests/test-wallet-refresh-blocked.ts 
b/packages/taler-harness/src/integrationtests/test-wallet-blocked-deposit.ts
similarity index 71%
rename from 
packages/taler-harness/src/integrationtests/test-wallet-refresh-blocked.ts
rename to 
packages/taler-harness/src/integrationtests/test-wallet-blocked-deposit.ts
index 4662c5110..cb9c54f1d 100644
--- a/packages/taler-harness/src/integrationtests/test-wallet-refresh-blocked.ts
+++ b/packages/taler-harness/src/integrationtests/test-wallet-blocked-deposit.ts
@@ -17,10 +17,16 @@
 /**
  * Imports.
  */
-import { AmountString, j2s } from "@gnu-taler/taler-util";
+import {
+  AmountString,
+  NotificationType,
+  TransactionMajorState,
+  TransactionMinorState,
+  j2s,
+} from "@gnu-taler/taler-util";
 import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
 import { CoinConfig } from "../harness/denomStructures.js";
-import { GlobalTestState } from "../harness/harness.js";
+import { GlobalTestState, generateRandomPayto } from "../harness/harness.js";
 import {
   createSimpleTestkudosEnvironmentV2,
   createWalletDaemonWithClient,
@@ -43,7 +49,7 @@ const coinCommon = {
 /**
  * Run test for refreshe after a payment.
  */
-export async function runWalletRefreshBlockedTest(t: GlobalTestState) {
+export async function runWalletBlockedDeposit(t: GlobalTestState) {
   // Set up test environment
 
   const coinConfigList: CoinConfig[] = [
@@ -66,6 +72,7 @@ export async function runWalletRefreshBlockedTest(t: 
GlobalTestState) {
 
   const { walletClient: w1 } = await createWalletDaemonWithClient(t, {
     name: "w1",
+    persistent: true,
     config: {
       testing: {
         devModeActive: true,
@@ -97,6 +104,8 @@ export async function runWalletRefreshBlockedTest(t: 
GlobalTestState) {
     },
   });
 
+  const userPayto = generateRandomPayto("foo");
+
   const bal = await w1.call(WalletApiOperation.GetBalances, {});
   console.log(`balance: ${j2s(bal)}`);
 
@@ -109,12 +118,35 @@ export async function runWalletRefreshBlockedTest(t: 
GlobalTestState) {
 
   const depositCheckResp = await w1.call(WalletApiOperation.PrepareDeposit, {
     amount: "TESTKUDOS:18" as AmountString,
-    depositPaytoUri: "payto://x-taler-bank/localhost/myuser",
+    depositPaytoUri: userPayto,
   });
 
   console.log(`check resp: ${j2s(depositCheckResp)}`);
 
-  // t.assertTrue(false);
+  const depositCreateResp = await w1.call(
+    WalletApiOperation.CreateDepositGroup,
+    {
+      amount: "TESTKUDOS:18" as AmountString,
+      depositPaytoUri: userPayto,
+    },
+  );
+
+  console.log(`create resp: ${j2s(depositCreateResp)}`);
+
+  const depositTrackCond = w1.waitForNotificationCond((n) => {
+    return (
+      n.type === NotificationType.TransactionStateTransition &&
+      n.transactionId === depositCreateResp.transactionId &&
+      n.newTxState.major === TransactionMajorState.Pending &&
+      n.newTxState.minor === TransactionMinorState.Track
+    );
+  });
+
+  await w1.call(WalletApiOperation.ApplyDevExperiment, {
+    devExperimentUri: "taler://dev-experiment/stop-block-refresh",
+  });
+
+  await depositTrackCond;
 }
 
-runWalletRefreshBlockedTest.suites = ["wallet"];
+runWalletBlockedDeposit.suites = ["wallet"];
diff --git a/packages/taler-harness/src/integrationtests/testrunner.ts 
b/packages/taler-harness/src/integrationtests/testrunner.ts
index 2bca91e45..063aefa43 100644
--- a/packages/taler-harness/src/integrationtests/testrunner.ts
+++ b/packages/taler-harness/src/integrationtests/testrunner.ts
@@ -101,7 +101,7 @@ import { runWalletGenDbTest } from "./test-wallet-gendb.js";
 import { runWalletInsufficientBalanceTest } from 
"./test-wallet-insufficient-balance.js";
 import { runWalletNotificationsTest } from "./test-wallet-notifications.js";
 import { runWalletObservabilityTest } from "./test-wallet-observability.js";
-import { runWalletRefreshBlockedTest } from "./test-wallet-refresh-blocked.js";
+import { runWalletBlockedDeposit } from "./test-wallet-blocked-deposit.js";
 import { runWalletRefreshTest } from "./test-wallet-refresh.js";
 import { runWalletWirefeesTest } from "./test-wallet-wirefees.js";
 import { runWallettestingTest } from "./test-wallettesting.js";
@@ -213,7 +213,7 @@ const allTests: TestMainFunction[] = [
   runWalletWirefeesTest,
   runDenomLostTest,
   runWalletDenomExpireTest,
-  runWalletRefreshBlockedTest,
+  runWalletBlockedDeposit,
 ];
 
 export interface TestRunSpec {
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index de22d78a8..7b9dfa2a2 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -1787,9 +1787,9 @@ export interface DepositGroupRecord {
 
   contractTermsHash: string;
 
-  payCoinSelection: DbCoinSelection;
+  payCoinSelection?: DbCoinSelection;
 
-  payCoinSelectionUid: string;
+  payCoinSelectionUid?: string;
 
   totalPayCost: AmountString;
 
@@ -1804,7 +1804,7 @@ export interface DepositGroupRecord {
 
   operationStatus: DepositOperationStatus;
 
-  statusPerCoin: DepositElementStatus[];
+  statusPerCoin?: DepositElementStatus[];
 
   infoPerExchange?: Record<string, DepositInfoPerExchange>;
 
diff --git a/packages/taler-wallet-core/src/deposits.ts 
b/packages/taler-wallet-core/src/deposits.ts
index 05a5d780a..5b23d8325 100644
--- a/packages/taler-wallet-core/src/deposits.ts
+++ b/packages/taler-wallet-core/src/deposits.ts
@@ -413,16 +413,28 @@ async function refundDepositGroup(
   wex: WalletExecutionContext,
   depositGroup: DepositGroupRecord,
 ): Promise<TaskRunResult> {
-  const newTxPerCoin = [...depositGroup.statusPerCoin];
+  const statusPerCoin = depositGroup.statusPerCoin;
+  const payCoinSelection = depositGroup.payCoinSelection;
+  if (!statusPerCoin) {
+    throw Error(
+      "unable to refund deposit group without coin selection (status missing)",
+    );
+  }
+  if (!payCoinSelection) {
+    throw Error(
+      "unable to refund deposit group without coin selection (selection 
missing)",
+    );
+  }
+  const newTxPerCoin = [...statusPerCoin];
   logger.info(`status per coin: ${j2s(depositGroup.statusPerCoin)}`);
-  for (let i = 0; i < depositGroup.statusPerCoin.length; i++) {
-    const st = depositGroup.statusPerCoin[i];
+  for (let i = 0; i < statusPerCoin.length; i++) {
+    const st = statusPerCoin[i];
     switch (st) {
       case DepositElementStatus.RefundFailed:
       case DepositElementStatus.RefundSuccess:
         break;
       default: {
-        const coinPub = depositGroup.payCoinSelection.coinPubs[i];
+        const coinPub = payCoinSelection.coinPubs[i];
         const coinExchange = await wex.db.runReadOnlyTx(
           ["coins"],
           async (tx) => {
@@ -431,7 +443,7 @@ async function refundDepositGroup(
             return coinRecord.exchangeBaseUrl;
           },
         );
-        const refundAmount = 
depositGroup.payCoinSelection.coinContributions[i];
+        const refundAmount = payCoinSelection.coinContributions[i];
         // We use a constant refund transaction ID, since there can
         // only be one refund.
         const rtid = 1;
@@ -503,8 +515,8 @@ async function refundDepositGroup(
       const refreshCoins: CoinRefreshRequest[] = [];
       for (let i = 0; i < newTxPerCoin.length; i++) {
         refreshCoins.push({
-          amount: depositGroup.payCoinSelection.coinContributions[i],
-          coinPub: depositGroup.payCoinSelection.coinPubs[i],
+          amount: payCoinSelection.coinContributions[i],
+          coinPub: payCoinSelection.coinPubs[i],
         });
       }
       let refreshRes: CreateRefreshGroupResult | undefined = undefined;
@@ -740,9 +752,21 @@ async function processDepositGroupPendingTrack(
   wex: WalletExecutionContext,
   depositGroup: DepositGroupRecord,
 ): Promise<TaskRunResult> {
+  const statusPerCoin = depositGroup.statusPerCoin;
+  const payCoinSelection = depositGroup.payCoinSelection;
+  if (!statusPerCoin) {
+    throw Error(
+      "unable to refund deposit group without coin selection (status missing)",
+    );
+  }
+  if (!payCoinSelection) {
+    throw Error(
+      "unable to refund deposit group without coin selection (selection 
missing)",
+    );
+  }
   const { depositGroupId } = depositGroup;
-  for (let i = 0; i < depositGroup.statusPerCoin.length; i++) {
-    const coinPub = depositGroup.payCoinSelection.coinPubs[i];
+  for (let i = 0; i < statusPerCoin.length; i++) {
+    const coinPub = payCoinSelection.coinPubs[i];
     // FIXME: Make the URL part of the coin selection?
     const exchangeBaseUrl = await wex.db.runReadWriteTx(
       ["coins"],
@@ -761,7 +785,7 @@ async function processDepositGroupPendingTrack(
         }
       | undefined;
 
-    if (depositGroup.statusPerCoin[i] !== DepositElementStatus.Wired) {
+    if (statusPerCoin[i] !== DepositElementStatus.Wired) {
       const track = await trackDeposit(
         wex,
         depositGroup,
@@ -826,6 +850,9 @@ async function processDepositGroupPendingTrack(
         if (!dg) {
           return;
         }
+        if (!dg.statusPerCoin) {
+          return;
+        }
         if (updatedTxStatus !== undefined) {
           dg.statusPerCoin[i] = updatedTxStatus;
         }
@@ -858,9 +885,12 @@ async function processDepositGroupPendingTrack(
       if (!dg) {
         return undefined;
       }
+      if (!dg.statusPerCoin) {
+        return undefined;
+      }
       const oldTxState = computeDepositTransactionStatus(dg);
-      for (let i = 0; i < depositGroup.statusPerCoin.length; i++) {
-        if (depositGroup.statusPerCoin[i] !== DepositElementStatus.Wired) {
+      for (let i = 0; i < dg.statusPerCoin.length; i++) {
+        if (dg.statusPerCoin[i] !== DepositElementStatus.Wired) {
           allWired = false;
           break;
         }
@@ -924,6 +954,87 @@ async function processDepositGroupPendingDeposit(
   // Check for cancellation before expensive operations.
   cancellationToken?.throwIfCancelled();
 
+  if (!depositGroup.payCoinSelection) {
+    logger.info("missing coin selection for deposit group, selecting now");
+    // FIXME: Consider doing the coin selection inside the txn
+    const payCoinSel = await selectPayCoins(wex, {
+      restrictExchanges: {
+        auditors: [],
+        exchanges: contractData.allowedExchanges,
+      },
+      restrictWireMethod: contractData.wireMethod,
+      contractTermsAmount: Amounts.parseOrThrow(contractData.amount),
+      depositFeeLimit: Amounts.parseOrThrow(contractData.maxDepositFee),
+      wireFeeAmortization: 1, // FIXME #8653
+      prevPayCoins: [],
+    });
+
+    switch (payCoinSel.type) {
+      case "success":
+        logger.info("coin selection success");
+        break;
+      case "failure":
+        logger.info("coin selection failure");
+        throw TalerError.fromDetail(
+          TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE,
+          {
+            insufficientBalanceDetails: payCoinSel.insufficientBalanceDetails,
+          },
+        );
+      case "prospective":
+        logger.info("coin selection prospective");
+        throw Error("insufficient balance (waiting on pending refresh)");
+      default:
+        assertUnreachable(payCoinSel);
+    }
+
+    const transitionDone = await wex.db.runReadWriteTx(
+      [
+        "depositGroups",
+        "coins",
+        "coinAvailability",
+        "refreshGroups",
+        "refreshSessions",
+        "denominations",
+      ],
+      async (tx) => {
+        const dg = await tx.depositGroups.get(depositGroupId);
+        if (!dg) {
+          return false;
+        }
+        if (dg.statusPerCoin) {
+          return false;
+        }
+        dg.payCoinSelection = {
+          coinContributions: payCoinSel.coinSel.coins.map(
+            (x) => x.contribution,
+          ),
+          coinPubs: payCoinSel.coinSel.coins.map((x) => x.coinPub),
+        };
+        dg.payCoinSelectionUid = encodeCrock(getRandomBytes(32));
+        dg.statusPerCoin = payCoinSel.coinSel.coins.map(
+          () => DepositElementStatus.DepositPending,
+        );
+        await tx.depositGroups.put(dg);
+        await spendCoins(wex, tx, {
+          allocationId: transactionId,
+          coinPubs: dg.payCoinSelection.coinPubs,
+          contributions: dg.payCoinSelection.coinContributions.map((x) =>
+            Amounts.parseOrThrow(x),
+          ),
+          refreshReason: RefreshReason.PayDeposit,
+        });
+        return true;
+      },
+    );
+
+    if (transitionDone) {
+      return TaskRunResult.progress();
+    } else {
+      return TaskRunResult.backoff();
+    }
+  }
+
   // FIXME: Cache these!
   const depositPermissions = await generateDepositPermissions(
     wex,
@@ -990,6 +1101,9 @@ async function processDepositGroupPendingDeposit(
       if (!dg) {
         return;
       }
+      if (!dg.statusPerCoin) {
+        return;
+      }
       for (const batchIndex of batchIndexes) {
         const coinStatus = dg.statusPerCoin[batchIndex];
         switch (coinStatus) {
@@ -1360,8 +1474,11 @@ export async function createDepositGroup(
     prevPayCoins: [],
   });
 
+  let coins: SelectedProspectiveCoin[] | undefined = undefined;
+
   switch (payCoinSel.type) {
     case "success":
+      coins = payCoinSel.coinSel.coins;
       break;
     case "failure":
       throw TalerError.fromDetail(
@@ -1371,17 +1488,13 @@ export async function createDepositGroup(
         },
       );
     case "prospective":
-      // FIXME: Here we need to create the deposit group without a full coin 
selection!
-      throw Error("insufficient balance (pending refresh)");
+      coins = payCoinSel.result.prospectiveCoins;
+      break;
     default:
       assertUnreachable(payCoinSel);
   }
 
-  const totalDepositCost = await getTotalPaymentCost(
-    wex,
-    currency,
-    payCoinSel.coinSel.coins,
-  );
+  const totalDepositCost = await getTotalPaymentCost(wex, currency, coins);
 
   let depositGroupId: string;
   if (req.transactionId) {
@@ -1396,34 +1509,23 @@ export async function createDepositGroup(
 
   const infoPerExchange: Record<string, DepositInfoPerExchange> = {};
 
-  await wex.db.runReadOnlyTx(["coins"], async (tx) => {
-    for (let i = 0; i < payCoinSel.coinSel.coins.length; i++) {
-      const coin = await tx.coins.get(payCoinSel.coinSel.coins[i].coinPub);
-      if (!coin) {
-        logger.error("coin not found anymore");
-        continue;
-      }
-      let depPerExchange = infoPerExchange[coin.exchangeBaseUrl];
-      if (!depPerExchange) {
-        infoPerExchange[coin.exchangeBaseUrl] = depPerExchange = {
-          amountEffective: Amounts.stringify(
-            Amounts.zeroOfAmount(totalDepositCost),
-          ),
-        };
-      }
-      const contrib = payCoinSel.coinSel.coins[i].contribution;
-      depPerExchange.amountEffective = Amounts.stringify(
-        Amounts.add(depPerExchange.amountEffective, contrib).amount,
-      );
+  for (let i = 0; i < coins.length; i++) {
+    let depPerExchange = infoPerExchange[coins[i].exchangeBaseUrl];
+    if (!depPerExchange) {
+      infoPerExchange[coins[i].exchangeBaseUrl] = depPerExchange = {
+        amountEffective: Amounts.stringify(
+          Amounts.zeroOfAmount(totalDepositCost),
+        ),
+      };
     }
-  });
+    const contrib = coins[i].contribution;
+    depPerExchange.amountEffective = Amounts.stringify(
+      Amounts.add(depPerExchange.amountEffective, contrib).amount,
+    );
+  }
 
   const counterpartyEffectiveDepositAmount =
-    await getCounterpartyEffectiveDepositAmount(
-      wex,
-      p.targetType,
-      payCoinSel.coinSel.coins,
-    );
+    await getCounterpartyEffectiveDepositAmount(wex, p.targetType, coins);
 
   const depositGroup: DepositGroupRecord = {
     contractTermsHash,
@@ -1436,14 +1538,9 @@ export async function createDepositGroup(
       AbsoluteTime.toPreciseTimestamp(now),
     ),
     timestampFinished: undefined,
-    statusPerCoin: payCoinSel.coinSel.coins.map(
-      () => DepositElementStatus.DepositPending,
-    ),
-    payCoinSelection: {
-      coinContributions: payCoinSel.coinSel.coins.map((x) => x.contribution),
-      coinPubs: payCoinSel.coinSel.coins.map((x) => x.coinPub),
-    },
-    payCoinSelectionUid: encodeCrock(getRandomBytes(32)),
+    statusPerCoin: undefined,
+    payCoinSelection: undefined,
+    payCoinSelectionUid: undefined,
     merchantPriv: merchantPair.priv,
     merchantPub: merchantPair.pub,
     totalPayCost: Amounts.stringify(totalDepositCost),
@@ -1461,6 +1558,17 @@ export async function createDepositGroup(
     infoPerExchange,
   };
 
+  if (payCoinSel.type === "success") {
+    depositGroup.payCoinSelection = {
+      coinContributions: payCoinSel.coinSel.coins.map((x) => x.contribution),
+      coinPubs: payCoinSel.coinSel.coins.map((x) => x.coinPub),
+    };
+    depositGroup.payCoinSelectionUid = encodeCrock(getRandomBytes(32));
+    depositGroup.statusPerCoin = payCoinSel.coinSel.coins.map(
+      () => DepositElementStatus.DepositPending,
+    );
+  }
+
   const ctx = new DepositTransactionContext(wex, depositGroupId);
   const transactionId = ctx.transactionId;
 
@@ -1476,14 +1584,16 @@ export async function createDepositGroup(
       "contractTerms",
     ],
     async (tx) => {
-      await spendCoins(wex, tx, {
-        allocationId: transactionId,
-        coinPubs: payCoinSel.coinSel.coins.map((x) => x.coinPub),
-        contributions: payCoinSel.coinSel.coins.map((x) =>
-          Amounts.parseOrThrow(x.contribution),
-        ),
-        refreshReason: RefreshReason.PayDeposit,
-      });
+      if (depositGroup.payCoinSelection) {
+        await spendCoins(wex, tx, {
+          allocationId: transactionId,
+          coinPubs: depositGroup.payCoinSelection.coinPubs,
+          contributions: depositGroup.payCoinSelection.coinContributions.map(
+            (x) => Amounts.parseOrThrow(x),
+          ),
+          refreshReason: RefreshReason.PayDeposit,
+        });
+      }
       await tx.depositGroups.put(depositGroup);
       await tx.contractTerms.put({
         contractTermsRaw: contractTerms,
diff --git a/packages/taler-wallet-core/src/dev-experiments.ts 
b/packages/taler-wallet-core/src/dev-experiments.ts
index 57810dbf4..7cf18e36c 100644
--- a/packages/taler-wallet-core/src/dev-experiments.ts
+++ b/packages/taler-wallet-core/src/dev-experiments.ts
@@ -67,52 +67,56 @@ export async function applyDevExperiment(
     throw Error("can't handle devmode URI unless devmode is active");
   }
 
-  if (parsedUri.devExperimentId === "start-block-refresh") {
-    wex.ws.devExperimentState.blockRefreshes = true;
-    return;
-  }
-
-  if (parsedUri.devExperimentId == "insert-pending-refresh") {
-    await wex.db.runReadWriteTx(["refreshGroups"], async (tx) => {
-      const refreshGroupId = encodeCrock(getRandomBytes(32));
-      const newRg: RefreshGroupRecord = {
-        currency: "TESTKUDOS",
-        expectedOutputPerCoin: [],
-        inputPerCoin: [],
-        oldCoinPubs: [],
-        operationStatus: RefreshOperationStatus.Pending,
-        reason: RefreshReason.Manual,
-        refreshGroupId,
-        statusPerCoin: [],
-        timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
-        timestampFinished: undefined,
-        originatingTransactionId: undefined,
-        infoPerExchange: {},
-      };
-      await tx.refreshGroups.put(newRg);
-    });
-    return;
-  }
-
-  if (parsedUri.devExperimentId == "insert-denom-loss") {
-    await wex.db.runReadWriteTx(["denomLossEvents"], async (tx) => {
-      const eventId = encodeCrock(getRandomBytes(32));
-      const newRg: DenomLossEventRecord = {
-        amount: "TESTKUDOS:42",
-        currency: "TESTKUDOS",
-        exchangeBaseUrl: "https://exchange.test.taler.net/";,
-        denomLossEventId: eventId,
-        denomPubHashes: [
-          encodeCrock(getRandomBytes(64)),
-          encodeCrock(getRandomBytes(64)),
-        ],
-        eventType: DenomLossEventType.DenomExpired,
-        status: DenomLossStatus.Done,
-        timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
-      };
-      await tx.denomLossEvents.put(newRg);
-    });
-    return;
+  switch (parsedUri.devExperimentId) {
+    case "start-block-refresh": {
+      wex.ws.devExperimentState.blockRefreshes = true;
+      return;
+    }
+    case "stop-block-refresh": {
+      wex.ws.devExperimentState.blockRefreshes = false;
+      return;
+    }
+    case "insert-pending-refresh": {
+      await wex.db.runReadWriteTx(["refreshGroups"], async (tx) => {
+        const refreshGroupId = encodeCrock(getRandomBytes(32));
+        const newRg: RefreshGroupRecord = {
+          currency: "TESTKUDOS",
+          expectedOutputPerCoin: [],
+          inputPerCoin: [],
+          oldCoinPubs: [],
+          operationStatus: RefreshOperationStatus.Pending,
+          reason: RefreshReason.Manual,
+          refreshGroupId,
+          statusPerCoin: [],
+          timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
+          timestampFinished: undefined,
+          originatingTransactionId: undefined,
+          infoPerExchange: {},
+        };
+        await tx.refreshGroups.put(newRg);
+      });
+      return;
+    }
+    case "insert-denom-loss": {
+      await wex.db.runReadWriteTx(["denomLossEvents"], async (tx) => {
+        const eventId = encodeCrock(getRandomBytes(32));
+        const newRg: DenomLossEventRecord = {
+          amount: "TESTKUDOS:42",
+          currency: "TESTKUDOS",
+          exchangeBaseUrl: "https://exchange.test.taler.net/";,
+          denomLossEventId: eventId,
+          denomPubHashes: [
+            encodeCrock(getRandomBytes(64)),
+            encodeCrock(getRandomBytes(64)),
+          ],
+          eventType: DenomLossEventType.DenomExpired,
+          status: DenomLossStatus.Done,
+          timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
+        };
+        await tx.denomLossEvents.put(newRg);
+      });
+      return;
+    }
   }
 
   throw Error(`dev-experiment id not understood ${parsedUri.devExperimentId}`);
diff --git a/packages/taler-wallet-core/src/transactions.ts 
b/packages/taler-wallet-core/src/transactions.ts
index e404c0354..463aa97ba 100644
--- a/packages/taler-wallet-core/src/transactions.ts
+++ b/packages/taler-wallet-core/src/transactions.ts
@@ -894,10 +894,14 @@ function buildTransactionForDeposit(
   ort?: OperationRetryRecord,
 ): Transaction {
   let deposited = true;
-  for (const d of dg.statusPerCoin) {
-    if (d == DepositElementStatus.DepositPending) {
-      deposited = false;
+  if (dg.statusPerCoin) {
+    for (const d of dg.statusPerCoin) {
+      if (d == DepositElementStatus.DepositPending) {
+        deposited = false;
+      }
     }
+  } else {
+    deposited = false;
   }
 
   const trackingState: DepositTransactionTrackingState[] = [];
@@ -911,6 +915,17 @@ function buildTransactionForDeposit(
     });
   }
 
+  let wireTransferProgress = 0;
+  if (dg.statusPerCoin) {
+    wireTransferProgress =
+      (100 *
+        dg.statusPerCoin.reduce(
+          (prev, cur) => prev + (cur === DepositElementStatus.Wired ? 1 : 0),
+          0,
+        )) /
+      dg.statusPerCoin.length;
+  }
+
   const txState = computeDepositTransactionStatus(dg);
   return {
     type: TransactionType.Deposit,
@@ -927,13 +942,7 @@ function buildTransactionForDeposit(
       tag: TransactionType.Deposit,
       depositGroupId: dg.depositGroupId,
     }),
-    wireTransferProgress:
-      (100 *
-        dg.statusPerCoin.reduce(
-          (prev, cur) => prev + (cur === DepositElementStatus.Wired ? 1 : 0),
-          0,
-        )) /
-      dg.statusPerCoin.length,
+    wireTransferProgress,
     depositGroupId: dg.depositGroupId,
     trackingState,
     deposited,

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