From d19aef746c1e67deaccc7c8cefba008f2f0d46ca Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 30 Aug 2023 15:54:56 +0200 Subject: wallet-core: towards DD48 --- .../src/operations/backup/import.ts | 27 ++++---- .../taler-wallet-core/src/operations/common.ts | 80 +++++++++++++++------- .../taler-wallet-core/src/operations/exchanges.ts | 56 +++++++++++---- .../taler-wallet-core/src/operations/pending.ts | 28 ++++---- .../taler-wallet-core/src/operations/refresh.ts | 10 +-- .../taler-wallet-core/src/operations/withdraw.ts | 7 +- 6 files changed, 135 insertions(+), 73 deletions(-) (limited to 'packages/taler-wallet-core/src/operations') diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts index a53b624e8..836c65643 100644 --- a/packages/taler-wallet-core/src/operations/backup/import.ts +++ b/packages/taler-wallet-core/src/operations/backup/import.ts @@ -342,20 +342,19 @@ export async function importBackup( if (existingExchange) { continue; } - await tx.exchanges.put({ - baseUrl: backupExchange.base_url, - detailsPointer: { - currency: backupExchange.currency, - masterPublicKey: backupExchange.master_public_key, - updateClock: backupExchange.update_clock, - }, - permanent: true, - lastUpdate: undefined, - nextUpdate: TalerPreciseTimestamp.now(), - nextRefreshCheck: TalerPreciseTimestamp.now(), - lastKeysEtag: undefined, - lastWireEtag: undefined, - }); + // await tx.exchanges.put({ + // baseUrl: backupExchange.base_url, + // detailsPointer: { + // currency: backupExchange.currency, + // masterPublicKey: backupExchange.master_public_key, + // updateClock: backupExchange.update_clock, + // }, + // lastUpdate: undefined, + // nextUpdate: TalerPreciseTimestamp.now(), + // nextRefreshCheck: TalerPreciseTimestamp.now(), + // lastKeysEtag: undefined, + // lastWireEtag: undefined, + // }); } for (const backupExchangeDetails of backupBlob.exchange_details) { diff --git a/packages/taler-wallet-core/src/operations/common.ts b/packages/taler-wallet-core/src/operations/common.ts index 7a8b78b53..e96beb5b2 100644 --- a/packages/taler-wallet-core/src/operations/common.ts +++ b/packages/taler-wallet-core/src/operations/common.ts @@ -30,6 +30,7 @@ import { ExchangeEntryStatus, ExchangeListItem, ExchangeTosStatus, + ExchangeUpdateStatus, getErrorDetailFromException, j2s, Logger, @@ -47,7 +48,7 @@ import { WalletStoresV1, CoinRecord, ExchangeDetailsRecord, - ExchangeRecord, + ExchangeEntryRecord, BackupProviderRecord, DepositGroupRecord, PeerPullPaymentIncomingRecord, @@ -59,6 +60,8 @@ import { RefreshGroupRecord, RewardRecord, WithdrawalGroupRecord, + ExchangeEntryDbUpdateStatus, + ExchangeEntryDbRecordStatus, } from "../db.js"; import { makeErrorDetail, TalerError } from "@gnu-taler/taler-util"; import { InternalWalletState } from "../internal-wallet-state.js"; @@ -529,16 +532,16 @@ export function getExchangeTosStatus( exchangeDetails: ExchangeDetailsRecord, ): ExchangeTosStatus { if (!exchangeDetails.tosAccepted) { - return ExchangeTosStatus.New; + return ExchangeTosStatus.Proposed; } if (exchangeDetails.tosAccepted?.etag == exchangeDetails.tosCurrentEtag) { return ExchangeTosStatus.Accepted; } - return ExchangeTosStatus.Changed; + return ExchangeTosStatus.Proposed; } export function makeExchangeListItem( - r: ExchangeRecord, + r: ExchangeEntryRecord, exchangeDetails: ExchangeDetailsRecord | undefined, lastError: TalerErrorDetail | undefined, ): ExchangeListItem { @@ -547,30 +550,57 @@ export function makeExchangeListItem( error: lastError, } : undefined; - if (!exchangeDetails) { - return { - exchangeBaseUrl: r.baseUrl, - currency: undefined, - tosStatus: ExchangeTosStatus.Unknown, - paytoUris: [], - exchangeStatus: ExchangeEntryStatus.Unknown, - permanent: r.permanent, - ageRestrictionOptions: [], - lastUpdateErrorInfo, - }; + + let exchangeUpdateStatus: ExchangeUpdateStatus; + switch (r.updateStatus) { + case ExchangeEntryDbUpdateStatus.Failed: + exchangeUpdateStatus = ExchangeUpdateStatus.Failed; + break; + case ExchangeEntryDbUpdateStatus.Initial: + exchangeUpdateStatus = ExchangeUpdateStatus.Initial; + break; + case ExchangeEntryDbUpdateStatus.InitialUpdate: + exchangeUpdateStatus = ExchangeUpdateStatus.InitialUpdate; + break; + case ExchangeEntryDbUpdateStatus.OutdatedUpdate: + exchangeUpdateStatus = ExchangeUpdateStatus.OutdatedUpdate; + break; + case ExchangeEntryDbUpdateStatus.Ready: + exchangeUpdateStatus = ExchangeUpdateStatus.Ready; + break; + case ExchangeEntryDbUpdateStatus.ReadyUpdate: + exchangeUpdateStatus = ExchangeUpdateStatus.ReadyUpdate; + break; + case ExchangeEntryDbUpdateStatus.Suspended: + exchangeUpdateStatus = ExchangeUpdateStatus.Suspended; + break; + } + + let exchangeEntryStatus: ExchangeEntryStatus; + switch (r.entryStatus) { + case ExchangeEntryDbRecordStatus.Ephemeral: + exchangeEntryStatus = ExchangeEntryStatus.Ephemeral; + break; + case ExchangeEntryDbRecordStatus.Preset: + exchangeEntryStatus = ExchangeEntryStatus.Preset; + break; + case ExchangeEntryDbRecordStatus.Used: + exchangeEntryStatus = ExchangeEntryStatus.Used; + break; } - let exchangeStatus; - exchangeStatus = ExchangeEntryStatus.Ok; + return { exchangeBaseUrl: r.baseUrl, - currency: exchangeDetails.currency, - tosStatus: getExchangeTosStatus(exchangeDetails), - paytoUris: exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri), - exchangeStatus, - permanent: r.permanent, - ageRestrictionOptions: exchangeDetails.ageMask + currency: exchangeDetails?.currency, + exchangeUpdateStatus, + exchangeEntryStatus, + tosStatus: exchangeDetails + ? getExchangeTosStatus(exchangeDetails) + : ExchangeTosStatus.Pending, + ageRestrictionOptions: exchangeDetails?.ageMask ? AgeRestriction.getAgeGroupsFromMask(exchangeDetails.ageMask) : [], + paytoUris: exchangeDetails?.wireInfo.accounts.map((x) => x.payto_uri) ?? [], lastUpdateErrorInfo, }; } @@ -892,13 +922,13 @@ export namespace TaskIdentifiers { export function forWithdrawal(wg: WithdrawalGroupRecord): TaskId { return `${PendingTaskType.Withdraw}:${wg.withdrawalGroupId}` as TaskId; } - export function forExchangeUpdate(exch: ExchangeRecord): TaskId { + export function forExchangeUpdate(exch: ExchangeEntryRecord): TaskId { return `${PendingTaskType.ExchangeUpdate}:${exch.baseUrl}` as TaskId; } export function forExchangeUpdateFromUrl(exchBaseUrl: string): TaskId { return `${PendingTaskType.ExchangeUpdate}:${exchBaseUrl}` as TaskId; } - export function forExchangeCheckRefresh(exch: ExchangeRecord): TaskId { + export function forExchangeCheckRefresh(exch: ExchangeEntryRecord): TaskId { return `${PendingTaskType.ExchangeCheckRefresh}:${exch.baseUrl}` as TaskId; } export function forTipPickup(tipRecord: RewardRecord): TaskId { diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index c6b46e360..311a71a6e 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -32,6 +32,7 @@ import { encodeCrock, ExchangeAuditor, ExchangeDenomination, + ExchangeEntryStatus, ExchangeGlobalFees, ExchangeSignKeyJson, ExchangeWireJson, @@ -66,10 +67,15 @@ import { DenominationRecord, DenominationVerificationStatus, ExchangeDetailsRecord, - ExchangeRecord, + ExchangeEntryRecord, WalletStoresV1, } from "../db.js"; -import { isWithdrawableDenom } from "../index.js"; +import { + ExchangeEntryDbRecordStatus, + ExchangeEntryDbUpdateStatus, + isWithdrawableDenom, + WalletDbReadWriteTransaction, +} from "../index.js"; import { InternalWalletState, TrustInfo } from "../internal-wallet-state.js"; import { checkDbInvariant } from "../util/invariants.js"; import { @@ -326,6 +332,26 @@ export async function downloadExchangeInfo( }; } +export async function addPresetExchangeEntry( + tx: WalletDbReadWriteTransaction<"exchanges">, + exchangeBaseUrl: string, +): Promise { + let exchange = await tx.exchanges.get(exchangeBaseUrl); + if (!exchange) { + const r: ExchangeEntryRecord = { + entryStatus: ExchangeEntryDbRecordStatus.Preset, + updateStatus: ExchangeEntryDbUpdateStatus.Initial, + baseUrl: exchangeBaseUrl, + detailsPointer: undefined, + lastUpdate: undefined, + lastKeysEtag: undefined, + nextRefreshCheckStampMs: AbsoluteTime.getStampMsNever(), + nextUpdateStampMs: AbsoluteTime.getStampMsNever(), + }; + await tx.exchanges.put(r); + } +} + export async function provideExchangeRecordInTx( ws: InternalWalletState, tx: GetReadWriteAccess<{ @@ -335,20 +361,20 @@ export async function provideExchangeRecordInTx( baseUrl: string, now: AbsoluteTime, ): Promise<{ - exchange: ExchangeRecord; + exchange: ExchangeEntryRecord; exchangeDetails: ExchangeDetailsRecord | undefined; }> { let exchange = await tx.exchanges.get(baseUrl); if (!exchange) { - const r: ExchangeRecord = { - permanent: true, + const r: ExchangeEntryRecord = { + entryStatus: ExchangeEntryDbRecordStatus.Ephemeral, + updateStatus: ExchangeEntryDbUpdateStatus.InitialUpdate, baseUrl: baseUrl, detailsPointer: undefined, lastUpdate: undefined, - nextUpdate: AbsoluteTime.toPreciseTimestamp(now), - nextRefreshCheck: AbsoluteTime.toPreciseTimestamp(now), + nextUpdateStampMs: AbsoluteTime.getStampMsNever(), + nextRefreshCheckStampMs: AbsoluteTime.getStampMsNever(), lastKeysEtag: undefined, - lastWireEtag: undefined, }; await tx.exchanges.put(r); exchange = r; @@ -534,6 +560,10 @@ export async function downloadTosFromAcceptedFormat( ); } +/** + * FIXME: Split this into two parts: (a) triggering the exchange + * to be updated and (b) waiting for the update to finish. + */ export async function updateExchangeFromUrl( ws: InternalWalletState, baseUrl: string, @@ -543,7 +573,7 @@ export async function updateExchangeFromUrl( cancellationToken?: CancellationToken; } = {}, ): Promise<{ - exchange: ExchangeRecord; + exchange: ExchangeEntryRecord; exchangeDetails: ExchangeDetailsRecord; }> { const canonUrl = canonicalizeBaseUrl(baseUrl); @@ -613,7 +643,7 @@ export async function updateExchangeFromUrlHandler( !forceNow && exchangeDetails !== undefined && !AbsoluteTime.isExpired( - AbsoluteTime.fromPreciseTimestamp(exchange.nextUpdate), + AbsoluteTime.fromStampMs(exchange.nextUpdateStampMs), ) ) { logger.trace("using existing exchange info"); @@ -755,11 +785,11 @@ export async function updateExchangeFromUrlHandler( newDetails.rowId = existingDetails.rowId; } r.lastUpdate = TalerPreciseTimestamp.now(); - r.nextUpdate = AbsoluteTime.toPreciseTimestamp( + r.nextUpdateStampMs = AbsoluteTime.toStampMs( AbsoluteTime.fromProtocolTimestamp(keysInfo.expiry), ); // New denominations might be available. - r.nextRefreshCheck = TalerPreciseTimestamp.now(); + r.nextRefreshCheckStampMs = AbsoluteTime.getStampMsNow(); if (detailsPointerChanged) { r.detailsPointer = { currency: newDetails.currency, @@ -948,7 +978,7 @@ export async function getExchangePaytoUri( */ export async function getExchangeTrust( ws: InternalWalletState, - exchangeInfo: ExchangeRecord, + exchangeInfo: ExchangeEntryRecord, ): Promise { let isTrusted = false; let isAudited = false; diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts index 6c6546f83..e37e45c16 100644 --- a/packages/taler-wallet-core/src/operations/pending.ts +++ b/packages/taler-wallet-core/src/operations/pending.ts @@ -45,6 +45,7 @@ import { PeerPushPaymentIncomingRecord, RefundGroupRecord, RefundGroupStatus, + ExchangeEntryDbUpdateStatus, } from "../db.js"; import { PendingOperationsResponse, @@ -81,19 +82,25 @@ async function gatherExchangePending( ws: InternalWalletState, tx: GetReadOnlyAccess<{ exchanges: typeof WalletStoresV1.exchanges; - exchangeDetails: typeof WalletStoresV1.exchangeDetails; operationRetries: typeof WalletStoresV1.operationRetries; }>, now: AbsoluteTime, resp: PendingOperationsResponse, ): Promise { - // FIXME: We should do a range query here based on the update time. + // FIXME: We should do a range query here based on the update time + // and/or the entry state. await tx.exchanges.iter().forEachAsync(async (exch) => { + switch (exch.updateStatus) { + case ExchangeEntryDbUpdateStatus.Initial: + case ExchangeEntryDbUpdateStatus.Suspended: + case ExchangeEntryDbUpdateStatus.Failed: + return; + } const opTag = TaskIdentifiers.forExchangeUpdate(exch); let opr = await tx.operationRetries.get(opTag); const timestampDue = opr?.retryInfo.nextRetry ?? - AbsoluteTime.fromPreciseTimestamp(exch.nextUpdate); + AbsoluteTime.fromStampMs(exch.nextUpdateStampMs); resp.pendingOperations.push({ type: PendingTaskType.ExchangeUpdate, ...getPendingCommon(ws, opTag, timestampDue), @@ -108,7 +115,7 @@ async function gatherExchangePending( resp.pendingOperations.push({ type: PendingTaskType.ExchangeCheckRefresh, ...getPendingCommon(ws, opTag, timestampDue), - timestampDue: AbsoluteTime.fromPreciseTimestamp(exch.nextRefreshCheck), + timestampDue: AbsoluteTime.fromStampMs(exch.nextRefreshCheckStampMs), givesLifeness: false, exchangeBaseUrl: exch.baseUrl, }); @@ -184,8 +191,9 @@ export async function iterRecordsForWithdrawal( WithdrawalGroupStatus.PendingRegisteringBank, WithdrawalGroupStatus.PendingAml, ); - withdrawalGroupRecords = - await tx.withdrawalGroups.indexes.byStatus.getAll(range); + withdrawalGroupRecords = await tx.withdrawalGroups.indexes.byStatus.getAll( + range, + ); } else { withdrawalGroupRecords = await tx.withdrawalGroups.indexes.byStatus.getAll(); @@ -344,12 +352,8 @@ export async function iterRecordsForRefund( f: (r: RefundGroupRecord) => Promise, ): Promise { if (filter.onlyState === "nonfinal") { - const keyRange = GlobalIDB.KeyRange.only( - RefundGroupStatus.Pending - ); - await tx.refundGroups.indexes.byStatus - .iter(keyRange) - .forEachAsync(f); + const keyRange = GlobalIDB.KeyRange.only(RefundGroupStatus.Pending); + await tx.refundGroups.indexes.byStatus.iter(keyRange).forEachAsync(f); } else { await tx.refundGroups.iter().forEachAsync(f); } diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index 72d1a2725..fb356f0fc 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -1190,14 +1190,14 @@ export async function autoRefresh( `created refresh group for auto-refresh (${res.refreshGroupId})`, ); } -// logger.trace( -// `current wallet time: ${AbsoluteTime.toIsoString(AbsoluteTime.now())}`, -// ); + // logger.trace( + // `current wallet time: ${AbsoluteTime.toIsoString(AbsoluteTime.now())}`, + // ); logger.trace( `next refresh check at ${AbsoluteTime.toIsoString(minCheckThreshold)}`, ); - exchange.nextRefreshCheck = - AbsoluteTime.toPreciseTimestamp(minCheckThreshold); + exchange.nextRefreshCheckStampMs = + AbsoluteTime.toStampMs(minCheckThreshold); await tx.exchanges.put(exchange); }); return TaskRunResult.finished(); diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index 040d191e1..d8ce0a9a2 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -128,6 +128,8 @@ import { } from "../util/coinSelection.js"; import { ExchangeDetailsRecord, + ExchangeEntryDbRecordStatus, + ExchangeEntryDbUpdateStatus, PendingTaskType, isWithdrawableDenom, } from "../index.js"; @@ -2341,10 +2343,6 @@ export async function internalPerformCreateWithdrawalGroup( }>, prep: PrepareCreateWithdrawalGroupResult, ): Promise { - const transactionId = constructTransactionIdentifier({ - tag: TransactionType.Withdrawal, - withdrawalGroupId: prep.withdrawalGroup.withdrawalGroupId, - }); const { withdrawalGroup } = prep; if (!prep.creationInfo) { return { withdrawalGroup, transitionInfo: undefined }; @@ -2361,6 +2359,7 @@ export async function internalPerformCreateWithdrawalGroup( const exchange = await tx.exchanges.get(withdrawalGroup.exchangeBaseUrl); if (exchange) { exchange.lastWithdrawal = TalerPreciseTimestamp.now(); + exchange.entryStatus = ExchangeEntryDbRecordStatus.Used; await tx.exchanges.put(exchange); } -- cgit v1.2.3