summaryrefslogtreecommitdiff
path: root/src/benchmark/taler-aggregator-benchmark.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/benchmark/taler-aggregator-benchmark.c')
-rw-r--r--src/benchmark/taler-aggregator-benchmark.c658
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,
+ &currency))
+ {
+ 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;
+}