diff options
Diffstat (limited to 'packages/taler-wallet-core/src/operations')
4 files changed, 78 insertions, 34 deletions
diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts b/packages/taler-wallet-core/src/operations/backup/export.ts index 4d9ca6697..0410ab3af 100644 --- a/packages/taler-wallet-core/src/operations/backup/export.ts +++ b/packages/taler-wallet-core/src/operations/backup/export.ts @@ -66,6 +66,7 @@ import { CoinSourceType, CoinStatus, ProposalStatus, + RefreshCoinStatus, RefundState, WALLET_BACKUP_STATE_KEY, } from "../../db.js"; @@ -440,7 +441,7 @@ export async function exportBackup( estimated_output_amount: Amounts.stringify( rg.estimatedOutputPerCoin[i], ), - finished: rg.finishedPerCoin[i], + finished: rg.statusPerCoin[i] === RefreshCoinStatus.Finished, input_amount: Amounts.stringify(rg.inputPerCoin[i]), refresh_session: refreshSession, }); diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts index 8ba4e4db3..a694d9f4d 100644 --- a/packages/taler-wallet-core/src/operations/backup/import.ts +++ b/packages/taler-wallet-core/src/operations/backup/import.ts @@ -45,6 +45,7 @@ import { RefreshSessionRecord, WireInfo, WalletStoresV1, + RefreshCoinStatus, } from "../../db.js"; import { PayCoinSelection } from "../../util/coinSelection.js"; import { j2s } from "@gnu-taler/taler-util"; @@ -831,8 +832,10 @@ export async function importBackup( lastError: undefined, lastErrorPerCoin: {}, oldCoinPubs: backupRefreshGroup.old_coins.map((x) => x.coin_pub), - finishedPerCoin: backupRefreshGroup.old_coins.map( - (x) => x.finished, + statusPerCoin: backupRefreshGroup.old_coins.map((x) => + x.finished + ? RefreshCoinStatus.Finished + : RefreshCoinStatus.Pending, ), inputPerCoin: backupRefreshGroup.old_coins.map((x) => Amounts.parseOrThrow(x.input_amount), diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts index 200e6ccbd..a4ca972a7 100644 --- a/packages/taler-wallet-core/src/operations/pending.ts +++ b/packages/taler-wallet-core/src/operations/pending.ts @@ -27,6 +27,7 @@ import { AbortStatus, WalletStoresV1, BackupProviderStateTag, + RefreshCoinStatus, } from "../db.js"; import { PendingOperationsResponse, @@ -111,12 +112,17 @@ async function gatherRefreshPending( if (r.timestampFinished) { return; } + if (r.frozen) { + return; + } resp.pendingOperations.push({ type: PendingTaskType.Refresh, givesLifeness: true, timestampDue: r.retryInfo.nextRetry, refreshGroupId: r.refreshGroupId, - finishedPerCoin: r.finishedPerCoin, + finishedPerCoin: r.statusPerCoin.map( + (x) => x === RefreshCoinStatus.Finished, + ), retryInfo: r.retryInfo, }); }); diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index 5c4ed4f70..8926559e3 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -20,6 +20,7 @@ import { CoinSourceType, CoinStatus, DenominationRecord, + RefreshCoinStatus, RefreshGroupRecord, RefreshPlanchet, WalletStoresV1, @@ -28,6 +29,7 @@ import { codecForExchangeMeltResponse, codecForExchangeRevealResponse, CoinPublicKey, + fnutil, NotificationType, RefreshGroupId, RefreshReason, @@ -37,7 +39,11 @@ import { } from "@gnu-taler/taler-util"; import { AmountJson, Amounts } from "@gnu-taler/taler-util"; import { amountToPretty } from "@gnu-taler/taler-util"; -import { readSuccessResponseJsonOrThrow } from "../util/http.js"; +import { + HttpResponseStatus, + readSuccessResponseJsonOrThrow, + readUnexpectedResponseDetails, +} from "../util/http.js"; import { checkDbInvariant } from "../util/invariants.js"; import { Logger } from "@gnu-taler/taler-util"; import { initRetryInfo, updateRetryInfoTimeout } from "../util/retries.js"; @@ -99,6 +105,26 @@ export function getTotalRefreshCost( return totalCost; } +function updateGroupStatus(rg: RefreshGroupRecord): void { + let allDone = fnutil.all( + rg.statusPerCoin, + (x) => x === RefreshCoinStatus.Finished || x === RefreshCoinStatus.Frozen, + ); + let anyFrozen = fnutil.any( + rg.statusPerCoin, + (x) => x === RefreshCoinStatus.Frozen, + ); + if (allDone) { + if (anyFrozen) { + rg.frozen = true; + rg.retryInfo = initRetryInfo(); + } else { + rg.timestampFinished = getTimestampNow(); + rg.retryInfo = initRetryInfo(); + } + } +} + /** * Create a refresh session for one particular coin inside a refresh group. */ @@ -121,7 +147,9 @@ async function refreshCreateSession( if (!refreshGroup) { return; } - if (refreshGroup.finishedPerCoin[coinIndex]) { + if ( + refreshGroup.statusPerCoin[coinIndex] === RefreshCoinStatus.Finished + ) { return; } const existingRefreshSession = @@ -211,18 +239,9 @@ async function refreshCreateSession( if (!rg) { return; } - rg.finishedPerCoin[coinIndex] = true; - let allDone = true; - for (const f of rg.finishedPerCoin) { - if (!f) { - allDone = false; - break; - } - } - if (allDone) { - rg.timestampFinished = getTimestampNow(); - rg.retryInfo = initRetryInfo(); - } + rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Finished; + updateGroupStatus(rg); + await tx.refreshGroups.put(rg); }); ws.notify({ type: NotificationType.RefreshUnwarranted }); @@ -358,6 +377,31 @@ async function refreshMelt( }); }); + if (resp.status === HttpResponseStatus.NotFound) { + const errDetails = await readUnexpectedResponseDetails(resp); + await ws.db + .mktx((x) => ({ + refreshGroups: x.refreshGroups, + })) + .runReadWrite(async (tx) => { + const rg = await tx.refreshGroups.get(refreshGroupId); + if (!rg) { + return; + } + if (rg.timestampFinished) { + return; + } + if (rg.statusPerCoin[coinIndex] !== RefreshCoinStatus.Pending) { + return; + } + rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Frozen; + rg.lastErrorPerCoin[coinIndex] = errDetails; + updateGroupStatus(rg); + await tx.refreshGroups.put(rg); + }); + return; + } + const meltResponse = await readSuccessResponseJsonOrThrow( resp, codecForExchangeMeltResponse(), @@ -598,18 +642,8 @@ async function refreshReveal( if (!rs) { return; } - rg.finishedPerCoin[coinIndex] = true; - let allDone = true; - for (const f of rg.finishedPerCoin) { - if (!f) { - allDone = false; - break; - } - } - if (allDone) { - rg.timestampFinished = getTimestampNow(); - rg.retryInfo = initRetryInfo(); - } + rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Finished; + updateGroupStatus(rg); for (const coin of coins) { await tx.coins.put(coin); } @@ -728,7 +762,7 @@ async function processRefreshSession( if (!refreshGroup) { return; } - if (refreshGroup.finishedPerCoin[coinIndex]) { + if (refreshGroup.statusPerCoin[coinIndex] === RefreshCoinStatus.Finished) { return; } if (!refreshGroup.refreshSessionPerCoin[coinIndex]) { @@ -744,7 +778,7 @@ async function processRefreshSession( } const refreshSession = refreshGroup.refreshSessionPerCoin[coinIndex]; if (!refreshSession) { - if (!refreshGroup.finishedPerCoin[coinIndex]) { + if (refreshGroup.statusPerCoin[coinIndex] !== RefreshCoinStatus.Finished) { throw Error( "BUG: refresh session was not created and coin not marked as finished", ); @@ -826,13 +860,13 @@ export async function createRefreshGroup( const refreshGroup: RefreshGroupRecord = { timestampFinished: undefined, - finishedPerCoin: oldCoinPubs.map((x) => false), + statusPerCoin: oldCoinPubs.map(() => RefreshCoinStatus.Pending), lastError: undefined, lastErrorPerCoin: {}, oldCoinPubs: oldCoinPubs.map((x) => x.coinPub), reason, refreshGroupId, - refreshSessionPerCoin: oldCoinPubs.map((x) => undefined), + refreshSessionPerCoin: oldCoinPubs.map(() => undefined), retryInfo: initRetryInfo(), inputPerCoin, estimatedOutputPerCoin, |