From b648238c4120ed341c76818b4ffa223d0122af78 Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Tue, 21 Feb 2023 21:02:36 +0100 Subject: harness: improve peer-pull integration test, check notifications --- packages/taler-harness/src/harness/helpers.ts | 35 +++++++++- .../src/integrationtests/test-peer-to-peer-pull.ts | 74 +++++++++++++++++----- packages/taler-util/src/notifications.ts | 15 ++++- .../taler-wallet-core/src/operations/pay-peer.ts | 17 +++-- .../src/operations/transactions.ts | 30 +++++++++ 5 files changed, 147 insertions(+), 24 deletions(-) diff --git a/packages/taler-harness/src/harness/helpers.ts b/packages/taler-harness/src/harness/helpers.ts index c1ce463fa..d203cc608 100644 --- a/packages/taler-harness/src/harness/helpers.ts +++ b/packages/taler-harness/src/harness/helpers.ts @@ -31,6 +31,7 @@ import { PreparePayResultType, NotificationType, WithdrawalGroupFinishedNotification, + WalletNotification, } from "@gnu-taler/taler-util"; import { BankAccessApi, @@ -297,7 +298,6 @@ export async function createSimpleTestkudosEnvironmentV2( const walletService = new WalletService(t, { name: "wallet", - useInMemoryDb: true, }); await walletService.start(); await walletService.pingUntilAvailable(); @@ -326,6 +326,39 @@ export async function createSimpleTestkudosEnvironmentV2( }; } +export interface CreateWalletArgs { + handleNotification?(wn: WalletNotification): void; + name: string; +} + +export async function createWalletDaemonWithClient( + t: GlobalTestState, + args: CreateWalletArgs, +): Promise<{ walletClient: WalletClient; walletService: WalletService }> { + const walletService = new WalletService(t, { + name: "wallet", + useInMemoryDb: true, + }); + await walletService.start(); + await walletService.pingUntilAvailable(); + + const walletClient = new WalletClient({ + unixPath: walletService.socketPath, + onNotification(n) { + console.log("got notification", n); + if (args.handleNotification) { + args.handleNotification(n); + } + }, + }); + await walletClient.connect(); + await walletClient.client.call(WalletApiOperation.InitWallet, { + skipDefaults: true, + }); + + return { walletClient, walletService }; +} + export interface FaultyMerchantTestEnvironment { commonDb: DbInfo; bank: BankService; diff --git a/packages/taler-harness/src/integrationtests/test-peer-to-peer-pull.ts b/packages/taler-harness/src/integrationtests/test-peer-to-peer-pull.ts index 0bab14578..58a1f271e 100644 --- a/packages/taler-harness/src/integrationtests/test-peer-to-peer-pull.ts +++ b/packages/taler-harness/src/integrationtests/test-peer-to-peer-pull.ts @@ -17,12 +17,21 @@ /** * Imports. */ -import { AbsoluteTime, Duration, j2s } from "@gnu-taler/taler-util"; +import { + AbsoluteTime, + Duration, + j2s, + NotificationType, + WalletNotification, +} from "@gnu-taler/taler-util"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { GlobalTestState, WalletCli } from "../harness/harness.js"; import { createSimpleTestkudosEnvironment, + createSimpleTestkudosEnvironmentV2, + createWalletDaemonWithClient, withdrawViaBank, + withdrawViaBankV2, } from "../harness/helpers.js"; /** @@ -31,19 +40,40 @@ import { export async function runPeerToPeerPullTest(t: GlobalTestState) { // Set up test environment - const { bank, exchange } = await createSimpleTestkudosEnvironment(t); + const { bank, exchange } = await createSimpleTestkudosEnvironmentV2(t); + + let allW1Notifications: WalletNotification[] = []; + let allW2Notifications: WalletNotification[] = []; + + const w1 = await createWalletDaemonWithClient(t, { + name: "w1", + handleNotification(wn) { + allW1Notifications.push(wn); + }, + }); + const w2 = await createWalletDaemonWithClient(t, { + name: "w2", + handleNotification(wn) { + allW2Notifications.push(wn); + }, + }); // Withdraw digital cash into the wallet. - const wallet1 = new WalletCli(t, "w1"); - const wallet2 = new WalletCli(t, "w2"); - await withdrawViaBank(t, { - wallet: wallet2, + const wallet1 = w1.walletClient; + const wallet2 = w2.walletClient; + + const withdrawalDoneCond = wallet2.waitForNotificationCond( + (x) => x.type === NotificationType.WithdrawGroupFinished, + ); + + await withdrawViaBankV2(t, { + walletClient: wallet2, bank, exchange, amount: "TESTKUDOS:20", }); - await wallet1.runUntilDone(); + await withdrawalDoneCond; const purse_expiration = AbsoluteTime.toTimestamp( AbsoluteTime.addDuration( @@ -52,6 +82,10 @@ export async function runPeerToPeerPullTest(t: GlobalTestState) { ), ); + const peerPullCreditReadyCond = wallet2.waitForNotificationCond( + (x) => x.type === NotificationType.PeerPullCreditReady, + ); + const resp = await wallet1.client.call( WalletApiOperation.InitiatePeerPullCredit, { @@ -64,9 +98,7 @@ export async function runPeerToPeerPullTest(t: GlobalTestState) { }, ); - // Wait until the initiation is actually done. - // FIXME: Use the daemonized wallet and notifications to know this - await wallet1.runPending(); + await peerPullCreditReadyCond; const checkResp = await wallet2.client.call( WalletApiOperation.PreparePeerPullDebit, @@ -77,20 +109,23 @@ export async function runPeerToPeerPullTest(t: GlobalTestState) { console.log(`checkResp: ${j2s(checkResp)}`); - const acceptResp = await wallet2.client.call( - WalletApiOperation.ConfirmPeerPullDebit, - { - peerPullPaymentIncomingId: checkResp.peerPullPaymentIncomingId, - }, + // FIXME: The wallet should emit a more appropriate notification here. + // Yes, it's technically a withdrawal. + const peerPullCreditDoneCond = wallet1.waitForNotificationCond( + (x) => x.type === NotificationType.WithdrawGroupFinished, ); - await wallet1.runUntilDone(); - await wallet2.runUntilDone(); + await wallet2.client.call(WalletApiOperation.ConfirmPeerPullDebit, { + peerPullPaymentIncomingId: checkResp.peerPullPaymentIncomingId, + }); + + await peerPullCreditDoneCond; const txn1 = await wallet1.client.call( WalletApiOperation.GetTransactions, {}, ); + const txn2 = await wallet2.client.call( WalletApiOperation.GetTransactions, {}, @@ -98,6 +133,11 @@ export async function runPeerToPeerPullTest(t: GlobalTestState) { console.log(`txn1: ${j2s(txn1)}`); console.log(`txn2: ${j2s(txn2)}`); + + console.log(`w1 notifications: ${j2s(allW1Notifications)}`); + + // Check that we don't have an excessive number of notifications. + t.assertTrue(allW1Notifications.length <= 60); } runPeerToPeerPullTest.suites = ["wallet"]; diff --git a/packages/taler-util/src/notifications.ts b/packages/taler-util/src/notifications.ts index 55d838007..51d573a98 100644 --- a/packages/taler-util/src/notifications.ts +++ b/packages/taler-util/src/notifications.ts @@ -65,6 +65,7 @@ export enum NotificationType { WithdrawalGroupKycRequested = "withdrawal-group-kyc-requested", WithdrawalGroupBankConfirmed = "withdrawal-group-bank-confirmed", WithdrawalGroupReserveReady = "withdrawal-group-reserve-ready", + PeerPullCreditReady = "peer-pull-credit-ready", DepositOperationError = "deposit-operation-error", } @@ -135,11 +136,20 @@ export interface WithdrawalGroupBankConfirmed { transactionId: string; } -export interface WithdrawalGroupReserveReady { +export interface WithdrawalGroupReserveReadyNotification { type: NotificationType.WithdrawalGroupReserveReady; transactionId: string; } +/** + * The purse creation of a peer-pull-credit transaction + * is done, and the other party can now pay. + */ +export interface PeerPullCreditReadyNotification { + type: NotificationType.PeerPullCreditReady; + transactionId: string; +} + export interface RefreshRevealedNotification { type: NotificationType.RefreshRevealed; } @@ -316,4 +326,5 @@ export type WalletNotification = | PayOperationSuccessNotification | WithdrawalGroupKycRequested | WithdrawalGroupBankConfirmed - | WithdrawalGroupReserveReady; + | WithdrawalGroupReserveReadyNotification + | PeerPullCreditReadyNotification; diff --git a/packages/taler-wallet-core/src/operations/pay-peer.ts b/packages/taler-wallet-core/src/operations/pay-peer.ts index 8bde47df4..541e96280 100644 --- a/packages/taler-wallet-core/src/operations/pay-peer.ts +++ b/packages/taler-wallet-core/src/operations/pay-peer.ts @@ -72,6 +72,7 @@ import { codecOptional, codecForTimestamp, CancellationToken, + NotificationType, } from "@gnu-taler/taler-util"; import { SpendCoinDetails } from "../crypto/cryptoImplementation.js"; import { @@ -119,7 +120,10 @@ import { processWithdrawalGroup, } from "./withdraw.js"; import { PendingTaskType } from "../pending-types.js"; -import { stopLongpolling } from "./transactions.js"; +import { + constructTransactionIdentifier, + stopLongpolling, +} from "./transactions.js"; const logger = new Logger("operations/peer-to-peer.ts"); @@ -1507,6 +1511,14 @@ export async function processPeerPullCredit( await tx.peerPullPaymentInitiations.put(pi2); }); + ws.notify({ + type: NotificationType.PeerPullCreditReady, + transactionId: constructTransactionIdentifier({ + tag: TransactionType.PeerPullCredit, + pursePub: pullIni.pursePub, + }), + }); + return { type: OperationAttemptResultType.Finished, result: undefined, @@ -1626,9 +1638,6 @@ export async function initiatePeerPullPayment( const pursePair = await ws.cryptoApi.createEddsaKeypair({}); const mergePair = await ws.cryptoApi.createEddsaKeypair({}); - const instructedAmount = Amounts.parseOrThrow( - req.partialContractTerms.amount, - ); const contractTerms = req.partialContractTerms; const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms); diff --git a/packages/taler-wallet-core/src/operations/transactions.ts b/packages/taler-wallet-core/src/operations/transactions.ts index 1c2ce34bb..764115cef 100644 --- a/packages/taler-wallet-core/src/operations/transactions.ts +++ b/packages/taler-wallet-core/src/operations/transactions.ts @@ -64,6 +64,7 @@ import { } from "../db.js"; import { InternalWalletState } from "../internal-wallet-state.js"; import { PendingTaskType } from "../pending-types.js"; +import { assertUnreachable } from "../util/assertUnreachable.js"; import { checkDbInvariant } from "../util/invariants.js"; import { constructTaskIdentifier, TaskIdentifiers } from "../util/retries.js"; import { @@ -1376,6 +1377,35 @@ export type ParsedTransactionIdentifier = | { tag: TransactionType.Tip; walletTipId: string } | { tag: TransactionType.Withdrawal; withdrawalGroupId: string }; +export function constructTransactionIdentifier( + pTxId: ParsedTransactionIdentifier, +): string { + switch (pTxId.tag) { + case TransactionType.Deposit: + return `txn:${pTxId.tag}:${pTxId.depositGroupId}`; + case TransactionType.Payment: + return `txn:${pTxId.tag}:${pTxId.proposalId}`; + case TransactionType.PeerPullCredit: + return `txn:${pTxId.tag}:${pTxId.pursePub}`; + case TransactionType.PeerPullDebit: + return `txn:${pTxId.tag}:${pTxId.peerPullPaymentIncomingId}`; + case TransactionType.PeerPushCredit: + return `txn:${pTxId.tag}:${pTxId.peerPushPaymentIncomingId}`; + case TransactionType.PeerPushDebit: + return `txn:${pTxId.tag}:${pTxId.pursePub}`; + case TransactionType.Refresh: + return `txn:${pTxId.tag}:${pTxId.refreshGroupId}`; + case TransactionType.Refund: + return `txn:${pTxId.tag}:${pTxId.proposalId}:${pTxId.executionTime}`; + case TransactionType.Tip: + return `txn:${pTxId.tag}:${pTxId.walletTipId}`; + case TransactionType.Withdrawal: + return `txn:${pTxId.tag}:${pTxId.withdrawalGroupId}`; + default: + assertUnreachable(pTxId); + } +} + /** * Parse a transaction identifier string into a typed, structured representation. */ -- cgit v1.2.3