/* This file is part of TALER (C) 2014--2020 Taler Systems SA TALER is free software; you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. 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 TALER; see the file COPYING. If not, see */ /** * @file merchant/backend/taler-merchant-benchmark.c * @brief benchmark the backend to evaluate performance * @author Marcello Stanisci * @author Christian Grothoff */ #include "platform.h" #include #include #include #include #include #include #include #include #include #include #include "taler_merchant_testing_lib.h" /** * Maximum length of an amount (value plus currency string) needed by the test. * We have a 32-bit and a 64-bit value (~48 characters), plus the currency, plus * some punctuation. */ #define MAX_AMOUNT_LEN (TALER_CURRENCY_LEN + 64) /** * Maximum length of an order JSON. Generously allocated. */ #define MAX_ORDER_LEN (MAX_AMOUNT_LEN * 4 + 2048) /* Error codes. */ enum PaymentGeneratorError { PG_SUCCESS = 0, PG_NO_SUBCOMMAND, PG_BAD_OPTIONS, PG_BAD_CONFIG_FILE, PG_FAILED_CFG_CURRENCY, PG_FAILED_TO_PREPARE_MERCHANT, PG_FAILED_TO_PREPARE_BANK, PG_FAILED_TO_LAUNCH_MERCHANT, PG_FAILED_TO_LAUNCH_BANK, PG_RUNTIME_FAILURE }; /** * What API key should we send in the HTTP 'Authorization' header? */ static char *apikey; /** * Witnesses if the ordinary cases payment suite should be run. */ static bool ordinary; /** * Witnesses if the corner cases payment suite should be run. */ static bool corner; /** * Base URL of the alternative non default instance. */ static char *alt_instance_url; /** * How many unaggregated payments we want to generate. */ static unsigned int unaggregated_number = 1; /** * How many payments that use two coins we want to generate. */ static unsigned int twocoins_number = 1; /** * How many payments we want to generate. */ static unsigned int payments_number = 1; /** * How many /tracks operation we want to perform. */ static unsigned int tracks_number = 1; /** * Config filename to give to commands (like wirewatch). */ static char *cfg_filename; /** * Bank configuration. */ static struct TALER_TESTING_BankConfiguration bc; /** * Merchant base URL. */ static char *merchant_url; /** * Currency used. */ static char *currency; /** * Actual commands collection. */ static void run (void *cls, struct TALER_TESTING_Interpreter *is) { char CURRENCY_10_02[MAX_AMOUNT_LEN]; char CURRENCY_10[MAX_AMOUNT_LEN]; char CURRENCY_9_98[MAX_AMOUNT_LEN]; char CURRENCY_5_01[MAX_AMOUNT_LEN]; char CURRENCY_5[MAX_AMOUNT_LEN]; char CURRENCY_4_99[MAX_AMOUNT_LEN]; char CURRENCY_0_02[MAX_AMOUNT_LEN]; char CURRENCY_0_01[MAX_AMOUNT_LEN]; GNUNET_snprintf (CURRENCY_10_02, sizeof (CURRENCY_10_02), "%s:10.02", currency); GNUNET_snprintf (CURRENCY_10, sizeof (CURRENCY_10), "%s:10", currency); GNUNET_snprintf (CURRENCY_9_98, sizeof (CURRENCY_9_98), "%s:9.98", currency); GNUNET_snprintf (CURRENCY_5_01, sizeof (CURRENCY_5_01), "%s:5.01", currency); GNUNET_snprintf (CURRENCY_5, sizeof (CURRENCY_5), "%s:5", currency); GNUNET_snprintf (CURRENCY_4_99, sizeof (CURRENCY_4_99), "%s:4.99", currency); GNUNET_snprintf (CURRENCY_0_02, sizeof (CURRENCY_0_02), "%s:0.02", currency); GNUNET_snprintf (CURRENCY_0_01, sizeof (CURRENCY_0_01), "%s:0.01", currency); if (NULL != apikey) { char *hdr; GNUNET_asprintf (&hdr, "%s: %s", MHD_HTTP_HEADER_AUTHORIZATION, apikey); GNUNET_assert (GNUNET_OK == GNUNET_CURL_append_header (is->ctx, hdr)); GNUNET_free (hdr); } if (ordinary) { struct TALER_TESTING_Command ordinary_commands[] = { TALER_TESTING_cmd_admin_add_incoming ("create-reserve-1", CURRENCY_10_02, &bc.exchange_auth, bc.user43_payto), TALER_TESTING_cmd_exec_wirewatch ("wirewatch-1", cfg_filename), TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1", "create-reserve-1", CURRENCY_5, MHD_HTTP_OK), TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2", "create-reserve-1", CURRENCY_5, MHD_HTTP_OK), TALER_TESTING_cmd_merchant_post_orders ("create-proposal-1", merchant_url, MHD_HTTP_OK, "order-worth-5", GNUNET_TIME_UNIT_ZERO_ABS, GNUNET_TIME_UNIT_FOREVER_ABS, "EUR:5.0"), TALER_TESTING_cmd_merchant_pay_order ("deposit-simple", merchant_url, MHD_HTTP_OK, "create-proposal-1", "withdraw-coin-1", CURRENCY_5, CURRENCY_4_99, NULL), TALER_TESTING_cmd_rewind_ip ("rewind-payments", "create-reserve", payments_number), /* Next proposal-pay cycle will be used by /track CMDs * and so it will not have to be looped over, only /track * CMDs will have to. */ TALER_TESTING_cmd_merchant_post_orders ("create-proposal-2", merchant_url, MHD_HTTP_OK, "order-worth-5-track", GNUNET_TIME_UNIT_ZERO_ABS, GNUNET_TIME_UNIT_FOREVER_ABS, "EUR:5.0"), TALER_TESTING_cmd_merchant_pay_order ("deposit-simple-2", merchant_url, MHD_HTTP_OK, "create-proposal-2", "withdraw-coin-2", CURRENCY_5, CURRENCY_4_99, NULL), /* /track/transaction over deposit-simple-2 */ TALER_TESTING_cmd_exec_aggregator ("aggregate-1", cfg_filename), TALER_TESTING_cmd_merchant_post_transfer ( "post-transfer-1", &bc.exchange_auth, bc.exchange_auth.wire_gateway_url, merchant_url, "EUR:4.98", /* FIXME: check amount! */ MHD_HTTP_OK, "deposit-simple-2", NULL), TALER_TESTING_cmd_merchant_get_transfers ("track-transfer-1", merchant_url, bc.user42_payto, MHD_HTTP_OK, "post-transaction-1", NULL), TALER_TESTING_cmd_rewind_ip ("rewind-tracks", "track-transfer-1", tracks_number), TALER_TESTING_cmd_end () }; TALER_TESTING_run (is, ordinary_commands); return; } if (corner) /* should never be 'false' here */ { struct TALER_TESTING_Command corner_commands[] = { TALER_TESTING_cmd_admin_add_incoming ("create-reserve-1", CURRENCY_5_01, &bc.exchange_auth, bc.user43_payto), TALER_TESTING_cmd_exec_wirewatch ("wirewatch-1", cfg_filename), TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1", "create-reserve-1", CURRENCY_5, MHD_HTTP_OK), TALER_TESTING_cmd_merchant_post_orders ("create-unaggregated-proposal", alt_instance_url, MHD_HTTP_OK, "order-worth-5-unaggregated", GNUNET_TIME_UNIT_ZERO_ABS, GNUNET_TIME_UNIT_FOREVER_ABS, "EUR:5.0"), TALER_TESTING_cmd_merchant_pay_order ("deposit-unaggregated", merchant_url, MHD_HTTP_OK, "create-unaggregated-proposal", "withdraw-coin-1", CURRENCY_5, CURRENCY_4_99, NULL), TALER_TESTING_cmd_rewind_ip ("rewind-unaggregated", "create-reserve-1", unaggregated_number), TALER_TESTING_cmd_admin_add_incoming ("create-reserve-2", CURRENCY_10_02, &bc.exchange_auth, bc.user43_payto), TALER_TESTING_cmd_exec_wirewatch ("wirewatch-2", cfg_filename), TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2", "create-reserve-2", CURRENCY_5, MHD_HTTP_OK), TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-3", "create-reserve-2", CURRENCY_5, MHD_HTTP_OK), TALER_TESTING_cmd_merchant_post_orders ("create-twocoins-proposal", merchant_url, MHD_HTTP_OK, "order-worth-10-2coins", GNUNET_TIME_UNIT_ZERO_ABS, GNUNET_TIME_UNIT_FOREVER_ABS, "EUR:10.0"), TALER_TESTING_cmd_merchant_pay_order ("deposit-twocoins", merchant_url, MHD_HTTP_OK, "create-twocoins-proposal", "withdraw-coin-2;withdraw-coin-3", CURRENCY_10, CURRENCY_9_98, NULL), TALER_TESTING_cmd_exec_aggregator ("aggregate-twocoins", cfg_filename), TALER_TESTING_cmd_rewind_ip ("rewind-twocoins", "create-reserve-2", twocoins_number), TALER_TESTING_cmd_end () }; TALER_TESTING_run (is, corner_commands); return; } } /** * Send SIGTERM and wait for process termination. * * @param process process to terminate. */ static void terminate_process (struct GNUNET_OS_Process *process) { GNUNET_OS_process_kill (process, SIGTERM); GNUNET_OS_process_wait (process); GNUNET_OS_process_destroy (process); } /** * The main function of the serve tool * * @param argc number of arguments from the command line * @param argv command line arguments * @return 0 ok, or `enum PaymentGeneratorError` on error */ int main (int argc, char *const *argv) { char *loglev; char *logfile; char *exchange_account; char *alt_instance_id; struct GNUNET_OS_Process *bankd; struct GNUNET_OS_Process *merchantd; struct GNUNET_GETOPT_CommandLineOption *options; struct GNUNET_GETOPT_CommandLineOption root_options[] = { GNUNET_GETOPT_option_cfgfile (&cfg_filename), GNUNET_GETOPT_option_version (PACKAGE_VERSION " " VCS_VERSION), GNUNET_GETOPT_option_help ("Runs benchmark logic against merchant backend. " "Must be used with either 'ordinary' or 'corner' sub-commands."), GNUNET_GETOPT_OPTION_END }; struct GNUNET_GETOPT_CommandLineOption corner_options[] = { GNUNET_GETOPT_option_help ("Populate databases with corner case payments"), GNUNET_GETOPT_option_loglevel (&loglev), GNUNET_GETOPT_option_uint ('u', "unaggregated-number", "UN", "will generate UN unaggregated payments, defaults to 1", &unaggregated_number), GNUNET_GETOPT_option_uint ('t', "two-coins", "TC", "will perform TC 2-coins payments, defaults to 1", &twocoins_number), GNUNET_GETOPT_option_mandatory ( GNUNET_GETOPT_option_string ('e', "exchange-account", "SECTION", "configuration section specifying the exchange account to use, mandatory", &exchange_account)), GNUNET_GETOPT_option_string ('a', "apikey", "APIKEY", "HTTP 'Authorization' header to send to the merchant", &apikey), GNUNET_GETOPT_option_mandatory ( GNUNET_GETOPT_option_string ('i', "alt-instance", "AI", "alternative (non default) instance," " used to provide fresh wire details to" " make unaggregated transactions stay so." " Note, this instance will be given far" " future wire deadline, and so it should" " never author now-deadlined transactions," " as they would get those far future ones" " aggregated too.", &alt_instance_id)), GNUNET_GETOPT_option_string ('l', "logfile", "LF", "will log to file LF", &logfile), GNUNET_GETOPT_OPTION_END }; struct GNUNET_GETOPT_CommandLineOption ordinary_options[] = { GNUNET_GETOPT_option_cfgfile (&cfg_filename), GNUNET_GETOPT_option_version (PACKAGE_VERSION " " VCS_VERSION), GNUNET_GETOPT_option_help ("Generate Taler ordinary payments" " to populate the databases"), GNUNET_GETOPT_option_loglevel (&loglev), GNUNET_GETOPT_option_mandatory ( GNUNET_GETOPT_option_string ('e', "exchange-account", "SECTION", "configuration section specifying the exchange account to use, mandatory", &exchange_account)), GNUNET_GETOPT_option_uint ('p', "payments-number", "PN", "will generate PN payments, defaults to 1", &payments_number), GNUNET_GETOPT_option_string ('a', "apikey", "APIKEY", "HTTP 'Authorization' header to send to the merchant", &apikey), GNUNET_GETOPT_option_uint ('t', "tracks-number", "TN", "will perform TN /track operations, defaults to 1", &tracks_number), GNUNET_GETOPT_option_string ('l', "logfile", "LF", "will log to file LF", &logfile), GNUNET_GETOPT_OPTION_END }; const char *default_config_file; default_config_file = GNUNET_OS_project_data_get ()->user_config_file; loglev = NULL; GNUNET_log_setup ("taler-merchant-benchmark", loglev, logfile); options = root_options; if (NULL != argv[1]) { if (0 == strcmp ("ordinary", argv[1])) { ordinary = true; options = ordinary_options; } if (0 == strcmp ("corner", argv[1])) { corner = true; options = corner_options; } } { int result; result = GNUNET_GETOPT_run ("taler-merchant-benchmark", options, argc, argv); if (GNUNET_SYSERR == result) return PG_BAD_OPTIONS; if (0 == result) return PG_SUCCESS; } if ( (! ordinary) && (! corner) ) { fprintf (stderr, "Please use 'ordinary' or 'corner' subcommands.\n"); return PG_NO_SUBCOMMAND; } if (NULL == cfg_filename) cfg_filename = (char *) default_config_file; /* load currency from configuration */ { struct GNUNET_CONFIGURATION_Handle *cfg; cfg = GNUNET_CONFIGURATION_create (); if (GNUNET_OK != GNUNET_CONFIGURATION_load (cfg, cfg_filename)) { TALER_LOG_ERROR ("Could not parse configuration\n"); return PG_BAD_CONFIG_FILE; } if (GNUNET_OK != TALER_config_get_currency (cfg, ¤cy)) { TALER_LOG_ERROR ("Failed to read currency from configuration\n"); GNUNET_CONFIGURATION_destroy (cfg); return PG_FAILED_CFG_CURRENCY; } GNUNET_CONFIGURATION_destroy (cfg); } /* prepare merchant and bank */ merchant_url = TALER_TESTING_prepare_merchant (cfg_filename); if (NULL == merchant_url) { TALER_LOG_ERROR ("Failed to prepare for the merchant\n"); return PG_FAILED_TO_PREPARE_MERCHANT; } if (NULL != alt_instance_id) { GNUNET_assert (0 < GNUNET_asprintf (&alt_instance_url, "%s/instances/%s/", merchant_url, &alt_instance_id)); } if (GNUNET_OK != TALER_TESTING_prepare_bank (cfg_filename, GNUNET_YES, exchange_account, &bc)) { TALER_LOG_ERROR ("Failed to prepare for the bank\n"); return PG_FAILED_TO_PREPARE_BANK; } /* launch merchant and bank */ if (NULL == (merchantd = TALER_TESTING_run_merchant (cfg_filename, merchant_url))) { TALER_LOG_ERROR ("Failed to launch the merchant\n"); return PG_FAILED_TO_LAUNCH_MERCHANT; } if (NULL == (bankd = TALER_TESTING_run_bank (cfg_filename, bc.exchange_auth.wire_gateway_url))) { TALER_LOG_ERROR ("Failed to run the bank\n"); terminate_process (merchantd); return PG_FAILED_TO_LAUNCH_BANK; } /* launch exchange and run benchmark */ { int result; result = TALER_TESTING_setup_with_exchange (run, NULL, cfg_filename); terminate_process (merchantd); terminate_process (bankd); return (GNUNET_OK == result) ? 0 : PG_RUNTIME_FAILURE; } }