finish implementation of abort / cancelAborting on all tx types

This commit is contained in:
Florian Dold 2023-05-30 12:28:21 +02:00
parent d1dade4426
commit 000359a5e7
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
8 changed files with 690 additions and 55 deletions

View File

@ -116,6 +116,7 @@ export enum TransactionMinorState {
WithdrawCoins = "withdraw-coins", WithdrawCoins = "withdraw-coins",
ExchangeWaitReserve = "exchange-wait-reserve", ExchangeWaitReserve = "exchange-wait-reserve",
AbortingBank = "aborting-bank", AbortingBank = "aborting-bank",
Aborting = "aborting",
Refused = "refused", Refused = "refused",
Withdraw = "withdraw", Withdraw = "withdraw",
MerchantOrderProposed = "merchant-order-proposed", MerchantOrderProposed = "merchant-order-proposed",

View File

@ -879,6 +879,7 @@ export enum TipRecordStatus {
SuspendidPickup = 21, SuspendidPickup = 21,
Done = 50, Done = 50,
Aborted = 51,
} }
export enum RefreshCoinStatus { export enum RefreshCoinStatus {
@ -899,9 +900,10 @@ export enum OperationStatus {
export enum RefreshOperationStatus { export enum RefreshOperationStatus {
Pending = 10 /* ACTIVE_START */, Pending = 10 /* ACTIVE_START */,
Suspended = 20 /* DORMANT_START + 2 */,
Finished = 50 /* DORMANT_START */, Finished = 50 /* DORMANT_START */,
FinishedWithError = 51 /* DORMANT_START + 1 */, Failed = 51 /* DORMANT_START + 1 */,
Suspended = 52 /* DORMANT_START + 2 */,
} }
export enum DepositGroupOperationStatus { export enum DepositGroupOperationStatus {
@ -1155,6 +1157,8 @@ export enum PurchaseStatus {
* Payment was successful. * Payment was successful.
*/ */
Done = 54, Done = 54,
FailedAbort = 55,
} }
/** /**
@ -1778,6 +1782,7 @@ export enum PeerPushPaymentInitiationStatus {
Done = 50 /* DORMANT_START */, Done = 50 /* DORMANT_START */,
Aborted = 51, Aborted = 51,
Failed = 52,
} }
export interface PeerPushPaymentCoinSelection { export interface PeerPushPaymentCoinSelection {
@ -1850,13 +1855,17 @@ export enum PeerPullPaymentInitiationStatus {
PendingReady = 11 /* ACTIVE_START + 1 */, PendingReady = 11 /* ACTIVE_START + 1 */,
PendingMergeKycRequired = 12 /* ACTIVE_START + 2 */, PendingMergeKycRequired = 12 /* ACTIVE_START + 2 */,
PendingWithdrawing = 13, PendingWithdrawing = 13,
AbortingDeletePurse = 14,
SuspendedCreatePurse = 30, SuspendedCreatePurse = 30,
SuspendedReady = 31, SuspendedReady = 31,
SuspendedMergeKycRequired = 32, SuspendedMergeKycRequired = 32,
SuspendedWithdrawing = 33, SuspendedWithdrawing = 33,
SuspendedAbortingDeletePurse = 34,
DonePurseDeposited = 50 /* DORMANT_START */, DonePurseDeposited = 50 /* DORMANT_START */,
Failed = 51,
Aborted = 52,
} }
export interface PeerPullPaymentInitiationRecord { export interface PeerPullPaymentInitiationRecord {
@ -1927,6 +1936,8 @@ export enum PeerPushPaymentIncomingStatus {
DialogProposed = 30 /* USER_ATTENTION_START */, DialogProposed = 30 /* USER_ATTENTION_START */,
Done = 50 /* DORMANT_START */, Done = 50 /* DORMANT_START */,
Aborted = 51,
Failed = 52,
} }
/** /**
@ -1978,12 +1989,16 @@ export interface PeerPushPaymentIncomingRecord {
export enum PeerPullDebitRecordStatus { export enum PeerPullDebitRecordStatus {
PendingDeposit = 10 /* ACTIVE_START */, PendingDeposit = 10 /* ACTIVE_START */,
AbortingRefresh = 11,
SuspendedDeposit = 11, SuspendedDeposit = 20,
SuspendedAbortingRefresh = 21,
DialogProposed = 30 /* USER_ATTENTION_START */, DialogProposed = 30 /* USER_ATTENTION_START */,
DonePaid = 50 /* DORMANT_START */, DonePaid = 50 /* DORMANT_START */,
Aborted = 51,
Failed = 52,
} }
export interface PeerPullPaymentCoinSelection { export interface PeerPullPaymentCoinSelection {

View File

@ -447,7 +447,7 @@ async function waitForRefreshOnDepositGroup(
newOpState = DepositOperationStatus.Aborted; newOpState = DepositOperationStatus.Aborted;
} else if ( } else if (
refreshGroup.operationStatus === refreshGroup.operationStatus ===
RefreshOperationStatus.FinishedWithError RefreshOperationStatus.Failed
) { ) {
newOpState = DepositOperationStatus.Aborted; newOpState = DepositOperationStatus.Aborted;
} }

View File

@ -1508,6 +1508,7 @@ export async function processPurchase(
case PurchaseStatus.SuspendedPendingAcceptRefund: case PurchaseStatus.SuspendedPendingAcceptRefund:
case PurchaseStatus.SuspendedQueryingAutoRefund: case PurchaseStatus.SuspendedQueryingAutoRefund:
case PurchaseStatus.SuspendedQueryingRefund: case PurchaseStatus.SuspendedQueryingRefund:
case PurchaseStatus.FailedAbort:
return { return {
type: OperationAttemptResultType.Finished, type: OperationAttemptResultType.Finished,
result: undefined, result: undefined,
@ -1790,10 +1791,55 @@ export async function abortPayMerchant(
ws.workAvailable.trigger(); ws.workAvailable.trigger();
} }
export async function cancelAbortingPaymentTransaction(
ws: InternalWalletState,
proposalId: string,
): Promise<void> {
const transactionId = constructTransactionIdentifier({
tag: TransactionType.Payment,
proposalId,
});
const opId = constructTaskIdentifier({
tag: PendingTaskType.Purchase,
proposalId,
});
const transitionInfo = await ws.db
.mktx((x) => [
x.purchases,
x.refreshGroups,
x.denominations,
x.coinAvailability,
x.coins,
x.operationRetries,
])
.runReadWrite(async (tx) => {
const purchase = await tx.purchases.get(proposalId);
if (!purchase) {
throw Error("purchase not found");
}
const oldTxState = computePayMerchantTransactionState(purchase);
let newState: PurchaseStatus | undefined = undefined;
switch (purchase.purchaseStatus) {
case PurchaseStatus.AbortingWithRefund:
newState = PurchaseStatus.FailedAbort;
break;
}
if (newState) {
purchase.purchaseStatus = newState;
await tx.purchases.put(purchase);
}
const newTxState = computePayMerchantTransactionState(purchase);
return { oldTxState, newTxState };
});
notifyTransition(ws, transactionId, transitionInfo);
ws.workAvailable.trigger();
}
const transitionSuspend: { [x in PurchaseStatus]?: { const transitionSuspend: {
next: PurchaseStatus | undefined, [x in PurchaseStatus]?: {
} } = { next: PurchaseStatus | undefined;
};
} = {
[PurchaseStatus.PendingDownloadingProposal]: { [PurchaseStatus.PendingDownloadingProposal]: {
next: PurchaseStatus.SuspendedDownloadingProposal, next: PurchaseStatus.SuspendedDownloadingProposal,
}, },
@ -1808,12 +1854,14 @@ const transitionSuspend: { [x in PurchaseStatus]?: {
}, },
[PurchaseStatus.PendingQueryingAutoRefund]: { [PurchaseStatus.PendingQueryingAutoRefund]: {
next: PurchaseStatus.SuspendedQueryingAutoRefund, next: PurchaseStatus.SuspendedQueryingAutoRefund,
} },
} };
const transitionResume: { [x in PurchaseStatus]?: { const transitionResume: {
next: PurchaseStatus | undefined, [x in PurchaseStatus]?: {
} } = { next: PurchaseStatus | undefined;
};
} = {
[PurchaseStatus.SuspendedDownloadingProposal]: { [PurchaseStatus.SuspendedDownloadingProposal]: {
next: PurchaseStatus.PendingDownloadingProposal, next: PurchaseStatus.PendingDownloadingProposal,
}, },
@ -1828,9 +1876,8 @@ const transitionResume: { [x in PurchaseStatus]?: {
}, },
[PurchaseStatus.SuspendedQueryingAutoRefund]: { [PurchaseStatus.SuspendedQueryingAutoRefund]: {
next: PurchaseStatus.PendingQueryingAutoRefund, next: PurchaseStatus.PendingQueryingAutoRefund,
} },
} };
export async function suspendPayMerchant( export async function suspendPayMerchant(
ws: InternalWalletState, ws: InternalWalletState,
@ -1846,9 +1893,7 @@ export async function suspendPayMerchant(
}); });
stopLongpolling(ws, opId); stopLongpolling(ws, opId);
const transitionInfo = await ws.db const transitionInfo = await ws.db
.mktx((x) => [ .mktx((x) => [x.purchases])
x.purchases,
])
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
const purchase = await tx.purchases.get(proposalId); const purchase = await tx.purchases.get(proposalId);
if (!purchase) { if (!purchase) {
@ -1867,7 +1912,6 @@ export async function suspendPayMerchant(
ws.workAvailable.trigger(); ws.workAvailable.trigger();
} }
export async function resumePayMerchant( export async function resumePayMerchant(
ws: InternalWalletState, ws: InternalWalletState,
proposalId: string, proposalId: string,
@ -1882,9 +1926,7 @@ export async function resumePayMerchant(
}); });
stopLongpolling(ws, opId); stopLongpolling(ws, opId);
const transitionInfo = await ws.db const transitionInfo = await ws.db
.mktx((x) => [ .mktx((x) => [x.purchases])
x.purchases,
])
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
const purchase = await tx.purchases.get(proposalId); const purchase = await tx.purchases.get(proposalId);
if (!purchase) { if (!purchase) {
@ -2010,6 +2052,11 @@ export function computePayMerchantTransactionState(
major: TransactionMajorState.Failed, major: TransactionMajorState.Failed,
minor: TransactionMinorState.ClaimProposal, minor: TransactionMinorState.ClaimProposal,
}; };
case PurchaseStatus.FailedAbort:
return {
major: TransactionMajorState.Failed,
minor: TransactionMinorState.AbortingBank,
};
} }
} }

View File

@ -2005,6 +2005,10 @@ export function computePeerPushDebitTransactionState(
return { return {
major: TransactionMajorState.Done, major: TransactionMajorState.Done,
}; };
case PeerPushPaymentInitiationStatus.Failed:
return {
major: TransactionMajorState.Failed,
}
} }
} }
@ -2048,6 +2052,8 @@ export async function abortPeerPushDebitTransaction(
case PeerPushPaymentInitiationStatus.Aborted: case PeerPushPaymentInitiationStatus.Aborted:
// Do nothing // Do nothing
break; break;
case PeerPushPaymentInitiationStatus.Failed:
break;
default: default:
assertUnreachable(pushDebitRec.status); assertUnreachable(pushDebitRec.status);
} }
@ -2104,6 +2110,7 @@ export async function cancelAbortingPeerPushDebitTransaction(
case PeerPushPaymentInitiationStatus.PendingCreatePurse: case PeerPushPaymentInitiationStatus.PendingCreatePurse:
case PeerPushPaymentInitiationStatus.Done: case PeerPushPaymentInitiationStatus.Done:
case PeerPushPaymentInitiationStatus.Aborted: case PeerPushPaymentInitiationStatus.Aborted:
case PeerPushPaymentInitiationStatus.Failed:
// Do nothing // Do nothing
break; break;
default: default:
@ -2166,6 +2173,7 @@ export async function suspendPeerPushDebitTransaction(
case PeerPushPaymentInitiationStatus.SuspendedCreatePurse: case PeerPushPaymentInitiationStatus.SuspendedCreatePurse:
case PeerPushPaymentInitiationStatus.Done: case PeerPushPaymentInitiationStatus.Done:
case PeerPushPaymentInitiationStatus.Aborted: case PeerPushPaymentInitiationStatus.Aborted:
case PeerPushPaymentInitiationStatus.Failed:
// Do nothing // Do nothing
break; break;
default: default:
@ -2220,6 +2228,138 @@ export async function suspendPeerPullDebitTransaction(
break; break;
case PeerPullDebitRecordStatus.SuspendedDeposit: case PeerPullDebitRecordStatus.SuspendedDeposit:
break; break;
case PeerPullDebitRecordStatus.Aborted:
break;
case PeerPullDebitRecordStatus.AbortingRefresh:
newStatus = PeerPullDebitRecordStatus.SuspendedAbortingRefresh;
break;
case PeerPullDebitRecordStatus.Failed:
break;
case PeerPullDebitRecordStatus.SuspendedAbortingRefresh:
break;
default:
assertUnreachable(pullDebitRec.status);
}
if (newStatus != null) {
const oldTxState = computePeerPullDebitTransactionState(pullDebitRec);
pullDebitRec.status = newStatus;
const newTxState = computePeerPullDebitTransactionState(pullDebitRec);
await tx.peerPullPaymentIncoming.put(pullDebitRec);
return {
oldTxState,
newTxState,
};
}
return undefined;
});
notifyTransition(ws, transactionId, transitionInfo);
}
export async function abortPeerPullDebitTransaction(
ws: InternalWalletState,
peerPullPaymentIncomingId: string,
) {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPullDebit,
peerPullPaymentIncomingId,
});
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPullDebit,
peerPullPaymentIncomingId,
});
stopLongpolling(ws, taskId);
const transitionInfo = await ws.db
.mktx((x) => [x.peerPullPaymentIncoming])
.runReadWrite(async (tx) => {
const pullDebitRec = await tx.peerPullPaymentIncoming.get(
peerPullPaymentIncomingId,
);
if (!pullDebitRec) {
logger.warn(`peer pull debit ${peerPullPaymentIncomingId} not found`);
return;
}
let newStatus: PeerPullDebitRecordStatus | undefined = undefined;
switch (pullDebitRec.status) {
case PeerPullDebitRecordStatus.DialogProposed:
newStatus = PeerPullDebitRecordStatus.Aborted;
break;
case PeerPullDebitRecordStatus.DonePaid:
break;
case PeerPullDebitRecordStatus.PendingDeposit:
newStatus = PeerPullDebitRecordStatus.AbortingRefresh;
break;
case PeerPullDebitRecordStatus.SuspendedDeposit:
break;
case PeerPullDebitRecordStatus.Aborted:
break;
case PeerPullDebitRecordStatus.AbortingRefresh:
break;
case PeerPullDebitRecordStatus.Failed:
break;
case PeerPullDebitRecordStatus.SuspendedAbortingRefresh:
break;
default:
assertUnreachable(pullDebitRec.status);
}
if (newStatus != null) {
const oldTxState = computePeerPullDebitTransactionState(pullDebitRec);
pullDebitRec.status = newStatus;
const newTxState = computePeerPullDebitTransactionState(pullDebitRec);
await tx.peerPullPaymentIncoming.put(pullDebitRec);
return {
oldTxState,
newTxState,
};
}
return undefined;
});
notifyTransition(ws, transactionId, transitionInfo);
}
export async function cancelAbortingPeerPullDebitTransaction(
ws: InternalWalletState,
peerPullPaymentIncomingId: string,
) {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPullDebit,
peerPullPaymentIncomingId,
});
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPullDebit,
peerPullPaymentIncomingId,
});
stopLongpolling(ws, taskId);
const transitionInfo = await ws.db
.mktx((x) => [x.peerPullPaymentIncoming])
.runReadWrite(async (tx) => {
const pullDebitRec = await tx.peerPullPaymentIncoming.get(
peerPullPaymentIncomingId,
);
if (!pullDebitRec) {
logger.warn(`peer pull debit ${peerPullPaymentIncomingId} not found`);
return;
}
let newStatus: PeerPullDebitRecordStatus | undefined = undefined;
switch (pullDebitRec.status) {
case PeerPullDebitRecordStatus.DialogProposed:
newStatus = PeerPullDebitRecordStatus.Aborted;
break;
case PeerPullDebitRecordStatus.DonePaid:
break;
case PeerPullDebitRecordStatus.PendingDeposit:
break;
case PeerPullDebitRecordStatus.SuspendedDeposit:
break;
case PeerPullDebitRecordStatus.Aborted:
break;
case PeerPullDebitRecordStatus.Failed:
break;
case PeerPullDebitRecordStatus.SuspendedAbortingRefresh:
case PeerPullDebitRecordStatus.AbortingRefresh:
// FIXME: abort underlying refresh!
newStatus = PeerPullDebitRecordStatus.Failed;
break;
default: default:
assertUnreachable(pullDebitRec.status); assertUnreachable(pullDebitRec.status);
} }
@ -2270,6 +2410,15 @@ export async function resumePeerPullDebitTransaction(
case PeerPullDebitRecordStatus.SuspendedDeposit: case PeerPullDebitRecordStatus.SuspendedDeposit:
newStatus = PeerPullDebitRecordStatus.PendingDeposit; newStatus = PeerPullDebitRecordStatus.PendingDeposit;
break; break;
case PeerPullDebitRecordStatus.Aborted:
break;
case PeerPullDebitRecordStatus.AbortingRefresh:
break;
case PeerPullDebitRecordStatus.Failed:
break;
case PeerPullDebitRecordStatus.SuspendedAbortingRefresh:
newStatus = PeerPullDebitRecordStatus.AbortingRefresh;
break;
default: default:
assertUnreachable(pullDebitRec.status); assertUnreachable(pullDebitRec.status);
} }
@ -2330,6 +2479,10 @@ export async function suspendPeerPushCreditTransaction(
// FIXME: Suspend internal withdrawal transaction! // FIXME: Suspend internal withdrawal transaction!
newStatus = PeerPushPaymentIncomingStatus.SuspendedWithdrawing; newStatus = PeerPushPaymentIncomingStatus.SuspendedWithdrawing;
break; break;
case PeerPushPaymentIncomingStatus.Aborted:
break;
case PeerPushPaymentIncomingStatus.Failed:
break;
default: default:
assertUnreachable(pushCreditRec.status); assertUnreachable(pushCreditRec.status);
} }
@ -2348,6 +2501,81 @@ export async function suspendPeerPushCreditTransaction(
notifyTransition(ws, transactionId, transitionInfo); notifyTransition(ws, transactionId, transitionInfo);
} }
export async function abortPeerPushCreditTransaction(
ws: InternalWalletState,
peerPushPaymentIncomingId: string,
) {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPushCredit,
peerPushPaymentIncomingId,
});
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPushCredit,
peerPushPaymentIncomingId,
});
stopLongpolling(ws, taskId);
const transitionInfo = await ws.db
.mktx((x) => [x.peerPushPaymentIncoming])
.runReadWrite(async (tx) => {
const pushCreditRec = await tx.peerPushPaymentIncoming.get(
peerPushPaymentIncomingId,
);
if (!pushCreditRec) {
logger.warn(`peer push credit ${peerPushPaymentIncomingId} not found`);
return;
}
let newStatus: PeerPushPaymentIncomingStatus | undefined = undefined;
switch (pushCreditRec.status) {
case PeerPushPaymentIncomingStatus.DialogProposed:
newStatus = PeerPushPaymentIncomingStatus.Aborted;
break;
case PeerPushPaymentIncomingStatus.Done:
break;
case PeerPushPaymentIncomingStatus.SuspendedMerge:
case PeerPushPaymentIncomingStatus.SuspendedMergeKycRequired:
case PeerPushPaymentIncomingStatus.SuspendedWithdrawing:
newStatus = PeerPushPaymentIncomingStatus.Aborted;
break;
case PeerPushPaymentIncomingStatus.PendingMergeKycRequired:
newStatus = PeerPushPaymentIncomingStatus.Aborted;
break;
case PeerPushPaymentIncomingStatus.PendingMerge:
newStatus = PeerPushPaymentIncomingStatus.Aborted;
break;
case PeerPushPaymentIncomingStatus.PendingWithdrawing:
newStatus = PeerPushPaymentIncomingStatus.Aborted;
break;
case PeerPushPaymentIncomingStatus.Aborted:
break;
case PeerPushPaymentIncomingStatus.Failed:
break;
default:
assertUnreachable(pushCreditRec.status);
}
if (newStatus != null) {
const oldTxState = computePeerPushCreditTransactionState(pushCreditRec);
pushCreditRec.status = newStatus;
const newTxState = computePeerPushCreditTransactionState(pushCreditRec);
await tx.peerPushPaymentIncoming.put(pushCreditRec);
return {
oldTxState,
newTxState,
};
}
return undefined;
});
notifyTransition(ws, transactionId, transitionInfo);
}
export async function cancelAbortingPeerPushCreditTransaction(
ws: InternalWalletState,
peerPushPaymentIncomingId: string,
) {
// We don't have any "aborting" states!
throw Error("can't run cancel-aborting on peer-push-credit transaction");
}
export async function resumePeerPushCreditTransaction( export async function resumePeerPushCreditTransaction(
ws: InternalWalletState, ws: InternalWalletState,
peerPushPaymentIncomingId: string, peerPushPaymentIncomingId: string,
@ -2388,6 +2616,10 @@ export async function resumePeerPushCreditTransaction(
// FIXME: resume underlying "internal-withdrawal" transaction. // FIXME: resume underlying "internal-withdrawal" transaction.
newStatus = PeerPushPaymentIncomingStatus.PendingWithdrawing; newStatus = PeerPushPaymentIncomingStatus.PendingWithdrawing;
break; break;
case PeerPushPaymentIncomingStatus.Aborted:
break;
case PeerPushPaymentIncomingStatus.Failed:
break;
default: default:
assertUnreachable(pushCreditRec.status); assertUnreachable(pushCreditRec.status);
} }
@ -2442,11 +2674,135 @@ export async function suspendPeerPullCreditTransaction(
case PeerPullPaymentInitiationStatus.PendingReady: case PeerPullPaymentInitiationStatus.PendingReady:
newStatus = PeerPullPaymentInitiationStatus.SuspendedReady; newStatus = PeerPullPaymentInitiationStatus.SuspendedReady;
break; break;
case PeerPullPaymentInitiationStatus.AbortingDeletePurse:
newStatus =
PeerPullPaymentInitiationStatus.SuspendedAbortingDeletePurse;
break;
case PeerPullPaymentInitiationStatus.DonePurseDeposited: case PeerPullPaymentInitiationStatus.DonePurseDeposited:
case PeerPullPaymentInitiationStatus.SuspendedCreatePurse: case PeerPullPaymentInitiationStatus.SuspendedCreatePurse:
case PeerPullPaymentInitiationStatus.SuspendedMergeKycRequired: case PeerPullPaymentInitiationStatus.SuspendedMergeKycRequired:
case PeerPullPaymentInitiationStatus.SuspendedReady: case PeerPullPaymentInitiationStatus.SuspendedReady:
case PeerPullPaymentInitiationStatus.SuspendedWithdrawing: case PeerPullPaymentInitiationStatus.SuspendedWithdrawing:
case PeerPullPaymentInitiationStatus.Aborted:
case PeerPullPaymentInitiationStatus.Failed:
case PeerPullPaymentInitiationStatus.SuspendedAbortingDeletePurse:
break;
default:
assertUnreachable(pullCreditRec.status);
}
if (newStatus != null) {
const oldTxState = computePeerPullCreditTransactionState(pullCreditRec);
pullCreditRec.status = newStatus;
const newTxState = computePeerPullCreditTransactionState(pullCreditRec);
await tx.peerPullPaymentInitiations.put(pullCreditRec);
return {
oldTxState,
newTxState,
};
}
return undefined;
});
notifyTransition(ws, transactionId, transitionInfo);
}
export async function abortPeerPullCreditTransaction(
ws: InternalWalletState,
pursePub: string,
) {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPullCredit,
pursePub,
});
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPullCredit,
pursePub,
});
stopLongpolling(ws, taskId);
const transitionInfo = await ws.db
.mktx((x) => [x.peerPullPaymentInitiations])
.runReadWrite(async (tx) => {
const pullCreditRec = await tx.peerPullPaymentInitiations.get(pursePub);
if (!pullCreditRec) {
logger.warn(`peer pull credit ${pursePub} not found`);
return;
}
let newStatus: PeerPullPaymentInitiationStatus | undefined = undefined;
switch (pullCreditRec.status) {
case PeerPullPaymentInitiationStatus.PendingCreatePurse:
case PeerPullPaymentInitiationStatus.PendingMergeKycRequired:
newStatus = PeerPullPaymentInitiationStatus.AbortingDeletePurse;
break;
case PeerPullPaymentInitiationStatus.PendingWithdrawing:
throw Error("can't abort anymore");
case PeerPullPaymentInitiationStatus.PendingReady:
newStatus = PeerPullPaymentInitiationStatus.AbortingDeletePurse;
break;
case PeerPullPaymentInitiationStatus.DonePurseDeposited:
case PeerPullPaymentInitiationStatus.SuspendedCreatePurse:
case PeerPullPaymentInitiationStatus.SuspendedMergeKycRequired:
case PeerPullPaymentInitiationStatus.SuspendedReady:
case PeerPullPaymentInitiationStatus.SuspendedWithdrawing:
case PeerPullPaymentInitiationStatus.Aborted:
case PeerPullPaymentInitiationStatus.AbortingDeletePurse:
case PeerPullPaymentInitiationStatus.Failed:
case PeerPullPaymentInitiationStatus.SuspendedAbortingDeletePurse:
break;
default:
assertUnreachable(pullCreditRec.status);
}
if (newStatus != null) {
const oldTxState = computePeerPullCreditTransactionState(pullCreditRec);
pullCreditRec.status = newStatus;
const newTxState = computePeerPullCreditTransactionState(pullCreditRec);
await tx.peerPullPaymentInitiations.put(pullCreditRec);
return {
oldTxState,
newTxState,
};
}
return undefined;
});
notifyTransition(ws, transactionId, transitionInfo);
}
export async function cancelAbortingPeerPullCreditTransaction(
ws: InternalWalletState,
pursePub: string,
) {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.PeerPullCredit,
pursePub,
});
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPullCredit,
pursePub,
});
stopLongpolling(ws, taskId);
const transitionInfo = await ws.db
.mktx((x) => [x.peerPullPaymentInitiations])
.runReadWrite(async (tx) => {
const pullCreditRec = await tx.peerPullPaymentInitiations.get(pursePub);
if (!pullCreditRec) {
logger.warn(`peer pull credit ${pursePub} not found`);
return;
}
let newStatus: PeerPullPaymentInitiationStatus | undefined = undefined;
switch (pullCreditRec.status) {
case PeerPullPaymentInitiationStatus.PendingCreatePurse:
case PeerPullPaymentInitiationStatus.PendingMergeKycRequired:
case PeerPullPaymentInitiationStatus.PendingWithdrawing:
case PeerPullPaymentInitiationStatus.PendingReady:
case PeerPullPaymentInitiationStatus.DonePurseDeposited:
case PeerPullPaymentInitiationStatus.SuspendedCreatePurse:
case PeerPullPaymentInitiationStatus.SuspendedMergeKycRequired:
case PeerPullPaymentInitiationStatus.SuspendedReady:
case PeerPullPaymentInitiationStatus.SuspendedWithdrawing:
case PeerPullPaymentInitiationStatus.Aborted:
case PeerPullPaymentInitiationStatus.Failed:
break;
case PeerPullPaymentInitiationStatus.AbortingDeletePurse:
case PeerPullPaymentInitiationStatus.SuspendedAbortingDeletePurse:
newStatus = PeerPullPaymentInitiationStatus.Failed;
break; break;
default: default:
assertUnreachable(pullCreditRec.status); assertUnreachable(pullCreditRec.status);
@ -2493,7 +2849,11 @@ export async function resumePeerPullCreditTransaction(
case PeerPullPaymentInitiationStatus.PendingMergeKycRequired: case PeerPullPaymentInitiationStatus.PendingMergeKycRequired:
case PeerPullPaymentInitiationStatus.PendingWithdrawing: case PeerPullPaymentInitiationStatus.PendingWithdrawing:
case PeerPullPaymentInitiationStatus.PendingReady: case PeerPullPaymentInitiationStatus.PendingReady:
case PeerPullPaymentInitiationStatus.AbortingDeletePurse:
case PeerPullPaymentInitiationStatus.DonePurseDeposited: case PeerPullPaymentInitiationStatus.DonePurseDeposited:
case PeerPullPaymentInitiationStatus.Failed:
case PeerPullPaymentInitiationStatus.Aborted:
break;
case PeerPullPaymentInitiationStatus.SuspendedCreatePurse: case PeerPullPaymentInitiationStatus.SuspendedCreatePurse:
newStatus = PeerPullPaymentInitiationStatus.PendingCreatePurse; newStatus = PeerPullPaymentInitiationStatus.PendingCreatePurse;
break; break;
@ -2506,6 +2866,9 @@ export async function resumePeerPullCreditTransaction(
case PeerPullPaymentInitiationStatus.SuspendedWithdrawing: case PeerPullPaymentInitiationStatus.SuspendedWithdrawing:
newStatus = PeerPullPaymentInitiationStatus.PendingWithdrawing; newStatus = PeerPullPaymentInitiationStatus.PendingWithdrawing;
break; break;
case PeerPullPaymentInitiationStatus.SuspendedAbortingDeletePurse:
newStatus = PeerPullPaymentInitiationStatus.AbortingDeletePurse;
break;
default: default:
assertUnreachable(pullCreditRec.status); assertUnreachable(pullCreditRec.status);
} }
@ -2566,6 +2929,7 @@ export async function resumePeerPushDebitTransaction(
case PeerPushPaymentInitiationStatus.PendingReady: case PeerPushPaymentInitiationStatus.PendingReady:
case PeerPushPaymentInitiationStatus.Done: case PeerPushPaymentInitiationStatus.Done:
case PeerPushPaymentInitiationStatus.Aborted: case PeerPushPaymentInitiationStatus.Aborted:
case PeerPushPaymentInitiationStatus.Failed:
// Do nothing // Do nothing
break; break;
default: default:
@ -2630,6 +2994,16 @@ export function computePeerPushCreditTransactionState(
major: TransactionMajorState.Suspended, major: TransactionMajorState.Suspended,
minor: TransactionMinorState.Withdraw, minor: TransactionMinorState.Withdraw,
}; };
case PeerPushPaymentIncomingStatus.Aborted:
return {
major: TransactionMajorState.Aborted
};
case PeerPushPaymentIncomingStatus.Failed:
return {
major: TransactionMajorState.Failed,
}
default:
assertUnreachable(pushCreditRecord.status);
} }
} }
@ -2681,6 +3055,24 @@ export function computePeerPullCreditTransactionState(
major: TransactionMajorState.Suspended, major: TransactionMajorState.Suspended,
minor: TransactionMinorState.MergeKycRequired, minor: TransactionMinorState.MergeKycRequired,
}; };
case PeerPullPaymentInitiationStatus.Aborted:
return {
major: TransactionMajorState.Aborted,
};
case PeerPullPaymentInitiationStatus.AbortingDeletePurse:
return {
major: TransactionMajorState.Aborting,
minor: TransactionMinorState.DeletePurse,
};
case PeerPullPaymentInitiationStatus.Failed:
return {
major: TransactionMajorState.Failed,
};
case PeerPullPaymentInitiationStatus.SuspendedAbortingDeletePurse:
return {
major: TransactionMajorState.Aborting,
minor: TransactionMinorState.DeletePurse,
};
} }
} }
@ -2707,5 +3099,23 @@ export function computePeerPullDebitTransactionState(
major: TransactionMajorState.Suspended, major: TransactionMajorState.Suspended,
minor: TransactionMinorState.Deposit, minor: TransactionMinorState.Deposit,
}; };
case PeerPullDebitRecordStatus.Aborted:
return {
major: TransactionMajorState.Aborted,
};
case PeerPullDebitRecordStatus.AbortingRefresh:
return {
major: TransactionMajorState.Aborting,
minor: TransactionMinorState.Refresh,
};
case PeerPullDebitRecordStatus.Failed:
return {
major: TransactionMajorState.Failed,
};
case PeerPullDebitRecordStatus.SuspendedAbortingRefresh:
return {
major: TransactionMajorState.SuspendedAborting,
minor: TransactionMinorState.Refresh,
};
} }
} }

View File

@ -96,7 +96,7 @@ import {
PendingTaskType, PendingTaskType,
WalletConfig, WalletConfig,
} from "../index.js"; } from "../index.js";
import { constructTransactionIdentifier } from "./transactions.js"; import { constructTransactionIdentifier, notifyTransition } from "./transactions.js";
const logger = new Logger("refresh.ts"); const logger = new Logger("refresh.ts");
@ -158,7 +158,7 @@ function updateGroupStatus(rg: RefreshGroupRecord): void {
if (allDone) { if (allDone) {
if (anyFrozen) { if (anyFrozen) {
rg.timestampFinished = TalerPreciseTimestamp.now(); rg.timestampFinished = TalerPreciseTimestamp.now();
rg.operationStatus = RefreshOperationStatus.FinishedWithError; rg.operationStatus = RefreshOperationStatus.Failed;
} else { } else {
rg.timestampFinished = TalerPreciseTimestamp.now(); rg.timestampFinished = TalerPreciseTimestamp.now();
rg.operationStatus = RefreshOperationStatus.Finished; rg.operationStatus = RefreshOperationStatus.Finished;
@ -1189,7 +1189,7 @@ export function computeRefreshTransactionState(
return { return {
major: TransactionMajorState.Done, major: TransactionMajorState.Done,
}; };
case RefreshOperationStatus.FinishedWithError: case RefreshOperationStatus.Failed:
return { return {
major: TransactionMajorState.Failed, major: TransactionMajorState.Failed,
}; };
@ -1261,7 +1261,7 @@ export async function resumeRefreshGroup(
tag: TransactionType.Refresh, tag: TransactionType.Refresh,
refreshGroupId, refreshGroupId,
}); });
let res = await ws.db const transitionInfo = await ws.db
.mktx((x) => [x.refreshGroups]) .mktx((x) => [x.refreshGroups])
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
const dg = await tx.refreshGroups.get(refreshGroupId); const dg = await tx.refreshGroups.get(refreshGroupId);
@ -1289,19 +1289,57 @@ export async function resumeRefreshGroup(
return undefined; return undefined;
}); });
ws.workAvailable.trigger(); ws.workAvailable.trigger();
if (res) { notifyTransition(ws, transactionId, transitionInfo);
ws.notify({ }
type: NotificationType.TransactionStateTransition,
transactionId, export async function cancelAbortingRefreshGroup(
oldTxState: res.oldTxState, ws: InternalWalletState,
newTxState: res.newTxState, refreshGroupId: string,
}); ): Promise<void> {
} throw Error("action cancel-aborting not allowed on refreshes");
} }
export async function abortRefreshGroup( export async function abortRefreshGroup(
ws: InternalWalletState, ws: InternalWalletState,
refreshGroupId: string, refreshGroupId: string,
): Promise<void> { ): Promise<void> {
throw Error("can't abort refresh groups."); 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);
} }

View File

@ -87,21 +87,28 @@ const logger = new Logger("operations/tip.ts");
export function computeTipTransactionStatus( export function computeTipTransactionStatus(
tipRecord: TipRecord, tipRecord: TipRecord,
): TransactionState { ): TransactionState {
if (tipRecord.pickedUpTimestamp) { switch (tipRecord.status) {
return { case TipRecordStatus.Done:
major: TransactionMajorState.Done, return {
}; major: TransactionMajorState.Done,
};
case TipRecordStatus.Aborted:
return {
major: TransactionMajorState.Aborted,
};
case TipRecordStatus.PendingPickup:
return {
major: TransactionMajorState.Pending,
minor: TransactionMinorState.Pickup,
};
case TipRecordStatus.SuspendidPickup:
return {
major: TransactionMajorState.Pending,
minor: TransactionMinorState.User,
};
default:
assertUnreachable(tipRecord.status);
} }
if (tipRecord.acceptedTimestamp) {
return {
major: TransactionMajorState.Pending,
minor: TransactionMinorState.Pickup,
};
}
return {
major: TransactionMajorState.Pending,
minor: TransactionMinorState.User,
};
} }
export async function prepareTip( export async function prepareTip(
@ -445,6 +452,7 @@ export async function suspendTipTransaction(
switch (tipRec.status) { switch (tipRec.status) {
case TipRecordStatus.Done: case TipRecordStatus.Done:
case TipRecordStatus.SuspendidPickup: case TipRecordStatus.SuspendidPickup:
case TipRecordStatus.Aborted:
break; break;
case TipRecordStatus.PendingPickup: case TipRecordStatus.PendingPickup:
newStatus = TipRecordStatus.SuspendidPickup; newStatus = TipRecordStatus.SuspendidPickup;
@ -492,11 +500,72 @@ export async function resumeTipTransaction(
let newStatus: TipRecordStatus | undefined = undefined; let newStatus: TipRecordStatus | undefined = undefined;
switch (tipRec.status) { switch (tipRec.status) {
case TipRecordStatus.Done: case TipRecordStatus.Done:
break;
case TipRecordStatus.SuspendidPickup: case TipRecordStatus.SuspendidPickup:
newStatus = TipRecordStatus.PendingPickup; newStatus = TipRecordStatus.PendingPickup;
break; break;
case TipRecordStatus.PendingPickup: case TipRecordStatus.PendingPickup:
break; break;
case TipRecordStatus.Aborted:
break;
default:
assertUnreachable(tipRec.status);
}
if (newStatus != null) {
const oldTxState = computeTipTransactionStatus(tipRec);
tipRec.status = newStatus;
const newTxState = computeTipTransactionStatus(tipRec);
await tx.tips.put(tipRec);
return {
oldTxState,
newTxState,
};
}
return undefined;
});
notifyTransition(ws, transactionId, transitionInfo);
}
export async function cancelAbortingTipTransaction(
ws: InternalWalletState,
walletTipId: string,
): Promise<void> {
// We don't have an "aborting" state, so this should never happen!
throw Error("can't run cance-aborting on tip transaction");
}
export async function abortTipTransaction(
ws: InternalWalletState,
walletTipId: string,
): Promise<void> {
const taskId = constructTaskIdentifier({
tag: PendingTaskType.TipPickup,
walletTipId,
});
const transactionId = constructTransactionIdentifier({
tag: TransactionType.Tip,
walletTipId,
});
stopLongpolling(ws, taskId);
const transitionInfo = await ws.db
.mktx((x) => [x.tips])
.runReadWrite(async (tx) => {
const tipRec = await tx.tips.get(walletTipId);
if (!tipRec) {
logger.warn(`transaction tip ${walletTipId} not found`);
return;
}
let newStatus: TipRecordStatus | undefined = undefined;
switch (tipRec.status) {
case TipRecordStatus.Done:
break;
case TipRecordStatus.SuspendidPickup:
newStatus = TipRecordStatus.Aborted;
break;
case TipRecordStatus.PendingPickup:
break;
case TipRecordStatus.Aborted:
break;
default: default:
assertUnreachable(tipRec.status); assertUnreachable(tipRec.status);
} }

View File

@ -82,6 +82,7 @@ import {
import { getExchangeDetails } from "./exchanges.js"; import { getExchangeDetails } from "./exchanges.js";
import { import {
abortPayMerchant, abortPayMerchant,
cancelAbortingPaymentTransaction,
computePayMerchantTransactionState, computePayMerchantTransactionState,
computeRefundTransactionState, computeRefundTransactionState,
expectProposalDownload, expectProposalDownload,
@ -90,6 +91,14 @@ import {
suspendPayMerchant, suspendPayMerchant,
} from "./pay-merchant.js"; } from "./pay-merchant.js";
import { import {
abortPeerPullCreditTransaction,
abortPeerPullDebitTransaction,
abortPeerPushCreditTransaction,
abortPeerPushDebitTransaction,
cancelAbortingPeerPullCreditTransaction,
cancelAbortingPeerPullDebitTransaction,
cancelAbortingPeerPushCreditTransaction,
cancelAbortingPeerPushDebitTransaction,
computePeerPullCreditTransactionState, computePeerPullCreditTransactionState,
computePeerPullDebitTransactionState, computePeerPullDebitTransactionState,
computePeerPushCreditTransactionState, computePeerPushCreditTransactionState,
@ -104,11 +113,15 @@ import {
suspendPeerPushDebitTransaction, suspendPeerPushDebitTransaction,
} from "./pay-peer.js"; } from "./pay-peer.js";
import { import {
abortRefreshGroup,
cancelAbortingRefreshGroup,
computeRefreshTransactionState, computeRefreshTransactionState,
resumeRefreshGroup, resumeRefreshGroup,
suspendRefreshGroup, suspendRefreshGroup,
} from "./refresh.js"; } from "./refresh.js";
import { import {
abortTipTransaction,
cancelAbortingTipTransaction,
computeTipTransactionStatus, computeTipTransactionStatus,
resumeTipTransaction, resumeTipTransaction,
suspendTipTransaction, suspendTipTransaction,
@ -1492,11 +1505,35 @@ export async function cancelAbortingTransaction(
case TransactionType.Deposit: case TransactionType.Deposit:
await cancelAbortingDepositGroup(ws, tx.depositGroupId); await cancelAbortingDepositGroup(ws, tx.depositGroupId);
return; return;
case TransactionType.InternalWithdrawal:
case TransactionType.Withdrawal: case TransactionType.Withdrawal:
await cancelAbortingWithdrawalTransaction(ws, tx.withdrawalGroupId); await cancelAbortingWithdrawalTransaction(ws, tx.withdrawalGroupId);
return; return;
case TransactionType.Payment:
await cancelAbortingPaymentTransaction(ws, tx.proposalId);
return;
case TransactionType.Refund:
throw Error("can't do cancel-aborting on refund transaction");
case TransactionType.Tip:
await cancelAbortingTipTransaction(ws, tx.walletTipId);
return;
case TransactionType.Refresh:
await cancelAbortingRefreshGroup(ws, tx.refreshGroupId);
return;
case TransactionType.PeerPullCredit:
await cancelAbortingPeerPullCreditTransaction(ws, tx.pursePub);
return;
case TransactionType.PeerPullDebit:
await cancelAbortingPeerPullDebitTransaction(ws, tx.peerPullPaymentIncomingId);
return;
case TransactionType.PeerPushCredit:
await cancelAbortingPeerPushCreditTransaction(ws, tx.peerPushPaymentIncomingId);
return;
case TransactionType.PeerPushDebit:
await cancelAbortingPeerPushDebitTransaction(ws, tx.pursePub);
return;
default: default:
logger.warn(`unable to suspend transaction of type '${tx.tag}'`); assertUnreachable(tx);
} }
} }
@ -1774,18 +1811,36 @@ export async function abortTransaction(
await abortPayMerchant(ws, txId.proposalId); await abortPayMerchant(ws, txId.proposalId);
break; break;
} }
case TransactionType.Withdrawal: { case TransactionType.Withdrawal:
case TransactionType.InternalWithdrawal: {
await abortWithdrawalTransaction(ws, txId.withdrawalGroupId); await abortWithdrawalTransaction(ws, txId.withdrawalGroupId);
break; break;
} }
case TransactionType.Deposit: case TransactionType.Deposit:
await abortDepositGroup(ws, txId.depositGroupId); await abortDepositGroup(ws, txId.depositGroupId);
break; break;
case TransactionType.Tip:
await abortTipTransaction(ws, txId.walletTipId);
break;
case TransactionType.Refund:
throw Error("can't abort refund transactions");
case TransactionType.Refresh:
await abortRefreshGroup(ws, txId.refreshGroupId);
break;
case TransactionType.PeerPullCredit:
await abortPeerPullCreditTransaction(ws, txId.pursePub);
break;
case TransactionType.PeerPullDebit:
await abortPeerPullDebitTransaction(ws, txId.peerPullPaymentIncomingId);
break;
case TransactionType.PeerPushCredit:
await abortPeerPushCreditTransaction(ws, txId.peerPushPaymentIncomingId);
break;
case TransactionType.PeerPushDebit:
await abortPeerPushDebitTransaction(ws, txId.pursePub);
break;
default: { default: {
const unknownTxType: any = txId.tag; assertUnreachable(txId);
throw Error(
`can't abort a '${unknownTxType}' transaction: not yet implemented`,
);
} }
} }
} }