taler-typescript-core

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

commit c80cbe224219a230240dafbaeec0ed83563b3b56
parent 0e6108a744e461547540f4002fb22e56abcb76fe
Author: Florian Dold <dold@taler.net>
Date:   Thu, 20 Aug 2026 19:06:43 +0200

wallet-core: make database-less deposit retries stable

Diffstat:
Apackages/taler-wallet-core/src/dbless.test.ts | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-wallet-core/src/dbless.ts | 27++++++++++++++++++++++-----
2 files changed, 102 insertions(+), 5 deletions(-)

diff --git a/packages/taler-wallet-core/src/dbless.test.ts b/packages/taler-wallet-core/src/dbless.test.ts @@ -0,0 +1,80 @@ +/* + This file is part of GNU Taler + (C) 2026 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 + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { + AmountString, + TalerProtocolTimestamp, +} from "@gnu-taler/taler-util"; +import assert from "node:assert"; +import { test } from "node:test"; +import { findDenomOrThrow, getDepositTimestamps } from "./dbless.js"; +import { ExchangeInfo } from "./exchanges.js"; + +test("deposit retries reuse the caller-provided protocol timestamp", () => { + const timestamp = TalerProtocolTimestamp.fromSeconds(1_800_000_000); + assert.deepStrictEqual(getDepositTimestamps(timestamp), { + depositTimestamp: timestamp, + refundDeadline: timestamp, + wireTransferDeadline: timestamp, + }); +}); + +test("denomination lookup skips an exchange key marked lost", () => { + const stamp = TalerProtocolTimestamp.fromSeconds(1_800_000_000); + const exchangeInfo = { + keys: { + base_url: "https://exchange.example/", + master_public_key: "master-public-key", + denominations: [ + { + cipher: "RSA", + value: "TESTKUDOS:8", + fee_deposit: "TESTKUDOS:0", + fee_refresh: "TESTKUDOS:0", + fee_refund: "TESTKUDOS:0", + fee_withdraw: "TESTKUDOS:0", + denoms: [ + { + rsa_pub: "040000W1", + master_sig: "lost-master-signature", + stamp_start: stamp, + stamp_expire_withdraw: stamp, + stamp_expire_deposit: stamp, + stamp_expire_legal: stamp, + lost: true, + }, + { + rsa_pub: "040000W2", + master_sig: "live-master-signature", + stamp_start: stamp, + stamp_expire_withdraw: stamp, + stamp_expire_deposit: stamp, + stamp_expire_legal: stamp, + lost: false, + }, + ], + }, + ], + }, + } as unknown as ExchangeInfo; + + const denomination = findDenomOrThrow( + exchangeInfo, + "TESTKUDOS:8" as AmountString, + ); + assert.strictEqual(denomination.isLost, false); + assert.strictEqual(denomination.masterSig, "live-master-signature"); +}); diff --git a/packages/taler-wallet-core/src/dbless.ts b/packages/taler-wallet-core/src/dbless.ts @@ -225,6 +225,9 @@ export function findDenomOrThrow( ageMask = denomFamily.age_mask; } for (const denom of denomFamily.denoms) { + if (denom.lost) { + continue; + } const denomPub: DenominationPubKey = { age_mask: ageMask, cipher: DenomKeyType.Rsa, @@ -246,7 +249,7 @@ export function findDenomOrThrow( stampStart: denom.stamp_start, value: denomFamily.value, isOffered: true, - isLost: denom.lost ?? false, + isLost: false, masterSig: denom.master_sig, }; return di; @@ -301,12 +304,12 @@ export async function depositCoinBatch(args: { const depositPayto = args.depositPayto ?? "payto://x-taler-bank/localhost/foo?receiver-name=foo"; const wireSalt = args.wireSalt ?? encodeCrock(getRandomBytes(16)); - const timestampNow = AbsoluteTime.toProtocolTimestamp(AbsoluteTime.now()); + const timestamps = getDepositTimestamps(args.timestamp); const contractTermsHash = args.contractTermsHash ?? encodeCrock(getRandomBytes(64)); - const depositTimestamp = timestampNow; - const refundDeadline = timestampNow; - const wireTransferDeadline = timestampNow; + const depositTimestamp = timestamps.depositTimestamp; + const refundDeadline = timestamps.refundDeadline; + const wireTransferDeadline = timestamps.wireTransferDeadline; let merchantPriv: EddsaPrivateKeyString; let merchantPub: EddsaPublicKeyString; if (args.merchantPriv) { @@ -372,6 +375,20 @@ export async function depositCoinBatch(args: { succeedOrThrow(await exchangeClient.batchDeposit({ body: requestBody })); } +export function getDepositTimestamps(timestamp?: TalerProtocolTimestamp): { + depositTimestamp: TalerProtocolTimestamp; + refundDeadline: TalerProtocolTimestamp; + wireTransferDeadline: TalerProtocolTimestamp; +} { + const effectiveTimestamp = + timestamp ?? AbsoluteTime.toProtocolTimestamp(AbsoluteTime.now()); + return { + depositTimestamp: effectiveTimestamp, + refundDeadline: effectiveTimestamp, + wireTransferDeadline: effectiveTimestamp, + }; +} + export async function refreshCoin(req: { http: HttpRequestLibrary; cryptoApi: TalerCryptoInterface;