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: simplify coin record


From: gnunet
Subject: [taler-wallet-core] 01/02: wallet-core: simplify coin record
Date: Sat, 15 Oct 2022 12:59:30 +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 e075134ffc94fda3582b179122bda594d91a962b
Author: Florian Dold <florian@dold.me>
AuthorDate: Sat Oct 15 11:52:07 2022 +0200

    wallet-core: simplify coin record
    
    we only track the allocation now, not the remaining amount
---
 packages/taler-util/src/backup-types.ts            | 11 ++-
 packages/taler-util/src/taler-types.ts             | 54 ---------------
 packages/taler-util/src/wallet-types.ts            | 81 +++++++++++++++++++++-
 packages/taler-wallet-cli/src/index.ts             |  4 +-
 .../integrationtests/test-refund-incremental.ts    |  2 +-
 .../src/integrationtests/test-wallettesting.ts     |  9 ++-
 packages/taler-wallet-core/src/db.ts               | 43 ++----------
 .../taler-wallet-core/src/internal-wallet-state.ts |  4 +-
 .../src/operations/backup/export.ts                | 10 ++-
 .../src/operations/backup/import.ts                |  6 +-
 .../taler-wallet-core/src/operations/balance.ts    | 25 ++++---
 .../taler-wallet-core/src/operations/common.ts     | 26 ++++---
 .../src/operations/pay-merchant.ts                 | 46 +++++-------
 .../taler-wallet-core/src/operations/pay-peer.ts   |  8 +--
 .../taler-wallet-core/src/operations/recoup.ts     | 42 +++++------
 .../taler-wallet-core/src/operations/refresh.ts    | 24 ++++---
 packages/taler-wallet-core/src/operations/tip.ts   |  5 +-
 .../src/operations/transactions.ts                 |  8 ++-
 .../taler-wallet-core/src/operations/withdraw.ts   |  7 +-
 packages/taler-wallet-core/src/wallet.ts           | 32 +++++++--
 20 files changed, 237 insertions(+), 210 deletions(-)

diff --git a/packages/taler-util/src/backup-types.ts 
b/packages/taler-util/src/backup-types.ts
index 6c7b203b5..5d53f178e 100644
--- a/packages/taler-util/src/backup-types.ts
+++ b/packages/taler-util/src/backup-types.ts
@@ -475,6 +475,7 @@ export interface BackupRecoupGroup {
 
   timestamp_finish?: TalerProtocolTimestamp;
   finish_clock?: TalerProtocolTimestamp;
+  // FIXME: Use some enum here!
   finish_is_failure?: boolean;
 
   /**
@@ -483,7 +484,6 @@ export interface BackupRecoupGroup {
   coins: {
     coin_pub: string;
     recoup_finished: boolean;
-    old_amount: BackupAmountString;
   }[];
 }
 
@@ -582,9 +582,14 @@ export interface BackupCoin {
   denom_sig: UnblindedSignature;
 
   /**
-   * Amount that's left on the coin.
+   * Information about where and how the coin was spent.
    */
-  current_amount: BackupAmountString;
+  spend_allocation:
+    | {
+        id: string;
+        amount: BackupAmountString;
+      }
+    | undefined;
 
   /**
    * Blinding key used when withdrawing the coin.
diff --git a/packages/taler-util/src/taler-types.ts 
b/packages/taler-util/src/taler-types.ts
index de88fef69..71ceb7939 100644
--- a/packages/taler-util/src/taler-types.ts
+++ b/packages/taler-util/src/taler-types.ts
@@ -968,60 +968,6 @@ export class WithdrawBatchResponse {
   ev_sigs: WithdrawResponse[];
 }
 
-/**
- * Easy to process format for the public data of coins
- * managed by the wallet.
- */
-export interface CoinDumpJson {
-  coins: Array<{
-    /**
-     * The coin's denomination's public key.
-     */
-    denom_pub: DenominationPubKey;
-    /**
-     * Hash of denom_pub.
-     */
-    denom_pub_hash: string;
-    /**
-     * Value of the denomination (without any fees).
-     */
-    denom_value: string;
-    /**
-     * Public key of the coin.
-     */
-    coin_pub: string;
-    /**
-     * Base URL of the exchange for the coin.
-     */
-    exchange_base_url: string;
-    /**
-     * Remaining value on the coin, to the knowledge of
-     * the wallet.
-     */
-    remaining_value: string;
-    /**
-     * Public key of the parent coin.
-     * Only present if this coin was obtained via refreshing.
-     */
-    refresh_parent_coin_pub: string | undefined;
-    /**
-     * Public key of the reserve for this coin.
-     * Only present if this coin was obtained via refreshing.
-     */
-    withdrawal_reserve_pub: string | undefined;
-    /**
-     * Is the coin suspended?
-     * Suspended coins are not considered for payments.
-     */
-    coin_suspended: boolean;
-
-    /**
-     * Information about the age restriction
-     */
-    ageCommitmentProof: AgeCommitmentProof | undefined;
-  }>;
-}
-
 export interface MerchantPayResponse {
   sig: string;
 }
diff --git a/packages/taler-util/src/wallet-types.ts 
b/packages/taler-util/src/wallet-types.ts
index d4de53972..54f4c54a2 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -63,7 +63,10 @@ import {
   ExchangeAuditor,
   UnblindedSignature,
 } from "./taler-types.js";
-import { OrderShortInfo, codecForOrderShortInfo } from 
"./transactions-types.js";
+import {
+  OrderShortInfo,
+  codecForOrderShortInfo,
+} from "./transactions-types.js";
 import { BackupRecovery } from "./backup-types.js";
 import { PaytoUri } from "./payto.js";
 import { TalerErrorCode } from "./taler-error-codes.js";
@@ -141,6 +144,77 @@ export function mkAmount(
   return { value, fraction, currency };
 }
 
+/**
+ * Status of a coin.
+ */
+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.
+   */
+  Dormant = "dormant",
+}
+
+/**
+ * Easy to process format for the public data of coins
+ * managed by the wallet.
+ */
+export interface CoinDumpJson {
+  coins: Array<{
+    /**
+     * The coin's denomination's public key.
+     */
+    denom_pub: DenominationPubKey;
+    /**
+     * Hash of denom_pub.
+     */
+    denom_pub_hash: string;
+    /**
+     * Value of the denomination (without any fees).
+     */
+    denom_value: string;
+    /**
+     * Public key of the coin.
+     */
+    coin_pub: string;
+    /**
+     * Base URL of the exchange for the coin.
+     */
+    exchange_base_url: string;
+    /**
+     * Public key of the parent coin.
+     * Only present if this coin was obtained via refreshing.
+     */
+    refresh_parent_coin_pub: string | undefined;
+    /**
+     * Public key of the reserve for this coin.
+     * Only present if this coin was obtained via refreshing.
+     */
+    withdrawal_reserve_pub: string | undefined;
+    coin_status: CoinStatus;
+    spend_allocation:
+      | {
+          id: string;
+          amount: string;
+        }
+      | undefined;
+    /**
+     * Information about the age restriction
+     */
+    ageCommitmentProof: AgeCommitmentProof | undefined;
+  }>;
+}
+
 export enum ConfirmPayResultType {
   Done = "done",
   Pending = "pending",
@@ -568,10 +642,11 @@ export enum RefreshReason {
 }
 
 /**
- * Wrapper for coin public keys.
+ * Request to refresh a single coin.
  */
-export interface CoinPublicKey {
+export interface CoinRefreshRequest {
   readonly coinPub: string;
+  readonly amount: AmountJson;
 }
 
 /**
diff --git a/packages/taler-wallet-cli/src/index.ts 
b/packages/taler-wallet-cli/src/index.ts
index 941a2f28f..b3abbac6f 100644
--- a/packages/taler-wallet-cli/src/index.ts
+++ b/packages/taler-wallet-cli/src/index.ts
@@ -1105,9 +1105,7 @@ advancedCli
         console.log(`coin ${coin.coin_pub}`);
         console.log(` exchange ${coin.exchange_base_url}`);
         console.log(` denomPubHash ${coin.denom_pub_hash}`);
-        console.log(
-          ` remaining amount ${Amounts.stringify(coin.remaining_value)}`,
-        );
+        console.log(` status ${coin.coin_status}`);
       }
     });
   });
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-refund-incremental.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-refund-incremental.ts
index e4e96a180..8d1f6e873 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-refund-incremental.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-refund-incremental.ts
@@ -193,7 +193,7 @@ export async function runRefundIncrementalTest(t: 
GlobalTestState) {
         .map((x) => x.amountEffective),
     ).amount;
 
-    t.assertAmountEquals("TESTKUDOS:8.33", effective);
+    t.assertAmountEquals("TESTKUDOS:8.59", effective);
   }
 
   await t.shutdown();
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-wallettesting.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-wallettesting.ts
index c16a85f19..03c446db3 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-wallettesting.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-wallettesting.ts
@@ -22,7 +22,7 @@
 /**
  * Imports.
  */
-import { Amounts } from "@gnu-taler/taler-util";
+import { Amounts, CoinStatus } from "@gnu-taler/taler-util";
 import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
 import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
 import {
@@ -32,7 +32,7 @@ import {
   MerchantService,
   setupDb,
   WalletCli,
-  getPayto
+  getPayto,
 } from "../harness/harness.js";
 import { SimpleTestEnvironment } from "../harness/helpers.js";
 
@@ -184,7 +184,10 @@ export async function runWallettestingTest(t: 
GlobalTestState) {
   let susp: string | undefined;
   {
     for (const c of coinDump.coins) {
-      if (0 === Amounts.cmp(c.remaining_value, "TESTKUDOS:8")) {
+      if (
+        c.coin_status === CoinStatus.Fresh &&
+        0 === Amounts.cmp(c.denom_value, "TESTKUDOS:8")
+      ) {
         susp = c.coin_pub;
       }
     }
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index c301ee457..b785efed8 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -49,6 +49,8 @@ import {
   ExchangeGlobalFees,
   DenomSelectionState,
   TransactionIdStr,
+  CoinRefreshRequest,
+  CoinStatus,
 } from "@gnu-taler/taler-util";
 import { RetryInfo, RetryTags } from "./util/retries.js";
 import { Event, IDBDatabase } from "@gnu-taler/idb-bridge";
@@ -603,27 +605,6 @@ export interface PlanchetRecord {
   ageCommitmentProof?: AgeCommitmentProof;
 }
 
-/**
- * Status of a coin.
- */
-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.
-   */
-  Dormant = "dormant",
-}
-
 export enum CoinSourceType {
   Withdraw = "withdraw",
   Refresh = "refresh",
@@ -692,14 +673,6 @@ export interface CoinRecord {
    */
   denomSig: UnblindedSignature;
 
-  /**
-   * Amount that's left on the coin.
-   *
-   * FIXME: This is pretty redundant with "allocation" and "status".
-   * Do we really need this?
-   */
-  currentAmount: AmountJson;
-
   /**
    * Base URL that identifies the exchange from which we got the
    * coin.
@@ -732,7 +705,7 @@ export interface CoinRecord {
    * - Diagnostics
    * - Idempotency of applying a coin selection (e.g. after re-selection)
    */
-  allocation: CoinAllocation | undefined;
+  spendAllocation: CoinAllocation | undefined;
 
   /**
    * Maximum age of purchases that can be made with this coin.
@@ -1461,18 +1434,11 @@ export interface RecoupGroupRecord {
    */
   recoupFinishedPerCoin: boolean[];
 
-  /**
-   * We store old amount (i.e. before recoup) of recouped coins here,
-   * as the balance of a recouped coin is set to zero when the
-   * recoup group is created.
-   */
-  oldAmountPerCoin: AmountJson[];
-
   /**
    * Public keys of coins that should be scheduled for refreshing
    * after all individual recoups are done.
    */
-  scheduleRefreshCoins: string[];
+  scheduleRefreshCoins: CoinRefreshRequest[];
 }
 
 export enum BackupProviderStateTag {
@@ -1875,7 +1841,6 @@ export const WalletStoresV1 = {
     "exchangeTos",
     describeContents<ExchangeTosRecord>({
       keyPath: ["exchangeBaseUrl", "etag"],
-      autoIncrement: true,
     }),
     {},
   ),
diff --git a/packages/taler-wallet-core/src/internal-wallet-state.ts 
b/packages/taler-wallet-core/src/internal-wallet-state.ts
index bc956bd17..ebb9cdb9b 100644
--- a/packages/taler-wallet-core/src/internal-wallet-state.ts
+++ b/packages/taler-wallet-core/src/internal-wallet-state.ts
@@ -38,7 +38,7 @@ import {
   CancellationToken,
   DenominationInfo,
   RefreshGroupId,
-  CoinPublicKey,
+  CoinRefreshRequest,
   RefreshReason,
 } from "@gnu-taler/taler-util";
 import { CryptoDispatcher } from "./crypto/workers/cryptoDispatcher.js";
@@ -86,7 +86,7 @@ export interface RefreshOperations {
       refreshGroups: typeof WalletStoresV1.refreshGroups;
       coinAvailability: typeof WalletStoresV1.coinAvailability;
     }>,
-    oldCoinPubs: CoinPublicKey[],
+    oldCoinPubs: CoinRefreshRequest[],
     reason: RefreshReason,
   ): Promise<RefreshGroupId>;
 }
diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts 
b/packages/taler-wallet-core/src/operations/backup/export.ts
index 30e61e382..1472b1b90 100644
--- a/packages/taler-wallet-core/src/operations/backup/export.ts
+++ b/packages/taler-wallet-core/src/operations/backup/export.ts
@@ -54,6 +54,7 @@ import {
   BACKUP_VERSION_MINOR,
   canonicalizeBaseUrl,
   canonicalJson,
+  CoinStatus,
   encodeCrock,
   getRandomBytes,
   hash,
@@ -63,7 +64,6 @@ import {
 } from "@gnu-taler/taler-util";
 import {
   CoinSourceType,
-  CoinStatus,
   ConfigRecordKey,
   DenominationRecord,
   PurchaseStatus,
@@ -206,7 +206,6 @@ export async function exportBackup(
           coins: recoupGroup.coinPubs.map((x, i) => ({
             coin_pub: x,
             recoup_finished: recoupGroup.recoupFinishedPerCoin[i],
-            old_amount: Amounts.stringify(recoupGroup.oldAmountPerCoin[i]),
           })),
         });
       });
@@ -259,8 +258,13 @@ export async function exportBackup(
           blinding_key: coin.blindingKey,
           coin_priv: coin.coinPriv,
           coin_source: bcs,
-          current_amount: Amounts.stringify(coin.currentAmount),
           fresh: coin.status === CoinStatus.Fresh,
+          spend_allocation: coin.spendAllocation
+            ? {
+                amount: coin.spendAllocation.amount,
+                id: coin.spendAllocation.id,
+              }
+            : undefined,
           denom_sig: coin.denomSig,
         });
       });
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts 
b/packages/taler-wallet-core/src/operations/backup/import.ts
index 3bbb7d798..9c5eea9af 100644
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ b/packages/taler-wallet-core/src/operations/backup/import.ts
@@ -27,6 +27,7 @@ import {
   BackupRefundState,
   BackupWgType,
   codecForContractTerms,
+  CoinStatus,
   DenomKeyType,
   DenomSelectionState,
   j2s,
@@ -41,10 +42,8 @@ import {
   CoinRecord,
   CoinSource,
   CoinSourceType,
-  CoinStatus,
   DenominationRecord,
   DenominationVerificationStatus,
-  OperationStatus,
   ProposalDownloadInfo,
   PurchaseStatus,
   PurchasePayInfo,
@@ -272,7 +271,6 @@ export async function importCoin(
       blindingKey: backupCoin.blinding_key,
       coinEvHash: compCoin.coinEvHash,
       coinPriv: backupCoin.coin_priv,
-      currentAmount: Amounts.parseOrThrow(backupCoin.current_amount),
       denomSig: backupCoin.denom_sig,
       coinPub: compCoin.coinPub,
       exchangeBaseUrl,
@@ -284,7 +282,7 @@ export async function importCoin(
       // FIXME!
       ageCommitmentProof: undefined,
       // FIXME!
-      allocation: undefined,
+      spendAllocation: undefined,
     };
     if (coinRecord.status === CoinStatus.Fresh) {
       await makeCoinAvailable(ws, tx, coinRecord);
diff --git a/packages/taler-wallet-core/src/operations/balance.ts 
b/packages/taler-wallet-core/src/operations/balance.ts
index 44357fdf4..3db66b5d9 100644
--- a/packages/taler-wallet-core/src/operations/balance.ts
+++ b/packages/taler-wallet-core/src/operations/balance.ts
@@ -23,7 +23,7 @@ import {
   Amounts,
   Logger,
 } from "@gnu-taler/taler-util";
-import { CoinStatus, WalletStoresV1 } from "../db.js";
+import { WalletStoresV1 } from "../db.js";
 import { GetReadOnlyAccess } from "../util/query.js";
 import { InternalWalletState } from "../internal-wallet-state.js";
 
@@ -42,6 +42,7 @@ export async function getBalancesInsideTransaction(
   ws: InternalWalletState,
   tx: GetReadOnlyAccess<{
     coins: typeof WalletStoresV1.coins;
+    coinAvailability: typeof WalletStoresV1.coinAvailability;
     refreshGroups: typeof WalletStoresV1.refreshGroups;
     withdrawalGroups: typeof WalletStoresV1.withdrawalGroups;
   }>,
@@ -64,12 +65,14 @@ export async function getBalancesInsideTransaction(
     return balanceStore[currency];
   };
 
-  await tx.coins.iter().forEach((c) => {
-    // Only count fresh coins, as dormant coins will
-    // already be in a refresh session.
-    if (c.status === CoinStatus.Fresh) {
-      const b = initBalance(c.currentAmount.currency);
-      b.available = Amounts.add(b.available, c.currentAmount).amount;
+  await tx.coinAvailability.iter().forEach((ca) => {
+    const b = initBalance(ca.currency);
+    for (let i = 0; i < ca.freshCoinCount; i++) {
+      b.available = Amounts.add(b.available, {
+        currency: ca.currency,
+        fraction: ca.amountFrac,
+        value: ca.amountVal,
+      }).amount;
     }
   });
 
@@ -139,7 +142,13 @@ export async function getBalances(
   logger.trace("starting to compute balance");
 
   const wbal = await ws.db
-    .mktx((x) => [x.coins, x.refreshGroups, x.purchases, x.withdrawalGroups])
+    .mktx((x) => [
+      x.coins,
+      x.coinAvailability,
+      x.refreshGroups,
+      x.purchases,
+      x.withdrawalGroups,
+    ])
     .runReadOnly(async (tx) => {
       return getBalancesInsideTransaction(ws, tx);
     });
diff --git a/packages/taler-wallet-core/src/operations/common.ts 
b/packages/taler-wallet-core/src/operations/common.ts
index d17530c7f..5e02f3d7b 100644
--- a/packages/taler-wallet-core/src/operations/common.ts
+++ b/packages/taler-wallet-core/src/operations/common.ts
@@ -20,6 +20,8 @@
 import {
   AmountJson,
   Amounts,
+  CoinRefreshRequest,
+  CoinStatus,
   j2s,
   Logger,
   RefreshReason,
@@ -29,7 +31,7 @@ import {
   TransactionIdStr,
   TransactionType,
 } from "@gnu-taler/taler-util";
-import { WalletStoresV1, CoinStatus, CoinRecord } from "../db.js";
+import { WalletStoresV1, CoinRecord } from "../db.js";
 import { makeErrorDetail, TalerError } from "../errors.js";
 import { InternalWalletState } from "../internal-wallet-state.js";
 import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
@@ -103,11 +105,19 @@ export async function spendCoins(
   }>,
   csi: CoinsSpendInfo,
 ): Promise<void> {
+  let refreshCoinPubs: CoinRefreshRequest[] = [];
   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 ws.getDenomInfo(
+      ws,
+      tx,
+      coin.exchangeBaseUrl,
+      coin.denomPubHash,
+    );
+    checkDbInvariant(!!denom);
     const coinAvailability = await tx.coinAvailability.get([
       coin.exchangeBaseUrl,
       coin.denomPubHash,
@@ -116,7 +126,7 @@ export async function spendCoins(
     checkDbInvariant(!!coinAvailability);
     const contrib = csi.contributions[i];
     if (coin.status !== CoinStatus.Fresh) {
-      const alloc = coin.allocation;
+      const alloc = coin.spendAllocation;
       if (!alloc) {
         continue;
       }
@@ -131,15 +141,18 @@ export async function spendCoins(
       continue;
     }
     coin.status = CoinStatus.Dormant;
-    coin.allocation = {
+    coin.spendAllocation = {
       id: csi.allocationId,
       amount: Amounts.stringify(contrib),
     };
-    const remaining = Amounts.sub(coin.currentAmount, contrib);
+    const remaining = Amounts.sub(denom.value, contrib);
     if (remaining.saturated) {
       throw Error("not enough remaining balance on coin for payment");
     }
-    coin.currentAmount = remaining.amount;
+    refreshCoinPubs.push({
+      amount: remaining.amount,
+      coinPub: coin.coinPub,
+    });
     checkDbInvariant(!!coinAvailability);
     if (coinAvailability.freshCoinCount === 0) {
       throw Error(
@@ -150,9 +163,6 @@ export async function spendCoins(
     await tx.coins.put(coin);
     await tx.coinAvailability.put(coinAvailability);
   }
-  const refreshCoinPubs = csi.coinPubs.map((x) => ({
-    coinPub: x,
-  }));
   await ws.refreshOps.createRefreshGroup(
     ws,
     tx,
diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts 
b/packages/taler-wallet-core/src/operations/pay-merchant.ts
index 6b14b60c6..2b0ea1f96 100644
--- a/packages/taler-wallet-core/src/operations/pay-merchant.ts
+++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts
@@ -40,7 +40,8 @@ import {
   codecForMerchantPayResponse,
   codecForProposal,
   CoinDepositPermission,
-  CoinPublicKey,
+  CoinRefreshRequest,
+  CoinStatus,
   ConfirmPayResult,
   ConfirmPayResultType,
   ContractTerms,
@@ -78,7 +79,6 @@ import {
   AllowedExchangeInfo,
   BackupProviderStateTag,
   CoinRecord,
-  CoinStatus,
   DenominationRecord,
   PurchaseRecord,
   PurchaseStatus,
@@ -2084,7 +2084,7 @@ async function applySuccessfulRefund(
     denominations: typeof WalletStoresV1.denominations;
   }>,
   p: PurchaseRecord,
-  refreshCoinsMap: Record<string, { coinPub: string }>,
+  refreshCoinsMap: Record<string, CoinRefreshRequest>,
   r: MerchantCoinRefundSuccessStatus,
 ): Promise<void> {
   // FIXME: check signature before storing it as valid!
@@ -2102,31 +2102,23 @@ async function applySuccessfulRefund(
   if (!denom) {
     throw Error("inconsistent database");
   }
-  refreshCoinsMap[coin.coinPub] = { coinPub: coin.coinPub };
   const refundAmount = Amounts.parseOrThrow(r.refund_amount);
   const refundFee = denom.fees.feeRefund;
+  const amountLeft = Amounts.sub(refundAmount, refundFee).amount;
   coin.status = CoinStatus.Dormant;
-  coin.currentAmount = Amounts.add(coin.currentAmount, refundAmount).amount;
-  coin.currentAmount = Amounts.sub(coin.currentAmount, refundFee).amount;
-  logger.trace(`coin amount after is 
${Amounts.stringify(coin.currentAmount)}`);
   await tx.coins.put(coin);
 
   const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl
     .iter(coin.exchangeBaseUrl)
     .toArray();
-
-  const amountLeft = Amounts.sub(
-    Amounts.add(coin.currentAmount, Amounts.parseOrThrow(r.refund_amount))
-      .amount,
-    denom.fees.feeRefund,
-  ).amount;
-
   const totalRefreshCostBound = getTotalRefreshCost(
     allDenoms,
     DenominationRecord.toDenomInfo(denom),
     amountLeft,
   );
 
+  refreshCoinsMap[coin.coinPub] = { coinPub: coin.coinPub, amount: amountLeft 
};
+
   p.refunds[refundKey] = {
     type: RefundState.Applied,
     obtainedTime: AbsoluteTime.toTimestamp(AbsoluteTime.now()),
@@ -2167,9 +2159,9 @@ async function storePendingRefund(
     .iter(coin.exchangeBaseUrl)
     .toArray();
 
+  // Refunded amount after fees.
   const amountLeft = Amounts.sub(
-    Amounts.add(coin.currentAmount, Amounts.parseOrThrow(r.refund_amount))
-      .amount,
+    Amounts.parseOrThrow(r.refund_amount),
     denom.fees.feeRefund,
   ).amount;
 
@@ -2197,7 +2189,7 @@ async function storeFailedRefund(
     denominations: typeof WalletStoresV1.denominations;
   }>,
   p: PurchaseRecord,
-  refreshCoinsMap: Record<string, { coinPub: string }>,
+  refreshCoinsMap: Record<string, CoinRefreshRequest>,
   r: MerchantCoinRefundFailureStatus,
 ): Promise<void> {
   const refundKey = getRefundKey(r);
@@ -2221,8 +2213,7 @@ async function storeFailedRefund(
     .toArray();
 
   const amountLeft = Amounts.sub(
-    Amounts.add(coin.currentAmount, Amounts.parseOrThrow(r.refund_amount))
-      .amount,
+    Amounts.parseOrThrow(r.refund_amount),
     denom.fees.feeRefund,
   ).amount;
 
@@ -2246,6 +2237,7 @@ async function storeFailedRefund(
   if (p.purchaseStatus === PurchaseStatus.AbortingWithRefund) {
     // Refund failed because the merchant didn't even try to deposit
     // the coin yet, so we try to refresh.
+    // FIXME: Is this case tested?!
     if (r.exchange_code === TalerErrorCode.EXCHANGE_REFUND_DEPOSIT_NOT_FOUND) {
       const coin = await tx.coins.get(r.coin_pub);
       if (!coin) {
@@ -2271,14 +2263,11 @@ async function storeFailedRefund(
           contrib = payCoinSelection.coinContributions[i];
         }
       }
-      if (contrib) {
-        coin.currentAmount = Amounts.add(coin.currentAmount, contrib).amount;
-        coin.currentAmount = Amounts.sub(
-          coin.currentAmount,
-          denom.fees.feeRefund,
-        ).amount;
-      }
-      refreshCoinsMap[coin.coinPub] = { coinPub: coin.coinPub };
+      // FIXME: Is this case tested?!
+      refreshCoinsMap[coin.coinPub] = {
+        coinPub: coin.coinPub,
+        amount: amountLeft,
+      };
       await tx.coins.put(coin);
     }
   }
@@ -2308,7 +2297,7 @@ async function acceptRefunds(
         return;
       }
 
-      const refreshCoinsMap: Record<string, CoinPublicKey> = {};
+      const refreshCoinsMap: Record<string, CoinRefreshRequest> = {};
 
       for (const refundStatus of refunds) {
         const refundKey = getRefundKey(refundStatus);
@@ -2350,6 +2339,7 @@ async function acceptRefunds(
       }
 
       const refreshCoinsPubs = Object.values(refreshCoinsMap);
+      logger.info(`refreshCoinMap ${j2s(refreshCoinsMap)}`);
       if (refreshCoinsPubs.length > 0) {
         await createRefreshGroup(
           ws,
diff --git a/packages/taler-wallet-core/src/operations/pay-peer.ts 
b/packages/taler-wallet-core/src/operations/pay-peer.ts
index ffc49c24c..3b65fba6b 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer.ts
@@ -36,6 +36,7 @@ import {
   codecForAmountString,
   codecForAny,
   codecForExchangeGetContractResponse,
+  CoinStatus,
   constructPayPullUri,
   constructPayPushUri,
   ContractTermsUtil,
@@ -63,17 +64,16 @@ import {
   WalletAccountMergeFlags,
 } from "@gnu-taler/taler-util";
 import {
-  CoinStatus,
-  WithdrawalGroupStatus,
+  ReserveRecord,
   WalletStoresV1,
+  WithdrawalGroupStatus,
   WithdrawalRecordType,
-  ReserveRecord,
 } from "../db.js";
 import { InternalWalletState } from "../internal-wallet-state.js";
+import { makeTransactionId, spendCoins } from "../operations/common.js";
 import { readSuccessResponseJsonOrThrow } from "../util/http.js";
 import { checkDbInvariant } from "../util/invariants.js";
 import { GetReadOnlyAccess } from "../util/query.js";
-import { spendCoins, makeTransactionId } from "../operations/common.js";
 import { updateExchangeFromUrl } from "./exchanges.js";
 import { internalCreateWithdrawalGroup } from "./withdraw.js";
 
diff --git a/packages/taler-wallet-core/src/operations/recoup.ts 
b/packages/taler-wallet-core/src/operations/recoup.ts
index ff6bb4efc..d3bcde048 100644
--- a/packages/taler-wallet-core/src/operations/recoup.ts
+++ b/packages/taler-wallet-core/src/operations/recoup.ts
@@ -28,6 +28,7 @@ import {
   Amounts,
   codecForRecoupConfirmation,
   codecForReserveStatus,
+  CoinStatus,
   encodeCrock,
   getRandomBytes,
   j2s,
@@ -40,7 +41,6 @@ import {
 import {
   CoinRecord,
   CoinSourceType,
-  CoinStatus,
   RecoupGroupRecord,
   RefreshCoinSource,
   WalletStoresV1,
@@ -50,6 +50,7 @@ import {
 } from "../db.js";
 import { InternalWalletState } from "../internal-wallet-state.js";
 import { readSuccessResponseJsonOrThrow } from "../util/http.js";
+import { checkDbInvariant } from "../util/invariants.js";
 import { GetReadWriteAccess } from "../util/query.js";
 import {
   OperationAttemptResult,
@@ -180,8 +181,6 @@ async function recoupWithdrawCoin(
         return;
       }
       updatedCoin.status = CoinStatus.Dormant;
-      const currency = updatedCoin.currentAmount.currency;
-      updatedCoin.currentAmount = Amounts.getZero(currency);
       await tx.coins.put(updatedCoin);
       await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
     });
@@ -265,16 +264,25 @@ async function recoupRefreshCoin(
         logger.warn("refresh old coin for recoup not found");
         return;
       }
-      revokedCoin.status = CoinStatus.Dormant;
-      oldCoin.currentAmount = Amounts.add(
-        oldCoin.currentAmount,
-        recoupGroup.oldAmountPerCoin[coinIdx],
-      ).amount;
-      logger.trace(
-        "recoup: setting old coin amount to",
-        Amounts.stringify(oldCoin.currentAmount),
+      const oldCoinDenom = await ws.getDenomInfo(
+        ws,
+        tx,
+        oldCoin.exchangeBaseUrl,
+        oldCoin.denomPubHash,
       );
-      recoupGroup.scheduleRefreshCoins.push(oldCoin.coinPub);
+      const revokedCoinDenom = await ws.getDenomInfo(
+        ws,
+        tx,
+        revokedCoin.exchangeBaseUrl,
+        revokedCoin.denomPubHash,
+      );
+      checkDbInvariant(!!oldCoinDenom);
+      checkDbInvariant(!!revokedCoinDenom);
+      revokedCoin.status = CoinStatus.Dormant;
+      recoupGroup.scheduleRefreshCoins.push({
+        coinPub: oldCoin.coinPub,
+        amount: Amounts.sub(oldCoinDenom.value, revokedCoinDenom.value).amount,
+      });
       await tx.coins.put(revokedCoin);
       await tx.coins.put(oldCoin);
       await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
@@ -410,7 +418,7 @@ export async function processRecoupGroupHandler(
         const refreshGroupId = await createRefreshGroup(
           ws,
           tx,
-          rg2.scheduleRefreshCoins.map((x) => ({ coinPub: x })),
+          rg2.scheduleRefreshCoins,
           RefreshReason.Recoup,
         );
         processRefreshGroup(ws, refreshGroupId.refreshGroupId).catch((e) => {
@@ -442,8 +450,6 @@ export async function createRecoupGroup(
     timestampFinished: undefined,
     timestampStarted: TalerProtocolTimestamp.now(),
     recoupFinishedPerCoin: coinPubs.map(() => false),
-    // Will be populated later
-    oldAmountPerCoin: [],
     scheduleRefreshCoins: [],
   };
 
@@ -454,12 +460,6 @@ export async function createRecoupGroup(
       await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
       continue;
     }
-    if (Amounts.isZero(coin.currentAmount)) {
-      await putGroupAsFinished(ws, tx, recoupGroup, coinIdx);
-      continue;
-    }
-    recoupGroup.oldAmountPerCoin[coinIdx] = coin.currentAmount;
-    coin.currentAmount = Amounts.getZero(coin.currentAmount.currency);
     await tx.coins.put(coin);
   }
 
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts 
b/packages/taler-wallet-core/src/operations/refresh.ts
index 83ab32f20..c7d2c320e 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -23,8 +23,9 @@ import {
   amountToPretty,
   codecForExchangeMeltResponse,
   codecForExchangeRevealResponse,
-  CoinPublicKey,
   CoinPublicKeyString,
+  CoinRefreshRequest,
+  CoinStatus,
   DenominationInfo,
   DenomKeyType,
   Duration,
@@ -55,9 +56,7 @@ import { CryptoApiStoppedError } from 
"../crypto/workers/cryptoDispatcher.js";
 import {
   CoinRecord,
   CoinSourceType,
-  CoinStatus,
   DenominationRecord,
-  OperationStatus,
   RefreshCoinStatus,
   RefreshGroupRecord,
   RefreshOperationStatus,
@@ -672,7 +671,6 @@ async function refreshReveal(
         blindingKey: pc.blindingKey,
         coinPriv: pc.coinPriv,
         coinPub: pc.coinPub,
-        currentAmount: ncd.value,
         denomPubHash: ncd.denomPubHash,
         denomSig,
         exchangeBaseUrl: oldCoin.exchangeBaseUrl,
@@ -684,7 +682,7 @@ async function refreshReveal(
         coinEvHash: pc.coinEvHash,
         maxAge: pc.maxAge,
         ageCommitmentProof: pc.ageCommitmentProof,
-        allocation: undefined,
+        spendAllocation: undefined,
       };
 
       coins.push(coin);
@@ -845,7 +843,7 @@ export async function createRefreshGroup(
     refreshGroups: typeof WalletStoresV1.refreshGroups;
     coinAvailability: typeof WalletStoresV1.coinAvailability;
   }>,
-  oldCoinPubs: CoinPublicKey[],
+  oldCoinPubs: CoinRefreshRequest[],
   reason: RefreshReason,
 ): Promise<RefreshGroupId> {
   const refreshGroupId = encodeCrock(getRandomBytes(32));
@@ -908,9 +906,8 @@ export async function createRefreshGroup(
       default:
         assertUnreachable(coin.status);
     }
-    const refreshAmount = coin.currentAmount;
+    const refreshAmount = ocp.amount;
     inputPerCoin.push(refreshAmount);
-    coin.currentAmount = Amounts.getZero(refreshAmount.currency);
     await tx.coins.put(coin);
     const denoms = await getDenoms(coin.exchangeBaseUrl);
     const cost = getTotalRefreshCost(denoms, denom, refreshAmount);
@@ -1008,7 +1005,7 @@ export async function autoRefresh(
       const coins = await tx.coins.indexes.byBaseUrl
         .iter(exchangeBaseUrl)
         .toArray();
-      const refreshCoins: CoinPublicKey[] = [];
+      const refreshCoins: CoinRefreshRequest[] = [];
       for (const coin of coins) {
         if (coin.status !== CoinStatus.Fresh) {
           continue;
@@ -1023,7 +1020,14 @@ export async function autoRefresh(
         }
         const executeThreshold = getAutoRefreshExecuteThreshold(denom);
         if (AbsoluteTime.isExpired(executeThreshold)) {
-          refreshCoins.push(coin);
+          refreshCoins.push({
+            coinPub: coin.coinPub,
+            amount: {
+              value: denom.amountVal,
+              fraction: denom.amountFrac,
+              currency: denom.currency,
+            },
+          });
         } else {
           const checkThreshold = getAutoRefreshCheckThreshold(denom);
           minCheckThreshold = AbsoluteTime.min(
diff --git a/packages/taler-wallet-core/src/operations/tip.ts 
b/packages/taler-wallet-core/src/operations/tip.ts
index b74e1182a..f98d69e26 100644
--- a/packages/taler-wallet-core/src/operations/tip.ts
+++ b/packages/taler-wallet-core/src/operations/tip.ts
@@ -24,6 +24,7 @@ import {
   BlindedDenominationSignature,
   codecForMerchantTipResponseV2,
   codecForTipPickupGetResponse,
+  CoinStatus,
   DenomKeyType,
   encodeCrock,
   getRandomBytes,
@@ -41,7 +42,6 @@ import { DerivedTipPlanchet } from "../crypto/cryptoTypes.js";
 import {
   CoinRecord,
   CoinSourceType,
-  CoinStatus,
   DenominationRecord,
   TipRecord,
 } from "../db.js";
@@ -311,7 +311,6 @@ export async function processTip(
         coinIndex: i,
         walletTipId: walletTipId,
       },
-      currentAmount: DenominationRecord.getValue(denom),
       denomPubHash: denom.denomPubHash,
       denomSig: { cipher: DenomKeyType.Rsa, rsa_signature: denomSigRsa.sig },
       exchangeBaseUrl: tipRecord.exchangeBaseUrl,
@@ -319,7 +318,7 @@ export async function processTip(
       coinEvHash: planchet.coinEvHash,
       maxAge: AgeRestriction.AGE_UNRESTRICTED,
       ageCommitmentProof: planchet.ageCommitmentProof,
-      allocation: undefined,
+      spendAllocation: undefined,
     });
   }
 
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts 
b/packages/taler-wallet-core/src/operations/transactions.ts
index c7ff4161a..1e7f982bc 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -540,7 +540,6 @@ function buildTransactionForTip(
 
 /**
  * For a set of refund with the same executionTime.
- *
  */
 interface MergedRefundInfo {
   executionTime: TalerProtocolTimestamp;
@@ -556,7 +555,7 @@ function mergeRefundByExecutionTime(
   const refundByExecTime = rs.reduce((prev, refund) => {
     const key = `${refund.executionTime.t_s}`;
 
-    //refunds counts if applied
+    // refunds count if applied
     const effective =
       refund.type === RefundState.Applied
         ? Amounts.sub(
@@ -582,7 +581,10 @@ function mergeRefundByExecutionTime(
         v.amountAppliedEffective,
         effective,
       ).amount;
-      v.amountAppliedRaw = Amounts.add(v.amountAppliedRaw).amount;
+      v.amountAppliedRaw = Amounts.add(
+        v.amountAppliedRaw,
+        refund.refundAmount,
+      ).amount;
       v.firstTimestamp = TalerProtocolTimestamp.min(
         v.firstTimestamp,
         refund.obtainedTime,
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts 
b/packages/taler-wallet-core/src/operations/withdraw.ts
index a258c5d76..d7627e6cf 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -36,6 +36,7 @@ import {
   codecForWithdrawBatchResponse,
   codecForWithdrawOperationStatusResponse,
   codecForWithdrawResponse,
+  CoinStatus,
   DenomKeyType,
   DenomSelectionState,
   Duration,
@@ -57,7 +58,6 @@ import {
   TransactionType,
   UnblindedSignature,
   URL,
-  VersionMatchResult,
   WithdrawBatchResponse,
   WithdrawResponse,
   WithdrawUriInfoResponse,
@@ -66,10 +66,8 @@ import { EddsaKeypair } from 
"../crypto/cryptoImplementation.js";
 import {
   CoinRecord,
   CoinSourceType,
-  CoinStatus,
   DenominationRecord,
   DenominationVerificationStatus,
-  ExchangeTosRecord,
   PlanchetRecord,
   PlanchetStatus,
   WalletStoresV1,
@@ -736,7 +734,6 @@ async function processPlanchetVerifyAndStoreCoin(
     blindingKey: planchet.blindingKey,
     coinPriv: planchet.coinPriv,
     coinPub: planchet.coinPub,
-    currentAmount: denomInfo.value,
     denomPubHash: planchet.denomPubHash,
     denomSig,
     coinEvHash: planchet.coinEvHash,
@@ -750,7 +747,7 @@ async function processPlanchetVerifyAndStoreCoin(
     },
     maxAge: planchet.maxAge,
     ageCommitmentProof: planchet.ageCommitmentProof,
-    allocation: undefined,
+    spendAllocation: undefined,
   };
 
   const planchetCoinPub = planchet.coinPub;
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index ef7a745ab..ef41c5101 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -95,6 +95,8 @@ import {
   WalletNotification,
   codecForSetDevModeRequest,
   ExchangeTosStatusDetails,
+  CoinRefreshRequest,
+  CoinStatus,
 } from "@gnu-taler/taler-util";
 import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
 import {
@@ -105,11 +107,9 @@ import { clearDatabase } from "./db-utils.js";
 import {
   AuditorTrustRecord,
   CoinSourceType,
-  CoinStatus,
   ConfigRecordKey,
   DenominationRecord,
   ExchangeDetailsRecord,
-  ExchangeTosRecord,
   exportDb,
   importDb,
   WalletStoresV1,
@@ -934,10 +934,15 @@ async function dumpCoins(ws: InternalWalletState): 
Promise<CoinDumpJson> {
           }),
           exchange_base_url: c.exchangeBaseUrl,
           refresh_parent_coin_pub: refreshParentCoinPub,
-          remaining_value: Amounts.stringify(c.currentAmount),
           withdrawal_reserve_pub: withdrawalReservePub,
-          coin_suspended: c.status === CoinStatus.FreshSuspended,
+          coin_status: c.status,
           ageCommitmentProof: c.ageCommitmentProof,
+          spend_allocation: c.spendAllocation
+            ? {
+                amount: c.spendAllocation.amount,
+                id: c.spendAllocation.id,
+              }
+            : undefined,
         });
       }
     });
@@ -1153,7 +1158,6 @@ async function dispatchRequestInternal(
     }
     case "forceRefresh": {
       const req = codecForForceRefreshRequest().decode(payload);
-      const coinPubs = req.coinPubList.map((x) => ({ coinPub: x }));
       const refreshGroupId = await ws.db
         .mktx((x) => [
           x.refreshGroups,
@@ -1162,6 +1166,24 @@ async function dispatchRequestInternal(
           x.coins,
         ])
         .runReadWrite(async (tx) => {
+          let coinPubs: CoinRefreshRequest[] = [];
+          for (const c of req.coinPubList) {
+            const coin = await tx.coins.get(c);
+            if (!coin) {
+              throw Error(`coin (pubkey ${c}) not found`);
+            }
+            const denom = await ws.getDenomInfo(
+              ws,
+              tx,
+              coin.exchangeBaseUrl,
+              coin.denomPubHash,
+            );
+            checkDbInvariant(!!denom);
+            coinPubs.push({
+              coinPub: c,
+              amount: denom?.value,
+            });
+          }
           return await createRefreshGroup(
             ws,
             tx,

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