gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] branch master updated: wallet-core: insufficient bal


From: gnunet
Subject: [taler-wallet-core] branch master updated: wallet-core: insufficient balance details for p2p payments
Date: Fri, 06 Jan 2023 13:55:11 +0100

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 417c07f3f wallet-core: insufficient balance details for p2p payments
417c07f3f is described below

commit 417c07f3f4866918e1aaa6d42b7d5ec0ca59dd51
Author: Florian Dold <florian@dold.me>
AuthorDate: Fri Jan 6 13:55:08 2023 +0100

    wallet-core: insufficient balance details for p2p payments
---
 .../src/integrationtests/test-peer-to-peer-push.ts | 16 ++++-
 packages/taler-util/src/taler-error-codes.ts       |  8 +++
 packages/taler-util/src/wallet-types.ts            | 23 +++----
 packages/taler-wallet-core/src/errors.ts           |  4 ++
 .../taler-wallet-core/src/operations/balance.ts    | 73 +++++++++++++++++++++-
 .../taler-wallet-core/src/operations/pay-peer.ts   | 72 ++++++++++++++++++---
 6 files changed, 170 insertions(+), 26 deletions(-)

diff --git 
a/packages/taler-harness/src/integrationtests/test-peer-to-peer-push.ts 
b/packages/taler-harness/src/integrationtests/test-peer-to-peer-push.ts
index 2a93c3559..eb29a81c2 100644
--- a/packages/taler-harness/src/integrationtests/test-peer-to-peer-push.ts
+++ b/packages/taler-harness/src/integrationtests/test-peer-to-peer-push.ts
@@ -67,7 +67,6 @@ export async function runPeerToPeerPushTest(t: 
GlobalTestState) {
     );
 
     console.log(resp);
-
   }
   const resp = await wallet1.client.call(
     WalletApiOperation.InitiatePeerPushPayment,
@@ -114,6 +113,21 @@ export async function runPeerToPeerPushTest(t: 
GlobalTestState) {
 
   console.log(`txn1: ${j2s(txn1)}`);
   console.log(`txn2: ${j2s(txn2)}`);
+
+  const ex1 = await t.assertThrowsTalerErrorAsync(async () => {
+    await wallet1.client.call(
+      WalletApiOperation.InitiatePeerPushPayment,
+      {
+        partialContractTerms: {
+          summary: "(this will fail)",
+          amount: "TESTKUDOS:15",
+          purse_expiration
+        },
+      },
+    );
+  });
+
+  console.log("got expected exception detail", j2s(ex1.errorDetail));
 }
 
 runPeerToPeerPushTest.suites = ["wallet"];
diff --git a/packages/taler-util/src/taler-error-codes.ts 
b/packages/taler-util/src/taler-error-codes.ts
index 5e3c8fdfb..9e735817d 100644
--- a/packages/taler-util/src/taler-error-codes.ts
+++ b/packages/taler-util/src/taler-error-codes.ts
@@ -3248,6 +3248,14 @@ export enum TalerErrorCode {
   WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE = 7027,
 
 
+  /**
+   * The wallet does not have sufficient balance to pay for an invoice.
+   * Returned with an HTTP status code of #MHD_HTTP_UNINITIALIZED (0).
+   * (A value of 0 indicates that the error is generated client-side).
+   */
+  WALLET_PEER_PULL_PAYMENT_INSUFFICIENT_BALANCE = 7028,
+
+
   /**
    * We encountered a timeout with our payment backend.
    * Returned with an HTTP status code of #MHD_HTTP_GATEWAY_TIMEOUT (504).
diff --git a/packages/taler-util/src/wallet-types.ts 
b/packages/taler-util/src/wallet-types.ts
index 3a1176021..06edfe285 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -2164,20 +2164,11 @@ export interface PayPeerInsufficientBalanceDetails {
    */
   balanceMaterial: AmountString;
 
-  /**
-   * Acceptable balance based on restrictions on which
-   * exchange can be used.
-   */
-  balanceExchangeAcceptable: AmountString
-
-  /**
-   * If the payment would succeed without fees
-   * (i.e. balanceExchangeAcceptable >= amountRequested),
-   * this field contains an estimate of the amount that would additionally
-   * be required to cover the fees.
-   *
-   * It is not possible to give an exact value here, since it depends
-   * on the coin selection for the amount that would be additionally withdrawn.
-   */
-  feeGapEstimate: AmountString;
+  perExchange: {
+    [url: string]: {
+      balanceAvailable: AmountString;
+      balanceMaterial: AmountString;
+      feeGapEstimate: AmountString;
+    };
+  };
 }
diff --git a/packages/taler-wallet-core/src/errors.ts 
b/packages/taler-wallet-core/src/errors.ts
index 68cd39b54..37a31a8aa 100644
--- a/packages/taler-wallet-core/src/errors.ts
+++ b/packages/taler-wallet-core/src/errors.ts
@@ -25,6 +25,7 @@
  */
 import {
   PayMerchantInsufficientBalanceDetails,
+  PayPeerInsufficientBalanceDetails,
   TalerErrorCode,
   TalerErrorDetail,
   TransactionType,
@@ -87,6 +88,9 @@ export interface DetailsMap {
   [TalerErrorCode.WALLET_DEPOSIT_GROUP_INSUFFICIENT_BALANCE]: {
     insufficientBalanceDetails: PayMerchantInsufficientBalanceDetails;
   };
+  [TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE]: {
+    insufficientBalanceDetails: PayPeerInsufficientBalanceDetails;
+  };
 }
 
 type ErrBody<Y> = Y extends keyof DetailsMap ? DetailsMap[Y] : never;
diff --git a/packages/taler-wallet-core/src/operations/balance.ts 
b/packages/taler-wallet-core/src/operations/balance.ts
index f697679af..383aa5dc1 100644
--- a/packages/taler-wallet-core/src/operations/balance.ts
+++ b/packages/taler-wallet-core/src/operations/balance.ts
@@ -56,7 +56,12 @@ import {
   canonicalizeBaseUrl,
   parsePaytoUri,
 } from "@gnu-taler/taler-util";
-import { AllowedAuditorInfo, AllowedExchangeInfo, RefreshGroupRecord, 
WalletStoresV1 } from "../db.js";
+import {
+  AllowedAuditorInfo,
+  AllowedExchangeInfo,
+  RefreshGroupRecord,
+  WalletStoresV1,
+} from "../db.js";
 import { GetReadOnlyAccess } from "../util/query.js";
 import { InternalWalletState } from "../internal-wallet-state.js";
 import { getExchangeDetails } from "./exchanges.js";
@@ -362,7 +367,7 @@ export async function getMerchantPaymentBalanceDetails(
     balanceMerchantDepositable: Amounts.zeroOfCurrency(req.currency),
   };
 
-  const wbal = await ws.db
+  await ws.db
     .mktx((x) => [
       x.coins,
       x.coinAvailability,
@@ -415,3 +420,67 @@ export async function getMerchantPaymentBalanceDetails(
 
   return d;
 }
+
+export interface PeerPaymentRestrictionsForBalance {
+  currency: string;
+  restrictExchangeTo?: string;
+}
+
+export interface PeerPaymentBalanceDetails {
+  /**
+   * Balance of type "available" (see balance.ts for definition).
+   */
+  balanceAvailable: AmountJson;
+
+  /**
+   * Balance of type "material" (see balance.ts for definition).
+   */
+  balanceMaterial: AmountJson;
+}
+
+export async function getPeerPaymentBalanceDetailsInTx(
+  ws: InternalWalletState,
+  tx: GetReadOnlyAccess<{
+    coinAvailability: typeof WalletStoresV1.coinAvailability;
+    refreshGroups: typeof WalletStoresV1.refreshGroups;
+  }>,
+  req: PeerPaymentRestrictionsForBalance,
+): Promise<PeerPaymentBalanceDetails> {
+  let balanceAvailable = Amounts.zeroOfCurrency(req.currency);
+  let balanceMaterial = Amounts.zeroOfCurrency(req.currency);
+
+  await tx.coinAvailability.iter().forEach((ca) => {
+    if (ca.currency != req.currency) {
+      return;
+    }
+    if (
+      req.restrictExchangeTo &&
+      req.restrictExchangeTo !== ca.exchangeBaseUrl
+    ) {
+      return;
+    }
+    const singleCoinAmount: AmountJson = {
+      currency: ca.currency,
+      fraction: ca.amountFrac,
+      value: ca.amountVal,
+    };
+    const coinAmount: AmountJson = Amounts.mult(
+      singleCoinAmount,
+      ca.freshCoinCount,
+    ).amount;
+    balanceAvailable = Amounts.add(balanceAvailable, coinAmount).amount;
+    balanceMaterial = Amounts.add(balanceMaterial, coinAmount).amount;
+  });
+
+  await tx.refreshGroups.iter().forEach((r) => {
+    balanceAvailable = Amounts.add(
+      balanceAvailable,
+      computeRefreshGroupAvailableAmount(r),
+    ).amount;
+  });
+
+  return {
+    balanceAvailable,
+    balanceMaterial,
+  };
+}
diff --git a/packages/taler-wallet-core/src/operations/pay-peer.ts 
b/packages/taler-wallet-core/src/operations/pay-peer.ts
index 3d03c46db..3ee1795b0 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer.ts
@@ -62,6 +62,7 @@ import {
   PreparePeerPushPaymentResponse,
   RefreshReason,
   strcmp,
+  TalerErrorCode,
   TalerProtocolTimestamp,
   TransactionType,
   UnblindedSignature,
@@ -77,11 +78,13 @@ import {
   WithdrawalGroupStatus,
   WithdrawalRecordType,
 } from "../db.js";
+import { TalerError } from "../errors.js";
 import { InternalWalletState } from "../internal-wallet-state.js";
 import { makeTransactionId, spendCoins } from "../operations/common.js";
 import { readSuccessResponseJsonOrThrow } from "../util/http.js";
 import { checkDbInvariant } from "../util/invariants.js";
 import { GetReadOnlyAccess } from "../util/query.js";
+import { getPeerPaymentBalanceDetailsInTx } from "./balance.js";
 import { updateExchangeFromUrl } from "./exchanges.js";
 import { internalCreateWithdrawalGroup } from "./withdraw.js";
 
@@ -135,6 +138,7 @@ export type SelectPeerCoinsResult =
   | { type: "success"; result: PeerCoinSelection }
   | {
       type: "failure";
+      insufficientBalanceDetails: PayPeerInsufficientBalanceDetails;
     };
 
 export async function selectPeerCoins(
@@ -143,12 +147,16 @@ export async function selectPeerCoins(
     exchanges: typeof WalletStoresV1.exchanges;
     denominations: typeof WalletStoresV1.denominations;
     coins: typeof WalletStoresV1.coins;
+    coinAvailability: typeof WalletStoresV1.coinAvailability;
+    refreshGroups: typeof WalletStoresV1.refreshGroups;
   }>,
   instructedAmount: AmountJson,
 ): Promise<SelectPeerCoinsResult> {
   const exchanges = await tx.exchanges.iter().toArray();
+  const exchangeFeeGap: { [url: string]: AmountJson } = {};
+  const currency = Amounts.currencyOf(instructedAmount);
   for (const exch of exchanges) {
-    if (exch.detailsPointer?.currency !== instructedAmount.currency) {
+    if (exch.detailsPointer?.currency !== currency) {
       continue;
     }
     const coins = (
@@ -184,8 +192,8 @@ export async function selectPeerCoins(
         -Amounts.cmp(o1.value, o2.value) ||
         strcmp(o1.denomPubHash, o2.denomPubHash),
     );
-    let amountAcc = Amounts.zeroOfCurrency(instructedAmount.currency);
-    let depositFeesAcc = Amounts.zeroOfCurrency(instructedAmount.currency);
+    let amountAcc = Amounts.zeroOfCurrency(currency);
+    let depositFeesAcc = Amounts.zeroOfCurrency(currency);
     const resCoins: {
       coinPub: string;
       coinPriv: string;
@@ -194,6 +202,7 @@ export async function selectPeerCoins(
       denomSig: UnblindedSignature;
       ageCommitmentProof: AgeCommitmentProof | undefined;
     }[] = [];
+    let lastDepositFee = Amounts.zeroOfCurrency(currency);
     for (const coin of coinInfos) {
       if (Amounts.cmp(amountAcc, instructedAmount) >= 0) {
         break;
@@ -216,6 +225,7 @@ export async function selectPeerCoins(
         denomSig: coin.denomSig,
         ageCommitmentProof: coin.ageCommitmentProof,
       });
+      lastDepositFee = coin.feeDeposit;
     }
     if (Amounts.cmp(amountAcc, instructedAmount) >= 0) {
       const res: PeerCoinSelection = {
@@ -225,9 +235,48 @@ export async function selectPeerCoins(
       };
       return { type: "success", result: res };
     }
+    const diff = Amounts.sub(instructedAmount, amountAcc).amount;
+    exchangeFeeGap[exch.baseUrl] = Amounts.add(lastDepositFee, diff).amount;
+
     continue;
   }
-  return { type: "failure" };
+  // We were unable to select coins.
+  // Now we need to produce error details.
+
+  const infoGeneral = await getPeerPaymentBalanceDetailsInTx(ws, tx, {
+    currency,
+  });
+
+  const perExchange: PayPeerInsufficientBalanceDetails["perExchange"] = {};
+
+  for (const exch of exchanges) {
+    if (exch.detailsPointer?.currency !== currency) {
+      continue;
+    }
+    const infoExchange = await getPeerPaymentBalanceDetailsInTx(ws, tx, {
+      currency,
+      restrictExchangeTo: exch.baseUrl,
+    });
+    let gap = exchangeFeeGap[exch.baseUrl] ?? Amounts.zeroOfCurrency(currency);
+    if (Amounts.cmp(infoExchange.balanceMaterial, instructedAmount) < 0) {
+      // Show fee gap only if we should've been able to pay with the material 
amount
+      gap = Amounts.zeroOfAmount(currency);
+    }
+    perExchange[exch.baseUrl] = {
+      balanceAvailable: Amounts.stringify(infoExchange.balanceAvailable),
+      balanceMaterial: Amounts.stringify(infoExchange.balanceMaterial),
+      feeGapEstimate: Amounts.stringify(gap),
+    };
+  }
+
+  const errDetails: PayPeerInsufficientBalanceDetails = {
+    amountRequested: Amounts.stringify(instructedAmount),
+    balanceAvailable: Amounts.stringify(infoGeneral.balanceAvailable),
+    balanceMaterial: Amounts.stringify(infoGeneral.balanceMaterial),
+    perExchange,
+  };
+
+  return { type: "failure", insufficientBalanceDetails: errDetails };
 }
 
 export async function preparePeerPushPayment(
@@ -316,8 +365,12 @@ export async function initiatePeerToPeerPush(
   logger.info(`selected p2p coins (push): ${j2s(coinSelRes)}`);
 
   if (coinSelRes.type !== "success") {
-    // FIXME: use error code with details here
-    throw Error("insufficient balance");
+    throw TalerError.fromDetail(
+      TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
+      {
+        insufficientBalanceDetails: coinSelRes.insufficientBalanceDetails,
+      },
+    );
   }
 
   const purseSigResp = await ws.cryptoApi.signPurseCreation({
@@ -675,7 +728,12 @@ export async function acceptPeerPullPayment(
   logger.info(`selected p2p coins (pull): ${j2s(coinSelRes)}`);
 
   if (coinSelRes.type !== "success") {
-    throw Error("insufficient balance");
+    throw TalerError.fromDetail(
+      TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE,
+      {
+        insufficientBalanceDetails: coinSelRes.insufficientBalanceDetails,
+      },
+    );
   }
 
   const pursePub = peerPullInc.pursePub;

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