gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: implement and test auto-refre


From: gnunet
Subject: [taler-wallet-core] branch master updated: implement and test auto-refresh
Date: Thu, 03 Sep 2020 17:08:32 +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 f51a59bc implement and test auto-refresh
f51a59bc is described below

commit f51a59bc72c2886cb2bb88b149a3353857e3eb44
Author: Florian Dold <florian.dold@gmail.com>
AuthorDate: Thu Sep 3 20:38:26 2020 +0530

    implement and test auto-refresh
---
 packages/taler-integrationtests/src/harness.ts     |  11 +++
 packages/taler-wallet-core/src/index.ts            |   1 +
 .../taler-wallet-core/src/operations/exchanges.ts  |   3 +
 packages/taler-wallet-core/src/operations/pay.ts   |  21 ++++-
 .../taler-wallet-core/src/operations/pending.ts    |   8 +-
 .../taler-wallet-core/src/operations/refresh.ts    | 100 ++++++++++++++++++++-
 .../taler-wallet-core/src/types/walletTypes.ts     |   1 +
 packages/taler-wallet-core/src/util/time.ts        |   7 ++
 packages/taler-wallet-core/src/wallet.ts           |   9 +-
 9 files changed, 152 insertions(+), 9 deletions(-)

diff --git a/packages/taler-integrationtests/src/harness.ts 
b/packages/taler-integrationtests/src/harness.ts
index b4652526..cc30df61 100644
--- a/packages/taler-integrationtests/src/harness.ts
+++ b/packages/taler-integrationtests/src/harness.ts
@@ -68,6 +68,7 @@ import {
   AmountString,
   ApplyRefundRequest,
   codecForApplyRefundResponse,
+  codecForAny,
 } from "taler-wallet-core";
 import { URL } from "url";
 import axios, { AxiosError } from "axios";
@@ -79,6 +80,7 @@ import {
   MerchantOrderPrivateStatusResponse,
 } from "./merchantApiTypes";
 import { ApplyRefundResponse } from "taler-wallet-core";
+import { PendingOperationsResponse } from "taler-wallet-core";
 
 const exec = util.promisify(require("child_process").exec);
 
@@ -1562,6 +1564,15 @@ export class WalletCli {
     throw new OperationFailedError(resp.error);
   }
 
+  async getPendingOperations(): Promise<PendingOperationsResponse> {
+    const resp = await this.apiRequest("getPendingOperations", {});
+    if (resp.type === "response") {
+      // FIXME: validate properly!
+      return codecForAny().decode(resp.result);
+    }
+    throw new OperationFailedError(resp.error);
+  }
+
   async getTransactions(): Promise<TransactionsResponse> {
     const resp = await this.apiRequest("getTransactions", {});
     if (resp.type === "response") {
diff --git a/packages/taler-wallet-core/src/index.ts 
b/packages/taler-wallet-core/src/index.ts
index 8d5d46b4..b78d7b82 100644
--- a/packages/taler-wallet-core/src/index.ts
+++ b/packages/taler-wallet-core/src/index.ts
@@ -64,3 +64,4 @@ export * from "./types/talerTypes";
 export * from "./types/walletTypes";
 export * from "./types/notifications";
 export * from "./types/transactions";
+export * from "./types/pending";
\ No newline at end of file
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts 
b/packages/taler-wallet-core/src/operations/exchanges.ts
index d162ca3b..d3c72d16 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -303,6 +303,9 @@ async function updateExchangeFinalize(
       }
       r.addComplete = true;
       r.updateStatus = ExchangeUpdateStatus.Finished;
+      // Reset time to next auto refresh check,
+      // as now new denominations might be available.
+      r.nextRefreshCheck = undefined;
       await tx.put(Stores.exchanges, r);
       const updateEvent: ExchangeUpdatedEventRecord = {
         exchangeBaseUrl: exchange.baseUrl,
diff --git a/packages/taler-wallet-core/src/operations/pay.ts 
b/packages/taler-wallet-core/src/operations/pay.ts
index 2c491ec6..c6f39858 100644
--- a/packages/taler-wallet-core/src/operations/pay.ts
+++ b/packages/taler-wallet-core/src/operations/pay.ts
@@ -36,6 +36,8 @@ import {
   PayEventRecord,
   WalletContractData,
   getRetryDuration,
+  CoinRecord,
+  DenominationRecord,
 } from "../types/dbTypes";
 import { NotificationType } from "../types/notifications";
 import {
@@ -65,6 +67,7 @@ import {
   Duration,
   durationMax,
   durationMin,
+  isTimestampExpired,
 } from "../util/time";
 import { strcmp, canonicalJson } from "../util/helpers";
 import {
@@ -285,6 +288,19 @@ export function selectPayCoins(
   return undefined;
 }
 
+export function isSpendableCoin(coin: CoinRecord, denom: DenominationRecord): 
boolean {
+  if (coin.suspended) {
+    return false;
+  }
+  if (coin.status !== CoinStatus.Fresh) {
+    return false;
+  }
+  if (isTimestampExpired(denom.stampExpireDeposit)) {
+    return false;
+  }
+  return true;
+}
+
 /**
  * Select coins from the wallet's database that can be used
  * to pay for the given contract.
@@ -370,10 +386,7 @@ async function getCoinsForPayment(
         );
         continue;
       }
-      if (coin.suspended) {
-        continue;
-      }
-      if (coin.status !== CoinStatus.Fresh) {
+      if (!isSpendableCoin(coin, denom)) {
         continue;
       }
       acis.push({
diff --git a/packages/taler-wallet-core/src/operations/pending.ts 
b/packages/taler-wallet-core/src/operations/pending.ts
index e24e8fc4..e51f3770 100644
--- a/packages/taler-wallet-core/src/operations/pending.ts
+++ b/packages/taler-wallet-core/src/operations/pending.ts
@@ -102,7 +102,13 @@ async function gatherExchangePending(
             lastError: e.lastError,
             reason: "scheduled",
           });
-          break;
+        }
+        if (e.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:
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts 
b/packages/taler-wallet-core/src/operations/refresh.ts
index 6c1e643a..76f3015f 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -42,8 +42,23 @@ import {
 import { guardOperationException } from "./errors";
 import { NotificationType } from "../types/notifications";
 import { getRandomBytes, encodeCrock } from "../crypto/talerCrypto";
-import { getTimestampNow, Duration } from "../util/time";
-import { readSuccessResponseJsonOrThrow, HttpResponse } from "../util/http";
+import {
+  getTimestampNow,
+  Duration,
+  Timestamp,
+  isTimestampExpired,
+  durationFromSpec,
+  timestampMin,
+  timestampAddDuration,
+  timestampDifference,
+  durationMax,
+  durationMul,
+} from "../util/time";
+import {
+  readSuccessResponseJsonOrThrow,
+  HttpResponse,
+  throwUnexpectedRequestError,
+} from "../util/http";
 import {
   codecForExchangeMeltResponse,
   codecForExchangeRevealResponse,
@@ -635,7 +650,86 @@ export async function createRefreshGroup(
   };
 }
 
+/**
+ * Timestamp after which the wallet would do the next check for an 
auto-refresh.
+ */
+function getAutoRefreshCheckThreshold(d: DenominationRecord): Timestamp {
+  const delta = timestampDifference(d.stampExpireWithdraw, 
d.stampExpireDeposit);
+  const deltaDiv = durationMul(delta, 0.75);
+  return timestampAddDuration(d.stampExpireWithdraw, deltaDiv);
+}
+
+/**
+ * Timestamp after which the wallet would do an auto-refresh.
+ */
+function getAutoRefreshExecuteThreshold(d: DenominationRecord): Timestamp {
+  const delta = timestampDifference(d.stampExpireWithdraw, 
d.stampExpireDeposit);
+  const deltaDiv = durationMul(delta, 0.5);
+  return timestampAddDuration(d.stampExpireWithdraw, deltaDiv);
+}
+
 export async function autoRefresh(
   ws: InternalWalletState,
   exchangeBaseUrl: string,
-): Promise<void> {}
+): Promise<void> {
+  await ws.db.runWithWriteTransaction(
+    [
+      Stores.coins,
+      Stores.denominations,
+      Stores.refreshGroups,
+      Stores.exchanges,
+    ],
+    async (tx) => {
+      const exchange = await tx.get(Stores.exchanges, exchangeBaseUrl);
+      if (!exchange) {
+        return;
+      }
+      const coins = await tx
+        .iterIndexed(Stores.coins.exchangeBaseUrlIndex, exchangeBaseUrl)
+        .toArray();
+      const refreshCoins: CoinPublicKey[] = [];
+      for (const coin of coins) {
+        if (coin.status !== CoinStatus.Fresh) {
+          continue;
+        }
+        if (coin.suspended) {
+          continue;
+        }
+        const denom = await tx.get(Stores.denominations, [
+          exchangeBaseUrl,
+          coin.denomPub,
+        ]);
+        if (!denom) {
+          logger.warn("denomination not in database");
+          continue;
+        }
+        const executeThreshold = getAutoRefreshExecuteThreshold(denom);
+        if (isTimestampExpired(executeThreshold)) {
+          refreshCoins.push(coin);
+        }
+      }
+      if (refreshCoins.length > 0) {
+        await createRefreshGroup(ws, tx, refreshCoins, 
RefreshReason.Scheduled);
+      }
+
+      const denoms = await tx
+        .iterIndexed(Stores.denominations.exchangeBaseUrlIndex, 
exchangeBaseUrl)
+        .toArray();
+      let minCheckThreshold = timestampAddDuration(
+        getTimestampNow(),
+        durationFromSpec({ days: 1 }),
+      );
+      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.put(Stores.exchanges, exchange);
+    },
+  );
+}
diff --git a/packages/taler-wallet-core/src/types/walletTypes.ts 
b/packages/taler-wallet-core/src/types/walletTypes.ts
index bde4fee6..5686ee61 100644
--- a/packages/taler-wallet-core/src/types/walletTypes.ts
+++ b/packages/taler-wallet-core/src/types/walletTypes.ts
@@ -538,6 +538,7 @@ export enum RefreshReason {
   AbortPay = "abort-pay",
   Recoup = "recoup",
   BackupRestored = "backup-restored",
+  Scheduled = "scheduled",
 }
 
 /**
diff --git a/packages/taler-wallet-core/src/util/time.ts 
b/packages/taler-wallet-core/src/util/time.ts
index 512d5e90..1f085107 100644
--- a/packages/taler-wallet-core/src/util/time.ts
+++ b/packages/taler-wallet-core/src/util/time.ts
@@ -144,6 +144,13 @@ export function durationMax(d1: Duration, d2: Duration): 
Duration {
   return { d_ms: Math.max(d1.d_ms, d2.d_ms) };
 }
 
+export function durationMul(d: Duration, n: number): Duration {
+  if (d.d_ms === "forever") {
+    return { d_ms: "forever" };
+  }
+  return { d_ms: Math.round( d.d_ms * n) };
+}
+
 export function timestampCmp(t1: Timestamp, t2: Timestamp): number {
   if (t1.t_ms === "never") {
     if (t2.t_ms === "never") {
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index 5ca3581a..21de541e 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -373,7 +373,13 @@ export class Wallet {
   private async runRetryLoopImpl(): Promise<void> {
     while (!this.stopped) {
       const pending = await this.getPendingOperations({ onlyDue: true });
-      if (pending.pendingOperations.length === 0) {
+      let numDueAndLive = 0;
+      for (const p of pending.pendingOperations) {
+        if (p.givesLifeness) {
+          numDueAndLive++;
+        }
+      }
+      if (numDueAndLive === 0) {
         const allPending = await this.getPendingOperations({ onlyDue: false });
         let numPending = 0;
         let numGivingLiveness = 0;
@@ -404,6 +410,7 @@ export class Wallet {
       } 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) {
           try {
             await this.processOnePendingOperation(p);

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