commit ac2d2c6dde5b1815dd25b4bef97255bf9a41037b
parent f3568d46d5af40f382ecd06f1fdde324f113b6f2
Author: Florian Dold <florian@dold.me>
Date: Wed, 23 Apr 2025 12:41:25 +0200
wallet-core: improve logging for tests
Diffstat:
4 files changed, 97 insertions(+), 56 deletions(-)
diff --git a/packages/taler-util/src/time.ts b/packages/taler-util/src/time.ts
@@ -97,6 +97,12 @@ export namespace TalerPreciseTimestamp {
}
}
+export namespace TalerProtocolDuration {
+ export function fromSpec(d: DurationUnitSpec) {
+ return Duration.toTalerProtocolDuration(Duration.fromSpec(d));
+ }
+}
+
export namespace TalerProtocolTimestamp {
export function now(): TalerProtocolTimestamp {
return AbsoluteTime.toProtocolTimestamp(AbsoluteTime.now());
@@ -136,6 +142,7 @@ export namespace TalerProtocolTimestamp {
}
return { t_s: Math.min(t1.t_s, t2.t_s) };
}
+
export function max(
t1: TalerProtocolTimestamp,
t2: TalerProtocolTimestamp,
@@ -172,6 +179,15 @@ export function setDangerousTimetravel(dt: number): void {
timeshift = dt;
}
+export interface DurationUnitSpec {
+ seconds?: number;
+ minutes?: number;
+ hours?: number;
+ days?: number;
+ months?: number;
+ years?: number;
+}
+
export namespace Duration {
export function toMilliseconds(d: Duration): number {
if (d.d_ms === "forever") {
@@ -285,14 +301,7 @@ export namespace Duration {
*
* Returns a zero duration if none of the units were specified.
*/
- export function fromSpec(spec: {
- seconds?: number;
- minutes?: number;
- hours?: number;
- days?: number;
- months?: number;
- years?: number;
- }): Duration {
+ export function fromSpec(spec: DurationUnitSpec): Duration {
let d_ms = 0;
d_ms += (spec.seconds ?? 0) * SECONDS;
d_ms += (spec.minutes ?? 0) * MINUTES;
@@ -303,14 +312,9 @@ export namespace Duration {
return { d_ms };
}
- export function fromSpecOrUndefined(spec: {
- seconds?: number;
- minutes?: number;
- hours?: number;
- days?: number;
- months?: number;
- years?: number;
- }): Duration | undefined {
+ export function fromSpecOrUndefined(
+ spec: DurationUnitSpec,
+ ): Duration | undefined {
if (
spec.seconds == undefined &&
spec.minutes == undefined &&
diff --git a/packages/taler-util/src/types-taler-wallet.ts b/packages/taler-util/src/types-taler-wallet.ts
@@ -50,6 +50,7 @@ import {
import {
AmountString,
CurrencySpecification,
+ DurationUnitSpec,
EddsaPrivateKeyString,
TalerMerchantApi,
TemplateParams,
@@ -3586,6 +3587,21 @@ export interface TransactionStatePattern {
export interface TestingWaitTransactionRequest {
transactionId: TransactionIdStr;
+
+ /**
+ * Additional identifier that is used in the logs
+ * to easily find the status of the particular wait
+ * request.
+ */
+ logId?: string;
+
+ /**
+ * After the timeout has passed, give up on
+ * waiting for the desired state and raise
+ * an error instead.
+ */
+ //timeout?: DurationUnitSpec;
+
txState: TransactionStatePattern | TransactionStatePattern[];
}
diff --git a/packages/taler-wallet-core/src/testing.ts b/packages/taler-wallet-core/src/testing.ts
@@ -43,6 +43,7 @@ import {
parsePaytoUri,
PreparePayResultType,
TalerCorebankApiClient,
+ TestingWaitTransactionRequest,
TestPayArgs,
TestPayResult,
TransactionIdStr,
@@ -145,9 +146,12 @@ export async function withdrawTestBalance(
// We need to wait until the wallet sent the reserve information
// to the bank, otherwise the confirmation in the bank would fail.
- await waitTransactionState(wex, acceptResp.transactionId, {
- major: TransactionMajorState.Pending,
- minor: TransactionMinorState.BankConfirmTransfer,
+ await waitTransactionState(wex, {
+ transactionId: acceptResp.transactionId,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.BankConfirmTransfer,
+ },
});
await corebankClient.confirmWithdrawalOperation(bankUser.username, {
@@ -592,9 +596,12 @@ async function waitUntilTransactionPendingReady(
wex: WalletExecutionContext,
transactionId: string,
): Promise<void> {
- return await waitTransactionState(wex, transactionId, {
- major: TransactionMajorState.Pending,
- minor: TransactionMinorState.Ready,
+ return await waitTransactionState(wex, {
+ transactionId: transactionId as TransactionIdStr,
+ txState: {
+ major: TransactionMajorState.Pending,
+ minor: TransactionMinorState.Ready,
+ },
});
}
@@ -613,19 +620,26 @@ function matchState(
*/
export async function waitTransactionState(
wex: WalletExecutionContext,
- transactionId: string,
- txState: TransactionStatePattern | TransactionStatePattern[],
+ req: TestingWaitTransactionRequest,
): Promise<void> {
+ const transactionId = req.transactionId;
+ const txState = req.txState;
+ const logId = req.logId ?? "none";
logger.info(
`starting waiting for ${transactionId} to be in ${JSON.stringify(
txState,
- )})`,
+ )}) (start logId: ${logId})`,
);
await genericWaitForState(wex, {
async checkState() {
const tx = await getTransactionById(wex, {
transactionId,
});
+ logger.info(
+ `current state for ${transactionId} is ${JSON.stringify(
+ tx.txState,
+ )} (update logId: ${logId})`,
+ );
if (Array.isArray(txState)) {
for (const myState of txState) {
if (matchState(tx.txState, myState)) {
@@ -638,10 +652,13 @@ export async function waitTransactionState(
}
},
filterNotification: (notif) =>
- notif.type === NotificationType.TransactionStateTransition && notif.transactionId === transactionId
+ notif.type === NotificationType.TransactionStateTransition &&
+ notif.transactionId === transactionId,
});
logger.info(
- `done waiting for ${transactionId} to be in ${JSON.stringify(txState)}`,
+ `done waiting for ${transactionId} to be in ${JSON.stringify(
+ txState,
+ )} (done logId: ${logId})`,
);
}
diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts
@@ -231,7 +231,6 @@ import {
performanceNow,
safeStringifyException,
setDangerousTimetravel,
- stringifyAddExchange,
stringifyScopeInfo,
validateIban,
} from "@gnu-taler/taler-util";
@@ -261,6 +260,7 @@ import {
getMaxDepositAmount,
getMaxPeerPushDebitAmount,
} from "./coinSelection.js";
+import { cancelableFetch } from "./common.js";
import { TalerCryptoInterface } from "./crypto/cryptoImplementation.js";
import {
CryptoDispatcher,
@@ -399,7 +399,6 @@ import {
getWithdrawalDetailsForUri,
prepareBankIntegratedWithdrawal,
} from "./withdraw.js";
-import { cancelableFetch } from "./common.js";
const logger = new Logger("wallet.ts");
@@ -1189,17 +1188,15 @@ async function handleGetChoicesForPayment(
wex: WalletExecutionContext,
req: GetChoicesForPaymentRequest,
): Promise<GetChoicesForPaymentResult> {
- return await getChoicesForPayment(wex,
- req.transactionId,
- req.forcedCoinSel,
- );
+ return await getChoicesForPayment(wex, req.transactionId, req.forcedCoinSel);
}
async function handleConfirmPay(
wex: WalletExecutionContext,
req: ConfirmPayRequest,
): Promise<ConfirmPayResult> {
- return await confirmPay(wex,
+ return await confirmPay(
+ wex,
req.transactionId,
req.sessionId,
undefined,
@@ -1498,11 +1495,13 @@ async function handleAddGlobalCurrencyExchange(
return;
}
wex.ws.exchangeCache.clear();
- const info = await tx.currencyInfo.get(stringifyScopeInfo({
- type: ScopeType.Exchange,
- currency: req.currency,
- url: req.exchangeBaseUrl,
- }));
+ const info = await tx.currencyInfo.get(
+ stringifyScopeInfo({
+ type: ScopeType.Exchange,
+ currency: req.currency,
+ url: req.exchangeBaseUrl,
+ }),
+ );
if (info) {
info.scopeInfoStr = stringifyScopeInfo({
type: ScopeType.Global,
@@ -1558,10 +1557,12 @@ async function handleRemoveGlobalCurrencyExchange(
}
wex.ws.exchangeCache.clear();
checkDbInvariant(!!existingRec.id, `no global exchange for ${j2s(key)}`);
- await tx.currencyInfo.delete(stringifyScopeInfo({
- type: ScopeType.Global,
- currency: req.currency,
- }));
+ await tx.currencyInfo.delete(
+ stringifyScopeInfo({
+ type: ScopeType.Global,
+ currency: req.currency,
+ }),
+ );
await tx.globalCurrencyExchanges.delete(existingRec.id);
},
);
@@ -2059,7 +2060,7 @@ const handlers: { [T in WalletApiOperation]: HandlerWithValidator<T> } = {
[WalletApiOperation.TestingWaitTransactionState]: {
codec: codecForAny(),
handler: async (wex, req) => {
- await waitTransactionState(wex, req.transactionId, req.txState);
+ await waitTransactionState(wex, req);
return {};
},
},
@@ -2450,7 +2451,7 @@ async function dispatchWalletCoreApiRequest(
wex = getObservedWalletExecutionContext(ws, cts.token, cts, oc);
} else {
oc = {
- observe(evt) { },
+ observe(evt) {},
};
wex = getNormalWalletExecutionContext(ws, cts.token, cts, oc);
}
@@ -2595,7 +2596,7 @@ export class Cache<T> {
constructor(
private maxCapacity: number,
private cacheDuration: Duration,
- ) { }
+ ) {}
get(key: string): T | undefined {
const r = this.map.get(key);
@@ -2631,7 +2632,7 @@ export class Cache<T> {
* Implementation of triggers for the wallet DB.
*/
class WalletDbTriggerSpec implements TriggerSpec {
- constructor(public ws: InternalWalletState) { }
+ constructor(public ws: InternalWalletState) {}
afterCommit(info: AfterCommitInfo): void {
if (info.mode !== "readwrite") {
@@ -2660,18 +2661,21 @@ type LongpollRunFn<T> = (timeoutMs: number) => Promise<T>;
const PER_HOSTNAME_PERMITS: number = 5;
class LongpollQueue {
- private idCounter: number = 1
- private hostNameQueues: Map<string, { queue: (() => void)[], permit: number }> = new Map();
+ private idCounter: number = 1;
+ private hostNameQueues: Map<
+ string,
+ { queue: (() => void)[]; permit: number }
+ > = new Map();
// FIXME add an additional global semaphore
- constructor() { }
+ constructor() {}
async queue<T>(
cancellationToken: CancellationToken,
url: URL,
- f: LongpollRunFn<T>
+ f: LongpollRunFn<T>,
): Promise<T> {
- const hostname = url.hostname
+ const hostname = url.hostname;
const rid = this.idCounter++;
const triggerNextLongpoll = () => {
@@ -2712,14 +2716,14 @@ class LongpollQueue {
if (state == null) {
state = {
permit: PER_HOSTNAME_PERMITS,
- queue: []
- }
- this.hostNameQueues.set(hostname, state)
+ queue: [],
+ };
+ this.hostNameQueues.set(hostname, state);
}
if (state.permit > 0) {
state.permit--;
- this.hostNameQueues.set(hostname, state)
+ this.hostNameQueues.set(hostname, state);
return doRunLongpoll();
} else {
logger.info(`long-poll request ${rid} to ${hostname} queued`);