diff options
author | Florian Dold <florian@dold.me> | 2024-04-05 13:29:45 +0200 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2024-04-05 13:29:45 +0200 |
commit | 7b2f95e482367183ca77f619d9ecbe34d5fd85bd (patch) | |
tree | 6ad95fae73143d5af464c96dc4df1e844a6451c9 /packages/taler-wallet-core/src | |
parent | 7054acfd0470e13b8fc4c1f08834bc2fcc776ef4 (diff) | |
download | wallet-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.ts | 43 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/db.ts | 2 | ||||
-rw-r--r-- | packages/taler-wallet-core/src/exchanges.ts | 40 |
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); |