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