taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 5a19d41cc1f18aaff9cea37acfdff14f9a972d1a
parent c164ffa9fde655f845787c7c6622f36a830cd20e
Author: Florian Dold <dold@taler.net>
Date:   Thu, 23 Jul 2026 01:01:03 +0200

wallet: retry and cancel peer-payment network requests

Diffstat:
Mpackages/taler-wallet-core/src/pay-peer-pull-credit.ts | 16++++++++++++++--
Mpackages/taler-wallet-core/src/pay-peer-pull-debit.ts | 27+++++++++++++++++++++++++--
Mpackages/taler-wallet-core/src/pay-peer-push-credit.ts | 50++++++++++++++++++++++++++++++++++++++++++++------
3 files changed, 83 insertions(+), 10 deletions(-)

diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts @@ -78,7 +78,7 @@ import { WalletDbTransaction } from "./dbtx.js"; import { BalanceThresholdCheckResult, checkIncomingAmountLegalUnderKycBalanceThreshold, - fetchFreshExchange, + fetchFreshExchangeWithRetryNow, getPreferredExchangeForCurrency, getScopeForAllExchanges, handleStartExchangeWalletKyc, @@ -1147,6 +1147,18 @@ export async function initiatePeerPullPayment( wex: WalletExecutionContext, req: InitiatePeerPullCreditRequest, ): Promise<InitiatePeerPullCreditResponse> { + return runWithMaybeProgressContext( + wex, + "initiatePeerPullCredit", + req.progressToken, + () => internalInitiatePeerPullPayment(wex, req), + ); +} + +async function internalInitiatePeerPullPayment( + wex: WalletExecutionContext, + req: InitiatePeerPullCreditRequest, +): Promise<InitiatePeerPullCreditResponse> { const currency = Amounts.currencyOf(req.partialContractTerms.amount); let maybeExchangeBaseUrl: string | undefined; if (req.exchangeBaseUrl) { @@ -1161,7 +1173,7 @@ export async function initiatePeerPullPayment( const exchangeBaseUrl = maybeExchangeBaseUrl; - const exchange = await fetchFreshExchange(wex, exchangeBaseUrl); + const exchange = await fetchFreshExchangeWithRetryNow(wex, exchangeBaseUrl); requireExchangeTosAcceptedOrThrow(wex, exchange); if ( diff --git a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts @@ -86,6 +86,10 @@ import { } from "./db-common.js"; import { getExchangeScopeInfo, getScopeForAllExchanges } from "./exchanges.js"; import { + runWithMaybeProgressContext, + runWithProgressRetries, +} from "./progress.js"; +import { getTotalPeerPaymentCost, isPurseDeposited, queryCoinInfosForSelection, @@ -821,6 +825,18 @@ export async function preparePeerPullDebit( wex: WalletExecutionContext, req: PreparePeerPullDebitRequest, ): Promise<PreparePeerPullDebitResponse> { + return runWithMaybeProgressContext( + wex, + "preparePeerPullDebit", + req.progressToken, + () => internalPreparePeerPullDebit(wex, req), + ); +} + +async function internalPreparePeerPullDebit( + wex: WalletExecutionContext, + req: PreparePeerPullDebitRequest, +): Promise<PreparePeerPullDebitResponse> { if (!req.talerUri && !req.transactionId) { throw Error("either talerUri or transactionId must be specified"); } @@ -903,7 +919,12 @@ export async function preparePeerPullDebit( const contractPub = encodeCrock(eddsaGetPublic(decodeCrock(contractPriv))); const exchangeClient = walletExchangeClient(exchangeBaseUrl, wex); - const contractResp = await exchangeClient.getContract(contractPub); + // Retried under a progress context: a terminal outcome (NotFound) comes back + // as a result case rather than an exception, so only transient failures are + // retried — the caller still handles NotFound below. + const contractResp = await runWithProgressRetries(wex, () => + exchangeClient.getContract(contractPub), + ); switch (contractResp.case) { case "ok": break; @@ -921,7 +942,9 @@ export async function preparePeerPullDebit( pursePub: pursePub, }); - const resp = await exchangeClient.getPurseStatusAtMerge(pursePub); + const resp = await runWithProgressRetries(wex, () => + exchangeClient.getPurseStatusAtMerge(pursePub), + ); switch (resp.case) { case "ok": break; diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts @@ -80,12 +80,16 @@ import { WalletIndexedDbTransaction } from "./db-indexeddb.js"; import { BalanceThresholdCheckResult, checkIncomingAmountLegalUnderKycBalanceThreshold, - fetchFreshExchange, + fetchFreshExchangeWithRetryNow, getExchangeScopeInfo, getScopeForAllExchanges, handleStartExchangeWalletKyc, } from "./exchanges.js"; import { + runWithMaybeProgressContext, + runWithProgressRetries, +} from "./progress.js"; +import { GenericKycStatusReq, checkPeerCreditHardLimitExceeded, getPeerCreditLimitInfo, @@ -471,6 +475,18 @@ export async function preparePeerPushCredit( wex: WalletExecutionContext, req: PreparePeerPushCreditRequest, ): Promise<PreparePeerPushCreditResponse> { + return runWithMaybeProgressContext( + wex, + "preparePeerPushCredit", + req.progressToken, + () => internalPreparePeerPushCredit(wex, req), + ); +} + +async function internalPreparePeerPushCredit( + wex: WalletExecutionContext, + req: PreparePeerPushCreditRequest, +): Promise<PreparePeerPushCreditResponse> { if (!req.talerUri && !req.transactionId) { throw Error("either talerUri or transactionId must be specified"); } @@ -522,7 +538,7 @@ export async function preparePeerPushCredit( }); if (existing) { - const exchange = await fetchFreshExchange( + const exchange = await fetchFreshExchangeWithRetryNow( wex, existing.existingPushInc.exchangeBaseUrl, ); @@ -555,12 +571,17 @@ export async function preparePeerPushCredit( } const exchangeBaseUrl = uri.exchangeBaseUrl; - const exchange = await fetchFreshExchange(wex, exchangeBaseUrl); + const exchange = await fetchFreshExchangeWithRetryNow(wex, exchangeBaseUrl); const contractPriv = uri.contractPriv; const contractPub = encodeCrock(eddsaGetPublic(decodeCrock(contractPriv))); const exchangeClient = walletExchangeClient(exchangeBaseUrl, wex); - const contractResp = await exchangeClient.getContract(contractPub); + // Retried under a progress context: a terminal outcome (NotFound) comes back + // as a result case rather than an exception, so only transient failures are + // retried — the caller still handles NotFound below. + const contractResp = await runWithProgressRetries(wex, () => + exchangeClient.getContract(contractPub), + ); switch (contractResp.case) { case "ok": break; @@ -579,7 +600,9 @@ export async function preparePeerPushCredit( }); const contractTerms = codecForPeerContractTerms().decode(dec.contractTerms); - const resp = await exchangeClient.getPurseStatusAtDeposit(pursePub); + const resp = await runWithProgressRetries(wex, () => + exchangeClient.getPurseStatusAtDeposit(pursePub), + ); switch (resp.case) { case "ok": break; @@ -1229,6 +1252,18 @@ export async function confirmPeerPushCredit( wex: WalletExecutionContext, req: ConfirmPeerPushCreditRequest, ): Promise<AcceptPeerPushPaymentResponse> { + return runWithMaybeProgressContext( + wex, + "confirmPeerPushCredit", + req.progressToken, + () => internalConfirmPeerPushCredit(wex, req), + ); +} + +async function internalConfirmPeerPushCredit( + wex: WalletExecutionContext, + req: ConfirmPeerPushCreditRequest, +): Promise<AcceptPeerPushPaymentResponse> { const parsedTx = parseTransactionIdentifier(req.transactionId); if (!parsedTx) { throw Error("invalid transaction ID"); @@ -1266,7 +1301,10 @@ export async function confirmPeerPushCredit( const peerInc = res.peerInc; - const exchange = await fetchFreshExchange(wex, peerInc.exchangeBaseUrl); + const exchange = await fetchFreshExchangeWithRetryNow( + wex, + peerInc.exchangeBaseUrl, + ); requireExchangeTosAcceptedOrThrow(wex, exchange); if (checkPeerCreditHardLimitExceeded(exchange, res.contractTerms.amount)) {