perf_select_refunds_by_coin.c (20953B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2014-2023 Taler Systems SA 4 5 TALER is free software; you can redistribute it and/or modify it under the 6 terms of the GNU General Public License as published by the Free Software 7 Foundation; either version 3, or (at your option) any later version. 8 9 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 10 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 11 A PARTICULAR PURPOSE. See the GNU General Public License for more details. 12 13 You should have received a copy of the GNU General Public License along with 14 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 15 */ 16 /** 17 * @file exchangedb/perf_select_refunds_by_coin.c 18 * @brief benchmark for select_refunds_by_coin 19 * @author Joseph Xu 20 */ 21 #include "taler/platform.h" 22 #include "taler/taler_exchangedb_lib.h" 23 #include "taler/taler_json_lib.h" 24 #include "taler/taler_exchangedb_plugin.h" 25 #include "math.h" 26 27 /** 28 * Global result from the testcase. 29 */ 30 static int result; 31 32 /** 33 * Report line of error if @a cond is true, and jump to label "drop". 34 */ 35 #define FAILIF(cond) \ 36 do { \ 37 if (! (cond)) {break;} \ 38 GNUNET_break (0); \ 39 goto drop; \ 40 } while (0) 41 42 /** 43 * Initializes @a ptr with random data. 44 */ 45 #define RND_BLK(ptr) \ 46 GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, ptr, \ 47 sizeof (*ptr)) 48 49 /** 50 * Initializes @a ptr with zeros. 51 */ 52 #define ZR_BLK(ptr) \ 53 memset (ptr, 0, sizeof (*ptr)) 54 55 /** 56 * Currency we use. Must match test-exchange-db-*.conf. 57 */ 58 #define CURRENCY "EUR" 59 #define RSA_KEY_SIZE 1024 60 #define ROUNDS 100 61 #define NUM_ROWS 1000 62 #define MELT_NEW_COINS 5 63 #define MELT_NOREVEAL_INDEX 1 64 65 /** 66 * Database plugin under test. 67 */ 68 static struct TALER_EXCHANGEDB_Plugin *plugin; 69 70 71 static struct TALER_MerchantWireHashP h_wire_wt; 72 73 static struct DenomKeyPair **new_dkp; 74 75 static struct CoinInfo *revealed_coins; 76 77 struct DenomKeyPair 78 { 79 struct TALER_DenominationPrivateKey priv; 80 struct TALER_DenominationPublicKey pub; 81 }; 82 83 84 struct CoinInfo 85 { 86 struct TALER_DenominationHashP h_denom_pub; 87 struct TALER_CoinSpendSignatureP orig_coin_link_sig; 88 struct TALER_BlindedCoinHashP coin_envelope_hash; 89 struct TALER_BlindedDenominationSignature coin_sig; 90 struct TALER_ExchangeBlindingValues exchange_vals; 91 struct TALER_BlindedPlanchet blinded_planchet; 92 }; 93 94 /** 95 * Destroy a denomination key pair. The key is not necessarily removed from the DB. 96 * 97 * @param dkp the key pair to destroy 98 */ 99 static void 100 destroy_denom_key_pair (struct DenomKeyPair *dkp) 101 { 102 TALER_denom_pub_free (&dkp->pub); 103 TALER_denom_priv_free (&dkp->priv); 104 GNUNET_free (dkp); 105 } 106 107 108 /** 109 * Create a denomination key pair by registering the denomination in the DB. 110 * 111 * @param size the size of the denomination key 112 * @param now time to use for key generation, legal expiration will be 3h later. 113 * @param fees fees to use 114 * @return the denominaiton key pair; NULL upon error 115 */ 116 static struct DenomKeyPair * 117 create_denom_key_pair (unsigned int size, 118 struct GNUNET_TIME_Timestamp now, 119 const struct TALER_Amount *value, 120 const struct TALER_DenomFeeSet *fees) 121 { 122 struct DenomKeyPair *dkp; 123 struct TALER_EXCHANGEDB_DenominationKey dki; 124 struct TALER_EXCHANGEDB_DenominationKeyInformation issue2; 125 126 dkp = GNUNET_new (struct DenomKeyPair); 127 GNUNET_assert (GNUNET_OK == 128 TALER_denom_priv_create (&dkp->priv, 129 &dkp->pub, 130 GNUNET_CRYPTO_BSA_RSA, 131 size)); 132 memset (&dki, 133 0, 134 sizeof (struct TALER_EXCHANGEDB_DenominationKey)); 135 dki.denom_pub = dkp->pub; 136 dki.issue.start = now; 137 dki.issue.expire_withdraw 138 = GNUNET_TIME_absolute_to_timestamp ( 139 GNUNET_TIME_absolute_add ( 140 now.abs_time, 141 GNUNET_TIME_UNIT_HOURS)); 142 dki.issue.expire_deposit 143 = GNUNET_TIME_absolute_to_timestamp ( 144 GNUNET_TIME_absolute_add ( 145 now.abs_time, 146 GNUNET_TIME_relative_multiply ( 147 GNUNET_TIME_UNIT_HOURS, 2))); 148 dki.issue.expire_legal 149 = GNUNET_TIME_absolute_to_timestamp ( 150 GNUNET_TIME_absolute_add ( 151 now.abs_time, 152 GNUNET_TIME_relative_multiply ( 153 GNUNET_TIME_UNIT_HOURS, 3))); 154 dki.issue.value = *value; 155 dki.issue.fees = *fees; 156 TALER_denom_pub_hash (&dkp->pub, 157 &dki.issue.denom_hash); 158 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 159 plugin->insert_denomination_info (plugin->cls, 160 &dki.denom_pub, 161 &dki.issue)) 162 { 163 GNUNET_break (0); 164 destroy_denom_key_pair (dkp); 165 return NULL; 166 } 167 memset (&issue2, 0, sizeof (issue2)); 168 if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 169 plugin->get_denomination_info (plugin->cls, 170 &dki.issue.denom_hash, 171 NULL, 172 &issue2)) 173 { 174 GNUNET_break (0); 175 destroy_denom_key_pair (dkp); 176 return NULL; 177 } 178 if (0 != GNUNET_memcmp (&dki.issue, 179 &issue2)) 180 { 181 GNUNET_break (0); 182 destroy_denom_key_pair (dkp); 183 return NULL; 184 } 185 return dkp; 186 } 187 188 189 /** 190 * Callback invoked with information about refunds applicable 191 * to a particular coin. 192 * 193 * @param cls closure with the `struct TALER_EXCHANGEDB_Refund *` we expect to get 194 * @param amount_with_fee amount being refunded 195 * @return #GNUNET_OK to continue to iterate, #GNUNET_SYSERR to stop 196 */ 197 static enum GNUNET_GenericReturnValue 198 check_refund_cb (void *cls, 199 const struct TALER_Amount *amount_with_fee) 200 { 201 const struct TALER_EXCHANGEDB_Refund *refund = cls; 202 203 if (0 != TALER_amount_cmp (amount_with_fee, 204 &refund->details.refund_amount)) 205 { 206 GNUNET_break (0); 207 result = 66; 208 } 209 return GNUNET_OK; 210 } 211 212 213 /** 214 * Main function that will be run by the scheduler. 215 * 216 * @param cls closure with config 217 */ 218 219 static void 220 run (void *cls) 221 { 222 struct GNUNET_CONFIGURATION_Handle *cfg = cls; 223 const uint32_t num_partitions = 10; 224 struct GNUNET_TIME_Timestamp ts; 225 struct TALER_EXCHANGEDB_CoinDepositInformation *depos = NULL; 226 struct GNUNET_TIME_Timestamp deadline; 227 struct TALER_Amount value; 228 union GNUNET_CRYPTO_BlindingSecretP bks; 229 struct TALER_EXCHANGEDB_CollectableBlindcoin cbc; 230 struct GNUNET_CRYPTO_BlindingInputValues bi = { 231 .cipher = GNUNET_CRYPTO_BSA_RSA, 232 .rc = 0 233 }; 234 struct TALER_ExchangeBlindingValues alg_values = { 235 .blinding_inputs = &bi 236 }; 237 struct GNUNET_TIME_Relative times = GNUNET_TIME_UNIT_ZERO; 238 unsigned long long sqrs = 0; 239 struct TALER_EXCHANGEDB_Refund *ref = NULL; 240 unsigned int *perm; 241 unsigned long long duration_sq; 242 struct CoinInfo *ccoin; 243 struct TALER_DenominationPublicKey *new_denom_pubs = NULL; 244 struct TALER_DenomFeeSet fees; 245 unsigned int count = 0; 246 247 ref = GNUNET_new_array (ROUNDS + 1, 248 struct TALER_EXCHANGEDB_Refund); 249 depos = GNUNET_new_array (ROUNDS + 1, 250 struct TALER_EXCHANGEDB_CoinDepositInformation); 251 ZR_BLK (&cbc); 252 253 if (NULL == 254 (plugin = TALER_EXCHANGEDB_plugin_load (cfg, 255 true))) 256 { 257 GNUNET_break (0); 258 result = 77; 259 return; 260 } 261 (void) plugin->drop_tables (plugin->cls); 262 if (GNUNET_OK != 263 plugin->create_tables (plugin->cls, 264 true, 265 num_partitions)) 266 { 267 GNUNET_break (0); 268 result = 77; 269 goto cleanup; 270 } 271 if (GNUNET_OK != 272 plugin->preflight (plugin->cls)) 273 { 274 GNUNET_break (0); 275 goto cleanup; 276 } 277 GNUNET_assert (GNUNET_OK == 278 TALER_string_to_amount (CURRENCY ":1.000010", 279 &value)); 280 GNUNET_assert (GNUNET_OK == 281 TALER_string_to_amount (CURRENCY ":0.000010", 282 &fees.withdraw)); 283 GNUNET_assert (GNUNET_OK == 284 TALER_string_to_amount (CURRENCY ":0.000010", 285 &fees.deposit)); 286 GNUNET_assert (GNUNET_OK == 287 TALER_string_to_amount (CURRENCY ":0.000010", 288 &fees.refresh)); 289 GNUNET_assert (GNUNET_OK == 290 TALER_string_to_amount (CURRENCY ":0.000010", 291 &fees.refund)); 292 GNUNET_assert (NUM_ROWS >= ROUNDS); 293 294 ts = GNUNET_TIME_timestamp_get (); 295 deadline = GNUNET_TIME_timestamp_get (); 296 { 297 new_dkp = GNUNET_new_array (MELT_NEW_COINS, 298 struct DenomKeyPair *); 299 new_denom_pubs = GNUNET_new_array (MELT_NEW_COINS, 300 struct TALER_DenominationPublicKey); 301 revealed_coins 302 = GNUNET_new_array (MELT_NEW_COINS, 303 struct CoinInfo); 304 for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++) 305 { 306 struct GNUNET_TIME_Timestamp now; 307 struct GNUNET_CRYPTO_RsaBlindedMessage *rp; 308 struct TALER_BlindedPlanchet *bp; 309 310 now = GNUNET_TIME_timestamp_get (); 311 new_dkp[cnt] = create_denom_key_pair (RSA_KEY_SIZE, 312 now, 313 &value, 314 &fees); 315 GNUNET_assert (NULL != new_dkp[cnt]); 316 new_denom_pubs[cnt] = new_dkp[cnt]->pub; 317 ccoin = &revealed_coins[cnt]; 318 bp = &ccoin->blinded_planchet; 319 bp->blinded_message = GNUNET_new (struct GNUNET_CRYPTO_BlindedMessage); 320 bp->blinded_message->rc = 1; 321 bp->blinded_message->cipher = GNUNET_CRYPTO_BSA_RSA; 322 rp = &bp->blinded_message->details.rsa_blinded_message; 323 rp->blinded_msg_size = 1 + (size_t) GNUNET_CRYPTO_random_u64 ( 324 GNUNET_CRYPTO_QUALITY_WEAK, 325 (RSA_KEY_SIZE / 8) - 1); 326 rp->blinded_msg = GNUNET_malloc (rp->blinded_msg_size); 327 GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_WEAK, 328 rp->blinded_msg, 329 rp->blinded_msg_size); 330 TALER_denom_pub_hash (&new_dkp[cnt]->pub, 331 &ccoin->h_denom_pub); 332 ccoin->exchange_vals = alg_values; 333 TALER_coin_ev_hash (bp, 334 &ccoin->h_denom_pub, 335 &ccoin->coin_envelope_hash); 336 GNUNET_assert (GNUNET_OK == 337 TALER_denom_sign_blinded (&ccoin->coin_sig, 338 &new_dkp[cnt]->priv, 339 true, 340 bp)); 341 TALER_coin_ev_hash (bp, 342 &cbc.denom_pub_hash, 343 &cbc.h_coin_envelope); 344 GNUNET_assert ( 345 GNUNET_OK == 346 TALER_denom_sign_blinded ( 347 &cbc.sig, 348 &new_dkp[cnt]->priv, 349 false, 350 bp)); 351 } 352 } 353 354 perm = GNUNET_CRYPTO_random_permute (GNUNET_CRYPTO_QUALITY_NONCE, 355 NUM_ROWS); 356 FAILIF (GNUNET_OK != 357 plugin->start (plugin->cls, 358 "Transaction")); 359 for (unsigned int j = 0; j< NUM_ROWS; j++) 360 { 361 unsigned int i = perm[j]; 362 unsigned int k = (unsigned int) rand () % 5; 363 struct TALER_CoinPubHashP c_hash; 364 uint64_t known_coin_id; 365 struct TALER_EXCHANGEDB_CoinDepositInformation *cdi 366 = &depos[i]; 367 struct TALER_EXCHANGEDB_BatchDeposit bd = { 368 .cdis = cdi, 369 .num_cdis = 1, 370 .wallet_timestamp = ts, 371 .refund_deadline = deadline, 372 .wire_deadline = deadline, 373 .receiver_wire_account.full_payto 374 = (char *) "payto://iban/DE67830654080004822650?receiver-name=Test" 375 }; 376 377 if (i >= ROUNDS) 378 i = ROUNDS; /* throw-away slot, do not keep around */ 379 RND_BLK (&bd.merchant_pub); 380 RND_BLK (&bd.h_contract_terms); 381 RND_BLK (&bd.wire_salt); 382 TALER_merchant_wire_signature_hash ( 383 bd.receiver_wire_account, 384 &bd.wire_salt, 385 &h_wire_wt); 386 RND_BLK (&cdi->coin.coin_pub); 387 RND_BLK (&cdi->csig); 388 RND_BLK (&c_hash); 389 TALER_denom_pub_hash (&new_dkp[k]->pub, 390 &cdi->coin.denom_pub_hash); 391 GNUNET_assert (GNUNET_OK == 392 TALER_denom_sig_unblind (&cdi->coin.denom_sig, 393 &cbc.sig, 394 &bks, 395 &c_hash, 396 &alg_values, 397 &new_dkp[k]->pub)); 398 cdi->amount_with_fee = value; 399 400 { 401 struct TALER_DenominationHashP dph; 402 struct TALER_AgeCommitmentHashP agh; 403 404 FAILIF (TALER_EXCHANGEDB_CKS_ADDED != 405 plugin->ensure_coin_known (plugin->cls, 406 &cdi->coin, 407 &known_coin_id, 408 &dph, 409 &agh)); 410 } 411 { 412 struct GNUNET_TIME_Timestamp now; 413 struct TALER_Amount total; 414 bool balance_ok; 415 uint32_t bad_idx; 416 bool in_conflict; 417 418 now = GNUNET_TIME_timestamp_get (); 419 GNUNET_assert (1 == bd.num_cdis); 420 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 421 plugin->do_deposit (plugin->cls, 422 &bd, 423 &fees.deposit, 424 &now, 425 &total, 426 &balance_ok, 427 &bad_idx, 428 &in_conflict)); 429 } 430 { 431 bool not_found; 432 bool refund_ok; 433 bool gone; 434 bool conflict; 435 unsigned int refund_percent = 0; 436 switch (refund_percent) 437 { 438 case 2: // 100% refund 439 ref[i].coin = depos[i].coin; 440 ref[i].details.merchant_pub = bd.merchant_pub; 441 RND_BLK (&ref[i].details.merchant_sig); 442 ref[i].details.h_contract_terms = bd.h_contract_terms; 443 ref[i].coin.coin_pub = depos[i].coin.coin_pub; 444 ref[i].details.rtransaction_id = i; 445 ref[i].details.refund_amount = value; 446 ref[i].details.refund_fee = fees.refund; 447 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 448 plugin->do_refund (plugin->cls, 449 &ref[i], 450 &fees.deposit, 451 known_coin_id, 452 ¬_found, 453 &refund_ok, 454 &gone, 455 &conflict)); 456 break; 457 case 1:// 10% refund 458 if (count < (NUM_ROWS / 10)) 459 { 460 ref[i].coin = depos[i].coin; 461 ref[i].details.merchant_pub = bd.merchant_pub; 462 RND_BLK (&ref[i].details.merchant_sig); 463 ref[i].details.h_contract_terms = bd.h_contract_terms; 464 ref[i].coin.coin_pub = depos[i].coin.coin_pub; 465 ref[i].details.rtransaction_id = i; 466 ref[i].details.refund_amount = value; 467 ref[i].details.refund_fee = fees.refund; 468 } 469 else 470 { 471 ref[i].coin = depos[i].coin; 472 RND_BLK (&ref[i].details.merchant_pub); 473 RND_BLK (&ref[i].details.merchant_sig); 474 RND_BLK (&ref[i].details.h_contract_terms); 475 RND_BLK (&ref[i].coin.coin_pub); 476 ref[i].details.rtransaction_id = i; 477 ref[i].details.refund_amount = value; 478 ref[i].details.refund_fee = fees.refund; 479 } 480 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 481 plugin->do_refund (plugin->cls, 482 &ref[i], 483 &fees.deposit, 484 known_coin_id, 485 ¬_found, 486 &refund_ok, 487 &gone, 488 &conflict)); 489 count++; 490 break; 491 case 0:// no refund 492 ref[i].coin = depos[i].coin; 493 RND_BLK (&ref[i].details.merchant_pub); 494 RND_BLK (&ref[i].details.merchant_sig); 495 RND_BLK (&ref[i].details.h_contract_terms); 496 RND_BLK (&ref[i].coin.coin_pub); 497 ref[i].details.rtransaction_id = i; 498 ref[i].details.refund_amount = value; 499 ref[i].details.refund_fee = fees.refund; 500 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 501 plugin->do_refund (plugin->cls, 502 &ref[i], 503 &fees.deposit, 504 known_coin_id, 505 ¬_found, 506 &refund_ok, 507 &gone, 508 &conflict)); 509 break; 510 }/* END OF SWITCH CASE */ 511 } 512 if (ROUNDS == i) 513 TALER_denom_sig_free (&depos[i].coin.denom_sig); 514 } 515 /* End of benchmark setup */ 516 GNUNET_free (perm); 517 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 518 plugin->commit (plugin->cls)); 519 for (unsigned int r = 0; r < ROUNDS; r++) 520 { 521 struct GNUNET_TIME_Absolute time; 522 struct GNUNET_TIME_Relative duration; 523 524 time = GNUNET_TIME_absolute_get (); 525 FAILIF (0 > 526 plugin->select_refunds_by_coin (plugin->cls, 527 &ref[r].coin.coin_pub, 528 &ref[r].details.merchant_pub, 529 &ref[r].details.h_contract_terms, 530 &check_refund_cb, 531 &ref[r])); 532 duration = GNUNET_TIME_absolute_get_duration (time); 533 times = GNUNET_TIME_relative_add (times, 534 duration); 535 duration_sq = duration.rel_value_us * duration.rel_value_us; 536 GNUNET_assert (duration_sq / duration.rel_value_us == 537 duration.rel_value_us); 538 GNUNET_assert (sqrs + duration_sq >= sqrs); 539 sqrs += duration_sq; 540 } 541 /* evaluation of performance */ 542 { 543 struct GNUNET_TIME_Relative avg; 544 double avg_dbl; 545 double variance; 546 547 avg = GNUNET_TIME_relative_divide (times, 548 ROUNDS); 549 avg_dbl = avg.rel_value_us; 550 variance = sqrs - (avg_dbl * avg_dbl * ROUNDS); 551 fprintf (stdout, 552 "%8llu ± %6.0f\n", 553 (unsigned long long) avg.rel_value_us, 554 sqrt (variance / (ROUNDS - 1))); 555 } 556 result = 0; 557 drop: 558 GNUNET_break (GNUNET_OK == 559 plugin->drop_tables (plugin->cls)); 560 cleanup: 561 if (NULL != revealed_coins) 562 { 563 for (unsigned int cnt = 0; cnt < MELT_NEW_COINS; cnt++) 564 { 565 TALER_blinded_denom_sig_free (&revealed_coins[cnt].coin_sig); 566 TALER_blinded_planchet_free (&revealed_coins[cnt].blinded_planchet); 567 } 568 GNUNET_free (revealed_coins); 569 revealed_coins = NULL; 570 } 571 GNUNET_free (new_denom_pubs); 572 for (unsigned int cnt = 0; 573 (NULL != new_dkp) && (cnt < MELT_NEW_COINS) && (NULL != new_dkp[cnt]); 574 cnt++) 575 destroy_denom_key_pair (new_dkp[cnt]); 576 GNUNET_free (new_dkp); 577 for (unsigned int i = 0; i< ROUNDS + 1; i++) 578 { 579 TALER_denom_sig_free (&depos[i].coin.denom_sig); 580 } 581 GNUNET_free (depos); 582 GNUNET_free (ref); 583 TALER_EXCHANGEDB_plugin_unload (plugin); 584 plugin = NULL; 585 } 586 587 588 int 589 main (int argc, 590 char *const argv[]) 591 { 592 const char *plugin_name; 593 char *config_filename; 594 struct GNUNET_CONFIGURATION_Handle *cfg; 595 596 (void) argc; 597 result = -1; 598 if (NULL == (plugin_name = strrchr (argv[0], (int) '-'))) 599 { 600 GNUNET_break (0); 601 return -1; 602 } 603 GNUNET_log_setup (argv[0], 604 "WARNING", 605 NULL); 606 plugin_name++; 607 { 608 char *testname; 609 610 GNUNET_asprintf (&testname, 611 "test-exchange-db-%s", 612 plugin_name); 613 GNUNET_asprintf (&config_filename, 614 "%s.conf", 615 testname); 616 GNUNET_free (testname); 617 } 618 cfg = GNUNET_CONFIGURATION_create (TALER_EXCHANGE_project_data ()); 619 if (GNUNET_OK != 620 GNUNET_CONFIGURATION_parse (cfg, 621 config_filename)) 622 { 623 GNUNET_break (0); 624 GNUNET_free (config_filename); 625 return 2; 626 } 627 GNUNET_SCHEDULER_run (&run, 628 cfg); 629 GNUNET_CONFIGURATION_destroy (cfg); 630 GNUNET_free (config_filename); 631 return result; 632 } 633 634 635 /* end of perf_select_refunds_by_coin.c */