implement import of backup recovery document
This commit is contained in:
parent
324f44ae69
commit
8921a5e8f2
@ -36,6 +36,8 @@ import {
|
|||||||
NodeThreadCryptoWorkerFactory,
|
NodeThreadCryptoWorkerFactory,
|
||||||
CryptoApi,
|
CryptoApi,
|
||||||
rsaBlind,
|
rsaBlind,
|
||||||
|
RecoveryMergeStrategy,
|
||||||
|
stringToBytes,
|
||||||
} from "taler-wallet-core";
|
} from "taler-wallet-core";
|
||||||
import * as clk from "./clk";
|
import * as clk from "./clk";
|
||||||
import { deepStrictEqual } from "assert";
|
import { deepStrictEqual } from "assert";
|
||||||
@ -453,19 +455,49 @@ backupCli.subcommand("run", "run").action(async (args) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
backupCli.subcommand("status", "status").action(async (args) => {
|
||||||
|
await withWallet(args, async (wallet) => {
|
||||||
|
const status = await wallet.getBackupStatus();
|
||||||
|
console.log(JSON.stringify(status, undefined, 2));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
backupCli
|
backupCli
|
||||||
.subcommand("recoveryLoad", "load-recovery")
|
.subcommand("recoveryLoad", "load-recovery")
|
||||||
.action(async (args) => {});
|
.maybeOption("strategy", ["--strategy"], clk.STRING, {
|
||||||
|
help:
|
||||||
backupCli.subcommand("status", "status").action(async (args) => {});
|
"Strategy for resolving a conflict with the existing wallet key ('theirs' or 'ours')",
|
||||||
|
})
|
||||||
|
.action(async (args) => {
|
||||||
|
await withWallet(args, async (wallet) => {
|
||||||
|
const data = JSON.parse(await read(process.stdin));
|
||||||
|
let strategy: RecoveryMergeStrategy | undefined;
|
||||||
|
const stratStr = args.recoveryLoad.strategy;
|
||||||
|
if (stratStr) {
|
||||||
|
if (stratStr === "theirs") {
|
||||||
|
strategy = RecoveryMergeStrategy.Theirs;
|
||||||
|
} else if (stratStr === "ours") {
|
||||||
|
strategy = RecoveryMergeStrategy.Theirs;
|
||||||
|
} else {
|
||||||
|
throw Error("invalid recovery strategy");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await wallet.loadBackupRecovery({
|
||||||
|
recovery: data,
|
||||||
|
strategy,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
backupCli
|
backupCli
|
||||||
.subcommand("addProvider", "add-provider")
|
.subcommand("addProvider", "add-provider")
|
||||||
.requiredArgument("url", clk.STRING)
|
.requiredArgument("url", clk.STRING)
|
||||||
|
.flag("activate", ["--activate"])
|
||||||
.action(async (args) => {
|
.action(async (args) => {
|
||||||
await withWallet(args, async (wallet) => {
|
await withWallet(args, async (wallet) => {
|
||||||
wallet.addBackupProvider({
|
wallet.addBackupProvider({
|
||||||
backupProviderBaseUrl: args.addProvider.url,
|
backupProviderBaseUrl: args.addProvider.url,
|
||||||
|
activate: args.addProvider.activate,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -27,6 +27,7 @@
|
|||||||
import { InternalWalletState } from "./state";
|
import { InternalWalletState } from "./state";
|
||||||
import {
|
import {
|
||||||
BackupBackupProvider,
|
BackupBackupProvider,
|
||||||
|
BackupBackupProviderTerms,
|
||||||
BackupCoin,
|
BackupCoin,
|
||||||
BackupCoinSource,
|
BackupCoinSource,
|
||||||
BackupCoinSourceType,
|
BackupCoinSourceType,
|
||||||
@ -52,6 +53,7 @@ import {
|
|||||||
import { TransactionHandle } from "../util/query";
|
import { TransactionHandle } from "../util/query";
|
||||||
import {
|
import {
|
||||||
AbortStatus,
|
AbortStatus,
|
||||||
|
BackupProviderStatus,
|
||||||
CoinSource,
|
CoinSource,
|
||||||
CoinSourceType,
|
CoinSourceType,
|
||||||
CoinStatus,
|
CoinStatus,
|
||||||
@ -110,6 +112,8 @@ import { initRetryInfo } from "../util/retries";
|
|||||||
import {
|
import {
|
||||||
ConfirmPayResultType,
|
ConfirmPayResultType,
|
||||||
PreparePayResultType,
|
PreparePayResultType,
|
||||||
|
RecoveryLoadRequest,
|
||||||
|
RecoveryMergeStrategy,
|
||||||
RefreshReason,
|
RefreshReason,
|
||||||
} from "../types/walletTypes";
|
} from "../types/walletTypes";
|
||||||
import { CryptoApi } from "../crypto/workers/cryptoApi";
|
import { CryptoApi } from "../crypto/workers/cryptoApi";
|
||||||
@ -303,12 +307,18 @@ export async function exportBackup(
|
|||||||
});
|
});
|
||||||
|
|
||||||
await tx.iter(Stores.backupProviders).forEach((bp) => {
|
await tx.iter(Stores.backupProviders).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({
|
backupBackupProviders.push({
|
||||||
annual_fee: Amounts.stringify(bp.annualFee),
|
terms,
|
||||||
base_url: canonicalizeBaseUrl(bp.baseUrl),
|
base_url: canonicalizeBaseUrl(bp.baseUrl),
|
||||||
pay_proposal_ids: [],
|
pay_proposal_ids: bp.paymentProposalIds,
|
||||||
storage_limit_in_megabytes: bp.storageLimitInMegabytes,
|
|
||||||
supported_protocol_version: bp.supportedProtocolVersion,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1256,7 +1266,13 @@ export async function importBackup(
|
|||||||
case "abort-refund":
|
case "abort-refund":
|
||||||
abortStatus = AbortStatus.AbortRefund;
|
abortStatus = AbortStatus.AbortRefund;
|
||||||
break;
|
break;
|
||||||
|
case undefined:
|
||||||
|
abortStatus = AbortStatus.None;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
|
logger.warn(
|
||||||
|
`got backup purchase abort_status ${j2s(backupPurchase.abort_status)}`,
|
||||||
|
);
|
||||||
throw Error("not reachable");
|
throw Error("not reachable");
|
||||||
}
|
}
|
||||||
const parsedContractTerms = codecForContractTerms().decode(
|
const parsedContractTerms = codecForContractTerms().decode(
|
||||||
@ -1484,11 +1500,9 @@ function deriveBlobSecret(bc: WalletBackupConfState): Uint8Array {
|
|||||||
*/
|
*/
|
||||||
export async function runBackupCycle(ws: InternalWalletState): Promise<void> {
|
export async function runBackupCycle(ws: InternalWalletState): Promise<void> {
|
||||||
const providers = await ws.db.iter(Stores.backupProviders).toArray();
|
const providers = await ws.db.iter(Stores.backupProviders).toArray();
|
||||||
const backupConfig = await provideBackupState(ws);
|
|
||||||
|
|
||||||
logger.trace("got backup providers", providers);
|
logger.trace("got backup providers", providers);
|
||||||
const backupJson = await exportBackup(ws);
|
const backupJson = await exportBackup(ws);
|
||||||
|
const backupConfig = await provideBackupState(ws);
|
||||||
const encBackup = await encryptBackup(backupConfig, backupJson);
|
const encBackup = await encryptBackup(backupConfig, backupJson);
|
||||||
|
|
||||||
const currentBackupHash = hash(encBackup);
|
const currentBackupHash = hash(encBackup);
|
||||||
@ -1549,6 +1563,15 @@ export async function runBackupCycle(ws: InternalWalletState): Promise<void> {
|
|||||||
if (!proposalId) {
|
if (!proposalId) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
const p = proposalId;
|
||||||
|
await ws.db.runWithWriteTransaction([Stores.backupProviders], async (tx) => {
|
||||||
|
const provRec = await tx.get(Stores.backupProviders, provider.baseUrl);
|
||||||
|
checkDbInvariant(!!provRec);
|
||||||
|
const ids = new Set(provRec.paymentProposalIds)
|
||||||
|
ids.add(p);
|
||||||
|
provRec.paymentProposalIds = Array.from(ids);
|
||||||
|
await tx.put(Stores.backupProviders, provRec);
|
||||||
|
});
|
||||||
const confirmRes = await confirmPay(ws, proposalId);
|
const confirmRes = await confirmPay(ws, proposalId);
|
||||||
switch (confirmRes.type) {
|
switch (confirmRes.type) {
|
||||||
case ConfirmPayResultType.Pending:
|
case ConfirmPayResultType.Pending:
|
||||||
@ -1565,6 +1588,7 @@ export async function runBackupCycle(ws: InternalWalletState): Promise<void> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
prov.lastBackupHash = encodeCrock(currentBackupHash);
|
prov.lastBackupHash = encodeCrock(currentBackupHash);
|
||||||
|
prov.lastBackupTimestamp = getTimestampNow();
|
||||||
prov.lastBackupClock =
|
prov.lastBackupClock =
|
||||||
backupJson.clocks[backupJson.current_device_id];
|
backupJson.clocks[backupJson.current_device_id];
|
||||||
await tx.put(Stores.backupProviders, prov);
|
await tx.put(Stores.backupProviders, prov);
|
||||||
@ -1587,8 +1611,8 @@ export async function runBackupCycle(ws: InternalWalletState): Promise<void> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
prov.lastBackupHash = encodeCrock(hash(backupEnc));
|
prov.lastBackupHash = encodeCrock(hash(backupEnc));
|
||||||
prov.lastBackupClock =
|
prov.lastBackupClock = blob.clocks[blob.current_device_id];
|
||||||
blob.clocks[blob.current_device_id];
|
prov.lastBackupTimestamp = getTimestampNow();
|
||||||
await tx.put(Stores.backupProviders, prov);
|
await tx.put(Stores.backupProviders, prov);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -1620,6 +1644,11 @@ const codecForSyncTermsOfServiceResponse = (): Codec<
|
|||||||
|
|
||||||
export interface AddBackupProviderRequest {
|
export interface AddBackupProviderRequest {
|
||||||
backupProviderBaseUrl: string;
|
backupProviderBaseUrl: string;
|
||||||
|
/**
|
||||||
|
* Activate the provider. Should only be done after
|
||||||
|
* the user has reviewed the provider.
|
||||||
|
*/
|
||||||
|
activate?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const codecForAddBackupProviderRequest = (): Codec<
|
export const codecForAddBackupProviderRequest = (): Codec<
|
||||||
@ -1637,6 +1666,10 @@ export async function addBackupProvider(
|
|||||||
const canonUrl = canonicalizeBaseUrl(req.backupProviderBaseUrl);
|
const canonUrl = canonicalizeBaseUrl(req.backupProviderBaseUrl);
|
||||||
const oldProv = await ws.db.get(Stores.backupProviders, canonUrl);
|
const oldProv = await ws.db.get(Stores.backupProviders, canonUrl);
|
||||||
if (oldProv) {
|
if (oldProv) {
|
||||||
|
if (req.activate) {
|
||||||
|
oldProv.active = true;
|
||||||
|
await ws.db.put(Stores.backupProviders, oldProv);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const termsUrl = new URL("terms", canonUrl);
|
const termsUrl = new URL("terms", canonUrl);
|
||||||
@ -1646,11 +1679,14 @@ export async function addBackupProvider(
|
|||||||
codecForSyncTermsOfServiceResponse(),
|
codecForSyncTermsOfServiceResponse(),
|
||||||
);
|
);
|
||||||
await ws.db.put(Stores.backupProviders, {
|
await ws.db.put(Stores.backupProviders, {
|
||||||
active: true,
|
active: !!req.activate,
|
||||||
annualFee: terms.annual_fee,
|
terms: {
|
||||||
|
annualFee: terms.annual_fee,
|
||||||
|
storageLimitInMegabytes: terms.storage_limit_in_megabytes,
|
||||||
|
supportedProtocolVersion: terms.version,
|
||||||
|
},
|
||||||
|
paymentProposalIds: [],
|
||||||
baseUrl: canonUrl,
|
baseUrl: canonUrl,
|
||||||
storageLimitInMegabytes: terms.storage_limit_in_megabytes,
|
|
||||||
supportedProtocolVersion: terms.version,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1667,9 +1703,11 @@ export async function restoreFromRecoverySecret(): Promise<void> {}
|
|||||||
* as that's derived from the wallet root key.
|
* as that's derived from the wallet root key.
|
||||||
*/
|
*/
|
||||||
export interface ProviderInfo {
|
export interface ProviderInfo {
|
||||||
|
active: boolean;
|
||||||
syncProviderBaseUrl: string;
|
syncProviderBaseUrl: string;
|
||||||
lastRemoteClock: number;
|
lastRemoteClock?: number;
|
||||||
lastBackup?: Timestamp;
|
lastBackupTimestamp?: Timestamp;
|
||||||
|
paymentProposalIds: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BackupInfo {
|
export interface BackupInfo {
|
||||||
@ -1697,7 +1735,20 @@ export async function importBackupPlain(
|
|||||||
export async function getBackupInfo(
|
export async function getBackupInfo(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
): Promise<BackupInfo> {
|
): Promise<BackupInfo> {
|
||||||
throw Error("not implemented");
|
const backupConfig = await provideBackupState(ws);
|
||||||
|
const providers = await ws.db.iter(Stores.backupProviders).toArray();
|
||||||
|
return {
|
||||||
|
deviceId: backupConfig.deviceId,
|
||||||
|
lastLocalClock: backupConfig.clocks[backupConfig.deviceId],
|
||||||
|
walletRootPub: backupConfig.walletRootPub,
|
||||||
|
providers: providers.map((x) => ({
|
||||||
|
active: x.active,
|
||||||
|
lastRemoteClock: x.lastBackupClock,
|
||||||
|
syncProviderBaseUrl: x.baseUrl,
|
||||||
|
lastBackupTimestamp: x.lastBackupTimestamp,
|
||||||
|
paymentProposalIds: x.paymentProposalIds,
|
||||||
|
})),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BackupRecovery {
|
export interface BackupRecovery {
|
||||||
@ -1727,6 +1778,77 @@ export async function getBackupRecovery(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function backupRecoveryTheirs(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
br: BackupRecovery,
|
||||||
|
) {
|
||||||
|
await ws.db.runWithWriteTransaction(
|
||||||
|
[Stores.config, Stores.backupProviders],
|
||||||
|
async (tx) => {
|
||||||
|
let backupStateEntry:
|
||||||
|
| ConfigRecord<WalletBackupConfState>
|
||||||
|
| undefined = await tx.get(Stores.config, WALLET_BACKUP_STATE_KEY);
|
||||||
|
checkDbInvariant(!!backupStateEntry);
|
||||||
|
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.put(Stores.config, backupStateEntry);
|
||||||
|
for (const prov of br.providers) {
|
||||||
|
const existingProv = await tx.get(Stores.backupProviders, prov.url);
|
||||||
|
if (!existingProv) {
|
||||||
|
await tx.put(Stores.backupProviders, {
|
||||||
|
active: true,
|
||||||
|
baseUrl: prov.url,
|
||||||
|
paymentProposalIds: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const providers = await tx.iter(Stores.backupProviders).toArray();
|
||||||
|
for (const prov of providers) {
|
||||||
|
prov.lastBackupTimestamp = undefined;
|
||||||
|
prov.lastBackupHash = undefined;
|
||||||
|
prov.lastBackupClock = undefined;
|
||||||
|
await tx.put(Stores.backupProviders, prov);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function backupRecoveryOurs(ws: InternalWalletState, br: BackupRecovery) {
|
||||||
|
throw Error("not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function loadBackupRecovery(
|
||||||
|
ws: InternalWalletState,
|
||||||
|
br: RecoveryLoadRequest,
|
||||||
|
): Promise<void> {
|
||||||
|
const bs = await provideBackupState(ws);
|
||||||
|
const providers = await ws.db.iter(Stores.backupProviders).toArray();
|
||||||
|
let strategy = br.strategy;
|
||||||
|
if (
|
||||||
|
br.recovery.walletRootPriv != bs.walletRootPriv &&
|
||||||
|
providers.length > 0 &&
|
||||||
|
!strategy
|
||||||
|
) {
|
||||||
|
throw Error(
|
||||||
|
"recovery load strategy must be specified for wallet with existing providers",
|
||||||
|
);
|
||||||
|
} else if (!strategy) {
|
||||||
|
// Default to using the new key if we don't have providers yet.
|
||||||
|
strategy = RecoveryMergeStrategy.Theirs;
|
||||||
|
}
|
||||||
|
if (strategy === RecoveryMergeStrategy.Theirs) {
|
||||||
|
return backupRecoveryTheirs(ws, br.recovery);
|
||||||
|
} else {
|
||||||
|
return backupRecoveryOurs(ws, br.recovery);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function exportBackupEncrypted(
|
export async function exportBackupEncrypted(
|
||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
): Promise<Uint8Array> {
|
): Promise<Uint8Array> {
|
||||||
|
@ -21,27 +21,22 @@
|
|||||||
* as the backup schema must remain very stable and should be self-contained.
|
* as the backup schema must remain very stable and should be self-contained.
|
||||||
*
|
*
|
||||||
* Current limitations:
|
* Current limitations:
|
||||||
* 1. Exchange/auditor trust isn't exported yet
|
* 1. "Ghost spends", where a coin is spent unexpectedly by another wallet
|
||||||
* (see https://bugs.gnunet.org/view.php?id=6448)
|
|
||||||
* 2. Reports to the auditor (cryptographic proofs and/or diagnostics) aren't exported yet
|
|
||||||
* 3. "Ghost spends", where a coin is spent unexpectedly by another wallet
|
|
||||||
* and a corresponding transaction (that is missing some details!) should
|
* and a corresponding transaction (that is missing some details!) should
|
||||||
* be added to the transaction history, aren't implemented yet.
|
* be added to the transaction history, aren't implemented yet.
|
||||||
* 4. Clocks for denom/coin selections aren't properly modeled yet.
|
* 2. Clocks for denom/coin selections aren't properly modeled yet.
|
||||||
* (Needed for re-denomination of withdrawal / re-selection of coins)
|
* (Needed for re-denomination of withdrawal / re-selection of coins)
|
||||||
* 5. Preferences about how currencies are to be displayed
|
* 3. Preferences about how currencies are to be displayed
|
||||||
* aren't exported yet (and not even implemented in wallet-core).
|
* aren't exported yet (and not even implemented in wallet-core).
|
||||||
* 6. Returning money to own bank account isn't supported/exported yet.
|
* 4. Returning money to own bank account isn't supported/exported yet.
|
||||||
* 7. Peer-to-peer payments aren't supported yet.
|
* 5. Peer-to-peer payments aren't supported yet.
|
||||||
* 8. Next update time / next refresh time isn't backed up yet.
|
* 6. Next update time / next auto-refresh time isn't backed up yet.
|
||||||
* 9. Coin/denom selections should be forgettable once that information
|
* 7. Coin/denom selections should be forgettable once that information
|
||||||
* becomes irrelevant.
|
* becomes irrelevant.
|
||||||
* 10. Re-denominated payments/refreshes are not shown properly in the total
|
* 8. Re-denominated payments/refreshes are not shown properly in the total
|
||||||
* payment cost.
|
* payment cost.
|
||||||
* 11. Failed refunds do not have any information about why they failed.
|
* 9. Permanently failed operations aren't properly modeled yet
|
||||||
* => This should go into the general "error reports"
|
* 10. Do we somehow need to model the mechanism for first only withdrawing
|
||||||
* 12. Tombstones for removed backup providers
|
|
||||||
* 13. Do we somehow need to model the mechanism for first only withdrawing
|
|
||||||
* the amount to pay the backup provider?
|
* the amount to pay the backup provider?
|
||||||
*
|
*
|
||||||
* Questions:
|
* Questions:
|
||||||
@ -299,15 +294,7 @@ export interface BackupTrustExchange {
|
|||||||
clock_removed?: ClockValue;
|
clock_removed?: ClockValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export class BackupBackupProviderTerms {
|
||||||
* Backup information about one backup storage provider.
|
|
||||||
*/
|
|
||||||
export class BackupBackupProvider {
|
|
||||||
/**
|
|
||||||
* Canonicalized base URL of the provider.
|
|
||||||
*/
|
|
||||||
base_url: string;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last known supported protocol version.
|
* Last known supported protocol version.
|
||||||
*/
|
*/
|
||||||
@ -322,6 +309,22 @@ export class BackupBackupProvider {
|
|||||||
* Last known storage limit.
|
* Last known storage limit.
|
||||||
*/
|
*/
|
||||||
storage_limit_in_megabytes: number;
|
storage_limit_in_megabytes: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Backup information about one backup storage provider.
|
||||||
|
*/
|
||||||
|
export class BackupBackupProvider {
|
||||||
|
/**
|
||||||
|
* Canonicalized base URL of the provider.
|
||||||
|
*/
|
||||||
|
base_url: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Last known terms. Might be unavailable in some situations, such
|
||||||
|
* as directly after restoring form a backup recovery document.
|
||||||
|
*/
|
||||||
|
terms?: BackupBackupProviderTerms;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Proposal IDs for payments to this provider.
|
* Proposal IDs for payments to this provider.
|
||||||
|
@ -1426,20 +1426,30 @@ export enum ImportPayloadType {
|
|||||||
CoreSchema = "core-schema",
|
CoreSchema = "core-schema",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum BackupProviderStatus {
|
||||||
|
PaymentRequired = "payment-required",
|
||||||
|
Ready = "ready",
|
||||||
|
}
|
||||||
|
|
||||||
export interface BackupProviderRecord {
|
export interface BackupProviderRecord {
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
|
|
||||||
supportedProtocolVersion: string;
|
/**
|
||||||
|
* Terms of service of the provider.
|
||||||
annualFee: AmountString;
|
* Might be unavailable in the DB in certain situations
|
||||||
|
* (such as loading a recovery document).
|
||||||
storageLimitInMegabytes: number;
|
*/
|
||||||
|
terms?: {
|
||||||
|
supportedProtocolVersion: string;
|
||||||
|
annualFee: AmountString;
|
||||||
|
storageLimitInMegabytes: number;
|
||||||
|
};
|
||||||
|
|
||||||
active: boolean;
|
active: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hash of the last backup that we already
|
* Hash of the last encrypted backup that we already merged
|
||||||
* merged.
|
* or successfully uploaded ourselves.
|
||||||
*/
|
*/
|
||||||
lastBackupHash?: string;
|
lastBackupHash?: string;
|
||||||
|
|
||||||
@ -1448,6 +1458,12 @@ export interface BackupProviderRecord {
|
|||||||
* merged.
|
* merged.
|
||||||
*/
|
*/
|
||||||
lastBackupClock?: number;
|
lastBackupClock?: number;
|
||||||
|
|
||||||
|
lastBackupTimestamp?: Timestamp;
|
||||||
|
|
||||||
|
currentPaymentProposalId?: string;
|
||||||
|
|
||||||
|
paymentProposalIds: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExchangesStore extends Store<"exchanges", ExchangeRecord> {
|
class ExchangesStore extends Store<"exchanges", ExchangeRecord> {
|
||||||
|
@ -56,6 +56,7 @@ import {
|
|||||||
ContractTerms,
|
ContractTerms,
|
||||||
} from "./talerTypes";
|
} from "./talerTypes";
|
||||||
import { OrderShortInfo, codecForOrderShortInfo } from "./transactionsTypes";
|
import { OrderShortInfo, codecForOrderShortInfo } from "./transactionsTypes";
|
||||||
|
import { BackupRecovery } from "../operations/backup";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response for the create reserve request to the wallet.
|
* Response for the create reserve request to the wallet.
|
||||||
@ -896,6 +897,29 @@ export interface MakeSyncSignatureRequest {
|
|||||||
newHash: string;
|
newHash: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strategy for loading recovery information.
|
||||||
|
*/
|
||||||
|
export enum RecoveryMergeStrategy {
|
||||||
|
/**
|
||||||
|
* Keep the local wallet root key, import and take over providers.
|
||||||
|
*/
|
||||||
|
Ours = "ours",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate to the wallet root key from the recovery information.
|
||||||
|
*/
|
||||||
|
Theirs = "theirs",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load recovery information into the wallet.
|
||||||
|
*/
|
||||||
|
export interface RecoveryLoadRequest {
|
||||||
|
recovery: BackupRecovery;
|
||||||
|
strategy?: RecoveryMergeStrategy;
|
||||||
|
}
|
||||||
|
|
||||||
export const codecForWithdrawTestBalance = (): Codec<
|
export const codecForWithdrawTestBalance = (): Codec<
|
||||||
WithdrawTestBalanceRequest
|
WithdrawTestBalanceRequest
|
||||||
> =>
|
> =>
|
||||||
|
@ -94,6 +94,7 @@ import {
|
|||||||
codecForAcceptTipRequest,
|
codecForAcceptTipRequest,
|
||||||
codecForAbortPayWithRefundRequest,
|
codecForAbortPayWithRefundRequest,
|
||||||
ApplyRefundResponse,
|
ApplyRefundResponse,
|
||||||
|
RecoveryLoadRequest,
|
||||||
} from "./types/walletTypes";
|
} from "./types/walletTypes";
|
||||||
import { Logger } from "./util/logging";
|
import { Logger } from "./util/logging";
|
||||||
|
|
||||||
@ -167,6 +168,9 @@ import {
|
|||||||
BackupRecovery,
|
BackupRecovery,
|
||||||
getBackupRecovery,
|
getBackupRecovery,
|
||||||
AddBackupProviderRequest,
|
AddBackupProviderRequest,
|
||||||
|
getBackupInfo,
|
||||||
|
BackupInfo,
|
||||||
|
loadBackupRecovery,
|
||||||
} from "./operations/backup";
|
} from "./operations/backup";
|
||||||
|
|
||||||
const builtinCurrencies: CurrencyRecord[] = [
|
const builtinCurrencies: CurrencyRecord[] = [
|
||||||
@ -959,6 +963,10 @@ export class Wallet {
|
|||||||
return getBackupRecovery(this.ws);
|
return getBackupRecovery(this.ws);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadBackupRecovery(req: RecoveryLoadRequest): Promise<void> {
|
||||||
|
return loadBackupRecovery(this.ws, req);
|
||||||
|
}
|
||||||
|
|
||||||
async addBackupProvider(req: AddBackupProviderRequest): Promise<void> {
|
async addBackupProvider(req: AddBackupProviderRequest): Promise<void> {
|
||||||
return addBackupProvider(this.ws, req);
|
return addBackupProvider(this.ws, req);
|
||||||
}
|
}
|
||||||
@ -967,6 +975,10 @@ export class Wallet {
|
|||||||
return runBackupCycle(this.ws);
|
return runBackupCycle(this.ws);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getBackupStatus(): Promise<BackupInfo> {
|
||||||
|
return getBackupInfo(this.ws);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the "wallet-core" API.
|
* Implementation of the "wallet-core" API.
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user