taler-typescript-core

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

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

wallet-core: scope coin loss to exchange master key

Diffstat:
Apackages/taler-wallet-core/src/exchanges.test.ts | 45+++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-wallet-core/src/exchanges.ts | 23+++++++++++++++++++----
2 files changed, 64 insertions(+), 4 deletions(-)

diff --git a/packages/taler-wallet-core/src/exchanges.test.ts b/packages/taler-wallet-core/src/exchanges.test.ts @@ -0,0 +1,45 @@ +/* + 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 assert from "node:assert"; +import { test } from "node:test"; +import { WalletCoin } from "./db-common.js"; +import { filterCoinsByExchangeMasterPub } from "./exchanges.js"; + +test("denomination loss processing is scoped to one exchange master key", () => { + const current = { + coinPub: "current-coin", + denomPubHash: "shared-denomination-hash", + exchangeMasterPub: "current-master", + } as WalletCoin; + const previous = { + coinPub: "previous-coin", + denomPubHash: "shared-denomination-hash", + exchangeMasterPub: "previous-master", + } as WalletCoin; + const legacyOrphan = { + coinPub: "legacy-orphan", + denomPubHash: "shared-denomination-hash", + } as WalletCoin; + + assert.deepStrictEqual( + filterCoinsByExchangeMasterPub( + [current, previous, legacyOrphan], + "current-master", + ), + [current], + ); +}); diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts @@ -2879,6 +2879,15 @@ export async function processTaskExchangeAutoRefresh( * /keys, and judging them against it would destroy the balance the wallet is * meant to be preserving. */ +export function filterCoinsByExchangeMasterPub( + coins: WalletCoin[], + exchangeMasterPub: string, +): WalletCoin[] { + return coins.filter( + (coin) => coin.exchangeMasterPub === exchangeMasterPub, + ); +} + async function handleDenomLoss( wex: WalletExecutionContext, tx: WalletDbTransaction, @@ -2908,8 +2917,11 @@ async function handleDenomLoss( denominations.map((denom) => [denomRefKey(denom), denom]), ); const coinsByDenomHash = new Map<string, WalletCoin[]>(); - const affectedCoins = await tx.getCoinsByDenomPubHashes( - coinAvailabilityRecs.map((availability) => availability.denomPubHash), + const affectedCoins = filterCoinsByExchangeMasterPub( + await tx.getCoinsByDenomPubHashes( + coinAvailabilityRecs.map((availability) => availability.denomPubHash), + ), + exchangeMasterPub, ); for (const coin of affectedCoins) { const group = coinsByDenomHash.get(coin.denomPubHash) ?? []; @@ -3249,8 +3261,11 @@ async function handleRecoup( denominations.map((denom) => [denomRefKey(denom), denom]), ); const coinsByDenomHash = new Map<string, WalletCoin[]>(); - const affectedCoins = await tx.getCoinsByDenomPubHashes( - recoupDenomList.map((recoupInfo) => recoupInfo.h_denom_pub), + const affectedCoins = filterCoinsByExchangeMasterPub( + await tx.getCoinsByDenomPubHashes( + recoupDenomList.map((recoupInfo) => recoupInfo.h_denom_pub), + ), + exchangeMasterPub, ); for (const coin of affectedCoins) { const group = coinsByDenomHash.get(coin.denomPubHash) ?? [];