gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: simplify pending transactions


From: gnunet
Subject: [taler-wallet-core] branch master updated: simplify pending transactions, make more tests pass again
Date: Thu, 10 Jun 2021 16:32:43 +0200

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

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

The following commit(s) were added to refs/heads/master by this push:
     new 8ad36d89 simplify pending transactions, make more tests pass again
8ad36d89 is described below

commit 8ad36d89f55783c34043ee9ef37759cd94bcec7c
Author: Florian Dold <florian@dold.me>
AuthorDate: Thu Jun 10 16:32:37 2021 +0200

    simplify pending transactions, make more tests pass again
---
 packages/taler-util/src/time.ts                    |   8 +
 .../test-timetravel-autorefresh.ts                 |   4 +
 packages/taler-wallet-core/src/db.ts               |  52 +---
 .../src/operations/backup/import.ts                |   8 +-
 .../taler-wallet-core/src/operations/exchanges.ts  |  36 ++-
 packages/taler-wallet-core/src/operations/pay.ts   |   2 +-
 .../taler-wallet-core/src/operations/pending.ts    | 300 ++++-----------------
 .../taler-wallet-core/src/operations/refresh.ts    |  30 +--
 packages/taler-wallet-core/src/pending-types.ts    |  62 +----
 .../taler-wallet-core/src/util/contractTerms.ts    |   2 -
 packages/taler-wallet-core/src/util/retries.ts     |   5 +-
 packages/taler-wallet-core/src/wallet.ts           |  96 ++++---
 12 files changed, 175 insertions(+), 430 deletions(-)

diff --git a/packages/taler-util/src/time.ts b/packages/taler-util/src/time.ts
index 980f42db..c0858ada 100644
--- a/packages/taler-util/src/time.ts
+++ b/packages/taler-util/src/time.ts
@@ -217,6 +217,14 @@ export function timestampDifference(t1: Timestamp, t2: 
Timestamp): Duration {
   return { d_ms: Math.abs(t1.t_ms - t2.t_ms) };
 }
 
+export function timestampToIsoString(t: Timestamp): string {
+  if (t.t_ms === "never") {
+    return "<never>";
+  } else {
+    return new Date(t.t_ms).toISOString();
+  }
+}
+
 export function timestampIsBetween(
   t: Timestamp,
   start: Timestamp,
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-timetravel-autorefresh.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-timetravel-autorefresh.ts
index 3f26aaf0..8146eafc 100644
--- 
a/packages/taler-wallet-cli/src/integrationtests/test-timetravel-autorefresh.ts
+++ 
b/packages/taler-wallet-cli/src/integrationtests/test-timetravel-autorefresh.ts
@@ -167,6 +167,10 @@ export async function runTimetravelAutorefreshTest(t: 
GlobalTestState) {
     merchant,
   });
 
+  // At this point, the original coins should've been refreshed.
+  // It would be too late to refresh them now, as we're past
+  // the two year deposit expiration.
+
   await wallet.runUntilDone();
 
   const orderResp = await MerchantPrivateApi.createOrder(merchant, "default", {
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index d02ea192..ca613e5e 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -515,25 +515,11 @@ export interface DenominationRecord {
   exchangeBaseUrl: string;
 }
 
-export enum ExchangeUpdateStatus {
-  FetchKeys = "fetch-keys",
-  FetchWire = "fetch-wire",
-  FetchTerms = "fetch-terms",
-  FinalizeUpdate = "finalize-update",
-  Finished = "finished",
-}
-
 export interface ExchangeBankAccount {
   payto_uri: string;
   master_sig: string;
 }
 
-export enum ExchangeUpdateReason {
-  Initial = "initial",
-  Forced = "forced",
-  Scheduled = "scheduled",
-}
-
 export interface ExchangeDetailsRecord {
   /**
    * Master public key of the exchange.
@@ -582,16 +568,6 @@ export interface ExchangeDetailsRecord {
    */
   termsOfServiceAcceptedEtag: string | undefined;
 
-  /**
-   * Timestamp for last update.
-   */
-  lastUpdateTime: Timestamp;
-
-  /**
-   * When should we next update the information about the exchange?
-   */
-  nextUpdateTime: Timestamp;
-
   wireInfo: WireInfo;
 }
 
@@ -629,20 +605,24 @@ export interface ExchangeRecord {
   permanent: boolean;
 
   /**
-   * Time when the update to the exchange has been started or
-   * undefined if no update is in progress.
+   * Last time when the exchange was updated.
    */
-  updateStarted: Timestamp | undefined;
+  lastUpdate: Timestamp | undefined;
 
   /**
-   * Status of updating the info about the exchange.
+   * Next scheduled update for the exchange.
    *
-   * FIXME:  Adapt this to recent changes regarding how
-   * updating exchange details works.
+   * (This field must always be present, so we can index on the timestamp.)
    */
-  updateStatus: ExchangeUpdateStatus;
+  nextUpdate: Timestamp;
 
-  updateReason?: ExchangeUpdateReason;
+  /**
+   * Next time that we should check if coins need to be refreshed.
+   *
+   * Updated whenever the exchange's denominations are updated or when
+   * the refresh check has been done.
+   */
+  nextRefreshCheck: Timestamp;
 
   lastError?: TalerErrorDetails;
 
@@ -650,14 +630,6 @@ export interface ExchangeRecord {
    * Retry status for fetching updated information about the exchange.
    */
   retryInfo: RetryInfo;
-
-  /**
-   * Next time that we should check if coins need to be refreshed.
-   *
-   * Updated whenever the exchange's denominations are updated or when
-   * the refresh check has been done.
-   */
-  nextRefreshCheck?: Timestamp;
 }
 
 /**
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts 
b/packages/taler-wallet-core/src/operations/backup/import.ts
index e024b76a..9363ecfb 100644
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ b/packages/taler-wallet-core/src/operations/backup/import.ts
@@ -31,7 +31,6 @@ import {
 import {
   WalletContractData,
   DenomSelectionState,
-  ExchangeUpdateStatus,
   DenominationStatus,
   CoinSource,
   CoinSourceType,
@@ -265,8 +264,9 @@ export async function importBackup(
           },
           permanent: true,
           retryInfo: initRetryInfo(false),
-          updateStarted: { t_ms: "never" },
-          updateStatus: ExchangeUpdateStatus.Finished,
+          lastUpdate: undefined,
+          nextUpdate: getTimestampNow(),
+          nextRefreshCheck: getTimestampNow(),
         });
       }
 
@@ -307,9 +307,7 @@ export async function importBackup(
               auditor_url: x.auditor_url,
               denomination_keys: x.denomination_keys,
             })),
-            lastUpdateTime: { t_ms: "never" },
             masterPublicKey: backupExchangeDetails.master_public_key,
-            nextUpdateTime: { t_ms: "never" },
             protocolVersion: backupExchangeDetails.protocol_version,
             reserveClosingDelay: backupExchangeDetails.reserve_closing_delay,
             signingKeys: backupExchangeDetails.signing_keys.map((x) => ({
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts 
b/packages/taler-wallet-core/src/operations/exchanges.ts
index 789ce1da..bea4b668 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -42,9 +42,7 @@ import {
   DenominationRecord,
   DenominationStatus,
   ExchangeRecord,
-  ExchangeUpdateStatus,
   WireFee,
-  ExchangeUpdateReason,
   ExchangeDetailsRecord,
   WireInfo,
   WalletStoresV1,
@@ -299,11 +297,11 @@ async function provideExchangeRecord(
         r = {
           permanent: true,
           baseUrl: baseUrl,
-          updateStatus: ExchangeUpdateStatus.FetchKeys,
-          updateStarted: now,
-          updateReason: ExchangeUpdateReason.Initial,
           retryInfo: initRetryInfo(false),
           detailsPointer: undefined,
+          lastUpdate: undefined,
+          nextUpdate: now,
+          nextRefreshCheck: now,
         };
         await tx.exchanges.put(r);
       }
@@ -411,6 +409,27 @@ async function updateExchangeFromUrlImpl(
 
   const r = await provideExchangeRecord(ws, baseUrl, now);
 
+  if (!forceNow && r && !isTimestampExpired(r.nextUpdate)) {
+    const res = await ws.db.mktx((x) => ({
+      exchanges: x.exchanges,
+      exchangeDetails: x.exchangeDetails,
+    })).runReadOnly(async (tx) => {
+      const exchange = await tx.exchanges.get(baseUrl);
+      if (!exchange) {
+        return;
+      }
+      const exchangeDetails = await getExchangeDetails(tx, baseUrl);
+      if (!exchangeDetails) {
+        return;
+      }
+      return { exchange, exchangeDetails };
+    });
+    if (res) {
+      logger.info("using existing exchange info");
+      return res;
+    }
+  }
+
   logger.info("updating exchange /keys info");
 
   const timeout = getExchangeRequestTimeout(r);
@@ -460,11 +479,9 @@ async function updateExchangeFromUrlImpl(
       details = {
         auditors: keysInfo.auditors,
         currency: keysInfo.currency,
-        lastUpdateTime: now,
         masterPublicKey: keysInfo.masterPublicKey,
         protocolVersion: keysInfo.protocolVersion,
         signingKeys: keysInfo.signingKeys,
-        nextUpdateTime: keysInfo.expiry,
         reserveClosingDelay: keysInfo.reserveClosingDelay,
         exchangeBaseUrl: r.baseUrl,
         wireInfo,
@@ -472,12 +489,13 @@ async function updateExchangeFromUrlImpl(
         termsOfServiceAcceptedEtag: undefined,
         termsOfServiceLastEtag: tosDownload.tosEtag,
       };
-      r.updateStatus = ExchangeUpdateStatus.FetchWire;
       // FIXME: only update if pointer got updated
       r.lastError = undefined;
       r.retryInfo = initRetryInfo(false);
+      r.lastUpdate = getTimestampNow();
+      r.nextUpdate = keysInfo.expiry,
       // New denominations might be available.
-      r.nextRefreshCheck = undefined;
+      r.nextRefreshCheck = getTimestampNow();
       r.detailsPointer = {
         currency: details.currency,
         masterPublicKey: details.masterPublicKey,
diff --git a/packages/taler-wallet-core/src/operations/pay.ts 
b/packages/taler-wallet-core/src/operations/pay.ts
index 9e23f6a1..cbb92dc8 100644
--- a/packages/taler-wallet-core/src/operations/pay.ts
+++ b/packages/taler-wallet-core/src/operations/pay.ts
@@ -468,7 +468,7 @@ async function recordConfirmPay(
       const p = await tx.proposals.get(proposal.proposalId);
       if (p) {
         p.proposalStatus = ProposalStatus.ACCEPTED;
-        p.lastError = undefined;
+        delete p.lastError;
         p.retryInfo = initRetryInfo(false);
         await tx.proposals.put(p);
       }
diff --git a/packages/taler-wallet-core/src/operations/pending.ts 
b/packages/taler-wallet-core/src/operations/pending.ts
index 4eee8527..b40c33c5 100644
--- a/packages/taler-wallet-core/src/operations/pending.ts
+++ b/packages/taler-wallet-core/src/operations/pending.ts
@@ -18,7 +18,6 @@
  * Imports.
  */
 import {
-  ExchangeUpdateStatus,
   ProposalStatus,
   ReserveRecordStatus,
   AbortStatus,
@@ -27,31 +26,13 @@ import {
 import {
   PendingOperationsResponse,
   PendingOperationType,
-  ExchangeUpdateOperationStage,
   ReserveType,
 } from "../pending-types";
-import {
-  Duration,
-  getTimestampNow,
-  Timestamp,
-  getDurationRemaining,
-  durationMin,
-} from "@gnu-taler/taler-util";
+import { getTimestampNow, Timestamp } from "@gnu-taler/taler-util";
 import { InternalWalletState } from "./state";
 import { getBalancesInsideTransaction } from "./balance";
-import { getExchangeDetails } from "./exchanges.js";
 import { GetReadOnlyAccess } from "../util/query.js";
 
-function updateRetryDelay(
-  oldDelay: Duration,
-  now: Timestamp,
-  retryTimestamp: Timestamp,
-): Duration {
-  const remaining = getDurationRemaining(retryTimestamp, now);
-  const nextDelay = durationMin(oldDelay, remaining);
-  return nextDelay;
-}
-
 async function gatherExchangePending(
   tx: GetReadOnlyAccess<{
     exchanges: typeof WalletStoresV1.exchanges;
@@ -59,97 +40,22 @@ async function gatherExchangePending(
   }>,
   now: Timestamp,
   resp: PendingOperationsResponse,
-  onlyDue = false,
 ): Promise<void> {
   await tx.exchanges.iter().forEachAsync(async (e) => {
-    switch (e.updateStatus) {
-      case ExchangeUpdateStatus.Finished:
-        if (e.lastError) {
-          resp.pendingOperations.push({
-            type: PendingOperationType.Bug,
-            givesLifeness: false,
-            message:
-              "Exchange record is in FINISHED state but has lastError set",
-            details: {
-              exchangeBaseUrl: e.baseUrl,
-            },
-          });
-        }
-        const details = await getExchangeDetails(tx, e.baseUrl);
-        const keysUpdateRequired =
-          details && details.nextUpdateTime.t_ms < now.t_ms;
-        if (keysUpdateRequired) {
-          resp.pendingOperations.push({
-            type: PendingOperationType.ExchangeUpdate,
-            givesLifeness: false,
-            stage: ExchangeUpdateOperationStage.FetchKeys,
-            exchangeBaseUrl: e.baseUrl,
-            lastError: e.lastError,
-            reason: "scheduled",
-          });
-        }
-        if (
-          details &&
-          (!e.nextRefreshCheck || e.nextRefreshCheck.t_ms < now.t_ms)
-        ) {
-          resp.pendingOperations.push({
-            type: PendingOperationType.ExchangeCheckRefresh,
-            exchangeBaseUrl: e.baseUrl,
-            givesLifeness: false,
-          });
-        }
-        break;
-      case ExchangeUpdateStatus.FetchKeys:
-        if (onlyDue && e.retryInfo.nextRetry.t_ms > now.t_ms) {
-          return;
-        }
-        resp.pendingOperations.push({
-          type: PendingOperationType.ExchangeUpdate,
-          givesLifeness: false,
-          stage: ExchangeUpdateOperationStage.FetchKeys,
-          exchangeBaseUrl: e.baseUrl,
-          lastError: e.lastError,
-          reason: e.updateReason || "unknown",
-        });
-        break;
-      case ExchangeUpdateStatus.FetchWire:
-        if (onlyDue && e.retryInfo.nextRetry.t_ms > now.t_ms) {
-          return;
-        }
-        resp.pendingOperations.push({
-          type: PendingOperationType.ExchangeUpdate,
-          givesLifeness: false,
-          stage: ExchangeUpdateOperationStage.FetchWire,
-          exchangeBaseUrl: e.baseUrl,
-          lastError: e.lastError,
-          reason: e.updateReason || "unknown",
-        });
-        break;
-      case ExchangeUpdateStatus.FinalizeUpdate:
-        if (onlyDue && e.retryInfo.nextRetry.t_ms > now.t_ms) {
-          return;
-        }
-        resp.pendingOperations.push({
-          type: PendingOperationType.ExchangeUpdate,
-          givesLifeness: false,
-          stage: ExchangeUpdateOperationStage.FinalizeUpdate,
-          exchangeBaseUrl: e.baseUrl,
-          lastError: e.lastError,
-          reason: e.updateReason || "unknown",
-        });
-        break;
-      default:
-        resp.pendingOperations.push({
-          type: PendingOperationType.Bug,
-          givesLifeness: false,
-          message: "Unknown exchangeUpdateStatus",
-          details: {
-            exchangeBaseUrl: e.baseUrl,
-            exchangeUpdateStatus: e.updateStatus,
-          },
-        });
-        break;
-    }
+    resp.pendingOperations.push({
+      type: PendingOperationType.ExchangeUpdate,
+      givesLifeness: false,
+      timestampDue: e.nextUpdate,
+      exchangeBaseUrl: e.baseUrl,
+      lastError: e.lastError,
+    });
+
+    resp.pendingOperations.push({
+      type: PendingOperationType.ExchangeCheckRefresh,
+      timestampDue: e.nextRefreshCheck,
+      givesLifeness: false,
+      exchangeBaseUrl: e.baseUrl,
+    });
   });
 }
 
@@ -157,16 +63,11 @@ async function gatherReservePending(
   tx: GetReadOnlyAccess<{ reserves: typeof WalletStoresV1.reserves }>,
   now: Timestamp,
   resp: PendingOperationsResponse,
-  onlyDue = false,
 ): Promise<void> {
-  // FIXME: this should be optimized by using an index for "onlyDue==true".
   await tx.reserves.iter().forEach((reserve) => {
     const reserveType = reserve.bankInfo
       ? ReserveType.TalerBankWithdraw
       : ReserveType.Manual;
-    if (!reserve.retryInfo.active) {
-      return;
-    }
     switch (reserve.reserveStatus) {
       case ReserveRecordStatus.DORMANT:
         // nothing to report as pending
@@ -174,17 +75,10 @@ async function gatherReservePending(
       case ReserveRecordStatus.WAIT_CONFIRM_BANK:
       case ReserveRecordStatus.QUERYING_STATUS:
       case ReserveRecordStatus.REGISTERING_BANK:
-        resp.nextRetryDelay = updateRetryDelay(
-          resp.nextRetryDelay,
-          now,
-          reserve.retryInfo.nextRetry,
-        );
-        if (onlyDue && reserve.retryInfo.nextRetry.t_ms > now.t_ms) {
-          return;
-        }
         resp.pendingOperations.push({
           type: PendingOperationType.Reserve,
           givesLifeness: true,
+          timestampDue: reserve.retryInfo.nextRetry,
           stage: reserve.reserveStatus,
           timestampCreated: reserve.timestampCreated,
           reserveType,
@@ -193,15 +87,7 @@ async function gatherReservePending(
         });
         break;
       default:
-        resp.pendingOperations.push({
-          type: PendingOperationType.Bug,
-          givesLifeness: false,
-          message: "Unknown reserve record status",
-          details: {
-            reservePub: reserve.reservePub,
-            reserveStatus: reserve.reserveStatus,
-          },
-        });
+        // FIXME: report problem!
         break;
     }
   });
@@ -211,24 +97,15 @@ async function gatherRefreshPending(
   tx: GetReadOnlyAccess<{ refreshGroups: typeof WalletStoresV1.refreshGroups 
}>,
   now: Timestamp,
   resp: PendingOperationsResponse,
-  onlyDue = false,
 ): Promise<void> {
   await tx.refreshGroups.iter().forEach((r) => {
     if (r.timestampFinished) {
       return;
     }
-    resp.nextRetryDelay = updateRetryDelay(
-      resp.nextRetryDelay,
-      now,
-      r.retryInfo.nextRetry,
-    );
-    if (onlyDue && r.retryInfo.nextRetry.t_ms > now.t_ms) {
-      return;
-    }
-
     resp.pendingOperations.push({
       type: PendingOperationType.Refresh,
       givesLifeness: true,
+      timestampDue: r.retryInfo.nextRetry,
       refreshGroupId: r.refreshGroupId,
       finishedPerCoin: r.finishedPerCoin,
       retryInfo: r.retryInfo,
@@ -243,20 +120,11 @@ async function gatherWithdrawalPending(
   }>,
   now: Timestamp,
   resp: PendingOperationsResponse,
-  onlyDue = false,
 ): Promise<void> {
   await tx.withdrawalGroups.iter().forEachAsync(async (wsr) => {
     if (wsr.timestampFinish) {
       return;
     }
-    resp.nextRetryDelay = updateRetryDelay(
-      resp.nextRetryDelay,
-      now,
-      wsr.retryInfo.nextRetry,
-    );
-    if (onlyDue && wsr.retryInfo.nextRetry.t_ms > now.t_ms) {
-      return;
-    }
     let numCoinsWithdrawn = 0;
     let numCoinsTotal = 0;
     await tx.planchets.indexes.byGroup
@@ -270,8 +138,7 @@ async function gatherWithdrawalPending(
     resp.pendingOperations.push({
       type: PendingOperationType.Withdraw,
       givesLifeness: true,
-      numCoinsTotal,
-      numCoinsWithdrawn,
+      timestampDue: wsr.retryInfo.nextRetry,
       withdrawalGroupId: wsr.withdrawalGroupId,
       lastError: wsr.lastError,
       retryInfo: wsr.retryInfo,
@@ -283,42 +150,15 @@ async function gatherProposalPending(
   tx: GetReadOnlyAccess<{ proposals: typeof WalletStoresV1.proposals }>,
   now: Timestamp,
   resp: PendingOperationsResponse,
-  onlyDue = false,
 ): Promise<void> {
   await tx.proposals.iter().forEach((proposal) => {
     if (proposal.proposalStatus == ProposalStatus.PROPOSED) {
-      if (onlyDue) {
-        return;
-      }
-      const dl = proposal.download;
-      if (!dl) {
-        resp.pendingOperations.push({
-          type: PendingOperationType.Bug,
-          message: "proposal is in invalid state",
-          details: {},
-          givesLifeness: false,
-        });
-      } else {
-        resp.pendingOperations.push({
-          type: PendingOperationType.ProposalChoice,
-          givesLifeness: false,
-          merchantBaseUrl: dl.contractData.merchantBaseUrl,
-          proposalId: proposal.proposalId,
-          proposalTimestamp: proposal.timestamp,
-        });
-      }
+      // Nothing to do, user needs to choose.
     } else if (proposal.proposalStatus == ProposalStatus.DOWNLOADING) {
-      resp.nextRetryDelay = updateRetryDelay(
-        resp.nextRetryDelay,
-        now,
-        proposal.retryInfo.nextRetry,
-      );
-      if (onlyDue && proposal.retryInfo.nextRetry.t_ms > now.t_ms) {
-        return;
-      }
       resp.pendingOperations.push({
         type: PendingOperationType.ProposalDownload,
         givesLifeness: true,
+        timestampDue: proposal.retryInfo.nextRetry,
         merchantBaseUrl: proposal.merchantBaseUrl,
         orderId: proposal.orderId,
         proposalId: proposal.proposalId,
@@ -334,24 +174,16 @@ async function gatherTipPending(
   tx: GetReadOnlyAccess<{ tips: typeof WalletStoresV1.tips }>,
   now: Timestamp,
   resp: PendingOperationsResponse,
-  onlyDue = false,
 ): Promise<void> {
   await tx.tips.iter().forEach((tip) => {
     if (tip.pickedUpTimestamp) {
       return;
     }
-    resp.nextRetryDelay = updateRetryDelay(
-      resp.nextRetryDelay,
-      now,
-      tip.retryInfo.nextRetry,
-    );
-    if (onlyDue && tip.retryInfo.nextRetry.t_ms > now.t_ms) {
-      return;
-    }
     if (tip.acceptedTimestamp) {
       resp.pendingOperations.push({
         type: PendingOperationType.TipPickup,
         givesLifeness: true,
+        timestampDue: tip.retryInfo.nextRetry,
         merchantBaseUrl: tip.merchantBaseUrl,
         tipId: tip.walletTipId,
         merchantTipId: tip.merchantTipId,
@@ -364,41 +196,28 @@ async function gatherPurchasePending(
   tx: GetReadOnlyAccess<{ purchases: typeof WalletStoresV1.purchases }>,
   now: Timestamp,
   resp: PendingOperationsResponse,
-  onlyDue = false,
 ): Promise<void> {
   await tx.purchases.iter().forEach((pr) => {
     if (pr.paymentSubmitPending && pr.abortStatus === AbortStatus.None) {
-      resp.nextRetryDelay = updateRetryDelay(
-        resp.nextRetryDelay,
-        now,
-        pr.payRetryInfo.nextRetry,
-      );
-      if (!onlyDue || pr.payRetryInfo.nextRetry.t_ms <= now.t_ms) {
-        resp.pendingOperations.push({
-          type: PendingOperationType.Pay,
-          givesLifeness: true,
-          isReplay: false,
-          proposalId: pr.proposalId,
-          retryInfo: pr.payRetryInfo,
-          lastError: pr.lastPayError,
-        });
-      }
+      resp.pendingOperations.push({
+        type: PendingOperationType.Pay,
+        givesLifeness: true,
+        timestampDue: pr.payRetryInfo.nextRetry,
+        isReplay: false,
+        proposalId: pr.proposalId,
+        retryInfo: pr.payRetryInfo,
+        lastError: pr.lastPayError,
+      });
     }
     if (pr.refundQueryRequested) {
-      resp.nextRetryDelay = updateRetryDelay(
-        resp.nextRetryDelay,
-        now,
-        pr.refundStatusRetryInfo.nextRetry,
-      );
-      if (!onlyDue || pr.refundStatusRetryInfo.nextRetry.t_ms <= now.t_ms) {
-        resp.pendingOperations.push({
-          type: PendingOperationType.RefundQuery,
-          givesLifeness: true,
-          proposalId: pr.proposalId,
-          retryInfo: pr.refundStatusRetryInfo,
-          lastError: pr.lastRefundStatusError,
-        });
-      }
+      resp.pendingOperations.push({
+        type: PendingOperationType.RefundQuery,
+        givesLifeness: true,
+        timestampDue: pr.refundStatusRetryInfo.nextRetry,
+        proposalId: pr.proposalId,
+        retryInfo: pr.refundStatusRetryInfo,
+        lastError: pr.lastRefundStatusError,
+      });
     }
   });
 }
@@ -407,23 +226,15 @@ async function gatherRecoupPending(
   tx: GetReadOnlyAccess<{ recoupGroups: typeof WalletStoresV1.recoupGroups }>,
   now: Timestamp,
   resp: PendingOperationsResponse,
-  onlyDue = false,
 ): Promise<void> {
   await tx.recoupGroups.iter().forEach((rg) => {
     if (rg.timestampFinished) {
       return;
     }
-    resp.nextRetryDelay = updateRetryDelay(
-      resp.nextRetryDelay,
-      now,
-      rg.retryInfo.nextRetry,
-    );
-    if (onlyDue && rg.retryInfo.nextRetry.t_ms > now.t_ms) {
-      return;
-    }
     resp.pendingOperations.push({
       type: PendingOperationType.Recoup,
       givesLifeness: true,
+      timestampDue: rg.retryInfo.nextRetry,
       recoupGroupId: rg.recoupGroupId,
       retryInfo: rg.retryInfo,
       lastError: rg.lastError,
@@ -435,23 +246,15 @@ async function gatherDepositPending(
   tx: GetReadOnlyAccess<{ depositGroups: typeof WalletStoresV1.depositGroups 
}>,
   now: Timestamp,
   resp: PendingOperationsResponse,
-  onlyDue = false,
 ): Promise<void> {
   await tx.depositGroups.iter().forEach((dg) => {
     if (dg.timestampFinished) {
       return;
     }
-    resp.nextRetryDelay = updateRetryDelay(
-      resp.nextRetryDelay,
-      now,
-      dg.retryInfo.nextRetry,
-    );
-    if (onlyDue && dg.retryInfo.nextRetry.t_ms > now.t_ms) {
-      return;
-    }
     resp.pendingOperations.push({
       type: PendingOperationType.Deposit,
       givesLifeness: true,
+      timestampDue: dg.retryInfo.nextRetry,
       depositGroupId: dg.depositGroupId,
       retryInfo: dg.retryInfo,
       lastError: dg.lastError,
@@ -461,7 +264,6 @@ async function gatherDepositPending(
 
 export async function getPendingOperations(
   ws: InternalWalletState,
-  { onlyDue = false } = {},
 ): Promise<PendingOperationsResponse> {
   const now = getTimestampNow();
   return await ws.db
@@ -482,20 +284,18 @@ export async function getPendingOperations(
     .runReadWrite(async (tx) => {
       const walletBalance = await getBalancesInsideTransaction(ws, tx);
       const resp: PendingOperationsResponse = {
-        nextRetryDelay: { d_ms: Number.MAX_SAFE_INTEGER },
-        onlyDue: onlyDue,
         walletBalance,
         pendingOperations: [],
       };
-      await gatherExchangePending(tx, now, resp, onlyDue);
-      await gatherReservePending(tx, now, resp, onlyDue);
-      await gatherRefreshPending(tx, now, resp, onlyDue);
-      await gatherWithdrawalPending(tx, now, resp, onlyDue);
-      await gatherProposalPending(tx, now, resp, onlyDue);
-      await gatherTipPending(tx, now, resp, onlyDue);
-      await gatherPurchasePending(tx, now, resp, onlyDue);
-      await gatherRecoupPending(tx, now, resp, onlyDue);
-      await gatherDepositPending(tx, now, resp, onlyDue);
+      await gatherExchangePending(tx, now, resp);
+      await gatherReservePending(tx, now, resp);
+      await gatherRefreshPending(tx, now, resp);
+      await gatherWithdrawalPending(tx, now, resp);
+      await gatherProposalPending(tx, now, resp);
+      await gatherTipPending(tx, now, resp);
+      await gatherPurchasePending(tx, now, resp);
+      await gatherRecoupPending(tx, now, resp);
+      await gatherDepositPending(tx, now, resp);
       return resp;
     });
 }
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts 
b/packages/taler-wallet-core/src/operations/refresh.ts
index 8d21e811..21c92c1b 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -32,6 +32,7 @@ import {
   RefreshGroupId,
   RefreshReason,
   TalerErrorDetails,
+  timestampToIsoString,
 } from "@gnu-taler/taler-util";
 import { AmountJson, Amounts } from "@gnu-taler/taler-util";
 import { amountToPretty } from "@gnu-taler/taler-util";
@@ -864,7 +865,12 @@ export async function autoRefresh(
   ws: InternalWalletState,
   exchangeBaseUrl: string,
 ): Promise<void> {
+  logger.info(`doing auto-refresh check for '${exchangeBaseUrl}'`);
   await updateExchangeFromUrl(ws, exchangeBaseUrl, true);
+  let minCheckThreshold = timestampAddDuration(
+    getTimestampNow(),
+    durationFromSpec({ days: 1 }),
+  );
   await ws.db
     .mktx((x) => ({
       coins: x.coins,
@@ -899,28 +905,20 @@ export async function autoRefresh(
         const executeThreshold = getAutoRefreshExecuteThreshold(denom);
         if (isTimestampExpired(executeThreshold)) {
           refreshCoins.push(coin);
+        } else {
+          const checkThreshold = getAutoRefreshCheckThreshold(denom);
+          minCheckThreshold = timestampMin(minCheckThreshold, checkThreshold);
         }
       }
       if (refreshCoins.length > 0) {
         await createRefreshGroup(ws, tx, refreshCoins, 
RefreshReason.Scheduled);
       }
-
-      const denoms = await tx.denominations.indexes.byExchangeBaseUrl
-        .iter(exchangeBaseUrl)
-        .toArray();
-      let minCheckThreshold = timestampAddDuration(
-        getTimestampNow(),
-        durationFromSpec({ days: 1 }),
+      logger.info(
+        `current wallet time: ${timestampToIsoString(getTimestampNow())}`,
+      );
+      logger.info(
+        `next refresh check at ${timestampToIsoString(minCheckThreshold)}`,
       );
-      for (const denom of denoms) {
-        const checkThreshold = getAutoRefreshCheckThreshold(denom);
-        const executeThreshold = getAutoRefreshExecuteThreshold(denom);
-        if (isTimestampExpired(executeThreshold)) {
-          // No need to consider this denomination, we already did an auto 
refresh check.
-          continue;
-        }
-        minCheckThreshold = timestampMin(minCheckThreshold, checkThreshold);
-      }
       exchange.nextRefreshCheck = minCheckThreshold;
       await tx.exchanges.put(exchange);
     });
diff --git a/packages/taler-wallet-core/src/pending-types.ts 
b/packages/taler-wallet-core/src/pending-types.ts
index 78e01416..5586903f 100644
--- a/packages/taler-wallet-core/src/pending-types.ts
+++ b/packages/taler-wallet-core/src/pending-types.ts
@@ -34,7 +34,6 @@ import { ReserveRecordStatus } from "./db.js";
 import { RetryInfo } from "./util/retries.js";
 
 export enum PendingOperationType {
-  Bug = "bug",
   ExchangeUpdate = "exchange-update",
   ExchangeCheckRefresh = "exchange-check-refresh",
   Pay = "pay",
@@ -44,7 +43,6 @@ export enum PendingOperationType {
   Reserve = "reserve",
   Recoup = "recoup",
   RefundQuery = "refund-query",
-  TipChoice = "tip-choice",
   TipPickup = "tip-pickup",
   Withdraw = "withdraw",
   Deposit = "deposit",
@@ -55,16 +53,13 @@ export enum PendingOperationType {
  */
 export type PendingOperationInfo = PendingOperationInfoCommon &
   (
-    | PendingBugOperation
     | PendingExchangeUpdateOperation
     | PendingExchangeCheckRefreshOperation
     | PendingPayOperation
-    | PendingProposalChoiceOperation
     | PendingProposalDownloadOperation
     | PendingRefreshOperation
     | PendingRefundQueryOperation
     | PendingReserveOperation
-    | PendingTipChoiceOperation
     | PendingTipPickupOperation
     | PendingWithdrawOperation
     | PendingRecoupOperation
@@ -76,8 +71,6 @@ export type PendingOperationInfo = PendingOperationInfoCommon 
&
  */
 export interface PendingExchangeUpdateOperation {
   type: PendingOperationType.ExchangeUpdate;
-  stage: ExchangeUpdateOperationStage;
-  reason: string;
   exchangeBaseUrl: string;
   lastError: TalerErrorDetails | undefined;
 }
@@ -91,26 +84,6 @@ export interface PendingExchangeCheckRefreshOperation {
   exchangeBaseUrl: string;
 }
 
-/**
- * Some internal error happened in the wallet.  This pending operation
- * should *only* be reported for problems in the wallet, not when
- * a problem with a merchant/exchange/etc. occurs.
- */
-export interface PendingBugOperation {
-  type: PendingOperationType.Bug;
-  message: string;
-  details: any;
-}
-
-/**
- * Current state of an exchange update operation.
- */
-export enum ExchangeUpdateOperationStage {
-  FetchKeys = "fetch-keys",
-  FetchWire = "fetch-wire",
-  FinalizeUpdate = "finalize-update",
-}
-
 export enum ReserveType {
   /**
    * Manually created.
@@ -183,17 +156,6 @@ export interface PendingTipPickupOperation {
   merchantTipId: string;
 }
 
-/**
- * The wallet has been offered a tip, and the user now needs to
- * decide whether to accept or reject the tip.
- */
-export interface PendingTipChoiceOperation {
-  type: PendingOperationType.TipChoice;
-  tipId: string;
-  merchantBaseUrl: string;
-  merchantTipId: string;
-}
-
 /**
  * The wallet is signing coins and then sending them to
  * the merchant.
@@ -232,8 +194,6 @@ export interface PendingWithdrawOperation {
   lastError: TalerErrorDetails | undefined;
   retryInfo: RetryInfo;
   withdrawalGroupId: string;
-  numCoinsWithdrawn: number;
-  numCoinsTotal: number;
 }
 
 /**
@@ -257,13 +217,18 @@ export interface PendingOperationInfoCommon {
 
   /**
    * Set to true if the operation indicates that something is really in 
progress,
-   * as opposed to some regular scheduled operation or a permanent failure.
+   * as opposed to some regular scheduled operation that can be tried later.
    */
   givesLifeness: boolean;
 
   /**
-   * Retry info, not available on all pending operations.
-   * If it is available, it must have the same name.
+   * Timestamp when the pending operation should be executed next.
+   */
+  timestampDue: Timestamp;
+
+  /**
+   * Retry info.  Currently used to stop the wallet after any operation
+   * exceeds a number of retries.
    */
   retryInfo?: RetryInfo;
 }
@@ -281,15 +246,4 @@ export interface PendingOperationsResponse {
    * Current wallet balance, including pending balances.
    */
   walletBalance: BalancesResponse;
-
-  /**
-   * When is the next pending operation due to be re-tried?
-   */
-  nextRetryDelay: Duration;
-
-  /**
-   * Does this response only include pending operations that
-   * are due to be executed right now?
-   */
-  onlyDue: boolean;
 }
diff --git a/packages/taler-wallet-core/src/util/contractTerms.ts 
b/packages/taler-wallet-core/src/util/contractTerms.ts
index cf61cc05..5fb23cf8 100644
--- a/packages/taler-wallet-core/src/util/contractTerms.ts
+++ b/packages/taler-wallet-core/src/util/contractTerms.ts
@@ -121,7 +121,6 @@ export namespace ContractTermsUtil {
    * to forgettable fields and other restrictions for forgettable JSON.
    */
   export function validateForgettable(anyJson: any): boolean {
-    console.warn("calling validateForgettable", anyJson);
     if (typeof anyJson === "string") {
       return true;
     }
@@ -206,7 +205,6 @@ export namespace ContractTermsUtil {
             }
           }
         } else {
-          console.warn("invalid type");
           return false;
         }
       }
diff --git a/packages/taler-wallet-core/src/util/retries.ts 
b/packages/taler-wallet-core/src/util/retries.ts
index 54bb0b2e..a7f4cd28 100644
--- a/packages/taler-wallet-core/src/util/retries.ts
+++ b/packages/taler-wallet-core/src/util/retries.ts
@@ -81,10 +81,11 @@ export function initRetryInfo(
       retryCounter: 0,
     };
   }
+  const now = getTimestampNow();
   const info = {
-    firstTry: getTimestampNow(),
+    firstTry: now,
     active: true,
-    nextRetry: { t_ms: 0 },
+    nextRetry: now,
     retryCounter: 0,
   };
   updateRetryInfoTimeout(info, p);
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index 70ddaffa..854039a8 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -27,7 +27,15 @@ import {
   codecForAny,
   codecForDeleteTransactionRequest,
   DeleteTransactionRequest,
+  durationFromSpec,
+  durationMax,
+  durationMin,
+  getDurationRemaining,
+  isTimestampExpired,
+  j2s,
   TalerErrorCode,
+  Timestamp,
+  timestampMin,
   WalletCurrencyInfo,
 } from "@gnu-taler/taler-util";
 import { CryptoWorkerFactory } from "./crypto/workers/cryptoApi";
@@ -105,11 +113,8 @@ import {
   AuditorTrustRecord,
   CoinRecord,
   CoinSourceType,
-  DenominationRecord,
   ExchangeDetailsRecord,
   ExchangeRecord,
-  PurchaseRecord,
-  RefundState,
   ReserveRecord,
   ReserveRecordStatus,
   WalletStoresV1,
@@ -164,7 +169,6 @@ import {
   ManualWithdrawalDetails,
   PreparePayResult,
   PrepareTipResult,
-  PurchaseDetails,
   RecoveryLoadRequest,
   RefreshReason,
   ReturnCoinsRequest,
@@ -180,7 +184,6 @@ import { AsyncOpMemoSingle } from "./util/asyncMemo";
 import { HttpRequestLibrary } from "./util/http";
 import { Logger } from "@gnu-taler/taler-util";
 import { AsyncCondition } from "./util/promiseUtils";
-import { Duration, durationMin } from "@gnu-taler/taler-util";
 import { TimerGroup } from "./util/timer";
 import { getExchangeTrust } from "./operations/currencies.js";
 import { DbAccess } from "./util/query.js";
@@ -261,9 +264,6 @@ export class Wallet {
   ): Promise<void> {
     logger.trace(`running pending ${JSON.stringify(pending, undefined, 2)}`);
     switch (pending.type) {
-      case PendingOperationType.Bug:
-        // Nothing to do, will just be displayed to the user
-        return;
       case PendingOperationType.ExchangeUpdate:
         await updateExchangeFromUrl(this.ws, pending.exchangeBaseUrl, 
forceNow);
         break;
@@ -280,15 +280,9 @@ export class Wallet {
           forceNow,
         );
         break;
-      case PendingOperationType.ProposalChoice:
-        // Nothing to do, user needs to accept/reject
-        break;
       case PendingOperationType.ProposalDownload:
         await processDownloadProposal(this.ws, pending.proposalId, forceNow);
         break;
-      case PendingOperationType.TipChoice:
-        // Nothing to do, user needs to accept/reject
-        break;
       case PendingOperationType.TipPickup:
         await processTip(this.ws, pending.tipId, forceNow);
         break;
@@ -316,9 +310,11 @@ export class Wallet {
    * Process pending operations.
    */
   public async runPending(forceNow = false): Promise<void> {
-    const onlyDue = !forceNow;
-    const pendingOpsResponse = await this.getPendingOperations({ onlyDue });
+    const pendingOpsResponse = await this.getPendingOperations();
     for (const p of pendingOpsResponse.pendingOperations) {
+      if (!forceNow && !isTimestampExpired(p.timestampDue)) {
+        continue;
+      }
       try {
         await this.processOnePendingOperation(p, forceNow);
       } catch (e) {
@@ -364,7 +360,7 @@ export class Wallet {
         if (!maxRetries) {
           return;
         }
-        this.getPendingOperations({ onlyDue: false })
+        this.getPendingOperations()
           .then((pending) => {
             for (const p of pending.pendingOperations) {
               if (p.retryInfo && p.retryInfo.retryCounter > maxRetries) {
@@ -408,51 +404,53 @@ export class Wallet {
   }
 
   private async runRetryLoopImpl(): Promise<void> {
-    let iteration = 0;
-    for (; !this.stopped; iteration++) {
-      const pending = await this.getPendingOperations({ onlyDue: true });
-      let numDueAndLive = 0;
+    for (let iteration = 0; !this.stopped; iteration++) {
+      const pending = await this.getPendingOperations();
+      logger.trace(`pending operations: ${j2s(pending)}`);
+      let numGivingLiveness = 0;
+      let numDue = 0;
+      let minDue: Timestamp = { t_ms: "never" };
       for (const p of pending.pendingOperations) {
+        minDue = timestampMin(minDue, p.timestampDue);
+        if (isTimestampExpired(p.timestampDue)) {
+          numDue++;
+        }
         if (p.givesLifeness) {
-          numDueAndLive++;
+            numGivingLiveness++;
         }
       }
       // Make sure that we run tasks that don't give lifeness at least
       // one time.
-      if (iteration !== 0 && numDueAndLive === 0) {
-        const allPending = await this.getPendingOperations({ onlyDue: false });
-        let numPending = 0;
-        let numGivingLiveness = 0;
-        for (const p of allPending.pendingOperations) {
-          numPending++;
-          if (p.givesLifeness) {
-            numGivingLiveness++;
-          }
-        }
-        let dt: Duration;
-        if (
-          allPending.pendingOperations.length === 0 ||
-          allPending.nextRetryDelay.d_ms === Number.MAX_SAFE_INTEGER
-        ) {
-          // Wait for 5 seconds
-          dt = { d_ms: 5000 };
-        } else {
-          dt = durationMin({ d_ms: 5000 }, allPending.nextRetryDelay);
-        }
+      if (iteration !== 0 && numDue === 0) {
+        // We've executed pending, due operations at least one.
+        // Now we don't have any more operations available,
+        // and need to wait.
+
+        // Wait for at most 5 seconds to the next check.
+        const dt = durationMin(
+          durationFromSpec({
+            seconds: 5,
+          }),
+          getDurationRemaining(minDue),
+        );
+        logger.trace(`waiting for at most ${dt.d_ms} ms`)
         const timeout = this.timerGroup.resolveAfter(dt);
         this.ws.notify({
           type: NotificationType.WaitingForRetry,
           numGivingLiveness,
-          numPending,
+          numPending: pending.pendingOperations.length,
         });
+        // Wait until either the timeout, or we are notified (via the latch)
+        // that more work might be available.
         await Promise.race([timeout, this.latch.wait()]);
       } else {
-        // FIXME: maybe be a bit smarter about executing these
-        // operations in parallel?
         logger.trace(
           `running ${pending.pendingOperations.length} pending operations`,
         );
         for (const p of pending.pendingOperations) {
+          if (!isTimestampExpired(p.timestampDue)) {
+            continue;
+          }
           try {
             await this.processOnePendingOperation(p);
           } catch (e) {
@@ -650,12 +648,8 @@ export class Wallet {
     }
   }
 
-  async getPendingOperations({
-    onlyDue = false,
-  } = {}): Promise<PendingOperationsResponse> {
-    return this.ws.memoGetPending.memo(() =>
-      getPendingOperations(this.ws, { onlyDue }),
-    );
+  async getPendingOperations(): Promise<PendingOperationsResponse> {
+    return this.ws.memoGetPending.memo(() => getPendingOperations(this.ws));
   }
 
   async acceptExchangeTermsOfService(

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