/* This file is part of TALER (C) 2014-2018 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-httpd.c * @brief HTTP serving layer intended to perform crypto-work and * communication with the exchange * @author Marcello Stanisci */ #include "platform.h" #include #include #include #include #include #include #include #include #include #include #include /* Error codes. */ enum BenchmarkError { MISSING_BANK_URL, FAILED_TO_LAUNCH_BANK, BAD_CLI_ARG, BAD_CONFIG_FILE, NO_CONFIG_FILE_GIVEN }; /** * Probability that a spent coin will be refreshed. */ #define REFRESH_PROBABILITY 0.1 /** * The whole benchmark is a repetition of a "unit". Each * unit is a array containing a withdraw+deposit operation, * and _possibly_ a refresh of the deposited coin. */ #define UNITY_SIZE 6 /* Hard-coded params. Note, the bank is expected to * have the Tor user with account number 3 and password 'x'. * * This is not a problem _so far_, as the fakebank mocks logins, * and the Python bank makes that account by default. */ #define USER_ACCOUNT_NO 3 #define EXCHANGE_ACCOUNT_NO 2 #define USER_LOGIN_NAME "Tor" #define USER_LOGIN_PASS "x" #define EXCHANGE_URL "http://example.com/" #define FIRST_INSTRUCTION -1 #define CMD_TRANSFER_TO_EXCHANGE(label,amount) \ TALER_TESTING_cmd_fakebank_transfer (label, amount, \ fakebank_url, USER_ACCOUNT_NO, EXCHANGE_ACCOUNT_NO, \ USER_LOGIN_NAME, USER_LOGIN_PASS, EXCHANGE_URL) /** * Exchange URL; never used, just needed by exchange preparator. */ static char *exchange_url; /** * Time snapshot taken right before executing the CMDs. */ static struct GNUNET_TIME_Absolute start_time; /** * Benchmark duration time taken right after the CMD interpreter * returns. */ static struct GNUNET_TIME_Relative duration; /** * Exit code. */ static unsigned int result; /** * How many refreshes got executed. */ static unsigned int howmany_refreshes; /** * How many coins we want to create. */ static unsigned int howmany_coins = 1; /** * Log level used during the run. */ static char *loglev; /** * Log file. */ static char *logfile; /** * Config filename. */ static char *cfg_filename; /** * Fake bank base URL. */ static char *fakebank_url; /** * Currency used. */ static char *currency; /** * Convenience macros to allocate all the currency-dependant * strings; note that the argument list of the macro is ignored. * It is kept as a way to make the macro more auto-descriptive * where it is called. */ #define ALLOCATE_AMOUNTS(...) \ char *AMOUNT_5; \ char *AMOUNT_4; \ char *AMOUNT_1; \ \ GNUNET_asprintf (&AMOUNT_5, \ "%s:5", \ currency); \ GNUNET_asprintf (&AMOUNT_4, \ "%s:4", \ currency); \ GNUNET_asprintf (&AMOUNT_1, \ "%s:1", \ currency); /** * Throw a weighted coin with @a probability. * * @return #GNUNET_OK with @a probability, * #GNUNET_NO with 1 - @a probability */ static unsigned int eval_probability (float probability) { uint64_t random; float random_01; random = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK, UINT64_MAX); random_01 = (double) random / UINT64_MAX; return (random_01 <= probability) ? GNUNET_OK : GNUNET_NO; } /** * Actual commands collection. */ static void run (void *cls, struct TALER_TESTING_Interpreter *is) { struct TALER_Amount total_reserve_amount; struct TALER_Amount withdraw_fee; char *withdraw_fee_str; struct TALER_TESTING_Command all_commands [1 + /* Withdraw block */ howmany_coins + /* All units */ 1 /* End CMD */]; ALLOCATE_AMOUNTS (AMOUNT_5, AMOUNT_4, AMOUNT_1); total_reserve_amount.value = 5 * howmany_coins; strncpy (total_reserve_amount.currency, currency, TALER_CURRENCY_LEN); GNUNET_asprintf (&withdraw_fee_str, "%s:0.1", currency); TALER_string_to_amount (withdraw_fee_str, &withdraw_fee); for (unsigned int i = 0; i < howmany_coins; i++) TALER_amount_add (&total_reserve_amount, &total_reserve_amount, &withdraw_fee); struct TALER_TESTING_Command make_reserve[] = { CMD_TRANSFER_TO_EXCHANGE ("create-reserve", TALER_amount_to_string (&total_reserve_amount)), TALER_TESTING_cmd_exec_wirewatch ("wirewatch", cfg_filename), TALER_TESTING_cmd_end () }; all_commands[0] = TALER_TESTING_cmd_batch ("make-reserve", make_reserve); for (unsigned int i = 0; i < howmany_coins; i++) { char *withdraw_label; char *order_enc; struct TALER_TESTING_Command unit[UNITY_SIZE]; GNUNET_asprintf (&withdraw_label, "withdraw-%u", i); GNUNET_asprintf (&order_enc, "{\"nonce\": %u}", i); unit[0] = TALER_TESTING_cmd_withdraw_amount (withdraw_label, is->exchange, "create-reserve", AMOUNT_5, MHD_HTTP_OK); unit[1] = TALER_TESTING_cmd_deposit ("deposit", is->exchange, withdraw_label, 0, /* Index of the one withdrawn coin in the traits. */ TALER_TESTING_make_wire_details (24, "no-aggregation"), order_enc, GNUNET_TIME_UNIT_ZERO, AMOUNT_1, MHD_HTTP_OK); if (eval_probability (REFRESH_PROBABILITY)) { char *melt_label; char *reveal_label; howmany_refreshes++; GNUNET_asprintf (&melt_label, "refresh-melt-%u", i); GNUNET_asprintf (&reveal_label, "refresh-reveal-%u", i); unit[2] = TALER_TESTING_cmd_refresh_melt (melt_label, is->exchange, AMOUNT_4, withdraw_label, MHD_HTTP_OK); unit[3] = TALER_TESTING_cmd_refresh_reveal (reveal_label, is->exchange, melt_label, MHD_HTTP_OK); unit[4] = TALER_TESTING_cmd_refresh_link ("refresh-link", is->exchange, reveal_label, MHD_HTTP_OK); unit[5] = TALER_TESTING_cmd_end (); } else unit[2] = TALER_TESTING_cmd_end (); all_commands[1 + i] = TALER_TESTING_cmd_batch ("unit", unit); } all_commands[1 + howmany_coins] = TALER_TESTING_cmd_end (); start_time = GNUNET_TIME_absolute_get (); TALER_TESTING_run_with_fakebank (is, all_commands, fakebank_url); result = 1; } /** * 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) { unsetenv ("XDG_DATA_HOME"); unsetenv ("XDG_CONFIG_HOME"); struct GNUNET_OS_Process *compute_wire_response; struct GNUNET_CONFIGURATION_Handle *cfg; struct GNUNET_GETOPT_CommandLineOption options[] = { GNUNET_GETOPT_option_cfgfile (&cfg_filename), GNUNET_GETOPT_option_version (PACKAGE_VERSION " " VCS_VERSION), GNUNET_GETOPT_option_help ("Exchange benchmark"), GNUNET_GETOPT_option_loglevel (&loglev), GNUNET_GETOPT_option_uint ('n', "coins-number", "CN", "How many coins we should instantiate", &howmany_coins), GNUNET_GETOPT_option_string ('b', "bank-url", "BU", "bank base url, mandatory," " must match exchange's escrow bank", &fakebank_url), GNUNET_GETOPT_option_string ('l', "logfile", "LF", "will log to file LF", &logfile), GNUNET_GETOPT_OPTION_END }; if (GNUNET_SYSERR == (result = GNUNET_GETOPT_run ("taler-exchange-benchmark", options, argc, argv))) { TALER_LOG_ERROR ("Unparsable CLI options\n"); return BAD_CLI_ARG; } GNUNET_log_setup ("taler-exchange-benchmark", NULL == loglev ? "INFO" : loglev, logfile); if (NULL == cfg_filename) { TALER_LOG_ERROR ("-c option is mandatory\n"); return NO_CONFIG_FILE_GIVEN; } cfg = GNUNET_CONFIGURATION_create (); if (GNUNET_OK != GNUNET_CONFIGURATION_load (cfg, cfg_filename)) { TALER_LOG_ERROR ("Could not parse configuration\n"); return BAD_CONFIG_FILE; } if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg, "taler", "currency", ¤cy)) { GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, "taler", "currency"); GNUNET_CONFIGURATION_destroy (cfg); return BAD_CONFIG_FILE; } GNUNET_CONFIGURATION_destroy (cfg); if (NULL == fakebank_url) { TALER_LOG_ERROR ("Option -b is mandatory!\n"); return MISSING_BANK_URL; } compute_wire_response = GNUNET_OS_start_process (GNUNET_NO, GNUNET_OS_INHERIT_STD_ALL, NULL, NULL, NULL, "taler-exchange-wire", "taler-exchange-wire", "-c", cfg_filename, NULL); if (NULL == compute_wire_response) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to run `taler-exchange-wire`," " is your PATH correct?\n"); return GNUNET_NO; } GNUNET_OS_process_wait (compute_wire_response); GNUNET_OS_process_destroy (compute_wire_response); GNUNET_assert /* Takes care of dropping all tables. */ (GNUNET_OK == TALER_TESTING_prepare_exchange (cfg_filename, &exchange_url)); // never used, we do all via handle. result = TALER_TESTING_setup_with_exchange (run, NULL, cfg_filename); duration = GNUNET_TIME_absolute_get_duration (start_time); TALER_LOG_INFO ("Executed W=%u, D=%u, R=%u, operations in %s\n", howmany_coins, howmany_coins, howmany_refreshes, GNUNET_STRINGS_relative_time_to_string (duration, GNUNET_YES)); return (GNUNET_OK == result) ? 0 : result; }