From 332745862e728dc5e79a424698b2736c4f2683bf Mon Sep 17 00:00:00 2001 From: Florian Dold Date: Mon, 14 Mar 2022 18:31:30 +0100 Subject: wallet: towards db-less benchmarking, some refactoring --- packages/taler-wallet-cli/src/bench2.ts | 106 ++++++ packages/taler-wallet-cli/src/harness/harness.ts | 187 +---------- packages/taler-wallet-cli/src/harness/helpers.ts | 5 +- .../src/integrationtests/test-bank-api.ts | 12 +- .../integrationtests/test-exchange-management.ts | 8 +- .../src/integrationtests/test-libeufin-basic.ts | 16 +- .../integrationtests/test-merchant-refund-api.ts | 23 +- .../src/integrationtests/test-payment-fault.ts | 11 +- .../src/integrationtests/test-payment-on-demo.ts | 54 ++-- .../src/integrationtests/test-tipping.ts | 17 +- .../src/integrationtests/test-wallet-dbless.ts | 358 +++++++++++++++++++++ .../integrationtests/test-withdrawal-abort-bank.ts | 8 +- .../test-withdrawal-bank-integrated.ts | 29 +- .../integrationtests/test-withdrawal-fakebank.ts | 2 - .../src/integrationtests/test-withdrawal-manual.ts | 24 +- .../src/integrationtests/testrunner.ts | 2 + 16 files changed, 590 insertions(+), 272 deletions(-) create mode 100644 packages/taler-wallet-cli/src/bench2.ts create mode 100644 packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts (limited to 'packages/taler-wallet-cli') diff --git a/packages/taler-wallet-cli/src/bench2.ts b/packages/taler-wallet-cli/src/bench2.ts new file mode 100644 index 000000000..884708207 --- /dev/null +++ b/packages/taler-wallet-cli/src/bench2.ts @@ -0,0 +1,106 @@ +/* + This file is part of GNU Taler + (C) 2022 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see + */ + +/** + * Imports. + */ +import { + buildCodecForObject, + codecForNumber, + codecForString, + codecOptional, + j2s, + Logger, +} from "@gnu-taler/taler-util"; +import { + getDefaultNodeWallet2, + NodeHttpLib, + WalletApiOperation, + Wallet, + AccessStats, + downloadExchangeInfo, +} from "@gnu-taler/taler-wallet-core"; + +/** + * Entry point for the benchmark. + * + * The benchmark runs against an existing Taler deployment and does not + * set up its own services. + */ +export async function runBench2(configJson: any): Promise { + const logger = new Logger("Bench1"); + + // Validate the configuration file for this benchmark. + const benchConf = codecForBench1Config().decode(configJson); + + const myHttpLib = new NodeHttpLib(); + myHttpLib.setThrottling(false); + + const exchangeInfo = await downloadExchangeInfo( + benchConf.exchange, + myHttpLib, + ); +} + +/** + * Format of the configuration file passed to the benchmark + */ +interface Bench2Config { + /** + * Base URL of the bank. + */ + bank: string; + + /** + * Payto url for deposits. + */ + payto: string; + + /** + * Base URL of the exchange. + */ + exchange: string; + + /** + * How many withdraw/deposit iterations should be made? + * Defaults to 1. + */ + iterations?: number; + + currency: string; + + deposits?: number; + + /** + * How any iterations run until the wallet db gets purged + * Defaults to 20. + */ + restartAfter?: number; +} + +/** + * Schema validation codec for Bench1Config. + */ +const codecForBench1Config = () => + buildCodecForObject() + .property("bank", codecForString()) + .property("payto", codecForString()) + .property("exchange", codecForString()) + .property("iterations", codecOptional(codecForNumber())) + .property("deposits", codecOptional(codecForNumber())) + .property("currency", codecForString()) + .property("restartAfter", codecOptional(codecForNumber())) + .build("Bench1Config"); diff --git a/packages/taler-wallet-cli/src/harness/harness.ts b/packages/taler-wallet-cli/src/harness/harness.ts index f4e422690..63bb17fcc 100644 --- a/packages/taler-wallet-cli/src/harness/harness.ts +++ b/packages/taler-wallet-cli/src/harness/harness.ts @@ -45,6 +45,9 @@ import { MerchantInstancesResponse, } from "./merchantApiTypes"; import { + BankServiceHandle, + HarnessExchangeBankAccount, + NodeHttpLib, openPromise, OperationFailedError, WalletCoreApiClient, @@ -468,164 +471,6 @@ export async function pingProc( } } -export interface HarnessExchangeBankAccount { - accountName: string; - accountPassword: string; - accountPaytoUri: string; - wireGatewayApiBaseUrl: string; -} - -export interface BankServiceInterface { - readonly baseUrl: string; - readonly port: number; -} - -export enum CreditDebitIndicator { - Credit = "credit", - Debit = "debit", -} - -export interface BankAccountBalanceResponse { - balance: { - amount: AmountString; - credit_debit_indicator: CreditDebitIndicator; - }; -} - -export namespace BankAccessApi { - export async function getAccountBalance( - bank: BankServiceInterface, - bankUser: BankUser, - ): Promise { - const url = new URL(`accounts/${bankUser.username}`, bank.baseUrl); - const resp = await axios.get(url.href, { - auth: bankUser, - }); - return resp.data; - } - - export async function createWithdrawalOperation( - bank: BankServiceInterface, - bankUser: BankUser, - amount: string, - ): Promise { - const url = new URL( - `accounts/${bankUser.username}/withdrawals`, - bank.baseUrl, - ); - const resp = await axios.post( - url.href, - { - amount, - }, - { - auth: bankUser, - }, - ); - return codecForWithdrawalOperationInfo().decode(resp.data); - } -} - -export namespace BankApi { - export async function registerAccount( - bank: BankServiceInterface, - username: string, - password: string, - ): Promise { - const url = new URL("testing/register", bank.baseUrl); - let resp = await axios.post(url.href, { - username, - password, - }); - let paytoUri = `payto://x-taler-bank/localhost/${username}`; - if (process.env.WALLET_HARNESS_WITH_EUFIN) { - paytoUri = resp.data.paytoUri; - } - return { - password, - username, - accountPaytoUri: paytoUri, - }; - } - - export async function createRandomBankUser( - bank: BankServiceInterface, - ): Promise { - const username = "user-" + encodeCrock(getRandomBytes(10)).toLowerCase(); - const password = "pw-" + encodeCrock(getRandomBytes(10)).toLowerCase(); - return await registerAccount(bank, username, password); - } - - export async function adminAddIncoming( - bank: BankServiceInterface, - params: { - exchangeBankAccount: HarnessExchangeBankAccount; - amount: string; - reservePub: string; - debitAccountPayto: string; - }, - ) { - let maybeBaseUrl = bank.baseUrl; - if (process.env.WALLET_HARNESS_WITH_EUFIN) { - maybeBaseUrl = (bank as EufinBankService).baseUrlDemobank; - } - let url = new URL( - `taler-wire-gateway/${params.exchangeBankAccount.accountName}/admin/add-incoming`, - maybeBaseUrl, - ); - await axios.post( - url.href, - { - amount: params.amount, - reserve_pub: params.reservePub, - debit_account: params.debitAccountPayto, - }, - { - auth: { - username: params.exchangeBankAccount.accountName, - password: params.exchangeBankAccount.accountPassword, - }, - }, - ); - } - - export async function confirmWithdrawalOperation( - bank: BankServiceInterface, - bankUser: BankUser, - wopi: WithdrawalOperationInfo, - ): Promise { - const url = new URL( - `accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/confirm`, - bank.baseUrl, - ); - await axios.post( - url.href, - {}, - { - auth: bankUser, - }, - ); - } - - export async function abortWithdrawalOperation( - bank: BankServiceInterface, - bankUser: BankUser, - wopi: WithdrawalOperationInfo, - ): Promise { - const url = new URL( - `accounts/${bankUser.username}/withdrawals/${wopi.withdrawal_id}/abort`, - bank.baseUrl, - ); - await axios.post( - url.href, - {}, - { - auth: bankUser, - }, - ); - } -} - class BankServiceBase { proc: ProcessWrapper | undefined; @@ -640,10 +485,12 @@ class BankServiceBase { * Work in progress. The key point is that both Sandbox and Nexus * will be configured and started by this class. */ -class EufinBankService extends BankServiceBase implements BankServiceInterface { +class EufinBankService extends BankServiceBase implements BankServiceHandle { sandboxProc: ProcessWrapper | undefined; nexusProc: ProcessWrapper | undefined; + http = new NodeHttpLib(); + static async create( gc: GlobalTestState, bc: BankConfig, @@ -914,9 +761,11 @@ class EufinBankService extends BankServiceBase implements BankServiceInterface { } } -class PybankService extends BankServiceBase implements BankServiceInterface { +class PybankService extends BankServiceBase implements BankServiceHandle { proc: ProcessWrapper | undefined; + http = new NodeHttpLib(); + static async create( gc: GlobalTestState, bc: BankConfig, @@ -955,6 +804,7 @@ class PybankService extends BankServiceBase implements BankServiceInterface { const config = Configuration.load(this.configFile); config.setString("bank", "suggested_exchange", e.baseUrl); config.setString("bank", "suggested_exchange_payto", exchangePayto); + config.write(this.configFile); } get baseUrl(): string { @@ -1087,23 +937,6 @@ export class FakeBankService { } } -export interface BankUser { - username: string; - password: string; - accountPaytoUri: string; -} - -export interface WithdrawalOperationInfo { - withdrawal_id: string; - taler_withdraw_uri: string; -} - -const codecForWithdrawalOperationInfo = (): Codec => - buildCodecForObject() - .property("withdrawal_id", codecForString()) - .property("taler_withdraw_uri", codecForString()) - .build("WithdrawalOperationInfo"); - export interface ExchangeConfig { name: string; currency: string; diff --git a/packages/taler-wallet-cli/src/harness/helpers.ts b/packages/taler-wallet-cli/src/harness/helpers.ts index f19c6a115..117bcdcf8 100644 --- a/packages/taler-wallet-cli/src/harness/helpers.ts +++ b/packages/taler-wallet-cli/src/harness/helpers.ts @@ -30,22 +30,19 @@ import { Duration, PreparePayResultType, } from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { BankAccessApi, BankApi, HarnessExchangeBankAccount, WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { CoinConfig, defaultCoinConfig } from "./denomStructures.js"; import { FaultInjectedExchangeService, FaultInjectedMerchantService, } from "./faultInjection.js"; import { - BankAccessApi, - BankApi, BankService, DbInfo, ExchangeService, ExchangeServiceInterface, getPayto, GlobalTestState, - HarnessExchangeBankAccount, MerchantPrivateApi, MerchantService, MerchantServiceInterface, diff --git a/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts b/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts index 2259dd8bb..8e4109752 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-bank-api.ts @@ -24,13 +24,15 @@ import { setupDb, BankService, MerchantService, - BankApi, - BankAccessApi, - CreditDebitIndicator, - getPayto + getPayto, } from "../harness/harness.js"; import { createEddsaKeyPair, encodeCrock } from "@gnu-taler/taler-util"; import { defaultCoinConfig } from "../harness/denomStructures"; +import { + BankApi, + BankAccessApi, + CreditDebitIndicator, +} from "@gnu-taler/taler-wallet-core"; /** * Run test for basic, bank-integrated withdrawal. @@ -97,8 +99,6 @@ export async function runBankApiTest(t: GlobalTestState) { console.log("setup done!"); - const wallet = new WalletCli(t); - const bankUser = await BankApi.registerAccount(bank, "user1", "pw1"); // Make sure that registering twice results in a 409 Conflict diff --git a/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts b/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts index 91e9bdec5..f9c7c4b99 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-exchange-management.ts @@ -24,11 +24,13 @@ import { BankService, ExchangeService, MerchantService, + getPayto, +} from "../harness/harness.js"; +import { + WalletApiOperation, BankApi, BankAccessApi, - getPayto -} from "../harness/harness.js"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +} from "@gnu-taler/taler-wallet-core"; import { ExchangesListRespose, URL, diff --git a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts index 3f7e1a9d1..33aad80d2 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-libeufin-basic.ts @@ -19,15 +19,16 @@ */ import { ContractTerms, - CoreApiResponse, getTimestampNow, timestampTruncateToSecond, } from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { + WalletApiOperation, + HarnessExchangeBankAccount, +} from "@gnu-taler/taler-wallet-core"; import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures"; import { DbInfo, - HarnessExchangeBankAccount, ExchangeService, GlobalTestState, MerchantService, @@ -233,13 +234,8 @@ export async function createLibeufinTestEnvironment( export async function runLibeufinBasicTest(t: GlobalTestState) { // Set up test environment - const { - wallet, - exchange, - merchant, - libeufinSandbox, - libeufinNexus, - } = await createLibeufinTestEnvironment(t); + const { wallet, exchange, merchant, libeufinSandbox, libeufinNexus } = + await createLibeufinTestEnvironment(t); await wallet.client.call(WalletApiOperation.AddExchange, { exchangeBaseUrl: exchange.baseUrl, diff --git a/packages/taler-wallet-cli/src/integrationtests/test-merchant-refund-api.ts b/packages/taler-wallet-cli/src/integrationtests/test-merchant-refund-api.ts index 466b1efbd..a9dbeef9a 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-merchant-refund-api.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-merchant-refund-api.ts @@ -20,25 +20,30 @@ import { GlobalTestState, MerchantPrivateApi, - BankServiceInterface, MerchantServiceInterface, WalletCli, ExchangeServiceInterface, } from "../harness/harness.js"; -import { createSimpleTestkudosEnvironment, withdrawViaBank } from "../harness/helpers.js"; +import { + createSimpleTestkudosEnvironment, + withdrawViaBank, +} from "../harness/helpers.js"; import { URL, durationFromSpec, PreparePayResultType, } from "@gnu-taler/taler-util"; import axios from "axios"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { + WalletApiOperation, + BankServiceHandle, +} from "@gnu-taler/taler-wallet-core"; async function testRefundApiWithFulfillmentUrl( t: GlobalTestState, env: { merchant: MerchantServiceInterface; - bank: BankServiceInterface; + bank: BankServiceHandle; wallet: WalletCli; exchange: ExchangeServiceInterface; }, @@ -152,7 +157,7 @@ async function testRefundApiWithFulfillmentMessage( t: GlobalTestState, env: { merchant: MerchantServiceInterface; - bank: BankServiceInterface; + bank: BankServiceHandle; wallet: WalletCli; exchange: ExchangeServiceInterface; }, @@ -267,12 +272,8 @@ async function testRefundApiWithFulfillmentMessage( export async function runMerchantRefundApiTest(t: GlobalTestState) { // Set up test environment - const { - wallet, - bank, - exchange, - merchant, - } = await createSimpleTestkudosEnvironment(t); + const { wallet, bank, exchange, merchant } = + await createSimpleTestkudosEnvironment(t); // Withdraw digital cash into the wallet. diff --git a/packages/taler-wallet-cli/src/integrationtests/test-payment-fault.ts b/packages/taler-wallet-cli/src/integrationtests/test-payment-fault.ts index 7e421cc35..c78f030c8 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-payment-fault.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-payment-fault.ts @@ -29,9 +29,7 @@ import { BankService, WalletCli, MerchantPrivateApi, - BankApi, - BankAccessApi, - getPayto + getPayto, } from "../harness/harness.js"; import { FaultInjectedExchangeService, @@ -40,7 +38,11 @@ import { } from "../harness/faultInjection"; import { CoreApiResponse } from "@gnu-taler/taler-util"; import { defaultCoinConfig } from "../harness/denomStructures"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { + WalletApiOperation, + BankApi, + BankAccessApi, +} from "@gnu-taler/taler-wallet-core"; /** * Run test for basic, bank-integrated withdrawal. @@ -146,7 +148,6 @@ export async function runPaymentFaultTest(t: GlobalTestState) { await wallet.runUntilDone(); - // Check balance await wallet.client.call(WalletApiOperation.GetBalances, {}); diff --git a/packages/taler-wallet-cli/src/integrationtests/test-payment-on-demo.ts b/packages/taler-wallet-cli/src/integrationtests/test-payment-on-demo.ts index 1d419fd9a..50a18944b 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-payment-on-demo.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-payment-on-demo.ts @@ -17,31 +17,33 @@ /** * Imports. */ +import { GlobalTestState, WalletCli } from "../harness/harness.js"; +import { makeTestPayment } from "../harness/helpers.js"; import { - GlobalTestState, + WalletApiOperation, BankApi, - WalletCli, - BankAccessApi -} from "../harness/harness.js"; -import { - makeTestPayment, -} from "../harness/helpers.js"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; + BankAccessApi, + BankServiceHandle, + NodeHttpLib, +} from "@gnu-taler/taler-wallet-core"; /** * Run test for basic, bank-integrated withdrawal and payment. */ export async function runPaymentDemoTest(t: GlobalTestState) { - // Withdraw digital cash into the wallet. - let bankInterface = { + let bankInterface: BankServiceHandle = { baseUrl: "https://bank.demo.taler.net/", - port: 0 // unused. + http: new NodeHttpLib(), }; let user = await BankApi.createRandomBankUser(bankInterface); - let wop = await BankAccessApi.createWithdrawalOperation(bankInterface, user, "KUDOS:20"); + let wop = await BankAccessApi.createWithdrawalOperation( + bankInterface, + user, + "KUDOS:20", + ); - let wallet = new WalletCli(t); + let wallet = new WalletCli(t); await wallet.client.call(WalletApiOperation.GetWithdrawalDetailsForUri, { talerWithdrawUri: wop.taler_withdraw_uri, }); @@ -60,7 +62,10 @@ export async function runPaymentDemoTest(t: GlobalTestState) { }); await wallet.runUntilDone(); - let balanceBefore = await wallet.client.call(WalletApiOperation.GetBalances, {}); + let balanceBefore = await wallet.client.call( + WalletApiOperation.GetBalances, + {}, + ); t.assertTrue(balanceBefore["balances"].length == 1); const order = { @@ -70,7 +75,7 @@ export async function runPaymentDemoTest(t: GlobalTestState) { }; let merchant = { - makeInstanceBaseUrl: function(instanceName?: string) { + makeInstanceBaseUrl: function (instanceName?: string) { return "https://backend.demo.taler.net/instances/donations/"; }, port: 0, @@ -82,17 +87,26 @@ export async function runPaymentDemoTest(t: GlobalTestState) { await makeTestPayment( t, { - merchant, wallet, order + merchant, + wallet, + order, }, { - "Authorization": `Bearer ${process.env["TALER_ENV_FRONTENDS_APITOKEN"]}`, - }); + Authorization: `Bearer ${process.env["TALER_ENV_FRONTENDS_APITOKEN"]}`, + }, + ); await wallet.runUntilDone(); - let balanceAfter = await wallet.client.call(WalletApiOperation.GetBalances, {}); + let balanceAfter = await wallet.client.call( + WalletApiOperation.GetBalances, + {}, + ); t.assertTrue(balanceAfter["balances"].length == 1); - t.assertTrue(balanceBefore["balances"][0]["available"] > balanceAfter["balances"][0]["available"]); + t.assertTrue( + balanceBefore["balances"][0]["available"] > + balanceAfter["balances"][0]["available"], + ); } runPaymentDemoTest.excludeByDefault = true; diff --git a/packages/taler-wallet-cli/src/integrationtests/test-tipping.ts b/packages/taler-wallet-cli/src/integrationtests/test-tipping.ts index f31220e24..f04293ed8 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-tipping.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-tipping.ts @@ -17,8 +17,12 @@ /** * Imports. */ -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { GlobalTestState, MerchantPrivateApi, BankApi, getWireMethod } from "../harness/harness.js"; +import { WalletApiOperation, BankApi } from "@gnu-taler/taler-wallet-core"; +import { + GlobalTestState, + MerchantPrivateApi, + getWireMethod, +} from "../harness/harness.js"; import { createSimpleTestkudosEnvironment } from "../harness/helpers.js"; /** @@ -27,13 +31,8 @@ import { createSimpleTestkudosEnvironment } from "../harness/helpers.js"; export async function runTippingTest(t: GlobalTestState) { // Set up test environment - const { - wallet, - bank, - exchange, - merchant, - exchangeBankAccount, - } = await createSimpleTestkudosEnvironment(t); + const { wallet, bank, exchange, merchant, exchangeBankAccount } = + await createSimpleTestkudosEnvironment(t); const mbu = await BankApi.createRandomBankUser(bank); diff --git a/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts b/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts new file mode 100644 index 000000000..9ff605df5 --- /dev/null +++ b/packages/taler-wallet-cli/src/integrationtests/test-wallet-dbless.ts @@ -0,0 +1,358 @@ +/* + This file is part of GNU Taler + (C) 2020 Taler Systems S.A. + + GNU Taler is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + GNU Taler; see the file COPYING. If not, see + */ + +/** + * Imports. + */ +import { + AmountJson, + AmountLike, + Amounts, + AmountString, + codecForBankWithdrawalOperationPostResponse, + codecForDepositSuccess, + codecForExchangeMeltResponse, + codecForWithdrawResponse, + DenominationPubKey, + eddsaGetPublic, + encodeCrock, + ExchangeMeltRequest, + ExchangeProtocolVersion, + ExchangeWithdrawRequest, + getRandomBytes, + getTimestampNow, + hashWire, + j2s, + Timestamp, + UnblindedSignature, +} from "@gnu-taler/taler-util"; +import { + BankAccessApi, + BankApi, + BankServiceHandle, + CryptoApi, + DenominationRecord, + downloadExchangeInfo, + ExchangeInfo, + getBankWithdrawalInfo, + HttpRequestLibrary, + isWithdrawableDenom, + NodeHttpLib, + OperationFailedError, + readSuccessResponseJsonOrThrow, + SynchronousCryptoWorkerFactory, +} from "@gnu-taler/taler-wallet-core"; +import { GlobalTestState } from "../harness/harness.js"; +import { createSimpleTestkudosEnvironment } from "../harness/helpers.js"; + +const httpLib = new NodeHttpLib(); + +export interface ReserveKeypair { + reservePub: string; + reservePriv: string; +} + +/** + * Denormalized info about a coin. + */ +export interface CoinInfo { + coinPub: string; + coinPriv: string; + exchangeBaseUrl: string; + denomSig: UnblindedSignature; + denomPub: DenominationPubKey; + denomPubHash: string; + feeDeposit: string; + feeRefresh: string; +} + +export function generateReserveKeypair(): ReserveKeypair { + const priv = getRandomBytes(32); + const pub = eddsaGetPublic(priv); + return { + reservePriv: encodeCrock(priv), + reservePub: encodeCrock(pub), + }; +} + +async function topupReserveWithDemobank( + reservePub: string, + bankBaseUrl: string, + exchangeInfo: ExchangeInfo, + amount: AmountString, +) { + const bankHandle: BankServiceHandle = { + baseUrl: bankBaseUrl, + http: httpLib, + }; + const bankUser = await BankApi.createRandomBankUser(bankHandle); + const wopi = await BankAccessApi.createWithdrawalOperation( + bankHandle, + bankUser, + amount, + ); + const bankInfo = await getBankWithdrawalInfo( + httpLib, + wopi.taler_withdraw_uri, + ); + const bankStatusUrl = bankInfo.extractedStatusUrl; + if (!bankInfo.suggestedExchange) { + throw Error("no suggested exchange"); + } + const plainPaytoUris = + exchangeInfo.wire.accounts.map((x) => x.payto_uri) ?? []; + if (plainPaytoUris.length <= 0) { + throw new Error(); + } + const httpResp = await httpLib.postJson(bankStatusUrl, { + reserve_pub: reservePub, + selected_exchange: plainPaytoUris[0], + }); + await readSuccessResponseJsonOrThrow( + httpResp, + codecForBankWithdrawalOperationPostResponse(), + ); + await BankApi.confirmWithdrawalOperation(bankHandle, bankUser, wopi); +} + +async function withdrawCoin(args: { + http: HttpRequestLibrary; + cryptoApi: CryptoApi; + reserveKeyPair: ReserveKeypair; + denom: DenominationRecord; + exchangeBaseUrl: string; +}): Promise { + const { http, cryptoApi, reserveKeyPair, denom, exchangeBaseUrl } = args; + const planchet = await cryptoApi.createPlanchet({ + coinIndex: 0, + denomPub: denom.denomPub, + feeWithdraw: denom.feeWithdraw, + reservePriv: reserveKeyPair.reservePriv, + reservePub: reserveKeyPair.reservePub, + secretSeed: encodeCrock(getRandomBytes(32)), + value: denom.value, + }); + + const reqBody: ExchangeWithdrawRequest = { + denom_pub_hash: planchet.denomPubHash, + reserve_sig: planchet.withdrawSig, + coin_ev: planchet.coinEv, + }; + const reqUrl = new URL( + `reserves/${planchet.reservePub}/withdraw`, + exchangeBaseUrl, + ).href; + + const resp = await http.postJson(reqUrl, reqBody); + const r = await readSuccessResponseJsonOrThrow( + resp, + codecForWithdrawResponse(), + ); + + const ubSig = await cryptoApi.unblindDenominationSignature({ + planchet, + evSig: r.ev_sig, + }); + + return { + coinPriv: planchet.coinPriv, + coinPub: planchet.coinPub, + denomSig: ubSig, + denomPub: denom.denomPub, + denomPubHash: denom.denomPubHash, + feeDeposit: Amounts.stringify(denom.feeDeposit), + feeRefresh: Amounts.stringify(denom.feeRefresh), + exchangeBaseUrl: args.exchangeBaseUrl, + }; +} + +function findDenomOrThrow( + exchangeInfo: ExchangeInfo, + amount: AmountString, +): DenominationRecord { + for (const d of exchangeInfo.keys.currentDenominations) { + if (Amounts.cmp(d.value, amount) === 0 && isWithdrawableDenom(d)) { + return d; + } + } + throw new Error("no matching denomination found"); +} + +async function depositCoin(args: { + http: HttpRequestLibrary; + cryptoApi: CryptoApi; + exchangeBaseUrl: string; + coin: CoinInfo; + amount: AmountString; +}) { + const { coin, http, cryptoApi } = args; + const depositPayto = "payto://x-taler-bank/localhost/foo"; + const wireSalt = encodeCrock(getRandomBytes(16)); + const contractTermsHash = encodeCrock(getRandomBytes(64)); + const depositTimestamp = getTimestampNow(); + const refundDeadline = getTimestampNow(); + const merchantPub = encodeCrock(getRandomBytes(32)); + const dp = await cryptoApi.signDepositPermission({ + coinPriv: coin.coinPriv, + coinPub: coin.coinPub, + contractTermsHash, + denomKeyType: coin.denomPub.cipher, + denomPubHash: coin.denomPubHash, + denomSig: coin.denomSig, + exchangeBaseUrl: args.exchangeBaseUrl, + feeDeposit: Amounts.parseOrThrow(coin.feeDeposit), + merchantPub, + spendAmount: Amounts.parseOrThrow(args.amount), + timestamp: depositTimestamp, + refundDeadline: refundDeadline, + wireInfoHash: hashWire(depositPayto, wireSalt), + }); + const requestBody = { + contribution: Amounts.stringify(dp.contribution), + merchant_payto_uri: depositPayto, + wire_salt: wireSalt, + h_contract_terms: contractTermsHash, + ub_sig: coin.denomSig, + timestamp: depositTimestamp, + wire_transfer_deadline: getTimestampNow(), + refund_deadline: refundDeadline, + coin_sig: dp.coin_sig, + denom_pub_hash: dp.h_denom, + merchant_pub: merchantPub, + }; + const url = new URL(`coins/${dp.coin_pub}/deposit`, dp.exchange_url); + const httpResp = await http.postJson(url.href, requestBody); + await readSuccessResponseJsonOrThrow(httpResp, codecForDepositSuccess()); +} + +async function refreshCoin(req: { + http: HttpRequestLibrary; + cryptoApi: CryptoApi; + oldCoin: CoinInfo; + newDenoms: DenominationRecord[]; +}): Promise { + const { cryptoApi, oldCoin, http } = req; + const refreshSessionSeed = encodeCrock(getRandomBytes(32)); + const session = await cryptoApi.deriveRefreshSession({ + exchangeProtocolVersion: ExchangeProtocolVersion.V12, + feeRefresh: Amounts.parseOrThrow(oldCoin.feeRefresh), + kappa: 3, + meltCoinDenomPubHash: oldCoin.denomPubHash, + meltCoinPriv: oldCoin.coinPriv, + meltCoinPub: oldCoin.coinPub, + sessionSecretSeed: refreshSessionSeed, + newCoinDenoms: req.newDenoms.map((x) => ({ + count: 1, + denomPub: x.denomPub, + feeWithdraw: x.feeWithdraw, + value: x.value, + })), + }); + + const meltReqBody: ExchangeMeltRequest = { + coin_pub: oldCoin.coinPub, + confirm_sig: session.confirmSig, + denom_pub_hash: oldCoin.denomPubHash, + denom_sig: oldCoin.denomSig, + rc: session.hash, + value_with_fee: Amounts.stringify(session.meltValueWithFee), + }; + + const reqUrl = new URL( + `coins/${oldCoin.coinPub}/melt`, + oldCoin.exchangeBaseUrl, + ); + + const resp = await http.postJson(reqUrl.href, meltReqBody); + + const meltResponse = await readSuccessResponseJsonOrThrow( + resp, + codecForExchangeMeltResponse(), + ); + + const norevealIndex = meltResponse.noreveal_index; + + +} + +/** + * Run test for basic, bank-integrated withdrawal and payment. + */ +export async function runWalletDblessTest(t: GlobalTestState) { + // Set up test environment + + const { bank, exchange } = await createSimpleTestkudosEnvironment(t); + + const http = new NodeHttpLib(); + const cryptoApi = new CryptoApi(new SynchronousCryptoWorkerFactory()); + + try { + // Withdraw digital cash into the wallet. + + const exchangeInfo = await downloadExchangeInfo(exchange.baseUrl, http); + + const reserveKeyPair = generateReserveKeypair(); + + await topupReserveWithDemobank( + reserveKeyPair.reservePub, + bank.baseUrl, + exchangeInfo, + "TESTKUDOS:10", + ); + + await exchange.runWirewatchOnce(); + + const d1 = findDenomOrThrow(exchangeInfo, "TESTKUDOS:8"); + + const coin = await withdrawCoin({ + http, + cryptoApi, + reserveKeyPair, + denom: d1, + exchangeBaseUrl: exchange.baseUrl, + }); + + await depositCoin({ + amount: "TESTKUDOS:4", + coin: coin, + cryptoApi, + exchangeBaseUrl: exchange.baseUrl, + http, + }); + + const refreshDenoms = [ + findDenomOrThrow(exchangeInfo, "TESTKUDOS:1"), + findDenomOrThrow(exchangeInfo, "TESTKUDOS:1"), + ]; + + const freshCoins = await refreshCoin({ + oldCoin: coin, + cryptoApi, + http, + newDenoms: refreshDenoms, + }); + } catch (e) { + if (e instanceof OperationFailedError) { + console.log(e); + console.log(j2s(e.operationError)); + } else { + console.log(e); + } + throw e; + } +} + +runWalletDblessTest.suites = ["wallet"]; diff --git a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts index 5ba1fa893..19668d760 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-abort-bank.ts @@ -18,8 +18,12 @@ * Imports. */ import { TalerErrorCode } from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; -import { GlobalTestState, BankApi, BankAccessApi } from "../harness/harness.js"; +import { + WalletApiOperation, + BankApi, + BankAccessApi, +} from "@gnu-taler/taler-wallet-core"; +import { GlobalTestState } from "../harness/harness.js"; import { createSimpleTestkudosEnvironment } from "../harness/helpers.js"; /** diff --git a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-bank-integrated.ts b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-bank-integrated.ts index 25df19e46..e8a8c5028 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-bank-integrated.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-bank-integrated.ts @@ -17,10 +17,13 @@ /** * Imports. */ -import { GlobalTestState, BankApi, BankAccessApi } from "../harness/harness.js"; +import { GlobalTestState } from "../harness/harness.js"; import { createSimpleTestkudosEnvironment } from "../harness/helpers.js"; -import { codecForBalancesResponse } from "@gnu-taler/taler-util"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { + WalletApiOperation, + BankApi, + BankAccessApi, +} from "@gnu-taler/taler-wallet-core"; /** * Run test for basic, bank-integrated withdrawal. @@ -41,18 +44,24 @@ export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) { // Hand it to the wallet - const r1 = await wallet.client.call(WalletApiOperation.GetWithdrawalDetailsForUri, { - talerWithdrawUri: wop.taler_withdraw_uri, - }); + const r1 = await wallet.client.call( + WalletApiOperation.GetWithdrawalDetailsForUri, + { + talerWithdrawUri: wop.taler_withdraw_uri, + }, + ); await wallet.runPending(); // Withdraw - const r2 = await wallet.client.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, { - exchangeBaseUrl: exchange.baseUrl, - talerWithdrawUri: wop.taler_withdraw_uri, - }); + const r2 = await wallet.client.call( + WalletApiOperation.AcceptBankIntegratedWithdrawal, + { + exchangeBaseUrl: exchange.baseUrl, + talerWithdrawUri: wop.taler_withdraw_uri, + }, + ); await wallet.runPending(); // Confirm it diff --git a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-fakebank.ts b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-fakebank.ts index abd25d282..5860aaf88 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-fakebank.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-fakebank.ts @@ -19,13 +19,11 @@ */ import { GlobalTestState, - BankApi, WalletCli, setupDb, ExchangeService, FakeBankService, } from "../harness/harness.js"; -import { createSimpleTestkudosEnvironment } from "../harness/helpers.js"; import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js"; import { URL } from "@gnu-taler/taler-util"; diff --git a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-manual.ts b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-manual.ts index 2f88b3024..6ae0e65e7 100644 --- a/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-manual.ts +++ b/packages/taler-wallet-cli/src/integrationtests/test-withdrawal-manual.ts @@ -17,9 +17,9 @@ /** * Imports. */ -import { GlobalTestState, BankApi } from "../harness/harness.js"; +import { GlobalTestState } from "../harness/harness.js"; import { createSimpleTestkudosEnvironment } from "../harness/helpers.js"; -import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; +import { WalletApiOperation, BankApi } from "@gnu-taler/taler-wallet-core"; /** * Run test for basic, bank-integrated withdrawal. @@ -27,12 +27,8 @@ import { WalletApiOperation } from "@gnu-taler/taler-wallet-core"; export async function runTestWithdrawalManualTest(t: GlobalTestState) { // Set up test environment - const { - wallet, - bank, - exchange, - exchangeBankAccount, - } = await createSimpleTestkudosEnvironment(t); + const { wallet, bank, exchange, exchangeBankAccount } = + await createSimpleTestkudosEnvironment(t); // Create a withdrawal operation @@ -42,11 +38,13 @@ export async function runTestWithdrawalManualTest(t: GlobalTestState) { exchangeBaseUrl: exchange.baseUrl, }); - - const wres = await wallet.client.call(WalletApiOperation.AcceptManualWithdrawal, { - exchangeBaseUrl: exchange.baseUrl, - amount: "TESTKUDOS:10", - }); + const wres = await wallet.client.call( + WalletApiOperation.AcceptManualWithdrawal, + { + exchangeBaseUrl: exchange.baseUrl, + amount: "TESTKUDOS:10", + }, + ); const reservePub: string = wres.reservePub; diff --git a/packages/taler-wallet-cli/src/integrationtests/testrunner.ts b/packages/taler-wallet-cli/src/integrationtests/testrunner.ts index 844904132..3839266c0 100644 --- a/packages/taler-wallet-cli/src/integrationtests/testrunner.ts +++ b/packages/taler-wallet-cli/src/integrationtests/testrunner.ts @@ -87,6 +87,7 @@ import { runExchangeTimetravelTest } from "./test-exchange-timetravel.js"; import { runDenomUnofferedTest } from "./test-denom-unoffered.js"; import { runWithdrawalFakebankTest } from "./test-withdrawal-fakebank.js"; import { runClauseSchnorrTest } from "./test-clause-schnorr.js"; +import { runWalletDblessTest } from "./test-wallet-dbless.js"; /** * Test runner. @@ -162,6 +163,7 @@ const allTests: TestMainFunction[] = [ runWalletBackupBasicTest, runWalletBackupDoublespendTest, runWallettestingTest, + runWalletDblessTest, runWithdrawalAbortBankTest, runWithdrawalBankIntegratedTest, ]; -- cgit v1.2.3