commit ce33080c539678355bba05b0a7312bbcfc85a6b0
parent 2d93886a7506296470f7328b5c3742133b17c33f
Author: Antoine A <>
Date: Thu, 17 Apr 2025 14:15:08 +0200
wallet-core: improve pay-peer-push
Diffstat:
2 files changed, 63 insertions(+), 12 deletions(-)
diff --git a/packages/taler-harness/src/integrationtests/test-peer-push.ts b/packages/taler-harness/src/integrationtests/test-peer-push.ts
@@ -214,14 +214,12 @@ export async function runPeerPushTest(t: GlobalTestState) {
minor: TransactionMinorState.Merge
},
}),
- // FIXME should be aborted
wallet4.call(WalletApiOperation.TestingWaitTransactionState, {
transactionId: prepare4.transactionId,
txState: {
- major: TransactionMajorState.Dialog,
- minor: TransactionMinorState.Proposed
+ major: TransactionMajorState.Aborted,
},
- }),
+ })
]);
{
@@ -282,12 +280,10 @@ export async function runPeerPushTest(t: GlobalTestState) {
minor: TransactionMinorState.Merge
},
}),
- // FIXME should be aborted
wallet3.call(WalletApiOperation.TestingWaitTransactionState, {
transactionId: prepare3.transactionId,
txState: {
- major: TransactionMajorState.Dialog,
- minor: TransactionMinorState.Proposed
+ major: TransactionMajorState.Aborted
},
}),
]);
@@ -342,12 +338,10 @@ export async function runPeerPushTest(t: GlobalTestState) {
minor: TransactionMinorState.Merge
},
}),
- // FIXME should be aborted
wallet3.call(WalletApiOperation.TestingWaitTransactionState, {
transactionId: prepare3.transactionId,
txState: {
- major: TransactionMajorState.Dialog,
- minor: TransactionMinorState.Proposed
+ major: TransactionMajorState.Aborted
},
}),
]);
diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts
@@ -31,6 +31,7 @@ import {
PreparePeerPushCreditResponse,
TalerErrorDetail,
TalerPreciseTimestamp,
+ TalerProtocolTimestamp,
Transaction,
TransactionAction,
TransactionIdStr,
@@ -464,7 +465,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext {
assertUnreachable(rec.status);
}
});
- this.wex.taskScheduler.startShepherdTask(this.taskId);
+ this.wex.taskScheduler.stopShepherdTask(this.taskId);
}
async resumeTransaction(): Promise<void> {
@@ -530,7 +531,6 @@ export class PeerPushCreditTransactionContext implements TransactionContext {
}
});
this.wex.taskScheduler.stopShepherdTask(this.taskId);
- this.wex.taskScheduler.startShepherdTask(this.taskId);
}
}
@@ -694,6 +694,7 @@ export async function preparePeerPushCredit(
},
);
notifyTransition(wex, ctx.transactionId, transitionInfo);
+ wex.taskScheduler.startShepherdTask(ctx.taskId);
const currency = Amounts.currencyOf(wi.withdrawalAmountRaw);
const scopeInfo = await wex.db.runAllStoresReadOnlyTx(
@@ -1072,6 +1073,59 @@ async function handlePendingWithdrawing(
}
}
+async function processPeerPushDebitDialogProposed(
+ wex: WalletExecutionContext,
+ pullIni: PeerPushPaymentIncomingRecord,
+): Promise<TaskRunResult> {
+ const purseDepositUrl = new URL(
+ `purses/${pullIni.pursePub}/merge`,
+ pullIni.exchangeBaseUrl,
+ );
+ logger.info(`querying purse status via ${purseDepositUrl.href}`);
+ const resp = await wex.ws.runLongpollQueueing(
+ wex.cancellationToken,
+ purseDepositUrl.hostname,
+ async (timeoutMs) => {
+ purseDepositUrl.searchParams.set("timeout_ms", `${timeoutMs}`);
+ return await wex.http.fetch(purseDepositUrl.href, {
+ cancellationToken: wex.cancellationToken,
+ });
+ },
+ );
+ const ctx = new PeerPushCreditTransactionContext(wex, pullIni.peerPushCreditId);
+
+ logger.info(`purse status code: HTTP ${resp.status}`);
+
+ switch (resp.status) {
+ case HttpStatusCode.Gone: {
+ // Exchange says that purse doesn't exist anymore => expired!
+ await ctx.transitionStatus(PeerPushCreditStatus.DialogProposed, PeerPushCreditStatus.Aborted);
+ return TaskRunResult.finished();
+ }
+ case HttpStatusCode.NotFound:
+ // FIXME: Maybe check error code? 404 could also mean something else.
+ return TaskRunResult.longpollReturnedPending();
+ }
+
+ const result = await readSuccessResponseJsonOrThrow(
+ resp,
+ codecForExchangePurseStatus(),
+ );
+
+ logger.trace(`purse status: ${j2s(result)}`);
+
+ const mergeTimestamp = result.merge_timestamp;
+
+ if (mergeTimestamp != null && !TalerProtocolTimestamp.isNever(mergeTimestamp)) {
+ logger.info("purse completed by another wallet");
+ await ctx.transitionStatus(PeerPushCreditStatus.DialogProposed, PeerPushCreditStatus.Aborted);
+ return TaskRunResult.finished();
+ }
+
+ return TaskRunResult.longpollReturnedPending();
+}
+
+
export async function processPeerPushCredit(
wex: WalletExecutionContext,
peerPushCreditId: string,
@@ -1115,6 +1169,8 @@ export async function processPeerPushCredit(
);
switch (peerInc.status) {
+ case PeerPushCreditStatus.DialogProposed:
+ return processPeerPushDebitDialogProposed(wex, peerInc);
case PeerPushCreditStatus.PendingMergeKycRequired: {
if (!peerInc.kycPaytoHash) {
throw Error("invalid state, kycPaytoHash required");
@@ -1259,6 +1315,7 @@ export async function confirmPeerPushCredit(
await ctx.transitionStatus(PeerPushCreditStatus.DialogProposed, PeerPushCreditStatus.PendingMerge);
+ wex.taskScheduler.stopShepherdTask(ctx.taskId);
wex.taskScheduler.startShepherdTask(ctx.taskId);
return {