tombstone processing in backup import

This commit is contained in:
Florian Dold 2021-05-21 11:47:11 +02:00
parent f0ab1449c5
commit 6b1aea426a
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
4 changed files with 84 additions and 4 deletions

View File

@ -1546,6 +1546,11 @@ export interface BackupProviderTerms {
} }
export interface BackupProviderRecord { export interface BackupProviderRecord {
/**
* Base URL of the provider.
*
* Primary key for the record.
*/
baseUrl: string; baseUrl: string;
/** /**

View File

@ -55,6 +55,7 @@ import { Logger } from "../../util/logging";
import { initRetryInfo } from "../../util/retries"; import { initRetryInfo } from "../../util/retries";
import { InternalWalletState } from "../state"; import { InternalWalletState } from "../state";
import { provideBackupState } from "./state"; import { provideBackupState } from "./state";
import { makeEventId, TombstoneTag } from "../transactions.js";
const logger = new Logger("operations/backup/import.ts"); const logger = new Logger("operations/backup/import.ts");
@ -121,6 +122,7 @@ async function recoverPayCoinSelection(
if (wireFee) { if (wireFee) {
totalWireFee = Amounts.add(totalWireFee, wireFee).amount; totalWireFee = Amounts.add(totalWireFee, wireFee).amount;
} }
coveredExchanges.add(coinRecord.exchangeBaseUrl);
} }
} }
@ -226,6 +228,8 @@ export async function importBackup(
Stores.recoupGroups, Stores.recoupGroups,
Stores.reserves, Stores.reserves,
Stores.withdrawalGroups, Stores.withdrawalGroups,
Stores.tombstones,
Stores.depositGroups,
], ],
async (tx) => { async (tx) => {
// FIXME: validate schema! // FIXME: validate schema!
@ -233,6 +237,14 @@ export async function importBackup(
// FIXME: validate version // FIXME: validate version
for (const tombstone of backupBlob.tombstones) {
await tx.put(Stores.tombstones, {
id: tombstone,
});
}
const tombstoneSet = new Set(backupBlob.tombstones);
for (const backupExchange of backupBlob.exchanges) { for (const backupExchange of backupBlob.exchanges) {
const existingExchange = await tx.get( const existingExchange = await tx.get(
Stores.exchanges, Stores.exchanges,
@ -381,6 +393,10 @@ export async function importBackup(
for (const backupReserve of backupExchange.reserves) { for (const backupReserve of backupExchange.reserves) {
const reservePub = const reservePub =
cryptoComp.reservePrivToPub[backupReserve.reserve_priv]; cryptoComp.reservePrivToPub[backupReserve.reserve_priv];
const ts = makeEventId(TombstoneTag.DeleteReserve, reservePub);
if (tombstoneSet.has(ts)) {
continue;
}
checkLogicInvariant(!!reservePub); checkLogicInvariant(!!reservePub);
const existingReserve = await tx.get(Stores.reserves, reservePub); const existingReserve = await tx.get(Stores.reserves, reservePub);
const instructedAmount = Amounts.parseOrThrow( const instructedAmount = Amounts.parseOrThrow(
@ -426,6 +442,13 @@ export async function importBackup(
}); });
} }
for (const backupWg of backupReserve.withdrawal_groups) { for (const backupWg of backupReserve.withdrawal_groups) {
const ts = makeEventId(
TombstoneTag.DeleteWithdrawalGroup,
backupWg.withdrawal_group_id,
);
if (tombstoneSet.has(ts)) {
continue;
}
const existingWg = await tx.get( const existingWg = await tx.get(
Stores.withdrawalGroups, Stores.withdrawalGroups,
backupWg.withdrawal_group_id, backupWg.withdrawal_group_id,
@ -456,6 +479,13 @@ export async function importBackup(
} }
for (const backupProposal of backupBlob.proposals) { for (const backupProposal of backupBlob.proposals) {
const ts = makeEventId(
TombstoneTag.DeletePayment,
backupProposal.proposal_id,
);
if (tombstoneSet.has(ts)) {
continue;
}
const existingProposal = await tx.get( const existingProposal = await tx.get(
Stores.proposals, Stores.proposals,
backupProposal.proposal_id, backupProposal.proposal_id,
@ -555,6 +585,13 @@ export async function importBackup(
} }
for (const backupPurchase of backupBlob.purchases) { for (const backupPurchase of backupBlob.purchases) {
const ts = makeEventId(
TombstoneTag.DeletePayment,
backupPurchase.proposal_id,
);
if (tombstoneSet.has(ts)) {
continue;
}
const existingPurchase = await tx.get( const existingPurchase = await tx.get(
Stores.purchases, Stores.purchases,
backupPurchase.proposal_id, backupPurchase.proposal_id,
@ -704,6 +741,13 @@ export async function importBackup(
} }
for (const backupRefreshGroup of backupBlob.refresh_groups) { for (const backupRefreshGroup of backupBlob.refresh_groups) {
const ts = makeEventId(
TombstoneTag.DeleteRefreshGroup,
backupRefreshGroup.refresh_group_id,
);
if (tombstoneSet.has(ts)) {
continue;
}
const existingRg = await tx.get( const existingRg = await tx.get(
Stores.refreshGroups, Stores.refreshGroups,
backupRefreshGroup.refresh_group_id, backupRefreshGroup.refresh_group_id,
@ -783,6 +827,10 @@ export async function importBackup(
} }
for (const backupTip of backupBlob.tips) { for (const backupTip of backupBlob.tips) {
const ts = makeEventId(TombstoneTag.DeleteTip, backupTip.wallet_tip_id);
if (tombstoneSet.has(ts)) {
continue;
}
const existingTip = await tx.get(Stores.tips, backupTip.wallet_tip_id); const existingTip = await tx.get(Stores.tips, backupTip.wallet_tip_id);
if (!existingTip) { if (!existingTip) {
const denomsSel = await getDenomSelStateFromBackup( const denomsSel = await getDenomSelStateFromBackup(
@ -809,6 +857,36 @@ export async function importBackup(
}); });
} }
} }
// 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 backupBlob.tombstones) {
const [type, ...rest] = tombstone.split(":");
if (type === TombstoneTag.DeleteDepositGroup) {
await tx.delete(Stores.depositGroups, rest[0]);
} else if (type === TombstoneTag.DeletePayment) {
await tx.delete(Stores.purchases, rest[0]);
await tx.delete(Stores.proposals, rest[0]);
} else if (type === TombstoneTag.DeleteRefreshGroup) {
await tx.delete(Stores.refreshGroups, rest[0]);
} else if (type === TombstoneTag.DeleteRefund) {
// Nothing required, will just prevent display
// in the transactions list
} else if (type === TombstoneTag.DeleteReserve) {
// FIXME: Once we also have account (=kyc) reserves,
// we need to check if the reserve is an account before deleting here
await tx.delete(Stores.reserves, rest[0]);
} else if (type === TombstoneTag.DeleteTip) {
await tx.delete(Stores.tips, rest[0]);
} else if (type === TombstoneTag.DeleteWithdrawalGroup) {
await tx.delete(Stores.withdrawalGroups, rest[0]);
} else {
logger.warn(`unable to process tombstone of type '${type}'`);
}
}
}, },
); );
} }

View File

@ -28,9 +28,6 @@ export interface WalletBackupConfState {
/** /**
* Last hash of the canonicalized plain-text backup. * Last hash of the canonicalized plain-text backup.
*
* Used to determine whether the wallet's content changed
* and we need to bump the clock.
*/ */
lastBackupPlainHash?: string; lastBackupPlainHash?: string;

View File

@ -42,7 +42,7 @@ import { getFundingPaytoUris } from "./reserves";
/** /**
* Create an event ID from the type and the primary key for the event. * Create an event ID from the type and the primary key for the event.
*/ */
function makeEventId( export function makeEventId(
type: TransactionType | TombstoneTag, type: TransactionType | TombstoneTag,
...args: string[] ...args: string[]
): string { ): string {