do withdrawal with fewer DB accesses

This commit is contained in:
Florian Dold 2022-01-12 16:54:38 +01:00
parent dd66e43b3c
commit 9f6e398884
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
4 changed files with 126 additions and 125 deletions

View File

@ -158,7 +158,7 @@ export interface ReserveRecord {
* *
* Only applies if bankWithdrawStatusUrl is defined. * Only applies if bankWithdrawStatusUrl is defined.
* *
* Set to 0 if that hasn't happened yet. * Set to undefined if that hasn't happened yet.
*/ */
timestampReserveInfoPosted: Timestamp | undefined; timestampReserveInfoPosted: Timestamp | undefined;
@ -210,7 +210,7 @@ export interface ReserveRecord {
/** /**
* Is there any work to be done for this reserve? * Is there any work to be done for this reserve?
* *
* FIXME: Technically redundant, since the reserveStatus would indicate this. * FIXME: Technically redundant, since the reserveStatus would indicate this.
*/ */
operationStatus: OperationStatus; operationStatus: OperationStatus;
@ -1341,6 +1341,9 @@ export interface DenomSelectionState {
* the coin selection we want to withdraw. * the coin selection we want to withdraw.
*/ */
export interface WithdrawalGroupRecord { export interface WithdrawalGroupRecord {
/**
* Unique identifier for the withdrawal group.
*/
withdrawalGroupId: string; withdrawalGroupId: string;
/** /**
@ -1348,8 +1351,15 @@ export interface WithdrawalGroupRecord {
*/ */
secretSeed: string; secretSeed: string;
/**
* Public key of the reserve that we're withdrawing from.
*/
reservePub: string; reservePub: string;
/**
* The exchange base URL that we're withdrawing from.
* (Redundantly stored, as the reserve record also has this info.)
*/
exchangeBaseUrl: string; exchangeBaseUrl: string;
/** /**
@ -1363,6 +1373,10 @@ export interface WithdrawalGroupRecord {
*/ */
timestampFinish?: Timestamp; timestampFinish?: Timestamp;
/**
* Operation status of the withdrawal group.
* Used for indexing in the database.
*/
operationStatus: OperationStatus; operationStatus: OperationStatus;
/** /**
@ -1371,8 +1385,18 @@ export interface WithdrawalGroupRecord {
*/ */
rawWithdrawalAmount: AmountJson; rawWithdrawalAmount: AmountJson;
/**
* Denominations selected for withdrawal.
*/
denomsSel: DenomSelectionState; denomsSel: DenomSelectionState;
/**
* UID of the denomination selection.
*
* Used for merging backups.
*
* FIXME: Should this not also include a timestamp for more logical merging?
*/
denomSelUid: string; denomSelUid: string;
/** /**

View File

@ -37,13 +37,9 @@ import {
} from "../pending-types.js"; } from "../pending-types.js";
import { import {
getTimestampNow, getTimestampNow,
isTimestampExpired,
j2s,
Logger,
Timestamp, Timestamp,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { InternalWalletState } from "../common.js"; import { InternalWalletState } from "../common.js";
import { getBalancesInsideTransaction } from "./balance.js";
import { GetReadOnlyAccess } from "../util/query.js"; import { GetReadOnlyAccess } from "../util/query.js";
async function gatherExchangePending( async function gatherExchangePending(
@ -353,9 +349,7 @@ export async function getPendingOperations(
recoupGroups: x.recoupGroups, recoupGroups: x.recoupGroups,
})) }))
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
const walletBalance = await getBalancesInsideTransaction(ws, tx);
const resp: PendingOperationsResponse = { const resp: PendingOperationsResponse = {
walletBalance,
pendingOperations: [], pendingOperations: [],
}; };
await gatherExchangePending(tx, now, resp); await gatherExchangePending(tx, now, resp);

View File

@ -55,6 +55,7 @@ import {
ExchangeRecord, ExchangeRecord,
OperationStatus, OperationStatus,
PlanchetRecord, PlanchetRecord,
WithdrawalGroupRecord,
} from "../db.js"; } from "../db.js";
import { walletCoreDebugFlags } from "../util/debugFlags.js"; import { walletCoreDebugFlags } from "../util/debugFlags.js";
import { readSuccessResponseJsonOrThrow } from "../util/http.js"; import { readSuccessResponseJsonOrThrow } from "../util/http.js";
@ -73,7 +74,7 @@ import {
/** /**
* Logger for this file. * Logger for this file.
*/ */
const logger = new Logger("withdraw.ts"); const logger = new Logger("operations/withdraw.ts");
/** /**
* FIXME: Eliminate this in favor of DenomSelectionState. * FIXME: Eliminate this in favor of DenomSelectionState.
@ -351,106 +352,95 @@ export async function getCandidateWithdrawalDenoms(
*/ */
async function processPlanchetGenerate( async function processPlanchetGenerate(
ws: InternalWalletState, ws: InternalWalletState,
withdrawalGroupId: string, withdrawalGroup: WithdrawalGroupRecord,
coinIdx: number, coinIdx: number,
): Promise<void> { ): Promise<void> {
const withdrawalGroup = await ws.db
.mktx((x) => ({ withdrawalGroups: x.withdrawalGroups }))
.runReadOnly(async (tx) => {
return await tx.withdrawalGroups.get(withdrawalGroupId);
});
if (!withdrawalGroup) {
return;
}
let planchet = await ws.db let planchet = await ws.db
.mktx((x) => ({ .mktx((x) => ({
planchets: x.planchets, planchets: x.planchets,
})) }))
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
return tx.planchets.indexes.byGroupAndIndex.get([ return tx.planchets.indexes.byGroupAndIndex.get([
withdrawalGroupId, withdrawalGroup.withdrawalGroupId,
coinIdx, coinIdx,
]); ]);
}); });
if (!planchet) { if (planchet) {
let ci = 0; return;
let denomPubHash: string | undefined;
for (
let di = 0;
di < withdrawalGroup.denomsSel.selectedDenoms.length;
di++
) {
const d = withdrawalGroup.denomsSel.selectedDenoms[di];
if (coinIdx >= ci && coinIdx < ci + d.count) {
denomPubHash = d.denomPubHash;
break;
}
ci += d.count;
}
if (!denomPubHash) {
throw Error("invariant violated");
}
const { denom, reserve } = await ws.db
.mktx((x) => ({
reserves: x.reserves,
denominations: x.denominations,
}))
.runReadOnly(async (tx) => {
const denom = await tx.denominations.get([
withdrawalGroup.exchangeBaseUrl,
denomPubHash!,
]);
if (!denom) {
throw Error("invariant violated");
}
const reserve = await tx.reserves.get(withdrawalGroup.reservePub);
if (!reserve) {
throw Error("invariant violated");
}
return { denom, reserve };
});
const r = await ws.cryptoApi.createPlanchet({
denomPub: denom.denomPub,
feeWithdraw: denom.feeWithdraw,
reservePriv: reserve.reservePriv,
reservePub: reserve.reservePub,
value: denom.value,
coinIndex: coinIdx,
secretSeed: withdrawalGroup.secretSeed,
});
const newPlanchet: PlanchetRecord = {
blindingKey: r.blindingKey,
coinEv: r.coinEv,
coinEvHash: r.coinEvHash,
coinIdx,
coinPriv: r.coinPriv,
coinPub: r.coinPub,
coinValue: r.coinValue,
denomPub: r.denomPub,
denomPubHash: r.denomPubHash,
isFromTip: false,
reservePub: r.reservePub,
withdrawalDone: false,
withdrawSig: r.withdrawSig,
withdrawalGroupId: withdrawalGroupId,
lastError: undefined,
};
await ws.db
.mktx((x) => ({ planchets: x.planchets }))
.runReadWrite(async (tx) => {
const p = await tx.planchets.indexes.byGroupAndIndex.get([
withdrawalGroupId,
coinIdx,
]);
if (p) {
planchet = p;
return;
}
await tx.planchets.put(newPlanchet);
planchet = newPlanchet;
});
} }
let ci = 0;
let denomPubHash: string | undefined;
for (let di = 0; di < withdrawalGroup.denomsSel.selectedDenoms.length; di++) {
const d = withdrawalGroup.denomsSel.selectedDenoms[di];
if (coinIdx >= ci && coinIdx < ci + d.count) {
denomPubHash = d.denomPubHash;
break;
}
ci += d.count;
}
if (!denomPubHash) {
throw Error("invariant violated");
}
const { denom, reserve } = await ws.db
.mktx((x) => ({
reserves: x.reserves,
denominations: x.denominations,
}))
.runReadOnly(async (tx) => {
const denom = await tx.denominations.get([
withdrawalGroup.exchangeBaseUrl,
denomPubHash!,
]);
if (!denom) {
throw Error("invariant violated");
}
const reserve = await tx.reserves.get(withdrawalGroup.reservePub);
if (!reserve) {
throw Error("invariant violated");
}
return { denom, reserve };
});
const r = await ws.cryptoApi.createPlanchet({
denomPub: denom.denomPub,
feeWithdraw: denom.feeWithdraw,
reservePriv: reserve.reservePriv,
reservePub: reserve.reservePub,
value: denom.value,
coinIndex: coinIdx,
secretSeed: withdrawalGroup.secretSeed,
});
const newPlanchet: PlanchetRecord = {
blindingKey: r.blindingKey,
coinEv: r.coinEv,
coinEvHash: r.coinEvHash,
coinIdx,
coinPriv: r.coinPriv,
coinPub: r.coinPub,
coinValue: r.coinValue,
denomPub: r.denomPub,
denomPubHash: r.denomPubHash,
isFromTip: false,
reservePub: r.reservePub,
withdrawalDone: false,
withdrawSig: r.withdrawSig,
withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
lastError: undefined,
};
await ws.db
.mktx((x) => ({ planchets: x.planchets }))
.runReadWrite(async (tx) => {
const p = await tx.planchets.indexes.byGroupAndIndex.get([
withdrawalGroup.withdrawalGroupId,
coinIdx,
]);
if (p) {
planchet = p;
return;
}
await tx.planchets.put(newPlanchet);
planchet = newPlanchet;
});
} }
/** /**
@ -460,7 +450,7 @@ async function processPlanchetGenerate(
*/ */
async function processPlanchetExchangeRequest( async function processPlanchetExchangeRequest(
ws: InternalWalletState, ws: InternalWalletState,
withdrawalGroupId: string, withdrawalGroup: WithdrawalGroupRecord,
coinIdx: number, coinIdx: number,
): Promise<WithdrawResponse | undefined> { ): Promise<WithdrawResponse | undefined> {
const d = await ws.db const d = await ws.db
@ -471,12 +461,8 @@ async function processPlanchetExchangeRequest(
denominations: x.denominations, denominations: x.denominations,
})) }))
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
const withdrawalGroup = await tx.withdrawalGroups.get(withdrawalGroupId);
if (!withdrawalGroup) {
return;
}
let planchet = await tx.planchets.indexes.byGroupAndIndex.get([ let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
withdrawalGroupId, withdrawalGroup.withdrawalGroupId,
coinIdx, coinIdx,
]); ]);
if (!planchet) { if (!planchet) {
@ -503,7 +489,7 @@ async function processPlanchetExchangeRequest(
} }
logger.trace( logger.trace(
`processing planchet #${coinIdx} in withdrawal ${withdrawalGroupId}`, `processing planchet #${coinIdx} in withdrawal ${withdrawalGroup.withdrawalGroupId}`,
); );
const reqBody: any = { const reqBody: any = {
@ -543,7 +529,7 @@ async function processPlanchetExchangeRequest(
.mktx((x) => ({ planchets: x.planchets })) .mktx((x) => ({ planchets: x.planchets }))
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
let planchet = await tx.planchets.indexes.byGroupAndIndex.get([ let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
withdrawalGroupId, withdrawalGroup.withdrawalGroupId,
coinIdx, coinIdx,
]); ]);
if (!planchet) { if (!planchet) {
@ -558,7 +544,7 @@ async function processPlanchetExchangeRequest(
async function processPlanchetVerifyAndStoreCoin( async function processPlanchetVerifyAndStoreCoin(
ws: InternalWalletState, ws: InternalWalletState,
withdrawalGroupId: string, withdrawalGroup: WithdrawalGroupRecord,
coinIdx: number, coinIdx: number,
resp: WithdrawResponse, resp: WithdrawResponse,
): Promise<void> { ): Promise<void> {
@ -568,12 +554,8 @@ async function processPlanchetVerifyAndStoreCoin(
planchets: x.planchets, planchets: x.planchets,
})) }))
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
const withdrawalGroup = await tx.withdrawalGroups.get(withdrawalGroupId);
if (!withdrawalGroup) {
return;
}
let planchet = await tx.planchets.indexes.byGroupAndIndex.get([ let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
withdrawalGroupId, withdrawalGroup.withdrawalGroupId,
coinIdx, coinIdx,
]); ]);
if (!planchet) { if (!planchet) {
@ -635,7 +617,7 @@ async function processPlanchetVerifyAndStoreCoin(
.mktx((x) => ({ planchets: x.planchets })) .mktx((x) => ({ planchets: x.planchets }))
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
let planchet = await tx.planchets.indexes.byGroupAndIndex.get([ let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
withdrawalGroupId, withdrawalGroup.withdrawalGroupId,
coinIdx, coinIdx,
]); ]);
if (!planchet) { if (!planchet) {
@ -679,7 +661,7 @@ async function processPlanchetVerifyAndStoreCoin(
type: CoinSourceType.Withdraw, type: CoinSourceType.Withdraw,
coinIndex: coinIdx, coinIndex: coinIdx,
reservePub: planchet.reservePub, reservePub: planchet.reservePub,
withdrawalGroupId: withdrawalGroupId, withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
}, },
suspended: false, suspended: false,
}; };
@ -694,10 +676,6 @@ async function processPlanchetVerifyAndStoreCoin(
planchets: x.planchets, planchets: x.planchets,
})) }))
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
const ws = await tx.withdrawalGroups.get(withdrawalGroupId);
if (!ws) {
return false;
}
const p = await tx.planchets.get(planchetCoinPub); const p = await tx.planchets.get(planchetCoinPub);
if (!p || p.withdrawalDone) { if (!p || p.withdrawalDone) {
return false; return false;
@ -914,7 +892,7 @@ async function processWithdrawGroupImpl(
let work: Promise<void>[] = []; let work: Promise<void>[] = [];
for (let i = 0; i < numTotalCoins; i++) { for (let i = 0; i < numTotalCoins; i++) {
work.push(processPlanchetGenerate(ws, withdrawalGroupId, i)); work.push(processPlanchetGenerate(ws, withdrawalGroup, i));
} }
// Generate coins concurrently (parallelism only happens in the crypto API workers) // Generate coins concurrently (parallelism only happens in the crypto API workers)
@ -925,14 +903,14 @@ async function processWithdrawGroupImpl(
for (let coinIdx = 0; coinIdx < numTotalCoins; coinIdx++) { for (let coinIdx = 0; coinIdx < numTotalCoins; coinIdx++) {
const resp = await processPlanchetExchangeRequest( const resp = await processPlanchetExchangeRequest(
ws, ws,
withdrawalGroupId, withdrawalGroup,
coinIdx, coinIdx,
); );
if (!resp) { if (!resp) {
continue; continue;
} }
work.push( work.push(
processPlanchetVerifyAndStoreCoin(ws, withdrawalGroupId, coinIdx, resp), processPlanchetVerifyAndStoreCoin(ws, withdrawalGroup, coinIdx, resp),
); );
} }
@ -1089,6 +1067,13 @@ export async function getExchangeWithdrawalInfo(
return ret; return ret;
} }
/**
* Get more information about a taler://withdraw URI.
*
* As side effects, the bank (via the bank integration API) is queried
* and the exchange suggested by the bank is permanently added
* to the wallet's list of known exchanges.
*/
export async function getWithdrawalDetailsForUri( export async function getWithdrawalDetailsForUri(
ws: InternalWalletState, ws: InternalWalletState,
talerWithdrawUri: string, talerWithdrawUri: string,
@ -1110,6 +1095,9 @@ export async function getWithdrawalDetailsForUri(
} }
} }
// Extract information about possible exchanges for the withdrawal
// operation from the database.
const exchanges: ExchangeListItem[] = []; const exchanges: ExchangeListItem[] = [];
await ws.db await ws.db

View File

@ -248,9 +248,4 @@ export interface PendingOperationsResponse {
* List of pending operations. * List of pending operations.
*/ */
pendingOperations: PendingTaskInfo[]; pendingOperations: PendingTaskInfo[];
/**
* Current wallet balance, including pending balances.
*/
walletBalance: BalancesResponse;
} }