taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 357f9d6eef887a5616c67c58dffaea78f1e5eb41
parent 5765db5ba77cd5ab8ad72f2ce8d117ee79bec809
Author: Antoine A <>
Date:   Thu, 24 Apr 2025 09:39:01 +0200

wallet-core: improve peer-push-credit

Diffstat:
Mpackages/taler-harness/src/integrationtests/test-peer-push.ts | 95+++++++++++++++++++++++++++++++++++++------------------------------------------
Mpackages/taler-util/src/http-client/exchange.ts | 26+++++++++++++++++---------
Mpackages/taler-util/src/operation.ts | 9++++++++-
Mpackages/taler-wallet-core/src/pay-peer-push-credit.ts | 15+++++++++++++--
4 files changed, 82 insertions(+), 63 deletions(-)

diff --git a/packages/taler-harness/src/integrationtests/test-peer-push.ts b/packages/taler-harness/src/integrationtests/test-peer-push.ts @@ -166,15 +166,16 @@ export async function runPeerPushTest(t: GlobalTestState) { t.assertTrue(prepare2.transactionId === idempotent.transactionId); } - await wallet2.call(WalletApiOperation.ConfirmPeerPushCredit, { - transactionId: prepare2.transactionId, - }); - await wallet3.call(WalletApiOperation.ConfirmPeerPushCredit, { - transactionId: prepare3.transactionId, - }); + await Promise.all([ + wallet2.call(WalletApiOperation.ConfirmPeerPushCredit, { + transactionId: prepare2.transactionId, + }), + wallet3.call(WalletApiOperation.ConfirmPeerPushCredit, { + transactionId: prepare3.transactionId, + }), + ]); // Idempotent - // FIXME support conflict during POST /merge await Promise.all([ wallet2.call(WalletApiOperation.ConfirmPeerPushCredit, { transactionId: prepare2.transactionId, @@ -189,35 +190,43 @@ export async function runPeerPushTest(t: GlobalTestState) { transactionId: tx.transactionId, txState: { major: TransactionMajorState.Done, - }, - logId: `confirm-w1`, - timeout: { seconds: 20 }, - }), - wallet2.call(WalletApiOperation.TestingWaitTransactionState, { - transactionId: prepare2.transactionId, - txState: { - major: TransactionMajorState.Done, - }, - logId: `confirm-w2`, - timeout: { seconds: 20 }, - }), - // FIXME should be aborted - wallet3.call(WalletApiOperation.TestingWaitTransactionState, { - transactionId: prepare3.transactionId, - txState: { - major: TransactionMajorState.Pending, - minor: TransactionMinorState.Merge, - }, - logId: `confirm-w3`, - timeout: { seconds: 20 }, + } }), + Promise.race([ + Promise.all([ + wallet2.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: prepare2.transactionId, + txState: { + major: TransactionMajorState.Done + } + }), + wallet3.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: prepare3.transactionId, + txState: { + major: TransactionMajorState.Aborted + } + }), + ]), + Promise.all([ + wallet2.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: prepare2.transactionId, + txState: { + major: TransactionMajorState.Aborted, + } + }), + wallet3.call(WalletApiOperation.TestingWaitTransactionState, { + transactionId: prepare3.transactionId, + txState: { + major: TransactionMajorState.Done + } + }), + ]) + ]), wallet4.call(WalletApiOperation.TestingWaitTransactionState, { transactionId: prepare4.transactionId, txState: { major: TransactionMajorState.Aborted, - }, - logId: `confirm-w4`, - timeout: { seconds: 20 }, + } }), ]); @@ -253,13 +262,6 @@ export async function runPeerPushTest(t: GlobalTestState) { transactionId: tx.transactionId, }); - await wallet1.call(WalletApiOperation.TestingWaitTransactionState, { - transactionId: tx.transactionId, - txState: { - major: TransactionMajorState.Aborted, - }, - }); - await wallet2.call(WalletApiOperation.ConfirmPeerPushCredit, { transactionId: prepare2.transactionId, }); @@ -271,12 +273,10 @@ export async function runPeerPushTest(t: GlobalTestState) { major: TransactionMajorState.Aborted, }, }), - // FIXME should be aborted wallet2.call(WalletApiOperation.TestingWaitTransactionState, { transactionId: prepare2.transactionId, txState: { - major: TransactionMajorState.Pending, - minor: TransactionMinorState.Merge, + major: TransactionMajorState.Aborted, }, }), wallet3.call(WalletApiOperation.TestingWaitTransactionState, { @@ -332,26 +332,19 @@ export async function runPeerPushTest(t: GlobalTestState) { transactionId: tx.transactionId, txState: { major: TransactionMajorState.Aborted, - }, - logId: `exp-wallet1`, - timeout: { seconds: 10 }, + } }), - // FIXME should be aborted wallet2.call(WalletApiOperation.TestingWaitTransactionState, { transactionId: prepare2.transactionId, txState: { major: TransactionMajorState.Aborted, - }, - logId: `exp-wallet2`, - timeout: { seconds: 10 }, + } }), wallet3.call(WalletApiOperation.TestingWaitTransactionState, { transactionId: prepare3.transactionId, txState: { major: TransactionMajorState.Aborted, - }, - logId: `exp-wallet3`, - timeout: { seconds: 10 }, + } }), ]); } diff --git a/packages/taler-util/src/http-client/exchange.ts b/packages/taler-util/src/http-client/exchange.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2022-2024 Taler Systems S.A. + (C) 2022-2025 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -32,9 +32,11 @@ import { opEmptySuccess, opFixedSuccess, opKnownAlternativeFailure, + opKnownFailure, opKnownHttpFailure, opSuccessFromHttp, opUnknownFailure, + opUnknownHttpFailure, } from "../operation.js"; import { EddsaPrivP, encodeCrock } from "../taler-crypto.js"; import { @@ -582,17 +584,19 @@ export class TalerExchangeHttpClient { async postPurseMerge(args: { pursePub: string; body: ExchangePurseMergeRequest; - cancellationToken?: CancellationToken; + cancellationToken: CancellationToken; }): Promise< | OperationOk<ExchangeMergeSuccessResponse> | OperationAlternative< - HttpStatusCode.UnavailableForLegalReasons, - LegitimizationNeededResponse - > + HttpStatusCode.UnavailableForLegalReasons, + LegitimizationNeededResponse + > | OperationAlternative< - HttpStatusCode.Conflict, - ExchangeMergeConflictResponse - > + HttpStatusCode.Conflict, + ExchangeMergeConflictResponse + > + | OperationFail<HttpStatusCode.NotFound> + | OperationFail<HttpStatusCode.Gone> > { const mergePurseUrl = new URL( `purses/${args.pursePub}/merge`, @@ -601,6 +605,7 @@ export class TalerExchangeHttpClient { const resp = await this.httpLib.fetch(mergePurseUrl.href, { method: "POST", body: args.body, + cancellationToken: args.cancellationToken }); switch (resp.status) { case HttpStatusCode.Ok: @@ -617,8 +622,11 @@ export class TalerExchangeHttpClient { resp.status, codecForExchangeMergeConflictResponse(), ); + case HttpStatusCode.Gone: + case HttpStatusCode.NotFound: + return opKnownHttpFailure(resp.status, resp); default: - return opUnknownFailure(resp, await readTalerErrorResponse(resp)); + return opUnknownHttpFailure(resp); } } diff --git a/packages/taler-util/src/operation.ts b/packages/taler-util/src/operation.ts @@ -1,6 +1,6 @@ /* This file is part of GNU Taler - (C) 2023-2024 Taler Systems S.A. + (C) 2023-2025 Taler Systems S.A. GNU Taler is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software @@ -139,6 +139,13 @@ export async function opKnownHttpFailure<T extends HttpStatusCode>( return { type: "fail", case: s, detail }; } +export async function opUnknownHttpFailure( + resp: HttpResponse, +): Promise<never> { + const detail = await readTalerErrorResponse(resp); + return opUnknownFailure(resp, detail) +} + export function opKnownTalerFailure<T extends TalerErrorCode>( s: T, detail: TalerErrorDetail, diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts @@ -317,7 +317,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { amountEffective: isUnsuccessfulTransaction(txState) ? Amounts.stringify(Amounts.zeroOfAmount(peerContractTerms.amount)) : // FIXME: This is wrong, needs to consider fees! - Amounts.stringify(peerContractTerms.amount), + Amounts.stringify(peerContractTerms.amount), amountRaw: Amounts.stringify(peerContractTerms.amount), exchangeBaseUrl: pushInc.exchangeBaseUrl, info: { @@ -922,11 +922,22 @@ async function handlePendingMerge( } case HttpStatusCode.Conflict: { // FIXME: Check signature. + // FIXME: status completed by other await ctx.transitionStatus( PeerPushCreditStatus.PendingMerge, PeerPushCreditStatus.Aborted, ); - return TaskRunResult.progress(); + return TaskRunResult.finished(); + } + case HttpStatusCode.NotFound: { + await ctx.failTransaction(mergeResp.detail); + return TaskRunResult.finished(); + } + case HttpStatusCode.Gone: { + // FIXME: status expired + ctx.abortTransaction() + await ctx.failTransaction(mergeResp.detail); + return TaskRunResult.finished(); } default: assertUnreachable(mergeResp);