test_coin_history.c (25248B)
1 /* 2 This file is part of TALER 3 Copyright (C) 2026 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/test_coin_history.c 18 * @brief tests for the exchangedb functions whose primary table is 19 * `coin_history` 20 * @author Christian Grothoff 21 * 22 * Covers #TALER_EXCHANGEDB_get_coin_transactions(). 23 * 24 * Nothing writes to `coin_history` directly: every table that can spend or 25 * credit a coin has an INSERT trigger that appends a row naming itself and 26 * its own serial. get_coin_transactions() walks those rows and looks each 27 * one up in the table it names. So the interesting part of this test is 28 * spending one coin in as many different ways as possible and then checking 29 * that every one of them comes back with the right type and amount. 30 */ 31 #include "test_common.h" 32 #include "exchange-database/do_purse_deposit.h" 33 #include "exchange-database/rollback.h" 34 #include "exchange-database/start.h" 35 #include "exchange-database/do_recoup.h" 36 #include "exchange-database/do_refresh.h" 37 #include "exchange-database/do_refund.h" 38 #include "exchange-database/get_coin_transactions.h" 39 #include "exchange-database/insert_reserve_open_deposit.h" 40 #include "exchange-database/update_to_refresh_revealed.h" 41 42 43 /** 44 * Account the checks fund their reserves from. 45 */ 46 static struct TDB_Account account; 47 48 49 /** 50 * Denomination the checks use. 51 */ 52 static struct TDB_Denom denom; 53 54 55 /** 56 * A different denomination used among the refresh outputs. 57 */ 58 static struct TDB_Denom fresh_denom; 59 60 61 /** 62 * The coin whose history we build up. 63 */ 64 static struct TALER_CoinPublicInfo coin; 65 66 67 /** 68 * Row of @e coin in `known_coins`. 69 */ 70 static uint64_t known_coin_id; 71 72 73 /** 74 * Reserve @e coin was withdrawn from. 75 */ 76 static struct TALER_ReservePublicKeyP reserve_pub; 77 78 79 /** 80 * Row of the withdraw that created @e coin. 81 */ 82 static uint64_t withdraw_id; 83 84 85 /** 86 * The deposit we later refund. 87 */ 88 static struct TDB_Deposit deposit; 89 90 91 /** 92 * ETag of the history right after the deposit was refunded. 93 */ 94 static uint64_t etag_after_refund; 95 96 97 /** 98 * Count the entries of @a tl and remember which types occurred. 99 * 100 * @param tl transaction list to walk 101 * @param[out] types set to the bitmask of the types seen 102 * @return number of entries in @a tl 103 */ 104 static unsigned int 105 summarize (const struct TALER_EXCHANGEDB_TransactionList *tl, 106 unsigned int *types) 107 { 108 unsigned int cnt = 0; 109 110 *types = 0; 111 for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl; 112 NULL != pos; 113 pos = pos->next) 114 { 115 cnt++; 116 *types |= 1U << pos->type; 117 } 118 return cnt; 119 } 120 121 122 /** 123 * Ask for the full history of @e coin. 124 * 125 * @param pg the database context 126 * @param start_off offset to start from 127 * @param etag_in ETag the caller already has 128 * @param[out] etag_out set to the current ETag 129 * @param[out] tlp set to the history 130 * @return transaction status 131 */ 132 static enum GNUNET_DB_QueryStatus 133 history (struct TALER_EXCHANGEDB_PostgresContext *pg, 134 uint64_t start_off, 135 uint64_t etag_in, 136 uint64_t *etag_out, 137 struct TALER_EXCHANGEDB_TransactionList **tlp) 138 { 139 struct TALER_Amount balance; 140 struct TALER_DenominationHashP h_denom_pub; 141 142 return TALER_EXCHANGEDB_get_coin_transactions (pg, 143 true, 144 &coin.coin_pub, 145 start_off, 146 etag_in, 147 etag_out, 148 &balance, 149 &h_denom_pub, 150 tlp); 151 } 152 153 154 /** 155 * A coin the exchange never saw has no history. 156 * 157 * @param pg the database context 158 * @return 0 on success 159 */ 160 static int 161 check_unknown_coin (struct TALER_EXCHANGEDB_PostgresContext *pg) 162 { 163 struct TALER_EXCHANGEDB_TransactionList *tl = (void *) 1; 164 struct TALER_CoinSpendPublicKeyP unknown; 165 struct TALER_Amount balance; 166 struct TALER_DenominationHashP h_denom_pub; 167 uint64_t etag = 42; 168 169 TDB_account (pg, 170 10, 171 &account); 172 TDB_denom (pg, 173 10, 174 "5", 175 "0.1", 176 &denom); 177 TDB_FILL (unknown, 178 99); 179 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 180 TALER_EXCHANGEDB_get_coin_transactions (pg, 181 true, 182 &unknown, 183 0, 184 0, 185 &etag, 186 &balance, 187 &h_denom_pub, 188 &tl)); 189 FAILIF (NULL != tl); 190 return 0; 191 } 192 193 194 /** 195 * A coin that was withdrawn but never spent has no history either: the 196 * `withdraw` table has no coin history trigger. 197 * 198 * @param pg the database context 199 * @return 0 on success 200 */ 201 static int 202 check_fresh_coin (struct TALER_EXCHANGEDB_PostgresContext *pg) 203 { 204 struct TALER_EXCHANGEDB_TransactionList *tl = (void *) 1; 205 uint64_t etag = 42; 206 207 TDB_reserve_in (pg, 208 &account, 209 11, 210 "10", 211 &reserve_pub); 212 withdraw_id = TDB_withdraw (pg, 213 &denom, 214 &reserve_pub, 215 11, 216 "5"); 217 TDB_coin (pg, 218 &denom, 219 20, 220 &coin, 221 &known_coin_id); 222 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 223 history (pg, 224 0, 225 0, 226 &etag, 227 &tl)); 228 FAILIF (NULL != tl); 229 FAILIF (0 != TDB_count (pg, 230 "FROM coin_history")); 231 return 0; 232 } 233 234 235 /** 236 * A deposit and its refund show up as two entries. 237 * 238 * @param pg the database context 239 * @return 0 on success 240 */ 241 static int 242 check_deposit_and_refund (struct TALER_EXCHANGEDB_PostgresContext *pg) 243 { 244 struct TALER_EXCHANGEDB_TransactionList *tl = NULL; 245 struct TALER_EXCHANGEDB_Refund refund; 246 struct TALER_Amount deposit_fee = TDB_amount ("0.1"); 247 struct TALER_Amount balance; 248 struct TALER_Amount expect = TDB_amount ("4.5"); 249 struct TALER_DenominationHashP h_denom_pub; 250 unsigned int types; 251 uint64_t etag = 0; 252 bool not_found; 253 bool refund_ok; 254 bool gone; 255 bool conflict; 256 257 TDB_deposit (pg, 258 &account, 259 &coin, 260 30, 261 "1", 262 "0.1", 263 GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS), 264 GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS), 265 &deposit); 266 FAILIF (1 != TDB_count (pg, 267 "FROM coin_history")); 268 269 memset (&refund, 270 0, 271 sizeof (refund)); 272 refund.coin = coin; 273 refund.details.merchant_pub = deposit.merchant_pub; 274 TDB_FILL (refund.details.merchant_sig, 275 31); 276 refund.details.h_contract_terms = deposit.h_contract_terms; 277 refund.details.rtransaction_id = 1; 278 refund.details.refund_amount = TDB_amount ("0.5"); 279 refund.details.refund_fee = TDB_amount ("0"); 280 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 281 TALER_EXCHANGEDB_do_refund (pg, 282 &refund, 283 &deposit_fee, 284 0, 285 ¬_found, 286 &refund_ok, 287 &gone, 288 &conflict)); 289 FAILIF (not_found); 290 FAILIF (! refund_ok); 291 FAILIF (2 != TDB_count (pg, 292 "FROM coin_history")); 293 294 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 295 TALER_EXCHANGEDB_get_coin_transactions (pg, 296 true, 297 &coin.coin_pub, 298 0, 299 0, 300 &etag, 301 &balance, 302 &h_denom_pub, 303 &tl)); 304 FAILIF_C (2 != summarize (tl, 305 &types), 306 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 307 FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_DEPOSIT)), 308 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 309 FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_REFUND)), 310 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 311 /* 5 - 1 + 0.5 */ 312 FAILIF_C (0 != TALER_amount_cmp (&balance, 313 &expect), 314 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 315 FAILIF_C (0 != GNUNET_memcmp (&h_denom_pub, 316 &denom.h_denom_pub), 317 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 318 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 319 FAILIF (0 == etag); 320 etag_after_refund = etag; 321 return 0; 322 } 323 324 325 /** 326 * A caller that is already up to date gets no list back, and the 327 * transaction the lookup opened is not left dangling. 328 * 329 * @param pg the database context 330 * @return 0 on success 331 */ 332 static int 333 check_etag (struct TALER_EXCHANGEDB_PostgresContext *pg) 334 { 335 struct TALER_EXCHANGEDB_TransactionList *tl = (void *) 1; 336 uint64_t etag = 0; 337 338 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 339 history (pg, 340 0, 341 etag_after_refund, 342 &etag, 343 &tl)); 344 FAILIF (NULL != tl); 345 FAILIF (etag != etag_after_refund); 346 /* the lookup must not leave a transaction open (it starts one of its 347 own when begin_transaction is true) */ 348 FAILIF (NULL != pg->transaction_name); 349 350 /* an ETag that is not the current one still returns the history */ 351 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 352 history (pg, 353 0, 354 etag_after_refund - 1, 355 &etag, 356 &tl)); 357 FAILIF (NULL == tl); 358 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 359 FAILIF (NULL != pg->transaction_name); 360 return 0; 361 } 362 363 364 /** 365 * The offset skips everything up to and including it. 366 * 367 * @param pg the database context 368 * @return 0 on success 369 */ 370 static int 371 check_offset (struct TALER_EXCHANGEDB_PostgresContext *pg) 372 { 373 struct TALER_EXCHANGEDB_TransactionList *tl = NULL; 374 unsigned int types; 375 uint64_t etag = 0; 376 377 /* everything after the deposit: only the refund is left */ 378 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 379 history (pg, 380 etag_after_refund - 1, 381 0, 382 &etag, 383 &tl)); 384 FAILIF_C (1 != summarize (tl, 385 &types), 386 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 387 FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_REFUND)), 388 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 389 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 390 391 /* everything after the last entry: nothing, but the ETag is still 392 reported */ 393 tl = (void *) 1; 394 etag = 0; 395 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 396 history (pg, 397 etag_after_refund, 398 0, 399 &etag, 400 &tl)); 401 FAILIF (NULL != tl); 402 FAILIF (etag != etag_after_refund); 403 return 0; 404 } 405 406 407 /** 408 * Spending the coin into a purse, on a reserve and in a melt adds one 409 * entry each. 410 * 411 * @param pg the database context 412 * @return 0 on success 413 */ 414 static int 415 check_other_spends (struct TALER_EXCHANGEDB_PostgresContext *pg) 416 { 417 struct TALER_EXCHANGEDB_TransactionList *tl = NULL; 418 struct TDB_Purse purse; 419 struct TALER_CoinSpendSignatureP coin_sig; 420 struct TALER_ReserveSignatureP reserve_sig; 421 struct TALER_Amount one = TDB_amount ("1"); 422 struct TALER_Amount zero = TDB_amount ("0"); 423 unsigned int types; 424 uint64_t etag = 0; 425 bool balance_ok; 426 bool too_late; 427 bool conflict; 428 bool insufficient_funds; 429 430 /* into a purse */ 431 TDB_purse (pg, 432 40, 433 "1", 434 GNUNET_TIME_relative_to_timestamp (GNUNET_TIME_UNIT_HOURS), 435 &purse); 436 TDB_FILL (coin_sig, 437 41); 438 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 439 TALER_EXCHANGEDB_do_purse_deposit (pg, 440 &purse.purse_pub, 441 &coin.coin_pub, 442 &one, 443 &coin_sig, 444 &one, 445 &balance_ok, 446 &too_late, 447 &conflict)); 448 FAILIF (! balance_ok); 449 FAILIF (conflict); 450 451 /* to keep a reserve open */ 452 TDB_FILL (coin_sig, 453 42); 454 TDB_FILL (reserve_sig, 455 42); 456 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 457 TALER_EXCHANGEDB_insert_reserve_open_deposit (pg, 458 &coin, 459 &coin_sig, 460 known_coin_id, 461 &one, 462 &reserve_sig, 463 &reserve_pub, 464 &insufficient_funds)); 465 FAILIF (insufficient_funds); 466 467 /* and into a melt */ 468 { 469 struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf; 470 struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get (); 471 struct TALER_Amount coin_balance; 472 bool found; 473 bool zombie_required = false; 474 bool nonce_reuse; 475 bool melt_balance_ok; 476 uint32_t noreveal_index; 477 enum GNUNET_DB_QueryStatus qs; 478 479 memset (&rf, 480 0, 481 sizeof (rf)); 482 rf.coin.coin_pub = coin.coin_pub; 483 rf.coin.denom_pub_hash = coin.denom_pub_hash; 484 rf.coin.no_age_commitment = coin.no_age_commitment; 485 TDB_FILL (rf.coin_sig, 486 43); 487 TDB_FILL (rf.rc, 488 43); 489 TDB_FILL (rf.refresh_seed, 490 43); 491 TDB_FILL (rf.planchets_h, 492 43); 493 TDB_FILL (rf.selected_h, 494 44); 495 rf.amount_with_fee = one; 496 TDB_denom (pg, 497 11, 498 "0.1", 499 "0.01", 500 &fresh_denom); 501 rf.num_coins = 3; 502 rf.denom_serials = GNUNET_new_array (rf.num_coins, 503 uint64_t); 504 /* Deliberately neither sorted nor distinct. */ 505 rf.denom_serials[0] = fresh_denom.serial; 506 rf.denom_serials[1] = denom.serial; 507 rf.denom_serials[2] = fresh_denom.serial; 508 rf.denom_sigs = GNUNET_new_array ( 509 rf.num_coins, 510 struct TALER_BlindedDenominationSignature); 511 for (unsigned int i = 0; i < rf.num_coins; i++) 512 TDB_blinded_denom_sig (43 + i, 513 &rf.denom_sigs[i]); 514 rf.noreveal_index = 1; 515 rf.is_v27_refresh = true; 516 rf.no_blinding_seed = true; 517 qs = TALER_EXCHANGEDB_do_refresh (pg, 518 &rf, 519 &now, 520 &found, 521 &noreveal_index, 522 &zombie_required, 523 &nonce_reuse, 524 &melt_balance_ok, 525 &coin_balance); 526 for (unsigned int i = 0; i < rf.num_coins; i++) 527 TALER_blinded_denom_sig_free (&rf.denom_sigs[i]); 528 GNUNET_free (rf.denom_sigs); 529 GNUNET_free (rf.denom_serials); 530 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs); 531 FAILIF (! melt_balance_ok); 532 533 /* The original output list is available before and after reveal. */ 534 for (unsigned int revealed = 0; revealed < 2; revealed++) 535 { 536 bool found_melt = false; 537 538 if (revealed) 539 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 540 TALER_EXCHANGEDB_update_to_refresh_revealed (pg, 541 &rf.rc)); 542 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 543 history (pg, 544 0, 545 0, 546 &etag, 547 &tl)); 548 for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl; 549 NULL != pos; 550 pos = pos->next) 551 { 552 const struct TALER_EXCHANGEDB_MeltListEntry *melt; 553 554 if (TALER_EXCHANGEDB_TT_MELT != pos->type) 555 continue; 556 found_melt = true; 557 melt = pos->details.melt; 558 FAILIF_C (3 != melt->num_coins, 559 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 560 FAILIF_C (0 != GNUNET_memcmp (&melt->denom_pub_hashes[0], 561 &fresh_denom.h_denom_pub), 562 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 563 FAILIF_C (0 != GNUNET_memcmp (&melt->denom_pub_hashes[1], 564 &denom.h_denom_pub), 565 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 566 FAILIF_C (0 != GNUNET_memcmp (&melt->denom_pub_hashes[2], 567 &fresh_denom.h_denom_pub), 568 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 569 } 570 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 571 tl = NULL; 572 FAILIF (! found_melt); 573 } 574 } 575 576 FAILIF (5 != TDB_count (pg, 577 "FROM coin_history")); 578 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 579 history (pg, 580 0, 581 0, 582 &etag, 583 &tl)); 584 FAILIF_C (5 != summarize (tl, 585 &types), 586 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 587 FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_PURSE_DEPOSIT)), 588 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 589 FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_RESERVE_OPEN)), 590 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 591 FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_MELT)), 592 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 593 594 /* the ETag is the highest coin history row of the list */ 595 { 596 uint64_t max = 0; 597 598 for (const struct TALER_EXCHANGEDB_TransactionList *pos = tl; 599 NULL != pos; 600 pos = pos->next) 601 max = GNUNET_MAX (max, 602 pos->coin_history_id); 603 FAILIF_C (etag != max, 604 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 605 } 606 607 /* the spends add up to what the coin no longer has */ 608 { 609 struct TALER_Amount total; 610 struct TALER_Amount expect = TDB_amount ("3.5"); 611 612 FAILIF_C (GNUNET_OK != 613 TALER_EXCHANGEDB_calculate_transaction_list_totals (tl, 614 &zero, 615 &total), 616 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 617 FAILIF_C (0 != TALER_amount_cmp (&total, 618 &expect), 619 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 620 } 621 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 622 return 0; 623 } 624 625 626 /** 627 * Recouping the coin adds the last entry and empties it. 628 * 629 * @param pg the database context 630 * @return 0 on success 631 */ 632 static int 633 check_recoup (struct TALER_EXCHANGEDB_PostgresContext *pg) 634 { 635 struct TALER_EXCHANGEDB_TransactionList *tl = NULL; 636 struct TALER_CoinSpendSignatureP coin_sig; 637 union GNUNET_CRYPTO_BlindingSecretP coin_bks; 638 struct GNUNET_TIME_Timestamp recoup_timestamp; 639 struct TALER_Amount balance; 640 struct TALER_Amount zero = TDB_amount ("0"); 641 struct TALER_DenominationHashP h_denom_pub; 642 unsigned int types; 643 uint64_t etag = 0; 644 bool recoup_ok; 645 bool internal_failure; 646 647 TDB_FILL (coin_sig, 648 50); 649 TDB_FILL (coin_bks, 650 50); 651 /* in/out: the caller picks the time, the callee only overwrites it if 652 the coin was recouped before */ 653 recoup_timestamp = GNUNET_TIME_timestamp_get (); 654 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 655 TALER_EXCHANGEDB_do_recoup (pg, 656 &reserve_pub, 657 withdraw_id, 658 &coin_bks, 659 &coin.coin_pub, 660 known_coin_id, 661 &coin_sig, 662 &recoup_timestamp, 663 &recoup_ok, 664 &internal_failure)); 665 FAILIF (internal_failure); 666 FAILIF (! recoup_ok); 667 FAILIF (6 != TDB_count (pg, 668 "FROM coin_history")); 669 670 FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 671 TALER_EXCHANGEDB_get_coin_transactions (pg, 672 true, 673 &coin.coin_pub, 674 0, 675 0, 676 &etag, 677 &balance, 678 &h_denom_pub, 679 &tl)); 680 FAILIF_C (6 != summarize (tl, 681 &types), 682 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 683 FAILIF_C (0 == (types & (1U << TALER_EXCHANGEDB_TT_RECOUP_WITHDRAW)), 684 TALER_EXCHANGEDB_free_coin_transaction_list (tl)); 685 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 686 FAILIF (0 != TALER_amount_cmp (&balance, 687 &zero)); 688 return 0; 689 } 690 691 692 /** 693 * With begin_transaction false the caller's transaction is used and left 694 * open. 695 * 696 * @param pg the database context 697 * @return 0 on success 698 */ 699 static int 700 check_in_transaction (struct TALER_EXCHANGEDB_PostgresContext *pg) 701 { 702 struct TALER_EXCHANGEDB_TransactionList *tl = NULL; 703 struct TALER_Amount balance; 704 struct TALER_DenominationHashP h_denom_pub; 705 unsigned int types; 706 uint64_t etag = 0; 707 708 FAILIF (GNUNET_OK != 709 TALER_EXCHANGEDB_start (pg, 710 "coin-history")); 711 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 712 TALER_EXCHANGEDB_get_coin_transactions (pg, 713 false, 714 &coin.coin_pub, 715 0, 716 0, 717 &etag, 718 &balance, 719 &h_denom_pub, 720 &tl), 721 TALER_EXCHANGEDB_rollback (pg)); 722 FAILIF_C (6 != summarize (tl, 723 &types), 724 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 725 TALER_EXCHANGEDB_rollback (pg)); 726 TALER_EXCHANGEDB_free_coin_transaction_list (tl); 727 FAILIF_C (NULL == pg->transaction_name, 728 TALER_EXCHANGEDB_rollback (pg)); 729 TALER_EXCHANGEDB_rollback (pg); 730 return 0; 731 } 732 733 734 /** 735 * The checks to run, in order. 736 */ 737 static const struct TDB_Test tests[] = { 738 { "coin-history-unknown-coin", 739 &check_unknown_coin }, 740 { "coin-history-fresh-coin", 741 &check_fresh_coin }, 742 { "coin-history-deposit-and-refund", 743 &check_deposit_and_refund }, 744 { "coin-history-etag", 745 &check_etag }, 746 { "coin-history-offset", 747 &check_offset }, 748 { "coin-history-other-spends", 749 &check_other_spends }, 750 { "coin-history-recoup", 751 &check_recoup }, 752 { "coin-history-in-transaction", 753 &check_in_transaction }, 754 { NULL, NULL } 755 }; 756 757 758 int 759 main (int argc, 760 char *const *argv) 761 { 762 int ret; 763 764 ret = TDB_main (argc, 765 argv, 766 "test-coin-history", 767 "Tests for the exchangedb `coin_history' table", 768 tests); 769 TDB_coin_free (&coin); 770 TDB_denom_free (&denom); 771 TDB_denom_free (&fresh_denom); 772 TDB_account_free (&account); 773 return ret; 774 } 775 776 777 /* end of test_coin_history.c */