diff options
Diffstat (limited to 'packages/taler-wallet-core')
23 files changed, 1430 insertions, 1530 deletions
diff --git a/packages/taler-wallet-core/src/attention.ts b/packages/taler-wallet-core/src/attention.ts index 61f95350d..60d2117f1 100644 --- a/packages/taler-wallet-core/src/attention.ts +++ b/packages/taler-wallet-core/src/attention.ts @@ -29,15 +29,15 @@ import { UserAttentionsResponse, } from "@gnu-taler/taler-util"; import { timestampPreciseFromDb, timestampPreciseToDb } from "./db.js"; -import { InternalWalletState } from "./wallet.js"; +import { InternalWalletState, WalletExecutionContext } from "./wallet.js"; const logger = new Logger("operations/attention.ts"); export async function getUserAttentionsUnreadCount( - ws: InternalWalletState, + wex: WalletExecutionContext, req: UserAttentionsRequest, ): Promise<UserAttentionsCountResponse> { - const total = await ws.db.runReadOnlyTx(["userAttention"], async (tx) => { + const total = await wex.db.runReadOnlyTx(["userAttention"], async (tx) => { let count = 0; await tx.userAttention.iter().forEach((x) => { if ( @@ -56,10 +56,10 @@ export async function getUserAttentionsUnreadCount( } export async function getUserAttentions( - ws: InternalWalletState, + wex: WalletExecutionContext, req: UserAttentionsRequest, ): Promise<UserAttentionsResponse> { - return await ws.db.runReadOnlyTx(["userAttention"], async (tx) => { + return await wex.db.runReadOnlyTx(["userAttention"], async (tx) => { const pending: UserAttentionUnreadList = []; await tx.userAttention.iter().forEach((x) => { if ( @@ -79,10 +79,10 @@ export async function getUserAttentions( } export async function markAttentionRequestAsRead( - ws: InternalWalletState, + wex: WalletExecutionContext, req: UserAttentionByIdRequest, ): Promise<void> { - await ws.db.runReadWriteTx(["userAttention"], async (tx) => { + await wex.db.runReadWriteTx(["userAttention"], async (tx) => { const ua = await tx.userAttention.get([req.entityId, req.type]); if (!ua) throw Error("attention request not found"); tx.userAttention.put({ @@ -96,15 +96,15 @@ export async function markAttentionRequestAsRead( * the wallet need the user attention to complete a task * internal API * - * @param ws + * @param wex * @param info */ export async function addAttentionRequest( - ws: InternalWalletState, + wex: WalletExecutionContext, info: AttentionInfo, entityId: string, ): Promise<void> { - await ws.db.runReadWriteTx(["userAttention"], async (tx) => { + await wex.db.runReadWriteTx(["userAttention"], async (tx) => { await tx.userAttention.put({ info, entityId, @@ -118,14 +118,14 @@ export async function addAttentionRequest( * user completed the task, attention request is not needed * internal API * - * @param ws + * @param wex * @param created */ export async function removeAttentionRequest( - ws: InternalWalletState, + wex: WalletExecutionContext, req: UserAttentionByIdRequest, ): Promise<void> { - await ws.db.runReadWriteTx(["userAttention"], async (tx) => { + await wex.db.runReadWriteTx(["userAttention"], async (tx) => { const ua = await tx.userAttention.get([req.entityId, req.type]); if (!ua) throw Error("attention request not found"); await tx.userAttention.delete([req.entityId, req.type]); diff --git a/packages/taler-wallet-core/src/backup/index.ts b/packages/taler-wallet-core/src/backup/index.ts index a5d7eee80..04c53526d 100644 --- a/packages/taler-wallet-core/src/backup/index.ts +++ b/packages/taler-wallet-core/src/backup/index.ts @@ -90,7 +90,7 @@ import { timestampPreciseToDb, } from "../db.js"; import { preparePayForUri } from "../pay-merchant.js"; -import { InternalWalletState } from "../wallet.js"; +import { InternalWalletState, WalletExecutionContext } from "../wallet.js"; const logger = new Logger("operations/backup.ts"); @@ -179,10 +179,10 @@ function getNextBackupTimestamp(): TalerPreciseTimestamp { } async function runBackupCycleForProvider( - ws: InternalWalletState, + wex: WalletExecutionContext, args: BackupForProviderArgs, ): Promise<TaskRunResult> { - const provider = await ws.db.runReadOnlyTx( + const provider = await wex.db.runReadOnlyTx( ["backupProviders"], async (tx) => { return tx.backupProviders.get(args.backupProviderBaseUrl); @@ -197,7 +197,7 @@ async function runBackupCycleForProvider( //const backupJson = await exportBackup(ws); // FIXME: re-implement backup const backupJson = {}; - const backupConfig = await provideBackupState(ws); + const backupConfig = await provideBackupState(wex); const encBackup = await encryptBackup(backupConfig, backupJson); const currentBackupHash = hash(encBackup); @@ -209,7 +209,7 @@ async function runBackupCycleForProvider( logger.trace(`trying to upload backup to ${provider.baseUrl}`); logger.trace(`old hash ${oldHash}, new hash ${newHash}`); - const syncSigResp = await ws.cryptoApi.makeSyncSignature({ + const syncSigResp = await wex.cryptoApi.makeSyncSignature({ newHash: encodeCrock(currentBackupHash), oldHash: provider.lastBackupHash, accountPriv: encodeCrock(accountKeyPair.eddsaPriv), @@ -226,7 +226,7 @@ async function runBackupCycleForProvider( accountBackupUrl.searchParams.set("fresh", "yes"); } - const resp = await ws.http.fetch(accountBackupUrl.href, { + const resp = await wex.http.fetch(accountBackupUrl.href, { method: "POST", body: encBackup, headers: { @@ -244,7 +244,7 @@ async function runBackupCycleForProvider( logger.trace(`sync response status: ${resp.status}`); if (resp.status === HttpStatusCode.NotModified) { - await ws.db.runReadWriteTx(["backupProviders"], async (tx) => { + await wex.db.runReadWriteTx(["backupProviders"], async (tx) => { const prov = await tx.backupProviders.get(provider.baseUrl); if (!prov) { return; @@ -259,7 +259,7 @@ async function runBackupCycleForProvider( await tx.backupProviders.put(prov); }); - removeAttentionRequest(ws, { + removeAttentionRequest(wex, { entityId: provider.baseUrl, type: AttentionType.BackupUnpaid, }); @@ -279,7 +279,7 @@ async function runBackupCycleForProvider( //FIXME: check download errors let res: PreparePayResult | undefined = undefined; try { - res = await preparePayForUri(ws, talerUri); + res = await preparePayForUri(wex, talerUri); } catch (e) { const error = TalerError.fromException(e); if (!error.hasErrorCode(TalerErrorCode.WALLET_ORDER_ALREADY_CLAIMED)) { @@ -290,7 +290,7 @@ async function runBackupCycleForProvider( if (res === undefined) { //claimed - await ws.db.runReadWriteTx(["backupProviders"], async (tx) => { + await wex.db.runReadWriteTx(["backupProviders"], async (tx) => { const prov = await tx.backupProviders.get(provider.baseUrl); if (!prov) { logger.warn("backup provider not found anymore"); @@ -310,7 +310,7 @@ async function runBackupCycleForProvider( } const result = res; - await ws.db.runReadWriteTx(["backupProviders"], async (tx) => { + await wex.db.runReadWriteTx(["backupProviders"], async (tx) => { const prov = await tx.backupProviders.get(provider.baseUrl); if (!prov) { logger.warn("backup provider not found anymore"); @@ -327,7 +327,7 @@ async function runBackupCycleForProvider( }); addAttentionRequest( - ws, + wex, { type: AttentionType.BackupUnpaid, provider_base_url: provider.baseUrl, @@ -343,7 +343,7 @@ async function runBackupCycleForProvider( } if (resp.status === HttpStatusCode.NoContent) { - await ws.db.runReadWriteTx(["backupProviders"], async (tx) => { + await wex.db.runReadWriteTx(["backupProviders"], async (tx) => { const prov = await tx.backupProviders.get(provider.baseUrl); if (!prov) { return; @@ -359,7 +359,7 @@ async function runBackupCycleForProvider( await tx.backupProviders.put(prov); }); - removeAttentionRequest(ws, { + removeAttentionRequest(wex, { entityId: provider.baseUrl, type: AttentionType.BackupUnpaid, }); @@ -372,11 +372,11 @@ async function runBackupCycleForProvider( if (resp.status === HttpStatusCode.Conflict) { logger.info("conflicting backup found"); const backupEnc = new Uint8Array(await resp.bytes()); - const backupConfig = await provideBackupState(ws); + const backupConfig = await provideBackupState(wex); // const blob = await decryptBackup(backupConfig, backupEnc); // FIXME: Re-implement backup import with merging // await importBackup(ws, blob, cryptoData); - await ws.db.runReadWriteTx(["backupProviders"], async (tx) => { + await wex.db.runReadWriteTx(["backupProviders"], async (tx) => { const prov = await tx.backupProviders.get(provider.baseUrl); if (!prov) { logger.warn("backup provider not found anymore"); @@ -394,7 +394,7 @@ async function runBackupCycleForProvider( }); logger.info("processed existing backup"); // Now upload our own, merged backup. - return await runBackupCycleForProvider(ws, args); + return await runBackupCycleForProvider(wex, args); } // Some other response that we did not expect! @@ -410,10 +410,10 @@ async function runBackupCycleForProvider( } export async function processBackupForProvider( - ws: InternalWalletState, + wex: WalletExecutionContext, backupProviderBaseUrl: string, ): Promise<TaskRunResult> { - const provider = await ws.db.runReadOnlyTx( + const provider = await wex.db.runReadOnlyTx( ["backupProviders"], async (tx) => { return await tx.backupProviders.get(backupProviderBaseUrl); @@ -425,7 +425,7 @@ export async function processBackupForProvider( logger.info(`running backup for provider ${backupProviderBaseUrl}`); - return await runBackupCycleForProvider(ws, { + return await runBackupCycleForProvider(wex, { backupProviderBaseUrl: provider.baseUrl, }); } @@ -441,10 +441,10 @@ export const codecForRemoveBackupProvider = .build("RemoveBackupProviderRequest"); export async function removeBackupProvider( - ws: InternalWalletState, + wex: WalletExecutionContext, req: RemoveBackupProviderRequest, ): Promise<void> { - await ws.db.runReadWriteTx(["backupProviders"], async (tx) => { + await wex.db.runReadWriteTx(["backupProviders"], async (tx) => { await tx.backupProviders.delete(req.provider); }); } @@ -469,10 +469,10 @@ export const codecForRunBackupCycle = (): Codec<RunBackupCycleRequest> => * 3. Upload the updated backup blob. */ export async function runBackupCycle( - ws: InternalWalletState, + wex: WalletExecutionContext, req: RunBackupCycleRequest, ): Promise<void> { - const providers = await ws.db.runReadOnlyTx( + const providers = await wex.db.runReadOnlyTx( ["backupProviders"], async (tx) => { if (req.providers) { @@ -486,7 +486,7 @@ export async function runBackupCycle( ); for (const provider of providers) { - await runBackupCycleForProvider(ws, { + await runBackupCycleForProvider(wex, { backupProviderBaseUrl: provider.baseUrl, }); } @@ -547,13 +547,13 @@ export const codecForAddBackupProviderResponse = .build("AddBackupProviderResponse"); export async function addBackupProvider( - ws: InternalWalletState, + wex: WalletExecutionContext, req: AddBackupProviderRequest, ): Promise<AddBackupProviderResponse> { logger.info(`adding backup provider ${j2s(req)}`); - await provideBackupState(ws); + await provideBackupState(wex); const canonUrl = canonicalizeBaseUrl(req.backupProviderBaseUrl); - await ws.db.runReadWriteTx(["backupProviders"], async (tx) => { + await wex.db.runReadWriteTx(["backupProviders"], async (tx) => { const oldProv = await tx.backupProviders.get(canonUrl); if (oldProv) { logger.info("old backup provider found"); @@ -571,12 +571,12 @@ export async function addBackupProvider( } }); const termsUrl = new URL("config", canonUrl); - const resp = await ws.http.fetch(termsUrl.href); + const resp = await wex.http.fetch(termsUrl.href); const terms = await readSuccessResponseJsonOrThrow( resp, codecForSyncTermsOfServiceResponse(), ); - await ws.db.runReadWriteTx(["backupProviders"], async (tx) => { + await wex.db.runReadWriteTx(["backupProviders"], async (tx) => { let state: BackupProviderState; //FIXME: what is the difference provisional and ready? if (req.activate) { @@ -604,13 +604,13 @@ export async function addBackupProvider( }); }); - return await runFirstBackupCycleForProvider(ws, { + return await runFirstBackupCycleForProvider(wex, { backupProviderBaseUrl: canonUrl, }); } async function runFirstBackupCycleForProvider( - ws: InternalWalletState, + wex: WalletExecutionContext, args: BackupForProviderArgs, ): Promise<AddBackupProviderResponse> { throw Error("not implemented"); @@ -647,7 +647,7 @@ export interface BackupInfo { } async function getProviderPaymentInfo( - ws: InternalWalletState, + wex: WalletExecutionContext, provider: BackupProviderRecord, ): Promise<ProviderPaymentStatus> { throw Error("not implemented"); @@ -702,10 +702,10 @@ async function getProviderPaymentInfo( * Get information about the current state of wallet backups. */ export async function getBackupInfo( - ws: InternalWalletState, + wex: WalletExecutionContext, ): Promise<BackupInfo> { - const backupConfig = await provideBackupState(ws); - const providerRecords = await ws.db.runReadOnlyTx( + const backupConfig = await provideBackupState(wex); + const providerRecords = await wex.db.runReadOnlyTx( ["backupProviders", "operationRetries"], async (tx) => { return await tx.backupProviders.iter().mapAsync(async (bp) => { @@ -731,7 +731,7 @@ export async function getBackupInfo( x.provider.state.tag === BackupProviderStateTag.Retrying ? x.retryRecord?.lastError : undefined, - paymentStatus: await getProviderPaymentInfo(ws, x.provider), + paymentStatus: await getProviderPaymentInfo(wex, x.provider), terms: x.provider.terms, name: x.provider.name, }); @@ -748,10 +748,10 @@ export async function getBackupInfo( * private key. */ export async function getBackupRecovery( - ws: InternalWalletState, + wex: WalletExecutionContext, ): Promise<BackupRecovery> { - const bs = await provideBackupState(ws); - const providers = await ws.db.runReadOnlyTx( + const bs = await provideBackupState(wex); + const providers = await wex.db.runReadOnlyTx( ["backupProviders"], async (tx) => { return await tx.backupProviders.iter().toArray(); @@ -771,10 +771,10 @@ export async function getBackupRecovery( } async function backupRecoveryTheirs( - ws: InternalWalletState, + wex: WalletExecutionContext, br: BackupRecovery, ) { - await ws.db.runReadWriteTx(["backupProviders", "config"], async (tx) => { + await wex.db.runReadWriteTx(["backupProviders", "config"], async (tx) => { let backupStateEntry: ConfigRecord | undefined = await tx.config.get( ConfigRecordKey.WalletBackupState, ); @@ -818,16 +818,19 @@ async function backupRecoveryTheirs( }); } -async function backupRecoveryOurs(ws: InternalWalletState, br: BackupRecovery) { +async function backupRecoveryOurs( + wex: WalletExecutionContext, + br: BackupRecovery, +) { throw Error("not implemented"); } export async function loadBackupRecovery( - ws: InternalWalletState, + wex: WalletExecutionContext, br: RecoveryLoadRequest, ): Promise<void> { - const bs = await provideBackupState(ws); - const providers = await ws.db.runReadOnlyTx( + const bs = await provideBackupState(wex); + const providers = await wex.db.runReadOnlyTx( ["backupProviders"], async (tx) => { return await tx.backupProviders.iter().toArray(); @@ -847,9 +850,9 @@ export async function loadBackupRecovery( strategy = RecoveryMergeStrategy.Theirs; } if (strategy === RecoveryMergeStrategy.Theirs) { - return backupRecoveryTheirs(ws, br.recovery); + return backupRecoveryTheirs(wex, br.recovery); } else { - return backupRecoveryOurs(ws, br.recovery); + return backupRecoveryOurs(wex, br.recovery); } } @@ -873,9 +876,9 @@ export async function decryptBackup( } export async function provideBackupState( - ws: InternalWalletState, + wex: WalletExecutionContext, ): Promise<WalletBackupConfState> { - const bs: ConfigRecord | undefined = await ws.db.runReadOnlyTx( + const bs: ConfigRecord | undefined = await wex.db.runReadOnlyTx( ["config"], async (tx) => { return await tx.config.get(ConfigRecordKey.WalletBackupState); @@ -887,12 +890,12 @@ export async function provideBackupState( } // We need to generate the key outside of the transaction // due to how IndexedDB works. - const k = await ws.cryptoApi.createEddsaKeypair({}); + const k = await wex.cryptoApi.createEddsaKeypair({}); const d = getRandomBytes(5); // FIXME: device ID should be configured when wallet is initialized // and be based on hostname const deviceId = `wallet-core-${encodeCrock(d)}`; - return await ws.db.runReadWriteTx(["config"], async (tx) => { + return await wex.db.runReadWriteTx(["config"], async (tx) => { let backupStateEntry: ConfigRecord | undefined = await tx.config.get( ConfigRecordKey.WalletBackupState, ); @@ -926,11 +929,11 @@ export async function getWalletBackupState( } export async function setWalletDeviceId( - ws: InternalWalletState, + wex: WalletExecutionContext, deviceId: string, ): Promise<void> { - await provideBackupState(ws); - await ws.db.runReadWriteTx(["config"], async (tx) => { + await provideBackupState(wex); + await wex.db.runReadWriteTx(["config"], async (tx) => { let backupStateEntry: ConfigRecord | undefined = await tx.config.get( ConfigRecordKey.WalletBackupState, ); @@ -946,8 +949,8 @@ export async function setWalletDeviceId( } export async function getWalletDeviceId( - ws: InternalWalletState, + wex: WalletExecutionContext, ): Promise<string> { - const bs = await provideBackupState(ws); + const bs = await provideBackupState(wex); return bs.deviceId; } diff --git a/packages/taler-wallet-core/src/balance.ts b/packages/taler-wallet-core/src/balance.ts index a287748f1..3b53699ac 100644 --- a/packages/taler-wallet-core/src/balance.ts +++ b/packages/taler-wallet-core/src/balance.ts @@ -80,7 +80,7 @@ import { getExchangeScopeInfo, getExchangeWireDetailsInTx, } from "./exchanges.js"; -import { InternalWalletState } from "./wallet.js"; +import { InternalWalletState, WalletExecutionContext } from "./wallet.js"; /** * Logger. @@ -131,7 +131,7 @@ class BalancesStore { private balanceStore: Record<string, WalletBalance> = {}; constructor( - private ws: InternalWalletState, + private wex: WalletExecutionContext, private tx: WalletDbReadOnlyTransaction< [ "globalCurrencyAuditors", @@ -281,7 +281,7 @@ class BalancesStore { * Get balance information. */ export async function getBalancesInsideTransaction( - ws: InternalWalletState, + wex: WalletExecutionContext, tx: WalletDbReadOnlyTransaction< [ "exchanges", @@ -295,7 +295,7 @@ export async function getBalancesInsideTransaction( ] >, ): Promise<BalancesResponse> { - const balanceStore: BalancesStore = new BalancesStore(ws, tx); + const balanceStore: BalancesStore = new BalancesStore(wex, tx); const keyRangeActive = GlobalIDB.KeyRange.bound( OPERATION_STATUS_ACTIVE_FIRST, @@ -428,11 +428,11 @@ export async function getBalancesInsideTransaction( * Get detailed balance information, sliced by exchange and by currency. */ export async function getBalances( - ws: InternalWalletState, + wex: WalletExecutionContext, ): Promise<BalancesResponse> { logger.trace("starting to compute balance"); - const wbal = await ws.db.runReadWriteTx( + const wbal = await wex.db.runReadWriteTx( [ "coinAvailability", "coins", @@ -446,7 +446,7 @@ export async function getBalances( "withdrawalGroups", ], async (tx) => { - return getBalancesInsideTransaction(ws, tx); + return getBalancesInsideTransaction(wex, tx); }, ); @@ -488,12 +488,12 @@ export interface AcceptableExchanges { * Get all exchanges that are acceptable for a particular payment. */ export async function getAcceptableExchangeBaseUrls( - ws: InternalWalletState, + wex: WalletExecutionContext, req: MerchantPaymentRestrictionsForBalance, ): Promise<AcceptableExchanges> { const acceptableExchangeUrls = new Set<string>(); const depositableExchangeUrls = new Set<string>(); - await ws.db.runReadOnlyTx(["exchanges", "exchangeDetails"], async (tx) => { + await wex.db.runReadOnlyTx(["exchanges", "exchangeDetails"], async (tx) => { // FIXME: We should have a DB index to look up all exchanges // for a particular auditor ... @@ -600,10 +600,10 @@ export interface MerchantPaymentBalanceDetails { } export async function getMerchantPaymentBalanceDetails( - ws: InternalWalletState, + wex: WalletExecutionContext, req: MerchantPaymentRestrictionsForBalance, ): Promise<MerchantPaymentBalanceDetails> { - const acceptability = await getAcceptableExchangeBaseUrls(ws, req); + const acceptability = await getAcceptableExchangeBaseUrls(wex, req); const d: MerchantPaymentBalanceDetails = { balanceAvailable: Amounts.zeroOfCurrency(req.currency), @@ -613,7 +613,7 @@ export async function getMerchantPaymentBalanceDetails( balanceMerchantDepositable: Amounts.zeroOfCurrency(req.currency), }; - await ws.db.runReadOnlyTx( + await wex.db.runReadOnlyTx( ["coinAvailability", "refreshGroups"], async (tx) => { await tx.coinAvailability.iter().forEach((ca) => { @@ -665,12 +665,12 @@ export async function getMerchantPaymentBalanceDetails( } export async function getBalanceDetail( - ws: InternalWalletState, + wex: WalletExecutionContext, req: GetBalanceDetailRequest, ): Promise<MerchantPaymentBalanceDetails> { const exchanges: { exchangeBaseUrl: string; exchangePub: string }[] = []; const wires = new Array<string>(); - await ws.db.runReadOnlyTx(["exchanges", "exchangeDetails"], async (tx) => { + await wex.db.runReadOnlyTx(["exchanges", "exchangeDetails"], async (tx) => { const allExchanges = await tx.exchanges.iter().toArray(); for (const e of allExchanges) { const details = await getExchangeWireDetailsInTx(tx, e.baseUrl); @@ -690,7 +690,7 @@ export async function getBalanceDetail( } }); - return await getMerchantPaymentBalanceDetails(ws, { + return await getMerchantPaymentBalanceDetails(wex, { currency: req.currency, acceptedAuditors: [], acceptedExchanges: exchanges, @@ -717,7 +717,7 @@ export interface PeerPaymentBalanceDetails { } export async function getPeerPaymentBalanceDetailsInTx( - ws: InternalWalletState, + wex: WalletExecutionContext, tx: WalletDbReadOnlyTransaction<["coinAvailability", "refreshGroups"]>, req: PeerPaymentRestrictionsForBalance, ): Promise<PeerPaymentBalanceDetails> { diff --git a/packages/taler-wallet-core/src/coinSelection.ts b/packages/taler-wallet-core/src/coinSelection.ts index e1ae613bc..680e5faa1 100644 --- a/packages/taler-wallet-core/src/coinSelection.ts +++ b/packages/taler-wallet-core/src/coinSelection.ts @@ -64,7 +64,11 @@ import { getAutoRefreshExecuteThreshold } from "./common.js"; import { DenominationRecord, WalletDbReadOnlyTransaction } from "./db.js"; import { isWithdrawableDenom } from "./denominations.js"; import { getExchangeWireDetailsInTx } from "./exchanges.js"; -import { getDenomInfo, InternalWalletState } from "./wallet.js"; +import { + getDenomInfo, + InternalWalletState, + WalletExecutionContext, +} from "./wallet.js"; const logger = new Logger("coinSelection.ts"); @@ -247,7 +251,7 @@ export type SelectPayCoinsResult = * This function is only exported for the sake of unit tests. */ export async function selectPayCoinsNew( - ws: InternalWalletState, + wex: WalletExecutionContext, req: SelectPayCoinRequestNg, ): Promise<SelectPayCoinsResult> { const { @@ -259,7 +263,7 @@ export async function selectPayCoinsNew( // FIXME: Why don't we do this in a transaction? const [candidateDenoms, wireFeesPerExchange] = - await selectPayMerchantCandidates(ws, req); + await selectPayMerchantCandidates(wex, req); const coinPubs: string[] = []; const coinContributions: AmountJson[] = []; @@ -312,7 +316,7 @@ export async function selectPayCoinsNew( } if (!selectedDenom) { - const details = await getMerchantPaymentBalanceDetails(ws, { + const details = await getMerchantPaymentBalanceDetails(wex, { acceptedAuditors: req.auditors, acceptedExchanges: req.exchanges, acceptedWireMethods: [req.wireMethod], @@ -357,7 +361,7 @@ export async function selectPayCoinsNew( logger.trace(`coin selection request ${j2s(req)}`); logger.trace(`selected coins (via denoms) for payment: ${j2s(finalSel)}`); - await ws.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { + await wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { for (const dph of Object.keys(finalSel)) { const selInfo = finalSel[dph]; const numRequested = selInfo.contributions.length; @@ -594,10 +598,10 @@ export type AvailableDenom = DenominationInfo & { }; async function selectPayMerchantCandidates( - ws: InternalWalletState, + wex: WalletExecutionContext, req: SelectPayCoinRequestNg, ): Promise<[AvailableDenom[], Record<string, AmountJson>]> { - return await ws.db.runReadOnlyTx( + return await wex.db.runReadOnlyTx( ["exchanges", "exchangeDetails", "denominations", "coinAvailability"], async (tx) => { // FIXME: Use the existing helper (from balance.ts) to @@ -906,7 +910,7 @@ export interface PeerCoinSelectionRequest { * Get coin availability information for a certain exchange. */ async function selectPayPeerCandidatesForExchange( - ws: InternalWalletState, + wex: WalletExecutionContext, tx: WalletDbReadOnlyTransaction<["coinAvailability", "denominations"]>, exchangeBaseUrl: string, ): Promise<AvailableDenom[]> { @@ -1035,7 +1039,7 @@ function greedySelectPeer( } export async function selectPeerCoins( - ws: InternalWalletState, + wex: WalletExecutionContext, req: PeerCoinSelectionRequest, ): Promise<SelectPeerCoinsResult> { const instructedAmount = req.instructedAmount; @@ -1044,7 +1048,7 @@ export async function selectPeerCoins( // one coin to spend. throw new Error("amount of zero not allowed"); } - return await ws.db.runReadWriteTx( + return await wex.db.runReadWriteTx( [ "exchanges", "contractTerms", @@ -1063,7 +1067,7 @@ export async function selectPeerCoins( continue; } const candidates = await selectPayPeerCandidatesForExchange( - ws, + wex, tx, exch.baseUrl, ); @@ -1089,7 +1093,7 @@ export async function selectPeerCoins( throw Error("repair not possible, coin not found"); } const denom = await getDenomInfo( - ws, + wex, tx, coin.exchangeBaseUrl, coin.denomPubHash, @@ -1188,7 +1192,7 @@ export async function selectPeerCoins( // We were unable to select coins. // Now we need to produce error details. - const infoGeneral = await getPeerPaymentBalanceDetailsInTx(ws, tx, { + const infoGeneral = await getPeerPaymentBalanceDetailsInTx(wex, tx, { currency, }); @@ -1200,7 +1204,7 @@ export async function selectPeerCoins( if (exch.detailsPointer?.currency !== currency) { continue; } - const infoExchange = await getPeerPaymentBalanceDetailsInTx(ws, tx, { + const infoExchange = await getPeerPaymentBalanceDetailsInTx(wex, tx, { currency, restrictExchangeTo: exch.baseUrl, }); diff --git a/packages/taler-wallet-core/src/common.ts b/packages/taler-wallet-core/src/common.ts index 08adb2515..5acdeeba4 100644 --- a/packages/taler-wallet-core/src/common.ts +++ b/packages/taler-wallet-core/src/common.ts @@ -61,7 +61,11 @@ import { timestampPreciseToDb, } from "./db.js"; import { createRefreshGroup } from "./refresh.js"; -import { InternalWalletState, getDenomInfo } from "./wallet.js"; +import { + InternalWalletState, + WalletExecutionContext, + getDenomInfo, +} from "./wallet.js"; const logger = new Logger("operations/common.ts"); @@ -76,7 +80,7 @@ export interface CoinsSpendInfo { } export async function makeCoinsVisible( - ws: InternalWalletState, + wex: WalletExecutionContext, tx: WalletDbReadWriteTransaction<["coins", "coinAvailability"]>, transactionId: string, ): Promise<void> { @@ -104,7 +108,7 @@ export async function makeCoinsVisible( } export async function makeCoinAvailable( - ws: InternalWalletState, + wex: WalletExecutionContext, tx: WalletDbReadWriteTransaction< ["coins", "coinAvailability", "denominations"] >, @@ -143,7 +147,7 @@ export async function makeCoinAvailable( } export async function spendCoins( - ws: InternalWalletState, + wex: WalletExecutionContext, tx: WalletDbReadWriteTransaction< ["coins", "coinAvailability", "refreshGroups", "denominations"] >, @@ -162,7 +166,7 @@ export async function spendCoins( throw Error("coin allocated for payment doesn't exist anymore"); } const denom = await getDenomInfo( - ws, + wex, tx, coin.exchangeBaseUrl, coin.denomPubHash, @@ -224,7 +228,7 @@ export async function spendCoins( } await createRefreshGroup( - ws, + wex, tx, Amounts.currencyOf(csi.contributions[0]), refreshCoinPubs, diff --git a/packages/taler-wallet-core/src/deposits.ts b/packages/taler-wallet-core/src/deposits.ts index 9db8cfc27..3abb614bd 100644 --- a/packages/taler-wallet-core/src/deposits.ts +++ b/packages/taler-wallet-core/src/deposits.ts @@ -109,7 +109,11 @@ import { notifyTransition, parseTransactionIdentifier, } from "./transactions.js"; -import { InternalWalletState, getDenomInfo } from "./wallet.js"; +import { + InternalWalletState, + WalletExecutionContext, + getDenomInfo, +} from "./wallet.js"; import { getCandidateWithdrawalDenomsTx } from "./withdraw.js"; /** @@ -122,7 +126,7 @@ export class DepositTransactionContext implements TransactionContext { readonly taskId: TaskIdStr; constructor( - public ws: InternalWalletState, + public wex: WalletExecutionContext, public depositGroupId: string, ) { this.transactionId = constructTransactionIdentifier({ @@ -137,7 +141,7 @@ export class DepositTransactionContext implements TransactionContext { async deleteTransaction(): Promise<void> { const depositGroupId = this.depositGroupId; - const ws = this.ws; + const ws = this.wex; // FIXME: We should check first if we are in a final state // where deletion is allowed. await ws.db.runReadWriteTx(["depositGroups", "tombstones"], async (tx) => { @@ -153,8 +157,8 @@ export class DepositTransactionContext implements TransactionContext { } async suspendTransaction(): Promise<void> { - const { ws, depositGroupId, transactionId, taskId: retryTag } = this; - const transitionInfo = await ws.db.runReadWriteTx( + const { wex, depositGroupId, transactionId, taskId: retryTag } = this; + const transitionInfo = await wex.db.runReadWriteTx( ["depositGroups"], async (tx) => { const dg = await tx.depositGroups.get(depositGroupId); @@ -191,13 +195,13 @@ export class DepositTransactionContext implements TransactionContext { }; }, ); - ws.taskScheduler.stopShepherdTask(retryTag); - notifyTransition(ws, transactionId, transitionInfo); + wex.taskScheduler.stopShepherdTask(retryTag); + notifyTransition(wex, transactionId, transitionInfo); } async abortTransaction(): Promise<void> { - const { ws, depositGroupId, transactionId, taskId: retryTag } = this; - const transitionInfo = await ws.db.runReadWriteTx( + const { wex, depositGroupId, transactionId, taskId: retryTag } = this; + const transitionInfo = await wex.db.runReadWriteTx( ["depositGroups"], async (tx) => { const dg = await tx.depositGroups.get(depositGroupId); @@ -224,17 +228,17 @@ export class DepositTransactionContext implements TransactionContext { return undefined; }, ); - ws.taskScheduler.stopShepherdTask(retryTag); - notifyTransition(ws, transactionId, transitionInfo); - ws.taskScheduler.startShepherdTask(retryTag); - ws.notify({ + wex.taskScheduler.stopShepherdTask(retryTag); + notifyTransition(wex, transactionId, transitionInfo); + wex.taskScheduler.startShepherdTask(retryTag); + wex.ws.notify({ type: NotificationType.BalanceChange, hintTransactionId: transactionId, }); } async resumeTransaction(): Promise<void> { - const { ws, depositGroupId, transactionId, taskId: retryTag } = this; + const { wex: ws, depositGroupId, transactionId, taskId: retryTag } = this; const transitionInfo = await ws.db.runReadWriteTx( ["depositGroups"], async (tx) => { @@ -277,8 +281,8 @@ export class DepositTransactionContext implements TransactionContext { } async failTransaction(): Promise<void> { - const { ws, depositGroupId, transactionId, taskId: retryTag } = this; - const transitionInfo = await ws.db.runReadWriteTx( + const { wex, depositGroupId, transactionId, taskId } = this; + const transitionInfo = await wex.db.runReadWriteTx( ["depositGroups"], async (tx) => { const dg = await tx.depositGroups.get(depositGroupId); @@ -303,9 +307,9 @@ export class DepositTransactionContext implements TransactionContext { return undefined; }, ); - ws.taskScheduler.stopShepherdTask(retryTag); - notifyTransition(ws, transactionId, transitionInfo); - ws.notify({ + wex.taskScheduler.stopShepherdTask(taskId); + notifyTransition(wex, transactionId, transitionInfo); + wex.ws.notify({ type: NotificationType.BalanceChange, hintTransactionId: transactionId, }); @@ -410,9 +414,8 @@ export function computeDepositTransactionActions( } async function refundDepositGroup( - ws: InternalWalletState, + wex: WalletExecutionContext, depositGroup: DepositGroupRecord, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const newTxPerCoin = [...depositGroup.statusPerCoin]; logger.info(`status per coin: ${j2s(depositGroup.statusPerCoin)}`); @@ -424,7 +427,7 @@ async function refundDepositGroup( break; default: { const coinPub = depositGroup.payCoinSelection.coinPubs[i]; - const coinExchange = await ws.db.runReadOnlyTx( + const coinExchange = await wex.db.runReadOnlyTx( ["coins"], async (tx) => { const coinRecord = await tx.coins.get(coinPub); @@ -436,7 +439,7 @@ async function refundDepositGroup( // We use a constant refund transaction ID, since there can // only be one refund. const rtid = 1; - const sig = await ws.cryptoApi.signRefund({ + const sig = await wex.cryptoApi.signRefund({ coinPub, contractTermsHash: depositGroup.contractTermsHash, merchantPriv: depositGroup.merchantPriv, @@ -452,10 +455,10 @@ async function refundDepositGroup( rtransaction_id: rtid, }; const refundUrl = new URL(`coins/${coinPub}/refund`, coinExchange); - const httpResp = await ws.http.fetch(refundUrl.href, { + const httpResp = await wex.http.fetch(refundUrl.href, { method: "POST", body: refundReq, - cancellationToken, + cancellationToken: wex.cancellationToken, }); logger.info( `coin ${i} refund HTTP status for coin: ${httpResp.status}`, @@ -486,7 +489,7 @@ async function refundDepositGroup( const currency = Amounts.currencyOf(depositGroup.totalPayCost); - const res = await ws.db.runReadWriteTx( + const res = await wex.db.runReadWriteTx( [ "depositGroups", "refreshGroups", @@ -510,7 +513,7 @@ async function refundDepositGroup( let refreshRes: CreateRefreshGroupResult | undefined = undefined; if (isDone) { refreshRes = await createRefreshGroup( - ws, + wex, tx, currency, refreshCoins, @@ -529,7 +532,7 @@ async function refundDepositGroup( if (res?.refreshRes) { for (const notif of res.refreshRes.notifications) { - ws.notify(notif); + wex.ws.notify(notif); } } @@ -550,7 +553,7 @@ async function refundDepositGroup( * transaction of processDepositGroup? */ async function waitForRefreshOnDepositGroup( - ws: InternalWalletState, + wex: WalletExecutionContext, depositGroup: DepositGroupRecord, ): Promise<TaskRunResult> { const abortRefreshGroupId = depositGroup.abortRefreshGroupId; @@ -559,7 +562,7 @@ async function waitForRefreshOnDepositGroup( tag: TransactionType.Deposit, depositGroupId: depositGroup.depositGroupId, }); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["depositGroups", "refreshGroups"], async (tx) => { const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId); @@ -593,8 +596,8 @@ async function waitForRefreshOnDepositGroup( }, ); - notifyTransition(ws, transactionId, transitionInfo); - ws.notify({ + notifyTransition(wex, transactionId, transitionInfo); + wex.ws.notify({ type: NotificationType.BalanceChange, hintTransactionId: transactionId, }); @@ -602,24 +605,22 @@ async function waitForRefreshOnDepositGroup( } async function processDepositGroupAborting( - ws: InternalWalletState, + wex: WalletExecutionContext, depositGroup: DepositGroupRecord, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { logger.info("processing deposit tx in 'aborting'"); const abortRefreshGroupId = depositGroup.abortRefreshGroupId; if (!abortRefreshGroupId) { logger.info("refunding deposit group"); - return refundDepositGroup(ws, depositGroup, cancellationToken); + return refundDepositGroup(wex, depositGroup); } logger.info("waiting for refresh"); - return waitForRefreshOnDepositGroup(ws, depositGroup); + return waitForRefreshOnDepositGroup(wex, depositGroup); } async function processDepositGroupPendingKyc( - ws: InternalWalletState, + wex: WalletExecutionContext, depositGroup: DepositGroupRecord, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const { depositGroupId } = depositGroup; const transactionId = constructTransactionIdentifier({ @@ -640,9 +641,9 @@ async function processDepositGroupPendingKyc( ); url.searchParams.set("timeout_ms", "10000"); logger.info(`kyc url ${url.href}`); - const kycStatusRes = await ws.http.fetch(url.href, { + const kycStatusRes = await wex.http.fetch(url.href, { method: "GET", - cancellationToken, + cancellationToken: wex.cancellationToken, }); if ( kycStatusRes.status === HttpStatusCode.Ok || @@ -650,7 +651,7 @@ async function processDepositGroupPendingKyc( // remove after the exchange is fixed or clarified kycStatusRes.status === HttpStatusCode.NoContent ) { - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["depositGroups"], async (tx) => { const newDg = await tx.depositGroups.get(depositGroupId); @@ -667,7 +668,7 @@ async function processDepositGroupPendingKyc( return { oldTxState, newTxState }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); } else if (kycStatusRes.status === HttpStatusCode.Accepted) { // FIXME: Do we have to update the URL here? } else { @@ -682,7 +683,7 @@ async function processDepositGroupPendingKyc( * and transition the transaction to the KYC required state. */ async function transitionToKycRequired( - ws: InternalWalletState, + wex: WalletExecutionContext, depositGroup: DepositGroupRecord, kycInfo: KycPendingInfo, exchangeUrl: string, @@ -700,7 +701,7 @@ async function transitionToKycRequired( exchangeUrl, ); logger.info(`kyc url ${url.href}`); - const kycStatusReq = await ws.http.fetch(url.href, { + const kycStatusReq = await wex.http.fetch(url.href, { method: "GET", }); if (kycStatusReq.status === HttpStatusCode.Ok) { @@ -709,7 +710,7 @@ async function transitionToKycRequired( } else if (kycStatusReq.status === HttpStatusCode.Accepted) { const kycStatus = await kycStatusReq.json(); logger.info(`kyc status: ${j2s(kycStatus)}`); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["depositGroups"], async (tx) => { const dg = await tx.depositGroups.get(depositGroupId); @@ -731,7 +732,7 @@ async function transitionToKycRequired( return { oldTxState, newTxState }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return TaskRunResult.finished(); } else { throw Error(`unexpected response from kyc-check (${kycStatusReq.status})`); @@ -739,15 +740,14 @@ async function transitionToKycRequired( } async function processDepositGroupPendingTrack( - ws: InternalWalletState, + wex: WalletExecutionContext, depositGroup: DepositGroupRecord, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const { depositGroupId } = depositGroup; for (let i = 0; i < depositGroup.statusPerCoin.length; i++) { const coinPub = depositGroup.payCoinSelection.coinPubs[i]; // FIXME: Make the URL part of the coin selection? - const exchangeBaseUrl = await ws.db.runReadWriteTx( + const exchangeBaseUrl = await wex.db.runReadWriteTx( ["coins"], async (tx) => { const coinRecord = await tx.coins.get(coinPub); @@ -766,11 +766,10 @@ async function processDepositGroupPendingTrack( if (depositGroup.statusPerCoin[i] !== DepositElementStatus.Wired) { const track = await trackDeposit( - ws, + wex, depositGroup, coinPub, exchangeBaseUrl, - cancellationToken, ); if (track.type === "accepted") { @@ -784,7 +783,7 @@ async function processDepositGroupPendingTrack( requirementRow, }; return transitionToKycRequired( - ws, + wex, depositGroup, kycInfo, exchangeBaseUrl, @@ -801,7 +800,7 @@ async function processDepositGroupPendingTrack( } const fee = await getExchangeWireFee( - ws, + wex, payto.targetType, exchangeBaseUrl, track.execution_time, @@ -825,7 +824,7 @@ async function processDepositGroupPendingTrack( } if (updatedTxStatus !== undefined) { - await ws.db.runReadWriteTx(["depositGroups"], async (tx) => { + await wex.db.runReadWriteTx(["depositGroups"], async (tx) => { const dg = await tx.depositGroups.get(depositGroupId); if (!dg) { return; @@ -855,7 +854,7 @@ async function processDepositGroupPendingTrack( let allWired = true; - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["depositGroups"], async (tx) => { const dg = await tx.depositGroups.get(depositGroupId); @@ -884,9 +883,9 @@ async function processDepositGroupPendingTrack( tag: TransactionType.Deposit, depositGroupId, }); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); if (allWired) { - ws.notify({ + wex.ws.notify({ type: NotificationType.BalanceChange, hintTransactionId: transactionId, }); @@ -899,13 +898,13 @@ async function processDepositGroupPendingTrack( } async function processDepositGroupPendingDeposit( - ws: InternalWalletState, + wex: WalletExecutionContext, depositGroup: DepositGroupRecord, cancellationToken?: CancellationToken, ): Promise<TaskRunResult> { logger.info("processing deposit group in pending(deposit)"); const depositGroupId = depositGroup.depositGroupId; - const contractTermsRec = await ws.db.runReadOnlyTx( + const contractTermsRec = await wex.db.runReadOnlyTx( ["contractTerms"], async (tx) => { return tx.contractTerms.get(depositGroup.contractTermsHash); @@ -932,7 +931,7 @@ async function processDepositGroupPendingDeposit( // FIXME: Cache these! const depositPermissions = await generateDepositPermissions( - ws, + wex, depositGroup.payCoinSelection, contractData, ); @@ -981,7 +980,7 @@ async function processDepositGroupPendingDeposit( const url = new URL(`batch-deposit`, exchangeUrl); logger.info(`depositing to ${url.href}`); logger.trace(`deposit request: ${j2s(batchReq)}`); - const httpResp = await ws.http.fetch(url.href, { + const httpResp = await wex.http.fetch(url.href, { method: "POST", body: batchReq, cancellationToken: cancellationToken, @@ -991,7 +990,7 @@ async function processDepositGroupPendingDeposit( codecForBatchDepositSuccess(), ); - await ws.db.runReadWriteTx(["depositGroups"], async (tx) => { + await wex.db.runReadWriteTx(["depositGroups"], async (tx) => { const dg = await tx.depositGroups.get(depositGroupId); if (!dg) { return; @@ -1007,7 +1006,7 @@ async function processDepositGroupPendingDeposit( }); } - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["depositGroups"], async (tx) => { const dg = await tx.depositGroups.get(depositGroupId); @@ -1022,7 +1021,7 @@ async function processDepositGroupPendingDeposit( }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return TaskRunResult.progress(); } @@ -1030,11 +1029,10 @@ async function processDepositGroupPendingDeposit( * Process a deposit group that is not in its final state yet. */ export async function processDepositGroup( - ws: InternalWalletState, + wex: WalletExecutionContext, depositGroupId: string, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { - const depositGroup = await ws.db.runReadOnlyTx( + const depositGroup = await wex.db.runReadOnlyTx( ["depositGroups"], async (tx) => { return tx.depositGroups.get(depositGroupId); @@ -1047,21 +1045,13 @@ export async function processDepositGroup( switch (depositGroup.operationStatus) { case DepositOperationStatus.PendingTrack: - return processDepositGroupPendingTrack( - ws, - depositGroup, - cancellationToken, - ); + return processDepositGroupPendingTrack(wex, depositGroup); case DepositOperationStatus.PendingKyc: - return processDepositGroupPendingKyc(ws, depositGroup, cancellationToken); + return processDepositGroupPendingKyc(wex, depositGroup); case DepositOperationStatus.PendingDeposit: - return processDepositGroupPendingDeposit( - ws, - depositGroup, - cancellationToken, - ); + return processDepositGroupPendingDeposit(wex, depositGroup); case DepositOperationStatus.Aborting: - return processDepositGroupAborting(ws, depositGroup, cancellationToken); + return processDepositGroupAborting(wex, depositGroup); } return TaskRunResult.finished(); @@ -1071,12 +1061,12 @@ export async function processDepositGroup( * FIXME: Consider moving this to exchanges.ts. */ async function getExchangeWireFee( - ws: InternalWalletState, + wex: WalletExecutionContext, wireType: string, baseUrl: string, time: TalerProtocolTimestamp, ): Promise<WireFee> { - const exchangeDetails = await ws.db.runReadOnlyTx( + const exchangeDetails = await wex.db.runReadOnlyTx( ["exchangeDetails", "exchanges"], async (tx) => { const ex = await tx.exchanges.get(baseUrl); @@ -1116,11 +1106,10 @@ async function getExchangeWireFee( } async function trackDeposit( - ws: InternalWalletState, + wex: WalletExecutionContext, depositGroup: DepositGroupRecord, coinPub: string, exchangeUrl: string, - cancellationToken: CancellationToken, ): Promise<TrackTransaction> { const wireHash = hashWire( depositGroup.wire.payto_uri, @@ -1131,7 +1120,7 @@ async function trackDeposit( `deposits/${wireHash}/${depositGroup.merchantPub}/${depositGroup.contractTermsHash}/${coinPub}`, exchangeUrl, ); - const sigResp = await ws.cryptoApi.signTrackTransaction({ + const sigResp = await wex.cryptoApi.signTrackTransaction({ coinPub, contractTermsHash: depositGroup.contractTermsHash, merchantPriv: depositGroup.merchantPriv, @@ -1141,9 +1130,9 @@ async function trackDeposit( url.searchParams.set("merchant_sig", sigResp.sig); // Not doing long-polling yet as it looks like it's broken in the exchange (2024-02-20) // url.searchParams.set("timeout_ms", "30000"); - const httpResp = await ws.http.fetch(url.href, { + const httpResp = await wex.http.fetch(url.href, { method: "GET", - cancellationToken, + cancellationToken: wex.cancellationToken, }); logger.trace(`deposits response status: ${httpResp.status}`); switch (httpResp.status) { @@ -1177,7 +1166,7 @@ async function trackDeposit( * as it doesn't prepare anything */ export async function prepareDepositGroup( - ws: InternalWalletState, + wex: WalletExecutionContext, req: PrepareDepositRequest, ): Promise<PrepareDepositResponse> { const p = parsePaytoUri(req.depositPaytoUri); @@ -1188,7 +1177,7 @@ export async function prepareDepositGroup( const exchangeInfos: { url: string; master_pub: string }[] = []; - await ws.db.runReadOnlyTx(["exchangeDetails", "exchanges"], async (tx) => { + await wex.db.runReadOnlyTx(["exchangeDetails", "exchanges"], async (tx) => { const allExchanges = await tx.exchanges.iter().toArray(); for (const e of allExchanges) { const details = await getExchangeWireDetailsInTx(tx, e.baseUrl); @@ -1227,7 +1216,7 @@ export async function prepareDepositGroup( refund_deadline: TalerProtocolTimestamp.zero(), }; - const { h: contractTermsHash } = await ws.cryptoApi.hashString({ + const { h: contractTermsHash } = await wex.cryptoApi.hashString({ str: canonicalJson(contractTerms), }); @@ -1237,7 +1226,7 @@ export async function prepareDepositGroup( "", ); - const payCoinSel = await selectPayCoinsNew(ws, { + const payCoinSel = await selectPayCoinsNew(wex, { auditors: [], exchanges: contractData.allowedExchanges, wireMethod: contractData.wireMethod, @@ -1257,16 +1246,16 @@ export async function prepareDepositGroup( ); } - const totalDepositCost = await getTotalPaymentCost(ws, payCoinSel.coinSel); + const totalDepositCost = await getTotalPaymentCost(wex, payCoinSel.coinSel); const effectiveDepositAmount = await getCounterpartyEffectiveDepositAmount( - ws, + wex, p.targetType, payCoinSel.coinSel, ); const fees = await getTotalFeesForDepositAmount( - ws, + wex, p.targetType, amount, payCoinSel.coinSel, @@ -1288,7 +1277,7 @@ export function generateDepositGroupTxId(): string { } export async function createDepositGroup( - ws: InternalWalletState, + wex: WalletExecutionContext, req: CreateDepositGroupRequest, ): Promise<CreateDepositGroupResponse> { const p = parsePaytoUri(req.depositPaytoUri); @@ -1300,7 +1289,7 @@ export async function createDepositGroup( const exchangeInfos: { url: string; master_pub: string }[] = []; - await ws.db.runReadOnlyTx(["exchanges", "exchangeDetails"], async (tx) => { + await wex.db.runReadOnlyTx(["exchanges", "exchangeDetails"], async (tx) => { const allExchanges = await tx.exchanges.iter().toArray(); for (const e of allExchanges) { const details = await getExchangeWireDetailsInTx(tx, e.baseUrl); @@ -1319,8 +1308,8 @@ export async function createDepositGroup( AbsoluteTime.addDuration(now, Duration.fromSpec({ minutes: 5 })), ); const nowRounded = AbsoluteTime.toProtocolTimestamp(now); - const noncePair = await ws.cryptoApi.createEddsaKeypair({}); - const merchantPair = await ws.cryptoApi.createEddsaKeypair({}); + const noncePair = await wex.cryptoApi.createEddsaKeypair({}); + const merchantPair = await wex.cryptoApi.createEddsaKeypair({}); const wireSalt = encodeCrock(getRandomBytes(16)); const wireHash = hashWire(req.depositPaytoUri, wireSalt); const contractTerms: MerchantContractTerms = { @@ -1346,7 +1335,7 @@ export async function createDepositGroup( refund_deadline: TalerProtocolTimestamp.zero(), }; - const { h: contractTermsHash } = await ws.cryptoApi.hashString({ + const { h: contractTermsHash } = await wex.cryptoApi.hashString({ str: canonicalJson(contractTerms), }); @@ -1356,7 +1345,7 @@ export async function createDepositGroup( "", ); - const payCoinSel = await selectPayCoinsNew(ws, { + const payCoinSel = await selectPayCoinsNew(wex, { auditors: [], exchanges: contractData.allowedExchanges, wireMethod: contractData.wireMethod, @@ -1376,7 +1365,7 @@ export async function createDepositGroup( ); } - const totalDepositCost = await getTotalPaymentCost(ws, payCoinSel.coinSel); + const totalDepositCost = await getTotalPaymentCost(wex, payCoinSel.coinSel); let depositGroupId: string; if (req.transactionId) { @@ -1391,7 +1380,7 @@ export async function createDepositGroup( const infoPerExchange: Record<string, DepositInfoPerExchange> = {}; - await ws.db.runReadOnlyTx(["coins"], async (tx) => { + await wex.db.runReadOnlyTx(["coins"], async (tx) => { for (let i = 0; i < payCoinSel.coinSel.coinPubs.length; i++) { const coin = await tx.coins.get(payCoinSel.coinSel.coinPubs[i]); if (!coin) { @@ -1415,7 +1404,7 @@ export async function createDepositGroup( const counterpartyEffectiveDepositAmount = await getCounterpartyEffectiveDepositAmount( - ws, + wex, p.targetType, payCoinSel.coinSel, ); @@ -1453,10 +1442,10 @@ export async function createDepositGroup( infoPerExchange, }; - const ctx = new DepositTransactionContext(ws, depositGroupId); + const ctx = new DepositTransactionContext(wex, depositGroupId); const transactionId = ctx.transactionId; - const newTxState = await ws.db.runReadWriteTx( + const newTxState = await wex.db.runReadWriteTx( [ "depositGroups", "coins", @@ -1467,7 +1456,7 @@ export async function createDepositGroup( "contractTerms", ], async (tx) => { - await spendCoins(ws, tx, { + await spendCoins(wex, tx, { allocationId: transactionId, coinPubs: payCoinSel.coinSel.coinPubs, contributions: payCoinSel.coinSel.coinContributions.map((x) => @@ -1484,7 +1473,7 @@ export async function createDepositGroup( }, ); - ws.notify({ + wex.ws.notify({ type: NotificationType.TransactionStateTransition, transactionId, oldTxState: { @@ -1493,12 +1482,12 @@ export async function createDepositGroup( newTxState, }); - ws.notify({ + wex.ws.notify({ type: NotificationType.BalanceChange, hintTransactionId: transactionId, }); - ws.taskScheduler.startShepherdTask(ctx.taskId); + wex.taskScheduler.startShepherdTask(ctx.taskId); return { depositGroupId, @@ -1511,7 +1500,7 @@ export async function createDepositGroup( * account after depositing, not considering aggregation. */ export async function getCounterpartyEffectiveDepositAmount( - ws: InternalWalletState, + wex: WalletExecutionContext, wireType: string, pcs: PayCoinSelection, ): Promise<AmountJson> { @@ -1519,7 +1508,7 @@ export async function getCounterpartyEffectiveDepositAmount( const fees: AmountJson[] = []; const exchangeSet: Set<string> = new Set(); - await ws.db.runReadOnlyTx( + await wex.db.runReadOnlyTx( ["coins", "denominations", "exchangeDetails", "exchanges"], async (tx) => { for (let i = 0; i < pcs.coinPubs.length; i++) { @@ -1528,7 +1517,7 @@ export async function getCounterpartyEffectiveDepositAmount( throw Error("can't calculate deposit amount, coin not found"); } const denom = await getDenomInfo( - ws, + wex, tx, coin.exchangeBaseUrl, coin.denomPubHash, @@ -1574,7 +1563,7 @@ export async function getCounterpartyEffectiveDepositAmount( * specified amount using the selected coins and the wire method. */ async function getTotalFeesForDepositAmount( - ws: InternalWalletState, + wex: WalletExecutionContext, wireType: string, total: AmountJson, pcs: PayCoinSelection, @@ -1585,7 +1574,7 @@ async function getTotalFeesForDepositAmount( const exchangeSet: Set<string> = new Set(); const currency = Amounts.currencyOf(total); - await ws.db.runReadOnlyTx( + await wex.db.runReadOnlyTx( ["coins", "denominations", "exchanges", "exchangeDetails"], async (tx) => { for (let i = 0; i < pcs.coinPubs.length; i++) { @@ -1594,7 +1583,7 @@ async function getTotalFeesForDepositAmount( throw Error("can't calculate deposit amount, coin not found"); } const denom = await getDenomInfo( - ws, + wex, tx, coin.exchangeBaseUrl, coin.denomPubHash, @@ -1606,7 +1595,7 @@ async function getTotalFeesForDepositAmount( exchangeSet.add(coin.exchangeBaseUrl); const allDenoms = await getCandidateWithdrawalDenomsTx( - ws, + wex, tx, coin.exchangeBaseUrl, currency, @@ -1619,7 +1608,7 @@ async function getTotalFeesForDepositAmount( allDenoms, denom, amountLeft, - ws.config.testing.denomselAllowLate, + wex.ws.config.testing.denomselAllowLate, ); refreshFee.push(refreshCost); } diff --git a/packages/taler-wallet-core/src/dev-experiments.ts b/packages/taler-wallet-core/src/dev-experiments.ts index b48aa716b..c94571ff8 100644 --- a/packages/taler-wallet-core/src/dev-experiments.ts +++ b/packages/taler-wallet-core/src/dev-experiments.ts @@ -43,7 +43,7 @@ import { RefreshOperationStatus, timestampPreciseToDb, } from "./db.js"; -import { InternalWalletState } from "./wallet.js"; +import { WalletExecutionContext } from "./wallet.js"; const logger = new Logger("dev-experiments.ts"); @@ -51,7 +51,7 @@ const logger = new Logger("dev-experiments.ts"); * Apply a dev experiment to the wallet database / state. */ export async function applyDevExperiment( - ws: InternalWalletState, + wex: WalletExecutionContext, uri: string, ): Promise<void> { logger.info(`applying dev experiment ${uri}`); @@ -60,14 +60,14 @@ export async function applyDevExperiment( logger.info("unable to parse dev experiment URI"); return; } - if (!ws.config.testing.devModeActive) { + if (!wex.ws.config.testing.devModeActive) { throw Error( "can't handle devmode URI (other than enable-devmode) unless devmode is active", ); } if (parsedUri.devExperimentId == "insert-pending-refresh") { - await ws.db.runReadWriteTx(["refreshGroups"], async (tx) => { + await wex.db.runReadWriteTx(["refreshGroups"], async (tx) => { const refreshGroupId = encodeCrock(getRandomBytes(32)); const newRg: RefreshGroupRecord = { currency: "TESTKUDOS", diff --git a/packages/taler-wallet-core/src/exchanges.ts b/packages/taler-wallet-core/src/exchanges.ts index 932df721d..a26b3f5ca 100644 --- a/packages/taler-wallet-core/src/exchanges.ts +++ b/packages/taler-wallet-core/src/exchanges.ts @@ -128,7 +128,7 @@ import { DbReadOnlyTransaction } from "./query.js"; import { createRecoupGroup } from "./recoup.js"; import { createRefreshGroup } from "./refresh.js"; import { WALLET_EXCHANGE_PROTOCOL_VERSION } from "./versions.js"; -import { InternalWalletState } from "./wallet.js"; +import { InternalWalletState, WalletExecutionContext } from "./wallet.js"; const logger = new Logger("exchanges.ts"); @@ -147,10 +147,10 @@ interface ExchangeTosDownloadResult { } async function downloadExchangeWithTermsOfService( + wex: WalletExecutionContext, exchangeBaseUrl: string, http: HttpRequestLibrary, timeout: Duration, - cancellationToken: CancellationToken, acceptFormat: string, acceptLanguage: string | undefined, ): Promise<ExchangeTosDownloadResult> { @@ -170,7 +170,7 @@ async function downloadExchangeWithTermsOfService( const resp = await http.fetch(reqUrl.href, { headers, timeout, - cancellationToken, + cancellationToken: wex.cancellationToken, }); const tosText = await readSuccessResponseTextOrThrow(resp); const tosEtag = resp.headers.get("etag") || "unknown"; @@ -344,10 +344,10 @@ export async function getExchangeWireDetailsInTx( } export async function lookupExchangeByUri( - ws: InternalWalletState, + wex: WalletExecutionContext, req: GetExchangeEntryByUrlRequest, ): Promise<ExchangeListItem> { - return await ws.db.runReadOnlyTx( + return await wex.db.runReadOnlyTx( [ "exchanges", "exchangeDetails", @@ -381,10 +381,10 @@ export async function lookupExchangeByUri( * Mark the current ToS version as accepted by the user. */ export async function acceptExchangeTermsOfService( - ws: InternalWalletState, + wex: WalletExecutionContext, exchangeBaseUrl: string, ): Promise<void> { - const notif = await ws.db.runReadWriteTx( + const notif = await wex.db.runReadWriteTx( ["exchangeDetails", "exchanges"], async (tx) => { const exch = await tx.exchanges.get(exchangeBaseUrl); @@ -407,7 +407,7 @@ export async function acceptExchangeTermsOfService( }, ); if (notif) { - ws.notify(notif); + wex.ws.notify(notif); } } @@ -449,7 +449,7 @@ export async function forgetExchangeTermsOfService( * Throw an exception if they are invalid. */ async function validateWireInfo( - ws: InternalWalletState, + wex: WalletExecutionContext, versionCurrent: number, wireInfo: ExchangeKeysDownloadResult, masterPublicKey: string, @@ -457,10 +457,10 @@ async function validateWireInfo( for (const a of wireInfo.accounts) { logger.trace("validating exchange acct"); let isValid = false; - if (ws.config.testing.insecureTrustExchange) { + if (wex.ws.config.testing.insecureTrustExchange) { isValid = true; } else { - const { valid: v } = await ws.cryptoApi.isValidWireAccount({ + const { valid: v } = await wex.ws.cryptoApi.isValidWireAccount({ masterPub: masterPublicKey, paytoUri: a.payto_uri, sig: a.master_sig, @@ -490,10 +490,10 @@ async function validateWireInfo( wireFee: Amounts.stringify(x.wire_fee), }; let isValid = false; - if (ws.config.testing.insecureTrustExchange) { + if (wex.ws.config.testing.insecureTrustExchange) { isValid = true; } else { - const { valid: v } = await ws.cryptoApi.isValidWireFee({ + const { valid: v } = await wex.ws.cryptoApi.isValidWireFee({ masterPub: masterPublicKey, type: wireMethod, wf: fee, @@ -520,7 +520,7 @@ async function validateWireInfo( * Throw an exception if they are invalid. */ async function validateGlobalFees( - ws: InternalWalletState, + wex: WalletExecutionContext, fees: GlobalFees[], masterPub: string, ): Promise<ExchangeGlobalFees[]> { @@ -528,10 +528,10 @@ async function validateGlobalFees( for (const gf of fees) { logger.trace("validating exchange global fees"); let isValid = false; - if (ws.config.testing.insecureTrustExchange) { + if (wex.ws.config.testing.insecureTrustExchange) { isValid = true; } else { - const { valid: v } = await ws.cryptoApi.isValidGlobalFees({ + const { valid: v } = await wex.cryptoApi.isValidGlobalFees({ masterPub, gf, }); @@ -832,10 +832,9 @@ async function downloadExchangeKeysInfo( } async function downloadTosFromAcceptedFormat( - ws: InternalWalletState, + wex: WalletExecutionContext, baseUrl: string, timeout: Duration, - cancellationToken: CancellationToken, acceptedFormat?: string[], acceptLanguage?: string, ): Promise<ExchangeTosDownloadResult> { @@ -844,10 +843,10 @@ async function downloadTosFromAcceptedFormat( if (acceptedFormat) for (const format of acceptedFormat) { const resp = await downloadExchangeWithTermsOfService( + wex, baseUrl, - ws.http, + wex.http, timeout, - cancellationToken, format, acceptLanguage, ); @@ -861,10 +860,10 @@ async function downloadTosFromAcceptedFormat( } // If none of the specified format was found try text/plain return await downloadExchangeWithTermsOfService( + wex, baseUrl, - ws.http, + wex.http, timeout, - cancellationToken, "text/plain", acceptLanguage, ); @@ -880,7 +879,7 @@ async function downloadTosFromAcceptedFormat( * a new ephemeral entry is created. */ async function startUpdateExchangeEntry( - ws: InternalWalletState, + wex: WalletExecutionContext, exchangeBaseUrl: string, options: { forceUpdate?: boolean } = {}, ): Promise<void> { @@ -892,19 +891,19 @@ async function startUpdateExchangeEntry( }`, ); - const { notification } = await ws.db.runReadWriteTx( + const { notification } = await wex.db.runReadWriteTx( ["exchanges", "exchangeDetails"], async (tx) => { - return provideExchangeRecordInTx(ws, tx, exchangeBaseUrl); + return provideExchangeRecordInTx(wex.ws, tx, exchangeBaseUrl); }, ); if (notification) { - ws.notify(notification); + wex.ws.notify(notification); } const { oldExchangeState, newExchangeState, taskId } = - await ws.db.runReadWriteTx( + await wex.db.runReadWriteTx( ["exchanges", "operationRetries"], async (tx) => { const r = await tx.exchanges.get(canonBaseUrl); @@ -952,13 +951,13 @@ async function startUpdateExchangeEntry( return { oldExchangeState, newExchangeState, taskId }; }, ); - ws.notify({ + wex.ws.notify({ type: NotificationType.ExchangeStateTransition, exchangeBaseUrl: canonBaseUrl, newExchangeState: newExchangeState, oldExchangeState: oldExchangeState, }); - await ws.taskScheduler.resetTaskRetries(taskId); + await wex.ws.taskScheduler.resetTaskRetries(taskId); } /** @@ -978,7 +977,7 @@ export interface ReadyExchangeSummary { } async function internalWaitReadyExchange( - ws: InternalWalletState, + wex: WalletExecutionContext, canonUrl: string, exchangeNotifFlag: AsyncFlag, options: { @@ -994,7 +993,7 @@ async function internalWaitReadyExchange( while (true) { logger.info(`waiting for ready exchange ${canonUrl}`); const { exchange, exchangeDetails, retryInfo, scopeInfo } = - await ws.db.runReadOnlyTx( + await wex.db.runReadOnlyTx( [ "exchanges", "exchangeDetails", @@ -1105,7 +1104,7 @@ async function internalWaitReadyExchange( * will still have been added as an ephemeral exchange entry. */ export async function fetchFreshExchange( - ws: InternalWalletState, + wex: WalletExecutionContext, baseUrl: string, options: { cancellationToken?: CancellationToken; @@ -1115,17 +1114,17 @@ export async function fetchFreshExchange( ): Promise<ReadyExchangeSummary> { const canonUrl = canonicalizeBaseUrl(baseUrl); - ws.taskScheduler.ensureRunning(); + wex.ws.taskScheduler.ensureRunning(); - await startUpdateExchangeEntry(ws, canonUrl, { + await startUpdateExchangeEntry(wex, canonUrl, { forceUpdate: options.forceUpdate, }); - return waitReadyExchange(ws, canonUrl, options); + return waitReadyExchange(wex, canonUrl, options); } async function waitReadyExchange( - ws: InternalWalletState, + wex: WalletExecutionContext, canonUrl: string, options: { cancellationToken?: CancellationToken; @@ -1138,7 +1137,7 @@ async function waitReadyExchange( const exchangeNotifFlag = new AsyncFlag(); // Raise exchangeNotifFlag whenever we get a notification // about our exchange. - const cancelNotif = ws.addNotificationListener((notif) => { + const cancelNotif = wex.ws.addNotificationListener((notif) => { if ( notif.type === NotificationType.ExchangeStateTransition && notif.exchangeBaseUrl === canonUrl @@ -1150,7 +1149,7 @@ async function waitReadyExchange( try { const res = await internalWaitReadyExchange( - ws, + wex, canonUrl, exchangeNotifFlag, options, @@ -1169,14 +1168,13 @@ async function waitReadyExchange( * exchange entry in then DB. */ export async function updateExchangeFromUrlHandler( - ws: InternalWalletState, + wex: WalletExecutionContext, exchangeBaseUrl: string, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { logger.trace(`updating exchange info for ${exchangeBaseUrl}`); exchangeBaseUrl = canonicalizeBaseUrl(exchangeBaseUrl); - const oldExchangeRec = await ws.db.runReadOnlyTx( + const oldExchangeRec = await wex.db.runReadOnlyTx( ["exchanges"], async (tx) => { return tx.exchanges.get(exchangeBaseUrl); @@ -1264,9 +1262,9 @@ export async function updateExchangeFromUrlHandler( const keysInfo = await downloadExchangeKeysInfo( exchangeBaseUrl, - ws.http, + wex.http, timeout, - cancellationToken, + wex.cancellationToken, oldExchangeRec.cachebreakNextUpdate ?? false, ); @@ -1279,14 +1277,14 @@ export async function updateExchangeFromUrlHandler( } const wireInfo = await validateWireInfo( - ws, + wex, version.current, keysInfo, keysInfo.masterPublicKey, ); const globalFees = await validateGlobalFees( - ws, + wex, keysInfo.globalFees, keysInfo.masterPublicKey, ); @@ -1311,10 +1309,9 @@ export async function updateExchangeFromUrlHandler( // because that one needs to exist, and we // will get the current etag from the response. const tosDownload = await downloadTosFromAcceptedFormat( - ws, + wex, exchangeBaseUrl, timeout, - cancellationToken, ["text/plain"], ); @@ -1327,7 +1324,7 @@ export async function updateExchangeFromUrlHandler( let ageMask = 0; for (const x of keysInfo.currentDenominations) { if ( - isWithdrawableDenom(x, ws.config.testing.denomselAllowLate) && + isWithdrawableDenom(x, wex.ws.config.testing.denomselAllowLate) && x.denomPub.age_mask != 0 ) { ageMask = x.denomPub.age_mask; @@ -1335,7 +1332,7 @@ export async function updateExchangeFromUrlHandler( } } - const updated = await ws.db.runReadWriteTx( + const updated = await wex.db.runReadWriteTx( [ "exchanges", "exchangeDetails", @@ -1492,7 +1489,7 @@ export async function updateExchangeFromUrlHandler( if (newlyRevokedCoinPubs.length != 0) { logger.info("recouping coins", newlyRevokedCoinPubs); recoupGroupId = await createRecoupGroup( - ws, + wex, tx, exchangeBaseUrl, newlyRevokedCoinPubs, @@ -1517,7 +1514,7 @@ export async function updateExchangeFromUrlHandler( }); // Asynchronously start recoup. This doesn't need to finish // for the exchange update to be considered finished. - ws.taskScheduler.startShepherdTask(recoupTaskId); + wex.ws.taskScheduler.startShepherdTask(recoupTaskId); } if (!updated) { @@ -1535,7 +1532,7 @@ export async function updateExchangeFromUrlHandler( if (refreshCheckNecessary) { // Do auto-refresh. - await ws.db.runReadWriteTx( + await wex.db.runReadWriteTx( [ "coins", "denominations", @@ -1581,7 +1578,7 @@ export async function updateExchangeFromUrlHandler( } if (refreshCoins.length > 0) { const res = await createRefreshGroup( - ws, + wex, tx, exchange.detailsPointer?.currency, refreshCoins, @@ -1605,7 +1602,7 @@ export async function updateExchangeFromUrlHandler( ); } - ws.notify({ + wex.ws.notify({ type: NotificationType.ExchangeStateTransition, exchangeBaseUrl, newExchangeState: updated.newExchangeState, @@ -1648,13 +1645,13 @@ function getAutoRefreshCheckThreshold(d: DenominationRecord): AbsoluteTime { * Throws if no matching account was found. */ export async function getExchangePaytoUri( - ws: InternalWalletState, + wex: WalletExecutionContext, exchangeBaseUrl: string, supportedTargetTypes: string[], ): Promise<string> { // We do the update here, since the exchange might not even exist // yet in our database. - const details = await ws.db.runReadOnlyTx( + const details = await wex.db.runReadOnlyTx( ["exchanges", "exchangeDetails"], async (tx) => { return getExchangeRecordsInternal(tx, exchangeBaseUrl); @@ -1682,23 +1679,22 @@ export async function getExchangePaytoUri( * Try to download in the accepted format not cached. */ export async function getExchangeTos( - ws: InternalWalletState, + wex: WalletExecutionContext, exchangeBaseUrl: string, acceptedFormat?: string[], acceptLanguage?: string, ): Promise<GetExchangeTosResult> { - const exch = await fetchFreshExchange(ws, exchangeBaseUrl); + const exch = await fetchFreshExchange(wex, exchangeBaseUrl); const tosDownload = await downloadTosFromAcceptedFormat( - ws, + wex, exchangeBaseUrl, getExchangeRequestTimeout(), - CancellationToken.CONTINUE, acceptedFormat, acceptLanguage, ); - await ws.db.runReadWriteTx(["exchanges"], async (tx) => { + await wex.db.runReadWriteTx(["exchanges"], async (tx) => { const updateExchangeEntry = await tx.exchanges.get(exchangeBaseUrl); if (updateExchangeEntry) { updateExchangeEntry.tosCurrentEtag = tosDownload.tosEtag; @@ -1750,10 +1746,10 @@ export async function downloadExchangeInfo( * List all exchange entries known to the wallet. */ export async function listExchanges( - ws: InternalWalletState, + wex: WalletExecutionContext, ): Promise<ExchangesListResponse> { const exchanges: ExchangeListItem[] = []; - await ws.db.runReadOnlyTx( + await wex.db.runReadOnlyTx( [ "exchanges", "operationRetries", @@ -1793,7 +1789,7 @@ export async function listExchanges( * succeeded. */ export async function markExchangeUsed( - ws: InternalWalletState, + wex: WalletExecutionContext, tx: WalletDbReadWriteTransaction<["exchanges"]>, exchangeBaseUrl: string, ): Promise<{ notif: WalletNotification | undefined }> { @@ -1833,10 +1829,10 @@ export async function markExchangeUsed( * for the fees charged by the exchange. */ export async function getExchangeDetailedInfo( - ws: InternalWalletState, + wex: WalletExecutionContext, exchangeBaseurl: string, ): Promise<ExchangeDetailedResponse> { - const exchange = await ws.db.runReadOnlyTx( + const exchange = await wex.db.runReadOnlyTx( ["exchanges", "exchangeDetails", "denominations"], async (tx) => { const ex = await tx.exchanges.get(exchangeBaseurl); @@ -1986,7 +1982,7 @@ export async function getExchangeDetailedInfo( } async function internalGetExchangeResources( - ws: InternalWalletState, + wex: WalletExecutionContext, tx: DbReadOnlyTransaction< typeof WalletStoresV1, ["exchanges", "coins", "withdrawalGroups"] @@ -2005,12 +2001,12 @@ async function internalGetExchangeResources( } export async function deleteExchange( - ws: InternalWalletState, + wex: WalletExecutionContext, req: DeleteExchangeRequest, ): Promise<void> { let inUse: boolean = false; const exchangeBaseUrl = canonicalizeBaseUrl(req.exchangeBaseUrl); - await ws.db.runReadWriteTx( + await wex.db.runReadWriteTx( ["exchanges", "coins", "withdrawalGroups", "exchangeDetails"], async (tx) => { const exchangeRec = await tx.exchanges.get(exchangeBaseUrl); @@ -2019,7 +2015,7 @@ export async function deleteExchange( logger.info("no exchange found to delete"); return; } - const res = await internalGetExchangeResources(ws, tx, exchangeBaseUrl); + const res = await internalGetExchangeResources(wex, tx, exchangeBaseUrl); if (res.hasResources) { if (req.purge) { const detRecs = @@ -2050,18 +2046,18 @@ export async function deleteExchange( } export async function getExchangeResources( - ws: InternalWalletState, + wex: WalletExecutionContext, exchangeBaseUrl: string, ): Promise<GetExchangeResourcesResponse> { // Withdrawals include internal withdrawals from peer transactions - const res = await ws.db.runReadOnlyTx( + const res = await wex.db.runReadOnlyTx( ["exchanges", "withdrawalGroups", "coins"], async (tx) => { const exchangeRecord = await tx.exchanges.get(exchangeBaseUrl); if (!exchangeRecord) { return undefined; } - return internalGetExchangeResources(ws, tx, exchangeBaseUrl); + return internalGetExchangeResources(wex, tx, exchangeBaseUrl); }, ); if (!res) { diff --git a/packages/taler-wallet-core/src/instructedAmountConversion.ts b/packages/taler-wallet-core/src/instructedAmountConversion.ts index 2250188b7..ccad050bf 100644 --- a/packages/taler-wallet-core/src/instructedAmountConversion.ts +++ b/packages/taler-wallet-core/src/instructedAmountConversion.ts @@ -34,7 +34,7 @@ import { import { CoinInfo } from "./coinSelection.js"; import { DenominationRecord, timestampProtocolFromDb } from "./db.js"; import { getExchangeWireDetailsInTx } from "./exchanges.js"; -import { InternalWalletState } from "./wallet.js"; +import { InternalWalletState, WalletExecutionContext } from "./wallet.js"; /** * If the operation going to be plan subtracts @@ -129,14 +129,14 @@ interface AvailableCoins { * of being cached */ async function getAvailableDenoms( - ws: InternalWalletState, + wex: WalletExecutionContext, op: TransactionType, currency: string, filters: CoinsFilter = {}, ): Promise<AvailableCoins> { const operationType = getOperationType(TransactionType.Deposit); - return await ws.db.runReadOnlyTx( + return await wex.db.runReadOnlyTx( ["exchanges", "exchangeDetails", "denominations", "coinAvailability"], async (tx) => { const list: CoinInfo[] = []; @@ -328,14 +328,14 @@ function buildCoinInfoFromDenom( } export async function convertDepositAmount( - ws: InternalWalletState, + wex: WalletExecutionContext, req: ConvertAmountRequest, ): Promise<AmountResponse> { const amount = Amounts.parseOrThrow(req.amount); // const filter = getCoinsFilter(req); const denoms = await getAvailableDenoms( - ws, + wex, TransactionType.Deposit, amount.currency, {}, @@ -430,13 +430,13 @@ export function convertDepositAmountForAvailableCoins( } export async function getMaxDepositAmount( - ws: InternalWalletState, + wex: WalletExecutionContext, req: GetAmountRequest, ): Promise<AmountResponse> { // const filter = getCoinsFilter(req); const denoms = await getAvailableDenoms( - ws, + wex, TransactionType.Deposit, req.currency, {}, @@ -473,25 +473,27 @@ export function getMaxDepositAmountForAvailableCoins( } export async function convertPeerPushAmount( - ws: InternalWalletState, + wex: WalletExecutionContext, req: ConvertAmountRequest, ): Promise<AmountResponse> { throw Error("to be implemented after 1.0"); } + export async function getMaxPeerPushAmount( - ws: InternalWalletState, + wex: WalletExecutionContext, req: GetAmountRequest, ): Promise<AmountResponse> { throw Error("to be implemented after 1.0"); } + export async function convertWithdrawalAmount( - ws: InternalWalletState, + wex: WalletExecutionContext, req: ConvertAmountRequest, ): Promise<AmountResponse> { const amount = Amounts.parseOrThrow(req.amount); const denoms = await getAvailableDenoms( - ws, + wex, TransactionType.Withdrawal, amount.currency, {}, diff --git a/packages/taler-wallet-core/src/pay-merchant.ts b/packages/taler-wallet-core/src/pay-merchant.ts index 73dc59ba2..a576930ba 100644 --- a/packages/taler-wallet-core/src/pay-merchant.ts +++ b/packages/taler-wallet-core/src/pay-merchant.ts @@ -33,7 +33,6 @@ import { AmountString, assertUnreachable, AsyncFlag, - CancellationToken, checkDbInvariant, codecForAbortResponse, codecForMerchantContractTerms, @@ -142,6 +141,7 @@ import { EXCHANGE_COINS_LOCK, getDenomInfo, InternalWalletState, + WalletExecutionContext, } from "./wallet.js"; import { getCandidateWithdrawalDenomsTx } from "./withdraw.js"; @@ -155,7 +155,7 @@ export class PayMerchantTransactionContext implements TransactionContext { readonly taskId: TaskIdStr; constructor( - public ws: InternalWalletState, + public wex: WalletExecutionContext, public proposalId: string, ) { this.transactionId = constructTransactionIdentifier({ @@ -198,7 +198,7 @@ export class PayMerchantTransactionContext implements TransactionContext { >, ) => Promise<TransitionResult>, ): Promise<void> { - const ws = this.ws; + const ws = this.wex; const extraStores = opts.extraStores ?? []; const transitionInfo = await ws.db.runReadWriteTx( ["purchases", ...extraStores], @@ -227,7 +227,7 @@ export class PayMerchantTransactionContext implements TransactionContext { } async deleteTransaction(): Promise<void> { - const { ws, proposalId } = this; + const { wex: ws, proposalId } = this; await ws.db.runReadWriteTx(["purchases", "tombstones"], async (tx) => { let found = false; const purchase = await tx.purchases.get(proposalId); @@ -244,7 +244,7 @@ export class PayMerchantTransactionContext implements TransactionContext { } async suspendTransaction(): Promise<void> { - const { ws, proposalId, transactionId } = this; + const { wex: ws, proposalId, transactionId } = this; ws.taskScheduler.stopShepherdTask(this.taskId); const transitionInfo = await ws.db.runReadWriteTx( ["purchases"], @@ -267,8 +267,8 @@ export class PayMerchantTransactionContext implements TransactionContext { } async abortTransaction(): Promise<void> { - const { ws, proposalId, transactionId } = this; - const transitionInfo = await ws.db.runReadWriteTx( + const { wex, proposalId, transactionId } = this; + const transitionInfo = await wex.db.runReadWriteTx( [ "purchases", "refreshGroups", @@ -308,7 +308,7 @@ export class PayMerchantTransactionContext implements TransactionContext { }); } await createRefreshGroup( - ws, + wex, tx, currency, refreshCoins, @@ -328,13 +328,13 @@ export class PayMerchantTransactionContext implements TransactionContext { return { oldTxState, newTxState }; }, ); - ws.taskScheduler.stopShepherdTask(this.taskId); - notifyTransition(ws, transactionId, transitionInfo); - ws.taskScheduler.startShepherdTask(this.taskId); + wex.taskScheduler.stopShepherdTask(this.taskId); + notifyTransition(wex, transactionId, transitionInfo); + wex.taskScheduler.startShepherdTask(this.taskId); } async resumeTransaction(): Promise<void> { - const { ws, proposalId, transactionId, taskId: retryTag } = this; + const { wex: ws, proposalId, transactionId, taskId: retryTag } = this; const transitionInfo = await ws.db.runReadWriteTx( ["purchases"], async (tx) => { @@ -357,7 +357,7 @@ export class PayMerchantTransactionContext implements TransactionContext { } async failTransaction(): Promise<void> { - const { ws, proposalId, transactionId } = this; + const { wex: ws, proposalId, transactionId } = this; const transitionInfo = await ws.db.runReadWriteTx( [ "purchases", @@ -396,7 +396,7 @@ export class RefundTransactionContext implements TransactionContext { public transactionId: TransactionIdStr; public taskId: TaskIdStr | undefined = undefined; constructor( - public ws: InternalWalletState, + public wex: WalletExecutionContext, public refundGroupId: string, ) { this.transactionId = constructTransactionIdentifier({ @@ -406,8 +406,8 @@ export class RefundTransactionContext implements TransactionContext { } async deleteTransaction(): Promise<void> { - const { ws, refundGroupId, transactionId } = this; - await ws.db.runReadWriteTx(["refundGroups", "tombstones"], async (tx) => { + const { wex, refundGroupId, transactionId } = this; + await wex.db.runReadWriteTx(["refundGroups", "tombstones"], async (tx) => { const refundRecord = await tx.refundGroups.get(refundGroupId); if (!refundRecord) { return; @@ -443,11 +443,11 @@ export class RefundTransactionContext implements TransactionContext { * of coins that are too small to spend. */ export async function getTotalPaymentCost( - ws: InternalWalletState, + wex: WalletExecutionContext, pcs: PayCoinSelection, ): Promise<AmountJson> { const currency = Amounts.currencyOf(pcs.paymentAmount); - return ws.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { + return wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { const costs: AmountJson[] = []; for (let i = 0; i < pcs.coinPubs.length; i++) { const coin = await tx.coins.get(pcs.coinPubs[i]); @@ -464,7 +464,7 @@ export async function getTotalPaymentCost( ); } const allDenoms = await getCandidateWithdrawalDenomsTx( - ws, + wex, tx, coin.exchangeBaseUrl, currency, @@ -477,7 +477,7 @@ export async function getTotalPaymentCost( allDenoms, DenominationRecord.toDenomInfo(denom), amountLeft, - ws.config.testing.denomselAllowLate, + wex.ws.config.testing.denomselAllowLate, ); costs.push(Amounts.parseOrThrow(pcs.coinContributions[i])); costs.push(refreshCost); @@ -488,7 +488,7 @@ export async function getTotalPaymentCost( } async function failProposalPermanently( - ws: InternalWalletState, + wex: WalletExecutionContext, proposalId: string, err: TalerErrorDetail, ): Promise<void> { @@ -496,7 +496,7 @@ async function failProposalPermanently( tag: TransactionType.Payment, proposalId, }); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["purchases"], async (tx) => { const p = await tx.purchases.get(proposalId); @@ -511,7 +511,7 @@ async function failProposalPermanently( return { oldTxState, newTxState }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); } function getPayRequestTimeout(purchase: PurchaseRecord): Duration { @@ -525,7 +525,7 @@ function getPayRequestTimeout(purchase: PurchaseRecord): Duration { * Return the proposal download data for a purchase, throw if not available. */ export async function expectProposalDownload( - ws: InternalWalletState, + wex: WalletExecutionContext, p: PurchaseRecord, parentTx?: WalletDbReadOnlyTransaction<["contractTerms"]>, ): Promise<{ @@ -559,7 +559,7 @@ export async function expectProposalDownload( if (parentTx) { return getFromTransaction(parentTx); } - return await ws.db.runReadOnlyTx(["contractTerms"], getFromTransaction); + return await wex.db.runReadOnlyTx(["contractTerms"], getFromTransaction); } export function extractContractData( @@ -603,11 +603,10 @@ export function extractContractData( } async function processDownloadProposal( - ws: InternalWalletState, + wex: WalletExecutionContext, proposalId: string, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { - const proposal = await ws.db.runReadOnlyTx(["purchases"], async (tx) => { + const proposal = await wex.db.runReadOnlyTx(["purchases"], async (tx) => { return await tx.purchases.get(proposalId); }); @@ -615,7 +614,7 @@ async function processDownloadProposal( return TaskRunResult.finished(); } - const ctx = new PayMerchantTransactionContext(ws, proposalId); + const ctx = new PayMerchantTransactionContext(wex, proposalId); if (proposal.purchaseStatus != PurchaseStatus.PendingDownloadingProposal) { logger.error( @@ -644,10 +643,10 @@ async function processDownloadProposal( requestBody.token = proposal.claimToken; } - const httpResponse = await ws.http.fetch(orderClaimUrl, { + const httpResponse = await wex.http.fetch(orderClaimUrl, { method: "POST", body: requestBody, - cancellationToken, + cancellationToken: wex.cancellationToken, }); const r = await readSuccessResponseJsonOrErrorCode( httpResponse, @@ -692,7 +691,7 @@ async function processDownloadProposal( {}, "validation for well-formedness failed", ); - await failProposalPermanently(ws, proposalId, err); + await failProposalPermanently(wex, proposalId, err); throw makePendingOperationFailedError( err, TransactionType.Payment, @@ -718,7 +717,7 @@ async function processDownloadProposal( {}, `schema validation failed: ${e}`, ); - await failProposalPermanently(ws, proposalId, err); + await failProposalPermanently(wex, proposalId, err); throw makePendingOperationFailedError( err, TransactionType.Payment, @@ -726,7 +725,7 @@ async function processDownloadProposal( ); } - const sigValid = await ws.cryptoApi.isValidContractTermsSignature({ + const sigValid = await wex.cryptoApi.isValidContractTermsSignature({ contractTermsHash, merchantPub: parsedContractTerms.merchant_pub, sig: proposalResp.sig, @@ -741,7 +740,7 @@ async function processDownloadProposal( }, "merchant's signature on contract terms is invalid", ); - await failProposalPermanently(ws, proposalId, err); + await failProposalPermanently(wex, proposalId, err); throw makePendingOperationFailedError( err, TransactionType.Payment, @@ -763,7 +762,7 @@ async function processDownloadProposal( }, "merchant base URL mismatch", ); - await failProposalPermanently(ws, proposalId, err); + await failProposalPermanently(wex, proposalId, err); throw makePendingOperationFailedError( err, TransactionType.Payment, @@ -779,7 +778,7 @@ async function processDownloadProposal( logger.trace(`extracted contract data: ${j2s(contractData)}`); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["purchases", "contractTerms"], async (tx) => { const p = await tx.purchases.get(proposalId); @@ -835,7 +834,7 @@ async function processDownloadProposal( }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return TaskRunResult.progress(); } @@ -846,15 +845,14 @@ async function processDownloadProposal( * return the old proposal ID. */ async function createOrReusePurchase( - ws: InternalWalletState, + wex: WalletExecutionContext, merchantBaseUrl: string, orderId: string, sessionId: string | undefined, claimToken: string | undefined, noncePriv: string | undefined, - cancellationToken: CancellationToken, ): Promise<string> { - const oldProposals = await ws.db.runReadOnlyTx(["purchases"], async (tx) => { + const oldProposals = await wex.db.runReadOnlyTx(["purchases"], async (tx) => { return tx.purchases.indexes.byUrlAndOrderId.getAll([ merchantBaseUrl, orderId, @@ -882,17 +880,13 @@ async function createOrReusePurchase( }) for order ${orderId} at ${merchantBaseUrl}`, ); if (oldProposal.purchaseStatus === PurchaseStatus.DialogShared) { - const download = await expectProposalDownload(ws, oldProposal); - const paid = await checkIfOrderIsAlreadyPaid( - ws, - download.contractData, - cancellationToken, - ); + const download = await expectProposalDownload(wex, oldProposal); + const paid = await checkIfOrderIsAlreadyPaid(wex, download.contractData); logger.info(`old proposal paid: ${paid}`); if (paid) { // if this transaction was shared and the order is paid then it // means that another wallet already paid the proposal - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["purchases"], async (tx) => { const p = await tx.purchases.get(oldProposal.proposalId); @@ -912,7 +906,7 @@ async function createOrReusePurchase( tag: TransactionType.Payment, proposalId: oldProposal.proposalId, }); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); } } return oldProposal.proposalId; @@ -924,10 +918,10 @@ async function createOrReusePurchase( shared = true; noncePair = { priv: noncePriv, - pub: (await ws.cryptoApi.eddsaGetPublic({ priv: noncePriv })).pub, + pub: (await wex.cryptoApi.eddsaGetPublic({ priv: noncePriv })).pub, }; } else { - noncePair = await ws.cryptoApi.createEddsaKeypair({}); + noncePair = await wex.cryptoApi.createEddsaKeypair({}); } const { priv, pub } = noncePair; @@ -958,7 +952,7 @@ async function createOrReusePurchase( shared: shared, }; - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["purchases"], async (tx) => { await tx.purchases.put(proposalRecord); @@ -977,12 +971,12 @@ async function createOrReusePurchase( tag: TransactionType.Payment, proposalId, }); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return proposalId; } async function storeFirstPaySuccess( - ws: InternalWalletState, + wex: WalletExecutionContext, proposalId: string, sessionId: string | undefined, payResponse: MerchantPayResponse, @@ -992,7 +986,7 @@ async function storeFirstPaySuccess( proposalId, }); const now = AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now()); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["contractTerms", "purchases"], async (tx) => { const purchase = await tx.purchases.get(proposalId); @@ -1044,11 +1038,11 @@ async function storeFirstPaySuccess( }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); } async function storePayReplaySuccess( - ws: InternalWalletState, + wex: WalletExecutionContext, proposalId: string, sessionId: string | undefined, ): Promise<void> { @@ -1056,7 +1050,7 @@ async function storePayReplaySuccess( tag: TransactionType.Payment, proposalId, }); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["purchases"], async (tx) => { const purchase = await tx.purchases.get(proposalId); @@ -1082,7 +1076,7 @@ async function storePayReplaySuccess( return { oldTxState, newTxState }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); } /** @@ -1094,13 +1088,13 @@ async function storePayReplaySuccess( * (3) re-do coin selection with the bad coin removed */ async function handleInsufficientFunds( - ws: InternalWalletState, + wex: WalletExecutionContext, proposalId: string, err: TalerErrorDetail, ): Promise<void> { logger.trace("handling insufficient funds, trying to re-select coins"); - const proposal = await ws.db.runReadOnlyTx(["purchases"], async (tx) => { + const proposal = await wex.db.runReadOnlyTx(["purchases"], async (tx) => { return tx.purchases.get(proposalId); }); if (!proposal) { @@ -1128,7 +1122,7 @@ async function handleInsufficientFunds( throw new TalerProtocolViolationError(); } - const { contractData } = await expectProposalDownload(ws, proposal); + const { contractData } = await expectProposalDownload(wex, proposal); const prevPayCoins: PreviousPayCoins = []; @@ -1139,7 +1133,7 @@ async function handleInsufficientFunds( const payCoinSelection = payInfo.payCoinSelection; - await ws.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { + await wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { for (let i = 0; i < payCoinSelection.coinPubs.length; i++) { const coinPub = payCoinSelection.coinPubs[i]; if (coinPub === brokenCoinPub) { @@ -1166,7 +1160,7 @@ async function handleInsufficientFunds( } }); - const res = await selectPayCoinsNew(ws, { + const res = await selectPayCoinsNew(wex, { auditors: [], exchanges: contractData.allowedExchanges, wireMethod: contractData.wireMethod, @@ -1185,7 +1179,7 @@ async function handleInsufficientFunds( logger.trace("re-selected coins"); - await ws.db.runReadWriteTx( + await wex.db.runReadWriteTx( [ "purchases", "coins", @@ -1205,7 +1199,7 @@ async function handleInsufficientFunds( payInfo.payCoinSelection = res.coinSel; payInfo.payCoinSelectionUid = encodeCrock(getRandomBytes(32)); await tx.purchases.put(p); - await spendCoins(ws, tx, { + await spendCoins(wex, tx, { // allocationId: `txn:proposal:${p.proposalId}`, allocationId: constructTransactionIdentifier({ tag: TransactionType.Payment, @@ -1220,7 +1214,7 @@ async function handleInsufficientFunds( }, ); - ws.notify({ + wex.ws.notify({ type: NotificationType.BalanceChange, hintTransactionId: constructTransactionIdentifier({ tag: TransactionType.Payment, @@ -1233,11 +1227,11 @@ async function handleInsufficientFunds( // FIXME: Does way more than checking the payment // FIXME: Should return immediately. async function checkPaymentByProposalId( - ws: InternalWalletState, + wex: WalletExecutionContext, proposalId: string, sessionId?: string, ): Promise<PreparePayResult> { - let proposal = await ws.db.runReadOnlyTx(["purchases"], async (tx) => { + let proposal = await wex.db.runReadOnlyTx(["purchases"], async (tx) => { return tx.purchases.get(proposalId); }); if (!proposal) { @@ -1247,7 +1241,7 @@ async function checkPaymentByProposalId( const existingProposalId = proposal.repurchaseProposalId; if (existingProposalId) { logger.trace("using existing purchase for same product"); - const oldProposal = await ws.db.runReadOnlyTx( + const oldProposal = await wex.db.runReadOnlyTx( ["purchases"], async (tx) => { return tx.purchases.get(existingProposalId); @@ -1258,7 +1252,7 @@ async function checkPaymentByProposalId( } } } - const d = await expectProposalDownload(ws, proposal); + const d = await expectProposalDownload(wex, proposal); const contractData = d.contractData; const merchantSig = d.contractData.merchantSig; if (!merchantSig) { @@ -1267,7 +1261,7 @@ async function checkPaymentByProposalId( proposalId = proposal.proposalId; - const ctx = new PayMerchantTransactionContext(ws, proposalId); + const ctx = new PayMerchantTransactionContext(wex, proposalId); const transactionId = ctx.transactionId; @@ -1280,7 +1274,7 @@ async function checkPaymentByProposalId( }); // First check if we already paid for it. - const purchase = await ws.db.runReadOnlyTx(["purchases"], async (tx) => { + const purchase = await wex.db.runReadOnlyTx(["purchases"], async (tx) => { return tx.purchases.get(proposalId); }); @@ -1290,7 +1284,7 @@ async function checkPaymentByProposalId( purchase.purchaseStatus === PurchaseStatus.DialogShared ) { // If not already paid, check if we could pay for it. - const res = await selectPayCoinsNew(ws, { + const res = await selectPayCoinsNew(wex, { auditors: [], exchanges: contractData.allowedExchanges, contractTermsAmount: Amounts.parseOrThrow(contractData.amount), @@ -1318,7 +1312,7 @@ async function checkPaymentByProposalId( }; } - const totalCost = await getTotalPaymentCost(ws, res.coinSel); + const totalCost = await getTotalPaymentCost(wex, res.coinSel); logger.trace("costInfo", totalCost); logger.trace("coinsForPayment", res); @@ -1342,7 +1336,7 @@ async function checkPaymentByProposalId( "automatically re-submitting payment with different session ID", ); logger.trace(`last: ${purchase.lastSessionId}, current: ${sessionId}`); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["purchases"], async (tx) => { const p = await tx.purchases.get(proposalId); @@ -1357,14 +1351,14 @@ async function checkPaymentByProposalId( return { oldTxState, newTxState }; }, ); - notifyTransition(ws, transactionId, transitionInfo); - ws.taskScheduler.startShepherdTask(ctx.taskId); + notifyTransition(wex, transactionId, transitionInfo); + wex.taskScheduler.startShepherdTask(ctx.taskId); // FIXME: Consider changing the API here so that we don't have to // wait inline for the repurchase. - await waitPaymentResult(ws, proposalId, sessionId); - const download = await expectProposalDownload(ws, purchase); + await waitPaymentResult(wex, proposalId, sessionId); + const download = await expectProposalDownload(wex, purchase); return { status: PreparePayResultType.AlreadyConfirmed, contractTerms: download.contractTermsRaw, @@ -1379,7 +1373,7 @@ async function checkPaymentByProposalId( talerUri, }; } else if (!purchase.timestampFirstSuccessfulPay) { - const download = await expectProposalDownload(ws, purchase); + const download = await expectProposalDownload(wex, purchase); return { status: PreparePayResultType.AlreadyConfirmed, contractTerms: download.contractTermsRaw, @@ -1398,7 +1392,7 @@ async function checkPaymentByProposalId( purchase.purchaseStatus === PurchaseStatus.Done || purchase.purchaseStatus === PurchaseStatus.PendingQueryingRefund || purchase.purchaseStatus === PurchaseStatus.PendingQueryingAutoRefund; - const download = await expectProposalDownload(ws, purchase); + const download = await expectProposalDownload(wex, purchase); return { status: PreparePayResultType.AlreadyConfirmed, contractTerms: download.contractTermsRaw, @@ -1417,10 +1411,10 @@ async function checkPaymentByProposalId( } export async function getContractTermsDetails( - ws: InternalWalletState, + wex: WalletExecutionContext, proposalId: string, ): Promise<WalletContractData> { - const proposal = await ws.db.runReadOnlyTx(["purchases"], async (tx) => { + const proposal = await wex.db.runReadOnlyTx(["purchases"], async (tx) => { return tx.purchases.get(proposalId); }); @@ -1428,7 +1422,7 @@ export async function getContractTermsDetails( throw Error(`proposal with id ${proposalId} not found`); } - const d = await expectProposalDownload(ws, proposal); + const d = await expectProposalDownload(wex, proposal); return d.contractData; } @@ -1440,7 +1434,7 @@ export async function getContractTermsDetails( * yet send to the merchant. */ export async function preparePayForUri( - ws: InternalWalletState, + wex: WalletExecutionContext, talerPayUri: string, ): Promise<PreparePayResult> { const uriResult = parsePayUri(talerPayUri); @@ -1456,39 +1450,38 @@ export async function preparePayForUri( } const proposalId = await createOrReusePurchase( - ws, + wex, uriResult.merchantBaseUrl, uriResult.orderId, uriResult.sessionId, uriResult.claimToken, uriResult.noncePriv, - CancellationToken.CONTINUE, ); - await waitProposalDownloaded(ws, proposalId); + await waitProposalDownloaded(wex, proposalId); - return checkPaymentByProposalId(ws, proposalId, uriResult.sessionId); + return checkPaymentByProposalId(wex, proposalId, uriResult.sessionId); } /** * Wait until a proposal is at least downloaded. */ async function waitProposalDownloaded( - ws: InternalWalletState, + wex: WalletExecutionContext, proposalId: string, ): Promise<void> { - const ctx = new PayMerchantTransactionContext(ws, proposalId); + const ctx = new PayMerchantTransactionContext(wex, proposalId); logger.info(`waiting for ${ctx.transactionId} to be downloaded`); - ws.taskScheduler.startShepherdTask(ctx.taskId); + wex.taskScheduler.startShepherdTask(ctx.taskId); // FIXME: We should use Symbol.dispose magic here for cleanup! const payNotifFlag = new AsyncFlag(); // Raise exchangeNotifFlag whenever we get a notification // about our exchange. - const cancelNotif = ws.addNotificationListener((notif) => { + const cancelNotif = wex.ws.addNotificationListener((notif) => { if ( notif.type === NotificationType.TransactionStateTransition && notif.transactionId === ctx.transactionId @@ -1511,7 +1504,7 @@ async function internalWaitProposalDownloaded( payNotifFlag: AsyncFlag, ): Promise<void> { while (true) { - const { purchase, retryInfo } = await ctx.ws.db.runReadOnlyTx( + const { purchase, retryInfo } = await ctx.wex.db.runReadOnlyTx( ["purchases", "operationRetries"], async (tx) => { return { @@ -1539,7 +1532,7 @@ async function internalWaitProposalDownloaded( } export async function preparePayForTemplate( - ws: InternalWalletState, + wex: WalletExecutionContext, req: PreparePayTemplateRequest, ): Promise<PreparePayResult> { const parsedUri = parsePayTemplateUri(req.talerPayTemplateUri); @@ -1575,7 +1568,7 @@ export async function preparePayForTemplate( `templates/${parsedUri.templateId}`, parsedUri.merchantBaseUrl, ); - const httpReq = await ws.http.fetch(reqUrl.href, { + const httpReq = await wex.http.fetch(reqUrl.href, { method: "POST", body: templateDetails, }); @@ -1591,7 +1584,7 @@ export async function preparePayForTemplate( claimToken: resp.token, }); - return await preparePayForUri(ws, payUri); + return await preparePayForUri(wex, payUri); } /** @@ -1600,7 +1593,7 @@ export async function preparePayForTemplate( * Accesses the database and the crypto worker. */ export async function generateDepositPermissions( - ws: InternalWalletState, + wex: WalletExecutionContext, payCoinSel: PayCoinSelection, contractData: WalletContractData, ): Promise<CoinDepositPermission[]> { @@ -1609,7 +1602,7 @@ export async function generateDepositPermissions( coin: CoinRecord; denom: DenominationRecord; }> = []; - await ws.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { + await wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { for (let i = 0; i < payCoinSel.coinPubs.length; i++) { const coin = await tx.coins.get(payCoinSel.coinPubs[i]); if (!coin) { @@ -1637,7 +1630,7 @@ export async function generateDepositPermissions( coin.ageCommitmentProof, )}`, ); - const dp = await ws.cryptoApi.signDepositPermission({ + const dp = await wex.cryptoApi.signDepositPermission({ coinPriv: coin.coinPriv, coinPub: coin.coinPub, contractTermsHash: contractData.contractTermsHash, @@ -1665,7 +1658,7 @@ async function internalWaitPaymentResult( waitSessionId?: string, ): Promise<ConfirmPayResult> { while (true) { - const txRes = await ctx.ws.db.runReadOnlyTx( + const txRes = await ctx.wex.db.runReadOnlyTx( ["purchases", "operationRetries"], async (tx) => { const purchase = await tx.purchases.get(ctx.proposalId); @@ -1684,7 +1677,7 @@ async function internalWaitPaymentResult( `purchase is in state ${PurchaseStatus[purchase.purchaseStatus]}`, ); - const d = await expectProposalDownload(ctx.ws, purchase); + const d = await expectProposalDownload(ctx.wex, purchase); if (txRes.purchase.timestampFirstSuccessfulPay) { if ( @@ -1726,20 +1719,20 @@ async function internalWaitPaymentResult( * b) the attempt to pay failed (merchant unavailable, etc.) */ async function waitPaymentResult( - ws: InternalWalletState, + wex: WalletExecutionContext, proposalId: string, waitSessionId?: string, ): Promise<ConfirmPayResult> { - const ctx = new PayMerchantTransactionContext(ws, proposalId); + const ctx = new PayMerchantTransactionContext(wex, proposalId); - ws.taskScheduler.ensureRunning(); - ws.taskScheduler.startShepherdTask(ctx.taskId); + wex.taskScheduler.ensureRunning(); + wex.taskScheduler.startShepherdTask(ctx.taskId); // FIXME: Clean up using the new JS "using" / Symbol.dispose syntax. const purchaseNotifFlag = new AsyncFlag(); // Raise purchaseNotifFlag whenever we get a notification // about our purchase. - const cancelNotif = ws.addNotificationListener((notif) => { + const cancelNotif = wex.ws.addNotificationListener((notif) => { if ( notif.type === NotificationType.TransactionStateTransition && notif.transactionId === ctx.transactionId @@ -1768,7 +1761,7 @@ async function waitPaymentResult( * Confirm payment for a proposal previously claimed by the wallet. */ export async function confirmPay( - ws: InternalWalletState, + wex: WalletExecutionContext, transactionId: string, sessionIdOverride?: string, forcedCoinSel?: ForcedCoinSel, @@ -1781,7 +1774,7 @@ export async function confirmPay( logger.trace( `executing confirmPay with proposalId ${proposalId} and sessionIdOverride ${sessionIdOverride}`, ); - const proposal = await ws.db.runReadOnlyTx(["purchases"], async (tx) => { + const proposal = await wex.db.runReadOnlyTx(["purchases"], async (tx) => { return tx.purchases.get(proposalId); }); @@ -1789,12 +1782,12 @@ export async function confirmPay( throw Error(`proposal with id ${proposalId} not found`); } - const d = await expectProposalDownload(ws, proposal); + const d = await expectProposalDownload(wex, proposal); if (!d) { throw Error("proposal is in invalid state"); } - const existingPurchase = await ws.db.runReadWriteTx( + const existingPurchase = await wex.db.runReadWriteTx( ["purchases"], async (tx) => { const purchase = await tx.purchases.get(proposalId); @@ -1817,18 +1810,18 @@ export async function confirmPay( if (existingPurchase && existingPurchase.payInfo) { logger.trace("confirmPay: submitting payment for existing purchase"); const ctx = new PayMerchantTransactionContext( - ws, + wex, existingPurchase.proposalId, ); - await ws.taskScheduler.resetTaskRetries(ctx.taskId); - return waitPaymentResult(ws, proposalId); + await wex.taskScheduler.resetTaskRetries(ctx.taskId); + return waitPaymentResult(wex, proposalId); } logger.trace("confirmPay: purchase record does not exist yet"); const contractData = d.contractData; - const selectCoinsResult = await selectPayCoinsNew(ws, { + const selectCoinsResult = await selectPayCoinsNew(wex, { auditors: [], exchanges: contractData.allowedExchanges, wireMethod: contractData.wireMethod, @@ -1852,7 +1845,7 @@ export async function confirmPay( } const coinSelection = selectCoinsResult.coinSel; - const payCostInfo = await getTotalPaymentCost(ws, coinSelection); + const payCostInfo = await getTotalPaymentCost(wex, coinSelection); let sessionId: string | undefined; if (sessionIdOverride) { @@ -1865,7 +1858,7 @@ export async function confirmPay( `recording payment on ${proposal.orderId} with session ID ${sessionId}`, ); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( [ "purchases", "coins", @@ -1891,7 +1884,7 @@ export async function confirmPay( p.timestampAccept = timestampPreciseToDb(TalerPreciseTimestamp.now()); p.purchaseStatus = PurchaseStatus.PendingPaying; await tx.purchases.put(p); - await spendCoins(ws, tx, { + await spendCoins(wex, tx, { //`txn:proposal:${p.proposalId}` allocationId: constructTransactionIdentifier({ tag: TransactionType.Payment, @@ -1914,22 +1907,21 @@ export async function confirmPay( }, ); - notifyTransition(ws, transactionId, transitionInfo); - ws.notify({ + notifyTransition(wex, transactionId, transitionInfo); + wex.ws.notify({ type: NotificationType.BalanceChange, hintTransactionId: transactionId, }); // Wait until we have completed the first attempt to pay. - return waitPaymentResult(ws, proposalId); + return waitPaymentResult(wex, proposalId); } export async function processPurchase( - ws: InternalWalletState, + wex: WalletExecutionContext, proposalId: string, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { - const purchase = await ws.db.runReadOnlyTx(["purchases"], async (tx) => { + const purchase = await wex.db.runReadOnlyTx(["purchases"], async (tx) => { return tx.purchases.get(proposalId); }); if (!purchase) { @@ -1947,20 +1939,20 @@ export async function processPurchase( switch (purchase.purchaseStatus) { case PurchaseStatus.PendingDownloadingProposal: - return processDownloadProposal(ws, proposalId, cancellationToken); + return processDownloadProposal(wex, proposalId); case PurchaseStatus.PendingPaying: case PurchaseStatus.PendingPayingReplay: - return processPurchasePay(ws, proposalId, cancellationToken); + return processPurchasePay(wex, proposalId); case PurchaseStatus.PendingQueryingRefund: - return processPurchaseQueryRefund(ws, purchase, cancellationToken); + return processPurchaseQueryRefund(wex, purchase); case PurchaseStatus.PendingQueryingAutoRefund: - return processPurchaseAutoRefund(ws, purchase, cancellationToken); + return processPurchaseAutoRefund(wex, purchase); case PurchaseStatus.AbortingWithRefund: - return processPurchaseAbortingRefund(ws, purchase, cancellationToken); + return processPurchaseAbortingRefund(wex, purchase); case PurchaseStatus.PendingAcceptRefund: - return processPurchaseAcceptRefund(ws, purchase, cancellationToken); + return processPurchaseAcceptRefund(wex, purchase); case PurchaseStatus.DialogShared: - return processPurchaseDialogShared(ws, purchase, cancellationToken); + return processPurchaseDialogShared(wex, purchase); case PurchaseStatus.FailedClaim: case PurchaseStatus.Done: case PurchaseStatus.DoneRepurchaseDetected: @@ -1984,11 +1976,10 @@ export async function processPurchase( } async function processPurchasePay( - ws: InternalWalletState, + wex: WalletExecutionContext, proposalId: string, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { - const purchase = await ws.db.runReadOnlyTx(["purchases"], async (tx) => { + const purchase = await wex.db.runReadOnlyTx(["purchases"], async (tx) => { return tx.purchases.get(proposalId); }); if (!purchase) { @@ -2018,17 +2009,13 @@ async function processPurchasePay( const payInfo = purchase.payInfo; checkDbInvariant(!!payInfo, "payInfo"); - const download = await expectProposalDownload(ws, purchase); + const download = await expectProposalDownload(wex, purchase); if (purchase.shared) { - const paid = await checkIfOrderIsAlreadyPaid( - ws, - download.contractData, - cancellationToken, - ); + const paid = await checkIfOrderIsAlreadyPaid(wex, download.contractData); if (paid) { - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["purchases"], async (tx) => { const p = await tx.purchases.get(purchase.proposalId); @@ -2048,7 +2035,7 @@ async function processPurchasePay( proposalId, }); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return { type: TaskRunResultType.Error, @@ -2069,7 +2056,7 @@ async function processPurchasePay( let depositPermissions: CoinDepositPermission[]; // FIXME: Cache! depositPermissions = await generateDepositPermissions( - ws, + wex, payInfo.payCoinSelection, download.contractData, ); @@ -2084,12 +2071,12 @@ async function processPurchasePay( JSON.stringify(reqBody, undefined, 2), ); - const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], () => - ws.http.fetch(payUrl, { + const resp = await wex.ws.runSequentialized([EXCHANGE_COINS_LOCK], () => + wex.http.fetch(payUrl, { method: "POST", body: reqBody, timeout: getPayRequestTimeout(purchase), - cancellationToken, + cancellationToken: wex.cancellationToken, }), ); @@ -2115,7 +2102,7 @@ async function processPurchasePay( TalerErrorCode.MERCHANT_POST_ORDERS_ID_PAY_INSUFFICIENT_FUNDS ) { // Do this in the background, as it might take some time - handleInsufficientFunds(ws, proposalId, err).catch(async (e) => { + handleInsufficientFunds(wex, proposalId, err).catch(async (e) => { logger.error("handling insufficient funds failed"); logger.error(`${e.toString()}`); }); @@ -2140,7 +2127,7 @@ async function processPurchasePay( logger.trace("got success from pay URL", merchantResp); const merchantPub = download.contractData.merchantPub; - const { valid } = await ws.cryptoApi.isValidPaymentSignature({ + const { valid } = await wex.cryptoApi.isValidPaymentSignature({ contractHash: download.contractData.contractTermsHash, merchantPub, sig: merchantResp.sig, @@ -2152,7 +2139,7 @@ async function processPurchasePay( throw Error("merchant payment signature invalid"); } - await storeFirstPaySuccess(ws, proposalId, sessionId, merchantResp); + await storeFirstPaySuccess(wex, proposalId, sessionId, merchantResp); } else { const payAgainUrl = new URL( `orders/${download.contractData.orderId}/paid`, @@ -2164,11 +2151,11 @@ async function processPurchasePay( session_id: sessionId ?? "", }; logger.trace(`/paid request body: ${j2s(reqBody)}`); - const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], () => - ws.http.fetch(payAgainUrl, { + const resp = await wex.ws.runSequentialized([EXCHANGE_COINS_LOCK], () => + wex.http.fetch(payAgainUrl, { method: "POST", body: reqBody, - cancellationToken, + cancellationToken: wex.cancellationToken, }), ); logger.trace(`/paid response status: ${resp.status}`); @@ -2182,21 +2169,21 @@ async function processPurchasePay( "/paid failed", ); } - await storePayReplaySuccess(ws, proposalId, sessionId); + await storePayReplaySuccess(wex, proposalId, sessionId); } return TaskRunResult.progress(); } export async function refuseProposal( - ws: InternalWalletState, + wex: WalletExecutionContext, proposalId: string, ): Promise<void> { const transactionId = constructTransactionIdentifier({ tag: TransactionType.Payment, proposalId, }); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["purchases"], async (tx) => { const proposal = await tx.purchases.get(proposalId); @@ -2218,7 +2205,7 @@ export async function refuseProposal( }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); } const transitionSuspend: { @@ -2456,11 +2443,11 @@ export function computePayMerchantTransactionActions( } export async function sharePayment( - ws: InternalWalletState, + wex: WalletExecutionContext, merchantBaseUrl: string, orderId: string, ): Promise<SharePaymentResult> { - const result = await ws.db.runReadWriteTx(["purchases"], async (tx) => { + const result = await wex.db.runReadWriteTx(["purchases"], async (tx) => { const p = await tx.purchases.indexes.byUrlAndOrderId.get([ merchantBaseUrl, orderId, @@ -2503,9 +2490,8 @@ export async function sharePayment( } async function checkIfOrderIsAlreadyPaid( - ws: InternalWalletState, + wex: WalletExecutionContext, contract: WalletContractData, - cancellationToken: CancellationToken, ) { const requestUrl = new URL( `orders/${contract.orderId}`, @@ -2515,8 +2501,8 @@ async function checkIfOrderIsAlreadyPaid( requestUrl.searchParams.set("timeout_ms", "1000"); - const resp = await ws.http.fetch(requestUrl.href, { - cancellationToken, + const resp = await wex.http.fetch(requestUrl.href, { + cancellationToken: wex.cancellationToken, }); if ( resp.status === HttpStatusCode.Ok || @@ -2532,25 +2518,20 @@ async function checkIfOrderIsAlreadyPaid( } async function processPurchaseDialogShared( - ws: InternalWalletState, + wex: WalletExecutionContext, purchase: PurchaseRecord, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const proposalId = purchase.proposalId; logger.trace(`processing dialog-shared for proposal ${proposalId}`); - const download = await expectProposalDownload(ws, purchase); + const download = await expectProposalDownload(wex, purchase); if (purchase.purchaseStatus !== PurchaseStatus.DialogShared) { return TaskRunResult.finished(); } - const paid = await checkIfOrderIsAlreadyPaid( - ws, - download.contractData, - cancellationToken, - ); + const paid = await checkIfOrderIsAlreadyPaid(wex, download.contractData); if (paid) { - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["purchases"], async (tx) => { const p = await tx.purchases.get(purchase.proposalId); @@ -2570,31 +2551,25 @@ async function processPurchaseDialogShared( proposalId, }); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); } return TaskRunResult.backoff(); } async function processPurchaseAutoRefund( - ws: InternalWalletState, + wex: WalletExecutionContext, purchase: PurchaseRecord, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const proposalId = purchase.proposalId; logger.trace(`processing auto-refund for proposal ${proposalId}`); - const taskId = constructTaskIdentifier({ - tag: PendingTaskType.Purchase, - proposalId, - }); - const transactionId = constructTransactionIdentifier({ tag: TransactionType.Payment, proposalId, }); - const download = await expectProposalDownload(ws, purchase); + const download = await expectProposalDownload(wex, purchase); if ( !purchase.autoRefundDeadline || @@ -2604,7 +2579,7 @@ async function processPurchaseAutoRefund( ), ) ) { - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["purchases"], async (tx) => { const p = await tx.purchases.get(purchase.proposalId); @@ -2623,7 +2598,7 @@ async function processPurchaseAutoRefund( return { oldTxState, newTxState }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return TaskRunResult.finished(); } @@ -2639,8 +2614,8 @@ async function processPurchaseAutoRefund( requestUrl.searchParams.set("timeout_ms", "1000"); requestUrl.searchParams.set("await_refund_obtained", "yes"); - const resp = await ws.http.fetch(requestUrl.href, { - cancellationToken, + const resp = await wex.http.fetch(requestUrl.href, { + cancellationToken: wex.cancellationToken, }); // FIXME: Check other status codes! @@ -2651,7 +2626,7 @@ async function processPurchaseAutoRefund( ); if (orderStatus.refund_pending) { - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["purchases"], async (tx) => { const p = await tx.purchases.get(purchase.proposalId); @@ -2669,19 +2644,18 @@ async function processPurchaseAutoRefund( return { oldTxState, newTxState }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); } return TaskRunResult.backoff(); } async function processPurchaseAbortingRefund( - ws: InternalWalletState, + wex: WalletExecutionContext, purchase: PurchaseRecord, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const proposalId = purchase.proposalId; - const download = await expectProposalDownload(ws, purchase); + const download = await expectProposalDownload(wex, purchase); logger.trace(`processing aborting-refund for proposal ${proposalId}`); const requestUrl = new URL( @@ -2696,7 +2670,7 @@ async function processPurchaseAbortingRefund( throw Error("can't abort, no coins selected"); } - await ws.db.runReadOnlyTx(["coins"], async (tx) => { + await wex.db.runReadOnlyTx(["coins"], async (tx) => { for (let i = 0; i < payCoinSelection.coinPubs.length; i++) { const coinPub = payCoinSelection.coinPubs[i]; const coin = await tx.coins.get(coinPub); @@ -2716,10 +2690,10 @@ async function processPurchaseAbortingRefund( logger.trace(`making order abort request to ${requestUrl.href}`); - const abortHttpResp = await ws.http.fetch(requestUrl.href, { + const abortHttpResp = await wex.http.fetch(requestUrl.href, { method: "POST", body: abortReq, - cancellationToken, + cancellationToken: wex.cancellationToken, }); if (abortHttpResp.status === HttpStatusCode.NotFound) { @@ -2728,7 +2702,7 @@ async function processPurchaseAbortingRefund( err.code === TalerErrorCode.MERCHANT_POST_ORDERS_ID_ABORT_CONTRACT_NOT_FOUND ) { - const ctx = new PayMerchantTransactionContext(ws, proposalId); + const ctx = new PayMerchantTransactionContext(wex, proposalId); await ctx.transition(async (rec) => { if (rec.purchaseStatus === PurchaseStatus.AbortingWithRefund) { rec.purchaseStatus = PurchaseStatus.AbortedOrderDeleted; @@ -2766,18 +2740,17 @@ async function processPurchaseAbortingRefund( ), }); } - return await storeRefunds(ws, purchase, refunds, RefundReason.AbortRefund); + return await storeRefunds(wex, purchase, refunds, RefundReason.AbortRefund); } async function processPurchaseQueryRefund( - ws: InternalWalletState, + wex: WalletExecutionContext, purchase: PurchaseRecord, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const proposalId = purchase.proposalId; logger.trace(`processing query-refund for proposal ${proposalId}`); - const download = await expectProposalDownload(ws, purchase); + const download = await expectProposalDownload(wex, purchase); const requestUrl = new URL( `orders/${download.contractData.orderId}`, @@ -2788,8 +2761,8 @@ async function processPurchaseQueryRefund( download.contractData.contractTermsHash, ); - const resp = await ws.http.fetch(requestUrl.href, { - cancellationToken, + const resp = await wex.http.fetch(requestUrl.href, { + cancellationToken: wex.cancellationToken, }); const orderStatus = await readSuccessResponseJsonOrThrow( resp, @@ -2802,7 +2775,7 @@ async function processPurchaseQueryRefund( }); if (!orderStatus.refund_pending) { - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["purchases"], async (tx) => { const p = await tx.purchases.get(purchase.proposalId); @@ -2821,7 +2794,7 @@ async function processPurchaseQueryRefund( return { oldTxState, newTxState }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return TaskRunResult.progress(); } else { const refundAwaiting = Amounts.sub( @@ -2829,7 +2802,7 @@ async function processPurchaseQueryRefund( Amounts.parseOrThrow(orderStatus.refund_taken), ).amount; - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["purchases"], async (tx) => { const p = await tx.purchases.get(purchase.proposalId); @@ -2848,17 +2821,16 @@ async function processPurchaseQueryRefund( return { oldTxState, newTxState }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return TaskRunResult.progress(); } } async function processPurchaseAcceptRefund( - ws: InternalWalletState, + wex: WalletExecutionContext, purchase: PurchaseRecord, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { - const download = await expectProposalDownload(ws, purchase); + const download = await expectProposalDownload(wex, purchase); const requestUrl = new URL( `orders/${download.contractData.orderId}/refund`, @@ -2867,12 +2839,12 @@ async function processPurchaseAcceptRefund( logger.trace(`making refund request to ${requestUrl.href}`); - const request = await ws.http.fetch(requestUrl.href, { + const request = await wex.http.fetch(requestUrl.href, { method: "POST", body: { h_contract: download.contractData.contractTermsHash, }, - cancellationToken, + cancellationToken: wex.cancellationToken, }); const refundResponse = await readSuccessResponseJsonOrThrow( @@ -2880,7 +2852,7 @@ async function processPurchaseAcceptRefund( codecForMerchantOrderRefundPickupResponse(), ); return await storeRefunds( - ws, + wex, purchase, refundResponse.refunds, RefundReason.AbortRefund, @@ -2888,7 +2860,7 @@ async function processPurchaseAcceptRefund( } export async function startRefundQueryForUri( - ws: InternalWalletState, + wex: WalletExecutionContext, talerUri: string, ): Promise<StartRefundQueryForUriResponse> { const parsedUri = parseTalerUri(talerUri); @@ -2898,7 +2870,7 @@ export async function startRefundQueryForUri( if (parsedUri.type !== TalerUriAction.Refund) { throw Error("expected taler://refund URI"); } - const purchaseRecord = await ws.db.runReadOnlyTx( + const purchaseRecord = await wex.db.runReadOnlyTx( ["purchases"], async (tx) => { return tx.purchases.indexes.byUrlAndOrderId.get([ @@ -2918,18 +2890,18 @@ export async function startRefundQueryForUri( tag: TransactionType.Payment, proposalId, }); - await startQueryRefund(ws, proposalId); + await startQueryRefund(wex, proposalId); return { transactionId, }; } export async function startQueryRefund( - ws: InternalWalletState, + wex: WalletExecutionContext, proposalId: string, ): Promise<void> { - const ctx = new PayMerchantTransactionContext(ws, proposalId); - const transitionInfo = await ws.db.runReadWriteTx( + const ctx = new PayMerchantTransactionContext(wex, proposalId); + const transitionInfo = await wex.db.runReadWriteTx( ["purchases"], async (tx) => { const p = await tx.purchases.get(proposalId); @@ -2947,12 +2919,12 @@ export async function startQueryRefund( return { oldTxState, newTxState }; }, ); - notifyTransition(ws, ctx.transactionId, transitionInfo); - ws.taskScheduler.startShepherdTask(ctx.taskId); + notifyTransition(wex, ctx.transactionId, transitionInfo); + wex.taskScheduler.startShepherdTask(ctx.taskId); } async function computeRefreshRequest( - ws: InternalWalletState, + wex: WalletExecutionContext, tx: WalletDbReadWriteTransaction<["coins", "denominations"]>, items: RefundItemRecord[], ): Promise<CoinRefreshRequest[]> { @@ -2963,7 +2935,7 @@ async function computeRefreshRequest( throw Error("coin not found"); } const denomInfo = await getDenomInfo( - ws, + wex, tx, coin.exchangeBaseUrl, coin.denomPubHash, @@ -3004,7 +2976,7 @@ function getItemStatus(rf: MerchantCoinRefundStatus): RefundItemStatus { * Store refunds, possibly creating a new refund group. */ async function storeRefunds( - ws: InternalWalletState, + wex: WalletExecutionContext, purchase: PurchaseRecord, refunds: MerchantCoinRefundStatus[], reason: RefundReason, @@ -3019,10 +2991,10 @@ async function storeRefunds( const newRefundGroupId = encodeCrock(randomBytes(32)); const now = TalerPreciseTimestamp.now(); - const download = await expectProposalDownload(ws, purchase); + const download = await expectProposalDownload(wex, purchase); const currency = Amounts.currencyOf(download.contractData.amount); - const result = await ws.db.runReadWriteTx( + const result = await wex.db.runReadWriteTx( [ "coins", "denominations", @@ -3116,12 +3088,12 @@ async function storeRefunds( if (newGroup) { const amountsRaw = newGroupRefunds.map((x) => x.refundAmount); const refreshCoins = await computeRefreshRequest( - ws, + wex, tx, newGroupRefunds, ); const outInfo = await calculateRefreshOutput( - ws, + wex, tx, currency, refreshCoins, @@ -3172,9 +3144,9 @@ async function storeRefunds( refundGroup.status = RefundGroupStatus.Failed; } await tx.refundGroups.put(refundGroup); - const refreshCoins = await computeRefreshRequest(ws, tx, items); + const refreshCoins = await computeRefreshRequest(wex, tx, items); await createRefreshGroup( - ws, + wex, tx, Amounts.currencyOf(download.contractData.amount), refreshCoins, @@ -3215,7 +3187,7 @@ async function storeRefunds( return TaskRunResult.finished(); } - notifyTransition(ws, transactionId, result.transitionInfo); + notifyTransition(wex, transactionId, result.transitionInfo); if (result.numPendingItemsTotal > 0) { return TaskRunResult.backoff(); diff --git a/packages/taler-wallet-core/src/pay-peer-common.ts b/packages/taler-wallet-core/src/pay-peer-common.ts index dbc3376b4..ff035d5e5 100644 --- a/packages/taler-wallet-core/src/pay-peer-common.ts +++ b/packages/taler-wallet-core/src/pay-peer-common.ts @@ -33,25 +33,25 @@ import type { SelectedPeerCoin } from "./coinSelection.js"; import { SpendCoinDetails } from "./crypto/cryptoImplementation.js"; import { PeerPushPaymentCoinSelection, ReserveRecord } from "./db.js"; import { getTotalRefreshCost } from "./refresh.js"; -import { InternalWalletState, getDenomInfo } from "./wallet.js"; +import { WalletExecutionContext, getDenomInfo } from "./wallet.js"; import { getCandidateWithdrawalDenomsTx } from "./withdraw.js"; /** * Get information about the coin selected for signatures. */ export async function queryCoinInfosForSelection( - ws: InternalWalletState, + wex: WalletExecutionContext, csel: PeerPushPaymentCoinSelection, ): Promise<SpendCoinDetails[]> { let infos: SpendCoinDetails[] = []; - await ws.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { + await wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { for (let i = 0; i < csel.coinPubs.length; i++) { const coin = await tx.coins.get(csel.coinPubs[i]); if (!coin) { throw Error("coin not found anymore"); } const denom = await getDenomInfo( - ws, + wex, tx, coin.exchangeBaseUrl, coin.denomPubHash, @@ -73,11 +73,11 @@ export async function queryCoinInfosForSelection( } export async function getTotalPeerPaymentCost( - ws: InternalWalletState, + wex: WalletExecutionContext, pcs: SelectedPeerCoin[], ): Promise<AmountJson> { const currency = Amounts.currencyOf(pcs[0].contribution); - return ws.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { + return wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { const costs: AmountJson[] = []; for (let i = 0; i < pcs.length; i++) { const coin = await tx.coins.get(pcs[i].coinPub); @@ -85,7 +85,7 @@ export async function getTotalPeerPaymentCost( throw Error("can't calculate payment cost, coin not found"); } const denomInfo = await getDenomInfo( - ws, + wex, tx, coin.exchangeBaseUrl, coin.denomPubHash, @@ -96,7 +96,7 @@ export async function getTotalPeerPaymentCost( ); } const allDenoms = await getCandidateWithdrawalDenomsTx( - ws, + wex, tx, coin.exchangeBaseUrl, currency, @@ -109,7 +109,7 @@ export async function getTotalPeerPaymentCost( allDenoms, denomInfo, amountLeft, - ws.config.testing.denomselAllowLate, + wex.ws.config.testing.denomselAllowLate, ); costs.push(Amounts.parseOrThrow(pcs[i].contribution)); costs.push(refreshCost); @@ -133,16 +133,16 @@ export const codecForExchangePurseStatus = (): Codec<ExchangePurseStatus> => .build("ExchangePurseStatus"); export async function getMergeReserveInfo( - ws: InternalWalletState, + wex: WalletExecutionContext, req: { exchangeBaseUrl: string; }, ): Promise<ReserveRecord> { // We have to eagerly create the key pair outside of the transaction, // due to the async crypto API. - const newReservePair = await ws.cryptoApi.createEddsaKeypair({}); + const newReservePair = await wex.cryptoApi.createEddsaKeypair({}); - const mergeReserveRecord: ReserveRecord = await ws.db.runReadWriteTx( + const mergeReserveRecord: ReserveRecord = await wex.db.runReadWriteTx( ["exchanges", "reserves"], async (tx) => { const ex = await tx.exchanges.get(req.exchangeBaseUrl); 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 7774dfd5f..c999a8d1f 100644 --- a/packages/taler-wallet-core/src/pay-peer-pull-credit.ts +++ b/packages/taler-wallet-core/src/pay-peer-pull-credit.ts @@ -17,7 +17,6 @@ import { AbsoluteTime, Amounts, - CancellationToken, CheckPeerPullCreditRequest, CheckPeerPullCreditResponse, ContractTermsUtil, @@ -81,7 +80,7 @@ import { constructTransactionIdentifier, notifyTransition, } from "./transactions.js"; -import { InternalWalletState } from "./wallet.js"; +import { WalletExecutionContext } from "./wallet.js"; import { getExchangeWithdrawalInfo, internalCreateWithdrawalGroup, @@ -94,7 +93,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { readonly taskId: TaskIdStr; constructor( - public ws: InternalWalletState, + public wex: WalletExecutionContext, public pursePub: string, ) { this.taskId = constructTaskIdentifier({ @@ -108,7 +107,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { } async deleteTransaction(): Promise<void> { - const { ws, pursePub } = this; + const { wex: ws, pursePub } = this; await ws.db.runReadWriteTx( ["withdrawalGroups", "peerPullCredit", "tombstones"], async (tx) => { @@ -138,7 +137,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { } async suspendTransaction(): Promise<void> { - const { ws, pursePub, taskId: retryTag, transactionId } = this; + const { wex: ws, pursePub, taskId: retryTag, transactionId } = this; const transitionInfo = await ws.db.runReadWriteTx( ["peerPullCredit"], async (tx) => { @@ -198,7 +197,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { } async failTransaction(): Promise<void> { - const { ws, pursePub, taskId: retryTag, transactionId } = this; + const { wex: ws, pursePub, taskId: retryTag, transactionId } = this; const transitionInfo = await ws.db.runReadWriteTx( ["peerPullCredit"], async (tx) => { @@ -249,7 +248,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { } async resumeTransaction(): Promise<void> { - const { ws, pursePub, taskId: retryTag, transactionId } = this; + const { wex: ws, pursePub, taskId: retryTag, transactionId } = this; const transitionInfo = await ws.db.runReadWriteTx( ["peerPullCredit"], async (tx) => { @@ -308,7 +307,7 @@ export class PeerPullCreditTransactionContext implements TransactionContext { } async abortTransaction(): Promise<void> { - const { ws, pursePub, taskId: retryTag, transactionId } = this; + const { wex: ws, pursePub, taskId: retryTag, transactionId } = this; const transitionInfo = await ws.db.runReadWriteTx( ["peerPullCredit"], async (tx) => { @@ -364,9 +363,8 @@ export class PeerPullCreditTransactionContext implements TransactionContext { } async function queryPurseForPeerPullCredit( - ws: InternalWalletState, + wex: WalletExecutionContext, pullIni: PeerPullCreditRecord, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const purseDepositUrl = new URL( `purses/${pullIni.pursePub}/deposit`, @@ -374,9 +372,9 @@ async function queryPurseForPeerPullCredit( ); purseDepositUrl.searchParams.set("timeout_ms", "30000"); logger.info(`querying purse status via ${purseDepositUrl.href}`); - const resp = await ws.http.fetch(purseDepositUrl.href, { + const resp = await wex.http.fetch(purseDepositUrl.href, { timeout: { d_ms: 60000 }, - cancellationToken, + cancellationToken: wex.cancellationToken, }); const transactionId = constructTransactionIdentifier({ tag: TransactionType.PeerPullCredit, @@ -388,7 +386,7 @@ async function queryPurseForPeerPullCredit( switch (resp.status) { case HttpStatusCode.Gone: { // Exchange says that purse doesn't exist anymore => expired! - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["peerPullCredit"], async (tx) => { const finPi = await tx.peerPullCredit.get(pullIni.pursePub); @@ -405,7 +403,7 @@ async function queryPurseForPeerPullCredit( return { oldTxState, newTxState }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return TaskRunResult.backoff(); } case HttpStatusCode.NotFound: @@ -427,7 +425,7 @@ async function queryPurseForPeerPullCredit( return TaskRunResult.backoff(); } - const reserve = await ws.db.runReadOnlyTx(["reserves"], async (tx) => { + const reserve = await wex.db.runReadOnlyTx(["reserves"], async (tx) => { return await tx.reserves.get(pullIni.mergeReserveRowId); }); @@ -435,7 +433,7 @@ async function queryPurseForPeerPullCredit( throw Error("reserve for peer pull credit not found in wallet DB"); } - await internalCreateWithdrawalGroup(ws, { + await internalCreateWithdrawalGroup(wex, { amount: Amounts.parseOrThrow(pullIni.amount), wgInfo: { withdrawalType: WithdrawalRecordType.PeerPullCredit, @@ -449,7 +447,7 @@ async function queryPurseForPeerPullCredit( pub: reserve.reservePub, }, }); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["peerPullCredit"], async (tx) => { const finPi = await tx.peerPullCredit.get(pullIni.pursePub); @@ -466,17 +464,16 @@ async function queryPurseForPeerPullCredit( return { oldTxState, newTxState }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return TaskRunResult.backoff(); } async function longpollKycStatus( - ws: InternalWalletState, + wex: WalletExecutionContext, pursePub: string, exchangeUrl: string, kycInfo: KycPendingInfo, userType: KycUserType, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const transactionId = constructTransactionIdentifier({ tag: TransactionType.PeerPullCredit, @@ -488,9 +485,9 @@ async function longpollKycStatus( ); url.searchParams.set("timeout_ms", "10000"); logger.info(`kyc url ${url.href}`); - const kycStatusRes = await ws.http.fetch(url.href, { + const kycStatusRes = await wex.http.fetch(url.href, { method: "GET", - cancellationToken, + cancellationToken: wex.cancellationToken, }); if ( kycStatusRes.status === HttpStatusCode.Ok || @@ -498,7 +495,7 @@ async function longpollKycStatus( // remove after the exchange is fixed or clarified kycStatusRes.status === HttpStatusCode.NoContent ) { - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["peerPullCredit"], async (tx) => { const peerIni = await tx.peerPullCredit.get(pursePub); @@ -517,7 +514,7 @@ async function longpollKycStatus( return { oldTxState, newTxState }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return TaskRunResult.progress(); } else if (kycStatusRes.status === HttpStatusCode.Accepted) { return TaskRunResult.longpollReturnedPending(); @@ -527,9 +524,8 @@ async function longpollKycStatus( } async function processPeerPullCreditAbortingDeletePurse( - ws: InternalWalletState, + wex: WalletExecutionContext, peerPullIni: PeerPullCreditRecord, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const { pursePub, pursePriv } = peerPullIni; const transactionId = constructTransactionIdentifier({ @@ -537,20 +533,20 @@ async function processPeerPullCreditAbortingDeletePurse( pursePub, }); - const sigResp = await ws.cryptoApi.signDeletePurse({ + const sigResp = await wex.cryptoApi.signDeletePurse({ pursePriv, }); const purseUrl = new URL(`purses/${pursePub}`, peerPullIni.exchangeBaseUrl); - const resp = await ws.http.fetch(purseUrl.href, { + const resp = await wex.http.fetch(purseUrl.href, { method: "DELETE", headers: { "taler-purse-signature": sigResp.sig, }, - cancellationToken, + cancellationToken: wex.cancellationToken, }); logger.info(`deleted purse with response status ${resp.status}`); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( [ "peerPullCredit", "refreshGroups", @@ -576,13 +572,13 @@ async function processPeerPullCreditAbortingDeletePurse( }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return TaskRunResult.backoff(); } async function handlePeerPullCreditWithdrawing( - ws: InternalWalletState, + wex: WalletExecutionContext, pullIni: PeerPullCreditRecord, ): Promise<TaskRunResult> { if (!pullIni.withdrawalGroupId) { @@ -594,7 +590,7 @@ async function handlePeerPullCreditWithdrawing( }); const wgId = pullIni.withdrawalGroupId; let finished: boolean = false; - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["peerPullCredit", "withdrawalGroups"], async (tx) => { const ppi = await tx.peerPullCredit.get(pullIni.pursePub); @@ -627,7 +623,7 @@ async function handlePeerPullCreditWithdrawing( }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); if (finished) { return TaskRunResult.finished(); } else { @@ -637,13 +633,12 @@ async function handlePeerPullCreditWithdrawing( } async function handlePeerPullCreditCreatePurse( - ws: InternalWalletState, + wex: WalletExecutionContext, pullIni: PeerPullCreditRecord, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const purseFee = Amounts.stringify(Amounts.zeroOfAmount(pullIni.amount)); const pursePub = pullIni.pursePub; - const mergeReserve = await ws.db.runReadOnlyTx(["reserves"], async (tx) => { + const mergeReserve = await wex.db.runReadOnlyTx(["reserves"], async (tx) => { return tx.reserves.get(pullIni.mergeReserveRowId); }); @@ -651,7 +646,7 @@ async function handlePeerPullCreditCreatePurse( throw Error("merge reserve for peer pull payment not found in database"); } - const contractTermsRecord = await ws.db.runReadOnlyTx( + const contractTermsRecord = await wex.db.runReadOnlyTx( ["contractTerms"], async (tx) => { return tx.contractTerms.get(pullIni.contractTermsHash); @@ -669,7 +664,7 @@ async function handlePeerPullCreditCreatePurse( mergeReserve.reservePub, ); - const econtractResp = await ws.cryptoApi.encryptContractForDeposit({ + const econtractResp = await wex.cryptoApi.encryptContractForDeposit({ contractPriv: pullIni.contractPriv, contractPub: pullIni.contractPub, contractTerms: contractTermsRecord.contractTermsRaw, @@ -681,7 +676,7 @@ async function handlePeerPullCreditCreatePurse( const mergeTimestamp = timestampPreciseFromDb(pullIni.mergeTimestamp); const purseExpiration = contractTerms.purse_expiration; - const sigRes = await ws.cryptoApi.signReservePurseCreate({ + const sigRes = await wex.cryptoApi.signReservePurseCreate({ contractTermsHash: pullIni.contractTermsHash, flags: WalletAccountMergeFlags.CreateWithPurseFee, mergePriv: pullIni.mergePriv, @@ -717,22 +712,17 @@ async function handlePeerPullCreditCreatePurse( pullIni.exchangeBaseUrl, ); - const httpResp = await ws.http.fetch(reservePurseMergeUrl.href, { + const httpResp = await wex.http.fetch(reservePurseMergeUrl.href, { method: "POST", body: reservePurseReqBody, - cancellationToken, + cancellationToken: wex.cancellationToken, }); if (httpResp.status === HttpStatusCode.UnavailableForLegalReasons) { const respJson = await httpResp.json(); const kycPending = codecForWalletKycUuid().decode(respJson); logger.info(`kyc uuid response: ${j2s(kycPending)}`); - return processPeerPullCreditKycRequired( - ws, - pullIni, - kycPending, - cancellationToken, - ); + return processPeerPullCreditKycRequired(wex, pullIni, kycPending); } const resp = await readSuccessResponseJsonOrThrow(httpResp, codecForAny()); @@ -744,7 +734,7 @@ async function handlePeerPullCreditCreatePurse( pursePub: pullIni.pursePub, }); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["peerPullCredit"], async (tx) => { const pi2 = await tx.peerPullCredit.get(pursePub); @@ -758,16 +748,15 @@ async function handlePeerPullCreditCreatePurse( return { oldTxState, newTxState }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return TaskRunResult.backoff(); } export async function processPeerPullCredit( - ws: InternalWalletState, + wex: WalletExecutionContext, pursePub: string, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { - const pullIni = await ws.db.runReadOnlyTx(["peerPullCredit"], async (tx) => { + const pullIni = await wex.db.runReadOnlyTx(["peerPullCredit"], async (tx) => { return tx.peerPullCredit.get(pursePub); }); if (!pullIni) { @@ -786,30 +775,25 @@ export async function processPeerPullCredit( return TaskRunResult.finished(); } case PeerPullPaymentCreditStatus.PendingReady: - return queryPurseForPeerPullCredit(ws, pullIni, cancellationToken); + return queryPurseForPeerPullCredit(wex, pullIni); case PeerPullPaymentCreditStatus.PendingMergeKycRequired: { if (!pullIni.kycInfo) { throw Error("invalid state, kycInfo required"); } return await longpollKycStatus( - ws, + wex, pursePub, pullIni.exchangeBaseUrl, pullIni.kycInfo, "individual", - cancellationToken, ); } case PeerPullPaymentCreditStatus.PendingCreatePurse: - return handlePeerPullCreditCreatePurse(ws, pullIni, cancellationToken); + return handlePeerPullCreditCreatePurse(wex, pullIni); case PeerPullPaymentCreditStatus.AbortingDeletePurse: - return await processPeerPullCreditAbortingDeletePurse( - ws, - pullIni, - cancellationToken, - ); + return await processPeerPullCreditAbortingDeletePurse(wex, pullIni); case PeerPullPaymentCreditStatus.PendingWithdrawing: - return handlePeerPullCreditWithdrawing(ws, pullIni); + return handlePeerPullCreditWithdrawing(wex, pullIni); case PeerPullPaymentCreditStatus.Aborted: case PeerPullPaymentCreditStatus.Failed: case PeerPullPaymentCreditStatus.Expired: @@ -827,10 +811,9 @@ export async function processPeerPullCredit( } async function processPeerPullCreditKycRequired( - ws: InternalWalletState, + wex: WalletExecutionContext, peerIni: PeerPullCreditRecord, kycPending: WalletKycUuid, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const transactionId = constructTransactionIdentifier({ tag: TransactionType.PeerPullCredit, @@ -845,9 +828,9 @@ async function processPeerPullCreditKycRequired( ); logger.info(`kyc url ${url.href}`); - const kycStatusRes = await ws.http.fetch(url.href, { + const kycStatusRes = await wex.http.fetch(url.href, { method: "GET", - cancellationToken, + cancellationToken: wex.cancellationToken, }); if ( @@ -861,7 +844,7 @@ async function processPeerPullCreditKycRequired( } else if (kycStatusRes.status === HttpStatusCode.Accepted) { const kycStatus = await kycStatusRes.json(); logger.info(`kyc status: ${j2s(kycStatus)}`); - const { transitionInfo, result } = await ws.db.runReadWriteTx( + const { transitionInfo, result } = await wex.db.runReadWriteTx( ["peerPullCredit"], async (tx) => { const peerInc = await tx.peerPullCredit.get(pursePub); @@ -897,7 +880,7 @@ async function processPeerPullCreditKycRequired( }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return TaskRunResult.backoff(); } else { throw Error(`unexpected response from kyc-check (${kycStatusRes.status})`); @@ -908,7 +891,7 @@ async function processPeerPullCreditKycRequired( * Check fees and available exchanges for a peer push payment initiation. */ export async function checkPeerPullPaymentInitiation( - ws: InternalWalletState, + wex: WalletExecutionContext, req: CheckPeerPullCreditRequest, ): Promise<CheckPeerPullCreditResponse> { // FIXME: We don't support exchanges with purse fees yet. @@ -922,7 +905,7 @@ export async function checkPeerPullPaymentInitiation( if (req.exchangeBaseUrl) { exchangeUrl = req.exchangeBaseUrl; } else { - exchangeUrl = await getPreferredExchangeForCurrency(ws, currency); + exchangeUrl = await getPreferredExchangeForCurrency(wex, currency); } if (!exchangeUrl) { @@ -932,7 +915,7 @@ export async function checkPeerPullPaymentInitiation( logger.trace(`found ${exchangeUrl} as preferred exchange`); const wi = await getExchangeWithdrawalInfo( - ws, + wex, exchangeUrl, Amounts.parseOrThrow(req.amount), undefined, @@ -957,12 +940,12 @@ export async function checkPeerPullPaymentInitiation( * Find a preferred exchange based on when we withdrew last from this exchange. */ async function getPreferredExchangeForCurrency( - ws: InternalWalletState, + wex: WalletExecutionContext, currency: string, ): Promise<string | undefined> { // Find an exchange with the matching currency. // Prefer exchanges with the most recent withdrawal. - const url = await ws.db.runReadOnlyTx(["exchanges"], async (tx) => { + const url = await wex.db.runReadOnlyTx(["exchanges"], async (tx) => { const exchanges = await tx.exchanges.iter().toArray(); let candidate = undefined; for (const e of exchanges) { @@ -1005,7 +988,7 @@ async function getPreferredExchangeForCurrency( * Initiate a peer pull payment. */ export async function initiatePeerPullPayment( - ws: InternalWalletState, + wex: WalletExecutionContext, req: InitiatePeerPullCreditRequest, ): Promise<InitiatePeerPullCreditResponse> { const currency = Amounts.currencyOf(req.partialContractTerms.amount); @@ -1013,7 +996,7 @@ export async function initiatePeerPullPayment( if (req.exchangeBaseUrl) { maybeExchangeBaseUrl = req.exchangeBaseUrl; } else { - maybeExchangeBaseUrl = await getPreferredExchangeForCurrency(ws, currency); + maybeExchangeBaseUrl = await getPreferredExchangeForCurrency(wex, currency); } if (!maybeExchangeBaseUrl) { @@ -1022,20 +1005,20 @@ export async function initiatePeerPullPayment( const exchangeBaseUrl = maybeExchangeBaseUrl; - await fetchFreshExchange(ws, exchangeBaseUrl); + await fetchFreshExchange(wex, exchangeBaseUrl); - const mergeReserveInfo = await getMergeReserveInfo(ws, { + const mergeReserveInfo = await getMergeReserveInfo(wex, { exchangeBaseUrl: exchangeBaseUrl, }); - const pursePair = await ws.cryptoApi.createEddsaKeypair({}); - const mergePair = await ws.cryptoApi.createEddsaKeypair({}); + const pursePair = await wex.cryptoApi.createEddsaKeypair({}); + const mergePair = await wex.cryptoApi.createEddsaKeypair({}); const contractTerms = req.partialContractTerms; const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms); - const contractKeyPair = await ws.cryptoApi.createEddsaKeypair({}); + const contractKeyPair = await wex.cryptoApi.createEddsaKeypair({}); const withdrawalGroupId = encodeCrock(getRandomBytes(32)); @@ -1045,7 +1028,7 @@ export async function initiatePeerPullPayment( const contractEncNonce = encodeCrock(getRandomBytes(24)); const wi = await getExchangeWithdrawalInfo( - ws, + wex, exchangeBaseUrl, Amounts.parseOrThrow(req.partialContractTerms.amount), undefined, @@ -1053,7 +1036,7 @@ export async function initiatePeerPullPayment( const mergeTimestamp = TalerPreciseTimestamp.now(); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["peerPullCredit", "contractTerms"], async (tx) => { const ppi: PeerPullCreditRecord = { @@ -1086,16 +1069,16 @@ export async function initiatePeerPullPayment( }, ); - const ctx = new PeerPullCreditTransactionContext(ws, pursePair.pub); + const ctx = new PeerPullCreditTransactionContext(wex, pursePair.pub); // The pending-incoming balance has changed. - ws.notify({ + wex.ws.notify({ type: NotificationType.BalanceChange, hintTransactionId: ctx.transactionId, }); - notifyTransition(ws, ctx.transactionId, transitionInfo); - ws.taskScheduler.startShepherdTask(ctx.taskId); + notifyTransition(wex, ctx.transactionId, transitionInfo); + wex.taskScheduler.startShepherdTask(ctx.taskId); return { talerUri: stringifyTalerUri({ 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 30bd1a2c8..828f68113 100644 --- a/packages/taler-wallet-core/src/pay-peer-pull-debit.ts +++ b/packages/taler-wallet-core/src/pay-peer-pull-debit.ts @@ -95,7 +95,7 @@ import { notifyTransition, parseTransactionIdentifier, } from "./transactions.js"; -import { InternalWalletState } from "./wallet.js"; +import { InternalWalletState, WalletExecutionContext } from "./wallet.js"; const logger = new Logger("pay-peer-pull-debit.ts"); @@ -103,13 +103,13 @@ const logger = new Logger("pay-peer-pull-debit.ts"); * Common context for a peer-pull-debit transaction. */ export class PeerPullDebitTransactionContext implements TransactionContext { - ws: InternalWalletState; + wex: WalletExecutionContext; readonly transactionId: TransactionIdStr; readonly taskId: TaskIdStr; peerPullDebitId: string; - constructor(ws: InternalWalletState, peerPullDebitId: string) { - this.ws = ws; + constructor(wex: WalletExecutionContext, peerPullDebitId: string) { + this.wex = wex; this.transactionId = constructTransactionIdentifier({ tag: TransactionType.PeerPullDebit, peerPullDebitId, @@ -123,7 +123,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { async deleteTransaction(): Promise<void> { const transactionId = this.transactionId; - const ws = this.ws; + const ws = this.wex; const peerPullDebitId = this.peerPullDebitId; await ws.db.runReadWriteTx(["peerPullDebit", "tombstones"], async (tx) => { const debit = await tx.peerPullDebit.get(peerPullDebitId); @@ -137,9 +137,9 @@ export class PeerPullDebitTransactionContext implements TransactionContext { async suspendTransaction(): Promise<void> { const taskId = this.taskId; const transactionId = this.transactionId; - const ws = this.ws; + const wex = this.wex; const peerPullDebitId = this.peerPullDebitId; - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["peerPullDebit"], async (tx) => { const pullDebitRec = await tx.peerPullDebit.get(peerPullDebitId); @@ -183,8 +183,8 @@ export class PeerPullDebitTransactionContext implements TransactionContext { return undefined; }, ); - notifyTransition(ws, transactionId, transitionInfo); - ws.taskScheduler.stopShepherdTask(taskId); + notifyTransition(wex, transactionId, transitionInfo); + wex.taskScheduler.stopShepherdTask(taskId); } async resumeTransaction(): Promise<void> { @@ -206,7 +206,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { return TransitionResult.Stay; } }); - this.ws.taskScheduler.startShepherdTask(this.taskId); + this.wex.taskScheduler.startShepherdTask(this.taskId); } async failTransaction(): Promise<void> { @@ -224,7 +224,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { return TransitionResult.Stay; } }); - this.ws.taskScheduler.stopShepherdTask(this.taskId); + this.wex.taskScheduler.stopShepherdTask(this.taskId); } async abortTransaction(): Promise<void> { @@ -262,7 +262,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { } const refresh = await createRefreshGroup( - ctx.ws, + ctx.wex, tx, currency, coinPubs, @@ -300,7 +300,7 @@ export class PeerPullDebitTransactionContext implements TransactionContext { >, ) => Promise<TransitionResult>, ): Promise<void> { - const ws = this.ws; + const ws = this.wex; const extraStores = opts.extraStores ?? []; const transitionInfo = await ws.db.runReadWriteTx( ["peerPullDebit", ...extraStores], @@ -336,7 +336,7 @@ async function handlePurseCreationConflict( peerPullInc: PeerPullPaymentIncomingRecord, resp: HttpResponse, ): Promise<TaskRunResult> { - const ws = ctx.ws; + const ws = ctx.wex; const errResp = await readTalerErrorResponse(resp); if (errResp.code !== TalerErrorCode.EXCHANGE_GENERIC_INSUFFICIENT_FUNDS) { await ctx.failTransaction(); @@ -411,9 +411,8 @@ async function handlePurseCreationConflict( } async function processPeerPullDebitPendingDeposit( - ws: InternalWalletState, + wex: WalletExecutionContext, peerPullInc: PeerPullPaymentIncomingRecord, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const pursePub = peerPullInc.pursePub; @@ -422,9 +421,9 @@ async function processPeerPullDebitPendingDeposit( throw Error("invalid state, no coins selected"); } - const coins = await queryCoinInfosForSelection(ws, coinSel); + const coins = await queryCoinInfosForSelection(wex, coinSel); - const depositSigsResp = await ws.cryptoApi.signPurseDeposits({ + const depositSigsResp = await wex.cryptoApi.signPurseDeposits({ exchangeBaseUrl: peerPullInc.exchangeBaseUrl, pursePub: peerPullInc.pursePub, coins, @@ -443,14 +442,14 @@ async function processPeerPullDebitPendingDeposit( logger.trace(`purse deposit payload: ${j2s(depositPayload)}`); } - const httpResp = await ws.http.fetch(purseDepositUrl.href, { + const httpResp = await wex.http.fetch(purseDepositUrl.href, { method: "POST", body: depositPayload, - cancellationToken, + cancellationToken: wex.cancellationToken, }); const ctx = new PeerPullDebitTransactionContext( - ws, + wex, peerPullInc.peerPullDebitId, ); @@ -489,9 +488,8 @@ async function processPeerPullDebitPendingDeposit( } async function processPeerPullDebitAbortingRefresh( - ws: InternalWalletState, + wex: WalletExecutionContext, peerPullInc: PeerPullPaymentIncomingRecord, - _cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const peerPullDebitId = peerPullInc.peerPullDebitId; const abortRefreshGroupId = peerPullInc.abortRefreshGroupId; @@ -500,7 +498,7 @@ async function processPeerPullDebitAbortingRefresh( tag: TransactionType.PeerPullDebit, peerPullDebitId, }); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["peerPullDebit", "refreshGroups"], async (tx) => { const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId); @@ -533,17 +531,16 @@ async function processPeerPullDebitAbortingRefresh( return undefined; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); // FIXME: Shouldn't this be finished in some cases?! return TaskRunResult.backoff(); } export async function processPeerPullDebit( - ws: InternalWalletState, + wex: WalletExecutionContext, peerPullDebitId: string, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { - const peerPullInc = await ws.db.runReadOnlyTx( + const peerPullInc = await wex.db.runReadOnlyTx( ["peerPullDebit"], async (tx) => { return tx.peerPullDebit.get(peerPullDebitId); @@ -556,22 +553,20 @@ export async function processPeerPullDebit( switch (peerPullInc.status) { case PeerPullDebitRecordStatus.PendingDeposit: return await processPeerPullDebitPendingDeposit( - ws, + wex, peerPullInc, - cancellationToken, ); case PeerPullDebitRecordStatus.AbortingRefresh: return await processPeerPullDebitAbortingRefresh( - ws, + wex, peerPullInc, - cancellationToken, ); } return TaskRunResult.finished(); } export async function confirmPeerPullDebit( - ws: InternalWalletState, + wex: WalletExecutionContext, req: ConfirmPeerPullDebitRequest, ): Promise<AcceptPeerPullPaymentResponse> { let peerPullDebitId: string; @@ -588,7 +583,7 @@ export async function confirmPeerPullDebit( throw Error("invalid request, transactionId or peerPullDebitId required"); } - const peerPullInc = await ws.db.runReadOnlyTx( + const peerPullInc = await wex.db.runReadOnlyTx( ["peerPullDebit"], async (tx) => { return tx.peerPullDebit.get(peerPullDebitId); @@ -603,7 +598,7 @@ export async function confirmPeerPullDebit( const instructedAmount = Amounts.parseOrThrow(peerPullInc.amount); - const coinSelRes = await selectPeerCoins(ws, { instructedAmount }); + const coinSelRes = await selectPeerCoins(wex, { instructedAmount }); if (logger.shouldLogTrace()) { logger.trace(`selected p2p coins (pull): ${j2s(coinSelRes)}`); } @@ -620,11 +615,11 @@ export async function confirmPeerPullDebit( const sel = coinSelRes.result; const totalAmount = await getTotalPeerPaymentCost( - ws, + wex, coinSelRes.result.coins, ); - await ws.db.runReadWriteTx( + await wex.db.runReadWriteTx( [ "exchanges", "coins", @@ -634,7 +629,7 @@ export async function confirmPeerPullDebit( "coinAvailability", ], async (tx) => { - await spendCoins(ws, tx, { + await spendCoins(wex, tx, { // allocationId: `txn:peer-pull-debit:${req.peerPullDebitId}`, allocationId: constructTransactionIdentifier({ tag: TransactionType.PeerPullDebit, @@ -663,16 +658,16 @@ export async function confirmPeerPullDebit( }, ); - const ctx = new PeerPullDebitTransactionContext(ws, peerPullDebitId); + const ctx = new PeerPullDebitTransactionContext(wex, peerPullDebitId); const transactionId = ctx.transactionId; - ws.notify({ + wex.ws.notify({ type: NotificationType.BalanceChange, hintTransactionId: transactionId, }); - ws.taskScheduler.startShepherdTask(ctx.taskId); + wex.taskScheduler.startShepherdTask(ctx.taskId); return { transactionId, @@ -684,7 +679,7 @@ export async function confirmPeerPullDebit( * Store the results in the wallet DB. */ export async function preparePeerPullDebit( - ws: InternalWalletState, + wex: WalletExecutionContext, req: PreparePeerPullDebitRequest, ): Promise<PreparePeerPullDebitResponse> { const uri = parsePayPullUri(req.talerUri); @@ -693,7 +688,7 @@ export async function preparePeerPullDebit( throw Error("got invalid taler://pay-pull URI"); } - const existing = await ws.db.runReadOnlyTx( + const existing = await wex.db.runReadOnlyTx( ["peerPullDebit", "contractTerms"], async (tx) => { const peerPullDebitRecord = @@ -734,7 +729,7 @@ export async function preparePeerPullDebit( const getContractUrl = new URL(`contracts/${contractPub}`, exchangeBaseUrl); - const contractHttpResp = await ws.http.fetch(getContractUrl.href); + const contractHttpResp = await wex.http.fetch(getContractUrl.href); const contractResp = await readSuccessResponseJsonOrThrow( contractHttpResp, @@ -743,7 +738,7 @@ export async function preparePeerPullDebit( const pursePub = contractResp.purse_pub; - const dec = await ws.cryptoApi.decryptContractForDeposit({ + const dec = await wex.cryptoApi.decryptContractForDeposit({ ciphertext: contractResp.econtract, contractPriv: contractPriv, pursePub: pursePub, @@ -751,7 +746,7 @@ export async function preparePeerPullDebit( const getPurseUrl = new URL(`purses/${pursePub}/merge`, exchangeBaseUrl); - const purseHttpResp = await ws.http.fetch(getPurseUrl.href); + const purseHttpResp = await wex.http.fetch(getPurseUrl.href); const purseStatus = await readSuccessResponseJsonOrThrow( purseHttpResp, @@ -777,7 +772,7 @@ export async function preparePeerPullDebit( const instructedAmount = Amounts.parseOrThrow(contractTerms.amount); - const coinSelRes = await selectPeerCoins(ws, { instructedAmount }); + const coinSelRes = await selectPeerCoins(wex, { instructedAmount }); if (logger.shouldLogTrace()) { logger.trace(`selected p2p coins (pull): ${j2s(coinSelRes)}`); } @@ -792,11 +787,11 @@ export async function preparePeerPullDebit( } const totalAmount = await getTotalPeerPaymentCost( - ws, + wex, coinSelRes.result.coins, ); - await ws.db.runReadWriteTx(["peerPullDebit", "contractTerms"], async (tx) => { + await wex.db.runReadWriteTx(["peerPullDebit", "contractTerms"], async (tx) => { await tx.contractTerms.put({ h: contractTermsHash, contractTermsRaw: contractTerms, 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 e629bffe4..772007bb6 100644 --- a/packages/taler-wallet-core/src/pay-peer-push-credit.ts +++ b/packages/taler-wallet-core/src/pay-peer-push-credit.ts @@ -83,7 +83,7 @@ import { notifyTransition, parseTransactionIdentifier, } from "./transactions.js"; -import { InternalWalletState } from "./wallet.js"; +import { InternalWalletState, WalletExecutionContext } from "./wallet.js"; import { PerformCreateWithdrawalGroupResult, getExchangeWithdrawalInfo, @@ -98,7 +98,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { readonly taskId: TaskIdStr; constructor( - public ws: InternalWalletState, + public wex: WalletExecutionContext, public peerPushCreditId: string, ) { this.transactionId = constructTransactionIdentifier({ @@ -112,8 +112,8 @@ export class PeerPushCreditTransactionContext implements TransactionContext { } async deleteTransaction(): Promise<void> { - const { ws, peerPushCreditId } = this; - await ws.db.runReadWriteTx( + const { wex, peerPushCreditId } = this; + await wex.db.runReadWriteTx( ["withdrawalGroups", "peerPushCredit", "tombstones"], async (tx) => { const pushInc = await tx.peerPushCredit.get(peerPushCreditId); @@ -141,8 +141,8 @@ export class PeerPushCreditTransactionContext implements TransactionContext { } async suspendTransaction(): Promise<void> { - const { ws, peerPushCreditId, taskId: retryTag, transactionId } = this; - const transitionInfo = await ws.db.runReadWriteTx( + const { wex, peerPushCreditId, taskId: retryTag, transactionId } = this; + const transitionInfo = await wex.db.runReadWriteTx( ["peerPushCredit"], async (tx) => { const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId); @@ -190,13 +190,13 @@ export class PeerPushCreditTransactionContext implements TransactionContext { return undefined; }, ); - notifyTransition(ws, transactionId, transitionInfo); - ws.taskScheduler.stopShepherdTask(retryTag); + notifyTransition(wex, transactionId, transitionInfo); + wex.taskScheduler.stopShepherdTask(retryTag); } async abortTransaction(): Promise<void> { - const { ws, peerPushCreditId, taskId: retryTag, transactionId } = this; - const transitionInfo = await ws.db.runReadWriteTx( + const { wex, peerPushCreditId, taskId: retryTag, transactionId } = this; + const transitionInfo = await wex.db.runReadWriteTx( ["peerPushCredit"], async (tx) => { const pushCreditRec = await tx.peerPushCredit.get(peerPushCreditId); @@ -247,12 +247,12 @@ export class PeerPushCreditTransactionContext implements TransactionContext { return undefined; }, ); - notifyTransition(ws, transactionId, transitionInfo); - ws.taskScheduler.startShepherdTask(retryTag); + notifyTransition(wex, transactionId, transitionInfo); + wex.taskScheduler.startShepherdTask(retryTag); } async resumeTransaction(): Promise<void> { - const { ws, peerPushCreditId, taskId: retryTag, transactionId } = this; + const { wex: ws, peerPushCreditId, taskId: retryTag, transactionId } = this; const transitionInfo = await ws.db.runReadWriteTx( ["peerPushCredit"], async (tx) => { @@ -305,7 +305,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { } async failTransaction(): Promise<void> { - const { ws, peerPushCreditId, taskId: retryTag, transactionId } = this; + const { wex: ws, peerPushCreditId, taskId: retryTag, transactionId } = this; const transitionInfo = await ws.db.runReadWriteTx( ["peerPushCredit"], async (tx) => { @@ -355,7 +355,7 @@ export class PeerPushCreditTransactionContext implements TransactionContext { } export async function preparePeerPushCredit( - ws: InternalWalletState, + wex: WalletExecutionContext, req: PreparePeerPushCreditRequest, ): Promise<PreparePeerPushCreditResponse> { const uri = parsePayPushUri(req.talerUri); @@ -364,7 +364,7 @@ export async function preparePeerPushCredit( throw Error("got invalid taler://pay-push URI"); } - const existing = await ws.db.runReadOnlyTx( + const existing = await wex.db.runReadOnlyTx( ["contractTerms", "peerPushCredit"], async (tx) => { const existingPushInc = @@ -407,14 +407,14 @@ export async function preparePeerPushCredit( const exchangeBaseUrl = uri.exchangeBaseUrl; - await fetchFreshExchange(ws, exchangeBaseUrl); + await fetchFreshExchange(wex, exchangeBaseUrl); const contractPriv = uri.contractPriv; const contractPub = encodeCrock(eddsaGetPublic(decodeCrock(contractPriv))); const getContractUrl = new URL(`contracts/${contractPub}`, exchangeBaseUrl); - const contractHttpResp = await ws.http.fetch(getContractUrl.href); + const contractHttpResp = await wex.http.fetch(getContractUrl.href); const contractResp = await readSuccessResponseJsonOrThrow( contractHttpResp, @@ -423,7 +423,7 @@ export async function preparePeerPushCredit( const pursePub = contractResp.purse_pub; - const dec = await ws.cryptoApi.decryptContractForMerge({ + const dec = await wex.cryptoApi.decryptContractForMerge({ ciphertext: contractResp.econtract, contractPriv: contractPriv, pursePub: pursePub, @@ -431,7 +431,7 @@ export async function preparePeerPushCredit( const getPurseUrl = new URL(`purses/${pursePub}/deposit`, exchangeBaseUrl); - const purseHttpResp = await ws.http.fetch(getPurseUrl.href); + const purseHttpResp = await wex.http.fetch(getPurseUrl.href); const contractTerms = codecForPeerContractTerms().decode(dec.contractTerms); @@ -453,13 +453,13 @@ export async function preparePeerPushCredit( const withdrawalGroupId = encodeCrock(getRandomBytes(32)); const wi = await getExchangeWithdrawalInfo( - ws, + wex, exchangeBaseUrl, Amounts.parseOrThrow(purseStatus.balance), undefined, ); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["contractTerms", "peerPushCredit"], async (tx) => { const rec: PeerPushPaymentIncomingRecord = { @@ -499,9 +499,9 @@ export async function preparePeerPushCredit( peerPushCreditId, }); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); - ws.notify({ + wex.ws.notify({ type: NotificationType.BalanceChange, hintTransactionId: transactionId, }); @@ -518,12 +518,11 @@ export async function preparePeerPushCredit( } async function longpollKycStatus( - ws: InternalWalletState, + wex: WalletExecutionContext, peerPushCreditId: string, exchangeUrl: string, kycInfo: KycPendingInfo, userType: KycUserType, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const transactionId = constructTransactionIdentifier({ tag: TransactionType.PeerPushCredit, @@ -535,9 +534,9 @@ async function longpollKycStatus( ); url.searchParams.set("timeout_ms", "30000"); logger.info(`kyc url ${url.href}`); - const kycStatusRes = await ws.http.fetch(url.href, { + const kycStatusRes = await wex.http.fetch(url.href, { method: "GET", - cancellationToken, + cancellationToken: wex.cancellationToken, }); if ( kycStatusRes.status === HttpStatusCode.Ok || @@ -545,7 +544,7 @@ async function longpollKycStatus( // remove after the exchange is fixed or clarified kycStatusRes.status === HttpStatusCode.NoContent ) { - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["peerPushCredit"], async (tx) => { const peerInc = await tx.peerPushCredit.get(peerPushCreditId); @@ -562,7 +561,7 @@ async function longpollKycStatus( return { oldTxState, newTxState }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return TaskRunResult.progress(); } else if (kycStatusRes.status === HttpStatusCode.Accepted) { // FIXME: Do we have to update the URL here? @@ -573,10 +572,9 @@ async function longpollKycStatus( } async function processPeerPushCreditKycRequired( - ws: InternalWalletState, + wex: WalletExecutionContext, peerInc: PeerPushPaymentIncomingRecord, kycPending: WalletKycUuid, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const transactionId = constructTransactionIdentifier({ tag: TransactionType.PeerPushCredit, @@ -591,9 +589,9 @@ async function processPeerPushCreditKycRequired( ); logger.info(`kyc url ${url.href}`); - const kycStatusRes = await ws.http.fetch(url.href, { + const kycStatusRes = await wex.http.fetch(url.href, { method: "GET", - cancellationToken, + cancellationToken: wex.cancellationToken, }); if ( @@ -607,7 +605,7 @@ async function processPeerPushCreditKycRequired( } else if (kycStatusRes.status === HttpStatusCode.Accepted) { const kycStatus = await kycStatusRes.json(); logger.info(`kyc status: ${j2s(kycStatus)}`); - const { transitionInfo, result } = await ws.db.runReadWriteTx( + const { transitionInfo, result } = await wex.db.runReadWriteTx( ["peerPushCredit"], async (tx) => { const peerInc = await tx.peerPushCredit.get(peerPushCreditId); @@ -643,7 +641,7 @@ async function processPeerPushCreditKycRequired( }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return result; } else { throw Error(`unexpected response from kyc-check (${kycStatusRes.status})`); @@ -651,10 +649,9 @@ async function processPeerPushCreditKycRequired( } async function handlePendingMerge( - ws: InternalWalletState, + wex: WalletExecutionContext, peerInc: PeerPushPaymentIncomingRecord, contractTerms: PeerContractTerms, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const { peerPushCreditId } = peerInc; const transactionId = constructTransactionIdentifier({ @@ -664,7 +661,7 @@ async function handlePendingMerge( const amount = Amounts.parseOrThrow(contractTerms.amount); - const mergeReserveInfo = await getMergeReserveInfo(ws, { + const mergeReserveInfo = await getMergeReserveInfo(wex, { exchangeBaseUrl: peerInc.exchangeBaseUrl, }); @@ -675,7 +672,7 @@ async function handlePendingMerge( mergeReserveInfo.reservePub, ); - const sigRes = await ws.cryptoApi.signPurseMerge({ + const sigRes = await wex.cryptoApi.signPurseMerge({ contractTermsHash: ContractTermsUtil.hashContractTerms(contractTerms), flags: WalletAccountMergeFlags.MergeFullyPaidPurse, mergePriv: peerInc.mergePriv, @@ -700,7 +697,7 @@ async function handlePendingMerge( reserve_sig: sigRes.accountSig, }; - const mergeHttpResp = await ws.http.fetch(mergePurseUrl.href, { + const mergeHttpResp = await wex.http.fetch(mergePurseUrl.href, { method: "POST", body: mergeReq, }); @@ -710,10 +707,9 @@ async function handlePendingMerge( const kycPending = codecForWalletKycUuid().decode(respJson); logger.info(`kyc uuid response: ${j2s(kycPending)}`); return processPeerPushCreditKycRequired( - ws, + wex, peerInc, kycPending, - cancellationToken, ); } @@ -724,7 +720,7 @@ async function handlePendingMerge( ); logger.trace(`merge response: ${j2s(res)}`); - const withdrawalGroupPrep = await internalPrepareCreateWithdrawalGroup(ws, { + const withdrawalGroupPrep = await internalPrepareCreateWithdrawalGroup(wex, { amount, wgInfo: { withdrawalType: WithdrawalRecordType.PeerPushCredit, @@ -738,7 +734,7 @@ async function handlePendingMerge( }, }); - const txRes = await ws.db.runReadWriteTx( + const txRes = await wex.db.runReadWriteTx( [ "contractTerms", "peerPushCredit", @@ -760,7 +756,7 @@ async function handlePendingMerge( case PeerPushCreditStatus.PendingMergeKycRequired: { peerInc.status = PeerPushCreditStatus.PendingWithdrawing; wgCreateRes = await internalPerformCreateWithdrawalGroup( - ws, + wex, tx, withdrawalGroupPrep, ); @@ -779,20 +775,20 @@ async function handlePendingMerge( ); // Transaction was committed, now we can emit notifications. if (txRes?.wgCreateRes?.exchangeNotif) { - ws.notify(txRes.wgCreateRes.exchangeNotif); + wex.ws.notify(txRes.wgCreateRes.exchangeNotif); } notifyTransition( - ws, + wex, withdrawalGroupPrep.transactionId, txRes?.wgCreateRes?.transitionInfo, ); - notifyTransition(ws, transactionId, txRes?.peerPushCreditTransition); + notifyTransition(wex, transactionId, txRes?.peerPushCreditTransition); return TaskRunResult.backoff(); } async function handlePendingWithdrawing( - ws: InternalWalletState, + wex: WalletExecutionContext, peerInc: PeerPushPaymentIncomingRecord, ): Promise<TaskRunResult> { if (!peerInc.withdrawalGroupId) { @@ -804,7 +800,7 @@ async function handlePendingWithdrawing( }); const wgId = peerInc.withdrawalGroupId; let finished: boolean = false; - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["peerPushCredit", "withdrawalGroups"], async (tx) => { const ppi = await tx.peerPushCredit.get(peerInc.peerPushCreditId); @@ -837,7 +833,7 @@ async function handlePendingWithdrawing( }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); if (finished) { return TaskRunResult.finished(); } else { @@ -847,13 +843,12 @@ async function handlePendingWithdrawing( } export async function processPeerPushCredit( - ws: InternalWalletState, + wex: WalletExecutionContext, peerPushCreditId: string, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { let peerInc: PeerPushPaymentIncomingRecord | undefined; let contractTerms: PeerContractTerms | undefined; - await ws.db.runReadWriteTx( + await wex.db.runReadWriteTx( ["contractTerms", "peerPushCredit"], async (tx) => { peerInc = await tx.peerPushCredit.get(peerPushCreditId); @@ -886,20 +881,19 @@ export async function processPeerPushCredit( throw Error("invalid state, kycInfo required"); } return await longpollKycStatus( - ws, + wex, peerPushCreditId, peerInc.exchangeBaseUrl, peerInc.kycInfo, "individual", - cancellationToken, ); } case PeerPushCreditStatus.PendingMerge: - return handlePendingMerge(ws, peerInc, contractTerms, cancellationToken); + return handlePendingMerge(wex, peerInc, contractTerms); case PeerPushCreditStatus.PendingWithdrawing: - return handlePendingWithdrawing(ws, peerInc); + return handlePendingWithdrawing(wex, peerInc); default: return TaskRunResult.finished(); @@ -907,7 +901,7 @@ export async function processPeerPushCredit( } export async function confirmPeerPushCredit( - ws: InternalWalletState, + wex: WalletExecutionContext, req: ConfirmPeerPushCreditRequest, ): Promise<AcceptPeerPushPaymentResponse> { let peerInc: PeerPushPaymentIncomingRecord | undefined; @@ -927,7 +921,7 @@ export async function confirmPeerPushCredit( throw Error("no transaction ID (or deprecated peerPushCreditId) provided"); } - await ws.db.runReadWriteTx( + await wex.db.runReadWriteTx( ["contractTerms", "peerPushCredit"], async (tx) => { peerInc = await tx.peerPushCredit.get(peerPushCreditId); @@ -947,9 +941,9 @@ export async function confirmPeerPushCredit( ); } - const ctx = new PeerPushCreditTransactionContext(ws, peerPushCreditId); + const ctx = new PeerPushCreditTransactionContext(wex, peerPushCreditId); - ws.taskScheduler.startShepherdTask(ctx.taskId); + wex.taskScheduler.startShepherdTask(ctx.taskId); const transactionId = constructTransactionIdentifier({ tag: TransactionType.PeerPushCredit, 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 40a5d97a4..5ee4d642b 100644 --- a/packages/taler-wallet-core/src/pay-peer-push-debit.ts +++ b/packages/taler-wallet-core/src/pay-peer-push-debit.ts @@ -16,7 +16,6 @@ import { Amounts, - CancellationToken, CheckPeerPushDebitRequest, CheckPeerPushDebitResponse, CoinRefreshRequest, @@ -78,7 +77,7 @@ import { constructTransactionIdentifier, notifyTransition, } from "./transactions.js"; -import { InternalWalletState } from "./wallet.js"; +import { WalletExecutionContext } from "./wallet.js"; const logger = new Logger("pay-peer-push-debit.ts"); @@ -87,7 +86,7 @@ export class PeerPushDebitTransactionContext implements TransactionContext { readonly taskId: TaskIdStr; constructor( - public ws: InternalWalletState, + public wex: WalletExecutionContext, public pursePub: string, ) { this.transactionId = constructTransactionIdentifier({ @@ -101,8 +100,8 @@ export class PeerPushDebitTransactionContext implements TransactionContext { } async deleteTransaction(): Promise<void> { - const { ws, pursePub, transactionId } = this; - await ws.db.runReadWriteTx(["peerPushDebit", "tombstones"], async (tx) => { + const { wex, pursePub, transactionId } = this; + await wex.db.runReadWriteTx(["peerPushDebit", "tombstones"], async (tx) => { const debit = await tx.peerPushDebit.get(pursePub); if (debit) { await tx.peerPushDebit.delete(pursePub); @@ -112,8 +111,8 @@ export class PeerPushDebitTransactionContext implements TransactionContext { } async suspendTransaction(): Promise<void> { - const { ws, pursePub, transactionId, taskId: retryTag } = this; - const transitionInfo = await ws.db.runReadWriteTx( + const { wex, pursePub, transactionId, taskId: retryTag } = this; + const transitionInfo = await wex.db.runReadWriteTx( ["peerPushDebit"], async (tx) => { const pushDebitRec = await tx.peerPushDebit.get(pursePub); @@ -165,13 +164,13 @@ export class PeerPushDebitTransactionContext implements TransactionContext { return undefined; }, ); - ws.taskScheduler.stopShepherdTask(retryTag); - notifyTransition(ws, transactionId, transitionInfo); + wex.taskScheduler.stopShepherdTask(retryTag); + notifyTransition(wex, transactionId, transitionInfo); } async abortTransaction(): Promise<void> { - const { ws, pursePub, transactionId, taskId: retryTag } = this; - const transitionInfo = await ws.db.runReadWriteTx( + const { wex, pursePub, transactionId, taskId: retryTag } = this; + const transitionInfo = await wex.db.runReadWriteTx( ["peerPushDebit"], async (tx) => { const pushDebitRec = await tx.peerPushDebit.get(pursePub); @@ -218,14 +217,14 @@ export class PeerPushDebitTransactionContext implements TransactionContext { return undefined; }, ); - ws.taskScheduler.stopShepherdTask(retryTag); - notifyTransition(ws, transactionId, transitionInfo); - ws.taskScheduler.startShepherdTask(retryTag); + wex.taskScheduler.stopShepherdTask(retryTag); + notifyTransition(wex, transactionId, transitionInfo); + wex.taskScheduler.startShepherdTask(retryTag); } async resumeTransaction(): Promise<void> { - const { ws, pursePub, transactionId, taskId: retryTag } = this; - const transitionInfo = await ws.db.runReadWriteTx( + const { wex, pursePub, transactionId, taskId: retryTag } = this; + const transitionInfo = await wex.db.runReadWriteTx( ["peerPushDebit"], async (tx) => { const pushDebitRec = await tx.peerPushDebit.get(pursePub); @@ -277,12 +276,12 @@ export class PeerPushDebitTransactionContext implements TransactionContext { return undefined; }, ); - ws.taskScheduler.startShepherdTask(retryTag); - notifyTransition(ws, transactionId, transitionInfo); + wex.taskScheduler.startShepherdTask(retryTag); + notifyTransition(wex, transactionId, transitionInfo); } async failTransaction(): Promise<void> { - const { ws, pursePub, transactionId, taskId: retryTag } = this; + const { wex: ws, pursePub, transactionId, taskId: retryTag } = this; const transitionInfo = await ws.db.runReadWriteTx( ["peerPushDebit"], async (tx) => { @@ -337,14 +336,14 @@ export class PeerPushDebitTransactionContext implements TransactionContext { } export async function checkPeerPushDebit( - ws: InternalWalletState, + wex: WalletExecutionContext, req: CheckPeerPushDebitRequest, ): Promise<CheckPeerPushDebitResponse> { const instructedAmount = Amounts.parseOrThrow(req.amount); logger.trace( `checking peer push debit for ${Amounts.stringify(instructedAmount)}`, ); - const coinSelRes = await selectPeerCoins(ws, { instructedAmount }); + const coinSelRes = await selectPeerCoins(wex, { instructedAmount }); if (coinSelRes.type === "failure") { throw TalerError.fromDetail( TalerErrorCode.WALLET_PEER_PUSH_PAYMENT_INSUFFICIENT_BALANCE, @@ -355,7 +354,7 @@ export async function checkPeerPushDebit( } logger.trace(`selected peer coins (len=${coinSelRes.result.coins.length})`); const totalAmount = await getTotalPeerPaymentCost( - ws, + wex, coinSelRes.result.coins, ); logger.trace("computed total peer payment cost"); @@ -368,13 +367,13 @@ export async function checkPeerPushDebit( } async function handlePurseCreationConflict( - ws: InternalWalletState, + wex: WalletExecutionContext, peerPushInitiation: PeerPushDebitRecord, resp: HttpResponse, ): Promise<TaskRunResult> { const pursePub = peerPushInitiation.pursePub; const errResp = await readTalerErrorResponse(resp); - const ctx = new PeerPushDebitTransactionContext(ws, pursePub); + const ctx = new PeerPushDebitTransactionContext(wex, pursePub); if (errResp.code !== TalerErrorCode.EXCHANGE_GENERIC_INSUFFICIENT_FUNDS) { await ctx.failTransaction(); return TaskRunResult.finished(); @@ -405,7 +404,7 @@ async function handlePurseCreationConflict( } } - const coinSelRes = await selectPeerCoins(ws, { instructedAmount, repair }); + const coinSelRes = await selectPeerCoins(wex, { instructedAmount, repair }); if (coinSelRes.type == "failure") { // FIXME: Details! @@ -414,7 +413,7 @@ async function handlePurseCreationConflict( ); } - await ws.db.runReadWriteTx(["peerPushDebit"], async (tx) => { + await wex.db.runReadWriteTx(["peerPushDebit"], async (tx) => { const myPpi = await tx.peerPushDebit.get(peerPushInitiation.pursePub); if (!myPpi) { return; @@ -438,19 +437,18 @@ async function handlePurseCreationConflict( } async function processPeerPushDebitCreateReserve( - ws: InternalWalletState, + wex: WalletExecutionContext, peerPushInitiation: PeerPushDebitRecord, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const pursePub = peerPushInitiation.pursePub; const purseExpiration = peerPushInitiation.purseExpiration; const hContractTerms = peerPushInitiation.contractTermsHash; - const ctx = new PeerPushDebitTransactionContext(ws, pursePub); + const ctx = new PeerPushDebitTransactionContext(wex, pursePub); const transactionId = ctx.transactionId; logger.trace(`processing ${transactionId} pending(create-reserve)`); - const contractTermsRecord = await ws.db.runReadOnlyTx( + const contractTermsRecord = await wex.db.runReadOnlyTx( ["contractTerms"], async (tx) => { return tx.contractTerms.get(hContractTerms); @@ -463,7 +461,7 @@ async function processPeerPushDebitCreateReserve( ); } - const purseSigResp = await ws.cryptoApi.signPurseCreation({ + const purseSigResp = await wex.cryptoApi.signPurseCreation({ hContractTerms, mergePub: peerPushInitiation.mergePub, minAge: 0, @@ -473,11 +471,11 @@ async function processPeerPushDebitCreateReserve( }); const coins = await queryCoinInfosForSelection( - ws, + wex, peerPushInitiation.coinSel, ); - const depositSigsResp = await ws.cryptoApi.signPurseDeposits({ + const depositSigsResp = await wex.cryptoApi.signPurseDeposits({ exchangeBaseUrl: peerPushInitiation.exchangeBaseUrl, pursePub: peerPushInitiation.pursePub, coins, @@ -495,7 +493,7 @@ async function processPeerPushDebitCreateReserve( logger.trace(`encrypt contract request: ${j2s(encryptContractRequest)}`); - const econtractResp = await ws.cryptoApi.encryptContractForMerge( + const econtractResp = await wex.cryptoApi.encryptContractForMerge( encryptContractRequest, ); @@ -517,10 +515,10 @@ async function processPeerPushDebitCreateReserve( logger.trace(`request body: ${j2s(reqBody)}`); - const httpResp = await ws.http.fetch(createPurseUrl.href, { + const httpResp = await wex.http.fetch(createPurseUrl.href, { method: "POST", body: reqBody, - cancellationToken, + cancellationToken: wex.cancellationToken, }); { @@ -538,7 +536,7 @@ async function processPeerPushDebitCreateReserve( } case HttpStatusCode.Conflict: { // Handle double-spending - return handlePurseCreationConflict(ws, peerPushInitiation, httpResp); + return handlePurseCreationConflict(wex, peerPushInitiation, httpResp); } default: { const errResp = await readTalerErrorResponse(httpResp); @@ -554,7 +552,7 @@ async function processPeerPushDebitCreateReserve( throw Error("got error response from exchange"); } - await transitionPeerPushDebitTransaction(ws, pursePub, { + await transitionPeerPushDebitTransaction(wex, pursePub, { stFrom: PeerPushDebitStatus.PendingCreatePurse, stTo: PeerPushDebitStatus.PendingReady, }); @@ -563,9 +561,8 @@ async function processPeerPushDebitCreateReserve( } async function processPeerPushDebitAbortingDeletePurse( - ws: InternalWalletState, + wex: WalletExecutionContext, peerPushInitiation: PeerPushDebitRecord, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const { pursePub, pursePriv } = peerPushInitiation; const transactionId = constructTransactionIdentifier({ @@ -573,23 +570,23 @@ async function processPeerPushDebitAbortingDeletePurse( pursePub, }); - const sigResp = await ws.cryptoApi.signDeletePurse({ + const sigResp = await wex.cryptoApi.signDeletePurse({ pursePriv, }); const purseUrl = new URL( `purses/${pursePub}`, peerPushInitiation.exchangeBaseUrl, ); - const resp = await ws.http.fetch(purseUrl.href, { + const resp = await wex.http.fetch(purseUrl.href, { method: "DELETE", headers: { "taler-purse-signature": sigResp.sig, }, - cancellationToken, + cancellationToken: wex.cancellationToken, }); logger.info(`deleted purse with response status ${resp.status}`); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( [ "peerPushDebit", "refreshGroups", @@ -617,7 +614,7 @@ async function processPeerPushDebitAbortingDeletePurse( } const refresh = await createRefreshGroup( - ws, + wex, tx, currency, coinPubs, @@ -634,7 +631,7 @@ async function processPeerPushDebitAbortingDeletePurse( }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return TaskRunResult.backoff(); } @@ -645,7 +642,7 @@ interface SimpleTransition { } async function transitionPeerPushDebitTransaction( - ws: InternalWalletState, + wex: WalletExecutionContext, pursePub: string, transitionSpec: SimpleTransition, ): Promise<void> { @@ -653,7 +650,7 @@ async function transitionPeerPushDebitTransaction( tag: TransactionType.PeerPushDebit, pursePub, }); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["peerPushDebit"], async (tx) => { const ppiRec = await tx.peerPushDebit.get(pursePub); @@ -673,11 +670,11 @@ async function transitionPeerPushDebitTransaction( }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); } async function processPeerPushDebitAbortingRefreshDeleted( - ws: InternalWalletState, + wex: WalletExecutionContext, peerPushInitiation: PeerPushDebitRecord, ): Promise<TaskRunResult> { const pursePub = peerPushInitiation.pursePub; @@ -687,7 +684,7 @@ async function processPeerPushDebitAbortingRefreshDeleted( tag: TransactionType.PeerPushDebit, pursePub: peerPushInitiation.pursePub, }); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["refreshGroups", "peerPushDebit"], async (tx) => { const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId); @@ -720,13 +717,13 @@ async function processPeerPushDebitAbortingRefreshDeleted( return undefined; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); // FIXME: Shouldn't this be finished in some cases?! return TaskRunResult.backoff(); } async function processPeerPushDebitAbortingRefreshExpired( - ws: InternalWalletState, + wex: WalletExecutionContext, peerPushInitiation: PeerPushDebitRecord, ): Promise<TaskRunResult> { const pursePub = peerPushInitiation.pursePub; @@ -736,7 +733,7 @@ async function processPeerPushDebitAbortingRefreshExpired( tag: TransactionType.PeerPushDebit, pursePub: peerPushInitiation.pursePub, }); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["peerPushDebit", "refreshGroups"], async (tx) => { const refreshGroup = await tx.refreshGroups.get(abortRefreshGroupId); @@ -769,7 +766,7 @@ async function processPeerPushDebitAbortingRefreshExpired( return undefined; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); // FIXME: Shouldn't this be finished in some cases?! return TaskRunResult.backoff(); } @@ -778,9 +775,8 @@ async function processPeerPushDebitAbortingRefreshExpired( * Process the "pending(ready)" state of a peer-push-debit transaction. */ async function processPeerPushDebitReady( - ws: InternalWalletState, + wex: WalletExecutionContext, peerPushInitiation: PeerPushDebitRecord, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { logger.trace("processing peer-push-debit pending(ready)"); const pursePub = peerPushInitiation.pursePub; @@ -794,9 +790,9 @@ async function processPeerPushDebitReady( ); mergeUrl.searchParams.set("timeout_ms", "30000"); logger.info(`long-polling on purse status at ${mergeUrl.href}`); - const resp = await ws.http.fetch(mergeUrl.href, { + const resp = await wex.http.fetch(mergeUrl.href, { // timeout: getReserveRequestTimeout(withdrawalGroup), - cancellationToken, + cancellationToken: wex.cancellationToken, }); if (resp.status === HttpStatusCode.Ok) { const purseStatus = await readSuccessResponseJsonOrThrow( @@ -809,7 +805,7 @@ async function processPeerPushDebitReady( return TaskRunResult.backoff(); } else { await transitionPeerPushDebitTransaction( - ws, + wex, peerPushInitiation.pursePub, { stFrom: PeerPushDebitStatus.PendingReady, @@ -820,7 +816,7 @@ async function processPeerPushDebitReady( } } else if (resp.status === HttpStatusCode.Gone) { logger.info(`purse ${pursePub} is gone, aborting peer-push-debit`); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( [ "peerPushDebit", "refreshGroups", @@ -848,7 +844,7 @@ async function processPeerPushDebitReady( } const refresh = await createRefreshGroup( - ws, + wex, tx, currency, coinPubs, @@ -865,7 +861,7 @@ async function processPeerPushDebitReady( }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return TaskRunResult.backoff(); } else { logger.warn(`unexpected HTTP status for purse: ${resp.status}`); @@ -874,11 +870,10 @@ async function processPeerPushDebitReady( } export async function processPeerPushDebit( - ws: InternalWalletState, + wex: WalletExecutionContext, pursePub: string, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { - const peerPushInitiation = await ws.db.runReadOnlyTx( + const peerPushInitiation = await wex.db.runReadOnlyTx( ["peerPushDebit"], async (tx) => { return tx.peerPushDebit.get(pursePub); @@ -890,27 +885,21 @@ export async function processPeerPushDebit( switch (peerPushInitiation.status) { case PeerPushDebitStatus.PendingCreatePurse: - return processPeerPushDebitCreateReserve( - ws, - peerPushInitiation, - cancellationToken, - ); + return processPeerPushDebitCreateReserve(wex, peerPushInitiation); case PeerPushDebitStatus.PendingReady: - return processPeerPushDebitReady( - ws, - peerPushInitiation, - cancellationToken, - ); + return processPeerPushDebitReady(wex, peerPushInitiation); case PeerPushDebitStatus.AbortingDeletePurse: - return processPeerPushDebitAbortingDeletePurse( - ws, + return processPeerPushDebitAbortingDeletePurse(wex, peerPushInitiation); + case PeerPushDebitStatus.AbortingRefreshDeleted: + return processPeerPushDebitAbortingRefreshDeleted( + wex, peerPushInitiation, - cancellationToken, ); - case PeerPushDebitStatus.AbortingRefreshDeleted: - return processPeerPushDebitAbortingRefreshDeleted(ws, peerPushInitiation); case PeerPushDebitStatus.AbortingRefreshExpired: - return processPeerPushDebitAbortingRefreshExpired(ws, peerPushInitiation); + return processPeerPushDebitAbortingRefreshExpired( + wex, + peerPushInitiation, + ); default: { const txState = computePeerPushDebitTransactionState(peerPushInitiation); logger.warn( @@ -926,7 +915,7 @@ export async function processPeerPushDebit( * Initiate sending a peer-to-peer push payment. */ export async function initiatePeerPushDebit( - ws: InternalWalletState, + wex: WalletExecutionContext, req: InitiatePeerPushDebitRequest, ): Promise<InitiatePeerPushDebitResponse> { const instructedAmount = Amounts.parseOrThrow( @@ -935,14 +924,14 @@ export async function initiatePeerPushDebit( const purseExpiration = req.partialContractTerms.purse_expiration; const contractTerms = req.partialContractTerms; - const pursePair = await ws.cryptoApi.createEddsaKeypair({}); - const mergePair = await ws.cryptoApi.createEddsaKeypair({}); + const pursePair = await wex.cryptoApi.createEddsaKeypair({}); + const mergePair = await wex.cryptoApi.createEddsaKeypair({}); const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms); - const contractKeyPair = await ws.cryptoApi.createEddsaKeypair({}); + const contractKeyPair = await wex.cryptoApi.createEddsaKeypair({}); - const coinSelRes = await selectPeerCoins(ws, { instructedAmount }); + const coinSelRes = await selectPeerCoins(wex, { instructedAmount }); if (coinSelRes.type !== "success") { throw TalerError.fromDetail( @@ -959,7 +948,7 @@ export async function initiatePeerPushDebit( logger.trace(`${j2s(coinSelRes)}`); const totalAmount = await getTotalPeerPaymentCost( - ws, + wex, coinSelRes.result.coins, ); @@ -967,13 +956,13 @@ export async function initiatePeerPushDebit( const pursePub = pursePair.pub; - const ctx = new PeerPushDebitTransactionContext(ws, pursePub); + const ctx = new PeerPushDebitTransactionContext(wex, pursePub); const transactionId = ctx.transactionId; const contractEncNonce = encodeCrock(getRandomBytes(24)); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( [ "exchanges", "contractTerms", @@ -987,7 +976,7 @@ export async function initiatePeerPushDebit( // FIXME: Instead of directly doing a spendCoin here, // we might want to mark the coins as used and spend them // after we've been able to create the purse. - await spendCoins(ws, tx, { + await spendCoins(wex, tx, { allocationId: constructTransactionIdentifier({ tag: TransactionType.PeerPushDebit, pursePub: pursePair.pub, @@ -1034,13 +1023,13 @@ export async function initiatePeerPushDebit( }; }, ); - notifyTransition(ws, transactionId, transitionInfo); - ws.notify({ + notifyTransition(wex, transactionId, transitionInfo); + wex.ws.notify({ type: NotificationType.BalanceChange, hintTransactionId: transactionId, }); - ws.taskScheduler.startShepherdTask(ctx.taskId); + wex.taskScheduler.startShepherdTask(ctx.taskId); return { contractPriv: contractKeyPair.priv, diff --git a/packages/taler-wallet-core/src/recoup.ts b/packages/taler-wallet-core/src/recoup.ts index 3ef494c3a..8d5d3dd1f 100644 --- a/packages/taler-wallet-core/src/recoup.ts +++ b/packages/taler-wallet-core/src/recoup.ts @@ -63,7 +63,11 @@ import { } from "./db.js"; import { createRefreshGroup } from "./refresh.js"; import { constructTransactionIdentifier } from "./transactions.js"; -import { getDenomInfo, type InternalWalletState } from "./wallet.js"; +import { + WalletExecutionContext, + getDenomInfo, + type InternalWalletState, +} from "./wallet.js"; import { internalCreateWithdrawalGroup } from "./withdraw.js"; export const logger = new Logger("operations/recoup.ts"); @@ -73,7 +77,7 @@ export const logger = new Logger("operations/recoup.ts"); * a coin in the group as finished. */ export async function putGroupAsFinished( - ws: InternalWalletState, + wex: WalletExecutionContext, tx: WalletDbReadWriteTransaction< ["recoupGroups", "denominations", "refreshGroups", "coins"] >, @@ -91,7 +95,7 @@ export async function putGroupAsFinished( } async function recoupRewardCoin( - ws: InternalWalletState, + wex: WalletExecutionContext, recoupGroupId: string, coinIdx: number, coin: CoinRecord, @@ -99,7 +103,7 @@ async function recoupRewardCoin( // We can't really recoup a coin we got via tipping. // Thus we just put the coin to sleep. // FIXME: somehow report this to the user - await ws.db.runReadWriteTx( + await wex.db.runReadWriteTx( ["recoupGroups", "denominations", "refreshGroups", "coins"], async (tx) => { const recoupGroup = await tx.recoupGroups.get(recoupGroupId); @@ -109,23 +113,23 @@ async function recoupRewardCoin( if (recoupGroup.recoupFinishedPerCoin[coinIdx]) { return; } - await putGroupAsFinished(ws, tx, recoupGroup, coinIdx); + await putGroupAsFinished(wex, tx, recoupGroup, coinIdx); }, ); } async function recoupRefreshCoin( - ws: InternalWalletState, + wex: WalletExecutionContext, recoupGroupId: string, coinIdx: number, coin: CoinRecord, cs: RefreshCoinSource, ): Promise<void> { - const d = await ws.db.runReadOnlyTx( + const d = await wex.db.runReadOnlyTx( ["coins", "denominations"], async (tx) => { const denomInfo = await getDenomInfo( - ws, + wex, tx, coin.exchangeBaseUrl, coin.denomPubHash, @@ -141,7 +145,7 @@ async function recoupRefreshCoin( return; } - const recoupRequest = await ws.cryptoApi.createRecoupRefreshRequest({ + const recoupRequest = await wex.cryptoApi.createRecoupRefreshRequest({ blindingKey: coin.blindingKey, coinPriv: coin.coinPriv, coinPub: coin.coinPub, @@ -155,7 +159,7 @@ async function recoupRefreshCoin( ); logger.trace(`making recoup request for ${coin.coinPub}`); - const resp = await ws.http.fetch(reqUrl.href, { + const resp = await wex.http.fetch(reqUrl.href, { method: "POST", body: recoupRequest, }); @@ -168,7 +172,7 @@ async function recoupRefreshCoin( throw Error(`Coin's oldCoinPub doesn't match reserve on recoup`); } - await ws.db.runReadWriteTx( + await wex.db.runReadWriteTx( ["coins", "denominations", "recoupGroups", "refreshGroups"], async (tx) => { const recoupGroup = await tx.recoupGroups.get(recoupGroupId); @@ -189,13 +193,13 @@ async function recoupRefreshCoin( return; } const oldCoinDenom = await getDenomInfo( - ws, + wex, tx, oldCoin.exchangeBaseUrl, oldCoin.denomPubHash, ); const revokedCoinDenom = await getDenomInfo( - ws, + wex, tx, revokedCoin.exchangeBaseUrl, revokedCoin.denomPubHash, @@ -220,22 +224,22 @@ async function recoupRefreshCoin( } await tx.coins.put(revokedCoin); await tx.coins.put(oldCoin); - await putGroupAsFinished(ws, tx, recoupGroup, coinIdx); + await putGroupAsFinished(wex, tx, recoupGroup, coinIdx); }, ); } export async function recoupWithdrawCoin( - ws: InternalWalletState, + wex: WalletExecutionContext, recoupGroupId: string, coinIdx: number, coin: CoinRecord, cs: WithdrawCoinSource, ): Promise<void> { const reservePub = cs.reservePub; - const denomInfo = await ws.db.runReadOnlyTx(["denominations"], async (tx) => { + const denomInfo = await wex.db.runReadOnlyTx(["denominations"], async (tx) => { const denomInfo = await getDenomInfo( - ws, + wex, tx, coin.exchangeBaseUrl, coin.denomPubHash, @@ -247,7 +251,7 @@ export async function recoupWithdrawCoin( return; } - const recoupRequest = await ws.cryptoApi.createRecoupRequest({ + const recoupRequest = await wex.cryptoApi.createRecoupRequest({ blindingKey: coin.blindingKey, coinPriv: coin.coinPriv, coinPub: coin.coinPub, @@ -257,7 +261,7 @@ export async function recoupWithdrawCoin( }); const reqUrl = new URL(`/coins/${coin.coinPub}/recoup`, coin.exchangeBaseUrl); logger.trace(`requesting recoup via ${reqUrl.href}`); - const resp = await ws.http.fetch(reqUrl.href, { + const resp = await wex.http.fetch(reqUrl.href, { method: "POST", body: recoupRequest, }); @@ -273,7 +277,7 @@ export async function recoupWithdrawCoin( } // FIXME: verify that our expectations about the amount match - await ws.db.runReadWriteTx( + await wex.db.runReadWriteTx( ["coins", "denominations", "recoupGroups", "refreshGroups"], async (tx) => { const recoupGroup = await tx.recoupGroups.get(recoupGroupId); @@ -289,17 +293,16 @@ export async function recoupWithdrawCoin( } updatedCoin.status = CoinStatus.Dormant; await tx.coins.put(updatedCoin); - await putGroupAsFinished(ws, tx, recoupGroup, coinIdx); + await putGroupAsFinished(wex, tx, recoupGroup, coinIdx); }, ); } export async function processRecoupGroup( - ws: InternalWalletState, + wex: WalletExecutionContext, recoupGroupId: string, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { - let recoupGroup = await ws.db.runReadOnlyTx(["recoupGroups"], async (tx) => { + let recoupGroup = await wex.db.runReadOnlyTx(["recoupGroups"], async (tx) => { return tx.recoupGroups.get(recoupGroupId); }); if (!recoupGroup) { @@ -311,7 +314,7 @@ export async function processRecoupGroup( } const ps = recoupGroup.coinPubs.map(async (x, i) => { try { - await processRecoupForCoin(ws, recoupGroupId, i); + await processRecoupForCoin(wex, recoupGroupId, i); } catch (e) { logger.warn(`processRecoup failed: ${e}`); throw e; @@ -319,7 +322,7 @@ export async function processRecoupGroup( }); await Promise.all(ps); - recoupGroup = await ws.db.runReadOnlyTx(["recoupGroups"], async (tx) => { + recoupGroup = await wex.db.runReadOnlyTx(["recoupGroups"], async (tx) => { return tx.recoupGroups.get(recoupGroupId); }); if (!recoupGroup) { @@ -338,7 +341,7 @@ export async function processRecoupGroup( const reservePrivMap: Record<string, string> = {}; for (let i = 0; i < recoupGroup.coinPubs.length; i++) { const coinPub = recoupGroup.coinPubs[i]; - await ws.db.runReadOnlyTx(["coins", "reserves"], async (tx) => { + await wex.db.runReadOnlyTx(["coins", "reserves"], async (tx) => { const coin = await tx.coins.get(coinPub); if (!coin) { throw Error(`Coin ${coinPub} not found, can't request recoup`); @@ -363,13 +366,13 @@ export async function processRecoupGroup( ); logger.info(`querying reserve status for recoup via ${reserveUrl}`); - const resp = await ws.http.fetch(reserveUrl.href); + const resp = await wex.http.fetch(reserveUrl.href); const result = await readSuccessResponseJsonOrThrow( resp, codecForReserveStatus(), ); - await internalCreateWithdrawalGroup(ws, { + await internalCreateWithdrawalGroup(wex, { amount: Amounts.parseOrThrow(result.balance), exchangeBaseUrl: recoupGroup.exchangeBaseUrl, reserveStatus: WithdrawalGroupStatus.PendingQueryingStatus, @@ -383,7 +386,7 @@ export async function processRecoupGroup( }); } - await ws.db.runReadWriteTx( + await wex.db.runReadWriteTx( [ "recoupGroups", "coinAvailability", @@ -400,7 +403,7 @@ export async function processRecoupGroup( rg2.operationStatus = RecoupOperationStatus.Finished; if (rg2.scheduleRefreshCoins.length > 0) { await createRefreshGroup( - ws, + wex, tx, Amounts.currencyOf(rg2.scheduleRefreshCoins[0].amount), rg2.scheduleRefreshCoins, @@ -452,7 +455,7 @@ export class RewardTransactionContext implements TransactionContext { } export async function createRecoupGroup( - ws: InternalWalletState, + wex: WalletExecutionContext, tx: WalletDbReadWriteTransaction< ["recoupGroups", "denominations", "refreshGroups", "coins"] >, @@ -476,7 +479,7 @@ export async function createRecoupGroup( const coinPub = coinPubs[coinIdx]; const coin = await tx.coins.get(coinPub); if (!coin) { - await putGroupAsFinished(ws, tx, recoupGroup, coinIdx); + await putGroupAsFinished(wex, tx, recoupGroup, coinIdx); continue; } await tx.coins.put(coin); @@ -491,11 +494,11 @@ export async function createRecoupGroup( * Run the recoup protocol for a single coin in a recoup group. */ async function processRecoupForCoin( - ws: InternalWalletState, + wex: WalletExecutionContext, recoupGroupId: string, coinIdx: number, ): Promise<void> { - const coin = await ws.db.runReadOnlyTx( + const coin = await wex.db.runReadOnlyTx( ["coins", "recoupGroups"], async (tx) => { const recoupGroup = await tx.recoupGroups.get(recoupGroupId); @@ -527,11 +530,11 @@ async function processRecoupForCoin( switch (cs.type) { case CoinSourceType.Reward: - return recoupRewardCoin(ws, recoupGroupId, coinIdx, coin); + return recoupRewardCoin(wex, recoupGroupId, coinIdx, coin); case CoinSourceType.Refresh: - return recoupRefreshCoin(ws, recoupGroupId, coinIdx, coin, cs); + return recoupRefreshCoin(wex, recoupGroupId, coinIdx, coin, cs); case CoinSourceType.Withdraw: - return recoupWithdrawCoin(ws, recoupGroupId, coinIdx, coin, cs); + return recoupWithdrawCoin(wex, recoupGroupId, coinIdx, coin, cs); default: throw Error("unknown coin source type"); } diff --git a/packages/taler-wallet-core/src/refresh.ts b/packages/taler-wallet-core/src/refresh.ts index cc5eff12c..b467a1c47 100644 --- a/packages/taler-wallet-core/src/refresh.ts +++ b/packages/taler-wallet-core/src/refresh.ts @@ -21,7 +21,6 @@ import { Amounts, amountToPretty, assertUnreachable, - CancellationToken, checkDbInvariant, codecForExchangeMeltResponse, codecForExchangeRevealResponse, @@ -103,6 +102,7 @@ import { EXCHANGE_COINS_LOCK, getDenomInfo, InternalWalletState, + WalletExecutionContext, } from "./wallet.js"; import { getCandidateWithdrawalDenomsTx } from "./withdraw.js"; @@ -113,7 +113,7 @@ export class RefreshTransactionContext implements TransactionContext { readonly taskId: TaskIdStr; constructor( - public ws: InternalWalletState, + public wex: WalletExecutionContext, public refreshGroupId: string, ) { this.transactionId = constructTransactionIdentifier({ @@ -128,7 +128,7 @@ export class RefreshTransactionContext implements TransactionContext { async deleteTransaction(): Promise<void> { const refreshGroupId = this.refreshGroupId; - const ws = this.ws; + const ws = this.wex; await ws.db.runReadWriteTx(["refreshGroups", "tombstones"], async (tx) => { const rg = await tx.refreshGroups.get(refreshGroupId); if (rg) { @@ -141,8 +141,8 @@ export class RefreshTransactionContext implements TransactionContext { } async suspendTransaction(): Promise<void> { - const { ws, refreshGroupId, transactionId } = this; - let res = await ws.db.runReadWriteTx(["refreshGroups"], async (tx) => { + const { wex, refreshGroupId, transactionId } = this; + let res = await wex.db.runReadWriteTx(["refreshGroups"], async (tx) => { const dg = await tx.refreshGroups.get(refreshGroupId); if (!dg) { logger.warn( @@ -168,7 +168,7 @@ export class RefreshTransactionContext implements TransactionContext { return undefined; }); if (res) { - ws.notify({ + wex.ws.notify({ type: NotificationType.TransactionStateTransition, transactionId, oldTxState: res.oldTxState, @@ -183,7 +183,7 @@ export class RefreshTransactionContext implements TransactionContext { } async resumeTransaction(): Promise<void> { - const { ws, refreshGroupId, transactionId } = this; + const { wex: ws, refreshGroupId, transactionId } = this; const transitionInfo = await ws.db.runReadWriteTx( ["refreshGroups"], async (tx) => { @@ -217,7 +217,7 @@ export class RefreshTransactionContext implements TransactionContext { } async failTransaction(): Promise<void> { - const { ws, refreshGroupId, transactionId } = this; + const { wex: ws, refreshGroupId, transactionId } = this; const transitionInfo = await ws.db.runReadWriteTx( ["refreshGroups"], async (tx) => { @@ -331,7 +331,7 @@ function updateGroupStatus(rg: RefreshGroupRecord): { final: boolean } { * finished), return undefined. */ async function provideRefreshSession( - ws: InternalWalletState, + wex: WalletExecutionContext, refreshGroupId: string, coinIndex: number, ): Promise<RefreshSessionRecord | undefined> { @@ -339,7 +339,7 @@ async function provideRefreshSession( `creating refresh session for coin ${coinIndex} in refresh group ${refreshGroupId}`, ); - const d = await ws.db.runReadWriteTx( + const d = await wex.db.runReadWriteTx( ["coins", "refreshGroups", "refreshSessions"], async (tx) => { const refreshGroup = await tx.refreshGroups.get(refreshGroupId); @@ -374,16 +374,16 @@ async function provideRefreshSession( const { refreshGroup, coin } = d; - const exch = await fetchFreshExchange(ws, coin.exchangeBaseUrl); + const exch = await fetchFreshExchange(wex, coin.exchangeBaseUrl); // FIXME: use helper functions from withdraw.ts // to update and filter withdrawable denoms. - const { availableAmount, availableDenoms } = await ws.db.runReadOnlyTx( + const { availableAmount, availableDenoms } = await wex.db.runReadOnlyTx( ["denominations"], async (tx) => { const oldDenom = await getDenomInfo( - ws, + wex, tx, exch.exchangeBaseUrl, coin.denomPubHash, @@ -410,7 +410,7 @@ async function provideRefreshSession( const newCoinDenoms = selectWithdrawalDenominations( availableAmount, availableDenoms, - ws.config.testing.denomselAllowLate, + wex.ws.config.testing.denomselAllowLate, ); const transactionId = constructTransactionIdentifier({ @@ -424,7 +424,7 @@ async function provideRefreshSession( availableAmount, )} too small`, ); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["refreshGroups", "coins", "coinAvailability"], async (tx) => { const rg = await tx.refreshGroups.get(refreshGroupId); @@ -435,25 +435,25 @@ async function provideRefreshSession( rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Finished; const updateRes = updateGroupStatus(rg); if (updateRes.final) { - await makeCoinsVisible(ws, tx, transactionId); + await makeCoinsVisible(wex, tx, transactionId); } await tx.refreshGroups.put(rg); const newTxState = computeRefreshTransactionState(rg); return { oldTxState, newTxState }; }, ); - ws.notify({ + wex.ws.notify({ type: NotificationType.BalanceChange, hintTransactionId: transactionId, }); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return; } const sessionSecretSeed = encodeCrock(getRandomBytes(64)); // Store refresh session for this coin in the database. - const mySession = await ws.db.runReadWriteTx( + const mySession = await wex.db.runReadWriteTx( ["refreshGroups", "refreshSessions"], async (tx) => { const rg = await tx.refreshGroups.get(refreshGroupId); @@ -495,12 +495,11 @@ function getRefreshRequestTimeout(rg: RefreshGroupRecord): Duration { } async function refreshMelt( - ws: InternalWalletState, + wex: WalletExecutionContext, refreshGroupId: string, coinIndex: number, - cancellationToken: CancellationToken, ): Promise<void> { - const d = await ws.db.runReadWriteTx( + const d = await wex.db.runReadWriteTx( ["refreshGroups", "refreshSessions", "coins", "denominations"], async (tx) => { const refreshGroup = await tx.refreshGroups.get(refreshGroupId); @@ -521,7 +520,7 @@ async function refreshMelt( const oldCoin = await tx.coins.get(refreshGroup.oldCoinPubs[coinIndex]); checkDbInvariant(!!oldCoin, "melt coin doesn't exist"); const oldDenom = await getDenomInfo( - ws, + wex, tx, oldCoin.exchangeBaseUrl, oldCoin.denomPubHash, @@ -535,7 +534,7 @@ async function refreshMelt( for (const dh of refreshSession.newDenoms) { const newDenom = await getDenomInfo( - ws, + wex, tx, oldCoin.exchangeBaseUrl, dh.denomPubHash, @@ -572,7 +571,7 @@ async function refreshMelt( throw Error("unsupported key type"); } - const derived = await ws.cryptoApi.deriveRefreshSession({ + const derived = await wex.cryptoApi.deriveRefreshSession({ exchangeProtocolVersion, kappa: 3, meltCoinDenomPubHash: oldCoin.denomPubHash, @@ -607,14 +606,17 @@ async function refreshMelt( age_commitment_hash: maybeAch, }; - const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], async () => { - return await ws.http.fetch(reqUrl.href, { - method: "POST", - body: meltReqBody, - timeout: getRefreshRequestTimeout(refreshGroup), - cancellationToken, - }); - }); + const resp = await wex.ws.runSequentialized( + [EXCHANGE_COINS_LOCK], + async () => { + return await wex.http.fetch(reqUrl.href, { + method: "POST", + body: meltReqBody, + timeout: getRefreshRequestTimeout(refreshGroup), + cancellationToken: wex.cancellationToken, + }); + }, + ); const transactionId = constructTransactionIdentifier({ tag: TransactionType.Refresh, @@ -623,7 +625,7 @@ async function refreshMelt( if (resp.status === HttpStatusCode.NotFound) { const errDetails = await readUnexpectedResponseDetails(resp); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["refreshGroups", "refreshSessions", "coins", "coinAvailability"], async (tx) => { const rg = await tx.refreshGroups.get(refreshGroupId); @@ -650,7 +652,7 @@ async function refreshMelt( refreshSession.lastError = errDetails; const updateRes = updateGroupStatus(rg); if (updateRes.final) { - await makeCoinsVisible(ws, tx, transactionId); + await makeCoinsVisible(wex, tx, transactionId); } await tx.refreshGroups.put(rg); await tx.refreshSessions.put(refreshSession); @@ -661,11 +663,11 @@ async function refreshMelt( }; }, ); - ws.notify({ + wex.ws.notify({ type: NotificationType.BalanceChange, hintTransactionId: transactionId, }); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return; } @@ -678,7 +680,7 @@ async function refreshMelt( )} failed in refresh group ${refreshGroupId} due to conflict`, ); - const historySig = await ws.cryptoApi.signCoinHistoryRequest({ + const historySig = await wex.cryptoApi.signCoinHistoryRequest({ coinPriv: oldCoin.coinPriv, coinPub: oldCoin.coinPub, startOffset: 0, @@ -689,12 +691,12 @@ async function refreshMelt( oldCoin.exchangeBaseUrl, ); - const historyResp = await ws.http.fetch(historyUrl.href, { + const historyResp = await wex.http.fetch(historyUrl.href, { method: "GET", headers: { "Taler-Coin-History-Signature": historySig.sig, }, - cancellationToken, + cancellationToken: wex.cancellationToken, }); const historyJson = await historyResp.json(); @@ -712,7 +714,7 @@ async function refreshMelt( refreshSession.norevealIndex = norevealIndex; - await ws.db.runReadWriteTx( + await wex.db.runReadWriteTx( ["refreshGroups", "refreshSessions"], async (tx) => { const rg = await tx.refreshGroups.get(refreshGroupId); @@ -794,15 +796,14 @@ export async function assembleRefreshRevealRequest(args: { } async function refreshReveal( - ws: InternalWalletState, + wex: WalletExecutionContext, refreshGroupId: string, coinIndex: number, - cancellationToken: CancellationToken, ): Promise<void> { logger.trace( `doing refresh reveal for ${refreshGroupId} (old coin ${coinIndex})`, ); - const d = await ws.db.runReadOnlyTx( + const d = await wex.db.runReadOnlyTx( ["refreshGroups", "refreshSessions", "coins", "denominations"], async (tx) => { const refreshGroup = await tx.refreshGroups.get(refreshGroupId); @@ -824,7 +825,7 @@ async function refreshReveal( const oldCoin = await tx.coins.get(refreshGroup.oldCoinPubs[coinIndex]); checkDbInvariant(!!oldCoin, "melt coin doesn't exist"); const oldDenom = await getDenomInfo( - ws, + wex, tx, oldCoin.exchangeBaseUrl, oldCoin.denomPubHash, @@ -838,7 +839,7 @@ async function refreshReveal( for (const dh of refreshSession.newDenoms) { const newDenom = await getDenomInfo( - ws, + wex, tx, oldCoin.exchangeBaseUrl, dh.denomPubHash, @@ -889,7 +890,7 @@ async function refreshReveal( throw Error("unsupported key type"); } - const derived = await ws.cryptoApi.deriveRefreshSession({ + const derived = await wex.cryptoApi.deriveRefreshSession({ exchangeProtocolVersion, kappa: 3, meltCoinDenomPubHash: oldCoin.denomPubHash, @@ -908,7 +909,7 @@ async function refreshReveal( ); const req = await assembleRefreshRevealRequest({ - cryptoApi: ws.cryptoApi, + cryptoApi: wex.cryptoApi, derived, newDenoms: newCoinDenoms, norevealIndex: norevealIndex, @@ -917,14 +918,17 @@ async function refreshReveal( oldAgeCommitment: oldCoin.ageCommitmentProof?.commitment, }); - const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], async () => { - return await ws.http.fetch(reqUrl.href, { - body: req, - method: "POST", - timeout: getRefreshRequestTimeout(refreshGroup), - cancellationToken, - }); - }); + const resp = await wex.ws.runSequentialized( + [EXCHANGE_COINS_LOCK], + async () => { + return await wex.http.fetch(reqUrl.href, { + body: req, + method: "POST", + timeout: getRefreshRequestTimeout(refreshGroup), + cancellationToken: wex.cancellationToken, + }); + }, + ); const reveal = await readSuccessResponseJsonOrThrow( resp, @@ -947,7 +951,7 @@ async function refreshReveal( throw Error("cipher unsupported"); } const evSig = reveal.ev_sigs[newCoinIndex].ev_sig; - const denomSig = await ws.cryptoApi.unblindDenominationSignature({ + const denomSig = await wex.cryptoApi.unblindDenominationSignature({ planchet: { blindingKey: pc.blindingKey, denomPub: ncd.denomPub, @@ -978,7 +982,7 @@ async function refreshReveal( } } - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( [ "coins", "denominations", @@ -1000,26 +1004,25 @@ async function refreshReveal( rg.statusPerCoin[coinIndex] = RefreshCoinStatus.Finished; updateGroupStatus(rg); for (const coin of coins) { - await makeCoinAvailable(ws, tx, coin); + await makeCoinAvailable(wex, tx, coin); } - await makeCoinsVisible(ws, tx, transactionId); + await makeCoinsVisible(wex, tx, transactionId); await tx.refreshGroups.put(rg); const newTxState = computeRefreshTransactionState(rg); return { oldTxState, newTxState }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); logger.trace("refresh finished (end of reveal)"); } export async function processRefreshGroup( - ws: InternalWalletState, + wex: WalletExecutionContext, refreshGroupId: string, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { logger.trace(`processing refresh group ${refreshGroupId}`); - const refreshGroup = await ws.db.runReadOnlyTx( + const refreshGroup = await wex.db.runReadOnlyTx( ["refreshGroups"], async (tx) => tx.refreshGroups.get(refreshGroupId), ); @@ -1036,28 +1039,26 @@ export async function processRefreshGroup( let errors: TalerErrorDetail[] = []; let inShutdown = false; const ps = refreshGroup.oldCoinPubs.map((x, i) => - processRefreshSession(ws, refreshGroupId, i, cancellationToken).catch( - (x) => { - if (x instanceof CryptoApiStoppedError) { - inShutdown = true; - logger.info( - "crypto API stopped while processing refresh group, probably the wallet is currently shutting down.", - ); - return; - } - if (x instanceof TalerError) { - logger.warn("process refresh session got exception (TalerError)"); - logger.warn(`exc ${x}`); - logger.warn(`exc stack ${x.stack}`); - logger.warn(`error detail: ${j2s(x.errorDetail)}`); - } else { - logger.warn("process refresh session got exception"); - logger.warn(`exc ${x}`); - logger.warn(`exc stack ${x.stack}`); - } - errors.push(getErrorDetailFromException(x)); - }, - ), + processRefreshSession(wex, refreshGroupId, i).catch((x) => { + if (x instanceof CryptoApiStoppedError) { + inShutdown = true; + logger.info( + "crypto API stopped while processing refresh group, probably the wallet is currently shutting down.", + ); + return; + } + if (x instanceof TalerError) { + logger.warn("process refresh session got exception (TalerError)"); + logger.warn(`exc ${x}`); + logger.warn(`exc stack ${x.stack}`); + logger.warn(`error detail: ${j2s(x.errorDetail)}`); + } else { + logger.warn("process refresh session got exception"); + logger.warn(`exc ${x}`); + logger.warn(`exc stack ${x.stack}`); + } + errors.push(getErrorDetailFromException(x)); + }), ); try { logger.info("waiting for refreshes"); @@ -1087,15 +1088,14 @@ export async function processRefreshGroup( } async function processRefreshSession( - ws: InternalWalletState, + wex: WalletExecutionContext, refreshGroupId: string, coinIndex: number, - cancellationToken: CancellationToken, ): Promise<void> { logger.trace( `processing refresh session for coin ${coinIndex} of group ${refreshGroupId}`, ); - let { refreshGroup, refreshSession } = await ws.db.runReadOnlyTx( + let { refreshGroup, refreshSession } = await wex.db.runReadOnlyTx( ["refreshGroups", "refreshSessions"], async (tx) => { const rg = await tx.refreshGroups.get(refreshGroupId); @@ -1113,7 +1113,11 @@ async function processRefreshSession( return; } if (!refreshSession) { - refreshSession = await provideRefreshSession(ws, refreshGroupId, coinIndex); + refreshSession = await provideRefreshSession( + wex, + refreshGroupId, + coinIndex, + ); } if (!refreshSession) { // We tried to create the refresh session, but didn't get a result back. @@ -1122,9 +1126,9 @@ async function processRefreshSession( return; } if (refreshSession.norevealIndex === undefined) { - await refreshMelt(ws, refreshGroupId, coinIndex, cancellationToken); + await refreshMelt(wex, refreshGroupId, coinIndex); } - await refreshReveal(ws, refreshGroupId, coinIndex, cancellationToken); + await refreshReveal(wex, refreshGroupId, coinIndex); } export interface RefreshOutputInfo { @@ -1133,7 +1137,7 @@ export interface RefreshOutputInfo { } export async function calculateRefreshOutput( - ws: InternalWalletState, + wex: WalletExecutionContext, tx: WalletDbReadOnlyTransaction< ["denominations", "coins", "refreshGroups", "coinAvailability"] >, @@ -1154,7 +1158,7 @@ export async function calculateRefreshOutput( return denomsPerExchange[exchangeBaseUrl]; } const allDenoms = await getCandidateWithdrawalDenomsTx( - ws, + wex, tx, exchangeBaseUrl, currency, @@ -1167,7 +1171,7 @@ export async function calculateRefreshOutput( const coin = await tx.coins.get(ocp.coinPub); checkDbInvariant(!!coin, "coin must be in database"); const denom = await getDenomInfo( - ws, + wex, tx, coin.exchangeBaseUrl, coin.denomPubHash, @@ -1182,7 +1186,7 @@ export async function calculateRefreshOutput( denoms, denom, Amounts.parseOrThrow(refreshAmount), - ws.config.testing.denomselAllowLate, + wex.ws.config.testing.denomselAllowLate, ); const output = Amounts.sub(refreshAmount, cost).amount; let exchInfo = infoPerExchange[coin.exchangeBaseUrl]; @@ -1204,7 +1208,7 @@ export async function calculateRefreshOutput( } async function applyRefresh( - ws: InternalWalletState, + wex: WalletExecutionContext, tx: WalletDbReadWriteTransaction< ["denominations", "coins", "refreshGroups", "coinAvailability"] >, @@ -1215,7 +1219,7 @@ async function applyRefresh( const coin = await tx.coins.get(ocp.coinPub); checkDbInvariant(!!coin, "coin must be in database"); const denom = await getDenomInfo( - ws, + wex, tx, coin.exchangeBaseUrl, coin.denomPubHash, @@ -1278,7 +1282,7 @@ export interface CreateRefreshGroupResult { * in the current database transaction. */ export async function createRefreshGroup( - ws: InternalWalletState, + wex: WalletExecutionContext, tx: WalletDbReadWriteTransaction< ["denominations", "coins", "refreshGroups", "coinAvailability"] >, @@ -1289,11 +1293,11 @@ export async function createRefreshGroup( ): Promise<CreateRefreshGroupResult> { const refreshGroupId = encodeCrock(getRandomBytes(32)); - const outInfo = await calculateRefreshOutput(ws, tx, currency, oldCoinPubs); + const outInfo = await calculateRefreshOutput(wex, tx, currency, oldCoinPubs); const estimatedOutputPerCoin = outInfo.outputPerCoin; - await applyRefresh(ws, tx, oldCoinPubs, refreshGroupId); + await applyRefresh(wex, tx, oldCoinPubs, refreshGroupId); const refreshGroup: RefreshGroupRecord = { operationStatus: RefreshOperationStatus.Pending, @@ -1326,12 +1330,12 @@ export async function createRefreshGroup( logger.trace(`created refresh group ${refreshGroupId}`); - const ctx = new RefreshTransactionContext(ws, refreshGroupId); + const ctx = new RefreshTransactionContext(wex, refreshGroupId); // Shepherd the task. // If the current transaction fails to commit the refresh // group to the DB, the shepherd will give up. - ws.taskScheduler.startShepherdTask(ctx.taskId); + wex.taskScheduler.startShepherdTask(ctx.taskId); return { refreshGroupId, @@ -1391,10 +1395,10 @@ export function computeRefreshTransactionActions( } export function getRefreshesForTransaction( - ws: InternalWalletState, + wex: WalletExecutionContext, transactionId: string, ): Promise<string[]> { - return ws.db.runReadOnlyTx(["refreshGroups"], async (tx) => { + return wex.db.runReadOnlyTx(["refreshGroups"], async (tx) => { const groups = await tx.refreshGroups.indexes.byOriginatingTransactionId.getAll( transactionId, @@ -1409,13 +1413,13 @@ export function getRefreshesForTransaction( } export async function forceRefresh( - ws: InternalWalletState, + wex: WalletExecutionContext, req: ForceRefreshRequest, ): Promise<{ refreshGroupId: RefreshGroupId }> { if (req.coinPubList.length == 0) { throw Error("refusing to create empty refresh group"); } - const refreshGroupId = await ws.db.runReadWriteTx( + const refreshGroupId = await wex.db.runReadWriteTx( ["refreshGroups", "coinAvailability", "denominations", "coins"], async (tx) => { let coinPubs: CoinRefreshRequest[] = []; @@ -1425,7 +1429,7 @@ export async function forceRefresh( throw Error(`coin (pubkey ${c}) not found`); } const denom = await getDenomInfo( - ws, + wex, tx, coin.exchangeBaseUrl, coin.denomPubHash, @@ -1437,7 +1441,7 @@ export async function forceRefresh( }); } return await createRefreshGroup( - ws, + wex, tx, Amounts.currencyOf(coinPubs[0].amount), coinPubs, diff --git a/packages/taler-wallet-core/src/reward.ts b/packages/taler-wallet-core/src/reward.ts index 51eb0f5bd..b8cf41326 100644 --- a/packages/taler-wallet-core/src/reward.ts +++ b/packages/taler-wallet-core/src/reward.ts @@ -42,7 +42,7 @@ import { constructTransactionIdentifier, notifyTransition, } from "./transactions.js"; -import { InternalWalletState } from "./wallet.js"; +import { InternalWalletState, WalletExecutionContext } from "./wallet.js"; const logger = new Logger("operations/tip.ts"); @@ -51,7 +51,7 @@ export class RewardTransactionContext implements TransactionContext { public taskId: TaskIdStr; constructor( - public ws: InternalWalletState, + public wex: WalletExecutionContext, public walletRewardId: string, ) { this.transactionId = constructTransactionIdentifier({ @@ -65,8 +65,8 @@ export class RewardTransactionContext implements TransactionContext { } async deleteTransaction(): Promise<void> { - const { ws, walletRewardId } = this; - await ws.db.runReadWriteTx(["rewards", "tombstones"], async (tx) => { + const { wex, walletRewardId } = this; + await wex.db.runReadWriteTx(["rewards", "tombstones"], async (tx) => { const tipRecord = await tx.rewards.get(walletRewardId); if (tipRecord) { await tx.rewards.delete(walletRewardId); @@ -78,8 +78,8 @@ export class RewardTransactionContext implements TransactionContext { } async suspendTransaction(): Promise<void> { - const { ws, walletRewardId, transactionId, taskId: retryTag } = this; - const transitionInfo = await ws.db.runReadWriteTx( + const { wex, walletRewardId, transactionId, taskId } = this; + const transitionInfo = await wex.db.runReadWriteTx( ["rewards"], async (tx) => { const tipRec = await tx.rewards.get(walletRewardId); @@ -115,12 +115,12 @@ export class RewardTransactionContext implements TransactionContext { return undefined; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); } async abortTransaction(): Promise<void> { - const { ws, walletRewardId, transactionId, taskId: retryTag } = this; - const transitionInfo = await ws.db.runReadWriteTx( + const { wex, walletRewardId, transactionId } = this; + const transitionInfo = await wex.db.runReadWriteTx( ["rewards"], async (tx) => { const tipRec = await tx.rewards.get(walletRewardId); @@ -155,11 +155,11 @@ export class RewardTransactionContext implements TransactionContext { return undefined; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); } async resumeTransaction(): Promise<void> { - const { ws, walletRewardId, transactionId, taskId: retryTag } = this; + const { wex: ws, walletRewardId, transactionId, taskId: retryTag } = this; const transitionInfo = await ws.db.runReadWriteTx( ["rewards"], async (tx) => { @@ -199,7 +199,7 @@ export class RewardTransactionContext implements TransactionContext { } async failTransaction(): Promise<void> { - const { ws, walletRewardId, transactionId, taskId: retryTag } = this; + const { wex: ws, walletRewardId, transactionId, taskId: retryTag } = this; const transitionInfo = await ws.db.runReadWriteTx( ["rewards"], async (tx) => { diff --git a/packages/taler-wallet-core/src/shepherd.ts b/packages/taler-wallet-core/src/shepherd.ts index ec0f6a76e..e3a0bd609 100644 --- a/packages/taler-wallet-core/src/shepherd.ts +++ b/packages/taler-wallet-core/src/shepherd.ts @@ -93,7 +93,7 @@ import { constructTransactionIdentifier, parseTransactionIdentifier, } from "./transactions.js"; -import { InternalWalletState } from "./wallet.js"; +import { InternalWalletState, WalletExecutionContext } from "./wallet.js"; import { computeWithdrawalTransactionStatus, processWithdrawalGroup, @@ -563,66 +563,41 @@ async function callOperationHandlerForTaskId( taskId: TaskIdStr, cancellationToken: CancellationToken, ): Promise<TaskRunResult> { + const wex: WalletExecutionContext = { + ws, + cancellationToken, + cryptoApi: ws.cryptoApi, + db: ws.db, + http: ws.http, + taskScheduler: ws.taskScheduler, + oc: { + observe(event) {}, + }, + }; const pending = parseTaskIdentifier(taskId); switch (pending.tag) { case PendingTaskType.ExchangeUpdate: - return await updateExchangeFromUrlHandler( - ws, - pending.exchangeBaseUrl, - cancellationToken, - ); + return await updateExchangeFromUrlHandler(wex, pending.exchangeBaseUrl); case PendingTaskType.Refresh: - return await processRefreshGroup( - ws, - pending.refreshGroupId, - cancellationToken, - ); + return await processRefreshGroup(wex, pending.refreshGroupId); case PendingTaskType.Withdraw: - return await processWithdrawalGroup( - ws, - pending.withdrawalGroupId, - cancellationToken, - ); + return await processWithdrawalGroup(wex, pending.withdrawalGroupId); case PendingTaskType.Purchase: - return await processPurchase(ws, pending.proposalId, cancellationToken); + return await processPurchase(wex, pending.proposalId); case PendingTaskType.Recoup: - return await processRecoupGroup( - ws, - pending.recoupGroupId, - cancellationToken, - ); + return await processRecoupGroup(wex, pending.recoupGroupId); case PendingTaskType.Deposit: - return await processDepositGroup( - ws, - pending.depositGroupId, - cancellationToken, - ); + return await processDepositGroup(wex, pending.depositGroupId); case PendingTaskType.Backup: - return await processBackupForProvider(ws, pending.backupProviderBaseUrl); + return await processBackupForProvider(wex, pending.backupProviderBaseUrl); case PendingTaskType.PeerPushDebit: - return await processPeerPushDebit( - ws, - pending.pursePub, - cancellationToken, - ); + return await processPeerPushDebit(wex, pending.pursePub); case PendingTaskType.PeerPullCredit: - return await processPeerPullCredit( - ws, - pending.pursePub, - cancellationToken, - ); + return await processPeerPullCredit(wex, pending.pursePub); case PendingTaskType.PeerPullDebit: - return await processPeerPullDebit( - ws, - pending.peerPullDebitId, - cancellationToken, - ); + return await processPeerPullDebit(wex, pending.peerPullDebitId); case PendingTaskType.PeerPushCredit: - return await processPeerPushCredit( - ws, - pending.peerPushCreditId, - cancellationToken, - ); + return await processPeerPushCredit(wex, pending.peerPushCreditId); case PendingTaskType.RewardPickup: throw Error("not supported anymore"); default: diff --git a/packages/taler-wallet-core/src/testing.ts b/packages/taler-wallet-core/src/testing.ts index 22af816e5..45a29a6e3 100644 --- a/packages/taler-wallet-core/src/testing.ts +++ b/packages/taler-wallet-core/src/testing.ts @@ -77,7 +77,7 @@ import { import { initiatePeerPushDebit } from "./pay-peer-push-debit.js"; import { getRefreshesForTransaction } from "./refresh.js"; import { getTransactionById, getTransactions } from "./transactions.js"; -import type { InternalWalletState } from "./wallet.js"; +import type { InternalWalletState, WalletExecutionContext } from "./wallet.js"; import { acceptWithdrawalFromUri } from "./withdraw.js"; const logger = new Logger("operations/testing.ts"); @@ -100,7 +100,7 @@ export interface WithdrawTestBalanceResult { } export async function withdrawTestBalance( - ws: InternalWalletState, + wex: WalletExecutionContext, req: WithdrawTestBalanceRequest, ): Promise<WithdrawTestBalanceResult> { const amount = req.amount; @@ -123,7 +123,7 @@ export async function withdrawTestBalance( amount, ); - const acceptResp = await acceptWithdrawalFromUri(ws, { + const acceptResp = await acceptWithdrawalFromUri(wex, { talerWithdrawUri: wresp.taler_withdraw_uri, selectedExchange: exchangeBaseUrl, forcedDenomSel: req.forcedDenomSel, @@ -239,13 +239,13 @@ interface MakePaymentResult { } async function makePayment( - ws: InternalWalletState, + wex: WalletExecutionContext, merchant: MerchantBackendInfo, amount: string, summary: string, ): Promise<MakePaymentResult> { const orderResp = await createOrder( - ws.http, + wex.http, merchant, amount, summary, @@ -254,7 +254,7 @@ async function makePayment( logger.trace("created order with orderId", orderResp.orderId); - let paymentStatus = await checkPayment(ws.http, merchant, orderResp.orderId); + let paymentStatus = await checkPayment(wex.http, merchant, orderResp.orderId); logger.trace("payment status", paymentStatus); @@ -263,7 +263,7 @@ async function makePayment( throw Error("no taler://pay/ URI in payment response"); } - const preparePayResult = await preparePayForUri(ws, talerPayUri); + const preparePayResult = await preparePayForUri(wex, talerPayUri); logger.trace("prepare pay result", preparePayResult); @@ -272,14 +272,14 @@ async function makePayment( } const confirmPayResult = await confirmPay( - ws, + wex, preparePayResult.transactionId, undefined, ); logger.trace("confirmPayResult", confirmPayResult); - paymentStatus = await checkPayment(ws.http, merchant, orderResp.orderId); + paymentStatus = await checkPayment(wex.http, merchant, orderResp.orderId); logger.trace("payment status after wallet payment:", paymentStatus); @@ -294,7 +294,7 @@ async function makePayment( } export async function runIntegrationTest( - ws: InternalWalletState, + wex: WalletExecutionContext, args: IntegrationTestArgs, ): Promise<void> { logger.info("running test with arguments", args); @@ -303,15 +303,15 @@ export async function runIntegrationTest( const currency = parsedSpendAmount.currency; logger.info("withdrawing test balance"); - const withdrawRes1 = await withdrawTestBalance(ws, { + const withdrawRes1 = await withdrawTestBalance(wex, { amount: args.amountToWithdraw, corebankApiBaseUrl: args.corebankApiBaseUrl, exchangeBaseUrl: args.exchangeBaseUrl, }); - await waitUntilGivenTransactionsFinal(ws, [withdrawRes1.transactionId]); + await waitUntilGivenTransactionsFinal(wex, [withdrawRes1.transactionId]); logger.info("done withdrawing test balance"); - const balance = await getBalances(ws); + const balance = await getBalances(wex); logger.trace(JSON.stringify(balance, null, 2)); @@ -321,14 +321,14 @@ export async function runIntegrationTest( }; const makePaymentRes = await makePayment( - ws, + wex, myMerchant, args.amountToSpend, "hello world", ); await waitUntilTransactionWithAssociatedRefreshesFinal( - ws, + wex, makePaymentRes.paymentTransactionId, ); @@ -338,23 +338,23 @@ export async function runIntegrationTest( const refundAmount = Amounts.parseOrThrow(`${currency}:6`); const spendAmountThree = Amounts.parseOrThrow(`${currency}:3`); - const withdrawRes2 = await withdrawTestBalance(ws, { + const withdrawRes2 = await withdrawTestBalance(wex, { amount: Amounts.stringify(withdrawAmountTwo), corebankApiBaseUrl: args.corebankApiBaseUrl, exchangeBaseUrl: args.exchangeBaseUrl, }); - await waitUntilGivenTransactionsFinal(ws, [withdrawRes2.transactionId]); + await waitUntilGivenTransactionsFinal(wex, [withdrawRes2.transactionId]); const { orderId: refundOrderId } = await makePayment( - ws, + wex, myMerchant, Amounts.stringify(spendAmountTwo), "order that will be refunded", ); const refundUri = await refund( - ws.http, + wex.http, myMerchant, refundOrderId, "test refund", @@ -363,20 +363,20 @@ export async function runIntegrationTest( logger.trace("refund URI", refundUri); - const refundResp = await startRefundQueryForUri(ws, refundUri); + const refundResp = await startRefundQueryForUri(wex, refundUri); logger.trace("integration test: applied refund"); // Wait until the refund is done await waitUntilTransactionWithAssociatedRefreshesFinal( - ws, + wex, refundResp.transactionId, ); logger.trace("integration test: making payment after refund"); const paymentResp2 = await makePayment( - ws, + wex, myMerchant, Amounts.stringify(spendAmountThree), "payment after refund", @@ -384,12 +384,12 @@ export async function runIntegrationTest( logger.trace("integration test: make payment done"); - await waitUntilGivenTransactionsFinal(ws, [ + await waitUntilGivenTransactionsFinal(wex, [ paymentResp2.paymentTransactionId, ]); await waitUntilGivenTransactionsFinal( - ws, - await getRefreshesForTransaction(ws, paymentResp2.paymentTransactionId), + wex, + await getRefreshesForTransaction(wex, paymentResp2.paymentTransactionId), ); logger.trace("integration test: all done!"); @@ -399,12 +399,12 @@ export async function runIntegrationTest( * Wait until all transactions are in a final state. */ export async function waitUntilAllTransactionsFinal( - ws: InternalWalletState, + wex: WalletExecutionContext, ): Promise<void> { logger.info("waiting until all transactions are in a final state"); - ws.taskScheduler.ensureRunning(); + wex.taskScheduler.ensureRunning(); let p: OpenedPromise<void> | undefined = undefined; - const cancelNotifs = ws.addNotificationListener((notif) => { + const cancelNotifs = wex.ws.addNotificationListener((notif) => { if (!p) { return; } @@ -420,7 +420,7 @@ export async function waitUntilAllTransactionsFinal( }); while (1) { p = openPromise(); - const txs = await getTransactions(ws, { + const txs = await getTransactions(wex, { includeRefreshes: true, filterByState: "nonfinal", }); @@ -452,7 +452,7 @@ export async function waitUntilAllTransactionsFinal( * Wait until all chosen transactions are in a final state. */ export async function waitUntilGivenTransactionsFinal( - ws: InternalWalletState, + wex: WalletExecutionContext, transactionIds: string[], ): Promise<void> { logger.info( @@ -462,10 +462,10 @@ export async function waitUntilGivenTransactionsFinal( if (transactionIds.length === 0) { return; } - ws.taskScheduler.ensureRunning(); + wex.taskScheduler.ensureRunning(); const txIdSet = new Set(transactionIds); let p: OpenedPromise<void> | undefined = undefined; - const cancelNotifs = ws.addNotificationListener((notif) => { + const cancelNotifs = wex.ws.addNotificationListener((notif) => { if (!p) { return; } @@ -486,7 +486,7 @@ export async function waitUntilGivenTransactionsFinal( }); while (1) { p = openPromise(); - const txs = await getTransactions(ws, { + const txs = await getTransactions(wex, { includeRefreshes: true, filterByState: "nonfinal", }); @@ -519,12 +519,12 @@ export async function waitUntilGivenTransactionsFinal( } export async function waitUntilRefreshesDone( - ws: InternalWalletState, + wex: WalletExecutionContext, ): Promise<void> { logger.info("waiting until all refresh transactions are in a final state"); - ws.taskScheduler.ensureRunning(); + wex.taskScheduler.ensureRunning(); let p: OpenedPromise<void> | undefined = undefined; - const cancelNotifs = ws.addNotificationListener((notif) => { + const cancelNotifs = wex.ws.addNotificationListener((notif) => { if (!p) { return; } @@ -540,7 +540,7 @@ export async function waitUntilRefreshesDone( }); while (1) { p = openPromise(); - const txs = await getTransactions(ws, { + const txs = await getTransactions(wex, { includeRefreshes: true, filterByState: "nonfinal", }); @@ -572,13 +572,13 @@ export async function waitUntilRefreshesDone( } async function waitUntilTransactionPendingReady( - ws: InternalWalletState, + wex: WalletExecutionContext, transactionId: string, ): Promise<void> { logger.info(`starting waiting for ${transactionId} to be in pending(ready)`); - ws.taskScheduler.ensureRunning(); + wex.taskScheduler.ensureRunning(); let p: OpenedPromise<void> | undefined = undefined; - const cancelNotifs = ws.addNotificationListener((notif) => { + const cancelNotifs = wex.ws.addNotificationListener((notif) => { if (!p) { return; } @@ -588,7 +588,7 @@ async function waitUntilTransactionPendingReady( }); while (1) { p = openPromise(); - const tx = await getTransactionById(ws, { + const tx = await getTransactionById(wex, { transactionId, }); if ( @@ -608,7 +608,7 @@ async function waitUntilTransactionPendingReady( * Wait until a transaction is in a particular state. */ export async function waitTransactionState( - ws: InternalWalletState, + wex: WalletExecutionContext, transactionId: string, txState: TransactionState, ): Promise<void> { @@ -617,9 +617,9 @@ export async function waitTransactionState( txState, )})`, ); - ws.taskScheduler.ensureRunning(); + wex.taskScheduler.ensureRunning(); let p: OpenedPromise<void> | undefined = undefined; - const cancelNotifs = ws.addNotificationListener((notif) => { + const cancelNotifs = wex.ws.addNotificationListener((notif) => { if (!p) { return; } @@ -629,7 +629,7 @@ export async function waitTransactionState( }); while (1) { p = openPromise(); - const tx = await getTransactionById(ws, { + const tx = await getTransactionById(wex, { transactionId, }); if ( @@ -648,31 +648,31 @@ export async function waitTransactionState( } export async function waitUntilTransactionWithAssociatedRefreshesFinal( - ws: InternalWalletState, + wex: WalletExecutionContext, transactionId: string, ): Promise<void> { - await waitUntilGivenTransactionsFinal(ws, [transactionId]); + await waitUntilGivenTransactionsFinal(wex, [transactionId]); await waitUntilGivenTransactionsFinal( - ws, - await getRefreshesForTransaction(ws, transactionId), + wex, + await getRefreshesForTransaction(wex, transactionId), ); } export async function waitUntilTransactionFinal( - ws: InternalWalletState, + wex: WalletExecutionContext, transactionId: string, ): Promise<void> { - await waitUntilGivenTransactionsFinal(ws, [transactionId]); + await waitUntilGivenTransactionsFinal(wex, [transactionId]); } export async function runIntegrationTest2( - ws: InternalWalletState, + wex: WalletExecutionContext, args: IntegrationTestV2Args, ): Promise<void> { - ws.taskScheduler.ensureRunning(); + wex.taskScheduler.ensureRunning(); logger.info("running test with arguments", args); - const exchangeInfo = await fetchFreshExchange(ws, args.exchangeBaseUrl); + const exchangeInfo = await fetchFreshExchange(wex, args.exchangeBaseUrl); const currency = exchangeInfo.currency; @@ -680,15 +680,15 @@ export async function runIntegrationTest2( const amountToSpend = Amounts.parseOrThrow(`${currency}:2`); logger.info("withdrawing test balance"); - const withdrawalRes = await withdrawTestBalance(ws, { + const withdrawalRes = await withdrawTestBalance(wex, { amount: Amounts.stringify(amountToWithdraw), corebankApiBaseUrl: args.corebankApiBaseUrl, exchangeBaseUrl: args.exchangeBaseUrl, }); - await waitUntilTransactionFinal(ws, withdrawalRes.transactionId); + await waitUntilTransactionFinal(wex, withdrawalRes.transactionId); logger.info("done withdrawing test balance"); - const balance = await getBalances(ws); + const balance = await getBalances(wex); logger.trace(JSON.stringify(balance, null, 2)); @@ -698,14 +698,14 @@ export async function runIntegrationTest2( }; const makePaymentRes = await makePayment( - ws, + wex, myMerchant, Amounts.stringify(amountToSpend), "hello world", ); await waitUntilTransactionWithAssociatedRefreshesFinal( - ws, + wex, makePaymentRes.paymentTransactionId, ); @@ -715,24 +715,24 @@ export async function runIntegrationTest2( const refundAmount = Amounts.parseOrThrow(`${currency}:6`); const spendAmountThree = Amounts.parseOrThrow(`${currency}:3`); - const withdrawalRes2 = await withdrawTestBalance(ws, { + const withdrawalRes2 = await withdrawTestBalance(wex, { amount: Amounts.stringify(withdrawAmountTwo), corebankApiBaseUrl: args.corebankApiBaseUrl, exchangeBaseUrl: args.exchangeBaseUrl, }); // Wait until the withdraw is done - await waitUntilTransactionFinal(ws, withdrawalRes2.transactionId); + await waitUntilTransactionFinal(wex, withdrawalRes2.transactionId); const { orderId: refundOrderId } = await makePayment( - ws, + wex, myMerchant, Amounts.stringify(spendAmountTwo), "order that will be refunded", ); const refundUri = await refund( - ws.http, + wex.http, myMerchant, refundOrderId, "test refund", @@ -741,33 +741,33 @@ export async function runIntegrationTest2( logger.trace("refund URI", refundUri); - const refundResp = await startRefundQueryForUri(ws, refundUri); + const refundResp = await startRefundQueryForUri(wex, refundUri); logger.trace("integration test: applied refund"); // Wait until the refund is done await waitUntilTransactionWithAssociatedRefreshesFinal( - ws, + wex, refundResp.transactionId, ); logger.trace("integration test: making payment after refund"); const makePaymentRes2 = await makePayment( - ws, + wex, myMerchant, Amounts.stringify(spendAmountThree), "payment after refund", ); await waitUntilTransactionWithAssociatedRefreshesFinal( - ws, + wex, makePaymentRes2.paymentTransactionId, ); logger.trace("integration test: make payment done"); - const peerPushInit = await initiatePeerPushDebit(ws, { + const peerPushInit = await initiatePeerPushDebit(wex, { partialContractTerms: { amount: `${currency}:1` as AmountString, summary: "Payment Peer Push Test", @@ -780,8 +780,8 @@ export async function runIntegrationTest2( }, }); - await waitUntilTransactionPendingReady(ws, peerPushInit.transactionId); - const txDetails = await getTransactionById(ws, { + await waitUntilTransactionPendingReady(wex, peerPushInit.transactionId); + const txDetails = await getTransactionById(wex, { transactionId: peerPushInit.transactionId, }); @@ -793,15 +793,15 @@ export async function runIntegrationTest2( throw Error("internal invariant failed"); } - const peerPushCredit = await preparePeerPushCredit(ws, { + const peerPushCredit = await preparePeerPushCredit(wex, { talerUri: txDetails.talerUri, }); - await confirmPeerPushCredit(ws, { + await confirmPeerPushCredit(wex, { transactionId: peerPushCredit.transactionId, }); - const peerPullInit = await initiatePeerPullPayment(ws, { + const peerPullInit = await initiatePeerPullPayment(wex, { partialContractTerms: { amount: `${currency}:1` as AmountString, summary: "Payment Peer Pull Test", @@ -814,33 +814,33 @@ export async function runIntegrationTest2( }, }); - await waitUntilTransactionPendingReady(ws, peerPullInit.transactionId); + await waitUntilTransactionPendingReady(wex, peerPullInit.transactionId); - const peerPullInc = await preparePeerPullDebit(ws, { + const peerPullInc = await preparePeerPullDebit(wex, { talerUri: peerPullInit.talerUri, }); - await confirmPeerPullDebit(ws, { + await confirmPeerPullDebit(wex, { peerPullDebitId: peerPullInc.peerPullDebitId, }); await waitUntilTransactionWithAssociatedRefreshesFinal( - ws, + wex, peerPullInc.transactionId, ); await waitUntilTransactionWithAssociatedRefreshesFinal( - ws, + wex, peerPullInit.transactionId, ); await waitUntilTransactionWithAssociatedRefreshesFinal( - ws, + wex, peerPushCredit.transactionId, ); await waitUntilTransactionWithAssociatedRefreshesFinal( - ws, + wex, peerPushInit.transactionId, ); @@ -858,7 +858,7 @@ export async function runIntegrationTest2( }); } - await createDepositGroup(ws, { + await createDepositGroup(wex, { amount: `${currency}:5` as AmountString, depositPaytoUri: depositPayto, }); @@ -867,7 +867,7 @@ export async function runIntegrationTest2( } export async function testPay( - ws: InternalWalletState, + wex: WalletExecutionContext, args: TestPayArgs, ): Promise<TestPayResult> { logger.trace("creating order"); @@ -876,26 +876,26 @@ export async function testPay( baseUrl: args.merchantBaseUrl, }; const orderResp = await createOrder( - ws.http, + wex.http, merchant, args.amount, args.summary, "taler://fulfillment-success/thank+you", ); logger.trace("created new order with order ID", orderResp.orderId); - const checkPayResp = await checkPayment(ws.http, merchant, orderResp.orderId); + const checkPayResp = await checkPayment(wex.http, merchant, orderResp.orderId); const talerPayUri = checkPayResp.taler_pay_uri; if (!talerPayUri) { console.error("fatal: no taler pay URI received from backend"); process.exit(1); } logger.trace("taler pay URI:", talerPayUri); - const result = await preparePayForUri(ws, talerPayUri); + const result = await preparePayForUri(wex, talerPayUri); if (result.status !== PreparePayResultType.PaymentPossible) { throw Error(`unexpected prepare pay status: ${result.status}`); } const r = await confirmPay( - ws, + wex, result.transactionId, undefined, args.forcedCoinSel, @@ -903,7 +903,7 @@ export async function testPay( if (r.type != ConfirmPayResultType.Done) { throw Error("payment not done"); } - const purchase = await ws.db.runReadOnlyTx(["purchases"], async (tx) => { + const purchase = await wex.db.runReadOnlyTx(["purchases"], async (tx) => { return tx.purchases.get(result.proposalId); }); checkLogicInvariant(!!purchase); diff --git a/packages/taler-wallet-core/src/transactions.ts b/packages/taler-wallet-core/src/transactions.ts index d7f0c0d18..3beb42187 100644 --- a/packages/taler-wallet-core/src/transactions.ts +++ b/packages/taler-wallet-core/src/transactions.ts @@ -132,7 +132,7 @@ import { computeTipTransactionActions, RewardTransactionContext, } from "./reward.js"; -import type { InternalWalletState } from "./wallet.js"; +import type { InternalWalletState, WalletExecutionContext } from "./wallet.js"; import { augmentPaytoUrisForWithdrawal, computeWithdrawalTransactionActions, @@ -215,7 +215,7 @@ const txOrder: { [t in TransactionType]: number } = { }; export async function getTransactionById( - ws: InternalWalletState, + wex: WalletExecutionContext, req: TransactionByIdRequest, ): Promise<Transaction> { const parsedTx = parseTransactionIdentifier(req.transactionId); @@ -228,7 +228,7 @@ export async function getTransactionById( case TransactionType.InternalWithdrawal: case TransactionType.Withdrawal: { const withdrawalGroupId = parsedTx.withdrawalGroupId; - return await ws.db.runReadWriteTx( + return await wex.db.runReadWriteTx( [ "withdrawalGroups", "exchangeDetails", @@ -273,7 +273,7 @@ export async function getTransactionById( case TransactionType.Payment: { const proposalId = parsedTx.proposalId; - return await ws.db.runReadWriteTx( + return await wex.db.runReadWriteTx( [ "purchases", "tombstones", @@ -284,7 +284,7 @@ export async function getTransactionById( async (tx) => { const purchase = await tx.purchases.get(proposalId); if (!purchase) throw Error("not found"); - const download = await expectProposalDownload(ws, purchase, tx); + const download = await expectProposalDownload(wex, purchase, tx); const contractData = download.contractData; const payOpId = TaskIdentifiers.forPay(purchase); const payRetryRecord = await tx.operationRetries.get(payOpId); @@ -306,7 +306,7 @@ export async function getTransactionById( case TransactionType.Refresh: { // FIXME: We should return info about the refresh here!; const refreshGroupId = parsedTx.refreshGroupId; - return await ws.db.runReadOnlyTx( + return await wex.db.runReadOnlyTx( ["refreshGroups", "operationRetries"], async (tx) => { const refreshGroupRec = await tx.refreshGroups.get(refreshGroupId); @@ -323,7 +323,7 @@ export async function getTransactionById( case TransactionType.Reward: { const tipId = parsedTx.walletRewardId; - return await ws.db.runReadWriteTx( + return await wex.db.runReadWriteTx( ["rewards", "operationRetries"], async (tx) => { const tipRecord = await tx.rewards.get(tipId); @@ -339,7 +339,7 @@ export async function getTransactionById( case TransactionType.Deposit: { const depositGroupId = parsedTx.depositGroupId; - return await ws.db.runReadWriteTx( + return await wex.db.runReadWriteTx( ["depositGroups", "operationRetries"], async (tx) => { const depositRecord = await tx.depositGroups.get(depositGroupId); @@ -354,7 +354,7 @@ export async function getTransactionById( } case TransactionType.Refund: { - return await ws.db.runReadOnlyTx( + return await wex.db.runReadOnlyTx( ["refundGroups", "purchases", "operationRetries", "contractTerms"], async (tx) => { const refundRecord = await tx.refundGroups.get( @@ -372,7 +372,7 @@ export async function getTransactionById( ); } case TransactionType.PeerPullDebit: { - return await ws.db.runReadWriteTx( + return await wex.db.runReadWriteTx( ["peerPullDebit", "contractTerms"], async (tx) => { const debit = await tx.peerPullDebit.get(parsedTx.peerPullDebitId); @@ -391,7 +391,7 @@ export async function getTransactionById( } case TransactionType.PeerPushDebit: { - return await ws.db.runReadWriteTx( + return await wex.db.runReadWriteTx( ["peerPushDebit", "contractTerms"], async (tx) => { const debit = await tx.peerPushDebit.get(parsedTx.pursePub); @@ -408,7 +408,7 @@ export async function getTransactionById( case TransactionType.PeerPushCredit: { const peerPushCreditId = parsedTx.peerPushCreditId; - return await ws.db.runReadWriteTx( + return await wex.db.runReadWriteTx( [ "peerPushCredit", "contractTerms", @@ -446,7 +446,7 @@ export async function getTransactionById( case TransactionType.PeerPullCredit: { const pursePub = parsedTx.pursePub; - return await ws.db.runReadWriteTx( + return await wex.db.runReadWriteTx( [ "peerPullCredit", "contractTerms", @@ -1039,10 +1039,10 @@ async function buildTransactionForPurchase( } export async function getWithdrawalTransactionByUri( - ws: InternalWalletState, + wex: WalletExecutionContext, request: WithdrawalTransactionByURIRequest, ): Promise<TransactionWithdrawal | undefined> { - return await ws.db.runReadWriteTx( + return await wex.db.runReadWriteTx( ["withdrawalGroups", "exchangeDetails", "exchanges", "operationRetries"], async (tx) => { const withdrawalGroupRecord = @@ -1085,7 +1085,7 @@ export async function getWithdrawalTransactionByUri( * Retrieve the full event history for this wallet. */ export async function getTransactions( - ws: InternalWalletState, + wex: WalletExecutionContext, transactionsRequest?: TransactionsRequest, ): Promise<TransactionsResponse> { const transactions: Transaction[] = []; @@ -1095,7 +1095,7 @@ export async function getTransactions( filter.onlyState = transactionsRequest.filterByState; } - await ws.db.runReadOnlyTx( + await wex.db.runReadOnlyTx( [ "coins", "denominations", @@ -1698,18 +1698,18 @@ function maybeTaskFromTransaction( * of a transaction. */ export async function retryTransaction( - ws: InternalWalletState, + wex: WalletExecutionContext, transactionId: string, ): Promise<void> { logger.info(`resetting retry timeout for ${transactionId}`); const taskId = maybeTaskFromTransaction(transactionId); if (taskId) { - ws.taskScheduler.resetTaskRetries(taskId); + wex.taskScheduler.resetTaskRetries(taskId); } } async function getContextForTransaction( - ws: InternalWalletState, + wex: WalletExecutionContext, transactionId: string, ): Promise<TransactionContext> { const tx = parseTransactionIdentifier(transactionId); @@ -1718,26 +1718,26 @@ async function getContextForTransaction( } switch (tx.tag) { case TransactionType.Deposit: - return new DepositTransactionContext(ws, tx.depositGroupId); + return new DepositTransactionContext(wex, tx.depositGroupId); case TransactionType.Refresh: - return new RefreshTransactionContext(ws, tx.refreshGroupId); + return new RefreshTransactionContext(wex, tx.refreshGroupId); case TransactionType.InternalWithdrawal: case TransactionType.Withdrawal: - return new WithdrawTransactionContext(ws, tx.withdrawalGroupId); + return new WithdrawTransactionContext(wex, tx.withdrawalGroupId); case TransactionType.Payment: - return new PayMerchantTransactionContext(ws, tx.proposalId); + return new PayMerchantTransactionContext(wex, tx.proposalId); case TransactionType.PeerPullCredit: - return new PeerPullCreditTransactionContext(ws, tx.pursePub); + return new PeerPullCreditTransactionContext(wex, tx.pursePub); case TransactionType.PeerPushDebit: - return new PeerPushDebitTransactionContext(ws, tx.pursePub); + return new PeerPushDebitTransactionContext(wex, tx.pursePub); case TransactionType.PeerPullDebit: - return new PeerPullDebitTransactionContext(ws, tx.peerPullDebitId); + return new PeerPullDebitTransactionContext(wex, tx.peerPullDebitId); case TransactionType.PeerPushCredit: - return new PeerPushCreditTransactionContext(ws, tx.peerPushCreditId); + return new PeerPushCreditTransactionContext(wex, tx.peerPushCreditId); case TransactionType.Refund: - return new RefundTransactionContext(ws, tx.refundGroupId); + return new RefundTransactionContext(wex, tx.refundGroupId); case TransactionType.Reward: - return new RewardTransactionContext(ws, tx.walletRewardId); + return new RewardTransactionContext(wex, tx.walletRewardId); case TransactionType.Recoup: throw new Error("not yet supported"); //return new RecoupTransactionContext(ws, tx.recoupGroupId); @@ -1753,18 +1753,18 @@ async function getContextForTransaction( * to take longer (such as a backup, recovery or very large withdrawal operation). */ export async function suspendTransaction( - ws: InternalWalletState, + wex: WalletExecutionContext, transactionId: string, ): Promise<void> { - const ctx = await getContextForTransaction(ws, transactionId); + const ctx = await getContextForTransaction(wex, transactionId); await ctx.suspendTransaction(); } export async function failTransaction( - ws: InternalWalletState, + wex: WalletExecutionContext, transactionId: string, ): Promise<void> { - const ctx = await getContextForTransaction(ws, transactionId); + const ctx = await getContextForTransaction(wex, transactionId); await ctx.failTransaction(); } @@ -1772,10 +1772,10 @@ export async function failTransaction( * Resume a suspended transaction. */ export async function resumeTransaction( - ws: InternalWalletState, + wex: WalletExecutionContext, transactionId: string, ): Promise<void> { - const ctx = await getContextForTransaction(ws, transactionId); + const ctx = await getContextForTransaction(wex, transactionId); await ctx.resumeTransaction(); } @@ -1783,21 +1783,21 @@ export async function resumeTransaction( * Permanently delete a transaction based on the transaction ID. */ export async function deleteTransaction( - ws: InternalWalletState, + wex: WalletExecutionContext, transactionId: string, ): Promise<void> { - const ctx = await getContextForTransaction(ws, transactionId); + const ctx = await getContextForTransaction(wex, transactionId); await ctx.deleteTransaction(); if (ctx.taskId) { - ws.taskScheduler.stopShepherdTask(ctx.taskId); + wex.taskScheduler.stopShepherdTask(ctx.taskId); } } export async function abortTransaction( - ws: InternalWalletState, + wex: WalletExecutionContext, transactionId: string, ): Promise<void> { - const ctx = await getContextForTransaction(ws, transactionId); + const ctx = await getContextForTransaction(wex, transactionId); await ctx.abortTransaction(); } @@ -1810,7 +1810,7 @@ export interface TransitionInfo { * Notify of a state transition if necessary. */ export function notifyTransition( - ws: InternalWalletState, + wex: WalletExecutionContext, transactionId: string, transitionInfo: TransitionInfo | undefined, experimentalUserData: any = undefined, @@ -1822,7 +1822,7 @@ export function notifyTransition( transitionInfo.oldTxState.minor === transitionInfo.newTxState.minor ) ) { - ws.notify({ + wex.ws.notify({ type: NotificationType.TransactionStateTransition, oldTxState: transitionInfo.oldTxState, newTxState: transitionInfo.newTxState, diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index f21ba6ec1..236b27575 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -285,6 +285,7 @@ export interface WalletExecutionContext { readonly http: HttpRequestLibrary; readonly db: DbAccess<typeof WalletStoresV1>; readonly oc: ObservabilityContext; + readonly taskScheduler: TaskScheduler; } export const EXCHANGE_COINS_LOCK = "exchange-coins-lock"; @@ -299,16 +300,16 @@ type CancelFn = () => void; * auditors into the database, unless these defaults have * already been applied. */ -async function fillDefaults(ws: InternalWalletState): Promise<void> { +async function fillDefaults(wex: WalletExecutionContext): Promise<void> { const notifications: WalletNotification[] = []; - await ws.db.runReadWriteTx(["config", "exchanges"], async (tx) => { + await wex.db.runReadWriteTx(["config", "exchanges"], async (tx) => { const appliedRec = await tx.config.get("currencyDefaultsApplied"); let alreadyApplied = appliedRec ? !!appliedRec.value : false; if (alreadyApplied) { logger.trace("defaults already applied"); return; } - for (const exch of ws.config.builtin.exchanges) { + for (const exch of wex.ws.config.builtin.exchanges) { const resp = await addPresetExchangeEntry( tx, exch.exchangeBaseUrl, @@ -324,18 +325,18 @@ async function fillDefaults(ws: InternalWalletState): Promise<void> { }); }); for (const notif of notifications) { - ws.notify(notif); + wex.ws.notify(notif); } } export async function getDenomInfo( - ws: InternalWalletState, + wex: WalletExecutionContext, tx: WalletDbReadOnlyTransaction<["denominations"]>, exchangeBaseUrl: string, denomPubHash: string, ): Promise<DenominationInfo | undefined> { const key = `${exchangeBaseUrl}:${denomPubHash}`; - const cached = ws.denomCache[key]; + const cached = wex.ws.denomCache[key]; if (cached) { return cached; } @@ -351,11 +352,11 @@ export async function getDenomInfo( * previous withdrawals. */ async function listKnownBankAccounts( - ws: InternalWalletState, + wex: WalletExecutionContext, currency?: string, ): Promise<KnownBankAccounts> { const accounts: KnownBankAccountsInfo[] = []; - await ws.db.runReadOnlyTx(["bankAccounts"], async (tx) => { + await wex.db.runReadOnlyTx(["bankAccounts"], async (tx) => { const knownAccounts = await tx.bankAccounts.iter().toArray(); for (const r of knownAccounts) { if (currency && currency !== r.currency) { @@ -378,12 +379,12 @@ async function listKnownBankAccounts( /** */ async function addKnownBankAccounts( - ws: InternalWalletState, + wex: WalletExecutionContext, payto: string, alias: string, currency: string, ): Promise<void> { - await ws.db.runReadWriteTx(["bankAccounts"], async (tx) => { + await wex.db.runReadWriteTx(["bankAccounts"], async (tx) => { tx.bankAccounts.put({ uri: payto, alias: alias, @@ -397,10 +398,10 @@ async function addKnownBankAccounts( /** */ async function forgetKnownBankAccounts( - ws: InternalWalletState, + wex: WalletExecutionContext, payto: string, ): Promise<void> { - await ws.db.runReadWriteTx(["bankAccounts"], async (tx) => { + await wex.db.runReadWriteTx(["bankAccounts"], async (tx) => { const account = await tx.bankAccounts.get(payto); if (!account) { throw Error(`account not found: ${payto}`); @@ -411,11 +412,11 @@ async function forgetKnownBankAccounts( } async function setCoinSuspended( - ws: InternalWalletState, + wex: WalletExecutionContext, coinPub: string, suspended: boolean, ): Promise<void> { - await ws.db.runReadWriteTx(["coins", "coinAvailability"], async (tx) => { + await wex.db.runReadWriteTx(["coins", "coinAvailability"], async (tx) => { const c = await tx.coins.get(coinPub); if (!c) { logger.warn(`coin ${coinPub} not found, won't suspend`); @@ -453,10 +454,10 @@ async function setCoinSuspended( /** * Dump the public information of coins we have in an easy-to-process format. */ -async function dumpCoins(ws: InternalWalletState): Promise<CoinDumpJson> { +async function dumpCoins(wex: WalletExecutionContext): Promise<CoinDumpJson> { const coinsJson: CoinDumpJson = { coins: [] }; logger.info("dumping coins"); - await ws.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { + await wex.db.runReadOnlyTx(["coins", "denominations"], async (tx) => { const coins = await tx.coins.iter().toArray(); for (const c of coins) { const denom = await tx.denominations.get([ @@ -477,7 +478,7 @@ async function dumpCoins(ws: InternalWalletState): Promise<CoinDumpJson> { withdrawalReservePub = cs.reservePub; } const denomInfo = await getDenomInfo( - ws, + wex, tx, c.exchangeBaseUrl, c.denomPubHash, @@ -530,10 +531,10 @@ async function getClientFromWalletState( } async function createStoredBackup( - ws: InternalWalletState, + wex: WalletExecutionContext, ): Promise<CreateStoredBackupResponse> { - const backup = await exportDb(ws.idb); - const backupsDb = await openStoredBackupsDatabase(ws.idb); + const backup = await exportDb(wex.ws.idb); + const backupsDb = await openStoredBackupsDatabase(wex.ws.idb); const name = `backup-${new Date().getTime()}`; await backupsDb.runAllStoresReadWriteTx(async (tx) => { await tx.backupMeta.add({ @@ -547,12 +548,12 @@ async function createStoredBackup( } async function listStoredBackups( - ws: InternalWalletState, + wex: WalletExecutionContext, ): Promise<StoredBackupList> { const storedBackups: StoredBackupList = { storedBackups: [], }; - const backupsDb = await openStoredBackupsDatabase(ws.idb); + const backupsDb = await openStoredBackupsDatabase(wex.ws.idb); await backupsDb.runAllStoresReadWriteTx(async (tx) => { await tx.backupMeta.iter().forEach((x) => { storedBackups.storedBackups.push({ @@ -564,10 +565,10 @@ async function listStoredBackups( } async function deleteStoredBackup( - ws: InternalWalletState, + wex: WalletExecutionContext, req: DeleteStoredBackupRequest, ): Promise<void> { - const backupsDb = await openStoredBackupsDatabase(ws.idb); + const backupsDb = await openStoredBackupsDatabase(wex.ws.idb); await backupsDb.runAllStoresReadWriteTx(async (tx) => { await tx.backupData.delete(req.name); await tx.backupMeta.delete(req.name); @@ -575,12 +576,12 @@ async function deleteStoredBackup( } async function recoverStoredBackup( - ws: InternalWalletState, + wex: WalletExecutionContext, req: RecoverStoredBackupRequest, ): Promise<void> { logger.info(`Recovering stored backup ${req.name}`); const { name } = req; - const backupsDb = await openStoredBackupsDatabase(ws.idb); + const backupsDb = await openStoredBackupsDatabase(wex.ws.idb); const bd = await backupsDb.runAllStoresReadWriteTx(async (tx) => { const backupMeta = tx.backupMeta.get(name); if (!backupMeta) { @@ -593,12 +594,12 @@ async function recoverStoredBackup( return backupData; }); logger.info(`backup found, now importing`); - await importDb(ws.db.idbHandle(), bd); + await importDb(wex.db.idbHandle(), bd); logger.info(`import done`); } async function handlePrepareWithdrawExchange( - ws: InternalWalletState, + wex: WalletExecutionContext, req: PrepareWithdrawExchangeRequest, ): Promise<PrepareWithdrawExchangeResponse> { const parsedUri = parseTalerUri(req.talerUri); @@ -606,7 +607,7 @@ async function handlePrepareWithdrawExchange( throw Error("expected a taler://withdraw-exchange URI"); } const exchangeBaseUrl = parsedUri.exchangeBaseUrl; - const exchange = await fetchFreshExchange(ws, exchangeBaseUrl); + const exchange = await fetchFreshExchange(wex, exchangeBaseUrl); if (exchange.masterPub != parsedUri.exchangePub) { throw Error("mismatch of exchange master public key (URI vs actual)"); } @@ -638,11 +639,11 @@ export interface PendingOperationsResponse { * Implementation of the "wallet-core" API. */ async function dispatchRequestInternal<Op extends WalletApiOperation>( - ws: InternalWalletState, + wex: WalletExecutionContext, operation: WalletApiOperation, payload: unknown, ): Promise<WalletCoreResponseType<typeof operation>> { - if (!ws.initCalled && operation !== WalletApiOperation.InitWallet) { + if (!wex.ws.initCalled && operation !== WalletApiOperation.InitWallet) { throw Error( `wallet must be initialized before running operation ${operation}`, ); @@ -651,17 +652,17 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( // definitions we already have? switch (operation) { case WalletApiOperation.CreateStoredBackup: - return createStoredBackup(ws); + return createStoredBackup(wex); case WalletApiOperation.DeleteStoredBackup: { const req = codecForDeleteStoredBackupRequest().decode(payload); - await deleteStoredBackup(ws, req); + await deleteStoredBackup(wex, req); return {}; } case WalletApiOperation.ListStoredBackups: - return listStoredBackups(ws); + return listStoredBackups(wex); case WalletApiOperation.RecoverStoredBackup: { const req = codecForRecoverStoredBackupRequest().decode(payload); - await recoverStoredBackup(ws, req); + await recoverStoredBackup(wex, req); return {}; } case WalletApiOperation.InitWallet: { @@ -669,7 +670,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( // Write to the DB to make sure that we're failing early in // case the DB is not writeable. try { - await ws.db.runReadWriteTx(["config"], async (tx) => { + await wex.db.runReadWriteTx(["config"], async (tx) => { tx.config.put({ key: ConfigRecordKey.LastInitInfo, value: timestampProtocolToDb(TalerProtocolTimestamp.now()), @@ -681,41 +682,41 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( innerError: getErrorDetailFromException(e), }); } - ws.initCalled = true; - if (ws.config.testing.skipDefaults) { + wex.ws.initCalled = true; + if (wex.ws.config.testing.skipDefaults) { logger.trace("skipping defaults"); } else { logger.trace("filling defaults"); - await fillDefaults(ws); + await fillDefaults(wex); } const resp: InitResponse = { - versionInfo: getVersion(ws), + versionInfo: getVersion(wex), }; return resp; } case WalletApiOperation.WithdrawTestkudos: { - await withdrawTestBalance(ws, { + await withdrawTestBalance(wex, { amount: "TESTKUDOS:10" as AmountString, corebankApiBaseUrl: "https://bank.test.taler.net/", exchangeBaseUrl: "https://exchange.test.taler.net/", }); return { - versionInfo: getVersion(ws), + versionInfo: getVersion(wex), }; } case WalletApiOperation.WithdrawTestBalance: { const req = codecForWithdrawTestBalance().decode(payload); - await withdrawTestBalance(ws, req); + await withdrawTestBalance(wex, req); return {}; } case WalletApiOperation.RunIntegrationTest: { const req = codecForIntegrationTestArgs().decode(payload); - await runIntegrationTest(ws, req); + await runIntegrationTest(wex, req); return {}; } case WalletApiOperation.RunIntegrationTestV2: { const req = codecForIntegrationTestV2Args().decode(payload); - await runIntegrationTest2(ws, req); + await runIntegrationTest2(wex, req); return {}; } case WalletApiOperation.ValidateIban: { @@ -728,45 +729,45 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( } case WalletApiOperation.TestPay: { const req = codecForTestPayArgs().decode(payload); - return await testPay(ws, req); + return await testPay(wex, req); } case WalletApiOperation.GetTransactions: { const req = codecForTransactionsRequest().decode(payload); - return await getTransactions(ws, req); + return await getTransactions(wex, req); } case WalletApiOperation.GetTransactionById: { const req = codecForTransactionByIdRequest().decode(payload); - return await getTransactionById(ws, req); + return await getTransactionById(wex, req); } case WalletApiOperation.GetWithdrawalTransactionByUri: { const req = codecForGetWithdrawalDetailsForUri().decode(payload); - return await getWithdrawalTransactionByUri(ws, req); + return await getWithdrawalTransactionByUri(wex, req); } case WalletApiOperation.AddExchange: { const req = codecForAddExchangeRequest().decode(payload); - await fetchFreshExchange(ws, req.exchangeBaseUrl, { + await fetchFreshExchange(wex, req.exchangeBaseUrl, { expectedMasterPub: req.masterPub, }); return {}; } case WalletApiOperation.UpdateExchangeEntry: { const req = codecForUpdateExchangeEntryRequest().decode(payload); - await fetchFreshExchange(ws, req.exchangeBaseUrl, { + await fetchFreshExchange(wex, req.exchangeBaseUrl, { forceUpdate: !!req.force, }); return {}; } case WalletApiOperation.ListExchanges: { - return await listExchanges(ws); + return await listExchanges(wex); } case WalletApiOperation.GetExchangeEntryByUrl: { const req = codecForGetExchangeEntryByUrlRequest().decode(payload); - return lookupExchangeByUri(ws, req); + return lookupExchangeByUri(wex, req); } case WalletApiOperation.ListExchangesForScopedCurrency: { const req = codecForListExchangesForScopedCurrencyRequest().decode(payload); - const exchangesResp = await listExchanges(ws); + const exchangesResp = await listExchanges(wex); const result: ExchangesShortListResponse = { exchanges: [], }; @@ -783,32 +784,32 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( } case WalletApiOperation.GetExchangeDetailedInfo: { const req = codecForAddExchangeRequest().decode(payload); - return await getExchangeDetailedInfo(ws, req.exchangeBaseUrl); + return await getExchangeDetailedInfo(wex, req.exchangeBaseUrl); } case WalletApiOperation.ListKnownBankAccounts: { const req = codecForListKnownBankAccounts().decode(payload); - return await listKnownBankAccounts(ws, req.currency); + return await listKnownBankAccounts(wex, req.currency); } case WalletApiOperation.AddKnownBankAccounts: { const req = codecForAddKnownBankAccounts().decode(payload); - await addKnownBankAccounts(ws, req.payto, req.alias, req.currency); + await addKnownBankAccounts(wex, req.payto, req.alias, req.currency); return {}; } case WalletApiOperation.ForgetKnownBankAccounts: { const req = codecForForgetKnownBankAccounts().decode(payload); - await forgetKnownBankAccounts(ws, req.payto); + await forgetKnownBankAccounts(wex, req.payto); return {}; } case WalletApiOperation.GetWithdrawalDetailsForUri: { const req = codecForGetWithdrawalDetailsForUri().decode(payload); - return await getWithdrawalDetailsForUri(ws, req.talerWithdrawUri, { + return await getWithdrawalDetailsForUri(wex, req.talerWithdrawUri, { notifyChangeFromPendingTimeoutMs: req.notifyChangeFromPendingTimeoutMs, restrictAge: req.restrictAge, }); } case WalletApiOperation.AcceptManualWithdrawal: { const req = codecForAcceptManualWithdrawalRequet().decode(payload); - const res = await createManualWithdrawal(ws, { + const res = await createManualWithdrawal(wex, { amount: Amounts.parseOrThrow(req.amount), exchangeBaseUrl: req.exchangeBaseUrl, restrictAge: req.restrictAge, @@ -819,7 +820,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( const req = codecForGetWithdrawalDetailsForAmountRequest().decode(payload); const wi = await getExchangeWithdrawalInfo( - ws, + wex, req.exchangeBaseUrl, Amounts.parseOrThrow(req.amount), req.restrictAge, @@ -843,23 +844,23 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( return resp; } case WalletApiOperation.GetBalances: { - return await getBalances(ws); + return await getBalances(wex); } case WalletApiOperation.GetBalanceDetail: { const req = codecForGetBalanceDetailRequest().decode(payload); - return await getBalanceDetail(ws, req); + return await getBalanceDetail(wex, req); } case WalletApiOperation.GetUserAttentionRequests: { const req = codecForUserAttentionsRequest().decode(payload); - return await getUserAttentions(ws, req); + return await getUserAttentions(wex, req); } case WalletApiOperation.MarkAttentionRequestAsRead: { const req = codecForUserAttentionByIdRequest().decode(payload); - return await markAttentionRequestAsRead(ws, req); + return await markAttentionRequestAsRead(wex, req); } case WalletApiOperation.GetUserAttentionUnreadCount: { const req = codecForUserAttentionsRequest().decode(payload); - return await getUserAttentionsUnreadCount(ws, req); + return await getUserAttentionsUnreadCount(wex, req); } case WalletApiOperation.GetPendingOperations: { // FIXME: Eventually remove the handler after deprecation period. @@ -869,7 +870,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( } case WalletApiOperation.SetExchangeTosAccepted: { const req = codecForAcceptExchangeTosRequest().decode(payload); - await acceptExchangeTermsOfService(ws, req.exchangeBaseUrl); + await acceptExchangeTermsOfService(wex, req.exchangeBaseUrl); return {}; } case WalletApiOperation.SetExchangeTosForgotten: { @@ -880,7 +881,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( case WalletApiOperation.AcceptBankIntegratedWithdrawal: { const req = codecForAcceptBankIntegratedWithdrawalRequest().decode(payload); - return await acceptWithdrawalFromUri(ws, { + return await acceptWithdrawalFromUri(wex, { selectedExchange: req.exchangeBaseUrl, talerWithdrawUri: req.talerWithdrawUri, forcedDenomSel: req.forcedDenomSel, @@ -890,7 +891,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( case WalletApiOperation.GetExchangeTos: { const req = codecForGetExchangeTosRequest().decode(payload); return getExchangeTos( - ws, + wex, req.exchangeBaseUrl, req.acceptedFormat, req.acceptLanguage, @@ -898,7 +899,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( } case WalletApiOperation.GetContractTermsDetails: { const req = codecForGetContractTermsDetails().decode(payload); - return getContractTermsDetails(ws, req.proposalId); + return getContractTermsDetails(wex, req.proposalId); } case WalletApiOperation.RetryPendingNow: { logger.error("retryPendingNow currently not implemented"); @@ -906,19 +907,19 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( } case WalletApiOperation.SharePayment: { const req = codecForSharePaymentRequest().decode(payload); - return await sharePayment(ws, req.merchantBaseUrl, req.orderId); + return await sharePayment(wex, req.merchantBaseUrl, req.orderId); } case WalletApiOperation.PrepareWithdrawExchange: { const req = codecForPrepareWithdrawExchangeRequest().decode(payload); - return handlePrepareWithdrawExchange(ws, req); + return handlePrepareWithdrawExchange(wex, req); } case WalletApiOperation.PreparePayForUri: { const req = codecForPreparePayRequest().decode(payload); - return await preparePayForUri(ws, req.talerPayUri); + return await preparePayForUri(wex, req.talerPayUri); } case WalletApiOperation.PreparePayForTemplate: { const req = codecForPreparePayTemplateRequest().decode(payload); - return preparePayForTemplate(ws, req); + return preparePayForTemplate(wex, req); } case WalletApiOperation.ConfirmPay: { const req = codecForConfirmPayRequest().decode(payload); @@ -934,45 +935,45 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( } else { throw Error("transactionId or (deprecated) proposalId required"); } - return await confirmPay(ws, transactionId, req.sessionId); + return await confirmPay(wex, transactionId, req.sessionId); } case WalletApiOperation.AbortTransaction: { const req = codecForAbortTransaction().decode(payload); - await abortTransaction(ws, req.transactionId); + await abortTransaction(wex, req.transactionId); return {}; } case WalletApiOperation.SuspendTransaction: { const req = codecForSuspendTransaction().decode(payload); - await suspendTransaction(ws, req.transactionId); + await suspendTransaction(wex, req.transactionId); return {}; } case WalletApiOperation.FailTransaction: { const req = codecForFailTransactionRequest().decode(payload); - await failTransaction(ws, req.transactionId); + await failTransaction(wex, req.transactionId); return {}; } case WalletApiOperation.ResumeTransaction: { const req = codecForResumeTransaction().decode(payload); - await resumeTransaction(ws, req.transactionId); + await resumeTransaction(wex, req.transactionId); return {}; } case WalletApiOperation.DumpCoins: { - return await dumpCoins(ws); + return await dumpCoins(wex); } case WalletApiOperation.SetCoinSuspended: { const req = codecForSetCoinSuspendedRequest().decode(payload); - await setCoinSuspended(ws, req.coinPub, req.suspended); + await setCoinSuspended(wex, req.coinPub, req.suspended); return {}; } case WalletApiOperation.TestingGetSampleTransactions: return { transactions: sampleWalletCoreTransactions }; case WalletApiOperation.ForceRefresh: { const req = codecForForceRefreshRequest().decode(payload); - return await forceRefresh(ws, req); + return await forceRefresh(wex, req); } case WalletApiOperation.StartRefundQueryForUri: { const req = codecForPrepareRefundRequest().decode(payload); - return await startRefundQueryForUri(ws, req.talerRefundUri); + return await startRefundQueryForUri(wex, req.talerRefundUri); } case WalletApiOperation.StartRefundQuery: { const req = codecForStartRefundQueryRequest().decode(payload); @@ -983,30 +984,30 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( if (txIdParsed.tag !== TransactionType.Payment) { throw Error("expected payment transaction ID"); } - await startQueryRefund(ws, txIdParsed.proposalId); + await startQueryRefund(wex, txIdParsed.proposalId); return {}; } case WalletApiOperation.AddBackupProvider: { const req = codecForAddBackupProviderRequest().decode(payload); - return await addBackupProvider(ws, req); + return await addBackupProvider(wex, req); } case WalletApiOperation.RunBackupCycle: { const req = codecForRunBackupCycle().decode(payload); - await runBackupCycle(ws, req); + await runBackupCycle(wex, req); return {}; } case WalletApiOperation.RemoveBackupProvider: { const req = codecForRemoveBackupProvider().decode(payload); - await removeBackupProvider(ws, req); + await removeBackupProvider(wex, req); return {}; } case WalletApiOperation.ExportBackupRecovery: { - const resp = await getBackupRecovery(ws); + const resp = await getBackupRecovery(wex); return resp; } case WalletApiOperation.TestingWaitTransactionState: { const req = payload as TestingWaitTransactionRequest; - await waitTransactionState(ws, req.transactionId, req.txState); + await waitTransactionState(wex, req.transactionId, req.txState); return {}; } case WalletApiOperation.GetCurrencySpecification: { @@ -1055,7 +1056,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( } case WalletApiOperation.ImportBackupRecovery: { const req = codecForAny().decode(payload); - await loadBackupRecovery(ws, req); + await loadBackupRecovery(wex, req); return {}; } // case WalletApiOperation.GetPlanForOperation: { @@ -1064,31 +1065,31 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( // } case WalletApiOperation.ConvertDepositAmount: { const req = codecForConvertAmountRequest.decode(payload); - return await convertDepositAmount(ws, req); + return await convertDepositAmount(wex, req); } case WalletApiOperation.GetMaxDepositAmount: { const req = codecForGetAmountRequest.decode(payload); - return await getMaxDepositAmount(ws, req); + return await getMaxDepositAmount(wex, req); } case WalletApiOperation.ConvertPeerPushAmount: { const req = codecForConvertAmountRequest.decode(payload); - return await convertPeerPushAmount(ws, req); + return await convertPeerPushAmount(wex, req); } case WalletApiOperation.GetMaxPeerPushAmount: { const req = codecForGetAmountRequest.decode(payload); - return await getMaxPeerPushAmount(ws, req); + return await getMaxPeerPushAmount(wex, req); } case WalletApiOperation.ConvertWithdrawalAmount: { const req = codecForConvertAmountRequest.decode(payload); - return await convertWithdrawalAmount(ws, req); + return await convertWithdrawalAmount(wex, req); } case WalletApiOperation.GetBackupInfo: { - const resp = await getBackupInfo(ws); + const resp = await getBackupInfo(wex); return resp; } case WalletApiOperation.PrepareDeposit: { const req = codecForPrepareDepositRequest().decode(payload); - return await prepareDepositGroup(ws, req); + return await prepareDepositGroup(wex, req); } case WalletApiOperation.GenerateDepositGroupTxId: return { @@ -1096,42 +1097,42 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( }; case WalletApiOperation.CreateDepositGroup: { const req = codecForCreateDepositGroupRequest().decode(payload); - return await createDepositGroup(ws, req); + return await createDepositGroup(wex, req); } case WalletApiOperation.DeleteTransaction: { const req = codecForDeleteTransactionRequest().decode(payload); - await deleteTransaction(ws, req.transactionId); + await deleteTransaction(wex, req.transactionId); return {}; } case WalletApiOperation.RetryTransaction: { const req = codecForRetryTransactionRequest().decode(payload); - await retryTransaction(ws, req.transactionId); + await retryTransaction(wex, req.transactionId); return {}; } case WalletApiOperation.SetWalletDeviceId: { const req = codecForSetWalletDeviceIdRequest().decode(payload); - await setWalletDeviceId(ws, req.walletDeviceId); + await setWalletDeviceId(wex, req.walletDeviceId); return {}; } case WalletApiOperation.TestCrypto: { - return await ws.cryptoApi.hashString({ str: "hello world" }); + return await wex.cryptoApi.hashString({ str: "hello world" }); } case WalletApiOperation.ClearDb: - await clearDatabase(ws.db.idbHandle()); + await clearDatabase(wex.db.idbHandle()); return {}; case WalletApiOperation.Recycle: { throw Error("not implemented"); return {}; } case WalletApiOperation.ExportDb: { - const dbDump = await exportDb(ws.idb); + const dbDump = await exportDb(wex.ws.idb); return dbDump; } case WalletApiOperation.ListGlobalCurrencyExchanges: { const resp: ListGlobalCurrencyExchangesResponse = { exchanges: [], }; - await ws.db.runReadOnlyTx(["globalCurrencyExchanges"], async (tx) => { + await wex.db.runReadOnlyTx(["globalCurrencyExchanges"], async (tx) => { const gceList = await tx.globalCurrencyExchanges.iter().toArray(); for (const gce of gceList) { resp.exchanges.push({ @@ -1147,7 +1148,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( const resp: ListGlobalCurrencyAuditorsResponse = { auditors: [], }; - await ws.db.runReadOnlyTx(["globalCurrencyAuditors"], async (tx) => { + await wex.db.runReadOnlyTx(["globalCurrencyAuditors"], async (tx) => { const gcaList = await tx.globalCurrencyAuditors.iter().toArray(); for (const gca of gcaList) { resp.auditors.push({ @@ -1161,7 +1162,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( } case WalletApiOperation.AddGlobalCurrencyExchange: { const req = codecForAddGlobalCurrencyExchangeRequest().decode(payload); - await ws.db.runReadWriteTx(["globalCurrencyExchanges"], async (tx) => { + await wex.db.runReadWriteTx(["globalCurrencyExchanges"], async (tx) => { const key = [req.currency, req.exchangeBaseUrl, req.exchangeMasterPub]; const existingRec = await tx.globalCurrencyExchanges.indexes.byCurrencyAndUrlAndPub.get( @@ -1180,7 +1181,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( } case WalletApiOperation.RemoveGlobalCurrencyExchange: { const req = codecForRemoveGlobalCurrencyExchangeRequest().decode(payload); - await ws.db.runReadWriteTx(["globalCurrencyExchanges"], async (tx) => { + await wex.db.runReadWriteTx(["globalCurrencyExchanges"], async (tx) => { const key = [req.currency, req.exchangeBaseUrl, req.exchangeMasterPub]; const existingRec = await tx.globalCurrencyExchanges.indexes.byCurrencyAndUrlAndPub.get( @@ -1196,7 +1197,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( } case WalletApiOperation.AddGlobalCurrencyAuditor: { const req = codecForAddGlobalCurrencyAuditorRequest().decode(payload); - await ws.db.runReadWriteTx(["globalCurrencyAuditors"], async (tx) => { + await wex.db.runReadWriteTx(["globalCurrencyAuditors"], async (tx) => { const key = [req.currency, req.auditorBaseUrl, req.auditorPub]; const existingRec = await tx.globalCurrencyAuditors.indexes.byCurrencyAndUrlAndPub.get( @@ -1215,7 +1216,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( } case WalletApiOperation.RemoveGlobalCurrencyAuditor: { const req = codecForRemoveGlobalCurrencyAuditorRequest().decode(payload); - await ws.db.runReadWriteTx(["globalCurrencyAuditors"], async (tx) => { + await wex.db.runReadWriteTx(["globalCurrencyAuditors"], async (tx) => { const key = [req.currency, req.auditorBaseUrl, req.auditorPub]; const existingRec = await tx.globalCurrencyAuditors.indexes.byCurrencyAndUrlAndPub.get( @@ -1231,67 +1232,67 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( } case WalletApiOperation.ImportDb: { const req = codecForImportDbRequest().decode(payload); - await importDb(ws.db.idbHandle(), req.dump); + await importDb(wex.db.idbHandle(), req.dump); return []; } case WalletApiOperation.CheckPeerPushDebit: { const req = codecForCheckPeerPushDebitRequest().decode(payload); - return await checkPeerPushDebit(ws, req); + return await checkPeerPushDebit(wex, req); } case WalletApiOperation.InitiatePeerPushDebit: { const req = codecForInitiatePeerPushDebitRequest().decode(payload); - return await initiatePeerPushDebit(ws, req); + return await initiatePeerPushDebit(wex, req); } case WalletApiOperation.PreparePeerPushCredit: { const req = codecForPreparePeerPushCreditRequest().decode(payload); - return await preparePeerPushCredit(ws, req); + return await preparePeerPushCredit(wex, req); } case WalletApiOperation.ConfirmPeerPushCredit: { const req = codecForConfirmPeerPushPaymentRequest().decode(payload); - return await confirmPeerPushCredit(ws, req); + return await confirmPeerPushCredit(wex, req); } case WalletApiOperation.CheckPeerPullCredit: { const req = codecForPreparePeerPullPaymentRequest().decode(payload); - return await checkPeerPullPaymentInitiation(ws, req); + return await checkPeerPullPaymentInitiation(wex, req); } case WalletApiOperation.InitiatePeerPullCredit: { const req = codecForInitiatePeerPullPaymentRequest().decode(payload); - return await initiatePeerPullPayment(ws, req); + return await initiatePeerPullPayment(wex, req); } case WalletApiOperation.PreparePeerPullDebit: { const req = codecForCheckPeerPullPaymentRequest().decode(payload); - return await preparePeerPullDebit(ws, req); + return await preparePeerPullDebit(wex, req); } case WalletApiOperation.ConfirmPeerPullDebit: { const req = codecForAcceptPeerPullPaymentRequest().decode(payload); - return await confirmPeerPullDebit(ws, req); + return await confirmPeerPullDebit(wex, req); } case WalletApiOperation.ApplyDevExperiment: { const req = codecForApplyDevExperiment().decode(payload); - await applyDevExperiment(ws, req.devExperimentUri); + await applyDevExperiment(wex, req.devExperimentUri); return {}; } case WalletApiOperation.GetVersion: { - return getVersion(ws); + return getVersion(wex); } case WalletApiOperation.TestingWaitTransactionsFinal: - return await waitUntilAllTransactionsFinal(ws); + return await waitUntilAllTransactionsFinal(wex); case WalletApiOperation.TestingWaitRefreshesFinal: - return await waitUntilRefreshesDone(ws); + return await waitUntilRefreshesDone(wex); case WalletApiOperation.TestingSetTimetravel: { const req = codecForTestingSetTimetravelRequest().decode(payload); setDangerousTimetravel(req.offsetMs); - ws.taskScheduler.reload(); + wex.taskScheduler.reload(); return {}; } case WalletApiOperation.DeleteExchange: { const req = codecForDeleteExchangeRequest().decode(payload); - await deleteExchange(ws, req); + await deleteExchange(wex, req); return {}; } case WalletApiOperation.GetExchangeResources: { const req = codecForGetExchangeResourcesRequest().decode(payload); - return await getExchangeResources(ws, req.exchangeBaseUrl); + return await getExchangeResources(wex, req.exchangeBaseUrl); } case WalletApiOperation.TestingInfiniteTransactionLoop: { const myDelayMs = (payload as any).delayMs ?? 5; @@ -1301,7 +1302,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( const url = "https://exchange.demo.taler.net/reserves/01PMMB9PJN0QBWAFBXV6R0KNJJMAKXCV4D6FDG0GJFDJQXGYP32G?timeout_ms=30000"; logger.info(`fetching ${url}`); - const res = await ws.http.fetch(url); + const res = await wex.http.fetch(url); logger.info(`fetch result ${res.status}`); } }; @@ -1312,7 +1313,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( let loopCount = 0; while (true) { logger.info(`looping test write tx, iteration ${loopCount}`); - await ws.db.runReadWriteTx(["config"], async (tx) => { + await wex.db.runReadWriteTx(["config"], async (tx) => { await tx.config.put({ key: ConfigRecordKey.TestLoopTx, value: loopCount, @@ -1338,7 +1339,7 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>( ); } -export function getVersion(ws: InternalWalletState): WalletCoreVersion { +export function getVersion(wex: WalletExecutionContext): WalletCoreVersion { const result: WalletCoreVersion = { implementationSemver: walletCoreBuildInfo.implementationSemver, implementationGitHash: walletCoreBuildInfo.implementationGitHash, @@ -1364,9 +1365,21 @@ async function handleCoreApiRequest( id: string, payload: unknown, ): Promise<CoreApiResponse> { + const wex: WalletExecutionContext = { + ws, + cancellationToken: CancellationToken.CONTINUE, + cryptoApi: ws.cryptoApi, + db: ws.db, + http: ws.http, + taskScheduler: ws.taskScheduler, + oc: { + observe(event) {}, + }, + }; + try { await ws.ensureWalletDbOpen(); - const result = await dispatchRequestInternal(ws, operation as any, payload); + const result = await dispatchRequestInternal(wex, operation as any, payload); return { type: "response", operation, diff --git a/packages/taler-wallet-core/src/withdraw.ts b/packages/taler-wallet-core/src/withdraw.ts index 541525c6b..44f1ee4f9 100644 --- a/packages/taler-wallet-core/src/withdraw.ts +++ b/packages/taler-wallet-core/src/withdraw.ts @@ -146,7 +146,11 @@ import { WALLET_BANK_INTEGRATION_PROTOCOL_VERSION, WALLET_EXCHANGE_PROTOCOL_VERSION, } from "./versions.js"; -import { getDenomInfo, type InternalWalletState } from "./wallet.js"; +import { + WalletExecutionContext, + getDenomInfo, + type InternalWalletState, +} from "./wallet.js"; /** * Logger for this file. @@ -158,7 +162,7 @@ export class WithdrawTransactionContext implements TransactionContext { readonly taskId: TaskIdStr; constructor( - public ws: InternalWalletState, + public wex: WalletExecutionContext, public withdrawalGroupId: string, ) { this.transactionId = constructTransactionIdentifier({ @@ -172,7 +176,7 @@ export class WithdrawTransactionContext implements TransactionContext { } async deleteTransaction(): Promise<void> { - const { ws, withdrawalGroupId } = this; + const { wex: ws, withdrawalGroupId } = this; await ws.db.runReadWriteTx( ["withdrawalGroups", "tombstones"], async (tx) => { @@ -190,8 +194,8 @@ export class WithdrawTransactionContext implements TransactionContext { } async suspendTransaction(): Promise<void> { - const { ws, withdrawalGroupId, transactionId, taskId } = this; - const transitionInfo = await ws.db.runReadWriteTx( + const { wex, withdrawalGroupId, transactionId, taskId } = this; + const transitionInfo = await wex.db.runReadWriteTx( ["withdrawalGroups"], async (tx) => { const wg = await tx.withdrawalGroups.get(withdrawalGroupId); @@ -240,12 +244,12 @@ export class WithdrawTransactionContext implements TransactionContext { return undefined; }, ); - ws.taskScheduler.stopShepherdTask(taskId); - notifyTransition(ws, transactionId, transitionInfo); + wex.taskScheduler.stopShepherdTask(taskId); + notifyTransition(wex, transactionId, transitionInfo); } async abortTransaction(): Promise<void> { - const { ws, withdrawalGroupId, transactionId, taskId } = this; + const { wex: ws, withdrawalGroupId, transactionId, taskId } = this; const transitionInfo = await ws.db.runReadWriteTx( ["withdrawalGroups"], async (tx) => { @@ -307,7 +311,12 @@ export class WithdrawTransactionContext implements TransactionContext { } async resumeTransaction(): Promise<void> { - const { ws, withdrawalGroupId, transactionId, taskId: retryTag } = this; + const { + wex: ws, + withdrawalGroupId, + transactionId, + taskId: retryTag, + } = this; const transitionInfo = await ws.db.runReadWriteTx( ["withdrawalGroups"], async (tx) => { @@ -362,7 +371,12 @@ export class WithdrawTransactionContext implements TransactionContext { } async failTransaction(): Promise<void> { - const { ws, withdrawalGroupId, transactionId, taskId: retryTag } = this; + const { + wex: ws, + withdrawalGroupId, + transactionId, + taskId: retryTag, + } = this; const stateUpdate = await ws.db.runReadWriteTx( ["withdrawalGroups"], async (tx) => { @@ -618,17 +632,17 @@ export async function getBankWithdrawalInfo( * Return denominations that can potentially used for a withdrawal. */ async function getCandidateWithdrawalDenoms( - ws: InternalWalletState, + wex: WalletExecutionContext, exchangeBaseUrl: string, currency: string, ): Promise<DenominationRecord[]> { - return await ws.db.runReadOnlyTx(["denominations"], async (tx) => { - return getCandidateWithdrawalDenomsTx(ws, tx, exchangeBaseUrl, currency); + return await wex.db.runReadOnlyTx(["denominations"], async (tx) => { + return getCandidateWithdrawalDenomsTx(wex, tx, exchangeBaseUrl, currency); }); } export async function getCandidateWithdrawalDenomsTx( - ws: InternalWalletState, + wex: WalletExecutionContext, tx: WalletDbReadOnlyTransaction<["denominations"]>, exchangeBaseUrl: string, currency: string, @@ -638,7 +652,9 @@ export async function getCandidateWithdrawalDenomsTx( await tx.denominations.indexes.byExchangeBaseUrl.getAll(exchangeBaseUrl); return allDenoms .filter((d) => d.currency === currency) - .filter((d) => isWithdrawableDenom(d, ws.config.testing.denomselAllowLate)); + .filter((d) => + isWithdrawableDenom(d, wex.ws.config.testing.denomselAllowLate), + ); } /** @@ -649,11 +665,11 @@ export async function getCandidateWithdrawalDenomsTx( * the exchange requests per reserve. */ async function processPlanchetGenerate( - ws: InternalWalletState, + wex: WalletExecutionContext, withdrawalGroup: WithdrawalGroupRecord, coinIdx: number, ): Promise<void> { - let planchet = await ws.db.runReadOnlyTx(["planchets"], async (tx) => { + let planchet = await wex.db.runReadOnlyTx(["planchets"], async (tx) => { return tx.planchets.indexes.byGroupAndIndex.get([ withdrawalGroup.withdrawalGroupId, coinIdx, @@ -677,11 +693,11 @@ async function processPlanchetGenerate( } const denomPubHash = maybeDenomPubHash; - const denom = await ws.db.runReadOnlyTx(["denominations"], async (tx) => { - return getDenomInfo(ws, tx, withdrawalGroup.exchangeBaseUrl, denomPubHash); + const denom = await wex.db.runReadOnlyTx(["denominations"], async (tx) => { + return getDenomInfo(wex, tx, withdrawalGroup.exchangeBaseUrl, denomPubHash); }); checkDbInvariant(!!denom); - const r = await ws.cryptoApi.createPlanchet({ + const r = await wex.cryptoApi.createPlanchet({ denomPub: denom.denomPub, feeWithdraw: Amounts.parseOrThrow(denom.feeWithdraw), reservePriv: withdrawalGroup.reservePriv, @@ -705,7 +721,7 @@ async function processPlanchetGenerate( ageCommitmentProof: r.ageCommitmentProof, lastError: undefined, }; - await ws.db.runReadWriteTx(["planchets"], async (tx) => { + await wex.db.runReadWriteTx(["planchets"], async (tx) => { const p = await tx.planchets.indexes.byGroupAndIndex.get([ withdrawalGroup.withdrawalGroupId, coinIdx, @@ -743,15 +759,15 @@ enum ExchangeAmlStatus { * Emit a notification for the (self-)transition. */ async function transitionKycUrlUpdate( - ws: InternalWalletState, + wex: WalletExecutionContext, withdrawalGroupId: string, kycUrl: string, ): Promise<void> { let notificationKycUrl: string | undefined = undefined; - const ctx = new WithdrawTransactionContext(ws, withdrawalGroupId); + const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId); const transactionId = ctx.transactionId; - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["withdrawalGroups"], async (tx) => { const wg2 = await tx.withdrawalGroups.get(withdrawalGroupId); @@ -777,7 +793,7 @@ async function transitionKycUrlUpdate( ); if (transitionInfo) { // Always notify, even on self-transition, as the KYC URL might have changed. - ws.notify({ + wex.ws.notify({ type: NotificationType.TransactionStateTransition, oldTxState: transitionInfo.oldTxState, newTxState: transitionInfo.newTxState, @@ -785,16 +801,15 @@ async function transitionKycUrlUpdate( experimentalUserData: notificationKycUrl, }); } - ws.taskScheduler.startShepherdTask(ctx.taskId); + wex.taskScheduler.startShepherdTask(ctx.taskId); } async function handleKycRequired( - ws: InternalWalletState, + wex: WalletExecutionContext, withdrawalGroup: WithdrawalGroupRecord, resp: HttpResponse, startIdx: number, requestCoinIdxs: number[], - cancellationToken: CancellationToken, ): Promise<void> { logger.info("withdrawal requires KYC"); const respJson = await resp.json(); @@ -816,9 +831,9 @@ async function handleKycRequired( exchangeUrl, ); logger.info(`kyc url ${url.href}`); - const kycStatusRes = await ws.http.fetch(url.href, { + const kycStatusRes = await wex.http.fetch(url.href, { method: "GET", - cancellationToken, + cancellationToken: wex.cancellationToken, }); let kycUrl: string; let amlStatus: ExchangeAmlStatus | undefined; @@ -846,7 +861,7 @@ async function handleKycRequired( let notificationKycUrl: string | undefined = undefined; - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["planchets", "withdrawalGroups"], async (tx) => { for (let i = startIdx; i < requestCoinIdxs.length; i++) { @@ -897,7 +912,7 @@ async function handleKycRequired( } }, ); - notifyTransition(ws, transactionId, transitionInfo, notificationKycUrl); + notifyTransition(wex, transactionId, transitionInfo, notificationKycUrl); } /** @@ -906,10 +921,9 @@ async function handleKycRequired( * The verification of the response is done asynchronously to enable parallelism. */ async function processPlanchetExchangeBatchRequest( - ws: InternalWalletState, + wex: WalletExecutionContext, wgContext: WithdrawalGroupContext, args: WithdrawalRequestBatchArgs, - cancellationToken: CancellationToken, ): Promise<WithdrawalBatchResult> { const withdrawalGroup: WithdrawalGroupRecord = wgContext.wgRecord; logger.info( @@ -920,7 +934,7 @@ async function processPlanchetExchangeBatchRequest( // Indices of coins that are included in the batch request const requestCoinIdxs: number[] = []; - await ws.db.runReadOnlyTx(["planchets", "denominations"], async (tx) => { + await wex.db.runReadOnlyTx(["planchets", "denominations"], async (tx) => { for ( let coinIdx = args.coinStartIndex; coinIdx < args.coinStartIndex + args.batchSize && @@ -939,7 +953,7 @@ async function processPlanchetExchangeBatchRequest( continue; } const denom = await getDenomInfo( - ws, + wex, tx, withdrawalGroup.exchangeBaseUrl, planchet.denomPubHash, @@ -972,7 +986,7 @@ async function processPlanchetExchangeBatchRequest( const errDetail = getErrorDetailFromException(e); logger.trace("withdrawal request failed", e); logger.trace(String(e)); - await ws.db.runReadWriteTx(["planchets"], async (tx) => { + await wex.db.runReadWriteTx(["planchets"], async (tx) => { let planchet = await tx.planchets.indexes.byGroupAndIndex.get([ withdrawalGroup.withdrawalGroupId, coinIdx, @@ -993,21 +1007,14 @@ async function processPlanchetExchangeBatchRequest( ).href; try { - const resp = await ws.http.fetch(reqUrl, { + const resp = await wex.http.fetch(reqUrl, { method: "POST", body: batchReq, - cancellationToken, + cancellationToken: wex.cancellationToken, timeout: Duration.fromSpec({ seconds: 40 }), }); if (resp.status === HttpStatusCode.UnavailableForLegalReasons) { - await handleKycRequired( - ws, - withdrawalGroup, - resp, - 0, - requestCoinIdxs, - cancellationToken, - ); + await handleKycRequired(wex, withdrawalGroup, resp, 0, requestCoinIdxs); return { batchResp: { ev_sigs: [] }, coinIdxs: [], @@ -1031,14 +1038,14 @@ async function processPlanchetExchangeBatchRequest( } async function processPlanchetVerifyAndStoreCoin( - ws: InternalWalletState, + wex: WalletExecutionContext, wgContext: WithdrawalGroupContext, coinIdx: number, resp: ExchangeWithdrawResponse, ): Promise<void> { const withdrawalGroup = wgContext.wgRecord; logger.trace(`checking and storing planchet idx=${coinIdx}`); - const d = await ws.db.runReadOnlyTx( + const d = await wex.db.runReadOnlyTx( ["planchets", "denominations"], async (tx) => { let planchet = await tx.planchets.indexes.byGroupAndIndex.get([ @@ -1053,7 +1060,7 @@ async function processPlanchetVerifyAndStoreCoin( return; } const denomInfo = await getDenomInfo( - ws, + wex, tx, withdrawalGroup.exchangeBaseUrl, planchet.denomPubHash, @@ -1090,20 +1097,20 @@ async function processPlanchetVerifyAndStoreCoin( throw Error("unsupported cipher"); } - const denomSigRsa = await ws.cryptoApi.rsaUnblind({ + const denomSigRsa = await wex.cryptoApi.rsaUnblind({ bk: planchet.blindingKey, blindedSig: evSig.blinded_rsa_signature, pk: planchetDenomPub.rsa_public_key, }); - const isValid = await ws.cryptoApi.rsaVerify({ + const isValid = await wex.cryptoApi.rsaVerify({ hm: planchet.coinPub, pk: planchetDenomPub.rsa_public_key, sig: denomSigRsa.sig, }); if (!isValid) { - await ws.db.runReadWriteTx(["planchets"], async (tx) => { + await wex.db.runReadWriteTx(["planchets"], async (tx) => { let planchet = await tx.planchets.indexes.byGroupAndIndex.get([ withdrawalGroup.withdrawalGroupId, coinIdx, @@ -1156,7 +1163,7 @@ async function processPlanchetVerifyAndStoreCoin( wgContext.planchetsFinished.add(planchet.coinPub); - await ws.db.runReadWriteTx( + await wex.db.runReadWriteTx( ["planchets", "coins", "coinAvailability", "denominations"], async (tx) => { const p = await tx.planchets.get(planchetCoinPub); @@ -1166,7 +1173,7 @@ async function processPlanchetVerifyAndStoreCoin( p.planchetStatus = PlanchetStatus.WithdrawalDone; p.lastError = undefined; await tx.planchets.put(p); - await makeCoinAvailable(ws, tx, coin); + await makeCoinAvailable(wex, tx, coin); }, ); } @@ -1176,13 +1183,13 @@ async function processPlanchetVerifyAndStoreCoin( * are validated, and the result of validation is stored in the database. */ async function updateWithdrawalDenoms( - ws: InternalWalletState, + wex: WalletExecutionContext, exchangeBaseUrl: string, ): Promise<void> { logger.trace( `updating denominations used for withdrawal for ${exchangeBaseUrl}`, ); - const exchangeDetails = await ws.db.runReadOnlyTx( + const exchangeDetails = await wex.db.runReadOnlyTx( ["exchanges", "exchangeDetails"], async (tx) => { return getExchangeWireDetailsInTx(tx, exchangeBaseUrl); @@ -1196,7 +1203,7 @@ async function updateWithdrawalDenoms( // is checked and the result is stored in the database. logger.trace("getting candidate denominations"); const denominations = await getCandidateWithdrawalDenoms( - ws, + wex, exchangeBaseUrl, exchangeDetails.currency, ); @@ -1222,10 +1229,10 @@ async function updateWithdrawalDenoms( }) signature of ${denom.denomPubHash}`, ); let valid = false; - if (ws.config.testing.insecureTrustExchange) { + if (wex.ws.config.testing.insecureTrustExchange) { valid = true; } else { - const res = await ws.cryptoApi.isValidDenom({ + const res = await wex.cryptoApi.isValidDenom({ denom, masterPub: exchangeDetails.masterPublicKey, }); @@ -1246,7 +1253,7 @@ async function updateWithdrawalDenoms( } if (updatedDenominations.length > 0) { logger.trace("writing denomination batch to db"); - await ws.db.runReadWriteTx(["denominations"], async (tx) => { + await wex.db.runReadWriteTx(["denominations"], async (tx) => { for (let i = 0; i < updatedDenominations.length; i++) { const denom = updatedDenominations[i]; await tx.denominations.put(denom); @@ -1266,15 +1273,14 @@ async function updateWithdrawalDenoms( * create a new withdrawal group for the remaining amount. */ async function processQueryReserve( - ws: InternalWalletState, + wex: WalletExecutionContext, withdrawalGroupId: string, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const transactionId = constructTransactionIdentifier({ tag: TransactionType.Withdrawal, withdrawalGroupId, }); - const withdrawalGroup = await getWithdrawalGroupRecordTx(ws.db, { + const withdrawalGroup = await getWithdrawalGroupRecordTx(wex.db, { withdrawalGroupId, }); checkDbInvariant(!!withdrawalGroup); @@ -1291,9 +1297,9 @@ async function processQueryReserve( logger.trace(`querying reserve status via ${reserveUrl.href}`); - const resp = await ws.http.fetch(reserveUrl.href, { + const resp = await wex.http.fetch(reserveUrl.href, { timeout: getReserveRequestTimeout(withdrawalGroup), - cancellationToken, + cancellationToken: wex.cancellationToken, }); logger.trace(`reserve status code: HTTP ${resp.status}`); @@ -1316,7 +1322,7 @@ async function processQueryReserve( logger.trace(`got reserve status ${j2s(result.response)}`); - const transitionResult = await ws.db.runReadWriteTx( + const transitionResult = await wex.db.runReadWriteTx( ["withdrawalGroups"], async (tx) => { const wg = await tx.withdrawalGroups.get(withdrawalGroupId); @@ -1336,7 +1342,7 @@ async function processQueryReserve( }, ); - notifyTransition(ws, transactionId, transitionResult); + notifyTransition(wex, transactionId, transitionResult); if (transitionResult) { return TaskRunResult.progress(); @@ -1361,9 +1367,8 @@ interface WithdrawalGroupContext { } async function processWithdrawalGroupAbortingBank( - ws: InternalWalletState, + wex: WalletExecutionContext, withdrawalGroup: WithdrawalGroupRecord, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const { withdrawalGroupId } = withdrawalGroup; const transactionId = constructTransactionIdentifier({ @@ -1377,14 +1382,14 @@ async function processWithdrawalGroupAbortingBank( } const abortUrl = getBankAbortUrl(wgInfo.bankInfo.talerWithdrawUri); logger.info(`aborting withdrawal at ${abortUrl}`); - const abortResp = await ws.http.fetch(abortUrl, { + const abortResp = await wex.http.fetch(abortUrl, { method: "POST", body: {}, - cancellationToken, + cancellationToken: wex.cancellationToken, }); logger.info(`abort response status: ${abortResp.status}`); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["withdrawalGroups"], async (tx) => { const wg = await tx.withdrawalGroups.get(withdrawalGroupId); @@ -1402,7 +1407,7 @@ async function processWithdrawalGroupAbortingBank( }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return TaskRunResult.finished(); } @@ -1411,14 +1416,14 @@ async function processWithdrawalGroupAbortingBank( * satisfied. */ async function transitionKycSatisfied( - ws: InternalWalletState, + wex: WalletExecutionContext, withdrawalGroup: WithdrawalGroupRecord, ): Promise<void> { const transactionId = constructTransactionIdentifier({ tag: TransactionType.Withdrawal, withdrawalGroupId: withdrawalGroup.withdrawalGroupId, }); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["withdrawalGroups"], async (tx) => { const wg2 = await tx.withdrawalGroups.get( @@ -1445,13 +1450,12 @@ async function transitionKycSatisfied( } }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); } async function processWithdrawalGroupPendingKyc( - ws: InternalWalletState, + wex: WalletExecutionContext, withdrawalGroup: WithdrawalGroupRecord, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const userType = "individual"; const kycInfo = withdrawalGroup.kycPending; @@ -1468,9 +1472,9 @@ async function processWithdrawalGroupPendingKyc( const withdrawalGroupId = withdrawalGroup.withdrawalGroupId; logger.info(`long-polling for withdrawal KYC status via ${url.href}`); - const kycStatusRes = await ws.http.fetch(url.href, { + const kycStatusRes = await wex.http.fetch(url.href, { method: "GET", - cancellationToken, + cancellationToken: wex.cancellationToken, }); logger.info(`kyc long-polling response status: HTTP ${kycStatusRes.status}`); if ( @@ -1479,13 +1483,13 @@ async function processWithdrawalGroupPendingKyc( // remove after the exchange is fixed or clarified kycStatusRes.status === HttpStatusCode.NoContent ) { - await transitionKycSatisfied(ws, withdrawalGroup); + await transitionKycSatisfied(wex, withdrawalGroup); } else if (kycStatusRes.status === HttpStatusCode.Accepted) { const kycStatus = await kycStatusRes.json(); logger.info(`kyc status: ${j2s(kycStatus)}`); const kycUrl = kycStatus.kyc_url; if (typeof kycUrl === "string") { - await transitionKycUrlUpdate(ws, withdrawalGroupId, kycUrl); + await transitionKycUrlUpdate(wex, withdrawalGroupId, kycUrl); } } else if ( kycStatusRes.status === HttpStatusCode.UnavailableForLegalReasons @@ -1499,9 +1503,8 @@ async function processWithdrawalGroupPendingKyc( } async function processWithdrawalGroupPendingReady( - ws: InternalWalletState, + wex: WalletExecutionContext, withdrawalGroup: WithdrawalGroupRecord, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { const { withdrawalGroupId } = withdrawalGroup; const transactionId = constructTransactionIdentifier({ @@ -1509,11 +1512,11 @@ async function processWithdrawalGroupPendingReady( withdrawalGroupId, }); - await fetchFreshExchange(ws, withdrawalGroup.exchangeBaseUrl); + await fetchFreshExchange(wex, withdrawalGroup.exchangeBaseUrl); if (withdrawalGroup.denomsSel.selectedDenoms.length === 0) { logger.warn("Finishing empty withdrawal group (no denoms)"); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["withdrawalGroups"], async (tx) => { const wg = await tx.withdrawalGroups.get(withdrawalGroupId); @@ -1531,7 +1534,7 @@ async function processWithdrawalGroupPendingReady( }; }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); return TaskRunResult.finished(); } @@ -1545,7 +1548,7 @@ async function processWithdrawalGroupPendingReady( wgRecord: withdrawalGroup, }; - await ws.db.runReadOnlyTx(["planchets"], async (tx) => { + await wex.db.runReadOnlyTx(["planchets"], async (tx) => { const planchets = await tx.planchets.indexes.byGroup.getAll(withdrawalGroupId); for (const p of planchets) { @@ -1558,21 +1561,16 @@ async function processWithdrawalGroupPendingReady( // We sequentially generate planchets, so that // large withdrawal groups don't make the wallet unresponsive. for (let i = 0; i < numTotalCoins; i++) { - await processPlanchetGenerate(ws, withdrawalGroup, i); + await processPlanchetGenerate(wex, withdrawalGroup, i); } const maxBatchSize = 100; for (let i = 0; i < numTotalCoins; i += maxBatchSize) { - const resp = await processPlanchetExchangeBatchRequest( - ws, - wgContext, - { - batchSize: maxBatchSize, - coinStartIndex: i, - }, - cancellationToken, - ); + const resp = await processPlanchetExchangeBatchRequest(wex, wgContext, { + batchSize: maxBatchSize, + coinStartIndex: i, + }); let work: Promise<void>[] = []; work = []; for (let j = 0; j < resp.coinIdxs.length; j++) { @@ -1582,7 +1580,7 @@ async function processWithdrawalGroupPendingReady( } work.push( processPlanchetVerifyAndStoreCoin( - ws, + wex, wgContext, resp.coinIdxs[j], resp.batchResp.ev_sigs[j], @@ -1597,7 +1595,7 @@ async function processWithdrawalGroupPendingReady( let numPlanchetErrors = 0; const maxReportedErrors = 5; - const res = await ws.db.runReadWriteTx( + const res = await wex.db.runReadWriteTx( ["coins", "coinAvailability", "withdrawalGroups", "planchets"], async (tx) => { const wg = await tx.withdrawalGroups.get(withdrawalGroupId); @@ -1623,7 +1621,7 @@ async function processWithdrawalGroupPendingReady( if (wg.timestampFinish === undefined && numFinished === numTotalCoins) { wg.timestampFinish = timestampPreciseToDb(TalerPreciseTimestamp.now()); wg.status = WithdrawalGroupStatus.Done; - await makeCoinsVisible(ws, tx, transactionId); + await makeCoinsVisible(wex, tx, transactionId); } const newTxState = computeWithdrawalTransactionStatus(wg); @@ -1643,8 +1641,8 @@ async function processWithdrawalGroupPendingReady( throw Error("withdrawal group does not exist anymore"); } - notifyTransition(ws, transactionId, res.transitionInfo); - ws.notify({ + notifyTransition(wex, transactionId, res.transitionInfo); + wex.ws.notify({ type: NotificationType.BalanceChange, hintTransactionId: transactionId, }); @@ -1666,12 +1664,11 @@ async function processWithdrawalGroupPendingReady( } export async function processWithdrawalGroup( - ws: InternalWalletState, + wex: WalletExecutionContext, withdrawalGroupId: string, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { logger.trace("processing withdrawal group", withdrawalGroupId); - const withdrawalGroup = await ws.db.runReadOnlyTx( + const withdrawalGroup = await wex.db.runReadOnlyTx( ["withdrawalGroups"], async (tx) => { return tx.withdrawalGroups.get(withdrawalGroupId); @@ -1684,41 +1681,21 @@ export async function processWithdrawalGroup( switch (withdrawalGroup.status) { case WithdrawalGroupStatus.PendingRegisteringBank: - return await processBankRegisterReserve( - ws, - withdrawalGroupId, - cancellationToken, - ); + return await processBankRegisterReserve(wex, withdrawalGroupId); case WithdrawalGroupStatus.PendingQueryingStatus: - return processQueryReserve(ws, withdrawalGroupId, cancellationToken); + return processQueryReserve(wex, withdrawalGroupId); case WithdrawalGroupStatus.PendingWaitConfirmBank: - return await processReserveBankStatus( - ws, - withdrawalGroupId, - cancellationToken, - ); + return await processReserveBankStatus(wex, withdrawalGroupId); case WithdrawalGroupStatus.PendingAml: // FIXME: Handle this case, withdrawal doesn't support AML yet. return TaskRunResult.backoff(); case WithdrawalGroupStatus.PendingKyc: - return processWithdrawalGroupPendingKyc( - ws, - withdrawalGroup, - cancellationToken, - ); + return processWithdrawalGroupPendingKyc(wex, withdrawalGroup); case WithdrawalGroupStatus.PendingReady: // Continue with the actual withdrawal! - return await processWithdrawalGroupPendingReady( - ws, - withdrawalGroup, - cancellationToken, - ); + return await processWithdrawalGroupPendingReady(wex, withdrawalGroup); case WithdrawalGroupStatus.AbortingBank: - return await processWithdrawalGroupAbortingBank( - ws, - withdrawalGroup, - cancellationToken, - ); + return await processWithdrawalGroupAbortingBank(wex, withdrawalGroup); case WithdrawalGroupStatus.AbortedBank: case WithdrawalGroupStatus.AbortedExchange: case WithdrawalGroupStatus.FailedAbortingBank: @@ -1743,13 +1720,13 @@ const AGE_MASK_GROUPS = "8:10:12:14:16:18" .map((n) => parseInt(n, 10)); export async function getExchangeWithdrawalInfo( - ws: InternalWalletState, + wex: WalletExecutionContext, exchangeBaseUrl: string, instructedAmount: AmountJson, ageRestricted: number | undefined, ): Promise<ExchangeWithdrawalDetails> { logger.trace("updating exchange"); - const exchange = await fetchFreshExchange(ws, exchangeBaseUrl); + const exchange = await fetchFreshExchange(wex, exchangeBaseUrl); if (exchange.currency != instructedAmount.currency) { // Specifying the amount in the conversion input currency is not yet supported. @@ -1760,7 +1737,7 @@ export async function getExchangeWithdrawalInfo( } const withdrawalAccountsList = await fetchWithdrawalAccountInfo( - ws, + wex, { exchange, instructedAmount, @@ -1769,11 +1746,11 @@ export async function getExchangeWithdrawalInfo( ); logger.trace("updating withdrawal denoms"); - await updateWithdrawalDenoms(ws, exchangeBaseUrl); + await updateWithdrawalDenoms(wex, exchangeBaseUrl); logger.trace("getting candidate denoms"); const denoms = await getCandidateWithdrawalDenoms( - ws, + wex, exchangeBaseUrl, instructedAmount.currency, ); @@ -1781,7 +1758,7 @@ export async function getExchangeWithdrawalInfo( const selectedDenoms = selectWithdrawalDenominations( instructedAmount, denoms, - ws.config.testing.denomselAllowLate, + wex.ws.config.testing.denomselAllowLate, ); logger.trace("selection done"); @@ -1806,11 +1783,11 @@ export async function getExchangeWithdrawalInfo( let earliestDepositExpiration: TalerProtocolTimestamp | undefined; - await ws.db.runReadOnlyTx(["denominations"], async (tx) => { + await wex.db.runReadOnlyTx(["denominations"], async (tx) => { for (let i = 0; i < selectedDenoms.selectedDenoms.length; i++) { const ds = selectedDenoms.selectedDenoms[i]; const denom = await getDenomInfo( - ws, + wex, tx, exchangeBaseUrl, ds.denomPubHash, @@ -1837,7 +1814,7 @@ export async function getExchangeWithdrawalInfo( checkLogicInvariant(!!earliestDepositExpiration); const possibleDenoms = await getCandidateWithdrawalDenoms( - ws, + wex, exchangeBaseUrl, instructedAmount.currency, ); @@ -1915,18 +1892,18 @@ const ongoingChecks: WithdrawalOperationMemoryMap = {}; * to the wallet's list of known exchanges. */ export async function getWithdrawalDetailsForUri( - ws: InternalWalletState, + wex: WalletExecutionContext, talerWithdrawUri: string, opts: GetWithdrawalDetailsForUriOpts = {}, ): Promise<WithdrawUriInfoResponse> { logger.trace(`getting withdrawal details for URI ${talerWithdrawUri}`); - const info = await getBankWithdrawalInfo(ws.http, talerWithdrawUri); + const info = await getBankWithdrawalInfo(wex.http, talerWithdrawUri); logger.trace(`got bank info`); if (info.suggestedExchange) { try { // If the exchange entry doesn't exist yet, // it'll be created as an ephemeral entry. - await fetchFreshExchange(ws, info.suggestedExchange); + await fetchFreshExchange(wex, 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. @@ -1938,7 +1915,7 @@ export async function getWithdrawalDetailsForUri( const currency = Amounts.currencyOf(info.amount); - const listExchangesResp = await listExchanges(ws); + const listExchangesResp = await listExchanges(wex); const possibleExchanges = listExchangesResp.exchanges.filter((x) => { return ( x.currency === currency && @@ -1957,7 +1934,7 @@ export async function getWithdrawalDetailsForUri( ongoingChecks[talerWithdrawUri] = true; const bankApi = new TalerBankIntegrationHttpClient( info.apiBaseUrl, - ws.http, + wex.http, ); console.log( `waiting operation (${info.operationId}) to change from pending`, @@ -2076,11 +2053,10 @@ export function getBankAbortUrl(talerWithdrawUri: string): string { } async function registerReserveWithBank( - ws: InternalWalletState, + wex: WalletExecutionContext, withdrawalGroupId: string, - cancellationToken: CancellationToken, ): Promise<void> { - const withdrawalGroup = await ws.db.runReadOnlyTx( + const withdrawalGroup = await wex.db.runReadOnlyTx( ["withdrawalGroups"], async (tx) => { return await tx.withdrawalGroups.get(withdrawalGroupId); @@ -2112,17 +2088,17 @@ async function registerReserveWithBank( selected_exchange: bankInfo.exchangePaytoUri, }; logger.info(`registering reserve with bank: ${j2s(reqBody)}`); - const httpResp = await ws.http.fetch(bankStatusUrl, { + const httpResp = await wex.http.fetch(bankStatusUrl, { method: "POST", body: reqBody, timeout: getReserveRequestTimeout(withdrawalGroup), - cancellationToken, + cancellationToken: wex.cancellationToken, }); const status = await readSuccessResponseJsonOrThrow( httpResp, codeForBankWithdrawalOperationPostResponse(), ); - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["withdrawalGroups"], async (tx) => { const r = await tx.withdrawalGroups.get(withdrawalGroupId); @@ -2154,14 +2130,14 @@ async function registerReserveWithBank( }, ); - notifyTransition(ws, transactionId, transitionInfo); + notifyTransition(wex, transactionId, transitionInfo); } async function transitionBankAborted( ctx: WithdrawTransactionContext, ): Promise<TaskRunResult> { logger.info("bank aborted the withdrawal"); - const transitionInfo = await ctx.ws.db.runReadWriteTx( + const transitionInfo = await ctx.wex.db.runReadWriteTx( ["withdrawalGroups"], async (tx) => { const r = await tx.withdrawalGroups.get(ctx.withdrawalGroupId); @@ -2190,17 +2166,16 @@ async function transitionBankAborted( }; }, ); - notifyTransition(ctx.ws, ctx.transactionId, transitionInfo); + notifyTransition(ctx.wex, ctx.transactionId, transitionInfo); return TaskRunResult.finished(); } async function processBankRegisterReserve( - ws: InternalWalletState, + wex: WalletExecutionContext, withdrawalGroupId: string, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { - const ctx = new WithdrawTransactionContext(ws, withdrawalGroupId); - const withdrawalGroup = await getWithdrawalGroupRecordTx(ws.db, { + const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId); + const withdrawalGroup = await getWithdrawalGroupRecordTx(wex.db, { withdrawalGroupId, }); if (!withdrawalGroup) { @@ -2226,9 +2201,9 @@ async function processBankRegisterReserve( uriResult.bankIntegrationApiBaseUrl, ); - const statusResp = await ws.http.fetch(url.href, { + const statusResp = await wex.http.fetch(url.href, { timeout: getReserveRequestTimeout(withdrawalGroup), - cancellationToken, + cancellationToken: wex.cancellationToken, }); const status = await readSuccessResponseJsonOrThrow( @@ -2242,16 +2217,15 @@ async function processBankRegisterReserve( // FIXME: Put confirm transfer URL in the DB! - await registerReserveWithBank(ws, withdrawalGroupId, cancellationToken); + await registerReserveWithBank(wex, withdrawalGroupId); return TaskRunResult.progress(); } async function processReserveBankStatus( - ws: InternalWalletState, + wex: WalletExecutionContext, withdrawalGroupId: string, - cancellationToken: CancellationToken, ): Promise<TaskRunResult> { - const withdrawalGroup = await getWithdrawalGroupRecordTx(ws.db, { + const withdrawalGroup = await getWithdrawalGroupRecordTx(wex.db, { withdrawalGroupId, }); @@ -2259,7 +2233,7 @@ async function processReserveBankStatus( return TaskRunResult.finished(); } - const ctx = new WithdrawTransactionContext(ws, withdrawalGroupId); + const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId); if ( withdrawalGroup.wgInfo.withdrawalType != WithdrawalRecordType.BankIntegrated @@ -2282,9 +2256,9 @@ async function processReserveBankStatus( bankStatusUrl.searchParams.set("long_poll_ms", "30000"); logger.info(`long-polling for withdrawal operation at ${bankStatusUrl.href}`); - const statusResp = await ws.http.fetch(bankStatusUrl.href, { + const statusResp = await wex.http.fetch(bankStatusUrl.href, { timeout: getReserveRequestTimeout(withdrawalGroup), - cancellationToken, + cancellationToken: wex.cancellationToken, }); logger.info( `long-polling for withdrawal operation returned status ${statusResp.status}`, @@ -2307,7 +2281,7 @@ async function processReserveBankStatus( return TaskRunResult.longpollReturnedPending(); } - const transitionInfo = await ws.db.runReadWriteTx( + const transitionInfo = await wex.db.runReadWriteTx( ["withdrawalGroups"], async (tx) => { const r = await tx.withdrawalGroups.get(withdrawalGroupId); @@ -2341,7 +2315,7 @@ async function processReserveBankStatus( }, ); - notifyTransition(ws, ctx.transactionId, transitionInfo); + notifyTransition(wex, ctx.transactionId, transitionInfo); if (transitionInfo) { return TaskRunResult.progress(); @@ -2360,7 +2334,7 @@ export interface PrepareCreateWithdrawalGroupResult { } export async function internalPrepareCreateWithdrawalGroup( - ws: InternalWalletState, + wex: WalletExecutionContext, args: { reserveStatus: WithdrawalGroupStatus; amount: AmountJson; @@ -2373,7 +2347,7 @@ export async function internalPrepareCreateWithdrawalGroup( }, ): Promise<PrepareCreateWithdrawalGroupResult> { const reserveKeyPair = - args.reserveKeyPair ?? (await ws.cryptoApi.createEddsaKeypair({})); + args.reserveKeyPair ?? (await wex.cryptoApi.createEddsaKeypair({})); const now = AbsoluteTime.toPreciseTimestamp(AbsoluteTime.now()); const secretSeed = encodeCrock(getRandomBytes(32)); const canonExchange = canonicalizeBaseUrl(args.exchangeBaseUrl); @@ -2385,7 +2359,7 @@ export async function internalPrepareCreateWithdrawalGroup( if (args.forcedWithdrawalGroupId) { withdrawalGroupId = args.forcedWithdrawalGroupId; const wgId = withdrawalGroupId; - const existingWg = await ws.db.runReadOnlyTx( + const existingWg = await wex.db.runReadOnlyTx( ["withdrawalGroups"], async (tx) => { return tx.withdrawalGroups.get(wgId); @@ -2403,9 +2377,9 @@ export async function internalPrepareCreateWithdrawalGroup( withdrawalGroupId = encodeCrock(getRandomBytes(32)); } - await updateWithdrawalDenoms(ws, canonExchange); + await updateWithdrawalDenoms(wex, canonExchange); const denoms = await getCandidateWithdrawalDenoms( - ws, + wex, canonExchange, currency, ); @@ -2418,13 +2392,13 @@ export async function internalPrepareCreateWithdrawalGroup( amount, denoms, args.forcedDenomSel, - ws.config.testing.denomselAllowLate, + wex.ws.config.testing.denomselAllowLate, ); } else { initialDenomSel = selectWithdrawalDenominations( amount, denoms, - ws.config.testing.denomselAllowLate, + wex.ws.config.testing.denomselAllowLate, ); } @@ -2447,7 +2421,7 @@ export async function internalPrepareCreateWithdrawalGroup( wgInfo: args.wgInfo, }; - await fetchFreshExchange(ws, canonExchange); + await fetchFreshExchange(wex, canonExchange); const transactionId = constructTransactionIdentifier({ tag: TransactionType.Withdrawal, withdrawalGroupId: withdrawalGroup.withdrawalGroupId, @@ -2476,7 +2450,7 @@ export interface PerformCreateWithdrawalGroupResult { } export async function internalPerformCreateWithdrawalGroup( - ws: InternalWalletState, + wex: WalletExecutionContext, tx: WalletDbReadWriteTransaction< ["withdrawalGroups", "reserves", "exchanges"] >, @@ -2523,17 +2497,17 @@ export async function internalPerformCreateWithdrawalGroup( }; const exchangeUsedRes = await markExchangeUsed( - ws, + wex, tx, prep.withdrawalGroup.exchangeBaseUrl, ); const ctx = new WithdrawTransactionContext( - ws, + wex, withdrawalGroup.withdrawalGroupId, ); - ws.taskScheduler.startShepherdTask(ctx.taskId); + wex.taskScheduler.startShepherdTask(ctx.taskId); return { withdrawalGroup, @@ -2551,7 +2525,7 @@ export async function internalPerformCreateWithdrawalGroup( * of the other arguments is done in that case. */ export async function internalCreateWithdrawalGroup( - ws: InternalWalletState, + wex: WalletExecutionContext, args: { reserveStatus: WithdrawalGroupStatus; amount: AmountJson; @@ -2563,21 +2537,21 @@ export async function internalCreateWithdrawalGroup( wgInfo: WgInfo; }, ): Promise<WithdrawalGroupRecord> { - const prep = await internalPrepareCreateWithdrawalGroup(ws, args); + const prep = await internalPrepareCreateWithdrawalGroup(wex, args); const transactionId = constructTransactionIdentifier({ tag: TransactionType.Withdrawal, withdrawalGroupId: prep.withdrawalGroup.withdrawalGroupId, }); - const res = await ws.db.runReadWriteTx( + const res = await wex.db.runReadWriteTx( ["withdrawalGroups", "reserves", "exchanges", "exchangeDetails"], async (tx) => { - return await internalPerformCreateWithdrawalGroup(ws, tx, prep); + return await internalPerformCreateWithdrawalGroup(wex, tx, prep); }, ); if (res.exchangeNotif) { - ws.notify(res.exchangeNotif); + wex.ws.notify(res.exchangeNotif); } - notifyTransition(ws, transactionId, res.transitionInfo); + notifyTransition(wex, transactionId, res.transitionInfo); return res.withdrawalGroup; } @@ -2590,7 +2564,7 @@ export async function internalCreateWithdrawalGroup( * with the bank. */ export async function acceptWithdrawalFromUri( - ws: InternalWalletState, + wex: WalletExecutionContext, req: { talerWithdrawUri: string; selectedExchange: string; @@ -2602,7 +2576,7 @@ export async function acceptWithdrawalFromUri( logger.info( `accepting withdrawal via ${req.talerWithdrawUri}, canonicalized selected exchange ${selectedExchange}`, ); - const existingWithdrawalGroup = await ws.db.runReadOnlyTx( + const existingWithdrawalGroup = await wex.db.runReadOnlyTx( ["withdrawalGroups"], async (tx) => { return await tx.withdrawalGroups.indexes.byTalerWithdrawUri.get( @@ -2629,21 +2603,21 @@ export async function acceptWithdrawalFromUri( }; } - await fetchFreshExchange(ws, selectedExchange); + await fetchFreshExchange(wex, selectedExchange); const withdrawInfo = await getBankWithdrawalInfo( - ws.http, + wex.http, req.talerWithdrawUri, ); const exchangePaytoUri = await getExchangePaytoUri( - ws, + wex, selectedExchange, withdrawInfo.wireTypes, ); - const exchange = await fetchFreshExchange(ws, selectedExchange); + const exchange = await fetchFreshExchange(wex, selectedExchange); const withdrawalAccountList = await fetchWithdrawalAccountInfo( - ws, + wex, { exchange, instructedAmount: withdrawInfo.amount, @@ -2651,7 +2625,7 @@ export async function acceptWithdrawalFromUri( CancellationToken.CONTINUE, ); - const withdrawalGroup = await internalCreateWithdrawalGroup(ws, { + const withdrawalGroup = await internalCreateWithdrawalGroup(wex, { amount: withdrawInfo.amount, exchangeBaseUrl: req.selectedExchange, wgInfo: { @@ -2672,16 +2646,16 @@ export async function acceptWithdrawalFromUri( const withdrawalGroupId = withdrawalGroup.withdrawalGroupId; - const ctx = new WithdrawTransactionContext(ws, withdrawalGroupId); + const ctx = new WithdrawTransactionContext(wex, withdrawalGroupId); - ws.notify({ + wex.ws.notify({ type: NotificationType.BalanceChange, hintTransactionId: ctx.transactionId, }); - await waitWithdrawalRegistered(ws, ctx); + await waitWithdrawalRegistered(wex, ctx); - ws.taskScheduler.startShepherdTask(ctx.taskId); + wex.taskScheduler.startShepherdTask(ctx.taskId); return { reservePub: withdrawalGroup.reservePub, @@ -2691,12 +2665,12 @@ export async function acceptWithdrawalFromUri( } async function internalWaitWithdrawalRegistered( - ws: InternalWalletState, + wex: WalletExecutionContext, ctx: WithdrawTransactionContext, withdrawalNotifFlag: AsyncFlag, ): Promise<void> { while (true) { - const { withdrawalRec, retryRec } = await ws.db.runReadOnlyTx( + const { withdrawalRec, retryRec } = await wex.db.runReadOnlyTx( ["withdrawalGroups", "operationRetries"], async (tx) => { return { @@ -2742,7 +2716,7 @@ async function internalWaitWithdrawalRegistered( } async function waitWithdrawalRegistered( - ws: InternalWalletState, + wex: WalletExecutionContext, ctx: WithdrawTransactionContext, ): Promise<void> { // FIXME: We should use Symbol.dispose magic here for cleanup! @@ -2750,7 +2724,7 @@ async function waitWithdrawalRegistered( const withdrawalNotifFlag = new AsyncFlag(); // Raise exchangeNotifFlag whenever we get a notification // about our exchange. - const cancelNotif = ws.addNotificationListener((notif) => { + const cancelNotif = wex.ws.addNotificationListener((notif) => { if ( notif.type === NotificationType.TransactionStateTransition && notif.transactionId === ctx.transactionId @@ -2762,7 +2736,7 @@ async function waitWithdrawalRegistered( try { const res = await internalWaitWithdrawalRegistered( - ws, + wex, ctx, withdrawalNotifFlag, ); @@ -2774,7 +2748,7 @@ async function waitWithdrawalRegistered( } async function fetchAccount( - ws: InternalWalletState, + wex: WalletExecutionContext, instructedAmount: AmountJson, acct: ExchangeWireAccount, reservePub: string | undefined, @@ -2789,7 +2763,7 @@ async function fetchAccount( "amount_credit", Amounts.stringify(instructedAmount), ); - const httpResp = await ws.http.fetch(reqUrl.href, { + const httpResp = await wex.http.fetch(reqUrl.href, { cancellationToken, }); const respOrErr = await readSuccessResponseJsonOrErrorCode( @@ -2807,7 +2781,7 @@ async function fetchAccount( paytoUri = acct.payto_uri; transferAmount = resp.amount_debit; const configUrl = new URL("config", acct.conversion_url); - const configResp = await ws.http.fetch(configUrl.href, { + const configResp = await wex.http.fetch(configUrl.href, { cancellationToken, }); const configRespOrError = await readSuccessResponseJsonOrErrorCode( @@ -2854,7 +2828,7 @@ async function fetchAccount( * currency and require conversion. */ async function fetchWithdrawalAccountInfo( - ws: InternalWalletState, + wex: WalletExecutionContext, req: { exchange: ReadyExchangeSummary; instructedAmount: AmountJson; @@ -2866,7 +2840,7 @@ async function fetchWithdrawalAccountInfo( const withdrawalAccounts: WithdrawalExchangeAccountDetails[] = []; for (let acct of exchange.wireInfo.accounts) { const acctInfo = await fetchAccount( - ws, + wex, req.instructedAmount, acct, req.reservePub, @@ -2886,7 +2860,7 @@ async function fetchWithdrawalAccountInfo( * Asynchronously starts the withdrawal. */ export async function createManualWithdrawal( - ws: InternalWalletState, + wex: WalletExecutionContext, req: { exchangeBaseUrl: string; amount: AmountLike; @@ -2896,19 +2870,19 @@ export async function createManualWithdrawal( ): Promise<AcceptManualWithdrawalResult> { const { exchangeBaseUrl } = req; const amount = Amounts.parseOrThrow(req.amount); - const exchange = await fetchFreshExchange(ws, exchangeBaseUrl); + const exchange = await fetchFreshExchange(wex, exchangeBaseUrl); if (exchange.currency != amount.currency) { throw Error( "manual withdrawal with conversion from foreign currency is not yet supported", ); } - const reserveKeyPair: EddsaKeypair = await ws.cryptoApi.createEddsaKeypair( + const reserveKeyPair: EddsaKeypair = await wex.cryptoApi.createEddsaKeypair( {}, ); const withdrawalAccountsList = await fetchWithdrawalAccountInfo( - ws, + wex, { exchange, instructedAmount: amount, @@ -2917,7 +2891,7 @@ export async function createManualWithdrawal( CancellationToken.CONTINUE, ); - const withdrawalGroup = await internalCreateWithdrawalGroup(ws, { + const withdrawalGroup = await internalCreateWithdrawalGroup(wex, { amount: Amounts.jsonifyAmount(req.amount), wgInfo: { withdrawalType: WithdrawalRecordType.BankManual, @@ -2931,23 +2905,23 @@ export async function createManualWithdrawal( }); const ctx = new WithdrawTransactionContext( - ws, + wex, withdrawalGroup.withdrawalGroupId, ); - const exchangePaytoUris = await ws.db.runReadOnlyTx( + const exchangePaytoUris = await wex.db.runReadOnlyTx( ["withdrawalGroups", "exchanges", "exchangeDetails"], async (tx) => { return await getFundingPaytoUris(tx, withdrawalGroup.withdrawalGroupId); }, ); - ws.notify({ + wex.ws.notify({ type: NotificationType.BalanceChange, hintTransactionId: ctx.transactionId, }); - ws.taskScheduler.startShepherdTask(ctx.taskId); + wex.taskScheduler.startShepherdTask(ctx.taskId); return { reservePub: withdrawalGroup.reservePub, |