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