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:
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)) {