diff options
author | Christian Blättler <blatc2@bfh.ch> | 2024-06-13 11:35:52 +0200 |
---|---|---|
committer | Christian Blättler <blatc2@bfh.ch> | 2024-06-13 11:35:52 +0200 |
commit | eb964dfae0a12f9a90eb066d610f627538f8997c (patch) | |
tree | 26a6cd74c9a29edce05b2dcd51cf497374bf8e30 /packages/taler-wallet-core/src/refresh.ts | |
parent | 9d0fc80a905e02a0a0b63dd547daac6e7b17fb52 (diff) | |
parent | f9d4ff5b43e48a07ac81d7e7ef800ddb12f5f90a (diff) | |
download | wallet-core-eb964dfae0a12f9a90eb066d610f627538f8997c.tar.xz |
Merge branch 'master' into feature/tokens
Diffstat (limited to 'packages/taler-wallet-core/src/refresh.ts')
-rw-r--r-- | packages/taler-wallet-core/src/refresh.ts | 143 |
1 files changed, 72 insertions, 71 deletions
diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts index 7800967e6..05c65f6b6 100644 --- a/packages/taler-wallet-core/src/refresh.ts +++ b/packages/taler-wallet-core/src/refresh.ts @@ -29,7 +29,6 @@ import { Amounts, amountToPretty, assertUnreachable, - AsyncFlag, checkDbInvariant, codecForCoinHistoryResponse, codecForExchangeMeltResponse, @@ -68,12 +67,14 @@ import { WalletNotification, } from "@gnu-taler/taler-util"; import { + HttpResponse, readSuccessResponseJsonOrThrow, readTalerErrorResponse, throwUnexpectedRequestError, } from "@gnu-taler/taler-util/http"; import { constructTaskIdentifier, + genericWaitForState, makeCoinsVisible, PendingTaskType, TaskIdStr, @@ -386,7 +387,6 @@ async function getCoinAvailabilityForDenom( denom: DenominationInfo, ageRestriction: number, ): Promise<CoinAvailabilityRecord> { - checkDbInvariant(!!denom); let car = await tx.coinAvailability.get([ denom.exchangeBaseUrl, denom.denomPubHash, @@ -537,7 +537,10 @@ async function destroyRefreshSession( denom, oldCoin.maxAge, ); - checkDbInvariant(car.pendingRefreshOutputCount != null); + checkDbInvariant( + car.pendingRefreshOutputCount != null, + `no pendingRefreshOutputCount for denom ${dph}`, + ); car.pendingRefreshOutputCount = car.pendingRefreshOutputCount - refreshSession.newDenoms[i].count; await tx.coinAvailability.put(car); @@ -693,7 +696,7 @@ async function refreshMelt( switch (resp.status) { case HttpStatusCode.NotFound: { const errDetail = await readTalerErrorResponse(resp); - await handleRefreshMeltNotFound(ctx, coinIndex, errDetail); + await handleRefreshMeltNotFound(ctx, coinIndex, resp, errDetail); return; } case HttpStatusCode.Gone: { @@ -898,9 +901,18 @@ async function handleRefreshMeltConflict( async function handleRefreshMeltNotFound( ctx: RefreshTransactionContext, coinIndex: number, + resp: HttpResponse, errDetails: TalerErrorDetail, ): Promise<void> { - // FIXME: Validate the exchange's error response + // Make sure that we only act on a 404 that indicates a final problem + // with the coin. + switch (errDetails.code) { + case TalerErrorCode.EXCHANGE_GENERIC_COIN_UNKNOWN: + case TalerErrorCode.EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN: + break; + default: + throwUnexpectedRequestError(resp, errDetails); + } await ctx.wex.db.runReadWriteTx( { storeNames: [ @@ -1242,7 +1254,10 @@ async function refreshReveal( coin.exchangeBaseUrl, coin.denomPubHash, ); - checkDbInvariant(!!denomInfo); + checkDbInvariant( + !!denomInfo, + `no denom with hash ${coin.denomPubHash}`, + ); const car = await getCoinAvailabilityForDenom( wex, tx, @@ -1252,6 +1267,7 @@ async function refreshReveal( checkDbInvariant( car.pendingRefreshOutputCount != null && car.pendingRefreshOutputCount > 0, + `no pendingRefreshOutputCount for denom ${coin.denomPubHash} age ${coin.maxAge}`, ); car.pendingRefreshOutputCount--; car.freshCoinCount++; @@ -1559,9 +1575,22 @@ async function applyRefreshToOldCoins( coin.denomPubHash, coin.maxAge, ]); - checkDbInvariant(!!coinAv); - checkDbInvariant(coinAv.freshCoinCount > 0); + checkDbInvariant( + !!coinAv, + `no denom info for ${coin.denomPubHash} age ${coin.maxAge}`, + ); + checkDbInvariant( + coinAv.freshCoinCount > 0, + `no fresh coins for ${coin.denomPubHash}`, + ); coinAv.freshCoinCount--; + if (coin.visible) { + if (!coinAv.visibleCoinCount) { + logger.error("coin availability inconsistent"); + } else { + coinAv.visibleCoinCount--; + } + } await tx.coinAvailability.put(coinAv); break; } @@ -1770,7 +1799,7 @@ export async function forceRefresh( ], }, async (tx) => { - let coinPubs: CoinRefreshRequest[] = []; + const coinPubs: CoinRefreshRequest[] = []; for (const c of req.refreshCoinSpecs) { const coin = await tx.coins.get(c.coinPub); if (!coin) { @@ -1782,7 +1811,7 @@ export async function forceRefresh( coin.exchangeBaseUrl, coin.denomPubHash, ); - checkDbInvariant(!!denom); + checkDbInvariant(!!denom, `no denom hash: ${coin.denomPubHash}`); coinPubs.push({ coinPub: c.coinPub, amount: c.amount ?? denom.value, @@ -1818,66 +1847,38 @@ export async function waitRefreshFinal( const ctx = new RefreshTransactionContext(wex, refreshGroupId); wex.taskScheduler.startShepherdTask(ctx.taskId); - // FIXME: Clean up using the new JS "using" / Symbol.dispose syntax. - const refreshNotifFlag = new AsyncFlag(); - // Raise purchaseNotifFlag whenever we get a notification - // about our refresh. - const cancelNotif = wex.ws.addNotificationListener((notif) => { - if ( - notif.type === NotificationType.TransactionStateTransition && - notif.transactionId === ctx.transactionId - ) { - refreshNotifFlag.raise(); - } - }); - const unregisterOnCancelled = wex.cancellationToken.onCancelled(() => { - cancelNotif(); - refreshNotifFlag.raise(); + await genericWaitForState(wex, { + async checkState(): Promise<boolean> { + // Check if refresh is final + const res = await ctx.wex.db.runReadOnlyTx( + { storeNames: ["refreshGroups"] }, + async (tx) => { + return { + rg: await tx.refreshGroups.get(ctx.refreshGroupId), + }; + }, + ); + const { rg } = res; + if (!rg) { + // Must've been deleted, we consider that final. + return true; + } + switch (rg.operationStatus) { + case RefreshOperationStatus.Failed: + case RefreshOperationStatus.Finished: + // Transaction is final + return true; + case RefreshOperationStatus.Pending: + case RefreshOperationStatus.Suspended: + break; + } + return false; + }, + filterNotification(notif): boolean { + return ( + notif.type === NotificationType.TransactionStateTransition && + notif.transactionId === ctx.transactionId + ); + }, }); - - try { - await internalWaitRefreshFinal(ctx, refreshNotifFlag); - } catch (e) { - unregisterOnCancelled(); - cancelNotif(); - } -} - -async function internalWaitRefreshFinal( - ctx: RefreshTransactionContext, - flag: AsyncFlag, -): Promise<void> { - while (true) { - if (ctx.wex.cancellationToken.isCancelled) { - throw Error("cancelled"); - } - - // Check if refresh is final - const res = await ctx.wex.db.runReadOnlyTx( - { storeNames: ["refreshGroups", "operationRetries"] }, - async (tx) => { - return { - rg: await tx.refreshGroups.get(ctx.refreshGroupId), - }; - }, - ); - const { rg } = res; - if (!rg) { - // Must've been deleted, we consider that final. - return; - } - switch (rg.operationStatus) { - case RefreshOperationStatus.Failed: - case RefreshOperationStatus.Finished: - // Transaction is final - return; - case RefreshOperationStatus.Pending: - case RefreshOperationStatus.Suspended: - break; - } - - // Wait for the next transition - await flag.wait(); - flag.reset(); - } } |