backup schema

This commit is contained in:
Florian Dold 2021-01-10 23:57:06 +01:00
parent 8921a5e8f2
commit c0dfcf247c
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
5 changed files with 140 additions and 75 deletions

View File

@ -246,7 +246,7 @@ export async function exportBackup(
count: x.count, count: x.count,
denom_pub_hash: x.denomPubHash, denom_pub_hash: x.denomPubHash,
})), })),
timestamp_start: wg.timestampStart, timestamp_created: wg.timestampStart,
timestamp_finish: wg.timestampFinish, timestamp_finish: wg.timestampFinish,
withdrawal_group_id: wg.withdrawalGroupId, withdrawal_group_id: wg.withdrawalGroupId,
secret_seed: wg.secretSeed, secret_seed: wg.secretSeed,
@ -267,6 +267,8 @@ export async function exportBackup(
timestamp_created: reserve.timestampCreated, timestamp_created: reserve.timestampCreated,
withdrawal_groups: withdrawal_groups:
withdrawalGroupsByReserve[reserve.reservePub] ?? [], withdrawalGroupsByReserve[reserve.reservePub] ?? [],
// FIXME!
timestamp_last_activity: reserve.timestampCreated,
}; };
const backupReserves = (backupReservesByExchange[ const backupReserves = (backupReservesByExchange[
reserve.exchangeBaseUrl reserve.exchangeBaseUrl
@ -285,7 +287,7 @@ export async function exportBackup(
count: x.count, count: x.count,
denom_pub_hash: x.denomPubHash, denom_pub_hash: x.denomPubHash,
})), })),
timestam_picked_up: tip.pickedUpTimestamp, timestamp_finished: tip.pickedUpTimestamp,
timestamp_accepted: tip.acceptedTimestamp, timestamp_accepted: tip.acceptedTimestamp,
timestamp_created: tip.createdTimestamp, timestamp_created: tip.createdTimestamp,
timestamp_expiration: tip.tipExpiration, timestamp_expiration: tip.tipExpiration,
@ -296,8 +298,8 @@ export async function exportBackup(
await tx.iter(Stores.recoupGroups).forEach((recoupGroup) => { await tx.iter(Stores.recoupGroups).forEach((recoupGroup) => {
backupRecoupGroups.push({ backupRecoupGroups.push({
recoup_group_id: recoupGroup.recoupGroupId, recoup_group_id: recoupGroup.recoupGroupId,
timestamp_started: recoupGroup.timestampStarted, timestamp_created: recoupGroup.timestampStarted,
timestamp_finished: recoupGroup.timestampFinished, timestamp_finish: recoupGroup.timestampFinished,
coins: recoupGroup.coinPubs.map((x, i) => ({ coins: recoupGroup.coinPubs.map((x, i) => ({
coin_pub: x, coin_pub: x,
recoup_finished: recoupGroup.recoupFinishedPerCoin[i], recoup_finished: recoupGroup.recoupFinishedPerCoin[i],
@ -414,6 +416,7 @@ export async function exportBackup(
backupExchanges.push({ backupExchanges.push({
base_url: ex.baseUrl, base_url: ex.baseUrl,
reserve_closing_delay: ex.details.reserveClosingDelay,
accounts: ex.wireInfo.accounts.map((x) => ({ accounts: ex.wireInfo.accounts.map((x) => ({
payto_uri: x.payto_uri, payto_uri: x.payto_uri,
master_sig: x.master_sig, master_sig: x.master_sig,
@ -472,7 +475,6 @@ export async function exportBackup(
} }
backupPurchases.push({ backupPurchases.push({
clock_created: 1,
contract_terms_raw: purch.download.contractTermsRaw, contract_terms_raw: purch.download.contractTermsRaw,
auto_refund_deadline: purch.autoRefundDeadline, auto_refund_deadline: purch.autoRefundDeadline,
merchant_pay_sig: purch.merchantPaySig, merchant_pay_sig: purch.merchantPaySig,
@ -486,7 +488,6 @@ export async function exportBackup(
refunds, refunds,
timestamp_accept: purch.timestampAccept, timestamp_accept: purch.timestampAccept,
timestamp_first_successful_pay: purch.timestampFirstSuccessfulPay, timestamp_first_successful_pay: purch.timestampFirstSuccessfulPay,
timestamp_last_refund_status: purch.timestampLastRefundStatus,
abort_status: abort_status:
purch.abortStatus === AbortStatus.None purch.abortStatus === AbortStatus.None
? undefined ? undefined
@ -564,8 +565,8 @@ export async function exportBackup(
backupRefreshGroups.push({ backupRefreshGroups.push({
reason: rg.reason as any, reason: rg.reason as any,
refresh_group_id: rg.refreshGroupId, refresh_group_id: rg.refreshGroupId,
timestamp_started: rg.timestampCreated, timestamp_created: rg.timestampCreated,
timestamp_finished: rg.timestampFinished, timestamp_finish: rg.timestampFinished,
old_coins: oldCoins, old_coins: oldCoins,
}); });
}); });
@ -592,6 +593,7 @@ export async function exportBackup(
trusted_auditors: {}, trusted_auditors: {},
trusted_exchanges: {}, trusted_exchanges: {},
intern_table: {}, intern_table: {},
error_reports: [],
}; };
// If the backup changed, we increment our clock. // If the backup changed, we increment our clock.
@ -934,6 +936,7 @@ export async function importBackup(
wireInfo, wireInfo,
details: { details: {
currency: backupExchange.currency, currency: backupExchange.currency,
reserveClosingDelay: backupExchange.reserve_closing_delay,
auditors: backupExchange.auditors.map((x) => ({ auditors: backupExchange.auditors.map((x) => ({
auditor_pub: x.auditor_pub, auditor_pub: x.auditor_pub,
auditor_url: x.auditor_url, auditor_url: x.auditor_url,
@ -1102,7 +1105,7 @@ export async function importBackup(
reservePub, reservePub,
retryInfo: initRetryInfo(false), retryInfo: initRetryInfo(false),
secretSeed: backupWg.secret_seed, secretSeed: backupWg.secret_seed,
timestampStart: backupWg.timestamp_start, timestampStart: backupWg.timestamp_created,
timestampFinish: backupWg.timestamp_finish, timestampFinish: backupWg.timestamp_finish,
withdrawalGroupId: backupWg.withdrawal_group_id, withdrawalGroupId: backupWg.withdrawal_group_id,
}); });
@ -1336,7 +1339,7 @@ export async function importBackup(
timestampFirstSuccessfulPay: timestampFirstSuccessfulPay:
backupPurchase.timestamp_first_successful_pay, backupPurchase.timestamp_first_successful_pay,
timestampLastRefundStatus: timestampLastRefundStatus:
backupPurchase.timestamp_last_refund_status, undefined,
merchantPaySig: backupPurchase.merchant_pay_sig, merchantPaySig: backupPurchase.merchant_pay_sig,
lastSessionId: undefined, lastSessionId: undefined,
abortStatus, abortStatus,
@ -1414,8 +1417,8 @@ export async function importBackup(
} }
} }
await tx.put(Stores.refreshGroups, { await tx.put(Stores.refreshGroups, {
timestampFinished: backupRefreshGroup.timestamp_finished, timestampFinished: backupRefreshGroup.timestamp_finish,
timestampCreated: backupRefreshGroup.timestamp_started, timestampCreated: backupRefreshGroup.timestamp_created,
refreshGroupId: backupRefreshGroup.refresh_group_id, refreshGroupId: backupRefreshGroup.refresh_group_id,
reason, reason,
lastError: undefined, lastError: undefined,
@ -1452,7 +1455,7 @@ export async function importBackup(
lastError: undefined, lastError: undefined,
merchantBaseUrl: backupTip.exchange_base_url, merchantBaseUrl: backupTip.exchange_base_url,
merchantTipId: backupTip.merchant_tip_id, merchantTipId: backupTip.merchant_tip_id,
pickedUpTimestamp: backupTip.timestam_picked_up, pickedUpTimestamp: backupTip.timestamp_finished,
retryInfo: initRetryInfo(false), retryInfo: initRetryInfo(false),
secretSeed: backupTip.secret_seed, secretSeed: backupTip.secret_seed,
tipAmountEffective: denomsSel.totalCoinValue, tipAmountEffective: denomsSel.totalCoinValue,

View File

@ -212,6 +212,7 @@ async function updateExchangeWithKeys(
nextUpdateTime: getExpiryTimestamp(resp, { nextUpdateTime: getExpiryTimestamp(resp, {
minDuration: durationFromSpec({ hours: 1 }), minDuration: durationFromSpec({ hours: 1 }),
}), }),
reserveClosingDelay: exchangeKeysJson.reserve_closing_delay,
}; };
r.updateStatus = ExchangeUpdateStatus.FetchWire; r.updateStatus = ExchangeUpdateStatus.FetchWire;
r.lastError = undefined; r.lastError = undefined;

View File

@ -20,24 +20,13 @@
* Contains some redundancy with the other type declarations, * Contains some redundancy with the other type declarations,
* 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: * Future:
* 1. "Ghost spends", where a coin is spent unexpectedly by another wallet * 1. Ghost spends (coin unexpectedly spend by a wallet with shared data)
* and a corresponding transaction (that is missing some details!) should * 2. Ghost withdrawals (reserve unexpectedly emptied by another wallet with shared data)
* be added to the transaction history, aren't implemented yet. * 3. Track losses through re-denomination of payments/refreshes
* 2. Clocks for denom/coin selections aren't properly modeled yet. * 4. (Feature:) Payments to own bank account and P2P-payments need to be backed up
* (Needed for re-denomination of withdrawal / re-selection of coins) * 5. Track last/next update time, so on restore we need to do less work
* 3. Preferences about how currencies are to be displayed * 6. Currency render preferences?
* aren't exported yet (and not even implemented in wallet-core).
* 4. Returning money to own bank account isn't supported/exported yet.
* 5. Peer-to-peer payments aren't supported yet.
* 6. Next update time / next auto-refresh time isn't backed up yet.
* 7. Coin/denom selections should be forgettable once that information
* becomes irrelevant.
* 8. Re-denominated payments/refreshes are not shown properly in the total
* payment cost.
* 9. Permanently failed operations aren't properly modeled yet
* 10. Do we somehow need to model the mechanism for first only withdrawing
* the amount to pay the backup provider?
* *
* Questions: * Questions:
* 1. What happens when two backups are merged that have * 1. What happens when two backups are merged that have
@ -64,7 +53,7 @@
/** /**
* Imports. * Imports.
*/ */
import { Timestamp } from "../util/time"; import { Duration, Timestamp } from "../util/time";
/** /**
* Type alias for strings that are to be treated like amounts. * Type alias for strings that are to be treated like amounts.
@ -82,9 +71,12 @@ type BackupAmountString = string;
type DeviceIdString = string; type DeviceIdString = string;
/** /**
* Integer-valued clock. * Lamport clock timestamp.
*/ */
type ClockValue = number; export interface ClockStamp {
deviceId: string;
value: number;
}
/** /**
* Contract terms JSON. * Contract terms JSON.
@ -131,7 +123,7 @@ export interface WalletBackupContentV1 {
* tombstones in the hopefully rare case that multiple wallets * tombstones in the hopefully rare case that multiple wallets
* are connected to the same sync server. * are connected to the same sync server.
*/ */
clocks: { [device_id: string]: ClockValue }; clocks: { [device_id: string]: number };
/** /**
* Timestamp of the backup. * Timestamp of the backup.
@ -227,6 +219,22 @@ export interface WalletBackupContentV1 {
* addresses, etc.) might be shared among many contract terms. * addresses, etc.) might be shared among many contract terms.
*/ */
intern_table: { [hash: string]: any }; intern_table: { [hash: string]: any };
/**
* Permanent error reports.
*/
error_reports: BackupErrorReport[];
}
/**
* Detailed error report.
*
* For auditor-relevant reports with attached cryptographic proof,
* the error report also should contain the submission status to
* the auditor(s).
*/
interface BackupErrorReport {
// FIXME: specify!
} }
/** /**
@ -253,12 +261,12 @@ export interface BackupTrustAuditor {
* Can be undefined if this entry represents a removal delta * Can be undefined if this entry represents a removal delta
* from the wallet's defaults. * from the wallet's defaults.
*/ */
clock_added?: ClockValue; clock_added?: ClockStamp;
/** /**
* Clock for when the auditor trust has been removed. * Clock for when the auditor trust has been removed.
*/ */
clock_removed?: ClockValue; clock_removed?: ClockStamp;
} }
/** /**
@ -286,12 +294,12 @@ export interface BackupTrustExchange {
* Can be undefined if this entry represents a removal delta * Can be undefined if this entry represents a removal delta
* from the wallet's defaults. * from the wallet's defaults.
*/ */
clock_added?: ClockValue; clock_added?: ClockStamp;
/** /**
* Clock for when the exchange trust has been removed. * Clock for when the exchange trust has been removed.
*/ */
clock_removed?: ClockValue; clock_removed?: ClockStamp;
} }
export class BackupBackupProviderTerms { export class BackupBackupProviderTerms {
@ -347,15 +355,11 @@ export interface BackupRecoupGroup {
/** /**
* Timestamp when the recoup was started. * Timestamp when the recoup was started.
*/ */
timestamp_started: Timestamp; timestamp_created: Timestamp;
/** timestamp_finish?: Timestamp;
* Timestamp when the recoup finished. finish_clock?: Timestamp;
* finish_is_failure?: boolean;
* (That means all coins have been recouped and coins to
* be refreshed have been put in a refresh group.)
*/
timestamp_finished: Timestamp | undefined;
/** /**
* Information about each coin being recouped. * Information about each coin being recouped.
@ -475,10 +479,15 @@ export interface BackupCoin {
/** /**
* Does the wallet think that the coin is still fresh? * Does the wallet think that the coin is still fresh?
* *
* FIXME: If we always refresh when importing a backup, do * Note that even if a fresh coin is imported, it should still
* we even need this flag? * be refreshed in most situations.
*/ */
fresh: boolean; fresh: boolean;
/**
* Clock for the last update to current_amount/fresh.
*/
last_clock?: ClockStamp;
} }
/** /**
@ -511,11 +520,15 @@ export interface BackupTip {
*/ */
timestamp_created: Timestamp; timestamp_created: Timestamp;
/** timestamp_finished?: Timestamp;
* Timestamp for when the wallet finished picking up the tip finish_clock?: ClockStamp;
* from the merchant. finish_is_failure?: boolean;
*/
timestam_picked_up: Timestamp | undefined; finish_info?: {
timestamp: Timestamp;
clock: ClockStamp;
failure: boolean;
};
/** /**
* The tipped amount. * The tipped amount.
@ -540,10 +553,9 @@ export interface BackupTip {
/** /**
* Selected denominations. Determines the effective tip amount. * Selected denominations. Determines the effective tip amount.
*/ */
selected_denoms: { selected_denoms: BackupDenomSel;
denom_pub_hash: string;
count: number; selected_denoms_clock?: ClockStamp;
}[];
} }
/** /**
@ -631,12 +643,11 @@ export interface BackupRefreshGroup {
*/ */
old_coins: BackupRefreshOldCoin[]; old_coins: BackupRefreshOldCoin[];
timestamp_started: Timestamp; timestamp_created: Timestamp;
/** timestamp_finish?: Timestamp;
* Timestamp when the refresh group finished. finish_clock?: ClockStamp;
*/ finish_is_failure?: boolean;
timestamp_finished: Timestamp | undefined;
} }
/** /**
@ -656,12 +667,11 @@ export interface BackupWithdrawalGroup {
* When was the withdrawal operation started started? * When was the withdrawal operation started started?
* Timestamp in milliseconds. * Timestamp in milliseconds.
*/ */
timestamp_start: Timestamp; timestamp_created: Timestamp;
/**
* When was the withdrawal operation completed?
*/
timestamp_finish?: Timestamp; timestamp_finish?: Timestamp;
finish_clock?: ClockStamp;
finish_is_failure?: boolean;
/** /**
* Amount including fees (i.e. the amount subtracted from the * Amount including fees (i.e. the amount subtracted from the
@ -677,6 +687,8 @@ export interface BackupWithdrawalGroup {
* Multiset of denominations selected for withdrawal. * Multiset of denominations selected for withdrawal.
*/ */
selected_denoms: BackupDenomSel; selected_denoms: BackupDenomSel;
selected_denoms_clock?: ClockStamp;
} }
export enum BackupRefundState { export enum BackupRefundState {
@ -725,6 +737,8 @@ export interface BackupRefundItemCommon {
* accurately. * accurately.
*/ */
total_refresh_cost_bound: BackupAmountString; total_refresh_cost_bound: BackupAmountString;
last_clock?: ClockStamp;
} }
/** /**
@ -758,11 +772,6 @@ export interface BackupPurchase {
*/ */
proposal_id: string; proposal_id: string;
/**
* Clock when this purchase was created.
*/
clock_created: number;
/** /**
* Contract terms we got from the merchant. * Contract terms we got from the merchant.
*/ */
@ -791,6 +800,11 @@ export interface BackupPurchase {
contribution: BackupAmountString; contribution: BackupAmountString;
}[]; }[];
/**
* Clock when the pay coin selection was made/updated.
*/
pay_coins_clock?: ClockStamp;
/** /**
* Total cost initially shown to the user. * Total cost initially shown to the user.
* *
@ -828,10 +842,15 @@ export interface BackupPurchase {
refunds: BackupRefundItem[]; refunds: BackupRefundItem[];
/** /**
* When was the last refund made? * Is the purchase considered defunct (either during payment
* Set to 0 if no refund was made on the purchase. * or during abort if abort_status is set).
*/ */
timestamp_last_refund_status: Timestamp | undefined; defunct?: boolean;
/**
* Clock for last update to defunct status.
*/
defunct_clock?: ClockStamp;
/** /**
* Abort status of the payment. * Abort status of the payment.
@ -945,6 +964,21 @@ export interface BackupReserve {
*/ */
timestamp_created: Timestamp; timestamp_created: Timestamp;
/**
* Timestamp of the last observed activity.
*
* Used to compute when to give up querying the exchange.
*/
timestamp_last_activity: Timestamp;
/**
* Timestamp of when the reserve closed.
*
* Note that the last activity can be after the closing time
* due to recouping.
*/
timestamp_closed?: Timestamp;
/** /**
* Wire information (as payto URI) for the bank account that * Wire information (as payto URI) for the bank account that
* transfered funds for this reserve. * transfered funds for this reserve.
@ -1012,6 +1046,9 @@ export interface BackupReserve {
* Groups of withdrawal operations for this reserve. Typically just one. * Groups of withdrawal operations for this reserve. Typically just one.
*/ */
withdrawal_groups: BackupWithdrawalGroup[]; withdrawal_groups: BackupWithdrawalGroup[];
defective?: boolean;
defective_clock?: ClockStamp;
} }
/** /**
@ -1134,6 +1171,11 @@ export interface BackupExchange {
*/ */
protocol_version: string; protocol_version: string;
/**
* Closing delay of reserves.
*/
reserve_closing_delay: Duration;
/** /**
* Signing keys we got from the exchange, can also contain * Signing keys we got from the exchange, can also contain
* older signing keys that are not returned by /keys anymore. * older signing keys that are not returned by /keys anymore.
@ -1159,6 +1201,18 @@ export interface BackupExchange {
* ETag for last terms of service download. * ETag for last terms of service download.
*/ */
tos_etag_accepted: string | undefined; tos_etag_accepted: string | undefined;
/**
* Clock value of the last update.
*/
last_clock?: ClockStamp;
/**
* Should this exchange be considered defective?
*/
defective?: boolean;
defective_clock?: ClockStamp;
} }
export enum BackupProposalStatus { export enum BackupProposalStatus {
@ -1236,6 +1290,8 @@ export interface BackupProposal {
*/ */
proposal_status: BackupProposalStatus; proposal_status: BackupProposalStatus;
proposal_status_clock?: ClockStamp;
/** /**
* Proposal that this one got "redirected" to as part of * Proposal that this one got "redirected" to as part of
* the repurchase detection. * the repurchase detection.

View File

@ -392,6 +392,8 @@ export interface ExchangeDetails {
*/ */
protocolVersion: string; protocolVersion: string;
reserveClosingDelay: Duration;
/** /**
* Signing keys we got from the exchange, can also contain * Signing keys we got from the exchange, can also contain
* older signing keys that are not returned by /keys anymore. * older signing keys that are not returned by /keys anymore.

View File

@ -672,6 +672,8 @@ export class ExchangeKeysJson {
* Protocol version. * Protocol version.
*/ */
version: string; version: string;
reserve_closing_delay: Duration;
} }
/** /**
@ -1193,6 +1195,7 @@ export const codecForExchangeKeysJson = (): Codec<ExchangeKeysJson> =>
.property("recoup", codecOptional(codecForList(codecForRecoup()))) .property("recoup", codecOptional(codecForList(codecForRecoup())))
.property("signkeys", codecForList(codecForExchangeSigningKey())) .property("signkeys", codecForList(codecForExchangeSigningKey()))
.property("version", codecForString()) .property("version", codecForString())
.property("reserve_closing_delay", codecForDuration)
.build("KeysJson"); .build("KeysJson");
export const codecForWireFeesJson = (): Codec<WireFeesJson> => export const codecForWireFeesJson = (): Codec<WireFeesJson> =>