commit 886d86262fb909f0939dfd95118846760da5d57d
parent 08dbab158e9e38cfc3fb95125cbcb24f28512955
Author: Florian Dold <florian@dold.me>
Date: Thu, 19 Mar 2026 23:04:47 +0100
wallet-core: fix/simplify tx retry logic
Diffstat:
1 file changed, 25 insertions(+), 36 deletions(-)
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
@@ -489,6 +489,11 @@ export interface WalletExecutionContext {
readonly oc: ObservabilityContext;
readonly cts: CancellationToken.Source | undefined;
readonly taskScheduler: TaskScheduler;
+ readonly dbRetryState: DbRetryState;
+}
+
+export interface DbRetryState {
+ retriedExchangeUpdate?: Set<string>;
}
export function walletExchangeClient(
@@ -2775,6 +2780,9 @@ export function getObservedWalletExecutionContext(
cts,
cryptoApi: observeTalerCrypto(ws.cryptoApi, oc),
db: new ObservableDbAccess(db, oc),
+ dbRetryState: {
+ retriedExchangeUpdate: new Set(),
+ },
http: new ObservableHttpClientLibrary(ws.http, oc),
taskScheduler: new ObservableTaskScheduler(ws.taskScheduler, oc),
oc,
@@ -2802,7 +2810,6 @@ async function handleTxRetries<T>(
wex: WalletExecutionContext,
f: () => Promise<T>,
): Promise<T> {
- const coveredExchanges = new Set<string>();
while (1) {
wex.cancellationToken.throwIfCancelled();
try {
@@ -2812,48 +2819,29 @@ async function handleTxRetries<T>(
if (exn == null) {
throw e;
}
+ let url: string | undefined = undefined;
if (exn instanceof UnverifiedDenomError) {
- const url = exn.denomInfo.exchangeBaseUrl;
+ url = exn.denomInfo.exchangeBaseUrl;
logger.info(`got unverified denominations, updating ${url}`);
- if (coveredExchanges.has(url)) {
- logger.error(`exchange was already covered, giving up`);
- throw e;
- }
- // Make sure this doesn't recurse
- if (wex.ws.disableTransactionRetries) {
- throw e;
- }
- try {
- wex.ws.disableTransactionRetries = true;
- await fetchFreshExchange(wex, url);
- await updateWithdrawalDenomsForExchange(wex, url);
- coveredExchanges.add(url);
- } catch (e) {
- logger.error(
- `Exception thrown while trying to heal UnverifiedDenomError`,
- );
- throw e;
- } finally {
- wex.ws.disableTransactionRetries = true;
- }
} else if (exn instanceof OutdatedExchangeError) {
const url = exn.exchangeBaseUrl;
logger.info(`got outdated exchange entry, updating ${url}`);
- // Make sure this doesn't recurse
- if (wex.ws.disableTransactionRetries) {
- throw e;
+ }
+ if (url) {
+ if (!wex.dbRetryState.retriedExchangeUpdate) {
+ wex.dbRetryState.retriedExchangeUpdate = new Set();
}
- try {
- wex.ws.disableTransactionRetries = true;
- await fetchFreshExchange(wex, url);
- } catch (e) {
+ if (wex.dbRetryState.retriedExchangeUpdate.has(url)) {
logger.error(
- `Exception thrown while trying to heal UnverifiedDenomError`,
+ `updating exchange already failed in execution context, not retrying`,
);
- throw e;
- } finally {
- wex.ws.disableTransactionRetries = true;
+ throw exn;
}
+ // Prevent both recursion and multiple updates
+ // per wallet execution context.
+ wex.dbRetryState.retriedExchangeUpdate.add(url);
+ await fetchFreshExchange(wex, url);
+ await updateWithdrawalDenomsForExchange(wex, url);
}
}
}
@@ -2908,6 +2896,9 @@ export function getNormalWalletExecutionContext(
cts,
cryptoApi: ws.cryptoApi,
db,
+ dbRetryState: {
+ retriedExchangeUpdate: new Set(),
+ },
get http() {
if (ws.initCalled) {
return ws.http;
@@ -3360,8 +3351,6 @@ export class InternalWalletState {
*/
refcntIgnoreTos: number = 0;
- disableTransactionRetries: boolean = false;
-
clearAllCaches(): void {
this.exchangeCache.clear();
this.denomInfoCache.clear();