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 --- packages/taler-wallet-core/src/bank-api-client.ts | 12 +-- packages/taler-wallet-core/src/common.ts | 11 +-- .../src/crypto/workers/cryptoApi.ts | 2 +- .../src/crypto/workers/cryptoImplementation.ts | 16 ++-- packages/taler-wallet-core/src/db.ts | 100 ++++++++++----------- packages/taler-wallet-core/src/dbless.ts | 9 +- .../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 ++++---- packages/taler-wallet-core/src/pending-types.ts | 11 +-- packages/taler-wallet-core/src/util/http.ts | 17 ++-- packages/taler-wallet-core/src/util/retries.ts | 26 +++--- packages/taler-wallet-core/src/wallet.ts | 19 ++-- 26 files changed, 388 insertions(+), 377 deletions(-) (limited to 'packages/taler-wallet-core') diff --git a/packages/taler-wallet-core/src/bank-api-client.ts b/packages/taler-wallet-core/src/bank-api-client.ts index a61ea2eef..128e9a7a7 100644 --- a/packages/taler-wallet-core/src/bank-api-client.ts +++ b/packages/taler-wallet-core/src/bank-api-client.ts @@ -25,6 +25,7 @@ import { AmountString, buildCodecForObject, Codec, + codecForAny, codecForString, encodeCrock, getRandomBytes, @@ -102,15 +103,16 @@ export namespace BankApi { const resp = await bank.http.postJson(url.href, { username, password }); let paytoUri = `payto://x-taler-bank/localhost/${username}`; if (resp.status !== 200 && resp.status !== 202) { - logger.error(`${j2s(await resp.json())}`) + logger.error(`${j2s(await resp.json())}`); throw new Error(); } + const respJson = await readSuccessResponseJsonOrThrow(resp, codecForAny()); + // LibEuFin demobank returns payto URI in response + if (respJson.paytoUri) { + paytoUri = respJson.paytoUri; + } try { const respJson = await resp.json(); - // LibEuFin demobank returns payto URI in response - if (respJson.paytoUri) { - paytoUri = respJson.paytoUri; - } } catch (e) {} return { password, diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts index 957ba1ca1..d37bbe6eb 100644 --- a/packages/taler-wallet-core/src/common.ts +++ b/packages/taler-wallet-core/src/common.ts @@ -34,7 +34,8 @@ import { BalancesResponse, AmountJson, DenominationPubKey, - Timestamp, + AbsoluteTime, + TalerProtocolTimestamp, } from "@gnu-taler/taler-util"; import { CryptoApi } from "./crypto/workers/cryptoApi.js"; import { ExchangeDetailsRecord, ExchangeRecord, WalletStoresV1 } from "./db.js"; @@ -165,22 +166,22 @@ export interface DenomInfo { /** * Validity start date of the denomination. */ - stampStart: Timestamp; + stampStart: TalerProtocolTimestamp; /** * Date after which the currency can't be withdrawn anymore. */ - stampExpireWithdraw: Timestamp; + stampExpireWithdraw: TalerProtocolTimestamp; /** * Date after the denomination officially doesn't exist anymore. */ - stampExpireLegal: Timestamp; + stampExpireLegal: TalerProtocolTimestamp; /** * Data after which coins of this denomination can't be deposited anymore. */ - stampExpireDeposit: Timestamp; + stampExpireDeposit: TalerProtocolTimestamp; } export type NotificationListener = (n: WalletNotification) => void; diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts index 820397346..d91aa08a2 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoApi.ts @@ -207,7 +207,7 @@ export class CryptoApi { } }; ws.terminationTimerHandle = timer.after(15 * 1000, destroy); - ws.terminationTimerHandle.unref(); + //ws.terminationTimerHandle.unref(); } handleWorkerError(ws: WorkerState, e: any): void { diff --git a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts index b51d499d5..b27067885 100644 --- a/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts +++ b/packages/taler-wallet-core/src/crypto/workers/cryptoImplementation.ts @@ -67,13 +67,11 @@ import { setupWithdrawPlanchet, stringToBytes, TalerSignaturePurpose, - Timestamp, - timestampTruncateToSecond, - typedArrayConcat, + AbsoluteTime, BlindedDenominationSignature, - RsaUnblindedSignature, UnblindedSignature, PlanchetUnblindInfo, + TalerProtocolTimestamp, } from "@gnu-taler/taler-util"; import bigint from "big-integer"; import { DenominationRecord, WireFee } from "../../db.js"; @@ -110,18 +108,16 @@ function amountToBuffer(amount: AmountJson): Uint8Array { return u8buf; } -function timestampRoundedToBuffer(ts: Timestamp): Uint8Array { +function timestampRoundedToBuffer(ts: TalerProtocolTimestamp): Uint8Array { const b = new ArrayBuffer(8); const v = new DataView(b); - const tsRounded = timestampTruncateToSecond(ts); + // The buffer we sign over represents the timestamp in microseconds. if (typeof v.setBigUint64 !== "undefined") { - const s = BigInt(tsRounded.t_ms) * BigInt(1000); + const s = BigInt(ts.t_s) * BigInt(1000 * 1000); v.setBigUint64(0, s); } else { const s = - tsRounded.t_ms === "never" - ? bigint.zero - : bigint(tsRounded.t_ms).times(1000); + ts.t_s === "never" ? bigint.zero : bigint(ts.t_s).multiply(1000 * 1000); const arr = s.toArray(2 ** 8).value; let offset = 8 - arr.length; for (let i = 0; i < arr.length; i++) { diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 2e76ab523..e9fe6a47b 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -36,9 +36,10 @@ import { Product, RefreshReason, TalerErrorDetails, - Timestamp, UnblindedSignature, CoinEnvelope, + TalerProtocolTimestamp, + TalerProtocolDuration, } from "@gnu-taler/taler-util"; import { RetryInfo } from "./util/retries.js"; import { PayCoinSelection } from "./util/coinSelection.js"; @@ -152,7 +153,7 @@ export interface ReserveRecord { /** * Time when the reserve was created. */ - timestampCreated: Timestamp; + timestampCreated: TalerProtocolTimestamp; /** * Time when the information about this reserve was posted to the bank. @@ -161,14 +162,14 @@ export interface ReserveRecord { * * Set to undefined if that hasn't happened yet. */ - timestampReserveInfoPosted: Timestamp | undefined; + timestampReserveInfoPosted: TalerProtocolTimestamp | undefined; /** * Time when the reserve was confirmed by the bank. * * Set to undefined if not confirmed yet. */ - timestampBankConfirmed: Timestamp | undefined; + timestampBankConfirmed: TalerProtocolTimestamp | undefined; /** * Wire information (as payto URI) for the bank account that @@ -217,11 +218,6 @@ export interface ReserveRecord { */ operationStatus: OperationStatus; - /** - * Time of the last successful status query. - */ - lastSuccessfulStatusQuery: Timestamp | undefined; - /** * Retry info, in case the reserve needs to be processed again * later, either due to an error or because the wallet needs to @@ -350,22 +346,22 @@ export interface DenominationRecord { /** * Validity start date of the denomination. */ - stampStart: Timestamp; + stampStart: TalerProtocolTimestamp; /** * Date after which the currency can't be withdrawn anymore. */ - stampExpireWithdraw: Timestamp; + stampExpireWithdraw: TalerProtocolTimestamp; /** * Date after the denomination officially doesn't exist anymore. */ - stampExpireLegal: Timestamp; + stampExpireLegal: TalerProtocolTimestamp; /** * Data after which coins of this denomination can't be deposited anymore. */ - stampExpireDeposit: Timestamp; + stampExpireDeposit: TalerProtocolTimestamp; /** * Signature by the exchange's master key over the denomination @@ -407,7 +403,7 @@ export interface DenominationRecord { * Latest list issue date of the "/keys" response * that includes this denomination. */ - listIssueDate: Timestamp; + listIssueDate: TalerProtocolTimestamp; } /** @@ -441,7 +437,7 @@ export interface ExchangeDetailsRecord { */ protocolVersion: string; - reserveClosingDelay: Duration; + reserveClosingDelay: TalerProtocolDuration; /** * Signing keys we got from the exchange, can also contain @@ -478,7 +474,7 @@ export interface ExchangeDetailsRecord { * * Used during backup merging. */ - termsOfServiceAcceptedTimestamp: Timestamp | undefined; + termsOfServiceAcceptedTimestamp: TalerProtocolTimestamp | undefined; wireInfo: WireInfo; } @@ -503,7 +499,7 @@ export interface ExchangeDetailsPointer { * Timestamp when the (masterPublicKey, currency) pointer * has been updated. */ - updateClock: Timestamp; + updateClock: TalerProtocolTimestamp; } /** @@ -528,14 +524,14 @@ export interface ExchangeRecord { /** * Last time when the exchange was updated. */ - lastUpdate: Timestamp | undefined; + lastUpdate: TalerProtocolTimestamp | undefined; /** * Next scheduled update for the exchange. * * (This field must always be present, so we can index on the timestamp.) */ - nextUpdate: Timestamp; + nextUpdate: TalerProtocolTimestamp; /** * Next time that we should check if coins need to be refreshed. @@ -543,7 +539,7 @@ export interface ExchangeRecord { * Updated whenever the exchange's denominations are updated or when * the refresh check has been done. */ - nextRefreshCheck: Timestamp; + nextRefreshCheck: TalerProtocolTimestamp; /** * Last error (if any) for fetching updated information about the @@ -793,7 +789,7 @@ export interface ProposalRecord { * Timestamp (in ms) of when the record * was created. */ - timestamp: Timestamp; + timestamp: TalerProtocolTimestamp; /** * Private key for the nonce. @@ -837,7 +833,7 @@ export interface TipRecord { * Has the user accepted the tip? Only after the tip has been accepted coins * withdrawn from the tip may be used. */ - acceptedTimestamp: Timestamp | undefined; + acceptedTimestamp: TalerProtocolTimestamp | undefined; /** * The tipped amount. @@ -849,7 +845,7 @@ export interface TipRecord { /** * Timestamp, the tip can't be picked up anymore after this deadline. */ - tipExpiration: Timestamp; + tipExpiration: TalerProtocolTimestamp; /** * The exchange that will sign our coins, chosen by the merchant. @@ -884,13 +880,13 @@ export interface TipRecord { */ merchantTipId: string; - createdTimestamp: Timestamp; + createdTimestamp: TalerProtocolTimestamp; /** * Timestamp for when the wallet finished picking up the tip * from the merchant. */ - pickedUpTimestamp: Timestamp | undefined; + pickedUpTimestamp: TalerProtocolTimestamp | undefined; /** * Retry info, even present when the operation isn't active to allow indexing @@ -959,12 +955,12 @@ export interface RefreshGroupRecord { */ statusPerCoin: RefreshCoinStatus[]; - timestampCreated: Timestamp; + timestampCreated: TalerProtocolTimestamp; /** * Timestamp when the refresh session finished. */ - timestampFinished: Timestamp | undefined; + timestampFinished: TalerProtocolTimestamp | undefined; /** * No coins are pending, but at least one is frozen. @@ -1025,12 +1021,12 @@ export interface WireFee { /** * Start date of the fee. */ - startStamp: Timestamp; + startStamp: TalerProtocolTimestamp; /** * End date of the fee. */ - endStamp: Timestamp; + endStamp: TalerProtocolTimestamp; /** * Signature made by the exchange master key. @@ -1054,12 +1050,12 @@ export type WalletRefundItem = export interface WalletRefundItemCommon { // Execution time as claimed by the merchant - executionTime: Timestamp; + executionTime: TalerProtocolTimestamp; /** * Time when the wallet became aware of the refund. */ - obtainedTime: Timestamp; + obtainedTime: TalerProtocolTimestamp; refundAmount: AmountJson; @@ -1141,14 +1137,14 @@ export interface WalletContractData { orderId: string; merchantBaseUrl: string; summary: string; - autoRefund: Duration | undefined; + autoRefund: TalerProtocolDuration | undefined; maxWireFee: AmountJson; wireFeeAmortization: number; - payDeadline: Timestamp; - refundDeadline: Timestamp; + payDeadline: TalerProtocolTimestamp; + refundDeadline: TalerProtocolTimestamp; allowedAuditors: AllowedAuditorInfo[]; allowedExchanges: AllowedExchangeInfo[]; - timestamp: Timestamp; + timestamp: TalerProtocolTimestamp; wireMethod: string; wireInfoHash: string; maxDepositFee: AmountJson; @@ -1215,8 +1211,10 @@ export interface PurchaseRecord { /** * Timestamp of the first time that sending a payment to the merchant * for this purchase was successful. + * + * FIXME: Does this need to be a timestamp, doensn't boolean suffice? */ - timestampFirstSuccessfulPay: Timestamp | undefined; + timestampFirstSuccessfulPay: TalerProtocolTimestamp | undefined; merchantPaySig: string | undefined; @@ -1224,7 +1222,7 @@ export interface PurchaseRecord { * When was the purchase made? * Refers to the time that the user accepted. */ - timestampAccept: Timestamp; + timestampAccept: TalerProtocolTimestamp; /** * Pending refunds for the purchase. A refund is pending @@ -1236,7 +1234,7 @@ export interface PurchaseRecord { * When was the last refund made? * Set to 0 if no refund was made on the purchase. */ - timestampLastRefundStatus: Timestamp | undefined; + timestampLastRefundStatus: TalerProtocolTimestamp | undefined; /** * Last session signature that we submitted to /pay (if any). @@ -1273,7 +1271,7 @@ export interface PurchaseRecord { /** * Continue querying the refund status until this deadline has expired. */ - autoRefundDeadline: Timestamp | undefined; + autoRefundDeadline: TalerProtocolTimestamp | undefined; /** * Is the payment frozen? I.e. did we encounter @@ -1308,12 +1306,12 @@ export interface WalletBackupConfState { /** * Timestamp stored in the last backup. */ - lastBackupTimestamp?: Timestamp; + lastBackupTimestamp?: TalerProtocolTimestamp; /** * Last time we tried to do a backup. */ - lastBackupCheckTimestamp?: Timestamp; + lastBackupCheckTimestamp?: TalerProtocolTimestamp; lastBackupNonce?: string; } @@ -1362,14 +1360,14 @@ export interface WithdrawalGroupRecord { * When was the withdrawal operation started started? * Timestamp in milliseconds. */ - timestampStart: Timestamp; + timestampStart: TalerProtocolTimestamp; /** * When was the withdrawal operation completed? * * FIXME: We should probably drop this and introduce an OperationStatus field. */ - timestampFinish?: Timestamp; + timestampFinish?: TalerProtocolTimestamp; /** * Operation status of the withdrawal group. @@ -1429,9 +1427,9 @@ export interface RecoupGroupRecord { */ recoupGroupId: string; - timestampStarted: Timestamp; + timestampStarted: TalerProtocolTimestamp; - timestampFinished: Timestamp | undefined; + timestampFinished: TalerProtocolTimestamp | undefined; /** * Public keys that identify the coins being recouped @@ -1482,7 +1480,7 @@ export type BackupProviderState = } | { tag: BackupProviderStateTag.Ready; - nextBackupTimestamp: Timestamp; + nextBackupTimestamp: TalerProtocolTimestamp; } | { tag: BackupProviderStateTag.Retrying; @@ -1529,7 +1527,7 @@ export interface BackupProviderRecord { * Does NOT correspond to the timestamp of the backup, * which only changes when the backup content changes. */ - lastBackupCycleTimestamp?: Timestamp; + lastBackupCycleTimestamp?: TalerProtocolTimestamp; /** * Proposal that we're currently trying to pay for. @@ -1594,9 +1592,9 @@ export interface DepositGroupRecord { depositedPerCoin: boolean[]; - timestampCreated: Timestamp; + timestampCreated: TalerProtocolTimestamp; - timestampFinished: Timestamp | undefined; + timestampFinished: TalerProtocolTimestamp | undefined; operationStatus: OperationStatus; @@ -1618,14 +1616,14 @@ export interface GhostDepositGroupRecord { * When multiple deposits for the same contract terms hash * have a different timestamp, we choose the earliest one. */ - timestamp: Timestamp; + timestamp: TalerProtocolTimestamp; contractTermsHash: string; deposits: { coinPub: string; amount: AmountString; - timestamp: Timestamp; + timestamp: TalerProtocolTimestamp; depositFee: AmountString; merchantPub: string; coinSig: string; diff --git a/packages/taler-wallet-core/src/dbless.ts b/packages/taler-wallet-core/src/dbless.ts index 169b6ae0a..854a3ea09 100644 --- a/packages/taler-wallet-core/src/dbless.ts +++ b/packages/taler-wallet-core/src/dbless.ts @@ -44,7 +44,7 @@ import { hashWire, Logger, parsePaytoUri, - Timestamp, + AbsoluteTime, UnblindedSignature, } from "@gnu-taler/taler-util"; import { DenominationRecord } from "./db.js"; @@ -222,10 +222,11 @@ export async function depositCoin(args: { const depositPayto = args.depositPayto ?? "payto://x-taler-bank/localhost/foo"; const wireSalt = encodeCrock(getRandomBytes(16)); + const timestampNow = AbsoluteTime.toTimestamp(AbsoluteTime.now()); const contractTermsHash = encodeCrock(getRandomBytes(64)); - const depositTimestamp = Timestamp.truncateToSecond(Timestamp.now()); - const refundDeadline = Timestamp.truncateToSecond(Timestamp.now()); - const wireTransferDeadline = Timestamp.truncateToSecond(Timestamp.now()); + const depositTimestamp = timestampNow; + const refundDeadline = timestampNow; + const wireTransferDeadline = timestampNow; const merchantPub = encodeCrock(getRandomBytes(32)); const dp = await cryptoApi.signDepositPermission({ coinPriv: coin.coinPriv, 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; } } diff --git a/packages/taler-wallet-core/src/pending-types.ts b/packages/taler-wallet-core/src/pending-types.ts index 911d0d8bd..4b1434bb5 100644 --- a/packages/taler-wallet-core/src/pending-types.ts +++ b/packages/taler-wallet-core/src/pending-types.ts @@ -27,7 +27,8 @@ import { TalerErrorDetails, BalancesResponse, - Timestamp, + AbsoluteTime, + TalerProtocolTimestamp, } from "@gnu-taler/taler-util"; import { ReserveRecordStatus } from "./db.js"; import { RetryInfo } from "./util/retries.js"; @@ -112,7 +113,7 @@ export interface PendingReserveTask { type: PendingTaskType.Reserve; retryInfo: RetryInfo | undefined; stage: ReserveRecordStatus; - timestampCreated: Timestamp; + timestampCreated: TalerProtocolTimestamp; reserveType: ReserveType; reservePub: string; bankWithdrawConfirmUrl?: string; @@ -135,7 +136,7 @@ export interface PendingRefreshTask { export interface PendingProposalDownloadTask { type: PendingTaskType.ProposalDownload; merchantBaseUrl: string; - proposalTimestamp: Timestamp; + proposalTimestamp: TalerProtocolTimestamp; proposalId: string; orderId: string; lastError?: TalerErrorDetails; @@ -149,7 +150,7 @@ export interface PendingProposalDownloadTask { export interface PendingProposalChoiceOperation { type: PendingTaskType.ProposalChoice; merchantBaseUrl: string; - proposalTimestamp: Timestamp; + proposalTimestamp: AbsoluteTime; proposalId: string; } @@ -231,7 +232,7 @@ export interface PendingTaskInfoCommon { /** * Timestamp when the pending operation should be executed next. */ - timestampDue: Timestamp; + timestampDue: AbsoluteTime; /** * Retry info. Currently used to stop the wallet after any operation diff --git a/packages/taler-wallet-core/src/util/http.ts b/packages/taler-wallet-core/src/util/http.ts index 43fe29bba..79afd5707 100644 --- a/packages/taler-wallet-core/src/util/http.ts +++ b/packages/taler-wallet-core/src/util/http.ts @@ -28,10 +28,7 @@ import { OperationFailedError, makeErrorDetails } from "../errors.js"; import { Logger, Duration, - Timestamp, - getTimestampNow, - timestampAddDuration, - timestampMax, + AbsoluteTime, TalerErrorDetails, Codec, j2s, @@ -314,24 +311,24 @@ export async function readSuccessResponseTextOrThrow( /** * Get the timestamp at which the response's content is considered expired. */ -export function getExpiryTimestamp( +export function getExpiry( httpResponse: HttpResponse, opt: { minDuration?: Duration }, -): Timestamp { +): AbsoluteTime { const expiryDateMs = new Date( httpResponse.headers.get("expiry") ?? "", ).getTime(); - let t: Timestamp; + let t: AbsoluteTime; if (Number.isNaN(expiryDateMs)) { - t = getTimestampNow(); + t = AbsoluteTime.now(); } else { t = { t_ms: expiryDateMs, }; } if (opt.minDuration) { - const t2 = timestampAddDuration(getTimestampNow(), opt.minDuration); - return timestampMax(t, t2); + const t2 = AbsoluteTime.addDuration(AbsoluteTime.now(), opt.minDuration); + return AbsoluteTime.max(t, t2); } return t; } diff --git a/packages/taler-wallet-core/src/util/retries.ts b/packages/taler-wallet-core/src/util/retries.ts index 8dec22bed..4b78d38ef 100644 --- a/packages/taler-wallet-core/src/util/retries.ts +++ b/packages/taler-wallet-core/src/util/retries.ts @@ -21,11 +21,11 @@ /** * Imports. */ -import { Timestamp, Duration, getTimestampNow } from "@gnu-taler/taler-util"; +import { AbsoluteTime, Duration } from "@gnu-taler/taler-util"; export interface RetryInfo { - firstTry: Timestamp; - nextRetry: Timestamp; + firstTry: AbsoluteTime; + nextRetry: AbsoluteTime; retryCounter: number; } @@ -45,7 +45,7 @@ export function updateRetryInfoTimeout( r: RetryInfo, p: RetryPolicy = defaultRetryPolicy, ): void { - const now = getTimestampNow(); + const now = AbsoluteTime.now(); if (now.t_ms === "never") { throw Error("assertion failed"); } @@ -54,10 +54,14 @@ export function updateRetryInfoTimeout( return; } - const nextIncrement = p.backoffDelta.d_ms * Math.pow(p.backoffBase, r.retryCounter) + const nextIncrement = + p.backoffDelta.d_ms * Math.pow(p.backoffBase, r.retryCounter); const t = - now.t_ms + (p.maxTimeout.d_ms === "forever" ? nextIncrement : Math.min(p.maxTimeout.d_ms, nextIncrement)); + now.t_ms + + (p.maxTimeout.d_ms === "forever" + ? nextIncrement + : Math.min(p.maxTimeout.d_ms, nextIncrement)); r.nextRetry = { t_ms: t }; } @@ -73,13 +77,13 @@ export function getRetryDuration( return { d_ms: "forever" }; } const t = p.backoffDelta.d_ms * Math.pow(p.backoffBase, r.retryCounter); - return { d_ms: p.maxTimeout.d_ms === "forever" ? t : Math.min(p.maxTimeout.d_ms, t) }; + return { + d_ms: p.maxTimeout.d_ms === "forever" ? t : Math.min(p.maxTimeout.d_ms, t), + }; } -export function initRetryInfo( - p: RetryPolicy = defaultRetryPolicy, -): RetryInfo { - const now = getTimestampNow(); +export function initRetryInfo(p: RetryPolicy = defaultRetryPolicy): RetryInfo { + const now = AbsoluteTime.now(); const info = { firstTry: now, nextRetry: now, diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 329417562..bbff465a8 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -64,9 +64,7 @@ import { durationMin, ExchangeListItem, ExchangesListRespose, - getDurationRemaining, GetExchangeTosResult, - isTimestampExpired, j2s, KnownBankAccounts, Logger, @@ -76,11 +74,12 @@ import { PaytoUri, RefreshReason, TalerErrorCode, - Timestamp, - timestampMin, + AbsoluteTime, URL, WalletNotification, + Duration, } from "@gnu-taler/taler-util"; +import { timeStamp } from "console"; import { DenomInfo, ExchangeOperations, @@ -292,7 +291,7 @@ export async function runPending( ): Promise { const pendingOpsResponse = await getPendingOperations(ws); for (const p of pendingOpsResponse.pendingOperations) { - if (!forceNow && !isTimestampExpired(p.timestampDue)) { + if (!forceNow && !AbsoluteTime.isExpired(p.timestampDue)) { continue; } try { @@ -340,10 +339,10 @@ async function runTaskLoop( logger.trace(`pending operations: ${j2s(pending)}`); let numGivingLiveness = 0; let numDue = 0; - let minDue: Timestamp = { t_ms: "never" }; + let minDue: AbsoluteTime = AbsoluteTime.never(); for (const p of pending.pendingOperations) { - minDue = timestampMin(minDue, p.timestampDue); - if (isTimestampExpired(p.timestampDue)) { + minDue = AbsoluteTime.min(minDue, p.timestampDue); + if (AbsoluteTime.isExpired(p.timestampDue)) { numDue++; } if (p.givesLifeness) { @@ -377,7 +376,7 @@ async function runTaskLoop( durationFromSpec({ seconds: 5, }), - getDurationRemaining(minDue), + Duration.getRemaining(minDue), ); logger.trace(`waiting for at most ${dt.d_ms} ms`); const timeout = ws.timerGroup.resolveAfter(dt); @@ -394,7 +393,7 @@ async function runTaskLoop( `running ${pending.pendingOperations.length} pending operations`, ); for (const p of pending.pendingOperations) { - if (!isTimestampExpired(p.timestampDue)) { + if (!AbsoluteTime.isExpired(p.timestampDue)) { continue; } try { -- cgit v1.2.3