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.
*
* Set to 0 if that hasn't happened yet.
* Set to undefined if that hasn't happened yet.
*/
timestampReserveInfoPosted: Timestamp | undefined;
@ -1341,6 +1341,9 @@ export interface DenomSelectionState {
* the coin selection we want to withdraw.
*/
export interface WithdrawalGroupRecord {
/**
* Unique identifier for the withdrawal group.
*/
withdrawalGroupId: string;
/**
@ -1348,8 +1351,15 @@ export interface WithdrawalGroupRecord {
*/
secretSeed: string;
/**
* Public key of the reserve that we're withdrawing from.
*/
reservePub: string;
/**
* The exchange base URL that we're withdrawing from.
* (Redundantly stored, as the reserve record also has this info.)
*/
exchangeBaseUrl: string;
/**
@ -1363,6 +1373,10 @@ export interface WithdrawalGroupRecord {
*/
timestampFinish?: Timestamp;
/**
* Operation status of the withdrawal group.
* Used for indexing in the database.
*/
operationStatus: OperationStatus;
/**
@ -1371,8 +1385,18 @@ export interface WithdrawalGroupRecord {
*/
rawWithdrawalAmount: AmountJson;
/**
* Denominations selected for withdrawal.
*/
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;
/**

View File

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

View File

@ -55,6 +55,7 @@ import {
ExchangeRecord,
OperationStatus,
PlanchetRecord,
WithdrawalGroupRecord,
} from "../db.js";
import { walletCoreDebugFlags } from "../util/debugFlags.js";
import { readSuccessResponseJsonOrThrow } from "../util/http.js";
@ -73,7 +74,7 @@ import {
/**
* Logger for this file.
*/
const logger = new Logger("withdraw.ts");
const logger = new Logger("operations/withdraw.ts");
/**
* FIXME: Eliminate this in favor of DenomSelectionState.
@ -351,106 +352,95 @@ export async function getCandidateWithdrawalDenoms(
*/
async function processPlanchetGenerate(
ws: InternalWalletState,
withdrawalGroupId: string,
withdrawalGroup: WithdrawalGroupRecord,
coinIdx: number,
): 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
.mktx((x) => ({
planchets: x.planchets,
}))
.runReadOnly(async (tx) => {
return tx.planchets.indexes.byGroupAndIndex.get([
withdrawalGroupId,
withdrawalGroup.withdrawalGroupId,
coinIdx,
]);
});
if (!planchet) {
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: 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;
});
if (planchet) {
return;
}
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(
ws: InternalWalletState,
withdrawalGroupId: string,
withdrawalGroup: WithdrawalGroupRecord,
coinIdx: number,
): Promise<WithdrawResponse | undefined> {
const d = await ws.db
@ -471,12 +461,8 @@ async function processPlanchetExchangeRequest(
denominations: x.denominations,
}))
.runReadOnly(async (tx) => {
const withdrawalGroup = await tx.withdrawalGroups.get(withdrawalGroupId);
if (!withdrawalGroup) {
return;
}
let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
withdrawalGroupId,
withdrawalGroup.withdrawalGroupId,
coinIdx,
]);
if (!planchet) {
@ -503,7 +489,7 @@ async function processPlanchetExchangeRequest(
}
logger.trace(
`processing planchet #${coinIdx} in withdrawal ${withdrawalGroupId}`,
`processing planchet #${coinIdx} in withdrawal ${withdrawalGroup.withdrawalGroupId}`,
);
const reqBody: any = {
@ -543,7 +529,7 @@ async function processPlanchetExchangeRequest(
.mktx((x) => ({ planchets: x.planchets }))
.runReadWrite(async (tx) => {
let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
withdrawalGroupId,
withdrawalGroup.withdrawalGroupId,
coinIdx,
]);
if (!planchet) {
@ -558,7 +544,7 @@ async function processPlanchetExchangeRequest(
async function processPlanchetVerifyAndStoreCoin(
ws: InternalWalletState,
withdrawalGroupId: string,
withdrawalGroup: WithdrawalGroupRecord,
coinIdx: number,
resp: WithdrawResponse,
): Promise<void> {
@ -568,12 +554,8 @@ async function processPlanchetVerifyAndStoreCoin(
planchets: x.planchets,
}))
.runReadOnly(async (tx) => {
const withdrawalGroup = await tx.withdrawalGroups.get(withdrawalGroupId);
if (!withdrawalGroup) {
return;
}
let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
withdrawalGroupId,
withdrawalGroup.withdrawalGroupId,
coinIdx,
]);
if (!planchet) {
@ -635,7 +617,7 @@ async function processPlanchetVerifyAndStoreCoin(
.mktx((x) => ({ planchets: x.planchets }))
.runReadWrite(async (tx) => {
let planchet = await tx.planchets.indexes.byGroupAndIndex.get([
withdrawalGroupId,
withdrawalGroup.withdrawalGroupId,
coinIdx,
]);
if (!planchet) {
@ -679,7 +661,7 @@ async function processPlanchetVerifyAndStoreCoin(
type: CoinSourceType.Withdraw,
coinIndex: coinIdx,
reservePub: planchet.reservePub,
withdrawalGroupId: withdrawalGroupId,
withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
},
suspended: false,
};
@ -694,10 +676,6 @@ async function processPlanchetVerifyAndStoreCoin(
planchets: x.planchets,
}))
.runReadWrite(async (tx) => {
const ws = await tx.withdrawalGroups.get(withdrawalGroupId);
if (!ws) {
return false;
}
const p = await tx.planchets.get(planchetCoinPub);
if (!p || p.withdrawalDone) {
return false;
@ -914,7 +892,7 @@ async function processWithdrawGroupImpl(
let work: Promise<void>[] = [];
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)
@ -925,14 +903,14 @@ async function processWithdrawGroupImpl(
for (let coinIdx = 0; coinIdx < numTotalCoins; coinIdx++) {
const resp = await processPlanchetExchangeRequest(
ws,
withdrawalGroupId,
withdrawalGroup,
coinIdx,
);
if (!resp) {
continue;
}
work.push(
processPlanchetVerifyAndStoreCoin(ws, withdrawalGroupId, coinIdx, resp),
processPlanchetVerifyAndStoreCoin(ws, withdrawalGroup, coinIdx, resp),
);
}
@ -1089,6 +1067,13 @@ export async function getExchangeWithdrawalInfo(
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(
ws: InternalWalletState,
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[] = [];
await ws.db

View File

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