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