/* This file is part of TALER (C) 2021 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 benchmark/taler-aggregator-benchmark.c * @brief Setup exchange database suitable for aggregator benchmarking * @author Christian Grothoff */ #include "platform.h" #include #include #include #include "taler_util.h" #include "taler_signatures.h" #include "taler_exchangedb_lib.h" #include "taler_json_lib.h" #include "taler_error_codes.h" /** * Exit code. */ static int global_ret; /** * How many deposits we want to create per merchant. */ static unsigned int howmany_deposits = 1; /** * How many merchants do we want to setup. */ static unsigned int howmany_merchants = 1; /** * Probability of a refund, as in $NUMBER:100. * Use 0 for no refunds. */ static unsigned int refund_rate = 0; /** * Currency used. */ static char *currency; /** * Configuration. */ static const struct GNUNET_CONFIGURATION_Handle *cfg; /** * Database plugin. */ static struct TALER_EXCHANGEDB_Plugin *plugin; /** * Main task doing the work(). */ static struct GNUNET_SCHEDULER_Task *task; /** * Hash of the denomination. */ static struct TALER_DenominationHash h_denom_pub; /** * "signature" to use for the coin(s). */ static struct TALER_DenominationSignature denom_sig; /** * Time range when deposits start. */ static struct GNUNET_TIME_Timestamp start; /** * Time range when deposits end. */ static struct GNUNET_TIME_Timestamp end; /** * 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 / (double) UINT64_MAX; return (random_01 <= probability) ? GNUNET_OK : GNUNET_NO; } /** * Randomize data at pointer @a x * * @param x pointer to data to randomize */ #define RANDOMIZE(x) \ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, x, sizeof (*x)) /** * Initialize @a out with an amount given by @a val and * @a frac using the main "currency". * * @param val value to set * @param frac fraction to set * @param[out] out where to write the amount */ static void make_amount (unsigned int val, unsigned int frac, struct TALER_Amount *out) { GNUNET_assert (GNUNET_OK == TALER_amount_set_zero (currency, out)); out->value = val; out->fraction = frac; } /** * Initialize @a out with an amount given by @a val and * @a frac using the main "currency". * * @param val value to set * @param frac fraction to set * @param[out] out where to write the amount */ static void make_amountN (unsigned int val, unsigned int frac, struct TALER_AmountNBO *out) { struct TALER_Amount in; make_amount (val, frac, &in); TALER_amount_hton (out, &in); } /** * Create random-ish timestamp. * * @return time stamp between start and end */ static struct GNUNET_TIME_Timestamp random_time (void) { uint64_t delta; struct GNUNET_TIME_Absolute ret; delta = end.abs_time.abs_value_us - start.abs_time.abs_value_us; delta = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, delta); ret.abs_value_us = start.abs_time.abs_value_us + delta; return GNUNET_TIME_absolute_to_timestamp (ret); } /** * Function run on shutdown. * * @param cls unused */ static void do_shutdown (void *cls) { (void) cls; if (NULL != plugin) { TALER_EXCHANGEDB_plugin_unload (plugin); plugin = NULL; } if (NULL != task) { GNUNET_SCHEDULER_cancel (task); task = NULL; } TALER_denom_sig_free (&denom_sig); } struct Merchant { /** * Public key of the merchant. Enables later identification * of the merchant in case of a need to rollback transactions. */ struct TALER_MerchantPublicKeyP merchant_pub; /** * Hash of the (canonical) representation of @e wire, used * to check the signature on the request. Generated by * the exchange from the detailed wire data provided by the * merchant. */ struct TALER_MerchantWireHash h_wire; /** * Salt used when computing @e h_wire. */ struct TALER_WireSaltP wire_salt; /** * Account information for the merchant. */ char *payto_uri; }; struct Deposit { /** * Information about the coin that is being deposited. */ struct TALER_CoinPublicInfo coin; /** * Hash over the proposal data between merchant and customer * (remains unknown to the Exchange). */ struct TALER_PrivateContractHash h_contract_terms; }; /** * Add a refund from @a m for @a d. * * @param m merchant granting the refund * @param d deposit being refunded * @return true on success */ static bool add_refund (const struct Merchant *m, const struct Deposit *d) { struct TALER_EXCHANGEDB_Refund r; r.coin = d->coin; r.details.merchant_pub = m->merchant_pub; RANDOMIZE (&r.details.merchant_sig); r.details.h_contract_terms = d->h_contract_terms; r.details.rtransaction_id = 42; make_amount (0, 5000000, &r.details.refund_amount); make_amount (0, 5, &r.details.refund_fee); if (0 <= plugin->insert_refund (plugin->cls, &r)) { GNUNET_break (0); global_ret = EXIT_FAILURE; GNUNET_SCHEDULER_shutdown (); return false; } return true; } /** * Add a (random-ish) deposit for merchant @a m. * * @param m merchant to receive the deposit * @return true on success */ static bool add_deposit (const struct Merchant *m) { struct Deposit d; struct TALER_EXCHANGEDB_Deposit deposit; uint64_t known_coin_id; struct TALER_DenominationHash dph; struct TALER_AgeCommitmentHash agh; RANDOMIZE (&d.coin.coin_pub); d.coin.denom_pub_hash = h_denom_pub; d.coin.denom_sig = denom_sig; RANDOMIZE (&d.h_contract_terms); if (0 >= plugin->ensure_coin_known (plugin->cls, &d.coin, &known_coin_id, &dph, &agh)) { GNUNET_break (0); global_ret = EXIT_FAILURE; GNUNET_SCHEDULER_shutdown (); return false; } deposit.coin = d.coin; RANDOMIZE (&deposit.csig); deposit.merchant_pub = m->merchant_pub; deposit.h_contract_terms = d.h_contract_terms; deposit.wire_salt = m->wire_salt; deposit.receiver_wire_account = m->payto_uri; deposit.timestamp = random_time (); deposit.refund_deadline = random_time (); deposit.wire_deadline = random_time (); make_amount (1, 0, &deposit.amount_with_fee); make_amount (0, 5, &deposit.deposit_fee); if (0 >= plugin->insert_deposit (plugin->cls, random_time (), &deposit)) { GNUNET_break (0); global_ret = EXIT_FAILURE; GNUNET_SCHEDULER_shutdown (); return false; } if (GNUNET_YES == eval_probability (((float) refund_rate) / 100.0)) return add_refund (m, &d); return true; } /** * Function to do the work. * * @param cls unused */ static void work (void *cls) { struct Merchant m; uint64_t rnd1; uint64_t rnd2; (void) cls; task = NULL; rnd1 = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, UINT64_MAX); rnd2 = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE, UINT64_MAX); GNUNET_asprintf (&m.payto_uri, "payto://x-taler-bank/localhost:8082/account-%llX-%llX", (unsigned long long) rnd1, (unsigned long long) rnd2); RANDOMIZE (&m.merchant_pub); RANDOMIZE (&m.wire_salt); TALER_merchant_wire_signature_hash (m.payto_uri, &m.wire_salt, &m.h_wire); if (GNUNET_OK != plugin->start (plugin->cls, "aggregator-benchmark-fill")) { GNUNET_break (0); global_ret = EXIT_FAILURE; GNUNET_free (m.payto_uri); GNUNET_SCHEDULER_shutdown (); return; } for (unsigned int i = 0; icommit (plugin->cls)) { if (0 == --howmany_merchants) { GNUNET_SCHEDULER_shutdown (); GNUNET_free (m.payto_uri); return; } } else { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Failed to commit, will try again\n"); } GNUNET_free (m.payto_uri); task = GNUNET_SCHEDULER_add_now (&work, NULL); } /** * Actual execution. * * @param cls unused * @param args remaining command-line arguments * @param cfgfile name of the configuration file used (for saving, can be NULL!) * @param c configuration */ static void run (void *cls, char *const *args, const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *c) { struct TALER_EXCHANGEDB_DenominationKeyInformationP issue; (void) cls; (void) args; (void) cfgfile; /* make sure everything 'ends' before the current time, so that the aggregator will process everything without need for time-travel */ end = GNUNET_TIME_timestamp_get (); start = GNUNET_TIME_absolute_to_timestamp ( GNUNET_TIME_absolute_subtract (end.abs_time, GNUNET_TIME_UNIT_MONTHS)); cfg = c; if (GNUNET_OK != TALER_config_get_currency (cfg, ¤cy)) { global_ret = EXIT_NOTCONFIGURED; return; } plugin = TALER_EXCHANGEDB_plugin_load (cfg); if (NULL == plugin) { global_ret = EXIT_NOTCONFIGURED; return; } if (GNUNET_SYSERR == plugin->preflight (plugin->cls)) { global_ret = EXIT_FAILURE; TALER_EXCHANGEDB_plugin_unload (plugin); return; } GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL); RANDOMIZE (&issue.signature); issue.properties.purpose.purpose = htonl ( TALER_SIGNATURE_MASTER_DENOMINATION_KEY_VALIDITY); issue.properties.purpose.size = htonl (sizeof (issue.properties)); RANDOMIZE (&issue.properties.master); issue.properties.start = GNUNET_TIME_timestamp_hton (start); issue.properties.expire_withdraw = GNUNET_TIME_timestamp_hton ( GNUNET_TIME_absolute_to_timestamp ( GNUNET_TIME_absolute_add (start.abs_time, GNUNET_TIME_UNIT_DAYS))); issue.properties.expire_deposit = GNUNET_TIME_timestamp_hton (end); issue.properties.expire_legal = GNUNET_TIME_timestamp_hton ( GNUNET_TIME_absolute_to_timestamp ( GNUNET_TIME_absolute_add (end.abs_time, GNUNET_TIME_UNIT_YEARS))); { struct TALER_DenominationPrivateKey pk; struct TALER_DenominationPublicKey denom_pub; struct TALER_CoinPubHash c_hash; struct TALER_PlanchetDetail pd; struct TALER_BlindedDenominationSignature bds; struct TALER_PlanchetMasterSecretP ps; struct TALER_ExchangeWithdrawValues alg_values; struct TALER_CoinSpendPublicKeyP coin_pub; union TALER_DenominationBlindingKeyP bks; RANDOMIZE (&coin_pub); GNUNET_assert (GNUNET_OK == TALER_denom_priv_create (&pk, &denom_pub, TALER_DENOMINATION_RSA, 1024)); alg_values.cipher = TALER_DENOMINATION_RSA; TALER_denom_pub_hash (&denom_pub, &h_denom_pub); make_amountN (2, 0, &issue.properties.value); make_amountN (0, 5, &issue.properties.fee_withdraw); make_amountN (0, 5, &issue.properties.fee_deposit); make_amountN (0, 5, &issue.properties.fee_refresh); make_amountN (0, 5, &issue.properties.fee_refund); issue.properties.denom_hash = h_denom_pub; if (0 >= plugin->insert_denomination_info (plugin->cls, &denom_pub, &issue)) { GNUNET_break (0); GNUNET_SCHEDULER_shutdown (); global_ret = EXIT_FAILURE; return; } TALER_planchet_blinding_secret_create (&ps, &alg_values, &bks); GNUNET_assert (GNUNET_OK == TALER_denom_blind (&denom_pub, &bks, NULL, /* FIXME-oec */ &coin_pub, &alg_values, &c_hash, &pd.blinded_planchet)); GNUNET_assert (GNUNET_OK == TALER_denom_sign_blinded (&bds, &pk, &pd.blinded_planchet)); TALER_blinded_planchet_free (&pd.blinded_planchet); GNUNET_assert (GNUNET_OK == TALER_denom_sig_unblind (&denom_sig, &bds, &bks, &c_hash, &alg_values, &denom_pub)); TALER_blinded_denom_sig_free (&bds); TALER_denom_pub_free (&denom_pub); TALER_denom_priv_free (&pk); } { struct TALER_Amount wire_fee; struct TALER_MasterSignatureP master_sig; unsigned int year; struct GNUNET_TIME_Timestamp ws; struct GNUNET_TIME_Timestamp we; year = GNUNET_TIME_get_current_year (); for (unsigned int y = year - 1; y plugin->insert_wire_fee (plugin->cls, "x-taler-bank", ws, we, &wire_fee, &wire_fee, &master_sig)) { GNUNET_break (0); GNUNET_SCHEDULER_shutdown (); global_ret = EXIT_FAILURE; return; } } } task = GNUNET_SCHEDULER_add_now (&work, NULL); } /** * The main function of the taler-aggregator-benchmark tool. * * @param argc number of arguments from the command line * @param argv command line arguments * @return 0 ok, non-zero on failure */ int main (int argc, char *const *argv) { struct GNUNET_GETOPT_CommandLineOption options[] = { GNUNET_GETOPT_option_uint ('d', "deposits", "DN", "How many deposits we should instantiate per merchant", &howmany_deposits), GNUNET_GETOPT_option_uint ('m', "merchants", "DM", "How many merchants should we create", &howmany_merchants), GNUNET_GETOPT_option_uint ('r', "refunds", "RATE", "Probability of refund per deposit (0-100)", &refund_rate), GNUNET_GETOPT_OPTION_END }; enum GNUNET_GenericReturnValue result; unsetenv ("XDG_DATA_HOME"); unsetenv ("XDG_CONFIG_HOME"); if (0 >= (result = GNUNET_PROGRAM_run (argc, argv, "taler-aggregator-benchmark", "generate database to benchmark the aggregator", options, &run, NULL))) { if (GNUNET_NO == result) return EXIT_SUCCESS; return EXIT_INVALIDARGUMENT; } return global_ret; }