From aba173d8a906fa0ede0c3660bd37b11fb7a6a127 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 30 Aug 2023 16:51:51 +0200 Subject: wallet-core: open DB inside wallet handle, back up meta DB as well --- packages/taler-wallet-core/src/db.ts | 78 +++++++++++++++------- packages/taler-wallet-core/src/host-impl.node.ts | 11 +-- packages/taler-wallet-core/src/host-impl.qtart.ts | 20 ++---- .../taler-wallet-core/src/internal-wallet-state.ts | 4 ++ packages/taler-wallet-core/src/util/query.ts | 2 +- packages/taler-wallet-core/src/wallet.ts | 50 ++++++++++---- .../taler-wallet-webextension/src/wxBackend.ts | 9 +-- 7 files changed, 104 insertions(+), 70 deletions(-) diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index ba5295dda..efc0333f4 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -104,7 +104,7 @@ import { RetryInfo, TaskIdentifiers } from "./operations/common.js"; * for all previous versions must be written, which should be * 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 @@ -112,7 +112,7 @@ export const TALER_DB_NAME = "taler-wallet-main-v9"; * * (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"; @@ -2806,25 +2806,36 @@ export interface DbDump { }; } -export function exportDb(db: IDBDatabase): Promise { - const dbDump: DbDump = { - databases: {}, - }; +export async function exportSingleDb( + idb: IDBFactory, + dbName: string, +): Promise { + 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 = { - version: db.version, + const singleDbDump: DbDumpDatabase = { + version: myDb.version, stores: {}, }; - dbDump.databases[db.name] = walletDb; return new Promise((resolve, reject) => { - const tx = db.transaction(Array.from(db.objectStoreNames)); + const tx = myDb.transaction(Array.from(myDb.objectStoreNames)); tx.addEventListener("complete", () => { - resolve(dbDump); + resolve(singleDbDump); }); // tslint:disable-next-line:prefer-for-of - for (let i = 0; i < db.objectStoreNames.length; i++) { - const name = db.objectStoreNames[i]; + for (let i = 0; i < myDb.objectStoreNames.length; i++) { + const name = myDb.objectStoreNames[i]; const store = tx.objectStore(name); const storeDump: DbStoreDump = { autoIncrement: store.autoIncrement, @@ -2842,7 +2853,7 @@ export function exportDb(db: IDBDatabase): Promise { unique: index.unique, }; } - walletDb.stores[name] = storeDump; + singleDbDump.stores[name] = storeDump; store.openCursor().addEventListener("success", (e: Event) => { const cursor = (e.target as any).result; if (cursor) { @@ -2862,6 +2873,23 @@ export function exportDb(db: IDBDatabase): Promise { }); } +export async function exportDb(idb: IDBFactory): Promise { + 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 { name: string; stores: { [s: string]: any }; @@ -2902,13 +2930,13 @@ export async function importDb(db: IDBDatabase, object: any): Promise { // looks like a IDBDatabase const someDatabase = object.databases; - if (TALER_META_DB_NAME in someDatabase) { + if (TALER_WALLET_META_DB_NAME in someDatabase) { //looks like a taler database const currentMainDbValue = - someDatabase[TALER_META_DB_NAME].objectStores.metaConfig.records[0] - .value.value; + someDatabase[TALER_WALLET_META_DB_NAME].objectStores.metaConfig + .records[0].value.value; - if (currentMainDbValue !== TALER_DB_NAME) { + if (currentMainDbValue !== TALER_WALLET_MAIN_DB_NAME) { console.log("not the current database version"); } @@ -3236,7 +3264,7 @@ export async function openTalerDatabase( ): Promise> { const metaDbHandle = await openDatabase( idbFactory, - TALER_META_DB_NAME, + TALER_WALLET_META_DB_NAME, 1, () => {}, onMetaDbUpgradeNeeded, @@ -3249,17 +3277,17 @@ export async function openTalerDatabase( .runReadWrite(async (tx) => { const dbVersionRecord = await tx.metaConfig.get(CURRENT_DB_CONFIG_KEY); if (!dbVersionRecord) { - currentMainVersion = TALER_DB_NAME; + currentMainVersion = TALER_WALLET_MAIN_DB_NAME; await tx.metaConfig.put({ key: CURRENT_DB_CONFIG_KEY, - value: TALER_DB_NAME, + value: TALER_WALLET_MAIN_DB_NAME, }); } else { currentMainVersion = dbVersionRecord.value; } }); - if (currentMainVersion !== TALER_DB_NAME) { + if (currentMainVersion !== TALER_WALLET_MAIN_DB_NAME) { switch (currentMainVersion) { case "taler-wallet-main-v2": case "taler-wallet-main-v3": @@ -3275,7 +3303,7 @@ export async function openTalerDatabase( .runReadWrite(async (tx) => { await tx.metaConfig.put({ key: CURRENT_DB_CONFIG_KEY, - value: TALER_DB_NAME, + value: TALER_WALLET_MAIN_DB_NAME, }); }); break; @@ -3288,7 +3316,7 @@ export async function openTalerDatabase( const mainDbHandle = await openDatabase( idbFactory, - TALER_DB_NAME, + TALER_WALLET_MAIN_DB_NAME, WALLET_DB_MINOR_VERSION, onVersionChange, onTalerDbUpgradeNeeded, @@ -3305,7 +3333,7 @@ export async function deleteTalerDatabase( idbFactory: IDBFactory, ): Promise { 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.onsuccess = () => resolve(); }); diff --git a/packages/taler-wallet-core/src/host-impl.node.ts b/packages/taler-wallet-core/src/host-impl.node.ts index 6a4f21d79..0b6539306 100644 --- a/packages/taler-wallet-core/src/host-impl.node.ts +++ b/packages/taler-wallet-core/src/host-impl.node.ts @@ -139,13 +139,6 @@ export async function createNativeWalletHost2( }); } - const myVersionChange = (): Promise => { - 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; if (args.persistentStoragePath &&args.persistentStoragePath.endsWith(".json")) { @@ -160,8 +153,6 @@ export async function createNativeWalletHost2( shimIndexedDB(dbResp.idbFactory); - const myDb = await openTalerDatabase(myIdbFactory, myVersionChange); - let workerFactory; const cryptoWorkerType = args.cryptoWorkerType ?? "node-worker-thread"; if (cryptoWorkerType === "sync") { @@ -189,7 +180,7 @@ export async function createNativeWalletHost2( const timer = new SetTimeoutTimerAPI(); const w = await Wallet.create( - myDb, + myIdbFactory, myHttpLib, timer, workerFactory, diff --git a/packages/taler-wallet-core/src/host-impl.qtart.ts b/packages/taler-wallet-core/src/host-impl.qtart.ts index 720f5affb..81dbe0acd 100644 --- a/packages/taler-wallet-core/src/host-impl.qtart.ts +++ b/packages/taler-wallet-core/src/host-impl.qtart.ts @@ -110,7 +110,7 @@ async function makeSqliteDb( return { ...myBackend.accessStats, primitiveStatements: numStmt, - } + }; }, idbFactory: myBridgeIdbFactory, }; @@ -167,12 +167,15 @@ export async function createNativeWalletHost2( 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!)"); dbResp = await makeFileDb(args); } else { logger.info("using sqlite3 DB backend (experimental!)"); - dbResp = await makeSqliteDb(args) + dbResp = await makeSqliteDb(args); } const myIdbFactory: IDBFactory = dbResp.idbFactory as any as IDBFactory; @@ -189,22 +192,13 @@ export async function createNativeWalletHost2( }); } - const myVersionChange = (): Promise => { - 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; workerFactory = new SynchronousCryptoWorkerFactoryPlain(); const timer = new SetTimeoutTimerAPI(); const w = await Wallet.create( - myDb, + myIdbFactory, myHttpLib, timer, workerFactory, diff --git a/packages/taler-wallet-core/src/internal-wallet-state.ts b/packages/taler-wallet-core/src/internal-wallet-state.ts index 76aee05bd..a189c9cb3 100644 --- a/packages/taler-wallet-core/src/internal-wallet-state.ts +++ b/packages/taler-wallet-core/src/internal-wallet-state.ts @@ -54,6 +54,7 @@ import { } from "./util/query.js"; import { TimerGroup } from "./util/timer.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_RESERVES_LOCK = "exchange-reserves-lock"; @@ -203,6 +204,9 @@ export interface InternalWalletState { denomPubHash: string, ): Promise; + ensureWalletDbOpen(): Promise; + + idb: IDBFactory; db: DbAccess; http: HttpRequestLibrary; diff --git a/packages/taler-wallet-core/src/util/query.ts b/packages/taler-wallet-core/src/util/query.ts index 71f80f8aa..eb2bddec1 100644 --- a/packages/taler-wallet-core/src/util/query.ts +++ b/packages/taler-wallet-core/src/util/query.ts @@ -239,7 +239,7 @@ class ResultStream { export function openDatabase( idbFactory: IDBFactory, databaseName: string, - databaseVersion: number, + databaseVersion: number | undefined, onVersionChange: () => void, onUpgradeNeeded: ( db: IDBDatabase, diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index c9ccda20d..9f754ed69 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -139,6 +139,7 @@ import { clearDatabase, exportDb, importDb, + openTalerDatabase, } from "./db.js"; import { DevExperimentHttpLib, applyDevExperiment } from "./dev-experiments.js"; import { @@ -315,6 +316,7 @@ import { getMaxPeerPushAmount, convertWithdrawalAmount, } from "./util/instructedAmountConversion.js"; +import { IDBFactory } from "@gnu-taler/idb-bridge"; const logger = new Logger("wallet.ts"); @@ -1539,7 +1541,7 @@ async function dispatchRequestInternal( return {}; } case WalletApiOperation.ExportDb: { - const dbDump = await exportDb(ws.db.idbHandle()); + const dbDump = await exportDb(ws.idb); return dbDump; } case WalletApiOperation.ImportDb: { @@ -1654,14 +1656,14 @@ export class Wallet { private _client: WalletCoreApiClient | undefined; private constructor( - db: DbAccess, + idb: IDBFactory, http: HttpRequestLibrary, timer: TimerAPI, cryptoWorkerFactory: CryptoWorkerFactory, config?: WalletConfigParameter, ) { this.ws = new InternalWalletStateImpl( - db, + idb, http, timer, cryptoWorkerFactory, @@ -1677,13 +1679,13 @@ export class Wallet { } static async create( - db: DbAccess, + idb: IDBFactory, http: HttpRequestLibrary, timer: TimerAPI, cryptoWorkerFactory: CryptoWorkerFactory, config?: WalletConfigParameter, ): Promise { - const w = new Wallet(db, http, timer, cryptoWorkerFactory, config); + const w = new Wallet(idb, http, timer, cryptoWorkerFactory, config); w._client = await getClientFromWalletState(w.ws); return w; } @@ -1725,19 +1727,22 @@ export class Wallet { this.ws.stop(); } - runPending(): Promise { + async runPending(): Promise { + await this.ws.ensureWalletDbOpen(); return runPending(this.ws); } - runTaskLoop(opts?: RetryLoopOpts): Promise { + async runTaskLoop(opts?: RetryLoopOpts): Promise { + await this.ws.ensureWalletDbOpen(); return runTaskLoop(this.ws, opts); } - handleCoreApiRequest( + async handleCoreApiRequest( operation: string, id: string, payload: unknown, ): Promise { + await this.ws.ensureWalletDbOpen(); return handleCoreApiRequest(this.ws, operation, id, payload); } } @@ -1801,12 +1806,17 @@ class InternalWalletStateImpl implements InternalWalletState { config: Readonly; + private _db: DbAccess | undefined = undefined; + + get db(): DbAccess { + if (!this._db) { + throw Error("db not initialized"); + } + return this._db; + } + constructor( - // FIXME: Make this a getter and make - // the actual value nullable. - // Check if we are in a DB migration / garbage collection - // and throw an error in that case. - public db: DbAccess, + public idb: IDBFactory, public http: HttpRequestLibrary, public timer: TimerAPI, cryptoWorkerFactory: CryptoWorkerFactory, @@ -1821,6 +1831,20 @@ class InternalWalletStateImpl implements InternalWalletState { } } + async ensureWalletDbOpen(): Promise { + if (this._db) { + return; + } + const myVersionChange = (): Promise => { + 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( ws: InternalWalletState, tx: GetReadOnlyAccess, diff --git a/packages/taler-wallet-webextension/src/wxBackend.ts b/packages/taler-wallet-webextension/src/wxBackend.ts index 95af1a3a4..f071d78df 100644 --- a/packages/taler-wallet-webextension/src/wxBackend.ts +++ b/packages/taler-wallet-webextension/src/wxBackend.ts @@ -298,13 +298,6 @@ async function reinitWallet(): Promise { } currentDatabase = undefined; // 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 cryptoWorker; let timer; @@ -325,7 +318,7 @@ async function reinitWallet(): Promise { const settings = await platform.getSettingsFromStorage(); logger.info("Setting up wallet"); const wallet = await Wallet.create( - currentDatabase, + indexedDB as any, httpLib, timer, cryptoWorker, -- cgit v1.2.3