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


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