aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2024-04-05 13:29:45 +0200
committerFlorian Dold <florian@dold.me>2024-04-05 13:29:45 +0200
commit7b2f95e482367183ca77f619d9ecbe34d5fd85bd (patch)
tree6ad95fae73143d5af464c96dc4df1e844a6451c9 /packages/taler-wallet-core/src
parent7054acfd0470e13b8fc4c1f08834bc2fcc776ef4 (diff)
downloadwallet-core-7b2f95e482367183ca77f619d9ecbe34d5fd85bd.tar.xz
wallet-core: put exchange in update-unavailable if master pub / currency change unexpectedly
Diffstat (limited to 'packages/taler-wallet-core/src')
-rw-r--r--packages/taler-wallet-core/src/common.ts43
-rw-r--r--packages/taler-wallet-core/src/db.ts2
-rw-r--r--packages/taler-wallet-core/src/exchanges.ts40
3 files changed, 63 insertions, 22 deletions
diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts
index 5b7ceeead..6d116c47e 100644
--- a/packages/taler-wallet-core/src/common.ts
+++ b/packages/taler-wallet-core/src/common.ts
@@ -42,10 +42,8 @@ import {
} from "@gnu-taler/taler-util";
import {
BackupProviderRecord,
- CoinAvailabilityRecord,
CoinRecord,
DbPreciseTimestamp,
- DenominationRecord,
DepositGroupRecord,
ExchangeEntryDbRecordStatus,
ExchangeEntryDbUpdateStatus,
@@ -283,6 +281,8 @@ export function getExchangeUpdateStatusFromRecord(
return ExchangeUpdateStatus.ReadyUpdate;
case ExchangeEntryDbUpdateStatus.Suspended:
return ExchangeUpdateStatus.Suspended;
+ default:
+ assertUnreachable(r.updateStatus);
}
}
@@ -296,6 +296,8 @@ export function getExchangeEntryStatusFromRecord(
return ExchangeEntryStatus.Preset;
case ExchangeEntryDbRecordStatus.Used:
return ExchangeEntryStatus.Used;
+ default:
+ assertUnreachable(r.entryStatus);
}
}
@@ -488,25 +490,28 @@ function updateTimeout(
r.nextRetry = timestampPreciseToDb(TalerPreciseTimestamp.fromMilliseconds(t));
}
-export namespace DbRetryInfo {
- export function getDuration(
- r: DbRetryInfo | undefined,
- p: RetryPolicy = defaultRetryPolicy,
- ): Duration {
- if (!r) {
- // If we don't have any retry info, run immediately.
- return { d_ms: 0 };
- }
- if (p.backoffDelta.d_ms === "forever") {
- return { d_ms: "forever" };
- }
- const t = p.backoffDelta.d_ms * Math.pow(p.backoffBase, r.retryCounter);
- return {
- d_ms:
- p.maxTimeout.d_ms === "forever" ? t : Math.min(p.maxTimeout.d_ms, t),
- };
+export function computeDbBackoff(retryCounter: number): DbPreciseTimestamp {
+ const now = AbsoluteTime.now();
+ if (now.t_ms === "never") {
+ throw Error("assertion failed");
+ }
+ const p = defaultRetryPolicy;
+ if (p.backoffDelta.d_ms === "forever") {
+ throw Error("assertion failed");
}
+ const nextIncrement =
+ p.backoffDelta.d_ms * Math.pow(p.backoffBase, retryCounter);
+
+ const t =
+ now.t_ms +
+ (p.maxTimeout.d_ms === "forever"
+ ? nextIncrement
+ : Math.min(p.maxTimeout.d_ms, nextIncrement));
+ return timestampPreciseToDb(TalerPreciseTimestamp.fromMilliseconds(t));
+}
+
+export namespace DbRetryInfo {
export function reset(p: RetryPolicy = defaultRetryPolicy): DbRetryInfo {
const now = TalerPreciseTimestamp.now();
const info: DbRetryInfo = {
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index 389d82a03..5bab70968 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -674,6 +674,8 @@ export interface ExchangeEntryRecord {
*/
nextUpdateStamp: DbPreciseTimestamp;
+ updateRetryCounter?: number;
+
lastKeysEtag: string | undefined;
/**
diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts
index fa2876e30..cae758a43 100644
--- a/packages/taler-wallet-core/src/exchanges.ts
+++ b/packages/taler-wallet-core/src/exchanges.ts
@@ -102,6 +102,7 @@ import {
TaskRunResult,
TaskRunResultType,
TransactionContext,
+ computeDbBackoff,
constructTaskIdentifier,
getAutoRefreshExecuteThreshold,
getExchangeEntryStatusFromRecord,
@@ -1061,6 +1062,14 @@ async function internalWaitReadyExchange(
ready = true;
}
break;
+ case ExchangeEntryDbUpdateStatus.UnavailableUpdate:
+ throw TalerError.fromDetail(
+ TalerErrorCode.WALLET_EXCHANGE_UNAVAILABLE,
+ {
+ exchangeBaseUrl: canonUrl,
+ innerError: retryInfo?.lastError,
+ },
+ );
default: {
if (retryInfo) {
throw TalerError.fromDetail(
@@ -1285,9 +1294,11 @@ export async function updateExchangeFromUrlHandler(
return TaskRunResult.finished();
case ExchangeEntryDbUpdateStatus.InitialUpdate:
case ExchangeEntryDbUpdateStatus.ReadyUpdate:
- case ExchangeEntryDbUpdateStatus.UnavailableUpdate:
updateRequestedExplicitly = true;
break;
+ case ExchangeEntryDbUpdateStatus.UnavailableUpdate:
+ // Only retry when scheduled to respect backoff
+ break;
case ExchangeEntryDbUpdateStatus.Ready:
break;
default:
@@ -1407,8 +1418,6 @@ export async function updateExchangeFromUrlHandler(
logger.trace("updating exchange info in database");
- let detailsPointerChanged = false;
-
let ageMask = 0;
for (const x of keysInfo.currentDenominations) {
if (
@@ -1442,18 +1451,42 @@ export async function updateExchangeFromUrlHandler(
}
const oldExchangeState = getExchangeState(r);
const existingDetails = await getExchangeRecordsInternal(tx, r.baseUrl);
+ let detailsPointerChanged = false;
if (!existingDetails) {
detailsPointerChanged = true;
}
+ let detailsIncompatible = false;
if (existingDetails) {
if (existingDetails.masterPublicKey !== keysInfo.masterPublicKey) {
+ detailsIncompatible = true;
detailsPointerChanged = true;
}
if (existingDetails.currency !== keysInfo.currency) {
+ detailsIncompatible = true;
detailsPointerChanged = true;
}
// FIXME: We need to do some consistency checks!
}
+ if (detailsIncompatible) {
+ logger.warn(
+ `exchange ${r.baseUrl} has incompatible data in /keys, not updating`,
+ );
+ // We don't support this gracefully right now.
+ // See https://bugs.taler.net/n/8576
+ r.updateStatus = ExchangeEntryDbUpdateStatus.UnavailableUpdate;
+ r.updateRetryCounter = (r.updateRetryCounter ?? 0) + 1;
+ r.nextUpdateStamp = computeDbBackoff(r.updateRetryCounter);
+ r.nextRefreshCheckStamp = timestampPreciseToDb(
+ AbsoluteTime.toPreciseTimestamp(AbsoluteTime.never()),
+ );
+ r.cachebreakNextUpdate = true;
+ await tx.exchanges.put(r);
+ return {
+ oldExchangeState,
+ newExchangeState: getExchangeState(r),
+ };
+ }
+ r.updateRetryCounter = 0;
const newDetails: ExchangeDetailsRecord = {
auditors: keysInfo.auditors,
currency: keysInfo.currency,
@@ -1488,6 +1521,7 @@ export async function updateExchangeFromUrlHandler(
updateClock: timestampPreciseToDb(TalerPreciseTimestamp.now()),
};
}
+
r.updateStatus = ExchangeEntryDbUpdateStatus.Ready;
r.cachebreakNextUpdate = false;
await tx.exchanges.put(r);