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