gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated (2c52046f -> b5b8f96c)


From: gnunet
Subject: [taler-wallet-core] branch master updated (2c52046f -> b5b8f96c)
Date: Thu, 12 Mar 2020 14:55:45 +0100

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

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

    from 2c52046f full recoup, untested/unfinished first attempt
     new 2ec6799c improve error reporting for DB queries
     new b5b8f96c improved error reporting / towards a working recoup

The 2 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:
 src/crypto/workers/cryptoImplementation.ts |   2 +-
 src/headless/taler-wallet-cli.ts           |   3 +-
 src/operations/errors.ts                   | 104 ++++++++++++++++++++++++-----
 src/operations/exchanges.ts                |  56 +++++++++-------
 src/operations/history.ts                  |   1 -
 src/operations/pending.ts                  |   2 +
 src/operations/recoup.ts                   |  34 +++++-----
 src/operations/refund.ts                   |   2 +-
 src/operations/reserves.ts                 |  45 +++++++++++--
 src/operations/withdraw.ts                 |  36 +++++-----
 src/types/ReserveTransaction.ts            |  34 +++-------
 src/types/dbTypes.ts                       |   5 ++
 src/types/notifications.ts                 |  21 +++---
 src/types/pending.ts                       |   2 +
 src/types/talerTypes.ts                    |  16 ++++-
 src/util/query.ts                          |   8 ++-
 src/wallet.ts                              |   7 +-
 17 files changed, 258 insertions(+), 120 deletions(-)

diff --git a/src/crypto/workers/cryptoImplementation.ts 
b/src/crypto/workers/cryptoImplementation.ts
index 3447c56f..5659fec2 100644
--- a/src/crypto/workers/cryptoImplementation.ts
+++ b/src/crypto/workers/cryptoImplementation.ts
@@ -214,7 +214,7 @@ export class CryptoImplementation {
       coin_blind_key_secret: coin.blindingKey,
       coin_pub: coin.coinPub,
       coin_sig: encodeCrock(coinSig),
-      denom_pub: coin.denomPub,
+      denom_pub_hash: coin.denomPubHash,
       denom_sig: coin.denomSig,
       refreshed: (coin.coinSource.type === CoinSourceType.Refresh),
     };
diff --git a/src/headless/taler-wallet-cli.ts b/src/headless/taler-wallet-cli.ts
index 70784995..28618bcc 100644
--- a/src/headless/taler-wallet-cli.ts
+++ b/src/headless/taler-wallet-cli.ts
@@ -217,9 +217,10 @@ walletCli
   .subcommand("runPendingOpt", "run-pending", {
     help: "Run pending operations.",
   })
+  .flag("forceNow", ["-f", "--force-now"])
   .action(async args => {
     await withWallet(args, async wallet => {
-      await wallet.runPending();
+      await wallet.runPending(args.runPendingOpt.forceNow);
     });
   });
 
diff --git a/src/operations/errors.ts b/src/operations/errors.ts
index 7e97fdb3..751a5711 100644
--- a/src/operations/errors.ts
+++ b/src/operations/errors.ts
@@ -1,8 +1,6 @@
-import { OperationError } from "../types/walletTypes";
-
 /*
  This file is part of GNU Taler
- (C) 2019 GNUnet e.V.
+ (C) 2019-2020 Taler Systems SA
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -16,13 +14,26 @@ import { OperationError } from "../types/walletTypes";
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
+/**
+ * Classes and helpers for error handling specific to wallet operations.
+ *
+ * @author Florian Dold <address@hidden>
+ */
+
+/**
+ * Imports.
+ */
+import { OperationError } from "../types/walletTypes";
+import { HttpResponse } from "../util/http";
+import { Codec } from "../util/codec";
+
 /**
  * This exception is there to let the caller know that an error happened,
  * but the error has already been reported by writing it to the database.
  */
 export class OperationFailedAndReportedError extends Error {
-  constructor(message: string) {
-    super(message);
+  constructor(public operationError: OperationError) {
+    super(operationError.message);
 
     // Set the prototype explicitly.
     Object.setPrototypeOf(this, OperationFailedAndReportedError.prototype);
@@ -34,14 +45,73 @@ export class OperationFailedAndReportedError extends Error {
  * responsible for recording the failure in the database.
  */
 export class OperationFailedError extends Error {
-  constructor(message: string, public err: OperationError) {
-    super(message);
+  constructor(public operationError: OperationError) {
+    super(operationError.message);
 
     // Set the prototype explicitly.
     Object.setPrototypeOf(this, OperationFailedError.prototype);
   }
 }
 
+/**
+ * Process an HTTP response that we expect to contain Taler-specific JSON.
+ *
+ * Depending on the status code, we throw an exception.  This function
+ * will try to extract Taler-specific error information from the HTTP response
+ * if possible.
+ */
+export async function scrutinizeTalerJsonResponse<T>(
+  resp: HttpResponse,
+  codec: Codec<T>,
+): Promise<T> {
+
+  // FIXME: We should distinguish between different types of error status
+  // to react differently (throttle, report permanent failure)
+
+  // FIXME: Make sure that when we receive an error message,
+  // it looks like a Taler error message
+
+  if (resp.status !== 200) {
+    let exc: OperationFailedError | undefined = undefined;
+    try {
+      const errorJson = await resp.json();
+      const m = `received error response (status ${resp.status})`;
+      exc = new OperationFailedError({
+        type: "protocol",
+        message: m,
+        details: {
+          httpStatusCode: resp.status,
+          errorResponse: errorJson,
+        }
+      });
+    } catch (e) {
+      const m = "could not parse response JSON";
+      exc = new OperationFailedError({
+        type: "network",
+        message: m,
+        details: {
+          status: resp.status,
+        }
+      });
+    }
+    throw exc;
+  }
+  let json: any;
+  try {
+    json = await resp.json();
+  } catch (e) {
+    const m = "could not parse response JSON";
+      throw new OperationFailedError({
+        type: "network",
+        message: m,
+        details: {
+          status: resp.status,
+        }
+      });
+  }
+  return codec.decode(json);
+}
+
 /**
  * Run an operation and call the onOpError callback
  * when there was an exception or operation error that must be reported.
@@ -59,26 +129,28 @@ export async function guardOperationException<T>(
       throw e;
     }
     if (e instanceof OperationFailedError) {
-      await onOpError(e.err);
-      throw new OperationFailedAndReportedError(e.message);
+      await onOpError(e.operationError);
+      throw new OperationFailedAndReportedError(e.operationError);
     }
     if (e instanceof Error) {
       console.log("guard: caught Error");
-      await onOpError({
+      const opErr = {
         type: "exception",
         message: e.message,
         details: {},
-      });
-      throw new OperationFailedAndReportedError(e.message);
+      }
+      await onOpError(opErr);
+      throw new OperationFailedAndReportedError(opErr);
     }
     console.log("guard: caught something else");
-    await onOpError({
+    const opErr = {
       type: "exception",
       message: "non-error exception thrown",
       details: {
         value: e.toString(),
       },
-    });
-    throw new OperationFailedAndReportedError(e.message);
+    };
+    await onOpError(opErr);
+    throw new OperationFailedAndReportedError(opErr);
   }
-}
\ No newline at end of file
+}
diff --git a/src/operations/exchanges.ts b/src/operations/exchanges.ts
index ed13a1e5..04238e61 100644
--- a/src/operations/exchanges.ts
+++ b/src/operations/exchanges.ts
@@ -115,72 +115,78 @@ async function updateExchangeWithKeys(
     keysResp = await r.json();
   } catch (e) {
     const m = `Fetching keys failed: ${e.message}`;
-    await setExchangeError(ws, baseUrl, {
+    const opErr = {
       type: "network",
       details: {
         requestUrl: e.config?.url,
       },
       message: m,
-    });
-    throw new OperationFailedAndReportedError(m);
+    };
+    await setExchangeError(ws, baseUrl, opErr);
+    throw new OperationFailedAndReportedError(opErr);
   }
   let exchangeKeysJson: ExchangeKeysJson;
   try {
     exchangeKeysJson = codecForExchangeKeysJson().decode(keysResp);
   } catch (e) {
     const m = `Parsing /keys response failed: ${e.message}`;
-    await setExchangeError(ws, baseUrl, {
+    const opErr = {
       type: "protocol-violation",
       details: {},
       message: m,
-    });
-    throw new OperationFailedAndReportedError(m);
+    };
+    await setExchangeError(ws, baseUrl, opErr);
+    throw new OperationFailedAndReportedError(opErr);
   }
 
   const lastUpdateTimestamp = exchangeKeysJson.list_issue_date;
   if (!lastUpdateTimestamp) {
     const m = `Parsing /keys response failed: invalid list_issue_date.`;
-    await setExchangeError(ws, baseUrl, {
+    const opErr = {
       type: "protocol-violation",
       details: {},
       message: m,
-    });
-    throw new OperationFailedAndReportedError(m);
+    };
+    await setExchangeError(ws, baseUrl, opErr);
+    throw new OperationFailedAndReportedError(opErr);
   }
 
   if (exchangeKeysJson.denoms.length === 0) {
     const m = "exchange doesn't offer any denominations";
-    await setExchangeError(ws, baseUrl, {
+    const opErr = {
       type: "protocol-violation",
       details: {},
       message: m,
-    });
-    throw new OperationFailedAndReportedError(m);
+    };
+    await setExchangeError(ws, baseUrl, opErr);
+    throw new OperationFailedAndReportedError(opErr);
   }
 
   const protocolVersion = exchangeKeysJson.version;
   if (!protocolVersion) {
     const m = "outdate exchange, no version in /keys response";
-    await setExchangeError(ws, baseUrl, {
+    const opErr = {
       type: "protocol-violation",
       details: {},
       message: m,
-    });
-    throw new OperationFailedAndReportedError(m);
+    };
+    await setExchangeError(ws, baseUrl, opErr);
+    throw new OperationFailedAndReportedError(opErr);
   }
 
   const versionRes = compare(WALLET_EXCHANGE_PROTOCOL_VERSION, 
protocolVersion);
   if (versionRes?.compatible != true) {
     const m = "exchange protocol version not compatible with wallet";
-    await setExchangeError(ws, baseUrl, {
+    const opErr = {
       type: "protocol-incompatible",
       details: {
         exchangeProtocolVersion: protocolVersion,
         walletProtocolVersion: WALLET_EXCHANGE_PROTOCOL_VERSION,
       },
       message: m,
-    });
-    throw new OperationFailedAndReportedError(m);
+    };
+    await setExchangeError(ws, baseUrl, opErr);
+    throw new OperationFailedAndReportedError(opErr);
   }
 
   const currency = Amounts.parseOrThrow(exchangeKeysJson.denoms[0].value)
@@ -195,7 +201,7 @@ async function updateExchangeWithKeys(
   let recoupGroupId: string | undefined = undefined;
 
   await ws.db.runWithWriteTransaction(
-    [Stores.exchanges, Stores.denominations],
+    [Stores.exchanges, Stores.denominations, Stores.recoupGroups, 
Stores.coins],
     async tx => {
       const r = await tx.get(Stores.exchanges, baseUrl);
       if (!r) {
@@ -231,10 +237,11 @@ async function updateExchangeWithKeys(
       // Handle recoup
       const recoupDenomList = exchangeKeysJson.recoup ?? [];
       const newlyRevokedCoinPubs: string[] = [];
-      for (const recoupDenomPubHash of recoupDenomList) {
+      console.log("recoup list from exchange", recoupDenomList);
+      for (const recoupInfo of recoupDenomList) {
         const oldDenom = await tx.getIndexed(
           Stores.denominations.denomPubHashIndex,
-          recoupDenomPubHash,
+          recoupInfo.h_denom_pub,
         );
         if (!oldDenom) {
           // We never even knew about the revoked denomination, all good.
@@ -243,18 +250,21 @@ async function updateExchangeWithKeys(
         if (oldDenom.isRevoked) {
           // We already marked the denomination as revoked,
           // this implies we revoked all coins
+          console.log("denom already revoked");
           continue;
         }
+        console.log("revoking denom", recoupInfo.h_denom_pub);
         oldDenom.isRevoked = true;
         await tx.put(Stores.denominations, oldDenom);
         const affectedCoins = await tx
-          .iterIndexed(Stores.coins.denomPubIndex)
+          .iterIndexed(Stores.coins.denomPubHashIndex, recoupInfo.h_denom_pub)
           .toArray();
         for (const ac of affectedCoins) {
           newlyRevokedCoinPubs.push(ac.coinPub);
         }
       }
       if (newlyRevokedCoinPubs.length != 0) {
+        console.log("recouping coins", newlyRevokedCoinPubs);
         await createRecoupGroup(ws, tx, newlyRevokedCoinPubs);
       }
     },
@@ -263,7 +273,7 @@ async function updateExchangeWithKeys(
   if (recoupGroupId) {
     // Asynchronously start recoup.  This doesn't need to finish
     // for the exchange update to be considered finished.
-    processRecoupGroup(ws, recoupGroupId).catch((e) => {
+    processRecoupGroup(ws, recoupGroupId).catch(e => {
       console.log("error while recouping coins:", e);
     });
   }
diff --git a/src/operations/history.ts b/src/operations/history.ts
index 2cf215a5..c09aa8d3 100644
--- a/src/operations/history.ts
+++ b/src/operations/history.ts
@@ -41,7 +41,6 @@ import {
 } from "../types/history";
 import { assertUnreachable } from "../util/assertUnreachable";
 import { TransactionHandle, Store } from "../util/query";
-import { ReserveTransactionType } from "../types/ReserveTransaction";
 import { timestampCmp } from "../util/time";
 
 /**
diff --git a/src/operations/pending.ts b/src/operations/pending.ts
index 08ec3fc9..a628d613 100644
--- a/src/operations/pending.ts
+++ b/src/operations/pending.ts
@@ -427,6 +427,8 @@ async function gatherRecoupPending(
       type: PendingOperationType.Recoup,
       givesLifeness: true,
       recoupGroupId: rg.recoupGroupId,
+      retryInfo: rg.retryInfo,
+      lastError: rg.lastError,
     });
   });
 }
diff --git a/src/operations/recoup.ts b/src/operations/recoup.ts
index 842a67b8..3097dd05 100644
--- a/src/operations/recoup.ts
+++ b/src/operations/recoup.ts
@@ -40,7 +40,7 @@ import {
 
 import { codecForRecoupConfirmation } from "../types/talerTypes";
 import { NotificationType } from "../types/notifications";
-import { processReserve } from "./reserves";
+import { forceQueryReserve } from "./reserves";
 
 import * as Amounts from "../util/amounts";
 import { createRefreshGroup, processRefreshGroup } from "./refresh";
@@ -48,7 +48,7 @@ import { RefreshReason, OperationError } from 
"../types/walletTypes";
 import { TransactionHandle } from "../util/query";
 import { encodeCrock, getRandomBytes } from "../crypto/talerCrypto";
 import { getTimestampNow } from "../util/time";
-import { guardOperationException } from "./errors";
+import { guardOperationException, scrutinizeTalerJsonResponse } from 
"./errors";
 
 async function incrementRecoupRetry(
   ws: InternalWalletState,
@@ -133,17 +133,17 @@ async function recoupWithdrawCoin(
   const recoupRequest = await ws.cryptoApi.createRecoupRequest(coin);
   const reqUrl = new URL(`/coins/${coin.coinPub}/recoup`, 
coin.exchangeBaseUrl);
   const resp = await ws.http.postJson(reqUrl.href, recoupRequest);
-  if (resp.status !== 200) {
-    throw Error("recoup request failed");
-  }
-  const recoupConfirmation = codecForRecoupConfirmation().decode(
-    await resp.json(),
+  const recoupConfirmation = await scrutinizeTalerJsonResponse(
+    resp,
+    codecForRecoupConfirmation(),
   );
 
   if (recoupConfirmation.reserve_pub !== reservePub) {
     throw Error(`Coin's reserve doesn't match reserve on recoup`);
   }
 
+  // FIXME: verify signature
+
   // FIXME: verify that our expectations about the amount match
 
   await ws.db.runWithWriteTransaction(
@@ -178,8 +178,8 @@ async function recoupWithdrawCoin(
     type: NotificationType.RecoupFinished,
   });
 
-  processReserve(ws, reserve.reservePub).catch(e => {
-    console.log("processing reserve after recoup failed:", e);
+  forceQueryReserve(ws, reserve.reservePub).catch(e => {
+    console.log("re-querying reserve after recoup failed:", e);
   });
 }
 
@@ -196,12 +196,11 @@ async function recoupRefreshCoin(
 
   const recoupRequest = await ws.cryptoApi.createRecoupRequest(coin);
   const reqUrl = new URL(`/coins/${coin.coinPub}/recoup`, 
coin.exchangeBaseUrl);
+  console.log("making recoup request");
   const resp = await ws.http.postJson(reqUrl.href, recoupRequest);
-  if (resp.status !== 200) {
-    throw Error("recoup request failed");
-  }
-  const recoupConfirmation = codecForRecoupConfirmation().decode(
-    await resp.json(),
+  const recoupConfirmation = await scrutinizeTalerJsonResponse(
+    resp,
+    codecForRecoupConfirmation(),
   );
 
   if (recoupConfirmation.old_coin_pub != cs.oldCoinPub) {
@@ -283,11 +282,14 @@ async function processRecoupGroupImpl(
   if (forceNow) {
     await resetRecoupGroupRetry(ws, recoupGroupId);
   }
+  console.log("in processRecoupGroupImpl");
   const recoupGroup = await ws.db.get(Stores.recoupGroups, recoupGroupId);
   if (!recoupGroup) {
     return;
   }
+  console.log(recoupGroup);
   if (recoupGroup.timestampFinished) {
+    console.log("recoup group finished");
     return;
   }
   const ps = recoupGroup.coinPubs.map((x, i) =>
@@ -317,11 +319,11 @@ export async function createRecoupGroup(
     const coinPub = coinPubs[coinIdx];
     const coin = await tx.get(Stores.coins, coinPub);
     if (!coin) {
-      recoupGroup.recoupFinishedPerCoin[coinIdx] = true;
+      await putGroupAsFinished(tx, recoupGroup, coinIdx);
       continue;
     }
     if (Amounts.isZero(coin.currentAmount)) {
-      recoupGroup.recoupFinishedPerCoin[coinIdx] = true;
+      await putGroupAsFinished(tx, recoupGroup, coinIdx);
       continue;
     }
     coin.currentAmount = Amounts.getZero(coin.currentAmount.currency);
diff --git a/src/operations/refund.ts b/src/operations/refund.ts
index 9d1c5308..c856bb7d 100644
--- a/src/operations/refund.ts
+++ b/src/operations/refund.ts
@@ -440,7 +440,7 @@ async function processPurchaseApplyRefundImpl(
           body = await resp.json();
         } catch {}
         const m = "refund request (at exchange) failed";
-        throw new OperationFailedError(m, {
+        throw new OperationFailedError({
           message: m,
           type: "network",
           details: {
diff --git a/src/operations/reserves.ts b/src/operations/reserves.ts
index c909555f..efca08a4 100644
--- a/src/operations/reserves.ts
+++ b/src/operations/reserves.ts
@@ -202,6 +202,35 @@ export async function createReserve(
   return resp;
 }
 
+/**
+ * Re-query the status of a reserve.
+ */
+export async function forceQueryReserve(
+  ws: InternalWalletState,
+  reservePub: string,
+): Promise<void> {
+  await ws.db.runWithWriteTransaction([Stores.reserves], async (tx) => {
+    const reserve = await tx.get(Stores.reserves, reservePub);
+    if (!reserve) {
+      return;
+    }
+    // Only force status query where it makes sense
+    switch (reserve.reserveStatus) {
+      case ReserveRecordStatus.DORMANT:
+      case ReserveRecordStatus.WITHDRAWING:
+      case ReserveRecordStatus.QUERYING_STATUS:
+        break;
+      default:
+        return;
+    }
+    reserve.reserveStatus = ReserveRecordStatus.QUERYING_STATUS;
+    reserve.retryInfo = initRetryInfo();
+    await tx.put(Stores.reserves, reserve);
+
+  });
+  await processReserve(ws, reservePub);
+}
+
 /**
  * First fetch information requred to withdraw from the reserve,
  * then deplete the reserve, withdrawing coins until it is empty.
@@ -408,7 +437,7 @@ async function updateReserve(
     console.log("got reserves/${RESERVE_PUB} response", await resp.json());
     if (resp.status === 404) {
       const m = "reserve not known to the exchange yet"
-      throw new OperationFailedError(m, {
+      throw new OperationFailedError({
         type: "waiting",
         message: m,
         details: {},
@@ -420,12 +449,13 @@ async function updateReserve(
   } catch (e) {
     logger.trace("caught exception for reserve/status");
     const m = e.message;
-    await incrementReserveRetry(ws, reservePub, {
+    const opErr = {
       type: "network",
       details: {},
       message: m,
-    });
-    throw new OperationFailedAndReportedError(m);
+    };
+    await incrementReserveRetry(ws, reservePub, opErr);
+    throw new OperationFailedAndReportedError(opErr);
   }
   const respJson = await resp.json();
   const reserveInfo = codecForReserveStatus().decode(respJson);
@@ -600,13 +630,14 @@ async function depleteReserve(
   logger.trace(`got denom list`);
   if (denomsForWithdraw.length === 0) {
     const m = `Unable to withdraw from reserve, no denominations are available 
to withdraw.`;
-    await incrementReserveRetry(ws, reserve.reservePub, {
+    const opErr = {
       type: "internal",
       message: m,
       details: {},
-    });
+    };
+    await incrementReserveRetry(ws, reserve.reservePub, opErr);
     console.log(m);
-    throw new OperationFailedAndReportedError(m);
+    throw new OperationFailedAndReportedError(opErr);
   }
 
   logger.trace("selected denominations");
diff --git a/src/operations/withdraw.ts b/src/operations/withdraw.ts
index 478aa4ce..09d912bc 100644
--- a/src/operations/withdraw.ts
+++ b/src/operations/withdraw.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2019 GNUnet e.V.
+ (C) 2019-2029 Taler Systems SA
 
  GNU Taler is free software; you can redistribute it and/or modify it under the
  terms of the GNU General Public License as published by the Free Software
@@ -33,7 +33,10 @@ import {
   WithdrawDetails,
   OperationError,
 } from "../types/walletTypes";
-import { WithdrawOperationStatusResponse, 
codecForWithdrawOperationStatusResponse } from "../types/talerTypes";
+import {
+  codecForWithdrawOperationStatusResponse,
+  codecForWithdrawResponse,
+} from "../types/talerTypes";
 import { InternalWalletState } from "./state";
 import { parseWithdrawUri } from "../util/taleruri";
 import { Logger } from "../util/logging";
@@ -41,7 +44,7 @@ import { updateExchangeFromUrl, getExchangeTrust } from 
"./exchanges";
 import { WALLET_EXCHANGE_PROTOCOL_VERSION } from "./versions";
 
 import * as LibtoolVersion from "../util/libtoolVersion";
-import { guardOperationException } from "./errors";
+import { guardOperationException, scrutinizeTalerJsonResponse } from 
"./errors";
 import { NotificationType } from "../types/notifications";
 import {
   getTimestampNow,
@@ -49,7 +52,6 @@ import {
   timestampCmp,
   timestampSubtractDuraction,
 } from "../util/time";
-import { Store } from "../util/query";
 
 const logger = new Logger("withdraw.ts");
 
@@ -62,7 +64,7 @@ function isWithdrawableDenom(d: DenominationRecord) {
   );
   const remaining = getDurationRemaining(lastPossibleWithdraw, now);
   const stillOkay = remaining.d_ms !== 0;
-  return started && stillOkay;
+  return started && stillOkay && !d.isRevoked;
 }
 
 /**
@@ -144,8 +146,9 @@ async function getPossibleDenoms(
     .iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchangeBaseUrl)
     .filter(d => {
       return (
-        d.status === DenominationStatus.Unverified ||
-        d.status === DenominationStatus.VerifiedGood
+        (d.status === DenominationStatus.Unverified ||
+          d.status === DenominationStatus.VerifiedGood) &&
+        !d.isRevoked
       );
     });
 }
@@ -199,13 +202,12 @@ async function processPlanchet(
   wd.reserve_pub = planchet.reservePub;
   wd.reserve_sig = planchet.withdrawSig;
   wd.coin_ev = planchet.coinEv;
-  const reqUrl = new URL(`reserves/${planchet.reservePub}/withdraw`, 
exchange.baseUrl).href;
+  const reqUrl = new URL(
+    `reserves/${planchet.reservePub}/withdraw`,
+    exchange.baseUrl,
+  ).href;
   const resp = await ws.http.postJson(reqUrl, wd);
-  if (resp.status !== 200) {
-    throw Error(`unexpected status ${resp.status} for withdraw`);
-  }
-
-  const r = await resp.json();
+  const r = await scrutinizeTalerJsonResponse(resp, 
codecForWithdrawResponse());
 
   const denomSig = await ws.cryptoApi.rsaUnblind(
     r.ev_sig,
@@ -236,8 +238,8 @@ async function processPlanchet(
       type: CoinSourceType.Withdraw,
       coinIndex: coinIdx,
       reservePub: planchet.reservePub,
-      withdrawSessionId: withdrawalSessionId
-    }
+      withdrawSessionId: withdrawalSessionId,
+    },
   };
 
   let withdrawSessionFinished = false;
@@ -458,11 +460,11 @@ async function processWithdrawCoin(
 
   if (planchet) {
     const coin = await ws.db.get(Stores.coins, planchet.coinPub);
-  
+
     if (coin) {
       console.log("coin already exists");
       return;
-    } 
+    }
   }
 
   if (!withdrawalSession.planchets[coinIndex]) {
diff --git a/src/types/ReserveTransaction.ts b/src/types/ReserveTransaction.ts
index e889f36a..ba5ce3ff 100644
--- a/src/types/ReserveTransaction.ts
+++ b/src/types/ReserveTransaction.ts
@@ -40,7 +40,7 @@ import { Timestamp, codecForTimestamp } from "../util/time";
 export const enum ReserveTransactionType {
   Withdraw = "WITHDRAW",
   Deposit = "DEPOSIT",
-  Payback = "PAYBACK",
+  Recoup = "RECOUP",
   Closing = "CLOSING",
 }
 
@@ -139,24 +139,14 @@ export interface ReserveClosingTransaction {
   timestamp: Timestamp;
 }
 
-export interface ReservePaybackTransaction {
-  type: ReserveTransactionType.Payback;
+export interface ReserveRecoupTransaction {
+  type: ReserveTransactionType.Recoup;
 
   /**
    * Amount paid back.
    */
   amount: AmountString;
 
-  /**
-   * Receiver account details.
-   */
-  receiver_account_details: any;
-
-  /**
-   * Wire transfer identifier.
-   */
-  wire_transfer: any;
-
   /**
    * This is a signature over
    * a struct TALER_PaybackConfirmationPS with purpose
@@ -187,7 +177,7 @@ export type ReserveTransaction =
   | ReserveWithdrawTransaction
   | ReserveDepositTransaction
   | ReserveClosingTransaction
-  | ReservePaybackTransaction;
+  | ReserveRecoupTransaction;
 
 export const codecForReserveWithdrawTransaction = () =>
   typecheckedCodec<ReserveWithdrawTransaction>(
@@ -229,18 +219,16 @@ export const codecForReserveClosingTransaction = () =>
       .build("ReserveClosingTransaction"),
   );
 
-export const codecForReservePaybackTransaction = () =>
-  typecheckedCodec<ReservePaybackTransaction>(
-    makeCodecForObject<ReservePaybackTransaction>()
+export const codecForReserveRecoupTransaction = () =>
+  typecheckedCodec<ReserveRecoupTransaction>(
+    makeCodecForObject<ReserveRecoupTransaction>()
       .property("amount", codecForString)
       .property("coin_pub", codecForString)
       .property("exchange_pub", codecForString)
       .property("exchange_sig", codecForString)
-      .property("receiver_account_details", codecForString)
       .property("timestamp", codecForTimestamp)
-      .property("type", 
makeCodecForConstString(ReserveTransactionType.Payback))
-      .property("wire_transfer", codecForString)
-      .build("ReservePaybackTransaction"),
+      .property("type", makeCodecForConstString(ReserveTransactionType.Recoup))
+      .build("ReserveRecoupTransaction"),
   );
 
 export const codecForReserveTransaction = () =>
@@ -256,8 +244,8 @@ export const codecForReserveTransaction = () =>
         codecForReserveClosingTransaction(),
       )
       .alternative(
-        ReserveTransactionType.Payback,
-        codecForReservePaybackTransaction(),
+        ReserveTransactionType.Recoup,
+        codecForReserveRecoupTransaction(),
       )
       .alternative(
         ReserveTransactionType.Deposit,
diff --git a/src/types/dbTypes.ts b/src/types/dbTypes.ts
index 56c1f82e..36b45f5a 100644
--- a/src/types/dbTypes.ts
+++ b/src/types/dbTypes.ts
@@ -1457,6 +1457,11 @@ export namespace Stores {
       "denomPubIndex",
       "denomPub",
     );
+    denomPubHashIndex = new Index<string, CoinRecord>(
+      this,
+      "denomPubHashIndex",
+      "denomPubHash",
+    );
   }
 
   class ProposalsStore extends Store<ProposalRecord> {
diff --git a/src/types/notifications.ts b/src/types/notifications.ts
index 34e98fe2..39930dcc 100644
--- a/src/types/notifications.ts
+++ b/src/types/notifications.ts
@@ -26,8 +26,8 @@ export const enum NotificationType {
   ProposalAccepted = "proposal-accepted",
   ProposalDownloaded = "proposal-downloaded",
   RefundsSubmitted = "refunds-submitted",
-  RecoupStarted = "payback-started",
-  RecoupFinished = "payback-finished",
+  RecoupStarted = "recoup-started",
+  RecoupFinished = "recoup-finished",
   RefreshRevealed = "refresh-revealed",
   RefreshMelted = "refresh-melted",
   RefreshStarted = "refresh-started",
@@ -44,7 +44,7 @@ export const enum NotificationType {
   RefundFinished = "refund-finished",
   ExchangeOperationError = "exchange-operation-error",
   RefreshOperationError = "refresh-operation-error",
-  RecoupOperationError = "refresh-operation-error",
+  RecoupOperationError = "recoup-operation-error",
   RefundApplyOperationError = "refund-apply-error",
   RefundStatusOperationError = "refund-status-error",
   ProposalOperationError = "proposal-error",
@@ -82,11 +82,11 @@ export interface RefundsSubmittedNotification {
   proposalId: string;
 }
 
-export interface PaybackStartedNotification {
+export interface RecoupStartedNotification {
   type: NotificationType.RecoupStarted;
 }
 
-export interface PaybackFinishedNotification {
+export interface RecoupFinishedNotification {
   type: NotificationType.RecoupFinished;
 }
 
@@ -171,6 +171,10 @@ export interface WithdrawOperationErrorNotification {
   type: NotificationType.WithdrawOperationError;
 }
 
+export interface RecoupOperationErrorNotification {
+  type: NotificationType.RecoupOperationError;
+}
+
 export interface ReserveOperationErrorNotification {
   type: NotificationType.ReserveOperationError;
   operationError: OperationError;
@@ -197,8 +201,8 @@ export type WalletNotification =
   | ProposalAcceptedNotification
   | ProposalDownloadedNotification
   | RefundsSubmittedNotification
-  | PaybackStartedNotification
-  | PaybackFinishedNotification
+  | RecoupStartedNotification
+  | RecoupFinishedNotification
   | RefreshMeltedNotification
   | RefreshRevealedNotification
   | RefreshStartedNotification
@@ -214,4 +218,5 @@ export type WalletNotification =
   | RefundQueriedNotification
   | WithdrawSessionCreatedNotification
   | CoinWithdrawnNotification
-  | WildcardNotification;
+  | WildcardNotification
+  | RecoupOperationErrorNotification;
diff --git a/src/types/pending.ts b/src/types/pending.ts
index 5d732c52..d9d17a3b 100644
--- a/src/types/pending.ts
+++ b/src/types/pending.ts
@@ -204,6 +204,8 @@ export interface PendingRefundApplyOperation {
 export interface PendingRecoupOperation {
   type: PendingOperationType.Recoup;
   recoupGroupId: string;
+  retryInfo: RetryInfo;
+  lastError: OperationError | undefined;
 }
 
 /**
diff --git a/src/types/talerTypes.ts b/src/types/talerTypes.ts
index e65c8238..2ecb8234 100644
--- a/src/types/talerTypes.ts
+++ b/src/types/talerTypes.ts
@@ -148,10 +148,10 @@ export class Auditor {
  */
 export interface RecoupRequest {
   /**
-   * Denomination public key of the coin we want to get
+   * Hashed enomination public key of the coin we want to get
    * paid back.
    */
-  denom_pub: string;
+  denom_pub_hash: string;
 
   /**
    * Signature over the coin public key by the denomination.
@@ -744,6 +744,10 @@ export class TipPickupGetResponse {
   stamp_created: Timestamp;
 }
 
+export class WithdrawResponse {
+  ev_sig: string;
+}
+
 export type AmountString = string;
 export type Base32String = string;
 export type EddsaSignatureString = string;
@@ -976,3 +980,11 @@ export const codecForRecoupConfirmation = () =>
       .property("exchange_pub", codecForString)
       .build("RecoupConfirmation"),
   );
+
+
+export const codecForWithdrawResponse = () =>
+  typecheckedCodec<WithdrawResponse>(
+    makeCodecForObject<WithdrawResponse>()
+      .property("ev_sig", codecForString)
+      .build("WithdrawResponse"),
+  );
\ No newline at end of file
diff --git a/src/util/query.ts b/src/util/query.ts
index d08c901a..3303907f 100644
--- a/src/util/query.ts
+++ b/src/util/query.ts
@@ -218,7 +218,7 @@ class ResultStream<T> {
       return { hasValue: false };
     }
     if (!this.awaitingResult) {
-      const cursor = this.req.result;
+      const cursor: IDBCursor | undefined = this.req.result;
       if (!cursor) {
         throw Error("assertion failed");
       }
@@ -330,7 +330,7 @@ function runWithTransaction<T>(
       reject(TransactionAbort);
     };
     const th = new TransactionHandle(tx);
-    const resP = f(th);
+    const resP = Promise.resolve().then(() => f(th));
     resP
       .then(result => {
         gotFunResult = true;
@@ -340,10 +340,12 @@ function runWithTransaction<T>(
         if (e == TransactionAbort) {
           console.info("aborting transaction");
         } else {
-          tx.abort();
           console.error("Transaction failed:", e);
           console.error(stack);
+          tx.abort();
         }
+      }).catch((e) => {
+        console.error("fatal: aborting transaction failed", e);
       });
   });
 }
diff --git a/src/wallet.ts b/src/wallet.ts
index 3b619f87..9cba1360 100644
--- a/src/wallet.ts
+++ b/src/wallet.ts
@@ -113,6 +113,7 @@ import {
 } from "./operations/refund";
 import { durationMin, Duration } from "./util/time";
 import { processRecoupGroup } from "./operations/recoup";
+import { OperationFailedAndReportedError } from "./operations/errors";
 
 const builtinCurrencies: CurrencyRecord[] = [
   {
@@ -235,7 +236,11 @@ export class Wallet {
       try {
         await this.processOnePendingOperation(p, forceNow);
       } catch (e) {
-        console.error(e);
+        if (e instanceof OperationFailedAndReportedError) {
+          console.error("Operation failed:", JSON.stringify(e.operationError, 
undefined, 2));
+        } else {
+          console.error(e);
+        }
       }
     }
   }

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]