diff options
author | Florian Dold <florian@dold.me> | 2023-12-18 23:00:33 +0100 |
---|---|---|
committer | Florian Dold <florian@dold.me> | 2023-12-18 23:00:33 +0100 |
commit | f22f5f89cc99fb0556b956b6432e4817d52378d2 (patch) | |
tree | 9998bd4fd0d2b51ba5c27ebe9c2f694134c93841 /packages/taler-wallet-core | |
parent | 0ba8fe9428dc5779cc98bf4e875f60eb01966323 (diff) | |
download | wallet-core-f22f5f89cc99fb0556b956b6432e4817d52378d2.tar.xz |
wallet-core: update exchange inline, not in task
A task (such as refresh) can't wait until the exchange update task
updates the exchange, as task handlers don't run concurrently.
The previous behavior caused some transactions to block indefinitely
when waiting for an updated exchange entry.
Diffstat (limited to 'packages/taler-wallet-core')
8 files changed, 169 insertions, 181 deletions
diff --git a/packages/taler-wallet-core/src/host-impl.qtart.ts b/packages/taler-wallet-core/src/host-impl.qtart.ts index 686374beb..0fc346b44 100644 --- a/packages/taler-wallet-core/src/host-impl.qtart.ts +++ b/packages/taler-wallet-core/src/host-impl.qtart.ts @@ -30,20 +30,19 @@ import type { } from "@gnu-taler/idb-bridge"; // eslint-disable-next-line no-duplicate-imports import { + AccessStats, BridgeIDBFactory, MemoryBackend, createSqliteBackend, shimIndexedDB, } from "@gnu-taler/idb-bridge"; -import { AccessStats } from "@gnu-taler/idb-bridge"; -import { SynchronousCryptoWorkerFactoryPlain } from "./crypto/workers/synchronousWorkerFactoryPlain.js"; -import { openTalerDatabase } from "./index.js"; -import { Logger, enableNativeLogging } from "@gnu-taler/taler-util"; +import { Logger } from "@gnu-taler/taler-util"; import { createPlatformHttpLib } from "@gnu-taler/taler-util/http"; -import { SetTimeoutTimerAPI } from "./util/timer.js"; -import { Wallet } from "./wallet.js"; import { qjsOs, qjsStd } from "@gnu-taler/taler-util/qtart"; +import { SynchronousCryptoWorkerFactoryPlain } from "./crypto/workers/synchronousWorkerFactoryPlain.js"; import { DefaultNodeWalletArgs, makeTempfileId } from "./host-common.js"; +import { SetTimeoutTimerAPI } from "./util/timer.js"; +import { Wallet } from "./wallet.js"; const logger = new Logger("host-impl.qtart.ts"); diff --git a/packages/taler-wallet-core/src/internal-wallet-state.ts b/packages/taler-wallet-core/src/internal-wallet-state.ts index 20f8a7511..b1389a359 100644 --- a/packages/taler-wallet-core/src/internal-wallet-state.ts +++ b/packages/taler-wallet-core/src/internal-wallet-state.ts @@ -55,6 +55,7 @@ import { import { TimerGroup } from "./util/timer.js"; import { WalletConfig } from "./wallet-api-types.js"; import { IDBFactory } from "@gnu-taler/idb-bridge"; +import { ReadyExchangeSummary } from "./index.js"; export const EXCHANGE_COINS_LOCK = "exchange-coins-lock"; export const EXCHANGE_RESERVES_LOCK = "exchange-reserves-lock"; @@ -107,17 +108,14 @@ export interface ExchangeOperations { }>, exchangeBaseUrl: string, ): Promise<ExchangeDetailsRecord | undefined>; - updateExchangeFromUrl( + fetchFreshExchange( ws: InternalWalletState, baseUrl: string, options?: { forceNow?: boolean; cancellationToken?: CancellationToken; }, - ): Promise<{ - exchange: ExchangeEntryRecord; - exchangeDetails: ExchangeDetailsRecord; - }>; + ): Promise<ReadyExchangeSummary>; } export interface RecoupOperations { diff --git a/packages/taler-wallet-core/src/operations/exchanges.ts b/packages/taler-wallet-core/src/operations/exchanges.ts index bf7d4424a..fe6060499 100644 --- a/packages/taler-wallet-core/src/operations/exchanges.ts +++ b/packages/taler-wallet-core/src/operations/exchanges.ts @@ -37,6 +37,7 @@ import { ExchangeGlobalFees, ExchangeListItem, ExchangeSignKeyJson, + ExchangeTosStatus, ExchangeWireAccount, ExchangesListResponse, FeeDescription, @@ -84,12 +85,15 @@ import { ExchangeEntryDbRecordStatus, ExchangeEntryDbUpdateStatus, OpenedPromise, + PendingTaskType, WalletDbReadWriteTransaction, createTimeline, isWithdrawableDenom, openPromise, selectBestForOverlappingDenominations, selectMinimumFee, + timestampOptionalAbsoluteFromDb, + timestampOptionalPreciseFromDb, timestampPreciseFromDb, timestampPreciseToDb, timestampProtocolToDb, @@ -102,9 +106,11 @@ import { TaskIdentifiers, TaskRunResult, TaskRunResultType, + constructTaskIdentifier, getExchangeState, getExchangeTosStatusFromRecord, makeExchangeListItem, + runTaskWithErrorReporting, } from "./common.js"; const logger = new Logger("exchanges.ts"); @@ -629,7 +635,7 @@ async function downloadTosFromAcceptedFormat( * If the update is forced, the exchange is put into an updating state * even if the old information should still be up to date. * - * For backwards compatibility, if the exchange entry doesn't exist, + * If the exchange entry doesn't exist, * a new ephemeral entry is created. */ export async function startUpdateExchangeEntry( @@ -740,76 +746,18 @@ export function createNotificationWaiter( } /** - * Wait until an exchange entry got successfully updated. - * - * Reject with an exception if the update encountered an error. + * Basic information about an exchange in a ready state. */ -export async function waitExchangeEntryUpdated( - ws: InternalWalletState, - exchangeBaseUrl: string, - cancellationToken?: CancellationToken, -): Promise<{ - exchange: ExchangeEntryRecord; - exchangeDetails: ExchangeDetailsRecord; -}> { - exchangeBaseUrl = canonicalizeBaseUrl(exchangeBaseUrl); - - const waiter = createNotificationWaiter( - ws, - (notif) => - notif.type == NotificationType.ExchangeStateTransition && - notif.exchangeBaseUrl === exchangeBaseUrl, - ); - - const taskId = TaskIdentifiers.forExchangeUpdateFromUrl(exchangeBaseUrl); - - while (1) { - const { exchange, retryRecord } = await ws.db - .mktx((x) => [x.exchanges, x.exchangeDetails, x.operationRetries]) - .runReadOnly(async (tx) => { - const exchange = await tx.exchanges.get(exchangeBaseUrl); - const retryRecord = await tx.operationRetries.get(taskId); - return { exchange, retryRecord }; - }); - - if (!exchange) { - throw Error("exchange does not exist anymore"); - } - - switch (exchange.updateStatus) { - case ExchangeEntryDbUpdateStatus.Ready: - const details = await ws.db - .mktx((x) => [x.exchanges, x.exchangeDetails]) - .runReadOnly(async (tx) => { - return getExchangeDetails(tx, exchangeBaseUrl); - }); - if (!details) { - throw Error("exchange entry inconsistent"); - } - waiter.cancel(); - return { exchange, exchangeDetails: details }; - case ExchangeEntryDbUpdateStatus.ReadyUpdate: - case ExchangeEntryDbUpdateStatus.InitialUpdate: { - waiter.cancel(); - if (retryRecord?.lastError) { - throw TalerError.fromUncheckedDetail(retryRecord.lastError); - } - break; - } - case ExchangeEntryDbUpdateStatus.UnavailableUpdate: - waiter.cancel(); - if (retryRecord?.lastError) { - throw TalerError.fromUncheckedDetail(retryRecord.lastError); - } else { - throw Error( - "updating exchange failed, error info unavailable (bug!)", - ); - } - } - - await waiter.waitNext(); - } - throw Error("not reached"); +export interface ReadyExchangeSummary { + exchangeBaseUrl: string; + currency: string; + masterPub: string; + tosStatus: ExchangeTosStatus; + tosAcceptedEtag: string | undefined; + tosCurrentEtag: string | undefined; + wireInfo: WireInfo; + protocolVersionRange: string; + tosAcceptedTimestamp: TalerPreciseTimestamp | undefined; } /** @@ -834,21 +782,81 @@ export async function fetchFreshExchange( forceUpdate?: boolean; expectedMasterPub?: string; } = {}, -): Promise<{ - exchange: ExchangeEntryRecord; - exchangeDetails: ExchangeDetailsRecord; -}> { +): Promise<ReadyExchangeSummary> { const canonUrl = canonicalizeBaseUrl(baseUrl); - await startUpdateExchangeEntry(ws, canonUrl, { - forceUpdate: options.forceUpdate, + const operationId = constructTaskIdentifier({ + tag: PendingTaskType.ExchangeUpdate, + exchangeBaseUrl: canonUrl, }); - const res = await waitExchangeEntryUpdated( - ws, - canonUrl, - options.cancellationToken, - ); + + const oldExchange = await ws.db + .mktx((x) => [x.exchanges]) + .runReadOnly(async (tx) => { + return tx.exchanges.get(canonUrl); + }); + + let needsUpdate = false; + + if (!oldExchange || options.forceUpdate) { + needsUpdate = true; + await startUpdateExchangeEntry(ws, canonUrl, { + forceUpdate: options.forceUpdate, + }); + } else { + const nextUpdate = timestampOptionalAbsoluteFromDb( + oldExchange.nextUpdateStamp, + ); + if (nextUpdate == null || AbsoluteTime.isExpired(nextUpdate)) { + needsUpdate = true; + } + } + + if (needsUpdate) { + await runTaskWithErrorReporting(ws, operationId, () => + updateExchangeFromUrlHandler(ws, canonUrl), + ); + } + + const { exchange, exchangeDetails } = await ws.db + .mktx((x) => [x.exchanges, x.exchangeDetails]) + .runReadOnly(async (tx) => { + const exchange = await tx.exchanges.get(canonUrl); + const exchangeDetails = await getExchangeDetails(tx, canonUrl); + return { exchange, exchangeDetails }; + }); + + if (!exchange) { + throw Error("exchange entry does not exist anymore"); + } + + switch (exchange.updateStatus) { + case ExchangeEntryDbUpdateStatus.Ready: + case ExchangeEntryDbUpdateStatus.ReadyUpdate: + break; + default: + throw Error("unable to update exchange"); + } + + if (!exchangeDetails) { + 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, + ), + }; + if (options.expectedMasterPub) { - if (res.exchangeDetails.masterPublicKey !== options.expectedMasterPub) { + if (res.masterPub !== options.expectedMasterPub) { throw Error( "public key of the exchange does not match expected public key", ); @@ -1187,10 +1195,7 @@ export async function getExchangeTos( acceptLanguage?: string, ): Promise<GetExchangeTosResult> { // FIXME: download ToS in acceptable format if passed! - const { exchange, exchangeDetails } = await fetchFreshExchange( - ws, - exchangeBaseUrl, - ); + const exch = await fetchFreshExchange(ws, exchangeBaseUrl); const tosDownload = await downloadTosFromAcceptedFormat( ws, @@ -1211,12 +1216,12 @@ export async function getExchangeTos( }); return { - acceptedEtag: exchange.tosAcceptedEtag, + acceptedEtag: exch.tosAcceptedEtag, currentEtag: tosDownload.tosEtag, content: tosDownload.tosText, contentType: tosDownload.tosContentType, contentLanguage: tosDownload.tosContentLanguage, - tosStatus: getExchangeTosStatusFromRecord(exchange), + tosStatus: exch.tosStatus, tosAvailableLanguages: tosDownload.tosAvailableLanguages, }; } @@ -1364,26 +1369,29 @@ export async function getExchangeDetailedInfo( const transferFees = Object.entries( exchange.info.wireInfo.feesForType, - ).reduce((prev, [wireType, infoForType]) => { - const feesByGroup = [ - ...infoForType.map((w) => ({ - ...w, - fee: Amounts.stringify(w.closingFee), - group: "closing", - })), - ...infoForType.map((w) => ({ ...w, fee: w.wireFee, group: "wire" })), - ]; - prev[wireType] = createTimeline( - feesByGroup, - "sig", - "startStamp", - "endStamp", - "fee", - "group", - selectMinimumFee, - ); - return prev; - }, {} as Record<string, FeeDescription[]>); + ).reduce( + (prev, [wireType, infoForType]) => { + const feesByGroup = [ + ...infoForType.map((w) => ({ + ...w, + fee: Amounts.stringify(w.closingFee), + group: "closing", + })), + ...infoForType.map((w) => ({ ...w, fee: w.wireFee, group: "wire" })), + ]; + prev[wireType] = createTimeline( + feesByGroup, + "sig", + "startStamp", + "endStamp", + "fee", + "group", + selectMinimumFee, + ); + return prev; + }, + {} as Record<string, FeeDescription[]>, + ); const globalFeesByGroup = [ ...exchange.info.globalFees.map((w) => ({ diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts b/packages/taler-wallet-core/src/operations/pay-merchant.ts index 17a879d90..078f0faf9 100644 --- a/packages/taler-wallet-core/src/operations/pay-merchant.ts +++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts @@ -1278,7 +1278,7 @@ export async function generateDepositPermissions( * Run the operation handler for a payment * and return the result as a {@link ConfirmPayResult}. */ -export async function runPayForConfirmPay( +async function runPayForConfirmPay( ws: InternalWalletState, proposalId: string, ): Promise<ConfirmPayResult> { diff --git a/packages/taler-wallet-core/src/operations/refresh.ts b/packages/taler-wallet-core/src/operations/refresh.ts index 3bbbc2a4b..5ea8fae23 100644 --- a/packages/taler-wallet-core/src/operations/refresh.ts +++ b/packages/taler-wallet-core/src/operations/refresh.ts @@ -99,11 +99,7 @@ import { TaskRunResult, TaskRunResultType, } from "./common.js"; -import { - fetchFreshExchange, - startUpdateExchangeEntry, - waitExchangeEntryUpdated, -} from "./exchanges.js"; +import { fetchFreshExchange } from "./exchanges.js"; import { constructTransactionIdentifier, notifyTransition, @@ -226,10 +222,7 @@ async function provideRefreshSession( const { refreshGroup, coin } = d; - const { exchange } = await fetchFreshExchange(ws, coin.exchangeBaseUrl); - if (!exchange) { - throw Error("db inconsistent: exchange of coin not found"); - } + const exch = await fetchFreshExchange(ws, coin.exchangeBaseUrl); // FIXME: use helper functions from withdraw.ts // to update and filter withdrawable denoms. @@ -240,7 +233,7 @@ async function provideRefreshSession( const oldDenom = await ws.getDenomInfo( ws, tx, - exchange.baseUrl, + exch.exchangeBaseUrl, coin.denomPubHash, ); @@ -251,7 +244,7 @@ async function provideRefreshSession( // FIXME: use an index here, based on the withdrawal expiration time. const availableDenoms: DenominationRecord[] = await tx.denominations.indexes.byExchangeBaseUrl - .iter(exchange.baseUrl) + .iter(exch.exchangeBaseUrl) .toArray(); const availableAmount = Amounts.sub( @@ -872,7 +865,7 @@ export async function processRefreshGroup( return TaskRunResult.finished(); } // Process refresh sessions of the group in parallel. - logger.trace("processing refresh sessions for old coins"); + logger.trace("processing refresh sessions for $ old coins"); let errors: TalerErrorDetail[] = []; let inShutdown = false; const ps = refreshGroup.oldCoinPubs.map((x, i) => diff --git a/packages/taler-wallet-core/src/operations/testing.ts b/packages/taler-wallet-core/src/operations/testing.ts index a03d54d3a..d75fb54a7 100644 --- a/packages/taler-wallet-core/src/operations/testing.ts +++ b/packages/taler-wallet-core/src/operations/testing.ts @@ -581,7 +581,7 @@ export async function runIntegrationTest2( const exchangeInfo = await fetchFreshExchange(ws, args.exchangeBaseUrl); - const currency = exchangeInfo.exchangeDetails.currency; + const currency = exchangeInfo.currency; const amountToWithdraw = Amounts.parseOrThrow(`${currency}:10`); const amountToSpend = Amounts.parseOrThrow(`${currency}:2`); diff --git a/packages/taler-wallet-core/src/operations/withdraw.ts b/packages/taler-wallet-core/src/operations/withdraw.ts index 8a718dd49..3dffab7d6 100644 --- a/packages/taler-wallet-core/src/operations/withdraw.ts +++ b/packages/taler-wallet-core/src/operations/withdraw.ts @@ -136,6 +136,7 @@ import { getExchangeDetails, getExchangePaytoUri, fetchFreshExchange, + ReadyExchangeSummary, } from "./exchanges.js"; import { TransitionInfo, @@ -616,9 +617,10 @@ export async function getCandidateWithdrawalDenoms( return await ws.db .mktx((x) => [x.denominations]) .runReadOnly(async (tx) => { - const allDenoms = await tx.denominations.indexes.byExchangeBaseUrl.getAll( - exchangeBaseUrl, - ); + const allDenoms = + await tx.denominations.indexes.byExchangeBaseUrl.getAll( + exchangeBaseUrl, + ); return allDenoms.filter((d) => isWithdrawableDenom(d, ws.config.testing.denomselAllowLate), ); @@ -870,10 +872,10 @@ async function handleKycRequired( amlStatus === AmlStatus.normal || amlStatus === undefined ? WithdrawalGroupStatus.PendingKyc : amlStatus === AmlStatus.pending - ? WithdrawalGroupStatus.PendingAml - : amlStatus === AmlStatus.fronzen - ? WithdrawalGroupStatus.SuspendedAml - : assertUnreachable(amlStatus); + ? WithdrawalGroupStatus.PendingAml + : amlStatus === AmlStatus.fronzen + ? WithdrawalGroupStatus.SuspendedAml + : assertUnreachable(amlStatus); notificationKycUrl = kycUrl; @@ -1508,10 +1510,7 @@ async function processWithdrawalGroupPendingReady( withdrawalGroupId, }); - await ws.exchangeOps.updateExchangeFromUrl( - ws, - withdrawalGroup.exchangeBaseUrl, - ); + await ws.exchangeOps.fetchFreshExchange(ws, withdrawalGroup.exchangeBaseUrl); if (withdrawalGroup.denomsSel.selectedDenoms.length === 0) { logger.warn("Finishing empty withdrawal group (no denoms)"); @@ -1549,9 +1548,8 @@ async function processWithdrawalGroupPendingReady( await ws.db .mktx((x) => [x.planchets]) .runReadOnly(async (tx) => { - const planchets = await tx.planchets.indexes.byGroup.getAll( - withdrawalGroupId, - ); + const planchets = + await tx.planchets.indexes.byGroup.getAll(withdrawalGroupId); for (const p of planchets) { if (p.planchetStatus === PlanchetStatus.WithdrawalDone) { wgContext.planchetsFinished.add(p.coinPub); @@ -1759,19 +1757,18 @@ export async function getExchangeWithdrawalInfo( ageRestricted: number | undefined, ): Promise<ExchangeWithdrawalDetails> { logger.trace("updating exchange"); - const { exchange, exchangeDetails } = - await ws.exchangeOps.updateExchangeFromUrl(ws, exchangeBaseUrl); + const exchange = await ws.exchangeOps.fetchFreshExchange(ws, exchangeBaseUrl); - if (exchangeDetails.currency != instructedAmount.currency) { + if (exchange.currency != instructedAmount.currency) { // Specifiying the amount in the conversion input currency is not yet supported. // We might add support for it later. throw new Error( - `withdrawal only supported when specifying target currency ${exchangeDetails.currency}`, + `withdrawal only supported when specifying target currency ${exchange.currency}`, ); } const withdrawalAccountsList = await fetchWithdrawalAccountInfo(ws, { - exchangeDetails, + exchange, instructedAmount, }); @@ -1799,7 +1796,7 @@ export async function getExchangeWithdrawalInfo( const exchangeWireAccounts: string[] = []; - for (const account of exchangeDetails.wireInfo.accounts) { + for (const account of exchange.wireInfo.accounts) { exchangeWireAccounts.push(account.payto_uri); } @@ -1839,17 +1836,18 @@ export async function getExchangeWithdrawalInfo( const possibleDenoms = await ws.db .mktx((x) => [x.denominations]) .runReadOnly(async (tx) => { - const ds = await tx.denominations.indexes.byExchangeBaseUrl.getAll( - exchangeBaseUrl, - ); + const ds = + await tx.denominations.indexes.byExchangeBaseUrl.getAll( + exchangeBaseUrl, + ); return ds.filter((x) => x.isOffered); }); let versionMatch; - if (exchangeDetails.protocolVersionRange) { + if (exchange.protocolVersionRange) { versionMatch = LibtoolVersion.compare( WALLET_EXCHANGE_PROTOCOL_VERSION, - exchangeDetails.protocolVersionRange, + exchange.protocolVersionRange, ); if ( @@ -1859,7 +1857,7 @@ export async function getExchangeWithdrawalInfo( ) { logger.warn( `wallet's support for exchange protocol version ${WALLET_EXCHANGE_PROTOCOL_VERSION} might be outdated ` + - `(exchange has ${exchangeDetails.protocolVersionRange}), checking for updates`, + `(exchange has ${exchange.protocolVersionRange}), checking for updates`, ); } } @@ -1871,7 +1869,7 @@ export async function getExchangeWithdrawalInfo( } } - const paytoUris = exchangeDetails.wireInfo.accounts.map((x) => x.payto_uri); + const paytoUris = exchange.wireInfo.accounts.map((x) => x.payto_uri); if (!paytoUris) { throw Error("exchange is in invalid state"); } @@ -1881,7 +1879,7 @@ export async function getExchangeWithdrawalInfo( exchangePaytoUris: paytoUris, exchangeWireAccounts, exchangeCreditAccountDetails: withdrawalAccountsList, - exchangeVersion: exchangeDetails.protocolVersionRange || "unknown", + exchangeVersion: exchange.protocolVersionRange || "unknown", numOfferedDenoms: possibleDenoms.length, selectedDenoms, // FIXME: delete this field / replace by something we can display to the user @@ -1923,7 +1921,7 @@ export async function getWithdrawalDetailsForUri( // FIXME: right now the exchange gets permanently added, // we might want to only temporarily add it. try { - await ws.exchangeOps.updateExchangeFromUrl(ws, info.suggestedExchange); + await ws.exchangeOps.fetchFreshExchange(ws, info.suggestedExchange); } 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. @@ -2289,7 +2287,6 @@ export interface PrepareCreateWithdrawalGroupResult { creationInfo?: { amount: AmountJson; canonExchange: string; - exchangeDetails: ExchangeDetailsRecord; }; } @@ -2376,7 +2373,6 @@ export async function internalPrepareCreateWithdrawalGroup( }; const exchangeInfo = await fetchFreshExchange(ws, canonExchange); - const exchangeDetails = exchangeInfo.exchangeDetails; const transactionId = constructTransactionIdentifier({ tag: TransactionType.Withdrawal, withdrawalGroupId: withdrawalGroup.withdrawalGroupId, @@ -2388,7 +2384,6 @@ export async function internalPrepareCreateWithdrawalGroup( creationInfo: { canonExchange, amount, - exchangeDetails, }, }; } @@ -2411,8 +2406,6 @@ export async function internalPerformCreateWithdrawalGroup( if (!prep.creationInfo) { return { withdrawalGroup, transitionInfo: undefined }; } - const { amount, canonExchange, exchangeDetails } = prep.creationInfo; - await tx.withdrawalGroups.add(withdrawalGroup); await tx.reserves.put({ reservePub: withdrawalGroup.reservePub, @@ -2529,13 +2522,13 @@ export async function acceptWithdrawalFromUri( withdrawInfo.wireTypes, ); - const { exchangeDetails } = await ws.exchangeOps.updateExchangeFromUrl( + const exchange = await ws.exchangeOps.fetchFreshExchange( ws, selectedExchange, ); const withdrawalAccountList = await fetchWithdrawalAccountInfo(ws, { - exchangeDetails, + exchange, instructedAmount: withdrawInfo.amount, }); @@ -2666,14 +2659,14 @@ async function fetchAccount( async function fetchWithdrawalAccountInfo( ws: InternalWalletState, req: { - exchangeDetails: ExchangeDetailsRecord; + exchange: ReadyExchangeSummary; instructedAmount: AmountJson; reservePub?: string; }, ): Promise<WithdrawalExchangeAccountDetails[]> { - const { exchangeDetails, instructedAmount } = req; + const { exchange, instructedAmount } = req; const withdrawalAccounts: WithdrawalExchangeAccountDetails[] = []; - for (let acct of exchangeDetails.wireInfo.accounts) { + for (let acct of exchange.wireInfo.accounts) { const acctInfo = await fetchAccount( ws, req.instructedAmount, @@ -2704,12 +2697,9 @@ export async function createManualWithdrawal( ): Promise<AcceptManualWithdrawalResult> { const { exchangeBaseUrl } = req; const amount = Amounts.parseOrThrow(req.amount); - const { exchangeDetails } = await ws.exchangeOps.updateExchangeFromUrl( - ws, - exchangeBaseUrl, - ); + const exchange = await ws.exchangeOps.fetchFreshExchange(ws, exchangeBaseUrl); - if (exchangeDetails.currency != amount.currency) { + if (exchange.currency != amount.currency) { throw Error( "manual withdrawal with conversion from foreign currency is not yet supported", ); @@ -2719,7 +2709,7 @@ export async function createManualWithdrawal( ); const withdrawalAccountsList = await fetchWithdrawalAccountInfo(ws, { - exchangeDetails, + exchange, instructedAmount: amount, reservePub: reserveKeyPair.pub, }); diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index da6ffa41f..2d422e59c 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -844,12 +844,12 @@ async function handlePrepareWithdrawExchange( } const exchangeBaseUrl = parsedUri.exchangeBaseUrl; const exchange = await fetchFreshExchange(ws, exchangeBaseUrl); - if (exchange.exchangeDetails.masterPublicKey != parsedUri.exchangePub) { + if (exchange.masterPub != parsedUri.exchangePub) { throw Error("mismatch of exchange master public key (URI vs actual)"); } if (parsedUri.amount) { const amt = Amounts.parseOrThrow(parsedUri.amount); - if (amt.currency !== exchange.exchangeDetails.currency) { + if (amt.currency !== exchange.currency) { throw Error("mismatch of currency (URI vs exchange)"); } } @@ -1689,7 +1689,7 @@ class InternalWalletStateImpl implements InternalWalletState { exchangeOps: ExchangeOperations = { getExchangeDetails, - updateExchangeFromUrl: fetchFreshExchange, + fetchFreshExchange, }; recoupOps: RecoupOperations = { |