gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 01/02: wallet-core: cache fresh coin count in DB


From: gnunet
Subject: [taler-wallet-core] 01/02: wallet-core: cache fresh coin count in DB
Date: Wed, 14 Sep 2022 21:27:08 +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 c021876b41bff11ad28c3a43808795fa0d02ce99
Author: Florian Dold <florian@dold.me>
AuthorDate: Wed Sep 14 20:34:37 2022 +0200

    wallet-core: cache fresh coin count in DB
---
 packages/taler-util/src/walletTypes.ts             |   1 +
 packages/taler-wallet-core/src/db.ts               |  17 ++-
 .../src/operations/backup/import.ts                |   1 -
 .../taler-wallet-core/src/operations/deposits.ts   |  20 ++--
 packages/taler-wallet-core/src/operations/pay.ts   |  77 +++----------
 .../src/operations/peer-to-peer.ts                 |  46 +++-----
 .../taler-wallet-core/src/operations/refresh.ts    |  23 ++--
 packages/taler-wallet-core/src/operations/tip.ts   |   6 +-
 .../taler-wallet-core/src/operations/withdraw.ts   |   7 +-
 packages/taler-wallet-core/src/util/query.ts       |   5 +-
 packages/taler-wallet-core/src/wallet.ts           | 123 ++++++++++++++++++++-
 11 files changed, 197 insertions(+), 129 deletions(-)

diff --git a/packages/taler-util/src/walletTypes.ts 
b/packages/taler-util/src/walletTypes.ts
index 437ac296..701049c2 100644
--- a/packages/taler-util/src/walletTypes.ts
+++ b/packages/taler-util/src/walletTypes.ts
@@ -529,6 +529,7 @@ export interface PlanchetCreationRequest {
 export enum RefreshReason {
   Manual = "manual",
   PayMerchant = "pay-merchant",
+  PayDeposit = "pay-deposit",
   PayPeerPush = "pay-peer-push",
   PayPeerPull = "pay-peer-pull",
   Refund = "refund",
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index da566ff2..6dfa63c0 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -314,6 +314,11 @@ export interface DenominationRecord {
    * that includes this denomination.
    */
   listIssueDate: TalerProtocolTimestamp;
+
+  /**
+   * Number of fresh coins of this denomination that are available.
+   */
+  freshCoinCount?: number;
 }
 
 /**
@@ -520,6 +525,13 @@ export enum CoinStatus {
    * Withdrawn and never shown to anybody.
    */
   Fresh = "fresh",
+
+  /**
+   * Fresh, but currently marked as "suspended", thus won't be used
+   * for spending.  Used for testing.
+   */
+  FreshSuspended = "fresh-suspended",
+
   /**
    * A coin that has been spent and refreshed.
    */
@@ -605,11 +617,6 @@ export interface CoinRecord {
    */
   exchangeBaseUrl: string;
 
-  /**
-   * The coin is currently suspended, and will not be used for payments.
-   */
-  suspended: boolean;
-
   /**
    * Blinding key used when withdrawing the coin.
    * Potentionally used again during payback.
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts 
b/packages/taler-wallet-core/src/operations/backup/import.ts
index 8f5d019d..53e45918 100644
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ b/packages/taler-wallet-core/src/operations/backup/import.ts
@@ -413,7 +413,6 @@ export async function importBackup(
                 currentAmount: Amounts.parseOrThrow(backupCoin.current_amount),
                 denomSig: backupCoin.denom_sig,
                 coinPub: compCoin.coinPub,
-                suspended: false,
                 exchangeBaseUrl: backupExchangeDetails.base_url,
                 denomPubHash,
                 status: backupCoin.fresh
diff --git a/packages/taler-wallet-core/src/operations/deposits.ts 
b/packages/taler-wallet-core/src/operations/deposits.ts
index 5838be76..6d63def5 100644
--- a/packages/taler-wallet-core/src/operations/deposits.ts
+++ b/packages/taler-wallet-core/src/operations/deposits.ts
@@ -33,12 +33,11 @@ import {
   getRandomBytes,
   hashWire,
   Logger,
-  NotificationType,
   parsePaytoUri,
   PayCoinSelection,
   PrepareDepositRequest,
   PrepareDepositResponse,
-  TalerErrorDetail,
+  RefreshReason,
   TalerProtocolTimestamp,
   TrackDepositGroupRequest,
   TrackDepositGroupResponse,
@@ -46,18 +45,15 @@ import {
 } from "@gnu-taler/taler-util";
 import {
   DepositGroupRecord,
-  OperationAttemptErrorResult,
   OperationAttemptResult,
   OperationStatus,
 } from "../db.js";
 import { InternalWalletState } from "../internal-wallet-state.js";
 import { selectPayCoins } from "../util/coinSelection.js";
 import { readSuccessResponseJsonOrThrow } from "../util/http.js";
-import { RetryInfo } from "../util/retries.js";
-import { guardOperationException } from "./common.js";
+import { spendCoins } from "../wallet.js";
 import { getExchangeDetails } from "./exchanges.js";
 import {
-  applyCoinSpend,
   CoinSelectionRequest,
   extractContractData,
   generateDepositPermissions,
@@ -525,12 +521,12 @@ export async function createDepositGroup(
       x.refreshGroups,
     ])
     .runReadWrite(async (tx) => {
-      await applyCoinSpend(
-        ws,
-        tx,
-        payCoinSel,
-        `deposit-group:${depositGroup.depositGroupId}`,
-      );
+      await spendCoins(ws, tx, {
+        allocationId: `deposit-group:${depositGroup.depositGroupId}`,
+        coinPubs: payCoinSel.coinPubs,
+        contributions: payCoinSel.coinContributions,
+        refreshReason: RefreshReason.PayDeposit,
+      });
       await tx.depositGroups.put(depositGroup);
     });
 
diff --git a/packages/taler-wallet-core/src/operations/pay.ts 
b/packages/taler-wallet-core/src/operations/pay.ts
index 322e9048..bd7b1f7f 100644
--- a/packages/taler-wallet-core/src/operations/pay.ts
+++ b/packages/taler-wallet-core/src/operations/pay.ts
@@ -100,6 +100,7 @@ import {
 } from "../util/http.js";
 import { GetReadWriteAccess } from "../util/query.js";
 import { RetryInfo, RetryTags, scheduleRetry } from "../util/retries.js";
+import { spendCoins } from "../wallet.js";
 import { getExchangeDetails } from "./exchanges.js";
 import { createRefreshGroup, getTotalRefreshCost } from "./refresh.js";
 
@@ -156,9 +157,6 @@ export async function getTotalPaymentCost(
 }
 
 function isSpendableCoin(coin: CoinRecord, denom: DenominationRecord): boolean 
{
-  if (coin.suspended) {
-    return false;
-  }
   if (denom.isRevoked) {
     return false;
   }
@@ -347,65 +345,6 @@ export async function getCandidatePayCoins(
   };
 }
 
-/**
- * Apply a coin selection to the database.  Marks coins as spent
- * and creates a refresh session for the remaining amount.
- *
- * FIXME:  This does not deal well with conflicting spends!
- * When two payments are made in parallel, the same coin can be selected
- * for two payments.
- * However, this is a situation that can also happen via sync.
- */
-export async function applyCoinSpend(
-  ws: InternalWalletState,
-  tx: GetReadWriteAccess<{
-    coins: typeof WalletStoresV1.coins;
-    refreshGroups: typeof WalletStoresV1.refreshGroups;
-    denominations: typeof WalletStoresV1.denominations;
-  }>,
-  coinSelection: PayCoinSelection,
-  allocationId: string,
-): Promise<void> {
-  logger.info(`applying coin spend ${j2s(coinSelection)}`);
-  for (let i = 0; i < coinSelection.coinPubs.length; i++) {
-    const coin = await tx.coins.get(coinSelection.coinPubs[i]);
-    if (!coin) {
-      throw Error("coin allocated for payment doesn't exist anymore");
-    }
-    const contrib = coinSelection.coinContributions[i];
-    if (coin.status !== CoinStatus.Fresh) {
-      const alloc = coin.allocation;
-      if (!alloc) {
-        continue;
-      }
-      if (alloc.id !== allocationId) {
-        // FIXME: assign error code
-        throw Error("conflicting coin allocation (id)");
-      }
-      if (0 !== Amounts.cmp(alloc.amount, contrib)) {
-        // FIXME: assign error code
-        throw Error("conflicting coin allocation (contrib)");
-      }
-      continue;
-    }
-    coin.status = CoinStatus.Dormant;
-    coin.allocation = {
-      id: allocationId,
-      amount: Amounts.stringify(contrib),
-    };
-    const remaining = Amounts.sub(coin.currentAmount, contrib);
-    if (remaining.saturated) {
-      throw Error("not enough remaining balance on coin for payment");
-    }
-    coin.currentAmount = remaining.amount;
-    await tx.coins.put(coin);
-  }
-  const refreshCoinPubs = coinSelection.coinPubs.map((x) => ({
-    coinPub: x,
-  }));
-  await createRefreshGroup(ws, tx, refreshCoinPubs, RefreshReason.PayMerchant);
-}
-
 /**
  * Record all information that is necessary to
  * pay for a proposal in the wallet's database.
@@ -468,7 +407,12 @@ async function recordConfirmPay(
         await tx.proposals.put(p);
       }
       await tx.purchases.put(t);
-      await applyCoinSpend(ws, tx, coinSelection, `proposal:${t.proposalId}`);
+      await spendCoins(ws, tx, {
+        allocationId: `proposal:${t.proposalId}`,
+        coinPubs: coinSelection.coinPubs,
+        contributions: coinSelection.coinContributions,
+        refreshReason: RefreshReason.PayMerchant,
+      });
     });
 
   ws.notify({
@@ -1038,7 +982,12 @@ async function handleInsufficientFunds(
       p.payCoinSelectionUid = encodeCrock(getRandomBytes(32));
       p.coinDepositPermissions = undefined;
       await tx.purchases.put(p);
-      await applyCoinSpend(ws, tx, res, `proposal:${p.proposalId}`);
+      await spendCoins(ws, tx, {
+        allocationId: `proposal:${p.proposalId}`,
+        coinPubs: p.payCoinSelection.coinPubs,
+        contributions: p.payCoinSelection.coinContributions,
+        refreshReason: RefreshReason.PayMerchant,
+      });
     });
 }
 
diff --git a/packages/taler-wallet-core/src/operations/peer-to-peer.ts 
b/packages/taler-wallet-core/src/operations/peer-to-peer.ts
index 59dad3d5..449a91c6 100644
--- a/packages/taler-wallet-core/src/operations/peer-to-peer.ts
+++ b/packages/taler-wallet-core/src/operations/peer-to-peer.ts
@@ -75,6 +75,8 @@ import { internalCreateWithdrawalGroup } from "./withdraw.js";
 import { GetReadOnlyAccess } from "../util/query.js";
 import { createRefreshGroup } from "./refresh.js";
 import { updateExchangeFromUrl } from "./exchanges.js";
+import { spendCoins } from "../wallet.js";
+import { RetryTags } from "../util/retries.js";
 
 const logger = new Logger("operations/peer-to-peer.ts");
 
@@ -256,18 +258,14 @@ export async function initiatePeerToPeerPush(
         return undefined;
       }
 
-      const pubs: CoinPublicKey[] = [];
-      for (const c of sel.coins) {
-        const coin = await tx.coins.get(c.coinPub);
-        checkDbInvariant(!!coin);
-        coin.currentAmount = Amounts.sub(
-          coin.currentAmount,
-          Amounts.parseOrThrow(c.contribution),
-        ).amount;
-        coin.status = CoinStatus.Dormant;
-        pubs.push({ coinPub: coin.coinPub });
-        await tx.coins.put(coin);
-      }
+      await spendCoins(ws, tx, {
+        allocationId: `peer-push:${pursePair.pub}`,
+        coinPubs: sel.coins.map((x) => x.coinPub),
+        contributions: sel.coins.map((x) =>
+          Amounts.parseOrThrow(x.contribution),
+        ),
+        refreshReason: RefreshReason.PayPeerPush,
+      });
 
       await tx.peerPushPaymentInitiations.add({
         amount: Amounts.stringify(instructedAmount),
@@ -284,8 +282,6 @@ export async function initiatePeerToPeerPush(
         timestampCreated: TalerProtocolTimestamp.now(),
       });
 
-      await createRefreshGroup(ws, tx, pubs, RefreshReason.PayPeerPush);
-
       return sel;
     });
   logger.info(`selected p2p coins (push): ${j2s(coinSelRes)}`);
@@ -588,20 +584,14 @@ export async function acceptPeerPullPayment(
         return undefined;
       }
 
-      const pubs: CoinPublicKey[] = [];
-      for (const c of sel.coins) {
-        const coin = await tx.coins.get(c.coinPub);
-        checkDbInvariant(!!coin);
-        coin.currentAmount = Amounts.sub(
-          coin.currentAmount,
-          Amounts.parseOrThrow(c.contribution),
-        ).amount;
-        coin.status = CoinStatus.Dormant;
-        pubs.push({ coinPub: coin.coinPub });
-        await tx.coins.put(coin);
-      }
-
-      await createRefreshGroup(ws, tx, pubs, RefreshReason.PayPeerPull);
+      await spendCoins(ws, tx, {
+        allocationId: `peer-pull:${req.peerPullPaymentIncomingId}`,
+        coinPubs: sel.coins.map((x) => x.coinPub),
+        contributions: sel.coins.map((x) =>
+          Amounts.parseOrThrow(x.contribution),
+        ),
+        refreshReason: RefreshReason.PayPeerPull,
+      });
 
       const pi = await tx.peerPullPaymentIncoming.get(
         req.peerPullPaymentIncomingId,
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts 
b/packages/taler-wallet-core/src/operations/refresh.ts
index 719093bd..d1c366cd 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -77,6 +77,7 @@ import {
 import { checkDbInvariant } from "../util/invariants.js";
 import { GetReadWriteAccess } from "../util/query.js";
 import { RetryInfo, runOperationHandlerForResult } from "../util/retries.js";
+import { makeCoinAvailable } from "../wallet.js";
 import { guardOperationException } from "./common.js";
 import { updateExchangeFromUrl } from "./exchanges.js";
 import {
@@ -670,7 +671,6 @@ async function refreshReveal(
           type: CoinSourceType.Refresh,
           oldCoinPub: refreshGroup.oldCoinPubs[coinIndex],
         },
-        suspended: false,
         coinEvHash: pc.coinEvHash,
         ageCommitmentProof: pc.ageCommitmentProof,
       };
@@ -680,7 +680,7 @@ async function refreshReveal(
   }
 
   await ws.db
-    .mktx((x) => [x.coins, x.refreshGroups])
+    .mktx((x) => [x.coins, x.denominations, x.refreshGroups])
     .runReadWrite(async (tx) => {
       const rg = await tx.refreshGroups.get(refreshGroupId);
       if (!rg) {
@@ -694,7 +694,7 @@ async function refreshReveal(
       rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Finished;
       updateGroupStatus(rg);
       for (const coin of coins) {
-        await tx.coins.put(coin);
+        await makeCoinAvailable(ws, tx, coin);
       }
       await tx.refreshGroups.put(rg);
     });
@@ -865,10 +865,22 @@ export async function createRefreshGroup(
       !!denom,
       "denomination for existing coin must be in database",
     );
+    if (coin.status !== CoinStatus.Dormant) {
+      coin.status = CoinStatus.Dormant;
+      const denom = await tx.denominations.get([
+        coin.exchangeBaseUrl,
+        coin.denomPubHash,
+      ]);
+      checkDbInvariant(!!denom);
+      checkDbInvariant(
+        denom.freshCoinCount != null && denom.freshCoinCount > 0,
+      );
+      denom.freshCoinCount--;
+      await tx.denominations.put(denom);
+    }
     const refreshAmount = coin.currentAmount;
     inputPerCoin.push(refreshAmount);
     coin.currentAmount = Amounts.getZero(refreshAmount.currency);
-    coin.status = CoinStatus.Dormant;
     await tx.coins.put(coin);
     const denoms = await getDenoms(coin.exchangeBaseUrl);
     const cost = getTotalRefreshCost(denoms, denom, refreshAmount);
@@ -965,9 +977,6 @@ export async function autoRefresh(
         if (coin.status !== CoinStatus.Fresh) {
           continue;
         }
-        if (coin.suspended) {
-          continue;
-        }
         const denom = await tx.denominations.get([
           exchangeBaseUrl,
           coin.denomPubHash,
diff --git a/packages/taler-wallet-core/src/operations/tip.ts 
b/packages/taler-wallet-core/src/operations/tip.ts
index 04da2b98..f70e2d02 100644
--- a/packages/taler-wallet-core/src/operations/tip.ts
+++ b/packages/taler-wallet-core/src/operations/tip.ts
@@ -51,6 +51,7 @@ import {
   readSuccessResponseJsonOrThrow,
 } from "../util/http.js";
 import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
+import { makeCoinAvailable } from "../wallet.js";
 import { updateExchangeFromUrl } from "./exchanges.js";
 import {
   getCandidateWithdrawalDenoms,
@@ -310,13 +311,12 @@ export async function processTip(
       denomSig: { cipher: DenomKeyType.Rsa, rsa_signature: denomSigRsa.sig },
       exchangeBaseUrl: tipRecord.exchangeBaseUrl,
       status: CoinStatus.Fresh,
-      suspended: false,
       coinEvHash: planchet.coinEvHash,
     });
   }
 
   await ws.db
-    .mktx((x) => [x.coins, x.tips, x.withdrawalGroups])
+    .mktx((x) => [x.coins, x.denominations, x.tips])
     .runReadWrite(async (tx) => {
       const tr = await tx.tips.get(walletTipId);
       if (!tr) {
@@ -328,7 +328,7 @@ export async function processTip(
       tr.pickedUpTimestamp = TalerProtocolTimestamp.now();
       await tx.tips.put(tr);
       for (const cr of newCoinRecords) {
-        await tx.coins.put(cr);
+        await makeCoinAvailable(ws, tx, cr);
       }
     });
 
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts 
b/packages/taler-wallet-core/src/operations/withdraw.ts
index 1b838377..bee83265 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -93,11 +93,11 @@ import {
 } from "../util/http.js";
 import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
 import { DbAccess, GetReadOnlyAccess } from "../util/query.js";
-import { RetryInfo } from "../util/retries.js";
 import {
   WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
   WALLET_EXCHANGE_PROTOCOL_VERSION,
 } from "../versions.js";
+import { makeCoinAvailable } from "../wallet.js";
 import {
   getExchangeDetails,
   getExchangePaytoUri,
@@ -805,7 +805,6 @@ async function processPlanchetVerifyAndStoreCoin(
       reservePub: planchet.reservePub,
       withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
     },
-    suspended: false,
     ageCommitmentProof: planchet.ageCommitmentProof,
   };
 
@@ -815,7 +814,7 @@ async function processPlanchetVerifyAndStoreCoin(
   // withdrawal succeeded.  If so, mark the withdrawal
   // group as finished.
   const firstSuccess = await ws.db
-    .mktx((x) => [x.coins, x.withdrawalGroups, x.planchets])
+    .mktx((x) => [x.coins, x.denominations, x.withdrawalGroups, x.planchets])
     .runReadWrite(async (tx) => {
       const p = await tx.planchets.get(planchetCoinPub);
       if (!p || p.withdrawalDone) {
@@ -823,7 +822,7 @@ async function processPlanchetVerifyAndStoreCoin(
       }
       p.withdrawalDone = true;
       await tx.planchets.put(p);
-      await tx.coins.add(coin);
+      await makeCoinAvailable(ws, tx, coin);
       return true;
     });
 
diff --git a/packages/taler-wallet-core/src/util/query.ts 
b/packages/taler-wallet-core/src/util/query.ts
index 02595925..17b71365 100644
--- a/packages/taler-wallet-core/src/util/query.ts
+++ b/packages/taler-wallet-core/src/util/query.ts
@@ -445,14 +445,15 @@ function runTx<Arg, Res>(
       if (!gotFunResult) {
         const msg =
           "BUG: transaction closed before transaction function returned";
-        console.error(msg);
+        logger.error(msg);
+        logger.error(`${stack.stack}`);
         reject(Error(msg));
       }
       resolve(funResult);
     };
     tx.onerror = () => {
       logger.error("error in transaction");
-      logger.error(`${stack}`);
+      logger.error(`${stack.stack}`);
     };
     tx.onabort = () => {
       let msg: string;
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index 0e777225..afbee4e6 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -99,7 +99,9 @@ import {
 } from "./crypto/workers/cryptoDispatcher.js";
 import {
   AuditorTrustRecord,
+  CoinRecord,
   CoinSourceType,
+  CoinStatus,
   exportDb,
   importDb,
   OperationAttemptResult,
@@ -216,6 +218,7 @@ import {
   HttpRequestLibrary,
   readSuccessResponseJsonOrThrow,
 } from "./util/http.js";
+import { checkDbInvariant } from "./util/invariants.js";
 import {
   AsyncCondition,
   OpenedPromise,
@@ -787,21 +790,135 @@ async function getExchangeDetailedInfo(
   };
 }
 
+export async function makeCoinAvailable(
+  ws: InternalWalletState,
+  tx: GetReadWriteAccess<{
+    coins: typeof WalletStoresV1.coins;
+    denominations: typeof WalletStoresV1.denominations;
+  }>,
+  coinRecord: CoinRecord,
+): Promise<void> {
+  const denom = await tx.denominations.get([
+    coinRecord.exchangeBaseUrl,
+    coinRecord.denomPubHash,
+  ]);
+  checkDbInvariant(!!denom);
+  if (!denom.freshCoinCount) {
+    denom.freshCoinCount = 0;
+  }
+  denom.freshCoinCount++;
+  await tx.coins.put(coinRecord);
+  await tx.denominations.put(denom);
+}
+
+export interface CoinsSpendInfo {
+  coinPubs: string[];
+  contributions: AmountJson[];
+  refreshReason: RefreshReason;
+  /**
+   * Identifier for what the coin has been spent for.
+   */
+  allocationId: string;
+}
+
+export async function spendCoins(
+  ws: InternalWalletState,
+  tx: GetReadWriteAccess<{
+    coins: typeof WalletStoresV1.coins;
+    refreshGroups: typeof WalletStoresV1.refreshGroups;
+    denominations: typeof WalletStoresV1.denominations;
+  }>,
+  csi: CoinsSpendInfo,
+): Promise<void> {
+  for (let i = 0; i < csi.coinPubs.length; i++) {
+    const coin = await tx.coins.get(csi.coinPubs[i]);
+    if (!coin) {
+      throw Error("coin allocated for payment doesn't exist anymore");
+    }
+    const denom = await tx.denominations.get([
+      coin.exchangeBaseUrl,
+      coin.denomPubHash,
+    ]);
+    checkDbInvariant(!!denom);
+    const contrib = csi.contributions[i];
+    if (coin.status !== CoinStatus.Fresh) {
+      const alloc = coin.allocation;
+      if (!alloc) {
+        continue;
+      }
+      if (alloc.id !== csi.allocationId) {
+        // FIXME: assign error code
+        throw Error("conflicting coin allocation (id)");
+      }
+      if (0 !== Amounts.cmp(alloc.amount, contrib)) {
+        // FIXME: assign error code
+        throw Error("conflicting coin allocation (contrib)");
+      }
+      continue;
+    }
+    coin.status = CoinStatus.Dormant;
+    coin.allocation = {
+      id: csi.allocationId,
+      amount: Amounts.stringify(contrib),
+    };
+    const remaining = Amounts.sub(coin.currentAmount, contrib);
+    if (remaining.saturated) {
+      throw Error("not enough remaining balance on coin for payment");
+    }
+    coin.currentAmount = remaining.amount;
+    checkDbInvariant(!!denom);
+    if (denom.freshCoinCount == null || denom.freshCoinCount === 0) {
+      throw Error(`invalid coin count ${denom.freshCoinCount} in DB`);
+    }
+    denom.freshCoinCount--;
+    await tx.coins.put(coin);
+    await tx.denominations.put(denom);
+  }
+  const refreshCoinPubs = csi.coinPubs.map((x) => ({
+    coinPub: x,
+  }));
+  await createRefreshGroup(ws, tx, refreshCoinPubs, RefreshReason.PayMerchant);
+}
+
 async function setCoinSuspended(
   ws: InternalWalletState,
   coinPub: string,
   suspended: boolean,
 ): Promise<void> {
   await ws.db
-    .mktx((x) => [x.coins])
+    .mktx((x) => [x.coins, x.denominations])
     .runReadWrite(async (tx) => {
       const c = await tx.coins.get(coinPub);
       if (!c) {
         logger.warn(`coin ${coinPub} not found, won't suspend`);
         return;
       }
-      c.suspended = suspended;
+      const denom = await tx.denominations.get([
+        c.exchangeBaseUrl,
+        c.denomPubHash,
+      ]);
+      checkDbInvariant(!!denom);
+      if (suspended) {
+        if (c.status !== CoinStatus.Fresh) {
+          return;
+        }
+        if (denom.freshCoinCount == null || denom.freshCoinCount === 0) {
+          throw Error(`invalid coin count ${denom.freshCoinCount} in DB`);
+        }
+        denom.freshCoinCount--;
+        c.status = CoinStatus.FreshSuspended;
+      } else {
+        if (c.status == CoinStatus.Dormant) {
+          return;
+        }
+        if (denom.freshCoinCount == null) {
+          denom.freshCoinCount = 0;
+        }
+        denom.freshCoinCount++;
+        c.status = CoinStatus.Fresh;
+      }
       await tx.coins.put(c);
+      await tx.denominations.put(denom);
     });
 }
 
@@ -857,7 +974,7 @@ async function dumpCoins(ws: InternalWalletState): 
Promise<CoinDumpJson> {
           refresh_parent_coin_pub: refreshParentCoinPub,
           remaining_value: Amounts.stringify(c.currentAmount),
           withdrawal_reserve_pub: withdrawalReservePub,
-          coin_suspended: c.suspended,
+          coin_suspended: c.status === CoinStatus.FreshSuspended,
           ageCommitmentProof: c.ageCommitmentProof,
         });
       }

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