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_refresh.c
18 : * @brief tests for the exchangedb functions whose primary table is
19 : * `refresh`
20 : * @author Christian Grothoff
21 : *
22 : * Covers #TALER_EXCHANGEDB_do_refresh(), #TALER_EXCHANGEDB_get_refresh(),
23 : * #TALER_EXCHANGEDB_update_to_refresh_revealed(),
24 : * #TALER_EXCHANGEDB_iterate_refreshes_above_serial_id() and, as far as it
25 : * can be, #TALER_EXCHANGEDB_get_old_coin_by_h_blind().
26 : *
27 : * `refresh` references `known_coins`, which TDB_coin() creates. The
28 : * do_refresh() answers checked here are: unknown coin, insufficient coin
29 : * balance, the zombie requirement, blinding-seed reuse and an idempotent
30 : * replay.
31 : *
32 : * get_old_coin_by_h_blind() cannot be checked beyond "does not invent an
33 : * answer": its statement selects on a `h_blind_evs` column that the current
34 : * `refresh` table does not have, so it cannot succeed at all -- see EDBT-8
35 : * in bugs.txt.
36 : */
37 : #include "test_common.h"
38 : #include "exchange-database/do_refresh.h"
39 : #include "exchange-database/get_known_coin.h"
40 : #include "exchange-database/get_old_coin_by_h_blind.h"
41 : #include "exchange-database/get_refresh.h"
42 : #include "exchange-database/iterate_refreshes_above_serial_id.h"
43 : #include "exchange-database/update_to_refresh_revealed.h"
44 :
45 :
46 : /**
47 : * Denomination the checks melt.
48 : */
49 : static struct TDB_Denom denom;
50 :
51 :
52 : /**
53 : * Fill in a refresh (melt) request for one fresh coin.
54 : *
55 : * @param seed seed for the commitment, signatures and seeds
56 : * @param amount amount to melt, e.g. "1"
57 : * @param coin coin being melted
58 : * @param with_seed true to pass a blinding seed and a CS R value
59 : * @param[out] rf set to the request; release with free_refresh()
60 : */
61 : static void
62 6 : make_refresh (uint32_t seed,
63 : const char *amount,
64 : const struct TALER_CoinPublicInfo *coin,
65 : bool with_seed,
66 : struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *rf)
67 : {
68 6 : memset (rf,
69 : 0,
70 : sizeof (*rf));
71 6 : rf->coin.coin_pub = coin->coin_pub;
72 6 : rf->coin.denom_pub_hash = coin->denom_pub_hash;
73 6 : rf->coin.no_age_commitment = coin->no_age_commitment;
74 6 : TDB_fill (&rf->coin_sig,
75 : sizeof (rf->coin_sig),
76 : seed);
77 6 : TDB_fill (&rf->rc,
78 : sizeof (rf->rc),
79 : seed);
80 6 : TDB_fill (&rf->refresh_seed,
81 : sizeof (rf->refresh_seed),
82 : seed);
83 6 : TDB_fill (&rf->planchets_h,
84 : sizeof (rf->planchets_h),
85 : seed);
86 6 : TDB_fill (&rf->selected_h,
87 : sizeof (rf->selected_h),
88 : seed + 1);
89 6 : rf->amount_with_fee = TDB_amount (amount);
90 6 : rf->num_coins = 1;
91 6 : rf->denom_serials = GNUNET_new (uint64_t);
92 6 : rf->denom_serials[0] = denom.serial;
93 6 : rf->denom_sigs = GNUNET_new (struct TALER_BlindedDenominationSignature);
94 6 : TDB_blinded_denom_sig (seed,
95 : &rf->denom_sigs[0]);
96 6 : rf->noreveal_index = 1;
97 : /* v27 refresh: the client provides no transfer public keys */
98 6 : rf->is_v27_refresh = true;
99 6 : rf->no_blinding_seed = ! with_seed;
100 6 : if (with_seed)
101 : {
102 2 : TDB_fill (&rf->blinding_seed,
103 : sizeof (rf->blinding_seed),
104 : seed);
105 2 : rf->num_cs_r_values = 1;
106 2 : rf->cs_r_values = GNUNET_new (struct GNUNET_CRYPTO_CSPublicRPairP);
107 2 : TDB_fill (rf->cs_r_values,
108 : sizeof (*rf->cs_r_values),
109 : seed);
110 2 : rf->cs_r_choices = 0;
111 : }
112 6 : }
113 :
114 :
115 : /**
116 : * Release what make_refresh() allocated.
117 : *
118 : * @param[in,out] rf request to clean up
119 : */
120 : static void
121 8 : free_refresh (struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *rf)
122 : {
123 16 : for (size_t i = 0; i<rf->num_coins; i++)
124 8 : TALER_blinded_denom_sig_free (&rf->denom_sigs[i]);
125 8 : GNUNET_free (rf->denom_sigs);
126 8 : GNUNET_free (rf->denom_serials);
127 8 : GNUNET_free (rf->cs_r_values);
128 8 : GNUNET_free (rf->transfer_pubs);
129 8 : GNUNET_free (rf->denom_pub_hashes);
130 8 : }
131 :
132 :
133 : /**
134 : * Outcome flags of a refresh request.
135 : */
136 : struct RefreshStatus
137 : {
138 : /**
139 : * Was there already a row for this commitment?
140 : */
141 : bool found;
142 :
143 : /**
144 : * Did the melt have to be of a zombie coin?
145 : */
146 : bool zombie_required;
147 :
148 : /**
149 : * Was the blinding seed used before?
150 : */
151 : bool nonce_reuse;
152 :
153 : /**
154 : * Was the coin balance sufficient?
155 : */
156 : bool balance_ok;
157 :
158 : /**
159 : * Balance the coin had.
160 : */
161 : struct TALER_Amount coin_balance;
162 :
163 : /**
164 : * Index the exchange chose not to reveal.
165 : */
166 : uint32_t noreveal_index;
167 : };
168 :
169 :
170 : /**
171 : * Perform a refresh request.
172 : *
173 : * @param pg the database context
174 : * @param rf the request
175 : * @param zombie_required whether the coin must be a zombie
176 : * @param[out] st set to the outcome
177 : * @return transaction status
178 : */
179 : static enum GNUNET_DB_QueryStatus
180 7 : run_refresh (struct TALER_EXCHANGEDB_PostgresContext *pg,
181 : struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS *rf,
182 : bool zombie_required,
183 : struct RefreshStatus *st)
184 : {
185 7 : struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
186 :
187 7 : memset (st,
188 : 0,
189 : sizeof (*st));
190 7 : st->zombie_required = zombie_required;
191 7 : return TALER_EXCHANGEDB_do_refresh (pg,
192 : rf,
193 : &now,
194 : &st->found,
195 : &st->noreveal_index,
196 : &st->zombie_required,
197 : &st->nonce_reuse,
198 : &st->balance_ok,
199 : &st->coin_balance);
200 : }
201 :
202 :
203 : /**
204 : * Melting a coin that is not known does nothing.
205 : *
206 : * @param pg the database context
207 : * @return 0 on success
208 : */
209 : static int
210 1 : check_unknown_coin (struct TALER_EXCHANGEDB_PostgresContext *pg)
211 : {
212 : struct TALER_CoinPublicInfo coin;
213 : struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf;
214 : struct RefreshStatus st;
215 :
216 1 : TDB_denom (pg,
217 : 10,
218 : "5",
219 : "0.1",
220 : &denom);
221 1 : memset (&coin,
222 : 0,
223 : sizeof (coin));
224 1 : TDB_FILL (coin.coin_pub,
225 : 1);
226 1 : coin.denom_pub_hash = denom.h_denom_pub;
227 1 : coin.no_age_commitment = true;
228 1 : make_refresh (1,
229 : "1",
230 : &coin,
231 : false,
232 : &rf);
233 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
234 : run_refresh (pg,
235 : &rf,
236 : false,
237 : &st),
238 : free_refresh (&rf));
239 1 : free_refresh (&rf);
240 1 : FAILIF (0 != TDB_count (pg,
241 : "FROM refresh"));
242 1 : return 0;
243 : }
244 :
245 :
246 : /**
247 : * Melting more than the coin is worth does nothing.
248 : *
249 : * @param pg the database context
250 : * @return 0 on success
251 : */
252 : static int
253 1 : check_insufficient_balance (struct TALER_EXCHANGEDB_PostgresContext *pg)
254 : {
255 : struct TALER_CoinPublicInfo coin;
256 : struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf;
257 : struct RefreshStatus st;
258 1 : struct TALER_Amount expect = TDB_amount ("5");
259 :
260 1 : TDB_coin (pg,
261 : &denom,
262 : 20,
263 : &coin,
264 : NULL);
265 1 : make_refresh (2,
266 : "6",
267 : &coin,
268 : false,
269 : &rf);
270 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
271 : run_refresh (pg,
272 : &rf,
273 : false,
274 : &st),
275 : free_refresh (&rf); TDB_coin_free (&coin));
276 1 : free_refresh (&rf);
277 1 : FAILIF_C (st.balance_ok,
278 : TDB_coin_free (&coin));
279 1 : FAILIF_C (0 != TALER_amount_cmp (&st.coin_balance,
280 : &expect),
281 : TDB_coin_free (&coin));
282 : /* the row was written before the balance was checked, so the caller has
283 : to roll back -- here the check simply notes that it is there */
284 1 : FAILIF_C (1 != TDB_count (pg,
285 : "FROM refresh"),
286 : TDB_coin_free (&coin));
287 1 : FAILIF_C (GNUNET_OK !=
288 : TDB_exec (pg,
289 : "DELETE FROM refresh;"),
290 : TDB_coin_free (&coin));
291 1 : TDB_coin_free (&coin);
292 1 : return 0;
293 : }
294 :
295 :
296 : /**
297 : * A melt within the coin's balance is recorded and debits the coin.
298 : *
299 : * @param pg the database context
300 : * @return 0 on success
301 : */
302 : static int
303 1 : check_refresh (struct TALER_EXCHANGEDB_PostgresContext *pg)
304 : {
305 : struct TALER_CoinPublicInfo coin;
306 : struct TALER_CoinPublicInfo got_coin;
307 : struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf;
308 : struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS got;
309 : struct RefreshStatus st;
310 1 : struct TALER_Amount expect_amount = TDB_amount ("1");
311 :
312 1 : TDB_coin (pg,
313 : &denom,
314 : 20,
315 : &coin,
316 : NULL);
317 1 : make_refresh (3,
318 : "1",
319 : &coin,
320 : true,
321 : &rf);
322 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
323 : run_refresh (pg,
324 : &rf,
325 : false,
326 : &st),
327 : free_refresh (&rf); TDB_coin_free (&coin));
328 1 : FAILIF_C (! st.balance_ok,
329 : free_refresh (&rf); TDB_coin_free (&coin));
330 1 : FAILIF_C (st.found,
331 : free_refresh (&rf); TDB_coin_free (&coin));
332 1 : FAILIF_C (st.nonce_reuse,
333 : free_refresh (&rf); TDB_coin_free (&coin));
334 1 : FAILIF_C (1 != TDB_count (pg,
335 : "FROM refresh"),
336 : free_refresh (&rf); TDB_coin_free (&coin));
337 :
338 : /* the melt is on file with everything it was created with */
339 1 : memset (&got,
340 : 0,
341 : sizeof (got));
342 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
343 : TALER_EXCHANGEDB_get_refresh (pg,
344 : &rf.rc,
345 : &got),
346 : free_refresh (&rf); TDB_coin_free (&coin));
347 1 : FAILIF_C (0 != TALER_amount_cmp (&got.amount_with_fee,
348 : &expect_amount),
349 : free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin));
350 1 : FAILIF_C (0 != GNUNET_memcmp (&got.coin.coin_pub,
351 : &coin.coin_pub),
352 : free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin));
353 1 : FAILIF_C (0 != GNUNET_memcmp (&got.coin_sig,
354 : &rf.coin_sig),
355 : free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin));
356 1 : FAILIF_C (0 != GNUNET_memcmp (&got.refresh_seed,
357 : &rf.refresh_seed),
358 : free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin));
359 1 : FAILIF_C (1 != got.num_coins,
360 : free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin));
361 1 : FAILIF_C (denom.serial != got.denom_serials[0],
362 : free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin));
363 1 : FAILIF_C (rf.noreveal_index != got.noreveal_index,
364 : free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin));
365 1 : FAILIF_C (got.revealed,
366 : free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin));
367 1 : FAILIF_C (got.no_blinding_seed,
368 : free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin));
369 1 : FAILIF_C (! got.is_v27_refresh,
370 : free_refresh (&got); free_refresh (&rf); TDB_coin_free (&coin));
371 1 : free_refresh (&got);
372 :
373 : /* the coin was debited */
374 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
375 : TALER_EXCHANGEDB_get_known_coin (pg,
376 : &coin.coin_pub,
377 : &got_coin),
378 : free_refresh (&rf); TDB_coin_free (&coin));
379 1 : TALER_denom_sig_free (&got_coin.denom_sig);
380 : {
381 1 : char *hex = TDB_hex (&coin.coin_pub,
382 : sizeof (coin.coin_pub));
383 :
384 1 : FAILIF_C (1 != TDB_count (pg,
385 : "FROM known_coins"
386 : " WHERE coin_pub=decode('%s','hex')"
387 : " AND remaining=ROW(4,0)::taler_amount",
388 : hex),
389 : GNUNET_free (hex);
390 : free_refresh (&rf); TDB_coin_free (&coin));
391 1 : GNUNET_free (hex);
392 : }
393 :
394 : /* a replay is idempotent and reports the index we chose */
395 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
396 : run_refresh (pg,
397 : &rf,
398 : false,
399 : &st),
400 : free_refresh (&rf); TDB_coin_free (&coin));
401 1 : FAILIF_C (! st.found,
402 : free_refresh (&rf); TDB_coin_free (&coin));
403 1 : FAILIF_C (rf.noreveal_index != st.noreveal_index,
404 : free_refresh (&rf); TDB_coin_free (&coin));
405 1 : FAILIF_C (1 != TDB_count (pg,
406 : "FROM refresh"),
407 : free_refresh (&rf); TDB_coin_free (&coin));
408 1 : free_refresh (&rf);
409 1 : TDB_coin_free (&coin);
410 1 : return 0;
411 : }
412 :
413 :
414 : /**
415 : * Reusing a blinding seed is refused.
416 : *
417 : * @param pg the database context
418 : * @return 0 on success
419 : */
420 : static int
421 1 : check_nonce_reuse (struct TALER_EXCHANGEDB_PostgresContext *pg)
422 : {
423 : struct TALER_CoinPublicInfo coin;
424 : struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf;
425 : struct RefreshStatus st;
426 :
427 1 : TDB_coin (pg,
428 : &denom,
429 : 20,
430 : &coin,
431 : NULL);
432 : /* a fresh commitment, but the blinding seed of check_refresh() */
433 1 : make_refresh (4,
434 : "1",
435 : &coin,
436 : true,
437 : &rf);
438 1 : TDB_fill (&rf.blinding_seed,
439 : sizeof (rf.blinding_seed),
440 : 3);
441 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
442 : run_refresh (pg,
443 : &rf,
444 : false,
445 : &st),
446 : free_refresh (&rf); TDB_coin_free (&coin));
447 1 : FAILIF_C (! st.nonce_reuse,
448 : free_refresh (&rf); TDB_coin_free (&coin));
449 1 : FAILIF_C (st.found,
450 : free_refresh (&rf); TDB_coin_free (&coin));
451 1 : free_refresh (&rf);
452 1 : TDB_coin_free (&coin);
453 1 : FAILIF (1 != TDB_count (pg,
454 : "FROM refresh"));
455 1 : return 0;
456 : }
457 :
458 :
459 : /**
460 : * A melt that insists on a zombie coin is refused when the coin never was
461 : * one.
462 : *
463 : * @param pg the database context
464 : * @return 0 on success
465 : */
466 : static int
467 1 : check_zombie_required (struct TALER_EXCHANGEDB_PostgresContext *pg)
468 : {
469 : struct TALER_CoinPublicInfo coin;
470 : struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf;
471 : struct RefreshStatus st;
472 :
473 1 : TDB_coin (pg,
474 : &denom,
475 : 21,
476 : &coin,
477 : NULL);
478 1 : make_refresh (5,
479 : "1",
480 : &coin,
481 : false,
482 : &rf);
483 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
484 : run_refresh (pg,
485 : &rf,
486 : true,
487 : &st),
488 : free_refresh (&rf); TDB_coin_free (&coin));
489 1 : FAILIF_C (! st.zombie_required,
490 : free_refresh (&rf); TDB_coin_free (&coin));
491 1 : FAILIF_C (st.balance_ok,
492 : free_refresh (&rf); TDB_coin_free (&coin));
493 1 : free_refresh (&rf);
494 1 : TDB_coin_free (&coin);
495 : /* the row went in before the zombie check, so the caller must roll back */
496 1 : FAILIF (2 != TDB_count (pg,
497 : "FROM refresh"));
498 1 : FAILIF (GNUNET_OK !=
499 : TDB_exec (pg,
500 : "DELETE FROM refresh WHERE NOT revealed"
501 : " AND rc <> (SELECT rc FROM refresh"
502 : " ORDER BY refresh_id ASC LIMIT 1);"));
503 1 : return 0;
504 : }
505 :
506 :
507 : /**
508 : * A melt can be marked as revealed, and marking one that does not exist
509 : * does nothing.
510 : *
511 : * @param pg the database context
512 : * @return 0 on success
513 : */
514 : static int
515 1 : check_revealed (struct TALER_EXCHANGEDB_PostgresContext *pg)
516 : {
517 : struct TALER_RefreshCommitmentP rc;
518 : struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS got;
519 :
520 1 : TDB_FILL (rc,
521 : 99);
522 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
523 : TALER_EXCHANGEDB_update_to_refresh_revealed (pg,
524 : &rc));
525 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
526 : TALER_EXCHANGEDB_get_refresh (pg,
527 : &rc,
528 : &got));
529 :
530 1 : TDB_FILL (rc,
531 : 3);
532 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
533 : TALER_EXCHANGEDB_update_to_refresh_revealed (pg,
534 : &rc));
535 1 : memset (&got,
536 : 0,
537 : sizeof (got));
538 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
539 : TALER_EXCHANGEDB_get_refresh (pg,
540 : &rc,
541 : &got));
542 1 : FAILIF_C (! got.revealed,
543 : free_refresh (&got));
544 1 : free_refresh (&got);
545 : /* marking it again is still reported as a row touched */
546 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
547 : TALER_EXCHANGEDB_update_to_refresh_revealed (pg,
548 : &rc));
549 1 : return 0;
550 : }
551 :
552 :
553 : /**
554 : * Closure for #refreshes_cb().
555 : */
556 : struct RefreshesContext
557 : {
558 : /**
559 : * How many melts did the callback see?
560 : */
561 : unsigned int total;
562 :
563 : /**
564 : * Stop after this many melts; 0 for no limit.
565 : */
566 : unsigned int stop_after;
567 :
568 : /**
569 : * Row of the last melt.
570 : */
571 : uint64_t rowid;
572 :
573 : /**
574 : * Amount of the last melt.
575 : */
576 : struct TALER_Amount amount;
577 :
578 : /**
579 : * Number of new denominations of the last melt.
580 : */
581 : size_t num_nds;
582 : };
583 :
584 :
585 : /**
586 : * Callback for #TALER_EXCHANGEDB_iterate_refreshes_above_serial_id().
587 : *
588 : * @param cls a `struct RefreshesContext *`
589 : * @param rowid row of the melt
590 : * @param old_denom_pub denomination of the melted coin
591 : * @param coin_pub the melted coin
592 : * @param coin_sig signature authorising the melt
593 : * @param h_age_commitment age commitment of the coin, NULL if none
594 : * @param amount_with_fee how much was melted
595 : * @param num_nds length of @a new_denom_serials
596 : * @param new_denom_serials denominations of the fresh coins
597 : * @param rc commitment of the melt
598 : * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop
599 : */
600 : static enum GNUNET_GenericReturnValue
601 3 : refreshes_cb (void *cls,
602 : uint64_t rowid,
603 : const struct TALER_DenominationPublicKey *old_denom_pub,
604 : const struct TALER_CoinSpendPublicKeyP *coin_pub,
605 : const struct TALER_CoinSpendSignatureP *coin_sig,
606 : const struct TALER_AgeCommitmentHashP *h_age_commitment,
607 : const struct TALER_Amount *amount_with_fee,
608 : size_t num_nds,
609 : uint64_t new_denom_serials[static num_nds],
610 : const struct TALER_RefreshCommitmentP *rc)
611 3 : {
612 3 : struct RefreshesContext *ctx = cls;
613 :
614 : (void) old_denom_pub;
615 : (void) coin_pub;
616 : (void) coin_sig;
617 : (void) h_age_commitment;
618 : (void) new_denom_serials;
619 : (void) rc;
620 3 : ctx->total++;
621 3 : ctx->rowid = rowid;
622 3 : ctx->amount = *amount_with_fee;
623 3 : ctx->num_nds = num_nds;
624 3 : if ( (0 != ctx->stop_after) &&
625 1 : (ctx->total >= ctx->stop_after) )
626 1 : return GNUNET_SYSERR;
627 2 : return GNUNET_OK;
628 : }
629 :
630 :
631 : /**
632 : * The auditor's view walks the melts by serial and stops when asked to.
633 : *
634 : * @param pg the database context
635 : * @return 0 on success
636 : */
637 : static int
638 1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
639 : {
640 : struct TALER_CoinPublicInfo coin;
641 : struct TALER_EXCHANGEDB_Refresh_vDOLDPLUS rf;
642 : struct RefreshStatus st;
643 : struct RefreshesContext ctx;
644 : uint64_t rows;
645 :
646 : /* one more melt, so that "stop after the first" is distinguishable
647 : from "saw everything" */
648 1 : TDB_coin (pg,
649 : &denom,
650 : 42,
651 : &coin,
652 : NULL);
653 1 : make_refresh (42,
654 : "1",
655 : &coin,
656 : false,
657 : &rf);
658 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
659 : run_refresh (pg,
660 : &rf,
661 : false,
662 : &st),
663 : free_refresh (&rf); TDB_coin_free (&coin));
664 1 : free_refresh (&rf);
665 1 : TDB_coin_free (&coin);
666 1 : FAILIF (! st.balance_ok);
667 1 : rows = TDB_count (pg,
668 : "FROM refresh");
669 1 : FAILIF (2 != rows);
670 1 : memset (&ctx,
671 : 0,
672 : sizeof (ctx));
673 1 : FAILIF (0 >=
674 : TALER_EXCHANGEDB_iterate_refreshes_above_serial_id (
675 : pg,
676 : 0,
677 : &refreshes_cb,
678 : &ctx));
679 1 : FAILIF (rows != ctx.total);
680 : /* every melt here asked for exactly one fresh coin */
681 1 : FAILIF (1 != ctx.num_nds);
682 :
683 : /* everything above the last row is nothing */
684 1 : memset (&ctx,
685 : 0,
686 : sizeof (ctx));
687 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
688 : TALER_EXCHANGEDB_iterate_refreshes_above_serial_id (
689 : pg,
690 : UINT32_MAX,
691 : &refreshes_cb,
692 : &ctx));
693 1 : FAILIF (0 != ctx.total);
694 :
695 : /* a callback that says stop is not called again */
696 1 : memset (&ctx,
697 : 0,
698 : sizeof (ctx));
699 1 : ctx.stop_after = 1;
700 1 : FAILIF (0 >=
701 : TALER_EXCHANGEDB_iterate_refreshes_above_serial_id (
702 : pg,
703 : 0,
704 : &refreshes_cb,
705 : &ctx));
706 1 : FAILIF (1 != ctx.total);
707 1 : return 0;
708 : }
709 :
710 :
711 : /**
712 : * A blinded coin hash that was never seen must not be attributed to any
713 : * old coin.
714 : *
715 : * This is all get_old_coin_by_h_blind() can be checked for today: its
716 : * statement selects on a `h_blind_evs` column that `refresh` does not
717 : * have, so it cannot return a row at all (EDBT-8). The assertion below
718 : * holds both now and once that is repaired.
719 : *
720 : * @param pg the database context
721 : * @return 0 on success
722 : */
723 : static int
724 1 : check_old_coin_by_h_blind (struct TALER_EXCHANGEDB_PostgresContext *pg)
725 : {
726 : struct TALER_BlindedCoinHashP h_blind_ev;
727 : struct TALER_CoinSpendPublicKeyP old_coin_pub;
728 : uint64_t rrc_serial;
729 :
730 1 : TDB_FILL (h_blind_ev,
731 : 123);
732 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT ==
733 : TALER_EXCHANGEDB_get_old_coin_by_h_blind (pg,
734 : &h_blind_ev,
735 : &old_coin_pub,
736 : &rrc_serial));
737 1 : return 0;
738 : }
739 :
740 :
741 : /**
742 : * The checks to run, in order.
743 : */
744 : static const struct TDB_Test tests[] = {
745 : { "refresh-unknown-coin",
746 : &check_unknown_coin },
747 : { "refresh-insufficient-balance",
748 : &check_insufficient_balance },
749 : { "refresh-refresh",
750 : &check_refresh },
751 : { "refresh-nonce-reuse",
752 : &check_nonce_reuse },
753 : { "refresh-zombie-required",
754 : &check_zombie_required },
755 : { "refresh-revealed",
756 : &check_revealed },
757 : { "refresh-iterate",
758 : &check_iterate },
759 : { "refresh-old-coin-by-h-blind",
760 : &check_old_coin_by_h_blind },
761 : { NULL, NULL }
762 : };
763 :
764 :
765 : int
766 1 : main (int argc,
767 : char *const *argv)
768 : {
769 : int ret;
770 :
771 1 : ret = TDB_main (argc,
772 : argv,
773 : "test-refresh",
774 : "Tests for the exchangedb `refresh' table",
775 : tests);
776 1 : TDB_denom_free (&denom);
777 1 : return ret;
778 : }
779 :
780 :
781 : /* end of test_refresh.c */
|