wallet-core: DB tweaks

This commit is contained in:
Florian Dold 2022-10-14 21:00:13 +02:00
parent 398e79d0d6
commit f1cba79c65
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
8 changed files with 74 additions and 47 deletions

View File

@ -671,6 +671,7 @@ export interface ExchangeAccount {
} }
export type WireFeeMap = { [wireMethod: string]: WireFee[] }; export type WireFeeMap = { [wireMethod: string]: WireFee[] };
export interface WireInfo { export interface WireInfo {
feesForType: WireFeeMap; feesForType: WireFeeMap;
accounts: ExchangeAccount[]; accounts: ExchangeAccount[];
@ -942,7 +943,7 @@ export interface ManualWithdrawalDetails {
/** /**
* Selected denominations withn some extra info. * Selected denominations withn some extra info.
*/ */
export interface DenomSelectionState { export interface DenomSelectionState {
totalCoinValue: AmountJson; totalCoinValue: AmountJson;
totalWithdrawCost: AmountJson; totalWithdrawCost: AmountJson;
selectedDenoms: { selectedDenoms: {
@ -956,7 +957,7 @@ export interface ManualWithdrawalDetails {
* *
* Sent to the wallet frontend to be rendered and shown to the user. * Sent to the wallet frontend to be rendered and shown to the user.
*/ */
export interface ExchangeWithdrawalDetails { export interface ExchangeWithdrawalDetails {
exchangePaytoUris: string[]; exchangePaytoUris: string[];
/** /**

View File

@ -101,11 +101,21 @@ export const CURRENT_DB_CONFIG_KEY = "currentMainDbName";
*/ */
export const WALLET_DB_MINOR_VERSION = 2; export const WALLET_DB_MINOR_VERSION = 2;
/**
* Ranges for operation status fields.
*
* All individual enums should make sure that the values they
* defined are in the right range.
*/
export enum OperationStatusRange { export enum OperationStatusRange {
// Operations that need to be actively processed.
ACTIVE_START = 10, ACTIVE_START = 10,
ACTIVE_END = 29, ACTIVE_END = 29,
// Operations that need user input, but nothing can be done
// automatically.
USER_ATTENTION_START = 30, USER_ATTENTION_START = 30,
USER_ATTENTION_END = 49, USER_ATTENTION_END = 49,
// Operations that don't need any attention or processing.
DORMANT_START = 50, DORMANT_START = 50,
DORMANT_END = 69, DORMANT_END = 69,
} }
@ -150,7 +160,7 @@ export enum WithdrawalGroupStatus {
} }
/** /**
* Extra info about a reserve that is used * Extra info about a withdrawal that is used
* with a bank-integrated withdrawal. * with a bank-integrated withdrawal.
*/ */
export interface ReserveBankInfo { export interface ReserveBankInfo {
@ -397,13 +407,9 @@ export namespace DenominationRecord {
} }
/** /**
* Information about one of the exchange's bank accounts. * Exchange details for a particular
* (exchangeBaseUrl, masterPublicKey, currency) tuple.
*/ */
export interface ExchangeBankAccount {
payto_uri: string;
master_sig: string;
}
export interface ExchangeDetailsRecord { export interface ExchangeDetailsRecord {
/** /**
* Master public key of the exchange. * Master public key of the exchange.
@ -425,7 +431,7 @@ export interface ExchangeDetailsRecord {
/** /**
* Last observed protocol version. * Last observed protocol version.
*/ */
protocolVersion: string; protocolVersionRange: string;
reserveClosingDelay: TalerProtocolDuration; reserveClosingDelay: TalerProtocolDuration;
@ -513,6 +519,10 @@ export interface ExchangeRecord {
/** /**
* Pointer to the current exchange details. * Pointer to the current exchange details.
*
* Should usually not change. Only changes when the
* exchange advertises a different master public key and/or
* currency.
*/ */
detailsPointer: ExchangeDetailsPointer | undefined; detailsPointer: ExchangeDetailsPointer | undefined;
@ -554,6 +564,11 @@ export interface ExchangeRecord {
currentMergeReserveInfo?: MergeReserveInfo; currentMergeReserveInfo?: MergeReserveInfo;
} }
export enum PlanchetStatus {
Pending = 10 /* ACTIVE_START */,
WithdrawalDone = 50 /* DORMANT_START */,
}
/** /**
* A coin that isn't yet signed by an exchange. * A coin that isn't yet signed by an exchange.
*/ */
@ -579,10 +594,7 @@ export interface PlanchetRecord {
*/ */
coinIdx: number; coinIdx: number;
/** planchetStatus: PlanchetStatus;
* FIXME: make this an enum!
*/
withdrawalDone: boolean;
lastError: TalerErrorDetail | undefined; lastError: TalerErrorDetail | undefined;
@ -743,12 +755,21 @@ export interface CoinRecord {
*/ */
allocation: CoinAllocation | undefined; allocation: CoinAllocation | undefined;
/**
* Maximum age of purchases that can be made with this coin.
*
* FIXME: Not used for indexing, isn't it redundant?
*/
maxAge: number; maxAge: number;
ageCommitmentProof: AgeCommitmentProof | undefined; ageCommitmentProof: AgeCommitmentProof | undefined;
} }
/**
* Coin allocation, i.e. what a coin has been used for.
*/
export interface CoinAllocation { export interface CoinAllocation {
// FIXME: Specify format!
id: string; id: string;
amount: AmountString; amount: AmountString;
} }
@ -839,8 +860,14 @@ export enum OperationStatus {
Pending = OperationStatusRange.ACTIVE_START, Pending = OperationStatusRange.ACTIVE_START,
} }
export enum RefreshOperationStatus {
Pending = 10 /* ACTIVE_START */,
Finished = 50 /* DORMANT_START */,
FinishedWithError = 51 /* DORMANT_START + 1 */,
}
export interface RefreshGroupRecord { export interface RefreshGroupRecord {
operationStatus: OperationStatus; operationStatus: RefreshOperationStatus;
// FIXME: Put this into a different object store? // FIXME: Put this into a different object store?
lastErrorPerCoin: { [coinIndex: number]: TalerErrorDetail }; lastErrorPerCoin: { [coinIndex: number]: TalerErrorDetail };
@ -880,13 +907,6 @@ export interface RefreshGroupRecord {
* Timestamp when the refresh session finished. * Timestamp when the refresh session finished.
*/ */
timestampFinished: TalerProtocolTimestamp | undefined; timestampFinished: TalerProtocolTimestamp | undefined;
/**
* No coins are pending, but at least one is frozen.
*
* FIXME: What does this mean?
*/
frozen?: boolean;
} }
/** /**
@ -1128,7 +1148,8 @@ export interface PurchasePayInfo {
* Record that stores status information about one purchase, starting from when * Record that stores status information about one purchase, starting from when
* the customer accepts a proposal. Includes refund status if applicable. * the customer accepts a proposal. Includes refund status if applicable.
* *
* FIXME: Should have a single "status" field. * Key: {@link proposalId}
* Operation status: {@link purchaseStatus}
*/ */
export interface PurchaseRecord { export interface PurchaseRecord {
/** /**

View File

@ -336,7 +336,7 @@ export async function exportBackup(
})), })),
master_public_key: ex.masterPublicKey, master_public_key: ex.masterPublicKey,
currency: ex.currency, currency: ex.currency,
protocol_version: ex.protocolVersion, protocol_version: ex.protocolVersionRange,
wire_fees: wireFees, wire_fees: wireFees,
signing_keys: ex.signingKeys.map((x) => ({ signing_keys: ex.signingKeys.map((x) => ({
key: x.key, key: x.key,

View File

@ -57,6 +57,7 @@ import {
WgInfo, WgInfo,
WithdrawalGroupStatus, WithdrawalGroupStatus,
WithdrawalRecordType, WithdrawalRecordType,
RefreshOperationStatus,
} from "../../db.js"; } from "../../db.js";
import { InternalWalletState } from "../../internal-wallet-state.js"; import { InternalWalletState } from "../../internal-wallet-state.js";
import { assertUnreachable } from "../../util/assertUnreachable.js"; import { assertUnreachable } from "../../util/assertUnreachable.js";
@ -403,7 +404,7 @@ export async function importBackup(
denomination_keys: x.denomination_keys, denomination_keys: x.denomination_keys,
})), })),
masterPublicKey: backupExchangeDetails.master_public_key, masterPublicKey: backupExchangeDetails.master_public_key,
protocolVersion: backupExchangeDetails.protocol_version, protocolVersionRange: backupExchangeDetails.protocol_version,
reserveClosingDelay: backupExchangeDetails.reserve_closing_delay, reserveClosingDelay: backupExchangeDetails.reserve_closing_delay,
globalFees: backupExchangeDetails.global_fees.map((x) => ({ globalFees: backupExchangeDetails.global_fees.map((x) => ({
accountFee: Amounts.parseOrThrow(x.accountFee), accountFee: Amounts.parseOrThrow(x.accountFee),
@ -773,8 +774,8 @@ export async function importBackup(
: RefreshCoinStatus.Pending, : RefreshCoinStatus.Pending,
), ),
operationStatus: backupRefreshGroup.timestamp_finish operationStatus: backupRefreshGroup.timestamp_finish
? OperationStatus.Finished ? RefreshOperationStatus.Finished
: OperationStatus.Pending, : RefreshOperationStatus.Pending,
inputPerCoin: backupRefreshGroup.old_coins.map((x) => inputPerCoin: backupRefreshGroup.old_coins.map((x) =>
Amounts.parseOrThrow(x.input_amount), Amounts.parseOrThrow(x.input_amount),
), ),

View File

@ -641,7 +641,7 @@ export async function updateExchangeFromUrlHandler(
auditors: keysInfo.auditors, auditors: keysInfo.auditors,
currency: keysInfo.currency, currency: keysInfo.currency,
masterPublicKey: keysInfo.masterPublicKey, masterPublicKey: keysInfo.masterPublicKey,
protocolVersion: keysInfo.protocolVersion, protocolVersionRange: keysInfo.protocolVersion,
signingKeys: keysInfo.signingKeys, signingKeys: keysInfo.signingKeys,
reserveClosingDelay: keysInfo.reserveClosingDelay, reserveClosingDelay: keysInfo.reserveClosingDelay,
globalFees, globalFees,

View File

@ -106,16 +106,17 @@ async function gatherRefreshPending(
now: AbsoluteTime, now: AbsoluteTime,
resp: PendingOperationsResponse, resp: PendingOperationsResponse,
): Promise<void> { ): Promise<void> {
const keyRange = GlobalIDB.KeyRange.bound(
OperationStatusRange.ACTIVE_START,
OperationStatusRange.ACTIVE_END,
);
const refreshGroups = await tx.refreshGroups.indexes.byStatus.getAll( const refreshGroups = await tx.refreshGroups.indexes.byStatus.getAll(
OperationStatus.Pending, keyRange,
); );
for (const r of refreshGroups) { for (const r of refreshGroups) {
if (r.timestampFinished) { if (r.timestampFinished) {
return; return;
} }
if (r.frozen) {
return;
}
const opId = RetryTags.forRefresh(r); const opId = RetryTags.forRefresh(r);
const retryRecord = await tx.operationRetries.get(opId); const retryRecord = await tx.operationRetries.get(opId);

View File

@ -60,6 +60,7 @@ import {
OperationStatus, OperationStatus,
RefreshCoinStatus, RefreshCoinStatus,
RefreshGroupRecord, RefreshGroupRecord,
RefreshOperationStatus,
WalletStoresV1, WalletStoresV1,
} from "../db.js"; } from "../db.js";
import { TalerError } from "../errors.js"; import { TalerError } from "../errors.js";
@ -139,10 +140,11 @@ function updateGroupStatus(rg: RefreshGroupRecord): void {
); );
if (allDone) { if (allDone) {
if (anyFrozen) { if (anyFrozen) {
rg.frozen = true; rg.timestampFinished = AbsoluteTime.toTimestamp(AbsoluteTime.now());
rg.operationStatus = RefreshOperationStatus.FinishedWithError;
} else { } else {
rg.timestampFinished = AbsoluteTime.toTimestamp(AbsoluteTime.now()); rg.timestampFinished = AbsoluteTime.toTimestamp(AbsoluteTime.now());
rg.operationStatus = OperationStatus.Finished; rg.operationStatus = RefreshOperationStatus.Finished;
} }
} }
} }
@ -917,7 +919,7 @@ export async function createRefreshGroup(
} }
const refreshGroup: RefreshGroupRecord = { const refreshGroup: RefreshGroupRecord = {
operationStatus: OperationStatus.Pending, operationStatus: RefreshOperationStatus.Pending,
timestampFinished: undefined, timestampFinished: undefined,
statusPerCoin: oldCoinPubs.map(() => RefreshCoinStatus.Pending), statusPerCoin: oldCoinPubs.map(() => RefreshCoinStatus.Pending),
lastErrorPerCoin: {}, lastErrorPerCoin: {},
@ -933,7 +935,7 @@ 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 = TalerProtocolTimestamp.now(); refreshGroup.timestampFinished = TalerProtocolTimestamp.now();
refreshGroup.operationStatus = OperationStatus.Finished; refreshGroup.operationStatus = RefreshOperationStatus.Finished;
} }
await tx.refreshGroups.put(refreshGroup); await tx.refreshGroups.put(refreshGroup);

View File

@ -70,6 +70,7 @@ import {
DenominationRecord, DenominationRecord,
DenominationVerificationStatus, DenominationVerificationStatus,
PlanchetRecord, PlanchetRecord,
PlanchetStatus,
WalletStoresV1, WalletStoresV1,
WgInfo, WgInfo,
WithdrawalGroupRecord, WithdrawalGroupRecord,
@ -430,7 +431,7 @@ async function processPlanchetGenerate(
coinPub: r.coinPub, coinPub: r.coinPub,
denomPubHash: r.denomPubHash, denomPubHash: r.denomPubHash,
reservePub: r.reservePub, reservePub: r.reservePub,
withdrawalDone: false, planchetStatus: PlanchetStatus.Pending,
withdrawSig: r.withdrawSig, withdrawSig: r.withdrawSig,
withdrawalGroupId: withdrawalGroup.withdrawalGroupId, withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
maxAge: withdrawalGroup.restrictAge ?? AgeRestriction.AGE_UNRESTRICTED, maxAge: withdrawalGroup.restrictAge ?? AgeRestriction.AGE_UNRESTRICTED,
@ -481,7 +482,7 @@ async function processPlanchetExchangeRequest(
if (!planchet) { if (!planchet) {
return; return;
} }
if (planchet.withdrawalDone) { if (planchet.planchetStatus === PlanchetStatus.WithdrawalDone) {
logger.warn("processPlanchet: planchet already withdrawn"); logger.warn("processPlanchet: planchet already withdrawn");
return; return;
} }
@ -593,7 +594,7 @@ async function processPlanchetExchangeBatchRequest(
if (!planchet) { if (!planchet) {
return; return;
} }
if (planchet.withdrawalDone) { if (planchet.planchetStatus === PlanchetStatus.WithdrawalDone) {
logger.warn("processPlanchet: planchet already withdrawn"); logger.warn("processPlanchet: planchet already withdrawn");
return; return;
} }
@ -652,7 +653,7 @@ async function processPlanchetVerifyAndStoreCoin(
if (!planchet) { if (!planchet) {
return; return;
} }
if (planchet.withdrawalDone) { if (planchet.planchetStatus === PlanchetStatus.WithdrawalDone) {
logger.warn("processPlanchet: planchet already withdrawn"); logger.warn("processPlanchet: planchet already withdrawn");
return; return;
} }
@ -767,10 +768,10 @@ async function processPlanchetVerifyAndStoreCoin(
]) ])
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
const p = await tx.planchets.get(planchetCoinPub); const p = await tx.planchets.get(planchetCoinPub);
if (!p || p.withdrawalDone) { if (!p || p.planchetStatus === PlanchetStatus.WithdrawalDone) {
return false; return false;
} }
p.withdrawalDone = true; p.planchetStatus = PlanchetStatus.WithdrawalDone;
await tx.planchets.put(p); await tx.planchets.put(p);
await makeCoinAvailable(ws, tx, coin); await makeCoinAvailable(ws, tx, coin);
return true; return true;
@ -1140,7 +1141,7 @@ export async function processWithdrawalGroup(
await tx.planchets.indexes.byGroup await tx.planchets.indexes.byGroup
.iter(withdrawalGroupId) .iter(withdrawalGroupId)
.forEach((x) => { .forEach((x) => {
if (x.withdrawalDone) { if (x.planchetStatus === PlanchetStatus.WithdrawalDone) {
numFinished++; numFinished++;
} }
if (x.lastError) { if (x.lastError) {
@ -1258,10 +1259,10 @@ export async function getExchangeWithdrawalInfo(
}); });
let versionMatch; let versionMatch;
if (exchangeDetails.protocolVersion) { if (exchangeDetails.protocolVersionRange) {
versionMatch = LibtoolVersion.compare( versionMatch = LibtoolVersion.compare(
WALLET_EXCHANGE_PROTOCOL_VERSION, WALLET_EXCHANGE_PROTOCOL_VERSION,
exchangeDetails.protocolVersion, exchangeDetails.protocolVersionRange,
); );
if ( if (
@ -1271,7 +1272,7 @@ export async function getExchangeWithdrawalInfo(
) { ) {
logger.warn( logger.warn(
`wallet's support for exchange protocol version ${WALLET_EXCHANGE_PROTOCOL_VERSION} might be outdated ` + `wallet's support for exchange protocol version ${WALLET_EXCHANGE_PROTOCOL_VERSION} might be outdated ` +
`(exchange has ${exchangeDetails.protocolVersion}), checking for updates`, `(exchange has ${exchangeDetails.protocolVersionRange}), checking for updates`,
); );
} }
} }
@ -1296,7 +1297,7 @@ export async function getExchangeWithdrawalInfo(
earliestDepositExpiration, earliestDepositExpiration,
exchangePaytoUris: paytoUris, exchangePaytoUris: paytoUris,
exchangeWireAccounts, exchangeWireAccounts,
exchangeVersion: exchangeDetails.protocolVersion || "unknown", exchangeVersion: exchangeDetails.protocolVersionRange || "unknown",
isAudited, isAudited,
isTrusted, isTrusted,
numOfferedDenoms: possibleDenoms.length, numOfferedDenoms: possibleDenoms.length,