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