Line data Source code
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 8 : insert_one (struct TALER_EXCHANGEDB_PostgresContext *pg,
56 : const struct TALER_CoinPublicInfo *coin,
57 : struct TALER_EXCHANGEDB_CoinKnownResult *res)
58 : {
59 8 : const struct TALER_CoinPublicInfo *coins[1] = { coin };
60 :
61 8 : 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 1 : 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 1 : struct TALER_DenominationPublicKey denom_pub = { 0 };
81 1 : struct TALER_DenominationSignature denom_sig = { 0 };
82 : uint64_t known_coin_id;
83 :
84 1 : TDB_FILL (coin_pub,
85 : 1);
86 1 : TDB_FILL (h_denom_pub,
87 : 1);
88 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
89 : TALER_EXCHANGEDB_get_known_coin (pg,
90 : &coin_pub,
91 : &info));
92 1 : 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 1 : 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 1 : FAILIF (NULL != denom_pub.bsign_pub_key);
103 1 : FAILIF (NULL != denom_sig.unblinded_sig);
104 : /* a denomination that does not exist has no coins */
105 1 : FAILIF (0 != TALER_EXCHANGEDB_get_count_known_coins (pg,
106 : &h_denom_pub));
107 1 : 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 1 : check_unknown_denomination (struct TALER_EXCHANGEDB_PostgresContext *pg)
119 : {
120 : struct TALER_CoinPublicInfo coin;
121 : struct TALER_EXCHANGEDB_CoinKnownResult res;
122 :
123 1 : memset (&coin,
124 : 0,
125 : sizeof (coin));
126 1 : TDB_FILL (coin.coin_pub,
127 : 2);
128 1 : TDB_FILL (coin.denom_pub_hash,
129 : 2);
130 1 : coin.no_age_commitment = true;
131 1 : 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 1 : FAILIF_C (GNUNET_DB_STATUS_HARD_ERROR !=
136 : insert_one (pg,
137 : &coin,
138 : &res),
139 : TALER_denom_sig_free (&coin.denom_sig));
140 1 : TALER_denom_sig_free (&coin.denom_sig);
141 1 : FAILIF (0 != TDB_count (pg,
142 : "FROM known_coins"));
143 1 : 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 1 : 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 1 : struct TALER_DenominationPublicKey denom_pub = { 0 };
161 1 : struct TALER_DenominationSignature denom_sig = { 0 };
162 1 : uint64_t known_coin_id = 0;
163 1 : uint64_t id2 = 0;
164 :
165 1 : TDB_denom (pg,
166 : 10,
167 : "5",
168 : "0.1",
169 : &denom);
170 1 : TDB_coin (pg,
171 : &denom,
172 : 20,
173 : &coin,
174 : &known_coin_id);
175 1 : FAILIF_C (0 == known_coin_id,
176 : TDB_coin_free (&coin); TDB_denom_free (&denom));
177 :
178 1 : memset (&got,
179 : 0,
180 : sizeof (got));
181 1 : 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 1 : 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 1 : FAILIF_C (! got.no_age_commitment,
191 : TALER_denom_sig_free (&got.denom_sig);
192 : TDB_coin_free (&coin); TDB_denom_free (&denom));
193 1 : 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 1 : TALER_denom_sig_free (&got.denom_sig);
198 :
199 1 : 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 1 : FAILIF_C (id2 != known_coin_id,
206 : TDB_coin_free (&coin); TDB_denom_free (&denom));
207 1 : FAILIF_C (0 != GNUNET_memcmp (&h_denom_pub,
208 : &denom.h_denom_pub),
209 : TDB_coin_free (&coin); TDB_denom_free (&denom));
210 :
211 1 : 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 1 : 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 1 : 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 1 : TALER_denom_pub_free (&denom_pub);
228 1 : TALER_denom_sig_free (&denom_sig);
229 :
230 1 : 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 1 : TDB_coin_free (&coin);
234 1 : TDB_denom_free (&denom);
235 1 : 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 1 : 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 1 : TDB_denom (pg,
255 : 10,
256 : "5",
257 : "0.1",
258 : &denom);
259 1 : TDB_denom (pg,
260 : 11,
261 : "5",
262 : "0.1",
263 : &other);
264 1 : memset (&coin,
265 : 0,
266 : sizeof (coin));
267 1 : TDB_FILL (coin.coin_pub,
268 : 20);
269 1 : coin.denom_pub_hash = denom.h_denom_pub;
270 1 : coin.no_age_commitment = true;
271 1 : TDB_denom_sig (20,
272 : &coin.denom_sig);
273 :
274 : /* the coin from the previous check: already present, no conflict */
275 1 : 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 1 : 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 1 : 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 1 : 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 1 : coin.denom_pub_hash = other.h_denom_pub;
294 1 : 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 1 : 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 1 : 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 1 : coin.denom_pub_hash = denom.h_denom_pub;
311 1 : coin.no_age_commitment = false;
312 1 : TDB_FILL (coin.h_age_commitment,
313 : 21);
314 1 : 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 1 : 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 1 : TALER_denom_sig_free (&coin.denom_sig);
324 :
325 : /* the conflicting attempts did not add anything */
326 1 : 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 1 : 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 1 : TDB_denom_free (&denom);
333 1 : TDB_denom_free (&other);
334 1 : 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 1 : 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 1 : TDB_denom (pg,
354 : 10,
355 : "5",
356 : "0.1",
357 : &denom);
358 1 : memset (&coin,
359 : 0,
360 : sizeof (coin));
361 1 : TDB_FILL (coin.coin_pub,
362 : 30);
363 1 : coin.denom_pub_hash = denom.h_denom_pub;
364 1 : coin.no_age_commitment = false;
365 1 : TDB_FILL (coin.h_age_commitment,
366 : 31);
367 1 : TDB_denom_sig (30,
368 : &coin.denom_sig);
369 1 : 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 1 : FAILIF_C (TALER_EXCHANGEDB_CKS_ADDED != res.status,
376 : TALER_denom_sig_free (&coin.denom_sig);
377 : TDB_denom_free (&denom));
378 1 : FAILIF_C (0 == res.known_coin_id,
379 : TALER_denom_sig_free (&coin.denom_sig);
380 : TDB_denom_free (&denom));
381 1 : memset (&got,
382 : 0,
383 : sizeof (got));
384 1 : 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 1 : 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 1 : 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 1 : TALER_denom_sig_free (&got.denom_sig);
400 :
401 : /* a different age commitment for the same coin is a conflict... */
402 1 : TDB_FILL (coin.h_age_commitment,
403 : 32);
404 1 : 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 1 : 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 1 : FAILIF_C (res.no_age_commitment,
415 : TALER_denom_sig_free (&coin.denom_sig);
416 : TDB_denom_free (&denom));
417 1 : 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 1 : coin.no_age_commitment = true;
423 1 : 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 1 : 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 1 : TALER_denom_sig_free (&coin.denom_sig);
433 :
434 : /* two coins of this denomination by now */
435 1 : FAILIF_C (2 != TALER_EXCHANGEDB_get_count_known_coins (pg,
436 : &denom.h_denom_pub),
437 : TDB_denom_free (&denom));
438 1 : TDB_denom_free (&denom);
439 1 : 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 1 : 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 1 : int ret = 1;
466 :
467 1 : TDB_denom (pg,
468 : 10,
469 : "5",
470 : "0.1",
471 : &denom);
472 1 : TDB_denom (pg,
473 : 11,
474 : "5",
475 : "0.1",
476 : &other);
477 1 : memset (coins,
478 : 0,
479 : sizeof (coins));
480 6 : for (unsigned int i = 0; i < 5; i++)
481 : {
482 5 : coins[i].no_age_commitment = true;
483 5 : coins[i].denom_pub_hash = denom.h_denom_pub;
484 5 : pcoins[i] = &coins[i];
485 : }
486 : /* [0]: new, no age commitment */
487 1 : TDB_FILL (coins[0].coin_pub,
488 : 40);
489 1 : TDB_denom_sig (40,
490 : &coins[0].denom_sig);
491 : /* [1]: new, with age commitment */
492 1 : TDB_FILL (coins[1].coin_pub,
493 : 41);
494 1 : coins[1].no_age_commitment = false;
495 1 : TDB_FILL (coins[1].h_age_commitment,
496 : 410);
497 1 : TDB_denom_sig (41,
498 : &coins[1].denom_sig);
499 : /* [2]: known from check_insert_and_lookup (seed 20), same data */
500 1 : TDB_FILL (coins[2].coin_pub,
501 : 20);
502 1 : TDB_denom_sig (20,
503 : &coins[2].denom_sig);
504 : /* [3]: known (seed 20 again, different key below), other denomination */
505 1 : TDB_FILL (coins[3].coin_pub,
506 : 20);
507 1 : coins[3].denom_pub_hash = other.h_denom_pub;
508 1 : TDB_denom_sig (20,
509 : &coins[3].denom_sig);
510 : /* [4]: known from check_age_commitment (seed 30), wrong age commitment */
511 1 : TDB_FILL (coins[4].coin_pub,
512 : 30);
513 1 : coins[4].no_age_commitment = false;
514 1 : TDB_FILL (coins[4].h_age_commitment,
515 : 33);
516 1 : TDB_denom_sig (30,
517 : &coins[4].denom_sig);
518 :
519 1 : count_before = TDB_count (pg,
520 : "FROM known_coins");
521 :
522 : /* [2] and [3] share a key: refused, nothing inserted */
523 1 : 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 1 : 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 1 : struct TALER_CoinPublicInfo tmp = coins[3];
537 :
538 1 : TDB_FILL (tmp.coin_pub,
539 : 43);
540 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
541 : insert_one (pg,
542 : &tmp,
543 : &r1),
544 : goto cleanup);
545 1 : FAILIF_C (TALER_EXCHANGEDB_CKS_ADDED != r1.status,
546 : goto cleanup);
547 1 : count_before++;
548 1 : TDB_FILL (coins[3].coin_pub,
549 : 43);
550 1 : 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 1 : TDB_FILL (coins[0].denom_pub_hash,
558 : 44);
559 1 : FAILIF_C (GNUNET_OK !=
560 : TALER_EXCHANGEDB_start (pg,
561 : "test unknown denomination"),
562 : goto cleanup);
563 1 : 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 1 : TALER_EXCHANGEDB_rollback (pg);
570 1 : FAILIF_C (count_before != TDB_count (pg,
571 : "FROM known_coins"),
572 : goto cleanup);
573 1 : coins[0].denom_pub_hash = denom.h_denom_pub;
574 :
575 : /* now the mixed batch goes through, with one row per coin */
576 1 : 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 1 : FAILIF_C (TALER_EXCHANGEDB_CKS_ADDED != res[0].status,
583 : goto cleanup);
584 1 : FAILIF_C (0 == res[0].known_coin_id,
585 : goto cleanup);
586 1 : FAILIF_C (TALER_EXCHANGEDB_CKS_ADDED != res[1].status,
587 : goto cleanup);
588 1 : FAILIF_C (0 == res[1].known_coin_id,
589 : goto cleanup);
590 : /* for added coins, the result reflects what was stored */
591 1 : FAILIF_C (! res[0].no_age_commitment,
592 : goto cleanup);
593 1 : FAILIF_C (0 != GNUNET_memcmp (&res[0].h_denom_pub,
594 : &denom.h_denom_pub),
595 : goto cleanup);
596 1 : FAILIF_C (res[1].no_age_commitment,
597 : goto cleanup);
598 1 : FAILIF_C (0 != GNUNET_memcmp (&res[1].h_age_commitment,
599 : &coins[1].h_age_commitment),
600 : goto cleanup);
601 1 : FAILIF_C (res[0].known_coin_id == res[1].known_coin_id,
602 : goto cleanup);
603 1 : FAILIF_C (TALER_EXCHANGEDB_CKS_PRESENT != res[2].status,
604 : goto cleanup);
605 1 : 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 1 : FAILIF_C (id != res[2].known_coin_id,
612 : goto cleanup);
613 1 : FAILIF_C (TALER_EXCHANGEDB_CKS_DENOM_CONFLICT != res[3].status,
614 : goto cleanup);
615 1 : FAILIF_C (0 != GNUNET_memcmp (&res[3].h_denom_pub,
616 : &other.h_denom_pub),
617 : goto cleanup);
618 1 : FAILIF_C (TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS != res[4].status,
619 : goto cleanup);
620 1 : FAILIF_C (res[4].no_age_commitment,
621 : goto cleanup);
622 1 : 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 1 : FAILIF_C (count_before + 2 != TDB_count (pg,
627 : "FROM known_coins"),
628 : goto cleanup);
629 : {
630 : struct TALER_CoinPublicInfo got;
631 :
632 1 : memset (&got,
633 : 0,
634 : sizeof (got));
635 1 : 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 1 : FAILIF_C (got.no_age_commitment,
641 : TALER_denom_sig_free (&got.denom_sig); goto cleanup);
642 1 : 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 1 : 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 1 : 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 1 : 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 1 : FAILIF_C (TALER_EXCHANGEDB_CKS_PRESENT != res[0].status,
660 : goto cleanup);
661 1 : FAILIF_C (TALER_EXCHANGEDB_CKS_PRESENT != res[1].status,
662 : goto cleanup);
663 1 : FAILIF_C (TALER_EXCHANGEDB_CKS_PRESENT != res[2].status,
664 : goto cleanup);
665 1 : FAILIF_C (TALER_EXCHANGEDB_CKS_DENOM_CONFLICT != res[3].status,
666 : goto cleanup);
667 1 : FAILIF_C (TALER_EXCHANGEDB_CKS_AGE_CONFLICT_VALUE_DIFFERS != res[4].status,
668 : goto cleanup);
669 1 : FAILIF_C (count_before + 2 != TDB_count (pg,
670 : "FROM known_coins"),
671 : goto cleanup);
672 1 : ret = 0;
673 1 : cleanup:
674 6 : for (unsigned int i = 0; i < 5; i++)
675 5 : TALER_denom_sig_free (&coins[i].denom_sig);
676 1 : TDB_denom_free (&denom);
677 1 : TDB_denom_free (&other);
678 1 : 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 1 : 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 1 : int ret = 1;
699 :
700 1 : TDB_denom (pg,
701 : 10,
702 : "5",
703 : "0.1",
704 : &denom);
705 1 : memset (coins,
706 : 0,
707 : sizeof (coins));
708 3 : for (unsigned int i = 0; i < 2; i++)
709 : {
710 2 : coins[i].no_age_commitment = true;
711 2 : coins[i].denom_pub_hash = denom.h_denom_pub;
712 2 : TDB_FILL (coins[i].coin_pub,
713 : 50 + i);
714 2 : TDB_denom_sig (50 + i,
715 : &coins[i].denom_sig);
716 2 : pcoins[i] = &coins[i];
717 2 : dups[i] = &coins[0];
718 : }
719 : /* the batch goes through: first inserted, then found */
720 3 : for (unsigned int i = 0; i < 2; i++)
721 : {
722 2 : 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 2 : 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 1 : 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 1 : ret = 0;
741 1 : cleanup:
742 3 : for (unsigned int i = 0; i < 2; i++)
743 2 : TALER_denom_sig_free (&coins[i].denom_sig);
744 1 : TDB_denom_free (&denom);
745 1 : 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 1 : main (int argc,
773 : char *const *argv)
774 : {
775 1 : 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 */
|