exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

taler-aggregator-benchmark.c (17801B)


      1 /*
      2   This file is part of TALER
      3   (C) 2021, 2024 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it
      6   under the terms of the GNU Affero General Public License as
      7   published by the Free Software Foundation; either version 3, or
      8   (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     13   General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public License
     16   along with TALER; see the file COPYING.  If not,
     17   see <http://www.gnu.org/licenses/>
     18 */
     19 /**
     20  * @file benchmark/taler-aggregator-benchmark.c
     21  * @brief Setup exchange database suitable for aggregator benchmarking
     22  * @author Christian Grothoff
     23  */
     24 #include "taler/platform.h"
     25 #include <jansson.h>
     26 #include <gnunet/gnunet_util_lib.h>
     27 #include <gnunet/gnunet_json_lib.h>
     28 #include "taler/taler_util.h"
     29 #include "taler/taler_signatures.h"
     30 #include "taler/taler_exchangedb_lib.h"
     31 #include "taler/taler_json_lib.h"
     32 #include "taler/taler_error_codes.h"
     33 
     34 
     35 /**
     36  * Exit code.
     37  */
     38 static int global_ret;
     39 
     40 /**
     41  * How many deposits we want to create per merchant.
     42  */
     43 static unsigned int howmany_deposits = 1;
     44 
     45 /**
     46  * How many merchants do we want to setup.
     47  */
     48 static unsigned int howmany_merchants = 1;
     49 
     50 /**
     51  * Probability of a refund, as in $NUMBER:100.
     52  * Use 0 for no refunds.
     53  */
     54 static unsigned int refund_rate = 0;
     55 
     56 /**
     57  * Currency used.
     58  */
     59 static char *currency;
     60 
     61 /**
     62  * Configuration.
     63  */
     64 static const struct GNUNET_CONFIGURATION_Handle *cfg;
     65 
     66 /**
     67  * Database plugin.
     68  */
     69 static struct TALER_EXCHANGEDB_Plugin *plugin;
     70 
     71 /**
     72  * Main task doing the work().
     73  */
     74 static struct GNUNET_SCHEDULER_Task *task;
     75 
     76 /**
     77  * Hash of the denomination.
     78  */
     79 static struct TALER_DenominationHashP h_denom_pub;
     80 
     81 /**
     82  * "signature" to use for the coin(s).
     83  */
     84 static struct TALER_DenominationSignature denom_sig;
     85 
     86 /**
     87  * Time range when deposits start.
     88  */
     89 static struct GNUNET_TIME_Timestamp start;
     90 
     91 /**
     92  * Time range when deposits end.
     93  */
     94 static struct GNUNET_TIME_Timestamp end;
     95 
     96 
     97 /**
     98  * Throw a weighted coin with @a probability.
     99  *
    100  * @return #GNUNET_OK with @a probability,
    101  *         #GNUNET_NO with 1 - @a probability
    102  */
    103 static unsigned int
    104 eval_probability (float probability)
    105 {
    106   uint64_t random;
    107   float random_01;
    108 
    109   random = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_WEAK,
    110                                      UINT64_MAX);
    111   random_01 = (double) random / (double) UINT64_MAX;
    112   return (random_01 <= probability) ? GNUNET_OK : GNUNET_NO;
    113 }
    114 
    115 
    116 /**
    117  * Randomize data at pointer @a x
    118  *
    119  * @param x pointer to data to randomize
    120  */
    121 #define RANDOMIZE(x) \
    122         GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, x, sizeof (*x))
    123 
    124 
    125 /**
    126  * Initialize @a out with an amount given by @a val and
    127  * @a frac using the main "currency".
    128  *
    129  * @param val value to set
    130  * @param frac fraction to set
    131  * @param[out] out where to write the amount
    132  */
    133 static void
    134 make_amount (unsigned int val,
    135              unsigned int frac,
    136              struct TALER_Amount *out)
    137 {
    138   GNUNET_assert (GNUNET_OK ==
    139                  TALER_amount_set_zero (currency,
    140                                         out));
    141   out->value = val;
    142   out->fraction = frac;
    143 }
    144 
    145 
    146 /**
    147  * Create random-ish timestamp.
    148  *
    149  * @return time stamp between start and end
    150  */
    151 static struct GNUNET_TIME_Timestamp
    152 random_time (void)
    153 {
    154   uint64_t delta;
    155   struct GNUNET_TIME_Absolute ret;
    156 
    157   delta = end.abs_time.abs_value_us - start.abs_time.abs_value_us;
    158   delta = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
    159                                     delta);
    160   ret.abs_value_us = start.abs_time.abs_value_us + delta;
    161   return GNUNET_TIME_absolute_to_timestamp (ret);
    162 }
    163 
    164 
    165 /**
    166  * Function run on shutdown.
    167  *
    168  * @param cls unused
    169  */
    170 static void
    171 do_shutdown (void *cls)
    172 {
    173   (void) cls;
    174   if (NULL != plugin)
    175   {
    176     TALER_EXCHANGEDB_plugin_unload (plugin);
    177     plugin = NULL;
    178   }
    179   if (NULL != task)
    180   {
    181     GNUNET_SCHEDULER_cancel (task);
    182     task = NULL;
    183   }
    184   TALER_denom_sig_free (&denom_sig);
    185 }
    186 
    187 
    188 struct Merchant
    189 {
    190 
    191   /**
    192    * Public key of the merchant.  Enables later identification
    193    * of the merchant in case of a need to rollback transactions.
    194    */
    195   struct TALER_MerchantPublicKeyP merchant_pub;
    196 
    197   /**
    198    * Hash of the (canonical) representation of @e wire, used
    199    * to check the signature on the request.  Generated by
    200    * the exchange from the detailed wire data provided by the
    201    * merchant.
    202    */
    203   struct TALER_MerchantWireHashP h_wire;
    204 
    205   /**
    206    * Salt used when computing @e h_wire.
    207    */
    208   struct TALER_WireSaltP wire_salt;
    209 
    210   /**
    211    * Account information for the merchant.
    212    */
    213   struct TALER_FullPayto payto_uri;
    214 
    215 };
    216 
    217 struct Deposit
    218 {
    219 
    220   /**
    221    * Information about the coin that is being deposited.
    222    */
    223   struct TALER_CoinPublicInfo coin;
    224 
    225   /**
    226    * Hash over the proposal data between merchant and customer
    227    * (remains unknown to the Exchange).
    228    */
    229   struct TALER_PrivateContractHashP h_contract_terms;
    230 
    231 };
    232 
    233 
    234 /**
    235  * Add a refund from @a m for @a d.
    236  *
    237  * @param m merchant granting the refund
    238  * @param d deposit being refunded
    239  * @return true on success
    240  */
    241 static bool
    242 add_refund (const struct Merchant *m,
    243             const struct Deposit *d)
    244 {
    245   struct TALER_EXCHANGEDB_Refund r;
    246 
    247   r.coin = d->coin;
    248   r.details.merchant_pub = m->merchant_pub;
    249   RANDOMIZE (&r.details.merchant_sig);
    250   r.details.h_contract_terms = d->h_contract_terms;
    251   r.details.rtransaction_id = 42;
    252   make_amount (0, 5000000, &r.details.refund_amount);
    253   make_amount (0, 5, &r.details.refund_fee);
    254   if (0 >=
    255       plugin->insert_refund (plugin->cls,
    256                              &r))
    257   {
    258     GNUNET_break (0);
    259     global_ret = EXIT_FAILURE;
    260     GNUNET_SCHEDULER_shutdown ();
    261     return false;
    262   }
    263   return true;
    264 }
    265 
    266 
    267 /**
    268  * Add a (random-ish) deposit for merchant @a m.
    269  *
    270  * @param m merchant to receive the deposit
    271  * @return true on success
    272  */
    273 static bool
    274 add_deposit (const struct Merchant *m)
    275 {
    276   struct Deposit d;
    277   struct TALER_EXCHANGEDB_CoinDepositInformation deposit;
    278   struct TALER_EXCHANGEDB_BatchDeposit bd = {
    279     .cdis = &deposit,
    280     .num_cdis = 1
    281   };
    282   uint64_t known_coin_id;
    283   struct TALER_DenominationHashP dph;
    284   struct TALER_AgeCommitmentHashP agh;
    285 
    286   RANDOMIZE (&d.coin.coin_pub);
    287   d.coin.denom_pub_hash = h_denom_pub;
    288   d.coin.denom_sig = denom_sig;
    289   RANDOMIZE (&d.h_contract_terms);
    290   d.coin.no_age_commitment = true;
    291   memset (&d.coin.h_age_commitment,
    292           0,
    293           sizeof (d.coin.h_age_commitment));
    294 
    295   if (0 >=
    296       plugin->ensure_coin_known (plugin->cls,
    297                                  &d.coin,
    298                                  &known_coin_id,
    299                                  &dph,
    300                                  &agh))
    301   {
    302     GNUNET_break (0);
    303     global_ret = EXIT_FAILURE;
    304     GNUNET_SCHEDULER_shutdown ();
    305     return false;
    306   }
    307   deposit.coin = d.coin;
    308   RANDOMIZE (&deposit.csig);
    309   bd.merchant_pub = m->merchant_pub;
    310   bd.h_contract_terms = d.h_contract_terms;
    311   bd.wire_salt = m->wire_salt;
    312   bd.receiver_wire_account = m->payto_uri;
    313   bd.wallet_timestamp = random_time ();
    314   do {
    315     bd.refund_deadline = random_time ();
    316     bd.wire_deadline = random_time ();
    317   } while (GNUNET_TIME_timestamp_cmp (bd.wire_deadline,
    318                                       <,
    319                                       bd.refund_deadline));
    320 
    321   make_amount (1, 0, &deposit.amount_with_fee);
    322 
    323   {
    324     struct GNUNET_TIME_Timestamp now;
    325     bool balance_ok;
    326     uint32_t bad_idx;
    327     bool conflict;
    328 
    329     now = random_time ();
    330     if (0 >=
    331         plugin->do_deposit (plugin->cls,
    332                             &bd,
    333                             &now,
    334                             &balance_ok,
    335                             &bad_idx,
    336                             &conflict))
    337     {
    338       GNUNET_break (0);
    339       global_ret = EXIT_FAILURE;
    340       GNUNET_SCHEDULER_shutdown ();
    341       return false;
    342     }
    343   }
    344   if (GNUNET_YES ==
    345       eval_probability (((float) refund_rate) / 100.0f))
    346     return add_refund (m,
    347                        &d);
    348   return true;
    349 }
    350 
    351 
    352 /**
    353  * Function to do the work.
    354  *
    355  * @param cls unused
    356  */
    357 static void
    358 work (void *cls)
    359 {
    360   struct Merchant m;
    361   uint64_t rnd1;
    362   uint64_t rnd2;
    363 
    364   (void) cls;
    365   task = NULL;
    366   rnd1 = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
    367                                    UINT64_MAX);
    368   rnd2 = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
    369                                    UINT64_MAX);
    370   GNUNET_asprintf (&m.payto_uri.full_payto,
    371                    "payto://x-taler-bank/localhost:8082/account-%llX-%llX",
    372                    (unsigned long long) rnd1,
    373                    (unsigned long long) rnd2);
    374   RANDOMIZE (&m.merchant_pub);
    375   RANDOMIZE (&m.wire_salt);
    376   TALER_merchant_wire_signature_hash (m.payto_uri,
    377                                       &m.wire_salt,
    378                                       &m.h_wire);
    379   if (GNUNET_OK !=
    380       plugin->start (plugin->cls,
    381                      "aggregator-benchmark-fill"))
    382   {
    383     GNUNET_break (0);
    384     global_ret = EXIT_FAILURE;
    385     goto exit;
    386   }
    387   for (unsigned int i = 0; i<howmany_deposits; i++)
    388   {
    389     if (! add_deposit (&m))
    390     {
    391       global_ret = EXIT_FAILURE;
    392       goto exit;
    393     }
    394   }
    395   if (0 <=
    396       plugin->commit (plugin->cls))
    397   {
    398     if (0 == --howmany_merchants)
    399     {
    400       goto exit;
    401     }
    402   }
    403   else
    404   {
    405     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    406                 "Failed to commit, will try again\n");
    407   }
    408   GNUNET_free (m.payto_uri.full_payto);
    409   task = GNUNET_SCHEDULER_add_now (&work,
    410                                    NULL);
    411   return;
    412 exit:
    413   GNUNET_SCHEDULER_shutdown ();
    414   GNUNET_free (m.payto_uri.full_payto);
    415   return;
    416 }
    417 
    418 
    419 /**
    420  * Actual execution.
    421  *
    422  * @param cls unused
    423  * @param args remaining command-line arguments
    424  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
    425  * @param c configuration
    426  */
    427 static void
    428 run (void *cls,
    429      char *const *args,
    430      const char *cfgfile,
    431      const struct GNUNET_CONFIGURATION_Handle *c)
    432 {
    433   struct TALER_EXCHANGEDB_DenominationKeyInformation issue;
    434 
    435   (void) cls;
    436   (void) args;
    437   (void) cfgfile;
    438   /* make sure everything 'ends' before the current time,
    439      so that the aggregator will process everything without
    440      need for time-travel */
    441   end = GNUNET_TIME_timestamp_get ();
    442   start = GNUNET_TIME_absolute_to_timestamp (
    443     GNUNET_TIME_absolute_subtract (end.abs_time,
    444                                    GNUNET_TIME_UNIT_MONTHS));
    445   cfg = c;
    446   if (GNUNET_OK !=
    447       TALER_config_get_currency (cfg,
    448                                  "exchange",
    449                                  &currency))
    450   {
    451     global_ret = EXIT_NOTCONFIGURED;
    452     return;
    453   }
    454   plugin = TALER_EXCHANGEDB_plugin_load (cfg,
    455                                          false);
    456   if (NULL == plugin)
    457   {
    458     global_ret = EXIT_NOTCONFIGURED;
    459     return;
    460   }
    461   if (GNUNET_SYSERR ==
    462       plugin->preflight (plugin->cls))
    463   {
    464     global_ret = EXIT_FAILURE;
    465     TALER_EXCHANGEDB_plugin_unload (plugin);
    466     return;
    467   }
    468   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
    469                                  NULL);
    470   memset (&issue,
    471           0,
    472           sizeof (issue));
    473   RANDOMIZE (&issue.signature);
    474   issue.start
    475     = start;
    476   issue.expire_withdraw
    477     = GNUNET_TIME_absolute_to_timestamp (
    478         GNUNET_TIME_absolute_add (start.abs_time,
    479                                   GNUNET_TIME_UNIT_DAYS));
    480   issue.expire_deposit
    481     = end;
    482   issue.expire_legal
    483     = GNUNET_TIME_absolute_to_timestamp (
    484         GNUNET_TIME_absolute_add (end.abs_time,
    485                                   GNUNET_TIME_UNIT_YEARS));
    486   {
    487     struct TALER_DenominationPrivateKey pk;
    488     struct TALER_DenominationPublicKey denom_pub;
    489     struct TALER_CoinPubHashP c_hash;
    490     struct TALER_PlanchetDetail pd;
    491     struct TALER_BlindedDenominationSignature bds;
    492     struct TALER_PlanchetMasterSecretP ps;
    493     struct TALER_CoinSpendPublicKeyP coin_pub;
    494     struct TALER_AgeCommitmentHashP hac;
    495     union GNUNET_CRYPTO_BlindingSecretP bks;
    496     const struct TALER_ExchangeBlindingValues *alg_values;
    497 
    498     RANDOMIZE (&coin_pub);
    499     GNUNET_assert (GNUNET_OK ==
    500                    TALER_denom_priv_create (&pk,
    501                                             &denom_pub,
    502                                             GNUNET_CRYPTO_BSA_RSA,
    503                                             1024));
    504     alg_values = TALER_denom_ewv_rsa_singleton ();
    505     denom_pub.age_mask = issue.age_mask;
    506     TALER_denom_pub_hash (&denom_pub,
    507                           &h_denom_pub);
    508     make_amount (2, 0, &issue.value);
    509     make_amount (0, 5, &issue.fees.withdraw);
    510     make_amount (0, 5, &issue.fees.deposit);
    511     make_amount (0, 5, &issue.fees.refresh);
    512     make_amount (0, 5, &issue.fees.refund);
    513     issue.denom_hash = h_denom_pub;
    514     if (0 >=
    515         plugin->insert_denomination_info (plugin->cls,
    516                                           &denom_pub,
    517                                           &issue))
    518     {
    519       GNUNET_break (0);
    520       GNUNET_SCHEDULER_shutdown ();
    521       global_ret = EXIT_FAILURE;
    522       return;
    523     }
    524 
    525     TALER_planchet_blinding_secret_create (&ps,
    526                                            TALER_denom_ewv_rsa_singleton (),
    527                                            &bks);
    528 
    529     {
    530       struct GNUNET_HashCode seed;
    531       struct TALER_AgeMask mask = {
    532         .bits = 1 | (1 << 8) | (1 << 12) | (1 << 16) | (1 << 18)
    533       };
    534       struct TALER_AgeCommitmentProof acp = {0};
    535 
    536       GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
    537                                   &seed,
    538                                   sizeof(seed));
    539       TALER_age_restriction_commit (&mask,
    540                                     13,
    541                                     &seed,
    542                                     &acp);
    543       TALER_age_commitment_hash (&acp.commitment,
    544                                  &hac);
    545     }
    546 
    547     GNUNET_assert (GNUNET_OK ==
    548                    TALER_denom_blind (&denom_pub,
    549                                       &bks,
    550                                       NULL,
    551                                       &hac,
    552                                       &coin_pub,
    553                                       alg_values,
    554                                       &c_hash,
    555                                       &pd.blinded_planchet));
    556     GNUNET_assert (GNUNET_OK ==
    557                    TALER_denom_sign_blinded (&bds,
    558                                              &pk,
    559                                              false,
    560                                              &pd.blinded_planchet));
    561     TALER_blinded_planchet_free (&pd.blinded_planchet);
    562     GNUNET_assert (GNUNET_OK ==
    563                    TALER_denom_sig_unblind (&denom_sig,
    564                                             &bds,
    565                                             &bks,
    566                                             &c_hash,
    567                                             alg_values,
    568                                             &denom_pub));
    569     TALER_blinded_denom_sig_free (&bds);
    570     TALER_denom_pub_free (&denom_pub);
    571     TALER_denom_priv_free (&pk);
    572   }
    573 
    574   {
    575     struct TALER_WireFeeSet fees;
    576     struct TALER_MasterSignatureP master_sig;
    577     unsigned int year;
    578     struct GNUNET_TIME_Timestamp ws;
    579     struct GNUNET_TIME_Timestamp we;
    580 
    581     year = GNUNET_TIME_get_current_year ();
    582     for (unsigned int y = year - 1; y<year + 2; y++)
    583     {
    584       ws = GNUNET_TIME_absolute_to_timestamp (GNUNET_TIME_year_to_time (y - 1));
    585       we = GNUNET_TIME_absolute_to_timestamp (GNUNET_TIME_year_to_time (y));
    586       make_amount (0, 5, &fees.wire);
    587       make_amount (0, 5, &fees.closing);
    588       memset (&master_sig,
    589               0,
    590               sizeof (master_sig));
    591       if (0 >
    592           plugin->insert_wire_fee (plugin->cls,
    593                                    "x-taler-bank",
    594                                    ws,
    595                                    we,
    596                                    &fees,
    597                                    &master_sig))
    598       {
    599         GNUNET_break (0);
    600         GNUNET_SCHEDULER_shutdown ();
    601         global_ret = EXIT_FAILURE;
    602         return;
    603       }
    604     }
    605   }
    606 
    607   task = GNUNET_SCHEDULER_add_now (&work,
    608                                    NULL);
    609 }
    610 
    611 
    612 /**
    613  * The main function of the taler-aggregator-benchmark tool.
    614  *
    615  * @param argc number of arguments from the command line
    616  * @param argv command line arguments
    617  * @return 0 ok, non-zero on failure
    618  */
    619 int
    620 main (int argc,
    621       char *const *argv)
    622 {
    623   struct GNUNET_GETOPT_CommandLineOption options[] = {
    624     GNUNET_GETOPT_option_uint ('d',
    625                                "deposits",
    626                                "DN",
    627                                "How many deposits we should instantiate per merchant",
    628                                &howmany_deposits),
    629     GNUNET_GETOPT_option_uint ('m',
    630                                "merchants",
    631                                "DM",
    632                                "How many merchants should we create",
    633                                &howmany_merchants),
    634     GNUNET_GETOPT_option_uint ('r',
    635                                "refunds",
    636                                "RATE",
    637                                "Probability of refund per deposit (0-100)",
    638                                &refund_rate),
    639     GNUNET_GETOPT_OPTION_END
    640   };
    641   enum GNUNET_GenericReturnValue result;
    642 
    643   unsetenv ("XDG_DATA_HOME");
    644   unsetenv ("XDG_CONFIG_HOME");
    645   if (0 >=
    646       (result = GNUNET_PROGRAM_run (
    647          TALER_EXCHANGE_project_data (),
    648          argc,
    649          argv,
    650          "taler-aggregator-benchmark",
    651          "generate database to benchmark the aggregator",
    652          options,
    653          &run,
    654          NULL)))
    655   {
    656     if (GNUNET_NO == result)
    657       return EXIT_SUCCESS;
    658     return EXIT_INVALIDARGUMENT;
    659   }
    660   return global_ret;
    661 }