wallet-core: open DB inside wallet handle, back up meta DB as well

This commit is contained in:
Florian Dold 2023-08-30 16:51:51 +02:00
parent 53613a137d
commit aba173d8a9
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
7 changed files with 104 additions and 70 deletions

View File

@ -104,7 +104,7 @@ import { RetryInfo, TaskIdentifiers } from "./operations/common.js";
* for all previous versions must be written, which should be * for all previous versions must be written, which should be
* avoided. * avoided.
*/ */
export const TALER_DB_NAME = "taler-wallet-main-v9"; export const TALER_WALLET_MAIN_DB_NAME = "taler-wallet-main-v9";
/** /**
* Name of the metadata database. This database is used * Name of the metadata database. This database is used
@ -112,7 +112,7 @@ export const TALER_DB_NAME = "taler-wallet-main-v9";
* *
* (Minor migrations are handled via upgrade transactions.) * (Minor migrations are handled via upgrade transactions.)
*/ */
export const TALER_META_DB_NAME = "taler-wallet-meta"; export const TALER_WALLET_META_DB_NAME = "taler-wallet-meta";
export const CURRENT_DB_CONFIG_KEY = "currentMainDbName"; export const CURRENT_DB_CONFIG_KEY = "currentMainDbName";
@ -2806,25 +2806,36 @@ export interface DbDump {
}; };
} }
export function exportDb(db: IDBDatabase): Promise<DbDump> { export async function exportSingleDb(
const dbDump: DbDump = { idb: IDBFactory,
databases: {}, dbName: string,
}; ): Promise<DbDumpDatabase> {
const myDb = await openDatabase(
idb,
dbName,
undefined,
() => {
// May not happen, since we're not requesting a specific version
throw Error("unexpected version change");
},
() => {
logger.info("unexpected onupgradeneeded");
},
);
const walletDb: DbDumpDatabase = { const singleDbDump: DbDumpDatabase = {
version: db.version, version: myDb.version,
stores: {}, stores: {},
}; };
dbDump.databases[db.name] = walletDb;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const tx = db.transaction(Array.from(db.objectStoreNames)); const tx = myDb.transaction(Array.from(myDb.objectStoreNames));
tx.addEventListener("complete", () => { tx.addEventListener("complete", () => {
resolve(dbDump); resolve(singleDbDump);
}); });
// tslint:disable-next-line:prefer-for-of // tslint:disable-next-line:prefer-for-of
for (let i = 0; i < db.objectStoreNames.length; i++) { for (let i = 0; i < myDb.objectStoreNames.length; i++) {
const name = db.objectStoreNames[i]; const name = myDb.objectStoreNames[i];
const store = tx.objectStore(name); const store = tx.objectStore(name);
const storeDump: DbStoreDump = { const storeDump: DbStoreDump = {
autoIncrement: store.autoIncrement, autoIncrement: store.autoIncrement,
@ -2842,7 +2853,7 @@ export function exportDb(db: IDBDatabase): Promise<DbDump> {
unique: index.unique, unique: index.unique,
}; };
} }
walletDb.stores[name] = storeDump; singleDbDump.stores[name] = storeDump;
store.openCursor().addEventListener("success", (e: Event) => { store.openCursor().addEventListener("success", (e: Event) => {
const cursor = (e.target as any).result; const cursor = (e.target as any).result;
if (cursor) { if (cursor) {
@ -2862,6 +2873,23 @@ export function exportDb(db: IDBDatabase): Promise<DbDump> {
}); });
} }
export async function exportDb(idb: IDBFactory): Promise<DbDump> {
const dbDump: DbDump = {
databases: {},
};
dbDump.databases[TALER_WALLET_META_DB_NAME] = await exportSingleDb(
idb,
TALER_WALLET_META_DB_NAME,
);
dbDump.databases[TALER_WALLET_MAIN_DB_NAME] = await exportSingleDb(
idb,
TALER_WALLET_MAIN_DB_NAME,
);
return dbDump;
}
export interface DatabaseDump { export interface DatabaseDump {
name: string; name: string;
stores: { [s: string]: any }; stores: { [s: string]: any };
@ -2902,13 +2930,13 @@ export async function importDb(db: IDBDatabase, object: any): Promise<void> {
// looks like a IDBDatabase // looks like a IDBDatabase
const someDatabase = object.databases; const someDatabase = object.databases;
if (TALER_META_DB_NAME in someDatabase) { if (TALER_WALLET_META_DB_NAME in someDatabase) {
//looks like a taler database //looks like a taler database
const currentMainDbValue = const currentMainDbValue =
someDatabase[TALER_META_DB_NAME].objectStores.metaConfig.records[0] someDatabase[TALER_WALLET_META_DB_NAME].objectStores.metaConfig
.value.value; .records[0].value.value;
if (currentMainDbValue !== TALER_DB_NAME) { if (currentMainDbValue !== TALER_WALLET_MAIN_DB_NAME) {
console.log("not the current database version"); console.log("not the current database version");
} }
@ -3236,7 +3264,7 @@ export async function openTalerDatabase(
): Promise<DbAccess<typeof WalletStoresV1>> { ): Promise<DbAccess<typeof WalletStoresV1>> {
const metaDbHandle = await openDatabase( const metaDbHandle = await openDatabase(
idbFactory, idbFactory,
TALER_META_DB_NAME, TALER_WALLET_META_DB_NAME,
1, 1,
() => {}, () => {},
onMetaDbUpgradeNeeded, onMetaDbUpgradeNeeded,
@ -3249,17 +3277,17 @@ export async function openTalerDatabase(
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
const dbVersionRecord = await tx.metaConfig.get(CURRENT_DB_CONFIG_KEY); const dbVersionRecord = await tx.metaConfig.get(CURRENT_DB_CONFIG_KEY);
if (!dbVersionRecord) { if (!dbVersionRecord) {
currentMainVersion = TALER_DB_NAME; currentMainVersion = TALER_WALLET_MAIN_DB_NAME;
await tx.metaConfig.put({ await tx.metaConfig.put({
key: CURRENT_DB_CONFIG_KEY, key: CURRENT_DB_CONFIG_KEY,
value: TALER_DB_NAME, value: TALER_WALLET_MAIN_DB_NAME,
}); });
} else { } else {
currentMainVersion = dbVersionRecord.value; currentMainVersion = dbVersionRecord.value;
} }
}); });
if (currentMainVersion !== TALER_DB_NAME) { if (currentMainVersion !== TALER_WALLET_MAIN_DB_NAME) {
switch (currentMainVersion) { switch (currentMainVersion) {
case "taler-wallet-main-v2": case "taler-wallet-main-v2":
case "taler-wallet-main-v3": case "taler-wallet-main-v3":
@ -3275,7 +3303,7 @@ export async function openTalerDatabase(
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
await tx.metaConfig.put({ await tx.metaConfig.put({
key: CURRENT_DB_CONFIG_KEY, key: CURRENT_DB_CONFIG_KEY,
value: TALER_DB_NAME, value: TALER_WALLET_MAIN_DB_NAME,
}); });
}); });
break; break;
@ -3288,7 +3316,7 @@ export async function openTalerDatabase(
const mainDbHandle = await openDatabase( const mainDbHandle = await openDatabase(
idbFactory, idbFactory,
TALER_DB_NAME, TALER_WALLET_MAIN_DB_NAME,
WALLET_DB_MINOR_VERSION, WALLET_DB_MINOR_VERSION,
onVersionChange, onVersionChange,
onTalerDbUpgradeNeeded, onTalerDbUpgradeNeeded,
@ -3305,7 +3333,7 @@ export async function deleteTalerDatabase(
idbFactory: IDBFactory, idbFactory: IDBFactory,
): Promise<void> { ): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const req = idbFactory.deleteDatabase(TALER_DB_NAME); const req = idbFactory.deleteDatabase(TALER_WALLET_MAIN_DB_NAME);
req.onerror = () => reject(req.error); req.onerror = () => reject(req.error);
req.onsuccess = () => resolve(); req.onsuccess = () => resolve();
}); });

View File

@ -139,13 +139,6 @@ export async function createNativeWalletHost2(
}); });
} }
const myVersionChange = (): Promise<void> => {
logger.error("version change requested, should not happen");
throw Error(
"BUG: wallet DB version change event can't happen with memory IDB",
);
};
let dbResp: MakeDbResult; let dbResp: MakeDbResult;
if (args.persistentStoragePath &&args.persistentStoragePath.endsWith(".json")) { if (args.persistentStoragePath &&args.persistentStoragePath.endsWith(".json")) {
@ -160,8 +153,6 @@ export async function createNativeWalletHost2(
shimIndexedDB(dbResp.idbFactory); shimIndexedDB(dbResp.idbFactory);
const myDb = await openTalerDatabase(myIdbFactory, myVersionChange);
let workerFactory; let workerFactory;
const cryptoWorkerType = args.cryptoWorkerType ?? "node-worker-thread"; const cryptoWorkerType = args.cryptoWorkerType ?? "node-worker-thread";
if (cryptoWorkerType === "sync") { if (cryptoWorkerType === "sync") {
@ -189,7 +180,7 @@ export async function createNativeWalletHost2(
const timer = new SetTimeoutTimerAPI(); const timer = new SetTimeoutTimerAPI();
const w = await Wallet.create( const w = await Wallet.create(
myDb, myIdbFactory,
myHttpLib, myHttpLib,
timer, timer,
workerFactory, workerFactory,

View File

@ -110,7 +110,7 @@ async function makeSqliteDb(
return { return {
...myBackend.accessStats, ...myBackend.accessStats,
primitiveStatements: numStmt, primitiveStatements: numStmt,
} };
}, },
idbFactory: myBridgeIdbFactory, idbFactory: myBridgeIdbFactory,
}; };
@ -167,12 +167,15 @@ export async function createNativeWalletHost2(
let dbResp: MakeDbResult; let dbResp: MakeDbResult;
if (args.persistentStoragePath && args.persistentStoragePath.endsWith(".json")) { if (
args.persistentStoragePath &&
args.persistentStoragePath.endsWith(".json")
) {
logger.info("using JSON file DB backend (slow!)"); logger.info("using JSON file DB backend (slow!)");
dbResp = await makeFileDb(args); dbResp = await makeFileDb(args);
} else { } else {
logger.info("using sqlite3 DB backend (experimental!)"); logger.info("using sqlite3 DB backend (experimental!)");
dbResp = await makeSqliteDb(args) dbResp = await makeSqliteDb(args);
} }
const myIdbFactory: IDBFactory = dbResp.idbFactory as any as IDBFactory; const myIdbFactory: IDBFactory = dbResp.idbFactory as any as IDBFactory;
@ -189,22 +192,13 @@ export async function createNativeWalletHost2(
}); });
} }
const myVersionChange = (): Promise<void> => {
logger.error("version change requested, should not happen");
throw Error(
"BUG: wallet DB version change event can't happen with memory IDB",
);
};
const myDb = await openTalerDatabase(myIdbFactory, myVersionChange);
let workerFactory; let workerFactory;
workerFactory = new SynchronousCryptoWorkerFactoryPlain(); workerFactory = new SynchronousCryptoWorkerFactoryPlain();
const timer = new SetTimeoutTimerAPI(); const timer = new SetTimeoutTimerAPI();
const w = await Wallet.create( const w = await Wallet.create(
myDb, myIdbFactory,
myHttpLib, myHttpLib,
timer, timer,
workerFactory, workerFactory,

View File

@ -54,6 +54,7 @@ import {
} from "./util/query.js"; } from "./util/query.js";
import { TimerGroup } from "./util/timer.js"; import { TimerGroup } from "./util/timer.js";
import { WalletConfig } from "./wallet-api-types.js"; import { WalletConfig } from "./wallet-api-types.js";
import { IDBFactory } from "@gnu-taler/idb-bridge";
export const EXCHANGE_COINS_LOCK = "exchange-coins-lock"; export const EXCHANGE_COINS_LOCK = "exchange-coins-lock";
export const EXCHANGE_RESERVES_LOCK = "exchange-reserves-lock"; export const EXCHANGE_RESERVES_LOCK = "exchange-reserves-lock";
@ -203,6 +204,9 @@ export interface InternalWalletState {
denomPubHash: string, denomPubHash: string,
): Promise<DenominationInfo | undefined>; ): Promise<DenominationInfo | undefined>;
ensureWalletDbOpen(): Promise<void>;
idb: IDBFactory;
db: DbAccess<typeof WalletStoresV1>; db: DbAccess<typeof WalletStoresV1>;
http: HttpRequestLibrary; http: HttpRequestLibrary;

View File

@ -239,7 +239,7 @@ class ResultStream<T> {
export function openDatabase( export function openDatabase(
idbFactory: IDBFactory, idbFactory: IDBFactory,
databaseName: string, databaseName: string,
databaseVersion: number, databaseVersion: number | undefined,
onVersionChange: () => void, onVersionChange: () => void,
onUpgradeNeeded: ( onUpgradeNeeded: (
db: IDBDatabase, db: IDBDatabase,

View File

@ -139,6 +139,7 @@ import {
clearDatabase, clearDatabase,
exportDb, exportDb,
importDb, importDb,
openTalerDatabase,
} from "./db.js"; } from "./db.js";
import { DevExperimentHttpLib, applyDevExperiment } from "./dev-experiments.js"; import { DevExperimentHttpLib, applyDevExperiment } from "./dev-experiments.js";
import { import {
@ -315,6 +316,7 @@ import {
getMaxPeerPushAmount, getMaxPeerPushAmount,
convertWithdrawalAmount, convertWithdrawalAmount,
} from "./util/instructedAmountConversion.js"; } from "./util/instructedAmountConversion.js";
import { IDBFactory } from "@gnu-taler/idb-bridge";
const logger = new Logger("wallet.ts"); const logger = new Logger("wallet.ts");
@ -1539,7 +1541,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
return {}; return {};
} }
case WalletApiOperation.ExportDb: { case WalletApiOperation.ExportDb: {
const dbDump = await exportDb(ws.db.idbHandle()); const dbDump = await exportDb(ws.idb);
return dbDump; return dbDump;
} }
case WalletApiOperation.ImportDb: { case WalletApiOperation.ImportDb: {
@ -1654,14 +1656,14 @@ export class Wallet {
private _client: WalletCoreApiClient | undefined; private _client: WalletCoreApiClient | undefined;
private constructor( private constructor(
db: DbAccess<typeof WalletStoresV1>, idb: IDBFactory,
http: HttpRequestLibrary, http: HttpRequestLibrary,
timer: TimerAPI, timer: TimerAPI,
cryptoWorkerFactory: CryptoWorkerFactory, cryptoWorkerFactory: CryptoWorkerFactory,
config?: WalletConfigParameter, config?: WalletConfigParameter,
) { ) {
this.ws = new InternalWalletStateImpl( this.ws = new InternalWalletStateImpl(
db, idb,
http, http,
timer, timer,
cryptoWorkerFactory, cryptoWorkerFactory,
@ -1677,13 +1679,13 @@ export class Wallet {
} }
static async create( static async create(
db: DbAccess<typeof WalletStoresV1>, idb: IDBFactory,
http: HttpRequestLibrary, http: HttpRequestLibrary,
timer: TimerAPI, timer: TimerAPI,
cryptoWorkerFactory: CryptoWorkerFactory, cryptoWorkerFactory: CryptoWorkerFactory,
config?: WalletConfigParameter, config?: WalletConfigParameter,
): Promise<Wallet> { ): Promise<Wallet> {
const w = new Wallet(db, http, timer, cryptoWorkerFactory, config); const w = new Wallet(idb, http, timer, cryptoWorkerFactory, config);
w._client = await getClientFromWalletState(w.ws); w._client = await getClientFromWalletState(w.ws);
return w; return w;
} }
@ -1725,19 +1727,22 @@ export class Wallet {
this.ws.stop(); this.ws.stop();
} }
runPending(): Promise<void> { async runPending(): Promise<void> {
await this.ws.ensureWalletDbOpen();
return runPending(this.ws); return runPending(this.ws);
} }
runTaskLoop(opts?: RetryLoopOpts): Promise<TaskLoopResult> { async runTaskLoop(opts?: RetryLoopOpts): Promise<TaskLoopResult> {
await this.ws.ensureWalletDbOpen();
return runTaskLoop(this.ws, opts); return runTaskLoop(this.ws, opts);
} }
handleCoreApiRequest( async handleCoreApiRequest(
operation: string, operation: string,
id: string, id: string,
payload: unknown, payload: unknown,
): Promise<CoreApiResponse> { ): Promise<CoreApiResponse> {
await this.ws.ensureWalletDbOpen();
return handleCoreApiRequest(this.ws, operation, id, payload); return handleCoreApiRequest(this.ws, operation, id, payload);
} }
} }
@ -1801,12 +1806,17 @@ class InternalWalletStateImpl implements InternalWalletState {
config: Readonly<WalletConfig>; config: Readonly<WalletConfig>;
private _db: DbAccess<typeof WalletStoresV1> | undefined = undefined;
get db(): DbAccess<typeof WalletStoresV1> {
if (!this._db) {
throw Error("db not initialized");
}
return this._db;
}
constructor( constructor(
// FIXME: Make this a getter and make public idb: IDBFactory,
// the actual value nullable.
// Check if we are in a DB migration / garbage collection
// and throw an error in that case.
public db: DbAccess<typeof WalletStoresV1>,
public http: HttpRequestLibrary, public http: HttpRequestLibrary,
public timer: TimerAPI, public timer: TimerAPI,
cryptoWorkerFactory: CryptoWorkerFactory, cryptoWorkerFactory: CryptoWorkerFactory,
@ -1821,6 +1831,20 @@ class InternalWalletStateImpl implements InternalWalletState {
} }
} }
async ensureWalletDbOpen(): Promise<void> {
if (this._db) {
return;
}
const myVersionChange = (): Promise<void> => {
logger.error("version change requested, should not happen");
throw Error(
"BUG: wallet DB version change event can't happen with memory IDB",
);
};
const myDb = await openTalerDatabase(this.idb, myVersionChange);
this._db = myDb;
}
async getTransactionState( async getTransactionState(
ws: InternalWalletState, ws: InternalWalletState,
tx: GetReadOnlyAccess<typeof WalletStoresV1>, tx: GetReadOnlyAccess<typeof WalletStoresV1>,

View File

@ -298,13 +298,6 @@ async function reinitWallet(): Promise<void> {
} }
currentDatabase = undefined; currentDatabase = undefined;
// setBadgeText({ text: "" }); // setBadgeText({ text: "" });
try {
currentDatabase = await openTalerDatabase(indexedDB as any, reinitWallet);
} catch (e) {
logger.error("could not open database", e);
walletInit.reject(e);
return;
}
let httpLib; let httpLib;
let cryptoWorker; let cryptoWorker;
let timer; let timer;
@ -325,7 +318,7 @@ async function reinitWallet(): Promise<void> {
const settings = await platform.getSettingsFromStorage(); const settings = await platform.getSettingsFromStorage();
logger.info("Setting up wallet"); logger.info("Setting up wallet");
const wallet = await Wallet.create( const wallet = await Wallet.create(
currentDatabase, indexedDB as any,
httpLib, httpLib,
timer, timer,
cryptoWorker, cryptoWorker,