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 ¤cy)) 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 }