646 lines
17 KiB
C
646 lines
17 KiB
C
/*
|
|
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_Deposit deposit;
|
|
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);
|
|
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 ();
|
|
do {
|
|
deposit.refund_deadline = random_time ();
|
|
deposit.wire_deadline = random_time ();
|
|
} while (GNUNET_TIME_timestamp_cmp (deposit.wire_deadline,
|
|
<,
|
|
deposit.refund_deadline));
|
|
|
|
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; 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_ExchangeWithdrawValues alg_values;
|
|
struct TALER_CoinSpendPublicKeyP coin_pub;
|
|
struct TALER_AgeCommitmentHash hac;
|
|
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;
|
|
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,
|
|
&alg_values,
|
|
&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));
|
|
|
|
GNUNET_assert (GNUNET_OK ==
|
|
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,
|
|
&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;
|
|
}
|