diff options
Diffstat (limited to 'src/benchmark/taler-aggregator-benchmark.c')
-rw-r--r-- | src/benchmark/taler-aggregator-benchmark.c | 658 |
1 files changed, 658 insertions, 0 deletions
diff --git a/src/benchmark/taler-aggregator-benchmark.c b/src/benchmark/taler-aggregator-benchmark.c new file mode 100644 index 000000000..228d050e4 --- /dev/null +++ b/src/benchmark/taler-aggregator-benchmark.c @@ -0,0 +1,658 @@ +/* + 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 <http://www.gnu.org/licenses/> +*/ +/** + * @file benchmark/taler-aggregator-benchmark.c + * @brief Setup exchange database suitable for aggregator benchmarking + * @author Christian Grothoff + */ +#include "platform.h" +#include <jansson.h> +#include <gnunet/gnunet_util_lib.h> +#include <gnunet/gnunet_json_lib.h> +#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_DenominationHashP 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; +} + + +/** + * 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_MerchantWireHashP 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_PrivateContractHashP 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_CoinDepositInformation deposit; + struct TALER_EXCHANGEDB_BatchDeposit bd = { + .cdis = &deposit, + .num_cdis = 1 + }; + uint64_t known_coin_id; + struct TALER_DenominationHashP 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); + d.coin.no_age_commitment = true; + memset (&d.coin.h_age_commitment, + 0, + sizeof (d.coin.h_age_commitment)); + + 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); + bd.merchant_pub = m->merchant_pub; + bd.h_contract_terms = d.h_contract_terms; + bd.wire_salt = m->wire_salt; + bd.receiver_wire_account = m->payto_uri; + bd.wallet_timestamp = random_time (); + do { + bd.refund_deadline = random_time (); + bd.wire_deadline = random_time (); + } while (GNUNET_TIME_timestamp_cmp (bd.wire_deadline, + <, + bd.refund_deadline)); + + make_amount (1, 0, &deposit.amount_with_fee); + + { + struct GNUNET_TIME_Timestamp now; + bool balance_ok; + uint32_t bad_idx; + bool conflict; + + now = random_time (); + if (0 >= + plugin->do_deposit (plugin->cls, + &bd, + &now, + &balance_ok, + &bad_idx, + &conflict)) + { + 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; i<howmany_deposits; i++) + { + if (! add_deposit (&m)) + { + global_ret = EXIT_FAILURE; + GNUNET_SCHEDULER_shutdown (); + GNUNET_free (m.payto_uri); + return; + } + } + if (0 <= + plugin->commit (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_DenominationKeyInformation 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); + memset (&issue, + 0, + sizeof (issue)); + RANDOMIZE (&issue.signature); + issue.start + = start; + issue.expire_withdraw + = GNUNET_TIME_absolute_to_timestamp ( + GNUNET_TIME_absolute_add (start.abs_time, + GNUNET_TIME_UNIT_DAYS)); + issue.expire_deposit + = end; + issue.expire_legal + = 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_CoinPubHashP c_hash; + struct TALER_PlanchetDetail pd; + struct TALER_BlindedDenominationSignature bds; + struct TALER_PlanchetMasterSecretP ps; + struct TALER_CoinSpendPublicKeyP coin_pub; + struct TALER_AgeCommitmentHash hac; + union GNUNET_CRYPTO_BlindingSecretP bks; + const struct TALER_ExchangeWithdrawValues *alg_values; + + RANDOMIZE (&coin_pub); + GNUNET_assert (GNUNET_OK == + TALER_denom_priv_create (&pk, + &denom_pub, + GNUNET_CRYPTO_BSA_RSA, + 1024)); + alg_values = TALER_denom_ewv_rsa_singleton (); + denom_pub.age_mask = issue.age_mask; + TALER_denom_pub_hash (&denom_pub, + &h_denom_pub); + make_amount (2, 0, &issue.value); + make_amount (0, 5, &issue.fees.withdraw); + make_amount (0, 5, &issue.fees.deposit); + make_amount (0, 5, &issue.fees.refresh); + make_amount (0, 5, &issue.fees.refund); + issue.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, + TALER_denom_ewv_rsa_singleton (), + &bks); + + { + struct GNUNET_HashCode seed; + struct TALER_AgeMask mask = { + .bits = 1 | (1 << 8) | (1 << 12) | (1 << 16) | (1 << 18) + }; + struct TALER_AgeCommitmentProof acp = {0}; + + GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, + &seed, + sizeof(seed)); + TALER_age_restriction_commit (&mask, + 13, + &seed, + &acp); + TALER_age_commitment_hash (&acp.commitment, + &hac); + } + + GNUNET_assert (GNUNET_OK == + TALER_denom_blind (&denom_pub, + &bks, + NULL, + &hac, + &coin_pub, + alg_values, + &c_hash, + &pd.blinded_planchet)); + GNUNET_assert (GNUNET_OK == + TALER_denom_sign_blinded (&bds, + &pk, + false, + &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_WireFeeSet fees; + 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<year + 2; y++) + { + ws = GNUNET_TIME_absolute_to_timestamp (GNUNET_TIME_year_to_time (y - 1)); + we = GNUNET_TIME_absolute_to_timestamp (GNUNET_TIME_year_to_time (y)); + make_amount (0, 5, &fees.wire); + make_amount (0, 5, &fees.closing); + memset (&master_sig, + 0, + sizeof (master_sig)); + if (0 > + plugin->insert_wire_fee (plugin->cls, + "x-taler-bank", + ws, + we, + &fees, + &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; +} |