aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src/operations/backup
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2024-02-19 12:49:17 +0100
committerFlorian Dold <florian@dold.me>2024-02-19 17:47:35 +0100
commit1ec521b9d214b286e747b3ccb3113730ac3a2509 (patch)
tree2f3d2b2906810dca45859b8cbfb8d18d53b27e80 /packages/taler-wallet-core/src/operations/backup
parent1034ecb5f20bd8c75e37e0b4b454ea6c1f4c1da6 (diff)
downloadwallet-core-1ec521b9d214b286e747b3ccb3113730ac3a2509.tar.xz
wallet-core: simplify/unify DB access
Diffstat (limited to 'packages/taler-wallet-core/src/operations/backup')
-rw-r--r--packages/taler-wallet-core/src/operations/backup/index.ts472
1 files changed, 227 insertions, 245 deletions
diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts
index e4e4e43f6..948b8eb85 100644
--- a/packages/taler-wallet-core/src/operations/backup/index.ts
+++ b/packages/taler-wallet-core/src/operations/backup/index.ts
@@ -79,7 +79,7 @@ import {
ConfigRecord,
ConfigRecordKey,
WalletBackupConfState,
- WalletStoresV1,
+ WalletDbReadOnlyTransaction,
timestampOptionalPreciseFromDb,
timestampPreciseToDb,
} from "../../db.js";
@@ -88,7 +88,6 @@ import {
checkDbInvariant,
checkLogicInvariant,
} from "../../util/invariants.js";
-import { GetReadOnlyAccess } from "../../util/query.js";
import { addAttentionRequest, removeAttentionRequest } from "../attention.js";
import {
TaskIdentifiers,
@@ -187,11 +186,12 @@ async function runBackupCycleForProvider(
ws: InternalWalletState,
args: BackupForProviderArgs,
): Promise<TaskRunResult> {
- const provider = await ws.db
- .mktx((x) => [x.backupProviders])
- .runReadOnly(async (tx) => {
+ const provider = await ws.db.runReadOnlyTx(
+ ["backupProviders"],
+ async (tx) => {
return tx.backupProviders.get(args.backupProviderBaseUrl);
- });
+ },
+ );
if (!provider) {
logger.warn("provider disappeared");
@@ -248,22 +248,20 @@ async function runBackupCycleForProvider(
logger.trace(`sync response status: ${resp.status}`);
if (resp.status === HttpStatusCode.NotModified) {
- await ws.db
- .mktx((x) => [x.backupProviders])
- .runReadWrite(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 ws.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);
+ });
removeAttentionRequest(ws, {
entityId: provider.baseUrl,
@@ -296,46 +294,42 @@ async function runBackupCycleForProvider(
if (res === undefined) {
//claimed
- await ws.db
- .mktx((x) => [x.backupProviders, x.operationRetries])
- .runReadWrite(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 ws.db
- .mktx((x) => [x.backupProviders, x.operationRetries])
- .runReadWrite(async (tx) => {
+ await ws.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.shouldRetryFreshProposal = true;
prov.state = {
tag: BackupProviderStateTag.Retrying,
};
await tx.backupProviders.put(prov);
});
+ throw Error("not implemented");
+ // return {
+ // type: TaskRunResultType.Pending,
+ // };
+ }
+ const result = res;
+
+ await ws.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(
ws,
{
@@ -353,23 +347,21 @@ async function runBackupCycleForProvider(
}
if (resp.status === HttpStatusCode.NoContent) {
- await ws.db
- .mktx((x) => [x.backupProviders])
- .runReadWrite(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 ws.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);
+ });
removeAttentionRequest(ws, {
entityId: provider.baseUrl,
@@ -388,24 +380,22 @@ async function runBackupCycleForProvider(
// const blob = await decryptBackup(backupConfig, backupEnc);
// FIXME: Re-implement backup import with merging
// await importBackup(ws, blob, cryptoData);
- await ws.db
- .mktx((x) => [x.backupProviders, x.operationRetries])
- .runReadWrite(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 ws.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);
+ });
logger.info("processed existing backup");
// Now upload our own, merged backup.
return await runBackupCycleForProvider(ws, args);
@@ -427,11 +417,12 @@ export async function processBackupForProvider(
ws: InternalWalletState,
backupProviderBaseUrl: string,
): Promise<TaskRunResult> {
- const provider = await ws.db
- .mktx((x) => [x.backupProviders])
- .runReadOnly(async (tx) => {
+ const provider = await ws.db.runReadOnlyTx(
+ ["backupProviders"],
+ async (tx) => {
return await tx.backupProviders.get(backupProviderBaseUrl);
- });
+ },
+ );
if (!provider) {
throw Error("unknown backup provider");
}
@@ -457,11 +448,9 @@ export async function removeBackupProvider(
ws: InternalWalletState,
req: RemoveBackupProviderRequest,
): Promise<void> {
- await ws.db
- .mktx((x) => [x.backupProviders])
- .runReadWrite(async (tx) => {
- await tx.backupProviders.delete(req.provider);
- });
+ await ws.db.runReadWriteTx(["backupProviders"], async (tx) => {
+ await tx.backupProviders.delete(req.provider);
+ });
}
export interface RunBackupCycleRequest {
@@ -487,9 +476,9 @@ export async function runBackupCycle(
ws: InternalWalletState,
req: RunBackupCycleRequest,
): Promise<void> {
- const providers = await ws.db
- .mktx((x) => [x.backupProviders])
- .runReadOnly(async (tx) => {
+ const providers = await ws.db.runReadOnlyTx(
+ ["backupProviders"],
+ async (tx) => {
if (req.providers) {
const rs = await Promise.all(
req.providers.map((id) => tx.backupProviders.get(id)),
@@ -497,7 +486,8 @@ export async function runBackupCycle(
return rs.filter(notEmpty);
}
return await tx.backupProviders.iter().toArray();
- });
+ },
+ );
for (const provider of providers) {
await runBackupCycleForProvider(ws, {
@@ -587,62 +577,56 @@ export async function addBackupProvider(
logger.info(`adding backup provider ${j2s(req)}`);
await provideBackupState(ws);
const canonUrl = canonicalizeBaseUrl(req.backupProviderBaseUrl);
- await ws.db
- .mktx((x) => [x.backupProviders])
- .runReadWrite(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 ws.http.fetch(termsUrl.href);
- const terms = await readSuccessResponseJsonOrThrow(
- resp,
- codecForSyncTermsOfServiceResponse(),
- );
- await ws.db
- .mktx((x) => [x.backupProviders])
- .runReadWrite(async (tx) => {
- let state: BackupProviderState;
- //FIXME: what is the difference provisional and ready?
+ await ws.db.runReadWriteTx(["backupProviders"], async (tx) => {
+ const oldProv = await tx.backupProviders.get(canonUrl);
+ if (oldProv) {
+ logger.info("old backup provider found");
if (req.activate) {
- state = {
+ oldProv.state = {
tag: BackupProviderStateTag.Ready,
nextBackupTimestamp: timestampPreciseToDb(
TalerPreciseTimestamp.now(),
),
};
- } else {
- state = {
- tag: BackupProviderStateTag.Provisional,
- };
+ logger.info("setting existing backup provider to active");
+ await tx.backupProviders.put(oldProv);
}
- 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;
+ }
+ });
+ const termsUrl = new URL("config", canonUrl);
+ const resp = await ws.http.fetch(termsUrl.href);
+ const terms = await readSuccessResponseJsonOrThrow(
+ resp,
+ codecForSyncTermsOfServiceResponse(),
+ );
+ await ws.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(ws, {
backupProviderBaseUrl: canonUrl,
@@ -827,9 +811,9 @@ export async function getBackupInfo(
ws: InternalWalletState,
): Promise<BackupInfo> {
const backupConfig = await provideBackupState(ws);
- const providerRecords = await ws.db
- .mktx((x) => [x.backupProviders, x.operationRetries])
- .runReadOnly(async (tx) => {
+ const providerRecords = await ws.db.runReadOnlyTx(
+ ["backupProviders", "operationRetries"],
+ async (tx) => {
return await tx.backupProviders.iter().mapAsync(async (bp) => {
const opId = TaskIdentifiers.forBackup(bp);
const retryRecord = await tx.operationRetries.get(opId);
@@ -838,7 +822,8 @@ export async function getBackupInfo(
retryRecord,
};
});
- });
+ },
+ );
const providers: ProviderInfo[] = [];
for (const x of providerRecords) {
providers.push({
@@ -872,11 +857,12 @@ export async function getBackupRecovery(
ws: InternalWalletState,
): Promise<BackupRecovery> {
const bs = await provideBackupState(ws);
- const providers = await ws.db
- .mktx((x) => [x.backupProviders])
- .runReadOnly(async (tx) => {
+ const providers = await ws.db.runReadOnlyTx(
+ ["backupProviders"],
+ async (tx) => {
return await tx.backupProviders.iter().toArray();
- });
+ },
+ );
return {
providers: providers
.filter((x) => x.state.tag !== BackupProviderStateTag.Provisional)
@@ -894,50 +880,48 @@ async function backupRecoveryTheirs(
ws: InternalWalletState,
br: BackupRecovery,
) {
- await ws.db
- .mktx((x) => [x.config, x.backupProviders])
- .runReadWrite(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);
+ await ws.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))],
+ });
}
- });
+ }
+ 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(ws: InternalWalletState, br: BackupRecovery) {
@@ -949,11 +933,12 @@ export async function loadBackupRecovery(
br: RecoveryLoadRequest,
): Promise<void> {
const bs = await provideBackupState(ws);
- const providers = await ws.db
- .mktx((x) => [x.backupProviders])
- .runReadOnly(async (tx) => {
+ const providers = await ws.db.runReadOnlyTx(
+ ["backupProviders"],
+ async (tx) => {
return await tx.backupProviders.iter().toArray();
- });
+ },
+ );
let strategy = br.strategy;
if (
br.recovery.walletRootPriv != bs.walletRootPriv &&
@@ -996,11 +981,12 @@ export async function decryptBackup(
export async function provideBackupState(
ws: InternalWalletState,
): Promise<WalletBackupConfState> {
- const bs: ConfigRecord | undefined = await ws.db
- .mktx((stores) => [stores.config])
- .runReadOnly(async (tx) => {
+ const bs: ConfigRecord | undefined = await ws.db.runReadOnlyTx(
+ ["config"],
+ async (tx) => {
return await tx.config.get(ConfigRecordKey.WalletBackupState);
- });
+ },
+ );
if (bs) {
checkDbInvariant(bs.key === ConfigRecordKey.WalletBackupState);
return bs.value;
@@ -1012,34 +998,32 @@ 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
- .mktx((x) => [x.config])
- .runReadWrite(async (tx) => {
- let backupStateEntry: ConfigRecord | undefined = await tx.config.get(
- ConfigRecordKey.WalletBackupState,
- );
- if (!backupStateEntry) {
- backupStateEntry = {
- key: ConfigRecordKey.WalletBackupState,
- value: {
- deviceId,
- walletRootPub: k.pub,
- walletRootPriv: k.priv,
- lastBackupPlainHash: undefined,
- },
- };
- await tx.config.put(backupStateEntry);
- }
- checkDbInvariant(
- backupStateEntry.key === ConfigRecordKey.WalletBackupState,
- );
- return backupStateEntry.value;
- });
+ return await ws.db.runReadWriteTx(["config"], async (tx) => {
+ let backupStateEntry: ConfigRecord | undefined = await tx.config.get(
+ ConfigRecordKey.WalletBackupState,
+ );
+ if (!backupStateEntry) {
+ backupStateEntry = {
+ key: ConfigRecordKey.WalletBackupState,
+ value: {
+ deviceId,
+ walletRootPub: k.pub,
+ walletRootPriv: k.priv,
+ lastBackupPlainHash: undefined,
+ },
+ };
+ await tx.config.put(backupStateEntry);
+ }
+ checkDbInvariant(
+ backupStateEntry.key === ConfigRecordKey.WalletBackupState,
+ );
+ return backupStateEntry.value;
+ });
}
export async function getWalletBackupState(
ws: InternalWalletState,
- tx: GetReadOnlyAccess<{ config: typeof WalletStoresV1.config }>,
+ tx: WalletDbReadOnlyTransaction<["config"]>,
): Promise<WalletBackupConfState> {
const bs = await tx.config.get(ConfigRecordKey.WalletBackupState);
checkDbInvariant(!!bs, "wallet backup state should be in DB");
@@ -1052,21 +1036,19 @@ export async function setWalletDeviceId(
deviceId: string,
): Promise<void> {
await provideBackupState(ws);
- await ws.db
- .mktx((x) => [x.config])
- .runReadWrite(async (tx) => {
- let backupStateEntry: ConfigRecord | undefined = await tx.config.get(
- ConfigRecordKey.WalletBackupState,
- );
- if (
- !backupStateEntry ||
- backupStateEntry.key !== ConfigRecordKey.WalletBackupState
- ) {
- return;
- }
- backupStateEntry.value.deviceId = deviceId;
- await tx.config.put(backupStateEntry);
- });
+ await ws.db.runReadWriteTx(["config"], async (tx) => {
+ let backupStateEntry: ConfigRecord | undefined = await tx.config.get(
+ ConfigRecordKey.WalletBackupState,
+ );
+ if (
+ !backupStateEntry ||
+ backupStateEntry.key !== ConfigRecordKey.WalletBackupState
+ ) {
+ return;
+ }
+ backupStateEntry.value.deviceId = deviceId;
+ await tx.config.put(backupStateEntry);
+ });
}
export async function getWalletDeviceId(