From 1edc144b3595ae1ab6b8af7a43d26b811b2c2623 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 23 Apr 2024 03:09:40 +0200 Subject: wallet-core: pass options object to all transactions --- packages/taler-wallet-core/src/attention.ts | 70 +-- packages/taler-wallet-core/src/backup/index.ts | 361 ++++++++------- packages/taler-wallet-core/src/balance.ts | 83 ++-- packages/taler-wallet-core/src/coinSelection.ts | 38 +- packages/taler-wallet-core/src/db.ts | 17 +- packages/taler-wallet-core/src/deposits.ts | 258 ++++++----- packages/taler-wallet-core/src/dev-experiments.ts | 74 +-- packages/taler-wallet-core/src/exchanges.ts | 141 +++--- .../src/instructedAmountConversion.ts | 9 +- .../taler-wallet-core/src/observable-wrappers.ts | 12 +- packages/taler-wallet-core/src/pay-merchant.ts | 508 ++++++++++++--------- packages/taler-wallet-core/src/pay-peer-common.ts | 112 ++--- .../taler-wallet-core/src/pay-peer-pull-credit.ts | 138 +++--- .../taler-wallet-core/src/pay-peer-pull-debit.ts | 73 +-- .../taler-wallet-core/src/pay-peer-push-credit.ts | 42 +- .../taler-wallet-core/src/pay-peer-push-debit.ts | 118 ++--- packages/taler-wallet-core/src/query.ts | 20 +- packages/taler-wallet-core/src/recoup.ts | 81 ++-- packages/taler-wallet-core/src/refresh.ts | 128 +++--- packages/taler-wallet-core/src/shepherd.ts | 44 +- packages/taler-wallet-core/src/testing.ts | 9 +- packages/taler-wallet-core/src/transactions.ts | 132 +++--- packages/taler-wallet-core/src/wallet.ts | 431 +++++++++-------- packages/taler-wallet-core/src/withdraw.ts | 197 ++++---- 24 files changed, 1721 insertions(+), 1375 deletions(-) (limited to 'packages') diff --git a/packages/taler-wallet-core/src/attention.ts b/packages/taler-wallet-core/src/attention.ts index 60d2117f1..7a52ceaa3 100644 --- a/packages/taler-wallet-core/src/attention.ts +++ b/packages/taler-wallet-core/src/attention.ts @@ -29,7 +29,7 @@ import { UserAttentionsResponse, } from "@gnu-taler/taler-util"; import { timestampPreciseFromDb, timestampPreciseToDb } from "./db.js"; -import { InternalWalletState, WalletExecutionContext } from "./wallet.js"; +import { WalletExecutionContext } from "./wallet.js"; const logger = new Logger("operations/attention.ts"); @@ -37,20 +37,23 @@ export async function getUserAttentionsUnreadCount( wex: WalletExecutionContext, req: UserAttentionsRequest, ): Promise { - const total = await wex.db.runReadOnlyTx(["userAttention"], async (tx) => { - let count = 0; - await tx.userAttention.iter().forEach((x) => { - if ( - req.priority !== undefined && - UserAttentionPriority[x.info.type] !== req.priority - ) - return; - if (x.read !== undefined) return; - count++; - }); + const total = await wex.db.runReadOnlyTx( + { storeNames: ["userAttention"] }, + async (tx) => { + let count = 0; + await tx.userAttention.iter().forEach((x) => { + if ( + req.priority !== undefined && + UserAttentionPriority[x.info.type] !== req.priority + ) + return; + if (x.read !== undefined) return; + count++; + }); - return count; - }); + return count; + }, + ); return { total }; } @@ -59,30 +62,33 @@ export async function getUserAttentions( wex: WalletExecutionContext, req: UserAttentionsRequest, ): Promise { - return await wex.db.runReadOnlyTx(["userAttention"], async (tx) => { - const pending: UserAttentionUnreadList = []; - await tx.userAttention.iter().forEach((x) => { - if ( - req.priority !== undefined && - UserAttentionPriority[x.info.type] !== req.priority - ) - return; - pending.push({ - info: x.info, - when: timestampPreciseFromDb(x.created), - read: x.read !== undefined, + return await wex.db.runReadOnlyTx( + { storeNames: ["userAttention"] }, + async (tx) => { + const pending: UserAttentionUnreadList = []; + await tx.userAttention.iter().forEach((x) => { + if ( + req.priority !== undefined && + UserAttentionPriority[x.info.type] !== req.priority + ) + return; + pending.push({ + info: x.info, + when: timestampPreciseFromDb(x.created), + read: x.read !== undefined, + }); }); - }); - return { pending }; - }); + return { pending }; + }, + ); } export async function markAttentionRequestAsRead( wex: WalletExecutionContext, req: UserAttentionByIdRequest, ): Promise { - await wex.db.runReadWriteTx(["userAttention"], async (tx) => { + await wex.db.runReadWriteTx({ storeNames: ["userAttention"] }, async (tx) => { const ua = await tx.userAttention.get([req.entityId, req.type]); if (!ua) throw Error("attention request not found"); tx.userAttention.put({ @@ -104,7 +110,7 @@ export async function addAttentionRequest( info: AttentionInfo, entityId: string, ): Promise { - await wex.db.runReadWriteTx(["userAttention"], async (tx) => { + await wex.db.runReadWriteTx({ storeNames: ["userAttention"] }, async (tx) => { await tx.userAttention.put({ info, entityId, @@ -125,7 +131,7 @@ export async function removeAttentionRequest( wex: WalletExecutionContext, req: UserAttentionByIdRequest, ): Promise { - await wex.db.runReadWriteTx(["userAttention"], async (tx) => { + await wex.db.runReadWriteTx({ storeNames: ["userAttention"] }, async (tx) => { const ua = await tx.userAttention.get([req.entityId, req.type]); if (!ua) throw Error("attention request not found"); await tx.userAttention.delete([req.entityId, req.type]); diff --git a/packages/taler-wallet-core/src/backup/index.ts b/packages/taler-wallet-core/src/backup/index.ts index 5dccf1998..16b5488e7 100644 --- a/packages/taler-wallet-core/src/backup/index.ts +++ b/packages/taler-wallet-core/src/backup/index.ts @@ -183,7 +183,7 @@ async function runBackupCycleForProvider( args: BackupForProviderArgs, ): Promise { const provider = await wex.db.runReadOnlyTx( - ["backupProviders"], + { storeNames: ["backupProviders"] }, async (tx) => { return tx.backupProviders.get(args.backupProviderBaseUrl); }, @@ -244,20 +244,23 @@ async function runBackupCycleForProvider( logger.trace(`sync response status: ${resp.status}`); if (resp.status === HttpStatusCode.NotModified) { - await wex.db.runReadWriteTx(["backupProviders"], async (tx) => { - const prov = await tx.backupProviders.get(provider.baseUrl); - if (!prov) { - return; - } - prov.lastBackupCycleTimestamp = timestampPreciseToDb( - TalerPreciseTimestamp.now(), - ); - prov.state = { - tag: BackupProviderStateTag.Ready, - nextBackupTimestamp: timestampPreciseToDb(getNextBackupTimestamp()), - }; - await tx.backupProviders.put(prov); - }); + await wex.db.runReadWriteTx( + { storeNames: ["backupProviders"] }, + async (tx) => { + const prov = await tx.backupProviders.get(provider.baseUrl); + if (!prov) { + return; + } + prov.lastBackupCycleTimestamp = timestampPreciseToDb( + TalerPreciseTimestamp.now(), + ); + prov.state = { + tag: BackupProviderStateTag.Ready, + nextBackupTimestamp: timestampPreciseToDb(getNextBackupTimestamp()), + }; + await tx.backupProviders.put(prov); + }, + ); removeAttentionRequest(wex, { entityId: provider.baseUrl, @@ -290,41 +293,47 @@ async function runBackupCycleForProvider( if (res === undefined) { //claimed - await wex.db.runReadWriteTx(["backupProviders"], async (tx) => { + await wex.db.runReadWriteTx( + { storeNames: ["backupProviders"] }, + async (tx) => { + const prov = await tx.backupProviders.get(provider.baseUrl); + if (!prov) { + logger.warn("backup provider not found anymore"); + return; + } + prov.shouldRetryFreshProposal = true; + prov.state = { + tag: BackupProviderStateTag.Retrying, + }; + await tx.backupProviders.put(prov); + }, + ); + + throw Error("not implemented"); + // return { + // type: TaskRunResultType.Pending, + // }; + } + const result = res; + + await wex.db.runReadWriteTx( + { storeNames: ["backupProviders"] }, + async (tx) => { const prov = await tx.backupProviders.get(provider.baseUrl); if (!prov) { logger.warn("backup provider not found anymore"); return; } - prov.shouldRetryFreshProposal = true; + // const opId = TaskIdentifiers.forBackup(prov); + // await scheduleRetryInTx(ws, tx, opId); + prov.currentPaymentProposalId = result.proposalId; + prov.shouldRetryFreshProposal = false; prov.state = { tag: BackupProviderStateTag.Retrying, }; await tx.backupProviders.put(prov); - }); - - throw Error("not implemented"); - // return { - // type: TaskRunResultType.Pending, - // }; - } - const result = res; - - await wex.db.runReadWriteTx(["backupProviders"], async (tx) => { - const prov = await tx.backupProviders.get(provider.baseUrl); - if (!prov) { - logger.warn("backup provider not found anymore"); - return; - } - // const opId = TaskIdentifiers.forBackup(prov); - // await scheduleRetryInTx(ws, tx, opId); - prov.currentPaymentProposalId = result.proposalId; - prov.shouldRetryFreshProposal = false; - prov.state = { - tag: BackupProviderStateTag.Retrying, - }; - await tx.backupProviders.put(prov); - }); + }, + ); addAttentionRequest( wex, @@ -343,21 +352,24 @@ async function runBackupCycleForProvider( } if (resp.status === HttpStatusCode.NoContent) { - await wex.db.runReadWriteTx(["backupProviders"], async (tx) => { - const prov = await tx.backupProviders.get(provider.baseUrl); - if (!prov) { - return; - } - prov.lastBackupHash = encodeCrock(currentBackupHash); - prov.lastBackupCycleTimestamp = timestampPreciseToDb( - TalerPreciseTimestamp.now(), - ); - prov.state = { - tag: BackupProviderStateTag.Ready, - nextBackupTimestamp: timestampPreciseToDb(getNextBackupTimestamp()), - }; - await tx.backupProviders.put(prov); - }); + await wex.db.runReadWriteTx( + { storeNames: ["backupProviders"] }, + async (tx) => { + const prov = await tx.backupProviders.get(provider.baseUrl); + if (!prov) { + return; + } + prov.lastBackupHash = encodeCrock(currentBackupHash); + prov.lastBackupCycleTimestamp = timestampPreciseToDb( + TalerPreciseTimestamp.now(), + ); + prov.state = { + tag: BackupProviderStateTag.Ready, + nextBackupTimestamp: timestampPreciseToDb(getNextBackupTimestamp()), + }; + await tx.backupProviders.put(prov); + }, + ); removeAttentionRequest(wex, { entityId: provider.baseUrl, @@ -376,22 +388,25 @@ async function runBackupCycleForProvider( // const blob = await decryptBackup(backupConfig, backupEnc); // FIXME: Re-implement backup import with merging // await importBackup(ws, blob, cryptoData); - await wex.db.runReadWriteTx(["backupProviders"], async (tx) => { - const prov = await tx.backupProviders.get(provider.baseUrl); - if (!prov) { - logger.warn("backup provider not found anymore"); - return; - } - prov.lastBackupHash = encodeCrock(hash(backupEnc)); - // FIXME: Allocate error code for this situation? - // FIXME: Add operation retry record! - const opId = TaskIdentifiers.forBackup(prov); - //await scheduleRetryInTx(ws, tx, opId); - prov.state = { - tag: BackupProviderStateTag.Retrying, - }; - await tx.backupProviders.put(prov); - }); + await wex.db.runReadWriteTx( + { storeNames: ["backupProviders"] }, + async (tx) => { + const prov = await tx.backupProviders.get(provider.baseUrl); + if (!prov) { + logger.warn("backup provider not found anymore"); + return; + } + prov.lastBackupHash = encodeCrock(hash(backupEnc)); + // FIXME: Allocate error code for this situation? + // FIXME: Add operation retry record! + const opId = TaskIdentifiers.forBackup(prov); + //await scheduleRetryInTx(ws, tx, opId); + prov.state = { + tag: BackupProviderStateTag.Retrying, + }; + await tx.backupProviders.put(prov); + }, + ); logger.info("processed existing backup"); // Now upload our own, merged backup. return await runBackupCycleForProvider(wex, args); @@ -414,7 +429,7 @@ export async function processBackupForProvider( backupProviderBaseUrl: string, ): Promise { const provider = await wex.db.runReadOnlyTx( - ["backupProviders"], + { storeNames: ["backupProviders"] }, async (tx) => { return await tx.backupProviders.get(backupProviderBaseUrl); }, @@ -444,9 +459,12 @@ export async function removeBackupProvider( wex: WalletExecutionContext, req: RemoveBackupProviderRequest, ): Promise { - await wex.db.runReadWriteTx(["backupProviders"], async (tx) => { - await tx.backupProviders.delete(req.provider); - }); + await wex.db.runReadWriteTx( + { storeNames: ["backupProviders"] }, + async (tx) => { + await tx.backupProviders.delete(req.provider); + }, + ); } export interface RunBackupCycleRequest { @@ -473,7 +491,7 @@ export async function runBackupCycle( req: RunBackupCycleRequest, ): Promise { const providers = await wex.db.runReadOnlyTx( - ["backupProviders"], + { storeNames: ["backupProviders"] }, async (tx) => { if (req.providers) { const rs = await Promise.all( @@ -553,56 +571,64 @@ export async function addBackupProvider( logger.info(`adding backup provider ${j2s(req)}`); await provideBackupState(wex); const canonUrl = canonicalizeBaseUrl(req.backupProviderBaseUrl); - await wex.db.runReadWriteTx(["backupProviders"], async (tx) => { - const oldProv = await tx.backupProviders.get(canonUrl); - if (oldProv) { - logger.info("old backup provider found"); + await wex.db.runReadWriteTx( + { storeNames: ["backupProviders"] }, + async (tx) => { + const oldProv = await tx.backupProviders.get(canonUrl); + if (oldProv) { + logger.info("old backup provider found"); + if (req.activate) { + oldProv.state = { + tag: BackupProviderStateTag.Ready, + nextBackupTimestamp: timestampPreciseToDb( + TalerPreciseTimestamp.now(), + ), + }; + logger.info("setting existing backup provider to active"); + await tx.backupProviders.put(oldProv); + } + return; + } + }, + ); + const termsUrl = new URL("config", canonUrl); + const resp = await wex.http.fetch(termsUrl.href); + const terms = await readSuccessResponseJsonOrThrow( + resp, + codecForSyncTermsOfServiceResponse(), + ); + await wex.db.runReadWriteTx( + { storeNames: ["backupProviders"] }, + async (tx) => { + let state: BackupProviderState; + //FIXME: what is the difference provisional and ready? if (req.activate) { - oldProv.state = { + state = { tag: BackupProviderStateTag.Ready, nextBackupTimestamp: timestampPreciseToDb( TalerPreciseTimestamp.now(), ), }; - logger.info("setting existing backup provider to active"); - await tx.backupProviders.put(oldProv); + } else { + state = { + tag: BackupProviderStateTag.Provisional, + }; } - return; - } - }); - const termsUrl = new URL("config", canonUrl); - const resp = await wex.http.fetch(termsUrl.href); - const terms = await readSuccessResponseJsonOrThrow( - resp, - codecForSyncTermsOfServiceResponse(), + await tx.backupProviders.put({ + state, + name: req.name, + terms: { + annualFee: terms.annual_fee, + storageLimitInMegabytes: terms.storage_limit_in_megabytes, + supportedProtocolVersion: terms.version, + }, + shouldRetryFreshProposal: false, + paymentProposalIds: [], + baseUrl: canonUrl, + uids: [encodeCrock(getRandomBytes(32))], + }); + }, ); - await wex.db.runReadWriteTx(["backupProviders"], async (tx) => { - let state: BackupProviderState; - //FIXME: what is the difference provisional and ready? - if (req.activate) { - state = { - tag: BackupProviderStateTag.Ready, - nextBackupTimestamp: timestampPreciseToDb(TalerPreciseTimestamp.now()), - }; - } else { - state = { - tag: BackupProviderStateTag.Provisional, - }; - } - await tx.backupProviders.put({ - state, - name: req.name, - terms: { - annualFee: terms.annual_fee, - storageLimitInMegabytes: terms.storage_limit_in_megabytes, - supportedProtocolVersion: terms.version, - }, - shouldRetryFreshProposal: false, - paymentProposalIds: [], - baseUrl: canonUrl, - uids: [encodeCrock(getRandomBytes(32))], - }); - }); return await runFirstBackupCycleForProvider(wex, { backupProviderBaseUrl: canonUrl, @@ -706,7 +732,7 @@ export async function getBackupInfo( ): Promise { const backupConfig = await provideBackupState(wex); const providerRecords = await wex.db.runReadOnlyTx( - ["backupProviders", "operationRetries"], + { storeNames: ["backupProviders", "operationRetries"] }, async (tx) => { return await tx.backupProviders.iter().mapAsync(async (bp) => { const opId = TaskIdentifiers.forBackup(bp); @@ -752,7 +778,7 @@ export async function getBackupRecovery( ): Promise { const bs = await provideBackupState(wex); const providers = await wex.db.runReadOnlyTx( - ["backupProviders"], + { storeNames: ["backupProviders"] }, async (tx) => { return await tx.backupProviders.iter().toArray(); }, @@ -774,48 +800,51 @@ async function backupRecoveryTheirs( wex: WalletExecutionContext, br: BackupRecovery, ) { - await wex.db.runReadWriteTx(["backupProviders", "config"], async (tx) => { - let backupStateEntry: ConfigRecord | undefined = await tx.config.get( - ConfigRecordKey.WalletBackupState, - ); - checkDbInvariant(!!backupStateEntry); - checkDbInvariant( - backupStateEntry.key === ConfigRecordKey.WalletBackupState, - ); - backupStateEntry.value.lastBackupNonce = undefined; - backupStateEntry.value.lastBackupTimestamp = undefined; - backupStateEntry.value.lastBackupCheckTimestamp = undefined; - backupStateEntry.value.lastBackupPlainHash = undefined; - backupStateEntry.value.walletRootPriv = br.walletRootPriv; - backupStateEntry.value.walletRootPub = encodeCrock( - eddsaGetPublic(decodeCrock(br.walletRootPriv)), - ); - await tx.config.put(backupStateEntry); - for (const prov of br.providers) { - const existingProv = await tx.backupProviders.get(prov.url); - if (!existingProv) { - await tx.backupProviders.put({ - baseUrl: prov.url, - name: prov.name, - paymentProposalIds: [], - shouldRetryFreshProposal: false, - state: { - tag: BackupProviderStateTag.Ready, - nextBackupTimestamp: timestampPreciseToDb( - TalerPreciseTimestamp.now(), - ), - }, - uids: [encodeCrock(getRandomBytes(32))], - }); + await wex.db.runReadWriteTx( + { storeNames: ["backupProviders", "config"] }, + async (tx) => { + let backupStateEntry: ConfigRecord | undefined = await tx.config.get( + ConfigRecordKey.WalletBackupState, + ); + checkDbInvariant(!!backupStateEntry); + checkDbInvariant( + backupStateEntry.key === ConfigRecordKey.WalletBackupState, + ); + backupStateEntry.value.lastBackupNonce = undefined; + backupStateEntry.value.lastBackupTimestamp = undefined; + backupStateEntry.value.lastBackupCheckTimestamp = undefined; + backupStateEntry.value.lastBackupPlainHash = undefined; + backupStateEntry.value.walletRootPriv = br.walletRootPriv; + backupStateEntry.value.walletRootPub = encodeCrock( + eddsaGetPublic(decodeCrock(br.walletRootPriv)), + ); + await tx.config.put(backupStateEntry); + for (const prov of br.providers) { + const existingProv = await tx.backupProviders.get(prov.url); + if (!existingProv) { + await tx.backupProviders.put({ + baseUrl: prov.url, + name: prov.name, + paymentProposalIds: [], + shouldRetryFreshProposal: false, + state: { + tag: BackupProviderStateTag.Ready, + nextBackupTimestamp: timestampPreciseToDb( + TalerPreciseTimestamp.now(), + ), + }, + uids: [encodeCrock(getRandomBytes(32))], + }); + } } - } - const providers = await tx.backupProviders.iter().toArray(); - for (const prov of providers) { - prov.lastBackupCycleTimestamp = undefined; - prov.lastBackupHash = undefined; - await tx.backupProviders.put(prov); - } - }); + const providers = await tx.backupProviders.iter().toArray(); + for (const prov of providers) { + prov.lastBackupCycleTimestamp = undefined; + prov.lastBackupHash = undefined; + await tx.backupProviders.put(prov); + } + }, + ); } async function backupRecoveryOurs( @@ -831,7 +860,7 @@ export async function loadBackupRecovery( ): Promise { const bs = await provideBackupState(wex); const providers = await wex.db.runReadOnlyTx( - ["backupProviders"], + { storeNames: ["backupProviders"] }, async (tx) => { return await tx.backupProviders.iter().toArray(); }, @@ -879,7 +908,7 @@ export async function provideBackupState( wex: WalletExecutionContext, ): Promise { const bs: ConfigRecord | undefined = await wex.db.runReadOnlyTx( - ["config"], + { storeNames: ["config"] }, async (tx) => { return await tx.config.get(ConfigRecordKey.WalletBackupState); }, @@ -895,7 +924,7 @@ export async function provideBackupState( // FIXME: device ID should be configured when wallet is initialized // and be based on hostname const deviceId = `wallet-core-${encodeCrock(d)}`; - return await wex.db.runReadWriteTx(["config"], async (tx) => { + return await wex.db.runReadWriteTx({ storeNames: ["config"] }, async (tx) => { let backupStateEntry: ConfigRecord | undefined = await tx.config.get( ConfigRecordKey.WalletBackupState, ); @@ -933,7 +962,7 @@ export async function setWalletDeviceId( deviceId: string, ): Promise { await provideBackupState(wex); - await wex.db.runReadWriteTx(["config"], async (tx) => { + await wex.db.runReadWriteTx({ storeNames: ["config"] }, async (tx) => { let backupStateEntry: ConfigRecord | undefined = await tx.config.get( ConfigRecordKey.WalletBackupState, ); diff --git a/packages/taler-wallet-core/src/balance.ts b/packages/taler-wallet-core/src/balance.ts index ca7642163..1fef9876e 100644 --- a/packages/taler-wallet-core/src/balance.ts +++ b/packages/taler-wallet-core/src/balance.ts @@ -472,19 +472,21 @@ export async function getBalances( logger.trace("starting to compute balance"); const wbal = await wex.db.runReadWriteTx( - [ - "coinAvailability", - "coins", - "depositGroups", - "exchangeDetails", - "exchanges", - "globalCurrencyAuditors", - "globalCurrencyExchanges", - "purchases", - "refreshGroups", - "withdrawalGroups", - "peerPushDebit", - ], + { + storeNames: [ + "coinAvailability", + "coins", + "depositGroups", + "exchangeDetails", + "exchanges", + "globalCurrencyAuditors", + "globalCurrencyExchanges", + "purchases", + "refreshGroups", + "withdrawalGroups", + "peerPushDebit", + ], + }, async (tx) => { return getBalancesInsideTransaction(wex, tx); }, @@ -557,13 +559,15 @@ export async function getPaymentBalanceDetails( req: PaymentRestrictionsForBalance, ): Promise { return await wex.db.runReadOnlyTx( - [ - "coinAvailability", - "refreshGroups", - "exchanges", - "exchangeDetails", - "denominations", - ], + { + storeNames: [ + "coinAvailability", + "refreshGroups", + "exchanges", + "exchangeDetails", + "denominations", + ], + }, async (tx) => { return getPaymentBalanceDetailsInTx(wex, tx, req); }, @@ -729,25 +733,28 @@ export async function getBalanceDetail( ): Promise { const exchanges: { exchangeBaseUrl: string; exchangePub: string }[] = []; const wires = new Array(); - await wex.db.runReadOnlyTx(["exchanges", "exchangeDetails"], async (tx) => { - const allExchanges = await tx.exchanges.iter().toArray(); - for (const e of allExchanges) { - const details = await getExchangeWireDetailsInTx(tx, e.baseUrl); - if (!details || req.currency !== details.currency) { - continue; - } - details.wireInfo.accounts.forEach((a) => { - const payto = parsePaytoUri(a.payto_uri); - if (payto && !wires.includes(payto.targetType)) { - wires.push(payto.targetType); + await wex.db.runReadOnlyTx( + { storeNames: ["exchanges", "exchangeDetails"] }, + async (tx) => { + const allExchanges = await tx.exchanges.iter().toArray(); + for (const e of allExchanges) { + const details = await getExchangeWireDetailsInTx(tx, e.baseUrl); + if (!details || req.currency !== details.currency) { + continue; } - }); - exchanges.push({ - exchangePub: details.masterPublicKey, - exchangeBaseUrl: e.baseUrl, - }); - } - }); + details.wireInfo.accounts.forEach((a) => { + const payto = parsePaytoUri(a.payto_uri); + if (payto && !wires.includes(payto.targetType)) { + wires.push(payto.targetType); + } + }); + exchanges.push({ + exchangePub: details.masterPublicKey, + exchangeBaseUrl: e.baseUrl, + }); + } + }, + ); return await getPaymentBalanceDetails(wex, { currency: req.currency, diff --git a/packages/taler-wallet-core/src/coinSelection.ts b/packages/taler-wallet-core/src/coinSelection.ts index 6a7d79d83..a60e41ecd 100644 --- a/packages/taler-wallet-core/src/coinSelection.ts +++ b/packages/taler-wallet-core/src/coinSelection.ts @@ -268,14 +268,16 @@ export async function selectPayCoins( } return await wex.db.runReadOnlyTx( - [ - "coinAvailability", - "denominations", - "refreshGroups", - "exchanges", - "exchangeDetails", - "coins", - ], + { + storeNames: [ + "coinAvailability", + "denominations", + "refreshGroups", + "exchanges", + "exchangeDetails", + "coins", + ], + }, async (tx) => { const materialAvSel = await internalSelectPayCoins(wex, tx, req, false); @@ -1140,15 +1142,17 @@ export async function selectPeerCoins( } return await wex.db.runReadWriteTx( - [ - "exchanges", - "contractTerms", - "coins", - "coinAvailability", - "denominations", - "refreshGroups", - "exchangeDetails", - ], + { + storeNames: [ + "exchanges", + "contractTerms", + "coins", + "coinAvailability", + "denominations", + "refreshGroups", + "exchangeDetails", + ], + }, async (tx): Promise => { const exchanges = await tx.exchanges.iter().toArray(); const currency = Amounts.currencyOf(instructedAmount); diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 5da8518ea..085e909cf 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -3305,7 +3305,7 @@ export async function openTalerDatabase( CancellationToken.CONTINUE, ); let currentMainVersion: string | undefined; - await metaDb.runReadWriteTx(["metaConfig"], async (tx) => { + await metaDb.runReadWriteTx({ storeNames: ["metaConfig"] }, async (tx) => { const dbVersionRecord = await tx.metaConfig.get(CURRENT_DB_CONFIG_KEY); if (!dbVersionRecord) { currentMainVersion = TALER_WALLET_MAIN_DB_NAME; @@ -3330,12 +3330,15 @@ export async function openTalerDatabase( case "taler-wallet-main-v9": // We consider this a pre-release // development version, no migration is done. - await metaDb.runReadWriteTx(["metaConfig"], async (tx) => { - await tx.metaConfig.put({ - key: CURRENT_DB_CONFIG_KEY, - value: TALER_WALLET_MAIN_DB_NAME, - }); - }); + await metaDb.runReadWriteTx( + { storeNames: ["metaConfig"] }, + async (tx) => { + await tx.metaConfig.put({ + key: CURRENT_DB_CONFIG_KEY, + value: TALER_WALLET_MAIN_DB_NAME, + }); + }, + ); break; default: throw Error( diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts index 50f26ea9c..dbba55247 100644 --- a/packages/taler-wallet-core/src/deposits.ts +++ b/packages/taler-wallet-core/src/deposits.ts @@ -139,22 +139,25 @@ export class DepositTransactionContext implements TransactionContext { const ws = this.wex; // FIXME: We should check first if we are in a final state // where deletion is allowed. - await ws.db.runReadWriteTx(["depositGroups", "tombstones"], async (tx) => { - const tipRecord = await tx.depositGroups.get(depositGroupId); - if (tipRecord) { - await tx.depositGroups.delete(depositGroupId); - await tx.tombstones.put({ - id: TombstoneTag.DeleteDepositGroup + ":" + depositGroupId, - }); - } - }); + await ws.db.runReadWriteTx( + { storeNames: ["depositGroups", "tombstones"] }, + async (tx) => { + const tipRecord = await tx.depositGroups.get(depositGroupId); + if (tipRecord) { + await tx.depositGroups.delete(depositGroupId); + await tx.tombstones.put({ + id: TombstoneTag.DeleteDepositGroup + ":" + depositGroupId, + }); + } + }, + ); return; } async suspendTransaction(): Promise { const { wex, depositGroupId, transactionId, taskId: retryTag } = this; const transitionInfo = await wex.db.runReadWriteTx( - ["depositGroups"], + { storeNames: ["depositGroups"] }, async (tx) => { const dg = await tx.depositGroups.get(depositGroupId); if (!dg) { @@ -197,7 +200,7 @@ export class DepositTransactionContext implements TransactionContext { async abortTransaction(): Promise { const { wex, depositGroupId, transactionId, taskId: retryTag } = this; const transitionInfo = await wex.db.runReadWriteTx( - ["depositGroups"], + { storeNames: ["depositGroups"] }, async (tx) => { const dg = await tx.depositGroups.get(depositGroupId); if (!dg) { @@ -235,7 +238,7 @@ export class DepositTransactionContext implements TransactionContext { async resumeTransaction(): Promise { const { wex, depositGroupId, transactionId, taskId: retryTag } = this; const transitionInfo = await wex.db.runReadWriteTx( - ["depositGroups"], + { storeNames: ["depositGroups"] }, async (tx) => { const dg = await tx.depositGroups.get(depositGroupId); if (!dg) { @@ -278,7 +281,7 @@ export class DepositTransactionContext implements TransactionContext { async failTransaction(): Promise { const { wex, depositGroupId, transactionId, taskId } = this; const transitionInfo = await wex.db.runReadWriteTx( - ["depositGroups"], + { storeNames: ["depositGroups"] }, async (tx) => { const dg = await tx.depositGroups.get(depositGroupId); if (!dg) { @@ -435,7 +438,7 @@ async function refundDepositGroup( default: { const coinPub = payCoinSelection.coinPubs[i]; const coinExchange = await wex.db.runReadOnlyTx( - ["coins"], + { storeNames: ["coins"] }, async (tx) => { const coinRecord = await tx.coins.get(coinPub); checkDbInvariant(!!coinRecord); @@ -497,14 +500,16 @@ async function refundDepositGroup( const currency = Amounts.currencyOf(depositGroup.totalPayCost); const res = await wex.db.runReadWriteTx( - [ - "depositGroups", - "refreshGroups", - "refreshSessions", - "coins", - "denominations", - "coinAvailability", - ], + { + storeNames: [ + "depositGroups", + "refreshGroups", + "refreshSessions", + "coins", + "denominations", + "coinAvailability", + ], + }, async (tx) => { const newDg = await tx.depositGroups.get(depositGroup.depositGroupId); if (!newDg) { @@ -571,7 +576,7 @@ async function waitForRefreshOnDepositGroup( depositGroupId: depositGroup.depositGroupId, }); const transitionInfo = await wex.db.runReadWriteTx( - ["depositGroups", "refreshGroups"], + { storeNames: ["depositGroups", "refreshGroups"] }, async (tx) => { const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId); let newOpState: DepositOperationStatus | undefined; @@ -660,7 +665,7 @@ async function processDepositGroupPendingKyc( kycStatusRes.status === HttpStatusCode.NoContent ) { const transitionInfo = await wex.db.runReadWriteTx( - ["depositGroups"], + { storeNames: ["depositGroups"] }, async (tx) => { const newDg = await tx.depositGroups.get(depositGroupId); if (!newDg) { @@ -719,7 +724,7 @@ async function transitionToKycRequired( const kycStatus = await kycStatusReq.json(); logger.info(`kyc status: ${j2s(kycStatus)}`); const transitionInfo = await wex.db.runReadWriteTx( - ["depositGroups"], + { storeNames: ["depositGroups"] }, async (tx) => { const dg = await tx.depositGroups.get(depositGroupId); if (!dg) { @@ -768,7 +773,7 @@ async function processDepositGroupPendingTrack( const coinPub = payCoinSelection.coinPubs[i]; // FIXME: Make the URL part of the coin selection? const exchangeBaseUrl = await wex.db.runReadWriteTx( - ["coins"], + { storeNames: ["coins"] }, async (tx) => { const coinRecord = await tx.coins.get(coinPub); checkDbInvariant(!!coinRecord); @@ -844,41 +849,44 @@ async function processDepositGroupPendingTrack( } if (updatedTxStatus !== undefined) { - await wex.db.runReadWriteTx(["depositGroups"], async (tx) => { - const dg = await tx.depositGroups.get(depositGroupId); - if (!dg) { - return; - } - if (!dg.statusPerCoin) { - return; - } - if (updatedTxStatus !== undefined) { - dg.statusPerCoin[i] = updatedTxStatus; - } - if (newWiredCoin) { - /** - * FIXME: if there is a new wire information from the exchange - * it should add up to the previous tracking states. - * - * This may loose information by overriding prev state. - * - * And: add checks to integration tests - */ - if (!dg.trackingState) { - dg.trackingState = {}; + await wex.db.runReadWriteTx( + { storeNames: ["depositGroups"] }, + async (tx) => { + const dg = await tx.depositGroups.get(depositGroupId); + if (!dg) { + return; } - - dg.trackingState[newWiredCoin.id] = newWiredCoin.value; - } - await tx.depositGroups.put(dg); - }); + if (!dg.statusPerCoin) { + return; + } + if (updatedTxStatus !== undefined) { + dg.statusPerCoin[i] = updatedTxStatus; + } + if (newWiredCoin) { + /** + * FIXME: if there is a new wire information from the exchange + * it should add up to the previous tracking states. + * + * This may loose information by overriding prev state. + * + * And: add checks to integration tests + */ + if (!dg.trackingState) { + dg.trackingState = {}; + } + + dg.trackingState[newWiredCoin.id] = newWiredCoin.value; + } + await tx.depositGroups.put(dg); + }, + ); } } let allWired = true; const transitionInfo = await wex.db.runReadWriteTx( - ["depositGroups"], + { storeNames: ["depositGroups"] }, async (tx) => { const dg = await tx.depositGroups.get(depositGroupId); if (!dg) { @@ -929,7 +937,7 @@ async function processDepositGroupPendingDeposit( logger.info("processing deposit group in pending(deposit)"); const depositGroupId = depositGroup.depositGroupId; const contractTermsRec = await wex.db.runReadOnlyTx( - ["contractTerms"], + { storeNames: ["contractTerms"] }, async (tx) => { return tx.contractTerms.get(depositGroup.contractTermsHash); }, @@ -987,14 +995,16 @@ async function processDepositGroupPendingDeposit( } const transitionDone = await wex.db.runReadWriteTx( - [ - "depositGroups", - "coins", - "coinAvailability", - "refreshGroups", - "refreshSessions", - "denominations", - ], + { + storeNames: [ + "depositGroups", + "coins", + "coinAvailability", + "refreshGroups", + "refreshSessions", + "denominations", + ], + }, async (tx) => { const dg = await tx.depositGroups.get(depositGroupId); if (!dg) { @@ -1094,27 +1104,30 @@ async function processDepositGroupPendingDeposit( codecForBatchDepositSuccess(), ); - await wex.db.runReadWriteTx(["depositGroups"], async (tx) => { - const dg = await tx.depositGroups.get(depositGroupId); - if (!dg) { - return; - } - if (!dg.statusPerCoin) { - return; - } - for (const batchIndex of batchIndexes) { - const coinStatus = dg.statusPerCoin[batchIndex]; - switch (coinStatus) { - case DepositElementStatus.DepositPending: - dg.statusPerCoin[batchIndex] = DepositElementStatus.Tracking; - await tx.depositGroups.put(dg); + await wex.db.runReadWriteTx( + { storeNames: ["depositGroups"] }, + async (tx) => { + const dg = await tx.depositGroups.get(depositGroupId); + if (!dg) { + return; } - } - }); + if (!dg.statusPerCoin) { + return; + } + for (const batchIndex of batchIndexes) { + const coinStatus = dg.statusPerCoin[batchIndex]; + switch (coinStatus) { + case DepositElementStatus.DepositPending: + dg.statusPerCoin[batchIndex] = DepositElementStatus.Tracking; + await tx.depositGroups.put(dg); + } + } + }, + ); } const transitionInfo = await wex.db.runReadWriteTx( - ["depositGroups"], + { storeNames: ["depositGroups"] }, async (tx) => { const dg = await tx.depositGroups.get(depositGroupId); if (!dg) { @@ -1140,7 +1153,7 @@ export async function processDepositGroup( depositGroupId: string, ): Promise { const depositGroup = await wex.db.runReadOnlyTx( - ["depositGroups"], + { storeNames: ["depositGroups"] }, async (tx) => { return tx.depositGroups.get(depositGroupId); }, @@ -1174,7 +1187,7 @@ async function getExchangeWireFee( time: TalerProtocolTimestamp, ): Promise { const exchangeDetails = await wex.db.runReadOnlyTx( - ["exchangeDetails", "exchanges"], + { storeNames: ["exchangeDetails", "exchanges"] }, async (tx) => { const ex = await tx.exchanges.get(baseUrl); if (!ex || !ex.detailsPointer) return undefined; @@ -1281,19 +1294,22 @@ export async function checkDepositGroup( const exchangeInfos: ExchangeHandle[] = []; - await wex.db.runReadOnlyTx(["exchangeDetails", "exchanges"], async (tx) => { - const allExchanges = await tx.exchanges.iter().toArray(); - for (const e of allExchanges) { - const details = await getExchangeWireDetailsInTx(tx, e.baseUrl); - if (!details || amount.currency !== details.currency) { - continue; + await wex.db.runReadOnlyTx( + { storeNames: ["exchangeDetails", "exchanges"] }, + async (tx) => { + const allExchanges = await tx.exchanges.iter().toArray(); + for (const e of allExchanges) { + const details = await getExchangeWireDetailsInTx(tx, e.baseUrl); + if (!details || amount.currency !== details.currency) { + continue; + } + exchangeInfos.push({ + master_pub: details.masterPublicKey, + url: e.baseUrl, + }); } - exchangeInfos.push({ - master_pub: details.masterPublicKey, - url: e.baseUrl, - }); - } - }); + }, + ); const now = AbsoluteTime.now(); const nowRounded = AbsoluteTime.toProtocolTimestamp(now); @@ -1404,19 +1420,22 @@ export async function createDepositGroup( const exchangeInfos: { url: string; master_pub: string }[] = []; - await wex.db.runReadOnlyTx(["exchanges", "exchangeDetails"], async (tx) => { - const allExchanges = await tx.exchanges.iter().toArray(); - for (const e of allExchanges) { - const details = await getExchangeWireDetailsInTx(tx, e.baseUrl); - if (!details || amount.currency !== details.currency) { - continue; + await wex.db.runReadOnlyTx( + { storeNames: ["exchanges", "exchangeDetails"] }, + async (tx) => { + const allExchanges = await tx.exchanges.iter().toArray(); + for (const e of allExchanges) { + const details = await getExchangeWireDetailsInTx(tx, e.baseUrl); + if (!details || amount.currency !== details.currency) { + continue; + } + exchangeInfos.push({ + master_pub: details.masterPublicKey, + url: e.baseUrl, + }); } - exchangeInfos.push({ - master_pub: details.masterPublicKey, - url: e.baseUrl, - }); - } - }); + }, + ); const now = AbsoluteTime.now(); const wireDeadline = AbsoluteTime.toProtocolTimestamp( @@ -1569,16 +1588,18 @@ export async function createDepositGroup( const transactionId = ctx.transactionId; const newTxState = await wex.db.runReadWriteTx( - [ - "depositGroups", - "coins", - "recoupGroups", - "denominations", - "refreshGroups", - "refreshSessions", - "coinAvailability", - "contractTerms", - ], + { + storeNames: [ + "depositGroups", + "coins", + "recoupGroups", + "denominations", + "refreshGroups", + "refreshSessions", + "coinAvailability", + "contractTerms", + ], + }, async (tx) => { if (depositGroup.payCoinSelection) { await spendCoins(wex, tx, { @@ -1635,7 +1656,7 @@ export async function getCounterpartyEffectiveDepositAmount( const exchangeSet: Set = new Set(); await wex.db.runReadOnlyTx( - ["coins", "denominations", "exchangeDetails", "exchanges"], + { storeNames: ["coins", "denominations", "exchangeDetails", "exchanges"] }, async (tx) => { for (let i = 0; i < pcs.length; i++) { const denom = await getDenomInfo( @@ -1694,10 +1715,9 @@ async function getTotalFeesForDepositAmount( const coinFee: AmountJson[] = []; const refreshFee: AmountJson[] = []; const exchangeSet: Set = new Set(); - const currency = Amounts.currencyOf(total); await wex.db.runReadOnlyTx( - ["coins", "denominations", "exchanges", "exchangeDetails"], + { storeNames: ["coins", "denominations", "exchanges", "exchangeDetails"] }, async (tx) => { for (let i = 0; i < pcs.length; i++) { const denom = await getDenomInfo( diff --git a/packages/taler-wallet-core/src/dev-experiments.ts b/packages/taler-wallet-core/src/dev-experiments.ts index db2ff5d06..5cb9400be 100644 --- a/packages/taler-wallet-core/src/dev-experiments.ts +++ b/packages/taler-wallet-core/src/dev-experiments.ts @@ -79,23 +79,26 @@ export async function applyDevExperiment( } case "insert-pending-refresh": { const refreshGroupId = encodeCrock(getRandomBytes(32)); - await wex.db.runReadWriteTx(["refreshGroups"], async (tx) => { - const newRg: RefreshGroupRecord = { - currency: "TESTKUDOS", - expectedOutputPerCoin: [], - inputPerCoin: [], - oldCoinPubs: [], - operationStatus: RefreshOperationStatus.Pending, - reason: RefreshReason.Manual, - refreshGroupId, - statusPerCoin: [], - timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()), - timestampFinished: undefined, - originatingTransactionId: undefined, - infoPerExchange: {}, - }; - await tx.refreshGroups.put(newRg); - }); + await wex.db.runReadWriteTx( + { storeNames: ["refreshGroups"] }, + async (tx) => { + const newRg: RefreshGroupRecord = { + currency: "TESTKUDOS", + expectedOutputPerCoin: [], + inputPerCoin: [], + oldCoinPubs: [], + operationStatus: RefreshOperationStatus.Pending, + reason: RefreshReason.Manual, + refreshGroupId, + statusPerCoin: [], + timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()), + timestampFinished: undefined, + originatingTransactionId: undefined, + infoPerExchange: {}, + }; + await tx.refreshGroups.put(newRg); + }, + ); wex.taskScheduler.startShepherdTask( constructTaskIdentifier({ tag: PendingTaskType.Refresh, @@ -105,23 +108,26 @@ export async function applyDevExperiment( return; } case "insert-denom-loss": { - await wex.db.runReadWriteTx(["denomLossEvents"], async (tx) => { - const eventId = encodeCrock(getRandomBytes(32)); - const newRg: DenomLossEventRecord = { - amount: "TESTKUDOS:42", - currency: "TESTKUDOS", - exchangeBaseUrl: "https://exchange.test.taler.net/", - denomLossEventId: eventId, - denomPubHashes: [ - encodeCrock(getRandomBytes(64)), - encodeCrock(getRandomBytes(64)), - ], - eventType: DenomLossEventType.DenomExpired, - status: DenomLossStatus.Done, - timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()), - }; - await tx.denomLossEvents.put(newRg); - }); + await wex.db.runReadWriteTx( + { storeNames: ["denomLossEvents"] }, + async (tx) => { + const eventId = encodeCrock(getRandomBytes(32)); + const newRg: DenomLossEventRecord = { + amount: "TESTKUDOS:42", + currency: "TESTKUDOS", + exchangeBaseUrl: "https://exchange.test.taler.net/", + denomLossEventId: eventId, + denomPubHashes: [ + encodeCrock(getRandomBytes(64)), + encodeCrock(getRandomBytes(64)), + ], + eventType: DenomLossEventType.DenomExpired, + status: DenomLossStatus.Done, + timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()), + }; + await tx.denomLossEvents.put(newRg); + }, + ); return; } } diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts index 5f0c744e2..d5ca7abbf 100644 --- a/packages/taler-wallet-core/src/exchanges.ts +++ b/packages/taler-wallet-core/src/exchanges.ts @@ -312,8 +312,8 @@ async function makeExchangeListItem( ): Promise { const lastUpdateErrorInfo: OperationErrorInfo | undefined = lastError ? { - error: lastError, - } + error: lastError, + } : undefined; let scopeInfo: ScopeInfo | undefined = undefined; @@ -377,13 +377,15 @@ export async function lookupExchangeByUri( req: GetExchangeEntryByUrlRequest, ): Promise { return await wex.db.runReadOnlyTx( - [ - "exchanges", - "exchangeDetails", - "operationRetries", - "globalCurrencyAuditors", - "globalCurrencyExchanges", - ], + { + storeNames: [ + "exchanges", + "exchangeDetails", + "operationRetries", + "globalCurrencyAuditors", + "globalCurrencyExchanges", + ], + }, async (tx) => { const exchangeRec = await tx.exchanges.get(req.exchangeBaseUrl); if (!exchangeRec) { @@ -414,7 +416,7 @@ export async function acceptExchangeTermsOfService( exchangeBaseUrl: string, ): Promise { const notif = await wex.db.runReadWriteTx( - ["exchangeDetails", "exchanges"], + { storeNames: ["exchangeDetails", "exchanges"] }, async (tx) => { const exch = await tx.exchanges.get(exchangeBaseUrl); if (exch && exch.tosCurrentEtag) { @@ -449,7 +451,7 @@ export async function forgetExchangeTermsOfService( exchangeBaseUrl: string, ): Promise { const notif = await wex.db.runReadWriteTx( - ["exchangeDetails", "exchanges"], + { storeNames: ["exchangeDetails", "exchanges"] }, async (tx) => { const exch = await tx.exchanges.get(exchangeBaseUrl); if (exch) { @@ -915,12 +917,13 @@ async function startUpdateExchangeEntry( const canonBaseUrl = canonicalizeBaseUrl(exchangeBaseUrl); logger.info( - `starting update of exchange entry ${canonBaseUrl}, forced=${options.forceUpdate ?? false + `starting update of exchange entry ${canonBaseUrl}, forced=${ + options.forceUpdate ?? false }`, ); const { notification } = await wex.db.runReadWriteTx( - ["exchanges", "exchangeDetails"], + { storeNames: ["exchanges", "exchangeDetails"] }, async (tx) => { wex.ws.exchangeCache.clear(); return provideExchangeRecordInTx(wex.ws, tx, exchangeBaseUrl); @@ -935,7 +938,7 @@ async function startUpdateExchangeEntry( const { oldExchangeState, newExchangeState, taskId } = await wex.db.runReadWriteTx( - ["exchanges", "operationRetries"], + { storeNames: ["exchanges", "operationRetries"] }, async (tx) => { const r = await tx.exchanges.get(canonBaseUrl); if (!r) { @@ -1029,13 +1032,15 @@ async function internalWaitReadyExchange( logger.info(`waiting for ready exchange ${canonUrl}`); const { exchange, exchangeDetails, retryInfo, scopeInfo } = await wex.db.runReadOnlyTx( - [ - "exchanges", - "exchangeDetails", - "operationRetries", - "globalCurrencyAuditors", - "globalCurrencyExchanges", - ], + { + storeNames: [ + "exchanges", + "exchangeDetails", + "operationRetries", + "globalCurrencyAuditors", + "globalCurrencyExchanges", + ], + }, async (tx) => { const exchange = await tx.exchanges.get(canonUrl); const exchangeDetails = await getExchangeRecordsInternal( @@ -1290,7 +1295,7 @@ export async function updateExchangeFromUrlHandler( exchangeBaseUrl = canonicalizeBaseUrl(exchangeBaseUrl); const oldExchangeRec = await wex.db.runReadOnlyTx( - ["exchanges"], + { storeNames: ["exchanges"] }, async (tx) => { return tx.exchanges.get(exchangeBaseUrl); }, @@ -1450,17 +1455,19 @@ export async function updateExchangeFromUrlHandler( let peerPaymentsDisabled = checkPeerPaymentsDisabled(keysInfo); const updated = await wex.db.runReadWriteTx( - [ - "exchanges", - "exchangeDetails", - "exchangeSignKeys", - "denominations", - "coins", - "refreshGroups", - "recoupGroups", - "coinAvailability", - "denomLossEvents", - ], + { + storeNames: [ + "exchanges", + "exchangeDetails", + "exchangeSignKeys", + "denominations", + "coins", + "refreshGroups", + "recoupGroups", + "coinAvailability", + "denomLossEvents", + ], + }, async (tx) => { const r = await tx.exchanges.get(exchangeBaseUrl); if (!r) { @@ -1666,14 +1673,16 @@ export async function updateExchangeFromUrlHandler( if (refreshCheckNecessary) { // Do auto-refresh. await wex.db.runReadWriteTx( - [ - "coins", - "denominations", - "coinAvailability", - "refreshGroups", - "refreshSessions", - "exchanges", - ], + { + storeNames: [ + "coins", + "denominations", + "coinAvailability", + "refreshGroups", + "refreshSessions", + "exchanges", + ], + }, async (tx) => { const exchange = await tx.exchanges.get(exchangeBaseUrl); if (!exchange || !exchange.detailsPointer) { @@ -1969,7 +1978,7 @@ export class DenomLossTransactionContext implements TransactionContext { } async deleteTransaction(): Promise { const transitionInfo = await this.wex.db.runReadWriteTx( - ["denomLossEvents"], + { storeNames: ["denomLossEvents"] }, async (tx) => { const rec = await tx.denomLossEvents.get(this.denomLossEventId); if (rec) { @@ -2080,7 +2089,7 @@ export async function getExchangePaytoUri( // We do the update here, since the exchange might not even exist // yet in our database. const details = await wex.db.runReadOnlyTx( - ["exchanges", "exchangeDetails"], + { storeNames: ["exchanges", "exchangeDetails"] }, async (tx) => { return getExchangeRecordsInternal(tx, exchangeBaseUrl); }, @@ -2122,7 +2131,7 @@ export async function getExchangeTos( acceptLanguage, ); - await wex.db.runReadWriteTx(["exchanges"], async (tx) => { + await wex.db.runReadWriteTx({ storeNames: ["exchanges"] }, async (tx) => { const updateExchangeEntry = await tx.exchanges.get(exchangeBaseUrl); if (updateExchangeEntry) { updateExchangeEntry.tosCurrentEtag = tosDownload.tosEtag; @@ -2179,13 +2188,15 @@ export async function listExchanges( ): Promise { const exchanges: ExchangeListItem[] = []; await wex.db.runReadOnlyTx( - [ - "exchanges", - "operationRetries", - "exchangeDetails", - "globalCurrencyAuditors", - "globalCurrencyExchanges", - ], + { + storeNames: [ + "exchanges", + "operationRetries", + "exchangeDetails", + "globalCurrencyAuditors", + "globalCurrencyExchanges", + ], + }, async (tx) => { const exchangeRecords = await tx.exchanges.iter().toArray(); for (const r of exchangeRecords) { @@ -2262,7 +2273,7 @@ export async function getExchangeDetailedInfo( exchangeBaseurl: string, ): Promise { const exchange = await wex.db.runReadOnlyTx( - ["exchanges", "exchangeDetails", "denominations"], + { storeNames: ["exchanges", "exchangeDetails", "denominations"] }, async (tx) => { const ex = await tx.exchanges.get(exchangeBaseurl); const dp = ex?.detailsPointer; @@ -2520,17 +2531,19 @@ export async function deleteExchange( let inUse: boolean = false; const exchangeBaseUrl = canonicalizeBaseUrl(req.exchangeBaseUrl); await wex.db.runReadWriteTx( - [ - "exchanges", - "exchangeDetails", - "transactions", - "coinAvailability", - "coins", - "denominations", - "exchangeSignKeys", - "withdrawalGroups", - "planchets", - ], + { + storeNames: [ + "exchanges", + "exchangeDetails", + "transactions", + "coinAvailability", + "coins", + "denominations", + "exchangeSignKeys", + "withdrawalGroups", + "planchets", + ], + }, async (tx) => { const exchangeRec = await tx.exchanges.get(exchangeBaseUrl); if (!exchangeRec) { @@ -2562,7 +2575,7 @@ export async function getExchangeResources( ): Promise { // Withdrawals include internal withdrawals from peer transactions const res = await wex.db.runReadOnlyTx( - ["exchanges", "withdrawalGroups", "coins"], + { storeNames: ["exchanges", "withdrawalGroups", "coins"] }, async (tx) => { const exchangeRecord = await tx.exchanges.get(exchangeBaseUrl); if (!exchangeRecord) { diff --git a/packages/taler-wallet-core/src/instructedAmountConversion.ts b/packages/taler-wallet-core/src/instructedAmountConversion.ts index 63ccb8b56..1f7d95959 100644 --- a/packages/taler-wallet-core/src/instructedAmountConversion.ts +++ b/packages/taler-wallet-core/src/instructedAmountConversion.ts @@ -150,7 +150,14 @@ async function getAvailableDenoms( const operationType = getOperationType(TransactionType.Deposit); return await wex.db.runReadOnlyTx( - ["exchanges", "exchangeDetails", "denominations", "coinAvailability"], + { + storeNames: [ + "exchanges", + "exchangeDetails", + "denominations", + "coinAvailability", + ], + }, async (tx) => { const list: CoinInfo[] = []; const exchanges: Record = {}; diff --git a/packages/taler-wallet-core/src/observable-wrappers.ts b/packages/taler-wallet-core/src/observable-wrappers.ts index 7cd65f38e..626899d9e 100644 --- a/packages/taler-wallet-core/src/observable-wrappers.ts +++ b/packages/taler-wallet-core/src/observable-wrappers.ts @@ -195,7 +195,9 @@ export class ObservableDbAccess implements DbAccess { } async runReadWriteTx[]>( - storeNames: StoreNameArray, + opts: { + storeNames: StoreNameArray; + }, txf: (tx: DbReadWriteTransaction) => Promise, ): Promise { const location = getCallerInfo(); @@ -205,7 +207,7 @@ export class ObservableDbAccess implements DbAccess { location, }); try { - const ret = await this.impl.runReadWriteTx(storeNames, txf); + const ret = await this.impl.runReadWriteTx(opts, txf); this.oc.observe({ type: ObservabilityEventType.DbQueryFinishSuccess, name: "", @@ -223,7 +225,9 @@ export class ObservableDbAccess implements DbAccess { } async runReadOnlyTx[]>( - storeNames: StoreNameArray, + opts: { + storeNames: StoreNameArray; + }, txf: (tx: DbReadOnlyTransaction) => Promise, ): Promise { const location = getCallerInfo(); @@ -233,7 +237,7 @@ export class ObservableDbAccess implements DbAccess { name: "", location, }); - const ret = await this.impl.runReadOnlyTx(storeNames, txf); + const ret = await this.impl.runReadOnlyTx(opts, txf); this.oc.observe({ type: ObservabilityEventType.DbQueryFinishSuccess, name: "", diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts index f9d20d415..49ebc282e 100644 --- a/packages/taler-wallet-core/src/pay-merchant.ts +++ b/packages/taler-wallet-core/src/pay-merchant.ts @@ -200,7 +200,7 @@ export class PayMerchantTransactionContext implements TransactionContext { const ws = this.wex; const extraStores = opts.extraStores ?? []; const transitionInfo = await ws.db.runReadWriteTx( - ["purchases", ...extraStores], + { storeNames: ["purchases", ...extraStores] }, async (tx) => { const purchaseRec = await tx.purchases.get(this.proposalId); if (!purchaseRec) { @@ -227,26 +227,29 @@ export class PayMerchantTransactionContext implements TransactionContext { async deleteTransaction(): Promise { const { wex: ws, proposalId } = this; - await ws.db.runReadWriteTx(["purchases", "tombstones"], async (tx) => { - let found = false; - const purchase = await tx.purchases.get(proposalId); - if (purchase) { - found = true; - await tx.purchases.delete(proposalId); - } - if (found) { - await tx.tombstones.put({ - id: TombstoneTag.DeletePayment + ":" + proposalId, - }); - } - }); + await ws.db.runReadWriteTx( + { storeNames: ["purchases", "tombstones"] }, + async (tx) => { + let found = false; + const purchase = await tx.purchases.get(proposalId); + if (purchase) { + found = true; + await tx.purchases.delete(proposalId); + } + if (found) { + await tx.tombstones.put({ + id: TombstoneTag.DeletePayment + ":" + proposalId, + }); + } + }, + ); } async suspendTransaction(): Promise { const { wex, proposalId, transactionId } = this; wex.taskScheduler.stopShepherdTask(this.taskId); const transitionInfo = await wex.db.runReadWriteTx( - ["purchases"], + { storeNames: ["purchases"] }, async (tx) => { const purchase = await tx.purchases.get(proposalId); if (!purchase) { @@ -268,15 +271,17 @@ export class PayMerchantTransactionContext implements TransactionContext { async abortTransaction(): Promise { const { wex, proposalId, transactionId } = this; const transitionInfo = await wex.db.runReadWriteTx( - [ - "purchases", - "refreshGroups", - "refreshSessions", - "denominations", - "coinAvailability", - "coins", - "operationRetries", - ], + { + storeNames: [ + "purchases", + "refreshGroups", + "refreshSessions", + "denominations", + "coinAvailability", + "coins", + "operationRetries", + ], + }, async (tx) => { const purchase = await tx.purchases.get(proposalId); if (!purchase) { @@ -344,7 +349,7 @@ export class PayMerchantTransactionContext implements TransactionContext { async resumeTransaction(): Promise { const { wex, proposalId, transactionId, taskId: retryTag } = this; const transitionInfo = await wex.db.runReadWriteTx( - ["purchases"], + { storeNames: ["purchases"] }, async (tx) => { const purchase = await tx.purchases.get(proposalId); if (!purchase) { @@ -367,14 +372,16 @@ export class PayMerchantTransactionContext implements TransactionContext { async failTransaction(): Promise { const { wex, proposalId, transactionId } = this; const transitionInfo = await wex.db.runReadWriteTx( - [ - "purchases", - "refreshGroups", - "denominations", - "coinAvailability", - "coins", - "operationRetries", - ], + { + storeNames: [ + "purchases", + "refreshGroups", + "denominations", + "coinAvailability", + "coins", + "operationRetries", + ], + }, async (tx) => { const purchase = await tx.purchases.get(proposalId); if (!purchase) { @@ -415,15 +422,18 @@ export class RefundTransactionContext implements TransactionContext { async deleteTransaction(): Promise { const { wex, refundGroupId, transactionId } = this; - await wex.db.runReadWriteTx(["refundGroups", "tombstones"], async (tx) => { - const refundRecord = await tx.refundGroups.get(refundGroupId); - if (!refundRecord) { - return; - } - await tx.refundGroups.delete(refundGroupId); - await tx.tombstones.put({ id: transactionId }); - // FIXME: Also tombstone the refund items, so that they won't reappear. - }); + await wex.db.runReadWriteTx( + { storeNames: ["refundGroups", "tombstones"] }, + async (tx) => { + const refundRecord = await tx.refundGroups.get(refundGroupId); + if (!refundRecord) { + return; + } + await tx.refundGroups.delete(refundGroupId); + await tx.tombstones.put({ id: transactionId }); + // FIXME: Also tombstone the refund items, so that they won't reappear. + }, + ); } suspendTransaction(): Promise { @@ -455,31 +465,34 @@ export async function getTotalPaymentCost( currency: string, pcs: SelectedProspectiveCoin[], ): Promise { - return wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { - const costs: AmountJson[] = []; - for (let i = 0; i < pcs.length; i++) { - const denom = await tx.denominations.get([ - pcs[i].exchangeBaseUrl, - pcs[i].denomPubHash, - ]); - if (!denom) { - throw Error( - "can't calculate payment cost, denomination for coin not found", + return wex.db.runReadOnlyTx( + { storeNames: ["coins", "denominations"] }, + async (tx) => { + const costs: AmountJson[] = []; + for (let i = 0; i < pcs.length; i++) { + const denom = await tx.denominations.get([ + pcs[i].exchangeBaseUrl, + pcs[i].denomPubHash, + ]); + if (!denom) { + throw Error( + "can't calculate payment cost, denomination for coin not found", + ); + } + const amountLeft = Amounts.sub(denom.value, pcs[i].contribution).amount; + const refreshCost = await getTotalRefreshCost( + wex, + tx, + DenominationRecord.toDenomInfo(denom), + amountLeft, ); + costs.push(Amounts.parseOrThrow(pcs[i].contribution)); + costs.push(refreshCost); } - const amountLeft = Amounts.sub(denom.value, pcs[i].contribution).amount; - const refreshCost = await getTotalRefreshCost( - wex, - tx, - DenominationRecord.toDenomInfo(denom), - amountLeft, - ); - costs.push(Amounts.parseOrThrow(pcs[i].contribution)); - costs.push(refreshCost); - } - const zero = Amounts.zeroOfCurrency(currency); - return Amounts.sum([zero, ...costs]).amount; - }); + const zero = Amounts.zeroOfCurrency(currency); + return Amounts.sum([zero, ...costs]).amount; + }, + ); } async function failProposalPermanently( @@ -492,7 +505,7 @@ async function failProposalPermanently( proposalId, }); const transitionInfo = await wex.db.runReadWriteTx( - ["purchases"], + { storeNames: ["purchases"] }, async (tx) => { const p = await tx.purchases.get(proposalId); if (!p) { @@ -554,7 +567,10 @@ export async function expectProposalDownload( if (parentTx) { return getFromTransaction(parentTx); } - return await wex.db.runReadOnlyTx(["contractTerms"], getFromTransaction); + return await wex.db.runReadOnlyTx( + { storeNames: ["contractTerms"] }, + getFromTransaction, + ); } export function extractContractData( @@ -593,9 +609,12 @@ async function processDownloadProposal( wex: WalletExecutionContext, proposalId: string, ): Promise { - const proposal = await wex.db.runReadOnlyTx(["purchases"], async (tx) => { - return await tx.purchases.get(proposalId); - }); + const proposal = await wex.db.runReadOnlyTx( + { storeNames: ["purchases"] }, + async (tx) => { + return await tx.purchases.get(proposalId); + }, + ); if (!proposal) { return TaskRunResult.finished(); @@ -766,7 +785,7 @@ async function processDownloadProposal( logger.trace(`extracted contract data: ${j2s(contractData)}`); const transitionInfo = await wex.db.runReadWriteTx( - ["purchases", "contractTerms"], + { storeNames: ["purchases", "contractTerms"] }, async (tx) => { const p = await tx.purchases.get(proposalId); if (!p) { @@ -839,12 +858,15 @@ async function createOrReusePurchase( claimToken: string | undefined, noncePriv: string | undefined, ): Promise { - const oldProposals = await wex.db.runReadOnlyTx(["purchases"], async (tx) => { - return tx.purchases.indexes.byUrlAndOrderId.getAll([ - merchantBaseUrl, - orderId, - ]); - }); + const oldProposals = await wex.db.runReadOnlyTx( + { storeNames: ["purchases"] }, + async (tx) => { + return tx.purchases.indexes.byUrlAndOrderId.getAll([ + merchantBaseUrl, + orderId, + ]); + }, + ); const oldProposal = oldProposals.find((p) => { return ( @@ -878,7 +900,7 @@ async function createOrReusePurchase( // if this transaction was shared and the order is paid then it // means that another wallet already paid the proposal const transitionInfo = await wex.db.runReadWriteTx( - ["purchases"], + { storeNames: ["purchases"] }, async (tx) => { const p = await tx.purchases.get(oldProposal.proposalId); if (!p) { @@ -944,7 +966,7 @@ async function createOrReusePurchase( }; const transitionInfo = await wex.db.runReadWriteTx( - ["purchases"], + { storeNames: ["purchases"] }, async (tx) => { await tx.purchases.put(proposalRecord); const oldTxState: TransactionState = { @@ -978,7 +1000,7 @@ async function storeFirstPaySuccess( }); const now = AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now()); const transitionInfo = await wex.db.runReadWriteTx( - ["contractTerms", "purchases"], + { storeNames: ["contractTerms", "purchases"] }, async (tx) => { const purchase = await tx.purchases.get(proposalId); @@ -1042,7 +1064,7 @@ async function storePayReplaySuccess( proposalId, }); const transitionInfo = await wex.db.runReadWriteTx( - ["purchases"], + { storeNames: ["purchases"] }, async (tx) => { const purchase = await tx.purchases.get(proposalId); @@ -1085,9 +1107,12 @@ async function handleInsufficientFunds( ): Promise { logger.trace("handling insufficient funds, trying to re-select coins"); - const proposal = await wex.db.runReadOnlyTx(["purchases"], async (tx) => { - return tx.purchases.get(proposalId); - }); + const proposal = await wex.db.runReadOnlyTx( + { storeNames: ["purchases"] }, + async (tx) => { + return tx.purchases.get(proposalId); + }, + ); if (!proposal) { return; } @@ -1127,16 +1152,19 @@ async function handleInsufficientFunds( return; } - await wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { - for (let i = 0; i < payCoinSelection.coinPubs.length; i++) { - const coinPub = payCoinSelection.coinPubs[i]; - const contrib = payCoinSelection.coinContributions[i]; - prevPayCoins.push({ - coinPub, - contribution: Amounts.parseOrThrow(contrib), - }); - } - }); + await wex.db.runReadOnlyTx( + { storeNames: ["coins", "denominations"] }, + async (tx) => { + for (let i = 0; i < payCoinSelection.coinPubs.length; i++) { + const coinPub = payCoinSelection.coinPubs[i]; + const contrib = payCoinSelection.coinContributions[i]; + prevPayCoins.push({ + coinPub, + contribution: Amounts.parseOrThrow(contrib), + }); + } + }, + ); const res = await selectPayCoins(wex, { restrictExchanges: { @@ -1165,14 +1193,16 @@ async function handleInsufficientFunds( logger.trace("re-selected coins"); await wex.db.runReadWriteTx( - [ - "purchases", - "coins", - "coinAvailability", - "denominations", - "refreshGroups", - "refreshSessions", - ], + { + storeNames: [ + "purchases", + "coins", + "coinAvailability", + "denominations", + "refreshGroups", + "refreshSessions", + ], + }, async (tx) => { const p = await tx.purchases.get(proposalId); if (!p) { @@ -1221,9 +1251,12 @@ async function checkPaymentByProposalId( proposalId: string, sessionId?: string, ): Promise { - let proposal = await wex.db.runReadOnlyTx(["purchases"], async (tx) => { - return tx.purchases.get(proposalId); - }); + let proposal = await wex.db.runReadOnlyTx( + { storeNames: ["purchases"] }, + async (tx) => { + return tx.purchases.get(proposalId); + }, + ); if (!proposal) { throw Error(`could not get proposal ${proposalId}`); } @@ -1232,7 +1265,7 @@ async function checkPaymentByProposalId( if (existingProposalId) { logger.trace("using existing purchase for same product"); const oldProposal = await wex.db.runReadOnlyTx( - ["purchases"], + { storeNames: ["purchases"] }, async (tx) => { return tx.purchases.get(existingProposalId); }, @@ -1266,9 +1299,12 @@ async function checkPaymentByProposalId( }); // First check if we already paid for it. - const purchase = await wex.db.runReadOnlyTx(["purchases"], async (tx) => { - return tx.purchases.get(proposalId); - }); + const purchase = await wex.db.runReadOnlyTx( + { storeNames: ["purchases"] }, + async (tx) => { + return tx.purchases.get(proposalId); + }, + ); if ( !purchase || @@ -1344,7 +1380,7 @@ async function checkPaymentByProposalId( ); logger.trace(`last: ${purchase.lastSessionId}, current: ${sessionId}`); const transitionInfo = await wex.db.runReadWriteTx( - ["purchases"], + { storeNames: ["purchases"] }, async (tx) => { const p = await tx.purchases.get(proposalId); if (!p) { @@ -1421,9 +1457,12 @@ export async function getContractTermsDetails( wex: WalletExecutionContext, proposalId: string, ): Promise { - const proposal = await wex.db.runReadOnlyTx(["purchases"], async (tx) => { - return tx.purchases.get(proposalId); - }); + const proposal = await wex.db.runReadOnlyTx( + { storeNames: ["purchases"] }, + async (tx) => { + return tx.purchases.get(proposalId); + }, + ); if (!proposal) { throw Error(`proposal with id ${proposalId} not found`); @@ -1513,7 +1552,7 @@ async function internalWaitProposalDownloaded( ): Promise { while (true) { const { purchase, retryInfo } = await ctx.wex.db.runReadOnlyTx( - ["purchases", "operationRetries"], + { storeNames: ["purchases", "operationRetries"] }, async (tx) => { return { purchase: await tx.purchases.get(ctx.proposalId), @@ -1610,24 +1649,27 @@ export async function generateDepositPermissions( coin: CoinRecord; denom: DenominationRecord; }> = []; - await wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { - for (let i = 0; i < payCoinSel.coinContributions.length; i++) { - const coin = await tx.coins.get(payCoinSel.coinPubs[i]); - if (!coin) { - throw Error("can't pay, allocated coin not found anymore"); - } - const denom = await tx.denominations.get([ - coin.exchangeBaseUrl, - coin.denomPubHash, - ]); - if (!denom) { - throw Error( - "can't pay, denomination of allocated coin not found anymore", - ); + await wex.db.runReadOnlyTx( + { storeNames: ["coins", "denominations"] }, + async (tx) => { + for (let i = 0; i < payCoinSel.coinContributions.length; i++) { + const coin = await tx.coins.get(payCoinSel.coinPubs[i]); + if (!coin) { + throw Error("can't pay, allocated coin not found anymore"); + } + const denom = await tx.denominations.get([ + coin.exchangeBaseUrl, + coin.denomPubHash, + ]); + if (!denom) { + throw Error( + "can't pay, denomination of allocated coin not found anymore", + ); + } + coinWithDenom.push({ coin, denom }); } - coinWithDenom.push({ coin, denom }); - } - }); + }, + ); for (let i = 0; i < payCoinSel.coinContributions.length; i++) { const { coin, denom } = coinWithDenom[i]; @@ -1662,7 +1704,7 @@ async function internalWaitPaymentResult( ): Promise { while (true) { const txRes = await ctx.wex.db.runReadOnlyTx( - ["purchases", "operationRetries"], + { storeNames: ["purchases", "operationRetries"] }, async (tx) => { const purchase = await tx.purchases.get(ctx.proposalId); const retryRecord = await tx.operationRetries.get(ctx.taskId); @@ -1776,9 +1818,12 @@ export async function confirmPay( logger.trace( `executing confirmPay with proposalId ${proposalId} and sessionIdOverride ${sessionIdOverride}`, ); - const proposal = await wex.db.runReadOnlyTx(["purchases"], async (tx) => { - return tx.purchases.get(proposalId); - }); + const proposal = await wex.db.runReadOnlyTx( + { storeNames: ["purchases"] }, + async (tx) => { + return tx.purchases.get(proposalId); + }, + ); if (!proposal) { throw Error(`proposal with id ${proposalId} not found`); @@ -1790,7 +1835,7 @@ export async function confirmPay( } const existingPurchase = await wex.db.runReadWriteTx( - ["purchases"], + { storeNames: ["purchases"] }, async (tx) => { const purchase = await tx.purchases.get(proposalId); if ( @@ -1875,14 +1920,16 @@ export async function confirmPay( ); const transitionInfo = await wex.db.runReadWriteTx( - [ - "purchases", - "coins", - "refreshGroups", - "refreshSessions", - "denominations", - "coinAvailability", - ], + { + storeNames: [ + "purchases", + "coins", + "refreshGroups", + "refreshSessions", + "denominations", + "coinAvailability", + ], + }, async (tx) => { const p = await tx.purchases.get(proposal.proposalId); if (!p) { @@ -1954,9 +2001,12 @@ export async function processPurchase( wex: WalletExecutionContext, proposalId: string, ): Promise { - const purchase = await wex.db.runReadOnlyTx(["purchases"], async (tx) => { - return tx.purchases.get(proposalId); - }); + const purchase = await wex.db.runReadOnlyTx( + { storeNames: ["purchases"] }, + async (tx) => { + return tx.purchases.get(proposalId); + }, + ); if (!purchase) { return { type: TaskRunResultType.Error, @@ -2013,9 +2063,12 @@ async function processPurchasePay( wex: WalletExecutionContext, proposalId: string, ): Promise { - const purchase = await wex.db.runReadOnlyTx(["purchases"], async (tx) => { - return tx.purchases.get(proposalId); - }); + const purchase = await wex.db.runReadOnlyTx( + { storeNames: ["purchases"] }, + async (tx) => { + return tx.purchases.get(proposalId); + }, + ); if (!purchase) { return { type: TaskRunResultType.Error, @@ -2056,7 +2109,7 @@ async function processPurchasePay( if (paid) { const transitionInfo = await wex.db.runReadWriteTx( - ["purchases"], + { storeNames: ["purchases"] }, async (tx) => { const p = await tx.purchases.get(purchase.proposalId); if (!p) { @@ -2128,14 +2181,16 @@ async function processPurchasePay( ); const transitionDone = await wex.db.runReadWriteTx( - [ - "purchases", - "coins", - "refreshGroups", - "refreshSessions", - "denominations", - "coinAvailability", - ], + { + storeNames: [ + "purchases", + "coins", + "refreshGroups", + "refreshSessions", + "denominations", + "coinAvailability", + ], + }, async (tx) => { const p = await tx.purchases.get(proposalId); if (!p) { @@ -2329,7 +2384,7 @@ export async function refuseProposal( proposalId, }); const transitionInfo = await wex.db.runReadWriteTx( - ["purchases"], + { storeNames: ["purchases"] }, async (tx) => { const proposal = await tx.purchases.get(proposalId); if (!proposal) { @@ -2603,42 +2658,45 @@ export async function sharePayment( merchantBaseUrl: string, orderId: string, ): Promise { - const result = await wex.db.runReadWriteTx(["purchases"], async (tx) => { - const p = await tx.purchases.indexes.byUrlAndOrderId.get([ - merchantBaseUrl, - orderId, - ]); - if (!p) { - logger.warn("purchase does not exist anymore"); - return undefined; - } - if ( - p.purchaseStatus !== PurchaseStatus.DialogProposed && - p.purchaseStatus !== PurchaseStatus.DialogShared - ) { - // FIXME: purchase can be shared before being paid - return undefined; - } - const oldTxState = computePayMerchantTransactionState(p); - if (p.purchaseStatus === PurchaseStatus.DialogProposed) { - p.purchaseStatus = PurchaseStatus.DialogShared; - p.shared = true; - await tx.purchases.put(p); - } + const result = await wex.db.runReadWriteTx( + { storeNames: ["purchases"] }, + async (tx) => { + const p = await tx.purchases.indexes.byUrlAndOrderId.get([ + merchantBaseUrl, + orderId, + ]); + if (!p) { + logger.warn("purchase does not exist anymore"); + return undefined; + } + if ( + p.purchaseStatus !== PurchaseStatus.DialogProposed && + p.purchaseStatus !== PurchaseStatus.DialogShared + ) { + // FIXME: purchase can be shared before being paid + return undefined; + } + const oldTxState = computePayMerchantTransactionState(p); + if (p.purchaseStatus === PurchaseStatus.DialogProposed) { + p.purchaseStatus = PurchaseStatus.DialogShared; + p.shared = true; + await tx.purchases.put(p); + } - const newTxState = computePayMerchantTransactionState(p); + const newTxState = computePayMerchantTransactionState(p); - return { - proposalId: p.proposalId, - nonce: p.noncePriv, - session: p.lastSessionId ?? p.downloadSessionId, - token: p.claimToken, - transitionInfo: { - oldTxState, - newTxState, - }, - }; - }); + return { + proposalId: p.proposalId, + nonce: p.noncePriv, + session: p.lastSessionId ?? p.downloadSessionId, + token: p.claimToken, + transitionInfo: { + oldTxState, + newTxState, + }, + }; + }, + ); if (result === undefined) { throw Error("This purchase can't be shared"); @@ -2713,7 +2771,7 @@ async function processPurchaseDialogShared( ); if (paid) { const transitionInfo = await wex.db.runReadWriteTx( - ["purchases"], + { storeNames: ["purchases"] }, async (tx) => { const p = await tx.purchases.get(purchase.proposalId); if (!p) { @@ -2752,15 +2810,16 @@ async function processPurchaseAutoRefund( const download = await expectProposalDownload(wex, purchase); - const noAutoRefundOrExpired = !purchase.autoRefundDeadline || - AbsoluteTime.isExpired( - AbsoluteTime.fromProtocolTimestamp( - timestampProtocolFromDb(purchase.autoRefundDeadline), - ), - ) + const noAutoRefundOrExpired = + !purchase.autoRefundDeadline || + AbsoluteTime.isExpired( + AbsoluteTime.fromProtocolTimestamp( + timestampProtocolFromDb(purchase.autoRefundDeadline), + ), + ); const totalKnownRefund = await wex.db.runReadOnlyTx( - ["refundGroups"], + { storeNames: ["refundGroups"] }, async (tx) => { const refunds = await tx.refundGroups.indexes.byProposalId.getAll( purchase.proposalId, @@ -2778,12 +2837,13 @@ async function processPurchaseAutoRefund( }, ); - const refundedIsLessThanPrice = Amounts.cmp(download.contractData.amount, totalKnownRefund) === +1 - const nothingMoreToRefund = !refundedIsLessThanPrice + const refundedIsLessThanPrice = + Amounts.cmp(download.contractData.amount, totalKnownRefund) === +1; + const nothingMoreToRefund = !refundedIsLessThanPrice; if (noAutoRefundOrExpired || nothingMoreToRefund) { const transitionInfo = await wex.db.runReadWriteTx( - ["purchases"], + { storeNames: ["purchases"] }, async (tx) => { const p = await tx.purchases.get(purchase.proposalId); if (!p) { @@ -2831,7 +2891,7 @@ async function processPurchaseAutoRefund( if (orderStatus.refund_pending) { const transitionInfo = await wex.db.runReadWriteTx( - ["purchases"], + { storeNames: ["purchases"] }, async (tx) => { const p = await tx.purchases.get(purchase.proposalId); if (!p) { @@ -2875,7 +2935,7 @@ async function processPurchaseAbortingRefund( throw Error("can't abort, no coins selected"); } - await wex.db.runReadOnlyTx(["coins"], async (tx) => { + await wex.db.runReadOnlyTx({ storeNames: ["coins"] }, async (tx) => { for (let i = 0; i < payCoinSelection.coinPubs.length; i++) { const coinPub = payCoinSelection.coinPubs[i]; const coin = await tx.coins.get(coinPub); @@ -2981,7 +3041,7 @@ async function processPurchaseQueryRefund( if (!orderStatus.refund_pending) { const transitionInfo = await wex.db.runReadWriteTx( - ["purchases"], + { storeNames: ["purchases"] }, async (tx) => { const p = await tx.purchases.get(purchase.proposalId); if (!p) { @@ -3008,7 +3068,7 @@ async function processPurchaseQueryRefund( ).amount; const transitionInfo = await wex.db.runReadWriteTx( - ["purchases"], + { storeNames: ["purchases"] }, async (tx) => { const p = await tx.purchases.get(purchase.proposalId); if (!p) { @@ -3076,7 +3136,7 @@ export async function startRefundQueryForUri( throw Error("expected taler://refund URI"); } const purchaseRecord = await wex.db.runReadOnlyTx( - ["purchases"], + { storeNames: ["purchases"] }, async (tx) => { return tx.purchases.indexes.byUrlAndOrderId.get([ parsedUri.merchantBaseUrl, @@ -3107,7 +3167,7 @@ export async function startQueryRefund( ): Promise { const ctx = new PayMerchantTransactionContext(wex, proposalId); const transitionInfo = await wex.db.runReadWriteTx( - ["purchases"], + { storeNames: ["purchases"] }, async (tx) => { const p = await tx.purchases.get(proposalId); if (!p) { @@ -3200,18 +3260,20 @@ async function storeRefunds( const currency = Amounts.currencyOf(download.contractData.amount); const result = await wex.db.runReadWriteTx( - [ - "coins", - "denominations", - "purchases", - "refundItems", - "refundGroups", - "denominations", - "coins", - "coinAvailability", - "refreshGroups", - "refreshSessions", - ], + { + storeNames: [ + "coins", + "denominations", + "purchases", + "refundItems", + "refundGroups", + "denominations", + "coins", + "coinAvailability", + "refreshGroups", + "refreshSessions", + ], + }, async (tx) => { const myPurchase = await tx.purchases.get(purchase.proposalId); if (!myPurchase) { diff --git a/packages/taler-wallet-core/src/pay-peer-common.ts b/packages/taler-wallet-core/src/pay-peer-common.ts index 0bb290440..bfd39b657 100644 --- a/packages/taler-wallet-core/src/pay-peer-common.ts +++ b/packages/taler-wallet-core/src/pay-peer-common.ts @@ -43,31 +43,34 @@ export async function queryCoinInfosForSelection( csel: DbPeerPushPaymentCoinSelection, ): Promise { let infos: SpendCoinDetails[] = []; - await wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { - for (let i = 0; i < csel.coinPubs.length; i++) { - const coin = await tx.coins.get(csel.coinPubs[i]); - if (!coin) { - throw Error("coin not found anymore"); - } - const denom = await getDenomInfo( - wex, - tx, - coin.exchangeBaseUrl, - coin.denomPubHash, - ); - if (!denom) { - throw Error("denom for coin not found anymore"); + await wex.db.runReadOnlyTx( + { storeNames: ["coins", "denominations"] }, + async (tx) => { + for (let i = 0; i < csel.coinPubs.length; i++) { + const coin = await tx.coins.get(csel.coinPubs[i]); + if (!coin) { + throw Error("coin not found anymore"); + } + const denom = await getDenomInfo( + wex, + tx, + coin.exchangeBaseUrl, + coin.denomPubHash, + ); + if (!denom) { + throw Error("denom for coin not found anymore"); + } + infos.push({ + coinPriv: coin.coinPriv, + coinPub: coin.coinPub, + denomPubHash: coin.denomPubHash, + denomSig: coin.denomSig, + ageCommitmentProof: coin.ageCommitmentProof, + contribution: csel.contributions[i], + }); } - infos.push({ - coinPriv: coin.coinPriv, - coinPub: coin.coinPub, - denomPubHash: coin.denomPubHash, - denomSig: coin.denomSig, - ageCommitmentProof: coin.ageCommitmentProof, - contribution: csel.contributions[i], - }); - } - }); + }, + ); return infos; } @@ -75,36 +78,39 @@ export async function getTotalPeerPaymentCost( wex: WalletExecutionContext, pcs: SelectedProspectiveCoin[], ): Promise { - return wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { - const costs: AmountJson[] = []; - for (let i = 0; i < pcs.length; i++) { - const denomInfo = await getDenomInfo( - wex, - tx, - pcs[i].exchangeBaseUrl, - pcs[i].denomPubHash, - ); - if (!denomInfo) { - throw Error( - "can't calculate payment cost, denomination for coin not found", + return wex.db.runReadOnlyTx( + { storeNames: ["coins", "denominations"] }, + async (tx) => { + const costs: AmountJson[] = []; + for (let i = 0; i < pcs.length; i++) { + const denomInfo = await getDenomInfo( + wex, + tx, + pcs[i].exchangeBaseUrl, + pcs[i].denomPubHash, + ); + if (!denomInfo) { + throw Error( + "can't calculate payment cost, denomination for coin not found", + ); + } + const amountLeft = Amounts.sub( + denomInfo.value, + pcs[i].contribution, + ).amount; + const refreshCost = await getTotalRefreshCost( + wex, + tx, + denomInfo, + amountLeft, ); + costs.push(Amounts.parseOrThrow(pcs[i].contribution)); + costs.push(refreshCost); } - const amountLeft = Amounts.sub( - denomInfo.value, - pcs[i].contribution, - ).amount; - const refreshCost = await getTotalRefreshCost( - wex, - tx, - denomInfo, - amountLeft, - ); - costs.push(Amounts.parseOrThrow(pcs[i].contribution)); - costs.push(refreshCost); - } - const zero = Amounts.zeroOfAmount(pcs[0].contribution); - return Amounts.sum([zero, ...costs]).amount; - }); + const zero = Amounts.zeroOfAmount(pcs[0].contribution); + return Amounts.sum([zero, ...costs]).amount; + }, + ); } interface ExchangePurseStatus { @@ -131,7 +137,7 @@ export async function getMergeReserveInfo( const newReservePair = await wex.cryptoApi.createEddsaKeypair({}); const mergeReserveRecord: ReserveRecord = await wex.db.runReadWriteTx( - ["exchanges", "reserves"], + { storeNames: ["exchanges", "reserves"] }, async (tx) => { const ex = await tx.exchanges.get(req.exchangeBaseUrl); checkDbInvariant(!!ex); diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts index 4155f83e6..840c244d0 100644 --- a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts +++ b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts @@ -110,7 +110,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { async deleteTransaction(): Promise { const { wex: ws, pursePub } = this; await ws.db.runReadWriteTx( - ["withdrawalGroups", "peerPullCredit", "tombstones"], + { storeNames: ["withdrawalGroups", "peerPullCredit", "tombstones"] }, async (tx) => { const pullIni = await tx.peerPullCredit.get(pursePub); if (!pullIni) { @@ -140,7 +140,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { async suspendTransaction(): Promise { const { wex, pursePub, taskId: retryTag, transactionId } = this; const transitionInfo = await wex.db.runReadWriteTx( - ["peerPullCredit"], + { storeNames: ["peerPullCredit"] }, async (tx) => { const pullCreditRec = await tx.peerPullCredit.get(pursePub); if (!pullCreditRec) { @@ -200,7 +200,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { async failTransaction(): Promise { const { wex, pursePub, taskId: retryTag, transactionId } = this; const transitionInfo = await wex.db.runReadWriteTx( - ["peerPullCredit"], + { storeNames: ["peerPullCredit"] }, async (tx) => { const pullCreditRec = await tx.peerPullCredit.get(pursePub); if (!pullCreditRec) { @@ -251,7 +251,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { async resumeTransaction(): Promise { const { wex, pursePub, taskId: retryTag, transactionId } = this; const transitionInfo = await wex.db.runReadWriteTx( - ["peerPullCredit"], + { storeNames: ["peerPullCredit"] }, async (tx) => { const pullCreditRec = await tx.peerPullCredit.get(pursePub); if (!pullCreditRec) { @@ -310,7 +310,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { async abortTransaction(): Promise { const { wex, pursePub, taskId: retryTag, transactionId } = this; const transitionInfo = await wex.db.runReadWriteTx( - ["peerPullCredit"], + { storeNames: ["peerPullCredit"] }, async (tx) => { const pullCreditRec = await tx.peerPullCredit.get(pursePub); if (!pullCreditRec) { @@ -388,7 +388,7 @@ async function queryPurseForPeerPullCredit( case HttpStatusCode.Gone: { // Exchange says that purse doesn't exist anymore => expired! const transitionInfo = await wex.db.runReadWriteTx( - ["peerPullCredit"], + { storeNames: ["peerPullCredit"] }, async (tx) => { const finPi = await tx.peerPullCredit.get(pullIni.pursePub); if (!finPi) { @@ -426,9 +426,12 @@ async function queryPurseForPeerPullCredit( return TaskRunResult.backoff(); } - const reserve = await wex.db.runReadOnlyTx(["reserves"], async (tx) => { - return await tx.reserves.get(pullIni.mergeReserveRowId); - }); + const reserve = await wex.db.runReadOnlyTx( + { storeNames: ["reserves"] }, + async (tx) => { + return await tx.reserves.get(pullIni.mergeReserveRowId); + }, + ); if (!reserve) { throw Error("reserve for peer pull credit not found in wallet DB"); @@ -449,7 +452,7 @@ async function queryPurseForPeerPullCredit( }, }); const transitionInfo = await wex.db.runReadWriteTx( - ["peerPullCredit"], + { storeNames: ["peerPullCredit"] }, async (tx) => { const finPi = await tx.peerPullCredit.get(pullIni.pursePub); if (!finPi) { @@ -497,7 +500,7 @@ async function longpollKycStatus( kycStatusRes.status === HttpStatusCode.NoContent ) { const transitionInfo = await wex.db.runReadWriteTx( - ["peerPullCredit"], + { storeNames: ["peerPullCredit"] }, async (tx) => { const peerIni = await tx.peerPullCredit.get(pursePub); if (!peerIni) { @@ -548,13 +551,15 @@ async function processPeerPullCreditAbortingDeletePurse( logger.info(`deleted purse with response status ${resp.status}`); const transitionInfo = await wex.db.runReadWriteTx( - [ - "peerPullCredit", - "refreshGroups", - "denominations", - "coinAvailability", - "coins", - ], + { + storeNames: [ + "peerPullCredit", + "refreshGroups", + "denominations", + "coinAvailability", + "coins", + ], + }, async (tx) => { const ppiRec = await tx.peerPullCredit.get(pursePub); if (!ppiRec) { @@ -593,7 +598,7 @@ async function handlePeerPullCreditWithdrawing( const wgId = pullIni.withdrawalGroupId; let finished: boolean = false; const transitionInfo = await wex.db.runReadWriteTx( - ["peerPullCredit", "withdrawalGroups"], + { storeNames: ["peerPullCredit", "withdrawalGroups"] }, async (tx) => { const ppi = await tx.peerPullCredit.get(pullIni.pursePub); if (!ppi) { @@ -640,16 +645,19 @@ async function handlePeerPullCreditCreatePurse( ): Promise { const purseFee = Amounts.stringify(Amounts.zeroOfAmount(pullIni.amount)); const pursePub = pullIni.pursePub; - const mergeReserve = await wex.db.runReadOnlyTx(["reserves"], async (tx) => { - return tx.reserves.get(pullIni.mergeReserveRowId); - }); + const mergeReserve = await wex.db.runReadOnlyTx( + { storeNames: ["reserves"] }, + async (tx) => { + return tx.reserves.get(pullIni.mergeReserveRowId); + }, + ); if (!mergeReserve) { throw Error("merge reserve for peer pull payment not found in database"); } const contractTermsRecord = await wex.db.runReadOnlyTx( - ["contractTerms"], + { storeNames: ["contractTerms"] }, async (tx) => { return tx.contractTerms.get(pullIni.contractTermsHash); }, @@ -737,7 +745,7 @@ async function handlePeerPullCreditCreatePurse( }); const transitionInfo = await wex.db.runReadWriteTx( - ["peerPullCredit"], + { storeNames: ["peerPullCredit"] }, async (tx) => { const pi2 = await tx.peerPullCredit.get(pursePub); if (!pi2) { @@ -758,9 +766,12 @@ export async function processPeerPullCredit( wex: WalletExecutionContext, pursePub: string, ): Promise { - const pullIni = await wex.db.runReadOnlyTx(["peerPullCredit"], async (tx) => { - return tx.peerPullCredit.get(pursePub); - }); + const pullIni = await wex.db.runReadOnlyTx( + { storeNames: ["peerPullCredit"] }, + async (tx) => { + return tx.peerPullCredit.get(pursePub); + }, + ); if (!pullIni) { throw Error("peer pull payment initiation not found in database"); } @@ -847,7 +858,7 @@ async function processPeerPullCreditKycRequired( const kycStatus = await kycStatusRes.json(); logger.info(`kyc status: ${j2s(kycStatus)}`); const { transitionInfo, result } = await wex.db.runReadWriteTx( - ["peerPullCredit"], + { storeNames: ["peerPullCredit"] }, async (tx) => { const peerInc = await tx.peerPullCredit.get(pursePub); if (!peerInc) { @@ -947,42 +958,45 @@ async function getPreferredExchangeForCurrency( ): Promise { // Find an exchange with the matching currency. // Prefer exchanges with the most recent withdrawal. - const url = await wex.db.runReadOnlyTx(["exchanges"], async (tx) => { - const exchanges = await tx.exchanges.iter().toArray(); - let candidate = undefined; - for (const e of exchanges) { - if (e.detailsPointer?.currency !== currency) { - continue; - } - if (!candidate) { - candidate = e; - continue; - } - if (candidate.lastWithdrawal && !e.lastWithdrawal) { - continue; - } - const exchangeLastWithdrawal = timestampOptionalPreciseFromDb( - e.lastWithdrawal, - ); - const candidateLastWithdrawal = timestampOptionalPreciseFromDb( - candidate.lastWithdrawal, - ); - if (exchangeLastWithdrawal && candidateLastWithdrawal) { - if ( - AbsoluteTime.cmp( - AbsoluteTime.fromPreciseTimestamp(exchangeLastWithdrawal), - AbsoluteTime.fromPreciseTimestamp(candidateLastWithdrawal), - ) > 0 - ) { + const url = await wex.db.runReadOnlyTx( + { storeNames: ["exchanges"] }, + async (tx) => { + const exchanges = await tx.exchanges.iter().toArray(); + let candidate = undefined; + for (const e of exchanges) { + if (e.detailsPointer?.currency !== currency) { + continue; + } + if (!candidate) { candidate = e; + continue; + } + if (candidate.lastWithdrawal && !e.lastWithdrawal) { + continue; + } + const exchangeLastWithdrawal = timestampOptionalPreciseFromDb( + e.lastWithdrawal, + ); + const candidateLastWithdrawal = timestampOptionalPreciseFromDb( + candidate.lastWithdrawal, + ); + if (exchangeLastWithdrawal && candidateLastWithdrawal) { + if ( + AbsoluteTime.cmp( + AbsoluteTime.fromPreciseTimestamp(exchangeLastWithdrawal), + AbsoluteTime.fromPreciseTimestamp(candidateLastWithdrawal), + ) > 0 + ) { + candidate = e; + } } } - } - if (candidate) { - return candidate.baseUrl; - } - return undefined; - }); + if (candidate) { + return candidate.baseUrl; + } + return undefined; + }, + ); return url; } @@ -1039,7 +1053,7 @@ export async function initiatePeerPullPayment( const mergeTimestamp = TalerPreciseTimestamp.now(); const transitionInfo = await wex.db.runReadWriteTx( - ["peerPullCredit", "contractTerms"], + { storeNames: ["peerPullCredit", "contractTerms"] }, async (tx) => { const ppi: PeerPullCreditRecord = { amount: req.partialContractTerms.amount, diff --git a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts index 92eb44a87..0355b58ad 100644 --- a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts +++ b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts @@ -126,13 +126,16 @@ export class PeerPullDebitTransactionContext implements TransactionContext { const transactionId = this.transactionId; const ws = this.wex; const peerPullDebitId = this.peerPullDebitId; - await ws.db.runReadWriteTx(["peerPullDebit", "tombstones"], async (tx) => { - const debit = await tx.peerPullDebit.get(peerPullDebitId); - if (debit) { - await tx.peerPullDebit.delete(peerPullDebitId); - await tx.tombstones.put({ id: transactionId }); - } - }); + await ws.db.runReadWriteTx( + { storeNames: ["peerPullDebit", "tombstones"] }, + async (tx) => { + const debit = await tx.peerPullDebit.get(peerPullDebitId); + if (debit) { + await tx.peerPullDebit.delete(peerPullDebitId); + await tx.tombstones.put({ id: transactionId }); + } + }, + ); } async suspendTransaction(): Promise { @@ -141,7 +144,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { const wex = this.wex; const peerPullDebitId = this.peerPullDebitId; const transitionInfo = await wex.db.runReadWriteTx( - ["peerPullDebit"], + { storeNames: ["peerPullDebit"] }, async (tx) => { const pullDebitRec = await tx.peerPullDebit.get(peerPullDebitId); if (!pullDebitRec) { @@ -305,7 +308,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { const wex = this.wex; const extraStores = opts.extraStores ?? []; const transitionInfo = await wex.db.runReadWriteTx( - ["peerPullDebit", ...extraStores], + { storeNames: ["peerPullDebit", ...extraStores] }, async (tx) => { const pi = await tx.peerPullDebit.get(this.peerPullDebitId); if (!pi) { @@ -398,7 +401,7 @@ async function handlePurseCreationConflict( coinSelRes.result.coins, ); - await ws.db.runReadWriteTx(["peerPullDebit"], async (tx) => { + await ws.db.runReadWriteTx({ storeNames: ["peerPullDebit"] }, async (tx) => { const myPpi = await tx.peerPullDebit.get(peerPullInc.peerPullDebitId); if (!myPpi) { return; @@ -470,15 +473,17 @@ async function processPeerPullDebitPendingDeposit( // FIXME: Missing notification here! const transitionDone = await wex.db.runReadWriteTx( - [ - "exchanges", - "coins", - "denominations", - "refreshGroups", - "refreshSessions", - "peerPullDebit", - "coinAvailability", - ], + { + storeNames: [ + "exchanges", + "coins", + "denominations", + "refreshGroups", + "refreshSessions", + "peerPullDebit", + "coinAvailability", + ], + }, async (tx) => { const pi = await tx.peerPullDebit.get(peerPullDebitId); if (!pi) { @@ -608,7 +613,7 @@ async function processPeerPullDebitAbortingRefresh( peerPullDebitId, }); const transitionInfo = await wex.db.runReadWriteTx( - ["peerPullDebit", "refreshGroups"], + { storeNames: ["peerPullDebit", "refreshGroups"] }, async (tx) => { const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId); let newOpState: PeerPullDebitRecordStatus | undefined; @@ -650,7 +655,7 @@ export async function processPeerPullDebit( peerPullDebitId: string, ): Promise { const peerPullInc = await wex.db.runReadOnlyTx( - ["peerPullDebit"], + { storeNames: ["peerPullDebit"] }, async (tx) => { return tx.peerPullDebit.get(peerPullDebitId); }, @@ -680,7 +685,7 @@ export async function confirmPeerPullDebit( peerPullDebitId = parsedTx.peerPullDebitId; const peerPullInc = await wex.db.runReadOnlyTx( - ["peerPullDebit"], + { storeNames: ["peerPullDebit"] }, async (tx) => { return tx.peerPullDebit.get(peerPullDebitId); }, @@ -726,15 +731,17 @@ export async function confirmPeerPullDebit( // FIXME: Missing notification here! await wex.db.runReadWriteTx( - [ - "exchanges", - "coins", - "denominations", - "refreshGroups", - "refreshSessions", - "peerPullDebit", - "coinAvailability", - ], + { + storeNames: [ + "exchanges", + "coins", + "denominations", + "refreshGroups", + "refreshSessions", + "peerPullDebit", + "coinAvailability", + ], + }, async (tx) => { const pi = await tx.peerPullDebit.get(peerPullDebitId); if (!pi) { @@ -798,7 +805,7 @@ export async function preparePeerPullDebit( } const existing = await wex.db.runReadOnlyTx( - ["peerPullDebit", "contractTerms"], + { storeNames: ["peerPullDebit", "contractTerms"] }, async (tx) => { const peerPullDebitRecord = await tx.peerPullDebit.indexes.byExchangeAndContractPriv.get([ @@ -911,7 +918,7 @@ export async function preparePeerPullDebit( const totalAmount = await getTotalPeerPaymentCost(wex, coins); await wex.db.runReadWriteTx( - ["peerPullDebit", "contractTerms"], + { storeNames: ["peerPullDebit", "contractTerms"] }, async (tx) => { await tx.contractTerms.put({ h: contractTermsHash, diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts index 281b3ff61..93f1a63a7 100644 --- a/packages/taler-wallet-core/src/pay-peer-push-credit.ts +++ b/packages/taler-wallet-core/src/pay-peer-push-credit.ts @@ -114,7 +114,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { async deleteTransaction(): Promise { const { wex, peerPushCreditId } = this; await wex.db.runReadWriteTx( - ["withdrawalGroups", "peerPushCredit", "tombstones"], + { storeNames: ["withdrawalGroups", "peerPushCredit", "tombstones"] }, async (tx) => { const pushInc = await tx.peerPushCredit.get(peerPushCreditId); if (!pushInc) { @@ -143,7 +143,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { async suspendTransaction(): Promise { const { wex, peerPushCreditId, taskId: retryTag, transactionId } = this; const transitionInfo = await wex.db.runReadWriteTx( - ["peerPushCredit"], + { storeNames: ["peerPushCredit"] }, async (tx) => { const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId); if (!pushCreditRec) { @@ -197,7 +197,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { async abortTransaction(): Promise { const { wex, peerPushCreditId, taskId: retryTag, transactionId } = this; const transitionInfo = await wex.db.runReadWriteTx( - ["peerPushCredit"], + { storeNames: ["peerPushCredit"] }, async (tx) => { const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId); if (!pushCreditRec) { @@ -254,7 +254,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { async resumeTransaction(): Promise { const { wex, peerPushCreditId, taskId: retryTag, transactionId } = this; const transitionInfo = await wex.db.runReadWriteTx( - ["peerPushCredit"], + { storeNames: ["peerPushCredit"] }, async (tx) => { const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId); if (!pushCreditRec) { @@ -307,7 +307,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { async failTransaction(): Promise { const { wex, peerPushCreditId, taskId: retryTag, transactionId } = this; const transitionInfo = await wex.db.runReadWriteTx( - ["peerPushCredit"], + { storeNames: ["peerPushCredit"] }, async (tx) => { const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId); if (!pushCreditRec) { @@ -365,7 +365,7 @@ export async function preparePeerPushCredit( } const existing = await wex.db.runReadOnlyTx( - ["contractTerms", "peerPushCredit"], + { storeNames: ["contractTerms", "peerPushCredit"] }, async (tx) => { const existingPushInc = await tx.peerPushCredit.indexes.byExchangeAndContractPriv.get([ @@ -460,7 +460,7 @@ export async function preparePeerPushCredit( ); const transitionInfo = await wex.db.runReadWriteTx( - ["contractTerms", "peerPushCredit"], + { storeNames: ["contractTerms", "peerPushCredit"] }, async (tx) => { const rec: PeerPushPaymentIncomingRecord = { peerPushCreditId, @@ -545,7 +545,7 @@ async function longpollKycStatus( kycStatusRes.status === HttpStatusCode.NoContent ) { const transitionInfo = await wex.db.runReadWriteTx( - ["peerPushCredit"], + { storeNames: ["peerPushCredit"] }, async (tx) => { const peerInc = await tx.peerPushCredit.get(peerPushCreditId); if (!peerInc) { @@ -606,7 +606,7 @@ async function processPeerPushCreditKycRequired( const kycStatus = await kycStatusRes.json(); logger.info(`kyc status: ${j2s(kycStatus)}`); const { transitionInfo, result } = await wex.db.runReadWriteTx( - ["peerPushCredit"], + { storeNames: ["peerPushCredit"] }, async (tx) => { const peerInc = await tx.peerPushCredit.get(peerPushCreditId); if (!peerInc) { @@ -731,14 +731,16 @@ async function handlePendingMerge( }); const txRes = await wex.db.runReadWriteTx( - [ - "contractTerms", - "peerPushCredit", - "withdrawalGroups", - "reserves", - "exchanges", - "exchangeDetails", - ], + { + storeNames: [ + "contractTerms", + "peerPushCredit", + "withdrawalGroups", + "reserves", + "exchanges", + "exchangeDetails", + ], + }, async (tx) => { const peerInc = await tx.peerPushCredit.get(peerPushCreditId); if (!peerInc) { @@ -798,7 +800,7 @@ async function handlePendingWithdrawing( const wgId = peerInc.withdrawalGroupId; let finished: boolean = false; const transitionInfo = await wex.db.runReadWriteTx( - ["peerPushCredit", "withdrawalGroups"], + { storeNames: ["peerPushCredit", "withdrawalGroups"] }, async (tx) => { const ppi = await tx.peerPushCredit.get(peerInc.peerPushCreditId); if (!ppi) { @@ -846,7 +848,7 @@ export async function processPeerPushCredit( let peerInc: PeerPushPaymentIncomingRecord | undefined; let contractTerms: PeerContractTerms | undefined; await wex.db.runReadWriteTx( - ["contractTerms", "peerPushCredit"], + { storeNames: ["contractTerms", "peerPushCredit"] }, async (tx) => { peerInc = await tx.peerPushCredit.get(peerPushCreditId); if (!peerInc) { @@ -915,7 +917,7 @@ export async function confirmPeerPushCredit( logger.trace(`confirming peer-push-credit ${peerPushCreditId}`); await wex.db.runReadWriteTx( - ["contractTerms", "peerPushCredit"], + { storeNames: ["contractTerms", "peerPushCredit"] }, async (tx) => { peerInc = await tx.peerPushCredit.get(peerPushCreditId); if (!peerInc) { diff --git a/packages/taler-wallet-core/src/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/pay-peer-push-debit.ts index 63a02d7a7..6452407ff 100644 --- a/packages/taler-wallet-core/src/pay-peer-push-debit.ts +++ b/packages/taler-wallet-core/src/pay-peer-push-debit.ts @@ -104,19 +104,22 @@ export class PeerPushDebitTransactionContext implements TransactionContext { async deleteTransaction(): Promise { const { wex, pursePub, transactionId } = this; - await wex.db.runReadWriteTx(["peerPushDebit", "tombstones"], async (tx) => { - const debit = await tx.peerPushDebit.get(pursePub); - if (debit) { - await tx.peerPushDebit.delete(pursePub); - await tx.tombstones.put({ id: transactionId }); - } - }); + await wex.db.runReadWriteTx( + { storeNames: ["peerPushDebit", "tombstones"] }, + async (tx) => { + const debit = await tx.peerPushDebit.get(pursePub); + if (debit) { + await tx.peerPushDebit.delete(pursePub); + await tx.tombstones.put({ id: transactionId }); + } + }, + ); } async suspendTransaction(): Promise { const { wex, pursePub, transactionId, taskId: retryTag } = this; const transitionInfo = await wex.db.runReadWriteTx( - ["peerPushDebit"], + { storeNames: ["peerPushDebit"] }, async (tx) => { const pushDebitRec = await tx.peerPushDebit.get(pursePub); if (!pushDebitRec) { @@ -174,7 +177,7 @@ export class PeerPushDebitTransactionContext implements TransactionContext { async abortTransaction(): Promise { const { wex, pursePub, transactionId, taskId: retryTag } = this; const transitionInfo = await wex.db.runReadWriteTx( - ["peerPushDebit"], + { storeNames: ["peerPushDebit"] }, async (tx) => { const pushDebitRec = await tx.peerPushDebit.get(pursePub); if (!pushDebitRec) { @@ -228,7 +231,7 @@ export class PeerPushDebitTransactionContext implements TransactionContext { async resumeTransaction(): Promise { const { wex, pursePub, transactionId, taskId: retryTag } = this; const transitionInfo = await wex.db.runReadWriteTx( - ["peerPushDebit"], + { storeNames: ["peerPushDebit"] }, async (tx) => { const pushDebitRec = await tx.peerPushDebit.get(pursePub); if (!pushDebitRec) { @@ -286,7 +289,7 @@ export class PeerPushDebitTransactionContext implements TransactionContext { async failTransaction(): Promise { const { wex, pursePub, transactionId, taskId: retryTag } = this; const transitionInfo = await wex.db.runReadWriteTx( - ["peerPushDebit"], + { storeNames: ["peerPushDebit"] }, async (tx) => { const pushDebitRec = await tx.peerPushDebit.get(pursePub); if (!pushDebitRec) { @@ -434,7 +437,7 @@ async function handlePurseCreationConflict( assertUnreachable(coinSelRes); } - await wex.db.runReadWriteTx(["peerPushDebit"], async (tx) => { + await wex.db.runReadWriteTx({ storeNames: ["peerPushDebit"] }, async (tx) => { const myPpi = await tx.peerPushDebit.get(peerPushInitiation.pursePub); if (!myPpi) { return; @@ -470,7 +473,7 @@ async function processPeerPushDebitCreateReserve( logger.trace(`processing ${transactionId} pending(create-reserve)`); const contractTermsRecord = await wex.db.runReadOnlyTx( - ["contractTerms"], + { storeNames: ["contractTerms"] }, async (tx) => { return tx.contractTerms.get(hContractTerms); }, @@ -503,16 +506,18 @@ async function processPeerPushDebitCreateReserve( assertUnreachable(coinSelRes); } const transitionDone = await wex.db.runReadWriteTx( - [ - "exchanges", - "contractTerms", - "coins", - "coinAvailability", - "denominations", - "refreshGroups", - "refreshSessions", - "peerPushDebit", - ], + { + storeNames: [ + "exchanges", + "contractTerms", + "coins", + "coinAvailability", + "denominations", + "refreshGroups", + "refreshSessions", + "peerPushDebit", + ], + }, async (tx) => { const ppi = await tx.peerPushDebit.get(pursePub); if (!ppi) { @@ -720,14 +725,16 @@ async function processPeerPushDebitAbortingDeletePurse( logger.info(`deleted purse with response status ${resp.status}`); const transitionInfo = await wex.db.runReadWriteTx( - [ - "peerPushDebit", - "refreshGroups", - "refreshSessions", - "denominations", - "coinAvailability", - "coins", - ], + { + storeNames: [ + "peerPushDebit", + "refreshGroups", + "refreshSessions", + "denominations", + "coinAvailability", + "coins", + ], + }, async (tx) => { const ppiRec = await tx.peerPushDebit.get(pursePub); if (!ppiRec) { @@ -779,6 +786,7 @@ interface SimpleTransition { stTo: PeerPushDebitStatus; } +// FIXME: This should be a transition on the peer push debit transaction context! async function transitionPeerPushDebitTransaction( wex: WalletExecutionContext, pursePub: string, @@ -789,7 +797,7 @@ async function transitionPeerPushDebitTransaction( pursePub, }); const transitionInfo = await wex.db.runReadWriteTx( - ["peerPushDebit"], + { storeNames: ["peerPushDebit"] }, async (tx) => { const ppiRec = await tx.peerPushDebit.get(pursePub); if (!ppiRec) { @@ -826,7 +834,7 @@ async function processPeerPushDebitAbortingRefreshDeleted( await waitRefreshFinal(wex, peerPushInitiation.abortRefreshGroupId); } const transitionInfo = await wex.db.runReadWriteTx( - ["refreshGroups", "peerPushDebit"], + { storeNames: ["refreshGroups", "peerPushDebit"] }, async (tx) => { const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId); let newOpState: PeerPushDebitStatus | undefined; @@ -875,7 +883,7 @@ async function processPeerPushDebitAbortingRefreshExpired( pursePub: peerPushInitiation.pursePub, }); const transitionInfo = await wex.db.runReadWriteTx( - ["peerPushDebit", "refreshGroups"], + { storeNames: ["peerPushDebit", "refreshGroups"] }, async (tx) => { const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId); let newOpState: PeerPushDebitStatus | undefined; @@ -958,14 +966,16 @@ async function processPeerPushDebitReady( } else if (resp.status === HttpStatusCode.Gone) { logger.info(`purse ${pursePub} is gone, aborting peer-push-debit`); const transitionInfo = await wex.db.runReadWriteTx( - [ - "peerPushDebit", - "refreshGroups", - "refreshSessions", - "denominations", - "coinAvailability", - "coins", - ], + { + storeNames: [ + "peerPushDebit", + "refreshGroups", + "refreshSessions", + "denominations", + "coinAvailability", + "coins", + ], + }, async (tx) => { const ppiRec = await tx.peerPushDebit.get(pursePub); if (!ppiRec) { @@ -1019,7 +1029,7 @@ export async function processPeerPushDebit( pursePub: string, ): Promise { const peerPushInitiation = await wex.db.runReadOnlyTx( - ["peerPushDebit"], + { storeNames: ["peerPushDebit"] }, async (tx) => { return tx.peerPushDebit.get(pursePub); }, @@ -1118,16 +1128,18 @@ export async function initiatePeerPushDebit( const contractEncNonce = encodeCrock(getRandomBytes(24)); const transitionInfo = await wex.db.runReadWriteTx( - [ - "exchanges", - "contractTerms", - "coins", - "coinAvailability", - "denominations", - "refreshGroups", - "refreshSessions", - "peerPushDebit", - ], + { + storeNames: [ + "exchanges", + "contractTerms", + "coins", + "coinAvailability", + "denominations", + "refreshGroups", + "refreshSessions", + "peerPushDebit", + ], + }, async (tx) => { const ppi: PeerPushDebitRecord = { amount: Amounts.stringify(instructedAmount), diff --git a/packages/taler-wallet-core/src/query.ts b/packages/taler-wallet-core/src/query.ts index 0a321b835..eb5752fbe 100644 --- a/packages/taler-wallet-core/src/query.ts +++ b/packages/taler-wallet-core/src/query.ts @@ -819,12 +819,16 @@ export interface DbAccess { ): Promise; runReadWriteTx>>( - storeNames: StoreNameArray, + opts: { + storeNames: StoreNameArray; + }, txf: (tx: DbReadWriteTransaction) => Promise, ): Promise; runReadOnlyTx>>( - storeNames: StoreNameArray, + opts: { + storeNames: StoreNameArray; + }, txf: (tx: DbReadOnlyTransaction) => Promise, ): Promise; } @@ -945,13 +949,15 @@ export class DbAccessImpl implements DbAccess { } async runReadWriteTx>>( - storeNames: StoreNameArray, + opts: { + storeNames: StoreNameArray; + }, txf: (tx: DbReadWriteTransaction) => Promise, ): Promise { const accessibleStores: { [x: string]: StoreWithIndexes } = {}; const strStoreNames: string[] = []; - for (const sn of storeNames) { + for (const sn of opts.storeNames) { const swi = (this.stores as any)[sn] as StoreWithIndexes; strStoreNames.push(swi.storeName); accessibleStores[swi.storeName] = swi; @@ -969,13 +975,15 @@ export class DbAccessImpl implements DbAccess { } runReadOnlyTx>>( - storeNames: StoreNameArray, + opts: { + storeNames: StoreNameArray; + }, txf: (tx: DbReadOnlyTransaction) => Promise, ): Promise { const accessibleStores: { [x: string]: StoreWithIndexes } = {}; const strStoreNames: string[] = []; - for (const sn of storeNames) { + for (const sn of opts.storeNames) { const swi = (this.stores as any)[sn] as StoreWithIndexes; strStoreNames.push(swi.storeName); accessibleStores[swi.storeName] = swi; diff --git a/packages/taler-wallet-core/src/recoup.ts b/packages/taler-wallet-core/src/recoup.ts index 758ba106d..6a09f9a0e 100644 --- a/packages/taler-wallet-core/src/recoup.ts +++ b/packages/taler-wallet-core/src/recoup.ts @@ -99,7 +99,7 @@ async function recoupRewardCoin( // Thus we just put the coin to sleep. // FIXME: somehow report this to the user await wex.db.runReadWriteTx( - ["recoupGroups", "denominations", "refreshGroups", "coins"], + { storeNames: ["recoupGroups", "denominations", "refreshGroups", "coins"] }, async (tx) => { const recoupGroup = await tx.recoupGroups.get(recoupGroupId); if (!recoupGroup) { @@ -121,7 +121,7 @@ async function recoupRefreshCoin( cs: RefreshCoinSource, ): Promise { const d = await wex.db.runReadOnlyTx( - ["coins", "denominations"], + { storeNames: ["coins", "denominations"] }, async (tx) => { const denomInfo = await getDenomInfo( wex, @@ -168,7 +168,7 @@ async function recoupRefreshCoin( } await wex.db.runReadWriteTx( - ["coins", "denominations", "recoupGroups", "refreshGroups"], + { storeNames: ["coins", "denominations", "recoupGroups", "refreshGroups"] }, async (tx) => { const recoupGroup = await tx.recoupGroups.get(recoupGroupId); if (!recoupGroup) { @@ -233,7 +233,7 @@ export async function recoupWithdrawCoin( ): Promise { const reservePub = cs.reservePub; const denomInfo = await wex.db.runReadOnlyTx( - ["denominations"], + { storeNames: ["denominations"] }, async (tx) => { const denomInfo = await getDenomInfo( wex, @@ -276,7 +276,7 @@ export async function recoupWithdrawCoin( // FIXME: verify that our expectations about the amount match await wex.db.runReadWriteTx( - ["coins", "denominations", "recoupGroups", "refreshGroups"], + { storeNames: ["coins", "denominations", "recoupGroups", "refreshGroups"] }, async (tx) => { const recoupGroup = await tx.recoupGroups.get(recoupGroupId); if (!recoupGroup) { @@ -300,9 +300,12 @@ export async function processRecoupGroup( wex: WalletExecutionContext, recoupGroupId: string, ): Promise { - let recoupGroup = await wex.db.runReadOnlyTx(["recoupGroups"], async (tx) => { - return tx.recoupGroups.get(recoupGroupId); - }); + let recoupGroup = await wex.db.runReadOnlyTx( + { storeNames: ["recoupGroups"] }, + async (tx) => { + return tx.recoupGroups.get(recoupGroupId); + }, + ); if (!recoupGroup) { return TaskRunResult.finished(); } @@ -320,9 +323,12 @@ export async function processRecoupGroup( }); await Promise.all(ps); - recoupGroup = await wex.db.runReadOnlyTx(["recoupGroups"], async (tx) => { - return tx.recoupGroups.get(recoupGroupId); - }); + recoupGroup = await wex.db.runReadOnlyTx( + { storeNames: ["recoupGroups"] }, + async (tx) => { + return tx.recoupGroups.get(recoupGroupId); + }, + ); if (!recoupGroup) { return TaskRunResult.finished(); } @@ -339,22 +345,25 @@ export async function processRecoupGroup( const reservePrivMap: Record = {}; for (let i = 0; i < recoupGroup.coinPubs.length; i++) { const coinPub = recoupGroup.coinPubs[i]; - await wex.db.runReadOnlyTx(["coins", "reserves"], async (tx) => { - const coin = await tx.coins.get(coinPub); - if (!coin) { - throw Error(`Coin ${coinPub} not found, can't request recoup`); - } - if (coin.coinSource.type === CoinSourceType.Withdraw) { - const reserve = await tx.reserves.indexes.byReservePub.get( - coin.coinSource.reservePub, - ); - if (!reserve) { - return; + await wex.db.runReadOnlyTx( + { storeNames: ["coins", "reserves"] }, + async (tx) => { + const coin = await tx.coins.get(coinPub); + if (!coin) { + throw Error(`Coin ${coinPub} not found, can't request recoup`); } - reserveSet.add(coin.coinSource.reservePub); - reservePrivMap[coin.coinSource.reservePub] = reserve.reservePriv; - } - }); + if (coin.coinSource.type === CoinSourceType.Withdraw) { + const reserve = await tx.reserves.indexes.byReservePub.get( + coin.coinSource.reservePub, + ); + if (!reserve) { + return; + } + reserveSet.add(coin.coinSource.reservePub); + reservePrivMap[coin.coinSource.reservePub] = reserve.reservePriv; + } + }, + ); } for (const reservePub of reserveSet) { @@ -385,14 +394,16 @@ export async function processRecoupGroup( } await wex.db.runReadWriteTx( - [ - "recoupGroups", - "coinAvailability", - "denominations", - "refreshGroups", - "refreshSessions", - "coins", - ], + { + storeNames: [ + "recoupGroups", + "coinAvailability", + "denominations", + "refreshGroups", + "refreshSessions", + "coins", + ], + }, async (tx) => { const rg2 = await tx.recoupGroups.get(recoupGroupId); if (!rg2) { @@ -502,7 +513,7 @@ async function processRecoupForCoin( coinIdx: number, ): Promise { const coin = await wex.db.runReadOnlyTx( - ["coins", "recoupGroups"], + { storeNames: ["coins", "recoupGroups"] }, async (tx) => { const recoupGroup = await tx.recoupGroups.get(recoupGroupId); if (!recoupGroup) { diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts index 99ac5737b..7800967e6 100644 --- a/packages/taler-wallet-core/src/refresh.ts +++ b/packages/taler-wallet-core/src/refresh.ts @@ -186,7 +186,7 @@ export class RefreshTransactionContext implements TransactionContext { ? [...baseStores, ...opts.extraStores] : baseStores; const transitionInfo = await this.wex.db.runReadWriteTx( - stores, + { storeNames: stores }, async (tx) => { const wgRec = await tx.refreshGroups.get(this.refreshGroupId); let oldTxState: TransactionState; @@ -565,7 +565,14 @@ async function refreshMelt( ): Promise { const ctx = new RefreshTransactionContext(wex, refreshGroupId); const d = await wex.db.runReadWriteTx( - ["refreshGroups", "refreshSessions", "coins", "denominations"], + { + storeNames: [ + "refreshGroups", + "refreshSessions", + "coins", + "denominations", + ], + }, async (tx) => { const refreshGroup = await tx.refreshGroups.get(refreshGroupId); if (!refreshGroup) { @@ -723,7 +730,7 @@ async function refreshMelt( refreshSession.norevealIndex = norevealIndex; await wex.db.runReadWriteTx( - ["refreshGroups", "refreshSessions"], + { storeNames: ["refreshGroups", "refreshSessions"] }, async (tx) => { const rg = await tx.refreshGroups.get(refreshGroupId); if (!rg) { @@ -755,13 +762,15 @@ async function handleRefreshMeltGone( // FIXME: Validate signature. await ctx.wex.db.runReadWriteTx( - [ - "refreshGroups", - "refreshSessions", - "coins", - "denominations", - "coinAvailability", - ], + { + storeNames: [ + "refreshGroups", + "refreshSessions", + "coins", + "denominations", + "coinAvailability", + ], + }, async (tx) => { const rg = await tx.refreshGroups.get(ctx.refreshGroupId); if (!rg) { @@ -832,13 +841,15 @@ async function handleRefreshMeltConflict( // FIXME: If response seems wrong, report to auditor (in the future!); await ctx.wex.db.runReadWriteTx( - [ - "refreshGroups", - "refreshSessions", - "denominations", - "coins", - "coinAvailability", - ], + { + storeNames: [ + "refreshGroups", + "refreshSessions", + "denominations", + "coins", + "coinAvailability", + ], + }, async (tx) => { const rg = await tx.refreshGroups.get(ctx.refreshGroupId); if (!rg) { @@ -891,13 +902,15 @@ async function handleRefreshMeltNotFound( ): Promise { // FIXME: Validate the exchange's error response await ctx.wex.db.runReadWriteTx( - [ - "refreshGroups", - "refreshSessions", - "coins", - "denominations", - "coinAvailability", - ], + { + storeNames: [ + "refreshGroups", + "refreshSessions", + "coins", + "denominations", + "coinAvailability", + ], + }, async (tx) => { const rg = await tx.refreshGroups.get(ctx.refreshGroupId); if (!rg) { @@ -993,7 +1006,14 @@ async function refreshReveal( ); const ctx = new RefreshTransactionContext(wex, refreshGroupId); const d = await wex.db.runReadOnlyTx( - ["refreshGroups", "refreshSessions", "coins", "denominations"], + { + storeNames: [ + "refreshGroups", + "refreshSessions", + "coins", + "denominations", + ], + }, async (tx) => { const refreshGroup = await tx.refreshGroups.get(refreshGroupId); if (!refreshGroup) { @@ -1187,13 +1207,15 @@ async function refreshReveal( } await wex.db.runReadWriteTx( - [ - "coins", - "denominations", - "coinAvailability", - "refreshGroups", - "refreshSessions", - ], + { + storeNames: [ + "coins", + "denominations", + "coinAvailability", + "refreshGroups", + "refreshSessions", + ], + }, async (tx) => { const rg = await tx.refreshGroups.get(refreshGroupId); if (!rg) { @@ -1247,13 +1269,15 @@ async function handleRefreshRevealError( errDetails: TalerErrorDetail, ): Promise { await ctx.wex.db.runReadWriteTx( - [ - "refreshGroups", - "refreshSessions", - "coins", - "denominations", - "coinAvailability", - ], + { + storeNames: [ + "refreshGroups", + "refreshSessions", + "coins", + "denominations", + "coinAvailability", + ], + }, async (tx) => { const rg = await tx.refreshGroups.get(ctx.refreshGroupId); if (!rg) { @@ -1288,7 +1312,7 @@ export async function processRefreshGroup( logger.trace(`processing refresh group ${refreshGroupId}`); const refreshGroup = await wex.db.runReadOnlyTx( - ["refreshGroups"], + { storeNames: ["refreshGroups"] }, async (tx) => tx.refreshGroups.get(refreshGroupId), ); if (!refreshGroup) { @@ -1344,7 +1368,7 @@ export async function processRefreshGroup( // status of the whole refresh group. const transitionInfo = await wex.db.runReadWriteTx( - ["coins", "coinAvailability", "refreshGroups"], + { storeNames: ["coins", "coinAvailability", "refreshGroups"] }, async (tx) => { const rg = await tx.refreshGroups.get(refreshGroupId); if (!rg) { @@ -1420,7 +1444,7 @@ async function processRefreshSession( `processing refresh session for coin ${coinIndex} of group ${refreshGroupId}`, ); let { refreshGroup, refreshSession } = await wex.db.runReadOnlyTx( - ["refreshGroups", "refreshSessions"], + { storeNames: ["refreshGroups", "refreshSessions"] }, async (tx) => { const rg = await tx.refreshGroups.get(refreshGroupId); const rs = await tx.refreshSessions.get([refreshGroupId, coinIndex]); @@ -1710,7 +1734,7 @@ export function getRefreshesForTransaction( wex: WalletExecutionContext, transactionId: string, ): Promise { - return wex.db.runReadOnlyTx(["refreshGroups"], async (tx) => { + return wex.db.runReadOnlyTx({ storeNames: ["refreshGroups"] }, async (tx) => { const groups = await tx.refreshGroups.indexes.byOriginatingTransactionId.getAll( transactionId, @@ -1736,13 +1760,15 @@ export async function forceRefresh( throw Error("refusing to create empty refresh group"); } const res = await wex.db.runReadWriteTx( - [ - "refreshGroups", - "coinAvailability", - "refreshSessions", - "denominations", - "coins", - ], + { + storeNames: [ + "refreshGroups", + "coinAvailability", + "refreshSessions", + "denominations", + "coins", + ], + }, async (tx) => { let coinPubs: CoinRefreshRequest[] = []; for (const c of req.refreshCoinSpecs) { @@ -1828,7 +1854,7 @@ async function internalWaitRefreshFinal( // Check if refresh is final const res = await ctx.wex.db.runReadOnlyTx( - ["refreshGroups", "operationRetries"], + { storeNames: ["refreshGroups", "operationRetries"] }, async (tx) => { return { rg: await tx.refreshGroups.get(ctx.refreshGroupId), diff --git a/packages/taler-wallet-core/src/shepherd.ts b/packages/taler-wallet-core/src/shepherd.ts index 62133d4b9..5e2d23fd9 100644 --- a/packages/taler-wallet-core/src/shepherd.ts +++ b/packages/taler-wallet-core/src/shepherd.ts @@ -474,9 +474,12 @@ async function storeTaskProgress( ws: InternalWalletState, pendingTaskId: string, ): Promise { - await ws.db.runReadWriteTx(["operationRetries"], async (tx) => { - await tx.operationRetries.delete(pendingTaskId); - }); + await ws.db.runReadWriteTx( + { storeNames: ["operationRetries"] }, + async (tx) => { + await tx.operationRetries.delete(pendingTaskId); + }, + ); } async function storePendingTaskPending( @@ -523,9 +526,12 @@ async function storePendingTaskFinished( ws: InternalWalletState, pendingTaskId: string, ): Promise { - await ws.db.runReadWriteTx(["operationRetries"], async (tx) => { - await tx.operationRetries.delete(pendingTaskId); - }); + await ws.db.runReadWriteTx( + { storeNames: ["operationRetries"] }, + async (tx) => { + await tx.operationRetries.delete(pendingTaskId); + }, + ); } function getWalletExecutionContextForTask( @@ -947,18 +953,20 @@ export async function getActiveTaskIds( taskIds: [], }; await ws.db.runReadWriteTx( - [ - "exchanges", - "refreshGroups", - "withdrawalGroups", - "purchases", - "depositGroups", - "recoupGroups", - "peerPullCredit", - "peerPushDebit", - "peerPullDebit", - "peerPushCredit", - ], + { + storeNames: [ + "exchanges", + "refreshGroups", + "withdrawalGroups", + "purchases", + "depositGroups", + "recoupGroups", + "peerPullCredit", + "peerPushDebit", + "peerPullDebit", + "peerPushCredit", + ], + }, async (tx) => { const active = GlobalIDB.KeyRange.bound( OPERATION_STATUS_ACTIVE_FIRST, diff --git a/packages/taler-wallet-core/src/testing.ts b/packages/taler-wallet-core/src/testing.ts index 2f149cfa8..899c4a8b2 100644 --- a/packages/taler-wallet-core/src/testing.ts +++ b/packages/taler-wallet-core/src/testing.ts @@ -858,9 +858,12 @@ export async function testPay( if (r.type != ConfirmPayResultType.Done) { throw Error("payment not done"); } - const purchase = await wex.db.runReadOnlyTx(["purchases"], async (tx) => { - return tx.purchases.get(result.proposalId); - }); + const purchase = await wex.db.runReadOnlyTx( + { storeNames: ["purchases"] }, + async (tx) => { + return tx.purchases.get(result.proposalId); + }, + ); checkLogicInvariant(!!purchase); return { numCoins: purchase.payInfo?.payCoinSelection?.coinContributions.length ?? 0, diff --git a/packages/taler-wallet-core/src/transactions.ts b/packages/taler-wallet-core/src/transactions.ts index 43ef09220..f6216d641 100644 --- a/packages/taler-wallet-core/src/transactions.ts +++ b/packages/taler-wallet-core/src/transactions.ts @@ -226,12 +226,14 @@ export async function getTransactionById( case TransactionType.Withdrawal: { const withdrawalGroupId = parsedTx.withdrawalGroupId; return await wex.db.runReadWriteTx( - [ - "withdrawalGroups", - "exchangeDetails", - "exchanges", - "operationRetries", - ], + { + storeNames: [ + "withdrawalGroups", + "exchangeDetails", + "exchanges", + "operationRetries", + ], + }, async (tx) => { const withdrawalGroupRecord = await tx.withdrawalGroups.get(withdrawalGroupId); @@ -267,7 +269,7 @@ export async function getTransactionById( case TransactionType.DenomLoss: { const rec = await wex.db.runReadOnlyTx( - ["denomLossEvents"], + { storeNames: ["denomLossEvents"] }, async (tx) => { return tx.denomLossEvents.get(parsedTx.denomLossEventId); }, @@ -284,13 +286,15 @@ export async function getTransactionById( case TransactionType.Payment: { const proposalId = parsedTx.proposalId; return await wex.db.runReadWriteTx( - [ - "purchases", - "tombstones", - "operationRetries", - "contractTerms", - "refundGroups", - ], + { + storeNames: [ + "purchases", + "tombstones", + "operationRetries", + "contractTerms", + "refundGroups", + ], + }, async (tx) => { const purchase = await tx.purchases.get(proposalId); if (!purchase) throw Error("not found"); @@ -317,7 +321,7 @@ export async function getTransactionById( // FIXME: We should return info about the refresh here!; const refreshGroupId = parsedTx.refreshGroupId; return await wex.db.runReadOnlyTx( - ["refreshGroups", "operationRetries"], + { storeNames: ["refreshGroups", "operationRetries"] }, async (tx) => { const refreshGroupRec = await tx.refreshGroups.get(refreshGroupId); if (!refreshGroupRec) { @@ -334,7 +338,7 @@ export async function getTransactionById( case TransactionType.Deposit: { const depositGroupId = parsedTx.depositGroupId; return await wex.db.runReadWriteTx( - ["depositGroups", "operationRetries"], + { storeNames: ["depositGroups", "operationRetries"] }, async (tx) => { const depositRecord = await tx.depositGroups.get(depositGroupId); if (!depositRecord) throw Error("not found"); @@ -349,7 +353,14 @@ export async function getTransactionById( case TransactionType.Refund: { return await wex.db.runReadOnlyTx( - ["refundGroups", "purchases", "operationRetries", "contractTerms"], + { + storeNames: [ + "refundGroups", + "purchases", + "operationRetries", + "contractTerms", + ], + }, async (tx) => { const refundRecord = await tx.refundGroups.get( parsedTx.refundGroupId, @@ -367,7 +378,7 @@ export async function getTransactionById( } case TransactionType.PeerPullDebit: { return await wex.db.runReadWriteTx( - ["peerPullDebit", "contractTerms"], + { storeNames: ["peerPullDebit", "contractTerms"] }, async (tx) => { const debit = await tx.peerPullDebit.get(parsedTx.peerPullDebitId); if (!debit) throw Error("not found"); @@ -386,7 +397,7 @@ export async function getTransactionById( case TransactionType.PeerPushDebit: { return await wex.db.runReadWriteTx( - ["peerPushDebit", "contractTerms"], + { storeNames: ["peerPushDebit", "contractTerms"] }, async (tx) => { const debit = await tx.peerPushDebit.get(parsedTx.pursePub); if (!debit) throw Error("not found"); @@ -403,12 +414,14 @@ export async function getTransactionById( case TransactionType.PeerPushCredit: { const peerPushCreditId = parsedTx.peerPushCreditId; return await wex.db.runReadWriteTx( - [ - "peerPushCredit", - "contractTerms", - "withdrawalGroups", - "operationRetries", - ], + { + storeNames: [ + "peerPushCredit", + "contractTerms", + "withdrawalGroups", + "operationRetries", + ], + }, async (tx) => { const pushInc = await tx.peerPushCredit.get(peerPushCreditId); if (!pushInc) throw Error("not found"); @@ -441,12 +454,14 @@ export async function getTransactionById( case TransactionType.PeerPullCredit: { const pursePub = parsedTx.pursePub; return await wex.db.runReadWriteTx( - [ - "peerPullCredit", - "contractTerms", - "withdrawalGroups", - "operationRetries", - ], + { + storeNames: [ + "peerPullCredit", + "contractTerms", + "withdrawalGroups", + "operationRetries", + ], + }, async (tx) => { const pushInc = await tx.peerPullCredit.get(pursePub); if (!pushInc) throw Error("not found"); @@ -1039,7 +1054,14 @@ export async function getWithdrawalTransactionByUri( request: WithdrawalTransactionByURIRequest, ): Promise { return await wex.db.runReadWriteTx( - ["withdrawalGroups", "exchangeDetails", "exchanges", "operationRetries"], + { + storeNames: [ + "withdrawalGroups", + "exchangeDetails", + "exchanges", + "operationRetries", + ], + }, async (tx) => { const withdrawalGroupRecord = await tx.withdrawalGroups.indexes.byTalerWithdrawUri.get( @@ -1092,28 +1114,30 @@ export async function getTransactions( } await wex.db.runReadOnlyTx( - [ - "coins", - "denominations", - "depositGroups", - "exchangeDetails", - "exchanges", - "operationRetries", - "peerPullDebit", - "peerPushDebit", - "peerPushCredit", - "peerPullCredit", - "planchets", - "purchases", - "contractTerms", - "recoupGroups", - "rewards", - "tombstones", - "withdrawalGroups", - "refreshGroups", - "refundGroups", - "denomLossEvents", - ], + { + storeNames: [ + "coins", + "denominations", + "depositGroups", + "exchangeDetails", + "exchanges", + "operationRetries", + "peerPullDebit", + "peerPushDebit", + "peerPushCredit", + "peerPullCredit", + "planchets", + "purchases", + "contractTerms", + "recoupGroups", + "rewards", + "tombstones", + "withdrawalGroups", + "refreshGroups", + "refundGroups", + "denomLossEvents", + ], + }, async (tx) => { await iterRecordsForPeerPushDebit(tx, filter, async (pi) => { const amount = Amounts.parseOrThrow(pi.amount); diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 810c78583..b59f52840 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -331,28 +331,31 @@ type CancelFn = () => void; */ async function fillDefaults(wex: WalletExecutionContext): Promise { const notifications: WalletNotification[] = []; - await wex.db.runReadWriteTx(["config", "exchanges"], async (tx) => { - const appliedRec = await tx.config.get("currencyDefaultsApplied"); - let alreadyApplied = appliedRec ? !!appliedRec.value : false; - if (alreadyApplied) { - logger.trace("defaults already applied"); - return; - } - for (const exch of wex.ws.config.builtin.exchanges) { - const resp = await addPresetExchangeEntry( - tx, - exch.exchangeBaseUrl, - exch.currencyHint, - ); - if (resp.notification) { - notifications.push(resp.notification); + await wex.db.runReadWriteTx( + { storeNames: ["config", "exchanges"] }, + async (tx) => { + const appliedRec = await tx.config.get("currencyDefaultsApplied"); + let alreadyApplied = appliedRec ? !!appliedRec.value : false; + if (alreadyApplied) { + logger.trace("defaults already applied"); + return; } - } - await tx.config.put({ - key: ConfigRecordKey.CurrencyDefaultsApplied, - value: true, - }); - }); + for (const exch of wex.ws.config.builtin.exchanges) { + const resp = await addPresetExchangeEntry( + tx, + exch.exchangeBaseUrl, + exch.currencyHint, + ); + if (resp.notification) { + notifications.push(resp.notification); + } + } + await tx.config.put({ + key: ConfigRecordKey.CurrencyDefaultsApplied, + value: true, + }); + }, + ); for (const notif of notifications) { wex.ws.notify(notif); } @@ -387,7 +390,7 @@ async function listKnownBankAccounts( currency?: string, ): Promise { const accounts: KnownBankAccountsInfo[] = []; - await wex.db.runReadOnlyTx(["bankAccounts"], async (tx) => { + await wex.db.runReadOnlyTx({ storeNames: ["bankAccounts"] }, async (tx) => { const knownAccounts = await tx.bankAccounts.iter().toArray(); for (const r of knownAccounts) { if (currency && currency !== r.currency) { @@ -415,7 +418,7 @@ async function addKnownBankAccounts( alias: string, currency: string, ): Promise { - await wex.db.runReadWriteTx(["bankAccounts"], async (tx) => { + await wex.db.runReadWriteTx({ storeNames: ["bankAccounts"] }, async (tx) => { tx.bankAccounts.put({ uri: payto, alias: alias, @@ -432,7 +435,7 @@ async function forgetKnownBankAccounts( wex: WalletExecutionContext, payto: string, ): Promise { - await wex.db.runReadWriteTx(["bankAccounts"], async (tx) => { + await wex.db.runReadWriteTx({ storeNames: ["bankAccounts"] }, async (tx) => { const account = await tx.bankAccounts.get(payto); if (!account) { throw Error(`account not found: ${payto}`); @@ -447,39 +450,42 @@ async function setCoinSuspended( coinPub: string, suspended: boolean, ): Promise { - await wex.db.runReadWriteTx(["coins", "coinAvailability"], async (tx) => { - const c = await tx.coins.get(coinPub); - if (!c) { - logger.warn(`coin ${coinPub} not found, won't suspend`); - return; - } - const coinAvailability = await tx.coinAvailability.get([ - c.exchangeBaseUrl, - c.denomPubHash, - c.maxAge, - ]); - checkDbInvariant(!!coinAvailability); - if (suspended) { - if (c.status !== CoinStatus.Fresh) { + await wex.db.runReadWriteTx( + { storeNames: ["coins", "coinAvailability"] }, + async (tx) => { + const c = await tx.coins.get(coinPub); + if (!c) { + logger.warn(`coin ${coinPub} not found, won't suspend`); return; } - if (coinAvailability.freshCoinCount === 0) { - throw Error( - `invalid coin count ${coinAvailability.freshCoinCount} in DB`, - ); - } - coinAvailability.freshCoinCount--; - c.status = CoinStatus.FreshSuspended; - } else { - if (c.status == CoinStatus.Dormant) { - return; + const coinAvailability = await tx.coinAvailability.get([ + c.exchangeBaseUrl, + c.denomPubHash, + c.maxAge, + ]); + checkDbInvariant(!!coinAvailability); + if (suspended) { + if (c.status !== CoinStatus.Fresh) { + return; + } + if (coinAvailability.freshCoinCount === 0) { + throw Error( + `invalid coin count ${coinAvailability.freshCoinCount} in DB`, + ); + } + coinAvailability.freshCoinCount--; + c.status = CoinStatus.FreshSuspended; + } else { + if (c.status == CoinStatus.Dormant) { + return; + } + coinAvailability.freshCoinCount++; + c.status = CoinStatus.Fresh; } - coinAvailability.freshCoinCount++; - c.status = CoinStatus.Fresh; - } - await tx.coins.put(c); - await tx.coinAvailability.put(coinAvailability); - }); + await tx.coins.put(c); + await tx.coinAvailability.put(coinAvailability); + }, + ); } /** @@ -488,55 +494,58 @@ async function setCoinSuspended( async function dumpCoins(wex: WalletExecutionContext): Promise { const coinsJson: CoinDumpJson = { coins: [] }; logger.info("dumping coins"); - await wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { - const coins = await tx.coins.iter().toArray(); - for (const c of coins) { - const denom = await tx.denominations.get([ - c.exchangeBaseUrl, - c.denomPubHash, - ]); - if (!denom) { - logger.warn("no denom found for coin"); - continue; - } - const cs = c.coinSource; - let refreshParentCoinPub: string | undefined; - if (cs.type == CoinSourceType.Refresh) { - refreshParentCoinPub = cs.oldCoinPub; - } - let withdrawalReservePub: string | undefined; - if (cs.type == CoinSourceType.Withdraw) { - withdrawalReservePub = cs.reservePub; - } - const denomInfo = await getDenomInfo( - wex, - tx, - c.exchangeBaseUrl, - c.denomPubHash, - ); - if (!denomInfo) { - logger.warn("no denomination found for coin"); - continue; + await wex.db.runReadOnlyTx( + { storeNames: ["coins", "denominations"] }, + async (tx) => { + const coins = await tx.coins.iter().toArray(); + for (const c of coins) { + const denom = await tx.denominations.get([ + c.exchangeBaseUrl, + c.denomPubHash, + ]); + if (!denom) { + logger.warn("no denom found for coin"); + continue; + } + const cs = c.coinSource; + let refreshParentCoinPub: string | undefined; + if (cs.type == CoinSourceType.Refresh) { + refreshParentCoinPub = cs.oldCoinPub; + } + let withdrawalReservePub: string | undefined; + if (cs.type == CoinSourceType.Withdraw) { + withdrawalReservePub = cs.reservePub; + } + const denomInfo = await getDenomInfo( + wex, + tx, + c.exchangeBaseUrl, + c.denomPubHash, + ); + if (!denomInfo) { + logger.warn("no denomination found for coin"); + continue; + } + coinsJson.coins.push({ + coin_pub: c.coinPub, + denom_pub: denomInfo.denomPub, + denom_pub_hash: c.denomPubHash, + denom_value: denom.value, + exchange_base_url: c.exchangeBaseUrl, + refresh_parent_coin_pub: refreshParentCoinPub, + withdrawal_reserve_pub: withdrawalReservePub, + coin_status: c.status, + ageCommitmentProof: c.ageCommitmentProof, + spend_allocation: c.spendAllocation + ? { + amount: c.spendAllocation.amount, + id: c.spendAllocation.id, + } + : undefined, + }); } - coinsJson.coins.push({ - coin_pub: c.coinPub, - denom_pub: denomInfo.denomPub, - denom_pub_hash: c.denomPubHash, - denom_value: denom.value, - exchange_base_url: c.exchangeBaseUrl, - refresh_parent_coin_pub: refreshParentCoinPub, - withdrawal_reserve_pub: withdrawalReservePub, - coin_status: c.status, - ageCommitmentProof: c.ageCommitmentProof, - spend_allocation: c.spendAllocation - ? { - amount: c.spendAllocation.amount, - id: c.spendAllocation.id, - } - : undefined, - }); - } - }); + }, + ); return coinsJson; } @@ -713,7 +722,7 @@ async function dispatchRequestInternal( // Write to the DB to make sure that we're failing early in // case the DB is not writeable. try { - await wex.db.runReadWriteTx(["config"], async (tx) => { + await wex.db.runReadWriteTx({ storeNames: ["config"] }, async (tx) => { tx.config.put({ key: ConfigRecordKey.LastInitInfo, value: timestampProtocolToDb(TalerProtocolTimestamp.now()), @@ -824,20 +833,24 @@ async function dispatchRequestInternal( numLost: 0, numOffered: 0, }; - await wex.db.runReadOnlyTx(["denominations"], async (tx) => { - const denoms = await tx.denominations.indexes.byExchangeBaseUrl.getAll( - req.exchangeBaseUrl, - ); - for (const d of denoms) { - denomStats.numKnown++; - if (d.isOffered) { - denomStats.numOffered++; - } - if (d.isLost) { - denomStats.numLost++; + await wex.db.runReadOnlyTx( + { storeNames: ["denominations"] }, + async (tx) => { + const denoms = + await tx.denominations.indexes.byExchangeBaseUrl.getAll( + req.exchangeBaseUrl, + ); + for (const d of denoms) { + denomStats.numKnown++; + if (d.isOffered) { + denomStats.numOffered++; + } + if (d.isLost) { + denomStats.numLost++; + } } - } - }); + }, + ); return denomStats; } case WalletApiOperation.ListExchanges: { @@ -1026,7 +1039,7 @@ async function dispatchRequestInternal( const tasksInfo = await Promise.all( allTasksId.map(async (id) => { return await wex.db.runReadOnlyTx( - ["operationRetries"], + { storeNames: ["operationRetries"] }, async (tx) => { return tx.operationRetries.get(id); }, @@ -1245,89 +1258,112 @@ async function dispatchRequestInternal( const resp: ListGlobalCurrencyExchangesResponse = { exchanges: [], }; - await wex.db.runReadOnlyTx(["globalCurrencyExchanges"], async (tx) => { - const gceList = await tx.globalCurrencyExchanges.iter().toArray(); - for (const gce of gceList) { - resp.exchanges.push({ - currency: gce.currency, - exchangeBaseUrl: gce.exchangeBaseUrl, - exchangeMasterPub: gce.exchangeMasterPub, - }); - } - }); + await wex.db.runReadOnlyTx( + { storeNames: ["globalCurrencyExchanges"] }, + async (tx) => { + const gceList = await tx.globalCurrencyExchanges.iter().toArray(); + for (const gce of gceList) { + resp.exchanges.push({ + currency: gce.currency, + exchangeBaseUrl: gce.exchangeBaseUrl, + exchangeMasterPub: gce.exchangeMasterPub, + }); + } + }, + ); return resp; } case WalletApiOperation.ListGlobalCurrencyAuditors: { const resp: ListGlobalCurrencyAuditorsResponse = { auditors: [], }; - await wex.db.runReadOnlyTx(["globalCurrencyAuditors"], async (tx) => { - const gcaList = await tx.globalCurrencyAuditors.iter().toArray(); - for (const gca of gcaList) { - resp.auditors.push({ - currency: gca.currency, - auditorBaseUrl: gca.auditorBaseUrl, - auditorPub: gca.auditorPub, - }); - } - }); + await wex.db.runReadOnlyTx( + { storeNames: ["globalCurrencyAuditors"] }, + async (tx) => { + const gcaList = await tx.globalCurrencyAuditors.iter().toArray(); + for (const gca of gcaList) { + resp.auditors.push({ + currency: gca.currency, + auditorBaseUrl: gca.auditorBaseUrl, + auditorPub: gca.auditorPub, + }); + } + }, + ); return resp; } case WalletApiOperation.AddGlobalCurrencyExchange: { const req = codecForAddGlobalCurrencyExchangeRequest().decode(payload); - await wex.db.runReadWriteTx(["globalCurrencyExchanges"], async (tx) => { - const key = [req.currency, req.exchangeBaseUrl, req.exchangeMasterPub]; - const existingRec = - await tx.globalCurrencyExchanges.indexes.byCurrencyAndUrlAndPub.get( - key, - ); - if (existingRec) { - return; - } - wex.ws.exchangeCache.clear(); - await tx.globalCurrencyExchanges.add({ - currency: req.currency, - exchangeBaseUrl: req.exchangeBaseUrl, - exchangeMasterPub: req.exchangeMasterPub, - }); - }); + await wex.db.runReadWriteTx( + { storeNames: ["globalCurrencyExchanges"] }, + async (tx) => { + const key = [ + req.currency, + req.exchangeBaseUrl, + req.exchangeMasterPub, + ]; + const existingRec = + await tx.globalCurrencyExchanges.indexes.byCurrencyAndUrlAndPub.get( + key, + ); + if (existingRec) { + return; + } + wex.ws.exchangeCache.clear(); + await tx.globalCurrencyExchanges.add({ + currency: req.currency, + exchangeBaseUrl: req.exchangeBaseUrl, + exchangeMasterPub: req.exchangeMasterPub, + }); + }, + ); return {}; } case WalletApiOperation.RemoveGlobalCurrencyExchange: { const req = codecForRemoveGlobalCurrencyExchangeRequest().decode(payload); - await wex.db.runReadWriteTx(["globalCurrencyExchanges"], async (tx) => { - const key = [req.currency, req.exchangeBaseUrl, req.exchangeMasterPub]; - const existingRec = - await tx.globalCurrencyExchanges.indexes.byCurrencyAndUrlAndPub.get( - key, - ); - if (!existingRec) { - return; - } - wex.ws.exchangeCache.clear(); - checkDbInvariant(!!existingRec.id); - await tx.globalCurrencyExchanges.delete(existingRec.id); - }); + await wex.db.runReadWriteTx( + { storeNames: ["globalCurrencyExchanges"] }, + async (tx) => { + const key = [ + req.currency, + req.exchangeBaseUrl, + req.exchangeMasterPub, + ]; + const existingRec = + await tx.globalCurrencyExchanges.indexes.byCurrencyAndUrlAndPub.get( + key, + ); + if (!existingRec) { + return; + } + wex.ws.exchangeCache.clear(); + checkDbInvariant(!!existingRec.id); + await tx.globalCurrencyExchanges.delete(existingRec.id); + }, + ); return {}; } case WalletApiOperation.AddGlobalCurrencyAuditor: { const req = codecForAddGlobalCurrencyAuditorRequest().decode(payload); - await wex.db.runReadWriteTx(["globalCurrencyAuditors"], async (tx) => { - const key = [req.currency, req.auditorBaseUrl, req.auditorPub]; - const existingRec = - await tx.globalCurrencyAuditors.indexes.byCurrencyAndUrlAndPub.get( - key, - ); - if (existingRec) { - return; - } - await tx.globalCurrencyAuditors.add({ - currency: req.currency, - auditorBaseUrl: req.auditorBaseUrl, - auditorPub: req.auditorPub, - }); - wex.ws.exchangeCache.clear(); - }); + await wex.db.runReadWriteTx( + { storeNames: ["globalCurrencyAuditors"] }, + async (tx) => { + const key = [req.currency, req.auditorBaseUrl, req.auditorPub]; + const existingRec = + await tx.globalCurrencyAuditors.indexes.byCurrencyAndUrlAndPub.get( + key, + ); + if (existingRec) { + return; + } + await tx.globalCurrencyAuditors.add({ + currency: req.currency, + auditorBaseUrl: req.auditorBaseUrl, + auditorPub: req.auditorPub, + }); + wex.ws.exchangeCache.clear(); + }, + ); return {}; } case WalletApiOperation.TestingWaitTasksDone: { @@ -1336,19 +1372,22 @@ async function dispatchRequestInternal( } case WalletApiOperation.RemoveGlobalCurrencyAuditor: { const req = codecForRemoveGlobalCurrencyAuditorRequest().decode(payload); - await wex.db.runReadWriteTx(["globalCurrencyAuditors"], async (tx) => { - const key = [req.currency, req.auditorBaseUrl, req.auditorPub]; - const existingRec = - await tx.globalCurrencyAuditors.indexes.byCurrencyAndUrlAndPub.get( - key, - ); - if (!existingRec) { - return; - } - checkDbInvariant(!!existingRec.id); - await tx.globalCurrencyAuditors.delete(existingRec.id); - wex.ws.exchangeCache.clear(); - }); + await wex.db.runReadWriteTx( + { storeNames: ["globalCurrencyAuditors"] }, + async (tx) => { + const key = [req.currency, req.auditorBaseUrl, req.auditorPub]; + const existingRec = + await tx.globalCurrencyAuditors.indexes.byCurrencyAndUrlAndPub.get( + key, + ); + if (!existingRec) { + return; + } + checkDbInvariant(!!existingRec.id); + await tx.globalCurrencyAuditors.delete(existingRec.id); + wex.ws.exchangeCache.clear(); + }, + ); return {}; } case WalletApiOperation.ImportDb: { @@ -1434,7 +1473,7 @@ async function dispatchRequestInternal( let loopCount = 0; while (true) { logger.info(`looping test write tx, iteration ${loopCount}`); - await wex.db.runReadWriteTx(["config"], async (tx) => { + await wex.db.runReadWriteTx({ storeNames: ["config"] }, async (tx) => { await tx.config.put({ key: ConfigRecordKey.TestLoopTx, value: loopCount, diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index ecd654edf..4936135bd 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -325,7 +325,7 @@ export class WithdrawTransactionContext implements TransactionContext { ? [...baseStores, ...opts.extraStores] : baseStores; const transitionInfo = await this.wex.db.runReadWriteTx( - stores, + { storeNames: stores }, async (tx) => { const wgRec = await tx.withdrawalGroups.get(this.withdrawalGroupId); let oldTxState: TransactionState; @@ -773,9 +773,12 @@ async function getCandidateWithdrawalDenoms( exchangeBaseUrl: string, currency: string, ): Promise { - return await wex.db.runReadOnlyTx(["denominations"], async (tx) => { - return getCandidateWithdrawalDenomsTx(wex, tx, exchangeBaseUrl, currency); - }); + return await wex.db.runReadOnlyTx( + { storeNames: ["denominations"] }, + async (tx) => { + return getCandidateWithdrawalDenomsTx(wex, tx, exchangeBaseUrl, currency); + }, + ); } export async function getCandidateWithdrawalDenomsTx( @@ -806,12 +809,15 @@ async function processPlanchetGenerate( withdrawalGroup: WithdrawalGroupRecord, coinIdx: number, ): Promise { - let planchet = await wex.db.runReadOnlyTx(["planchets"], async (tx) => { - return tx.planchets.indexes.byGroupAndIndex.get([ - withdrawalGroup.withdrawalGroupId, - coinIdx, - ]); - }); + let planchet = await wex.db.runReadOnlyTx( + { storeNames: ["planchets"] }, + async (tx) => { + return tx.planchets.indexes.byGroupAndIndex.get([ + withdrawalGroup.withdrawalGroupId, + coinIdx, + ]); + }, + ); if (planchet) { return; } @@ -837,9 +843,17 @@ async function processPlanchetGenerate( } const denomPubHash = maybeDenomPubHash; - const denom = await wex.db.runReadOnlyTx(["denominations"], async (tx) => { - return getDenomInfo(wex, tx, withdrawalGroup.exchangeBaseUrl, denomPubHash); - }); + const denom = await wex.db.runReadOnlyTx( + { storeNames: ["denominations"] }, + async (tx) => { + return getDenomInfo( + wex, + tx, + withdrawalGroup.exchangeBaseUrl, + denomPubHash, + ); + }, + ); checkDbInvariant(!!denom); const r = await wex.cryptoApi.createPlanchet({ denomPub: denom.denomPub, @@ -865,7 +879,7 @@ async function processPlanchetGenerate( ageCommitmentProof: r.ageCommitmentProof, lastError: undefined, }; - await wex.db.runReadWriteTx(["planchets"], async (tx) => { + await wex.db.runReadWriteTx({ storeNames: ["planchets"] }, async (tx) => { const p = await tx.planchets.indexes.byGroupAndIndex.get([ withdrawalGroup.withdrawalGroupId, coinIdx, @@ -1008,48 +1022,51 @@ async function processPlanchetExchangeBatchRequest( // Indices of coins that are included in the batch request const requestCoinIdxs: number[] = []; - await wex.db.runReadOnlyTx(["planchets", "denominations"], async (tx) => { - for ( - let coinIdx = args.coinStartIndex; - coinIdx < args.coinStartIndex + args.batchSize && - coinIdx < wgContext.numPlanchets; - coinIdx++ - ) { - let planchet = await tx.planchets.indexes.byGroupAndIndex.get([ - withdrawalGroup.withdrawalGroupId, - coinIdx, - ]); - if (!planchet) { - continue; - } - if (planchet.planchetStatus === PlanchetStatus.WithdrawalDone) { - logger.warn("processPlanchet: planchet already withdrawn"); - continue; - } - if (planchet.planchetStatus === PlanchetStatus.AbortedReplaced) { - continue; - } - const denom = await getDenomInfo( - wex, - tx, - withdrawalGroup.exchangeBaseUrl, - planchet.denomPubHash, - ); + await wex.db.runReadOnlyTx( + { storeNames: ["planchets", "denominations"] }, + async (tx) => { + for ( + let coinIdx = args.coinStartIndex; + coinIdx < args.coinStartIndex + args.batchSize && + coinIdx < wgContext.numPlanchets; + coinIdx++ + ) { + let planchet = await tx.planchets.indexes.byGroupAndIndex.get([ + withdrawalGroup.withdrawalGroupId, + coinIdx, + ]); + if (!planchet) { + continue; + } + if (planchet.planchetStatus === PlanchetStatus.WithdrawalDone) { + logger.warn("processPlanchet: planchet already withdrawn"); + continue; + } + if (planchet.planchetStatus === PlanchetStatus.AbortedReplaced) { + continue; + } + const denom = await getDenomInfo( + wex, + tx, + withdrawalGroup.exchangeBaseUrl, + planchet.denomPubHash, + ); - if (!denom) { - logger.error("db inconsistent: denom for planchet not found"); - continue; - } + if (!denom) { + logger.error("db inconsistent: denom for planchet not found"); + continue; + } - const planchetReq: ExchangeWithdrawRequest = { - denom_pub_hash: planchet.denomPubHash, - reserve_sig: planchet.withdrawSig, - coin_ev: planchet.coinEv, - }; - batchReq.planchets.push(planchetReq); - requestCoinIdxs.push(coinIdx); - } - }); + const planchetReq: ExchangeWithdrawRequest = { + denom_pub_hash: planchet.denomPubHash, + reserve_sig: planchet.withdrawSig, + coin_ev: planchet.coinEv, + }; + batchReq.planchets.push(planchetReq); + requestCoinIdxs.push(coinIdx); + } + }, + ); if (batchReq.planchets.length == 0) { logger.warn("empty withdrawal batch"); @@ -1064,7 +1081,7 @@ async function processPlanchetExchangeBatchRequest( coinIdx: number, ): Promise { logger.trace(`withdrawal request failed: ${j2s(errDetail)}`); - await wex.db.runReadWriteTx(["planchets"], async (tx) => { + await wex.db.runReadWriteTx({ storeNames: ["planchets"] }, async (tx) => { let planchet = await tx.planchets.indexes.byGroupAndIndex.get([ withdrawalGroup.withdrawalGroupId, coinIdx, @@ -1136,7 +1153,7 @@ async function processPlanchetVerifyAndStoreCoin( const withdrawalGroup = wgContext.wgRecord; logger.trace(`checking and storing planchet idx=${coinIdx}`); const d = await wex.db.runReadOnlyTx( - ["planchets", "denominations"], + { storeNames: ["planchets", "denominations"] }, async (tx) => { let planchet = await tx.planchets.indexes.byGroupAndIndex.get([ withdrawalGroup.withdrawalGroupId, @@ -1200,7 +1217,7 @@ async function processPlanchetVerifyAndStoreCoin( }); if (!isValid) { - await wex.db.runReadWriteTx(["planchets"], async (tx) => { + await wex.db.runReadWriteTx({ storeNames: ["planchets"] }, async (tx) => { let planchet = await tx.planchets.indexes.byGroupAndIndex.get([ withdrawalGroup.withdrawalGroupId, coinIdx, @@ -1254,7 +1271,7 @@ async function processPlanchetVerifyAndStoreCoin( wgContext.planchetsFinished.add(planchet.coinPub); await wex.db.runReadWriteTx( - ["planchets", "coins", "coinAvailability", "denominations"], + { storeNames: ["planchets", "coins", "coinAvailability", "denominations"] }, async (tx) => { const p = await tx.planchets.get(planchetCoinPub); if (!p || p.planchetStatus === PlanchetStatus.WithdrawalDone) { @@ -1280,7 +1297,7 @@ export async function updateWithdrawalDenoms( `updating denominations used for withdrawal for ${exchangeBaseUrl}`, ); const exchangeDetails = await wex.db.runReadOnlyTx( - ["exchanges", "exchangeDetails"], + { storeNames: ["exchanges", "exchangeDetails"] }, async (tx) => { return getExchangeWireDetailsInTx(tx, exchangeBaseUrl); }, @@ -1343,12 +1360,15 @@ export async function updateWithdrawalDenoms( } if (updatedDenominations.length > 0) { logger.trace("writing denomination batch to db"); - await wex.db.runReadWriteTx(["denominations"], async (tx) => { - for (let i = 0; i < updatedDenominations.length; i++) { - const denom = updatedDenominations[i]; - await tx.denominations.put(denom); - } - }); + await wex.db.runReadWriteTx( + { storeNames: ["denominations"] }, + async (tx) => { + for (let i = 0; i < updatedDenominations.length; i++) { + const denom = updatedDenominations[i]; + await tx.denominations.put(denom); + } + }, + ); wex.ws.denomInfoCache.clear(); logger.trace("done with DB write"); } @@ -1560,7 +1580,7 @@ async function redenominateWithdrawal( ): Promise { logger.trace(`redenominating withdrawal group ${withdrawalGroupId}`); await wex.db.runReadWriteTx( - ["withdrawalGroups", "planchets", "denominations"], + { storeNames: ["withdrawalGroups", "planchets", "denominations"] }, async (tx) => { const wg = await tx.withdrawalGroups.get(withdrawalGroupId); if (!wg) { @@ -1728,7 +1748,7 @@ async function processWithdrawalGroupPendingReady( wgRecord: withdrawalGroup, }; - await wex.db.runReadOnlyTx(["planchets"], async (tx) => { + await wex.db.runReadOnlyTx({ storeNames: ["planchets"] }, async (tx) => { const planchets = await tx.planchets.indexes.byGroup.getAll(withdrawalGroupId); for (const p of planchets) { @@ -1772,7 +1792,7 @@ async function processWithdrawalGroupPendingReady( let redenomRequired = false; - await wex.db.runReadOnlyTx(["planchets"], async (tx) => { + await wex.db.runReadOnlyTx({ storeNames: ["planchets"] }, async (tx) => { const planchets = await tx.planchets.indexes.byGroup.getAll(withdrawalGroupId); for (const p of planchets) { @@ -1876,7 +1896,7 @@ export async function processWithdrawalGroup( ): Promise { logger.trace("processing withdrawal group", withdrawalGroupId); const withdrawalGroup = await wex.db.runReadOnlyTx( - ["withdrawalGroups"], + { storeNames: ["withdrawalGroups"] }, async (tx) => { return tx.withdrawalGroups.get(withdrawalGroupId); }, @@ -2192,9 +2212,12 @@ async function getWithdrawalGroupRecordTx( withdrawalGroupId: string; }, ): Promise { - return await db.runReadOnlyTx(["withdrawalGroups"], async (tx) => { - return tx.withdrawalGroups.get(req.withdrawalGroupId); - }); + return await db.runReadOnlyTx( + { storeNames: ["withdrawalGroups"] }, + async (tx) => { + return tx.withdrawalGroups.get(req.withdrawalGroupId); + }, + ); } export function getReserveRequestTimeout(r: WithdrawalGroupRecord): Duration { @@ -2230,7 +2253,7 @@ async function registerReserveWithBank( withdrawalGroupId: string, ): Promise { const withdrawalGroup = await wex.db.runReadOnlyTx( - ["withdrawalGroups"], + { storeNames: ["withdrawalGroups"] }, async (tx) => { return await tx.withdrawalGroups.get(withdrawalGroupId); }, @@ -2497,7 +2520,7 @@ export async function internalPrepareCreateWithdrawalGroup( withdrawalGroupId = args.forcedWithdrawalGroupId; const wgId = withdrawalGroupId; const existingWg = await wex.db.runReadOnlyTx( - ["withdrawalGroups"], + { storeNames: ["withdrawalGroups"] }, async (tx) => { return tx.withdrawalGroups.get(wgId); }, @@ -2684,14 +2707,16 @@ export async function internalCreateWithdrawalGroup( prep.withdrawalGroup.withdrawalGroupId, ); const res = await wex.db.runReadWriteTx( - [ - "withdrawalGroups", - "reserves", - "exchanges", - "exchangeDetails", - "transactions", - "operationRetries", - ], + { + storeNames: [ + "withdrawalGroups", + "reserves", + "exchanges", + "exchangeDetails", + "transactions", + "operationRetries", + ], + }, async (tx) => { const res = await internalPerformCreateWithdrawalGroup(wex, tx, prep); await updateWithdrawalTransaction(ctx, tx); @@ -2727,7 +2752,7 @@ export async function acceptWithdrawalFromUri( `accepting withdrawal via ${req.talerWithdrawUri}, canonicalized selected exchange ${selectedExchange}`, ); const existingWithdrawalGroup = await wex.db.runReadOnlyTx( - ["withdrawalGroups"], + { storeNames: ["withdrawalGroups"] }, async (tx) => { return await tx.withdrawalGroups.indexes.byTalerWithdrawUri.get( req.talerWithdrawUri, @@ -2821,7 +2846,7 @@ async function internalWaitWithdrawalRegistered( ): Promise { while (true) { const { withdrawalRec, retryRec } = await wex.db.runReadOnlyTx( - ["withdrawalGroups", "operationRetries"], + { storeNames: ["withdrawalGroups", "operationRetries"] }, async (tx) => { return { withdrawalRec: await tx.withdrawalGroups.get(ctx.withdrawalGroupId), @@ -3069,7 +3094,7 @@ export async function createManualWithdrawal( ); const exchangePaytoUris = await wex.db.runReadOnlyTx( - ["withdrawalGroups", "exchanges", "exchangeDetails"], + { storeNames: ["withdrawalGroups", "exchanges", "exchangeDetails"] }, async (tx) => { return await getFundingPaytoUris(tx, withdrawalGroup.withdrawalGroupId); }, @@ -3136,7 +3161,7 @@ async function internalWaitWithdrawalFinal( // Check if refresh is final const res = await ctx.wex.db.runReadOnlyTx( - ["withdrawalGroups", "operationRetries"], + { storeNames: ["withdrawalGroups", "operationRetries"] }, async (tx) => { return { wg: await tx.withdrawalGroups.get(ctx.withdrawalGroupId), -- cgit v1.2.3