test_known_coins.c (16150B)
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_coin(), 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_coin() 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 status codes are for. 33 */ 34 #include "test_common.h" 35 #include "exchange-database/do_insert_known_coin.h" 36 #include "exchange-database/get_known_coin.h" 37 #include "exchange-database/get_coin_denomination.h" 38 #include "exchange-database/get_signature_for_known_coin.h" 39 #include "exchange-database/get_count_known_coins.h" 40 41 42 /** 43 * Nothing is known about a coin that was never inserted. 44 * 45 * @param pg the database context 46 * @return 0 on success 47 */ 48 static int 49 check_empty (struct TALER_EXCHANGEDB_PostgresContext *pg) 50 { 51 struct TALER_CoinSpendPublicKeyP coin_pub; 52 struct TALER_CoinPublicInfo info; 53 struct TALER_DenominationHashP h_denom_pub; 54 struct TALER_DenominationPublicKey denom_pub = { 0 }; 55 struct TALER_DenominationSignature denom_sig = { 0 }; 56 uint64_t known_coin_id; 57 58 TDB_FILL (coin_pub, 59 1); 60 TDB_FILL (h_denom_pub, 61 1); 62 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 63 TALER_EXCHANGEDB_get_known_coin (pg, 64 &coin_pub, 65 &info)); 66 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 67 TALER_EXCHANGEDB_get_coin_denomination (pg, 68 &coin_pub, 69 &known_coin_id, 70 &h_denom_pub)); 71 FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != 72 TALER_EXCHANGEDB_get_signature_for_known_coin (pg, 73 &coin_pub, 74 &denom_pub, 75 &denom_sig)); 76 FAILIF (NULL != denom_pub.bsign_pub_key); 77 FAILIF (NULL != denom_sig.unblinded_sig); 78 /* a denomination that does not exist has no coins */ 79 FAILIF (0 != TALER_EXCHANGEDB_get_count_known_coins (pg, 80 &h_denom_pub)); 81 return 0; 82 } 83 84 85 /** 86 * A coin of an unknown denomination cannot be made known. 87 * 88 * @param pg the database context 89 * @return 0 on success 90 */ 91 static int 92 check_unknown_denomination (struct TALER_EXCHANGEDB_PostgresContext *pg) 93 { 94 struct TALER_CoinPublicInfo coin; 95 struct TALER_DenominationHashP dh; 96 struct TALER_AgeCommitmentHashP hac; 97 uint64_t known_coin_id; 98 99 memset (&coin, 100 0, 101 sizeof (coin)); 102 TDB_FILL (coin.coin_pub, 103 2); 104 TDB_FILL (coin.denom_pub_hash, 105 2); 106 coin.no_age_commitment = true; 107 TDB_denom_sig (2, 108 &coin.denom_sig); 109 /* the "dd" CTE finds no denomination, so nothing is inserted and the 110 UNION's second branch finds no coin either */ 111 FAILIF_C (TALER_EXCHANGEDB_CKS_HARD_FAIL != 112 TALER_EXCHANGEDB_do_insert_known_coin (pg, 113 &coin, 114 &known_coin_id, 115 &dh, 116 &hac), 117 TALER_denom_sig_free (&coin.denom_sig)); 118 TALER_denom_sig_free (&coin.denom_sig); 119 FAILIF (0 != TDB_count (pg, 120 "FROM known_coins")); 121 return 0; 122 } 123 124 125 /** 126 * Making a coin known stores it, and every lookup finds it. 127 * 128 * @param pg the database context 129 * @return 0 on success 130 */ 131 static int 132 check_insert_and_lookup (struct TALER_EXCHANGEDB_PostgresContext *pg) 133 { 134 struct TDB_Denom denom; 135 struct TALER_CoinPublicInfo coin; 136 struct TALER_CoinPublicInfo got; 137 struct TALER_DenominationHashP h_denom_pub; 138 struct TALER_DenominationPublicKey denom_pub = { 0 }; 139 struct TALER_DenominationSignature denom_sig = { 0 }; 140 uint64_t known_coin_id = 0; 141 uint64_t id2 = 0; 142 143 TDB_denom (pg, 144 10, 145 "5", 146 "0.1", 147 &denom); 148 TDB_coin (pg, 149 &denom, 150 20, 151 &coin, 152 &known_coin_id); 153 FAILIF_C (0 == known_coin_id, 154 TDB_coin_free (&coin); TDB_denom_free (&denom)); 155 156 memset (&got, 157 0, 158 sizeof (got)); 159 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 160 TALER_EXCHANGEDB_get_known_coin (pg, 161 &coin.coin_pub, 162 &got), 163 TDB_coin_free (&coin); TDB_denom_free (&denom)); 164 FAILIF_C (0 != GNUNET_memcmp (&got.denom_pub_hash, 165 &denom.h_denom_pub), 166 TALER_denom_sig_free (&got.denom_sig); 167 TDB_coin_free (&coin); TDB_denom_free (&denom)); 168 FAILIF_C (! got.no_age_commitment, 169 TALER_denom_sig_free (&got.denom_sig); 170 TDB_coin_free (&coin); TDB_denom_free (&denom)); 171 FAILIF_C (0 != TALER_denom_sig_cmp (&got.denom_sig, 172 &coin.denom_sig), 173 TALER_denom_sig_free (&got.denom_sig); 174 TDB_coin_free (&coin); TDB_denom_free (&denom)); 175 TALER_denom_sig_free (&got.denom_sig); 176 177 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 178 TALER_EXCHANGEDB_get_coin_denomination (pg, 179 &coin.coin_pub, 180 &id2, 181 &h_denom_pub), 182 TDB_coin_free (&coin); TDB_denom_free (&denom)); 183 FAILIF_C (id2 != known_coin_id, 184 TDB_coin_free (&coin); TDB_denom_free (&denom)); 185 FAILIF_C (0 != GNUNET_memcmp (&h_denom_pub, 186 &denom.h_denom_pub), 187 TDB_coin_free (&coin); TDB_denom_free (&denom)); 188 189 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 190 TALER_EXCHANGEDB_get_signature_for_known_coin (pg, 191 &coin.coin_pub, 192 &denom_pub, 193 &denom_sig), 194 TDB_coin_free (&coin); TDB_denom_free (&denom)); 195 FAILIF_C (0 != TALER_denom_pub_cmp (&denom_pub, 196 &denom.pub), 197 TALER_denom_pub_free (&denom_pub); 198 TALER_denom_sig_free (&denom_sig); 199 TDB_coin_free (&coin); TDB_denom_free (&denom)); 200 FAILIF_C (0 != TALER_denom_sig_cmp (&denom_sig, 201 &coin.denom_sig), 202 TALER_denom_pub_free (&denom_pub); 203 TALER_denom_sig_free (&denom_sig); 204 TDB_coin_free (&coin); TDB_denom_free (&denom)); 205 TALER_denom_pub_free (&denom_pub); 206 TALER_denom_sig_free (&denom_sig); 207 208 FAILIF_C (1 != TALER_EXCHANGEDB_get_count_known_coins (pg, 209 &denom.h_denom_pub), 210 TDB_coin_free (&coin); TDB_denom_free (&denom)); 211 TDB_coin_free (&coin); 212 TDB_denom_free (&denom); 213 return 0; 214 } 215 216 217 /** 218 * Re-inserting the same coin is a no-op; re-inserting it under a different 219 * denomination or with an age commitment is a conflict. 220 * 221 * @param pg the database context 222 * @return 0 on success 223 */ 224 static int 225 check_conflicts (struct TALER_EXCHANGEDB_PostgresContext *pg) 226 { 227 struct TDB_Denom denom; 228 struct TDB_Denom other; 229 struct TALER_CoinPublicInfo coin; 230 struct TALER_DenominationHashP dh; 231 struct TALER_AgeCommitmentHashP hac; 232 uint64_t known_coin_id = 0; 233 uint64_t id2 = 0; 234 235 TDB_denom (pg, 236 10, 237 "5", 238 "0.1", 239 &denom); 240 TDB_denom (pg, 241 11, 242 "5", 243 "0.1", 244 &other); 245 memset (&coin, 246 0, 247 sizeof (coin)); 248 TDB_FILL (coin.coin_pub, 249 20); 250 coin.denom_pub_hash = denom.h_denom_pub; 251 coin.no_age_commitment = true; 252 TDB_denom_sig (20, 253 &coin.denom_sig); 254 255 /* the coin from the previous check: already present, no conflict */ 256 FAILIF_C (TALER_EXCHANGEDB_CKS_PRESENT != 257 TALER_EXCHANGEDB_do_insert_known_coin (pg, 258 &coin, 259 &known_coin_id, 260 &dh, 261 &hac), 262 TALER_denom_sig_free (&coin.denom_sig); 263 TDB_denom_free (&denom); TDB_denom_free (&other)); 264 FAILIF_C (0 == known_coin_id, 265 TALER_denom_sig_free (&coin.denom_sig); 266 TDB_denom_free (&denom); TDB_denom_free (&other)); 267 268 /* same coin key, different denomination: conflict, and the stored 269 denomination is handed back so the caller can report it */ 270 coin.denom_pub_hash = other.h_denom_pub; 271 FAILIF_C (TALER_EXCHANGEDB_CKS_DENOM_CONFLICT != 272 TALER_EXCHANGEDB_do_insert_known_coin (pg, 273 &coin, 274 &id2, 275 &dh, 276 &hac), 277 TALER_denom_sig_free (&coin.denom_sig); 278 TDB_denom_free (&denom); TDB_denom_free (&other)); 279 FAILIF_C (0 != GNUNET_memcmp (&dh, 280 &denom.h_denom_pub), 281 TALER_denom_sig_free (&coin.denom_sig); 282 TDB_denom_free (&denom); TDB_denom_free (&other)); 283 284 /* same coin key and denomination, but now with an age commitment where 285 the stored row has none: the caller should have passed NULL */ 286 coin.denom_pub_hash = denom.h_denom_pub; 287 coin.no_age_commitment = false; 288 TDB_FILL (coin.h_age_commitment, 289 21); 290 FAILIF_C (TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NULL != 291 TALER_EXCHANGEDB_do_insert_known_coin (pg, 292 &coin, 293 &id2, 294 &dh, 295 &hac), 296 TALER_denom_sig_free (&coin.denom_sig); 297 TDB_denom_free (&denom); TDB_denom_free (&other)); 298 TALER_denom_sig_free (&coin.denom_sig); 299 300 /* the conflicting attempts did not add anything */ 301 FAILIF_C (1 != TALER_EXCHANGEDB_get_count_known_coins (pg, 302 &denom.h_denom_pub), 303 TDB_denom_free (&denom); TDB_denom_free (&other)); 304 FAILIF_C (0 != TALER_EXCHANGEDB_get_count_known_coins (pg, 305 &other.h_denom_pub), 306 TDB_denom_free (&denom); TDB_denom_free (&other)); 307 TDB_denom_free (&denom); 308 TDB_denom_free (&other); 309 return 0; 310 } 311 312 313 /** 314 * A coin with an age commitment round-trips, and the mirror-image 315 * conflict is reported. 316 * 317 * @param pg the database context 318 * @return 0 on success 319 */ 320 static int 321 check_age_commitment (struct TALER_EXCHANGEDB_PostgresContext *pg) 322 { 323 struct TDB_Denom denom; 324 struct TALER_CoinPublicInfo coin; 325 struct TALER_CoinPublicInfo got; 326 struct TALER_DenominationHashP dh; 327 struct TALER_AgeCommitmentHashP hac; 328 uint64_t known_coin_id = 0; 329 330 TDB_denom (pg, 331 10, 332 "5", 333 "0.1", 334 &denom); 335 memset (&coin, 336 0, 337 sizeof (coin)); 338 TDB_FILL (coin.coin_pub, 339 30); 340 coin.denom_pub_hash = denom.h_denom_pub; 341 coin.no_age_commitment = false; 342 TDB_FILL (coin.h_age_commitment, 343 31); 344 TDB_denom_sig (30, 345 &coin.denom_sig); 346 FAILIF_C (TALER_EXCHANGEDB_CKS_ADDED != 347 TALER_EXCHANGEDB_do_insert_known_coin (pg, 348 &coin, 349 &known_coin_id, 350 &dh, 351 &hac), 352 TALER_denom_sig_free (&coin.denom_sig); 353 TDB_denom_free (&denom)); 354 memset (&got, 355 0, 356 sizeof (got)); 357 FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != 358 TALER_EXCHANGEDB_get_known_coin (pg, 359 &coin.coin_pub, 360 &got), 361 TALER_denom_sig_free (&coin.denom_sig); 362 TDB_denom_free (&denom)); 363 FAILIF_C (got.no_age_commitment, 364 TALER_denom_sig_free (&got.denom_sig); 365 TALER_denom_sig_free (&coin.denom_sig); 366 TDB_denom_free (&denom)); 367 FAILIF_C (0 != GNUNET_memcmp (&got.h_age_commitment, 368 &coin.h_age_commitment), 369 TALER_denom_sig_free (&got.denom_sig); 370 TALER_denom_sig_free (&coin.denom_sig); 371 TDB_denom_free (&denom)); 372 TALER_denom_sig_free (&got.denom_sig); 373 374 /* a different age commitment for the same coin is a conflict... */ 375 TDB_FILL (coin.h_age_commitment, 376 32); 377 FAILIF_C (TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS != 378 TALER_EXCHANGEDB_do_insert_known_coin (pg, 379 &coin, 380 &known_coin_id, 381 &dh, 382 &hac), 383 TALER_denom_sig_free (&coin.denom_sig); 384 TDB_denom_free (&denom)); 385 /* ...and so is no age commitment at all, where one is on file */ 386 coin.no_age_commitment = true; 387 FAILIF_C (TALER_EXCHANGEDB_CKS_AGE_CONFLICT_EXPECTED_NON_NULL != 388 TALER_EXCHANGEDB_do_insert_known_coin (pg, 389 &coin, 390 &known_coin_id, 391 &dh, 392 &hac), 393 TALER_denom_sig_free (&coin.denom_sig); 394 TDB_denom_free (&denom)); 395 TALER_denom_sig_free (&coin.denom_sig); 396 397 /* two coins of this denomination by now */ 398 FAILIF_C (2 != TALER_EXCHANGEDB_get_count_known_coins (pg, 399 &denom.h_denom_pub), 400 TDB_denom_free (&denom)); 401 TDB_denom_free (&denom); 402 return 0; 403 } 404 405 406 /** 407 * The checks to run, in order. 408 */ 409 static const struct TDB_Test tests[] = { 410 { "known-coins-empty", 411 &check_empty }, 412 { "known-coins-unknown-denomination", 413 &check_unknown_denomination }, 414 { "known-coins-insert-and-lookup", 415 &check_insert_and_lookup }, 416 { "known-coins-conflicts", 417 &check_conflicts }, 418 { "known-coins-age-commitment", 419 &check_age_commitment }, 420 { NULL, NULL } 421 }; 422 423 424 int 425 main (int argc, 426 char *const *argv) 427 { 428 return TDB_main (argc, 429 argv, 430 "test-known-coins", 431 "Tests for the exchangedb `known_coins' table", 432 tests); 433 } 434 435 436 /* end of test_known_coins.c */