aboutsummaryrefslogtreecommitdiff
path: root/packages/taler-wallet-core/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/taler-wallet-core/src')
-rw-r--r--packages/taler-wallet-core/src/crypto/cryptoImplementation.ts22
-rw-r--r--packages/taler-wallet-core/src/db.ts263
-rw-r--r--packages/taler-wallet-core/src/dbless.ts17
-rw-r--r--packages/taler-wallet-core/src/host-impl.node.ts5
-rw-r--r--packages/taler-wallet-core/src/operations/attention.ts7
-rw-r--r--packages/taler-wallet-core/src/operations/backup/index.ts31
-rw-r--r--packages/taler-wallet-core/src/operations/common.ts47
-rw-r--r--packages/taler-wallet-core/src/operations/deposits.ts17
-rw-r--r--packages/taler-wallet-core/src/operations/exchanges.ts67
-rw-r--r--packages/taler-wallet-core/src/operations/pay-merchant.ts37
-rw-r--r--packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts29
-rw-r--r--packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts3
-rw-r--r--packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts46
-rw-r--r--packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts16
-rw-r--r--packages/taler-wallet-core/src/operations/pending.ts112
-rw-r--r--packages/taler-wallet-core/src/operations/recoup.ts5
-rw-r--r--packages/taler-wallet-core/src/operations/refresh.ts25
-rw-r--r--packages/taler-wallet-core/src/operations/reward.ts23
-rw-r--r--packages/taler-wallet-core/src/operations/testing.ts194
-rw-r--r--packages/taler-wallet-core/src/operations/transactions.ts52
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.test.ts114
-rw-r--r--packages/taler-wallet-core/src/operations/withdraw.ts46
-rw-r--r--packages/taler-wallet-core/src/pending-types.ts14
-rw-r--r--packages/taler-wallet-core/src/util/coinSelection.test.ts244
-rw-r--r--packages/taler-wallet-core/src/util/coinSelection.ts25
-rw-r--r--packages/taler-wallet-core/src/util/denominations.ts9
-rw-r--r--packages/taler-wallet-core/src/util/instructedAmountConversion.ts17
-rw-r--r--packages/taler-wallet-core/src/versions.ts2
-rw-r--r--packages/taler-wallet-core/src/wallet-api-types.ts45
-rw-r--r--packages/taler-wallet-core/src/wallet.ts79
30 files changed, 931 insertions, 682 deletions
diff --git a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
index 35777e714..56392f090 100644
--- a/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
+++ b/packages/taler-wallet-core/src/crypto/cryptoImplementation.ts
@@ -87,7 +87,7 @@ import {
WithdrawalPlanchet,
} from "@gnu-taler/taler-util";
// FIXME: Crypto should not use DB Types!
-import { DenominationRecord } from "../db.js";
+import { DenominationRecord, timestampProtocolFromDb } from "../db.js";
import {
CreateRecoupRefreshReqRequest,
CreateRecoupReqRequest,
@@ -962,10 +962,22 @@ export const nativeCryptoR: TalerCryptoInterfaceR = {
const value: AmountJson = Amounts.parseOrThrow(denom.value);
const p = buildSigPS(TalerSignaturePurpose.MASTER_DENOMINATION_KEY_VALIDITY)
.put(decodeCrock(masterPub))
- .put(timestampRoundedToBuffer(denom.stampStart))
- .put(timestampRoundedToBuffer(denom.stampExpireWithdraw))
- .put(timestampRoundedToBuffer(denom.stampExpireDeposit))
- .put(timestampRoundedToBuffer(denom.stampExpireLegal))
+ .put(timestampRoundedToBuffer(timestampProtocolFromDb(denom.stampStart)))
+ .put(
+ timestampRoundedToBuffer(
+ timestampProtocolFromDb(denom.stampExpireWithdraw),
+ ),
+ )
+ .put(
+ timestampRoundedToBuffer(
+ timestampProtocolFromDb(denom.stampExpireDeposit),
+ ),
+ )
+ .put(
+ timestampRoundedToBuffer(
+ timestampProtocolFromDb(denom.stampExpireLegal),
+ ),
+ )
.put(amountToBuffer(value))
.put(amountToBuffer(denom.fees.feeWithdraw))
.put(amountToBuffer(denom.fees.feeDeposit))
diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts
index 239a6d4a4..46a073156 100644
--- a/packages/taler-wallet-core/src/db.ts
+++ b/packages/taler-wallet-core/src/db.ts
@@ -27,8 +27,8 @@ import {
structuredEncapsulate,
} from "@gnu-taler/idb-bridge";
import {
+ AbsoluteTime,
AgeCommitmentProof,
- AmountJson,
AmountString,
Amounts,
AttentionInfo,
@@ -45,23 +45,20 @@ import {
ExchangeAuditor,
ExchangeGlobalFees,
HashCodeString,
- InternationalizedString,
Logger,
- MerchantContractTerms,
- MerchantInfo,
PayCoinSelection,
- PeerContractTerms,
RefreshReason,
TalerErrorDetail,
TalerPreciseTimestamp,
TalerProtocolDuration,
TalerProtocolTimestamp,
+ //TalerProtocolTimestamp,
TransactionIdStr,
UnblindedSignature,
WireInfo,
codecForAny,
} from "@gnu-taler/taler-util";
-import { RetryInfo, TaskIdentifiers } from "./operations/common.js";
+import { DbRetryInfo, TaskIdentifiers } from "./operations/common.js";
import {
DbAccess,
DbReadOnlyTransaction,
@@ -151,6 +148,91 @@ export const CURRENT_DB_CONFIG_KEY = "currentMainDbName";
*/
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;
+ }
+}
+
+export function timestampProtocolToDb(
+ stamp: TalerProtocolTimestamp,
+): DbProtocolTimestamp {
+ if (stamp.t_s === "never") {
+ return DB_TIMESTAMP_FOREVER as DbProtocolTimestamp;
+ } else {
+ let tUs = stamp.t_s * 1000000;
+ return tUs as DbProtocolTimestamp;
+ }
+}
+
+export function timestampProtocolFromDb(
+ stamp: DbProtocolTimestamp,
+): TalerProtocolTimestamp {
+ return TalerProtocolTimestamp.fromSeconds(Math.floor(stamp / 1000000));
+}
+
+export function timestampAbsoluteFromDb(
+ stamp: DbProtocolTimestamp | DbPreciseTimestamp,
+): AbsoluteTime {
+ if (stamp >= DB_TIMESTAMP_FOREVER) {
+ return AbsoluteTime.never();
+ }
+ return AbsoluteTime.fromMilliseconds(Math.floor(stamp / 1000));
+}
+
+export function timestampOptionalAbsoluteFromDb(
+ stamp: DbProtocolTimestamp | DbPreciseTimestamp | undefined,
+): AbsoluteTime | undefined {
+ if (stamp == null) {
+ return undefined;
+ }
+ if (stamp >= DB_TIMESTAMP_FOREVER) {
+ return AbsoluteTime.never();
+ }
+ return AbsoluteTime.fromMilliseconds(Math.floor(stamp / 1000));
+}
+
/**
* Format of the operation status code: 0x0abc_nnnn
@@ -217,7 +299,7 @@ export enum WithdrawalGroupStatus {
* Exchange is doing AML checks.
*/
PendingAml = 0x0100_0006,
- SuspendedAml = 0x0100_0006,
+ SuspendedAml = 0x0110_0006,
/**
* The corresponding withdraw record has been created.
@@ -268,14 +350,14 @@ export interface ReserveBankInfo {
*
* Set to undefined if that hasn't happened yet.
*/
- timestampReserveInfoPosted: TalerPreciseTimestamp | undefined;
+ timestampReserveInfoPosted: DbPreciseTimestamp | undefined;
/**
* Time when the reserve was confirmed by the bank.
*
* Set to undefined if not confirmed yet.
*/
- timestampBankConfirmed: TalerPreciseTimestamp | undefined;
+ timestampBankConfirmed: DbPreciseTimestamp | undefined;
}
/**
@@ -349,22 +431,22 @@ export interface DenominationRecord {
/**
* Validity start date of the denomination.
*/
- stampStart: TalerProtocolTimestamp;
+ stampStart: DbProtocolTimestamp;
/**
* Date after which the currency can't be withdrawn anymore.
*/
- stampExpireWithdraw: TalerProtocolTimestamp;
+ stampExpireWithdraw: DbProtocolTimestamp;
/**
* Date after the denomination officially doesn't exist anymore.
*/
- stampExpireLegal: TalerProtocolTimestamp;
+ stampExpireLegal: DbProtocolTimestamp;
/**
* Data after which coins of this denomination can't be deposited anymore.
*/
- stampExpireDeposit: TalerProtocolTimestamp;
+ stampExpireDeposit: DbProtocolTimestamp;
/**
* Signature by the exchange's master key over the denomination
@@ -406,7 +488,7 @@ export interface DenominationRecord {
* Latest list issue date of the "/keys" response
* that includes this denomination.
*/
- listIssueDate: TalerProtocolTimestamp;
+ listIssueDate: DbProtocolTimestamp;
}
export namespace DenominationRecord {
@@ -418,10 +500,10 @@ export namespace DenominationRecord {
feeRefresh: Amounts.stringify(d.fees.feeRefresh),
feeRefund: Amounts.stringify(d.fees.feeRefund),
feeWithdraw: Amounts.stringify(d.fees.feeWithdraw),
- stampExpireDeposit: d.stampExpireDeposit,
- stampExpireLegal: d.stampExpireLegal,
- stampExpireWithdraw: d.stampExpireWithdraw,
- stampStart: d.stampStart,
+ stampExpireDeposit: timestampProtocolFromDb(d.stampExpireDeposit),
+ stampExpireLegal: timestampProtocolFromDb(d.stampExpireLegal),
+ stampExpireWithdraw: timestampProtocolFromDb(d.stampExpireWithdraw),
+ stampStart: timestampProtocolFromDb(d.stampStart),
value: Amounts.stringify(d.value),
exchangeBaseUrl: d.exchangeBaseUrl,
};
@@ -429,9 +511,9 @@ export namespace DenominationRecord {
}
export interface ExchangeSignkeysRecord {
- stampStart: TalerProtocolTimestamp;
- stampExpire: TalerProtocolTimestamp;
- stampEnd: TalerProtocolTimestamp;
+ stampStart: DbProtocolTimestamp;
+ stampExpire: DbProtocolTimestamp;
+ stampEnd: DbProtocolTimestamp;
signkeyPub: EddsaPublicKeyString;
masterSig: EddsaSignatureString;
@@ -488,7 +570,7 @@ export interface ExchangeDetailsRecord {
tosAccepted:
| {
etag: string;
- timestamp: TalerPreciseTimestamp;
+ timestamp: DbPreciseTimestamp;
}
| undefined;
@@ -500,25 +582,6 @@ export interface ExchangeDetailsRecord {
ageMask?: number;
}
-export interface ExchangeTosRecord {
- exchangeBaseUrl: string;
-
- etag: string;
-
- /**
- * Terms of service text or undefined if not downloaded yet.
- *
- * This is just used as a cache of the last downloaded ToS.
- *
- */
- termsOfServiceText: string | undefined;
-
- /**
- * Content-type of the last downloaded termsOfServiceText.
- */
- termsOfServiceContentType: string | undefined;
-}
-
export interface ExchangeDetailsPointer {
masterPublicKey: string;
@@ -528,7 +591,7 @@ export interface ExchangeDetailsPointer {
* Timestamp when the (masterPublicKey, currency) pointer
* has been updated.
*/
- updateClock: TalerPreciseTimestamp;
+ updateClock: DbPreciseTimestamp;
}
export enum ExchangeEntryDbRecordStatus {
@@ -549,11 +612,6 @@ export enum ExchangeEntryDbUpdateStatus {
}
/**
- * Timestamp stored as a IEEE 754 double, in milliseconds.
- */
-export type DbIndexableTimestampMs = number;
-
-/**
* Exchange record as stored in the wallet's database.
*/
export interface ExchangeEntryRecord {
@@ -563,11 +621,17 @@ export interface ExchangeEntryRecord {
baseUrl: string;
/**
+ * Currency hint for a preset exchange, relevant
+ * when we didn't contact a preset exchange yet.
+ */
+ presetCurrencyHint?: string;
+
+ /**
* When did we confirm the last withdrawal from this exchange?
*
* Used mostly in the UI to suggest exchanges.
*/
- lastWithdrawal?: TalerPreciseTimestamp;
+ lastWithdrawal?: DbPreciseTimestamp;
/**
* Pointer to the current exchange details.
@@ -588,17 +652,12 @@ export interface ExchangeEntryRecord {
/**
* Last time when the exchange /keys info was updated.
*/
- lastUpdate: TalerPreciseTimestamp | undefined;
+ lastUpdate: DbPreciseTimestamp | undefined;
/**
* Next scheduled update for the exchange.
- *
- * (This field must always be present, so we can index on the timestamp.)
- *
- * FIXME: To index on the timestamp, this needs to be a number of
- * binary timestamp!
*/
- nextUpdateStampMs: DbIndexableTimestampMs;
+ nextUpdateStamp: DbPreciseTimestamp;
lastKeysEtag: string | undefined;
@@ -608,7 +667,7 @@ export interface ExchangeEntryRecord {
* Updated whenever the exchange's denominations are updated or when
* the refresh check has been done.
*/
- nextRefreshCheckStampMs: DbIndexableTimestampMs;
+ nextRefreshCheckStamp: DbPreciseTimestamp;
/**
* Public key of the reserve that we're currently using for
@@ -823,7 +882,7 @@ export interface RewardRecord {
* Has the user accepted the tip? Only after the tip has been accepted coins
* withdrawn from the tip may be used.
*/
- acceptedTimestamp: TalerPreciseTimestamp | undefined;
+ acceptedTimestamp: DbPreciseTimestamp | undefined;
/**
* The tipped amount.
@@ -838,7 +897,7 @@ export interface RewardRecord {
/**
* Timestamp, the tip can't be picked up anymore after this deadline.
*/
- rewardExpiration: TalerProtocolTimestamp;
+ rewardExpiration: DbProtocolTimestamp;
/**
* The exchange that will sign our coins, chosen by the merchant.
@@ -876,7 +935,7 @@ export interface RewardRecord {
*/
merchantRewardId: string;
- createdTimestamp: TalerPreciseTimestamp;
+ createdTimestamp: DbPreciseTimestamp;
/**
* The url to be redirected after the tip is accepted.
@@ -887,7 +946,7 @@ export interface RewardRecord {
* Timestamp for when the wallet finished picking up the tip
* from the merchant.
*/
- pickedUpTimestamp: TalerPreciseTimestamp | undefined;
+ pickedUpTimestamp: DbPreciseTimestamp | undefined;
status: RewardRecordStatus;
}
@@ -985,12 +1044,12 @@ export interface RefreshGroupRecord {
*/
statusPerCoin: RefreshCoinStatus[];
- timestampCreated: TalerPreciseTimestamp;
+ timestampCreated: DbPreciseTimestamp;
/**
* Timestamp when the refresh session finished.
*/
- timestampFinished: TalerPreciseTimestamp | undefined;
+ timestampFinished: DbPreciseTimestamp | undefined;
}
/**
@@ -1215,7 +1274,7 @@ export interface PurchaseRecord {
* Timestamp of the first time that sending a payment to the merchant
* for this purchase was successful.
*/
- timestampFirstSuccessfulPay: TalerPreciseTimestamp | undefined;
+ timestampFirstSuccessfulPay: DbPreciseTimestamp | undefined;
merchantPaySig: string | undefined;
@@ -1230,19 +1289,19 @@ export interface PurchaseRecord {
/**
* When was the purchase record created?
*/
- timestamp: TalerPreciseTimestamp;
+ timestamp: DbPreciseTimestamp;
/**
* When was the purchase made?
* Refers to the time that the user accepted.
*/
- timestampAccept: TalerPreciseTimestamp | undefined;
+ timestampAccept: DbPreciseTimestamp | undefined;
/**
* When was the last refund made?
* 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).
@@ -1252,7 +1311,7 @@ export interface PurchaseRecord {
/**
* Continue querying the refund status until this deadline has expired.
*/
- autoRefundDeadline: TalerProtocolTimestamp | undefined;
+ autoRefundDeadline: DbProtocolTimestamp | undefined;
/**
* How much merchant has refund to be taken but the wallet
@@ -1292,12 +1351,12 @@ export interface WalletBackupConfState {
/**
* Timestamp stored in the last backup.
*/
- lastBackupTimestamp?: TalerPreciseTimestamp;
+ lastBackupTimestamp?: DbPreciseTimestamp;
/**
* Last time we tried to do a backup.
*/
- lastBackupCheckTimestamp?: TalerPreciseTimestamp;
+ lastBackupCheckTimestamp?: DbPreciseTimestamp;
lastBackupNonce?: string;
}
@@ -1405,12 +1464,12 @@ export interface WithdrawalGroupRecord {
* When was the withdrawal operation started started?
* Timestamp in milliseconds.
*/
- timestampStart: TalerPreciseTimestamp;
+ timestampStart: DbPreciseTimestamp;
/**
* When was the withdrawal operation completed?
*/
- timestampFinish?: TalerPreciseTimestamp;
+ timestampFinish?: DbPreciseTimestamp;
/**
* Current status of the reserve.
@@ -1501,9 +1560,9 @@ export interface RecoupGroupRecord {
exchangeBaseUrl: string;
- timestampStarted: TalerPreciseTimestamp;
+ timestampStarted: DbPreciseTimestamp;
- timestampFinished: TalerPreciseTimestamp | undefined;
+ timestampFinished: DbPreciseTimestamp | undefined;
/**
* Public keys that identify the coins being recouped
@@ -1537,7 +1596,7 @@ export type BackupProviderState =
}
| {
tag: BackupProviderStateTag.Ready;
- nextBackupTimestamp: TalerPreciseTimestamp;
+ nextBackupTimestamp: DbPreciseTimestamp;
}
| {
tag: BackupProviderStateTag.Retrying;
@@ -1582,7 +1641,7 @@ export interface BackupProviderRecord {
* Does NOT correspond to the timestamp of the backup,
* which only changes when the backup content changes.
*/
- lastBackupCycleTimestamp?: TalerPreciseTimestamp;
+ lastBackupCycleTimestamp?: DbPreciseTimestamp;
/**
* Proposal that we're currently trying to pay for.
@@ -1633,7 +1692,7 @@ export interface DepositTrackingInfo {
// Raw wire transfer identifier of the deposit.
wireTransferId: string;
// When was the wire transfer given to the bank.
- timestampExecuted: TalerProtocolTimestamp;
+ timestampExecuted: DbProtocolTimestamp;
// Total amount transfer for this wtid (including fees)
amountRaw: AmountString;
// Wire fee amount for this exchange
@@ -1655,7 +1714,7 @@ export interface DepositGroupRecord {
*/
amount: AmountString;
- wireTransferDeadline: TalerProtocolTimestamp;
+ wireTransferDeadline: DbProtocolTimestamp;
merchantPub: string;
merchantPriv: string;
@@ -1685,9 +1744,9 @@ export interface DepositGroupRecord {
*/
counterpartyEffectiveDepositAmount: AmountString;
- timestampCreated: TalerPreciseTimestamp;
+ timestampCreated: DbPreciseTimestamp;
- timestampFinished: TalerPreciseTimestamp | undefined;
+ timestampFinished: DbPreciseTimestamp | undefined;
operationStatus: DepositOperationStatus;
@@ -1796,9 +1855,9 @@ export interface PeerPushDebitRecord {
*/
contractEncNonce: string;
- purseExpiration: TalerProtocolTimestamp;
+ purseExpiration: DbProtocolTimestamp;
- timestampCreated: TalerPreciseTimestamp;
+ timestampCreated: DbPreciseTimestamp;
abortRefreshGroupId?: string;
@@ -1871,7 +1930,7 @@ export interface PeerPullCreditRecord {
contractEncNonce: string;
- mergeTimestamp: TalerPreciseTimestamp;
+ mergeTimestamp: DbPreciseTimestamp;
mergeReserveRowId: number;
@@ -1923,7 +1982,7 @@ export interface PeerPushPaymentIncomingRecord {
contractPriv: string;
- timestamp: TalerPreciseTimestamp;
+ timestamp: DbPreciseTimestamp;
estimatedAmountEffective: AmountString;
@@ -1995,7 +2054,7 @@ export interface PeerPullPaymentIncomingRecord {
contractTermsHash: string;
- timestampCreated: TalerPreciseTimestamp;
+ timestampCreated: DbPreciseTimestamp;
/**
* Contract priv that we got from the other party.
@@ -2042,7 +2101,7 @@ export interface OperationRetryRecord {
lastError?: TalerErrorDetail;
- retryInfo: RetryInfo;
+ retryInfo: DbRetryInfo;
}
/**
@@ -2095,14 +2154,13 @@ export interface UserAttentionRecord {
/**
* When the notification was created.
- * FIXME: This should be a TalerPreciseTimestamp
*/
- createdMs: number;
+ created: DbPreciseTimestamp;
/**
* When the user mark this notification as read.
*/
- read: TalerPreciseTimestamp | undefined;
+ read: DbPreciseTimestamp | undefined;
}
export interface DbExchangeHandle {
@@ -2146,7 +2204,7 @@ export interface RefundGroupRecord {
/**
* Timestamp when the refund group was created.
*/
- timestampCreated: TalerPreciseTimestamp;
+ timestampCreated: DbPreciseTimestamp;
proposalId: string;
@@ -2198,12 +2256,12 @@ export interface RefundItemRecord {
/**
* Execution time as claimed by the merchant
*/
- executionTime: TalerProtocolTimestamp;
+ executionTime: DbProtocolTimestamp;
/**
* Time when the wallet became aware of the refund.
*/
- obtainedTime: TalerPreciseTimestamp;
+ obtainedTime: DbPreciseTimestamp;
refundAmount: AmountString;
@@ -2389,15 +2447,19 @@ export const WalletStoresV1 = {
"planchets",
describeContents<PlanchetRecord>({ keyPath: "coinPub" }),
{
- byGroupAgeCoin: describeIndex("byGroupAgeCoin", [
- "withdrawalGroupId",
- "ageWithdrawIdx",
- "coinIdx",
- ]),
- byGroupAndIndex: describeIndex("byGroupAndIndex", [
- "withdrawalGroupId",
- "coinIdx",
- ]),
+ byGroupAgeCoin: describeIndex(
+ "byGroupAgeCoin", [ "withdrawalGroupId", "ageWithdrawIdx", "coinIdx" ],
+ {
+ unique: true,
+ },
+ ),
+ byGroupAndIndex: describeIndex(
+ "byGroupAndIndex",
+ ["withdrawalGroupId", "coinIdx"],
+ {
+ unique: true,
+ },
+ ),
byGroup: describeIndex("byGroup", "withdrawalGroupId"),
byCoinEvHash: describeIndex("byCoinEv", "coinEvHash"),
},
@@ -3049,6 +3111,7 @@ export async function openTalerDatabase(
case "taler-wallet-main-v6":
case "taler-wallet-main-v7":
case "taler-wallet-main-v8":
+ case "taler-wallet-main-v9":
// We consider this a pre-release
// development version, no migration is done.
await metaDb
diff --git a/packages/taler-wallet-core/src/dbless.ts b/packages/taler-wallet-core/src/dbless.ts
index d70eab888..4fc890788 100644
--- a/packages/taler-wallet-core/src/dbless.ts
+++ b/packages/taler-wallet-core/src/dbless.ts
@@ -31,7 +31,7 @@ import {
AmountJson,
Amounts,
AmountString,
- BankAccessApiClient,
+ TalerCorebankApiClient,
codecForAny,
codecForBankWithdrawalOperationPostResponse,
codecForBatchDepositSuccess,
@@ -109,7 +109,7 @@ export async function checkReserve(
export interface TopupReserveWithDemobankArgs {
http: HttpRequestLibrary;
reservePub: string;
- bankAccessApiBaseUrl: string;
+ corebankApiBaseUrl: string;
exchangeInfo: ExchangeInfo;
amount: AmountString;
}
@@ -117,8 +117,8 @@ export interface TopupReserveWithDemobankArgs {
export async function topupReserveWithDemobank(
args: TopupReserveWithDemobankArgs,
) {
- const { http, bankAccessApiBaseUrl, amount, exchangeInfo, reservePub } = args;
- const bankClient = new BankAccessApiClient(bankAccessApiBaseUrl);
+ const { http, corebankApiBaseUrl, amount, exchangeInfo, reservePub } = args;
+ const bankClient = new TalerCorebankApiClient(corebankApiBaseUrl);
const bankUser = await bankClient.createRandomBankUser();
const wopi = await bankClient.createWithdrawalOperation(
bankUser.username,
@@ -142,7 +142,9 @@ export async function topupReserveWithDemobank(
httpResp,
codecForBankWithdrawalOperationPostResponse(),
);
- await bankClient.confirmWithdrawalOperation(bankUser.username, wopi);
+ await bankClient.confirmWithdrawalOperation(bankUser.username, {
+ withdrawalOperationId: wopi.withdrawal_id,
+ });
}
export async function withdrawCoin(args: {
@@ -276,7 +278,10 @@ export async function depositCoin(args: {
merchant_pub: merchantPub,
};
const url = new URL(`batch-deposit`, dp.exchange_url);
- const httpResp = await http.fetch(url.href, { body: requestBody });
+ const httpResp = await http.fetch(url.href, {
+ method: "POST",
+ body: requestBody,
+ });
await readSuccessResponseJsonOrThrow(httpResp, codecForBatchDepositSuccess());
}
diff --git a/packages/taler-wallet-core/src/host-impl.node.ts b/packages/taler-wallet-core/src/host-impl.node.ts
index a6dae58a1..33162ec50 100644
--- a/packages/taler-wallet-core/src/host-impl.node.ts
+++ b/packages/taler-wallet-core/src/host-impl.node.ts
@@ -108,10 +108,13 @@ async function makeSqliteDb(
filename: args.persistentStoragePath ?? ":memory:",
});
myBackend.enableTracing = false;
+ if (process.env.TALER_WALLET_DBSTATS) {
+ myBackend.trackStats = true;
+ }
const myBridgeIdbFactory = new BridgeIDBFactory(myBackend);
return {
getStats() {
- throw Error("not implemented");
+ return myBackend.accessStats;
},
idbFactory: myBridgeIdbFactory,
};
diff --git a/packages/taler-wallet-core/src/operations/attention.ts b/packages/taler-wallet-core/src/operations/attention.ts
index 7d84b43ef..92d69e93e 100644
--- a/packages/taler-wallet-core/src/operations/attention.ts
+++ b/packages/taler-wallet-core/src/operations/attention.ts
@@ -31,6 +31,7 @@ import {
UserAttentionUnreadList,
} from "@gnu-taler/taler-util";
import { InternalWalletState } from "../internal-wallet-state.js";
+import { timestampPreciseFromDb, timestampPreciseToDb } from "../index.js";
const logger = new Logger("operations/attention.ts");
@@ -74,7 +75,7 @@ export async function getUserAttentions(
return;
pending.push({
info: x.info,
- when: TalerPreciseTimestamp.fromMilliseconds(x.createdMs),
+ when: timestampPreciseFromDb(x.created),
read: x.read !== undefined,
});
});
@@ -94,7 +95,7 @@ export async function markAttentionRequestAsRead(
if (!ua) throw Error("attention request not found");
tx.userAttention.put({
...ua,
- read: TalerPreciseTimestamp.now(),
+ read: timestampPreciseToDb(TalerPreciseTimestamp.now()),
});
});
}
@@ -117,7 +118,7 @@ export async function addAttentionRequest(
await tx.userAttention.put({
info,
entityId,
- createdMs: AbsoluteTime.now().t_ms as number,
+ created: timestampPreciseToDb(TalerPreciseTimestamp.now()),
read: undefined,
});
});
diff --git a/packages/taler-wallet-core/src/operations/backup/index.ts b/packages/taler-wallet-core/src/operations/backup/index.ts
index a5e8dbd42..7a2771c57 100644
--- a/packages/taler-wallet-core/src/operations/backup/index.ts
+++ b/packages/taler-wallet-core/src/operations/backup/index.ts
@@ -84,6 +84,9 @@ import {
ConfigRecord,
ConfigRecordKey,
WalletBackupConfState,
+ timestampOptionalPreciseFromDb,
+ timestampPreciseFromDb,
+ timestampPreciseToDb,
} from "../../db.js";
import { InternalWalletState } from "../../internal-wallet-state.js";
import { assertUnreachable } from "../../util/assertUnreachable.js";
@@ -259,10 +262,12 @@ async function runBackupCycleForProvider(
if (!prov) {
return;
}
- prov.lastBackupCycleTimestamp = TalerPreciseTimestamp.now();
+ prov.lastBackupCycleTimestamp = timestampPreciseToDb(
+ TalerPreciseTimestamp.now(),
+ );
prov.state = {
tag: BackupProviderStateTag.Ready,
- nextBackupTimestamp: getNextBackupTimestamp(),
+ nextBackupTimestamp: timestampPreciseToDb(getNextBackupTimestamp()),
};
await tx.backupProviders.put(prov);
});
@@ -361,10 +366,12 @@ async function runBackupCycleForProvider(
return;
}
prov.lastBackupHash = encodeCrock(currentBackupHash);
- prov.lastBackupCycleTimestamp = TalerPreciseTimestamp.now();
+ prov.lastBackupCycleTimestamp = timestampPreciseToDb(
+ TalerPreciseTimestamp.now(),
+ );
prov.state = {
tag: BackupProviderStateTag.Ready,
- nextBackupTimestamp: getNextBackupTimestamp(),
+ nextBackupTimestamp: timestampPreciseToDb(getNextBackupTimestamp()),
};
await tx.backupProviders.put(prov);
});
@@ -594,7 +601,9 @@ export async function addBackupProvider(
if (req.activate) {
oldProv.state = {
tag: BackupProviderStateTag.Ready,
- nextBackupTimestamp: TalerPreciseTimestamp.now(),
+ nextBackupTimestamp: timestampPreciseToDb(
+ TalerPreciseTimestamp.now(),
+ ),
};
logger.info("setting existing backup provider to active");
await tx.backupProviders.put(oldProv);
@@ -616,7 +625,9 @@ export async function addBackupProvider(
if (req.activate) {
state = {
tag: BackupProviderStateTag.Ready,
- nextBackupTimestamp: TalerPreciseTimestamp.now(),
+ nextBackupTimestamp: timestampPreciseToDb(
+ TalerPreciseTimestamp.now(),
+ ),
};
} else {
state = {
@@ -840,7 +851,9 @@ export async function getBackupInfo(
providers.push({
active: x.provider.state.tag !== BackupProviderStateTag.Provisional,
syncProviderBaseUrl: x.provider.baseUrl,
- lastSuccessfulBackupTimestamp: x.provider.lastBackupCycleTimestamp,
+ lastSuccessfulBackupTimestamp: timestampOptionalPreciseFromDb(
+ x.provider.lastBackupCycleTimestamp,
+ ),
paymentProposalIds: x.provider.paymentProposalIds,
lastError:
x.provider.state.tag === BackupProviderStateTag.Retrying
@@ -917,7 +930,9 @@ async function backupRecoveryTheirs(
shouldRetryFreshProposal: false,
state: {
tag: BackupProviderStateTag.Ready,
- nextBackupTimestamp: TalerPreciseTimestamp.now(),
+ nextBackupTimestamp: timestampPreciseToDb(
+ TalerPreciseTimestamp.now(),
+ ),
},
uids: [encodeCrock(getRandomBytes(32))],
});
diff --git a/packages/taler-wallet-core/src/operations/common.ts b/packages/taler-wallet-core/src/operations/common.ts
index 50dd3dc5c..b28a5363d 100644
--- a/packages/taler-wallet-core/src/operations/common.ts
+++ b/packages/taler-wallet-core/src/operations/common.ts
@@ -40,6 +40,7 @@ import {
TalerError,
TalerErrorCode,
TalerErrorDetail,
+ TalerPreciseTimestamp,
TombstoneIdStr,
TransactionIdStr,
TransactionType,
@@ -49,6 +50,7 @@ import { CryptoApiStoppedError } from "../crypto/workers/crypto-dispatcher.js";
import {
BackupProviderRecord,
CoinRecord,
+ DbPreciseTimestamp,
DepositGroupRecord,
ExchangeDetailsRecord,
ExchangeEntryDbRecordStatus,
@@ -62,6 +64,7 @@ import {
RecoupGroupRecord,
RefreshGroupRecord,
RewardRecord,
+ timestampPreciseToDb,
WalletStoresV1,
WithdrawalGroupRecord,
} from "../db.js";
@@ -360,11 +363,11 @@ async function storePendingTaskError(
retryRecord = {
id: pendingTaskId,
lastError: e,
- retryInfo: RetryInfo.reset(),
+ retryInfo: DbRetryInfo.reset(),
};
} else {
retryRecord.lastError = e;
- retryRecord.retryInfo = RetryInfo.increment(retryRecord.retryInfo);
+ retryRecord.retryInfo = DbRetryInfo.increment(retryRecord.retryInfo);
}
await tx.operationRetries.put(retryRecord);
return taskToTransactionNotification(ws, tx, pendingTaskId, e);
@@ -383,7 +386,7 @@ export async function resetPendingTaskTimeout(
if (retryRecord) {
// Note that we don't reset the lastError, it should still be visible
// while the retry runs.
- retryRecord.retryInfo = RetryInfo.reset();
+ retryRecord.retryInfo = DbRetryInfo.reset();
await tx.operationRetries.put(retryRecord);
}
return taskToTransactionNotification(ws, tx, pendingTaskId, undefined);
@@ -403,14 +406,14 @@ async function storePendingTaskPending(
if (!retryRecord) {
retryRecord = {
id: pendingTaskId,
- retryInfo: RetryInfo.reset(),
+ retryInfo: DbRetryInfo.reset(),
};
} else {
if (retryRecord.lastError) {
hadError = true;
}
delete retryRecord.lastError;
- retryRecord.retryInfo = RetryInfo.increment(retryRecord.retryInfo);
+ retryRecord.retryInfo = DbRetryInfo.increment(retryRecord.retryInfo);
}
await tx.operationRetries.put(retryRecord);
if (hadError) {
@@ -590,7 +593,7 @@ export function makeExchangeListItem(
return {
exchangeBaseUrl: r.baseUrl,
- currency: exchangeDetails?.currency,
+ currency: exchangeDetails?.currency ?? r.presetCurrencyHint,
exchangeUpdateStatus,
exchangeEntryStatus,
tosStatus: exchangeDetails
@@ -736,9 +739,9 @@ export interface TaskRunLongpollResult {
type: TaskRunResultType.Longpoll;
}
-export interface RetryInfo {
- firstTry: AbsoluteTime;
- nextRetry: AbsoluteTime;
+export interface DbRetryInfo {
+ firstTry: DbPreciseTimestamp;
+ nextRetry: DbPreciseTimestamp;
retryCounter: number;
}
@@ -755,7 +758,7 @@ const defaultRetryPolicy: RetryPolicy = {
};
function updateTimeout(
- r: RetryInfo,
+ r: DbRetryInfo,
p: RetryPolicy = defaultRetryPolicy,
): void {
const now = AbsoluteTime.now();
@@ -763,7 +766,9 @@ function updateTimeout(
throw Error("assertion failed");
}
if (p.backoffDelta.d_ms === "forever") {
- r.nextRetry = AbsoluteTime.never();
+ r.nextRetry = timestampPreciseToDb(
+ AbsoluteTime.toPreciseTimestamp(AbsoluteTime.never()),
+ );
return;
}
@@ -775,12 +780,12 @@ function updateTimeout(
(p.maxTimeout.d_ms === "forever"
? nextIncrement
: Math.min(p.maxTimeout.d_ms, nextIncrement));
- r.nextRetry = AbsoluteTime.fromMilliseconds(t);
+ r.nextRetry = timestampPreciseToDb(TalerPreciseTimestamp.fromMilliseconds(t));
}
-export namespace RetryInfo {
+export namespace DbRetryInfo {
export function getDuration(
- r: RetryInfo | undefined,
+ r: DbRetryInfo | undefined,
p: RetryPolicy = defaultRetryPolicy,
): Duration {
if (!r) {
@@ -797,11 +802,11 @@ export namespace RetryInfo {
};
}
- export function reset(p: RetryPolicy = defaultRetryPolicy): RetryInfo {
- const now = AbsoluteTime.now();
- const info = {
- firstTry: now,
- nextRetry: now,
+ export function reset(p: RetryPolicy = defaultRetryPolicy): DbRetryInfo {
+ const now = TalerPreciseTimestamp.now();
+ const info: DbRetryInfo = {
+ firstTry: timestampPreciseToDb(now),
+ nextRetry: timestampPreciseToDb(now),
retryCounter: 0,
};
updateTimeout(info, p);
@@ -809,9 +814,9 @@ export namespace RetryInfo {
}
export function increment(
- r: RetryInfo | undefined,
+ r: DbRetryInfo | undefined,
p: RetryPolicy = defaultRetryPolicy,
- ): RetryInfo {
+ ): DbRetryInfo {
if (!r) {
return reset(p);
}
diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts
index 2de8f30a1..111d15989 100644
--- a/packages/taler-wallet-core/src/operations/deposits.ts
+++ b/packages/taler-wallet-core/src/operations/deposits.ts
@@ -73,6 +73,9 @@ import {
RefreshOperationStatus,
createRefreshGroup,
getTotalRefreshCost,
+ timestampPreciseToDb,
+ timestampProtocolFromDb,
+ timestampProtocolToDb,
} from "../index.js";
import { InternalWalletState } from "../internal-wallet-state.js";
import { assertUnreachable } from "../util/assertUnreachable.js";
@@ -799,7 +802,7 @@ async function processDepositGroupPendingTrack(
amountRaw: Amounts.stringify(raw),
wireFee: Amounts.stringify(wireFee),
exchangePub: track.exchange_pub,
- timestampExecuted: track.execution_time,
+ timestampExecuted: timestampProtocolToDb(track.execution_time),
wireTransferId: track.wtid,
},
id: track.exchange_sig,
@@ -857,7 +860,9 @@ async function processDepositGroupPendingTrack(
}
}
if (allWired) {
- dg.timestampFinished = TalerPreciseTimestamp.now();
+ dg.timestampFinished = timestampPreciseToDb(
+ TalerPreciseTimestamp.now(),
+ );
dg.operationStatus = DepositOperationStatus.Finished;
await tx.depositGroups.put(dg);
}
@@ -1375,7 +1380,9 @@ export async function createDepositGroup(
amount: contractData.amount,
noncePriv: noncePair.priv,
noncePub: noncePair.pub,
- timestampCreated: AbsoluteTime.toPreciseTimestamp(now),
+ timestampCreated: timestampPreciseToDb(
+ AbsoluteTime.toPreciseTimestamp(now),
+ ),
timestampFinished: undefined,
statusPerCoin: payCoinSel.coinSel.coinPubs.map(
() => DepositElementStatus.DepositPending,
@@ -1388,7 +1395,9 @@ export async function createDepositGroup(
counterpartyEffectiveDepositAmount: Amounts.stringify(
counterpartyEffectiveDepositAmount,
),
- wireTransferDeadline: contractTerms.wire_transfer_deadline,
+ wireTransferDeadline: timestampProtocolToDb(
+ contractTerms.wire_transfer_deadline,
+ ),
wire: {
payto_uri: req.depositPaytoUri,
salt: wireSalt,
diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts
index 43a08ed3b..82d7b42bf 100644
--- a/packages/taler-wallet-core/src/operations/exchanges.ts
+++ b/packages/taler-wallet-core/src/operations/exchanges.ts
@@ -74,6 +74,9 @@ import {
ExchangeEntryDbRecordStatus,
ExchangeEntryDbUpdateStatus,
isWithdrawableDenom,
+ timestampPreciseFromDb,
+ timestampPreciseToDb,
+ timestampProtocolToDb,
WalletDbReadWriteTransaction,
} from "../index.js";
import { InternalWalletState, TrustInfo } from "../internal-wallet-state.js";
@@ -174,7 +177,7 @@ export async function acceptExchangeTermsOfService(
if (d) {
d.tosAccepted = {
etag: etag || d.tosCurrentEtag,
- timestamp: TalerPreciseTimestamp.now(),
+ timestamp: timestampPreciseToDb(TalerPreciseTimestamp.now()),
};
await tx.exchangeDetails.put(d);
}
@@ -306,6 +309,7 @@ export async function downloadExchangeInfo(
export async function addPresetExchangeEntry(
tx: WalletDbReadWriteTransaction<"exchanges">,
exchangeBaseUrl: string,
+ currencyHint?: string,
): Promise<void> {
let exchange = await tx.exchanges.get(exchangeBaseUrl);
if (!exchange) {
@@ -313,17 +317,22 @@ export async function addPresetExchangeEntry(
entryStatus: ExchangeEntryDbRecordStatus.Preset,
updateStatus: ExchangeEntryDbUpdateStatus.Initial,
baseUrl: exchangeBaseUrl,
+ presetCurrencyHint: currencyHint,
detailsPointer: undefined,
lastUpdate: undefined,
lastKeysEtag: undefined,
- nextRefreshCheckStampMs: AbsoluteTime.getStampMsNever(),
- nextUpdateStampMs: AbsoluteTime.getStampMsNever(),
+ nextRefreshCheckStamp: timestampPreciseToDb(
+ AbsoluteTime.toPreciseTimestamp(AbsoluteTime.never()),
+ ),
+ nextUpdateStamp: timestampPreciseToDb(
+ AbsoluteTime.toPreciseTimestamp(AbsoluteTime.never()),
+ ),
};
await tx.exchanges.put(r);
}
}
-export async function provideExchangeRecordInTx(
+async function provideExchangeRecordInTx(
ws: InternalWalletState,
tx: GetReadWriteAccess<{
exchanges: typeof WalletStoresV1.exchanges;
@@ -343,8 +352,12 @@ export async function provideExchangeRecordInTx(
baseUrl: baseUrl,
detailsPointer: undefined,
lastUpdate: undefined,
- nextUpdateStampMs: AbsoluteTime.getStampMsNever(),
- nextRefreshCheckStampMs: AbsoluteTime.getStampMsNever(),
+ nextUpdateStamp: timestampPreciseToDb(
+ AbsoluteTime.toPreciseTimestamp(AbsoluteTime.never()),
+ ),
+ nextRefreshCheckStamp: timestampPreciseToDb(
+ AbsoluteTime.toPreciseTimestamp(AbsoluteTime.never()),
+ ),
lastKeysEtag: undefined,
};
await tx.exchanges.put(r);
@@ -445,13 +458,19 @@ async function downloadExchangeKeysInfo(
isRevoked: false,
value: Amounts.stringify(value),
currency: value.currency,
- stampExpireDeposit: denomIn.stamp_expire_deposit,
- stampExpireLegal: denomIn.stamp_expire_legal,
- stampExpireWithdraw: denomIn.stamp_expire_withdraw,
- stampStart: denomIn.stamp_start,
+ stampExpireDeposit: timestampProtocolToDb(
+ denomIn.stamp_expire_deposit,
+ ),
+ stampExpireLegal: timestampProtocolToDb(denomIn.stamp_expire_legal),
+ stampExpireWithdraw: timestampProtocolToDb(
+ denomIn.stamp_expire_withdraw,
+ ),
+ stampStart: timestampProtocolToDb(denomIn.stamp_start),
verificationStatus: DenominationVerificationStatus.Unverified,
masterSig: denomIn.master_sig,
- listIssueDate: exchangeKeysJsonUnchecked.list_issue_date,
+ listIssueDate: timestampProtocolToDb(
+ exchangeKeysJsonUnchecked.list_issue_date,
+ ),
fees: {
feeDeposit: Amounts.stringify(denomGroup.fee_deposit),
feeRefresh: Amounts.stringify(denomGroup.fee_refresh),
@@ -613,7 +632,9 @@ export async function updateExchangeFromUrlHandler(
!forceNow &&
exchangeDetails !== undefined &&
!AbsoluteTime.isExpired(
- AbsoluteTime.fromStampMs(exchange.nextUpdateStampMs),
+ AbsoluteTime.fromPreciseTimestamp(
+ timestampPreciseFromDb(exchange.nextUpdateStamp),
+ ),
)
) {
logger.trace("using existing exchange info");
@@ -753,17 +774,21 @@ export async function updateExchangeFromUrlHandler(
if (existingDetails?.rowId) {
newDetails.rowId = existingDetails.rowId;
}
- r.lastUpdate = TalerPreciseTimestamp.now();
- r.nextUpdateStampMs = AbsoluteTime.toStampMs(
- AbsoluteTime.fromProtocolTimestamp(keysInfo.expiry),
+ r.lastUpdate = timestampPreciseToDb(TalerPreciseTimestamp.now());
+ r.nextUpdateStamp = timestampPreciseToDb(
+ AbsoluteTime.toPreciseTimestamp(
+ AbsoluteTime.fromProtocolTimestamp(keysInfo.expiry),
+ ),
);
// New denominations might be available.
- r.nextRefreshCheckStampMs = AbsoluteTime.getStampMsNow();
+ r.nextRefreshCheckStamp = timestampPreciseToDb(
+ TalerPreciseTimestamp.now(),
+ );
if (detailsPointerChanged) {
r.detailsPointer = {
currency: newDetails.currency,
masterPublicKey: newDetails.masterPublicKey,
- updateClock: TalerPreciseTimestamp.now(),
+ updateClock: timestampPreciseToDb(TalerPreciseTimestamp.now()),
};
}
await tx.exchanges.put(r);
@@ -776,9 +801,9 @@ export async function updateExchangeFromUrlHandler(
exchangeDetailsRowId: drRowId.key,
masterSig: sk.master_sig,
signkeyPub: sk.key,
- stampEnd: sk.stamp_end,
- stampExpire: sk.stamp_expire,
- stampStart: sk.stamp_start,
+ stampEnd: timestampProtocolToDb(sk.stamp_end),
+ stampExpire: timestampProtocolToDb(sk.stamp_expire),
+ stampStart: timestampProtocolToDb(sk.stamp_start),
});
}
@@ -813,7 +838,7 @@ export async function updateExchangeFromUrlHandler(
);
}
} else {
- x.listIssueDate = keysInfo.listIssueDate;
+ x.listIssueDate = timestampProtocolToDb(keysInfo.listIssueDate);
if (!x.isOffered) {
x.isOffered = true;
logger.info(
diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts b/packages/taler-wallet-core/src/operations/pay-merchant.ts
index fe0cbeda0..157541ed3 100644
--- a/packages/taler-wallet-core/src/operations/pay-merchant.ts
+++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts
@@ -103,6 +103,9 @@ import {
RefundGroupStatus,
RefundItemRecord,
RefundItemStatus,
+ timestampPreciseToDb,
+ timestampProtocolFromDb,
+ timestampProtocolToDb,
} from "../index.js";
import {
EXCHANGE_COINS_LOCK,
@@ -114,7 +117,7 @@ import { checkDbInvariant } from "../util/invariants.js";
import { GetReadOnlyAccess } from "../util/query.js";
import {
constructTaskIdentifier,
- RetryInfo,
+ DbRetryInfo,
runLongpollAsync,
runTaskWithErrorReporting,
spendCoins,
@@ -216,11 +219,13 @@ async function failProposalPermanently(
notifyTransition(ws, transactionId, transitionInfo);
}
-function getProposalRequestTimeout(retryInfo?: RetryInfo): Duration {
+function getProposalRequestTimeout(retryInfo?: DbRetryInfo): Duration {
return Duration.clamp({
lower: Duration.fromSpec({ seconds: 1 }),
upper: Duration.fromSpec({ seconds: 60 }),
- value: retryInfo ? RetryInfo.getDuration(retryInfo) : Duration.fromSpec({}),
+ value: retryInfo
+ ? DbRetryInfo.getDuration(retryInfo)
+ : Duration.fromSpec({}),
});
}
@@ -644,7 +649,7 @@ async function createPurchase(
noncePriv: priv,
noncePub: pub,
claimToken,
- timestamp: TalerPreciseTimestamp.now(),
+ timestamp: timestampPreciseToDb(TalerPreciseTimestamp.now()),
merchantBaseUrl,
orderId,
proposalId: proposalId,
@@ -717,7 +722,7 @@ async function storeFirstPaySuccess(
if (purchase.purchaseStatus === PurchaseStatus.PendingPaying) {
purchase.purchaseStatus = PurchaseStatus.Done;
}
- purchase.timestampFirstSuccessfulPay = now;
+ purchase.timestampFirstSuccessfulPay = timestampPreciseToDb(now);
purchase.lastSessionId = sessionId;
purchase.merchantPaySig = payResponse.sig;
purchase.posConfirmation = payResponse.pos_confirmation;
@@ -737,8 +742,10 @@ async function storeFirstPaySuccess(
const ar = Duration.fromTalerProtocolDuration(protoAr);
logger.info("auto_refund present");
purchase.purchaseStatus = PurchaseStatus.PendingQueryingAutoRefund;
- purchase.autoRefundDeadline = AbsoluteTime.toProtocolTimestamp(
- AbsoluteTime.addDuration(AbsoluteTime.now(), ar),
+ purchase.autoRefundDeadline = timestampProtocolToDb(
+ AbsoluteTime.toProtocolTimestamp(
+ AbsoluteTime.addDuration(AbsoluteTime.now(), ar),
+ ),
);
}
await tx.purchases.put(purchase);
@@ -941,7 +948,9 @@ async function unblockBackup(
.forEachAsync(async (bp) => {
bp.state = {
tag: BackupProviderStateTag.Ready,
- nextBackupTimestamp: TalerPreciseTimestamp.now(),
+ nextBackupTimestamp: timestampPreciseToDb(
+ TalerPreciseTimestamp.now(),
+ ),
};
tx.backupProviders.put(bp);
});
@@ -1447,7 +1456,7 @@ export async function confirmPay(
totalPayCost: Amounts.stringify(payCostInfo),
};
p.lastSessionId = sessionId;
- p.timestampAccept = TalerPreciseTimestamp.now();
+ p.timestampAccept = timestampPreciseToDb(TalerPreciseTimestamp.now());
p.purchaseStatus = PurchaseStatus.PendingPaying;
await tx.purchases.put(p);
await spendCoins(ws, tx, {
@@ -2340,7 +2349,9 @@ async function processPurchaseAutoRefund(
if (
!purchase.autoRefundDeadline ||
AbsoluteTime.isExpired(
- AbsoluteTime.fromProtocolTimestamp(purchase.autoRefundDeadline),
+ AbsoluteTime.fromProtocolTimestamp(
+ timestampProtocolFromDb(purchase.autoRefundDeadline),
+ ),
)
) {
const transitionInfo = await ws.db
@@ -2791,7 +2802,7 @@ async function storeRefunds(
proposalId: purchase.proposalId,
refundGroupId: newRefundGroupId,
status: RefundGroupStatus.Pending,
- timestampCreated: now,
+ timestampCreated: timestampPreciseToDb(now),
amountEffective: Amounts.stringify(
Amounts.zeroOfCurrency(currency),
),
@@ -2801,8 +2812,8 @@ async function storeRefunds(
const status: RefundItemStatus = getItemStatus(rf);
const newItem: RefundItemRecord = {
coinPub: rf.coin_pub,
- executionTime: rf.execution_time,
- obtainedTime: now,
+ executionTime: timestampProtocolToDb(rf.execution_time),
+ obtainedTime: timestampPreciseToDb(now),
refundAmount: rf.refund_amount,
refundGroupId: newGroup.refundGroupId,
rtxid: rf.rtransaction_id,
diff --git a/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts
index 0355eb152..54b78957f 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer-pull-credit.ts
@@ -60,6 +60,9 @@ import {
PeerPullPaymentCreditStatus,
WithdrawalGroupStatus,
WithdrawalRecordType,
+ timestampOptionalPreciseFromDb,
+ timestampPreciseFromDb,
+ timestampPreciseToDb,
updateExchangeFromUrl,
} from "../index.js";
import { InternalWalletState } from "../internal-wallet-state.js";
@@ -389,18 +392,20 @@ async function handlePeerPullCreditCreatePurse(
const econtractResp = await ws.cryptoApi.encryptContractForDeposit({
contractPriv: pullIni.contractPriv,
contractPub: pullIni.contractPub,
- contractTerms: contractTermsRecord,
+ contractTerms: contractTermsRecord.contractTermsRaw,
pursePriv: pullIni.pursePriv,
pursePub: pullIni.pursePub,
nonce: pullIni.contractEncNonce,
});
+ const mergeTimestamp = timestampPreciseFromDb(pullIni.mergeTimestamp);
+
const purseExpiration = contractTerms.purse_expiration;
const sigRes = await ws.cryptoApi.signReservePurseCreate({
contractTermsHash: pullIni.contractTermsHash,
flags: WalletAccountMergeFlags.CreateWithPurseFee,
mergePriv: pullIni.mergePriv,
- mergeTimestamp: TalerPreciseTimestamp.round(pullIni.mergeTimestamp),
+ mergeTimestamp: TalerPreciseTimestamp.round(mergeTimestamp),
purseAmount: pullIni.amount,
purseExpiration: purseExpiration,
purseFee: purseFee,
@@ -412,7 +417,7 @@ async function handlePeerPullCreditCreatePurse(
const reservePurseReqBody: ExchangeReservePurseRequest = {
merge_sig: sigRes.mergeSig,
- merge_timestamp: TalerPreciseTimestamp.round(pullIni.mergeTimestamp),
+ merge_timestamp: TalerPreciseTimestamp.round(mergeTimestamp),
h_contract_terms: pullIni.contractTermsHash,
merge_pub: pullIni.mergePub,
min_age: 0,
@@ -695,11 +700,17 @@ async function getPreferredExchangeForCurrency(
if (candidate.lastWithdrawal && !e.lastWithdrawal) {
continue;
}
- if (candidate.lastWithdrawal && e.lastWithdrawal) {
+ const exchangeLastWithdrawal = timestampOptionalPreciseFromDb(
+ e.lastWithdrawal,
+ );
+ const candidateLastWithdrawal = timestampOptionalPreciseFromDb(
+ candidate.lastWithdrawal,
+ );
+ if (exchangeLastWithdrawal && candidateLastWithdrawal) {
if (
AbsoluteTime.cmp(
- AbsoluteTime.fromPreciseTimestamp(e.lastWithdrawal),
- AbsoluteTime.fromPreciseTimestamp(candidate.lastWithdrawal),
+ AbsoluteTime.fromPreciseTimestamp(exchangeLastWithdrawal),
+ AbsoluteTime.fromPreciseTimestamp(candidateLastWithdrawal),
) > 0
) {
candidate = e;
@@ -741,8 +752,6 @@ export async function initiatePeerPullPayment(
exchangeBaseUrl: exchangeBaseUrl,
});
- const mergeTimestamp = TalerPreciseTimestamp.now();
-
const pursePair = await ws.cryptoApi.createEddsaKeypair({});
const mergePair = await ws.cryptoApi.createEddsaKeypair({});
@@ -766,6 +775,8 @@ export async function initiatePeerPullPayment(
undefined,
);
+ const mergeTimestamp = TalerPreciseTimestamp.now();
+
const transitionInfo = await ws.db
.mktx((x) => [x.peerPullCredit, x.contractTerms])
.runReadWrite(async (tx) => {
@@ -778,7 +789,7 @@ export async function initiatePeerPullPayment(
mergePriv: mergePair.priv,
mergePub: mergePair.pub,
status: PeerPullPaymentCreditStatus.PendingCreatePurse,
- mergeTimestamp,
+ mergeTimestamp: timestampPreciseToDb(mergeTimestamp),
contractEncNonce,
mergeReserveRowId: mergeReserveRowId,
contractPriv: contractKeyPair.priv,
diff --git a/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts
index 5bcfa3418..48cbf574f 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer-pull-debit.ts
@@ -59,6 +59,7 @@ import {
PendingTaskType,
RefreshOperationStatus,
createRefreshGroup,
+ timestampPreciseToDb,
} from "../index.js";
import { assertUnreachable } from "../util/assertUnreachable.js";
import { checkLogicInvariant } from "../util/invariants.js";
@@ -595,7 +596,7 @@ export async function preparePeerPullDebit(
contractPriv: contractPriv,
exchangeBaseUrl: exchangeBaseUrl,
pursePub: pursePub,
- timestampCreated: TalerPreciseTimestamp.now(),
+ timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
contractTermsHash,
amount: contractTerms.amount,
status: PeerPullDebitRecordStatus.DialogProposed,
diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts
index 89d9e3b49..e4698c203 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer-push-credit.ts
@@ -59,6 +59,7 @@ import {
PendingTaskType,
WithdrawalGroupStatus,
WithdrawalRecordType,
+ timestampPreciseToDb,
} from "../index.js";
import { assertUnreachable } from "../util/assertUnreachable.js";
import { checkDbInvariant } from "../util/invariants.js";
@@ -129,12 +130,10 @@ export async function preparePeerPushCredit(
amountEffective: existing.existingPushInc.estimatedAmountEffective,
amountRaw: existing.existingContractTerms.amount,
contractTerms: existing.existingContractTerms,
- peerPushCreditId:
- existing.existingPushInc.peerPushCreditId,
+ peerPushCreditId: existing.existingPushInc.peerPushCreditId,
transactionId: constructTransactionIdentifier({
tag: TransactionType.PeerPushCredit,
- peerPushCreditId:
- existing.existingPushInc.peerPushCreditId,
+ peerPushCreditId: existing.existingPushInc.peerPushCreditId,
}),
};
}
@@ -196,7 +195,7 @@ export async function preparePeerPushCredit(
exchangeBaseUrl: exchangeBaseUrl,
mergePriv: dec.mergePriv,
pursePub: pursePub,
- timestamp: TalerPreciseTimestamp.now(),
+ timestamp: timestampPreciseToDb(TalerPreciseTimestamp.now()),
contractTermsHash,
status: PeerPushCreditStatus.DialogProposed,
withdrawalGroupId,
@@ -263,16 +262,11 @@ async function longpollKycStatus(
const transitionInfo = await ws.db
.mktx((x) => [x.peerPushCredit])
.runReadWrite(async (tx) => {
- const peerInc = await tx.peerPushCredit.get(
- peerPushCreditId,
- );
+ const peerInc = await tx.peerPushCredit.get(peerPushCreditId);
if (!peerInc) {
return;
}
- if (
- peerInc.status !==
- PeerPushCreditStatus.PendingMergeKycRequired
- ) {
+ if (peerInc.status !== PeerPushCreditStatus.PendingMergeKycRequired) {
return;
}
const oldTxState = computePeerPushCreditTransactionState(peerInc);
@@ -333,9 +327,7 @@ async function processPeerPushCreditKycRequired(
const { transitionInfo, result } = await ws.db
.mktx((x) => [x.peerPushCredit])
.runReadWrite(async (tx) => {
- const peerInc = await tx.peerPushCredit.get(
- peerPushCreditId,
- );
+ const peerInc = await tx.peerPushCredit.get(peerPushCreditId);
if (!peerInc) {
return {
transitionInfo: undefined,
@@ -466,9 +458,7 @@ async function handlePendingMerge(
x.exchangeDetails,
])
.runReadWrite(async (tx) => {
- const peerInc = await tx.peerPushCredit.get(
- peerPushCreditId,
- );
+ const peerInc = await tx.peerPushCredit.get(peerPushCreditId);
if (!peerInc) {
return undefined;
}
@@ -520,9 +510,7 @@ async function handlePendingWithdrawing(
const transitionInfo = await ws.db
.mktx((x) => [x.peerPushCredit, x.withdrawalGroups])
.runReadWrite(async (tx) => {
- const ppi = await tx.peerPushCredit.get(
- peerInc.peerPushCreditId,
- );
+ const ppi = await tx.peerPushCredit.get(peerInc.peerPushCreditId);
if (!ppi) {
finished = true;
return;
@@ -631,9 +619,7 @@ export async function confirmPeerPushCredit(
}
peerPushCreditId = parsedTx.peerPushCreditId;
} else {
- throw Error(
- "no transaction ID (or deprecated peerPushCreditId) provided",
- );
+ throw Error("no transaction ID (or deprecated peerPushCreditId) provided");
}
await ws.db
@@ -683,9 +669,7 @@ export async function suspendPeerPushCreditTransaction(
const transitionInfo = await ws.db
.mktx((x) => [x.peerPushCredit])
.runReadWrite(async (tx) => {
- const pushCreditRec = await tx.peerPushCredit.get(
- peerPushCreditId,
- );
+ const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId);
if (!pushCreditRec) {
logger.warn(`peer push credit ${peerPushCreditId} not found`);
return;
@@ -746,9 +730,7 @@ export async function abortPeerPushCreditTransaction(
const transitionInfo = await ws.db
.mktx((x) => [x.peerPushCredit])
.runReadWrite(async (tx) => {
- const pushCreditRec = await tx.peerPushCredit.get(
- peerPushCreditId,
- );
+ const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId);
if (!pushCreditRec) {
logger.warn(`peer push credit ${peerPushCreditId} not found`);
return;
@@ -820,9 +802,7 @@ export async function resumePeerPushCreditTransaction(
const transitionInfo = await ws.db
.mktx((x) => [x.peerPushCredit])
.runReadWrite(async (tx) => {
- const pushCreditRec = await tx.peerPushCredit.get(
- peerPushCreditId,
- );
+ const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId);
if (!pushCreditRec) {
logger.warn(`peer push credit ${peerPushCreditId} not found`);
return;
diff --git a/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts
index e80ffc059..50ae8d41b 100644
--- a/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts
+++ b/packages/taler-wallet-core/src/operations/pay-peer-push-debit.ts
@@ -55,10 +55,14 @@ import {
PeerPushDebitStatus,
RefreshOperationStatus,
createRefreshGroup,
+ timestampPreciseToDb,
+ timestampProtocolFromDb,
+ timestampProtocolToDb,
} from "../index.js";
import { InternalWalletState } from "../internal-wallet-state.js";
import { PendingTaskType } from "../pending-types.js";
import { assertUnreachable } from "../util/assertUnreachable.js";
+import { PeerCoinRepair, selectPeerCoins } from "../util/coinSelection.js";
import { checkLogicInvariant } from "../util/invariants.js";
import {
TaskRunResult,
@@ -77,7 +81,6 @@ import {
notifyTransition,
stopLongpolling,
} from "./transactions.js";
-import { PeerCoinRepair, selectPeerCoins } from "../util/coinSelection.js";
const logger = new Logger("pay-peer-push-debit.ts");
@@ -207,7 +210,7 @@ async function processPeerPushDebitCreateReserve(
mergePub: peerPushInitiation.mergePub,
minAge: 0,
purseAmount: peerPushInitiation.amount,
- purseExpiration,
+ purseExpiration: timestampProtocolFromDb(purseExpiration),
pursePriv: peerPushInitiation.pursePriv,
});
@@ -242,8 +245,6 @@ async function processPeerPushDebitCreateReserve(
hash(decodeCrock(econtractResp.econtract.econtract)),
);
- logger.info(`econtract hash: ${econtractHash}`);
-
const createPurseUrl = new URL(
`purses/${peerPushInitiation.pursePub}/create`,
peerPushInitiation.exchangeBaseUrl,
@@ -254,7 +255,7 @@ async function processPeerPushDebitCreateReserve(
merge_pub: peerPushInitiation.mergePub,
purse_sig: purseSigResp.sig,
h_contract_terms: hContractTerms,
- purse_expiration: purseExpiration,
+ purse_expiration: timestampProtocolFromDb(purseExpiration),
deposits: depositSigsResp.deposits,
min_age: 0,
econtract: econtractResp.econtract,
@@ -646,7 +647,6 @@ export async function initiatePeerPushDebit(
// we might want to mark the coins as used and spend them
// after we've been able to create the purse.
await spendCoins(ws, tx, {
- // allocationId: `txn:peer-push-debit:${pursePair.pub}`,
allocationId: constructTransactionIdentifier({
tag: TransactionType.PeerPushDebit,
pursePub: pursePair.pub,
@@ -666,10 +666,10 @@ export async function initiatePeerPushDebit(
exchangeBaseUrl: sel.exchangeBaseUrl,
mergePriv: mergePair.priv,
mergePub: mergePair.pub,
- purseExpiration: purseExpiration,
+ purseExpiration: timestampProtocolToDb(purseExpiration),
pursePriv: pursePair.priv,
pursePub: pursePair.pub,
- timestampCreated: TalerPreciseTimestamp.now(),
+ timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
status: PeerPushDebitStatus.PendingCreatePurse,
contractEncNonce,
coinSel: {
diff --git a/packages/taler-wallet-core/src/operations/pending.ts b/packages/taler-wallet-core/src/operations/pending.ts
index 6115f848b..1819aa1b8 100644
--- a/packages/taler-wallet-core/src/operations/pending.ts
+++ b/packages/taler-wallet-core/src/operations/pending.ts
@@ -21,42 +21,46 @@
/**
* Imports.
*/
+import { GlobalIDB } from "@gnu-taler/idb-bridge";
+import { AbsoluteTime, TransactionRecordFilter } from "@gnu-taler/taler-util";
import {
- PurchaseStatus,
- WalletStoresV1,
BackupProviderStateTag,
- RefreshCoinStatus,
- PeerPushDebitStatus,
- PeerPullDebitRecordStatus,
- PeerPushCreditStatus,
- PeerPullPaymentCreditStatus,
- WithdrawalGroupStatus,
- RewardRecordStatus,
- DepositOperationStatus,
- RefreshGroupRecord,
- WithdrawalGroupRecord,
+ DepositElementStatus,
DepositGroupRecord,
- RewardRecord,
- PurchaseRecord,
+ DepositOperationStatus,
+ ExchangeEntryDbUpdateStatus,
PeerPullCreditRecord,
+ PeerPullDebitRecordStatus,
+ PeerPullPaymentCreditStatus,
PeerPullPaymentIncomingRecord,
+ PeerPushCreditStatus,
PeerPushDebitRecord,
+ PeerPushDebitStatus,
PeerPushPaymentIncomingRecord,
+ PurchaseRecord,
+ PurchaseStatus,
+ RefreshCoinStatus,
+ RefreshGroupRecord,
+ RefreshOperationStatus,
RefundGroupRecord,
RefundGroupStatus,
- ExchangeEntryDbUpdateStatus,
- RefreshOperationStatus,
- DepositElementStatus,
+ RewardRecord,
+ RewardRecordStatus,
+ WalletStoresV1,
+ WithdrawalGroupRecord,
+ WithdrawalGroupStatus,
+ timestampAbsoluteFromDb,
+ timestampOptionalAbsoluteFromDb,
+ timestampPreciseFromDb,
+ timestampPreciseToDb,
} from "../db.js";
+import { InternalWalletState } from "../internal-wallet-state.js";
import {
PendingOperationsResponse,
PendingTaskType,
TaskId,
} from "../pending-types.js";
-import { AbsoluteTime, TransactionRecordFilter } from "@gnu-taler/taler-util";
-import { InternalWalletState } from "../internal-wallet-state.js";
import { GetReadOnlyAccess } from "../util/query.js";
-import { GlobalIDB } from "@gnu-taler/idb-bridge";
import { TaskIdentifiers } from "./common.js";
function getPendingCommon(
@@ -99,12 +103,14 @@ async function gatherExchangePending(
}
const opTag = TaskIdentifiers.forExchangeUpdate(exch);
let opr = await tx.operationRetries.get(opTag);
- const timestampDue =
- opr?.retryInfo.nextRetry ??
- AbsoluteTime.fromStampMs(exch.nextUpdateStampMs);
+ const timestampDue = opr?.retryInfo.nextRetry ?? exch.nextRefreshCheckStamp;
resp.pendingOperations.push({
type: PendingTaskType.ExchangeUpdate,
- ...getPendingCommon(ws, opTag, timestampDue),
+ ...getPendingCommon(
+ ws,
+ opTag,
+ AbsoluteTime.fromPreciseTimestamp(timestampPreciseFromDb(timestampDue)),
+ ),
givesLifeness: false,
exchangeBaseUrl: exch.baseUrl,
lastError: opr?.lastError,
@@ -115,8 +121,16 @@ async function gatherExchangePending(
if (!opr?.lastError) {
resp.pendingOperations.push({
type: PendingTaskType.ExchangeCheckRefresh,
- ...getPendingCommon(ws, opTag, timestampDue),
- timestampDue: AbsoluteTime.fromStampMs(exch.nextRefreshCheckStampMs),
+ ...getPendingCommon(
+ ws,
+ opTag,
+ AbsoluteTime.fromPreciseTimestamp(
+ timestampPreciseFromDb(timestampDue),
+ ),
+ ),
+ timestampDue: AbsoluteTime.fromPreciseTimestamp(
+ timestampPreciseFromDb(exch.nextRefreshCheckStamp),
+ ),
givesLifeness: false,
exchangeBaseUrl: exch.baseUrl,
});
@@ -165,7 +179,9 @@ async function gatherRefreshPending(
}
const opId = TaskIdentifiers.forRefresh(r);
const retryRecord = await tx.operationRetries.get(opId);
- const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
+ const timestampDue =
+ timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ??
+ AbsoluteTime.now();
resp.pendingOperations.push({
type: PendingTaskType.Refresh,
...getPendingCommon(ws, opId, timestampDue),
@@ -222,8 +238,8 @@ async function gatherWithdrawalPending(
opr = {
id: opTag,
retryInfo: {
- firstTry: now,
- nextRetry: now,
+ firstTry: timestampPreciseToDb(AbsoluteTime.toPreciseTimestamp(now)),
+ nextRetry: timestampPreciseToDb(AbsoluteTime.toPreciseTimestamp(now)),
retryCounter: 0,
},
};
@@ -233,7 +249,8 @@ async function gatherWithdrawalPending(
...getPendingCommon(
ws,
opTag,
- opr.retryInfo?.nextRetry ?? AbsoluteTime.now(),
+ timestampOptionalAbsoluteFromDb(opr.retryInfo?.nextRetry) ??
+ AbsoluteTime.now(),
),
givesLifeness: true,
withdrawalGroupId: wsr.withdrawalGroupId,
@@ -285,7 +302,9 @@ async function gatherDepositPending(
}
const opId = TaskIdentifiers.forDeposit(dg);
const retryRecord = await tx.operationRetries.get(opId);
- const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
+ const timestampDue =
+ timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ??
+ AbsoluteTime.now();
resp.pendingOperations.push({
type: PendingTaskType.Deposit,
...getPendingCommon(ws, opId, timestampDue),
@@ -330,13 +349,15 @@ async function gatherRewardPending(
await iterRecordsForReward(tx, { onlyState: "nonfinal" }, async (tip) => {
const opId = TaskIdentifiers.forTipPickup(tip);
const retryRecord = await tx.operationRetries.get(opId);
- const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
+ const timestampDue =
+ timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ??
+ AbsoluteTime.now();
if (tip.acceptedTimestamp) {
resp.pendingOperations.push({
type: PendingTaskType.RewardPickup,
...getPendingCommon(ws, opId, timestampDue),
givesLifeness: true,
- timestampDue: retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now(),
+ timestampDue,
merchantBaseUrl: tip.merchantBaseUrl,
tipId: tip.walletRewardId,
merchantTipId: tip.merchantRewardId,
@@ -390,7 +411,9 @@ async function gatherPurchasePending(
await iterRecordsForPurchase(tx, { onlyState: "nonfinal" }, async (pr) => {
const opId = TaskIdentifiers.forPay(pr);
const retryRecord = await tx.operationRetries.get(opId);
- const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
+ const timestampDue =
+ timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ??
+ AbsoluteTime.now();
resp.pendingOperations.push({
type: PendingTaskType.Purchase,
...getPendingCommon(ws, opId, timestampDue),
@@ -419,7 +442,9 @@ async function gatherRecoupPending(
}
const opId = TaskIdentifiers.forRecoup(rg);
const retryRecord = await tx.operationRetries.get(opId);
- const timestampDue = retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
+ const timestampDue =
+ timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ??
+ AbsoluteTime.now();
resp.pendingOperations.push({
type: PendingTaskType.Recoup,
...getPendingCommon(ws, opId, timestampDue),
@@ -444,7 +469,7 @@ async function gatherBackupPending(
const opId = TaskIdentifiers.forBackup(bp);
const retryRecord = await tx.operationRetries.get(opId);
if (bp.state.tag === BackupProviderStateTag.Ready) {
- const timestampDue = AbsoluteTime.fromPreciseTimestamp(
+ const timestampDue = timestampAbsoluteFromDb(
bp.state.nextBackupTimestamp,
);
resp.pendingOperations.push({
@@ -456,7 +481,8 @@ async function gatherBackupPending(
});
} else if (bp.state.tag === BackupProviderStateTag.Retrying) {
const timestampDue =
- retryRecord?.retryInfo?.nextRetry ?? AbsoluteTime.now();
+ timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo?.nextRetry) ??
+ AbsoluteTime.now();
resp.pendingOperations.push({
type: PendingTaskType.Backup,
...getPendingCommon(ws, opId, timestampDue),
@@ -503,7 +529,8 @@ async function gatherPeerPullInitiationPending(
const opId = TaskIdentifiers.forPeerPullPaymentInitiation(pi);
const retryRecord = await tx.operationRetries.get(opId);
const timestampDue =
- retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
+ timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ??
+ AbsoluteTime.now();
resp.pendingOperations.push({
type: PendingTaskType.PeerPullCredit,
...getPendingCommon(ws, opId, timestampDue),
@@ -549,7 +576,8 @@ async function gatherPeerPullDebitPending(
const opId = TaskIdentifiers.forPeerPullPaymentDebit(pi);
const retryRecord = await tx.operationRetries.get(opId);
const timestampDue =
- retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
+ timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ??
+ AbsoluteTime.now();
resp.pendingOperations.push({
type: PendingTaskType.PeerPullDebit,
...getPendingCommon(ws, opId, timestampDue),
@@ -595,7 +623,8 @@ async function gatherPeerPushInitiationPending(
const opId = TaskIdentifiers.forPeerPushPaymentInitiation(pi);
const retryRecord = await tx.operationRetries.get(opId);
const timestampDue =
- retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
+ timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ??
+ AbsoluteTime.now();
resp.pendingOperations.push({
type: PendingTaskType.PeerPushDebit,
...getPendingCommon(ws, opId, timestampDue),
@@ -645,7 +674,8 @@ async function gatherPeerPushCreditPending(
const opId = TaskIdentifiers.forPeerPushCredit(pi);
const retryRecord = await tx.operationRetries.get(opId);
const timestampDue =
- retryRecord?.retryInfo.nextRetry ?? AbsoluteTime.now();
+ timestampOptionalAbsoluteFromDb(retryRecord?.retryInfo.nextRetry) ??
+ AbsoluteTime.now();
resp.pendingOperations.push({
type: PendingTaskType.PeerPushCredit,
...getPendingCommon(ws, opId, timestampDue),
diff --git a/packages/taler-wallet-core/src/operations/recoup.ts b/packages/taler-wallet-core/src/operations/recoup.ts
index 6a18e5de6..782e98d1c 100644
--- a/packages/taler-wallet-core/src/operations/recoup.ts
+++ b/packages/taler-wallet-core/src/operations/recoup.ts
@@ -47,6 +47,7 @@ import {
WithdrawCoinSource,
WithdrawalGroupStatus,
WithdrawalRecordType,
+ timestampPreciseToDb,
} from "../db.js";
import { InternalWalletState } from "../internal-wallet-state.js";
import { checkDbInvariant } from "../util/invariants.js";
@@ -391,7 +392,7 @@ export async function processRecoupGroup(
if (!rg2) {
return;
}
- rg2.timestampFinished = TalerPreciseTimestamp.now();
+ rg2.timestampFinished = timestampPreciseToDb(TalerPreciseTimestamp.now());
if (rg2.scheduleRefreshCoins.length > 0) {
const refreshGroupId = await createRefreshGroup(
ws,
@@ -424,7 +425,7 @@ export async function createRecoupGroup(
exchangeBaseUrl: exchangeBaseUrl,
coinPubs: coinPubs,
timestampFinished: undefined,
- timestampStarted: TalerPreciseTimestamp.now(),
+ timestampStarted: timestampPreciseToDb(TalerPreciseTimestamp.now()),
recoupFinishedPerCoin: coinPubs.map(() => false),
scheduleRefreshCoins: [],
};
diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts
index 75adbc860..95aedbbd6 100644
--- a/packages/taler-wallet-core/src/operations/refresh.ts
+++ b/packages/taler-wallet-core/src/operations/refresh.ts
@@ -80,6 +80,8 @@ import {
isWithdrawableDenom,
PendingTaskType,
RefreshSessionRecord,
+ timestampPreciseToDb,
+ timestampProtocolFromDb,
} from "../index.js";
import {
EXCHANGE_COINS_LOCK,
@@ -157,10 +159,10 @@ function updateGroupStatus(rg: RefreshGroupRecord): { final: boolean } {
);
if (allFinal) {
if (anyFailed) {
- rg.timestampFinished = TalerPreciseTimestamp.now();
+ rg.timestampFinished = timestampPreciseToDb(TalerPreciseTimestamp.now());
rg.operationStatus = RefreshOperationStatus.Failed;
} else {
- rg.timestampFinished = TalerPreciseTimestamp.now();
+ rg.timestampFinished = timestampPreciseToDb(TalerPreciseTimestamp.now());
rg.operationStatus = RefreshOperationStatus.Finished;
}
return { final: true };
@@ -1099,12 +1101,14 @@ export async function createRefreshGroup(
expectedOutputPerCoin: estimatedOutputPerCoin.map((x) =>
Amounts.stringify(x),
),
- timestampCreated: TalerPreciseTimestamp.now(),
+ timestampCreated: timestampPreciseToDb(TalerPreciseTimestamp.now()),
};
if (oldCoinPubs.length == 0) {
logger.warn("created refresh group with zero coins");
- refreshGroup.timestampFinished = TalerPreciseTimestamp.now();
+ refreshGroup.timestampFinished = timestampPreciseToDb(
+ TalerPreciseTimestamp.now(),
+ );
refreshGroup.operationStatus = RefreshOperationStatus.Finished;
}
@@ -1122,10 +1126,10 @@ export async function createRefreshGroup(
*/
function getAutoRefreshCheckThreshold(d: DenominationRecord): AbsoluteTime {
const expireWithdraw = AbsoluteTime.fromProtocolTimestamp(
- d.stampExpireWithdraw,
+ timestampProtocolFromDb(d.stampExpireWithdraw),
);
const expireDeposit = AbsoluteTime.fromProtocolTimestamp(
- d.stampExpireDeposit,
+ timestampProtocolFromDb(d.stampExpireDeposit),
);
const delta = AbsoluteTime.difference(expireWithdraw, expireDeposit);
const deltaDiv = durationMul(delta, 0.75);
@@ -1137,10 +1141,10 @@ function getAutoRefreshCheckThreshold(d: DenominationRecord): AbsoluteTime {
*/
function getAutoRefreshExecuteThreshold(d: DenominationRecord): AbsoluteTime {
const expireWithdraw = AbsoluteTime.fromProtocolTimestamp(
- d.stampExpireWithdraw,
+ timestampProtocolFromDb(d.stampExpireWithdraw),
);
const expireDeposit = AbsoluteTime.fromProtocolTimestamp(
- d.stampExpireDeposit,
+ timestampProtocolFromDb(d.stampExpireDeposit),
);
const delta = AbsoluteTime.difference(expireWithdraw, expireDeposit);
const deltaDiv = durationMul(delta, 0.5);
@@ -1224,8 +1228,9 @@ export async function autoRefresh(
logger.trace(
`next refresh check at ${AbsoluteTime.toIsoString(minCheckThreshold)}`,
);
- exchange.nextRefreshCheckStampMs =
- AbsoluteTime.toStampMs(minCheckThreshold);
+ exchange.nextRefreshCheckStamp = timestampPreciseToDb(
+ AbsoluteTime.toPreciseTimestamp(minCheckThreshold),
+ );
await tx.exchanges.put(exchange);
});
return TaskRunResult.finished();
diff --git a/packages/taler-wallet-core/src/operations/reward.ts b/packages/taler-wallet-core/src/operations/reward.ts
index 6ae021174..4e16d977d 100644
--- a/packages/taler-wallet-core/src/operations/reward.ts
+++ b/packages/taler-wallet-core/src/operations/reward.ts
@@ -50,6 +50,10 @@ import {
DenominationRecord,
RewardRecord,
RewardRecordStatus,
+ timestampPreciseFromDb,
+ timestampPreciseToDb,
+ timestampProtocolFromDb,
+ timestampProtocolToDb,
} from "../db.js";
import { makeErrorDetail } from "@gnu-taler/taler-util";
import { InternalWalletState } from "../internal-wallet-state.js";
@@ -199,11 +203,11 @@ export async function prepareTip(
acceptedTimestamp: undefined,
status: RewardRecordStatus.DialogAccept,
rewardAmountRaw: Amounts.stringify(amount),
- rewardExpiration: tipPickupStatus.expiration,
+ rewardExpiration: timestampProtocolToDb(tipPickupStatus.expiration),
exchangeBaseUrl: tipPickupStatus.exchange_url,
next_url: tipPickupStatus.next_url,
merchantBaseUrl: res.merchantBaseUrl,
- createdTimestamp: TalerPreciseTimestamp.now(),
+ createdTimestamp: timestampPreciseToDb(TalerPreciseTimestamp.now()),
merchantRewardId: res.merchantRewardId,
rewardAmountEffective: Amounts.stringify(selectedDenoms.totalCoinValue),
denomsSel: selectedDenoms,
@@ -229,7 +233,7 @@ export async function prepareTip(
rewardAmountRaw: Amounts.stringify(tipRecord.rewardAmountRaw),
exchangeBaseUrl: tipRecord.exchangeBaseUrl,
merchantBaseUrl: tipRecord.merchantBaseUrl,
- expirationTimestamp: tipRecord.rewardExpiration,
+ expirationTimestamp: timestampProtocolFromDb(tipRecord.rewardExpiration),
rewardAmountEffective: Amounts.stringify(tipRecord.rewardAmountEffective),
walletRewardId: tipRecord.walletRewardId,
transactionId,
@@ -300,13 +304,16 @@ export async function processTip(
}
const tipStatusUrl = new URL(
- `tips/${tipRecord.merchantRewardId}/pickup`,
+ `rewards/${tipRecord.merchantRewardId}/pickup`,
tipRecord.merchantBaseUrl,
);
const req = { planchets: planchetsDetail };
logger.trace(`sending tip request: ${j2s(req)}`);
- const merchantResp = await ws.http.postJson(tipStatusUrl.href, req);
+ const merchantResp = await ws.http.fetch(tipStatusUrl.href, {
+ method: "POST",
+ body: req,
+ });
logger.trace(`got tip response, status ${merchantResp.status}`);
@@ -411,7 +418,7 @@ export async function processTip(
return;
}
const oldTxState = computeRewardTransactionStatus(tr);
- tr.pickedUpTimestamp = TalerPreciseTimestamp.now();
+ tr.pickedUpTimestamp = timestampPreciseToDb(TalerPreciseTimestamp.now());
tr.status = RewardRecordStatus.Done;
await tx.rewards.put(tr);
const newTxState = computeRewardTransactionStatus(tr);
@@ -448,7 +455,9 @@ export async function acceptTip(
return { tipRecord };
}
const oldTxState = computeRewardTransactionStatus(tipRecord);
- tipRecord.acceptedTimestamp = TalerPreciseTimestamp.now();
+ tipRecord.acceptedTimestamp = timestampPreciseToDb(
+ TalerPreciseTimestamp.now(),
+ );
tipRecord.status = RewardRecordStatus.PendingPickup;
await tx.rewards.put(tipRecord);
const newTxState = computeRewardTransactionStatus(tipRecord);
diff --git a/packages/taler-wallet-core/src/operations/testing.ts b/packages/taler-wallet-core/src/operations/testing.ts
index f71d842c7..607d03470 100644
--- a/packages/taler-wallet-core/src/operations/testing.ts
+++ b/packages/taler-wallet-core/src/operations/testing.ts
@@ -23,12 +23,16 @@ import {
ConfirmPayResultType,
Duration,
IntegrationTestV2Args,
+ j2s,
Logger,
NotificationType,
+ RegisterAccountRequest,
stringToBytes,
+ TalerCorebankApiClient,
TestPayResult,
TransactionMajorState,
TransactionMinorState,
+ TransactionState,
TransactionType,
WithdrawTestBalanceRequest,
} from "@gnu-taler/taler-util";
@@ -73,16 +77,6 @@ import { getTransactionById, getTransactions } from "./transactions.js";
const logger = new Logger("operations/testing.ts");
-interface BankUser {
- username: string;
- password: string;
-}
-
-interface BankWithdrawalResponse {
- taler_withdraw_uri: string;
- withdrawal_id: string;
-}
-
interface MerchantBackendInfo {
baseUrl: string;
authToken?: string;
@@ -102,34 +96,27 @@ function makeId(length: number): string {
return result;
}
-/**
- * Helper function to generate the "Authorization" HTTP header.
- * FIXME: redundant, put in taler-util
- */
-function makeBasicAuthHeader(username: string, password: string): string {
- const auth = `${username}:${password}`;
- const authEncoded: string = base64FromArrayBuffer(stringToBytes(auth));
- return `Basic ${authEncoded}`;
-}
-
export async function withdrawTestBalance(
ws: InternalWalletState,
req: WithdrawTestBalanceRequest,
): Promise<void> {
const amount = req.amount;
const exchangeBaseUrl = req.exchangeBaseUrl;
- const bankAccessApiBaseUrl = req.bankAccessApiBaseUrl;
+ const corebankApiBaseUrl = req.corebankApiBaseUrl;
logger.trace(
- `Registered bank user, bank access base url ${bankAccessApiBaseUrl}`,
+ `Registering bank user, bank access base url ${corebankApiBaseUrl}`,
);
- const bankUser = await registerRandomBankUser(ws.http, bankAccessApiBaseUrl);
+
+ const corebankClient = new TalerCorebankApiClient(corebankApiBaseUrl);
+
+ const bankUser = await corebankClient.createRandomBankUser();
logger.trace(`Registered bank user ${JSON.stringify(bankUser)}`);
- const wresp = await createDemoBankWithdrawalUri(
- ws.http,
- bankAccessApiBaseUrl,
- bankUser,
+ corebankClient.setAuth(bankUser);
+
+ const wresp = await corebankClient.createWithdrawalOperation(
+ bankUser.username,
amount,
);
@@ -139,14 +126,14 @@ export async function withdrawTestBalance(
forcedDenomSel: req.forcedDenomSel,
});
- await confirmBankWithdrawalUri(
- ws.http,
- bankAccessApiBaseUrl,
- bankUser,
- wresp.withdrawal_id,
- );
+ await corebankClient.confirmWithdrawalOperation(bankUser.username, {
+ withdrawalOperationId: wresp.withdrawal_id,
+ });
}
+/**
+ * FIXME: User MerchantApiClient instead.
+ */
function getMerchantAuthHeader(m: MerchantBackendInfo): Record<string, string> {
if (m.authToken) {
return {
@@ -157,80 +144,8 @@ function getMerchantAuthHeader(m: MerchantBackendInfo): Record<string, string> {
}
/**
- * Use the testing API of a demobank to create a taler://withdraw URI
- * that the wallet can then use to make a withdrawal.
+ * FIXME: User MerchantApiClient instead.
*/
-export async function createDemoBankWithdrawalUri(
- http: HttpRequestLibrary,
- bankAccessApiBaseUrl: string,
- bankUser: BankUser,
- amount: AmountString,
-): Promise<BankWithdrawalResponse> {
- const reqUrl = new URL(
- `accounts/${bankUser.username}/withdrawals`,
- bankAccessApiBaseUrl,
- ).href;
- const resp = await http.postJson(
- reqUrl,
- {
- amount,
- },
- {
- headers: {
- Authorization: makeBasicAuthHeader(
- bankUser.username,
- bankUser.password,
- ),
- },
- },
- );
- const respJson = await readSuccessResponseJsonOrThrow(resp, codecForAny());
- return respJson;
-}
-
-async function confirmBankWithdrawalUri(
- http: HttpRequestLibrary,
- bankAccessApiBaseUrl: string,
- bankUser: BankUser,
- withdrawalId: string,
-): Promise<void> {
- const reqUrl = new URL(
- `accounts/${bankUser.username}/withdrawals/${withdrawalId}/confirm`,
- bankAccessApiBaseUrl,
- ).href;
- const resp = await http.postJson(
- reqUrl,
- {},
- {
- headers: {
- Authorization: makeBasicAuthHeader(
- bankUser.username,
- bankUser.password,
- ),
- },
- },
- );
- await readSuccessResponseJsonOrThrow(resp, codecForAny());
- return;
-}
-
-async function registerRandomBankUser(
- http: HttpRequestLibrary,
- bankAccessApiBaseUrl: string,
-): Promise<BankUser> {
- const reqUrl = new URL("testing/register", bankAccessApiBaseUrl).href;
- const randId = makeId(8);
- const bankUser: BankUser = {
- // euFin doesn't allow resource names to have upper case letters.
- username: `testuser-${randId.toLowerCase()}`,
- password: `testpw-${randId}`,
- };
-
- const resp = await http.postJson(reqUrl, bankUser);
- await checkSuccessResponseOrThrow(resp);
- return bankUser;
-}
-
async function refund(
http: HttpRequestLibrary,
merchantBackend: MerchantBackendInfo,
@@ -258,6 +173,9 @@ async function refund(
return refundUri;
}
+/**
+ * FIXME: User MerchantApiClient instead.
+ */
async function createOrder(
http: HttpRequestLibrary,
merchantBackend: MerchantBackendInfo,
@@ -287,6 +205,9 @@ async function createOrder(
return { orderId };
}
+/**
+ * FIXME: User MerchantApiClient instead.
+ */
async function checkPayment(
http: HttpRequestLibrary,
merchantBackend: MerchantBackendInfo,
@@ -300,16 +221,6 @@ async function checkPayment(
return readSuccessResponseJsonOrThrow(resp, codecForCheckPaymentResponse());
}
-interface BankUser {
- username: string;
- password: string;
-}
-
-interface BankWithdrawalResponse {
- taler_withdraw_uri: string;
- withdrawal_id: string;
-}
-
async function makePayment(
ws: InternalWalletState,
merchant: MerchantBackendInfo,
@@ -376,7 +287,7 @@ export async function runIntegrationTest(
logger.info("withdrawing test balance");
await withdrawTestBalance(ws, {
amount: args.amountToWithdraw,
- bankAccessApiBaseUrl: args.bankAccessApiBaseUrl,
+ corebankApiBaseUrl: args.corebankApiBaseUrl,
exchangeBaseUrl: args.exchangeBaseUrl,
});
await waitUntilDone(ws);
@@ -404,7 +315,7 @@ export async function runIntegrationTest(
await withdrawTestBalance(ws, {
amount: Amounts.stringify(withdrawAmountTwo),
- bankAccessApiBaseUrl: args.bankAccessApiBaseUrl,
+ corebankApiBaseUrl: args.corebankApiBaseUrl,
exchangeBaseUrl: args.exchangeBaseUrl,
});
@@ -585,6 +496,49 @@ async function waitUntilPendingReady(
cancelNotifs();
}
+/**
+ * Wait until a transaction is in a particular state.
+ */
+export async function waitTransactionState(
+ ws: InternalWalletState,
+ transactionId: string,
+ txState: TransactionState,
+): Promise<void> {
+ logger.info(
+ `starting waiting for ${transactionId} to be in ${JSON.stringify(
+ txState,
+ )})`,
+ );
+ ws.ensureTaskLoopRunning();
+ let p: OpenedPromise<void> | undefined = undefined;
+ const cancelNotifs = ws.addNotificationListener((notif) => {
+ if (!p) {
+ return;
+ }
+ if (notif.type === NotificationType.TransactionStateTransition) {
+ p.resolve();
+ }
+ });
+ while (1) {
+ p = openPromise();
+ const tx = await getTransactionById(ws, {
+ transactionId,
+ });
+ if (
+ tx.txState.major === txState.major &&
+ tx.txState.minor === txState.minor
+ ) {
+ break;
+ }
+ // Wait until transaction state changed
+ await p.promise;
+ }
+ logger.info(
+ `done waiting for ${transactionId} to be in ${JSON.stringify(txState)}`,
+ );
+ cancelNotifs();
+}
+
export async function runIntegrationTest2(
ws: InternalWalletState,
args: IntegrationTestV2Args,
@@ -603,7 +557,7 @@ export async function runIntegrationTest2(
logger.info("withdrawing test balance");
await withdrawTestBalance(ws, {
amount: Amounts.stringify(amountToWithdraw),
- bankAccessApiBaseUrl: args.bankAccessApiBaseUrl,
+ corebankApiBaseUrl: args.corebankApiBaseUrl,
exchangeBaseUrl: args.exchangeBaseUrl,
});
await waitUntilDone(ws);
@@ -636,7 +590,7 @@ export async function runIntegrationTest2(
await withdrawTestBalance(ws, {
amount: Amounts.stringify(withdrawAmountTwo),
- bankAccessApiBaseUrl: args.bankAccessApiBaseUrl,
+ corebankApiBaseUrl: args.corebankApiBaseUrl,
exchangeBaseUrl: args.exchangeBaseUrl,
});
diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts
index d7b277faf..cf2006406 100644
--- a/packages/taler-wallet-core/src/operations/transactions.ts
+++ b/packages/taler-wallet-core/src/operations/transactions.ts
@@ -20,6 +20,7 @@
import {
AbsoluteTime,
Amounts,
+ DepositTransactionTrackingState,
j2s,
Logger,
NotificationType,
@@ -65,7 +66,13 @@ import {
WithdrawalGroupStatus,
WithdrawalRecordType,
} from "../db.js";
-import { GetReadOnlyAccess, WalletStoresV1 } from "../index.js";
+import {
+ GetReadOnlyAccess,
+ timestampOptionalPreciseFromDb,
+ timestampPreciseFromDb,
+ timestampProtocolFromDb,
+ WalletStoresV1,
+} from "../index.js";
import { InternalWalletState } from "../internal-wallet-state.js";
import { PendingTaskType } from "../pending-types.js";
import { assertUnreachable } from "../util/assertUnreachable.js";
@@ -470,7 +477,7 @@ function buildTransactionForPushPaymentDebit(
expiration: contractTerms.purse_expiration,
summary: contractTerms.summary,
},
- timestamp: pi.timestampCreated,
+ timestamp: timestampPreciseFromDb(pi.timestampCreated),
talerUri: stringifyPayPushUri({
exchangeBaseUrl: pi.exchangeBaseUrl,
contractPriv: pi.contractPriv,
@@ -501,7 +508,7 @@ function buildTransactionForPullPaymentDebit(
expiration: contractTerms.purse_expiration,
summary: contractTerms.summary,
},
- timestamp: pi.timestampCreated,
+ timestamp: timestampPreciseFromDb(pi.timestampCreated),
transactionId: constructTransactionIdentifier({
tag: TransactionType.PeerPullDebit,
peerPullDebitId: pi.peerPullDebitId,
@@ -543,8 +550,7 @@ function buildTransactionForPeerPullCredit(
amountEffective: Amounts.stringify(wsr.denomsSel.totalCoinValue),
amountRaw: Amounts.stringify(wsr.instructedAmount),
exchangeBaseUrl: wsr.exchangeBaseUrl,
- // Old transactions don't have it!
- timestamp: pullCredit.mergeTimestamp ?? TalerPreciseTimestamp.now(),
+ timestamp: timestampPreciseFromDb(pullCredit.mergeTimestamp),
info: {
expiration: peerContractTerms.purse_expiration,
summary: peerContractTerms.summary,
@@ -575,8 +581,7 @@ function buildTransactionForPeerPullCredit(
amountEffective: Amounts.stringify(pullCredit.estimatedAmountEffective),
amountRaw: Amounts.stringify(peerContractTerms.amount),
exchangeBaseUrl: pullCredit.exchangeBaseUrl,
- // Old transactions don't have it!
- timestamp: pullCredit.mergeTimestamp ?? TalerProtocolTimestamp.now(),
+ timestamp: timestampPreciseFromDb(pullCredit.mergeTimestamp),
info: {
expiration: peerContractTerms.purse_expiration,
summary: peerContractTerms.summary,
@@ -617,7 +622,7 @@ function buildTransactionForPeerPushCredit(
expiration: peerContractTerms.purse_expiration,
summary: peerContractTerms.summary,
},
- timestamp: wsr.timestampStart,
+ timestamp: timestampPreciseFromDb(wsr.timestampStart),
transactionId: constructTransactionIdentifier({
tag: TransactionType.PeerPushCredit,
peerPushCreditId: pushInc.peerPushCreditId,
@@ -640,7 +645,7 @@ function buildTransactionForPeerPushCredit(
summary: peerContractTerms.summary,
},
kycUrl: pushInc.kycUrl,
- timestamp: pushInc.timestamp,
+ timestamp: timestampPreciseFromDb(pushInc.timestamp),
transactionId: constructTransactionIdentifier({
tag: TransactionType.PeerPushCredit,
peerPushCreditId: pushInc.peerPushCreditId,
@@ -673,7 +678,7 @@ function buildTransactionForBankIntegratedWithdraw(
},
kycUrl: wgRecord.kycUrl,
exchangeBaseUrl: wgRecord.exchangeBaseUrl,
- timestamp: wgRecord.timestampStart,
+ timestamp: timestampPreciseFromDb(wgRecord.timestampStart),
transactionId: constructTransactionIdentifier({
tag: TransactionType.Withdrawal,
withdrawalGroupId: wgRecord.withdrawalGroupId,
@@ -717,7 +722,7 @@ function buildTransactionForManualWithdraw(
},
kycUrl: withdrawalGroup.kycUrl,
exchangeBaseUrl: withdrawalGroup.exchangeBaseUrl,
- timestamp: withdrawalGroup.timestampStart,
+ timestamp: timestampPreciseFromDb(withdrawalGroup.timestampStart),
transactionId: constructTransactionIdentifier({
tag: TransactionType.Withdrawal,
withdrawalGroupId: withdrawalGroup.withdrawalGroupId,
@@ -748,7 +753,7 @@ function buildTransactionForRefund(
tag: TransactionType.Payment,
proposalId: refundRecord.proposalId,
}),
- timestamp: refundRecord.timestampCreated,
+ timestamp: timestampPreciseFromDb(refundRecord.timestampCreated),
transactionId: constructTransactionIdentifier({
tag: TransactionType.Refund,
refundGroupId: refundRecord.refundGroupId,
@@ -786,7 +791,7 @@ function buildTransactionForRefresh(
refreshOutputAmount: Amounts.stringify(outputAmount),
originatingTransactionId:
refreshGroupRecord.reasonDetails?.originatingTransactionId,
- timestamp: refreshGroupRecord.timestampCreated,
+ timestamp: timestampPreciseFromDb(refreshGroupRecord.timestampCreated),
transactionId: constructTransactionIdentifier({
tag: TransactionType.Refresh,
refreshGroupId: refreshGroupRecord.refreshGroupId,
@@ -806,15 +811,26 @@ function buildTransactionForDeposit(
}
}
+ const trackingState: DepositTransactionTrackingState[] = [];
+
+ for (const ts of Object.values(dg.trackingState ?? {})) {
+ trackingState.push({
+ amountRaw: ts.amountRaw,
+ timestampExecuted: timestampProtocolFromDb(ts.timestampExecuted),
+ wireFee: ts.wireFee,
+ wireTransferId: ts.wireTransferId,
+ });
+ }
+
return {
type: TransactionType.Deposit,
txState: computeDepositTransactionStatus(dg),
txActions: computeDepositTransactionActions(dg),
amountRaw: Amounts.stringify(dg.counterpartyEffectiveDepositAmount),
amountEffective: Amounts.stringify(dg.totalPayCost),
- timestamp: dg.timestampCreated,
+ timestamp: timestampPreciseFromDb(dg.timestampCreated),
targetPaytoUri: dg.wire.payto_uri,
- wireTransferDeadline: dg.wireTransferDeadline,
+ wireTransferDeadline: timestampProtocolFromDb(dg.wireTransferDeadline),
transactionId: constructTransactionIdentifier({
tag: TransactionType.Deposit,
depositGroupId: dg.depositGroupId,
@@ -827,7 +843,7 @@ function buildTransactionForDeposit(
)) /
dg.statusPerCoin.length,
depositGroupId: dg.depositGroupId,
- trackingState: Object.values(dg.trackingState ?? {}),
+ trackingState,
deposited,
...(ort?.lastError ? { error: ort.lastError } : {}),
};
@@ -845,7 +861,7 @@ function buildTransactionForTip(
txActions: computeTipTransactionActions(tipRecord),
amountEffective: Amounts.stringify(tipRecord.rewardAmountEffective),
amountRaw: Amounts.stringify(tipRecord.rewardAmountRaw),
- timestamp: tipRecord.acceptedTimestamp,
+ timestamp: timestampPreciseFromDb(tipRecord.acceptedTimestamp),
transactionId: constructTransactionIdentifier({
tag: TransactionType.Reward,
walletRewardId: tipRecord.walletRewardId,
@@ -922,7 +938,7 @@ async function buildTransactionForPurchase(
: Amounts.stringify(purchaseRecord.refundAmountAwaiting),
refunds,
posConfirmation: purchaseRecord.posConfirmation,
- timestamp,
+ timestamp: timestampPreciseFromDb(timestamp),
transactionId: constructTransactionIdentifier({
tag: TransactionType.Payment,
proposalId: purchaseRecord.proposalId,
diff --git a/packages/taler-wallet-core/src/operations/withdraw.test.ts b/packages/taler-wallet-core/src/operations/withdraw.test.ts
index 2d9286610..cb8aa5e81 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.test.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.test.ts
@@ -16,7 +16,11 @@
import { Amounts, DenomKeyType } from "@gnu-taler/taler-util";
import test from "ava";
-import { DenominationRecord, DenominationVerificationStatus } from "../db.js";
+import {
+ DenominationRecord,
+ DenominationVerificationStatus,
+ timestampProtocolToDb,
+} from "../db.js";
import { selectWithdrawalDenominations } from "../util/coinSelection.js";
test("withdrawal selection bug repro", (t) => {
@@ -64,22 +68,22 @@ test("withdrawal selection bug repro", (t) => {
isRevoked: false,
masterSig:
"4F0P456CNNTTWK8BFJHGM3JTD6FVVNZY8EP077GYAHDJ5Y81S5RQ3SMS925NXMDVG9A88JAAP0E2GDZBC21PP5NHFFVWHAW3AVT8J3R",
- stampExpireDeposit: {
+ stampExpireDeposit: timestampProtocolToDb({
t_s: 1742909388,
- },
- stampExpireLegal: {
+ }),
+ stampExpireLegal: timestampProtocolToDb({
t_s: 1900589388,
- },
- stampExpireWithdraw: {
+ }),
+ stampExpireWithdraw: timestampProtocolToDb({
t_s: 1679837388,
- },
- stampStart: {
+ }),
+ stampStart: timestampProtocolToDb({
t_s: 1585229388,
- },
+ }),
verificationStatus: DenominationVerificationStatus.Unverified,
currency: "KUDOS",
value: "KUDOS:1000",
- listIssueDate: { t_s: 0 },
+ listIssueDate: timestampProtocolToDb({ t_s: 0 }),
},
{
denomPub: {
@@ -119,22 +123,22 @@ test("withdrawal selection bug repro", (t) => {
isRevoked: false,
masterSig:
"P99AW82W46MZ0AKW7Z58VQPXFNTJQM9DVTYPBDF6KVYF38PPVDAZTV7JQ8TY7HGEC7JJJAY4E7AY7J3W1WV10DAZZQHHKTAVTSRAC20",
- stampExpireDeposit: {
+ stampExpireDeposit: timestampProtocolToDb({
t_s: 1742909388,
- },
- stampExpireLegal: {
+ }),
+ stampExpireLegal: timestampProtocolToDb({
t_s: 1900589388,
- },
- stampExpireWithdraw: {
+ }),
+ stampExpireWithdraw: timestampProtocolToDb({
t_s: 1679837388,
- },
- stampStart: {
+ }),
+ stampStart: timestampProtocolToDb({
t_s: 1585229388,
- },
+ }),
verificationStatus: DenominationVerificationStatus.Unverified,
value: "KUDOS:10",
currency: "KUDOS",
- listIssueDate: { t_s: 0 },
+ listIssueDate: timestampProtocolToDb({ t_s: 0 }),
},
{
denomPub: {
@@ -173,22 +177,22 @@ test("withdrawal selection bug repro", (t) => {
isRevoked: false,
masterSig:
"8S4VZGHE5WE0N5ZVCHYW9KZZR4YAKK15S46MV1HR1QB9AAMH3NWPW4DCR4NYGJK33Q8YNFY80SWNS6XKAP5DEVK933TM894FJ2VGE3G",
- stampExpireDeposit: {
+ stampExpireDeposit: timestampProtocolToDb({
t_s: 1742909388,
- },
- stampExpireLegal: {
+ }),
+ stampExpireLegal: timestampProtocolToDb({
t_s: 1900589388,
- },
- stampExpireWithdraw: {
+ }),
+ stampExpireWithdraw: timestampProtocolToDb({
t_s: 1679837388,
- },
- stampStart: {
+ }),
+ stampStart: timestampProtocolToDb({
t_s: 1585229388,
- },
+ }),
verificationStatus: DenominationVerificationStatus.Unverified,
value: "KUDOS:5",
currency: "KUDOS",
- listIssueDate: { t_s: 0 },
+ listIssueDate: timestampProtocolToDb({ t_s: 0 }),
},
{
denomPub: {
@@ -228,22 +232,22 @@ test("withdrawal selection bug repro", (t) => {
isRevoked: false,
masterSig:
"E3AWGAG8VB42P3KXM8B04Z6M483SX59R3Y4T53C3NXCA2NPB6C7HVCMVX05DC6S58E9X40NGEBQNYXKYMYCF3ASY2C4WP1WCZ4ME610",
- stampExpireDeposit: {
+ stampExpireDeposit: timestampProtocolToDb({
t_s: 1742909388,
- },
- stampExpireLegal: {
+ }),
+ stampExpireLegal: timestampProtocolToDb({
t_s: 1900589388,
- },
- stampExpireWithdraw: {
+ }),
+ stampExpireWithdraw: timestampProtocolToDb({
t_s: 1679837388,
- },
- stampStart: {
+ }),
+ stampStart: timestampProtocolToDb({
t_s: 1585229388,
- },
+ }),
verificationStatus: DenominationVerificationStatus.Unverified,
value: "KUDOS:1",
currency: "KUDOS",
- listIssueDate: { t_s: 0 },
+ listIssueDate: timestampProtocolToDb({ t_s: 0 }),
},
{
denomPub: {
@@ -282,18 +286,18 @@ test("withdrawal selection bug repro", (t) => {
isRevoked: false,
masterSig:
"0ES1RKV002XB4YP21SN0QB7RSDHGYT0XAE65JYN8AVJAA6H7JZFN7JADXT521DJS89XMGPZGR8GCXF1516Y0Q9QDV00E6NMFA6CF838",
- stampExpireDeposit: {
+ stampExpireDeposit: timestampProtocolToDb({
t_s: 1742909388,
- },
- stampExpireLegal: {
+ }),
+ stampExpireLegal: timestampProtocolToDb({
t_s: 1900589388,
- },
- stampExpireWithdraw: {
+ }),
+ stampExpireWithdraw: timestampProtocolToDb({
t_s: 1679837388,
- },
- stampStart: {
+ }),
+ stampStart: timestampProtocolToDb({
t_s: 1585229388,
- },
+ }),
verificationStatus: DenominationVerificationStatus.Unverified,
value: Amounts.stringify({
currency: "KUDOS",
@@ -301,7 +305,7 @@ test("withdrawal selection bug repro", (t) => {
value: 0,
}),
currency: "KUDOS",
- listIssueDate: { t_s: 0 },
+ listIssueDate: timestampProtocolToDb({ t_s: 0 }),
},
{
denomPub: {
@@ -340,22 +344,22 @@ test("withdrawal selection bug repro", (t) => {
isRevoked: false,
masterSig:
"58QEB6C6N7602E572E3JYANVVJ9BRW0V9E2ZFDW940N47YVQDK9SAFPWBN5YGT3G1742AFKQ0CYR4DM2VWV0Z0T1XMEKWN6X2EZ9M0R",
- stampExpireDeposit: {
+ stampExpireDeposit: timestampProtocolToDb({
t_s: 1742909388,
- },
- stampExpireLegal: {
+ }),
+ stampExpireLegal: timestampProtocolToDb({
t_s: 1900589388,
- },
- stampExpireWithdraw: {
+ }),
+ stampExpireWithdraw: timestampProtocolToDb({
t_s: 1679837388,
- },
- stampStart: {
+ }),
+ stampStart: timestampProtocolToDb({
t_s: 1585229388,
- },
+ }),
verificationStatus: DenominationVerificationStatus.Unverified,
value: "KUDOS:2",
currency: "KUDOS",
- listIssueDate: { t_s: 0 },
+ listIssueDate: timestampProtocolToDb({ t_s: 0 }),
},
];
diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts
index bae348dc1..eff427bec 100644
--- a/packages/taler-wallet-core/src/operations/withdraw.ts
+++ b/packages/taler-wallet-core/src/operations/withdraw.ts
@@ -135,6 +135,7 @@ import {
ExchangeEntryDbUpdateStatus,
PendingTaskType,
isWithdrawableDenom,
+ timestampPreciseToDb,
} from "../index.js";
import {
TransitionInfo,
@@ -573,7 +574,7 @@ export async function getBankWithdrawalInfo(
throw TalerError.fromDetail(
TalerErrorCode.WALLET_BANK_INTEGRATION_PROTOCOL_VERSION_INCOMPATIBLE,
{
- exchangeProtocolVersion: config.version,
+ bankProtocolVersion: config.version,
walletProtocolVersion: WALLET_BANK_INTEGRATION_PROTOCOL_VERSION,
},
"bank integration protocol version not compatible with wallet",
@@ -816,10 +817,10 @@ async function handleKycRequired(
amlStatus === AmlStatus.normal || amlStatus === undefined
? WithdrawalGroupStatus.PendingKyc
: amlStatus === AmlStatus.pending
- ? WithdrawalGroupStatus.PendingAml
- : amlStatus === AmlStatus.fronzen
- ? WithdrawalGroupStatus.SuspendedAml
- : assertUnreachable(amlStatus);
+ ? WithdrawalGroupStatus.PendingAml
+ : amlStatus === AmlStatus.fronzen
+ ? WithdrawalGroupStatus.SuspendedAml
+ : assertUnreachable(amlStatus);
await tx.withdrawalGroups.put(wg2);
const newTxState = computeWithdrawalTransactionStatus(wg2);
@@ -1149,8 +1150,7 @@ export async function updateWithdrawalDenoms(
denom.verificationStatus === DenominationVerificationStatus.Unverified
) {
logger.trace(
- `Validating denomination (${current + 1}/${
- denominations.length
+ `Validating denomination (${current + 1}/${denominations.length
}) signature of ${denom.denomPubHash}`,
);
let valid = false;
@@ -1244,7 +1244,7 @@ async function queryReserve(
if (
resp.status === 404 &&
result.talerErrorResponse.code ===
- TalerErrorCode.EXCHANGE_RESERVES_STATUS_UNKNOWN
+ TalerErrorCode.EXCHANGE_RESERVES_STATUS_UNKNOWN
) {
return { ready: false };
} else {
@@ -1330,7 +1330,7 @@ async function processWithdrawalGroupAbortingBank(
}
const txStatusOld = computeWithdrawalTransactionStatus(wg);
wg.status = WithdrawalGroupStatus.AbortedBank;
- wg.timestampFinish = TalerPreciseTimestamp.now();
+ wg.timestampFinish = timestampPreciseToDb(TalerPreciseTimestamp.now());
const txStatusNew = computeWithdrawalTransactionStatus(wg);
await tx.withdrawalGroups.put(wg);
return {
@@ -1463,7 +1463,7 @@ async function processWithdrawalGroupPendingReady(
}
const txStatusOld = computeWithdrawalTransactionStatus(wg);
wg.status = WithdrawalGroupStatus.Done;
- wg.timestampFinish = TalerPreciseTimestamp.now();
+ wg.timestampFinish = timestampPreciseToDb(TalerPreciseTimestamp.now());
const txStatusNew = computeWithdrawalTransactionStatus(wg);
await tx.withdrawalGroups.put(wg);
return {
@@ -1559,7 +1559,7 @@ async function processWithdrawalGroupPendingReady(
const oldTxState = computeWithdrawalTransactionStatus(wg);
logger.info(`now withdrawn ${numFinished} of ${numTotalCoins} coins`);
if (wg.timestampFinish === undefined && numFinished === numTotalCoins) {
- wg.timestampFinish = TalerPreciseTimestamp.now();
+ wg.timestampFinish = timestampPreciseToDb(TalerPreciseTimestamp.now());
wg.status = WithdrawalGroupStatus.Done;
await makeCoinsVisible(ws, tx, transactionId);
}
@@ -1779,7 +1779,7 @@ export async function getExchangeWithdrawalInfo(
) {
logger.warn(
`wallet's support for exchange protocol version ${WALLET_EXCHANGE_PROTOCOL_VERSION} might be outdated ` +
- `(exchange has ${exchangeDetails.protocolVersionRange}), checking for updates`,
+ `(exchange has ${exchangeDetails.protocolVersionRange}), checking for updates`,
);
}
}
@@ -2052,8 +2052,9 @@ async function registerReserveWithBank(
if (r.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) {
throw Error("invariant failed");
}
- r.wgInfo.bankInfo.timestampReserveInfoPosted =
- AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now());
+ r.wgInfo.bankInfo.timestampReserveInfoPosted = timestampPreciseToDb(
+ AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now()),
+ );
const oldTxState = computeWithdrawalTransactionStatus(r);
r.status = WithdrawalGroupStatus.PendingWaitConfirmBank;
const newTxState = computeWithdrawalTransactionStatus(r);
@@ -2135,7 +2136,7 @@ async function processReserveBankStatus(
}
const now = AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now());
const oldTxState = computeWithdrawalTransactionStatus(r);
- r.wgInfo.bankInfo.timestampBankConfirmed = now;
+ r.wgInfo.bankInfo.timestampBankConfirmed = timestampPreciseToDb(now);
r.status = WithdrawalGroupStatus.FailedBankAborted;
const newTxState = computeWithdrawalTransactionStatus(r);
await tx.withdrawalGroups.put(r);
@@ -2184,7 +2185,7 @@ async function processReserveBankStatus(
if (status.transfer_done) {
logger.info("withdrawal: transfer confirmed by bank.");
const now = AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now());
- r.wgInfo.bankInfo.timestampBankConfirmed = now;
+ r.wgInfo.bankInfo.timestampBankConfirmed = timestampPreciseToDb(now);
r.status = WithdrawalGroupStatus.PendingQueryingStatus;
} else {
logger.info("withdrawal: transfer not yet confirmed by bank");
@@ -2290,7 +2291,7 @@ export async function internalPrepareCreateWithdrawalGroup(
denomsSel: initialDenomSel,
exchangeBaseUrl: canonExchange,
instructedAmount: Amounts.stringify(amount),
- timestampStart: now,
+ timestampStart: timestampPreciseToDb(now),
rawWithdrawalAmount: initialDenomSel.totalWithdrawCost,
effectiveWithdrawalAmount: initialDenomSel.totalCoinValue,
secretSeed,
@@ -2344,8 +2345,7 @@ export async function internalPerformCreateWithdrawalGroup(
if (!prep.creationInfo) {
return { withdrawalGroup, transitionInfo: undefined };
}
- const { amount, canonExchange, exchangeDetails } =
- prep.creationInfo;
+ const { amount, canonExchange, exchangeDetails } = prep.creationInfo;
await tx.withdrawalGroups.add(withdrawalGroup);
await tx.reserves.put({
@@ -2355,7 +2355,7 @@ export async function internalPerformCreateWithdrawalGroup(
const exchange = await tx.exchanges.get(withdrawalGroup.exchangeBaseUrl);
if (exchange) {
- exchange.lastWithdrawal = TalerPreciseTimestamp.now();
+ exchange.lastWithdrawal = timestampPreciseToDb(TalerPreciseTimestamp.now());
exchange.entryStatus = ExchangeEntryDbRecordStatus.Used;
await tx.exchanges.put(exchange);
}
@@ -2546,11 +2546,7 @@ export async function createManualWithdrawal(
});
const exchangePaytoUris = await ws.db
- .mktx((x) => [
- x.withdrawalGroups,
- x.exchanges,
- x.exchangeDetails,
- ])
+ .mktx((x) => [x.withdrawalGroups, x.exchanges, x.exchangeDetails])
.runReadOnly(async (tx) => {
return await getFundingPaytoUris(tx, withdrawalGroup.withdrawalGroupId);
});
diff --git a/packages/taler-wallet-core/src/pending-types.ts b/packages/taler-wallet-core/src/pending-types.ts
index 627888b4d..e7a40e81b 100644
--- a/packages/taler-wallet-core/src/pending-types.ts
+++ b/packages/taler-wallet-core/src/pending-types.ts
@@ -25,7 +25,7 @@
* Imports.
*/
import { TalerErrorDetail, AbsoluteTime } from "@gnu-taler/taler-util";
-import { RetryInfo } from "./operations/common.js";
+import { DbRetryInfo } from "./operations/common.js";
export enum PendingTaskType {
ExchangeUpdate = "exchange-update",
@@ -137,7 +137,7 @@ export interface PendingRefreshTask {
lastError?: TalerErrorDetail;
refreshGroupId: string;
finishedPerCoin: boolean[];
- retryInfo?: RetryInfo;
+ retryInfo?: DbRetryInfo;
}
/**
@@ -156,7 +156,7 @@ export interface PendingTipPickupTask {
export interface PendingPurchaseTask {
type: PendingTaskType.Purchase;
proposalId: string;
- retryInfo?: RetryInfo;
+ retryInfo?: DbRetryInfo;
/**
* Status of the payment as string, used only for debugging.
*/
@@ -167,7 +167,7 @@ export interface PendingPurchaseTask {
export interface PendingRecoupTask {
type: PendingTaskType.Recoup;
recoupGroupId: string;
- retryInfo?: RetryInfo;
+ retryInfo?: DbRetryInfo;
lastError: TalerErrorDetail | undefined;
}
@@ -177,7 +177,7 @@ export interface PendingRecoupTask {
export interface PendingWithdrawTask {
type: PendingTaskType.Withdraw;
lastError: TalerErrorDetail | undefined;
- retryInfo?: RetryInfo;
+ retryInfo?: DbRetryInfo;
withdrawalGroupId: string;
}
@@ -187,7 +187,7 @@ export interface PendingWithdrawTask {
export interface PendingDepositTask {
type: PendingTaskType.Deposit;
lastError: TalerErrorDetail | undefined;
- retryInfo: RetryInfo | undefined;
+ retryInfo: DbRetryInfo | undefined;
depositGroupId: string;
}
@@ -233,7 +233,7 @@ export interface PendingTaskInfoCommon {
* Retry info. Currently used to stop the wallet after any operation
* exceeds a number of retries.
*/
- retryInfo?: RetryInfo;
+ retryInfo?: DbRetryInfo;
}
/**
diff --git a/packages/taler-wallet-core/src/util/coinSelection.test.ts b/packages/taler-wallet-core/src/util/coinSelection.test.ts
index 2a322c4a9..81a656f8a 100644
--- a/packages/taler-wallet-core/src/util/coinSelection.test.ts
+++ b/packages/taler-wallet-core/src/util/coinSelection.test.ts
@@ -15,65 +15,58 @@
*/
import {
AbsoluteTime,
- AgeRestriction,
- AmountJson,
AmountString,
Amounts,
DenomKeyType,
Duration,
- TransactionAmountMode,
+ j2s,
} from "@gnu-taler/taler-util";
import test, { ExecutionContext } from "ava";
-import { AvailableDenom, testing_greedySelectPeer } from "./coinSelection.js"
-
-type Tester<T> = {
- deep: {
- equal(another: T): ReturnType<ExecutionContext["deepEqual"]>;
- equals(another: T): ReturnType<ExecutionContext["deepEqual"]>;
- }
-}
-
-function expect<T>(t: ExecutionContext, thing: T): Tester<T> {
- return {
- deep: {
- equal: (another: T) => t.deepEqual(thing, another),
- equals: (another: T) => t.deepEqual(thing, another),
- },
- };
-}
+import {
+ AvailableDenom,
+ testing_greedySelectPeer,
+ testing_selectGreedy,
+} from "./coinSelection.js";
const inTheDistantFuture = AbsoluteTime.toProtocolTimestamp(
- AbsoluteTime.addDuration(AbsoluteTime.now(), Duration.fromSpec({ hours: 1 }))
-)
+ AbsoluteTime.addDuration(AbsoluteTime.now(), Duration.fromSpec({ hours: 1 })),
+);
const inThePast = AbsoluteTime.toProtocolTimestamp(
- AbsoluteTime.subtractDuraction(AbsoluteTime.now(), Duration.fromSpec({ hours: 1 }))
-)
-
-test("should select the coin", (t) => {
- const instructedAmount = Amounts.parseOrThrow("LOCAL:2")
+ AbsoluteTime.subtractDuraction(
+ AbsoluteTime.now(),
+ Duration.fromSpec({ hours: 1 }),
+ ),
+);
+
+test("p2p: should select the coin", (t) => {
+ const instructedAmount = Amounts.parseOrThrow("LOCAL:2");
const tally = {
amountAcc: Amounts.zeroOfCurrency(instructedAmount.currency),
depositFeesAcc: Amounts.zeroOfCurrency(instructedAmount.currency),
lastDepositFee: Amounts.zeroOfCurrency(instructedAmount.currency),
};
const coins = testing_greedySelectPeer(
- createCandidates([{
- amount: "LOCAL:10",
- numAvailable: 5,
- depositFee: "LOCAL:0.1",
- fromExchange: "http://exchange.localhost/",
- }]),
- instructedAmount,
- tally
+ createCandidates([
+ {
+ amount: "LOCAL:10",
+ numAvailable: 5,
+ depositFee: "LOCAL:0.1",
+ fromExchange: "http://exchange.localhost/",
+ },
+ ]),
+ instructedAmount,
+ tally,
);
+ t.log(j2s(coins));
+
expect(t, coins).deep.equal({
"hash0;32;http://exchange.localhost/": {
exchangeBaseUrl: "http://exchange.localhost/",
denomPubHash: "hash0",
maxAge: 32,
- contributions: [Amounts.parseOrThrow("LOCAL:2")],
- }
+ contributions: [Amounts.parseOrThrow("LOCAL:2.1")],
+ },
});
expect(t, tally).deep.equal({
@@ -81,25 +74,26 @@ test("should select the coin", (t) => {
depositFeesAcc: Amounts.parseOrThrow("LOCAL:0.1"),
lastDepositFee: Amounts.parseOrThrow("LOCAL:0.1"),
});
-
});
-test("should select 3 coins", (t) => {
- const instructedAmount = Amounts.parseOrThrow("LOCAL:20")
+test("p2p: should select 3 coins", (t) => {
+ const instructedAmount = Amounts.parseOrThrow("LOCAL:20");
const tally = {
amountAcc: Amounts.zeroOfCurrency(instructedAmount.currency),
depositFeesAcc: Amounts.zeroOfCurrency(instructedAmount.currency),
lastDepositFee: Amounts.zeroOfCurrency(instructedAmount.currency),
};
const coins = testing_greedySelectPeer(
- createCandidates([{
- amount: "LOCAL:10",
- numAvailable: 5,
- depositFee: "LOCAL:0.1",
- fromExchange: "http://exchange.localhost/",
- }]),
- instructedAmount,
- tally
+ createCandidates([
+ {
+ amount: "LOCAL:10",
+ numAvailable: 5,
+ depositFee: "LOCAL:0.1",
+ fromExchange: "http://exchange.localhost/",
+ },
+ ]),
+ instructedAmount,
+ tally,
);
expect(t, coins).deep.equal({
@@ -110,9 +104,9 @@ test("should select 3 coins", (t) => {
contributions: [
Amounts.parseOrThrow("LOCAL:9.9"),
Amounts.parseOrThrow("LOCAL:9.9"),
- Amounts.parseOrThrow("LOCAL:0.2")
+ Amounts.parseOrThrow("LOCAL:0.5"),
],
- }
+ },
});
expect(t, tally).deep.equal({
@@ -120,62 +114,142 @@ test("should select 3 coins", (t) => {
depositFeesAcc: Amounts.parseOrThrow("LOCAL:0.3"),
lastDepositFee: Amounts.parseOrThrow("LOCAL:0.1"),
});
-
});
-test("can't select since the instructed amount is too high", (t) => {
- const instructedAmount = Amounts.parseOrThrow("LOCAL:60")
+test("p2p: can't select since the instructed amount is too high", (t) => {
+ const instructedAmount = Amounts.parseOrThrow("LOCAL:60");
const tally = {
amountAcc: Amounts.zeroOfCurrency(instructedAmount.currency),
depositFeesAcc: Amounts.zeroOfCurrency(instructedAmount.currency),
lastDepositFee: Amounts.zeroOfCurrency(instructedAmount.currency),
};
const coins = testing_greedySelectPeer(
- createCandidates([{
- amount: "LOCAL:10",
- numAvailable: 5,
- depositFee: "LOCAL:0.1",
- fromExchange: "http://exchange.localhost/",
- }]),
- instructedAmount,
- tally
+ createCandidates([
+ {
+ amount: "LOCAL:10",
+ numAvailable: 5,
+ depositFee: "LOCAL:0.1",
+ fromExchange: "http://exchange.localhost/",
+ },
+ ]),
+ instructedAmount,
+ tally,
);
expect(t, coins).deep.equal(undefined);
expect(t, tally).deep.equal({
- amountAcc: Amounts.parseOrThrow("LOCAL:49.5"),
+ amountAcc: Amounts.parseOrThrow("LOCAL:49"),
depositFeesAcc: Amounts.parseOrThrow("LOCAL:0.5"),
lastDepositFee: Amounts.parseOrThrow("LOCAL:0.1"),
});
-
});
+test("pay: select one coin to pay with fee", (t) => {
+ const payment = Amounts.parseOrThrow("LOCAL:2");
+ const exchangeWireFee = Amounts.parseOrThrow("LOCAL:0.1");
+ const zero = Amounts.zeroOfCurrency(payment.currency);
+ const tally = {
+ amountPayRemaining: payment,
+ amountWireFeeLimitRemaining: zero,
+ amountDepositFeeLimitRemaining: zero,
+ customerDepositFees: zero,
+ customerWireFees: zero,
+ wireFeeCoveredForExchange: new Set<string>(),
+ lastDepositFee: zero,
+ };
+ const coins = testing_selectGreedy(
+ {
+ auditors: [],
+ exchanges: [
+ {
+ exchangeBaseUrl: "http://exchange.localhost/",
+ exchangePub: "E5M8CGRDHXF1RCVP3B8TQCTDYNQ7T4XHWR5SVEQRGVVMVME41VJ0",
+ },
+ ],
+ contractTermsAmount: payment,
+ depositFeeLimit: zero,
+ wireFeeAmortization: 1,
+ wireFeeLimit: zero,
+ prevPayCoins: [],
+ wireMethod: "x-taler-bank",
+ },
+ createCandidates([
+ {
+ amount: "LOCAL:10",
+ numAvailable: 5,
+ depositFee: "LOCAL:0.1",
+ fromExchange: "http://exchange.localhost/",
+ },
+ ]),
+ { "http://exchange.localhost/": exchangeWireFee },
+ tally,
+ );
+ expect(t, coins).deep.equal({
+ "hash0;32;http://exchange.localhost/": {
+ exchangeBaseUrl: "http://exchange.localhost/",
+ denomPubHash: "hash0",
+ maxAge: 32,
+ contributions: [Amounts.parseOrThrow("LOCAL:2.2")],
+ },
+ });
+ expect(t, tally).deep.equal({
+ amountPayRemaining: Amounts.parseOrThrow("LOCAL:2"),
+ amountWireFeeLimitRemaining: zero,
+ amountDepositFeeLimitRemaining: zero,
+ customerDepositFees: zero,
+ customerWireFees: zero,
+ wireFeeCoveredForExchange: new Set(),
+ lastDepositFee: zero,
+ });
+});
-function createCandidates(ar: {amount: AmountString, depositFee: AmountString, numAvailable: number, fromExchange: string}[]): AvailableDenom[] {
- return ar.map((r,idx) => {
+function createCandidates(
+ ar: {
+ amount: AmountString;
+ depositFee: AmountString;
+ numAvailable: number;
+ fromExchange: string;
+ }[],
+): AvailableDenom[] {
+ return ar.map((r, idx) => {
return {
- "denomPub": {
- "age_mask": 0,
- "cipher": DenomKeyType.Rsa,
- "rsa_public_key": "PPP"
+ denomPub: {
+ age_mask: 0,
+ cipher: DenomKeyType.Rsa,
+ rsa_public_key: "PPP",
},
- "denomPubHash": `hash${idx}`,
- "value": r.amount,
- "feeDeposit": r.depositFee,
- "feeRefresh": "LOCAL:0",
- "feeRefund": "LOCAL:0",
- "feeWithdraw": "LOCAL:0",
- "stampExpireDeposit": inTheDistantFuture,
- "stampExpireLegal": inTheDistantFuture,
- "stampExpireWithdraw": inTheDistantFuture,
- "stampStart": inThePast,
- "exchangeBaseUrl": r.fromExchange,
- "numAvailable": r.numAvailable,
- "maxAge": 32,
-
- }
- })
+ denomPubHash: `hash${idx}`,
+ value: r.amount,
+ feeDeposit: r.depositFee,
+ feeRefresh: "LOCAL:0",
+ feeRefund: "LOCAL:0",
+ feeWithdraw: "LOCAL:0",
+ stampExpireDeposit: inTheDistantFuture,
+ stampExpireLegal: inTheDistantFuture,
+ stampExpireWithdraw: inTheDistantFuture,
+ stampStart: inThePast,
+ exchangeBaseUrl: r.fromExchange,
+ numAvailable: r.numAvailable,
+ maxAge: 32,
+ };
+ });
+}
+
+type Tester<T> = {
+ deep: {
+ equal(another: T): ReturnType<ExecutionContext["deepEqual"]>;
+ equals(another: T): ReturnType<ExecutionContext["deepEqual"]>;
+ };
+};
+
+function expect<T>(t: ExecutionContext, thing: T): Tester<T> {
+ return {
+ deep: {
+ equal: (another: T) => t.deepEqual(thing, another),
+ equals: (another: T) => t.deepEqual(thing, another),
+ },
+ };
}
diff --git a/packages/taler-wallet-core/src/util/coinSelection.ts b/packages/taler-wallet-core/src/util/coinSelection.ts
index 0885215dd..8c90f26f1 100644
--- a/packages/taler-wallet-core/src/util/coinSelection.ts
+++ b/packages/taler-wallet-core/src/util/coinSelection.ts
@@ -419,6 +419,11 @@ interface SelResult {
};
}
+export function testing_selectGreedy(
+ ...args: Parameters<typeof selectGreedy>
+): ReturnType<typeof selectGreedy> {
+ return selectGreedy(...args);
+}
function selectGreedy(
req: SelectPayCoinRequestNg,
candidateDenoms: AvailableDenom[],
@@ -897,9 +902,12 @@ interface PeerCoinSelectionTally {
/**
* exporting for testing
*/
-export function testing_greedySelectPeer(...args: Parameters<typeof greedySelectPeer>): ReturnType<typeof greedySelectPeer> {
- return greedySelectPeer(...args)
+export function testing_greedySelectPeer(
+ ...args: Parameters<typeof greedySelectPeer>
+): ReturnType<typeof greedySelectPeer> {
+ return greedySelectPeer(...args);
}
+
function greedySelectPeer(
candidates: AvailableDenom[],
instructedAmount: AmountLike,
@@ -918,11 +926,16 @@ function greedySelectPeer(
instructedAmount,
tally.amountAcc,
).amount;
- const coinContrib = Amounts.sub(denom.value, denom.feeDeposit).amount
+ // Maximum amount the coin could effectively contribute.
+ const maxCoinContrib = Amounts.sub(denom.value, denom.feeDeposit).amount;
+
+ const coinSpend = Amounts.min(
+ Amounts.add(amountPayRemaining, denom.feeDeposit).amount,
+ maxCoinContrib,
+ );
- const coinSpend = Amounts.min(amountPayRemaining, coinContrib)
-
tally.amountAcc = Amounts.add(tally.amountAcc, coinSpend).amount;
+ tally.amountAcc = Amounts.sub(tally.amountAcc, denom.feeDeposit).amount;
tally.depositFeesAcc = Amounts.add(
tally.depositFeesAcc,
@@ -930,7 +943,7 @@ function greedySelectPeer(
).amount;
tally.lastDepositFee = Amounts.parseOrThrow(denom.feeDeposit);
-
+
contributions.push(coinSpend);
}
if (contributions.length > 0) {
diff --git a/packages/taler-wallet-core/src/util/denominations.ts b/packages/taler-wallet-core/src/util/denominations.ts
index 76716cf7a..db6e69956 100644
--- a/packages/taler-wallet-core/src/util/denominations.ts
+++ b/packages/taler-wallet-core/src/util/denominations.ts
@@ -26,10 +26,9 @@ import {
FeeDescriptionPair,
TalerProtocolTimestamp,
TimePoint,
- WireFee,
} from "@gnu-taler/taler-util";
import { DenominationRecord } from "../db.js";
-import { WalletConfig } from "../index.js";
+import { timestampProtocolFromDb } from "../index.js";
/**
* Given a list of denominations with the same value and same period of time:
@@ -457,9 +456,11 @@ export function isWithdrawableDenom(
denomselAllowLate?: boolean,
): boolean {
const now = AbsoluteTime.now();
- const start = AbsoluteTime.fromProtocolTimestamp(d.stampStart);
+ const start = AbsoluteTime.fromProtocolTimestamp(
+ timestampProtocolFromDb(d.stampStart),
+ );
const withdrawExpire = AbsoluteTime.fromProtocolTimestamp(
- d.stampExpireWithdraw,
+ timestampProtocolFromDb(d.stampExpireWithdraw),
);
const started = AbsoluteTime.cmp(now, start) >= 0;
let lastPossibleWithdraw: AbsoluteTime;
diff --git a/packages/taler-wallet-core/src/util/instructedAmountConversion.ts b/packages/taler-wallet-core/src/util/instructedAmountConversion.ts
index 54c08eee4..a0394a687 100644
--- a/packages/taler-wallet-core/src/util/instructedAmountConversion.ts
+++ b/packages/taler-wallet-core/src/util/instructedAmountConversion.ts
@@ -14,6 +14,7 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
+import { GlobalIDB } from "@gnu-taler/idb-bridge";
import {
AbsoluteTime,
AgeRestriction,
@@ -29,14 +30,14 @@ import {
parsePaytoUri,
strcmp,
} from "@gnu-taler/taler-util";
-import { checkDbInvariant } from "./invariants.js";
import {
DenominationRecord,
InternalWalletState,
getExchangeDetails,
+ timestampProtocolFromDb,
} from "../index.js";
import { CoinInfo } from "./coinSelection.js";
-import { GlobalIDB } from "@gnu-taler/idb-bridge";
+import { checkDbInvariant } from "./invariants.js";
/**
* If the operation going to be plan subtracts
@@ -224,10 +225,10 @@ async function getAvailableDenoms(
);
for (const denom of ds) {
const expiresWithdraw = AbsoluteTime.fromProtocolTimestamp(
- denom.stampExpireWithdraw,
+ timestampProtocolFromDb(denom.stampExpireWithdraw),
);
const expiresDeposit = AbsoluteTime.fromProtocolTimestamp(
- denom.stampExpireDeposit,
+ timestampProtocolFromDb(denom.stampExpireDeposit),
);
creditDeadline = AbsoluteTime.min(deadline, expiresWithdraw);
debitDeadline = AbsoluteTime.min(deadline, expiresDeposit);
@@ -270,10 +271,10 @@ async function getAvailableDenoms(
continue;
}
const expiresWithdraw = AbsoluteTime.fromProtocolTimestamp(
- denom.stampExpireWithdraw,
+ timestampProtocolFromDb(denom.stampExpireWithdraw),
);
const expiresDeposit = AbsoluteTime.fromProtocolTimestamp(
- denom.stampExpireDeposit,
+ timestampProtocolFromDb(denom.stampExpireDeposit),
);
creditDeadline = AbsoluteTime.min(deadline, expiresWithdraw);
debitDeadline = AbsoluteTime.min(deadline, expiresDeposit);
@@ -318,7 +319,9 @@ function buildCoinInfoFromDenom(
exchangeBaseUrl: denom.exchangeBaseUrl,
duration: AbsoluteTime.difference(
AbsoluteTime.now(),
- AbsoluteTime.fromProtocolTimestamp(denom.stampExpireDeposit),
+ AbsoluteTime.fromProtocolTimestamp(
+ timestampProtocolFromDb(denom.stampExpireDeposit),
+ ),
),
totalAvailable: total,
value: Amounts.parseOrThrow(denom.value),
diff --git a/packages/taler-wallet-core/src/versions.ts b/packages/taler-wallet-core/src/versions.ts
index 8b9177bc3..022f4900d 100644
--- a/packages/taler-wallet-core/src/versions.ts
+++ b/packages/taler-wallet-core/src/versions.ts
@@ -29,7 +29,7 @@ export const WALLET_EXCHANGE_PROTOCOL_VERSION = "17:0:0";
export const WALLET_MERCHANT_PROTOCOL_VERSION = "2:0:1";
/**
- * Protocol version spoken with the merchant.
+ * Protocol version spoken with the bank.
*
* Uses libtool's current:revision:age versioning.
*/
diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts
index 67c05a42f..375e0a1b2 100644
--- a/packages/taler-wallet-core/src/wallet-api-types.ts
+++ b/packages/taler-wallet-core/src/wallet-api-types.ts
@@ -104,11 +104,13 @@ import {
TestPayArgs,
TestPayResult,
TestingSetTimetravelRequest,
+ TestingWaitTransactionRequest,
Transaction,
TransactionByIdRequest,
TransactionsRequest,
TransactionsResponse,
TxIdResponse,
+ UpdateExchangeEntryRequest,
UserAttentionByIdRequest,
UserAttentionsCountResponse,
UserAttentionsRequest,
@@ -118,7 +120,6 @@ import {
WalletContractData,
WalletCoreVersion,
WalletCurrencyInfo,
- WithdrawFakebankRequest,
WithdrawTestBalanceRequest,
WithdrawUriInfoResponse,
} from "@gnu-taler/taler-util";
@@ -199,7 +200,6 @@ export enum WalletApiOperation {
GenerateDepositGroupTxId = "generateDepositGroupTxId",
CreateDepositGroup = "createDepositGroup",
SetWalletDeviceId = "setWalletDeviceId",
- WithdrawFakebank = "withdrawFakebank",
ImportDb = "importDb",
ExportDb = "exportDb",
PreparePeerPushCredit = "preparePeerPushCredit",
@@ -216,12 +216,14 @@ export enum WalletApiOperation {
ValidateIban = "validateIban",
TestingWaitTransactionsFinal = "testingWaitTransactionsFinal",
TestingWaitRefreshesFinal = "testingWaitRefreshesFinal",
+ TestingWaitTransactionState = "testingWaitTransactionState",
TestingSetTimetravel = "testingSetTimetravel",
GetScopedCurrencyInfo = "getScopedCurrencyInfo",
ListStoredBackups = "listStoredBackups",
CreateStoredBackup = "createStoredBackup",
DeleteStoredBackup = "deleteStoredBackup",
RecoverStoredBackup = "recoverStoredBackup",
+ UpdateExchangeEntry = "updateExchangeEntry",
}
// group: Initialization
@@ -252,6 +254,11 @@ export type GetVersionOp = {
*/
export type WalletConfigParameter = RecursivePartial<WalletConfig>;
+export interface BuiltinExchange {
+ exchangeBaseUrl: string;
+ currencyHint?: string;
+}
+
export interface WalletConfig {
/**
* Initialization values useful for a complete startup.
@@ -259,7 +266,7 @@ export interface WalletConfig {
* These are values may be overridden by different wallets
*/
builtin: {
- exchanges: string[];
+ exchanges: BuiltinExchange[];
};
/**
@@ -557,6 +564,15 @@ export type AddExchangeOp = {
response: EmptyObject;
};
+/**
+ * Update an exchange entry.
+ */
+export type UpdateExchangeEntryOp = {
+ op: WalletApiOperation.UpdateExchangeEntry;
+ request: UpdateExchangeEntryRequest;
+ response: EmptyObject;
+};
+
export type ListKnownBankAccountsOp = {
op: WalletApiOperation.ListKnownBankAccounts;
request: ListKnownBankAccountsRequest;
@@ -935,17 +951,6 @@ export type TestPayOp = {
};
/**
- * Make a withdrawal from a fakebank, i.e.
- * a bank where test users can be registered freely
- * and testing APIs are available.
- */
-export type WithdrawFakebankOp = {
- op: WalletApiOperation.WithdrawFakebank;
- request: WithdrawFakebankRequest;
- response: EmptyObject;
-};
-
-/**
* Get wallet-internal pending tasks.
*/
export type GetUserAttentionRequests = {
@@ -1018,6 +1023,15 @@ export type TestingWaitRefreshesFinal = {
};
/**
+ * Wait until a transaction is in a particular state.
+ */
+export type TestingWaitTransactionStateOp = {
+ op: WalletApiOperation.TestingWaitTransactionState;
+ request: TestingWaitTransactionRequest;
+ response: EmptyObject;
+};
+
+/**
* Set a coin as (un-)suspended.
* Suspended coins won't be used for payments.
*/
@@ -1040,7 +1054,6 @@ export type ForceRefreshOp = {
export type WalletOperations = {
[WalletApiOperation.InitWallet]: InitWalletOp;
[WalletApiOperation.GetVersion]: GetVersionOp;
- [WalletApiOperation.WithdrawFakebank]: WithdrawFakebankOp;
[WalletApiOperation.PreparePayForUri]: PreparePayForUriOp;
[WalletApiOperation.SharePayment]: SharePaymentOp;
[WalletApiOperation.PreparePayForTemplate]: PreparePayForTemplateOp;
@@ -1122,11 +1135,13 @@ export type WalletOperations = {
[WalletApiOperation.TestingWaitTransactionsFinal]: TestingWaitTransactionsFinal;
[WalletApiOperation.TestingWaitRefreshesFinal]: TestingWaitRefreshesFinal;
[WalletApiOperation.TestingSetTimetravel]: TestingSetTimetravelOp;
+ [WalletApiOperation.TestingWaitTransactionState]: TestingWaitTransactionStateOp;
[WalletApiOperation.GetScopedCurrencyInfo]: GetScopedCurrencyInfoOp;
[WalletApiOperation.CreateStoredBackup]: CreateStoredBackupsOp;
[WalletApiOperation.ListStoredBackups]: ListStoredBackupsOp;
[WalletApiOperation.DeleteStoredBackup]: DeleteStoredBackupOp;
[WalletApiOperation.RecoverStoredBackup]: RecoverStoredBackupsOp;
+ [WalletApiOperation.UpdateExchangeEntry]: UpdateExchangeEntryOp;
};
export type WalletCoreRequestType<
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
index 2d0878afc..ead0ee407 100644
--- a/packages/taler-wallet-core/src/wallet.ts
+++ b/packages/taler-wallet-core/src/wallet.ts
@@ -127,6 +127,8 @@ import {
codecForRecoverStoredBackupRequest,
codecForTestingSetTimetravelRequest,
setDangerousTimetravel,
+ TestingWaitTransactionRequest,
+ codecForUpdateExchangeEntryRequest,
} from "@gnu-taler/taler-util";
import type { HttpRequestLibrary } from "@gnu-taler/taler-util/http";
import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
@@ -198,7 +200,6 @@ import {
downloadTosFromAcceptedFormat,
getExchangeDetails,
getExchangeRequestTimeout,
- provideExchangeRecordInTx,
updateExchangeFromUrl,
updateExchangeFromUrlHandler,
} from "./operations/exchanges.js";
@@ -250,6 +251,7 @@ import {
runIntegrationTest,
runIntegrationTest2,
testPay,
+ waitTransactionState,
waitUntilDone,
waitUntilRefreshesDone,
withdrawTestBalance,
@@ -532,10 +534,12 @@ async function fillDefaults(ws: InternalWalletState): Promise<void> {
logger.trace("defaults already applied");
return;
}
- for (const baseUrl of ws.config.builtin.exchanges) {
- await addPresetExchangeEntry(tx, baseUrl);
- const now = AbsoluteTime.now();
- provideExchangeRecordInTx(ws, tx, baseUrl, now);
+ for (const exch of ws.config.builtin.exchanges) {
+ await addPresetExchangeEntry(
+ tx,
+ exch.exchangeBaseUrl,
+ exch.currencyHint,
+ );
}
await tx.config.put({
key: ConfigRecordKey.CurrencyDefaultsApplied,
@@ -923,9 +927,9 @@ async function dumpCoins(ws: InternalWalletState): Promise<CoinDumpJson> {
ageCommitmentProof: c.ageCommitmentProof,
spend_allocation: c.spendAllocation
? {
- amount: c.spendAllocation.amount,
- id: c.spendAllocation.id,
- }
+ amount: c.spendAllocation.amount,
+ id: c.spendAllocation.id,
+ }
: undefined,
});
}
@@ -1069,8 +1073,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
case WalletApiOperation.WithdrawTestkudos: {
await withdrawTestBalance(ws, {
amount: "TESTKUDOS:10",
- bankAccessApiBaseUrl:
- "https://bank.test.taler.net/demobanks/default/access-api/",
+ corebankApiBaseUrl: "https://bank.test.taler.net/",
exchangeBaseUrl: "https://exchange.test.taler.net/",
});
return {
@@ -1120,6 +1123,11 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
});
return {};
}
+ case WalletApiOperation.UpdateExchangeEntry: {
+ const req = codecForUpdateExchangeEntryRequest().decode(payload);
+ await updateExchangeFromUrl(ws, req.exchangeBaseUrl, {});
+ return {};
+ }
case WalletApiOperation.ListExchanges: {
return await getExchanges(ws);
}
@@ -1261,7 +1269,10 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
`templates/${url.templateId}`,
url.merchantBaseUrl,
);
- const httpReq = await ws.http.postJson(reqUrl.href, templateDetails);
+ const httpReq = await ws.http.fetch(reqUrl.href, {
+ method: "POST",
+ body: templateDetails,
+ });
const resp = await readSuccessResponseJsonOrThrow(
httpReq,
codecForMerchantPostOrderResponse(),
@@ -1411,6 +1422,11 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
const resp = await getBackupRecovery(ws);
return resp;
}
+ case WalletApiOperation.TestingWaitTransactionState: {
+ const req = payload as TestingWaitTransactionRequest;
+ await waitTransactionState(ws, req.transactionId, req.txState);
+ return {};
+ }
case WalletApiOperation.GetScopedCurrencyInfo: {
// Ignore result, just validate in this mock implementation
codecForGetCurrencyInfoRequest().decode(payload);
@@ -1489,40 +1505,6 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
trustedExchanges: [],
};
}
- case WalletApiOperation.WithdrawFakebank: {
- const req = codecForWithdrawFakebankRequest().decode(payload);
- const amount = Amounts.parseOrThrow(req.amount);
- const details = await getExchangeWithdrawalInfo(
- ws,
- req.exchange,
- amount,
- undefined,
- );
- const wres = await createManualWithdrawal(ws, {
- amount: amount,
- exchangeBaseUrl: req.exchange,
- });
- const paytoUri = details.exchangePaytoUris[0];
- const pt = parsePaytoUri(paytoUri);
- if (!pt) {
- throw Error("failed to parse payto URI");
- }
- const components = pt.targetPath.split("/");
- const creditorAcct = components[components.length - 1];
- logger.info(`making testbank transfer to '${creditorAcct}'`);
- const fbReq = await ws.http.postJson(
- new URL(`${creditorAcct}/admin/add-incoming`, req.bank).href,
- {
- amount: Amounts.stringify(amount),
- reserve_pub: wres.reservePub,
- debit_account:
- "payto://x-taler-bank/localhost/testdebtor?receiver-name=Foo",
- },
- );
- const fbResp = await readSuccessResponseJsonOrThrow(fbReq, codecForAny());
- logger.info(`started fakebank withdrawal: ${j2s(fbResp)}`);
- return {};
- }
case WalletApiOperation.TestCrypto: {
return await ws.cryptoApi.hashString({ str: "hello world" });
}
@@ -1691,7 +1673,12 @@ export class Wallet {
public static defaultConfig: Readonly<WalletConfig> = {
builtin: {
- exchanges: ["https://exchange.demo.taler.net/"],
+ exchanges: [
+ {
+ exchangeBaseUrl: "https://exchange.demo.taler.net/",
+ currencyHint: "KUDOS",
+ },
+ ],
},
features: {
allowHttp: false,