summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-06-05 17:58:20 +0200
committerFlorian Dold <florian@dold.me>2023-06-05 17:58:25 +0200
commit9fca44893a6f7fcee5c828da5fc10e7d76592b5d (patch)
tree634830c7a769302d5e107f2aee323c3f705cf113
parent6e7c88a62073082b28ef563561d08f56acc0b017 (diff)
downloadwallet-core-9fca44893a6f7fcee5c828da5fc10e7d76592b5d.tar.gz
wallet-core-9fca44893a6f7fcee5c828da5fc10e7d76592b5d.tar.bz2
wallet-core-9fca44893a6f7fcee5c828da5fc10e7d76592b5d.zip
wallet-core: handle more p2p abort cases nicely
-rw-r--r--packages/taler-util/src/http-common.ts2
-rw-r--r--packages/taler-util/src/taler-crypto.ts1
-rw-r--r--packages/taler-util/src/transactions-types.ts1
-rw-r--r--packages/taler-util/src/wallet-types.ts1
-rw-r--r--packages/taler-wallet-core/src/crypto/cryptoImplementation.ts24
-rw-r--r--packages/taler-wallet-core/src/crypto/cryptoTypes.ts9
-rw-r--r--packages/taler-wallet-core/src/db.ts3
-rw-r--r--packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts59
-rw-r--r--packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts127
-rw-r--r--packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts70
-rw-r--r--packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts188
11 files changed, 385 insertions, 100 deletions
diff --git a/packages/taler-util/src/http-common.ts b/packages/taler-util/src/http-common.ts
index 4f8f12789..4f6aaaf44 100644
--- a/packages/taler-util/src/http-common.ts
+++ b/packages/taler-util/src/http-common.ts
@@ -45,7 +45,7 @@ export interface HttpResponse {
export const DEFAULT_REQUEST_TIMEOUT_MS = 60000;
export interface HttpRequestOptions {
- method?: "POST" | "PUT" | "GET";
+ method?: "POST" | "PUT" | "GET" | "DELETE";
headers?: { [name: string]: string };
/**
diff --git a/packages/taler-util/src/taler-crypto.ts b/packages/taler-util/src/taler-crypto.ts
index 4a657b621..c4eb925f7 100644
--- a/packages/taler-util/src/taler-crypto.ts
+++ b/packages/taler-util/src/taler-crypto.ts
@@ -958,6 +958,7 @@ export enum TalerSignaturePurpose {
WALLET_PURSE_MERGE = 1213,
WALLET_ACCOUNT_MERGE = 1214,
WALLET_PURSE_ECONTRACT = 1216,
+ WALLET_PURSE_DELETE = 1220,
EXCHANGE_CONFIRM_RECOUP = 1039,
EXCHANGE_CONFIRM_RECOUP_REFRESH = 1041,
ANASTASIS_POLICY_UPLOAD = 1400,
diff --git a/packages/taler-util/src/transactions-types.ts b/packages/taler-util/src/transactions-types.ts
index 3f3fe8ac0..576c8e335 100644
--- a/packages/taler-util/src/transactions-types.ts
+++ b/packages/taler-util/src/transactions-types.ts
@@ -83,6 +83,7 @@ export enum TransactionMajorState {
Dialog = "dialog",
SuspendedAborting = "suspended-aborting",
Failed = "failed",
+ Expired = "expired",
// Only used for the notification, never in the transaction history
Deleted = "deleted",
}
diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts
index 05ff8d891..af02807a6 100644
--- a/packages/taler-util/src/wallet-types.ts
+++ b/packages/taler-util/src/wallet-types.ts
@@ -734,6 +734,7 @@ export enum RefreshReason {
Refund = "refund",
AbortPay = "abort-pay",
AbortDeposit = "abort-deposit",
+ AbortPeerPushDebit = "abort-peer-push-debit",
Recoup = "recoup",
BackupRestored = "backup-restored",
Scheduled = "scheduled",
diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
index 6b44c297d..c0c8e0d01 100644
--- a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
+++ b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
@@ -106,6 +106,8 @@ import {
EncryptContractRequest,
EncryptContractResponse,
EncryptedContract,
+ SignDeletePurseRequest,
+ SignDeletePurseResponse,
SignPurseMergeRequest,
SignPurseMergeResponse,
SignRefundRequest,
@@ -240,6 +242,8 @@ export interface TalerCryptoInterface {
): Promise<SignReservePurseCreateResponse>;
signRefund(req: SignRefundRequest): Promise<SignRefundResponse>;
+
+ signDeletePurse(req: SignDeletePurseRequest): Promise<SignDeletePurseResponse>;
}
/**
@@ -419,6 +423,11 @@ export const nullCrypto: TalerCryptoInterface = {
signRefund: function (req: SignRefundRequest): Promise<SignRefundResponse> {
throw new Error("Function not implemented.");
},
+ signDeletePurse: function (
+ req: SignDeletePurseRequest,
+ ): Promise<SignDeletePurseResponse> {
+ throw new Error("Function not implemented.");
+ },
};
export type WithArg<X> = X extends (req: infer T) => infer R
@@ -1671,6 +1680,21 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
sig: refundSigResp.sig,
};
},
+ async signDeletePurse(
+ tci: TalerCryptoInterfaceR,
+ req: SignDeletePurseRequest,
+ ): Promise<SignDeletePurseResponse> {
+ const deleteSigBlob = buildSigPS(
+ TalerSignaturePurpose.WALLET_PURSE_DELETE,
+ ).build();
+ const sigResp = await tci.eddsaSign(tci, {
+ msg: encodeCrock(deleteSigBlob),
+ priv: req.pursePriv,
+ });
+ return {
+ sig: sigResp.sig,
+ }
+ },
};
function amountToBuffer(amount: AmountLike): Uint8Array {
diff --git a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts
index 3b27db0c0..930db03a8 100644
--- a/packages/taler-wallet-core/src/crypto/cryptoTypes.ts
+++ b/packages/taler-wallet-core/src/crypto/cryptoTypes.ts
@@ -268,7 +268,14 @@ export interface SignRefundResponse {
sig: string;
}
-export interface SignRefundResponse {}
+
+export interface SignDeletePurseRequest {
+ pursePriv: string;
+}
+
+export interface SignDeletePurseResponse {
+ sig: EddsaSignatureString;
+}
export interface SignReservePurseCreateRequest {
mergeTimestamp: TalerProtocolTimestamp;
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index 3f19822c8..d64d1fbc6 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -1787,6 +1787,7 @@ export enum PeerPushPaymentInitiationStatus {
Done = 50 /* DORMANT_START */,
Aborted = 51,
Failed = 52,
+ Expired = 53,
}
export interface PeerPushPaymentCoinSelection {
@@ -1844,6 +1845,8 @@ export interface PeerPushPaymentInitiationRecord {
timestampCreated: TalerPreciseTimestamp;
+ abortRefreshGroupId?: string;
+
/**
* Status of the peer push payment initiation.
*/
diff --git a/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts
index 333202a69..e9c34cf73 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts
@@ -83,7 +83,6 @@ import {
stopLongpolling,
} from "./transactions.js";
import {
- checkWithdrawalKycStatus,
getExchangeWithdrawalInfo,
internalCreateWithdrawalGroup,
processWithdrawalGroup,
@@ -241,6 +240,62 @@ async function longpollKycStatus(
};
}
+async function processPeerPullCreditAbortingDeletePurse(
+ ws: InternalWalletState,
+ peerPullIni: PeerPullPaymentInitiationRecord,
+): Promise<OperationAttemptResult> {
+ const { pursePub, pursePriv } = peerPullIni;
+ const transactionId = constructTransactionIdentifier({
+ tag: TransactionType.PeerPushDebit,
+ pursePub,
+ });
+
+ const sigResp = await ws.cryptoApi.signDeletePurse({
+ pursePriv,
+ });
+ const purseUrl = new URL(
+ `purses/${pursePub}`,
+ peerPullIni.exchangeBaseUrl,
+ );
+ const resp = await ws.http.fetch(purseUrl.href, {
+ method: "DELETE",
+ headers: {
+ "taler-purse-signature": sigResp.sig,
+ },
+ });
+ logger.info(`deleted purse with response status ${resp.status}`);
+
+ const transitionInfo = await ws.db
+ .mktx((x) => [
+ x.peerPullPaymentInitiations,
+ x.refreshGroups,
+ x.denominations,
+ x.coinAvailability,
+ x.coins,
+ ])
+ .runReadWrite(async (tx) => {
+ const ppiRec = await tx.peerPullPaymentInitiations.get(pursePub);
+ if (!ppiRec) {
+ return undefined;
+ }
+ if (
+ ppiRec.status !== PeerPullPaymentInitiationStatus.AbortingDeletePurse
+ ) {
+ return undefined;
+ }
+ const oldTxState = computePeerPullCreditTransactionState(ppiRec);
+ ppiRec.status = PeerPullPaymentInitiationStatus.Aborted;
+ const newTxState = computePeerPullCreditTransactionState(ppiRec);
+ return {
+ oldTxState,
+ newTxState,
+ };
+ });
+ notifyTransition(ws, transactionId, transitionInfo);
+
+ return OperationAttemptResult.pendingEmpty();
+}
+
export async function processPeerPullCredit(
ws: InternalWalletState,
pursePub: string,
@@ -320,6 +375,8 @@ export async function processPeerPullCredit(
}
case PeerPullPaymentInitiationStatus.PendingCreatePurse:
break;
+ case PeerPullPaymentInitiationStatus.AbortingDeletePurse:
+ return await processPeerPullCreditAbortingDeletePurse(ws, pullIni);
default:
throw Error(`unknown PeerPullPaymentInitiationStatus ${pullIni.status}`);
}
diff --git a/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts
index fdec42bbd..212d69eea 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts
@@ -15,55 +15,152 @@
*/
import {
- ConfirmPeerPullDebitRequest,
AcceptPeerPullPaymentResponse,
Amounts,
- j2s,
- TalerError,
- TalerErrorCode,
- TransactionType,
- RefreshReason,
+ ConfirmPeerPullDebitRequest,
+ ExchangePurseDeposits,
Logger,
PeerContractTerms,
PreparePeerPullDebitRequest,
PreparePeerPullDebitResponse,
+ RefreshReason,
+ TalerError,
+ TalerErrorCode,
TalerPreciseTimestamp,
+ TransactionAction,
+ TransactionMajorState,
+ TransactionMinorState,
+ TransactionState,
+ TransactionType,
+ codecForAny,
codecForExchangeGetContractResponse,
codecForPeerContractTerms,
decodeCrock,
eddsaGetPublic,
encodeCrock,
getRandomBytes,
+ j2s,
parsePayPullUri,
- TransactionAction,
- TransactionMajorState,
- TransactionMinorState,
- TransactionState,
} from "@gnu-taler/taler-util";
+import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
import {
InternalWalletState,
PeerPullDebitRecordStatus,
PeerPullPaymentIncomingRecord,
PendingTaskType,
} from "../index.js";
-import { TaskIdentifiers, constructTaskIdentifier } from "../util/retries.js";
-import { spendCoins, runOperationWithErrorReporting } from "./common.js";
+import { assertUnreachable } from "../util/assertUnreachable.js";
+import {
+ OperationAttemptResult,
+ OperationAttemptResultType,
+ TaskIdentifiers,
+ constructTaskIdentifier,
+} from "../util/retries.js";
+import { runOperationWithErrorReporting, spendCoins } from "./common.js";
import {
codecForExchangePurseStatus,
getTotalPeerPaymentCost,
+ queryCoinInfosForSelection,
selectPeerCoins,
} from "./pay-peer-common.js";
-import { processPeerPullDebit } from "./pay-peer-push-credit.js";
import {
constructTransactionIdentifier,
notifyTransition,
stopLongpolling,
} from "./transactions.js";
-import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
-import { assertUnreachable } from "../util/assertUnreachable.js";
const logger = new Logger("pay-peer-pull-debit.ts");
+async function processPeerPullDebitPendingDeposit(
+ ws: InternalWalletState,
+ peerPullInc: PeerPullPaymentIncomingRecord,
+): Promise<OperationAttemptResult> {
+ const peerPullPaymentIncomingId = peerPullInc.peerPullPaymentIncomingId;
+ const pursePub = peerPullInc.pursePub;
+
+ const coinSel = peerPullInc.coinSel;
+ if (!coinSel) {
+ throw Error("invalid state, no coins selected");
+ }
+
+ const coins = await queryCoinInfosForSelection(ws, coinSel);
+
+ const depositSigsResp = await ws.cryptoApi.signPurseDeposits({
+ exchangeBaseUrl: peerPullInc.exchangeBaseUrl,
+ pursePub: peerPullInc.pursePub,
+ coins,
+ });
+
+ const purseDepositUrl = new URL(
+ `purses/${pursePub}/deposit`,
+ peerPullInc.exchangeBaseUrl,
+ );
+
+ const depositPayload: ExchangePurseDeposits = {
+ deposits: depositSigsResp.deposits,
+ };
+
+ if (logger.shouldLogTrace()) {
+ logger.trace(`purse deposit payload: ${j2s(depositPayload)}`);
+ }
+
+ const httpResp = await ws.http.postJson(purseDepositUrl.href, depositPayload);
+ const resp = await readSuccessResponseJsonOrThrow(httpResp, codecForAny());
+ logger.trace(`purse deposit response: ${j2s(resp)}`);
+
+ await ws.db
+ .mktx((x) => [x.peerPullPaymentIncoming])
+ .runReadWrite(async (tx) => {
+ const pi = await tx.peerPullPaymentIncoming.get(
+ peerPullPaymentIncomingId,
+ );
+ if (!pi) {
+ throw Error("peer pull payment not found anymore");
+ }
+ if (pi.status === PeerPullDebitRecordStatus.PendingDeposit) {
+ pi.status = PeerPullDebitRecordStatus.DonePaid;
+ }
+ await tx.peerPullPaymentIncoming.put(pi);
+ });
+
+ return {
+ type: OperationAttemptResultType.Finished,
+ result: undefined,
+ };
+}
+
+async function processPeerPullDebitAbortingRefresh(
+ ws: InternalWalletState,
+ peerPullInc: PeerPullPaymentIncomingRecord,
+): Promise<OperationAttemptResult> {
+ throw Error("not implemented");
+}
+
+export async function processPeerPullDebit(
+ ws: InternalWalletState,
+ peerPullPaymentIncomingId: string,
+): Promise<OperationAttemptResult> {
+ const peerPullInc = await ws.db
+ .mktx((x) => [x.peerPullPaymentIncoming])
+ .runReadOnly(async (tx) => {
+ return tx.peerPullPaymentIncoming.get(peerPullPaymentIncomingId);
+ });
+ if (!peerPullInc) {
+ throw Error("peer pull debit not found");
+ }
+
+ switch (peerPullInc.status) {
+ case PeerPullDebitRecordStatus.PendingDeposit:
+ return await processPeerPullDebitPendingDeposit(ws, peerPullInc);
+ case PeerPullDebitRecordStatus.AbortingRefresh:
+ return await processPeerPullDebitAbortingRefresh(ws, peerPullInc);
+ }
+ return {
+ type: OperationAttemptResultType.Finished,
+ result: undefined,
+ }
+}
+
export async function confirmPeerPullDebit(
ws: InternalWalletState,
req: ConfirmPeerPullDebitRequest,
diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts
index 91b0b6022..1a79c7b87 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts
@@ -553,76 +553,6 @@ export async function confirmPeerPushCredit(
};
}
-export async function processPeerPullDebit(
- ws: InternalWalletState,
- peerPullPaymentIncomingId: string,
-): Promise<OperationAttemptResult> {
- const peerPullInc = await ws.db
- .mktx((x) => [x.peerPullPaymentIncoming])
- .runReadOnly(async (tx) => {
- return tx.peerPullPaymentIncoming.get(peerPullPaymentIncomingId);
- });
- if (!peerPullInc) {
- throw Error("peer pull debit not found");
- }
- if (peerPullInc.status === PeerPullDebitRecordStatus.PendingDeposit) {
- const pursePub = peerPullInc.pursePub;
-
- const coinSel = peerPullInc.coinSel;
- if (!coinSel) {
- throw Error("invalid state, no coins selected");
- }
-
- const coins = await queryCoinInfosForSelection(ws, coinSel);
-
- const depositSigsResp = await ws.cryptoApi.signPurseDeposits({
- exchangeBaseUrl: peerPullInc.exchangeBaseUrl,
- pursePub: peerPullInc.pursePub,
- coins,
- });
-
- const purseDepositUrl = new URL(
- `purses/${pursePub}/deposit`,
- peerPullInc.exchangeBaseUrl,
- );
-
- const depositPayload: ExchangePurseDeposits = {
- deposits: depositSigsResp.deposits,
- };
-
- if (logger.shouldLogTrace()) {
- logger.trace(`purse deposit payload: ${j2s(depositPayload)}`);
- }
-
- const httpResp = await ws.http.postJson(
- purseDepositUrl.href,
- depositPayload,
- );
- const resp = await readSuccessResponseJsonOrThrow(httpResp, codecForAny());
- logger.trace(`purse deposit response: ${j2s(resp)}`);
- }
-
- await ws.db
- .mktx((x) => [x.peerPullPaymentIncoming])
- .runReadWrite(async (tx) => {
- const pi = await tx.peerPullPaymentIncoming.get(
- peerPullPaymentIncomingId,
- );
- if (!pi) {
- throw Error("peer pull payment not found anymore");
- }
- if (pi.status === PeerPullDebitRecordStatus.PendingDeposit) {
- pi.status = PeerPullDebitRecordStatus.DonePaid;
- }
- await tx.peerPullPaymentIncoming.put(pi);
- });
-
- return {
- type: OperationAttemptResultType.Finished,
- result: undefined,
- };
-}
-
export async function suspendPeerPushCreditTransaction(
ws: InternalWalletState,
peerPushPaymentIncomingId: string,
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 dead6313d..ac0aa9c87 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
@@ -18,6 +18,7 @@ import {
Amounts,
CheckPeerPushDebitRequest,
CheckPeerPushDebitResponse,
+ CoinRefreshRequest,
ContractTermsUtil,
HttpStatusCode,
InitiatePeerPushDebitRequest,
@@ -27,13 +28,14 @@ import {
TalerError,
TalerErrorCode,
TalerPreciseTimestamp,
+ TalerUriAction,
TransactionAction,
TransactionMajorState,
TransactionMinorState,
TransactionState,
TransactionType,
- constructPayPushUri,
j2s,
+ stringifyTalerUri,
} from "@gnu-taler/taler-util";
import { InternalWalletState } from "../internal-wallet-state.js";
import {
@@ -46,6 +48,8 @@ import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
import {
PeerPushPaymentInitiationRecord,
PeerPushPaymentInitiationStatus,
+ RefreshOperationStatus,
+ createRefreshGroup,
} from "../index.js";
import { PendingTaskType } from "../pending-types.js";
import {
@@ -64,6 +68,7 @@ import {
stopLongpolling,
} from "./transactions.js";
import { assertUnreachable } from "../util/assertUnreachable.js";
+import { checkLogicInvariant } from "../util/invariants.js";
const logger = new Logger("pay-peer-push-debit.ts");
@@ -172,9 +177,89 @@ async function processPeerPushDebitCreateReserve(
};
}
-async function transitionPeerPushDebitFromReadyToDone(
+async function processPeerPushDebitAbortingDeletePurse(
+ ws: InternalWalletState,
+ peerPushInitiation: PeerPushPaymentInitiationRecord,
+): Promise<OperationAttemptResult> {
+ const { pursePub, pursePriv } = peerPushInitiation;
+ const transactionId = constructTransactionIdentifier({
+ tag: TransactionType.PeerPushDebit,
+ pursePub,
+ });
+
+ const sigResp = await ws.cryptoApi.signDeletePurse({
+ pursePriv,
+ });
+ const purseUrl = new URL(
+ `purses/${pursePub}`,
+ peerPushInitiation.exchangeBaseUrl,
+ );
+ const resp = await ws.http.fetch(purseUrl.href, {
+ method: "DELETE",
+ headers: {
+ "taler-purse-signature": sigResp.sig,
+ },
+ });
+ logger.info(`deleted purse with response status ${resp.status}`);
+
+ const transitionInfo = await ws.db
+ .mktx((x) => [
+ x.peerPushPaymentInitiations,
+ x.refreshGroups,
+ x.denominations,
+ x.coinAvailability,
+ x.coins,
+ ])
+ .runReadWrite(async (tx) => {
+ const ppiRec = await tx.peerPushPaymentInitiations.get(pursePub);
+ if (!ppiRec) {
+ return undefined;
+ }
+ if (
+ ppiRec.status !== PeerPushPaymentInitiationStatus.AbortingDeletePurse
+ ) {
+ 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 = PeerPushPaymentInitiationStatus.AbortingRefresh;
+ ppiRec.abortRefreshGroupId = refresh.refreshGroupId;
+ const newTxState = computePeerPushDebitTransactionState(ppiRec);
+ return {
+ oldTxState,
+ newTxState,
+ };
+ });
+ notifyTransition(ws, transactionId, transitionInfo);
+
+ return OperationAttemptResult.pendingEmpty();
+}
+
+interface SimpleTransition {
+ stFrom: PeerPushPaymentInitiationStatus;
+ stTo: PeerPushPaymentInitiationStatus;
+}
+
+async function transitionPeerPushDebitTransaction(
ws: InternalWalletState,
pursePub: string,
+ transitionSpec: SimpleTransition,
): Promise<void> {
const transactionId = constructTransactionIdentifier({
tag: TransactionType.PeerPushDebit,
@@ -187,11 +272,11 @@ async function transitionPeerPushDebitFromReadyToDone(
if (!ppiRec) {
return undefined;
}
- if (ppiRec.status !== PeerPushPaymentInitiationStatus.PendingReady) {
+ if (ppiRec.status !== transitionSpec.stFrom) {
return undefined;
}
const oldTxState = computePeerPushDebitTransactionState(ppiRec);
- ppiRec.status = PeerPushPaymentInitiationStatus.Done;
+ ppiRec.status = transitionSpec.stTo;
const newTxState = computePeerPushDebitTransactionState(ppiRec);
return {
oldTxState,
@@ -201,6 +286,54 @@ async function transitionPeerPushDebitFromReadyToDone(
notifyTransition(ws, transactionId, transitionInfo);
}
+async function processPeerPushDebitAbortingRefresh(
+ ws: InternalWalletState,
+ peerPushInitiation: PeerPushPaymentInitiationRecord,
+): Promise<OperationAttemptResult> {
+ 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.peerPushPaymentInitiations])
+ .runReadWrite(async (tx) => {
+ const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId);
+ let newOpState: PeerPushPaymentInitiationStatus | 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 = PeerPushPaymentInitiationStatus.Failed;
+ } else {
+ if (refreshGroup.operationStatus === RefreshOperationStatus.Finished) {
+ newOpState = PeerPushPaymentInitiationStatus.Aborted;
+ } else if (
+ refreshGroup.operationStatus === RefreshOperationStatus.Failed
+ ) {
+ newOpState = PeerPushPaymentInitiationStatus.Failed;
+ }
+ }
+ if (newOpState) {
+ const newDg = await tx.peerPushPaymentInitiations.get(pursePub);
+ if (!newDg) {
+ return;
+ }
+ const oldTxState = computePeerPushDebitTransactionState(newDg);
+ newDg.status = newOpState;
+ const newTxState = computePeerPushDebitTransactionState(newDg);
+ await tx.peerPushPaymentInitiations.put(newDg);
+ return { oldTxState, newTxState };
+ }
+ return undefined;
+ });
+ notifyTransition(ws, transactionId, transitionInfo);
+ // FIXME: Shouldn't this be finished in some cases?!
+ return OperationAttemptResult.pendingEmpty();
+}
+
/**
* Process the "pending(ready)" state of a peer-push-debit transaction.
*/
@@ -214,7 +347,10 @@ async function processPeerPushDebitReady(
pursePub,
});
runLongpollAsync(ws, retryTag, async (ct) => {
- const mergeUrl = new URL(`purses/${pursePub}/merge`);
+ const mergeUrl = new URL(
+ `purses/${pursePub}/merge`,
+ peerPushInitiation.exchangeBaseUrl,
+ );
mergeUrl.searchParams.set("timeout_ms", "30000");
const resp = await ws.http.fetch(mergeUrl.href, {
// timeout: getReserveRequestTimeout(withdrawalGroup),
@@ -226,16 +362,30 @@ async function processPeerPushDebitReady(
codecForExchangePurseStatus(),
);
if (purseStatus.deposit_timestamp) {
- await transitionPeerPushDebitFromReadyToDone(
+ await transitionPeerPushDebitTransaction(
ws,
peerPushInitiation.pursePub,
+ {
+ stFrom: PeerPushPaymentInitiationStatus.PendingReady,
+ stTo: PeerPushPaymentInitiationStatus.Done,
+ },
);
return {
ready: true,
};
}
} else if (resp.status === HttpStatusCode.Gone) {
- // FIXME: transition the reserve into the expired state
+ await transitionPeerPushDebitTransaction(
+ ws,
+ peerPushInitiation.pursePub,
+ {
+ stFrom: PeerPushPaymentInitiationStatus.PendingReady,
+ stTo: PeerPushPaymentInitiationStatus.Expired,
+ },
+ );
+ return {
+ ready: true,
+ };
}
return {
ready: false,
@@ -280,6 +430,10 @@ export async function processPeerPushDebit(
return processPeerPushDebitCreateReserve(ws, peerPushInitiation);
case PeerPushPaymentInitiationStatus.PendingReady:
return processPeerPushDebitReady(ws, peerPushInitiation);
+ case PeerPushPaymentInitiationStatus.AbortingDeletePurse:
+ return processPeerPushDebitAbortingDeletePurse(ws, peerPushInitiation);
+ case PeerPushPaymentInitiationStatus.AbortingRefresh:
+ return processPeerPushDebitAbortingRefresh(ws, peerPushInitiation);
}
return {
@@ -396,7 +550,8 @@ export async function initiatePeerPushDebit(
mergePriv: mergePair.priv,
pursePub: pursePair.pub,
exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl,
- talerUri: constructPayPushUri({
+ talerUri: stringifyTalerUri({
+ type: TalerUriAction.PayPush,
exchangeBaseUrl: coinSelRes.result.exchangeBaseUrl,
contractPriv: contractKeyPair.priv,
}),
@@ -431,6 +586,8 @@ export function computePeerPushDebitTransactionActions(
return [TransactionAction.Suspend, TransactionAction.Abort];
case PeerPushPaymentInitiationStatus.Done:
return [TransactionAction.Delete];
+ case PeerPushPaymentInitiationStatus.Expired:
+ return [TransactionAction.Delete];
case PeerPushPaymentInitiationStatus.Failed:
return [TransactionAction.Delete];
}
@@ -474,9 +631,9 @@ export async function abortPeerPushDebitTransaction(
case PeerPushPaymentInitiationStatus.Done:
case PeerPushPaymentInitiationStatus.AbortingDeletePurse:
case PeerPushPaymentInitiationStatus.Aborted:
- // Do nothing
- break;
+ case PeerPushPaymentInitiationStatus.Expired:
case PeerPushPaymentInitiationStatus.Failed:
+ // Do nothing
break;
default:
assertUnreachable(pushDebitRec.status);
@@ -535,6 +692,7 @@ export async function failPeerPushDebitTransaction(
case PeerPushPaymentInitiationStatus.Done:
case PeerPushPaymentInitiationStatus.Aborted:
case PeerPushPaymentInitiationStatus.Failed:
+ case PeerPushPaymentInitiationStatus.Expired:
// Do nothing
break;
default:
@@ -598,6 +756,7 @@ export async function suspendPeerPushDebitTransaction(
case PeerPushPaymentInitiationStatus.Done:
case PeerPushPaymentInitiationStatus.Aborted:
case PeerPushPaymentInitiationStatus.Failed:
+ case PeerPushPaymentInitiationStatus.Expired:
// Do nothing
break;
default:
@@ -660,6 +819,7 @@ export async function resumePeerPushDebitTransaction(
case PeerPushPaymentInitiationStatus.Done:
case PeerPushPaymentInitiationStatus.Aborted:
case PeerPushPaymentInitiationStatus.Failed:
+ case PeerPushPaymentInitiationStatus.Expired:
// Do nothing
break;
default:
@@ -681,7 +841,6 @@ export async function resumePeerPushDebitTransaction(
notifyTransition(ws, transactionId, transitionInfo);
}
-
export function computePeerPushDebitTransactionState(
ppiRecord: PeerPushPaymentInitiationRecord,
): TransactionState {
@@ -738,5 +897,10 @@ export function computePeerPushDebitTransactionState(
return {
major: TransactionMajorState.Failed,
};
+ case PeerPushPaymentInitiationStatus.Expired:
+ return {
+ major: TransactionMajorState.Expired,
+ };
}
-} \ No newline at end of file
+}
+