gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated (b011c8a32 -> a57fcb144)


From: gnunet
Subject: [taler-wallet-core] branch master updated (b011c8a32 -> a57fcb144)
Date: Fri, 14 Oct 2022 22:10:13 +0200

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

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

    from b011c8a32 terms and privacy on exchange selection
     new 398e79d0d -remove obsolete README section
     new f1cba79c6 wallet-core: DB tweaks
     new a57fcb144 wallet-core: pull out ToS into separate object store

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 packages/taler-util/src/backupTypes.ts             |   2 -
 packages/taler-util/src/walletTypes.ts             |  15 +--
 packages/taler-wallet-core/src/db.ts               | 126 ++++++++++++---------
 .../taler-wallet-core/src/operations/README.md     |  19 ----
 .../src/operations/backup/export.ts                |   7 +-
 .../src/operations/backup/import.ts                |  27 +++--
 .../taler-wallet-core/src/operations/exchanges.ts  |  47 +++++---
 .../taler-wallet-core/src/operations/pending.ts    |   9 +-
 .../taler-wallet-core/src/operations/refresh.ts    |  10 +-
 .../taler-wallet-core/src/operations/withdraw.ts   |  52 +++++----
 packages/taler-wallet-core/src/wallet.ts           |  89 +++++++++++----
 11 files changed, 244 insertions(+), 159 deletions(-)

diff --git a/packages/taler-util/src/backupTypes.ts 
b/packages/taler-util/src/backupTypes.ts
index a1506e90f..0270f2586 100644
--- a/packages/taler-util/src/backupTypes.ts
+++ b/packages/taler-util/src/backupTypes.ts
@@ -1165,8 +1165,6 @@ export interface BackupExchange {
 
   currency: string;
 
-  protocol_version_range: string;
-
   /**
    * Time when the pointer to the exchange details
    * was last updated.
diff --git a/packages/taler-util/src/walletTypes.ts 
b/packages/taler-util/src/walletTypes.ts
index 0891f5cf9..0b2ef1d5f 100644
--- a/packages/taler-util/src/walletTypes.ts
+++ b/packages/taler-util/src/walletTypes.ts
@@ -620,7 +620,7 @@ export interface KnownBankAccounts {
   accounts: KnownBankAccountsInfo[];
 }
 
-export interface ExchangeTos {
+export interface ExchangeTosStatusDetails {
   acceptedVersion?: string;
   currentVersion?: string;
   contentType?: string;
@@ -671,6 +671,7 @@ export interface ExchangeAccount {
 }
 
 export type WireFeeMap = { [wireMethod: string]: WireFee[] };
+
 export interface WireInfo {
   feesForType: WireFeeMap;
   accounts: ExchangeAccount[];
@@ -804,7 +805,7 @@ export interface ExchangeFullDetails {
   exchangeBaseUrl: string;
   currency: string;
   paytoUris: string[];
-  tos: ExchangeTos;
+  tos: ExchangeTosStatusDetails;
   auditors: ExchangeAuditor[];
   wireInfo: WireInfo;
   denomFees: DenomOperationMap<FeeDescription[]>;
@@ -816,7 +817,7 @@ export interface ExchangeListItem {
   exchangeBaseUrl: string;
   currency: string;
   paytoUris: string[];
-  tos: ExchangeTos;
+  tos: ExchangeTosStatusDetails;
 }
 
 const codecForAuditorDenomSig = (): Codec<AuditorDenomSig> =>
@@ -832,8 +833,8 @@ const codecForExchangeAuditor = (): Codec<ExchangeAuditor> 
=>
     .property("denomination_keys", codecForList(codecForAuditorDenomSig()))
     .build("codecForExchangeAuditor");
 
-const codecForExchangeTos = (): Codec<ExchangeTos> =>
-  buildCodecForObject<ExchangeTos>()
+const codecForExchangeTos = (): Codec<ExchangeTosStatusDetails> =>
+  buildCodecForObject<ExchangeTosStatusDetails>()
     .property("acceptedVersion", codecOptional(codecForString()))
     .property("currentVersion", codecOptional(codecForString()))
     .property("contentType", codecOptional(codecForString()))
@@ -942,7 +943,7 @@ export interface ManualWithdrawalDetails {
 /**
  * Selected denominations withn some extra info.
  */
- export interface DenomSelectionState {
+export interface DenomSelectionState {
   totalCoinValue: AmountJson;
   totalWithdrawCost: AmountJson;
   selectedDenoms: {
@@ -956,7 +957,7 @@ export interface ManualWithdrawalDetails {
  *
  * Sent to the wallet frontend to be rendered and shown to the user.
  */
- export interface ExchangeWithdrawalDetails {
+export interface ExchangeWithdrawalDetails {
   exchangePaytoUris: string[];
 
   /**
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index 8f7f22292..304efd852 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -80,7 +80,7 @@ import { Event, IDBDatabase } from "@gnu-taler/idb-bridge";
  * for all previous versions must be written, which should be
  * avoided.
  */
-export const TALER_DB_NAME = "taler-wallet-main-v6";
+export const TALER_DB_NAME = "taler-wallet-main-v7";
 
 /**
  * Name of the metadata database.  This database is used
@@ -99,13 +99,23 @@ export const CURRENT_DB_CONFIG_KEY = "currentMainDbName";
  * backwards-compatible way or object stores and indices
  * are added.
  */
-export const WALLET_DB_MINOR_VERSION = 2;
+export const WALLET_DB_MINOR_VERSION = 1;
 
+/**
+ * Ranges for operation status fields.
+ *
+ * All individual enums should make sure that the values they
+ * defined are in the right range.
+ */
 export enum OperationStatusRange {
+  // Operations that need to be actively processed.
   ACTIVE_START = 10,
   ACTIVE_END = 29,
+  // Operations that need user input, but nothing can be done
+  // automatically.
   USER_ATTENTION_START = 30,
   USER_ATTENTION_END = 49,
+  // Operations that don't need any attention or processing.
   DORMANT_START = 50,
   DORMANT_END = 69,
 }
@@ -150,7 +160,7 @@ export enum WithdrawalGroupStatus {
 }
 
 /**
- * Extra info about a reserve that is used
+ * Extra info about a withdrawal that is used
  * with a bank-integrated withdrawal.
  */
 export interface ReserveBankInfo {
@@ -397,13 +407,9 @@ export namespace DenominationRecord {
 }
 
 /**
- * Information about one of the exchange's bank accounts.
+ * Exchange details for a particular
+ * (exchangeBaseUrl, masterPublicKey, currency) tuple.
  */
-export interface ExchangeBankAccount {
-  payto_uri: string;
-  master_sig: string;
-}
-
 export interface ExchangeDetailsRecord {
   /**
    * Master public key of the exchange.
@@ -425,7 +431,7 @@ export interface ExchangeDetailsRecord {
   /**
    * Last observed protocol version.
    */
-  protocolVersion: string;
+  protocolVersionRange: string;
 
   reserveClosingDelay: TalerProtocolDuration;
 
@@ -445,39 +451,40 @@ export interface ExchangeDetailsRecord {
   signingKeys: ExchangeSignKeyJson[];
 
   /**
-   * Terms of service text or undefined if not downloaded yet.
-   *
-   * This is just used as a cache of the last downloaded ToS.
-   *
-   * FIXME:  Put in separate object store!
+   * Etag of the current ToS of the exchange.
    */
-  termsOfServiceText: string | undefined;
+  tosCurrentEtag: string;
 
   /**
-   * content-type of the last downloaded termsOfServiceText.
-   *
-   * * FIXME:  Put in separate object store!
+   * Information about ToS acceptance from the user.
    */
-  termsOfServiceContentType: string | undefined;
+  tosAccepted:
+    | {
+        etag: string;
+        timestamp: TalerProtocolTimestamp;
+      }
+    | undefined;
 
-  /**
-   * ETag for last terms of service download.
-   */
-  termsOfServiceLastEtag: string | undefined;
+  wireInfo: WireInfo;
+}
 
-  /**
-   * ETag for last terms of service accepted.
-   */
-  termsOfServiceAcceptedEtag: string | undefined;
+export interface ExchangeTosRecord {
+  exchangeBaseUrl: string;
+
+  etag: string;
 
   /**
-   * Timestamp when the ToS was accepted.
+   * Terms of service text or undefined if not downloaded yet.
+   *
+   * This is just used as a cache of the last downloaded ToS.
    *
-   * Used during backup merging.
    */
-  termsOfServiceAcceptedTimestamp: TalerProtocolTimestamp | undefined;
+  termsOfServiceText: string | undefined;
 
-  wireInfo: WireInfo;
+  /**
+   * Content-type of the last downloaded termsOfServiceText.
+   */
+  termsOfServiceContentType: string | undefined;
 }
 
 export interface ExchangeDetailsPointer {
@@ -485,11 +492,6 @@ export interface ExchangeDetailsPointer {
 
   currency: string;
 
-  /**
-   * Last observed protocol version range offered by the exchange.
-   */
-  protocolVersionRange: string;
-
   /**
    * Timestamp when the (masterPublicKey, currency) pointer
    * has been updated.
@@ -513,6 +515,10 @@ export interface ExchangeRecord {
 
   /**
    * Pointer to the current exchange details.
+   *
+   * Should usually not change.  Only changes when the
+   * exchange advertises a different master public key and/or
+   * currency.
    */
   detailsPointer: ExchangeDetailsPointer | undefined;
 
@@ -554,6 +560,11 @@ export interface ExchangeRecord {
   currentMergeReserveInfo?: MergeReserveInfo;
 }
 
+export enum PlanchetStatus {
+  Pending = 10 /* ACTIVE_START */,
+  WithdrawalDone = 50 /* DORMANT_START */,
+}
+
 /**
  * A coin that isn't yet signed by an exchange.
  */
@@ -579,10 +590,7 @@ export interface PlanchetRecord {
    */
   coinIdx: number;
 
-  /**
-   * FIXME: make this an enum!
-   */
-  withdrawalDone: boolean;
+  planchetStatus: PlanchetStatus;
 
   lastError: TalerErrorDetail | undefined;
 
@@ -743,12 +751,21 @@ export interface CoinRecord {
    */
   allocation: CoinAllocation | undefined;
 
+  /**
+   * Maximum age of purchases that can be made with this coin.
+   *
+   * FIXME: Not used for indexing, isn't it redundant?
+   */
   maxAge: number;
 
   ageCommitmentProof: AgeCommitmentProof | undefined;
 }
 
+/**
+ * Coin allocation, i.e. what a coin has been used for.
+ */
 export interface CoinAllocation {
+  // FIXME: Specify format!
   id: string;
   amount: AmountString;
 }
@@ -839,8 +856,14 @@ export enum OperationStatus {
   Pending = OperationStatusRange.ACTIVE_START,
 }
 
+export enum RefreshOperationStatus {
+  Pending = 10 /* ACTIVE_START */,
+  Finished = 50 /* DORMANT_START */,
+  FinishedWithError = 51 /* DORMANT_START + 1 */,
+}
+
 export interface RefreshGroupRecord {
-  operationStatus: OperationStatus;
+  operationStatus: RefreshOperationStatus;
 
   // FIXME: Put this into a different object store?
   lastErrorPerCoin: { [coinIndex: number]: TalerErrorDetail };
@@ -880,13 +903,6 @@ export interface RefreshGroupRecord {
    * Timestamp when the refresh session finished.
    */
   timestampFinished: TalerProtocolTimestamp | undefined;
-
-  /**
-   * No coins are pending, but at least one is frozen.
-   *
-   * FIXME: What does this mean?
-   */
-  frozen?: boolean;
 }
 
 /**
@@ -1128,7 +1144,8 @@ export interface PurchasePayInfo {
  * Record that stores status information about one purchase, starting from when
  * the customer accepts a proposal.  Includes refund status if applicable.
  *
- * FIXME: Should have a single "status" field.
+ * Key: {@link proposalId}
+ * Operation status: {@link purchaseStatus}
  */
 export interface PurchaseRecord {
   /**
@@ -1878,6 +1895,14 @@ export const WalletStoresV1 = {
       byReservePub: describeIndex("byReservePub", "reservePub", {}),
     },
   ),
+  exchangeTos: describeStore(
+    "exchangeTos",
+    describeContents<ExchangeTosRecord>({
+      keyPath: ["exchangeBaseUrl", "etag"],
+      autoIncrement: true,
+    }),
+    {},
+  ),
   config: describeStore(
     "config",
     describeContents<ConfigRecord>({ keyPath: "key" }),
@@ -2095,7 +2120,6 @@ export const WalletStoresV1 = {
     "bankAccounts",
     describeContents<BankAccountsRecord>({
       keyPath: "uri",
-      versionAdded: 2,
     }),
     {},
   ),
diff --git a/packages/taler-wallet-core/src/operations/README.md 
b/packages/taler-wallet-core/src/operations/README.md
index 9a2937c0c..a40349d37 100644
--- a/packages/taler-wallet-core/src/operations/README.md
+++ b/packages/taler-wallet-core/src/operations/README.md
@@ -5,22 +5,3 @@ This folder contains the implementations for all wallet 
operations that operate
 To avoid cyclic dependencies, these files must **not** reference each other. 
Instead, other operations should only be accessed via injected dependencies.
 
 Avoiding cyclic dependencies is important for module bundlers.
-
-## Retries
-
-Many operations in the wallet are automatically retried when they fail or when 
the wallet
-is still waiting for some external condition (such as a wire transfer to the 
exchange).
-
-Retries are generally controlled by a "retryInfo" field in the corresponding 
database record. This field is set to undefined when no retry should be 
scheduled.
-
-Generally, the code to process a pending operation should first increment the
-retryInfo (and reset the lastError) and then process the operation. This way,
-it is impossble to forget incrementing the retryInfo.
-
-For each retriable operation, there are usually `setup<Op>Retry`, 
`increment<Op>Retry` and
-`report<Op>Error` operations.
-
-Note that this means that _during_ some operation, lastError will be cleared. 
The UI
-should accommodate for this.
-
-It would be possible to store a list of last errors, but we currently don't do 
that.
diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts 
b/packages/taler-wallet-core/src/operations/backup/export.ts
index c7890b5d8..30e61e382 100644
--- a/packages/taler-wallet-core/src/operations/backup/export.ts
+++ b/packages/taler-wallet-core/src/operations/backup/export.ts
@@ -298,7 +298,6 @@ export async function exportBackup(
           currency: dp.currency,
           master_public_key: dp.masterPublicKey,
           update_clock: dp.updateClock,
-          protocol_version_range: dp.protocolVersionRange,
         });
       });
 
@@ -336,7 +335,7 @@ export async function exportBackup(
           })),
           master_public_key: ex.masterPublicKey,
           currency: ex.currency,
-          protocol_version: ex.protocolVersion,
+          protocol_version: ex.protocolVersionRange,
           wire_fees: wireFees,
           signing_keys: ex.signingKeys.map((x) => ({
             key: x.key,
@@ -358,8 +357,8 @@ export async function exportBackup(
             purseTimeout: x.purseTimeout,
             startDate: x.startDate,
           })),
-          tos_accepted_etag: ex.termsOfServiceAcceptedEtag,
-          tos_accepted_timestamp: ex.termsOfServiceAcceptedTimestamp,
+          tos_accepted_etag: ex.tosAccepted?.etag,
+          tos_accepted_timestamp: ex.tosAccepted?.timestamp,
           denominations:
             backupDenominationsByExchange[ex.exchangeBaseUrl] ?? [],
         });
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts 
b/packages/taler-wallet-core/src/operations/backup/import.ts
index f4e6ab5eb..599b02dea 100644
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ b/packages/taler-wallet-core/src/operations/backup/import.ts
@@ -57,6 +57,7 @@ import {
   WgInfo,
   WithdrawalGroupStatus,
   WithdrawalRecordType,
+  RefreshOperationStatus,
 } from "../../db.js";
 import { InternalWalletState } from "../../internal-wallet-state.js";
 import { assertUnreachable } from "../../util/assertUnreachable.js";
@@ -350,7 +351,6 @@ export async function importBackup(
             currency: backupExchange.currency,
             masterPublicKey: backupExchange.master_public_key,
             updateClock: backupExchange.update_clock,
-            protocolVersionRange: backupExchange.protocol_version_range,
           },
           permanent: true,
           lastUpdate: undefined,
@@ -387,14 +387,18 @@ export async function importBackup(
               wadFee: Amounts.parseOrThrow(fee.wad_fee),
             });
           }
+          let tosAccepted = undefined;
+          if (
+            backupExchangeDetails.tos_accepted_etag &&
+            backupExchangeDetails.tos_accepted_timestamp
+          ) {
+            tosAccepted = {
+              etag: backupExchangeDetails.tos_accepted_etag,
+              timestamp: backupExchangeDetails.tos_accepted_timestamp,
+            };
+          }
           await tx.exchangeDetails.put({
             exchangeBaseUrl: backupExchangeDetails.base_url,
-            termsOfServiceAcceptedEtag: 
backupExchangeDetails.tos_accepted_etag,
-            termsOfServiceText: undefined,
-            termsOfServiceLastEtag: undefined,
-            termsOfServiceContentType: undefined,
-            termsOfServiceAcceptedTimestamp:
-              backupExchangeDetails.tos_accepted_timestamp,
             wireInfo,
             currency: backupExchangeDetails.currency,
             auditors: backupExchangeDetails.auditors.map((x) => ({
@@ -403,8 +407,10 @@ export async function importBackup(
               denomination_keys: x.denomination_keys,
             })),
             masterPublicKey: backupExchangeDetails.master_public_key,
-            protocolVersion: backupExchangeDetails.protocol_version,
+            protocolVersionRange: backupExchangeDetails.protocol_version,
             reserveClosingDelay: backupExchangeDetails.reserve_closing_delay,
+            tosCurrentEtag: backupExchangeDetails.tos_accepted_etag || "",
+            tosAccepted,
             globalFees: backupExchangeDetails.global_fees.map((x) => ({
               accountFee: Amounts.parseOrThrow(x.accountFee),
               historyFee: Amounts.parseOrThrow(x.historyFee),
@@ -418,7 +424,6 @@ export async function importBackup(
               purseTimeout: x.purseTimeout,
               startDate: x.startDate,
             })),
-
             signingKeys: backupExchangeDetails.signing_keys.map((x) => ({
               key: x.key,
               master_sig: x.master_sig,
@@ -773,8 +778,8 @@ export async function importBackup(
                 : RefreshCoinStatus.Pending,
             ),
             operationStatus: backupRefreshGroup.timestamp_finish
-              ? OperationStatus.Finished
-              : OperationStatus.Pending,
+              ? RefreshOperationStatus.Finished
+              : RefreshOperationStatus.Pending,
             inputPerCoin: backupRefreshGroup.old_coins.map((x) =>
               Amounts.parseOrThrow(x.input_amount),
             ),
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts 
b/packages/taler-wallet-core/src/operations/exchanges.ts
index 3da16e303..6569cb394 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -174,24 +174,40 @@ export async function getExchangeDetails(
 getExchangeDetails.makeContext = (db: DbAccess<typeof WalletStoresV1>) =>
   db.mktx((x) => [x.exchanges, x.exchangeDetails]);
 
+/**
+ * Update the database based on the download of the terms of service.
+ */
 export async function updateExchangeTermsOfService(
   ws: InternalWalletState,
   exchangeBaseUrl: string,
   tos: ExchangeTosDownloadResult,
 ): Promise<void> {
   await ws.db
-    .mktx((x) => [x.exchanges, x.exchangeDetails])
+    .mktx((x) => [x.exchanges, x.exchangeTos, x.exchangeDetails])
     .runReadWrite(async (tx) => {
       const d = await getExchangeDetails(tx, exchangeBaseUrl);
+      let tosRecord = await tx.exchangeTos.get([exchangeBaseUrl, tos.tosEtag]);
+      if (!tosRecord) {
+        tosRecord = {
+          etag: tos.tosEtag,
+          exchangeBaseUrl,
+          termsOfServiceContentType: tos.tosContentType,
+          termsOfServiceText: tos.tosText,
+        };
+        await tx.exchangeTos.put(tosRecord);
+      }
       if (d) {
-        d.termsOfServiceText = tos.tosText;
-        d.termsOfServiceContentType = tos.tosContentType;
-        d.termsOfServiceLastEtag = tos.tosEtag;
+        d.tosCurrentEtag = tos.tosEtag;
         await tx.exchangeDetails.put(d);
       }
     });
 }
 
+/**
+ * Mark a ToS version as accepted by the user.
+ * 
+ * @param etag version of the ToS to accept, or current ToS version of not 
given
+ */
 export async function acceptExchangeTermsOfService(
   ws: InternalWalletState,
   exchangeBaseUrl: string,
@@ -202,7 +218,10 @@ export async function acceptExchangeTermsOfService(
     .runReadWrite(async (tx) => {
       const d = await getExchangeDetails(tx, exchangeBaseUrl);
       if (d) {
-        d.termsOfServiceAcceptedEtag = etag;
+        d.tosAccepted = {
+          etag: etag || d.tosCurrentEtag,
+          timestamp: TalerProtocolTimestamp.now(),
+        };
         await tx.exchangeDetails.put(d);
       }
     });
@@ -611,7 +630,8 @@ export async function updateExchangeFromUrlHandler(
     ["text/plain"],
   );
   const tosHasBeenAccepted =
-    exchangeDetails?.termsOfServiceAcceptedEtag === tosDownload.tosEtag;
+    exchangeDetails?.tosAccepted &&
+    exchangeDetails.tosAccepted.etag === tosDownload.tosEtag;
 
   let recoupGroupId: string | undefined;
 
@@ -641,19 +661,19 @@ export async function updateExchangeFromUrlHandler(
         auditors: keysInfo.auditors,
         currency: keysInfo.currency,
         masterPublicKey: keysInfo.masterPublicKey,
-        protocolVersion: keysInfo.protocolVersion,
+        protocolVersionRange: keysInfo.protocolVersion,
         signingKeys: keysInfo.signingKeys,
         reserveClosingDelay: keysInfo.reserveClosingDelay,
         globalFees,
         exchangeBaseUrl: r.baseUrl,
         wireInfo,
-        termsOfServiceText: tosDownload.tosText,
-        termsOfServiceAcceptedEtag: tosHasBeenAccepted
-          ? tosDownload.tosEtag
+        tosCurrentEtag: tosDownload.tosContentType,
+        tosAccepted: tosHasBeenAccepted
+          ? {
+              etag: tosDownload.tosEtag,
+              timestamp: TalerProtocolTimestamp.now(),
+            }
           : undefined,
-        termsOfServiceContentType: tosDownload.tosContentType,
-        termsOfServiceLastEtag: tosDownload.tosEtag,
-        termsOfServiceAcceptedTimestamp: TalerProtocolTimestamp.now(),
       };
       // FIXME: only update if pointer got updated
       r.lastUpdate = TalerProtocolTimestamp.now();
@@ -665,7 +685,6 @@ export async function updateExchangeFromUrlHandler(
         masterPublicKey: details.masterPublicKey,
         // FIXME: only change if pointer really changed
         updateClock: TalerProtocolTimestamp.now(),
-        protocolVersionRange: keysInfo.protocolVersion,
       };
       await tx.exchanges.put(r);
       await tx.exchangeDetails.put(details);
diff --git a/packages/taler-wallet-core/src/operations/pending.ts 
b/packages/taler-wallet-core/src/operations/pending.ts
index 285cef534..d2066d4fc 100644
--- a/packages/taler-wallet-core/src/operations/pending.ts
+++ b/packages/taler-wallet-core/src/operations/pending.ts
@@ -106,16 +106,17 @@ async function gatherRefreshPending(
   now: AbsoluteTime,
   resp: PendingOperationsResponse,
 ): Promise<void> {
+  const keyRange = GlobalIDB.KeyRange.bound(
+    OperationStatusRange.ACTIVE_START,
+    OperationStatusRange.ACTIVE_END,
+  );
   const refreshGroups = await tx.refreshGroups.indexes.byStatus.getAll(
-    OperationStatus.Pending,
+    keyRange,
   );
   for (const r of refreshGroups) {
     if (r.timestampFinished) {
       return;
     }
-    if (r.frozen) {
-      return;
-    }
     const opId = RetryTags.forRefresh(r);
     const retryRecord = await tx.operationRetries.get(opId);
 
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts 
b/packages/taler-wallet-core/src/operations/refresh.ts
index a5951ea53..83ab32f20 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -60,6 +60,7 @@ import {
   OperationStatus,
   RefreshCoinStatus,
   RefreshGroupRecord,
+  RefreshOperationStatus,
   WalletStoresV1,
 } from "../db.js";
 import { TalerError } from "../errors.js";
@@ -139,10 +140,11 @@ function updateGroupStatus(rg: RefreshGroupRecord): void {
   );
   if (allDone) {
     if (anyFrozen) {
-      rg.frozen = true;
+      rg.timestampFinished = AbsoluteTime.toTimestamp(AbsoluteTime.now());
+      rg.operationStatus = RefreshOperationStatus.FinishedWithError;
     } else {
       rg.timestampFinished = AbsoluteTime.toTimestamp(AbsoluteTime.now());
-      rg.operationStatus = OperationStatus.Finished;
+      rg.operationStatus = RefreshOperationStatus.Finished;
     }
   }
 }
@@ -917,7 +919,7 @@ export async function createRefreshGroup(
   }
 
   const refreshGroup: RefreshGroupRecord = {
-    operationStatus: OperationStatus.Pending,
+    operationStatus: RefreshOperationStatus.Pending,
     timestampFinished: undefined,
     statusPerCoin: oldCoinPubs.map(() => RefreshCoinStatus.Pending),
     lastErrorPerCoin: {},
@@ -933,7 +935,7 @@ export async function createRefreshGroup(
   if (oldCoinPubs.length == 0) {
     logger.warn("created refresh group with zero coins");
     refreshGroup.timestampFinished = TalerProtocolTimestamp.now();
-    refreshGroup.operationStatus = OperationStatus.Finished;
+    refreshGroup.operationStatus = RefreshOperationStatus.Finished;
   }
 
   await tx.refreshGroups.put(refreshGroup);
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts 
b/packages/taler-wallet-core/src/operations/withdraw.ts
index e4bf6cd11..700c4620c 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -69,7 +69,9 @@ import {
   CoinStatus,
   DenominationRecord,
   DenominationVerificationStatus,
+  ExchangeTosRecord,
   PlanchetRecord,
+  PlanchetStatus,
   WalletStoresV1,
   WgInfo,
   WithdrawalGroupRecord,
@@ -430,7 +432,7 @@ async function processPlanchetGenerate(
     coinPub: r.coinPub,
     denomPubHash: r.denomPubHash,
     reservePub: r.reservePub,
-    withdrawalDone: false,
+    planchetStatus: PlanchetStatus.Pending,
     withdrawSig: r.withdrawSig,
     withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
     maxAge: withdrawalGroup.restrictAge ?? AgeRestriction.AGE_UNRESTRICTED,
@@ -481,7 +483,7 @@ async function processPlanchetExchangeRequest(
       if (!planchet) {
         return;
       }
-      if (planchet.withdrawalDone) {
+      if (planchet.planchetStatus === PlanchetStatus.WithdrawalDone) {
         logger.warn("processPlanchet: planchet already withdrawn");
         return;
       }
@@ -593,7 +595,7 @@ async function processPlanchetExchangeBatchRequest(
         if (!planchet) {
           return;
         }
-        if (planchet.withdrawalDone) {
+        if (planchet.planchetStatus === PlanchetStatus.WithdrawalDone) {
           logger.warn("processPlanchet: planchet already withdrawn");
           return;
         }
@@ -652,7 +654,7 @@ async function processPlanchetVerifyAndStoreCoin(
       if (!planchet) {
         return;
       }
-      if (planchet.withdrawalDone) {
+      if (planchet.planchetStatus === PlanchetStatus.WithdrawalDone) {
         logger.warn("processPlanchet: planchet already withdrawn");
         return;
       }
@@ -767,10 +769,10 @@ async function processPlanchetVerifyAndStoreCoin(
     ])
     .runReadWrite(async (tx) => {
       const p = await tx.planchets.get(planchetCoinPub);
-      if (!p || p.withdrawalDone) {
+      if (!p || p.planchetStatus === PlanchetStatus.WithdrawalDone) {
         return false;
       }
-      p.withdrawalDone = true;
+      p.planchetStatus = PlanchetStatus.WithdrawalDone;
       await tx.planchets.put(p);
       await makeCoinAvailable(ws, tx, coin);
       return true;
@@ -1140,7 +1142,7 @@ export async function processWithdrawalGroup(
       await tx.planchets.indexes.byGroup
         .iter(withdrawalGroupId)
         .forEach((x) => {
-          if (x.withdrawalDone) {
+          if (x.planchetStatus === PlanchetStatus.WithdrawalDone) {
             numFinished++;
           }
           if (x.lastError) {
@@ -1258,10 +1260,10 @@ export async function getExchangeWithdrawalInfo(
     });
 
   let versionMatch;
-  if (exchangeDetails.protocolVersion) {
+  if (exchangeDetails.protocolVersionRange) {
     versionMatch = LibtoolVersion.compare(
       WALLET_EXCHANGE_PROTOCOL_VERSION,
-      exchangeDetails.protocolVersion,
+      exchangeDetails.protocolVersionRange,
     );
 
     if (
@@ -1271,18 +1273,14 @@ export async function getExchangeWithdrawalInfo(
     ) {
       logger.warn(
         `wallet's support for exchange protocol version 
${WALLET_EXCHANGE_PROTOCOL_VERSION} might be outdated ` +
-          `(exchange has ${exchangeDetails.protocolVersion}), checking for 
updates`,
+          `(exchange has ${exchangeDetails.protocolVersionRange}), checking 
for updates`,
       );
     }
   }
 
   let tosAccepted = false;
-
-  if (exchangeDetails.termsOfServiceLastEtag) {
-    if (
-      exchangeDetails.termsOfServiceAcceptedEtag ===
-      exchangeDetails.termsOfServiceLastEtag
-    ) {
+  if (exchangeDetails.tosAccepted?.timestamp) {
+    if (exchangeDetails.tosAccepted.etag === exchangeDetails.tosCurrentEtag) {
       tosAccepted = true;
     }
   }
@@ -1296,7 +1294,7 @@ export async function getExchangeWithdrawalInfo(
     earliestDepositExpiration,
     exchangePaytoUris: paytoUris,
     exchangeWireAccounts,
-    exchangeVersion: exchangeDetails.protocolVersion || "unknown",
+    exchangeVersion: exchangeDetails.protocolVersionRange || "unknown",
     isAudited,
     isTrusted,
     numOfferedDenoms: possibleDenoms.length,
@@ -1356,7 +1354,12 @@ export async function getWithdrawalDetailsForUri(
   const exchanges: ExchangeListItem[] = [];
 
   await ws.db
-    .mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations])
+    .mktx((x) => [
+      x.exchanges,
+      x.exchangeDetails,
+      x.exchangeTos,
+      x.denominations,
+    ])
     .runReadOnly(async (tx) => {
       const exchangeRecords = await tx.exchanges.iter().toArray();
       for (const r of exchangeRecords) {
@@ -1365,14 +1368,19 @@ export async function getWithdrawalDetailsForUri(
           .iter(r.baseUrl)
           .toArray();
         if (details && denominations) {
+          const tosRecord = await tx.exchangeTos.get([
+            details.exchangeBaseUrl,
+            details.tosCurrentEtag,
+          ]);
           exchanges.push({
             exchangeBaseUrl: details.exchangeBaseUrl,
             currency: details.currency,
+            // FIXME: We probably don't want to include the full ToS here!
             tos: {
-              acceptedVersion: details.termsOfServiceAcceptedEtag,
-              currentVersion: details.termsOfServiceLastEtag,
-              contentType: details.termsOfServiceContentType,
-              content: details.termsOfServiceText,
+              acceptedVersion: details.tosAccepted?.etag,
+              currentVersion: details.tosCurrentEtag,
+              contentType: tosRecord?.termsOfServiceContentType ?? "",
+              content: tosRecord?.termsOfServiceText ?? "",
             },
             paytoUris: details.wireInfo.accounts.map((x) => x.payto_uri),
           });
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index 22cbeb4b8..ef7a745ab 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -94,6 +94,7 @@ import {
   WalletCoreVersion,
   WalletNotification,
   codecForSetDevModeRequest,
+  ExchangeTosStatusDetails,
 } from "@gnu-taler/taler-util";
 import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
 import {
@@ -107,6 +108,8 @@ import {
   CoinStatus,
   ConfigRecordKey,
   DenominationRecord,
+  ExchangeDetailsRecord,
+  ExchangeTosRecord,
   exportDb,
   importDb,
   WalletStoresV1,
@@ -228,7 +231,11 @@ import {
   OpenedPromise,
   openPromise,
 } from "./util/promiseUtils.js";
-import { DbAccess, GetReadWriteAccess } from "./util/query.js";
+import {
+  DbAccess,
+  GetReadOnlyAccess,
+  GetReadWriteAccess,
+} from "./util/query.js";
 import { OperationAttemptResult } from "./util/retries.js";
 import { TimerAPI, TimerGroup } from "./util/timer.js";
 import {
@@ -461,6 +468,10 @@ async function fillDefaults(ws: InternalWalletState): 
Promise<void> {
     });
 }
 
+/**
+ * Get the exchange ToS in the requested format.
+ * Try to download in the accepted format not cached.
+ */
 async function getExchangeTos(
   ws: InternalWalletState,
   exchangeBaseUrl: string,
@@ -468,9 +479,14 @@ async function getExchangeTos(
 ): Promise<GetExchangeTosResult> {
   // FIXME: download ToS in acceptable format if passed!
   const { exchangeDetails } = await updateExchangeFromUrl(ws, exchangeBaseUrl);
-  const content = exchangeDetails.termsOfServiceText;
-  const currentEtag = exchangeDetails.termsOfServiceLastEtag;
-  const contentType = exchangeDetails.termsOfServiceContentType;
+  const tosDetails = await ws.db
+    .mktx((x) => [x.exchangeTos])
+    .runReadOnly(async (tx) => {
+      return await getExchangeTosStatusDetails(tx, exchangeDetails);
+    });
+  const content = tosDetails.content;
+  const currentEtag = tosDetails.currentVersion;
+  const contentType = tosDetails.contentType;
   if (
     content === undefined ||
     currentEtag === undefined ||
@@ -483,7 +499,7 @@ async function getExchangeTos(
     acceptedFormat.findIndex((f) => f === contentType) !== -1
   ) {
     return {
-      acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag,
+      acceptedEtag: exchangeDetails.tosAccepted?.etag,
       currentEtag,
       content,
       contentType,
@@ -499,16 +515,17 @@ async function getExchangeTos(
 
   if (tosDownload.tosContentType === contentType) {
     return {
-      acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag,
+      acceptedEtag: exchangeDetails.tosAccepted?.etag,
       currentEtag,
       content,
       contentType,
     };
   }
+
   await updateExchangeTermsOfService(ws, exchangeBaseUrl, tosDownload);
 
   return {
-    acceptedEtag: exchangeDetails.termsOfServiceAcceptedEtag,
+    acceptedEtag: exchangeDetails.tosAccepted?.etag,
     currentEtag: tosDownload.tosEtag,
     content: tosDownload.tosText,
     contentType: tosDownload.tosContentType,
@@ -585,12 +602,43 @@ async function forgetKnownBankAccounts(
   return;
 }
 
+async function getExchangeTosStatusDetails(
+  tx: GetReadOnlyAccess<{ exchangeTos: typeof WalletStoresV1.exchangeTos }>,
+  exchangeDetails: ExchangeDetailsRecord,
+): Promise<ExchangeTosStatusDetails> {
+  let exchangeTos = await tx.exchangeTos.get([
+    exchangeDetails.exchangeBaseUrl,
+    exchangeDetails.tosCurrentEtag,
+  ]);
+
+  if (!exchangeTos) {
+    exchangeTos = {
+      etag: "not-available",
+      termsOfServiceContentType: "text/plain",
+      termsOfServiceText: "terms of service unavailable",
+      exchangeBaseUrl: exchangeDetails.exchangeBaseUrl,
+    };
+  }
+
+  return {
+    acceptedVersion: exchangeDetails.tosAccepted?.etag,
+    content: exchangeTos.termsOfServiceContentType,
+    contentType: exchangeTos.termsOfServiceContentType,
+    currentVersion: exchangeTos.etag,
+  };
+}
+
 async function getExchanges(
   ws: InternalWalletState,
 ): Promise<ExchangesListResponse> {
   const exchanges: ExchangeListItem[] = [];
   await ws.db
-    .mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations])
+    .mktx((x) => [
+      x.exchanges,
+      x.exchangeDetails,
+      x.exchangeTos,
+      x.denominations,
+    ])
     .runReadOnly(async (tx) => {
       const exchangeRecords = await tx.exchanges.iter().toArray();
       for (const r of exchangeRecords) {
@@ -612,15 +660,12 @@ async function getExchanges(
           continue;
         }
 
+        const tos = await getExchangeTosStatusDetails(tx, exchangeDetails);
+
         exchanges.push({
           exchangeBaseUrl: r.baseUrl,
           currency,
-          tos: {
-            acceptedVersion: exchangeDetails.termsOfServiceAcceptedEtag,
-            currentVersion: exchangeDetails.termsOfServiceLastEtag,
-            contentType: exchangeDetails.termsOfServiceContentType,
-            content: exchangeDetails.termsOfServiceText,
-          },
+          tos,
           paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
         });
       }
@@ -634,7 +679,12 @@ async function getExchangeDetailedInfo(
 ): Promise<ExchangeFullDetails> {
   //TODO: should we use the forceUpdate parameter?
   const exchange = await ws.db
-    .mktx((x) => [x.exchanges, x.exchangeDetails, x.denominations])
+    .mktx((x) => [
+      x.exchanges,
+      x.exchangeTos,
+      x.exchangeDetails,
+      x.denominations,
+    ])
     .runReadOnly(async (tx) => {
       const ex = await tx.exchanges.get(exchangeBaseurl);
       const dp = ex?.detailsPointer;
@@ -656,6 +706,8 @@ async function getExchangeDetailedInfo(
         return;
       }
 
+      const tos = await getExchangeTosStatusDetails(tx, exchangeDetails);
+
       const denominations: DenominationInfo[] = denominationRecords.map((x) =>
         DenominationRecord.toDenomInfo(x),
       );
@@ -664,12 +716,7 @@ async function getExchangeDetailedInfo(
         info: {
           exchangeBaseUrl: ex.baseUrl,
           currency,
-          tos: {
-            acceptedVersion: exchangeDetails.termsOfServiceAcceptedEtag,
-            currentVersion: exchangeDetails.termsOfServiceLastEtag,
-            contentType: exchangeDetails.termsOfServiceContentType,
-            content: exchangeDetails.termsOfServiceText,
-          },
+          tos,
           paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri),
           auditors: exchangeDetails.auditors,
           wireInfo: exchangeDetails.wireInfo,

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