perf: reserve history in separate object store
This commit is contained in:
parent
277a513a8f
commit
857a2b9dca
@ -7,7 +7,7 @@ import { openDatabase, Database, Store, Index } from "./util/query";
|
|||||||
* with each major change. When incrementing the major version,
|
* with each major change. When incrementing the major version,
|
||||||
* the wallet should import data from the previous version.
|
* the wallet should import data from the previous version.
|
||||||
*/
|
*/
|
||||||
const TALER_DB_NAME = "taler-walletdb-v2";
|
const TALER_DB_NAME = "taler-walletdb-v3";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current database minor version, should be incremented
|
* Current database minor version, should be incremented
|
||||||
|
@ -145,7 +145,7 @@ export async function getBalances(
|
|||||||
): Promise<WalletBalance> {
|
): Promise<WalletBalance> {
|
||||||
logger.trace("starting to compute balance");
|
logger.trace("starting to compute balance");
|
||||||
|
|
||||||
return await ws.db.runWithReadTransaction(
|
const wbal = await ws.db.runWithReadTransaction(
|
||||||
[
|
[
|
||||||
Stores.coins,
|
Stores.coins,
|
||||||
Stores.refreshGroups,
|
Stores.refreshGroups,
|
||||||
@ -157,4 +157,8 @@ export async function getBalances(
|
|||||||
return getBalancesInsideTransaction(ws, tx);
|
return getBalancesInsideTransaction(ws, tx);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
logger.trace("finished computing wallet balance");
|
||||||
|
|
||||||
|
return wbal;
|
||||||
}
|
}
|
||||||
|
@ -172,6 +172,7 @@ export async function getHistory(
|
|||||||
Stores.purchases,
|
Stores.purchases,
|
||||||
Stores.refreshGroups,
|
Stores.refreshGroups,
|
||||||
Stores.reserves,
|
Stores.reserves,
|
||||||
|
Stores.reserveHistory,
|
||||||
Stores.tips,
|
Stores.tips,
|
||||||
Stores.withdrawalGroups,
|
Stores.withdrawalGroups,
|
||||||
Stores.payEvents,
|
Stores.payEvents,
|
||||||
@ -384,8 +385,12 @@ export async function getHistory(
|
|||||||
type: ReserveType.Manual,
|
type: ReserveType.Manual,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
const hist = await tx.get(Stores.reserveHistory, reserve.reservePub);
|
||||||
|
if (!hist) {
|
||||||
|
throw Error("inconsistent database");
|
||||||
|
}
|
||||||
const s = summarizeReserveHistory(
|
const s = summarizeReserveHistory(
|
||||||
reserve.reserveTransactions,
|
hist.reserveTransactions,
|
||||||
reserve.currency,
|
reserve.currency,
|
||||||
);
|
);
|
||||||
history.push({
|
history.push({
|
||||||
|
@ -33,8 +33,8 @@ import {
|
|||||||
updateRetryInfoTimeout,
|
updateRetryInfoTimeout,
|
||||||
ReserveUpdatedEventRecord,
|
ReserveUpdatedEventRecord,
|
||||||
WalletReserveHistoryItemType,
|
WalletReserveHistoryItemType,
|
||||||
PlanchetRecord,
|
|
||||||
WithdrawalSourceType,
|
WithdrawalSourceType,
|
||||||
|
ReserveHistoryRecord,
|
||||||
} from "../types/dbTypes";
|
} from "../types/dbTypes";
|
||||||
import { Logger } from "../util/logging";
|
import { Logger } from "../util/logging";
|
||||||
import { Amounts } from "../util/amounts";
|
import { Amounts } from "../util/amounts";
|
||||||
@ -114,11 +114,15 @@ export async function createReserve(
|
|||||||
lastSuccessfulStatusQuery: undefined,
|
lastSuccessfulStatusQuery: undefined,
|
||||||
retryInfo: initRetryInfo(),
|
retryInfo: initRetryInfo(),
|
||||||
lastError: undefined,
|
lastError: undefined,
|
||||||
reserveTransactions: [],
|
|
||||||
currency: req.amount.currency,
|
currency: req.amount.currency,
|
||||||
};
|
};
|
||||||
|
|
||||||
reserveRecord.reserveTransactions.push({
|
const reserveHistoryRecord: ReserveHistoryRecord = {
|
||||||
|
reservePub: keypair.pub,
|
||||||
|
reserveTransactions: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
reserveHistoryRecord.reserveTransactions.push({
|
||||||
type: WalletReserveHistoryItemType.Credit,
|
type: WalletReserveHistoryItemType.Credit,
|
||||||
expectedAmount: req.amount,
|
expectedAmount: req.amount,
|
||||||
});
|
});
|
||||||
@ -161,7 +165,12 @@ export async function createReserve(
|
|||||||
const cr: CurrencyRecord = currencyRecord;
|
const cr: CurrencyRecord = currencyRecord;
|
||||||
|
|
||||||
const resp = await ws.db.runWithWriteTransaction(
|
const resp = await ws.db.runWithWriteTransaction(
|
||||||
[Stores.currencies, Stores.reserves, Stores.bankWithdrawUris],
|
[
|
||||||
|
Stores.currencies,
|
||||||
|
Stores.reserves,
|
||||||
|
Stores.reserveHistory,
|
||||||
|
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
|
||||||
if (reserveRecord.bankWithdrawStatusUrl) {
|
if (reserveRecord.bankWithdrawStatusUrl) {
|
||||||
@ -188,6 +197,7 @@ export async function createReserve(
|
|||||||
}
|
}
|
||||||
await tx.put(Stores.currencies, cr);
|
await tx.put(Stores.currencies, cr);
|
||||||
await tx.put(Stores.reserves, reserveRecord);
|
await tx.put(Stores.reserves, reserveRecord);
|
||||||
|
await tx.put(Stores.reserveHistory, reserveHistoryRecord);
|
||||||
const r: CreateReserveResponse = {
|
const r: CreateReserveResponse = {
|
||||||
exchange: canonExchange,
|
exchange: canonExchange,
|
||||||
reservePub: keypair.pub,
|
reservePub: keypair.pub,
|
||||||
@ -462,7 +472,7 @@ async function updateReserve(
|
|||||||
const balance = Amounts.parseOrThrow(reserveInfo.balance);
|
const balance = Amounts.parseOrThrow(reserveInfo.balance);
|
||||||
const currency = balance.currency;
|
const currency = balance.currency;
|
||||||
await ws.db.runWithWriteTransaction(
|
await ws.db.runWithWriteTransaction(
|
||||||
[Stores.reserves, Stores.reserveUpdatedEvents],
|
[Stores.reserves, Stores.reserveUpdatedEvents, Stores.reserveHistory],
|
||||||
async (tx) => {
|
async (tx) => {
|
||||||
const r = await tx.get(Stores.reserves, reservePub);
|
const r = await tx.get(Stores.reserves, reservePub);
|
||||||
if (!r) {
|
if (!r) {
|
||||||
@ -472,14 +482,19 @@ async function updateReserve(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hist = await tx.get(Stores.reserveHistory, reservePub);
|
||||||
|
if (!hist) {
|
||||||
|
throw Error("inconsistent database");
|
||||||
|
}
|
||||||
|
|
||||||
const newHistoryTransactions = reserveInfo.history.slice(
|
const newHistoryTransactions = reserveInfo.history.slice(
|
||||||
r.reserveTransactions.length,
|
hist.reserveTransactions.length,
|
||||||
);
|
);
|
||||||
|
|
||||||
const reserveUpdateId = encodeCrock(getRandomBytes(32));
|
const reserveUpdateId = encodeCrock(getRandomBytes(32));
|
||||||
|
|
||||||
const reconciled = reconcileReserveHistory(
|
const reconciled = reconcileReserveHistory(
|
||||||
r.reserveTransactions,
|
hist.reserveTransactions,
|
||||||
reserveInfo.history,
|
reserveInfo.history,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -514,9 +529,10 @@ async function updateReserve(
|
|||||||
r.retryInfo = initRetryInfo(false);
|
r.retryInfo = initRetryInfo(false);
|
||||||
}
|
}
|
||||||
r.lastSuccessfulStatusQuery = getTimestampNow();
|
r.lastSuccessfulStatusQuery = getTimestampNow();
|
||||||
r.reserveTransactions = reconciled.updatedLocalHistory;
|
hist.reserveTransactions = reconciled.updatedLocalHistory;
|
||||||
r.lastError = undefined;
|
r.lastError = undefined;
|
||||||
await tx.put(Stores.reserves, r);
|
await tx.put(Stores.reserves, r);
|
||||||
|
await tx.put(Stores.reserveHistory, hist);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
ws.notify({ type: NotificationType.ReserveUpdated });
|
ws.notify({ type: NotificationType.ReserveUpdated });
|
||||||
@ -602,17 +618,29 @@ async function depleteReserve(
|
|||||||
ws: InternalWalletState,
|
ws: InternalWalletState,
|
||||||
reservePub: string,
|
reservePub: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const reserve = await ws.db.get(Stores.reserves, reservePub);
|
let reserve: ReserveRecord | undefined;
|
||||||
|
let hist: ReserveHistoryRecord | undefined;
|
||||||
|
await ws.db.runWithReadTransaction(
|
||||||
|
[Stores.reserves, Stores.reserveHistory],
|
||||||
|
async (tx) => {
|
||||||
|
reserve = await tx.get(Stores.reserves, reservePub);
|
||||||
|
hist = await tx.get(Stores.reserveHistory, reservePub);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (!reserve) {
|
if (!reserve) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!hist) {
|
||||||
|
throw Error("inconsistent database");
|
||||||
|
}
|
||||||
if (reserve.reserveStatus !== ReserveRecordStatus.WITHDRAWING) {
|
if (reserve.reserveStatus !== ReserveRecordStatus.WITHDRAWING) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logger.trace(`depleting reserve ${reservePub}`);
|
logger.trace(`depleting reserve ${reservePub}`);
|
||||||
|
|
||||||
const summary = summarizeReserveHistory(
|
const summary = summarizeReserveHistory(
|
||||||
reserve.reserveTransactions,
|
hist.reserveTransactions,
|
||||||
reserve.currency,
|
reserve.currency,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -674,7 +702,12 @@ async function depleteReserve(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const success = await ws.db.runWithWriteTransaction(
|
const success = await ws.db.runWithWriteTransaction(
|
||||||
[Stores.withdrawalGroups, Stores.reserves, Stores.planchets],
|
[
|
||||||
|
Stores.withdrawalGroups,
|
||||||
|
Stores.reserves,
|
||||||
|
Stores.reserveHistory,
|
||||||
|
Stores.planchets,
|
||||||
|
],
|
||||||
async (tx) => {
|
async (tx) => {
|
||||||
const newReserve = await tx.get(Stores.reserves, reservePub);
|
const newReserve = await tx.get(Stores.reserves, reservePub);
|
||||||
if (!newReserve) {
|
if (!newReserve) {
|
||||||
@ -683,8 +716,12 @@ async function depleteReserve(
|
|||||||
if (newReserve.reserveStatus !== ReserveRecordStatus.WITHDRAWING) {
|
if (newReserve.reserveStatus !== ReserveRecordStatus.WITHDRAWING) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const newHist = await tx.get(Stores.reserveHistory, reservePub);
|
||||||
|
if (!newHist) {
|
||||||
|
throw Error("inconsistent database");
|
||||||
|
}
|
||||||
const newSummary = summarizeReserveHistory(
|
const newSummary = summarizeReserveHistory(
|
||||||
newReserve.reserveTransactions,
|
newHist.reserveTransactions,
|
||||||
newReserve.currency,
|
newReserve.currency,
|
||||||
);
|
);
|
||||||
if (
|
if (
|
||||||
@ -703,7 +740,7 @@ async function depleteReserve(
|
|||||||
const sd = denomsForWithdraw.selectedDenoms[i];
|
const sd = denomsForWithdraw.selectedDenoms[i];
|
||||||
for (let j = 0; j < sd.count; j++) {
|
for (let j = 0; j < sd.count; j++) {
|
||||||
const amt = Amounts.add(sd.denom.value, sd.denom.feeWithdraw).amount;
|
const amt = Amounts.add(sd.denom.value, sd.denom.feeWithdraw).amount;
|
||||||
newReserve.reserveTransactions.push({
|
newHist.reserveTransactions.push({
|
||||||
type: WalletReserveHistoryItemType.Withdraw,
|
type: WalletReserveHistoryItemType.Withdraw,
|
||||||
expectedAmount: amt,
|
expectedAmount: amt,
|
||||||
});
|
});
|
||||||
@ -712,6 +749,7 @@ async function depleteReserve(
|
|||||||
newReserve.reserveStatus = ReserveRecordStatus.DORMANT;
|
newReserve.reserveStatus = ReserveRecordStatus.DORMANT;
|
||||||
newReserve.retryInfo = initRetryInfo(false);
|
newReserve.retryInfo = initRetryInfo(false);
|
||||||
await tx.put(Stores.reserves, newReserve);
|
await tx.put(Stores.reserves, newReserve);
|
||||||
|
await tx.put(Stores.reserveHistory, newHist);
|
||||||
await tx.put(Stores.withdrawalGroups, withdrawalRecord);
|
await tx.put(Stores.withdrawalGroups, withdrawalRecord);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
@ -564,7 +564,7 @@ async function processWithdrawGroupImpl(
|
|||||||
|
|
||||||
// Withdraw coins in batches.
|
// Withdraw coins in batches.
|
||||||
// The batch size is relatively large
|
// The batch size is relatively large
|
||||||
await processInBatches(genWork(), 50);
|
await processInBatches(genWork(), 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getExchangeWithdrawalInfo(
|
export async function getExchangeWithdrawalInfo(
|
||||||
|
@ -210,6 +210,11 @@ export type WalletReserveHistoryItem =
|
|||||||
| WalletReserveHistoryRecoupItem
|
| WalletReserveHistoryRecoupItem
|
||||||
| WalletReserveHistoryClosingItem;
|
| WalletReserveHistoryClosingItem;
|
||||||
|
|
||||||
|
export interface ReserveHistoryRecord {
|
||||||
|
reservePub: string;
|
||||||
|
reserveTransactions: WalletReserveHistoryItem[];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A reserve record as stored in the wallet's database.
|
* A reserve record as stored in the wallet's database.
|
||||||
*/
|
*/
|
||||||
@ -295,8 +300,6 @@ export interface ReserveRecord {
|
|||||||
* (either talking to the bank or the exchange).
|
* (either talking to the bank or the exchange).
|
||||||
*/
|
*/
|
||||||
lastError: OperationError | undefined;
|
lastError: OperationError | undefined;
|
||||||
|
|
||||||
reserveTransactions: WalletReserveHistoryItem[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1639,6 +1642,12 @@ export namespace Stores {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ReserveHistoryStore extends Store<ReserveHistoryRecord> {
|
||||||
|
constructor() {
|
||||||
|
super("reserveHistory", { keyPath: "reservePub" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TipsStore extends Store<TipRecord> {
|
class TipsStore extends Store<TipRecord> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super("tips", { keyPath: "tipId" });
|
super("tips", { keyPath: "tipId" });
|
||||||
@ -1725,6 +1734,7 @@ export namespace Stores {
|
|||||||
keyPath: "recoupGroupId",
|
keyPath: "recoupGroupId",
|
||||||
});
|
});
|
||||||
export const reserves = new ReservesStore();
|
export const reserves = new ReservesStore();
|
||||||
|
export const reserveHistory = new ReserveHistoryStore();
|
||||||
export const purchases = new PurchasesStore();
|
export const purchases = new PurchasesStore();
|
||||||
export const tips = new TipsStore();
|
export const tips = new TipsStore();
|
||||||
export const senderWires = new SenderWiresStore();
|
export const senderWires = new SenderWiresStore();
|
||||||
|
@ -177,6 +177,7 @@ class WalletBalanceView extends React.Component<any, any> {
|
|||||||
private gotError = false;
|
private gotError = false;
|
||||||
private canceler: (() => void) | undefined = undefined;
|
private canceler: (() => void) | undefined = undefined;
|
||||||
private unmount = false;
|
private unmount = false;
|
||||||
|
private updateBalanceRunning = false;
|
||||||
|
|
||||||
componentWillMount(): void {
|
componentWillMount(): void {
|
||||||
this.canceler = wxApi.onUpdateNotification(() => this.updateBalance());
|
this.canceler = wxApi.onUpdateNotification(() => this.updateBalance());
|
||||||
@ -192,6 +193,10 @@ class WalletBalanceView extends React.Component<any, any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async updateBalance(): Promise<void> {
|
async updateBalance(): Promise<void> {
|
||||||
|
if (this.updateBalanceRunning) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.updateBalanceRunning = true;
|
||||||
let balance: WalletBalance;
|
let balance: WalletBalance;
|
||||||
try {
|
try {
|
||||||
balance = await wxApi.getBalance();
|
balance = await wxApi.getBalance();
|
||||||
@ -203,6 +208,8 @@ class WalletBalanceView extends React.Component<any, any> {
|
|||||||
console.error("could not retrieve balances", e);
|
console.error("could not retrieve balances", e);
|
||||||
this.setState({});
|
this.setState({});
|
||||||
return;
|
return;
|
||||||
|
} finally {
|
||||||
|
this.updateBalanceRunning = false;
|
||||||
}
|
}
|
||||||
if (this.unmount) {
|
if (this.unmount) {
|
||||||
return;
|
return;
|
||||||
|
Loading…
Reference in New Issue
Block a user