wallet-core: use typed microsecond timestamps in DB

This commit is contained in:
Florian Dold 2023-09-13 16:08:51 +02:00
parent 59ef010b0e
commit f4587c44fd
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
17 changed files with 216 additions and 143 deletions

View File

@ -52,6 +52,10 @@ export interface TalerProtocolTimestamp {
readonly _flavor?: typeof flavor_TalerProtocolTimestamp; readonly _flavor?: typeof flavor_TalerProtocolTimestamp;
} }
/**
* Precise timestamp, typically used in the wallet-core
* API but not in other Taler APIs so far.
*/
export interface TalerPreciseTimestamp { export interface TalerPreciseTimestamp {
/** /**
* Seconds (as integer) since epoch. * Seconds (as integer) since epoch.

View File

@ -28,7 +28,6 @@ import {
} from "@gnu-taler/idb-bridge"; } from "@gnu-taler/idb-bridge";
import { import {
AgeCommitmentProof, AgeCommitmentProof,
AmountJson,
AmountString, AmountString,
Amounts, Amounts,
AttentionInfo, AttentionInfo,
@ -45,12 +44,8 @@ import {
ExchangeAuditor, ExchangeAuditor,
ExchangeGlobalFees, ExchangeGlobalFees,
HashCodeString, HashCodeString,
InternationalizedString,
Logger, Logger,
MerchantContractTerms,
MerchantInfo,
PayCoinSelection, PayCoinSelection,
PeerContractTerms,
RefreshReason, RefreshReason,
TalerErrorDetail, TalerErrorDetail,
TalerPreciseTimestamp, TalerPreciseTimestamp,
@ -151,6 +146,53 @@ export const CURRENT_DB_CONFIG_KEY = "currentMainDbName";
*/ */
export const WALLET_DB_MINOR_VERSION = 1; export const WALLET_DB_MINOR_VERSION = 1;
declare const symDbProtocolTimestamp: unique symbol;
declare const symDbPreciseTimestamp: unique symbol;
/**
* Timestamp, stored as microseconds.
*
* Always rounded to a full second.
*/
export type DbProtocolTimestamp = number & { [symDbProtocolTimestamp]: true };
/**
* Timestamp, stored as microseconds.
*/
export type DbPreciseTimestamp = number & { [symDbPreciseTimestamp]: true };
const DB_TIMESTAMP_FOREVER = Number.MAX_SAFE_INTEGER;
export function timestampPreciseFromDb(
dbTs: DbPreciseTimestamp,
): TalerPreciseTimestamp {
return TalerPreciseTimestamp.fromMilliseconds(Math.floor(dbTs / 1000));
}
export function timestampOptionalPreciseFromDb(
dbTs: DbPreciseTimestamp | undefined,
): TalerPreciseTimestamp | undefined {
if (!dbTs) {
return undefined;
}
return TalerPreciseTimestamp.fromMilliseconds(Math.floor(dbTs / 1000));
}
export function timestampPreciseToDb(
stamp: TalerPreciseTimestamp,
): DbPreciseTimestamp {
if (stamp.t_s === "never") {
return DB_TIMESTAMP_FOREVER as DbPreciseTimestamp;
} else {
let tUs = stamp.t_s * 1000000;
if (stamp.off_us) {
tUs == stamp.off_us;
}
return tUs as DbPreciseTimestamp;
}
}
/** /**
* Format of the operation status code: 0x0abc_nnnn * Format of the operation status code: 0x0abc_nnnn
@ -217,7 +259,7 @@ export enum WithdrawalGroupStatus {
* Exchange is doing AML checks. * Exchange is doing AML checks.
*/ */
PendingAml = 0x0100_0006, PendingAml = 0x0100_0006,
SuspendedAml = 0x0100_0006, SuspendedAml = 0x0110_0006,
/** /**
* The corresponding withdraw record has been created. * The corresponding withdraw record has been created.
@ -268,14 +310,14 @@ export interface ReserveBankInfo {
* *
* Set to undefined if that hasn't happened yet. * Set to undefined if that hasn't happened yet.
*/ */
timestampReserveInfoPosted: TalerPreciseTimestamp | undefined; timestampReserveInfoPosted: DbPreciseTimestamp | undefined;
/** /**
* Time when the reserve was confirmed by the bank. * Time when the reserve was confirmed by the bank.
* *
* Set to undefined if not confirmed yet. * Set to undefined if not confirmed yet.
*/ */
timestampBankConfirmed: TalerPreciseTimestamp | undefined; timestampBankConfirmed: DbPreciseTimestamp | undefined;
} }
/** /**
@ -488,7 +530,7 @@ export interface ExchangeDetailsRecord {
tosAccepted: tosAccepted:
| { | {
etag: string; etag: string;
timestamp: TalerPreciseTimestamp; timestamp: DbPreciseTimestamp;
} }
| undefined; | undefined;
@ -528,7 +570,7 @@ export interface ExchangeDetailsPointer {
* Timestamp when the (masterPublicKey, currency) pointer * Timestamp when the (masterPublicKey, currency) pointer
* has been updated. * has been updated.
*/ */
updateClock: TalerPreciseTimestamp; updateClock: DbPreciseTimestamp;
} }
export enum ExchangeEntryDbRecordStatus { export enum ExchangeEntryDbRecordStatus {
@ -567,7 +609,7 @@ export interface ExchangeEntryRecord {
* *
* Used mostly in the UI to suggest exchanges. * Used mostly in the UI to suggest exchanges.
*/ */
lastWithdrawal?: TalerPreciseTimestamp; lastWithdrawal?: DbPreciseTimestamp;
/** /**
* Pointer to the current exchange details. * Pointer to the current exchange details.
@ -588,7 +630,7 @@ export interface ExchangeEntryRecord {
/** /**
* Last time when the exchange /keys info was updated. * Last time when the exchange /keys info was updated.
*/ */
lastUpdate: TalerPreciseTimestamp | undefined; lastUpdate: DbPreciseTimestamp | undefined;
/** /**
* Next scheduled update for the exchange. * Next scheduled update for the exchange.
@ -816,7 +858,7 @@ export interface RewardRecord {
* Has the user accepted the tip? Only after the tip has been accepted coins * Has the user accepted the tip? Only after the tip has been accepted coins
* withdrawn from the tip may be used. * withdrawn from the tip may be used.
*/ */
acceptedTimestamp: TalerPreciseTimestamp | undefined; acceptedTimestamp: DbPreciseTimestamp | undefined;
/** /**
* The tipped amount. * The tipped amount.
@ -869,7 +911,7 @@ export interface RewardRecord {
*/ */
merchantRewardId: string; merchantRewardId: string;
createdTimestamp: TalerPreciseTimestamp; createdTimestamp: DbPreciseTimestamp;
/** /**
* The url to be redirected after the tip is accepted. * The url to be redirected after the tip is accepted.
@ -880,7 +922,7 @@ export interface RewardRecord {
* Timestamp for when the wallet finished picking up the tip * Timestamp for when the wallet finished picking up the tip
* from the merchant. * from the merchant.
*/ */
pickedUpTimestamp: TalerPreciseTimestamp | undefined; pickedUpTimestamp: DbPreciseTimestamp | undefined;
status: RewardRecordStatus; status: RewardRecordStatus;
} }
@ -978,12 +1020,12 @@ export interface RefreshGroupRecord {
*/ */
statusPerCoin: RefreshCoinStatus[]; statusPerCoin: RefreshCoinStatus[];
timestampCreated: TalerPreciseTimestamp; timestampCreated: DbPreciseTimestamp;
/** /**
* Timestamp when the refresh session finished. * Timestamp when the refresh session finished.
*/ */
timestampFinished: TalerPreciseTimestamp | undefined; timestampFinished: DbPreciseTimestamp | undefined;
} }
/** /**
@ -1208,7 +1250,7 @@ export interface PurchaseRecord {
* Timestamp of the first time that sending a payment to the merchant * Timestamp of the first time that sending a payment to the merchant
* for this purchase was successful. * for this purchase was successful.
*/ */
timestampFirstSuccessfulPay: TalerPreciseTimestamp | undefined; timestampFirstSuccessfulPay: DbPreciseTimestamp | undefined;
merchantPaySig: string | undefined; merchantPaySig: string | undefined;
@ -1223,19 +1265,19 @@ export interface PurchaseRecord {
/** /**
* When was the purchase record created? * When was the purchase record created?
*/ */
timestamp: TalerPreciseTimestamp; timestamp: DbPreciseTimestamp;
/** /**
* When was the purchase made? * When was the purchase made?
* Refers to the time that the user accepted. * Refers to the time that the user accepted.
*/ */
timestampAccept: TalerPreciseTimestamp | undefined; timestampAccept: DbPreciseTimestamp | undefined;
/** /**
* When was the last refund made? * When was the last refund made?
* Set to 0 if no refund was made on the purchase. * Set to 0 if no refund was made on the purchase.
*/ */
timestampLastRefundStatus: TalerPreciseTimestamp | undefined; timestampLastRefundStatus: DbPreciseTimestamp | undefined;
/** /**
* Last session signature that we submitted to /pay (if any). * Last session signature that we submitted to /pay (if any).
@ -1285,12 +1327,12 @@ export interface WalletBackupConfState {
/** /**
* Timestamp stored in the last backup. * Timestamp stored in the last backup.
*/ */
lastBackupTimestamp?: TalerPreciseTimestamp; lastBackupTimestamp?: DbPreciseTimestamp;
/** /**
* Last time we tried to do a backup. * Last time we tried to do a backup.
*/ */
lastBackupCheckTimestamp?: TalerPreciseTimestamp; lastBackupCheckTimestamp?: DbPreciseTimestamp;
lastBackupNonce?: string; lastBackupNonce?: string;
} }
@ -1398,12 +1440,12 @@ export interface WithdrawalGroupRecord {
* When was the withdrawal operation started started? * When was the withdrawal operation started started?
* Timestamp in milliseconds. * Timestamp in milliseconds.
*/ */
timestampStart: TalerPreciseTimestamp; timestampStart: DbPreciseTimestamp;
/** /**
* When was the withdrawal operation completed? * When was the withdrawal operation completed?
*/ */
timestampFinish?: TalerPreciseTimestamp; timestampFinish?: DbPreciseTimestamp;
/** /**
* Current status of the reserve. * Current status of the reserve.
@ -1494,9 +1536,9 @@ export interface RecoupGroupRecord {
exchangeBaseUrl: string; exchangeBaseUrl: string;
timestampStarted: TalerPreciseTimestamp; timestampStarted: DbPreciseTimestamp;
timestampFinished: TalerPreciseTimestamp | undefined; timestampFinished: DbPreciseTimestamp | undefined;
/** /**
* Public keys that identify the coins being recouped * Public keys that identify the coins being recouped
@ -1530,7 +1572,7 @@ export type BackupProviderState =
} }
| { | {
tag: BackupProviderStateTag.Ready; tag: BackupProviderStateTag.Ready;
nextBackupTimestamp: TalerPreciseTimestamp; nextBackupTimestamp: DbPreciseTimestamp;
} }
| { | {
tag: BackupProviderStateTag.Retrying; tag: BackupProviderStateTag.Retrying;
@ -1575,7 +1617,7 @@ export interface BackupProviderRecord {
* Does NOT correspond to the timestamp of the backup, * Does NOT correspond to the timestamp of the backup,
* which only changes when the backup content changes. * which only changes when the backup content changes.
*/ */
lastBackupCycleTimestamp?: TalerPreciseTimestamp; lastBackupCycleTimestamp?: DbPreciseTimestamp;
/** /**
* Proposal that we're currently trying to pay for. * Proposal that we're currently trying to pay for.
@ -1678,9 +1720,9 @@ export interface DepositGroupRecord {
*/ */
counterpartyEffectiveDepositAmount: AmountString; counterpartyEffectiveDepositAmount: AmountString;
timestampCreated: TalerPreciseTimestamp; timestampCreated: DbPreciseTimestamp;
timestampFinished: TalerPreciseTimestamp | undefined; timestampFinished: DbPreciseTimestamp | undefined;
operationStatus: DepositOperationStatus; operationStatus: DepositOperationStatus;
@ -1791,7 +1833,7 @@ export interface PeerPushDebitRecord {
purseExpiration: TalerProtocolTimestamp; purseExpiration: TalerProtocolTimestamp;
timestampCreated: TalerPreciseTimestamp; timestampCreated: DbPreciseTimestamp;
abortRefreshGroupId?: string; abortRefreshGroupId?: string;
@ -1864,7 +1906,7 @@ export interface PeerPullCreditRecord {
contractEncNonce: string; contractEncNonce: string;
mergeTimestamp: TalerPreciseTimestamp; mergeTimestamp: DbPreciseTimestamp;
mergeReserveRowId: number; mergeReserveRowId: number;
@ -1916,7 +1958,7 @@ export interface PeerPushPaymentIncomingRecord {
contractPriv: string; contractPriv: string;
timestamp: TalerPreciseTimestamp; timestamp: DbPreciseTimestamp;
estimatedAmountEffective: AmountString; estimatedAmountEffective: AmountString;
@ -1988,7 +2030,7 @@ export interface PeerPullPaymentIncomingRecord {
contractTermsHash: string; contractTermsHash: string;
timestampCreated: TalerPreciseTimestamp; timestampCreated: DbPreciseTimestamp;
/** /**
* Contract priv that we got from the other party. * Contract priv that we got from the other party.
@ -2095,7 +2137,7 @@ export interface UserAttentionRecord {
/** /**
* When the user mark this notification as read. * When the user mark this notification as read.
*/ */
read: TalerPreciseTimestamp | undefined; read: DbPreciseTimestamp | undefined;
} }
export interface DbExchangeHandle { export interface DbExchangeHandle {
@ -2139,7 +2181,7 @@ export interface RefundGroupRecord {
/** /**
* Timestamp when the refund group was created. * Timestamp when the refund group was created.
*/ */
timestampCreated: TalerPreciseTimestamp; timestampCreated: DbPreciseTimestamp;
proposalId: string; proposalId: string;
@ -2196,7 +2238,7 @@ export interface RefundItemRecord {
/** /**
* Time when the wallet became aware of the refund. * Time when the wallet became aware of the refund.
*/ */
obtainedTime: TalerPreciseTimestamp; obtainedTime: DbPreciseTimestamp;
refundAmount: AmountString; refundAmount: AmountString;

View File

@ -31,6 +31,7 @@ import {
UserAttentionUnreadList, UserAttentionUnreadList,
} from "@gnu-taler/taler-util"; } from "@gnu-taler/taler-util";
import { InternalWalletState } from "../internal-wallet-state.js"; import { InternalWalletState } from "../internal-wallet-state.js";
import { timestampPreciseToDb } from "../index.js";
const logger = new Logger("operations/attention.ts"); const logger = new Logger("operations/attention.ts");
@ -94,7 +95,7 @@ export async function markAttentionRequestAsRead(
if (!ua) throw Error("attention request not found"); if (!ua) throw Error("attention request not found");
tx.userAttention.put({ tx.userAttention.put({
...ua, ...ua,
read: TalerPreciseTimestamp.now(), read: timestampPreciseToDb(TalerPreciseTimestamp.now()),
}); });
}); });
} }

View File

@ -84,6 +84,9 @@ import {
ConfigRecord, ConfigRecord,
ConfigRecordKey, ConfigRecordKey,
WalletBackupConfState, WalletBackupConfState,
timestampOptionalPreciseFromDb,
timestampPreciseFromDb,
timestampPreciseToDb,
} from "../../db.js"; } from "../../db.js";
import { InternalWalletState } from "../../internal-wallet-state.js"; import { InternalWalletState } from "../../internal-wallet-state.js";
import { assertUnreachable } from "../../util/assertUnreachable.js"; import { assertUnreachable } from "../../util/assertUnreachable.js";
@ -259,10 +262,12 @@ async function runBackupCycleForProvider(
if (!prov) { if (!prov) {
return; return;
} }
prov.lastBackupCycleTimestamp = TalerPreciseTimestamp.now(); prov.lastBackupCycleTimestamp = timestampPreciseToDb(
TalerPreciseTimestamp.now(),
);
prov.state = { prov.state = {
tag: BackupProviderStateTag.Ready, tag: BackupProviderStateTag.Ready,
nextBackupTimestamp: getNextBackupTimestamp(), nextBackupTimestamp: timestampPreciseToDb(getNextBackupTimestamp()),
}; };
await tx.backupProviders.put(prov); await tx.backupProviders.put(prov);
}); });
@ -361,10 +366,12 @@ async function runBackupCycleForProvider(
return; return;
} }
prov.lastBackupHash = encodeCrock(currentBackupHash); prov.lastBackupHash = encodeCrock(currentBackupHash);
prov.lastBackupCycleTimestamp = TalerPreciseTimestamp.now(); prov.lastBackupCycleTimestamp = timestampPreciseToDb(
TalerPreciseTimestamp.now(),
);
prov.state = { prov.state = {
tag: BackupProviderStateTag.Ready, tag: BackupProviderStateTag.Ready,
nextBackupTimestamp: getNextBackupTimestamp(), nextBackupTimestamp: timestampPreciseToDb(getNextBackupTimestamp()),
}; };
await tx.backupProviders.put(prov); await tx.backupProviders.put(prov);
}); });
@ -594,7 +601,9 @@ export async function addBackupProvider(
if (req.activate) { if (req.activate) {
oldProv.state = { oldProv.state = {
tag: BackupProviderStateTag.Ready, tag: BackupProviderStateTag.Ready,
nextBackupTimestamp: TalerPreciseTimestamp.now(), nextBackupTimestamp: timestampPreciseToDb(
TalerPreciseTimestamp.now(),
),
}; };
logger.info("setting existing backup provider to active"); logger.info("setting existing backup provider to active");
await tx.backupProviders.put(oldProv); await tx.backupProviders.put(oldProv);
@ -616,7 +625,9 @@ export async function addBackupProvider(
if (req.activate) { if (req.activate) {
state = { state = {
tag: BackupProviderStateTag.Ready, tag: BackupProviderStateTag.Ready,
nextBackupTimestamp: TalerPreciseTimestamp.now(), nextBackupTimestamp: timestampPreciseToDb(
TalerPreciseTimestamp.now(),
),
}; };
} else { } else {
state = { state = {
@ -840,7 +851,9 @@ export async function getBackupInfo(
providers.push({ providers.push({
active: x.provider.state.tag !== BackupProviderStateTag.Provisional, active: x.provider.state.tag !== BackupProviderStateTag.Provisional,
syncProviderBaseUrl: x.provider.baseUrl, syncProviderBaseUrl: x.provider.baseUrl,
lastSuccessfulBackupTimestamp: x.provider.lastBackupCycleTimestamp, lastSuccessfulBackupTimestamp: timestampOptionalPreciseFromDb(
x.provider.lastBackupCycleTimestamp,
),
paymentProposalIds: x.provider.paymentProposalIds, paymentProposalIds: x.provider.paymentProposalIds,
lastError: lastError:
x.provider.state.tag === BackupProviderStateTag.Retrying x.provider.state.tag === BackupProviderStateTag.Retrying
@ -917,7 +930,9 @@ async function backupRecoveryTheirs(
shouldRetryFreshProposal: false, shouldRetryFreshProposal: false,
state: { state: {
tag: BackupProviderStateTag.Ready, tag: BackupProviderStateTag.Ready,
nextBackupTimestamp: TalerPreciseTimestamp.now(), nextBackupTimestamp: timestampPreciseToDb(
TalerPreciseTimestamp.now(),
),
}, },
uids: [encodeCrock(getRandomBytes(32))], uids: [encodeCrock(getRandomBytes(32))],
}); });

View File

@ -73,6 +73,7 @@ import {
RefreshOperationStatus, RefreshOperationStatus,
createRefreshGroup, createRefreshGroup,
getTotalRefreshCost, getTotalRefreshCost,
timestampPreciseToDb,
} from "../index.js"; } from "../index.js";
import { InternalWalletState } from "../internal-wallet-state.js"; import { InternalWalletState } from "../internal-wallet-state.js";
import { assertUnreachable } from "../util/assertUnreachable.js"; import { assertUnreachable } from "../util/assertUnreachable.js";
@ -857,7 +858,9 @@ async function processDepositGroupPendingTrack(
} }
} }
if (allWired) { if (allWired) {
dg.timestampFinished = TalerPreciseTimestamp.now(); dg.timestampFinished = timestampPreciseToDb(
TalerPreciseTimestamp.now(),
);
dg.operationStatus = DepositOperationStatus.Finished; dg.operationStatus = DepositOperationStatus.Finished;
await tx.depositGroups.put(dg); await tx.depositGroups.put(dg);
} }
@ -1375,7 +1378,9 @@ export async function createDepositGroup(
amount: contractData.amount, amount: contractData.amount,
noncePriv: noncePair.priv, noncePriv: noncePair.priv,
noncePub: noncePair.pub, noncePub: noncePair.pub,
timestampCreated: AbsoluteTime.toPreciseTimestamp(now), timestampCreated: timestampPreciseToDb(
AbsoluteTime.toPreciseTimestamp(now),
),
timestampFinished: undefined, timestampFinished: undefined,
statusPerCoin: payCoinSel.coinSel.coinPubs.map( statusPerCoin: payCoinSel.coinSel.coinPubs.map(
() => DepositElementStatus.DepositPending, () => DepositElementStatus.DepositPending,

View File

@ -74,6 +74,7 @@ import {
ExchangeEntryDbRecordStatus, ExchangeEntryDbRecordStatus,
ExchangeEntryDbUpdateStatus, ExchangeEntryDbUpdateStatus,
isWithdrawableDenom, isWithdrawableDenom,
timestampPreciseToDb,
WalletDbReadWriteTransaction, WalletDbReadWriteTransaction,
} from "../index.js"; } from "../index.js";
import { InternalWalletState, TrustInfo } from "../internal-wallet-state.js"; import { InternalWalletState, TrustInfo } from "../internal-wallet-state.js";
@ -174,7 +175,7 @@ export async function acceptExchangeTermsOfService(
if (d) { if (d) {
d.tosAccepted = { d.tosAccepted = {
etag: etag || d.tosCurrentEtag, etag: etag || d.tosCurrentEtag,
timestamp: TalerPreciseTimestamp.now(), timestamp: timestampPreciseToDb(TalerPreciseTimestamp.now()),
}; };
await tx.exchangeDetails.put(d); await tx.exchangeDetails.put(d);
} }
@ -753,7 +754,7 @@ export async function updateExchangeFromUrlHandler(
if (existingDetails?.rowId) { if (existingDetails?.rowId) {
newDetails.rowId = existingDetails.rowId; newDetails.rowId = existingDetails.rowId;
} }
r.lastUpdate = TalerPreciseTimestamp.now(); r.lastUpdate = timestampPreciseToDb(TalerPreciseTimestamp.now());
r.nextUpdateStampMs = AbsoluteTime.toStampMs( r.nextUpdateStampMs = AbsoluteTime.toStampMs(
AbsoluteTime.fromProtocolTimestamp(keysInfo.expiry), AbsoluteTime.fromProtocolTimestamp(keysInfo.expiry),
); );
@ -763,7 +764,7 @@ export async function updateExchangeFromUrlHandler(
r.detailsPointer = { r.detailsPointer = {
currency: newDetails.currency, currency: newDetails.currency,
masterPublicKey: newDetails.masterPublicKey, masterPublicKey: newDetails.masterPublicKey,
updateClock: TalerPreciseTimestamp.now(), updateClock: timestampPreciseToDb(TalerPreciseTimestamp.now()),
}; };
} }
await tx.exchanges.put(r); await tx.exchanges.put(r);

View File

@ -103,6 +103,7 @@ import {
RefundGroupStatus, RefundGroupStatus,
RefundItemRecord, RefundItemRecord,
RefundItemStatus, RefundItemStatus,
timestampPreciseToDb,
} from "../index.js"; } from "../index.js";
import { import {
EXCHANGE_COINS_LOCK, EXCHANGE_COINS_LOCK,
@ -644,7 +645,7 @@ async function createPurchase(
noncePriv: priv, noncePriv: priv,
noncePub: pub, noncePub: pub,
claimToken, claimToken,
timestamp: TalerPreciseTimestamp.now(), timestamp: timestampPreciseToDb(TalerPreciseTimestamp.now()),
merchantBaseUrl, merchantBaseUrl,
orderId, orderId,
proposalId: proposalId, proposalId: proposalId,
@ -717,7 +718,7 @@ async function storeFirstPaySuccess(
if (purchase.purchaseStatus === PurchaseStatus.PendingPaying) { if (purchase.purchaseStatus === PurchaseStatus.PendingPaying) {
purchase.purchaseStatus = PurchaseStatus.Done; purchase.purchaseStatus = PurchaseStatus.Done;
} }
purchase.timestampFirstSuccessfulPay = now; purchase.timestampFirstSuccessfulPay = timestampPreciseToDb(now);
purchase.lastSessionId = sessionId; purchase.lastSessionId = sessionId;
purchase.merchantPaySig = payResponse.sig; purchase.merchantPaySig = payResponse.sig;
purchase.posConfirmation = payResponse.pos_confirmation; purchase.posConfirmation = payResponse.pos_confirmation;
@ -941,7 +942,9 @@ async function unblockBackup(
.forEachAsync(async (bp) => { .forEachAsync(async (bp) => {
bp.state = { bp.state = {
tag: BackupProviderStateTag.Ready, tag: BackupProviderStateTag.Ready,
nextBackupTimestamp: TalerPreciseTimestamp.now(), nextBackupTimestamp: timestampPreciseToDb(
TalerPreciseTimestamp.now(),
),
}; };
tx.backupProviders.put(bp); tx.backupProviders.put(bp);
}); });
@ -1447,7 +1450,7 @@ export async function confirmPay(
totalPayCost: Amounts.stringify(payCostInfo), totalPayCost: Amounts.stringify(payCostInfo),
}; };
p.lastSessionId = sessionId; p.lastSessionId = sessionId;
p.timestampAccept = TalerPreciseTimestamp.now(); p.timestampAccept = timestampPreciseToDb(TalerPreciseTimestamp.now());
p.purchaseStatus = PurchaseStatus.PendingPaying; p.purchaseStatus = PurchaseStatus.PendingPaying;
await tx.purchases.put(p); await tx.purchases.put(p);
await spendCoins(ws, tx, { await spendCoins(ws, tx, {
@ -2791,7 +2794,7 @@ async function storeRefunds(
proposalId: purchase.proposalId, proposalId: purchase.proposalId,
refundGroupId: newRefundGroupId, refundGroupId: newRefundGroupId,
status: RefundGroupStatus.Pending, status: RefundGroupStatus.Pending,
timestampCreated: now, timestampCreated: timestampPreciseToDb(now),
amountEffective: Amounts.stringify( amountEffective: Amounts.stringify(
Amounts.zeroOfCurrency(currency), Amounts.zeroOfCurrency(currency),
), ),
@ -2802,7 +2805,7 @@ async function storeRefunds(
const newItem: RefundItemRecord = { const newItem: RefundItemRecord = {
coinPub: rf.coin_pub, coinPub: rf.coin_pub,
executionTime: rf.execution_time, executionTime: rf.execution_time,
obtainedTime: now, obtainedTime: timestampPreciseToDb(now),
refundAmount: rf.refund_amount, refundAmount: rf.refund_amount,
refundGroupId: newGroup.refundGroupId, refundGroupId: newGroup.refundGroupId,
rtxid: rf.rtransaction_id, rtxid: rf.rtransaction_id,

View File

@ -60,6 +60,9 @@ import {
PeerPullPaymentCreditStatus, PeerPullPaymentCreditStatus,
WithdrawalGroupStatus, WithdrawalGroupStatus,
WithdrawalRecordType, WithdrawalRecordType,
timestampOptionalPreciseFromDb,
timestampPreciseFromDb,
timestampPreciseToDb,
updateExchangeFromUrl, updateExchangeFromUrl,
} from "../index.js"; } from "../index.js";
import { InternalWalletState } from "../internal-wallet-state.js"; import { InternalWalletState } from "../internal-wallet-state.js";
@ -395,12 +398,14 @@ async function handlePeerPullCreditCreatePurse(
nonce: pullIni.contractEncNonce, nonce: pullIni.contractEncNonce,
}); });
const mergeTimestamp = timestampPreciseFromDb(pullIni.mergeTimestamp);
const purseExpiration = contractTerms.purse_expiration; const purseExpiration = contractTerms.purse_expiration;
const sigRes = await ws.cryptoApi.signReservePurseCreate({ const sigRes = await ws.cryptoApi.signReservePurseCreate({
contractTermsHash: pullIni.contractTermsHash, contractTermsHash: pullIni.contractTermsHash,
flags: WalletAccountMergeFlags.CreateWithPurseFee, flags: WalletAccountMergeFlags.CreateWithPurseFee,
mergePriv: pullIni.mergePriv, mergePriv: pullIni.mergePriv,
mergeTimestamp: TalerPreciseTimestamp.round(pullIni.mergeTimestamp), mergeTimestamp: TalerPreciseTimestamp.round(mergeTimestamp),
purseAmount: pullIni.amount, purseAmount: pullIni.amount,
purseExpiration: purseExpiration, purseExpiration: purseExpiration,
purseFee: purseFee, purseFee: purseFee,
@ -412,7 +417,7 @@ async function handlePeerPullCreditCreatePurse(
const reservePurseReqBody: ExchangeReservePurseRequest = { const reservePurseReqBody: ExchangeReservePurseRequest = {
merge_sig: sigRes.mergeSig, merge_sig: sigRes.mergeSig,
merge_timestamp: TalerPreciseTimestamp.round(pullIni.mergeTimestamp), merge_timestamp: TalerPreciseTimestamp.round(mergeTimestamp),
h_contract_terms: pullIni.contractTermsHash, h_contract_terms: pullIni.contractTermsHash,
merge_pub: pullIni.mergePub, merge_pub: pullIni.mergePub,
min_age: 0, min_age: 0,
@ -695,11 +700,17 @@ async function getPreferredExchangeForCurrency(
if (candidate.lastWithdrawal && !e.lastWithdrawal) { if (candidate.lastWithdrawal && !e.lastWithdrawal) {
continue; continue;
} }
if (candidate.lastWithdrawal && e.lastWithdrawal) { const exchangeLastWithdrawal = timestampOptionalPreciseFromDb(
e.lastWithdrawal,
);
const candidateLastWithdrawal = timestampOptionalPreciseFromDb(
candidate.lastWithdrawal,
);
if (exchangeLastWithdrawal && candidateLastWithdrawal) {
if ( if (
AbsoluteTime.cmp( AbsoluteTime.cmp(
AbsoluteTime.fromPreciseTimestamp(e.lastWithdrawal), AbsoluteTime.fromPreciseTimestamp(exchangeLastWithdrawal),
AbsoluteTime.fromPreciseTimestamp(candidate.lastWithdrawal), AbsoluteTime.fromPreciseTimestamp(candidateLastWithdrawal),
) > 0 ) > 0
) { ) {
candidate = e; candidate = e;
@ -741,8 +752,6 @@ export async function initiatePeerPullPayment(
exchangeBaseUrl: exchangeBaseUrl, exchangeBaseUrl: exchangeBaseUrl,
}); });
const mergeTimestamp = TalerPreciseTimestamp.now();
const pursePair = await ws.cryptoApi.createEddsaKeypair({}); const pursePair = await ws.cryptoApi.createEddsaKeypair({});
const mergePair = await ws.cryptoApi.createEddsaKeypair({}); const mergePair = await ws.cryptoApi.createEddsaKeypair({});
@ -766,6 +775,8 @@ export async function initiatePeerPullPayment(
undefined, undefined,
); );
const mergeTimestamp = TalerPreciseTimestamp.now();
const transitionInfo = await ws.db const transitionInfo = await ws.db
.mktx((x) => [x.peerPullCredit, x.contractTerms]) .mktx((x) => [x.peerPullCredit, x.contractTerms])
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
@ -778,7 +789,7 @@ export async function initiatePeerPullPayment(
mergePriv: mergePair.priv, mergePriv: mergePair.priv,
mergePub: mergePair.pub, mergePub: mergePair.pub,
status: PeerPullPaymentCreditStatus.PendingCreatePurse, status: PeerPullPaymentCreditStatus.PendingCreatePurse,
mergeTimestamp, mergeTimestamp: timestampPreciseToDb(mergeTimestamp),
contractEncNonce, contractEncNonce,
mergeReserveRowId: mergeReserveRowId, mergeReserveRowId: mergeReserveRowId,
contractPriv: contractKeyPair.priv, contractPriv: contractKeyPair.priv,

View File

@ -59,6 +59,7 @@ import {
PendingTaskType, PendingTaskType,
RefreshOperationStatus, RefreshOperationStatus,
createRefreshGroup, createRefreshGroup,
timestampPreciseToDb,
} from "../index.js"; } from "../index.js";
import { assertUnreachable } from "../util/assertUnreachable.js"; import { assertUnreachable } from "../util/assertUnreachable.js";
import { checkLogicInvariant } from "../util/invariants.js"; import { checkLogicInvariant } from "../util/invariants.js";
@ -595,7 +596,7 @@ export async function preparePeerPullDebit(
contractPriv: contractPriv, contractPriv: contractPriv,
exchangeBaseUrl: exchangeBaseUrl, exchangeBaseUrl: exchangeBaseUrl,
pursePub: pursePub, pursePub: pursePub,
timestampCreated: TalerPreciseTimestamp.now(), timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
contractTermsHash, contractTermsHash,
amount: contractTerms.amount, amount: contractTerms.amount,
status: PeerPullDebitRecordStatus.DialogProposed, status: PeerPullDebitRecordStatus.DialogProposed,

View File

@ -59,6 +59,7 @@ import {
PendingTaskType, PendingTaskType,
WithdrawalGroupStatus, WithdrawalGroupStatus,
WithdrawalRecordType, WithdrawalRecordType,
timestampPreciseToDb,
} from "../index.js"; } from "../index.js";
import { assertUnreachable } from "../util/assertUnreachable.js"; import { assertUnreachable } from "../util/assertUnreachable.js";
import { checkDbInvariant } from "../util/invariants.js"; import { checkDbInvariant } from "../util/invariants.js";
@ -129,12 +130,10 @@ export async function preparePeerPushCredit(
amountEffective: existing.existingPushInc.estimatedAmountEffective, amountEffective: existing.existingPushInc.estimatedAmountEffective,
amountRaw: existing.existingContractTerms.amount, amountRaw: existing.existingContractTerms.amount,
contractTerms: existing.existingContractTerms, contractTerms: existing.existingContractTerms,
peerPushCreditId: peerPushCreditId: existing.existingPushInc.peerPushCreditId,
existing.existingPushInc.peerPushCreditId,
transactionId: constructTransactionIdentifier({ transactionId: constructTransactionIdentifier({
tag: TransactionType.PeerPushCredit, tag: TransactionType.PeerPushCredit,
peerPushCreditId: peerPushCreditId: existing.existingPushInc.peerPushCreditId,
existing.existingPushInc.peerPushCreditId,
}), }),
}; };
} }
@ -196,7 +195,7 @@ export async function preparePeerPushCredit(
exchangeBaseUrl: exchangeBaseUrl, exchangeBaseUrl: exchangeBaseUrl,
mergePriv: dec.mergePriv, mergePriv: dec.mergePriv,
pursePub: pursePub, pursePub: pursePub,
timestamp: TalerPreciseTimestamp.now(), timestamp: timestampPreciseToDb(TalerPreciseTimestamp.now()),
contractTermsHash, contractTermsHash,
status: PeerPushCreditStatus.DialogProposed, status: PeerPushCreditStatus.DialogProposed,
withdrawalGroupId, withdrawalGroupId,
@ -263,16 +262,11 @@ async function longpollKycStatus(
const transitionInfo = await ws.db const transitionInfo = await ws.db
.mktx((x) => [x.peerPushCredit]) .mktx((x) => [x.peerPushCredit])
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
const peerInc = await tx.peerPushCredit.get( const peerInc = await tx.peerPushCredit.get(peerPushCreditId);
peerPushCreditId,
);
if (!peerInc) { if (!peerInc) {
return; return;
} }
if ( if (peerInc.status !== PeerPushCreditStatus.PendingMergeKycRequired) {
peerInc.status !==
PeerPushCreditStatus.PendingMergeKycRequired
) {
return; return;
} }
const oldTxState = computePeerPushCreditTransactionState(peerInc); const oldTxState = computePeerPushCreditTransactionState(peerInc);
@ -333,9 +327,7 @@ async function processPeerPushCreditKycRequired(
const { transitionInfo, result } = await ws.db const { transitionInfo, result } = await ws.db
.mktx((x) => [x.peerPushCredit]) .mktx((x) => [x.peerPushCredit])
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
const peerInc = await tx.peerPushCredit.get( const peerInc = await tx.peerPushCredit.get(peerPushCreditId);
peerPushCreditId,
);
if (!peerInc) { if (!peerInc) {
return { return {
transitionInfo: undefined, transitionInfo: undefined,
@ -466,9 +458,7 @@ async function handlePendingMerge(
x.exchangeDetails, x.exchangeDetails,
]) ])
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
const peerInc = await tx.peerPushCredit.get( const peerInc = await tx.peerPushCredit.get(peerPushCreditId);
peerPushCreditId,
);
if (!peerInc) { if (!peerInc) {
return undefined; return undefined;
} }
@ -520,9 +510,7 @@ async function handlePendingWithdrawing(
const transitionInfo = await ws.db const transitionInfo = await ws.db
.mktx((x) => [x.peerPushCredit, x.withdrawalGroups]) .mktx((x) => [x.peerPushCredit, x.withdrawalGroups])
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
const ppi = await tx.peerPushCredit.get( const ppi = await tx.peerPushCredit.get(peerInc.peerPushCreditId);
peerInc.peerPushCreditId,
);
if (!ppi) { if (!ppi) {
finished = true; finished = true;
return; return;
@ -631,9 +619,7 @@ export async function confirmPeerPushCredit(
} }
peerPushCreditId = parsedTx.peerPushCreditId; peerPushCreditId = parsedTx.peerPushCreditId;
} else { } else {
throw Error( throw Error("no transaction ID (or deprecated peerPushCreditId) provided");
"no transaction ID (or deprecated peerPushCreditId) provided",
);
} }
await ws.db await ws.db
@ -683,9 +669,7 @@ export async function suspendPeerPushCreditTransaction(
const transitionInfo = await ws.db const transitionInfo = await ws.db
.mktx((x) => [x.peerPushCredit]) .mktx((x) => [x.peerPushCredit])
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
const pushCreditRec = await tx.peerPushCredit.get( const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId);
peerPushCreditId,
);
if (!pushCreditRec) { if (!pushCreditRec) {
logger.warn(`peer push credit ${peerPushCreditId} not found`); logger.warn(`peer push credit ${peerPushCreditId} not found`);
return; return;
@ -746,9 +730,7 @@ export async function abortPeerPushCreditTransaction(
const transitionInfo = await ws.db const transitionInfo = await ws.db
.mktx((x) => [x.peerPushCredit]) .mktx((x) => [x.peerPushCredit])
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
const pushCreditRec = await tx.peerPushCredit.get( const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId);
peerPushCreditId,
);
if (!pushCreditRec) { if (!pushCreditRec) {
logger.warn(`peer push credit ${peerPushCreditId} not found`); logger.warn(`peer push credit ${peerPushCreditId} not found`);
return; return;
@ -820,9 +802,7 @@ export async function resumePeerPushCreditTransaction(
const transitionInfo = await ws.db const transitionInfo = await ws.db
.mktx((x) => [x.peerPushCredit]) .mktx((x) => [x.peerPushCredit])
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
const pushCreditRec = await tx.peerPushCredit.get( const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId);
peerPushCreditId,
);
if (!pushCreditRec) { if (!pushCreditRec) {
logger.warn(`peer push credit ${peerPushCreditId} not found`); logger.warn(`peer push credit ${peerPushCreditId} not found`);
return; return;

View File

@ -55,6 +55,7 @@ import {
PeerPushDebitStatus, PeerPushDebitStatus,
RefreshOperationStatus, RefreshOperationStatus,
createRefreshGroup, createRefreshGroup,
timestampPreciseToDb,
} from "../index.js"; } from "../index.js";
import { InternalWalletState } from "../internal-wallet-state.js"; import { InternalWalletState } from "../internal-wallet-state.js";
import { PendingTaskType } from "../pending-types.js"; import { PendingTaskType } from "../pending-types.js";
@ -669,7 +670,7 @@ export async function initiatePeerPushDebit(
purseExpiration: purseExpiration, purseExpiration: purseExpiration,
pursePriv: pursePair.priv, pursePriv: pursePair.priv,
pursePub: pursePair.pub, pursePub: pursePair.pub,
timestampCreated: TalerPreciseTimestamp.now(), timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
status: PeerPushDebitStatus.PendingCreatePurse, status: PeerPushDebitStatus.PendingCreatePurse,
contractEncNonce, contractEncNonce,
coinSel: { coinSel: {

View File

@ -47,6 +47,7 @@ import {
ExchangeEntryDbUpdateStatus, ExchangeEntryDbUpdateStatus,
RefreshOperationStatus, RefreshOperationStatus,
DepositElementStatus, DepositElementStatus,
timestampPreciseFromDb,
} from "../db.js"; } from "../db.js";
import { import {
PendingOperationsResponse, PendingOperationsResponse,
@ -445,7 +446,7 @@ async function gatherBackupPending(
const retryRecord = await tx.operationRetries.get(opId); const retryRecord = await tx.operationRetries.get(opId);
if (bp.state.tag === BackupProviderStateTag.Ready) { if (bp.state.tag === BackupProviderStateTag.Ready) {
const timestampDue = AbsoluteTime.fromPreciseTimestamp( const timestampDue = AbsoluteTime.fromPreciseTimestamp(
bp.state.nextBackupTimestamp, timestampPreciseFromDb(bp.state.nextBackupTimestamp),
); );
resp.pendingOperations.push({ resp.pendingOperations.push({
type: PendingTaskType.Backup, type: PendingTaskType.Backup,

View File

@ -47,6 +47,7 @@ import {
WithdrawCoinSource, WithdrawCoinSource,
WithdrawalGroupStatus, WithdrawalGroupStatus,
WithdrawalRecordType, WithdrawalRecordType,
timestampPreciseToDb,
} from "../db.js"; } from "../db.js";
import { InternalWalletState } from "../internal-wallet-state.js"; import { InternalWalletState } from "../internal-wallet-state.js";
import { checkDbInvariant } from "../util/invariants.js"; import { checkDbInvariant } from "../util/invariants.js";
@ -391,7 +392,7 @@ export async function processRecoupGroup(
if (!rg2) { if (!rg2) {
return; return;
} }
rg2.timestampFinished = TalerPreciseTimestamp.now(); rg2.timestampFinished = timestampPreciseToDb(TalerPreciseTimestamp.now());
if (rg2.scheduleRefreshCoins.length > 0) { if (rg2.scheduleRefreshCoins.length > 0) {
const refreshGroupId = await createRefreshGroup( const refreshGroupId = await createRefreshGroup(
ws, ws,
@ -424,7 +425,7 @@ export async function createRecoupGroup(
exchangeBaseUrl: exchangeBaseUrl, exchangeBaseUrl: exchangeBaseUrl,
coinPubs: coinPubs, coinPubs: coinPubs,
timestampFinished: undefined, timestampFinished: undefined,
timestampStarted: TalerPreciseTimestamp.now(), timestampStarted: timestampPreciseToDb(TalerPreciseTimestamp.now()),
recoupFinishedPerCoin: coinPubs.map(() => false), recoupFinishedPerCoin: coinPubs.map(() => false),
scheduleRefreshCoins: [], scheduleRefreshCoins: [],
}; };

View File

@ -80,6 +80,7 @@ import {
isWithdrawableDenom, isWithdrawableDenom,
PendingTaskType, PendingTaskType,
RefreshSessionRecord, RefreshSessionRecord,
timestampPreciseToDb,
} from "../index.js"; } from "../index.js";
import { import {
EXCHANGE_COINS_LOCK, EXCHANGE_COINS_LOCK,
@ -157,10 +158,10 @@ function updateGroupStatus(rg: RefreshGroupRecord): { final: boolean } {
); );
if (allFinal) { if (allFinal) {
if (anyFailed) { if (anyFailed) {
rg.timestampFinished = TalerPreciseTimestamp.now(); rg.timestampFinished = timestampPreciseToDb(TalerPreciseTimestamp.now());
rg.operationStatus = RefreshOperationStatus.Failed; rg.operationStatus = RefreshOperationStatus.Failed;
} else { } else {
rg.timestampFinished = TalerPreciseTimestamp.now(); rg.timestampFinished = timestampPreciseToDb(TalerPreciseTimestamp.now());
rg.operationStatus = RefreshOperationStatus.Finished; rg.operationStatus = RefreshOperationStatus.Finished;
} }
return { final: true }; return { final: true };
@ -1099,12 +1100,14 @@ export async function createRefreshGroup(
expectedOutputPerCoin: estimatedOutputPerCoin.map((x) => expectedOutputPerCoin: estimatedOutputPerCoin.map((x) =>
Amounts.stringify(x), Amounts.stringify(x),
), ),
timestampCreated: TalerPreciseTimestamp.now(), timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
}; };
if (oldCoinPubs.length == 0) { if (oldCoinPubs.length == 0) {
logger.warn("created refresh group with zero coins"); logger.warn("created refresh group with zero coins");
refreshGroup.timestampFinished = TalerPreciseTimestamp.now(); refreshGroup.timestampFinished = timestampPreciseToDb(
TalerPreciseTimestamp.now(),
);
refreshGroup.operationStatus = RefreshOperationStatus.Finished; refreshGroup.operationStatus = RefreshOperationStatus.Finished;
} }

View File

@ -50,6 +50,8 @@ import {
DenominationRecord, DenominationRecord,
RewardRecord, RewardRecord,
RewardRecordStatus, RewardRecordStatus,
timestampPreciseFromDb,
timestampPreciseToDb,
} from "../db.js"; } from "../db.js";
import { makeErrorDetail } from "@gnu-taler/taler-util"; import { makeErrorDetail } from "@gnu-taler/taler-util";
import { InternalWalletState } from "../internal-wallet-state.js"; import { InternalWalletState } from "../internal-wallet-state.js";
@ -203,7 +205,7 @@ export async function prepareTip(
exchangeBaseUrl: tipPickupStatus.exchange_url, exchangeBaseUrl: tipPickupStatus.exchange_url,
next_url: tipPickupStatus.next_url, next_url: tipPickupStatus.next_url,
merchantBaseUrl: res.merchantBaseUrl, merchantBaseUrl: res.merchantBaseUrl,
createdTimestamp: TalerPreciseTimestamp.now(), createdTimestamp: timestampPreciseToDb(TalerPreciseTimestamp.now()),
merchantRewardId: res.merchantRewardId, merchantRewardId: res.merchantRewardId,
rewardAmountEffective: Amounts.stringify(selectedDenoms.totalCoinValue), rewardAmountEffective: Amounts.stringify(selectedDenoms.totalCoinValue),
denomsSel: selectedDenoms, denomsSel: selectedDenoms,
@ -411,7 +413,7 @@ export async function processTip(
return; return;
} }
const oldTxState = computeRewardTransactionStatus(tr); const oldTxState = computeRewardTransactionStatus(tr);
tr.pickedUpTimestamp = TalerPreciseTimestamp.now(); tr.pickedUpTimestamp = timestampPreciseToDb(TalerPreciseTimestamp.now());
tr.status = RewardRecordStatus.Done; tr.status = RewardRecordStatus.Done;
await tx.rewards.put(tr); await tx.rewards.put(tr);
const newTxState = computeRewardTransactionStatus(tr); const newTxState = computeRewardTransactionStatus(tr);
@ -448,7 +450,9 @@ export async function acceptTip(
return { tipRecord }; return { tipRecord };
} }
const oldTxState = computeRewardTransactionStatus(tipRecord); const oldTxState = computeRewardTransactionStatus(tipRecord);
tipRecord.acceptedTimestamp = TalerPreciseTimestamp.now(); tipRecord.acceptedTimestamp = timestampPreciseToDb(
TalerPreciseTimestamp.now(),
);
tipRecord.status = RewardRecordStatus.PendingPickup; tipRecord.status = RewardRecordStatus.PendingPickup;
await tx.rewards.put(tipRecord); await tx.rewards.put(tipRecord);
const newTxState = computeRewardTransactionStatus(tipRecord); const newTxState = computeRewardTransactionStatus(tipRecord);

View File

@ -65,7 +65,12 @@ import {
WithdrawalGroupStatus, WithdrawalGroupStatus,
WithdrawalRecordType, WithdrawalRecordType,
} from "../db.js"; } from "../db.js";
import { GetReadOnlyAccess, WalletStoresV1 } from "../index.js"; import {
GetReadOnlyAccess,
timestampOptionalPreciseFromDb,
timestampPreciseFromDb,
WalletStoresV1,
} from "../index.js";
import { InternalWalletState } from "../internal-wallet-state.js"; import { InternalWalletState } from "../internal-wallet-state.js";
import { PendingTaskType } from "../pending-types.js"; import { PendingTaskType } from "../pending-types.js";
import { assertUnreachable } from "../util/assertUnreachable.js"; import { assertUnreachable } from "../util/assertUnreachable.js";
@ -470,7 +475,7 @@ function buildTransactionForPushPaymentDebit(
expiration: contractTerms.purse_expiration, expiration: contractTerms.purse_expiration,
summary: contractTerms.summary, summary: contractTerms.summary,
}, },
timestamp: pi.timestampCreated, timestamp: timestampPreciseFromDb(pi.timestampCreated),
talerUri: stringifyPayPushUri({ talerUri: stringifyPayPushUri({
exchangeBaseUrl: pi.exchangeBaseUrl, exchangeBaseUrl: pi.exchangeBaseUrl,
contractPriv: pi.contractPriv, contractPriv: pi.contractPriv,
@ -501,7 +506,7 @@ function buildTransactionForPullPaymentDebit(
expiration: contractTerms.purse_expiration, expiration: contractTerms.purse_expiration,
summary: contractTerms.summary, summary: contractTerms.summary,
}, },
timestamp: pi.timestampCreated, timestamp: timestampPreciseFromDb(pi.timestampCreated),
transactionId: constructTransactionIdentifier({ transactionId: constructTransactionIdentifier({
tag: TransactionType.PeerPullDebit, tag: TransactionType.PeerPullDebit,
peerPullDebitId: pi.peerPullDebitId, peerPullDebitId: pi.peerPullDebitId,
@ -543,8 +548,7 @@ function buildTransactionForPeerPullCredit(
amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue), amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
amountRaw: Amounts.stringify(wsr.instructedAmount), amountRaw: Amounts.stringify(wsr.instructedAmount),
exchangeBaseUrl: wsr.exchangeBaseUrl, exchangeBaseUrl: wsr.exchangeBaseUrl,
// Old transactions don't have it! timestamp: timestampPreciseFromDb(pullCredit.mergeTimestamp),
timestamp: pullCredit.mergeTimestamp ?? TalerPreciseTimestamp.now(),
info: { info: {
expiration: peerContractTerms.purse_expiration, expiration: peerContractTerms.purse_expiration,
summary: peerContractTerms.summary, summary: peerContractTerms.summary,
@ -575,8 +579,7 @@ function buildTransactionForPeerPullCredit(
amountEffective: Amounts.stringify(pullCredit.estimatedAmountEffective), amountEffective: Amounts.stringify(pullCredit.estimatedAmountEffective),
amountRaw: Amounts.stringify(peerContractTerms.amount), amountRaw: Amounts.stringify(peerContractTerms.amount),
exchangeBaseUrl: pullCredit.exchangeBaseUrl, exchangeBaseUrl: pullCredit.exchangeBaseUrl,
// Old transactions don't have it! timestamp: timestampPreciseFromDb(pullCredit.mergeTimestamp),
timestamp: pullCredit.mergeTimestamp ?? TalerProtocolTimestamp.now(),
info: { info: {
expiration: peerContractTerms.purse_expiration, expiration: peerContractTerms.purse_expiration,
summary: peerContractTerms.summary, summary: peerContractTerms.summary,
@ -617,7 +620,7 @@ function buildTransactionForPeerPushCredit(
expiration: peerContractTerms.purse_expiration, expiration: peerContractTerms.purse_expiration,
summary: peerContractTerms.summary, summary: peerContractTerms.summary,
}, },
timestamp: wsr.timestampStart, timestamp: timestampPreciseFromDb(wsr.timestampStart),
transactionId: constructTransactionIdentifier({ transactionId: constructTransactionIdentifier({
tag: TransactionType.PeerPushCredit, tag: TransactionType.PeerPushCredit,
peerPushCreditId: pushInc.peerPushCreditId, peerPushCreditId: pushInc.peerPushCreditId,
@ -640,7 +643,7 @@ function buildTransactionForPeerPushCredit(
summary: peerContractTerms.summary, summary: peerContractTerms.summary,
}, },
kycUrl: pushInc.kycUrl, kycUrl: pushInc.kycUrl,
timestamp: pushInc.timestamp, timestamp: timestampPreciseFromDb(pushInc.timestamp),
transactionId: constructTransactionIdentifier({ transactionId: constructTransactionIdentifier({
tag: TransactionType.PeerPushCredit, tag: TransactionType.PeerPushCredit,
peerPushCreditId: pushInc.peerPushCreditId, peerPushCreditId: pushInc.peerPushCreditId,
@ -673,7 +676,7 @@ function buildTransactionForBankIntegratedWithdraw(
}, },
kycUrl: wgRecord.kycUrl, kycUrl: wgRecord.kycUrl,
exchangeBaseUrl: wgRecord.exchangeBaseUrl, exchangeBaseUrl: wgRecord.exchangeBaseUrl,
timestamp: wgRecord.timestampStart, timestamp: timestampPreciseFromDb(wgRecord.timestampStart),
transactionId: constructTransactionIdentifier({ transactionId: constructTransactionIdentifier({
tag: TransactionType.Withdrawal, tag: TransactionType.Withdrawal,
withdrawalGroupId: wgRecord.withdrawalGroupId, withdrawalGroupId: wgRecord.withdrawalGroupId,
@ -717,7 +720,7 @@ function buildTransactionForManualWithdraw(
}, },
kycUrl: withdrawalGroup.kycUrl, kycUrl: withdrawalGroup.kycUrl,
exchangeBaseUrl: withdrawalGroup.exchangeBaseUrl, exchangeBaseUrl: withdrawalGroup.exchangeBaseUrl,
timestamp: withdrawalGroup.timestampStart, timestamp: timestampPreciseFromDb(withdrawalGroup.timestampStart),
transactionId: constructTransactionIdentifier({ transactionId: constructTransactionIdentifier({
tag: TransactionType.Withdrawal, tag: TransactionType.Withdrawal,
withdrawalGroupId: withdrawalGroup.withdrawalGroupId, withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
@ -748,7 +751,7 @@ function buildTransactionForRefund(
tag: TransactionType.Payment, tag: TransactionType.Payment,
proposalId: refundRecord.proposalId, proposalId: refundRecord.proposalId,
}), }),
timestamp: refundRecord.timestampCreated, timestamp: timestampPreciseFromDb(refundRecord.timestampCreated),
transactionId: constructTransactionIdentifier({ transactionId: constructTransactionIdentifier({
tag: TransactionType.Refund, tag: TransactionType.Refund,
refundGroupId: refundRecord.refundGroupId, refundGroupId: refundRecord.refundGroupId,
@ -786,7 +789,7 @@ function buildTransactionForRefresh(
refreshOutputAmount: Amounts.stringify(outputAmount), refreshOutputAmount: Amounts.stringify(outputAmount),
originatingTransactionId: originatingTransactionId:
refreshGroupRecord.reasonDetails?.originatingTransactionId, refreshGroupRecord.reasonDetails?.originatingTransactionId,
timestamp: refreshGroupRecord.timestampCreated, timestamp: timestampPreciseFromDb(refreshGroupRecord.timestampCreated),
transactionId: constructTransactionIdentifier({ transactionId: constructTransactionIdentifier({
tag: TransactionType.Refresh, tag: TransactionType.Refresh,
refreshGroupId: refreshGroupRecord.refreshGroupId, refreshGroupId: refreshGroupRecord.refreshGroupId,
@ -812,7 +815,7 @@ function buildTransactionForDeposit(
txActions: computeDepositTransactionActions(dg), txActions: computeDepositTransactionActions(dg),
amountRaw: Amounts.stringify(dg.counterpartyEffectiveDepositAmount), amountRaw: Amounts.stringify(dg.counterpartyEffectiveDepositAmount),
amountEffective: Amounts.stringify(dg.totalPayCost), amountEffective: Amounts.stringify(dg.totalPayCost),
timestamp: dg.timestampCreated, timestamp: timestampPreciseFromDb(dg.timestampCreated),
targetPaytoUri: dg.wire.payto_uri, targetPaytoUri: dg.wire.payto_uri,
wireTransferDeadline: dg.wireTransferDeadline, wireTransferDeadline: dg.wireTransferDeadline,
transactionId: constructTransactionIdentifier({ transactionId: constructTransactionIdentifier({
@ -845,7 +848,7 @@ function buildTransactionForTip(
txActions: computeTipTransactionActions(tipRecord), txActions: computeTipTransactionActions(tipRecord),
amountEffective: Amounts.stringify(tipRecord.rewardAmountEffective), amountEffective: Amounts.stringify(tipRecord.rewardAmountEffective),
amountRaw: Amounts.stringify(tipRecord.rewardAmountRaw), amountRaw: Amounts.stringify(tipRecord.rewardAmountRaw),
timestamp: tipRecord.acceptedTimestamp, timestamp: timestampPreciseFromDb(tipRecord.acceptedTimestamp),
transactionId: constructTransactionIdentifier({ transactionId: constructTransactionIdentifier({
tag: TransactionType.Reward, tag: TransactionType.Reward,
walletRewardId: tipRecord.walletRewardId, walletRewardId: tipRecord.walletRewardId,
@ -922,7 +925,7 @@ async function buildTransactionForPurchase(
: Amounts.stringify(purchaseRecord.refundAmountAwaiting), : Amounts.stringify(purchaseRecord.refundAmountAwaiting),
refunds, refunds,
posConfirmation: purchaseRecord.posConfirmation, posConfirmation: purchaseRecord.posConfirmation,
timestamp, timestamp: timestampPreciseFromDb(timestamp),
transactionId: constructTransactionIdentifier({ transactionId: constructTransactionIdentifier({
tag: TransactionType.Payment, tag: TransactionType.Payment,
proposalId: purchaseRecord.proposalId, proposalId: purchaseRecord.proposalId,

View File

@ -131,6 +131,7 @@ import {
ExchangeEntryDbUpdateStatus, ExchangeEntryDbUpdateStatus,
PendingTaskType, PendingTaskType,
isWithdrawableDenom, isWithdrawableDenom,
timestampPreciseToDb,
} from "../index.js"; } from "../index.js";
import { import {
TransitionInfo, TransitionInfo,
@ -1325,7 +1326,7 @@ async function processWithdrawalGroupAbortingBank(
} }
const txStatusOld = computeWithdrawalTransactionStatus(wg); const txStatusOld = computeWithdrawalTransactionStatus(wg);
wg.status = WithdrawalGroupStatus.AbortedBank; wg.status = WithdrawalGroupStatus.AbortedBank;
wg.timestampFinish = TalerPreciseTimestamp.now(); wg.timestampFinish = timestampPreciseToDb(TalerPreciseTimestamp.now());
const txStatusNew = computeWithdrawalTransactionStatus(wg); const txStatusNew = computeWithdrawalTransactionStatus(wg);
await tx.withdrawalGroups.put(wg); await tx.withdrawalGroups.put(wg);
return { return {
@ -1458,7 +1459,7 @@ async function processWithdrawalGroupPendingReady(
} }
const txStatusOld = computeWithdrawalTransactionStatus(wg); const txStatusOld = computeWithdrawalTransactionStatus(wg);
wg.status = WithdrawalGroupStatus.Done; wg.status = WithdrawalGroupStatus.Done;
wg.timestampFinish = TalerPreciseTimestamp.now(); wg.timestampFinish = timestampPreciseToDb(TalerPreciseTimestamp.now());
const txStatusNew = computeWithdrawalTransactionStatus(wg); const txStatusNew = computeWithdrawalTransactionStatus(wg);
await tx.withdrawalGroups.put(wg); await tx.withdrawalGroups.put(wg);
return { return {
@ -1554,7 +1555,7 @@ async function processWithdrawalGroupPendingReady(
const oldTxState = computeWithdrawalTransactionStatus(wg); const oldTxState = computeWithdrawalTransactionStatus(wg);
logger.info(`now withdrawn ${numFinished} of ${numTotalCoins} coins`); logger.info(`now withdrawn ${numFinished} of ${numTotalCoins} coins`);
if (wg.timestampFinish === undefined && numFinished === numTotalCoins) { if (wg.timestampFinish === undefined && numFinished === numTotalCoins) {
wg.timestampFinish = TalerPreciseTimestamp.now(); wg.timestampFinish = timestampPreciseToDb(TalerPreciseTimestamp.now());
wg.status = WithdrawalGroupStatus.Done; wg.status = WithdrawalGroupStatus.Done;
await makeCoinsVisible(ws, tx, transactionId); await makeCoinsVisible(ws, tx, transactionId);
} }
@ -2047,8 +2048,9 @@ async function registerReserveWithBank(
if (r.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) { if (r.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) {
throw Error("invariant failed"); throw Error("invariant failed");
} }
r.wgInfo.bankInfo.timestampReserveInfoPosted = r.wgInfo.bankInfo.timestampReserveInfoPosted = timestampPreciseToDb(
AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now()); AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now()),
);
const oldTxState = computeWithdrawalTransactionStatus(r); const oldTxState = computeWithdrawalTransactionStatus(r);
r.status = WithdrawalGroupStatus.PendingWaitConfirmBank; r.status = WithdrawalGroupStatus.PendingWaitConfirmBank;
const newTxState = computeWithdrawalTransactionStatus(r); const newTxState = computeWithdrawalTransactionStatus(r);
@ -2130,7 +2132,7 @@ async function processReserveBankStatus(
} }
const now = AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now()); const now = AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now());
const oldTxState = computeWithdrawalTransactionStatus(r); const oldTxState = computeWithdrawalTransactionStatus(r);
r.wgInfo.bankInfo.timestampBankConfirmed = now; r.wgInfo.bankInfo.timestampBankConfirmed = timestampPreciseToDb(now);
r.status = WithdrawalGroupStatus.FailedBankAborted; r.status = WithdrawalGroupStatus.FailedBankAborted;
const newTxState = computeWithdrawalTransactionStatus(r); const newTxState = computeWithdrawalTransactionStatus(r);
await tx.withdrawalGroups.put(r); await tx.withdrawalGroups.put(r);
@ -2179,7 +2181,7 @@ async function processReserveBankStatus(
if (status.transfer_done) { if (status.transfer_done) {
logger.info("withdrawal: transfer confirmed by bank."); logger.info("withdrawal: transfer confirmed by bank.");
const now = AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now()); const now = AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now());
r.wgInfo.bankInfo.timestampBankConfirmed = now; r.wgInfo.bankInfo.timestampBankConfirmed = timestampPreciseToDb(now);
r.status = WithdrawalGroupStatus.PendingQueryingStatus; r.status = WithdrawalGroupStatus.PendingQueryingStatus;
} else { } else {
logger.info("withdrawal: transfer not yet confirmed by bank"); logger.info("withdrawal: transfer not yet confirmed by bank");
@ -2285,7 +2287,7 @@ export async function internalPrepareCreateWithdrawalGroup(
denomsSel: initialDenomSel, denomsSel: initialDenomSel,
exchangeBaseUrl: canonExchange, exchangeBaseUrl: canonExchange,
instructedAmount: Amounts.stringify(amount), instructedAmount: Amounts.stringify(amount),
timestampStart: now, timestampStart: timestampPreciseToDb(now),
rawWithdrawalAmount: initialDenomSel.totalWithdrawCost, rawWithdrawalAmount: initialDenomSel.totalWithdrawCost,
effectiveWithdrawalAmount: initialDenomSel.totalCoinValue, effectiveWithdrawalAmount: initialDenomSel.totalCoinValue,
secretSeed, secretSeed,
@ -2339,8 +2341,7 @@ export async function internalPerformCreateWithdrawalGroup(
if (!prep.creationInfo) { if (!prep.creationInfo) {
return { withdrawalGroup, transitionInfo: undefined }; return { withdrawalGroup, transitionInfo: undefined };
} }
const { amount, canonExchange, exchangeDetails } = const { amount, canonExchange, exchangeDetails } = prep.creationInfo;
prep.creationInfo;
await tx.withdrawalGroups.add(withdrawalGroup); await tx.withdrawalGroups.add(withdrawalGroup);
await tx.reserves.put({ await tx.reserves.put({
@ -2350,7 +2351,7 @@ export async function internalPerformCreateWithdrawalGroup(
const exchange = await tx.exchanges.get(withdrawalGroup.exchangeBaseUrl); const exchange = await tx.exchanges.get(withdrawalGroup.exchangeBaseUrl);
if (exchange) { if (exchange) {
exchange.lastWithdrawal = TalerPreciseTimestamp.now(); exchange.lastWithdrawal = timestampPreciseToDb(TalerPreciseTimestamp.now());
exchange.entryStatus = ExchangeEntryDbRecordStatus.Used; exchange.entryStatus = ExchangeEntryDbRecordStatus.Used;
await tx.exchanges.put(exchange); await tx.exchanges.put(exchange);
} }
@ -2541,11 +2542,7 @@ export async function createManualWithdrawal(
}); });
const exchangePaytoUris = await ws.db const exchangePaytoUris = await ws.db
.mktx((x) => [ .mktx((x) => [x.withdrawalGroups, x.exchanges, x.exchangeDetails])
x.withdrawalGroups,
x.exchanges,
x.exchangeDetails,
])
.runReadOnly(async (tx) => { .runReadOnly(async (tx) => {
return await getFundingPaytoUris(tx, withdrawalGroup.withdrawalGroupId); return await getFundingPaytoUris(tx, withdrawalGroup.withdrawalGroupId);
}); });