From 396bb61db70f654599256e512bfec4c008ee8269 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Sat, 7 Dec 2019 22:02:11 +0100 Subject: reset retry counter when forcing operations --- src/wallet-impl/exchanges.ts | 11 +++-- src/wallet-impl/pay.ts | 99 +++++++++++++++++++++++++++++++++++++++----- src/wallet-impl/refresh.ts | 30 +++++++++++--- src/wallet-impl/tip.ts | 19 ++++++++- src/wallet-impl/withdraw.ts | 22 +++++++++- src/wallet.ts | 16 +++---- 6 files changed, 164 insertions(+), 33 deletions(-) (limited to 'src') diff --git a/src/wallet-impl/exchanges.ts b/src/wallet-impl/exchanges.ts index 3814971a3..42d626a71 100644 --- a/src/wallet-impl/exchanges.ts +++ b/src/wallet-impl/exchanges.ts @@ -20,7 +20,6 @@ import { KeysJson, Denomination, ExchangeWireJson, - WireFeesJson, } from "../talerTypes"; import { getTimestampNow, OperationError } from "../walletTypes"; import { @@ -313,11 +312,11 @@ async function updateExchangeWithWireInfo( export async function updateExchangeFromUrl( ws: InternalWalletState, baseUrl: string, - force: boolean = false, + forceNow: boolean = false, ): Promise { const onOpErr = (e: OperationError) => setExchangeError(ws, baseUrl, e); return await guardOperationException( - () => updateExchangeFromUrlImpl(ws, baseUrl, force), + () => updateExchangeFromUrlImpl(ws, baseUrl, forceNow), onOpErr, ); } @@ -330,7 +329,7 @@ export async function updateExchangeFromUrl( async function updateExchangeFromUrlImpl( ws: InternalWalletState, baseUrl: string, - force: boolean = false, + forceNow: boolean = false, ): Promise { const now = getTimestampNow(); baseUrl = canonicalizeBaseUrl(baseUrl); @@ -353,10 +352,10 @@ async function updateExchangeFromUrlImpl( if (!rec) { return; } - if (rec.updateStatus != ExchangeUpdateStatus.FETCH_KEYS && !force) { + if (rec.updateStatus != ExchangeUpdateStatus.FETCH_KEYS && !forceNow) { return; } - if (rec.updateStatus != ExchangeUpdateStatus.FETCH_KEYS && force) { + if (rec.updateStatus != ExchangeUpdateStatus.FETCH_KEYS && forceNow) { rec.updateReason = "forced"; } rec.updateStarted = now; diff --git a/src/wallet-impl/pay.ts b/src/wallet-impl/pay.ts index 7f9e90327..d100ad26c 100644 --- a/src/wallet-impl/pay.ts +++ b/src/wallet-impl/pay.ts @@ -67,7 +67,11 @@ import { } from "../util/helpers"; import { Logger } from "../util/logging"; import { InternalWalletState } from "./state"; -import { parsePayUri, parseRefundUri, getOrderDownloadUrl } from "../util/taleruri"; +import { + parsePayUri, + parseRefundUri, + getOrderDownloadUrl, +} from "../util/taleruri"; import { getTotalRefreshCost, refresh } from "./refresh"; import { encodeCrock, getRandomBytes } from "../crypto/talerCrypto"; import { guardOperationException } from "./errors"; @@ -540,19 +544,37 @@ async function incrementPurchaseApplyRefundRetry( export async function processDownloadProposal( ws: InternalWalletState, proposalId: string, + forceNow: boolean = false, ): Promise { const onOpErr = (err: OperationError) => incrementProposalRetry(ws, proposalId, err); await guardOperationException( - () => processDownloadProposalImpl(ws, proposalId), + () => processDownloadProposalImpl(ws, proposalId, forceNow), onOpErr, ); } +async function resetDownloadProposalRetry( + ws: InternalWalletState, + proposalId: string, +) { + await oneShotMutate(ws.db, Stores.proposals, proposalId, (x) => { + if (x.retryInfo.active) { + x.retryInfo = initRetryInfo(); + } + return x; + }); + +} + async function processDownloadProposalImpl( ws: InternalWalletState, proposalId: string, + forceNow: boolean, ): Promise { + if (forceNow) { + await resetDownloadProposalRetry(ws, proposalId); + } const proposal = await oneShotGet(ws.db, Stores.proposals, proposalId); if (!proposal) { return; @@ -560,8 +582,10 @@ async function processDownloadProposalImpl( if (proposal.proposalStatus != ProposalStatus.DOWNLOADING) { return; } - - const parsedUrl = new URL(getOrderDownloadUrl(proposal.merchantBaseUrl, proposal.orderId)); + + const parsedUrl = new URL( + getOrderDownloadUrl(proposal.merchantBaseUrl, proposal.orderId), + ); parsedUrl.searchParams.set("nonce", proposal.noncePub); const urlWithNonce = parsedUrl.href; console.log("downloading contract from '" + urlWithNonce + "'"); @@ -714,12 +738,16 @@ export async function submitPay( if (isFirst) { const ar = purchase.contractTerms.auto_refund; if (ar) { + console.log("auto_refund present"); const autoRefundDelay = extractTalerDuration(ar); + console.log("auto_refund valid", autoRefundDelay); if (autoRefundDelay) { purchase.refundStatusRequested = true; + purchase.refundStatusRetryInfo = initRetryInfo(); + purchase.lastRefundStatusError = undefined; purchase.autoRefundDeadline = { t_ms: getTimestampNow().t_ms + autoRefundDelay.d_ms, - } + }; } } } @@ -1091,7 +1119,10 @@ async function acceptRefundResponse( let queryDone = true; if (numNewRefunds === 0) { - if (p.autoRefundDeadline && p.autoRefundDeadline.t_ms < getTimestampNow().t_ms) { + if ( + p.autoRefundDeadline && + p.autoRefundDeadline.t_ms > getTimestampNow().t_ms + ) { queryDone = false; } } @@ -1101,12 +1132,14 @@ async function acceptRefundResponse( p.lastRefundStatusError = undefined; p.refundStatusRetryInfo = initRetryInfo(); p.refundStatusRequested = false; + console.log("refund query done"); } else { // No error, but we need to try again! p.lastRefundStatusTimestamp = getTimestampNow(); p.refundStatusRetryInfo.retryCounter++; updateRetryInfoTimeout(p.refundStatusRetryInfo); p.lastRefundStatusError = undefined; + console.log("refund query not done"); } if (numNewRefunds) { @@ -1137,8 +1170,6 @@ async function startRefundQuery( console.log("no purchase found for refund URL"); return false; } - if (p.refundStatusRequested) { - } p.refundStatusRequested = true; p.lastRefundStatusError = undefined; p.refundStatusRetryInfo = initRetryInfo(); @@ -1193,19 +1224,36 @@ export async function applyRefund( export async function processPurchasePay( ws: InternalWalletState, proposalId: string, + forceNow: boolean = false, ): Promise { const onOpErr = (e: OperationError) => incrementPurchasePayRetry(ws, proposalId, e); await guardOperationException( - () => processPurchasePayImpl(ws, proposalId), + () => processPurchasePayImpl(ws, proposalId, forceNow), onOpErr, ); } +async function resetPurchasePayRetry( + ws: InternalWalletState, + proposalId: string, +) { + await oneShotMutate(ws.db, Stores.purchases, proposalId, (x) => { + if (x.payRetryInfo.active) { + x.payRetryInfo = initRetryInfo(); + } + return x; + }); +} + async function processPurchasePayImpl( ws: InternalWalletState, proposalId: string, + forceNow: boolean, ): Promise { + if (forceNow) { + await resetPurchasePayRetry(ws, proposalId); + } const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId); if (!purchase) { return; @@ -1220,19 +1268,34 @@ async function processPurchasePayImpl( export async function processPurchaseQueryRefund( ws: InternalWalletState, proposalId: string, + forceNow: boolean = false, ): Promise { const onOpErr = (e: OperationError) => incrementPurchaseQueryRefundRetry(ws, proposalId, e); await guardOperationException( - () => processPurchaseQueryRefundImpl(ws, proposalId), + () => processPurchaseQueryRefundImpl(ws, proposalId, forceNow), onOpErr, ); } + +async function resetPurchaseQueryRefundRetry(ws: InternalWalletState, proposalId: string) { + await oneShotMutate(ws.db, Stores.purchases, proposalId, x => { + if (x.refundStatusRetryInfo.active) { + x.refundStatusRetryInfo = initRetryInfo(); + } + return x; + }); +} + async function processPurchaseQueryRefundImpl( ws: InternalWalletState, proposalId: string, + forceNow: boolean, ): Promise { + if (forceNow) { + await resetPurchaseQueryRefundRetry(ws, proposalId); + } const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId); if (!purchase) { return; @@ -1262,19 +1325,33 @@ async function processPurchaseQueryRefundImpl( export async function processPurchaseApplyRefund( ws: InternalWalletState, proposalId: string, + forceNow: boolean = false, ): Promise { const onOpErr = (e: OperationError) => incrementPurchaseApplyRefundRetry(ws, proposalId, e); await guardOperationException( - () => processPurchaseApplyRefundImpl(ws, proposalId), + () => processPurchaseApplyRefundImpl(ws, proposalId, forceNow), onOpErr, ); } +async function resetPurchaseApplyRefundRetry(ws: InternalWalletState, proposalId: string) { + await oneShotMutate(ws.db, Stores.purchases, proposalId, x => { + if (x.refundApplyRetryInfo.active) { + x.refundApplyRetryInfo = initRetryInfo(); + } + return x; + }); +} + async function processPurchaseApplyRefundImpl( ws: InternalWalletState, proposalId: string, + forceNow: boolean, ): Promise { + if (forceNow) { + await resetPurchaseApplyRefundRetry(ws, proposalId); + } const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId); if (!purchase) { console.error("not submitting refunds, payment not found:"); diff --git a/src/wallet-impl/refresh.ts b/src/wallet-impl/refresh.ts index 93be1435d..a23f34324 100644 --- a/src/wallet-impl/refresh.ts +++ b/src/wallet-impl/refresh.ts @@ -38,7 +38,11 @@ import { InternalWalletState } from "./state"; import { Logger } from "../util/logging"; import { getWithdrawDenomList } from "./withdraw"; import { updateExchangeFromUrl } from "./exchanges"; -import { getTimestampNow, OperationError, NotificationType } from "../walletTypes"; +import { + getTimestampNow, + OperationError, + NotificationType, +} from "../walletTypes"; import { guardOperationException } from "./errors"; const logger = new Logger("refresh.ts"); @@ -315,25 +319,41 @@ async function incrementRefreshRetry( ws.notify({ type: NotificationType.RefreshOperationError }); } - export async function processRefreshSession( ws: InternalWalletState, refreshSessionId: string, + forceNow: boolean = false, ) { return ws.memoProcessRefresh.memo(refreshSessionId, async () => { const onOpErr = (e: OperationError) => incrementRefreshRetry(ws, refreshSessionId, e); return guardOperationException( - () => processRefreshSessionImpl(ws, refreshSessionId), + () => processRefreshSessionImpl(ws, refreshSessionId, forceNow), onOpErr, ); }); } +async function resetRefreshSessionRetry( + ws: InternalWalletState, + refreshSessionId: string, +) { + await oneShotMutate(ws.db, Stores.refresh, refreshSessionId, (x) => { + if (x.retryInfo.active) { + x.retryInfo = initRetryInfo(); + } + return x; + }); +} + async function processRefreshSessionImpl( ws: InternalWalletState, refreshSessionId: string, + forceNow: boolean, ) { + if (forceNow) { + await resetRefreshSessionRetry(ws, refreshSessionId); + } const refreshSession = await oneShotGet( ws.db, Stores.refresh, @@ -413,7 +433,7 @@ export async function refresh( x.status = CoinStatus.Dormant; return x; }); - ws.notify( { type: NotificationType.RefreshRefused }); + ws.notify({ type: NotificationType.RefreshRefused }); return; } @@ -450,7 +470,7 @@ export async function refresh( }, ); logger.info(`created refresh session ${refreshSession.refreshSessionId}`); - ws.notify( { type: NotificationType.RefreshStarted }); + ws.notify({ type: NotificationType.RefreshStarted }); await processRefreshSession(ws, refreshSession.refreshSessionId); } diff --git a/src/wallet-impl/tip.ts b/src/wallet-impl/tip.ts index 11e029fcd..e11eb3b42 100644 --- a/src/wallet-impl/tip.ts +++ b/src/wallet-impl/tip.ts @@ -128,15 +128,32 @@ async function incrementTipRetry( export async function processTip( ws: InternalWalletState, tipId: string, + forceNow: boolean = false, ): Promise { const onOpErr = (e: OperationError) => incrementTipRetry(ws, tipId, e); - await guardOperationException(() => processTipImpl(ws, tipId), onOpErr); + await guardOperationException(() => processTipImpl(ws, tipId, forceNow), onOpErr); +} + +async function resetTipRetry( + ws: InternalWalletState, + tipId: string, +): Promise { + await oneShotMutate(ws.db, Stores.tips, tipId, (x) => { + if (x.retryInfo.active) { + x.retryInfo = initRetryInfo(); + } + return x; + }) } async function processTipImpl( ws: InternalWalletState, tipId: string, + forceNow: boolean, ) { + if (forceNow) { + await resetTipRetry(ws, tipId); + } let tipRecord = await oneShotGet(ws.db, Stores.tips, tipId); if (!tipRecord) { return; diff --git a/src/wallet-impl/withdraw.ts b/src/wallet-impl/withdraw.ts index 5d89f64a9..96055c9c5 100644 --- a/src/wallet-impl/withdraw.ts +++ b/src/wallet-impl/withdraw.ts @@ -45,6 +45,7 @@ import { oneShotIterIndex, oneShotGetIndexed, runWithWriteTransaction, + oneShotMutate, } from "../util/query"; import { updateExchangeFromUrl, @@ -516,20 +517,37 @@ async function incrementWithdrawalRetry( export async function processWithdrawSession( ws: InternalWalletState, withdrawalSessionId: string, + forceNow: boolean = false, ): Promise { const onOpErr = (e: OperationError) => incrementWithdrawalRetry(ws, withdrawalSessionId, e); await guardOperationException( - () => processWithdrawSessionImpl(ws, withdrawalSessionId), + () => processWithdrawSessionImpl(ws, withdrawalSessionId, forceNow), onOpErr, ); } -export async function processWithdrawSessionImpl( +async function resetWithdrawSessionRetry( ws: InternalWalletState, withdrawalSessionId: string, +) { + await oneShotMutate(ws.db, Stores.withdrawalSession, withdrawalSessionId, (x) => { + if (x.retryInfo.active) { + x.retryInfo = initRetryInfo(); + } + return x; + }); +} + +async function processWithdrawSessionImpl( + ws: InternalWalletState, + withdrawalSessionId: string, + forceNow: boolean, ): Promise { logger.trace("processing withdraw session", withdrawalSessionId); + if (forceNow) { + await resetWithdrawSessionRetry(ws, withdrawalSessionId); + } const withdrawalSession = await oneShotGet( ws.db, Stores.withdrawalSession, diff --git a/src/wallet.ts b/src/wallet.ts index a4fc09f7d..328baf722 100644 --- a/src/wallet.ts +++ b/src/wallet.ts @@ -191,34 +191,34 @@ export class Wallet { await refresh(this.ws, pending.coinPub); break; case "exchange-update": - await updateExchangeFromUrl(this.ws, pending.exchangeBaseUrl); + await updateExchangeFromUrl(this.ws, pending.exchangeBaseUrl, forceNow); break; case "refresh": - await processRefreshSession(this.ws, pending.refreshSessionId); + await processRefreshSession(this.ws, pending.refreshSessionId, forceNow); break; case "reserve": await processReserve(this.ws, pending.reservePub, forceNow); break; case "withdraw": - await processWithdrawSession(this.ws, pending.withdrawSessionId); + await processWithdrawSession(this.ws, pending.withdrawSessionId, forceNow); break; case "proposal-choice": // Nothing to do, user needs to accept/reject break; case "proposal-download": - await processDownloadProposal(this.ws, pending.proposalId); + await processDownloadProposal(this.ws, pending.proposalId, forceNow); break; case "tip": - await processTip(this.ws, pending.tipId); + await processTip(this.ws, pending.tipId, forceNow); break; case "pay": - await processPurchasePay(this.ws, pending.proposalId); + await processPurchasePay(this.ws, pending.proposalId, forceNow); break; case "refund-query": - await processPurchaseQueryRefund(this.ws, pending.proposalId); + await processPurchaseQueryRefund(this.ws, pending.proposalId, forceNow); break; case "refund-apply": - await processPurchaseApplyRefund(this.ws, pending.proposalId); + await processPurchaseApplyRefund(this.ws, pending.proposalId, forceNow); break; default: assertUnreachable(pending); -- cgit v1.2.3