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:
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;