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",
dbPath,
"-LDEBUG", // FIXME: Make this configurable?
"--no-throttle", // FIXME: Optionally do throttling for some tests?
"advanced",
"serve",

View File

@ -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,
{},

View File

@ -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<CreateDepositGroupRequest>()
.property("amount", codecForAmountString())
.property("depositPaytoUri", codecForString())
.property("transactionId", codecOptional(codecForString()))
.build("CreateDepositGroupRequest");
export interface CreateDepositGroupResponse {

View File

@ -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;

View File

@ -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,
};
}

View File

@ -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),
}),
);

View File

@ -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}`;

View File

@ -1335,7 +1335,9 @@ async function dispatchRequestInternal<Op extends WalletApiOperation>(
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);