wallet-harness: make sure events are not lost in deposit test

This commit is contained in:
Florian Dold 2023-04-21 22:02:34 +02:00
parent fc2adae6bd
commit e81ae0f3e5
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
8 changed files with 69 additions and 14 deletions

View File

@ -2062,6 +2062,7 @@ export class WalletService {
[ [
"--wallet-db", "--wallet-db",
dbPath, dbPath,
"-LDEBUG", // FIXME: Make this configurable?
"--no-throttle", // FIXME: Optionally do throttling for some tests? "--no-throttle", // FIXME: Optionally do throttling for some tests?
"advanced", "advanced",
"serve", "serve",

View File

@ -45,9 +45,17 @@ export async function runDepositTest(t: GlobalTestState) {
await withdrawalResult.withdrawalFinishedCond; 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) =>
n.type == NotificationType.TransactionStateTransition && n.type == NotificationType.TransactionStateTransition &&
n.transactionId == depositTxId &&
n.newTxState == TransactionState.Done, n.newTxState == TransactionState.Done,
); );
@ -56,9 +64,14 @@ export async function runDepositTest(t: GlobalTestState) {
{ {
amount: "TESTKUDOS:10", amount: "TESTKUDOS:10",
depositPaytoUri: getPayto("foo"), depositPaytoUri: getPayto("foo"),
transactionId: depositTxId,
}, },
); );
t.assertDeepEqual(depositGroupResult.transactionId, depositTxId);
await depositDone;
const transactions = await walletClient.client.call( const transactions = await walletClient.client.call(
WalletApiOperation.GetTransactions, WalletApiOperation.GetTransactions,
{}, {},

View File

@ -1708,6 +1708,13 @@ export interface DepositGroupFees {
} }
export interface CreateDepositGroupRequest { 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; depositPaytoUri: string;
amount: AmountString; amount: AmountString;
} }
@ -1733,6 +1740,7 @@ export const codecForCreateDepositGroupRequest =
buildCodecForObject<CreateDepositGroupRequest>() buildCodecForObject<CreateDepositGroupRequest>()
.property("amount", codecForAmountString()) .property("amount", codecForAmountString())
.property("depositPaytoUri", codecForString()) .property("depositPaytoUri", codecForString())
.property("transactionId", codecOptional(codecForString()))
.build("CreateDepositGroupRequest"); .build("CreateDepositGroupRequest");
export interface CreateDepositGroupResponse { export interface CreateDepositGroupResponse {

View File

@ -865,6 +865,7 @@ export enum DepositGroupOperationStatus {
AbortingWithRefresh = 11 /* ACTIVE_START + 1 */, AbortingWithRefresh = 11 /* ACTIVE_START + 1 */,
} }
// FIXME: Improve name! This enum is very specific to deposits.
export enum TransactionStatus { export enum TransactionStatus {
Unknown = 10, Unknown = 10,
Accepted = 20, Accepted = 20,
@ -1380,6 +1381,7 @@ export type WgInfo =
| WgInfoBankRecoup; | WgInfoBankRecoup;
export type KycUserType = "individual" | "business"; export type KycUserType = "individual" | "business";
export interface KycPendingInfo { export interface KycPendingInfo {
paytoHash: string; paytoHash: string;
requirementRow: number; requirementRow: number;

View File

@ -67,7 +67,7 @@ import { getTotalRefreshCost, KycPendingInfo, KycUserType } from "../index.js";
import { InternalWalletState } from "../internal-wallet-state.js"; import { InternalWalletState } from "../internal-wallet-state.js";
import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http"; import { readSuccessResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
import { OperationAttemptResult } from "../util/retries.js"; import { OperationAttemptResult } from "../util/retries.js";
import { makeTransactionId, spendCoins } from "./common.js"; import { spendCoins } from "./common.js";
import { getExchangeDetails } from "./exchanges.js"; import { getExchangeDetails } from "./exchanges.js";
import { import {
extractContractData, extractContractData,
@ -75,13 +75,20 @@ import {
getTotalPaymentCost, getTotalPaymentCost,
} from "./pay-merchant.js"; } from "./pay-merchant.js";
import { selectPayCoinsNew } from "../util/coinSelection.js"; import { selectPayCoinsNew } from "../util/coinSelection.js";
import { constructTransactionIdentifier } from "./transactions.js"; import {
constructTransactionIdentifier,
parseTransactionIdentifier,
} from "./transactions.js";
/** /**
* Logger. * Logger.
*/ */
const logger = new Logger("deposits.ts"); 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( export async function computeDepositTransactionStatus(
ws: InternalWalletState, ws: InternalWalletState,
dg: DepositGroupRecord, 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? * FIXME: Why does this throw an exception when KYC is required?
* Should we not return some proper result record here? * Should we not return some proper result record here?
@ -221,6 +229,7 @@ export async function processDepositGroup(
// Check for cancellation before expensive operations. // Check for cancellation before expensive operations.
options.cancellationToken?.throwIfCancelled(); options.cancellationToken?.throwIfCancelled();
// FIXME: Cache these!
const depositPermissions = await generateDepositPermissions( const depositPermissions = await generateDepositPermissions(
ws, ws,
depositGroup.payCoinSelection, depositGroup.payCoinSelection,
@ -438,7 +447,7 @@ async function trackDepositPermission(
wireHash, wireHash,
}); });
url.searchParams.set("merchant_sig", sigResp.sig); 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) { switch (httpResp.status) {
case HttpStatusCode.Accepted: { case HttpStatusCode.Accepted: {
const accepted = await readSuccessResponseJsonOrThrow( 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, * FIXME: This should be renamed to checkDepositGroup,
* as it doesn't prepare anything * as it doesn't prepare anything
*/ */
@ -671,9 +683,18 @@ export async function createDepositGroup(
const totalDepositCost = await getTotalPaymentCost(ws, payCoinSel.coinSel); 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( await getCounterpartyEffectiveDepositAmount(
ws, ws,
p.targetType, p.targetType,
@ -698,7 +719,7 @@ export async function createDepositGroup(
merchantPub: merchantPair.pub, merchantPub: merchantPair.pub,
totalPayCost: Amounts.stringify(totalDepositCost), totalPayCost: Amounts.stringify(totalDepositCost),
effectiveDepositAmount: Amounts.stringify( effectiveDepositAmount: Amounts.stringify(
countarpartyEffectiveDepositAmount, counterpartyEffectiveDepositAmount,
), ),
wire: { wire: {
payto_uri: req.depositPaytoUri, payto_uri: req.depositPaytoUri,
@ -707,6 +728,11 @@ export async function createDepositGroup(
operationStatus: OperationStatus.Pending, operationStatus: OperationStatus.Pending,
}; };
const transactionId = constructTransactionIdentifier({
tag: TransactionType.Deposit,
depositGroupId,
});
await ws.db await ws.db
.mktx((x) => [ .mktx((x) => [
x.depositGroups, x.depositGroups,
@ -718,7 +744,7 @@ export async function createDepositGroup(
]) ])
.runReadWrite(async (tx) => { .runReadWrite(async (tx) => {
await spendCoins(ws, tx, { await spendCoins(ws, tx, {
allocationId: `txn:deposit:${depositGroup.depositGroupId}`, allocationId: transactionId,
coinPubs: payCoinSel.coinSel.coinPubs, coinPubs: payCoinSel.coinSel.coinPubs,
contributions: payCoinSel.coinSel.coinContributions.map((x) => contributions: payCoinSel.coinSel.coinContributions.map((x) =>
Amounts.parseOrThrow(x), Amounts.parseOrThrow(x),
@ -729,8 +755,8 @@ export async function createDepositGroup(
}); });
return { return {
depositGroupId: depositGroupId, depositGroupId,
transactionId: makeTransactionId(TransactionType.Deposit, depositGroupId), transactionId,
}; };
} }

View File

@ -1459,7 +1459,9 @@ export async function processPurchasePay(
); );
const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], () => const resp = await ws.runSequentialized([EXCHANGE_COINS_LOCK], () =>
ws.http.postJson(payUrl, reqBody, { ws.http.fetch(payUrl, {
method: "POST",
body: reqBody,
timeout: getPayRequestTimeout(purchase), timeout: getPayRequestTimeout(purchase),
}), }),
); );

View File

@ -34,6 +34,7 @@ import {
TalerProtocolTimestamp, TalerProtocolTimestamp,
Transaction, Transaction,
TransactionByIdRequest, TransactionByIdRequest,
TransactionIdStr,
TransactionsRequest, TransactionsRequest,
TransactionsResponse, TransactionsResponse,
TransactionState, TransactionState,
@ -1428,7 +1429,7 @@ export type ParsedTransactionIdentifier =
export function constructTransactionIdentifier( export function constructTransactionIdentifier(
pTxId: ParsedTransactionIdentifier, pTxId: ParsedTransactionIdentifier,
): string { ): TransactionIdStr {
switch (pTxId.tag) { switch (pTxId.tag) {
case TransactionType.Deposit: case TransactionType.Deposit:
return `txn:${pTxId.tag}:${pTxId.depositGroupId}`; return `txn:${pTxId.tag}:${pTxId.depositGroupId}`;

View File

@ -1335,7 +1335,9 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
return await prepareDepositGroup(ws, req); return await prepareDepositGroup(ws, req);
} }
case WalletApiOperation.GenerateDepositGroupTxId: case WalletApiOperation.GenerateDepositGroupTxId:
return generateDepositGroupTxId(); return {
transactionId: generateDepositGroupTxId(),
};
case WalletApiOperation.CreateDepositGroup: { case WalletApiOperation.CreateDepositGroup: {
const req = codecForCreateDepositGroupRequest().decode(payload); const req = codecForCreateDepositGroupRequest().decode(payload);
return await createDepositGroup(ws, req); return await createDepositGroup(ws, req);