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
* 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<DbDump> {
const dbDump: DbDump = {
databases: {},
};
export async function exportSingleDb(
idb: IDBFactory,
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 = {
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<DbDump> {
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<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 {
name: string;
stores: { [s: string]: any };
@ -2902,13 +2930,13 @@ export async function importDb(db: IDBDatabase, object: any): Promise<void> {
// 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<DbAccess<typeof WalletStoresV1>> {
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<void> {
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();
});

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;
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,

View File

@ -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<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;
workerFactory = new SynchronousCryptoWorkerFactoryPlain();
const timer = new SetTimeoutTimerAPI();
const w = await Wallet.create(
myDb,
myIdbFactory,
myHttpLib,
timer,
workerFactory,

View File

@ -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<DenominationInfo | undefined>;
ensureWalletDbOpen(): Promise<void>;
idb: IDBFactory;
db: DbAccess<typeof WalletStoresV1>;
http: HttpRequestLibrary;

View File

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

View File

@ -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<Op extends WalletApiOperation>(
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<typeof WalletStoresV1>,
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<typeof WalletStoresV1>,
idb: IDBFactory,
http: HttpRequestLibrary,
timer: TimerAPI,
cryptoWorkerFactory: CryptoWorkerFactory,
config?: WalletConfigParameter,
): 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);
return w;
}
@ -1725,19 +1727,22 @@ export class Wallet {
this.ws.stop();
}
runPending(): Promise<void> {
async runPending(): Promise<void> {
await this.ws.ensureWalletDbOpen();
return runPending(this.ws);
}
runTaskLoop(opts?: RetryLoopOpts): Promise<TaskLoopResult> {
async runTaskLoop(opts?: RetryLoopOpts): Promise<TaskLoopResult> {
await this.ws.ensureWalletDbOpen();
return runTaskLoop(this.ws, opts);
}
handleCoreApiRequest(
async handleCoreApiRequest(
operation: string,
id: string,
payload: unknown,
): Promise<CoreApiResponse> {
await this.ws.ensureWalletDbOpen();
return handleCoreApiRequest(this.ws, operation, id, payload);
}
}
@ -1801,12 +1806,17 @@ class InternalWalletStateImpl implements InternalWalletState {
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(
// 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<typeof WalletStoresV1>,
public idb: IDBFactory,
public http: HttpRequestLibrary,
public timer: TimerAPI,
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(
ws: InternalWalletState,
tx: GetReadOnlyAccess<typeof WalletStoresV1>,

View File

@ -298,13 +298,6 @@ async function reinitWallet(): Promise<void> {
}
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<void> {
const settings = await platform.getSettingsFromStorage();
logger.info("Setting up wallet");
const wallet = await Wallet.create(
currentDatabase,
indexedDB as any,
httpLib,
timer,
cryptoWorker,