taler-typescript-core

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

commit 63cd73839548b6c00aa400746d2f84b2b973b098
parent 8fe4b0a52ba9754f8a64bf7951322f542cf37052
Author: Florian Dold <florian@dold.me>
Date:   Wed, 23 Apr 2025 21:03:55 +0200

wallet-core: handle conflict in peer-push-credit

Diffstat:
Mpackages/aml-backoffice-ui/src/stories.test.ts | 5++++-
Mpackages/aml-backoffice-ui/src/stories.tsx | 5++++-
Mpackages/taler-harness/src/integrationtests/test-peer-push.ts | 3+--
Mpackages/taler-util/src/http-client/exchange.ts | 24++++++++++++++----------
Mpackages/taler-util/src/types-taler-exchange.ts | 29+++++++++++++++++++++++++++++
Mpackages/taler-wallet-core/src/pay-peer-push-credit.ts | 10++++++++++
6 files changed, 62 insertions(+), 14 deletions(-)

diff --git a/packages/aml-backoffice-ui/src/stories.test.ts b/packages/aml-backoffice-ui/src/stories.test.ts @@ -76,7 +76,10 @@ function DefaultTestingContext({ url: new URL("/", "http://localhost"), hints: [], lib: { - exchange: undefined!, //FIXME: mock + exchange: undefined!, // FIXME: mock + }, + unthrottledApi: { + exchange: undefined!, // FIXME: mock }, onActivity: () => null!, }; diff --git a/packages/aml-backoffice-ui/src/stories.tsx b/packages/aml-backoffice-ui/src/stories.tsx @@ -64,7 +64,10 @@ function getWrapperForGroup(): FunctionComponent { url: new URL("/", "http://localhost"), hints: [], lib: { - exchange: undefined!, //FIXME: mock + exchange: undefined!, // FIXME: mock + }, + unthrottledApi: { + exchange: undefined!, // FIXME: mock }, onActivity: () => null!, }; diff --git a/packages/taler-harness/src/integrationtests/test-peer-push.ts b/packages/taler-harness/src/integrationtests/test-peer-push.ts @@ -340,8 +340,7 @@ export async function runPeerPushTest(t: GlobalTestState) { wallet2.call(WalletApiOperation.TestingWaitTransactionState, { transactionId: prepare2.transactionId, txState: { - major: TransactionMajorState.Pending, - minor: TransactionMinorState.Merge, + major: TransactionMajorState.Aborted, }, logId: `exp-wallet2`, timeout: { seconds: 10 }, diff --git a/packages/taler-util/src/http-client/exchange.ts b/packages/taler-util/src/http-client/exchange.ts @@ -50,6 +50,7 @@ import { BatchWithdrawResponse, ExchangeKycUploadFormRequest, ExchangeLegacyBatchWithdrawRequest, + ExchangeMergeConflictResponse, ExchangeMergeSuccessResponse, ExchangePurseMergeRequest, ExchangeVersionResponse, @@ -64,6 +65,7 @@ import { codecForEventCounter, codecForExchangeConfig, codecForExchangeKeysResponse, + codecForExchangeMergeConflictResponse, codecForExchangeMergeSuccessResponse, codecForExchangeTransferList, codecForKycProcessClientInformation, @@ -483,14 +485,6 @@ export class TalerExchangeHttpClient { } /** - * https://docs.taler.net/core/api-exchange.html#post--refreshes-$RCH-reveal - * - */ - async releaveCoin(): Promise<never> { - throw Error("not yet implemented"); - } - - /** * https://docs.taler.net/core/api-exchange.html#get--coins-$COIN_PUB-link * */ @@ -584,7 +578,6 @@ export class TalerExchangeHttpClient { * POST /purses/$PURSE_PUB/merge * * https://docs.taler.net/core/api-exchange.html#post--purses-$PURSE_PUB-merge - * */ async postPurseMerge(args: { pursePub: string; @@ -596,6 +589,10 @@ export class TalerExchangeHttpClient { HttpStatusCode.UnavailableForLegalReasons, LegitimizationNeededResponse > + | OperationAlternative< + HttpStatusCode.Conflict, + ExchangeMergeConflictResponse + > > { const mergePurseUrl = new URL( `purses/${args.pursePub}/merge`, @@ -614,8 +611,15 @@ export class TalerExchangeHttpClient { resp.status, codecForLegitimizationNeededResponse(), ); + case HttpStatusCode.Conflict: + return opKnownAlternativeFailure( + resp, + resp.status, + codecForExchangeMergeConflictResponse(), + ); + default: + return opUnknownFailure(resp, await readTalerErrorResponse(resp)); } - throw Error("not yet implemented"); } /** diff --git a/packages/taler-util/src/types-taler-exchange.ts b/packages/taler-util/src/types-taler-exchange.ts @@ -1268,6 +1268,35 @@ export const codecForExchangeMergeSuccessResponse = .build("ExchangeMergeSuccessResponse"); /** + * Doc name: api-exchange/MergeConflict + */ +export interface ExchangeMergeConflictResponse { + // Client-side timestamp of when the merge request was made. + merge_timestamp: Timestamp; + + // EdDSA signature of the purse private key affirming the merge + // over a TALER_PurseMergeSignaturePS. + // Must be of purpose TALER_SIGNATURE_PURSE_MERGE. + merge_sig: EddsaSignatureString; + + // Base URL of the exchange receiving the payment, only present + // if the exchange hosting the reserve is not this exchange. + partner_url?: string; + + // Public key of the reserve that the purse was merged into. + reserve_pub: EddsaPublicKeyString; +} + +export const codecForExchangeMergeConflictResponse = + (): Codec<ExchangeMergeConflictResponse> => + buildCodecForObject<ExchangeMergeConflictResponse>() + .property("merge_timestamp", codecForTimestamp) + .property("merge_sig", codecForString()) + .property("reserve_pub", codecForString()) + .property("partner_url", codecOptional(codecForString())) + .build("ExchangeMergeConflictResponse"); + +/** * Contract terms between two wallets (as opposed to a merchant and wallet). */ export interface PeerContractTerms { diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts @@ -920,6 +920,16 @@ async function handlePendingMerge( logger.info(`kyc legitimization needed response: ${j2s(mergeResp.body)}`); return processPeerPushCreditKycRequired(wex, peerInc, kycLegiNeededResp); } + case HttpStatusCode.Conflict: { + // FIXME: Check signature. + await ctx.transitionStatus( + PeerPushCreditStatus.PendingMerge, + PeerPushCreditStatus.Aborted, + ); + return TaskRunResult.progress(); + } + default: + assertUnreachable(mergeResp); } const withdrawalGroupPrep = await internalPrepareCreateWithdrawalGroup(wex, {