gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: estimate refresh output, show


From: gnunet
Subject: [taler-wallet-core] branch master updated: estimate refresh output, show correct(er) balance
Date: Tue, 01 Sep 2020 19:31:51 +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 38e6d519 estimate refresh output, show correct(er) balance
38e6d519 is described below

commit 38e6d519461cff32107b5eebfc217fd9276960db
Author: Florian Dold <florian.dold@gmail.com>
AuthorDate: Tue Sep 1 23:01:44 2020 +0530

    estimate refresh output, show correct(er) balance
---
 packages/taler-integrationtests/src/harness.ts     | 44 +++---------
 .../src/test-refund-incremental.ts                 |  4 +-
 .../taler-wallet-core/src/operations/balance.ts    |  5 +-
 packages/taler-wallet-core/src/operations/pay.ts   |  8 ++-
 .../taler-wallet-core/src/operations/recoup.ts     |  2 +-
 .../taler-wallet-core/src/operations/refresh.ts    | 78 +++++++++++++++++-----
 .../taler-wallet-core/src/operations/withdraw.ts   |  6 +-
 packages/taler-wallet-core/src/types/dbTypes.ts    |  4 ++
 packages/taler-wallet-core/src/util/amounts.ts     |  7 +-
 packages/taler-wallet-core/src/wallet.ts           |  2 +-
 10 files changed, 99 insertions(+), 61 deletions(-)

diff --git a/packages/taler-integrationtests/src/harness.ts 
b/packages/taler-integrationtests/src/harness.ts
index 0b41923a..0beed397 100644
--- a/packages/taler-integrationtests/src/harness.ts
+++ b/packages/taler-integrationtests/src/harness.ts
@@ -361,53 +361,27 @@ export class GlobalTestState {
   }
 
   assertAmountEquals(
-    amtExpected: string | AmountJson,
     amtActual: string | AmountJson,
+    amtExpected: string | AmountJson,
   ): void {
-    let ja1: AmountJson;
-    let ja2: AmountJson;
-    if (typeof amtExpected === "string") {
-      ja1 = Amounts.parseOrThrow(amtExpected);
-    } else {
-      ja1 = amtExpected;
-    }
-    if (typeof amtActual === "string") {
-      ja2 = Amounts.parseOrThrow(amtActual);
-    } else {
-      ja2 = amtActual;
-    }
-
-    if (Amounts.cmp(ja1, ja2) != 0) {
+    if (Amounts.cmp(amtActual, amtExpected) != 0) {
       throw Error(
         `test assertion failed: expected ${Amounts.stringify(
-          ja1,
-        )} but got ${Amounts.stringify(ja2)}`,
+          amtExpected,
+        )} but got ${Amounts.stringify(amtActual)}`,
       );
     }
   }
 
   assertAmountLeq(
-    amtExpected: string | AmountJson,
-    amtActual: string | AmountJson,
+    a: string | AmountJson,
+    b: string | AmountJson,
   ): void {
-    let ja1: AmountJson;
-    let ja2: AmountJson;
-    if (typeof amtExpected === "string") {
-      ja1 = Amounts.parseOrThrow(amtExpected);
-    } else {
-      ja1 = amtExpected;
-    }
-    if (typeof amtActual === "string") {
-      ja2 = Amounts.parseOrThrow(amtActual);
-    } else {
-      ja2 = amtActual;
-    }
-
-    if (Amounts.cmp(ja1, ja2) > 0) {
+    if (Amounts.cmp(a, b) > 0) {
       throw Error(
         `test assertion failed: expected ${Amounts.stringify(
-          ja1,
-        )} to be less or equal (leq) than ${Amounts.stringify(ja2)}`,
+          a,
+        )} to be less or equal (leq) than ${Amounts.stringify(b)}`,
       );
     }
   }
diff --git a/packages/taler-integrationtests/src/test-refund-incremental.ts 
b/packages/taler-integrationtests/src/test-refund-incremental.ts
index 2e0f03f2..661378dc 100644
--- a/packages/taler-integrationtests/src/test-refund-incremental.ts
+++ b/packages/taler-integrationtests/src/test-refund-incremental.ts
@@ -175,7 +175,7 @@ runTest(async (t: GlobalTestState) => {
         .map((x) => x.amountRaw),
     ).amount;
 
-    t.assertAmountEquals(raw, "TESTKUDOS:10");
+    t.assertAmountEquals("TESTKUDOS:10", raw);
 
     const effective = Amounts.sum(
       txs.transactions
@@ -183,7 +183,7 @@ runTest(async (t: GlobalTestState) => {
         .map((x) => x.amountEffective),
     ).amount;
 
-    t.assertAmountEquals(effective, "TESTKUDOS:8.17");
+    t.assertAmountEquals("TESTKUDOS:8.33", effective);
   }
 
   await t.shutdown();
diff --git a/packages/taler-wallet-core/src/operations/balance.ts 
b/packages/taler-wallet-core/src/operations/balance.ts
index 26f0aaee..47ce5f48 100644
--- a/packages/taler-wallet-core/src/operations/balance.ts
+++ b/packages/taler-wallet-core/src/operations/balance.ts
@@ -90,7 +90,10 @@ export async function getBalancesInsideTransaction(
         const b = initBalance(session.amountRefreshOutput.currency);
         // We are always assuming the refresh will succeed, thus we
         // report the output as available balance.
-        b.available = Amounts.add(session.amountRefreshOutput).amount;
+        b.available = Amounts.add(b.available, 
session.amountRefreshOutput).amount;
+      } else {
+        const b = initBalance(r.inputPerCoin[i].currency);
+        b.available = Amounts.add(b.available, 
r.estimatedOutputPerCoin[i]).amount;
       }
     }
   });
diff --git a/packages/taler-wallet-core/src/operations/pay.ts 
b/packages/taler-wallet-core/src/operations/pay.ts
index 6981b44f..ce71737d 100644
--- a/packages/taler-wallet-core/src/operations/pay.ts
+++ b/packages/taler-wallet-core/src/operations/pay.ts
@@ -473,7 +473,13 @@ async function recordConfirmPay(
   };
 
   await ws.db.runWithWriteTransaction(
-    [Stores.coins, Stores.purchases, Stores.proposals, Stores.refreshGroups],
+    [
+      Stores.coins,
+      Stores.purchases,
+      Stores.proposals,
+      Stores.refreshGroups,
+      Stores.denominations,
+    ],
     async (tx) => {
       const p = await tx.get(Stores.proposals, proposal.proposalId);
       if (p) {
diff --git a/packages/taler-wallet-core/src/operations/recoup.ts 
b/packages/taler-wallet-core/src/operations/recoup.ts
index 7896fc27..0e4ce18d 100644
--- a/packages/taler-wallet-core/src/operations/recoup.ts
+++ b/packages/taler-wallet-core/src/operations/recoup.ts
@@ -180,7 +180,7 @@ async function recoupWithdrawCoin(
   // FIXME: verify that our expectations about the amount match
 
   await ws.db.runWithWriteTransaction(
-    [Stores.coins, Stores.reserves, Stores.recoupGroups],
+    [Stores.coins, Stores.denominations, Stores.reserves, Stores.recoupGroups],
     async (tx) => {
       const recoupGroup = await tx.get(Stores.recoupGroups, recoupGroupId);
       if (!recoupGroup) {
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts 
b/packages/taler-wallet-core/src/operations/refresh.ts
index d951cfeb..770c67da 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -31,7 +31,7 @@ import { amountToPretty } from "../util/helpers";
 import { TransactionHandle } from "../util/query";
 import { InternalWalletState, EXCHANGE_COINS_LOCK } from "./state";
 import { Logger } from "../util/logging";
-import { getWithdrawDenomList } from "./withdraw";
+import { getWithdrawDenomList, isWithdrawableDenom } from "./withdraw";
 import { updateExchangeFromUrl } from "./exchanges";
 import {
   TalerErrorDetails,
@@ -49,6 +49,7 @@ import {
   codecForExchangeRevealResponse,
 } from "../types/talerTypes";
 import { URL } from "../util/url";
+import { checkDbInvariant } from "../util/invariants";
 
 const logger = new Logger("refresh.ts");
 
@@ -132,8 +133,10 @@ async function refreshCreateSession(
     .iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchange.baseUrl)
     .toArray();
 
-  const availableAmount = Amounts.sub(coin.currentAmount, oldDenom.feeRefresh)
-    .amount;
+  const availableAmount = Amounts.sub(
+    refreshGroup.inputPerCoin[coinIndex],
+    oldDenom.feeRefresh,
+  ).amount;
 
   const newCoinDenoms = getWithdrawDenomList(availableAmount, availableDenoms);
 
@@ -177,22 +180,10 @@ async function refreshCreateSession(
     oldDenom.feeRefresh,
   );
 
-  // Store refresh session and subtract refreshed amount from
-  // coin in the same transaction.
+  // Store refresh session for this coin in the database.
   await ws.db.runWithWriteTransaction(
     [Stores.refreshGroups, Stores.coins],
     async (tx) => {
-      const c = await tx.get(Stores.coins, coin.coinPub);
-      if (!c) {
-        throw Error("coin not found, but marked for refresh");
-      }
-      const r = Amounts.sub(c.currentAmount, 
refreshSession.amountRefreshInput);
-      if (r.saturated) {
-        logger.warn("can't refresh coin, no amount left");
-        return;
-      }
-      c.currentAmount = r.amount;
-      c.status = CoinStatus.Dormant;
       const rg = await tx.get(Stores.refreshGroups, refreshGroupId);
       if (!rg) {
         return;
@@ -202,7 +193,6 @@ async function refreshCreateSession(
       }
       rg.refreshSessionPerCoin[coinIndex] = refreshSession;
       await tx.put(Stores.refreshGroups, rg);
-      await tx.put(Stores.coins, c);
     },
   );
   logger.info(
@@ -552,6 +542,16 @@ async function processRefreshSession(
 
 /**
  * Create a refresh group for a list of coins.
+ *
+ * Refreshes the remaining amount on the coin, effectively capturing the 
remaining
+ * value in the refresh group.
+ *
+ * The caller must ensure that
+ * the remaining amount was updated correctly before the coin was deposited or
+ * credited.
+ *
+ * The caller must also ensure that the coins that should be refreshed exist
+ * in the current database transaction.
  */
 export async function createRefreshGroup(
   ws: InternalWalletState,
@@ -561,6 +561,48 @@ export async function createRefreshGroup(
 ): Promise<RefreshGroupId> {
   const refreshGroupId = encodeCrock(getRandomBytes(32));
 
+  const inputPerCoin: AmountJson[] = [];
+  const estimatedOutputPerCoin: AmountJson[] = [];
+
+  const denomsPerExchange: Record<string, DenominationRecord[]> = {};
+
+  const getDenoms = async (
+    exchangeBaseUrl: string,
+  ): Promise<DenominationRecord[]> => {
+    if (denomsPerExchange[exchangeBaseUrl]) {
+      return denomsPerExchange[exchangeBaseUrl];
+    }
+    const allDenoms = await tx
+      .iterIndexed(Stores.denominations.exchangeBaseUrlIndex, exchangeBaseUrl)
+      .filter((x) => {
+        return isWithdrawableDenom(x);
+      });
+    denomsPerExchange[exchangeBaseUrl] = allDenoms;
+    return allDenoms;
+  };
+
+  for (const ocp of oldCoinPubs) {
+    const coin = await tx.get(Stores.coins, ocp.coinPub);
+    checkDbInvariant(!!coin, "coin must be in database");
+    const denom = await tx.get(Stores.denominations, [
+      coin.exchangeBaseUrl,
+      coin.denomPub,
+    ]);
+    checkDbInvariant(
+      !!denom,
+      "denomination for existing coin must be in database",
+    );
+    const refreshAmount = coin.currentAmount;
+    inputPerCoin.push(refreshAmount);
+    coin.currentAmount = Amounts.getZero(refreshAmount.currency);
+    coin.status = CoinStatus.Dormant;
+    await tx.put(Stores.coins, coin);
+    const denoms = await getDenoms(coin.exchangeBaseUrl);
+    const cost = getTotalRefreshCost(denoms, denom, refreshAmount);
+    const output = Amounts.sub(refreshAmount, cost).amount;
+    estimatedOutputPerCoin.push(output);
+  }
+
   const refreshGroup: RefreshGroupRecord = {
     timestampFinished: undefined,
     finishedPerCoin: oldCoinPubs.map((x) => false),
@@ -571,6 +613,8 @@ export async function createRefreshGroup(
     refreshGroupId,
     refreshSessionPerCoin: oldCoinPubs.map((x) => undefined),
     retryInfo: initRetryInfo(),
+    inputPerCoin,
+    estimatedOutputPerCoin,
   };
 
   if (oldCoinPubs.length == 0) {
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts 
b/packages/taler-wallet-core/src/operations/withdraw.ts
index c68f1521..c719f7ab 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -67,7 +67,11 @@ import { encodeCrock } from "../crypto/talerCrypto";
 
 const logger = new Logger("withdraw.ts");
 
-function isWithdrawableDenom(d: DenominationRecord): boolean {
+/**
+ * Check if a denom is withdrawable based on the expiration time
+ * and revocation state.
+ */
+export function isWithdrawableDenom(d: DenominationRecord): boolean {
   const now = getTimestampNow();
   const started = timestampCmp(now, d.stampStart) >= 0;
   const lastPossibleWithdraw = timestampSubtractDuraction(
diff --git a/packages/taler-wallet-core/src/types/dbTypes.ts 
b/packages/taler-wallet-core/src/types/dbTypes.ts
index 148ec8c3..9ba07a0f 100644
--- a/packages/taler-wallet-core/src/types/dbTypes.ts
+++ b/packages/taler-wallet-core/src/types/dbTypes.ts
@@ -1014,6 +1014,10 @@ export interface RefreshGroupRecord {
 
   refreshSessionPerCoin: (RefreshSessionRecord | undefined)[];
 
+  inputPerCoin: AmountJson[];
+
+  estimatedOutputPerCoin: AmountJson[];
+
   /**
    * Flag for each coin whether refreshing finished.
    * If a coin can't be refreshed (remaining value too small),
diff --git a/packages/taler-wallet-core/src/util/amounts.ts 
b/packages/taler-wallet-core/src/util/amounts.ts
index 2f912cff..533005b1 100644
--- a/packages/taler-wallet-core/src/util/amounts.ts
+++ b/packages/taler-wallet-core/src/util/amounts.ts
@@ -195,7 +195,9 @@ export function sub(a: AmountJson, ...rest: AmountJson[]): 
Result {
  * Compare two amounts.  Returns 0 when equal, -1 when a < b
  * and +1 when a > b.  Throws when currencies don't match.
  */
-export function cmp(a: AmountJson, b: AmountJson): -1 | 0 | 1 {
+export function cmp(a: AmountLike, b: AmountLike): -1 | 0 | 1 {
+  a = jsonifyAmount(a);
+  b = jsonifyAmount(b);
   if (a.currency !== b.currency) {
     throw Error(`Mismatched currency: ${a.currency} and ${b.currency}`);
   }
@@ -310,7 +312,8 @@ export function fromFloat(floatVal: number, currency: 
string): AmountJson {
  * Convert to standard human-readable string representation that's
  * also used in JSON formats.
  */
-export function stringify(a: AmountJson): string {
+export function stringify(a: AmountLike): string {
+  a = jsonifyAmount(a);
   const av = a.value + Math.floor(a.fraction / fractionalBase);
   const af = a.fraction % fractionalBase;
   let s = av.toString();
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index ccc4e9d2..b448df64 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -571,7 +571,7 @@ export class Wallet {
   async refresh(oldCoinPub: string): Promise<void> {
     try {
       const refreshGroupId = await this.db.runWithWriteTransaction(
-        [Stores.refreshGroups],
+        [Stores.refreshGroups, Stores.denominations, Stores.coins],
         async (tx) => {
           return await createRefreshGroup(
             this.ws,

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