wallet-core,wallet-cli: properly serialize manual DB export

This commit is contained in:
Florian Dold 2023-08-30 15:54:44 +02:00
parent 557213f9c4
commit 88f7338d7c
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
2 changed files with 116 additions and 120 deletions

View File

@ -257,8 +257,7 @@ async function createLocalWallet(
},
cryptoWorkerType: walletCliArgs.wallet.cryptoWorker as any,
config: {
features: {
},
features: {},
testing: {
devModeActive: checkEnvFlag("TALER_WALLET_DEV_MODE"),
denomselAllowLate: checkEnvFlag(
@ -651,9 +650,12 @@ walletCli
});
break;
case TalerUriAction.Reward: {
const res = await wallet.client.call(WalletApiOperation.PrepareReward, {
const res = await wallet.client.call(
WalletApiOperation.PrepareReward,
{
talerRewardUri: uri,
});
},
);
console.log("tip status", res);
await wallet.client.call(WalletApiOperation.AcceptReward, {
walletRewardId: res.walletRewardId,
@ -874,96 +876,13 @@ const backupCli = walletCli.subcommand("backupArgs", "backup", {
help: "Subcommands for backups",
});
backupCli
.subcommand("setDeviceId", "set-device-id")
.requiredArgument("deviceId", clk.STRING, {
help: "new device ID",
})
.action(async (args) => {
backupCli.subcommand("exportDb", "export-db").action(async (args) => {
await withWallet(args, async (wallet) => {
await wallet.client.call(WalletApiOperation.SetWalletDeviceId, {
walletDeviceId: args.setDeviceId.deviceId,
});
});
});
backupCli.subcommand("exportPlain", "export-plain").action(async (args) => {
await withWallet(args, async (wallet) => {
const backup = await wallet.client.call(
WalletApiOperation.ExportBackupPlain,
{},
);
const backup = await wallet.client.call(WalletApiOperation.ExportDb, {});
console.log(JSON.stringify(backup, undefined, 2));
});
});
backupCli.subcommand("recoverySave", "save-recovery").action(async (args) => {
await withWallet(args, async (wallet) => {
const recoveryJson = await wallet.client.call(
WalletApiOperation.ExportBackupRecovery,
{},
);
console.log(JSON.stringify(recoveryJson, undefined, 2));
});
});
backupCli.subcommand("run", "run").action(async (args) => {
await withWallet(args, async (wallet) => {
await wallet.client.call(WalletApiOperation.RunBackupCycle, {});
});
});
backupCli.subcommand("status", "status").action(async (args) => {
await withWallet(args, async (wallet) => {
const status = await wallet.client.call(
WalletApiOperation.GetBackupInfo,
{},
);
console.log(JSON.stringify(status, undefined, 2));
});
});
backupCli
.subcommand("recoveryLoad", "load-recovery")
.maybeOption("strategy", ["--strategy"], clk.STRING, {
help: "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.client.call(WalletApiOperation.ImportBackupRecovery, {
recovery: data,
strategy,
});
});
});
backupCli
.subcommand("addProvider", "add-provider")
.requiredArgument("url", clk.STRING)
.maybeArgument("name", clk.STRING)
.flag("activate", ["--activate"])
.action(async (args) => {
await withWallet(args, async (wallet) => {
await wallet.client.call(WalletApiOperation.AddBackupProvider, {
backupProviderBaseUrl: args.addProvider.url,
activate: args.addProvider.activate,
name: args.addProvider.name || args.addProvider.url,
});
});
});
const depositCli = walletCli.subcommand("depositArgs", "deposit", {
help: "Subcommands for depositing money to payto:// accounts",
});

View File

@ -23,6 +23,7 @@ import {
IDBFactory,
IDBObjectStore,
IDBTransaction,
structuredEncapsulate,
} from "@gnu-taler/idb-bridge";
import {
AgeCommitmentProof,
@ -566,10 +567,31 @@ export interface ExchangeDetailsPointer {
updateClock: TalerPreciseTimestamp;
}
export enum ExchangeEntryDbRecordStatus {
Preset = 1,
Ephemeral = 2,
Used = 3,
}
export enum ExchangeEntryDbUpdateStatus {
Initial = 1,
InitialUpdate = 2,
Suspended = 3,
Failed = 4,
OutdatedUpdate = 5,
Ready = 6,
ReadyUpdate = 7,
}
/**
* Timestamp stored as a IEEE 754 double, in milliseconds.
*/
export type DbIndexableTimestampMs = number;
/**
* Exchange record as stored in the wallet's database.
*/
export interface ExchangeRecord {
export interface ExchangeEntryRecord {
/**
* Base url of the exchange.
*/
@ -594,13 +616,12 @@ export interface ExchangeRecord {
*/
detailsPointer: ExchangeDetailsPointer | undefined;
/**
* Is this a permanent or temporary exchange record?
*/
permanent: boolean;
entryStatus: ExchangeEntryDbRecordStatus;
updateStatus: ExchangeEntryDbUpdateStatus;
/**
* Last time when the exchange was updated (both /keys and /wire).
* Last time when the exchange /keys info was updated.
*/
lastUpdate: TalerPreciseTimestamp | undefined;
@ -608,20 +629,21 @@ export interface ExchangeRecord {
* Next scheduled update for the exchange.
*
* (This field must always be present, so we can index on the timestamp.)
*
* FIXME: To index on the timestamp, this needs to be a number of
* binary timestamp!
*/
nextUpdate: TalerPreciseTimestamp;
nextUpdateStampMs: DbIndexableTimestampMs;
lastKeysEtag: string | undefined;
lastWireEtag: string | undefined;
/**
* Next time that we should check if coins need to be refreshed.
*
* Updated whenever the exchange's denominations are updated or when
* the refresh check has been done.
*/
nextRefreshCheck: TalerPreciseTimestamp;
nextRefreshCheckStampMs: DbIndexableTimestampMs;
/**
* Public key of the reserve that we're currently using for
@ -2424,7 +2446,7 @@ export const WalletStoresV1 = {
),
exchanges: describeStore(
"exchanges",
describeContents<ExchangeRecord>({
describeContents<ExchangeEntryRecord>({
keyPath: "baseUrl",
}),
{},
@ -2713,11 +2735,10 @@ export type WalletDbReadOnlyTransaction<
Stores extends StoreNames<typeof WalletStoresV1> & string,
> = DbReadOnlyTransaction<typeof WalletStoresV1, Stores>;
export type WalletReadWriteTransaction<
export type WalletDbReadWriteTransaction<
Stores extends StoreNames<typeof WalletStoresV1> & string,
> = DbReadWriteTransaction<typeof WalletStoresV1, Stores>;
/**
* An applied migration.
*/
@ -2748,29 +2769,85 @@ export const walletMetadataStore = {
),
};
export function exportDb(db: IDBDatabase): Promise<any> {
const dump = {
name: db.name,
stores: {} as { [s: string]: any },
version: db.version,
export interface DbDumpRecord {
/**
* Key, serialized with structuredEncapsulated.
*/
key: any;
/**
* Value, serialized with structuredEncapsulated.
*/
value: any;
}
export interface DbIndexDump {
keyPath: string | string[];
multiEntry: boolean;
unique: boolean;
}
export interface DbStoreDump {
keyPath?: string | string[];
autoIncrement: boolean;
indexes: { [indexName: string]: DbIndexDump };
records: DbDumpRecord[];
}
export interface DbDumpDatabase {
version: number;
stores: { [storeName: string]: DbStoreDump };
}
export interface DbDump {
databases: {
[name: string]: DbDumpDatabase;
};
}
export function exportDb(db: IDBDatabase): Promise<DbDump> {
const dbDump: DbDump = {
databases: {},
};
const walletDb: DbDumpDatabase = {
version: db.version,
stores: {},
};
dbDump.databases[db.name] = walletDb;
return new Promise((resolve, reject) => {
const tx = db.transaction(Array.from(db.objectStoreNames));
tx.addEventListener("complete", () => {
resolve(dump);
resolve(dbDump);
});
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < db.objectStoreNames.length; i++) {
const name = db.objectStoreNames[i];
const storeDump = {} as { [s: string]: any };
dump.stores[name] = storeDump;
tx.objectStore(name)
.openCursor()
.addEventListener("success", (e: Event) => {
const store = tx.objectStore(name);
const storeDump: DbStoreDump = {
autoIncrement: store.autoIncrement,
keyPath: store.keyPath,
indexes: {},
records: [],
};
const indexNames = store.indexNames;
for (let j = 0; j < indexNames.length; j++) {
const idxName = indexNames[j];
const index = store.index(idxName);
storeDump.indexes[idxName] = {
keyPath: index.keyPath,
multiEntry: index.multiEntry,
unique: index.unique,
};
}
walletDb.stores[name] = storeDump;
store.openCursor().addEventListener("success", (e: Event) => {
const cursor = (e.target as any).result;
if (cursor) {
storeDump[cursor.key] = cursor.value;
storeDump.records.push({
key: structuredEncapsulate(cursor.key),
value: structuredEncapsulate(cursor.value),
});
cursor.continue();
}
});