From f8d12f7b0d4af1b1769b89e80c87f9c169678564 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Fri, 18 Mar 2022 15:32:41 +0100 Subject: wallet: t_s/d_us migration --- .../src/operations/backup/export.ts | 11 ++-- .../src/operations/backup/import.ts | 10 +-- .../src/operations/backup/index.ts | 40 ++++++------ .../taler-wallet-core/src/operations/deposits.ts | 59 +++++++---------- .../taler-wallet-core/src/operations/exchanges.ts | 66 +++++++++++-------- packages/taler-wallet-core/src/operations/pay.ts | 33 +++++----- .../taler-wallet-core/src/operations/pending.ts | 42 ++++++------ .../taler-wallet-core/src/operations/recoup.ts | 6 +- .../taler-wallet-core/src/operations/refresh.ts | 74 ++++++++-------------- .../taler-wallet-core/src/operations/refund.ts | 41 +++++++----- .../taler-wallet-core/src/operations/reserves.ts | 19 +++--- .../taler-wallet-core/src/operations/testing.ts | 4 +- packages/taler-wallet-core/src/operations/tip.ts | 11 ++-- .../src/operations/transactions.ts | 29 ++++++--- .../src/operations/withdraw.test.ts | 60 +++++++++--------- .../taler-wallet-core/src/operations/withdraw.ts | 37 ++++++----- 16 files changed, 277 insertions(+), 265 deletions(-) (limited to 'packages/taler-wallet-core/src/operations') diff --git a/packages/taler-wallet-core/src/operations/backup/export.ts b/packages/taler-wallet-core/src/operations/backup/export.ts index 12b309418..35306da63 100644 --- a/packages/taler-wallet-core/src/operations/backup/export.ts +++ b/packages/taler-wallet-core/src/operations/backup/export.ts @@ -49,14 +49,13 @@ import { BackupWithdrawalGroup, canonicalizeBaseUrl, canonicalJson, - getTimestampNow, Logger, - timestampToIsoString, WalletBackupContentV1, hash, encodeCrock, getRandomBytes, stringToBytes, + AbsoluteTime, } from "@gnu-taler/taler-util"; import { InternalWalletState } from "../../common.js"; import { @@ -455,7 +454,7 @@ export async function exportBackup( }); }); - const ts = getTimestampNow(); + const ts = AbsoluteTime.toTimestamp(AbsoluteTime.now()); if (!bs.lastBackupTimestamp) { bs.lastBackupTimestamp = ts; @@ -496,9 +495,9 @@ export async function exportBackup( ); bs.lastBackupNonce = encodeCrock(getRandomBytes(32)); logger.trace( - `setting timestamp to ${timestampToIsoString(ts)} and nonce to ${ - bs.lastBackupNonce - }`, + `setting timestamp to ${AbsoluteTime.toIsoString( + AbsoluteTime.fromTimestamp(ts), + )} and nonce to ${bs.lastBackupNonce}`, ); await tx.config.put({ key: WALLET_BACKUP_STATE_KEY, diff --git a/packages/taler-wallet-core/src/operations/backup/import.ts b/packages/taler-wallet-core/src/operations/backup/import.ts index 35b62c2e4..4b17a5f33 100644 --- a/packages/taler-wallet-core/src/operations/backup/import.ts +++ b/packages/taler-wallet-core/src/operations/backup/import.ts @@ -20,7 +20,6 @@ import { Amounts, BackupDenomSel, WalletBackupContentV1, - getTimestampNow, BackupCoinSourceType, BackupProposalStatus, codecForContractTerms, @@ -28,6 +27,8 @@ import { RefreshReason, BackupRefreshReason, DenomKeyType, + AbsoluteTime, + TalerProtocolTimestamp, } from "@gnu-taler/taler-util"; import { WalletContractData, @@ -277,8 +278,8 @@ export async function importBackup( permanent: true, retryInfo: initRetryInfo(), lastUpdate: undefined, - nextUpdate: getTimestampNow(), - nextRefreshCheck: getTimestampNow(), + nextUpdate: TalerProtocolTimestamp.now(), + nextRefreshCheck: TalerProtocolTimestamp.now(), }); } @@ -465,7 +466,6 @@ export async function importBackup( senderWire: backupReserve.sender_wire, retryInfo: initRetryInfo(), lastError: undefined, - lastSuccessfulStatusQuery: { t_ms: "never" }, initialWithdrawalGroupId: backupReserve.initial_withdrawal_group_id, initialWithdrawalStarted: @@ -752,7 +752,7 @@ export async function importBackup( noncePub: cryptoComp.proposalNoncePrivToPub[backupPurchase.nonce_priv], lastPayError: undefined, - autoRefundDeadline: { t_ms: "never" }, + autoRefundDeadline: TalerProtocolTimestamp.never(), refundStatusRetryInfo: initRetryInfo(), lastRefundStatusError: undefined, timestampAccept: backupPurchase.timestamp_accept, diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts index 2a1a774f1..48eea56ad 100644 --- a/packages/taler-wallet-core/src/operations/backup/index.ts +++ b/packages/taler-wallet-core/src/operations/backup/index.ts @@ -40,21 +40,19 @@ import { ConfirmPayResultType, DenomKeyType, durationFromSpec, - getTimestampNow, hashDenomPub, HttpStatusCode, j2s, - LibtoolVersion, Logger, notEmpty, PreparePayResultType, RecoveryLoadRequest, RecoveryMergeStrategy, TalerErrorDetails, - Timestamp, - timestampAddDuration, + AbsoluteTime, URL, WalletBackupContentV1, + TalerProtocolTimestamp, } from "@gnu-taler/taler-util"; import { gunzipSync, gzipSync } from "fflate"; import { InternalWalletState } from "../../common.js"; @@ -250,11 +248,13 @@ interface BackupForProviderArgs { retryAfterPayment: boolean; } -function getNextBackupTimestamp(): Timestamp { +function getNextBackupTimestamp(): TalerProtocolTimestamp { // FIXME: Randomize! - return timestampAddDuration( - getTimestampNow(), - durationFromSpec({ minutes: 5 }), + return AbsoluteTime.toTimestamp( + AbsoluteTime.addDuration( + AbsoluteTime.now(), + durationFromSpec({ minutes: 5 }), + ), ); } @@ -324,7 +324,7 @@ async function runBackupCycleForProvider( if (!prov) { return; } - prov.lastBackupCycleTimestamp = getTimestampNow(); + prov.lastBackupCycleTimestamp = TalerProtocolTimestamp.now(); prov.state = { tag: BackupProviderStateTag.Ready, nextBackupTimestamp: getNextBackupTimestamp(), @@ -404,7 +404,7 @@ async function runBackupCycleForProvider( return; } prov.lastBackupHash = encodeCrock(currentBackupHash); - prov.lastBackupCycleTimestamp = getTimestampNow(); + prov.lastBackupCycleTimestamp = TalerProtocolTimestamp.now(); prov.state = { tag: BackupProviderStateTag.Ready, nextBackupTimestamp: getNextBackupTimestamp(), @@ -641,7 +641,7 @@ export async function addBackupProvider( if (req.activate) { oldProv.state = { tag: BackupProviderStateTag.Ready, - nextBackupTimestamp: getTimestampNow(), + nextBackupTimestamp: TalerProtocolTimestamp.now(), }; logger.info("setting existing backup provider to active"); await tx.backupProviders.put(oldProv); @@ -662,7 +662,7 @@ export async function addBackupProvider( if (req.activate) { state = { tag: BackupProviderStateTag.Ready, - nextBackupTimestamp: getTimestampNow(), + nextBackupTimestamp: TalerProtocolTimestamp.now(), }; } else { state = { @@ -701,8 +701,8 @@ export interface ProviderInfo { * Last communication issue with the provider. */ lastError?: TalerErrorDetails; - lastSuccessfulBackupTimestamp?: Timestamp; - lastAttemptedBackupTimestamp?: Timestamp; + lastSuccessfulBackupTimestamp?: TalerProtocolTimestamp; + lastAttemptedBackupTimestamp?: TalerProtocolTimestamp; paymentProposalIds: string[]; backupProblem?: BackupProblem; paymentStatus: ProviderPaymentStatus; @@ -724,7 +724,7 @@ export interface BackupConflictingDeviceProblem { type: "backup-conflicting-device"; otherDeviceId: string; myDeviceId: string; - backupTimestamp: Timestamp; + backupTimestamp: AbsoluteTime; } export type ProviderPaymentStatus = @@ -774,12 +774,12 @@ export interface ProviderPaymentPending { export interface ProviderPaymentPaid { type: ProviderPaymentType.Paid; - paidUntil: Timestamp; + paidUntil: AbsoluteTime; } export interface ProviderPaymentTermsChanged { type: ProviderPaymentType.TermsChanged; - paidUntil: Timestamp; + paidUntil: AbsoluteTime; oldTerms: BackupProviderTerms; newTerms: BackupProviderTerms; } @@ -811,8 +811,8 @@ async function getProviderPaymentInfo( if (status.paid) { return { type: ProviderPaymentType.Paid, - paidUntil: timestampAddDuration( - status.contractTerms.timestamp, + paidUntil: AbsoluteTime.addDuration( + AbsoluteTime.fromTimestamp(status.contractTerms.timestamp), durationFromSpec({ years: 1 }), ), }; @@ -915,7 +915,7 @@ async function backupRecoveryTheirs( paymentProposalIds: [], state: { tag: BackupProviderStateTag.Ready, - nextBackupTimestamp: getTimestampNow(), + nextBackupTimestamp: TalerProtocolTimestamp.now(), }, uids: [encodeCrock(getRandomBytes(32))], }); diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index a5d6c93cf..4b976011b 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -15,6 +15,7 @@ */ import { + AbsoluteTime, AmountJson, Amounts, buildCodecForObject, @@ -27,21 +28,16 @@ import { ContractTerms, CreateDepositGroupRequest, CreateDepositGroupResponse, - DenomKeyType, durationFromSpec, encodeCrock, GetFeeForDepositRequest, getRandomBytes, - getTimestampNow, hashWire, Logger, NotificationType, parsePaytoUri, TalerErrorDetails, - Timestamp, - timestampAddDuration, - timestampIsBetween, - timestampTruncateToSecond, + TalerProtocolTimestamp, TrackDepositGroupRequest, TrackDepositGroupResponse, URL, @@ -212,7 +208,7 @@ async function processDepositGroupImpl( } } if (allDeposited) { - dg.timestampFinished = getTimestampNow(); + dg.timestampFinished = TalerProtocolTimestamp.now(); dg.operationStatus = OperationStatus.Finished; delete dg.lastError; delete dg.retryInfo; @@ -310,13 +306,8 @@ export async function getFeeForDeposit( } }); - const timestamp = getTimestampNow(); - const timestampRound = timestampTruncateToSecond(timestamp); - // const noncePair = await ws.cryptoApi.createEddsaKeypair(); - // const merchantPair = await ws.cryptoApi.createEddsaKeypair(); - // const wireSalt = encodeCrock(getRandomBytes(16)); - // const wireHash = hashWire(req.depositPaytoUri, wireSalt); - // const wireHashLegacy = hashWireLegacy(req.depositPaytoUri, wireSalt); + const timestamp = AbsoluteTime.now(); + const timestampRound = AbsoluteTime.toTimestamp(timestamp); const contractTerms: ContractTerms = { auditors: [], exchanges: exchangeInfos, @@ -331,15 +322,14 @@ export async function getFeeForDeposit( wire_transfer_deadline: timestampRound, order_id: "", h_wire: "", - pay_deadline: timestampAddDuration( - timestampRound, - durationFromSpec({ hours: 1 }), + pay_deadline: AbsoluteTime.toTimestamp( + AbsoluteTime.addDuration(timestamp, durationFromSpec({ hours: 1 })), ), merchant: { name: "", }, merchant_pub: "", - refund_deadline: { t_ms: 0 }, + refund_deadline: TalerProtocolTimestamp.zero(), }; const contractData = extractContractData(contractTerms, "", ""); @@ -399,8 +389,8 @@ export async function createDepositGroup( } }); - const timestamp = getTimestampNow(); - const timestampRound = timestampTruncateToSecond(timestamp); + const now = AbsoluteTime.now(); + const nowRounded = AbsoluteTime.toTimestamp(now); const noncePair = await ws.cryptoApi.createEddsaKeypair(); const merchantPair = await ws.cryptoApi.createEddsaKeypair(); const wireSalt = encodeCrock(getRandomBytes(16)); @@ -412,24 +402,23 @@ export async function createDepositGroup( max_fee: Amounts.stringify(amount), max_wire_fee: Amounts.stringify(amount), wire_method: p.targetType, - timestamp: timestampRound, + timestamp: nowRounded, merchant_base_url: "", summary: "", nonce: noncePair.pub, - wire_transfer_deadline: timestampRound, + wire_transfer_deadline: nowRounded, order_id: "", // This is always the v2 wire hash, as we're the "merchant" and support v2. h_wire: wireHash, // Required for older exchanges. - pay_deadline: timestampAddDuration( - timestampRound, - durationFromSpec({ hours: 1 }), + pay_deadline: AbsoluteTime.toTimestamp( + AbsoluteTime.addDuration(now, durationFromSpec({ hours: 1 })), ), merchant: { name: "", }, merchant_pub: merchantPair.pub, - refund_deadline: { t_ms: 0 }, + refund_deadline: TalerProtocolTimestamp.zero(), }; const contractTermsHash = await ws.cryptoApi.hashString( @@ -482,7 +471,7 @@ export async function createDepositGroup( depositGroupId, noncePriv: noncePair.priv, noncePub: noncePair.pub, - timestampCreated: timestamp, + timestampCreated: AbsoluteTime.toTimestamp(now), timestampFinished: undefined, payCoinSelection: payCoinSel, payCoinSelectionUid: encodeCrock(getRandomBytes(32)), @@ -570,10 +559,10 @@ export async function getEffectiveDepositAmount( // about "find method not found on undefined" when the wireType // is not supported by the Exchange. const fee = exchangeDetails.wireInfo.feesForType[wireType].find((x) => { - return timestampIsBetween( - getTimestampNow(), - x.startStamp, - x.endStamp, + return AbsoluteTime.isBetween( + AbsoluteTime.now(), + AbsoluteTime.fromTimestamp(x.startStamp), + AbsoluteTime.fromTimestamp(x.endStamp), ); })?.wireFee; if (fee) { @@ -656,10 +645,10 @@ export async function getTotalFeeForDepositAmount( // about "find method not found on undefined" when the wireType // is not supported by the Exchange. const fee = exchangeDetails.wireInfo.feesForType[wireType].find((x) => { - return timestampIsBetween( - getTimestampNow(), - x.startStamp, - x.endStamp, + return AbsoluteTime.isBetween( + AbsoluteTime.now(), + AbsoluteTime.fromTimestamp(x.startStamp), + AbsoluteTime.fromTimestamp(x.endStamp), ); })?.wireFee; if (fee) { diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index 536d4e3bf..df7eee76d 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -28,8 +28,6 @@ import { durationFromSpec, ExchangeSignKeyJson, ExchangeWireJson, - getTimestampNow, - isTimestampExpired, Logger, NotificationType, parsePaytoUri, @@ -37,13 +35,15 @@ import { TalerErrorCode, URL, TalerErrorDetails, - Timestamp, + AbsoluteTime, hashDenomPub, LibtoolVersion, codecForAny, DenominationPubKey, DenomKeyType, ExchangeKeysJson, + TalerProtocolTimestamp, + TalerProtocolDuration, } from "@gnu-taler/taler-util"; import { decodeCrock, encodeCrock, hash } from "@gnu-taler/taler-util"; import { CryptoApi } from "../crypto/workers/cryptoApi.js"; @@ -57,7 +57,7 @@ import { WireInfo, } from "../db.js"; import { - getExpiryTimestamp, + getExpiry, HttpRequestLibrary, readSuccessResponseJsonOrThrow, readSuccessResponseTextOrThrow, @@ -80,7 +80,7 @@ const logger = new Logger("exchanges.ts"); function denominationRecordFromKeys( exchangeBaseUrl: string, exchangeMasterPub: string, - listIssueDate: Timestamp, + listIssueDate: TalerProtocolTimestamp, denomIn: ExchangeDenomination, ): DenominationRecord { let denomPub: DenominationPubKey; @@ -132,7 +132,9 @@ async function handleExchangeUpdateError( } export function getExchangeRequestTimeout(): Duration { - return { d_ms: 5000 }; + return Duration.fromSpec({ + seconds: 5, + }); } export interface ExchangeTosDownloadResult { @@ -362,12 +364,11 @@ export async function updateExchangeFromUrl( async function provideExchangeRecord( ws: InternalWalletState, baseUrl: string, - now: Timestamp, + now: AbsoluteTime, ): Promise<{ exchange: ExchangeRecord; exchangeDetails: ExchangeDetailsRecord | undefined; }> { - return await ws.db .mktx((x) => ({ exchanges: x.exchanges, @@ -376,14 +377,14 @@ async function provideExchangeRecord( .runReadWrite(async (tx) => { let exchange = await tx.exchanges.get(baseUrl); if (!exchange) { - const r = { + const r: ExchangeRecord = { permanent: true, baseUrl: baseUrl, retryInfo: initRetryInfo(), detailsPointer: undefined, lastUpdate: undefined, - nextUpdate: now, - nextRefreshCheck: now, + nextUpdate: AbsoluteTime.toTimestamp(now), + nextRefreshCheck: AbsoluteTime.toTimestamp(now), }; await tx.exchanges.put(r); exchange = r; @@ -400,10 +401,10 @@ interface ExchangeKeysDownloadResult { currentDenominations: DenominationRecord[]; protocolVersion: string; signingKeys: ExchangeSignKeyJson[]; - reserveClosingDelay: Duration; - expiry: Timestamp; + reserveClosingDelay: TalerProtocolDuration; + expiry: TalerProtocolTimestamp; recoup: Recoup[]; - listIssueDate: Timestamp; + listIssueDate: TalerProtocolTimestamp; } /** @@ -475,9 +476,11 @@ async function downloadExchangeKeysInfo( protocolVersion: exchangeKeysJsonUnchecked.version, signingKeys: exchangeKeysJsonUnchecked.signkeys, reserveClosingDelay: exchangeKeysJsonUnchecked.reserve_closing_delay, - expiry: getExpiryTimestamp(resp, { - minDuration: durationFromSpec({ hours: 1 }), - }), + expiry: AbsoluteTime.toTimestamp( + getExpiry(resp, { + minDuration: durationFromSpec({ hours: 1 }), + }), + ), recoup: exchangeKeysJsonUnchecked.recoup ?? [], listIssueDate: exchangeKeysJsonUnchecked.list_issue_date, }; @@ -529,12 +532,20 @@ async function updateExchangeFromUrlImpl( exchangeDetails: ExchangeDetailsRecord; }> { logger.info(`updating exchange info for ${baseUrl}, forced: ${forceNow}`); - const now = getTimestampNow(); + const now = AbsoluteTime.now(); baseUrl = canonicalizeBaseUrl(baseUrl); - const { exchange, exchangeDetails } = await provideExchangeRecord(ws, baseUrl, now); + const { exchange, exchangeDetails } = await provideExchangeRecord( + ws, + baseUrl, + now, + ); - if (!forceNow && exchangeDetails !== undefined && !isTimestampExpired(exchange.nextUpdate)) { + if ( + !forceNow && + exchangeDetails !== undefined && + !AbsoluteTime.isExpired(AbsoluteTime.fromTimestamp(exchange.nextUpdate)) + ) { logger.info("using existing exchange info"); return { exchange, exchangeDetails }; } @@ -575,7 +586,8 @@ async function updateExchangeFromUrlImpl( timeout, acceptedFormat, ); - const tosHasBeenAccepted = exchangeDetails?.termsOfServiceAcceptedEtag === tosDownload.tosEtag + const tosHasBeenAccepted = + exchangeDetails?.termsOfServiceAcceptedEtag === tosDownload.tosEtag; let recoupGroupId: string | undefined; @@ -611,23 +623,25 @@ async function updateExchangeFromUrlImpl( exchangeBaseUrl: r.baseUrl, wireInfo, termsOfServiceText: tosDownload.tosText, - termsOfServiceAcceptedEtag: tosHasBeenAccepted ? tosDownload.tosEtag : undefined, + termsOfServiceAcceptedEtag: tosHasBeenAccepted + ? tosDownload.tosEtag + : undefined, termsOfServiceContentType: tosDownload.tosContentType, termsOfServiceLastEtag: tosDownload.tosEtag, - termsOfServiceAcceptedTimestamp: getTimestampNow(), + termsOfServiceAcceptedTimestamp: TalerProtocolTimestamp.now(), }; // FIXME: only update if pointer got updated r.lastError = undefined; r.retryInfo = initRetryInfo(); - r.lastUpdate = getTimestampNow(); + r.lastUpdate = TalerProtocolTimestamp.now(); r.nextUpdate = keysInfo.expiry; // New denominations might be available. - r.nextRefreshCheck = getTimestampNow(); + r.nextRefreshCheck = TalerProtocolTimestamp.now(); r.detailsPointer = { currency: details.currency, masterPublicKey: details.masterPublicKey, // FIXME: only change if pointer really changed - updateClock: getTimestampNow(), + updateClock: TalerProtocolTimestamp.now(), protocolVersionRange: keysInfo.protocolVersion, }; await tx.exchanges.put(r); diff --git a/packages/taler-wallet-core/src/operations/pay.ts b/packages/taler-wallet-core/src/operations/pay.ts index 9844dc52e..9521d544f 100644 --- a/packages/taler-wallet-core/src/operations/pay.ts +++ b/packages/taler-wallet-core/src/operations/pay.ts @@ -27,7 +27,6 @@ import { AmountJson, Amounts, - CheckPaymentResponse, codecForContractTerms, codecForMerchantPayResponse, codecForProposal, @@ -41,11 +40,8 @@ import { durationMin, durationMul, encodeCrock, - getDurationRemaining, getRandomBytes, - getTimestampNow, HttpStatusCode, - isTimestampExpired, j2s, kdf, Logger, @@ -57,9 +53,9 @@ import { stringToBytes, TalerErrorCode, TalerErrorDetails, - Timestamp, - timestampAddDuration, + AbsoluteTime, URL, + TalerProtocolTimestamp, } from "@gnu-taler/taler-util"; import { EXCHANGE_COINS_LOCK, InternalWalletState } from "../common.js"; import { @@ -172,7 +168,9 @@ function isSpendableCoin(coin: CoinRecord, denom: DenominationRecord): boolean { if (coin.status !== CoinStatus.Fresh) { return false; } - if (isTimestampExpired(denom.stampExpireDeposit)) { + if ( + AbsoluteTime.isExpired(AbsoluteTime.fromTimestamp(denom.stampExpireDeposit)) + ) { return false; } return true; @@ -187,7 +185,7 @@ export interface CoinSelectionRequest { /** * Timestamp of the contract. */ - timestamp: Timestamp; + timestamp: TalerProtocolTimestamp; wireMethod: string; @@ -422,7 +420,7 @@ async function recordConfirmPay( payCoinSelectionUid: encodeCrock(getRandomBytes(32)), totalPayCost: payCostInfo, coinDepositPermissions, - timestampAccept: getTimestampNow(), + timestampAccept: AbsoluteTime.toTimestamp(AbsoluteTime.now()), timestampLastRefundStatus: undefined, proposalId: proposal.proposalId, lastPayError: undefined, @@ -784,7 +782,7 @@ async function processDownloadProposalImpl( } catch (e) { const err = makeErrorDetails( TalerErrorCode.WALLET_CONTRACT_TERMS_MALFORMED, - "schema validation failed", + `schema validation failed: ${e}`, {}, ); await failProposalPermanently(ws, proposalId, err); @@ -921,7 +919,7 @@ async function startDownloadProposal( noncePriv: priv, noncePub: pub, claimToken, - timestamp: getTimestampNow(), + timestamp: AbsoluteTime.toTimestamp(AbsoluteTime.now()), merchantBaseUrl, orderId, proposalId: proposalId, @@ -956,7 +954,7 @@ async function storeFirstPaySuccess( sessionId: string | undefined, paySig: string, ): Promise { - const now = getTimestampNow(); + const now = AbsoluteTime.toTimestamp(AbsoluteTime.now()); await ws.db .mktx((x) => ({ purchases: x.purchases })) .runReadWrite(async (tx) => { @@ -978,13 +976,16 @@ async function storeFirstPaySuccess( purchase.payRetryInfo = initRetryInfo(); purchase.merchantPaySig = paySig; if (isFirst) { - const ar = purchase.download.contractData.autoRefund; - if (ar) { + const protoAr = purchase.download.contractData.autoRefund; + if (protoAr) { + const ar = Duration.fromTalerProtocolDuration(protoAr); logger.info("auto_refund present"); purchase.refundQueryRequested = true; purchase.refundStatusRetryInfo = initRetryInfo(); purchase.lastRefundStatusError = undefined; - purchase.autoRefundDeadline = timestampAddDuration(now, ar); + purchase.autoRefundDeadline = AbsoluteTime.toTimestamp( + AbsoluteTime.addDuration(AbsoluteTime.now(), ar), + ); } } await tx.purchases.put(purchase); @@ -1150,7 +1151,7 @@ async function unblockBackup( if (bp.state.tag === BackupProviderStateTag.Retrying) { bp.state = { tag: BackupProviderStateTag.Ready, - nextBackupTimestamp: getTimestampNow(), + nextBackupTimestamp: TalerProtocolTimestamp.now(), }; } }); diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts index 6d686fb3a..fc76eeb19 100644 --- a/packages/taler-wallet-core/src/operations/pending.ts +++ b/packages/taler-wallet-core/src/operations/pending.ts @@ -35,7 +35,7 @@ import { PendingTaskType, ReserveType, } from "../pending-types.js"; -import { getTimestampNow, Timestamp } from "@gnu-taler/taler-util"; +import { AbsoluteTime } from "@gnu-taler/taler-util"; import { InternalWalletState } from "../common.js"; import { GetReadOnlyAccess } from "../util/query.js"; @@ -44,21 +44,25 @@ async function gatherExchangePending( exchanges: typeof WalletStoresV1.exchanges; exchangeDetails: typeof WalletStoresV1.exchangeDetails; }>, - now: Timestamp, + now: AbsoluteTime, resp: PendingOperationsResponse, ): Promise { await tx.exchanges.iter().forEachAsync(async (e) => { resp.pendingOperations.push({ type: PendingTaskType.ExchangeUpdate, givesLifeness: false, - timestampDue: e.lastError ? e.retryInfo.nextRetry : e.nextUpdate, + timestampDue: e.lastError + ? e.retryInfo.nextRetry + : AbsoluteTime.fromTimestamp(e.nextUpdate), exchangeBaseUrl: e.baseUrl, lastError: e.lastError, }); resp.pendingOperations.push({ type: PendingTaskType.ExchangeCheckRefresh, - timestampDue: e.lastError ? e.retryInfo.nextRetry : e.nextRefreshCheck, + timestampDue: e.lastError + ? e.retryInfo.nextRetry + : AbsoluteTime.fromTimestamp(e.nextRefreshCheck), givesLifeness: false, exchangeBaseUrl: e.baseUrl, }); @@ -67,7 +71,7 @@ async function gatherExchangePending( async function gatherReservePending( tx: GetReadOnlyAccess<{ reserves: typeof WalletStoresV1.reserves }>, - now: Timestamp, + now: AbsoluteTime, resp: PendingOperationsResponse, ): Promise { const reserves = await tx.reserves.indexes.byStatus.getAll( @@ -87,7 +91,7 @@ async function gatherReservePending( resp.pendingOperations.push({ type: PendingTaskType.Reserve, givesLifeness: true, - timestampDue: reserve.retryInfo?.nextRetry ?? Timestamp.now(), + timestampDue: reserve.retryInfo?.nextRetry ?? AbsoluteTime.now(), stage: reserve.reserveStatus, timestampCreated: reserve.timestampCreated, reserveType, @@ -105,7 +109,7 @@ async function gatherReservePending( async function gatherRefreshPending( tx: GetReadOnlyAccess<{ refreshGroups: typeof WalletStoresV1.refreshGroups }>, - now: Timestamp, + now: AbsoluteTime, resp: PendingOperationsResponse, ): Promise { const refreshGroups = await tx.refreshGroups.indexes.byStatus.getAll( @@ -136,7 +140,7 @@ async function gatherWithdrawalPending( withdrawalGroups: typeof WalletStoresV1.withdrawalGroups; planchets: typeof WalletStoresV1.planchets; }>, - now: Timestamp, + now: AbsoluteTime, resp: PendingOperationsResponse, ): Promise { const wsrs = await tx.withdrawalGroups.indexes.byStatus.getAll( @@ -169,14 +173,14 @@ async function gatherWithdrawalPending( async function gatherProposalPending( tx: GetReadOnlyAccess<{ proposals: typeof WalletStoresV1.proposals }>, - now: Timestamp, + now: AbsoluteTime, resp: PendingOperationsResponse, ): Promise { await tx.proposals.iter().forEach((proposal) => { if (proposal.proposalStatus == ProposalStatus.Proposed) { // Nothing to do, user needs to choose. } else if (proposal.proposalStatus == ProposalStatus.Downloading) { - const timestampDue = proposal.retryInfo?.nextRetry ?? getTimestampNow(); + const timestampDue = proposal.retryInfo?.nextRetry ?? AbsoluteTime.now(); resp.pendingOperations.push({ type: PendingTaskType.ProposalDownload, givesLifeness: true, @@ -194,7 +198,7 @@ async function gatherProposalPending( async function gatherDepositPending( tx: GetReadOnlyAccess<{ depositGroups: typeof WalletStoresV1.depositGroups }>, - now: Timestamp, + now: AbsoluteTime, resp: PendingOperationsResponse, ): Promise { const dgs = await tx.depositGroups.indexes.byStatus.getAll( @@ -204,7 +208,7 @@ async function gatherDepositPending( if (dg.timestampFinished) { return; } - const timestampDue = dg.retryInfo?.nextRetry ?? getTimestampNow(); + const timestampDue = dg.retryInfo?.nextRetry ?? AbsoluteTime.now(); resp.pendingOperations.push({ type: PendingTaskType.Deposit, givesLifeness: true, @@ -218,7 +222,7 @@ async function gatherDepositPending( async function gatherTipPending( tx: GetReadOnlyAccess<{ tips: typeof WalletStoresV1.tips }>, - now: Timestamp, + now: AbsoluteTime, resp: PendingOperationsResponse, ): Promise { await tx.tips.iter().forEach((tip) => { @@ -240,7 +244,7 @@ async function gatherTipPending( async function gatherPurchasePending( tx: GetReadOnlyAccess<{ purchases: typeof WalletStoresV1.purchases }>, - now: Timestamp, + now: AbsoluteTime, resp: PendingOperationsResponse, ): Promise { await tx.purchases.iter().forEach((pr) => { @@ -249,7 +253,7 @@ async function gatherPurchasePending( pr.abortStatus === AbortStatus.None && !pr.payFrozen ) { - const timestampDue = pr.payRetryInfo?.nextRetry ?? getTimestampNow(); + const timestampDue = pr.payRetryInfo?.nextRetry ?? AbsoluteTime.now(); resp.pendingOperations.push({ type: PendingTaskType.Pay, givesLifeness: true, @@ -275,7 +279,7 @@ async function gatherPurchasePending( async function gatherRecoupPending( tx: GetReadOnlyAccess<{ recoupGroups: typeof WalletStoresV1.recoupGroups }>, - now: Timestamp, + now: AbsoluteTime, resp: PendingOperationsResponse, ): Promise { await tx.recoupGroups.iter().forEach((rg) => { @@ -297,7 +301,7 @@ async function gatherBackupPending( tx: GetReadOnlyAccess<{ backupProviders: typeof WalletStoresV1.backupProviders; }>, - now: Timestamp, + now: AbsoluteTime, resp: PendingOperationsResponse, ): Promise { await tx.backupProviders.iter().forEach((bp) => { @@ -305,7 +309,7 @@ async function gatherBackupPending( resp.pendingOperations.push({ type: PendingTaskType.Backup, givesLifeness: false, - timestampDue: bp.state.nextBackupTimestamp, + timestampDue: AbsoluteTime.fromTimestamp(bp.state.nextBackupTimestamp), backupProviderBaseUrl: bp.baseUrl, lastError: undefined, }); @@ -325,7 +329,7 @@ async function gatherBackupPending( export async function getPendingOperations( ws: InternalWalletState, ): Promise { - const now = getTimestampNow(); + const now = AbsoluteTime.now(); return await ws.db .mktx((x) => ({ backupProviders: x.backupProviders, diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts index 23d14f212..84a27966d 100644 --- a/packages/taler-wallet-core/src/operations/recoup.ts +++ b/packages/taler-wallet-core/src/operations/recoup.ts @@ -27,11 +27,11 @@ import { Amounts, codecForRecoupConfirmation, - getTimestampNow, j2s, NotificationType, RefreshReason, TalerErrorDetails, + TalerProtocolTimestamp, } from "@gnu-taler/taler-util"; import { encodeCrock, getRandomBytes } from "@gnu-taler/taler-util"; import { @@ -110,7 +110,7 @@ async function putGroupAsFinished( } if (allFinished) { logger.info("all recoups of recoup group are finished"); - recoupGroup.timestampFinished = getTimestampNow(); + recoupGroup.timestampFinished = TalerProtocolTimestamp.now(); recoupGroup.retryInfo = initRetryInfo(); recoupGroup.lastError = undefined; if (recoupGroup.scheduleRefreshCoins.length > 0) { @@ -467,7 +467,7 @@ export async function createRecoupGroup( coinPubs: coinPubs, lastError: undefined, timestampFinished: undefined, - timestampStarted: getTimestampNow(), + timestampStarted: TalerProtocolTimestamp.now(), retryInfo: initRetryInfo(), recoupFinishedPerCoin: coinPubs.map(() => false), // Will be populated later diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index 8b6d8b2e4..11f0f6c51 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -23,7 +23,7 @@ import { ExchangeRefreshRevealRequest, getRandomBytes, HttpStatusCode, - j2s, + TalerProtocolTimestamp, } from "@gnu-taler/taler-util"; import { CoinRecord, @@ -42,11 +42,8 @@ import { fnutil, NotificationType, RefreshGroupId, - RefreshPlanchetInfo, RefreshReason, - stringifyTimestamp, TalerErrorDetails, - timestampToIsoString, } from "@gnu-taler/taler-util"; import { AmountJson, Amounts } from "@gnu-taler/taler-util"; import { amountToPretty } from "@gnu-taler/taler-util"; @@ -61,12 +58,7 @@ import { Duration, durationFromSpec, durationMul, - getTimestampNow, - isTimestampExpired, - Timestamp, - timestampAddDuration, - timestampDifference, - timestampMin, + AbsoluteTime, URL, } from "@gnu-taler/taler-util"; import { guardOperationException } from "../errors.js"; @@ -139,7 +131,7 @@ function updateGroupStatus(rg: RefreshGroupRecord): void { rg.frozen = true; rg.retryInfo = initRetryInfo(); } else { - rg.timestampFinished = getTimestampNow(); + rg.timestampFinished = AbsoluteTime.toTimestamp(AbsoluteTime.now()); rg.operationStatus = OperationStatus.Finished; rg.retryInfo = initRetryInfo(); } @@ -234,19 +226,6 @@ async function refreshCreateSession( availableDenoms, ); - if (logger.shouldLogTrace()) { - logger.trace(`printing selected denominations for refresh`); - logger.trace(`current time: ${stringifyTimestamp(getTimestampNow())}`); - for (const denom of newCoinDenoms.selectedDenoms) { - logger.trace(`denom ${denom.denom}, count ${denom.count}`); - logger.trace( - `withdrawal expiration ${stringifyTimestamp( - denom.denom.stampExpireWithdraw, - )}`, - ); - } - } - if (newCoinDenoms.selectedDenoms.length === 0) { logger.trace( `not refreshing, available amount ${amountToPretty( @@ -306,7 +285,9 @@ async function refreshCreateSession( } function getRefreshRequestTimeout(rg: RefreshGroupRecord): Duration { - return { d_ms: 5000 }; + return Duration.fromSpec({ + seconds: 5, + }); } async function refreshMelt( @@ -949,12 +930,12 @@ export async function createRefreshGroup( retryInfo: initRetryInfo(), inputPerCoin, estimatedOutputPerCoin, - timestampCreated: getTimestampNow(), + timestampCreated: TalerProtocolTimestamp.now(), }; if (oldCoinPubs.length == 0) { logger.warn("created refresh group with zero coins"); - refreshGroup.timestampFinished = getTimestampNow(); + refreshGroup.timestampFinished = TalerProtocolTimestamp.now(); refreshGroup.operationStatus = OperationStatus.Finished; } @@ -974,25 +955,23 @@ export async function createRefreshGroup( /** * Timestamp after which the wallet would do the next check for an auto-refresh. */ -function getAutoRefreshCheckThreshold(d: DenominationRecord): Timestamp { - const delta = timestampDifference( - d.stampExpireWithdraw, - d.stampExpireDeposit, - ); +function getAutoRefreshCheckThreshold(d: DenominationRecord): AbsoluteTime { + const expireWithdraw = AbsoluteTime.fromTimestamp(d.stampExpireWithdraw); + const expireDeposit = AbsoluteTime.fromTimestamp(d.stampExpireDeposit); + const delta = AbsoluteTime.difference(expireWithdraw, expireDeposit); const deltaDiv = durationMul(delta, 0.75); - return timestampAddDuration(d.stampExpireWithdraw, deltaDiv); + return AbsoluteTime.addDuration(expireWithdraw, deltaDiv); } /** * Timestamp after which the wallet would do an auto-refresh. */ -function getAutoRefreshExecuteThreshold(d: DenominationRecord): Timestamp { - const delta = timestampDifference( - d.stampExpireWithdraw, - d.stampExpireDeposit, - ); +function getAutoRefreshExecuteThreshold(d: DenominationRecord): AbsoluteTime { + const expireWithdraw = AbsoluteTime.fromTimestamp(d.stampExpireWithdraw); + const expireDeposit = AbsoluteTime.fromTimestamp(d.stampExpireDeposit); + const delta = AbsoluteTime.difference(expireWithdraw, expireDeposit); const deltaDiv = durationMul(delta, 0.5); - return timestampAddDuration(d.stampExpireWithdraw, deltaDiv); + return AbsoluteTime.addDuration(expireWithdraw, deltaDiv); } export async function autoRefresh( @@ -1001,8 +980,8 @@ export async function autoRefresh( ): Promise { logger.info(`doing auto-refresh check for '${exchangeBaseUrl}'`); await updateExchangeFromUrl(ws, exchangeBaseUrl, undefined, true); - let minCheckThreshold = timestampAddDuration( - getTimestampNow(), + let minCheckThreshold = AbsoluteTime.addDuration( + AbsoluteTime.now(), durationFromSpec({ days: 1 }), ); await ws.db @@ -1037,11 +1016,14 @@ export async function autoRefresh( continue; } const executeThreshold = getAutoRefreshExecuteThreshold(denom); - if (isTimestampExpired(executeThreshold)) { + if (AbsoluteTime.isExpired(executeThreshold)) { refreshCoins.push(coin); } else { const checkThreshold = getAutoRefreshCheckThreshold(denom); - minCheckThreshold = timestampMin(minCheckThreshold, checkThreshold); + minCheckThreshold = AbsoluteTime.min( + minCheckThreshold, + checkThreshold, + ); } } if (refreshCoins.length > 0) { @@ -1056,12 +1038,12 @@ export async function autoRefresh( ); } logger.info( - `current wallet time: ${timestampToIsoString(getTimestampNow())}`, + `current wallet time: ${AbsoluteTime.toIsoString(AbsoluteTime.now())}`, ); logger.info( - `next refresh check at ${timestampToIsoString(minCheckThreshold)}`, + `next refresh check at ${AbsoluteTime.toIsoString(minCheckThreshold)}`, ); - exchange.nextRefreshCheck = minCheckThreshold; + exchange.nextRefreshCheck = AbsoluteTime.toTimestamp(minCheckThreshold); await tx.exchanges.put(exchange); }); } diff --git a/packages/taler-wallet-core/src/operations/refund.ts b/packages/taler-wallet-core/src/operations/refund.ts index 106c79365..686d545df 100644 --- a/packages/taler-wallet-core/src/operations/refund.ts +++ b/packages/taler-wallet-core/src/operations/refund.ts @@ -32,7 +32,6 @@ import { codecForAbortResponse, codecForMerchantOrderRefundPickupResponse, CoinPublicKey, - getTimestampNow, Logger, MerchantCoinRefundFailureStatus, MerchantCoinRefundStatus, @@ -43,9 +42,10 @@ import { TalerErrorCode, TalerErrorDetails, URL, - timestampAddDuration, codecForMerchantOrderStatusPaid, - isTimestampExpired, + AbsoluteTime, + TalerProtocolTimestamp, + Duration, } from "@gnu-taler/taler-util"; import { AbortStatus, @@ -170,7 +170,7 @@ async function applySuccessfulRefund( p.refunds[refundKey] = { type: RefundState.Applied, - obtainedTime: getTimestampNow(), + obtainedTime: AbsoluteTime.toTimestamp(AbsoluteTime.now()), executionTime: r.execution_time, refundAmount: Amounts.parseOrThrow(r.refund_amount), refundFee: denom.feeRefund, @@ -222,7 +222,7 @@ async function storePendingRefund( p.refunds[refundKey] = { type: RefundState.Pending, - obtainedTime: getTimestampNow(), + obtainedTime: AbsoluteTime.toTimestamp(AbsoluteTime.now()), executionTime: r.execution_time, refundAmount: Amounts.parseOrThrow(r.refund_amount), refundFee: denom.feeRefund, @@ -275,7 +275,7 @@ async function storeFailedRefund( p.refunds[refundKey] = { type: RefundState.Failed, - obtainedTime: getTimestampNow(), + obtainedTime: TalerProtocolTimestamp.now(), executionTime: r.execution_time, refundAmount: Amounts.parseOrThrow(r.refund_amount), refundFee: denom.feeRefund, @@ -327,7 +327,7 @@ async function acceptRefunds( reason: RefundReason, ): Promise { logger.trace("handling refunds", refunds); - const now = getTimestampNow(); + const now = TalerProtocolTimestamp.now(); await ws.db .mktx((x) => ({ @@ -401,7 +401,10 @@ async function acceptRefunds( if ( p.timestampFirstSuccessfulPay && p.autoRefundDeadline && - p.autoRefundDeadline.t_ms > now.t_ms + AbsoluteTime.cmp( + AbsoluteTime.fromTimestamp(p.autoRefundDeadline), + AbsoluteTime.fromTimestamp(now), + ) > 0 ) { queryDone = false; } @@ -556,8 +559,10 @@ export async function applyRefund( ).amount, ).amount; } else { - amountRefundGone = Amounts.add(amountRefundGone, refund.refundAmount) - .amount; + amountRefundGone = Amounts.add( + amountRefundGone, + refund.refundAmount, + ).amount; } }); @@ -623,7 +628,9 @@ async function processPurchaseQueryRefundImpl( if ( waitForAutoRefund && purchase.autoRefundDeadline && - !isTimestampExpired(purchase.autoRefundDeadline) + !AbsoluteTime.isExpired( + AbsoluteTime.fromTimestamp(purchase.autoRefundDeadline), + ) ) { const requestUrl = new URL( `orders/${purchase.download.contractData.orderId}`, @@ -731,11 +738,13 @@ async function processPurchaseQueryRefundImpl( purchase.payCoinSelection.coinContributions[i], ), rtransaction_id: 0, - execution_time: timestampAddDuration( - purchase.download.contractData.timestamp, - { - d_ms: 1000, - }, + execution_time: AbsoluteTime.toTimestamp( + AbsoluteTime.addDuration( + AbsoluteTime.fromTimestamp( + purchase.download.contractData.timestamp, + ), + Duration.fromSpec({ seconds: 1 }), + ), ), }); } diff --git a/packages/taler-wallet-core/src/operations/reserves.ts b/packages/taler-wallet-core/src/operations/reserves.ts index d91ce89f1..ac9483631 100644 --- a/packages/taler-wallet-core/src/operations/reserves.ts +++ b/packages/taler-wallet-core/src/operations/reserves.ts @@ -29,15 +29,13 @@ import { durationMin, encodeCrock, getRandomBytes, - getTimestampNow, j2s, Logger, NotificationType, randomBytes, - ReserveTransactionType, TalerErrorCode, TalerErrorDetails, - Timestamp, + AbsoluteTime, URL, } from "@gnu-taler/taler-util"; import { InternalWalletState } from "../common.js"; @@ -172,7 +170,7 @@ export async function createReserve( req: CreateReserveRequest, ): Promise { const keypair = await ws.cryptoApi.createEddsaKeypair(); - const now = getTimestampNow(); + const now = AbsoluteTime.toTimestamp(AbsoluteTime.now()); const canonExchange = canonicalizeBaseUrl(req.exchange); let reserveStatus; @@ -217,7 +215,6 @@ export async function createReserve( timestampReserveInfoPosted: undefined, bankInfo, reserveStatus, - lastSuccessfulStatusQuery: undefined, retryInfo: initRetryInfo(), lastError: undefined, currency: req.amount.currency, @@ -403,7 +400,9 @@ async function registerReserveWithBank( default: return; } - r.timestampReserveInfoPosted = getTimestampNow(); + r.timestampReserveInfoPosted = AbsoluteTime.toTimestamp( + AbsoluteTime.now(), + ); r.reserveStatus = ReserveRecordStatus.WaitConfirmBank; r.operationStatus = OperationStatus.Pending; if (!r.bankInfo) { @@ -472,7 +471,7 @@ async function processReserveBankStatus( default: return; } - const now = getTimestampNow(); + const now = AbsoluteTime.toTimestamp(AbsoluteTime.now()); r.timestampBankConfirmed = now; r.reserveStatus = ReserveRecordStatus.BankAborted; r.operationStatus = OperationStatus.Finished; @@ -509,7 +508,7 @@ async function processReserveBankStatus( default: return; } - const now = getTimestampNow(); + const now = AbsoluteTime.toTimestamp(AbsoluteTime.now()); r.timestampBankConfirmed = now; r.reserveStatus = ReserveRecordStatus.QueryingStatus; r.operationStatus = OperationStatus.Pending; @@ -683,7 +682,7 @@ async function updateReserve( exchangeBaseUrl: reserve.exchangeBaseUrl, reservePub: reserve.reservePub, rawWithdrawalAmount: remainingAmount, - timestampStart: getTimestampNow(), + timestampStart: AbsoluteTime.toTimestamp(AbsoluteTime.now()), retryInfo: initRetryInfo(), lastError: undefined, denomsSel: denomSelectionInfoToState(denomSelInfo), @@ -736,7 +735,7 @@ async function processReserveImpl( await resetReserveRetry(ws, reservePub); } else if ( reserve.retryInfo && - !Timestamp.isExpired(reserve.retryInfo.nextRetry) + !AbsoluteTime.isExpired(reserve.retryInfo.nextRetry) ) { logger.trace("processReserve retry not due yet"); return; diff --git a/packages/taler-wallet-core/src/operations/testing.ts b/packages/taler-wallet-core/src/operations/testing.ts index 93f48fb83..23fee56c1 100644 --- a/packages/taler-wallet-core/src/operations/testing.ts +++ b/packages/taler-wallet-core/src/operations/testing.ts @@ -229,8 +229,8 @@ async function createOrder( amount, summary, fulfillment_url: fulfillmentUrl, - refund_deadline: { t_ms: t * 1000 }, - wire_transfer_deadline: { t_ms: t * 1000 }, + refund_deadline: { t_s: t }, + wire_transfer_deadline: { t_s: t }, }, }; const resp = await http.postJson(reqUrl, orderReq, { diff --git a/packages/taler-wallet-core/src/operations/tip.ts b/packages/taler-wallet-core/src/operations/tip.ts index a2a4e6f49..765120294 100644 --- a/packages/taler-wallet-core/src/operations/tip.ts +++ b/packages/taler-wallet-core/src/operations/tip.ts @@ -22,7 +22,6 @@ import { parseTipUri, codecForTipPickupGetResponse, Amounts, - getTimestampNow, TalerErrorDetails, NotificationType, TipPlanchetDetail, @@ -32,6 +31,7 @@ import { DenomKeyType, BlindedDenominationSignature, codecForMerchantTipResponseV2, + TalerProtocolTimestamp, } from "@gnu-taler/taler-util"; import { DerivedTipPlanchet } from "../crypto/cryptoTypes.js"; import { @@ -39,6 +39,7 @@ import { CoinRecord, CoinSourceType, CoinStatus, + TipRecord, } from "../db.js"; import { j2s } from "@gnu-taler/taler-util"; import { checkDbInvariant, checkLogicInvariant } from "../util/invariants.js"; @@ -115,14 +116,14 @@ export async function prepareTip( const secretSeed = encodeCrock(getRandomBytes(64)); const denomSelUid = encodeCrock(getRandomBytes(32)); - const newTipRecord = { + const newTipRecord: TipRecord = { walletTipId: walletTipId, acceptedTimestamp: undefined, tipAmountRaw: amount, tipExpiration: tipPickupStatus.expiration, exchangeBaseUrl: tipPickupStatus.exchange_url, merchantBaseUrl: res.merchantBaseUrl, - createdTimestamp: getTimestampNow(), + createdTimestamp: TalerProtocolTimestamp.now(), merchantTipId: res.merchantTipId, tipAmountEffective: Amounts.sub( amount, @@ -397,7 +398,7 @@ async function processTipImpl( if (tr.pickedUpTimestamp) { return; } - tr.pickedUpTimestamp = getTimestampNow(); + tr.pickedUpTimestamp = TalerProtocolTimestamp.now(); tr.lastError = undefined; tr.retryInfo = initRetryInfo(); await tx.tips.put(tr); @@ -421,7 +422,7 @@ export async function acceptTip( logger.error("tip not found"); return false; } - tipRecord.acceptedTimestamp = getTimestampNow(); + tipRecord.acceptedTimestamp = TalerProtocolTimestamp.now(); await tx.tips.put(tipRecord); return true; }); diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index 23ab39052..bc466f5a0 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -18,12 +18,12 @@ * Imports. */ import { + AbsoluteTime, AmountJson, Amounts, Logger, OrderShortInfo, PaymentStatus, - timestampCmp, Transaction, TransactionsRequest, TransactionsResponse, @@ -309,7 +309,7 @@ export async function getTransactions( for (const rk of Object.keys(pr.refunds)) { const refund = pr.refunds[rk]; - const groupKey = `${refund.executionTime.t_ms}`; + const groupKey = `${refund.executionTime.t_s}`; refundGroupKeys.add(groupKey); } @@ -333,7 +333,7 @@ export async function getTransactions( let amountEffective = Amounts.getZero(contractData.amount.currency); for (const rk of Object.keys(pr.refunds)) { const refund = pr.refunds[rk]; - const myGroupKey = `${refund.executionTime.t_ms}`; + const myGroupKey = `${refund.executionTime.t_s}`; if (myGroupKey !== groupKey) { continue; } @@ -403,8 +403,18 @@ export async function getTransactions( const txPending = transactions.filter((x) => x.pending); const txNotPending = transactions.filter((x) => !x.pending); - txPending.sort((h1, h2) => timestampCmp(h1.timestamp, h2.timestamp)); - txNotPending.sort((h1, h2) => timestampCmp(h1.timestamp, h2.timestamp)); + txPending.sort((h1, h2) => + AbsoluteTime.cmp( + AbsoluteTime.fromTimestamp(h1.timestamp), + AbsoluteTime.fromTimestamp(h2.timestamp), + ), + ); + txNotPending.sort((h1, h2) => + AbsoluteTime.cmp( + AbsoluteTime.fromTimestamp(h1.timestamp), + AbsoluteTime.fromTimestamp(h2.timestamp), + ), + ); return { transactions: [...txNotPending, ...txPending] }; } @@ -485,11 +495,10 @@ export async function deleteTransaction( }); return; } - const reserveRecord: - | ReserveRecord - | undefined = await tx.reserves.indexes.byInitialWithdrawalGroupId.get( - withdrawalGroupId, - ); + const reserveRecord: ReserveRecord | undefined = + await tx.reserves.indexes.byInitialWithdrawalGroupId.get( + withdrawalGroupId, + ); if (reserveRecord && !reserveRecord.initialWithdrawalStarted) { const reservePub = reserveRecord.reservePub; await tx.reserves.delete(reservePub); diff --git a/packages/taler-wallet-core/src/operations/withdraw.test.ts b/packages/taler-wallet-core/src/operations/withdraw.test.ts index 02540848a..e5894a3e7 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.test.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.test.ts @@ -62,16 +62,16 @@ test("withdrawal selection bug repro", (t) => { masterSig: "4F0P456CNNTTWK8BFJHGM3JTD6FVVNZY8EP077GYAHDJ5Y81S5RQ3SMS925NXMDVG9A88JAAP0E2GDZBC21PP5NHFFVWHAW3AVT8J3R", stampExpireDeposit: { - t_ms: 1742909388000, + t_s: 1742909388, }, stampExpireLegal: { - t_ms: 1900589388000, + t_s: 1900589388, }, stampExpireWithdraw: { - t_ms: 1679837388000, + t_s: 1679837388, }, stampStart: { - t_ms: 1585229388000, + t_s: 1585229388, }, verificationStatus: DenominationVerificationStatus.Unverified, value: { @@ -79,7 +79,7 @@ test("withdrawal selection bug repro", (t) => { fraction: 0, value: 1000, }, - listIssueDate: { t_ms: 0 }, + listIssueDate: { t_s: 0 }, }, { denomPub: { @@ -117,16 +117,16 @@ test("withdrawal selection bug repro", (t) => { masterSig: "P99AW82W46MZ0AKW7Z58VQPXFNTJQM9DVTYPBDF6KVYF38PPVDAZTV7JQ8TY7HGEC7JJJAY4E7AY7J3W1WV10DAZZQHHKTAVTSRAC20", stampExpireDeposit: { - t_ms: 1742909388000, + t_s: 1742909388, }, stampExpireLegal: { - t_ms: 1900589388000, + t_s: 1900589388, }, stampExpireWithdraw: { - t_ms: 1679837388000, + t_s: 1679837388, }, stampStart: { - t_ms: 1585229388000, + t_s: 1585229388, }, verificationStatus: DenominationVerificationStatus.Unverified, value: { @@ -134,7 +134,7 @@ test("withdrawal selection bug repro", (t) => { fraction: 0, value: 10, }, - listIssueDate: { t_ms: 0 }, + listIssueDate: { t_s: 0 }, }, { denomPub: { @@ -171,16 +171,16 @@ test("withdrawal selection bug repro", (t) => { masterSig: "8S4VZGHE5WE0N5ZVCHYW9KZZR4YAKK15S46MV1HR1QB9AAMH3NWPW4DCR4NYGJK33Q8YNFY80SWNS6XKAP5DEVK933TM894FJ2VGE3G", stampExpireDeposit: { - t_ms: 1742909388000, + t_s: 1742909388, }, stampExpireLegal: { - t_ms: 1900589388000, + t_s: 1900589388, }, stampExpireWithdraw: { - t_ms: 1679837388000, + t_s: 1679837388, }, stampStart: { - t_ms: 1585229388000, + t_s: 1585229388, }, verificationStatus: DenominationVerificationStatus.Unverified, value: { @@ -188,7 +188,7 @@ test("withdrawal selection bug repro", (t) => { fraction: 0, value: 5, }, - listIssueDate: { t_ms: 0 }, + listIssueDate: { t_s: 0 }, }, { denomPub: { @@ -226,16 +226,16 @@ test("withdrawal selection bug repro", (t) => { masterSig: "E3AWGAG8VB42P3KXM8B04Z6M483SX59R3Y4T53C3NXCA2NPB6C7HVCMVX05DC6S58E9X40NGEBQNYXKYMYCF3ASY2C4WP1WCZ4ME610", stampExpireDeposit: { - t_ms: 1742909388000, + t_s: 1742909388, }, stampExpireLegal: { - t_ms: 1900589388000, + t_s: 1900589388, }, stampExpireWithdraw: { - t_ms: 1679837388000, + t_s: 1679837388, }, stampStart: { - t_ms: 1585229388000, + t_s: 1585229388, }, verificationStatus: DenominationVerificationStatus.Unverified, value: { @@ -243,7 +243,7 @@ test("withdrawal selection bug repro", (t) => { fraction: 0, value: 1, }, - listIssueDate: { t_ms: 0 }, + listIssueDate: { t_s: 0 }, }, { denomPub: { @@ -280,16 +280,16 @@ test("withdrawal selection bug repro", (t) => { masterSig: "0ES1RKV002XB4YP21SN0QB7RSDHGYT0XAE65JYN8AVJAA6H7JZFN7JADXT521DJS89XMGPZGR8GCXF1516Y0Q9QDV00E6NMFA6CF838", stampExpireDeposit: { - t_ms: 1742909388000, + t_s: 1742909388, }, stampExpireLegal: { - t_ms: 1900589388000, + t_s: 1900589388, }, stampExpireWithdraw: { - t_ms: 1679837388000, + t_s: 1679837388, }, stampStart: { - t_ms: 1585229388000, + t_s: 1585229388, }, verificationStatus: DenominationVerificationStatus.Unverified, value: { @@ -297,7 +297,7 @@ test("withdrawal selection bug repro", (t) => { fraction: 10000000, value: 0, }, - listIssueDate: { t_ms: 0 }, + listIssueDate: { t_s: 0 }, }, { denomPub: { @@ -334,16 +334,16 @@ test("withdrawal selection bug repro", (t) => { masterSig: "58QEB6C6N7602E572E3JYANVVJ9BRW0V9E2ZFDW940N47YVQDK9SAFPWBN5YGT3G1742AFKQ0CYR4DM2VWV0Z0T1XMEKWN6X2EZ9M0R", stampExpireDeposit: { - t_ms: 1742909388000, + t_s: 1742909388, }, stampExpireLegal: { - t_ms: 1900589388000, + t_s: 1900589388, }, stampExpireWithdraw: { - t_ms: 1679837388000, + t_s: 1679837388, }, stampStart: { - t_ms: 1585229388000, + t_s: 1585229388, }, verificationStatus: DenominationVerificationStatus.Unverified, value: { @@ -351,7 +351,7 @@ test("withdrawal selection bug repro", (t) => { fraction: 0, value: 2, }, - listIssueDate: { t_ms: 0 }, + listIssueDate: { t_s: 0 }, }, ]; diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index 392cecf0b..e4c6f2a6a 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -26,16 +26,12 @@ import { codecForWithdrawResponse, durationFromSpec, ExchangeListItem, - getDurationRemaining, - getTimestampNow, Logger, NotificationType, parseWithdrawUri, TalerErrorCode, TalerErrorDetails, - Timestamp, - timestampCmp, - timestampSubtractDuraction, + AbsoluteTime, WithdrawResponse, URL, WithdrawUriInfoResponse, @@ -44,6 +40,8 @@ import { LibtoolVersion, UnblindedSignature, ExchangeWithdrawRequest, + Duration, + TalerProtocolTimestamp, } from "@gnu-taler/taler-util"; import { CoinRecord, @@ -147,7 +145,7 @@ export interface ExchangeWithdrawDetails { /** * The earliest deposit expiration of the selected coins. */ - earliestDepositExpiration: Timestamp; + earliestDepositExpiration: TalerProtocolTimestamp; /** * Number of currently offered denominations. @@ -184,18 +182,20 @@ export interface ExchangeWithdrawDetails { * revocation and offered state. */ export function isWithdrawableDenom(d: DenominationRecord): boolean { - const now = getTimestampNow(); - const started = timestampCmp(now, d.stampStart) >= 0; - let lastPossibleWithdraw: Timestamp; + const now = AbsoluteTime.now(); + const start = AbsoluteTime.fromTimestamp(d.stampStart); + const withdrawExpire = AbsoluteTime.fromTimestamp(d.stampExpireWithdraw); + const started = AbsoluteTime.cmp(now, start) >= 0; + let lastPossibleWithdraw: AbsoluteTime; if (walletCoreDebugFlags.denomselAllowLate) { - lastPossibleWithdraw = d.stampExpireWithdraw; + lastPossibleWithdraw = start; } else { - lastPossibleWithdraw = timestampSubtractDuraction( - d.stampExpireWithdraw, + lastPossibleWithdraw = AbsoluteTime.subtractDuraction( + withdrawExpire, durationFromSpec({ minutes: 5 }), ); } - const remaining = getDurationRemaining(lastPossibleWithdraw, now); + const remaining = Duration.getRemaining(lastPossibleWithdraw, now); const stillOkay = remaining.d_ms !== 0; return started && stillOkay && !d.isRevoked && d.isOffered; } @@ -274,7 +274,7 @@ export function selectWithdrawalDenominations( /** * Get information about a withdrawal from * a taler://withdraw URI by asking the bank. - * + * * FIXME: Move into bank client. */ export async function getBankWithdrawalInfo( @@ -947,7 +947,7 @@ async function processWithdrawGroupImpl( logger.trace(`now withdrawn ${numFinished} of ${numTotalCoins} coins`); if (wg.timestampFinish === undefined && numFinished === numTotalCoins) { finishedForFirstTime = true; - wg.timestampFinish = getTimestampNow(); + wg.timestampFinish = TalerProtocolTimestamp.now(); wg.operationStatus = OperationStatus.Finished; delete wg.lastError; wg.retryInfo = initRetryInfo(); @@ -999,7 +999,12 @@ export async function getExchangeWithdrawalInfo( for (let i = 1; i < selectedDenoms.selectedDenoms.length; i++) { const expireDeposit = selectedDenoms.selectedDenoms[i].denom.stampExpireDeposit; - if (expireDeposit.t_ms < earliestDepositExpiration.t_ms) { + if ( + AbsoluteTime.cmp( + AbsoluteTime.fromTimestamp(expireDeposit), + AbsoluteTime.fromTimestamp(earliestDepositExpiration), + ) < 0 + ) { earliestDepositExpiration = expireDeposit; } } -- cgit v1.2.3