towards backup based on add/remove set instead of clocks

This commit is contained in:
Florian Dold 2021-05-12 13:34:49 +02:00
parent 83b02069c9
commit debc2254fd
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
8 changed files with 47 additions and 96 deletions

View File

@ -70,19 +70,13 @@ type BackupAmountString = string;
*/ */
type DeviceIdString = string; type DeviceIdString = string;
/**
* Lamport clock timestamp.
*/
export interface ClockStamp {
deviceId: string;
value: number;
}
/** /**
* Contract terms JSON. * Contract terms JSON.
*/ */
type RawContractTerms = any; type RawContractTerms = any;
type OperationUid = string;
/** /**
* Content of the backup. * Content of the backup.
* *
@ -115,16 +109,6 @@ export interface WalletBackupContentV1 {
*/ */
current_device_id: DeviceIdString; current_device_id: DeviceIdString;
/**
* Monotonically increasing clock of the wallet,
* used to determine causality when merging backups.
*
* Information about other clocks, used to delete
* tombstones in the hopefully rare case that multiple wallets
* are connected to the same sync server.
*/
clocks: { [device_id: string]: number };
/** /**
* Timestamp of the backup. * Timestamp of the backup.
* *
@ -182,21 +166,6 @@ export interface WalletBackupContentV1 {
*/ */
recoup_groups: BackupRecoupGroup[]; recoup_groups: BackupRecoupGroup[];
/**
* Tombstones for deleting purchases.
*/
purchase_tombstones: {
/**
* Clock when the purchase was deleted
*/
clock_deleted: ClockStamp;
/**
* Proposal ID identifying the purchase.
*/
proposal_id: string;
}[];
/** /**
* Trusted auditors, either for official (3 letter) or local (4-12 letter) * Trusted auditors, either for official (3 letter) or local (4-12 letter)
* currencies. * currencies.
@ -224,6 +193,17 @@ export interface WalletBackupContentV1 {
* Permanent error reports. * Permanent error reports.
*/ */
error_reports: BackupErrorReport[]; error_reports: BackupErrorReport[];
/**
* Deletion tombstones. Sorted by (type, id)
* in ascending order.
*/
tombstones: Tombstone[];
}
export interface Tombstone {
type: string;
id: string;
} }
/** /**
@ -256,17 +236,10 @@ export interface BackupTrustAuditor {
auditor_pub: string; auditor_pub: string;
/** /**
* Clock when the auditor trust has been added. * UIDs for the operation of adding this auditor
* * as a trusted auditor.
* Can be undefined if this entry represents a removal delta
* from the wallet's defaults.
*/ */
clock_added?: ClockStamp; uids: OperationUid;
/**
* Clock for when the auditor trust has been removed.
*/
clock_removed?: ClockStamp;
} }
/** /**
@ -289,17 +262,10 @@ export interface BackupTrustExchange {
exchange_master_pub: string; exchange_master_pub: string;
/** /**
* Clock when the exchange trust has been added. * UIDs for the operation of adding this exchange
* * as trusted.
* Can be undefined if this entry represents a removal delta
* from the wallet's defaults.
*/ */
clock_added?: ClockStamp; uids: OperationUid;
/**
* Clock for when the exchange trust has been removed.
*/
clock_removed?: ClockStamp;
} }
export class BackupBackupProviderTerms { export class BackupBackupProviderTerms {
@ -483,11 +449,6 @@ export interface BackupCoin {
* be refreshed in most situations. * be refreshed in most situations.
*/ */
fresh: boolean; fresh: boolean;
/**
* Clock for the last update to current_amount/fresh.
*/
last_clock?: ClockStamp;
} }
/** /**
@ -521,7 +482,6 @@ export interface BackupTip {
timestamp_created: Timestamp; timestamp_created: Timestamp;
timestamp_finished?: Timestamp; timestamp_finished?: Timestamp;
finish_clock?: ClockStamp;
finish_is_failure?: boolean; finish_is_failure?: boolean;
/** /**
@ -549,7 +509,11 @@ export interface BackupTip {
*/ */
selected_denoms: BackupDenomSel; selected_denoms: BackupDenomSel;
selected_denoms_clock?: ClockStamp; /**
* UID for the denomination selection.
* Used to disambiguate when merging.
*/
selected_denoms_uid: OperationUid;
} }
/** /**
@ -640,7 +604,6 @@ export interface BackupRefreshGroup {
timestamp_created: Timestamp; timestamp_created: Timestamp;
timestamp_finish?: Timestamp; timestamp_finish?: Timestamp;
finish_clock?: ClockStamp;
finish_is_failure?: boolean; finish_is_failure?: boolean;
} }
@ -664,7 +627,6 @@ export interface BackupWithdrawalGroup {
timestamp_created: Timestamp; timestamp_created: Timestamp;
timestamp_finish?: Timestamp; timestamp_finish?: Timestamp;
finish_clock?: ClockStamp;
finish_is_failure?: boolean; finish_is_failure?: boolean;
/** /**
@ -682,7 +644,7 @@ export interface BackupWithdrawalGroup {
*/ */
selected_denoms: BackupDenomSel; selected_denoms: BackupDenomSel;
selected_denoms_clock?: ClockStamp; selected_denoms_id: OperationUid;
} }
export enum BackupRefundState { export enum BackupRefundState {
@ -731,8 +693,6 @@ export interface BackupRefundItemCommon {
* accurately. * accurately.
*/ */
total_refresh_cost_bound: BackupAmountString; total_refresh_cost_bound: BackupAmountString;
last_clock?: ClockStamp;
} }
/** /**
@ -795,9 +755,9 @@ export interface BackupPurchase {
}[]; }[];
/** /**
* Clock when the pay coin selection was made/updated. * Unique ID to disambiguate pay coin selection on merge.
*/ */
pay_coins_clock?: ClockStamp; pay_coins_uid: OperationUid;
/** /**
* Total cost initially shown to the user. * Total cost initially shown to the user.
@ -841,11 +801,6 @@ export interface BackupPurchase {
*/ */
defunct?: boolean; defunct?: boolean;
/**
* Clock for last update to defunct status.
*/
defunct_clock?: ClockStamp;
/** /**
* Abort status of the payment. * Abort status of the payment.
*/ */
@ -1042,7 +997,6 @@ export interface BackupReserve {
withdrawal_groups: BackupWithdrawalGroup[]; withdrawal_groups: BackupWithdrawalGroup[];
defective?: boolean; defective?: boolean;
defective_clock?: ClockStamp;
} }
/** /**
@ -1196,17 +1150,10 @@ export interface BackupExchange {
*/ */
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? * Should this exchange be considered defective?
*/ */
defective?: boolean; defective?: boolean;
defective_clock?: ClockStamp;
} }
export enum BackupProposalStatus { export enum BackupProposalStatus {
@ -1284,8 +1231,6 @@ 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

@ -978,6 +978,8 @@ export interface TipRecord {
*/ */
denomsSel: DenomSelectionState; denomsSel: DenomSelectionState;
denomSelUid: string;
/** /**
* Tip ID chosen by the wallet. * Tip ID chosen by the wallet.
*/ */
@ -1310,6 +1312,8 @@ export interface PurchaseRecord {
payCoinSelection: PayCoinSelection; payCoinSelection: PayCoinSelection;
payCoinSelectionUid: string;
/** /**
* Pending removals from pay coin selection. * Pending removals from pay coin selection.
* *
@ -1460,6 +1464,8 @@ export interface WithdrawalGroupRecord {
denomsSel: DenomSelectionState; denomsSel: DenomSelectionState;
denomSelUid: string;
/** /**
* Retry info, always present even on completed operations so that indexing works. * Retry info, always present even on completed operations so that indexing works.
*/ */
@ -1564,12 +1570,6 @@ export interface BackupProviderRecord {
*/ */
lastBackupHash?: string; lastBackupHash?: string;
/**
* Clock of the last backup that we already
* merged.
*/
lastBackupClock?: number;
lastBackupTimestamp?: Timestamp; lastBackupTimestamp?: Timestamp;
/** /**

View File

@ -118,6 +118,7 @@ export async function exportBackup(
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,
selected_denoms_id: wg.denomSelUid,
}); });
}); });
@ -160,6 +161,7 @@ export async function exportBackup(
timestamp_created: tip.createdTimestamp, timestamp_created: tip.createdTimestamp,
timestamp_expiration: tip.tipExpiration, timestamp_expiration: tip.tipExpiration,
tip_amount_raw: Amounts.stringify(tip.tipAmountRaw), tip_amount_raw: Amounts.stringify(tip.tipAmountRaw),
selected_denoms_uid: tip.denomSelUid,
}); });
}); });
@ -363,6 +365,7 @@ export async function exportBackup(
nonce_priv: purch.noncePriv, nonce_priv: purch.noncePriv,
merchant_sig: purch.download.contractData.merchantSig, merchant_sig: purch.download.contractData.merchantSig,
total_pay_cost: Amounts.stringify(purch.totalPayCost), total_pay_cost: Amounts.stringify(purch.totalPayCost),
pay_coins_uid: purch.payCoinSelectionUid,
}); });
}); });
@ -446,13 +449,11 @@ export async function exportBackup(
const backupBlob: WalletBackupContentV1 = { const backupBlob: WalletBackupContentV1 = {
schema_id: "gnu-taler-wallet-backup-content", schema_id: "gnu-taler-wallet-backup-content",
schema_version: 1, schema_version: 1,
clocks: bs.clocks,
exchanges: backupExchanges, exchanges: backupExchanges,
wallet_root_pub: bs.walletRootPub, wallet_root_pub: bs.walletRootPub,
backup_providers: backupBackupProviders, backup_providers: backupBackupProviders,
current_device_id: bs.deviceId, current_device_id: bs.deviceId,
proposals: backupProposals, proposals: backupProposals,
purchase_tombstones: [],
purchases: backupPurchases, purchases: backupPurchases,
recoup_groups: backupRecoupGroups, recoup_groups: backupRecoupGroups,
refresh_groups: backupRefreshGroups, refresh_groups: backupRefreshGroups,
@ -462,13 +463,13 @@ export async function exportBackup(
trusted_exchanges: {}, trusted_exchanges: {},
intern_table: {}, intern_table: {},
error_reports: [], error_reports: [],
tombstones: [],
}; };
// If the backup changed, we increment our clock. // If the backup changed, we increment our clock.
let h = encodeCrock(hash(stringToBytes(canonicalJson(backupBlob)))); let h = encodeCrock(hash(stringToBytes(canonicalJson(backupBlob))));
if (h != bs.lastBackupPlainHash) { if (h != bs.lastBackupPlainHash) {
backupBlob.clocks[bs.deviceId] = ++bs.clocks[bs.deviceId];
bs.lastBackupPlainHash = encodeCrock( bs.lastBackupPlainHash = encodeCrock(
hash(stringToBytes(canonicalJson(backupBlob))), hash(stringToBytes(canonicalJson(backupBlob))),
); );

View File

@ -302,7 +302,9 @@ export async function importBackup(
denomPubHash, denomPubHash,
]); ]);
if (!existingDenom) { if (!existingDenom) {
logger.info(`importing backup denomination: ${j2s(backupDenomination)}`); logger.info(
`importing backup denomination: ${j2s(backupDenomination)}`,
);
await tx.put(Stores.denominations, { await tx.put(Stores.denominations, {
denomPub: backupDenomination.denom_pub, denomPub: backupDenomination.denom_pub,
@ -446,6 +448,7 @@ export async function importBackup(
timestampStart: backupWg.timestamp_created, timestampStart: backupWg.timestamp_created,
timestampFinish: backupWg.timestamp_finish, timestampFinish: backupWg.timestamp_finish,
withdrawalGroupId: backupWg.withdrawal_group_id, withdrawalGroupId: backupWg.withdrawal_group_id,
denomSelUid: backupWg.selected_denoms_id,
}); });
} }
} }
@ -695,6 +698,7 @@ export async function importBackup(
coinDepositPermissions: undefined, coinDepositPermissions: undefined,
totalPayCost: Amounts.parseOrThrow(backupPurchase.total_pay_cost), totalPayCost: Amounts.parseOrThrow(backupPurchase.total_pay_cost),
refunds, refunds,
payCoinSelectionUid: backupPurchase.pay_coins_uid,
}); });
} }
} }
@ -801,6 +805,7 @@ export async function importBackup(
tipAmountRaw: Amounts.parseOrThrow(backupTip.tip_amount_raw), tipAmountRaw: Amounts.parseOrThrow(backupTip.tip_amount_raw),
tipExpiration: backupTip.timestamp_expiration, tipExpiration: backupTip.timestamp_expiration,
walletTipId: backupTip.wallet_tip_id, walletTipId: backupTip.wallet_tip_id,
denomSelUid: backupTip.selected_denoms_uid,
}); });
} }
} }

View File

@ -349,7 +349,6 @@ async function runBackupCycleForProvider(
} }
prov.lastBackupHash = encodeCrock(currentBackupHash); prov.lastBackupHash = encodeCrock(currentBackupHash);
prov.lastBackupTimestamp = getTimestampNow(); prov.lastBackupTimestamp = getTimestampNow();
prov.lastBackupClock = backupJson.clocks[backupJson.current_device_id];
prov.lastError = undefined; prov.lastError = undefined;
await tx.put(Stores.backupProviders, prov); await tx.put(Stores.backupProviders, prov);
}, },
@ -372,7 +371,6 @@ async function runBackupCycleForProvider(
return; return;
} }
prov.lastBackupHash = encodeCrock(hash(backupEnc)); prov.lastBackupHash = encodeCrock(hash(backupEnc));
prov.lastBackupClock = blob.clocks[blob.current_device_id];
prov.lastBackupTimestamp = getTimestampNow(); prov.lastBackupTimestamp = getTimestampNow();
prov.lastError = undefined; prov.lastError = undefined;
await tx.put(Stores.backupProviders, prov); await tx.put(Stores.backupProviders, prov);
@ -624,7 +622,6 @@ export async function getBackupInfo(
for (const x of providerRecords) { for (const x of providerRecords) {
providers.push({ providers.push({
active: x.active, active: x.active,
lastRemoteClock: x.lastBackupClock,
syncProviderBaseUrl: x.baseUrl, syncProviderBaseUrl: x.baseUrl,
lastBackupTimestamp: x.lastBackupTimestamp, lastBackupTimestamp: x.lastBackupTimestamp,
paymentProposalIds: x.paymentProposalIds, paymentProposalIds: x.paymentProposalIds,
@ -696,7 +693,6 @@ async function backupRecoveryTheirs(
for (const prov of providers) { for (const prov of providers) {
prov.lastBackupTimestamp = undefined; prov.lastBackupTimestamp = undefined;
prov.lastBackupHash = undefined; prov.lastBackupHash = undefined;
prov.lastBackupClock = undefined;
await tx.put(Stores.backupProviders, prov); await tx.put(Stores.backupProviders, prov);
} }
}, },

View File

@ -406,6 +406,7 @@ async function recordConfirmPay(
download: d, download: d,
lastSessionId: sessionId, lastSessionId: sessionId,
payCoinSelection: coinSelection, payCoinSelection: coinSelection,
payCoinSelectionUid: encodeCrock(getRandomBytes(32)),
totalPayCost: payCostInfo, totalPayCost: payCostInfo,
coinDepositPermissions, coinDepositPermissions,
timestampAccept: getTimestampNow(), timestampAccept: getTimestampNow(),

View File

@ -633,6 +633,7 @@ async function updateReserve(
lastError: undefined, lastError: undefined,
denomsSel: denomSelectionInfoToState(denomSelInfo), denomsSel: denomSelectionInfoToState(denomSelInfo),
secretSeed: encodeCrock(getRandomBytes(64)), secretSeed: encodeCrock(getRandomBytes(64)),
denomSelUid: encodeCrock(getRandomBytes(32)),
}; };
newReserve.lastError = undefined; newReserve.lastError = undefined;

View File

@ -107,6 +107,7 @@ export async function prepareTip(
const selectedDenoms = selectWithdrawalDenominations(amount, denoms); const selectedDenoms = selectWithdrawalDenominations(amount, denoms);
const secretSeed = encodeCrock(getRandomBytes(64)); const secretSeed = encodeCrock(getRandomBytes(64));
const denomSelUid = encodeCrock(getRandomBytes(32));
tipRecord = { tipRecord = {
walletTipId: walletTipId, walletTipId: walletTipId,
@ -127,6 +128,7 @@ export async function prepareTip(
denomsSel: denomSelectionInfoToState(selectedDenoms), denomsSel: denomSelectionInfoToState(selectedDenoms),
pickedUpTimestamp: undefined, pickedUpTimestamp: undefined,
secretSeed, secretSeed,
denomSelUid,
}; };
await ws.db.put(Stores.tips, tipRecord); await ws.db.put(Stores.tips, tipRecord);
} }