taler-aggregator-benchmark.c (18585B)
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/do_insert_known_coins.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 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 { 302 const struct TALER_CoinPublicInfo *coins[1] = { &d.coin }; 303 struct TALER_EXCHANGEDB_CoinKnownResult ckr; 304 305 if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 306 TALER_EXCHANGEDB_do_insert_known_coins (pg, 307 1, 308 coins, 309 &ckr)) || 310 (TALER_EXCHANGEDB_CKS_ADDED != ckr.status) ) 311 known_coin_id = 0; 312 else 313 known_coin_id = ckr.known_coin_id; 314 } 315 if (0 == known_coin_id) 316 { 317 GNUNET_break (0); 318 global_ret = EXIT_FAILURE; 319 GNUNET_SCHEDULER_shutdown (); 320 return false; 321 } 322 deposit.coin = d.coin; 323 RANDOMIZE (&deposit.csig); 324 bd.merchant_pub = m->merchant_pub; 325 bd.h_contract_terms = d.h_contract_terms; 326 bd.wire_salt = m->wire_salt; 327 bd.receiver_wire_account = m->payto_uri; 328 bd.wallet_timestamp = random_time (); 329 do { 330 bd.refund_deadline = random_time (); 331 bd.wire_deadline = random_time (); 332 } while (GNUNET_TIME_timestamp_cmp (bd.wire_deadline, 333 <, 334 bd.refund_deadline)); 335 336 make_amount (1, 337 0, 338 &deposit.amount_with_fee); 339 340 { 341 struct GNUNET_TIME_Timestamp now; 342 struct TALER_Amount deposit_fee; 343 struct TALER_Amount total; 344 bool balance_ok; 345 uint32_t bad_idx; 346 bool conflict; 347 348 GNUNET_assert (1 == bd.num_cdis); 349 make_amount (0, 350 0, 351 &deposit_fee); 352 now = random_time (); 353 if (0 >= 354 TALER_EXCHANGEDB_do_deposit (pg, 355 &bd, 356 &deposit_fee, 357 &now, 358 &total, 359 &balance_ok, 360 &bad_idx, 361 &conflict)) 362 { 363 GNUNET_break (0); 364 global_ret = EXIT_FAILURE; 365 GNUNET_SCHEDULER_shutdown (); 366 return false; 367 } 368 } 369 if (GNUNET_YES == 370 eval_probability (((float) refund_rate) / 100.0f)) 371 return add_refund (m, 372 &d); 373 return true; 374 } 375 376 377 /** 378 * Function to do the work. 379 * 380 * @param cls unused 381 */ 382 static void 383 work (void *cls) 384 { 385 struct Merchant m; 386 uint64_t rnd1; 387 uint64_t rnd2; 388 389 (void) cls; 390 task = NULL; 391 rnd1 = GNUNET_CRYPTO_random_u64 (UINT64_MAX); 392 rnd2 = GNUNET_CRYPTO_random_u64 (UINT64_MAX); 393 GNUNET_asprintf (&m.payto_uri.full_payto, 394 "payto://x-taler-bank/localhost:8082/account-%llX-%llX", 395 (unsigned long long) rnd1, 396 (unsigned long long) rnd2); 397 RANDOMIZE (&m.merchant_pub); 398 RANDOMIZE (&m.wire_salt); 399 TALER_merchant_wire_signature_hash (m.payto_uri, 400 &m.wire_salt, 401 &m.h_wire); 402 if (GNUNET_OK != 403 TALER_EXCHANGEDB_start (pg, 404 "aggregator-benchmark-fill")) 405 { 406 GNUNET_break (0); 407 global_ret = EXIT_FAILURE; 408 goto exit; 409 } 410 for (unsigned int i = 0; i<howmany_deposits; i++) 411 { 412 if (! add_deposit (&m)) 413 { 414 global_ret = EXIT_FAILURE; 415 goto exit; 416 } 417 } 418 if (0 <= 419 TALER_EXCHANGEDB_commit (pg)) 420 { 421 if (0 == --howmany_merchants) 422 { 423 goto exit; 424 } 425 } 426 else 427 { 428 /* Note: on persistent failures, this could 429 re-try forever; OK because this is a benchmark */ 430 GNUNET_log (GNUNET_ERROR_TYPE_WARNING, 431 "Failed to commit, will try again\n"); 432 } 433 GNUNET_free (m.payto_uri.full_payto); 434 task = GNUNET_SCHEDULER_add_now (&work, 435 NULL); 436 return; 437 exit: 438 GNUNET_SCHEDULER_shutdown (); 439 GNUNET_free (m.payto_uri.full_payto); 440 return; 441 } 442 443 444 /** 445 * Actual execution. 446 * 447 * @param cls unused 448 * @param args remaining command-line arguments 449 * @param cfgfile name of the configuration file used (for saving, can be NULL!) 450 * @param c configuration 451 */ 452 static void 453 run (void *cls, 454 char *const *args, 455 const char *cfgfile, 456 const struct GNUNET_CONFIGURATION_Handle *c) 457 { 458 struct TALER_EXCHANGEDB_DenominationKeyInformation issue; 459 460 (void) cls; 461 (void) args; 462 (void) cfgfile; 463 /* make sure everything 'ends' before the current time, 464 so that the aggregator will process everything without 465 need for time-travel */ 466 end = GNUNET_TIME_timestamp_get (); 467 start = GNUNET_TIME_absolute_to_timestamp ( 468 GNUNET_TIME_absolute_subtract (end.abs_time, 469 GNUNET_TIME_UNIT_MONTHS)); 470 cfg = c; 471 if (GNUNET_OK != 472 TALER_config_get_currency (cfg, 473 "exchange", 474 ¤cy)) 475 { 476 global_ret = EXIT_NOTCONFIGURED; 477 return; 478 } 479 pg = TALER_EXCHANGEDB_connect (cfg); 480 if (NULL == pg) 481 { 482 global_ret = EXIT_NOTCONFIGURED; 483 return; 484 } 485 if (GNUNET_SYSERR == 486 TALER_EXCHANGEDB_preflight (pg)) 487 { 488 global_ret = EXIT_FAILURE; 489 TALER_EXCHANGEDB_disconnect (pg); 490 return; 491 } 492 GNUNET_SCHEDULER_add_shutdown (&do_shutdown, 493 NULL); 494 memset (&issue, 495 0, 496 sizeof (issue)); 497 RANDOMIZE (&issue.signature); 498 issue.start 499 = start; 500 issue.expire_withdraw 501 = GNUNET_TIME_absolute_to_timestamp ( 502 GNUNET_TIME_absolute_add (start.abs_time, 503 GNUNET_TIME_UNIT_DAYS)); 504 issue.expire_deposit 505 = end; 506 issue.expire_legal 507 = GNUNET_TIME_absolute_to_timestamp ( 508 GNUNET_TIME_absolute_add (end.abs_time, 509 GNUNET_TIME_UNIT_YEARS)); 510 { 511 struct TALER_DenominationPrivateKey pk; 512 struct TALER_DenominationPublicKey denom_pub; 513 struct TALER_CoinPubHashP c_hash; 514 struct TALER_PlanchetDetail pd; 515 struct TALER_BlindedDenominationSignature bds; 516 struct TALER_PlanchetMasterSecretP ps; 517 struct TALER_CoinSpendPublicKeyP coin_pub; 518 struct TALER_AgeCommitmentHashP hac; 519 union GNUNET_CRYPTO_BlindingSecretP bks; 520 const struct TALER_ExchangeBlindingValues *alg_values; 521 522 RANDOMIZE (&coin_pub); 523 GNUNET_assert (GNUNET_OK == 524 TALER_denom_priv_create (&pk, 525 &denom_pub, 526 GNUNET_CRYPTO_BSA_RSA, 527 1024)); 528 alg_values = TALER_denom_ewv_rsa_singleton (); 529 denom_pub.age_mask = issue.age_mask; 530 TALER_denom_pub_hash (&denom_pub, 531 &h_denom_pub); 532 make_amount (2, 0, &issue.value); 533 make_amount (0, 5, &issue.fees.withdraw); 534 make_amount (0, 5, &issue.fees.deposit); 535 make_amount (0, 5, &issue.fees.refresh); 536 make_amount (0, 5, &issue.fees.refund); 537 issue.denom_hash = h_denom_pub; 538 if (0 >= 539 TALER_EXCHANGEDB_insert_denomination_info (pg, 540 &denom_pub, 541 &issue)) 542 { 543 GNUNET_break (0); 544 GNUNET_SCHEDULER_shutdown (); 545 global_ret = EXIT_FAILURE; 546 return; 547 } 548 549 TALER_planchet_blinding_secret_create (&ps, 550 TALER_denom_ewv_rsa_singleton (), 551 &bks); 552 553 { 554 struct GNUNET_HashCode seed; 555 struct TALER_AgeMask mask = { 556 .bits = 1 | (1 << 8) | (1 << 12) | (1 << 16) | (1 << 18) 557 }; 558 struct TALER_AgeCommitmentProof acp = {0}; 559 560 GNUNET_CRYPTO_random_block (&seed, 561 sizeof(seed)); 562 TALER_age_restriction_commit (&mask, 563 13, 564 &seed, 565 &acp); 566 TALER_age_commitment_hash (&acp.commitment, 567 &hac); 568 } 569 570 GNUNET_assert (GNUNET_OK == 571 TALER_denom_blind (&denom_pub, 572 &bks, 573 NULL, 574 &hac, 575 &coin_pub, 576 alg_values, 577 &c_hash, 578 &pd.blinded_planchet)); 579 GNUNET_assert (GNUNET_OK == 580 TALER_denom_sign_blinded (&bds, 581 &pk, 582 false, 583 &pd.blinded_planchet)); 584 TALER_blinded_planchet_free (&pd.blinded_planchet); 585 GNUNET_assert (GNUNET_OK == 586 TALER_denom_sig_unblind (&denom_sig, 587 &bds, 588 &bks, 589 &c_hash, 590 alg_values, 591 &denom_pub)); 592 TALER_blinded_denom_sig_free (&bds); 593 TALER_denom_pub_free (&denom_pub); 594 TALER_denom_priv_free (&pk); 595 } 596 597 { 598 struct TALER_WireFeeSet fees; 599 struct TALER_MasterSignatureP master_sig; 600 unsigned int year; 601 struct GNUNET_TIME_Timestamp ws; 602 struct GNUNET_TIME_Timestamp we; 603 604 year = GNUNET_TIME_get_current_year (); 605 for (unsigned int y = year - 1; y<year + 2; y++) 606 { 607 ws = GNUNET_TIME_absolute_to_timestamp (GNUNET_TIME_year_to_time (y - 1)); 608 we = GNUNET_TIME_absolute_to_timestamp (GNUNET_TIME_year_to_time (y)); 609 make_amount (0, 5, &fees.wire); 610 make_amount (0, 5, &fees.closing); 611 memset (&master_sig, 612 0, 613 sizeof (master_sig)); 614 if (0 > 615 TALER_EXCHANGEDB_insert_wire_fee (pg, 616 "x-taler-bank", 617 ws, 618 we, 619 &fees, 620 &master_sig)) 621 { 622 GNUNET_break (0); 623 GNUNET_SCHEDULER_shutdown (); 624 global_ret = EXIT_FAILURE; 625 return; 626 } 627 } 628 } 629 630 task = GNUNET_SCHEDULER_add_now (&work, 631 NULL); 632 } 633 634 635 /** 636 * The main function of the taler-aggregator-benchmark tool. 637 * 638 * @param argc number of arguments from the command line 639 * @param argv command line arguments 640 * @return 0 ok, non-zero on failure 641 */ 642 int 643 main (int argc, 644 char *const *argv) 645 { 646 struct GNUNET_GETOPT_CommandLineOption options[] = { 647 GNUNET_GETOPT_option_uint ('d', 648 "deposits", 649 "DN", 650 "How many deposits we should instantiate per merchant", 651 &howmany_deposits), 652 GNUNET_GETOPT_option_uint ('m', 653 "merchants", 654 "DM", 655 "How many merchants should we create", 656 &howmany_merchants), 657 GNUNET_GETOPT_option_uint ('r', 658 "refunds", 659 "RATE", 660 "Probability of refund per deposit (0-100)", 661 &refund_rate), 662 GNUNET_GETOPT_OPTION_END 663 }; 664 enum GNUNET_GenericReturnValue result; 665 666 unsetenv ("XDG_DATA_HOME"); 667 unsetenv ("XDG_CONFIG_HOME"); 668 if (0 >= 669 (result = GNUNET_PROGRAM_run ( 670 TALER_EXCHANGE_project_data (), 671 argc, 672 argv, 673 "taler-aggregator-benchmark", 674 "generate database to benchmark the aggregator", 675 options, 676 &run, 677 NULL))) 678 { 679 if (GNUNET_NO == result) 680 return EXIT_SUCCESS; 681 return EXIT_INVALIDARGUMENT; 682 } 683 return global_ret; 684 }