commit 1776039b6f6cb9c896810f01d37b63ddc7b6cbf0
parent 5f00bcf20b16940f6761a9ec58117ea7ff665ada
Author: Florian Dold <dold@taler.net>
Date: Thu, 3 Sep 2026 15:47:37 +0200
wallet-core: skip coins without a denomination in availability
Such coins exist after a denomination was retired or cleaned up;
throwing on them made every wallet start fail.
Diffstat:
1 file changed, 16 insertions(+), 5 deletions(-)
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
@@ -418,7 +418,7 @@ async function getOrCreateCoinAvailabilityForRematerialization(
availabilities: WalletCoinAvailability[],
byRef: Map<string, WalletCoinAvailability>,
ref: WalletCoinAvailabilityRef,
-): Promise<WalletCoinAvailability> {
+): Promise<WalletCoinAvailability | undefined> {
const key = coinAvailabilityRefKey(ref);
const existing = byRef.get(key);
if (existing) {
@@ -426,10 +426,14 @@ async function getOrCreateCoinAvailabilityForRematerialization(
}
const denom = await tx.getDenomination(ref);
if (!denom) {
- throw Error(
+ // A coin can outlive its denomination record (the exchange retired it,
+ // or a cleanup removed it). It is not spendable without one, so it is
+ // not counted; the rebuild must not fail on it.
+ logger.warn(
`denomination ${ref.denomPubHash} is missing while rebuilding` +
- ` coin availability for age ${ref.maxAge}`,
+ ` coin availability for age ${ref.maxAge}, skipping`,
);
+ return undefined;
}
const availability: WalletCoinAvailability = {
maxAge: ref.maxAge,
@@ -482,6 +486,9 @@ export async function recomputeCoinAvailability(
byRef,
coin,
);
+ if (!availability) {
+ continue;
+ }
availability.freshCoinCount++;
if (coin.visible) {
availability.visibleCoinCount++;
@@ -509,9 +516,10 @@ export async function recomputeCoinAvailability(
const oldCoinPub = group.oldCoinPubs[session.coinIndex];
const oldCoin = coinsByPub.get(oldCoinPub);
if (!oldCoin) {
- throw Error(
- `old coin ${oldCoinPub} missing for refresh ${session.refreshGroupId}`,
+ logger.warn(
+ `old coin ${oldCoinPub} missing for refresh ${session.refreshGroupId}, skipping`,
);
+ continue;
}
for (const output of session.newDenoms) {
const availability =
@@ -525,6 +533,9 @@ export async function recomputeCoinAvailability(
maxAge: oldCoin.maxAge,
},
);
+ if (!availability) {
+ continue;
+ }
availability.pendingRefreshOutputCount =
(availability.pendingRefreshOutputCount ?? 0) + output.count;
}