taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

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:
Mpackages/taler-util/src/time.ts | 36++++++++++++++++++++----------------
Mpackages/taler-util/src/types-taler-wallet.ts | 16++++++++++++++++
Mpackages/taler-wallet-core/src/testing.ts | 39++++++++++++++++++++++++++++-----------
Mpackages/taler-wallet-core/src/wallet.ts | 62+++++++++++++++++++++++++++++++++-----------------------------
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`);