test_known_coins.c (28480B)
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_known_coins.c 18 * @brief tests for the exchangedb functions whose primary table is 19 * `known_coins` 20 * @author Christian Grothoff 21 * 22 * Covers #TALER_EXCHANGEDB_do_insert_known_coins(), 23 * #TALER_EXCHANGEDB_get_known_coin(), 24 * #TALER_EXCHANGEDB_get_coin_denomination(), 25 * #TALER_EXCHANGEDB_get_signature_for_known_coin() and 26 * #TALER_EXCHANGEDB_get_count_known_coins(). 27 * 28 * `known_coins` references `denominations`, so a denomination is created 29 * first with TDB_denom(). do_insert_known_coins() is the interesting one: 30 * it is idempotent, but only for a coin that comes back with the *same* 31 * denomination and age commitment -- the conflicting cases are what its 32 * negative per-coin status codes are for. It takes a whole batch of 33 * coins in one round trip, so the last check mixes all cases in one call 34 * and verifies that every coin is reported at its own position. 35 */ 36 #include "test_common.h" 37 #include "exchange-database/do_insert_known_coins.h" 38 #include "exchange-database/get_known_coin.h" 39 #include "exchange-database/get_coin_denomination.h" 40 #include "exchange-database/get_signature_for_known_coin.h" 41 #include "exchange-database/get_count_known_coins.h" 42 #include "exchange-database/start.h" 43 #include "exchange-database/rollback.h" 44 45 46 /** 47 * Make a single @a coin known via the batch function. 48 * 49 * @param pg the database context 50 * @param coin the coin to make known 51 * @param[out] res outcome for the coin 52 * @return database status, #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT on success 53 */ 54 static enum GNUNET_DB_QueryStatus 55 insert_one (struct TALER_EXCHANGEDB_PostgresContext *pg, 56 const struct TALER_CoinPublicInfo *coin, 57 struct TALER_EXCHANGEDB_CoinKnownResult *res) 58 { 59 const struct TALER_CoinPublicInfo *coins[1] = { coin }; 60 61 return TALER_EXCHANGEDB_do_insert_known_coins (pg, 62 1, 63 coins, 64 res); 65 } 66 67 68 /** 69 * Nothing is known about a coin that was never inserted. 70 * 71 * @param pg the database context 72 * @return 0 on success 73 */ 74 static int 75 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg) 76 { 77 struct TALER_CoinSpendPublicKeyP coin_pub; 78 struct TALER_CoinPublicInfo info; 79 struct TALER_DenominationHashP h_denom_pub; 80 struct TALER_DenominationPublicKey denom_pub = { 0 }; 81 struct TALER_DenominationSignature denom_sig = { 0 }; 82 uint64_t known_coin_id; 83 84 TDB_FILL (coin_pub, 85 1); 86 TDB_FILL (h_denom_pub, 87 1); 88 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 89 TALER_EXCHANGEDB_get_known_coin (pg, 90 &coin_pub, 91 &info)); 92 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 93 TALER_EXCHANGEDB_get_coin_denomination (pg, 94 &coin_pub, 95 &known_coin_id, 96 &h_denom_pub)); 97 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 98 TALER_EXCHANGEDB_get_signature_for_known_coin (pg, 99 &coin_pub, 100 &denom_pub, 101 &denom_sig)); 102 FAILIF (NULL != denom_pub.bsign_pub_key); 103 FAILIF (NULL != denom_sig.unblinded_sig); 104 /* a denomination that does not exist has no coins */ 105 FAILIF (0 != TALER_EXCHANGEDB_get_count_known_coins (pg, 106 &h_denom_pub)); 107 return 0; 108 } 109 110 111 /** 112 * A coin of an unknown denomination cannot be made known. 113 * 114 * @param pg the database context 115 * @return 0 on success 116 */ 117 static int 118 check_unknown_denomination (struct TALER_EXCHANGEDB_PostgresContext *pg) 119 { 120 struct TALER_CoinPublicInfo coin; 121 struct TALER_EXCHANGEDB_CoinKnownResult res; 122 123 memset (&coin, 124 0, 125 sizeof (coin)); 126 TDB_FILL (coin.coin_pub, 127 2); 128 TDB_FILL (coin.denom_pub_hash, 129 2); 130 coin.no_age_commitment = true; 131 TDB_denom_sig (2, 132 &coin.denom_sig); 133 /* no denomination row, so nothing is inserted and no coin is found 134 either: the row for the coin has no known_coin_id, a hard error */ 135 FAILIF_C (GNUNET_DB_STATUS_HARD_ERROR != 136 insert_one (pg, 137 &coin, 138 &res), 139 TALER_denom_sig_free (&coin.denom_sig)); 140 TALER_denom_sig_free (&coin.denom_sig); 141 FAILIF (0 != TDB_count (pg, 142 "FROM known_coins")); 143 return 0; 144 } 145 146 147 /** 148 * Making a coin known stores it, and every lookup finds it. 149 * 150 * @param pg the database context 151 * @return 0 on success 152 */ 153 static int 154 check_insert_and_lookup (struct TALER_EXCHANGEDB_PostgresContext *pg) 155 { 156 struct TDB_Denom denom; 157 struct TALER_CoinPublicInfo coin; 158 struct TALER_CoinPublicInfo got; 159 struct TALER_DenominationHashP h_denom_pub; 160 struct TALER_DenominationPublicKey denom_pub = { 0 }; 161 struct TALER_DenominationSignature denom_sig = { 0 }; 162 uint64_t known_coin_id = 0; 163 uint64_t id2 = 0; 164 165 TDB_denom (pg, 166 10, 167 "5", 168 "0.1", 169 &denom); 170 TDB_coin (pg, 171 &denom, 172 20, 173 &coin, 174 &known_coin_id); 175 FAILIF_C (0 == known_coin_id, 176 TDB_coin_free (&coin); TDB_denom_free (&denom)); 177 178 memset (&got, 179 0, 180 sizeof (got)); 181 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 182 TALER_EXCHANGEDB_get_known_coin (pg, 183 &coin.coin_pub, 184 &got), 185 TDB_coin_free (&coin); TDB_denom_free (&denom)); 186 FAILIF_C (0 != GNUNET_memcmp (&got.denom_pub_hash, 187 &denom.h_denom_pub), 188 TALER_denom_sig_free (&got.denom_sig); 189 TDB_coin_free (&coin); TDB_denom_free (&denom)); 190 FAILIF_C (! got.no_age_commitment, 191 TALER_denom_sig_free (&got.denom_sig); 192 TDB_coin_free (&coin); TDB_denom_free (&denom)); 193 FAILIF_C (0 != TALER_denom_sig_cmp (&got.denom_sig, 194 &coin.denom_sig), 195 TALER_denom_sig_free (&got.denom_sig); 196 TDB_coin_free (&coin); TDB_denom_free (&denom)); 197 TALER_denom_sig_free (&got.denom_sig); 198 199 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 200 TALER_EXCHANGEDB_get_coin_denomination (pg, 201 &coin.coin_pub, 202 &id2, 203 &h_denom_pub), 204 TDB_coin_free (&coin); TDB_denom_free (&denom)); 205 FAILIF_C (id2 != known_coin_id, 206 TDB_coin_free (&coin); TDB_denom_free (&denom)); 207 FAILIF_C (0 != GNUNET_memcmp (&h_denom_pub, 208 &denom.h_denom_pub), 209 TDB_coin_free (&coin); TDB_denom_free (&denom)); 210 211 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 212 TALER_EXCHANGEDB_get_signature_for_known_coin (pg, 213 &coin.coin_pub, 214 &denom_pub, 215 &denom_sig), 216 TDB_coin_free (&coin); TDB_denom_free (&denom)); 217 FAILIF_C (0 != TALER_denom_pub_cmp (&denom_pub, 218 &denom.pub), 219 TALER_denom_pub_free (&denom_pub); 220 TALER_denom_sig_free (&denom_sig); 221 TDB_coin_free (&coin); TDB_denom_free (&denom)); 222 FAILIF_C (0 != TALER_denom_sig_cmp (&denom_sig, 223 &coin.denom_sig), 224 TALER_denom_pub_free (&denom_pub); 225 TALER_denom_sig_free (&denom_sig); 226 TDB_coin_free (&coin); TDB_denom_free (&denom)); 227 TALER_denom_pub_free (&denom_pub); 228 TALER_denom_sig_free (&denom_sig); 229 230 FAILIF_C (1 != TALER_EXCHANGEDB_get_count_known_coins (pg, 231 &denom.h_denom_pub), 232 TDB_coin_free (&coin); TDB_denom_free (&denom)); 233 TDB_coin_free (&coin); 234 TDB_denom_free (&denom); 235 return 0; 236 } 237 238 239 /** 240 * Re-inserting the same coin is a no-op; re-inserting it under a different 241 * denomination or with an age commitment is a conflict. 242 * 243 * @param pg the database context 244 * @return 0 on success 245 */ 246 static int 247 check_conflicts (struct TALER_EXCHANGEDB_PostgresContext *pg) 248 { 249 struct TDB_Denom denom; 250 struct TDB_Denom other; 251 struct TALER_CoinPublicInfo coin; 252 struct TALER_EXCHANGEDB_CoinKnownResult res; 253 254 TDB_denom (pg, 255 10, 256 "5", 257 "0.1", 258 &denom); 259 TDB_denom (pg, 260 11, 261 "5", 262 "0.1", 263 &other); 264 memset (&coin, 265 0, 266 sizeof (coin)); 267 TDB_FILL (coin.coin_pub, 268 20); 269 coin.denom_pub_hash = denom.h_denom_pub; 270 coin.no_age_commitment = true; 271 TDB_denom_sig (20, 272 &coin.denom_sig); 273 274 /* the coin from the previous check: already present, no conflict */ 275 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 276 insert_one (pg, 277 &coin, 278 &res), 279 TALER_denom_sig_free (&coin.denom_sig); 280 TDB_denom_free (&denom); TDB_denom_free (&other)); 281 FAILIF_C (TALER_EXCHANGEDB_CKS_PRESENT != res.status, 282 TALER_denom_sig_free (&coin.denom_sig); 283 TDB_denom_free (&denom); TDB_denom_free (&other)); 284 FAILIF_C (0 == res.known_coin_id, 285 TALER_denom_sig_free (&coin.denom_sig); 286 TDB_denom_free (&denom); TDB_denom_free (&other)); 287 FAILIF_C (! res.no_age_commitment, 288 TALER_denom_sig_free (&coin.denom_sig); 289 TDB_denom_free (&denom); TDB_denom_free (&other)); 290 291 /* same coin key, different denomination: conflict, and the stored 292 denomination is handed back so the caller can report it */ 293 coin.denom_pub_hash = other.h_denom_pub; 294 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 295 insert_one (pg, 296 &coin, 297 &res), 298 TALER_denom_sig_free (&coin.denom_sig); 299 TDB_denom_free (&denom); TDB_denom_free (&other)); 300 FAILIF_C (TALER_EXCHANGEDB_CKS_DENOM_CONFLICT != res.status, 301 TALER_denom_sig_free (&coin.denom_sig); 302 TDB_denom_free (&denom); TDB_denom_free (&other)); 303 FAILIF_C (0 != GNUNET_memcmp (&res.h_denom_pub, 304 &denom.h_denom_pub), 305 TALER_denom_sig_free (&coin.denom_sig); 306 TDB_denom_free (&denom); TDB_denom_free (&other)); 307 308 /* same coin key and denomination, but now with an age commitment where 309 the stored row has none: the caller should have passed none */ 310 coin.denom_pub_hash = denom.h_denom_pub; 311 coin.no_age_commitment = false; 312 TDB_FILL (coin.h_age_commitment, 313 21); 314 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 315 insert_one (pg, 316 &coin, 317 &res), 318 TALER_denom_sig_free (&coin.denom_sig); 319 TDB_denom_free (&denom); TDB_denom_free (&other)); 320 FAILIF_C (TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NULL != res.status, 321 TALER_denom_sig_free (&coin.denom_sig); 322 TDB_denom_free (&denom); TDB_denom_free (&other)); 323 TALER_denom_sig_free (&coin.denom_sig); 324 325 /* the conflicting attempts did not add anything */ 326 FAILIF_C (1 != TALER_EXCHANGEDB_get_count_known_coins (pg, 327 &denom.h_denom_pub), 328 TDB_denom_free (&denom); TDB_denom_free (&other)); 329 FAILIF_C (0 != TALER_EXCHANGEDB_get_count_known_coins (pg, 330 &other.h_denom_pub), 331 TDB_denom_free (&denom); TDB_denom_free (&other)); 332 TDB_denom_free (&denom); 333 TDB_denom_free (&other); 334 return 0; 335 } 336 337 338 /** 339 * A coin with an age commitment round-trips, and the mirror-image 340 * conflict is reported. 341 * 342 * @param pg the database context 343 * @return 0 on success 344 */ 345 static int 346 check_age_commitment (struct TALER_EXCHANGEDB_PostgresContext *pg) 347 { 348 struct TDB_Denom denom; 349 struct TALER_CoinPublicInfo coin; 350 struct TALER_CoinPublicInfo got; 351 struct TALER_EXCHANGEDB_CoinKnownResult res; 352 353 TDB_denom (pg, 354 10, 355 "5", 356 "0.1", 357 &denom); 358 memset (&coin, 359 0, 360 sizeof (coin)); 361 TDB_FILL (coin.coin_pub, 362 30); 363 coin.denom_pub_hash = denom.h_denom_pub; 364 coin.no_age_commitment = false; 365 TDB_FILL (coin.h_age_commitment, 366 31); 367 TDB_denom_sig (30, 368 &coin.denom_sig); 369 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 370 insert_one (pg, 371 &coin, 372 &res), 373 TALER_denom_sig_free (&coin.denom_sig); 374 TDB_denom_free (&denom)); 375 FAILIF_C (TALER_EXCHANGEDB_CKS_ADDED != res.status, 376 TALER_denom_sig_free (&coin.denom_sig); 377 TDB_denom_free (&denom)); 378 FAILIF_C (0 == res.known_coin_id, 379 TALER_denom_sig_free (&coin.denom_sig); 380 TDB_denom_free (&denom)); 381 memset (&got, 382 0, 383 sizeof (got)); 384 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 385 TALER_EXCHANGEDB_get_known_coin (pg, 386 &coin.coin_pub, 387 &got), 388 TALER_denom_sig_free (&coin.denom_sig); 389 TDB_denom_free (&denom)); 390 FAILIF_C (got.no_age_commitment, 391 TALER_denom_sig_free (&got.denom_sig); 392 TALER_denom_sig_free (&coin.denom_sig); 393 TDB_denom_free (&denom)); 394 FAILIF_C (0 != GNUNET_memcmp (&got.h_age_commitment, 395 &coin.h_age_commitment), 396 TALER_denom_sig_free (&got.denom_sig); 397 TALER_denom_sig_free (&coin.denom_sig); 398 TDB_denom_free (&denom)); 399 TALER_denom_sig_free (&got.denom_sig); 400 401 /* a different age commitment for the same coin is a conflict... */ 402 TDB_FILL (coin.h_age_commitment, 403 32); 404 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 405 insert_one (pg, 406 &coin, 407 &res), 408 TALER_denom_sig_free (&coin.denom_sig); 409 TDB_denom_free (&denom)); 410 FAILIF_C (TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS != res.status, 411 TALER_denom_sig_free (&coin.denom_sig); 412 TDB_denom_free (&denom)); 413 /* the stored age commitment is handed back for the error report */ 414 FAILIF_C (res.no_age_commitment, 415 TALER_denom_sig_free (&coin.denom_sig); 416 TDB_denom_free (&denom)); 417 FAILIF_C (0 != GNUNET_memcmp (&res.h_age_commitment, 418 &got.h_age_commitment), 419 TALER_denom_sig_free (&coin.denom_sig); 420 TDB_denom_free (&denom)); 421 /* ...and so is no age commitment at all, where one is on file */ 422 coin.no_age_commitment = true; 423 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 424 insert_one (pg, 425 &coin, 426 &res), 427 TALER_denom_sig_free (&coin.denom_sig); 428 TDB_denom_free (&denom)); 429 FAILIF_C (TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NON_NULL != res.status, 430 TALER_denom_sig_free (&coin.denom_sig); 431 TDB_denom_free (&denom)); 432 TALER_denom_sig_free (&coin.denom_sig); 433 434 /* two coins of this denomination by now */ 435 FAILIF_C (2 != TALER_EXCHANGEDB_get_count_known_coins (pg, 436 &denom.h_denom_pub), 437 TDB_denom_free (&denom)); 438 TDB_denom_free (&denom); 439 return 0; 440 } 441 442 443 /** 444 * One call with a mixed batch: two new coins (one with an age 445 * commitment), one already known, one with a conflicting denomination 446 * and one with a conflicting age commitment. Every coin must be 447 * reported at its own position, the new ones must be inserted, and a 448 * batch with a repeated coin or an unknown denomination is refused 449 * without inserting anything. 450 * 451 * @param pg the database context 452 * @return 0 on success 453 */ 454 static int 455 check_batch (struct TALER_EXCHANGEDB_PostgresContext *pg) 456 { 457 struct TDB_Denom denom; 458 struct TDB_Denom other; 459 struct TALER_CoinPublicInfo coins[5]; 460 const struct TALER_CoinPublicInfo *pcoins[5]; 461 struct TALER_EXCHANGEDB_CoinKnownResult res[5]; 462 struct TALER_DenominationHashP dh; 463 uint64_t id; 464 uint64_t count_before; 465 int ret = 1; 466 467 TDB_denom (pg, 468 10, 469 "5", 470 "0.1", 471 &denom); 472 TDB_denom (pg, 473 11, 474 "5", 475 "0.1", 476 &other); 477 memset (coins, 478 0, 479 sizeof (coins)); 480 for (unsigned int i = 0; i < 5; i++) 481 { 482 coins[i].no_age_commitment = true; 483 coins[i].denom_pub_hash = denom.h_denom_pub; 484 pcoins[i] = &coins[i]; 485 } 486 /* [0]: new, no age commitment */ 487 TDB_FILL (coins[0].coin_pub, 488 40); 489 TDB_denom_sig (40, 490 &coins[0].denom_sig); 491 /* [1]: new, with age commitment */ 492 TDB_FILL (coins[1].coin_pub, 493 41); 494 coins[1].no_age_commitment = false; 495 TDB_FILL (coins[1].h_age_commitment, 496 410); 497 TDB_denom_sig (41, 498 &coins[1].denom_sig); 499 /* [2]: known from check_insert_and_lookup (seed 20), same data */ 500 TDB_FILL (coins[2].coin_pub, 501 20); 502 TDB_denom_sig (20, 503 &coins[2].denom_sig); 504 /* [3]: known (seed 20 again, different key below), other denomination */ 505 TDB_FILL (coins[3].coin_pub, 506 20); 507 coins[3].denom_pub_hash = other.h_denom_pub; 508 TDB_denom_sig (20, 509 &coins[3].denom_sig); 510 /* [4]: known from check_age_commitment (seed 30), wrong age commitment */ 511 TDB_FILL (coins[4].coin_pub, 512 30); 513 coins[4].no_age_commitment = false; 514 TDB_FILL (coins[4].h_age_commitment, 515 33); 516 TDB_denom_sig (30, 517 &coins[4].denom_sig); 518 519 count_before = TDB_count (pg, 520 "FROM known_coins"); 521 522 /* [2] and [3] share a key: refused, nothing inserted */ 523 FAILIF_C (GNUNET_DB_STATUS_HARD_ERROR != 524 TALER_EXCHANGEDB_do_insert_known_coins (pg, 525 5, 526 pcoins, 527 res), 528 goto cleanup); 529 FAILIF_C (count_before != TDB_count (pg, 530 "FROM known_coins"), 531 goto cleanup); 532 533 /* give [3] its own key: a coin known under the other denomination */ 534 { 535 struct TALER_EXCHANGEDB_CoinKnownResult r1; 536 struct TALER_CoinPublicInfo tmp = coins[3]; 537 538 TDB_FILL (tmp.coin_pub, 539 43); 540 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 541 insert_one (pg, 542 &tmp, 543 &r1), 544 goto cleanup); 545 FAILIF_C (TALER_EXCHANGEDB_CKS_ADDED != r1.status, 546 goto cleanup); 547 count_before++; 548 TDB_FILL (coins[3].coin_pub, 549 43); 550 coins[3].denom_pub_hash = denom.h_denom_pub; 551 } 552 553 /* a new coin of an unknown denomination anywhere in the batch: the 554 batch is refused. The INSERT itself is one statement, so this is 555 done inside a transaction as the callers do it, and the rollback 556 removes the other new coin again. */ 557 TDB_FILL (coins[0].denom_pub_hash, 558 44); 559 FAILIF_C (GNUNET_OK != 560 TALER_EXCHANGEDB_start (pg, 561 "test unknown denomination"), 562 goto cleanup); 563 FAILIF_C (GNUNET_DB_STATUS_HARD_ERROR != 564 TALER_EXCHANGEDB_do_insert_known_coins (pg, 565 5, 566 pcoins, 567 res), 568 TALER_EXCHANGEDB_rollback (pg); goto cleanup); 569 TALER_EXCHANGEDB_rollback (pg); 570 FAILIF_C (count_before != TDB_count (pg, 571 "FROM known_coins"), 572 goto cleanup); 573 coins[0].denom_pub_hash = denom.h_denom_pub; 574 575 /* now the mixed batch goes through, with one row per coin */ 576 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 577 TALER_EXCHANGEDB_do_insert_known_coins (pg, 578 5, 579 pcoins, 580 res), 581 goto cleanup); 582 FAILIF_C (TALER_EXCHANGEDB_CKS_ADDED != res[0].status, 583 goto cleanup); 584 FAILIF_C (0 == res[0].known_coin_id, 585 goto cleanup); 586 FAILIF_C (TALER_EXCHANGEDB_CKS_ADDED != res[1].status, 587 goto cleanup); 588 FAILIF_C (0 == res[1].known_coin_id, 589 goto cleanup); 590 /* for added coins, the result reflects what was stored */ 591 FAILIF_C (! res[0].no_age_commitment, 592 goto cleanup); 593 FAILIF_C (0 != GNUNET_memcmp (&res[0].h_denom_pub, 594 &denom.h_denom_pub), 595 goto cleanup); 596 FAILIF_C (res[1].no_age_commitment, 597 goto cleanup); 598 FAILIF_C (0 != GNUNET_memcmp (&res[1].h_age_commitment, 599 &coins[1].h_age_commitment), 600 goto cleanup); 601 FAILIF_C (res[0].known_coin_id == res[1].known_coin_id, 602 goto cleanup); 603 FAILIF_C (TALER_EXCHANGEDB_CKS_PRESENT != res[2].status, 604 goto cleanup); 605 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 606 TALER_EXCHANGEDB_get_coin_denomination (pg, 607 &coins[2].coin_pub, 608 &id, 609 &dh), 610 goto cleanup); 611 FAILIF_C (id != res[2].known_coin_id, 612 goto cleanup); 613 FAILIF_C (TALER_EXCHANGEDB_CKS_DENOM_CONFLICT != res[3].status, 614 goto cleanup); 615 FAILIF_C (0 != GNUNET_memcmp (&res[3].h_denom_pub, 616 &other.h_denom_pub), 617 goto cleanup); 618 FAILIF_C (TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS != res[4].status, 619 goto cleanup); 620 FAILIF_C (res[4].no_age_commitment, 621 goto cleanup); 622 FAILIF_C (0 == GNUNET_memcmp (&res[4].h_age_commitment, 623 &coins[4].h_age_commitment), 624 goto cleanup); 625 /* exactly the two new coins were added */ 626 FAILIF_C (count_before + 2 != TDB_count (pg, 627 "FROM known_coins"), 628 goto cleanup); 629 { 630 struct TALER_CoinPublicInfo got; 631 632 memset (&got, 633 0, 634 sizeof (got)); 635 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 636 TALER_EXCHANGEDB_get_known_coin (pg, 637 &coins[1].coin_pub, 638 &got), 639 goto cleanup); 640 FAILIF_C (got.no_age_commitment, 641 TALER_denom_sig_free (&got.denom_sig); goto cleanup); 642 FAILIF_C (0 != GNUNET_memcmp (&got.h_age_commitment, 643 &coins[1].h_age_commitment), 644 TALER_denom_sig_free (&got.denom_sig); goto cleanup); 645 FAILIF_C (0 != TALER_denom_sig_cmp (&got.denom_sig, 646 &coins[1].denom_sig), 647 TALER_denom_sig_free (&got.denom_sig); goto cleanup); 648 TALER_denom_sig_free (&got.denom_sig); 649 } 650 651 /* running the same batch again changes nothing: the new coins are 652 now merely present, the conflicts are reported as before */ 653 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 654 TALER_EXCHANGEDB_do_insert_known_coins (pg, 655 5, 656 pcoins, 657 res), 658 goto cleanup); 659 FAILIF_C (TALER_EXCHANGEDB_CKS_PRESENT != res[0].status, 660 goto cleanup); 661 FAILIF_C (TALER_EXCHANGEDB_CKS_PRESENT != res[1].status, 662 goto cleanup); 663 FAILIF_C (TALER_EXCHANGEDB_CKS_PRESENT != res[2].status, 664 goto cleanup); 665 FAILIF_C (TALER_EXCHANGEDB_CKS_DENOM_CONFLICT != res[3].status, 666 goto cleanup); 667 FAILIF_C (TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS != res[4].status, 668 goto cleanup); 669 FAILIF_C (count_before + 2 != TDB_count (pg, 670 "FROM known_coins"), 671 goto cleanup); 672 ret = 0; 673 cleanup: 674 for (unsigned int i = 0; i < 5; i++) 675 TALER_denom_sig_free (&coins[i].denom_sig); 676 TDB_denom_free (&denom); 677 TDB_denom_free (&other); 678 return ret; 679 } 680 681 682 /** 683 * Both ways out of do_insert_known_coins() release the closures its 684 * query parameters allocate. Nothing to assert here: the leak shows up 685 * when the test runs under valgrind (meson test --setup=valgrind). 686 * 687 * @param pg the database context 688 * @return 0 on success 689 */ 690 static int 691 check_no_leak (struct TALER_EXCHANGEDB_PostgresContext *pg) 692 { 693 struct TDB_Denom denom; 694 struct TALER_CoinPublicInfo coins[2]; 695 const struct TALER_CoinPublicInfo *pcoins[2]; 696 const struct TALER_CoinPublicInfo *dups[2]; 697 struct TALER_EXCHANGEDB_CoinKnownResult res[2]; 698 int ret = 1; 699 700 TDB_denom (pg, 701 10, 702 "5", 703 "0.1", 704 &denom); 705 memset (coins, 706 0, 707 sizeof (coins)); 708 for (unsigned int i = 0; i < 2; i++) 709 { 710 coins[i].no_age_commitment = true; 711 coins[i].denom_pub_hash = denom.h_denom_pub; 712 TDB_FILL (coins[i].coin_pub, 713 50 + i); 714 TDB_denom_sig (50 + i, 715 &coins[i].denom_sig); 716 pcoins[i] = &coins[i]; 717 dups[i] = &coins[0]; 718 } 719 /* the batch goes through: first inserted, then found */ 720 for (unsigned int i = 0; i < 2; i++) 721 { 722 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 723 TALER_EXCHANGEDB_do_insert_known_coins (pg, 724 2, 725 pcoins, 726 res), 727 goto cleanup); 728 FAILIF_C ( (i == 0) 729 ? (TALER_EXCHANGEDB_CKS_ADDED != res[1].status) 730 : (TALER_EXCHANGEDB_CKS_PRESENT != res[1].status), 731 goto cleanup); 732 } 733 /* the batch is refused before the query for repeating a coin */ 734 FAILIF_C (GNUNET_DB_STATUS_HARD_ERROR != 735 TALER_EXCHANGEDB_do_insert_known_coins (pg, 736 2, 737 dups, 738 res), 739 goto cleanup); 740 ret = 0; 741 cleanup: 742 for (unsigned int i = 0; i < 2; i++) 743 TALER_denom_sig_free (&coins[i].denom_sig); 744 TDB_denom_free (&denom); 745 return ret; 746 } 747 748 749 /** 750 * The checks to run, in order. 751 */ 752 static const struct TDB_Test tests[] = { 753 { "known-coins-empty", 754 &check_empty }, 755 { "known-coins-unknown-denomination", 756 &check_unknown_denomination }, 757 { "known-coins-insert-and-lookup", 758 &check_insert_and_lookup }, 759 { "known-coins-conflicts", 760 &check_conflicts }, 761 { "known-coins-age-commitment", 762 &check_age_commitment }, 763 { "known-coins-batch", 764 &check_batch }, 765 { "known-coins-no-leak", 766 &check_no_leak }, 767 { NULL, NULL } 768 }; 769 770 771 int 772 main (int argc, 773 char *const *argv) 774 { 775 return TDB_main (argc, 776 argv, 777 "test-known-coins", 778 "Tests for the exchangedb `known_coins' table", 779 tests); 780 } 781 782 783 /* end of test_known_coins.c */