From 12a9b08c6f8c31f684239a30fc39acc9189c6571 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 18 Dec 2023 19:25:26 +0100 Subject: wallet-core: towards properly handling peer-pull-debit expiry --- .../src/operations/pay-peer-push-debit.ts | 168 +++++++++++++++++---- .../taler-wallet-core/src/operations/pending.ts | 2 +- .../taler-wallet-core/src/operations/refresh.ts | 23 +++ 3 files changed, 166 insertions(+), 27 deletions(-) (limited to 'packages/taler-wallet-core/src/operations') diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts index da779a07d..c79aca1ad 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts @@ -365,7 +365,7 @@ async function processPeerPushDebitAbortingDeletePurse( coinPubs, RefreshReason.AbortPeerPushDebit, ); - ppiRec.status = PeerPushDebitStatus.AbortingRefresh; + ppiRec.status = PeerPushDebitStatus.AbortingRefreshDeleted; ppiRec.abortRefreshGroupId = refresh.refreshGroupId; await tx.peerPushDebit.put(ppiRec); const newTxState = computePeerPushDebitTransactionState(ppiRec); @@ -415,7 +415,7 @@ async function transitionPeerPushDebitTransaction( notifyTransition(ws, transactionId, transitionInfo); } -async function processPeerPushDebitAbortingRefresh( +async function processPeerPushDebitAbortingRefreshDeleted( ws: InternalWalletState, peerPushInitiation: PeerPushDebitRecord, ): Promise { @@ -463,6 +463,54 @@ async function processPeerPushDebitAbortingRefresh( return TaskRunResult.pending(); } +async function processPeerPushDebitAbortingRefreshExpired( + ws: InternalWalletState, + peerPushInitiation: PeerPushDebitRecord, +): Promise { + const pursePub = peerPushInitiation.pursePub; + const abortRefreshGroupId = peerPushInitiation.abortRefreshGroupId; + checkLogicInvariant(!!abortRefreshGroupId); + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.PeerPushDebit, + pursePub: peerPushInitiation.pursePub, + }); + const transitionInfo = await ws.db + .mktx((x) => [x.refreshGroups, x.peerPushDebit]) + .runReadWrite(async (tx) => { + const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId); + let newOpState: PeerPushDebitStatus | undefined; + if (!refreshGroup) { + // Maybe it got manually deleted? Means that we should + // just go into failed. + logger.warn("no aborting refresh group found for deposit group"); + newOpState = PeerPushDebitStatus.Failed; + } else { + if (refreshGroup.operationStatus === RefreshOperationStatus.Finished) { + newOpState = PeerPushDebitStatus.Expired; + } else if ( + refreshGroup.operationStatus === RefreshOperationStatus.Failed + ) { + newOpState = PeerPushDebitStatus.Failed; + } + } + if (newOpState) { + const newDg = await tx.peerPushDebit.get(pursePub); + if (!newDg) { + return; + } + const oldTxState = computePeerPushDebitTransactionState(newDg); + newDg.status = newOpState; + const newTxState = computePeerPushDebitTransactionState(newDg); + await tx.peerPushDebit.put(newDg); + return { oldTxState, newTxState }; + } + return undefined; + }); + notifyTransition(ws, transactionId, transitionInfo); + // FIXME: Shouldn't this be finished in some cases?! + return TaskRunResult.pending(); +} + /** * Process the "pending(ready)" state of a peer-push-debit transaction. */ @@ -476,6 +524,10 @@ async function processPeerPushDebitReady( tag: PendingTaskType.PeerPushDebit, pursePub, }); + const transactionId = constructTaskIdentifier({ + tag: PendingTaskType.PeerPushDebit, + pursePub, + }); runLongpollAsync(ws, retryTag, async (ct) => { const mergeUrl = new URL( `purses/${pursePub}/merge`, @@ -510,14 +562,50 @@ async function processPeerPushDebitReady( }; } } else if (resp.status === HttpStatusCode.Gone) { - await transitionPeerPushDebitTransaction( - ws, - peerPushInitiation.pursePub, - { - stFrom: PeerPushDebitStatus.PendingReady, - stTo: PeerPushDebitStatus.Expired, - }, - ); + const transitionInfo = await ws.db + .mktx((x) => [ + x.peerPushDebit, + x.refreshGroups, + x.denominations, + x.coinAvailability, + x.coins, + ]) + .runReadWrite(async (tx) => { + const ppiRec = await tx.peerPushDebit.get(pursePub); + if (!ppiRec) { + return undefined; + } + if (ppiRec.status !== PeerPushDebitStatus.PendingReady) { + return undefined; + } + const currency = Amounts.currencyOf(ppiRec.amount); + const oldTxState = computePeerPushDebitTransactionState(ppiRec); + const coinPubs: CoinRefreshRequest[] = []; + + for (let i = 0; i < ppiRec.coinSel.coinPubs.length; i++) { + coinPubs.push({ + amount: ppiRec.coinSel.contributions[i], + coinPub: ppiRec.coinSel.coinPubs[i], + }); + } + + const refresh = await createRefreshGroup( + ws, + tx, + currency, + coinPubs, + RefreshReason.AbortPeerPushDebit, + ); + ppiRec.status = PeerPushDebitStatus.AbortingRefreshExpired; + ppiRec.abortRefreshGroupId = refresh.refreshGroupId; + await tx.peerPushDebit.put(ppiRec); + const newTxState = computePeerPushDebitTransactionState(ppiRec); + return { + oldTxState, + newTxState, + }; + }); + notifyTransition(ws, transactionId, transitionInfo); return { ready: true, }; @@ -569,8 +657,10 @@ export async function processPeerPushDebit( return processPeerPushDebitReady(ws, peerPushInitiation); case PeerPushDebitStatus.AbortingDeletePurse: return processPeerPushDebitAbortingDeletePurse(ws, peerPushInitiation); - case PeerPushDebitStatus.AbortingRefresh: - return processPeerPushDebitAbortingRefresh(ws, peerPushInitiation); + case PeerPushDebitStatus.AbortingRefreshDeleted: + return processPeerPushDebitAbortingRefreshDeleted(ws, peerPushInitiation); + case PeerPushDebitStatus.AbortingRefreshExpired: + return processPeerPushDebitAbortingRefreshExpired(ws, peerPushInitiation); default: { const txState = computePeerPushDebitTransactionState(peerPushInitiation); logger.warn( @@ -722,11 +812,15 @@ export function computePeerPushDebitTransactionActions( return [TransactionAction.Delete]; case PeerPushDebitStatus.AbortingDeletePurse: return [TransactionAction.Suspend, TransactionAction.Fail]; - case PeerPushDebitStatus.AbortingRefresh: + case PeerPushDebitStatus.AbortingRefreshDeleted: return [TransactionAction.Suspend, TransactionAction.Fail]; + case PeerPushDebitStatus.AbortingRefreshExpired: + return [TransactionAction.Resume, TransactionAction.Fail]; + case PeerPushDebitStatus.SuspendedAbortingRefreshExpired: + return [TransactionAction.Resume, TransactionAction.Fail]; case PeerPushDebitStatus.SuspendedAbortingDeletePurse: return [TransactionAction.Resume, TransactionAction.Fail]; - case PeerPushDebitStatus.SuspendedAbortingRefresh: + case PeerPushDebitStatus.SuspendedAbortingRefreshDeleted: return [TransactionAction.Resume, TransactionAction.Fail]; case PeerPushDebitStatus.SuspendedCreatePurse: return [TransactionAction.Resume, TransactionAction.Abort]; @@ -773,9 +867,11 @@ export async function abortPeerPushDebitTransaction( // Network request might already be in-flight! newStatus = PeerPushDebitStatus.AbortingDeletePurse; break; - case PeerPushDebitStatus.SuspendedAbortingRefresh: + case PeerPushDebitStatus.SuspendedAbortingRefreshDeleted: case PeerPushDebitStatus.SuspendedAbortingDeletePurse: - case PeerPushDebitStatus.AbortingRefresh: + case PeerPushDebitStatus.SuspendedAbortingRefreshExpired: + case PeerPushDebitStatus.AbortingRefreshDeleted: + case PeerPushDebitStatus.AbortingRefreshExpired: case PeerPushDebitStatus.Done: case PeerPushDebitStatus.AbortingDeletePurse: case PeerPushDebitStatus.Aborted: @@ -824,13 +920,15 @@ export async function failPeerPushDebitTransaction( } let newStatus: PeerPushDebitStatus | undefined = undefined; switch (pushDebitRec.status) { - case PeerPushDebitStatus.AbortingRefresh: - case PeerPushDebitStatus.SuspendedAbortingRefresh: + case PeerPushDebitStatus.AbortingRefreshDeleted: + case PeerPushDebitStatus.SuspendedAbortingRefreshDeleted: // FIXME: What to do about the refresh group? newStatus = PeerPushDebitStatus.Failed; break; case PeerPushDebitStatus.AbortingDeletePurse: case PeerPushDebitStatus.SuspendedAbortingDeletePurse: + case PeerPushDebitStatus.AbortingRefreshExpired: + case PeerPushDebitStatus.SuspendedAbortingRefreshExpired: case PeerPushDebitStatus.PendingReady: case PeerPushDebitStatus.SuspendedReady: case PeerPushDebitStatus.SuspendedCreatePurse: @@ -887,8 +985,11 @@ export async function suspendPeerPushDebitTransaction( case PeerPushDebitStatus.PendingCreatePurse: newStatus = PeerPushDebitStatus.SuspendedCreatePurse; break; - case PeerPushDebitStatus.AbortingRefresh: - newStatus = PeerPushDebitStatus.SuspendedAbortingRefresh; + case PeerPushDebitStatus.AbortingRefreshDeleted: + newStatus = PeerPushDebitStatus.SuspendedAbortingRefreshDeleted; + break; + case PeerPushDebitStatus.AbortingRefreshExpired: + newStatus = PeerPushDebitStatus.SuspendedAbortingRefreshExpired; break; case PeerPushDebitStatus.AbortingDeletePurse: newStatus = PeerPushDebitStatus.SuspendedAbortingDeletePurse; @@ -897,7 +998,8 @@ export async function suspendPeerPushDebitTransaction( newStatus = PeerPushDebitStatus.SuspendedReady; break; case PeerPushDebitStatus.SuspendedAbortingDeletePurse: - case PeerPushDebitStatus.SuspendedAbortingRefresh: + case PeerPushDebitStatus.SuspendedAbortingRefreshDeleted: + case PeerPushDebitStatus.SuspendedAbortingRefreshExpired: case PeerPushDebitStatus.SuspendedReady: case PeerPushDebitStatus.SuspendedCreatePurse: case PeerPushDebitStatus.Done: @@ -950,8 +1052,11 @@ export async function resumePeerPushDebitTransaction( case PeerPushDebitStatus.SuspendedAbortingDeletePurse: newStatus = PeerPushDebitStatus.AbortingDeletePurse; break; - case PeerPushDebitStatus.SuspendedAbortingRefresh: - newStatus = PeerPushDebitStatus.AbortingRefresh; + case PeerPushDebitStatus.SuspendedAbortingRefreshDeleted: + newStatus = PeerPushDebitStatus.AbortingRefreshDeleted; + break; + case PeerPushDebitStatus.SuspendedAbortingRefreshExpired: + newStatus = PeerPushDebitStatus.AbortingRefreshExpired; break; case PeerPushDebitStatus.SuspendedReady: newStatus = PeerPushDebitStatus.PendingReady; @@ -960,7 +1065,8 @@ export async function resumePeerPushDebitTransaction( newStatus = PeerPushDebitStatus.PendingCreatePurse; break; case PeerPushDebitStatus.PendingCreatePurse: - case PeerPushDebitStatus.AbortingRefresh: + case PeerPushDebitStatus.AbortingRefreshDeleted: + case PeerPushDebitStatus.AbortingRefreshExpired: case PeerPushDebitStatus.AbortingDeletePurse: case PeerPushDebitStatus.PendingReady: case PeerPushDebitStatus.Done: @@ -1011,17 +1117,27 @@ export function computePeerPushDebitTransactionState( major: TransactionMajorState.Aborting, minor: TransactionMinorState.DeletePurse, }; - case PeerPushDebitStatus.AbortingRefresh: + case PeerPushDebitStatus.AbortingRefreshDeleted: return { major: TransactionMajorState.Aborting, minor: TransactionMinorState.Refresh, }; + case PeerPushDebitStatus.AbortingRefreshExpired: + return { + major: TransactionMajorState.Aborting, + minor: TransactionMinorState.RefreshExpired, + }; case PeerPushDebitStatus.SuspendedAbortingDeletePurse: return { major: TransactionMajorState.SuspendedAborting, minor: TransactionMinorState.DeletePurse, }; - case PeerPushDebitStatus.SuspendedAbortingRefresh: + case PeerPushDebitStatus.SuspendedAbortingRefreshExpired: + return { + major: TransactionMajorState.SuspendedAborting, + minor: TransactionMinorState.RefreshExpired, + }; + case PeerPushDebitStatus.SuspendedAbortingRefreshDeleted: return { major: TransactionMajorState.SuspendedAborting, minor: TransactionMinorState.Refresh, diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts index a9d6c5595..233ca3fa4 100644 --- a/packages/taler-wallet-core/src/operations/pending.ts +++ b/packages/taler-wallet-core/src/operations/pending.ts @@ -640,7 +640,7 @@ export async function iterRecordsForPeerPushInitiation( if (filter.onlyState === "nonfinal") { const keyRange = GlobalIDB.KeyRange.bound( PeerPushDebitStatus.PendingCreatePurse, - PeerPushDebitStatus.AbortingRefresh, + PeerPushDebitStatus.AbortingRefreshDeleted, ); await tx.peerPushDebit.indexes.byStatus.iter(keyRange).forEachAsync(f); } else { diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index c58f03e05..3bbbc2a4b 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -528,6 +528,29 @@ async function refreshMelt( derived.meltValueWithFee, )} failed in refresh group ${refreshGroupId} due to conflict`, ); + + const historySig = await ws.cryptoApi.signCoinHistoryRequest({ + coinPriv: oldCoin.coinPriv, + coinPub: oldCoin.coinPub, + startOffset: 0, + }); + + const historyUrl = new URL( + `coins/${oldCoin.coinPub}/history`, + oldCoin.exchangeBaseUrl, + ); + + const historyResp = await ws.http.fetch(historyUrl.href, { + method: "GET", + headers: { + "Taler-Coin-History-Signature": historySig.sig, + }, + }); + + const historyJson = await historyResp.json(); + logger.info(`coin history: ${j2s(historyJson)}`); + + // FIXME: Before failing and re-trying, analyse response and adjust amount } const meltResponse = await readSuccessResponseJsonOrThrow( -- cgit v1.2.3