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