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_withdraw.c
18 : * @brief tests for the exchangedb functions whose primary table is
19 : * `withdraw`
20 : * @author Christian Grothoff
21 : *
22 : * Covers #TALER_EXCHANGEDB_do_withdraw(),
23 : * #TALER_EXCHANGEDB_get_withdraw(),
24 : * #TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id() and
25 : * #TALER_EXCHANGEDB_iterate_withdraw_amounts_for_kyc_check().
26 : *
27 : * `withdraw` references `reserves` and `denominations`. do_withdraw() has
28 : * four distinct "did not do the work" answers -- unknown reserve,
29 : * insufficient balance, age requirement not met and blinding-seed reuse --
30 : * plus an idempotent replay, and each of them is checked.
31 : */
32 : #include "test_common.h"
33 : #include "exchange-database/do_withdraw.h"
34 : #include "exchange-database/get_withdraw.h"
35 : #include "exchange-database/get_reserve.h"
36 : #include "exchange-database/iterate_withdrawals_above_serial_id.h"
37 : #include "exchange-database/iterate_withdraw_amounts_for_kyc_check.h"
38 :
39 :
40 : /**
41 : * Account the checks fund their reserves from.
42 : */
43 : static struct TDB_Account account;
44 :
45 :
46 : /**
47 : * Denomination the checks withdraw.
48 : */
49 : static struct TDB_Denom denom;
50 :
51 :
52 : /**
53 : * Fill in a withdraw request for one coin of #denom.
54 : *
55 : * @param seed seed for the planchet hash, signature and blinding seed
56 : * @param amount amount to withdraw, e.g. "5"
57 : * @param reserve_pub reserve to withdraw from
58 : * @param with_seed true to pass a blinding seed
59 : * @param[out] wd set to the request; release with free_withdraw()
60 : */
61 : static void
62 5 : make_withdraw (uint32_t seed,
63 : const char *amount,
64 : const struct TALER_ReservePublicKeyP *reserve_pub,
65 : bool with_seed,
66 : struct TALER_EXCHANGEDB_Withdraw *wd)
67 : {
68 5 : memset (wd,
69 : 0,
70 : sizeof (*wd));
71 5 : wd->amount_with_fee = TDB_amount (amount);
72 5 : wd->age_proof_required = false;
73 5 : wd->reserve_pub = *reserve_pub;
74 5 : TDB_fill (&wd->planchets_h,
75 : sizeof (wd->planchets_h),
76 : seed);
77 5 : TDB_fill (&wd->reserve_sig,
78 : sizeof (wd->reserve_sig),
79 : seed);
80 5 : wd->num_coins = 1;
81 5 : wd->denom_serials = GNUNET_new (uint64_t);
82 5 : wd->denom_serials[0] = denom.serial;
83 5 : wd->denom_sigs = GNUNET_new (struct TALER_BlindedDenominationSignature);
84 5 : TDB_blinded_denom_sig (seed,
85 : &wd->denom_sigs[0]);
86 5 : wd->no_blinding_seed = ! with_seed;
87 5 : if (with_seed)
88 2 : TDB_fill (&wd->blinding_seed,
89 : sizeof (wd->blinding_seed),
90 : seed);
91 5 : }
92 :
93 :
94 : /**
95 : * Release what make_withdraw() allocated.
96 : *
97 : * @param[in,out] wd request to clean up
98 : */
99 : static void
100 6 : free_withdraw (struct TALER_EXCHANGEDB_Withdraw *wd)
101 : {
102 12 : for (size_t i = 0; i<wd->num_coins; i++)
103 6 : TALER_blinded_denom_sig_free (&wd->denom_sigs[i]);
104 6 : GNUNET_free (wd->denom_sigs);
105 6 : GNUNET_free (wd->denom_serials);
106 6 : }
107 :
108 :
109 : /**
110 : * Run a withdraw request.
111 : *
112 : * @param pg the database context
113 : * @param wd the request
114 : * @param[out] st set to the outcome flags
115 : * @return transaction status
116 : */
117 : struct WithdrawStatus
118 : {
119 : /**
120 : * Was the balance sufficient?
121 : */
122 : bool balance_ok;
123 :
124 : /**
125 : * Were the age requirements met?
126 : */
127 : bool age_ok;
128 :
129 : /**
130 : * Was this a replay?
131 : */
132 : bool idempotent;
133 :
134 : /**
135 : * Was the blinding seed used before?
136 : */
137 : bool nonce_reuse;
138 :
139 : /**
140 : * Balance the reserve had.
141 : */
142 : struct TALER_Amount reserve_balance;
143 :
144 : /**
145 : * Maximum age the reserve allows.
146 : */
147 : uint16_t allowed_maximum_age;
148 :
149 : /**
150 : * Birthday recorded for the reserve.
151 : */
152 : uint32_t reserve_birthday;
153 :
154 : /**
155 : * Index the exchange chose not to reveal.
156 : */
157 : uint16_t noreveal_index;
158 : };
159 :
160 :
161 : /**
162 : * Perform a withdraw request.
163 : *
164 : * @param pg the database context
165 : * @param wd the request
166 : * @param[out] st set to the outcome
167 : * @return transaction status
168 : */
169 : static enum GNUNET_DB_QueryStatus
170 6 : run_withdraw (struct TALER_EXCHANGEDB_PostgresContext *pg,
171 : const struct TALER_EXCHANGEDB_Withdraw *wd,
172 : struct WithdrawStatus *st)
173 : {
174 6 : struct GNUNET_TIME_Timestamp now = GNUNET_TIME_timestamp_get ();
175 :
176 6 : memset (st,
177 : 0,
178 : sizeof (*st));
179 6 : return TALER_EXCHANGEDB_do_withdraw (pg,
180 : wd,
181 : &now,
182 : &st->balance_ok,
183 : &st->reserve_balance,
184 : &st->age_ok,
185 : &st->allowed_maximum_age,
186 : &st->reserve_birthday,
187 : &st->idempotent,
188 : &st->noreveal_index,
189 : &st->nonce_reuse);
190 : }
191 :
192 :
193 : /**
194 : * Closure for #withdraw_cb().
195 : */
196 : struct WithdrawContext
197 : {
198 : /**
199 : * How many rows did the callback see?
200 : */
201 : unsigned int total;
202 :
203 : /**
204 : * Stop after this many rows; 0 for no limit.
205 : */
206 : unsigned int stop_after;
207 :
208 : /**
209 : * Planchet hash we are looking for, NULL to match nothing.
210 : */
211 : const struct TALER_HashBlindedPlanchetsP *planchets_h;
212 :
213 : /**
214 : * How many times did we see it?
215 : */
216 : unsigned int matched;
217 :
218 : /**
219 : * Amount reported for it.
220 : */
221 : struct TALER_Amount amount;
222 :
223 : /**
224 : * Denomination serials reported for it.
225 : */
226 : uint64_t denom_serial;
227 :
228 : /**
229 : * Number of denominations reported for it.
230 : */
231 : size_t num_denom_serials;
232 :
233 : /**
234 : * Was a blinding seed reported for it?
235 : */
236 : bool have_seed;
237 : };
238 :
239 :
240 : /**
241 : * Callback for #TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id().
242 : *
243 : * @param cls a `struct WithdrawContext *`
244 : * @param rowid row of the withdraw
245 : * @param num_denom_serials number of denominations withdrawn
246 : * @param denom_serials the denominations withdrawn
247 : * @param selected_h hash over the selected planchets
248 : * @param h_planchets hash over all planchets
249 : * @param blinding_seed blinding seed, NULL if none
250 : * @param age_proof_required was an age proof required?
251 : * @param max_age maximum age of the coins
252 : * @param noreveal_index index the exchange did not reveal
253 : * @param reserve_pub reserve that was drained
254 : * @param reserve_sig signature over the request
255 : * @param execution_date when the withdraw happened
256 : * @param amount_with_fee how much was withdrawn
257 : * @return #GNUNET_OK to continue, #GNUNET_SYSERR to stop
258 : */
259 : static enum GNUNET_GenericReturnValue
260 2 : withdraw_cb (void *cls,
261 : uint64_t rowid,
262 : size_t num_denom_serials,
263 : const uint64_t *denom_serials,
264 : const struct TALER_HashBlindedPlanchetsP *selected_h,
265 : const struct TALER_HashBlindedPlanchetsP *h_planchets,
266 : const struct TALER_BlindingMasterSeedP *blinding_seed,
267 : bool age_proof_required,
268 : uint8_t max_age,
269 : uint8_t noreveal_index,
270 : const struct TALER_ReservePublicKeyP *reserve_pub,
271 : const struct TALER_ReserveSignatureP *reserve_sig,
272 : struct GNUNET_TIME_Timestamp execution_date,
273 : const struct TALER_Amount *amount_with_fee)
274 : {
275 2 : struct WithdrawContext *ctx = cls;
276 :
277 : (void) rowid;
278 : (void) selected_h;
279 : (void) age_proof_required;
280 : (void) max_age;
281 : (void) noreveal_index;
282 : (void) reserve_pub;
283 : (void) reserve_sig;
284 : (void) execution_date;
285 2 : ctx->total++;
286 2 : if ( (NULL != ctx->planchets_h) &&
287 1 : (0 == GNUNET_memcmp (h_planchets,
288 : ctx->planchets_h)) )
289 : {
290 1 : ctx->matched++;
291 1 : ctx->amount = *amount_with_fee;
292 1 : ctx->num_denom_serials = num_denom_serials;
293 1 : if (0 < num_denom_serials)
294 1 : ctx->denom_serial = denom_serials[0];
295 1 : ctx->have_seed = (NULL != blinding_seed);
296 : }
297 2 : if ( (0 != ctx->stop_after) &&
298 1 : (ctx->total >= ctx->stop_after) )
299 1 : return GNUNET_SYSERR;
300 1 : return GNUNET_OK;
301 : }
302 :
303 :
304 : /**
305 : * Closure for #amount_cb().
306 : */
307 : struct AmountContext
308 : {
309 : /**
310 : * How many amounts did the callback see?
311 : */
312 : unsigned int total;
313 :
314 : /**
315 : * Sum of the whole-unit parts of the amounts seen.
316 : */
317 : uint64_t value_sum;
318 :
319 : /**
320 : * Return this from the callback.
321 : */
322 : enum GNUNET_GenericReturnValue ret;
323 : };
324 :
325 :
326 : /**
327 : * Callback for #TALER_EXCHANGEDB_iterate_withdraw_amounts_for_kyc_check().
328 : *
329 : * @param cls a `struct AmountContext *`
330 : * @param amount the withdrawn amount
331 : * @param date when it was withdrawn
332 : * @return what @e ret of the closure says
333 : */
334 : static enum GNUNET_GenericReturnValue
335 1 : amount_cb (void *cls,
336 : const struct TALER_Amount *amount,
337 : struct GNUNET_TIME_Absolute date)
338 : {
339 1 : struct AmountContext *ctx = cls;
340 :
341 : (void) date;
342 1 : ctx->total++;
343 1 : ctx->value_sum += amount->value;
344 1 : return ctx->ret;
345 : }
346 :
347 :
348 : /**
349 : * Withdrawing from a reserve that does not exist does nothing.
350 : *
351 : * @param pg the database context
352 : * @return 0 on success
353 : */
354 : static int
355 1 : check_unknown_reserve (struct TALER_EXCHANGEDB_PostgresContext *pg)
356 : {
357 : struct TALER_ReservePublicKeyP reserve_pub;
358 : struct TALER_EXCHANGEDB_Withdraw wd;
359 : struct WithdrawStatus st;
360 : struct TALER_HashBlindedPlanchetsP h;
361 :
362 1 : TDB_denom (pg,
363 : 10,
364 : "5",
365 : "0.1",
366 : &denom);
367 1 : TDB_FILL (reserve_pub,
368 : 1);
369 1 : make_withdraw (1,
370 : "5",
371 : &reserve_pub,
372 : false,
373 : &wd);
374 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
375 : run_withdraw (pg,
376 : &wd,
377 : &st),
378 : free_withdraw (&wd));
379 1 : free_withdraw (&wd);
380 1 : FAILIF (0 != TDB_count (pg,
381 : "FROM withdraw"));
382 1 : TDB_FILL (h,
383 : 1);
384 : {
385 : struct TALER_EXCHANGEDB_Withdraw got;
386 :
387 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
388 : TALER_EXCHANGEDB_get_withdraw (pg,
389 : &h,
390 : &got));
391 : }
392 1 : return 0;
393 : }
394 :
395 :
396 : /**
397 : * A reserve without enough money keeps it.
398 : *
399 : * @param pg the database context
400 : * @return 0 on success
401 : */
402 : static int
403 1 : check_insufficient_balance (struct TALER_EXCHANGEDB_PostgresContext *pg)
404 : {
405 : struct TALER_ReservePublicKeyP reserve_pub;
406 : struct TALER_EXCHANGEDB_Withdraw wd;
407 : struct WithdrawStatus st;
408 1 : struct TALER_Amount expect = TDB_amount ("1");
409 :
410 1 : TDB_account (pg,
411 : 10,
412 : &account);
413 1 : TDB_reserve_in (pg,
414 : &account,
415 : 10,
416 : "1",
417 : &reserve_pub);
418 1 : make_withdraw (2,
419 : "5",
420 : &reserve_pub,
421 : false,
422 : &wd);
423 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
424 : run_withdraw (pg,
425 : &wd,
426 : &st),
427 : free_withdraw (&wd));
428 1 : free_withdraw (&wd);
429 1 : FAILIF (st.balance_ok);
430 1 : FAILIF (! st.age_ok);
431 1 : FAILIF (st.idempotent);
432 1 : FAILIF (0 != TALER_amount_cmp (&st.reserve_balance,
433 : &expect));
434 1 : FAILIF (0 != TDB_count (pg,
435 : "FROM withdraw"));
436 1 : return 0;
437 : }
438 :
439 :
440 : /**
441 : * A funded reserve is drained and the withdraw is recorded.
442 : *
443 : * @param pg the database context
444 : * @return 0 on success
445 : */
446 : static int
447 1 : check_withdraw (struct TALER_EXCHANGEDB_PostgresContext *pg)
448 : {
449 : struct TALER_ReservePublicKeyP reserve_pub;
450 : struct TALER_EXCHANGEDB_Withdraw wd;
451 : struct TALER_EXCHANGEDB_Withdraw got;
452 : struct TALER_EXCHANGEDB_Reserve reserve;
453 : struct WithdrawStatus st;
454 1 : struct TALER_Amount expect_balance = TDB_amount ("5");
455 1 : struct TALER_Amount expect_left = TDB_amount ("5");
456 :
457 1 : TDB_reserve_in (pg,
458 : &account,
459 : 11,
460 : "10",
461 : &reserve_pub);
462 1 : make_withdraw (11,
463 : "5",
464 : &reserve_pub,
465 : true,
466 : &wd);
467 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
468 : run_withdraw (pg,
469 : &wd,
470 : &st),
471 : free_withdraw (&wd));
472 1 : FAILIF_C (! st.balance_ok,
473 : free_withdraw (&wd));
474 1 : FAILIF_C (! st.age_ok,
475 : free_withdraw (&wd));
476 1 : FAILIF_C (st.idempotent,
477 : free_withdraw (&wd));
478 1 : FAILIF_C (st.nonce_reuse,
479 : free_withdraw (&wd));
480 1 : FAILIF_C (1 != TDB_count (pg,
481 : "FROM withdraw"),
482 : free_withdraw (&wd));
483 :
484 : /* the reserve was debited */
485 1 : memset (&reserve,
486 : 0,
487 : sizeof (reserve));
488 1 : reserve.pub = reserve_pub;
489 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
490 : TALER_EXCHANGEDB_get_reserve (pg,
491 : &reserve),
492 : free_withdraw (&wd));
493 1 : FAILIF_C (0 != TALER_amount_cmp (&reserve.balance,
494 : &expect_left),
495 : free_withdraw (&wd));
496 :
497 : /* the request can be looked up by its planchet hash */
498 1 : memset (&got,
499 : 0,
500 : sizeof (got));
501 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
502 : TALER_EXCHANGEDB_get_withdraw (pg,
503 : &wd.planchets_h,
504 : &got),
505 : free_withdraw (&wd));
506 1 : FAILIF_C (0 != TALER_amount_cmp (&got.amount_with_fee,
507 : &expect_balance),
508 : free_withdraw (&got); free_withdraw (&wd));
509 : /* ... and names its reserve and its row */
510 1 : FAILIF_C (0 != GNUNET_memcmp (&got.reserve_pub,
511 : &reserve_pub),
512 : free_withdraw (&got); free_withdraw (&wd));
513 1 : FAILIF_C (0 == got.withdraw_id,
514 : free_withdraw (&got); free_withdraw (&wd));
515 1 : FAILIF_C (0 != GNUNET_memcmp (&got.reserve_pub,
516 : &reserve_pub),
517 : free_withdraw (&got); free_withdraw (&wd));
518 1 : FAILIF_C (0 != GNUNET_memcmp (&got.reserve_sig,
519 : &wd.reserve_sig),
520 : free_withdraw (&got); free_withdraw (&wd));
521 1 : FAILIF_C (1 != got.num_coins,
522 : free_withdraw (&got); free_withdraw (&wd));
523 1 : FAILIF_C (denom.serial != got.denom_serials[0],
524 : free_withdraw (&got); free_withdraw (&wd));
525 1 : FAILIF_C (got.no_blinding_seed,
526 : free_withdraw (&got); free_withdraw (&wd));
527 1 : free_withdraw (&got);
528 :
529 : /* a replay of the same request is idempotent and does not debit again */
530 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
531 : run_withdraw (pg,
532 : &wd,
533 : &st),
534 : free_withdraw (&wd));
535 1 : FAILIF_C (! st.idempotent,
536 : free_withdraw (&wd));
537 1 : FAILIF_C (1 != TDB_count (pg,
538 : "FROM withdraw"),
539 : free_withdraw (&wd));
540 1 : memset (&reserve,
541 : 0,
542 : sizeof (reserve));
543 1 : reserve.pub = reserve_pub;
544 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
545 : TALER_EXCHANGEDB_get_reserve (pg,
546 : &reserve),
547 : free_withdraw (&wd));
548 1 : FAILIF_C (0 != TALER_amount_cmp (&reserve.balance,
549 : &expect_left),
550 : free_withdraw (&wd));
551 1 : free_withdraw (&wd);
552 1 : return 0;
553 : }
554 :
555 :
556 : /**
557 : * Reusing a blinding seed for a different withdraw is refused, and the
558 : * reserve is left debited -- the caller has to roll back.
559 : *
560 : * @param pg the database context
561 : * @return 0 on success
562 : */
563 : static int
564 1 : check_nonce_reuse (struct TALER_EXCHANGEDB_PostgresContext *pg)
565 : {
566 : struct TALER_ReservePublicKeyP reserve_pub;
567 : struct TALER_EXCHANGEDB_Withdraw wd;
568 : struct WithdrawStatus st;
569 :
570 1 : TDB_reserve_in (pg,
571 : &account,
572 : 12,
573 : "10",
574 : &reserve_pub);
575 : /* a fresh planchet hash, but the blinding seed of check_withdraw() */
576 1 : make_withdraw (13,
577 : "5",
578 : &reserve_pub,
579 : true,
580 : &wd);
581 1 : TDB_fill (&wd.blinding_seed,
582 : sizeof (wd.blinding_seed),
583 : 11);
584 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
585 : run_withdraw (pg,
586 : &wd,
587 : &st),
588 : free_withdraw (&wd));
589 1 : FAILIF_C (! st.nonce_reuse,
590 : free_withdraw (&wd));
591 1 : FAILIF_C (st.idempotent,
592 : free_withdraw (&wd));
593 1 : free_withdraw (&wd);
594 : /* No withdraw row was written... */
595 1 : FAILIF (1 != TDB_count (pg,
596 : "FROM withdraw"));
597 : /* ...but the reserve was debited before the seed was checked, so the
598 : caller has to roll the transaction back. This is what the check is
599 : really about: the function is not safe to call outside a transaction. */
600 : {
601 : struct TALER_EXCHANGEDB_Reserve reserve;
602 1 : struct TALER_Amount debited = TDB_amount ("5");
603 :
604 1 : memset (&reserve,
605 : 0,
606 : sizeof (reserve));
607 1 : reserve.pub = reserve_pub;
608 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
609 : TALER_EXCHANGEDB_get_reserve (pg,
610 : &reserve));
611 1 : FAILIF (0 != TALER_amount_cmp (&reserve.balance,
612 : &debited));
613 : }
614 1 : return 0;
615 : }
616 :
617 :
618 : /**
619 : * A reserve with a birthday refuses a withdraw that commits to too high
620 : * an age.
621 : *
622 : * @param pg the database context
623 : * @return 0 on success
624 : */
625 : static int
626 1 : check_age_restriction (struct TALER_EXCHANGEDB_PostgresContext *pg)
627 : {
628 : struct TALER_ReservePublicKeyP reserve_pub;
629 : struct TALER_EXCHANGEDB_Withdraw wd;
630 : struct WithdrawStatus st;
631 : char *hex;
632 :
633 1 : TDB_reserve_in (pg,
634 : &account,
635 : 14,
636 : "10",
637 : &reserve_pub);
638 : /* born 20000 days after the epoch, i.e. in 2024 */
639 1 : hex = TDB_hex (&reserve_pub,
640 : sizeof (reserve_pub));
641 1 : FAILIF_C (GNUNET_OK !=
642 : TDB_exec (pg,
643 : "UPDATE reserves"
644 : " SET birthday=20000"
645 : " WHERE reserve_pub=decode('%s','hex');",
646 : hex),
647 : GNUNET_free (hex));
648 1 : GNUNET_free (hex);
649 :
650 1 : make_withdraw (14,
651 : "5",
652 : &reserve_pub,
653 : false,
654 : &wd);
655 : /* no age commitment at all, from a reserve that has a birthday */
656 1 : FAILIF_C (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
657 : run_withdraw (pg,
658 : &wd,
659 : &st),
660 : free_withdraw (&wd));
661 1 : free_withdraw (&wd);
662 1 : FAILIF (st.age_ok);
663 1 : FAILIF (20000 != st.reserve_birthday);
664 1 : FAILIF (1 != TDB_count (pg,
665 : "FROM withdraw"));
666 1 : return 0;
667 : }
668 :
669 :
670 : /**
671 : * The iterators see the withdraw, and the KYC view attributes it to the
672 : * account that funded the reserve.
673 : *
674 : * @param pg the database context
675 : * @return 0 on success
676 : */
677 : static int
678 1 : check_iterate (struct TALER_EXCHANGEDB_PostgresContext *pg)
679 : {
680 : struct TALER_HashBlindedPlanchetsP h;
681 : struct WithdrawContext ctx;
682 : struct AmountContext actx;
683 :
684 1 : TDB_FILL (h,
685 : 11);
686 1 : memset (&ctx,
687 : 0,
688 : sizeof (ctx));
689 1 : ctx.planchets_h = &h;
690 1 : FAILIF (1 !=
691 : TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg,
692 : 0,
693 : &withdraw_cb,
694 : &ctx));
695 1 : FAILIF (1 != ctx.matched);
696 1 : FAILIF (1 != ctx.num_denom_serials);
697 1 : FAILIF (denom.serial != ctx.denom_serial);
698 1 : FAILIF (! ctx.have_seed);
699 :
700 1 : memset (&ctx,
701 : 0,
702 : sizeof (ctx));
703 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
704 : TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg,
705 : 1000,
706 : &withdraw_cb,
707 : &ctx));
708 1 : FAILIF (0 != ctx.total);
709 :
710 1 : memset (&ctx,
711 : 0,
712 : sizeof (ctx));
713 1 : ctx.stop_after = 1;
714 1 : FAILIF (1 !=
715 : TALER_EXCHANGEDB_iterate_withdrawals_above_serial_id (pg,
716 : 0,
717 : &withdraw_cb,
718 : &ctx));
719 1 : FAILIF (1 != ctx.total);
720 :
721 : /* the KYC view groups by the account that funded the reserve */
722 1 : memset (&actx,
723 : 0,
724 : sizeof (actx));
725 1 : actx.ret = GNUNET_OK;
726 1 : FAILIF (1 !=
727 : TALER_EXCHANGEDB_iterate_withdraw_amounts_for_kyc_check (
728 : pg,
729 : &account.h_normalized,
730 : GNUNET_TIME_UNIT_ZERO_ABS,
731 : &amount_cb,
732 : &actx));
733 1 : FAILIF (5 != actx.value_sum);
734 :
735 : /* an account nobody withdrew against has nothing */
736 : {
737 : struct TALER_NormalizedPaytoHashP other;
738 :
739 1 : TDB_FILL (other,
740 : 77);
741 1 : memset (&actx,
742 : 0,
743 : sizeof (actx));
744 1 : actx.ret = GNUNET_OK;
745 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
746 : TALER_EXCHANGEDB_iterate_withdraw_amounts_for_kyc_check (
747 : pg,
748 : &other,
749 : GNUNET_TIME_UNIT_ZERO_ABS,
750 : &amount_cb,
751 : &actx));
752 1 : FAILIF (0 != actx.total);
753 : }
754 :
755 : /* a time limit in the future hides everything */
756 1 : memset (&actx,
757 : 0,
758 : sizeof (actx));
759 1 : actx.ret = GNUNET_OK;
760 1 : FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
761 : TALER_EXCHANGEDB_iterate_withdraw_amounts_for_kyc_check (
762 : pg,
763 : &account.h_normalized,
764 : GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS),
765 : &amount_cb,
766 : &actx));
767 1 : FAILIF (0 != actx.total);
768 1 : return 0;
769 : }
770 :
771 :
772 : /**
773 : * The checks to run, in order.
774 : */
775 : static const struct TDB_Test tests[] = {
776 : { "withdraw-unknown-reserve",
777 : &check_unknown_reserve },
778 : { "withdraw-insufficient-balance",
779 : &check_insufficient_balance },
780 : { "withdraw-withdraw",
781 : &check_withdraw },
782 : { "withdraw-nonce-reuse",
783 : &check_nonce_reuse },
784 : { "withdraw-age-restriction",
785 : &check_age_restriction },
786 : { "withdraw-iterate",
787 : &check_iterate },
788 : { NULL, NULL }
789 : };
790 :
791 :
792 : int
793 1 : main (int argc,
794 : char *const *argv)
795 : {
796 : int ret;
797 :
798 1 : ret = TDB_main (argc,
799 : argv,
800 : "test-withdraw",
801 : "Tests for the exchangedb `withdraw' table",
802 : tests);
803 1 : TDB_account_free (&account);
804 1 : TDB_denom_free (&denom);
805 1 : return ret;
806 : }
807 :
808 :
809 : /* end of test_withdraw.c */
|