From ee8993f11cf81721cc30b4473e40124c2fee0dff Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 12 Sep 2023 12:24:42 +0200 Subject: wallet-core: use batch deposit API --- packages/taler-wallet-core/src/db.ts | 2 + .../taler-wallet-core/src/operations/deposits.ts | 178 ++++++++++----------- 2 files changed, 83 insertions(+), 97 deletions(-) (limited to 'packages/taler-wallet-core') 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 { - 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 = 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, -- cgit v1.2.3