aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-06-19 12:02:43 +0200
committerFlorian Dold <florian@dold.me>2023-06-19 12:02:43 +0200
commitbcff03949b40d0d37069bdb7af941061e367a093 (patch)
tree2c39d704d1400a35443df95eb84dc99df8094c39 /packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts
parented01d407e7e224960337614385676dcf1ae6ca8d (diff)
downloadwallet-core-bcff03949b40d0d37069bdb7af941061e367a093.tar.xz
wallet-core: implement coin selection repair for p2p payments
Diffstat (limited to 'packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts')
-rw-r--r--packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts104
1 files changed, 99 insertions, 5 deletions
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 2835a1f64..33d317c6f 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
@@ -28,6 +28,7 @@ import {
TalerError,
TalerErrorCode,
TalerPreciseTimestamp,
+ TalerProtocolViolationError,
TalerUriAction,
TransactionAction,
TransactionMajorState,
@@ -47,8 +48,13 @@ import {
getTotalPeerPaymentCost,
codecForExchangePurseStatus,
queryCoinInfosForSelection,
+ PeerCoinRepair,
} from "./pay-peer-common.js";
-import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
+import {
+ HttpResponse,
+ readSuccessResponseJsonOrThrow,
+ readTalerErrorResponse,
+} from "@gnu-taler/taler-util/http";
import {
PeerPushPaymentInitiationRecord,
PeerPushPaymentInitiationStatus,
@@ -97,6 +103,73 @@ export async function checkPeerPushDebit(
};
}
+async function handlePurseCreationConflict(
+ ws: InternalWalletState,
+ peerPushInitiation: PeerPushPaymentInitiationRecord,
+ resp: HttpResponse,
+): Promise<OperationAttemptResult> {
+ const pursePub = peerPushInitiation.pursePub;
+ const errResp = await readTalerErrorResponse(resp);
+ if (errResp.code !== TalerErrorCode.EXCHANGE_GENERIC_INSUFFICIENT_FUNDS) {
+ await failPeerPushDebitTransaction(ws, pursePub);
+ return OperationAttemptResult.finishedEmpty();
+ }
+
+ // FIXME: Properly parse!
+ const brokenCoinPub = (errResp as any).coin_pub;
+ logger.trace(`excluded broken coin pub=${brokenCoinPub}`);
+
+ if (!brokenCoinPub) {
+ // FIXME: Details!
+ throw new TalerProtocolViolationError();
+ }
+
+ const instructedAmount = Amounts.parseOrThrow(peerPushInitiation.amount);
+
+ const repair: PeerCoinRepair = {
+ coinPubs: peerPushInitiation.coinSel.coinPubs,
+ contribs: peerPushInitiation.coinSel.contributions.map((x) =>
+ Amounts.parseOrThrow(x),
+ ),
+ exchangeBaseUrl: peerPushInitiation.exchangeBaseUrl,
+ };
+
+ const coinSelRes = await selectPeerCoins(ws, { instructedAmount, repair });
+
+ if (coinSelRes.type == "failure") {
+ // FIXME: Details!
+ throw Error(
+ "insufficient balance to re-select coins to repair double spending",
+ );
+ }
+
+ await ws.db
+ .mktx((x) => [x.peerPushPaymentInitiations])
+ .runReadWrite(async (tx) => {
+ const myPpi = await tx.peerPushPaymentInitiations.get(
+ peerPushInitiation.pursePub,
+ );
+ if (!myPpi) {
+ return;
+ }
+ switch (myPpi.status) {
+ case PeerPushPaymentInitiationStatus.PendingCreatePurse:
+ case PeerPushPaymentInitiationStatus.SuspendedCreatePurse: {
+ const sel = coinSelRes.result;
+ myPpi.coinSel = {
+ coinPubs: sel.coins.map((x) => x.coinPub),
+ contributions: sel.coins.map((x) => x.contribution),
+ }
+ break;
+ }
+ default:
+ return;
+ }
+ await tx.peerPushPaymentInitiations.put(myPpi);
+ });
+ return OperationAttemptResult.finishedEmpty();
+}
+
async function processPeerPushDebitCreateReserve(
ws: InternalWalletState,
peerPushInitiation: PeerPushPaymentInitiationRecord,
@@ -175,6 +248,27 @@ async function processPeerPushDebitCreateReserve(
logger.info(`resp: ${j2s(resp)}`);
+ switch (httpResp.status) {
+ case HttpStatusCode.Ok:
+ break;
+ case HttpStatusCode.Forbidden: {
+ // FIXME: Store this error!
+ await failPeerPushDebitTransaction(ws, pursePub);
+ return OperationAttemptResult.finishedEmpty();
+ }
+ case HttpStatusCode.Conflict: {
+ // Handle double-spending
+ return handlePurseCreationConflict(ws, peerPushInitiation, resp);
+ }
+ default: {
+ const errResp = await readTalerErrorResponse(resp);
+ return {
+ type: OperationAttemptResultType.Error,
+ errorDetail: errResp,
+ };
+ }
+ }
+
if (httpResp.status !== HttpStatusCode.Ok) {
// FIXME: do proper error reporting
throw Error("got error response from exchange");
@@ -710,17 +804,17 @@ export async function failPeerPushDebitTransaction(
switch (pushDebitRec.status) {
case PeerPushPaymentInitiationStatus.AbortingRefresh:
case PeerPushPaymentInitiationStatus.SuspendedAbortingRefresh:
- // FIXME: We also need to abort the refresh group!
- newStatus = PeerPushPaymentInitiationStatus.Aborted;
+ // FIXME: What to do about the refresh group?
+ newStatus = PeerPushPaymentInitiationStatus.Failed;
break;
case PeerPushPaymentInitiationStatus.AbortingDeletePurse:
case PeerPushPaymentInitiationStatus.SuspendedAbortingDeletePurse:
- newStatus = PeerPushPaymentInitiationStatus.Aborted;
- break;
case PeerPushPaymentInitiationStatus.PendingReady:
case PeerPushPaymentInitiationStatus.SuspendedReady:
case PeerPushPaymentInitiationStatus.SuspendedCreatePurse:
case PeerPushPaymentInitiationStatus.PendingCreatePurse:
+ newStatus = PeerPushPaymentInitiationStatus.Failed;
+ break;
case PeerPushPaymentInitiationStatus.Done:
case PeerPushPaymentInitiationStatus.Aborted:
case PeerPushPaymentInitiationStatus.Failed: