gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated (5c0ee81e -> 5056da65)


From: gnunet
Subject: [taler-wallet-core] branch master updated (5c0ee81e -> 5056da65)
Date: Tue, 01 Sep 2020 14:31:01 +0200

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

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

    from 5c0ee81e logging / do not crash on error
     new 5e7149f7 fix exception thrown during logging, include stack trace
     new 5056da65 test error handling

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:
 packages/taler-integrationtests/src/harness.ts     | 90 +++++++++++++++++++---
 packages/taler-integrationtests/src/helpers.ts     | 22 +++++-
 .../src/test-exchange-management.ts                |  4 +-
 .../src/test-payment-claim.ts                      |  2 +-
 .../taler-integrationtests/src/test-timetravel.ts  | 42 ++++++++--
 .../src/test-withdrawal-abort-bank.ts              |  2 +-
 packages/taler-wallet-cli/src/index.ts             |  6 +-
 packages/taler-wallet-core/src/TalerErrorCode.ts   | 62 ++++++++++++++-
 .../taler-wallet-core/src/operations/errors.ts     |  8 +-
 .../taler-wallet-core/src/operations/pending.ts    |  1 +
 .../src/operations/transactions.ts                 |  1 +
 .../taler-wallet-core/src/operations/withdraw.ts   | 32 ++++----
 packages/taler-wallet-core/src/types/pending.ts    |  7 ++
 .../taler-wallet-core/src/types/transactions.ts    | 21 +----
 .../taler-wallet-core/src/types/walletTypes.ts     |  3 +-
 packages/taler-wallet-core/src/util/logging.ts     | 25 ++++--
 packages/taler-wallet-core/src/wallet.ts           | 64 +++++++++------
 17 files changed, 299 insertions(+), 93 deletions(-)

diff --git a/packages/taler-integrationtests/src/harness.ts 
b/packages/taler-integrationtests/src/harness.ts
index fd96c316..93999c87 100644
--- a/packages/taler-integrationtests/src/harness.ts
+++ b/packages/taler-integrationtests/src/harness.ts
@@ -133,6 +133,62 @@ export async function sh(
   });
 }
 
+function shellescape(args: string[]) {
+  const ret = args.map((s) => {
+    if (/[^A-Za-z0-9_\/:=-]/.test(s)) {
+      s = "'" + s.replace(/'/g, "'\\''") + "'";
+      s = s.replace(/^(?:'')+/g, "").replace(/\\'''/g, "\\'");
+    }
+    return s;
+  });
+  return ret.join(" ");
+}
+
+/**
+ * Run a shell command, return stdout.
+ *
+ * Log stderr to a log file.
+ */
+export async function runCommand(
+  t: GlobalTestState,
+  logName: string,
+  command: string,
+  args: string[],
+): Promise<string> {
+  console.log("runing command", shellescape([command, ...args]));
+  return new Promise((resolve, reject) => {
+    const stdoutChunks: Buffer[] = [];
+    const proc = spawn(command, args, {
+      stdio: ["inherit", "pipe", "pipe"],
+      shell: false,
+    });
+    proc.stdout.on("data", (x) => {
+      if (x instanceof Buffer) {
+        stdoutChunks.push(x);
+      } else {
+        throw Error("unexpected data chunk type");
+      }
+    });
+    const stderrLogFileName = path.join(t.testDir, `${logName}-stderr.log`);
+    const stderrLog = fs.createWriteStream(stderrLogFileName, {
+      flags: "a",
+    });
+    proc.stderr.pipe(stderrLog);
+    proc.on("exit", (code, signal) => {
+      console.log(`child process exited (${code} / ${signal})`);
+      if (code != 0) {
+        reject(Error(`Unexpected exit code ${code} for '${command}'`));
+        return;
+      }
+      const b = Buffer.concat(stdoutChunks).toString("utf-8");
+      resolve(b);
+    });
+    proc.on("error", () => {
+      reject(Error("Child process had error"));
+    });
+  });
+}
+
 export class ProcessWrapper {
   private waitPromise: Promise<WaitResult>;
   constructor(public proc: ChildProcess) {
@@ -298,7 +354,7 @@ export class GlobalTestState {
     }
   }
 
-  assertDeepEqual(actual: any, expected: any): asserts actual is any {
+  assertDeepEqual<T>(actual: any, expected: T): asserts actual is T {
     deepStrictEqual(actual, expected);
   }
 
@@ -349,7 +405,9 @@ export class GlobalTestState {
     args: string[],
     logName: string,
   ): ProcessWrapper {
-    console.log(`spawning process ${command} with arguments ${args})`);
+    console.log(
+      `spawning process (${logName}): ${shellescape([command, ...args])}`,
+    );
     const proc = spawn(command, args, {
       stdio: ["inherit", "pipe", "pipe"],
     });
@@ -1028,8 +1086,8 @@ export class ExchangeService implements 
ExchangeServiceInterface {
     await sh(
       this.globalState,
       "exchange-wire",
-      `taler-exchange-wire ${this.timetravelArg} -c "${this.configFilename}"`
-    )
+      `taler-exchange-wire ${this.timetravelArg} -c "${this.configFilename}"`,
+    );
 
     this.exchangeWirewatchProc = this.globalState.spawnService(
       "taler-exchange-wirewatch",
@@ -1403,6 +1461,14 @@ export class WalletCli {
     fs.unlinkSync(this.dbfile);
   }
 
+  private get timetravelArgArr(): string[] {
+    const tta = this.timetravelArg;
+    if (tta) {
+      return [tta];
+    }
+    return [];
+  }
+
   async apiRequest(
     request: string,
     payload: unknown,
@@ -1420,13 +1486,19 @@ export class WalletCli {
     return JSON.parse(resp) as CoreApiResponse;
   }
 
-  async runUntilDone(): Promise<void> {
-    await sh(
+  async runUntilDone(args: { maxRetries?: number } = {}): Promise<void> {
+    await runCommand(
       this.globalTestState,
       `wallet-${this.name}`,
-      `taler-wallet-cli ${this.timetravelArg ?? ""} --no-throttle --wallet-db 
${
-        this.dbfile
-      } run-until-done`,
+      "taler-wallet-cli",
+      [
+        "--no-throttle",
+        ...this.timetravelArgArr,
+        "--wallet-db",
+        this.dbfile,
+        "run-until-done",
+        ...(args.maxRetries ? ["--max-retries", `${args.maxRetries}`] : []),
+      ],
     );
   }
 
diff --git a/packages/taler-integrationtests/src/helpers.ts 
b/packages/taler-integrationtests/src/helpers.ts
index 61b01519..515ae54b 100644
--- a/packages/taler-integrationtests/src/helpers.ts
+++ b/packages/taler-integrationtests/src/helpers.ts
@@ -221,7 +221,7 @@ export async function 
createFaultInjectedMerchantTestkudosEnvironment(
 /**
  * Withdraw balance.
  */
-export async function withdrawViaBank(
+export async function startWithdrawViaBank(
   t: GlobalTestState,
   p: {
     wallet: WalletCli;
@@ -255,6 +255,26 @@ export async function withdrawViaBank(
     talerWithdrawUri: wop.taler_withdraw_uri,
   });
   t.assertTrue(r2.type === "response");
+}
+
+
+/**
+ * Withdraw balance.
+ */
+export async function withdrawViaBank(
+  t: GlobalTestState,
+  p: {
+    wallet: WalletCli;
+    bank: BankService;
+    exchange: ExchangeService;
+    amount: AmountString;
+  },
+): Promise<void> {
+
+  const { wallet } = p;
+
+  await startWithdrawViaBank(t, p);
+
   await wallet.runUntilDone();
 
   // Check balance
diff --git a/packages/taler-integrationtests/src/test-exchange-management.ts 
b/packages/taler-integrationtests/src/test-exchange-management.ts
index 4ca86b34..7da26097 100644
--- a/packages/taler-integrationtests/src/test-exchange-management.ts
+++ b/packages/taler-integrationtests/src/test-exchange-management.ts
@@ -177,7 +177,7 @@ runTest(async (t: GlobalTestState) => {
   // Response is malformed, since it didn't even contain a version code
   // in a format the wallet can understand.
   t.assertTrue(
-    err1.operationError.talerErrorCode ===
+    err1.operationError.code ===
       TalerErrorCode.WALLET_RECEIVED_MALFORMED_RESPONSE,
   );
 
@@ -214,7 +214,7 @@ runTest(async (t: GlobalTestState) => {
   });
 
   t.assertTrue(
-    err2.operationError.talerErrorCode ===
+    err2.operationError.code ===
       TalerErrorCode.WALLET_EXCHANGE_PROTOCOL_VERSION_INCOMPATIBLE,
   );
 
diff --git a/packages/taler-integrationtests/src/test-payment-claim.ts 
b/packages/taler-integrationtests/src/test-payment-claim.ts
index c93057ef..3755394f 100644
--- a/packages/taler-integrationtests/src/test-payment-claim.ts
+++ b/packages/taler-integrationtests/src/test-payment-claim.ts
@@ -102,7 +102,7 @@ runTest(async (t: GlobalTestState) => {
   });
 
   t.assertTrue(
-    err.operationError.talerErrorCode ===
+    err.operationError.code ===
       TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED,
   );
 
diff --git a/packages/taler-integrationtests/src/test-timetravel.ts 
b/packages/taler-integrationtests/src/test-timetravel.ts
index acc770d5..086606b9 100644
--- a/packages/taler-integrationtests/src/test-timetravel.ts
+++ b/packages/taler-integrationtests/src/test-timetravel.ts
@@ -17,9 +17,18 @@
 /**
  * Imports.
  */
-import { runTest, GlobalTestState, MerchantPrivateApi, WalletCli } from 
"./harness";
-import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers";
-import { PreparePayResultType, durationMin, Duration } from 
"taler-wallet-core";
+import {
+  runTest,
+  GlobalTestState,
+  MerchantPrivateApi,
+  WalletCli,
+} from "./harness";
+import {
+  createSimpleTestkudosEnvironment,
+  withdrawViaBank,
+  startWithdrawViaBank,
+} from "./helpers";
+import { PreparePayResultType, durationMin, Duration, TransactionType } from 
"taler-wallet-core";
 
 /**
  * Basic time travel test.
@@ -36,7 +45,7 @@ runTest(async (t: GlobalTestState) => {
 
   // Withdraw digital cash into the wallet.
 
-  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" });
+  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:15" });
 
   // Travel 400 days into the future,
   // as the deposit expiration is two years
@@ -56,9 +65,28 @@ runTest(async (t: GlobalTestState) => {
   await merchant.pingUntilAvailable();
 
   // This should fail, as the wallet didn't time travel yet.
-  await withdrawViaBank(t, { wallet, bank, exchange, amount: "TESTKUDOS:20" });
+  await startWithdrawViaBank(t, {
+    wallet,
+    bank,
+    exchange,
+    amount: "TESTKUDOS:20",
+  });
+
+  // Check that transactions are correct for the failed withdrawal
+  {
+    await wallet.runUntilDone({ maxRetries: 5 });
+    const transactions = await wallet.getTransactions();
+    console.log(transactions);
+    const types = transactions.transactions.map((x) => x.type);
+    t.assertDeepEqual(types, ["withdrawal", "withdrawal"]);
+    const wtrans = transactions.transactions[0];
+    t.assertTrue(wtrans.type === TransactionType.Withdrawal);
+    t.assertTrue(wtrans.pending);
+  }
+
+  // Now we also let the wallet time travel
 
-  const bal = await wallet.getBalances();
+  wallet.setTimetravel(timetravelDuration);
 
-  console.log(bal);
+  await wallet.runUntilDone({ maxRetries: 5 });
 });
diff --git a/packages/taler-integrationtests/src/test-withdrawal-abort-bank.ts 
b/packages/taler-integrationtests/src/test-withdrawal-abort-bank.ts
index 3c1e6292..dd848b93 100644
--- a/packages/taler-integrationtests/src/test-withdrawal-abort-bank.ts
+++ b/packages/taler-integrationtests/src/test-withdrawal-abort-bank.ts
@@ -59,7 +59,7 @@ runTest(async (t: GlobalTestState) => {
   });
   t.assertTrue(r2.type === "error");
   t.assertTrue(
-    r2.error.talerErrorCode ===
+    r2.error.code ===
       TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK,
   );
 
diff --git a/packages/taler-wallet-cli/src/index.ts 
b/packages/taler-wallet-cli/src/index.ts
index 94f01ba8..a19b8a8f 100644
--- a/packages/taler-wallet-cli/src/index.ts
+++ b/packages/taler-wallet-cli/src/index.ts
@@ -262,9 +262,13 @@ walletCli
   .subcommand("finishPendingOpt", "run-until-done", {
     help: "Run until no more work is left.",
   })
+  .maybeOption("maxRetries", ["--max-retries"], clk.INT)
   .action(async (args) => {
     await withWallet(args, async (wallet) => {
-      await wallet.runUntilDoneAndStop();
+      await wallet.runUntilDone({
+        maxRetries: args.finishPendingOpt.maxRetries,
+      });
+      wallet.stop();
     });
   });
 
diff --git a/packages/taler-wallet-core/src/TalerErrorCode.ts 
b/packages/taler-wallet-core/src/TalerErrorCode.ts
index 7285a0fb..a4c4b5a6 100644
--- a/packages/taler-wallet-core/src/TalerErrorCode.ts
+++ b/packages/taler-wallet-core/src/TalerErrorCode.ts
@@ -969,6 +969,13 @@ export enum TalerErrorCode {
    */
   REFUND_INVALID_FAILURE_PROOF_BY_EXCHANGE = 1516,
 
+  /**
+   * The exchange failed to lookup information for the refund from its 
database.
+   * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR 
(500).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  REFUND_DATABASE_LOOKUP_ERROR = 1517,
+
   /**
    * The wire format specified in the "sender_account_details" is not 
understood or not supported by this exchange.
    * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
@@ -1571,6 +1578,20 @@ export enum TalerErrorCode {
    */
   FORGET_PATH_NOT_FORGETTABLE = 2182,
 
+  /**
+   * The merchant backend cannot forget part of an order because it failed to 
start the database transaction.
+   * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR 
(500).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  FORGET_ORDER_DB_START_ERROR = 2183,
+
+  /**
+   * The merchant backend cannot forget part of an order because it failed to 
commit the database transaction.
+   * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR 
(500).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  FORGET_ORDER_DB_COMMIT_ERROR = 2184,
+
   /**
    * Integer overflow with specified timestamp argument detected.
    * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
@@ -1991,6 +2012,13 @@ export enum TalerErrorCode {
    */
   ORDERS_ALREADY_CLAIMED = 2521,
 
+  /**
+   * The merchant backend couldn't find a product with the specified id.
+   * Returned with an HTTP status code of #MHD_HTTP_NOT_FOUND (404).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  GET_PRODUCTS_NOT_FOUND = 2549,
+
   /**
    * The merchant backend failed to lookup the products.
    * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR 
(500).
@@ -2983,7 +3011,7 @@ export enum TalerErrorCode {
    * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR 
(500).
    * (A value of 0 indicates that the error is generated client-side).
    */
-  SYNC_DB_FETCH_ERROR = 6000,
+  SYNC_DB_HARD_FETCH_ERROR = 6000,
 
   /**
    * The sync service failed find the record in its database.
@@ -3028,11 +3056,11 @@ export enum TalerErrorCode {
   SYNC_INVALID_SIGNATURE = 6007,
 
   /**
-   * The "Content-length" field for the upload is either not a number, or too 
big, or missing.
+   * The "Content-length" field for the upload is either not a number, or too 
big.
    * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
    * (A value of 0 indicates that the error is generated client-side).
    */
-  SYNC_BAD_CONTENT_LENGTH = 6008,
+  SYNC_MALFORMED_CONTENT_LENGTH = 6008,
 
   /**
    * The "Content-length" field for the upload is too big based on the 
server's terms of service.
@@ -3111,6 +3139,27 @@ export enum TalerErrorCode {
    */
   SYNC_PREVIOUS_BACKUP_UNKNOWN = 6019,
 
+  /**
+   * The sync service had a serialization failure when accessing its database.
+   * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR 
(500).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  SYNC_DB_SOFT_FETCH_ERROR = 6020,
+
+  /**
+   * The sync service first found information, and then later not. This could 
happen if a backup was garbage collected just when it was being accessed. 
Trying again may give a different answer.
+   * Returned with an HTTP status code of #MHD_HTTP_INTERNAL_SERVER_ERROR 
(500).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  SYNC_DB_INCONSISTENT_FETCH_ERROR = 6021,
+
+  /**
+   * The "Content-length" field for the upload is missing.
+   * Returned with an HTTP status code of #MHD_HTTP_BAD_REQUEST (400).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  SYNC_MISSING_CONTENT_LENGTH = 6022,
+
   /**
    * The wallet does not implement a version of the exchange protocol that is 
compatible with the protocol version of the exchange.
    * Returned with an HTTP status code of #MHD_HTTP_NOT_IMPLEMENTED (501).
@@ -3216,6 +3265,13 @@ export enum TalerErrorCode {
    */
   WALLET_ORDER_ALREADY_CLAIMED = 7014,
 
+  /**
+   * A group of withdrawal operations (typically for the same reserve at the 
same exchange) has errors and will be tried again later.
+   * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  WALLET_WITHDRAWAL_GROUP_INCOMPLETE = 7015,
+
   /**
    * End of error code range.
    * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
diff --git a/packages/taler-wallet-core/src/operations/errors.ts 
b/packages/taler-wallet-core/src/operations/errors.ts
index 198d3f8c..6d9f44e0 100644
--- a/packages/taler-wallet-core/src/operations/errors.ts
+++ b/packages/taler-wallet-core/src/operations/errors.ts
@@ -66,8 +66,8 @@ export function makeErrorDetails(
   details: Record<string, unknown>,
 ): OperationErrorDetails {
   return {
-    talerErrorCode: ec,
-    talerErrorHint: `Error: ${TalerErrorCode[ec]}`,
+    code: ec,
+    hint: `Error: ${TalerErrorCode[ec]}`,
     details: details,
     message,
   };
@@ -96,7 +96,9 @@ export async function guardOperationException<T>(
       const opErr = makeErrorDetails(
         TalerErrorCode.WALLET_UNEXPECTED_EXCEPTION,
         `unexpected exception (message: ${e.message})`,
-        {},
+        {
+          stack: e.stack,
+        },
       );
       await onOpError(opErr);
       throw new OperationFailedAndReportedError(opErr);
diff --git a/packages/taler-wallet-core/src/operations/pending.ts 
b/packages/taler-wallet-core/src/operations/pending.ts
index acad5e63..88196162 100644
--- a/packages/taler-wallet-core/src/operations/pending.ts
+++ b/packages/taler-wallet-core/src/operations/pending.ts
@@ -262,6 +262,7 @@ async function gatherWithdrawalPending(
       source: wsr.source,
       withdrawalGroupId: wsr.withdrawalGroupId,
       lastError: wsr.lastError,
+      retryInfo: wsr.retryInfo,
     });
   });
 }
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts 
b/packages/taler-wallet-core/src/operations/transactions.ts
index d869ed77..3115b950 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -165,6 +165,7 @@ export async function getTransactions(
                   TransactionType.Withdrawal,
                   wsr.withdrawalGroupId,
                 ),
+                ...(wsr.lastError ? { error: wsr.lastError} : {}),
               });
             }
             break;
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts 
b/packages/taler-wallet-core/src/operations/withdraw.ts
index 270735fc..3977ba12 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -59,6 +59,7 @@ import {
 import { readSuccessResponseJsonOrThrow } from "../util/http";
 import { URL } from "../util/url";
 import { TalerErrorCode } from "../TalerErrorCode";
+import { encodeCrock } from "../crypto/talerCrypto";
 
 const logger = new Logger("withdraw.ts");
 
@@ -558,9 +559,6 @@ async function incrementWithdrawalRetry(
     if (!wsr) {
       return;
     }
-    if (!wsr.retryInfo) {
-      return;
-    }
     wsr.retryInfo.retryCounter++;
     updateRetryInfoTimeout(wsr.retryInfo);
     wsr.lastError = err;
@@ -647,12 +645,13 @@ async function processWithdrawGroupImpl(
 
   let numFinished = 0;
   let finishedForFirstTime = false;
+  let errorsPerCoin: Record<number, OperationErrorDetails> = {};
 
   await ws.db.runWithWriteTransaction(
     [Stores.coins, Stores.withdrawalGroups, Stores.reserves, Stores.planchets],
     async (tx) => {
-      const ws = await tx.get(Stores.withdrawalGroups, withdrawalGroupId);
-      if (!ws) {
+      const wg = await tx.get(Stores.withdrawalGroups, withdrawalGroupId);
+      if (!wg) {
         return;
       }
 
@@ -662,22 +661,29 @@ async function processWithdrawGroupImpl(
           if (x.withdrawalDone) {
             numFinished++;
           }
+          if (x.lastError) {
+            errorsPerCoin[x.coinIdx] = x.lastError;
+          }
         });
-
-      if (ws.timestampFinish === undefined && numFinished == numTotalCoins) {
+      logger.trace(`now withdrawn ${numFinished} of ${numTotalCoins} coins`);
+      if (wg.timestampFinish === undefined && numFinished === numTotalCoins) {
         finishedForFirstTime = true;
-        ws.timestampFinish = getTimestampNow();
-        ws.lastError = undefined;
-        ws.retryInfo = initRetryInfo(false);
+        wg.timestampFinish = getTimestampNow();
+        wg.lastError = undefined;
+        wg.retryInfo = initRetryInfo(false);
       }
-      await tx.put(Stores.withdrawalGroups, ws);
+
+      await tx.put(Stores.withdrawalGroups, wg);
     },
   );
 
   if (numFinished != numTotalCoins) {
-    // FIXME: aggregate individual problems into the big error message here.
-    throw Error(
+    throw OperationFailedError.fromCode(
+      TalerErrorCode.WALLET_WITHDRAWAL_GROUP_INCOMPLETE,
       `withdrawal did not finish (${numFinished} / ${numTotalCoins} coins 
withdrawn)`,
+      {
+        errorsPerCoin,
+      },
     );
   }
 
diff --git a/packages/taler-wallet-core/src/types/pending.ts 
b/packages/taler-wallet-core/src/types/pending.ts
index 85f7585c..67d243a3 100644
--- a/packages/taler-wallet-core/src/types/pending.ts
+++ b/packages/taler-wallet-core/src/types/pending.ts
@@ -210,6 +210,7 @@ export interface PendingWithdrawOperation {
   type: PendingOperationType.Withdraw;
   source: WithdrawalSource;
   lastError: OperationErrorDetails | undefined;
+  retryInfo: RetryInfo;
   withdrawalGroupId: string;
   numCoinsWithdrawn: number;
   numCoinsTotal: number;
@@ -229,6 +230,12 @@ export interface PendingOperationInfoCommon {
    * as opposed to some regular scheduled operation or a permanent failure.
    */
   givesLifeness: boolean;
+
+  /**
+   * Retry info, not available on all pending operations.
+   * If it is available, it must have the same name.
+   */
+  retryInfo?: RetryInfo;
 }
 
 /**
diff --git a/packages/taler-wallet-core/src/types/transactions.ts 
b/packages/taler-wallet-core/src/types/transactions.ts
index 061ce28f..40043954 100644
--- a/packages/taler-wallet-core/src/types/transactions.ts
+++ b/packages/taler-wallet-core/src/types/transactions.ts
@@ -42,6 +42,7 @@ import {
   codecForList,
   codecForAny,
 } from "../util/codec";
+import { OperationErrorDetails } from "./walletTypes";
 
 export interface TransactionsRequest {
   /**
@@ -63,24 +64,6 @@ export interface TransactionsResponse {
   transactions: Transaction[];
 }
 
-export interface TransactionError {
-  /**
-   * TALER_EC_* unique error code.
-   * The action(s) offered and message displayed on the transaction item 
depend on this code.
-   */
-  ec: number;
-
-  /**
-   * English-only error hint, if available.
-   */
-  hint?: string;
-
-  /**
-   * Error details specific to "ec", if applicable/available
-   */
-  details?: any;
-}
-
 export interface TransactionCommon {
   // opaque unique ID for the transaction, used as a starting point for 
paginating queries
   // and for invoking actions on the transaction (e.g. deleting/hiding it from 
the history)
@@ -103,7 +86,7 @@ export interface TransactionCommon {
   // Amount added or removed from the wallet's balance (including all fees and 
other costs)
   amountEffective: AmountString;
 
-  error?: TransactionError;
+  error?: OperationErrorDetails;
 }
 
 export type Transaction =
diff --git a/packages/taler-wallet-core/src/types/walletTypes.ts 
b/packages/taler-wallet-core/src/types/walletTypes.ts
index 2cf3c7fb..eb7d878f 100644
--- a/packages/taler-wallet-core/src/types/walletTypes.ts
+++ b/packages/taler-wallet-core/src/types/walletTypes.ts
@@ -51,7 +51,6 @@ import {
   buildCodecForUnion,
 } from "../util/codec";
 import { AmountString, codecForContractTerms, ContractTerms } from 
"./talerTypes";
-import { TransactionError, OrderShortInfo, codecForOrderShortInfo } from 
"./transactions";
 
 /**
  * Response for the create reserve request to the wallet.
@@ -215,7 +214,7 @@ export interface ConfirmPayResultDone {
 export interface ConfirmPayResultPending {
   type: ConfirmPayResultType.Pending;
 
-  lastError: TransactionError;
+  lastError: OperationErrorDetails;
 }
 
 export type ConfirmPayResult = ConfirmPayResultDone | ConfirmPayResultPending;
diff --git a/packages/taler-wallet-core/src/util/logging.ts 
b/packages/taler-wallet-core/src/util/logging.ts
index e4f3be2f..230cb705 100644
--- a/packages/taler-wallet-core/src/util/logging.ts
+++ b/packages/taler-wallet-core/src/util/logging.ts
@@ -22,18 +22,29 @@ const isNode =
   typeof process !== "undefined" && process.release.name === "node";
 
 function writeNodeLog(
-  message: string,
+  message: any,
   tag: string,
   level: string,
   args: any[],
 ): void {
-  process.stderr.write(`${new Date().toISOString()} ${tag} ${level} `);
-  process.stderr.write(message);
-  if (args.length != 0) {
-    process.stderr.write(" ");
-    process.stderr.write(JSON.stringify(args, undefined, 2));
+  try {
+    process.stderr.write(`${new Date().toISOString()} ${tag} ${level} `);
+    process.stderr.write(`${message}`);
+    if (args.length != 0) {
+      process.stderr.write(" ");
+      process.stderr.write(JSON.stringify(args, undefined, 2));
+    }
+    process.stderr.write("\n");
+  } catch (e) {
+    // This can happen when we're trying to log something that doesn't want to 
be
+    // converted to a string.
+    process.stderr.write(`${new Date().toISOString()} (logger) FATAL `);
+    if (e instanceof Error) {
+      process.stderr.write("failed to write log: ");
+      process.stderr.write(e.message);
+    }
+    process.stderr.write("\n");
   }
-  process.stderr.write("\n");
 }
 
 /**
diff --git a/packages/taler-wallet-core/src/wallet.ts 
b/packages/taler-wallet-core/src/wallet.ts
index 41b096bc..845c6d71 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -299,10 +299,15 @@ export class Wallet {
    * liveness left.  The wallet will be in a stopped state when this function
    * returns without resolving to an exception.
    */
-  public async runUntilDone(): Promise<void> {
+  public async runUntilDone(
+    req: {
+      maxRetries?: number;
+    } = {},
+  ): Promise<void> {
     let done = false;
     const p = new Promise((resolve, reject) => {
-      // Run this asynchronously
+      // Monitor for conditions that means we're done or we
+      // should quit with an error (due to exceeded retries).
       this.addNotificationListener((n) => {
         if (done) {
           return;
@@ -315,7 +320,29 @@ export class Wallet {
           logger.trace("no liveness-giving operations left");
           resolve();
         }
+        const maxRetries = req.maxRetries;
+        if (!maxRetries) {
+          return;
+        }
+        this.getPendingOperations({ onlyDue: false })
+          .then((pending) => {
+            for (const p of pending.pendingOperations) {
+              if (p.retryInfo && p.retryInfo.retryCounter > maxRetries) {
+                console.warn(
+                  `stopping, as ${maxRetries} retries are exceeded in an 
operation of type ${p.type}`,
+                );
+                this.stop();
+                done = true;
+                resolve();
+              }
+            }
+          })
+          .catch((e) => {
+            logger.error(e);
+            reject(e);
+          });
       });
+      // Run this asynchronously
       this.runRetryLoop().catch((e) => {
         logger.error("exception in wallet retry loop");
         reject(e);
@@ -324,16 +351,6 @@ export class Wallet {
     await p;
   }
 
-  /**
-   * Run the wallet until there are no more pending operations that give
-   * liveness left.  The wallet will be in a stopped state when this function
-   * returns without resolving to an exception.
-   */
-  public async runUntilDoneAndStop(): Promise<void> {
-    await this.runUntilDone();
-    logger.trace("stopping after liveness-giving operations done");
-    this.stop();
-  }
 
   /**
    * Process pending operations and wait for scheduled operations in
@@ -392,7 +409,7 @@ export class Wallet {
             if (e instanceof OperationFailedAndReportedError) {
               logger.warn("operation processed resulted in reported error");
             } else {
-              console.error("Uncaught exception", e);
+              logger.error("Uncaught exception", e);
               this.ws.notify({
                 type: NotificationType.InternalError,
                 message: "uncaught exception",
@@ -902,10 +919,13 @@ export class Wallet {
     return getTransactions(this.ws, request);
   }
 
-  async withdrawTestBalance(
-    req: WithdrawTestBalanceRequest,
-  ): Promise<void> {
-    await withdrawTestBalance(this.ws, req.amount, req.bankBaseUrl, 
req.exchangeBaseUrl);
+  async withdrawTestBalance(req: WithdrawTestBalanceRequest): Promise<void> {
+    await withdrawTestBalance(
+      this.ws,
+      req.amount,
+      req.bankBaseUrl,
+      req.exchangeBaseUrl,
+    );
   }
 
   async runIntegrationtest(args: IntegrationTestArgs): Promise<void> {
@@ -919,7 +939,6 @@ export class Wallet {
   /**
    * Implementation of the "wallet-core" API.
    */
-
   private async dispatchRequestInternal(
     operation: string,
     payload: unknown,
@@ -941,12 +960,12 @@ export class Wallet {
       case "runIntegrationTest": {
         const req = codecForIntegrationTestArgs().decode(payload);
         await this.runIntegrationtest(req);
-        return {}
+        return {};
       }
       case "testPay": {
         const req = codecForTestPayArgs().decode(payload);
         await this.testPay(req);
-        return {}
+        return {};
       }
       case "getTransactions": {
         const req = codecForTransactionsRequest().decode(payload);
@@ -989,10 +1008,7 @@ export class Wallet {
       }
       case "setExchangeTosAccepted": {
         const req = codecForAcceptExchangeTosRequest().decode(payload);
-        await this.acceptExchangeTermsOfService(
-          req.exchangeBaseUrl,
-          req.etag,
-        );
+        await this.acceptExchangeTermsOfService(req.exchangeBaseUrl, req.etag);
         return {};
       }
       case "applyRefund": {

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