From 5c26461247040c07c86291babf0c87631df638b5 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 9 Jun 2021 15:14:17 +0200 Subject: database access refactor --- .../src/operations/backup/export.ts | 65 ++++---- .../src/operations/backup/import.ts | 134 ++++++++-------- .../src/operations/backup/index.ts | 169 ++++++++++++--------- .../src/operations/backup/state.ts | 63 ++++---- 4 files changed, 226 insertions(+), 205 deletions(-) (limited to 'packages/taler-wallet-core/src/operations/backup') diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts b/packages/taler-wallet-core/src/operations/backup/export.ts index fa0af1b07..a6b2ff2a7 100644 --- a/packages/taler-wallet-core/src/operations/backup/export.ts +++ b/packages/taler-wallet-core/src/operations/backup/export.ts @@ -57,7 +57,6 @@ import { } from "./state"; import { Amounts, getTimestampNow } from "@gnu-taler/taler-util"; import { - Stores, CoinSourceType, CoinStatus, RefundState, @@ -66,29 +65,28 @@ import { } from "../../db.js"; import { encodeCrock, stringToBytes, getRandomBytes } from "../../index.js"; import { canonicalizeBaseUrl, canonicalJson } from "@gnu-taler/taler-util"; -import { getExchangeDetails } from "../exchanges.js"; export async function exportBackup( ws: InternalWalletState, ): Promise { await provideBackupState(ws); - return ws.db.runWithWriteTransaction( - [ - Stores.config, - Stores.exchanges, - Stores.exchangeDetails, - Stores.coins, - Stores.denominations, - Stores.purchases, - Stores.proposals, - Stores.refreshGroups, - Stores.backupProviders, - Stores.tips, - Stores.recoupGroups, - Stores.reserves, - Stores.withdrawalGroups, - ], - async (tx) => { + return ws.db + .mktx((x) => ({ + config: x.config, + exchanges: x.exchanges, + exchangeDetails: x.exchangeDetails, + coins: x.coins, + denominations: x.denominations, + purchases: x.purchases, + proposals: x.proposals, + refreshGroups: x.refreshGroups, + backupProviders: x.backupProviders, + tips: x.tips, + recoupGroups: x.recoupGroups, + reserves: x.reserves, + withdrawalGroups: x.withdrawalGroups, + })) + .runReadWrite(async (tx) => { const bs = await getWalletBackupState(ws, tx); const backupExchangeDetails: BackupExchangeDetails[] = []; @@ -108,7 +106,7 @@ export async function exportBackup( [reservePub: string]: BackupWithdrawalGroup[]; } = {}; - await tx.iter(Stores.withdrawalGroups).forEachAsync(async (wg) => { + await tx.withdrawalGroups.iter().forEachAsync(async (wg) => { const withdrawalGroups = (withdrawalGroupsByReserve[ wg.reservePub ] ??= []); @@ -126,7 +124,7 @@ export async function exportBackup( }); }); - await tx.iter(Stores.reserves).forEach((reserve) => { + await tx.reserves.iter().forEach((reserve) => { const backupReserve: BackupReserve = { initial_selected_denoms: reserve.initialDenomSel.selectedDenoms.map( (x) => ({ @@ -149,7 +147,7 @@ export async function exportBackup( backupReserves.push(backupReserve); }); - await tx.iter(Stores.tips).forEach((tip) => { + await tx.tips.iter().forEach((tip) => { backupTips.push({ exchange_base_url: tip.exchangeBaseUrl, merchant_base_url: tip.merchantBaseUrl, @@ -169,7 +167,7 @@ export async function exportBackup( }); }); - await tx.iter(Stores.recoupGroups).forEach((recoupGroup) => { + await tx.recoupGroups.iter().forEach((recoupGroup) => { backupRecoupGroups.push({ recoup_group_id: recoupGroup.recoupGroupId, timestamp_created: recoupGroup.timestampStarted, @@ -182,7 +180,7 @@ export async function exportBackup( }); }); - await tx.iter(Stores.backupProviders).forEach((bp) => { + await tx.backupProviders.iter().forEach((bp) => { let terms: BackupBackupProviderTerms | undefined; if (bp.terms) { terms = { @@ -199,7 +197,7 @@ export async function exportBackup( }); }); - await tx.iter(Stores.coins).forEach((coin) => { + await tx.coins.iter().forEach((coin) => { let bcs: BackupCoinSource; switch (coin.coinSource.type) { case CoinSourceType.Refresh: @@ -236,7 +234,7 @@ export async function exportBackup( }); }); - await tx.iter(Stores.denominations).forEach((denom) => { + await tx.denominations.iter().forEach((denom) => { const backupDenoms = (backupDenominationsByExchange[ denom.exchangeBaseUrl ] ??= []); @@ -258,7 +256,7 @@ export async function exportBackup( }); }); - await tx.iter(Stores.exchanges).forEachAsync(async (ex) => { + await tx.exchanges.iter().forEachAsync(async (ex) => { const dp = ex.detailsPointer; if (!dp) { return; @@ -271,7 +269,7 @@ export async function exportBackup( }); }); - await tx.iter(Stores.exchangeDetails).forEachAsync(async (ex) => { + await tx.exchangeDetails.iter().forEachAsync(async (ex) => { // Only back up permanently added exchanges. const wi = ex.wireInfo; @@ -323,7 +321,7 @@ export async function exportBackup( const purchaseProposalIdSet = new Set(); - await tx.iter(Stores.purchases).forEach((purch) => { + await tx.purchases.iter().forEach((purch) => { const refunds: BackupRefundItem[] = []; purchaseProposalIdSet.add(purch.proposalId); for (const refundKey of Object.keys(purch.refunds)) { @@ -376,7 +374,7 @@ export async function exportBackup( }); }); - await tx.iter(Stores.proposals).forEach((prop) => { + await tx.proposals.iter().forEach((prop) => { if (purchaseProposalIdSet.has(prop.proposalId)) { return; } @@ -413,7 +411,7 @@ export async function exportBackup( }); }); - await tx.iter(Stores.refreshGroups).forEach((rg) => { + await tx.refreshGroups.iter().forEach((rg) => { const oldCoins: BackupRefreshOldCoin[] = []; for (let i = 0; i < rg.oldCoinPubs.length; i++) { @@ -482,13 +480,12 @@ export async function exportBackup( hash(stringToBytes(canonicalJson(backupBlob))), ); bs.lastBackupNonce = encodeCrock(getRandomBytes(32)); - await tx.put(Stores.config, { + await tx.config.put({ key: WALLET_BACKUP_STATE_KEY, value: bs, }); } return backupBlob; - }, - ); + }); } diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts index 74b7a3b59..e024b76ab 100644 --- a/packages/taler-wallet-core/src/operations/backup/import.ts +++ b/packages/taler-wallet-core/src/operations/backup/import.ts @@ -29,7 +29,6 @@ import { BackupRefreshReason, } from "@gnu-taler/taler-util"; import { - Stores, WalletContractData, DenomSelectionState, ExchangeUpdateStatus, @@ -46,8 +45,8 @@ import { AbortStatus, RefreshSessionRecord, WireInfo, + WalletStoresV1, } from "../../db.js"; -import { TransactionHandle } from "../../index.js"; import { PayCoinSelection } from "../../util/coinSelection"; import { j2s } from "@gnu-taler/taler-util"; import { checkDbInvariant, checkLogicInvariant } from "../../util/invariants"; @@ -57,6 +56,7 @@ import { InternalWalletState } from "../state"; import { provideBackupState } from "./state"; import { makeEventId, TombstoneTag } from "../transactions.js"; import { getExchangeDetails } from "../exchanges.js"; +import { GetReadOnlyAccess, GetReadWriteAccess } from "../../util/query.js"; const logger = new Logger("operations/backup/import.ts"); @@ -74,9 +74,12 @@ function checkBackupInvariant(b: boolean, m?: string): asserts b { * Re-compute information about the coin selection for a payment. */ async function recoverPayCoinSelection( - tx: TransactionHandle< - typeof Stores.exchanges | typeof Stores.coins | typeof Stores.denominations - >, + tx: GetReadWriteAccess<{ + exchanges: typeof WalletStoresV1.exchanges; + exchangeDetails: typeof WalletStoresV1.exchangeDetails; + coins: typeof WalletStoresV1.coins; + denominations: typeof WalletStoresV1.denominations; + }>, contractData: WalletContractData, backupPurchase: BackupPurchase, ): Promise { @@ -93,9 +96,9 @@ async function recoverPayCoinSelection( ); for (const coinPub of coinPubs) { - const coinRecord = await tx.get(Stores.coins, coinPub); + const coinRecord = await tx.coins.get(coinPub); checkBackupInvariant(!!coinRecord); - const denom = await tx.get(Stores.denominations, [ + const denom = await tx.denominations.get([ coinRecord.exchangeBaseUrl, coinRecord.denomPubHash, ]); @@ -154,11 +157,11 @@ async function recoverPayCoinSelection( } async function getDenomSelStateFromBackup( - tx: TransactionHandle, + tx: GetReadOnlyAccess<{ denominations: typeof WalletStoresV1.denominations }>, exchangeBaseUrl: string, sel: BackupDenomSel, ): Promise { - const d0 = await tx.get(Stores.denominations, [ + const d0 = await tx.denominations.get([ exchangeBaseUrl, sel[0].denom_pub_hash, ]); @@ -170,10 +173,7 @@ async function getDenomSelStateFromBackup( let totalCoinValue = Amounts.getZero(d0.value.currency); let totalWithdrawCost = Amounts.getZero(d0.value.currency); for (const s of sel) { - const d = await tx.get(Stores.denominations, [ - exchangeBaseUrl, - s.denom_pub_hash, - ]); + const d = await tx.denominations.get([exchangeBaseUrl, s.denom_pub_hash]); checkBackupInvariant(!!d); totalCoinValue = Amounts.add(totalCoinValue, d.value).amount; totalWithdrawCost = Amounts.add(totalWithdrawCost, d.value, d.feeWithdraw) @@ -215,32 +215,32 @@ export async function importBackup( logger.info(`importing backup ${j2s(backupBlobArg)}`); - return ws.db.runWithWriteTransaction( - [ - Stores.config, - Stores.exchanges, - Stores.exchangeDetails, - Stores.coins, - Stores.denominations, - Stores.purchases, - Stores.proposals, - Stores.refreshGroups, - Stores.backupProviders, - Stores.tips, - Stores.recoupGroups, - Stores.reserves, - Stores.withdrawalGroups, - Stores.tombstones, - Stores.depositGroups, - ], - async (tx) => { + return ws.db + .mktx((x) => ({ + config: x.config, + exchanges: x.exchanges, + exchangeDetails: x.exchangeDetails, + coins: x.coins, + denominations: x.denominations, + purchases: x.purchases, + proposals: x.proposals, + refreshGroups: x.refreshGroups, + backupProviders: x.backupProviders, + tips: x.tips, + recoupGroups: x.recoupGroups, + reserves: x.reserves, + withdrawalGroups: x.withdrawalGroups, + tombstones: x.tombstones, + depositGroups: x.depositGroups, + })) + .runReadWrite(async (tx) => { // FIXME: validate schema! const backupBlob = backupBlobArg as WalletBackupContentV1; // FIXME: validate version for (const tombstone of backupBlob.tombstones) { - await tx.put(Stores.tombstones, { + await tx.tombstones.put({ id: tombstone, }); } @@ -250,14 +250,13 @@ export async function importBackup( // FIXME: Validate that the "details pointer" is correct for (const backupExchange of backupBlob.exchanges) { - const existingExchange = await tx.get( - Stores.exchanges, + const existingExchange = await tx.exchanges.get( backupExchange.base_url, ); if (existingExchange) { continue; } - await tx.put(Stores.exchanges, { + await tx.exchanges.put({ baseUrl: backupExchange.base_url, detailsPointer: { currency: backupExchange.currency, @@ -272,7 +271,7 @@ export async function importBackup( } for (const backupExchangeDetails of backupBlob.exchange_details) { - const existingExchangeDetails = await tx.get(Stores.exchangeDetails, [ + const existingExchangeDetails = await tx.exchangeDetails.get([ backupExchangeDetails.base_url, backupExchangeDetails.currency, backupExchangeDetails.master_public_key, @@ -296,7 +295,7 @@ export async function importBackup( wireFee: Amounts.parseOrThrow(fee.wire_fee), }); } - await tx.put(Stores.exchangeDetails, { + await tx.exchangeDetails.put({ exchangeBaseUrl: backupExchangeDetails.base_url, termsOfServiceAcceptedEtag: backupExchangeDetails.tos_etag_accepted, termsOfServiceText: undefined, @@ -327,7 +326,7 @@ export async function importBackup( const denomPubHash = cryptoComp.denomPubToHash[backupDenomination.denom_pub]; checkLogicInvariant(!!denomPubHash); - const existingDenom = await tx.get(Stores.denominations, [ + const existingDenom = await tx.denominations.get([ backupExchangeDetails.base_url, denomPubHash, ]); @@ -336,7 +335,7 @@ export async function importBackup( `importing backup denomination: ${j2s(backupDenomination)}`, ); - await tx.put(Stores.denominations, { + await tx.denominations.put({ denomPub: backupDenomination.denom_pub, denomPubHash: denomPubHash, exchangeBaseUrl: backupExchangeDetails.base_url, @@ -361,7 +360,7 @@ export async function importBackup( const compCoin = cryptoComp.coinPrivToCompletedCoin[backupCoin.coin_priv]; checkLogicInvariant(!!compCoin); - const existingCoin = await tx.get(Stores.coins, compCoin.coinPub); + const existingCoin = await tx.coins.get(compCoin.coinPub); if (!existingCoin) { let coinSource: CoinSource; switch (backupCoin.coin_source.type) { @@ -388,7 +387,7 @@ export async function importBackup( }; break; } - await tx.put(Stores.coins, { + await tx.coins.put({ blindingKey: backupCoin.blinding_key, coinEvHash: compCoin.coinEvHash, coinPriv: backupCoin.coin_priv, @@ -416,7 +415,7 @@ export async function importBackup( continue; } checkLogicInvariant(!!reservePub); - const existingReserve = await tx.get(Stores.reserves, reservePub); + const existingReserve = await tx.reserves.get(reservePub); const instructedAmount = Amounts.parseOrThrow( backupReserve.instructed_amount, ); @@ -429,7 +428,7 @@ export async function importBackup( confirmUrl: backupReserve.bank_info.confirm_url, }; } - await tx.put(Stores.reserves, { + await tx.reserves.put({ currency: instructedAmount.currency, instructedAmount, exchangeBaseUrl: backupExchangeDetails.base_url, @@ -467,12 +466,11 @@ export async function importBackup( if (tombstoneSet.has(ts)) { continue; } - const existingWg = await tx.get( - Stores.withdrawalGroups, + const existingWg = await tx.withdrawalGroups.get( backupWg.withdrawal_group_id, ); if (!existingWg) { - await tx.put(Stores.withdrawalGroups, { + await tx.withdrawalGroups.put({ denomsSel: await getDenomSelStateFromBackup( tx, backupExchangeDetails.base_url, @@ -504,8 +502,7 @@ export async function importBackup( if (tombstoneSet.has(ts)) { continue; } - const existingProposal = await tx.get( - Stores.proposals, + const existingProposal = await tx.proposals.get( backupProposal.proposal_id, ); if (!existingProposal) { @@ -584,7 +581,7 @@ export async function importBackup( contractTermsRaw: backupProposal.contract_terms_raw, }; } - await tx.put(Stores.proposals, { + await tx.proposals.put({ claimToken: backupProposal.claim_token, lastError: undefined, merchantBaseUrl: backupProposal.merchant_base_url, @@ -610,17 +607,16 @@ export async function importBackup( if (tombstoneSet.has(ts)) { continue; } - const existingPurchase = await tx.get( - Stores.purchases, + const existingPurchase = await tx.purchases.get( backupPurchase.proposal_id, ); if (!existingPurchase) { const refunds: { [refundKey: string]: WalletRefundItem } = {}; for (const backupRefund of backupPurchase.refunds) { const key = `${backupRefund.coin_pub}-${backupRefund.rtransaction_id}`; - const coin = await tx.get(Stores.coins, backupRefund.coin_pub); + const coin = await tx.coins.get(backupRefund.coin_pub); checkBackupInvariant(!!coin); - const denom = await tx.get(Stores.denominations, [ + const denom = await tx.denominations.get([ coin.exchangeBaseUrl, coin.denomPubHash, ]); @@ -724,7 +720,7 @@ export async function importBackup( }, contractTermsRaw: backupPurchase.contract_terms_raw, }; - await tx.put(Stores.purchases, { + await tx.purchases.put({ proposalId: backupPurchase.proposal_id, noncePriv: backupPurchase.nonce_priv, noncePub: @@ -766,8 +762,7 @@ export async function importBackup( if (tombstoneSet.has(ts)) { continue; } - const existingRg = await tx.get( - Stores.refreshGroups, + const existingRg = await tx.refreshGroups.get( backupRefreshGroup.refresh_group_id, ); if (!existingRg) { @@ -800,7 +795,7 @@ export async function importBackup( | undefined )[] = []; for (const oldCoin of backupRefreshGroup.old_coins) { - const c = await tx.get(Stores.coins, oldCoin.coin_pub); + const c = await tx.coins.get(oldCoin.coin_pub); checkBackupInvariant(!!c); if (oldCoin.refresh_session) { const denomSel = await getDenomSelStateFromBackup( @@ -821,7 +816,7 @@ export async function importBackup( refreshSessionPerCoin.push(undefined); } } - await tx.put(Stores.refreshGroups, { + await tx.refreshGroups.put({ timestampFinished: backupRefreshGroup.timestamp_finish, timestampCreated: backupRefreshGroup.timestamp_created, refreshGroupId: backupRefreshGroup.refresh_group_id, @@ -849,14 +844,14 @@ export async function importBackup( if (tombstoneSet.has(ts)) { continue; } - const existingTip = await tx.get(Stores.tips, backupTip.wallet_tip_id); + const existingTip = await tx.tips.get(backupTip.wallet_tip_id); if (!existingTip) { const denomsSel = await getDenomSelStateFromBackup( tx, backupTip.exchange_base_url, backupTip.selected_denoms, ); - await tx.put(Stores.tips, { + await tx.tips.put({ acceptedTimestamp: backupTip.timestamp_accepted, createdTimestamp: backupTip.timestamp_created, denomsSel, @@ -884,27 +879,26 @@ export async function importBackup( for (const tombstone of backupBlob.tombstones) { const [type, ...rest] = tombstone.split(":"); if (type === TombstoneTag.DeleteDepositGroup) { - await tx.delete(Stores.depositGroups, rest[0]); + await tx.depositGroups.delete(rest[0]); } else if (type === TombstoneTag.DeletePayment) { - await tx.delete(Stores.purchases, rest[0]); - await tx.delete(Stores.proposals, rest[0]); + await tx.purchases.delete(rest[0]); + await tx.proposals.delete(rest[0]); } else if (type === TombstoneTag.DeleteRefreshGroup) { - await tx.delete(Stores.refreshGroups, rest[0]); + await tx.refreshGroups.delete(rest[0]); } else if (type === TombstoneTag.DeleteRefund) { // Nothing required, will just prevent display // in the transactions list } else if (type === TombstoneTag.DeleteReserve) { // FIXME: Once we also have account (=kyc) reserves, // we need to check if the reserve is an account before deleting here - await tx.delete(Stores.reserves, rest[0]); + await tx.reserves.delete(rest[0]); } else if (type === TombstoneTag.DeleteTip) { - await tx.delete(Stores.tips, rest[0]); + await tx.tips.delete(rest[0]); } else if (type === TombstoneTag.DeleteWithdrawalGroup) { - await tx.delete(Stores.withdrawalGroups, rest[0]); + await tx.withdrawalGroups.delete(rest[0]); } else { logger.warn(`unable to process tombstone of type '${type}'`); } } - }, - ); + }); } diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts index 743314791..bb067dfb5 100644 --- a/packages/taler-wallet-core/src/operations/backup/index.ts +++ b/packages/taler-wallet-core/src/operations/backup/index.ts @@ -35,7 +35,6 @@ import { BackupProviderRecord, BackupProviderTerms, ConfigRecord, - Stores, } from "../../db.js"; import { checkDbInvariant, checkLogicInvariant } from "../../util/invariants"; import { @@ -312,18 +311,17 @@ async function runBackupCycleForProvider( // FIXME: check if the provider is overcharging us! - await ws.db.runWithWriteTransaction( - [Stores.backupProviders], - async (tx) => { - const provRec = await tx.get(Stores.backupProviders, provider.baseUrl); + await ws.db + .mktx((x) => ({ backupProviders: x.backupProviders })) + .runReadWrite(async (tx) => { + const provRec = await tx.backupProviders.get(provider.baseUrl); checkDbInvariant(!!provRec); const ids = new Set(provRec.paymentProposalIds); ids.add(proposalId); provRec.paymentProposalIds = Array.from(ids).sort(); provRec.currentPaymentProposalId = proposalId; - await tx.put(Stores.backupProviders, provRec); - }, - ); + await tx.backupProviders.put(provRec); + }); if (doPay) { const confirmRes = await confirmPay(ws, proposalId); @@ -344,19 +342,18 @@ async function runBackupCycleForProvider( } if (resp.status === HttpResponseStatus.NoContent) { - await ws.db.runWithWriteTransaction( - [Stores.backupProviders], - async (tx) => { - const prov = await tx.get(Stores.backupProviders, provider.baseUrl); + await ws.db + .mktx((x) => ({ backupProviders: x.backupProviders })) + .runReadWrite(async (tx) => { + const prov = await tx.backupProviders.get(provider.baseUrl); if (!prov) { return; } prov.lastBackupHash = encodeCrock(currentBackupHash); prov.lastBackupTimestamp = getTimestampNow(); prov.lastError = undefined; - await tx.put(Stores.backupProviders, prov); - }, - ); + await tx.backupProviders.put(prov); + }); return; } @@ -367,19 +364,18 @@ async function runBackupCycleForProvider( const blob = await decryptBackup(backupConfig, backupEnc); const cryptoData = await computeBackupCryptoData(ws.cryptoApi, blob); await importBackup(ws, blob, cryptoData); - await ws.db.runWithWriteTransaction( - [Stores.backupProviders], - async (tx) => { - const prov = await tx.get(Stores.backupProviders, provider.baseUrl); + await ws.db + .mktx((x) => ({ backupProvider: x.backupProviders })) + .runReadWrite(async (tx) => { + const prov = await tx.backupProvider.get(provider.baseUrl); if (!prov) { return; } prov.lastBackupHash = encodeCrock(hash(backupEnc)); prov.lastBackupTimestamp = getTimestampNow(); prov.lastError = undefined; - await tx.put(Stores.backupProviders, prov); - }, - ); + await tx.backupProvider.put(prov); + }); logger.info("processed existing backup"); return; } @@ -390,14 +386,16 @@ async function runBackupCycleForProvider( const err = await readTalerErrorResponse(resp); logger.error(`got error response from backup provider: ${j2s(err)}`); - await ws.db.runWithWriteTransaction([Stores.backupProviders], async (tx) => { - const prov = await tx.get(Stores.backupProviders, provider.baseUrl); - if (!prov) { - return; - } - prov.lastError = err; - await tx.put(Stores.backupProviders, prov); - }); + await ws.db + .mktx((x) => ({ backupProvider: x.backupProviders })) + .runReadWrite(async (tx) => { + const prov = await tx.backupProvider.get(provider.baseUrl); + if (!prov) { + return; + } + prov.lastError = err; + await tx.backupProvider.put(prov); + }); } /** @@ -408,7 +406,11 @@ async function runBackupCycleForProvider( * 3. Upload the updated backup blob. */ export async function runBackupCycle(ws: InternalWalletState): Promise { - const providers = await ws.db.iter(Stores.backupProviders).toArray(); + const providers = await ws.db + .mktx((x) => ({ backupProviders: x.backupProviders })) + .runReadOnly(async (tx) => { + return await tx.backupProviders.iter().toArray(); + }); logger.trace("got backup providers", providers); const backupJson = await exportBackup(ws); @@ -472,35 +474,43 @@ export async function addBackupProvider( logger.info(`adding backup provider ${j2s(req)}`); await provideBackupState(ws); const canonUrl = canonicalizeBaseUrl(req.backupProviderBaseUrl); - const oldProv = await ws.db.get(Stores.backupProviders, canonUrl); - if (oldProv) { - logger.info("old backup provider found"); - if (req.activate) { - oldProv.active = true; - logger.info("setting existing backup provider to active"); - await ws.db.put(Stores.backupProviders, oldProv); - } - return; - } + await ws.db + .mktx((x) => ({ backupProviders: x.backupProviders })) + .runReadWrite(async (tx) => { + const oldProv = await tx.backupProviders.get(canonUrl); + if (oldProv) { + logger.info("old backup provider found"); + if (req.activate) { + oldProv.active = true; + logger.info("setting existing backup provider to active"); + await tx.backupProviders.put(oldProv); + } + return; + } + }); const termsUrl = new URL("terms", canonUrl); const resp = await ws.http.get(termsUrl.href); const terms = await readSuccessResponseJsonOrThrow( resp, codecForSyncTermsOfServiceResponse(), ); - await ws.db.put(Stores.backupProviders, { - active: !!req.activate, - terms: { - annualFee: terms.annual_fee, - storageLimitInMegabytes: terms.storage_limit_in_megabytes, - supportedProtocolVersion: terms.version, - }, - paymentProposalIds: [], - baseUrl: canonUrl, - lastError: undefined, - retryInfo: initRetryInfo(false), - uids: [encodeCrock(getRandomBytes(32))], - }); + await ws.db + .mktx((x) => ({ backupProviders: x.backupProviders })) + .runReadWrite(async (tx) => { + await tx.backupProviders.put({ + active: !!req.activate, + terms: { + annualFee: terms.annual_fee, + storageLimitInMegabytes: terms.storage_limit_in_megabytes, + supportedProtocolVersion: terms.version, + }, + paymentProposalIds: [], + baseUrl: canonUrl, + lastError: undefined, + retryInfo: initRetryInfo(false), + uids: [encodeCrock(getRandomBytes(32))], + }); + }); } export async function removeBackupProvider( @@ -654,7 +664,11 @@ export async function getBackupInfo( ws: InternalWalletState, ): Promise { const backupConfig = await provideBackupState(ws); - const providerRecords = await ws.db.iter(Stores.backupProviders).toArray(); + const providerRecords = await ws.db + .mktx((x) => ({ backupProviders: x.backupProviders })) + .runReadOnly(async (tx) => { + return await tx.backupProviders.iter().toArray(); + }); const providers: ProviderInfo[] = []; for (const x of providerRecords) { providers.push({ @@ -675,13 +689,18 @@ export async function getBackupInfo( } /** - * Get information about the current state of wallet backups. + * Get backup recovery information, including the wallet's + * private key. */ export async function getBackupRecovery( ws: InternalWalletState, ): Promise { const bs = await provideBackupState(ws); - const providers = await ws.db.iter(Stores.backupProviders).toArray(); + const providers = await ws.db + .mktx((x) => ({ backupProviders: x.backupProviders })) + .runReadOnly(async (tx) => { + return await tx.backupProviders.iter().toArray(); + }); return { providers: providers .filter((x) => x.active) @@ -698,12 +717,12 @@ async function backupRecoveryTheirs( ws: InternalWalletState, br: BackupRecovery, ) { - await ws.db.runWithWriteTransaction( - [Stores.config, Stores.backupProviders], - async (tx) => { + await ws.db + .mktx((x) => ({ config: x.config, backupProviders: x.backupProviders })) + .runReadWrite(async (tx) => { let backupStateEntry: | ConfigRecord - | undefined = await tx.get(Stores.config, WALLET_BACKUP_STATE_KEY); + | undefined = await tx.config.get(WALLET_BACKUP_STATE_KEY); checkDbInvariant(!!backupStateEntry); backupStateEntry.value.lastBackupNonce = undefined; backupStateEntry.value.lastBackupTimestamp = undefined; @@ -713,11 +732,11 @@ async function backupRecoveryTheirs( backupStateEntry.value.walletRootPub = encodeCrock( eddsaGetPublic(decodeCrock(br.walletRootPriv)), ); - await tx.put(Stores.config, backupStateEntry); + await tx.config.put(backupStateEntry); for (const prov of br.providers) { - const existingProv = await tx.get(Stores.backupProviders, prov.url); + const existingProv = await tx.backupProviders.get(prov.url); if (!existingProv) { - await tx.put(Stores.backupProviders, { + await tx.backupProviders.put({ active: true, baseUrl: prov.url, paymentProposalIds: [], @@ -727,14 +746,13 @@ async function backupRecoveryTheirs( }); } } - const providers = await tx.iter(Stores.backupProviders).toArray(); + const providers = await tx.backupProviders.iter().toArray(); for (const prov of providers) { prov.lastBackupTimestamp = undefined; prov.lastBackupHash = undefined; - await tx.put(Stores.backupProviders, prov); + await tx.backupProviders.put(prov); } - }, - ); + }); } async function backupRecoveryOurs(ws: InternalWalletState, br: BackupRecovery) { @@ -746,7 +764,11 @@ export async function loadBackupRecovery( br: RecoveryLoadRequest, ): Promise { const bs = await provideBackupState(ws); - const providers = await ws.db.iter(Stores.backupProviders).toArray(); + const providers = await ws.db + .mktx((x) => ({ backupProviders: x.backupProviders })) + .runReadOnly(async (tx) => { + return await tx.backupProviders.iter().toArray(); + }); let strategy = br.strategy; if ( br.recovery.walletRootPriv != bs.walletRootPriv && @@ -772,12 +794,11 @@ export async function exportBackupEncrypted( ): Promise { await provideBackupState(ws); const blob = await exportBackup(ws); - const bs = await ws.db.runWithWriteTransaction( - [Stores.config], - async (tx) => { + const bs = await ws.db + .mktx((x) => ({ config: x.config })) + .runReadOnly(async (tx) => { return await getWalletBackupState(ws, tx); - }, - ); + }); return encryptBackup(bs, blob); } diff --git a/packages/taler-wallet-core/src/operations/backup/state.ts b/packages/taler-wallet-core/src/operations/backup/state.ts index e2a0f4cf3..226880439 100644 --- a/packages/taler-wallet-core/src/operations/backup/state.ts +++ b/packages/taler-wallet-core/src/operations/backup/state.ts @@ -15,9 +15,11 @@ */ import { Timestamp } from "@gnu-taler/taler-util"; -import { ConfigRecord, Stores } from "../../db.js"; -import { getRandomBytes, encodeCrock, TransactionHandle } from "../../index.js"; +import { ConfigRecord, WalletStoresV1 } from "../../db.js"; +import { getRandomBytes, encodeCrock } from "../../index.js"; import { checkDbInvariant } from "../../util/invariants"; +import { GetReadOnlyAccess } from "../../util/query.js"; +import { Wallet } from "../../wallet.js"; import { InternalWalletState } from "../state"; export interface WalletBackupConfState { @@ -48,10 +50,13 @@ export const WALLET_BACKUP_STATE_KEY = "walletBackupState"; export async function provideBackupState( ws: InternalWalletState, ): Promise { - const bs: ConfigRecord | undefined = await ws.db.get( - Stores.config, - WALLET_BACKUP_STATE_KEY, - ); + const bs: ConfigRecord | undefined = await ws.db + .mktx((x) => ({ + config: x.config, + })) + .runReadOnly(async (tx) => { + return tx.config.get(WALLET_BACKUP_STATE_KEY); + }); if (bs) { return bs.value; } @@ -62,32 +67,36 @@ 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 ws.db.runWithWriteTransaction([Stores.config], async (tx) => { - let backupStateEntry: - | ConfigRecord - | undefined = await tx.get(Stores.config, WALLET_BACKUP_STATE_KEY); - if (!backupStateEntry) { - backupStateEntry = { - key: WALLET_BACKUP_STATE_KEY, - value: { - deviceId, - clocks: { [deviceId]: 1 }, - walletRootPub: k.pub, - walletRootPriv: k.priv, - lastBackupPlainHash: undefined, - }, - }; - await tx.put(Stores.config, backupStateEntry); - } - return backupStateEntry.value; - }); + return await ws.db + .mktx((x) => ({ + config: x.config, + })) + .runReadWrite(async (tx) => { + let backupStateEntry: + | ConfigRecord + | undefined = await tx.config.get(WALLET_BACKUP_STATE_KEY); + if (!backupStateEntry) { + backupStateEntry = { + key: WALLET_BACKUP_STATE_KEY, + value: { + deviceId, + clocks: { [deviceId]: 1 }, + walletRootPub: k.pub, + walletRootPriv: k.priv, + lastBackupPlainHash: undefined, + }, + }; + await tx.config.put(backupStateEntry); + } + return backupStateEntry.value; + }); } export async function getWalletBackupState( ws: InternalWalletState, - tx: TransactionHandle, + tx: GetReadOnlyAccess<{ config: typeof WalletStoresV1.config }>, ): Promise { - let bs = await tx.get(Stores.config, WALLET_BACKUP_STATE_KEY); + const bs = await tx.config.get(WALLET_BACKUP_STATE_KEY); checkDbInvariant(!!bs, "wallet backup state should be in DB"); return bs.value; } -- cgit v1.2.3