taler-typescript-core

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

commit a37d90bf6fe6286caa6b406b854d72cd5a901445
parent af6c5877c41937eb580b2090ddedfde4212302b2
Author: Florian Dold <dold@taler.net>
Date:   Sun, 19 Jul 2026 00:09:16 +0200

db: move remaining exchanges.ts access behind the DAL

Diffstat:
Mpackages/taler-wallet-core/src/db-common.ts | 14++++++++++++++
Mpackages/taler-wallet-core/src/db-indexeddb.ts | 20++++----------------
Mpackages/taler-wallet-core/src/dbtx-indexeddb.ts | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/taler-wallet-core/src/dbtx.ts | 22++++++++++++++++++++++
Mpackages/taler-wallet-core/src/exchanges.ts | 153+++++++++++++++++++++++++++++++++++++------------------------------------------
5 files changed, 162 insertions(+), 98 deletions(-)

diff --git a/packages/taler-wallet-core/src/db-common.ts b/packages/taler-wallet-core/src/db-common.ts @@ -1380,6 +1380,20 @@ export interface WalletExchangeMigrationLog { reason: ExchangeMigrationReason; } +export interface WalletGlobalCurrencyAuditor { + id?: number; + currency: string; + auditorBaseUrl: string; + auditorPub: string; +} + +export interface WalletGlobalCurrencyExchange { + id?: number; + currency: string; + exchangeBaseUrl: string; + exchangeMasterPub: string; +} + export interface WalletWithdrawCoinSource { type: CoinSourceType.Withdraw; diff --git a/packages/taler-wallet-core/src/db-indexeddb.ts b/packages/taler-wallet-core/src/db-indexeddb.ts @@ -148,6 +148,8 @@ import { WalletDenominationFamily, WalletExchangeBaseUrlFixup, WalletExchangeMigrationLog, + WalletGlobalCurrencyAuditor, + WalletGlobalCurrencyExchange, WalletToken, TokenFamilyInfo, WalletRefundGroup, @@ -542,20 +544,6 @@ export function passthroughCodec<T>(): Codec<T> { return codecForAny(); } -export interface GlobalCurrencyAuditorRecord { - id?: number; - currency: string; - auditorBaseUrl: string; - auditorPub: string; -} - -export interface GlobalCurrencyExchangeRecord { - id?: number; - currency: string; - exchangeBaseUrl: string; - exchangeMasterPub: string; -} - export interface CurrencyInfoRecord { /** * Stringified scope info. @@ -666,7 +654,7 @@ export const WalletIndexedDbStoresV1 = { versionAdded: 12, }), globalCurrencyAuditors: describeStoreV2({ - recordCodec: passthroughCodec<GlobalCurrencyAuditorRecord>(), + recordCodec: passthroughCodec<WalletGlobalCurrencyAuditor>(), storeName: "globalCurrencyAuditors", keyPath: "id", autoIncrement: true, @@ -683,7 +671,7 @@ export const WalletIndexedDbStoresV1 = { }, }), globalCurrencyExchanges: describeStoreV2({ - recordCodec: passthroughCodec<GlobalCurrencyExchangeRecord>(), + recordCodec: passthroughCodec<WalletGlobalCurrencyExchange>(), storeName: "globalCurrencyExchanges", keyPath: "id", autoIncrement: true, diff --git a/packages/taler-wallet-core/src/dbtx-indexeddb.ts b/packages/taler-wallet-core/src/dbtx-indexeddb.ts @@ -78,6 +78,8 @@ import { WalletDenominationFamily, WalletExchangeBaseUrlFixup, WalletExchangeMigrationLog, + WalletGlobalCurrencyExchange, + WalletGlobalCurrencyAuditor, WalletExchangeDetails, } from "./db-common.js"; import type { @@ -354,6 +356,55 @@ export class IdbWalletTransaction implements WalletDbTransaction { }); } + async countWithdrawalGroupsByExchange( + exchangeBaseUrl: string, + ): Promise<number> { + const tx = this.tx; + return await tx.withdrawalGroups.indexes.byExchangeBaseUrl.count( + exchangeBaseUrl, + ); + } + + async getWithdrawalGroupsByExchangeForRekey( + exchangeBaseUrl: string, + ): Promise<WalletWithdrawalGroup[]> { + const tx = this.tx; + return await tx.withdrawalGroups.indexes.byExchangeBaseUrl.getAll( + exchangeBaseUrl, + ); + } + + async getGlobalCurrencyExchange( + currency: string, + exchangeBaseUrl: string, + exchangeMasterPub: string, + ): Promise<WalletGlobalCurrencyExchange | undefined> { + const tx = this.tx; + return await tx.globalCurrencyExchanges.indexes.byCurrencyAndUrlAndPub.get([ + currency, + exchangeBaseUrl, + exchangeMasterPub, + ]); + } + + async getGlobalCurrencyAuditor( + currency: string, + auditorBaseUrl: string, + auditorPub: string, + ): Promise<WalletGlobalCurrencyAuditor | undefined> { + const tx = this.tx; + return await tx.globalCurrencyAuditors.indexes.byCurrencyAndUrlAndPub.get([ + currency, + auditorBaseUrl, + auditorPub, + ]); + } + + async listAllDenomLossEvents(): Promise<WalletDenomLossEvent[]> { + const tx = this.tx; + return await tx.denomLossEvents.iter().toArray(); + } + async getCoinsByExchange(exchangeBaseUrl: string): Promise<WalletCoin[]> { const tx = this.tx; return await tx.coins.indexes.byBaseUrl.getAll(exchangeBaseUrl); diff --git a/packages/taler-wallet-core/src/dbtx.ts b/packages/taler-wallet-core/src/dbtx.ts @@ -78,6 +78,8 @@ import { WalletDenominationFamily, WalletExchangeBaseUrlFixup, WalletExchangeMigrationLog, + WalletGlobalCurrencyExchange, + WalletGlobalCurrencyAuditor, WalletExchangeDetails, } from "./db-common.js"; export interface GetCurrencyInfoDbResult { @@ -251,6 +253,26 @@ export interface WalletDbTransaction { /** * Look up a denomination family by its parameters. */ + countWithdrawalGroupsByExchange(exchangeBaseUrl: string): Promise<number>; + + getWithdrawalGroupsByExchangeForRekey( + exchangeBaseUrl: string, + ): Promise<WalletWithdrawalGroup[]>; + + getGlobalCurrencyExchange( + currency: string, + exchangeBaseUrl: string, + exchangeMasterPub: string, + ): Promise<WalletGlobalCurrencyExchange | undefined>; + + getGlobalCurrencyAuditor( + currency: string, + auditorBaseUrl: string, + auditorPub: string, + ): Promise<WalletGlobalCurrencyAuditor | undefined>; + + listAllDenomLossEvents(): Promise<WalletDenomLossEvent[]>; + getCoinsByExchange(exchangeBaseUrl: string): Promise<WalletCoin[]>; countCoinsByExchange(exchangeBaseUrl: string): Promise<number>; diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts @@ -261,7 +261,7 @@ export async function getScopeForAllCoins( ): Promise<ScopeInfo[]> { let exchangeSet = new Set<string>(); for (const pub of coinPubs) { - const coin = await tx.coins.get(pub); + const coin = await tx.wtx.getCoin(pub); if (!coin) { logger.warn(`coin ${coinPubs} not found, unable to compute full scope`); continue; @@ -326,11 +326,7 @@ async function internalGetExchangeScopeInfo( exchangeDetails: WalletExchangeDetails, ): Promise<ScopeInfo> { const globalExchangeRec = - await tx.globalCurrencyExchanges.indexes.byCurrencyAndUrlAndPub.get([ - exchangeDetails.currency, - exchangeDetails.exchangeBaseUrl, - exchangeDetails.masterPublicKey, - ]); + await tx.wtx.getGlobalCurrencyExchange(exchangeDetails.currency, exchangeDetails.exchangeBaseUrl, exchangeDetails.masterPublicKey); if (globalExchangeRec) { return { currency: exchangeDetails.currency, @@ -339,11 +335,7 @@ async function internalGetExchangeScopeInfo( } else { for (const aud of exchangeDetails.auditors) { const globalAuditorRec = - await tx.globalCurrencyAuditors.indexes.byCurrencyAndUrlAndPub.get([ - exchangeDetails.currency, - aud.auditor_url, - aud.auditor_pub, - ]); + await tx.wtx.getGlobalCurrencyAuditor(exchangeDetails.currency, aud.auditor_url, aud.auditor_pub); if (globalAuditorRec) { return { currency: exchangeDetails.currency, @@ -2211,9 +2203,7 @@ async function doExchangeAutoRefresh( if (!exchange || !exchange.detailsPointer) { return; } - const coins = await tx.coins.indexes.byBaseUrl - .iter(exchangeBaseUrl) - .toArray(); + const coins = await tx.wtx.getCoinsByExchange(exchangeBaseUrl); const refreshCoins: CoinRefreshRequest[] = []; for (const coin of coins) { if (coin.status !== CoinStatus.Fresh) { @@ -2323,7 +2313,7 @@ async function handleDenomLoss( exchangeBaseUrl: string, ): Promise<void> { const coinAvailabilityRecs = - await tx.coinAvailability.indexes.byExchangeBaseUrl.getAll(exchangeBaseUrl); + await tx.wtx.getCoinAvailabilityByExchange(exchangeBaseUrl); const denomsVanished: string[] = []; const denomsUnoffered: string[] = []; const denomsExpired: string[] = []; @@ -2344,7 +2334,7 @@ async function handleDenomLoss( // Remove availability coinAv.freshCoinCount = 0; coinAv.visibleCoinCount = 0; - await tx.coinAvailability.put(coinAv); + await tx.wtx.upsertCoinAvailability(coinAv); denomsVanished.push(coinAv.denomPubHash); const total = Amount.from(coinAv.value).mult(n); amountVanished = amountVanished.add(total); @@ -2352,7 +2342,7 @@ async function handleDenomLoss( // Remove availability coinAv.freshCoinCount = 0; coinAv.visibleCoinCount = 0; - await tx.coinAvailability.put(coinAv); + await tx.wtx.upsertCoinAvailability(coinAv); denomsUnoffered.push(coinAv.denomPubHash); const total = Amount.from(coinAv.value).mult(n); amountUnoffered = amountUnoffered.add(total); @@ -2363,7 +2353,7 @@ async function handleDenomLoss( // Remove availability coinAv.freshCoinCount = 0; coinAv.visibleCoinCount = 0; - await tx.coinAvailability.put(coinAv); + await tx.wtx.upsertCoinAvailability(coinAv); denomsExpired.push(coinAv.denomPubHash); const total = Amount.from(coinAv.value).mult(n); amountExpired = amountExpired.add(total); @@ -2374,15 +2364,13 @@ async function handleDenomLoss( logger.warn(`denomination ${coinAv.denomPubHash} is a loss`); - const coins = await tx.coins.indexes.byDenomPubHash.getAll( - coinAv.denomPubHash, - ); + const coins = await tx.wtx.getCoinsByDenomPubHash(coinAv.denomPubHash); for (const coin of coins) { switch (coin.status) { case CoinStatus.Fresh: case CoinStatus.FreshSuspended: { coin.status = CoinStatus.DenomLoss; - await tx.coins.put(coin); + await tx.wtx.upsertCoin(coin); break; } } @@ -2631,9 +2619,7 @@ async function handleRecoup( logger.info("revoking denom", recoupInfo.h_denom_pub); oldDenom.isRevoked = true; await tx.wtx.upsertDenomination(oldDenom); - const affectedCoins = await tx.coins.indexes.byDenomPubHash.getAll( - recoupInfo.h_denom_pub, - ); + const affectedCoins = await tx.wtx.getCoinsByDenomPubHash(recoupInfo.h_denom_pub); for (const ac of affectedCoins) { newlyRevokedCoinPubs.push(ac.coinPub); } @@ -3128,9 +3114,9 @@ async function internalGetExchangeResources( ): Promise<GetExchangeResourcesResponse> { let numWithdrawals = 0; let numCoins = 0; - numCoins = await tx.coins.indexes.byBaseUrl.count(exchangeBaseUrl); + numCoins = await tx.wtx.countCoinsByExchange(exchangeBaseUrl); numWithdrawals = - await tx.withdrawalGroups.indexes.byExchangeBaseUrl.count(exchangeBaseUrl); + await tx.wtx.countWithdrawalGroupsByExchange(exchangeBaseUrl); const total = numWithdrawals + numCoins; return { hasResources: total != 0, @@ -3452,9 +3438,7 @@ export async function checkIncomingAmountLegalUnderKycBalanceThreshold( throw Error("exchange not found"); } const coinAvRecs = - await tx.coinAvailability.indexes.byExchangeBaseUrl.getAll( - exchangeBaseUrl, - ); + await tx.wtx.getCoinAvailabilityByExchange(exchangeBaseUrl); let balAmount = Amounts.zeroOfCurrency(det.currency); for (const av of coinAvRecs) { const n = av.freshCoinCount + (av.pendingRefreshOutputCount ?? 0); @@ -4066,11 +4050,7 @@ export async function checkExchangeInScopeTx( return false; } const gr = - await tx.globalCurrencyExchanges.indexes.byCurrencyAndUrlAndPub.get([ - exchangeDetails.currency, - exchangeBaseUrl, - exchangeDetails.masterPublicKey, - ]); + await tx.wtx.getGlobalCurrencyExchange(exchangeDetails.currency, exchangeBaseUrl, exchangeDetails.masterPublicKey); logger.trace(`global currency record: ${j2s(gr)}`); return gr != null; } @@ -4198,21 +4178,23 @@ export async function migrateExchange( } { - await tx.denomLossEvents.iter().forEachAsync(async (rec) => { + const recs = await tx.wtx.listAllDenomLossEvents(); + for (const rec of recs) { if (rec.exchangeBaseUrl === req.oldExchangeBaseUrl) { rec.exchangeBaseUrl = req.newExchangeBaseUrl; await tx.wtx.upsertDenomLossEvent(rec); } - }); + } } { - await tx.recoupGroups.indexes.byExchangeBaseUrl - .iter(req.oldExchangeBaseUrl) - .forEachAsync(async (rec) => { - rec.exchangeBaseUrl = req.newExchangeBaseUrl; - await tx.wtx.upsertRecoupGroup(rec); - }); + const recs = await tx.wtx.getRecoupGroupsByExchange( + req.oldExchangeBaseUrl, + ); + for (const rec of recs) { + rec.exchangeBaseUrl = req.newExchangeBaseUrl; + await tx.wtx.upsertRecoupGroup(rec); + } } { @@ -4244,75 +4226,81 @@ export async function migrateExchange( } { - await tx.coins.indexes.byBaseUrl - .iter(req.oldExchangeBaseUrl) - .forEachAsync(async (rec) => { - rec.exchangeBaseUrl = req.newExchangeBaseUrl; - await tx.coins.put(rec); - }); + const recs = await tx.wtx.getCoinsByExchange(req.oldExchangeBaseUrl); + for (const rec of recs) { + rec.exchangeBaseUrl = req.newExchangeBaseUrl; + await tx.wtx.upsertCoin(rec); + } } { - await tx.coinAvailability.indexes.byExchangeBaseUrl - .iter(req.oldExchangeBaseUrl) - .forEachAsync(async (rec) => { - await tx.coinAvailability.delete([ - rec.exchangeBaseUrl, - rec.denomPubHash, - rec.maxAge, - ]); - rec.exchangeBaseUrl = req.newExchangeBaseUrl; - await tx.coinAvailability.put(rec); - }); + const recs = await tx.wtx.getCoinAvailabilityByExchange( + req.oldExchangeBaseUrl, + ); + for (const rec of recs) { + await tx.wtx.deleteCoinAvailability( + rec.exchangeBaseUrl, + rec.denomPubHash, + rec.maxAge, + ); + rec.exchangeBaseUrl = req.newExchangeBaseUrl; + await tx.wtx.upsertCoinAvailability(rec); + } } { - await tx.peerPullCredit.iter().forEachAsync(async (rec) => { + const recs = await tx.wtx.listAllPeerPullCredits(); + for (const rec of recs) { if (rec.exchangeBaseUrl === req.oldExchangeBaseUrl) { rec.exchangeBaseUrl = req.newExchangeBaseUrl; - await tx.peerPullCredit.put(rec); + await tx.wtx.upsertPeerPullCredit(rec); } - }); + } } { - await tx.peerPullDebit.iter().forEachAsync(async (rec) => { + const recs = await tx.wtx.listAllPeerPullDebits(); + for (const rec of recs) { if (rec.exchangeBaseUrl === req.oldExchangeBaseUrl) { rec.exchangeBaseUrl = req.newExchangeBaseUrl; - await tx.peerPullDebit.put(rec); + await tx.wtx.upsertPeerPullDebit(rec); } - }); + } } { - await tx.peerPushCredit.iter().forEachAsync(async (rec) => { + const recs = await tx.wtx.listAllPeerPushCredits(); + for (const rec of recs) { if (rec.exchangeBaseUrl === req.oldExchangeBaseUrl) { rec.exchangeBaseUrl = req.newExchangeBaseUrl; - await tx.peerPushCredit.put(rec); + await tx.wtx.upsertPeerPushCredit(rec); } - }); + } } { - await tx.peerPushDebit.iter().forEachAsync(async (rec) => { + const recs = await tx.wtx.listAllPeerPushDebits(); + for (const rec of recs) { if (rec.exchangeBaseUrl === req.oldExchangeBaseUrl) { rec.exchangeBaseUrl = req.newExchangeBaseUrl; - await tx.peerPushDebit.put(rec); + await tx.wtx.upsertPeerPushDebit(rec); } - }); + } } { - await tx.withdrawalGroups.indexes.byExchangeBaseUrl - .iter(req.oldExchangeBaseUrl) - .forEachAsync(async (rec) => { - rec.exchangeBaseUrl = req.newExchangeBaseUrl; - await tx.wtx.upsertWithdrawalGroup(rec); - }); + const recs = await tx.wtx.getWithdrawalGroupsByExchangeForRekey( + req.oldExchangeBaseUrl, + ); + for (const rec of recs) { + rec.exchangeBaseUrl = req.newExchangeBaseUrl; + await tx.wtx.upsertWithdrawalGroup(rec); + } } { - await tx.refreshGroups.iter().forEachAsync(async (rec) => { + const recs = await tx.wtx.listAllRefreshGroups(); + for (const rec of recs) { if ( rec.infoPerExchange && rec.infoPerExchange[req.oldExchangeBaseUrl] != null @@ -4322,11 +4310,12 @@ export async function migrateExchange( delete rec.infoPerExchange[req.oldExchangeBaseUrl]; await tx.wtx.upsertRefreshGroup(rec); } - }); + } } { - await tx.depositGroups.iter().forEachAsync(async (rec) => { + const recs = await tx.wtx.listAllDepositGroups(); + for (const rec of recs) { if ( rec.infoPerExchange && rec.infoPerExchange[req.oldExchangeBaseUrl] != null @@ -4336,7 +4325,7 @@ export async function migrateExchange( delete rec.infoPerExchange[req.oldExchangeBaseUrl]; await tx.wtx.upsertDepositGroup(rec); } - }); + } } tx.notify({