gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: get coin re-selection after a


From: gnunet
Subject: [taler-wallet-core] branch master updated: get coin re-selection after accidental double spending to work
Date: Wed, 07 Apr 2021 17:29:52 +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 4fa88007 get coin re-selection after accidental double spending to work
4fa88007 is described below

commit 4fa88007f958796d7fe65d0fe4f6f45fcf953887
Author: Florian Dold <florian@dold.me>
AuthorDate: Wed Apr 7 19:29:51 2021 +0200

    get coin re-selection after accidental double spending to work
---
 .../integrationtests/test-wallet-backup-basic.ts   |  30 +++-
 .../test-wallet-backup-doublespend.ts              |  20 ++-
 packages/taler-wallet-core/src/db.ts               |   1 +
 .../src/operations/backup/export.ts                |  54 ++++++--
 .../src/operations/backup/import.ts                |  39 +++++-
 .../src/operations/backup/index.ts                 |  18 ++-
 .../taler-wallet-core/src/operations/deposits.ts   |   4 +-
 .../taler-wallet-core/src/operations/exchanges.ts  |   2 +-
 packages/taler-wallet-core/src/operations/pay.ts   | 103 +++++++++++++-
 .../taler-wallet-core/src/operations/refresh.ts    |   2 +-
 .../taler-wallet-core/src/operations/reserves.ts   |  48 ++++++-
 packages/taler-wallet-core/src/operations/tip.ts   |   2 +-
 .../taler-wallet-core/src/operations/withdraw.ts   |  27 +++-
 .../taler-wallet-core/src/util/coinSelection.ts    |   4 +-
 .../taler-wallet-core/src/util/helpers.test.ts     |  46 -------
 packages/taler-wallet-core/src/util/helpers.ts     | 151 ---------------------
 16 files changed, 300 insertions(+), 251 deletions(-)

diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-wallet-backup-basic.ts 
b/packages/taler-wallet-cli/src/integrationtests/test-wallet-backup-basic.ts
index 2ed16fe1..dd448c87 100644
--- a/packages/taler-wallet-cli/src/integrationtests/test-wallet-backup-basic.ts
+++ b/packages/taler-wallet-cli/src/integrationtests/test-wallet-backup-basic.ts
@@ -17,12 +17,8 @@
 /**
  * Imports.
  */
-import { GlobalTestState, BankApi, BankAccessApi, WalletCli } from "./harness";
-import {
-  createSimpleTestkudosEnvironment,
-  makeTestPayment,
-  withdrawViaBank,
-} from "./helpers";
+import { GlobalTestState, WalletCli } from "./harness";
+import { createSimpleTestkudosEnvironment, withdrawViaBank } from "./helpers";
 import { SyncService } from "./sync";
 
 /**
@@ -101,7 +97,7 @@ export async function runWalletBackupBasicTest(t: 
GlobalTestState) {
     const bi = await wallet.getBackupInfo();
     console.log(bi);
   }
-  
+
   const backupRecovery = await wallet.exportBackupRecovery();
 
   const wallet2 = new WalletCli(t, "wallet2");
@@ -122,4 +118,24 @@ export async function runWalletBackupBasicTest(t: 
GlobalTestState) {
     t.assertTrue(bal.balances.length === 1);
     console.log(bal);
   }
+
+  // Now do some basic checks that the restored wallet is still functional
+  {
+    const bal1 = await wallet2.getBalances();
+
+    t.assertAmountEquals(bal1.balances[0].available, "TESTKUDOS:14.1");
+
+    await withdrawViaBank(t, {
+      wallet: wallet2,
+      bank,
+      exchange,
+      amount: "TESTKUDOS:10",
+    });
+
+    await wallet2.runUntilDone();
+
+    const bal2 = await wallet2.getBalances();
+
+    t.assertAmountEquals(bal2.balances[0].available, "TESTKUDOS:23.82");
+  }
 }
diff --git 
a/packages/taler-wallet-cli/src/integrationtests/test-wallet-backup-doublespend.ts
 
b/packages/taler-wallet-cli/src/integrationtests/test-wallet-backup-doublespend.ts
index ef53046c..b9bc30a9 100644
--- 
a/packages/taler-wallet-cli/src/integrationtests/test-wallet-backup-doublespend.ts
+++ 
b/packages/taler-wallet-cli/src/integrationtests/test-wallet-backup-doublespend.ts
@@ -18,11 +18,7 @@
  * Imports.
  */
 import { PreparePayResultType } from "@gnu-taler/taler-util";
-import {
-  GlobalTestState,
-  WalletCli,
-  MerchantPrivateApi,
-} from "./harness";
+import { GlobalTestState, WalletCli, MerchantPrivateApi } from "./harness";
 import {
   createSimpleTestkudosEnvironment,
   makeTestPayment,
@@ -133,5 +129,19 @@ export async function runWalletBackupDoublespendTest(t: 
GlobalTestState) {
     });
 
     console.log(res);
+
+    // FIXME: wait for a notification that indicates insufficient funds!
+
+    await withdrawViaBank(t, {
+      wallet: wallet2,
+      bank,
+      exchange,
+      amount: "TESTKUDOS:50",
+    });
+
+    const bal = await wallet2.getBalances();
+    console.log("bal", bal);
+
+    await wallet2.runUntilDone();
   }
 }
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index c1076b90..640ff24a 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -1274,6 +1274,7 @@ export enum AbortStatus {
   AbortFinished = "abort-finished",
 }
 
+
 /**
  * Record that stores status information about one purchase, starting from when
  * the customer accepts a proposal.  Includes refund status if applicable.
diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts 
b/packages/taler-wallet-core/src/operations/backup/export.ts
index c6e24289..07c7b9ec 100644
--- a/packages/taler-wallet-core/src/operations/backup/export.ts
+++ b/packages/taler-wallet-core/src/operations/backup/export.ts
@@ -14,15 +14,6 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { hash } from "../../crypto/primitives/nacl-fast";
-import { WalletBackupContentV1, BackupExchange, BackupCoin, 
BackupDenomination, BackupReserve, BackupPurchase, BackupProposal, 
BackupRefreshGroup, BackupBackupProvider, BackupTip, BackupRecoupGroup, 
BackupWithdrawalGroup, BackupBackupProviderTerms, BackupCoinSource, 
BackupCoinSourceType, BackupExchangeWireFee, BackupRefundItem, 
BackupRefundState, BackupProposalStatus, BackupRefreshOldCoin, 
BackupRefreshSession } from "@gnu-taler/taler-util";
-import { canonicalizeBaseUrl, canonicalJson } from "../../util/helpers";
-import { InternalWalletState } from "../state";
-import { provideBackupState, getWalletBackupState, WALLET_BACKUP_STATE_KEY } 
from "./state";
-import { Amounts, getTimestampNow } from "@gnu-taler/taler-util";
-import { Stores, CoinSourceType, CoinStatus, RefundState, AbortStatus, 
ProposalStatus } from "../../db.js";
-import { encodeCrock, stringToBytes, getRandomBytes } from "../../index.js";
-
 /**
  * Implementation of wallet backups (export/import/upload) and sync
  * server management.
@@ -30,6 +21,51 @@ import { encodeCrock, stringToBytes, getRandomBytes } from 
"../../index.js";
  * @author Florian Dold <dold@taler.net>
  */
 
+/**
+ * Imports.
+ */
+import { hash } from "../../crypto/primitives/nacl-fast";
+import {
+  WalletBackupContentV1,
+  BackupExchange,
+  BackupCoin,
+  BackupDenomination,
+  BackupReserve,
+  BackupPurchase,
+  BackupProposal,
+  BackupRefreshGroup,
+  BackupBackupProvider,
+  BackupTip,
+  BackupRecoupGroup,
+  BackupWithdrawalGroup,
+  BackupBackupProviderTerms,
+  BackupCoinSource,
+  BackupCoinSourceType,
+  BackupExchangeWireFee,
+  BackupRefundItem,
+  BackupRefundState,
+  BackupProposalStatus,
+  BackupRefreshOldCoin,
+  BackupRefreshSession,
+} from "@gnu-taler/taler-util";
+import { InternalWalletState } from "../state";
+import {
+  provideBackupState,
+  getWalletBackupState,
+  WALLET_BACKUP_STATE_KEY,
+} from "./state";
+import { Amounts, getTimestampNow } from "@gnu-taler/taler-util";
+import {
+  Stores,
+  CoinSourceType,
+  CoinStatus,
+  RefundState,
+  AbortStatus,
+  ProposalStatus,
+} from "../../db.js";
+import { encodeCrock, stringToBytes, getRandomBytes } from "../../index.js";
+import { canonicalizeBaseUrl, canonicalJson } from "@gnu-taler/taler-util";
+
 export async function exportBackup(
   ws: InternalWalletState,
 ): Promise<WalletBackupContentV1> {
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts 
b/packages/taler-wallet-core/src/operations/backup/import.ts
index 05b6da08..e0ae379a 100644
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ b/packages/taler-wallet-core/src/operations/backup/import.ts
@@ -14,11 +14,42 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { BackupPurchase, AmountJson, Amounts, BackupDenomSel, 
WalletBackupContentV1, getTimestampNow, BackupCoinSourceType, 
BackupProposalStatus, codecForContractTerms, BackupRefundState, RefreshReason, 
BackupRefreshReason } from "@gnu-taler/taler-util";
-import { Stores, WalletContractData, DenomSelectionState, ExchangeWireInfo, 
ExchangeUpdateStatus, DenominationStatus, CoinSource, CoinSourceType, 
CoinStatus, ReserveBankInfo, ReserveRecordStatus, ProposalDownload, 
ProposalStatus, WalletRefundItem, RefundState, AbortStatus, 
RefreshSessionRecord } from "../../db.js";
+import {
+  BackupPurchase,
+  AmountJson,
+  Amounts,
+  BackupDenomSel,
+  WalletBackupContentV1,
+  getTimestampNow,
+  BackupCoinSourceType,
+  BackupProposalStatus,
+  codecForContractTerms,
+  BackupRefundState,
+  RefreshReason,
+  BackupRefreshReason,
+} from "@gnu-taler/taler-util";
+import {
+  Stores,
+  WalletContractData,
+  DenomSelectionState,
+  ExchangeWireInfo,
+  ExchangeUpdateStatus,
+  DenominationStatus,
+  CoinSource,
+  CoinSourceType,
+  CoinStatus,
+  ReserveBankInfo,
+  ReserveRecordStatus,
+  ProposalDownload,
+  ProposalStatus,
+  WalletRefundItem,
+  RefundState,
+  AbortStatus,
+  RefreshSessionRecord,
+} from "../../db.js";
 import { TransactionHandle } from "../../index.js";
 import { PayCoinSelection } from "../../util/coinSelection";
-import { j2s } from "../../util/helpers";
+import { j2s } from "@gnu-taler/taler-util";
 import { checkDbInvariant, checkLogicInvariant } from "../../util/invariants";
 import { Logger } from "../../util/logging";
 import { initRetryInfo } from "../../util/retries";
@@ -271,6 +302,8 @@ export async function importBackup(
             denomPubHash,
           ]);
           if (!existingDenom) {
+            logger.info(`importing backup denomination: 
${j2s(backupDenomination)}`);
+
             await tx.put(Stores.denominations, {
               denomPub: backupDenomination.denom_pub,
               denomPubHash: denomPubHash,
diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts 
b/packages/taler-wallet-core/src/operations/backup/index.ts
index 77a3219a..49129d7d 100644
--- a/packages/taler-wallet-core/src/operations/backup/index.ts
+++ b/packages/taler-wallet-core/src/operations/backup/index.ts
@@ -25,13 +25,14 @@
  * Imports.
  */
 import { InternalWalletState } from "../state";
-import { AmountString, BackupRecovery, codecForAmountString, 
WalletBackupContentV1 } from "@gnu-taler/taler-util";
-import { TransactionHandle } from "../../util/query";
 import {
-  BackupProviderRecord,
-  ConfigRecord,
-  Stores,
-} from "../../db.js";
+  AmountString,
+  BackupRecovery,
+  codecForAmountString,
+  WalletBackupContentV1,
+} from "@gnu-taler/taler-util";
+import { TransactionHandle } from "../../util/query";
+import { BackupProviderRecord, ConfigRecord, Stores } from "../../db.js";
 import { checkDbInvariant, checkLogicInvariant } from "../../util/invariants";
 import {
   bytesToString,
@@ -43,7 +44,7 @@ import {
   rsaBlind,
   stringToBytes,
 } from "../../crypto/talerCrypto";
-import { canonicalizeBaseUrl, canonicalJson, j2s } from "../../util/helpers";
+import { canonicalizeBaseUrl, canonicalJson, j2s } from 
"@gnu-taler/taler-util";
 import {
   durationAdd,
   durationFromSpec,
@@ -408,6 +409,9 @@ export async function runBackupCycle(ws: 
InternalWalletState): Promise<void> {
   const providers = await ws.db.iter(Stores.backupProviders).toArray();
   logger.trace("got backup providers", providers);
   const backupJson = await exportBackup(ws);
+
+  logger.trace(`running backup cycle with backup JSON: ${j2s(backupJson)}`);
+
   const backupConfig = await provideBackupState(ws);
   const encBackup = await encryptBackup(backupConfig, backupJson);
 
diff --git a/packages/taler-wallet-core/src/operations/deposits.ts 
b/packages/taler-wallet-core/src/operations/deposits.ts
index 6bb4f3d5..4c87f122 100644
--- a/packages/taler-wallet-core/src/operations/deposits.ts
+++ b/packages/taler-wallet-core/src/operations/deposits.ts
@@ -21,7 +21,7 @@ import {
   stringToBytes,
 } from "../crypto/talerCrypto";
 import { selectPayCoins } from "../util/coinSelection";
-import { canonicalJson } from "../util/helpers";
+import { canonicalJson } from "@gnu-taler/taler-util";
 import { readSuccessResponseJsonOrThrow } from "../util/http";
 import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries";
 import {
@@ -433,4 +433,4 @@ export async function createDepositGroup(
   await ws.db.put(Stores.depositGroups, depositGroup);
 
   return { depositGroupId };
-}
\ 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 08c55416..f48b08ff 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -48,7 +48,7 @@ import {
   getExpiryTimestamp,
   readSuccessResponseTextOrThrow,
 } from "../index.js";
-import { j2s, canonicalizeBaseUrl } from "../util/helpers.js";
+import { j2s, canonicalizeBaseUrl } from "@gnu-taler/taler-util";
 import { checkDbInvariant } from "../util/invariants.js";
 import { updateRetryInfoTimeout, initRetryInfo } from "../util/retries.js";
 import {
diff --git a/packages/taler-wallet-core/src/operations/pay.ts 
b/packages/taler-wallet-core/src/operations/pay.ts
index da398056..1e93f413 100644
--- a/packages/taler-wallet-core/src/operations/pay.ts
+++ b/packages/taler-wallet-core/src/operations/pay.ts
@@ -83,8 +83,9 @@ import {
   CoinCandidateSelection,
   AvailableCoinInfo,
   selectPayCoins,
+  PreviousPayCoins,
 } from "../util/coinSelection.js";
-import { canonicalJson } from "../util/helpers.js";
+import { canonicalJson, j2s } from "@gnu-taler/taler-util";
 import {
   initRetryInfo,
   updateRetryInfoTimeout,
@@ -350,6 +351,13 @@ export async function applyCoinSpend(
     if (!coin) {
       throw Error("coin allocated for payment doesn't exist anymore");
     }
+    if (coin.status !== CoinStatus.Fresh) {
+      // applyCoinSpend was called again, probably
+      // because of a coin re-selection to recover after
+      // accidental double spending.
+      // Ignore coins we already marked as spent.
+      continue;
+    }
     coin.status = CoinStatus.Dormant;
     const remaining = Amounts.sub(
       coin.currentAmount,
@@ -867,7 +875,7 @@ async function storePayReplaySuccess(
  *
  * We do this by going through the coin history provided by the exchange and
  * (1) verifying the signatures from the exchange
- * (2) adjusting the remaining coin value
+ * (2) adjusting the remaining coin value and refreshing it
  * (3) re-do coin selection with the bad coin removed
  */
 async function handleInsufficientFunds(
@@ -875,12 +883,99 @@ async function handleInsufficientFunds(
   proposalId: string,
   err: TalerErrorDetails,
 ): Promise<void> {
+  logger.trace("handling insufficient funds, trying to re-select coins");
+
   const proposal = await ws.db.get(Stores.purchases, proposalId);
   if (!proposal) {
     return;
   }
 
-  throw Error("payment re-denomination not implemented yet");
+  const brokenCoinPub = (err as any).coin_pub;
+
+  const exchangeReply = (err as any).exchange_reply;
+  if (
+    exchangeReply.code !== TalerErrorCode.EXCHANGE_DEPOSIT_INSUFFICIENT_FUNDS
+  ) {
+    // FIXME: set as failed
+    throw Error("can't handle error code");
+  }
+
+  logger.trace(`got error details: ${j2s(err)}`);
+
+  const { contractData } = proposal.download;
+
+  const candidates = await getCandidatePayCoins(ws, {
+    allowedAuditors: contractData.allowedAuditors,
+    allowedExchanges: contractData.allowedExchanges,
+    amount: contractData.amount,
+    maxDepositFee: contractData.maxDepositFee,
+    maxWireFee: contractData.maxWireFee,
+    timestamp: contractData.timestamp,
+    wireFeeAmortization: contractData.wireFeeAmortization,
+    wireMethod: contractData.wireMethod,
+  });
+
+  const prevPayCoins: PreviousPayCoins = [];
+
+  for (let i = 0; i < proposal.payCoinSelection.coinPubs.length; i++) {
+    const coinPub = proposal.payCoinSelection.coinPubs[i];
+    if (coinPub === brokenCoinPub) {
+      continue;
+    }
+    const contrib = proposal.payCoinSelection.coinContributions[i];
+    const coin = await ws.db.get(Stores.coins, coinPub);
+    if (!coin) {
+      continue;
+    }
+    const denom = await ws.db.get(Stores.denominations, [
+      coin.exchangeBaseUrl,
+      coin.denomPubHash,
+    ]);
+    if (!denom) {
+      continue;
+    }
+    prevPayCoins.push({
+      coinPub,
+      contribution: contrib,
+      exchangeBaseUrl: coin.exchangeBaseUrl,
+      feeDeposit: denom.feeDeposit,
+    });
+  }
+
+  const res = selectPayCoins({
+    candidates,
+    contractTermsAmount: contractData.amount,
+    depositFeeLimit: contractData.maxDepositFee,
+    wireFeeAmortization: contractData.wireFeeAmortization ?? 1,
+    wireFeeLimit: contractData.maxWireFee,
+    prevPayCoins,
+  });
+
+  if (!res) {
+    logger.trace("insufficient funds for coin re-selection");
+    return;
+  }
+
+  logger.trace("re-selected coins");
+
+  await ws.db.runWithWriteTransaction(
+    [
+      Stores.purchases,
+      Stores.coins,
+      Stores.denominations,
+      Stores.refreshGroups,
+    ],
+    async (tx) => {
+      const p = await tx.get(Stores.purchases, proposalId);
+      if (!p) {
+        return;
+      }
+      p.payCoinSelection = res;
+      p.coinDepositPermissions = undefined;
+      await tx.put(Stores.purchases, p);
+      await applyCoinSpend(ws, tx, res);
+    },
+  );
 }
 
 /**
@@ -973,7 +1068,7 @@ async function submitPay(
             message: "unexpected exception",
             hint: "unexpected exception",
             details: {
-              exception: e,
+              exception: e.toString(),
             },
           });
         });
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts 
b/packages/taler-wallet-core/src/operations/refresh.ts
index d82ff946..84460fb8 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -34,7 +34,7 @@ import {
   TalerErrorDetails,
 } from "@gnu-taler/taler-util";
 import { AmountJson, Amounts } from "@gnu-taler/taler-util";
-import { amountToPretty } from "../util/helpers";
+import { amountToPretty } from "@gnu-taler/taler-util";
 import { readSuccessResponseJsonOrThrow } from "../util/http";
 import { checkDbInvariant } from "../util/invariants";
 import { Logger } from "../util/logging";
diff --git a/packages/taler-wallet-core/src/operations/reserves.ts 
b/packages/taler-wallet-core/src/operations/reserves.ts
index fe6f323c..9467287a 100644
--- a/packages/taler-wallet-core/src/operations/reserves.ts
+++ b/packages/taler-wallet-core/src/operations/reserves.ts
@@ -33,15 +33,46 @@ import {
   addPaytoQueryParams,
 } from "@gnu-taler/taler-util";
 import { randomBytes } from "../crypto/primitives/nacl-fast.js";
-import { Stores, ReserveRecordStatus, ReserveBankInfo, ReserveRecord, 
CurrencyRecord, WithdrawalGroupRecord } from "../db.js";
-import { Logger, encodeCrock, getRandomBytes, readSuccessResponseJsonOrThrow, 
URL, readSuccessResponseJsonOrErrorCode, throwUnexpectedRequestError, 
TransactionHandle } from "../index.js";
+import {
+  Stores,
+  ReserveRecordStatus,
+  ReserveBankInfo,
+  ReserveRecord,
+  CurrencyRecord,
+  WithdrawalGroupRecord,
+} from "../db.js";
+import {
+  Logger,
+  encodeCrock,
+  getRandomBytes,
+  readSuccessResponseJsonOrThrow,
+  URL,
+  readSuccessResponseJsonOrErrorCode,
+  throwUnexpectedRequestError,
+  TransactionHandle,
+} from "../index.js";
 import { assertUnreachable } from "../util/assertUnreachable.js";
-import { canonicalizeBaseUrl } from "../util/helpers.js";
-import { initRetryInfo, getRetryDuration, updateRetryInfoTimeout } from 
"../util/retries.js";
+import { canonicalizeBaseUrl } from "@gnu-taler/taler-util";
+import {
+  initRetryInfo,
+  getRetryDuration,
+  updateRetryInfoTimeout,
+} from "../util/retries.js";
 import { guardOperationException, OperationFailedError } from "./errors.js";
-import { updateExchangeFromUrl, getExchangeTrust, getExchangePaytoUri } from 
"./exchanges.js";
+import {
+  updateExchangeFromUrl,
+  getExchangeTrust,
+  getExchangePaytoUri,
+} from "./exchanges.js";
 import { InternalWalletState } from "./state.js";
-import { updateWithdrawalDenoms, getCandidateWithdrawalDenoms, 
selectWithdrawalDenominations, denomSelectionInfoToState, processWithdrawGroup, 
getBankWithdrawalInfo } from "./withdraw.js";
+import {
+  updateWithdrawalDenoms,
+  getCandidateWithdrawalDenoms,
+  selectWithdrawalDenominations,
+  denomSelectionInfoToState,
+  processWithdrawGroup,
+  getBankWithdrawalInfo,
+} from "./withdraw.js";
 
 const logger = new Logger("reserves.ts");
 
@@ -488,7 +519,10 @@ async function updateReserve(
   const currency = balance.currency;
 
   await updateWithdrawalDenoms(ws, reserve.exchangeBaseUrl);
-  const denoms = await getCandidateWithdrawalDenoms(ws, 
reserve.exchangeBaseUrl);
+  const denoms = await getCandidateWithdrawalDenoms(
+    ws,
+    reserve.exchangeBaseUrl,
+  );
 
   const newWithdrawalGroup = await ws.db.runWithWriteTransaction(
     [Stores.coins, Stores.planchets, Stores.withdrawalGroups, Stores.reserves],
diff --git a/packages/taler-wallet-core/src/operations/tip.ts 
b/packages/taler-wallet-core/src/operations/tip.ts
index 5ea92912..cc527464 100644
--- a/packages/taler-wallet-core/src/operations/tip.ts
+++ b/packages/taler-wallet-core/src/operations/tip.ts
@@ -45,7 +45,7 @@ import {
   getRandomBytes,
   getHttpResponseErrorDetails,
 } from "../index.js";
-import { j2s } from "../util/helpers.js";
+import { j2s } from "@gnu-taler/taler-util";
 import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
 import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js";
 import { guardOperationException, makeErrorDetails } from "./errors.js";
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts 
b/packages/taler-wallet-core/src/operations/withdraw.ts
index 0c1acf8e..fcaa0e6d 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -1,6 +1,6 @@
 /*
  This file is part of GNU Taler
- (C) 2019-2020 Taler Systems SA
+ (C) 2019-2021 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
@@ -14,7 +14,15 @@
  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
  */
 
-import { AmountJson, Amounts, parseWithdrawUri, Timestamp } from 
"@gnu-taler/taler-util";
+/**
+ * Imports.
+ */
+import {
+  AmountJson,
+  Amounts,
+  parseWithdrawUri,
+  Timestamp,
+} from "@gnu-taler/taler-util";
 import {
   DenominationRecord,
   Stores,
@@ -67,15 +75,17 @@ import { TalerErrorCode } from "@gnu-taler/taler-util";
 import { updateRetryInfoTimeout, initRetryInfo } from "../util/retries";
 import { compare } from "@gnu-taler/taler-util";
 
+/**
+ * Logger for this file.
+ */
 const logger = new Logger("withdraw.ts");
 
-
 /**
  * Information about what will happen when creating a reserve.
  *
  * Sent to the wallet frontend to be rendered and shown to the user.
  */
- interface ExchangeWithdrawDetails {
+interface ExchangeWithdrawDetails {
   /**
    * Exchange that the reserve will be created at.
    */
@@ -631,6 +641,8 @@ export async function updateWithdrawalDenoms(
     logger.error("exchange details not available");
     throw Error(`exchange ${exchangeBaseUrl} details not available`);
   }
+  // First do a pass where the validity of candidate denominations
+  // is checked and the result is stored in the database.
   const denominations = await getCandidateWithdrawalDenoms(ws, 
exchangeBaseUrl);
   for (const denom of denominations) {
     if (denom.status === DenominationStatus.Unverified) {
@@ -639,6 +651,9 @@ export async function updateWithdrawalDenoms(
         exchangeDetails.masterPublicKey,
       );
       if (!valid) {
+        logger.warn(
+          `Signature check for denomination h=${denom.denomPubHash} failed`,
+        );
         denom.status = DenominationStatus.VerifiedBad;
       } else {
         denom.status = DenominationStatus.VerifiedGood;
@@ -648,11 +663,13 @@ export async function updateWithdrawalDenoms(
   }
   // FIXME:  This debug info should either be made conditional on some flag
   // or put into some wallet-core API.
-  logger.trace("updated withdrawable denominations");
   const nextDenominations = await getCandidateWithdrawalDenoms(
     ws,
     exchangeBaseUrl,
   );
+  logger.trace(
+    `updated withdrawable denominations for "${exchangeBaseUrl}, 
n=${nextDenominations.length}"`,
+  );
   const now = getTimestampNow();
   for (const denom of nextDenominations) {
     const startDelay = getDurationRemaining(denom.stampStart, now);
diff --git a/packages/taler-wallet-core/src/util/coinSelection.ts 
b/packages/taler-wallet-core/src/util/coinSelection.ts
index e1fec5c9..c5a75878 100644
--- a/packages/taler-wallet-core/src/util/coinSelection.ts
+++ b/packages/taler-wallet-core/src/util/coinSelection.ts
@@ -24,7 +24,7 @@
  * Imports.
  */
 import { AmountJson, AmountLike, Amounts } from "@gnu-taler/taler-util";
-import { strcmp } from "./helpers.js";
+import { strcmp } from "@gnu-taler/taler-util";
 import { Logger } from "./logging.js";
 
 const logger = new Logger("coinSelection.ts");
@@ -89,7 +89,7 @@ export interface AvailableCoinInfo {
   exchangeBaseUrl: string;
 }
 
-type PreviousPayCoins = {
+export type PreviousPayCoins = {
   coinPub: string;
   contribution: AmountJson;
   feeDeposit: AmountJson;
diff --git a/packages/taler-wallet-core/src/util/helpers.test.ts 
b/packages/taler-wallet-core/src/util/helpers.test.ts
deleted file mode 100644
index dbecf14b..00000000
--- a/packages/taler-wallet-core/src/util/helpers.test.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- This file is part of TALER
- (C) 2017 Inria and GNUnet e.V.
-
- 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
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-import test from "ava";
-import * as helpers from "./helpers";
-
-test("URL canonicalization", (t) => {
-  // converts to relative, adds https
-  t.is(
-    "https://alice.example.com/exchange/";,
-    helpers.canonicalizeBaseUrl("alice.example.com/exchange"),
-  );
-
-  // keeps http, adds trailing slash
-  t.is(
-    "http://alice.example.com/exchange/";,
-    helpers.canonicalizeBaseUrl("http://alice.example.com/exchange";),
-  );
-
-  // keeps http, adds trailing slash
-  t.is(
-    "http://alice.example.com/exchange/";,
-    helpers.canonicalizeBaseUrl("http://alice.example.com/exchange#foobar";),
-  );
-
-  // Remove search component
-  t.is(
-    "http://alice.example.com/exchange/";,
-    helpers.canonicalizeBaseUrl("http://alice.example.com/exchange?foo=bar";),
-  );
-
-  t.pass();
-});
diff --git a/packages/taler-wallet-core/src/util/helpers.ts 
b/packages/taler-wallet-core/src/util/helpers.ts
deleted file mode 100644
index 87fa2e93..00000000
--- a/packages/taler-wallet-core/src/util/helpers.ts
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- This file is part of TALER
- (C) 2016 GNUnet e.V.
-
- 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
- Foundation; either version 3, or (at your option) any later version.
-
- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Small helper functions that don't fit anywhere else.
- */
-
-/**
- * Imports.
- */
-import { amountFractionalBase, AmountJson, Amounts } from 
"@gnu-taler/taler-util";
-import { URL } from "./url";
-
-/**
- * Show an amount in a form suitable for the user.
- * FIXME:  In the future, this should consider currency-specific
- * settings such as significant digits or currency symbols.
- */
-export function amountToPretty(amount: AmountJson): string {
-  const x = amount.value + amount.fraction / amountFractionalBase;
-  return `${x} ${amount.currency}`;
-}
-
-/**
- * Canonicalize a base url, typically for the exchange.
- *
- * See http://api.taler.net/wallet.html#general
- */
-export function canonicalizeBaseUrl(url: string): string {
-  if (!url.startsWith("http") && !url.startsWith("https")) {
-    url = "https://"; + url;
-  }
-  const x = new URL(url);
-  if (!x.pathname.endsWith("/")) {
-    x.pathname = x.pathname + "/";
-  }
-  x.search = "";
-  x.hash = "";
-  return x.href;
-}
-
-/**
- * Convert object to JSON with canonical ordering of keys
- * and whitespace omitted.
- */
-export function canonicalJson(obj: any): string {
-  // Check for cycles, etc.
-  obj = JSON.parse(JSON.stringify(obj));
-  if (typeof obj === "string" || typeof obj === "number" || obj === null) {
-    return JSON.stringify(obj);
-  }
-  if (Array.isArray(obj)) {
-    const objs: string[] = obj.map((e) => canonicalJson(e));
-    return `[${objs.join(",")}]`;
-  }
-  const keys: string[] = [];
-  for (const key in obj) {
-    keys.push(key);
-  }
-  keys.sort();
-  let s = "{";
-  for (let i = 0; i < keys.length; i++) {
-    const key = keys[i];
-    s += JSON.stringify(key) + ":" + canonicalJson(obj[key]);
-    if (i !== keys.length - 1) {
-      s += ",";
-    }
-  }
-  return s + "}";
-}
-
-/**
- * Check for deep equality of two objects.
- * Only arrays, objects and primitives are supported.
- */
-export function deepEquals(x: any, y: any): boolean {
-  if (x === y) {
-    return true;
-  }
-
-  if (Array.isArray(x) && x.length !== y.length) {
-    return false;
-  }
-
-  const p = Object.keys(x);
-  return (
-    Object.keys(y).every((i) => p.indexOf(i) !== -1) &&
-    p.every((i) => deepEquals(x[i], y[i]))
-  );
-}
-
-export function deepCopy(x: any): any {
-  // FIXME: this has many issues ...
-  return JSON.parse(JSON.stringify(x));
-}
-
-/**
- * Map from a collection to a list or results and then
- * concatenate the results.
- */
-export function flatMap<T, U>(xs: T[], f: (x: T) => U[]): U[] {
-  return xs.reduce((acc: U[], next: T) => [...f(next), ...acc], []);
-}
-
-/**
- * Compute the hash function of a JSON object.
- */
-export function hash(val: any): number {
-  const str = canonicalJson(val);
-  // https://github.com/darkskyapp/string-hash
-  let h = 5381;
-  let i = str.length;
-  while (i) {
-    h = (h * 33) ^ str.charCodeAt(--i);
-  }
-
-  /* JavaScript does bitwise operations (like XOR, above) on 32-bit signed
-   * integers. Since we want the results to be always positive, convert the
-   * signed int to an unsigned by doing an unsigned bitshift. */
-  return h >>> 0;
-}
-
-/**
- * Lexically compare two strings.
- */
-export function strcmp(s1: string, s2: string): number {
-  if (s1 < s2) {
-    return -1;
-  }
-  if (s1 > s2) {
-    return 1;
-  }
-  return 0;
-}
-
-export function j2s(x: any): string {
-  return JSON.stringify(x, undefined, 2);
-}

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