From e81ae0f3e5a510424076b611ac32385057cbdaed Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Fri, 21 Apr 2023 22:02:34 +0200 Subject: wallet-harness: make sure events are not lost in deposit test --- packages/taler-harness/src/harness/harness.ts | 1 + .../src/integrationtests/test-deposit.ts | 15 ++++++- packages/taler-util/src/wallet-types.ts | 8 ++++ packages/taler-wallet-core/src/db.ts | 2 + .../taler-wallet-core/src/operations/deposits.ts | 46 +++++++++++++++++----- .../src/operations/pay-merchant.ts | 4 +- .../src/operations/transactions.ts | 3 +- packages/taler-wallet-core/src/wallet.ts | 4 +- 8 files changed, 69 insertions(+), 14 deletions(-) diff --git a/packages/taler-harness/src/harness/harness.ts b/packages/taler-harness/src/harness/harness.ts index 840149e7c..0a898414d 100644 --- a/packages/taler-harness/src/harness/harness.ts +++ b/packages/taler-harness/src/harness/harness.ts @@ -2062,6 +2062,7 @@ export class WalletService { [ "--wallet-db", dbPath, + "-LDEBUG", // FIXME: Make this configurable? "--no-throttle", // FIXME: Optionally do throttling for some tests? "advanced", "serve", diff --git a/packages/taler-harness/src/integrationtests/test-deposit.ts b/packages/taler-harness/src/integrationtests/test-deposit.ts index 1b46daf5f..6aa086107 100644 --- a/packages/taler-harness/src/integrationtests/test-deposit.ts +++ b/packages/taler-harness/src/integrationtests/test-deposit.ts @@ -45,9 +45,17 @@ export async function runDepositTest(t: GlobalTestState) { await withdrawalResult.withdrawalFinishedCond; - const depositDone = await walletClient.waitForNotificationCond( + const dgIdResp = await walletClient.client.call( + WalletApiOperation.GenerateDepositGroupTxId, + {}, + ); + + const depositTxId = dgIdResp.transactionId; + + const depositDone = walletClient.waitForNotificationCond( (n) => n.type == NotificationType.TransactionStateTransition && + n.transactionId == depositTxId && n.newTxState == TransactionState.Done, ); @@ -56,9 +64,14 @@ export async function runDepositTest(t: GlobalTestState) { { amount: "TESTKUDOS:10", depositPaytoUri: getPayto("foo"), + transactionId: depositTxId, }, ); + t.assertDeepEqual(depositGroupResult.transactionId, depositTxId); + + await depositDone; + const transactions = await walletClient.client.call( WalletApiOperation.GetTransactions, {}, diff --git a/packages/taler-util/src/wallet-types.ts b/packages/taler-util/src/wallet-types.ts index 5979f14b4..4297e838d 100644 --- a/packages/taler-util/src/wallet-types.ts +++ b/packages/taler-util/src/wallet-types.ts @@ -1708,6 +1708,13 @@ export interface DepositGroupFees { } export interface CreateDepositGroupRequest { + /** + * Pre-allocated transaction ID. + * Allows clients to easily handle notifications + * that occur while the operation has been created but + * before the creation request has returned. + */ + transactionId?: string; depositPaytoUri: string; amount: AmountString; } @@ -1733,6 +1740,7 @@ export const codecForCreateDepositGroupRequest = buildCodecForObject() .property("amount", codecForAmountString()) .property("depositPaytoUri", codecForString()) + .property("transactionId", codecOptional(codecForString())) .build("CreateDepositGroupRequest"); export interface CreateDepositGroupResponse { diff --git a/packages/taler-wallet-core/src/db.ts b/packages/taler-wallet-core/src/db.ts index 4e9228c6d..f5342b4cd 100644 --- a/packages/taler-wallet-core/src/db.ts +++ b/packages/taler-wallet-core/src/db.ts @@ -865,6 +865,7 @@ export enum DepositGroupOperationStatus { AbortingWithRefresh = 11 /* ACTIVE_START + 1 */, } +// FIXME: Improve name! This enum is very specific to deposits. export enum TransactionStatus { Unknown = 10, Accepted = 20, @@ -1380,6 +1381,7 @@ export type WgInfo = | WgInfoBankRecoup; export type KycUserType = "individual" | "business"; + export interface KycPendingInfo { paytoHash: string; requirementRow: number; diff --git a/packages/taler-wallet-core/src/operations/deposits.ts b/packages/taler-wallet-core/src/operations/deposits.ts index 700b875d3..f5ea41e01 100644 --- a/packages/taler-wallet-core/src/operations/deposits.ts +++ b/packages/taler-wallet-core/src/operations/deposits.ts @@ -67,7 +67,7 @@ import { getTotalRefreshCost, KycPendingInfo, KycUserType } from "../index.js"; import { InternalWalletState } from "../internal-wallet-state.js"; import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; import { OperationAttemptResult } from "../util/retries.js"; -import { makeTransactionId, spendCoins } from "./common.js"; +import { spendCoins } from "./common.js"; import { getExchangeDetails } from "./exchanges.js"; import { extractContractData, @@ -75,13 +75,20 @@ import { getTotalPaymentCost, } from "./pay-merchant.js"; import { selectPayCoinsNew } from "../util/coinSelection.js"; -import { constructTransactionIdentifier } from "./transactions.js"; +import { + constructTransactionIdentifier, + parseTransactionIdentifier, +} from "./transactions.js"; /** * Logger. */ const logger = new Logger("deposits.ts"); +/** + * Get the (DD37-style) transaction status based on the + * database record of a deposit group. + */ export async function computeDepositTransactionStatus( ws: InternalWalletState, dg: DepositGroupRecord, @@ -151,7 +158,8 @@ export async function abortDepositGroup( } /** - * Check KYC status with the exchange, throw an appropriate exception when KYC is required. + * Check KYC status with the exchange, throw an appropriate exception when KYC + * is required. * * FIXME: Why does this throw an exception when KYC is required? * Should we not return some proper result record here? @@ -221,6 +229,7 @@ export async function processDepositGroup( // Check for cancellation before expensive operations. options.cancellationToken?.throwIfCancelled(); + // FIXME: Cache these! const depositPermissions = await generateDepositPermissions( ws, depositGroup.payCoinSelection, @@ -438,7 +447,7 @@ async function trackDepositPermission( wireHash, }); url.searchParams.set("merchant_sig", sigResp.sig); - const httpResp = await ws.http.get(url.href); + const httpResp = await ws.http.fetch(url.href, { method: "GET" }); switch (httpResp.status) { case HttpStatusCode.Accepted: { const accepted = await readSuccessResponseJsonOrThrow( @@ -463,6 +472,9 @@ async function trackDepositPermission( } /** + * Check if creating a deposit group is possible and calculate + * the associated fees. + * * FIXME: This should be renamed to checkDepositGroup, * as it doesn't prepare anything */ @@ -671,9 +683,18 @@ export async function createDepositGroup( const totalDepositCost = await getTotalPaymentCost(ws, payCoinSel.coinSel); - const depositGroupId = encodeCrock(getRandomBytes(32)); + let depositGroupId: string; + if (req.transactionId) { + const txId = parseTransactionIdentifier(req.transactionId); + if (!txId || txId.tag !== TransactionType.Deposit) { + throw Error("invalid transaction ID"); + } + depositGroupId = txId.depositGroupId; + } else { + depositGroupId = encodeCrock(getRandomBytes(32)); + } - const countarpartyEffectiveDepositAmount = + const counterpartyEffectiveDepositAmount = await getCounterpartyEffectiveDepositAmount( ws, p.targetType, @@ -698,7 +719,7 @@ export async function createDepositGroup( merchantPub: merchantPair.pub, totalPayCost: Amounts.stringify(totalDepositCost), effectiveDepositAmount: Amounts.stringify( - countarpartyEffectiveDepositAmount, + counterpartyEffectiveDepositAmount, ), wire: { payto_uri: req.depositPaytoUri, @@ -707,6 +728,11 @@ export async function createDepositGroup( operationStatus: OperationStatus.Pending, }; + const transactionId = constructTransactionIdentifier({ + tag: TransactionType.Deposit, + depositGroupId, + }); + await ws.db .mktx((x) => [ x.depositGroups, @@ -718,7 +744,7 @@ export async function createDepositGroup( ]) .runReadWrite(async (tx) => { await spendCoins(ws, tx, { - allocationId: `txn:deposit:${depositGroup.depositGroupId}`, + allocationId: transactionId, coinPubs: payCoinSel.coinSel.coinPubs, contributions: payCoinSel.coinSel.coinContributions.map((x) => Amounts.parseOrThrow(x), @@ -729,8 +755,8 @@ export async function createDepositGroup( }); return { - depositGroupId: depositGroupId, - transactionId: makeTransactionId(TransactionType.Deposit, depositGroupId), + depositGroupId, + transactionId, }; } diff --git a/packages/taler-wallet-core/src/operations/pay-merchant.ts b/packages/taler-wallet-core/src/operations/pay-merchant.ts index 2419d32a2..e79314416 100644 --- a/packages/taler-wallet-core/src/operations/pay-merchant.ts +++ b/packages/taler-wallet-core/src/operations/pay-merchant.ts @@ -1459,7 +1459,9 @@ export async function processPurchasePay( ); const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], () => - ws.http.postJson(payUrl, reqBody, { + ws.http.fetch(payUrl, { + method: "POST", + body: reqBody, timeout: getPayRequestTimeout(purchase), }), ); diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index 3ace5bf71..6a71b5c1e 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -34,6 +34,7 @@ import { TalerProtocolTimestamp, Transaction, TransactionByIdRequest, + TransactionIdStr, TransactionsRequest, TransactionsResponse, TransactionState, @@ -1428,7 +1429,7 @@ export type ParsedTransactionIdentifier = export function constructTransactionIdentifier( pTxId: ParsedTransactionIdentifier, -): string { +): TransactionIdStr { switch (pTxId.tag) { case TransactionType.Deposit: return `txn:${pTxId.tag}:${pTxId.depositGroupId}`; diff --git a/packages/taler-wallet-core/src/wallet.ts b/packages/taler-wallet-core/src/wallet.ts index d554e5b83..435a4e241 100644 --- a/packages/taler-wallet-core/src/wallet.ts +++ b/packages/taler-wallet-core/src/wallet.ts @@ -1335,7 +1335,9 @@ async function dispatchRequestInternal( return await prepareDepositGroup(ws, req); } case WalletApiOperation.GenerateDepositGroupTxId: - return generateDepositGroupTxId(); + return { + transactionId: generateDepositGroupTxId(), + }; case WalletApiOperation.CreateDepositGroup: { const req = codecForCreateDepositGroupRequest().decode(payload); return await createDepositGroup(ws, req); -- cgit v1.2.3