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 (18087B)


      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,
    322                0,
    323                &deposit.amount_with_fee);
    324 
    325   {
    326     struct GNUNET_TIME_Timestamp now;
    327     struct TALER_Amount deposit_fee;
    328     struct TALER_Amount total;
    329     bool balance_ok;
    330     uint32_t bad_idx;
    331     bool conflict;
    332 
    333     GNUNET_assert (1 == bd.num_cdis);
    334     make_amount (0,
    335                  0,
    336                  &deposit_fee);
    337     now = random_time ();
    338     if (0 >=
    339         plugin->do_deposit (plugin->cls,
    340                             &bd,
    341                             &deposit_fee,
    342                             &now,
    343                             &total,
    344                             &balance_ok,
    345                             &bad_idx,
    346                             &conflict))
    347     {
    348       GNUNET_break (0);
    349       global_ret = EXIT_FAILURE;
    350       GNUNET_SCHEDULER_shutdown ();
    351       return false;
    352     }
    353   }
    354   if (GNUNET_YES ==
    355       eval_probability (((float) refund_rate) / 100.0f))
    356     return add_refund (m,
    357                        &d);
    358   return true;
    359 }
    360 
    361 
    362 /**
    363  * Function to do the work.
    364  *
    365  * @param cls unused
    366  */
    367 static void
    368 work (void *cls)
    369 {
    370   struct Merchant m;
    371   uint64_t rnd1;
    372   uint64_t rnd2;
    373 
    374   (void) cls;
    375   task = NULL;
    376   rnd1 = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
    377                                    UINT64_MAX);
    378   rnd2 = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
    379                                    UINT64_MAX);
    380   GNUNET_asprintf (&m.payto_uri.full_payto,
    381                    "payto://x-taler-bank/localhost:8082/account-%llX-%llX",
    382                    (unsigned long long) rnd1,
    383                    (unsigned long long) rnd2);
    384   RANDOMIZE (&m.merchant_pub);
    385   RANDOMIZE (&m.wire_salt);
    386   TALER_merchant_wire_signature_hash (m.payto_uri,
    387                                       &m.wire_salt,
    388                                       &m.h_wire);
    389   if (GNUNET_OK !=
    390       plugin->start (plugin->cls,
    391                      "aggregator-benchmark-fill"))
    392   {
    393     GNUNET_break (0);
    394     global_ret = EXIT_FAILURE;
    395     goto exit;
    396   }
    397   for (unsigned int i = 0; i<howmany_deposits; i++)
    398   {
    399     if (! add_deposit (&m))
    400     {
    401       global_ret = EXIT_FAILURE;
    402       goto exit;
    403     }
    404   }
    405   if (0 <=
    406       plugin->commit (plugin->cls))
    407   {
    408     if (0 == --howmany_merchants)
    409     {
    410       goto exit;
    411     }
    412   }
    413   else
    414   {
    415     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
    416                 "Failed to commit, will try again\n");
    417   }
    418   GNUNET_free (m.payto_uri.full_payto);
    419   task = GNUNET_SCHEDULER_add_now (&work,
    420                                    NULL);
    421   return;
    422 exit:
    423   GNUNET_SCHEDULER_shutdown ();
    424   GNUNET_free (m.payto_uri.full_payto);
    425   return;
    426 }
    427 
    428 
    429 /**
    430  * Actual execution.
    431  *
    432  * @param cls unused
    433  * @param args remaining command-line arguments
    434  * @param cfgfile name of the configuration file used (for saving, can be NULL!)
    435  * @param c configuration
    436  */
    437 static void
    438 run (void *cls,
    439      char *const *args,
    440      const char *cfgfile,
    441      const struct GNUNET_CONFIGURATION_Handle *c)
    442 {
    443   struct TALER_EXCHANGEDB_DenominationKeyInformation issue;
    444 
    445   (void) cls;
    446   (void) args;
    447   (void) cfgfile;
    448   /* make sure everything 'ends' before the current time,
    449      so that the aggregator will process everything without
    450      need for time-travel */
    451   end = GNUNET_TIME_timestamp_get ();
    452   start = GNUNET_TIME_absolute_to_timestamp (
    453     GNUNET_TIME_absolute_subtract (end.abs_time,
    454                                    GNUNET_TIME_UNIT_MONTHS));
    455   cfg = c;
    456   if (GNUNET_OK !=
    457       TALER_config_get_currency (cfg,
    458                                  "exchange",
    459                                  &currency))
    460   {
    461     global_ret = EXIT_NOTCONFIGURED;
    462     return;
    463   }
    464   plugin = TALER_EXCHANGEDB_plugin_load (cfg,
    465                                          false);
    466   if (NULL == plugin)
    467   {
    468     global_ret = EXIT_NOTCONFIGURED;
    469     return;
    470   }
    471   if (GNUNET_SYSERR ==
    472       plugin->preflight (plugin->cls))
    473   {
    474     global_ret = EXIT_FAILURE;
    475     TALER_EXCHANGEDB_plugin_unload (plugin);
    476     return;
    477   }
    478   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
    479                                  NULL);
    480   memset (&issue,
    481           0,
    482           sizeof (issue));
    483   RANDOMIZE (&issue.signature);
    484   issue.start
    485     = start;
    486   issue.expire_withdraw
    487     = GNUNET_TIME_absolute_to_timestamp (
    488         GNUNET_TIME_absolute_add (start.abs_time,
    489                                   GNUNET_TIME_UNIT_DAYS));
    490   issue.expire_deposit
    491     = end;
    492   issue.expire_legal
    493     = GNUNET_TIME_absolute_to_timestamp (
    494         GNUNET_TIME_absolute_add (end.abs_time,
    495                                   GNUNET_TIME_UNIT_YEARS));
    496   {
    497     struct TALER_DenominationPrivateKey pk;
    498     struct TALER_DenominationPublicKey denom_pub;
    499     struct TALER_CoinPubHashP c_hash;
    500     struct TALER_PlanchetDetail pd;
    501     struct TALER_BlindedDenominationSignature bds;
    502     struct TALER_PlanchetMasterSecretP ps;
    503     struct TALER_CoinSpendPublicKeyP coin_pub;
    504     struct TALER_AgeCommitmentHashP hac;
    505     union GNUNET_CRYPTO_BlindingSecretP bks;
    506     const struct TALER_ExchangeBlindingValues *alg_values;
    507 
    508     RANDOMIZE (&coin_pub);
    509     GNUNET_assert (GNUNET_OK ==
    510                    TALER_denom_priv_create (&pk,
    511                                             &denom_pub,
    512                                             GNUNET_CRYPTO_BSA_RSA,
    513                                             1024));
    514     alg_values = TALER_denom_ewv_rsa_singleton ();
    515     denom_pub.age_mask = issue.age_mask;
    516     TALER_denom_pub_hash (&denom_pub,
    517                           &h_denom_pub);
    518     make_amount (2, 0, &issue.value);
    519     make_amount (0, 5, &issue.fees.withdraw);
    520     make_amount (0, 5, &issue.fees.deposit);
    521     make_amount (0, 5, &issue.fees.refresh);
    522     make_amount (0, 5, &issue.fees.refund);
    523     issue.denom_hash = h_denom_pub;
    524     if (0 >=
    525         plugin->insert_denomination_info (plugin->cls,
    526                                           &denom_pub,
    527                                           &issue))
    528     {
    529       GNUNET_break (0);
    530       GNUNET_SCHEDULER_shutdown ();
    531       global_ret = EXIT_FAILURE;
    532       return;
    533     }
    534 
    535     TALER_planchet_blinding_secret_create (&ps,
    536                                            TALER_denom_ewv_rsa_singleton (),
    537                                            &bks);
    538 
    539     {
    540       struct GNUNET_HashCode seed;
    541       struct TALER_AgeMask mask = {
    542         .bits = 1 | (1 << 8) | (1 << 12) | (1 << 16) | (1 << 18)
    543       };
    544       struct TALER_AgeCommitmentProof acp = {0};
    545 
    546       GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
    547                                   &seed,
    548                                   sizeof(seed));
    549       TALER_age_restriction_commit (&mask,
    550                                     13,
    551                                     &seed,
    552                                     &acp);
    553       TALER_age_commitment_hash (&acp.commitment,
    554                                  &hac);
    555     }
    556 
    557     GNUNET_assert (GNUNET_OK ==
    558                    TALER_denom_blind (&denom_pub,
    559                                       &bks,
    560                                       NULL,
    561                                       &hac,
    562                                       &coin_pub,
    563                                       alg_values,
    564                                       &c_hash,
    565                                       &pd.blinded_planchet));
    566     GNUNET_assert (GNUNET_OK ==
    567                    TALER_denom_sign_blinded (&bds,
    568                                              &pk,
    569                                              false,
    570                                              &pd.blinded_planchet));
    571     TALER_blinded_planchet_free (&pd.blinded_planchet);
    572     GNUNET_assert (GNUNET_OK ==
    573                    TALER_denom_sig_unblind (&denom_sig,
    574                                             &bds,
    575                                             &bks,
    576                                             &c_hash,
    577                                             alg_values,
    578                                             &denom_pub));
    579     TALER_blinded_denom_sig_free (&bds);
    580     TALER_denom_pub_free (&denom_pub);
    581     TALER_denom_priv_free (&pk);
    582   }
    583 
    584   {
    585     struct TALER_WireFeeSet fees;
    586     struct TALER_MasterSignatureP master_sig;
    587     unsigned int year;
    588     struct GNUNET_TIME_Timestamp ws;
    589     struct GNUNET_TIME_Timestamp we;
    590 
    591     year = GNUNET_TIME_get_current_year ();
    592     for (unsigned int y = year - 1; y<year + 2; y++)
    593     {
    594       ws = GNUNET_TIME_absolute_to_timestamp (GNUNET_TIME_year_to_time (y - 1));
    595       we = GNUNET_TIME_absolute_to_timestamp (GNUNET_TIME_year_to_time (y));
    596       make_amount (0, 5, &fees.wire);
    597       make_amount (0, 5, &fees.closing);
    598       memset (&master_sig,
    599               0,
    600               sizeof (master_sig));
    601       if (0 >
    602           plugin->insert_wire_fee (plugin->cls,
    603                                    "x-taler-bank",
    604                                    ws,
    605                                    we,
    606                                    &fees,
    607                                    &master_sig))
    608       {
    609         GNUNET_break (0);
    610         GNUNET_SCHEDULER_shutdown ();
    611         global_ret = EXIT_FAILURE;
    612         return;
    613       }
    614     }
    615   }
    616 
    617   task = GNUNET_SCHEDULER_add_now (&work,
    618                                    NULL);
    619 }
    620 
    621 
    622 /**
    623  * The main function of the taler-aggregator-benchmark tool.
    624  *
    625  * @param argc number of arguments from the command line
    626  * @param argv command line arguments
    627  * @return 0 ok, non-zero on failure
    628  */
    629 int
    630 main (int argc,
    631       char *const *argv)
    632 {
    633   struct GNUNET_GETOPT_CommandLineOption options[] = {
    634     GNUNET_GETOPT_option_uint ('d',
    635                                "deposits",
    636                                "DN",
    637                                "How many deposits we should instantiate per merchant",
    638                                &howmany_deposits),
    639     GNUNET_GETOPT_option_uint ('m',
    640                                "merchants",
    641                                "DM",
    642                                "How many merchants should we create",
    643                                &howmany_merchants),
    644     GNUNET_GETOPT_option_uint ('r',
    645                                "refunds",
    646                                "RATE",
    647                                "Probability of refund per deposit (0-100)",
    648                                &refund_rate),
    649     GNUNET_GETOPT_OPTION_END
    650   };
    651   enum GNUNET_GenericReturnValue result;
    652 
    653   unsetenv ("XDG_DATA_HOME");
    654   unsetenv ("XDG_CONFIG_HOME");
    655   if (0 >=
    656       (result = GNUNET_PROGRAM_run (
    657          TALER_EXCHANGE_project_data (),
    658          argc,
    659          argv,
    660          "taler-aggregator-benchmark",
    661          "generate database to benchmark the aggregator",
    662          options,
    663          &run,
    664          NULL)))
    665   {
    666     if (GNUNET_NO == result)
    667       return EXIT_SUCCESS;
    668     return EXIT_INVALIDARGUMENT;
    669   }
    670   return global_ret;
    671 }