From 8b37f6ef4987cb06b4682d5bd2cfc907fd9f8d2d Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 23 May 2024 13:09:04 +0200 Subject: wallet-core: clean up logging, fix shepherd typo --- packages/taler-wallet-core/src/shepherd.ts | 2 +- packages/taler-wallet-core/src/wallet.ts | 16 +++------------- 2 files changed, 4 insertions(+), 14 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/shepherd.ts b/packages/taler-wallet-core/src/shepherd.ts index 3b160d97f..dbdd7aac5 100644 --- a/packages/taler-wallet-core/src/shepherd.ts +++ b/packages/taler-wallet-core/src/shepherd.ts @@ -256,7 +256,7 @@ export class TaskSchedulerImpl implements TaskScheduler { async reload(): Promise { await this.ensureRunning(); const tasksIds = [...this.sheps.keys()]; - logger.info(`reloading sheperd with ${tasksIds.length} tasks`); + logger.info(`reloading shepherd with ${tasksIds.length} tasks`); for (const taskId of tasksIds) { this.stopShepherdTask(taskId); } diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 68da15410..49ffbcfb6 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -721,12 +721,9 @@ async function dispatchRequestInternal( case WalletApiOperation.InitWallet: { const req = codecForInitRequest().decode(payload); - logger.info(`init request: ${j2s(req)}`); - - if (wex.ws.initCalled) { - logger.info("initializing wallet (repeat initialization)"); - } else { - logger.info("initializing wallet (first initialization)"); + if (logger.shouldLogTrace()) { + const initType = wex.ws.initCalled ? "repeat initialization" : "first initialization"; + logger.trace(`init request (${initType}): ${j2s(req)}`); } // Write to the DB to make sure that we're failing early in @@ -1832,11 +1829,6 @@ class WalletDbTriggerSpec implements TriggerSpec { if (info.mode !== "readwrite") { return; } - logger.info( - `in after commit callback for readwrite, modified ${j2s([ - ...info.modifiedStores, - ])}`, - ); const modified = info.accessedStores; if ( modified.has(WalletStoresV1.exchanges.storeName) || @@ -1924,8 +1916,6 @@ export class InternalWalletState { initWithConfig(newConfig: WalletRunConfig): void { this._config = newConfig; - logger.info(`setting new config to ${j2s(newConfig)}`); - this._http = this.httpFactory(newConfig); if (this.config.testing.devModeActive) { -- cgit v1.2.3 From 527716758f154eb863acb0052f004dd23313f765 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 23 May 2024 10:26:54 -0300 Subject: redo commit #aa39162de --- packages/taler-wallet-core/src/wallet.ts | 33 +++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 49ffbcfb6..c17a2b467 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -107,6 +107,7 @@ import { codecForGetExchangeTosRequest, codecForGetWithdrawalDetailsForAmountRequest, codecForGetWithdrawalDetailsForUri, + codecForHintNetworkAvailabilityRequest, codecForImportDbRequest, codecForInitRequest, codecForInitiatePeerPullPaymentRequest, @@ -287,6 +288,7 @@ import { getWithdrawalTransactionByUri, parseTransactionIdentifier, resumeTransaction, + retryAll, retryTransaction, suspendTransaction, } from "./transactions.js"; @@ -741,7 +743,6 @@ async function dispatchRequestInternal( innerError: getErrorDetailFromException(e), }); } - wex.ws.initWithConfig(applyRunConfigDefaults(req.config)); if (wex.ws.config.testing.skipDefaults) { @@ -754,8 +755,11 @@ async function dispatchRequestInternal( versionInfo: getVersion(wex), }; - // After initialization, task loop should run. - await wex.taskScheduler.ensureRunning(); + if (req.config?.lazyTaskLoop) { + logger.trace("lazily starting task loop"); + } else { + await wex.taskScheduler.ensureRunning(); + } wex.ws.initCalled = true; return resp; @@ -1043,6 +1047,10 @@ async function dispatchRequestInternal( const req = codecForPrepareWithdrawExchangeRequest().decode(payload); return handlePrepareWithdrawExchange(wex, req); } + case WalletApiOperation.CheckPayForTemplate: { + const req = codecForCheckPayTemplateRequest().decode(payload); + return await checkPayForTemplate(wex, req); + } case WalletApiOperation.PreparePayForUri: { const req = codecForPreparePayRequest().decode(payload); return await preparePayForUri(wex, req.talerPayUri); @@ -1231,10 +1239,16 @@ async function dispatchRequestInternal( await loadBackupRecovery(wex, req); return {}; } - // case WalletApiOperation.GetPlanForOperation: { - // const req = codecForGetPlanForOperationRequest().decode(payload); - // return await getPlanForOperation(ws, req); - // } + case WalletApiOperation.HintNetworkAvailability: { + const req = codecForHintNetworkAvailabilityRequest().decode(payload); + if (req.isNetworkAvailable) { + await retryAll(wex); + } else { + // We're not doing anything right now, but we could stop showing + // certain errors! + } + return {}; + } case WalletApiOperation.ConvertDepositAmount: { const req = codecForConvertAmountRequest.decode(payload); return await convertDepositAmount(wex, req); @@ -1829,6 +1843,11 @@ class WalletDbTriggerSpec implements TriggerSpec { if (info.mode !== "readwrite") { return; } + logger.trace( + `in after commit callback for readwrite, modified ${j2s([ + ...info.modifiedStores, + ])}`, + ); const modified = info.accessedStores; if ( modified.has(WalletStoresV1.exchanges.storeName) || -- cgit v1.2.3 From cdcedf65595393fc186eb2012d3ae6cd55540f59 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 23 May 2024 19:50:59 +0200 Subject: wallet-core: support bank-integrated withdrawal with amount selection by wallet --- packages/taler-wallet-core/src/db.ts | 1 + packages/taler-wallet-core/src/shepherd.ts | 8 ++++- packages/taler-wallet-core/src/wallet.ts | 5 ++- packages/taler-wallet-core/src/withdraw.ts | 58 +++++++++++++++++++++++------- 4 files changed, 58 insertions(+), 14 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 44c241aed..640d94753 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -1439,6 +1439,7 @@ export interface WgInfoBankIntegrated { * a Taler-integrated bank. */ bankInfo: ReserveBankInfo; + /** * Info about withdrawal accounts, possibly including currency conversion. */ diff --git a/packages/taler-wallet-core/src/shepherd.ts b/packages/taler-wallet-core/src/shepherd.ts index dbdd7aac5..d662bd7ae 100644 --- a/packages/taler-wallet-core/src/shepherd.ts +++ b/packages/taler-wallet-core/src/shepherd.ts @@ -382,7 +382,13 @@ export class TaskSchedulerImpl implements TaskScheduler { }); switch (res.type) { case TaskRunResultType.Error: { - logger.trace(`Shepherd for ${taskId} got error result.`); + if (logger.shouldLogTrace()) { + logger.trace( + `Shepherd for ${taskId} got error result: ${j2s( + res.errorDetail, + )}`, + ); + } const retryRecord = await storePendingTaskError( this.ws, taskId, diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index c17a2b467..3455d451b 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -724,7 +724,9 @@ async function dispatchRequestInternal( const req = codecForInitRequest().decode(payload); if (logger.shouldLogTrace()) { - const initType = wex.ws.initCalled ? "repeat initialization" : "first initialization"; + const initType = wex.ws.initCalled + ? "repeat initialization" + : "first initialization"; logger.trace(`init request (${initType}): ${j2s(req)}`); } @@ -997,6 +999,7 @@ async function dispatchRequestInternal( talerWithdrawUri: req.talerWithdrawUri, forcedDenomSel: req.forcedDenomSel, restrictAge: req.restrictAge, + amount: req.amount, }); } case WalletApiOperation.ConfirmWithdrawal: { diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index 4a7c7873c..16289b1ef 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -857,10 +857,16 @@ export async function getBankWithdrawalInfo( } const { body: status } = resp; + let amount: AmountJson | undefined; + if (status.amount) { + amount = Amounts.parseOrThrow(status.amount); + } + return { operationId: uriResult.withdrawalOperationId, apiBaseUrl: uriResult.bankIntegrationApiBaseUrl, - amount: Amounts.parseOrThrow(status.amount), + currency: config.currency, + amount, confirmTransferUrl: status.confirm_transfer_url, senderWire: status.sender_wire, suggestedExchange: status.suggested_exchange, @@ -2262,7 +2268,7 @@ export async function getWithdrawalDetailsForUri( } } - const currency = Amounts.currencyOf(info.amount); + const currency = info.currency; const listExchangesResp = await listExchanges(wex); const possibleExchanges = listExchangesResp.exchanges.filter((x) => { @@ -2277,7 +2283,8 @@ export async function getWithdrawalDetailsForUri( operationId: info.operationId, confirmTransferUrl: info.confirmTransferUrl, status: info.status, - amount: Amounts.stringify(info.amount), + currency, + amount: info.amount ? Amounts.stringify(info.amount) : undefined, defaultExchangeBaseUrl: info.suggestedExchange, possibleExchanges, }; @@ -2379,6 +2386,7 @@ export function getBankAbortUrl(talerWithdrawUri: string): string { async function registerReserveWithBank( wex: WalletExecutionContext, withdrawalGroupId: string, + isFlexibleAmount: boolean, ): Promise { const withdrawalGroup = await wex.db.runReadOnlyTx( { storeNames: ["withdrawalGroups"] }, @@ -2407,7 +2415,11 @@ async function registerReserveWithBank( const reqBody = { reserve_pub: withdrawalGroup.reservePub, selected_exchange: bankInfo.exchangePaytoUri, - }; + } as any; + if (isFlexibleAmount) { + reqBody.amount = withdrawalGroup.instructedAmount; + } + logger.trace(`isFlexibleAmount: ${isFlexibleAmount}`); logger.info(`registering reserve with bank: ${j2s(reqBody)}`); const httpResp = await wex.http.fetch(bankStatusUrl, { method: "POST", @@ -2516,7 +2528,9 @@ async function processBankRegisterReserve( // FIXME: Put confirm transfer URL in the DB! - await registerReserveWithBank(wex, withdrawalGroupId); + const isFlexibleAmount = status.amount == null; + + await registerReserveWithBank(wex, withdrawalGroupId, isFlexibleAmount); return TaskRunResult.progress(); } @@ -2985,7 +2999,7 @@ export async function confirmWithdrawal( const confirmUrl = withdrawalGroup.wgInfo.bankInfo.confirmUrl; /** - * The only reasong this to be undefined is because it is an old wallet + * The only reason this could be undefined is because it is an old wallet * database before adding the wireType field was added */ let wtypes: string[]; @@ -3025,7 +3039,7 @@ export async function confirmWithdrawal( req.forcedDenomSel, ); - ctx.transition({}, async (rec) => { + await ctx.transition({}, async (rec) => { if (!rec) { return TransitionResult.stay(); } @@ -3060,7 +3074,6 @@ export async function confirmWithdrawal( }); await wex.taskScheduler.resetTaskRetries(ctx.taskId); - wex.taskScheduler.startShepherdTask(ctx.taskId); } /** @@ -3080,6 +3093,7 @@ export async function acceptWithdrawalFromUri( selectedExchange: string; forcedDenomSel?: ForcedDenomSel; restrictAge?: number; + amount?: AmountLike; }, ): Promise { const selectedExchange = req.selectedExchange; @@ -3124,17 +3138,37 @@ export async function acceptWithdrawalFromUri( withdrawInfo.wireTypes, ); + let amount: AmountJson; + if (withdrawInfo.amount == null) { + if (req.amount == null) { + throw Error( + "amount required, as withdrawal operation has flexible amount", + ); + } + amount = Amounts.parseOrThrow(req.amount); + } else { + if ( + req.amount != null && + Amounts.cmp(req.amount, withdrawInfo.amount) != 0 + ) { + throw Error( + "mismatched amount, amount is fixed by bank but client provided different amount", + ); + } + amount = withdrawInfo.amount; + } + const withdrawalAccountList = await fetchWithdrawalAccountInfo( wex, { exchange, - instructedAmount: withdrawInfo.amount, + instructedAmount: amount, }, CancellationToken.CONTINUE, ); const withdrawalGroup = await internalCreateWithdrawalGroup(wex, { - amount: withdrawInfo.amount, + amount, exchangeBaseUrl: req.selectedExchange, wgInfo: { withdrawalType: WithdrawalRecordType.BankIntegrated, @@ -3162,10 +3196,10 @@ export async function acceptWithdrawalFromUri( hintTransactionId: ctx.transactionId, }); - await waitWithdrawalRegistered(wex, ctx); - wex.taskScheduler.startShepherdTask(ctx.taskId); + await waitWithdrawalRegistered(wex, ctx); + return { reservePub: withdrawalGroup.reservePub, confirmTransferUrl: withdrawInfo.confirmTransferUrl, -- cgit v1.2.3 From 5d7cad858d994489d8c79ec9bccbd03bb04d359c Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 23 May 2024 19:55:06 +0200 Subject: -simplify --- packages/taler-wallet-core/src/withdraw.ts | 119 +++++++++++------------------ 1 file changed, 46 insertions(+), 73 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index 16289b1ef..1dc4e0999 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -114,6 +114,7 @@ import { TransitionResult, TransitionResultType, constructTaskIdentifier, + genericWaitForState, makeCoinAvailable, makeCoinsVisible, } from "./common.js"; @@ -3207,88 +3208,60 @@ export async function acceptWithdrawalFromUri( }; } -async function internalWaitWithdrawalRegistered( +async function waitWithdrawalRegistered( wex: WalletExecutionContext, ctx: WithdrawTransactionContext, - withdrawalNotifFlag: AsyncFlag, ): Promise { - while (true) { - const { withdrawalRec, retryRec } = await wex.db.runReadOnlyTx( - { storeNames: ["withdrawalGroups", "operationRetries"] }, - async (tx) => { - return { - withdrawalRec: await tx.withdrawalGroups.get(ctx.withdrawalGroupId), - retryRec: await tx.operationRetries.get(ctx.taskId), - }; - }, - ); + await genericWaitForState(wex, { + async checkState(): Promise { + const { withdrawalRec, retryRec } = await wex.db.runReadOnlyTx( + { storeNames: ["withdrawalGroups", "operationRetries"] }, + async (tx) => { + return { + withdrawalRec: await tx.withdrawalGroups.get(ctx.withdrawalGroupId), + retryRec: await tx.operationRetries.get(ctx.taskId), + }; + }, + ); - if (!withdrawalRec) { - throw Error("withdrawal not found anymore"); - } + if (!withdrawalRec) { + throw Error("withdrawal not found anymore"); + } - switch (withdrawalRec.status) { - case WithdrawalGroupStatus.FailedBankAborted: - throw TalerError.fromDetail( - TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK, - {}, - ); - case WithdrawalGroupStatus.PendingKyc: - case WithdrawalGroupStatus.PendingAml: - case WithdrawalGroupStatus.PendingQueryingStatus: - case WithdrawalGroupStatus.PendingReady: - case WithdrawalGroupStatus.Done: - case WithdrawalGroupStatus.PendingWaitConfirmBank: - return; - case WithdrawalGroupStatus.PendingRegisteringBank: - break; - default: { - if (retryRec) { - if (retryRec.lastError) { - throw TalerError.fromUncheckedDetail(retryRec.lastError); - } else { - throw Error("withdrawal unexpectedly pending"); + switch (withdrawalRec.status) { + case WithdrawalGroupStatus.FailedBankAborted: + throw TalerError.fromDetail( + TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK, + {}, + ); + case WithdrawalGroupStatus.PendingKyc: + case WithdrawalGroupStatus.PendingAml: + case WithdrawalGroupStatus.PendingQueryingStatus: + case WithdrawalGroupStatus.PendingReady: + case WithdrawalGroupStatus.Done: + case WithdrawalGroupStatus.PendingWaitConfirmBank: + return true; + case WithdrawalGroupStatus.PendingRegisteringBank: + break; + default: { + if (retryRec) { + if (retryRec.lastError) { + throw TalerError.fromUncheckedDetail(retryRec.lastError); + } else { + throw Error("withdrawal unexpectedly pending"); + } } } } - } - - await withdrawalNotifFlag.wait(); - withdrawalNotifFlag.reset(); - } -} - -async function waitWithdrawalRegistered( - wex: WalletExecutionContext, - ctx: WithdrawTransactionContext, -): Promise { - // FIXME: Doesn't support cancellation yet - // FIXME: We should use Symbol.dispose magic here for cleanup! - - const withdrawalNotifFlag = new AsyncFlag(); - // Raise exchangeNotifFlag whenever we get a notification - // about our exchange. - const cancelNotif = wex.ws.addNotificationListener((notif) => { - if ( - notif.type === NotificationType.TransactionStateTransition && - notif.transactionId === ctx.transactionId - ) { - logger.info(`raising update notification: ${j2s(notif)}`); - withdrawalNotifFlag.raise(); - } + return false; + }, + filterNotification(notif) { + return ( + notif.type === NotificationType.TransactionStateTransition && + notif.transactionId === ctx.transactionId + ); + }, }); - - try { - const res = await internalWaitWithdrawalRegistered( - wex, - ctx, - withdrawalNotifFlag, - ); - logger.info("done waiting for ready exchange"); - return res; - } finally { - cancelNotif(); - } } async function fetchAccount( -- cgit v1.2.3 From 274b72f6ea4ac92e334b97a9cc427d64b2307217 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 23 May 2024 22:11:23 +0200 Subject: wallet-core: implement acceptBankIntegratedWithdrawal via prepare/confirm step This avoids duplication of some subtle logic. --- packages/taler-wallet-core/src/versions.ts | 2 +- packages/taler-wallet-core/src/withdraw.ts | 158 +++++++++++------------------ 2 files changed, 63 insertions(+), 97 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/versions.ts b/packages/taler-wallet-core/src/versions.ts index d33a23cdd..b89ff16f6 100644 --- a/packages/taler-wallet-core/src/versions.ts +++ b/packages/taler-wallet-core/src/versions.ts @@ -52,7 +52,7 @@ export const WALLET_BANK_CONVERSION_API_PROTOCOL_VERSION = "2:0:0"; /** * Libtool version of the wallet-core API. */ -export const WALLET_CORE_API_PROTOCOL_VERSION = "5:0:0"; +export const WALLET_CORE_API_PROTOCOL_VERSION = "6:0:0"; /** * Libtool rules: diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index 1dc4e0999..7f43b4333 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -2953,6 +2953,7 @@ export async function prepareBankIntegratedWithdrawal( }, }, reserveStatus: WithdrawalGroupStatus.DialogProposed, + amount: info.amount == null ? undefined : Amounts.parseOrThrow(info.amount), }); const withdrawalGroupId = withdrawalGroup.withdrawalGroupId; @@ -2999,6 +3000,25 @@ export async function confirmWithdrawal( const talerWithdrawUri = withdrawalGroup.wgInfo.bankInfo.talerWithdrawUri; const confirmUrl = withdrawalGroup.wgInfo.bankInfo.confirmUrl; + let amount: AmountString; + + if (withdrawalGroup.instructedAmount == null) { + if (req.amount == null) { + throw Error( + "neither the withdrawal group nor the request specifies an amount", + ); + } + amount = req.amount; + } else { + if ( + req.amount != null && + Amounts.cmp(withdrawalGroup.instructedAmount, req.amount) != 0 + ) { + throw Error("conflicting amount"); + } + amount = withdrawalGroup.instructedAmount; + } + /** * The only reason this could be undefined is because it is an old wallet * database before adding the wireType field was added @@ -3024,7 +3044,7 @@ export async function confirmWithdrawal( wex, { exchange, - instructedAmount: Amounts.parseOrThrow(req.amount), + instructedAmount: Amounts.parseOrThrow(amount), }, wex.cancellationToken, ); @@ -3036,7 +3056,7 @@ export async function confirmWithdrawal( const initalDenoms = await getInitialDenomsSelection( wex, req.exchangeBaseUrl, - Amounts.parseOrThrow(req.amount), + Amounts.parseOrThrow(amount), req.forcedDenomSel, ); @@ -3047,7 +3067,7 @@ export async function confirmWithdrawal( switch (rec.status) { case WithdrawalGroupStatus.DialogProposed: { rec.exchangeBaseUrl = req.exchangeBaseUrl; - rec.instructedAmount = req.amount; + rec.instructedAmount = amount; rec.denomsSel = initalDenoms; rec.rawWithdrawalAmount = initalDenoms.totalWithdrawCost; rec.effectiveWithdrawalAmount = initalDenoms.totalCoinValue; @@ -3074,6 +3094,11 @@ export async function confirmWithdrawal( } }); + wex.ws.notify({ + type: NotificationType.BalanceChange, + hintTransactionId: ctx.transactionId, + }); + await wex.taskScheduler.resetTaskRetries(ctx.taskId); } @@ -3097,113 +3122,54 @@ export async function acceptWithdrawalFromUri( amount?: AmountLike; }, ): Promise { - const selectedExchange = req.selectedExchange; - logger.info( - `accepting withdrawal via ${req.talerWithdrawUri}, canonicalized selected exchange ${selectedExchange}`, - ); - const existingWithdrawalGroup = await wex.db.runReadOnlyTx( - { storeNames: ["withdrawalGroups"] }, - async (tx) => { - return await tx.withdrawalGroups.indexes.byTalerWithdrawUri.get( - req.talerWithdrawUri, - ); - }, - ); - - if (existingWithdrawalGroup) { - let url: string | undefined; - if ( - existingWithdrawalGroup.wgInfo.withdrawalType === - WithdrawalRecordType.BankIntegrated - ) { - url = existingWithdrawalGroup.wgInfo.bankInfo.confirmUrl; - } - return { - reservePub: existingWithdrawalGroup.reservePub, - confirmTransferUrl: url, - transactionId: constructTransactionIdentifier({ - tag: TransactionType.Withdrawal, - withdrawalGroupId: existingWithdrawalGroup.withdrawalGroupId, - }), - }; - } - - const exchange = await fetchFreshExchange(wex, selectedExchange); - const withdrawInfo = await getBankWithdrawalInfo( - wex.http, - req.talerWithdrawUri, - ); - const exchangePaytoUri = await getExchangePaytoUri( - wex, - selectedExchange, - withdrawInfo.wireTypes, - ); + const resp = await prepareBankIntegratedWithdrawal(wex, { + talerWithdrawUri: req.talerWithdrawUri, + selectedExchange: req.selectedExchange, + }); - let amount: AmountJson; - if (withdrawInfo.amount == null) { - if (req.amount == null) { - throw Error( - "amount required, as withdrawal operation has flexible amount", - ); - } - amount = Amounts.parseOrThrow(req.amount); - } else { - if ( - req.amount != null && - Amounts.cmp(req.amount, withdrawInfo.amount) != 0 - ) { - throw Error( - "mismatched amount, amount is fixed by bank but client provided different amount", - ); - } - amount = withdrawInfo.amount; + const transactionId = resp.transactionId; + if (!transactionId) { + throw Error("internal error, no transaction ID"); } - const withdrawalAccountList = await fetchWithdrawalAccountInfo( - wex, - { - exchange, - instructedAmount: amount, - }, - CancellationToken.CONTINUE, - ); - - const withdrawalGroup = await internalCreateWithdrawalGroup(wex, { - amount, + await confirmWithdrawal(wex, { + transactionId, + amount: req.amount == null ? undefined : Amounts.stringify(req.amount), exchangeBaseUrl: req.selectedExchange, - wgInfo: { - withdrawalType: WithdrawalRecordType.BankIntegrated, - exchangeCreditAccounts: withdrawalAccountList, - bankInfo: { - exchangePaytoUri, - talerWithdrawUri: req.talerWithdrawUri, - confirmUrl: withdrawInfo.confirmTransferUrl, - timestampBankConfirmed: undefined, - timestampReserveInfoPosted: undefined, - wireTypes: withdrawInfo.wireTypes, - }, - }, - restrictAge: req.restrictAge, forcedDenomSel: req.forcedDenomSel, - reserveStatus: WithdrawalGroupStatus.PendingRegisteringBank, + restrictAge: req.restrictAge, }); - const withdrawalGroupId = withdrawalGroup.withdrawalGroupId; + const parsedTx = parseTransactionIdentifier(transactionId); + checkLogicInvariant(parsedTx?.tag === TransactionType.Withdrawal); - const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId); + const ctx = new WithdrawTransactionContext(wex, parsedTx.withdrawalGroupId); - wex.ws.notify({ - type: NotificationType.BalanceChange, - hintTransactionId: ctx.transactionId, - }); + await waitWithdrawalRegistered(wex, ctx); - wex.taskScheduler.startShepherdTask(ctx.taskId); + const withdrawalGroup = await wex.db.runReadOnlyTx( + { + storeNames: ["withdrawalGroups"], + }, + async (tx) => { + return tx.withdrawalGroups.get(parsedTx.withdrawalGroupId); + }, + ); - await waitWithdrawalRegistered(wex, ctx); + if (!withdrawalGroup) { + throw Error("withdrawal group does not exist anymore"); + } + + if ( + withdrawalGroup.wgInfo.withdrawalType !== + WithdrawalRecordType.BankIntegrated + ) { + throw Error("withdrawal group has unexpected withdrawal type"); + } return { reservePub: withdrawalGroup.reservePub, - confirmTransferUrl: withdrawInfo.confirmTransferUrl, + confirmTransferUrl: withdrawalGroup.wgInfo.bankInfo.confirmUrl, transactionId: ctx.transactionId, }; } -- cgit v1.2.3 From ec53ef04777cc1047079285538330de996e7e0f2 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Thu, 23 May 2024 22:44:52 +0200 Subject: Revert "wallet-core: implement acceptBankIntegratedWithdrawal via prepare/confirm step" This reverts commit 274b72f6ea4ac92e334b97a9cc427d64b2307217. --- packages/taler-wallet-core/src/versions.ts | 2 +- packages/taler-wallet-core/src/withdraw.ts | 158 ++++++++++++++++++----------- 2 files changed, 97 insertions(+), 63 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/versions.ts b/packages/taler-wallet-core/src/versions.ts index b89ff16f6..d33a23cdd 100644 --- a/packages/taler-wallet-core/src/versions.ts +++ b/packages/taler-wallet-core/src/versions.ts @@ -52,7 +52,7 @@ export const WALLET_BANK_CONVERSION_API_PROTOCOL_VERSION = "2:0:0"; /** * Libtool version of the wallet-core API. */ -export const WALLET_CORE_API_PROTOCOL_VERSION = "6:0:0"; +export const WALLET_CORE_API_PROTOCOL_VERSION = "5:0:0"; /** * Libtool rules: diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index 7f43b4333..1dc4e0999 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -2953,7 +2953,6 @@ export async function prepareBankIntegratedWithdrawal( }, }, reserveStatus: WithdrawalGroupStatus.DialogProposed, - amount: info.amount == null ? undefined : Amounts.parseOrThrow(info.amount), }); const withdrawalGroupId = withdrawalGroup.withdrawalGroupId; @@ -3000,25 +2999,6 @@ export async function confirmWithdrawal( const talerWithdrawUri = withdrawalGroup.wgInfo.bankInfo.talerWithdrawUri; const confirmUrl = withdrawalGroup.wgInfo.bankInfo.confirmUrl; - let amount: AmountString; - - if (withdrawalGroup.instructedAmount == null) { - if (req.amount == null) { - throw Error( - "neither the withdrawal group nor the request specifies an amount", - ); - } - amount = req.amount; - } else { - if ( - req.amount != null && - Amounts.cmp(withdrawalGroup.instructedAmount, req.amount) != 0 - ) { - throw Error("conflicting amount"); - } - amount = withdrawalGroup.instructedAmount; - } - /** * The only reason this could be undefined is because it is an old wallet * database before adding the wireType field was added @@ -3044,7 +3024,7 @@ export async function confirmWithdrawal( wex, { exchange, - instructedAmount: Amounts.parseOrThrow(amount), + instructedAmount: Amounts.parseOrThrow(req.amount), }, wex.cancellationToken, ); @@ -3056,7 +3036,7 @@ export async function confirmWithdrawal( const initalDenoms = await getInitialDenomsSelection( wex, req.exchangeBaseUrl, - Amounts.parseOrThrow(amount), + Amounts.parseOrThrow(req.amount), req.forcedDenomSel, ); @@ -3067,7 +3047,7 @@ export async function confirmWithdrawal( switch (rec.status) { case WithdrawalGroupStatus.DialogProposed: { rec.exchangeBaseUrl = req.exchangeBaseUrl; - rec.instructedAmount = amount; + rec.instructedAmount = req.amount; rec.denomsSel = initalDenoms; rec.rawWithdrawalAmount = initalDenoms.totalWithdrawCost; rec.effectiveWithdrawalAmount = initalDenoms.totalCoinValue; @@ -3094,11 +3074,6 @@ export async function confirmWithdrawal( } }); - wex.ws.notify({ - type: NotificationType.BalanceChange, - hintTransactionId: ctx.transactionId, - }); - await wex.taskScheduler.resetTaskRetries(ctx.taskId); } @@ -3122,54 +3097,113 @@ export async function acceptWithdrawalFromUri( amount?: AmountLike; }, ): Promise { - const resp = await prepareBankIntegratedWithdrawal(wex, { - talerWithdrawUri: req.talerWithdrawUri, - selectedExchange: req.selectedExchange, - }); + const selectedExchange = req.selectedExchange; + logger.info( + `accepting withdrawal via ${req.talerWithdrawUri}, canonicalized selected exchange ${selectedExchange}`, + ); + const existingWithdrawalGroup = await wex.db.runReadOnlyTx( + { storeNames: ["withdrawalGroups"] }, + async (tx) => { + return await tx.withdrawalGroups.indexes.byTalerWithdrawUri.get( + req.talerWithdrawUri, + ); + }, + ); - const transactionId = resp.transactionId; - if (!transactionId) { - throw Error("internal error, no transaction ID"); + if (existingWithdrawalGroup) { + let url: string | undefined; + if ( + existingWithdrawalGroup.wgInfo.withdrawalType === + WithdrawalRecordType.BankIntegrated + ) { + url = existingWithdrawalGroup.wgInfo.bankInfo.confirmUrl; + } + return { + reservePub: existingWithdrawalGroup.reservePub, + confirmTransferUrl: url, + transactionId: constructTransactionIdentifier({ + tag: TransactionType.Withdrawal, + withdrawalGroupId: existingWithdrawalGroup.withdrawalGroupId, + }), + }; } - await confirmWithdrawal(wex, { - transactionId, - amount: req.amount == null ? undefined : Amounts.stringify(req.amount), + const exchange = await fetchFreshExchange(wex, selectedExchange); + const withdrawInfo = await getBankWithdrawalInfo( + wex.http, + req.talerWithdrawUri, + ); + const exchangePaytoUri = await getExchangePaytoUri( + wex, + selectedExchange, + withdrawInfo.wireTypes, + ); + + let amount: AmountJson; + if (withdrawInfo.amount == null) { + if (req.amount == null) { + throw Error( + "amount required, as withdrawal operation has flexible amount", + ); + } + amount = Amounts.parseOrThrow(req.amount); + } else { + if ( + req.amount != null && + Amounts.cmp(req.amount, withdrawInfo.amount) != 0 + ) { + throw Error( + "mismatched amount, amount is fixed by bank but client provided different amount", + ); + } + amount = withdrawInfo.amount; + } + + const withdrawalAccountList = await fetchWithdrawalAccountInfo( + wex, + { + exchange, + instructedAmount: amount, + }, + CancellationToken.CONTINUE, + ); + + const withdrawalGroup = await internalCreateWithdrawalGroup(wex, { + amount, exchangeBaseUrl: req.selectedExchange, - forcedDenomSel: req.forcedDenomSel, + wgInfo: { + withdrawalType: WithdrawalRecordType.BankIntegrated, + exchangeCreditAccounts: withdrawalAccountList, + bankInfo: { + exchangePaytoUri, + talerWithdrawUri: req.talerWithdrawUri, + confirmUrl: withdrawInfo.confirmTransferUrl, + timestampBankConfirmed: undefined, + timestampReserveInfoPosted: undefined, + wireTypes: withdrawInfo.wireTypes, + }, + }, restrictAge: req.restrictAge, + forcedDenomSel: req.forcedDenomSel, + reserveStatus: WithdrawalGroupStatus.PendingRegisteringBank, }); - const parsedTx = parseTransactionIdentifier(transactionId); - checkLogicInvariant(parsedTx?.tag === TransactionType.Withdrawal); - - const ctx = new WithdrawTransactionContext(wex, parsedTx.withdrawalGroupId); + const withdrawalGroupId = withdrawalGroup.withdrawalGroupId; - await waitWithdrawalRegistered(wex, ctx); + const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId); - const withdrawalGroup = await wex.db.runReadOnlyTx( - { - storeNames: ["withdrawalGroups"], - }, - async (tx) => { - return tx.withdrawalGroups.get(parsedTx.withdrawalGroupId); - }, - ); + wex.ws.notify({ + type: NotificationType.BalanceChange, + hintTransactionId: ctx.transactionId, + }); - if (!withdrawalGroup) { - throw Error("withdrawal group does not exist anymore"); - } + wex.taskScheduler.startShepherdTask(ctx.taskId); - if ( - withdrawalGroup.wgInfo.withdrawalType !== - WithdrawalRecordType.BankIntegrated - ) { - throw Error("withdrawal group has unexpected withdrawal type"); - } + await waitWithdrawalRegistered(wex, ctx); return { reservePub: withdrawalGroup.reservePub, - confirmTransferUrl: withdrawalGroup.wgInfo.bankInfo.confirmUrl, + confirmTransferUrl: withdrawInfo.confirmTransferUrl, transactionId: ctx.transactionId, }; } -- cgit v1.2.3 From 9fe8bfbc9f0fa8d5d21e62928175a74e4e50213d Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 27 May 2024 11:38:18 +0200 Subject: wallet-core: requests don't need a running shepherd --- packages/taler-wallet-core/src/wallet.ts | 2 -- 1 file changed, 2 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 3455d451b..71e8c652b 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -1643,8 +1643,6 @@ async function handleCoreApiRequest( if (!ws.initCalled) { throw Error("init must be called first"); } - // Might be lazily initialized! - await ws.taskScheduler.ensureRunning(); } let wex: WalletExecutionContext; -- cgit v1.2.3 From bce10929f3b51fa965ff57b61bea0cbb84426da4 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 27 May 2024 21:19:44 +0200 Subject: wallet-core: test-only request to reset all retries --- packages/taler-wallet-core/src/transactions.ts | 5 ++++- packages/taler-wallet-core/src/wallet-api-types.ts | 13 ++++++++++++- packages/taler-wallet-core/src/wallet.ts | 6 +++++- 3 files changed, 21 insertions(+), 3 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/transactions.ts b/packages/taler-wallet-core/src/transactions.ts index 9a9fb524f..1adfa5425 100644 --- a/packages/taler-wallet-core/src/transactions.ts +++ b/packages/taler-wallet-core/src/transactions.ts @@ -93,7 +93,6 @@ import { computeDenomLossTransactionStatus, DenomLossTransactionContext, ExchangeWireDetails, - fetchFreshExchange, getExchangeWireDetailsInTx, } from "./exchanges.js"; import { @@ -1772,7 +1771,11 @@ export async function retryTransaction( } } +/** + * Reset the task retry counter for all tasks. + */ export async function retryAll(wex: WalletExecutionContext): Promise { + await wex.taskScheduler.ensureRunning(); const tasks = wex.taskScheduler.getActiveTasks(); for (const task of tasks) { await wex.taskScheduler.resetTaskRetries(task); diff --git a/packages/taler-wallet-core/src/wallet-api-types.ts b/packages/taler-wallet-core/src/wallet-api-types.ts index 1bcab801c..aa88331ea 100644 --- a/packages/taler-wallet-core/src/wallet-api-types.ts +++ b/packages/taler-wallet-core/src/wallet-api-types.ts @@ -123,7 +123,6 @@ import { StartRefundQueryForUriResponse, StartRefundQueryRequest, StoredBackupList, - TalerMerchantApi, TestPayArgs, TestPayResult, TestingGetDenomStatsRequest, @@ -277,6 +276,7 @@ export enum WalletApiOperation { TestingGetDenomStats = "testingGetDenomStats", TestingPing = "testingPing", TestingGetReserveHistory = "testingGetReserveHistory", + TestingResetAllRetries = "testingResetAllRetries", } // group: Initialization @@ -1212,6 +1212,16 @@ export type TestingGetReserveHistoryOp = { response: any; }; +/** + * Reset all task/transaction retries, + * resulting in immediate re-try of all operations. + */ +export type TestingResetAllRetriesOp = { + op: WalletApiOperation.TestingResetAllRetries; + request: EmptyObject; + response: EmptyObject; +}; + /** * Get stats about an exchange denomination. */ @@ -1356,6 +1366,7 @@ export type WalletOperations = { [WalletApiOperation.ConfirmWithdrawal]: ConfirmWithdrawalOp; [WalletApiOperation.CanonicalizeBaseUrl]: CanonicalizeBaseUrlOp; [WalletApiOperation.TestingGetReserveHistory]: TestingGetReserveHistoryOp; + [WalletApiOperation.TestingResetAllRetries]: TestingResetAllRetriesOp; [WalletApiOperation.HintNetworkAvailability]: HintNetworkAvailabilityOp; }; diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 71e8c652b..f301ca3a7 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -266,6 +266,7 @@ import { TaskScheduler, TaskSchedulerImpl, convertTaskToTransactionId, + getActiveTaskIds, listTaskForTransactionId, } from "./shepherd.js"; import { @@ -1093,7 +1094,7 @@ async function dispatchRequestInternal( return {}; } case WalletApiOperation.GetActiveTasks: { - const allTasksId = wex.taskScheduler.getActiveTasks(); + const allTasksId = (await getActiveTaskIds(wex.ws)).taskIds; const tasksInfo = await Promise.all( allTasksId.map(async (id) => { @@ -1435,6 +1436,9 @@ async function dispatchRequestInternal( await waitTasksDone(wex); return {}; } + case WalletApiOperation.TestingResetAllRetries: + await retryAll(wex); + return {}; case WalletApiOperation.RemoveGlobalCurrencyAuditor: { const req = codecForRemoveGlobalCurrencyAuditorRequest().decode(payload); await wex.db.runReadWriteTx( -- cgit v1.2.3 From 883788565a968cbbaab04678c9d2ce92b7bdc432 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 27 May 2024 21:37:05 +0200 Subject: wallet-core: ensure DB is open before handling request --- packages/taler-wallet-core/src/wallet.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index f301ca3a7..86ea7bb4c 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -1649,6 +1649,8 @@ async function handleCoreApiRequest( } } + await ws.ensureWalletDbOpen(); + let wex: WalletExecutionContext; let oc: ObservabilityContext; -- cgit v1.2.3 From de96059ad067cc4a28518db47d27889107b22232 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 27 May 2024 21:48:49 +0200 Subject: wallet-core: rethrow exception --- packages/taler-wallet-core/src/common.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts index edaba5ba4..755c46188 100644 --- a/packages/taler-wallet-core/src/common.ts +++ b/packages/taler-wallet-core/src/common.ts @@ -819,5 +819,6 @@ export async function genericWaitForState( } catch (e) { unregisterOnCancelled(); cancelNotif(); + throw e; } } -- cgit v1.2.3 From db45b25fe871de42a68a4303b6eb31aa6da64f9b Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 27 May 2024 21:54:31 +0200 Subject: wallet-core: also check EC of melt response before aborting --- packages/taler-wallet-core/src/refresh.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts index 7800967e6..38b8b097c 100644 --- a/packages/taler-wallet-core/src/refresh.ts +++ b/packages/taler-wallet-core/src/refresh.ts @@ -68,6 +68,7 @@ import { WalletNotification, } from "@gnu-taler/taler-util"; import { + HttpResponse, readSuccessResponseJsonOrThrow, readTalerErrorResponse, throwUnexpectedRequestError, @@ -693,7 +694,7 @@ async function refreshMelt( switch (resp.status) { case HttpStatusCode.NotFound: { const errDetail = await readTalerErrorResponse(resp); - await handleRefreshMeltNotFound(ctx, coinIndex, errDetail); + await handleRefreshMeltNotFound(ctx, coinIndex, resp, errDetail); return; } case HttpStatusCode.Gone: { @@ -898,9 +899,17 @@ async function handleRefreshMeltConflict( async function handleRefreshMeltNotFound( ctx: RefreshTransactionContext, coinIndex: number, + resp: HttpResponse, errDetails: TalerErrorDetail, ): Promise { - // FIXME: Validate the exchange's error response + // Make sure that we only act on a 404 that indicates a final problem + // with the coin. + switch (errDetails.code) { + case TalerErrorCode.EXCHANGE_GENERIC_COIN_UNKNOWN: + break; + default: + throwUnexpectedRequestError(resp, errDetails); + } await ctx.wex.db.runReadWriteTx( { storeNames: [ -- cgit v1.2.3 From 02ceacd75fa16889925c8eda6b28f181b2ad392c Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 28 May 2024 10:37:58 -0300 Subject: fix #8828 --- packages/taler-wallet-core/src/backup/index.ts | 16 +++++-- packages/taler-wallet-core/src/coinSelection.ts | 5 ++- packages/taler-wallet-core/src/common.ts | 51 +++++++++++----------- packages/taler-wallet-core/src/db.ts | 4 +- packages/taler-wallet-core/src/deposits.ts | 2 +- packages/taler-wallet-core/src/exchanges.ts | 2 +- .../src/instructedAmountConversion.ts | 22 +++++++--- packages/taler-wallet-core/src/pay-merchant.ts | 8 ++-- packages/taler-wallet-core/src/pay-peer-common.ts | 6 +-- .../taler-wallet-core/src/pay-peer-pull-credit.ts | 2 +- .../taler-wallet-core/src/pay-peer-push-credit.ts | 2 +- .../taler-wallet-core/src/pay-peer-push-debit.ts | 2 +- packages/taler-wallet-core/src/recoup.ts | 4 +- packages/taler-wallet-core/src/refresh.ts | 14 +++--- packages/taler-wallet-core/src/transactions.ts | 22 +++++----- packages/taler-wallet-core/src/wallet.ts | 10 ++--- packages/taler-wallet-core/src/withdraw.ts | 4 +- 17 files changed, 95 insertions(+), 81 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/backup/index.ts b/packages/taler-wallet-core/src/backup/index.ts index 15904b470..09d5ae75d 100644 --- a/packages/taler-wallet-core/src/backup/index.ts +++ b/packages/taler-wallet-core/src/backup/index.ts @@ -805,9 +805,10 @@ async function backupRecoveryTheirs( let backupStateEntry: ConfigRecord | undefined = await tx.config.get( ConfigRecordKey.WalletBackupState, ); - checkDbInvariant(!!backupStateEntry); + checkDbInvariant(!!backupStateEntry, `no backup entry`); checkDbInvariant( backupStateEntry.key === ConfigRecordKey.WalletBackupState, + `backup entry inconsistent`, ); backupStateEntry.value.lastBackupNonce = undefined; backupStateEntry.value.lastBackupTimestamp = undefined; @@ -913,7 +914,10 @@ export async function provideBackupState( }, ); if (bs) { - checkDbInvariant(bs.key === ConfigRecordKey.WalletBackupState); + checkDbInvariant( + bs.key === ConfigRecordKey.WalletBackupState, + `backup entry inconsistent`, + ); return bs.value; } // We need to generate the key outside of the transaction @@ -941,6 +945,7 @@ export async function provideBackupState( } checkDbInvariant( backupStateEntry.key === ConfigRecordKey.WalletBackupState, + `backup entry inconsistent`, ); return backupStateEntry.value; }); @@ -952,7 +957,10 @@ export async function getWalletBackupState( ): Promise { const bs = await tx.config.get(ConfigRecordKey.WalletBackupState); checkDbInvariant(!!bs, "wallet backup state should be in DB"); - checkDbInvariant(bs.key === ConfigRecordKey.WalletBackupState); + checkDbInvariant( + bs.key === ConfigRecordKey.WalletBackupState, + `backup entry inconsistent`, + ); return bs.value; } @@ -962,7 +970,7 @@ export async function setWalletDeviceId( ): Promise { await provideBackupState(wex); await wex.db.runReadWriteTx({ storeNames: ["config"] }, async (tx) => { - let backupStateEntry: ConfigRecord | undefined = await tx.config.get( + const backupStateEntry: ConfigRecord | undefined = await tx.config.get( ConfigRecordKey.WalletBackupState, ); if ( diff --git a/packages/taler-wallet-core/src/coinSelection.ts b/packages/taler-wallet-core/src/coinSelection.ts index a60e41ecd..db6384c93 100644 --- a/packages/taler-wallet-core/src/coinSelection.ts +++ b/packages/taler-wallet-core/src/coinSelection.ts @@ -691,7 +691,7 @@ export function checkAccountRestriction( switch (myRestriction.type) { case "deny": return { ok: false }; - case "regex": + case "regex": { const regex = new RegExp(myRestriction.payto_regex); if (!regex.test(paytoUri)) { return { @@ -700,6 +700,7 @@ export function checkAccountRestriction( hintI18n: myRestriction.human_hint_i18n, }; } + } } } return { @@ -909,7 +910,7 @@ async function selectPayCandidates( coinAvail.exchangeBaseUrl, coinAvail.denomPubHash, ]); - checkDbInvariant(!!denom); + checkDbInvariant(!!denom, `denomination of a coin is missing hash: ${coinAvail.denomPubHash}`); if (denom.isRevoked) { logger.trace("denom is revoked"); continue; diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts index 755c46188..00d462d6f 100644 --- a/packages/taler-wallet-core/src/common.ts +++ b/packages/taler-wallet-core/src/common.ts @@ -121,7 +121,7 @@ export async function makeCoinAvailable( coinRecord.exchangeBaseUrl, coinRecord.denomPubHash, ]); - checkDbInvariant(!!denom); + checkDbInvariant(!!denom, `denomination of a coin is missing hash: ${coinRecord.denomPubHash}`); const ageRestriction = coinRecord.maxAge; let car = await tx.coinAvailability.get([ coinRecord.exchangeBaseUrl, @@ -175,13 +175,13 @@ export async function spendCoins( coin.exchangeBaseUrl, coin.denomPubHash, ); - checkDbInvariant(!!denom); + checkDbInvariant(!!denom, `denomination of a coin is missing hash: ${coin.denomPubHash}`); const coinAvailability = await tx.coinAvailability.get([ coin.exchangeBaseUrl, coin.denomPubHash, coin.maxAge, ]); - checkDbInvariant(!!coinAvailability); + checkDbInvariant(!!coinAvailability, `age denom info is missing for ${coin.maxAge}`); const contrib = csi.contributions[i]; if (coin.status !== CoinStatus.Fresh) { const alloc = coin.spendAllocation; @@ -213,7 +213,6 @@ export async function spendCoins( amount: Amounts.stringify(remaining.amount), coinPub: coin.coinPub, }); - checkDbInvariant(!!coinAvailability); if (coinAvailability.freshCoinCount === 0) { throw Error( `invalid coin count ${coinAvailability.freshCoinCount} in DB`, @@ -557,6 +556,28 @@ export function getAutoRefreshExecuteThreshold(d: { return AbsoluteTime.addDuration(expireWithdraw, deltaDiv); } +/** + * Type and schema definitions for pending tasks in the wallet. + * + * These are only used internally, and are not part of the stable public + * interface to the wallet. + */ + +export enum PendingTaskType { + ExchangeUpdate = "exchange-update", + Purchase = "purchase", + Refresh = "refresh", + Recoup = "recoup", + RewardPickup = "reward-pickup", + Withdraw = "withdraw", + Deposit = "deposit", + Backup = "backup", + PeerPushDebit = "peer-push-debit", + PeerPullCredit = "peer-pull-credit", + PeerPushCredit = "peer-push-credit", + PeerPullDebit = "peer-pull-debit", +} + /** * Parsed representation of task identifiers. */ @@ -747,28 +768,6 @@ export interface TransactionContext { deleteTransaction(): Promise; } -/** - * Type and schema definitions for pending tasks in the wallet. - * - * These are only used internally, and are not part of the stable public - * interface to the wallet. - */ - -export enum PendingTaskType { - ExchangeUpdate = "exchange-update", - Purchase = "purchase", - Refresh = "refresh", - Recoup = "recoup", - RewardPickup = "reward-pickup", - Withdraw = "withdraw", - Deposit = "deposit", - Backup = "backup", - PeerPushDebit = "peer-push-debit", - PeerPullCredit = "peer-pull-credit", - PeerPushCredit = "peer-push-credit", - PeerPullDebit = "peer-pull-debit", -} - declare const __taskIdStr: unique symbol; export type TaskIdStr = string & { [__taskIdStr]: true }; diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 640d94753..ad9b4f1cb 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -2941,6 +2941,8 @@ export interface DbDump { }; } +const logger = new Logger("db.ts"); + export async function exportSingleDb( idb: IDBFactory, dbName: string, @@ -3082,8 +3084,6 @@ export interface FixupDescription { */ export const walletDbFixups: FixupDescription[] = []; -const logger = new Logger("db.ts"); - export async function applyFixups( db: DbAccess, ): Promise { diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts index c4cd98d73..32b22c1ef 100644 --- a/packages/taler-wallet-core/src/deposits.ts +++ b/packages/taler-wallet-core/src/deposits.ts @@ -441,7 +441,7 @@ async function refundDepositGroup( { storeNames: ["coins"] }, async (tx) => { const coinRecord = await tx.coins.get(coinPub); - checkDbInvariant(!!coinRecord); + checkDbInvariant(!!coinRecord, `coin ${coinPub} not found in DB`); return coinRecord.exchangeBaseUrl; }, ); diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts index d8063d561..fa3146c38 100644 --- a/packages/taler-wallet-core/src/exchanges.ts +++ b/packages/taler-wallet-core/src/exchanges.ts @@ -1548,7 +1548,7 @@ export async function updateExchangeFromUrlHandler( r.cachebreakNextUpdate = false; await tx.exchanges.put(r); const drRowId = await tx.exchangeDetails.put(newDetails); - checkDbInvariant(typeof drRowId.key === "number"); + checkDbInvariant(typeof drRowId.key === "number", "exchange details key is not a number"); for (const sk of keysInfo.signingKeys) { // FIXME: validate signing keys before inserting them diff --git a/packages/taler-wallet-core/src/instructedAmountConversion.ts b/packages/taler-wallet-core/src/instructedAmountConversion.ts index 1f7d95959..5b399a0a7 100644 --- a/packages/taler-wallet-core/src/instructedAmountConversion.ts +++ b/packages/taler-wallet-core/src/instructedAmountConversion.ts @@ -283,7 +283,7 @@ async function getAvailableDenoms( coinAvail.exchangeBaseUrl, coinAvail.denomPubHash, ]); - checkDbInvariant(!!denom); + checkDbInvariant(!!denom, `denomination of a coin is missing hash: ${coinAvail.denomPubHash}`); if (denom.isRevoked || !denom.isOffered) { continue; } @@ -472,7 +472,7 @@ export async function getMaxDepositAmount( export function getMaxDepositAmountForAvailableCoins( denoms: AvailableCoins, currency: string, -) { +): AmountWithFee { const zero = Amounts.zeroOfCurrency(currency); if (!denoms.list.length) { // no coins in the database @@ -663,8 +663,13 @@ function rankDenominationForWithdrawals( //different exchanges may have different wireFee //ranking should take the relative contribution in the exchange //which is (value - denomFee / fixedFee) - const rate1 = Amounts.divmod(d1.value, d1.denomWithdraw).quotient; - const rate2 = Amounts.divmod(d2.value, d2.denomWithdraw).quotient; + + const rate1 = Amounts.isZero(d1.denomWithdraw) + ? Number.MIN_SAFE_INTEGER + : Amounts.divmod(d1.value, d1.denomWithdraw).quotient; + const rate2 = Amounts.isZero(d2.denomWithdraw) + ? Number.MIN_SAFE_INTEGER + : Amounts.divmod(d2.value, d2.denomWithdraw).quotient; const contribCmp = rate1 === rate2 ? 0 : rate1 < rate2 ? 1 : -1; return ( contribCmp || @@ -719,8 +724,13 @@ function rankDenominationForDeposit( //different exchanges may have different wireFee //ranking should take the relative contribution in the exchange //which is (value - denomFee / fixedFee) - const rate1 = Amounts.divmod(d1.value, d1.denomDeposit).quotient; - const rate2 = Amounts.divmod(d2.value, d2.denomDeposit).quotient; + const rate1 = Amounts.isZero(d1.denomDeposit) + ? Number.MIN_SAFE_INTEGER + : Amounts.divmod(d1.value, d1.denomDeposit).quotient; + const rate2 = Amounts.isZero(d2.denomDeposit) + ? Number.MIN_SAFE_INTEGER + : Amounts.divmod(d2.value, d2.denomDeposit).quotient; + const contribCmp = rate1 === rate2 ? 0 : rate1 < rate2 ? 1 : -1; return ( contribCmp || diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts index 090a11cf0..aa4919285 100644 --- a/packages/taler-wallet-core/src/pay-merchant.ts +++ b/packages/taler-wallet-core/src/pay-merchant.ts @@ -1028,11 +1028,11 @@ async function storeFirstPaySuccess( purchase.merchantPaySig = payResponse.sig; purchase.posConfirmation = payResponse.pos_confirmation; const dl = purchase.download; - checkDbInvariant(!!dl); + checkDbInvariant(!!dl, `purchase ${purchase.orderId} without ct downloaded`); const contractTermsRecord = await tx.contractTerms.get( dl.contractTermsHash, ); - checkDbInvariant(!!contractTermsRecord); + checkDbInvariant(!!contractTermsRecord, `no contract terms found for purchase ${purchase.orderId}`); const contractData = extractContractData( contractTermsRecord.contractTermsRaw, dl.contractTermsHash, @@ -2155,7 +2155,7 @@ async function processPurchasePay( logger.trace(`paying with session ID ${sessionId}`); const payInfo = purchase.payInfo; - checkDbInvariant(!!payInfo, "payInfo"); + checkDbInvariant(!!payInfo, `purchase ${purchase.orderId} without payInfo`); const download = await expectProposalDownload(wex, purchase); @@ -2997,7 +2997,7 @@ async function processPurchaseAbortingRefund( for (let i = 0; i < payCoinSelection.coinPubs.length; i++) { const coinPub = payCoinSelection.coinPubs[i]; const coin = await tx.coins.get(coinPub); - checkDbInvariant(!!coin, "expected coin to be present"); + checkDbInvariant(!!coin, `coin not found for ${coinPub}`); abortingCoins.push({ coin_pub: coinPub, contribution: Amounts.stringify(payCoinSelection.coinContributions[i]), diff --git a/packages/taler-wallet-core/src/pay-peer-common.ts b/packages/taler-wallet-core/src/pay-peer-common.ts index bfd39b657..a1729ced7 100644 --- a/packages/taler-wallet-core/src/pay-peer-common.ts +++ b/packages/taler-wallet-core/src/pay-peer-common.ts @@ -140,10 +140,10 @@ export async function getMergeReserveInfo( { storeNames: ["exchanges", "reserves"] }, async (tx) => { const ex = await tx.exchanges.get(req.exchangeBaseUrl); - checkDbInvariant(!!ex); + checkDbInvariant(!!ex, `no exchange record for ${req.exchangeBaseUrl}`); if (ex.currentMergeReserveRowId != null) { const reserve = await tx.reserves.get(ex.currentMergeReserveRowId); - checkDbInvariant(!!reserve); + checkDbInvariant(!!reserve, `reserver ${ex.currentMergeReserveRowId} missing in db`); return reserve; } const reserve: ReserveRecord = { @@ -151,7 +151,7 @@ export async function getMergeReserveInfo( reservePub: newReservePair.pub, }; const insertResp = await tx.reserves.put(reserve); - checkDbInvariant(typeof insertResp.key === "number"); + checkDbInvariant(typeof insertResp.key === "number", `reserve key is not a number`); reserve.rowId = insertResp.key; ex.currentMergeReserveRowId = reserve.rowId; await tx.exchanges.put(ex); diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts index 840c244d0..14b3eeaf0 100644 --- a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts +++ b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts @@ -1039,7 +1039,7 @@ export async function initiatePeerPullPayment( const withdrawalGroupId = encodeCrock(getRandomBytes(32)); const mergeReserveRowId = mergeReserveInfo.rowId; - checkDbInvariant(!!mergeReserveRowId); + checkDbInvariant(!!mergeReserveRowId, `merge reserve for ${exchangeBaseUrl} without rowid`); const contractEncNonce = encodeCrock(getRandomBytes(24)); diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts index 93f1a63a7..1476a0f4b 100644 --- a/packages/taler-wallet-core/src/pay-peer-push-credit.ts +++ b/packages/taler-wallet-core/src/pay-peer-push-credit.ts @@ -872,7 +872,7 @@ export async function processPeerPushCredit( `processing peerPushCredit in state ${peerInc.status.toString(16)}`, ); - checkDbInvariant(!!contractTerms); + checkDbInvariant(!!contractTerms, `not contract terms for peer push ${peerPushCreditId}`); switch (peerInc.status) { case PeerPushCreditStatus.PendingMergeKycRequired: { diff --git a/packages/taler-wallet-core/src/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/pay-peer-push-debit.ts index 6452407ff..3a936fb04 100644 --- a/packages/taler-wallet-core/src/pay-peer-push-debit.ts +++ b/packages/taler-wallet-core/src/pay-peer-push-debit.ts @@ -406,7 +406,7 @@ async function handlePurseCreationConflict( const instructedAmount = Amounts.parseOrThrow(peerPushInitiation.amount); const sel = peerPushInitiation.coinSel; - checkDbInvariant(!!sel); + checkDbInvariant(!!sel, `no coin selected for peer push initiation ${peerPushInitiation.pursePub}`); const repair: PreviousPayCoins = []; diff --git a/packages/taler-wallet-core/src/recoup.ts b/packages/taler-wallet-core/src/recoup.ts index 6a09f9a0e..be5731b0b 100644 --- a/packages/taler-wallet-core/src/recoup.ts +++ b/packages/taler-wallet-core/src/recoup.ts @@ -199,8 +199,8 @@ async function recoupRefreshCoin( revokedCoin.exchangeBaseUrl, revokedCoin.denomPubHash, ); - checkDbInvariant(!!oldCoinDenom); - checkDbInvariant(!!revokedCoinDenom); + checkDbInvariant(!!oldCoinDenom, `no denom for coin, hash ${oldCoin.denomPubHash}`); + checkDbInvariant(!!revokedCoinDenom, `no revoked denom for coin, hash ${revokedCoin.denomPubHash}`); revokedCoin.status = CoinStatus.Dormant; if (!revokedCoin.spendAllocation) { // We don't know what happened to this coin diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts index 38b8b097c..f160e0731 100644 --- a/packages/taler-wallet-core/src/refresh.ts +++ b/packages/taler-wallet-core/src/refresh.ts @@ -387,7 +387,6 @@ async function getCoinAvailabilityForDenom( denom: DenominationInfo, ageRestriction: number, ): Promise { - checkDbInvariant(!!denom); let car = await tx.coinAvailability.get([ denom.exchangeBaseUrl, denom.denomPubHash, @@ -538,7 +537,7 @@ async function destroyRefreshSession( denom, oldCoin.maxAge, ); - checkDbInvariant(car.pendingRefreshOutputCount != null); + checkDbInvariant(car.pendingRefreshOutputCount != null, `no pendingRefreshOutputCount for denom ${dph}`); car.pendingRefreshOutputCount = car.pendingRefreshOutputCount - refreshSession.newDenoms[i].count; await tx.coinAvailability.put(car); @@ -1251,7 +1250,7 @@ async function refreshReveal( coin.exchangeBaseUrl, coin.denomPubHash, ); - checkDbInvariant(!!denomInfo); + checkDbInvariant(!!denomInfo, `no denom with hash ${coin.denomPubHash}`); const car = await getCoinAvailabilityForDenom( wex, tx, @@ -1261,6 +1260,7 @@ async function refreshReveal( checkDbInvariant( car.pendingRefreshOutputCount != null && car.pendingRefreshOutputCount > 0, + `no pendingRefreshOutputCount for denom ${coin.denomPubHash} age ${coin.maxAge}` ); car.pendingRefreshOutputCount--; car.freshCoinCount++; @@ -1568,8 +1568,8 @@ async function applyRefreshToOldCoins( coin.denomPubHash, coin.maxAge, ]); - checkDbInvariant(!!coinAv); - checkDbInvariant(coinAv.freshCoinCount > 0); + checkDbInvariant(!!coinAv, `no denom info for ${coin.denomPubHash} age ${coin.maxAge}`); + checkDbInvariant(coinAv.freshCoinCount > 0, `no fresh coins for ${coin.denomPubHash}`); coinAv.freshCoinCount--; await tx.coinAvailability.put(coinAv); break; @@ -1779,7 +1779,7 @@ export async function forceRefresh( ], }, async (tx) => { - let coinPubs: CoinRefreshRequest[] = []; + const coinPubs: CoinRefreshRequest[] = []; for (const c of req.refreshCoinSpecs) { const coin = await tx.coins.get(c.coinPub); if (!coin) { @@ -1791,7 +1791,7 @@ export async function forceRefresh( coin.exchangeBaseUrl, coin.denomPubHash, ); - checkDbInvariant(!!denom); + checkDbInvariant(!!denom, `no denom hash: ${coin.denomPubHash}`); coinPubs.push({ coinPub: c.coinPub, amount: c.amount ?? denom.value, diff --git a/packages/taler-wallet-core/src/transactions.ts b/packages/taler-wallet-core/src/transactions.ts index 1adfa5425..72aff319a 100644 --- a/packages/taler-wallet-core/src/transactions.ts +++ b/packages/taler-wallet-core/src/transactions.ts @@ -404,7 +404,7 @@ export async function getTransactionById( const debit = await tx.peerPushDebit.get(parsedTx.pursePub); if (!debit) throw Error("not found"); const ct = await tx.contractTerms.get(debit.contractTermsHash); - checkDbInvariant(!!ct); + checkDbInvariant(!!ct, `no contract terms for p2p push ${parsedTx.pursePub}`); return buildTransactionForPushPaymentDebit( debit, ct.contractTermsRaw, @@ -428,7 +428,7 @@ export async function getTransactionById( const pushInc = await tx.peerPushCredit.get(peerPushCreditId); if (!pushInc) throw Error("not found"); const ct = await tx.contractTerms.get(pushInc.contractTermsHash); - checkDbInvariant(!!ct); + checkDbInvariant(!!ct, `no contract terms for p2p push ${peerPushCreditId}`); let wg: WithdrawalGroupRecord | undefined = undefined; let wgOrt: OperationRetryRecord | undefined = undefined; @@ -440,7 +440,7 @@ export async function getTransactionById( } } const pushIncOpId = TaskIdentifiers.forPeerPushCredit(pushInc); - let pushIncOrt = await tx.operationRetries.get(pushIncOpId); + const pushIncOrt = await tx.operationRetries.get(pushIncOpId); return buildTransactionForPeerPushCredit( pushInc, @@ -468,7 +468,7 @@ export async function getTransactionById( const pushInc = await tx.peerPullCredit.get(pursePub); if (!pushInc) throw Error("not found"); const ct = await tx.contractTerms.get(pushInc.contractTermsHash); - checkDbInvariant(!!ct); + checkDbInvariant(!!ct, `no contract terms for p2p push ${pursePub}`); let wg: WithdrawalGroupRecord | undefined = undefined; let wgOrt: OperationRetryRecord | undefined = undefined; @@ -1034,8 +1034,8 @@ function buildTransactionForPurchase( })); const timestamp = purchaseRecord.timestampAccept; - checkDbInvariant(!!timestamp); - checkDbInvariant(!!purchaseRecord.payInfo); + checkDbInvariant(!!timestamp, `purchase ${purchaseRecord.orderId} without accepted time`); + checkDbInvariant(!!purchaseRecord.payInfo, `purchase ${purchaseRecord.orderId} without payinfo`); const txState = computePayMerchantTransactionState(purchaseRecord); return { @@ -1175,7 +1175,7 @@ export async function getTransactions( return; } const ct = await tx.contractTerms.get(pi.contractTermsHash); - checkDbInvariant(!!ct); + checkDbInvariant(!!ct, `no contract terms for p2p push ${pi.pursePub}`); transactions.push( buildTransactionForPushPaymentDebit(pi, ct.contractTermsRaw), ); @@ -1249,9 +1249,9 @@ export async function getTransactions( } } const pushIncOpId = TaskIdentifiers.forPeerPushCredit(pi); - let pushIncOrt = await tx.operationRetries.get(pushIncOpId); + const pushIncOrt = await tx.operationRetries.get(pushIncOpId); - checkDbInvariant(!!ct); + checkDbInvariant(!!ct, `no contract terms for p2p push ${pi.pursePub}`); transactions.push( buildTransactionForPeerPushCredit( pi, @@ -1283,9 +1283,9 @@ export async function getTransactions( } } const pushIncOpId = TaskIdentifiers.forPeerPullPaymentInitiation(pi); - let pushIncOrt = await tx.operationRetries.get(pushIncOpId); + const pushIncOrt = await tx.operationRetries.get(pushIncOpId); - checkDbInvariant(!!ct); + checkDbInvariant(!!ct, `no contract terms for p2p push ${pi.pursePub}`); transactions.push( buildTransactionForPeerPullCredit( pi, diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index 86ea7bb4c..d98106d1f 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -479,7 +479,7 @@ async function setCoinSuspended( c.denomPubHash, c.maxAge, ]); - checkDbInvariant(!!coinAvailability); + checkDbInvariant(!!coinAvailability, `no denom info for ${c.denomPubHash} age ${c.maxAge}`); if (suspended) { if (c.status !== CoinStatus.Fresh) { return; @@ -1063,10 +1063,6 @@ async function dispatchRequestInternal( const req = codecForPreparePayTemplateRequest().decode(payload); return preparePayForTemplate(wex, req); } - case WalletApiOperation.CheckPayForTemplate: { - const req = codecForCheckPayTemplateRequest().decode(payload); - return checkPayForTemplate(wex, req); - } case WalletApiOperation.ConfirmPay: { const req = codecForConfirmPayRequest().decode(payload); let transactionId; @@ -1403,7 +1399,7 @@ async function dispatchRequestInternal( return; } wex.ws.exchangeCache.clear(); - checkDbInvariant(!!existingRec.id); + checkDbInvariant(!!existingRec.id, `no global exchange for ${j2s(key)}`); await tx.globalCurrencyExchanges.delete(existingRec.id); }, ); @@ -1452,7 +1448,7 @@ async function dispatchRequestInternal( if (!existingRec) { return; } - checkDbInvariant(!!existingRec.id); + checkDbInvariant(!!existingRec.id, `no global currency for ${j2s(key)}`); await tx.globalCurrencyAuditors.delete(existingRec.id); wex.ws.exchangeCache.clear(); }, diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index 1dc4e0999..b2cecea16 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -965,7 +965,7 @@ async function processPlanchetGenerate( return getDenomInfo(wex, tx, exchangeBaseUrl, denomPubHash); }, ); - checkDbInvariant(!!denom); + checkDbInvariant(!!denom, `no denom info for ${denomPubHash}`); const r = await wex.cryptoApi.createPlanchet({ denomPub: denom.denomPub, feeWithdraw: Amounts.parseOrThrow(denom.feeWithdraw), @@ -2314,7 +2314,7 @@ export async function getFundingPaytoUris( withdrawalGroupId: string, ): Promise { const withdrawalGroup = await tx.withdrawalGroups.get(withdrawalGroupId); - checkDbInvariant(!!withdrawalGroup); + checkDbInvariant(!!withdrawalGroup, `no withdrawal for ${withdrawalGroupId}`); checkDbInvariant( withdrawalGroup.instructedAmount !== undefined, "can't get funding uri from uninitialized wg", -- cgit v1.2.3 From 0ec928cd8f70de0489904001f5be0130acd46e48 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Tue, 4 Jun 2024 13:39:13 -0300 Subject: more log --- packages/taler-wallet-core/src/exchanges.ts | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts index fa3146c38..13c2ca316 100644 --- a/packages/taler-wallet-core/src/exchanges.ts +++ b/packages/taler-wallet-core/src/exchanges.ts @@ -989,6 +989,8 @@ async function startUpdateExchangeEntry( newExchangeState: newExchangeState, oldExchangeState: oldExchangeState, }); + logger.info(`start update ${exchangeBaseUrl} task ${taskId}`); + await wex.taskScheduler.resetTaskRetries(taskId); } @@ -1155,6 +1157,8 @@ export async function fetchFreshExchange( forceUpdate?: boolean; } = {}, ): Promise { + logger.info(`fetch fresh ${baseUrl} forced ${options.forceUpdate}`); + if (!options.forceUpdate) { const cachedResp = wex.ws.exchangeCache.get(baseUrl); if (cachedResp) { @@ -2227,10 +2231,13 @@ export async function markExchangeUsed( logger.info(`marking exchange ${exchangeBaseUrl} as used`); const exch = await tx.exchanges.get(exchangeBaseUrl); if (!exch) { + logger.info(`exchange ${exchangeBaseUrl} NOT found`); return { notif: undefined, }; } + logger.info(`exchange ${exch.baseUrl} IS IN THE DATABASE`); + const oldExchangeState = getExchangeState(exch); switch (exch.entryStatus) { case ExchangeEntryDbRecordStatus.Ephemeral: @@ -2252,6 +2259,7 @@ export async function markExchangeUsed( notif: undefined, }; } + } /** -- cgit v1.2.3 From 7631db2a902664f475b9074e305a95b0ad71a9df Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 5 Jun 2024 19:09:29 +0200 Subject: wallet-core: fail refresh when denomination is unknown to exchange --- packages/taler-wallet-core/src/refresh.ts | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts index f160e0731..7dcb755be 100644 --- a/packages/taler-wallet-core/src/refresh.ts +++ b/packages/taler-wallet-core/src/refresh.ts @@ -537,7 +537,10 @@ async function destroyRefreshSession( denom, oldCoin.maxAge, ); - checkDbInvariant(car.pendingRefreshOutputCount != null, `no pendingRefreshOutputCount for denom ${dph}`); + checkDbInvariant( + car.pendingRefreshOutputCount != null, + `no pendingRefreshOutputCount for denom ${dph}`, + ); car.pendingRefreshOutputCount = car.pendingRefreshOutputCount - refreshSession.newDenoms[i].count; await tx.coinAvailability.put(car); @@ -905,6 +908,7 @@ async function handleRefreshMeltNotFound( // with the coin. switch (errDetails.code) { case TalerErrorCode.EXCHANGE_GENERIC_COIN_UNKNOWN: + case TalerErrorCode.EXCHANGE_GENERIC_DENOMINATION_KEY_UNKNOWN: break; default: throwUnexpectedRequestError(resp, errDetails); @@ -1250,7 +1254,10 @@ async function refreshReveal( coin.exchangeBaseUrl, coin.denomPubHash, ); - checkDbInvariant(!!denomInfo, `no denom with hash ${coin.denomPubHash}`); + checkDbInvariant( + !!denomInfo, + `no denom with hash ${coin.denomPubHash}`, + ); const car = await getCoinAvailabilityForDenom( wex, tx, @@ -1260,7 +1267,7 @@ async function refreshReveal( checkDbInvariant( car.pendingRefreshOutputCount != null && car.pendingRefreshOutputCount > 0, - `no pendingRefreshOutputCount for denom ${coin.denomPubHash} age ${coin.maxAge}` + `no pendingRefreshOutputCount for denom ${coin.denomPubHash} age ${coin.maxAge}`, ); car.pendingRefreshOutputCount--; car.freshCoinCount++; @@ -1568,8 +1575,14 @@ async function applyRefreshToOldCoins( coin.denomPubHash, coin.maxAge, ]); - checkDbInvariant(!!coinAv, `no denom info for ${coin.denomPubHash} age ${coin.maxAge}`); - checkDbInvariant(coinAv.freshCoinCount > 0, `no fresh coins for ${coin.denomPubHash}`); + checkDbInvariant( + !!coinAv, + `no denom info for ${coin.denomPubHash} age ${coin.maxAge}`, + ); + checkDbInvariant( + coinAv.freshCoinCount > 0, + `no fresh coins for ${coin.denomPubHash}`, + ); coinAv.freshCoinCount--; await tx.coinAvailability.put(coinAv); break; -- cgit v1.2.3 From 93623571b0146c9a67085455bc3721e106a207cb Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 5 Jun 2024 19:37:49 +0200 Subject: wallet-core: properly adjust visibleCoinCount in availability record when refreshing a fresh coin --- packages/taler-wallet-core/src/refresh.ts | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts index 7dcb755be..a8f1cc61d 100644 --- a/packages/taler-wallet-core/src/refresh.ts +++ b/packages/taler-wallet-core/src/refresh.ts @@ -1584,6 +1584,13 @@ async function applyRefreshToOldCoins( `no fresh coins for ${coin.denomPubHash}`, ); coinAv.freshCoinCount--; + if (coin.visible) { + if (!coinAv.visibleCoinCount) { + logger.error("coin availability inconsistent"); + } else { + coinAv.visibleCoinCount--; + } + } await tx.coinAvailability.put(coinAv); break; } -- cgit v1.2.3 From c6016a297e8ad22358057de1373077ccfc0daa09 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Wed, 5 Jun 2024 19:48:39 +0200 Subject: -simplify --- packages/taler-wallet-core/src/refresh.ts | 96 +++++++++++-------------------- 1 file changed, 34 insertions(+), 62 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts index a8f1cc61d..05c65f6b6 100644 --- a/packages/taler-wallet-core/src/refresh.ts +++ b/packages/taler-wallet-core/src/refresh.ts @@ -29,7 +29,6 @@ import { Amounts, amountToPretty, assertUnreachable, - AsyncFlag, checkDbInvariant, codecForCoinHistoryResponse, codecForExchangeMeltResponse, @@ -75,6 +74,7 @@ import { } from "@gnu-taler/taler-util/http"; import { constructTaskIdentifier, + genericWaitForState, makeCoinsVisible, PendingTaskType, TaskIdStr, @@ -1847,66 +1847,38 @@ export async function waitRefreshFinal( const ctx = new RefreshTransactionContext(wex, refreshGroupId); wex.taskScheduler.startShepherdTask(ctx.taskId); - // FIXME: Clean up using the new JS "using" / Symbol.dispose syntax. - const refreshNotifFlag = new AsyncFlag(); - // Raise purchaseNotifFlag whenever we get a notification - // about our refresh. - const cancelNotif = wex.ws.addNotificationListener((notif) => { - if ( - notif.type === NotificationType.TransactionStateTransition && - notif.transactionId === ctx.transactionId - ) { - refreshNotifFlag.raise(); - } - }); - const unregisterOnCancelled = wex.cancellationToken.onCancelled(() => { - cancelNotif(); - refreshNotifFlag.raise(); + await genericWaitForState(wex, { + async checkState(): Promise { + // Check if refresh is final + const res = await ctx.wex.db.runReadOnlyTx( + { storeNames: ["refreshGroups"] }, + async (tx) => { + return { + rg: await tx.refreshGroups.get(ctx.refreshGroupId), + }; + }, + ); + const { rg } = res; + if (!rg) { + // Must've been deleted, we consider that final. + return true; + } + switch (rg.operationStatus) { + case RefreshOperationStatus.Failed: + case RefreshOperationStatus.Finished: + // Transaction is final + return true; + case RefreshOperationStatus.Pending: + case RefreshOperationStatus.Suspended: + break; + } + return false; + }, + filterNotification(notif): boolean { + return ( + notif.type === NotificationType.TransactionStateTransition && + notif.transactionId === ctx.transactionId + ); + }, }); - - try { - await internalWaitRefreshFinal(ctx, refreshNotifFlag); - } catch (e) { - unregisterOnCancelled(); - cancelNotif(); - } -} - -async function internalWaitRefreshFinal( - ctx: RefreshTransactionContext, - flag: AsyncFlag, -): Promise { - while (true) { - if (ctx.wex.cancellationToken.isCancelled) { - throw Error("cancelled"); - } - - // Check if refresh is final - const res = await ctx.wex.db.runReadOnlyTx( - { storeNames: ["refreshGroups", "operationRetries"] }, - async (tx) => { - return { - rg: await tx.refreshGroups.get(ctx.refreshGroupId), - }; - }, - ); - const { rg } = res; - if (!rg) { - // Must've been deleted, we consider that final. - return; - } - switch (rg.operationStatus) { - case RefreshOperationStatus.Failed: - case RefreshOperationStatus.Finished: - // Transaction is final - return; - case RefreshOperationStatus.Pending: - case RefreshOperationStatus.Suspended: - break; - } - - // Wait for the next transition - await flag.wait(); - flag.reset(); - } } -- cgit v1.2.3 From 920595aa3864615c07aba0ecfc233acf05f3e3e6 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 6 Jun 2024 09:24:50 -0300 Subject: update bank api with card fee --- packages/taler-wallet-core/src/withdraw.ts | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index b2cecea16..24e2861e1 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -862,12 +862,17 @@ export async function getBankWithdrawalInfo( if (status.amount) { amount = Amounts.parseOrThrow(status.amount); } + let wireFee: AmountJson | undefined; + if (status.card_fees) { + wireFee = Amounts.parseOrThrow(status.card_fees); + } return { operationId: uriResult.withdrawalOperationId, apiBaseUrl: uriResult.bankIntegrationApiBaseUrl, currency: config.currency, amount, + wireFee, confirmTransferUrl: status.confirm_transfer_url, senderWire: status.sender_wire, suggestedExchange: status.suggested_exchange, -- cgit v1.2.3 From 60a4640a35be584ee004de5e362f21ed03fa239e Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 30 May 2024 13:16:15 -0300 Subject: working #8882 --- packages/taler-wallet-core/src/balance.ts | 12 + packages/taler-wallet-core/src/db.ts | 4 +- packages/taler-wallet-core/src/transactions.ts | 51 ++++- packages/taler-wallet-core/src/wallet.ts | 5 +- packages/taler-wallet-core/src/withdraw.ts | 301 ++++++++++++++----------- 5 files changed, 228 insertions(+), 145 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/balance.ts b/packages/taler-wallet-core/src/balance.ts index 76e604324..4f06e3756 100644 --- a/packages/taler-wallet-core/src/balance.ts +++ b/packages/taler-wallet-core/src/balance.ts @@ -379,6 +379,10 @@ export async function getBalancesInsideTransaction( wg.denomsSel !== undefined, "wg in kyc state should have been initialized", ); + checkDbInvariant( + wg.exchangeBaseUrl !== undefined, + "wg in kyc state should have been initialized", + ); const currency = Amounts.currencyOf(wg.denomsSel.totalCoinValue); await balanceStore.setFlagIncomingKyc(currency, wg.exchangeBaseUrl); break; @@ -389,6 +393,10 @@ export async function getBalancesInsideTransaction( wg.denomsSel !== undefined, "wg in aml state should have been initialized", ); + checkDbInvariant( + wg.exchangeBaseUrl !== undefined, + "wg in kyc state should have been initialized", + ); const currency = Amounts.currencyOf(wg.denomsSel.totalCoinValue); await balanceStore.setFlagIncomingAml(currency, wg.exchangeBaseUrl); break; @@ -408,6 +416,10 @@ export async function getBalancesInsideTransaction( wg.denomsSel !== undefined, "wg in confirmed state should have been initialized", ); + checkDbInvariant( + wg.exchangeBaseUrl !== undefined, + "wg in kyc state should have been initialized", + ); const currency = Amounts.currencyOf(wg.denomsSel.totalCoinValue); await balanceStore.setFlagIncomingConfirmation( currency, diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index ad9b4f1cb..4ec83a783 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -395,6 +395,8 @@ export interface ReserveBankInfo { timestampBankConfirmed: DbPreciseTimestamp | undefined; wireTypes: string[] | undefined; + + currency: string | undefined; } /** @@ -1531,7 +1533,7 @@ export interface WithdrawalGroupRecord { * The exchange base URL that we're withdrawing from. * (Redundantly stored, as the reserve record also has this info.) */ - exchangeBaseUrl: string; + exchangeBaseUrl?: string; /** * When was the withdrawal operation started started? diff --git a/packages/taler-wallet-core/src/transactions.ts b/packages/taler-wallet-core/src/transactions.ts index 72aff319a..bcf3fcaf6 100644 --- a/packages/taler-wallet-core/src/transactions.ts +++ b/packages/taler-wallet-core/src/transactions.ts @@ -243,11 +243,14 @@ export async function getTransactionById( const opId = TaskIdentifiers.forWithdrawal(withdrawalGroupRecord); const ort = await tx.operationRetries.get(opId); - const exchangeDetails = await getExchangeWireDetailsInTx( - tx, - withdrawalGroupRecord.exchangeBaseUrl, - ); - if (!exchangeDetails) throw Error("not exchange details"); + const exchangeDetails = + withdrawalGroupRecord.exchangeBaseUrl === undefined + ? undefined + : await getExchangeWireDetailsInTx( + tx, + withdrawalGroupRecord.exchangeBaseUrl, + ); + // if (!exchangeDetails) throw Error("not exchange details"); if ( withdrawalGroupRecord.wgInfo.withdrawalType === @@ -259,7 +262,10 @@ export async function getTransactionById( ort, ); } - + checkDbInvariant( + exchangeDetails !== undefined, + "manual withdrawal without exchange", + ); return buildTransactionForManualWithdraw( withdrawalGroupRecord, exchangeDetails, @@ -404,7 +410,10 @@ export async function getTransactionById( const debit = await tx.peerPushDebit.get(parsedTx.pursePub); if (!debit) throw Error("not found"); const ct = await tx.contractTerms.get(debit.contractTermsHash); - checkDbInvariant(!!ct, `no contract terms for p2p push ${parsedTx.pursePub}`); + checkDbInvariant( + !!ct, + `no contract terms for p2p push ${parsedTx.pursePub}`, + ); return buildTransactionForPushPaymentDebit( debit, ct.contractTermsRaw, @@ -428,7 +437,10 @@ export async function getTransactionById( const pushInc = await tx.peerPushCredit.get(peerPushCreditId); if (!pushInc) throw Error("not found"); const ct = await tx.contractTerms.get(pushInc.contractTermsHash); - checkDbInvariant(!!ct, `no contract terms for p2p push ${peerPushCreditId}`); + checkDbInvariant( + !!ct, + `no contract terms for p2p push ${peerPushCreditId}`, + ); let wg: WithdrawalGroupRecord | undefined = undefined; let wgOrt: OperationRetryRecord | undefined = undefined; @@ -593,6 +605,7 @@ function buildTransactionForPeerPullCredit( const txState = computePeerPullCreditTransactionState(pullCredit); checkDbInvariant(wsr.instructedAmount !== undefined, "wg uninitialized"); checkDbInvariant(wsr.denomsSel !== undefined, "wg uninitialized"); + checkDbInvariant(wsr.exchangeBaseUrl !== undefined, "wg uninitialized"); return { type: TransactionType.PeerPullCredit, txState, @@ -667,6 +680,7 @@ function buildTransactionForPeerPushCredit( } checkDbInvariant(wg.instructedAmount !== undefined, "wg uninitialized"); checkDbInvariant(wg.denomsSel !== undefined, "wg uninitialized"); + checkDbInvariant(wg.exchangeBaseUrl !== undefined, "wg uninitialized"); const txState = computePeerPushCreditTransactionState(pushInc); return { @@ -719,15 +733,17 @@ function buildTransactionForPeerPushCredit( function buildTransactionForBankIntegratedWithdraw( wg: WithdrawalGroupRecord, - exchangeDetails: ExchangeWireDetails, + exchangeDetails: ExchangeWireDetails | undefined, ort?: OperationRetryRecord, ): TransactionWithdrawal { if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) { throw Error(""); } + checkDbInvariant(wg.wgInfo.bankInfo.currency !== undefined, "wg uninitialized"); const txState = computeWithdrawalTransactionStatus(wg); + const zero = Amounts.stringify( - Amounts.zeroOfCurrency(exchangeDetails.currency), + Amounts.zeroOfCurrency(wg.wgInfo.bankInfo.currency), ); return { type: TransactionType.Withdrawal, @@ -784,6 +800,7 @@ function buildTransactionForManualWithdraw( checkDbInvariant(wg.instructedAmount !== undefined, "wg uninitialized"); checkDbInvariant(wg.denomsSel !== undefined, "wg uninitialized"); + checkDbInvariant(wg.exchangeBaseUrl !== undefined, "wg uninitialized"); const exchangePaytoUris = augmentPaytoUrisForWithdrawal( plainPaytoUris, wg.reservePub, @@ -1034,8 +1051,14 @@ function buildTransactionForPurchase( })); const timestamp = purchaseRecord.timestampAccept; - checkDbInvariant(!!timestamp, `purchase ${purchaseRecord.orderId} without accepted time`); - checkDbInvariant(!!purchaseRecord.payInfo, `purchase ${purchaseRecord.orderId} without payinfo`); + checkDbInvariant( + !!timestamp, + `purchase ${purchaseRecord.orderId} without accepted time`, + ); + checkDbInvariant( + !!purchaseRecord.payInfo, + `purchase ${purchaseRecord.orderId} without payinfo`, + ); const txState = computePayMerchantTransactionState(purchaseRecord); return { @@ -1089,6 +1112,10 @@ export async function getWithdrawalTransactionByUri( if (!withdrawalGroupRecord) { return undefined; } + if (withdrawalGroupRecord.exchangeBaseUrl === undefined) { + // prepared and unconfirmed withdrawals are hidden + return undefined; + } const opId = TaskIdentifiers.forWithdrawal(withdrawalGroupRecord); const ort = await tx.operationRetries.get(opId); diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index d98106d1f..f1d53b7d5 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -1010,10 +1010,7 @@ async function dispatchRequestInternal( case WalletApiOperation.PrepareBankIntegratedWithdrawal: { const req = codecForPrepareBankIntegratedWithdrawalRequest().decode(payload); - return prepareBankIntegratedWithdrawal(wex, { - talerWithdrawUri: req.talerWithdrawUri, - selectedExchange: req.selectedExchange, - }); + return prepareBankIntegratedWithdrawal(wex, req); } case WalletApiOperation.GetExchangeTos: { const req = codecForGetExchangeTosRequest().decode(payload); diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index 24e2861e1..0e7ed144c 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -344,9 +344,11 @@ export class WithdrawTransactionContext implements TransactionContext { "exchanges" as const, "exchangeDetails" as const, ]; - let stores = opts.extraStores + const stores = opts.extraStores ? [...baseStores, ...opts.extraStores] : baseStores; + + let errorThrown: Error | undefined; const transitionInfo = await this.wex.db.runReadWriteTx( { storeNames: stores }, async (tx) => { @@ -359,7 +361,17 @@ export class WithdrawTransactionContext implements TransactionContext { major: TransactionMajorState.None, }; } - const res = await f(wgRec, tx); + let res: TransitionResult | undefined; + try { + res = await f(wgRec, tx); + } catch (error) { + if (error instanceof Error) { + errorThrown = error; + } + return undefined; + } + + // const res = await f(wgRec, tx); switch (res.type) { case TransitionResultType.Transition: { await tx.withdrawalGroups.put(res.rec); @@ -384,6 +396,9 @@ export class WithdrawTransactionContext implements TransactionContext { } }, ); + if (errorThrown) { + throw errorThrown; + } notifyTransition(this.wex, this.transactionId, transitionInfo); return transitionInfo; } @@ -929,6 +944,10 @@ async function processPlanchetGenerate( withdrawalGroup.denomsSel !== undefined, "can't process uninitialized exchange", ); + checkDbInvariant( + withdrawalGroup.exchangeBaseUrl !== undefined, + "can't get funding uri from uninitialized wg", + ); const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl; let planchet = await wex.db.runReadOnlyTx( { storeNames: ["planchets"] }, @@ -1133,6 +1152,10 @@ async function processPlanchetExchangeBatchRequest( logger.info( `processing planchet exchange batch request ${withdrawalGroup.withdrawalGroupId}, start=${args.coinStartIndex}, len=${args.batchSize}`, ); + checkDbInvariant( + withdrawalGroup.exchangeBaseUrl !== undefined, + "can't get funding uri from uninitialized wg", + ); const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl; const batchReq: ExchangeBatchWithdrawRequest = { planchets: [] }; @@ -1268,6 +1291,10 @@ async function processPlanchetVerifyAndStoreCoin( resp: ExchangeWithdrawResponse, ): Promise { const withdrawalGroup = wgContext.wgRecord; + checkDbInvariant( + withdrawalGroup.exchangeBaseUrl !== undefined, + "can't get funding uri from uninitialized wg", + ); const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl; logger.trace(`checking and storing planchet idx=${coinIdx}`); @@ -1516,6 +1543,10 @@ async function processQueryReserve( if (withdrawalGroup.status !== WithdrawalGroupStatus.PendingQueryingStatus) { return TaskRunResult.backoff(); } + checkDbInvariant( + withdrawalGroup.exchangeBaseUrl !== undefined, + "can't get funding uri from uninitialized wg", + ); checkDbInvariant( withdrawalGroup.denomsSel !== undefined, "can't process uninitialized exchange", @@ -1751,6 +1782,10 @@ async function redenominateWithdrawal( if (!wg) { return; } + checkDbInvariant( + wg.exchangeBaseUrl !== undefined, + "can't get funding uri from uninitialized wg", + ); checkDbInvariant( wg.denomsSel !== undefined, "can't process uninitialized exchange", @@ -1894,6 +1929,10 @@ async function processWithdrawalGroupPendingReady( withdrawalGroup.denomsSel !== undefined, "can't process uninitialized exchange", ); + checkDbInvariant( + withdrawalGroup.exchangeBaseUrl !== undefined, + "can't get funding uri from uninitialized wg", + ); const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl; await fetchFreshExchange(wex, withdrawalGroup.exchangeBaseUrl); @@ -2320,6 +2359,10 @@ export async function getFundingPaytoUris( ): Promise { const withdrawalGroup = await tx.withdrawalGroups.get(withdrawalGroupId); checkDbInvariant(!!withdrawalGroup, `no withdrawal for ${withdrawalGroupId}`); + checkDbInvariant( + withdrawalGroup.exchangeBaseUrl !== undefined, + "can't get funding uri from uninitialized wg", + ); checkDbInvariant( withdrawalGroup.instructedAmount !== undefined, "can't get funding uri from uninitialized wg", @@ -2675,7 +2718,7 @@ export async function internalPrepareCreateWithdrawalGroup( args: { reserveStatus: WithdrawalGroupStatus; amount?: AmountJson; - exchangeBaseUrl: string; + exchangeBaseUrl: string | undefined; forcedWithdrawalGroupId?: string; forcedDenomSel?: ForcedDenomSel; reserveKeyPair?: EddsaKeypair; @@ -2716,7 +2759,7 @@ export async function internalPrepareCreateWithdrawalGroup( let initialDenomSel: DenomSelectionState | undefined; const denomSelUid = encodeCrock(getRandomBytes(16)); - if (amount !== undefined) { + if (amount !== undefined && exchangeBaseUrl !== undefined) { initialDenomSel = await getInitialDenomsSelection( wex, exchangeBaseUrl, @@ -2747,7 +2790,9 @@ export async function internalPrepareCreateWithdrawalGroup( wgInfo: args.wgInfo, }; - await fetchFreshExchange(wex, exchangeBaseUrl); + if (exchangeBaseUrl !== undefined) { + await fetchFreshExchange(wex, exchangeBaseUrl); + } const transactionId = constructTransactionIdentifier({ tag: TransactionType.Withdrawal, @@ -2757,12 +2802,13 @@ export async function internalPrepareCreateWithdrawalGroup( return { withdrawalGroup, transactionId, - creationInfo: !amount - ? undefined - : { - amount, - canonExchange: exchangeBaseUrl, - }, + creationInfo: + !amount || !exchangeBaseUrl + ? undefined + : { + amount, + canonExchange: exchangeBaseUrl, + }, }; } @@ -2792,8 +2838,8 @@ export async function internalPerformCreateWithdrawalGroup( if (existingWg) { return { withdrawalGroup: existingWg, - exchangeNotif: undefined, transitionInfo: undefined, + exchangeNotif: undefined, }; } await tx.withdrawalGroups.add(withdrawalGroup); @@ -2809,7 +2855,21 @@ export async function internalPerformCreateWithdrawalGroup( exchangeNotif: undefined, }; } - const exchange = await tx.exchanges.get(prep.creationInfo.canonExchange); + return internalPerformExchangeWasUsed( + wex, + tx, + prep.creationInfo.canonExchange, + withdrawalGroup, + ); +} + +export async function internalPerformExchangeWasUsed( + wex: WalletExecutionContext, + tx: WalletDbReadWriteTransaction<["exchanges"]>, + canonExchange: string, + withdrawalGroup: WithdrawalGroupRecord, +): Promise { + const exchange = await tx.exchanges.get(canonExchange); if (exchange) { exchange.lastWithdrawal = timestampPreciseToDb(TalerPreciseTimestamp.now()); await tx.exchanges.put(exchange); @@ -2825,11 +2885,7 @@ export async function internalPerformCreateWithdrawalGroup( newTxState, }; - const exchangeUsedRes = await markExchangeUsed( - wex, - tx, - prep.creationInfo.canonExchange, - ); + const exchangeUsedRes = await markExchangeUsed(wex, tx, canonExchange); const ctx = new WithdrawTransactionContext( wex, @@ -2857,7 +2913,7 @@ export async function internalCreateWithdrawalGroup( wex: WalletExecutionContext, args: { reserveStatus: WithdrawalGroupStatus; - exchangeBaseUrl: string; + exchangeBaseUrl: string | undefined; amount?: AmountJson; forcedWithdrawalGroupId?: string; forcedDenomSel?: ForcedDenomSel; @@ -2903,7 +2959,6 @@ export async function prepareBankIntegratedWithdrawal( wex: WalletExecutionContext, req: { talerWithdrawUri: string; - selectedExchange?: string; }, ): Promise { const existingWithdrawalGroup = await wex.db.runReadOnlyTx( @@ -2932,12 +2987,6 @@ export async function prepareBankIntegratedWithdrawal( const info = await getWithdrawalDetailsForUri(wex, req.talerWithdrawUri); - const exchangeBaseUrl = - req.selectedExchange ?? withdrawInfo.suggestedExchange; - if (!exchangeBaseUrl) { - return { info }; - } - /** * Withdrawal group without exchange and amount * this is an special case when the user haven't yet @@ -2946,7 +2995,7 @@ export async function prepareBankIntegratedWithdrawal( * same URI */ const withdrawalGroup = await internalCreateWithdrawalGroup(wex, { - exchangeBaseUrl, + exchangeBaseUrl: undefined, wgInfo: { withdrawalType: WithdrawalRecordType.BankIntegrated, bankInfo: { @@ -2955,6 +3004,7 @@ export async function prepareBankIntegratedWithdrawal( timestampBankConfirmed: undefined, timestampReserveInfoPosted: undefined, wireTypes: withdrawInfo.wireTypes, + currency: withdrawInfo.currency, }, }, reserveStatus: WithdrawalGroupStatus.DialogProposed, @@ -2977,6 +3027,9 @@ export async function confirmWithdrawal( req: ConfirmWithdrawalRequest, ): Promise { const parsedTx = parseTransactionIdentifier(req.transactionId); + const selectedExchange = req.exchangeBaseUrl; + const instructedAmount = Amounts.parseOrThrow(req.amount); + if (parsedTx?.tag !== TransactionType.Withdrawal) { throw Error("invalid withdrawal transaction ID"); } @@ -2998,7 +3051,6 @@ export async function confirmWithdrawal( throw Error("not a bank integrated withdrawal"); } - const selectedExchange = req.exchangeBaseUrl; const exchange = await fetchFreshExchange(wex, selectedExchange); const talerWithdrawUri = withdrawalGroup.wgInfo.bankInfo.talerWithdrawUri; @@ -3006,30 +3058,36 @@ export async function confirmWithdrawal( /** * The only reason this could be undefined is because it is an old wallet - * database before adding the wireType field was added + * database before adding the prepareWithdrawal feature */ - let wtypes: string[]; - if (withdrawalGroup.wgInfo.bankInfo.wireTypes === undefined) { + let bankWireTypes: string[]; + let bankCurrency: string; + if ( + withdrawalGroup.wgInfo.bankInfo.wireTypes === undefined || + withdrawalGroup.wgInfo.bankInfo.currency === undefined + ) { const withdrawInfo = await getBankWithdrawalInfo( wex.http, talerWithdrawUri, ); - wtypes = withdrawInfo.wireTypes; + bankWireTypes = withdrawInfo.wireTypes; + bankCurrency = withdrawInfo.currency; } else { - wtypes = withdrawalGroup.wgInfo.bankInfo.wireTypes; + bankWireTypes = withdrawalGroup.wgInfo.bankInfo.wireTypes; + bankCurrency = withdrawalGroup.wgInfo.bankInfo.currency; } const exchangePaytoUri = await getExchangePaytoUri( wex, selectedExchange, - wtypes, + bankWireTypes, ); const withdrawalAccountList = await fetchWithdrawalAccountInfo( wex, { exchange, - instructedAmount: Amounts.parseOrThrow(req.amount), + instructedAmount, }, wex.cancellationToken, ); @@ -3040,23 +3098,34 @@ export async function confirmWithdrawal( ); const initalDenoms = await getInitialDenomsSelection( wex, - req.exchangeBaseUrl, - Amounts.parseOrThrow(req.amount), + exchange.exchangeBaseUrl, + instructedAmount, req.forcedDenomSel, ); + let pending = false; await ctx.transition({}, async (rec) => { if (!rec) { return TransitionResult.stay(); } switch (rec.status) { + case WithdrawalGroupStatus.PendingWaitConfirmBank: { + pending = true; + return TransitionResult.stay(); + } + case WithdrawalGroupStatus.AbortedOtherWallet: { + throw TalerError.fromDetail( + TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK, + {}, + ); + } case WithdrawalGroupStatus.DialogProposed: { - rec.exchangeBaseUrl = req.exchangeBaseUrl; + rec.exchangeBaseUrl = exchange.exchangeBaseUrl; rec.instructedAmount = req.amount; + rec.restrictAge = req.restrictAge; rec.denomsSel = initalDenoms; rec.rawWithdrawalAmount = initalDenoms.totalWithdrawCost; rec.effectiveWithdrawalAmount = initalDenoms.totalCoinValue; - rec.restrictAge = req.restrictAge; rec.wgInfo = { withdrawalType: WithdrawalRecordType.BankIntegrated, @@ -3067,19 +3136,50 @@ export async function confirmWithdrawal( confirmUrl: confirmUrl, timestampBankConfirmed: undefined, timestampReserveInfoPosted: undefined, - wireTypes: wtypes, + wireTypes: bankWireTypes, + currency: bankCurrency, }, }; - + pending = true; rec.status = WithdrawalGroupStatus.PendingRegisteringBank; return TransitionResult.transition(rec); } - default: - throw Error("unable to confirm withdrawal in current state"); + default: { + throw Error( + `unable to confirm withdrawal in current state: ${rec.status}`, + ); + } } }); await wex.taskScheduler.resetTaskRetries(ctx.taskId); + + wex.ws.notify({ + type: NotificationType.BalanceChange, + hintTransactionId: ctx.transactionId, + }); + + const res = await wex.db.runReadWriteTx( + { + storeNames: ["exchanges"], + }, + async (tx) => { + const r = await internalPerformExchangeWasUsed( + wex, + tx, + exchange.exchangeBaseUrl, + withdrawalGroup, + ); + return r; + }, + ); + if (res.exchangeNotif) { + wex.ws.notify(res.exchangeNotif); + } + + if (pending) { + await waitWithdrawalRegistered(wex, ctx); + } } /** @@ -3104,112 +3204,57 @@ export async function acceptWithdrawalFromUri( ): Promise { const selectedExchange = req.selectedExchange; logger.info( - `accepting withdrawal via ${req.talerWithdrawUri}, canonicalized selected exchange ${selectedExchange}`, - ); - const existingWithdrawalGroup = await wex.db.runReadOnlyTx( - { storeNames: ["withdrawalGroups"] }, - async (tx) => { - return await tx.withdrawalGroups.indexes.byTalerWithdrawUri.get( - req.talerWithdrawUri, - ); - }, + `preparing withdrawal via ${req.talerWithdrawUri}, canonicalized selected exchange ${selectedExchange}`, ); - if (existingWithdrawalGroup) { - let url: string | undefined; - if ( - existingWithdrawalGroup.wgInfo.withdrawalType === - WithdrawalRecordType.BankIntegrated - ) { - url = existingWithdrawalGroup.wgInfo.bankInfo.confirmUrl; - } - return { - reservePub: existingWithdrawalGroup.reservePub, - confirmTransferUrl: url, - transactionId: constructTransactionIdentifier({ - tag: TransactionType.Withdrawal, - withdrawalGroupId: existingWithdrawalGroup.withdrawalGroupId, - }), - }; - } - - const exchange = await fetchFreshExchange(wex, selectedExchange); - const withdrawInfo = await getBankWithdrawalInfo( - wex.http, - req.talerWithdrawUri, - ); - const exchangePaytoUri = await getExchangePaytoUri( - wex, - selectedExchange, - withdrawInfo.wireTypes, - ); + const p = await prepareBankIntegratedWithdrawal(wex, { + talerWithdrawUri: req.talerWithdrawUri, + }); - let amount: AmountJson; - if (withdrawInfo.amount == null) { + let amount: AmountString; + if (p.info.amount == null) { if (req.amount == null) { throw Error( "amount required, as withdrawal operation has flexible amount", ); } - amount = Amounts.parseOrThrow(req.amount); + amount = req.amount as AmountString; } else { - if ( - req.amount != null && - Amounts.cmp(req.amount, withdrawInfo.amount) != 0 - ) { + if (req.amount != null && Amounts.cmp(req.amount, p.info.amount) != 0) { throw Error( "mismatched amount, amount is fixed by bank but client provided different amount", ); } - amount = withdrawInfo.amount; + amount = p.info.amount; } - const withdrawalAccountList = await fetchWithdrawalAccountInfo( - wex, - { - exchange, - instructedAmount: amount, - }, - CancellationToken.CONTINUE, - ); - - const withdrawalGroup = await internalCreateWithdrawalGroup(wex, { - amount, - exchangeBaseUrl: req.selectedExchange, - wgInfo: { - withdrawalType: WithdrawalRecordType.BankIntegrated, - exchangeCreditAccounts: withdrawalAccountList, - bankInfo: { - exchangePaytoUri, - talerWithdrawUri: req.talerWithdrawUri, - confirmUrl: withdrawInfo.confirmTransferUrl, - timestampBankConfirmed: undefined, - timestampReserveInfoPosted: undefined, - wireTypes: withdrawInfo.wireTypes, - }, - }, + logger.info(`confirming withdrawal with tx ${p.transactionId}`); + await confirmWithdrawal(wex, { + amount: Amounts.stringify(amount), + exchangeBaseUrl: selectedExchange, + transactionId: p.transactionId, restrictAge: req.restrictAge, forcedDenomSel: req.forcedDenomSel, - reserveStatus: WithdrawalGroupStatus.PendingRegisteringBank, - }); - - const withdrawalGroupId = withdrawalGroup.withdrawalGroupId; - - const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId); - - wex.ws.notify({ - type: NotificationType.BalanceChange, - hintTransactionId: ctx.transactionId, }); - wex.taskScheduler.startShepherdTask(ctx.taskId); + const newWithdrawralGroup = await wex.db.runReadOnlyTx( + { storeNames: ["withdrawalGroups"] }, + async (tx) => { + return await tx.withdrawalGroups.indexes.byTalerWithdrawUri.get( + req.talerWithdrawUri, + ); + }, + ); - await waitWithdrawalRegistered(wex, ctx); + checkDbInvariant( + newWithdrawralGroup !== undefined, + "withdrawal don't exist after confirm", + ); return { - reservePub: withdrawalGroup.reservePub, - confirmTransferUrl: withdrawInfo.confirmTransferUrl, - transactionId: ctx.transactionId, + reservePub: newWithdrawralGroup.reservePub, + confirmTransferUrl: p.info.confirmTransferUrl, + transactionId: p.transactionId, }; } @@ -3434,7 +3479,7 @@ export async function createManualWithdrawal( ); const withdrawalGroup = await internalCreateWithdrawalGroup(wex, { - amount: Amounts.jsonifyAmount(req.amount), + amount: amount, wgInfo: { withdrawalType: WithdrawalRecordType.BankManual, exchangeCreditAccounts: withdrawalAccountsList, -- cgit v1.2.3 From 1e22b5bcf77b3391f86105727e8c0b413b652fd4 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 6 Jun 2024 13:01:56 -0300 Subject: making the error visible --- packages/taler-wallet-core/src/exchanges.ts | 8 +++++--- packages/taler-wallet-core/src/shepherd.ts | 27 ++++++++++++++++++--------- packages/taler-wallet-core/src/withdraw.ts | 3 ++- 3 files changed, 25 insertions(+), 13 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts index 13c2ca316..05de3d8e5 100644 --- a/packages/taler-wallet-core/src/exchanges.ts +++ b/packages/taler-wallet-core/src/exchanges.ts @@ -1158,7 +1158,7 @@ export async function fetchFreshExchange( } = {}, ): Promise { logger.info(`fetch fresh ${baseUrl} forced ${options.forceUpdate}`); - + if (!options.forceUpdate) { const cachedResp = wex.ws.exchangeCache.get(baseUrl); if (cachedResp) { @@ -1552,7 +1552,10 @@ export async function updateExchangeFromUrlHandler( r.cachebreakNextUpdate = false; await tx.exchanges.put(r); const drRowId = await tx.exchangeDetails.put(newDetails); - checkDbInvariant(typeof drRowId.key === "number", "exchange details key is not a number"); + checkDbInvariant( + typeof drRowId.key === "number", + "exchange details key is not a number", + ); for (const sk of keysInfo.signingKeys) { // FIXME: validate signing keys before inserting them @@ -2259,7 +2262,6 @@ export async function markExchangeUsed( notif: undefined, }; } - } /** diff --git a/packages/taler-wallet-core/src/shepherd.ts b/packages/taler-wallet-core/src/shepherd.ts index d662bd7ae..050de479a 100644 --- a/packages/taler-wallet-core/src/shepherd.ts +++ b/packages/taler-wallet-core/src/shepherd.ts @@ -306,6 +306,7 @@ export class TaskSchedulerImpl implements TaskScheduler { const maybeNotification = await this.ws.db.runAllStoresReadWriteTx( {}, async (tx) => { + logger.trace(`storing task [reset] for ${taskId}`); await tx.operationRetries.delete(taskId); return taskToRetryNotification(this.ws, tx, taskId, undefined); }, @@ -325,7 +326,13 @@ export class TaskSchedulerImpl implements TaskScheduler { try { await info.cts.token.racePromise(this.ws.timerGroup.resolveAfter(delay)); } catch (e) { - logger.info(`waiting for ${taskId} interrupted`); + if (e instanceof CancellationToken.CancellationError) { + logger.info( + `waiting for ${taskId} interrupted: ${e.message} ${j2s(e.reason)}`, + ); + } else { + logger.info(`waiting for ${taskId} interrupted: ${e}`); + } } } @@ -363,6 +370,7 @@ export class TaskSchedulerImpl implements TaskScheduler { try { res = await callOperationHandlerForTaskId(wex, taskId); } catch (e) { + logger.trace(`Shepherd error ${taskId} saving response ${e}`); res = { type: TaskRunResultType.Error, errorDetail: getErrorDetailFromException(e), @@ -382,13 +390,11 @@ export class TaskSchedulerImpl implements TaskScheduler { }); switch (res.type) { case TaskRunResultType.Error: { - if (logger.shouldLogTrace()) { - logger.trace( - `Shepherd for ${taskId} got error result: ${j2s( - res.errorDetail, - )}`, - ); - } + // if (logger.shouldLogTrace()) { + logger.trace( + `Shepherd for ${taskId} got error result: ${j2s(res.errorDetail)}`, + ); + // } const retryRecord = await storePendingTaskError( this.ws, taskId, @@ -457,7 +463,7 @@ async function storePendingTaskError( pendingTaskId: string, e: TalerErrorDetail, ): Promise { - logger.info(`storing pending task error for ${pendingTaskId}`); + logger.trace(`storing task [pending] with ERROR for ${pendingTaskId}`); const res = await ws.db.runAllStoresReadWriteTx({}, async (tx) => { let retryRecord = await tx.operationRetries.get(pendingTaskId); if (!retryRecord) { @@ -489,6 +495,7 @@ async function storeTaskProgress( ws: InternalWalletState, pendingTaskId: string, ): Promise { + logger.trace(`storing task [progress] for ${pendingTaskId}`); await ws.db.runReadWriteTx( { storeNames: ["operationRetries"] }, async (tx) => { @@ -501,6 +508,7 @@ async function storePendingTaskPending( ws: InternalWalletState, pendingTaskId: string, ): Promise { + logger.trace(`storing task [pending] for ${pendingTaskId}`); const res = await ws.db.runAllStoresReadWriteTx({}, async (tx) => { let retryRecord = await tx.operationRetries.get(pendingTaskId); let hadError = false; @@ -541,6 +549,7 @@ async function storePendingTaskFinished( ws: InternalWalletState, pendingTaskId: string, ): Promise { + logger.trace(`storing task [finished] for ${pendingTaskId}`); await ws.db.runReadWriteTx( { storeNames: ["operationRetries"] }, async (tx) => { diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index 0e7ed144c..42adf3585 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -1934,6 +1934,7 @@ async function processWithdrawalGroupPendingReady( "can't get funding uri from uninitialized wg", ); const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl; + logger.trace(`updating exchange beofre processing wg`) await fetchFreshExchange(wex, withdrawalGroup.exchangeBaseUrl); if (withdrawalGroup.denomsSel.selectedDenoms.length === 0) { @@ -3564,7 +3565,7 @@ async function internalWaitWithdrawalFinal( // Check if refresh is final const res = await ctx.wex.db.runReadOnlyTx( - { storeNames: ["withdrawalGroups", "operationRetries"] }, + { storeNames: ["withdrawalGroups"] }, async (tx) => { return { wg: await tx.withdrawalGroups.get(ctx.withdrawalGroupId), -- cgit v1.2.3 From 2a37575cf6db006431a7d174b85203ae41cc629f Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 6 Jun 2024 15:49:36 -0300 Subject: wait for previous task to be cancelled --- packages/taler-wallet-core/src/common.ts | 3 ++- packages/taler-wallet-core/src/exchanges.ts | 2 +- packages/taler-wallet-core/src/shepherd.ts | 28 ++++++++++++++++++---------- packages/taler-wallet-core/src/withdraw.ts | 2 +- 4 files changed, 22 insertions(+), 13 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts index 00d462d6f..a9e962dda 100644 --- a/packages/taler-wallet-core/src/common.ts +++ b/packages/taler-wallet-core/src/common.ts @@ -41,6 +41,7 @@ import { checkDbInvariant, checkLogicInvariant, durationMul, + j2s, } from "@gnu-taler/taler-util"; import { BackupProviderRecord, @@ -798,7 +799,7 @@ export async function genericWaitForState( flag.raise(); } }); - const unregisterOnCancelled = wex.cancellationToken.onCancelled(() => { + const unregisterOnCancelled = wex.cancellationToken.onCancelled((reason) => { cancelNotif(); flag.raise(); }); diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts index 05de3d8e5..626441d35 100644 --- a/packages/taler-wallet-core/src/exchanges.ts +++ b/packages/taler-wallet-core/src/exchanges.ts @@ -1363,13 +1363,13 @@ export async function updateExchangeFromUrlHandler( ); refreshCheckNecessary = false; } - if (!(updateNecessary || refreshCheckNecessary)) { logger.trace("update not necessary, running again later"); return TaskRunResult.runAgainAt( AbsoluteTime.min(nextUpdateStamp, nextRefreshCheckStamp), ); } + } // When doing the auto-refresh check, we always update diff --git a/packages/taler-wallet-core/src/shepherd.ts b/packages/taler-wallet-core/src/shepherd.ts index 050de479a..ee55e1da1 100644 --- a/packages/taler-wallet-core/src/shepherd.ts +++ b/packages/taler-wallet-core/src/shepherd.ts @@ -56,6 +56,7 @@ import { WalletDbAllStoresReadOnlyTransaction, WalletDbReadOnlyTransaction, timestampAbsoluteFromDb, + timestampPreciseToDb, } from "./db.js"; import { computeDepositTransactionStatus, @@ -113,6 +114,8 @@ const logger = new Logger("shepherd.ts"); */ interface ShepherdInfo { cts: CancellationToken.Source; + latch?: Promise; + stopped: boolean; } /** @@ -258,27 +261,34 @@ export class TaskSchedulerImpl implements TaskScheduler { const tasksIds = [...this.sheps.keys()]; logger.info(`reloading shepherd with ${tasksIds.length} tasks`); for (const taskId of tasksIds) { - this.stopShepherdTask(taskId); + await this.stopShepherdTask(taskId); } for (const taskId of tasksIds) { this.startShepherdTask(taskId); } } - private async internalStartShepherdTask(taskId: TaskIdStr): Promise { logger.trace(`Starting to shepherd task ${taskId}`); const oldShep = this.sheps.get(taskId); if (oldShep) { - logger.trace(`Already have a shepherd for ${taskId}`); - return; + if (!oldShep.stopped) { + logger.trace(`Already have a shepherd for ${taskId}`); + return; + } + logger.trace( + `Waiting old task to complete the loop in cancel mode ${taskId}`, + ); + await oldShep.latch; } logger.trace(`Creating new shepherd for ${taskId}`); const newShep: ShepherdInfo = { cts: CancellationToken.create(), + stopped: false, }; this.sheps.set(taskId, newShep); try { - await this.internalShepherdTask(taskId, newShep); + newShep.latch = this.internalShepherdTask(taskId, newShep); + await newShep.latch; } finally { logger.trace(`Done shepherding ${taskId}`); this.sheps.delete(taskId); @@ -291,8 +301,8 @@ export class TaskSchedulerImpl implements TaskScheduler { const oldShep = this.sheps.get(taskId); if (oldShep) { logger.trace(`Cancelling old shepherd for ${taskId}`); - oldShep.cts.cancel(); - this.sheps.delete(taskId); + oldShep.cts.cancel(`stopping task ${taskId}`); + oldShep.stopped = true; this.iterCond.trigger(); } } @@ -377,7 +387,7 @@ export class TaskSchedulerImpl implements TaskScheduler { }; } if (info.cts.token.isCancelled) { - logger.trace("task cancelled, not processing result"); + logger.trace(`task ${taskId} cancelled, not processing result`); return; } if (this.ws.stopped) { @@ -390,11 +400,9 @@ export class TaskSchedulerImpl implements TaskScheduler { }); switch (res.type) { case TaskRunResultType.Error: { - // if (logger.shouldLogTrace()) { logger.trace( `Shepherd for ${taskId} got error result: ${j2s(res.errorDetail)}`, ); - // } const retryRecord = await storePendingTaskError( this.ws, taskId, diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index 42adf3585..dbd7e8673 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -3608,7 +3608,7 @@ export async function getWithdrawalDetailsForAmount( type: ObservabilityEventType.Message, contents: `Cancelling previous key ${clientCancelKey}`, }); - prevCts.cancel(); + prevCts.cancel(`getting details amount`); } else { wex.oc.observe({ type: ObservabilityEventType.Message, -- cgit v1.2.3 From 3f35ef8ed328c7736cb4eb76b4ec7874274b4d7d Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 7 Jun 2024 10:22:11 -0300 Subject: operation retries * should not be changed outside of shepherd * scheduled result should be saved --- packages/taler-wallet-core/src/exchanges.ts | 3 --- packages/taler-wallet-core/src/pay-merchant.ts | 1 - packages/taler-wallet-core/src/shepherd.ts | 15 +++++++++++++-- 3 files changed, 13 insertions(+), 6 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts index 626441d35..adb696de0 100644 --- a/packages/taler-wallet-core/src/exchanges.ts +++ b/packages/taler-wallet-core/src/exchanges.ts @@ -977,9 +977,7 @@ async function startUpdateExchangeEntry( wex.ws.exchangeCache.clear(); await tx.exchanges.put(r); const newExchangeState = getExchangeState(r); - // Reset retries for updating the exchange entry. const taskId = TaskIdentifiers.forExchangeUpdate(r); - await tx.operationRetries.delete(taskId); return { oldExchangeState, newExchangeState, taskId }; }, ); @@ -2239,7 +2237,6 @@ export async function markExchangeUsed( notif: undefined, }; } - logger.info(`exchange ${exch.baseUrl} IS IN THE DATABASE`); const oldExchangeState = getExchangeState(exch); switch (exch.entryStatus) { diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts index aa4919285..badbd32f4 100644 --- a/packages/taler-wallet-core/src/pay-merchant.ts +++ b/packages/taler-wallet-core/src/pay-merchant.ts @@ -342,7 +342,6 @@ export class PayMerchantTransactionContext implements TransactionContext { return; } await tx.purchases.put(purchase); - await tx.operationRetries.delete(this.taskId); const newTxState = computePayMerchantTransactionState(purchase); return { oldTxState, newTxState }; }, diff --git a/packages/taler-wallet-core/src/shepherd.ts b/packages/taler-wallet-core/src/shepherd.ts index ee55e1da1..0f285eb39 100644 --- a/packages/taler-wallet-core/src/shepherd.ts +++ b/packages/taler-wallet-core/src/shepherd.ts @@ -432,8 +432,13 @@ export class TaskSchedulerImpl implements TaskScheduler { } case TaskRunResultType.ScheduleLater: { logger.trace(`Shepherd for ${taskId} got schedule-later result.`); - await storeTaskProgress(this.ws, taskId); - const delay = AbsoluteTime.remaining(res.runAt); + const retryRecord = await storePendingTaskPending( + this.ws, + taskId, + res.runAt, + ); + const t = timestampAbsoluteFromDb(retryRecord.retryInfo.nextRetry); + const delay = AbsoluteTime.remaining(t); logger.trace(`Waiting for ${delay.d_ms} ms`); await this.wait(taskId, info, delay); break; @@ -515,6 +520,7 @@ async function storeTaskProgress( async function storePendingTaskPending( ws: InternalWalletState, pendingTaskId: string, + schedTime?: AbsoluteTime, ): Promise { logger.trace(`storing task [pending] for ${pendingTaskId}`); const res = await ws.db.runAllStoresReadWriteTx({}, async (tx) => { @@ -532,6 +538,11 @@ async function storePendingTaskPending( delete retryRecord.lastError; retryRecord.retryInfo = DbRetryInfo.increment(retryRecord.retryInfo); } + if (schedTime) { + retryRecord.retryInfo.nextRetry = timestampPreciseToDb( + AbsoluteTime.toPreciseTimestamp(schedTime), + ); + } await tx.operationRetries.put(retryRecord); let notification: WalletNotification | undefined = undefined; if (hadError) { -- cgit v1.2.3 From 09bb08db467efb0449c22b8958454d9ce1c5d1f2 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Fri, 7 Jun 2024 19:12:53 +0200 Subject: bump versions to 0.11.3 --- packages/taler-wallet-core/src/pay-merchant.ts | 3 +++ 1 file changed, 3 insertions(+) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts index badbd32f4..41e1b3c9e 100644 --- a/packages/taler-wallet-core/src/pay-merchant.ts +++ b/packages/taler-wallet-core/src/pay-merchant.ts @@ -1624,6 +1624,9 @@ export async function checkPayForTemplate( throw TalerError.fromUncheckedDetail(cfg.detail); } + // FIXME: Put body.currencies *and* body.currency in the set of + // supported currencies. + return { templateDetails, supportedCurrencies: Object.keys(cfg.body.currencies), -- cgit v1.2.3 From b36a1cb508fc1dd5421dc92221d25a115c0f3eab Mon Sep 17 00:00:00 2001 From: Sebastian Date: Fri, 7 Jun 2024 15:53:18 -0300 Subject: takin currency from instructed --- packages/taler-wallet-core/src/transactions.ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/transactions.ts b/packages/taler-wallet-core/src/transactions.ts index bcf3fcaf6..0f7b1c3ca 100644 --- a/packages/taler-wallet-core/src/transactions.ts +++ b/packages/taler-wallet-core/src/transactions.ts @@ -739,12 +739,15 @@ function buildTransactionForBankIntegratedWithdraw( if (wg.wgInfo.withdrawalType !== WithdrawalRecordType.BankIntegrated) { throw Error(""); } - checkDbInvariant(wg.wgInfo.bankInfo.currency !== undefined, "wg uninitialized"); + const instructedCurrency = + wg.instructedAmount === undefined + ? undefined + : Amounts.currencyOf(wg.instructedAmount); + const currency = wg.wgInfo.bankInfo.currency ?? instructedCurrency; + checkDbInvariant(currency !== undefined, "wg uninitialized (missing currency)"); const txState = computeWithdrawalTransactionStatus(wg); - - const zero = Amounts.stringify( - Amounts.zeroOfCurrency(wg.wgInfo.bankInfo.currency), - ); + + const zero = Amounts.stringify(Amounts.zeroOfCurrency(currency)); return { type: TransactionType.Withdrawal, txState, -- cgit v1.2.3 From ccdc69ac4941188eb3d63947f6031a7a1cc6c6af Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 10 Jun 2024 14:39:17 +0200 Subject: wallet-core: report 'retry' as part of transaction actions where applicable --- packages/taler-wallet-core/src/deposits.ts | 24 +++++++-- packages/taler-wallet-core/src/pay-merchant.ts | 57 +++++++++++++++++----- .../taler-wallet-core/src/pay-peer-pull-credit.ts | 35 ++++++++++--- .../taler-wallet-core/src/pay-peer-pull-debit.ts | 2 +- .../taler-wallet-core/src/pay-peer-push-credit.ts | 25 ++++++++-- .../taler-wallet-core/src/pay-peer-push-debit.ts | 35 ++++++++++--- packages/taler-wallet-core/src/withdraw.ts | 14 +++--- 7 files changed, 151 insertions(+), 41 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts index 32b22c1ef..2004c12cb 100644 --- a/packages/taler-wallet-core/src/deposits.ts +++ b/packages/taler-wallet-core/src/deposits.ts @@ -387,11 +387,19 @@ export function computeDepositTransactionActions( case DepositOperationStatus.Finished: return [TransactionAction.Delete]; case DepositOperationStatus.PendingDeposit: - return [TransactionAction.Suspend, TransactionAction.Abort]; + return [ + TransactionAction.Retry, + TransactionAction.Suspend, + TransactionAction.Abort, + ]; case DepositOperationStatus.SuspendedDeposit: return [TransactionAction.Resume]; case DepositOperationStatus.Aborting: - return [TransactionAction.Fail, TransactionAction.Suspend]; + return [ + TransactionAction.Retry, + TransactionAction.Fail, + TransactionAction.Suspend, + ]; case DepositOperationStatus.Aborted: return [TransactionAction.Delete]; case DepositOperationStatus.Failed: @@ -399,9 +407,17 @@ export function computeDepositTransactionActions( case DepositOperationStatus.SuspendedAborting: return [TransactionAction.Resume, TransactionAction.Fail]; case DepositOperationStatus.PendingKyc: - return [TransactionAction.Suspend, TransactionAction.Fail]; + return [ + TransactionAction.Retry, + TransactionAction.Suspend, + TransactionAction.Fail, + ]; case DepositOperationStatus.PendingTrack: - return [TransactionAction.Suspend, TransactionAction.Abort]; + return [ + TransactionAction.Retry, + TransactionAction.Suspend, + TransactionAction.Abort, + ]; case DepositOperationStatus.SuspendedKyc: return [TransactionAction.Resume, TransactionAction.Fail]; case DepositOperationStatus.SuspendedTrack: diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts index 41e1b3c9e..91ea4bd1d 100644 --- a/packages/taler-wallet-core/src/pay-merchant.ts +++ b/packages/taler-wallet-core/src/pay-merchant.ts @@ -34,7 +34,6 @@ import { assertUnreachable, AsyncFlag, checkDbInvariant, - CheckPaymentResponse, CheckPayTemplateReponse, CheckPayTemplateRequest, codecForAbortResponse, @@ -1027,11 +1026,17 @@ async function storeFirstPaySuccess( purchase.merchantPaySig = payResponse.sig; purchase.posConfirmation = payResponse.pos_confirmation; const dl = purchase.download; - checkDbInvariant(!!dl, `purchase ${purchase.orderId} without ct downloaded`); + checkDbInvariant( + !!dl, + `purchase ${purchase.orderId} without ct downloaded`, + ); const contractTermsRecord = await tx.contractTerms.get( dl.contractTermsHash, ); - checkDbInvariant(!!contractTermsRecord, `no contract terms found for purchase ${purchase.orderId}`); + checkDbInvariant( + !!contractTermsRecord, + `no contract terms found for purchase ${purchase.orderId}`, + ); const contractData = extractContractData( contractTermsRecord.contractTermsRaw, dl.contractTermsHash, @@ -2650,21 +2655,45 @@ export function computePayMerchantTransactionActions( switch (purchaseRecord.purchaseStatus) { // Pending States case PurchaseStatus.PendingDownloadingProposal: - return [TransactionAction.Suspend, TransactionAction.Abort]; + return [ + TransactionAction.Retry, + TransactionAction.Suspend, + TransactionAction.Abort, + ]; case PurchaseStatus.PendingPaying: - return [TransactionAction.Suspend, TransactionAction.Abort]; + return [ + TransactionAction.Retry, + TransactionAction.Suspend, + TransactionAction.Abort, + ]; case PurchaseStatus.PendingPayingReplay: // Special "abort" since it goes back to "done". - return [TransactionAction.Suspend, TransactionAction.Abort]; + return [ + TransactionAction.Retry, + TransactionAction.Suspend, + TransactionAction.Abort, + ]; case PurchaseStatus.PendingQueryingAutoRefund: // Special "abort" since it goes back to "done". - return [TransactionAction.Suspend, TransactionAction.Abort]; + return [ + TransactionAction.Retry, + TransactionAction.Suspend, + TransactionAction.Abort, + ]; case PurchaseStatus.PendingQueryingRefund: // Special "abort" since it goes back to "done". - return [TransactionAction.Suspend, TransactionAction.Abort]; + return [ + TransactionAction.Retry, + TransactionAction.Suspend, + TransactionAction.Abort, + ]; case PurchaseStatus.PendingAcceptRefund: // Special "abort" since it goes back to "done". - return [TransactionAction.Suspend, TransactionAction.Abort]; + return [ + TransactionAction.Retry, + TransactionAction.Suspend, + TransactionAction.Abort, + ]; // Suspended Pending States case PurchaseStatus.SuspendedDownloadingProposal: return [TransactionAction.Resume, TransactionAction.Abort]; @@ -2684,14 +2713,18 @@ export function computePayMerchantTransactionActions( return [TransactionAction.Resume, TransactionAction.Abort]; // Aborting States case PurchaseStatus.AbortingWithRefund: - return [TransactionAction.Fail, TransactionAction.Suspend]; + return [ + TransactionAction.Retry, + TransactionAction.Fail, + TransactionAction.Suspend, + ]; case PurchaseStatus.SuspendedAbortingWithRefund: return [TransactionAction.Fail, TransactionAction.Resume]; // Dialog States case PurchaseStatus.DialogProposed: - return []; + return [TransactionAction.Retry]; case PurchaseStatus.DialogShared: - return []; + return [TransactionAction.Retry]; // Final States case PurchaseStatus.AbortedProposalRefused: case PurchaseStatus.AbortedOrderDeleted: diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts index 14b3eeaf0..b7fb13da3 100644 --- a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts +++ b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts @@ -1039,7 +1039,10 @@ export async function initiatePeerPullPayment( const withdrawalGroupId = encodeCrock(getRandomBytes(32)); const mergeReserveRowId = mergeReserveInfo.rowId; - checkDbInvariant(!!mergeReserveRowId, `merge reserve for ${exchangeBaseUrl} without rowid`); + checkDbInvariant( + !!mergeReserveRowId, + `merge reserve for ${exchangeBaseUrl} without rowid`, + ); const contractEncNonce = encodeCrock(getRandomBytes(24)); @@ -1184,15 +1187,31 @@ export function computePeerPullCreditTransactionActions( ): TransactionAction[] { switch (pullCreditRecord.status) { case PeerPullPaymentCreditStatus.PendingCreatePurse: - return [TransactionAction.Abort, TransactionAction.Suspend]; + return [ + TransactionAction.Retry, + TransactionAction.Abort, + TransactionAction.Suspend, + ]; case PeerPullPaymentCreditStatus.PendingMergeKycRequired: - return [TransactionAction.Abort, TransactionAction.Suspend]; + return [ + TransactionAction.Retry, + TransactionAction.Abort, + TransactionAction.Suspend, + ]; case PeerPullPaymentCreditStatus.PendingReady: - return [TransactionAction.Abort, TransactionAction.Suspend]; + return [ + TransactionAction.Retry, + TransactionAction.Abort, + TransactionAction.Suspend, + ]; case PeerPullPaymentCreditStatus.Done: return [TransactionAction.Delete]; case PeerPullPaymentCreditStatus.PendingWithdrawing: - return [TransactionAction.Abort, TransactionAction.Suspend]; + return [ + TransactionAction.Retry, + TransactionAction.Abort, + TransactionAction.Suspend, + ]; case PeerPullPaymentCreditStatus.SuspendedCreatePurse: return [TransactionAction.Resume, TransactionAction.Abort]; case PeerPullPaymentCreditStatus.SuspendedReady: @@ -1204,7 +1223,11 @@ export function computePeerPullCreditTransactionActions( case PeerPullPaymentCreditStatus.Aborted: return [TransactionAction.Delete]; case PeerPullPaymentCreditStatus.AbortingDeletePurse: - return [TransactionAction.Suspend, TransactionAction.Fail]; + return [ + TransactionAction.Retry, + TransactionAction.Suspend, + TransactionAction.Fail, + ]; case PeerPullPaymentCreditStatus.Failed: return [TransactionAction.Delete]; case PeerPullPaymentCreditStatus.Expired: diff --git a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts index 0355b58ad..e9be15026 100644 --- a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts +++ b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts @@ -1000,7 +1000,7 @@ export function computePeerPullDebitTransactionActions( ): TransactionAction[] { switch (pullDebitRecord.status) { case PeerPullDebitRecordStatus.DialogProposed: - return []; + return [TransactionAction.Retry, TransactionAction.Delete]; case PeerPullDebitRecordStatus.PendingDeposit: return [TransactionAction.Abort, TransactionAction.Suspend]; case PeerPullDebitRecordStatus.Done: diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts index 1476a0f4b..6d9f329e5 100644 --- a/packages/taler-wallet-core/src/pay-peer-push-credit.ts +++ b/packages/taler-wallet-core/src/pay-peer-push-credit.ts @@ -872,7 +872,10 @@ export async function processPeerPushCredit( `processing peerPushCredit in state ${peerInc.status.toString(16)}`, ); - checkDbInvariant(!!contractTerms, `not contract terms for peer push ${peerPushCreditId}`); + checkDbInvariant( + !!contractTerms, + `not contract terms for peer push ${peerPushCreditId}`, + ); switch (peerInc.status) { case PeerPushCreditStatus.PendingMergeKycRequired: { @@ -1011,15 +1014,27 @@ export function computePeerPushCreditTransactionActions( ): TransactionAction[] { switch (pushCreditRecord.status) { case PeerPushCreditStatus.DialogProposed: - return [TransactionAction.Delete]; + return [TransactionAction.Retry, TransactionAction.Delete]; case PeerPushCreditStatus.PendingMerge: - return [TransactionAction.Abort, TransactionAction.Suspend]; + return [ + TransactionAction.Retry, + TransactionAction.Abort, + TransactionAction.Suspend, + ]; case PeerPushCreditStatus.Done: return [TransactionAction.Delete]; case PeerPushCreditStatus.PendingMergeKycRequired: - return [TransactionAction.Abort, TransactionAction.Suspend]; + return [ + TransactionAction.Retry, + TransactionAction.Abort, + TransactionAction.Suspend, + ]; case PeerPushCreditStatus.PendingWithdrawing: - return [TransactionAction.Suspend, TransactionAction.Fail]; + return [ + TransactionAction.Retry, + TransactionAction.Suspend, + TransactionAction.Fail, + ]; case PeerPushCreditStatus.SuspendedMerge: return [TransactionAction.Resume, TransactionAction.Abort]; case PeerPushCreditStatus.SuspendedMergeKycRequired: diff --git a/packages/taler-wallet-core/src/pay-peer-push-debit.ts b/packages/taler-wallet-core/src/pay-peer-push-debit.ts index 3a936fb04..f8e6adb3c 100644 --- a/packages/taler-wallet-core/src/pay-peer-push-debit.ts +++ b/packages/taler-wallet-core/src/pay-peer-push-debit.ts @@ -406,7 +406,10 @@ async function handlePurseCreationConflict( const instructedAmount = Amounts.parseOrThrow(peerPushInitiation.amount); const sel = peerPushInitiation.coinSel; - checkDbInvariant(!!sel, `no coin selected for peer push initiation ${peerPushInitiation.pursePub}`); + checkDbInvariant( + !!sel, + `no coin selected for peer push initiation ${peerPushInitiation.pursePub}`, + ); const repair: PreviousPayCoins = []; @@ -1218,17 +1221,37 @@ export function computePeerPushDebitTransactionActions( ): TransactionAction[] { switch (ppiRecord.status) { case PeerPushDebitStatus.PendingCreatePurse: - return [TransactionAction.Abort, TransactionAction.Suspend]; + return [ + TransactionAction.Retry, + TransactionAction.Abort, + TransactionAction.Suspend, + ]; case PeerPushDebitStatus.PendingReady: - return [TransactionAction.Abort, TransactionAction.Suspend]; + return [ + TransactionAction.Retry, + TransactionAction.Abort, + TransactionAction.Suspend, + ]; case PeerPushDebitStatus.Aborted: return [TransactionAction.Delete]; case PeerPushDebitStatus.AbortingDeletePurse: - return [TransactionAction.Suspend, TransactionAction.Fail]; + return [ + TransactionAction.Retry, + TransactionAction.Suspend, + TransactionAction.Fail, + ]; case PeerPushDebitStatus.AbortingRefreshDeleted: - return [TransactionAction.Suspend, TransactionAction.Fail]; + return [ + TransactionAction.Retry, + TransactionAction.Suspend, + TransactionAction.Fail, + ]; case PeerPushDebitStatus.AbortingRefreshExpired: - return [TransactionAction.Suspend, TransactionAction.Fail]; + return [ + TransactionAction.Retry, + TransactionAction.Suspend, + TransactionAction.Fail, + ]; case PeerPushDebitStatus.SuspendedAbortingRefreshExpired: return [TransactionAction.Resume, TransactionAction.Fail]; case PeerPushDebitStatus.SuspendedAbortingDeletePurse: diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index dbd7e8673..b88b1d5c6 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -731,15 +731,15 @@ export function computeWithdrawalTransactionActions( case WithdrawalGroupStatus.Done: return [TransactionAction.Delete]; case WithdrawalGroupStatus.PendingRegisteringBank: - return [TransactionAction.Suspend, TransactionAction.Abort]; + return [TransactionAction.Retry, TransactionAction.Suspend, TransactionAction.Abort]; case WithdrawalGroupStatus.PendingReady: - return [TransactionAction.Suspend, TransactionAction.Abort]; + return [TransactionAction.Retry, TransactionAction.Suspend, TransactionAction.Abort]; case WithdrawalGroupStatus.PendingQueryingStatus: - return [TransactionAction.Suspend, TransactionAction.Abort]; + return [TransactionAction.Retry, TransactionAction.Suspend, TransactionAction.Abort]; case WithdrawalGroupStatus.PendingWaitConfirmBank: - return [TransactionAction.Suspend, TransactionAction.Abort]; + return [TransactionAction.Retry, TransactionAction.Suspend, TransactionAction.Abort]; case WithdrawalGroupStatus.AbortingBank: - return [TransactionAction.Suspend, TransactionAction.Fail]; + return [TransactionAction.Retry, TransactionAction.Suspend, TransactionAction.Fail]; case WithdrawalGroupStatus.SuspendedAbortingBank: return [TransactionAction.Resume, TransactionAction.Fail]; case WithdrawalGroupStatus.SuspendedQueryingStatus: @@ -751,9 +751,9 @@ export function computeWithdrawalTransactionActions( case WithdrawalGroupStatus.SuspendedReady: return [TransactionAction.Resume, TransactionAction.Abort]; case WithdrawalGroupStatus.PendingAml: - return [TransactionAction.Resume, TransactionAction.Abort]; + return [TransactionAction.Retry, TransactionAction.Resume, TransactionAction.Abort]; case WithdrawalGroupStatus.PendingKyc: - return [TransactionAction.Resume, TransactionAction.Abort]; + return [TransactionAction.Retry, TransactionAction.Resume, TransactionAction.Abort]; case WithdrawalGroupStatus.SuspendedAml: return [TransactionAction.Resume, TransactionAction.Abort]; case WithdrawalGroupStatus.SuspendedKyc: -- cgit v1.2.3 From 3e46e9c06a3846da827093833457dbda61e4d13a Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 10 Jun 2024 14:45:15 +0200 Subject: wallet-core: fix duplicated transaction states, remove some dead code --- packages/taler-wallet-core/src/db.ts | 105 ++++------------------------------- 1 file changed, 10 insertions(+), 95 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 4ec83a783..6bf212c6f 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -920,92 +920,6 @@ export interface CoinAllocation { amount: AmountString; } -/** - * Status of a reward we got from a merchant. - */ -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: DbPreciseTimestamp | undefined; - - /** - * The tipped amount. - */ - rewardAmountRaw: AmountString; - - /** - * Effect on the balance (including fees etc). - */ - rewardAmountEffective: AmountString; - - /** - * Timestamp, the tip can't be picked up anymore after this deadline. - */ - rewardExpiration: DbProtocolTimestamp; - - /** - * The exchange that will sign our coins, chosen by the merchant. - */ - exchangeBaseUrl: string; - - /** - * Base URL of the merchant that is giving us the tip. - */ - merchantBaseUrl: string; - - /** - * Denomination selection made by the wallet for picking up - * this tip. - * - * FIXME: Put this into some DenomSelectionCacheRecord instead of - * storing it here! - */ - denomsSel: DenomSelectionState; - - denomSelUid: string; - - /** - * Tip ID chosen by the wallet. - */ - walletRewardId: string; - - /** - * Secret seed used to derive planchets for this tip. - */ - secretSeed: string; - - /** - * The merchant's identifier for this reward. - */ - merchantRewardId: string; - - createdTimestamp: DbPreciseTimestamp; - - /** - * The url to be redirected after the tip is accepted. - */ - next_url: string | undefined; - - /** - * Timestamp for when the wallet finished picking up the tip - * from the merchant. - */ - pickedUpTimestamp: DbPreciseTimestamp | undefined; - - status: RewardRecordStatus; -} - -export enum RewardRecordStatus { - PendingPickup = 0x0100_0000, - SuspendedPickup = 0x0110_0000, - DialogAccept = 0x0101_0000, - Done = 0x0500_0000, - Aborted = 0x0500_0000, - Failed = 0x0501_000, -} - export enum RefreshCoinStatus { Pending = 0x0100_0000, Finished = 0x0500_0000, @@ -1198,11 +1112,6 @@ export enum PurchaseStatus { */ DialogShared = 0x0101_0001, - /** - * The user has rejected the proposal. - */ - AbortedProposalRefused = 0x0503_0000, - /** * Downloading or processing the proposal has failed permanently. */ @@ -1226,13 +1135,18 @@ export enum PurchaseStatus { DoneRepurchaseDetected = 0x0500_0001, /** - * The payment has been aborted. + * The user has rejected the proposal. */ - AbortedIncompletePayment = 0x0503_0000, + AbortedProposalRefused = 0x0503_0000, AbortedRefunded = 0x0503_0001, AbortedOrderDeleted = 0x0503_0002, + + /** + * The payment has been aborted. + */ + AbortedIncompletePayment = 0x0503_0003, } /** @@ -1979,7 +1893,7 @@ export enum PeerPullPaymentCreditStatus { SuspendedCreatePurse = 0x0110_0000, SuspendedReady = 0x0110_0001, SuspendedMergeKycRequired = 0x0110_0002, - SuspendedWithdrawing = 0x0110_0000, + SuspendedWithdrawing = 0x0110_0003, SuspendedAbortingDeletePurse = 0x0113_0000, @@ -2633,9 +2547,10 @@ export const WalletStoresV1 = { ]), }, ), + // Just a tombstone at this point. rewards: describeStore( "rewards", - describeContents({ keyPath: "walletRewardId" }), + describeContents({ keyPath: "walletRewardId" }), { byMerchantTipIdAndBaseUrl: describeIndex("byMerchantRewardIdAndBaseUrl", [ "merchantRewardId", -- cgit v1.2.3 From d1da42300ed989cae9bf0c77b1a841f1bee86738 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 10 Jun 2024 14:51:11 +0200 Subject: -dce --- packages/taler-wallet-core/src/common.ts | 4 ---- 1 file changed, 4 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts index a9e962dda..a20278cf3 100644 --- a/packages/taler-wallet-core/src/common.ts +++ b/packages/taler-wallet-core/src/common.ts @@ -58,7 +58,6 @@ import { PurchaseRecord, RecoupGroupRecord, RefreshGroupRecord, - RewardRecord, WalletDbReadWriteTransaction, WithdrawalGroupRecord, timestampPreciseToDb, @@ -682,9 +681,6 @@ export namespace TaskIdentifiers { exchBaseUrl, )}` as TaskIdStr; } - export function forTipPickup(tipRecord: RewardRecord): TaskIdStr { - return `${PendingTaskType.RewardPickup}:${tipRecord.walletRewardId}` as TaskIdStr; - } export function forRefresh( refreshGroupRecord: RefreshGroupRecord, ): TaskIdStr { -- cgit v1.2.3 From 472899f066ce29b86bdc6b02a621f755a7c3b260 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 10 Jun 2024 16:04:18 +0200 Subject: wallet-core: introduce finalizing state --- packages/taler-wallet-core/src/balance.ts | 8 ++--- packages/taler-wallet-core/src/db.ts | 12 +++++-- packages/taler-wallet-core/src/pay-merchant.ts | 45 +++++++++++++++++++++++--- packages/taler-wallet-core/src/shepherd.ts | 8 ++--- packages/taler-wallet-core/src/transactions.ts | 40 +++++++++++------------ packages/taler-wallet-core/src/versions.ts | 2 +- 6 files changed, 79 insertions(+), 36 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/balance.ts b/packages/taler-wallet-core/src/balance.ts index 4f06e3756..381028906 100644 --- a/packages/taler-wallet-core/src/balance.ts +++ b/packages/taler-wallet-core/src/balance.ts @@ -69,8 +69,8 @@ import { ExchangeRestrictionSpec, findMatchingWire } from "./coinSelection.js"; import { DepositOperationStatus, ExchangeEntryDbRecordStatus, - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, + OPERATION_STATUS_NONFINAL_FIRST, + OPERATION_STATUS_NONFINAL_LAST, PeerPushDebitStatus, RefreshGroupRecord, RefreshOperationStatus, @@ -304,8 +304,8 @@ export async function getBalancesInsideTransaction( const balanceStore: BalancesStore = new BalancesStore(wex, tx); const keyRangeActive = GlobalIDB.KeyRange.bound( - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, + OPERATION_STATUS_NONFINAL_FIRST, + OPERATION_STATUS_NONFINAL_LAST, ); await tx.exchanges.iter().forEachAsync(async (ex) => { diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 6bf212c6f..5c381eea7 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -248,6 +248,9 @@ export function timestampOptionalAbsoluteFromDb( * 0x0103_nnnn: aborting * 0x0110_nnnn: suspended * 0x0113_nnnn: suspended-aborting + * a=2: finalizing + * 0x0200_nnnn: finalizing + * 0x0210_nnnn: suspended-finalizing * a=5: final * 0x0500_nnnn: done * 0x0501_nnnn: failed @@ -260,12 +263,12 @@ export function timestampOptionalAbsoluteFromDb( /** * First possible operation status in the active range (inclusive). */ -export const OPERATION_STATUS_ACTIVE_FIRST = 0x0100_0000; +export const OPERATION_STATUS_NONFINAL_FIRST = 0x0100_0000; /** * LAST possible operation status in the active range (inclusive). */ -export const OPERATION_STATUS_ACTIVE_LAST = 0x0113_ffff; +export const OPERATION_STATUS_NONFINAL_LAST = 0x0210_ffff; /** * Status of a withdrawal. @@ -1094,10 +1097,15 @@ export enum PurchaseStatus { /** * Query for refund (until auto-refund deadline is reached). + * + * Legacy state for compatibility. */ PendingQueryingAutoRefund = 0x0100_0004, SuspendedQueryingAutoRefund = 0x0110_0004, + FinalizingQueryingAutoRefund = 0x0200_0001, + SuspendedFinalizingQueryingAutoRefund = 0x0210_0001, + PendingAcceptRefund = 0x0100_0005, SuspendedPendingAcceptRefund = 0x0110_0005, diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts index 91ea4bd1d..ee154252f 100644 --- a/packages/taler-wallet-core/src/pay-merchant.ts +++ b/packages/taler-wallet-core/src/pay-merchant.ts @@ -2093,6 +2093,7 @@ export async function processPurchase( case PurchaseStatus.PendingPayingReplay: return processPurchasePay(wex, proposalId); case PurchaseStatus.PendingQueryingRefund: + case PurchaseStatus.FinalizingQueryingAutoRefund: return processPurchaseQueryRefund(wex, purchase); case PurchaseStatus.PendingQueryingAutoRefund: return processPurchaseAutoRefund(wex, purchase); @@ -2117,6 +2118,7 @@ export async function processPurchase( case PurchaseStatus.SuspendedPendingAcceptRefund: case PurchaseStatus.SuspendedQueryingAutoRefund: case PurchaseStatus.SuspendedQueryingRefund: + case PurchaseStatus.SuspendedFinalizingQueryingAutoRefund: case PurchaseStatus.FailedAbort: case PurchaseStatus.FailedPaidByOther: return TaskRunResult.finished(); @@ -2494,6 +2496,9 @@ const transitionSuspend: { [PurchaseStatus.PendingQueryingAutoRefund]: { next: PurchaseStatus.SuspendedQueryingAutoRefund, }, + [PurchaseStatus.FinalizingQueryingAutoRefund]: { + next: PurchaseStatus.SuspendedFinalizingQueryingAutoRefund, + }, }; const transitionResume: { @@ -2516,6 +2521,9 @@ const transitionResume: { [PurchaseStatus.SuspendedQueryingAutoRefund]: { next: PurchaseStatus.PendingQueryingAutoRefund, }, + [PurchaseStatus.SuspendedFinalizingQueryingAutoRefund]: { + next: PurchaseStatus.FinalizingQueryingAutoRefund, + }, }; export function computePayMerchantTransactionState( @@ -2644,6 +2652,16 @@ export function computePayMerchantTransactionState( major: TransactionMajorState.Failed, minor: TransactionMinorState.PaidByOther, }; + case PurchaseStatus.FinalizingQueryingAutoRefund: + return { + major: TransactionMajorState.Finalizing, + minor: TransactionMinorState.AutoRefund, + }; + case PurchaseStatus.SuspendedFinalizingQueryingAutoRefund: + return { + major: TransactionMajorState.SuspendedFinalizing, + minor: TransactionMinorState.AutoRefund, + }; default: assertUnreachable(purchaseRecord.purchaseStatus); } @@ -2742,6 +2760,14 @@ export function computePayMerchantTransactionActions( return [TransactionAction.Delete]; case PurchaseStatus.FailedPaidByOther: return [TransactionAction.Delete]; + case PurchaseStatus.FinalizingQueryingAutoRefund: + return [ + TransactionAction.Suspend, + TransactionAction.Retry, + TransactionAction.Delete, + ]; + case PurchaseStatus.SuspendedFinalizingQueryingAutoRefund: + return [TransactionAction.Resume, TransactionAction.Delete]; default: assertUnreachable(purchaseRecord.purchaseStatus); } @@ -2944,8 +2970,12 @@ async function processPurchaseAutoRefund( logger.warn("purchase does not exist anymore"); return; } - if (p.purchaseStatus !== PurchaseStatus.PendingQueryingAutoRefund) { - return; + switch (p.purchaseStatus) { + case PurchaseStatus.PendingQueryingAutoRefund: + case PurchaseStatus.FinalizingQueryingAutoRefund: + break; + default: + return; } const oldTxState = computePayMerchantTransactionState(p); p.purchaseStatus = PurchaseStatus.Done; @@ -2991,8 +3021,12 @@ async function processPurchaseAutoRefund( logger.warn("purchase does not exist anymore"); return; } - if (p.purchaseStatus !== PurchaseStatus.PendingQueryingAutoRefund) { - return; + switch (p.purchaseStatus) { + case PurchaseStatus.PendingQueryingAutoRefund: + case PurchaseStatus.FinalizingQueryingAutoRefund: + break; + default: + return; } const oldTxState = computePayMerchantTransactionState(p); p.purchaseStatus = PurchaseStatus.PendingAcceptRefund; @@ -3536,7 +3570,8 @@ async function storeRefunds( if (isAborting) { myPurchase.purchaseStatus = PurchaseStatus.AbortedRefunded; } else if (shouldCheckAutoRefund) { - myPurchase.purchaseStatus = PurchaseStatus.PendingQueryingAutoRefund; + myPurchase.purchaseStatus = + PurchaseStatus.FinalizingQueryingAutoRefund; } else { myPurchase.purchaseStatus = PurchaseStatus.Done; } diff --git a/packages/taler-wallet-core/src/shepherd.ts b/packages/taler-wallet-core/src/shepherd.ts index 0f285eb39..470f45aff 100644 --- a/packages/taler-wallet-core/src/shepherd.ts +++ b/packages/taler-wallet-core/src/shepherd.ts @@ -50,8 +50,8 @@ import { parseTaskIdentifier, } from "./common.js"; import { - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, + OPERATION_STATUS_NONFINAL_FIRST, + OPERATION_STATUS_NONFINAL_LAST, OperationRetryRecord, WalletDbAllStoresReadOnlyTransaction, WalletDbReadOnlyTransaction, @@ -1012,8 +1012,8 @@ export async function getActiveTaskIds( }, async (tx) => { const active = GlobalIDB.KeyRange.bound( - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, + OPERATION_STATUS_NONFINAL_FIRST, + OPERATION_STATUS_NONFINAL_LAST, ); // Withdrawals diff --git a/packages/taler-wallet-core/src/transactions.ts b/packages/taler-wallet-core/src/transactions.ts index 0f7b1c3ca..7782d09ba 100644 --- a/packages/taler-wallet-core/src/transactions.ts +++ b/packages/taler-wallet-core/src/transactions.ts @@ -62,8 +62,8 @@ import { DenomLossEventRecord, DepositElementStatus, DepositGroupRecord, - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, + OPERATION_STATUS_NONFINAL_FIRST, + OPERATION_STATUS_NONFINAL_LAST, OperationRetryRecord, PeerPullCreditRecord, PeerPullDebitRecordStatus, @@ -1968,8 +1968,8 @@ async function iterRecordsForWithdrawal( let withdrawalGroupRecords: WithdrawalGroupRecord[]; if (filter.onlyState === "nonfinal") { const keyRange = GlobalIDB.KeyRange.bound( - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, + OPERATION_STATUS_NONFINAL_FIRST, + OPERATION_STATUS_NONFINAL_LAST, ); withdrawalGroupRecords = await tx.withdrawalGroups.indexes.byStatus.getAll(keyRange); @@ -1990,8 +1990,8 @@ async function iterRecordsForDeposit( let dgs: DepositGroupRecord[]; if (filter.onlyState === "nonfinal") { const keyRange = GlobalIDB.KeyRange.bound( - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, + OPERATION_STATUS_NONFINAL_FIRST, + OPERATION_STATUS_NONFINAL_LAST, ); dgs = await tx.depositGroups.indexes.byStatus.getAll(keyRange); } else { @@ -2011,8 +2011,8 @@ async function iterRecordsForDenomLoss( let dgs: DenomLossEventRecord[]; if (filter.onlyState === "nonfinal") { const keyRange = GlobalIDB.KeyRange.bound( - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, + OPERATION_STATUS_NONFINAL_FIRST, + OPERATION_STATUS_NONFINAL_LAST, ); dgs = await tx.denomLossEvents.indexes.byStatus.getAll(keyRange); } else { @@ -2031,8 +2031,8 @@ async function iterRecordsForRefund( ): Promise { if (filter.onlyState === "nonfinal") { const keyRange = GlobalIDB.KeyRange.bound( - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, + OPERATION_STATUS_NONFINAL_FIRST, + OPERATION_STATUS_NONFINAL_LAST, ); await tx.refundGroups.indexes.byStatus.iter(keyRange).forEachAsync(f); } else { @@ -2047,8 +2047,8 @@ async function iterRecordsForPurchase( ): Promise { if (filter.onlyState === "nonfinal") { const keyRange = GlobalIDB.KeyRange.bound( - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, + OPERATION_STATUS_NONFINAL_FIRST, + OPERATION_STATUS_NONFINAL_LAST, ); await tx.purchases.indexes.byStatus.iter(keyRange).forEachAsync(f); } else { @@ -2063,8 +2063,8 @@ async function iterRecordsForPeerPullCredit( ): Promise { if (filter.onlyState === "nonfinal") { const keyRange = GlobalIDB.KeyRange.bound( - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, + OPERATION_STATUS_NONFINAL_FIRST, + OPERATION_STATUS_NONFINAL_LAST, ); await tx.peerPullCredit.indexes.byStatus.iter(keyRange).forEachAsync(f); } else { @@ -2079,8 +2079,8 @@ async function iterRecordsForPeerPullDebit( ): Promise { if (filter.onlyState === "nonfinal") { const keyRange = GlobalIDB.KeyRange.bound( - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, + OPERATION_STATUS_NONFINAL_FIRST, + OPERATION_STATUS_NONFINAL_LAST, ); await tx.peerPullDebit.indexes.byStatus.iter(keyRange).forEachAsync(f); } else { @@ -2095,8 +2095,8 @@ async function iterRecordsForPeerPushDebit( ): Promise { if (filter.onlyState === "nonfinal") { const keyRange = GlobalIDB.KeyRange.bound( - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, + OPERATION_STATUS_NONFINAL_FIRST, + OPERATION_STATUS_NONFINAL_LAST, ); await tx.peerPushDebit.indexes.byStatus.iter(keyRange).forEachAsync(f); } else { @@ -2111,8 +2111,8 @@ async function iterRecordsForPeerPushCredit( ): Promise { if (filter.onlyState === "nonfinal") { const keyRange = GlobalIDB.KeyRange.bound( - OPERATION_STATUS_ACTIVE_FIRST, - OPERATION_STATUS_ACTIVE_LAST, + OPERATION_STATUS_NONFINAL_FIRST, + OPERATION_STATUS_NONFINAL_LAST, ); await tx.peerPushCredit.indexes.byStatus.iter(keyRange).forEachAsync(f); } else { diff --git a/packages/taler-wallet-core/src/versions.ts b/packages/taler-wallet-core/src/versions.ts index d33a23cdd..8c1ac5fc2 100644 --- a/packages/taler-wallet-core/src/versions.ts +++ b/packages/taler-wallet-core/src/versions.ts @@ -52,7 +52,7 @@ export const WALLET_BANK_CONVERSION_API_PROTOCOL_VERSION = "2:0:0"; /** * Libtool version of the wallet-core API. */ -export const WALLET_CORE_API_PROTOCOL_VERSION = "5:0:0"; +export const WALLET_CORE_API_PROTOCOL_VERSION = "7:0:0"; /** * Libtool rules: -- cgit v1.2.3 From 99e73bee7070477b66e0f1db29ac03da92f128f8 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 10 Jun 2024 19:56:29 +0200 Subject: wallet-core: fix long-polling issue We must specify the old_state, because otherwise the withdrawal operation status returns query immediately in state "selected", causing the withdrawal to go into back-off. --- packages/taler-wallet-core/src/withdraw.ts | 45 ++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 8 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index b88b1d5c6..6aa3b186a 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -731,15 +731,35 @@ export function computeWithdrawalTransactionActions( case WithdrawalGroupStatus.Done: return [TransactionAction.Delete]; case WithdrawalGroupStatus.PendingRegisteringBank: - return [TransactionAction.Retry, TransactionAction.Suspend, TransactionAction.Abort]; + return [ + TransactionAction.Retry, + TransactionAction.Suspend, + TransactionAction.Abort, + ]; case WithdrawalGroupStatus.PendingReady: - return [TransactionAction.Retry, TransactionAction.Suspend, TransactionAction.Abort]; + return [ + TransactionAction.Retry, + TransactionAction.Suspend, + TransactionAction.Abort, + ]; case WithdrawalGroupStatus.PendingQueryingStatus: - return [TransactionAction.Retry, TransactionAction.Suspend, TransactionAction.Abort]; + return [ + TransactionAction.Retry, + TransactionAction.Suspend, + TransactionAction.Abort, + ]; case WithdrawalGroupStatus.PendingWaitConfirmBank: - return [TransactionAction.Retry, TransactionAction.Suspend, TransactionAction.Abort]; + return [ + TransactionAction.Retry, + TransactionAction.Suspend, + TransactionAction.Abort, + ]; case WithdrawalGroupStatus.AbortingBank: - return [TransactionAction.Retry, TransactionAction.Suspend, TransactionAction.Fail]; + return [ + TransactionAction.Retry, + TransactionAction.Suspend, + TransactionAction.Fail, + ]; case WithdrawalGroupStatus.SuspendedAbortingBank: return [TransactionAction.Resume, TransactionAction.Fail]; case WithdrawalGroupStatus.SuspendedQueryingStatus: @@ -751,9 +771,17 @@ export function computeWithdrawalTransactionActions( case WithdrawalGroupStatus.SuspendedReady: return [TransactionAction.Resume, TransactionAction.Abort]; case WithdrawalGroupStatus.PendingAml: - return [TransactionAction.Retry, TransactionAction.Resume, TransactionAction.Abort]; + return [ + TransactionAction.Retry, + TransactionAction.Resume, + TransactionAction.Abort, + ]; case WithdrawalGroupStatus.PendingKyc: - return [TransactionAction.Retry, TransactionAction.Resume, TransactionAction.Abort]; + return [ + TransactionAction.Retry, + TransactionAction.Resume, + TransactionAction.Abort, + ]; case WithdrawalGroupStatus.SuspendedAml: return [TransactionAction.Resume, TransactionAction.Abort]; case WithdrawalGroupStatus.SuspendedKyc: @@ -1934,7 +1962,7 @@ async function processWithdrawalGroupPendingReady( "can't get funding uri from uninitialized wg", ); const exchangeBaseUrl = withdrawalGroup.exchangeBaseUrl; - logger.trace(`updating exchange beofre processing wg`) + logger.trace(`updating exchange beofre processing wg`); await fetchFreshExchange(wex, withdrawalGroup.exchangeBaseUrl); if (withdrawalGroup.denomsSel.selectedDenoms.length === 0) { @@ -2617,6 +2645,7 @@ async function processReserveBankStatus( uriResult.bankIntegrationApiBaseUrl, ); bankStatusUrl.searchParams.set("long_poll_ms", "30000"); + bankStatusUrl.searchParams.set("old_state", "selected"); logger.info(`long-polling for withdrawal operation at ${bankStatusUrl.href}`); const statusResp = await wex.http.fetch(bankStatusUrl.href, { -- cgit v1.2.3 From 689d58a5c79e98fec733c3cda31146620d8bde1d Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 10 Jun 2024 15:25:51 -0300 Subject: fix #8886 --- packages/taler-wallet-core/src/dbless.ts | 2 +- packages/taler-wallet-core/src/withdraw.ts | 69 +++++++++++++++++++++++------- 2 files changed, 55 insertions(+), 16 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/dbless.ts b/packages/taler-wallet-core/src/dbless.ts index d3085ecb4..ec9655e6f 100644 --- a/packages/taler-wallet-core/src/dbless.ts +++ b/packages/taler-wallet-core/src/dbless.ts @@ -123,7 +123,7 @@ export async function topupReserveWithBank(args: TopupReserveWithBankArgs) { ); const bankInfo = await getBankWithdrawalInfo(http, wopi.taler_withdraw_uri); const bankStatusUrl = getBankStatusUrl(wopi.taler_withdraw_uri); - if (!bankInfo.suggestedExchange) { + if (!bankInfo.exchange) { throw Error("no suggested exchange"); } const plainPaytoUris = diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index 6aa3b186a..0434aefc2 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -44,6 +44,7 @@ import { Duration, EddsaPrivateKeyString, ExchangeBatchWithdrawRequest, + ExchangeListItem, ExchangeUpdateStatus, ExchangeWireAccount, ExchangeWithdrawBatchResponse, @@ -150,6 +151,7 @@ import { getExchangePaytoUri, getExchangeWireDetailsInTx, listExchanges, + lookupExchangeByUri, markExchangeUsed, } from "./exchanges.js"; import { DbAccess } from "./query.js"; @@ -886,7 +888,7 @@ export async function getBankWithdrawalInfo( TalerErrorCode.WALLET_BANK_INTEGRATION_PROTOCOL_VERSION_INCOMPATIBLE, { bankProtocolVersion: config.version, - walletProtocolVersion: WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, + walletProtocolVersion: bankApi.PROTOCOL_VERSION, }, "bank integration protocol version not compatible with wallet", ); @@ -901,15 +903,36 @@ export async function getBankWithdrawalInfo( } const { body: status } = resp; + const maxAmount = + status.max_amount === undefined + ? undefined + : Amounts.parseOrThrow(status.max_amount); + let amount: AmountJson | undefined; - if (status.amount) { + let editableAmount = false; + if (status.amount !== undefined) { amount = Amounts.parseOrThrow(status.amount); + } else { + amount = + status.suggested_amount === undefined + ? undefined + : Amounts.parseOrThrow(status.suggested_amount); + editableAmount = true; } + let wireFee: AmountJson | undefined; if (status.card_fees) { wireFee = Amounts.parseOrThrow(status.card_fees); } + let exchange: string | undefined = undefined; + let editableExchange = false; + if (status.required_exchange !== undefined) { + exchange = status.required_exchange; + } else { + exchange = status.suggested_exchange; + editableExchange = true; + } return { operationId: uriResult.withdrawalOperationId, apiBaseUrl: uriResult.bankIntegrationApiBaseUrl, @@ -918,7 +941,10 @@ export async function getBankWithdrawalInfo( wireFee, confirmTransferUrl: status.confirm_transfer_url, senderWire: status.sender_wire, - suggestedExchange: status.suggested_exchange, + exchange, + editableAmount, + editableExchange, + maxAmount, wireTypes: status.wire_types, status: status.status, }; @@ -2328,39 +2354,52 @@ export async function getWithdrawalDetailsForUri( logger.trace(`getting withdrawal details for URI ${talerWithdrawUri}`); const info = await getBankWithdrawalInfo(wex.http, talerWithdrawUri); logger.trace(`got bank info`); - if (info.suggestedExchange) { + if (info.exchange) { try { // If the exchange entry doesn't exist yet, // it'll be created as an ephemeral entry. - await fetchFreshExchange(wex, info.suggestedExchange); + await fetchFreshExchange(wex, info.exchange); } catch (e) { // We still continued if it failed, as other exchanges might be available. // We don't want to fail if the bank-suggested exchange is broken/offline. logger.trace( - `querying bank-suggested exchange (${info.suggestedExchange}) failed`, + `querying bank-suggested exchange (${info.exchange}) failed`, ); } } const currency = info.currency; - const listExchangesResp = await listExchanges(wex); - const possibleExchanges = listExchangesResp.exchanges.filter((x) => { - return ( - x.currency === currency && - (x.exchangeUpdateStatus === ExchangeUpdateStatus.Ready || - x.exchangeUpdateStatus === ExchangeUpdateStatus.ReadyUpdate) - ); - }); + let possibleExchanges: ExchangeListItem[]; + if (!info.editableExchange && info.exchange !== undefined) { + const ex: ExchangeListItem = await lookupExchangeByUri(wex, { + exchangeBaseUrl: info.exchange, + }); + possibleExchanges = [ex]; + } else { + const listExchangesResp = await listExchanges(wex); + + possibleExchanges = listExchangesResp.exchanges.filter((x) => { + return ( + x.currency === currency && + (x.exchangeUpdateStatus === ExchangeUpdateStatus.Ready || + x.exchangeUpdateStatus === ExchangeUpdateStatus.ReadyUpdate) + ); + }); + } return { operationId: info.operationId, confirmTransferUrl: info.confirmTransferUrl, status: info.status, currency, + editableAmount: info.editableAmount, + editableExchange: info.editableExchange, + maxAmount: info.maxAmount ? Amounts.stringify(info.maxAmount) : undefined, amount: info.amount ? Amounts.stringify(info.amount) : undefined, - defaultExchangeBaseUrl: info.suggestedExchange, + defaultExchangeBaseUrl: info.exchange, possibleExchanges, + wireFee: info.wireFee ? Amounts.stringify(info.wireFee) : undefined, }; } -- cgit v1.2.3 From 11648f3bfc7e87b9472602b95a2f05cfdd964a4c Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 11 Jun 2024 14:46:04 +0200 Subject: wallet-core: introduce 'missing-tos' tosStatus for exchange entries --- packages/taler-wallet-core/src/common.ts | 19 ++++++++-- packages/taler-wallet-core/src/exchanges.ts | 57 +++++++++++++++++++++++------ 2 files changed, 61 insertions(+), 15 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts index a20278cf3..4b4f73185 100644 --- a/packages/taler-wallet-core/src/common.ts +++ b/packages/taler-wallet-core/src/common.ts @@ -41,7 +41,6 @@ import { checkDbInvariant, checkLogicInvariant, durationMul, - j2s, } from "@gnu-taler/taler-util"; import { BackupProviderRecord, @@ -121,7 +120,10 @@ export async function makeCoinAvailable( coinRecord.exchangeBaseUrl, coinRecord.denomPubHash, ]); - checkDbInvariant(!!denom, `denomination of a coin is missing hash: ${coinRecord.denomPubHash}`); + checkDbInvariant( + !!denom, + `denomination of a coin is missing hash: ${coinRecord.denomPubHash}`, + ); const ageRestriction = coinRecord.maxAge; let car = await tx.coinAvailability.get([ coinRecord.exchangeBaseUrl, @@ -175,13 +177,19 @@ export async function spendCoins( coin.exchangeBaseUrl, coin.denomPubHash, ); - checkDbInvariant(!!denom, `denomination of a coin is missing hash: ${coin.denomPubHash}`); + checkDbInvariant( + !!denom, + `denomination of a coin is missing hash: ${coin.denomPubHash}`, + ); const coinAvailability = await tx.coinAvailability.get([ coin.exchangeBaseUrl, coin.denomPubHash, coin.maxAge, ]); - checkDbInvariant(!!coinAvailability, `age denom info is missing for ${coin.maxAge}`); + checkDbInvariant( + !!coinAvailability, + `age denom info is missing for ${coin.maxAge}`, + ); const contrib = csi.contributions[i]; if (coin.status !== CoinStatus.Fresh) { const alloc = coin.spendAllocation; @@ -257,6 +265,9 @@ export enum TombstoneTag { export function getExchangeTosStatusFromRecord( exchange: ExchangeEntryRecord, ): ExchangeTosStatus { + if (exchange.tosCurrentEtag == null) { + return ExchangeTosStatus.MissingTos; + } if (!exchange.tosAcceptedEtag) { return ExchangeTosStatus.Proposed; } diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts index adb696de0..51c702ca9 100644 --- a/packages/taler-wallet-core/src/exchanges.ts +++ b/packages/taler-wallet-core/src/exchanges.ts @@ -53,6 +53,7 @@ import { GetExchangeResourcesResponse, GetExchangeTosResult, GlobalFees, + HttpStatusCode, LibtoolVersion, Logger, NotificationType, @@ -93,6 +94,8 @@ import { getExpiry, readSuccessResponseJsonOrThrow, readSuccessResponseTextOrThrow, + readTalerErrorResponse, + throwUnexpectedRequestError, } from "@gnu-taler/taler-util/http"; import { PendingTaskType, @@ -861,6 +864,40 @@ async function downloadExchangeKeysInfo( }; } +type TosMetaResult = { type: "not-found" } | { type: "ok"; etag: string }; + +/** + * Download metadata about an exchange's terms of service. + */ +async function downloadTosMeta( + wex: WalletExecutionContext, + exchangeBaseUrl: string, +): Promise { + logger.trace(`downloading exchange tos metadata for ${exchangeBaseUrl}`); + const reqUrl = new URL("terms", exchangeBaseUrl); + + // FIXME: We can/should make a HEAD request here. + // Not sure if qtart supports it at the moment. + const resp = await wex.http.fetch(reqUrl.href, { + cancellationToken: wex.cancellationToken, + }); + + switch (resp.status) { + case HttpStatusCode.NotFound: + return { type: "not-found" }; + case HttpStatusCode.Ok: + break; + default: + throwUnexpectedRequestError(resp, await readTalerErrorResponse(resp)); + } + + const etag = resp.headers.get("etag") || "unknown"; + return { + type: "ok", + etag, + }; +} + async function downloadTosFromAcceptedFormat( wex: WalletExecutionContext, baseUrl: string, @@ -1367,7 +1404,6 @@ export async function updateExchangeFromUrlHandler( AbsoluteTime.min(nextUpdateStamp, nextRefreshCheckStamp), ); } - } // When doing the auto-refresh check, we always update @@ -1423,15 +1459,7 @@ export async function updateExchangeFromUrlHandler( logger.trace("finished validating exchange /wire info"); - // We download the text/plain version here, - // because that one needs to exist, and we - // will get the current etag from the response. - const tosDownload = await downloadTosFromAcceptedFormat( - wex, - exchangeBaseUrl, - timeout, - ["text/plain"], - ); + const tosMeta = await downloadTosMeta(wex, exchangeBaseUrl); logger.trace("updating exchange info in database"); @@ -1524,7 +1552,14 @@ export async function updateExchangeFromUrlHandler( }; r.noFees = noFees; r.peerPaymentsDisabled = peerPaymentsDisabled; - r.tosCurrentEtag = tosDownload.tosEtag; + switch (tosMeta.type) { + case "not-found": + r.tosCurrentEtag = undefined; + break; + case "ok": + r.tosCurrentEtag = tosMeta.etag; + break; + } if (existingDetails?.rowId) { newDetails.rowId = existingDetails.rowId; } -- cgit v1.2.3 From 2a590ca82f3b63edcaeeba054961e6947df7a651 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 11 Jun 2024 14:56:25 +0200 Subject: wallet-core: refactor wait function --- packages/taler-wallet-core/src/exchanges.ts | 277 ++++++++++++---------------- 1 file changed, 122 insertions(+), 155 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts index 51c702ca9..be46e4352 100644 --- a/packages/taler-wallet-core/src/exchanges.ts +++ b/packages/taler-wallet-core/src/exchanges.ts @@ -28,7 +28,6 @@ import { AgeRestriction, Amount, Amounts, - AsyncFlag, CancellationToken, CoinRefreshRequest, CoinStatus, @@ -80,6 +79,7 @@ import { WireInfo, assertUnreachable, checkDbInvariant, + checkLogicInvariant, codecForExchangeKeysJson, durationMul, encodeCrock, @@ -106,6 +106,7 @@ import { TransactionContext, computeDbBackoff, constructTaskIdentifier, + genericWaitForState, getAutoRefreshExecuteThreshold, getExchangeEntryStatusFromRecord, getExchangeState, @@ -1045,132 +1046,6 @@ export interface ReadyExchangeSummary { scopeInfo: ScopeInfo; } -async function internalWaitReadyExchange( - wex: WalletExecutionContext, - canonUrl: string, - exchangeNotifFlag: AsyncFlag, - options: { - cancellationToken?: CancellationToken; - forceUpdate?: boolean; - expectedMasterPub?: string; - } = {}, -): Promise { - const operationId = constructTaskIdentifier({ - tag: PendingTaskType.ExchangeUpdate, - exchangeBaseUrl: canonUrl, - }); - while (true) { - if (wex.cancellationToken.isCancelled) { - throw Error("cancelled"); - } - logger.info(`waiting for ready exchange ${canonUrl}`); - const { exchange, exchangeDetails, retryInfo, scopeInfo } = - await wex.db.runReadOnlyTx( - { - storeNames: [ - "exchanges", - "exchangeDetails", - "operationRetries", - "globalCurrencyAuditors", - "globalCurrencyExchanges", - ], - }, - async (tx) => { - const exchange = await tx.exchanges.get(canonUrl); - const exchangeDetails = await getExchangeRecordsInternal( - tx, - canonUrl, - ); - const retryInfo = await tx.operationRetries.get(operationId); - let scopeInfo: ScopeInfo | undefined = undefined; - if (exchange && exchangeDetails) { - scopeInfo = await internalGetExchangeScopeInfo(tx, exchangeDetails); - } - return { exchange, exchangeDetails, retryInfo, scopeInfo }; - }, - ); - - if (!exchange) { - throw Error("exchange entry does not exist anymore"); - } - - let ready = false; - - switch (exchange.updateStatus) { - case ExchangeEntryDbUpdateStatus.Ready: - ready = true; - break; - case ExchangeEntryDbUpdateStatus.ReadyUpdate: - // If the update is forced, - // we wait until we're in a full "ready" state, - // as we're not happy with the stale information. - if (!options.forceUpdate) { - ready = true; - } - break; - case ExchangeEntryDbUpdateStatus.UnavailableUpdate: - throw TalerError.fromDetail( - TalerErrorCode.WALLET_EXCHANGE_UNAVAILABLE, - { - exchangeBaseUrl: canonUrl, - innerError: retryInfo?.lastError, - }, - ); - default: { - if (retryInfo) { - throw TalerError.fromDetail( - TalerErrorCode.WALLET_EXCHANGE_UNAVAILABLE, - { - exchangeBaseUrl: canonUrl, - innerError: retryInfo?.lastError, - }, - ); - } - } - } - - if (!ready) { - logger.info("waiting for exchange update notification"); - await exchangeNotifFlag.wait(); - logger.info("done waiting for exchange update notification"); - exchangeNotifFlag.reset(); - continue; - } - - if (!exchangeDetails) { - throw Error("invariant failed"); - } - - if (!scopeInfo) { - throw Error("invariant failed"); - } - - const res: ReadyExchangeSummary = { - currency: exchangeDetails.currency, - exchangeBaseUrl: canonUrl, - masterPub: exchangeDetails.masterPublicKey, - tosStatus: getExchangeTosStatusFromRecord(exchange), - tosAcceptedEtag: exchange.tosAcceptedEtag, - wireInfo: exchangeDetails.wireInfo, - protocolVersionRange: exchangeDetails.protocolVersionRange, - tosCurrentEtag: exchange.tosCurrentEtag, - tosAcceptedTimestamp: timestampOptionalPreciseFromDb( - exchange.tosAcceptedTimestamp, - ), - scopeInfo, - }; - - if (options.expectedMasterPub) { - if (res.masterPub !== options.expectedMasterPub) { - throw Error( - "public key of the exchange does not match expected public key", - ); - } - } - return res; - } -} - /** * Ensure that a fresh exchange entry exists for the given * exchange base URL. @@ -1223,39 +1098,131 @@ async function waitReadyExchange( } = {}, ): Promise { logger.trace(`waiting for exchange ${canonUrl} to become ready`); - // FIXME: We should use Symbol.dispose magic here for cleanup! - const exchangeNotifFlag = new AsyncFlag(); - // Raise exchangeNotifFlag whenever we get a notification - // about our exchange. - const cancelNotif = wex.ws.addNotificationListener((notif) => { - if ( - notif.type === NotificationType.ExchangeStateTransition && - notif.exchangeBaseUrl === canonUrl - ) { - logger.info(`raising update notification: ${j2s(notif)}`); - exchangeNotifFlag.raise(); - } + const operationId = constructTaskIdentifier({ + tag: PendingTaskType.ExchangeUpdate, + exchangeBaseUrl: canonUrl, }); - const unregisterOnCancelled = wex.cancellationToken.onCancelled(() => { - cancelNotif(); - exchangeNotifFlag.raise(); + let res: ReadyExchangeSummary | undefined = undefined; + + await genericWaitForState(wex, { + filterNotification(notif): boolean { + return ( + notif.type === NotificationType.ExchangeStateTransition && + notif.exchangeBaseUrl === canonUrl + ); + }, + async checkState(): Promise { + const { exchange, exchangeDetails, retryInfo, scopeInfo } = + await wex.db.runReadOnlyTx( + { + storeNames: [ + "exchanges", + "exchangeDetails", + "operationRetries", + "globalCurrencyAuditors", + "globalCurrencyExchanges", + ], + }, + async (tx) => { + const exchange = await tx.exchanges.get(canonUrl); + const exchangeDetails = await getExchangeRecordsInternal( + tx, + canonUrl, + ); + const retryInfo = await tx.operationRetries.get(operationId); + let scopeInfo: ScopeInfo | undefined = undefined; + if (exchange && exchangeDetails) { + scopeInfo = await internalGetExchangeScopeInfo( + tx, + exchangeDetails, + ); + } + return { exchange, exchangeDetails, retryInfo, scopeInfo }; + }, + ); + + if (!exchange) { + throw Error("exchange entry does not exist anymore"); + } + + let ready = false; + + switch (exchange.updateStatus) { + case ExchangeEntryDbUpdateStatus.Ready: + ready = true; + break; + case ExchangeEntryDbUpdateStatus.ReadyUpdate: + // If the update is forced, + // we wait until we're in a full "ready" state, + // as we're not happy with the stale information. + if (!options.forceUpdate) { + ready = true; + } + break; + case ExchangeEntryDbUpdateStatus.UnavailableUpdate: + throw TalerError.fromDetail( + TalerErrorCode.WALLET_EXCHANGE_UNAVAILABLE, + { + exchangeBaseUrl: canonUrl, + innerError: retryInfo?.lastError, + }, + ); + default: { + if (retryInfo) { + throw TalerError.fromDetail( + TalerErrorCode.WALLET_EXCHANGE_UNAVAILABLE, + { + exchangeBaseUrl: canonUrl, + innerError: retryInfo?.lastError, + }, + ); + } + } + } + + if (!ready) { + return false; + } + + if (!exchangeDetails) { + throw Error("invariant failed"); + } + + if (!scopeInfo) { + throw Error("invariant failed"); + } + + const mySummary: ReadyExchangeSummary = { + currency: exchangeDetails.currency, + exchangeBaseUrl: canonUrl, + masterPub: exchangeDetails.masterPublicKey, + tosStatus: getExchangeTosStatusFromRecord(exchange), + tosAcceptedEtag: exchange.tosAcceptedEtag, + wireInfo: exchangeDetails.wireInfo, + protocolVersionRange: exchangeDetails.protocolVersionRange, + tosCurrentEtag: exchange.tosCurrentEtag, + tosAcceptedTimestamp: timestampOptionalPreciseFromDb( + exchange.tosAcceptedTimestamp, + ), + scopeInfo, + }; + + if (options.expectedMasterPub) { + if (mySummary.masterPub !== options.expectedMasterPub) { + throw Error( + "public key of the exchange does not match expected public key", + ); + } + } + res = mySummary; + return true; + }, }); - try { - const res = await internalWaitReadyExchange( - wex, - canonUrl, - exchangeNotifFlag, - options, - ); - logger.info("done waiting for ready exchange"); - return res; - } finally { - unregisterOnCancelled(); - cancelNotif(); - } + checkLogicInvariant(!!res); + return res; } function checkPeerPaymentsDisabled( -- cgit v1.2.3 From 81b4a53a42c23e6f8e6b7e193a173b2c9cc35e83 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 11 Jun 2024 15:39:34 +0200 Subject: wallet-core: fix bank integration API protocol range --- packages/taler-wallet-core/src/versions.ts | 7 ------- packages/taler-wallet-core/src/wallet.ts | 21 +++++++++++++++------ packages/taler-wallet-core/src/withdraw.ts | 5 +---- 3 files changed, 16 insertions(+), 17 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/versions.ts b/packages/taler-wallet-core/src/versions.ts index 8c1ac5fc2..8b4b24351 100644 --- a/packages/taler-wallet-core/src/versions.ts +++ b/packages/taler-wallet-core/src/versions.ts @@ -28,13 +28,6 @@ export const WALLET_EXCHANGE_PROTOCOL_VERSION = "17:0:0"; */ export const WALLET_MERCHANT_PROTOCOL_VERSION = "5:0:1"; -/** - * Protocol version spoken with the bank (bank integration API). - * - * Uses libtool's current:revision:age versioning. - */ -export const WALLET_BANK_INTEGRATION_PROTOCOL_VERSION = "1:0:0"; - /** * Protocol version spoken with the bank (corebank API). * diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index f1d53b7d5..7a69fcb21 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -56,6 +56,7 @@ import { PrepareWithdrawExchangeResponse, RecoverStoredBackupRequest, StoredBackupList, + TalerBankIntegrationHttpClient, TalerError, TalerErrorCode, TalerProtocolTimestamp, @@ -295,7 +296,6 @@ import { } from "./transactions.js"; import { WALLET_BANK_CONVERSION_API_PROTOCOL_VERSION, - WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, WALLET_COREBANK_API_PROTOCOL_VERSION, WALLET_CORE_API_PROTOCOL_VERSION, WALLET_EXCHANGE_PROTOCOL_VERSION, @@ -479,7 +479,10 @@ async function setCoinSuspended( c.denomPubHash, c.maxAge, ]); - checkDbInvariant(!!coinAvailability, `no denom info for ${c.denomPubHash} age ${c.maxAge}`); + checkDbInvariant( + !!coinAvailability, + `no denom info for ${c.denomPubHash} age ${c.maxAge}`, + ); if (suspended) { if (c.status !== CoinStatus.Fresh) { return; @@ -1396,7 +1399,10 @@ async function dispatchRequestInternal( return; } wex.ws.exchangeCache.clear(); - checkDbInvariant(!!existingRec.id, `no global exchange for ${j2s(key)}`); + checkDbInvariant( + !!existingRec.id, + `no global exchange for ${j2s(key)}`, + ); await tx.globalCurrencyExchanges.delete(existingRec.id); }, ); @@ -1445,7 +1451,10 @@ async function dispatchRequestInternal( if (!existingRec) { return; } - checkDbInvariant(!!existingRec.id, `no global currency for ${j2s(key)}`); + checkDbInvariant( + !!existingRec.id, + `no global currency for ${j2s(key)}`, + ); await tx.globalCurrencyAuditors.delete(existingRec.id); wex.ws.exchangeCache.clear(); }, @@ -1580,9 +1589,9 @@ export function getVersion(wex: WalletExecutionContext): WalletCoreVersion { exchange: WALLET_EXCHANGE_PROTOCOL_VERSION, merchant: WALLET_MERCHANT_PROTOCOL_VERSION, bankConversionApiRange: WALLET_BANK_CONVERSION_API_PROTOCOL_VERSION, - bankIntegrationApiRange: WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, + bankIntegrationApiRange: TalerBankIntegrationHttpClient.PROTOCOL_VERSION, corebankApiRange: WALLET_COREBANK_API_PROTOCOL_VERSION, - bank: WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, + bank: TalerBankIntegrationHttpClient.PROTOCOL_VERSION, devMode: wex.ws.config.testing.devModeActive, }; return result; diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index 0434aefc2..e90aa1dd3 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -162,10 +162,7 @@ import { notifyTransition, parseTransactionIdentifier, } from "./transactions.js"; -import { - WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, - WALLET_EXCHANGE_PROTOCOL_VERSION, -} from "./versions.js"; +import { WALLET_EXCHANGE_PROTOCOL_VERSION } from "./versions.js"; import { WalletExecutionContext, getDenomInfo } from "./wallet.js"; /** -- cgit v1.2.3 From ce89223b51e71dae83d00fa93a0ab1c09b3c190a Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 11 Jun 2024 16:39:09 +0200 Subject: wallet-core: check that ToS was accepted for certain requests --- packages/taler-wallet-core/src/common.ts | 22 ++++++++++++++++++++++ packages/taler-wallet-core/src/exchanges.ts | 1 + .../taler-wallet-core/src/pay-peer-pull-credit.ts | 4 +++- .../taler-wallet-core/src/pay-peer-push-credit.ts | 4 +++- packages/taler-wallet-core/src/withdraw.ts | 3 +++ 5 files changed, 32 insertions(+), 2 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts index 4b4f73185..13c875575 100644 --- a/packages/taler-wallet-core/src/common.ts +++ b/packages/taler-wallet-core/src/common.ts @@ -31,6 +31,8 @@ import { ExchangeUpdateStatus, Logger, RefreshReason, + TalerError, + TalerErrorCode, TalerErrorDetail, TalerPreciseTimestamp, TalerProtocolTimestamp, @@ -61,6 +63,7 @@ import { WithdrawalGroupRecord, timestampPreciseToDb, } from "./db.js"; +import { ReadyExchangeSummary } from "./exchanges.js"; import { createRefreshGroup } from "./refresh.js"; import { WalletExecutionContext, getDenomInfo } from "./wallet.js"; @@ -829,3 +832,22 @@ export async function genericWaitForState( throw e; } } + +export function requireExchangeTosAcceptedOrThrow( + exchange: ReadyExchangeSummary, +): void { + switch (exchange.tosStatus) { + case ExchangeTosStatus.Accepted: + case ExchangeTosStatus.MissingTos: + break; + default: + throw TalerError.fromDetail( + TalerErrorCode.WALLET_EXCHANGE_TOS_NOT_ACCEPTED, + { + exchangeBaseUrl: exchange.exchangeBaseUrl, + currentEtag: exchange.tosCurrentEtag, + tosStatus: exchange.tosStatus, + }, + ); + } +} diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts index be46e4352..dd88fa836 100644 --- a/packages/taler-wallet-core/src/exchanges.ts +++ b/packages/taler-wallet-core/src/exchanges.ts @@ -885,6 +885,7 @@ async function downloadTosMeta( switch (resp.status) { case HttpStatusCode.NotFound: + case HttpStatusCode.NotImplemented: return { type: "not-found" }; case HttpStatusCode.Ok: break; diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts index b7fb13da3..d3c4f0d7f 100644 --- a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts +++ b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts @@ -59,6 +59,7 @@ import { TombstoneTag, TransactionContext, constructTaskIdentifier, + requireExchangeTosAcceptedOrThrow, } from "./common.js"; import { KycPendingInfo, @@ -1021,7 +1022,8 @@ export async function initiatePeerPullPayment( const exchangeBaseUrl = maybeExchangeBaseUrl; - await fetchFreshExchange(wex, exchangeBaseUrl); + const exchange = await fetchFreshExchange(wex, exchangeBaseUrl); + requireExchangeTosAcceptedOrThrow(exchange); const mergeReserveInfo = await getMergeReserveInfo(wex, { exchangeBaseUrl: exchangeBaseUrl, diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts index 6d9f329e5..ae2372eeb 100644 --- a/packages/taler-wallet-core/src/pay-peer-push-credit.ts +++ b/packages/taler-wallet-core/src/pay-peer-push-credit.ts @@ -61,6 +61,7 @@ import { TombstoneTag, TransactionContext, constructTaskIdentifier, + requireExchangeTosAcceptedOrThrow, } from "./common.js"; import { KycPendingInfo, @@ -407,7 +408,8 @@ export async function preparePeerPushCredit( const exchangeBaseUrl = uri.exchangeBaseUrl; - await fetchFreshExchange(wex, exchangeBaseUrl); + const exchange = await fetchFreshExchange(wex, exchangeBaseUrl); + requireExchangeTosAcceptedOrThrow(exchange); const contractPriv = uri.contractPriv; const contractPub = encodeCrock(eddsaGetPublic(decodeCrock(contractPriv))); diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index e90aa1dd3..d7a1c6d24 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -45,6 +45,7 @@ import { EddsaPrivateKeyString, ExchangeBatchWithdrawRequest, ExchangeListItem, + ExchangeTosStatus, ExchangeUpdateStatus, ExchangeWireAccount, ExchangeWithdrawBatchResponse, @@ -118,6 +119,7 @@ import { genericWaitForState, makeCoinAvailable, makeCoinsVisible, + requireExchangeTosAcceptedOrThrow, } from "./common.js"; import { EddsaKeypair } from "./crypto/cryptoImplementation.js"; import { @@ -3118,6 +3120,7 @@ export async function confirmWithdrawal( } const exchange = await fetchFreshExchange(wex, selectedExchange); + requireExchangeTosAcceptedOrThrow(exchange); const talerWithdrawUri = withdrawalGroup.wgInfo.bankInfo.talerWithdrawUri; const confirmUrl = withdrawalGroup.wgInfo.bankInfo.confirmUrl; -- cgit v1.2.3 From f9d4ff5b43e48a07ac81d7e7ef800ddb12f5f90a Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 12 Jun 2024 12:38:28 -0300 Subject: do not throw error when asking withdrawal info for instructed amount 0 --- packages/taler-wallet-core/src/pay-peer-pull-credit.ts | 10 ++++++++++ packages/taler-wallet-core/src/pay-peer-push-credit.ts | 6 ++++++ packages/taler-wallet-core/src/withdraw.ts | 8 -------- 3 files changed, 16 insertions(+), 8 deletions(-) (limited to 'packages/taler-wallet-core/src') diff --git a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts index d3c4f0d7f..3e7fdd36b 100644 --- a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts +++ b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts @@ -934,6 +934,11 @@ export async function checkPeerPullPaymentInitiation( Amounts.parseOrThrow(req.amount), undefined, ); + if (wi.selectedDenoms.selectedDenoms.length === 0) { + throw Error( + `unable to check pull payment from ${exchangeUrl}, can't select denominations for instructed amount (${req.amount}`, + ); + } logger.trace(`got withdrawal info`); @@ -1054,6 +1059,11 @@ export async function initiatePeerPullPayment( Amounts.parseOrThrow(req.partialContractTerms.amount), undefined, ); + if (wi.selectedDenoms.selectedDenoms.length === 0) { + throw Error( + `unable to initiate pull payment from ${exchangeBaseUrl}, can't select denominations for instructed amount (${req.partialContractTerms.amount}`, + ); + } const mergeTimestamp = TalerPreciseTimestamp.now(); diff --git a/packages/taler-wallet-core/src/pay-peer-push-credit.ts b/packages/taler-wallet-core/src/pay-peer-push-credit.ts index ae2372eeb..5a1bfbdbd 100644 --- a/packages/taler-wallet-core/src/pay-peer-push-credit.ts +++ b/packages/taler-wallet-core/src/pay-peer-push-credit.ts @@ -461,6 +461,12 @@ export async function preparePeerPushCredit( undefined, ); + if (wi.selectedDenoms.selectedDenoms.length === 0) { + throw Error( + `unable to prepare push credit from ${exchangeBaseUrl}, can't select denominations for instructed amount (${purseStatus.balance}`, + ); + } + const transitionInfo = await wex.db.runReadWriteTx( { storeNames: ["contractTerms", "peerPushCredit"] }, async (tx) => { diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index d7a1c6d24..8bc4aafd1 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -2267,14 +2267,6 @@ export async function getExchangeWithdrawalInfo( logger.trace("selection done"); - if (selectedDenoms.selectedDenoms.length === 0) { - throw Error( - `unable to withdraw from ${exchangeBaseUrl}, can't select denominations for instructed amount (${Amounts.stringify( - instructedAmount, - )}`, - ); - } - const exchangeWireAccounts: string[] = []; for (const account of exchange.wireInfo.accounts) { -- cgit v1.2.3