diff options
Diffstat (limited to 'packages/taler-wallet-core/src/operations')
18 files changed, 472 insertions, 399 deletions
diff --git a/packages/taler-wallet-core/src/operations/attention.ts b/packages/taler-wallet-core/src/operations/attention.ts index 7d84b43ef..92d69e93e 100644 --- a/packages/taler-wallet-core/src/operations/attention.ts +++ b/packages/taler-wallet-core/src/operations/attention.ts @@ -31,6 +31,7 @@ import { UserAttentionUnreadList, } from "@gnu-taler/taler-util"; import { InternalWalletState } from "../internal-wallet-state.js"; +import { timestampPreciseFromDb, timestampPreciseToDb } from "../index.js"; const logger = new Logger("operations/attention.ts"); @@ -74,7 +75,7 @@ export async function getUserAttentions( return; pending.push({ info: x.info, - when: TalerPreciseTimestamp.fromMilliseconds(x.createdMs), + when: timestampPreciseFromDb(x.created), read: x.read !== undefined, }); }); @@ -94,7 +95,7 @@ export async function markAttentionRequestAsRead( if (!ua) throw Error("attention request not found"); tx.userAttention.put({ ...ua, - read: TalerPreciseTimestamp.now(), + read: timestampPreciseToDb(TalerPreciseTimestamp.now()), }); }); } @@ -117,7 +118,7 @@ export async function addAttentionRequest( await tx.userAttention.put({ info, entityId, - createdMs: AbsoluteTime.now().t_ms as number, + created: timestampPreciseToDb(TalerPreciseTimestamp.now()), read: undefined, }); }); diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts index a5e8dbd42..7a2771c57 100644 --- a/packages/taler-wallet-core/src/operations/backup/index.ts +++ b/packages/taler-wallet-core/src/operations/backup/index.ts @@ -84,6 +84,9 @@ import { ConfigRecord, ConfigRecordKey, WalletBackupConfState, + timestampOptionalPreciseFromDb, + timestampPreciseFromDb, + timestampPreciseToDb, } from "../../db.js"; import { InternalWalletState } from "../../internal-wallet-state.js"; import { assertUnreachable } from "../../util/assertUnreachable.js"; @@ -259,10 +262,12 @@ async function runBackupCycleForProvider( if (!prov) { return; } - prov.lastBackupCycleTimestamp = TalerPreciseTimestamp.now(); + prov.lastBackupCycleTimestamp = timestampPreciseToDb( + TalerPreciseTimestamp.now(), + ); prov.state = { tag: BackupProviderStateTag.Ready, - nextBackupTimestamp: getNextBackupTimestamp(), + nextBackupTimestamp: timestampPreciseToDb(getNextBackupTimestamp()), }; await tx.backupProviders.put(prov); }); @@ -361,10 +366,12 @@ async function runBackupCycleForProvider( return; } prov.lastBackupHash = encodeCrock(currentBackupHash); - prov.lastBackupCycleTimestamp = TalerPreciseTimestamp.now(); + prov.lastBackupCycleTimestamp = timestampPreciseToDb( + TalerPreciseTimestamp.now(), + ); prov.state = { tag: BackupProviderStateTag.Ready, - nextBackupTimestamp: getNextBackupTimestamp(), + nextBackupTimestamp: timestampPreciseToDb(getNextBackupTimestamp()), }; await tx.backupProviders.put(prov); }); @@ -594,7 +601,9 @@ export async function addBackupProvider( if (req.activate) { oldProv.state = { tag: BackupProviderStateTag.Ready, - nextBackupTimestamp: TalerPreciseTimestamp.now(), + nextBackupTimestamp: timestampPreciseToDb( + TalerPreciseTimestamp.now(), + ), }; logger.info("setting existing backup provider to active"); await tx.backupProviders.put(oldProv); @@ -616,7 +625,9 @@ export async function addBackupProvider( if (req.activate) { state = { tag: BackupProviderStateTag.Ready, - nextBackupTimestamp: TalerPreciseTimestamp.now(), + nextBackupTimestamp: timestampPreciseToDb( + TalerPreciseTimestamp.now(), + ), }; } else { state = { @@ -840,7 +851,9 @@ export async function getBackupInfo( providers.push({ active: x.provider.state.tag !== BackupProviderStateTag.Provisional, syncProviderBaseUrl: x.provider.baseUrl, - lastSuccessfulBackupTimestamp: x.provider.lastBackupCycleTimestamp, + lastSuccessfulBackupTimestamp: timestampOptionalPreciseFromDb( + x.provider.lastBackupCycleTimestamp, + ), paymentProposalIds: x.provider.paymentProposalIds, lastError: x.provider.state.tag === BackupProviderStateTag.Retrying @@ -917,7 +930,9 @@ async function backupRecoveryTheirs( shouldRetryFreshProposal: false, state: { tag: BackupProviderStateTag.Ready, - nextBackupTimestamp: TalerPreciseTimestamp.now(), + nextBackupTimestamp: timestampPreciseToDb( + TalerPreciseTimestamp.now(), + ), }, uids: [encodeCrock(getRandomBytes(32))], }); diff --git a/packages/taler-wallet-core/src/operations/common.ts b/packages/taler-wallet-core/src/operations/common.ts index 50dd3dc5c..b28a5363d 100644 --- a/packages/taler-wallet-core/src/operations/common.ts +++ b/packages/taler-wallet-core/src/operations/common.ts @@ -40,6 +40,7 @@ import { TalerError, TalerErrorCode, TalerErrorDetail, + TalerPreciseTimestamp, TombstoneIdStr, TransactionIdStr, TransactionType, @@ -49,6 +50,7 @@ import { CryptoApiStoppedError } from "../crypto/workers/crypto-dispatcher.js"; import { BackupProviderRecord, CoinRecord, + DbPreciseTimestamp, DepositGroupRecord, ExchangeDetailsRecord, ExchangeEntryDbRecordStatus, @@ -62,6 +64,7 @@ import { RecoupGroupRecord, RefreshGroupRecord, RewardRecord, + timestampPreciseToDb, WalletStoresV1, WithdrawalGroupRecord, } from "../db.js"; @@ -360,11 +363,11 @@ async function storePendingTaskError( retryRecord = { id: pendingTaskId, lastError: e, - retryInfo: RetryInfo.reset(), + retryInfo: DbRetryInfo.reset(), }; } else { retryRecord.lastError = e; - retryRecord.retryInfo = RetryInfo.increment(retryRecord.retryInfo); + retryRecord.retryInfo = DbRetryInfo.increment(retryRecord.retryInfo); } await tx.operationRetries.put(retryRecord); return taskToTransactionNotification(ws, tx, pendingTaskId, e); @@ -383,7 +386,7 @@ export async function resetPendingTaskTimeout( if (retryRecord) { // Note that we don't reset the lastError, it should still be visible // while the retry runs. - retryRecord.retryInfo = RetryInfo.reset(); + retryRecord.retryInfo = DbRetryInfo.reset(); await tx.operationRetries.put(retryRecord); } return taskToTransactionNotification(ws, tx, pendingTaskId, undefined); @@ -403,14 +406,14 @@ async function storePendingTaskPending( if (!retryRecord) { retryRecord = { id: pendingTaskId, - retryInfo: RetryInfo.reset(), + retryInfo: DbRetryInfo.reset(), }; } else { if (retryRecord.lastError) { hadError = true; } delete retryRecord.lastError; - retryRecord.retryInfo = RetryInfo.increment(retryRecord.retryInfo); + retryRecord.retryInfo = DbRetryInfo.increment(retryRecord.retryInfo); } await tx.operationRetries.put(retryRecord); if (hadError) { @@ -590,7 +593,7 @@ export function makeExchangeListItem( return { exchangeBaseUrl: r.baseUrl, - currency: exchangeDetails?.currency, + currency: exchangeDetails?.currency ?? r.presetCurrencyHint, exchangeUpdateStatus, exchangeEntryStatus, tosStatus: exchangeDetails @@ -736,9 +739,9 @@ export interface TaskRunLongpollResult { type: TaskRunResultType.Longpoll; } -export interface RetryInfo { - firstTry: AbsoluteTime; - nextRetry: AbsoluteTime; +export interface DbRetryInfo { + firstTry: DbPreciseTimestamp; + nextRetry: DbPreciseTimestamp; retryCounter: number; } @@ -755,7 +758,7 @@ const defaultRetryPolicy: RetryPolicy = { }; function updateTimeout( - r: RetryInfo, + r: DbRetryInfo, p: RetryPolicy = defaultRetryPolicy, ): void { const now = AbsoluteTime.now(); @@ -763,7 +766,9 @@ function updateTimeout( throw Error("assertion failed"); } if (p.backoffDelta.d_ms === "forever") { - r.nextRetry = AbsoluteTime.never(); + r.nextRetry = timestampPreciseToDb( + AbsoluteTime.toPreciseTimestamp(AbsoluteTime.never()), + ); return; } @@ -775,12 +780,12 @@ function updateTimeout( (p.maxTimeout.d_ms === "forever" ? nextIncrement : Math.min(p.maxTimeout.d_ms, nextIncrement)); - r.nextRetry = AbsoluteTime.fromMilliseconds(t); + r.nextRetry = timestampPreciseToDb(TalerPreciseTimestamp.fromMilliseconds(t)); } -export namespace RetryInfo { +export namespace DbRetryInfo { export function getDuration( - r: RetryInfo | undefined, + r: DbRetryInfo | undefined, p: RetryPolicy = defaultRetryPolicy, ): Duration { if (!r) { @@ -797,11 +802,11 @@ export namespace RetryInfo { }; } - export function reset(p: RetryPolicy = defaultRetryPolicy): RetryInfo { - const now = AbsoluteTime.now(); - const info = { - firstTry: now, - nextRetry: now, + export function reset(p: RetryPolicy = defaultRetryPolicy): DbRetryInfo { + const now = TalerPreciseTimestamp.now(); + const info: DbRetryInfo = { + firstTry: timestampPreciseToDb(now), + nextRetry: timestampPreciseToDb(now), retryCounter: 0, }; updateTimeout(info, p); @@ -809,9 +814,9 @@ export namespace RetryInfo { } export function increment( - r: RetryInfo | undefined, + r: DbRetryInfo | undefined, p: RetryPolicy = defaultRetryPolicy, - ): RetryInfo { + ): DbRetryInfo { if (!r) { return reset(p); } diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index 2de8f30a1..111d15989 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -73,6 +73,9 @@ import { RefreshOperationStatus, createRefreshGroup, getTotalRefreshCost, + timestampPreciseToDb, + timestampProtocolFromDb, + timestampProtocolToDb, } from "../index.js"; import { InternalWalletState } from "../internal-wallet-state.js"; import { assertUnreachable } from "../util/assertUnreachable.js"; @@ -799,7 +802,7 @@ async function processDepositGroupPendingTrack( amountRaw: Amounts.stringify(raw), wireFee: Amounts.stringify(wireFee), exchangePub: track.exchange_pub, - timestampExecuted: track.execution_time, + timestampExecuted: timestampProtocolToDb(track.execution_time), wireTransferId: track.wtid, }, id: track.exchange_sig, @@ -857,7 +860,9 @@ async function processDepositGroupPendingTrack( } } if (allWired) { - dg.timestampFinished = TalerPreciseTimestamp.now(); + dg.timestampFinished = timestampPreciseToDb( + TalerPreciseTimestamp.now(), + ); dg.operationStatus = DepositOperationStatus.Finished; await tx.depositGroups.put(dg); } @@ -1375,7 +1380,9 @@ export async function createDepositGroup( amount: contractData.amount, noncePriv: noncePair.priv, noncePub: noncePair.pub, - timestampCreated: AbsoluteTime.toPreciseTimestamp(now), + timestampCreated: timestampPreciseToDb( + AbsoluteTime.toPreciseTimestamp(now), + ), timestampFinished: undefined, statusPerCoin: payCoinSel.coinSel.coinPubs.map( () => DepositElementStatus.DepositPending, @@ -1388,7 +1395,9 @@ export async function createDepositGroup( counterpartyEffectiveDepositAmount: Amounts.stringify( counterpartyEffectiveDepositAmount, ), - wireTransferDeadline: contractTerms.wire_transfer_deadline, + wireTransferDeadline: timestampProtocolToDb( + contractTerms.wire_transfer_deadline, + ), wire: { payto_uri: req.depositPaytoUri, salt: wireSalt, diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index 43a08ed3b..82d7b42bf 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -74,6 +74,9 @@ import { ExchangeEntryDbRecordStatus, ExchangeEntryDbUpdateStatus, isWithdrawableDenom, + timestampPreciseFromDb, + timestampPreciseToDb, + timestampProtocolToDb, WalletDbReadWriteTransaction, } from "../index.js"; import { InternalWalletState, TrustInfo } from "../internal-wallet-state.js"; @@ -174,7 +177,7 @@ export async function acceptExchangeTermsOfService( if (d) { d.tosAccepted = { etag: etag || d.tosCurrentEtag, - timestamp: TalerPreciseTimestamp.now(), + timestamp: timestampPreciseToDb(TalerPreciseTimestamp.now()), }; await tx.exchangeDetails.put(d); } @@ -306,6 +309,7 @@ export async function downloadExchangeInfo( export async function addPresetExchangeEntry( tx: WalletDbReadWriteTransaction<"exchanges">, exchangeBaseUrl: string, + currencyHint?: string, ): Promise<void> { let exchange = await tx.exchanges.get(exchangeBaseUrl); if (!exchange) { @@ -313,17 +317,22 @@ export async function addPresetExchangeEntry( entryStatus: ExchangeEntryDbRecordStatus.Preset, updateStatus: ExchangeEntryDbUpdateStatus.Initial, baseUrl: exchangeBaseUrl, + presetCurrencyHint: currencyHint, detailsPointer: undefined, lastUpdate: undefined, lastKeysEtag: undefined, - nextRefreshCheckStampMs: AbsoluteTime.getStampMsNever(), - nextUpdateStampMs: AbsoluteTime.getStampMsNever(), + nextRefreshCheckStamp: timestampPreciseToDb( + AbsoluteTime.toPreciseTimestamp(AbsoluteTime.never()), + ), + nextUpdateStamp: timestampPreciseToDb( + AbsoluteTime.toPreciseTimestamp(AbsoluteTime.never()), + ), }; await tx.exchanges.put(r); } } -export async function provideExchangeRecordInTx( +async function provideExchangeRecordInTx( ws: InternalWalletState, tx: GetReadWriteAccess<{ exchanges: typeof WalletStoresV1.exchanges; @@ -343,8 +352,12 @@ export async function provideExchangeRecordInTx( baseUrl: baseUrl, detailsPointer: undefined, lastUpdate: undefined, - nextUpdateStampMs: AbsoluteTime.getStampMsNever(), - nextRefreshCheckStampMs: AbsoluteTime.getStampMsNever(), + nextUpdateStamp: timestampPreciseToDb( + AbsoluteTime.toPreciseTimestamp(AbsoluteTime.never()), + ), + nextRefreshCheckStamp: timestampPreciseToDb( + AbsoluteTime.toPreciseTimestamp(AbsoluteTime.never()), + ), lastKeysEtag: undefined, }; await tx.exchanges.put(r); @@ -445,13 +458,19 @@ async function downloadExchangeKeysInfo( isRevoked: false, value: Amounts.stringify(value), currency: value.currency, - stampExpireDeposit: denomIn.stamp_expire_deposit, - stampExpireLegal: denomIn.stamp_expire_legal, - stampExpireWithdraw: denomIn.stamp_expire_withdraw, - stampStart: denomIn.stamp_start, + stampExpireDeposit: timestampProtocolToDb( + denomIn.stamp_expire_deposit, + ), + stampExpireLegal: timestampProtocolToDb(denomIn.stamp_expire_legal), + stampExpireWithdraw: timestampProtocolToDb( + denomIn.stamp_expire_withdraw, + ), + stampStart: timestampProtocolToDb(denomIn.stamp_start), verificationStatus: DenominationVerificationStatus.Unverified, masterSig: denomIn.master_sig, - listIssueDate: exchangeKeysJsonUnchecked.list_issue_date, + listIssueDate: timestampProtocolToDb( + exchangeKeysJsonUnchecked.list_issue_date, + ), fees: { feeDeposit: Amounts.stringify(denomGroup.fee_deposit), feeRefresh: Amounts.stringify(denomGroup.fee_refresh), @@ -613,7 +632,9 @@ export async function updateExchangeFromUrlHandler( !forceNow && exchangeDetails !== undefined && !AbsoluteTime.isExpired( - AbsoluteTime.fromStampMs(exchange.nextUpdateStampMs), + AbsoluteTime.fromPreciseTimestamp( + timestampPreciseFromDb(exchange.nextUpdateStamp), + ), ) ) { logger.trace("using existing exchange info"); @@ -753,17 +774,21 @@ export async function updateExchangeFromUrlHandler( if (existingDetails?.rowId) { newDetails.rowId = existingDetails.rowId; } - r.lastUpdate = TalerPreciseTimestamp.now(); - r.nextUpdateStampMs = AbsoluteTime.toStampMs( - AbsoluteTime.fromProtocolTimestamp(keysInfo.expiry), + r.lastUpdate = timestampPreciseToDb(TalerPreciseTimestamp.now()); + r.nextUpdateStamp = timestampPreciseToDb( + AbsoluteTime.toPreciseTimestamp( + AbsoluteTime.fromProtocolTimestamp(keysInfo.expiry), + ), ); // New denominations might be available. - r.nextRefreshCheckStampMs = AbsoluteTime.getStampMsNow(); + r.nextRefreshCheckStamp = timestampPreciseToDb( + TalerPreciseTimestamp.now(), + ); if (detailsPointerChanged) { r.detailsPointer = { currency: newDetails.currency, masterPublicKey: newDetails.masterPublicKey, - updateClock: TalerPreciseTimestamp.now(), + updateClock: timestampPreciseToDb(TalerPreciseTimestamp.now()), }; } await tx.exchanges.put(r); @@ -776,9 +801,9 @@ export async function updateExchangeFromUrlHandler( exchangeDetailsRowId: drRowId.key, masterSig: sk.master_sig, signkeyPub: sk.key, - stampEnd: sk.stamp_end, - stampExpire: sk.stamp_expire, - stampStart: sk.stamp_start, + stampEnd: timestampProtocolToDb(sk.stamp_end), + stampExpire: timestampProtocolToDb(sk.stamp_expire), + stampStart: timestampProtocolToDb(sk.stamp_start), }); } @@ -813,7 +838,7 @@ export async function updateExchangeFromUrlHandler( ); } } else { - x.listIssueDate = keysInfo.listIssueDate; + x.listIssueDate = timestampProtocolToDb(keysInfo.listIssueDate); if (!x.isOffered) { x.isOffered = true; logger.info( diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts b/packages/taler-wallet-core/src/operations/pay-merchant.ts index fe0cbeda0..157541ed3 100644 --- a/packages/taler-wallet-core/src/operations/pay-merchant.ts +++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts @@ -103,6 +103,9 @@ import { RefundGroupStatus, RefundItemRecord, RefundItemStatus, + timestampPreciseToDb, + timestampProtocolFromDb, + timestampProtocolToDb, } from "../index.js"; import { EXCHANGE_COINS_LOCK, @@ -114,7 +117,7 @@ import { checkDbInvariant } from "../util/invariants.js"; import { GetReadOnlyAccess } from "../util/query.js"; import { constructTaskIdentifier, - RetryInfo, + DbRetryInfo, runLongpollAsync, runTaskWithErrorReporting, spendCoins, @@ -216,11 +219,13 @@ async function failProposalPermanently( notifyTransition(ws, transactionId, transitionInfo); } -function getProposalRequestTimeout(retryInfo?: RetryInfo): Duration { +function getProposalRequestTimeout(retryInfo?: DbRetryInfo): Duration { return Duration.clamp({ lower: Duration.fromSpec({ seconds: 1 }), upper: Duration.fromSpec({ seconds: 60 }), - value: retryInfo ? RetryInfo.getDuration(retryInfo) : Duration.fromSpec({}), + value: retryInfo + ? DbRetryInfo.getDuration(retryInfo) + : Duration.fromSpec({}), }); } @@ -644,7 +649,7 @@ async function createPurchase( noncePriv: priv, noncePub: pub, claimToken, - timestamp: TalerPreciseTimestamp.now(), + timestamp: timestampPreciseToDb(TalerPreciseTimestamp.now()), merchantBaseUrl, orderId, proposalId: proposalId, @@ -717,7 +722,7 @@ async function storeFirstPaySuccess( if (purchase.purchaseStatus === PurchaseStatus.PendingPaying) { purchase.purchaseStatus = PurchaseStatus.Done; } - purchase.timestampFirstSuccessfulPay = now; + purchase.timestampFirstSuccessfulPay = timestampPreciseToDb(now); purchase.lastSessionId = sessionId; purchase.merchantPaySig = payResponse.sig; purchase.posConfirmation = payResponse.pos_confirmation; @@ -737,8 +742,10 @@ async function storeFirstPaySuccess( const ar = Duration.fromTalerProtocolDuration(protoAr); logger.info("auto_refund present"); purchase.purchaseStatus = PurchaseStatus.PendingQueryingAutoRefund; - purchase.autoRefundDeadline = AbsoluteTime.toProtocolTimestamp( - AbsoluteTime.addDuration(AbsoluteTime.now(), ar), + purchase.autoRefundDeadline = timestampProtocolToDb( + AbsoluteTime.toProtocolTimestamp( + AbsoluteTime.addDuration(AbsoluteTime.now(), ar), + ), ); } await tx.purchases.put(purchase); @@ -941,7 +948,9 @@ async function unblockBackup( .forEachAsync(async (bp) => { bp.state = { tag: BackupProviderStateTag.Ready, - nextBackupTimestamp: TalerPreciseTimestamp.now(), + nextBackupTimestamp: timestampPreciseToDb( + TalerPreciseTimestamp.now(), + ), }; tx.backupProviders.put(bp); }); @@ -1447,7 +1456,7 @@ export async function confirmPay( totalPayCost: Amounts.stringify(payCostInfo), }; p.lastSessionId = sessionId; - p.timestampAccept = TalerPreciseTimestamp.now(); + p.timestampAccept = timestampPreciseToDb(TalerPreciseTimestamp.now()); p.purchaseStatus = PurchaseStatus.PendingPaying; await tx.purchases.put(p); await spendCoins(ws, tx, { @@ -2340,7 +2349,9 @@ async function processPurchaseAutoRefund( if ( !purchase.autoRefundDeadline || AbsoluteTime.isExpired( - AbsoluteTime.fromProtocolTimestamp(purchase.autoRefundDeadline), + AbsoluteTime.fromProtocolTimestamp( + timestampProtocolFromDb(purchase.autoRefundDeadline), + ), ) ) { const transitionInfo = await ws.db @@ -2791,7 +2802,7 @@ async function storeRefunds( proposalId: purchase.proposalId, refundGroupId: newRefundGroupId, status: RefundGroupStatus.Pending, - timestampCreated: now, + timestampCreated: timestampPreciseToDb(now), amountEffective: Amounts.stringify( Amounts.zeroOfCurrency(currency), ), @@ -2801,8 +2812,8 @@ async function storeRefunds( const status: RefundItemStatus = getItemStatus(rf); const newItem: RefundItemRecord = { coinPub: rf.coin_pub, - executionTime: rf.execution_time, - obtainedTime: now, + executionTime: timestampProtocolToDb(rf.execution_time), + obtainedTime: timestampPreciseToDb(now), refundAmount: rf.refund_amount, refundGroupId: newGroup.refundGroupId, rtxid: rf.rtransaction_id, diff --git a/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts index 0355eb152..54b78957f 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts @@ -60,6 +60,9 @@ import { PeerPullPaymentCreditStatus, WithdrawalGroupStatus, WithdrawalRecordType, + timestampOptionalPreciseFromDb, + timestampPreciseFromDb, + timestampPreciseToDb, updateExchangeFromUrl, } from "../index.js"; import { InternalWalletState } from "../internal-wallet-state.js"; @@ -389,18 +392,20 @@ async function handlePeerPullCreditCreatePurse( const econtractResp = await ws.cryptoApi.encryptContractForDeposit({ contractPriv: pullIni.contractPriv, contractPub: pullIni.contractPub, - contractTerms: contractTermsRecord, + contractTerms: contractTermsRecord.contractTermsRaw, pursePriv: pullIni.pursePriv, pursePub: pullIni.pursePub, nonce: pullIni.contractEncNonce, }); + const mergeTimestamp = timestampPreciseFromDb(pullIni.mergeTimestamp); + const purseExpiration = contractTerms.purse_expiration; const sigRes = await ws.cryptoApi.signReservePurseCreate({ contractTermsHash: pullIni.contractTermsHash, flags: WalletAccountMergeFlags.CreateWithPurseFee, mergePriv: pullIni.mergePriv, - mergeTimestamp: TalerPreciseTimestamp.round(pullIni.mergeTimestamp), + mergeTimestamp: TalerPreciseTimestamp.round(mergeTimestamp), purseAmount: pullIni.amount, purseExpiration: purseExpiration, purseFee: purseFee, @@ -412,7 +417,7 @@ async function handlePeerPullCreditCreatePurse( const reservePurseReqBody: ExchangeReservePurseRequest = { merge_sig: sigRes.mergeSig, - merge_timestamp: TalerPreciseTimestamp.round(pullIni.mergeTimestamp), + merge_timestamp: TalerPreciseTimestamp.round(mergeTimestamp), h_contract_terms: pullIni.contractTermsHash, merge_pub: pullIni.mergePub, min_age: 0, @@ -695,11 +700,17 @@ async function getPreferredExchangeForCurrency( if (candidate.lastWithdrawal && !e.lastWithdrawal) { continue; } - if (candidate.lastWithdrawal && e.lastWithdrawal) { + const exchangeLastWithdrawal = timestampOptionalPreciseFromDb( + e.lastWithdrawal, + ); + const candidateLastWithdrawal = timestampOptionalPreciseFromDb( + candidate.lastWithdrawal, + ); + if (exchangeLastWithdrawal && candidateLastWithdrawal) { if ( AbsoluteTime.cmp( - AbsoluteTime.fromPreciseTimestamp(e.lastWithdrawal), - AbsoluteTime.fromPreciseTimestamp(candidate.lastWithdrawal), + AbsoluteTime.fromPreciseTimestamp(exchangeLastWithdrawal), + AbsoluteTime.fromPreciseTimestamp(candidateLastWithdrawal), ) > 0 ) { candidate = e; @@ -741,8 +752,6 @@ export async function initiatePeerPullPayment( exchangeBaseUrl: exchangeBaseUrl, }); - const mergeTimestamp = TalerPreciseTimestamp.now(); - const pursePair = await ws.cryptoApi.createEddsaKeypair({}); const mergePair = await ws.cryptoApi.createEddsaKeypair({}); @@ -766,6 +775,8 @@ export async function initiatePeerPullPayment( undefined, ); + const mergeTimestamp = TalerPreciseTimestamp.now(); + const transitionInfo = await ws.db .mktx((x) => [x.peerPullCredit, x.contractTerms]) .runReadWrite(async (tx) => { @@ -778,7 +789,7 @@ export async function initiatePeerPullPayment( mergePriv: mergePair.priv, mergePub: mergePair.pub, status: PeerPullPaymentCreditStatus.PendingCreatePurse, - mergeTimestamp, + mergeTimestamp: timestampPreciseToDb(mergeTimestamp), contractEncNonce, mergeReserveRowId: mergeReserveRowId, contractPriv: contractKeyPair.priv, diff --git a/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts index 5bcfa3418..48cbf574f 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts @@ -59,6 +59,7 @@ import { PendingTaskType, RefreshOperationStatus, createRefreshGroup, + timestampPreciseToDb, } from "../index.js"; import { assertUnreachable } from "../util/assertUnreachable.js"; import { checkLogicInvariant } from "../util/invariants.js"; @@ -595,7 +596,7 @@ export async function preparePeerPullDebit( contractPriv: contractPriv, exchangeBaseUrl: exchangeBaseUrl, pursePub: pursePub, - timestampCreated: TalerPreciseTimestamp.now(), + timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()), contractTermsHash, amount: contractTerms.amount, status: PeerPullDebitRecordStatus.DialogProposed, diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts index 89d9e3b49..e4698c203 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts @@ -59,6 +59,7 @@ import { PendingTaskType, WithdrawalGroupStatus, WithdrawalRecordType, + timestampPreciseToDb, } from "../index.js"; import { assertUnreachable } from "../util/assertUnreachable.js"; import { checkDbInvariant } from "../util/invariants.js"; @@ -129,12 +130,10 @@ export async function preparePeerPushCredit( amountEffective: existing.existingPushInc.estimatedAmountEffective, amountRaw: existing.existingContractTerms.amount, contractTerms: existing.existingContractTerms, - peerPushCreditId: - existing.existingPushInc.peerPushCreditId, + peerPushCreditId: existing.existingPushInc.peerPushCreditId, transactionId: constructTransactionIdentifier({ tag: TransactionType.PeerPushCredit, - peerPushCreditId: - existing.existingPushInc.peerPushCreditId, + peerPushCreditId: existing.existingPushInc.peerPushCreditId, }), }; } @@ -196,7 +195,7 @@ export async function preparePeerPushCredit( exchangeBaseUrl: exchangeBaseUrl, mergePriv: dec.mergePriv, pursePub: pursePub, - timestamp: TalerPreciseTimestamp.now(), + timestamp: timestampPreciseToDb(TalerPreciseTimestamp.now()), contractTermsHash, status: PeerPushCreditStatus.DialogProposed, withdrawalGroupId, @@ -263,16 +262,11 @@ async function longpollKycStatus( const transitionInfo = await ws.db .mktx((x) => [x.peerPushCredit]) .runReadWrite(async (tx) => { - const peerInc = await tx.peerPushCredit.get( - peerPushCreditId, - ); + const peerInc = await tx.peerPushCredit.get(peerPushCreditId); if (!peerInc) { return; } - if ( - peerInc.status !== - PeerPushCreditStatus.PendingMergeKycRequired - ) { + if (peerInc.status !== PeerPushCreditStatus.PendingMergeKycRequired) { return; } const oldTxState = computePeerPushCreditTransactionState(peerInc); @@ -333,9 +327,7 @@ async function processPeerPushCreditKycRequired( const { transitionInfo, result } = await ws.db .mktx((x) => [x.peerPushCredit]) .runReadWrite(async (tx) => { - const peerInc = await tx.peerPushCredit.get( - peerPushCreditId, - ); + const peerInc = await tx.peerPushCredit.get(peerPushCreditId); if (!peerInc) { return { transitionInfo: undefined, @@ -466,9 +458,7 @@ async function handlePendingMerge( x.exchangeDetails, ]) .runReadWrite(async (tx) => { - const peerInc = await tx.peerPushCredit.get( - peerPushCreditId, - ); + const peerInc = await tx.peerPushCredit.get(peerPushCreditId); if (!peerInc) { return undefined; } @@ -520,9 +510,7 @@ async function handlePendingWithdrawing( const transitionInfo = await ws.db .mktx((x) => [x.peerPushCredit, x.withdrawalGroups]) .runReadWrite(async (tx) => { - const ppi = await tx.peerPushCredit.get( - peerInc.peerPushCreditId, - ); + const ppi = await tx.peerPushCredit.get(peerInc.peerPushCreditId); if (!ppi) { finished = true; return; @@ -631,9 +619,7 @@ export async function confirmPeerPushCredit( } peerPushCreditId = parsedTx.peerPushCreditId; } else { - throw Error( - "no transaction ID (or deprecated peerPushCreditId) provided", - ); + throw Error("no transaction ID (or deprecated peerPushCreditId) provided"); } await ws.db @@ -683,9 +669,7 @@ export async function suspendPeerPushCreditTransaction( const transitionInfo = await ws.db .mktx((x) => [x.peerPushCredit]) .runReadWrite(async (tx) => { - const pushCreditRec = await tx.peerPushCredit.get( - peerPushCreditId, - ); + const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId); if (!pushCreditRec) { logger.warn(`peer push credit ${peerPushCreditId} not found`); return; @@ -746,9 +730,7 @@ export async function abortPeerPushCreditTransaction( const transitionInfo = await ws.db .mktx((x) => [x.peerPushCredit]) .runReadWrite(async (tx) => { - const pushCreditRec = await tx.peerPushCredit.get( - peerPushCreditId, - ); + const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId); if (!pushCreditRec) { logger.warn(`peer push credit ${peerPushCreditId} not found`); return; @@ -820,9 +802,7 @@ export async function resumePeerPushCreditTransaction( const transitionInfo = await ws.db .mktx((x) => [x.peerPushCredit]) .runReadWrite(async (tx) => { - const pushCreditRec = await tx.peerPushCredit.get( - peerPushCreditId, - ); + const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId); if (!pushCreditRec) { logger.warn(`peer push credit ${peerPushCreditId} not found`); return; diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts index e80ffc059..50ae8d41b 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts @@ -55,10 +55,14 @@ import { PeerPushDebitStatus, RefreshOperationStatus, createRefreshGroup, + timestampPreciseToDb, + timestampProtocolFromDb, + timestampProtocolToDb, } from "../index.js"; import { InternalWalletState } from "../internal-wallet-state.js"; import { PendingTaskType } from "../pending-types.js"; import { assertUnreachable } from "../util/assertUnreachable.js"; +import { PeerCoinRepair, selectPeerCoins } from "../util/coinSelection.js"; import { checkLogicInvariant } from "../util/invariants.js"; import { TaskRunResult, @@ -77,7 +81,6 @@ import { notifyTransition, stopLongpolling, } from "./transactions.js"; -import { PeerCoinRepair, selectPeerCoins } from "../util/coinSelection.js"; const logger = new Logger("pay-peer-push-debit.ts"); @@ -207,7 +210,7 @@ async function processPeerPushDebitCreateReserve( mergePub: peerPushInitiation.mergePub, minAge: 0, purseAmount: peerPushInitiation.amount, - purseExpiration, + purseExpiration: timestampProtocolFromDb(purseExpiration), pursePriv: peerPushInitiation.pursePriv, }); @@ -242,8 +245,6 @@ async function processPeerPushDebitCreateReserve( hash(decodeCrock(econtractResp.econtract.econtract)), ); - logger.info(`econtract hash: ${econtractHash}`); - const createPurseUrl = new URL( `purses/${peerPushInitiation.pursePub}/create`, peerPushInitiation.exchangeBaseUrl, @@ -254,7 +255,7 @@ async function processPeerPushDebitCreateReserve( merge_pub: peerPushInitiation.mergePub, purse_sig: purseSigResp.sig, h_contract_terms: hContractTerms, - purse_expiration: purseExpiration, + purse_expiration: timestampProtocolFromDb(purseExpiration), deposits: depositSigsResp.deposits, min_age: 0, econtract: econtractResp.econtract, @@ -646,7 +647,6 @@ export async function initiatePeerPushDebit( // we might want to mark the coins as used and spend them // after we've been able to create the purse. await spendCoins(ws, tx, { - // allocationId: `txn:peer-push-debit:${pursePair.pub}`, allocationId: constructTransactionIdentifier({ tag: TransactionType.PeerPushDebit, pursePub: pursePair.pub, @@ -666,10 +666,10 @@ export async function initiatePeerPushDebit( exchangeBaseUrl: sel.exchangeBaseUrl, mergePriv: mergePair.priv, mergePub: mergePair.pub, - purseExpiration: purseExpiration, + purseExpiration: timestampProtocolToDb(purseExpiration), pursePriv: pursePair.priv, pursePub: pursePair.pub, - timestampCreated: TalerPreciseTimestamp.now(), + timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()), status: PeerPushDebitStatus.PendingCreatePurse, contractEncNonce, coinSel: { diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts index 6115f848b..1819aa1b8 100644 --- a/packages/taler-wallet-core/src/operations/pending.ts +++ b/packages/taler-wallet-core/src/operations/pending.ts @@ -21,42 +21,46 @@ /** * Imports. */ +import { GlobalIDB } from "@gnu-taler/idb-bridge"; +import { AbsoluteTime, TransactionRecordFilter } from "@gnu-taler/taler-util"; import { - PurchaseStatus, - WalletStoresV1, BackupProviderStateTag, - RefreshCoinStatus, - PeerPushDebitStatus, - PeerPullDebitRecordStatus, - PeerPushCreditStatus, - PeerPullPaymentCreditStatus, - WithdrawalGroupStatus, - RewardRecordStatus, - DepositOperationStatus, - RefreshGroupRecord, - WithdrawalGroupRecord, + DepositElementStatus, DepositGroupRecord, - RewardRecord, - PurchaseRecord, + DepositOperationStatus, + ExchangeEntryDbUpdateStatus, PeerPullCreditRecord, + PeerPullDebitRecordStatus, + PeerPullPaymentCreditStatus, PeerPullPaymentIncomingRecord, + PeerPushCreditStatus, PeerPushDebitRecord, + PeerPushDebitStatus, PeerPushPaymentIncomingRecord, + PurchaseRecord, + PurchaseStatus, + RefreshCoinStatus, + RefreshGroupRecord, + RefreshOperationStatus, RefundGroupRecord, RefundGroupStatus, - ExchangeEntryDbUpdateStatus, - RefreshOperationStatus, - DepositElementStatus, + RewardRecord, + RewardRecordStatus, + WalletStoresV1, + WithdrawalGroupRecord, + WithdrawalGroupStatus, + timestampAbsoluteFromDb, + timestampOptionalAbsoluteFromDb, + timestampPreciseFromDb, + timestampPreciseToDb, } from "../db.js"; +import { InternalWalletState } from "../internal-wallet-state.js"; import { PendingOperationsResponse, PendingTaskType, TaskId, } from "../pending-types.js"; -import { AbsoluteTime, TransactionRecordFilter } from "@gnu-taler/taler-util"; -import { InternalWalletState } from "../internal-wallet-state.js"; import { GetReadOnlyAccess } from "../util/query.js"; -import { GlobalIDB } from "@gnu-taler/idb-bridge"; import { TaskIdentifiers } from "./common.js"; function getPendingCommon( @@ -99,12 +103,14 @@ async function gatherExchangePending( } const opTag = TaskIdentifiers.forExchangeUpdate(exch); let opr = await tx.operationRetries.get(opTag); - const timestampDue = - opr?.retryInfo.nextRetry ?? - AbsoluteTime.fromStampMs(exch.nextUpdateStampMs); + const timestampDue = opr?.retryInfo.nextRetry ?? exch.nextRefreshCheckStamp; resp.pendingOperations.push({ type: PendingTaskType.ExchangeUpdate, - ...getPendingCommon(ws, opTag, timestampDue), + ...getPendingCommon( + ws, + opTag, + AbsoluteTime.fromPreciseTimestamp(timestampPreciseFromDb(timestampDue)), + ), givesLifeness: false, exchangeBaseUrl: exch.baseUrl, lastError: opr?.lastError, @@ -115,8 +121,16 @@ async function gatherExchangePending( if (!opr?.lastError) { resp.pendingOperations.push({ type: PendingTaskType.ExchangeCheckRefresh, - ...getPendingCommon(ws, opTag, timestampDue), - timestampDue: AbsoluteTime.fromStampMs(exch.nextRefreshCheckStampMs), + ...getPendingCommon( + ws, + opTag, + AbsoluteTime.fromPreciseTimestamp( + timestampPreciseFromDb(timestampDue), + ), + ), + timestampDue: AbsoluteTime.fromPreciseTimestamp( + timestampPreciseFromDb(exch.nextRefreshCheckStamp), + ), givesLifeness: false, exchangeBaseUrl: exch.baseUrl, }); @@ -165,7 +179,9 @@ async function gatherRefreshPending( } const opId = TaskIdentifiers.forRefresh(r); const retryRecord = await tx.operationRetries.get(opId); - const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(); + const timestampDue = + timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ?? + AbsoluteTime.now(); resp.pendingOperations.push({ type: PendingTaskType.Refresh, ...getPendingCommon(ws, opId, timestampDue), @@ -222,8 +238,8 @@ async function gatherWithdrawalPending( opr = { id: opTag, retryInfo: { - firstTry: now, - nextRetry: now, + firstTry: timestampPreciseToDb(AbsoluteTime.toPreciseTimestamp(now)), + nextRetry: timestampPreciseToDb(AbsoluteTime.toPreciseTimestamp(now)), retryCounter: 0, }, }; @@ -233,7 +249,8 @@ async function gatherWithdrawalPending( ...getPendingCommon( ws, opTag, - opr.retryInfo?.nextRetry ?? AbsoluteTime.now(), + timestampOptionalAbsoluteFromDb(opr.retryInfo?.nextRetry) ?? + AbsoluteTime.now(), ), givesLifeness: true, withdrawalGroupId: wsr.withdrawalGroupId, @@ -285,7 +302,9 @@ async function gatherDepositPending( } const opId = TaskIdentifiers.forDeposit(dg); const retryRecord = await tx.operationRetries.get(opId); - const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(); + const timestampDue = + timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ?? + AbsoluteTime.now(); resp.pendingOperations.push({ type: PendingTaskType.Deposit, ...getPendingCommon(ws, opId, timestampDue), @@ -330,13 +349,15 @@ async function gatherRewardPending( await iterRecordsForReward(tx, { onlyState: "nonfinal" }, async (tip) => { const opId = TaskIdentifiers.forTipPickup(tip); const retryRecord = await tx.operationRetries.get(opId); - const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(); + const timestampDue = + timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ?? + AbsoluteTime.now(); if (tip.acceptedTimestamp) { resp.pendingOperations.push({ type: PendingTaskType.RewardPickup, ...getPendingCommon(ws, opId, timestampDue), givesLifeness: true, - timestampDue: retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(), + timestampDue, merchantBaseUrl: tip.merchantBaseUrl, tipId: tip.walletRewardId, merchantTipId: tip.merchantRewardId, @@ -390,7 +411,9 @@ async function gatherPurchasePending( await iterRecordsForPurchase(tx, { onlyState: "nonfinal" }, async (pr) => { const opId = TaskIdentifiers.forPay(pr); const retryRecord = await tx.operationRetries.get(opId); - const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(); + const timestampDue = + timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ?? + AbsoluteTime.now(); resp.pendingOperations.push({ type: PendingTaskType.Purchase, ...getPendingCommon(ws, opId, timestampDue), @@ -419,7 +442,9 @@ async function gatherRecoupPending( } const opId = TaskIdentifiers.forRecoup(rg); const retryRecord = await tx.operationRetries.get(opId); - const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(); + const timestampDue = + timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ?? + AbsoluteTime.now(); resp.pendingOperations.push({ type: PendingTaskType.Recoup, ...getPendingCommon(ws, opId, timestampDue), @@ -444,7 +469,7 @@ async function gatherBackupPending( const opId = TaskIdentifiers.forBackup(bp); const retryRecord = await tx.operationRetries.get(opId); if (bp.state.tag === BackupProviderStateTag.Ready) { - const timestampDue = AbsoluteTime.fromPreciseTimestamp( + const timestampDue = timestampAbsoluteFromDb( bp.state.nextBackupTimestamp, ); resp.pendingOperations.push({ @@ -456,7 +481,8 @@ async function gatherBackupPending( }); } else if (bp.state.tag === BackupProviderStateTag.Retrying) { const timestampDue = - retryRecord?.retryInfo?.nextRetry ?? AbsoluteTime.now(); + timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo?.nextRetry) ?? + AbsoluteTime.now(); resp.pendingOperations.push({ type: PendingTaskType.Backup, ...getPendingCommon(ws, opId, timestampDue), @@ -503,7 +529,8 @@ async function gatherPeerPullInitiationPending( const opId = TaskIdentifiers.forPeerPullPaymentInitiation(pi); const retryRecord = await tx.operationRetries.get(opId); const timestampDue = - retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(); + timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ?? + AbsoluteTime.now(); resp.pendingOperations.push({ type: PendingTaskType.PeerPullCredit, ...getPendingCommon(ws, opId, timestampDue), @@ -549,7 +576,8 @@ async function gatherPeerPullDebitPending( const opId = TaskIdentifiers.forPeerPullPaymentDebit(pi); const retryRecord = await tx.operationRetries.get(opId); const timestampDue = - retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(); + timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ?? + AbsoluteTime.now(); resp.pendingOperations.push({ type: PendingTaskType.PeerPullDebit, ...getPendingCommon(ws, opId, timestampDue), @@ -595,7 +623,8 @@ async function gatherPeerPushInitiationPending( const opId = TaskIdentifiers.forPeerPushPaymentInitiation(pi); const retryRecord = await tx.operationRetries.get(opId); const timestampDue = - retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(); + timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ?? + AbsoluteTime.now(); resp.pendingOperations.push({ type: PendingTaskType.PeerPushDebit, ...getPendingCommon(ws, opId, timestampDue), @@ -645,7 +674,8 @@ async function gatherPeerPushCreditPending( const opId = TaskIdentifiers.forPeerPushCredit(pi); const retryRecord = await tx.operationRetries.get(opId); const timestampDue = - retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(); + timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ?? + AbsoluteTime.now(); resp.pendingOperations.push({ type: PendingTaskType.PeerPushCredit, ...getPendingCommon(ws, opId, timestampDue), diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts index 6a18e5de6..782e98d1c 100644 --- a/packages/taler-wallet-core/src/operations/recoup.ts +++ b/packages/taler-wallet-core/src/operations/recoup.ts @@ -47,6 +47,7 @@ import { WithdrawCoinSource, WithdrawalGroupStatus, WithdrawalRecordType, + timestampPreciseToDb, } from "../db.js"; import { InternalWalletState } from "../internal-wallet-state.js"; import { checkDbInvariant } from "../util/invariants.js"; @@ -391,7 +392,7 @@ export async function processRecoupGroup( if (!rg2) { return; } - rg2.timestampFinished = TalerPreciseTimestamp.now(); + rg2.timestampFinished = timestampPreciseToDb(TalerPreciseTimestamp.now()); if (rg2.scheduleRefreshCoins.length > 0) { const refreshGroupId = await createRefreshGroup( ws, @@ -424,7 +425,7 @@ export async function createRecoupGroup( exchangeBaseUrl: exchangeBaseUrl, coinPubs: coinPubs, timestampFinished: undefined, - timestampStarted: TalerPreciseTimestamp.now(), + timestampStarted: timestampPreciseToDb(TalerPreciseTimestamp.now()), recoupFinishedPerCoin: coinPubs.map(() => false), scheduleRefreshCoins: [], }; diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index 75adbc860..95aedbbd6 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -80,6 +80,8 @@ import { isWithdrawableDenom, PendingTaskType, RefreshSessionRecord, + timestampPreciseToDb, + timestampProtocolFromDb, } from "../index.js"; import { EXCHANGE_COINS_LOCK, @@ -157,10 +159,10 @@ function updateGroupStatus(rg: RefreshGroupRecord): { final: boolean } { ); if (allFinal) { if (anyFailed) { - rg.timestampFinished = TalerPreciseTimestamp.now(); + rg.timestampFinished = timestampPreciseToDb(TalerPreciseTimestamp.now()); rg.operationStatus = RefreshOperationStatus.Failed; } else { - rg.timestampFinished = TalerPreciseTimestamp.now(); + rg.timestampFinished = timestampPreciseToDb(TalerPreciseTimestamp.now()); rg.operationStatus = RefreshOperationStatus.Finished; } return { final: true }; @@ -1099,12 +1101,14 @@ export async function createRefreshGroup( expectedOutputPerCoin: estimatedOutputPerCoin.map((x) => Amounts.stringify(x), ), - timestampCreated: TalerPreciseTimestamp.now(), + timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()), }; if (oldCoinPubs.length == 0) { logger.warn("created refresh group with zero coins"); - refreshGroup.timestampFinished = TalerPreciseTimestamp.now(); + refreshGroup.timestampFinished = timestampPreciseToDb( + TalerPreciseTimestamp.now(), + ); refreshGroup.operationStatus = RefreshOperationStatus.Finished; } @@ -1122,10 +1126,10 @@ export async function createRefreshGroup( */ function getAutoRefreshCheckThreshold(d: DenominationRecord): AbsoluteTime { const expireWithdraw = AbsoluteTime.fromProtocolTimestamp( - d.stampExpireWithdraw, + timestampProtocolFromDb(d.stampExpireWithdraw), ); const expireDeposit = AbsoluteTime.fromProtocolTimestamp( - d.stampExpireDeposit, + timestampProtocolFromDb(d.stampExpireDeposit), ); const delta = AbsoluteTime.difference(expireWithdraw, expireDeposit); const deltaDiv = durationMul(delta, 0.75); @@ -1137,10 +1141,10 @@ function getAutoRefreshCheckThreshold(d: DenominationRecord): AbsoluteTime { */ function getAutoRefreshExecuteThreshold(d: DenominationRecord): AbsoluteTime { const expireWithdraw = AbsoluteTime.fromProtocolTimestamp( - d.stampExpireWithdraw, + timestampProtocolFromDb(d.stampExpireWithdraw), ); const expireDeposit = AbsoluteTime.fromProtocolTimestamp( - d.stampExpireDeposit, + timestampProtocolFromDb(d.stampExpireDeposit), ); const delta = AbsoluteTime.difference(expireWithdraw, expireDeposit); const deltaDiv = durationMul(delta, 0.5); @@ -1224,8 +1228,9 @@ export async function autoRefresh( logger.trace( `next refresh check at ${AbsoluteTime.toIsoString(minCheckThreshold)}`, ); - exchange.nextRefreshCheckStampMs = - AbsoluteTime.toStampMs(minCheckThreshold); + exchange.nextRefreshCheckStamp = timestampPreciseToDb( + AbsoluteTime.toPreciseTimestamp(minCheckThreshold), + ); await tx.exchanges.put(exchange); }); return TaskRunResult.finished(); diff --git a/packages/taler-wallet-core/src/operations/reward.ts b/packages/taler-wallet-core/src/operations/reward.ts index 6ae021174..4e16d977d 100644 --- a/packages/taler-wallet-core/src/operations/reward.ts +++ b/packages/taler-wallet-core/src/operations/reward.ts @@ -50,6 +50,10 @@ import { DenominationRecord, RewardRecord, RewardRecordStatus, + timestampPreciseFromDb, + timestampPreciseToDb, + timestampProtocolFromDb, + timestampProtocolToDb, } from "../db.js"; import { makeErrorDetail } from "@gnu-taler/taler-util"; import { InternalWalletState } from "../internal-wallet-state.js"; @@ -199,11 +203,11 @@ export async function prepareTip( acceptedTimestamp: undefined, status: RewardRecordStatus.DialogAccept, rewardAmountRaw: Amounts.stringify(amount), - rewardExpiration: tipPickupStatus.expiration, + rewardExpiration: timestampProtocolToDb(tipPickupStatus.expiration), exchangeBaseUrl: tipPickupStatus.exchange_url, next_url: tipPickupStatus.next_url, merchantBaseUrl: res.merchantBaseUrl, - createdTimestamp: TalerPreciseTimestamp.now(), + createdTimestamp: timestampPreciseToDb(TalerPreciseTimestamp.now()), merchantRewardId: res.merchantRewardId, rewardAmountEffective: Amounts.stringify(selectedDenoms.totalCoinValue), denomsSel: selectedDenoms, @@ -229,7 +233,7 @@ export async function prepareTip( rewardAmountRaw: Amounts.stringify(tipRecord.rewardAmountRaw), exchangeBaseUrl: tipRecord.exchangeBaseUrl, merchantBaseUrl: tipRecord.merchantBaseUrl, - expirationTimestamp: tipRecord.rewardExpiration, + expirationTimestamp: timestampProtocolFromDb(tipRecord.rewardExpiration), rewardAmountEffective: Amounts.stringify(tipRecord.rewardAmountEffective), walletRewardId: tipRecord.walletRewardId, transactionId, @@ -300,13 +304,16 @@ export async function processTip( } const tipStatusUrl = new URL( - `tips/${tipRecord.merchantRewardId}/pickup`, + `rewards/${tipRecord.merchantRewardId}/pickup`, tipRecord.merchantBaseUrl, ); const req = { planchets: planchetsDetail }; logger.trace(`sending tip request: ${j2s(req)}`); - const merchantResp = await ws.http.postJson(tipStatusUrl.href, req); + const merchantResp = await ws.http.fetch(tipStatusUrl.href, { + method: "POST", + body: req, + }); logger.trace(`got tip response, status ${merchantResp.status}`); @@ -411,7 +418,7 @@ export async function processTip( return; } const oldTxState = computeRewardTransactionStatus(tr); - tr.pickedUpTimestamp = TalerPreciseTimestamp.now(); + tr.pickedUpTimestamp = timestampPreciseToDb(TalerPreciseTimestamp.now()); tr.status = RewardRecordStatus.Done; await tx.rewards.put(tr); const newTxState = computeRewardTransactionStatus(tr); @@ -448,7 +455,9 @@ export async function acceptTip( return { tipRecord }; } const oldTxState = computeRewardTransactionStatus(tipRecord); - tipRecord.acceptedTimestamp = TalerPreciseTimestamp.now(); + tipRecord.acceptedTimestamp = timestampPreciseToDb( + TalerPreciseTimestamp.now(), + ); tipRecord.status = RewardRecordStatus.PendingPickup; await tx.rewards.put(tipRecord); const newTxState = computeRewardTransactionStatus(tipRecord); diff --git a/packages/taler-wallet-core/src/operations/testing.ts b/packages/taler-wallet-core/src/operations/testing.ts index f71d842c7..607d03470 100644 --- a/packages/taler-wallet-core/src/operations/testing.ts +++ b/packages/taler-wallet-core/src/operations/testing.ts @@ -23,12 +23,16 @@ import { ConfirmPayResultType, Duration, IntegrationTestV2Args, + j2s, Logger, NotificationType, + RegisterAccountRequest, stringToBytes, + TalerCorebankApiClient, TestPayResult, TransactionMajorState, TransactionMinorState, + TransactionState, TransactionType, WithdrawTestBalanceRequest, } from "@gnu-taler/taler-util"; @@ -73,16 +77,6 @@ import { getTransactionById, getTransactions } from "./transactions.js"; const logger = new Logger("operations/testing.ts"); -interface BankUser { - username: string; - password: string; -} - -interface BankWithdrawalResponse { - taler_withdraw_uri: string; - withdrawal_id: string; -} - interface MerchantBackendInfo { baseUrl: string; authToken?: string; @@ -102,34 +96,27 @@ function makeId(length: number): string { return result; } -/** - * Helper function to generate the "Authorization" HTTP header. - * FIXME: redundant, put in taler-util - */ -function makeBasicAuthHeader(username: string, password: string): string { - const auth = `${username}:${password}`; - const authEncoded: string = base64FromArrayBuffer(stringToBytes(auth)); - return `Basic ${authEncoded}`; -} - export async function withdrawTestBalance( ws: InternalWalletState, req: WithdrawTestBalanceRequest, ): Promise<void> { const amount = req.amount; const exchangeBaseUrl = req.exchangeBaseUrl; - const bankAccessApiBaseUrl = req.bankAccessApiBaseUrl; + const corebankApiBaseUrl = req.corebankApiBaseUrl; logger.trace( - `Registered bank user, bank access base url ${bankAccessApiBaseUrl}`, + `Registering bank user, bank access base url ${corebankApiBaseUrl}`, ); - const bankUser = await registerRandomBankUser(ws.http, bankAccessApiBaseUrl); + + const corebankClient = new TalerCorebankApiClient(corebankApiBaseUrl); + + const bankUser = await corebankClient.createRandomBankUser(); logger.trace(`Registered bank user ${JSON.stringify(bankUser)}`); - const wresp = await createDemoBankWithdrawalUri( - ws.http, - bankAccessApiBaseUrl, - bankUser, + corebankClient.setAuth(bankUser); + + const wresp = await corebankClient.createWithdrawalOperation( + bankUser.username, amount, ); @@ -139,14 +126,14 @@ export async function withdrawTestBalance( forcedDenomSel: req.forcedDenomSel, }); - await confirmBankWithdrawalUri( - ws.http, - bankAccessApiBaseUrl, - bankUser, - wresp.withdrawal_id, - ); + await corebankClient.confirmWithdrawalOperation(bankUser.username, { + withdrawalOperationId: wresp.withdrawal_id, + }); } +/** + * FIXME: User MerchantApiClient instead. + */ function getMerchantAuthHeader(m: MerchantBackendInfo): Record<string, string> { if (m.authToken) { return { @@ -157,80 +144,8 @@ function getMerchantAuthHeader(m: MerchantBackendInfo): Record<string, string> { } /** - * Use the testing API of a demobank to create a taler://withdraw URI - * that the wallet can then use to make a withdrawal. + * FIXME: User MerchantApiClient instead. */ -export async function createDemoBankWithdrawalUri( - http: HttpRequestLibrary, - bankAccessApiBaseUrl: string, - bankUser: BankUser, - amount: AmountString, -): Promise<BankWithdrawalResponse> { - const reqUrl = new URL( - `accounts/${bankUser.username}/withdrawals`, - bankAccessApiBaseUrl, - ).href; - const resp = await http.postJson( - reqUrl, - { - amount, - }, - { - headers: { - Authorization: makeBasicAuthHeader( - bankUser.username, - bankUser.password, - ), - }, - }, - ); - const respJson = await readSuccessResponseJsonOrThrow(resp, codecForAny()); - return respJson; -} - -async function confirmBankWithdrawalUri( - http: HttpRequestLibrary, - bankAccessApiBaseUrl: string, - bankUser: BankUser, - withdrawalId: string, -): Promise<void> { - const reqUrl = new URL( - `accounts/${bankUser.username}/withdrawals/${withdrawalId}/confirm`, - bankAccessApiBaseUrl, - ).href; - const resp = await http.postJson( - reqUrl, - {}, - { - headers: { - Authorization: makeBasicAuthHeader( - bankUser.username, - bankUser.password, - ), - }, - }, - ); - await readSuccessResponseJsonOrThrow(resp, codecForAny()); - return; -} - -async function registerRandomBankUser( - http: HttpRequestLibrary, - bankAccessApiBaseUrl: string, -): Promise<BankUser> { - const reqUrl = new URL("testing/register", bankAccessApiBaseUrl).href; - const randId = makeId(8); - const bankUser: BankUser = { - // euFin doesn't allow resource names to have upper case letters. - username: `testuser-${randId.toLowerCase()}`, - password: `testpw-${randId}`, - }; - - const resp = await http.postJson(reqUrl, bankUser); - await checkSuccessResponseOrThrow(resp); - return bankUser; -} - async function refund( http: HttpRequestLibrary, merchantBackend: MerchantBackendInfo, @@ -258,6 +173,9 @@ async function refund( return refundUri; } +/** + * FIXME: User MerchantApiClient instead. + */ async function createOrder( http: HttpRequestLibrary, merchantBackend: MerchantBackendInfo, @@ -287,6 +205,9 @@ async function createOrder( return { orderId }; } +/** + * FIXME: User MerchantApiClient instead. + */ async function checkPayment( http: HttpRequestLibrary, merchantBackend: MerchantBackendInfo, @@ -300,16 +221,6 @@ async function checkPayment( return readSuccessResponseJsonOrThrow(resp, codecForCheckPaymentResponse()); } -interface BankUser { - username: string; - password: string; -} - -interface BankWithdrawalResponse { - taler_withdraw_uri: string; - withdrawal_id: string; -} - async function makePayment( ws: InternalWalletState, merchant: MerchantBackendInfo, @@ -376,7 +287,7 @@ export async function runIntegrationTest( logger.info("withdrawing test balance"); await withdrawTestBalance(ws, { amount: args.amountToWithdraw, - bankAccessApiBaseUrl: args.bankAccessApiBaseUrl, + corebankApiBaseUrl: args.corebankApiBaseUrl, exchangeBaseUrl: args.exchangeBaseUrl, }); await waitUntilDone(ws); @@ -404,7 +315,7 @@ export async function runIntegrationTest( await withdrawTestBalance(ws, { amount: Amounts.stringify(withdrawAmountTwo), - bankAccessApiBaseUrl: args.bankAccessApiBaseUrl, + corebankApiBaseUrl: args.corebankApiBaseUrl, exchangeBaseUrl: args.exchangeBaseUrl, }); @@ -585,6 +496,49 @@ async function waitUntilPendingReady( cancelNotifs(); } +/** + * Wait until a transaction is in a particular state. + */ +export async function waitTransactionState( + ws: InternalWalletState, + transactionId: string, + txState: TransactionState, +): Promise<void> { + logger.info( + `starting waiting for ${transactionId} to be in ${JSON.stringify( + txState, + )})`, + ); + ws.ensureTaskLoopRunning(); + let p: OpenedPromise<void> | undefined = undefined; + const cancelNotifs = ws.addNotificationListener((notif) => { + if (!p) { + return; + } + if (notif.type === NotificationType.TransactionStateTransition) { + p.resolve(); + } + }); + while (1) { + p = openPromise(); + const tx = await getTransactionById(ws, { + transactionId, + }); + if ( + tx.txState.major === txState.major && + tx.txState.minor === txState.minor + ) { + break; + } + // Wait until transaction state changed + await p.promise; + } + logger.info( + `done waiting for ${transactionId} to be in ${JSON.stringify(txState)}`, + ); + cancelNotifs(); +} + export async function runIntegrationTest2( ws: InternalWalletState, args: IntegrationTestV2Args, @@ -603,7 +557,7 @@ export async function runIntegrationTest2( logger.info("withdrawing test balance"); await withdrawTestBalance(ws, { amount: Amounts.stringify(amountToWithdraw), - bankAccessApiBaseUrl: args.bankAccessApiBaseUrl, + corebankApiBaseUrl: args.corebankApiBaseUrl, exchangeBaseUrl: args.exchangeBaseUrl, }); await waitUntilDone(ws); @@ -636,7 +590,7 @@ export async function runIntegrationTest2( await withdrawTestBalance(ws, { amount: Amounts.stringify(withdrawAmountTwo), - bankAccessApiBaseUrl: args.bankAccessApiBaseUrl, + corebankApiBaseUrl: args.corebankApiBaseUrl, exchangeBaseUrl: args.exchangeBaseUrl, }); diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index d7b277faf..cf2006406 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -20,6 +20,7 @@ import { AbsoluteTime, Amounts, + DepositTransactionTrackingState, j2s, Logger, NotificationType, @@ -65,7 +66,13 @@ import { WithdrawalGroupStatus, WithdrawalRecordType, } from "../db.js"; -import { GetReadOnlyAccess, WalletStoresV1 } from "../index.js"; +import { + GetReadOnlyAccess, + timestampOptionalPreciseFromDb, + timestampPreciseFromDb, + timestampProtocolFromDb, + WalletStoresV1, +} from "../index.js"; import { InternalWalletState } from "../internal-wallet-state.js"; import { PendingTaskType } from "../pending-types.js"; import { assertUnreachable } from "../util/assertUnreachable.js"; @@ -470,7 +477,7 @@ function buildTransactionForPushPaymentDebit( expiration: contractTerms.purse_expiration, summary: contractTerms.summary, }, - timestamp: pi.timestampCreated, + timestamp: timestampPreciseFromDb(pi.timestampCreated), talerUri: stringifyPayPushUri({ exchangeBaseUrl: pi.exchangeBaseUrl, contractPriv: pi.contractPriv, @@ -501,7 +508,7 @@ function buildTransactionForPullPaymentDebit( expiration: contractTerms.purse_expiration, summary: contractTerms.summary, }, - timestamp: pi.timestampCreated, + timestamp: timestampPreciseFromDb(pi.timestampCreated), transactionId: constructTransactionIdentifier({ tag: TransactionType.PeerPullDebit, peerPullDebitId: pi.peerPullDebitId, @@ -543,8 +550,7 @@ function buildTransactionForPeerPullCredit( amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue), amountRaw: Amounts.stringify(wsr.instructedAmount), exchangeBaseUrl: wsr.exchangeBaseUrl, - // Old transactions don't have it! - timestamp: pullCredit.mergeTimestamp ?? TalerPreciseTimestamp.now(), + timestamp: timestampPreciseFromDb(pullCredit.mergeTimestamp), info: { expiration: peerContractTerms.purse_expiration, summary: peerContractTerms.summary, @@ -575,8 +581,7 @@ function buildTransactionForPeerPullCredit( amountEffective: Amounts.stringify(pullCredit.estimatedAmountEffective), amountRaw: Amounts.stringify(peerContractTerms.amount), exchangeBaseUrl: pullCredit.exchangeBaseUrl, - // Old transactions don't have it! - timestamp: pullCredit.mergeTimestamp ?? TalerProtocolTimestamp.now(), + timestamp: timestampPreciseFromDb(pullCredit.mergeTimestamp), info: { expiration: peerContractTerms.purse_expiration, summary: peerContractTerms.summary, @@ -617,7 +622,7 @@ function buildTransactionForPeerPushCredit( expiration: peerContractTerms.purse_expiration, summary: peerContractTerms.summary, }, - timestamp: wsr.timestampStart, + timestamp: timestampPreciseFromDb(wsr.timestampStart), transactionId: constructTransactionIdentifier({ tag: TransactionType.PeerPushCredit, peerPushCreditId: pushInc.peerPushCreditId, @@ -640,7 +645,7 @@ function buildTransactionForPeerPushCredit( summary: peerContractTerms.summary, }, kycUrl: pushInc.kycUrl, - timestamp: pushInc.timestamp, + timestamp: timestampPreciseFromDb(pushInc.timestamp), transactionId: constructTransactionIdentifier({ tag: TransactionType.PeerPushCredit, peerPushCreditId: pushInc.peerPushCreditId, @@ -673,7 +678,7 @@ function buildTransactionForBankIntegratedWithdraw( }, kycUrl: wgRecord.kycUrl, exchangeBaseUrl: wgRecord.exchangeBaseUrl, - timestamp: wgRecord.timestampStart, + timestamp: timestampPreciseFromDb(wgRecord.timestampStart), transactionId: constructTransactionIdentifier({ tag: TransactionType.Withdrawal, withdrawalGroupId: wgRecord.withdrawalGroupId, @@ -717,7 +722,7 @@ function buildTransactionForManualWithdraw( }, kycUrl: withdrawalGroup.kycUrl, exchangeBaseUrl: withdrawalGroup.exchangeBaseUrl, - timestamp: withdrawalGroup.timestampStart, + timestamp: timestampPreciseFromDb(withdrawalGroup.timestampStart), transactionId: constructTransactionIdentifier({ tag: TransactionType.Withdrawal, withdrawalGroupId: withdrawalGroup.withdrawalGroupId, @@ -748,7 +753,7 @@ function buildTransactionForRefund( tag: TransactionType.Payment, proposalId: refundRecord.proposalId, }), - timestamp: refundRecord.timestampCreated, + timestamp: timestampPreciseFromDb(refundRecord.timestampCreated), transactionId: constructTransactionIdentifier({ tag: TransactionType.Refund, refundGroupId: refundRecord.refundGroupId, @@ -786,7 +791,7 @@ function buildTransactionForRefresh( refreshOutputAmount: Amounts.stringify(outputAmount), originatingTransactionId: refreshGroupRecord.reasonDetails?.originatingTransactionId, - timestamp: refreshGroupRecord.timestampCreated, + timestamp: timestampPreciseFromDb(refreshGroupRecord.timestampCreated), transactionId: constructTransactionIdentifier({ tag: TransactionType.Refresh, refreshGroupId: refreshGroupRecord.refreshGroupId, @@ -806,15 +811,26 @@ function buildTransactionForDeposit( } } + const trackingState: DepositTransactionTrackingState[] = []; + + for (const ts of Object.values(dg.trackingState ?? {})) { + trackingState.push({ + amountRaw: ts.amountRaw, + timestampExecuted: timestampProtocolFromDb(ts.timestampExecuted), + wireFee: ts.wireFee, + wireTransferId: ts.wireTransferId, + }); + } + return { type: TransactionType.Deposit, txState: computeDepositTransactionStatus(dg), txActions: computeDepositTransactionActions(dg), amountRaw: Amounts.stringify(dg.counterpartyEffectiveDepositAmount), amountEffective: Amounts.stringify(dg.totalPayCost), - timestamp: dg.timestampCreated, + timestamp: timestampPreciseFromDb(dg.timestampCreated), targetPaytoUri: dg.wire.payto_uri, - wireTransferDeadline: dg.wireTransferDeadline, + wireTransferDeadline: timestampProtocolFromDb(dg.wireTransferDeadline), transactionId: constructTransactionIdentifier({ tag: TransactionType.Deposit, depositGroupId: dg.depositGroupId, @@ -827,7 +843,7 @@ function buildTransactionForDeposit( )) / dg.statusPerCoin.length, depositGroupId: dg.depositGroupId, - trackingState: Object.values(dg.trackingState ?? {}), + trackingState, deposited, ...(ort?.lastError ? { error: ort.lastError } : {}), }; @@ -845,7 +861,7 @@ function buildTransactionForTip( txActions: computeTipTransactionActions(tipRecord), amountEffective: Amounts.stringify(tipRecord.rewardAmountEffective), amountRaw: Amounts.stringify(tipRecord.rewardAmountRaw), - timestamp: tipRecord.acceptedTimestamp, + timestamp: timestampPreciseFromDb(tipRecord.acceptedTimestamp), transactionId: constructTransactionIdentifier({ tag: TransactionType.Reward, walletRewardId: tipRecord.walletRewardId, @@ -922,7 +938,7 @@ async function buildTransactionForPurchase( : Amounts.stringify(purchaseRecord.refundAmountAwaiting), refunds, posConfirmation: purchaseRecord.posConfirmation, - timestamp, + timestamp: timestampPreciseFromDb(timestamp), transactionId: constructTransactionIdentifier({ tag: TransactionType.Payment, proposalId: purchaseRecord.proposalId, diff --git a/packages/taler-wallet-core/src/operations/withdraw.test.ts b/packages/taler-wallet-core/src/operations/withdraw.test.ts index 2d9286610..cb8aa5e81 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.test.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.test.ts @@ -16,7 +16,11 @@ import { Amounts, DenomKeyType } from "@gnu-taler/taler-util"; import test from "ava"; -import { DenominationRecord, DenominationVerificationStatus } from "../db.js"; +import { + DenominationRecord, + DenominationVerificationStatus, + timestampProtocolToDb, +} from "../db.js"; import { selectWithdrawalDenominations } from "../util/coinSelection.js"; test("withdrawal selection bug repro", (t) => { @@ -64,22 +68,22 @@ test("withdrawal selection bug repro", (t) => { isRevoked: false, masterSig: "4F0P456CNNTTWK8BFJHGM3JTD6FVVNZY8EP077GYAHDJ5Y81S5RQ3SMS925NXMDVG9A88JAAP0E2GDZBC21PP5NHFFVWHAW3AVT8J3R", - stampExpireDeposit: { + stampExpireDeposit: timestampProtocolToDb({ t_s: 1742909388, - }, - stampExpireLegal: { + }), + stampExpireLegal: timestampProtocolToDb({ t_s: 1900589388, - }, - stampExpireWithdraw: { + }), + stampExpireWithdraw: timestampProtocolToDb({ t_s: 1679837388, - }, - stampStart: { + }), + stampStart: timestampProtocolToDb({ t_s: 1585229388, - }, + }), verificationStatus: DenominationVerificationStatus.Unverified, currency: "KUDOS", value: "KUDOS:1000", - listIssueDate: { t_s: 0 }, + listIssueDate: timestampProtocolToDb({ t_s: 0 }), }, { denomPub: { @@ -119,22 +123,22 @@ test("withdrawal selection bug repro", (t) => { isRevoked: false, masterSig: "P99AW82W46MZ0AKW7Z58VQPXFNTJQM9DVTYPBDF6KVYF38PPVDAZTV7JQ8TY7HGEC7JJJAY4E7AY7J3W1WV10DAZZQHHKTAVTSRAC20", - stampExpireDeposit: { + stampExpireDeposit: timestampProtocolToDb({ t_s: 1742909388, - }, - stampExpireLegal: { + }), + stampExpireLegal: timestampProtocolToDb({ t_s: 1900589388, - }, - stampExpireWithdraw: { + }), + stampExpireWithdraw: timestampProtocolToDb({ t_s: 1679837388, - }, - stampStart: { + }), + stampStart: timestampProtocolToDb({ t_s: 1585229388, - }, + }), verificationStatus: DenominationVerificationStatus.Unverified, value: "KUDOS:10", currency: "KUDOS", - listIssueDate: { t_s: 0 }, + listIssueDate: timestampProtocolToDb({ t_s: 0 }), }, { denomPub: { @@ -173,22 +177,22 @@ test("withdrawal selection bug repro", (t) => { isRevoked: false, masterSig: "8S4VZGHE5WE0N5ZVCHYW9KZZR4YAKK15S46MV1HR1QB9AAMH3NWPW4DCR4NYGJK33Q8YNFY80SWNS6XKAP5DEVK933TM894FJ2VGE3G", - stampExpireDeposit: { + stampExpireDeposit: timestampProtocolToDb({ t_s: 1742909388, - }, - stampExpireLegal: { + }), + stampExpireLegal: timestampProtocolToDb({ t_s: 1900589388, - }, - stampExpireWithdraw: { + }), + stampExpireWithdraw: timestampProtocolToDb({ t_s: 1679837388, - }, - stampStart: { + }), + stampStart: timestampProtocolToDb({ t_s: 1585229388, - }, + }), verificationStatus: DenominationVerificationStatus.Unverified, value: "KUDOS:5", currency: "KUDOS", - listIssueDate: { t_s: 0 }, + listIssueDate: timestampProtocolToDb({ t_s: 0 }), }, { denomPub: { @@ -228,22 +232,22 @@ test("withdrawal selection bug repro", (t) => { isRevoked: false, masterSig: "E3AWGAG8VB42P3KXM8B04Z6M483SX59R3Y4T53C3NXCA2NPB6C7HVCMVX05DC6S58E9X40NGEBQNYXKYMYCF3ASY2C4WP1WCZ4ME610", - stampExpireDeposit: { + stampExpireDeposit: timestampProtocolToDb({ t_s: 1742909388, - }, - stampExpireLegal: { + }), + stampExpireLegal: timestampProtocolToDb({ t_s: 1900589388, - }, - stampExpireWithdraw: { + }), + stampExpireWithdraw: timestampProtocolToDb({ t_s: 1679837388, - }, - stampStart: { + }), + stampStart: timestampProtocolToDb({ t_s: 1585229388, - }, + }), verificationStatus: DenominationVerificationStatus.Unverified, value: "KUDOS:1", currency: "KUDOS", - listIssueDate: { t_s: 0 }, + listIssueDate: timestampProtocolToDb({ t_s: 0 }), }, { denomPub: { @@ -282,18 +286,18 @@ test("withdrawal selection bug repro", (t) => { isRevoked: false, masterSig: "0ES1RKV002XB4YP21SN0QB7RSDHGYT0XAE65JYN8AVJAA6H7JZFN7JADXT521DJS89XMGPZGR8GCXF1516Y0Q9QDV00E6NMFA6CF838", - stampExpireDeposit: { + stampExpireDeposit: timestampProtocolToDb({ t_s: 1742909388, - }, - stampExpireLegal: { + }), + stampExpireLegal: timestampProtocolToDb({ t_s: 1900589388, - }, - stampExpireWithdraw: { + }), + stampExpireWithdraw: timestampProtocolToDb({ t_s: 1679837388, - }, - stampStart: { + }), + stampStart: timestampProtocolToDb({ t_s: 1585229388, - }, + }), verificationStatus: DenominationVerificationStatus.Unverified, value: Amounts.stringify({ currency: "KUDOS", @@ -301,7 +305,7 @@ test("withdrawal selection bug repro", (t) => { value: 0, }), currency: "KUDOS", - listIssueDate: { t_s: 0 }, + listIssueDate: timestampProtocolToDb({ t_s: 0 }), }, { denomPub: { @@ -340,22 +344,22 @@ test("withdrawal selection bug repro", (t) => { isRevoked: false, masterSig: "58QEB6C6N7602E572E3JYANVVJ9BRW0V9E2ZFDW940N47YVQDK9SAFPWBN5YGT3G1742AFKQ0CYR4DM2VWV0Z0T1XMEKWN6X2EZ9M0R", - stampExpireDeposit: { + stampExpireDeposit: timestampProtocolToDb({ t_s: 1742909388, - }, - stampExpireLegal: { + }), + stampExpireLegal: timestampProtocolToDb({ t_s: 1900589388, - }, - stampExpireWithdraw: { + }), + stampExpireWithdraw: timestampProtocolToDb({ t_s: 1679837388, - }, - stampStart: { + }), + stampStart: timestampProtocolToDb({ t_s: 1585229388, - }, + }), verificationStatus: DenominationVerificationStatus.Unverified, value: "KUDOS:2", currency: "KUDOS", - listIssueDate: { t_s: 0 }, + listIssueDate: timestampProtocolToDb({ t_s: 0 }), }, ]; diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index bae348dc1..eff427bec 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -135,6 +135,7 @@ import { ExchangeEntryDbUpdateStatus, PendingTaskType, isWithdrawableDenom, + timestampPreciseToDb, } from "../index.js"; import { TransitionInfo, @@ -573,7 +574,7 @@ export async function getBankWithdrawalInfo( throw TalerError.fromDetail( TalerErrorCode.WALLET_BANK_INTEGRATION_PROTOCOL_VERSION_INCOMPATIBLE, { - exchangeProtocolVersion: config.version, + bankProtocolVersion: config.version, walletProtocolVersion: WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, }, "bank integration protocol version not compatible with wallet", @@ -816,10 +817,10 @@ async function handleKycRequired( amlStatus === AmlStatus.normal || amlStatus === undefined ? WithdrawalGroupStatus.PendingKyc : amlStatus === AmlStatus.pending - ? WithdrawalGroupStatus.PendingAml - : amlStatus === AmlStatus.fronzen - ? WithdrawalGroupStatus.SuspendedAml - : assertUnreachable(amlStatus); + ? WithdrawalGroupStatus.PendingAml + : amlStatus === AmlStatus.fronzen + ? WithdrawalGroupStatus.SuspendedAml + : assertUnreachable(amlStatus); await tx.withdrawalGroups.put(wg2); const newTxState = computeWithdrawalTransactionStatus(wg2); @@ -1149,8 +1150,7 @@ export async function updateWithdrawalDenoms( denom.verificationStatus === DenominationVerificationStatus.Unverified ) { logger.trace( - `Validating denomination (${current + 1}/${ - denominations.length + `Validating denomination (${current + 1}/${denominations.length }) signature of ${denom.denomPubHash}`, ); let valid = false; @@ -1244,7 +1244,7 @@ async function queryReserve( if ( resp.status === 404 && result.talerErrorResponse.code === - TalerErrorCode.EXCHANGE_RESERVES_STATUS_UNKNOWN + TalerErrorCode.EXCHANGE_RESERVES_STATUS_UNKNOWN ) { return { ready: false }; } else { @@ -1330,7 +1330,7 @@ async function processWithdrawalGroupAbortingBank( } const txStatusOld = computeWithdrawalTransactionStatus(wg); wg.status = WithdrawalGroupStatus.AbortedBank; - wg.timestampFinish = TalerPreciseTimestamp.now(); + wg.timestampFinish = timestampPreciseToDb(TalerPreciseTimestamp.now()); const txStatusNew = computeWithdrawalTransactionStatus(wg); await tx.withdrawalGroups.put(wg); return { @@ -1463,7 +1463,7 @@ async function processWithdrawalGroupPendingReady( } const txStatusOld = computeWithdrawalTransactionStatus(wg); wg.status = WithdrawalGroupStatus.Done; - wg.timestampFinish = TalerPreciseTimestamp.now(); + wg.timestampFinish = timestampPreciseToDb(TalerPreciseTimestamp.now()); const txStatusNew = computeWithdrawalTransactionStatus(wg); await tx.withdrawalGroups.put(wg); return { @@ -1559,7 +1559,7 @@ async function processWithdrawalGroupPendingReady( const oldTxState = computeWithdrawalTransactionStatus(wg); logger.info(`now withdrawn ${numFinished} of ${numTotalCoins} coins`); if (wg.timestampFinish === undefined && numFinished === numTotalCoins) { - wg.timestampFinish = TalerPreciseTimestamp.now(); + wg.timestampFinish = timestampPreciseToDb(TalerPreciseTimestamp.now()); wg.status = WithdrawalGroupStatus.Done; await makeCoinsVisible(ws, tx, transactionId); } @@ -1779,7 +1779,7 @@ export async function getExchangeWithdrawalInfo( ) { logger.warn( `wallet's support for exchange protocol version ${WALLET_EXCHANGE_PROTOCOL_VERSION} might be outdated ` + - `(exchange has ${exchangeDetails.protocolVersionRange}), checking for updates`, + `(exchange has ${exchangeDetails.protocolVersionRange}), checking for updates`, ); } } @@ -2052,8 +2052,9 @@ async function registerReserveWithBank( if (r.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) { throw Error("invariant failed"); } - r.wgInfo.bankInfo.timestampReserveInfoPosted = - AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now()); + r.wgInfo.bankInfo.timestampReserveInfoPosted = timestampPreciseToDb( + AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now()), + ); const oldTxState = computeWithdrawalTransactionStatus(r); r.status = WithdrawalGroupStatus.PendingWaitConfirmBank; const newTxState = computeWithdrawalTransactionStatus(r); @@ -2135,7 +2136,7 @@ async function processReserveBankStatus( } const now = AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now()); const oldTxState = computeWithdrawalTransactionStatus(r); - r.wgInfo.bankInfo.timestampBankConfirmed = now; + r.wgInfo.bankInfo.timestampBankConfirmed = timestampPreciseToDb(now); r.status = WithdrawalGroupStatus.FailedBankAborted; const newTxState = computeWithdrawalTransactionStatus(r); await tx.withdrawalGroups.put(r); @@ -2184,7 +2185,7 @@ async function processReserveBankStatus( if (status.transfer_done) { logger.info("withdrawal: transfer confirmed by bank."); const now = AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now()); - r.wgInfo.bankInfo.timestampBankConfirmed = now; + r.wgInfo.bankInfo.timestampBankConfirmed = timestampPreciseToDb(now); r.status = WithdrawalGroupStatus.PendingQueryingStatus; } else { logger.info("withdrawal: transfer not yet confirmed by bank"); @@ -2290,7 +2291,7 @@ export async function internalPrepareCreateWithdrawalGroup( denomsSel: initialDenomSel, exchangeBaseUrl: canonExchange, instructedAmount: Amounts.stringify(amount), - timestampStart: now, + timestampStart: timestampPreciseToDb(now), rawWithdrawalAmount: initialDenomSel.totalWithdrawCost, effectiveWithdrawalAmount: initialDenomSel.totalCoinValue, secretSeed, @@ -2344,8 +2345,7 @@ export async function internalPerformCreateWithdrawalGroup( if (!prep.creationInfo) { return { withdrawalGroup, transitionInfo: undefined }; } - const { amount, canonExchange, exchangeDetails } = - prep.creationInfo; + const { amount, canonExchange, exchangeDetails } = prep.creationInfo; await tx.withdrawalGroups.add(withdrawalGroup); await tx.reserves.put({ @@ -2355,7 +2355,7 @@ export async function internalPerformCreateWithdrawalGroup( const exchange = await tx.exchanges.get(withdrawalGroup.exchangeBaseUrl); if (exchange) { - exchange.lastWithdrawal = TalerPreciseTimestamp.now(); + exchange.lastWithdrawal = timestampPreciseToDb(TalerPreciseTimestamp.now()); exchange.entryStatus = ExchangeEntryDbRecordStatus.Used; await tx.exchanges.put(exchange); } @@ -2546,11 +2546,7 @@ export async function createManualWithdrawal( }); const exchangePaytoUris = await ws.db - .mktx((x) => [ - x.withdrawalGroups, - x.exchanges, - x.exchangeDetails, - ]) + .mktx((x) => [x.withdrawalGroups, x.exchanges, x.exchangeDetails]) .runReadOnly(async (tx) => { return await getFundingPaytoUris(tx, withdrawalGroup.withdrawalGroupId); }); |