wallet-core: DB improvements

This commit is contained in:
Florian Dold 2022-09-21 21:47:00 +02:00
parent a398959670
commit 4649469b58
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
5 changed files with 72 additions and 33 deletions

View File

@ -57,16 +57,17 @@ import {
TransactionInactiveError, TransactionInactiveError,
VersionError, VersionError,
} from "./util/errors.js"; } from "./util/errors.js";
import { FakeDOMStringList, fakeDOMStringList } from "./util/fakeDOMStringList.js"; import {
FakeDOMStringList,
fakeDOMStringList,
} from "./util/fakeDOMStringList.js";
import FakeEvent from "./util/FakeEvent.js"; import FakeEvent from "./util/FakeEvent.js";
import FakeEventTarget from "./util/FakeEventTarget.js"; import FakeEventTarget from "./util/FakeEventTarget.js";
import { makeStoreKeyValue } from "./util/makeStoreKeyValue.js"; import { makeStoreKeyValue } from "./util/makeStoreKeyValue.js";
import { normalizeKeyPath } from "./util/normalizeKeyPath.js"; import { normalizeKeyPath } from "./util/normalizeKeyPath.js";
import { openPromise } from "./util/openPromise.js"; import { openPromise } from "./util/openPromise.js";
import queueTask from "./util/queueTask.js"; import queueTask from "./util/queueTask.js";
import { import { checkStructuredCloneOrThrow } from "./util/structuredClone.js";
checkStructuredCloneOrThrow,
} from "./util/structuredClone.js";
import { validateKeyPath } from "./util/validateKeyPath.js"; import { validateKeyPath } from "./util/validateKeyPath.js";
import { valueToKey } from "./util/valueToKey.js"; import { valueToKey } from "./util/valueToKey.js";
@ -933,9 +934,8 @@ export class BridgeIDBFactory {
); );
// We need to expose the new version number to the upgrade transaction. // We need to expose the new version number to the upgrade transaction.
db._schema = this.backend.getCurrentTransactionSchema( db._schema =
backendTransaction, this.backend.getCurrentTransactionSchema(backendTransaction);
);
const transaction = db._internalTransaction( const transaction = db._internalTransaction(
[], [],
@ -1110,9 +1110,8 @@ export class BridgeIDBIndex implements IDBIndex {
this._backend.renameIndex(btx, this._objectStore.name, oldName, newName); this._backend.renameIndex(btx, this._objectStore.name, oldName, newName);
this._objectStore._transaction._db._schema = this._backend.getCurrentTransactionSchema( this._objectStore._transaction._db._schema =
btx, this._backend.getCurrentTransactionSchema(btx);
);
this._objectStore._indexesCache.delete(oldName); this._objectStore._indexesCache.delete(oldName);
this._objectStore._indexesCache.set(newName, this); this._objectStore._indexesCache.set(newName, this);
@ -1629,9 +1628,8 @@ export class BridgeIDBObjectStore implements IDBObjectStore {
} }
this._backend.renameObjectStore(btx, oldName, newName); this._backend.renameObjectStore(btx, oldName, newName);
this._transaction._db._schema = this._backend.getCurrentTransactionSchema( this._transaction._db._schema =
btx, this._backend.getCurrentTransactionSchema(btx);
);
// We don't modify scope, as the scope of the transaction // We don't modify scope, as the scope of the transaction
// doesn't matter if we're in an upgrade transaction. // doesn't matter if we're in an upgrade transaction.
@ -2235,11 +2233,8 @@ export class BridgeIDBRequest extends FakeEventTarget implements IDBRequest {
} }
return this._source; return this._source;
} }
_source: _source: BridgeIDBCursor | BridgeIDBIndex | BridgeIDBObjectStore | null =
| BridgeIDBCursor null;
| BridgeIDBIndex
| BridgeIDBObjectStore
| null = null;
transaction: BridgeIDBTransaction | null = null; transaction: BridgeIDBTransaction | null = null;
readyState: "done" | "pending" = "pending"; readyState: "done" | "pending" = "pending";
onsuccess: EventListener | null = null; onsuccess: EventListener | null = null;
@ -2302,7 +2297,8 @@ export class BridgeIDBRequest extends FakeEventTarget implements IDBRequest {
/** @public */ /** @public */
export class BridgeIDBOpenDBRequest export class BridgeIDBOpenDBRequest
extends BridgeIDBRequest extends BridgeIDBRequest
implements IDBOpenDBRequest { implements IDBOpenDBRequest
{
public onupgradeneeded: EventListener | null = null; public onupgradeneeded: EventListener | null = null;
public onblocked: EventListener | null = null; public onblocked: EventListener | null = null;
@ -2350,7 +2346,8 @@ function waitMacroQueue(): Promise<void> {
/** @public */ /** @public */
export class BridgeIDBTransaction export class BridgeIDBTransaction
extends FakeEventTarget extends FakeEventTarget
implements IDBTransaction { implements IDBTransaction
{
_committed: boolean = false; _committed: boolean = false;
/** /**
* A transaction is active as long as new operations can be * A transaction is active as long as new operations can be

View File

@ -60,6 +60,7 @@ function upgradeFromStoreMap(
const indexDesc: IndexDescriptor = swi.indexMap[indexName]; const indexDesc: IndexDescriptor = swi.indexMap[indexName];
s.createIndex(indexDesc.name, indexDesc.keyPath, { s.createIndex(indexDesc.name, indexDesc.keyPath, {
multiEntry: indexDesc.multiEntry, multiEntry: indexDesc.multiEntry,
unique: indexDesc.unique,
}); });
} }
} }

View File

@ -527,6 +527,8 @@ export interface ExchangeRecord {
/** /**
* Public key of the reserve that we're currently using for * Public key of the reserve that we're currently using for
* receiving P2P payments. * receiving P2P payments.
*
* FIXME: Make this a rowId of reserves!
*/ */
currentMergeReserveInfo?: MergeReserveInfo; currentMergeReserveInfo?: MergeReserveInfo;
} }
@ -569,6 +571,8 @@ export interface PlanchetRecord {
* *
* Can be the empty string (non-null/undefined for DB indexing) * Can be the empty string (non-null/undefined for DB indexing)
* if this is a tipping reserve. * if this is a tipping reserve.
*
* FIXME: Where is this used?
*/ */
reservePub: string; reservePub: string;
@ -763,6 +767,8 @@ export interface ProposalDownload {
/** /**
* Extracted / parsed data from the contract terms. * Extracted / parsed data from the contract terms.
*
* FIXME: Do we need to store *all* that data in duplicate?
*/ */
contractData: WalletContractData; contractData: WalletContractData;
} }
@ -1762,9 +1768,14 @@ export interface PeerPullPaymentIncomingRecord {
contractPriv: string; contractPriv: string;
} }
// FIXME: give this some smaller "row ID" to /**
// reference in other records? * Store for extra information about a reserve.
*
* Mostly used to store the private key for a reserve and to allow
* other records to reference the reserve key pair via a small row ID.
*/
export interface ReserveRecord { export interface ReserveRecord {
rowId?: number;
reservePub: string; reservePub: string;
reservePriv: string; reservePriv: string;
} }
@ -1844,9 +1855,12 @@ export const WalletStoresV1 = {
reserves: describeStore( reserves: describeStore(
"reserves", "reserves",
describeContents<ReserveRecord>({ describeContents<ReserveRecord>({
keyPath: "reservePub", keyPath: "rowId",
autoIncrement: true,
}), }),
{}, {
byReservePub: describeIndex("byReservePub", "reservePub", {}),
},
), ),
config: describeStore( config: describeStore(
"config", "config",
@ -1956,7 +1970,6 @@ export const WalletStoresV1 = {
keyPath: "withdrawalGroupId", keyPath: "withdrawalGroupId",
}), }),
{ {
byReservePub: describeIndex("byReservePub", "reservePub"),
byStatus: describeIndex("byStatus", "status"), byStatus: describeIndex("byStatus", "status"),
byTalerWithdrawUri: describeIndex( byTalerWithdrawUri: describeIndex(
"byTalerWithdrawUri", "byTalerWithdrawUri",

View File

@ -356,7 +356,9 @@ export async function processRecoupGroupHandler(
throw Error(`Coin ${coinPub} not found, can't request recoup`); throw Error(`Coin ${coinPub} not found, can't request recoup`);
} }
if (coin.coinSource.type === CoinSourceType.Withdraw) { if (coin.coinSource.type === CoinSourceType.Withdraw) {
const reserve = await tx.reserves.get(coin.coinSource.reservePub); const reserve = await tx.reserves.indexes.byReservePub.get(
coin.coinSource.reservePub,
);
if (!reserve) { if (!reserve) {
return; return;
} }

View File

@ -61,6 +61,13 @@ export interface IndexOptions {
* undefined if added in the first version. * undefined if added in the first version.
*/ */
versionAdded?: number; versionAdded?: number;
/**
* Does this index enforce unique keys?
*
* Defaults to false.
*/
unique?: boolean;
} }
function requestToPromise(req: IDBRequest): Promise<any> { function requestToPromise(req: IDBRequest): Promise<any> {
@ -276,6 +283,7 @@ export interface IndexDescriptor {
name: string; name: string;
keyPath: IDBKeyPath | IDBKeyPath[]; keyPath: IDBKeyPath | IDBKeyPath[];
multiEntry?: boolean; multiEntry?: boolean;
unique?: boolean;
} }
export interface StoreDescriptor<RecordType> { export interface StoreDescriptor<RecordType> {
@ -292,7 +300,11 @@ export interface StoreOptions {
export function describeContents<RecordType = never>( export function describeContents<RecordType = never>(
options: StoreOptions, options: StoreOptions,
): StoreDescriptor<RecordType> { ): StoreDescriptor<RecordType> {
return { keyPath: options.keyPath, _dummy: undefined as any }; return {
keyPath: options.keyPath,
_dummy: undefined as any,
autoIncrement: options.autoIncrement,
};
} }
export function describeIndex( export function describeIndex(
@ -304,6 +316,7 @@ export function describeIndex(
keyPath, keyPath,
name, name,
multiEntry: options.multiEntry, multiEntry: options.multiEntry,
unique: options.unique,
}; };
} }
@ -339,11 +352,18 @@ export interface StoreReadOnlyAccessor<RecordType, IndexMap> {
indexes: GetIndexReadOnlyAccess<RecordType, IndexMap>; indexes: GetIndexReadOnlyAccess<RecordType, IndexMap>;
} }
export interface InsertResponse {
/**
* Key of the newly inserted (via put/add) record.
*/
key: IDBValidKey;
}
export interface StoreReadWriteAccessor<RecordType, IndexMap> { export interface StoreReadWriteAccessor<RecordType, IndexMap> {
get(key: IDBValidKey): Promise<RecordType | undefined>; get(key: IDBValidKey): Promise<RecordType | undefined>;
iter(query?: IDBValidKey): ResultStream<RecordType>; iter(query?: IDBValidKey): ResultStream<RecordType>;
put(r: RecordType): Promise<void>; put(r: RecordType): Promise<InsertResponse>;
add(r: RecordType): Promise<void>; add(r: RecordType): Promise<InsertResponse>;
delete(key: IDBValidKey): Promise<void>; delete(key: IDBValidKey): Promise<void>;
indexes: GetIndexReadWriteAccess<RecordType, IndexMap>; indexes: GetIndexReadWriteAccess<RecordType, IndexMap>;
} }
@ -577,13 +597,19 @@ function makeWriteContext(
const req = tx.objectStore(storeName).openCursor(query); const req = tx.objectStore(storeName).openCursor(query);
return new ResultStream<any>(req); return new ResultStream<any>(req);
}, },
add(r) { async add(r) {
const req = tx.objectStore(storeName).add(r); const req = tx.objectStore(storeName).add(r);
return requestToPromise(req); const key = await requestToPromise(req);
return {
key: key,
};
}, },
put(r) { async put(r) {
const req = tx.objectStore(storeName).put(r); const req = tx.objectStore(storeName).put(r);
return requestToPromise(req); const key = await requestToPromise(req);
return {
key: key,
};
}, },
delete(k) { delete(k) {
const req = tx.objectStore(storeName).delete(k); const req = tx.objectStore(storeName).delete(k);