From 80e43db2cac84e588c2ef3889e8d90b76bd53714 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 28 Mar 2022 23:21:49 +0200 Subject: wallet: timeout handling refactoring WIP --- packages/taler-util/src/amounts.ts | 11 ++ .../taler-wallet-core/src/operations/common.ts | 3 + .../taler-wallet-core/src/operations/deposits.ts | 129 ++++++++++----------- .../taler-wallet-core/src/operations/exchanges.ts | 26 ++--- packages/taler-wallet-core/src/operations/pay.ts | 7 +- .../taler-wallet-core/src/operations/withdraw.ts | 33 +++--- packages/taler-wallet-core/src/util/retries.ts | 15 +++ packages/taler-wallet-core/src/wallet.ts | 5 +- .../src/wallet/DepositPage.tsx | 2 - pnpm-lock.yaml | 6 - 10 files changed, 120 insertions(+), 117 deletions(-) diff --git a/packages/taler-util/src/amounts.ts b/packages/taler-util/src/amounts.ts index 505a68f46..98cd4ad62 100644 --- a/packages/taler-util/src/amounts.ts +++ b/packages/taler-util/src/amounts.ts @@ -129,6 +129,17 @@ export class Amounts { return Amounts.add(jsonAmounts[0], ...jsonAmounts.slice(1)); } + static sumOrZero(currency: string, amounts: AmountLike[]): Result { + if (amounts.length <= 0) { + return { + amount: Amounts.getZero(currency), + saturated: false, + }; + } + const jsonAmounts = amounts.map((x) => Amounts.jsonifyAmount(x)); + return Amounts.add(jsonAmounts[0], ...jsonAmounts.slice(1)); + } + /** * Add two amounts. Return the result and whether * the addition overflowed. The overflow is always handled diff --git a/packages/taler-wallet-core/src/operations/common.ts b/packages/taler-wallet-core/src/operations/common.ts index 5261b114d..0fdde9dca 100644 --- a/packages/taler-wallet-core/src/operations/common.ts +++ b/packages/taler-wallet-core/src/operations/common.ts @@ -14,6 +14,9 @@ GNU Taler; see the file COPYING. If not, see */ +/** + * Imports. + */ import { TalerErrorDetail, TalerErrorCode } from "@gnu-taler/taler-util"; import { CryptoApiStoppedError } from "../crypto/workers/cryptoDispatcher.js"; import { TalerError, getErrorDetailFromException } from "../errors.js"; diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index 2e14afdf1..501e9b76b 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -14,17 +14,15 @@ GNU Taler; see the file COPYING. If not, see */ +/** + * Imports. + */ import { AbsoluteTime, AmountJson, Amounts, - buildCodecForObject, canonicalJson, - Codec, codecForDepositSuccess, - codecForString, - codecForTimestamp, - codecOptional, ContractTerms, CreateDepositGroupRequest, CreateDepositGroupResponse, @@ -42,21 +40,22 @@ import { TrackDepositGroupResponse, URL, } from "@gnu-taler/taler-util"; -import { InternalWalletState } from "../internal-wallet-state.js"; import { DepositGroupRecord, OperationStatus } from "../db.js"; +import { InternalWalletState } from "../internal-wallet-state.js"; import { PayCoinSelection, selectPayCoins } from "../util/coinSelection.js"; import { readSuccessResponseJsonOrThrow } from "../util/http.js"; -import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; +import { initRetryInfo, RetryInfo } from "../util/retries.js"; +import { guardOperationException } from "./common.js"; import { getExchangeDetails } from "./exchanges.js"; import { applyCoinSpend, + CoinSelectionRequest, extractContractData, generateDepositPermissions, getCandidatePayCoins, getTotalPaymentCost, } from "./pay.js"; import { getTotalRefreshCost } from "./refresh.js"; -import { guardOperationException } from "./common.js"; /** * Logger. @@ -73,17 +72,36 @@ async function resetDepositGroupRetry( })) .runReadWrite(async (tx) => { const x = await tx.depositGroups.get(depositGroupId); - if (x) { - x.retryInfo = initRetryInfo(); - await tx.depositGroups.put(x); + if (!x) { + return; + } + x.retryInfo = initRetryInfo(); + delete x.lastError; + await tx.depositGroups.put(x); + }); +} + +async function incrementDepositGroupRetry( + ws: InternalWalletState, + depositGroupId: string, +): Promise { + await ws.db + .mktx((x) => ({ depositGroups: x.depositGroups })) + .runReadWrite(async (tx) => { + const r = await tx.depositGroups.get(depositGroupId); + if (!r) { + return; } + r.retryInfo = RetryInfo.increment(r.retryInfo); + delete r.lastError; + await tx.depositGroups.put(r); }); } -async function incrementDepositRetry( +async function reportDepositGroupError( ws: InternalWalletState, depositGroupId: string, - err: TalerErrorDetail | undefined, + err: TalerErrorDetail, ): Promise { await ws.db .mktx((x) => ({ depositGroups: x.depositGroups })) @@ -93,16 +111,15 @@ async function incrementDepositRetry( return; } if (!r.retryInfo) { + logger.error( + `deposit group record (${depositGroupId}) reports error, but no retry active`, + ); return; } - r.retryInfo.retryCounter++; - updateRetryInfoTimeout(r.retryInfo); r.lastError = err; await tx.depositGroups.put(r); }); - if (err) { - ws.notify({ type: NotificationType.DepositOperationError, error: err }); - } + ws.notify({ type: NotificationType.DepositOperationError, error: err }); } export async function processDepositGroup( @@ -111,8 +128,8 @@ export async function processDepositGroup( forceNow = false, ): Promise { await ws.memoProcessDeposit.memo(depositGroupId, async () => { - const onOpErr = (e: TalerErrorDetail): Promise => - incrementDepositRetry(ws, depositGroupId, e); + const onOpErr = (err: TalerErrorDetail): Promise => + reportDepositGroupError(ws, depositGroupId, err); return await guardOperationException( async () => await processDepositGroupImpl(ws, depositGroupId, forceNow), onOpErr, @@ -125,9 +142,6 @@ async function processDepositGroupImpl( depositGroupId: string, forceNow = false, ): Promise { - if (forceNow) { - await resetDepositGroupRetry(ws, depositGroupId); - } const depositGroup = await ws.db .mktx((x) => ({ depositGroups: x.depositGroups, @@ -144,6 +158,12 @@ async function processDepositGroupImpl( return; } + if (forceNow) { + await resetDepositGroupRetry(ws, depositGroupId); + } else { + await incrementDepositGroupRetry(ws, depositGroupId); + } + const contractData = extractContractData( depositGroup.contractTermsRaw, depositGroup.contractTermsHash, @@ -306,42 +326,25 @@ export async function getFeeForDeposit( } }); - const timestamp = AbsoluteTime.now(); - const timestampRound = AbsoluteTime.toTimestamp(timestamp); - const contractTerms: ContractTerms = { - auditors: [], - exchanges: exchangeInfos, - amount: req.amount, - max_fee: Amounts.stringify(amount), - max_wire_fee: Amounts.stringify(amount), - wire_method: p.targetType, - timestamp: timestampRound, - merchant_base_url: "", - summary: "", - nonce: "", - wire_transfer_deadline: timestampRound, - order_id: "", - h_wire: "", - pay_deadline: AbsoluteTime.toTimestamp( - AbsoluteTime.addDuration(timestamp, durationFromSpec({ hours: 1 })), - ), - merchant: { - name: "", - }, - merchant_pub: "", - refund_deadline: TalerProtocolTimestamp.zero(), + const csr: CoinSelectionRequest = { + allowedAuditors: [], + allowedExchanges: [], + amount: Amounts.parseOrThrow(req.amount), + maxDepositFee: Amounts.parseOrThrow(req.amount), + maxWireFee: Amounts.parseOrThrow(req.amount), + timestamp: TalerProtocolTimestamp.now(), + wireFeeAmortization: 1, + wireMethod: p.targetType, }; - const contractData = extractContractData(contractTerms, "", ""); - - const candidates = await getCandidatePayCoins(ws, contractData); + const candidates = await getCandidatePayCoins(ws, csr); const payCoinSel = selectPayCoins({ candidates, - contractTermsAmount: contractData.amount, - depositFeeLimit: contractData.maxDepositFee, - wireFeeAmortization: contractData.wireFeeAmortization ?? 1, - wireFeeLimit: contractData.maxWireFee, + contractTermsAmount: csr.amount, + depositFeeLimit: csr.maxDepositFee, + wireFeeAmortization: csr.wireFeeAmortization, + wireFeeLimit: csr.maxWireFee, prevPayCoins: [], }); @@ -573,6 +576,7 @@ export async function getEffectiveDepositAmount( return Amounts.sub(Amounts.sum(amt).amount, Amounts.sum(fees).amount).amount; } +// FIXME: rename to DepositGroupFee export interface DepositFee { coin: AmountJson; wire: AmountJson; @@ -594,8 +598,6 @@ export async function getTotalFeeForDepositAmount( const refreshFee: AmountJson[] = []; const exchangeSet: Set = new Set(); - // let acc: AmountJson = Amounts.getZero(total.currency); - await ws.db .mktx((x) => ({ coins: x.coins, @@ -658,17 +660,8 @@ export async function getTotalFeeForDepositAmount( }); return { - coin: - coinFee.length === 0 - ? Amounts.getZero(total.currency) - : Amounts.sum(coinFee).amount, - wire: - wireFee.length === 0 - ? Amounts.getZero(total.currency) - : Amounts.sum(wireFee).amount, - refresh: - refreshFee.length === 0 - ? Amounts.getZero(total.currency) - : Amounts.sum(refreshFee).amount, + coin: Amounts.sumOrZero(total.currency, coinFee).amount, + wire: Amounts.sumOrZero(total.currency, wireFee).amount, + refresh: Amounts.sumOrZero(total.currency, refreshFee).amount, }; } diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index 51b5c7806..09449c875 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -18,35 +18,31 @@ * Imports. */ import { + AbsoluteTime, Amounts, - ExchangeAuditor, canonicalizeBaseUrl, codecForExchangeKeysJson, codecForExchangeWireJson, - ExchangeDenomination, + DenominationPubKey, Duration, durationFromSpec, + encodeCrock, + ExchangeAuditor, + ExchangeDenomination, ExchangeSignKeyJson, ExchangeWireJson, + hashDenomPub, + LibtoolVersion, Logger, NotificationType, parsePaytoUri, Recoup, TalerErrorCode, - URL, TalerErrorDetail, - AbsoluteTime, - hashDenomPub, - LibtoolVersion, - codecForAny, - DenominationPubKey, - DenomKeyType, - ExchangeKeysJson, - TalerProtocolTimestamp, TalerProtocolDuration, + TalerProtocolTimestamp, + URL, } from "@gnu-taler/taler-util"; -import { decodeCrock, encodeCrock, hash } from "@gnu-taler/taler-util"; -import { CryptoDispatcher } from "../crypto/workers/cryptoDispatcher.js"; import { DenominationRecord, DenominationVerificationStatus, @@ -56,6 +52,8 @@ import { WireFee, WireInfo, } from "../db.js"; +import { TalerError } from "../errors.js"; +import { InternalWalletState, TrustInfo } from "../internal-wallet-state.js"; import { getExpiry, HttpRequestLibrary, @@ -64,8 +62,6 @@ import { } from "../util/http.js"; import { DbAccess, GetReadOnlyAccess } from "../util/query.js"; import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; -import { TalerError } from "../errors.js"; -import { InternalWalletState, TrustInfo } from "../internal-wallet-state.js"; import { WALLET_CACHE_BREAKER_CLIENT_VERSION, WALLET_EXCHANGE_PROTOCOL_VERSION, diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts index 193ce54e2..b761367fb 100644 --- a/packages/taler-wallet-core/src/operations/pay.ts +++ b/packages/taler-wallet-core/src/operations/pay.ts @@ -98,6 +98,7 @@ import { GetReadWriteAccess } from "../util/query.js"; import { getRetryDuration, initRetryInfo, + RetryInfo, updateRetryInfoTimeout, } from "../util/retries.js"; import { getExchangeDetails } from "./exchanges.js"; @@ -539,11 +540,7 @@ async function incrementPurchasePayRetry( if (!pr) { return; } - if (!pr.payRetryInfo) { - pr.payRetryInfo = initRetryInfo(); - } - pr.payRetryInfo.retryCounter++; - updateRetryInfoTimeout(pr.payRetryInfo); + pr.payRetryInfo = RetryInfo.increment(pr.payRetryInfo); delete pr.lastPayError; await tx.purchases.put(pr); }); diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index 7997ab5be..4a7adbb9c 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -18,32 +18,31 @@ * Imports. */ import { + AbsoluteTime, AmountJson, Amounts, + AmountString, BankWithdrawDetails, codecForTalerConfigResponse, codecForWithdrawOperationStatusResponse, codecForWithdrawResponse, + DenomKeyType, + Duration, durationFromSpec, ExchangeListItem, + ExchangeWithdrawRequest, + LibtoolVersion, Logger, NotificationType, parseWithdrawUri, TalerErrorCode, TalerErrorDetail, - AbsoluteTime, - WithdrawResponse, + TalerProtocolTimestamp, + UnblindedSignature, URL, - WithdrawUriInfoResponse, VersionMatchResult, - DenomKeyType, - LibtoolVersion, - UnblindedSignature, - ExchangeWithdrawRequest, - Duration, - TalerProtocolTimestamp, - TransactionType, - AmountString, + WithdrawResponse, + WithdrawUriInfoResponse, } from "@gnu-taler/taler-util"; import { CoinRecord, @@ -58,18 +57,18 @@ import { PlanchetRecord, WithdrawalGroupRecord, } from "../db.js"; -import { walletCoreDebugFlags } from "../util/debugFlags.js"; -import { - HttpRequestLibrary, - readSuccessResponseJsonOrThrow, -} from "../util/http.js"; -import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; import { getErrorDetailFromException, makeErrorDetail, TalerError, } from "../errors.js"; import { InternalWalletState } from "../internal-wallet-state.js"; +import { walletCoreDebugFlags } from "../util/debugFlags.js"; +import { + HttpRequestLibrary, + readSuccessResponseJsonOrThrow, +} from "../util/http.js"; +import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; import { WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, WALLET_EXCHANGE_PROTOCOL_VERSION, diff --git a/packages/taler-wallet-core/src/util/retries.ts b/packages/taler-wallet-core/src/util/retries.ts index 4b78d38ef..25b4c5055 100644 --- a/packages/taler-wallet-core/src/util/retries.ts +++ b/packages/taler-wallet-core/src/util/retries.ts @@ -92,3 +92,18 @@ export function initRetryInfo(p: RetryPolicy = defaultRetryPolicy): RetryInfo { updateRetryInfoTimeout(info, p); return info; } + +export namespace RetryInfo { + export function increment( + r: RetryInfo | undefined, + p: RetryPolicy = defaultRetryPolicy, + ) { + if (!r) { + return initRetryInfo(p); + } + const r2 = { ...r }; + r2.retryCounter++; + updateRetryInfoTimeout(r2, p); + return r2; + } +} diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index bb560774a..943051153 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -193,10 +193,7 @@ import { import { DbAccess, GetReadWriteAccess } from "./util/query.js"; import { TimerGroup } from "./util/timer.js"; import { WalletCoreApiClient } from "./wallet-api-types.js"; -import { - TalerCryptoInterface, - TalerCryptoInterfaceR, -} from "./crypto/cryptoImplementation.js"; +import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js"; const builtinAuditors: AuditorTrustRecord[] = [ { diff --git a/packages/taler-wallet-webextension/src/wallet/DepositPage.tsx b/packages/taler-wallet-webextension/src/wallet/DepositPage.tsx index 65cdee4e9..2f1f84d94 100644 --- a/packages/taler-wallet-webextension/src/wallet/DepositPage.tsx +++ b/packages/taler-wallet-webextension/src/wallet/DepositPage.tsx @@ -22,7 +22,6 @@ import { PaytoUri, } from "@gnu-taler/taler-util"; import { DepositFee } from "@gnu-taler/taler-wallet-core/src/operations/deposits"; -import { saturate } from "polished"; import { Fragment, h, VNode } from "preact"; import { useEffect, useState } from "preact/hooks"; import { Loading } from "../components/Loading"; @@ -30,7 +29,6 @@ import { LoadingError } from "../components/LoadingError"; import { SelectList } from "../components/SelectList"; import { Button, - ButtonBoxWarning, ButtonPrimary, ErrorText, Input, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8197c8369..c7b17ce00 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -193,7 +193,6 @@ importers: '@rollup/plugin-replace': ^3.0.1 '@types/node': ^17.0.17 axios: ^0.25.0 - cancellationtoken: ^2.2.0 prettier: ^2.5.1 rimraf: ^3.0.2 rollup: ^2.67.2 @@ -207,7 +206,6 @@ importers: '@gnu-taler/taler-util': link:../taler-util '@gnu-taler/taler-wallet-core': link:../taler-wallet-core axios: 0.25.0 - cancellationtoken: 2.2.0 source-map-support: 0.5.21 tslib: 2.3.1 devDependencies: @@ -8726,10 +8724,6 @@ packages: engines: {node: '>=10'} dev: true - /cancellationtoken/2.2.0: - resolution: {integrity: sha512-uF4sHE5uh2VdEZtIRJKGoXAD9jm7bFY0tDRCzH4iLp262TOJ2lrtNHjMG2zc8H+GICOpELIpM7CGW5JeWnb3Hg==} - dev: false - /caniuse-api/3.0.0: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} dependencies: -- cgit v1.2.3