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: use batch deposi


From: gnunet
Subject: [taler-wallet-core] branch master updated: wallet-core: use batch deposit API
Date: Tue, 12 Sep 2023 12:25:40 +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 ee8993f11 wallet-core: use batch deposit API
ee8993f11 is described below

commit ee8993f11cf81721cc30b4473e40124c2fee0dff
Author: Florian Dold <florian@dold.me>
AuthorDate: Tue Sep 12 12:24:42 2023 +0200

    wallet-core: use batch deposit API
---
 packages/taler-util/src/taler-types.ts             | 116 +++++++++++---
 packages/taler-wallet-core/src/db.ts               |   2 +
 .../taler-wallet-core/src/operations/deposits.ts   | 178 ++++++++++-----------
 3 files changed, 181 insertions(+), 115 deletions(-)

diff --git a/packages/taler-util/src/taler-types.ts 
b/packages/taler-util/src/taler-types.ts
index a78df7452..eaba1ae3d 100644
--- a/packages/taler-util/src/taler-types.ts
+++ b/packages/taler-util/src/taler-types.ts
@@ -1889,42 +1889,58 @@ export interface ExchangeRefreshRevealRequest {
   old_age_commitment?: Edx25519PublicKeyEnc[];
 }
 
-export interface DepositSuccess {
+interface DepositConfirmationSignature {
+  // The EdDSA signature of `TALER_DepositConfirmationPS` using a current
+  // `signing key of the exchange <sign-key-priv>` affirming the successful
+  // deposit and that the exchange will transfer the funds after the refund
+  // deadline, or as soon as possible if the refund deadline is zero.
+  exchange_sig: EddsaSignatureString;
+}
+
+export interface BatchDepositSuccess {
   // Optional base URL of the exchange for looking up wire transfers
   // associated with this transaction.  If not given,
   // the base URL is the same as the one used for this request.
-  // Can be used if the base URL for /transactions/ differs from that
-  // for /coins/, i.e. for load balancing.  Clients SHOULD
-  // respect the transaction_base_url if provided.  Any HTTP server
+  // Can be used if the base URL for ``/transactions/`` differs from that
+  // for ``/coins/``, i.e. for load balancing.  Clients SHOULD
+  // respect the ``transaction_base_url`` if provided.  Any HTTP server
   // belonging to an exchange MUST generate a 307 or 308 redirection
   // to the correct base URL should a client uses the wrong base
   // URL, or if the base URL has changed since the deposit.
   transaction_base_url?: string;
 
-  // timestamp when the deposit was received by the exchange.
+  // Timestamp when the deposit was received by the exchange.
   exchange_timestamp: TalerProtocolTimestamp;
 
-  // the EdDSA signature of TALER_DepositConfirmationPS using a current
-  // signing key of the exchange affirming the successful
-  // deposit and that the exchange will transfer the funds after the refund
-  // deadline, or as soon as possible if the refund deadline is zero.
-  exchange_sig: string;
-
-  // public EdDSA key of the exchange that was used to
+  // `Public EdDSA key of the exchange <sign-key-pub>` that was used to
   // generate the signature.
-  // Should match one of the exchange's signing keys from /keys.  It is given
+  // Should match one of the exchange's signing keys from ``/keys``.  It is 
given
   // explicitly as the client might otherwise be confused by clock skew as to
   // which signing key was used.
-  exchange_pub: string;
+  exchange_pub: EddsaPublicKeyString;
+
+  // Array of deposit confirmation signatures from the exchange
+  // Entries must be in the same order the coins were given
+  // in the batch deposit request.
+  exchange_sigs: DepositConfirmationSignature[];
 }
 
-export const codecForDepositSuccess = (): Codec<DepositSuccess> =>
-  buildCodecForObject<DepositSuccess>()
+export const codecForDepositConfirmationSignature =
+  (): Codec<DepositConfirmationSignature> =>
+    buildCodecForObject<DepositConfirmationSignature>()
+      .property("exchange_sig", codecForString())
+      .build("DepositConfirmationSignature");
+
+export const codecForBatchDepositSuccess = (): Codec<BatchDepositSuccess> =>
+  buildCodecForObject<BatchDepositSuccess>()
     .property("exchange_pub", codecForString())
-    .property("exchange_sig", codecForString())
+    .property(
+      "exchange_sigs",
+      codecForList(codecForDepositConfirmationSignature()),
+    )
     .property("exchange_timestamp", codecForTimestamp)
     .property("transaction_base_url", codecOptional(codecForString()))
-    .build("DepositSuccess");
+    .build("BatchDepositSuccess");
 
 export interface TrackTransactionWired {
   // Raw wire transfer identifier of the deposit.
@@ -2148,6 +2164,9 @@ export interface ExchangePurseDeposits {
   deposits: PurseDeposit[];
 }
 
+/**
+ * @deprecated batch deposit should be used.
+ */
 export interface ExchangeDepositRequest {
   // Amount to be deposited, can be a fraction of the
   // coin's total value.
@@ -2210,6 +2229,67 @@ export interface ExchangeDepositRequest {
   h_age_commitment?: string;
 }
 
+export type WireSalt = string;
+
+export interface ExchangeBatchDepositRequest {
+  // The merchant's account details.
+  merchant_payto_uri: string;
+
+  // The salt is used to hide the ``payto_uri`` from customers
+  // when computing the ``h_wire`` of the merchant.
+  wire_salt: WireSalt;
+
+  // SHA-512 hash of the contract of the merchant with the customer.  Further
+  // details are never disclosed to the exchange.
+  h_contract_terms: HashCodeString;
+
+  // The list of coins that are going to be deposited with this Request.
+  coins: BatchDepositRequestCoin[];
+
+  // Timestamp when the contract was finalized.
+  timestamp: TalerProtocolTimestamp;
+
+  // Indicative time by which the exchange undertakes to transfer the funds to
+  // the merchant, in case of successful payment. A wire transfer deadline of 
'never'
+  // is not allowed.
+  wire_transfer_deadline: TalerProtocolTimestamp;
+
+  // EdDSA `public key of the merchant <merchant-pub>`, so that the client can 
identify the
+  // merchant for refund requests.
+  merchant_pub: EddsaPublicKeyString;
+
+  // Date until which the merchant can issue a refund to the customer via the
+  // exchange, to be omitted if refunds are not allowed.
+  //
+  // THIS FIELD WILL BE DEPRICATED, once the refund mechanism becomes a
+  // policy via extension.
+  refund_deadline?: TalerProtocolTimestamp;
+
+  // CAVEAT: THIS IS WORK IN PROGRESS
+  // (Optional) policy for the batch-deposit.
+  // This might be a refund, auction or escrow policy.
+  policy?: any;
+}
+
+export interface BatchDepositRequestCoin {
+  // EdDSA public key of the coin being deposited.
+  coin_pub: EddsaPublicKeyString;
+
+  // Hash of denomination RSA key with which the coin is signed.
+  denom_pub_hash: HashCodeString;
+
+  // Exchange's unblinded RSA signature of the coin.
+  ub_sig: UnblindedSignature;
+
+  // Amount to be deposited, can be a fraction of the
+  // coin's total value.
+  contribution: Amounts;
+
+  // Signature over `TALER_DepositRequestPS`, made by the customer with the
+  // `coin's private key <coin-priv>`.
+  coin_sig: EddsaSignatureString;
+}
+
 export interface WalletKycUuid {
   // UUID that the wallet should use when initiating
   // the KYC check.
diff --git a/packages/taler-wallet-core/src/db.ts 
b/packages/taler-wallet-core/src/db.ts
index ba1f5b8c0..04c3ce723 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -1657,6 +1657,8 @@ export interface DepositGroupRecord {
 
   /**
    * Verbatim contract terms.
+   * 
+   * FIXME: Move this to the contract terms object store!
    */
   contractTermsRaw: MerchantContractTerms;
 
diff --git a/packages/taler-wallet-core/src/operations/deposits.ts 
b/packages/taler-wallet-core/src/operations/deposits.ts
index 8ea792d91..a3483a332 100644
--- a/packages/taler-wallet-core/src/operations/deposits.ts
+++ b/packages/taler-wallet-core/src/operations/deposits.ts
@@ -21,70 +21,69 @@ import {
   AbsoluteTime,
   AmountJson,
   Amounts,
+  BatchDepositRequestCoin,
   CancellationToken,
-  canonicalJson,
-  codecForDepositSuccess,
-  codecForTackTransactionAccepted,
-  codecForTackTransactionWired,
   CoinRefreshRequest,
   CreateDepositGroupRequest,
   CreateDepositGroupResponse,
   DepositGroupFees,
-  durationFromSpec,
-  encodeCrock,
-  ExchangeDepositRequest,
+  Duration,
+  ExchangeBatchDepositRequest,
   ExchangeRefundRequest,
-  getRandomBytes,
-  hashTruncate32,
-  hashWire,
   HttpStatusCode,
-  j2s,
   Logger,
   MerchantContractTerms,
   NotificationType,
-  parsePaytoUri,
   PayCoinSelection,
   PrepareDepositRequest,
   PrepareDepositResponse,
   RefreshReason,
-  stringToBytes,
+  TalerError,
   TalerErrorCode,
-  TalerProtocolTimestamp,
   TalerPreciseTimestamp,
+  TalerProtocolTimestamp,
   TrackTransaction,
+  TransactionAction,
   TransactionMajorState,
   TransactionMinorState,
   TransactionState,
   TransactionType,
   URL,
   WireFee,
-  TransactionAction,
-  Duration,
+  canonicalJson,
+  codecForBatchDepositSuccess,
+  codecForTackTransactionAccepted,
+  codecForTackTransactionWired,
+  durationFromSpec,
+  encodeCrock,
+  getRandomBytes,
+  hashTruncate32,
+  hashWire,
+  j2s,
+  parsePaytoUri,
+  stringToBytes,
 } from "@gnu-taler/taler-util";
+import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
+import { DepositElementStatus, DepositGroupRecord } from "../db.js";
 import {
-  DenominationRecord,
-  DepositGroupRecord,
-  DepositElementStatus,
-} from "../db.js";
-import { TalerError } from "@gnu-taler/taler-util";
-import {
-  createRefreshGroup,
   DepositOperationStatus,
   DepositTrackingInfo,
-  getTotalRefreshCost,
   KycPendingInfo,
-  KycUserType,
   PendingTaskType,
   RefreshOperationStatus,
+  createRefreshGroup,
+  getTotalRefreshCost,
 } from "../index.js";
 import { InternalWalletState } from "../internal-wallet-state.js";
-import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
+import { assertUnreachable } from "../util/assertUnreachable.js";
+import { selectPayCoinsNew } from "../util/coinSelection.js";
+import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
 import {
-  constructTaskIdentifier,
   TaskRunResult,
+  TombstoneTag,
+  constructTaskIdentifier,
   runLongpollAsync,
   spendCoins,
-  TombstoneTag,
 } from "./common.js";
 import { getExchangeDetails } from "./exchanges.js";
 import {
@@ -92,15 +91,12 @@ import {
   generateDepositPermissions,
   getTotalPaymentCost,
 } from "./pay-merchant.js";
-import { selectPayCoinsNew } from "../util/coinSelection.js";
 import {
   constructTransactionIdentifier,
   notifyTransition,
   parseTransactionIdentifier,
   stopLongpolling,
 } from "./transactions.js";
-import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js";
-import { assertUnreachable } from "../util/assertUnreachable.js";
 
 /**
  * Logger.
@@ -169,6 +165,10 @@ export function computeDepositTransactionStatus(
   }
 }
 
+/**
+ * Compute the possible actions possible on a deposit transaction
+ * based on the current transaction state.
+ */
 export function computeDepositTransactionActions(
   dg: DepositGroupRecord,
 ): TransactionAction[] {
@@ -200,6 +200,11 @@ export function computeDepositTransactionActions(
   }
 }
 
+/**
+ * Put a deposit group in a suspended state.
+ * While the deposit group is suspended, no network requests
+ * will be made to advance the transaction status.
+ */
 export async function suspendDepositGroup(
   ws: InternalWalletState,
   depositGroupId: string,
@@ -406,46 +411,6 @@ export async function deleteDepositGroup(
     });
 }
 
-/**
- * Check KYC status with the exchange, throw an appropriate exception when KYC
- * is required.
- *
- * FIXME: Why does this throw an exception when KYC is required?
- * Should we not return some proper result record here?
- */
-async function checkDepositKycStatus(
-  ws: InternalWalletState,
-  exchangeUrl: string,
-  kycInfo: KycPendingInfo,
-  userType: KycUserType,
-): Promise<void> {
-  const url = new URL(
-    `kyc-check/${kycInfo.requirementRow}/${kycInfo.paytoHash}/${userType}`,
-    exchangeUrl,
-  );
-  logger.info(`kyc url ${url.href}`);
-  const kycStatusReq = await ws.http.fetch(url.href, {
-    method: "GET",
-  });
-  if (kycStatusReq.status === HttpStatusCode.Ok) {
-    logger.warn("kyc requested, but already fulfilled");
-    return;
-  } else if (kycStatusReq.status === HttpStatusCode.Accepted) {
-    const kycStatus = await kycStatusReq.json();
-    logger.info(`kyc status: ${j2s(kycStatus)}`);
-    // FIXME: This error code is totally wrong
-    throw TalerError.fromDetail(
-      TalerErrorCode.WALLET_WITHDRAWAL_KYC_REQUIRED,
-      {
-        kycUrl: kycStatus.kyc_url,
-      },
-      `KYC check required for deposit`,
-    );
-  } else {
-    throw Error(`unexpected response from kyc-check (${kycStatusReq.status})`);
-  }
-}
-
 /**
  * Check whether the refresh associated with the
  * aborting deposit group is done.
@@ -940,38 +905,58 @@ async function processDepositGroupPendingDeposit(
     contractData,
   );
 
-  for (let i = 0; i < depositPermissions.length; i++) {
-    const perm = depositPermissions[i];
+  // Exchanges involved in the deposit
+  const exchanges: Set<string> = new Set();
 
-    if (depositGroup.statusPerCoin[i] !== DepositElementStatus.DepositPending) 
{
-      continue;
-    }
+  for (const dp of depositPermissions) {
+    exchanges.add(dp.exchange_url);
+  }
 
-    const requestBody: ExchangeDepositRequest = {
-      contribution: Amounts.stringify(perm.contribution),
-      merchant_payto_uri: depositGroup.wire.payto_uri,
-      wire_salt: depositGroup.wire.salt,
+  // We need to do one batch per exchange.
+  for (const exchangeUrl of exchanges.values()) {
+    const coins: BatchDepositRequestCoin[] = [];
+    const batchIndexes: number[] = [];
+
+    const batchReq: ExchangeBatchDepositRequest = {
+      coins,
       h_contract_terms: depositGroup.contractTermsHash,
-      ub_sig: perm.ub_sig,
+      merchant_payto_uri: depositGroup.wire.payto_uri,
+      merchant_pub: depositGroup.contractTermsRaw.merchant_pub,
       timestamp: depositGroup.contractTermsRaw.timestamp,
+      wire_salt: depositGroup.wire.salt,
       wire_transfer_deadline:
         depositGroup.contractTermsRaw.wire_transfer_deadline,
       refund_deadline: depositGroup.contractTermsRaw.refund_deadline,
-      coin_sig: perm.coin_sig,
-      denom_pub_hash: perm.h_denom,
-      merchant_pub: depositGroup.merchantPub,
-      h_age_commitment: perm.h_age_commitment,
     };
+
+    for (let i = 0; i < depositPermissions.length; i++) {
+      const perm = depositPermissions[i];
+      if (perm.exchange_url != exchangeUrl) {
+        continue;
+      }
+      coins.push({
+        coin_pub: perm.coin_pub,
+        coin_sig: perm.coin_sig,
+        contribution: Amounts.stringify(perm.contribution),
+        denom_pub_hash: perm.h_denom,
+        ub_sig: perm.ub_sig,
+      });
+      batchIndexes.push(i);
+    }
+
     // Check for cancellation before making network request.
     cancellationToken?.throwIfCancelled();
-    const url = new URL(`coins/${perm.coin_pub}/deposit`, perm.exchange_url);
+    const url = new URL(`batch-deposit`, exchangeUrl);
     logger.info(`depositing to ${url}`);
     const httpResp = await ws.http.fetch(url.href, {
       method: "POST",
-      body: requestBody,
+      body: batchReq,
       cancellationToken: cancellationToken,
     });
-    await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess());
+    await readSuccessResponseJsonOrThrow(
+      httpResp,
+      codecForBatchDepositSuccess(),
+    );
 
     await ws.db
       .mktx((x) => [x.depositGroups])
@@ -980,11 +965,13 @@ async function processDepositGroupPendingDeposit(
         if (!dg) {
           return;
         }
-        const coinStatus = dg.statusPerCoin[i];
-        switch (coinStatus) {
-          case DepositElementStatus.DepositPending:
-            dg.statusPerCoin[i] = DepositElementStatus.Tracking;
-            await tx.depositGroups.put(dg);
+        for (const batchIndex of batchIndexes) {
+          const coinStatus = dg.statusPerCoin[batchIndex];
+          switch (coinStatus) {
+            case DepositElementStatus.DepositPending:
+              dg.statusPerCoin[batchIndex] = DepositElementStatus.Tracking;
+              await tx.depositGroups.put(dg);
+          }
         }
       });
   }
@@ -1538,10 +1525,7 @@ async function getTotalFeesForDepositAmount(
         const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl
           .iter(coin.exchangeBaseUrl)
           .filter((x) =>
-            Amounts.isSameCurrency(
-              x.value,
-              pcs.coinContributions[i],
-            ),
+            Amounts.isSameCurrency(x.value, pcs.coinContributions[i]),
           );
         const amountLeft = Amounts.sub(
           denom.value,

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