refactor DB access

This commit is contained in:
Florian Dold 2019-12-12 22:39:45 +01:00
parent 24650b1781
commit f3329ecf06
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
17 changed files with 389 additions and 485 deletions

119
src/db.ts
View File

@ -1,118 +1,27 @@
import { Stores, WALLET_DB_VERSION } from "./types/dbTypes"; import { Stores, WALLET_DB_VERSION } from "./types/dbTypes";
import { Store, Index } from "./util/query"; import { openDatabase, Database } from "./util/query";
const DB_NAME = "taler"; const TALER_DB_NAME = "taler";
/** /**
* Return a promise that resolves * Return a promise that resolves
* to the taler wallet db. * to the taler wallet db.
*/ */
export function openDatabase( export function openTalerDatabase(
idbFactory: IDBFactory, idbFactory: IDBFactory,
onVersionChange: () => void, onVersionChange: () => void,
onUpgradeUnsupported: (oldVersion: number, newVersion: number) => void, onUpgradeUnsupported: (oldVersion: number, newVersion: number) => void,
): Promise<IDBDatabase> { ): Promise<IDBDatabase> {
return new Promise<IDBDatabase>((resolve, reject) => { return openDatabase(
const req = idbFactory.open(DB_NAME, WALLET_DB_VERSION); idbFactory,
req.onerror = e => { TALER_DB_NAME,
console.log("taler database error", e); WALLET_DB_VERSION,
reject(new Error("database error")); Stores,
}; onVersionChange,
req.onsuccess = e => { onUpgradeUnsupported,
req.result.onversionchange = (evt: IDBVersionChangeEvent) => { );
console.log(
`handling live db version change from ${evt.oldVersion} to ${evt.newVersion}`,
);
req.result.close();
onVersionChange();
};
resolve(req.result);
};
req.onupgradeneeded = e => {
const db = req.result;
console.log(
`DB: upgrade needed: oldVersion=${e.oldVersion}, newVersion=${e.newVersion}`,
);
switch (e.oldVersion) {
case 0: // DB does not exist yet
for (const n in Stores) {
if ((Stores as any)[n] instanceof Store) {
const si: Store<any> = (Stores as any)[n];
const s = db.createObjectStore(si.name, si.storeParams);
for (const indexName in si as any) {
if ((si as any)[indexName] instanceof Index) {
const ii: Index<any, any> = (si as any)[indexName];
s.createIndex(ii.indexName, ii.keyPath, ii.options);
}
}
}
}
break;
default:
if (e.oldVersion !== WALLET_DB_VERSION) {
onUpgradeUnsupported(e.oldVersion, WALLET_DB_VERSION);
throw Error("incompatible DB");
}
break;
}
};
});
} }
export function exportDatabase(db: IDBDatabase): Promise<any> { export function deleteTalerDatabase(idbFactory: IDBFactory) {
const dump = { Database.deleteDatabase(idbFactory, TALER_DB_NAME);
name: db.name, }
stores: {} as { [s: string]: any },
version: db.version,
};
return new Promise((resolve, reject) => {
const tx = db.transaction(Array.from(db.objectStoreNames));
tx.addEventListener("complete", () => {
resolve(dump);
});
// 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 cursor = (e.target as any).result;
if (cursor) {
storeDump[cursor.key] = cursor.value;
cursor.continue();
}
});
}
});
}
export function importDatabase(db: IDBDatabase, dump: any): Promise<void> {
console.log("importing db", dump);
return new Promise<void>((resolve, reject) => {
const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
if (dump.stores) {
for (const storeName in dump.stores) {
const objects = [];
const dumpStore = dump.stores[storeName];
for (const key in dumpStore) {
objects.push(dumpStore[key]);
}
console.log(`importing ${objects.length} records into ${storeName}`);
const store = tx.objectStore(storeName);
for (const obj of objects) {
store.put(obj);
}
}
}
tx.addEventListener("complete", () => {
resolve();
});
});
}
export function deleteDatabase(idbFactory: IDBFactory) {
idbFactory.deleteDatabase(DB_NAME);
}

View File

@ -23,7 +23,7 @@
*/ */
import { Wallet } from "../wallet"; import { Wallet } from "../wallet";
import { MemoryBackend, BridgeIDBFactory, shimIndexedDB } from "idb-bridge"; import { MemoryBackend, BridgeIDBFactory, shimIndexedDB } from "idb-bridge";
import { openDatabase } from "../db"; import { openTalerDatabase } from "../db";
import Axios, { AxiosPromise, AxiosResponse } from "axios"; import Axios, { AxiosPromise, AxiosResponse } from "axios";
import { import {
HttpRequestLibrary, HttpRequestLibrary,
@ -39,6 +39,7 @@ import { NodeThreadCryptoWorkerFactory } from "../crypto/workers/nodeThreadWorke
import { SynchronousCryptoWorkerFactory } from "../crypto/workers/synchronousWorker"; import { SynchronousCryptoWorkerFactory } from "../crypto/workers/synchronousWorker";
import { RequestThrottler } from "../util/RequestThrottler"; import { RequestThrottler } from "../util/RequestThrottler";
import { WalletNotification, NotificationType } from "../types/notifications"; import { WalletNotification, NotificationType } from "../types/notifications";
import { Database } from "../util/query";
const logger = new Logger("helpers.ts"); const logger = new Logger("helpers.ts");
@ -191,7 +192,7 @@ export async function getDefaultNodeWallet(
shimIndexedDB(myBridgeIdbFactory); shimIndexedDB(myBridgeIdbFactory);
const myDb = await openDatabase( const myDb = await openTalerDatabase(
myIdbFactory, myIdbFactory,
myVersionChange, myVersionChange,
myUnsupportedUpgrade, myUnsupportedUpgrade,
@ -202,7 +203,9 @@ export async function getDefaultNodeWallet(
const worker = new NodeThreadCryptoWorkerFactory(); const worker = new NodeThreadCryptoWorkerFactory();
const w = new Wallet(myDb, myHttpLib, worker); const dbWrap = new Database(myDb);
const w = new Wallet(dbWrap, myHttpLib, worker);
if (args.notifyHandler) { if (args.notifyHandler) {
w.addNotificationListener(args.notifyHandler); w.addNotificationListener(args.notifyHandler);
} }

View File

@ -18,7 +18,7 @@
* Imports. * Imports.
*/ */
import { WalletBalance, WalletBalanceEntry } from "../types/walletTypes"; import { WalletBalance, WalletBalanceEntry } from "../types/walletTypes";
import { runWithReadTransaction } from "../util/query"; import { Database } from "../util/query";
import { InternalWalletState } from "./state"; import { InternalWalletState } from "./state";
import { Stores, TipRecord, CoinStatus } from "../types/dbTypes"; import { Stores, TipRecord, CoinStatus } from "../types/dbTypes";
import * as Amounts from "../util/amounts"; import * as Amounts from "../util/amounts";
@ -73,8 +73,7 @@ export async function getBalances(
byExchange: {}, byExchange: {},
}; };
await runWithReadTransaction( await ws.db.runWithReadTransaction(
ws.db,
[Stores.coins, Stores.refresh, Stores.reserves, Stores.purchases, Stores.withdrawalSession], [Stores.coins, Stores.refresh, Stores.reserves, Stores.purchases, Stores.withdrawalSession],
async tx => { async tx => {
await tx.iter(Stores.coins).forEach(c => { await tx.iter(Stores.coins).forEach(c => {

View File

@ -32,10 +32,7 @@ import {
extractTalerStampOrThrow, extractTalerStampOrThrow,
} from "../util/helpers"; } from "../util/helpers";
import { import {
oneShotGet, Database
oneShotPut,
runWithWriteTransaction,
oneShotMutate,
} from "../util/query"; } from "../util/query";
import * as Amounts from "../util/amounts"; import * as Amounts from "../util/amounts";
import { parsePaytoUri } from "../util/payto"; import { parsePaytoUri } from "../util/payto";
@ -81,7 +78,7 @@ async function setExchangeError(
exchange.lastError = err; exchange.lastError = err;
return exchange; return exchange;
}; };
await oneShotMutate(ws.db, Stores.exchanges, baseUrl, mut); await ws.db.mutate( Stores.exchanges, baseUrl, mut);
} }
/** /**
@ -94,8 +91,7 @@ async function updateExchangeWithKeys(
ws: InternalWalletState, ws: InternalWalletState,
baseUrl: string, baseUrl: string,
): Promise<void> { ): Promise<void> {
const existingExchangeRecord = await oneShotGet( const existingExchangeRecord = await ws.db.get(
ws.db,
Stores.exchanges, Stores.exchanges,
baseUrl, baseUrl,
); );
@ -180,8 +176,7 @@ async function updateExchangeWithKeys(
), ),
); );
await runWithWriteTransaction( await ws.db.runWithWriteTransaction(
ws.db,
[Stores.exchanges, Stores.denominations], [Stores.exchanges, Stores.denominations],
async tx => { async tx => {
const r = await tx.get(Stores.exchanges, baseUrl); const r = await tx.get(Stores.exchanges, baseUrl);
@ -222,7 +217,7 @@ async function updateExchangeWithTermsOfService(
ws: InternalWalletState, ws: InternalWalletState,
exchangeBaseUrl: string, exchangeBaseUrl: string,
) { ) {
const exchange = await oneShotGet(ws.db, Stores.exchanges, exchangeBaseUrl); const exchange = await ws.db.get(Stores.exchanges, exchangeBaseUrl);
if (!exchange) { if (!exchange) {
return; return;
} }
@ -243,7 +238,7 @@ async function updateExchangeWithTermsOfService(
const tosText = await resp.text(); const tosText = await resp.text();
const tosEtag = resp.headers.get("etag") || undefined; const tosEtag = resp.headers.get("etag") || undefined;
await runWithWriteTransaction(ws.db, [Stores.exchanges], async tx => { await ws.db.runWithWriteTransaction([Stores.exchanges], async tx => {
const r = await tx.get(Stores.exchanges, exchangeBaseUrl); const r = await tx.get(Stores.exchanges, exchangeBaseUrl);
if (!r) { if (!r) {
return; return;
@ -263,7 +258,7 @@ export async function acceptExchangeTermsOfService(
exchangeBaseUrl: string, exchangeBaseUrl: string,
etag: string | undefined, etag: string | undefined,
) { ) {
await runWithWriteTransaction(ws.db, [Stores.exchanges], async tx => { await ws.db.runWithWriteTransaction([Stores.exchanges], async tx => {
const r = await tx.get(Stores.exchanges, exchangeBaseUrl); const r = await tx.get(Stores.exchanges, exchangeBaseUrl);
if (!r) { if (!r) {
return; return;
@ -283,7 +278,7 @@ async function updateExchangeWithWireInfo(
ws: InternalWalletState, ws: InternalWalletState,
exchangeBaseUrl: string, exchangeBaseUrl: string,
) { ) {
const exchange = await oneShotGet(ws.db, Stores.exchanges, exchangeBaseUrl); const exchange = await ws.db.get(Stores.exchanges, exchangeBaseUrl);
if (!exchange) { if (!exchange) {
return; return;
} }
@ -349,7 +344,7 @@ async function updateExchangeWithWireInfo(
feesForType[wireMethod] = feeList; feesForType[wireMethod] = feeList;
} }
await runWithWriteTransaction(ws.db, [Stores.exchanges], async tx => { await ws.db.runWithWriteTransaction([Stores.exchanges], async tx => {
const r = await tx.get(Stores.exchanges, exchangeBaseUrl); const r = await tx.get(Stores.exchanges, exchangeBaseUrl);
if (!r) { if (!r) {
return; return;
@ -392,7 +387,7 @@ async function updateExchangeFromUrlImpl(
const now = getTimestampNow(); const now = getTimestampNow();
baseUrl = canonicalizeBaseUrl(baseUrl); baseUrl = canonicalizeBaseUrl(baseUrl);
const r = await oneShotGet(ws.db, Stores.exchanges, baseUrl); const r = await ws.db.get(Stores.exchanges, baseUrl);
if (!r) { if (!r) {
const newExchangeRecord: ExchangeRecord = { const newExchangeRecord: ExchangeRecord = {
baseUrl: baseUrl, baseUrl: baseUrl,
@ -407,9 +402,9 @@ async function updateExchangeFromUrlImpl(
termsOfServiceLastEtag: undefined, termsOfServiceLastEtag: undefined,
termsOfServiceText: undefined, termsOfServiceText: undefined,
}; };
await oneShotPut(ws.db, Stores.exchanges, newExchangeRecord); await ws.db.put(Stores.exchanges, newExchangeRecord);
} else { } else {
await runWithWriteTransaction(ws.db, [Stores.exchanges], async t => { await ws.db.runWithWriteTransaction([Stores.exchanges], async t => {
const rec = await t.get(Stores.exchanges, baseUrl); const rec = await t.get(Stores.exchanges, baseUrl);
if (!rec) { if (!rec) {
return; return;
@ -431,7 +426,7 @@ async function updateExchangeFromUrlImpl(
await updateExchangeWithWireInfo(ws, baseUrl); await updateExchangeWithWireInfo(ws, baseUrl);
await updateExchangeWithTermsOfService(ws, baseUrl); await updateExchangeWithTermsOfService(ws, baseUrl);
const updatedExchange = await oneShotGet(ws.db, Stores.exchanges, baseUrl); const updatedExchange = await ws.db.get(Stores.exchanges, baseUrl);
if (!updatedExchange) { if (!updatedExchange) {
// This should practically never happen // This should practically never happen
@ -453,8 +448,7 @@ export async function getExchangeTrust(
if (!exchangeDetails) { if (!exchangeDetails) {
throw Error(`exchange ${exchangeInfo.baseUrl} details not available`); throw Error(`exchange ${exchangeInfo.baseUrl} details not available`);
} }
const currencyRecord = await oneShotGet( const currencyRecord = await ws.db.get(
ws.db,
Stores.currencies, Stores.currencies,
exchangeDetails.currency, exchangeDetails.currency,
); );

View File

@ -17,7 +17,7 @@
/** /**
* Imports. * Imports.
*/ */
import { oneShotIter, runWithReadTransaction } from "../util/query"; import { Database } from "../util/query";
import { InternalWalletState } from "./state"; import { InternalWalletState } from "./state";
import { Stores, TipRecord } from "../types/dbTypes"; import { Stores, TipRecord } from "../types/dbTypes";
import * as Amounts from "../util/amounts"; import * as Amounts from "../util/amounts";
@ -38,8 +38,7 @@ export async function getHistory(
// This works as timestamps are guaranteed to be monotonically // This works as timestamps are guaranteed to be monotonically
// increasing even // increasing even
await runWithReadTransaction( await ws.db.runWithReadTransaction(
ws.db,
[ [
Stores.currencies, Stores.currencies,
Stores.coins, Stores.coins,

View File

@ -36,13 +36,7 @@ import {
OperationError, OperationError,
} from "../types/walletTypes"; } from "../types/walletTypes";
import { import {
oneShotIter, Database
oneShotIterIndex,
oneShotGet,
runWithWriteTransaction,
oneShotPut,
oneShotGetIndexed,
oneShotMutate,
} from "../util/query"; } from "../util/query";
import { import {
Stores, Stores,
@ -202,7 +196,7 @@ async function getCoinsForPayment(
let remainingAmount = paymentAmount; let remainingAmount = paymentAmount;
const exchanges = await oneShotIter(ws.db, Stores.exchanges).toArray(); const exchanges = await ws.db.iter(Stores.exchanges).toArray();
for (const exchange of exchanges) { for (const exchange of exchanges) {
let isOkay: boolean = false; let isOkay: boolean = false;
@ -242,14 +236,12 @@ async function getCoinsForPayment(
continue; continue;
} }
const coins = await oneShotIterIndex( const coins = await ws.db.iterIndex(
ws.db,
Stores.coins.exchangeBaseUrlIndex, Stores.coins.exchangeBaseUrlIndex,
exchange.baseUrl, exchange.baseUrl,
).toArray(); ).toArray();
const denoms = await oneShotIterIndex( const denoms = await ws.db.iterIndex(
ws.db,
Stores.denominations.exchangeBaseUrlIndex, Stores.denominations.exchangeBaseUrlIndex,
exchange.baseUrl, exchange.baseUrl,
).toArray(); ).toArray();
@ -260,7 +252,7 @@ async function getCoinsForPayment(
// Denomination of the first coin, we assume that all other // Denomination of the first coin, we assume that all other
// coins have the same currency // coins have the same currency
const firstDenom = await oneShotGet(ws.db, Stores.denominations, [ const firstDenom = await ws.db.get(Stores.denominations, [
exchange.baseUrl, exchange.baseUrl,
coins[0].denomPub, coins[0].denomPub,
]); ]);
@ -270,7 +262,7 @@ async function getCoinsForPayment(
const currency = firstDenom.value.currency; const currency = firstDenom.value.currency;
const cds: CoinWithDenom[] = []; const cds: CoinWithDenom[] = [];
for (const coin of coins) { for (const coin of coins) {
const denom = await oneShotGet(ws.db, Stores.denominations, [ const denom = await ws.db.get(Stores.denominations, [
exchange.baseUrl, exchange.baseUrl,
coin.denomPub, coin.denomPub,
]); ]);
@ -377,8 +369,7 @@ async function recordConfirmPay(
paymentSubmitPending: true, paymentSubmitPending: true,
}; };
await runWithWriteTransaction( await ws.db.runWithWriteTransaction(
ws.db,
[Stores.coins, Stores.purchases, Stores.proposals], [Stores.coins, Stores.purchases, Stores.proposals],
async tx => { async tx => {
const p = await tx.get(Stores.proposals, proposal.proposalId); const p = await tx.get(Stores.proposals, proposal.proposalId);
@ -417,7 +408,7 @@ export async function abortFailedPayment(
ws: InternalWalletState, ws: InternalWalletState,
proposalId: string, proposalId: string,
): Promise<void> { ): Promise<void> {
const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId); const purchase = await ws.db.get(Stores.purchases, proposalId);
if (!purchase) { if (!purchase) {
throw Error("Purchase not found, unable to abort with refund"); throw Error("Purchase not found, unable to abort with refund");
} }
@ -434,7 +425,7 @@ export async function abortFailedPayment(
// From now on, we can't retry payment anymore, // From now on, we can't retry payment anymore,
// so mark this in the DB in case the /pay abort // so mark this in the DB in case the /pay abort
// does not complete on the first try. // does not complete on the first try.
await oneShotPut(ws.db, Stores.purchases, purchase); await ws.db.put(Stores.purchases, purchase);
let resp; let resp;
@ -457,7 +448,7 @@ export async function abortFailedPayment(
const refundResponse = MerchantRefundResponse.checked(await resp.json()); const refundResponse = MerchantRefundResponse.checked(await resp.json());
await acceptRefundResponse(ws, purchase.proposalId, refundResponse); await acceptRefundResponse(ws, purchase.proposalId, refundResponse);
await runWithWriteTransaction(ws.db, [Stores.purchases], async tx => { await ws.db.runWithWriteTransaction([Stores.purchases], async tx => {
const p = await tx.get(Stores.purchases, proposalId); const p = await tx.get(Stores.purchases, proposalId);
if (!p) { if (!p) {
return; return;
@ -472,7 +463,7 @@ async function incrementProposalRetry(
proposalId: string, proposalId: string,
err: OperationError | undefined, err: OperationError | undefined,
): Promise<void> { ): Promise<void> {
await runWithWriteTransaction(ws.db, [Stores.proposals], async tx => { await ws.db.runWithWriteTransaction([Stores.proposals], async tx => {
const pr = await tx.get(Stores.proposals, proposalId); const pr = await tx.get(Stores.proposals, proposalId);
if (!pr) { if (!pr) {
return; return;
@ -494,7 +485,7 @@ async function incrementPurchasePayRetry(
err: OperationError | undefined, err: OperationError | undefined,
): Promise<void> { ): Promise<void> {
console.log("incrementing purchase pay retry with error", err); console.log("incrementing purchase pay retry with error", err);
await runWithWriteTransaction(ws.db, [Stores.purchases], async tx => { await ws.db.runWithWriteTransaction([Stores.purchases], async tx => {
const pr = await tx.get(Stores.purchases, proposalId); const pr = await tx.get(Stores.purchases, proposalId);
if (!pr) { if (!pr) {
return; return;
@ -516,7 +507,7 @@ async function incrementPurchaseQueryRefundRetry(
err: OperationError | undefined, err: OperationError | undefined,
): Promise<void> { ): Promise<void> {
console.log("incrementing purchase refund query retry with error", err); console.log("incrementing purchase refund query retry with error", err);
await runWithWriteTransaction(ws.db, [Stores.purchases], async tx => { await ws.db.runWithWriteTransaction([Stores.purchases], async tx => {
const pr = await tx.get(Stores.purchases, proposalId); const pr = await tx.get(Stores.purchases, proposalId);
if (!pr) { if (!pr) {
return; return;
@ -538,7 +529,7 @@ async function incrementPurchaseApplyRefundRetry(
err: OperationError | undefined, err: OperationError | undefined,
): Promise<void> { ): Promise<void> {
console.log("incrementing purchase refund apply retry with error", err); console.log("incrementing purchase refund apply retry with error", err);
await runWithWriteTransaction(ws.db, [Stores.purchases], async tx => { await ws.db.runWithWriteTransaction([Stores.purchases], async tx => {
const pr = await tx.get(Stores.purchases, proposalId); const pr = await tx.get(Stores.purchases, proposalId);
if (!pr) { if (!pr) {
return; return;
@ -571,7 +562,7 @@ async function resetDownloadProposalRetry(
ws: InternalWalletState, ws: InternalWalletState,
proposalId: string, proposalId: string,
) { ) {
await oneShotMutate(ws.db, Stores.proposals, proposalId, x => { await ws.db.mutate(Stores.proposals, proposalId, x => {
if (x.retryInfo.active) { if (x.retryInfo.active) {
x.retryInfo = initRetryInfo(); x.retryInfo = initRetryInfo();
} }
@ -587,7 +578,7 @@ async function processDownloadProposalImpl(
if (forceNow) { if (forceNow) {
await resetDownloadProposalRetry(ws, proposalId); await resetDownloadProposalRetry(ws, proposalId);
} }
const proposal = await oneShotGet(ws.db, Stores.proposals, proposalId); const proposal = await ws.db.get(Stores.proposals, proposalId);
if (!proposal) { if (!proposal) {
return; return;
} }
@ -621,8 +612,7 @@ async function processDownloadProposalImpl(
const fulfillmentUrl = proposalResp.contract_terms.fulfillment_url; const fulfillmentUrl = proposalResp.contract_terms.fulfillment_url;
await runWithWriteTransaction( await ws.db.runWithWriteTransaction(
ws.db,
[Stores.proposals, Stores.purchases], [Stores.proposals, Stores.purchases],
async tx => { async tx => {
const p = await tx.get(Stores.proposals, proposalId); const p = await tx.get(Stores.proposals, proposalId);
@ -677,8 +667,7 @@ async function startDownloadProposal(
orderId: string, orderId: string,
sessionId: string | undefined, sessionId: string | undefined,
): Promise<string> { ): Promise<string> {
const oldProposal = await oneShotGetIndexed( const oldProposal = await ws.db.getIndexed(
ws.db,
Stores.proposals.urlAndOrderIdIndex, Stores.proposals.urlAndOrderIdIndex,
[merchantBaseUrl, orderId], [merchantBaseUrl, orderId],
); );
@ -705,7 +694,7 @@ async function startDownloadProposal(
downloadSessionId: sessionId, downloadSessionId: sessionId,
}; };
await runWithWriteTransaction(ws.db, [Stores.proposals], async (tx) => { await ws.db.runWithWriteTransaction([Stores.proposals], async (tx) => {
const existingRecord = await tx.getIndexed(Stores.proposals.urlAndOrderIdIndex, [ const existingRecord = await tx.getIndexed(Stores.proposals.urlAndOrderIdIndex, [
merchantBaseUrl, merchantBaseUrl,
orderId, orderId,
@ -725,7 +714,7 @@ export async function submitPay(
ws: InternalWalletState, ws: InternalWalletState,
proposalId: string, proposalId: string,
): Promise<ConfirmPayResult> { ): Promise<ConfirmPayResult> {
const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId); const purchase = await ws.db.get(Stores.purchases, proposalId);
if (!purchase) { if (!purchase) {
throw Error("Purchase not found: " + proposalId); throw Error("Purchase not found: " + proposalId);
} }
@ -788,7 +777,7 @@ export async function submitPay(
const modifiedCoins: CoinRecord[] = []; const modifiedCoins: CoinRecord[] = [];
for (const pc of purchase.payReq.coins) { for (const pc of purchase.payReq.coins) {
const c = await oneShotGet(ws.db, Stores.coins, pc.coin_pub); const c = await ws.db.get(Stores.coins, pc.coin_pub);
if (!c) { if (!c) {
console.error("coin not found"); console.error("coin not found");
throw Error("coin used in payment not found"); throw Error("coin used in payment not found");
@ -797,8 +786,7 @@ export async function submitPay(
modifiedCoins.push(c); modifiedCoins.push(c);
} }
await runWithWriteTransaction( await ws.db.runWithWriteTransaction(
ws.db,
[Stores.coins, Stores.purchases], [Stores.coins, Stores.purchases],
async tx => { async tx => {
for (let c of modifiedCoins) { for (let c of modifiedCoins) {
@ -849,7 +837,7 @@ export async function preparePay(
uriResult.sessionId, uriResult.sessionId,
); );
let proposal = await oneShotGet(ws.db, Stores.proposals, proposalId); let proposal = await ws.db.get(Stores.proposals, proposalId);
if (!proposal) { if (!proposal) {
throw Error(`could not get proposal ${proposalId}`); throw Error(`could not get proposal ${proposalId}`);
} }
@ -859,7 +847,7 @@ export async function preparePay(
throw Error("invalid proposal state"); throw Error("invalid proposal state");
} }
console.log("using existing purchase for same product"); console.log("using existing purchase for same product");
proposal = await oneShotGet(ws.db, Stores.proposals, existingProposalId); proposal = await ws.db.get(Stores.proposals, existingProposalId);
if (!proposal) { if (!proposal) {
throw Error("existing proposal is in wrong state"); throw Error("existing proposal is in wrong state");
} }
@ -878,7 +866,7 @@ export async function preparePay(
proposalId = proposal.proposalId; proposalId = proposal.proposalId;
// First check if we already payed for it. // First check if we already payed for it.
const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId); const purchase = await ws.db.get(Stores.purchases, proposalId);
if (!purchase) { if (!purchase) {
const paymentAmount = Amounts.parseOrThrow(contractTerms.amount); const paymentAmount = Amounts.parseOrThrow(contractTerms.amount);
@ -966,7 +954,7 @@ async function getSpeculativePayData(
const coinKeys = sp.payCoinInfo.updatedCoins.map(x => x.coinPub); const coinKeys = sp.payCoinInfo.updatedCoins.map(x => x.coinPub);
const coins: CoinRecord[] = []; const coins: CoinRecord[] = [];
for (let coinKey of coinKeys) { for (let coinKey of coinKeys) {
const cc = await oneShotGet(ws.db, Stores.coins, coinKey); const cc = await ws.db.get(Stores.coins, coinKey);
if (cc) { if (cc) {
coins.push(cc); coins.push(cc);
} }
@ -997,7 +985,7 @@ export async function confirmPay(
logger.trace( logger.trace(
`executing confirmPay with proposalId ${proposalId} and sessionIdOverride ${sessionIdOverride}`, `executing confirmPay with proposalId ${proposalId} and sessionIdOverride ${sessionIdOverride}`,
); );
const proposal = await oneShotGet(ws.db, Stores.proposals, proposalId); const proposal = await ws.db.get(Stores.proposals, proposalId);
if (!proposal) { if (!proposal) {
throw Error(`proposal with id ${proposalId} not found`); throw Error(`proposal with id ${proposalId} not found`);
@ -1008,7 +996,7 @@ export async function confirmPay(
throw Error("proposal is in invalid state"); throw Error("proposal is in invalid state");
} }
let purchase = await oneShotGet(ws.db, Stores.purchases, d.contractTermsHash); let purchase = await ws.db.get(Stores.purchases, d.contractTermsHash);
if (purchase) { if (purchase) {
if ( if (
@ -1016,7 +1004,7 @@ export async function confirmPay(
sessionIdOverride != purchase.lastSessionId sessionIdOverride != purchase.lastSessionId
) { ) {
logger.trace(`changing session ID to ${sessionIdOverride}`); logger.trace(`changing session ID to ${sessionIdOverride}`);
await oneShotMutate(ws.db, Stores.purchases, purchase.proposalId, x => { await ws.db.mutate(Stores.purchases, purchase.proposalId, x => {
x.lastSessionId = sessionIdOverride; x.lastSessionId = sessionIdOverride;
x.paymentSubmitPending = true; x.paymentSubmitPending = true;
return x; return x;
@ -1092,8 +1080,7 @@ export async function getFullRefundFees(
if (refundPermissions.length === 0) { if (refundPermissions.length === 0) {
throw Error("no refunds given"); throw Error("no refunds given");
} }
const coin0 = await oneShotGet( const coin0 = await ws.db.get(
ws.db,
Stores.coins, Stores.coins,
refundPermissions[0].coin_pub, refundPermissions[0].coin_pub,
); );
@ -1104,18 +1091,17 @@ export async function getFullRefundFees(
Amounts.parseOrThrow(refundPermissions[0].refund_amount).currency, Amounts.parseOrThrow(refundPermissions[0].refund_amount).currency,
); );
const denoms = await oneShotIterIndex( const denoms = await ws.db.iterIndex(
ws.db,
Stores.denominations.exchangeBaseUrlIndex, Stores.denominations.exchangeBaseUrlIndex,
coin0.exchangeBaseUrl, coin0.exchangeBaseUrl,
).toArray(); ).toArray();
for (const rp of refundPermissions) { for (const rp of refundPermissions) {
const coin = await oneShotGet(ws.db, Stores.coins, rp.coin_pub); const coin = await ws.db.get(Stores.coins, rp.coin_pub);
if (!coin) { if (!coin) {
throw Error("coin not found"); throw Error("coin not found");
} }
const denom = await oneShotGet(ws.db, Stores.denominations, [ const denom = await ws.db.get(Stores.denominations, [
coin0.exchangeBaseUrl, coin0.exchangeBaseUrl,
coin.denomPub, coin.denomPub,
]); ]);
@ -1147,7 +1133,7 @@ async function acceptRefundResponse(
let numNewRefunds = 0; let numNewRefunds = 0;
await runWithWriteTransaction(ws.db, [Stores.purchases], async tx => { await ws.db.runWithWriteTransaction([Stores.purchases], async tx => {
const p = await tx.get(Stores.purchases, proposalId); const p = await tx.get(Stores.purchases, proposalId);
if (!p) { if (!p) {
console.error("purchase not found, not adding refunds"); console.error("purchase not found, not adding refunds");
@ -1215,8 +1201,7 @@ async function startRefundQuery(
ws: InternalWalletState, ws: InternalWalletState,
proposalId: string, proposalId: string,
): Promise<void> { ): Promise<void> {
const success = await runWithWriteTransaction( const success = await ws.db.runWithWriteTransaction(
ws.db,
[Stores.purchases], [Stores.purchases],
async tx => { async tx => {
const p = await tx.get(Stores.purchases, proposalId); const p = await tx.get(Stores.purchases, proposalId);
@ -1259,8 +1244,7 @@ export async function applyRefund(
throw Error("invalid refund URI"); throw Error("invalid refund URI");
} }
const purchase = await oneShotGetIndexed( const purchase = await ws.db.getIndexed(
ws.db,
Stores.purchases.orderIdIndex, Stores.purchases.orderIdIndex,
[parseResult.merchantBaseUrl, parseResult.orderId], [parseResult.merchantBaseUrl, parseResult.orderId],
); );
@ -1292,7 +1276,7 @@ async function resetPurchasePayRetry(
ws: InternalWalletState, ws: InternalWalletState,
proposalId: string, proposalId: string,
) { ) {
await oneShotMutate(ws.db, Stores.purchases, proposalId, x => { await ws.db.mutate(Stores.purchases, proposalId, x => {
if (x.payRetryInfo.active) { if (x.payRetryInfo.active) {
x.payRetryInfo = initRetryInfo(); x.payRetryInfo = initRetryInfo();
} }
@ -1308,7 +1292,7 @@ async function processPurchasePayImpl(
if (forceNow) { if (forceNow) {
await resetPurchasePayRetry(ws, proposalId); await resetPurchasePayRetry(ws, proposalId);
} }
const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId); const purchase = await ws.db.get(Stores.purchases, proposalId);
if (!purchase) { if (!purchase) {
return; return;
} }
@ -1336,7 +1320,7 @@ async function resetPurchaseQueryRefundRetry(
ws: InternalWalletState, ws: InternalWalletState,
proposalId: string, proposalId: string,
) { ) {
await oneShotMutate(ws.db, Stores.purchases, proposalId, x => { await ws.db.mutate(Stores.purchases, proposalId, x => {
if (x.refundStatusRetryInfo.active) { if (x.refundStatusRetryInfo.active) {
x.refundStatusRetryInfo = initRetryInfo(); x.refundStatusRetryInfo = initRetryInfo();
} }
@ -1352,7 +1336,7 @@ async function processPurchaseQueryRefundImpl(
if (forceNow) { if (forceNow) {
await resetPurchaseQueryRefundRetry(ws, proposalId); await resetPurchaseQueryRefundRetry(ws, proposalId);
} }
const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId); const purchase = await ws.db.get(Stores.purchases, proposalId);
if (!purchase) { if (!purchase) {
return; return;
} }
@ -1398,7 +1382,7 @@ async function resetPurchaseApplyRefundRetry(
ws: InternalWalletState, ws: InternalWalletState,
proposalId: string, proposalId: string,
) { ) {
await oneShotMutate(ws.db, Stores.purchases, proposalId, x => { await ws.db.mutate(Stores.purchases, proposalId, x => {
if (x.refundApplyRetryInfo.active) { if (x.refundApplyRetryInfo.active) {
x.refundApplyRetryInfo = initRetryInfo(); x.refundApplyRetryInfo = initRetryInfo();
} }
@ -1414,7 +1398,7 @@ async function processPurchaseApplyRefundImpl(
if (forceNow) { if (forceNow) {
await resetPurchaseApplyRefundRetry(ws, proposalId); await resetPurchaseApplyRefundRetry(ws, proposalId);
} }
const purchase = await oneShotGet(ws.db, Stores.purchases, proposalId); const purchase = await ws.db.get(Stores.purchases, proposalId);
if (!purchase) { if (!purchase) {
console.error("not submitting refunds, payment not found:"); console.error("not submitting refunds, payment not found:");
return; return;
@ -1448,8 +1432,7 @@ async function processPurchaseApplyRefundImpl(
let allRefundsProcessed = false; let allRefundsProcessed = false;
await runWithWriteTransaction( await ws.db.runWithWriteTransaction(
ws.db,
[Stores.purchases, Stores.coins], [Stores.purchases, Stores.coins],
async tx => { async tx => {
const p = await tx.get(Stores.purchases, proposalId); const p = await tx.get(Stores.purchases, proposalId);

View File

@ -18,10 +18,7 @@
* Imports. * Imports.
*/ */
import { import {
oneShotIter, Database
runWithWriteTransaction,
oneShotGet,
oneShotPut,
} from "../util/query"; } from "../util/query";
import { InternalWalletState } from "./state"; import { InternalWalletState } from "./state";
import { Stores, TipRecord, CoinStatus } from "../types/dbTypes"; import { Stores, TipRecord, CoinStatus } from "../types/dbTypes";
@ -37,7 +34,7 @@ export async function payback(
ws: InternalWalletState, ws: InternalWalletState,
coinPub: string, coinPub: string,
): Promise<void> { ): Promise<void> {
let coin = await oneShotGet(ws.db, Stores.coins, coinPub); let coin = await ws.db.get(Stores.coins, coinPub);
if (!coin) { if (!coin) {
throw Error(`Coin ${coinPub} not found, can't request payback`); throw Error(`Coin ${coinPub} not found, can't request payback`);
} }
@ -45,7 +42,7 @@ export async function payback(
if (!reservePub) { if (!reservePub) {
throw Error(`Can't request payback for a refreshed coin`); throw Error(`Can't request payback for a refreshed coin`);
} }
const reserve = await oneShotGet(ws.db, Stores.reserves, reservePub); const reserve = await ws.db.get(Stores.reserves, reservePub);
if (!reserve) { if (!reserve) {
throw Error(`Reserve of coin ${coinPub} not found`); throw Error(`Reserve of coin ${coinPub} not found`);
} }
@ -58,8 +55,7 @@ export async function payback(
// technically we might update reserve status before we get the response // technically we might update reserve status before we get the response
// from the reserve for the payback request. // from the reserve for the payback request.
reserve.hasPayback = true; reserve.hasPayback = true;
await runWithWriteTransaction( await ws.db.runWithWriteTransaction(
ws.db,
[Stores.coins, Stores.reserves], [Stores.coins, Stores.reserves],
async tx => { async tx => {
await tx.put(Stores.coins, coin!!); await tx.put(Stores.coins, coin!!);
@ -80,12 +76,12 @@ export async function payback(
if (paybackConfirmation.reserve_pub !== coin.reservePub) { if (paybackConfirmation.reserve_pub !== coin.reservePub) {
throw Error(`Coin's reserve doesn't match reserve on payback`); throw Error(`Coin's reserve doesn't match reserve on payback`);
} }
coin = await oneShotGet(ws.db, Stores.coins, coinPub); coin = await ws.db.get(Stores.coins, coinPub);
if (!coin) { if (!coin) {
throw Error(`Coin ${coinPub} not found, can't confirm payback`); throw Error(`Coin ${coinPub} not found, can't confirm payback`);
} }
coin.status = CoinStatus.Dormant; coin.status = CoinStatus.Dormant;
await oneShotPut(ws.db, Stores.coins, coin); await ws.db.put(Stores.coins, coin);
ws.notify({ ws.notify({
type: NotificationType.PaybackFinished, type: NotificationType.PaybackFinished,
}); });

View File

@ -22,7 +22,7 @@ import {
Timestamp, Timestamp,
Duration, Duration,
} from "../types/walletTypes"; } from "../types/walletTypes";
import { runWithReadTransaction, TransactionHandle } from "../util/query"; import { Database, TransactionHandle } from "../util/query";
import { InternalWalletState } from "./state"; import { InternalWalletState } from "./state";
import { import {
Stores, Stores,
@ -425,8 +425,7 @@ export async function getPendingOperations(
pendingOperations: [], pendingOperations: [],
}; };
const now = getTimestampNow(); const now = getTimestampNow();
await runWithReadTransaction( await ws.db.runWithReadTransaction(
ws.db,
[ [
Stores.exchanges, Stores.exchanges,
Stores.reserves, Stores.reserves,

View File

@ -27,21 +27,12 @@ import {
updateRetryInfoTimeout, updateRetryInfoTimeout,
} from "../types/dbTypes"; } from "../types/dbTypes";
import { amountToPretty } from "../util/helpers"; import { amountToPretty } from "../util/helpers";
import { import { Database } from "../util/query";
oneShotGet,
oneShotMutate,
runWithWriteTransaction,
TransactionAbort,
oneShotIterIndex,
} from "../util/query";
import { InternalWalletState } from "./state"; import { InternalWalletState } from "./state";
import { Logger } from "../util/logging"; import { Logger } from "../util/logging";
import { getWithdrawDenomList } from "./withdraw"; import { getWithdrawDenomList } from "./withdraw";
import { updateExchangeFromUrl } from "./exchanges"; import { updateExchangeFromUrl } from "./exchanges";
import { import { getTimestampNow, OperationError } from "../types/walletTypes";
getTimestampNow,
OperationError,
} from "../types/walletTypes";
import { guardOperationException } from "./errors"; import { guardOperationException } from "./errors";
import { NotificationType } from "../types/notifications"; import { NotificationType } from "../types/notifications";
@ -84,11 +75,7 @@ async function refreshMelt(
ws: InternalWalletState, ws: InternalWalletState,
refreshSessionId: string, refreshSessionId: string,
): Promise<void> { ): Promise<void> {
const refreshSession = await oneShotGet( const refreshSession = await ws.db.get(Stores.refresh, refreshSessionId);
ws.db,
Stores.refresh,
refreshSessionId,
);
if (!refreshSession) { if (!refreshSession) {
return; return;
} }
@ -96,11 +83,7 @@ async function refreshMelt(
return; return;
} }
const coin = await oneShotGet( const coin = await ws.db.get(Stores.coins, refreshSession.meltCoinPub);
ws.db,
Stores.coins,
refreshSession.meltCoinPub,
);
if (!coin) { if (!coin) {
console.error("can't melt coin, it does not exist"); console.error("can't melt coin, it does not exist");
@ -139,7 +122,7 @@ async function refreshMelt(
refreshSession.norevealIndex = norevealIndex; refreshSession.norevealIndex = norevealIndex;
await oneShotMutate(ws.db, Stores.refresh, refreshSessionId, rs => { await ws.db.mutate(Stores.refresh, refreshSessionId, rs => {
if (rs.norevealIndex !== undefined) { if (rs.norevealIndex !== undefined) {
return; return;
} }
@ -159,11 +142,7 @@ async function refreshReveal(
ws: InternalWalletState, ws: InternalWalletState,
refreshSessionId: string, refreshSessionId: string,
): Promise<void> { ): Promise<void> {
const refreshSession = await oneShotGet( const refreshSession = await ws.db.get(Stores.refresh, refreshSessionId);
ws.db,
Stores.refresh,
refreshSessionId,
);
if (!refreshSession) { if (!refreshSession) {
return; return;
} }
@ -179,8 +158,7 @@ async function refreshReveal(
throw Error("refresh index error"); throw Error("refresh index error");
} }
const meltCoinRecord = await oneShotGet( const meltCoinRecord = await ws.db.get(
ws.db,
Stores.coins, Stores.coins,
refreshSession.meltCoinPub, refreshSession.meltCoinPub,
); );
@ -241,7 +219,7 @@ async function refreshReveal(
const coins: CoinRecord[] = []; const coins: CoinRecord[] = [];
for (let i = 0; i < respJson.ev_sigs.length; i++) { for (let i = 0; i < respJson.ev_sigs.length; i++) {
const denom = await oneShotGet(ws.db, Stores.denominations, [ const denom = await ws.db.get(Stores.denominations, [
refreshSession.exchangeBaseUrl, refreshSession.exchangeBaseUrl,
refreshSession.newDenoms[i], refreshSession.newDenoms[i],
]); ]);
@ -274,8 +252,7 @@ async function refreshReveal(
coins.push(coin); coins.push(coin);
} }
await runWithWriteTransaction( await ws.db.runWithWriteTransaction(
ws.db,
[Stores.coins, Stores.refresh], [Stores.coins, Stores.refresh],
async tx => { async tx => {
const rs = await tx.get(Stores.refresh, refreshSessionId); const rs = await tx.get(Stores.refresh, refreshSessionId);
@ -306,7 +283,7 @@ async function incrementRefreshRetry(
refreshSessionId: string, refreshSessionId: string,
err: OperationError | undefined, err: OperationError | undefined,
): Promise<void> { ): Promise<void> {
await runWithWriteTransaction(ws.db, [Stores.refresh], async tx => { await ws.db.runWithWriteTransaction([Stores.refresh], async tx => {
const r = await tx.get(Stores.refresh, refreshSessionId); const r = await tx.get(Stores.refresh, refreshSessionId);
if (!r) { if (!r) {
return; return;
@ -341,7 +318,7 @@ async function resetRefreshSessionRetry(
ws: InternalWalletState, ws: InternalWalletState,
refreshSessionId: string, refreshSessionId: string,
) { ) {
await oneShotMutate(ws.db, Stores.refresh, refreshSessionId, (x) => { await ws.db.mutate(Stores.refresh, refreshSessionId, x => {
if (x.retryInfo.active) { if (x.retryInfo.active) {
x.retryInfo = initRetryInfo(); x.retryInfo = initRetryInfo();
} }
@ -357,11 +334,7 @@ async function processRefreshSessionImpl(
if (forceNow) { if (forceNow) {
await resetRefreshSessionRetry(ws, refreshSessionId); await resetRefreshSessionRetry(ws, refreshSessionId);
} }
const refreshSession = await oneShotGet( const refreshSession = await ws.db.get(Stores.refresh, refreshSessionId);
ws.db,
Stores.refresh,
refreshSessionId,
);
if (!refreshSession) { if (!refreshSession) {
return; return;
} }
@ -380,7 +353,7 @@ export async function refresh(
oldCoinPub: string, oldCoinPub: string,
force: boolean = false, force: boolean = false,
): Promise<void> { ): Promise<void> {
const coin = await oneShotGet(ws.db, Stores.coins, oldCoinPub); const coin = await ws.db.get(Stores.coins, oldCoinPub);
if (!coin) { if (!coin) {
console.warn("can't refresh, coin not in database"); console.warn("can't refresh, coin not in database");
return; return;
@ -402,7 +375,7 @@ export async function refresh(
throw Error("db inconsistent: exchange of coin not found"); throw Error("db inconsistent: exchange of coin not found");
} }
const oldDenom = await oneShotGet(ws.db, Stores.denominations, [ const oldDenom = await ws.db.get(Stores.denominations, [
exchange.baseUrl, exchange.baseUrl,
coin.denomPub, coin.denomPub,
]); ]);
@ -411,11 +384,9 @@ export async function refresh(
throw Error("db inconsistent: denomination for coin not found"); throw Error("db inconsistent: denomination for coin not found");
} }
const availableDenoms: DenominationRecord[] = await oneShotIterIndex( const availableDenoms: DenominationRecord[] = await ws.db
ws.db, .iterIndex(Stores.denominations.exchangeBaseUrlIndex, exchange.baseUrl)
Stores.denominations.exchangeBaseUrlIndex, .toArray();
exchange.baseUrl,
).toArray();
const availableAmount = Amounts.sub(coin.currentAmount, oldDenom.feeRefresh) const availableAmount = Amounts.sub(coin.currentAmount, oldDenom.feeRefresh)
.amount; .amount;
@ -428,7 +399,7 @@ export async function refresh(
availableAmount, availableAmount,
)} too small`, )} too small`,
); );
await oneShotMutate(ws.db, Stores.coins, oldCoinPub, x => { await ws.db.mutate(Stores.coins, oldCoinPub, x => {
if (x.status != coin.status) { if (x.status != coin.status) {
// Concurrent modification? // Concurrent modification?
return; return;
@ -450,8 +421,7 @@ export async function refresh(
// Store refresh session and subtract refreshed amount from // Store refresh session and subtract refreshed amount from
// coin in the same transaction. // coin in the same transaction.
await runWithWriteTransaction( await ws.db.runWithWriteTransaction(
ws.db,
[Stores.refresh, Stores.coins], [Stores.refresh, Stores.coins],
async tx => { async tx => {
const c = await tx.get(Stores.coins, coin.coinPub); const c = await tx.get(Stores.coins, coin.coinPub);

View File

@ -33,10 +33,7 @@ import {
updateRetryInfoTimeout, updateRetryInfoTimeout,
} from "../types/dbTypes"; } from "../types/dbTypes";
import { import {
oneShotMutate, Database,
oneShotPut,
oneShotGet,
runWithWriteTransaction,
TransactionAbort, TransactionAbort,
} from "../util/query"; } from "../util/query";
import { Logger } from "../util/logging"; import { Logger } from "../util/logging";
@ -104,7 +101,7 @@ export async function createReserve(
const rec = { const rec = {
paytoUri: senderWire, paytoUri: senderWire,
}; };
await oneShotPut(ws.db, Stores.senderWires, rec); await ws.db.put(Stores.senderWires, rec);
} }
const exchangeInfo = await updateExchangeFromUrl(ws, req.exchange); const exchangeInfo = await updateExchangeFromUrl(ws, req.exchange);
@ -114,8 +111,7 @@ export async function createReserve(
throw Error("exchange not updated"); throw Error("exchange not updated");
} }
const { isAudited, isTrusted } = await getExchangeTrust(ws, exchangeInfo); const { isAudited, isTrusted } = await getExchangeTrust(ws, exchangeInfo);
let currencyRecord = await oneShotGet( let currencyRecord = await ws.db.get(
ws.db,
Stores.currencies, Stores.currencies,
exchangeDetails.currency, exchangeDetails.currency,
); );
@ -137,8 +133,7 @@ export async function createReserve(
const cr: CurrencyRecord = currencyRecord; const cr: CurrencyRecord = currencyRecord;
const resp = await runWithWriteTransaction( const resp = await ws.db.runWithWriteTransaction(
ws.db,
[Stores.currencies, Stores.reserves, Stores.bankWithdrawUris], [Stores.currencies, Stores.reserves, Stores.bankWithdrawUris],
async tx => { async tx => {
// Check if we have already created a reserve for that bankWithdrawStatusUrl // Check if we have already created a reserve for that bankWithdrawStatusUrl
@ -212,7 +207,7 @@ async function registerReserveWithBank(
ws: InternalWalletState, ws: InternalWalletState,
reservePub: string, reservePub: string,
): Promise<void> { ): Promise<void> {
let reserve = await oneShotGet(ws.db, Stores.reserves, reservePub); let reserve = await ws.db.get(Stores.reserves, reservePub);
switch (reserve?.reserveStatus) { switch (reserve?.reserveStatus) {
case ReserveRecordStatus.WAIT_CONFIRM_BANK: case ReserveRecordStatus.WAIT_CONFIRM_BANK:
case ReserveRecordStatus.REGISTERING_BANK: case ReserveRecordStatus.REGISTERING_BANK:
@ -233,7 +228,7 @@ async function registerReserveWithBank(
selected_exchange: reserve.exchangeWire, selected_exchange: reserve.exchangeWire,
}); });
console.log("got response", bankResp); console.log("got response", bankResp);
await oneShotMutate(ws.db, Stores.reserves, reservePub, r => { await ws.db.mutate(Stores.reserves, reservePub, r => {
switch (r.reserveStatus) { switch (r.reserveStatus) {
case ReserveRecordStatus.REGISTERING_BANK: case ReserveRecordStatus.REGISTERING_BANK:
case ReserveRecordStatus.WAIT_CONFIRM_BANK: case ReserveRecordStatus.WAIT_CONFIRM_BANK:
@ -266,7 +261,7 @@ async function processReserveBankStatusImpl(
ws: InternalWalletState, ws: InternalWalletState,
reservePub: string, reservePub: string,
): Promise<void> { ): Promise<void> {
let reserve = await oneShotGet(ws.db, Stores.reserves, reservePub); let reserve = await ws.db.get(Stores.reserves, reservePub);
switch (reserve?.reserveStatus) { switch (reserve?.reserveStatus) {
case ReserveRecordStatus.WAIT_CONFIRM_BANK: case ReserveRecordStatus.WAIT_CONFIRM_BANK:
case ReserveRecordStatus.REGISTERING_BANK: case ReserveRecordStatus.REGISTERING_BANK:
@ -303,7 +298,7 @@ async function processReserveBankStatusImpl(
} }
if (status.transfer_done) { if (status.transfer_done) {
await oneShotMutate(ws.db, Stores.reserves, reservePub, r => { await ws.db.mutate(Stores.reserves, reservePub, r => {
switch (r.reserveStatus) { switch (r.reserveStatus) {
case ReserveRecordStatus.REGISTERING_BANK: case ReserveRecordStatus.REGISTERING_BANK:
case ReserveRecordStatus.WAIT_CONFIRM_BANK: case ReserveRecordStatus.WAIT_CONFIRM_BANK:
@ -319,7 +314,7 @@ async function processReserveBankStatusImpl(
}); });
await processReserveImpl(ws, reservePub, true); await processReserveImpl(ws, reservePub, true);
} else { } else {
await oneShotMutate(ws.db, Stores.reserves, reservePub, r => { await ws.db.mutate(Stores.reserves, reservePub, r => {
switch (r.reserveStatus) { switch (r.reserveStatus) {
case ReserveRecordStatus.WAIT_CONFIRM_BANK: case ReserveRecordStatus.WAIT_CONFIRM_BANK:
break; break;
@ -339,7 +334,7 @@ async function incrementReserveRetry(
reservePub: string, reservePub: string,
err: OperationError | undefined, err: OperationError | undefined,
): Promise<void> { ): Promise<void> {
await runWithWriteTransaction(ws.db, [Stores.reserves], async tx => { await ws.db.runWithWriteTransaction([Stores.reserves], async tx => {
const r = await tx.get(Stores.reserves, reservePub); const r = await tx.get(Stores.reserves, reservePub);
if (!r) { if (!r) {
return; return;
@ -363,7 +358,7 @@ async function updateReserve(
ws: InternalWalletState, ws: InternalWalletState,
reservePub: string, reservePub: string,
): Promise<void> { ): Promise<void> {
const reserve = await oneShotGet(ws.db, Stores.reserves, reservePub); const reserve = await ws.db.get(Stores.reserves, reservePub);
if (!reserve) { if (!reserve) {
throw Error("reserve not in db"); throw Error("reserve not in db");
} }
@ -400,7 +395,7 @@ async function updateReserve(
} }
const reserveInfo = ReserveStatus.checked(await resp.json()); const reserveInfo = ReserveStatus.checked(await resp.json());
const balance = Amounts.parseOrThrow(reserveInfo.balance); const balance = Amounts.parseOrThrow(reserveInfo.balance);
await oneShotMutate(ws.db, Stores.reserves, reserve.reservePub, r => { await ws.db.mutate(Stores.reserves, reserve.reservePub, r => {
if (r.reserveStatus !== ReserveRecordStatus.QUERYING_STATUS) { if (r.reserveStatus !== ReserveRecordStatus.QUERYING_STATUS) {
return; return;
} }
@ -442,7 +437,7 @@ async function processReserveImpl(
reservePub: string, reservePub: string,
forceNow: boolean = false, forceNow: boolean = false,
): Promise<void> { ): Promise<void> {
const reserve = await oneShotGet(ws.db, Stores.reserves, reservePub); const reserve = await ws.db.get(Stores.reserves, reservePub);
if (!reserve) { if (!reserve) {
console.log("not processing reserve: reserve does not exist"); console.log("not processing reserve: reserve does not exist");
return; return;
@ -488,7 +483,7 @@ export async function confirmReserve(
req: ConfirmReserveRequest, req: ConfirmReserveRequest,
): Promise<void> { ): Promise<void> {
const now = getTimestampNow(); const now = getTimestampNow();
await oneShotMutate(ws.db, Stores.reserves, req.reservePub, reserve => { await ws.db.mutate(Stores.reserves, req.reservePub, reserve => {
if (reserve.reserveStatus !== ReserveRecordStatus.UNCONFIRMED) { if (reserve.reserveStatus !== ReserveRecordStatus.UNCONFIRMED) {
return; return;
} }
@ -515,7 +510,7 @@ async function depleteReserve(
ws: InternalWalletState, ws: InternalWalletState,
reservePub: string, reservePub: string,
): Promise<void> { ): Promise<void> {
const reserve = await oneShotGet(ws.db, Stores.reserves, reservePub); const reserve = await ws.db.get(Stores.reserves, reservePub);
if (!reserve) { if (!reserve) {
return; return;
} }
@ -600,8 +595,7 @@ async function depleteReserve(
return r; return r;
} }
const success = await runWithWriteTransaction( const success = await ws.db.runWithWriteTransaction(
ws.db,
[Stores.withdrawalSession, Stores.reserves], [Stores.withdrawalSession, Stores.reserves],
async tx => { async tx => {
const myReserve = await tx.get(Stores.reserves, reservePub); const myReserve = await tx.get(Stores.reserves, reservePub);

View File

@ -21,7 +21,7 @@ import {
ReturnCoinsRequest, ReturnCoinsRequest,
CoinWithDenom, CoinWithDenom,
} from "../types/walletTypes"; } from "../types/walletTypes";
import { runWithWriteTransaction, oneShotGet, oneShotIterIndex, oneShotPut } from "../util/query"; import { Database } from "../util/query";
import { InternalWalletState } from "./state"; import { InternalWalletState } from "./state";
import { Stores, TipRecord, CoinStatus, CoinsReturnRecord, CoinRecord } from "../types/dbTypes"; import { Stores, TipRecord, CoinStatus, CoinsReturnRecord, CoinRecord } from "../types/dbTypes";
import * as Amounts from "../util/amounts"; import * as Amounts from "../util/amounts";
@ -38,8 +38,7 @@ async function getCoinsForReturn(
exchangeBaseUrl: string, exchangeBaseUrl: string,
amount: AmountJson, amount: AmountJson,
): Promise<CoinWithDenom[] | undefined> { ): Promise<CoinWithDenom[] | undefined> {
const exchange = await oneShotGet( const exchange = await ws.db.get(
ws.db,
Stores.exchanges, Stores.exchanges,
exchangeBaseUrl, exchangeBaseUrl,
); );
@ -47,8 +46,7 @@ async function getCoinsForReturn(
throw Error(`Exchange ${exchangeBaseUrl} not known to the wallet`); throw Error(`Exchange ${exchangeBaseUrl} not known to the wallet`);
} }
const coins: CoinRecord[] = await oneShotIterIndex( const coins: CoinRecord[] = await ws.db.iterIndex(
ws.db,
Stores.coins.exchangeBaseUrlIndex, Stores.coins.exchangeBaseUrlIndex,
exchange.baseUrl, exchange.baseUrl,
).toArray(); ).toArray();
@ -57,15 +55,14 @@ async function getCoinsForReturn(
return []; return [];
} }
const denoms = await oneShotIterIndex( const denoms = await ws.db.iterIndex(
ws.db,
Stores.denominations.exchangeBaseUrlIndex, Stores.denominations.exchangeBaseUrlIndex,
exchange.baseUrl, exchange.baseUrl,
).toArray(); ).toArray();
// Denomination of the first coin, we assume that all other // Denomination of the first coin, we assume that all other
// coins have the same currency // coins have the same currency
const firstDenom = await oneShotGet(ws.db, Stores.denominations, [ const firstDenom = await ws.db.get(Stores.denominations, [
exchange.baseUrl, exchange.baseUrl,
coins[0].denomPub, coins[0].denomPub,
]); ]);
@ -76,7 +73,7 @@ async function getCoinsForReturn(
const cds: CoinWithDenom[] = []; const cds: CoinWithDenom[] = [];
for (const coin of coins) { for (const coin of coins) {
const denom = await oneShotGet(ws.db, Stores.denominations, [ const denom = await ws.db.get(Stores.denominations, [
exchange.baseUrl, exchange.baseUrl,
coin.denomPub, coin.denomPub,
]); ]);
@ -121,7 +118,7 @@ export async function returnCoins(
return; return;
} }
const stampSecNow = Math.floor(new Date().getTime() / 1000); const stampSecNow = Math.floor(new Date().getTime() / 1000);
const exchange = await oneShotGet(ws.db, Stores.exchanges, req.exchange); const exchange = await ws.db.get(Stores.exchanges, req.exchange);
if (!exchange) { if (!exchange) {
console.error(`Exchange ${req.exchange} not known to the wallet`); console.error(`Exchange ${req.exchange} not known to the wallet`);
return; return;
@ -190,8 +187,7 @@ export async function returnCoins(
wire: req.senderWire, wire: req.senderWire,
}; };
await runWithWriteTransaction( await ws.db.runWithWriteTransaction(
ws.db,
[Stores.coinsReturns, Stores.coins], [Stores.coinsReturns, Stores.coins],
async tx => { async tx => {
await tx.put(Stores.coinsReturns, coinsReturnRecord); await tx.put(Stores.coinsReturns, coinsReturnRecord);
@ -248,8 +244,7 @@ async function depositReturnedCoins(
// FIXME: verify signature // FIXME: verify signature
// For every successful deposit, we replace the old record with an updated one // For every successful deposit, we replace the old record with an updated one
const currentCrr = await oneShotGet( const currentCrr = await ws.db.get(
ws.db,
Stores.coinsReturns, Stores.coinsReturns,
coinsReturnRecord.contractTermsHash, coinsReturnRecord.contractTermsHash,
); );
@ -262,6 +257,6 @@ async function depositReturnedCoins(
nc.depositedSig = respJson.sig; nc.depositedSig = respJson.sig;
} }
} }
await oneShotPut(ws.db, Stores.coinsReturns, currentCrr); await ws.db.put(Stores.coinsReturns, currentCrr);
} }
} }

View File

@ -25,6 +25,7 @@ import { AsyncOpMemoMap, AsyncOpMemoSingle } from "../util/asyncMemo";
import { Logger } from "../util/logging"; import { Logger } from "../util/logging";
import { PendingOperationsResponse } from "../types/pending"; import { PendingOperationsResponse } from "../types/pending";
import { WalletNotification } from "../types/notifications"; import { WalletNotification } from "../types/notifications";
import { Database } from "../util/query";
type NotificationListener = (n: WalletNotification) => void; type NotificationListener = (n: WalletNotification) => void;
@ -45,7 +46,7 @@ export class InternalWalletState {
listeners: NotificationListener[] = []; listeners: NotificationListener[] = [];
constructor( constructor(
public db: IDBDatabase, public db: Database,
public http: HttpRequestLibrary, public http: HttpRequestLibrary,
cryptoWorkerFactory: CryptoWorkerFactory, cryptoWorkerFactory: CryptoWorkerFactory,
) { ) {

View File

@ -15,7 +15,7 @@
*/ */
import { oneShotGet, oneShotPut, oneShotMutate, runWithWriteTransaction } from "../util/query"; import { Database } from "../util/query";
import { InternalWalletState } from "./state"; import { InternalWalletState } from "./state";
import { parseTipUri } from "../util/taleruri"; import { parseTipUri } from "../util/taleruri";
import { TipStatus, getTimestampNow, OperationError } from "../types/walletTypes"; import { TipStatus, getTimestampNow, OperationError } from "../types/walletTypes";
@ -53,7 +53,7 @@ export async function getTipStatus(
let amount = Amounts.parseOrThrow(tipPickupStatus.amount); let amount = Amounts.parseOrThrow(tipPickupStatus.amount);
let tipRecord = await oneShotGet(ws.db, Stores.tips, [ let tipRecord = await ws.db.get(Stores.tips, [
res.merchantTipId, res.merchantTipId,
res.merchantOrigin, res.merchantOrigin,
]); ]);
@ -87,7 +87,7 @@ export async function getTipStatus(
retryInfo: initRetryInfo(), retryInfo: initRetryInfo(),
lastError: undefined, lastError: undefined,
}; };
await oneShotPut(ws.db, Stores.tips, tipRecord); await ws.db.put(Stores.tips, tipRecord);
} }
const tipStatus: TipStatus = { const tipStatus: TipStatus = {
@ -112,7 +112,7 @@ async function incrementTipRetry(
refreshSessionId: string, refreshSessionId: string,
err: OperationError | undefined, err: OperationError | undefined,
): Promise<void> { ): Promise<void> {
await runWithWriteTransaction(ws.db, [Stores.tips], async tx => { await ws.db.runWithWriteTransaction([Stores.tips], async tx => {
const t = await tx.get(Stores.tips, refreshSessionId); const t = await tx.get(Stores.tips, refreshSessionId);
if (!t) { if (!t) {
return; return;
@ -141,7 +141,7 @@ async function resetTipRetry(
ws: InternalWalletState, ws: InternalWalletState,
tipId: string, tipId: string,
): Promise<void> { ): Promise<void> {
await oneShotMutate(ws.db, Stores.tips, tipId, (x) => { await ws.db.mutate(Stores.tips, tipId, (x) => {
if (x.retryInfo.active) { if (x.retryInfo.active) {
x.retryInfo = initRetryInfo(); x.retryInfo = initRetryInfo();
} }
@ -157,7 +157,7 @@ async function processTipImpl(
if (forceNow) { if (forceNow) {
await resetTipRetry(ws, tipId); await resetTipRetry(ws, tipId);
} }
let tipRecord = await oneShotGet(ws.db, Stores.tips, tipId); let tipRecord = await ws.db.get(Stores.tips, tipId);
if (!tipRecord) { if (!tipRecord) {
return; return;
} }
@ -179,7 +179,7 @@ async function processTipImpl(
denomsForWithdraw.map(d => ws.cryptoApi.createTipPlanchet(d)), denomsForWithdraw.map(d => ws.cryptoApi.createTipPlanchet(d)),
); );
await oneShotMutate(ws.db, Stores.tips, tipId, r => { await ws.db.mutate(Stores.tips, tipId, r => {
if (!r.planchets) { if (!r.planchets) {
r.planchets = planchets; r.planchets = planchets;
} }
@ -187,7 +187,7 @@ async function processTipImpl(
}); });
} }
tipRecord = await oneShotGet(ws.db, Stores.tips, tipId); tipRecord = await ws.db.get(Stores.tips, tipId);
if (!tipRecord) { if (!tipRecord) {
throw Error("tip not in database"); throw Error("tip not in database");
} }
@ -267,7 +267,7 @@ async function processTipImpl(
}; };
await runWithWriteTransaction(ws.db, [Stores.tips, Stores.withdrawalSession], async (tx) => { await ws.db.runWithWriteTransaction([Stores.tips, Stores.withdrawalSession], async (tx) => {
const tr = await tx.get(Stores.tips, tipId); const tr = await tx.get(Stores.tips, tipId);
if (!tr) { if (!tr) {
return; return;
@ -291,14 +291,14 @@ export async function acceptTip(
ws: InternalWalletState, ws: InternalWalletState,
tipId: string, tipId: string,
): Promise<void> { ): Promise<void> {
const tipRecord = await oneShotGet(ws.db, Stores.tips, tipId); const tipRecord = await ws.db.get(Stores.tips, tipId);
if (!tipRecord) { if (!tipRecord) {
console.log("tip not found"); console.log("tip not found");
return; return;
} }
tipRecord.accepted = true; tipRecord.accepted = true;
await oneShotPut(ws.db, Stores.tips, tipRecord); await ws.db.put(Stores.tips, tipRecord);
await processTip(ws, tipId); await processTip(ws, tipId);
return; return;

View File

@ -39,12 +39,7 @@ import { InternalWalletState } from "./state";
import { parseWithdrawUri } from "../util/taleruri"; import { parseWithdrawUri } from "../util/taleruri";
import { Logger } from "../util/logging"; import { Logger } from "../util/logging";
import { import {
oneShotGet, Database
oneShotPut,
oneShotIterIndex,
oneShotGetIndexed,
runWithWriteTransaction,
oneShotMutate,
} from "../util/query"; } from "../util/query";
import { import {
updateExchangeFromUrl, updateExchangeFromUrl,
@ -167,8 +162,7 @@ async function getPossibleDenoms(
ws: InternalWalletState, ws: InternalWalletState,
exchangeBaseUrl: string, exchangeBaseUrl: string,
): Promise<DenominationRecord[]> { ): Promise<DenominationRecord[]> {
return await oneShotIterIndex( return await ws.db.iterIndex(
ws.db,
Stores.denominations.exchangeBaseUrlIndex, Stores.denominations.exchangeBaseUrlIndex,
exchangeBaseUrl, exchangeBaseUrl,
).filter(d => { ).filter(d => {
@ -187,8 +181,7 @@ async function processPlanchet(
withdrawalSessionId: string, withdrawalSessionId: string,
coinIdx: number, coinIdx: number,
): Promise<void> { ): Promise<void> {
const withdrawalSession = await oneShotGet( const withdrawalSession = await ws.db.get(
ws.db,
Stores.withdrawalSession, Stores.withdrawalSession,
withdrawalSessionId, withdrawalSessionId,
); );
@ -205,8 +198,7 @@ async function processPlanchet(
console.log("processPlanchet: planchet not found"); console.log("processPlanchet: planchet not found");
return; return;
} }
const exchange = await oneShotGet( const exchange = await ws.db.get(
ws.db,
Stores.exchanges, Stores.exchanges,
withdrawalSession.exchangeBaseUrl, withdrawalSession.exchangeBaseUrl,
); );
@ -215,7 +207,7 @@ async function processPlanchet(
return; return;
} }
const denom = await oneShotGet(ws.db, Stores.denominations, [ const denom = await ws.db.get(Stores.denominations, [
withdrawalSession.exchangeBaseUrl, withdrawalSession.exchangeBaseUrl,
planchet.denomPub, planchet.denomPub,
]); ]);
@ -268,8 +260,7 @@ async function processPlanchet(
let withdrawSessionFinished = false; let withdrawSessionFinished = false;
let reserveDepleted = false; let reserveDepleted = false;
const success = await runWithWriteTransaction( const success = await ws.db.runWithWriteTransaction(
ws.db,
[Stores.coins, Stores.withdrawalSession, Stores.reserves], [Stores.coins, Stores.withdrawalSession, Stores.reserves],
async tx => { async tx => {
const ws = await tx.get(Stores.withdrawalSession, withdrawalSessionId); const ws = await tx.get(Stores.withdrawalSession, withdrawalSessionId);
@ -346,7 +337,7 @@ export async function getVerifiedWithdrawDenomList(
exchangeBaseUrl: string, exchangeBaseUrl: string,
amount: AmountJson, amount: AmountJson,
): Promise<DenominationRecord[]> { ): Promise<DenominationRecord[]> {
const exchange = await oneShotGet(ws.db, Stores.exchanges, exchangeBaseUrl); const exchange = await ws.db.get(Stores.exchanges, exchangeBaseUrl);
if (!exchange) { if (!exchange) {
console.log("exchange not found"); console.log("exchange not found");
throw Error(`exchange ${exchangeBaseUrl} not found`); throw Error(`exchange ${exchangeBaseUrl} not found`);
@ -391,7 +382,7 @@ export async function getVerifiedWithdrawDenomList(
denom.status = DenominationStatus.VerifiedGood; denom.status = DenominationStatus.VerifiedGood;
nextPossibleDenoms.push(denom); nextPossibleDenoms.push(denom);
} }
await oneShotPut(ws.db, Stores.denominations, denom); await ws.db.put(Stores.denominations, denom);
} else { } else {
nextPossibleDenoms.push(denom); nextPossibleDenoms.push(denom);
} }
@ -408,8 +399,7 @@ async function makePlanchet(
withdrawalSessionId: string, withdrawalSessionId: string,
coinIndex: number, coinIndex: number,
): Promise<void> { ): Promise<void> {
const withdrawalSession = await oneShotGet( const withdrawalSession = await ws.db.get(
ws.db,
Stores.withdrawalSession, Stores.withdrawalSession,
withdrawalSessionId, withdrawalSessionId,
); );
@ -420,11 +410,11 @@ async function makePlanchet(
if (src.type !== "reserve") { if (src.type !== "reserve") {
throw Error("invalid state"); throw Error("invalid state");
} }
const reserve = await oneShotGet(ws.db, Stores.reserves, src.reservePub); const reserve = await ws.db.get(Stores.reserves, src.reservePub);
if (!reserve) { if (!reserve) {
return; return;
} }
const denom = await oneShotGet(ws.db, Stores.denominations, [ const denom = await ws.db.get(Stores.denominations, [
withdrawalSession.exchangeBaseUrl, withdrawalSession.exchangeBaseUrl,
withdrawalSession.denoms[coinIndex], withdrawalSession.denoms[coinIndex],
]); ]);
@ -450,7 +440,7 @@ async function makePlanchet(
reservePub: r.reservePub, reservePub: r.reservePub,
withdrawSig: r.withdrawSig, withdrawSig: r.withdrawSig,
}; };
await runWithWriteTransaction(ws.db, [Stores.withdrawalSession], async tx => { await ws.db.runWithWriteTransaction([Stores.withdrawalSession], async tx => {
const myWs = await tx.get(Stores.withdrawalSession, withdrawalSessionId); const myWs = await tx.get(Stores.withdrawalSession, withdrawalSessionId);
if (!myWs) { if (!myWs) {
return; return;
@ -469,8 +459,7 @@ async function processWithdrawCoin(
coinIndex: number, coinIndex: number,
) { ) {
logger.trace("starting withdraw for coin", coinIndex); logger.trace("starting withdraw for coin", coinIndex);
const withdrawalSession = await oneShotGet( const withdrawalSession = await ws.db.get(
ws.db,
Stores.withdrawalSession, Stores.withdrawalSession,
withdrawalSessionId, withdrawalSessionId,
); );
@ -479,8 +468,7 @@ async function processWithdrawCoin(
return; return;
} }
const coin = await oneShotGetIndexed( const coin = await ws.db.getIndexed(
ws.db,
Stores.coins.byWithdrawalWithIdx, Stores.coins.byWithdrawalWithIdx,
[withdrawalSessionId, coinIndex], [withdrawalSessionId, coinIndex],
); );
@ -505,7 +493,7 @@ async function incrementWithdrawalRetry(
withdrawalSessionId: string, withdrawalSessionId: string,
err: OperationError | undefined, err: OperationError | undefined,
): Promise<void> { ): Promise<void> {
await runWithWriteTransaction(ws.db, [Stores.withdrawalSession], async tx => { await ws.db.runWithWriteTransaction([Stores.withdrawalSession], async tx => {
const wsr = await tx.get(Stores.withdrawalSession, withdrawalSessionId); const wsr = await tx.get(Stores.withdrawalSession, withdrawalSessionId);
if (!wsr) { if (!wsr) {
return; return;
@ -538,7 +526,7 @@ async function resetWithdrawSessionRetry(
ws: InternalWalletState, ws: InternalWalletState,
withdrawalSessionId: string, withdrawalSessionId: string,
) { ) {
await oneShotMutate(ws.db, Stores.withdrawalSession, withdrawalSessionId, (x) => { await ws.db.mutate(Stores.withdrawalSession, withdrawalSessionId, (x) => {
if (x.retryInfo.active) { if (x.retryInfo.active) {
x.retryInfo = initRetryInfo(); x.retryInfo = initRetryInfo();
} }
@ -555,8 +543,7 @@ async function processWithdrawSessionImpl(
if (forceNow) { if (forceNow) {
await resetWithdrawSessionRetry(ws, withdrawalSessionId); await resetWithdrawSessionRetry(ws, withdrawalSessionId);
} }
const withdrawalSession = await oneShotGet( const withdrawalSession = await ws.db.get(
ws.db,
Stores.withdrawalSession, Stores.withdrawalSession,
withdrawalSessionId, withdrawalSessionId,
); );
@ -615,15 +602,13 @@ export async function getExchangeWithdrawalInfo(
} }
} }
const possibleDenoms = await oneShotIterIndex( const possibleDenoms = await ws.db.iterIndex(
ws.db,
Stores.denominations.exchangeBaseUrlIndex, Stores.denominations.exchangeBaseUrlIndex,
baseUrl, baseUrl,
).filter(d => d.isOffered); ).filter(d => d.isOffered);
const trustedAuditorPubs = []; const trustedAuditorPubs = [];
const currencyRecord = await oneShotGet( const currencyRecord = await ws.db.get(
ws.db,
Stores.currencies, Stores.currencies,
amount.currency, amount.currency,
); );

View File

@ -25,22 +25,6 @@
*/ */
import { openPromise } from "./promiseUtils"; import { openPromise } from "./promiseUtils";
/**
* Result of an inner join.
*/
export interface JoinResult<L, R> {
left: L;
right: R;
}
/**
* Result of a left outer join.
*/
export interface JoinLeftResult<L, R> {
left: L;
right?: R;
}
/** /**
* Definition of an object store. * Definition of an object store.
*/ */
@ -95,46 +79,6 @@ function transactionToPromise(tx: IDBTransaction): Promise<void> {
}); });
} }
export async function oneShotGet<T>(
db: IDBDatabase,
store: Store<T>,
key: any,
): Promise<T | undefined> {
const tx = db.transaction([store.name], "readonly");
const req = tx.objectStore(store.name).get(key);
const v = await requestToPromise(req);
await transactionToPromise(tx);
return v;
}
export async function oneShotGetIndexed<S extends IDBValidKey, T>(
db: IDBDatabase,
index: Index<S, T>,
key: any,
): Promise<T | undefined> {
const tx = db.transaction([index.storeName], "readonly");
const req = tx
.objectStore(index.storeName)
.index(index.indexName)
.get(key);
const v = await requestToPromise(req);
await transactionToPromise(tx);
return v;
}
export async function oneShotPut<T>(
db: IDBDatabase,
store: Store<T>,
value: T,
key?: any,
): Promise<any> {
const tx = db.transaction([store.name], "readwrite");
const req = tx.objectStore(store.name).put(value, key);
const v = await requestToPromise(req);
await transactionToPromise(tx);
return v;
}
function applyMutation<T>( function applyMutation<T>(
req: IDBRequest, req: IDBRequest,
f: (x: T) => T | undefined, f: (x: T) => T | undefined,
@ -166,18 +110,6 @@ function applyMutation<T>(
}); });
} }
export async function oneShotMutate<T>(
db: IDBDatabase,
store: Store<T>,
key: any,
f: (x: T) => T | undefined,
): Promise<void> {
const tx = db.transaction([store.name], "readwrite");
const req = tx.objectStore(store.name).openCursor(key);
await applyMutation(req, f);
await transactionToPromise(tx);
}
type CursorResult<T> = CursorEmptyResult<T> | CursorValueResult<T>; type CursorResult<T> = CursorEmptyResult<T> | CursorValueResult<T>;
interface CursorEmptyResult<T> { interface CursorEmptyResult<T> {
@ -294,28 +226,6 @@ class ResultStream<T> {
} }
} }
export function oneShotIter<T>(
db: IDBDatabase,
store: Store<T>,
): ResultStream<T> {
const tx = db.transaction([store.name], "readonly");
const req = tx.objectStore(store.name).openCursor();
return new ResultStream<T>(req);
}
export function oneShotIterIndex<S extends IDBValidKey, T>(
db: IDBDatabase,
index: Index<S, T>,
query?: any,
): ResultStream<T> {
const tx = db.transaction([index.storeName], "readonly");
const req = tx
.objectStore(index.storeName)
.index(index.indexName)
.openCursor(query);
return new ResultStream<T>(req);
}
export class TransactionHandle { export class TransactionHandle {
constructor(private tx: IDBTransaction) {} constructor(private tx: IDBTransaction) {}
@ -361,22 +271,6 @@ export class TransactionHandle {
} }
} }
export function runWithReadTransaction<T>(
db: IDBDatabase,
stores: Store<any>[],
f: (t: TransactionHandle) => Promise<T>,
): Promise<T> {
return runWithTransaction<T>(db, stores, f, "readonly");
}
export function runWithWriteTransaction<T>(
db: IDBDatabase,
stores: Store<any>[],
f: (t: TransactionHandle) => Promise<T>,
): Promise<T> {
return runWithTransaction<T>(db, stores, f, "readwrite");
}
function runWithTransaction<T>( function runWithTransaction<T>(
db: IDBDatabase, db: IDBDatabase,
stores: Store<any>[], stores: Store<any>[],
@ -470,7 +364,203 @@ export class Index<S extends IDBValidKey, T> {
protected _dummyKey: S | undefined; protected _dummyKey: S | undefined;
} }
/**
* Return a promise that resolves
* to the taler wallet db.
*/
export function openDatabase(
idbFactory: IDBFactory,
databaseName: string,
databaseVersion: number,
schema: any,
onVersionChange: () => void,
onUpgradeUnsupported: (oldVersion: number, newVersion: number) => void,
): Promise<IDBDatabase> {
return new Promise<IDBDatabase>((resolve, reject) => {
const req = idbFactory.open(databaseName, databaseVersion);
req.onerror = e => {
console.log("taler database error", e);
reject(new Error("database error"));
};
req.onsuccess = e => {
req.result.onversionchange = (evt: IDBVersionChangeEvent) => {
console.log(
`handling live db version change from ${evt.oldVersion} to ${evt.newVersion}`,
);
req.result.close();
onVersionChange();
};
resolve(req.result);
};
req.onupgradeneeded = e => {
const db = req.result;
console.log(
`DB: upgrade needed: oldVersion=${e.oldVersion}, newVersion=${e.newVersion}`,
);
switch (e.oldVersion) {
case 0: // DB does not exist yet
for (const n in schema) {
if (schema[n] instanceof Store) {
const si: Store<any> = schema[n];
const s = db.createObjectStore(si.name, si.storeParams);
for (const indexName in si as any) {
if ((si as any)[indexName] instanceof Index) {
const ii: Index<any, any> = (si as any)[indexName];
s.createIndex(ii.indexName, ii.keyPath, ii.options);
}
}
}
}
break;
default:
if (e.oldVersion !== databaseVersion) {
onUpgradeUnsupported(e.oldVersion, databaseVersion);
throw Error("incompatible DB");
}
break;
}
};
});
}
/** /**
* Exception that should be thrown by client code to abort a transaction. * Exception that should be thrown by client code to abort a transaction.
*/ */
export const TransactionAbort = Symbol("transaction_abort"); export const TransactionAbort = Symbol("transaction_abort");
export class Database {
constructor(private db: IDBDatabase) {}
static deleteDatabase(idbFactory: IDBFactory, dbName: string) {
idbFactory.deleteDatabase(dbName);
}
async exportDatabase(): Promise<any> {
const db = this.db;
const dump = {
name: db.name,
stores: {} as { [s: string]: any },
version: db.version,
};
return new Promise((resolve, reject) => {
const tx = db.transaction(Array.from(db.objectStoreNames));
tx.addEventListener("complete", () => {
resolve(dump);
});
// 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 cursor = (e.target as any).result;
if (cursor) {
storeDump[cursor.key] = cursor.value;
cursor.continue();
}
});
}
});
}
importDatabase(dump: any): Promise<void> {
const db = this.db;
console.log("importing db", dump);
return new Promise<void>((resolve, reject) => {
const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
if (dump.stores) {
for (const storeName in dump.stores) {
const objects = [];
const dumpStore = dump.stores[storeName];
for (const key in dumpStore) {
objects.push(dumpStore[key]);
}
console.log(`importing ${objects.length} records into ${storeName}`);
const store = tx.objectStore(storeName);
for (const obj of objects) {
store.put(obj);
}
}
}
tx.addEventListener("complete", () => {
resolve();
});
});
}
async get<T>(store: Store<T>, key: any): Promise<T | undefined> {
const tx = this.db.transaction([store.name], "readonly");
const req = tx.objectStore(store.name).get(key);
const v = await requestToPromise(req);
await transactionToPromise(tx);
return v;
}
async getIndexed<S extends IDBValidKey, T>(
index: Index<S, T>,
key: any,
): Promise<T | undefined> {
const tx = this.db.transaction([index.storeName], "readonly");
const req = tx
.objectStore(index.storeName)
.index(index.indexName)
.get(key);
const v = await requestToPromise(req);
await transactionToPromise(tx);
return v;
}
async put<T>(store: Store<T>, value: T, key?: any): Promise<any> {
const tx = this.db.transaction([store.name], "readwrite");
const req = tx.objectStore(store.name).put(value, key);
const v = await requestToPromise(req);
await transactionToPromise(tx);
return v;
}
async mutate<T>(
store: Store<T>,
key: any,
f: (x: T) => T | undefined,
): Promise<void> {
const tx = this.db.transaction([store.name], "readwrite");
const req = tx.objectStore(store.name).openCursor(key);
await applyMutation(req, f);
await transactionToPromise(tx);
}
iter<T>(store: Store<T>): ResultStream<T> {
const tx = this.db.transaction([store.name], "readonly");
const req = tx.objectStore(store.name).openCursor();
return new ResultStream<T>(req);
}
iterIndex<S extends IDBValidKey, T>(
index: Index<S, T>,
query?: any,
): ResultStream<T> {
const tx = this.db.transaction([index.storeName], "readonly");
const req = tx
.objectStore(index.storeName)
.index(index.indexName)
.openCursor(query);
return new ResultStream<T>(req);
}
async runWithReadTransaction<T>(
stores: Store<any>[],
f: (t: TransactionHandle) => Promise<T>,
): Promise<T> {
return runWithTransaction<T>(this.db, stores, f, "readonly");
}
async runWithWriteTransaction<T>(
stores: Store<any>[],
f: (t: TransactionHandle) => Promise<T>,
): Promise<T> {
return runWithTransaction<T>(this.db, stores, f, "readwrite");
}
}

View File

@ -25,11 +25,7 @@
import { CryptoWorkerFactory } from "./crypto/workers/cryptoApi"; import { CryptoWorkerFactory } from "./crypto/workers/cryptoApi";
import { HttpRequestLibrary } from "./util/http"; import { HttpRequestLibrary } from "./util/http";
import { import {
oneShotPut, Database
oneShotGet,
runWithWriteTransaction,
oneShotIter,
oneShotIterIndex,
} from "./util/query"; } from "./util/query";
import { AmountJson } from "./util/amounts"; import { AmountJson } from "./util/amounts";
@ -148,12 +144,12 @@ export class Wallet {
private stopped: boolean = false; private stopped: boolean = false;
private memoRunRetryLoop = new AsyncOpMemoSingle<void>(); private memoRunRetryLoop = new AsyncOpMemoSingle<void>();
get db(): IDBDatabase { get db(): Database {
return this.ws.db; return this.ws.db;
} }
constructor( constructor(
db: IDBDatabase, db: Database,
http: HttpRequestLibrary, http: HttpRequestLibrary,
cryptoWorkerFactory: CryptoWorkerFactory, cryptoWorkerFactory: CryptoWorkerFactory,
) { ) {
@ -345,8 +341,7 @@ export class Wallet {
* already been applied. * already been applied.
*/ */
async fillDefaults() { async fillDefaults() {
await runWithWriteTransaction( await this.db.runWithWriteTransaction(
this.db,
[Stores.config, Stores.currencies], [Stores.config, Stores.currencies],
async tx => { async tx => {
let applied = false; let applied = false;
@ -381,7 +376,7 @@ export class Wallet {
*/ */
async refreshDirtyCoins(): Promise<{ numRefreshed: number }> { async refreshDirtyCoins(): Promise<{ numRefreshed: number }> {
let n = 0; let n = 0;
const coins = await oneShotIter(this.db, Stores.coins).toArray(); const coins = await this.db.iter(Stores.coins).toArray();
for (let coin of coins) { for (let coin of coins) {
if (coin.status == CoinStatus.Dirty) { if (coin.status == CoinStatus.Dirty) {
try { try {
@ -512,7 +507,7 @@ export class Wallet {
async findExchange( async findExchange(
exchangeBaseUrl: string, exchangeBaseUrl: string,
): Promise<ExchangeRecord | undefined> { ): Promise<ExchangeRecord | undefined> {
return await oneShotGet(this.db, Stores.exchanges, exchangeBaseUrl); return await this.db.get(Stores.exchanges, exchangeBaseUrl);
} }
/** /**
@ -540,8 +535,7 @@ export class Wallet {
} }
async getDenoms(exchangeUrl: string): Promise<DenominationRecord[]> { async getDenoms(exchangeUrl: string): Promise<DenominationRecord[]> {
const denoms = await oneShotIterIndex( const denoms = await this.db.iterIndex(
this.db,
Stores.denominations.exchangeBaseUrlIndex, Stores.denominations.exchangeBaseUrlIndex,
exchangeUrl, exchangeUrl,
).toArray(); ).toArray();
@ -549,37 +543,37 @@ export class Wallet {
} }
async getProposal(proposalId: string): Promise<ProposalRecord | undefined> { async getProposal(proposalId: string): Promise<ProposalRecord | undefined> {
const proposal = await oneShotGet(this.db, Stores.proposals, proposalId); const proposal = await this.db.get(Stores.proposals, proposalId);
return proposal; return proposal;
} }
async getExchanges(): Promise<ExchangeRecord[]> { async getExchanges(): Promise<ExchangeRecord[]> {
return await oneShotIter(this.db, Stores.exchanges).toArray(); return await this.db.iter(Stores.exchanges).toArray();
} }
async getCurrencies(): Promise<CurrencyRecord[]> { async getCurrencies(): Promise<CurrencyRecord[]> {
return await oneShotIter(this.db, Stores.currencies).toArray(); return await this.db.iter(Stores.currencies).toArray();
} }
async updateCurrency(currencyRecord: CurrencyRecord): Promise<void> { async updateCurrency(currencyRecord: CurrencyRecord): Promise<void> {
logger.trace("updating currency to", currencyRecord); logger.trace("updating currency to", currencyRecord);
await oneShotPut(this.db, Stores.currencies, currencyRecord); await this.db.put(Stores.currencies, currencyRecord);
} }
async getReserves(exchangeBaseUrl: string): Promise<ReserveRecord[]> { async getReserves(exchangeBaseUrl: string): Promise<ReserveRecord[]> {
return await oneShotIter(this.db, Stores.reserves).filter( return await this.db.iter(Stores.reserves).filter(
r => r.exchangeBaseUrl === exchangeBaseUrl, r => r.exchangeBaseUrl === exchangeBaseUrl,
); );
} }
async getCoinsForExchange(exchangeBaseUrl: string): Promise<CoinRecord[]> { async getCoinsForExchange(exchangeBaseUrl: string): Promise<CoinRecord[]> {
return await oneShotIter(this.db, Stores.coins).filter( return await this.db.iter(Stores.coins).filter(
c => c.exchangeBaseUrl === exchangeBaseUrl, c => c.exchangeBaseUrl === exchangeBaseUrl,
); );
} }
async getCoins(): Promise<CoinRecord[]> { async getCoins(): Promise<CoinRecord[]> {
return await oneShotIter(this.db, Stores.coins).toArray(); return await this.db.iter(Stores.coins).toArray();
} }
async payback(coinPub: string): Promise<void> { async payback(coinPub: string): Promise<void> {
@ -587,7 +581,7 @@ export class Wallet {
} }
async getPaybackReserves(): Promise<ReserveRecord[]> { async getPaybackReserves(): Promise<ReserveRecord[]> {
return await oneShotIter(this.db, Stores.reserves).filter( return await this.db.iter(Stores.reserves).filter(
r => r.hasPayback, r => r.hasPayback,
); );
} }
@ -604,7 +598,7 @@ export class Wallet {
async getSenderWireInfos(): Promise<SenderWireInfos> { async getSenderWireInfos(): Promise<SenderWireInfos> {
const m: { [url: string]: Set<string> } = {}; const m: { [url: string]: Set<string> } = {};
await oneShotIter(this.db, Stores.exchanges).forEach(x => { await this.db.iter(Stores.exchanges).forEach(x => {
const wi = x.wireInfo; const wi = x.wireInfo;
if (!wi) { if (!wi) {
return; return;
@ -619,7 +613,7 @@ export class Wallet {
}); });
const senderWiresSet: Set<string> = new Set(); const senderWiresSet: Set<string> = new Set();
await oneShotIter(this.db, Stores.senderWires).forEach(x => { await this.db.iter(Stores.senderWires).forEach(x => {
senderWiresSet.add(x.paytoUri); senderWiresSet.add(x.paytoUri);
}); });
@ -649,7 +643,7 @@ export class Wallet {
async getPurchase( async getPurchase(
contractTermsHash: string, contractTermsHash: string,
): Promise<PurchaseRecord | undefined> { ): Promise<PurchaseRecord | undefined> {
return oneShotGet(this.db, Stores.purchases, contractTermsHash); return this.db.get(Stores.purchases, contractTermsHash);
} }
async getFullRefundFees( async getFullRefundFees(
@ -683,7 +677,7 @@ export class Wallet {
* confirmation from the bank.). * confirmation from the bank.).
*/ */
public async handleNotifyReserve() { public async handleNotifyReserve() {
const reserves = await oneShotIter(this.db, Stores.reserves).toArray(); const reserves = await this.db.iter(Stores.reserves).toArray();
for (const r of reserves) { for (const r of reserves) {
if (r.reserveStatus === ReserveRecordStatus.WAIT_CONFIRM_BANK) { if (r.reserveStatus === ReserveRecordStatus.WAIT_CONFIRM_BANK) {
try { try {
@ -718,7 +712,7 @@ export class Wallet {
} }
async getPurchaseDetails(hc: string): Promise<PurchaseDetails> { async getPurchaseDetails(hc: string): Promise<PurchaseDetails> {
const purchase = await oneShotGet(this.db, Stores.purchases, hc); const purchase = await this.db.get(Stores.purchases, hc);
if (!purchase) { if (!purchase) {
throw Error("unknown purchase"); throw Error("unknown purchase");
} }

View File

@ -24,7 +24,7 @@
* Imports. * Imports.
*/ */
import { BrowserCryptoWorkerFactory } from "../crypto/workers/cryptoApi"; import { BrowserCryptoWorkerFactory } from "../crypto/workers/cryptoApi";
import { deleteDatabase, exportDatabase, importDatabase, openDatabase } from "../db"; import { deleteTalerDatabase, openTalerDatabase } from "../db";
import { WALLET_DB_VERSION } from "../types/dbTypes"; import { WALLET_DB_VERSION } from "../types/dbTypes";
import { ConfirmReserveRequest, CreateReserveRequest, ReturnCoinsRequest, WalletDiagnostics } from "../types/walletTypes"; import { ConfirmReserveRequest, CreateReserveRequest, ReturnCoinsRequest, WalletDiagnostics } from "../types/walletTypes";
import { AmountJson } from "../util/amounts"; import { AmountJson } from "../util/amounts";
@ -37,6 +37,7 @@ import { isFirefox } from "./compat";
import { MessageType } from "./messages"; import { MessageType } from "./messages";
import * as wxApi from "./wxApi"; import * as wxApi from "./wxApi";
import MessageSender = chrome.runtime.MessageSender; import MessageSender = chrome.runtime.MessageSender;
import { Database } from "../util/query";
const NeedsWallet = Symbol("NeedsWallet"); const NeedsWallet = Symbol("NeedsWallet");
@ -67,25 +68,17 @@ async function handleMessage(
} }
case "dump-db": { case "dump-db": {
const db = needsWallet().db; const db = needsWallet().db;
return exportDatabase(db); return db.exportDatabase()
} }
case "import-db": { case "import-db": {
const db = needsWallet().db; const db = needsWallet().db;
return importDatabase(db, detail.dump); return db.importDatabase(detail.dump);
} }
case "ping": { case "ping": {
return Promise.resolve(); return Promise.resolve();
} }
case "reset-db": { case "reset-db": {
if (currentWallet) { deleteTalerDatabase(indexedDB);
const db = currentWallet.db;
const tx = db.transaction(Array.from(db.objectStoreNames), "readwrite");
// tslint:disable-next-line:prefer-for-of
for (let i = 0; i < db.objectStoreNames.length; i++) {
tx.objectStore(db.objectStoreNames[i]).clear();
}
}
deleteDatabase(indexedDB);
setBadgeText({ text: "" }); setBadgeText({ text: "" });
console.log("reset done"); console.log("reset done");
if (!currentWallet) { if (!currentWallet) {
@ -417,7 +410,7 @@ async function reinitWallet() {
setBadgeText({ text: "" }); setBadgeText({ text: "" });
const badge = new ChromeBadge(); const badge = new ChromeBadge();
try { try {
currentDatabase = await openDatabase( currentDatabase = await openTalerDatabase(
indexedDB, indexedDB,
reinitWallet, reinitWallet,
handleUpgradeUnsupported, handleUpgradeUnsupported,
@ -430,7 +423,7 @@ async function reinitWallet() {
const http = new BrowserHttpLib(); const http = new BrowserHttpLib();
console.log("setting wallet"); console.log("setting wallet");
const wallet = new Wallet( const wallet = new Wallet(
currentDatabase, new Database(currentDatabase),
http, http,
new BrowserCryptoWorkerFactory(), new BrowserCryptoWorkerFactory(),
); );