From f5d194dfc61ae0d358a27b994861dc20b83cf98e Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 28 Mar 2022 23:59:16 +0200 Subject: wallet: cancellation for deposit --- .../taler-wallet-core/src/operations/deposits.ts | 40 +++++++++++---- .../taler-wallet-core/src/operations/exchanges.ts | 60 ++++++++++++++++++---- .../src/operations/transactions.ts | 4 +- 3 files changed, 82 insertions(+), 22 deletions(-) (limited to 'packages/taler-wallet-core/src/operations') diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index 501e9b76b..c11c45dc6 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -21,6 +21,7 @@ import { AbsoluteTime, AmountJson, Amounts, + CancellationToken, canonicalJson, codecForDepositSuccess, ContractTerms, @@ -125,23 +126,34 @@ async function reportDepositGroupError( export async function processDepositGroup( ws: InternalWalletState, depositGroupId: string, - forceNow = false, + options: { + forceNow?: boolean; + cancellationToken?: CancellationToken; + } = {}, ): Promise { - await ws.memoProcessDeposit.memo(depositGroupId, async () => { - const onOpErr = (err: TalerErrorDetail): Promise => - reportDepositGroupError(ws, depositGroupId, err); - return await guardOperationException( - async () => await processDepositGroupImpl(ws, depositGroupId, forceNow), - onOpErr, - ); - }); + if (ws.taskCancellationSourceForDeposit) { + ws.taskCancellationSourceForDeposit.cancel(); + } + const onOpErr = (err: TalerErrorDetail): Promise => + reportDepositGroupError(ws, depositGroupId, err); + return await guardOperationException( + async () => await processDepositGroupImpl(ws, depositGroupId, options), + onOpErr, + ); } +/** + * @see {processDepositGroup} + */ async function processDepositGroupImpl( ws: InternalWalletState, depositGroupId: string, - forceNow = false, + options: { + forceNow?: boolean; + cancellationToken?: CancellationToken; + } = {}, ): Promise { + const forceNow = options.forceNow ?? false; const depositGroup = await ws.db .mktx((x) => ({ depositGroups: x.depositGroups, @@ -170,6 +182,8 @@ async function processDepositGroupImpl( "", ); + // Check for cancellation before expensive operations. + options.cancellationToken?.throwIfCancelled(); const depositPermissions = await generateDepositPermissions( ws, depositGroup.payCoinSelection, @@ -196,9 +210,13 @@ async function processDepositGroupImpl( denom_pub_hash: perm.h_denom, merchant_pub: depositGroup.merchantPub, }; + // Check for cancellation before making network request. + options.cancellationToken?.throwIfCancelled(); const url = new URL(`coins/${perm.coin_pub}/deposit`, perm.exchange_url); logger.info(`depositing to ${url}`); - const httpResp = await ws.http.postJson(url.href, requestBody); + const httpResp = await ws.http.postJson(url.href, requestBody, { + cancellationToken: options.cancellationToken, + }); await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess()); await ws.db .mktx((x) => ({ depositGroups: x.depositGroups })) diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index 09449c875..fe1c9ef35 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -61,7 +61,11 @@ import { readSuccessResponseTextOrThrow, } from "../util/http.js"; import { DbAccess, GetReadOnlyAccess } from "../util/query.js"; -import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; +import { + initRetryInfo, + RetryInfo, + updateRetryInfoTimeout, +} from "../util/retries.js"; import { WALLET_CACHE_BREAKER_CLIENT_VERSION, WALLET_EXCHANGE_PROTOCOL_VERSION, @@ -102,7 +106,7 @@ function denominationRecordFromKeys( return d; } -async function handleExchangeUpdateError( +async function reportExchangeUpdateError( ws: InternalWalletState, baseUrl: string, err: TalerErrorDetail, @@ -114,14 +118,44 @@ async function handleExchangeUpdateError( if (!exchange) { return; } - exchange.retryInfo.retryCounter++; - updateRetryInfoTimeout(exchange.retryInfo); exchange.lastError = err; await tx.exchanges.put(exchange); }); - if (err) { - ws.notify({ type: NotificationType.ExchangeOperationError, error: err }); - } + ws.notify({ type: NotificationType.ExchangeOperationError, error: err }); +} + +async function resetExchangeUpdateRetry( + ws: InternalWalletState, + baseUrl: string, +): Promise { + await ws.db + .mktx((x) => ({ exchanges: x.exchanges })) + .runReadWrite(async (tx) => { + const exchange = await tx.exchanges.get(baseUrl); + if (!exchange) { + return; + } + delete exchange.lastError; + exchange.retryInfo = initRetryInfo(); + await tx.exchanges.put(exchange); + }); +} + +async function incrementExchangeUpdateRetry( + ws: InternalWalletState, + baseUrl: string, +): Promise { + await ws.db + .mktx((x) => ({ exchanges: x.exchanges })) + .runReadWrite(async (tx) => { + const exchange = await tx.exchanges.get(baseUrl); + if (!exchange) { + return; + } + delete exchange.lastError; + exchange.retryInfo = RetryInfo.increment(exchange.retryInfo); + await tx.exchanges.put(exchange); + }); } export function getExchangeRequestTimeout(): Duration { @@ -349,7 +383,7 @@ export async function updateExchangeFromUrl( exchangeDetails: ExchangeDetailsRecord; }> { const onOpErr = (e: TalerErrorDetail): Promise => - handleExchangeUpdateError(ws, baseUrl, e); + reportExchangeUpdateError(ws, baseUrl, e); return await guardOperationException( () => updateExchangeFromUrlImpl(ws, baseUrl, acceptedFormat, forceNow), onOpErr, @@ -543,6 +577,12 @@ async function updateExchangeFromUrlImpl( return { exchange, exchangeDetails }; } + if (forceNow) { + await resetExchangeUpdateRetry(ws, baseUrl); + } else { + await incrementExchangeUpdateRetry(ws, baseUrl); + } + logger.info("updating exchange /keys info"); const timeout = getExchangeRequestTimeout(); @@ -624,8 +664,8 @@ async function updateExchangeFromUrlImpl( termsOfServiceAcceptedTimestamp: TalerProtocolTimestamp.now(), }; // FIXME: only update if pointer got updated - r.lastError = undefined; - r.retryInfo = initRetryInfo(); + delete r.lastError; + delete r.retryInfo; r.lastUpdate = TalerProtocolTimestamp.now(); r.nextUpdate = keysInfo.expiry; // New denominations might be available. diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index cb312154e..bb5306189 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -444,7 +444,9 @@ export async function retryTransaction( switch (type) { case TransactionType.Deposit: const depositGroupId = rest[0]; - processDepositGroup(ws, depositGroupId, true); + processDepositGroup(ws, depositGroupId, { + forceNow: true, + }); break; case TransactionType.Withdrawal: const withdrawalGroupId = rest[0]; -- cgit v1.2.3