summaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations/backup
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2021-06-09 15:14:17 +0200
committerFlorian Dold <florian@dold.me>2021-06-09 15:24:19 +0200
commit5c26461247040c07c86291babf0c87631df638b5 (patch)
tree8ff93454d0c21d2675d6734f210d4e8ff91d2bfb /packages/taler-wallet-core/src/operations/backup
parent68dddc848f2f650d74697bb3a5c05d649e5db3c7 (diff)
downloadwallet-core-5c26461247040c07c86291babf0c87631df638b5.tar.gz
wallet-core-5c26461247040c07c86291babf0c87631df638b5.tar.bz2
wallet-core-5c26461247040c07c86291babf0c87631df638b5.zip
database access refactor
Diffstat (limited to 'packages/taler-wallet-core/src/operations/backup')
-rw-r--r--packages/taler-wallet-core/src/operations/backup/export.ts65
-rw-r--r--packages/taler-wallet-core/src/operations/backup/import.ts134
-rw-r--r--packages/taler-wallet-core/src/operations/backup/index.ts169
-rw-r--r--packages/taler-wallet-core/src/operations/backup/state.ts63
4 files changed, 226 insertions, 205 deletions
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<WalletBackupContentV1> {
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<string>();
- 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<PayCoinSelection> {
@@ -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<typeof Stores.denominations>,
+ tx: GetReadOnlyAccess<{ denominations: typeof WalletStoresV1.denominations }>,
exchangeBaseUrl: string,
sel: BackupDenomSel,
): Promise<DenomSelectionState> {
- 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<void> {
- 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<BackupInfo> {
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<BackupRecovery> {
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<WalletBackupConfState>
- | 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<void> {
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<Uint8Array> {
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<WalletBackupConfState> {
- const bs: ConfigRecord<WalletBackupConfState> | undefined = await ws.db.get(
- Stores.config,
- WALLET_BACKUP_STATE_KEY,
- );
+ const bs: ConfigRecord<WalletBackupConfState> | 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<WalletBackupConfState>
- | 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<WalletBackupConfState>
+ | 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<typeof Stores.config>,
+ tx: GetReadOnlyAccess<{ config: typeof WalletStoresV1.config }>,
): Promise<WalletBackupConfState> {
- 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;
}