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


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