make more use of the denom cache

This commit is contained in:
Florian Dold 2022-01-13 22:01:14 +01:00
parent cd2473e1ad
commit 17c3ced648
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
9 changed files with 89 additions and 48 deletions

View File

@ -1979,6 +1979,7 @@ export async function runTestWithState(
}); });
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
rl.question("Press enter to shut down test.", () => { rl.question("Press enter to shut down test.", () => {
console.error("Requested shutdown");
resolve(); resolve();
}); });
}); });

View File

@ -228,7 +228,7 @@ export interface InternalWalletState {
getDenomInfo( getDenomInfo(
ws: InternalWalletState, ws: InternalWalletState,
tx: GetReadWriteAccess<{ tx: GetReadOnlyAccess<{
denominations: typeof WalletStoresV1.denominations; denominations: typeof WalletStoresV1.denominations;
}>, }>,
exchangeBaseUrl: string, exchangeBaseUrl: string,

View File

@ -26,7 +26,6 @@ import {
ContractTerms, ContractTerms,
CreateDepositGroupRequest, CreateDepositGroupRequest,
CreateDepositGroupResponse, CreateDepositGroupResponse,
decodeCrock,
DenomKeyType, DenomKeyType,
durationFromSpec, durationFromSpec,
GetFeeForDepositRequest, GetFeeForDepositRequest,
@ -250,6 +249,7 @@ async function processDepositGroupImpl(
}; };
} }
const url = new URL(`coins/${perm.coin_pub}/deposit`, perm.exchange_url); const url = new URL(`coins/${perm.coin_pub}/deposit`, perm.exchange_url);
logger.info(`depositing to ${url}`);
const httpResp = await ws.http.postJson(url.href, requestBody); const httpResp = await ws.http.postJson(url.href, requestBody);
await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess()); await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess());
await ws.db await ws.db
@ -616,10 +616,12 @@ export async function getEffectiveDepositAmount(
if (!coin) { if (!coin) {
throw Error("can't calculate deposit amount, coin not found"); throw Error("can't calculate deposit amount, coin not found");
} }
const denom = await tx.denominations.get([ const denom = await ws.getDenomInfo(
ws,
tx,
coin.exchangeBaseUrl, coin.exchangeBaseUrl,
coin.denomPubHash, coin.denomPubHash,
]); );
if (!denom) { if (!denom) {
throw Error("can't find denomination to calculate deposit amount"); throw Error("can't find denomination to calculate deposit amount");
} }
@ -688,10 +690,12 @@ export async function getTotalFeeForDepositAmount(
if (!coin) { if (!coin) {
throw Error("can't calculate deposit amount, coin not found"); throw Error("can't calculate deposit amount, coin not found");
} }
const denom = await tx.denominations.get([ const denom = await ws.getDenomInfo(
ws,
tx,
coin.exchangeBaseUrl, coin.exchangeBaseUrl,
coin.denomPubHash, coin.denomPubHash,
]); );
if (!denom) { if (!denom) {
throw Error("can't find denomination to calculate deposit amount"); throw Error("can't find denomination to calculate deposit amount");
} }

View File

@ -131,7 +131,7 @@ async function handleExchangeUpdateError(
exchange.retryInfo.retryCounter++; exchange.retryInfo.retryCounter++;
updateRetryInfoTimeout(exchange.retryInfo); updateRetryInfoTimeout(exchange.retryInfo);
exchange.lastError = err; exchange.lastError = err;
await tx.exchanges.put(exchange) await tx.exchanges.put(exchange);
}); });
if (err) { if (err) {
ws.notify({ type: NotificationType.ExchangeOperationError, error: err }); ws.notify({ type: NotificationType.ExchangeOperationError, error: err });
@ -527,11 +527,11 @@ async function updateExchangeFromUrlImpl(
tosFound !== undefined tosFound !== undefined
? tosFound ? tosFound
: await downloadExchangeWithTermsOfService( : await downloadExchangeWithTermsOfService(
baseUrl, baseUrl,
ws.http, ws.http,
timeout, timeout,
"text/plain", "text/plain",
); );
let recoupGroupId: string | undefined = undefined; let recoupGroupId: string | undefined = undefined;
@ -589,7 +589,7 @@ async function updateExchangeFromUrlImpl(
await tx.exchanges.put(r); await tx.exchanges.put(r);
await tx.exchangeDetails.put(details); await tx.exchangeDetails.put(details);
logger.trace("updating denominations in database"); logger.info("updating denominations in database");
const currentDenomSet = new Set<string>( const currentDenomSet = new Set<string>(
keysInfo.currentDenominations.map((x) => x.denomPubHash), keysInfo.currentDenominations.map((x) => x.denomPubHash),
); );
@ -750,9 +750,10 @@ 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 exchangeTrustRecord = await tx.exchangesTrustStore.indexes.byExchangeMasterPub.get( const exchangeTrustRecord =
exchangeDetails.masterPublicKey, await tx.exchangesTrustStore.indexes.byExchangeMasterPub.get(
); exchangeDetails.masterPublicKey,
);
if ( if (
exchangeTrustRecord && exchangeTrustRecord &&
exchangeTrustRecord.uids.length > 0 && exchangeTrustRecord.uids.length > 0 &&
@ -762,9 +763,8 @@ export async function getExchangeTrust(
} }
for (const auditor of exchangeDetails.auditors) { for (const auditor of exchangeDetails.auditors) {
const auditorTrustRecord = await tx.auditorTrust.indexes.byAuditorPub.get( const auditorTrustRecord =
auditor.auditor_pub, await tx.auditorTrust.indexes.byAuditorPub.get(auditor.auditor_pub);
);
if (auditorTrustRecord && auditorTrustRecord.uids.length > 0) { if (auditorTrustRecord && auditorTrustRecord.uids.length > 0) {
isAudited = true; isAudited = true;
break; break;

View File

@ -166,8 +166,10 @@ export async function getTotalPaymentCost(
.filter((x) => .filter((x) =>
Amounts.isSameCurrency(x.value, pcs.coinContributions[i]), Amounts.isSameCurrency(x.value, pcs.coinContributions[i]),
); );
const amountLeft = Amounts.sub(denom.value, pcs.coinContributions[i]) const amountLeft = Amounts.sub(
.amount; denom.value,
pcs.coinContributions[i],
).amount;
const refreshCost = getTotalRefreshCost(allDenoms, denom, amountLeft); const refreshCost = getTotalRefreshCost(allDenoms, denom, amountLeft);
costs.push(pcs.coinContributions[i]); costs.push(pcs.coinContributions[i]);
costs.push(refreshCost); costs.push(refreshCost);
@ -290,10 +292,12 @@ export async function getCandidatePayCoins(
// 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 tx.denominations.get([ const firstDenom = await ws.getDenomInfo(
ws,
tx,
exchange.baseUrl, exchange.baseUrl,
coins[0].denomPubHash, coins[0].denomPubHash,
]); );
if (!firstDenom) { if (!firstDenom) {
throw Error("db inconsistent"); throw Error("db inconsistent");
} }
@ -365,6 +369,7 @@ export async function applyCoinSpend(
coinSelection: PayCoinSelection, coinSelection: PayCoinSelection,
allocationId: string, allocationId: string,
) { ) {
logger.info(`applying coin spend ${j2s(coinSelection)}`);
for (let i = 0; i < coinSelection.coinPubs.length; i++) { for (let i = 0; i < coinSelection.coinPubs.length; i++) {
const coin = await tx.coins.get(coinSelection.coinPubs[i]); const coin = await tx.coins.get(coinSelection.coinPubs[i]);
if (!coin) { if (!coin) {
@ -525,7 +530,8 @@ async function incrementPurchasePayRetry(
pr.payRetryInfo.retryCounter++; pr.payRetryInfo.retryCounter++;
updateRetryInfoTimeout(pr.payRetryInfo); updateRetryInfoTimeout(pr.payRetryInfo);
logger.trace( logger.trace(
`retrying pay in ${getDurationRemaining(pr.payRetryInfo.nextRetry).d_ms `retrying pay in ${
getDurationRemaining(pr.payRetryInfo.nextRetry).d_ms
} ms`, } ms`,
); );
pr.lastPayError = err; pr.lastPayError = err;
@ -812,9 +818,8 @@ async function processDownloadProposalImpl(
(fulfillmentUrl.startsWith("http://") || (fulfillmentUrl.startsWith("http://") ||
fulfillmentUrl.startsWith("https://")) fulfillmentUrl.startsWith("https://"))
) { ) {
const differentPurchase = await tx.purchases.indexes.byFulfillmentUrl.get( const differentPurchase =
fulfillmentUrl, await tx.purchases.indexes.byFulfillmentUrl.get(fulfillmentUrl);
);
if (differentPurchase) { if (differentPurchase) {
logger.warn("repurchase detected"); logger.warn("repurchase detected");
p.proposalStatus = ProposalStatus.REPURCHASE; p.proposalStatus = ProposalStatus.REPURCHASE;

View File

@ -67,7 +67,11 @@ import {
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { guardOperationException } from "../errors.js"; import { guardOperationException } from "../errors.js";
import { updateExchangeFromUrl } from "./exchanges.js"; import { updateExchangeFromUrl } from "./exchanges.js";
import { EXCHANGE_COINS_LOCK, InternalWalletState } from "../common.js"; import {
DenomInfo,
EXCHANGE_COINS_LOCK,
InternalWalletState,
} from "../common.js";
import { import {
isWithdrawableDenom, isWithdrawableDenom,
selectWithdrawalDenominations, selectWithdrawalDenominations,
@ -90,7 +94,7 @@ const logger = new Logger("refresh.ts");
*/ */
export function getTotalRefreshCost( export function getTotalRefreshCost(
denoms: DenominationRecord[], denoms: DenominationRecord[],
refreshedDenom: DenominationRecord, refreshedDenom: DenomInfo,
amountLeft: AmountJson, amountLeft: AmountJson,
): AmountJson { ): AmountJson {
const withdrawAmount = Amounts.sub( const withdrawAmount = Amounts.sub(
@ -193,10 +197,12 @@ async function refreshCreateSession(
denominations: x.denominations, denominations: x.denominations,
})) }))
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
const oldDenom = await tx.denominations.get([ const oldDenom = await ws.getDenomInfo(
ws,
tx,
exchange.baseUrl, exchange.baseUrl,
coin.denomPubHash, coin.denomPubHash,
]); );
if (!oldDenom) { if (!oldDenom) {
throw Error("db inconsistent: denomination for coin not found"); throw Error("db inconsistent: denomination for coin not found");
@ -321,10 +327,12 @@ async function refreshMelt(
const oldCoin = await tx.coins.get(refreshGroup.oldCoinPubs[coinIndex]); const oldCoin = await tx.coins.get(refreshGroup.oldCoinPubs[coinIndex]);
checkDbInvariant(!!oldCoin, "melt coin doesn't exist"); checkDbInvariant(!!oldCoin, "melt coin doesn't exist");
const oldDenom = await tx.denominations.get([ const oldDenom = await ws.getDenomInfo(
ws,
tx,
oldCoin.exchangeBaseUrl, oldCoin.exchangeBaseUrl,
oldCoin.denomPubHash, oldCoin.denomPubHash,
]); );
checkDbInvariant( checkDbInvariant(
!!oldDenom, !!oldDenom,
"denomination for melted coin doesn't exist", "denomination for melted coin doesn't exist",
@ -333,10 +341,12 @@ async function refreshMelt(
const newCoinDenoms: RefreshNewDenomInfo[] = []; const newCoinDenoms: RefreshNewDenomInfo[] = [];
for (const dh of refreshSession.newDenoms) { for (const dh of refreshSession.newDenoms) {
const newDenom = await tx.denominations.get([ const newDenom = await ws.getDenomInfo(
ws,
tx,
oldCoin.exchangeBaseUrl, oldCoin.exchangeBaseUrl,
dh.denomPubHash, dh.denomPubHash,
]); );
checkDbInvariant( checkDbInvariant(
!!newDenom, !!newDenom,
"new denomination for refresh not in database", "new denomination for refresh not in database",
@ -480,6 +490,7 @@ async function refreshReveal(
refreshGroupId: string, refreshGroupId: string,
coinIndex: number, coinIndex: number,
): Promise<void> { ): Promise<void> {
logger.info("doing refresh reveal");
const d = await ws.db const d = await ws.db
.mktx((x) => ({ .mktx((x) => ({
refreshGroups: x.refreshGroups, refreshGroups: x.refreshGroups,
@ -502,10 +513,12 @@ async function refreshReveal(
const oldCoin = await tx.coins.get(refreshGroup.oldCoinPubs[coinIndex]); const oldCoin = await tx.coins.get(refreshGroup.oldCoinPubs[coinIndex]);
checkDbInvariant(!!oldCoin, "melt coin doesn't exist"); checkDbInvariant(!!oldCoin, "melt coin doesn't exist");
const oldDenom = await tx.denominations.get([ const oldDenom = await ws.getDenomInfo(
ws,
tx,
oldCoin.exchangeBaseUrl, oldCoin.exchangeBaseUrl,
oldCoin.denomPubHash, oldCoin.denomPubHash,
]); );
checkDbInvariant( checkDbInvariant(
!!oldDenom, !!oldDenom,
"denomination for melted coin doesn't exist", "denomination for melted coin doesn't exist",
@ -514,10 +527,12 @@ async function refreshReveal(
const newCoinDenoms: RefreshNewDenomInfo[] = []; const newCoinDenoms: RefreshNewDenomInfo[] = [];
for (const dh of refreshSession.newDenoms) { for (const dh of refreshSession.newDenoms) {
const newDenom = await tx.denominations.get([ const newDenom = await ws.getDenomInfo(
ws,
tx,
oldCoin.exchangeBaseUrl, oldCoin.exchangeBaseUrl,
dh.denomPubHash, dh.denomPubHash,
]); );
checkDbInvariant( checkDbInvariant(
!!newDenom, !!newDenom,
"new denomination for refresh not in database", "new denomination for refresh not in database",
@ -794,6 +809,7 @@ async function processRefreshGroupImpl(
refreshGroupId: string, refreshGroupId: string,
forceNow: boolean, forceNow: boolean,
): Promise<void> { ): Promise<void> {
logger.info(`processing refresh group ${refreshGroupId}`);
if (forceNow) { if (forceNow) {
await resetRefreshGroupRetry(ws, refreshGroupId); await resetRefreshGroupRetry(ws, refreshGroupId);
} }
@ -823,7 +839,7 @@ async function processRefreshSession(
refreshGroupId: string, refreshGroupId: string,
coinIndex: number, coinIndex: number,
): Promise<void> { ): Promise<void> {
logger.trace( logger.info(
`processing refresh session for coin ${coinIndex} of group ${refreshGroupId}`, `processing refresh session for coin ${coinIndex} of group ${refreshGroupId}`,
); );
let refreshGroup = await ws.db let refreshGroup = await ws.db
@ -911,10 +927,12 @@ export async function createRefreshGroup(
for (const ocp of oldCoinPubs) { for (const ocp of oldCoinPubs) {
const coin = await tx.coins.get(ocp.coinPub); const coin = await tx.coins.get(ocp.coinPub);
checkDbInvariant(!!coin, "coin must be in database"); checkDbInvariant(!!coin, "coin must be in database");
const denom = await tx.denominations.get([ const denom = await ws.getDenomInfo(
ws,
tx,
coin.exchangeBaseUrl, coin.exchangeBaseUrl,
coin.denomPubHash, coin.denomPubHash,
]); );
checkDbInvariant( checkDbInvariant(
!!denom, !!denom,
"denomination for existing coin must be in database", "denomination for existing coin must be in database",
@ -949,11 +967,12 @@ export async function createRefreshGroup(
if (oldCoinPubs.length == 0) { if (oldCoinPubs.length == 0) {
logger.warn("created refresh group with zero coins"); logger.warn("created refresh group with zero coins");
refreshGroup.timestampFinished = getTimestampNow(); refreshGroup.timestampFinished = getTimestampNow();
refreshGroup.operationStatus = OperationStatus.Finished;
} }
await tx.refreshGroups.put(refreshGroup); await tx.refreshGroups.put(refreshGroup);
logger.trace(`created refresh group ${refreshGroupId}`); logger.info(`created refresh group ${refreshGroupId}`);
processRefreshGroup(ws, refreshGroupId).catch((e) => { processRefreshGroup(ws, refreshGroupId).catch((e) => {
logger.warn(`processing refresh group ${refreshGroupId} failed: ${e}`); logger.warn(`processing refresh group ${refreshGroupId} failed: ${e}`);

View File

@ -453,6 +453,9 @@ async function processPlanchetExchangeRequest(
withdrawalGroup: WithdrawalGroupRecord, withdrawalGroup: WithdrawalGroupRecord,
coinIdx: number, coinIdx: number,
): Promise<WithdrawResponse | undefined> { ): Promise<WithdrawResponse | undefined> {
logger.info(
`processing planchet exchange request ${withdrawalGroup.withdrawalGroupId}/${coinIdx}`,
);
const d = await ws.db const d = await ws.db
.mktx((x) => ({ .mktx((x) => ({
withdrawalGroups: x.withdrawalGroups, withdrawalGroups: x.withdrawalGroups,
@ -478,10 +481,12 @@ async function processPlanchetExchangeRequest(
return; return;
} }
const denom = await tx.denominations.get([ const denom = await ws.getDenomInfo(
ws,
tx,
withdrawalGroup.exchangeBaseUrl, withdrawalGroup.exchangeBaseUrl,
planchet.denomPubHash, planchet.denomPubHash,
]); );
if (!denom) { if (!denom) {
logger.error("db inconsistent: denom for planchet not found"); logger.error("db inconsistent: denom for planchet not found");

View File

@ -333,7 +333,7 @@ export interface StoreReadWriteAccessor<RecordType, IndexMap> {
export interface StoreWithIndexes< export interface StoreWithIndexes<
SD extends StoreDescriptor<unknown>, SD extends StoreDescriptor<unknown>,
IndexMap IndexMap,
> { > {
store: SD; store: SD;
indexMap: IndexMap; indexMap: IndexMap;
@ -586,7 +586,7 @@ export class DbAccess<StoreMap> {
mktx< mktx<
PickerType extends (x: StoreMap) => unknown, PickerType extends (x: StoreMap) => unknown,
BoundStores extends GetPickerType<PickerType, StoreMap> BoundStores extends GetPickerType<PickerType, StoreMap>,
>(f: PickerType): TransactionContext<BoundStores> { >(f: PickerType): TransactionContext<BoundStores> {
const storePick = f(this.stores) as any; const storePick = f(this.stores) as any;
if (typeof storePick !== "object" || storePick === null) { if (typeof storePick !== "object" || storePick === null) {

View File

@ -626,6 +626,7 @@ async function setCoinSuspended(
*/ */
async function dumpCoins(ws: InternalWalletState): Promise<CoinDumpJson> { async function dumpCoins(ws: InternalWalletState): Promise<CoinDumpJson> {
const coinsJson: CoinDumpJson = { coins: [] }; const coinsJson: CoinDumpJson = { coins: [] };
logger.info("dumping coins");
await ws.db await ws.db
.mktx((x) => ({ .mktx((x) => ({
coins: x.coins, coins: x.coins,
@ -1206,9 +1207,15 @@ class InternalWalletStateImpl implements InternalWalletState {
const key = `${exchangeBaseUrl}:${denomPubHash}`; const key = `${exchangeBaseUrl}:${denomPubHash}`;
const cached = this.denomCache[key]; const cached = this.denomCache[key];
if (cached) { if (cached) {
logger.info("using cached denom");
return cached; return cached;
} }
return await tx.denominations.get([exchangeBaseUrl, denomPubHash]); logger.info("looking up denom denom");
const d = await tx.denominations.get([exchangeBaseUrl, denomPubHash]);
if (d) {
this.denomCache[key] = d;
}
return d;
} }
notify(n: WalletNotification): void { notify(n: WalletNotification): void {