diff options
Diffstat (limited to 'packages/taler-wallet-core/src/operations/refresh.ts')
-rw-r--r-- | packages/taler-wallet-core/src/operations/refresh.ts | 287 |
1 files changed, 148 insertions, 139 deletions
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index 390433f66..fc2508cd3 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -98,6 +98,8 @@ import { makeCoinsVisible, TaskRunResult, TaskRunResultType, + TombstoneTag, + TransactionContext, } from "./common.js"; import { fetchFreshExchange } from "./exchanges.js"; import { @@ -107,6 +109,152 @@ import { const logger = new Logger("refresh.ts"); +export class RefreshTransactionContext implements TransactionContext { + public transactionId: string; + + constructor( + public ws: InternalWalletState, + public refreshGroupId: string, + ) { + this.transactionId = constructTransactionIdentifier({ + tag: TransactionType.Refresh, + refreshGroupId, + }); + } + + async deleteTransaction(): Promise<void> { + const refreshGroupId = this.refreshGroupId; + const ws = this.ws; + await ws.db + .mktx((x) => [x.refreshGroups, x.tombstones]) + .runReadWrite(async (tx) => { + const rg = await tx.refreshGroups.get(refreshGroupId); + if (rg) { + await tx.refreshGroups.delete(refreshGroupId); + await tx.tombstones.put({ + id: TombstoneTag.DeleteRefreshGroup + ":" + refreshGroupId, + }); + } + }); + } + + async suspendTransaction(): Promise<void> { + const { ws, refreshGroupId, transactionId } = this; + let res = await ws.db + .mktx((x) => [x.refreshGroups]) + .runReadWrite(async (tx) => { + const dg = await tx.refreshGroups.get(refreshGroupId); + if (!dg) { + logger.warn( + `can't suspend refresh group, refreshGroupId=${refreshGroupId} not found`, + ); + return undefined; + } + const oldState = computeRefreshTransactionState(dg); + switch (dg.operationStatus) { + case RefreshOperationStatus.Finished: + return undefined; + case RefreshOperationStatus.Pending: { + dg.operationStatus = RefreshOperationStatus.Suspended; + await tx.refreshGroups.put(dg); + return { + oldTxState: oldState, + newTxState: computeRefreshTransactionState(dg), + }; + } + case RefreshOperationStatus.Suspended: + return undefined; + } + return undefined; + }); + if (res) { + ws.notify({ + type: NotificationType.TransactionStateTransition, + transactionId, + oldTxState: res.oldTxState, + newTxState: res.newTxState, + }); + } + } + + async abortTransaction(): Promise<void> { + // Refresh transactions only support fail, not abort. + throw new Error("refresh transactions cannot be aborted"); + } + + async resumeTransaction(): Promise<void> { + const { ws, refreshGroupId, transactionId } = this; + const transitionInfo = await ws.db + .mktx((x) => [x.refreshGroups]) + .runReadWrite(async (tx) => { + const dg = await tx.refreshGroups.get(refreshGroupId); + if (!dg) { + logger.warn( + `can't resume refresh group, refreshGroupId=${refreshGroupId} not found`, + ); + return; + } + const oldState = computeRefreshTransactionState(dg); + switch (dg.operationStatus) { + case RefreshOperationStatus.Finished: + return; + case RefreshOperationStatus.Pending: { + return; + } + case RefreshOperationStatus.Suspended: + dg.operationStatus = RefreshOperationStatus.Pending; + await tx.refreshGroups.put(dg); + return { + oldTxState: oldState, + newTxState: computeRefreshTransactionState(dg), + }; + } + return undefined; + }); + ws.workAvailable.trigger(); + notifyTransition(ws, transactionId, transitionInfo); + } + + async failTransaction(): Promise<void> { + const { ws, refreshGroupId, transactionId } = this; + const transitionInfo = await ws.db + .mktx((x) => [x.refreshGroups]) + .runReadWrite(async (tx) => { + const dg = await tx.refreshGroups.get(refreshGroupId); + if (!dg) { + logger.warn( + `can't resume refresh group, refreshGroupId=${refreshGroupId} not found`, + ); + return; + } + const oldState = computeRefreshTransactionState(dg); + let newStatus: RefreshOperationStatus | undefined; + switch (dg.operationStatus) { + case RefreshOperationStatus.Finished: + break; + case RefreshOperationStatus.Pending: + case RefreshOperationStatus.Suspended: + newStatus = RefreshOperationStatus.Failed; + break; + case RefreshOperationStatus.Failed: + break; + default: + assertUnreachable(dg.operationStatus); + } + if (newStatus) { + dg.operationStatus = newStatus; + await tx.refreshGroups.put(dg); + } + return { + oldTxState: oldState, + newTxState: computeRefreshTransactionState(dg), + }; + }); + ws.workAvailable.trigger(); + notifyTransition(ws, transactionId, transitionInfo); + } +} + /** * Get the amount that we lose when refreshing a coin of the given denomination * with a certain amount left. @@ -1256,9 +1404,6 @@ export async function autoRefresh( `created refresh group for auto-refresh (${res.refreshGroupId})`, ); } - // logger.trace( - // `current wallet time: ${AbsoluteTime.toIsoString(AbsoluteTime.now())}`, - // ); logger.trace( `next refresh check at ${AbsoluteTime.toIsoString(minCheckThreshold)}`, ); @@ -1308,142 +1453,6 @@ export function computeRefreshTransactionActions( } } -export async function suspendRefreshGroup( - ws: InternalWalletState, - refreshGroupId: string, -): Promise<void> { - const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Refresh, - refreshGroupId, - }); - let res = await ws.db - .mktx((x) => [x.refreshGroups]) - .runReadWrite(async (tx) => { - const dg = await tx.refreshGroups.get(refreshGroupId); - if (!dg) { - logger.warn( - `can't suspend refresh group, refreshGroupId=${refreshGroupId} not found`, - ); - return undefined; - } - const oldState = computeRefreshTransactionState(dg); - switch (dg.operationStatus) { - case RefreshOperationStatus.Finished: - return undefined; - case RefreshOperationStatus.Pending: { - dg.operationStatus = RefreshOperationStatus.Suspended; - await tx.refreshGroups.put(dg); - return { - oldTxState: oldState, - newTxState: computeRefreshTransactionState(dg), - }; - } - case RefreshOperationStatus.Suspended: - return undefined; - } - return undefined; - }); - if (res) { - ws.notify({ - type: NotificationType.TransactionStateTransition, - transactionId, - oldTxState: res.oldTxState, - newTxState: res.newTxState, - }); - } -} - -export async function resumeRefreshGroup( - ws: InternalWalletState, - refreshGroupId: string, -): Promise<void> { - const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Refresh, - refreshGroupId, - }); - const transitionInfo = await ws.db - .mktx((x) => [x.refreshGroups]) - .runReadWrite(async (tx) => { - const dg = await tx.refreshGroups.get(refreshGroupId); - if (!dg) { - logger.warn( - `can't resume refresh group, refreshGroupId=${refreshGroupId} not found`, - ); - return; - } - const oldState = computeRefreshTransactionState(dg); - switch (dg.operationStatus) { - case RefreshOperationStatus.Finished: - return; - case RefreshOperationStatus.Pending: { - return; - } - case RefreshOperationStatus.Suspended: - dg.operationStatus = RefreshOperationStatus.Pending; - await tx.refreshGroups.put(dg); - return { - oldTxState: oldState, - newTxState: computeRefreshTransactionState(dg), - }; - } - return undefined; - }); - ws.workAvailable.trigger(); - notifyTransition(ws, transactionId, transitionInfo); -} - -export async function failRefreshGroup( - ws: InternalWalletState, - refreshGroupId: string, -): Promise<void> { - throw Error("action cancel-aborting not allowed on refreshes"); -} - -export async function abortRefreshGroup( - ws: InternalWalletState, - refreshGroupId: string, -): Promise<void> { - const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Refresh, - refreshGroupId, - }); - const transitionInfo = await ws.db - .mktx((x) => [x.refreshGroups]) - .runReadWrite(async (tx) => { - const dg = await tx.refreshGroups.get(refreshGroupId); - if (!dg) { - logger.warn( - `can't resume refresh group, refreshGroupId=${refreshGroupId} not found`, - ); - return; - } - const oldState = computeRefreshTransactionState(dg); - let newStatus: RefreshOperationStatus | undefined; - switch (dg.operationStatus) { - case RefreshOperationStatus.Finished: - break; - case RefreshOperationStatus.Pending: - case RefreshOperationStatus.Suspended: - newStatus = RefreshOperationStatus.Failed; - break; - case RefreshOperationStatus.Failed: - break; - default: - assertUnreachable(dg.operationStatus); - } - if (newStatus) { - dg.operationStatus = newStatus; - await tx.refreshGroups.put(dg); - } - return { - oldTxState: oldState, - newTxState: computeRefreshTransactionState(dg), - }; - }); - ws.workAvailable.trigger(); - notifyTransition(ws, transactionId, transitionInfo); -} - export async function forceRefresh( ws: InternalWalletState, req: ForceRefreshRequest, |