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


      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 (GNUNET_CRYPTO_QUALITY_WEAK,
    118                                      UINT64_MAX);
    119   random_01 = (double) random / (double) UINT64_MAX;
    120   return (random_01 <= probability) ? GNUNET_OK : GNUNET_NO;
    121 }
    122 
    123 
    124 /**
    125  * Randomize data at pointer @a x
    126  *
    127  * @param x pointer to data to randomize
    128  */
    129 #define RANDOMIZE(x) \
    130         GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, x, sizeof (*x))
    131 
    132 
    133 /**
    134  * Initialize @a out with an amount given by @a val and
    135  * @a frac using the main "currency".
    136  *
    137  * @param val value to set
    138  * @param frac fraction to set
    139  * @param[out] out where to write the amount
    140  */
    141 static void
    142 make_amount (unsigned int val,
    143              unsigned int frac,
    144              struct TALER_Amount *out)
    145 {
    146   GNUNET_assert (GNUNET_OK ==
    147                  TALER_amount_set_zero (currency,
    148                                         out));
    149   out->value = val;
    150   out->fraction = frac;
    151 }
    152 
    153 
    154 /**
    155  * Create random-ish timestamp.
    156  *
    157  * @return time stamp between start and end
    158  */
    159 static struct GNUNET_TIME_Timestamp
    160 random_time (void)
    161 {
    162   uint64_t delta;
    163   struct GNUNET_TIME_Absolute ret;
    164 
    165   delta = end.abs_time.abs_value_us - start.abs_time.abs_value_us;
    166   delta = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
    167                                     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 (GNUNET_CRYPTO_QUALITY_NONCE,
    385                                    UINT64_MAX);
    386   rnd2 = GNUNET_CRYPTO_random_u64 (GNUNET_CRYPTO_QUALITY_NONCE,
    387                                    UINT64_MAX);
    388   GNUNET_asprintf (&m.payto_uri.full_payto,
    389                    "payto://x-taler-bank/localhost:8082/account-%llX-%llX",
    390                    (unsigned long long) rnd1,
    391                    (unsigned long long) rnd2);
    392   RANDOMIZE (&m.merchant_pub);
    393   RANDOMIZE (&m.wire_salt);
    394   TALER_merchant_wire_signature_hash (m.payto_uri,
    395                                       &m.wire_salt,
    396                                       &m.h_wire);
    397   if (GNUNET_OK !=
    398       TALER_EXCHANGEDB_start (pg,
    399                               "aggregator-benchmark-fill"))
    400   {
    401     GNUNET_break (0);
    402     global_ret = EXIT_FAILURE;
    403     goto exit;
    404   }
    405   for (unsigned int i = 0; i<howmany_deposits; i++)
    406   {
    407     if (! add_deposit (&m))
    408     {
    409       global_ret = EXIT_FAILURE;
    410       goto exit;
    411     }
    412   }
    413   if (0 <=
    414       TALER_EXCHANGEDB_commit (pg))
    415   {
    416     if (0 == --howmany_merchants)
    417     {
    418       goto exit;
    419     }
    420   }
    421   else
    422   {
    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                                  false);
    474   if (NULL == pg)
    475   {
    476     global_ret = EXIT_NOTCONFIGURED;
    477     return;
    478   }
    479   if (GNUNET_SYSERR ==
    480       TALER_EXCHANGEDB_preflight (pg))
    481   {
    482     global_ret = EXIT_FAILURE;
    483     TALER_EXCHANGEDB_disconnect (pg);
    484     return;
    485   }
    486   GNUNET_SCHEDULER_add_shutdown (&do_shutdown,
    487                                  NULL);
    488   memset (&issue,
    489           0,
    490           sizeof (issue));
    491   RANDOMIZE (&issue.signature);
    492   issue.start
    493     = start;
    494   issue.expire_withdraw
    495     = GNUNET_TIME_absolute_to_timestamp (
    496         GNUNET_TIME_absolute_add (start.abs_time,
    497                                   GNUNET_TIME_UNIT_DAYS));
    498   issue.expire_deposit
    499     = end;
    500   issue.expire_legal
    501     = GNUNET_TIME_absolute_to_timestamp (
    502         GNUNET_TIME_absolute_add (end.abs_time,
    503                                   GNUNET_TIME_UNIT_YEARS));
    504   {
    505     struct TALER_DenominationPrivateKey pk;
    506     struct TALER_DenominationPublicKey denom_pub;
    507     struct TALER_CoinPubHashP c_hash;
    508     struct TALER_PlanchetDetail pd;
    509     struct TALER_BlindedDenominationSignature bds;
    510     struct TALER_PlanchetMasterSecretP ps;
    511     struct TALER_CoinSpendPublicKeyP coin_pub;
    512     struct TALER_AgeCommitmentHashP hac;
    513     union GNUNET_CRYPTO_BlindingSecretP bks;
    514     const struct TALER_ExchangeBlindingValues *alg_values;
    515 
    516     RANDOMIZE (&coin_pub);
    517     GNUNET_assert (GNUNET_OK ==
    518                    TALER_denom_priv_create (&pk,
    519                                             &denom_pub,
    520                                             GNUNET_CRYPTO_BSA_RSA,
    521                                             1024));
    522     alg_values = TALER_denom_ewv_rsa_singleton ();
    523     denom_pub.age_mask = issue.age_mask;
    524     TALER_denom_pub_hash (&denom_pub,
    525                           &h_denom_pub);
    526     make_amount (2, 0, &issue.value);
    527     make_amount (0, 5, &issue.fees.withdraw);
    528     make_amount (0, 5, &issue.fees.deposit);
    529     make_amount (0, 5, &issue.fees.refresh);
    530     make_amount (0, 5, &issue.fees.refund);
    531     issue.denom_hash = h_denom_pub;
    532     if (0 >=
    533         TALER_EXCHANGEDB_insert_denomination_info (pg,
    534                                                    &denom_pub,
    535                                                    &issue))
    536     {
    537       GNUNET_break (0);
    538       GNUNET_SCHEDULER_shutdown ();
    539       global_ret = EXIT_FAILURE;
    540       return;
    541     }
    542 
    543     TALER_planchet_blinding_secret_create (&ps,
    544                                            TALER_denom_ewv_rsa_singleton (),
    545                                            &bks);
    546 
    547     {
    548       struct GNUNET_HashCode seed;
    549       struct TALER_AgeMask mask = {
    550         .bits = 1 | (1 << 8) | (1 << 12) | (1 << 16) | (1 << 18)
    551       };
    552       struct TALER_AgeCommitmentProof acp = {0};
    553 
    554       GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK,
    555                                   &seed,
    556                                   sizeof(seed));
    557       TALER_age_restriction_commit (&mask,
    558                                     13,
    559                                     &seed,
    560                                     &acp);
    561       TALER_age_commitment_hash (&acp.commitment,
    562                                  &hac);
    563     }
    564 
    565     GNUNET_assert (GNUNET_OK ==
    566                    TALER_denom_blind (&denom_pub,
    567                                       &bks,
    568                                       NULL,
    569                                       &hac,
    570                                       &coin_pub,
    571                                       alg_values,
    572                                       &c_hash,
    573                                       &pd.blinded_planchet));
    574     GNUNET_assert (GNUNET_OK ==
    575                    TALER_denom_sign_blinded (&bds,
    576                                              &pk,
    577                                              false,
    578                                              &pd.blinded_planchet));
    579     TALER_blinded_planchet_free (&pd.blinded_planchet);
    580     GNUNET_assert (GNUNET_OK ==
    581                    TALER_denom_sig_unblind (&denom_sig,
    582                                             &bds,
    583                                             &bks,
    584                                             &c_hash,
    585                                             alg_values,
    586                                             &denom_pub));
    587     TALER_blinded_denom_sig_free (&bds);
    588     TALER_denom_pub_free (&denom_pub);
    589     TALER_denom_priv_free (&pk);
    590   }
    591 
    592   {
    593     struct TALER_WireFeeSet fees;
    594     struct TALER_MasterSignatureP master_sig;
    595     unsigned int year;
    596     struct GNUNET_TIME_Timestamp ws;
    597     struct GNUNET_TIME_Timestamp we;
    598 
    599     year = GNUNET_TIME_get_current_year ();
    600     for (unsigned int y = year - 1; y<year + 2; y++)
    601     {
    602       ws = GNUNET_TIME_absolute_to_timestamp (GNUNET_TIME_year_to_time (y - 1));
    603       we = GNUNET_TIME_absolute_to_timestamp (GNUNET_TIME_year_to_time (y));
    604       make_amount (0, 5, &fees.wire);
    605       make_amount (0, 5, &fees.closing);
    606       memset (&master_sig,
    607               0,
    608               sizeof (master_sig));
    609       if (0 >
    610           TALER_TALER_EXCHANGEDB_insert_wire_fee (pg,
    611                                                   "x-taler-bank",
    612                                                   ws,
    613                                                   we,
    614                                                   &fees,
    615                                                   &master_sig))
    616       {
    617         GNUNET_break (0);
    618         GNUNET_SCHEDULER_shutdown ();
    619         global_ret = EXIT_FAILURE;
    620         return;
    621       }
    622     }
    623   }
    624 
    625   task = GNUNET_SCHEDULER_add_now (&work,
    626                                    NULL);
    627 }
    628 
    629 
    630 /**
    631  * The main function of the taler-aggregator-benchmark tool.
    632  *
    633  * @param argc number of arguments from the command line
    634  * @param argv command line arguments
    635  * @return 0 ok, non-zero on failure
    636  */
    637 int
    638 main (int argc,
    639       char *const *argv)
    640 {
    641   struct GNUNET_GETOPT_CommandLineOption options[] = {
    642     GNUNET_GETOPT_option_uint ('d',
    643                                "deposits",
    644                                "DN",
    645                                "How many deposits we should instantiate per merchant",
    646                                &howmany_deposits),
    647     GNUNET_GETOPT_option_uint ('m',
    648                                "merchants",
    649                                "DM",
    650                                "How many merchants should we create",
    651                                &howmany_merchants),
    652     GNUNET_GETOPT_option_uint ('r',
    653                                "refunds",
    654                                "RATE",
    655                                "Probability of refund per deposit (0-100)",
    656                                &refund_rate),
    657     GNUNET_GETOPT_OPTION_END
    658   };
    659   enum GNUNET_GenericReturnValue result;
    660 
    661   unsetenv ("XDG_DATA_HOME");
    662   unsetenv ("XDG_CONFIG_HOME");
    663   if (0 >=
    664       (result = GNUNET_PROGRAM_run (
    665          TALER_EXCHANGE_project_data (),
    666          argc,
    667          argv,
    668          "taler-aggregator-benchmark",
    669          "generate database to benchmark the aggregator",
    670          options,
    671          &run,
    672          NULL)))
    673   {
    674     if (GNUNET_NO == result)
    675       return EXIT_SUCCESS;
    676     return EXIT_INVALIDARGUMENT;
    677   }
    678   return global_ret;
    679 }