From f8d12f7b0d4af1b1769b89e80c87f9c169678564 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Fri, 18 Mar 2022 15:32:41 +0100 Subject: [PATCH] wallet: t_s/d_us migration --- contrib/sample-data/history1.json | 402 ------------------ packages/anastasis-core/src/index.ts | 28 +- packages/anastasis-core/src/reducer-types.ts | 11 +- .../home/BackupFinishedScreen.stories.tsx | 4 +- .../src/pages/home/BackupFinishedScreen.tsx | 4 +- .../src/pages/home/SecretEditorScreen.tsx | 2 +- packages/taler-util/src/RequestThrottler.ts | 10 +- packages/taler-util/src/ReserveTransaction.ts | 90 ++-- packages/taler-util/src/backupTypes.ts | 74 ++-- packages/taler-util/src/talerTypes.ts | 51 ++- packages/taler-util/src/time.ts | 358 +++++++++------- packages/taler-util/src/transactionsTypes.ts | 4 +- packages/taler-util/src/types-test.ts | 16 +- packages/taler-util/src/walletTypes.ts | 14 +- .../taler-wallet-cli/src/harness/harness.ts | 18 +- .../src/harness/merchantApiTypes.ts | 12 +- .../src/integrationtests/test-bank-api.ts | 4 +- .../test-exchange-timetravel.ts | 24 +- .../integrationtests/test-libeufin-basic.ts | 5 +- .../test-merchant-instances-delete.ts | 2 +- .../src/integrationtests/test-refund-auto.ts | 15 +- .../src/integrationtests/test-refund-gone.ts | 10 +- .../taler-wallet-core/src/bank-api-client.ts | 12 +- packages/taler-wallet-core/src/common.ts | 11 +- .../src/crypto/workers/cryptoApi.ts | 2 +- .../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 +- .../src/operations/deposits.ts | 59 ++- .../src/operations/exchanges.ts | 66 +-- .../taler-wallet-core/src/operations/pay.ts | 33 +- .../src/operations/pending.ts | 42 +- .../src/operations/recoup.ts | 6 +- .../src/operations/refresh.ts | 74 ++-- .../src/operations/refund.ts | 41 +- .../src/operations/reserves.ts | 19 +- .../src/operations/testing.ts | 4 +- .../taler-wallet-core/src/operations/tip.ts | 11 +- .../src/operations/transactions.ts | 29 +- .../src/operations/withdraw.test.ts | 60 +-- .../src/operations/withdraw.ts | 37 +- .../taler-wallet-core/src/pending-types.ts | 11 +- packages/taler-wallet-core/src/util/http.ts | 17 +- .../taler-wallet-core/src/util/retries.ts | 26 +- packages/taler-wallet-core/src/wallet.ts | 19 +- .../PendingTransactions.stories.tsx | 62 +-- .../src/components/PendingTransactions.tsx | 13 +- .../src/components/Time.tsx | 4 +- .../src/components/TransactionItem.tsx | 16 +- .../src/cta/Tip.stories.tsx | 9 +- .../src/popup/DeveloperPage.tsx | 2 +- .../src/wallet/Backup.stories.tsx | 16 +- .../src/wallet/BackupPage.tsx | 18 +- .../src/wallet/History.stories.tsx | 7 +- .../src/wallet/History.tsx | 2 +- .../src/wallet/ProviderDetail.stories.tsx | 26 +- .../src/wallet/ProviderDetailPage.tsx | 5 +- .../src/wallet/Transaction.stories.tsx | 13 +- .../src/wallet/Transaction.tsx | 20 +- 62 files changed, 901 insertions(+), 1235 deletions(-) delete mode 100644 contrib/sample-data/history1.json diff --git a/contrib/sample-data/history1.json b/contrib/sample-data/history1.json deleted file mode 100644 index 8d0076a31..000000000 --- a/contrib/sample-data/history1.json +++ /dev/null @@ -1,402 +0,0 @@ -{ - "history": [ - { - "type": "exchange-added", - "builtIn": false, - "eventId": "exchange-added;https%3A%2F%2Fexchange.demo.taler.net%2F", - "exchangeBaseUrl": "https://exchange.demo.taler.net/", - "timestamp": { - "t_ms": 1578334008633 - } - }, - { - "type": "exchange-updated", - "eventId": "exchange-updated;https%3A%2F%2Fexchange.demo.taler.net%2F", - "exchangeBaseUrl": "https://exchange.demo.taler.net/", - "timestamp": { - "t_ms": 1578334009266 - } - }, - { - "type": "reserve-balance-updated", - "eventId": "reserve-balance-updated;HHG1KBFSW4PM8J43D14GVJYB8F5J56RDHANY1EQSW6RTYDAQJC6G", - "amountExpected": "KUDOS:5", - "amountReserveBalance": "KUDOS:5", - "timestamp": { - "t_ms": 1578334039291 - }, - "newHistoryTransactions": [ - { - "amount": "KUDOS:5", - "sender_account_url": "payto://x-taler-bank/bank.demo.taler.net/65", - "timestamp": { - "t_ms": 1578334028000 - }, - "wire_reference": "000000000038Y", - "type": "DEPOSIT" - } - ], - "reserveShortInfo": { - "exchangeBaseUrl": "https://exchange.demo.taler.net/", - "reserveCreationDetail": { - "type": "taler-bank-withdraw", - "bankUrl": "https://bank.demo.taler.net/api/withdraw-operation/6fd6a78f-3d12-4c91-b5e4-c6fc31f44e8d" - }, - "reservePub": "JPE7VR8R985WQ7ZX3EEYRTEGJQ1FAFE7P3JK1J7WFJEP7AGNTJD0" - } - }, - { - "type": "withdrawn", - "withdrawSessionId": "SFW3JS0JV0GZQQ1W07TNQEAGBD84X2QMH38PJ2CCTTKSDKQFCBY0", - "eventId": "withdrawn;SFW3JS0JV0GZQQ1W07TNQEAGBD84X2QMH38PJ2CCTTKSDKQFCBY0", - "amountWithdrawnEffective": "KUDOS:4.8", - "amountWithdrawnRaw": "KUDOS:5", - "exchangeBaseUrl": "https://exchange.demo.taler.net/", - "timestamp": { - "t_ms": 1578334039853 - }, - "withdrawalSource": { - "type": "reserve", - "reservePub": "JPE7VR8R985WQ7ZX3EEYRTEGJQ1FAFE7P3JK1J7WFJEP7AGNTJD0" - } - }, - { - "type": "order-accepted", - "eventId": "order-accepted;RNFEQ2FHF6NPM5M58HJH635TD4CE9S2SRNK3VN9SCMH3H7H0REBG", - "orderShortInfo": { - "amount": "KUDOS:0.5", - "orderId": "2020.006-G1NT65XRPQ8GP", - "merchantBaseUrl": "https://backend.demo.taler.net/public/instances/FSF/", - "proposalId": "RNFEQ2FHF6NPM5M58HJH635TD4CE9S2SRNK3VN9SCMH3H7H0REBG", - "summary": "Essay: 2. The GNU Project" - }, - "timestamp": { - "t_ms": 1578334078823 - } - }, - { - "type": "order-redirected", - "eventId": "order-redirected;0W4EBHQJ90XX4TSQ9C0M6K9MBFJ1ENKTWH4R3CXFT986A2QHCESG", - "alreadyPaidOrderShortInfo": { - "amount": "KUDOS:0.5", - "orderId": "2020.006-G1NT65XRPQ8GP", - "merchantBaseUrl": "https://backend.demo.taler.net/public/instances/FSF/", - "proposalId": "RNFEQ2FHF6NPM5M58HJH635TD4CE9S2SRNK3VN9SCMH3H7H0REBG", - "summary": "Essay: 2. The GNU Project" - }, - "newOrderShortInfo": { - "amount": "KUDOS:0.5", - "orderId": "2020.006-00W4ANVVKAHAP", - "merchantBaseUrl": "https://backend.demo.taler.net/public/instances/FSF/", - "proposalId": "0W4EBHQJ90XX4TSQ9C0M6K9MBFJ1ENKTWH4R3CXFT986A2QHCESG", - "summary": "Essay: 2. The GNU Project" - }, - "timestamp": { - "t_ms": 1578334108380 - } - }, - { - "type": "payment-sent", - "eventId": "payment-sent;RNFEQ2FHF6NPM5M58HJH635TD4CE9S2SRNK3VN9SCMH3H7H0REBG", - "orderShortInfo": { - "amount": "KUDOS:0.5", - "orderId": "2020.006-G1NT65XRPQ8GP", - "merchantBaseUrl": "https://backend.demo.taler.net/public/instances/FSF/", - "proposalId": "RNFEQ2FHF6NPM5M58HJH635TD4CE9S2SRNK3VN9SCMH3H7H0REBG", - "summary": "Essay: 2. The GNU Project" - }, - "replay": true, - "sessionId": "ab48396f-3aa1-4e1f-bfb5-30852d1e0d5e", - "timestamp": { - "t_ms": 1578334108677 - }, - "numCoins": 6, - "amountPaidWithFees": "KUDOS:0.54" - }, - { - "type": "exchange-added", - "builtIn": false, - "eventId": "exchange-added;https%3A%2F%2Fexchange.test.taler.net%2F", - "exchangeBaseUrl": "https://exchange.test.taler.net/", - "timestamp": { - "t_ms": 1578334134741 - } - }, - { - "type": "exchange-updated", - "eventId": "exchange-updated;https%3A%2F%2Fexchange.test.taler.net%2F", - "exchangeBaseUrl": "https://exchange.test.taler.net/", - "timestamp": { - "t_ms": 1578334135451 - } - }, - { - "type": "reserve-balance-updated", - "eventId": "reserve-balance-updated;498DDH4ZB41QX45FH38T4Y8JM14WX8Q2J1VKKZTE0CMS6TCPYZAG", - "amountExpected": "TESTKUDOS:5", - "amountReserveBalance": "TESTKUDOS:5", - "timestamp": { - "t_ms": 1578334141843 - }, - "newHistoryTransactions": [ - { - "amount": "TESTKUDOS:5", - "sender_account_url": "payto://x-taler-bank/bank.test.taler.net/9", - "timestamp": { - "t_ms": 1578334138000 - }, - "wire_reference": "0000000000184", - "type": "DEPOSIT" - } - ], - "reserveShortInfo": { - "exchangeBaseUrl": "https://exchange.test.taler.net/", - "reserveCreationDetail": { - "type": "taler-bank-withdraw", - "bankUrl": "https://bank.test.taler.net/api/withdraw-operation/e6210f62-d27b-4f58-815c-c5160de8804c" - }, - "reservePub": "ZQ2N7V8M035HAD1HTW7ZX22NM9GAXDCGX6GSJECD2KEY9TN3C0V0" - } - }, - { - "type": "withdrawn", - "withdrawSessionId": "AAVX0GVZ8GRPYX2RWANQ9J279ABA7KNFYEQ3A0C63TW7NMV0GAT0", - "eventId": "withdrawn;AAVX0GVZ8GRPYX2RWANQ9J279ABA7KNFYEQ3A0C63TW7NMV0GAT0", - "amountWithdrawnEffective": "TESTKUDOS:5", - "amountWithdrawnRaw": "TESTKUDOS:5", - "exchangeBaseUrl": "https://exchange.test.taler.net/", - "timestamp": { - "t_ms": 1578334142432 - }, - "withdrawalSource": { - "type": "reserve", - "reservePub": "ZQ2N7V8M035HAD1HTW7ZX22NM9GAXDCGX6GSJECD2KEY9TN3C0V0" - } - }, - { - "type": "refreshed", - "refreshGroupId": "2TARBASBNCE0X7F0D89Z3TJGPXKRARFSBH3HKZ5JFQRKPV9CA5C0", - "eventId": "refreshed;2TARBASBNCE0X7F0D89Z3TJGPXKRARFSBH3HKZ5JFQRKPV9CA5C0", - "timestamp": { - "t_ms": 1578334142528 - }, - "refreshReason": "pay", - "amountRefreshedEffective": "KUDOS:0", - "amountRefreshedRaw": "KUDOS:0.06", - "numInputCoins": 6, - "numOutputCoins": 0, - "numRefreshedInputCoins": 0 - }, - { - "type": "order-accepted", - "eventId": "order-accepted;W39MQT31M1ZY3NPF9ZSGXM8Q1K5XS5D5R1J10ZSHBREC6EZ570F0", - "orderShortInfo": { - "amount": "TESTKUDOS:1", - "orderId": "2020.006-00GBW7AD1VFRW", - "merchantBaseUrl": "https://backend.test.taler.net/public/instances/GNUnet/", - "proposalId": "W39MQT31M1ZY3NPF9ZSGXM8Q1K5XS5D5R1J10ZSHBREC6EZ570F0", - "summary": "Donation to GNUnet" - }, - "timestamp": { - "t_ms": 1578334230099 - } - }, - { - "type": "payment-sent", - "eventId": "payment-sent;W39MQT31M1ZY3NPF9ZSGXM8Q1K5XS5D5R1J10ZSHBREC6EZ570F0", - "orderShortInfo": { - "amount": "TESTKUDOS:1", - "orderId": "2020.006-00GBW7AD1VFRW", - "merchantBaseUrl": "https://backend.test.taler.net/public/instances/GNUnet/", - "proposalId": "W39MQT31M1ZY3NPF9ZSGXM8Q1K5XS5D5R1J10ZSHBREC6EZ570F0", - "summary": "Donation to GNUnet" - }, - "replay": false, - "timestamp": { - "t_ms": 1578334232527 - }, - "numCoins": 4, - "amountPaidWithFees": "TESTKUDOS:1" - }, - { - "type": "order-accepted", - "eventId": "order-accepted;Y8230SR6DP52J61CHEAPM5NHRVK408YP5KJP6VRBGZ3QZ0TBZQ90", - "orderShortInfo": { - "amount": "TESTKUDOS:0.1", - "orderId": "2020.006-02RFGFSSAZY9Y", - "merchantBaseUrl": "https://backend.test.taler.net/public/instances/Taler/", - "proposalId": "Y8230SR6DP52J61CHEAPM5NHRVK408YP5KJP6VRBGZ3QZ0TBZQ90", - "summary": "Donation to Taler" - }, - "timestamp": { - "t_ms": 1578334258703 - } - }, - { - "type": "payment-sent", - "eventId": "payment-sent;Y8230SR6DP52J61CHEAPM5NHRVK408YP5KJP6VRBGZ3QZ0TBZQ90", - "orderShortInfo": { - "amount": "TESTKUDOS:0.1", - "orderId": "2020.006-02RFGFSSAZY9Y", - "merchantBaseUrl": "https://backend.test.taler.net/public/instances/Taler/", - "proposalId": "Y8230SR6DP52J61CHEAPM5NHRVK408YP5KJP6VRBGZ3QZ0TBZQ90", - "summary": "Donation to Taler" - }, - "replay": false, - "timestamp": { - "t_ms": 1578334260497 - }, - "numCoins": 1, - "amountPaidWithFees": "TESTKUDOS:0.1" - }, - { - "type": "reserve-balance-updated", - "eventId": "reserve-balance-updated;NBZX24YB4GEHFXFXD5NJAC84ZZD63DFAD6Q7YFJQGX8WX9YQ7B90", - "amountExpected": "TESTKUDOS:15", - "amountReserveBalance": "TESTKUDOS:15", - "timestamp": { - "t_ms": 1578334530519 - }, - "newHistoryTransactions": [ - { - "amount": "TESTKUDOS:15", - "sender_account_url": "payto://x-taler-bank/bank.test.taler.net/9", - "timestamp": { - "t_ms": 1578334522000 - }, - "wire_reference": "000000000018C", - "type": "DEPOSIT" - } - ], - "reserveShortInfo": { - "exchangeBaseUrl": "https://exchange.test.taler.net/", - "reserveCreationDetail": { - "type": "taler-bank-withdraw", - "bankUrl": "https://bank.test.taler.net/api/withdraw-operation/6b5fae55-3634-4e6b-a877-2f8bd76dfaf9" - }, - "reservePub": "5XZC4DQMNR3482443727Q2RNKRVEBEW7SFJ8N9TYV5AZ74AZJB4G" - } - }, - { - "type": "withdrawn", - "withdrawSessionId": "312AKNY5BGF08PY1ZK0Z2QBEZ3481NFP9BN36Z08FHJQQZW80EZG", - "eventId": "withdrawn;312AKNY5BGF08PY1ZK0Z2QBEZ3481NFP9BN36Z08FHJQQZW80EZG", - "amountWithdrawnEffective": "TESTKUDOS:15", - "amountWithdrawnRaw": "TESTKUDOS:15", - "exchangeBaseUrl": "https://exchange.test.taler.net/", - "timestamp": { - "t_ms": 1578334531085 - }, - "withdrawalSource": { - "type": "reserve", - "reservePub": "5XZC4DQMNR3482443727Q2RNKRVEBEW7SFJ8N9TYV5AZ74AZJB4G" - } - }, - { - "type": "refreshed", - "refreshGroupId": "3FN9PFD2JCPS3FDHZDPRS50VQT4G7SE54JDTG2ZW2HV1R3PJ9KBG", - "eventId": "refreshed;3FN9PFD2JCPS3FDHZDPRS50VQT4G7SE54JDTG2ZW2HV1R3PJ9KBG", - "timestamp": { - "t_ms": 1578334532478 - }, - "refreshReason": "pay", - "amountRefreshedEffective": "TESTKUDOS:2.46", - "amountRefreshedRaw": "TESTKUDOS:2.46", - "numInputCoins": 1, - "numOutputCoins": 6, - "numRefreshedInputCoins": 1 - }, - { - "type": "refreshed", - "refreshGroupId": "DF0DQ6MGJ39R0891B0P86MY2QTMPF9FPDJN30PRBMXBZ8XEVHZE0", - "eventId": "refreshed;DF0DQ6MGJ39R0891B0P86MY2QTMPF9FPDJN30PRBMXBZ8XEVHZE0", - "timestamp": { - "t_ms": 1578334533177 - }, - "refreshReason": "pay", - "amountRefreshedEffective": "TESTKUDOS:1.12", - "amountRefreshedRaw": "TESTKUDOS:1.12", - "numInputCoins": 4, - "numOutputCoins": 3, - "numRefreshedInputCoins": 1 - }, - { - "type": "order-accepted", - "eventId": "order-accepted;KYSVHAENJFQB308KF6ENPM46VJRFAXFRDGW856P7MM90AW60REZ0", - "orderShortInfo": { - "amount": "TESTKUDOS:0.5", - "orderId": "2020.006-03WSPCDEZK6HG", - "merchantBaseUrl": "https://backend.test.taler.net/public/instances/FSF/", - "proposalId": "KYSVHAENJFQB308KF6ENPM46VJRFAXFRDGW856P7MM90AW60REZ0", - "summary": "Essay: 6. Why Software Should Be Free" - }, - "timestamp": { - "t_ms": 1578334554161 - } - }, - { - "type": "payment-sent", - "eventId": "payment-sent;KYSVHAENJFQB308KF6ENPM46VJRFAXFRDGW856P7MM90AW60REZ0", - "orderShortInfo": { - "amount": "TESTKUDOS:0.5", - "orderId": "2020.006-03WSPCDEZK6HG", - "merchantBaseUrl": "https://backend.test.taler.net/public/instances/FSF/", - "proposalId": "KYSVHAENJFQB308KF6ENPM46VJRFAXFRDGW856P7MM90AW60REZ0", - "summary": "Essay: 6. Why Software Should Be Free" - }, - "replay": false, - "sessionId": "489c048b-7702-49e8-b66f-2de5e33f0008", - "timestamp": { - "t_ms": 1578334556292 - }, - "numCoins": 5, - "amountPaidWithFees": "TESTKUDOS:0.5" - }, - { - "type": "refreshed", - "refreshGroupId": "NG8Z05Q8CM7KCC98PDNDQR0G920J2SGYM15ACBV0PMBE6XA8Q87G", - "eventId": "refreshed;NG8Z05Q8CM7KCC98PDNDQR0G920J2SGYM15ACBV0PMBE6XA8Q87G", - "timestamp": { - "t_ms": 1578334568850 - }, - "refreshReason": "pay", - "amountRefreshedEffective": "TESTKUDOS:0.06", - "amountRefreshedRaw": "TESTKUDOS:0.06", - "numInputCoins": 5, - "numOutputCoins": 2, - "numRefreshedInputCoins": 1 - }, - { - "type": "refund", - "eventId": "refund;FRW9DGXKPFS9HZEGFYMABDF6FRXDYNMFHH23FT71AAKN8FHGV2EG", - "refundGroupId": "FRW9DGXKPFS9HZEGFYMABDF6FRXDYNMFHH23FT71AAKN8FHGV2EG", - "orderShortInfo": { - "amount": "TESTKUDOS:0.5", - "orderId": "2020.006-03WSPCDEZK6HG", - "merchantBaseUrl": "https://backend.test.taler.net/public/instances/FSF/", - "proposalId": "KYSVHAENJFQB308KF6ENPM46VJRFAXFRDGW856P7MM90AW60REZ0", - "summary": "Essay: 6. Why Software Should Be Free" - }, - "timestamp": { - "t_ms": 1578334581129 - }, - "amountRefundedEffective": "TESTKUDOS:0.5", - "amountRefundedRaw": "TESTKUDOS:0.5", - "amountRefundedInvalid": "TESTKUDOS:0" - }, - { - "type": "refreshed", - "refreshGroupId": "TY8G0FDE83YJY3CWBYKMV891CADG06X8MTBZHE42XNQV2B2C95RG", - "eventId": "refreshed;TY8G0FDE83YJY3CWBYKMV891CADG06X8MTBZHE42XNQV2B2C95RG", - "timestamp": { - "t_ms": 1578334585583 - }, - "refreshReason": "refund", - "amountRefreshedEffective": "TESTKUDOS:0.5", - "amountRefreshedRaw": "TESTKUDOS:0.5", - "numInputCoins": 5, - "numOutputCoins": 6, - "numRefreshedInputCoins": 5 - } - ] -} diff --git a/packages/anastasis-core/src/index.ts b/packages/anastasis-core/src/index.ts index 15e1e5d97..d1afc706b 100644 --- a/packages/anastasis-core/src/index.ts +++ b/packages/anastasis-core/src/index.ts @@ -19,8 +19,9 @@ import { parsePayUri, stringToBytes, TalerErrorCode, + TalerProtocolTimestamp, TalerSignaturePurpose, - Timestamp, + AbsoluteTime, } from "@gnu-taler/taler-util"; import { anastasisData } from "./anastasis-data.js"; import { @@ -631,15 +632,13 @@ async function uploadSecret( logger.info(`got response for policy upload (http status ${resp.status})`); if (resp.status === HttpStatusCode.NoContent) { let policyVersion = 0; - let policyExpiration: Timestamp = { t_ms: 0 }; + let policyExpiration: TalerProtocolTimestamp = { t_s: 0 }; try { policyVersion = Number(resp.headers.get("Anastasis-Version") ?? "0"); } catch (e) {} try { policyExpiration = { - t_ms: - 1000 * - Number(resp.headers.get("Anastasis-Policy-Expiration") ?? "0"), + t_s: Number(resp.headers.get("Anastasis-Policy-Expiration") ?? "0"), }; } catch (e) {} successDetails[prov.provider_url] = { @@ -1187,7 +1186,7 @@ async function addProviderBackup( state: ReducerStateBackup, args: ActionArgsAddProvider, ): Promise { - const info = await getProviderInfo(args.provider_url) + const info = await getProviderInfo(args.provider_url); return { ...state, authentication_providers: { @@ -1202,8 +1201,10 @@ async function deleteProviderBackup( state: ReducerStateBackup, args: ActionArgsDeleteProvider, ): Promise { - const authentication_providers = {... state.authentication_providers ?? {} } - delete authentication_providers[args.provider_url] + const authentication_providers = { + ...(state.authentication_providers ?? {}), + }; + delete authentication_providers[args.provider_url]; return { ...state, authentication_providers, @@ -1214,7 +1215,7 @@ async function addProviderRecovery( state: ReducerStateRecovery, args: ActionArgsAddProvider, ): Promise { - const info = await getProviderInfo(args.provider_url) + const info = await getProviderInfo(args.provider_url); return { ...state, authentication_providers: { @@ -1228,8 +1229,10 @@ async function deleteProviderRecovery( state: ReducerStateRecovery, args: ActionArgsDeleteProvider, ): Promise { - const authentication_providers = {... state.authentication_providers ?? {} } - delete authentication_providers[args.provider_url] + const authentication_providers = { + ...(state.authentication_providers ?? {}), + }; + delete authentication_providers[args.provider_url]; return { ...state, authentication_providers, @@ -1347,7 +1350,8 @@ async function updateUploadFees( x, ).amount; }; - const years = Duration.toIntegerYears(Duration.getRemaining(expiration)); + const expirationTime = AbsoluteTime.fromTimestamp(expiration); + const years = Duration.toIntegerYears(Duration.getRemaining(expirationTime)); logger.info(`computing fees for ${years} years`); // For now, we compute fees for *all* available providers. for (const provUrl in state.authentication_providers ?? {}) { diff --git a/packages/anastasis-core/src/reducer-types.ts b/packages/anastasis-core/src/reducer-types.ts index 8d375552f..2a869fe47 100644 --- a/packages/anastasis-core/src/reducer-types.ts +++ b/packages/anastasis-core/src/reducer-types.ts @@ -7,7 +7,8 @@ import { codecForString, codecForTimestamp, Duration, - Timestamp, + TalerProtocolTimestamp, + AbsoluteTime, } from "@gnu-taler/taler-util"; import { ChallengeFeedback } from "./challenge-feedback-types.js"; import { KeyShare } from "./crypto.js"; @@ -43,7 +44,7 @@ export interface PolicyProvider { export interface SuccessDetails { [provider_url: string]: { policy_version: number; - policy_expiration: Timestamp; + policy_expiration: TalerProtocolTimestamp; }; } @@ -112,7 +113,7 @@ export interface ReducerStateBackup { core_secret?: CoreSecret; - expiration?: Timestamp; + expiration?: TalerProtocolTimestamp; upload_fees?: { fee: AmountString }[]; @@ -369,7 +370,7 @@ export interface ActionArgsEnterSecret { value: string; mime?: string; }; - expiration: Timestamp; + expiration: TalerProtocolTimestamp; } export interface ActionArgsSelectContinent { @@ -438,7 +439,7 @@ export interface ActionArgsAddPolicy { } export interface ActionArgsUpdateExpiration { - expiration: Timestamp; + expiration: TalerProtocolTimestamp; } export interface ActionArgsChangeVersion { diff --git a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx index bb0c3c4b6..1c6ae4b22 100644 --- a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx +++ b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.stories.tsx @@ -51,13 +51,13 @@ export const WithDetails = createExample(TestedComponent, { success_details: { "https://anastasis.demo.taler.net/": { policy_expiration: { - t_ms: "never", + t_s: "never", }, policy_version: 0, }, "https://kudos.demo.anastasis.lu/": { policy_expiration: { - t_ms: new Date().getTime() + 60 * 60 * 24 * 1000, + t_s: new Date().getTime() + 60 * 60 * 24, }, policy_version: 1, }, diff --git a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx index 66473345f..d6272d843 100644 --- a/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/BackupFinishedScreen.tsx @@ -35,9 +35,9 @@ export function BackupFinishedScreen(): VNode {

version {sd.policy_version} - {sd.policy_expiration.t_ms !== "never" + {sd.policy_expiration.t_s !== "never" ? ` expires at: ${format( - new Date(sd.policy_expiration.t_ms), + new Date(sd.policy_expiration.t_s), "dd-MM-yyyy", )}` : " without expiration date"} diff --git a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx index e401c3d73..d9bf084ab 100644 --- a/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx +++ b/packages/anastasis-webui/src/pages/home/SecretEditorScreen.tsx @@ -52,7 +52,7 @@ export function SecretEditorScreen(): VNode { await tx.transition("enter_secret", { secret, expiration: { - t_ms: new Date().getTime() + 1000 * 60 * 60 * 24 * 365 * 5, + t_s: new Date().getTime() + 60 * 60 * 24 * 365 * 5, }, }); await tx.transition("next", {}); diff --git a/packages/taler-util/src/RequestThrottler.ts b/packages/taler-util/src/RequestThrottler.ts index 7689b4215..a151cc634 100644 --- a/packages/taler-util/src/RequestThrottler.ts +++ b/packages/taler-util/src/RequestThrottler.ts @@ -15,7 +15,7 @@ */ import { Logger } from "./logging.js"; -import { getTimestampNow, timestampCmp, timestampDifference } from "./time.js"; +import { AbsoluteTime } from "./time.js"; /** * Implementation of token bucket throttling. @@ -46,16 +46,16 @@ class OriginState { tokensSecond: number = MAX_PER_SECOND; tokensMinute: number = MAX_PER_MINUTE; tokensHour: number = MAX_PER_HOUR; - private lastUpdate = getTimestampNow(); + private lastUpdate = AbsoluteTime.now(); private refill(): void { - const now = getTimestampNow(); - if (timestampCmp(now, this.lastUpdate) < 0) { + const now = AbsoluteTime.now(); + if (AbsoluteTime.cmp(now, this.lastUpdate) < 0) { // Did the system time change? this.lastUpdate = now; return; } - const d = timestampDifference(now, this.lastUpdate); + const d = AbsoluteTime.difference(now, this.lastUpdate); if (d.d_ms === "forever") { throw Error("assertion failed"); } diff --git a/packages/taler-util/src/ReserveTransaction.ts b/packages/taler-util/src/ReserveTransaction.ts index b282ef189..50610483f 100644 --- a/packages/taler-util/src/ReserveTransaction.ts +++ b/packages/taler-util/src/ReserveTransaction.ts @@ -38,7 +38,11 @@ import { EddsaPublicKeyString, CoinPublicKeyString, } from "./talerTypes"; -import { Timestamp, codecForTimestamp } from "./time.js"; +import { + AbsoluteTime, + codecForTimestamp, + TalerProtocolTimestamp, +} from "./time.js"; export enum ReserveTransactionType { Withdraw = "WITHDRAW", @@ -98,7 +102,7 @@ export interface ReserveCreditTransaction { /** * Timestamp of the incoming wire transfer. */ - timestamp: Timestamp; + timestamp: TalerProtocolTimestamp; } export interface ReserveClosingTransaction { @@ -139,7 +143,7 @@ export interface ReserveClosingTransaction { /** * Time when the reserve was closed. */ - timestamp: Timestamp; + timestamp: TalerProtocolTimestamp; } export interface ReserveRecoupTransaction { @@ -165,7 +169,7 @@ export interface ReserveRecoupTransaction { /** * Time when the funds were paid back into the reserve. */ - timestamp: Timestamp; + timestamp: TalerProtocolTimestamp; /** * Public key of the coin that was paid back. @@ -182,46 +186,50 @@ export type ReserveTransaction = | ReserveClosingTransaction | ReserveRecoupTransaction; -export const codecForReserveWithdrawTransaction = (): Codec => - buildCodecForObject() - .property("amount", codecForString()) - .property("h_coin_envelope", codecForString()) - .property("h_denom_pub", codecForString()) - .property("reserve_sig", codecForString()) - .property("type", codecForConstString(ReserveTransactionType.Withdraw)) - .property("withdraw_fee", codecForString()) - .build("ReserveWithdrawTransaction"); +export const codecForReserveWithdrawTransaction = + (): Codec => + buildCodecForObject() + .property("amount", codecForString()) + .property("h_coin_envelope", codecForString()) + .property("h_denom_pub", codecForString()) + .property("reserve_sig", codecForString()) + .property("type", codecForConstString(ReserveTransactionType.Withdraw)) + .property("withdraw_fee", codecForString()) + .build("ReserveWithdrawTransaction"); -export const codecForReserveCreditTransaction = (): Codec => - buildCodecForObject() - .property("amount", codecForString()) - .property("sender_account_url", codecForString()) - .property("timestamp", codecForTimestamp) - .property("wire_reference", codecForNumber()) - .property("type", codecForConstString(ReserveTransactionType.Credit)) - .build("ReserveCreditTransaction"); +export const codecForReserveCreditTransaction = + (): Codec => + buildCodecForObject() + .property("amount", codecForString()) + .property("sender_account_url", codecForString()) + .property("timestamp", codecForTimestamp) + .property("wire_reference", codecForNumber()) + .property("type", codecForConstString(ReserveTransactionType.Credit)) + .build("ReserveCreditTransaction"); -export const codecForReserveClosingTransaction = (): Codec => - buildCodecForObject() - .property("amount", codecForString()) - .property("closing_fee", codecForString()) - .property("exchange_pub", codecForString()) - .property("exchange_sig", codecForString()) - .property("h_wire", codecForString()) - .property("timestamp", codecForTimestamp) - .property("type", codecForConstString(ReserveTransactionType.Closing)) - .property("wtid", codecForString()) - .build("ReserveClosingTransaction"); +export const codecForReserveClosingTransaction = + (): Codec => + buildCodecForObject() + .property("amount", codecForString()) + .property("closing_fee", codecForString()) + .property("exchange_pub", codecForString()) + .property("exchange_sig", codecForString()) + .property("h_wire", codecForString()) + .property("timestamp", codecForTimestamp) + .property("type", codecForConstString(ReserveTransactionType.Closing)) + .property("wtid", codecForString()) + .build("ReserveClosingTransaction"); -export const codecForReserveRecoupTransaction = (): Codec => - buildCodecForObject() - .property("amount", codecForString()) - .property("coin_pub", codecForString()) - .property("exchange_pub", codecForString()) - .property("exchange_sig", codecForString()) - .property("timestamp", codecForTimestamp) - .property("type", codecForConstString(ReserveTransactionType.Recoup)) - .build("ReserveRecoupTransaction"); +export const codecForReserveRecoupTransaction = + (): Codec => + buildCodecForObject() + .property("amount", codecForString()) + .property("coin_pub", codecForString()) + .property("exchange_pub", codecForString()) + .property("exchange_sig", codecForString()) + .property("timestamp", codecForTimestamp) + .property("type", codecForConstString(ReserveTransactionType.Recoup)) + .build("ReserveRecoupTransaction"); export const codecForReserveTransaction = (): Codec => buildCodecForUnion() diff --git a/packages/taler-util/src/backupTypes.ts b/packages/taler-util/src/backupTypes.ts index 41a9ce98e..b31a83831 100644 --- a/packages/taler-util/src/backupTypes.ts +++ b/packages/taler-util/src/backupTypes.ts @@ -54,7 +54,7 @@ * Imports. */ import { DenominationPubKey, UnblindedSignature } from "./talerTypes.js"; -import { Duration, Timestamp } from "./time.js"; +import { TalerProtocolDuration, TalerProtocolTimestamp } from "./time.js"; /** * Type alias for strings that are to be treated like amounts. @@ -120,7 +120,7 @@ export interface WalletBackupContentV1 { * This timestamp should only be advanced if the content * of the backup changes. */ - timestamp: Timestamp; + timestamp: TalerProtocolTimestamp; /** * Per-exchange data sorted by exchange master public key. @@ -333,10 +333,10 @@ export interface BackupRecoupGroup { /** * Timestamp when the recoup was started. */ - timestamp_created: Timestamp; + timestamp_created: TalerProtocolTimestamp; - timestamp_finish?: Timestamp; - finish_clock?: Timestamp; + timestamp_finish?: TalerProtocolTimestamp; + finish_clock?: TalerProtocolTimestamp; finish_is_failure?: boolean; /** @@ -486,14 +486,14 @@ export interface BackupTip { * Has the user accepted the tip? Only after the tip has been accepted coins * withdrawn from the tip may be used. */ - timestamp_accepted: Timestamp | undefined; + timestamp_accepted: TalerProtocolTimestamp | undefined; /** * When was the tip first scanned by the wallet? */ - timestamp_created: Timestamp; + timestamp_created: TalerProtocolTimestamp; - timestamp_finished?: Timestamp; + timestamp_finished?: TalerProtocolTimestamp; finish_is_failure?: boolean; /** @@ -504,7 +504,7 @@ export interface BackupTip { /** * Timestamp, the tip can't be picked up anymore after this deadline. */ - timestamp_expiration: Timestamp; + timestamp_expiration: TalerProtocolTimestamp; /** * The exchange that will sign our coins, chosen by the merchant. @@ -613,9 +613,9 @@ export interface BackupRefreshGroup { */ old_coins: BackupRefreshOldCoin[]; - timestamp_created: Timestamp; + timestamp_created: TalerProtocolTimestamp; - timestamp_finish?: Timestamp; + timestamp_finish?: TalerProtocolTimestamp; finish_is_failure?: boolean; } @@ -636,9 +636,9 @@ export interface BackupWithdrawalGroup { * When was the withdrawal operation started started? * Timestamp in milliseconds. */ - timestamp_created: Timestamp; + timestamp_created: TalerProtocolTimestamp; - timestamp_finish?: Timestamp; + timestamp_finish?: TalerProtocolTimestamp; finish_is_failure?: boolean; /** @@ -672,12 +672,12 @@ export interface BackupRefundItemCommon { /** * Execution time as claimed by the merchant */ - execution_time: Timestamp; + execution_time: TalerProtocolTimestamp; /** * Time when the wallet became aware of the refund. */ - obtained_time: Timestamp; + obtained_time: TalerProtocolTimestamp; /** * Amount refunded for the coin. @@ -788,7 +788,7 @@ export interface BackupPurchase { * Timestamp of the first time that sending a payment to the merchant * for this purchase was successful. */ - timestamp_first_successful_pay: Timestamp | undefined; + timestamp_first_successful_pay: TalerProtocolTimestamp | undefined; /** * Signature by the merchant confirming the payment. @@ -799,7 +799,7 @@ export interface BackupPurchase { * When was the purchase made? * Refers to the time that the user accepted. */ - timestamp_accept: Timestamp; + timestamp_accept: TalerProtocolTimestamp; /** * Pending refunds for the purchase. A refund is pending @@ -815,7 +815,7 @@ export interface BackupPurchase { /** * Continue querying the refund status until this deadline has expired. */ - auto_refund_deadline: Timestamp | undefined; + auto_refund_deadline: TalerProtocolTimestamp | undefined; } /** @@ -857,22 +857,22 @@ export interface BackupDenomination { /** * Validity start date of the denomination. */ - stamp_start: Timestamp; + stamp_start: TalerProtocolTimestamp; /** * Date after which the currency can't be withdrawn anymore. */ - stamp_expire_withdraw: Timestamp; + stamp_expire_withdraw: TalerProtocolTimestamp; /** * Date after the denomination officially doesn't exist anymore. */ - stamp_expire_legal: Timestamp; + stamp_expire_legal: TalerProtocolTimestamp; /** * Data after which coins of this denomination can't be deposited anymore. */ - stamp_expire_deposit: Timestamp; + stamp_expire_deposit: TalerProtocolTimestamp; /** * Signature by the exchange's master key over the denomination @@ -903,7 +903,7 @@ export interface BackupDenomination { * The list issue date of the exchange "/keys" response * that this denomination was last seen in. */ - list_issue_date: Timestamp; + list_issue_date: TalerProtocolTimestamp; } /** @@ -923,14 +923,14 @@ export interface BackupReserve { /** * Time when the reserve was created. */ - timestamp_created: Timestamp; + timestamp_created: TalerProtocolTimestamp; /** * Timestamp of the last observed activity. * * Used to compute when to give up querying the exchange. */ - timestamp_last_activity: Timestamp; + timestamp_last_activity: TalerProtocolTimestamp; /** * Timestamp of when the reserve closed. @@ -938,7 +938,7 @@ export interface BackupReserve { * Note that the last activity can be after the closing time * due to recouping. */ - timestamp_closed?: Timestamp; + timestamp_closed?: TalerProtocolTimestamp; /** * Wire information (as payto URI) for the bank account that @@ -977,14 +977,14 @@ export interface BackupReserve { /** * Time when the information about this reserve was posted to the bank. */ - timestamp_reserve_info_posted: Timestamp | undefined; + timestamp_reserve_info_posted: TalerProtocolTimestamp | undefined; /** * Time when the reserve was confirmed by the bank. * * Set to undefined if not confirmed yet. */ - timestamp_bank_confirmed: Timestamp | undefined; + timestamp_bank_confirmed: TalerProtocolTimestamp | undefined; }; /** @@ -1033,12 +1033,12 @@ export interface BackupExchangeWireFee { /** * Start date of the fee. */ - start_stamp: Timestamp; + start_stamp: TalerProtocolTimestamp; /** * End date of the fee. */ - end_stamp: Timestamp; + end_stamp: TalerProtocolTimestamp; /** * Signature made by the exchange master key. @@ -1050,9 +1050,9 @@ export interface BackupExchangeWireFee { * Structure of one exchange signing key in the /keys response. */ export class BackupExchangeSignKey { - stamp_start: Timestamp; - stamp_expire: Timestamp; - stamp_end: Timestamp; + stamp_start: TalerProtocolTimestamp; + stamp_expire: TalerProtocolTimestamp; + stamp_end: TalerProtocolTimestamp; key: string; master_sig: string; } @@ -1112,7 +1112,7 @@ export interface BackupExchange { * * Used to facilitate automatic merging. */ - update_clock: Timestamp; + update_clock: TalerProtocolTimestamp; } /** @@ -1161,7 +1161,7 @@ export interface BackupExchangeDetails { /** * Closing delay of reserves. */ - reserve_closing_delay: Duration; + reserve_closing_delay: TalerProtocolDuration; /** * Signing keys we got from the exchange, can also contain @@ -1187,7 +1187,7 @@ export interface BackupExchangeDetails { /** * Timestamp when the ToS has been accepted. */ - tos_accepted_timestamp: Timestamp | undefined; + tos_accepted_timestamp: TalerProtocolTimestamp | undefined; } export enum BackupProposalStatus { @@ -1248,7 +1248,7 @@ export interface BackupProposal { * Timestamp of when the record * was created. */ - timestamp: Timestamp; + timestamp: TalerProtocolTimestamp; /** * Private key for the nonce. diff --git a/packages/taler-util/src/talerTypes.ts b/packages/taler-util/src/talerTypes.ts index 4ccfffce0..b1bf6ab38 100644 --- a/packages/taler-util/src/talerTypes.ts +++ b/packages/taler-util/src/talerTypes.ts @@ -38,15 +38,14 @@ import { codecForConstNumber, buildCodecForUnion, codecForConstString, - codecForEither, } from "./codec.js"; import { - Timestamp, codecForTimestamp, - Duration, codecForDuration, + TalerProtocolTimestamp, + TalerProtocolDuration, } from "./time.js"; -import { Amounts, codecForAmountString } from "./amounts.js"; +import { codecForAmountString } from "./amounts.js"; import { strcmp } from "./helpers.js"; /** @@ -86,24 +85,24 @@ export class ExchangeDenomination { /** * Start date from which withdraw is allowed. */ - stamp_start: Timestamp; + stamp_start: TalerProtocolTimestamp; /** * End date for withdrawing. */ - stamp_expire_withdraw: Timestamp; + stamp_expire_withdraw: TalerProtocolTimestamp; /** * Expiration date after which the exchange can forget about * the currency. */ - stamp_expire_legal: Timestamp; + stamp_expire_legal: TalerProtocolTimestamp; /** * Date after which the coins of this denomination can't be * deposited anymore. */ - stamp_expire_deposit: Timestamp; + stamp_expire_deposit: TalerProtocolTimestamp; /** * Signature over the denomination information by the exchange's master @@ -394,7 +393,7 @@ export interface Product { taxes?: Tax[]; // time indicating when this product should be delivered - delivery_date?: Timestamp; + delivery_date?: TalerProtocolTimestamp; } export interface InternationalizedString { @@ -413,7 +412,7 @@ export interface ContractTerms { /** * Hash of the merchant's wire details. */ - auto_refund?: Duration; + auto_refund?: TalerProtocolDuration; /** * Wire method the merchant wants to use. @@ -445,7 +444,7 @@ export interface ContractTerms { /** * Deadline to pay for the contract. */ - pay_deadline: Timestamp; + pay_deadline: TalerProtocolTimestamp; /** * Maximum deposit fee covered by the merchant. @@ -466,7 +465,7 @@ export interface ContractTerms { * Time indicating when the order should be delivered. * May be overwritten by individual products. */ - delivery_date?: Timestamp; + delivery_date?: TalerProtocolTimestamp; /** * Delivery location for (all!) products. @@ -486,17 +485,17 @@ export interface ContractTerms { /** * Deadline for refunds. */ - refund_deadline: Timestamp; + refund_deadline: TalerProtocolTimestamp; /** * Deadline for the wire transfer. */ - wire_transfer_deadline: Timestamp; + wire_transfer_deadline: TalerProtocolTimestamp; /** * Time when the contract was generated by the merchant. */ - timestamp: Timestamp; + timestamp: TalerProtocolTimestamp; /** * Order id to uniquely identify the purchase within @@ -700,9 +699,9 @@ export class Recoup { * Structure of one exchange signing key in the /keys response. */ export class ExchangeSignKeyJson { - stamp_start: Timestamp; - stamp_expire: Timestamp; - stamp_end: Timestamp; + stamp_start: TalerProtocolTimestamp; + stamp_expire: TalerProtocolTimestamp; + stamp_end: TalerProtocolTimestamp; key: EddsaPublicKeyString; master_sig: EddsaSignatureString; } @@ -729,7 +728,7 @@ export class ExchangeKeysJson { /** * Timestamp when this response was issued. */ - list_issue_date: Timestamp; + list_issue_date: TalerProtocolTimestamp; /** * List of revoked denominations. @@ -747,7 +746,7 @@ export class ExchangeKeysJson { */ version: string; - reserve_closing_delay: Duration; + reserve_closing_delay: TalerProtocolDuration; } /** @@ -774,12 +773,12 @@ export class WireFeesJson { /** * Date from which the fee applies. */ - start_date: Timestamp; + start_date: TalerProtocolTimestamp; /** * Data after which the fee doesn't apply anymore. */ - end_date: Timestamp; + end_date: TalerProtocolTimestamp; } export interface AccountInfo { @@ -850,7 +849,7 @@ export class TipPickupGetResponse { exchange_url: string; - expiration: Timestamp; + expiration: TalerProtocolTimestamp; } export enum DenomKeyType { @@ -1067,7 +1066,7 @@ export interface MerchantCoinRefundSuccessStatus { // to the customer. refund_amount: AmountString; - execution_time: Timestamp; + execution_time: TalerProtocolTimestamp; } export interface MerchantCoinRefundFailureStatus { @@ -1092,7 +1091,7 @@ export interface MerchantCoinRefundFailureStatus { // to the customer. refund_amount: AmountString; - execution_time: Timestamp; + execution_time: TalerProtocolTimestamp; } export interface MerchantOrderStatusUnpaid { @@ -1733,7 +1732,7 @@ export interface DepositSuccess { transaction_base_url?: string; // timestamp when the deposit was received by the exchange. - exchange_timestamp: Timestamp; + exchange_timestamp: TalerProtocolTimestamp; // the EdDSA signature of TALER_DepositConfirmationPS using a current // signing key of the exchange affirming the successful diff --git a/packages/taler-util/src/time.ts b/packages/taler-util/src/time.ts index 9f9571019..43cb7ad4d 100644 --- a/packages/taler-util/src/time.ts +++ b/packages/taler-util/src/time.ts @@ -23,13 +23,41 @@ */ import { Codec, renderContext, Context } from "./codec.js"; -export interface Timestamp { +export interface AbsoluteTime { /** * Timestamp in milliseconds. */ readonly t_ms: number | "never"; } +export interface TalerProtocolTimestamp { + readonly t_s: number | "never"; +} + +export namespace TalerProtocolTimestamp { + export function now(): TalerProtocolTimestamp { + return AbsoluteTime.toTimestamp(AbsoluteTime.now()); + } + + export function zero(): TalerProtocolTimestamp { + return { + t_s: 0, + }; + } + + export function never(): TalerProtocolTimestamp { + return { + t_s: "never", + }; + } + + export function fromSeconds(s: number): TalerProtocolTimestamp { + return { + t_s: s, + }; + } +} + export interface Duration { /** * Duration in milliseconds. @@ -37,40 +65,32 @@ export interface Duration { readonly d_ms: number | "forever"; } +export interface TalerProtocolDuration { + readonly d_us: number | "forever"; +} + let timeshift = 0; export function setDangerousTimetravel(dt: number): void { timeshift = dt; } -export function getTimestampNow(): Timestamp { - return { - t_ms: new Date().getTime() + timeshift, - }; -} - -export function isTimestampExpired(t: Timestamp) { - return timestampCmp(t, getTimestampNow()) <= 0; -} - -export function getDurationRemaining( - deadline: Timestamp, - now = getTimestampNow(), -): Duration { - if (deadline.t_ms === "never") { - return { d_ms: "forever" }; - } - if (now.t_ms === "never") { - throw Error("invalid argument for 'now'"); - } - if (deadline.t_ms < now.t_ms) { - return { d_ms: 0 }; - } - return { d_ms: deadline.t_ms - now.t_ms }; -} - export namespace Duration { - export const getRemaining = getDurationRemaining; + export function getRemaining( + deadline: AbsoluteTime, + now = AbsoluteTime.now(), + ): Duration { + if (deadline.t_ms === "never") { + return { d_ms: "forever" }; + } + if (now.t_ms === "never") { + throw Error("invalid argument for 'now'"); + } + if (deadline.t_ms < now.t_ms) { + return { d_ms: 0 }; + } + return { d_ms: deadline.t_ms - now.t_ms }; + } export function toIntegerYears(d: Duration): number { if (typeof d.d_ms !== "number") { throw Error("infinite duration"); @@ -81,33 +101,152 @@ export namespace Duration { export function getForever(): Duration { return { d_ms: "forever" }; } + export function fromTalerProtocolDuration( + d: TalerProtocolDuration, + ): Duration { + if (d.d_us === "forever") { + return { + d_ms: "forever", + }; + } + return { + d_ms: d.d_us / 1000, + }; + } } -export namespace Timestamp { - export const now = getTimestampNow; - export const min = timestampMin; - export const isExpired = isTimestampExpired; - export const truncateToSecond = timestampTruncateToSecond; -} +export namespace AbsoluteTime { + export function now(): AbsoluteTime { + return { + t_ms: new Date().getTime() + timeshift, + }; + } -export function timestampMin(t1: Timestamp, t2: Timestamp): Timestamp { - if (t1.t_ms === "never") { - return { t_ms: t2.t_ms }; + export function never(): AbsoluteTime { + return { + t_ms: "never", + }; } - if (t2.t_ms === "never") { - return { t_ms: t2.t_ms }; - } - return { t_ms: Math.min(t1.t_ms, t2.t_ms) }; -} -export function timestampMax(t1: Timestamp, t2: Timestamp): Timestamp { - if (t1.t_ms === "never") { - return { t_ms: "never" }; + export function cmp(t1: AbsoluteTime, t2: AbsoluteTime): number { + if (t1.t_ms === "never") { + if (t2.t_ms === "never") { + return 0; + } + return 1; + } + if (t2.t_ms === "never") { + return -1; + } + if (t1.t_ms == t2.t_ms) { + return 0; + } + if (t1.t_ms > t2.t_ms) { + return 1; + } + return -1; } - if (t2.t_ms === "never") { - return { t_ms: "never" }; + + export function min(t1: AbsoluteTime, t2: AbsoluteTime): AbsoluteTime { + if (t1.t_ms === "never") { + return { t_ms: t2.t_ms }; + } + if (t2.t_ms === "never") { + return { t_ms: t2.t_ms }; + } + return { t_ms: Math.min(t1.t_ms, t2.t_ms) }; + } + + export function max(t1: AbsoluteTime, t2: AbsoluteTime): AbsoluteTime { + if (t1.t_ms === "never") { + return { t_ms: "never" }; + } + if (t2.t_ms === "never") { + return { t_ms: "never" }; + } + return { t_ms: Math.max(t1.t_ms, t2.t_ms) }; + } + + export function difference(t1: AbsoluteTime, t2: AbsoluteTime): Duration { + if (t1.t_ms === "never") { + return { d_ms: "forever" }; + } + if (t2.t_ms === "never") { + return { d_ms: "forever" }; + } + return { d_ms: Math.abs(t1.t_ms - t2.t_ms) }; + } + + export function isExpired(t: AbsoluteTime) { + return cmp(t, now()) <= 0; + } + + export function fromTimestamp(t: TalerProtocolTimestamp): AbsoluteTime { + if (t.t_s === "never") { + return { t_ms: "never" }; + } + return { + t_ms: t.t_s * 1000, + }; + } + + export function toTimestamp(at: AbsoluteTime): TalerProtocolTimestamp { + if (at.t_ms === "never") { + return { t_s: "never" }; + } + return { + t_s: Math.floor(at.t_ms / 1000), + }; + } + + export function isBetween( + t: AbsoluteTime, + start: AbsoluteTime, + end: AbsoluteTime, + ): boolean { + if (cmp(t, start) < 0) { + return false; + } + if (cmp(t, end) > 0) { + return false; + } + return true; + } + + export function toIsoString(t: AbsoluteTime): string { + if (t.t_ms === "never") { + return ""; + } else { + return new Date(t.t_ms).toISOString(); + } + } + + export function addDuration(t1: AbsoluteTime, d: Duration): AbsoluteTime { + if (t1.t_ms === "never" || d.d_ms === "forever") { + return { t_ms: "never" }; + } + return { t_ms: t1.t_ms + d.d_ms }; + } + + export function subtractDuraction( + t1: AbsoluteTime, + d: Duration, + ): AbsoluteTime { + if (t1.t_ms === "never") { + return { t_ms: "never" }; + } + if (d.d_ms === "forever") { + return { t_ms: 0 }; + } + return { t_ms: Math.max(0, t1.t_ms - d.d_ms) }; + } + + export function stringify(t: AbsoluteTime): string { + if (t.t_ms === "never") { + return "never"; + } + return new Date(t.t_ms).toISOString(); } - return { t_ms: Math.max(t1.t_ms, t2.t_ms) }; } const SECONDS = 1000; @@ -135,19 +274,6 @@ export function durationFromSpec(spec: { return { d_ms }; } -/** - * Truncate a timestamp so that that it represents a multiple - * of seconds. The timestamp is always rounded down. - */ -export function timestampTruncateToSecond(t1: Timestamp): Timestamp { - if (t1.t_ms === "never") { - return { t_ms: "never" }; - } - return { - t_ms: Math.floor(t1.t_ms / 1000) * 1000, - }; -} - export function durationMin(d1: Duration, d2: Duration): Duration { if (d1.d_ms === "forever") { return { d_ms: d2.d_ms }; @@ -182,111 +308,33 @@ export function durationAdd(d1: Duration, d2: Duration): Duration { return { d_ms: d1.d_ms + d2.d_ms }; } -export function timestampCmp(t1: Timestamp, t2: Timestamp): number { - if (t1.t_ms === "never") { - if (t2.t_ms === "never") { - return 0; - } - return 1; - } - if (t2.t_ms === "never") { - return -1; - } - if (t1.t_ms == t2.t_ms) { - return 0; - } - if (t1.t_ms > t2.t_ms) { - return 1; - } - return -1; -} - -export function timestampAddDuration(t1: Timestamp, d: Duration): Timestamp { - if (t1.t_ms === "never" || d.d_ms === "forever") { - return { t_ms: "never" }; - } - return { t_ms: t1.t_ms + d.d_ms }; -} - -export function timestampSubtractDuraction( - t1: Timestamp, - d: Duration, -): Timestamp { - if (t1.t_ms === "never") { - return { t_ms: "never" }; - } - if (d.d_ms === "forever") { - return { t_ms: 0 }; - } - return { t_ms: Math.max(0, t1.t_ms - d.d_ms) }; -} - -export function stringifyTimestamp(t: Timestamp): string { - if (t.t_ms === "never") { - return "never"; - } - return new Date(t.t_ms).toISOString(); -} - -export function timestampDifference(t1: Timestamp, t2: Timestamp): Duration { - if (t1.t_ms === "never") { - return { d_ms: "forever" }; - } - if (t2.t_ms === "never") { - return { d_ms: "forever" }; - } - return { d_ms: Math.abs(t1.t_ms - t2.t_ms) }; -} - -export function timestampToIsoString(t: Timestamp): string { - if (t.t_ms === "never") { - return ""; - } else { - return new Date(t.t_ms).toISOString(); - } -} - -export function timestampIsBetween( - t: Timestamp, - start: Timestamp, - end: Timestamp, -): boolean { - if (timestampCmp(t, start) < 0) { - return false; - } - if (timestampCmp(t, end) > 0) { - return false; - } - return true; -} - -export const codecForTimestamp: Codec = { - decode(x: any, c?: Context): Timestamp { - const t_ms = x.t_ms; - if (typeof t_ms === "string") { - if (t_ms === "never") { - return { t_ms: "never" }; +export const codecForTimestamp: Codec = { + decode(x: any, c?: Context): TalerProtocolTimestamp { + const t_s = x.t_s; + if (typeof t_s === "string") { + if (t_s === "never") { + return { t_s: "never" }; } throw Error(`expected timestamp at ${renderContext(c)}`); } - if (typeof t_ms === "number") { - return { t_ms }; + if (typeof t_s === "number") { + return { t_s }; } throw Error(`expected timestamp at ${renderContext(c)}`); }, }; -export const codecForDuration: Codec = { - decode(x: any, c?: Context): Duration { - const d_ms = x.d_ms; - if (typeof d_ms === "string") { - if (d_ms === "forever") { - return { d_ms: "forever" }; +export const codecForDuration: Codec = { + decode(x: any, c?: Context): TalerProtocolDuration { + const d_us = x.d_us; + if (typeof d_us === "string") { + if (d_us === "forever") { + return { d_us: "forever" }; } throw Error(`expected duration at ${renderContext(c)}`); } - if (typeof d_ms === "number") { - return { d_ms }; + if (typeof d_us === "number") { + return { d_us }; } throw Error(`expected duration at ${renderContext(c)}`); }, diff --git a/packages/taler-util/src/transactionsTypes.ts b/packages/taler-util/src/transactionsTypes.ts index e780ca411..bccbc7737 100644 --- a/packages/taler-util/src/transactionsTypes.ts +++ b/packages/taler-util/src/transactionsTypes.ts @@ -24,7 +24,7 @@ /** * Imports. */ -import { Timestamp } from "./time.js"; +import { TalerProtocolTimestamp } from "./time.js"; import { AmountString, Product, @@ -73,7 +73,7 @@ export interface TransactionCommon { type: TransactionType; // main timestamp of the transaction - timestamp: Timestamp; + timestamp: TalerProtocolTimestamp; // true if the transaction is still pending, false otherwise // If a transaction is not longer pending, its timestamp will be updated, diff --git a/packages/taler-util/src/types-test.ts b/packages/taler-util/src/types-test.ts index 6998bb5fb..e8af13119 100644 --- a/packages/taler-util/src/types-test.ts +++ b/packages/taler-util/src/types-test.ts @@ -29,13 +29,13 @@ test("contract terms validation", (t) => { merchant_pub: "12345", merchant: { name: "Foo" }, order_id: "test_order", - pay_deadline: { t_ms: 42 }, - wire_transfer_deadline: { t_ms: 42 }, + pay_deadline: { t_s: 42 }, + wire_transfer_deadline: { t_s: 42 }, merchant_base_url: "https://example.com/pay", products: [], - refund_deadline: { t_ms: 42 }, + refund_deadline: { t_s: 42 }, summary: "hello", - timestamp: { t_ms: 42 }, + timestamp: { t_s: 42 }, wire_method: "test", }; @@ -71,13 +71,13 @@ test("contract terms validation (locations)", (t) => { }, }, order_id: "test_order", - pay_deadline: { t_ms: 42 }, - wire_transfer_deadline: { t_ms: 42 }, + pay_deadline: { t_s: 42 }, + wire_transfer_deadline: { t_s: 42 }, merchant_base_url: "https://example.com/pay", products: [], - refund_deadline: { t_ms: 42 }, + refund_deadline: { t_s: 42 }, summary: "hello", - timestamp: { t_ms: 42 }, + timestamp: { t_s: 42 }, wire_method: "test", delivery_location: { country: "FR", diff --git a/packages/taler-util/src/walletTypes.ts b/packages/taler-util/src/walletTypes.ts index 9a3f1f8f8..3c4fa96c7 100644 --- a/packages/taler-util/src/walletTypes.ts +++ b/packages/taler-util/src/walletTypes.ts @@ -32,7 +32,11 @@ import { codecForAmountJson, codecForAmountString, } from "./amounts.js"; -import { Timestamp, codecForTimestamp } from "./time.js"; +import { + AbsoluteTime, + codecForTimestamp, + TalerProtocolTimestamp, +} from "./time.js"; import { buildCodecForObject, codecForString, @@ -299,7 +303,7 @@ export interface PrepareTipResult { * Time when the tip will expire. After it expired, it can't be picked * up anymore. */ - expirationTimestamp: Timestamp; + expirationTimestamp: TalerProtocolTimestamp; } export const codecForPrepareTipResult = (): Codec => @@ -460,7 +464,7 @@ export interface TalerErrorDetails { /** * Minimal information needed about a planchet for unblinding a signature. - * + * * Can be a withdrawal/tipping/refresh planchet. */ export interface PlanchetUnblindInfo { @@ -527,8 +531,8 @@ export interface DepositInfo { coinPub: string; coinPriv: string; spendAmount: AmountJson; - timestamp: Timestamp; - refundDeadline: Timestamp; + timestamp: TalerProtocolTimestamp; + refundDeadline: TalerProtocolTimestamp; merchantPub: string; feeDeposit: AmountJson; wireInfoHash: string; diff --git a/packages/taler-wallet-cli/src/harness/harness.ts b/packages/taler-wallet-cli/src/harness/harness.ts index 1500d7743..63bb17fcc 100644 --- a/packages/taler-wallet-cli/src/harness/harness.ts +++ b/packages/taler-wallet-cli/src/harness/harness.ts @@ -478,7 +478,7 @@ class BankServiceBase { protected globalTestState: GlobalTestState, protected bankConfig: BankConfig, protected configFile: string, - ) { } + ) {} } /** @@ -920,7 +920,7 @@ export class FakeBankService { private globalTestState: GlobalTestState, private bankConfig: FakeBankConfig, private configFile: string, - ) { } + ) {} async start(): Promise { this.proc = this.globalTestState.spawnService( @@ -1175,7 +1175,7 @@ export class ExchangeService implements ExchangeServiceInterface { private exchangeConfig: ExchangeConfig, private configFilename: string, private keyPair: EddsaKeyPair, - ) { } + ) {} get name() { return this.exchangeConfig.name; @@ -1276,7 +1276,7 @@ export class ExchangeService implements ExchangeServiceInterface { accTargetType, `${this.exchangeConfig.currency}:0.01`, `${this.exchangeConfig.currency}:0.01`, - // `${this.exchangeConfig.currency}:0.01`, + `${this.exchangeConfig.currency}:0.01`, "upload", ], ); @@ -1398,7 +1398,7 @@ export class MerchantApiClient { constructor( private baseUrl: string, public readonly auth: MerchantAuthConfiguration, - ) { } + ) {} async changeAuth(auth: MerchantAuthConfiguration): Promise { const url = new URL("private/auth", this.baseUrl); @@ -1591,7 +1591,7 @@ export class MerchantService implements MerchantServiceInterface { private globalState: GlobalTestState, private merchantConfig: MerchantConfig, private configFilename: string, - ) { } + ) {} private currentTimetravel: Duration | undefined; @@ -1888,8 +1888,10 @@ export class WalletCli { const resp = await sh( self.globalTestState, `wallet-${self.name}`, - `taler-wallet-cli ${self.timetravelArg ?? "" - } --no-throttle -LTRACE --wallet-db '${self.dbfile + `taler-wallet-cli ${ + self.timetravelArg ?? "" + } --no-throttle -LTRACE --wallet-db '${ + self.dbfile }' api '${op}' ${shellWrap(JSON.stringify(payload))}`, ); console.log("--- wallet core response ---"); diff --git a/packages/taler-wallet-cli/src/harness/merchantApiTypes.ts b/packages/taler-wallet-cli/src/harness/merchantApiTypes.ts index a93a0ed25..dcb1a2b42 100644 --- a/packages/taler-wallet-cli/src/harness/merchantApiTypes.ts +++ b/packages/taler-wallet-cli/src/harness/merchantApiTypes.ts @@ -38,7 +38,7 @@ import { codecForAny, buildCodecForUnion, AmountString, - Timestamp, + AbsoluteTime, CoinPublicKeyString, EddsaPublicKeyString, codecForAmountString, @@ -195,7 +195,7 @@ export interface RefundDetails { reason: string; // when was the refund approved - timestamp: Timestamp; + timestamp: AbsoluteTime; // Total amount that was refunded (minus a refund fee). amount: AmountString; @@ -209,7 +209,7 @@ export interface TransactionWireTransfer { wtid: string; // execution time of the wire transfer - execution_time: Timestamp; + execution_time: AbsoluteTime; // Total amount that has been wire transferred // to the merchant @@ -247,10 +247,10 @@ export interface ReserveStatusEntry { reserve_pub: string; // Timestamp when it was established - creation_time: Timestamp; + creation_time: AbsoluteTime; // Timestamp when it expires - expiration_time: Timestamp; + expiration_time: AbsoluteTime; // Initial amount as per reserve creation call merchant_initial_amount: AmountString; @@ -281,7 +281,7 @@ export interface TipCreateConfirmation { tip_status_url: string; // when does the tip expire - tip_expiration: Timestamp; + tip_expiration: AbsoluteTime; } export interface TipCreateRequest { diff --git a/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts b/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts index 8e4109752..8a11b79c6 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts @@ -32,6 +32,7 @@ import { BankApi, BankAccessApi, CreditDebitIndicator, + OperationFailedError, } from "@gnu-taler/taler-wallet-core"; /** @@ -106,8 +107,7 @@ export async function runBankApiTest(t: GlobalTestState) { const e = await t.assertThrowsAsync(async () => { await BankApi.registerAccount(bank, "user1", "pw1"); }); - t.assertAxiosError(e); - t.assertTrue(e.response?.status === 409); + t.assertTrue(e.details.httpStatusCode === 409); } let balResp = await BankAccessApi.getAccountBalance(bank, bankUser); diff --git a/packages/taler-wallet-cli/src/integrationtests/test-exchange-timetravel.ts b/packages/taler-wallet-cli/src/integrationtests/test-exchange-timetravel.ts index ed07114eb..e2b81ee6a 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-exchange-timetravel.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-exchange-timetravel.ts @@ -18,11 +18,11 @@ * Imports. */ import { + AbsoluteTime, codecForExchangeKeysJson, DenominationPubKey, Duration, durationFromSpec, - stringifyTimestamp, } from "@gnu-taler/taler-util"; import { NodeHttpLib, @@ -174,27 +174,37 @@ export async function runExchangeTimetravelTest(t: GlobalTestState) { const denomPubs1 = keys1.denoms.map((x) => { return { denomPub: x.denom_pub, - expireDeposit: stringifyTimestamp(x.stamp_expire_deposit), + expireDeposit: AbsoluteTime.stringify( + AbsoluteTime.fromTimestamp(x.stamp_expire_deposit), + ), }; }); const denomPubs2 = keys2.denoms.map((x) => { return { denomPub: x.denom_pub, - expireDeposit: stringifyTimestamp(x.stamp_expire_deposit), + expireDeposit: AbsoluteTime.stringify( + AbsoluteTime.fromTimestamp(x.stamp_expire_deposit), + ), }; }); const dps2 = new Set(denomPubs2.map((x) => x.denomPub)); console.log("=== KEYS RESPONSE 1 ==="); - console.log("list issue date", stringifyTimestamp(keys1.list_issue_date)); + console.log( + "list issue date", + AbsoluteTime.stringify(AbsoluteTime.fromTimestamp(keys1.list_issue_date)), + ); console.log("num denoms", keys1.denoms.length); console.log("denoms", JSON.stringify(denomPubs1, undefined, 2)); console.log("=== KEYS RESPONSE 2 ==="); - console.log("list issue date", stringifyTimestamp(keys2.list_issue_date)); + console.log( + "list issue date", + AbsoluteTime.stringify(AbsoluteTime.fromTimestamp(keys2.list_issue_date)), + ); console.log("num denoms", keys2.denoms.length); console.log("denoms", JSON.stringify(denomPubs2, undefined, 2)); @@ -214,8 +224,8 @@ export async function runExchangeTimetravelTest(t: GlobalTestState) { `denomination with public key ${da.denomPub} is not present in new /keys response`, ); console.log( - `the new /keys response was issued ${stringifyTimestamp( - keys2.list_issue_date, + `the new /keys response was issued ${AbsoluteTime.stringify( + AbsoluteTime.fromTimestamp(keys2.list_issue_date), )}`, ); console.log( diff --git a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts index 33aad80d2..8f7d77712 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts @@ -18,9 +18,8 @@ * Imports. */ import { + AbsoluteTime, ContractTerms, - getTimestampNow, - timestampTruncateToSecond, } from "@gnu-taler/taler-util"; import { WalletApiOperation, @@ -277,7 +276,7 @@ export async function runLibeufinBasicTest(t: GlobalTestState) { summary: "Buy me!", amount: "EUR:5", fulfillment_url: "taler://fulfillment-success/thx", - wire_transfer_deadline: timestampTruncateToSecond(getTimestampNow()), + wire_transfer_deadline: AbsoluteTime.toTimestamp(AbsoluteTime.now()), }; await makeTestPayment(t, { wallet, merchant, order }); diff --git a/packages/taler-wallet-cli/src/integrationtests/test-merchant-instances-delete.ts b/packages/taler-wallet-cli/src/integrationtests/test-merchant-instances-delete.ts index ef926c4af..2bef87b20 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-merchant-instances-delete.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-merchant-instances-delete.ts @@ -25,7 +25,7 @@ import { MerchantApiClient, MerchantService, setupDb, - getPayto + getPayto, } from "../harness/harness.js"; /** diff --git a/packages/taler-wallet-cli/src/integrationtests/test-refund-auto.ts b/packages/taler-wallet-cli/src/integrationtests/test-refund-auto.ts index 230fc942d..574177c8c 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-refund-auto.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-refund-auto.ts @@ -18,7 +18,10 @@ * Imports. */ import { GlobalTestState, MerchantPrivateApi } from "../harness/harness.js"; -import { createSimpleTestkudosEnvironment, withdrawViaBank } from "../harness/helpers.js"; +import { + createSimpleTestkudosEnvironment, + withdrawViaBank, +} from "../harness/helpers.js"; import { durationFromSpec } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; @@ -28,12 +31,8 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; export async function runRefundAutoTest(t: GlobalTestState) { // Set up test environment - const { - wallet, - bank, - exchange, - merchant, - } = await createSimpleTestkudosEnvironment(t); + const { wallet, bank, exchange, merchant } = + await createSimpleTestkudosEnvironment(t); // Withdraw digital cash into the wallet. @@ -46,7 +45,7 @@ export async function runRefundAutoTest(t: GlobalTestState) { amount: "TESTKUDOS:5", fulfillment_url: "taler://fulfillment-success/thx", auto_refund: { - d_ms: 3000, + d_us: 3000 * 1000, }, }, refund_delay: durationFromSpec({ minutes: 5 }), diff --git a/packages/taler-wallet-cli/src/integrationtests/test-refund-gone.ts b/packages/taler-wallet-cli/src/integrationtests/test-refund-gone.ts index c6442a240..24600384b 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-refund-gone.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-refund-gone.ts @@ -24,10 +24,8 @@ import { applyTimeTravel, } from "../harness/helpers.js"; import { + AbsoluteTime, durationFromSpec, - timestampAddDuration, - getTimestampNow, - timestampTruncateToSecond, } from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; @@ -55,9 +53,9 @@ export async function runRefundGoneTest(t: GlobalTestState) { summary: "Buy me!", amount: "TESTKUDOS:5", fulfillment_url: "taler://fulfillment-success/thx", - pay_deadline: timestampTruncateToSecond( - timestampAddDuration( - getTimestampNow(), + pay_deadline: AbsoluteTime.toTimestamp( + AbsoluteTime.addDuration( + AbsoluteTime.now(), durationFromSpec({ minutes: 10, }), 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 { diff --git a/packages/taler-wallet-webextension/src/components/PendingTransactions.stories.tsx b/packages/taler-wallet-webextension/src/components/PendingTransactions.stories.tsx index 658a41aa8..ea29d4a78 100644 --- a/packages/taler-wallet-webextension/src/components/PendingTransactions.stories.tsx +++ b/packages/taler-wallet-webextension/src/components/PendingTransactions.stories.tsx @@ -22,7 +22,11 @@ import { PendingTransactionsView as TestedComponent } from "./PendingTransactions"; import { Fragment, h, VNode } from "preact"; import { createExample } from "../test-utils"; -import { Transaction, TransactionType } from "@gnu-taler/taler-util"; +import { + TalerProtocolTimestamp, + Transaction, + TransactionType, +} from "@gnu-taler/taler-util"; export default { title: "component/PendingTransactions", @@ -34,9 +38,7 @@ export const OnePendingTransaction = createExample(TestedComponent, { { amountEffective: "USD:10", type: TransactionType.Withdrawal, - timestamp: { - t_ms: 1, - }, + timestamp: TalerProtocolTimestamp.fromSeconds(1), } as Transaction, ], }); @@ -46,23 +48,17 @@ export const ThreePendingTransactions = createExample(TestedComponent, { { amountEffective: "USD:10", type: TransactionType.Withdrawal, - timestamp: { - t_ms: 1, - }, + timestamp: TalerProtocolTimestamp.fromSeconds(1), } as Transaction, { amountEffective: "USD:10", type: TransactionType.Withdrawal, - timestamp: { - t_ms: 1, - }, + timestamp: TalerProtocolTimestamp.fromSeconds(1), } as Transaction, { amountEffective: "USD:10", type: TransactionType.Withdrawal, - timestamp: { - t_ms: 1, - }, + timestamp: TalerProtocolTimestamp.fromSeconds(1), } as Transaction, ], }); @@ -72,72 +68,52 @@ export const TenPendingTransactions = createExample(TestedComponent, { { amountEffective: "USD:10", type: TransactionType.Withdrawal, - timestamp: { - t_ms: 1, - }, + timestamp: TalerProtocolTimestamp.fromSeconds(1), } as Transaction, { amountEffective: "USD:10", type: TransactionType.Withdrawal, - timestamp: { - t_ms: 1, - }, + timestamp: TalerProtocolTimestamp.fromSeconds(1) } as Transaction, { amountEffective: "USD:10", type: TransactionType.Withdrawal, - timestamp: { - t_ms: 1, - }, + timestamp: TalerProtocolTimestamp.fromSeconds(1), } as Transaction, { amountEffective: "USD:10", type: TransactionType.Withdrawal, - timestamp: { - t_ms: 1, - }, + timestamp: TalerProtocolTimestamp.fromSeconds(1), } as Transaction, { amountEffective: "USD:10", type: TransactionType.Withdrawal, - timestamp: { - t_ms: 1, - }, + timestamp: TalerProtocolTimestamp.fromSeconds(1), } as Transaction, { amountEffective: "USD:10", type: TransactionType.Withdrawal, - timestamp: { - t_ms: 1, - }, + timestamp: TalerProtocolTimestamp.fromSeconds(1), } as Transaction, { amountEffective: "USD:10", type: TransactionType.Withdrawal, - timestamp: { - t_ms: 1, - }, + timestamp: TalerProtocolTimestamp.fromSeconds(1), } as Transaction, { amountEffective: "USD:10", type: TransactionType.Withdrawal, - timestamp: { - t_ms: 1, - }, + timestamp: TalerProtocolTimestamp.fromSeconds(1), } as Transaction, { amountEffective: "USD:10", type: TransactionType.Withdrawal, - timestamp: { - t_ms: 1, - }, + timestamp: TalerProtocolTimestamp.fromSeconds(1), } as Transaction, { amountEffective: "USD:10", type: TransactionType.Withdrawal, - timestamp: { - t_ms: 1, - }, + timestamp: TalerProtocolTimestamp.fromSeconds(1), } as Transaction, ], }); diff --git a/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx b/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx index eed31beb4..7923eb6ad 100644 --- a/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx +++ b/packages/taler-wallet-webextension/src/components/PendingTransactions.tsx @@ -1,4 +1,9 @@ -import { Amounts, NotificationType, Transaction } from "@gnu-taler/taler-util"; +import { + AbsoluteTime, + Amounts, + NotificationType, + Transaction, +} from "@gnu-taler/taler-util"; import { PendingTaskInfo } from "@gnu-taler/taler-wallet-core"; import { Fragment, h, JSX } from "preact"; import { useAsyncAsHook } from "../hooks/useAsyncAsHook"; @@ -70,7 +75,11 @@ export function PendingTransactionsView({ {amount.currency} {Amounts.stringifyValue(amount)} {" "} - -

Withdrawal

-