aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core
diff options
context:
space:
mode:
authorFlorian Dold <florian@dold.me>2023-08-30 18:01:18 +0200
committerFlorian Dold <florian@dold.me>2023-08-30 18:01:18 +0200
commita713d90c3c564408309d92223d383ecc9225924f (patch)
tree18142a4ce3d98df6e8a4945dbca23c47715eee6a /packages/taler-wallet-core
parent0a4782a0da631aba31dc0ecef7427df2467cc3e6 (diff)
downloadwallet-core-a713d90c3c564408309d92223d383ecc9225924f.tar.xz
wallet-core: remove old sync code, add stored backups skeleton
Diffstat (limited to 'packages/taler-wallet-core')
-rw-r--r--packages/taler-wallet-core/src/db.ts49
-rw-r--r--packages/taler-wallet-core/src/operations/backup/export.ts586
-rw-r--r--packages/taler-wallet-core/src/operations/backup/import.ts874
-rw-r--r--packages/taler-wallet-core/src/operations/backup/index.ts188
-rw-r--r--packages/taler-wallet-core/src/operations/backup/state.ts92
-rw-r--r--packages/taler-wallet-core/src/wallet-api-types.ts42
-rw-r--r--packages/taler-wallet-core/src/wallet.ts32
7 files changed, 194 insertions, 1669 deletions
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index e68385267..1255e8c71 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -2769,6 +2769,24 @@ export const walletMetadataStore = {
),
};
+export interface StoredBackupMeta {
+ name: string;
+}
+
+export interface StoredBackupData {
+ name: string;
+ data: any;
+}
+
+export const StoredBackupStores = {
+ backupMeta: describeStore(
+ "backupMeta",
+ describeContents<MetaConfigRecord>({ keyPath: "name" }),
+ {},
+ ),
+ backupData: describeStore("backupData", describeContents<any>({}), {}),
+};
+
export interface DbDumpRecord {
/**
* Key, serialized with structuredEncapsulated.
@@ -2831,6 +2849,7 @@ export async function exportSingleDb(
return new Promise((resolve, reject) => {
const tx = myDb.transaction(Array.from(myDb.objectStoreNames));
tx.addEventListener("complete", () => {
+ myDb.close();
resolve(singleDbDump);
});
// tslint:disable-next-line:prefer-for-of
@@ -3211,6 +3230,36 @@ function onMetaDbUpgradeNeeded(
);
}
+function onStoredBackupsDbUpgradeNeeded(
+ db: IDBDatabase,
+ oldVersion: number,
+ newVersion: number,
+ upgradeTransaction: IDBTransaction,
+) {
+ upgradeFromStoreMap(
+ StoredBackupStores,
+ db,
+ oldVersion,
+ newVersion,
+ upgradeTransaction,
+ );
+}
+
+export async function openStoredBackupsDatabase(
+ idbFactory: IDBFactory,
+): Promise<DbAccess<typeof StoredBackupStores>> {
+ const backupsDbHandle = await openDatabase(
+ idbFactory,
+ TALER_WALLET_META_DB_NAME,
+ 1,
+ () => {},
+ onStoredBackupsDbUpgradeNeeded,
+ );
+
+ const handle = new DbAccess(backupsDbHandle, StoredBackupStores);
+ return handle;
+}
+
/**
* Return a promise that resolves
* to the taler wallet db.
diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts b/packages/taler-wallet-core/src/operations/backup/export.ts
deleted file mode 100644
index c9446a05f..000000000
--- a/packages/taler-wallet-core/src/operations/backup/export.ts
+++ /dev/null
@@ -1,586 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2020 Taler Systems SA
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Implementation of wallet backups (export/import/upload) and sync
- * server management.
- *
- * @author Florian Dold <dold@taler.net>
- */
-
-/**
- * Imports.
- */
-import {
- AbsoluteTime,
- Amounts,
- BackupBackupProvider,
- BackupBackupProviderTerms,
- BackupCoin,
- BackupCoinSource,
- BackupCoinSourceType,
- BackupDenomination,
- BackupExchange,
- BackupExchangeDetails,
- BackupExchangeSignKey,
- BackupExchangeWireFee,
- BackupOperationStatus,
- BackupPayInfo,
- BackupProposalStatus,
- BackupPurchase,
- BackupRecoupGroup,
- BackupRefreshGroup,
- BackupRefreshOldCoin,
- BackupRefreshSession,
- BackupRefundItem,
- BackupRefundState,
- BackupTip,
- BackupWgInfo,
- BackupWgType,
- BackupWithdrawalGroup,
- BACKUP_VERSION_MAJOR,
- BACKUP_VERSION_MINOR,
- canonicalizeBaseUrl,
- canonicalJson,
- CoinStatus,
- encodeCrock,
- getRandomBytes,
- hash,
- Logger,
- stringToBytes,
- WalletBackupContentV1,
- TalerPreciseTimestamp,
-} from "@gnu-taler/taler-util";
-import {
- CoinSourceType,
- ConfigRecordKey,
- DenominationRecord,
- PurchaseStatus,
- RefreshCoinStatus,
- WithdrawalGroupStatus,
- WithdrawalRecordType,
-} from "../../db.js";
-import { InternalWalletState } from "../../internal-wallet-state.js";
-import { assertUnreachable } from "../../util/assertUnreachable.js";
-import { checkDbInvariant } from "../../util/invariants.js";
-import { getWalletBackupState, provideBackupState } from "./state.js";
-
-const logger = new Logger("backup/export.ts");
-
-export async function exportBackup(
- ws: InternalWalletState,
-): Promise<WalletBackupContentV1> {
- await provideBackupState(ws);
- return ws.db
- .mktx((x) => [
- x.config,
- x.exchanges,
- x.exchangeDetails,
- x.exchangeSignKeys,
- x.coins,
- x.contractTerms,
- x.denominations,
- x.purchases,
- x.refreshGroups,
- x.backupProviders,
- x.rewards,
- x.recoupGroups,
- x.withdrawalGroups,
- ])
- .runReadWrite(async (tx) => {
- const bs = await getWalletBackupState(ws, tx);
-
- const backupExchangeDetails: BackupExchangeDetails[] = [];
- const backupExchanges: BackupExchange[] = [];
- const backupCoinsByDenom: { [dph: string]: BackupCoin[] } = {};
- const backupDenominationsByExchange: {
- [url: string]: BackupDenomination[];
- } = {};
- const backupPurchases: BackupPurchase[] = [];
- const backupRefreshGroups: BackupRefreshGroup[] = [];
- const backupBackupProviders: BackupBackupProvider[] = [];
- const backupTips: BackupTip[] = [];
- const backupRecoupGroups: BackupRecoupGroup[] = [];
- const backupWithdrawalGroups: BackupWithdrawalGroup[] = [];
-
- await tx.withdrawalGroups.iter().forEachAsync(async (wg) => {
- let info: BackupWgInfo;
- switch (wg.wgInfo.withdrawalType) {
- case WithdrawalRecordType.BankIntegrated:
- info = {
- type: BackupWgType.BankIntegrated,
- exchange_payto_uri: wg.wgInfo.bankInfo.exchangePaytoUri,
- taler_withdraw_uri: wg.wgInfo.bankInfo.talerWithdrawUri,
- confirm_url: wg.wgInfo.bankInfo.confirmUrl,
- timestamp_bank_confirmed:
- wg.wgInfo.bankInfo.timestampBankConfirmed,
- timestamp_reserve_info_posted:
- wg.wgInfo.bankInfo.timestampReserveInfoPosted,
- };
- break;
- case WithdrawalRecordType.BankManual:
- info = {
- type: BackupWgType.BankManual,
- };
- break;
- case WithdrawalRecordType.PeerPullCredit:
- info = {
- type: BackupWgType.PeerPullCredit,
- contract_priv: wg.wgInfo.contractPriv,
- contract_terms: wg.wgInfo.contractTerms,
- };
- break;
- case WithdrawalRecordType.PeerPushCredit:
- info = {
- type: BackupWgType.PeerPushCredit,
- contract_terms: wg.wgInfo.contractTerms,
- };
- break;
- case WithdrawalRecordType.Recoup:
- info = {
- type: BackupWgType.Recoup,
- };
- break;
- default:
- assertUnreachable(wg.wgInfo);
- }
- backupWithdrawalGroups.push({
- raw_withdrawal_amount: Amounts.stringify(wg.rawWithdrawalAmount),
- info,
- timestamp_created: wg.timestampStart,
- timestamp_finish: wg.timestampFinish,
- withdrawal_group_id: wg.withdrawalGroupId,
- secret_seed: wg.secretSeed,
- exchange_base_url: wg.exchangeBaseUrl,
- instructed_amount: Amounts.stringify(wg.instructedAmount),
- effective_withdrawal_amount: Amounts.stringify(
- wg.effectiveWithdrawalAmount,
- ),
- reserve_priv: wg.reservePriv,
- restrict_age: wg.restrictAge,
- // FIXME: proper status conversion!
- operation_status:
- wg.status == WithdrawalGroupStatus.Finished
- ? BackupOperationStatus.Finished
- : BackupOperationStatus.Pending,
- selected_denoms_uid: wg.denomSelUid,
- selected_denoms: wg.denomsSel.selectedDenoms.map((x) => ({
- count: x.count,
- denom_pub_hash: x.denomPubHash,
- })),
- });
- });
-
- await tx.rewards.iter().forEach((tip) => {
- backupTips.push({
- exchange_base_url: tip.exchangeBaseUrl,
- merchant_base_url: tip.merchantBaseUrl,
- merchant_tip_id: tip.merchantRewardId,
- wallet_tip_id: tip.walletRewardId,
- next_url: tip.next_url,
- secret_seed: tip.secretSeed,
- selected_denoms: tip.denomsSel.selectedDenoms.map((x) => ({
- count: x.count,
- denom_pub_hash: x.denomPubHash,
- })),
- timestamp_finished: tip.pickedUpTimestamp,
- timestamp_accepted: tip.acceptedTimestamp,
- timestamp_created: tip.createdTimestamp,
- timestamp_expiration: tip.rewardExpiration,
- tip_amount_raw: Amounts.stringify(tip.rewardAmountRaw),
- selected_denoms_uid: tip.denomSelUid,
- });
- });
-
- await tx.recoupGroups.iter().forEach((recoupGroup) => {
- backupRecoupGroups.push({
- recoup_group_id: recoupGroup.recoupGroupId,
- timestamp_created: recoupGroup.timestampStarted,
- timestamp_finish: recoupGroup.timestampFinished,
- coins: recoupGroup.coinPubs.map((x, i) => ({
- coin_pub: x,
- recoup_finished: recoupGroup.recoupFinishedPerCoin[i],
- })),
- });
- });
-
- await tx.backupProviders.iter().forEach((bp) => {
- let terms: BackupBackupProviderTerms | undefined;
- if (bp.terms) {
- terms = {
- annual_fee: Amounts.stringify(bp.terms.annualFee),
- storage_limit_in_megabytes: bp.terms.storageLimitInMegabytes,
- supported_protocol_version: bp.terms.supportedProtocolVersion,
- };
- }
- backupBackupProviders.push({
- terms,
- base_url: canonicalizeBaseUrl(bp.baseUrl),
- pay_proposal_ids: bp.paymentProposalIds,
- uids: bp.uids,
- });
- });
-
- await tx.coins.iter().forEach((coin) => {
- let bcs: BackupCoinSource;
- switch (coin.coinSource.type) {
- case CoinSourceType.Refresh:
- bcs = {
- type: BackupCoinSourceType.Refresh,
- old_coin_pub: coin.coinSource.oldCoinPub,
- refresh_group_id: coin.coinSource.refreshGroupId,
- };
- break;
- case CoinSourceType.Reward:
- bcs = {
- type: BackupCoinSourceType.Reward,
- coin_index: coin.coinSource.coinIndex,
- wallet_tip_id: coin.coinSource.walletRewardId,
- };
- break;
- case CoinSourceType.Withdraw:
- bcs = {
- type: BackupCoinSourceType.Withdraw,
- coin_index: coin.coinSource.coinIndex,
- reserve_pub: coin.coinSource.reservePub,
- withdrawal_group_id: coin.coinSource.withdrawalGroupId,
- };
- break;
- }
-
- const coins = (backupCoinsByDenom[coin.denomPubHash] ??= []);
- coins.push({
- blinding_key: coin.blindingKey,
- coin_priv: coin.coinPriv,
- coin_source: bcs,
- fresh: coin.status === CoinStatus.Fresh,
- spend_allocation: coin.spendAllocation
- ? {
- amount: coin.spendAllocation.amount,
- id: coin.spendAllocation.id,
- }
- : undefined,
- denom_sig: coin.denomSig,
- });
- });
-
- await tx.denominations.iter().forEach((denom) => {
- const backupDenoms = (backupDenominationsByExchange[
- denom.exchangeBaseUrl
- ] ??= []);
- backupDenoms.push({
- coins: backupCoinsByDenom[denom.denomPubHash] ?? [],
- denom_pub: denom.denomPub,
- fee_deposit: Amounts.stringify(denom.fees.feeDeposit),
- fee_refresh: Amounts.stringify(denom.fees.feeRefresh),
- fee_refund: Amounts.stringify(denom.fees.feeRefund),
- fee_withdraw: Amounts.stringify(denom.fees.feeWithdraw),
- is_offered: denom.isOffered,
- is_revoked: denom.isRevoked,
- master_sig: denom.masterSig,
- stamp_expire_deposit: denom.stampExpireDeposit,
- stamp_expire_legal: denom.stampExpireLegal,
- stamp_expire_withdraw: denom.stampExpireWithdraw,
- stamp_start: denom.stampStart,
- value: Amounts.stringify(DenominationRecord.getValue(denom)),
- list_issue_date: denom.listIssueDate,
- });
- });
-
- await tx.exchanges.iter().forEachAsync(async (ex) => {
- const dp = ex.detailsPointer;
- if (!dp) {
- return;
- }
- backupExchanges.push({
- base_url: ex.baseUrl,
- currency: dp.currency,
- master_public_key: dp.masterPublicKey,
- update_clock: dp.updateClock,
- });
- });
-
- await tx.exchangeDetails.iter().forEachAsync(async (ex) => {
- // Only back up permanently added exchanges.
-
- const wi = ex.wireInfo;
- const wireFees: BackupExchangeWireFee[] = [];
-
- Object.keys(wi.feesForType).forEach((x) => {
- for (const f of wi.feesForType[x]) {
- wireFees.push({
- wire_type: x,
- closing_fee: Amounts.stringify(f.closingFee),
- end_stamp: f.endStamp,
- sig: f.sig,
- start_stamp: f.startStamp,
- wire_fee: Amounts.stringify(f.wireFee),
- });
- }
- });
- checkDbInvariant(ex.rowId != null);
- const exchangeSk =
- await tx.exchangeSignKeys.indexes.byExchangeDetailsRowId.getAll(
- ex.rowId,
- );
- let signingKeys: BackupExchangeSignKey[] = exchangeSk.map((x) => ({
- key: x.signkeyPub,
- master_sig: x.masterSig,
- stamp_end: x.stampEnd,
- stamp_expire: x.stampExpire,
- stamp_start: x.stampStart,
- }));
-
- backupExchangeDetails.push({
- base_url: ex.exchangeBaseUrl,
- reserve_closing_delay: ex.reserveClosingDelay,
- accounts: ex.wireInfo.accounts.map((x) => ({
- payto_uri: x.payto_uri,
- master_sig: x.master_sig,
- })),
- auditors: ex.auditors.map((x) => ({
- auditor_pub: x.auditor_pub,
- auditor_url: x.auditor_url,
- denomination_keys: x.denomination_keys,
- })),
- master_public_key: ex.masterPublicKey,
- currency: ex.currency,
- protocol_version: ex.protocolVersionRange,
- wire_fees: wireFees,
- signing_keys: signingKeys,
- global_fees: ex.globalFees.map((x) => ({
- accountFee: Amounts.stringify(x.accountFee),
- historyFee: Amounts.stringify(x.historyFee),
- purseFee: Amounts.stringify(x.purseFee),
- endDate: x.endDate,
- historyTimeout: x.historyTimeout,
- signature: x.signature,
- purseLimit: x.purseLimit,
- purseTimeout: x.purseTimeout,
- startDate: x.startDate,
- })),
- tos_accepted_etag: ex.tosAccepted?.etag,
- tos_accepted_timestamp: ex.tosAccepted?.timestamp,
- denominations:
- backupDenominationsByExchange[ex.exchangeBaseUrl] ?? [],
- });
- });
-
- const purchaseProposalIdSet = new Set<string>();
-
- await tx.purchases.iter().forEachAsync(async (purch) => {
- const refunds: BackupRefundItem[] = [];
- purchaseProposalIdSet.add(purch.proposalId);
- // for (const refundKey of Object.keys(purch.refunds)) {
- // const ri = purch.refunds[refundKey];
- // const common = {
- // coin_pub: ri.coinPub,
- // execution_time: ri.executionTime,
- // obtained_time: ri.obtainedTime,
- // refund_amount: Amounts.stringify(ri.refundAmount),
- // rtransaction_id: ri.rtransactionId,
- // total_refresh_cost_bound: Amounts.stringify(
- // ri.totalRefreshCostBound,
- // ),
- // };
- // switch (ri.type) {
- // case RefundState.Applied:
- // refunds.push({ type: BackupRefundState.Applied, ...common });
- // break;
- // case RefundState.Failed:
- // refunds.push({ type: BackupRefundState.Failed, ...common });
- // break;
- // case RefundState.Pending:
- // refunds.push({ type: BackupRefundState.Pending, ...common });
- // break;
- // }
- // }
-
- let propStatus: BackupProposalStatus;
- switch (purch.purchaseStatus) {
- case PurchaseStatus.Done:
- case PurchaseStatus.PendingQueryingAutoRefund:
- case PurchaseStatus.PendingQueryingRefund:
- propStatus = BackupProposalStatus.Paid;
- break;
- case PurchaseStatus.PendingPayingReplay:
- case PurchaseStatus.PendingDownloadingProposal:
- case PurchaseStatus.DialogProposed:
- case PurchaseStatus.PendingPaying:
- propStatus = BackupProposalStatus.Proposed;
- break;
- case PurchaseStatus.DialogShared:
- propStatus = BackupProposalStatus.Shared;
- break;
- case PurchaseStatus.FailedClaim:
- case PurchaseStatus.AbortedIncompletePayment:
- propStatus = BackupProposalStatus.PermanentlyFailed;
- break;
- case PurchaseStatus.AbortingWithRefund:
- case PurchaseStatus.AbortedProposalRefused:
- propStatus = BackupProposalStatus.Refused;
- break;
- case PurchaseStatus.RepurchaseDetected:
- propStatus = BackupProposalStatus.Repurchase;
- break;
- default: {
- const error = purch.purchaseStatus;
- throw Error(`purchase status ${error} is not handled`);
- }
- }
-
- const payInfo = purch.payInfo;
- let backupPayInfo: BackupPayInfo | undefined = undefined;
- if (payInfo) {
- backupPayInfo = {
- pay_coins: payInfo.payCoinSelection.coinPubs.map((x, i) => ({
- coin_pub: x,
- contribution: Amounts.stringify(
- payInfo.payCoinSelection.coinContributions[i],
- ),
- })),
- total_pay_cost: Amounts.stringify(payInfo.totalPayCost),
- pay_coins_uid: payInfo.payCoinSelectionUid,
- };
- }
-
- let contractTermsRaw = undefined;
- if (purch.download) {
- const contractTermsRecord = await tx.contractTerms.get(
- purch.download.contractTermsHash,
- );
- if (contractTermsRecord) {
- contractTermsRaw = contractTermsRecord.contractTermsRaw;
- }
- }
-
- backupPurchases.push({
- contract_terms_raw: contractTermsRaw,
- auto_refund_deadline: purch.autoRefundDeadline,
- merchant_pay_sig: purch.merchantPaySig,
- pos_confirmation: purch.posConfirmation,
- pay_info: backupPayInfo,
- proposal_id: purch.proposalId,
- refunds,
- timestamp_accepted: purch.timestampAccept,
- timestamp_first_successful_pay: purch.timestampFirstSuccessfulPay,
- nonce_priv: purch.noncePriv,
- merchant_sig: purch.download?.contractTermsMerchantSig,
- claim_token: purch.claimToken,
- merchant_base_url: purch.merchantBaseUrl,
- order_id: purch.orderId,
- proposal_status: propStatus,
- repurchase_proposal_id: purch.repurchaseProposalId,
- download_session_id: purch.downloadSessionId,
- timestamp_proposed: purch.timestamp,
- shared: purch.shared,
- });
- });
-
- await tx.refreshGroups.iter().forEach((rg) => {
- const oldCoins: BackupRefreshOldCoin[] = [];
-
- for (let i = 0; i < rg.oldCoinPubs.length; i++) {
- let refreshSession: BackupRefreshSession | undefined;
- const s = rg.refreshSessionPerCoin[i];
- if (s) {
- refreshSession = {
- new_denoms: s.newDenoms.map((x) => ({
- count: x.count,
- denom_pub_hash: x.denomPubHash,
- })),
- session_secret_seed: s.sessionSecretSeed,
- noreveal_index: s.norevealIndex,
- };
- }
- oldCoins.push({
- coin_pub: rg.oldCoinPubs[i],
- estimated_output_amount: Amounts.stringify(
- rg.estimatedOutputPerCoin[i],
- ),
- finished: rg.statusPerCoin[i] === RefreshCoinStatus.Finished,
- input_amount: Amounts.stringify(rg.inputPerCoin[i]),
- refresh_session: refreshSession,
- });
- }
-
- backupRefreshGroups.push({
- reason: rg.reason as any,
- refresh_group_id: rg.refreshGroupId,
- timestamp_created: rg.timestampCreated,
- timestamp_finish: rg.timestampFinished,
- old_coins: oldCoins,
- });
- });
-
- const ts = TalerPreciseTimestamp.now();
-
- if (!bs.lastBackupTimestamp) {
- bs.lastBackupTimestamp = ts;
- }
-
- const backupBlob: WalletBackupContentV1 = {
- schema_id: "gnu-taler-wallet-backup-content",
- schema_version: BACKUP_VERSION_MAJOR,
- minor_version: BACKUP_VERSION_MINOR,
- exchanges: backupExchanges,
- exchange_details: backupExchangeDetails,
- wallet_root_pub: bs.walletRootPub,
- backup_providers: backupBackupProviders,
- current_device_id: bs.deviceId,
- purchases: backupPurchases,
- recoup_groups: backupRecoupGroups,
- refresh_groups: backupRefreshGroups,
- tips: backupTips,
- timestamp: bs.lastBackupTimestamp,
- trusted_auditors: {},
- trusted_exchanges: {},
- intern_table: {},
- error_reports: [],
- tombstones: [],
- // FIXME!
- withdrawal_groups: backupWithdrawalGroups,
- };
-
- // If the backup changed, we change our nonce and timestamp.
-
- let h = encodeCrock(hash(stringToBytes(canonicalJson(backupBlob))));
- if (h !== bs.lastBackupPlainHash) {
- logger.trace(
- `plain backup hash changed (from ${bs.lastBackupPlainHash}to ${h})`,
- );
- bs.lastBackupTimestamp = ts;
- backupBlob.timestamp = ts;
- bs.lastBackupPlainHash = encodeCrock(
- hash(stringToBytes(canonicalJson(backupBlob))),
- );
- bs.lastBackupNonce = encodeCrock(getRandomBytes(32));
- logger.trace(
- `setting timestamp to ${AbsoluteTime.toIsoString(
- AbsoluteTime.fromPreciseTimestamp(ts),
- )} and nonce to ${bs.lastBackupNonce}`,
- );
- await tx.config.put({
- key: ConfigRecordKey.WalletBackupState,
- value: bs,
- });
- } else {
- logger.trace("backup hash did not change");
- }
-
- return backupBlob;
- });
-}
diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts
deleted file mode 100644
index 836c65643..000000000
--- a/packages/taler-wallet-core/src/operations/backup/import.ts
+++ /dev/null
@@ -1,874 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2020 Taler Systems SA
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
- */
-
-import {
- AgeRestriction,
- AmountJson,
- Amounts,
- BackupCoin,
- BackupCoinSourceType,
- BackupDenomSel,
- BackupPayInfo,
- BackupProposalStatus,
- BackupRefreshReason,
- BackupRefundState,
- BackupWgType,
- codecForMerchantContractTerms,
- CoinStatus,
- DenomKeyType,
- DenomSelectionState,
- j2s,
- Logger,
- PayCoinSelection,
- RefreshReason,
- TalerProtocolTimestamp,
- TalerPreciseTimestamp,
- WalletBackupContentV1,
- WireInfo,
-} from "@gnu-taler/taler-util";
-import {
- CoinRecord,
- CoinSource,
- CoinSourceType,
- DenominationRecord,
- DenominationVerificationStatus,
- ProposalDownloadInfo,
- PurchaseStatus,
- PurchasePayInfo,
- RefreshCoinStatus,
- RefreshSessionRecord,
- WalletContractData,
- WalletStoresV1,
- WgInfo,
- WithdrawalGroupStatus,
- WithdrawalRecordType,
- RefreshOperationStatus,
- RewardRecordStatus,
-} from "../../db.js";
-import { InternalWalletState } from "../../internal-wallet-state.js";
-import { assertUnreachable } from "../../util/assertUnreachable.js";
-import { checkLogicInvariant } from "../../util/invariants.js";
-import { GetReadOnlyAccess, GetReadWriteAccess } from "../../util/query.js";
-import {
- constructTombstone,
- makeCoinAvailable,
- TombstoneTag,
-} from "../common.js";
-import { getExchangeDetails } from "../exchanges.js";
-import { extractContractData } from "../pay-merchant.js";
-import { provideBackupState } from "./state.js";
-
-const logger = new Logger("operations/backup/import.ts");
-
-function checkBackupInvariant(b: boolean, m?: string): asserts b {
- if (!b) {
- if (m) {
- throw Error(`BUG: backup invariant failed (${m})`);
- } else {
- throw Error("BUG: backup invariant failed");
- }
- }
-}
-
-/**
- * Re-compute information about the coin selection for a payment.
- */
-async function recoverPayCoinSelection(
- tx: GetReadWriteAccess<{
- exchanges: typeof WalletStoresV1.exchanges;
- exchangeDetails: typeof WalletStoresV1.exchangeDetails;
- coins: typeof WalletStoresV1.coins;
- denominations: typeof WalletStoresV1.denominations;
- }>,
- contractData: WalletContractData,
- payInfo: BackupPayInfo,
-): Promise<PayCoinSelection> {
- const coinPubs: string[] = payInfo.pay_coins.map((x) => x.coin_pub);
- const coinContributions: AmountJson[] = payInfo.pay_coins.map((x) =>
- Amounts.parseOrThrow(x.contribution),
- );
-
- const coveredExchanges: Set<string> = new Set();
-
- let totalWireFee: AmountJson = Amounts.zeroOfAmount(contractData.amount);
- let totalDepositFees: AmountJson = Amounts.zeroOfAmount(contractData.amount);
-
- for (const coinPub of coinPubs) {
- const coinRecord = await tx.coins.get(coinPub);
- checkBackupInvariant(!!coinRecord);
- const denom = await tx.denominations.get([
- coinRecord.exchangeBaseUrl,
- coinRecord.denomPubHash,
- ]);
- checkBackupInvariant(!!denom);
- totalDepositFees = Amounts.add(
- totalDepositFees,
- denom.fees.feeDeposit,
- ).amount;
-
- if (!coveredExchanges.has(coinRecord.exchangeBaseUrl)) {
- const exchangeDetails = await getExchangeDetails(
- tx,
- coinRecord.exchangeBaseUrl,
- );
- checkBackupInvariant(!!exchangeDetails);
- let wireFee: AmountJson | undefined;
- const feesForType = exchangeDetails.wireInfo.feesForType;
- checkBackupInvariant(!!feesForType);
- for (const fee of feesForType[contractData.wireMethod] || []) {
- if (
- fee.startStamp <= contractData.timestamp &&
- fee.endStamp >= contractData.timestamp
- ) {
- wireFee = Amounts.parseOrThrow(fee.wireFee);
- break;
- }
- }
- if (wireFee) {
- totalWireFee = Amounts.add(totalWireFee, wireFee).amount;
- }
- coveredExchanges.add(coinRecord.exchangeBaseUrl);
- }
- }
-
- let customerWireFee: AmountJson;
-
- const amortizedWireFee = Amounts.divide(
- totalWireFee,
- contractData.wireFeeAmortization,
- );
- if (Amounts.cmp(contractData.maxWireFee, amortizedWireFee) < 0) {
- customerWireFee = amortizedWireFee;
- } else {
- customerWireFee = Amounts.zeroOfAmount(contractData.amount);
- }
-
- const customerDepositFees = Amounts.sub(
- totalDepositFees,
- contractData.maxDepositFee,
- ).amount;
-
- return {
- coinPubs,
- coinContributions: coinContributions.map((x) => Amounts.stringify(x)),
- paymentAmount: Amounts.stringify(contractData.amount),
- customerWireFees: Amounts.stringify(customerWireFee),
- customerDepositFees: Amounts.stringify(customerDepositFees),
- };
-}
-
-async function getDenomSelStateFromBackup(
- tx: GetReadOnlyAccess<{ denominations: typeof WalletStoresV1.denominations }>,
- currency: string,
- exchangeBaseUrl: string,
- sel: BackupDenomSel,
-): Promise<DenomSelectionState> {
- const selectedDenoms: {
- denomPubHash: string;
- count: number;
- }[] = [];
- let totalCoinValue = Amounts.zeroOfCurrency(currency);
- let totalWithdrawCost = Amounts.zeroOfCurrency(currency);
- for (const s of sel) {
- const d = await tx.denominations.get([exchangeBaseUrl, s.denom_pub_hash]);
- checkBackupInvariant(!!d);
- totalCoinValue = Amounts.add(
- totalCoinValue,
- DenominationRecord.getValue(d),
- ).amount;
- totalWithdrawCost = Amounts.add(
- totalWithdrawCost,
- DenominationRecord.getValue(d),
- d.fees.feeWithdraw,
- ).amount;
- }
- return {
- selectedDenoms,
- totalCoinValue: Amounts.stringify(totalCoinValue),
- totalWithdrawCost: Amounts.stringify(totalWithdrawCost),
- };
-}
-
-export interface CompletedCoin {
- coinPub: string;
- coinEvHash: string;
-}
-
-/**
- * Precomputed cryptographic material for a backup import.
- *
- * We separate this data from the backup blob as we want the backup
- * blob to be small, and we can't compute it during the database transaction,
- * as the async crypto worker communication would auto-close the database transaction.
- */
-export interface BackupCryptoPrecomputedData {
- rsaDenomPubToHash: Record<string, string>;
- coinPrivToCompletedCoin: Record<string, CompletedCoin>;
- proposalNoncePrivToPub: { [priv: string]: string };
- proposalIdToContractTermsHash: { [proposalId: string]: string };
- reservePrivToPub: Record<string, string>;
-}
-
-export async function importCoin(
- ws: InternalWalletState,
- tx: GetReadWriteAccess<{
- coins: typeof WalletStoresV1.coins;
- coinAvailability: typeof WalletStoresV1.coinAvailability;
- denominations: typeof WalletStoresV1.denominations;
- }>,
- cryptoComp: BackupCryptoPrecomputedData,
- args: {
- backupCoin: BackupCoin;
- exchangeBaseUrl: string;
- denomPubHash: string;
- },
-): Promise<void> {
- const { backupCoin, exchangeBaseUrl, denomPubHash } = args;
- const compCoin = cryptoComp.coinPrivToCompletedCoin[backupCoin.coin_priv];
- checkLogicInvariant(!!compCoin);
- const existingCoin = await tx.coins.get(compCoin.coinPub);
- if (!existingCoin) {
- let coinSource: CoinSource;
- switch (backupCoin.coin_source.type) {
- case BackupCoinSourceType.Refresh:
- coinSource = {
- type: CoinSourceType.Refresh,
- oldCoinPub: backupCoin.coin_source.old_coin_pub,
- refreshGroupId: backupCoin.coin_source.refresh_group_id,
- };
- break;
- case BackupCoinSourceType.Reward:
- coinSource = {
- type: CoinSourceType.Reward,
- coinIndex: backupCoin.coin_source.coin_index,
- walletRewardId: backupCoin.coin_source.wallet_tip_id,
- };
- break;
- case BackupCoinSourceType.Withdraw:
- coinSource = {
- type: CoinSourceType.Withdraw,
- coinIndex: backupCoin.coin_source.coin_index,
- reservePub: backupCoin.coin_source.reserve_pub,
- withdrawalGroupId: backupCoin.coin_source.withdrawal_group_id,
- };
- break;
- }
- const coinRecord: CoinRecord = {
- blindingKey: backupCoin.blinding_key,
- coinEvHash: compCoin.coinEvHash,
- coinPriv: backupCoin.coin_priv,
- denomSig: backupCoin.denom_sig,
- coinPub: compCoin.coinPub,
- exchangeBaseUrl,
- denomPubHash,
- status: backupCoin.fresh ? CoinStatus.Fresh : CoinStatus.Dormant,
- coinSource,
- // FIXME!
- maxAge: AgeRestriction.AGE_UNRESTRICTED,
- // FIXME!
- ageCommitmentProof: undefined,
- // FIXME!
- spendAllocation: undefined,
- };
- if (coinRecord.status === CoinStatus.Fresh) {
- await makeCoinAvailable(ws, tx, coinRecord);
- } else {
- await tx.coins.put(coinRecord);
- }
- }
-}
-
-export async function importBackup(
- ws: InternalWalletState,
- backupBlobArg: any,
- cryptoComp: BackupCryptoPrecomputedData,
-): Promise<void> {
- await provideBackupState(ws);
-
- logger.info(`importing backup ${j2s(backupBlobArg)}`);
-
- return ws.db
- .mktx((x) => [
- x.config,
- x.exchangeDetails,
- x.exchanges,
- x.coins,
- x.coinAvailability,
- x.denominations,
- x.purchases,
- x.refreshGroups,
- x.backupProviders,
- x.rewards,
- x.recoupGroups,
- x.withdrawalGroups,
- x.tombstones,
- x.depositGroups,
- ])
- .runReadWrite(async (tx) => {
- // FIXME: validate schema!
- const backupBlob = backupBlobArg as WalletBackupContentV1;
-
- // FIXME: validate version
-
- for (const tombstone of backupBlob.tombstones) {
- await tx.tombstones.put({
- id: tombstone,
- });
- }
-
- const tombstoneSet = new Set(
- (await tx.tombstones.iter().toArray()).map((x) => x.id),
- );
-
- // FIXME: Validate that the "details pointer" is correct
-
- for (const backupExchange of backupBlob.exchanges) {
- const existingExchange = await tx.exchanges.get(
- backupExchange.base_url,
- );
- if (existingExchange) {
- continue;
- }
- // await tx.exchanges.put({
- // baseUrl: backupExchange.base_url,
- // detailsPointer: {
- // currency: backupExchange.currency,
- // masterPublicKey: backupExchange.master_public_key,
- // updateClock: backupExchange.update_clock,
- // },
- // lastUpdate: undefined,
- // nextUpdate: TalerPreciseTimestamp.now(),
- // nextRefreshCheck: TalerPreciseTimestamp.now(),
- // lastKeysEtag: undefined,
- // lastWireEtag: undefined,
- // });
- }
-
- for (const backupExchangeDetails of backupBlob.exchange_details) {
- const existingExchangeDetails =
- await tx.exchangeDetails.indexes.byPointer.get([
- backupExchangeDetails.base_url,
- backupExchangeDetails.currency,
- backupExchangeDetails.master_public_key,
- ]);
-
- if (!existingExchangeDetails) {
- const wireInfo: WireInfo = {
- accounts: backupExchangeDetails.accounts.map((x) => ({
- master_sig: x.master_sig,
- payto_uri: x.payto_uri,
- })),
- feesForType: {},
- };
- for (const fee of backupExchangeDetails.wire_fees) {
- const w = (wireInfo.feesForType[fee.wire_type] ??= []);
- w.push({
- closingFee: Amounts.stringify(fee.closing_fee),
- endStamp: fee.end_stamp,
- sig: fee.sig,
- startStamp: fee.start_stamp,
- wireFee: Amounts.stringify(fee.wire_fee),
- });
- }
- let tosAccepted = undefined;
- if (
- backupExchangeDetails.tos_accepted_etag &&
- backupExchangeDetails.tos_accepted_timestamp
- ) {
- tosAccepted = {
- etag: backupExchangeDetails.tos_accepted_etag,
- timestamp: backupExchangeDetails.tos_accepted_timestamp,
- };
- }
- await tx.exchangeDetails.put({
- exchangeBaseUrl: backupExchangeDetails.base_url,
- wireInfo,
- currency: backupExchangeDetails.currency,
- auditors: backupExchangeDetails.auditors.map((x) => ({
- auditor_pub: x.auditor_pub,
- auditor_url: x.auditor_url,
- denomination_keys: x.denomination_keys,
- })),
- masterPublicKey: backupExchangeDetails.master_public_key,
- protocolVersionRange: backupExchangeDetails.protocol_version,
- reserveClosingDelay: backupExchangeDetails.reserve_closing_delay,
- tosCurrentEtag: backupExchangeDetails.tos_accepted_etag || "",
- tosAccepted,
- globalFees: backupExchangeDetails.global_fees.map((x) => ({
- accountFee: Amounts.stringify(x.accountFee),
- historyFee: Amounts.stringify(x.historyFee),
- purseFee: Amounts.stringify(x.purseFee),
- endDate: x.endDate,
- historyTimeout: x.historyTimeout,
- signature: x.signature,
- purseLimit: x.purseLimit,
- purseTimeout: x.purseTimeout,
- startDate: x.startDate,
- })),
- });
- }
-
- for (const backupDenomination of backupExchangeDetails.denominations) {
- if (backupDenomination.denom_pub.cipher !== DenomKeyType.Rsa) {
- throw Error("unsupported cipher");
- }
- const denomPubHash =
- cryptoComp.rsaDenomPubToHash[
- backupDenomination.denom_pub.rsa_public_key
- ];
- checkLogicInvariant(!!denomPubHash);
- const existingDenom = await tx.denominations.get([
- backupExchangeDetails.base_url,
- denomPubHash,
- ]);
- if (!existingDenom) {
- const value = Amounts.parseOrThrow(backupDenomination.value);
-
- await tx.denominations.put({
- denomPub: backupDenomination.denom_pub,
- denomPubHash: denomPubHash,
- exchangeBaseUrl: backupExchangeDetails.base_url,
- exchangeMasterPub: backupExchangeDetails.master_public_key,
- fees: {
- feeDeposit: Amounts.stringify(backupDenomination.fee_deposit),
- feeRefresh: Amounts.stringify(backupDenomination.fee_refresh),
- feeRefund: Amounts.stringify(backupDenomination.fee_refund),
- feeWithdraw: Amounts.stringify(backupDenomination.fee_withdraw),
- },
- isOffered: backupDenomination.is_offered,
- isRevoked: backupDenomination.is_revoked,
- masterSig: backupDenomination.master_sig,
- stampExpireDeposit: backupDenomination.stamp_expire_deposit,
- stampExpireLegal: backupDenomination.stamp_expire_legal,
- stampExpireWithdraw: backupDenomination.stamp_expire_withdraw,
- stampStart: backupDenomination.stamp_start,
- verificationStatus: DenominationVerificationStatus.VerifiedGood,
- currency: value.currency,
- amountFrac: value.fraction,
- amountVal: value.value,
- listIssueDate: backupDenomination.list_issue_date,
- });
- }
- for (const backupCoin of backupDenomination.coins) {
- await importCoin(ws, tx, cryptoComp, {
- backupCoin,
- denomPubHash,
- exchangeBaseUrl: backupExchangeDetails.base_url,
- });
- }
- }
- }
-
- for (const backupWg of backupBlob.withdrawal_groups) {
- const reservePub = cryptoComp.reservePrivToPub[backupWg.reserve_priv];
- checkLogicInvariant(!!reservePub);
- const ts = constructTombstone({
- tag: TombstoneTag.DeleteReserve,
- reservePub,
- });
- if (tombstoneSet.has(ts)) {
- continue;
- }
- const existingWg = await tx.withdrawalGroups.get(
- backupWg.withdrawal_group_id,
- );
- if (existingWg) {
- continue;
- }
- let wgInfo: WgInfo;
- switch (backupWg.info.type) {
- case BackupWgType.BankIntegrated:
- wgInfo = {
- withdrawalType: WithdrawalRecordType.BankIntegrated,
- bankInfo: {
- exchangePaytoUri: backupWg.info.exchange_payto_uri,
- talerWithdrawUri: backupWg.info.taler_withdraw_uri,
- confirmUrl: backupWg.info.confirm_url,
- timestampBankConfirmed: backupWg.info.timestamp_bank_confirmed,
- timestampReserveInfoPosted:
- backupWg.info.timestamp_reserve_info_posted,
- },
- };
- break;
- case BackupWgType.BankManual:
- wgInfo = {
- withdrawalType: WithdrawalRecordType.BankManual,
- };
- break;
- case BackupWgType.PeerPullCredit:
- wgInfo = {
- withdrawalType: WithdrawalRecordType.PeerPullCredit,
- contractTerms: backupWg.info.contract_terms,
- contractPriv: backupWg.info.contract_priv,
- };
- break;
- case BackupWgType.PeerPushCredit:
- wgInfo = {
- withdrawalType: WithdrawalRecordType.PeerPushCredit,
- contractTerms: backupWg.info.contract_terms,
- };
- break;
- case BackupWgType.Recoup:
- wgInfo = {
- withdrawalType: WithdrawalRecordType.Recoup,
- };
- break;
- default:
- assertUnreachable(backupWg.info);
- }
- const instructedAmount = Amounts.parseOrThrow(
- backupWg.instructed_amount,
- );
- await tx.withdrawalGroups.put({
- withdrawalGroupId: backupWg.withdrawal_group_id,
- exchangeBaseUrl: backupWg.exchange_base_url,
- instructedAmount: Amounts.stringify(instructedAmount),
- secretSeed: backupWg.secret_seed,
- denomsSel: await getDenomSelStateFromBackup(
- tx,
- instructedAmount.currency,
- backupWg.exchange_base_url,
- backupWg.selected_denoms,
- ),
- denomSelUid: backupWg.selected_denoms_uid,
- rawWithdrawalAmount: Amounts.stringify(
- backupWg.raw_withdrawal_amount,
- ),
- effectiveWithdrawalAmount: Amounts.stringify(
- backupWg.effective_withdrawal_amount,
- ),
- reservePriv: backupWg.reserve_priv,
- reservePub,
- status: backupWg.timestamp_finish
- ? WithdrawalGroupStatus.Finished
- : WithdrawalGroupStatus.PendingQueryingStatus, // FIXME!
- timestampStart: backupWg.timestamp_created,
- wgInfo,
- restrictAge: backupWg.restrict_age,
- senderWire: undefined, // FIXME!
- timestampFinish: backupWg.timestamp_finish,
- });
- }
-
- for (const backupPurchase of backupBlob.purchases) {
- const ts = constructTombstone({
- tag: TombstoneTag.DeletePayment,
- proposalId: backupPurchase.proposal_id,
- });
- if (tombstoneSet.has(ts)) {
- continue;
- }
- const existingPurchase = await tx.purchases.get(
- backupPurchase.proposal_id,
- );
- let proposalStatus: PurchaseStatus;
- switch (backupPurchase.proposal_status) {
- case BackupProposalStatus.Paid:
- proposalStatus = PurchaseStatus.Done;
- break;
- case BackupProposalStatus.Shared:
- proposalStatus = PurchaseStatus.DialogShared;
- break;
- case BackupProposalStatus.Proposed:
- proposalStatus = PurchaseStatus.DialogProposed;
- break;
- case BackupProposalStatus.PermanentlyFailed:
- proposalStatus = PurchaseStatus.AbortedIncompletePayment;
- break;
- case BackupProposalStatus.Refused:
- proposalStatus = PurchaseStatus.AbortedProposalRefused;
- break;
- case BackupProposalStatus.Repurchase:
- proposalStatus = PurchaseStatus.RepurchaseDetected;
- break;
- default: {
- const error: never = backupPurchase.proposal_status;
- throw Error(`backup status ${error} is not handled`);
- }
- }
- 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.coins.get(backupRefund.coin_pub);
- // checkBackupInvariant(!!coin);
- // const denom = await tx.denominations.get([
- // coin.exchangeBaseUrl,
- // coin.denomPubHash,
- // ]);
- // checkBackupInvariant(!!denom);
- // const common = {
- // coinPub: backupRefund.coin_pub,
- // executionTime: backupRefund.execution_time,
- // obtainedTime: backupRefund.obtained_time,
- // refundAmount: Amounts.stringify(backupRefund.refund_amount),
- // refundFee: Amounts.stringify(denom.fees.feeRefund),
- // rtransactionId: backupRefund.rtransaction_id,
- // totalRefreshCostBound: Amounts.stringify(
- // backupRefund.total_refresh_cost_bound,
- // ),
- // };
- // switch (backupRefund.type) {
- // case BackupRefundState.Applied:
- // refunds[key] = {
- // type: RefundState.Applied,
- // ...common,
- // };
- // break;
- // case BackupRefundState.Failed:
- // refunds[key] = {
- // type: RefundState.Failed,
- // ...common,
- // };
- // break;
- // case BackupRefundState.Pending:
- // refunds[key] = {
- // type: RefundState.Pending,
- // ...common,
- // };
- // break;
- // }
- // }
- const parsedContractTerms = codecForMerchantContractTerms().decode(
- backupPurchase.contract_terms_raw,
- );
- const amount = Amounts.parseOrThrow(parsedContractTerms.amount);
- const contractTermsHash =
- cryptoComp.proposalIdToContractTermsHash[
- backupPurchase.proposal_id
- ];
- let maxWireFee: AmountJson;
- if (parsedContractTerms.max_wire_fee) {
- maxWireFee = Amounts.parseOrThrow(parsedContractTerms.max_wire_fee);
- } else {
- maxWireFee = Amounts.zeroOfCurrency(amount.currency);
- }
- const download: ProposalDownloadInfo = {
- contractTermsHash,
- contractTermsMerchantSig: backupPurchase.merchant_sig!,
- currency: amount.currency,
- fulfillmentUrl: backupPurchase.contract_terms_raw.fulfillment_url,
- };
-
- const contractData = extractContractData(
- backupPurchase.contract_terms_raw,
- contractTermsHash,
- download.contractTermsMerchantSig,
- );
-
- let payInfo: PurchasePayInfo | undefined = undefined;
- if (backupPurchase.pay_info) {
- payInfo = {
- payCoinSelection: await recoverPayCoinSelection(
- tx,
- contractData,
- backupPurchase.pay_info,
- ),
- payCoinSelectionUid: backupPurchase.pay_info.pay_coins_uid,
- totalPayCost: Amounts.stringify(
- backupPurchase.pay_info.total_pay_cost,
- ),
- };
- }
-
- await tx.purchases.put({
- proposalId: backupPurchase.proposal_id,
- noncePriv: backupPurchase.nonce_priv,
- noncePub:
- cryptoComp.proposalNoncePrivToPub[backupPurchase.nonce_priv],
- autoRefundDeadline: TalerProtocolTimestamp.never(),
- timestampAccept: backupPurchase.timestamp_accepted,
- timestampFirstSuccessfulPay:
- backupPurchase.timestamp_first_successful_pay,
- timestampLastRefundStatus: undefined,
- merchantPaySig: backupPurchase.merchant_pay_sig,
- posConfirmation: backupPurchase.pos_confirmation,
- lastSessionId: undefined,
- download,
- //refunds,
- claimToken: backupPurchase.claim_token,
- downloadSessionId: backupPurchase.download_session_id,
- merchantBaseUrl: backupPurchase.merchant_base_url,
- orderId: backupPurchase.order_id,
- payInfo,
- refundAmountAwaiting: undefined,
- repurchaseProposalId: backupPurchase.repurchase_proposal_id,
- purchaseStatus: proposalStatus,
- timestamp: backupPurchase.timestamp_proposed,
- shared: backupPurchase.shared,
- });
- }
- }
-
- for (const backupRefreshGroup of backupBlob.refresh_groups) {
- const ts = constructTombstone({
- tag: TombstoneTag.DeleteRefreshGroup,
- refreshGroupId: backupRefreshGroup.refresh_group_id,
- });
- if (tombstoneSet.has(ts)) {
- continue;
- }
- const existingRg = await tx.refreshGroups.get(
- backupRefreshGroup.refresh_group_id,
- );
- if (!existingRg) {
- let reason: RefreshReason;
- switch (backupRefreshGroup.reason) {
- case BackupRefreshReason.AbortPay:
- reason = RefreshReason.AbortPay;
- break;
- case BackupRefreshReason.BackupRestored:
- reason = RefreshReason.BackupRestored;
- break;
- case BackupRefreshReason.Manual:
- reason = RefreshReason.Manual;
- break;
- case BackupRefreshReason.Pay:
- reason = RefreshReason.PayMerchant;
- break;
- case BackupRefreshReason.Recoup:
- reason = RefreshReason.Recoup;
- break;
- case BackupRefreshReason.Refund:
- reason = RefreshReason.Refund;
- break;
- case BackupRefreshReason.Scheduled:
- reason = RefreshReason.Scheduled;
- break;
- }
- const refreshSessionPerCoin: (RefreshSessionRecord | undefined)[] =
- [];
- for (const oldCoin of backupRefreshGroup.old_coins) {
- const c = await tx.coins.get(oldCoin.coin_pub);
- checkBackupInvariant(!!c);
- const d = await tx.denominations.get([
- c.exchangeBaseUrl,
- c.denomPubHash,
- ]);
- checkBackupInvariant(!!d);
-
- if (oldCoin.refresh_session) {
- const denomSel = await getDenomSelStateFromBackup(
- tx,
- d.currency,
- c.exchangeBaseUrl,
- oldCoin.refresh_session.new_denoms,
- );
- refreshSessionPerCoin.push({
- sessionSecretSeed: oldCoin.refresh_session.session_secret_seed,
- norevealIndex: oldCoin.refresh_session.noreveal_index,
- newDenoms: oldCoin.refresh_session.new_denoms.map((x) => ({
- count: x.count,
- denomPubHash: x.denom_pub_hash,
- })),
- amountRefreshOutput: Amounts.stringify(denomSel.totalCoinValue),
- });
- } else {
- refreshSessionPerCoin.push(undefined);
- }
- }
- await tx.refreshGroups.put({
- timestampFinished: backupRefreshGroup.timestamp_finish,
- timestampCreated: backupRefreshGroup.timestamp_created,
- refreshGroupId: backupRefreshGroup.refresh_group_id,
- currency: Amounts.currencyOf(
- backupRefreshGroup.old_coins[0].input_amount,
- ),
- reason,
- lastErrorPerCoin: {},
- oldCoinPubs: backupRefreshGroup.old_coins.map((x) => x.coin_pub),
- statusPerCoin: backupRefreshGroup.old_coins.map((x) =>
- x.finished
- ? RefreshCoinStatus.Finished
- : RefreshCoinStatus.Pending,
- ),
- operationStatus: backupRefreshGroup.timestamp_finish
- ? RefreshOperationStatus.Finished
- : RefreshOperationStatus.Pending,
- inputPerCoin: backupRefreshGroup.old_coins.map(
- (x) => x.input_amount,
- ),
- estimatedOutputPerCoin: backupRefreshGroup.old_coins.map(
- (x) => x.estimated_output_amount,
- ),
- refreshSessionPerCoin,
- });
- }
- }
-
- for (const backupTip of backupBlob.tips) {
- const ts = constructTombstone({
- tag: TombstoneTag.DeleteReward,
- walletTipId: backupTip.wallet_tip_id,
- });
- if (tombstoneSet.has(ts)) {
- continue;
- }
- const existingTip = await tx.rewards.get(backupTip.wallet_tip_id);
- if (!existingTip) {
- const tipAmountRaw = Amounts.parseOrThrow(backupTip.tip_amount_raw);
- const denomsSel = await getDenomSelStateFromBackup(
- tx,
- tipAmountRaw.currency,
- backupTip.exchange_base_url,
- backupTip.selected_denoms,
- );
- await tx.rewards.put({
- acceptedTimestamp: backupTip.timestamp_accepted,
- createdTimestamp: backupTip.timestamp_created,
- denomsSel,
- next_url: backupTip.next_url,
- exchangeBaseUrl: backupTip.exchange_base_url,
- merchantBaseUrl: backupTip.exchange_base_url,
- merchantRewardId: backupTip.merchant_tip_id,
- pickedUpTimestamp: backupTip.timestamp_finished,
- secretSeed: backupTip.secret_seed,
- rewardAmountEffective: Amounts.stringify(denomsSel.totalCoinValue),
- rewardAmountRaw: Amounts.stringify(tipAmountRaw),
- rewardExpiration: backupTip.timestamp_expiration,
- walletRewardId: backupTip.wallet_tip_id,
- denomSelUid: backupTip.selected_denoms_uid,
- status: RewardRecordStatus.Done, // FIXME!
- });
- }
- }
-
- // We now process tombstones.
- // The import code above should already prevent
- // importing things that are tombstoned,
- // but we do tombstone processing last just to be sure.
-
- for (const tombstone of tombstoneSet) {
- const [type, ...rest] = tombstone.split(":");
- if (type === TombstoneTag.DeleteDepositGroup) {
- await tx.depositGroups.delete(rest[0]);
- } else if (type === TombstoneTag.DeletePayment) {
- await tx.purchases.delete(rest[0]);
- } else if (type === TombstoneTag.DeleteRefreshGroup) {
- 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.DeleteReward) {
- await tx.rewards.delete(rest[0]);
- } else if (type === TombstoneTag.DeleteWithdrawalGroup) {
- 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 e35765165..a5e8dbd42 100644
--- a/packages/taler-wallet-core/src/operations/backup/index.ts
+++ b/packages/taler-wallet-core/src/operations/backup/index.ts
@@ -43,7 +43,6 @@ import {
TalerErrorDetail,
TalerPreciseTimestamp,
URL,
- WalletBackupContentV1,
buildCodecForObject,
buildCodecForUnion,
bytesToString,
@@ -99,9 +98,8 @@ import {
TaskIdentifiers,
} from "../common.js";
import { checkPaymentByProposalId, preparePayForUri } from "../pay-merchant.js";
-import { exportBackup } from "./export.js";
-import { BackupCryptoPrecomputedData, importBackup } from "./import.js";
-import { getWalletBackupState, provideBackupState } from "./state.js";
+import { WalletStoresV1 } from "../../db.js";
+import { GetReadOnlyAccess } from "../../util/query.js";
const logger = new Logger("operations/backup.ts");
@@ -131,7 +129,7 @@ const magic = "TLRWBK01";
*/
export async function encryptBackup(
config: WalletBackupConfState,
- blob: WalletBackupContentV1,
+ blob: any,
): Promise<Uint8Array> {
const chunks: Uint8Array[] = [];
chunks.push(stringToBytes(magic));
@@ -150,64 +148,6 @@ export async function encryptBackup(
return concatArrays(chunks);
}
-/**
- * Compute cryptographic values for a backup blob.
- *
- * FIXME: Take data that we already know from the DB.
- * FIXME: Move computations into crypto worker.
- */
-async function computeBackupCryptoData(
- cryptoApi: TalerCryptoInterface,
- backupContent: WalletBackupContentV1,
-): Promise<BackupCryptoPrecomputedData> {
- const cryptoData: BackupCryptoPrecomputedData = {
- coinPrivToCompletedCoin: {},
- rsaDenomPubToHash: {},
- proposalIdToContractTermsHash: {},
- proposalNoncePrivToPub: {},
- reservePrivToPub: {},
- };
- for (const backupExchangeDetails of backupContent.exchange_details) {
- for (const backupDenom of backupExchangeDetails.denominations) {
- if (backupDenom.denom_pub.cipher !== DenomKeyType.Rsa) {
- throw Error("unsupported cipher");
- }
- for (const backupCoin of backupDenom.coins) {
- const coinPub = encodeCrock(
- eddsaGetPublic(decodeCrock(backupCoin.coin_priv)),
- );
- const blindedCoin = rsaBlind(
- hash(decodeCrock(backupCoin.coin_priv)),
- decodeCrock(backupCoin.blinding_key),
- decodeCrock(backupDenom.denom_pub.rsa_public_key),
- );
- cryptoData.coinPrivToCompletedCoin[backupCoin.coin_priv] = {
- coinEvHash: encodeCrock(hash(blindedCoin)),
- coinPub,
- };
- }
- cryptoData.rsaDenomPubToHash[backupDenom.denom_pub.rsa_public_key] =
- encodeCrock(hashDenomPub(backupDenom.denom_pub));
- }
- }
- for (const backupWg of backupContent.withdrawal_groups) {
- cryptoData.reservePrivToPub[backupWg.reserve_priv] = encodeCrock(
- eddsaGetPublic(decodeCrock(backupWg.reserve_priv)),
- );
- }
- for (const purch of backupContent.purchases) {
- if (!purch.contract_terms_raw) continue;
- const { h: contractTermsHash } = await cryptoApi.hashString({
- str: canonicalJson(purch.contract_terms_raw),
- });
- const noncePub = encodeCrock(eddsaGetPublic(decodeCrock(purch.nonce_priv)));
- cryptoData.proposalNoncePrivToPub[purch.nonce_priv] = noncePub;
- cryptoData.proposalIdToContractTermsHash[purch.proposal_id] =
- contractTermsHash;
- }
- return cryptoData;
-}
-
function deriveAccountKeyPair(
bc: WalletBackupConfState,
providerUrl: string,
@@ -262,7 +202,9 @@ async function runBackupCycleForProvider(
return TaskRunResult.finished();
}
- const backupJson = await exportBackup(ws);
+ //const backupJson = await exportBackup(ws);
+ // FIXME: re-implement backup
+ const backupJson = {};
const backupConfig = await provideBackupState(ws);
const encBackup = await encryptBackup(backupConfig, backupJson);
const currentBackupHash = hash(encBackup);
@@ -441,9 +383,9 @@ async function runBackupCycleForProvider(
logger.info("conflicting backup found");
const backupEnc = new Uint8Array(await resp.bytes());
const backupConfig = await provideBackupState(ws);
- const blob = await decryptBackup(backupConfig, backupEnc);
- const cryptoData = await computeBackupCryptoData(ws.cryptoApi, blob);
- await importBackup(ws, blob, cryptoData);
+ // 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) => {
@@ -789,18 +731,6 @@ export interface BackupInfo {
providers: ProviderInfo[];
}
-export async function importBackupPlain(
- ws: InternalWalletState,
- blob: any,
-): Promise<void> {
- // FIXME: parse
- const backup: WalletBackupContentV1 = blob;
-
- const cryptoData = await computeBackupCryptoData(ws.cryptoApi, backup);
-
- await importBackup(ws, blob, cryptoData);
-}
-
export enum ProviderPaymentType {
Unpaid = "unpaid",
Pending = "pending",
@@ -1036,23 +966,10 @@ export async function loadBackupRecovery(
}
}
-export async function exportBackupEncrypted(
- ws: InternalWalletState,
-): Promise<Uint8Array> {
- await provideBackupState(ws);
- const blob = await exportBackup(ws);
- const bs = await ws.db
- .mktx((x) => [x.config])
- .runReadOnly(async (tx) => {
- return await getWalletBackupState(ws, tx);
- });
- return encryptBackup(bs, blob);
-}
-
export async function decryptBackup(
backupConfig: WalletBackupConfState,
data: Uint8Array,
-): Promise<WalletBackupContentV1> {
+): Promise<any> {
const rMagic = bytesToString(data.slice(0, 8));
if (rMagic != magic) {
throw Error("invalid backup file (magic tag mismatch)");
@@ -1068,12 +985,85 @@ export async function decryptBackup(
return JSON.parse(bytesToString(gunzipSync(dataCompressed)));
}
-export async function importBackupEncrypted(
+export async function provideBackupState(
ws: InternalWalletState,
- data: Uint8Array,
+): Promise<WalletBackupConfState> {
+ const bs: ConfigRecord | undefined = await ws.db
+ .mktx((stores) => [stores.config])
+ .runReadOnly(async (tx) => {
+ return await tx.config.get(ConfigRecordKey.WalletBackupState);
+ });
+ if (bs) {
+ checkDbInvariant(bs.key === ConfigRecordKey.WalletBackupState);
+ return bs.value;
+ }
+ // We need to generate the key outside of the transaction
+ // due to how IndexedDB works.
+ const k = await ws.cryptoApi.createEddsaKeypair({});
+ const d = getRandomBytes(5);
+ // 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;
+ });
+}
+
+export async function getWalletBackupState(
+ ws: InternalWalletState,
+ tx: GetReadOnlyAccess<{ config: typeof WalletStoresV1.config }>,
+): Promise<WalletBackupConfState> {
+ const bs = await tx.config.get(ConfigRecordKey.WalletBackupState);
+ checkDbInvariant(!!bs, "wallet backup state should be in DB");
+ checkDbInvariant(bs.key === ConfigRecordKey.WalletBackupState);
+ return bs.value;
+}
+
+export async function setWalletDeviceId(
+ ws: InternalWalletState,
+ deviceId: string,
): Promise<void> {
- const backupConfig = await provideBackupState(ws);
- const blob = await decryptBackup(backupConfig, data);
- const cryptoData = await computeBackupCryptoData(ws.cryptoApi, blob);
- await importBackup(ws, blob, cryptoData);
+ 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);
+ });
+}
+
+export async function getWalletDeviceId(
+ ws: InternalWalletState,
+): Promise<string> {
+ const bs = await provideBackupState(ws);
+ return bs.deviceId;
}
diff --git a/packages/taler-wallet-core/src/operations/backup/state.ts b/packages/taler-wallet-core/src/operations/backup/state.ts
index fa632f44c..d02ead783 100644
--- a/packages/taler-wallet-core/src/operations/backup/state.ts
+++ b/packages/taler-wallet-core/src/operations/backup/state.ts
@@ -14,96 +14,4 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
-import { encodeCrock, getRandomBytes } from "@gnu-taler/taler-util";
-import {
- ConfigRecord,
- ConfigRecordKey,
- WalletBackupConfState,
- WalletStoresV1,
-} from "../../db.js";
-import { checkDbInvariant } from "../../util/invariants.js";
-import { GetReadOnlyAccess } from "../../util/query.js";
-import { InternalWalletState } from "../../internal-wallet-state.js";
-export async function provideBackupState(
- ws: InternalWalletState,
-): Promise<WalletBackupConfState> {
- const bs: ConfigRecord | undefined = await ws.db
- .mktx((stores) => [stores.config])
- .runReadOnly(async (tx) => {
- return await tx.config.get(ConfigRecordKey.WalletBackupState);
- });
- if (bs) {
- checkDbInvariant(bs.key === ConfigRecordKey.WalletBackupState);
- return bs.value;
- }
- // We need to generate the key outside of the transaction
- // due to how IndexedDB works.
- const k = await ws.cryptoApi.createEddsaKeypair({});
- const d = getRandomBytes(5);
- // 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;
- });
-}
-
-export async function getWalletBackupState(
- ws: InternalWalletState,
- tx: GetReadOnlyAccess<{ config: typeof WalletStoresV1.config }>,
-): Promise<WalletBackupConfState> {
- const bs = await tx.config.get(ConfigRecordKey.WalletBackupState);
- checkDbInvariant(!!bs, "wallet backup state should be in DB");
- checkDbInvariant(bs.key === ConfigRecordKey.WalletBackupState);
- return bs.value;
-}
-
-export async function setWalletDeviceId(
- ws: InternalWalletState,
- 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);
- });
-}
-
-export async function getWalletDeviceId(
- ws: InternalWalletState,
-): Promise<string> {
- const bs = await provideBackupState(ws);
- return bs.deviceId;
-}
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts
index 06ccdf6f3..4d9d40c43 100644
--- a/packages/taler-wallet-core/src/wallet-api-types.ts
+++ b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -106,7 +106,6 @@ import {
UserAttentionsResponse,
ValidateIbanRequest,
ValidateIbanResponse,
- WalletBackupContentV1,
WalletCoreVersion,
WalletCurrencyInfo,
WithdrawFakebankRequest,
@@ -116,6 +115,10 @@ import {
SharePaymentResult,
GetCurrencyInfoRequest,
GetCurrencyInfoResponse,
+ StoredBackupList,
+ CreateStoredBackupResponse,
+ RecoverStoredBackupRequest,
+ DeleteStoredBackupRequest,
} from "@gnu-taler/taler-util";
import { AuditorTrustRecord, WalletContractData } from "./db.js";
import {
@@ -195,7 +198,6 @@ export enum WalletApiOperation {
GenerateDepositGroupTxId = "generateDepositGroupTxId",
CreateDepositGroup = "createDepositGroup",
SetWalletDeviceId = "setWalletDeviceId",
- ExportBackupPlain = "exportBackupPlain",
WithdrawFakebank = "withdrawFakebank",
ImportDb = "importDb",
ExportDb = "exportDb",
@@ -214,6 +216,10 @@ export enum WalletApiOperation {
TestingWaitTransactionsFinal = "testingWaitTransactionsFinal",
TestingWaitRefreshesFinal = "testingWaitRefreshesFinal",
GetScopedCurrencyInfo = "getScopedCurrencyInfo",
+ ListStoredBackups = "listStoredBackups",
+ CreateStoredBackup = "createStoredBackup",
+ DeleteStoredBackup = "deleteStoredBackup",
+ RecoverStoredBackup = "recoverStoredBackup",
}
// group: Initialization
@@ -713,13 +719,28 @@ export type SetWalletDeviceIdOp = {
response: EmptyObject;
};
-/**
- * Export a backup JSON, mostly useful for testing.
- */
-export type ExportBackupPlainOp = {
- op: WalletApiOperation.ExportBackupPlain;
+export type ListStoredBackupsOp = {
+ op: WalletApiOperation.ListStoredBackups;
+ request: EmptyObject;
+ response: StoredBackupList;
+};
+
+export type CreateStoredBackupsOp = {
+ op: WalletApiOperation.CreateStoredBackup;
request: EmptyObject;
- response: WalletBackupContentV1;
+ response: CreateStoredBackupResponse;
+};
+
+export type RecoverStoredBackupsOp = {
+ op: WalletApiOperation.RecoverStoredBackup;
+ request: RecoverStoredBackupRequest;
+ response: EmptyObject;
+};
+
+export type DeleteStoredBackupOp = {
+ op: WalletApiOperation.DeleteStoredBackup;
+ request: DeleteStoredBackupRequest;
+ response: EmptyObject;
};
// group: Peer Payments
@@ -1062,7 +1083,6 @@ export type WalletOperations = {
[WalletApiOperation.GenerateDepositGroupTxId]: GenerateDepositGroupTxIdOp;
[WalletApiOperation.CreateDepositGroup]: CreateDepositGroupOp;
[WalletApiOperation.SetWalletDeviceId]: SetWalletDeviceIdOp;
- [WalletApiOperation.ExportBackupPlain]: ExportBackupPlainOp;
[WalletApiOperation.ExportBackupRecovery]: ExportBackupRecoveryOp;
[WalletApiOperation.ImportBackupRecovery]: ImportBackupRecoveryOp;
[WalletApiOperation.RunBackupCycle]: RunBackupCycleOp;
@@ -1092,6 +1112,10 @@ export type WalletOperations = {
[WalletApiOperation.TestingWaitTransactionsFinal]: TestingWaitTransactionsFinal;
[WalletApiOperation.TestingWaitRefreshesFinal]: TestingWaitRefreshesFinal;
[WalletApiOperation.GetScopedCurrencyInfo]: GetScopedCurrencyInfoOp;
+ [WalletApiOperation.CreateStoredBackup]: CreateStoredBackupsOp;
+ [WalletApiOperation.ListStoredBackups]: ListStoredBackupsOp;
+ [WalletApiOperation.DeleteStoredBackup]: DeleteStoredBackupOp;
+ [WalletApiOperation.RecoverStoredBackup]: RecoverStoredBackupsOp;
};
export type WalletCoreRequestType<
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index 9f754ed69..283539a08 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -120,6 +120,7 @@ import {
codecForSharePaymentRequest,
GetCurrencyInfoResponse,
codecForGetCurrencyInfoRequest,
+ CreateStoredBackupResponse,
} from "@gnu-taler/taler-util";
import {
HttpRequestLibrary,
@@ -139,6 +140,7 @@ import {
clearDatabase,
exportDb,
importDb,
+ openStoredBackupsDatabase,
openTalerDatabase,
} from "./db.js";
import { DevExperimentHttpLib, applyDevExperiment } from "./dev-experiments.js";
@@ -158,7 +160,6 @@ import {
getUserAttentionsUnreadCount,
markAttentionRequestAsRead,
} from "./operations/attention.js";
-import { exportBackup } from "./operations/backup/export.js";
import {
addBackupProvider,
codecForAddBackupProviderRequest,
@@ -166,13 +167,12 @@ import {
codecForRunBackupCycle,
getBackupInfo,
getBackupRecovery,
- importBackupPlain,
loadBackupRecovery,
processBackupForProvider,
removeBackupProvider,
runBackupCycle,
+ setWalletDeviceId,
} from "./operations/backup/index.js";
-import { setWalletDeviceId } from "./operations/backup/state.js";
import { getBalanceDetail, getBalances } from "./operations/balance.js";
import {
TaskIdentifiers,
@@ -1025,6 +1025,17 @@ export async function getClientFromWalletState(
return client;
}
+async function createStoredBackup(
+ ws: InternalWalletState,
+): Promise<CreateStoredBackupResponse> {
+ const backup = await exportDb(ws.idb);
+ const backupsDb = await openStoredBackupsDatabase(ws.idb);
+ const name = `backup-${new Date().getTime()}`;
+ backupsDb.mktxAll().runReadWrite(async (tx) => {});
+
+ throw Error("not implemented");
+}
+
/**
* Implementation of the "wallet-core" API.
*/
@@ -1041,6 +1052,14 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
// FIXME: Can we make this more type-safe by using the request/response type
// definitions we already have?
switch (operation) {
+ case WalletApiOperation.CreateStoredBackup:
+ return createStoredBackup(ws);
+ case WalletApiOperation.DeleteStoredBackup:
+ return {};
+ case WalletApiOperation.ListStoredBackups:
+ return {};
+ case WalletApiOperation.RecoverStoredBackup:
+ return {};
case WalletApiOperation.InitWallet: {
logger.trace("initializing wallet");
ws.initCalled = true;
@@ -1382,9 +1401,6 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
const req = codecForAcceptTipRequest().decode(payload);
return await acceptTip(ws, req.walletRewardId);
}
- case WalletApiOperation.ExportBackupPlain: {
- return exportBackup(ws);
- }
case WalletApiOperation.AddBackupProvider: {
const req = codecForAddBackupProviderRequest().decode(payload);
return await addBackupProvider(ws, req);
@@ -1535,9 +1551,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
await clearDatabase(ws.db.idbHandle());
return {};
case WalletApiOperation.Recycle: {
- const backup = await exportBackup(ws);
- await clearDatabase(ws.db.idbHandle());
- await importBackupPlain(ws, backup);
+ throw Error("not implemented");
return {};
}
case WalletApiOperation.ExportDb: {