gnunet-svn
[Top][All Lists]
Advanced

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

[taler-wallet-core] 01/02: simplify /pay, add pay event


From: gnunet
Subject: [taler-wallet-core] 01/02: simplify /pay, add pay event
Date: Mon, 16 Dec 2019 12:53:31 +0100

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

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

commit 1b9c5855a8afb6833ff7a706f5bed5650e1191ad
Author: Florian Dold <address@hidden>
AuthorDate: Sun Dec 15 21:40:06 2019 +0100

    simplify /pay, add pay event
---
 src/crypto/workers/cryptoApi.ts            |   6 +-
 src/crypto/workers/cryptoImplementation.ts |  22 ++--
 src/operations/pay.ts                      | 160 ++++++++---------------------
 src/operations/return.ts                   |  15 ++-
 src/operations/state.ts                    |   2 -
 src/types/dbTypes.ts                       |  17 +++
 src/types/walletTypes.ts                   |  24 ++++-
 7 files changed, 105 insertions(+), 141 deletions(-)

diff --git a/src/crypto/workers/cryptoApi.ts b/src/crypto/workers/cryptoApi.ts
index 3c675867..da807cce 100644
--- a/src/crypto/workers/cryptoApi.ts
+++ b/src/crypto/workers/cryptoApi.ts
@@ -39,7 +39,7 @@ import { ContractTerms, PaybackRequest } from 
"../../types/talerTypes";
 import {
   BenchmarkResult,
   CoinWithDenom,
-  PayCoinInfo,
+  PaySigInfo,
   PlanchetCreationResult,
   PlanchetCreationRequest,
 } from "../../types/walletTypes";
@@ -387,8 +387,8 @@ export class CryptoApi {
     contractTerms: ContractTerms,
     cds: CoinWithDenom[],
     totalAmount: AmountJson,
-  ): Promise<PayCoinInfo> {
-    return this.doRpc<PayCoinInfo>(
+  ): Promise<PaySigInfo> {
+    return this.doRpc<PaySigInfo>(
       "signDeposit",
       3,
       contractTerms,
diff --git a/src/crypto/workers/cryptoImplementation.ts 
b/src/crypto/workers/cryptoImplementation.ts
index 01cd797b..0049a122 100644
--- a/src/crypto/workers/cryptoImplementation.ts
+++ b/src/crypto/workers/cryptoImplementation.ts
@@ -39,11 +39,12 @@ import { CoinPaySig, ContractTerms, PaybackRequest } from 
"../../types/talerType
 import {
   BenchmarkResult,
   CoinWithDenom,
-  PayCoinInfo,
+  PaySigInfo,
   Timestamp,
   PlanchetCreationResult,
   PlanchetCreationRequest,
   getTimestampNow,
+  CoinPayInfo,
 } from "../../types/walletTypes";
 import { canonicalJson, getTalerStampSec } from "../../util/helpers";
 import { AmountJson } from "../../util/amounts";
@@ -348,11 +349,9 @@ export class CryptoImplementation {
     contractTerms: ContractTerms,
     cds: CoinWithDenom[],
     totalAmount: AmountJson,
-  ): PayCoinInfo {
-    const ret: PayCoinInfo = {
-      originalCoins: [],
-      sigs: [],
-      updatedCoins: [],
+  ): PaySigInfo {
+    const ret: PaySigInfo = {
+      coinInfo: [],
     };
 
     const contractTermsHash = this.hashString(canonicalJson(contractTerms));
@@ -369,8 +368,6 @@ export class CryptoImplementation {
     let amountRemaining = total;
 
     for (const cd of cds) {
-      const originalCoin = { ...cd.coin };
-
       if (amountRemaining.value === 0 && amountRemaining.fraction === 0) {
         break;
       }
@@ -416,9 +413,12 @@ export class CryptoImplementation {
         exchange_url: cd.denom.exchangeBaseUrl,
         ub_sig: cd.coin.denomSig,
       };
-      ret.sigs.push(s);
-      ret.updatedCoins.push(cd.coin);
-      ret.originalCoins.push(originalCoin);
+      const coinInfo: CoinPayInfo = {
+        sig: s,
+        coinPub: cd.coin.coinPub,
+        subtractedAmount: coinSpend,
+      };
+      ret.coinInfo.push(coinInfo);
     }
     return ret;
   }
diff --git a/src/operations/pay.ts b/src/operations/pay.ts
index 5ed29350..363688db 100644
--- a/src/operations/pay.ts
+++ b/src/operations/pay.ts
@@ -36,6 +36,7 @@ import {
   RefundReason,
   Stores,
   updateRetryInfoTimeout,
+  PayEventRecord,
 } from "../types/dbTypes";
 import { NotificationType } from "../types/notifications";
 import {
@@ -52,7 +53,7 @@ import {
   ConfirmPayResult,
   getTimestampNow,
   OperationError,
-  PayCoinInfo,
+  PaySigInfo,
   PreparePayResult,
   RefreshReason,
   Timestamp,
@@ -73,13 +74,6 @@ import { createRefreshGroup, getTotalRefreshCost } from 
"./refresh";
 import { acceptRefundResponse } from "./refund";
 import { InternalWalletState } from "./state";
 
-export interface SpeculativePayData {
-  payCoinInfo: PayCoinInfo;
-  exchangeUrl: string;
-  orderDownloadId: string;
-  proposal: ProposalRecord;
-}
-
 interface CoinsForPaymentArgs {
   allowedAuditors: Auditor[];
   allowedExchanges: ExchangeHandle[];
@@ -323,8 +317,7 @@ async function getCoinsForPayment(
 async function recordConfirmPay(
   ws: InternalWalletState,
   proposal: ProposalRecord,
-  payCoinInfo: PayCoinInfo,
-  chosenExchange: string,
+  payCoinInfo: PaySigInfo,
   sessionIdOverride: string | undefined,
 ): Promise<PurchaseRecord> {
   const d = proposal.download;
@@ -339,7 +332,7 @@ async function recordConfirmPay(
   }
   logger.trace(`recording payment with session ID ${sessionId}`);
   const payReq: PayReq = {
-    coins: payCoinInfo.sigs,
+    coins: payCoinInfo.coinInfo.map((x) => x.sig),
     merchant_pub: d.contractTerms.merchant_pub,
     mode: "pay",
     order_id: d.contractTerms.order_id,
@@ -374,7 +367,7 @@ async function recordConfirmPay(
   };
 
   await ws.db.runWithWriteTransaction(
-    [Stores.coins, Stores.purchases, Stores.proposals],
+    [Stores.coins, Stores.purchases, Stores.proposals, Stores.refreshGroups],
     async tx => {
       const p = await tx.get(Stores.proposals, proposal.proposalId);
       if (p) {
@@ -384,9 +377,21 @@ async function recordConfirmPay(
         await tx.put(Stores.proposals, p);
       }
       await tx.put(Stores.purchases, t);
-      for (let c of payCoinInfo.updatedCoins) {
-        await tx.put(Stores.coins, c);
+      for (let coinInfo of payCoinInfo.coinInfo) {
+        const coin = await tx.get(Stores.coins, coinInfo.coinPub);
+        if (!coin) {
+          throw Error("coin allocated for payment doesn't exist anymore");
+        }
+        coin.status = CoinStatus.Dormant;
+        const remaining = Amounts.sub(coin.currentAmount, 
coinInfo.subtractedAmount);
+        if (remaining.saturated) {
+          throw Error("not enough remaining balance on coin for payment");
+        }
+        coin.currentAmount = remaining.amount;
+        await tx.put(Stores.coins, coin);
       }
+      const refreshCoinPubs = payCoinInfo.coinInfo.map((x) => ({coinPub: 
x.coinPub}));
+      await createRefreshGroup(tx, refreshCoinPubs, RefreshReason.Pay);
     },
   );
 
@@ -707,6 +712,8 @@ export async function submitPay(
   const merchantResp = await resp.json();
   console.log("got success from pay URL", merchantResp);
 
+  const now = getTimestampNow();
+
   const merchantPub = purchase.contractTerms.merchant_pub;
   const valid: boolean = await ws.cryptoApi.isValidPaymentSignature(
     merchantResp.sig,
@@ -719,7 +726,7 @@ export async function submitPay(
     throw Error("merchant payment signature invalid");
   }
   const isFirst = purchase.firstSuccessfulPayTimestamp === undefined;
-  purchase.firstSuccessfulPayTimestamp = getTimestampNow();
+  purchase.firstSuccessfulPayTimestamp = now;
   purchase.paymentSubmitPending = false;
   purchase.lastPayError = undefined;
   purchase.payRetryInfo = initRetryInfo(false);
@@ -734,35 +741,22 @@ export async function submitPay(
         purchase.refundStatusRetryInfo = initRetryInfo();
         purchase.lastRefundStatusError = undefined;
         purchase.autoRefundDeadline = {
-          t_ms: getTimestampNow().t_ms + autoRefundDelay.d_ms,
+          t_ms: now.t_ms + autoRefundDelay.d_ms,
         };
       }
     }
   }
 
-  const modifiedCoins: CoinRecord[] = [];
-  for (const pc of purchase.payReq.coins) {
-    const c = await ws.db.get(Stores.coins, pc.coin_pub);
-    if (!c) {
-      console.error("coin not found");
-      throw Error("coin used in payment not found");
-    }
-    c.status = CoinStatus.Dormant;
-    modifiedCoins.push(c);
-  }
-
   await ws.db.runWithWriteTransaction(
-    [Stores.coins, Stores.purchases, Stores.refreshGroups],
+    [Stores.purchases, Stores.payEvents],
     async tx => {
-      for (let c of modifiedCoins) {
-        await tx.put(Stores.coins, c);
-      }
-      await createRefreshGroup(
-        tx,
-        modifiedCoins.map(x => ({ coinPub: x.coinPub })),
-        RefreshReason.Pay,
-      );
       await tx.put(Stores.purchases, purchase);
+      const payEvent: PayEventRecord = {
+        proposalId,
+        sessionId,
+        timestamp: now,
+      };
+      await tx.put(Stores.payEvents, payEvent);
     },
   );
 
@@ -861,27 +855,6 @@ export async function preparePay(
       };
     }
 
-    // Only create speculative signature if we don't already have one for this 
proposal
-    if (
-      !ws.speculativePayData ||
-      (ws.speculativePayData &&
-        ws.speculativePayData.orderDownloadId !== proposalId)
-    ) {
-      const { exchangeUrl, cds, totalAmount } = res;
-      const payCoinInfo = await ws.cryptoApi.signDeposit(
-        contractTerms,
-        cds,
-        totalAmount,
-      );
-      ws.speculativePayData = {
-        exchangeUrl,
-        payCoinInfo,
-        proposal,
-        orderDownloadId: proposalId,
-      };
-      logger.trace("created speculative pay data for payment");
-    }
-
     return {
       status: "payment-possible",
       contractTerms: contractTerms,
@@ -901,43 +874,6 @@ export async function preparePay(
   };
 }
 
-/**
- * Get the speculative pay data, but only if coins have not changed in between.
- */
-async function getSpeculativePayData(
-  ws: InternalWalletState,
-  proposalId: string,
-): Promise<SpeculativePayData | undefined> {
-  const sp = ws.speculativePayData;
-  if (!sp) {
-    return;
-  }
-  if (sp.orderDownloadId !== proposalId) {
-    return;
-  }
-  const coinKeys = sp.payCoinInfo.updatedCoins.map(x => x.coinPub);
-  const coins: CoinRecord[] = [];
-  for (let coinKey of coinKeys) {
-    const cc = await ws.db.get(Stores.coins, coinKey);
-    if (cc) {
-      coins.push(cc);
-    }
-  }
-  for (let i = 0; i < coins.length; i++) {
-    const specCoin = sp.payCoinInfo.originalCoins[i];
-    const currentCoin = coins[i];
-
-    // Coin does not exist anymore!
-    if (!currentCoin) {
-      return;
-    }
-    if (Amounts.cmp(specCoin.currentAmount, currentCoin.currentAmount) !== 0) {
-      return;
-    }
-  }
-  return sp;
-}
-
 /**
  * Add a contract to the wallet and sign coins, and send them.
  */
@@ -1008,30 +944,18 @@ export async function confirmPay(
     throw Error("insufficient balance");
   }
 
-  const sd = await getSpeculativePayData(ws, proposalId);
-  if (!sd) {
-    const { exchangeUrl, cds, totalAmount } = res;
-    const payCoinInfo = await ws.cryptoApi.signDeposit(
-      d.contractTerms,
-      cds,
-      totalAmount,
-    );
-    purchase = await recordConfirmPay(
-      ws,
-      proposal,
-      payCoinInfo,
-      exchangeUrl,
-      sessionIdOverride,
-    );
-  } else {
-    purchase = await recordConfirmPay(
-      ws,
-      sd.proposal,
-      sd.payCoinInfo,
-      sd.exchangeUrl,
-      sessionIdOverride,
-    );
-  }
+  const { cds, totalAmount } = res;
+  const payCoinInfo = await ws.cryptoApi.signDeposit(
+    d.contractTerms,
+    cds,
+    totalAmount,
+  );
+  purchase = await recordConfirmPay(
+    ws,
+    proposal,
+    payCoinInfo,
+    sessionIdOverride
+  );
 
   logger.trace("confirmPay: submitting payment after creating purchase 
record");
   return submitPay(ws, proposalId);
diff --git a/src/operations/return.ts b/src/operations/return.ts
index 01d2802d..4238f6cd 100644
--- a/src/operations/return.ts
+++ b/src/operations/return.ts
@@ -176,7 +176,7 @@ export async function returnCoins(
 
   logger.trace("pci", payCoinInfo);
 
-  const coins = payCoinInfo.sigs.map(s => ({ coinPaySig: s }));
+  const coins = payCoinInfo.coinInfo.map(s => ({ coinPaySig: s.sig }));
 
   const coinsReturnRecord: CoinsReturnRecord = {
     coins,
@@ -191,8 +191,17 @@ export async function returnCoins(
     [Stores.coinsReturns, Stores.coins],
     async tx => {
       await tx.put(Stores.coinsReturns, coinsReturnRecord);
-      for (let c of payCoinInfo.updatedCoins) {
-        await tx.put(Stores.coins, c);
+      for (let coinInfo of payCoinInfo.coinInfo) {
+        const coin = await tx.get(Stores.coins, coinInfo.coinPub);
+        if (!coin) {
+          throw Error("coin allocated for deposit not in database anymore");
+        }
+        const remaining = Amounts.sub(coin.currentAmount, 
coinInfo.subtractedAmount);
+        if (remaining.saturated) {
+          throw Error("coin allocated for deposit does not have enough 
balance");
+        }
+        coin.currentAmount = remaining.amount;
+        await tx.put(Stores.coins, coin);
       }
     },
   );
diff --git a/src/operations/state.ts b/src/operations/state.ts
index 1e4b9036..3e4936c9 100644
--- a/src/operations/state.ts
+++ b/src/operations/state.ts
@@ -19,7 +19,6 @@ import {
   NextUrlResult,
   WalletBalance,
 } from "../types/walletTypes";
-import { SpeculativePayData } from "./pay";
 import { CryptoApi, CryptoWorkerFactory } from "../crypto/workers/cryptoApi";
 import { AsyncOpMemoMap, AsyncOpMemoSingle } from "../util/asyncMemo";
 import { Logger } from "../util/logging";
@@ -32,7 +31,6 @@ type NotificationListener = (n: WalletNotification) => void;
 const logger = new Logger("state.ts");
 
 export class InternalWalletState {
-  speculativePayData: SpeculativePayData | undefined = undefined;
   cachedNextUrl: { [fulfillmentUrl: string]: NextUrlResult } = {};
   memoProcessReserve: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
   memoMakePlanchet: AsyncOpMemoMap<void> = new AsyncOpMemoMap();
diff --git a/src/types/dbTypes.ts b/src/types/dbTypes.ts
index 9d2f6fe5..7447fc54 100644
--- a/src/types/dbTypes.ts
+++ b/src/types/dbTypes.ts
@@ -1059,6 +1059,16 @@ export interface PurchaseRefundState {
   refundsFailed: { [refundSig: string]: RefundInfo };
 }
 
+/**
+ * Record stored for every time we successfully submitted
+ * a payment to the merchant (both first time and re-play).
+ */
+export interface PayEventRecord {
+  proposalId: string;
+  sessionId: string | undefined;
+  timestamp: Timestamp;
+}
+
 /**
  * Record that stores status information about one purchase, starting from when
  * the customer accepts a proposal.  Includes refund status if applicable.
@@ -1432,6 +1442,12 @@ export namespace Stores {
     }
   }
 
+  class PayEventsStore extends Store<PayEventRecord> {
+    constructor() {
+      super("payEvents", { keyPath: "proposalId" });
+    }
+  }
+
   class BankWithdrawUrisStore extends Store<BankWithdrawUriRecord> {
     constructor() {
       super("bankWithdrawUris", { keyPath: "talerWithdrawUri" });
@@ -1457,6 +1473,7 @@ export namespace Stores {
   export const withdrawalSession = new WithdrawalSessionsStore();
   export const bankWithdrawUris = new BankWithdrawUrisStore();
   export const refundEvents = new RefundEventsStore();
+  export const payEvents = new PayEventsStore();
 }
 
 /* tslint:enable:completed-docs */
diff --git a/src/types/walletTypes.ts b/src/types/walletTypes.ts
index eedae6f2..df19d8dc 100644
--- a/src/types/walletTypes.ts
+++ b/src/types/walletTypes.ts
@@ -195,14 +195,30 @@ export interface WalletBalanceEntry {
   pendingIncomingDirty: AmountJson;
 }
 
+export interface CoinPayInfo {
+  /**
+   * Amount that will be subtracted from the coin when the payment is 
finalized.
+   */
+  subtractedAmount: AmountJson;
+
+  /**
+   * Public key of the coin that is being spent.
+   */
+  coinPub: string;
+
+  /**
+   * Signature together with the other information needed by the merchant,
+   * directly in the format expected by the merchant.
+   */
+  sig: CoinPaySig;
+}
+
 /**
  * Coins used for a payment, with signatures authorizing the payment and the
  * coins with remaining value updated to accomodate for a payment.
  */
-export interface PayCoinInfo {
-  originalCoins: CoinRecord[];
-  updatedCoins: CoinRecord[];
-  sigs: CoinPaySig[];
+export interface PaySigInfo {
+  coinInfo: CoinPayInfo[];
 }
 
 /**

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



reply via email to

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